Phosphoric › Artikelen › LOCI-uitbreidingen
De grenzen van de Oric verleggen: vijf experimentele uitbreidingen voor de LOCI-kaart
Hoe een cycle-exacte emulator dienstdoet als hardware-testbank — met de ABI, de registers en de schema's.
Over de foto's. De illustraties van de LOCI-kaart zijn afkomstig uit de officiële hardwaredocumentatie van sodiumlb (loci-hardware) en behoren hun auteur toe.
- LOCI-kaart, productfoto's: raxiss.com
- Verkooppagina: Tindie — 8BitClub
- Ontwikkeldraad: forum.defence-force.org t=2593
1. De hardwarecontext
De Oric-1 en de Atmos (1983) draaien op een
MOS 6502 op 1 MHz, met 64 KB adresruimte waarvan het bovenste deel
($C000-$FFFF) wordt ingenomen door de BASIC-ROM. Geen
bedrade vermenigvuldiging, geen geheugenbanken, geen moderne opslag: alles loopt
via de uitbreidingsbus achter in de machine.
De LOCI-kaart (Lovely Oric Computer Interface) van sodiumlb wordt op deze bus aangesloten. Technisch gezien is het een afgeleide van de Picocomputer 6502 (RP6502): een microcontroller RP2040 (een Raspberry Pi Pico) vervult de rol van een MIA (Media Interface Adapter) die zich aan de Oric presenteert als een in-/uitvoerapparaat. Hij emuleert diskettedrives en cassettes, beheert een SD-kaart en fungeert als USB-host — via deze USB-poort wordt een Wi-Fi-modem (aparte dongle, de PicoWiFiModemUSB) of een HID-apparaat (muis, controller) aangesloten; Wi-Fi zit niet ingebouwd op de LOCI-kaart zelf. Twee adresseringsvlakken zijn hier voor ons van belang:
Oric-adresruimte (64 KB)
$0000 ┌──────────────────────────────┐
│ RAM │
$0300 ├──────────────────────────────┤
│ VIA 6522 $0300-$030F │
$0310 │ Microdisc WD1793 $0310-$031F │
├──────────────────────────────┤
$0380 │ ACIA 6551 (LOCI) $0380-$0383 │ ← seriële console / modem
├──────────────────────────────┤
$03A0 │ MIA (LOCI) $03A0-$03BF │ ← API-venster 32 bytes
├──────────────────────────────┤
$C000 │ ROM BASIC $C000-$FFFF │ ← doel van de bank-overlay
$FFFF └──────────────────────────────┘
De emulator Phosphoric (cycle-exact, C11) reproduceert deze kaart getrouw, wat een zeldzame aanpak mogelijk maakt: de spec van een uitbreiding schrijven, ze coderen en ze valideren met deterministische tests — voordat je de soldeerbout aanraakt. Een detail dat telt: de auteur bezit een LOCI-kaart, maar geen Oric. De softwaretestbank is geen luxe, het is de enige testruimte.
2. De fastcall-ABI — hoe de 6502 de kaart aanroept
Alles berust op een venster van 32 registers op
$03A0-$03BF (de MIA). Hier is de werkelijke kaart van de registers die door
de ABI worden gebruikt (namen getrouw aan de firmware):
Offset Adres Register Rol
------ ------- ---------------- ------------------------------------------
$00 $03A0 CONS_FLAGS bit7 = TX vrij, bit6 = RX gereed
$01 $03A1 CONS_TX console schrijven (UART)
$02 $03A2 CONS_CHAR console lezen (verbruikt de byte)
$0C $03AC API_STACK xstack-pointer (argumentenstack)
$0D $03AD API_ERRNO_LO errno laag
$0E $03AE API_ERRNO_HI errno hoog
$0F $03AF API_OP ← HIER SCHRIJVEN start de operatie
$12 $03B2 BUSY bit7 = kaart bezig
$14 $03B4 API_A retourwaarde A
$16 $03B6 API_X retourwaarde X
$18 $03B8 API_SREG 16-bits retour (SREG)
include/io/loci.h).Het aanroepprotocol (fastcall) verloopt in vier stappen:
6502 (Oric) MIA (LOCI, µC)
─────────── ──────────────
1. push args ──► xstack ($03AC)
2. set A/X (directe parameters)
3. write op ──► API_OP ($03AF) ───► start de handler
├─ BUSY=1
poll BUSY ($03B2) ◄──────────────┤ voert uit
└─ BUSY=0, vult API_A/X/SREG, ERRNO
4. read API_A/API_X ($03B4/$03B6) ◄─ resultaat
Elke nog vrije opcodewaarde is een toegangspunt voor een
nieuwe functie. De standaardoperaties lopen van $01 tot $98
(klok, open/read/lseek, mappen, image
mounten, TAP…). In de ongebruikte opcodes nestelen zich onze
vijf uitbreidingen:
$A7 SET_BANK schakelbare bank 16 KB (--loci-bank)
$A8 STREAM_BANK asset-streamer (--loci-bank)
$A9 MATH rekencoprocessor (--loci-coproc)
$AA ACIA_RELIABLE betrouwbare ACIA-modus (seqlock) (opt-in-modus)
+ acia_stat_checked : lossless RX-handshake (--loci-acia-rx-nag)
Vangrail by design. Zonder de bijbehorende activeringsvlag geeft de opcode
ENOSYSterug (errno 13) — precies zoals een niet-gepatchte firmware. De Oric-software kan dus de aanwezigheid van de uitbreiding detecteren en terugvallen op haar eigen routines. Het standaardgedrag van de LOCI blijft strikt dat van de oorspronkelijke hardware.
3. Rekencoprocessor — $A9
Het probleem. De 6502 heeft geen bedrade vermenigvuldiging, deling of zwevende komma. Elke operatie is een softwareroutine: traag, omvangrijk, kostbaar in cycli. Op een machine van 1 MHz telt een vermenigvuldiging 16×16 honderden cycli.
Het idee. De berekening delegeren aan de microcontroller van de kaart, die veel
sneller is, via de bestaande fastcall-ABI — één enkele opcode $A9,
de operatie-subcode in API_A, de operanden op de xstack, het
resultaat in API_A/SREG.
; conceptueel voorbeeld: A×B via de coprocessor
LDA #<op_mul : STA API_A ; operatie-subcode
... push A, B op de xstack ($03AC)
LDA #$A9 : STA API_OP ; start MATH
; poll BUSY, lees dan het 32-bits resultaat in SREG
De implementatie. Een geïsoleerd bestand,
src/io/loci_math.c (op_math), aangesloten op de dispatch.
Integers, zwevende komma, vectoroperaties. Gepoort door --loci-coproc.
Dekking: 23 deterministische tests (integer-vectoren, zwevende komma, rand-
gevallen). Nul willekeur — dezelfde invoer, dezelfde uitvoer, voorwaarde voor een
reproduceerbare testbank.
4. Betrouwbare ACIA-modus — $AA (seqlock + ACK)
Het probleem. De echte ACIA 6551 heeft een bekend gebrek: als de 6502 het dataregister niet op tijd leest, wordt de ontvangen byte overschreven door de volgende. Op hoge snelheid — een Wi-Fi-modem, bijvoorbeeld — gaan bytes verloren, en wordt de verbinding onbruikbaar. Dat is getrouw aan het silicium, maar hinderlijk.
De oplossing: een seqlock. Een sequentieteller voor ontvangst en een bevestiging aan de 6502-zijde. De byte wordt pas verbruikt wanneer hij is bevestigd — nooit verloren, zelfs als het lezen te laat komt of mislukt.
Klassieke 6551-ontvangst (destructief)
─────────────────────────────────────
RX byte1 ──► RDR (6502 heeft niet gelezen…)
RX byte2 ──► RDR ✗ byte1 OVERSCHREVEN, verloren
Betrouwbare modus $AA (seqlock + ACK)
───────────────────────────────
RXSEQ $0384 (teller, verhoogd bij elke aangeboden byte)
RXACK $0385 (bevestiging geschreven door de 6502)
RX byte1 ─► aanbieden, RXSEQ++ verbruikt := (RXACK == RXSEQ)
6502 leest byte1, schrijft RXACK = RXSEQ ─► byte1 bevestigd → verder
RX byte2 ─► aanbieden alleen indien bevestigd ✓ geen byte verloren
Het zendkanaal blijft ongewijzigd (altijd betrouwbaar). Toestand gedragen
door loci_t (acia_reliable, acia_rx_seq,
acia_rx_presented), opcode $AA, activering via
API_A bit0. Dekking: +7 tests
(test-loci-acia-miss, 13 → 20) — DATA niet-destructief, verbruik
ACK-gated, seqlock voor meerdere bytes in volgorde, en vooral
overleving van een leesfout.
4bis. Lossless RX-handshake — acia_stat_checked (--loci-acia-rx-nag)
Aangrenzende verfijning, gemodelleerd naar de echte firmware
(feature/acia-rx-lossless). Op de echte LOCI is de /IRQ van
de ACIA een niveausignaal, geen puls. We modelleren dit
niveau met een «nag»: zolang de byte niet is gelezen (stat_checked
onwaar), wordt de interrupt periodiek opnieuw uitgezonden (standaard: elke
1000 cycli), en zwijgt zodra de 6502 het statusregister
heeft geraadpleegd.
RDRF=1 (byte klaar) ───┐
│ nag: deassert+assert /IRQ elke 1000 cyc
/IRQ ▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔ │ zolang RDRF && !stat_checked && RX-IRQ actief
│
6502 leest STATUS ─────┘ stat_checked = true ──► /IRQ stil
Zonder --loci-acia-rx-nag blijft de ACIA strikt een
6551 (geen nag). Dekking in test-loci-acia-miss: nag
waargenomen vóór bevestiging, stilte erna, lege
buffer ⇒ geen IRQ.
5. Schakelbare bank van 16 KB — $A7 (--loci-bank)
Het probleem. Hoe geef je meer geheugen aan een machine waarvan de adresruimte is verzadigd door de ROM?
De oplossing. Tijdelijk 16 KB van de RAM van de kaart
(xram) overlappen in het venster $C000-$FFFF, waar de ROM zetelt.
$A7 uitgeschakeld $A7 EN | SEL=n
$C000 ┌───────────┐ $C000 ┌───────────────┐
│ ROM BASIC │ ─────► │ xram[n*0x4000]│ overlay (lezen)
$FFFF └───────────┘ $FFFF └───────────────┘
└─ ROM intact ERONDER (niet overschreven)
basis xram = SEL * 0x4000 ; SEL geklemd op 0..3 (zoals mia_set_bank)
Het kernpunt: niet-destructieve overlay. De bank krijgt voorrang bij het
lezen (en voor geheugeninspectie) zonder ooit de ROM-tabel
te overschrijven. Een deactivering herstelt de machine byte voor byte.
De oude prototype-aanpak met memcpy + back-up is verlaten ten
gunste van deze schone overlay (memory_set_loci_bank() in
memory.c).
Dubbele modus via reset. De activering door
$A7 EN triggert een reset: de 6502 herleest zijn vector
$FFFC uit de bank (men kan dus bankcode booten).
De hot-swap (gebruikt door de streamer $A8) schakelt daarentegen
zonder CPU-reset — een absolute vereiste voor double-buffering.
Dekking: test-loci 170/170 (+4: enable/toestand,
disable, gepoort OFF → ENOSYS, clamp SEL 15→3) en een
test-loci-bank-e2e end-to-end.
6. Asset-streamer — $A8
Het idee. Zodra de bank aanwezig is, er gegevens
uit een bestand in storten (flash of SD-kaart) in één enkele
fastcall: lseek(SEEK_SET) + read → bank 16 KB,
met optionele mapping in $C000-$FFFF. Een Oric-programma kan dan
de bruikbare 48 KB overschrijden: overlays, decors, levels op verzoek.
Double-buffering (48 KB overschrijden zonder reset)
─────────────────────────────────────────────
$A8 MAP=0 SEL=1 ─► laadt bank 1 vooraf (onzichtbaar) ┐ gedurende
(de 6502 blijft bank 0 uitvoeren/tonen) ┘ deze tijd
$A8 MAP=1 SEL=1 ─► schakelt bank 1 in $C000 (hot-swap, PC intact)
Details: API_A bit7 = MAP, bits3:0 = SEL
(0..3 geldig; >3 = EINVAL, geen clamp
in tegenstelling tot $A7). Argumenten op de xstack LIFO
(len, dst, off, fd), schrijven begrensd tot de bankgrootte, retour
AX = gelezen bytes. Twee leespaden: hostbestand en
SD-image. Hergebruikt de opt-in --loci-bank.
7. Het tearing-model — de kwestie van de twee cores
De subtielste uitbreiding, en de intellectueel eerlijkste.
De vraag. Wanneer men een bank schakelt terwijl een buscyclus bezig is, wat latcht de 6502 dan? Op de single-thread-emulator is de swap atomisch: onzichtbaar, de vraag rijst niet. Maar de echte hardware heeft twee cores; een swap gelijktijdig met een geheugentoegang kan tearing veroorzaken.
Het antwoord: het slechtste geval expliciet modelleren. Met
--loci-bank-tearing laat een hot-swap $A8 MAP gelijktijdig met een
verloren PHI2-busrace de open-bus latchen bij de
6502 op de eerste lezing van het venster (one-shot-gedrag),
waarna de bank het overneemt.
PHI2-buscyclus ─┬─ race gewonnen ─► bank netjes geserveerd (atomisch)
│
└─ race VERLOREN ─► 1e lezing = OPEN-BUS (gelatchte waarde)
dan ─► bank (one-shot verbruikt)
(hergebruikt loci_mia_serve_lost_sampled + geseede jitter, deterministisch)
De bijbehorende test is zelfdiagnosticerend: hij verifieert eerst de
preconditie (de race is inderdaad verloren), dan dat het model
de tearing-vlag armt, dan dat de one-shot wordt
verbruikt. Een onvolledige build faalt voortaan op een duidelijke assertie
in plaats van een cryptische torn != pat[0]. Deterministisch (jitter 0): drie
schone builds, identieke resultaten. test-loci-bank-e2e
8/8.
8. Wat de oefening leert
test-loci-suite uit — de headless
prototypeerbank. Werkelijke uitvoer van de stabiele branch
(main, 166/166); de vijf uitbreidingen brengen het totaal op 170 op de
branch experiment.Vijf uitbreidingen, vijf minimale toevoegingen aan een bestaande ABI,
nul regressie op het standaardgedrag. Elk is omkeerbaar
(met vlag), gepoort op ENOSYS zonder zijn opt-in, en onderbouwd met
deterministische tests.
De echte les schuilt misschien hierin: op een machine uit 1983 is de moeilijkheid niet het bedenken van moderne functies, maar het enten ervan zonder het oorspronkelijke gedrag te verraden — en het te bewijzen voordat je de soldeerbout tevoorschijn haalt.
De cycle-exacte emulator houdt op een louter speelbaar museum te zijn: hij wordt een hardware-prototypeerbank. Je schrijft er de spec, je codeert er de uitbreiding, je doorstaat er de tests… en het silicium komt pas als laatste, met het acceptatieboek al ingevuld.
De vijf uitbreidingen leven op de branch
experiment/loci-coproc-acia-reliable van Phosphoric — opt-in, omkeerbaar,
buiten de stabiele versie. Te testen, te bekritiseren, te verbeteren.
Bronnen & links
- Firmware / hardware LOCI (sodiumlb): loci-firmware, loci-hardware, loci-rom
- Gebruikershandleiding (FR): wiki LOCI, ceo.oric.org
- Verkoop & foto's: RAXISS, Tindie
- Ontwikkeldraad: forum.defence-force.org
- Emulator Phosphoric: github.com/benedictemarty/Phosphoric, LOCI-documentatie op deze site