picoZ80 テクニカルガイド

picoZ80 テクニカルガイド

このガイドでは、picoZ80 のハードウェアアーキテクチャ、RP2350 PIO バスインターフェース、メモリモデル、JSON 設定リファレンス、仮想デバイスフレームワーク、デバッグ手順について説明します。内部構造を理解したい、新しいドライバーを書きたい、新しいホストマシンにファームウェアを移植したい、またはファームウェアレベルの問題をデバッグしたいデベロッパーを対象としています。
エンドユーザーのセットアップとウェブインターフェースの使用方法についてはpicoZ80 ユーザーマニュアルを参照してください。プロジェクト概要とビルド手順についてはpicoZ80 プロジェクトページを参照してください。

ハードウェアアーキテクチャ

picoZ80 は DIP-40 パッケージのフットプリント内に収まるように設計されたコンパクトな単一 PCB に 5 つのサブシステムを統合しています。すべてのロジックは 3.3V で動作し、Z80 バスインターフェースが 5V ホストバスのレベル変換と電流駆動を処理します。

システムブロック図

┌─────────────────────────────────────────────────────────────────────────┐
│                         picoZ80 PCB                                     │
│                                                                         │
│  ┌────────────────────────────┐      ┌──────────────────────────────┐  │
│  │         RP2350B            │      │           ESP32-S3           │  │
│  │  (Cortex-M33、デュアルコア)│      │                              │  │
│  │                            │      │  ┌──────┐  ┌──────────────┐ │  │
│  │  コア 0:USB、ファイル I/O、│◄────►│  │  SD  │  │  ウェブサーバー│ │  │
│  │          ESP32 リレー      │ FSPI │  │ カード│  │  (Bootstrap) │ │  │
│  │  コア 1:Z80 バスホットループ│ UART │  └──────┘  └──────────────┘ │  │
│  │                            │      │                              │  │
│  │  PIO 0,1,2:バスインターフェース│  │  WiFi ─── 802.11 b/g/n AP   │  │
│  │                            │      │           またはクライアントモード│  │
│  │  16MB SPI フラッシュ        │      └──────────────────────────────┘  │
│  │  8MB PSRAM(SPI)          │                                         │
│  └────────────────────────────┘                                         │
│                │                                                         │
│       ┌────────┴────────┐                                               │
│       │ Z80 バスインターフェース│                                       │
│       │ (40 ピン DIP 出力) │                                             │
│       └────────┬────────┘                                               │
│                │ 5V バス(A0–A15、D0–D7、MREQ、IORQ、RD、WR...)        │
└────────────────┼────────────────────────────────────────────────────────┘
                 │
         ┌───────┴───────┐
         │  ホスト Z80    │
         │  DIP-40 ソケット│
         │  (レガシー    │
         │   コンピューター)│
         └───────────────┘

主要コンポーネント

コンポーネント デバイス 役割
プライマリ MCURP2350B(QFN-80)デュアル Cortex-M33、150MHz(最大 300MHz OC)、512KB SRAM、12 PIO ステートマシン、48 GPIO ピン
フラッシュW25Q128(16MB SPI)ブートローダー、デュアルファームウェアスロット、設定パーティション
PSRAM8MB SPI PSRAMZ80 アドレス空間用の 64 × 64KB RAM/ROM バンク
コプロセッサESP32-S3-PICO-1WiFi、SD カード、ウェブサーバー、OTA
USB ハブCH334FUSB ハブ、ファームウェアアップデートブリッジ
電源供給TLV62590BV5V → 3.3V 同期バックコンバーター

RP2350B GPIO 割り当て

RP2350B QFN-80 パッケージは 48 本の GPIO ピンを提供します。picoZ80 はほぼすべてのピンを使用します。割り当てはボードの設計に固定されており PIO プログラムに反映されています:
GPIO 範囲 シグナル 方向
GPIO 0–15 A0–A15(Z80 アドレスバス) 出力(PIO が駆動)
GPIO 16–23 D0–D7(Z80 データバス) 双方向(PIO トライステート)
GPIO 24 MREQ 出力
GPIO 25 IORQ 出力
GPIO 26 RD 出力
GPIO 27 WR 出力
GPIO 28 M1 出力
GPIO 29 RFSH 出力
GPIO 30 BUSREQ 入力
GPIO 31 BUSACK 出力
GPIO 32 HALT 出力
GPIO 33 INT 入力
GPIO 34 NMI 入力
GPIO 35 WAIT 出力
GPIO 36 CLK 入力(ホストクロック)
GPIO 37 RESET 入力
GPIO 38–41 ESP32 FSPI(CS、CLK、MOSI、MISO) SPI
GPIO 42–43 ESP32 UART(TX、RX) UART
GPIO 44–45 PSRAM SPI SPI
GPIO 46–47 USB(D+、D–) USB

ファームウェアアーキテクチャ

RP2350 ファームウェアは、RP2350-arm-s プラットフォームをターゲットとした Raspberry Pi Pico SDK 2.x でビルドされます。ファームウェアは 2 つの独立した実行ファイルに分かれています:ブートローダーアプリケーション

フラッシュメモリレイアウト

パーティション アドレス範囲 サイズ 内容
ブートローダー 0x10000000 – 0x1001FFFF 128KB USB ブリッジ、ファームウェアアップデート、パーティションセレクタ
App スロット 1 0x10020000 – 0x1051FFFF 5MB Z80 ファームウェア — アクティブアプリケーション(スロット 1)
App スロット 2 0x10520000 – 0x10A1FFFF 5MB Z80 ファームウェア — アクティブアプリケーション(スロット 2)
App 設定 1 0x10A20000 – 0x10C9FFFF 2.5MB ROM イメージ + 最小化設定 JSON(スロット 1)
App 設定 2 0x10CA0000 – 0x10F1FFFF 2.5MB ROM イメージ + 最小化設定 JSON(スロット 2)
一般設定 0x10F20000 – 0x10FFEFFF 892KB コア設定、スクラッチスペース
パーティションテーブル 0x10FFF000 – 0x11000000 4KB アクティブスロット番号、チェックサム、メタデータ

デュアルコアの責任

2 つの Cortex-M33 コアは完全に別々の責任を割り当てられており、コア間メッセージキュー(queue_t)を介して通信します。この分離により、コア 0 の非リアルタイム作業がコア 1 の Z80 バストランザクションにジッターを導入しないことが保証されます。
コア 責任
コア 0 USB CDC シリアルブリッジ;ファームウェアアップデート調整;ファイル I/O(UART 経由で ESP32 にリレー);ESP32 コマンドディスパッチ(ディスクイメージ変更、設定リロード、バージョンクエリ);パーティション管理;コア間メッセージディスパッチ。
コア 1 Z80 バスエミュレーションホットループ — 独占的に実行。PIO FIFO をサービスし、各バストランザクションをメモリマップに対して解決し、物理ホストハードウェア(PHYSICAL)、PSRAM(RAM/ROM)、仮想デバイスハンドラ(FUNC)のいずれかにディスパッチする。内部ループは SRAM に配置。

PIO バスインターフェース

Z80 バスインターフェースは RP2350 PIO アセンブリ(z80.pio)で完全に実装されています。RP2350 は 3 つの PIO ブロック(PIO 0、PIO 1、PIO 2)を提供し、それぞれ 4 つのステートマシンを持ちます — 合計 12 ステートマシンで、Z80 ファームウェアはそれらすべてを使用します。
PIO プログラムは Cortex-M33 コアとは独立して実行されます。バスインターフェースはコア 1 が PSRAM アクセスや仮想デバイス関数呼び出しで占有されているときでも確定的に応答し続けます。ステートマシンはポーリングではなく PIO IRQ フラグを介して通信し、マシン間のレイテンシーを排除します。

PIO プログラムテーブル

PIO ステートマシン プログラム 機能
0 SM 0 z80_addr 16 ビットアドレス(A0–A15)をバスに出力し、サイクル開始を SM 2 にシグナルする。
0 SM 1 z80_data トライステート制御で D0–D7 を駆動またはサンプリング;BUSRQ 中に解放。
0 SM 2 z80_cycle トップレベルバスサイクルシーケンサー — フェッチ、読み取り、書き込み、I/O、DRAM リフレッシュサイクルをオーケストレート。
0 SM 3 z80_fetch オペコードフェッチサイクル(M1 + MREQ + RD)。
1 SM 0 z80_mem_read メモリ読み取りサイクル(MREQ + RD)。
1 SM 1 z80_mem_write メモリ書き込みサイクル(MREQ + WR)。
1 SM 2 z80_io_read I/O 読み取りサイクル(IORQ + RD)。
1 SM 3 z80_io_write I/O 書き込みサイクル(IORQ + WR)。
2 SM 0 z80_busrq BUSREQ/BUSACK を管理;/IORQ、/MREQ、/RFSH、/M1、/HALT、/WR、/RD を解放。
2 SM 1 z80_nmi NMI アサーションを検出してコア 1 にシグナルする。
2 SM 2 z80_clk_sync PIO ステートマシンをホスト Z80 CLK シグナルに同期させる。
2 SM 3 z80_int_ack 割り込みアクノリッジサイクル(M1 + IORQ)を処理する。

PIO IRQ シグナル規約

ステートマシン間の通信には PIO IRQ フラグを使用します。コア 1 はホットループでこれらのフラグを監視して各バスイベントでアクションを取ります:
IRQ イベント
IRQ 0 アドレス有効 / サイクル開始 — 新しいバスサイクルが始まり A0–A15 が安定している。
IRQ 1 データフェーズ — データバスの方向が解決された;D0–D7 を駆動またはサンプリングすべき。
IRQ 2 T1 検出 — 現在のサイクルの T1 の立ち上がりエッジ。内部操作をホストクロックに同期させるために使用。
IRQ 3 RESET イベント — ホストの RESET ラインがアサートされた。コア 1 はエミュレーション状態を再初期化すべき。
IRQ 4 NMI 検出 — ホストの NMI ラインがアサートされた。
IRQ 6 BUSRQ アクティブ — ホストが BUSREQ をアサートした;PIO がバスを解放中。

ウェイトステート生成

PIO 2 SM 0 の z80_wait PIO プログラムは /WAIT をアサートすることでホストバスに設定可能な T サイクルウェイトステートを挿入します。追加ウェイトステートの数は config.jsontcycwait パラメータでメモリまたは I/O ブロックごとに制御されます。
ウェイトステートは、RP2350 がホストバスにデータを提示する前に PSRAM アクセスや仮想デバイス関数呼び出しを完了するために追加時間が必要な場合に必要です。tcycsync パラメータは T1 同期を有効化し(PIO 2 SM 1 の z80_sync)、PSRAM アクセスウィンドウを各バスサイクルの T1 立ち上がりエッジにロックし、カセット、シリアルビットバンギングなど精密なタイミングのためにホストクロックに依存するアプリケーションのタイミングドリフトを防ぎます。

PIO アーキテクチャ — バスサイクルの再現方法

RP2350 のプログラマブル I/O(PIO)サブシステムは、picoZ80 がサイクル精度の Z80 バスタイミングを再現するための中核技術です。PIO ステートマシンがどのように連携して動作するかを理解することは、バスインターフェースの変更やタイミング問題のデバッグに不可欠です。

RP2350 PIO の基礎
各 PIO ブロックには、共有 32 命令メモリからプログラムを実行する 4 つの独立したステートマシン(SM)が含まれています。ステートマシンは Cortex-M33 コアとは独立してシステムクロック周波数(最大 300 MHz)で動作します。picoZ80 が使用する主要な PIO リソース:
  • TX FIFO — CPU からステートマシンへの 4 エントリーキュー。コア 1 の C コードがデータ(アドレス、制御ワード、インジェクトされる命令)を FIFO にプッシュし、PIO プログラムが out または pull を使用して読み取ります。
  • RX FIFO — ステートマシンから CPU への 4 エントリーキュー。PIO が in を使用してデータバスサンプルを FIFO にプッシュし、コア 1 が各バスサイクル完了後に読み取ります。
  • IRQ フラグ — PIO ブロック内のすべてのステートマシンで共有される 8 つのフラグ(IRQ 0〜7)。フラグは PIO ブロック間でも参照可能(あるブロックの IRQ 0〜3 は隣接ブロックの IRQ 4〜7 にマップ)。ステートマシンは irq set / irq wait / irq clear を使用して互いおよび C コードと同期します。
  • スクラッチレジスタ X と Y — SM ごとに 2 つの 32 ビットレジスタ。ループカウンターや一時値に使用されます。
  • out exec — TX FIFO から値を取得し PIO 命令として実行する特殊命令。C コードがバスサイクルシーケンスを動的に制御するメカニズムです(後述)。
  • set pins / out pins — GPIO ピンを直接駆動。set は即値 5 ビットを使用、out は出力シフトレジスタ(OSR)からピンにデータをシフトします。
  • in pins — GPIO ピンを入力シフトレジスタ(ISR)にサンプリングし、RX FIFO に自動プッシュします。
  • wait gpio — 特定の GPIO ピンが指定レベルに達するまで SM をストールさせます。ホスト Z80 クロック信号との同期に広く使用されます。
  • サイドセット — 命令サイクルを使用せずに、任意の命令の副作用として 1 つまたは 2 つの GPIO ピンを駆動可能。picoZ80 は 2 ビットサイドセットを使用して /RD/WR を他の操作と同時に制御します。
  • JMP PIN — 指定 GPIO ピンレベルに基づく条件分岐。/WAITBUSREQ/NMI/RESET のテストに使用されます。

out exec メカニズム — 動的命令インジェクション
picoZ80 PIO 設計の最も特徴的な機能は、z80_cycle オーケストレーターステートマシンにおける out exec, 16 の使用です。この命令は TX FIFO から 16 ビット値を取得し、PIO 命令として即座に実行します。値はデータではなく、ステートマシンが次に実行する命令そのものです。
このメカニズムにより、コア 1 の C コードがリアルタイムでバスサイクルシーケンスを制御できます。各サイクルタイプの固定 PIO プログラムをロードするのではなく、コア 1 がプリエンコードされた PIO 命令のシーケンスを TX FIFO にプッシュし、サイクル SM がそれを一つずつ実行します:
// z80_cycle SM(PIO 0 SM 2)— オーケストレーター
//
// .program z80_cycle
// .side_set 2 opt
// public start_cycle:
//     wait 0 irq 6             ; BUSACK がアクティブなら一時停止(バス放棄中)。
//     irq set 0                ; 「新しいサイクル準備完了」を通知。
//     wait 0 irq 0             ; C コードが IRQ 0 をクリアするまで待機(アドレスロード済み)。
//     wait 1 gpio Z80_PIN_CLK  ; ホストクロックの T1 立ち上がりエッジに同期。
// cycle_exec:
//     out exec, 16             ; ← TX FIFO から次の命令を取得し実行。
//     jmp cycle_exec           ; ループ:インジェクトされた命令を実行し続ける。
//
// C コードはエンコード済み PIO 命令のシーケンスを FIFO にプッシュします。
// 各命令はバスサイクルの1ステップ(/MREQ アサート、クロックエッジ待機、
// データバス読み取りなど)を制御します。シーケンスの最後は start_cycle への
// JMP で、オーケストレーターを再起動して次のバストランザクションに備えます。
C コードはこれらの命令シーケンスを起動時に各サイクルタイプ(フェッチ、メモリリード、メモリライト、I/O リード、I/O ライト、リフレッシュ、割り込みアクノリッジ)用にプリコンパイルします。実行中、コア 1 は適切なプリビルドシーケンスを選択して FIFO にプッシュします。このアプローチには 2 つの重要な利点があります:
  • プログラム空間の効率性 — 各 PIO ブロックには 32 命令スロットしかありません。命令を動的にインジェクトすることで、サイクル SM はすべてのサイクルタイプをオーケストレートするためにわずか 7 命令分のプログラムメモリしか必要としません。実際のサイクルタイププログラム(フェッチ、リード、ライトなど)は PIO 常駐プログラムではなく、エンコード済み命令の C 配列として存在します。
  • 柔軟性 — C コードはインジェクトする命令シーケンスを実行時に変更でき、特殊なケース(追加ウェイトステートの挿入、リフレッシュフェーズのスキップ、デバッグ用の非標準サイクルの生成など)に対応できます。

ステートマシンの連携
12 個のステートマシンは協調パイプラインとして動作します。以下の図は典型的なメモリリードサイクルのフローを示しています:
                  コア 1(C コード)              PIO ステートマシン
                  ──────────────                ──────────────────
    1. メモリマップから                          z80_cycle: IRQ 0 セット
       アドレスを解決                              (作業待ち)
                  │
    2. アドレス → TX FIFO プッシュ ────────────→ z80_addr: アドレス受信
       IRQ 0 クリア                                A0–A15 をピンに出力
                  │
    3. サイクル命令プッシュ ──────────────────→ z80_cycle: out exec, 16
       (例: mem_read シーケンス)                   実行: /MREQ ロー設定
       サイクル SM TX FIFO へ                       実行: /RD ロー設定
                  │                                実行: CLK エッジ待機
    4. RX FIFO 待機 ←────────────────────────  z80_data: D0–D7 サンプリング
       (バスからのデータバイト)                     RX FIFO にプッシュ
                  │
    5. RX FIFO から                             z80_cycle: JMP start_cycle
       データ読み取り                               (次のサイクル準備完了)
                  │
    6. PSRAM またはドライバー
       ハンドラーにディスパッチ
IRQ ベースのハンドシェイクにより、制御信号がアサートされる前にアドレスバスが安定していること、およびバスサイクルの正しいポイントでデータがサンプリングされることが保証されます。ステートマシンはポーリングを一切行いません。wait 0 irq N を使用して関連イベントが発生するまでスリープし、待機中の CPU サイクル消費はゼロです。

Z80 フェッチサイクル(M1 サイクル)— ステップバイステップ
オペコードフェッチは最も複雑な Z80 バスサイクルです — メモリリードとリフレッシュサイクルを組み合わせます。z80_fetch プログラムはホストクロックの 4 T サイクルにわたって実行されます:
ホスト CLK: ──┐  ┌──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │  │  │
             └──┘  └──┘  └──┘  └──┘
              T1    T2    T3    T4

A0–A15:    ══╤═══ PC アドレス ═════╤═══ リフレッシュアドレス ══╗
             │                     │                          ║
/M1:       ──┘                     └──────────────────────────╜── (T1–T2 ロー、T3–T4 ハイ)
/MREQ:     ────┘              ┌────┘              ┌──── (T1↓–T3↑ ロー、T3↓–T4↓ リフレッシュ用)
/RD:       ────┘              ┌─────────────────────── (T1↓–T3↑ ロー)
/RFSH:     ────────────────────┘                   ┌── (T3↑–T4↓ ロー)
D0–D7:     ═══════════════╤═══╗                        (T3↑ でサンプリング)
                          │   ║
                       オペコード読み取り
PIO プログラムはこれを以下のように実装します:
  1. T1 立ち上がりエッジz80_addr SM が PC 値を A0–A15 に出力。z80_fetchset pins/M1 をローにアサート。
  2. T1 立ち下がりエッジ/MREQ/RD がローにアサート(set pins とサイドセットを使用)。アドレスが有効になり、メモリシステムが応答を開始可能。
  3. T2 — SM がウェイトステートループに入る:CLK 立ち上がりから立ち下がりエッジを待ち、jmp pin/WAIT ピンを確認。/WAIT がローなら SM はループ(Tw サイクル追加)。ハイなら T3 に進む。
  4. T3 立ち上がりエッジin pins, 8 で D0–D7(オペコードバイト)をサンプリングし RX FIFO にプッシュ。IRQ 1 をセットして z80_data/z80_addr にリフレッシュアドレスの出力を通知。/M1/MREQ/RD をデアサート。/RFSH をローにアサート。
  5. T3 立ち下がりエッジ/MREQ を再度アサート(リフレッシュ行ストローブ用)。
  6. T4 — リフレッシュ継続。T4 終了時に /MREQ/RFSH をデアサート。サイクル SM は start_cycle に戻り、次のバストランザクションの準備完了。
コア 1 は RX FIFO からオペコードを読み取り、命令をデコードして必要な後続のメモリまたは I/O サイクル数を決定し、適切な命令シーケンスをプッシュします。

メモリリードとライトサイクル
メモリリードとライトサイクルはフェッチよりシンプルです — リフレッシュフェーズなしで 3 T サイクルにわたります。
メモリリード:
ホスト CLK: ──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │
             └──┘  └──┘  └──┘
              T1    T2    T3

A0–A15:    ══╤═══ アドレス ══════╗
/MREQ:     ────┘              ┌──── (T1↓–T3↓ ロー)
/RD:       ────┘              ┌──── (T1↓–T3↓ ロー)
D0–D7:     ═══════════════╤═══╗     (T3↓ でサンプリング)


メモリライト:
ホスト CLK: ──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │
             └──┘  └──┘  └──┘
              T1    T2    T3

A0–A15:    ══╤═══ アドレス ══════╗
D0–D7:     ══════╤═══ データ ════╗   (T2 以降駆動)
/MREQ:     ────┘              ┌──── (T1↓–T3↓ ロー)
/WR:       ──────────┘        ┌──── (T2↓–T3↓ ロー)
リードの場合、z80_mem_read SM は T1 立ち下がりエッジで /MREQ/RD をアサートし、T2 を待機(/WAIT でウェイトステートを確認)、その後 T3 立ち下がりエッジで in pins, 8 を使用してデータバスをサンプリングします。ライトの場合、z80_mem_write は T1 立ち下がりエッジで /MREQ をアサートし、z80_data がデータバスを駆動した後、T2 立ち下がりエッジで /WR をアサートします。どちらも T3 の終わりにすべての制御信号をデアサートします。

I/O リードとライトサイクル
Z80 I/O サイクルは /MREQ の代わりに /IORQ を使用し、T2 と T3 の間に常に自動ウェイトステート(Tw)が挿入されます。これは Z80 のアーキテクチャ上の特徴で、低速な I/O デバイスに応答する時間を与えます:
I/O リード:
ホスト CLK: ──┐  ┌──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │  │  │
             └──┘  └──┘  └──┘  └──┘
              T1    T2    Tw    T3

A0–A15:    ══╤═══ ポートアドレス ═══════╗
/IORQ:     ──────┘                    ┌──── (T2↑–T3↓ ロー)
/RD:       ──────┘                    ┌──── (T2↑–T3↓ ロー)
D0–D7:     ═══════════════════════╤═══╗     (T3↓ でサンプリング)
z80_io_read SM は T2 立ち上がりエッジで /IORQ/RD をアサートします(メモリサイクルの T1 ではなく — これは Z80 仕様に準拠)。自動 Tw ウェイトステートはメモリサイクルと同じ jmp pin / wait ループパターンで実装されます。I/O ライトは /RD の代わりに /WR を使用する同様のパターンに従います。

BUSREQ / BUSACK の処理
z80_busrq SM(PIO 2 SM 0)はホストの /BUSREQ 入力ピンを監視します。/BUSREQ がアクティブ(ロー)になると、SM は以下を実行します:
  1. IRQ 6 をセットして、バスリクエストが保留中であることをサイクル SM に通知。
  2. 現在のバスサイクルの完了を待機(wait 1 irq 0)。
  3. TX FIFO から 32 ビット制御ワードを取得し、バス解放状態のピン方向と値を指定 — アドレスバスとデータバスをトライステートにし、/BUSACK をローにアサート。
  4. /BUSREQ がインアクティブ(ハイ)になるまで jmp pin でスピン。
  5. 2 番目の 32 ビットワードを取得して通常のピン方向を復元し、/BUSACK をデアサート。
  6. IRQ 6 をクリアし、サイクル SM の再開を許可。
サイクル SM は wait 0 irq 6 で毎サイクルの開始時に IRQ 6 を確認します。フラグがセットされていると、バスリクエストが完了するまで SM がストールします。これにより、バス解放はサイクル間でクリーンに発生し、サイクル途中では決して発生しません。

クロック同期
すべてのサイクルタイプ SM は wait 1 gpio Z80_PIN_CLK(立ち上がりエッジ待機)と wait 0 gpio Z80_PIN_CLK(立ち下がりエッジ待機)を使用してホスト Z80 クロックに同期します。これは以下を意味します:
  • PIO プログラムはクロック周波数に依存しない — DC から RP2350 が追跡可能な最大レートまで、あらゆるホストクロック速度で動作します(PIO システムクロックと GPIO サンプリングレートによって制限)。
  • RP2350 の 300 MHz PIO クロックは 3.5 MHz の Z80 T ステートあたり約 85 PIO サイクルを提供し、クロックエッジ間で PIO 命令の実行、FIFO のプッシュ/プル、IRQ フラグの確認に十分すぎる時間があります。
  • z80_sync SM(PIO 2 SM 1)は T1 同期 IRQ を提供し、C コードが PSRAM アクセスをホストクロックに合わせて整列させ、クロックセンシティブなホストソフトウェアのタイミングドリフトを防ぎます。
  • z80_clk_sync SM(PIO 2 SM 2)はホストクロックを別の GPIO に再生成し、外部モニタリングやロジックアナライザーのトリガー用にクリーンなクロック出力を提供します。

割り込みアクノリッジサイクル
z80_int_ack SM(PIO 2 SM 3)は Z80 割り込みアクノリッジシーケンスを実装します。C コードが割り込み条件を検出すると、int_ack プログラムをロードします。このサイクルはフェッチに似ていますが、重要な違いがあります:
  • /M1 は T1 でアサートされます(フェッチと同様)が、ウェイトステート(Tw1)で /MREQ の代わりに /IORQ がアサートされます。
  • 2 つの自動ウェイトステート(Tw1、Tw2)が挿入され、割り込みデバイスがデータバスにベクターを配置する時間を確保します。
  • ベクターバイトが D0–D7 から読み取られ、RX FIFO にプッシュされます。
  • フェッチのリフレッシュフェーズと同じリフレッシュサイクルが続きます。

メモリモデル

メモリアクセスはレイテンシーが増加する 3 つの階層を通じて解決されます。この 3 層設計により、一般的なケース(PSRAM バックの RAM/ROM)が高速でありながら、仮想デバイスと物理ホストパススルーに最大限の柔軟性を提供します。

階層 1 — RP2350 SRAM ディスパッチテーブル

RP2350 の 512KB オンチップ SRAM に常駐する 32 ビット membankPtr 値の 128 エントリ配列が、すべてのバストランザクションに対して O(1) ブロックタイプルックアップを提供します。1 エントリが 64KB Z80 アドレス空間の 512 バイトブロックをカバーします(128 × 512 = 65,536 バイト)。各エントリはエンコードされています:
  • ブロックタイプ(PHYSICAL、RAM、ROM、FUNC など)。
  • PSRAM バックブロックの場合:PSRAM バンク番号とオフセット。
  • FUNC ブロックの場合:仮想デバイス関数ポインターテーブルへのインデックス。
これが最速のパスです — コア 1 は次に何をするかを決める前に単一の SRAM アクセス(300MHz でゼロウェイト状態)で現在のアドレスのディスパッチテーブルエントリを読み取ります。

階層 2 — 外部 PSRAM

8MB の PSRAM は以下のように編成されています:
  • 64 バンク × 64KB — Z80 アドレス空間の RAM または ROM イメージデータ。
  • 64KB memPtr — PTR タイプブロックのバイト単位リダイレクトポインター配列。
  • 64KB memioPtr — メモリマッピング FUNC デバイスの関数ポインター配列。
  • 64KB ioPtr — I/O ポート FUNC デバイスの関数ポインター配列。
PSRAM は DMA を使用した RP2350 の専用 SPI ペリフェラルを介してアクセスされます。アクセスレイテンシーは確定的で、バス違反を避けるためにウェイトステートジェネレーターによって管理されます。

階層 3 — 16MB SPI フラッシュ

ROM イメージは起動時にフラッシュ(または ESP32 経由の SD カード)から PSRAM に読み込まれます。実行時にはバストランザクション用にフラッシュにアクセスしません — すべての ROM データは PSRAM から提供されます。フラッシュは以下に使用されます:
  • ブートローダーとアプリケーションファームウェア。
  • 最小化された config.json(各起動時に SD カードからキャッシュ)。
  • App 設定パーティションの ROM イメージ(SD カードがない場合に使用)。

メモリブロックタイプ

タイプ 説明
PHYSICAL パススルー — RP2350 がバスを解放し、物理ホストメモリが応答する。ホストのネイティブ ROM と RAM に使用。
PHYSICAL_VRAM PHYSICAL と同じだがホストビデオ RAM タイミング用の追加ウェイトステートあり。MZ-700/MZ-80A VRAM 領域に適切。
PHYSICAL_HW ホストハードウェアレジスタのパススルー(メモリ空間の I/O マッピングデバイス)。
RAM 読み書き — PSRAM バンクによってバック。RP2350 が PSRAM への読み書きをサービスする。
ROM 読み取り専用 — PSRAM バンクによってバック。書き込みサイクルはサイレントに無視される(ホストは通常のバスタイミングを見るがデータは保存されない)。
VRAM PSRAM バックビデオ RAM。書き込みサイクルは PSRAM と物理ホスト VRAM の両方に同時にミラーリングされる。
FUNC 仮想デバイス — 各アクセスは memioPtr または ioPtr 関数ポインターテーブルを介して C 関数呼び出しをトリガーし、任意の I/O エミュレーションを可能にする。
PTR バイト単位リダイレクト — 512 バイトブロックの各バイトが独立して他のブロックタイプまたは PSRAM の場所を指すことができる。

設定リファレンス

すべての picoZ80 の動作は SD カードの config.json で制御されます。RP2350 は起動時にこのファイルを読み取り最小化して、フラッシュに保存します。その後の起動では SD カードがない場合はフラッシュのコピーを使用します。
トップレベルの JSON 構造:
{
  "esp32": {
    "core":  { ... },
    "wifi":  { ... }
  },
  "rp2350": {
    "core":  { ... },
    "z80":   [ { "memory": [...], "io": [...], "drivers": [...] } ]
  }
}

esp32.core

キー タイプ 説明
device 文字列 CPU ペルソナ — picoZ80 には "Z80"、pico6502 には "6502"、pico6512 には "6512"
mode 整数 デフォルト WiFi 起動モード:0 = クライアント(ステーション)、1 = アクセスポイント。

esp32.wifi

キー タイプ 説明
override 0/1 マスタースイッチ:1 = 以下のすべての設定を適用;0 = 永続化された NVS 設定を使用。
wifimode 文字列 "ap" = アクセスポイントモード;"client" = ステーション/クライアントモード。
ssid 文字列 作成する(AP)または参加する(クライアント)WiFi ネットワーク名。
password 文字列 WiFi パスフレーズ。
ip 文字列 固定 IP アドレス(例:"192.168.1.192")。
netmask 文字列 サブネットマスク(例:"255.255.255.0")。
gateway 文字列 デフォルトゲートウェイ(例:"192.168.1.1")。
dhcp 0/1 クライアントモード:1 = DHCP;0 = 固定 IP 設定を使用。
webfs 文字列 SD カード上のウェブファイルシステムのルートディレクトリ(デフォルト "webfs")。
persist 0/1 1 = 再起動をまたいで永続化するために解決された設定を NVS に書き込む。

rp2350.core

キー タイプ 説明
cpufreq 整数 RP2350 システムクロック(Hz 単位、例:300000000)。最大安定周波数は PSRAM 周波数とコア電圧に依存する。
psramfreq 整数 PSRAM SPI クロック(Hz 単位、例:133000000)。
voltage 浮動小数点 RP2350 コア電圧(ボルト単位、例:1.10)。高クロック速度にはより高い電圧が必要。

z80[].memory — メモリマップエントリ

memory 配列は Z80 メモリマップを定義します。エントリはアドレス順に並べる必要があります。領域は 512 バイトに揃え、512 バイトの倍数でサイズを指定する必要があります。エントリ間のギャップは PHYSICAL パススルーとして扱われます。
キー タイプ 説明
enable 0/1 このエントリがアクティブかどうか。無効なエントリは起動時に無視される。
addr 16 進文字列 Z80 アドレス空間の開始アドレス(例:"0x0000")。512 バイト境界に合わせる必要がある。
size 16 進文字列 バイト単位の領域サイズ(例:8KB の場合は "0x2000")。512 の倍数でなければならない。
type 文字列 ブロックタイプ — メモリブロックタイプを参照。
bank 整数 RAM/ROM/VRAM/FUNC タイプの PSRAM バンク番号(0–63)。
tcycwait 整数 この領域の各アクセスに挿入する追加 T サイクルウェイトステート。
tcycsync 0/1 この領域に T1 同期を有効化する。タイミングセンシティブな領域に必要。
task 文字列 FUNC タイプブロックのオプションタスク識別子(ドライバーバインディング文字列)。
file 文字列 起動時に PSRAM バンクにプリロードする ROM イメージへの SD カードパス(例:"/ROM/mz700.rom")。
fileofs 整数 ROM イメージファイルから読み取りを開始するバイトオフセット。

z80[].io — I/O ポートマップエントリ

io 配列は Z80 I/O ポート範囲をブロックタイプにマッピングします。I/O エントリには PHYSICAL と FUNC タイプのみが意味を持ちます。
キー タイプ 説明
enable 0/1 この I/O エントリがアクティブかどうか。
addr 16 進文字列 開始 I/O ポートアドレス(例:"0xE0")。
size 16 進文字列 連続するポートの数(例:ポート E0–E3 の場合は "0x04")。
type 文字列 PHYSICAL = ホストにパス;FUNC = C ハンドラ関数を呼び出す。
task 文字列 FUNC タイプエントリのドライバーバインディング文字列。

z80[].drivers — ドライバーインスタンス

drivers 配列は仮想デバイスドライバーをインスタンス化してメモリまたは I/O 領域にバインドします。各ドライバーにはタイプ(C ドライバーモジュール)、名前(インスタンス識別子)、そのドライバーインスタンスの ROM イメージ、アドレスマップ、I/O マップ、パラメータを定義する 1 つ以上のインターフェースオブジェクトがあります。
"drivers": [
  {
    "type":   "WD1773",
    "name":   "FDC",
    "interfaces": [
      {
        "name":    "FDC_0",
        "type":    "FDC",
        "rom":     [],
        "addrmap": [],
        "iomap": [
          { "enable": 1, "addr": "0xE0", "size": "0x04", "type": "FUNC" }
        ],
        "param": [
          { "name": "tracks",   "value": "80" },
          { "name": "heads",    "value": "2" },
          { "name": "sectors",  "value": "8" },
          { "name": "image",    "value": "/DSK/disk1.dsk" }
        ]
      }
    ]
  }
]

組み込みドライバー(INCLUDE_SHARP_DRIVERS)

ファームウェアが INCLUDE_SHARP_DRIVERS でビルドされた場合、以下のドライバーモジュールがコンパイルされ drivers 配列を介してインスタンス化できます:
ドライバー タイプ文字列 説明
MZ700.c MZ700 Sharp MZ-700 バンク切り替え、ビデオ、キーボード I/O
MZ80A.c MZ80A Sharp MZ-80A ドライバー — モニター ROM (SA-1510)、VRAM、Intel 8253 PIT エミュレーション、8255 PPI、MEMSW/MEMSWR メモリスワップ(CP/M バンク切り替え含む)、物理+仮想ミックスドモード対応、RFS/MZ80AFI/MZ-1E14/MZ-1E19/MZ-1R12/MZ-1R18 サブインターフェース対応
MZ2000.c MZ2000 Sharp MZ-2000 — 物理モード(実 MZ-2000 ハードウェアでのドロップイン Z80 置換、ブート/通常モード自動検出)と仮想モード(PSRAM ベースの完全エミュレーション、IPL ROM ミラーリング)の両方をサポート。BST/NST メモリモード切替、キャラクター+グラフィックス VRAM オーバーレイ、8253 PIT、8255 PPI、Z80 PIO、MB8866 FDC
MZ80AFI.c MZ80AFI Sharp MZ-80A フロッピーインターフェース — MZ-80A AFI フロッピーディスクコントローラーのエミュレーション
WD1773.c WD1773 WD1773 FDC — 80 トラック、2 ヘッド、8 セクター DSK、RAW、D88 イメージフォーマット対応
QDDrive.c QDDRIVE Sharp QuickDisk ドライブ — フル Z80 SIO/2 エミュレーション、スパイラルトラックデータ、モーター制御、非同期 SD カード I/O
RFS.c RFS ROM ファイリングシステム — SD カードからの MZF 読み込み、CP/M、BASIC
TZFS.c TZFS TranZPUter ファイリングシステム(開発中)
MZ-1E05.c MZ1E05 Sharp MZ-1E05 フロッピーディスクインターフェースユニット(WD1773 ベース)
MZ8BFI.c MZ8BFI / E0054PA MZ-2000 フロッピーディスクインターフェース — ドライバー ROM なし MB8866 FDC(コードは IPL 内)。D88 フォーマット対応。
MZ-1E14.c MZ1E14 BIOS ROM 付き MZ-1E14 QuickDisk コントローラ(MZ-700/MZ-800)
MZ-1E19.c MZ1E19 BIOS ROM なし MZ-1E19 QuickDisk コントローラ
MZ-1R12.c MZ1R12 32KB バッテリーバックアップ RAM ボード(SD カードに永続化)
MZ-1R18.c MZ1R18 64KB RAM 拡張ボード
PIT8253.c PIT8253 スタンドアロン Intel 8253 PIT エミュレーション — 全 6 カウンターモード、BCD/バイナリ、ラッチ、LSB/MSB 読み書きモード対応
PPI8255.c PPI8255 スタンドアロン Intel 8255 PPI エミュレーション — モード 0 I/O、ビットセット/リセット、出力コールバック、入力インジェクション対応

仮想デバイスフレームワーク

FUNC ブロックタイプは各バスアクセスで C ハンドラ関数を呼び出すことで任意の I/O エミュレーションを可能にします。メモリの任意の 512 バイトブロックまたは I/O ポートの範囲を関数でバックすることができます。

ハンドラ関数シグネチャ

メモリ FUNC ハンドラは PSRAM の memioPtr テーブルに保存されます。I/O FUNC ハンドラは ioPtr テーブルに保存されます。関数シグネチャは以下の通りです:
/* メモリ読み取りハンドラ */
uint8_t mem_read_handler(uint16_t addr, void *ctx);

/* メモリ書き込みハンドラ */
void mem_write_handler(uint16_t addr, uint8_t data, void *ctx);

/* I/O 読み取りハンドラ */
uint8_t io_read_handler(uint8_t port, void *ctx);

/* I/O 書き込みハンドラ */
void io_write_handler(uint8_t port, uint8_t data, void *ctx);
ハンドラ関数はコア 1 のホットループから直接呼び出されます。現在のバスサイクルのウェイトステートが切れる前に完了しなければなりません — ハンドラは短く、ブロッキング操作(ファイル I/O、UART など)は避けてください。ハンドラがより長い操作(例えばディスクセクターの読み込み)をトリガーする必要がある場合は、コア間キューを介してコア 0 にメッセージを送信して直ちに返すべきです。実際の I/O はコア 0 に委ねます。

新しいドライバーの作成

新しいペリフェラルまたはホストマシンのサポートを追加するには:
  1. src/drivers/ ディレクトリに新しい .c / .h ファイルを作成する。
  2. 上記のシグネチャに一致する読み取りと書き込みハンドラ関数を実装する。
  3. ドライバー初期化中に memioPtr または ioPtr テーブルにハンドラ関数ポインターを登録する。
  4. CMakeLists.txt ビルドターゲットにドライバーを追加する。
  5. JSON 設定パーサーが名前でドライバーをインスタンス化できるようにタイプ文字列エントリを追加する。
  6. ドライバーのヘッダーファイルにドライバーの param キーを文書化する。
ドライバー初期化関数は config.json が解析された後、起動時に一度呼び出されます。ドライバーはインターフェース設定ブロックへのポインターを受け取り、この時点で内部状態を設定してハンドラを登録すべきです。

ICE(デバッグシェル)

picoZ80 には USB CDC チャネル 1(ボードを USB 接続した際に列挙される 2 番目のシリアルポート)で動作する組み込み ICE(インサーキットエミュレーター)デバッグシェルが含まれています。シェルはコア 0 で動作し、Z80CPU コンテキスト構造体の共有フラグを介してコア 1 のエミュレーションループと通信します。

アーキテクチャ

  • 入出力:USB CDC チャネル 1、115200 baud。シェルプロンプトは dbg> 。コマンド履歴(16 エントリ)とキャラクターエコーをサポート。
  • ブレークポイント:最大 8 個の同時ブレークポイントを cpu->dbgBpAddr[] に格納。コア 1 は各オペコードフェッチ前にブレークポイント配列をチェックし、ヒット時に cpu->hold = true を設定して dbgBpHit 経由でコア 0 に通知。
  • シングルステップ:step コマンドは cpu->dbgStepCount を設定。コア 1 は各命令後にこのカウンターをデクリメントし、ゼロに達すると自動的にホールド。各ステップで前後のレジスタ状態とディスアセンブルされた命令が表示される。
  • 実行トレース:512 エントリのリングバッファ(cpu->dbgTrace[])がトレース有効時に各実行命令の PC、オペコード、フラグレジスタを記録。各 32 ビットエントリは [31:16]=PC, [15:8]=opcode, [7:0]=F register にパック。
  • メモリアクセス:物理メモリアクセス(dm pwm p)は PIO ステートマシンを介して実 Z80 バスサイクルを駆動。仮想アクセス(dm vwm v)は PSRAM を直接読み書き。自動モード(修飾子なしの wm)はメモリマップに従う。RP2350 アクセス(dm r)は範囲検証付きでホストマイクロコントローラーのアドレス空間を読み取り。
  • ホールド/リリース:hold コマンドは cpu->hold = true を設定。コア 1 は cpu->holdAck で応答し、シェルが共有状態にアクセスする前に CPU が静止していることを保証。

コマンドリファレンス

コマンド 構文 説明
help help 全コマンドの一覧表示
regs regs 全 Z80 レジスター、フラグ、サイクルカウントをダンプ
dm dm [p|f|v|r] <addr> [len] メモリダンプ(物理 / フェッチ / 仮想 / RP2350)
dis dis [p|v] [addr] [count] Z80 コードをディスアセンブル
asm asm [addr] インタラクティブ Z80 アセンブラー
memmap memmap [block] メモリバンクポインターテーブルを表示
memptr memptr [addr] PSRAM memPtr テーブルを表示
iomap iomap [port] I/O ポートハンドラーテーブルを表示
status status システムステータス(CPU 周波数、PSRAM、稼働時間)
ver ver ファームウェアバージョンとパーティション情報
drivers drivers アクティブなドライバーとインターフェースの一覧
hold hold CPU エミュレーションを一時停止
release release CPU エミュレーションを再開
go go 実行を継続(ホールド解除、ブレークポイントはアクティブ)
cont cont go のエイリアス。現在のホールドポイントから実行を続行。ブレークポイントはアクティブなまま
step step [n] n 命令をシングルステップ実行
bp bp <addr> ブレークポイントを設定(最大 8 個)
bc bc <n|*> ブレークポイント n または全てをクリア
bl bl ブレークポイントの一覧表示
wm wm [p|v] <addr> <byte>... メモリに書き込み(物理/仮想/自動)
fill fill [p|v] <addr> <len> [w|d] <val> メモリを定数値で充填
in in <port> Z80 I/O ポートを読み取り
out out <port> <byte> Z80 I/O ポートに書き込み
trace trace <on|off|dump [n]|clear|rt|byte> 実行トレースの制御。rt でリアルタイムトレース出力。byte でバイトレベルトレース
set set <reg|flags|memmap|memptr|iomap> Z80 レジスター、フラグ、メモリマップ、memPtr、I/O マップエントリを実行時に変更
hist hist [n] コマンド履歴を表示(ESP32 NVS に保存)
save save コマンド履歴を ESP32 NVS に即座に強制保存
fdctrace fdctrace <on|off|dump> FDC I/O トレースログの有効/無効切替。dump で最後の 64 回の操作をダンプ
qdtrace qdtrace <on|off|dump> Quick Disk I/O トレースログの有効/無効切替。dump で最後の 64 回の操作をダンプ
piodbg piodbg [clear] 全 3 PIO ブロックの RP2350 PIO ハードウェア診断を表示。clear で FDEBUG スティッキーフラグをリセット
cmp cmp [f] <phys> <virt> <len> 物理バスメモリと仮想 PSRAM を比較。f でフェッチサイクルを使用
copy copy <pv|fp|vp> <src> <len> <dst> 物理メモリと仮想メモリ間でコピー
memtest memtest <addr> <len> [pattern] 物理メモリを 3 パスでテスト(書き込み+読み取り、書き込み+フェッチ、インターリーブ)
verify verify <on|off> 全オペコードフェッチ検証の切り替え
fwait fwait <0-4> M1(オペコードフェッチ)サイクルに追加ウェイトステートを強制。0 = オフ(デフォルト)。パラメータなしで現在の設定を表示
iowait iowait <0-8> I/O 読み書きサイクルに追加ウェイトステートを強制。0 = オフ(デフォルト)。パラメータなしで現在の設定を表示
corrupt corrupt [clear] 検出されたフェッチ破損を表示。clear でログをリセット
echo echo [on|off] ターミナルエコーの切り替え
reset reset Z80 をリセット

ESP32 コプロセッサ

ESP32-S3-PICO-1 モジュールはすべてのネットワークとストレージ機能を処理するコプロセッサとして機能します。2 つのインターフェースで RP2350 と通信します:
  • FSPI(50MHz、4 線 SPI) — Binary IPC Protocol v1.1 — 高速バルクデータ転送(ROM イメージ、ディスクセクター読み書き、設定ファイルダウンロード)。プロトコルは固定 64 バイトバイナリフレームヘッダーと CRC32 整合性チェック(以前の XOR チェックサムを置き換え)を使用します。DMA チャネルは初期化時に事前割り当てされ解放されないため、転送ごとの claim/unclaim オーバーヘッドと競合状態を排除します。バーストセクター転送は単一の SPI トランザクションで最大 16 × 512 バイトセクター(8KB)を可能にし、フロッピーおよび QuickDisk イメージの読み込み時間を大幅に短縮します。RX DMA チャネルは HIGH PRIORITY に昇格され、コア 1 の PSRAM QMI バス競合による FIFO オーバーフローを防止します。
  • UART(460.8kbaud) — 制御メッセージ、ステータスクエリ、短いデータ交換のためのコマンド/レスポンスプロトコル。

ネットワーキングモード

ESP32 ファームウェアは 3 つのネットワーキングモードをサポートしており、ビルド時にプリビルト sdkconfig ファイルで選択します:
モード 設定ファイル WiFi USB NCM コンソール FCC/RED 必要
WiFi のみ sdkconfig.mode_wifi_only Yes No USB Serial/JTAG Yes
WiFi + NCM sdkconfig.mode_wifi_and_ncm Yes Yes TinyUSB CDC-ACM Yes
NCM のみ sdkconfig.mode_ncm_only No Yes TinyUSB CDC-ACM No
USB NCM (Network Control Model) は ESP32-S3 USB OTG ポート(GPIO 19/20)上に CDC-NCM Ethernet アダプターを提供します。コンポジット USB デバイスが CDC-ACM シリアルポート(デバッグログ用)と NCM ネットワークインターフェースの両方を公開します。内蔵 DHCP サーバーが 192.168.7.0/24 サブネットからホストに IP アドレスを割り当て、picoZ80 は 192.168.7.1 でアクセス可能です。リース時間は 120 分です。
WiFi+NCM モードでは、HTTP サーバーは INADDR_ANY:80 にバインドし、両方のインターフェースを同時に提供します。WiFi は非同期で接続するため、USB NCM インターフェースは電源投入後すぐに利用可能です。
NCM のみが有効な場合、WiFi 無線は完全に無効化され、WiFi マネージャーページはウェブインターフェースから非表示になります。ESP32-S3 アンテナ整合回路は PCB 上に実装する必要がありません。

SD カードインターフェース

ESP32 は SPI インターフェースを介して SD カードを管理します。SD カードは FAT32 としてマウントされ、RP2350 からのすべてのファイルアクセスは ESP32 が仲介します — RP2350 は FSPI/UART リンク経由でファイル I/O コマンドを送信し、ESP32 が実際の FAT32 読み書き操作を実行します。
SD カードは ESP32 ウェブサーバーからも直接アクセス可能で、webfs/ ディレクトリからファイルを提供し、ファイルマネージャーが HTTP 経由でカードの内容を参照・変更することを可能にします。

ウェブサーバー

ESP32 はポート 80 で HTTP サーバーを実行します(TLS なし — ローカルネットワーク使用のみ)。すべてのウェブアセット(HTML、CSS、JavaScript)は SD カードの webfs/ ディレクトリから提供され、ESP32 ファームウェアを再フラッシュせずにウェブインターフェースを更新できます。ウェブサーバーは以下を処理します:
  • SD カードの webfs/ ディレクトリからの静的ウェブアセットの提供。
  • JSON データ(システムステータス、設定の読み書き、ファイル操作)の REST API エンドポイント。
  • RP2350 と ESP32 の両方の OTA ファームウェアアップロードエンドポイント。
  • リアルタイムダッシュボードステータス更新のための WebSocket 接続。

RP2350 ↔ ESP32 コマンドプロトコル

RP2350(コア 0)は UART リンクを介したシンプルなコマンド/レスポンスプロトコルで ESP32 と通信します。コマンドはオプションのペイロードバイトを持つ 1 バイトのオペコードです。ESP32 は各コマンドに対してステータスバイトとその後の応答データで応答します。
一般的なコマンドカテゴリ:
  • ファイル I/O — オープン、読み取り、書き込み、クローズ、ディレクトリリスト、ファイル情報。
  • 設定 — config.json の内容を要求、更新された設定を書き込む、リロードリクエスト。
  • ディスク — ディスクイメージのマウント/アンマウント、セクター読み書き(WD1773 および QDDrive エミュレーションからリレー)。現在マウントされているフロッピーおよび QuickDisk イメージのファイル名は ESP32 が追跡し、ウェブインターフェースの Actions メニューに表示されます。
  • システム — バージョンクエリ、再起動リクエスト、NVS 読み書き。
FSPI インターフェースはペイロードが UART には大きすぎるバルク転送(ROM イメージアップロード、ディスクセクターデータ)に使用され、UART はすべての制御コマンドを処理します。

SWD デバッグ — RP2350

RP2350 は ARM シリアルワイヤデバッグ(SWD)を介したフルソースレベルデバッグをサポートします。CMSIS-DAP 互換プローブ(Raspberry Pi Debug Probe、Black Magic Probe、または類似品)をデバッグヘッダーのピン 1(SWCLK)、ピン 2(SWDIO)、ピン 5(GND)に接続します。

OpenOCD セットアップ

picoZ80 は、コアごとに独立した GDB ポートを持つ SMP デバッグを有効化するために標準の OpenOCD RP2350 ターゲットスクリプトへの小さな変更を必要とします:
sudo cp /usr/local/share/openocd/scripts/target/rp2350.cfg \
        /usr/local/share/openocd/scripts/target/rp2350_tzpu.cfg
rp2350_tzpu.cfg を編集します — if {[string compare $_USE_CORE SMP] == 0} ブロック内の target smp 行を見つけて先頭の # を削除します:
# 変更前:
    #target smp $_TARGETNAME_0 $_TARGETNAME_1


# 変更後:
    target smp $_TARGETNAME_0 $_TARGETNAME_1
この 1 つの変更により OpenOCD はコア 0 を GDB ポート 3333 に、コア 1 を GDB ポート 3334 に登録し、独立したコアごとの GDB セッションを可能にします。GDB を起動する前に OpenOCD を起動します:
openocd -f interface/cmsis-dap.cfg -f target/rp2350_tzpu.cfg -c "adapter speed 5000"

GDB 設定

ディレクトリごとの .gdbinit ファイルの自動読み込みを許可するために以下を ~/.gdbinit に追加します(プロジェクトの場所に合った絶対パスで):
set history save on
set history filename ~/.gdb_history
set history size 65536
add-auto-load-safe-path /path/to/project/build/bin/model/BaseZ80/.gdbinit
add-auto-load-safe-path /path/to/project/build/bin/model/Bootloader/.gdbinit
ブートローダーのデバッグ
# ターミナル 1 — コア 0(ポート 3333)
cd build/bin/model/Bootloader
cp ../../../../.gdbinit.bootloader.3333 .gdbinit
gdb-multiarch Bootloader.elf


# ターミナル 2 — コア 1(ポート 3334)
cd build/bin/model/Bootloader
cp ../../../../.gdbinit.bootloader.3334 .gdbinit
gdb-multiarch Bootloader.elf
メインファームウェアのデバッグ
# ターミナル 1 — コア 0(ポート 3333)
cd build/bin/model/BaseZ80
cp ../../../../.gdbinit.3333 .gdbinit
gdb-multiarch BaseZ80_0x10020000.elf

# ターミナル 2 — コア 1(ポート 3334)
cd build/bin/model/BaseZ80
cp ../../../../.gdbinit.3334 .gdbinit
gdb-multiarch BaseZ80_0x10020000.elf


# メモリダンプ(GDB プロンプトから)— 16 進 + ASCII:
(gdb) xac 0x20000000 64
xac <アドレス> <カウント> GDB コマンドは .gdbinit.3333 / .gdbinit.3334 ファイルで定義されています。メモリを 16 進と ASCII の組み合わせ出力としてダンプし、PSRAM バンクの内容やメモリマッピングデバイスの状態を調べるのに便利です。

ESP32 USB デバッグ

ESP32-S3 コプロセッサには組み込みの USB-JTAG インターフェースがあります — 外部デバッグプローブは不要です。ホスト PC から picoZ80 ボードの ESP32 USB ポートに USB ケーブルを接続します。
# ESP32-S3 用に OpenOCD を起動する
openocd -f board/esp32s3-builtin.cfg


# 2 番目のターミナルで — Xtensa GDB を起動する
xtensa-esp32s3-elf-gdb esp32/build/main.elf
(gdb) target extended-remote :3333
シンボルとアドレスが正しく一致するよう、ELF がデバイスで実行されているファームウェアと同じソースリビジョンからビルドされていることを確認してください。

ビルドシステム

picoZ80 ファームウェアは Raspberry Pi Pico SDK 2.x を使用した CMake を使用します。ビルドシステムは 2 つの独立したファームウェアイメージを生成します:ブートローダーアプリケーション(異なるフラッシュオフセットの 2 つのスロット)。ESP32 ファームウェアは Docker 経由で管理される ESP-IDF v5.4 を使用して別途ビルドされます。

CMake ビルドターゲット

ターゲット 出力 フラッシュアドレス
Bootloader Bootloader.elfBootloader.uf2 0x10000000
BaseZ80_0x10020000 BaseZ80_0x10020000.elf.bin 0x10020000(スロット 1)
BaseZ80_0x10520000 BaseZ80_0x10520000.elf.bin 0x10520000(スロット 2)

主要 CMake ビルドフラグ

フラグ 効果
INCLUDE_SHARP_DRIVERS すべての Sharp MZ ペリフェラルドライバー(MZ700、WD1773、QDDrive、RFS、TZFS、MZ-1E05、MZ8BFI、MZ-1E14、MZ-1E19、MZ-1R12、MZ-1R18)をコンパイルイン。
INCLUDE_DBGSH ICE デバッグシェル(dbgsh.c)をコンパイルイン。定義時は DBGSH バリアントとしてビルドされ、USB CDC チャネル 1 で 35 コマンドの ICE デバッガーが有効化される。
CMAKE_BUILD_TYPE=Debug デバッグシンボルを有効化し最適化を無効化。ソースレベル GDB デバッグに必要。
CMAKE_BUILD_TYPE=Release フル最適化(-O3)。本番ファームウェアに使用。

ビルドコマンド

# 初回:SDK をクローンしてビルドする
./get_and_build_sdk.sh


# 標準リリースビルド(RP2350 のみ)
./build_tzpuPico.sh


# デバッグビルド
./build_tzpuPico.sh DEBUG


# フルビルド:RP2350 + ESP32(ESP32 は Docker 経由でビルド)
./build_tzpuPico.sh ALL


# ESP32 のみ、Docker idf54 エイリアスを使用
cd projects/tzpuPico/esp32
idf54 build
build_tzpuPico.sh スクリプトはビルド成功時にバージョン番号を自動的にインクリメントし、バージョンスタンプ付きの出力ファイルを fw/uf2/(ブートローダー UF2)と fw/bin/(OTA 用アプリケーションバイナリ)にコピーします。ブートローダー UF2 は初期 USB 大容量ストレージフラッシュにのみ使用されます。アプリケーションスロットのバイナリは非標準のフラッシュアドレスに存在するため(UF2 ではなく)プレーンバイナリ形式を使用します。
ビルドシステムは 4 つのファームウェアバリアントを生成します:標準(デバッグシェルなし)と DBGSH(ICE デバッガー付き)× 2 パーティション。DBGSH バリアントは INCLUDE_DBGSH コンパイル定義で制御されます。

ESP32 ネットワーキングモード選択

ネットワーキングモードを切り替えるには、ビルド前に適切なプリビルト設定ファイルを sdkconfig にコピーします:
cd esp32/
cp sdkconfig.mode_ncm_only sdkconfig    # NCM のみ(FCC/RED 安全)
# or: cp sdkconfig.mode_wifi_only sdkconfig
# or: cp sdkconfig.mode_wifi_and_ncm sdkconfig
idf.py build
idf.py flash

ウォッチドッグとブート診断

RP2350 ファームウェアはハードウェアウォッチドッグタイマーを使用して、ブート時のハングやメインループのストールを検出・復旧します。ウォッチドッグはブートシーケンスの初期段階で 30 秒のタイムアウトで有効化され、各主要マイルストーンで kick(watchdog_update())されます。いずれかのブートステージまたはメインループの反復がタイムアウトを超えた場合、ウォッチドッグは RP2350 を自動的にリセットします。

起動進捗トラッキング

起動進捗は RP2350 のウォッチドッグ scratch レジスタを使用して追跡されます。scratch レジスタはウォッチドッグリセットを生き延びます(電源投入リセットでは生き延びません)。これにより、ウォッチドッグリセット後にファームウェアがハングする前にどのブートステージに到達したかを正確に判断できます。
Scratch レジスタ 名前 内容
scratch[0–3] ブート履歴 直近 4 回のリセット試行 — 各エントリは (attempt_count << 24) | (stage << 16) | (resetCause & 0xFFFF) をエンコード。ウォッチドッグリセットのたびにエントリがシフト:[0]←[1]←[2]←[3]←current
scratch[4] SPI 診断 FSPI リンクのパック済み診断カウンター:breadcrumbs、メッセージタイプ、T1/T3 タイムアウトカウンター、不正フレーム数、OK 数。
scratch[5] マジックマーカー scratch レジスタに有効なブート進捗データが含まれていることを示す 0xB00710BE に設定。
scratch[6] 現在のステージ 最新のブートステージコード(以下の表を参照)。
scratch[7] リセット原因 ハードウェアリセットコントローラからのリセット原因コード。
ブートステージコードは 0x01(開始)から 0x10(メインループ突入)まで進行します。メインループ内のサブステージ(0x11–0x17)とコア間コマンド処理(0x20–0x27)は詳細なトラッキングを提供します:
コード ステージ 説明
0x01 BOOTP_START エントリーポイント到達
0x02 BOOTP_CLK_SET システムクロック設定完了
0x03 BOOTP_PSRAM_INIT PSRAM 初期化開始
0x04 BOOTP_PSRAM_OK PSRAM 初期化成功
0x05 BOOTP_STDIO_INIT USB stdio 初期化完了
0x06 BOOTP_PIO_INIT PIO ステートマシンロード完了
0x07 BOOTP_Z80_INIT Z80 CPU コンテキスト初期化完了
0x08 BOOTP_USB_INIT USB ブリッジ初期化完了
0x0A BOOTP_ESP_HS_SYNC ESP32 SPI ハンドシェイク同期
0x0B BOOTP_CORE1_LAUNCH コア 1 起動
0x0D BOOTP_FSPI_INIT FSPI Binary IPC 初期化完了
0x0E BOOTP_ESP_INIT ESP32 通信準備完了
0x10 BOOTP_MAIN_LOOP メインループ突入
0x11–0x17 メインループサブステージ USB ポーリング、コア間通信、SPI NOP/CMD、タスク
0x20–0x27 コア間コマンド フロッピーロード、QD ロード、RAMFILE ロード、ファイル I/O

PSRAM 永続ログ(plogf)

8MB PSRAM の最後の 4KB(アドレス 0x117FF000)は、ウォッチドッグリセットを生き延びる永続デバッグログ用に予約されています。plogf() マクロはブート中、通常の debugf() 出力用の USB が利用可能になる前に printf スタイルのメッセージをこのバッファに書き込みます。次回の正常起動時に dump_plog() 関数がキャプチャされたメッセージをデバッグコンソールに出力し、バッファをクリアします。ログは 4 バイトのマジックマーカー(0x504C4F47 = "PLOG")、4 バイトの長さカウンター、3840 バイトの循環テキストバッファというシンプルな構造を使用します。

フォールト診断

ファームウェアはハードフォールト、メモリ管理フォールト、バスフォールト、使用フォールトに対して Cortex-M33 フォールトハンドラをインストールします。フォールト発生時、ハンドラは PSRAM の最後の 256 バイト(アドレス 0x117FFF00)にマジックマーカー(0xFA017000)、フォールトタイプ、すべての関連レジスタ(PC、LR、SP、R0–R3、R12、PSR)、Configurable Fault Status Register(CFSR)、Hard Fault Status Register(HFSR)、Bus Fault Address Register(BFAR)、Memory Management Fault Address Register(MMFAR)、およびコア ID を含む完全な診断スナップショットを保存します。ハンドラはその後無限ループに入り、ウォッチドッグにリセットをトリガーさせます。次回起動時にファームウェアが有効なフォールト診断をチェックし、キャプチャされた情報を debugf() 経由で出力するため、ライブデバッガセッションなしで事後分析が可能です。

フラッシュ設定クリア

RP2350 OTA アップデートメカニズムはファームウェアアップロード以外に 2 つの追加操作をサポートします:
  • App Config クリアFW_CFGCLEAR_ID = 0xB1D7E5FA)— ターゲットファームウェアスロットに関連付けられた App Config パーティション(ROM イメージとミニファイ済み JSON)を消去します。これにより、次回起動時にファームウェアが SD カードから config.json を再読み込みすることを強制します。ファームウェアバージョン間で設定スキーマが変更された場合に必要です。
  • フラッシュヘッダークリアFW_HDRCLEAR_ID = 0xC2E8F6AB)— フラッシュパーティションヘッダーを工場出荷時のデフォルトにリセットします。ブートローダー設定(パーティション 0)は保持されますが、すべてのアプリケーションパーティションメタデータがゼロから再構築されます。パーティションテーブルが破損した場合、または異なるパーティションレイアウトを想定する以前のファームウェアバージョンにダウングレードする場合に使用します。
両方の操作は RP2350 OTA ウェブページのチェックボックスからトリガーされ、ファームウェアアップデートプロセス中にブートローダーによって実行されます。

参考サイト

リソース リンク
picoZ80 プロジェクトページ /picoz80/
picoZ80 ユーザーマニュアル /picoz80-usermanual/
pico6502 プロジェクトページ /pico6502/
RP2350 データシート datasheets.raspberrypi.com
RP2350 PIO リファレンス datasheets.raspberrypi.com — 付録 B
Pico SDK ドキュメント raspberrypi.github.io/pico-sdk-doxygen
ESP32-S3 技術リファレンス docs.espressif.com
ESP-IDF プログラミングガイド docs.espressif.com/esp-idf
Zilog Z80 CPU ユーザーマニュアル zilog.com
OpenOCD ドキュメント openocd.org
X(Twitter)プロジェクトプレビュー engineerswork1

無線規制に関する注意事項

本デバイスは 2.4 GHz ISM バンドで送信する ESP32-S3-PICO-1 無線モジュールを搭載しており、WiFi ファームウェアがインストールされている場合、世界各国の無線周波数規制(米国の FCC Part 15 Subpart C、欧州連合の無線機器指令 2014/53/EU を含む)において意図的放射器に該当します。
出荷時の構成
出荷時には、picoZ80 ボードは NCM のみのファームウェア(sdkconfig.mode_ncm_only)でフラッシュされています。この構成では WiFi 無線は完全に無効化され、ESP32-S3 アンテナ整合回路の部品は PCB 上に実装されていません。高周波送信が発生しないため、デバイスは意図的放射器に該当せず、FCC、CE/RED、または同等の認証は不要です。規制上の認可なしに販売、配布、または贈与が可能です。
WiFi の追加
エンドユーザーはアンテナ整合回路を実装し、WiFi 対応ファームウェア(sdkconfig.mode_wifi_only または sdkconfig.mode_wifi_and_ncm)をフラッシュすることで、個人使用、実験、または教育目的で WiFi を使用できます。WiFi を有効にすると、デバイスは意図的放射器となり、以下のルールが適用されます:
ESP32-S3-PICO-1 モジュール自体は既存の規制認証(FCC、CE など)を取得していますが、そのモジュールレベルの認証は、モジュールを組み込んだ完成品に自動的に適用されるものではありません。事前認証モジュールの免除規定は、個人の趣味愛好家個人使用、実験、または教育目的で少数のデバイスを製作する場合に、個別の機器認可を取得せずに行うことを許可するものです。
重要な制限事項
  • WiFi ファームウェアを搭載したデバイスは、完成品が独自にテストされ、該当する管轄区域で機器認可(例:FCC ID、認定機関による CE マーキング評価)を取得しない限り、第三者への販売、販売の申し出、贈与、またはその他の方法での配布を行ってはなりません
  • 個人使用のために WiFi を有効にして少数を製作することは、趣味愛好家および実験使用の規定(例:FCC § 15.23)に基づき、デバイスが有害な干渉を引き起こさない限り、一般的に許可されています。
  • WiFi を有効にした商業販売には、完全な製品レベルの FCC/RED(または同等の)認証が必要です。
  • 規制要件は国によって異なります。米国外の製作者は、適用される規則について自国の無線周波数当局に確認してください。
製作者の責任
本設計に基づいて製作されたデバイスが、管轄区域内の適用されるすべての無線周波数規制に準拠することは、製作者の単独の責任です。著者は本設計を個人使用、教育、および趣味愛好家向けに提供しており、本設計から製作されたデバイスが商業的配布の規制要件を満たすことについて、いかなる表明も行いません。