699 Views
November 11, 25
スライド概要
RTLを語る会(18) ~ おかえりaltera, こんにちはGowin (仮) ~
で発表した奴です。
発表版から一部個人情報を削除しました。
たるいです
Verylでつくる! FPGA ファミコン! 2025/11/08 RTLを語る会(18) たるい @taru_logi
目次 ● ● ● ● ● ● ● ● ● Why? ファミコンについて 全体構成 ファミコンの作り方 VerylによるRTL設計例 Verilatorによる検証 GowinFPGA実装 まとめ 今後の展望
なぜファミコン? ● スーファミ世代だから、まずは原点のファミコンから ○ ● ● 子供のころに触れたゲーム機を、技術者として再現してみたい レトロゲームはFPGA応用の定番テーマ ○ オープンソース実装や自作事例も多く、 SNSでも人気 ○ FPGAで“動くレトロゲーム ”は映える&わかりやすい成果物 中規模・資料が豊富で習作に最適 ○ CPU・PPU・APUが明確に分かれ、構造理解にちょうど良い ○ nesdev.orgなどでファミコンの仕様は明示されている
なぜVerylでファミコン? ● ● ● ● Verilog、SystemVerilogは多数例がある しかし、Veryl実装は見当たらない(たるい調べ) RISC-V が実装できるなら 6502 もできるはず あとVeryl勉強したい https://github.com/nand2mario/nestang https://techbookfest.org/product/h1hG3qHj89bxFVEu2jWLnx? productVariantID=gdbWTV3i9Ewwzvyp4C03wZ
ファミコンの仕様 任天堂, 1983年発売 CPU:Ricoh RP2A03(MOS 6502派生, 1.79 MHz) ・BCD削除+APU内蔵 ・アドレス空間 64 KB/WRAM 2 KB PPU:Ricoh RP2C02(映像) ・解像度 256×240 px ・色 52 色/スプライト 最大64枚(横8枚制限) APU(音源) ・矩形波×2、三角波×1、ノイズ×1、DPCM×1(計5ch) ROM/拡張 ・カートリッジ ROM ・一部で拡張音源・ RAM搭載 入出力 ・コントローラ ×2(2Pマイク付) プログラム32KB キャラクタ8KB 合計40KB
全体構成&利用ツール 32KB 8KB PRG ROM CHR ROM dvi_tx Veryl 映像 720x480 60fps CPU PPU 256x240 60fps WRAM 2KB video Scaler VRAM tarunes_top 2KB 主要部品をVerylで記述 ROMもFPGA内に埋め込み PPUは一部機能のみ実装 APUは未実装 SV IP RTL設計 RTL検証 FPGA実装 FPGA_TOP 無料ツ ール& 格安ボ ード RTL設計:Veryl RTL検証:Verilator 実装:GOWIN Tang Primer 20k
ファミコンの作り方 1. 最初はhelloworld 2. 次にnestest すみませんまだここです … 3. そしてスーパーマリオ 4. あとはMapper対応など… CPU:10命令のみでOK! 割り込みなしで OK! PPU:背景レンダリングのみで OK! CPU:151命令 割り込み、サブルーチン追加 コントローラ入力追加 PPU:スプライト描画追加 APU:サウンド追加 Writing NES Emulator in Rust というサイトが詳しい https://bugzmanov.github.io/nes_ebook/
VerylによるRTL設計 ● ● ● Verylで作るCPUの記事を踏襲して6502を実装 これの通りでファミコンのCPUも作れます 神 ● https://cpu.kanataso.net/04-impl-rv32i.html
VerylによるRTL設計例: Interface
interface ram_if::<DATA_WIDTH: u32, ADDR_WIDTH:
u32> {
var addr : logic<ADDR_WIDTH>;
var wen
import tarunes_pkg::*;
: logic
;
var wdata: logic<DATA_WIDTH>;
var rdata: logic<DATA_WIDTH>;
module cpu (
modport master {
clk: input clock,
rst: input reset,
addr : output,
// Memory interface
wen
membus: modport ram_if::<8, 16>::master,
wdata: output,
// Interrupts
rdata: input ,
nmi: input logic,
}
irq: input logic,
modport slave {
..converse(master)
// rdy
}
rdy: input logic,
)
: output,
}
VerylによるRTL設計例: enum
// CPU state machine
enum cpu_state_enum_t {
RESET_LO_SET , // 00
RESET_HI_SET ,
function debug_cpu_state_str (
state: input cpu_state_enum_t ,
) -> logic<128> {
case (state) {
RESET_LO_RD ,
cpu_state_enum_t ::RESET_LO_SET
: return "RE1";
RESET_HI_RD ,
cpu_state_enum_t ::RESET_HI_SET
: return "RE2";
FETCH_SET , // 04
cpu_state_enum_t ::RESET_LO_RD
: return "RE3";
FETCH_WAIT ,
cpu_state_enum_t ::RESET_HI_RD
: return "RE4";
DECODE, // 06
cpu_state_enum_t ::FETCH_SET
: return "F_S";
OP1_WAIT ,
cpu_state_enum_t ::FETCH_WAIT
: return "F_W";
OP1,
cpu_state_enum_t ::DECODE
: return "DEC";
OP2_WAIT ,
cpu_state_enum_t ::OP1_WAIT
: return "OP1_W" ;
OP2,
cpu_state_enum_t ::OP1
: return "OP1";
OP3_WAIT ,
cpu_state_enum_t ::OP2_WAIT
: return "OP2_W" ;
cpu_state_enum_t ::OP2
: return "OP2";
OP3,
EXEC_MEM_READ_WAIT ,
(以下略)
EXECUTE ,
ADDRESS_CALC ,
// 波形デバッグ用
(以下略)
let state_debug : logic<128> = debug_cpu_state_str (state);
VerylによるRTL設計例: enum state_debug信号がASCII表示できる
VerylによるRTL設計例:struct typedef struct packed { logic n ; logic v ; logic one; logic b ; logic d ; logic i ; logic z ; logic c ; } cpu_status_t; var reg_p : cpu_status_t // Flags opcode_t::SEI: { // IRQ Disable reg_p.i = 1'b1; } state = cpu_state_enum_t::FETCH_SET; reg_pc = reg_pc + 1; ;
VerylによるRTL設計例: INX命令
always_ff (clk, rst) {
if_reset {
resultはノンブロッキング代入される↓
} else {
(推奨されない書き方かもしれない…)
…
…
// Xレジスタを加算して、Z,Nフラグ更新
tarunes_tarunes_pkg::opcode_t_INX: begin
opcode_t::INX: {
logic [8-1:0] result ;
let result : logic<8> = reg_x + 1;
result
= reg_x + 1;
reg_x
reg_x
<= result;
= result;
reg_p.z = (result == 0);
reg_p.z <= (result == 0);
reg_p.n = result[7];
reg_p.n <= result[7];
state
= cpu_state_enum_t::FETCH_SET;
state
<= cpu_state_enum_t_FETCH_SET;
reg_pc
= reg_pc + 1;
reg_pc
<= reg_pc + 1;
}
↑always_ff内でletで変数を定義すれば中間変数
(即座に反映される)として利用可能
end
Verilatorによる検証 ● ● ● Verylで作るCPUの記事を踏襲して6502を実装 これの通りでファミコンのCPUも作れます 神 ● https://cpu.kanataso.net/04-impl-rv32i.html
Verilatorによる検証: SDL2による画面表示 // SDL output signals PRG ROM CPU PPU WRAM VRAM tarunes_top ● sdl_sx: output logic<CORDW>, // horizontal SDL position CHR ROM sdl_sy: output logic<CORDW>, // vertical SDL position logic sdl_de: output logic , // data enable sdl_r : output logic<8> , // 8-bit red sdl_g : output logic<8> , // 8-bit green sdl_b : output logic<8> , // 8-bit blue SIM_TOP PPUの出力信号をx,y,de,r,g,bに変換するSIM用ロジックを作成
Verilatorによる検証: SDL2による画面表示 ● ● RTLの出力信号から1clk毎にSDL用 のフレームバッファを更新 1フレームの描画が終わったら、生成 したフレームバッファを用いてSDL ウィンドウを更新 // Write to framebuffer after clock edge if (top->sdl_de) { int x = top->sdl_sx; int y = top->sdl_sy; Pixel* p = &framebuffer [y * H_RES + x]; // Read RGB values directly from RTL SDL output p->a = 0xFF; // transparency p->r = top->sdl_r; // red from RTL p->g = top->sdl_g; // green from RTL p->b = top->sdl_b; // blue from RTL … // Update SDL texture and render (once per frame) SDL_UpdateTexture (sdl_texture, NULL, framebuffer , H_RES*sizeof(Pixel)); SDL_RenderClear (sdl_renderer); SDL_RenderCopy (sdl_renderer, sdl_texture, SDL_RenderPresent (sdl_renderer); frame_count ++; NULL, NULL);
Verilatorによる検証: SDL2による画面表示 ● ● verilatorでSIM開始するとRTL-SIM の結果に基づいて画像がSDLのウィ ンドウに表示される デバッグが楽! ↓PPUデバッグ中
Gowin FPGA実装 ● ● NESの出力は256x240なので、モニ タに表示させるため、2倍にして、左右 に黒を追加し720x480にする 720x480の映像をGOWINのDVI_TX のIPを使ってHDMI(DVI)出力をする 32KB 8KB PRG ROM CHR ROM dvi_tx 映像 720x480 60fps CPU PPU 256x240 60fps (SystemverilogなのはChatGPTに書いてもらったから) WRAM 2KB video Scaler VRAM tarunes_top 2KB FPGA_TOP
Gowin FPGA実装
●
RAMやROMはVerylからSV変換した
記述で問題なくRAMブロックに推論さ
れる
module ram::<DATA_WIDTH: u32, ADDR_WIDTH: u32> (
clk
: input
clock
,
rst
: input
reset
,
rambus: modport ram_if::<DATA_WIDTH, ADDR_WIDTH>::slave,
) {
var mem_ram: logic<DATA_WIDTH> [2 ** ADDR_WIDTH];
always_ff {
if_reset {
for i: u32 in 0..2 ** ADDR_WIDTH {
mem_ram[i] = 0;
}
rambus.rdata = 0;
} else {
rambus.rdata = mem_ram[rambus.addr];
if rambus.wen {
mem_ram[rambus.addr] = rambus.wdata;
先行事例( nestang)とのサイズ比較 ● CPU ○ ○ ● 自作コア:1209LUT nestang:448LUT PPU ○ ○ 自作コア(一部機能): 800LUT nestang:966LUT 実装としてはだいぶ微妙 (まあ習作だしいいか…) 補足:これは Verylのせいではなくて、たるいの RTLが微妙なだけ 最適化していきたい
まとめ ● ● ● Verylを用いてファミコンの一部機能を実装することができた Verilatorにより画面を表示させる検証環境を構築することができた Verylを変換したRTLをTang Primer 20kに実装することができた HELLOWORLD表示だけなら結構簡 単(1-2日でいける)なのでまずはそこ まででもTRYしてみてはいかがでしょう か
今後の展望 ● ● ● ● ● ● ● ● Githubで公開する マリオのタイトル画面表示させる マリオをちゃんと動かす APU実装して音出す RTL最適化する 実カートリッジ読み込めるようにする まとめて技術書典とかで出す(?) etc... 今後もVeryl環境でRTLしばいていきたいと思います