Posts Tagged with "Design"

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

6. Verilogシミュレーション

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

$ make vsim
bsc -verilog -bdir vobj -u -g mkTopDirect -vdir rtl TopDirect.bsv
checking package dependencies
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
205: produce = d
205: consume = d
405: produce = e
405: consume = e
605: produce = a
605: consume = a
805: produce = b
805: consume = b
1005: produce = 6
1005: consume = 6
1205: produce = 8
1205: consume = 8
1405: produce = 2
1405: consume = 2
1605: produce = 9
1605: consume = 9
1805: produce = f
1805: consume = f
=== simulation finished ===
rtl/mkTopDirect.v:115: $finish(1) called at 1815 (1s)

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

$ gtkwave -A vsim.vcd

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

verilogシミュレーションではモジュール階層は保たれており、producerが適当なタイミングで値を変更しますが、consumerは自分のタイミングでそれを受け取ります。両方のタイミングが揃った時がtopで見えているWILL_FIRE_RL_connectです。従って、1$\rightarrow$9$\rightarrow$D$\rightarrow$F$\rightarrow$EというProducerの動作に対して9, Fの取りこぼしが起きているのが確認できます。


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

posted by sakurai on August 27, 2025 #1010

5. 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/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/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
210: consume = d
210: produce = d
410: consume = e
410: produce = e
610: consume = a
610: produce = a
810: consume = b
810: produce = b
1010: consume = 6
1010: produce = 6
1210: consume = 8
1210: produce = 8
1410: consume = 2
1410: produce = 2
1610: consume = 9
1610: produce = 9
1810: consume = f
1810: produce = f
=== simulation finished ===

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

$ gtkwave -A bsim.vcd

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

bsimシミュレーションではtop/producer/consumerのモジュール階層は存在せず、最適化が図られています。受け渡しする信号xは内部信号となるため、使用されていなければ削除されてしまいます。ここではcons_Lastvalとしてオレンジ色にて波形が表示されています。


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

posted by sakurai on August 25, 2025 #1008

4. Makefile

次にMakefileのサンプルを提供します。

########################################
#  Makefile(サブ→トップ→リンク:.v→.v 依存で必ず階層化)
########################################

BSC         := bsc
SIMFLAGS    := -sim
VLOG_FLAGS  := -verilog

# -------- トップとソース ---------------------------------
TOP_MOD   := mkTopDirect
TOP_SRC   := TopDirect.bsv
SRCS      := Producer4.bsv Consumer4.bsv OneStage.bsv $(TOP_SRC)

# -------- サブ(mk付き=モジュール名)---------------------
SUB_MODS  := mkProducer4 mkConsumer4

# -------- 生成物/ディレクトリ ---------------------------
RTL_DIR   := rtl
BSIM_DIR  := bsim
VOBJ_DIR  := vobj

BA        := $(TOP_MOD).ba
SIM_EXE   := $(TOP_MOD).exe
V_EXE     := $(TOP_MOD).vexe

TOP_V     := $(RTL_DIR)/$(TOP_MOD).v                       # rtl/mkTopDirect.v
SUB_V     := $(SUB_MODS:%=$(RTL_DIR)/%.v)                  # rtl/mkProducer4.v など

BVCD      := bsim.vcd
VVCD      := vsim.vcd

# -------- デフォルト -------------------------------------
.PHONY: all
all: $(SIM_EXE) $(V_EXE)

############################################################################
# Bluesim(トップ一発)
############################################################################
$(BSIM_DIR)/%.ba : %.bsv
    @mkdir -p $(BSIM_DIR)
    $(BSC) $(SIMFLAGS) -bdir $(BSIM_DIR) -u -g $(basename $<) $<

$(BA): $(SRCS)
    @mkdir -p $(BSIM_DIR)
    $(BSC) $(SIMFLAGS) -bdir $(BSIM_DIR) -u -g $(TOP_MOD) $(TOP_SRC)

$(SIM_EXE): $(BA)
    @mkdir -p $(BSIM_DIR)
    cd $(BSIM_DIR) && \
        $(BSC) $(SIMFLAGS) -bdir . -e $(TOP_MOD) -o ../$@

.PHONY: bsim
bsim: $(SIM_EXE)
    ./$(SIM_EXE) -V $(BVCD)

.PHONY: bwave
bwave: bsim
    gtkwave -A $(BVCD)

############################################################################
# Verilog RTL(.v→.v 依存で順序を強制し、階層を固定)
############################################################################
# 1) サブ .v を個別に生成(必ず mkX.v を出す)。依存解決のため -u を付与
$(RTL_DIR)/mk%.v: %.bsv
    @mkdir -p $(RTL_DIR) $(VOBJ_DIR)
    $(BSC) $(VLOG_FLAGS) -u -bdir $(VOBJ_DIR) -g mk$* -vdir $(RTL_DIR) $<

# 2) トップ .v は **サブ .v に依存**(= サブ→トップの順序を強制)
#    従来挙動どおり -u を付与(同一 bdir の .bo を参照)
$(TOP_V): $(SUB_V) $(TOP_SRC)
    @mkdir -p $(RTL_DIR) $(VOBJ_DIR)
    $(BSC) $(VLOG_FLAGS) -u -bdir $(VOBJ_DIR) -g $(TOP_MOD) -vdir $(RTL_DIR) $(TOP_SRC)

# 3) vexe はトップとサブを兄弟依存でリンク
$(V_EXE): $(TOP_V) $(SUB_V)
    @mkdir -p $(VOBJ_DIR)
    $(BSC) $(VLOG_FLAGS) -bdir $(VOBJ_DIR) -vsearch $(RTL_DIR) -e $(TOP_MOD) -o $@

# 並列実行時の順序ゆらぎを抑止(任意だが推奨)
.NOTPARALLEL: $(TOP_V) $(SUB_V) vsim vwave

.PHONY: vsim
vsim: $(V_EXE)
    ./$(V_EXE) +bscvcd=$(VVCD) +bscdumpvars

.PHONY: vwave
vwave: vsim
    gtkwave -A $(VVCD)

############################################################################
# クリーン
############################################################################
.PHONY: clean
clean:
    rm -f *.ba *.bo *.o *.exe *.vexe *.cxx *.h *.vcd core *~ *.so
    rm -f $(SUB_V) $(TOP_V)
    rm -rf $(RTL_DIR) $(BSIM_DIR) $(VOBJ_DIR)

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

posted by sakurai on August 8, 2025 #1007

3. トップモジュール

次にトップモジュールのサンプルを提供します。

package TopDirect;
import Producer4::*;
import Consumer4::*;

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

   /* 10 回受信したらシミュレーション終了 */
   Reg#(UInt#(5)) recvCnt <- mkReg(0);

   rule connect;
      UInt#(4) x <- prod.produce;
      cons.consume(x);                  // 直ちに渡す
      $display("%0t: produce = %h", $time, x);
      recvCnt <= recvCnt + 1;
   endrule

   /* 受信 10 回で $finish */
   rule finish (recvCnt == 10);
      $display("=== simulation finished ===");
      $finish;
   endrule
endmodule
endpackage

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

posted by sakurai on August 7, 2025 #1006

2. Consumer

次にコンシューマのサンプルを提供します。

package Consumer4;

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

module mkConsumer4 (Consumer4Ifc);

   Reg#(UInt#(8)) cnt  <- mkReg(0);
   Reg#(Bool)     busy <- mkReg(False);
   Reg#(UInt#(4)) lastVal <- mkRegU;   // 取り込み結果を保持

   rule timer (busy);
      if (cnt == 14) begin
         cnt  <= 0;
         busy <= False;
      end
      else
         cnt <= cnt + 1;
   endrule

   method Action consume(UInt#(4) x) if (!busy);
      lastVal <= x;                   // EN=1 サイクルでラッチ
      $display("%0t: consume = %h", $time, x);
      busy <= True;
   endmethod
endmodule
endpackage


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

posted by sakurai on August 5, 2025 #1004

それではダイレクト制御(途中にバッファを置かずに直接2線ハンドシェイクを行う)のサンプルを作成してください。

1. Producer

それではプロデューサのサンプルを提供します。

/* Producer4.bsv : 4bit データを 10 サイクルごとに出すだけ */
package Producer4;
import LFSR :: *;

interface Producer4Ifc;
   method ActionValue#(UInt#(4)) produce();
endinterface

module mkProducer4 (Producer4Ifc);
   Reg#(UInt#(7)) cnt <- mkReg(0);   // 0–
   LFSR#(Bit#(4)) lfsr <- mkLFSR_4;
   
   rule tick;
      cnt <= (cnt==9)?0:cnt+1;
   endrule
   rule random (cnt == 4);
      lfsr.next();              // ② LFSR を 1-step 進める
   endrule

   method ActionValue#(UInt#(4)) produce() if (cnt==0);
      Bit#(4) v = lfsr.value;   // ① 今の値を読む
      return unpack(v);         // ③ 読み取った値を返す
   endmethod
endmodule
endpackage


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

posted by sakurai on August 4, 2025 #1003

それでは実際のソース、GameFSM.bsv, OneStage.bsv, SoundFSM.bsvを提供します。これらはそれぞれゲームFSM、中間バッファ、サウンドFSMのソースです。両FSMを直結できるかどうか調査して、できるのであれば、ソースを修正してください。


目的

現在の設計では、GameFSMとSoundFSMの間にOneStageという中間モジュールが存在しています。これはFIFOではなく、コマンド受け渡しのbusyフラグを持つ調停回路です。 これを 完全に排除し、BSV の Ready/Enable による自動ハンドシェークだけで同期をとる構成に書き換えます。

Before / After 構成

現在(Before)

GameFSM ──► OneStage ──► SoundFSM

  • 実データは GameFSM 側の inscode レジスタに保持
  • OneStage は empty/wr_en/rd_en による制御信号だけを調停している

変更後(After)

GameFSM ──► SoundFSM × 4ch

  • 余計な 1 cycle が削減
  • BSC が各 sound() 呼び出しに Ready/Enable を自動合成

修正方針(概要)

修正対象ファイル 修正内容
OneStage.bsv 完全に削除 (または mkTop から除外)
GameFSM.bsv OneStage 接続を削除し、Vector#(4, FSM_ifc) を受けるよう改修
SoundFSM.bsv 変更不要(既にmethod sound(Code_t)をエクスポート)
mkTop.bsv Vector を生成し、そのまま GameFSM へ 1引数で渡す

修正内容の詳細

1. OneStage.bsv を削除

このファイルは完全に不要になるため

2. GameFSM.bsv の修正

(A) インターフェースを Vector 版へ

interface GameFSM_ifc;
  method Action step();
  interface Vector#(4, FSM_ifc#(Code_t)) soundCh; // ★ 追加
endinterface
-module invader_move(GameFSM_ifc /*AUTOARG*/);
+module invader_move #(Vector#(4, FSM_ifc#(Code_t)) ch) (GameFSM_ifc);

(B) 不要レジスタ削除

- Wire#(Bool)       fempty <- mkWire;
- Reg#(Bool)        fwen   <- mkReg(False);
- Reg#(UInt#(4))    inscode<- mkRegU;

(C) sound() ではなく 1 行呼び出し

rule send_sound;
  UInt#(2) idx = truncate(nextCode);   // ch[0]~[3] を選択
  ch[idx].sound(nextCode);            // BSC が ready/enable を自動合成
  nextCode <= nextCode + 1;
endrule

3. SoundFSM.bsv の変更は不要

既に以下のようにインターフェースが定義されています:

interface FSM_ifc;
   method Action sound(Code_t code);
endinterface

module mkSoundFSM0(FSM_ifc);

4. トップモジュール(mkTop.bsv)の修正

module mkTop ();
  Vector#(4, FSM_ifc#(Code_t)) sc <- replicateM(mkSoundFSM0); // または個別生成
  sc[1] <- mkSoundFSM1();
  sc[2] <- mkSoundFSM2();
  sc[3] <- mkSoundFSM3();

  GameFSM_ifc gfsm <- mkGameFSM(sc);  // ← 1 引数で OK
endmodule

メリット

  • OneStage 削除 → 余計な 1 cycle 遅延を解消
  • 自前ハンドシェーク (fempty, fwen) ロジック消失 → 記述量削減
  • 音チャンネルが Vector に集約 → 拡張時もTopと呼び出し構文は不変
  • Ready/Enable は BSC が自動生成 → 衝突・競合を静的解析で保証

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

posted by sakurai on July 31, 2025 #1002

まず、ChatGPTに、BSVの2線ハンドシェイクアルゴリズムを尋ねてみます。

bluespecのモジュールでは自動的に2線方式のハンドシェークが行われるが、そのアルゴリズムを解説してください。


BSVにおける2線ハンドシェークのアルゴリズム

Bluespec SystemVerilog(BSV)では、モジュール間やルール間の通信において、Ready/Enable方式(2線ハンドシェーク)が自動的に挿入されます。

本記事では、BSVのハンドシェークの動作アルゴリズムをまとめます。

用語定義

  • caller:メソッドを呼び出す側。通常はルールや上位モジュール。
  • callee:メソッドを提供する側(FIFO, Reg, モジュール等)。

信号の方向:

  • readyは callee から caller への出力(callee が「受け入れ可能」かを知らせる)
  • enableは caller から callee への入力(caller が「今起動する」意思を伝える)

1. ハンドシェークの基本モデル

各メソッド呼び出し(またはルール起動)には、以下の2本の信号が対応します。

信号名 BSV上の名称 役割
Ready can_fire() callee が実行可能であることを示す純組合せフラグ
Enable will_fire caller が実際に起動する1サイクルパルス
  • FIFOなら!full、Regなら常にTrueなど、内部状態から算出される組合せ信号がready
  • enablecan_fireとスケジューラの許可を元に生成され、callee の*_enポートに入力されます。

2. 合成フロー(ルール単位)

2.1 各メソッドのcan_fire取得

rule foo when (guard);
   let v  <- A.method1;   // メソッド呼び出し①
       w  <- B.method2;   // メソッド呼び出し②
endrule

実際は BSC が.can_fireを各メソッドに自動付与して評価します。

2.2 can_fire_rule(= 全 Ready の AND)

複数メソッドがある場合、各 callee の Ready を AND し、ガード条件と組み合わせて以下のように合成されます:

wire can_fire_rule = guard && A_ready && B_ready;

2.3 スケジューラ仲裁 (scheduler_grant)

競合する他ルールとの排他処理・優先度制御が組み合わさり、fire生成条件となるgrant_ruleが作られます:

wire grant_rule = can_fire_rule & scheduler_logic(...);

2.4 will_fire(= 発火パルス)

wire will_fire = grant_rule;  // 1サイクルだけ true になる

これが Enableパルスとして callee に伝わります。

2.5 Callee への配線(扇形ファンアウト)

assign A_en = will_fire;
assign B_en = will_fire;

caller はfireをすべての callee に配線します。

2.6 レジスタ書き込みの en 端子にも使用

同じwill_fireパルスが、そのルール内で行われるレジスタ更新の*_en信号にも使われます。

【構造図イメージ】

Ready ─────┐
       ├──> AND (+guard) ──> fire──┬──> EN_A
Ready ─────┘               └──> EN_B

3. 複数ルール/メソッドの競合処理

  • 同一リソースを書き換えるルール同士は、静的スケジューラにより優先順位が付けられ、同時発火しないように制御されます。
  • 同一ルール内のメソッド呼び出しは、すべて同時にfireする。
  • 非競合のルールは並列にwill_fireが生成され、同時実行可能です。

例:レジスタの write-data MUX / enable OR

assign reg_din =
   (rule2_will_fire) ? data2 :
   (rule1_will_fire) ? data1 :
                       reg_q;

assign reg_en = rule1_will_fire | rule2_will_fire | ... ;

4. まとめ:2線ハンドシェークの要点

  • Ready(can_fire):callee が「今可能か」を知らせる組合せ出力
  • Enable(will_fire):caller が実行する1サイクルパルス(fire
  • 論理合成:Ready と guard を AND、スケジューラで仲裁 → fireを生成
  • 配線:fireはすべての callee のenableに扇形配線される
  • AND 回路は自動挿入される:ユーザーが手で書く必要はないが、論理上は存在する
  • ユーザーの記述負荷は低い:can_fire 以外の信号論理はすべて BSC が生成する

備考

このアルゴリズムは、BSC コンパイラが提供する強力なスケジューラと構造的合成の基盤であり、明示的な回路記述が不要で高い信頼性と安全性を確保します。


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

posted by sakurai on July 30, 2025 #1001

外部投稿記事において、2つのFSM(Finite State Machine)の調停を図るコマンドバッファOneStageを設計しました。このコマンドバッファもFSMであり、GameFSMからくるサウンドコマンドを一時保持し、SoundFSMがそれを引き取り当該サウンドを鳴らすというものです。実際には中間バッファは省略し、制御のみでコマンドバッファ制御を実行しています。

図%%.1
図1001.1 GameFSMとSoundFSMの調停

実はBSVはこのような調停の仕組みを自動で持っていて、bscはそれを自動生成することができます。ChatGPTの助けを借りて、これを実装します。

また、中間バッファとしてFIFOが必要かどうかも助言を受けます。以前の記事中にあるとおり、FIFOは付ければ良いというものではなく、付ければ付けるほど取りこぼしは減るもののレイテンシーが大きくなります。

さらに、取りこぼしを防ぐため、SoundFSMがbusyとなることを避ける目的から、基本的には全てのコマンドをプリエンプティブにしています。これは割り込み可能という意味で、サウンド演奏中にコマンドが入ると演奏を中断し、新たに演奏を開始するものです。


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

Pongと強化学習 (114)

posted by sakurai on July 16, 2025 #998

このプロトコルを用いてpythonのCustomAtariEnvと統合します。


CustomAtariEnv クラスを Device クラスを用いてシリアル通信の詳細を抽象化し、インタフェースをシンプルにするために再設計します。この方法により、CustomAtariEnv クラスはゲーム環境のロジックに集中し、シリアル通信の詳細は Device クラスが担当します。

Device クラスの設計

Device クラスはシリアル通信を担当し、コマンドの送受信を簡単に行えるように設計します。

    import serial
    
    class Device:
        def __init__(self, port):
            self.serial = serial.Serial(port, baudrate=115200, timeout=1)
        
        def send(self, command, payload=None):
            message = bytearray([SOH])
            message.append(ord(command))
            if payload is not None:
                message.extend(payload)
            message.append(EOT)
            self.serial.write(message)
        
        def receive(self):
            response = bytearray()
            while True:
                byte = self.serial.read(1)
                if byte:
                    response.append(byte[0])
                    if byte[0] == EOT:
                        break
            return response
        
        def close(self):
            self.serial.close()

CustomAtariEnv クラスの再設計

CustomAtariEnv クラスは Device クラスを使用して通信を行います。これにより、CustomAtariEnv はシリアル通信の詳細から切り離されます。

    import gym
    from gym import spaces
    
    class CustomAtariEnv(gym.Env):
        def __init__(self, device):
            super(CustomAtariEnv, self).__init__()
            self.device = device
            self.action_space = spaces.Discrete(4)  # 仮に4つのアクション
            self.observation_space = spaces.Box(low=0, high=255, shape=(256, 160, 3), dtype=np.uint8)  # 仮の解像度
    
        def reset(self):
            self.device.send(CMD_RESET)
            return self.device.receive()
    
        def step(self, action):
            action_command = self.get_action_command(action)
            self.device.send(CMD_ACTION, [action_command])
            return self.device.receive()
    
        def get_action_command(self, action):
            # Map the action to the actual command
            if action == 0:
                return ACTION_MOVE_LEFT
            elif action == 1:
                return ACTION_MOVE_RIGHT
            elif action == 2:
                return ACTION_FIRE
            else:
                raise ValueError("Invalid action")
        
        def close(self):
            self.device.close()

使用例

    device = Device('/dev/ttyUSB0')
    env = CustomAtariEnv(device)
    
    initial_state = env.reset()
    action = 1  # 仮に右移動
    new_state = env.step(action)
    
    env.close()

この再設計により、CustomAtariEnv クラスはシリアル通信の詳細から独立し、デバイスとの通信は Device クラスを通じて抽象化されます。これにより、ゲームロジックと通信ロジックが分離され、コードの可読性と保守性が向上します。


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


ページ: