Pipeline Join

Takes in multiple input ready/valid handshakes with associated data, and once all input handshakes can complete, joins them together into a single output ready/valid handshake with all data concatenated. This function synchronizes multiple pipelines into a single wider one.

Avoiding combinational loops

As a design convention, we must avoid a combinational path between the valid and ready signals in a given pipeline interface, because if the other end of the pipeline connection also has a ready/valid combinational path, connecting these two interfaces will form a combinational loop, which cannot be analyzed for timing, or simulated reliably.

Thus, the input interfaces here are buffered to break the combinational path, even if the buffering is redundant. It's not worth the risk of a bad simulation or synthesis otherwise.

`default_nettype none

module Pipeline_Join
    parameter WORD_WIDTH     = 0,
    parameter INPUT_COUNT    = 0,

    // Do not set at instantiation, except in IPI
    input  wire                     clock,
    input  wire                     clear,

    input  wire [INPUT_COUNT-1:0]   input_valid,
    output wire [INPUT_COUNT-1:0]   input_ready,
    input  wire [TOTAL_WIDTH-1:0]   input_data,

    output reg                      output_valid,
    input  wire                     output_ready,
    output reg  [TOTAL_WIDTH-1:0]   output_data

    localparam INPUT_ZERO = {INPUT_COUNT{1'b0}};
    localparam INPUT_ONES = {INPUT_COUNT{1'b1}};
    localparam TOTAL_ZERO = {TOTAL_WIDTH{1'b0}};

    initial begin
        output_valid = 1'b0;
        output_data  = TOTAL_ZERO;

First, we must buffer the input interfaces to break the combinational paths from valid to ready.

    wire [INPUT_COUNT-1:0]   input_valid_buffered;
    reg  [INPUT_COUNT-1:0]   input_ready_buffered = INPUT_ZERO;
    wire [TOTAL_WIDTH-1:0]   input_data_buffered;

        genvar j;
        for(j=0; j < INPUT_COUNT; j=j+1) begin: per_input
                .WORD_WIDTH (WORD_WIDTH)
                .clock          (clock),
                .clear          (clear),
                .input_valid    (input_valid[j]),
                .input_ready    (input_ready[j]),
                .input_data     (input_data [WORD_WIDTH*j +: WORD_WIDTH]),
                .output_valid   (input_valid_buffered[j]),
                .output_ready   (input_ready_buffered[j]),
                .output_data    (input_data_buffered [WORD_WIDTH*j +: WORD_WIDTH])

Once all inputs are valid, declare the output valid and make all the input ready equal to the output ready. Pass the input data to the output data.

    always @(*) begin
        output_valid            = (input_valid_buffered == INPUT_ONES);
        input_ready_buffered    = (output_valid == 1'b1) ? {INPUT_COUNT{output_ready}} : INPUT_ZERO;
        output_data             = input_data_buffered;


back to FPGA Design Elements