stream_fork.sv
1つの入力ストリームを複数の出力ストリームへ分配するモジュールである。 各入力ハンドシェイクに対して、すべての出力が正確に1回ハンドシェイクする。 入力はすべての出力がハンドシェイクした後にのみハンドシェイクするが、出力は同時にハンドシェイクする必要はない。
// Stream fork: Connects the input stream (ready-valid) handshake to *all* of `N_OUP` output stream // handshakes. For each input stream handshake, every output stream handshakes exactly once. The // input stream only handshakes when all output streams have handshaked, but the output streams do // not have to handshake simultaneously. // // This module has no data ports because stream data does not need to be forked: the data of the // input stream can just be applied at all output streams. `include "common_cells/assertions.svh" module stream_fork #( parameter int unsigned N_OUP = 0 // Synopsys DC requires a default value for parameters. ) ( input logic clk_i, input logic rst_ni, input logic valid_i, output logic ready_o, output logic [N_OUP-1:0] valid_o, input logic [N_OUP-1:0] ready_i ); typedef enum logic {READY, WAIT} state_t; logic [N_OUP-1:0] oup_ready, all_ones; state_t inp_state_d, inp_state_q; // Input control FSM always_comb begin // ready_o = 1'b0; inp_state_d = inp_state_q; unique case (inp_state_q) READY: begin if (valid_i) begin if (valid_o == all_ones && ready_i == all_ones) begin // If handshake on all outputs, handshake on input. ready_o = 1'b1; end else begin ready_o = 1'b0; // Otherwise, wait for inputs that did not handshake yet. inp_state_d = WAIT; end end else begin ready_o = 1'b0; end end WAIT: begin if (valid_i && oup_ready == all_ones) begin ready_o = 1'b1; inp_state_d = READY; end else begin ready_o = 1'b0; end end default: begin inp_state_d = READY; ready_o = 1'b0; end endcase end always_ff @(posedge clk_i, negedge rst_ni) begin if (!rst_ni) begin inp_state_q <= READY; end else begin inp_state_q <= inp_state_d; end end // Output control FSM for (genvar i = 0; i < N_OUP; i++) begin: gen_oup_state state_t oup_state_d, oup_state_q; always_comb begin oup_ready[i] = 1'b1; valid_o[i] = 1'b0; oup_state_d = oup_state_q; unique case (oup_state_q) READY: begin if (valid_i) begin valid_o[i] = 1'b1; if (ready_i[i]) begin // Output handshake if (!ready_o) begin // No input handshake yet oup_state_d = WAIT; end end else begin // No output handshake oup_ready[i] = 1'b0; end end end WAIT: begin if (valid_i && ready_o) begin // Input handshake oup_state_d = READY; end end default: begin oup_state_d = READY; end endcase end always_ff @(posedge clk_i, negedge rst_ni) begin if (!rst_ni) begin oup_state_q <= READY; end else begin oup_state_q <= oup_state_d; end end end assign all_ones = '1; // Synthesis fix for Vivado, which does not correctly compute the width // of the '1 literal when assigned to a port of parametrized width. `ifndef COMMON_CELLS_ASSERTS_OFF `ASSERT_INIT(n_oup_0, N_OUP >= 1, "Number of outputs must be at least 1!") `endif endmodule
ステートマシンの解析:
stream_forkは、入力側と各出力側の2種類のステートマシンで構成される。
1. 入力側ステートマシン (inp_state_q):
入力側はREADYとWAITの2状態を持つ。
READY状態: 新しい入力ハンドシェイクを受け入れる準備ができている状態valid_iがアサートされ、かつすべての出力が同時にハンドシェイクした場合(valid_o == all_ones && ready_i == all_ones)、入力のready_oをアサートして入力ハンドシェイクを完了し、状態はREADYのままvalid_iがアサートされたが、すべての出力が同時にハンドシェイクしなかった場合、ready_oを0にし、状態をWAITに遷移valid_iがアサートされていない場合、ready_oは0
WAIT状態: すべての出力がハンドシェイクするのを待っている状態valid_iがアサートされており、かつすべての出力が準備できている場合(oup_ready == all_ones)、ready_oをアサートして入力ハンドシェイクを完了し、状態をREADYに戻す- それ以外の場合、
ready_oは0のままWAIT状態を維持
2. 出力側ステートマシン (oup_state_q[i]):
各出力は独立したREADYとWAITの2状態を持つ。
READY状態: 出力がハンドシェイクを受け入れる準備ができている状態valid_iがアサートされている場合、valid_o[i]をアサート- 出力がハンドシェイクした場合(
ready_i[i] == 1):- 入力がまだハンドシェイクしていない場合(
ready_o == 0)、状態をWAITに遷移(出力はハンドシェイク済みだが、入力のハンドシェイクを待つ) - 入力もハンドシェイクした場合、状態は
READYのまま
- 入力がまだハンドシェイクしていない場合(
- 出力がハンドシェイクしていない場合(
ready_i[i] == 0)、oup_ready[i]を0にして、出力の準備ができていないことを示す
WAIT状態: 入力のハンドシェイクを待っている状態valid_iがアサートされており、かつ入力がハンドシェイクした場合(ready_o == 1)、状態をREADYに戻す- それ以外の場合、
WAIT状態を維持
動作の流れ (例: N_OUP=3の場合):
タイムライン例:
Cycle 0: valid_i=1, ready_i={1,0,0}
- 入力FSM: READY → WAIT (すべての出力が同時にハンドシェイクしていない)
- 出力0: READY → WAIT (ハンドシェイクしたが、入力がまだ)
- 出力1: READY (valid_o[1]=1, ready_i[1]=0なので oup_ready[1]=0)
- 出力2: READY (valid_o[2]=1, ready_i[2]=0なので oup_ready[2]=0)
- ready_o = 0
Cycle 1: valid_i=1, ready_i={1,1,0}
- 入力FSM: WAIT (oup_ready != all_ones)
- 出力0: WAIT (入力のハンドシェイク待ち)
- 出力1: READY → WAIT (ハンドシェイクしたが、入力がまだ)
- 出力2: READY (valid_o[2]=1, ready_i[2]=0なので oup_ready[2]=0)
- ready_o = 0
Cycle 2: valid_i=1, ready_i={1,1,1}
- 入力FSM: WAIT → READY (すべての出力が準備できた)
- 出力0: WAIT → READY (入力がハンドシェイクした)
- 出力1: WAIT → READY (入力がハンドシェイクした)
- 出力2: READY → WAIT (ハンドシェイクしたが、入力がまだ)
- ready_o = 1 (入力ハンドシェイク完了)
Cycle 3: valid_i=0
- すべてのFSMがREADY状態に戻る
stream_fork_dynamic.sv
動的に選択可能なフォークモジュールである。別のストリームから提供されるビットマスクにより、どの出力ストリームにハンドシェイクするかを動的に決定する。
/// Dynamic stream fork: Connects the input stream (ready-valid) handshake to a combination of output /// stream handshake. The combination is determined dynamically through another stream, which /// provides a bitmask for the fork. For each input stream handshake, every output stream handshakes /// exactly once. The input stream only handshakes when all output streams have handshaked, but the /// output streams do not have to handshake simultaneously. /// /// This module has no data ports because stream data does not need to be forked: the data of the /// input stream can just be applied at all output streams. module stream_fork_dynamic #( /// Number of output streams parameter int unsigned N_OUP = 32'd0 // Synopsys DC requires a default value for parameters. ) ( /// Clock input logic clk_i, /// Asynchronous reset, active low input logic rst_ni, /// Input stream valid handshake, input logic valid_i, /// Input stream ready handshake output logic ready_o, /// Selection mask for the output handshake input logic [N_OUP-1:0] sel_i, /// Selection mask valid input logic sel_valid_i, /// Selection mask ready output logic sel_ready_o, /// Output streams valid handshakes output logic [N_OUP-1:0] valid_o, /// Output streams ready handshakes input logic [N_OUP-1:0] ready_i ); logic int_inp_valid, int_inp_ready; logic [N_OUP-1:0] int_oup_valid, int_oup_ready; // Output handshaking for (genvar i = 0; i < N_OUP; i++) begin : gen_oups always_comb begin valid_o[i] = 1'b0; int_oup_ready[i] = 1'b0; if (sel_valid_i) begin if (sel_i[i]) begin valid_o[i] = int_oup_valid[i]; int_oup_ready[i] = ready_i[i]; end else begin int_oup_ready[i] = 1'b1; end end end end // Input handshaking always_comb begin int_inp_valid = 1'b0; ready_o = 1'b0; sel_ready_o = 1'b0; if (sel_valid_i) begin int_inp_valid = valid_i; ready_o = int_inp_ready; sel_ready_o = int_inp_ready; end end stream_fork #( .N_OUP ( N_OUP ) ) i_fork ( .clk_i, .rst_ni, .valid_i ( int_inp_valid ), .ready_o ( int_inp_ready ), .valid_o ( int_oup_valid ), .ready_i ( int_oup_ready ) ); `ifndef COMMON_CELLS_ASSERTS_OFF `ASSERT_INIT(n_oup_0, N_OUP >= 1, "N_OUP must be at least 1!") `endif endmodule
内部でstream_forkを使用し、選択マスクにより特定の出力のみにハンドシェイクを分配する。
stream_join.sv
複数の入力ストリームを1つの出力ストリームへ結合するモジュールである。 すべての入力がvalidの場合にのみ出力がvalidになる。
/// Stream join: Joins a parametrizable number of input streams (i.e., valid-ready handshaking with /// dependency rules as in AXI4) to a single output stream. The output handshake happens only once /// all inputs are valid. The data channel flows outside of this module. module stream_join #( /// Number of input streams parameter int unsigned N_INP = 32'd0 // Synopsys DC requires a default value for parameters. ) ( /// Input streams valid handshakes input logic [N_INP-1:0] inp_valid_i, /// Input streams ready handshakes output logic [N_INP-1:0] inp_ready_o, /// Output stream valid handshake output logic oup_valid_o, /// Output stream ready handshake input logic oup_ready_i ); stream_join_dynamic #( .N_INP(N_INP) ) i_stream_join_dynamic ( .inp_valid_i(inp_valid_i), .inp_ready_o(inp_ready_o), .sel_i ({N_INP{1'b1}}), .oup_valid_o(oup_valid_o), .oup_ready_i(oup_ready_i) ); endmodule
内部でstream_join_dynamicを使用し、すべての入力を選択するマスクを渡す。
stream_join_dynamic.sv
動的に選択可能なジョインモジュールである。 選択マスクにより、どの入力ストリームを結合するかを動的に決定する。
// Stream join dynamic: Joins a parametrizable number of input streams (i.e. valid-ready // handshaking with dependency rules as in AXI4) to a single output stream. The subset of streams // to join can be configured dynamically via `sel_i`. The output handshake happens only after // there has been a handshake. The data channel flows outside of this module. module stream_join_dynamic #( /// Number of input streams parameter int unsigned N_INP = 32'd0 // Synopsys DC requires a default value for parameters. ) ( /// Input streams valid handshakes input logic [N_INP-1:0] inp_valid_i, /// Input streams ready handshakes output logic [N_INP-1:0] inp_ready_o, /// Selection mask for the output handshake input logic [N_INP-1:0] sel_i, /// Output stream valid handshake output logic oup_valid_o, /// Output stream ready handshake input logic oup_ready_i ); // Corner case when `sel_i` is all 0s should not generate valid assign oup_valid_o = &(inp_valid_i | ~sel_i) && |sel_i; for (genvar i = 0; i < N_INP; i++) begin : gen_inp_ready assign inp_ready_o[i] = oup_valid_o & oup_ready_i & sel_i[i]; end `ifndef COMMON_CELLS_ASSERTS_OFF `ASSERT_INIT(n_inp_0, N_INP >= 1, "N_INP must be at least 1!") `endif endmodule
選択されたすべての入力がvalidの場合にのみ出力がvalidになり、選択された入力のみがready信号を受け取る。
stream_forkとstream_joinのユースケース:
stream_forkとstream_joinは必ずしもペアで使う必要はないが、組み合わせて使用する典型的なパターンがある。
1. ペアで使用する場合: 並列処理パイプライン
1つのストリームを複数の処理ユニットに分配し、処理後に結果を結合するパターン。
入力ストリーム
│
├──> stream_fork ──┬──> 処理ユニット0 ──┐
│ ├──> 処理ユニット1 ──┤
│ └──> 処理ユニット2 ──┘
│ │
│ ├──> stream_join ──> 出力ストリーム
│ │
data_i ──────────────────────────────┘ (データは外部で接続)
使用例: - 画像処理: 1つの画像データを複数のフィルタ処理ユニットに分配し、結果を結合 - データ変換: 1つのデータストリームを複数の変換処理に分配し、変換結果を統合 - 検証・監視: 同じデータを複数の検証モジュールに送り、すべての検証が完了したら次のデータを処理
2. stream_forkのみを使用する場合: ブロードキャスト/モニタリング
1つのストリームを複数の宛先に同時に送信するパターン。データは同じものを複数の場所に送るだけ。
入力ストリーム
│
├──> stream_fork ──┬──> モニター0 (ログ記録)
│ ├──> モニター1 (デバッグ)
│ └──> メイン処理パス
│
data_i ────────────┴──> (すべての出力に同じデータを接続)
使用例: - デバッグ: メインデータパスを変更せずに、デバッグ用のモニターを追加 - ログ記録: 同じデータを複数のログシステムに送信 - 冗長処理: 同じデータを複数のバックアップ処理ユニットに送信
3. stream_joinのみを使用する場合: マージ/統合
複数の独立したストリームを1つのストリームに統合するパターン。
ストリーム0 ──┐ ストリーム1 ──┼──> stream_join ──> 出力ストリーム ストリーム2 ──┘
使用例: - マルチソース統合: 複数のデータソースからの結果を1つのストリームに統合 - パイプライン統合: 複数の並列パイプラインの結果を1つにまとめる - リクエスト統合: 複数のリクエストストリームを1つの処理パスに統合