参考资料:
ic基础|时序篇:握手协议valid和ready的时序优化_valid和ready握手信号-CSDN博客
https://zhuanlan.zhihu.com/p/365573848
1、AXI握手协议
当我们遇到时序违例时,通常采用的方式为插入寄存器(打拍)或者是后端插入buffer,这对使能信号或数据信号是有用的,但是对于那些需要满足握手协议的信号来说(例如:AXI协议中的多组握手信号xxready和xxvalid)单纯的打拍是行不通的,因为需要满足valid-ready协议,如果仅仅使用打拍很容易丢失数据。
因此,需要采取特殊的方法进行打拍,这种针对AXI协议中握手信号的打拍通常称为axi register slice,通常来说,根据需要打拍的信号的不同有三种模式:
前向寄存器Forward Registered :对valid和data路打拍。
后向寄存器Backward Registered :对ready路打拍。
双向寄存器Forward-Backward Registered :同时对valid/data路和ready路打拍。
(1)Forward Registered
Forward指的是从数据发送方到数据接收方之间的数据传递方向,为了保持时序上的一致性,需要打拍的当然不止是valid信号,还得包括data信号。否则,valid信号和data信号对不齐的话,数据传输会出错。
对于forward寄存器打拍,我们所要考虑的是何时可以往寄存器里打拍,即满足以下两个条件之一:
1.forward寄存器里是空的;
2.forward寄存器非空,但是接收端准备好接收数据了;
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) begin
if (!rst_n) begin
valid_dst <= 1'b0;
end else if (ready_src) begin
valid_dst <= valid_src;
end
end
always @(posedge clk) begin
if (valid_src & ready_src) begin
data_dst <= data_src;
end
end
assign ready_src = ready_dst || (~valid_dst);
endmodule
(2)Backward Registered
Backward指的是接收端向输入端发送的ready信号这条路径,虽然只有ready这一个信号,但是我们也不能简单的对其进行打拍处理。因为,如果只对ready信号打拍,而不对valid和data信号进行处理,很容易导致两端握手信号将会无法正确同步,而导致数据丢失。
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) begin
if (!rst_n) begin
valid_r <= 1'b0;
end else if (ready_dst) begin
valid_r <= 1'b0;
end else if (valid_src & ready_src & ~ready_dst) begin
valid_r <= 1'b1;
end
end
always @(posedge clk) begin
if (!rst_n) begin
data_r <= 1'b0;
end else if (valid_src & ready_src & ~ready_dst) begin
data_r <= data_src;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ready_src <= 1'b1;
end else if (ready_dst) begin
ready_src <= 1'b1;
end else if (valid_src) begin
ready_src <= 1'b0;
end
end
assign valid_dst = ready_src ? valid_src : valid_r;
assign data_dst = ready_src ? data_src : data_r;
endmodule
单看代码有点复杂了……,来一个个信号看。相比于前向寄存器,后向寄存器单独创建了两个中间寄存器,valid_r用来对发送端的valid_src进行暂存。
当接收端ready_dst信号为1时,置0,此时代表接受端有能力接受数据,因此无需进行暂存;而当发送端valid_src信号有效,ready_src有效,代表发送方完成了一次握手,若此时接受端ready_dst无效,则代表此时需要缓存数据(即将valid_r置1),以避免丢失。(ready_dst的信号还没传递到src端)
data_r和valid_r同理。
ready_src用于在接收端未准备好时,一旦发送端准备好数据,即可暂存data_src。当ready_dst=0且valid_src=1时暂存一拍。
valid_dst和data_src这两个信号受到ready_src的控制,ready_src=1时直接传递发送端口的数据,ready_src=0时使用寄存器暂存的数据。
(3)Forward-Backward Registered
即对valid/data和ready都进行时序优化的寄存器,我们可以使用上面提到的Forward Registered 和Backward Registered拼合在一起即可得到Forward-Backward Registered。
其中,Backward Registered靠近dst端,Forward Registered靠近src端。
2、AXI的outstanding
首先要理解概念,什么是outstanding?
当需要传输一段数据时,在正常情况下,我们需要等到地址握手成功才能传输。但是每一次握手过程中都不能保证主从机此刻都准备好,那这样在数据传输过程中就难免会有气泡,难以实现真正的全流水和满性能。所以从设计的角度,我们就有多发几个地址的需求,比如我们可以发出去10个地址,尽管我们还没给数据,但是可以让从机知道,我们接下来要向这些地址写数据,这样就可以不用刻板地遵循,每次都要写地址握手成功再写数据。
outstanding就是发出去的地址数量,未处理的地址可以先存放在AXI总线的缓存里,等完成一次传输事物之后,无需再握手传输地址,即可立即进行下一次的数据传输,所以outstanding本质上是为了实现数据传输的pipeline。
那么如何计算outstanding数量的最大值呢。
有几个重要参数:
期望的最大带宽,在满带宽情况下最大值自然是数据源源不断的传输,即Expected Bandwidth = AXI Clock Frequency x AXI Data Width
往返时延,即AXI总线数据由于握手信号的等待而产生的传输延迟,用Round Trip Latency表示。
因此,如果要满足数据传输没有气泡,即在总线上由延迟产生的空位全被outstanding的数据填满,等式:
经过移相,可以化简为:
最后丢一道前几天笔试遇到的题目:
根据上方的公式推导,很容易就可以算出来BestOutstanding=12.5,由于outstanding只能为整数,又要满足满带宽,所以向上取整,最后答案为13。