Posts Tagged with "back pressure"

既に発行済みのブログであっても適宜修正・追加することがあります。
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 どうしを配線だけでつなぐための約束です。


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