Merge pull request #751 from davidgiven/tartu

Add support for the Tartu Palivere.
This commit is contained in:
David Given
2024-04-23 22:21:05 +02:00
committed by GitHub
14 changed files with 309 additions and 64 deletions

View File

@@ -136,6 +136,7 @@ choices because they can store multiple types of file system.
| [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦄 | 🦖 | ROLAND |
| [`rx50`](doc/disk-rx50.md) | Digital RX50: 400kB 5.25" 80-track 10-sector SSDD | 🦖 | 🦖 | |
| [`smaky6`](doc/disk-smaky6.md) | Smaky 6: 308kB 5.25" 77-track 16-sector SSDD, hard sectored | 🦖 | | SMAKY6 |
| [`tartu`](doc/disk-tartu.md) | Tartu: The Palivere and variations | 🦄 | | CPMFS |
| [`tids990`](doc/disk-tids990.md) | Texas Instruments DS990: 1126kB 8" DSSD | 🦖 | 🦖 | |
| [`tiki`](doc/disk-tiki.md) | Tiki 100: CP/M | | | CPMFS |
| [`victor9k`](doc/disk-victor9k.md) | Victor 9000 / Sirius One: 1224kB 5.25" DSDD GCR | 🦖 | 🦖 | |

View File

@@ -19,6 +19,7 @@ proto(
"./northstar/northstar.proto",
"./rolandd20/rolandd20.proto",
"./smaky6/smaky6.proto",
"./tartu/tartu.proto",
"./tids990/tids990.proto",
"./victor9k/victor9k.proto",
"./zilogmcz/zilogmcz.proto",

View File

@@ -1,6 +1,4 @@
syntax = "proto2";
import "lib/common.proto";
message Smaky6DecoderProto {}

84
arch/tartu/decoder.cc Normal file
View File

@@ -0,0 +1,84 @@
#include "lib/globals.h"
#include "lib/decoders/decoders.h"
#include "arch/tartu/tartu.h"
#include "lib/crc.h"
#include "lib/fluxmap.h"
#include "lib/decoders/fluxmapreader.h"
#include "lib/sector.h"
#include <string.h>
constexpr uint64_t HEADER_BITS = 0xaaaaaaaa44895554LL;
constexpr uint64_t DATA_BITS = 0xaaaaaaaa44895545LL;
static const FluxPattern HEADER_PATTERN(64, HEADER_BITS);
static const FluxPattern DATA_PATTERN(64, DATA_BITS);
const FluxMatchers ANY_RECORD_PATTERN {
&HEADER_PATTERN,
&DATA_PATTERN
};
class TartuDecoder : public Decoder
{
public:
TartuDecoder(const DecoderProto& config):
Decoder(config),
_config(config.tartu())
{
}
void beginTrack() override
{
}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord() override
{
if (readRaw64() != HEADER_BITS)
return;
auto bits = readRawBits(16 * 8);
auto bytes = decodeFmMfm(bits).slice(0, 8);
ByteReader br(bytes);
uint8_t track = br.read_8();
_sector->logicalTrack = track >> 1;
_sector->logicalSide = track & 1;
br.skip(1); /* seems always to be 1 */
_sector->logicalSector = br.read_8();
uint8_t wantChecksum = br.read_8();
uint8_t gotChecksum = ~sumBytes(bytes.slice(0, 3));
if (wantChecksum == gotChecksum)
_sector->status = Sector::DATA_MISSING;
_sector->status = Sector::DATA_MISSING;
}
void decodeDataRecord() override
{
if (readRaw64() != DATA_BITS)
return;
const auto& bits = readRawBits(129 * 16);
const auto& bytes = decodeFmMfm(bits).slice(0, 129);
_sector->data = bytes.slice(0, 128);
uint8_t wantChecksum = bytes.reader().seek(128).read_8();
uint8_t gotChecksum = ~sumBytes(_sector->data);
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
private:
const TartuDecoderProto& _config;
};
std::unique_ptr<Decoder> createTartuDecoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new TartuDecoder(config));
}

7
arch/tartu/tartu.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef TARTU_H
#define TARTU_H
extern std::unique_ptr<Decoder> createTartuDecoder(const DecoderProto& config);
#endif

4
arch/tartu/tartu.proto Normal file
View File

@@ -0,0 +1,4 @@
syntax = "proto2";
message TartuDecoderProto {}

View File

@@ -143,6 +143,7 @@ cxxlibrary(
"./arch/northstar/encoder.cc",
"./arch/rolandd20/decoder.cc",
"./arch/smaky6/decoder.cc",
"./arch/tartu/decoder.cc",
"./arch/tids990/decoder.cc",
"./arch/tids990/encoder.cc",
"./arch/victor9k/decoder.cc",
@@ -175,6 +176,7 @@ cxxlibrary(
"arch/micropolis/micropolis.h": "./arch/micropolis/micropolis.h",
"arch/c64/data_gcr.h": "./arch/c64/data_gcr.h",
"arch/c64/c64.h": "./arch/c64/c64.h",
"arch/tartu/tartu.h": "./arch/tartu/tartu.h",
"lib/a2r.h": "./lib/a2r.h",
"lib/bitmap.h": "./lib/bitmap.h",
"lib/bytes.h": "./lib/bytes.h",

43
doc/disk-tartu.md Normal file
View File

@@ -0,0 +1,43 @@
tartu
====
## The Palivere and variations
<!-- This file is automatically generated. Do not edit. -->
The Tartu Palivere is a 1988 Z80-based computer from Estonia. It is a CP/M
machine with 64kB of RAM, running off a 2MHz ꃣ0e30
clone; it operated off punched tape, cassette, external hard drive or floppy, and was notable as being the first ever computer with an Estonian keyboard.
<div style="text-align: center">
<img src="tartu.jpg" alt="The Tartu computer's developer Leo Humal working with one."/>
</div>
From a floppy disk perspective, it is interesting because the floppy drive
interface is almost entirely handled in software --- necessary at the time as
the usual floppy disk interface chip at the time, the ⎲fba5
of the WD1793), was hard to find. Instead, the floppy controller board was
implemented entirely using TTL logic. Despite this, the encoding is fairly high
density, using MFM and with up to 780kB on a double-sided 80 track disk.
<div style="text-align: center">
<img src="tartu-fdc.jpg" alt="The Tartu FDC with Soviet TTL logic chips."/>
</div>
FluxEngine supports reading Tartu disks with CP/M filesystem access.
## Options
- Format variants:
- `390`: 390kB 5.25" 40-track DSDD
- `780`: 780kB 5.25" 80-track DSDD
## Examples
To read:
- `fluxengine read tartu --390 -s drive:0 -o tartu.img`
- `fluxengine read tartu --780 -s drive:0 -o tartu.img`
## References
- [The Estonia Museum of Electronics](https://www.elektroonikamuuseum.ee/tartu_arvuti_lugu.html)

BIN
doc/tartu-fdc.jpg Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

BIN
doc/tartu.jpg Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@@ -18,6 +18,7 @@
#include "arch/northstar/northstar.h"
#include "arch/rolandd20/rolandd20.h"
#include "arch/smaky6/smaky6.h"
#include "arch/tartu/tartu.h"
#include "arch/tids990/tids990.h"
#include "arch/victor9k/victor9k.h"
#include "arch/zilogmcz/zilogmcz.h"
@@ -51,6 +52,7 @@ std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
{DecoderProto::kNorthstar, createNorthstarDecoder },
{DecoderProto::kRolandd20, createRolandD20Decoder },
{DecoderProto::kSmaky6, createSmaky6Decoder },
{DecoderProto::kTartu, createTartuDecoder },
{DecoderProto::kTids990, createTids990Decoder },
{DecoderProto::kVictor9K, createVictor9kDecoder },
{DecoderProto::kZilogmcz, createZilogMczDecoder },
@@ -89,7 +91,7 @@ std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
Fluxmap::Position recordStart = fmr.tell();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
return _trackdata;
break;
/* Read the sector record. */
@@ -108,28 +110,26 @@ std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
{
/* The data is in a separate record. */
for (;;)
_sector->headerStartTime = before.ns();
_sector->headerEndTime = after.ns();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
break;
before = fmr.tell();
decodeDataRecord();
after = fmr.tell();
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
pushRecord(before, after);
}
else
{
_sector->headerStartTime = before.ns();
_sector->headerEndTime = after.ns();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
break;
before = fmr.tell();
decodeDataRecord();
after = fmr.tell();
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
pushRecord(before, after);
break;
}
fmr.skipToEvent(F_BIT_PULSE);
resetFluxDecoder();
}
@@ -142,6 +142,8 @@ std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
_trackdata->sectors.push_back(_sector);
}
}
return _trackdata;
}
void Decoder::pushRecord(

View File

@@ -15,13 +15,14 @@ import "arch/mx/mx.proto";
import "arch/northstar/northstar.proto";
import "arch/rolandd20/rolandd20.proto";
import "arch/smaky6/smaky6.proto";
import "arch/tartu/tartu.proto";
import "arch/tids990/tids990.proto";
import "arch/victor9k/victor9k.proto";
import "arch/zilogmcz/zilogmcz.proto";
import "lib/fluxsink/fluxsink.proto";
import "lib/common.proto";
//NEXT: 32
//NEXT: 33
message DecoderProto {
optional double pulse_debounce_threshold = 1 [default = 0.30,
(help) = "ignore pulses with intervals shorter than this, in fractions of a clock"];
@@ -50,6 +51,7 @@ message DecoderProto {
NorthstarDecoderProto northstar = 24;
RolandD20DecoderProto rolandd20 = 31;
Smaky6DecoderProto smaky6 = 30;
TartuDecoderProto tartu = 32;
Tids990DecoderProto tids990 = 16;
Victor9kDecoderProto victor9k = 17;
ZilogMczDecoderProto zilogmcz = 18;

View File

@@ -34,6 +34,7 @@ formats = [
"rx50",
"shugart_drive",
"smaky6",
"tartu",
"tids990",
"tiki",
"victor9k",

100
src/formats/tartu.textpb Normal file
View File

@@ -0,0 +1,100 @@
shortname: 'Tartu'
comment: 'The Palivere and variations'
read_support_status: UNICORN
documentation:
<<<
The Tartu Palivere is a 1988 Z80-based computer from Estonia. It is a CP/M
machine with 64kB of RAM, running off a 2MHz КP580ВМ80А, a Soviet Union 8080
clone; it operated off punched tape, cassette, external hard drive or floppy, and was notable as being the first ever computer with an Estonian keyboard.
<div style="text-align: center">
<img src="tartu.jpg" alt="The Tartu computer's developer Leo Humal working with one."/>
</div>
From a floppy disk perspective, it is interesting because the floppy drive
interface is almost entirely handled in software --- necessary at the time as
the usual floppy disk interface chip at the time, the КR1818VG93 (a Soviet clone
of the WD1793), was hard to find. Instead, the floppy controller board was
implemented entirely using TTL logic. Despite this, the encoding is fairly high
density, using MFM and with up to 780kB on a double-sided 80 track disk.
<div style="text-align: center">
<img src="tartu-fdc.jpg" alt="The Tartu FDC with Soviet TTL logic chips."/>
</div>
FluxEngine supports reading Tartu disks with CP/M filesystem access.
>>>
documentation:
<<<
## References
- [The Estonia Museum of Electronics](https://www.elektroonikamuuseum.ee/tartu_arvuti_lugu.html)
>>>
image_writer {
filename: "tartu.img"
type: IMAGETYPE_IMG
}
layout {
layoutdata {
sector_size: 128
physical {
start_sector: 1
count: 39
}
filesystem {
start_sector: 1
count: 39
skew: 3
}
}
}
decoder {
tartu {}
}
option_group {
comment: "$formats"
option {
name: "390"
comment: '390kB 5.25" 40-track DSDD'
config {
layout {
format_type: FORMATTYPE_40TRACK
tracks: 40
sides: 2
}
}
}
option {
name: "780"
comment: '780kB 5.25" 80-track DSDD'
config {
layout {
format_type: FORMATTYPE_80TRACK
tracks: 80
sides: 2
}
}
}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 1
}
block_size: 2048
dir_entries: 128
}
}