FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

common_cells リポジトリに含まれる stream_モジュールの構成分析 (2. ストリーム・インタフェースの基本モジュール)

stream_ モジュール群についての解析を行っている。 まずは、基本的なストリーム・モジュールについて。

stream_register.sv

シンプルなready/validハンドシェイクを備えたレジスタモジュールである。 すべての制御信号の組み合わせパスを切断しないため、完全なパス切断が必要な場合はspill_registerを使用すべきである。

/// Register with a simple stream-like ready/valid handshake.
/// This register does not cut combinatorial paths on all control signals; if you need a complete
/// cut, use the `spill_register`.
module stream_register #(
    parameter type T = logic  // Vivado requires a default value for type parameters.
) (
    input  logic    clk_i,          // Clock
    input  logic    rst_ni,         // Asynchronous active-low reset
    input  logic    clr_i,          // Synchronous clear
    input  logic    testmode_i,     // Test mode to bypass clock gating
    // Input port
    input  logic    valid_i,
    output logic    ready_o,
    input  T        data_i,
    // Output port
    output logic    valid_o,
    input  logic    ready_i,
    output T        data_o
);

    logic reg_ena;
    assign ready_o = ready_i | ~valid_o;
    assign reg_ena = valid_i & ready_o;
    // Load-enable FFs with synch clear
    `FFLARNC(valid_o, valid_i, ready_o, clr_i, 1'b0  , clk_i, rst_ni)
    `FFLARNC(data_o,   data_i, reg_ena, clr_i, T'('0), clk_i, rst_ni)

endmodule

ready_oの条件はassign ready_o = ready_i | ~valid_o;で定義されている。 これは、以下の2つの条件のいずれかが満たされた場合に、上流に対して準備できている(ready_o = 1)ことを示す。

  1. 下流が準備できている場合 (ready_i = 1): 下流がデータを受け取る準備ができている場合、レジスタに保持されているデータが次のクロックサイクルで下流に転送されるため、レジスタは空き状態になり、新しいデータを受け入れられる。

  2. レジスタが空いている場合 (valid_o = 0、つまり~valid_o = 1): レジスタにデータが保持されていない場合、常に新しいデータを受け入れられる。

逆に、レジスタにデータが保持されている(valid_o = 1)かつ下流が準備できていない(ready_i = 0)場合のみ、ready_o = 0となり、上流からのデータ受け入れを停止する。 (これは論理的には valid_o & ~ready_i でデータを受け入れ停止することを意味し、つまりこの否定 ~(valid_o & ~ready_i) = ~valid_o | ready_i が受け入れ条件として成立している)。 これは、1段のレジスタしかないため、保持されているデータが下流に転送されるまで、新しいデータを受け入れられないことを意味する。

レジスタイネーブル信号reg_enavalid_i & ready_oで生成され、上流がデータを有効にしている(valid_i = 1)かつ、レジスタが受け入れ可能な状態(ready_o = 1)の場合にのみ、データがレジスタにロードされる。

stream_fifo.sv

任意の深さ(0から232まで)をサポートするストリームFIFOモジュールである。フォールスルーモードをサポートし、fifo_v3を内部で使用している。

module stream_fifo #(
    /// FIFO is in fall-through mode
    parameter bit          FALL_THROUGH = 1'b0,
    /// Default data width if the fifo is of type logic
    parameter int unsigned DATA_WIDTH   = 32,
    /// Depth can be arbitrary from 0 to 2**32
    parameter int unsigned DEPTH        = 8,
    parameter type         T            = logic [DATA_WIDTH-1:0],
    // DO NOT OVERWRITE THIS PARAMETER
    parameter int unsigned ADDR_DEPTH  = (DEPTH > 1) ? $clog2(DEPTH) : 1
) (
    input  logic                  clk_i,      // Clock
    input  logic                  rst_ni,     // Asynchronous reset active low
    input  logic                  flush_i,    // flush the fifo
    input  logic                  testmode_i, // test_mode to bypass clock gating
    output logic [ADDR_DEPTH-1:0] usage_o,    // fill pointer
    // input interface
    input  T                      data_i,     // data to push into the fifo
    input  logic                  valid_i,    // input data valid
    output logic                  ready_o,    // fifo is not full
    // output interface
    output T                      data_o,     // output data
    output logic                  valid_o,    // fifo is not empty
    input  logic                  ready_i     // pop head from fifo
);

    logic push, pop;
    logic empty, full;

    assign push    = valid_i & ~full;
    assign pop     = ready_i & ~empty;
    assign ready_o = ~full;
    assign valid_o = ~empty;

    fifo_v3 #(
        .FALL_THROUGH   (FALL_THROUGH),
        .DATA_WIDTH     (DATA_WIDTH),
        .DEPTH          (DEPTH),
        .dtype(T)
    ) fifo_i (
        .clk_i,
        .rst_ni,
        .flush_i,
        .testmode_i,
        .full_o     (full),
        .empty_o    (empty),
        .usage_o,
        .data_i,
        .push_i     (push),
        .data_o,
        .pop_i      (pop)
    );

endmodule

ready_oの条件はassign ready_o = ~full;で定義されている。 これは、FIFOが満杯でない場合(full0の場合)にready_o1になることを意味する。 つまり、FIFOに空きがある限り、上流からのデータを受け入れる準備ができていることを示す。

実際のデータプッシュ操作は、assign push = valid_i & ~full;で制御される。 上流がvalid_iをアサートし、かつFIFOが満杯でない場合にのみ、データがFIFOにプッシュされる。 この設計により、FIFOが満杯の場合は上流に対してready_o = 0を返し、データの受け入れを一時的に停止する。 これにより、FIFOのオーバーフローを防止し、ready/validハンドシェイクプロトコルに準拠した動作を実現している。

同様に、出力側のvalid_oassign valid_o = ~empty;で定義されており、FIFOが空でない場合にデータが有効であることを示す。

stream_fifo_optimal_wrap.sv

深さに応じて最適な実装を選択するラッパーモジュールである。 深さが2の場合はspill_registerを使用し、深さが3以上の場合はstream_fifoを使用する。 深さ0と1の設定は無意味であるため、エラーを発生させる。

/// Optimal implementation of a stream FIFO based on the common cells modules.
/// Selects the smaller and faster spill register if the depth is 2 and the FIFO if
/// the depth is >2. Throws an error for the meaningless configurations depth 0 and 1.
module stream_fifo_optimal_wrap #(
    /// Depth can be arbitrary from 2 to 2**32
    parameter int unsigned Depth = 32'd8,
    /// Type of the FIFO
    parameter type type_t = logic,
    /// Print information when the simulation launches
    parameter bit PrintInfo = 1'b0,
    // DO NOT OVERWRITE THIS PARAMETER
    parameter int unsigned AddrDepth  = (Depth > 32'd1) ? $clog2(Depth) : 32'd1
) (
    input  logic                 clk_i,      // Clock
    input  logic                 rst_ni,     // Asynchronous reset active low
    input  logic                 flush_i,    // flush the fifo
    input  logic                 testmode_i, // test_mode to bypass clock gating
    output logic [AddrDepth-1:0] usage_o,    // fill pointer
    // input interface
    input  type_t                data_i,     // data to push into the fifo
    input  logic                 valid_i,    // input data valid
    output logic                 ready_o,    // fifo is not full
    // output interface
    output type_t                data_o,     // output data
    output logic                 valid_o,    // fifo is not empty
    input  logic                 ready_i     // pop head from fifo
);

    //--------------------------------------
    // Prevent Depth 0 and 1
    //--------------------------------------
    // Throw an error if depth is 0 or 1
    `ifndef SYNTHESIS
    if (Depth < 32'd2) begin : gen_fatal
        initial begin
            $fatal(1, "FIFO of depth %d does not make any sense!", Depth);
        end
    end
    `endif

    //--------------------------------------
    // Spill register (depth 2)
    //--------------------------------------
    // Instantiate a spill register for depth 2
    if (Depth == 32'd2) begin : gen_spill

        // print info
        `ifndef SYNTHESIS
        if (PrintInfo) begin : gen_info
            initial begin
                $display("[%m] Instantiate spill register (of depth %d)", Depth);
            end
        end
        `endif

        // spill register
        spill_register_flushable #(
            .T       ( type_t ),
            .Bypass  ( 1'b0   )
        ) i_spill_register_flushable (
            .clk_i,
            .rst_ni,
            .flush_i,
            .valid_i,
            .ready_o,
            .data_i,
            .valid_o,
            .ready_i,
            .data_o
        );

        // usage is not supported
        assign usage_o = 'x;
    end


    //--------------------------------------
    // FIFO register (depth 3+)
    //--------------------------------------
    // default to stream fifo
    if (Depth > 32'd2) begin : gen_fifo

        // print info
        `ifndef SYNTHESIS
        if (PrintInfo) begin : gen_info
            initial begin
                $info("[%m] Instantiate stream FIFO of depth %d", Depth);
            end
        end
        `endif

        // stream fifo
        stream_fifo #(
            .DEPTH        ( Depth  ),
            .T            ( type_t )
        ) i_stream_fifo (
            .clk_i,
            .rst_ni,
            .flush_i,
            .testmode_i,
            .usage_o,
            .data_i,
            .valid_i,
            .ready_o,
            .data_o,
            .valid_o,
            .ready_i
        );
    end

endmodule : stream_fifo_optimal_wrap