Posts Tagged with "Design"

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

二つは同じものの置き方違い

対応を並べると次のようになります。

EN/RDY (BSV) valid/ready (AXI) 意味
送り手の RDY 送り手の valid 送り手に渡すものがある
受け手の RDY 受け手の ready 受け手が受け取れる
接続ルールの AND 両端それぞれの AND 転送の判断
EN 線上に存在しない 判断の結果は各端が自分で使う
RDY は EN に依存してはならない valid は ready に依存してはならない 依存を一方向に保つ

AXI の valid/ready は、EN/RDY の接続ルールにあった AND を両端の内側に移し、EN の線を消したものです。逆に言えば EN/RDY は、両端が持つはずの AND を一か所に集め、その結果を配ることで、二者を超えた合意と衝突の調停を可能にしたものです。

ここで ready という語に注意が要ります。二つの流儀で同じ語が違う意味を持っています。

BSV の RDY は報告です。「呼んでよい状態にある」と上位に知らせるだけで、それ自体は何も起こしません。呼ぶかどうかは上位のルールが決め、RDY が立っていても呼ばれないサイクルは普通にあります。送り手も受け手も同じ形で RDY を出し、両端は対称です。

AXI の ready は決定です。受け手だけが出し、送り手は上位の許可を待たずに valid で勝手に送り、受け手が ready を立てたサイクルで転送が完了します。ready は「受け取れる」という報告であると同時に「このサイクルで受け取る」という受理の決定でもあり、送り手側には ready がなく、対になるのは valid です。

対応表の一行目と二行目がその翻訳です。BSV の受け手の RDY が AXI の ready に当たり、BSV の送り手の RDY は AXI では valid と呼ばれます。BSV で ready と言えば両端が上位に出す報告、AXI で ready と言えば受け手が下す決定、と読み分けてください。

比較

観点 EN/RDY (BSV) valid/ready (AXI)
判断の場所 接続ルールを持つモジュール 両端それぞれ
両端の間 AND と EN の配線 配線のみ、接続ルールの AND は定数になって消える
三者以上の同時性 ルールが複数メソッドを原子的に呼べる 1対1のチャネル単位、複数チャネルの同時性は上位の責任
衝突の調停 bscがスケジューリングで解決し EN に畳み込む fabric や arbiter を別に置く
規約の検査 bscが保証する IP の作り手が守る
他者 IP との結合 相手も EN/RDY を理解する必要がある 線をつなぐだけ
変換 always_ready と always_enabled で valid/ready を作れる EN/RDY に戻すにはトランザクタが要る
遅延要素の挿入 ルールの意味が変わるので慎重になる 両端が独立なのでレジスタスライスを自由に挟める

使い分けは、誰が両端を見渡せるかで決まります。一つの設計の内側では、bscが全てのモジュールを見渡せるので EN/RDY が有利です。複数のメソッドを一度に呼ぶルールが書け、衝突はbscが解き、規約違反は生成物に入りません。設計の境界では、相手の中身が見えないので valid/ready が有利です。両端が自己完結し、間は配線だけで、途中にレジスタスライスを挟んでも両端の設計は変わりません。

BSV で AXI を設計するときの基本方針

ここまでの内容を設計方針として言い直すと一つの文になります。IP の境界は AXI で切り、内側は EN/RDY で書いてbscに任せる、です。

境界とは、両側を別々の人や別々のツールが作る場所のことです。他社 IP との接続、Vivado の block design に置く単位、別々に検証する単位、将来切り離して再利用する単位が境界です。そこでは相手の中身が見えないので、両端が自己完結する valid/ready を使います。BSV 側では、境界のモジュールに synthesize を付け、インタフェースのメソッドを always_ready と always_enabled にして、ポートを素の AXI 信号にします。bsc-contrib のトランザクタはこの境界の定型です。

境界の内側では、モジュールを synthesize で分けても EN/RDY のままで構いません。分けたモジュールの親も BSV であれば、親の中の接続ルールが AND を計算し、bscが両端を見渡しています。EN/RDY が使える範囲は、モジュールの大きさではなく、親をbscが見ているかどうかで決まります。

今回の AxiRegs はこの方針で書かれています。境界には xactor があり、外から見ると素の AXI4-Lite スレーブです。内側の書き込み処理は次の一つの action です。

   Stmt wrseq = seq
      while (True) action
         let a <- pop_o (xactor.o_wr_addr);
         let d <- pop_o (xactor.o_wr_data);
         if (inRange (a.awaddr)) begin
            regs [index (a.awaddr)] <= merge (regs [index (a.awaddr)], d.wdata, d.wstrb);
            xactor.i_wr_resp.enq (AXI4L_Wr_Resp { bresp: AXI4L_OKAY, buser: ? });
         end
         else
            xactor.i_wr_resp.enq (AXI4L_Wr_Resp { bresp: AXI4L_SLVERR, buser: ? });
      endaction
   endseq;
   mkAutoFSM (wrseq);

この action は AW の FIFO から1件、W の FIFO から1件を取り、B の FIFO に1件を入れます。三つの FIFO の RDY が全て立ったサイクルにだけ発火し、発火すれば三つが同時に起こります。AW と W が揃うまで待つ、B が満杯なら待つ、という制御はどこにも書かれていません。三つの RDY の AND をbscが作っているからです。

なぜ BSV の内側を AXI で書かないのか

内側も AXI で統一したほうが一貫している、という考え方は一見正しそうです。しかし、ここでそれを避ける理由は、valid/ready を内側に持ち込むと、bscがやっている三つの仕事を、それぞれ別の IP や別の道具に置き換えて加えなければいけないからです。比較表の三者以上の同時性、衝突の調停、規約の検査の三行がそれです。

三者以上の同時性について:上の wrseq は AW と W と B の三つの FIFO を一つの action で扱い、三つの RDY の AND をbscが作っています。AXI 流では AW と W は独立したチャネルなので、両方が揃ったことを判定して一つの書き込みにまとめる論理を自分で書きます。Xilinx の IP カタログにこの部分を担う汎用の IP はなく、スレーブを書く人がそれぞれのスレーブの中に同じ論理を繰り返し書いています。チャネルが増えれば AND と保持の論理が増え、ルールの原子性という BSV の主要な利点を捨てることになります。

衝突の調停について:二つのルールが同じスレーブを使おうとするとき、EN/RDY ではbscが優先順位を決めて EN に畳み込み、設計者は何も足しません。AXI 流では複数のマスタが一つのスレーブに向かう場合、AXI Interconnect や AXI Crossbar といった調停の IP を別に置きます。その IP はレイテンシを数サイクル足し、面積を消費し、優先順位や ID の幅といった設定項目を持ち、設計の一部として管理する対象になります。BSV の内側なら、調停はbscの出力の一部であり、設定も管理も要りません。

規約の検査について:EN/RDY では、RDY の立っていないメソッドを呼ぶルールは発火しないので、規約違反は生成物に入らず、検査はコンパイル時に終わっています。AXI 流では valid を ready まで保持しているか、その間データを変えていないか、といった規約はbscにとってただの 1 ビットの値の推移で、検査の対象になりません。そこでシミュレーションに AXI Protocol Checker や AXI VIP といった検証用 IP を置き、アサーションで規約違反を捕まえます。検査はシミュレーションで走らせた範囲に限られ、走らせなかった場面の違反は残ります。

まとめると、内側を AXI 流にすると、bscが無償で作っていた結合、調停、検査を、手書きの結合論理、Interconnect IP、Protocol Checker IP として設計に足し、それぞれのレイテンシ、面積、設定、検証範囲を自分で持つことになります。

内側で BSV流EN/RDY を使う理由はこの三つをbscに任せるためであり、一方、境界でAXI流 valid/ready を使う理由は相手を選ばないためです。


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

posted by sakurai on September 17, 2026 #1122

接続法 2、valid/ready をつなぐ

AXI流のvalid/readyによる接続を図1122.1に示します。

図1122.1
図1122.1 valid/ready の接続

AXI4-Lite の書き込みアドレスチャネルを例にします。スレーブ側のインタフェースは、メソッドに always_ready と always_enabled を付けて EN と RDY を消し、Bool の引数と返り値をそのまま valid と ready の線にします。

interface AxiLiteAW #(numeric type wd_addr);
   (* always_ready, always_enabled, prefix="" *)
   method Action awvalid (Bool awvalid, Bit #(wd_addr) awaddr, Bit #(3) awprot);
   (* always_ready *)
   method Bool   awready;
endinterface

マスタとスレーブをつなぐ mkConnection も、接続法 1 と同じく Connectable のインスタンスが定義するルールです。valid とアドレスをスレーブに渡し、ready をマスタに返しています。

      (* fire_when_enabled, no_implicit_conditions *)
      rule rl_aw;
         s.aw.awvalid (m.m_awvalid, m.m_awaddr, m.m_awprot);
         m.m_awready (s.aw.awready);
      endrule

接続法 1 との違いは、呼んでいるメソッドの側にあります。always_ready は RDY が定数 1 であること、always_enabled は EN が定数 1 で毎サイクル必ず呼ばれることを意味します。発火条件は定数 1 の AND なので定数 1 になり、ルールは毎サイクル無条件に発火します。fire_when_enabled と no_implicit_conditions の属性は、発火条件が本当に定数であることをコンパイラに検査させるためのもので、RDY を持つメソッドが混じっていればコンパイルエラーになります。生成された mkTb.v で、この接続は assign だけになります。mkConnection に AXI 用の特別な動作があるのではなく、接続法 1 と同じ機構で AND を計算した結果、その AND が定数になって消えたものです。

  assign dut$awaddr = master_f_wr_addr$D_OUT[10:3] ;
  assign dut$awprot = master_f_wr_addr$D_OUT[2:0] ;
  assign dut$awvalid = master_f_wr_addr$EMPTY_N ;

AND は両端の中に現れます。マスタ側の FIFO の DEQ と、スレーブ側の FIFO の ENQ です。

  // mkTb.v : マスタ側の xactor
  assign master_f_wr_addr$DEQ = master_f_wr_addr$EMPTY_N && dut$awready ;

  // mkAxiRegs.v : スレーブ側の xactor
  assign awready = xactor_f_wr_addr$FULL_N ;
  assign xactor_f_wr_addr$D_IN = { awaddr, awprot } ;
  assign xactor_f_wr_addr$ENQ = awvalid && xactor_f_wr_addr$FULL_N ;

接続法 1 で mkTop にあった AND が、接続法 2 では両端の内側に一つずつ複製されています。それ以外は同じで、valid は送り手の FIFO の EMPTY_N、ready は受け手の FIFO の FULL_N です。

この変換を行っているのが bsc-contrib のトランザクタです。外側のメソッドを always_ready と always_enabled にして EN/RDY を消し、内側で FIFO の ENQ を valid && FULL_N と書いて AND を自分の中に置きます。BSV の内部は EN/RDY のまま、境界だけを valid/ready にする、というアダプタです。


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

posted by sakurai on September 16, 2026 #1121

接続法 1、EN/RDY を mkConnection でつなぐ

BSV流のEN/RDYによる接続を図1121.1に示します。

図1121.1
図1121.1 EN/RDY の接続

送り手 mkProducer は FIFO からの取り出しを Get で、受け手 mkConsumer は FIFO への書き込みを Put で公開し、最上位 mkTop が mkConnection でつなぎます。

package EnRdy;

import GetPut :: *;
import FIFOF  :: *;
import Connectable :: *;

// 送り手 : FIFO から取り出す Get
(* synthesize *)
module mkProducer (Get #(Bit #(8)));
   FIFOF #(Bit #(8)) q <- mkFIFOF;
   Reg #(Bit #(8)) cnt <- mkReg (0);

   rule fill;
      q.enq (cnt);
      cnt <= cnt + 1;
   endrule

   method ActionValue #(Bit #(8)) get;
      q.deq;
      return q.first;
   endmethod
endmodule

// 受け手 : FIFO に入れる Put
(* synthesize *)
module mkConsumer (Put #(Bit #(8)));
   FIFOF #(Bit #(8)) q <- mkFIFOF;

   rule drain;
      q.deq;
   endrule

   method Action put (Bit #(8) x);
      q.enq (x);
   endmethod
endmodule

// 最上位 : mkConnection でつなぐ
(* synthesize *)
module mkTop (Empty);
   Get #(Bit #(8)) p <- mkProducer;
   Put #(Bit #(8)) c <- mkConsumer;
   mkConnection (p, c);
endmodule

endpackage

bsc が生成した mkTop.v のモジュール本体です。

module mkTop(CLK,
	     RST_N);
  input  CLK;
  input  RST_N;

  // ports of submodule c
  wire [7 : 0] c$put;
  wire c$EN_put, c$RDY_put;

  // ports of submodule p
  wire [7 : 0] p$get;
  wire p$EN_get, p$RDY_get;

  // submodule c
  mkConsumer c(.CLK(CLK),
	       .RST_N(RST_N),
	       .put(c$put),
	       .EN_put(c$EN_put),
	       .RDY_put(c$RDY_put));

  // submodule p
  mkProducer p(.CLK(CLK),
	       .RST_N(RST_N),
	       .EN_get(p$EN_get),
	       .get(p$get),
	       .RDY_get(p$RDY_get));

  // submodule c
  assign c$put = p$get ;
  assign c$EN_put = p$RDY_get && c$RDY_put ;

  // submodule p
  assign p$EN_get = p$RDY_get && c$RDY_put ;
endmodule  // mkTop

mkConnection は Connectable 型クラスのメソッドで、インタフェースの組ごとにインスタンスが定義されています。Get と Put の組のインスタンスは、本質的に次のルール一つです。

instance Connectable #(Get #(a), Put #(a));
   module mkConnection #(Get #(a) g, Put #(a) p) (Empty);
      rule connect;
         let x <- g.get;
         p.put (x);
      endrule
   endmodule
endinstance

ルールの発火条件は、呼んでいるメソッド全部の RDY の AND です。get と put は RDY を持つ普通のメソッドなので、この接続ルールは mkTop の中の AND ゲート一つになりました。両端の RDY を集めて AND を取り、その結果を EN として両端に返しています。両端の中では、RDY は FIFO の状態そのもので、EN はそのまま FIFO の制御になります。mkProducer.v と mkConsumer.v の該当行です。

  // mkProducer.v
  assign get = q$D_OUT ;
  assign RDY_get = q$EMPTY_N ;
  assign q$DEQ = EN_get ;

  // mkConsumer.v
  assign RDY_put = q$FULL_N ;
  assign q$D_IN = put ;
  assign q$ENQ = EN_put ;

両端の判断は外の EN に委ねられ、EN で「今回は転送」と言われて動きます。


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

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

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


ページ: