NES on FPGA 2026/05/08

dev MMC

 もちろん実カートリッジでの動作を目標にしているわけですが、 毎回カートリッジを差し替えて動作確認するのは物理的にも精神的にも大変です。 そこで簡単な動作確認にはカートリッジから吸い出したROMデータを使用します。 吸い出したデータの配布は権利違反に問われるため気を付けてください。

_
▼ さまざまなカートリッジマッピング

 カートリッジの内部回路は、開発元や開発時期によって数十タイプ存在し、 それぞれのMMC(Multi Memory Controller)チップによって実現されています。 カートリッジから吸い出したROMを動作させるためには、 そのROMのカートリッジに搭載されているMMCをマッパーとして実装する必要があります。

 実装済みのマッパー

・Mapper0 (NROM 32KBPROM/8KVROM)  スーパーマリオブラザーズ、レッキングクルー、スパルタンX、など
・Mapper1 (Nintendo MMC1)   DQ3、DQ4、FF1、FF2、ロックマン2、など
・Mapper2 (UxROM/PROMswitch) DQ2、ロックマン、たけしの挑戦状、など
・Mapper3 (CNROM/VROMswitch) DQ1、グラディウス、アルカノイド、など
・Mapper4 (Nintendo MMC3)   スーパーマリオブラザーズ3、FF3、ロックマン3456、など
・Mapper5 (Nintendo MMC5)   ジャストブリード
・Mapper10 (Nintendo MMC4)  ファイアーエムブレム、ファミコンウォーズ、など
・Mapper16 (Bandai FCG) ナイトガンダム物語3、ドラゴンボールZ、など
・Mapper19 (Namcot 129/163)  えりかとさとるの夢冒険、ワギャンランド3、貝獣物語、など
・Mapper23 (Konami VRC2 type B) クライシスフォース、魂斗羅、ワイワイワールド、など
・Mapper25 (Konami VRC4)  グラディウス2、レーサーミニ四駆、など
・Mapper69 (Sunsoft FME-7)  ギミック!、へべれけ、など
・Mapper73 (Konami VRC3)  沙羅曼蛇
・Mapper80 (Tatio X1-005) ミネルバトンサーガ、未来神話ジャーヴァス、など
・Mapper118 (TxSROM IGS MMC3) アルマジロ、イース3

_
▼ タイミングの再現って難しい 2013/2/5


画面切り替えが異常

正常な画面切り替え

 「アルマジロ」はカスタムMMC3チップを使用している。 オリジナルのMMC3と違うのは、CHR-ROMバンク指定時にVRAMのミラーリングも指定されるというところ。 実装してみたところ、右の動画のように、画面切り替え時に本来は黒い画面からステージ画面に切り替わるはずが、 他のパターンが表示されているような不具合が発生した。

 最初、ミラーリングの実装が間違ってるのかと思い、いろいろ試してみたものの状況は変わらず。 エミュでも再現できないので、ふと、IRQについて思い出した。 で、試しにIRQを発生させないようにすると、黒い画面からステージ画面への切り替えができた (ただしIRQが入っていないので下の点数画面が表示されない)。

 MMC3のIRQについては、描画ラインでカウンタをデクリメントし、ゼロになるとIRQが発生する。 この描画ラインの判定方法が悪く、IRQの発生タイミングが悪かったのが原因だった。

 ドキュメントを探してみると、CHR_A12でいろいろと判定しているように書かれてあるが、 今回は「if(chr_read & (CHR_A13==0b0)) f_draw := 1;」にて描画ラインと判定することとした。 つまりCHR_ROMへのReadアクセス時にフラグを立て、 HBlank時にフラグが立っていたら描画ラインとしてIRQカウンタをデクリメントする。 これを実装して、ようやく正常にステージ画面への切り替えができるようになった。

 ただしこれだと1スキャンライン分、IRQが遅れるんだよね…。

_
▼ MMC3のIRQ生成回路を改善しよう 2026/4/13

 これまでのMMC実装で、特にIRQカウンタについてはムリヤリhsync信号をMMCモジュールに入れてカウントしていた。 しかし実機ではhsync信号なんて無いので、実機同様にM2クロックやCHRアドレスバス遷移でカウントするように修正する。 そのためにPPUもリファインしたことだし。

 MMC3(スーパーマリオ3、星のカービィ、など)はchr_A12の立ち上がりを描画ラインとして検出しIRQカウンタを動かす。 ほぼ描画終了タイミングだね。 ポイントはVRAMアクセス時にもchr_Aは遷移するという事と、 1ライン中一回だけIRQカウンタが動くようにする必要があるということ。

基本的に(PPU:Ctrl1 bit4=0なら)BGデータ取得時はchr_A12=0のため長いLow期間が続く。 このため(PPU:Ctrl1 bit3=1なら)最初のスプライトパターン取得タイミングでchr_A12=1となる。 このchr_A12の立ち上がりによってラインとして検出する。


IRQタイミング(ステータス表示への切り替えライン)が期待通りに
 注意点としては、スプライトデータリード期間には未使用のネームテーブルアクセスも発生しchr_A12=0が見られる。 つまり短いchr_A12=0があってもラインカウントしないようにマスクする必要があるということ。 今回は一定期間chr_A12=0であった場合にchr_A12=1を検出するようにしたが、 この“一定期間”のカウントにはchr_readとM2クロックのどちらを使っているかはまだ分からず。

_
▼ MMC5のパターン生成機能はいろいろと 2026/5/8


ジャストブリード(大きい)

 ジャストブリードもプレイしたことがあるし、使われているMMC5チップは高性能と言う触れ込みなので再現したい。 MMC3に加えてMMC5も再現できれば、CHRアドレスバスについての再現性をより高めることができそう。

 MMC5についてもIRQ生成回路はCHRアドレスバスを参照しており、 chr_A13=1、つまりネームテーブルかどうかを見ている。 ネームテーブルの連続3回リードが発生するので、これを描画ラインとして検出しIRQカウンタを動作させる。 また、ラインが検出されたらインフレームフラグ($5204:bit6)を立て、$5204リード時に返したり、 拡張RAMアクセス制御に使用する。 今回はCPUクロックで120クロックほどラインが検出されなければVBlank中と判断しインフレームフラグを下ろしている。

 描画のためのCHRデータ取得制御についても工夫が必要。 他のオープンソースエミュを見ると、PPUにMMC5のためのロジックが書かれていたりしてもにょるので、 今回の実装ではPPUの機能追加ではなくMMC5として実装を進める。 機能的にはグラフィックモードや分割モードの指定をすることでカートリッジ内の拡張RAMを使用し、 BGのパレット範囲が16x16ドットから8x8ドットにできたり、 パターンテーブルを高速に切り替えたりできるらしい。


BGタイルデータ取得ロジック周りの修正
 ジャストブリードでは、ネームテーブルリード時に拡張RAMからリードし (MMC5内部で保持し、ネームテーブルデータは使用せず)、 属性テーブルリード時はその拡張RAMからのデータをタイルインデックスと属性データとして使用。 そのタイルインデックスのパターンを取得する。 この辺りのロジックが正しくないと図のようにBGがバグる。 パターン取得についてはBGとスプライトでのchr_Aでの区別がつかないため、 今回はパターンリードカウンタを使用し、 前半の 34 x 2 = 68 リードについてはBGパターンを返し、 後半の 8 x 2 = 16 リードについてはスプライトパターンを返すようにした。


画像のバグりとハング
 また、FPGAで検証中にBGパターンが異常になる現象があり、エミュでは再現できず。 描画が異常なだけでなくゲームがハングするため、どうやらネームテーブルとして使用している拡張RAMは、 拡張WRAMとしてCPUからのアクセスも発生しているようだった。 FPGA内での同時アクセスをソフトエミュで再現するのは難しいからね…。 とりあえずDualPortメモリとしてPPUとCPUの両方からのアクセスでも正常な描画になりゲームもハングしなくなったが、 厳密にはMMC5外のメモリチップらしいのでSinglePortメモリにしてアクセス調停すべきかも。


ジャストブリードはプレイできるようになった!
 そんなこんなで、IRQタイミングやらCHRデータ取得ロジックやら拡張RAMやらについて、 それぞれの不具合が絡み合い修正に手こずってしまったけど、 ジャストブリードについてはプレイできるようになった。 他のMMC5使用ソフトとしてメタルスレイダーグローリーなどがあるけど、 今は置いておこう。


Copyright(C) pgate1 All Rights Reserved.