Posts Tagged with "AI"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.

GameFSMの改良 (2)

posted by sakurai on September 15, 2025 #1022

まずString配列があまり美しくなかったので改良します。

以下にオリジナルのソースの部分を示します。本来はCと同様にstructの配列を構成したかったのですが、初期値の設定がうまくいかなかったので、しかたなく要素を分解し、以下のようにsx, sy, dx, dy, w, hの6要素のそれぞれの配列としました。sxはソースのx座標、syはソースのu座標、dxはデストのx座標、dyはデストのu座標、wは幅、hは高さを示します。

初期化部分:

 //                     P   L   A   ^   S   P   A   C   E  --  --   I   N   V   A   D   E   R   S
   UInt#(8) s1sx[19] = { 42, 50, 58, 77, 66, 42, 58, 74, 82, 42, 42, 90, 98,106, 58,114, 82,122, 66},
            s1sy[19] = {137,137,137,126,137,137,137,137,137,129,129,137,137,137,137,137,137,137,137},
            s1dx[19] = {112,120,128,136, 72, 80, 88, 96,104,112,120,128,136,144,152,160,168,176,184},
        s1dy[19] = { 68, 68, 68, 68, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93},
        s1w[19]  = {  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5},
        s1h[19]  = {  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7};

描画部分:

  function Stmt stringS1; // PLAY SPACE INVADERS
      return (seq
         for (str_idx <= 0; str_idx < 19; str_idx <=  str_idx + 1) seq
            copyArea(s1sx[str_idx], s1sy[str_idx], s1dx[str_idx], s1dy[str_idx], s1w[str_idx], s1h[str_idx]);
            wait_timer(`TICK_WAIT8);
            if (fbutton) break;
         endseq // for
      endseq);
   endfunction

これをChatGPTの助けを借りながら初期値付きの2次元配列にしようと思います。


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

GameFSMの改良

posted by sakurai on September 12, 2025 #1021

過去の外部発表記事において、初めてのBSVによるかなり規模の大きなプログラムを開発しましたが、まだノウハウがあまりなかったため、以下のような課題がありました。

改良点
本プログラムには、以下のような改良点があります。

・グローバル変数のローカル化
関数内でのみ使用している変数は、通常関数スコープ内で定義されるローカル変数とすべきですが、現在はグローバル変数になっている。

・関数内シーケンシャル構文のローカル化
メインループでは関数を多数呼び出していますが、それぞれがインライン展開されているようです。これはBSCコンパイル時間も1時間以上と莫大になり、また資源も再利用できないので、サブルーチンのように呼び出したい。

ChatGPTも"5"になり、相当推論能力も上がっているので、ChatGPTの助けを借りてこの辺りを最適化してみます。

現状は例えばCopyAreaは9回呼び出されていますが、functionであり、moduleではないため、メインモジュールからinlineで呼び出され、そのたびに展開されるのでハードウエアとしては膨大な量になります。

一方、module化すれば呼び出し毎にインスタンスされないので、関数は再利用されますが、残念ながらBSVにローカル変数は無いようです。


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

posted by sakurai on September 11, 2025 #1020

Producer側もラッパー化も提供してください。

了解です。 では、Producer 側も既存のproduceメソッドを壊さずにGet#(UInt#(4))インタフェースに変換するラッパーを作ります。

Producer 側ラッパー

import GetPut::*;

interface Producer4GetIfc;
  interface Get#(UInt#(4)) out;   // 外部から見えるのはGet
endinterface

module mkProducer4GetWrapper #(Producer4Ifc prod) (Producer4GetIfc);
  interface Get out;
    method ActionValue#(UInt#(4)) get;
      let x <- prod.produce;   // 既存のproduceを呼び出すだけ
      return x;
    endmethod
  endinterface
endmodule

これで両方ラッパー化したTop

import GetPut::*;
import Connectable::*;

module mkTopDirect(...);
  // 元のインスタンス
  Producer4Ifc prod_orig <- mkProducer4;
  Consumer4Ifc cons_orig <- mkConsumer4;

  // ラッパーを通してGet/Put化
  Producer4GetIfc prod <- mkProducer4GetWrapper(prod_orig);
  Consumer4PutIfc cons <- mkConsumer4PutWrapper(cons_orig);

  // 2行版:元をインライン生成してラップ
  Producer4GetIfc prod <- mkProducer4GetWrapper(mkProducer4);
  Consumer4PutIfc cons <- mkConsumer4PutWrapper(mkConsumer4);

  // 直結(rule connect不要)
  mkConnection(prod.out, cons.in);

  // 必要ならここにTopの他の処理(recvCntなど)
endmodule

この構成のメリット

  • 元の Producer4 / Consumer4 を一切改造しない
  • Top では mkConnection だけでシンプルに接続
  • Get/Put標準インタフェースなので、他のモジュールやパイプライン部品と直結可能
  • 将来、GameFSM / SoundFSM のように直接接続できる構造に発展しやすい

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

posted by sakurai on September 10, 2025 #1019

既存の consume を崩したくないなら、薄いラッパーで Put#(T) を包んで中で consume を呼ぶだけでもOKとのことだが、このラッパーを提供してください。

了解です。 既存のConsumer4モジュールを一切壊さず、外部からはPut#(UInt#(4))に見える薄いラッパーを提供します。 中で単にconsume()を呼び出すだけなので、既存のテストや内部ロジックには手を入れません。

ラッパーモジュール例

import GetPut::*;

interface Consumer4PutIfc;
  interface Put#(UInt#(4)) in;   // 外部から見えるのはPut
endinterface

module mkConsumer4PutWrapper #(Consumer4Ifc cons) (Consumer4PutIfc);
  // Putインタフェースを定義
  interface Put in;
    method Action put(UInt#(4) x);
      cons.consume(x);   // 既存のconsumeを呼び出すだけ
    endmethod
  endinterface
endmodule

Top 側での使い方

import GetPut::*;
import Connectable::*;

module mkTopDirect(...);
  Producer4Ifc     prod <- mkProducer4;
  Consumer4Ifc     cons <- mkConsumer4;
  Consumer4PutIfc  consP <- mkConsumer4PutWrapper(cons);

  // ProducerとConsumerを接続
  mkConnection(prod.out, consP.in);

  // 他のTop内ロジック(recvCntなど)が必要ならここに
endmodule

ポイント

  • 既存の Consumer4はそのまま使う
  • Put#(T)の形に変換するのは外部ラッパーが担当
  • mkConnectionGet#(T)Put#(T)のみを接続できるため、この変換が必要
  • recvCnt$displayをTopでやりたい場合は、Consumer4側でRWirePulseWireを使って通知できます

この構成なら、Topのrule connectは不要で、元のConsumer4のメソッド構造もそのままにできます。 もしこれに加えてProducer側もラッパー化した例も欲しいですか?


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

posted by sakurai on September 9, 2025 #1018

実際のプログラムでは最上位はConnectableにより直結しています。つまり上位のANDは生成されません。どうしたら良いか聞いてみます。

TopDirect.bsvにおいては、
rule connect; UInt#(4) x <- prod.produce; cons.consume(x); // 直ちに渡す $display("%0t: produce = %h", $time, x); recvCnt <= recvCnt + 1; endrule このようになっている。これはcallerが2つのcalleeを呼び出すので、topにおいて2つのcalleeのrdyのANDがとられ、calleeのenableとなるはずです。一方で、本来のGameFSM.bsv, SoundFSM.bsvは独立にコンパイルされ、topではconnectableで直接結線されるので、ANDは出てきません。つまり、上記のrule connectをconnectableに変更しても動作させるにはcalleeをどのようにしたら良いですか?

なるほど、rule connectをやめて Connectable で直結したいって話ですね。 その場合は、callee(Producer/Consumer)を標準の Get#/Put#インタフェースで公開するのが王道です。そうすれば mkConnectionが内部で「両方readyなら転送」のルールを自動生成してくれるので、トップで2つの enable を同一ルールでANDする構造から卒業できます。

方針

  • Producer:Get#(T)を出す(getガードで ready 表現)
  • Consumer:Put#(T)を受ける(putガードで ready 表現)
  • Top:mkConnection(prod.out, cons.in)で直結(rule connect不要)

具体例

Consumer 側(今の consumePut化)

import FIFO::*;
import GetPut::*;  // ← これ必要

interface Consumer4Ifc;
  interface Put#(UInt#(4)) in;   // ← 受け口をPutに
  // 必要なら状態確認用のmethodも追加可
endinterface

module mkConsumer4 (Consumer4Ifc);

  Reg#(UInt#(4)) cnt     <- mkReg(0);
  Reg#(Bool)     busy    <- mkReg(False);
  Reg#(Bool)     play    <- mkReg(False);
  Reg#(UInt#(4)) lastVal <- mkRegU;

  // timer は consume 中(=busy)は止める
  rule timer (play && !busy);
    if (cnt == 14) begin
      cnt  <= 0;
      play <= False;
    end
    else
      cnt <= cnt + 1;
  endrule

  rule clear_busy (busy);
    busy <= False;
  endrule

  // これが元の consume 相当
  interface Put in;
    method Action put(UInt#(4) x) if (!busy);
      lastVal <= x;
      $display("%0t: consume = %h", $time, x);
      busy <= True;
      play <= True;
      cnt  <= 0;
    endmethod
  endinterface
endmodule

Producer 側(produceGet化)


import GetPut::*;

interface Producer4Ifc;
  interface Get#(UInt#(4)) out;   // ← 出口をGetに
endinterface

module mkProducer4 (Producer4Ifc);
  // 例:なんらかのソースから値を出す
  Reg#(UInt#(4)) v <- mkReg(0);
  // ready 条件をガードで表現(例:常にOKならガード無しでも可)
  interface Get out;
    method ActionValue#(UInt#(4)) get;
      UInt#(4) x = v;
      v <= v + 1;
      return x;
    endmethod
  endinterface
endmodule

Top(直結)


import GetPut::*;
import Connectable::*;  // ← これ必要

module mkTopDirect(...);
  Producer4Ifc prod <- mkProducer4;
  Consumer4Ifc cons <- mkConsumer4;

  // 直結! これが rule connect の置き換え
  mkConnection(prod.out, cons.in);

  // もし recvCnt を増やしたいなら、Consumer 側でカウンタを増やすか、
  // Consumer の put 内で RWire を叩いて Top で拾うなどの手もあります。
endmodule

補足(recvCnt$displayを Top でやりたい場合)

mkConnectionの中でput/get が呼ばれるので、Top からはその瞬間が見えません。 Top で観測したいなら:

  • Consumer のput内でRWire/PulseWireに書く → Top でwgetを見てカウント・表示
  • あるいは Consumer に「通知用のメソッド」を用意して Top から呼ぶ(設計次第)

これで Top に AND は出ず、Connectableでの疎結合が成立します。 既存のconsumeを崩したくないなら、薄いラッパーで Put#(T)を包んで中で consumeを呼ぶだけでもOKです。


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

posted by sakurai on September 8, 2025 #1017

Verilogシミュレーション

次にverilogシミュレーションを実行します。

$ make vsim
bsc -verilog -bdir vobj -g mkConsumer4 -vdir rtl Consumer4.bsv
Warning: "Consumer4.bsv", line 7, column 8: (G0020)
  System functions (e.g., $display) called by interface methods execute in an
  unpredictable order during Verilog simulations.
      Top-level interface method `consume' calls a system function (e.g.,
      $display) at "Consumer4.bsv", line 34, column 7
Warning: "Consumer4.bsv", line 7, column 8: (G0117)
  Rule `consume' shadows the effects of `timer' when they execute in the same
  clock cycle. Affected method calls:
    cnt.write, play.write
  To silence this warning, use the `-no-warn-action-shadowing' flag.
Verilog file created: rtl/mkConsumer4.v
bsc -verilog -bdir vobj -u -g mkTopDirect -vdir rtl TopDirect.bsv
checking package dependencies
compiling TopDirect.bsv
code generation for mkTopDirect starts
Verilog file created: rtl/mkTopDirect.v
All packages are up to date.
bsc -verilog -bdir vobj -vsearch rtl -e mkTopDirect -o mkTopDirect.vexe
Verilog binary file created: mkTopDirect.vexe
./mkTopDirect.vexe +bscvcd=vsim.vcd +bscdumpvars
VCD info: dumpfile vsim.vcd opened for output.
5: produce = 1
5: consume = 1
105: produce = 9
105: consume = 9
205: produce = d
205: consume = d
305: produce = f
305: consume = f
405: produce = e
405: consume = e
505: produce = 7
505: consume = 7
605: produce = a
605: consume = a
705: produce = 5
705: consume = 5
805: produce = b
805: consume = b
905: produce = c
905: consume = c
=== simulation finished ===
rtl/mkTopDirect.v:115: $finish(1) called at 915 (1s)

シミュレーション波形を確認します。

$ gtkwave -A vsim.vcd

図%%.1
図1017.1 verilogシミュレーション波形

本来15サイクルかかるConsumeの動作が強制的に10サイクルでリセットされるのが確認できます。


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

posted by sakurai on September 5, 2025 #1016

Bsimシミュレーション

bsimシミュレーションを実行します。

$ make bsim
bsc -sim -bdir bsim -u -g mkTopDirect TopDirect.bsv
checking package dependencies
All packages are up to date.
cd bsim && \
    bsc -sim -bdir . -e mkTopDirect -o ../mkTopDirect.exe
/home/sakurai/src/bsv/testFSM_preemptive/bsim
Warning: Command line: (S0073)
  Duplicate directories were found in the path specified with the -p flag.
  Only the first occurrence will be used. The duplicates are:
    /home/sakurai/src/bsv/testFSM_preemptive/bsim
  Note that when the -bdir flag is used, that directory is automatically added
  to the head of the path.
Bluesim object reused: mkTopDirect.{h,o}
Bluesim object created: model_mkTopDirect.{h,o}
Simulation shared library created: ../mkTopDirect.exe.so
Simulation executable created: ../mkTopDirect.exe
./mkTopDirect.exe -V bsim.vcd
10: consume = 1
10: produce = 1
110: consume = 9
110: produce = 9
210: consume = d
210: produce = d
310: consume = f
310: produce = f
410: consume = e
410: produce = e
510: consume = 7
510: produce = 7
610: consume = a
610: produce = a
710: consume = 5
710: produce = 5
810: consume = b
810: produce = b
910: consume = c
910: produce = c
=== simulation finished ===

シミュレーション波形を確認します。

$ gtkwave -A bsim.vcd

図%%.1
図1016.1 bsimシミュレーション波形

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

posted by sakurai on September 4, 2025 #1015

ChatGPTのコードがあまり良くないので、次の考え方で整理してコードを修正します。

元の処理はbusyを内部演奏状態に使用すると同時に外部にbusyを見せていたので、プリエンプティブではなかった。この2つを分離し、playを内部演奏状態とし、外部には受取りの1サイクルのみbusyを立てるようにしてください。

以下に変更したコードを示します。

package Consumer4;

interface Consumer4Ifc;
   method Action consume(UInt#(4) x);
endinterface

module mkConsumer4 (Consumer4Ifc);

   Reg#(UInt#(4)) cnt      <- mkReg(0);     // 0-14 を数える 4bit で十分
   Reg#(Bool)     busy     <- mkReg(False); // ハンドシェーク停止用 1 サイクル
   Reg#(Bool)     play     <- mkReg(False); // 15 サイクル演奏フラグ
   Reg#(UInt#(4)) lastVal  <- mkRegU;       // 取り込んだコード
   
   /* 15 サイクル演奏タイマ */
   rule timer (play);
      if (cnt == 14) begin
         cnt  <= 0;
         play <= False;          // 演奏終了
      end
      else
         cnt <= cnt + 1;
   endrule
   
   /* busy は 1 サイクル後に自動クリア */
   rule clear_busy (busy);
      busy <= False;
   endrule

  /* 受信メソッド
      ガード if (!busy) を付けることで
      can_fire = !busy が自動生成される */
   method Action consume(UInt#(4) x) if (!busy);
      lastVal <= x;                   // EN=1 サイクルでラッチ
      $display("%0t: consume = %h", $time, x);
      busy <= True;              // 1 サイクルだけbusyを上げる
      play <= True;              // 演奏開始
      cnt  <= 0;
   endmethod
endmodule
endpackage

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

posted by sakurai on September 3, 2025 #1014

前の記事で、ChatGPTのコード中に、busyなら受け付けないとあったのが気になったので確認します。

start_playに疑義があります。忙しかったらやらないのではプリエンプションを実装とはいえないのでは。

「ready は常に True」はハンドシェークの意味を成しません。

Producer が validをパルスで出したい場合、同サイクルにreadyが1でなければ 転送が成立しません。 そこで Consumer 側は「受け取った直後だけready=0に落とす」ことで 1 サイクル確実にハンドシェークを区切ります。

修正版 mkConsumer4_Preemptive

module mkConsumer4_Preemptive (ConsumerRaw);
   Reg#(Bool) busy       <- mkReg(False);   // 今まさに演奏中か
   Reg#(Bool) hasNewCmd  <- mkReg(False);   // 未処理コマンド保持
   Reg#(UInt#(4)) last   <- mkRegU;
   Reg#(Bool) ready_reg  <- mkReg(True);    // 出力する ready

   /* Producer から毎サイクル呼ばれるメソッド
      - always_ready/always_enabled: 条件文を書かない */
   (* always_ready, always_enabled *)
   method Action soundPreempt(UInt#(4) d);
      // 受信したサイクルで ready = 0 に落とす
      ready_reg  <= False;
      last       <= d;          // 最新コードを保持
      hasNewCmd  <= True;
   endmethod

   /* ready 出力メソッドは最後に置く */
   method Bool rdy_o();
      return ready_reg;
   endmethod

   /* 受信直後の次サイクルで ready を 1 に戻す */
   rule raise_ready (!ready_reg);
      ready_reg <= True;
   endrule

   /* 新コマンドがあればいつでも演奏を開始(busy を更新) */
   rule start_play (hasNewCmd);
      $display("%0t play %h", $time, last);
      busy       <= True;
      hasNewCmd  <= False;
   endrule

   /* ここでは 1 サイクルで演奏完了とする簡易例 */
   rule finish_play (busy && ready_reg);
      busy <= False;
   endrule
endmodule
 

挙動

サイクル Producer.valid Consumer.ready 動作
t 1 1 転送成立 → Consumer.ready を 0 へ
t+1 0 0→1 ready が復活。
busy が上がり演奏開始
busy中 1 1 新 valid が来れば上書きし即プリエンプト

このようにreadyを 1 サイクルだけ落とすことで

  • ハンドシェーク単位は厳密に 1 サイクル
  • それでもbusy中にいつでも新コードを受理し、即プリエンプション

が両立します。


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

posted by sakurai on September 2, 2025 #1013

前の記事でChatGPTの回答があたかもalways_ready, always_enable疑似命令を付加すればalways_ready, always_enableとなるように聞こえたので、確認します。

このように、属性だけ変えても中身を正しく書かないとエラーになるのでは?

always_ready / always_enabled を使うときの注意

  1. 属性の意味

    • always_ready : can_fire が常に True と解釈される
    • always_enabled: ルールが発火したら必ずメソッドも実行される
  2. メソッド本体にはガードを書かない

    • if や case で条件分岐するとコンパイルエラーになる
    • 無条件でレジスタへ書き込むだけにする
(* always_ready, always_enabled *)
method Action soundPreempt(Code_t d);
   lastCode  <= d;        // 無条件で最新コードを保持
   hasNewCmd <= True;     // 新コマンド到着フラグ
endmethod
 
  1. 忙しいかどうかの判定は別ルールで行う
rule start_play (!busy && hasNewCmd);
   current    <= lastCode;
   busy       <= True;
   hasNewCmd  <= False;
endrule
  1. 上位モジュールはガード無しで毎サイクル呼んでよい
rule send_cmd;
   sndFsm.soundPreempt(latestCode);   // guard を付けない
endrule

これで busy の間に新しいコードが来ても、旧コードは上書きされて演奏されない。プリエンプションの目的を達成できます。


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


ページ: