Verylでつくる!FPGA ファミコン!公開版

699 Views

November 11, 25

スライド概要

RTLを語る会(18) ~ おかえりaltera, こんにちはGowin (仮) ~
で発表した奴です。
発表版から一部個人情報を削除しました。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

Verylでつくる! FPGA ファミコン! 2025/11/08 RTLを語る会(18) たるい @taru_logi

2.

目次 ● ● ● ● ● ● ● ● ● Why? ファミコンについて 全体構成 ファミコンの作り方 VerylによるRTL設計例 Verilatorによる検証 GowinFPGA実装 まとめ 今後の展望

3.

なぜファミコン? ● スーファミ世代だから、まずは原点のファミコンから ○ ● ● 子供のころに触れたゲーム機を、技術者として再現してみたい レトロゲームはFPGA応用の定番テーマ ○ オープンソース実装や自作事例も多く、 SNSでも人気 ○ FPGAで“動くレトロゲーム ”は映える&わかりやすい成果物 中規模・資料が豊富で習作に最適 ○ CPU・PPU・APUが明確に分かれ、構造理解にちょうど良い ○ nesdev.orgなどでファミコンの仕様は明示されている

4.

なぜVerylでファミコン? ● ● ● ● Verilog、SystemVerilogは多数例がある しかし、Veryl実装は見当たらない(たるい調べ) RISC-V が実装できるなら 6502 もできるはず あとVeryl勉強したい https://github.com/nand2mario/nestang https://techbookfest.org/product/h1hG3qHj89bxFVEu2jWLnx? productVariantID=gdbWTV3i9Ewwzvyp4C03wZ

5.

ファミコンの仕様 任天堂, 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

6.

全体構成&利用ツール 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

7.

ファミコンの作り方 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/

8.

VerylによるRTL設計 ● ● ● Verylで作るCPUの記事を踏襲して6502を実装 これの通りでファミコンのCPUも作れます 神 ● https://cpu.kanataso.net/04-impl-rv32i.html

9.
[beta]
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,

}

10.
[beta]
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);

11.

VerylによるRTL設計例: enum state_debug信号がASCII表示できる

12.

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; ;

13.
[beta]
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

14.

Verilatorによる検証 ● ● ● Verylで作るCPUの記事を踏襲して6502を実装 これの通りでファミコンのCPUも作れます 神 ● https://cpu.kanataso.net/04-impl-rv32i.html

15.

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用ロジックを作成

16.

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);

17.

Verilatorによる検証: SDL2による画面表示 ● ● verilatorでSIM開始するとRTL-SIM の結果に基づいて画像がSDLのウィ ンドウに表示される デバッグが楽! ↓PPUデバッグ中

18.

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

19.
[beta]
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;

20.

先行事例( nestang)とのサイズ比較 ● CPU ○ ○ ● 自作コア:1209LUT nestang:448LUT PPU ○ ○ 自作コア(一部機能): 800LUT nestang:966LUT 実装としてはだいぶ微妙 (まあ習作だしいいか…) 補足:これは Verylのせいではなくて、たるいの RTLが微妙なだけ 最適化していきたい

21.

まとめ ● ● ● Verylを用いてファミコンの一部機能を実装することができた Verilatorにより画面を表示させる検証環境を構築することができた Verylを変換したRTLをTang Primer 20kに実装することができた HELLOWORLD表示だけなら結構簡 単(1-2日でいける)なのでまずはそこ まででもTRYしてみてはいかがでしょう か

22.

今後の展望 ● ● ● ● ● ● ● ● Githubで公開する マリオのタイトル画面表示させる マリオをちゃんと動かす APU実装して音出す RTL最適化する 実カートリッジ読み込めるようにする まとめて技術書典とかで出す(?) etc... 今後もVeryl環境でRTLしばいていきたいと思います