M.X
M.X

Reputation: 195

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

Upvotes: 1

Views: 8755

Answers (1)

rascob
rascob

Reputation: 463

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.

Upvotes: 5

Related Questions