Repository navigation
Architecture
Automatically synchronized from
doc/K1520_architecture.md. Edit the source file in the project repository.
Autor: Olaf Krieger
Stand: Oktober 2026 (erste Fassung Mai 2026)
Basis: TGL 37271/01, technische Dokumentation der emulierten Rechner und Karten
Wozu dieses Dokument. Es ist die Übersicht: wie Kern, Bibliotheken, Oberfläche und Werkzeuge zusammenhängen, welche Regeln für alle Maschinen gelten und wo die Einzelheiten stehen. Die Feinentwürfe je Baugruppe und Maschine liegen unter
doc/design/, die Festlegungen mit ihren Wächtern unterdoc/merkposten/. Wo sich beide widersprechen, gilt der jüngere Entwurf — und dieses Dokument ist zu berichtigen.Die Abschnittsnummern §5.1a/b, §5.3a, §8.5–§8.8 und §14 werden aus Quelltext und anderen Dokumenten zitiert und bleiben deshalb stabil.
Ein hardwarenaher, modularer Emulator für Rechner aus dem K1520-Bausteinsystem (DDR, 1980er Jahre) — und für die EAW P8000, die kein K1520-Rechner ist, aber genug Bausteine mit ihnen teilt, um im selben Kern zu leben.
| Rechner | Wesentliche Karten / Bausteine | Betriebssysteme | Entwurf |
|---|---|---|---|
| robotron A5120 | K2526 (ZRE, zwei Z80), K3526, K7024, K8025, K5122, Tastatur K7637 | CP/A, SCPX, UDOS | dieses Dokument, design/03–09 |
| A5120.16 | A5120 + Erweiterungsmodul EM064/EM256 mit U8001 | wie A5120 | design/17 |
| robotron K8915 V3 / V2 | ZRE 045-8762 bzw. K2521 + K3528, K7028, K7024, K5122, Tastatur K7672 | SCPX 8915 | design/16, design/24 |
| PRG 710 / PRG 710-1 | K2521, Speicherverwaltung, K7024, K5122, ATP 590068 (EPROMmer), ASS 590069 | UDOS 4.3, SCPX | design/20 |
| robotron PC 1715 / 1715W | eigene ZRE mit 8275-Bild, Tastatur mit eigenem U880, K5122-Bauart bzw. U8272 + Z80-DMA | SCP 1715, CP/A, CP/Z, UDOS 1715, SCP 3.0 | design/21 |
| EAW P8000 | 8-Bit-Karte (U880), 16-Bit-Karte (U8001 + 3 × U8010), WDC, Originalterminal (Z8) | UDOS, WEGA | design/25, 26, 28 |
Dazu Optionen, die in jede K1520-Maschine gesteckt werden können: RAM-Floppy RAF (design/22) und Lochstreifen K6022 (design/23).
Emulator-Oberfläche (app/) k1520DiskTool Werkzeuge (tools/)
a5120emu k8915emu prg710emu Oberfläche app/disktool/ k1520dbg boot_trace
pc1715emu p8000emu CLI k1520disktool floppy_diag format_driver
│ ctypes │ ctypes / C++ │ C++, statisch gelinkt
▼ ▼ │
libk1520core (C-ABI, §9) libk1520disk (C-ABI, §9) │
Maschinen · Karten · Bus · Dateisysteme · DiskVolume │
Primitive · Peripherie │ │
│ │ │
└──────────────┬──────────────────┘◄────────────────────────────┘
▼
gemeinsame statische Bibliotheken: Floppy-Schicht (§8), Formatkatalog,
Geometrieerkennung, Logger, Hilfsbibliotheken
-
libk1520coreist der Emulator: alle Maschinen hinter einer C-ABI (§9). -
libk1520diskist das DiskTool ohne Z80, Karten und Bus; es teilt mit dem Kern nur die Floppy-Schicht (§8.7a) und die Geometrieerkennung (§8.6.9). - Die Oberflächen sind Python/PySide6 um diese Bibliotheken herum (§10).
- Die Werkzeuge (§11) linken die statischen Kernbibliotheken direkt und sehen damit mehr als die C-ABI — etwa die ZVE2 des A5120 oder Save-States.
-
cparun/ist ein unabhängiges Unterprojekt (CP/M-Programme direkt auf dem Wirt) und teilt keinen Code.
Ein früherer monolithischer Emulator mit BIOS-Traps (src/) ist im August 2026 entfernt
worden; nichts hier hängt von ihm ab.
Der Emulator bildet echte Hardware nach — keine BIOS-Traps, keine Kenntnis des Betriebssystems. Boot-ROM, BIOS und Betriebssystem laufen als echter Maschinencode; dadurch bootet jedes System, das auf der echten Hardware läuft, einschließlich Formatieren mit den Originalprogrammen.
Der A5120 war die erste Maschine des Kerns; an ihm sind Bus, Primitive und Floppy-Schicht entstanden. Die übrigen Maschinen sind in §2.6 und in ihren Entwürfen beschrieben.
Der K1520 ist ein genormter Rückwandbus (TGL 37271/01), auf dem Karten mit Z80-kompatiblen Bausteinen stecken.
A5120 Rückwand (11 Steckplätze)
┌────────────────────────────────────────────────────────────────┐
│ Slot 1: K3526 (OPS) – Operationsspeicher 64 KB DRAM │
│ Slot 2: K5122 (AFS) – Anschlußsteuerung Folienspeicher (FDC) │
│ Slot 3: K8025 (ASS) – Anschlußsteuerung Seriell (V.24+IFSS) │
│ Slot 4: K2526 (ZRE) – Zentrale Recheneinheit │
│ Slot 5: K7024 (ABS) – Adapter Bildschirm (CRT-Controller) │
│ Slot 6-11: leer (erweiterbar) │
└────────────────────────────────────────────────────────────────┘
│ │
Systembus Koppelbus
(X1, 58-pol.) (X2, 58-pol.)
Dokumentierte Steckfolge des A5120.16:
1. K8025 (ASS) – serielle Schnittstellen
2. 062-9005 – Z8000-CPU-Karte
3. 062-9000 – Z8000-RAM-Karte
4. K2526 (ZRE) – Zentrale Recheneinheit
5. K7024 (ABS) – Bildschirm
6. K5122 (AFS) – Floppy-Controller
7. K3526 (OPS) – RAM
| Gruppe | Signale | Beschreibung |
|---|---|---|
| Datenbus | DB0–DB7 | 8-Bit bidirektional |
| Adressbus | AB0–AB15 | 16-Bit (bei /IORQ: AB8–AB15 = Register-Inhalt) |
| Steuerbus | /MREQ, /IORQ, /RD, /WR, /RFSH, /M1 | CPU-Zyklen |
| Steuerbus | /HALT, /BUSRQ, /INT, /NMI, /WAIT, /RDY | Synchronisierung |
| Steuerbus | /RESET, /MEMDI, /IODI | System |
| Kette | /IEI, /IEO | Interrupt-Prioritätskette (Daisy-Chain) |
| Kette | /BAI, /BAO | DMA-Prioritätskette |
| Takt | TAKT | 2.4576 MHz (407 ns Zyklus) |
Der Koppelbus (X2) überträgt Signale, die nicht zum Standard-Z80-Busprotokoll gehören und maschinenspezifisch verdrahtet werden. Im A5120 erfolgt die Verdrahtung über Wickelbrücken in der Backplane.
Koppelbus-Signale:
-
/IEI1,/IEO1– zweite Interrupt-Prioritätskette -
CLK/TRGO,CLK/TRG1,CLK/TRG2– CTC-Taktausgänge -
ZC/TO,ZC/TO1,ZC/TO2– CTC Zero-Count-Ausgänge -
/SUE– Spannungsüberwachung -
/MEMDI1,/MEMDI2– Speicherbereichsumschaltung -
/SA– Sonderausgang (Netzausschaltung) -
12N– −12V Versorgung
| Adressen | Karte | Funktion |
|---|---|---|
| 00H–07H | K2526 ZRE | U-Bus (Tastaturcode, Lampen), DMA-Reset |
| 08H–0BH | K2526 ZRE | BS-PIO (Steuerung, Schutz) |
| 0CH–0FH | K2526 ZRE | CTC Kanäle 0–3 |
| 10H–17H | K5122 AFS | Floppy Steuer-PIO + Daten-PIO |
| 18H | K5122 AFS | Laufwerksauswahl (8212) |
| 50H–53H | K8025 ASS | SIO A33 (DFÜ/Tastatur) |
| 54H–57H | K8025 ASS | Register A31 |
| 58H–5BH | K8025 ASS | CTC A34 |
| 5CH–5FH | K8025 ASS | SIO A32 (Drucker) |
| mem. | K7024 ABS | VRAM F800H–FFFFH (konfigurierbar) |
0000H–03FFH: Lade-ROM (1 KB, auf K2526, nach /RESET aktiv)
0400H–F7FFH: RAM (K3526, 64 KB DRAM)
F800H–FFFFH: VRAM (K7024, 2 KB, konfigurierbar)
| Maschine | Bus / Aufbau | Besonderheit im Kern | Merkposten |
|---|---|---|---|
| K8915 | K1520, eine CPU, 128 KB mit Speicherumschaltung A8H (V3) bzw. K2521 + K3528 (V2) | K5122 in der Betriebsart /WAIT ohne ZVE2; Bild nur über k1520_screen_char
|
k8915, k8915_varianten |
| PRG 710 / 710-1 | K1520, K2521, Seitenregister E8H–EBH | Variante wählt ROM, Tastaturweg (8279/K7609 bzw. K7672) und Marken-FF-Polarität; EPROMmer, Fernschreiber | prg710 |
| PC 1715 / 1715W | eigene Platine, kein Steckbus | ROM-Overlay, Bild aus dem Haupt-RAM über 8275; 1715W mit 256 KB, U8272 + Z80-DMA | pc1715 |
| P8000 | eigener Bus, kein K1520 | 8- und 16-Bit-Teil mit eigener Zeitbasis (laufeBis), MMU U8010, WDC mit Original-Firmware, Originalterminal mit Z8 |
p8000 |
Der PC 1715 und die P8000 haben keinen K1520-Steckplatz; RAF und K6022 lassen sich am
PC 1715 nur mechanisch gegenprüfen, an der P8000 gar nicht stecken
(K1520Machine::zusatzkartenSteckbar()).
k1520emu/
├── core/ C++-Kern
│ ├── bus/ K1520Bus (Speicher-/E-A-Verteilung, INT-Kette, BUSRQ, NMI, MEMDI)
│ │ + Koppelbus (benannte Signale der Rückwand)
│ ├── primitives/ Bausteine: Z80 (U880), Z80-PIO/-CTC/-SIO/-DMA, U8001 (z8000),
│ │ U8010 (z8010), Z8, 8275, 8279, µPD765/U8272, RAM/EPROM
│ ├── cards/ Steckkarten, je Karte ein Verzeichnis (§5.4)
│ ├── peripherals/ Geräte außerhalb des Busses: Tastaturen (k7637, k7672, k7609,
│ │ tastatur1715), floppy_drive/ (§8), winchester/, lochstreifen/,
│ │ p8000_terminal{,_hw}/
│ ├── machines/ machine.h (K1520Machine), laufwerke.*, je Maschine ein Verzeichnis
│ ├── filesystem/ Dateisysteme fürs DiskTool: cpm/, udos/, wega/, check/, DiskVolume
│ ├── serial/ SerialHub, Wandler, Telnet/RFC 2217/Datei (§16.1)
│ ├── api/ C-ABI: k1520_api (Emulator), k1520_disk_api (DiskTool),
│ │ k1520_sync_api (physische Diskette), k1520_export.h
│ ├── util/ yaml_lite, gzip_datei, os_compat, zustand
│ ├── logger.{h,cpp} Laufzeit-gesteuertes Protokoll (§12)
│ └── version.h.in Bauversion (§12)
├── app/ Python/PySide6: Emulator-Oberfläche (§10), disktool/, gw/
├── tools/ Debugger, Tracer, DiskTool-CLI, Disassembler, Gastprogramme (§11)
├── data/ formats.yaml (§8.6), default_config_*.yaml (Auslieferungszustand)
├── tests/ Testebenen unit/ debugtools/ integration/ cli/ system/ python/ (§13)
├── packaging/ verteilbares Paket, Inno-Setup
├── disks/ Beispieldisketten je <maschine>_<system>/
├── third_party/ miniz, isocline
├── cparun/ eigenständiges Unterprojekt
└── doc/ dieses Dokument, design/, merkposten/, Analysen, EPROM-Abzüge
Jede Karte wird nach ihrer technischen Dokumentation implementiert. Ergebnis: jedes Betriebssystem, das auf der echten Hardware läuft, läuft auch im Emulator — ohne spezifische Kenntnis dieses Systems.
| BIOS-Trap-Emulator | Dieser Kern |
|---|---|
| HALT-Trap → BIOS-Funktion in C++ | E/A-Port → Register der Karte |
| nur das eine System bootbar | jedes System der Maschine bootbar |
| BIOS nachprogrammiert | BIOS läuft als Maschinencode |
| monolithisch | modular, Karte = eigene Klasse |
Jede Steckkarte ist eine in sich geschlossene C++-Klasse, die
- sich am Bus anmeldet (E/A- und Speicherbereiche, Glied der Interruptkette),
- über den Koppelbus Signale ausgibt und empfängt,
- Brücken und DIP-Schalter als Konfigurationsstruktur zur Bauzeit bekommt
(z. B.
K2526::A5120Config), nicht als Laufzeiteinstellung, - EPROM-Inhalte als eingebettete Datenfelder trägt (§4.4).
Jede Karte ist eine eigene statische Bibliothek (k1520_k5122, k1520_k7024, …) und
damit für sich testbar; libk1520core linkt alle zusammen.
Der Kern ist befehls- bzw. transaktionsgenau:
- die CPU führt einen Befehl als Ganzes aus; Karten reagieren sofort auf den Buszugriff,
- nach jedem Befehl bekommen Zähler, Controller und Geräte die verstrichenen Takte
(
clockTick(n),takt(n),advance(n)) — Maschinenzeit, nicht Wirtszeit, - keine Laufzeiten und Bus-Timings auf Signalebene, aber die Interruptkette ist reihenfolgetreu.
Wo Zeit für das Gastsystem sichtbar ist (Indexpuls, Baudrate, Wartezyklen der EPROMs, Tastaturwiederholung), wird sie in Maschinentakten gerechnet. Mehrere Prozessoren einer Maschine werden in fester Reihenfolge bis zur selben Maschinenzeit nachgezogen (A5120.16 §5.1b, P8000 in design/25 §10.2) — der Lauf bleibt deterministisch. Was der Rechenaufwand dabei kostet und welche Regeln daraus folgen: §5.3a.
// Erzeugt aus: tools/eprom_to_h.py doc/EPROMS/K7024/...
#pragma once
#include <cstdint>
static constexpr uint8_t CHARGEN_ZG1[1024] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Zeichen 0x00
// ...
};Boot-ROMs, Zeichengeneratoren und Gerätefirmware (WDC, Originalterminal) werden so eingebaut, nicht zur Laufzeit geladen. Ausnahme ist der EPROMmer des PRG 710, dessen Sockel ein Gerät ist und Dateien liest.
┌─────────────────────────────────────────────────────────────────┐
│ Schicht 4: Maschinen │
│ K1520Machine ← A5120 · K8915 · PRG 710 · PC 1715 · P8000 │
│ Karten anlegen, Koppelbus verdrahten, Laufschleife │
├─────────────────────────────────────────────────────────────────┤
│ Schicht 3: Peripherie │
│ Tastaturen · Laufwerke (§8) · Winchester · Terminal · SerialHub│
├─────────────────────────────────────────────────────────────────┤
│ Schicht 2: Steckkarten │
│ K2526 · K3526 · K7024 · K8025 · K5122 · K7028 · EM · RAF · … │
├─────────────────────────────────────────────────────────────────┤
│ Schicht 1: Bausteine (Primitive) │
│ Z80 · PIO · CTC · SIO · DMA · U8001 · U8010 · Z8 · 8275 · … │
├─────────────────────────────────────────────────────────────────┤
│ Schicht 0: Bus │
│ K1520Bus (Systembus) · Koppelbus (Signal-Router) │
└─────────────────────────────────────────────────────────────────┘
Jede Schicht kennt nur die darunter. Die Floppy-Schicht unterhalb von DiskImage kennt
überhaupt keine Maschine (§8.7a).
Karten melden sich am Bus an und bekommen Rückrufe für ihre Bereiche. Die Verteilung läuft
über eine Seitentabelle (rebuildPageTable), sodass ein Speicherzugriff keinen
Bereichsvergleich kostet.
class K1520Bus {
public:
// Anmeldung
void registerIO(BusDevice* dev, uint8_t basePort, uint8_t numPorts = 1);
void registerMem(MemDevice* dev, uint16_t base, uint16_t size);
void unregisterMem(MemDevice* dev);
void addMemdiDriver(MemdiDriver* dev); // /MEMDI je Zugriff (§5.1a)
// Zugriffe der CPU — E/A mit voller Adresse AB0–15 (§5.1a)
uint8_t memRead(uint16_t addr); void memWrite(uint16_t addr, uint8_t data);
uint8_t ioRead(uint16_t addr); void ioWrite(uint16_t addr, uint8_t data);
// Interruptkette (Reihenfolge = Priorität) und Annahme
void setInterruptChain(std::initializer_list<InterruptSlave*> chain);
void appendInterruptChain(InterruptSlave* dev);
uint8_t interruptAcknowledge(); void signalRETI();
// Bussignale
void assertINT(); void releaseINT();
void assertNMI(); void clearNMI();
void assertWAIT(); void releaseWAIT(); void addWaitCycles(int n);
void assertBUSRQ(); void releaseBUSRQ();
void assertRESET();
void setIODI(bool disabled);
};Die Interruptkette wird nur neu bewertet, wenn ein Glied sich als geändert meldet
(markIntDirty). Der A5120 nutzt zusätzlich setBusMasterZVE2 für die zweite CPU der
K2526 (§14.5).
Zwei Leitungen, die der Emulator früher in einem Flag zusammenwarf:
-
Bus-/MEMDI (X1 C09) ist die Speicherprioritätenkette. Ein
MemdiDriver(heute nur das Erweiterungsmodul EM064/EM256,core/cards/em/) entscheidet je Zugriff allein aus der Adresse, ob er den Zyklus übernimmt; dann liefert ER das Byte, und eine K3526-Gruppe mitmemdi_source = falseverwirft den Schreibzyklus. Beim EM ist das MEN = ¬(8/16 ∧ RAMEN ∧ PEN) (A310/12 → A112 → A17/11, Scan 9005/2), nicht PEN allein. -
MEMDI1/2 der Rückverdrahtung ist ein statischer Pegel vom BS-PIO A7
(
K2526::onMemdi12→ Koppelbus). Darauf hört nur eine Gruppe mitmemdi_source = true— am A5120 keine. Deshalb läuft HARDYs MEMDI-Test durch.
Ohne angemeldeten Vorrangspeicher kostet das im Speicherpfad einen Vergleich. Die
E/A-Zyklen tragen seit demselben Umbau die volle Adresse AB0–15 (ioAddress()),
weil der Attributspeicher des EM256 über AB12–15 adressiert wird (OUT (n),A legt
dabei A auf AB8–15).
Der U8001 (Z8000, core/primitives/z8000.h) sitzt auf der EM-Karte (EM::u8001())
und sieht nur den EM-Speicher und die Register A33/A35/A34/A36 — die Karte
übersetzt jeden seiner Buszyklen (Status ST3..0, N/S, SN) über Segmentweiche A42 und
Umschalter A41 in eine DRAM-Zelle (EM::cellFor16, dieselbe Zelle wie beim U880,
big-endian). Zeit: nach jedem Schritt des U880 (bzw. ZVE2/WAIT-Takt) ruft die
Maschine EM::advance(n); die Karte zieht den U8001 befehlsweise im Verhältnis
f16/f8 (4 MHz / 2,45 MHz, EM::Config) nach und führt den Rest als Guthaben. Im
Reset, bei abgegebenem Bus (BUSAK bei anliegendem BUSRQ) und im Stop wird die Zeit
nur verbucht. Jeder Befehl des U880 beginnt mit EM::onU880M1() — der Takt des FF
A29 (8/16). Der U880 läuft im 16-Bit-Mode weiter (Handbuch §1.7.3); BUSRQ/BUSAK
ist der Handschlag zwischen Karte und U8001. Ohne EM (Config::em = none) fällt jeder
dieser Aufrufe weg — der Lauf ist bitgleich dem A5120. Save-State v6 hängt den
EM-Block (DRAM, A22, PIO, Register, U8001 mit runState()) mit Kennbyte an den
Geräteteil an.
Debug-Anschluss (S5): EM::setStepHook wird vor jedem Schritt des U8001 gerufen
(nicht, solange er geparkt ist); liefert er true, kehrt advance sofort zurück und die
Zeit bleibt als Guthaben stehen — so hält k1520dbg den U8001 vor dem Befehl an.
EM::setEventHook meldet jede Kommunikationstransaktion (PIO A32, A33–A36, A22,
Quittungen) und jeden Pegelwechsel (FF A29, INT-16, TREN, BUSRQ/BUSAK, RESET16, NVI,
STOP) als EM::EreignisInfo; Text dazu in tools/em_trace.h (boot_trace --em,
k1520dbg emlog). Ohne Rückruf kostet jede Stelle einen Test. Nach aussen:
k1520_create_with_em, k1520_em_variant, k1520_em_led_v1/_v2, k1520_em_mode16,
k1520_em_state (K1520EmState: Register, FCW, PSAP, Kartenzustand).
Der Koppelbus (core/bus/koppelbus.h) bildet die maschinenspezifisch verdrahteten
Leitungen der Rückwand nach. Er ist kein Bus im eigentlichen Sinn, sondern ein
Signal-Router: eine Karte treibt eine benannte Leitung (drive(pegel)), andere hängen
sich mit einem Rückruf daran (connect(...)). Verdrahtet wird in der Maschine, beim A5120
im Konstruktor von A5120Machine:
// core/machines/a5120/a5120.cpp (Auszug)
koppel_.zc_to[ch].drive(lvl); // ZRE-CTC gibt ZC/TO aus
koppel_.zc_to[2].connect([this](bool lvl) { ... }); // Taktkaskade
koppel_.memdi1.connect([this, g](bool level) { ops_.setMemDI(g, !level); });Die zweite Interruptkette (/IEI1–/IEO1) ist als Leitung vorhanden, aber nicht verdrahtet (§14.7).
Die Primitive sind generische Nachbildungen der Bausteine, unabhängig von jeder Karte. Grundsatz seit der P8000: ein Baustein wird vollständig und systematisch getestet, nicht nur so weit, wie die erste Karte ihn braucht, und die Debugger unterstützen ihn ganz.
| Baustein | Datei | Benutzt von |
|---|---|---|
| Z80 / U880 | z80.{h,cpp} |
allen Maschinen, auch im WDC der P8000 |
| Z80-PIO / U855 | z80_pio.* |
allen Maschinen |
| Z80-CTC / U857 | z80_ctc.* |
allen Maschinen (Rechenaufwand §5.3a) |
| Z80-SIO / U856 | z80_sio.* |
K8025, K7028, PC 1715, P8000 |
| Z80-DMA / U858 | z80_dma.* |
PC 1715W, P8000 |
| U8001 (Z8000) |
z8000.*, z8000/
|
EM256 (A5120.16), P8000 16-Bit-Karte |
| U8010 (MMU) | z8010.* |
P8000 |
| Z8 |
z8.*, z8/
|
P8000-Originalterminal |
| 8275 (CRT-Controller) | i8275.* |
PC 1715, P8000-Originalterminal |
| 8279 (Tastatur/Anzeige) | i8279.* |
PRG 710 (K7609, ATP 590068) |
| µPD765 / U8272 | upd765.* |
PC 1715W, P8000 |
| RAM- / EPROM-Block |
ram_device.h, eprom_device.h
|
Karten |
Die Z80 ruft den Bus über Rückrufe; sie hält einen Taktzähler und kann für den Debugger
vor einem Befehl anhalten (abortBeforeExecute).
Befund (Callgrind, Release + -g). Der Kern, nicht die Oberfläche, begrenzt das
erreichbare Vielfache der Echtzeit: der Emulationsfaden (app/emulationstakt.py, 2-ms-Scheiben)
läuft bei Überlast zu 100 %, der GUI-Faden braucht ≈ 6 %, es gibt kein Busy-Wait. Im Kern lag
der größte Posten bei allen Maschinen im Z80CTC: A5120 ≈ 32 %, K8915 ≈ 45 %, P8000
≈ 62 %. Ursache: Baudraten-Kanäle (Zeitgeber, ZK klein, z. B. Steuerwort 47H/ZK 2) erzeugen
alle paar Takte einen Nulldurchgang; die alte Fenster-Vorprüfung in clockTick(int) sah darum
fast immer ein Ereignis und fiel auf „ein clockTick() je Takt" zurück — und die Karten rufen
clockTick(n) nach jedem Befehl mit n = 4…20.
Zwei Stufen im CTC (core/primitives/z80_ctc.{h,cpp}), beide bitgleich zum Einzeltakten:
-
Sprung bis vor die nächste BEOBACHTETE Flanke (
springe). Beobachtet ist ein Kanal, wenn seine ZC/TO-Flanke eine Wirkung hat: Interrupt freigegeben, Trigger-Betrieb (CTRL_CLK_TRG,fireZCTOhält ihn an) oder Bit in der Rückruf-Maske (setZCTOCallback(cb, kanalmaske),zcto_beobachtet(i)). Unbeobachtete Kanäle werden rein arithmetisch fortgeschrieben (Prescaler/Zähler, Nachladen per Modulo); nur die Flanke eines beobachteten Kanals läuft als exakter Einzeltakt. Der Zustand am Fensterende ist byte-gleich, auchzcto_active(Save-State). Prescaler ≥ Teiler bzw. Bruchtakt ≥ φ fallen auf Einzeltakte zurück. -
Aufgeschobenes Takten (
clockTick(int)inline im Header): die Takte werden nur aufsummiert (aufgeschoben_); verrechnet wird erst, wenn die Summe den Horizont (berechneHorizont(): Takte bis vor die nächste beobachtete Flanke) überschreitet — dann fällt die Flanke samt Dirty-Meldung im selbenclockTick-Aufruf wie vorher, also im selben Befehl — oder wenn ein Zugriff den Zustand sieht.
Zugriffsdisziplin (nicht aufweichen): jede Methode, die CTC-Zustand liest oder schreibt,
ruft vorher sichtbar() — heute ioRead, ioWrite, clkTrg, getCount, debugState,
serialize/serializeTakt, reset, setZCTOCallback, setzeEingangsTakt, das einzelne
clockTick(). Nach deserialize* werden die angesammelten Takte verworfen; nach jeder
Neuprogrammierung ist der Horizont 0 (wird beim nächsten Takt neu bestimmt). Wer dem CTC
einen neuen Zugriffsweg gibt, muss sichtbar() rufen, sonst sieht er einen veralteten Zähler.
Rückruf-Maske je Maschine — Vorgabe 0x0F (alle beobachtet) ist die sichere Wahl;
verkleinert nur, wo geprüft ist, dass niemand sonst die Flanke abnimmt (Kaskade, Koppelbus,
SIO-Takt als Flanke):
| Maschine / Karte | Maske | Grund |
|---|---|---|
| A5120 (K2526) | 0x07 |
der Koppelbus kennt nur ZC/TO0…2 |
| PRG 710 | 0x01 |
nach außen geht nur ZC/TO0 |
| K2521 (PRG 710, K8915 V2) | aus Kaskaden + äußerem Rückruf (zctoVerdrahten()) |
Kaskade K2→K3 hängt als Rückruf an K2 |
| PC 1715W | 0x02 |
nur K1 → K2 (Uhr) |
| P8000 Karte16, WDC | 0x04 |
nur K2 wird ausgewertet |
| K7028, PC-1715-ZRE, P8000 Karte8 | — | kein Rückruf ⇒ nichts per Maske beobachtet |
Kleinere Posten: Z80PIO::pins/treiber und Z80SIO::Channel::format stehen inline im
Header; Z80CTC::teilerTakteQ16 merkt sich die letzte 64-Bit-Division.
Wirkung (headless über die C-ABI, Mio. Takte je Sekunde, gleicher Rechner):
| Maschine | vorher | nachher |
|---|---|---|
| A5120 (CP/A-Prompt) | 66 | 135 (≈ 54× bei 2,5 MHz) |
| K8915 | 39 | 114 |
| PRG 710 | 100 | 125 |
| PC 1715 | ≈ 83 | 94 |
| P8000 (WEGA, Originalterminal + WDC) | 12,6 (3,2×) | 47 (11,8×) — dazu §10.2a im Entwurf 25 |
Nachweis der Bitgleichheit: Hashes von RAM + Bild nach 200–400 Mio. Takten vorher/nachher
gleich (A5120, K8915, PRG 710, PC 1715); P8000-Save-State nach 100 Mio. Takten cmp-gleich.
Wächter: CtcSprung.* (tests/unit/primitives/test_ctc_sprung.cpp: 400 Zufallskonfigurationen
Sprung == Einzeltakt inkl. Kaskade, Zählerlesen, Neuprogrammierung, Kanal-Reset;
AufgeschobenesTaktenGleichEinzeltaktenBeiZugriffenDazwischen mit 300 × 3000 Fenstern und
zufälligen Zugriffen dazwischen). Messen für den A5120: tools/dev.sh tool bench_run.
Jede Karte besteht aus einer Klasse, die sich an Bus und Koppelbus anmeldet, den Bausteinen,
die sie intern benutzt, und einer Config-Struktur für Brücken und Schalter.
| Karte | Verzeichnis | Maschinen |
|---|---|---|
| K2526 ZRE (zwei Z80, Q240-Schutz, Boot-ROM) | k2526/ |
A5120 |
| K3526 OPS (64 KB) | k3526/ |
A5120 |
| K7024 ABS (Bildschirm) | k7024/ |
A5120, K8915, PRG 710 |
| K8025 ASS (seriell) | k8025/ |
A5120, PRG 710 |
K5122 AFS (Floppy, Betriebsarten /BUSRQ und /WAIT) |
k5122/ |
A5120, K8915, PRG 710, PC 1715 (Portlage::Pc1715) |
| EM064/EM256 (U8001) | em/ |
A5120.16 |
| ZRE 045-8762 (CPU + 128 KB) | zre8762/ |
K8915 V3 |
| K2521 ZRE · K3528 RAM |
k2521/, k3528/
|
K8915 V2, PRG 710 |
| K7028 ATS (2 × SIO, 2 × CTC) | k7028/ |
K8915 |
| Speicherverwaltung · ATP 590068 · ASS 590069 |
prg710_speicher/, atp590068/, ass590069/
|
PRG 710 |
| PC-1715-ZRE · 1715W-Speicher/Bild |
pc1715_zre/, pc1715w_speicher/
|
PC 1715 / 1715W |
| 8-/16-Bit-Karte, MMU-Logik, DRAM, Floppy, Kopplung, WDC | p8000/ |
P8000 |
| RAF 128/512/2M | raf/ |
Option, alle K1520-Maschinen |
| K6022 (Lochstreifen) | k6022/ |
Option, alle K1520-Maschinen |
| K3820 PFS (Festwertspeicher) | k3820/ |
keine — liegt für spätere Geräte bereit |
Die Einzelheiten jeder Karte stehen in ihrem Feinentwurf; die K5122 hat mit design/07 den umfangreichsten.
Alle Maschinen leiten von K1520Machine (core/machines/machine.h) ab. Diese Klasse
enthält genau das, was die C-ABI braucht: Lebenslauf (powerOn, reset, run,
stop, nmi), Bild, Tastatur, Disketten, serielle Schnittstellen, Optionen,
Diagnosezugriffe. Was einer Maschine eigen ist (ZVE2 und Save-States des A5120,
Speicherumschaltung des K8915, Platten der P8000), bleibt in der konkreten Klasse und wird
von den Werkzeugen direkt benutzt.
K1520Machine (abstrakt)
├── A5120Machine core/machines/a5120/ (+ EM über Config::em)
├── K8915Machine core/machines/k8915/ (Config::generation = V3 | Gen2)
├── Prg710Machine core/machines/prg710/ (Variante 710 | 710-1)
├── Pc1715Machine core/machines/pc1715/ (Variante PC1715 | PC1715W)
├── P8000Machine core/machines/p8000/ (Ausstattung, Terminal, WDC)
└── P8000TerminalMachine core/machines/p8000/ (nur Originalterminal, ohne Rechner)
Laufwerke (core/machines/laufwerke.*) ist der gemeinsame Baustein für alles, was
an der K5122 und ihren vier Laufwerken hängt — Einlegen, Anlegen, Speichern unter,
Formaterkennung, Schreibschutz, Autosave. Die Maschinen reichen nur durch; auch die
Floppy der P8000-8-Bit-Karte benutzt ihn.
Anlegen und Bestücken. Die Maschine wird über die C-ABI erzeugt
(k1520_create, k1520_create_configured, k1520_create_k8915, k1520_create_prg710,
k1520_create_pc1715[_ex], k1520_create_p8000[_terminal], k1520_create_with_em).
Optionen werden nach dem Anlegen und vor dem ersten Lauf gesteckt
(installRaf, installK6022), damit es nicht für jede Kombination eine eigene
Erzeugerfunktion braucht.
Laufschleife. run(max_cycles) führt Befehl für Befehl aus und gibt nach jedem Befehl
die verbrauchten Takte an Zähler, Controller, Tastatur und SerialHub weiter (§4.3).
Eingaben von außen (Tasten, NMI, Anhalten) werden fadensicher eingereiht und in run()
verbraucht. Die Oberfläche ruft run aus ihrem eigenen Faden (§10.2).
Save-State. Der A5120 (Format v7, mit EM-Block) und die P8000 (P8KS v4) können ihren Zustand speichern und laden; der Medieninhalt gehört nicht dazu.
| Maschine | Erzeugt von | Zugang |
|---|---|---|
| A5120, K8915, PRG 710 | K7024: VRAM + Zeichengenerator → Pixelbild 640 × 288 |
k1520_framebuffer, k1520_screen_char
|
| PC 1715 / 1715W | 8275 liest den Bildspeicher per DMA aus dem Haupt-RAM bzw. dem Bild-RAM, Pixelbild 640 × 300 | ebenso |
| P8000 | Originalterminal: Z8-Firmware + 8275 im Terminal |
k1520_term_framebuffer, k1520_term_*
|
Der Text wird nie über k1520_mem_read gelesen: beim K8915 überdeckt die
Speicherumschaltung den Bildspeicher, beim PC 1715 und der P8000 liegt er gar nicht im
Adressraum der Haupt-CPU. k1520_screen_char fragt die Bildschirmkarte direkt.
Die K7024 nutzt zwei Zeichen-EPROMs: chargen_zg1.h (Pixelzeilen 0–7) und
chargen_zg2.h (Zeilen 8–11); Einzelheiten in design/05.
Der Kern liefert ein monochromes Pixelbild (8 Bit je Punkt, Stufen für Halbhell). Alles
Weitere — Phosphorfarbe, Nachleuchten, Krümmung, Rasterlinien — macht die Oberfläche in
einem GLSL-Shader (app/ui/screen_widget.py, QOpenGLWidget, eingestellt über CRTParams).
Die Emulation weiß davon nichts.
Für Tests, Werkzeuge und die Zwischenablage gibt es den Bildinhalt als Text:
k1520_screen_char(col, row) je Zeichen, k1520_console_poll für geänderte Zeichen,
beim P8000-Terminal k1520_term_snapshot und eine Mitschrift (k1520_term_log_*).
| Maschine | Tastatur | Anschluss |
|---|---|---|
| A5120 | K7637 | seriell an K8025 SIO A32, 9600 Bd |
| K8915 | K7672 (Z8 in der Tastatur, SCP/DCP) | seriell an K7028 |
| PRG 710 | K7609 | 8279 auf der ATP 590068 |
| PRG 710-1 | K7672 | K8025 A32-B |
| PC 1715 | eigene Tastatur mit U880 + S600 | seriell an SIO-A der ZRE |
| P8000 | K7673.09 | am Originalterminal (Z8) |
Alle Tastaturen sind Peripherie, keine Busgeräte; sie liefern die physischen Codes der
echten Tastatur (nicht ASCII), die das Gastsystem selbst umsetzt. Die Oberfläche übersetzt
Wirtstasten über eine Tabelle je Tastatur und hat für jede eine Bildschirmtastatur
(app/ui/keyboard_*.py).
Tastaturbytes werden nach der Zeichenzeit der Schnittstelle freigegeben, in Maschinentakten
(service(now_cycles)). Beim K7637 war das die Lösung für Doppel- und Fehleingaben: ohne
die 9600-Baud-Latenz konkurrierten der Tastatur-Scan der Timer-ISR und die LED-Quittung um
dasselbe Empfangsbyte (design/08).
Die Tastenwiederholung zählt in der Oberfläche in Echtzeit (setKeyRepeatRealtime):
der Rechnertakt ist einstellbar, die Tastatur hat ihren eigenen Quarz. In Tests und
Werkzeugen bleibt sie in Maschinentakten, damit Läufe wiederholbar sind.
Signalkette, Codetabellen und die Kommandos des Rechners an die Tastatur (Reset, LED, Signalton) stehen in design/08; der Weg der Zeichen im A5120-BIOS (25-ms-Timer-ISR, Puffer, CONIN) ebenda.
K5122 (Karte) core/cards/k5122/
│ PIO-Register 10H–18H, /STR /ST MK MK1, Index aus der Drehzahl
▼
FloppyDriveV2 — DriveProfile + Kopfposition + Spurdichte-Übersetzung
▼
DiskImage — DiskMedium (alle Spuren im Speicher) + Bindung + Autosave
├── Datei: ImageCodec (.img | .hfe | .dmk) §8.7
└── echte Diskette: TrackSync + Arbeitsfaden (Greaseweazle) §8.8
▼
TrackImage · TrackCodec · BitCodec §8.5
Die Geschichte des Stapels in Kürze: zuerst ein Controller, der Sektoren nachbildete (§8.3/§8.4 beschreiben dessen Portprotokoll, das unverändert gilt); dann der formatagnostische Lesekopf (§8.5); dann der Formatkatalog in YAML (§8.6); dann das interne Medium mit Container-Codecs (§8.7); zuletzt die echte Diskette als zweite Bindung (§8.8). Wo ältere Abschnitte etwas als abgelöst kennzeichnen, gilt der jüngere.
Die P8000 nutzt die Floppy-Schicht ebenfalls (Floppy der 8-Bit-Karte); ihre Winchester-Platte ist ein eigenes Gerät (§8.9).
Historisch. Hier stand ein
cpaFormates.cfg-artiges Format, das nie produktiv benutzt wurde. Die Formate stehen seit August 2026 indata/formats.yaml(§8.6).
Die K5122 verwendet zwei Z80-PIO-Bausteine:
- Steuer-PIO (10H–13H): Steuersignale (Schritt, Kopf laden, Schreibfreigabe, Marken)
- Daten-PIO (14H–17H): Datenbytes beim Lesen und Schreiben
- Laufwerksauswahl (18H, 8212): High-Nibble = /SE (Auswahl), Low-Nibble = /LCK (Motor), beide low-aktiv
Im A5120 holt die ZVE2 die Bytes unter /BUSRQ ab (§14.5); K8915, PRG 710 und PC 1715
fahren die zweite Betriebsart /WAIT mit nur einer CPU. Vollständiges Modell:
design/07.
// Laufwerk A: Abbild einlegen. Der Formatname zählt nur bei .img; bei .hfe/.dmk steht
// die Geometrie in der Datei, verlangt wird trotzdem ein gültiger Katalogname
// (die Oberfläche gibt den Laufwerksstandard, k1520_drive_default_format).
k1520_mount_disk(h, 0, "/pfad/cpadisk.hfe", "cpa800", false);
// Laufwerk B: leere, unformatierte Diskette in Laufwerksgeometrie anlegen
k1520_create_disk(h, 1, "/pfad/neu.hfe", "", false);Laufwerksbestückung — welche Laufwerkstypen in den vier Anschlüssen stecken, wird beim Erzeugen der Maschine festgelegt:
K1520Handle h = k1520_create(K1520_MACHINE_A5120); // 4 × K5601
K1520Handle h2 = k1520_create_configured(K1520_MACHINE_A5120,
"MF3200", NULL, NULL, NULL);| Profil | Zoll | Spuren × Köpfe | Verfahren | Kapazität |
|---|---|---|---|---|
K5601 (Vorgabe) |
5,25″ | 80 × 2 | MFM + FM | 800 KB |
K5600.10 |
5,25″ | 40 × 1 | MFM | 200 KB |
K5600.20 |
5,25″ | 80 × 1 | MFM + FM | 400 KB |
MF3200 |
8″ | 77 × 1 | nur FM | ~300 KB |
MF6400 |
8″ | 77 × 1 | FM + MFM | ~600 KB |
none |
— | — | — | leerer Anschluss |
Das K5602 ist zum MF3200 voll kompatibel und hat kein eigenes Profil; frühere technische
Namen (ss_525_40, …) werden als Alias aufgelöst. Das Verfahren (FM/MFM) ist
Laufwerksvorgabe (DriveProfile::default_read_encoding), das Gastsystem schaltet per
Steuerwort um (0x85 = MFM / 0x87 = FM).
Passt eine Diskette nicht zur Spurdichte des Laufwerks (40 Spuren im 80er-Laufwerk und
umgekehrt, zweiseitig im einseitigen), wird sie übersetzt statt abgewiesen
(FloppyDriveV2::mount, mediumCylinder()); die Einschränkung meldet k1520_disk_notice.
Einzelheiten: design/09 §7.2.
Laufwerkstyp = Eigenschaft des BIOS, keine Grenze der K5122. Welche Formate die Formatierprogramme anbieten, entscheidet der Diskettenparameterblock des BIOS; die K5122 streamt formatagnostisch Bits. Deshalb lassen sich mit Combo-Boot-Disketten alle Laufwerkstypen-Menüs ohne echte Hardware abgreifen (format.md).
Teilweise abgelöst durch §8.7 (2026-08-05). Controller-Modell,
TrackImage,TrackCodec,BitCodecund der Boot-Lesepfad gelten unverändert. Die hier beschriebene Backend-Schicht (DiskImageabstrakt,RawSectorImage/HfeImageals dateigebundene Unterklassen, Spur-Cache inFloppyDriveV2) ist durch das interne Medium + Container-Codecs ersetzt.
Die K5122 (core/cards/k5122/) ist der formatagnostische Floppy-Controller (einzige
Floppy-Karte) auf der Peripherie-Schicht core/peripherals/floppy_drive/. Sie modelliert einen
Lesekopf über der rotierenden Spur und kennt keine Sektorgrößen/CRCs/Boot-Stadien mehr. (Sie
ersetzte eine ältere monolithische On-the-fly-Synthese-K5122 — die §8.1–§8.4 beschreiben deren
PIO-Protokoll, das auf Port-/Signalebene unverändert gilt; das dort skizzierte karten-interne
Synthese-Modell ist durch den TrackImage-Stack ersetzt.) Vollständiges Modell:
doc/design/07_k5122_afs.md.
K5122 (Controller-Karte) core/cards/k5122/
│ PIO 10H–18H, /STR /ST MK MK1, BUSRQ-Arbitrierung, Index aus rpm
│ streamt TrackImage byteweise über 16H; MK/MK1 → nextMark()
▼ fordert TrackImage(cyl,head)
FloppyDriveV2 — DriveProfile + 1-Spur-Cache je Kopf floppy_drive2.*
▼ readTrack / writeTrack / geometry
DiskImage (abstrakt) disk_image.* (open()/Sniffing)
├── RawSectorImage (.img + DiskFormat) ──► TrackCodec raw_sector_image.*
└── HfeImage (.hfe, HFE v1) ──► BitCodec hfe_image.*
▼ liefert/nimmt
TrackImage — zentrale Abstraktion (decodierter Byte- + Markenstrom, Encoding-Tag) track_image.*
TrackCodec — IBM-Track (FM/MFM) bauen/parsen + CRC-16 track_codec.*
BitCodec — Bitzellen ⇆ Bytes (MFM/FM) für HFE bit_codec.*
DriveProfile[4] — Zoll/Spuren/Köpfe/U-min/Verfahren je Slot drive_profile.*
Kernpunkte:
-
TrackImageist der einzige Berührungspunkt zwischen Controller und Dateiformat: decodierte Spur-Bytes (Gaps/Sync/IDAM/DATA/echte CRCs) + parallelesmarks[](Adressmarken aus dem fehlenden Clock-Bit, nicht aus dem Bytewert) +encoding(FM/MFM). -
Verfahrensneutral: FM vs. MFM lebt allein in der Codec-Schicht (
TrackCodec/BitCodec); Controller undTrackImagesind agnostisch. 4 Laufwerksprofile (5,25″ 80×2 MFM, 5,25″ 40×1, 8″ 77×1 FM, 8″ 77×2 MFM) je Slot zur Laufzeit konfigurierbar (A5120Machine::Config/k1520_create_configured, Default 4× K5601, siehe §8.4); Index-Periode ausrpm. -
HFE v1 (
HXCPICFE, ISOIBM_MFM=0/FM=2) wird lesend+schreibend unterstützt;DiskImage::openerkennt die Signatur und ist self-describing (keinDiskFormatnötig). Die Bitebene (BitCodec: 16 Zellen/Byte, HFE-LSB-first ↔ intern MSB-first via bytereverse, A1-Sync = Zellwort0x4489, MFM-Clockc_i=¬(d_{i-1}∨d_i), FM-Sondertakt C7/D7) ist nach der Greaseweazle/HxC-Spec implementiert und per unabhängigem Python-Konverter (tools/img_to_hfe.py) cross-validiert. -
CRC: eine zentrale Primitive
TrackCodec::crc16(Standard-IBM-CCITT, Poly 0x1021, Seed 0xFFFF). Der Boot-Lesepfad benutzt die Daten-CRC über[A1 A1 A1 FB]+Daten— die echte Disk-CRC; beide Boot-Leser werden über den gemeinsamen 4×A1-Stream bedient (s. u.). -
Verdrahtung & Boot:
A5120Machineverdrahtet dieK5122als Slot-2-Floppy (a5120.h). Die A5120 bootet die echte Standard-IBM-MFM-Diskette vollständig in CP/A (CP/A, Version 25.09.89 …) bis zum interaktiven Prompt, alletest_boot_integration-Stadien grün — inkl. Boot von den Laufwerken B: und C: (leere niedrigere Laufwerke werden vom ROM übersprungen). Der Boot-Lesepfad ist codierungstreu (keine Fake-Umwandlung):startReadTransfer()streamtbuildFaithfulReadTrack(4×A1-Sync — der gemeinsame Modus für Boot-ROM und SYL-Lader), Resync überromReadResyncTarget; das 4. A1 ist reines Sync und nicht in der CRC. Die MK/MK1-Re-Sync-Strobes (resyncToNextMark) springen IDAM→DATEN→nächstes IDAM. Vollständiges Modell:doc/design/07_k5122_afs.md§10; Boot-Kette (ROM → SYL → Sekundärlader → CP/A-Bootsystem →@OS.COM) im Detail: §14.5. -
Schreiben & Formatieren: Der
/WE-flankengesteuerte Schreibpfad (beginWriteField/commitWriteField) und der Vollspur-FORMAT-Schreibpfad (parseFormatStream/commitFormatTrack/writeTrackAt) sind implementiert und codierungstreu (FM ohne A1-Sync, MFM mit);FORMAT.COMformatiert+verifiziert alle Sektorgrößen.HfeImage::readTrackerkennt das Verfahren pro Spur (Mischdichte-Disks: FM-Systemspuren + MFM-Datenspuren, z. B. 8″-MF6400). Details + Formatkatalog:doc/format.md. -
Tests:
test_track_codec,test_bit_codec,test_hfe_codec,test_img_codec,test_dmk_codec,test_disk_medium,test_disk_image,test_drive_profile,test_floppy_drive2,test_k5122(GoogleTest); alle grün, ebensotest_boot_integration(Full-Machine) undtest_k2526(ZVE2-Floppy-Kette).
Status: umgesetzt.
FormatParser::builtinFormats()undparseFile()sind entfallen; die Formate stehen indata/formats.yaml. Neue Dateien:core/util/yaml_lite.{h,cpp}(Parser),core/peripherals/floppy_drive/format_catalog.{h,cpp}(Laden/Validieren),disk_format.{h,cpp}(Datenmodell, ehem.format_parser.*). Guard-Tests:test_yaml_lite(16),test_format_catalog(19); Gesamtstand 644/644 ctest grün.
Die Formatdefinitionen sind heute fest einkompiliert: FormatParser::builtinFormats()
(format_parser.cpp:102) baut ~25 DiskFormat-Structs im Code auf, A5120Machine übernimmt sie
im Konstruktor (a5120.cpp:55). Daneben existiert FormatParser::parseFile() — ein INI-artiger
Parser für ein aus dem CP/A-Umfeld stammendes Fremdformat, der ausschließlich in Tests
aufgerufen wird (test_format_parser.cpp) und dessen tatsächliche Grammatik ([name] +
track cf cl hf hl sp bps) nicht einmal dem in §8.2 dokumentierten disk … end-Stil entspricht.
Beide Altformate können das Entscheidende nicht ausdrücken:
| Fehlt heute | Konsequenz |
|---|---|
| Verfahren (FM/MFM) pro Spurbereich |
TrackFormat (format_parser.h:7) hat kein encoding-Feld. Das Verfahren gilt pro Image: RawSectorImage::enc_ (raw_sector_image.h:62) und der enc-Parameter von DiskImage::create (disk_image.h:109). Mischdichte-Disketten (FM-Systemspur + MFM-Daten) können daher zwar gelesen werden (HfeImage::readTrack probiert beide Verfahren, hfe_image.cpp:220-238), aber weder angelegt noch sauber zurückgeschrieben werden. |
| Laufwerks-Kompatibilität |
A5120Machine::compatibleFormats() (a5120.cpp:608) rät über eine reine Geometrie-Heuristik (numCylinders ≤ prof.num_cyls && numHeads ≤ prof.num_heads). Ein 8″-FM-Format erscheint damit im Dropdown eines 5,25″-Laufwerks. |
| Standardformat je Laufwerkstyp |
defaultFormatFor() (a5120.cpp:551) ist eine hartkodierte if-Kette. |
| Klartextbeschreibung | Die GUI zeigt nur nackte Katalognamen (k5601_ds40_17x256). |
Ziel: eine menschenlesbare YAML-Datei als alleinige Quelle, die Mischdichte, Sektorgrößen-Mix und Laufwerks-Kompatibilität ausdrückt; beide Altparser entfallen.
# Katalog der Diskettenformate. Schema-Version für spätere Migrationen.
version: 1
formats:
# ── Einfaches Format: alle Spuren gleich ─────────────────────────────────
- name: cpa800
description: "CP/A 800K — 80 Spuren, doppelseitig, 5×1024 MFM"
drives: [K5601]
default_for: [K5601] # Standard beim Anlegen (leerer Formatname)
encoding: mfm # Vorgabe für alle Spurbereiche
tracks:
- { cyls: 0-79, heads: 0-1, sectors: 5, size: 1024 }
# ── Asymmetrischer Systembereich (boot-kritisch, exakt wie der Builtin) ──
- name: cpa780
description: "CP/A 780K Bootdiskette — 128B-Systembereich + 1024B-Daten"
drives: [K5601]
encoding: mfm
tracks:
- { cyls: 0, heads: 0-1, sectors: 26, size: 128 } # System
- { cyls: 1, heads: 0, sectors: 26, size: 128 } # Stage-2-Lader
- { cyls: 1, heads: 1, sectors: 5, size: 1024 } # 1. Datenspur
- { cyls: 2-79, heads: 0-1, sectors: 5, size: 1024 } # Daten + Dateisystem
# ── MISCHDICHTE: FM-Systemspur + MFM-Datenspuren (neu ausdrückbar) ───────
- name: mf6400_sys
description: "8″ MF6400 600K — FM-Systemspur, MFM-Daten"
drives: [MF6400]
tracks:
- { cyls: 0, heads: 0, sectors: 26, size: 128, encoding: fm }
- { cyls: 1-76, heads: 0, sectors: 8, size: 1024, encoding: mfm }
# ── Nur als .hfe darstellbar ─────────────────────────────────────────────
- name: k5601_ds40_5x1024
description: "K5601 §3.4 Format V — 40 Spuren, doppelseitig, 5×1024"
drives: [K5600.10]
encoding: mfm
containers: [hfe] # .img nicht erzeugbar (Doppelschritt)
tracks:
- { cyls: 0-39, heads: 0-1, sectors: 5, size: 1024 }Format-Ebene:
| Feld | Pflicht | Typ | Bedeutung |
|---|---|---|---|
name |
ja | string | Eindeutiger Katalogname (C-API, GUI, Tools, --format) |
description |
nein | string | Klartext fürs GUI-Dropdown |
drives |
ja | list<string> | Kompatible DriveProfile-Namen (§8.4). Nur diese Formate bietet ein Slot an. |
default_for |
nein | list<string> | Profile, für die dies das Standardformat ist — ersetzt defaultFormatFor()
|
encoding |
nein |
fm|mfm
|
Vorgabe für Spurbereiche ohne eigenes encoding
|
containers |
nein | list |
hfe, img — darstellbare Dateitypen (Default: beide) |
step |
nein |
1|2
|
Doppelschritt (2026-08-11): logische Spur n liegt auf physischem Zylinder n·step; tracks: bleibt logisch. Default 1. |
tracks |
ja | list | ≥ 1 Spurbereich |
Spurbereich (tracks[]):
| Feld | Pflicht | Typ | Bedeutung |
|---|---|---|---|
cyls |
ja |
N oder N-M
|
Zylinderbereich, inklusive |
heads |
ja |
N oder N-M
|
Kopfbereich, inklusive |
sectors |
ja | int | Sektoren je Spur |
size |
ja | int | Bytes/Sektor (128/256/512/1024) |
encoding |
nein |
fm|mfm
|
Pro Spurbereich — überschreibt die Format-Vorgabe |
first_sector |
nein | int | Erste Sektor-ID (Default 1) |
interleave |
nein | int | Sektor-Verschränkung (Default 1) — Phase 2 |
gaps |
nein | map | Überschreibt GapParams (§track_codec.h) — Phase 2
|
Auflösungsregeln: Verfahren = tracks[].encoding → formats[].encoding → Laufwerks-Default
(prof.supports_mfm ? MFM : FM, heutiges Verhalten). Bereiche sind inklusive und müssen
überlappungsfrei sein (V2); DiskFormat::findTrack() behält seine „erster Treffer"-Semantik.
Bewusst kein yaml-cpp: keine externe Abhängigkeit für libk1520core.so, offline baubar,
konsistent mit dem vorhandenen handgeschriebenen Parser. Der Parser (core/util/yaml_lite.{h,cpp},
~300 Zeilen) unterstützt genau:
| Unterstützt | Nicht unterstützt (→ Ladefehler mit Zeilennummer) |
|---|---|
Kommentare # bis Zeilenende |
Anchors/Aliases &a / *a, Merge-Keys <<:
|
| Einrückungs-Verschachtelung (nur Leerzeichen) | Tabs als Einrückung |
Block-Maps key: value, Block-Listen - item
|
Mehrzeilige Skalare ` |
Flow-Maps { a: 1, b: 2 } (einzeilig) |
Mehrere Dokumente (---), Tags (!!str) |
Flow-Listen [a, b, c]
|
Komplexe Keys (? …) |
Skalare: bare, '…', "…"; int dez/0x; bool true/false
|
Zeitstempel, Sets, Ordered-Maps |
Das Ergebnis ist ein generischer YamlNode (Map/List/Scalar); FormatCatalog bildet ihn auf
DiskFormat ab. Damit ist der Parser eigenständig testbar (test_yaml_lite) und für spätere
Konfigurationsdateien (z. B. drives.yaml, §8.6.8/E6) wiederverwendbar.
struct TrackFormat { // disk_format.h (ehem. format_parser.h)
uint8_t cyl_first, cyl_last;
uint8_t head_first, head_last;
uint8_t secs_per_track;
uint16_t bytes_per_sec;
Encoding encoding = Encoding::MFM; // NEU — pro Spurbereich
uint8_t first_sector_id = 1; // NEU
// Phase 2: uint8_t interleave; std::optional<GapParams> gaps;
};
struct DiskFormat {
std::string name, description;
std::vector<std::string> drives, default_for; // NEU
bool allow_img = true, allow_hfe = true; // NEU (containers)
std::vector<TrackFormat> tracks;
uint8_t numHeads() const; uint8_t numCylinders() const; uint64_t totalBytes() const;
const TrackFormat* findTrack(uint8_t cyl, uint8_t head) const;
Encoding predominantEncoding() const; // NEU — HFE-Header, DiskGeometry
bool isMixedEncoding() const; // NEU
bool fitsDrive(const DriveProfile&) const; // NEU — Validierung V4
};
class FormatCatalog { // NEU — ersetzt FormatParser
public:
static std::vector<std::string> searchPaths();
static FormatCatalog load(const std::vector<std::string>& files,
std::string* error);
const DiskFormat* find(const std::string& name) const;
std::vector<const DiskFormat*> forDrive(const DriveProfile&) const; // explizite Liste
const DiskFormat* defaultFor(const DriveProfile&) const;
const std::vector<std::string>& warnings() const; // nicht-fatale Befunde
const std::vector<std::string>& sources() const; // geladene Dateien (Diagnose)
};Der Katalog ist eine reine Laufzeitdatei (kein Build-Codegen). Geladen wird in
aufsteigender Priorität — alle gefundenen Dateien werden gelesen, ein späterer name
ersetzt einen früheren gleichen Namens (User-Override):
-
K1520_FORMATS_DEFAULT— Compile-Define viaadd_compile_definitions(CMake setzt es im Dev-Build auf${CMAKE_SOURCE_DIR}/data/formats.yaml, im Install-Build auf den Install-Pfad) <Verzeichnis von libk1520core.so>/../share/a5120emu/formats.yaml-
./data/formats.yaml(CWD) ${XDG_CONFIG_HOME:-~/.config}/a5120emu/formats.yaml-
$K1520_FORMATS(Datei oder Verzeichnis; mehrere:-getrennt) — höchste Priorität
Wird keine Datei gefunden, schlägt die A5120Machine-Konstruktion laut fehl:
last_error_ listet alle durchsuchten Pfade auf, k1520dbg/boot_trace drucken sie.
Das ist die bewusste Kehrseite der Laufzeitdatei (siehe R2) — Schritt 1 sorgt dafür, dass
ctest, boot_trace und k1520dbg ohne jede Umgebungsvariable funktionieren.
| # | Regel | Verstoß |
|---|---|---|
| V1 | Pflichtfelder vorhanden, name katalogweit eindeutig |
Fehler |
| V1b | Unbekannte Felder | Warnung (vorwärtskompatibel) |
| V2 | Spurbereiche eines Formats überlappungsfrei; cyl_first ≤ cyl_last, head ≤ 1
|
Fehler |
| V3 |
drives/default_for verweisen auf existierende Profilnamen |
Fehler |
| V4 | Für jedes drives-Profil: numHeads ≤ prof.num_heads, numCylinders ≤ prof.num_cyls, und jedes verwendete Verfahren von prof.supports(enc) gedeckt |
Fehler |
| V5 | Spurkapazität: sectors × size + Gaps ≤ indexPeriodCycles / bytePeriodCycles
|
Warnung (s. u.) |
V3 — Fallstrick:
builtinDriveProfile()(drive_profile.h:155) liefert für unbekannte Namen stillschweigend das Default-Profil. Die Validierung darf daher nicht über den Rückgabewert prüfen, sonst geht jeder Tippfehler alsK5601durch. Nötig ist ein neuer AccessorknownDriveProfileNames(), gegen den geprüft wird.
V5 — nur Warnung, und warum:
DriveProfile::bytePeriodCycles()(drive_profile.h:132) hat die Datenrate fest auf FM = 125 kbit/s / MFM = 250 kbit/s verdrahtet — die 5,25″-Werte aus dem K5601-Datenblatt. Reale 8″-FM-Laufwerke arbeiten mit 250 kbit/s. Rechnet man V5 mit dem heutigen Modell, ergibt sich für das existierendemf3200-Format (8″, 360 min⁻¹, FM, 4×1024) eine Kapazität von nur408333 / 156 ≈ 2617Bytes gegenüber 4096 Bytes Nutzdaten — das bestehende, funktionierende Format würde abgelehnt. V5 bleibt deshalb Warnung, bisDriveProfiledie Datenrate als eigenes Feld führt (Folgearbeit, nicht Teil dieses Umbaus). Stand 2026-11-04: 8″-FM ist verdoppelt (39_mutos8000.md§9.11), und 8″-MFM mit 500 kbit/s entsteht zur Laufzeit über den/HF-Ausgang der Steuer-PIO (07_k5122_afs.md), nicht über das Profil — V5 kann die Laufzeitrate nicht kennen und bleibt deshalb Warnung.
Das ist der eigentliche Eingriff; die YAML ist nur die Eingabe dafür. Betroffen:
| Ort | Heute | Nachher |
|---|---|---|
raw_sector_image.cpp:121 |
buildTrack(sektoren, enc_) — Image-weites Verfahren |
buildTrack(sektoren, tf->encoding, gaps) aus fmt_.findTrack(cyl, head)
|
raw_sector_image.h:62 |
enc_ bestimmt alles |
enc_ nur noch Fallback für Formate ohne encoding
|
raw_sector_image.cpp:83 |
geometry().encoding = enc_ |
= fmt_.predominantEncoding() |
disk_image.cpp:199 |
buildTrack(emptySectors(*tf,c,h), enc) |
…, tf->encoding — Mischdichte anlegbar |
disk_image.cpp:207 |
side_len aus max. Spurlänge |
unverändert max über alle Spuren, aber Mischdichte-Test nötig (R3) |
disk_image.cpp:122 |
HFE-Header enc
|
fmt.predominantEncoding(); die andere Dichte deckt der vorhandene Dual-Decode ab (hfe_image.cpp:220-238) |
disk_image.h |
open(…, raw_encoding) (2026-08-03 ergänzt) |
Parameter entfernt — das Format ist jetzt die Autorität (s. u.) |
k5122.cpp |
Verfahren aus profile().supports_mfm abgeleitet |
entfällt — DiskImage::open(path, fmt, wp)
|
a5120.cpp |
disk_formats_ = builtinFormats() |
FormatCatalog::loadDefault(&fatal), wirft bei fatalem Fehler |
a5120.cpp |
defaultFormatFor() (if-Kette) |
disk_formats_.defaultFor(prof) — aus default_for:
|
a5120.cpp |
compatibleFormats() (Geometrie-Heuristik) |
disk_formats_.forDrive(prof) — explizite drives:-Liste |
format_parser.{h,cpp} |
builtinFormats() + parseFile()
|
entfallen beide; Datei → disk_format.{h,cpp} + format_catalog.{h,cpp}
|
writeTrack braucht keine Änderung: TrackCodec::parseTrack liest das Verfahren bereits aus
TrackImage::encoding — der Controller liefert die Spur in der Codierung, in der er sie gelesen
bzw. geschrieben hat.
Abweichung 1 vom Entwurf —
raw_encodingganz entfernt statt „Fallback". Der Plan wollte den 2026-08-03 ergänzten Parameter als Rückfall behalten. Das ist nicht darstellbar:TrackFormat::encodinghat immer einen Wert, „nicht deklariert" ist von „MFM" nicht unterscheidbar — der Parameter wäre wirkungslose Ballast-API geworden. Das Verfahren kommt jetzt ausschließlich aus dem Format (pro Spurbereich). Für Formate ganz ohne Spurbereiche bleibt intern MFM.Abweichung 2 —
drives:wird beim MOUNTEN nicht erzwungen, nur beicreateDisk()und in der angebotenen Auswahl (compatibleFormats()). Grund: bei self-describing.hfeist der übergebene Formatname nur ein Platzhalter (die Geometrie steht in der Datei) —tools/format_drivermountet z. B. alle Slots nominell als"cpa780", auch die 8″-Laufwerke der Combo-Boot-Tests. Zudem ist der Laufwerkstyp auf der A5120 reine BIOS-Software (§8.4), Combo-Disketten betreiben an B:/C: bewusst Fremdtypen. Eine Prüfung beim Mounten hat genau diese vierformat_integration-Tests zerlegt. Das Ziel „kein doppelseitiges Format an einseitigem Laufwerk auswählbar" wird dort erzwungen, wo das Format die Struktur wirklich bestimmt: beim Anlegen und in der GUI-Liste.
Die C-ABI bleibt stabil — DiskFormat wandert nie über die Grenze, nur Namen/Strings.
Bestehend bleiben k1520_drive_format_count/_name/_default_format (ihre Semantik wird
präziser: explizite Kompatibilität statt Geometrie-Heuristik). Neu:
const char* k1520_format_description(K1520Handle h, const char* name); /* GUI-Dropdown */
const char* k1520_formats_source(K1520Handle h); /* geladene Datei(en) — Diagnose */
const char* k1520_last_init_error(void); /* Grund eines fehlgeschlagenen create */Startabbruch ohne Handle. Fehlt der Katalog oder ist er syntaktisch kaputt, wirft der
A5120Machine-Konstruktor; k1520_create* fängt das, gibt NULL zurück und legt den Grund in
k1520_last_init_error() ab — k1520_last_error(h) ist mangels Handle nicht erreichbar. Die
Python-Bindung (K1520Emulator.__init__) macht daraus ein RuntimeError, app/main.py gibt es
aus und beendet mit Exit-Code 1. Beispielausgabe:
Keine Formatkatalog-Datei (formats.yaml) gefunden.
Gesucht wurde in:
- /home/…/a5120emu_ui/data/formats.yaml
- /usr/bin/../share/a5120emu/formats.yaml
- data/formats.yaml
- /home/…/.config/a5120emu/formats.yaml
Abhilfe: data/formats.yaml bereitstellen oder K1520_FORMATS=<datei> setzen.
Einzelne fehlerhafte Definitionen sind dagegen nicht fatal — sie werden übersprungen und mit Datei, Zeile, Name und Grund auf stderr (und ins Log) gemeldet, die übrigen Formate bleiben nutzbar:
[Formatkatalog] …/formats.yaml:8: Format 'kaputt' übersprungen — 'size': 777 — erlaubt sind 128, 256, 512, 1024
GUI (app/ui/drive_widget.py): der Laufwerkskasten zeigt hinter Format: das erkannte
Format (k1520_disk_detected_format → A5120Machine::detectedFormatName, §8.6.9), nicht eine
Einstellung. Eine Formatauswahl gibt es nur noch als Rückfrage bei .img
(app/ui/format_dialog.py) — dort zeigt die Liste "<name> — <description>" und behält den
Katalognamen in Qt::UserRole.
Dieselbe Geometrie-Erkennung wie im k1520DiskTool (GeometryProbe, doc/design/13_k1520disktool.md
§12.1), angewandt auf das Medium, das gerade im Laufwerk liegt. Dafür steht geometry_probe.cpp
seit 2026-09-13 in einer eigenen statischen Bibliothek k1520_geometry; sie ist das einzige
Stück der Dateisystem-Schicht, das auch libk1520core.so braucht — die beiden gemeinsamen
Bibliotheken bleiben ansonsten getrennt (§10 des DiskTool-Entwurfs).
Drei Fälle liefern leer ≡ „unbekannt": kein Katalogformat passt, zwei passen gleich gut und
beschreiben verschiedene Sektorräume, oder die Diskette liegt erst teilweise im Speicher
(physisches Laufwerk — eine Messung zöge dort die ganze Scheibe ein). Die Einschränkung im
zweiten Fall ist wesentlich: cpa640 und k5601_16x256 sind bis auf den Namen derselbe Eintrag,
und ein gleich guter Treffer mit identischer aufgelöster Spurbelegung ist keine Mehrdeutigkeit
— beide Namen ergäben byteweise dasselbe .img.
Bei .img wird nicht gemessen, sondern das beim Einlegen erklärte Format gemeldet
(DiskImage::diskFormat()): ein rohes Sektorabbild trägt keine Adressmarken, seine Geometrie ist
Vereinbarung, nicht Befund. Wächter: A5120DiskApi.DetectedFormat_*.
| # | Inhalt | Status |
|---|---|---|
| E1 |
yaml_lite + Tests, ohne jede Verdrahtung |
✅ test_yaml_lite (16 Tests) |
| E2 |
FormatCatalog + data/formats.yaml als 1:1-Abbild der 25 Builtins (encoding: mfm durchgängig; nur mf3200 ist FM — wie die bisherige Ableitung aus dem FM-Laufwerk) |
✅ BootKritischeGeometrien_Unveraendert prüft cpa780 Spurbereich für Spurbereich |
| E3 |
A5120Machine auf Katalog umstellen; builtinFormats()/parseFile() entfernt; §8.2 als abgelöst markiert |
✅ 644/644 ctest, test_boot_integration grün |
| E4 | Verfahren pro Spur durch RawSectorImage/DiskImage::create gezogen |
✅ RawMischdichte_VerfahrenJeSpur, Mischdichte_VerfahrenProSpurbereich
|
| E5 | C-API _description/_source/_last_init_error, GUI-Labels, Startabbruch |
✅ end-to-end geprüft |
| E6 |
offen, optional: DriveProfile ebenfalls aus YAML (drives.yaml) — derselbe Parser |
— |
-
R1 — Boot-Regression durch Encoding-Deklaration (größtes Risiko). Die A5120-Bootdisketten
sind reines Standard-IBM-MFM; der Boot-ROM startet in FM, findet keine IDAM und schaltet
per MK auf MFM um (§14.5, CLAUDE.md „Boot-Invarianten"). Würde
cpa780seine 128-B-Systemspuren alsencoding: fmdeklarieren — was intuitiv plausibel wirkt —, bräche der verifizierte Bootpfad. Deshalb ist E2 strikt verfahrens-neutral (encoding: mfmüberall, exakt wie heute); Mischdichte kommt erst in E4 und nur an neuen Formaten. Guard:test_boot_integration(Stage3_FullyLoadsAndJumpsToOs). -
R2 — Laufzeit-Dateiabhängigkeit (bewusste Entscheidung): Fehlt
formats.yaml, kennt die Maschine kein einziges Format — auch die boot-kritischencpa780/cpa800nicht. Mitigation: Compile-Define als letzter Rückfall (§8.6.4 Schritt 1) + laute, alle Pfade nennende Fehlermeldung. -
R3 — HFE-Mischdichte. HFE v1 trägt nur eine
bitrateund ein Verfahren im Header. Lesen ist gelöst (Dual-Decode). Beim Anlegen ist zu beachten:BitCodeckodiert FM und MFM mit 16 Zellen/Byte (bit_codec.h:15), die reale Zeitkapazität je Umdrehung unterscheidet sich aber.side_lenmuss nach der dichtesten Spur bemessen werden; per Test absichern. -
R4 — Doppelschritt-Formate (40 Spuren in einem 80-Spur-Laufwerk, physisch = 2 × logisch)
sind gelöst (2026-08-11): das Format trägt
step: 2,tracks:bleibt logisch, undDiskFormat::physicalCylinder()/logicalCylinder()rechnen um. Umgesetzt inSectorSpace(Slot kennt logische und physische Spur),ImgCodec(das rohe Sektorabbild ist logisch),DiskImage::create(ungerade Zylinder bleiben unformatiert) undGeometryProbe(die Lücken sind ein positives Erkennungsmerkmal, zusätzlich muss die Spurnummer im ID-Feld die logische sein).formatFitsDriveprüft die physische Ausdehnung. Acht Katalogeinträge*_dstepdecken die §3.4-Geometrien T und U ab; Gegenprobe am laufenden CP/A:DiskToolNeueDisketten.CpaLiestDoppelschrittDiskette.
Ablösung der Backend-Schicht aus §8.5. Dort hatte jedes Dateiformat eine eigene
DiskImage-Unterklasse, die auf der Datei arbeitete (RawSectorImage: seek/write je Sektor;HfeImage: In-place-Blöcke). Ab hier gibt es ein internes, bitstrom-orientiertes Diskettenabbild; Dateiformate sind reine Container-Codecs davor. DerTrackImage-Stack (§8.5), der Boot-Lesepfad und das PIO-Protokoll bleiben unverändert. Vollständiger Feinentwurf:doc/design/09_floppy_drive.md.
Warum der Umbau. Die dateigebundenen Backends erzwangen drei Einschränkungen, die sich nicht lokal reparieren ließen:
-
.imgverliert das Dateisystem fremder OS. Ein rohes Sektorimage speichert nur Datenfeld-Nutzbytes. UDOS hängt aber je Sektor einen Sektorkontrollblock (Verkettungszeiger + eigene CRC) hinter die Daten-CRC — genau diese Bytes fielen beim Rückschreiben weg (doc/udos_diskettenformat.md). - Kein Formatwechsel. Das Backend war an den Dateityp gebunden; eine gemountete Diskette ließ sich nicht als anderer Typ herausschreiben.
-
Keine echte Leerdiskette.
DiskImage::createmusste.hfevorformatiert anlegen, weil eine gap-leere Datei den Controller hängen ließ — auf einer vorformatierten Diskette kann UDOS seinen Zeiger-Anhang aber nicht unterbringen.
Neues Modell.
Datei (.img | .hfe | .dmk) ──load──► DiskMedium (alle Spuren als TrackImage) ──► K5122
◄─save── ▲ Schreibzugriffe des Gastsystems
-
DiskMedium(disk_medium.*) hält jede Spur alsTrackImage(Vollumdrehungs-Byte-/Markenstrom inkl. Gaps, Sync, echter CRCs, FM und MFM gemischt) plus Dirty-Bit je Spur. Das ist der verlustfreie, bitzellen-rückcodierbare „Bitstrom“ des Auftrags — Bytes statt Zellen, weil der Controller ohnehin byteweise arbeitet und die Markeninformation (fehlendes Clock-Bit) sonst verloren ginge. -
ImageCodec(image_codec.*+img_codec/hfe_codec/dmk_codec.*) lädt/speichert Container vollständig; kein Codec hält Dateizustand..imgbraucht einDiskFormat,.hfe/.dmksind self-describing. -
DiskImageist jetzt konkret: Medium + Dateibindung + Schreibschutz. Es schreibt schmutzige Spuren verzögert (≈ 0,5 s Maschinenzeit,autoFlush()ausA5120Machine::run()) in die gebundene Datei zurück, sodass die Datei dem Abbild stets mit leichtem Zeitversatz entspricht.saveAs()schreibt in einen beliebigen Container und bindet um. -
FloppyDriveV2hat keinen Spur-Cache mehr — es referenziert das Medium direkt. -
.dmk(David Keil) ist neu: 16-B-Header, je Spur 128-B-IDAM-Tabelle (u16-Offsets, Bit15 = MFM) + roher Spur-Byte-Strom (FM-Bytes verdoppelt). Damit verlustfrei wie HFE, aber ohne Bitzellen-Ebene. -
rawCompatible()ist das geforderte Flag: eine Spur ist.img-tauglich nur mit ≥ 1 Sektor, gültigen CRCs und reinen Gap-Bytes hinter der Daten-CRC. Sobald das Gastsystem etwas anderes schreibt (UDOS-Anhang) oder eine Spur unformatiert bleibt, wird.imgals Ziel abgelehnt (GUI blendet es aus). -
Leerdiskette:
DiskImage::createBlank()erzeugt ein Medium in Laufwerks-Geometrie (K5601 80×2, K5600.10 40×1, …) mit unformatierten Spuren. Der Controller streamt für sie markenlosen Gap-Flux (§7 des Feinentwurfs), das Gastsystem läuft in den Index-Timeout — genau wie auf echter Hardware — und kann die Diskette formatieren. Die Ablehnung markenloser Images beim Mounten (hasFormattedData()) entfällt damit.
Nicht betroffen: TrackImage/TrackCodec/BitCodec, der treue FM/MFM-Lesepfad
(§8.5, §14.5), die Boot-Invarianten und das K5122-Portprotokoll.
Seit dem k1520DiskTool (doc/design/13_k1520disktool.md) ist
core/peripherals/floppy_drive/ nicht mehr nur Zulieferer des Emulators: eine zweite
Bibliothek libk1520disk.so übersetzt diese Schicht zusammen mit
core/filesystem/ — ohne Z80, ohne Karten, ohne Bus. Daraus folgt eine Regel, die
für jede Änderung unterhalb von DiskImage gilt: nichts darf die Maschine kennen.
Kein Maschinentakt in einer Signatur, keine A5120Machine, keine Kartenabhängigkeit;
die Maschinenuhr des Autosave steht als Parameter in autoFlush(now).
Das DiskTool hat die Schicht dabei um eine Schreibseite erweitert, die der Emulator
mitträgt (Einzelheiten: doc/design/09_floppy_drive.md §11):
-
TrackCodecschreibt in vorhandene Spuren statt sie neu zu bauen —writeSector/writeSectorAt(Datenfeld + CRC, CRC auch wörtlich setzbar),writeSectorTail(die Bytes hinter der Daten-CRC = UDOS-Sektorkontrollblock),createSector/eraseSectorAt.buildTrack()taugt dafür nicht: es verlöre alles hinter der Daten-CRC, bei UDOS also die gesamte Dateiverkettung. parseTrackliefert zusätzlich Byte-Offsets, gespeicherte CRCs unddeleted.-
TrackView(track_view.*) zerlegt eine Spur in eine lückenlose Folge zeichenbarer Abschnitte (Sektor/Gap/unformatiert) für den Diskeditor. Der Winkel eines Bytes istPosition ÷ Spurlänge— eineTrackImageist eine Umdrehung; Bitrate und Drehzahl werden dafür nicht gebraucht. -
Stapeloperationen sind Transaktionen: die Rücknahme ist eine Kopie des ganzen
DiskMedium(~1 MB) — billiger als Buchführung über Einzeländerungen.
Zweite Art von Bindung neben der Datei. Voller Feinentwurf:
doc/design/14_physische_diskette.md, Medium-Sicht:doc/design/09_floppy_drive.md§12.
Eine gemountete Diskette hatte bisher genau eine Quelle: eine Datei, die beim Öffnen vollständig gelesen und beim Autosave vollständig geschrieben wird. Daneben tritt jetzt die echte Diskette in einem echten Laufwerk, angebunden über einen Greaseweazle-Adapter am USB. Der Unterschied ist nicht das Medium, sondern die Körnung: gelesen und geschrieben wird spurweise nach Bedarf, nicht am Stück.
┌── K5122 (Emulator)
echte Diskette ◄─gw─► Arbeitsfaden ◄─Aufträge─► DiskMedium
(Python) └── DiskVolume (DiskTool)
Vier Festlegungen tragen das:
-
Je Spur ein Zustand statt eines Dirty-Bits:
Unknown(nie gelesen, Inhalt ungültig) /Clean/Dirty.Unknownist nicht „unformatiert“ — letzteres ist eine belegte Aussage über die Diskette (§8.7), ersteres gar keine. Darum meldenformatted()/rawCompatible()zusätzlichcomplete(); sonst erklärte sich eine halb gelesene Diskette für unformatiert und ein.imgschriebe die ungelesenen Spuren als Füllbytes fest. -
Nachgeladen wird in
DiskMedium::track()— der einzigen Stelle, durch die jeder Verbraucher geht (Controller, DiskTool, Formaterkennung). Der Aufruf blockiert (≈ 0,5–0,8 s je Spur); im Emulator wartet der Maschinenfaden, also genau der, der auch an einem echten Laufwerk wartete. Die medienweiten Reihenläufe benutzen dagegenpeek()und laden nie nach — sonst zöge eine beiläufige Statusabfrage die ganze Diskette ein. -
Drei Prioritäten in
TrackSync: 1 Lesen auf Anforderung (jemand wartet), 2 geänderte Spuren zurückschreiben (nach einer Schreibpause von ≈ 0,5 s — dieselbe Regel wie der Autosave, sonst schriebe eine UDOS-Dateioperation dieselbe Spur dutzendfach), 3 unbekannte Spuren vorausschauend lesen (kürzester Kopfweg zuerst). Prio 1 verdrängt, unterbricht aber keinen laufenden Zugriff. 3a. Geschrieben gilt erst nach dem Zurücklesen (Feinentwurf §7.1). Der Verify-Lauf des Gastsystems (FORMAT) prüft das Speicherabbild gegen sich selbst und sieht eine Schadstelle der Diskette nie; deshalb folgt jedemWriteeinVerify, das die Spur zurückliest und auf Sektorebene vergleicht. Erst dann wirdDirtygelöscht. Misslingt es zweimal, gilt die Spur als schadhaft: sie bleibtDirty, „Speichern" meldet Misserfolg mit Spurnummer, und die Oberfläche bietet „Diskette neu beschreiben" für eine heile Diskette. Das Zurückgelesene wird nie ins Abbild übernommen — sonst überschriebe ein misslungener Schreibvorgang genau die Daten, die er zerstört hat. -
Der Kern kennt Greaseweazle nicht.
TrackSynchat keinen eigenen Faden und kein USB; ein fremder Arbeitsfaden holt sich Aufträge ab (k1520s_take_job, die einzige blockierende Funktion der ABI) und liefert HFE-Bitzellen zurück, die durch denselbenBitCodec::decodelaufen wie eine HFE-Datei. Rückrufe in die Anwendung gibt es nicht — das hält die GIL aus dem Kern heraus und die Verklemmungen aus dem Entwurf.
Warum die Gerätehälfte in Python liegt: Greaseweazle liefert Firmware + Hosttools in Python und keine C++-Bibliothek. Das USB-Protokoll wäre nachbaubar, die gereifte Flusswechsel-Auswertung (PLL, Index, Wiederholungen) nicht sinnvoll. Beide Anwendungen sind ohnehin Python um eine Bibliothek herum. Das Paket ist freiwillig: fehlt es, fehlt der Menüpunkt.
Testbarkeit ohne Hardware (das Laufwerk hängt in der CI nie am Rechner): Der
Ersatz-Arbeitsfaden bedient die Aufträge aus einer .hfe-Datei — dieselben
Bitzellen, die der Adapter liefern würde, denn eine HFE-Aufnahme ist genau das. Damit
liegen Zustandsverwaltung, Warteschlange, Decodierung und sogar ein voller Boot „von
einer spurweisen Quelle“ in der Regression; die Hardware fügt nur USB hinzu und läuft von
Hand hinter K1520_GW_HARDWARE=1.
Die Platte am WDC (core/peripherals/winchester/platte.*) gehört nicht zur Floppy-Schicht.
Die Original-Firmware des WDC sieht nie Bits, sondern Bytes; eine Spur ist deshalb ein
umlaufender Ring aus Datenbytes mit Markenkennzeichen, das Medium ein rohes LBA-Abbild.
Plattendateien tragen das Typkürzel (<name>.<k5504|d5126|d5146|vs>.img) und liegen
gepackt als .img.gz vor: das Abbild liegt ganz im Speicher, ein eigener Faden schreibt
nach einer Schreibpause zurück (core/util/gzip_datei.*). Einzelheiten:
design/25 §10.8 und merkposten/p8000.
Die Python-Seite sieht den Kern nur über extern "C"-Funktionen. Es gibt drei
Schnittstellen in zwei Bibliotheken:
| Kopf | Präfix | Bibliothek | Inhalt |
|---|---|---|---|
core/api/k1520_api.h |
k1520_ |
libk1520core |
Emulator: Maschinen, Bild, Tastatur, Disketten, Schnittstellen, Optionen, Diagnose |
core/api/k1520_disk_api.h |
k1520d_ |
libk1520disk |
DiskTool: Abbild öffnen, Dateien, Diskeditor, Prüfung, Rettung |
core/api/k1520_sync_api.h |
k1520s_ |
beide | physische Diskette: Aufträge abholen und erledigen (§8.8) |
Regeln, die für alle drei gelten:
-
K1520_API(core/api/k1520_export.h) vor jeder Funktion — ohne das exportiert eine MSVC-DLL nichts. -
Erweitern, nicht ändern. Neue Fähigkeiten kommen als neue Funktionen; eine
bestehende Signatur bleibt. Ein Handle (
K1520Handle) steht für eine Maschine; welche, entscheidet allein die Erzeugerfunktion (§5.5). -
Kein C++-Typ über die Grenze — nur Zahlen, Zeichenketten, Puffer und einfache
Strukturen (
K1520EmState, …). -
Fehler über
k1520_last_error(h); scheitert schon das Anlegen, überk1520_last_init_error(). -
Drei Stellen müssen übereinstimmen — Kopf, Bibliothek und die ctypes-Deklarationen
in
app/core_binding/k1520.pybzw.k1520disk.py.tests/python/test_c_api.pyvergleicht sie mechanisch, denn eine abweichende Signatur bricht sonst still.
Funktionsliste und Gruppen: der Kopf selbst und design/10.
Feinentwurf: design/11, Bedienung:
app/help/handbuch.md.
Die Oberfläche (app/main.py → app/ui/main_window.py) ist für alle Maschinen dieselbe.
Was einer Maschine eigen ist, steht im Programmprofil (app/profil.py): Titel,
Konfigurationsdatei, Takt, Tastatur, Frontplatte, Modellwahl, eigene Aktionen. In der
Oberfläche gibt es kein if machine == …. Der Name des Starters (a5120emu,
k8915emu, prg710emu, pc1715emu, p8000emu) bzw. --machine wählt das Profil.
Bausteine der Oberfläche:
-
Aktionen — jede Bedienung ist eine
QActioninapp/ui/actions.py; Menü und Symbolleiste zeigen dieselbe, die Leiste wird aus einer Namensliste der Konfiguration gebaut. Tastenkürzel nur mitStrg+Umschalt(AusnahmeF11), weil alle übrigen Tasten dem emulierten Rechner gehören. -
Bildschirm —
ScreenWidgetmit CRT-Shader (§6.2). - Laufwerkskasten, Statuszeile (eingestellter Takt, Laufwerksleuchte, Abbild, Schreibschutz), Einstellungen mit den Reitern für Schnittstellen, RAM-Disk, Lochstreifen.
-
Pfade — alles über
app/paths.py: Umgebungsvariable → Installationslayout → Quellbaum.python3 app/main.py --pathszeigt die ganze Auflösung. -
Konfiguration — je Programm eine YAML-Datei (
a5120emu.yaml, …) im Benutzerordner; der Auslieferungszustand ist ebenfalls eine Datei (data/default_config_<maschine>.yaml).
Die Maschine rechnet in einem eigenen Faden (app/emulationstakt.py) in kurzen
Scheiben; k1520_run gibt die GIL frei. Kann der Wirt den eingestellten Takt nicht halten,
wird die Maschine langsamer, nicht die Oberfläche.
Der Kern ist nicht threadsicher: jeder Aufruf geht über die Kernsperre
(_GesperrteBibliothek in app/core_binding/k1520.py), mehrere zusammenhängende mit
with kern_sperre():. Weil ein RLock nicht fair ist, melden Wartende sich an und
bekommen nach der laufenden Scheibe den Vortritt — sonst verhungert die Oberfläche.
Weitere Fäden außerhalb der Maschine: der E/A-Faden des SerialHub (§16.1) und der
Arbeitsfaden der physischen Diskette (§8.8).
app/disktool/ ist eine zweite Anwendung über libk1520disk mit eigener Oberfläche und
eigenem Handbuch (app/disktool/help/handbuch.md).
Beide Programme starten einander über das Menü Werkzeuge (app/programme.py).
Entwurf: design/13_k1520disktool,
design/15 (Prüfung), design/27 (WEGA).
Die Werkzeuge in tools/ linken die statischen Kernbibliotheken direkt und kommen damit an
alles, was die C-ABI bewusst nicht zeigt.
| Werkzeug | Zweck | Doku |
|---|---|---|
k1520dbg |
interaktiver Debugger für alle Maschinen (--machine), beide CPUs des A5120, U8001, Z8 |
handbuch_k1520dbg, tools/k1520dbg.md |
boot_trace |
nicht-interaktiver Tracer: lokalisiert, wo ein Lauf hängt | tools/boot_trace.md |
k1520disktool |
DiskTool auf der Kommandozeile | tools/k1520disktool.md |
floppy_diag, format_driver, kbd_test, bench_run
|
Diagnose, Formatier-Pipeline, Messung | tools/README.md |
z8kasm, Disassembler |
Assembler für U8001-Firmware, Z80/Z8-Disassembler | tools/z80_disasm.md |
Einstieg in die Fehlersuche: tools/how_to_debug_and_trace.md.
Daneben liegen Gastprogramme mit Quelle und fertigem .com (SERTEST, ROMREAD, EM256-Prüfungen),
die auf die Beispieldisketten kopiert werden.
-
CMake, jede Komponente eine statische Bibliothek (
k1520_bus,k1520_z80,k1520_k5122,k1520_a5120, …), daraus die zwei gemeinsamen Bibliothekenk1520coreundk1520disk, die Werkzeuge und die Testprogramme. -
LOG_LEVEL(0–5) ist die Obergrenze zur Bauzeit; was darüber liegt, wird nicht übersetzt. Die tatsächliche Ausgabe steuert der Logger zur Laufzeit (core/logger.h): Grundstufe, dazu Fenster nach Befehlszähler oder Takt und ein bereichsweiser Schub (K1520_LOG_BOOST). - Zwei Bauverzeichnisse:
build/(LOG_LEVEL 3) undbuild_trace/(LOG_LEVEL 5). Gebaut und getestet wird immer übertools/dev.sh, das vorher neu baut — ein Werkzeug direkt aus einem nicht neu gebauten Verzeichnis testet veraltete Objekte. -
Plattformen: Linux (GCC) und Windows (MSVC, eigener Zweig im
CMakeLists.txt);tools/dev.sh winbaut zur Vorprüfung mit MinGW-w64 und testet unter wine. -
Version aus der Datei
VERSION, abgeleitet übertools/version.py(core/version.h.in→k1520_version()); Konzept in ci_pipeline §7. -
Paket über
packaging/build_payload.shbzw. das Inno-Setup: design/13_distribution, merkposten/paketierung.
Das Testsystem ist nach Ebenen gegliedert (unit, debugtools, integration, cli,
system, python), jede Ebene ein Verzeichnis unter tests/ und zugleich ein
ctest-Label; quer dazu fast/slow. Begründung der Gliederung:
design/12. Ausführen und erweitern: tests/README.md.
Testdisketten: tests/fixtures/README.md.
tools/dev.sh test # Regression (rund 2900 Fälle, einige Minuten)
tools/dev.sh test-format # langsame Formatier- und Bootketten
tools/dev.sh test-matrix # jeder FORMAT.COM-Menüeintrag einzeln
tools/dev.sh test-level unitZwei Grundsätze tragen den Aufbau: Festlegungen haben Wächter — jede Regel in den Merkposten nennt den Test, der sie hält —, und die Python-Ebene prüft, was C++ nicht erreicht: die C-ABI gegen ihre ctypes-Deklarationen und die Verdrahtung der Oberfläche (Qt ohne Bildschirm).
Quellenlage / Belegstatus. Diese Spezifikation wurde gegen die transkribierte Originaldokumentation abgeglichen (
doc/trascripted/: Zentrale Recheneinheit K2526/K2527, Anschlußsteuerung K 8025, Floppy Anschlußsteuerung K 5122, Linieninterface BUS K 1520 (TGL 37271/01), Büro Computer Robotron A5120). Jede Aussage ist markiert: [belegt] = wörtlich in der Originaldoku, [abgeleitet] = aus Schaltungslogik geschlossen (plausibel, aber nicht wörtlich), [Emulator] = Implementierungsentscheidung im Code, [offen] = ungeklärt.
[belegt] TGL 37271/01 (Tabelle 11) definiert auf dem Systembus X1 genau eine Interrupt-Prioritätskette und eine DMA-Kette:
| Signal | Pin | Signal | Pin |
|---|---|---|---|
| /INT | C23 | /NMI | A23 |
| /IEI | C10 | /IEO | A10 |
| /BAI | (DMA-Kette) | /BAO | (DMA-Kette) |
[belegt] Die zweite Kette /IEI1–/IEO1 ist laut TGL kein X1-Signal, sondern ein Mittel, um innerhalb einer Karte mehrere Interrupt-Controller in Reihe zu schalten (TGL Bild 2, Verweis auf Koppelbus-Tabelle 12 / KROS 0314). Wird sie zwischen Karten geführt, dann über den Koppelbus X2. Die konkrete A5120-X2- Belegung ist in den vorliegenden Transkripten nicht enthalten (KROS 0314 fehlt).
[belegt] Verbindung: /IEO (A10) eines Slots → /IEI (C10) des nächsten, über fest verlötete Wire-Wrap-Brücken der Backplane (nicht automatisch durch Slot-Nachbarschaft). Karten ohne Interrupt-Fähigkeit (K3526 OPS, K7024 ABS) müssen /IEI→/IEO brücken oder am Kettenende liegen.
Implementierte Reihenfolge (höchste → niedrigste Priorität) [Emulator]:
K5122 AFS → K8025 ASS → K2526 ZRE
// core/machines/a5120/a5120.cpp:46
bus_.setInterruptChain({&afs_, &ass_, &zre_});Begründung & Auflösung eines Doku-Widerspruchs. Die robotrontechnik-Beschreibung (Büro Computer Robotron A5120) sagt narrativ „die ZRE besitzt die höchste Priorität". Das K2526-Datenblatt ist hier aber präziser und maßgeblich [belegt]:
- Der CTC der ZRE liegt am Ende der 1. Prioritätenkette (Eingang /IEI Systembus, Ausgang /IEO1 Koppelbus) — §2.5 K2526-Doku.
- Die zentrale Baugruppensteuerung (BS-PIO) liegt am Ende der 2. Prioritätenkette (/IEI1 Koppelbus) — §2.9 K2526-Doku.
Die ZRE-Bausteine sitzen also an den Kettenenden = niedrigste Priorität; die
zeitkritischen Peripheriekarten (AFS, ASS) gehen voran. Der Emulator faltet beide
physischen Ketten in eine Kette zusammen: {afs, ass, zre}, wobei zre intern
CTC → BS-PIO ist. Effektiv ergibt das AFS → ASS → ZRE-CTC → ZRE-BS-PIO und bildet
damit die reale Zwei-Ketten-Topologie korrekt nach (CTC am Ende von Kette 1, BS-PIO am
absoluten Ende). Die narrative „ZRE höchste Priorität"-Aussage ist damit als ungenau
einzustufen.
/IEI-Eingang
→ Z80CTC A35 (Q302, Ports 0CH–0FH; Kanal 0 > 1 > 2 > 3) ← höhere Priorität
→ Z80PIO A36 BS-PIO (Q301, Ports 08H–0BH; Port A > Port B)
/IEO-Ausgang
-
[belegt] „Bei der ZRE K 2526 hat der CTC eine höhere Priorität als der BS-PIO"
(K2526-Doku §5). Code:
K2526::setIEI→ctc_.setIEI(iei); bs_pio_.setIEI(ctc_.getIEO()). - [belegt] CTC Kanal 2 → Kanal 3 ist fest auf der Platine verdrahtet (ZC/TO2 → CLK/TRG3) zur Bildung der Systemzeit; Kanal 3 erzeugt die Systemzeit-Interrupts.
- [belegt] Die 2. ZVE (ZVE2, DMA-Prozessor) ist nicht interruptfähig (/INT, /NMI fest high) — sie führt keine ISR aus.
- BS-PIO Port A (Bitmode): gemeinsamer Vektor für /M1 (Einzelschritt), /SUE (Batterie), SPS-Verletzung, /EBF; ROM-Phase nutzt /ASTB-Pfad. Port B: separater Vektor, einzige Quelle B1 (/INT-BS via OUT 00H = OS-Ebenen-Wechsel).
/IEI-Eingang
→ Z80SIO A33 (DFÜ-SIO, Ports 50H–53H; Ch A > Ch B) ← höchste Priorität [belegt]
→ Z80SIO A32 (Tastatur/Drucker-SIO, Ports 5CH–5FH) ← [abgeleitet: Reihenfolge]
→ Z80CTC A34 (Baudraten-Generator, Ports 58H–5BH) ← [abgeleitet: Position]
/IEO-Ausgang
-
[belegt] „Die DFÜ besitzt in jedem Fall die höhere Priorität gegenüber dem Drucker"
(K8025-Doku §2.2.2) ⇒ SIO A33 vor SIO A32. Position von CTC A34 in der Kette ist
abgeleitet (im Code
sio_dfue_ → sio_kbd_printer_ → ctc_a34_). -
Korrektur ggü. früheren Fassungen: A31 (Ports 54H–57H) ist KEIN PIO, sondern ein
U212-Latch-Register ohne Interrupt-Logik [belegt]. Es gehört nicht in die Daisy-Chain.
Der Emulator modelliert es zwar als
Z80PIO pio_a31_(DIL-Schalter-Readout), schließt es aber bereits aus der Kette aus (k8025.cpp:58„PIO A31 excluded from chain") — korrekt. - [belegt] SIO A32 Kanal A (Port 5CH): Tastatur K7637, 9600 Baud IFSS (X4; verdrängt den Zusatzdrucker). Kanal B (Port 5EH): Hauptdrucker IFSS (X3).
- [belegt] SIO A33 Kanal A (Port 50H): DFÜ V.24 (X6). Kanal B (Port 52H): DFÜ IFSS (X5).
- [belegt] CTC A34 Kanal 0 taktet den Drucker-SIO (fest 9600 Bd); die DFÜ-Takte kommen im Normalfall vom ZRE-CTC über den Koppelbus (ZC/TO). Drucker-Priorität ist per Brücken W1:8–11 umschaltbar (zeitkritisch = 1. Kette nach DFÜ; zeitunkritisch = 2. Kette).
/IEI-Eingang
→ Z80PIO ctrl_pio_ (Steuer-PIO A1.2, Ports 10H–13H; Port A > Port B)
→ Z80PIO data_pio_ (Daten-PIO A1.1, Ports 14H–17H; Port A > Port B)
/IEO-Ausgang
- [belegt] Der Steuer-PIO ist „in die Interruptkette für zeitkritische Geräte eingeordnet (/IEI–/IEO)". Interne Reihenfolge Steuer→Daten ist [abgeleitet].
- [belegt] Steuer-PIO Port A läuft im Mode 0 (Output), sein Strobe-Eingang /ASTB = physikalischer INDEX-Puls (IX) des Laufwerks (Pinbelegungstabelle Tor A). Die fallende Flanke löst den Port-A-Interrupt aus → in der ROM-Phase Vektor 0xBA.
- [belegt] Steuer-PIO Port B läuft im Mode 3 (Bitmode) mit Statussignalen (B0 /RDYL, B1 MKE, B5 /WP, B6 /FW, B7 /TO …). Der Interrupt entsteht hier durch Pegelüberwachung der maskierten Bits, nicht über eine /BSTB-Flanke.
- [offen] Welches physikalische Signal in der Bootloader-Phase den Port-B-Interrupt (Vektor 0x60) treiben soll, ist in der Hardware-Doku nicht dokumentiert.
Der Z80 arbeitet im Interrupt-Modus 2 (IM 2). Die Interrupt-Vektortabelle liegt im RAM:
- I-Register × 256 + Vektor = Adresse des 16-Bit-ISR-Zeigers
- Das niederwertigste Bit des Gerätevektors muss 0 sein (gerade Tabellenadressen)
| Interrupt-Quelle | Vektor | Tabellen-Adresse | ISR | Funktion |
|---|---|---|---|---|
| K2526 BS-PIO Port A (/ASTB) | 0xB8 | [0x00B8..B9] | 0x007A | Unbekannt (ROM-intern) |
| K5122 ctrl_pio_ Port A (/ASTB) | 0xBA | [0x00BA..BB] | 0x01C7 | Indexpuls-Dekrement [0x03F7] |
| K5122 ctrl_pio_ Port B | 0xE2 | [0x00E2..E3] | — | Nicht genutzt |
Der Vektor 0xBA (ISR 0x01C7) ist der kritische Index-Puls-Interrupt: Der K5122 ctrl_pio_ Port A empfängt den Floppy-Indexpuls als /ASTB-Signal (Mode 0, Output) und triggert diesen Interrupt. Die ISR dekrementiert [0x03F7] und signalisiert Timeout, falls der Sektor nicht innerhalb von 16 Umdrehungen gelesen wird.
Wichtig: Vektor 0xBA kommt von der K5122 ctrl_pio_ Port A, nicht vom ZRE-CTC.
Nach Übergabe an 0x0437 setzt der Loader LD A,07H / LD I,A. Alle Vektoren werden nun über Seite 0x07 aufgelöst.
| Interrupt-Quelle | Vektor | Tabellen-Adresse | ISR | Funktion |
|---|---|---|---|---|
| K5122 ctrl_pio_ Port B | 0x60 | [0x0760..61] | 0x0624 | Event-ISR: setzt Bit 0 in [0x07F7] |
| K5122 ctrl_pio_ Port A | 0x62 | [0x0762..63] | 0x060E | Timer-ISR: dekrementiert [0x07EC], setzt Bit 1 in [0x07F7] bei Timeout |
Die Loader-Phase erbt IFF1=1 und IM 2 vom Boot-ROM (kein DI im Übergabepfad).
Der Bootvorgang nutzt drei verschiedene Sektor-Leseroutinen (Z80-seitig, RE-Befund) — der Emulator bedient alle drei über einen codierungstreuen Streaming-Lesekopf:
-
Boot-ROM (
0x01DD, läuft auf ZVE2): liest IDAM-Header und Daten mit einem/STR-Strobe am Stück, mit festen Offsets, ohne Markensuche. -
Sekundär-Bootloader (
0x062E, eingehängt via[0x0000]=JP 0x062E): sucht aktiv die Adressmarken (0xFEIDAM,0xFBDatenmarke), verträgt variable Gaps, und verifiziert jeden Sektor per CRC-16. NutztMK= Steuer-Port-A Bit 1 (0xB5→0x87). -
Dritte Stufe / CP/A-Loader (
0x1F7D, 1024-B-Datenbereich): liest IDAM und Daten kontinuierlich (INIR, kein Per-Byte-Strobe) und re-synchronisiert überMK1= Steuer-Port-A Bit 4 (0xB5↔0x85) stattMK(§14.5b).
Alle Routinen synchronisieren über die MK/MK1-Steuersignale (Steuer-Port-A Bit 1
bzw. Bit 4): jede Re-Sync-Flanke lässt den K5122-Datenseparator auf die nächste
Adressmarke synchronisieren — IDAM → DATEN → nächstes IDAM → …. Belegstellen:
ROM 0x0224/0x022D und 0x0249/0x025F; Loader 0x066E/0x0670 und 0x06B9/0x06BB.
Emulator-Modell (aktuell — TrackImage-Streaming, doc/design/07_k5122_afs.md §10). Die
oben unterschiedenen Feld-/Stream-Modelle sind zu einem codierungstreuen Lesekopf-Streaming
vereinheitlicht: die aktive Spur ist eine fertige TrackImage (reale Gaps/Sync/Marken/CRC),
ioRead(0x16) streamt sie byteweise rotierend, und jede MK/MK1-Re-Sync-Flanke
(resyncToNextMark) springt zur nächsten Adressmarke (IDAM→DATEN→nächstes IDAM). Damit
bedient ein Stream ROM, Sekundärlader und dritte Stufe; die früheren
buildField/advanceField/stream_continuous_-Sonderfälle sind entfallen (eine
Standard-IBM-CCITT-Daten-CRC, §8.5). Für den Boot-Read materialisiert
startReadTransfer() den 4×A1-buildFaithfulReadTrack, der Boot-ROM (1 Wegwerf + 3 Reads,
FE@buf[4]) und SYL-Lader (skip-A1-bis-FE) zugleich akzeptiert.
Track-Ende / BUSRQ-Arbitrierung (weiterhin nötig): Nach einer voll gelesenen 128-B-Spur
fällt die ZVE2-Routine in ihre Idle-Schleife L0696 und disabled dort den ctrl-PIO-Port-B-
Interrupt (OUT(13H),03H). Auf echter HW setzt /STR=1 dann /BUSRQ zurück; im durchgängig
gestepperten Emulator gibt K5122::ioWrite bei OUT(13H),03H während eines Lese-Transfers
/BUSRQ frei → ZVE1 übernimmt, verarbeitet die Spur und setzt ZVE2 per OUT(04) zurück.
So trägt der Stream die ganze Boot-Kette: ZVE2 liest ganze Boot-Spuren (cyl 0/1, 128 B), ZVE1
CRC-verifiziert jeden Sektor, und nach 52 Sektoren springt der Loader nach 0x0880 (JP 0x1800)
in die dritte Stufe — das CP/A-Bootsystem, das @OS.COM aus dem 1024-B-Datenbereich lädt.
Die dritte Stufe (CP/A-Bootsystem, Einsprung 0x1800, FCB @OS COM @0x08CD) gibt ihren
Banner aus (CP/A-Bootsystem, Version 05.04.88 laedt @OS.COM …) und liest @OS.COM aus dem
1024-B-Datenbereich. Ihre ZVE2-Routine 0x1F7D (IDAM-Verify) + 0x2038 (Daten-Read) liest IDAM
und Daten in einem kontinuierlichen Strom (INIR) und re-synchronisiert über MK1
(Steuer-Port-A Bit 4, 0xB5↔0x85) statt MK (Bit 1). Fehleranzeige sub_1BF0 baut
"RC;T,Si,Se=TTSSSS" ('C'=CRC, 'S'=Suche, 'U'=Timeout).
Drei Ursachen mussten dafür gelöst werden — alle im aktuellen Stand behoben:
-
ZVE2-Start aus dem Reset (
K2526::zve2StartFromReset). Die Stufe setzt[0x0000]=JP 0x1F7D, resettet ZVE2 (OUT(04)=0x00) und stellt[0x0000]sofort wieder her — sie startet ZVE2 nie explizit (bit0=1).A5120Machine::run()startet ZVE2 aus dem Reset, sobald/BUSRQ(das/STR) assertiert und ZVE2 im Reset steht, sodass es das aktuelle[0x0000]=JP 0x1F7Dfetcht. -
Asymmetrische Mixed-Geometrie (
format_parser.cpp, §14.5c). -
MK1-Re-Sync (
K5122::resyncToNextMarkauf der MK1-Fallflanke). Der letzte Stall (Timeout'U'bei cyl 2/3 head 1) war ein K5122-Feldmodell-Bug: ZVE2s MK1-Datenseparator-Resync wurde ignoriert, sodass ein Daten-0xA1als A1-Adressmarken-Sync missverstanden wurde und die IDAM-Suche entgleiste. Der Fix springt bei MK1 IDAM→DATEN→nächstes IDAM (überspringt die Daten). Kein ZVE1↔ZVE2-Handshake-Bug (das war eine falsche Fährte). Guards: Boot-TestStage3_FullyLoadsAndJumpsToOs+K5122 Continuous1024_MK1ResyncJumpsToNextAddressMark.
Ergebnis: @OS.COM lädt vollständig, die dritte Stufe springt ins OS, und der A5120 bootet
den vollen CP/A-Kaltstart bis zum interaktiven Prompt (CP/A, Version 25.09.89 …).
0x1F7D erwartet den 1024-B-Datenbereich hardkodiert ab cyl 2 (IDAM cyl=2, size_code=3).
Die Seiten sind interleaved (cyl0/A, cyl0/B, cyl1/A, cyl1/B, …); der Systembereich sind drei
physische 128-B-Seiten (cyl 0 beide Seiten + cyl 1 Seite A), der 1024-B-Datenbereich beginnt
bei cyl 1 Seite B:
| phys. Spur | Datei-Offset | Inhalt | Sektorgröße |
|---|---|---|---|
| cyl 0 A | 0x0000 |
Stage-1-Boot + SYL | 128 B |
| cyl 0 B | 0x0D00 |
Füller (0x53) |
128 B |
| cyl 1 A | 0x1A00 |
Stage-2-Loader + SYL | 128 B |
| cyl 1 B | 0x2700 |
erste Datenspur (Füller) | 1024 B |
| cyl 2 A | 0x3B00 |
CP/M-Verzeichnis ("@OS COM" + CPABCGEN/FORMAT/…) |
1024 B |
| cyl 2 B | 0x5000 |
@OS.COM-Daten … |
1024 B |
3 × 3328 + 5120 = 0x3B00 → das Verzeichnis liegt exakt auf der cyl-2-Seite-A-Grenze,
sektor-aligned. Format (format_parser.cpp, asymmetrisch): {0,0,0,1,26,128} +
{1,1,0,0,26,128} + {1,1,1,1,5,1024} + {2,79,0,1,5,1024}; findTrack/sectorOffset werten
den Head-Bereich aus (mid-Zylinder-Asymmetrie cyl 1 A = 128 B, cyl 1 B = 1024 B). Würde man cyl 1 B
als 128 B modellieren, verschöbe sich der Datenbereich 0x700 nach vorn und die @OS.COM-Alloc-
Blöcke wären fehlausgerichtet.
Der Loader bewaffnet zusätzlich ctrl_pio_ Port B (Vektor 0x60, Event-ISR 0x0624)
via OUT(13H),97H/AFH. Dieser Interrupt wird im Emulator nie ausgelöst (setBSTB nie
aufgerufen) — der volle Boot gelingt trotzdem (§14.5b), er ist also offenbar nicht nötig.
[belegt] Real existiert die zweite Kette: das K2526-Datenblatt führt /IEI1 (X2 Pin 26C) und /IEO1 (X2 Pin 26A) und ordnet die BS-PIO „am Ende der 2. Prioritätenkette" ein. Der ZRE-CTC speist seinen /IEO als /IEO1 auf den Koppelbus.
[spekulativ] Die konkrete A5120-Ringverdrahtung über alle Karten ist in den
vorliegenden Transkripten nicht belegt (KROS 0314 / TGL-Tabelle 12 fehlt). Die früher
hier angegebene Kette ZRE.IEO1 → ASS.IEI1 → … → ABS → zurück ist eine Annahme, nicht
gesichert. Sicher ist nur: BS-PIO der ZRE = niedrigste Priorität; ein „zeitunkritischer"
Drucker-SIO der K8025 kann (Brücken W1:8–11) in dieser Kette liegen.
[Emulator] Diese Kette ist nur als Koppelbus-Platzhalter vorhanden, nicht verdrahtet
(backplane.h). Für den aktuellen Boot ist sie nicht relevant (alle Boot-Interrupts laufen
über die primäre Kette).
| Quelle | Mechanismus | NMI? |
|---|---|---|
| Q240 MemIOProtect: unerlaubter E/A-Befehl |
/IORQ über A39/6 → /NMI (flüchtig) |
ja → bus_.assertNMI()
|
| Q240: unerlaubter Speicher-Schreibzugriff | setzt BS-PIO Port A Bit 3 (SPS) + /MEMDI=0
|
nein (maskierbarer Port-A-Interrupt) |
| /SUE (Koppelbus): Spannungsüberwachung/Netzeinbruch | permanenter NMI | ja (im Emulator nicht implementiert) |
Wichtige Korrektur: Nicht jede Schutzverletzung löst NMI aus. Nur die E/A-Verletzung
führt zum NMI; die Speicher-Schreibschutzverletzung erzeugt einen maskierbaren Interrupt
über BS-PIO Port A Bit 3. NMI führt zu RST 0066H. Der NMI-Handler liest BS-PIO Port A Bit 2
zur Quellenunterscheidung (low=Sonderbedingung permanent, high=E/A-Verletzung flüchtig) und
schreibt Port 02H (/RES-SPA) zum Rücksetzen.
Der volle CP/A-Kaltstart (Boot-Kette bis interaktiver Prompt) funktioniert; die Restpunkte sind nicht boot-kritisch:
| Lücke | Auswirkung |
|---|---|
ctrl_pio_ Port B Floppy-Events (setBSTB) |
Event-ISR 0x0624 (Vektor 0x60) feuert nie; für den Boot nicht nötig, evtl. obsolet (§14.7a) |
| Port 0xEE nicht dekodiert (vermutl. CTC-Alias 0x0E) | niedrige Priorität; 1 Schreibzugriff in 9M Zyklen, vermutl. Nebenwirkung |
| Koppelbus-Kette (/IEI1–/IEO1) nicht verdrahtet | für Boot irrelevant; niedrigprioritäre BS-PIO-Interrupts nicht gereiht |
| RETI-Erkennung im Z80CTC | im Z80PIO vorhanden (onRETI/IUS); verschachtelte CTC-Interrupts ggf. unvollständig |
| Post-Boot VRAM-Wipe nach ~50–65M Idle-Takten | kosmetisch; vermutl. Uhr-/Timing-Drift + streunende ZVE2-Floppy-Aktivität (doc/open_points.md) |
Hat die K3526 außer dem 64KB DRAM irgendwelche Steuerlogik (Banking-Register, Schreibschutz-Logik)? Oder ist sie wirklich nur ein passiver 64KB DRAM-Block?
Beide sind inzwischen als Maschinen umgesetzt (§2.6). Was am Gerät noch zu klären ist, führen die Pläne als Anwenderfragen: K8915 in design/16 §6 und merkposten/k8915, PRG 710 in design/20 §8 und merkposten/prg710. Weitere offene Punkte: open_points.md.
Die seriellen Kanäle der Karten gehen über Telnet, RFC 2217 (Client oder Server)
oder in eine Datei nach außen. Je Maschine ein SerialHub (core/serial/) mit eigenem
E/A-Faden; je Schnittstelle ein Wandler, der in Maschinenzeit nach der vom Gast
programmierten Baudrate taktet und verlustfrei durch Rückstau ist. Die Karten liefern nur
einen SerialAnschluss und kennen kein Netz. Der Gast ist maßgeblich: eine Anfrage der
Gegenstelle ändert Baudrate und Format nie.
design/19, merkposten/serielle_schnittstellen.
- RAM-Floppy RAF 128/512/2M auf E/A 88H/89H; RESET erhält den Inhalt, die Oberfläche legt ihn als Stand-by-Ablage ab. design/22, merkposten/raf.
- Lochstreifen K6022 (Leser und Stanzer) auf E/A E0H–E7H; Bandformate roh, Intel HEX und ASCII-Bild, der Stanzer schreibt nach einer Pause zurück. design/23, merkposten/lochstreifen.
Beide werden nach dem Anlegen und vor dem ersten Lauf gesteckt (§5.5).
Ein Greaseweazle-Adapter ersetzt die Datei als Bindung des Mediums; der Kern kennt den
Adapter nicht (§8.8). Das Python-Paket greaseweazle ist freiwillig — fehlt es, fehlt der
Menüpunkt, und die Tests laufen trotzdem.
design/14, merkposten/physische_diskette.
Das Anwenderpaket (~2 MB) bringt Python und Qt bei der Installation in eine eigene Umgebung innerhalb der Installation, ohne Administratorrechte; unter Windows über ein Inno-Setup. Arbeitsdisketten liegen im Dokumentenordner, getrennt vom Programm. design/13_distribution, packaging/README.
Feinentwürfe je Baugruppe und Maschine: doc/design/. Festlegungen mit Wächtern:
doc/merkposten/.