Valid-Ready protocol

握手机制Valid-Ready handshake reg

由于该部分网上内容十分全面,所以大部分都是复制的网上当作自己复习的笔记,结尾贴出参考链接

问题

There’s a “valid-ready” protocol-based data transferring interface between module TX
and module RX, with connection and interface timing diagram showed below. However,
for timing issue in real physical design, you need to insert a pipeline slice betweer
TX and Rx for timing closure.
1)Please use Verilog or SystemVerilog to implement such 1-depth pipeline slice.
(15 points)
2)Please use Verilog orto implement a generic parameterized N-depthpipeline slice.
(15 points)


| TX | | RX |
| vld |--------------->|vld |
| data|--------------->|data |
| rdy |<---------------|rdy |



| TX | | S | | RX |
| vld |---->| |----->|vld |
| data|---->| |----->|data |
| rdy |<----| |<-----|rdy |


由该问题引发的一些思考

handshake reg的设计

首先为什么会出现这样一个handshake呢,我们知道,在我们修改setup违例时,经常采用增加reg的方式,而对于这样一个使用上下游使用握手机制产生了时序违例,我们就需要这样一个reg来改善时序问题了。

我们以AXI的握手机制为例,AXI master 到AXI slave 这一簇信号出现了setup违例有以下三种情况:

img

1、从AXI master 到AXI slave 出现setup违例;

2、从AXI slave 到AXI master出现setup违例;

3、两者都出现setup时序违例。

所以AXI master 和 AXI slave 之间的打牌有四种模式

Forward Registered : 对valid和payload路打拍

Backward Registered : 对ready路打拍

Fully Registered : 同时对valid/payload路和ready路打拍

Pass Through Mode: Bypass,均不打拍

实际上前三种模式已经形成了独立的IP

Forward Registered

请添加图片描述

// 将进入的valid 和 payload 打拍,ready使用组合逻辑输出

always @(posedge clk or negedge rst_n)begin

if (rst_n == 1'd0)

valid_dst <= 1'd0;

else if (valid_src == 1'd1)

valid_dst <= #`DLY 1'd1;

else if (ready_dst == 1'd1)

valid_dst <= #`DLY 1'd0;

end

always @(posedge clk or negedge rst_n)begin

if (rst_n == 1'd0)

payload_dst <= 'd0;

else if (valid_src == 1'd1 && ready_src == 1'd1)

payload_dst <= #`DLY payload_src;

end

ready_src = (~valid_dst) | ready_dst
/*valid_dst: 在master发请求(拉高valid_src)时拉高valid_dst,直到当前master没有valid请求并且slave可以接收请求(拉高ready_dst)时拉低valid_dst,表示一次传输完成。

payload_dst: 在master发请求(拉高valid_src),并且前面没有请求、请求已经被接收或者正在被接收时将payload_src打拍赋给payload_dst。

其实master本身也会遵循valid-ready协议,payload_src和valid_src做同样处理就行,即也可以在(valid_src == 1'd1 && ready_src == 1'd0)时进行赋值,因为此时payload_src输入应该约束保持原始数据。

ready_src: register slice或者slave可以接收数据时拉高ready_src.
*/

Backward Registered

请添加图片描述

//Backward Registered 打拍相比较 Forward Registered 会复杂点,因为存在 slave 没有 ready 时 master 发来请求,需要暂存 payload 的场景。
always @(posedge clk or negedge rst_n)begin

if (rst_n == 1'd0)

valid_tmp0 <= 1'd0;

else if (valid_src == 1'd1 && ready_dst == 1'd0 &&valid_tmp0 == 1'd0)

valid_tmp0 <= #`DLY 1'd1;

else if (ready_dst == 1'd1)

valid_tmp0 <= #`DLY 1'd0;

end

always @(posedge clk or negedge rst_n)begin

if (rst_n == 1'd0)

payload_tmp0 <= 'd0;

else if (valid_src == 1'd1 && ready_dst == 1'd0 &&valid_tmp0 == 1'd0)

payload_tmp0 <= #`DLY payload_src;

end

assign payload_dst = (valid_tmp0 == 1'd1) ?payload_tmp0 : payload_src;

always @(posedge clk or negedge rst_n)begin

if (rst_n == 1'd0)

ready_src <= 1'd0;

else

ready_src <= #`DLY ready_dst;

end
/*
ready_src: 对ready通路直接进行打拍。

valid_dst: 当slave没有ready,master发来请求时拉高标志位valid_tmp0,表示下一次slave准备好之后应该从register slice内暂存的payload拿数据

payload_dst: 当slave没有ready,master发来请求时暂存payload到payload_tmp。最终的payload_dst根据标志位valid_tmp0从payload_tmp和payload_src之间选择
*/

Fully Registered

类似于,简单理解就是个乒乓BUFFER,使用非空信号做valid_dst;payload的非满信号做ready_src

Pass Through Mode 该方式不作考虑 实习上并未做任何处理

实际上我们在考虑打拍时,重要的点在于ready的打拍是如何实现的

img

在思考打拍时 主要有这么两点需要考虑

情况1 : 当后级的ready拉低后,而自身ready没有拉低,此时会出现丢数的情况----数据2在输出端口无法握手,而丢失(因为前级还在收数,对数据进行了覆盖)

情况2:当后级的ready拉高,而自身ready未拉高时,这时会出现重复

解决方法为引入 skid buffer,所谓skid, 即在data_o_ready拉低后,data_i接口是"滑入"STOP状态的,而不是立即停止的。

// ports
output reg     data_i_ready;
input          data_i_valid;
input [DW-1:0] data_i;

input           data_o_ready;
output          data_o_valid;
output [DW-1:0] data_o;

// signals
wire          buf_valid;
reg  [DW-1:0] buf_data;

// data_i_ready由组合逻辑改为时序逻辑
// assign data_i_ready = ~data_o_valid || data_o_ready;
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n)
        data_i_ready <= 0; //有个buf放在那,因此复位后至少可以收一个数据。
    else if(data_o_ready)
        data_i_ready <= 1;
    else if(data_i_valid)  //在data_o_ready拉低后的那一个周期,如果没有数据过来,表明buf为空,可以进数据,data_i_ready保持为1
        data_i_ready <= 0; //如果有数据过来,buf被占,则不允许再进数据了,data_i_ready拉低。
end

// ************关于buf_valid和buf_data有2种方案
/*
方案1:所有输入端口的数据都会进入buf,但其实只有(!data_o_ready && data_i_valid && data_i_ready)时刻进去才会发挥作用
优点:控制逻辑简单;缺点:功耗有所增加
*/
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) //可能有些控制信号通过本模块的data端口进行传输,因此有必要进行复位。
        buf_data <= 0;
    else if(data_i_ready) //不挑时刻,所有数据都进,但是真正有效的还是(!data_o_ready && data_i_valid && data_i_ready)时刻的数据
        buf_data <= data_i;
end

assign buf_valid = ~data_i_ready;

/*
方案2:只有(!data_o_ready && data_i_valid && data_i_ready)时刻数据才会进入buf
优点:功耗略低;缺点:控制逻辑稍微复杂点
*/
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) 
        buf_data <= 0;
    else if(!data_o_ready && data_i_valid && data_i_ready) //该条件下,将向buf中存入数据
        buf_data <= data_i;
end

// 这段其实等效于assign buf_valid = ~data_i_ready; 写成下面这样更好理解
always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) 
        buf_valid <= 0;
    else if(data_o_ready)
        buf_valid <= 0; //当data_o_ready拉高,优先读走buf中的数据。
    else if(!data_o_ready && data_i_valid && data_i_ready) //该条件下,将向buf中存入数据
        buf_valid <= 1; 
end

// out,如果buf中有数据,优先读走buf中的数据
assign data_o_valid = data_i_ready? data_i_valid : buf_valid;
assign data_o       = data_i_ready? data_i       : buf_data;

此外还有一个更简单的方式,就是直接使用fifo进行数据的交互。

反压

应用场景:入口流量大于出口流量时,就需要反压

常用的反压方法:

(1)不带存储器的反压

后级反压信号对本级模块中所有流水reg都进行控制

优点:节省面积资源

缺点:寄存器端口控制复杂

适用情况:流水线深度较大时

(2)带存储体的逐级反压

流水级数不深,可以在每一个需要握手交互

请添加图片描述

类似该图,只需要对源头进行反压,但是流水线本身不受影响,此时后级需要fifo、ram等存储元件

优点:各级流水寄存器端口控制简单

缺点:需要额外存储体

适用情况:流水线深度较小,每一模块都包含存储体时

(3)带存储体的跨级反压

很多时候在具体设计过程中,颗粒度划分不精细,反压这时候是对模块而言,而不是说模块内部有多少级流水。此外,并不是每一模块都带有存储体。比如,其中可能a模块没有存储体,b模块没有存储体,但ab模块内部还有多级流水,如果c模块有存储体,并且需要反压前级模块,这时候可以选择反压a模块的源头输入数据,然后将ab的流水都存储到带有存储体的c模块,但是如果ab不是都没有存储体的话,就不应该跨级反压,而应该逐级反压

优点:控制简单方便

缺点:需要额外存储体,模块间耦合度高

适用情况:某些模块带存储体,某些模块不带存储体时

问题答案

module slice1 #(
    parameter WIDTH = 8
)(
    input                   clk,
    input                   rst_n,

    input                   tx_vld,
    input       [WIDTH-1:0] tx_data,
    output reg              tx_rdy,

    output reg              rx_vld,
    output reg  [WIDTH-1:0] rx_data,
    input                   rx_rdy
);
    reg     [WIDTH-1:0] data_buffer;
    reg                 data_buffer_vld;
    always @(posedge clk or negedge rst_n) begin
        if(~rst_n)
            tx_rdy  <= 1'b0;
        else
            tx_rdy <= rx_rdy || ~(rx_vld && tx_vld) || data_buffer_vld;
    end

    always @(posedge clk or negedge rst_n) begin
        if(~rst_n) begin
            data_buffer <= 'd0;
            data_buffer_vld <= 1'b0;
        end else if(tx_rdy && rx_vld && (~rx_rdy) && tx_vld)begin
            data_buffer <= tx_data;
            data_buffer_vld<= 1'b1;
        end else if(rx_rdy) begin
            data_buffer <= data_buffer;
            data_buffer_vld<= 1'b0;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if(~rst_n) begin
            rx_vld <= 1'b0;
            rx_data<= 'd0;
        end else if(rx_rdy & data_buffer_vld)begin
            rx_vld <= 1'b1;
            rx_data<= data_buffer;
        end else if(tx_rdy & tx_vld  & rx_rdy)begin
            rx_vld <= 1'b1;
            rx_data<= tx_data;
        end else begin
            rx_vld <= 1'b0;
            rx_data<= 'd0;
        end
    end
endmodule
// ues fifo

module slice2 #(
    parameter WIDTH = 8,
    parameter DEPTH = 8,
    parameter       POINTER_WIDTH = $clog2(DEPTH)

)(
    input                   clk,
    input                   rst_n,

    input                   tx_vld,
    input       [WIDTH-1:0] tx_data,
    output                  tx_rdy,

    output reg              rx_vld,
    output reg  [WIDTH-1:0] rx_data,
    input                   rx_rdy
);
    reg         [WIDTH-1:0] RAM[0:DEPTH];
    reg         [POINTER_WIDTH:0]   wr_cnt,rd_cnt;

    wire        wr_en;
    wire        rd_en;

    reg        full;
    reg        empty;

    assign      wr_en = tx_vld & tx_rdy;
    assign      rd_en = rx_rdy & rx_vld;


    always @(*) begin
        if(~rst_n)
            full <= 1'b0;
        else if( (wr_cnt[POINTER_WIDTH-1:0] == rd_cnt[POINTER_WIDTH-1:0]) && (wr_cnt[POINTER_WIDTH] != rd_cnt[POINTER_WIDTH]))
            full <= 1'b1;
        else
            full <= 1'b0;
    end

    always @(*) begin
        if(~rst_n)
            empty <= 1'b1;
        else if(wr_cnt == rd_cnt)
            empty <= 1'b1;
        else
            empty <= 1'b0;
    end

    always @(posedge clk) begin
        if(!rst_n)
            wr_cnt <= 0;
        else if(wr_en & (!full) )
            wr_cnt <= wr_cnt + 1;
        else
            wr_cnt <= wr_cnt;
    end

    always @(posedge clk) begin
        if(!rst_n)
            rd_cnt <= 0;
        else if( rd_en & (!empty) )
            rd_cnt <= rd_cnt + 1;
        else
            rd_cnt <= rd_cnt;
    end

    always @(posedge clk) begin
        if(wr_en && (!full) )
            RAM[wr_cnt[POINTER_WIDTH-1:0]] <= tx_data;
    end

    always @(posedge clk) begin
        if(rd_en & (!empty) )
            rx_data <= ram[rd_cnt[POINTER_WIDTH-1:0]];
    end


    assign tx_rdy = ~full;  //只要非满就能够写入
    assign rx_vld = ~empty; //只要非空就能够读出


endmodule

参考链接

1.流水线中的valid-ready握手控制打拍 - 知乎 (zhihu.com) 这里面给出的链接从头开始看即可,下面列举不在其中的链接

2.数字芯片设计——握手与反压 - 知乎 (zhihu.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值