|
20 |
ClaudeによるGameFSMの改善 (2) |
2. サブFSMの起動
サブFSMを1個だけインスタンスし、呼び出し側はstartとdoneだけを触る、という構造はChatGPT版と同じです。RUN_FSMマクロも、メインに置くwt_fsmやinitAll_fsmなど9個のFSMでは今も使っています。変わったのはブリッタの呼び方で、引数のラッチと起動が呼び出し側の2つの文からモジュールの中に移りました。
書き換え前
Reg#(Blit) blit_arg <- mkReg(Blit{
op: BLIT_COPY, // 無難なデフォルト
sx: 0, sy: 0,
dx: 0, dy: 0,
w: 0, h: 0,
col: 0
}); // blit 用の引数保持レジスタ
FSM gfx_fsm <- mkFSM(blit(blit_arg));
// RUN_FSM: サブFSM 起動マクロ(start → done 待ち)。seq - endseq 内で呼ぶこと
`define RUN_FSM(F) action F.start(); endaction await(F.done);
// runBlit: gfx_fsm の起動ラッパ
// 引数を blit_arg にラッチしてから gfx_fsm を start し、done を待つ。
function Stmt runBlit(Blit b);
return (seq
action blit_arg <= b; endaction
seq
`RUN_FSM(gfx_fsm) // start→done
endseq
endseq);
endfunction // runBlit
function Stmt copyArea(U8 sx, U8 sy, U8 dx, U8 dy, U9 w, U9 h);
return runBlit(Blit{op:BLIT_COPY, sx:sx, sy:sy, dx:dx, dy:dy, w:w, h:h, col:0});
endfunction // copyArea
書き換え後、Blitter.bsv側
// BlitPort: ブリッタの利用者に見せる面
interface BlitPort;
method Action start(Blit b); // コマンド投入(1 サイクル後に FSM 起動)
method Bool done; // 完了(コマンド未投入時も True)
method Bool obj; // 直前の VDOTS/HDOTS でスプライト画素あり
method Bool base; // 直前の VDOTS/HDOTS でシールド画素あり
endinterface
// runBlitOn と同名ラッパ群の On 版: BlitPort を第 1 引数に取る。
// 各モジュールでは let copyArea = copyAreaOn(port); のように部分適用して使う。
function Stmt runBlitOn(BlitPort p, Blit b);
return (seq
p.start(b);
await(p.done);
endseq);
endfunction // runBlitOn
function Stmt copyAreaOn(BlitPort p, U8 sx, U8 sy, U8 dx, U8 dy, U9 w, U9 h);
return runBlitOn(p, Blit{op:BLIT_COPY, sx:sx, sy:sy, dx:dx, dy:dy, w:w, h:h, col:0});
endfunction // copyAreaOn
FSM gfx_fsm <- mkFSM(blitDispatch(blit_arg));
// ---- ブリッタの起動 ----
// port.start はコマンドを RWire に載せるだけで、同じサイクルに kick が引数を
// blit_arg にラッチし、次のサイクルに launch が gfx_fsm を起動する。
// 元の runBlit(引数ラッチ → start → done 待ち)と同じサイクル数になる。
Reg#(Bool) go <- mkReg(False);
RWire#(Blit) cmd <- mkRWire;
rule kick (cmd.wget matches tagged Valid .b);
blit_arg <= b;
go <= True;
endrule
rule launch (go);
go <= False;
gfx_fsm.start;
endrule
interface BlitPort port;
method Action start(Blit b) if (!go && gfx_fsm.done);
cmd.wset(b);
endmethod
method Bool done = !go && gfx_fsm.done;
method Bool obj = fobj;
method Bool base = fbase;
endinterface
書き換え後、GameFSM.bsv側
Blitter_ifc blt <- mkBlitter;
// 原実装と同名の薄ラッパ群: Blitter.bsv の On 版に blt.port を部分適用
let copyArea = copyAreaOn(blt.port);
let orArea = orAreaOn(blt.port);
let eraseArea = eraseAreaOn(blt.port);
let eraseAreaSP = eraseAreaSPOn(blt.port);
let readVdots = readVdotsOn(blt.port);
let readHdots = readHdotsOn(blt.port);
呼び出し箇所のcopyAreaの書き方は変わりません。変わるのは展開結果で、ChatGPT版では呼び出し1箇所が「ラッチ、start、await」の3状態に展開されていたのが、Claude版では「start、await」の2状態になります。ラッチと起動の2サイクルはモジュールの中のkickとlaunchが担うので、動作のサイクル数は同じです。Verilatorで1,000万サイクルのVRAM書き込み系列を比べ、サイクル単位で一致することを確認しました。
生成Verilogで見ると、呼び出し箇所の規則はこう変わります。書き換え前は1箇所につきラッチの規則とawaitの規則が別々にあります。
// rule RL_action_l731c20_12 ← blit_arg <= b のラッチ
assign WILL_FIRE_RL_action_l731c20_12 =
NOT_wt_fsm_jj_repeat_count_read__86_EQ_0_87_88_ETC___d895 &&
!wt_fsm_start_reg &&
state_mkFSMstate == 8'd46 ;
// rule RL_action_l731c59_9 ← await(gfx_fsm.done)
assign WILL_FIRE_RL_action_l731c59_9 =
blit_arg_2_BITS_56_TO_54_3_EQ_0_4_AND_gfx_fsm__ETC___d954 &&
state_mkFSMstate == 8'd43 ;
書き換え後はstartの規則とawaitの規則だけで、doneはモジュールの出力ポート1本になります。
// rule RL_action_l55c6_1 ← blt.port.start(b)
assign WILL_FIRE_RL_action_l55c6_1 =
blt$RDY_port_start && str_idx_129_ULT_24___d5130 &&
(state_mkFSMstate == 7'd3 || state_mkFSMstate == 7'd6) ;
// rule RL_action_l56c10_1 ← await(blt.port.done)
assign WILL_FIRE_RL_action_l56c10_1 =
blt$port_done && state_mkFSMstate == 7'd4 ;
呼び出し箇所ごとに状態が1つ減った結果、各FSMの状態数は次のように減りました。
| FSM | 前 | 後 | 比較 |
|---|---|---|---|
| メイン | 194 | 168 | ▲26 |
| updatePlayerBullet_fsm | 68 | 58 | ▲10 |
| updateAlienBullet_fsm | 42 | 34 | ▲8 |
| initAll_fsm | 28 | 24 | ▲4 |
| drawLives_fsm | 13 | 9 | ▲4 |
| drawScores_fsm | 26 | 18 | ▲8 |
状態が減ればその分の状態デコードと遷移論理が減ります。これがモジュール化の段階でLUTが減った主な理由で、Yosysの概算では6,273から6,018に4%減っています。
メリットをまとめると、呼び出し側からblit_argとgfx_fsmが消えてstartとdoneだけを見ればよくなったこと、そして呼び出し箇所ごとの状態が1つ減ったこと、の2つです。
Leave a Comment