SEGA-CD(メガCD)の 2 つの 68000 CPU が、実機で MOVIE.DAT ストリームを再生する
とき 実際に何を実行し、どう同期し、どのメモリをどれだけ使うか を実行時の視点で
まとめたもの。対象は Tile Texture Reuse Codec
のプレイヤー boot/movieplay_sp.s(Sub CPU)と boot/movieplay_ip.s(Main CPU)。
メモリ数値は H40フル(320×224 / 40×28=1120セル / 15fps)の実測ビルドに基づく。
この設計は、3 つの厳しいハード制約を 同時に 満たすために存在する:
- CD は連続読みを維持 —
ROM_READN1 回、シークし直さない (再シークはストリームが許せる帯域よりはるかに高くつく)。 - VDP の VRAM 転送は垂直帰線(VBLANK)中しか DMA できない — アクティブ表示に はみ出した DMA は、厳密なハード/エミュで画面を壊す。
- 2 つの CPU が 1 つの Word RAM を共有 — 同じバンクを同時には触れないので、 フレームバッファを両者で ping-pong させる。
答えは producer/consumer パイプライン: Sub CPU が裏バンクへ 1 フレームを生成する間、 Main CPU が表バンクから前フレームを消費し、フレーム境界でバンクを受け渡す。
┌──────────────┐ セクタ ┌───────────────┐ 復号 ┌──────────────┐
│ CDドライブ │─(CDC DMA)─▶│ Sub CPU (SP) │──(pop)──▶│ Word RAM │
│ 連続 │ │ 68000 │ │ 1M / 1M │
│ ROM_READN │ │ 生成側 │ │ 各バンク128KB │
└──────────────┘ └─┬────┬────┬──┘ └──────┬───────┘
apply/RING │ │ │ 887B/frame │ swap
▼ │ ▼ (MEMMODE bit0)
┌──────────────┐ │ ┌────────────────┐ ▼
│ PRG-RAM │ │ │ RF5C164 PCM │ ┌──────────────┐
│ 512KB (SP) │ │ │ wave-RAMリング │ │ Main CPU(MD)│
│ ┌──────────┐ │ │ │ 音声を再生 │ │ 68000 │
│ │RING 貯水池│ │ │ └────────────────┘ │ 消費側 │
│ │420KB │ │ │ └──────┬───────┘
│ │(タンク) │ │ │ │ VBLANK内DMA
│ └──────────┘ │ │ ┌──────▼───────┐
│ apply 34KB │ │ │ Main-RAM │
│ routing 8KB │ │ │ 64KB (MD) │
└──────────────┘ │ │ DMA_STAGE24KB│
└─(Sub が両方に給餌) │ →VDP→VRAM→画面│
└──────────────┘
- Sub CPU(SP) — CD セクタを吸い上げ、1 フレーム分の control ブロックを復号し、
結果のネームテーブル+新規タイルパターンを Word-RAM の 裏バンク へ書き、PCM 音声
リングを補充する。連続読み中は PRG-RAM へバースト書込みしない(payload は
CDC_TRNで DMA し、PRG は読むだけ=連続読み中も安全)。 - PRG-RAM(Sub CPU 側・512KB) — Sub CPU のワーク RAM。中心は RING(貯水池=タンク・ 420KB): 先読みした冷たい(新規)タイルパターンを溜めておき、CD 供給が一時的に細っても 枯渇しないための緩衝。ほかに CDC 着地+control 展開の apply(34KB)、フレーム→セクタの routing(8KB)、プログラムとスタック。
- Main CPU(MD) — Word-RAM の 表バンク を読み、新規タイルパターンを VRAM へ DMA(VBLANK 内のみ・予算制)、表示を flip、デバッグ HUD を描画、フレームハンドシェイク を主導する。
- Main-RAM(Main CPU 側・64KB) — Genesis 68000 のワーク RAM。中心は DMA_STAGE(24KB):
VDP の DMA 源は必ず Main-RAM なので、Word-RAM から読んだ 1 フレーム分のタイルをここへ整列
してから VRAM へ流す。ほかに
shadow[1120](セル→エントリ表・2.2KB)、ラン表、スタック。 - Word RAM(1M/1M・各バンク 128KB) — 共有ダブルバッファ。一方が「表」(Main が読む)、
他方が「裏」(Sub が書く)。
MEMMODEbit0 で所有権を swap。 - RF5C164 PCM — 自走の音声。Sub CPU がハード再生カーソルの少し先を保つように wave-RAM リングを補充する。
通信は Gate Array の 2 つのメールボックス: COMCMD0(Main → Sub)と
COMSTAT0(Sub → Main)、加えて診断用の COMSTAT1。
| 信号 | レジスタ | 意味 |
|---|---|---|
CMD_STREAM 0x50 |
COMCMD0 | Main: 先頭からストリーム開始 |
CMD_SWAP 0x51 |
COMCMD0 | Main: 表バンクの処理完了 — swap して |
STAT_READY 0x8003 |
COMSTAT0 | Sub: バンクswap済み、次フレームが表に乗った |
STAT_END 0x8004 |
COMSTAT0 | Sub: 映画終端(Main は 15 秒静止しループ) |
| desync / bad-magic | COMSTAT1 | Sub: 診断カウンタ |
sequenceDiagram
participant M as Main CPU (MD)<br/>Main-RAM 64KB
participant S as Sub CPU (SP)
participant P as PRG-RAM<br/>RING 420KB
participant W as Word RAM<br/>バンク 128KB
Note over M,W: 起動
M->>S: CMD_STREAM (0x50)
S->>P: prebuffer で RING を先読み充填
Note over P: RING 使用 ~380KB / 420KB (90%)
S->>S: ROM_READN(連続読み1回), frame 0 を裏バンクへ構築
S-->>M: STAT_READY (0x8003)
S->>W: swap → frame 0 を表へ
loop 各 movie frame N
par Main が frame N を消費
M->>W: 表を読む(ネームテーブル+新規パターン)
M->>M: タイルを DMA_STAGE へ整列, VRAMへDMA(VBLANK予算内)
Note over M: DMA_STAGE 使用 ≤24KB (peak 45%) / 平常 ~9KB(17%)
M->>M: CRAM入替? + flip(同一VBLANK内で原子的)
and Sub が frame N+1 を生成
S->>P: apply でセクタ着地 + RING からパターンpop
Note over P: apply 34KB / RING 380KB(90%)
S->>W: 裏バンクへ書込(セル+パターン)
Note over W: 裏バンク出力窓 平常 ~9KB(7%) / keyframe ~37KB(29%)
S->>S: 887 B PCM を給餌
end
M->>S: CMD_SWAP (0x51)
S->>W: バンクswap (MEMMODE bit0)
S-->>M: STAT_READY
Note over M,W: 表/裏の役割が反転, N+1 へ
end
S-->>M: STAT_END(映画終端) → 15秒静止 → 再び CMD_STREAM
3 つの独立した RAM がある。PRG-RAM(Sub 専用・512KB)、Word-RAM の 1M バンク (共有・128KB を 2 面)、Main-RAM(Main 専用・64KB)。各面の確保レイアウトと、 H40フルでの実使用(率 & KB):
| 領域 | 確保 | 用途 | H40フル使用 |
|---|---|---|---|
| program / BIOS 予約 | ~48 KB | SP プログラム+起動時 ISO_BUF | 固定 |
| RING(貯水池=タンク) | 420 KB | 冷たいタイルパターンの先読みリング | 380 KB(12160枚×32B)= リングの90% / PRGの74% |
| routing | 8 KB | フレーム→セクタ経路表 | ~7.8 KB(3998フレーム×2B) |
| apply | 34 KB | CDC 着地+control 線形化 | ≤34 KB |
| stack | 2 KB | SP スタック | — |
| PRG 合計 | 512 KB | ライブピーク ~472 KB ≈ 92% |
Sub が「裏」バンクへ 1 フレームの出力を書き、swap 後に Main が「表」として読む。同じ レイアウトの面が 2 つ。1 面あたり:
| 領域 | オフセット | 確保 | H40フル使用 |
|---|---|---|---|
| palette(PALW+CRAM) | +0x0000 | 130 B | 130 B(区間切替時のみ) |
| loads(冷パターン列) | +0x0084 | ≤37.9 KB | keyframe 全面 ~35 KB(1120枚)/ 平常 ~7 KB(~223枚) |
| updates(ネームテーブル差分) | +0x9802 | ≤21.7 KB | ≤2.2 KB(最大1120セル×2B) |
| diag / header | +0xAF00 | ~256 B | 固定 |
| SP scratch(CTRL/PAD) | +0x10000 | ~8 KB | ≤2.2 KB(control 線形化) |
| 1M バンク使用(出力窓) | ~44 KB 確保 | 平常 ~9 KB(7%)/ keyframe ~37 KB(29%)— 128KB中 |
| 領域 | オフセット | 確保 | H40フル使用 |
|---|---|---|---|
| program + shadow + vars | 0xFF0000 | 8 KB | shadow 1120×2=2.2 KB +プログラム |
| DMA_STAGE(タイル整列) | 0xFF2000 | 24 KB | ≤24 KB(~768枚, VDP DMA 源)/ 平常 ~9 KB |
| RUN_TABLE | 0xFF8000 | 8 KB | ~0.5 KB(~128ラン×4B) |
| DBG_STAGE + heap | 0xFFA000 | ~23 KB | 小(フォント色替え等) |
| stack | 0xFFFD00 | — | — |
| Main-RAM 使用(ワーキングセット) | 64 KB 全域に配置 | 平常 ~11 KB(17%)/ peak ~29 KB(45%)— 64KB中 |
pie showData title PRG-RAM 512KB 使用率
"使用 472KB (92%)" : 472
"空き 40KB (8%)" : 40
pie showData title Word-RAM 1Mバンク 128KB 使用率(keyframe)
"出力窓 使用 37KB (29%)" : 37
"空き 91KB (71%)" : 91
pie showData title Main-RAM 64KB 使用率(peak)
"使用 29KB (45%)" : 29
"空き 35KB (55%)" : 35
要点: **PRG-RAM は RING(先読み貯水池)でほぼ満杯(92%)**=ここが一番のメモリ消費源。 一方 1M バンクと Main-RAM は余裕がある(平常 1 桁 %、keyframe/peak でも 3〜5 割)。 これは意図通りで、CD 供給のジッタを PRG の大きなタンクで吸収し、フレーム毎に触る共有 バッファ(1M)と整列バッファ(Main-RAM)は小さく速く保っている。
flowchart LR
CD["CDドライブ<br/>連続 ROM_READN"] -->|セクタ CDC DMA| APPLY["PRG apply<br/>34KB"]
APPLY --> RING[("PRG RING 貯水池<br/>420KB / 使用 380KB=90%")]
RING -->|パターン pop| WB["Word-RAM 裏バンク<br/>128KB / 使用 9〜37KB"]
WB -->|CMD_SWAP| WF["Word-RAM 表バンク<br/>128KB"]
WF -->|タイル copy| STAGE["Main-RAM DMA_STAGE<br/>24KB / peak 使用 24KB"]
STAGE -->|VBLANK DMA| VRAM["VDP VRAM → 画面"]
RING -.887B/frame.-> PCM["RF5C164 PCM"]
| フェーズ | 主に使うメモリ | H40フル使用(率 / KB) |
|---|---|---|
| ① CD 吸上(SP) | PRG apply + RING 補充 | RING 380 / 420 KB(90%)、apply ≤34 KB |
| ② 復号・展開(SP) | Word-RAM 裏バンク(出力窓) | 平常 9 / 128 KB(7%) / keyframe 37 / 128 KB(29%) |
| ③ Main 読取+整列(MD) | Main-RAM DMA_STAGE | 平常 ~9 / 64 KB(14%) / peak 24 / 64 KB(38%) |
| ④ VBLANK DMA(MD) | DMA_STAGE → VRAM(排出) | 上の stage をそのまま VRAM へ流す |
| ⑤ CRAM+flip(MD) | Main-RAM shadow[1120] |
2.2 / 64 KB(3%) |
ソース本来のレート(例: 15 fps NTSC)では、1 movie frame は 60Hz フィールド 4 枚
= VBLANK 窓 4 回 ぶん。以下はすべてこの 1 期間の中で、2 CPU に並列で起きる。
時間は 左→右。右端の [mem] は各レーンがそのフェーズで触るメモリの H40フル使用量。
┌──────────────── movie frame 期間 (≈66.7 ms @15fps = 4フィールド) ────────────────┐
フィールド │ active走査 ░VB░│ active走査 ░VB░│ active走査 ░VB░│ active走査 ░VB░│
/VDP │ (ここでDMA不可) │ DMA窓 ~1.3ms │ DMA窓 │ CRAM+flip @頭 │
├────────────────────┼────────────────────┼────────────────────┼────────────────────┤
CD (SP) │══ ROM_READN: セクタが流れ込み続ける, ドライブは再シークしない ═══════════════════▶ [PRG apply 34KB]
│ (pump_poll が余裕+セクタ準備の時だけ CDC → PRGリング/applyリング へ吸上) [RING 380/420KB=90%]
├────────────────────┴────────────────────┴────────────────────┴────────────────────┤
Sub CPU (SP) │ frame N+1 の5セクタ吸上 → control復号 → WORD-RAM *裏* へ展開 │ [Word裏 出力窓]
│ ├ payload: 32B 新規タイルパターン → PRGリング (CDC_TRN DMA) │ [平常 9/128KB=7%]
│ ├ control: bitmap + ネームテーブルentry → 裏バンクのセル │ [key 37/128KB=29%]
│ └ 887B PCM → RF5C164 wave リング ······ そして: CMD_SWAP を待つ ······─────────▶│swap│READY
├─────────────────────────────────────────────────────────────────────────────────────┤
Word RAM │ 表 = frame N (Mainが読/DMA) ┃ 裏 = frame N+1 (Subが書) ────┈┈ swap ┈┈▶ 役割反転 [各128KB×2]
├─────────────────────────────────────────────────────────────────────────────────────┤
Main CPU(MD) │ 表を読む: DMA_STAGE へ整列 │ ▓STAGE→VRAM DMA▓ │ ▓DMA継続▓ │ CRAM+flip │→ CMD_SWAP [DMA_STAGE]
│ (Pass1: CPUコピー, active中も可) │ (Pass2: VBLANK内のみ, ≤予算/field) │ ▲原子的 [≤24/64KB=38%]
├─────────────────────────────────────────────────────────────────────────────────────┤
PCM(RF5C164) │▶▶▶ wave-RAMリングをハードが連続再生(映像と独立) ▶▶▶ │ [wave-RAM ~別領域]
│ Sub は書込ポインタをハード再生カーソルの SYNC_MIN..SYNC_MAX 先に保つ │
└─────────────────────────────────────────────────────────────────────────────────────┘
2 CPU が出会うのは破線の所だけ: CMD_SWAP / STAT_READY バリア
要点: バリアはフレームに 1 回で、VBLANK ごとではない。 バリアの間は 2 CPU が 逆バンクで完全に独立して走る。Main の DMA は VBLANK に収まるよう刻まれ、Sub の復号+ CD 吸上が残りの実時間を埋める。
process_frame: 1回の呼び出し = 1フレームを裏バンクへ復号
├─ pf_pump : このフレームの5セクタが届くまで pump1 で CDC を吸上 (→ apply 34KB)
├─ seq check : control の frame_seq が期待index と一致するか
│ 不一致 → desync: そのフレームを破棄, 前コマ維持(0更新), 回数を計上
├─ fetch_control : 可変長 control ブロックを PRG→Word-RAM scratch へコピー
└─ expand_frame : bitmap を走査 → 立ったセル毎にネームテーブルentry(base+slot)を書く
coldセルはリング(貯水池420KB)から32Bパターンをそのスロットへpop。
その後887B PCM給餌
stream_loop:
process_frame → CMD_SWAP を待つ間 (pump_poll)*
→ bchg #0,MEMMODE+1 (バンクswap) → swap_settle → STAT_READY
pump_pollが安全弁。 ループの至る所で—Main を待つ間でも—呼ばれ、CDC セクタが 滞留して落ちないようにする。PRG リングと apply リングに空きがあり かつ 実際にセクタが 準備できている(CDC_STAT)時だけ吸うので、バッファを溢れさせない。- RING(貯水池)がジッタ吸収の要。 H40フルでは 12160 枚(380KB)まで先読みを溜め、 CD 供給が一時的に足りないフレームでも枯渇せずパターンを供給する(それでも供給が続かなければ starved フレームになる)。
- 連続読み中、Sub CPU は PRG-RAM へバースト書込みしない(固まる)。payload は
CDC_TRNDMA で PRG に届き、Sub はパターン pop 時に PRG を 読む だけ。出力は Word RAM(書込安全)。
build_frame:
├─ Pass1 (active中も可): 表バンクを読み、タイルDMAのラン表を作り、必要分を DMA_STAGE(24KB) へ整列
│ (遅いCPU作業をVBLANK外でやる。VDP DMAの源は必ずMain-RAM)
├─ Pass2 (VBLANK内のみ): DMA_STAGE の新規タイルパターンを Main-RAM → VRAM へ DMA。
│ d7 = md_vbudget 語の予算/フィールド (H32=2800, H40=3400, dmabench実測)。
│ 1フィールドの予算を超えるフレームは分割し次VBLANKへ継続=DMAはactiveに入らない。
└─ bf_flip (新しいVBLANKの頭で, その1フィールド内に原子的):
render_dbg(HUDを裏へ) → 区間が変われば CRAM入替 → do_flip(VDPが新フレーム表示)。
shadow[1120](セル→エントリ表)は Main-RAM 常駐で差分適用に使う。
play_loop:
build_frame → swap_or_end: CMD_SWAP送出, STAT_READY待ち (または STAT_END)
- タイル転送は DMA(VDP が自走し CPU を空ける)だが、VBLANK 内のみ・モード別語予算まで。 これが最重要のタイミング規則: VBLANK 超過が画面を壊す元凶。
- DMA_STAGE(24KB)が Main-RAM 最大の消費。VDP の DMA 源は必ず Main-RAM なので、 Word-RAM から読んだ 1 フレーム分のタイル(H40フルで最大 ~768 枚)をここへ整列してから流す。
- CRAM(パレット)入替と flip は 同一 VBLANK で行う。 別々だと一瞬「旧フレーム×新 パレット」が見える。
frame N frame N+1 frame N+2
┌─────────┐ ┌─────────┐ ┌─────────┐
BankA │ Main 読 │ ─swap→ │ Sub 書 │ ─swap→ │ Main 読 │ … 各面 128KB
BankB │ Sub 書 │ │ Main 読 │ │ Sub 書 │ 出力窓 9〜37KB使用
└─────────┘ └─────────┘ └─────────┘
MEMMODE bit0 が CMD_SWAP 毎にトグル; Sub が書き終えたバンクが次フレームで Main の読みバンクに。
RF5C164 は自分のクロックで wave-RAM リングを再生する。Sub CPU はフレーム毎に 887 バイト
(13.3 kHz mono 8bit PCM)をそのリングへ書き、書込ポインタをハード再生カーソルの少し先に
置き、映像ジッタでズレたら SYNC_MIN … SYNC_MAX の窓へ戻す。だから音声タイミングは PCM
チップの再生カーソルに紐づき、映像のフレームバリアには 依存しない — 遅い映像フレームが
あっても音声はクリックしない。
- バリアはフレームに1回。
CMD_SWAP → STAT_READYが唯一のランデブー。間は 2 CPU が 逆バンクで自分のペースで走る。 - 連続CD, 機会的に吸上。 読みは再開しない;
pump_pollが全ての待ちで CDC を吸うので セクタを落とさない。CD 供給のジッタは PRG の RING(貯水池 380/420KB) が吸収する。 - DMAはVBLANKに封じ, モード別に予算化。 大きいフレームは連続VBLANKへ分割、activeへ 入らない。DMA 源は Main-RAM の DMA_STAGE(≤24KB) に整列済み。
- パレット+flipは同一VBLANK内で原子的。
- 音声はPCM再生カーソルに乗る, 映像バリアと独立。
| 症状 | 原因 | プレイヤーのガード |
|---|---|---|
| フレームが裂ける/化ける | DMAがVBLANKを超えactiveへ | モード別語予算, フィールド跨ぎ分割 |
| 画面全体が崩壊 | CDCセクタ落ち→ストリームdesync | frame_seq チェック→フレーム破棄, 前コマ維持, 計上 |
| runaway read/クラッシュ | コンパクトブロックのcount破損 | 残数へクランプ, 0を終端扱い |
| 音声クリック/途切れ | wave リング underrun / lead 誤り | 書込ptrを再生カーソルの SYNC_MIN…SYNC_MAX に保つ |
| 一瞬フリーズ/コマ落ち | CD供給が続かず RING 枯渇 | RING(貯水池420KB)で吸収; それでも尽きれば starved フレーム |
| 新規タイルに暗い破線 | Word-RAM源DMAが先頭ワードを落とす | dst[0]をCPU書き, 残りを src+2 → dst+2 でDMA |
出典: boot/movieplay_sp.s(Sub)と boot/movieplay_ip.s(Main)。メモリ数値は H40フル
(out/movieplay/MOVIE.DAT = 40×28 / 1120セル / pool 1400 / ring_peak 12160枚 = 380KB)の
実測ビルドに基づく。モードは H32(256×224)と H40(320×224)が同じパイプラインを共有し、
モード別値は VBLANK の DMA 語予算だけ。フレームレートはソース本来のレートなので、1 movie
frame あたりの VBLANK 窓数 = 60 / fps。