在知乎看到的2篇文章,部分转载过来,觉得这个细节以前确实不怎么注意,里面还是有点小学问的.
原文:https://zhuanlan.zhihu.com/p/212356622
内容提要
-
valid 与data 的时序修复时的打拍
-
如何无气泡?
问题描述
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 <= READY_UP ? VALID_UP : VALID_DOWN ;
DATA_DOWN <= handshake ? DATA_UP : DATA_DOWN ;
READY_UP = READY_DOWN ;
进行修改——无气泡传输
对其继续进行修改,可以发现现在的电路,已经存在了一级寄存器。这一级寄存器,可以给上一级的data,多提供一级存储。也就是说,就算是下级ready是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打拍的情况。详细示例代码如下,仅供参考:
module valid_flop
(
CLK ,
RESET ,
VALID_UP ,
READY_UP ,
DATA_UP ,
VALID_DOWN ,
READY_DOWN ,
DATA_DOWN
);
//-----------------------------------------------------------------------------
parameter WIDTH = 32 ;
//-----------------------------------------------------------------------------
input CLK ;
input RESET ;
input VALID_UP ;
output READY_UP ;
input [WIDTH-1:0] DATA_UP ;
output VALID_DOWN ;
input READY_DOWN ;
output [WIDTH-1:0] DATA_DOWN ;
//-----------------------------------------------------------------------------
wire CLK ;
wire RESET ;
wire VALID_UP ;
wire READY_UP ;
wire [WIDTH-1:0] DATA_UP ;
//Down Stream
reg VALID_DOWN ;
wire READY_DOWN ;
reg [WIDTH-1:0] DATA_DOWN ;
//-----------------------------------------------------------------------------
//Valid
always @(posedge CLK)
if (RESET) VALID_DOWN <= 1'b0 ;
else VALID_DOWN <= READY_UP ? VALID_UP : VALID_DOWN ;
//Data
always @(posedge CLK)
if (RESET) DATA_DOWN <= {WIDTH{1'b0}} ;
else DATA_DOWN <= (READY_UP && VALID_UP) ? DATA_UP : DATA_DOWN ;
//READY with buble collapsing.
assign READY_UP = READY_DOWN || ~VALID_DOWN ;
//READY with no buble collapsing.
//assign READY_UP = READY_DOWN ;
endmodule
模型变化
上面的模型可以有很多变化,比如一种协议,不存在反压,或者说只有valid 和data,数据总是可以发送的。
VALID_DOWN <= VALID_UP ;
DATA_DOWN <= VALID_UP ? DATA_UP : DATA_DOWN ;
valid only_no_data模型
上面的模型继续变化,比如只有control信号,没有数据(data)。就简化成直接打一拍了。
VALID_DOWN <= VALID_UP ;
上面是valid相关的部分,下面是ready相关的部分.
内容提要
- ready打拍的问题
- 用FIFO的思路去解决
- 用Buffer的思路去解决
问题提出:ready时序如何优化?
在valid/ready 握手协议中,valid 与 data的时序优化比较容易理解,(不熟悉valid/ready协议或者valid打拍方法的)大家可以参考上次推送(握手协议(pvld/prdy或者valid-ready或AXI)中Valid及data打拍技巧)。 但是有时候,关键路径是在ready信号上,如何对ready信号打拍呢?
首先将把目标设计想象成一个黑盒子,如图1所示,我们的目标是将READY_DOWN通过打拍的方法获得时序优化。

(图1)
尝试直接对ready打一拍
READY_UP <= READY_DOWN; VALID_DOWN = valid_up;
(仅示例,非verilog代码。下同)
这样是行不通的。
一个简单的例子(case 1)就是你让READY_DOWN像一个时钟一个,间隔一个cycle起来一次,那么VALID_UP && READY_UP 与 VALID_DOWN && READY_DOWN无法同步,数据无法传输下去。
思路:将其分解成两个interfaces
将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; //or READY_UP = function(READY_DOWN_next); transfer_up = VALID_UP && READY_UP; transfer_down = VALID_DOWN && READY_DOWN;
如果去解决刚才例子(case 1),那么这个黑盒子:
当READY_UP为高的时候,可以接受数据; 当READY_DOWN为高的时候,如果我们有数据可发的话,我们可以向downstream发送数据;
是不是很像一个FIFO?
用FIFO去解决
将一个FIFO插在黑盒子这里,那么就会变成这样子:

(图2)
VALID_UP/READ_YUP ==> FIFO ==> VALID_DOWN/READY_DOWN
也就是:
VALID_UP = fifo_push_valid; READY_UP = fifo_push_ready; VALID_DOWN = fifo_pop_valid;READY_DOWN = 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传输数据,可以这样设计:
// 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; 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?
无气泡传输
关于无气泡传输,可以参考上一篇推送(握手协议(pvld/prdy或者valid-ready或AXI)中Valid及data打拍技巧)。具体的说,就是既然你这里有个深度为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穿越
考虑一个特殊情况(case 2):
假设READY_DOWN在复位之后始终为1,
然后某个时刻开始VALID_UP为1了。
是不是每个周期,数据都可以直接传下来而不用进入FIFO,即使READY_DOWN打过一拍?
换句话说:如果READY_UP=1, READY_DOWN=1, FIFO是空的这种情况下,数据可以直通。
- 上文特殊情况(case 2),READY_DOWN/READY_UP一直是1,显然可以。
- READY_UP从0到1的跳变:READY_DOWN也会在前一周期有一个从0到1的跳变。在READY_DOWN为0时,有一笔数据存到FIFO里边(无气泡传输);当READY_DOWN在时刻a从0变到1时,READY_UP在时刻a+1也会从0变为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;
将所有这些结合起来:
//--------------------------------------- // File Name : ready_flop.v // Author : Xiangzhi Meng // Date : 2020-06-06 // Version : 0.1 // Description : // 1. ready_flop using one depth-1 FIFO to hold data. // // All rights reserved. `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