片上网络(NoC)应用广泛,其基本构建模块是需要支持反压机制的连接;该机制允许通信双方相互告知是否有数据待发送或是否具备接收数据的能力以便进行数据正确传输。AXI Strean协议中的 valid/ready(有效/就绪)握手就是现代设计中的典型范例。
如果对上下游的握手控制信号(Ready/Valid)就必须进行“跨级的组合逻辑硬连”。这会引入极长的路径,导致芯片最高时钟频率(Fmax)下降;同时由于 Ready/Valid 信号是“硬连线”(hard-wired),因此也失去了流水线(Pipelining)切断关键路径的初衷。但是,对握手接口进行流水线化处理则更为复杂:虽然在 valid、ready 和数据信号线上简单添加流水线寄存器即可实现功能,但这常常会导致每次突发传输(burst transfer)的启动和停止都需要耗费两个时钟周期。如果涉及批量数据传输,这种延迟对带宽的影响就不可接受了。
设计需求
为了设计这个单一的流水线级(Pipeline Stage)流水线级单元,我们首先需要设计一个基础单元:对外提供 valid/ready 握手协议来接收输入的数据,同时按照相同的握手协议将该数据发送出去。在 AXI stream总线术语中,负责接收的这一侧被称为从接口(Slave Interface),负责发送的另一侧被称为主接 口(Master Interface)。
Input Output
----- ------
-------------
ready <--| |<-- ready
valid -->| Skid Buffer |--> valid
data -->| |--> data
-------------
理想情况下,从(slave)接口和主(master)接口应并发工作以实现最大带宽:即在同一个时钟周期内,从接口接收新数据并将其存入寄存器,同时主接口读取该寄存器中的数据。然而,如果主接口在某周期内不进行数据传输,从接口也绝不能传输数据,否则就会在数据被读取之前覆盖掉寄存器中的原有数据。为避免这一问题,从接口需要在主接口声明“未就绪”的同一周期内也声明“未就绪”。但这会形成两者之间的直接组合逻辑连接,而非流水线连接,不能提供系统的工作时钟。 为了解决这一矛盾,我们需要一个额外的缓冲寄存器。当从接口正在传输数据而主接口未传输,且主寄存器中已有数据时,该缓冲寄存器可用于暂存输入数据。随后,在下一个周期,从接口即可发出“未就绪”信号,从而确保数据不丢失。我们可以把这个额外的缓冲寄存器想象成skid buffer,它允许主接口在等待数据传输。
这是一个典型的、输出全寄存器化的 Skid Buffer 代码结构:
module skid_buffer #(
parameter WIDTH = 32
)(
input wire clk,
input wire rst_n,
// Upstream Interface (Producer)
input wire i_valid,
output reg i_ready,
input wire [WIDTH-1:0] i_data,
// Downstream Interface (Consumer)
output reg o_valid,
input wire o_ready,
output reg [WIDTH-1:0] o_data
);
// 内部核心存储
reg [WIDTH-1:0] main_reg;
reg [WIDTH-1:0] skid_reg;
reg skid_valid;
// 1. 上游 Ready 信号(时序输出,切断反向路径)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
i_ready <= 1'b1;
else
// 只要滑行寄存器是空的,或者下游现在是通的,就能接收新数据
i_ready <= !skid_valid || o_ready;
end
// 2. Skid 寄存器控制逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
skid_valid <= 1'b0;
skid_reg <= {WIDTH{1'b0}};
end else begin
// 核心刹车条件:上游发了有效数据,且我方之前允许接收,但下游堵住了
if (i_valid && i_ready && o_valid && !o_ready) begin
skid_valid <= 1'b1;
skid_reg <= i_data; // 缓存滑行数据
end else if (o_ready) begin
skid_valid <= 1'b0; // 下游通了,Skid 寄存器释放
end
end
end
// 3. Main 寄存器与 o_valid 控制
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
o_valid <= 1'b0;
main_reg <= {WIDTH{1'b0}};
end else begin
if (i_ready && i_valid) begin
if (!o_valid || o_ready) begin
main_reg <= i_data;
o_valid <= 1'b1;
end
end else if (o_ready) begin
o_valid <= skid_valid; // 如果流水线空了,看 Skid 里有没有存货
end
end
end
// 4. 输出数据多路复用
// 如果 Skid 里面有数据,说明它才是最老的(先入的)数据,必须先吐给下游
always @(*) begin
if (skid_valid)
o_data = skid_reg;
else
o_data = main_reg;
end
endmodule
完美的时序切断(Timing Isolation)
在高速数字设计(如 NoC、AXI 总线互联)中,Skid Buffer 通过一种优雅的硬件架构,成功解决了前向数据流与反向反压流的组合逻辑时序瓶颈:
-
前向路径(Forward Path):
输入信号i_valid和i_data必须经过 Main Register 或 Skid Register 的时序采样(寄存)后,才会输出为o_valid和o_data。这使得前向的组合逻辑路径被彻底切断,消除了由于前级扇出过大或物理走线过长导致的时序违例。 -
反向路径(Backward Path):
下游的o_ready并不直接通过组合逻辑连接到上游的i_ready。控制i_ready的核心逻辑采用时序逻辑来实现,即: $$\text{i\_ready} \Leftarrow \text{!skid\_valid} \parallel \text{o\_ready};$$ 通过这种机制,反向的反压(Backpressure)关键路径也被彻底切断。