Posts Tagged with "BSV"

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

バックプレッシャの二つの流儀、BSV の EN/RDY と AXI の valid/ready

バックプレッシャは、受け手が受け取れないときに送り手を待たせる仕組みです。Bluespec SystemVerilog のメソッド呼び出しと AXI のチャネルは、どちらもバックプレッシャを持つ点対点の転送ですが、転送するかどうかの判断をどこで下すかが違います。BSV は両端の外側で判断し、AXI は両端の内側でそれぞれ判断します。この違いを、両方の思想、それぞれの接続法、生成された Verilog、そして比較の順に見ていきます。

BSV の思想、ルールが判断する

BSV のメソッドは、呼ばれる側が RDY を出し、呼ぶ側が EN を返す約束で動きます。ここで注意すべきは、点対点の両方の点はいずれも呼ばれる側ということです。呼ぶ側は点の外にいます。RDY は「今呼んでよい」という呼ばれる側の状態で、EN は「今呼ぶ」という呼ぶ側の決定です。EN を立ててよいのは RDY が立っているときだけで、RDY は EN に依存してはいけません。依存は RDY から EN への一方向です。

判断を下すのはルールです。ルールは複数のモジュールの複数のメソッドを一度に呼び、呼ぶメソッド全部の RDY が立っているときだけ発火し、発火すれば全部が同じサイクルに起こり、発火しなければ何も起こりません。この原子性を実現するには、関係する全員の RDY を一か所に集めて AND を取る場所が必要で、それがルールです。EN はその AND の結果を各メソッドに配る信号です。

EN にはもう一つの情報が畳み込まれています。同じサイクルに衝突する他のルールとの優先順位です。二つのルールが同じレジスタを書こうとすれば、どちらが勝つかをコンパイラが決め、負けた側の EN は落ちます。これは設計全体を見渡せるコンパイラだけが決められることです。

そしてこの約束はコンパイラが検査します。RDY の立っていないメソッドを呼ぶルールはそもそも発火しないので、準備のできていない相手を呼ぶ誤りは生成物に入りません。EN/RDY は、コンパイラが両端を見渡せる一つの設計の中で、原子的な合成を機械的に保証するための約束です。

AXI の思想、両端が判断する

AXI のチャネルは、送り手が valid を出し、受け手が ready を出し、両方が立っているクロックの立ち上がりで1件が渡ります。valid は「渡すものがある」という送り手の状態、ready は「受け取れる」という受け手の状態です。どちらも自分の状態の表明です。点対点の転送において、送信点と受信点のみがあり、その外にロジックはありません。

転送するかどうかは両端がそれぞれ判断します。送り手は valid と ready の AND を自分で計算して自分の出口を進め、受け手も同じ AND を自分で計算して自分の入口に取り込みます。両端の間にあるのは配線だけです。AND は両端の内側に一つずつ、合わせて二つあり、同じ値を計算しています。

この構造が成り立つ条件が、valid を ready に依存させてはいけないという規則です。両端が互いの状態を見て自分の AND を作るので、両方が相手に依存すると組み合わせループになります。そこで valid は自分の状態だけから決め、ready の側だけに相手を見ることを許して、依存を一方向に保ちます。EN/RDY の一方向の規則と同じ役割を、valid/ready では ready から valid への禁止が担っています。

判断を両端に閉じ込めた効果は、接続に第三者が要らなくなることです。どこの誰が作った IP でも、線をつなげば結合できます。fabric はルーティングとマルチプレクスに専念でき、転送の可否には関与しません。AXI は、互いの中身を知らない IP どうしを配線だけでつなぐための約束です。


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

posted by sakurai on September 14, 2026 #1119

10. 順序の正しい手順、B を受け取ってから読む

図1119.1
図1119.1

場面 9 と同じ 0x08 に、今度は順序を守って書いて読みます。1000 で AW と W を出し、1000 で成立します。1020 で B が返り、getB がその握手で受け取ります。regs_2 が 77777777 になるのも 1020 です。getB の次の文が putAR なので、1030 のサイクルに文が実行されて 1040 から arvalid が立ち、1060 の R で 77777777 が返って 1070 で rdReg に入ります。

書いた値を読みたければ B を受け取ってから AR を出す、という責任はマスタにあり、以下のコードのようにgetB の後に putAR を置くことがその手順の記述です。場面 9 との違いは AR を出す位置だけで、スレーブは何も変わっていません。

   Stmt s10 = seq
      mark (10, "RAW ordered: write, wait B, then read");
      par
         putAW (8'h08);
         putW  (32'h77777777, 4'hF);
      endpar
      getB;                                      // ここで書き込みの完了が確定する
      putAR (8'h08);                             // B を受け取った次の文で AR
      getR;                                      // 77777777
   endseq;

putAW
putW → getB → putAR → getR

11. エラー応答

図1119.2
図1119.2

範囲外の 0x10 に書きます。握手は通常どおり 1110 で成立し、1130 の B で bresp が 2、SLVERR になります。regs はどれも変わりません。1170 で 0x10 を読むと、1190 の R で rresp が 2、rdata は 0 です。エラーは握手の失敗として現れるのではなく、握手は成立させたうえで応答コードで伝える、というのが AXI の流儀です。

場面と見どころの対応

場面 出すもの 見るもの
1 AW と W を同時 三つの握手が独立に成立、B は処理の後
2 AR rdata は rvalid と同時にだけ有効
3 W の後に AW スレーブは揃うまで動かない
4 AW の後に W 同上、順序に決まりはない
5 AW を3件連続 awready が落ちる、マスタは保持、B は順に返る
6 bready を下げて書く bvalid と bresp が保持される
7 rready を下げて読む rvalid と rdata が保持される
8 AW、W、AR を同時 読み書きが並行に進む
9 同じ番地へ書きながら読む 古い値が返る、順序は保証されない
10 書いて B を受け取ってから読む 新しい値が返る、順序はマスタの責任
11 範囲外の番地 握手は成立、応答コードで SLVERR

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

posted by sakurai on September 13, 2026 #1118

7. 読み出しで rready を下げたまま待つ

図1118.1
図1118.1

場面 6 の読み出し版です。740 で rready を下げ、750 で AR を出します。770 で rvalid と rdata=44444444 が立ち、rready が上がる 810 まで4サイクル保持されます。810 で成立し、820 でマスタが rdReg に記録します。

8. 読み出しと書き込みを同じサイクルに出す

図1118.2
図1118.2

860 で AW、W、AR を同時に出します。三つとも 860 で成立し、スレーブは書き込み FSM と読み出し FSM で並行に処理して、880 で bvalid と rvalid が同時に立ちます。書き込み経路と読み出し経路が独立に動くこと、それが AW と AR を分けている理由であることが見えます。

9. 順序の罠、同じアドレスへの書き込みと読み出しを同時に出す

図1118.3
図1118.3

930 で 0x08 への書き込みと 0x08 の読み出しを同じサイクルに出します。950 で返った rdata は AAAA0008 で、場面 5 で書いた古い値です。同じ 950 で B も返っていて、regs_2 が 99999999 に変わるのも 950 です。読み出し FSM が regs を読んだのは 940 のサイクルで、書き込み FSM が regs を書いたのも 940 のサイクルなので、読めるのは書く前の値です。AXI は読み出しと書き込みの間の順序を保証しません。以下のコードのようにマスタが B を待たずに AR を出せば、こうなります。

   Stmt s9 = seq
      mark (9, "RAW hazard: write and read same address together");
      par
         seq
            par
               putAW (8'h08);
               putW  (32'h99999999, 4'hF);
            endpar
            getB;
         endseq
         seq
            putAR (8'h08);
            getR;                                // 古い値 (場面5で書いた AAAA0008) が読める
         endseq
      endpar
   endseq;

seqの中では順序が保たれるので、書き込みアドレスとデータを同時に出し、次にBを待ちますが、それと読み出しは並行して行われます。

putAW
putW → getB
putAR → getR


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

posted by sakurai on September 12, 2026 #1117

4. AW を先に、W を後に出す

図1117.1
図1117.1

場面 3 の鏡像です。310 で AW、360 で W。処理は W が揃った次のサイクルで、380 で bvalid が立ち regs_2 が 33333333 になります。

5. スレーブ側のバックプレッシャ、awready が落ちる

図1117.2
図1117.2

W を出さずに AW だけを3件続けて出します。連続で出すときは valid を下ろさず、成立したサイクルでアドレスだけ差し替えます。430 で awaddr=00 を出して成立、440 で 04 に差し替えて成立、450 で 08 に差し替えます。ここでスレーブの AW 受け口が2件で満杯になり、450 で awready が落ちます。マスタは awvalid と awaddr=08 をそのまま保持して待ちます。これが「valid を立てたら ready が来るまで下ろさない、データを変えない」の規則です。

490 で W の1件目を出すと、スレーブは 500 のサイクルで AW と W を1件ずつ処理して受け口が1つ空き、510 で awready が上がり、3件目の AW が 510 で成立します。W の2件目と3件目が続き、B は 510、530、550 に3件、出した順に返ります。regs_0、regs_1、regs_2 が順に AAAA0000、AAAA0004、AAAA0008 に変わります。

AXI4-Lite でも未完了のトランザクションを複数抱えられること、応答は出した順に返ることが見えます。

6. マスタ側のバックプレッシャ、bready を下げたまま書く

図1117.3
図1117.3

620 で bready を下げてから、630 で AW と W を出します。650 で bvalid が立ちますが、bready が下がっているので成立しません。スレーブは bvalid と bresp を立てたまま待ちます。690 で bready を上げると、その 690 の立ち上がりで成立し、700 で bvalid が下がります。

受け手が ready を下げれば送り手は待つ、という規則が応答チャネルでも同じであることが見えます。


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

posted by sakurai on September 11, 2026 #1116

1. 書き込みの基本形

図1116.1
図1116.1

マスタは 50 のサイクルで awvalid と wvalid を同時に立て、awaddr に 00、wdata に 11111111、wstrb に F を置きます。スレーブは awready と wready を常に立てているので、両方とも 50 の立ち上がりで成立し、マスタは 60 で valid を下ろします。スレーブは受け取った2つを揃えて 60 のサイクルで処理し、regs_0 が 70 で 11111111 になります。同じ 70 で bvalid が立ち、bresp は 0 で OKAY です。bready は最初から立てているので B はその 70 の立ち上がりで成立し、80 で bvalid が下がります。

見るべき点は、AW と W と B が三つの独立した握手だということです。マスタ側で AW と W は par で並べた別々の文が処理していて、たまたま同じサイクルに成立しています。

2. 読み出しの基本形

図1116.2
図1116.2

120 で arvalid を立て、araddr に 00 を置きます。arready は立っているので 120 で成立し、130 で下ろします。スレーブは 130 のサイクルで regs_0 を読み、140 で rvalid と rdata と rresp を同時に出します。rdata は rvalid と同じサイクルにだけ意味を持ちます。rready は立っているので 140 で成立し、マスタは 150 で rdReg に 11111111 を記録します。

3. W を先に、AW を後に出す

図1116.3
図1116.3

190 で W だけを出し、190 で成立します。スレーブはデータだけ受け取っても何もしません。regs はどれも変わらず、bvalid も立ちません。3サイクル空けて 240 で AW を出し、240 で成立すると、揃った次の 250 のサイクルで処理され、260 で bvalid が立ち regs_1 が 22222222 になります。

AW と W の順序に決まりがないことと、スレーブが両方揃うまで待つことが見えます。


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

posted by sakurai on September 10, 2026 #1115

Claude Codeがbsc環境を持てるということで、AXIのコードを書いて貰いました。今まではChatレベルではAIの想像でコードを書いていました。従ってコードベースの多いPython等は100%一発動作しましたが、逆に少ないBSVではSyntax errorが頻発していました。

ちなみに英語圏ではARMの動画を見ても分かるように、エーエックスアイと発音するので、アクシー等と言わないようにしましょう。

AXI4-Lite ハンドシェイク チュートリアル

ブロック図を示します。

図1115.1
図1115.1 AXIチュートリアルブロック図

スレーブは AxiRegs.bsv、32bit レジスタ4本を 0x00、0x04、0x08、0x0C に持つ Full 版です。テストベンチ TbTut.bsv はマスタ側の信号を1本ずつレジスタで直接駆動し、スレーブの応答を Wire で観測します。トランザクタも FIFO も使いません。何を見て何を出すかを、文の並びがそのまま示すためです。

波形は bsc が生成した Verilog を iverilog で走らせた verilog.vcd から取り、1サイクルは 10 単位です。上から、場面番号、CLK、書き込みアドレスチャネル AW、書き込みデータチャネル W、書き込み応答チャネル B、読み出しアドレスチャネル AR、読み出しデータチャネル R、そしてマスタが記録した値とスレーブのレジスタです。

0. 規則はこれだけ

AXI4-Lite の5チャネルは全て同じ握手で動きます。送り手が valid を立て、受け手が ready を立て、両方が立っているクロックの立ち上がりで1件が渡ります。送り手は valid を立てたら ready が来るまで下ろしてはならず、その間データを変えてはいけません。valid を ready に依存させてはいけません。ready は valid を見て決めて構いません。チャネルは互いに独立で、AW と W の成立順に決まりはなく、B は AW と W の両方が成立した後にだけ出ます。

送り手を StmtFSM で書くときの型は次のとおりです。

   function Stmt putAW (Addr a) = seq
      action
         awvalid <= True;
         awaddr <= a;
      endaction                              // 次のサイクルからバスに出る
      while (!awready) noAction;                // ready が立つサイクルまで待つ
      awvalid <= False;                         // 立ったその同じサイクルに下ろす
   endseq;

while を抜けた同じサイクルに次の文が実行されるので(つまり最初からawready=Trueならwhileは0サイクル)、valid は ready を見たサイクルだけ立ち、転送はちょうど1件になります。await (awready) と書くと次の文が1サイクル遅れて valid が2サイクル残り、二重転送になります。この差は測って確かめました。

受け手側は ready を立てたまま valid を待ちます。

   function Stmt getB = seq
      while (!bvalid) noAction;     // bready は立てたまま
      bRespReg <= bresp;            // 成立したサイクルに応答を読む
   endseq;

これもwhile を抜けた同じサイクルに次の文が実行されます(つまり最初からbvalid=Trueならwhileは0サイクル)。


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

BSVによるUARTの再設計 (7)

posted by sakurai on August 24, 2026 #1112

ブロック図を示します。

図1112.1

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

図1112.2

各サイクル間の動きです。表現はサイクル中に発現するデータであり、確定は次のサイクルの頭となります。

  • n:送信stop bit, 受信最終bit, putfsm.done,
  • n+1: 受信stop bit
  • n+2: getfsm.done
  • n+3: topでデータ取得、チェック, getfsm.start
  • n+4: putfsm.start
  • n+5: 送信start bit

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

BSVによるUARTの再設計 (6)

posted by sakurai on August 23, 2026 #1111

Tb.bsvを示します。

import StmtFSM::*;
import Uart::*;
import Connectable::*;
typedef Bit#(8) Byte;

(* synthesize *)
module mkTb();
   // UARTインターフェースのインスタンスを生成
   Uart_ifc uart <- mkUart;
   // テストバイトレジスタの定義
   Reg#(Byte) tbrReg <- mkRegU;
   
   // UARTの送信と受信を接続
   mkConnection(uart.sout, uart.sin);

   // データを取得する関数
   function Action getData;
      return
         action
            // UARTからデータを読み取り、tbrRegに格納
            let data <- uart.read;
            tbrReg <= data;
         endaction;
   endfunction

   // データを送信する関数
   function Stmt putData(Byte c);
      return (seq
         // UARTにデータを書き込む
         uart.write(c);
      endseq);
   endfunction

   // 応答を確認する関数
   function Stmt checkResp(Byte correctData);
      return (seq
         // データを取得
         getData;
         // 正しいデータと比較
         if (correctData != tbrReg) seq
            // データが異なる場合、表示して終了
            $display("%d->%d", correctData, tbrReg);
            $finish;
         endseq
      endseq);
   endfunction

   // テストシーケンスの定義
   Stmt test = seq
      putData(8'haa); // データ1を送信
      checkResp(8'haa); // 応答を確認
      putData(8'h55); // データ2を送信
      checkResp(8'h55); // 応答を確認
      putData(8'hc3); // データ3を送信
      checkResp(8'hc3); // 応答を確認
      putData(8'h3c); // データ4を送信
      checkResp(8'h3c); // 応答を確認
      $display("test OK"); // テスト成功を表示
      await(uart.pdone);
      $finish; // テスト終了
   endseq;
   mkAutoFSM(test); // テストシーケンスを自動化

endmodule

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

BSVによるUARTの再設計 (5)

posted by sakurai on August 22, 2026 #1110

次にUartを送受信化します。送信部のFSMと並行で動作する受信部のFSMを設計します。

import StmtFSM::*;

interface Uart_ifc;
   method Bit#(1) sout; // シリアル出力
   (* prefix="" *)
   method Action write((* port="write" *) Bit#(8) nodata); // パラレル入力
   (* prefix="" *)
   method Action sin((* port="sin" *) Bit#(1) nidata); // シリアル入力
   method ActionValue#(Bit#(8)) read; // パラレル出力
   method Bool pdone; // 送信完了フラグ
endinterface

(* synthesize, always_ready="sin, sout, pdone" *)
module mkUart(Uart_ifc);

   // レジスタ定義
   Reg#(Bit#(8)) odata <- mkRegU; // 送信データレジスタ
   Reg#(Bit#(1)) osdata <- mkReg(1'h1); // シリアル出力データレジスタ
   Reg#(Bit#(8)) idata <- mkRegU; // 受信データレジスタ
   Reg#(Bit#(1)) isdata <- mkReg(1'h1); // シリアル入力データレジスタ
   Reg#(Bool) getfsmDone <- mkReg(False); // 受信完了フラグ

   // 送信ステートマシンの定義
   Stmt putseq = seq
      osdata <= 1'h0; // スタートビット
      repeat (8) action
         osdata <= odata[0]; // データを1ビットずつシリアル出力
         odata <= (odata >> 1); // 右シフト
      endaction
      osdata <= 1'h1; // ストップビット
   endseq;
   FSM putfsm <- mkFSM(putseq);

   // 受信ステートマシンの定義
   Stmt getseq = seq
      await(isdata == 1'h0); // スタートビットを待つ
      repeat (8) action
         idata <= {isdata, (idata >> 1)[6:0]}; // データを1ビットずつ受信
      endaction
      getfsmDone <= True; // 受信完了フラグをセット
   endseq;
   FSM getfsm <- mkFSM(getseq);

   // 受信ステートマシンの再起動
   rule restartGetFSM if (getfsm.done);
        getfsm.start;
   endrule

   // シリアル出力
   method Bit#(1) sout;
      return osdata;
   endmethod
   // パラレル入力(データを書き込む)
   method Action write(Bit#(8) nodata) if (putfsm.done);
      odata <= nodata;
      putfsm.start;
   endmethod
   // シリアル入力
   method Action sin(Bit#(1) nidata);
      isdata <= nidata;
   endmethod
   // パラレル出力(データを読み込む)
   method ActionValue#(Bit#(8)) read if (getfsmDone);
      getfsmDone <= False; // 受信完了フラグをリセット
      return idata;
   endmethod
   // 送信完了フラグ
   method Bool pdone;
      return putfsmDone;
   endmethod
endmodule

送受信のFSMにおいて、起動のタイミングが非対称的です。

  • 送信FSMの起動は上位からパラレル入力をロードされてパラシリが開始する
  • 受信FSMはシリアルデータがいつくるかわからないので、常に起動されていなくてはならない

従って、送信側は

   // パラレル入力(データを書き込む)
   method Action write(Bit#(8) nodata) if (putfsm.done);
      odata <= nodata;
      putfsm.start;
   endmethod

このように、データが書き込まれた契機でfsmをスタートさせるのに対し、受信側は

   // 受信ステートマシンの再起動
   rule restartGetFSM if (getfsm.done);
        getfsm.start;
   endrule

FSMの終了を待ってその起動をかけるルールを追加しています。これはAutoFSMとして自動起動にすることも可能です。


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

BSVによるUARTの再設計 (4)

posted by sakurai on August 21, 2026 #1109

最後にシミュレーション結果を示します。

シミュレーションの最初はbluesimによるものです。

図1109.1
図1109.1 bluesimシミュレーション

次はiverilogによるものです。テストベンチとUartの間のインタフェースのEN/RDY信号が見えています。

図1109.2
図1109.2 verilogシミュレーション

次はUartの完了待ち合わせをdoneを待つことではなく、コール待ちに変更したものです。

図1109.3
図1109.3 bluesimシミュレーション(drain化)

コール待ちであるEN_drainが見えています。

図1109.4
図1109.4 verilogシミュレーション(drain化)


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


ページ: