![]() |
21 |
ClaudeによるGameFSMの改善 (3) |
3. モジュール化
ChatGPT版では、ブリッタが使う作業レジスタと画面メモリ側のレジスタが全部メインモジュールの中で宣言されていました。メインのどの規則からも読み書きできるので、実質はグローバル変数です。
書き換え前、mkGameFSMの中
Reg#(PAddr_t) p_addr <- mkRegU;
Reg#(VAddr_t) v_addr <- mkRegU;
Reg#(Bool) fvwe <- mkReg(False),
fwen <- mkReg(False),
fgunexp <- mkReg(False),
fbullet <- mkReg(False),
fobj <- mkReg(False),
fbase <- mkReg(False),
fempty <- mkReg(False),
fhit_alien <- mkReg(False);
Reg#(SoundCode_t) inscode <- mkRegU;
Reg#(Pattern_t) vd <- mkRegU;
Wire#(Pattern_t) vdata <- mkWire,
romdata <- mkWire;
Wire#(UInt#(3)) dipsw <- mkWire;
Reg#(UInt#(9)) xs <- mkRegU,
ys <- mkRegU,
xd <- mkRegU,
yd <- mkRegU;
method Action pdin(Pattern_t in_romdata);
romdata <= in_romdata;
endmethod
method Action vdin(Pattern_t in_vdata);
vdata <= in_vdata;
endmethod
method PAddr_t prom_addr();
return p_addr;
endmethod
method VAddr_t vaddr();
return v_addr;
endmethod
method Bool vwe();
return fvwe;
endmethod
method Pattern_t vdout();
return vd;
endmethod
書き換え後、Blitter.bsvのmkBlitterの中
// MemPort: 画面メモリ側の面
interface MemPort;
(* always_ready *) method PAddr_t prom_addr;
(* always_ready *) method VAddr_t vaddr;
(* always_ready *) method Bool vwe;
(* always_ready *) method Pattern_t vdout;
(* always_enabled *) method Action pdin(Pattern_t d);
(* always_enabled *) method Action vdin(Pattern_t d);
endinterface
interface Blitter_ifc;
interface BlitPort port;
interface MemPort mem;
endinterface
(* synthesize *)
module mkBlitter(Blitter_ifc);
Reg#(PAddr_t) p_addr <- mkRegU;
Reg#(VAddr_t) v_addr <- mkRegU;
Reg#(Bool) fvwe <- mkReg(False),
fobj <- mkReg(False),
fbase <- mkReg(False);
Reg#(Pattern_t) vd <- mkRegU;
Wire#(Pattern_t) vdata <- mkWire,
romdata <- mkWire;
Reg#(UInt#(9)) xs <- mkRegU,
ys <- mkRegU,
xd <- mkRegU,
yd <- mkRegU;
interface MemPort mem;
method PAddr_t prom_addr = p_addr;
method VAddr_t vaddr = v_addr;
method Bool vwe = fvwe;
method Pattern_t vdout = vd;
method Action pdin(Pattern_t d); romdata <= d; endmethod
method Action vdin(Pattern_t d); vdata <= d; endmethod
endinterface
書き換え後、mkGameFSMの中
Blitter_ifc blt <- mkBlitter;
method Action pdin(Pattern_t in_romdata);
blt.mem.pdin(in_romdata);
endmethod
method Action vdin(Pattern_t in_vdata);
blt.mem.vdin(in_vdata);
endmethod
method PAddr_t prom_addr();
return blt.mem.prom_addr;
endmethod
method VAddr_t vaddr();
return blt.mem.vaddr;
endmethod
method Bool vwe();
return blt.mem.vwe;
endmethod
method Pattern_t vdout();
return blt.mem.vdout;
endmethod
レジスタの宣言は一字も変わらず、置き場所だけがmkBlitterに移りました。名前はインタフェースに現れないので、メインからは触れません。これがBSVにおけるローカル変数の実体で、関数の中で実行時の状態を持つ手段が無いため、状態の局所化はモジュールの単位で行います。モジュールに入れたのはブリッタだけで、drawLivesやdrawScoresのような描画ルーチンとハイスコアはゲームロジックとしてメインに残しています。モジュール境界の目的は、42箇所から呼ばれる1個のFSMを切り出すことにあり、画面を触るものを集めることとは別です。
物量への効果は、この段階だけを取り出すと2点目に書いた状態数の削減と同じもので、モジュールの境界そのものはLUTを増やしも減らしもしません。モジュール化が効いたのは別の2点です。
一つはコンパイル時間で、bscのスケジューラはメインの規則とブリッタの規則の組を調べなくなり、mkBlitterは独立に数秒でコンパイルされます。モジュール化した時点で、抑止していた警告は1,734件から344件に減り、bscの時間は同じPCで48秒から26秒になりました。教科書版ではdrawLivesとdrawScoresがメインに戻り、4点目でFSMを3個増やすので、G0036とG0010は477件になります。加えて4点目の段階からG0009が92件出ます。別のFSMの状態どうしが同じレジスタを読み書きして順序の循環ができ、bscがその組をconflictにして循環を切った、という警告で、FSMは1個ずつしか動かないので実害はありません。G0036、G0010と同じ根拠で抑止し、合計は569件になります。8/3版にはG0009が無いので、前の1,734件はG0036とG0010だけの数です。
もう一つは、ブリッタが独立したことで内部の整理がしやすくなったことで、矩形4種のループを1組に統合しました。画素ごとの書き込み値だけが違うので、その部分だけをcaseに閉じ込めました。
action
case (b.op)
BLIT_COPY: vd <= romdata;
BLIT_OR: vd <= vdata | romdata;
BLIT_ANDN: vd <= vdata & ~romdata;
BLIT_ERASE: vd <= 0;
endcase
xs <= xs + 1;
xd <= xd + 1;
endaction
ブリッタ部分の規則数は38から14に減りました。
