Posts Issued on September 20, 2026

posted by sakurai on September 20, 2026 #1125

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つです。


左矢前のブログ 次のブログ右矢