Tachibana

Phosphoric › 記事 › LOCI 拡張

Oric の限界を押し広げる:LOCI カード向けの 5 つの実験的拡張

サイクル精密エミュレータをハードウェアの試験台とする方法 — ABI、レジスタ、そして回路図とともに。

著者: bmarty · Phosphoric

写真について。 LOCI カードの図版は sodiumlb の公式ハードウェアドキュメント (loci-hardware) に由来し、その著者に帰属します。

1. ハードウェアの背景

プリントケースに収められた LOCI カードの上面図:Action ボタン(赤)、Reset ボタン、ファームウェア更新ボタン。
ケースに収められた LOCI カード(RP2040/RP2350 + MIA)— ActionReset、ファームウェア更新の各ボタン。図版:sodiumlb / loci-hardware。

Oric-1Atmos(1983 年)は、1 MHz の MOS 6502 を搭載し、64 KB のアドレス空間のうち上位 ($C000-$FFFF) は BASIC ROM に占有されています。ハードウェア乗算はなく、 メモリバンクもなく、現代的なストレージもありません。すべては筐体背面の 拡張バスを経由します。

sodiumlbLOCI カード(Lovely Oric Computer Interface)は このバスに接続します。技術的には Picocomputer 6502(RP6502)の派生で、 RP2040 マイクロコントローラ(Raspberry Pi Pico)が MIAMedia Interface Adapter)の役割を担い、Oric に対して入出力 周辺機器として現れます。フロッピーディスクドライブとカセットをエミュレートし、SD カードを管理し、 USB ホストとしても機能します。この USB ポートを通じて、Wi-Fi モデム(別体のドングル、 PicoWiFiModemUSB)や HID 周辺機器(マウス、ゲームパッド)が接続されます。Wi-Fi は LOCI カード本体には搭載されていません。ここで注目するアドレス指定面は 2 つです:

LOCI カードの Oric 拡張バスコネクタ。Pin 1 が示されている。
LOCI の Oric 拡張バスコネクタ(Pin 1 を表示)— ここを通じてカードが筐体背面に挿し込まれます。 図版:sodiumlb / loci-hardware。
         Espace d'adressage Oric (64 Ko)
   $0000 ┌──────────────────────────────┐
         │ RAM                          │
   $0300 ├──────────────────────────────┤
         │ VIA 6522        $0300-$030F  │
   $0310 │ Microdisc WD1793 $0310-$031F │
         ├──────────────────────────────┤
   $0380 │ ACIA 6551 (LOCI) $0380-$0383 │ ← console série / modem
         ├──────────────────────────────┤
   $03A0 │ MIA (LOCI)      $03A0-$03BF  │ ← fenêtre API 32 octets
         ├──────────────────────────────┤
   $C000 │ ROM BASIC       $C000-$FFFF  │ ← cible de l'overlay de banque
   $FFFF └──────────────────────────────┘
LOCI カードから見た Oric の I/O マッピング。

エミュレータ Phosphoric(サイクル精密、C11)はこのカードを忠実に 再現しており、まれな手法が可能になります:拡張の仕様を書き、それをコーディングし、 決定論的なテストで検証する — はんだごてに触れる前に。重要なのは、著者は LOCI カードを所有していますが Oric は持っていない、という点です。ソフトウェアの試験台は贅沢品ではなく、唯一の試験室なのです。

2. fastcall ABI — 6502 がカードを呼び出す仕組み

すべては $03A0-$03BF(MIA)にある 32 個のレジスタウィンドウに 基づいています。ABI が使用する実際のレジスタマップは以下のとおりです(名前はファームウェアに忠実):

 Offset  Adresse  Registre          Rôle
 ------  -------  ----------------  ------------------------------------------
  $00    $03A0    CONS_FLAGS        bit7 = TX libre, bit6 = RX prêt
  $01    $03A1    CONS_TX           écriture console (UART)
  $02    $03A2    CONS_CHAR         lecture console (consomme l'octet)
  $0C    $03AC    API_STACK         pointeur de xstack (pile d'arguments)
  $0D    $03AD    API_ERRNO_LO      errno bas
  $0E    $03AE    API_ERRNO_HI      errno haut
  $0F    $03AF    API_OP            ← ÉCRIRE ICI déclenche l'opération
  $12    $03B2    BUSY              bit7 = carte occupée
  $14    $03B4    API_A             valeur de retour A
  $16    $03B6    API_X             valeur de retour X
  $18    $03B8    API_SREG          retour 16 bits (SREG)
ABI の MIA レジスタ(include/io/loci.h より抜粋)。

呼び出しプロトコル(fastcall)は 4 段階に収まります:

   6502 (Oric)                         MIA (LOCI, µC)
   ───────────                         ──────────────
   1. push args ──► xstack ($03AC)
   2. set A/X (paramètres directs)
   3. write op ──► API_OP ($03AF) ───► déclenche le handler
                                       ├─ BUSY=1
      poll BUSY ($03B2) ◄──────────────┤  exécute
                                       └─ BUSY=0, remplit API_A/X/SREG, ERRNO
   4. read API_A/API_X ($03B4/$03B6) ◄─ résultat
LOCI の fastcall 呼び出しのシーケンス。

まだ空いているオペコード値のそれぞれが、新しい機能へのエントリポイントです。 標準操作は $01 から $98 まで(時計、open/read/lseek、 ディレクトリ、イメージのマウント、TAP…)に及びます。未使用のオペコードにこそ、 私たちの 5 つの拡張が収まります:

  $A7  SET_BANK        banque commutable 16 Ko      (--loci-bank)
  $A8  STREAM_BANK     streamer d'assets            (--loci-bank)
  $A9  MATH            coprocesseur arithmétique    (--loci-coproc)
  $AA  ACIA_RELIABLE   mode ACIA fiable (seqlock)   (mode opt-in)
       + acia_stat_checked : handshake RX lossless  (--loci-acia-rx-nag)

設計による安全装置。対応する有効化フラグがなければ、オペコードは ENOSYS(errno 13)を返します — パッチされていないファームウェアと全く同じです。 したがって Oric ソフトウェアは拡張の存在を検出し、自前のルーチンに フォールバックできます。LOCI のデフォルト動作は、厳密に元のハードウェアの ままです。

3. 算術コプロセッサ — $A9

問題。6502 には乗算も除算も浮動小数点もハードウェアに備わっていません。 各演算はソフトウェアルーチンであり、遅く、かさばり、サイクルを多く消費します。1 MHz の マシンでは、16×16 の乗算は数百サイクルにも達します。

着想。計算を、はるかに高速なカードのマイクロコントローラに、既存の fastcall ABI を介して委譲します — 単一のオペコード $A9、演算のサブコードは API_A に、オペランドは xstack に、結果は API_A/SREG に。

   ; exemple conceptuel : A×B via le coprocesseur
   LDA #<op_mul  : STA API_A     ; sous-code d'opération
   ... push A, B sur la xstack ($03AC)
   LDA #$A9      : STA API_OP     ; déclenche MATH
   ; poll BUSY, puis lire le résultat 32 bits dans SREG

実装。独立したファイル src/io/loci_math.cop_math)をディスパッチに接続します。 整数、浮動小数点、ベクトル演算。--loci-coproc でゲートされます。 カバレッジ:23 個の決定論的テスト(整数ベクトル、浮動小数点、限界ケース)。 乱数はゼロ — 同じ入力なら同じ出力、これが再現可能な試験台の条件です。

4. 信頼 ACIA モード — $AA(seqlock + ACK)

LOCI カードの USB-C ホストポート。ケースの側面で強調されている。
LOCI の USB-C ホストポート — ここに Wi-Fi モデムドングル (PicoWiFiModemUSB) が接続され、高速時に ACIA を飽和させます。 図版:sodiumlb / loci-hardware。

問題。実際の ACIA 6551 には知られた欠点があります: 6502 がデータレジスタを時間内に読み取らないと、受信したバイトは次のバイトによって 上書きされます。高速時 — 例えば Wi-Fi モデム — にはバイトが失われ、リンクは 使い物にならなくなります。シリコンに忠実ではありますが、足かせになります。

解決策:seqlock。受信シーケンスカウンタと 6502 側のアクノリッジ。 バイトはアクノリッジされて初めて消費されます — 読み取りが 遅れたり失敗したりしても、決して失われません。

     Réception classique 6551 (destructive)
     ─────────────────────────────────────
     RX octet1 ──► RDR   (6502 n'a pas lu…)
     RX octet2 ──► RDR   ✗ octet1 ÉCRASÉ, perdu

     Mode fiable $AA (seqlock + ACK)
     ───────────────────────────────
        RXSEQ $0384  (compteur, incrémenté à chaque octet présenté)
        RXACK $0385  (accusé écrit par le 6502)

     RX octet1 ─► présente, RXSEQ++          consommé := (RXACK == RXSEQ)
     6502 lit octet1, écrit RXACK = RXSEQ ─► octet1 acquitté → avance
     RX octet2 ─► présente seulement si acquitté  ✓ aucun octet perdu
6551 の破壊的受信 vs. seqlock による信頼モード。

送信チャンネルは変更なし(常に信頼できる)。状態は loci_tacia_reliableacia_rx_seqacia_rx_presented)が保持し、オペコード $AA、有効化は API_A の bit0。カバレッジ:+7 テストtest-loci-acia-miss、13 → 20)— DATA が非破壊、ACK でゲートされた消費、 複数バイトの seqlock が順序どおり、そして何より読み取り失敗からの 生存

4bis. ロスレス RX ハンドシェイク — acia_stat_checked--loci-acia-rx-nag

実ファームウェア(feature/acia-rx-lossless)に倣った隣接する洗練です。 実機の LOCI では、ACIA の /IRQ はパルスではなくレベル信号です。 このレベルを「nag」でモデル化します:バイトが読み取られない間(stat_checked が偽)、割り込みは周期的に再発行され(デフォルト:1000 サイクルごと)、 6502 が状態レジスタを参照した途端に沈黙します。

   RDRF=1 (octet dispo) ──┐
                          │  nag: deassert+assert /IRQ tous les 1000 cyc
   /IRQ  ▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔ │  tant que  RDRF && !stat_checked && RX-IRQ activée
                          │
   6502 lit STATUS ───────┘  stat_checked = true  ──►  /IRQ silencieux
「nag」は実機 6551 のレベル IRQ をモデル化する。

--loci-acia-rx-nag がなければ、ACIA は厳密に 6551 のまま (nag なし)です。test-loci-acia-miss でのカバレッジ:アクノリッジ前に nag が 観測され、後は沈黙し、バッファが空 ⇒ IRQ なし。

5. 16 KB の切替可能バンク — $A7--loci-bank

問題。アドレス空間が ROM で飽和しているマシンに、どうやって より多くのメモリを与えるか?

解決策。カードの RAM(xram)の 16 KB を、ROM が居座る $C000-$FFFF ウィンドウに一時的に重ね合わせます。

        $A7 désactivé                 $A7 EN | SEL=n
   $C000 ┌───────────┐          $C000 ┌───────────────┐
         │ ROM BASIC │   ─────►        │ xram[n*0x4000]│  overlay (lecture)
   $FFFF └───────────┘          $FFFF  └───────────────┘
                                       └─ ROM intacte DESSOUS (non écrasée)
   base xram = SEL * 0x4000 ; SEL clampé à 0..3 (comme mia_set_bank)
ROM ウィンドウ上の非破壊的なバンクオーバーレイ。

要点:非破壊的オーバーレイ。バンクは読み取り(およびメモリ検査)において 優先されますが、ROM 配列を決して上書きしません。無効化すればマシンは 1 バイトずつ復元されます。memcpy + 退避による古いプロトタイプ手法は、 このクリーンな重ね合わせ(memory.c 内の memory_set_loci_bank())の ために放棄されました。

reset による二重モード。$A7 EN による有効化は リセットを発生させます:6502 はそのベクタ $FFFCバンクから再読み込みします(したがってバンクのコードからブートできます)。 対照的にホットスワップ(ストリーマ $A8 が使用)はCPU リセットなしで 切り替えます — ダブルバッファリングの絶対的な前提条件です。カバレッジ:test-loci 170/170(+4:有効化/状態、無効化、OFF でゲート → ENOSYS、 SEL のクランプ 15→3)と、エンドツーエンドの test-loci-bank-e2e

6. アセットストリーマ — $A8

着想。バンクを配置したら、そこへファイル(flash または SD カード) からデータを流し込む単一の fastcall でlseek(SEEK_SET) + read → 16 KB バンク、任意で $C000-$FFFF へのマッピング付き。Oric ソフトウェアはこうして 実用 48 KB を超えることができます:オーバーレイ、背景、レベルをオンデマンドで。

   Double-buffering (dépasser 48 Ko sans reset)
   ─────────────────────────────────────────────
   $A8 MAP=0 SEL=1 ─► précharge la banque 1 (invisible)   ┐ pendant
   (le 6502 continue d'exécuter/afficher la banque 0)      ┘ ce temps
   $A8 MAP=1 SEL=1 ─► bascule banque 1 en $C000 (hot-swap, PC intact)
アセットのストリーミング + ダブルバッファリング。

詳細:API_A bit7 = MAP、bits3:0 = SEL0..3 が有効;>3 = EINVAL$A7 とは異なりクランプなし)。 引数は xstack の LIFO(len, dst, off, fd)、書き込みはバンクサイズに制限され、 戻り値 AX = 読み取りバイト数。読み取り経路は 2 つ:ホストファイル および SD イメージ。--loci-bank のオプトインを再利用します。

7. tearing モデル — 2 コアという問い

最も微妙で、知的に最も誠実な拡張です。

問い。バス サイクルの進行にバンクを切り替えると、 6502 は何をラッチするのか?シングルスレッドのエミュレータでは、スワップは アトミックで、目に見えず、この問いは生じません。しかし実際のハードウェアには 2 つのコアがあり、メモリアクセスと同時のスワップは tearing を 生じさせ得ます。

答え:最悪ケースを明示的にモデル化する。--loci-bank-tearing を用いると、失われた PHI2 バスレースと同時の $A8 MAP ホットスワップが、 ウィンドウの最初の読み取りで 6502 に open-bus をラッチさせ (ワンショット動作)、その後バンクが引き継ぎます。

   Cycle bus PHI2  ─┬─ course gagnée ─► banque servie proprement (atomique)
                    │
                    └─ course PERDUE ─► 1re lecture = OPEN-BUS (valeur latchée)
                                        puis  ─► banque   (one-shot consommé)
   (réutilise loci_mia_serve_lost_sampled + jitter seedé, déterministe)
tearing モデル:失われた PHI2 レース → ワンショットの open-bus。

関連するテストは自己診断的です:まず前提条件(レースが 確かに失われていること)を検証し、次にモデルが tearing フラグをアームする ことを、そしてワンショットが消費されることを検証します。不完全なビルドは、 これまでの謎めいた torn != pat[0] ではなく、明確なアサーションで失敗するようになりました。 決定論的(jitter 0):3 回のクリーンビルドで同一の結果。test-loci-bank-e2e 8/8

8. この演習が教えること

Phosphoric の test-loci スイートのターミナル出力:166 テスト合格、0 失敗。
Phosphoric が test-loci スイートを実行 — ヘッドレスの プロトタイピング試験台。安定ブランチ (main、166/166) の実際の出力;5 つの拡張は experiment ブランチで合計を 170 に押し上げます。

5 つの拡張、既存の ABI への 5 つの最小限の追加、デフォルト動作への ゼロ・リグレッション。それぞれが可逆的(フラグに基づく)で、オプトインなしでは ENOSYS でゲートされ、決定論的なテストに支えられています。

真の教訓はおそらくここにあります:1983 年のマシンにおける難しさは、現代的な機能を 思い描くことではなく、それらを元の動作を裏切ることなく接ぎ木すること — そして はんだごてを取り出す前にそれを証明することにあります。

サイクル精密エミュレータは、単なる遊べる博物館であることをやめ、 ハードウェアのプロトタイピング試験台になります。そこで仕様を書き、拡張を コーディングし、テストを通し… シリコンは最後に、検収書をすでに埋め終えた状態でようやく やって来るのです。


5 つの拡張は Phosphoric の experiment/loci-coproc-acia-reliable ブランチに存在します — オプトイン、可逆的、 安定版の外。テスト、批評、改善を歓迎します。

出典・リンク

← Phosphoric · LOCI ドキュメント →