Posts Tagged with "Design"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.
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は付ければ良いというものではなく、付ければ付けるほど取りこぼしは減るもののレイテンシーが大きくなります。従ってサウンドシステムのようなリアルタイムシステムには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プログラムが多様な機能とモジュールを必要としていることを示しており、強化学習タスクを効果的に実行するための準備が整っています。


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

Pongと強化学習 (110)

posted by sakurai on June 27, 2025 #993

コードの続きの説明です。


このコードブロックは、訓練されたモデルと関連データをオンラインのリポジトリ、具体的にはHugging Face Hubへアップロードするための処理を行っています。Hugging Face Hubは、機械学習モデルを共有、探索、再利用するためのプラットフォームです。以下に、このプロセスの詳細を説明します。

コードの詳細解説

1. アップロード条件の確認:
           if args.upload_model:

この行は、モデルをアップロードするかどうかをコントロールするフラグ args.upload_model をチェックしています。このフラグが真(True)の場合のみ、モデルと関連データのアップロードが実行されます。

2. Hugging Faceのユーティリティのインポート:
           from huggingface import push_to_hub

Hugging FaceのAPIを利用するための関数 push_to_hub をインポートしています。この関数は、モデルやその他のアーティファクトをHugging Face Hubにプッシュするために使用されます。

3. リポジトリ名とIDの設定:
           repo_name = f"{args.exp_name}"
           repo_id = f"{args.hf_entity}/{repo_name}" if args.hf_entity else repo_name
  • repo_name: リポジトリ名は実験名(args.exp_name)を基に設定されています。
  • repo_id: 完全なリポジトリIDを生成します。ユーザーまたは組織のエンティティ名が args.hf_entity に設定されている場合、それを含めた形式でIDが構成されます。
4. モデルとデータのアップロード:
           push_to_hub(args, episodic_returns, repo_id, "DQN", f"runs/{run_name}", f"videos/{run_name}-eval")
  • args: 実行時の設定やパラメータが含まれる引数オブジェクト。
  • episodic_returns: 評価フェーズで得られたエピソードごとの報酬リスト。
  • repo_id: リポジトリのID。
  • "DQN": 使用されたアルゴリズムの名前。
  • f"runs/{run_name}": 訓練されたモデルファイルが保存されているディレクトリのパス。
  • f"videos/{run_name}-eval": 評価フェーズのビデオやその他のメディアファイルが保存されているディレクトリのパス。

役割と重要性

このステップは、研究者や開発者が自身のモデルを広く共有するために非常に重要です。Hugging Face Hubにモデルを公開することで、他の研究者や開発者がアクセスし、利用することが可能となります。これにより、コラボレーションが促進され、モデルの再利用や改良が容易になります。また、学習プロセスや評価結果を透明に共有することで、信頼性の高い機械学習コミュニティの構築に貢献します。


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

Pongと強化学習 (109)

posted by sakurai on June 24, 2025 #991

コードの続きの説明です。


このコードブロックでは、評価フェーズで得られた各エピソードの結果(報酬の合計)を記録しています。この情報は、トレーニングされたモデルの性能を可視化し、分析するために使用されます。以下に、このプロセスについて詳しく説明します。

コードの詳細解説

1. エピソードごとの報酬の列挙:
           for idx, episodic_return in enumerate(episodic_returns):

この行では、evaluate 関数から返された episodic_returns リストをループしています。このリストには、評価フェーズの各エピソードで得られた累積報酬が含まれています。enumerate 関数を使用することで、エピソードのインデックス (idx) とそのエピソードでの報酬 (episodic_return) を取得しています。

2. 報酬の記録:
           writer.add_scalar("eval/episodic_return", episodic_return, idx)

writer.add_scalar 関数を使用して、TensorBoardなどの視覚化ツールに報酬を記録しています。ここで、"eval/episodic_return" は記録するデータの名前(タグ)、episodic_return はそのエピソードでの累積報酬、idx はエピソードのインデックスを指します。この方法で記録されたデータは、学習の監視および後の分析のために視覚的に確認できるようになります。

役割と重要性

このステップは、モデルの評価結果を詳細に記録し、モデルの性能を定量的に追跡するために重要です。各エピソードの報酬を記録することで、モデルが一貫して高いパフォーマンスを達成しているか、または一部のエピソードでのみ良好な結果が得られているかを識別できます。これにより、モデルの堅牢性や特定の状況における弱点を明らかにすることができ、さらなる改善や調整のための具体的なデータが提供されます。

このようなフィードバックは、モデルの調整や、将来的なトレーニング戦略の計画に不可欠です。また、実際のアプリケーションや異なる環境でのモデルの振る舞いを予測するための重要な手がかりを提供します。


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

Pongと強化学習 (108)

posted by sakurai on June 12, 2025 #986

コードの続きの説明です。


このコードブロックは、訓練されたDeep Q-Network (DQN) モデルを評価するプロセスを実行する部分です。ここで、evaluate 関数を使用して、保存されたモデルを特定の環境において複数のエピソードにわたってテストし、そのパフォーマンスを測定しています。以下、コードの各部分について詳しく説明します。

コードの詳細解説

1. 評価関数のインポート:
           from dqn_eval import evaluate

この行では、DQNモデルを評価するための関数 evaluate をインポートしています。この関数は通常、モデルのパフォーマンスをテストするために設計されたモジュールに定義されています。

2. モデル評価の実行:
           episodic_returns = evaluate(
               model_path,
               make_env,
               args.env_id,
               eval_episode=10,
               run_name=f"{run_name}-eval",
               Model=QNetwork,
               device=device,
               epsilon=0.05,
           )
  • model_path: 評価するモデルのファイルパス。
  • make_env: 環境を生成する関数。テスト時に使用する環境をセットアップするために必要です。
  • args.env_id: 評価に使用する環境のID。これは具体的なゲームやタスクを指定するために使用されます。
  • eval_episode: 評価を行うエピソードの数。ここでは10エピソードでモデルを評価しています。
  • run_name: 評価のための実行名。ログや結果の保存に使われる名前を指定しています。
  • Model: 使用するモデルクラス。ここでは QNetwork を指定しています。
  • device: 評価を行うデバイス(CPUまたはGPU)。
  • epsilon: 探索率。評価時には低い探索率(ここでは0.05)を設定することが一般的です。

役割と重要性

この評価プロセスは、訓練されたモデルが実際の環境でどれだけ効果的に機能するかを測定するために重要です。訓練中に得られた知見が新しい、未見の状況にどれだけ一般化できるかを確かめるために、通常、訓練環境とは異なる環境や設定で評価を行います。evaluate 関数は、特定のパラメータ(探索率など)の下でモデルの振る舞いを試験し、得られた報酬(episodic returns)を返します。これにより、モデルの性能を定量的に評価し、さらなる改善の方向性を定めることができます。


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


ページ: