Posts Tagged with "BSV"

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

Pongと強化学習 (90)

posted by sakurai on March 21, 2025 #963

TbのBSVソースを示します。

Tb.bsv:

    import StmtFSM::*;
    import DumpFSM::*;
    import Uart::*;
    import RegFile::*;
    import Connectable::*;
    
    typedef Bit#(16) Addr;
    typedef Bit#(4) Data;
    
    `define STX 8'h02
    
    (* synthesize *)
    module mkTb();
       RegFile#(Addr, Data) vram <- mkRegFileLoad("data.hex", 0, 256*256-1);
       Reg#(Bool) cwait <- mkReg(False);
       Reg#(Bool) game <- mkReg(True);
       Reg#(Bool) tic <- mkReg(False);
         
       DumpFSM_ifc dump <- mkDumpFSM();
       Uart_ifc uart <- mkUart();
       
       mkConnection(cwait, dump.cwaitm);
       mkConnection(dump.datam, vram.sub(dump.addrm));
       mkConnection(tic, dump.c60Hz);
       mkConnection(game, dump.mode);
       mkConnection(uart.sout, dump.sin);
       mkConnection(uart.sin, dump.sout);
    
       Stmt test =
       seq
          // Game Mode: ticをスルーする
          game <= True;
          cwait <= False;
          repeat(100) noAction;
          // 非Game Mode: GameFSMに停止要求を出して停止したらダンプする
          game <= False;
          uart.write(8'h31);
          uart.write(8'h32);
          uart.write(8'h33);
          uart.write(`STX); // ここからダンプスタート
          uart.write(8'h31);
          uart.write(8'h32);
          uart.write(8'h33);
          cwait <= True; // GameFSMが停止したシミュレーション
          repeat(20) uart.write(8'h31);
          repeat(100) noAction;
          game <= True;
          await(!dump.selm()); // DumpFSMが動作完了し、SELをOFF
          repeat(100) noAction;
          $finish;
       endseq;
    
       mkAutoFSM(test);
    
        // クロックのトグルをカウントするレジスタ
        Reg#(Bit#(4)) counter <- mkReg(0);
        // カウンターが特定の値に達したらクロックをトグル
        rule toggleClock;
            if (counter == 5) begin // ここで分周比を調整
                tic <= !tic;
                counter <= 0;
            end else begin
                counter <= counter + 1;
            end
        endrule
        
    endmodule

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

Pongと強化学習 (89)

posted by sakurai on March 20, 2025 #962

BSVにより、DumpFSMを改造します。従来のDumpDSMの仕様は、

  • SW等の外部指示により、GameFSMにウエイト要求を出す
  • GameFSMはSyncWait時にウエイト信号(停止中信号)を出力する
  • DumpFSMは停止中信号を確認し、SELをTrueとする
  • VRAMの内容をUARTで出力する
  • 完了したらSELとウエイト要求をFalseにする

という動作でした。今回改造するポイントは外部指示ではなく、強化学習ソフトウエアの指示により画面ダンプを実施する点です。そのために従来送信部のみであったUARTを受信部も作成します。

  • SW等の外部指示により、GameモードがFalseになる
  • シリアルデータがSTXを指示したらGameFSMにwait要求を出す
  • GameFSMはSyncWait時にウエイト信号(停止中信号)を出力する
  • DumpFSMは停止中信号を確認し、SELをTrueとする
  • SELがTrueの間は60Hz信号を遮断する。これによりGameFSMが進むことは無い
  • VRAMの内容をUARTで出力する
  • 完了したらSELとウエイト要求をFalseにする。
  • GameモードがTrueになったら60Hz信号を再開する。

図962.1に構造図を示します。DumpFSMモジュールには送信と受信のUARTを配置し、それをテストするテストベンチ内にも同じUARTを対向して配置します。

図%%.1
図962.1 ブロック図

DumpFSM.bsv

    import StmtFSM::*;
    import Uart::*;
    import Connectable::*;
    
    typedef Bit#(16) Addr;
    typedef Bit#(4) Data;
    typedef Bit#(8) Byte;
    
    `define STX 8'h02
    
    interface DumpFSM_ifc;
       (* prefix="" *)
       method Action datam(Data newdata); // input
       (* prefix="" *)
       method Action cwaitm(Bool newcwait); // input
       method Bool sreqm(); // output
       method Addr addrm(); // output
       method Bool selm(); // output
       method Bit#(1) sout(); // serial output
       (* prefix="" *)
       method Action sin(Bit#(1) nidata); // serial input
       (* prefix="" *)
       method Action c60Hz(Bool newtic); // input
       method Bool ticfunction(); // output
       (* prefix="" *)
       method Action mode(Bool newgame); // input
    endinterface
    
    (* synthesize, always_ready="sreqm, addrm, selm, sout, ticfunction",
                   always_enabled="datam, cwaitm, sin, c60Hz, mode" *)
    module mkDumpFSM(DumpFSM_ifc);
    
       Reg#(Addr) addr <- mkReg(0);
       Reg#(Data) data <- mkRegU;
       Reg#(Byte) byteData <- mkRegU;
       Reg#(UInt#(9)) x <- mkRegU;
       Reg#(UInt#(9)) y <- mkRegU;
       Reg#(UInt#(4)) i <- mkRegU;
       Reg#(Bool) cwait <- mkReg(False);
       Reg#(Bool) sreq <- mkReg(False);
       Reg#(Bool) sel <- mkReg(False);
       Reg#(Bool) tic <- mkReg(False);
       Reg#(Bool) game <- mkReg(True);
       
       Uart_ifc uart <- mkUart();
    
       function Stmt nibbleOut(Data nibble);
          return (seq
             byteData <= extend(nibble) + ((nibble >= 10) ? (-10 + 8'h61) : 8'h30);
             uart.write(byteData);
             $write("%c", byteData);
          endseq);
       endfunction: nibbleOut
    
       Stmt test = seq
           while (True) seq
               await(!game); // Gameでない場合、トリガが来たら吸出し
               await(uart.read() == `STX);
               sreq <= True; // 描画マスタ停止要求を出す
               await(cwait); // 描画マスタ停止したら、
               sel <= True; // ダンプ開始
               for (i <= 1; i <= 4; i <= i + 1) seq
                   uart.write(8'h38); // start of header '8888' -> remove
                   $display("8");
               endseq
               addr <= 0;
               for (y <= 0; y <= 2/* 255 */; y <= y + 1) seq
                   for (x <= 0; x <= 255; x <= x + 1) seq
    //                 $display("%3d, %3d", x, y);
    //                 $display(" %04x ", addr);
                       action
                          addr <= addr + 1;
                       endaction
                       nibbleOut(data);
                   endseq // for x
                   uart.write(8'h0a); // LF
               endseq // for y
               await (uart.done()); // 送信終了待ち
               action
                   sreq <= False; // 要求OFF
                   sel <= False; // 選択OFF
               endaction
          endseq // while
       endseq; // Stmt
    
       mkAutoFSM(test);
    
       method Bool sreqm(); // output
          return sreq;
       endmethod
       method Addr addrm(); // output
          return addr;
       endmethod
       method Action datam(Data newdata); // input
          data <= newdata;
       endmethod
       method Action cwaitm(Bool newcwait); // input
          cwait <= newcwait;
       endmethod
       method Bool selm(); // output
          return sel;
       endmethod
       method Bit#(1) sout(); // serial output
          return uart.sout();
       endmethod
       method Action sin(Bit#(1) nidata); // serial input
          action
             uart.sin(nidata);
          endaction
       endmethod
       method Action c60Hz(Bool newtic); // input
          if (game && !sel) action
             tic <= newtic;
          endaction
       endmethod
       method Bool ticfunction; // output
          return tic;
       endmethod
       method Action mode(Bool newgame); // input
          game <= newgame;
       endmethod
    
    endmodule

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

Pongと強化学習 (78)

posted by sakurai on November 22, 2024 #899

このコードにReLUを組み込むにはどうするのかを聞いてみます。


ReLU(Rectified Linear Unit)関数は、一般的にニューラルネットワークの活性化関数として使用されます。ReLU関数は入力が0以下の場合は0を出力し、入力が0より大きい場合はその入力をそのまま出力します。ハードウェア(特にBSVでの実装)においてReLUを組み込む場合、各計算ステップの後にこの単純な条件チェックを適用することになります。

以下の例では、畳み込み計算の結果に対してReLUを適用する方法を示します。ここでは、畳み込み演算の結果を格納する前にReLU関数を適用しています。

package ConvolutionFSMwithReLU;
import StmtFSM::*;

interface Conv2D_IF;
    method Action start();
    method ActionValue#(Bit#(8)) getResult(Int#(32) x, Int#(32) y);
endinterface

module mkConvolutionFSMwithReLU#(parameter Int#(32) width = 84,
                                  parameter Int#(32) height = 84,
                                  parameter Int#(32) kWidth = 8,
                                  parameter Int#(32) kHeight = 8)(Conv2D_IF);
    // ここで入力行列、カーネル行列、出力行列を定義
    Reg#(Bit#(8)) inputMatrix[0:width-1][0:height-1];
    Reg#(Bit#(8)) kernelMatrix[0:kWidth-1][0:kHeight-1];
    Reg#(Bit#(8)) resultMatrix[0:width-kWidth][0:height-kHeight];
    Reg#(Bool) startFlag <- mkReg(False);

    // ReLU関数の定義
    function Bit#(8) ReLU(Bit#(32) x);
        return (x > 0) ? truncate(x) : 0;
    endfunction

    // 畳み込み計算のFSM
    Stmt main = seq
        // x, y のループ
        for (x <= 0; x < width - kWidth + 1; x <= x + 1) seq
            for (y <= 0; y < height - kHeight + 1; y <= y + 1) seq
                // 畳み込み計算
                action
                    Bit#(32) sum = 0;
                    for (Int#(32) dx = 0; dx < kWidth; dx = dx + 1) begin
                        for (Int#(32) dy = 0; dy < kHeight; dy = dy + 1) begin
                            sum = sum + (inputMatrix[x+dx][y+dy] * kernelMatrix[dx][dy]);
                        end
                    end
                    resultMatrix[x][y] = ReLU(sum); // 結果にReLUを適用して格納
                endaction
            endseq
        endseq
    endseq;
    
    mkAutoFSM(main);

    method Action start();
        startFlag <= True;
    endmethod
    
    method ActionValue#(Bit#(8)) getResult(Int#(32) x, Int#(32) y);
        return resultMatrix[x][y];
    endmethod

endmodule
endpackage

この例では、ReLU関数を定義し、畳み込み計算の結果に適用してから結果をresultMatrixに格納しています。この方法により、畳み込み後の特徴マップに非線形性を導入し、ニューラルネットワークの表現力を高めることができます。ただし、ReLU関数の出力ビット幅に注意が必要です。ここでは出力をBit#(8)にトランケートしていますが、実際のアプリケーションに応じて適切なビット幅を選択する必要があります。


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

Pongと強化学習 (77)

posted by sakurai on November 21, 2024 #898

2重のfor loopはAutoFSMを用いてStmt文で実装するとステートマシンに合成されるので、それを示唆します。


Bluespec SystemVerilog (BSV) で StmtFSM ライブラリを使用して、畳み込み演算を行うための自動有限状態機械 (FSM) を実装する例を示します。StmtFSM を使用すると、for ループや条件分岐などを含む複雑な制御フローを簡単に記述でき、これらが複数のクロックサイクルにわたって自動的に実行されるようにすることができます。

以下の例では、畳み込み計算を行うために、2重の for ループを使用しています。この計算は、入力行列とカーネル行列上で畳み込みを実行し、出力行列に結果を格納します。

package ConvolutionFSM;
import StmtFSM::*;

interface Conv2D_IF;
    method Action start();
    method ActionValue#(Bit#(8)) getResult(Int#(32) x, Int#(32) y);
endinterface

module mkConvolutionFSM#(parameter Int#(32) width = 84,
                         parameter Int#(32) height = 84,
                         parameter Int#(32) kWidth = 8,
                         parameter Int#(32) kHeight = 8)(Conv2D_IF);
    // ここで入力行列、カーネル行列、出力行列を定義
    Reg#(Bit#(8)) inputMatrix[0:width-1][0:height-1];
    Reg#(Bit#(8)) kernelMatrix[0:kWidth-1][0:kHeight-1];
    Reg#(Bit#(8)) resultMatrix[0:width-kWidth][0:height-kHeight];
    Reg#(Bool) startFlag <- mkReg(False);

    // 畳み込み計算のFSM
    Stmt main = seq
        // x, y のループ
        for (x <= 0; x < width - kWidth + 1; x <= x + 1) seq
            for (y <= 0; y < height - kHeight + 1; y <= y + 1) seq
                // 畳み込み計算
                action
                    Bit#(32) sum = 0;
                    for (Int#(32) dx = 0; dx < kWidth; dx = dx + 1) begin
                        for (Int#(32) dy = 0; dy < kHeight; dy = dy + 1) begin
                            sum = sum + (inputMatrix[x+dx][y+dy] * kernelMatrix[dx][dy]);
                        end
                    end
                    resultMatrix[x][y] = truncate(sum); // 結果を格納
                endaction
            endseq
        endseq
    endseq;
    
    mkAutoFSM(main);

    method Action start();
        startFlag <= True;
    endmethod
    
    method ActionValue#(Bit#(8)) getResult(Int#(32) x, Int#(32) y);
        return resultMatrix[x][y];
    endmethod

endmodule
endpackage

この例では、畳み込み計算を実行するFSMを mkAutoFSM(main); を用いて生成しています。for ループ内で行列の各要素に対して畳み込み計算を行い、結果を resultMatrix に格納しています。

このコードは概念的なものであり、実際のプロジェクトにそのまま適用する前には、入力行列とカーネル行列をどのように初期化・更新するか、出力行列の結果をどのように利用するかなど、さらに多くの詳細を考慮する必要があります。また、StmtFSM ライブラリを使用するには、Bluespecの環境にそれが含まれていることを確認する必要があります。


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

Pongと強化学習 (75)

posted by sakurai on November 18, 2024 #896

nn.Conv2d(4, 32, 8, stride=4)に相当するBSVコードを要求してみます。


Bluespec SystemVerilog (BSV)は、ハードウェア記述言語(HDL)であり、主にデジタル回路やシステムの設計に使われます。ニューラルネットワークのような複雑なアルゴリズムをBSVで直接記述するのは一般的ではありませんが、ハードウェアアクセラレーションのためのカスタムコンポーネントを設計する際に用いられることがあります。

nn.Conv2d(4, 32, 8, stride=4)に相当する機能をBSVで実装するには、畳み込み演算を行うモジュールを設計する必要があります。これには、入力データのロード、カーネル(フィルター)の適用、ストライドに従ったサンプリング、出力データの生成など、複数のステップが含まれます。

以下に、畳み込み演算の概念的なBSV実装の概要を示します。これは具体的なコードではなく、畳み込み演算を実装する際のアプローチを説明したものです。実際のプロジェクトでは、性能要件、使用するハードウェアリソース、並列性のレベルなどに応じて設計を調整する必要があります。

// 畳み込みモジュールの概念的な定義
module mkConvolution#(parameter DATA_WIDTH = 8,  // 入力データおよびカーネルのビット幅
                      parameter KERNEL_SIZE = 8,  // カーネルのサイズ (ここでは8x8)
                      parameter STRIDE = 4,      // ストライドのサイズ
                      parameter IN_CHANNELS = 4,  // 入力チャネル数
                      parameter OUT_CHANNELS = 32) // 出力チャネル数
                     (ConvolutionInterface);
    // モジュールの内部実装
    // 入力データのロード、カーネルの適用、ストライドに基づくサンプリング、
    // 出力データの生成などの処理を実装する

    // 入力データおよびカーネルのメモリ管理、畳み込み計算、結果の格納など
    // 必要なレジスタやメモリの宣言

    // 畳み込み演算の実行ロジック
    // forループや条件分岐を使ったデータの処理、計算の実行

endmodule

この概要から、畳み込み演算をBSVで実装するには、多くの詳細を考慮し、ハードウェアに適した設計を行う必要があります。また、高い性能を得るためには、データのフェッチ、計算の並列化、メモリアクセスの最適化など、ハードウェア設計の専門知識が必要となります。

BSVでニューラルネットワークや畳み込み層を実装する場合は、通常、特定のアプリケーションやタスクに特化したカスタムアクセラレータとして設計されます。そのため、全体のシステム設計との整合性や、実装におけるトレードオフを考慮することが重要です。


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

bsvのMakefile作成 (3)

posted by sakurai on July 3, 2024 #830

Makefile

過去記事でbsvのMakefileを作成しましたが、その後この記事によりtop.vが不要なことが判明したのでMakefileを再作成しました。以前のものよりtop.vを削除した分だけ若干シンプルになっています。

# ファイル名の生成
BSRCS = $(wildcard *.bsv) # BSVソースファイル
BASRCS = $(addprefix mk, $(addsuffix .ba, $(basename $(BSRCS)))) # BA中間ファイル
VSRCS = $(addprefix mk, $(addsuffix .v, $(basename $(BSRCS)))) # Verilogファイル

# .PHONY ターゲットの定義
.PHONY: all bsv_view verilog_view clean

# 全体のターゲット定義
all: bsv_view verilog_view

# BSV波形ビューアの起動
bsv_view: bsim.vcd
    gtkwave -A bsim.vcd

# BSV波形ファイルの生成
bsim.vcd: mkTb.exe
    ./mkTb.exe -V bsim.vcd

# BSV実行ファイルの生成
mkTb.exe: $(BASRCS)
    bsc -sim -e mkTb -parallel-sim-link 4 -o mkTb.exe

# BSVファイルのコンパイル
mk%.ba: %.bsv
    time bsc -sim -u -steps-warn-interval 1000000 -steps 8000000 -suppress-warnings T0054 $<

# Verilog波形ビューアの起動
verilog_view: verilog.vcd
    gtkwave -A verilog.vcd

# Verilog波形ファイルの生成
verilog.vcd: mkTb.exev
    ./mkTb.exev +bscvcd=verilog.vcd

# Verilog実行ファイルの生成
mkTb.exev: $(VSRCS)
     bsc -verilog -e mkTb -parallel-sim-link 4 -o mkTb.exev

# BSVファイルからVerilogファイルを生成
mk%.v: %.bsv
    time bsc -verilog -u -steps-warn-interval 1000000 -steps 8000000 -suppress-warnings T0054 $<

# クリーンアップ
clean:
    @rm -f mk*.v
    @rm -f *.bi *.bo *.ba a.out \#*
    @rm -f *.cxx *.h *.o *.so *.bexe
    @rm -f *.exe *.exev
    @rm -f *.vcd *~ *.fsdb *.log

依存関係グラフ

ここで、’Makefile'の依存関係の可視化の記事のプログラムをそのまま用いて、

$ LANG=C make -np | python3 make_p_to_json.py > graph.json; python json_to_dot.py workflow.png; xv workflow.png

このコマンドにより作成した依存関係図を図830.1に示します。

図%%.1
図830.1 依存関係図

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

Prismの拡張

posted by sakurai on June 25, 2024 #824

bsvのコードハイライティングを行う際に、Prism.jsを利用しました。ただしbsvはなくverilogまでなので、以下のjsを追加して文法を拡張しています。

bsv対応のcssやjsはChatGPTに作成してもらい人間がデバッグしました。ChatGPTはあっという間にほぼ完成度の高いcssやjsを作成するのですが、どうしてもバグが残ります。それをChatGPTに取らせるには却って工数がかかるので、人間がとってやったほうが今のところ全体工数が少なくて済みます。

そのためにはChatGPTをスーパーバイズする必要があるので、ChatGPTよりも知識が上である必要があります。つまり「ChatGPTには知ってることを聞け=知らないことを聞くな」と言うことです。

ソースコード

Prism.languages.bsv = {
  'comment': [
    {
      pattern: /\/\/.*/,
      alias: 'comment'
    },
    {
      pattern: /\/\*[\s\S]*?\*\//,
      alias: 'comment'
    }
  ],
  'module-name-outer': {
    pattern: /(\bmodule\s+|endmodule\s*:\s*)([a-z_]\w*)/,
    inside: {
      'keyword': /\b(?:module|endmodule)\b/,
      'punctuation': /:/,
      'module-name': /\b[a-z_]\w*\b/
    }
  },
  'interface-name-outer': {
    pattern: /(\binterface\s+|endinterface\s*:\s*)([A-Z]\w*)/,
    inside: {
      'keyword': /\b(?:interface|endinterface)\b/,
      'punctuation': /:/,
      'interface-name': /\b[A-Z]\w*\b/
    }
  },
  'package-name-outer': {
    pattern: /(\bpackage\s+|endpackage\s*:\s+|import\s+)([A-Z]\w*)/,
    inside: {
      'keyword': /\b(?:package|endpackage|import)\b/,
      'punctuation': /:/,
      'package-name': /\b[A-Z]\w*\b/
    }
  },
  'rule-name-outer': {
    pattern: /(\brule\s+|endrule\s*:\s*)([a-z_]\w*)/,
    inside: {
      'keyword': /\b(?:rule|endrule)\b/,
      'punctuation': /:/,
      'rule-name': /\b[a-z_]\w*\b/
    }
  },
  'method-name-outer': {
    pattern: /(\bmethod\s+|endmethod\s*:\s*)([a-z_]\w*)/,
    inside: {
      'keyword': /\b(?:method|endmethod)\b/,
      'punctuation': /:/,
      'method-name': /\b[a-z_]\w*\b/
    }
  },
  'return-variable': {
    pattern: /(\breturn\s+)([a-z_]\w*)/,
    inside: {
      'keyword': /\breturn\b/,
      'variable': /\b[a-z_]\w*\b/
    }
  },
  'keyword': {
    pattern: /\b(?:action|endaction|await|clocked_by|default_clock|deriving|else|function|endfunction|import|if|inout|input|interface|endinterface|match|method|endmethod|module|endmodule|no_clock|output|package|parameter|provisos|register|rule|endrule|rules|struct|type|typedef|union|value_of|var|while|seq|endseq|endpackage|return|let|case|endcase|delay|noAction|for|continue)\b/,
  },
  'right-hand-variable': {
    pattern: /([a-z_]\w*)\s*(?=\s*<=)/,
    alias: 'variable'
  },
  'right-hand-variable-operator': {
    pattern: /(\s*<=\s*)([a-z_]\w*)/,
    inside: {
      'operator': /<=/,
      'variable': /\b[a-z_]\w*\b/
    }
  },
  'generator-variable': {
    pattern: /([a-z_]\w*)\s*(?=\s*<-\s*)/,
    alias: 'variable'
  },
  'generator-operator': {
    pattern: /(\s*<-\s*)([a-z_]\w*)/,
    inside: {
      'operator': /<-/,
      'function': /\b[a-z_]\w*\b/
    }
  },
  'assignment-variable': {
    pattern: /([a-z_]\w*)\s*(?=\s*<=)/,
    alias: 'variable'
  },
  'function-definition': {
    pattern: /(?:\bfunction\s+\w+\s+)([a-z_]\w*)/,
    alias: 'function'
  },
  'system-function': {
    pattern: /\$\w+/,
    alias: 'function'
  },
  'function-call': {
    pattern: /(\b[a-z_]\w*)(?=\s*\()/,
    alias: 'function'
  },
  'function-call-no-parens': {
    pattern: /\b[a-z_]\w*(?=\s*;)/,
    alias: 'function'
  },
  'type': {
    pattern: /\b(Bit#\(\d+\)|UInt#\(\d+\)|Int#\(\d+\)|Bool|Maybe|Tuple2|Tuple3|Tuple4|Tuple5|Tuple6|Tuple7|Tuple8|Vector|Vector#\(\d+\)|Integer|Real|String|Fmt|Reg|FIFO|Clock|Reset|Inout|Action|ActionValue|Rules|UInt|Int)\b/,
    alias: 'type'
  },
  'directive': {
    pattern: /\(\*[\s\S]*?\*\)/,
    inside: {
      'comment': {
        pattern: /(\(\*|\*\))/,
        alias: 'comment'
      },
      'string': {
        pattern: /[\s\S]+/,
        alias: 'string'
      }
    }
  },
  'number': {
    pattern: /\b\d+(\.\d+)?([eE][+-]?\d+)?\b/,
    alias: 'number'
  },
  'class-name': {
    pattern: /\b[A-Z][a-zA-Z0-9_]*\b/,
    alias: 'class-name'
  },
  'string': {
    pattern: /(^|[^\\])(["'`])(?:\\.|(?!\2)[^\\\r\n])*\2/
  },
  'operator': {
    pattern: /(\+|\-|\!|\~|\&|\~\&|\||\~\||\^|\~\^|\^\~|\*|\/|\%|\<\<|\>\>|\<\=|\>\=|\<|\>|\=\=|\!\=|\&\&|\|\|)/
  }
};

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

BSVの例題 (4)

posted by sakurai on June 24, 2024 #823

もともとinuseが不定だったのはバグと言えます。ただ、初期値を設定する代わりに最初にputdownシーケンスを入れたら良いと考え、実行したところうまく動作しました。ついでにChatGPTにコメントを付けてもらいました。以下にソースコードを示します。

ソースコード

//
// CBG SQUANDERER : Dining Philosophers In Bluespec
// (C) 2012 David Greaves, University of Cambridge

// 必要なモジュールをインポート
import StmtFSM::*;

// スプーンのインターフェースを定義
interface Spoon_if;
   method Action pickup;             // スプーンを持ち上げるメソッド
   method Action putdown;            // スプーンを置くメソッド
   method Action putnumber(UInt#(5) value); // スプーンに番号を設定するメソッド
endinterface

// スプーンのモジュールを定義
(* synthesize *)
module spoon (Spoon_if);
   Reg#(Bool) inuse <- mkReg(?);       // スプーンの使用状況を表すレジスタ
   Reg#(UInt#(5)) number <- mkReg(?);  // スプーンの番号を保持するレジスタ

   // スプーンを持ち上げるメソッドの実装
   method Action pickup if (!inuse);
     inuse <= True;
   endmethod

   // スプーンを置くメソッドの実装
   method Action putdown;
     inuse <= False;
   endmethod

   // スプーンに番号を設定するメソッドの実装
   method Action putnumber(UInt#(5) value);
     number <= value;
   endmethod
endmodule

// 哲学者のベンチモジュールを定義
(* synthesize *)
module philoBENCH (Empty);
   // 5つのスプーンインスタンスを作成
   Spoon_if spoon0 <- spoon; 
   Spoon_if spoon1 <- spoon; 
   Spoon_if spoon2 <- spoon; 
   Spoon_if spoon3 <- spoon; 
   Spoon_if spoon4 <- spoon; 

   // 5人の哲学者インスタンスを作成
   Diner_if din0 <- mkDiner(0, 7, 7, spoon0, spoon1);
   Diner_if din1 <- mkDiner(1, 6, 4, spoon2, spoon1); // <---- 逆順で持ち上げる
   Diner_if din2 <- mkDiner(2, 5, 9, spoon2, spoon3);
   Diner_if din3 <- mkDiner(3, 6, 6, spoon3, spoon4);
   Diner_if din4 <- mkDiner(4, 8, 8, spoon4, spoon0);

   // タイマーのレジスタを作成
   Reg#(UInt#(15)) timer <- mkReg(1000);

   // タイマーをカウントダウンするルール
   rule foo;
      timer <= timer - 1;
      if (timer == 0) $finish;
   endrule
endmodule: philoBENCH

// ランダム数生成器のインターフェースを定義
interface Random_if;
   method ActionValue#(UInt#(15)) gen; // ランダムな値を生成するメソッド
endinterface    

// ランダム数生成器のモジュールを定義
module mkRandom_gen #(UInt#(15) seed) (Random_if);
   Reg#(UInt#(15)) prbs <- mkReg(seed); // ランダム数を保持するレジスタ

   // ランダムな値を生成するメソッドの実装
   method ActionValue#(UInt#(15)) gen;
      prbs <= (prbs << 1) | (((prbs >> 14) ^ (prbs >> 13)) & 1); // シフトとXORを使用してランダム値を生成
      return prbs;
   endmethod
endmodule 

// 哲学者のインターフェースを定義
interface Diner_if;
endinterface

// 哲学者のモジュールを定義
module mkDiner #(UInt#(5) number, UInt#(15) on, UInt#(15) seed) (Spoon_if left, Spoon_if right, Diner_if i);
   Reg#(Bool) eating <- mkReg(?);      // 食事中かどうかを表すレジスタ
   Reg#(UInt#(15)) timer <- mkReg(0);  // タイマーのレジスタ
   Random_if random<- mkRandom_gen(seed); // ランダム数生成器のインスタンスを作成

   // タイマーをカウントダウンするルール
   rule foo (timer != 0);
      timer <= timer - 1;
   endrule

   // 哲学者の動作シーケンスを定義
   Stmt seq_behaviour = (seq
      while (True) seq
         action // 初期状態を確定するサイクル(食事の後だったのを前に持ってきた)
            eating <= False;
            left.putdown; 
            right.putdown;
            left.putnumber(?);
            right.putnumber(?);
         endaction
         action
            UInt#(15) x <- random.gen;
            timer <= x & 31; // ランダムな時間を設定
         endaction
         await(timer== 0);
         left.pickup; // 左のスプーンを持ち上げる
         action
            UInt#(15) x <- random.gen;
            timer <= x & 31; // ランダムな時間を設定
         endaction
         await(timer== 0);
         right.pickup; // 右のスプーンを持ち上げる
         action
            eating <= True; // 食事を開始
            timer <= on;    // 食事時間を設定
            left.putnumber(number);  // スプーンに哲学者の番号を設定
            right.putnumber(number); // スプーンに哲学者の番号を設定
         endaction
         await(timer==0);
      endseq
   endseq);

   // シーケンスを自動的に実行するFSMを作成
   mkAutoFSM(seq_behaviour);
endmodule

以下にシミュレーション波形を示します。putdownは終了処理だった(pickup→putdown)ものをシーケンスの最初に持ってくる(putdown→pickup)ことにより、初期状態が確定し不定が継続しなくなりました。

図%%.1
図823.1 verilogシミュレーション

図らずも元のコードのバグを発見しましたが、あえて不定を設定しないと見つからないため、シミュレーションは不定も込みで実施したほうが良いと思います。


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

BSVの例題 (3)

posted by sakurai on June 21, 2024 #822

どの哲学者がどのスプーンを使っているかを見やすくするため、スプーンを使用中に哲学者の番号を付けることにします。初期状態は'X'ですが、bsvのシミュレータであるbsimでは'X'は取れないようです。

$ bsc -u -opt-undetermined-vals -unspecified-to X -no-warn-action-shadowing -sim philo.bsv
$ bsc -sim -e philoBENCH -o philoBENCH.exe
$ ./philoBENCH.exe -V bsim.vcd
$ gtkwave -A bsim.vcd

図%%.1
図822.1 bsimシミュレーション

spoonのナンバーを見ると、哲学者の取得するスプーンの様子が良くわかります。最初に1番の哲学者が(1, 2)を用いて食事し、次に0番が(0, 1)、4番が(0, 4)、3番が(3, 4)とたまたま順に食事します。次に2番が(2, 3)を使用し、ほぼ同時に4番が(0, 4)を用いて食事をしています。

verilogシミュレーション

$ bsc -u -opt-undetermined-vals -unspecified-to X -no-warn-action-shadowing -verilog philo.bsv
$ bsc -verilog -e philoBENCH -o philoBENCH.exev
$ ./philoBENCH.exev +bscvcd=verilog.vcd
$ gtkwave -A verilog.vcd

図%%.2
図822.2 verilogシミュレーション

最初に1番の哲学者が(1, 2)を用いて食事し、次に0番が(0, 1)、4番が(0, 4)、3番が(3, 4)とたまたま順に食事します。次に2番が(2, 3)を使用し、ほぼ同時に4番が(0, 4)に食事をしています。不定が赤なので確定信号が良く分かります。

なお当初全面Xになって動作しなかったのですが、原因はspoonモジュールのinuseの初期値がXとなっていたためでした。Falseにしたところ正しく動作しました。

(* synthesize *)
module spoon (Spoon_if) ;
   Reg#(Bool) inuse <- mkReg(?);
   method Action pickup if (!inuse);
     inuse <= True;
   endmethod

この記述で分かるように、inuseの初期値は?(bsvで言うX)にも関わらず、次の行のpickupというメソッドに if (!inuse);条件が付いています。条件が不定なのでpickupメソッドをコールすると結果が不定となり、不定が伝搬することでシミュレーションが真っ赤になってしまいます。


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

BSVの例題 (2)

posted by sakurai on June 20, 2024 #821

食事する哲学者全員が左手→右手の順にスプーンを取るシミュレーションです。当然ですが全員左手にスプーン持った状態でデッドロックします。

図%%.1
図821.1 シミュレーション1

ソースを修正し、2番目の哲学者のみ右手→左手の順のシミュレーションです。この場合はランダム的に全員が食事することができます。

図%%.1
図821.2 シミュレーション2

spoon0とテストベンチは親子モジュールとなっており、その間にpickupとputdownのEN-RDYインタフェースが自動生成されます。まずspoon0にpickup指示を出すと、RDYが落ちます(=busy)。同時にspoon0のinuseがTrueとなり使用中を示します。哲学者の食事が終了するとputdown指示がされ、inuseはFalseになります。

スプーンを取得する場合は当然使用中で無いことを確認してから取得するコードとなっていますが、一方、スプーンをリリースする場合は、直前まで自分が使用していることはシーケンスから明らかなため特に使用中であることは確認していません。


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


ページ: