stream_mux.sv
複数の入力ストリームから1つの出力ストリームへ選択的に接続するマルチプレクサモジュールである。
/// Stream multiplexer: connects the output to one of `N_INP` data streams with valid-ready /// handshaking. module stream_mux #( parameter type DATA_T = logic, // Vivado requires a default value for type parameters. parameter integer N_INP = 0, // Synopsys DC requires a default value for value parameters. /// Dependent parameters, DO NOT OVERRIDE! parameter integer LOG_N_INP = $clog2(N_INP) ) ( input DATA_T [N_INP-1:0] inp_data_i, input logic [N_INP-1:0] inp_valid_i, output logic [N_INP-1:0] inp_ready_o, input logic [LOG_N_INP-1:0] inp_sel_i, output DATA_T oup_data_o, output logic oup_valid_o, input logic oup_ready_i ); always_comb begin inp_ready_o = '0; inp_ready_o[inp_sel_i] = oup_ready_i; end assign oup_data_o = inp_data_i[inp_sel_i]; assign oup_valid_o = inp_valid_i[inp_sel_i]; `ifndef COMMON_CELLS_ASSERTS_OFF `ASSERT_INIT(n_inp_0, N_INP >= 1, "The number of inputs must be at least 1!") `endif endmodule
選択された入力のみがready信号を受け取り、それ以外の入力はready信号が0になる。
stream_demux.sv
1つの入力ストリームを複数の出力ストリームへ選択的に分配するデマルチプレクサモジュールである。 データポートを持たないため、データは外部で複数の出力に接続される必要がある。
/// Connects the input stream (valid-ready) handshake to one of `N_OUP` output stream handshakes. /// /// This module has no data ports because stream data does not need to be demultiplexed: the data of /// the input stream can just be applied at all output streams. module stream_demux #( /// Number of connected outputs. parameter int unsigned N_OUP = 32'd1, /// Dependent parameters, DO NOT OVERRIDE! parameter int unsigned LOG_N_OUP = (N_OUP > 32'd1) ? unsigned'($clog2(N_OUP)) : 1'b1 ) ( input logic inp_valid_i, output logic inp_ready_o, input logic [LOG_N_OUP-1:0] oup_sel_i, output logic [N_OUP-1:0] oup_valid_o, input logic [N_OUP-1:0] oup_ready_i ); always_comb begin oup_valid_o = '0; oup_valid_o[oup_sel_i] = inp_valid_i; end assign inp_ready_o = oup_ready_i[oup_sel_i]; endmodule
選択された出力のみがvalid信号をアサートし、選択された出力のready信号が入力のready信号になる。
stream_xbar.sv
完全結合型のストリームクロスバーモジュールである。
複数の入力から複数の出力への任意の接続を実現し、各出力に対してrr_arb_treeを使用したアービトレーションを提供する。
/// Fully connected stream crossbar. /// /// Handshaking rules as defined by the `AMBA AXI` standard on default. module stream_xbar #( /// Number of inputs into the crossbar (`> 0`). parameter int unsigned NumInp = 32'd0, /// Number of outputs from the crossbar (`> 0`). parameter int unsigned NumOut = 32'd0, /// Data width of the stream. Can be overwritten by defining the type parameter `payload_t`. parameter int unsigned DataWidth = 32'd1, /// Payload type of the data ports, only usage of parameter `DataWidth`. parameter type payload_t = logic [DataWidth-1:0], /// Adds a spill register stage at each output. parameter bit OutSpillReg = 1'b0, /// Use external priority for the individual `rr_arb_trees`. parameter int unsigned ExtPrio = 1'b0, /// Use strict AXI valid ready handshaking. /// To be protocol conform also the parameter `LockIn` has to be set. parameter int unsigned AxiVldRdy = 1'b1, /// Lock in the arbitration decision of the `rr_arb_tree`. /// When this is set, valids have to be asserted until the corresponding transaction is indicated /// by ready. parameter int unsigned LockIn = 1'b1, /// If `AxiVldReady` is 1, which bits of the payload to check for stability on valid inputs. /// In some cases, we may want to allow parts of the payload to change depending on the value of /// other parts (e.g. write data in read requests), requiring more nuanced external assertions. parameter payload_t AxiVldMask = '1, /// Derived parameter, do **not** overwrite! /// /// Width of the output selection signal. parameter int unsigned SelWidth = (NumOut > 32'd1) ? unsigned'($clog2(NumOut)) : 32'd1, /// Derived parameter, do **not** overwrite! /// /// Signal type definition for selecting the output at the inputs. parameter type sel_oup_t = logic[SelWidth-1:0], /// Derived parameter, do **not** overwrite! /// /// Width of the input index signal. parameter int unsigned IdxWidth = (NumInp > 32'd1) ? unsigned'($clog2(NumInp)) : 32'd1, /// Derived parameter, do **not** overwrite! /// /// Signal type definition indicating from which input the output came. parameter type idx_inp_t = logic[IdxWidth-1:0] ) ( /// Clock, positive edge triggered. input logic clk_i, /// Asynchronous reset, active low. input logic rst_ni, /// Flush the state of the internal `rr_arb_tree` modules. /// If not used set to `0`. /// Flush should only be used if there are no active `valid_i`, otherwise it will /// not adhere to the AXI handshaking. input logic flush_i, /// Provide an external state for the `rr_arb_tree` models. /// Will only do something if ExtPrio is `1` otherwise tie to `0`. input idx_inp_t [NumOut-1:0] rr_i, /// Input data ports. /// Has to be stable as long as `valid_i` is asserted when parameter `AxiVldRdy` is set. input payload_t [NumInp-1:0] data_i, /// Selection of the output port where the data should be routed. /// Has to be stable as long as `valid_i` is asserted and parameter `AxiVldRdy` is set. input sel_oup_t [NumInp-1:0] sel_i, /// Input is valid. input logic [NumInp-1:0] valid_i, /// Input is ready to accept data. output logic [NumInp-1:0] ready_o, /// Output data ports. Valid if `valid_o = 1` output payload_t [NumOut-1:0] data_o, /// Index of the input port where data came from. output idx_inp_t [NumOut-1:0] idx_o, /// Output is valid. output logic [NumOut-1:0] valid_o, /// Output can be accepted. input logic [NumOut-1:0] ready_i ); typedef struct packed { payload_t data; idx_inp_t idx; } spill_data_t; logic [NumInp-1:0][NumOut-1:0] inp_valid; logic [NumInp-1:0][NumOut-1:0] inp_ready; payload_t [NumOut-1:0][NumInp-1:0] out_data; logic [NumOut-1:0][NumInp-1:0] out_valid; logic [NumOut-1:0][NumInp-1:0] out_ready; // Generate the input selection for (genvar i = 0; unsigned'(i) < NumInp; i++) begin : gen_inps stream_demux #( .N_OUP ( NumOut ) ) i_stream_demux ( .inp_valid_i ( valid_i[i] ), .inp_ready_o ( ready_o[i] ), .oup_sel_i ( sel_i[i] ), .oup_valid_o ( inp_valid[i] ), .oup_ready_i ( inp_ready[i] ) ); // Do the switching cross of the signals. for (genvar j = 0; unsigned'(j) < NumOut; j++) begin : gen_cross // Propagate the data from this input to all outputs. assign out_data[j][i] = data_i[i]; // switch handshaking assign out_valid[j][i] = inp_valid[i][j]; assign inp_ready[i][j] = out_ready[j][i]; end end // Generate the output arbitration. for (genvar j = 0; unsigned'(j) < NumOut; j++) begin : gen_outs spill_data_t arb; logic arb_valid, arb_ready; rr_arb_tree #( .NumIn ( NumInp ), .DataType ( payload_t ), .ExtPrio ( ExtPrio ), .AxiVldRdy ( AxiVldRdy ), .LockIn ( LockIn ) ) i_rr_arb_tree ( .clk_i, .rst_ni, .flush_i, .rr_i ( rr_i[j] ), .req_i ( out_valid[j] ), .gnt_o ( out_ready[j] ), .data_i ( out_data[j] ), .req_o ( arb_valid ), .gnt_i ( arb_ready ), .data_o ( arb.data ), .idx_o ( arb.idx ) ); spill_data_t spill; spill_register #( .T ( spill_data_t ), .Bypass ( !OutSpillReg ) ) i_spill_register ( .clk_i, .rst_ni, .valid_i ( arb_valid ), .ready_o ( arb_ready ), .data_i ( arb ), .valid_o ( valid_o[j] ), .ready_i ( ready_i[j] ), .data_o ( spill ) ); // Assign the outputs (deaggregate the data). always_comb begin data_o[j] = spill.data; idx_o[j] = spill.idx; end end // Assertions // Make sure that the handshake and payload is stable `ifndef COMMON_CELLS_ASSERTS_OFF for (genvar i = 0; unsigned'(i) < NumInp; i++) begin : gen_sel_assertions `ASSERT(non_existing_output, valid_i[i] |-> sel_i[i] < NumOut, clk_i, !rst_ni, "Non-existing output is selected!") end if (AxiVldRdy) begin : gen_handshake_assertions for (genvar i = 0; unsigned'(i) < NumInp; i++) begin : gen_inp_assertions `ASSERT(input_data_unstable, valid_i[i] && !ready_o[i] |=> $stable(data_i[i] & AxiVldMask), clk_i, !rst_ni, $sformatf("data_i is unstable at input: %0d", i)) `ASSERT(input_sel_unstable, valid_i[i] && !ready_o[i] |=> $stable(sel_i[i]), clk_i, !rst_ni, $sformatf("sel_i is unstable at input: %0d", i)) `ASSERT(input_valid_taken, valid_i[i] && !ready_o[i] |=> valid_i[i], clk_i, !rst_ni, $sformatf("valid_i at input %0d has been taken away without a ready.", i)) end for (genvar i = 0; unsigned'(i) < NumOut; i++) begin : gen_out_assertions `ASSERT(output_data_unstable, valid_o[i] && !ready_i[i] |=> $stable(data_o[i] & AxiVldMask), clk_i, !rst_ni, $sformatf("data_o is unstable at output: %0d Check that parameter LockIn is set.", i)) `ASSERT(output_idx_unstable, valid_o[i] && !ready_i[i] |=> $stable(idx_o[i]), clk_i, !rst_ni, $sformatf("idx_o is unstable at output: %0d Check that parameter LockIn is set.", i)) `ASSERT(output_valid_taken, valid_o[i] && !ready_i[i] |=> valid_o[i], clk_i, !rst_ni, $sformatf("valid_o at output %0d has been taken away without a ready.", i)) end end `ASSERT_INIT(numinp_0, NumInp > 32'd0, "NumInp has to be > 0!") `ASSERT_INIT(numout_0, NumOut > 32'd0, "NumOut has to be > 0!") `endif endmodule
主要パラメータの説明:
NumInp(Number of inputs): クロスバーへの入力ポート数を指定する。この値は0より大きい必要がある。各入力に対してstream_demuxモジュールが生成され、入力データを選択された出力ポートへルーティングする。入力ポート数に応じて、IdxWidthパラメータ(入力インデックス信号の幅)が自動的に計算される($clog2(NumInp))。NumOut(Number of outputs): クロスバーからの出力ポート数を指定する。この値は0より大きい必要がある。各出力に対してrr_arb_treeモジュールが生成され、複数の入力からの要求をアービトレーションする。出力ポート数に応じて、SelWidthパラメータ(出力選択信号の幅)が自動的に計算される($clog2(NumOut))。
クロスバーは完全結合型であるため、NumInp個の入力からNumOut個の出力への任意の接続が可能である。各入力はsel_i信号により、どの出力ポートへデータを送信するかを指定する。
その他のパラメータ:
DataWidth: ストリームのデータ幅を指定する。デフォルト値は1。payload_tパラメータで型を指定した場合は、このパラメータは使用されない。payload_t: ストリームで転送するデータの型を指定する。デフォルト値はlogic [DataWidth-1:0]。カスタム型(構造体など)を指定することで、任意のデータ構造を転送できる。OutSpillReg: 各出力にスピルレジスタステージを追加するかどうかを指定する。デフォルト値は0(追加しない)。1に設定すると、各出力にレジスタステージが追加され、タイミング特性が改善されるが、レイテンシが1クロックサイクル増加する。ExtPrio: 各出力のrr_arb_treeで外部優先度を使用するかどうかを指定する。デフォルト値は0(使用しない)。1に設定すると、rr_i信号により外部から優先度を制御できる。AxiVldRdy: 厳密なAXI準拠のready/validハンドシェイクを使用するかどうかを指定する。デフォルト値は1(使用する)。1の場合、valid_iがアサートされている間、data_iとsel_iが安定している必要がある。プロトコル準拠のためには、LockInパラメータも1に設定する必要がある。LockIn: アービトレーション決定をロックするかどうかを指定する。デフォルト値は1(ロックする)。1の場合、valid_iは対応するトランザクションがready_oで示されるまでアサートされ続ける必要がある。これにより、AXI準拠の動作が保証される。AxiVldMask:AxiVldRdyが1の場合、ペイロードのどのビットを安定性チェックの対象とするかを指定する。デフォルト値は'1(すべてのビット)。一部のビットのみをチェック対象としたい場合(例: リードリクエスト内のライトデータなど)に使用する。SelWidth,sel_oup_t: 派生パラメータ(上書き禁止)。SelWidthは出力選択信号の幅($clog2(NumOut))、sel_oup_tはその型定義。各入力のsel_i信号の型として使用される。IdxWidth,idx_inp_t: 派生パラメータ(上書き禁止)。IdxWidthは入力インデックス信号の幅($clog2(NumInp))、idx_inp_tはその型定義。各出力のidx_o信号(データがどの入力から来たかを示す)の型として使用される。
クロスバーの構成図 (NumInp=3, NumOut=3の例):
入力側 クロス接続部 出力側
┌───────────┐ ┌───────────┐
│ Input 0 │───┐ │Output 0 │
│ │ │ ┌──────────┐ ┌──────────┐ │ │
│ sel_i[0] │───┼──┤ │ │ │──┼──┤ │
│ data_i[0] │ │ │ │ │ │ │ │ data_o[0] │
│ valid_i[0]│ │ │ │ │ │ │ │ valid_o[0]│
│ ready_o[0]│ │ │ │ │ │ │ │ ready_i[0]│
└───────────┘ │ │ │ │ │ │ └───────────┘
│ │ │ │ │ │
┌───────────┐ │ │ stream_ │ │ rr_arb_ │ │ ┌───────────┐
│ Input 1 │───┼──┤ demux[0] │ │ tree[0] │──┼──│Output 1 │
│ │ │ │ │ │ │ │ │ │
│ sel_i[1] │───┼──┤ │ │ │──┼──┤ │
│ data_i[1] │ │ │ │ │ │ │ │ data_o[1] │
│ valid_i[1]│ │ │ │ │ │ │ │ valid_o[1]│
│ ready_o[1]│ │ │ │ │ │ │ │ ready_i[1]│
└───────────┘ │ │ │ │ │ │ └───────────┘
│ │ │ │ │ │
┌───────────┐ │ │ │ │ │ │ ┌───────────┐
│ Input 2 │───┼──┤ │ │ │──┼──│Output 2 │
│ │ │ │ │ │ │ │ │ │
│ sel_i[2] │───┼──┤ │ │ │──┼──┤ │
│ data_i[2] │ │ │ │ │ │ │ │ data_o[2] │
│ valid_i[2]│ │ │ │ │ │ │ │ valid_o[2]│
│ ready_o[2]│ │ │ │ │ │ │ │ ready_i[2]│
└───────────┘ │ └──────────┘ └──────────┘ │ └───────────┘
│ │
└──────────────────────────────────┘
(完全結合型の接続)
データフローの詳細:
各入力はstream_demuxにより、sel_i信号で指定された出力へルーティングされる。
すべての入力のデータはすべての出力に接続されており、各出力のrr_arb_treeが複数の入力からの要求をアービトレーションする。
例: Input 0がOutput 2へ、Input 1がOutput 0へ、Input 2がOutput 1へ送信する場合
Input 0 ──[sel_i=2]──> stream_demux[0] ──> すべての出力へ接続
│
├─> Output 0 (rr_arb_tree[0]でアービトレーション)
├─> Output 1 (rr_arb_tree[1]でアービトレーション)
└─> Output 2 (rr_arb_tree[2]でアービトレーション) ← 選択
Input 1 ──[sel_i=0]──> stream_demux[1] ──> すべての出力へ接続
│
├─> Output 0 (rr_arb_tree[0]でアービトレーション) ← 選択
├─> Output 1 (rr_arb_tree[1]でアービトレーション)
└─> Output 2 (rr_arb_tree[2]でアービトレーション)
Input 2 ──[sel_i=1]──> stream_demux[2] ──> すべての出力へ接続
│
├─> Output 0 (rr_arb_tree[0]でアービトレーション)
├─> Output 1 (rr_arb_tree[1]でアービトレーション) ← 選択
└─> Output 2 (rr_arb_tree[2]でアービトレーション)
アービトレーションの仕組み:
各出力にはrr_arb_treeが接続されており、複数の入力からの同時要求を処理する。
ラウンドロビン方式で公平にアービトレーションが行われる。
Output 0への要求:
Input 0 ──[valid]──> rr_arb_tree[0] ──> Output 0
Input 1 ──[valid]──> rr_arb_tree[0] ──> Output 0
Input 2 ──[valid]──> rr_arb_tree[0] ──> Output 0
│
└─> ラウンドロビンで1つを選択
オプション: スピルレジスタ (OutSpillReg=1の場合):
各出力にスピルレジスタが追加され、タイミング特性が改善される。
rr_arb_tree[0] ──> spill_register ──> Output 0 rr_arb_tree[1] ──> spill_register ──> Output 1 rr_arb_tree[2] ──> spill_register ──> Output 2
クロスバーは以下のように動作する:
1. 各入力はstream_demuxにより選択された出力へルーティングされる
2. 各出力に対して、複数の入力からの要求をrr_arb_treeでアービトレーションする
3. オプションで出力にスピルレジスタを追加可能
stream_omega_net.sv
オメガネットワーク(バタフライネットワークと同型)を実装したモジュールである。
複数のstream_xbarをスイッチとして使用し、大規模な接続を効率的に実現する。
/// Omega network using multiple `stream_xbar` as switches. /// /// An omega network is isomorphic to a butterfly network. /// /// Handshaking rules as defined by the `AMBA AXI` standard on default. module stream_omega_net #( /// Number of inputs into the network (`> 0`). parameter int unsigned NumInp = 32'd0, /// Number of outputs from the network (`> 0`). parameter int unsigned NumOut = 32'd0, /// Radix of the individual switch points of the network. /// Currently supported are `32'd2` and `32'd4`. parameter int unsigned Radix = 32'd2, /// Data width of the stream. Can be overwritten by defining the type parameter `payload_t`. parameter int unsigned DataWidth = 32'd1, /// Payload type of the data ports, only usage of parameter `DataWidth`. parameter type payload_t = logic [DataWidth-1:0], /// Adds a spill register stage at each output. parameter bit SpillReg = 1'b0, /// Use external priority for the individual `rr_arb_trees`. parameter int unsigned ExtPrio = 1'b0, /// Use strict AXI valid ready handshaking. /// To be protocol conform also the parameter `LockIn` has to be set. parameter int unsigned AxiVldRdy = 1'b1, /// Lock in the arbitration decision of the `rr_arb_tree`. /// When this is set, valids have to be asserted until the corresponding transaction is indicated /// by ready. parameter int unsigned LockIn = 1'b1, /// If `AxiVldReady` is 1, which bits of the payload to check for stability on valid inputs. /// In some cases, we may want to allow parts of the payload to change depending on the value of /// other parts (e.g. write data in read requests), requiring more nuanced external assertions. parameter payload_t AxiVldMask = '1, /// Derived parameter, do **not** overwrite! /// /// Width of the output selection signal. parameter int unsigned SelWidth = (NumOut > 32'd1) ? unsigned'($clog2(NumOut)) : 32'd1, /// Derived parameter, do **not** overwrite! /// /// Signal type definition for selecting the output at the inputs. parameter type sel_oup_t = logic[SelWidth-1:0], /// Derived parameter, do **not** overwrite! /// /// Width of the input index signal. parameter int unsigned IdxWidth = (NumInp > 32'd1) ? unsigned'($clog2(NumInp)) : 32'd1, /// Derived parameter, do **not** overwrite! /// /// Signal type definition indicating from which input the output came. parameter type idx_inp_t = logic[IdxWidth-1:0] ) ( /// Clock, positive edge triggered. input logic clk_i, /// Asynchronous reset, active low. input logic rst_ni, /// Flush the state of the internal `rr_arb_tree` modules. /// If not used set to `0`. /// Flush should only be used if there are no active `valid_i`, otherwise it will /// not adhere to the AXI handshaking. input logic flush_i, /// Provide an external state for the `rr_arb_tree` models. /// Will only do something if ExtPrio is `1` otherwise tie to `0`. input idx_inp_t [NumOut-1:0] rr_i, /// Input data ports. /// Has to be stable as long as `valid_i` is asserted when parameter `AxiVldRdy` is set. input payload_t [NumInp-1:0] data_i, /// Selection of the output port where the data should be routed. /// Has to be stable as long as `valid_i` is asserted and parameter `AxiVldRdy` is set. input sel_oup_t [NumInp-1:0] sel_i, /// Input is valid. input logic [NumInp-1:0] valid_i, /// Input is ready to accept data. output logic [NumInp-1:0] ready_o, /// Output data ports. Valid if `valid_o = 1` output payload_t [NumOut-1:0] data_o, /// Index of the input port where data came from. output idx_inp_t [NumOut-1:0] idx_o, /// Output is valid. output logic [NumOut-1:0] valid_o, /// Output can be accepted. input logic [NumOut-1:0] ready_i );
主要パラメータの説明:
NumInp(Number of inputs): ネットワークへの入力ポート数を指定する。この値は0より大きい必要がある。NumOut(Number of outputs): ネットワークからの出力ポート数を指定する。この値は0より大きい必要がある。Radix: ネットワーク内の各スイッチポイントのラディクス(基数)を指定する。現在サポートされている値は2と4のみ。ラディクスは、各スイッチが同時に処理できる入出力の数を決定する。Radix=2の場合、各スイッチは2入力×2出力、Radix=4の場合は4入力×4出力となる。ラディクスが大きいほど、より多くの接続を効率的に処理できるが、ハードウェアリソースも増加する。
動作の特徴:
1. 縮退ケース (NumInp <= Radix && NumOut <= Radix):
入力数と出力数がラディクス以下の場合は、stream_xbarに縮退する。これは、単一のクロスバーで十分に接続を実現できるためである。
例: NumInp=2, NumOut=2, Radix=2の場合
Input 0 ──┐
├──> stream_xbar ──┬──> Output 0
Input 1 ──┘ └──> Output 1
2. 多段ネットワーク (それ以外の場合):
複数段のルーターを構成する。各段でperfect shuffle(完全シャッフル)を行い、パケットを目的地へルーティングする。
オメガネットワークの構造図 (NumInp=8, NumOut=8, Radix=2の例):
Perfect Shuffleの概念:
各段の間でperfect shuffleが行われ、パケットが再配置される。これにより、任意の入力から任意の出力への接続が可能になる。
Perfect Shuffle (Radix=2の場合): 段0の出力 → 段1の入力への接続パターン Router 0, Output 0 ──> Router 0, Input 0 Router 0, Output 1 ──> Router 1, Input 0 Router 1, Output 0 ──> Router 0, Input 1 Router 1, Output 1 ──> Router 1, Input 1 ...
各スイッチの構造:
各段の各ルーターは、stream_xbarで実装されたRadix×Radixのクロスバーである。
各ルーター (Radix=2の場合):
┌─────────────┐
Input 0 ─┤ ├──> Output 0
│ stream_xbar │
Input 1 ─┤ (2×2) ├──> Output 1
└─────────────┘
ルーティングの仕組み:
各パケットは、目的地の出力アドレスのビットを使用して、各段でルーティングされる。 - 段0: 最上位ビットを使用 - 段1: 次のビットを使用 - 段2: 最下位ビットを使用
これにより、段数はceil(log_Radix(NumLanes))となる。
オメガネットワークは、バタフライネットワークと同型であり、大規模な接続を効率的に実現するための多段ネットワークトポロジーを提供する。