大家好,我是数字小熊饼干,一个练习时长两年半的ic打工人。我在两年前通过自学跨行社招加入了IC行业。现在我打算将这两年的工作经验和当初面试时最常问的一些问题进行总结,并通过汇总成文章的形式进行输出,相信无论你是在职的还是已经还准备入行,看过之后都会有有一些收获,如果看完后喜欢的话就请关注我吧~谢谢~
当我们遇到时序违例时,通常采用的方式为插入寄存器(打拍)或者是后端插入buffer,这对使能信号或数据信号是有用的,但是对于那些需要满足握手协议的信号来说(例如:AXI协议中的多组握手信号xxready和xxvalid)单纯的打拍是行不通的,因为需要满足valid-ready协议,如果仅仅使用打拍很容易丢失数据。
因此,需要采取特殊的方法进行打拍,这种针对AXI协议中握手信号的打拍通常称为axi register slice,通常来说,根据需要打拍的信号的不同有三种模式:
- 前向寄存器Forward Registered :对valid和data路打拍。
- 后向寄存器Backward Registered :对ready路打拍。
- 双向寄存器Forward-Backward Registered :同时对valid/data路和ready路打拍。
一、Forward Registered
首先来分析的是第一种Forward Registered。
Forward指的是从数据发送方到数据接收方之间的数据传递方向,为了保持时序上的一致性,需要打拍的当然不止是valid信号,还得包括data信号。否则,valid信号和data信号对不齐的话,数据传输会出错。
Forward 打拍的电路结构如下所示:
对于forward寄存器打拍,我们所要考虑的是何时可以往寄存器里打拍,即满足以下两个条件:
1.forward寄存器里是空的;
2.forward寄存器非空,但是接收端准备好接收数据了;
因此有以下的verilog代码:
module forward_buffer(input wire clk ,input wire rst_n ,input wire valid_src ,input wire [31:0] data_src,output wire ready_src ,output reg valid_dst ,output reg [31:0] data_dst ,input wire ready_dst
);always @(posedge clk or negedge rst_n) beginif (!rst_n) beginvalid_dst <= 1'b0;end else if (ready_src) beginvalid_dst <= valid_src;endend always @(posedge clk) begin if (valid_src & ready_src) begindata_dst <= data_src;endendassign ready_src = ready_dst || (~valid_dst);endmodule
解释一下上面的代码:
-valid_dst: 用于对发送端的valid_src进行打拍,它会在以下两种情况接收发送端的valid_src的值:
valid_dst为0:代表此时valid_dst寄存器和data_dst寄存器没有有效数据,为空,可以接收发送端的输入valid_src。ready_dst为1:代表此时接收端准备好了接收数据,则有以下两种情况:-若此时valid_dst为0:代表此时接收方先准备好接受数据,而valid_dst寄存器和data_dst寄存器没有有效数据,可以接收发送端的输入valid_src。-若valid_dst为1:发送方和接收方都完成了一次握手,valid_dst寄存器和data_dst寄存器中的数据可以更新。
-data_dst: 数据的打拍很好理解,同时满足以下两个条件即可对数据打拍:
当发送端数据准备好(即valid_src有效时)。接收端准备好接受数据时(ready_dst为1)或者是valid_dst寄存器和data_dst寄存器没有有效数据时(valid_dst为0)。提示一下,这里的数据通路如果没有用于控制逻辑是可以使用不带复位端以节省面积的。
-ready_src: 输出给发送端的ready信号,满足以下两个条件之一即有效:
当接受端准备好接受数据时(ready_dst);valid_dst寄存器和data_dst寄存器为空,即没有有效数据时(valid_dst为0)。
简单仿真一下上述模块,波形如下所示:
二、backward Registered
Backward指的是接收端向输入端发送的ready信号这条路径,虽然只有ready这一个信号,但是我们也不能简单的对其进行打拍处理。因为,如果只对ready信号打拍,而不对valid和data信号进行处理,很容易导致两端握手信号将会无法正确同步,而导致数据丢失,例如下图所示:
如图所示,由于发送端的ready_src与ready_dst相比存在一个周期的延迟,导致ready_dst变化后,ready_src在一个周期后才相应变化,从而导致了data2丢失,data3重复采样。
因此合适的方法还是在为发送端的信号加一级寄存器。以满足接收端没有ready时发送端发来请求,需要暂存数据的场景,即valid_src & ready_src & ~ready_dst。等接收端准备好接受数据后,会接受到暂存的数据,因此数据不会丢失,也不会重复采样。
backward 打拍的电路结构如下所示:
verilog代码如下所示:
module backward_buffer(input wire clk ,input wire rst_n ,input wire valid_src ,input wire [31:0] data_src ,output reg ready_src ,output wire valid_dst ,output wire [31:0] data_dst ,input wire ready_dst
);reg [31:0] data_r;reg valid_r;always @(posedge clk or negedge rst_n) beginif (!rst_n) beginvalid_r <= 1'b0;end else if (ready_dst) beginvalid_r <= 1'b0;end else if (valid_src & ready_src & ~ready_dst) beginvalid_r <= 1'b1;endendalways @(posedge clk) beginif (valid_src & ready_src & ~ready_dst) begindata_r <= data_src;endendalways @(posedge clk or negedge rst_n) beginif (!rst_n) beginready_src <= 1'b1;end else if (ready_dst) beginready_src <= 1'b1;end else if (valid_src) beginready_src <= 1'b0;endend assign valid_dst = ready_src ? valid_src : valid_r;assign data_dst = ready_src ? data_src : data_r;endmodule
解释一下上面的代码:
-valid_r: 用于对发送端的valid_src进行暂存,会在以下情况置1或置0:
当接收端ready_dst信号为1时,置0,此时代表接受端有能力接受数据,因此无需进行暂存。当发送端valid_src信号有效,ready_src有效,代表发送方完成了一次握手,若此时接受端ready_dst无效,则代表此时需要缓存数据(即将valid_r置1),以避免丢失。
-data_r: 和valid_dst的逻辑类似,当满足valid_src & ready_src & ~ready_dst时,暂存发送端输入的数据data_src。
-ready_src:通过以下逻辑控制:
复位值为1,用于在接收端未准备好时,一旦发送端准备好数据,即可暂存data_src。接收端ready_dst有效,则置1。发送端valid_src有效时,置0。
-valid_dst和data_src,这两个信号根据ready_src的值选择输出来自发送端还是暂存寄存器,即:
当ready_src有效时,代表此时接收端或者是暂存寄存器可以接受数据,此时输入接收端的valid和data为发送端的输入信号valid_src和data_src。当ready_src无效时,代表此时暂存寄存器中的数据有效,因此输入接收端的数据为暂存信号valid_r和data_r。
使用上述backward寄存器后,理论上有以下时序图:
可见在使用了backward寄存器后,数据不会出现丢失的情况。
接下来我们对backward_buffer模块进行仿真验证,波形如下:
观察波形可以发现,ready_src相对于ready_dst延迟了一拍,并且在数据接收方还未准备好(即ready_src有效,ready_dst无效)但数据发送方发送数据(即valid_src有效)的这种情况下,对数据发送端的data_src和valid_src进行了缓存,因此该模块达成了我们的设计目标。
三、Forward-Backward Registered
Forward-Backward Registered即对valid/data和ready都进行时序优化的寄存器,我们可以使用上面提到的Forward Registered 和Backward Registered拼合在一起即可得到Forward-Backward Registered,只要注意一下两类寄存器之间的摆放顺序即可:由于前向寄存器Forward Registered是对valid和ready进行打拍,而后向寄存器Backward Registered是对ready进行打拍,因此我们要将Forward Registered放在距离接收端近的地方,将Backward Registered放在距离发送端近的地方,如下图所示:
波形仿真如下:
观察波形,可见该模块对valid_src、data_src和ready_dst都打了一拍,达成了我们的设计目标!
除了上面的方法之外,我们也可以利用同步fifo来实现发送端和同步端之间的数据传输,利用full信号取反后来作为发送端的ready_src信号,fifo非满即可写入数据,利用empty信号取反,来作为接收端的valid_dst信号,fifo非空即说明其中有数据,可进行读操作。
而这个同步fifo的深度至少要大于2,因为如果fifo深度为1,那么fifo就会要么是“满”要么是“空”,接收端和发送端的传输总是会由于fifo的空满状态而变得断断续续的,影响数据传输效率。
如果你喜欢这篇文章的话,请关注我的公众号-熊熊的ic车间,里面还有ic设计和ic验证的学习资料和书籍等着你呢~欢迎您的关注!