NES on FPGA | 2022/09/22 Tweet |
Youtube : NES on FPGA feat. 1chipMSX
私は1chipMSXの発売を待てず、 その数ヵ月前に発売されたほぼ同価格のTerasic社DE1を購入したため、 1chipMSXは見送ってしまった。 DE1にはファミコンを実装したけど、FPGAボードとしての1chipMSXはずっと気になっていた。 そしてしばらくして…。
数日後、1chipMSXが届く。 また、別途購入していた後期版DE1も到着し、2006年の再演を感じた。 これでようやく、1chipMSXにファミコンを実装する、という心の隅に置いておいたタスクを実行できそう! ムネがドキドキする。
1chipMSXと、DE1 FPGAボード(後期版)、どちらも2006年頃に発売され、本日同時にお迎えできたこと、どこか運命的ですね。MSXソフト持ってないので、1chipMSXにファミコンを実装していきたいと思います pic.twitter.com/1kiRqrU4C4
— !!かんな丸 (@pgate1) July 18, 2022
マニュアルやサンプル回路はあったので問題なし。
動作確認は、背面のDIPスイッチでSDカードとVGA出力を有効化して電源投入。 1chipMSXの青い画面が出る、PS/2キーボードを接続して簡単なBASICを書いてみて動作確認。
できたできた pic.twitter.com/xyolpOyICJ
— !!かんな丸 (@pgate1) July 19, 2022
ダウンロードケーブルとして、Intel社のUSB-Blasterは4万円以上と高価なのと、 やたら安い互換品は不具合があると面倒そうだったので、 パートナー互換品であるTerasic社 USB Blaster をDigikeyから9,600円で購入。 6,000円以上なので送料無料、10,000円未満なので消費税もかからなかった。 注文時に用途欄にはhobbyとだけ記入し、翌々日には到着。 ちなみに、Mouserだと消費税がかかり、Terasicストアは品切れ状態で納期がかかりそうだった。
Terasic USB Blaster到着!発注から2日!はや!
— !!かんな丸 (@pgate1) July 20, 2022
これで1chipMSXを書き換えられる、たぶん
今まで使ってたDEシリーズFPGAボードはUSB Blaster機能がボードに載ってたから必要なかったんだよ pic.twitter.com/QsTk6RAaHB
ダウンロードケーブルの接続のためには、1chipMSXのかっこいいクリアブルーのケースを開ける必要があり、 ちょっともったいない。 基板の右側にJTAGピン、ピン番号を間違わないように Terasic USB Blasterのサイトを参考にした。
とりあえずLチカするデザインをコンパイルするために、 初代Cycloneをサポートしている最終バージョンのQuartusII 11.0sp1 WebEditionを持っていたのでインストールする。 こんなこともあろうかとインストーラを保管しておいたのだよ。私えらい。
Lチカの回路を記述してコンパイル。 プロジェクト名に悩む、1chipMSX…OneChipMSX…OCM(ここで初めてOCMの意味を理解する)。 付属のソースコードはVHDLだけど、VHDLは複数行コメントアウトができず、 VerilogHDLのスケルトン を公開されている方がいたのでありがたく使わせていただく。
.pofができたのでいよいよ書き込み。 ところが、USB Blasterを認識しない。他のFPGAボードなんかも認識しなくなった。 おそらく古いバージョンのQuartusIIをインストールしたせいでドライバ関連が混乱したためと考えられる。 新しいバージョンのProgrammerを再インストールしたらUSB Blasterも認識された。
PROMの書き換えには、.pofを選択し、ASモードで書き込む。 10秒ほどでPROMを上書きし、無事Lチカできた。 ちょっとびっくりしたのが、8個のLEDの並びが[0:7]となっていること。[7:0]ではないんだね。 [7:0]にすべく
.LED({pLed[0],pLed[1],pLed[2],pLed[3],pLed[4],pLed[5],pLed[6],pLed[7]})
としてしまった。
USB Blaster接続とLチカのようす(滑り止めにMentorGraphicsのシート)
ちなみに、付属のemsx_top.pofをPROMに書けば、元の1chipMSXに戻ることも確認。1chipMSXでLチカできた。やっぱりFPGAのJTAG系ピンは接続されてないので、JTAGモードはだめ。PROMに書く形で使う。オリジナルのPROMを書けば元の1chipMSXにも戻せた。ここまでくれば一安心…かな pic.twitter.com/y1ZwEfRFPG
— !!かんな丸 (@pgate1) July 22, 2022
サウンドはパラレル6bitだけど、16bitを1bitΔΣを通して、最上位ビットから出力。 一応ステレオ出力できるみたいだけど、とりあえずモノラル。 ダイソーの300円ミニスピーカーを変換プラグを介して接続。 スピーカーの電源は1chipMSXのUSB端子からとれるかな? 電力次第かな。
SDRAMコントローラも流用してビット幅を修正した程度で、 SDRAMテストもパス。
ファミコンのROMを読み込ませるためにはどうするか。 1chipMSXの2カートリッジスロットに、ファミコンカートリッジを接続する“げた”のような拡張基板を作るのも妄想されたけど、 ここはシンプルに別途吸い出したROMをSDカードから読み込ませる。
SDカードコントローラもテスト、ただ2回に一回くらいの頻度で読み込みに失敗する現象あり。 試行錯誤して、SDカードの電源投入時間確保のためにリセット時間を250ms以上としたことに加え、 そもそも回路図を見るとSD_D0がプルアップされておらず、 1回目のCMD0でエラーを返していることに対処するためにCMD0を2回実行することでSDカードも安定した。 CMD0の2度打ちについては、Dummy 74clock以上 → CMD0 → Dummy 74clock以上 → CMD0 というようにした方が安定するSDカードもあるらしい。
ちなみに基本はSPIモードで使用するけど、配線はされているようで一応SDモードでも動作した。やっぱりこれが原因っぽい
— !!かんな丸 (@pgate1) September 13, 2022
1回目のCMD0をエラー処理しないようにしたら通った https://t.co/gnC3cjeezZ
操作系はPS/2キーボードかMSXのジョイパッド、 PS/2キーボードは1chipMSXコアでは相性のせいか動作しなかったものも動作させた。 ジョイパッドは別途入手し動作確認OK。 ジョイパッドにはスタートボタンセレクトボタンが無いので、それはキーボードで代用する。
やはりキーボードではプレイに無理があると思われたのでジョイパッドを入手したよ。想像してたよりもちっこい pic.twitter.com/1g4zotL79w
— !!かんな丸 (@pgate1) August 19, 2022
これでFPGAボードとして使用する準備が整った。
このまま完成動画を撮ってもよかったんだけど、動かしているといくつか問題点が気になる。
組み込み屋はプログラムカウンタをLEDに出力して光り方でプログラムがちゃんと走ってるか確認する。これは何のファミコンソフトでしょうか?? pic.twitter.com/McVwvfhCq3
— !!かんな丸 (@pgate1) August 8, 2022
一番の課題はCHR-ROMが足りないこと。 スーパーマリオ程度もしくはCHR-RAMを使用するソフトなら8kBのCHR-ROMを内蔵RAMで実装すればいいけど、 それ以上のサイズのCHR-ROMのものを動作させるには廉価版FPGAの内蔵RAM程度では足りない。
Mapper cart;
sdram_ctrl sdram; // PRG-ROMとして使用
instruct cart.prg_rom_read par{
sdram.read(0b0000||cart.prg_rom_adrs);
}
cart.prg_rom_rdata = sdram.rdata<7:0>;
instruct cart.prg_rom_write par{
sdram.write(0b0000||cart.prg_rom_adrs, 0x00||cart.prg_rom_wdata);
}
ram_8x8k chr_ram; // 8kB程度しか確保できない
instruct cart.chr_ram_read par{
chr_ram.read(cart.chr_ram_adrs<12:0>);
}
cart.chr_ram_rdata = chr_ram.dout;
instruct cart.chr_ram_write par{
chr_ram.write(cart.chr_ram_adrs<12:0>, cart.chr_ram_wdata);
}
そこで、PRG-ROMとして使用しているSDRAMをCHR-ROMとしても共有する。
共有はアービトレーション回路を組めばいいけど、
ポイントはCPUやPPUの動作に間に合うかということ。
基本的にNES on FPGAは実カートリッジをサポートするために、
可能な限りファミコン実機同様のタイミングで各モジュールを動作させるようにしている。
つまり、CPUやPPUがメモリアクセス要求を出して、既定サイクル数以内でそれを完了させる必要がある。
これまでは、例えばメモリリード時は、その規定サイクル数をカウントして、メモリからの
出力をレジスタにセットしていた。
しかし、SDRAMを共有する場合、CPUからのアクセスとPPUからのアクセスと、
SDRAMのリフレッシュ動作が入り混じるため、どのタイミングでメモリアクセスが終了するか確定しない。
そこで、アクセス要求をReq信号、アクセス終了をAck信号で管理することで、
メモリアクセスのアービトレーションを行いやすくする。
アービトレーションは、
SDRAMリフレッシュ > PRG-ROMアクセス > CHR-ROMアクセス
の順に優先度をつけることで、
同時にリクエストが発生した場合のロジックが少し簡単になる。
またこれに伴い、グラフィック描画ユニットであるPPUのレジスタ反映タイミングを少し修正。
ここだけはファミコンの回路を少し思い出しながらの作業となった。
50MHzでのメモリアクセスを考えると、 CHR-ROM ⇒ CHR-ROMアクセス間隔は 18クロック なので、 最速のメモリアクセスは 18クロック - CHR-ROMアクセス5クロック = 13クロック の猶予がある。 最遅のメモリアクセスパターンは、SDRAMリフレッシュ3クロック + PRG-ROMアクセス5クロック = 8クロック。 なので、間に合わない場合はSDRAMを100MHzで動作させることも想定していたけど、 50MHzで間に合いそうだ。
今回はざっくり計算して一応動いてはいるけど、 本来ならコーナーケースも考えて、全パターンのシミュレーションした方が安心。
メモリアービトレーション回路の動作をDE0で確認し、1chipMSXに持っていくことで、 CHR-ROMが128kBあるようなグラディウスIIや星のカービィやスーパーマリオ3が問題なく動作した。 副次的に、DE0でもこれまでサポートできていなかったそれらのROMも動かせるようになった。DE0でファミコン動かすだけなのになんでこんなに苦労してんだあたしゃ
— !!かんな丸 (@pgate1) August 22, 2022
Mapper cart;
sdram_ctrl sdram;
reg_wr prg_read_wait, prg_read_ack_wait;
reg_wr chr_read_wait, chr_read_ack_wait;
reg_wr chr_write_wait, chr_write_ack_wait;
// PRG-ROM readは、CHR-ROMアクセス発行と同時だったらこちら優先だが、
// すでにCHR-ROMアクセス時なら終了待ち、ack必要。
if( (cart.prg_rom_read | prg_read_wait) &
^chr_read_ack_wait & ^chr_write_ack_wait & sdram.ack){
sdram.read(0b0000||cart.prg_rom_adrs);
prg_read_wait := 0b0;
prg_read_ack_wait := 0b1;
}
else if(cart.prg_rom_read) prg_read_wait := 0b1;
if(prg_read_ack_wait & sdram.ack){
prg_read_ack_wait := 0b0;
cart.prg_rom_ack();
}
cart.prg_rom_rdata = sdram.rdata<7:0>;
// PRG-ROM writeは、ROM読み込み時のみなので排他処理不要。
if(cart.prg_rom_write){
sdram.write(0b0000||cart.prg_rom_adrs, 0x00||cart.prg_rom_wdata);
}
// CHR-ROM readは、PRG-ROM readアクセス終了待ち、ack必要。
if( (cart.chr_ram_read | chr_read_wait) &
^cart.prg_rom_read & ^prg_read_ack_wait & sdram.ack){
sdram.read(0b000100||cart.chr_ram_adrs);
chr_read_wait := 0b0;
chr_read_ack_wait := 0b1;
}
else if(cart.chr_ram_read) chr_read_wait := 0b1;
if(chr_read_ack_wait & sdram.ack){
chr_read_ack_wait := 0b0;
cart.chr_ram_ack();
}
cart.chr_ram_rdata = sdram.rdata<7:0>;
// CHR-RAM writeは、PRG-ROM readアクセス終了待ち、ack不要。
if( (cart.chr_ram_write | chr_write_wait) &
^cart.prg_rom_read & ^prg_read_ack_wait & sdram.ack){
sdram.write(0b000100||cart.chr_ram_adrs, 0x00||cart.chr_ram_wdata);
chr_write_wait := 0b0;
chr_write_ack_wait := 0b1;
}
else if(cart.chr_ram_write) chr_write_wait := 0b1;
if(chr_write_ack_wait & sdram.ack){
chr_write_ack_wait := 0b0;
}
NES on FPGA:PRG-ROMとCHR-ROMを1つのSDRAM内で共有することで、DE0ボードCycloneIIIの内蔵メモリだけでは賄えないサイズのROMを動かせました!星のカービィとかマリオ3とか。これでようやく1chipMSXにも移植できそう。 pic.twitter.com/W8SV6SQoiI
— !!かんな丸 (@pgate1) September 10, 2022
ちなみに1chipMSXと同時期に発売されて私が購入したDE1は、SDRAMに加えてSRAMも搭載している。 ファミコン実装の際は、SDRAMをPRG-ROMとして、SRAMをCHR-ROMとして使っていたので、 上述したような工夫は必要なかったってわけ。
DE0にはオーディオジャックが付いていないので、これらの調整は1chipMSX上で行った。
ROMを切り替えた時に余計なサウンドが鳴る。 例えばファイナルファンタジー3の動作時にすべてのサウンドチャンネルが有効になる瞬間があり、 不要な音が鳴ってしまう。 これはROM読み込み時にサウンド生成ユニットであるAPUの各チャンネルレジスタのリセットを強化して回避。
拡張音源が鳴りっぱなしになる。 これは、拡張音源を使用したNSF再生後に、通常のROMに切り替えると拡張音源の出力値が残ってしまうので、 拡張音源フラグをミュートフラグとしても使用することで回避。
スピーカーの低音が弱いので、三角波が聞こえづらい。 コスパが良いダイソーの300円スピーカーを使ってみたけど、低音が少し弱かった。 そこでNES on FPGAにボリュームブースト信号回路を追加し、 このスピーカーを使う場合は、三角波のボリュームを加算することで聞こえるようにした。
当然ながら拡張音源モリモリ森鴎外。 ちなみにVRC7はvm2413のVoiceROMをVRC7のものに変更したものを使用しているので、 実質MSX上でYM2413が動作しているような感じになっています(?)。 いとをかし。NSFプレイヤーにピアノロール的波形エフェクト画面を追加してみました。ちなみに3曲目のV.G.NEOは拡張音源のVRC7を使っている曲で、VRC7の本体としてvm2413を使わせて頂いています。 pic.twitter.com/kAPpSmT6zw
— !!かんな丸 (@pgate1) September 22, 2022
NES on FPGA feat. 1chip MSX Cyclone EP1C12Q240C8 約12kLEのうち使用率 88% 内蔵RAM約30kBのうち使用率 54% NES core : 3313 LEs 拡張音源 : 4826 LEs Mapper 0,1,2,3,4,25,73 : 966 LEs
FPGAデータはこちら Github: NES on FPGA
ご利用にあたっては自己責任でお願いいたします。
2006年ぶりにファミコンを1chipMSXに実装するという宿題を終わらせることができたけど、 はたして、私が当時1chipMSXを購入してファミコンを実装できていただろうかね? 初心者にやさしいDE1というボードで実装し経験を積み、 当時1chipMSXを購入した人たちが使い倒して情報公開しているからこそ今回のチャレンジができたように思います。
そんな形で1chipMSXをFPGAボードとして使ってみて、 1chipMSXの発売当初に提唱されていたOSXって、今、実現できてるのかな?と考えたりして。 1chipMSXに限らず、入手性の良い高性能なFPGAボード、バッドノウハウの少ない開発環境、 作ったものの共有とそれを通じた同志たちの繋がり。 そのような中で成果を出しているプロジェクトはいくつもあって、 それらのプロジェクトが相互に協力し合っている場面もみられます。 なんとなくプラットフォームとしてもうまくいっているんじゃないかな。 プロジェクトはtime goes by、でも私はゆるりとやるばい。 その過程でなにか貢献出来たらなーという感じです。FPGAエンジョイ勢なので。
2022年、AlteraもXilinxも買収され、少しFPGA界隈のすそ野の広がりが落ち着いてきた感じはあるものの(そうでもない?)、 コンピュータアーキテクチャの初学的な位置づけとしてファミコン実装はどうでしょうか、 と無理やりお勧めする流れを作ってみる。 ただ最近、部材不足と円安でFPGAボードの値上がりがつらいですよね…、 学生のうちに研究室で買ってもらうか、アカデミック価格で購入するのが良さそうです。
おまけ
よーし、次はMSX3にプレステ実装しちゃうぞー。1chipMSXでもポリゴン回路動いた。DSPが内蔵されてないから乗算回路のパスが長くて、動作周波数は25MHz。緑色が白飛びしてるのはデジカメのせい pic.twitter.com/dyy9LNGskc
— !!かんな丸 (@pgate1) September 25, 2022