Základ multiplatformního emulátoru československého počítače FK-1 v C++20 a SDL3.
Aktuální první milník obsahuje:
- samostatnou knihovnu
fk1_corebez závislosti na SDL, - procesor Z80 běžící na 4 MHz přes knihovnu
redcode/Z80, - deterministický společný čas 12 MHz (tři tiky na takt CPU),
- přepínatelnou mapu ROM/Video RAM/RAM podle skutečného zapojení,
- tři obvody 8255 PPI s režimy 0/1, BSR a handshake signály,
- čítač 8253 se šesti režimy, BCD, LSB/MSB sekvencemi a count latch,
- UART 8251 s asynchronním i synchronním režimem a sériovými hodinami z 8253,
- obousměrný binárně transparentní raw TCP most pro sériový port,
- prioritní řadič 3214 s maskou, prioritou I7–I0 a vektory IM2,
- trvalé video/myš latche a úrovňové propojení požadavků PPI, PIT a UART,
- časovanou dvoufázovou kvadraturu kuličkové myši se dvěma tlačítky,
- ASCII klávesnici s Ctrl kódy, speciálními klávesami a emulovaným autorepeatem,
- virtuální tiskárnu Centronics s handshake 8255 a binárním výstupem do souboru,
- dvě 8palcové mechaniky s přesnou rotací 360 RPM, Track 00, Write Protect a Index,
- časované úplné FM stopy, které oddělují datový bajt a masku hodinových pulzů,
- import sektorových obrazů
.8sddo úplné IBM 3740 stopy se skewem, gapy a CRC, - čtení, zápis a formátování low-level FM bajtů přes handshake datové 8255,
- zpětnou analýzu změněných stop a uložení platných sektorů do
.8sd, - základ video RAM a převod obrazu 512 × 256 podle
hardware-spec.md, - bootovací ROM vloženou do programu a přepínatelnou pomocí
--rom, - Dear ImGui rozhraní s menu, lištou mechanik, stavovým řádkem a nativními dialogy SDL3,
- malé testy časování a video adresace,
- CI sestavení na Linuxu a Windows.
Obvody 8255, 8253, 8251 a prioritní řadič jsou připojené k portové mapě.
Hardwarové vedlejší účinky čtení skupin 0x30, 0x50 a 0x70 a zápisů do
0x30, 0x50 a 0x70 jsou oddělené stejně jako ve skutečném dekodéru.
Bez zvoleného výstupního souboru se tiskárna hlásí offline. SDL myš se po
zachycení ukazatele převádí na původní fázové signály AX/BX a AY/BY.
- CMake 3.24 nebo novější,
- kompilátor s podporou C++20,
- Git,
- SDL3 3.2 nebo novější.
CMake nejprve hledá nainstalované SDL3. Pokud je nenajde, standardně stáhne ověřenou stabilní verzi SDL 3.4.14 pomocí FetchContent. Automatické stažení lze vypnout volbou -DFK1_FETCH_SDL3=OFF.
Grafické rozhraní používá připnutou verzi Dear ImGui, kterou CMake stáhne a staticky připojí k frontendu. Dear ImGui je dostupné pod licencí MIT.
Procesor používá redcode/Z80 a jeho
hlavičkovou závislost redcode/Zeta. CMake
standardně stáhne revize připnuté v CMakeLists.txt a sestaví je staticky jako
součást projektu. Stažení lze vypnout pomocí -DFK1_FETCH_Z80=OFF, pokud je
statický CMake balíček Z80 už nainstalovaný. Obě knihovny jsou dostupné pod
LGPL-3.0-or-later; výsledný emulátor zůstává pod GPL-3.0.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build --parallel
ctest --test-dir build --output-on-failure
./build/fk1Volba FK1_STATIC_BUILD=ON na Linuxu staticky připojí SDL3, Dear ImGui,
Z80/Zeta, libstdc++ a libgcc. Bootovací ROM je stejně jako v každém jiném
buildu vložena přímo v programu. Glibc a systémový grafický stack zůstávají
dynamické; v cílovém systému proto musí být dostupné odpovídající runtime
knihovny X11 nebo Wayland a grafický ovladač.
Při sestavení SDL3 ze zdrojů jsou zároveň potřeba vývojové hlavičky alespoň
jednoho z těchto grafických backendů.
cmake -S . -B build/linux-static \
-DCMAKE_BUILD_TYPE=Release \
-DFK1_FETCH_SDL3=ON \
-DFK1_STATIC_BUILD=ON
cmake --build build/linux-static --parallel 2
ctest --test-dir build/linux-static --output-on-failure
./build/linux-static/fk1Výsledný soubor nepotřebuje libSDL3.so, libstdc++.so ani libgcc_s.so.
Je nativní pro architekturu, na které vznikl: build pro Raspberry Pi je proto
nutné vytvořit přímo na aarch64/armhf, build pro běžné PC na x86_64.
Dynamické závislosti lze zkontrolovat příkazem:
ldd build/linux-static/fk1Projekt je na Windows primárně ověřován v prostředí MSYS2 UCRT64. V terminálu MSYS2 UCRT64 nejprve nainstalujte nástroje a nativní SDL3:
pacman -Syu
pacman -S --needed \
git \
mingw-w64-ucrt-x86_64-toolchain \
mingw-w64-ucrt-x86_64-cmake \
mingw-w64-ucrt-x86_64-ninja \
mingw-w64-ucrt-x86_64-sdl3Potom projekt sestavte v témže UCRT64 terminálu:
cmake -S . -B build/ucrt64 -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DFK1_FETCH_SDL3=OFF
cmake --build build/ucrt64 --parallel
ctest --test-dir build/ucrt64 --output-on-failure
./build/ucrt64/fk1.exeVolba FK1_FETCH_SDL3=OFF zaručí, že se použije SDL3 spravované MSYS2 a
nezačne se sestavovat další kopie knihovny přes FetchContent.
Volba FK1_STATIC_BUILD=ON vytvoří samostatný fk1.exe, do kterého se
staticky připojí SDL3, Dear ImGui, libstdc++, libgcc a libwinpthread:
cmake -S . -B build/ucrt64-static -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DFK1_FETCH_SDL3=OFF \
-DFK1_STATIC_BUILD=ON
cmake --build build/ucrt64-static --parallel
ctest --test-dir build/ucrt64-static --output-on-failure
./build/ucrt64-static/fk1.exeVýsledný program nepotřebuje vedle sebe MSYS2 ani SDL3.dll. Stejně jako
každý běžný Windows program nadále používá systémové knihovny Windows, například
KERNEL32.dll a USER32.dll. Plně statický Windows režim je podporován pro
MinGW/MSYS2; při použití jiného Windows toolchainu CMake skončí s jasnou chybou.
Seznam importovaných DLL lze zkontrolovat příkazem:
objdump -p build/ucrt64-static/fk1.exe | grep 'DLL Name'Soubor roms/boot.rom se při konfiguraci CMake převede do C++ dat a vloží
přímo do knihovny fk1_core. Běžné ani statické fk1.exe proto při spuštění
nepotřebuje externí ROM soubor. Výchozí obraz má 2048 bajtů.
Alternativní bootovací nebo diagnostickou ROM lze vybrat za běhu:
./build/ucrt64/fk1.exe --rom cesta/k/diagnostic.romPřijímají se přesně obrazy o velikosti 2, 4, 8 nebo 16 KiB. V mapě po resetu
ROM zabírá rozsah 0x0000–0x3FFF; menší obrazy se v tomto rozsahu zrcadlí
podle své skutečné velikosti. Vybraný zdroj a velikost lze zkontrolovat bez
otevření SDL okna:
./build/ucrt64/fk1.exe --rom-info
./build/ucrt64/fk1.exe --rom cesta/k/diagnostic.rom --rom-infoJinou ROM lze také vložit natrvalo už při sestavení:
cmake -S . -B build/custom -G Ninja \
-DFK1_BOOT_ROM=cesta/k/diagnostic.romdisk/boot.8sd je surový sektorový obraz v pořadí stopa–sektor. Musí mít
přesně 77 × 26 × 128 = 256 256 bajtů. Bootovací disketu připojíte do
mechaniky A takto:
V grafickém rozhraní slouží pro každou mechaniku samostatný řádek Drive A/B.
Tlačítko New… vytvoří novou zapisovatelnou image se všemi sektorovými bajty
nastavenými na E5h; existující soubor nepřepíše. Open… zobrazí nativní
dialog pro .8sd, Writable určuje, zda nově otevřený obraz dovolí zápis,
a Eject médium vysune. Stejné operace jsou v menu File > Drive A/B;
zde lze režim pouze pro čtení nebo zápis vybrat přímo. Výchozí stav je bezpečně
pouze pro čtení.
Před výměnou nebo vysunutím zapisovatelného média se low-level stopy analyzují a uloží. Neplatný nově vybraný soubor nevysune právě používanou disketu. Pokud nelze změněné médium převést nebo uložit, zůstane vložené v mechanice a chyba se ukáže ve stavovém řádku.
Stejné obrazy lze nadále připojit už na příkazové řádce:
./build/ucrt64/fk1.exe --disk-a disk/boot.8sdVolba --disk-b připojí stejný formát do mechaniky B. Tyto dvě volby zůstávají
bezpečně pouze pro čtení. Pro zápis použijte výslovnou variantu -rw, ideálně
nejprve na kopii obrazu:
cp disk/boot.8sd disk/work.8sd
./build/ucrt64/fk1.exe --disk-a-rw disk/work.8sdK dispozici je také --disk-b-rw. Při připojení se každý sektorový obraz
rozvine do celé časované IBM 3740 FM stopy: 5 208 bajtů na otáčku, fyzický skew
6, IAM/IDAM/DAM, mezery a CRC-16. Řadič čte i zapisuje jednotlivé low-level
bajty podle skutečné rotační polohy; neprovádí okamžité sektorové operace.
Při normálním ukončení se zapisovatelná stopa znovu analyzuje podle IDAM a DAM.
Jestliže je přítomno všech 77 × 26 sektorů, jejich 128bajtová datová pole se
uloží zpět do stejného .8sd. Neúplná nebo nečitelně naformátovaná stopa se
neuloží a původní soubor zůstane beze změny.
Frontend používá pro kompatibilitu výchozí šířku Index impulzu 1 700 µs. Lze ji změnit nebo vypnout:
./build/ucrt64/fk1.exe --disk-a disk/boot.8sd --index-us 400
./build/ucrt64/fk1.exe --disk-a disk/boot.8sd --index-us 0Virtuální tiskárnu připojí volba --printer. Výstupní bajty se bez převodu
znakové sady nebo konců řádků přidávají do zvoleného binárního souboru:
./build/ucrt64/fk1.exe --disk-a disk/boot.8sd --printer vystup.prnS touto volbou je PC5 (ONLINE/SELECT) v jedničce. Každý bajt z PA první 8255 se
zapíše do souboru a virtuální tiskárna vytvoří /ACK_A; při povoleném INTE tak
vznikne I0. Bez --printer zůstává PC5 v nule, tiskárna data nepotvrdí a žádný
I0 nevytvoří. Soubor se otevírá v režimu append, takže dřívější obsah nemaže.
Dvě běžící instance lze propojit přímo. První otevře poslouchající konec:
./build/ucrt64/fk1.exe --disk-a disk/boot.8sd --serial-listen 127.0.0.1:2025Druhá se k němu připojí:
./build/ucrt64/fk1.exe --disk-a disk/boot.8sd --serial-connect 127.0.0.1:2025Volby --serial-listen a --serial-connect se vzájemně vylučují. Listener
přijme vždy jednoho klienta a po jeho odpojení znovu čeká. Klient se po ztrátě
spojení pokouší připojit znovu každou sekundu. Stav spojení se projeví na
vstupech CTS a DSR 8251; při rozpojení jsou oba neaktivní. Bez kterékoli sériové
volby zůstává CTS aktivní kvůli původním diagnostickým rutinám a DSR neaktivní.
Přenos je surový osmibitový TCP stream: emulátor nepřidává echo, nemění konce řádků a nepoužívá Telnet protokol. Lze proto připojit také terminál nastavený na režim „Raw“, ale ne Telnet. Příchozí bajty se serializují podle právě naprogramovaného režimu, parity a dělení 8251 a vstupují přes RxC z 8253. Odchozí bajt se odešle do TCP až po dokončení celého emulovaného rámce na TxC. Fronty jsou omezené a při zaplnění využijí TCP backpressure. Při zaplnění výstupní fronty emulátor dočasně deaktivuje CTS, takže 8251 pozastaví další rámce bez neomezeného růstu paměti.
Pro propojení na jednom počítači je nejbezpečnější 127.0.0.1. Adresa
0.0.0.0 zpřístupní listener i z okolní sítě, takže ji používejte jen záměrně.
Okno emulátoru se ukončuje jeho zavřením nebo přes File > Exit. Klávesa F5
pozastaví nebo obnoví emulaci a F12 zachytí či uvolní myš. Escape se nadále
předává emulovanému stroji jako kód 0x1B.
Pro práci bez SDL lze frontend vypnout:
cmake -S . -B build -DFK1_BUILD_FRONTEND=OFF
cmake --build build --parallel
ctest --test-dir build --output-on-failurefk1_core vlastní procesor, fyzickou ROM, 16 KiB Video RAM a 64 KiB hlavní
RAM. Po resetu vidí Z80 ROM na 0x0000–0x3FFF, Video RAM na
0x4000–0x7FFF a hlavní RAM na 0x8000–0xFFFF. Čtení libovolného zrcadla
I/O skupiny 0x30 zpřístupní hlavní RAM v celém 64KiB prostoru; čtení skupiny
0x50 vrátí výchozí mapu. Horních 32 KiB je v obou mapách tatáž fyzická RAM a
videoobvod stále čte samostatnou Video RAM.
Všechny emulované události postupují po společné 12MHz časové ose. SDL3 zůstává na hranici aplikace: předává hostitelské vstupy, zobrazuje hotový framebuffer a pouze reguluje rychlost běhu. Tím hostitelský čas ani SDL události neurčují pořadí emulovaných hardwarových událostí.
I/O registry zachovávají hardwarová zrcadla; skupinu vybírá port & 0x70 a
registr 8255/8253 bity A1–A0. UART používá pouze A0.
| Skupina | Model |
|---|---|
0x00 |
8255 tiskárny a klávesnice |
0x10 |
tříkanálový 8253 |
0x20 |
datová 8255 disku |
0x30 |
zápis masky prioritního řadiče 3214 |
0x40 |
8251 UART |
0x50 |
pomocný diskový registr |
0x60 |
řídicí 8255 disku a videa |
0x70 |
video I4 latch a surové signály myši/I3 |
PPI modeluje mode-set, BSR, vstupní a výstupní latche, aktivně nízké
STB/ACK/OBF, IBF, interní povolení přerušení a INTR. PIT podporuje režimy
0–5, binární i BCD počítání, všechny přístupové sekvence a latch příkaz; 8254
Read-Back je záměrně ignorován. Kanál 0 dostává 50Hz V impulzy a kanál 1 běží
z přesného 1MHz dělení společného času. Jeho výstup taktuje RxC i TxC 8251.
UART implementuje příkazy a stavové bity 8251, dělení ×1/×16/×64, sériové
vysílání a příjem, chyby a synchronní režim s jedním nebo dvěma SYN znaky.
To zahrnuje inicializační sekvenci 0x3C, 0x55, 0x55, 0x33 použitou v
doc/DIAG.MAC. Volitelný raw TCP frontend připojuje dokončené vysílané znaky
k síti a síťové bajty vrací přes časovaný sériový vstup; neobchází tak režim ani
hodiny emulovaného obvodu.
Virtuální Centronics používá PA první 8255 v handshake režimu 1. Online stav pochází z připojeného hostitelského souboru, přijaté osmibitové hodnoty čekají ve frontě jádra a frontend je průběžně zapisuje jako surová binární data.
SDL key-down se převádí na logický osmibitový kód. Datové vodiče klávesnice jsou
aktivní v nule, proto se celý bajt na rozhraní klávesnice–8255 invertuje a přes
/STB_B předá vstupnímu handshake portu B první 8255. Uvolnění klávesy neposílá
žádný kód. Hostitelský autorepeat se ignoruje. Jádro po 0,5 s držení samo opakuje
stejný invertovaný kód přes emulovaný čas frekvencí 4 znaky/s.
Běžné znaky používají ASCII. Ctrl se řídí terminálovým převodem znak & 0x1F,
tedy Ctrl+@ až Ctrl+_ dávají 0x00–0x1F. Escape je 0x1B, Backspace
0x7F, Tab 0x09 a Enter 0x0D.
| Hostitelská klávesa | FK-1 | Kód | Se Shift |
|---|---|---|---|
| Delete | ROL | 0x80 |
0x81 |
| Insert | COPY | 0x82 |
0x83 |
| End | BREAK | 0x84 |
0x85 |
| ↑ / ↓ / → / ← | kurzor | 0xC1 / 0xC2 / 0xC3 / 0xC4 |
stejné |
| Home | Home | 0x8D |
stejné |
| F1 / F2 / F3 | uživatelská 1–3 | 0xD0 / 0xD1 / 0xD2 |
stejné |
Kliknutí do okna zachytí hostitelský ukazatel; klávesa F12 zachycení zapíná a vypíná. První kliknutí slouží pouze k zachycení, aby FK-1 nedostal neúplný stisk. Potom levé a pravé tlačítko ovládají aktivně nízké vstupy T1 a T2. Ztráta focusu ukazatel uvolní a obě tlačítka bezpečně pustí.
Relativní pohyb nevytváří souřadnice. Jádro jej převádí na původní kvadraturní
signály po jedné hraně za 1 ms emulovaného času. Při pohybu doprava vede BX
před AX: 00 → 10 → 11 → 01 → 00. Při pohybu dolů vede AY před BY:
00 → 01 → 11 → 10 → 00. Opačné směry obě posloupnosti obracejí. Osy se čtou
jako AX/BX na PD0/PD1 a AY/BY na PD2/PD3. T1/T2 jsou na PD4/PD5, v klidu
v jedničce a při stisku v nule. Každá hrana při PA3=1 nastaví I3; čtení portu
0x70 vrátí aktuální stav a I3 zruší.
Jádro modeluje dvě nezávislé jednostranné 8palcové mechaniky se 77 stopami.
Každá vložená disketa se otáčí rychlostí 360 RPM, tedy přesně jednu otáčku za
2 000 000 ticků společného 12MHz času. Výběr mechaniky přes PC5 datové 8255
rotační fázi nemění a obě vložená média se otáčejí současně. Prázdná mechanika
se netočí a negeneruje Index.
Řídicí 8255 promítá stav vybrané mechaniky na PC4–PC7: Track 00, Write Protect,
pevnou jedničku a Index. Krok hlavy se dokončí sestupnou hranou PC0. Polarita
PC1 je převzata přímo z doc/DIAG.MAC: nula pohybuje ke stopě 0 a jednička k
vyšší stopě. Systémový reset nemění vložené médium, polohu hlavy ani rotační
fázi, pouze vrátí sdílenou volbu mechaniky na B.
FloppyDiskImage ukládá pro každou stopu uspořádané položky TimedFmByte:
struct TimedFmByte {
uint32_t tick; // poloha v otáčce 0 až 1 999 999
uint8_t data;
uint8_t clocks; // FF běžný bajt, D7/EF dílčí masky, C7 IDAM/DAM
};Tím zůstávají zachované mezery, rotační poloha i chybějící FM hodinové pulzy
adresních a datových značek. Překrývající se 32µs bajty se při sestavení stopy
odmítnou. Ibm3740SectorImage převádí .8sd do této reprezentace podle
referenční implementace FK-1, včetně sektorového skewu 6 a CRC počítaného přes
značku i pole.
Šířka Indexu zůstává výslovným parametrem
Machine::set_floppy_index_pulse_width() v tickách; samotné jádro má bezpečný
výchozí stav nula a SDL frontend používá 1 700 µs. Čtecí
cesta rozpoznává IDAM/DAM, předává i samotnou značku přes handshake PB a
ukončuje pole pomocí counteru 2. TOUT/I6 se podle E14/8 a komentovaných rutin
v doc/DIAG.MAC hlídá až při RE+RDM, tedy při čekání na datovou značku po
nalezeném ID; hledání adresní značky může bezpečně projít přes Index.
Zápisová cesta čeká na náběžnou hranu OUT1, převezme předem připravený bajt z
PA, vytvoří /ACK_A a potom pokračuje po 32 µs až do OUT2. Chybějící FM
hodinové pulzy zapisuje přímo podle C3/C4 na PA0/PA1 řídicí 8255; jejich masky
jsou FF, D7, EF a C7.
FOR začne fyzický zápis prvním Indexem a druhý Index jej přímo ukončí; I6
zůstává vyhrazen skutečnému hardwarovému timeoutu.
Regresní test bootuje skutečný systém a příkazem SAVE 100 BIG.COM ověřuje
vícesektorový zápis, zachování všech IDAM/DAM, adresář CP/M i zpětný export do
.8sd.
Zápis do libovolného zrcadla skupiny 0x30 povolí 0 až 8 vstupů od nejvyšší
priority I7. Řadič drží /INT procesoru podle nejvýše prioritního aktivního
zdroje a při Z80 INTA dodává sudé vektory IM2 0x00 až 0x0E. Potvrzení
požadavek samo nemaže.
I3 od myši a I4 od 50Hz videa jsou latche. Čtení 0x70 vrátí aktuální surový
bajt myši a zruší pouze I3; zápis 0x70 zruší pouze I4. PA3 a PA2 řídicí 8255
povolují zachycení nových hran/impulzů, ale již nastavený latch neruší. PD0–PD5
nesou v pořadí AX, BX, AY, BY, T1 a T2 podle listu 5 schématu.
I7 je úrovňový součet INTR_A OR INTR_B datové 8255 a jeho náběžná hrana
taktuje counter 2; synchronizační značka se přenese do 8255 ještě před začátkem
počítání bajtů pole. I5 sleduje OUT2, I2 je TXRDY OR RXRDY, I1 je klávesnice
INTR_B a I0 tiskárna INTR_A. I6 timeout se ruší přeprogramováním counteru 1,
vypnutím diskového časování nebo vynulováním pomocného registru, jak používá
obsluha v boot.rom.