Search code examples
verilogsystem-veriloghdl

Valid-Ready handshake in Verilog


I am trying to learn valid/ready handshake in verilog. In particular, I am interested to use ready as a flag that indicates the successful transaction of data (i.e., ready_in becomes high after valid_out goes high). I would like to explain my problem using a very simple Verilog example. I have written a convolutional encoder (code below)

module Conv_Encoder_Core(
    input wire clk,                 
    input wire reset,        
    input wire in_bit,
    output reg out_A,
    output reg out_B,
    input wire sleep,          
    input wire valid_in,
    input wire ready_in,
    output reg valid_out,
    output reg ready_out);

reg [5:0] S;
wire S_A, S_B, clkON;
assign S_A = S[1] ^ S[2] ^ S[4] ^S[5];
assign S_B = S[0] ^ S[1] ^ S[2] ^S[5];
assign clkON = clk & !sleep;

always @(posedge clkON)begin
    if (reset) begin
        S <=0;
        valid_out <=0; 
        ready_out <=0;
    end else if (valid_in) begin
        out_A <= in_bit ^ S_A;
        out_B <= in_bit ^ S_B;
        valid_out <=1;
        if (ready_in)begin
            S<= S<<1;
            S[0] <=in_bit;
            ready_out <=1;
        end else begin
            ready_out <=0;
        end

    end else begin
        valid_out <=0;
        ready_out <=0;
    end


end 
endmodule

I am interested to use ready_in flag as an indicator that data out_A and out_B are received by the next block, so my block can accept the new data by setting ready_out flag high. I have written a testbench for this block, however, I am not getting the results I am expecting

`timescale 1 ns/1 ns
module TB_Conv();
reg  clk;
//---------------clock generator-----------------------
initial begin
    clk = 1'b0; 
    #5; 
    clk = 1'b1; 
    forever    begin
        #5 clk = ~clk;      
    end
end
//------------------ dump -----------------------
initial begin
    $dumpfile("dumpVCD.vcd");
    $dumpvars(10);  
end

localparam N_DATA=10;
reg in_bits_vec [0:N_DATA-1];
initial begin
    in_bits_vec[0] = 1'b1;
    in_bits_vec[1] = 1'b0; 
    in_bits_vec[2] = 1'b0; 
    in_bits_vec[3] = 1'b0; 
    in_bits_vec[4] = 1'b0;
    in_bits_vec[5] = 1'b0;
    in_bits_vec[6] = 1'b0;
    in_bits_vec[7] = 1'b0;
    in_bits_vec[8] = 1'b0;
    in_bits_vec[9] = 1'b1;
end
reg in_bit, ready_in,reset, valid_in;
Conv_Encoder_Core UUT(.clk(clk),
                        .reset(reset),
                        .in_bit(in_bit),
                        .out_A(out_A),
                        .out_B(out_B),
                        .sleep(1'b0),
                        .valid_in(valid_in),
                        .ready_in(ready_in),
                        .valid_out(valid_out),
                        .ready_out(ready_out));

//---------------- code starts here -------------------//
reg [3:0] addr;
always @(posedge clk) begin
    if (reset)begin
        addr<=0;
        valid_in <=0;
        in_bit <=0;
    end else if (addr < 10) begin
        in_bit <= in_bits_vec[addr];
        valid_in <=1'b1;
        if (ready_out) begin
            addr <= addr+1'b1;
        end

    end else  begin
        in_bit <=0;
        valid_in <=0;
    end

    if (valid_out==1) ready_in <= 1;
    else              ready_in <= 0;

end 
// ----------- reset logic -----------//
reg [3:0] cnt;
initial cnt=0;
always @(negedge clk)begin
    if (cnt<5) begin
        reset = 1;
        cnt=cnt+1;
    end else  reset =0;
end

initial begin
 #1000;
$finish;
end
endmodule

if you look at the input data (in the testbech), you can see it is 1000000000. I am expecting to see 1 being passed through S register as follows:

S = 000000 //at beginning
S = 000001 // after ready_out=1
S = 000010
S = 000100

however, the results I get is entirely different(please see snapshot). Another problem I have is that inbit=1 continues two clock cycles more than what I expect. in fact when ready_out=1, I expect to see that in_bit becomes zero but this happens two clock cycles later(yellow cursor in the snapshot ).

I would be most grateful if someone could explain what I do wrong in this example.

enter image description here


Solution

  • Conv_Encoder_Core

    module Conv_Encoder_Core
    (
        input wire clk,
        input wire reset,
        input wire in_bit,
        output reg out_A,
        output reg out_B,
        input wire sleep,
        // input channel
        input  wire inp_valid_i,
        output wire inp_ready_o,
        // output channel
        output reg out_valid_o,
        input  reg out_ready_i
    );
    
    reg [5:0] S;
    wire S_A, S_B, clkON;
    assign S_A = S[1] ^ S[2] ^ S[4] ^S[5];
    assign S_B = S[0] ^ S[1] ^ S[2] ^S[5];
    assign clkON = clk & !sleep;
    
    
    // -- Changes start here -- //
    wire wr_en;
    reg full_r;
    
    assign wr_en = ~full_r | out_ready_i;
    always @(posedge clkON)begin
        if (reset) begin
            S <=0;
            full_r <=0;
        end else begin
            if (wr_en) begin
                if (inp_valid_i) begin
                    full_r  <= 1;
                    out_A   <= in_bit ^ S_A;
                    out_B   <= in_bit ^ S_B;
                    S       <= S<<1;
                    S[0]    <=in_bit;
                end else begin
                    full_r  <= 0;
                end
            end
        end
    end
    
    assign inp_ready_o = wr_en;
    assign out_valid_o = full_r;
    
    endmodule
    

    tb

    `timescale 1 ns/1 ns
    module tb();
    reg  clk;
    //---------------clock generator-----------------------
    initial begin
        clk = 1'b0; 
        #5; 
        clk = 1'b1; 
        forever    begin
            #5 clk = ~clk;      
        end
    end
    //------------------ dump -----------------------
    initial begin
        $dumpfile("dumpVCD.vcd");
        $dumpvars(10);  
    end
    
    localparam N_DATA=10;
    reg in_bits_vec [0:N_DATA-1];
    initial begin
        in_bits_vec[0] = 1'b1;
        in_bits_vec[1] = 1'b0; 
        in_bits_vec[2] = 1'b0; 
        in_bits_vec[3] = 1'b0; 
        in_bits_vec[4] = 1'b0;
        in_bits_vec[5] = 1'b0;
        in_bits_vec[6] = 1'b0;
        in_bits_vec[7] = 1'b0;
        in_bits_vec[8] = 1'b0;
        in_bits_vec[9] = 1'b1;
    end
    reg in_bit, reset, inp_valid, inp_ready, out_valid, out_ready;
    Conv_Encoder_Core UUT(.clk(clk),
                            .reset(reset),
                            .in_bit(in_bit),
                            .out_A(out_A),
                            .out_B(out_B),
                            .sleep(1'b0),
                            // input channel
                            .inp_valid_i(inp_valid),
                            .inp_ready_o(inp_ready),
                            // output channel
                            .out_valid_o(out_valid),
                            .out_ready_i(out_ready));
    
    //---------------- code starts here -------------------//
    reg [3:0] addr;
    
    // -- Transmitter Side -- //
    always @(posedge clk) begin: ff_addr
        if (reset)begin
            addr <= 0;
        end else begin
            if (addr < 10) begin
                if (inp_valid && inp_ready) begin
                    addr <= addr + 1;
                end
            end else begin
                addr <= 0;
            end
        end
    end
    
    assign inp_valid = (addr < 10) ? 1'b1 : 1'b0;
    
    assign in_bit = in_bits_vec[addr];
    
    // -- Receiver Side -- //
    always @(posedge clk) begin: ff_ready_in
        if (reset) begin
            out_ready <= 0;
        end else begin
            out_ready <= $urandom_range(0, 1); // some randomness on the receiver, otherwise, we won't see if our DUT behaves correctly in case of ready=0
        end
    end
    
    // ----------- reset logic -----------//
    reg [3:0] cnt;
    initial cnt=0;
    always @(negedge clk)begin
        if (cnt<5) begin
            reset = 1;
            cnt=cnt+1;
        end else  reset =0;
    end
    
    initial begin
     #1000;
    $finish;
    end
    endmodule
    

    Issues with your implementation

    Bad protocol definition & implementation

    You are defining a protocol that looks more like "request/acknowledge" than "ready/valid" one, because data transmissions in your protocol are acknowledged after a one-cycle delay. What you need is concurrent transmission acknowledge in the same cycle, something like the following:

    enter image description here

    A valid data transmission is indicated by the Transmitter through valid=1 and are acknowledged by the Receiver through ready=1. So, a data transmission is valid only when valid && ready in the same cycle. Note that input data is equivalent to in_bit in your case, while output data is out_A and out_B.

    Input/Output ready/valid channel confusion

    If you add a processing/buffering unit between the Transmitter and the Receiver of the above channel, then what you got is something like this:

    enter image description here

    In that case, your buffer is the Conv_Encoder_Core module and, apart from its internal core logic, it must expose an input ready/valid channel, from which it receives input data, and an output one, from which it outputs its data. Also note that the Transmitter and the Receiver, are implemented by the testbench code (tb module). See "Transmitter Side" and "Receiver Side" comments in code.