NES on FPGA | 2024/08/09 Post |
目標は、基本的な『スーパーマリオブラザーズ』から、 ROMサイズの大きい『星のカービィ』『沙羅曼蛇』も動かしたい。
そしてNESの次は…!
今回はWindows 10 22H2 にて、Gowin EDA FPGA Designer ver. 1.9.9 (64-bit) を使用。
価格 | 約2,000円 |
デバイス | LittleBee GW1NSR-LV4CQN48PC6/I5 (55nm LP embedded flash) |
リソース | 4,608 LUTs、BlockSRAM 約22kByte |
メモリ | HyperRAM 8MByte |
参考 | Sipeed wiki Tang Nano 4K |
NES on FPGA feat. TangNano4K
— かんな丸!! (@pgate1) 2024年1月4日
使用率93%でMapper 0のみ実装して、マリオやレッキングクルーをUSB-JTAG経由で送信して動かせました。4KでNESは誰もやってなかったので移植してみたけど、LED1個でデバグつら。 pic.twitter.com/2aKNpUjpHM
circuit ram_8x8k
{
input adrs<13>, din<8>;
instrin write(adrs, din);
instrin read(adrs);
output dout<8>;
mem ram[8192]<8>;
reg dout_reg<8>; // 出力レジスタ使用
if(write) ram[adrs] := din;
if(read) dout_reg := ram[adrs];
dout = dout_reg;
}
最初、HyperRAMが動かなくて困っていたけど、 サンプル通りのポート宣言に修正したらポートが正常に割り当てられて動いた。
// OK こちらで動作した
output wire [0:0] O_hpram_ck,
output wire [0:0] O_hpram_ck_n,
output wire [0:0] O_hpram_cs_n,
output wire [0:0] O_hpram_reset_n,
inout wire [7:0] IO_hpram_dq,
inout wire [0:0] IO_hpram_rwds
/*
// NG ピンが正常に割り当てられない
output wire O_hpram_ck,
output wire O_hpram_ck_n,
output wire O_hpram_cs_n,
output wire O_hpram_reset_n,
inout wire [7:0] IO_hpram_dq,
inout wire IO_hpram_rwds
*/
参考: Tang-Nano-4Kとパソコンとの、JTAG経由の直接通信
UARTにはJTAGピンをユーザピンとして使用している。 このため、FPGAデータを書き込むと Gowin Programmer からは見えなくなるが、 電源を入れなおせば再度FPGA内にデフォルトデータが書き込まれるので問題ない。 ただしデバグの度にUSB電源とHDMIケーブルを抜き差しするのが非常に面倒だった。注意点として、FPGAデータは SRAM Program を使用すること。 Flash Program で書き込んでしまうと電源を入れなおしてもそれがFPGAに書き込まれてしまうため Gowin Programmer から認識されなくなってしまう。 対処法としては、JTAGSEL(ピン8)をGNDに接続した状態で、JTAGピンをユーザIOとして使用していないデザインを書き込むといいらしい。
PCからはFTDIドライバを使用してFT_Writeでデータを送信する。 データ送信が速いとHyperRAMへのWiteが間に合わなかったので、1バイトずつFT_Writeしている。 最大65535Byteまで送信できるので、マリオの約40kByteは問題なし。
// ポートオープン
FT_HANDLE ftHandle;
FT_OpenEx((PVOID)"USB Debugger A", FT_OPEN_BY_DESCRIPTION, &ftHandle);
int send_size = 16 + 32768 + 8192 + 2 + 3;
// Sendサイズセット
uint8 init[3];
init[0] = 0x31; // Writeコマンド
init[1] = (uint8)send_size;
init[2] = (uint8)(send_size >> 8);
uint32 write_size;
FT_Write(ftHandle, init, 3, &write_size); // Writeサイズセット(初期化)
// ROMのロード
FILE *fp = fopen("mario.nes", "rb");
uint8 *rom = new uint8[16+32768+8192];
fread(rom, 1, 16+32768+8192, fp);
fclose(fp);
uint8 begin[1];
begin[0] = 0x00; // Mapper 0
FT_Write(ftHandle, begin, 1, &write_size);
FT_Write(ftHandle, begin, 1, &write_size);
// ヘッダー send
for(i=0; i<16; i++){
FT_Write(ftHandle, rom+i, 1, &write_size);
}
// PRG-ROM send
for(i=16; i<16+32768; i++){
FT_Write(ftHandle, rom+i, 1, &write_size);
}
// CHR-ROM send
for(i=16+32768; i<16+32768+8192; i++){
FT_Write(ftHandle, rom+i, 1, &write_size);
}
uint8 end[1];
FT_Write(ftHandle, end, 1, &write_size);
FT_Write(ftHandle, end, 1, &write_size);
FT_Write(ftHandle, end, 1, &write_size);
delete[] rom;
FT_Close(ftHandle);
最初、NES解像度を倍にした 512 x 448 で表示させてみたところ、 HDMI-USBキャプチャではスケーリング表示できたが、例のモニタでは表示できなかった。 他のモニタでも確実に表示されるよう、 640 x 480 で出力し 512 x 448 はスケーリングせずに中央に描画させた。
NESコアは 25 MHz もしくは 50 MHz をベースに実装しているので、 クロックを 25.2 MHz にした場合、PPUの描画がHsyncに間に合わず、画面が下に流れてしまう現象が発生してしまう。 よって今回は、25 MHz 以下で設定する必要があった。
LED1個でデバグするのは非常につらいので、Gowin Analizer Oscilloscopeを活用した。
リソース使用率 | 4264/4608 LUTs (93%) |
BlockSRAM使用率 | 9/10 (90%) |
NESコアFmax | 30.7 MHz |
論理合成・配置配線時間 | 40 sec |
FPGAデータ・コード | Tang Nano 4K 用FPGAデータ(GitHub) |
試しにいくつかのMapperを有効化して合成してみたところ、 LUT使用率ほぼ100%になった。 それでも合成・配置配線時間は変わらず、Fmaxも達成している。 Gowin EDA やるじゃん。
価格 | 約2,300円 |
デバイス | LittleBee GW1NR-LV9QN88PC6/I5 (55nm LP embedded flash) |
リソース | 8640 LUTs、BlockSRAM 約58kByte |
メモリ | PSRAM 64Mbit (32Mbit 2ch) |
参考 | Sipeed wiki Tang Nano 9K |
NES on FPGA feat. TangNano9K
— かんな丸!! (@pgate1) 2024年1月6日
PSRAMが2つ使えてそれぞれPRG-ROM・CHR-ROMとして実装できるので移植難易度は低いですね。使用率は61%程度なのでMapperも複数実装できる。容量の大きいマリオ3や沙羅曼蛇も難なく動きました。 pic.twitter.com/8AEa3zZ0UF
参考: hdl-util / hdmi
ただ、NES coreからのサウンド出力をそのままHDMIモジュールに接続しても、 クロックドメインが異なるためノイズが乗る。 HDMIモジュール内ではaudio_clkの立ち上がりでAudio信号をホールドしているようなので、 以下のようにaudio_clkの立ち下がりでサウンドを保持することによりノイズは消えた。
localparam CLKFRQ = 25000000;
localparam AUDIO_RATE = 32000;
localparam AUDIO_CLK_DELAY = CLKFRQ / AUDIO_RATE / 2;
reg [$clog2(AUDIO_CLK_DELAY)-1:0] audio_divider;
reg audio_clk;
wire [15:0] Audio [1:0];
reg [15:0] s_Audio [1:0];
always @(posedge pixel_clk) begin
if(audio_divider != AUDIO_CLK_DELAY - 1)
audio_divider++;
else begin
audio_clk <= ~audio_clk;
audio_divider <= 0;
// クロックたち下がりでホールド
if(audio_clk) s_Audio <= Audio;
end
end
あと、電源を入れた後に「ブツッ…ブツッ…」という定常的なノイズがあるのが謎(1分くらいで消える)。
上記の HDMI IP に移行してもちろん映像は映るものの、 ドットクロックがIP想定25.2MHzのところ、24.975MHzのためか画面がちょっと横にずれる。 HDMIモジュールの入力にオフセット指定できるポートがあるので微調整した。
入力クロック 27 MHz から、49.5 MHz および 24.75 MHz を作るより、 まずDVI用のクロックを作って割った方が 25 MHz に近いのはたまたまなのか、 それとも高クロックからクロックツリーを設計すべきなのか。
また、CHR-ROMリードが間に合ってなかったので、 1クロック早くリードリクエストを出せるようにちょっと修正。
リソース使用率 | 5190/8640 LUTs (61%) |
BlockSRAM使用率 | 10/26 (39%) |
NESコアFmax | 34.7 MHz |
論理合成・配置配線時間 | 50 sec |
FPGAデータ・コード | Tang Nano 9K 用FPGAデータ(GitHub) |
映像はHDMI-USBキャプチャでOBSに映してて、 UARTでキャプチャできればUSBキャプチャいらないのでは!?と考えたが、 1画面(256x240)は約122kByteなので1fps程度だと気づく (テーブルゲームのようなものならいけそう)。
NES on FPGA feat. TangNano9K:ROMデータをUART経由で送信するアプリ作ったので、ついでにキー操作もUARTで送信してゲームプレイできるようにした!PCにゲームパッドつなげて使えるようにすればもっと操作性良くなるかな pic.twitter.com/KMB6Hr4qTc
— かんな丸!! (@pgate1) 2024年6月11日
NSFプレイヤーにピアノロール的波形エフェクト画面を追加してみました。ちなみに3曲目のV.G.NEOは拡張音源のVRC7を使っている曲で、VRC7の本体としてvm2413を使わせて頂いています。 pic.twitter.com/kAPpSmT6zw
— かんな丸!! (@pgate1) 2022年9月22日
価格 | 約4,000円 |
デバイス | Arora GW2AR-LV18QN88C8/I7 (55nm LP SRAM) |
リソース | 20,736 LUTs、BlockSRAM 約103kByte |
メモリ | SDR SDRAM 8MByte |
参考 | Sipeed wiki Tang Nano 20K |
NES on FPGA feat. TangNano20K
— かんな丸!! (@pgate1) 2024年1月7日
1chipMSXに移植した時と同様に、SDRAM1個をPRG-ROMとCHR-ROMで共有してます。9Kよりも動作周波数上がって、NESコアは約50MHzでいけた。 pic.twitter.com/KKi8Dzhvzm
リソース使用率 | 5240/20736 LUTs (26%) |
BlockSRAM使用率 | 10/46 (22%) |
NESコアFmax | 68.1 MHz |
論理合成・配置配線時間 | 45 sec |
FPGAデータ・コード | Tang Nano 20K 用FPGAデータ(GitHub) |
microSDカードアクセスとDVI出力とサウンド出力は Tang Nano 9K 同様の実装とした。
ちなみにTFスロット(microSDスロット)はSDモードが使用できるよう配線接続されているので、 そのうちSDモードアクセスモジュールに切り替えてROMの読み込み時間を半分にしたい (NESの場合は数十kByteなので読み込み一瞬だけど、SNESだと数MByteなので読み込みに少し時間がかかる)。 あとはmicroSDHCカードとFAT32のサポートに移行したい。
2024/02/18 追記: microSDHCコントローラとFAT32モジュールを実装し移行した。
2024/05/04: プレステパッドつなげて操作できるようになった。 他のボードにつなげた時とちょっと受信信号がずれる(?)ので少し調整。 付属の似非プレステパッドもオリジナルプレステパッドとほぼ同じアクセスで使用できたが、 振動や感圧は無し。
NES on FPGA feat. TangNano20K:プレステパッドつなげて操作できるようになった。他のボードにつなげたときとちょっと受信信号がずれる(なんでだろ?)ので少し調整。これは自由に動けるようになって喜ぶ人 pic.twitter.com/7fSw8dWnoV
— かんな丸!! (@pgate1) 2024年5月4日
2024/05/06: ホーンからサウンド出せた。 アンプICのディジタル入力にはバンクIOの制約で3.3Vを使用。 低音が弱いので、三角波の音量を倍にする感じでちょうどいい。
NES on FPGA feat. TangNano20K:おとでた
— かんな丸!! (@pgate1) 2024年5月6日
ピントは永遠に合わない
HDMIにオーディオ流すのはまた時間が取れたらにしよ pic.twitter.com/Na9rb4XAyc
SNES on FPGA feat. TangNano20K(No sound)
— かんな丸!! (@pgate1) 2024年1月8日
LUTが足りなくサウンドDSPを除外して使用率73%で実装しました。でもサウンドメモリは必要なのでBlockSRAMを使ってます。本当はSDRAM1個でWorkRAMと共存させたいけどアクセス速度がちょっと厳しくて無理だった。 pic.twitter.com/3IRMiSfHXs
その他のCPUからアクセスするROMが最大 4MByte、WorkRAM 128kByte、SaveRAM 256kByteはSDRAMを共用する。 ただし、ROMからWorkRAMへのDMAは通常リードとライトが並列に動作するが、 ここでは同じSDRAM内のため並列動作は解除して順次動作としている。
また、SoundRAM 64kByteについてもSDRAMに共存させるつもりだったが、 CPUメモリアクセスとサウンドプロセッサメモリアクセスをアービトレーションしてもアクセスタイムオーバーのため共存は諦めた。 しかし、SNESのCPUはサウンドプロセッサと協調して動作するので、SoundRAMの実装は必須。 結果的には、SoundRAMは4分の1の 16kByte でBlockRAMとして実装した。 観測した限りではあるけど、サウンドドライバはSoundRAMの低アドレス領域(0x0000~0x3FFF)に配置しているケースが多かったので動いている模様。 サウンドDSPをオミットして音を出さないのであれば、後半(0x4000~0xFFFF)の波形メモリ領域が不要であり、このようなトリッキーな実装が可能だった。 なお、8kByteだとバハムートラグーンやクロノトリガーが動かず、 32kByteだとBlockSRAMが足りない。
2024/03/28 追記: アービトレーション回路を修正し、 SDRAMクロックを 99.9 MHz に引き上げ、 配置配線のオプションをタイミング優先にして SoundRAMもSDRAMで共存させることができた。 一応『クロノトリガー』や『バハムートラグーン』のOPデモをパス。 しかしやはりサウンドDSPが入らず音は出ない。 それに、影響ない部分の回路を変更すると動かなくなるし、 そもそもアクセスタイムぎりぎりで動かすためCDCケアしてないので この実装は実験的なものに終わった。
できるだけSDRAMアクセス時間を短くしたいということもあり、 従来SDRAMリフレッシュはオートランさせていたが、 今回はSNESのDRAMリフレッシュタイミング中にリフレッシュを行うよう修正。 動作としてはこちらの方が実機に近いかな。
2024/11/08 追記:
SNESコアのクリティカルパス箇所の修正と、不要なリセットの削減を行った。
また、リセットがLW(Long Wire、ロジックラインにも使用可能なパス)だったものを、
PRIMARY(グローバル専用ライン)に乗せた。
これによりSNESコアFmax 49.95MHz を達成した
(ただしコア 24.975MHz の方が安定して動作する…)。
リセット信号をグローバルPRIMARYラインに乗せるには2通りの方法があるようだ。
ドキュメントだとグローバルクロックラインとあるが、
リセットも乗せられるらしい。
記述例:CLOCK_LOC "sys_reset_6" BUFG = SR;
ここでsys_reset_6は論理合成で生成されるネット名のためHDLを修正すると名前が変わる可能性があるので、
レポートのHigh fanoutから判断して指定する。
記述例:DQCE dqce_inst ( .CLKIN(sys_reset), .CE(1'b1), .CLKOUT(g_sys_reset) );
DQCEプリミティブを使用することでHDLでリセット信号を指定できる。
SNES on FPGA feat. TangNano20K(No sound)
— かんな丸!! (@pgate1) 2024年1月8日
大好物なバハムートラグーンのOPデモもパスしました。どろどろ愛憎劇でリメイク待ってる。 pic.twitter.com/pBAiZa2ZrG
SNES on FPGA feat. TangNano20K(No sound)
— かんな丸!! (@pgate1) 2024年1月9日
セーブデータも読めるようにした pic.twitter.com/jXk2MJ4XYR
リソース使用率 | 14960/20736 LUTs (73%) |
BlockSRAM使用率 | 46/46 (100%) |
SNESコアFmax | 33.4 MHz |
論理合成・配置配線時間 | 150 sec |
FPGAデータ・コード | Tang Nano 20K 用FPGAデータ(GitHub) |
以下の表は CPU,PPU,DMA 回路をリセットを変更して比較したもの。 なぜか同期リセットの方がLUT少し減ったような気もするけど、 どちらでもあまり変わりなさそう。
リセットタイプ | 論理合成・配置配線時間 | 使用リソース | Fmax |
---|---|---|---|
非同期リセット | 150 sec | 9237 LUTs, 4145 regs | 33.4 MHz |
同期リセット | 160 sec | 9140 LUTs, 4150 regs | 35.1 MHz |
簡単なテストHDLでも、ボードによって動いたり動かなかったりするのは品質にばらつきがあるのかな? Tang Nano 60K の発売待ってますよ!Sipeedさん。
NESコアとSNESコアは、どちらもクロック1つの完全同期設計であることと、 CPUがマイクロプログラムベースではないためBlockSRAMを使わないで済むことも 移植が容易だった要因だろう。
2,500円の中華製HDMI-USBキャプチャなかなか良くて小型モニタいらないね。TangNano4Kのカメラデモ普通に映ってるし。MS2130っていうチップ使ってるらしいhttps://t.co/3lgboU3VvK pic.twitter.com/YpjplBOXou
— かんな丸!! (@pgate1) 2023年11月7日