今天来聊一个简单的握手打拍技巧

转载:握手协议(pvld/prdy或者valid-ready或AXI)中ready打拍技巧

握手协议是数字逻辑设计中最最常见的一种设计了,今天就来聊一聊valid-ready握手时,valid和data的打拍技巧。

内容提要:

  • valid与data的时序修复时的打拍
  • 如何无气泡?
  • 预告:ready修复

问题描述: 

AXI协议使用的是valid-ready握手的方式去传输数据,关于valid ready握手,有几个要点:

  • 数据data使用valid作为有效性指示,当valid为1时,data数据才有效 
  • valid和ready信号同时为高时,数据传输真正发生
  • valid在没有ready到来的情况下,不能自己变为0,也就是说,数据没有被处理,必须一直等待。
  • ready表征下一级是否准备好,ready信号可以随时起来,随时下去。

valid与data的时序修复: 

对于valid跟data的时序问题,比较好修,这也是pipeline机制中,最常见的修timing的方法:打一拍,所有的打一拍,都可以抽象为valid-ready data模型,在这个模型中,valid和data需要打一拍,改善时序。

最常见的修复方法: 

valid在握手的情况下,打一拍,传到下级,不握手的情况下,维持原值,data数据一样,对于ready则时直接传过去即可。

VALID_DOWN <= handshake ? VALID_UP : VALID_DOWN;
DATA_DOWN  <= handshake ? DATA_UP : DATA_DOWN;
READY_UP   = READY_DOWN;

进行修改——简化 

对其进行修改,可以发现逻辑进行简化:valid的逻辑,在传输的时候,可以直接使用ready-up,也就是,ready_up是1的时候,你可以传输,也就是变为如下的代码:

VALID_DOWN <= RAEDY_UP ? VALID_UP : VALID_DOWN;
DATA_DOWN  <= handshake ? DATA_UP : DATA_DOWN;
RAEDY_UP = READY_DOWN;

进行修改——无气泡传输

对其继续进行修改,可以发现现在的电路,已经存在了一级寄存器,这一级寄存器,可以给上一级的data,多提供一份存储,也就是说,就算是下级raedy是0,只要寄存器里面没有数,上一级仍然可以ready为高,将数据存储一拍,本质上就是消除了一级气泡。

VALID_DOWN <= READY_UP ? VALID_UP :VALID_DOWN;
DATA_DOWN  <= handshake ? DATA_UP :DATA_DOWN;
assign READY_UP = READY_DOWN || ~VALID_DOWN;

 以上是对握手协议中valid和data的打拍技巧,那么对ready信号的打拍技巧呢?请继续往下看:

内容提要: 

  • ready打拍的问题
  • 用FIFO的思路去解决
  • 用Buffer的思路去解决 

问题提出:ready时序如何优化? 

在valid-ready握手协议中,valid与data的时序优化比较容易理解(看到这里对valid-ready协议或者valid打拍方法相信大家已经掌握了)但是有时候,关键路径在ready信号上,如何对ready信号打拍呢?

首先将把目标设计想象成一个黑盒子,如图1所示,我们的目标是将READY_DOWN通过打拍的方法获得时序优化。

尝试直接对ready打一拍

READY_UP <= READY_DOWN;
VALID_DOWN <= VALID_UP;

 这样是行不通的。

一个简单的例子(case1)就是你让READY_DOWN像时钟一样,间隔一个cycle起来一次,那么VALID_UP && READY_UP与VALID_DOWN && READY_DOWN无法同步(怎么理解呢,就是ready_down拉起来了数据没有下去,数据下去了,ready_down又被拉低了),因此数据无法传输下去。

思路:将其分解成两个interface

将ready打拍的逻辑想象成一个黑盒子,去分析这个黑盒子的设计,分为up interface 和down interface将问题细化:

  • up interface有VALID_UP,DATA_UP,READY_UP
  • down interface有VALID_DOWN,DATA_DOWN,READY_DOWN

可以总结成下面的样子:

READY_UP <= READY_DOWN;
transfer_up = VALID_UP && READY_UP;
transfer_down= VALID_DOWN && READY_DOWN;

如果去解决刚才的例子(case1),那么这个黑盒子:

当READY_UP为高的时候,可以接收数据;

当READY_DOWN为高的时候,如果我们有数据可发的话,我们可以向downstream发送数据; 

是不是很像一个FIFO?

用FIFO去解决

将一个FIFO插在黑盒子里面,那么就会变成这个样子:

VALID_UP/READY_UP ==> FIFO ==> VALID_DOWN/READY_DOWN

也就是:

valid_up = fifo_push_valid;
read_up  = fifo_push_ready;
valid_down = fifo_pop_valid;
ready_down = fifo_pop_ready;fifo_pop_ready;

 现在的问题变成了:如何设计这个FIFO呢?

  • 这个FIFO深度多少?
  • 怎么设计,能够保证READY_UP是READY_DOWN打过一拍呢?

FIFO设计多深?

因为本身valid/ready协议是反压协议(也就是ready_up为0的时候,不会写FIFO,而不会导致FIFO溢出)而且此处的读写时钟是同一个时钟,是一个同步FIFO,所以FIFO深度是1或者2就足够了。

深度是1还是2要看极端情况下需要存储几笔数据。

简单分析可以知道,只有一种情况会去向FIFO中存储数据:

  • READY_UP是1,可以从upstream接收数据
  • 同时READY_DOWN是0,不可以向downstream发送数据

这种情况在极端情况下最多维持多久呢?

答案是:一个周期

因为如果cycle a时:READY_DOWN=0,那么cycle a+1时,READY_UP变为0了,开始反压,所以只用存一个数就够了。

所以设计为一个深度为1的FIFO就可以了。

深度为1的FIFO有很多特点,设计起来比较简单,比如:wr_ptr/rd_ptr始终指向地址0,所以我们可以删掉wr_ptr和rd_ptr,因为是一个常值0。

简单的depth-1 FIFO实现

使用depth-1 FIFO传输数据,可以这样设计:

always @(posedge CLK)begin
  if(~RESET)begin
    fifo_line_valid <= 0;
    fifo_push_ready <= 1'b0;
    fifo_data <= {WIDTH{1'b0}};
  end    
  else begin
    fifo_push_ready <= fifo_pop_ready;
    if(fifo_push_ready) begin
      fifo_line_valid <= fifo_push_valid;
      fifo_data  <= DATA_UP;   
  end      
  else begin     
    if (fifo_pop_valid && fifo_pop_ready)
      fifo_line_valid <= 1'b0; //表示fifo为空
    else 
      fifo_line_valid <= fifo_line_valid;
    end
   end
end

assign fifo_push_valid = VALID_UP;
assign fifo_pop_valid = fifo_line_valid;
assign fifo_pop_ready = READY_DOWN;

assign READY_UP   = fifo_push_ready;
assign VALID_DOWN = fifo_line_valid;
assign DATA_DOWN  = fifo_data;

这解决了READY打拍的问题,但是这里有一些可以改进的地方,比如:

  • 是不是可以挤掉多余的气泡?
  • 在FIFO为空的时候,数据是不是可以直接bypass FIFO?

无气派传输:

关于气泡传输,可以参考上文,具体的说,就是既然你这里有一个深度为1的FIFO了,那么我是不是可以利用起来放点数据啊。

当READY_DOWN持续是0的时候,READY_UP依然可以有一个cycle去接收一笔数据,把FIFO资源利用起来:

fifo_no_push = ~(fifo_push_valid && fifo_push_ready);

fifo_push_ready <= ( fifo_pop_ready||(fifo_no_push && ~ fifo_line_valid));

同样的原因,在RESET情况下,READY_UP可以为1,可以将复位值修改,那么FIFO直通呢?

FIFO 穿越:

考虑一个特殊情况(case2):

假设READY_DOWN在复位后始终为1,然后某一个时刻开始VALID_UP为1了。

是不是每个周期,数据都可以直接传下来而不用进入到FIFO中,即使READY_DOWN打过一拍?

换句话说:如果READY_UP = 1,READY_DOWN = 1,FIFO是空的情况下,数据可以直通。

  • 上文特殊情况(case2),READY_DOWN和READY_UP一直是1,显然可以。
  • RAEDY_UP从0到1跳变:READY_DOWN也会在前一周期有一个从0到1的跳变,在READY_DOWN为0时,READY_UP在时刻a+1也会变为1,如果此时READY_DOWN也为1,可以直通,不用进入到FIFO,也就是:
assign pass_through = READY_UP && READY_DOWN && ~fifo_line_valid;
assign VALID_DOWN   = pass_through ? VALID_UP : fifo_line_valid;
assign DATA_DOWN    = pass_through ? DATA_UP : fifo_data;

注意:在直通时,我们不希望数据进入FIFO:

assign fifo_push_valid = ~ pass_through && VALID_UP;

将这些所有的结合起来:

`timescale 1ns/1ns

module ready_flop(
        CLK,
        RESET,
        VALID_UP,
        READY_UP,
        DATA_UP,
        VALID_DOWN,
        READY_DOWN,
        DATA_DOWN
        );

//---------------------------------------
parameter WIDTH = 32;
//---------------------------------------

input                      CLK;
input                      RESET;

//Up stream
input                      VALID_UP;
output                     READY_UP;
input  [0:WIDTH-1]         DATA_UP;
//Down Stream
output                     VALID_DOWN;
input                      READY_DOWN;
output [0:WIDTH-1]         DATA_DOWN;

//---------------------------------------

wire                       CLK;
wire                       RESET;

//Up stream

wire                       VALID_UP;
wire                       READY_UP;
wire   [0:WIDTH-1]         DATA_UP;

//Down Stream

wire                       VALID_DOWN;
wire                       READY_DOWN;
wire   [0:WIDTH-1]         DATA_DOWN;


reg                        fifo_line_valid;
wire                       fifo_push_valid;
reg                        fifo_push_ready;
wire                       fifo_pop_ready;
wire                       fifo_no_push;
wire                       pass_through;
wire                       fifo_pop_valid;
reg    [0:WIDTH-1]         fifo_data;

// Depth 1 FIFO.

always @(posedge CLK)begin
  if(RESET)
    begin
    fifo_line_valid <= 0;
    fifo_push_ready <= 1'b1;
    fifo_data <= {WIDTH{1'b0}};
    end
  else
    begin
      fifo_push_ready <= (fifo_pop_ready||(fifo_no_push && ~fifo_line_valid));
      //Bubble clampping: If last cycle there's no FIFO push and
      //fifo_line is empty,it can be ready.
      if (fifo_push_ready)
        begin
          fifo_line_valid <= fifo_push_valid;
          fifo_data <= DATA_UP;
        end
        else begin
            if (fifo_pop_valid && fifo_pop_ready)
              fifo_line_valid <= 1'b0;
            else 
              fifo_line_valid <= fifo_line_valid;
        end
    end
end

assign fifo_no_push = ~(fifo_push_valid && fifo_push_ready);
assign pass_through = READY_UP && READY_DOWN && ~fifo_line_valid;
assign fifo_push_valid = ~pass_through && VALID_UP;

assign fifo_pop_valid = fifo_line_valid;
assign fifo_pop_ready = READY_DOWN;
assign READY_UP = fifo_push_ready;

//bypass

assign VALID_DOWN = pass_through ? VALID_UP : fifo_line_valid;
assign DATA_DOWN = pass_through ? DATA_UP : fifo_data;

endmodule

换一种思路:

经过上面怼FIFO的分析,我们可以总结起来,主要是以下几点:

  • 加入一个深度为1的同步FIFO,这个FIFO在READY_DOWN为0,且READY_UP为1时暂时存一个数据;
  • 在READY_DOWN从0-1时,FIFO里面的数据先输出到下级
  • 如果READY_DOWN继续为1,数据可以绕过FIFO直通

深度为1的FIFO(不管是同步还是异步FIFO),都是一个特殊的逻辑单元;

所以我们可以这样设计:

1:加一级寄存器作为buffer(实际上就是深度为1的FIFO)

2:当以下条件满足,这一级寄存器会暂存一级数据:

    2.1:READY_DOWN是0,READY_UP是1,并且VALID_UP是1;

也就是:

assign store_data = VALID_UP && READY_UP && ~READY_DOWN;

1:当READY_UP是1时,数据可以直接暴露在下级接口:READY_UP为1时,BUFFER中一定是空的,因为上一个时钟周期数据已经排空了,也就是:

assign VALID_DOWN = READY_UP ? VALID_UP : buffer_valid;

这其实就是上面的FIFO直通模式,同样我们可以挤掉气泡:

READY_UP <= READY_DOWN ||((~buffer_valid) && (~store_data));

把这所有的总结起来:

assign store_data = VALID_UP && READY_UP && ~READY_DOWN;
always@(posedge clk or negedge resetn)
  if(~resetn)
    buffer_valid <= 1'b0;
  else 
    buffer_valid <= buffer_valid ? ~READY_DOWN : store_data;
end

always@(posedge clk or negedge resetn)begin
  if(~resetn)
    buffered_data <= {WIDTH{1'b0}};
  else 
    buffered_data <= store_data ? DATA_UP : buffered_data;
end

always@(posedge clk or negedge resetn)begin
  if(~resetn)
    READY_UP <= 1'b1;
  else 
    READY_UP <= READY_DOWN || ((~buffer_valid) && (~store_data));
end

assign VALID_DOWN = READY_UP ? VALID_UP : buffer_valid;
assign DATA_DOWN = READY_UP ? DATA_UP : buffered_data;

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值