Posts Tagged with "Design"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.
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 クラスを通じて抽象化されます。これにより、ゲームロジックと通信ロジックが分離され、コードの可読性と保守性が向上します。


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

Pongと強化学習 (113)

posted by sakurai on July 15, 2025 #997

FPGAとdqn_atari.pyとのインタフェースプロトコルをChatGPTと相談した結果、以下のプロトコルにまとまりました。ChatGPTは最初はjsonフォーマットを提案してきましたが、FPGAでデコードする都合上シンプルなフォーマットとしました。


通信プロトコル

PythonからFPGAへのコマンド

  • SOH = 0x01
  • EOT = 0x04
  • ACK = 0x06
  • NAK = 0x15
  • CMD_RESET = 'R' = 0x52
  • CMD_ACTION = 'A' = 0x41
  1. リセットコマンド:

    • 目的: ゲームを初期状態にリセットし、新しいエピソードを開始する。
    • フォーマット: [SOH, CMD_RESET, データ長(0), EOT]
    • データ長はこのコマンドにおいてペイロードがないため0。
      図%%.1
      図997.1 リセットコマンド
  2. ステップコマンド:

    • 目的: 特定のアクションを実行し、その結果としての新しいゲーム状態を取得する。
    • フォーマット: [SOH, CMD_ACTION, データ長(1), アクションコード, EOT]
    • データ長アクションコード1バイト。
      図%%.2
      図997.2 ステップコマンド

FPGAからPythonへの応答

  1. リセット応答:

    • 目的: リセットが完了したことを確認し、新しいゲームフレームを提供する。
    • フォーマット: [SOH, ACK/NAK, データ長, フレームデータ, EOT]
    • コマンドフォーマットが正常ならACK、異常ならNAKを返す。
    • データ長フレームデータのバイト数。
      図%%.3
      図997.3 リセット応答
  2. ステップ応答:

    • 目的: アクションの結果としての新しいゲーム状態、報酬、およびゲームの継続状態を提供する。
    • フォーマット: [SOH, ACK/NAK, データ長, フレームデータ, 報酬, ゲーム終了フラグ, EOT]
    • コマンドフォーマットが正常ならACK、異常ならNAKを返す。
    • データ長は(フレームデータのバイト数 + 報酬1バイト + ゲーム終了フラグ1バイト)のバイト数。
      図%%.4
      図997.4 ステップ応答

このプロトコルでは、ペイロードの長さを明確にすることで、EOTがペイロード内に現れる問題を解消しています。また、データの一貫性と通信の信頼性を高めるためにペイロードの長さを先頭に加えています。


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

Pongと強化学習 (112)

posted by sakurai on July 14, 2025 #996

ゲームの見栄えが良いので、Space Invadersで強化学習を実装しようと思っていましたが、考えを変えてPongで実装することにしました。その理由はpythonにより記述されたatari-dqnの学習の効果があまり現れなかったためです。

Pongのほうが学習の結果がずっと良かったのですがその学習効果を図996.1に示します。

図%%.1
図996.1 エピソード8と5000の学習効果の比較

画面中左のオレンジパドルが単純コンピュータ制御で、右側のグリーンパドルが強化学習により学習したエージェントです。左画面がエピソード8の段階で0対20で一点も取れないのに対し、右画面のエピソード5000の段階では20対14と単純AIに勝ち越しています。約1日程度でこのくらいの学習効果があります。

なお単純コンピュータ制御は、単純にパドルのY座標をボールと一致させているだけです。ただし完全に一致すると絶対に負けることがなく強化学習ができなくなるため、一度に一ドットしか移動できない制約をいれているようです。従ってかなり強いですが、急激な変化には弱い性質があります。

DQN学習中はロス等のパラメータをサーバーに送信しており、以下のような学習効果に関するチャートを取得することが可能です。

図%%.2
図996.2 各種パラメータのチャート

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

Pongと強化学習 (111)

posted by sakurai on July 8, 2025 #994

コードの先頭の部分の説明です。


プログラムの冒頭にあるインポートリストは、Deep Q-Network (DQN) の実装において使用されるさまざまなライブラリやモジュールを取り込むためのものです。それぞれのインポートがプログラム内でどのような役割を担っているか、詳しく解説します。

基本ライブラリのインポート

  1. argparse: コマンドライン引数を処理するためのモジュール。このモジュールを使ってプログラム実行時に外部から設定値を受け取ることができます。
  2. os: オペレーティングシステムとのインタラクションを行うためのモジュール。ファイルパスの操作や、環境変数へのアクセスなどに使用します。
  3. random: 乱数を生成するためのモジュール。DQNでの探索処理やランダムサンプリングに使用されます。
  4. time: 時間に関連する関数を提供するモジュール。プログラムのパフォーマンス測定や待機処理などに使われることがあります。
  5. strtobool(from distutils.util): 文字列形式の真偽値をPythonのブール型に変換する関数。設定ファイルやコマンドライン引数からの入力を扱う際に便利です。

機械学習・強化学習ライブラリのインポート

  1. gymnasium as gym: OpenAI Gym(現在はGymnasiumとして知られています)の環境を扱うためのライブラリ。強化学習アルゴリズムのトレーニングに必要な標準的なインターフェイスを提供します。
  2. numpy as np: 数値計算を効率的に行うためのライブラリ。配列操作や数学的計算に広く用いられます。
  3. torch: PyTorchライブラリで、ディープラーニングモデルの構築とトレーニングに使用されます。
    • torch.nn: ニューラルネットワークの構築に使用されるクラスや関数を含むモジュール。
    • torch.nn.functional as F: 損失関数や活性化関数など、ニューラルネットワークで頻繁に使用される関数を提供します。
    • torch.optim as optim: 最適化アルゴリズムを提供するモジュール。SGDやAdamなど、ニューラルネットワークの訓練に必要です。
  4. stable_baselines3.common.atari_wrappers: Atariゲーム環境のためのラッパー関数群。ゲームの観測や報酬の前処理を行います(例:ClipRewardEnv, EpisodicLifeEnvなど)。
  5. stable_baselines3.common.buffers: 経験再生バッファ(ReplayBuffer)など、強化学習においてデータを効率的に保存・利用するためのデータ構造を提供します。
  6. torch.utils.tensorboard: TensorBoardで訓練プロセスを可視化するためのユーティリティ。訓練中の損失や精度などのメトリクスをログに記録します。

このインポートリストは、DQNプログラムが多様な機能とモジュールを必要としていることを示しており、強化学習タスクを効果的に実行するための準備が整っています。


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


ページ: