一种valid-ready握手协议打拍的处理方案

前言

valid-ready握手协议相对来说较简单,sender给出valid表示数据有效,同时给出数据,receiver给出ready代表可以接收数据。当valid和ready同时为高,代表握手成功,receiver端即可接收数据,sender端可准备发送下一个数据。

在实际应用场景中,当valid-ready握手电路中组合逻辑较多,频率上不去,就需要插寄存器打拍来提高频率。(虽然本人没遇到过这种应用场景,但觉得比较有意思,所以想尝试解决一下)

打拍分三种情况:哪条路频率上不去,就对哪条路打拍

  • 对valid和data打拍
  • 对ready打拍
  • 对valid, data和ready都进行打拍

经过打拍后,sender端和receiver端就都要经过握手,如下图所示。打拍之后,肯定会出问题,比如,valid和data没有打拍,而ready打了一拍。本来receiver在T cycle表示我可以接收数据,然而传到sender端却表示在T+1 cycle可以接收数据,那就会丢数据。或者,valid和data打了一拍,ready没有打拍,由于数据延迟了一个周期,可以理解为在sender端,ready提前一个周期就到了,那么sender端就会认为receiver已经接收了数据,就会把新的数据放出来,把旧的数顶掉,也可能会造成丢数。

e3cef87ab2e4488d99923aa3977a4553.png

所以,就要对三种打拍的情况在逻辑上进行修正,使其仍可以正常传输数据。 

说来惭愧,由于我逻辑思维较差,经验不足,本人针对前两种打拍情况,试着去写了一下,都没有实现最大化的传输效率。去看了些别人的代码并试着跑了一下,最终得出了正确的处理方案。我在sender端和receiver端都随机化了valid和ready的请求,遵循了AXI协议中对valid和ready的规范,以下的代码可以作为参考。

 

1. 对valid和data打拍

问题:如前所述,会出现这种错误:“valid和data打了一拍,ready没有打拍,由于数据延迟了一个周期,可以理解为在sender端,ready提前一个周期就到了,那么sender端就会认为receiver已经接收了数据,就会把新的数据放出来,把旧的数顶掉,也可能会造成丢数。”如下面波形所示,“2”和"7"都丢掉了。

0e834a612229499da5017c2e635267e2.png

 解决:

1. 既然担心数据被顶掉,那就等待每次传输成功后再发送下一次的数据。

always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        data_dst <= 8'd0;
    end else if(ready_dst && valid_src) begin 
        data_dst <= data_src;
    end
end

2.  valid与data同理,等待每次传输成功后再发送下一次的valid。也可以简单一点,sender端有请求就拉高valid_dst,没有请求的话,就等待ready_src拉高,即receiver端接收到数据后,将valid_dst拉低。

always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        valid_dst <= 1'b0;
    end else if(valid_src) begin 
        valid_dst <= 1'b1;
    end else if(ready_src) begin
        valid_dst <= 1'b0;
    end
end

3. ready信号不打拍,通过组合逻辑给到sender。这里需要注意,由于data每次都要通过寄存器才能到达receiver,所以每当receiver拉高ready想要接收数据,其data先要存到寄存器,才能给到receiver,这会使传输产生气泡,无法达到最高效率。为了消除气泡,我们需要对sender端接收到的ready信号做一点改动:当data寄存器空的时候(即valid_dst == 1'b0),也要拉高sender端的ready,先把数据存到data寄存器中。这样的话,receiver想要数据的时候,只需要从data寄存器中取数就可以了,实现了类似流水线的操作,降低了latency。

always@(*) begin
    ready_dst = ready_src || ~valid_dst;
end

总体代码如下: 

always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        valid_dst <= 1'b0;
    end else if(valid_src) begin 
        valid_dst <= 1'b1;
    end else if(ready_src) begin
        valid_dst <= 1'b0;
    end
end

always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        data_dst <= 8'd0;
    end else if(ready_dst && valid_src) begin 
        data_dst <= data_src;
    end
end

always@(*) begin
    ready_dst = ready_src || ~valid_dst;
end

仿真波形: 5fa9afe88f0e45fdb47e91d2e86d2d1e.png

 

2. 对ready打拍 

问题:如前所述,会出现这种错误:“valid和data没有打拍,而ready打了一拍。本来receiver在T cycle表示我可以接收数据,然而传到sender端却表示在T+1 cycle可以接收数据,那就会丢数据。”如下波形所示。

f3532f8936944f82bab07b2a60edbdc5.png

解决:

1. 与valid data打拍类似,也是等待传输成功后再给出下一笔数据。

2. 消除气泡:这里是个难点,valid data打拍时,可以使用valid_dst消除气泡,但是ready打拍时,由于ready信号延迟了一个周期,如果还是通过判断valid_dst是否为0,仿真发现是无法消除气泡的。我参考了网上的两个设计,发现加入passby后自然会将气泡消除(也就是说,在ready打了拍的前提下,valid和data也需要打一拍寄存,只不过有些情况可以将valid和data直通,此种方式传输效率最高,无气泡),但将passby功能去掉后,仍有气泡。由于时间关系,还未找到更好的方案。

3. passby(直通):由于只对ready要求了打拍,valid和data都可以组合逻辑直通,那么在一些情况下,无需将数据输入data寄存器,而是直接直通给receiver。在如下代码中,valid_buf(可以理解为valid_dst)为0时,表示数据寄存器中没有待传的数据了,就可以进行data直通。但做到当前这里,仍然会有气泡,问题出在valid_buf和data_buf的产生,比如,当ready_src == 1'b1并且valid_src == 1'b1时,valid_buf不应拉高,data不应更新,这是因为此时需要进行data直通,若将valid_buf拉高,receiver会选择data寄存器中的数据,而不会选择直通。

最终代码如下所示:

always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        valid_buf <= 1'b0;
    end else if(valid_src && ~ready_src) begin      //bubble clamping for passby
        valid_buf <= 1'b1;
    end else if(ready_src) begin
        valid_buf <= 1'b0;
    end
end

always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        data_buf <= 8'd0;
    end else if(src_ocu && ~ready_src) begin        //for data_buf debug    
        data_buf <= data_src;
    end
end

assign src_ocu = valid_src && ready_dst;

assign valid_dst = (valid_src && ~valid_buf) ? 1'b1 : valid_buf;    //passby, also bubble clamping
assign data_dst = (~valid_buf) ? data_src : data_buf;               //passby

always@(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        ready_dst_pre <= 1'b1;
    end else begin
        ready_dst_pre <= ready_src;
    end
end

always@(*) begin
    ready_dst = ready_dst_pre || (~valid_buf);    
end

仿真波形如下:

e6b649f7765c4283a4717d704ed28090.png

3. 对valid, data和ready都进行打拍  

问题:这个不用多说,肯定会出问题。

0d69d265a8f644d2ad062e5b184922c7.png

解决:其实,这种情况可以理解为“对ready打拍”的一个特例,因为在对ready打拍时,我们其实也对valid和data进行了打拍。但不同的是,在对ready打拍时,并不强制要求valid和data必须经过打拍,所以valid和data可以经过组合逻辑直通,来提高传输效率;现在要对所有信号都打拍,就不允许直通了。然而,我参考的方案,在去掉passby后,就无法消除气泡了,所以需要改变方法。(如果不需要消除气泡,仍可以采用之前的 “ 等待传输结束,再给出下一笔数据 ” 的方法)

经过了许久的尝试之后,我意识到,如果对valid, data和ready都进行打拍,不利用passby的情况下,只用一个寄存器进行寄存,应该是无法消除气泡的,应该是需要两个寄存器来寄存。包括网上有人说,这种情况可以采用ping-pong buffer,也印证了我这一猜想,所以我采用了深度为2的同步FIFO来解决(经典遇事不决用FIFO),代码如下,用FIFO的话还是比较简单的。

assign ready_dst = ~full;
assign valid_dst = ~empty;

assign wr_en = valid_src && ready_dst;
assign rd_en = ready_src && valid_dst;

fifo_reg #(
    .DEPTH          (2),
    .WIDTH          (8)
) u_fifo_reg (
    .clk            (clk),
    .rst_n          (rst_n),
    .wr_en          (wr_en),
    .rd_en          (rd_en),
    .clr            (1'b0),
    .full           (full),
    .empty          (empty),
    .thres          ('d0),
    .almost_full    (), //not use
    .almost_empty   (), //not use
    .din            (data_src),
    .dout           (data_dst)
);

仿真波形如下:

2c257f6aaeb74086844b409b210287e4.png

 

 后话

ping-pong buffer和深度为2的FIFO道理差不多,我就没再写,另外,了解到有个IP是专门解决valid-ready打拍问题的,叫skid buffer,下一期可能会去分析一下这个IP 

 

 

 

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一个基本的valid-ready握手协议的Verilog代码实现,如下所示: ```verilog module valid_ready_handshake ( input logic clk, input logic reset, input logic valid_in, output logic ready_out, output logic valid_out ); // 用于存储当前的握手状态 typedef enum logic [1:0] { IDLE = 2'b00, // 空闲状态 READY = 2'b01, // 准备状态 ACK = 2'b10 // 确认状态 } handshake_state_t; // 定义握手状态变量并初始化为空闲状态 logic [1:0] handshake_state = IDLE; // 当 valid_in 信号变成高电平并且当前状态为空闲状态时,进入准备状态 always_ff @(posedge clk) begin if (reset) begin handshake_state <= IDLE; end else begin case (handshake_state) IDLE: begin if (valid_in) begin handshake_state <= READY; end end READY: begin if (ready_out) begin handshake_state <= ACK; end else if (!valid_in) begin handshake_state <= IDLE; end end ACK: begin if (!valid_in) begin handshake_state <= IDLE; end end endcase end end // 根据当前状态输出 valid_out 和 ready_out 信号 assign valid_out = (handshake_state == READY); assign ready_out = (handshake_state == ACK); endmodule ``` 这个Verilog模块实现了一个简单的 valid-ready 握手协议,其中包含了三种状态:IDLE(空闲状态)、READY(准备状态)和ACK(确认状态)。当输入信号 valid_in 变成高电平时,模块将进入准备状态,此时它会等待输出信号 ready_out 变成高电平。当 ready_out 变成高电平时,模块将进入确认状态,此时它会等待 valid_in 信号变成低电平。一旦 valid_in 信号变成低电平,模块将重新进入空闲状态。 请注意,这只是一个基本的实现,您可以根据您的具体需求进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值