流水线设计中的反压操作
在流水线设计中,如果考虑数据的安全性,就需要与前后级模块进行握手通信,这个时候就需要对流水数据进行反压处理
在握手协议中valid与ready不可以过度依赖,比如valid不可以等待ready到达再拉高,原因在于“防止死锁”。但是axi协议中的握手信号,ready是可以等待valid信号拉高在进行拉高的。同时,valid拉高要与有效数据同步,时钟对齐。此外,数据准备好后,valid可以拉高等待ready拉高,但是每当握手成功后,数据需要更新,此时没有有效数据的话,valid需要拉低。
当输入流量大于输出流量的时候,就需要反压。或者说,后级没有准备好进行数据接收,如果前级进行数据传递,就需要后级进行反压前级,如果只反压后级的前一级,就会导致后级的前一级数据丢失。
带存储体的反压
如果对每一级流水进行反压的话,就太过于麻烦,可以使用带存储体的反压,也就是增加RAM或者FIFO,在反压上一级模块的同时,还能够安全存储流水线上的数据。
假设车库就是最后一级模块,闸机是上级模块,流水线(也就是途中)最多10个数据,那么安全起见,车库在数据量达到90的时候就需要进行反压,不再让闸机向流水线放数据。
示例
设计一个6输入的32bit加法器,输出一个带截断的32bit加法结果,要求用三级流水设计,带前后反压
分析:在多级流水设计中,如果每一级只有一个寄存器,并在一个模块中,也就是当颗粒度划分很细的时候,一般使用带存储体的反压,比如6级流水,那么就设计好流水线,在FIFO未满的时候提前发出反压信号,一般水线设置为FIFO_DEPTH - 流水线级数,当FIFO未达到水线的时候,给上一级的ready持续拉高,否则拉低;FIFO非空的时候给下一级的valid拉高,下一级给FIFO的ready信号可以当做读使能信号
带存储体的反压
//多级流水设计中,每一级都只有一个寄存器,并且都在一个模块,也就是颗粒度划分很细,
//一般使用带存储体的反压,设计好流水线,FIFO没有满的时候提前发出反压信号,一般水线=FIFO_DEPTH - 流水级数
//核心思想 fifo未达到水线时候给上一级的ready_o信号持续拉高,否则为低;fifo非空,就给下一级valid_o拉高,
//下一级的反压信号ready_i还可以作为FIFO读使能信号
//https://zhuanlan.zhihu.com/p/359330607
module hand_shake_fifo#(
parameter FIFO_DATA_WIDTH = 32,
parameter FIFO_DEPTH = 8
)(
input clk,
input rst,
input valid_i,
input ready_i,
input [31:0] a,
input [31:0] b,
input [31:0] c,
input [31:0] d,
input [31:0] e,
input [31:0] f,
output [31:0] dout,
output ready_o,
output valid_o
);
localparam WATERLINE = FIFO_DEPTH - 3;
wire handshake;
reg handshake_ff1;
reg handshake_ff2;
reg wr_en;
assign handshake = ready_o & valid_i;
always@(posedge clk or negedge rst)begin
if(rst)begin
handshake_ff1 <= 'd0;
handshake_ff2 <= 'd0;
end
else begin
handshake_ff1 <= handshake;
handshake_ff2 <= handshake_ff1;
end
end
reg [31:0] r1_ab;
always@(posedge clk or negedge rst)begin
if(rst)
r1_ab <= 'd0;
else if(handshake)
r1_ab <= a+b;
end
reg [31:0] r1_cd;
always@(posedge clk or negedge rst)begin
if(rst)
r1_cd <= 'd0;
else if(handshake)
r1_cd <= c+d;
end
reg [31:0] r1_ef;
always@(posedge clk or negedge rst)begin
if(rst)
r1_ef <= 'd0;
else if(handshake)
r1_ef <= e+f;
end
reg [31:0] r2_abcd;
always@(posedge clk or negedge rst)begin
if(rst)
r2_abcd <= 'd0;
else if(handshake_ff1)
r2_abcd <= r1_ab+r1_cd;
end
reg [31:0] r2_ef;
always@(posedge clk or negedge rst)begin
if(rst)
r2_ef <= 'd0;
else if(handshake_ff1)
r2_ef <= e+f;
end
reg [31:0] r3;
always@(posedge clk or negedge rst)begin
if(rst)
r3 <= 'd0;
else if(handshake_ff2)
r3 <= r2_abcd + r2_ef;
end
always@(posedge clk or negedge rst)begin
if(rst)
wr_en <= 1'b0;
else if(handshake_ff2)
wr_en <= 1'b1;
else
wr_en <= 1'b0;
end
always@(posedge clk or negedge rst)begin
if(rst)begin
ready_o <= 1'b0;
end
else if(usedw > WATERLINE)
ready_o <= 1'b0;
else
ready_o <= 1'b1;
end
assign valid_o = ~empty;
sync_fifo # (
.MEM_TYPE ("auto" ),
.READ_MODE ("fwft" ),
.WIDTH (FIFO_DATA_WIDTH),
.DEPTH (FIFO_DEPTH )
)fifo_inst(
.clk (clk ), // input wire
.rst_n (rst_n ), // input wire
.wren (wr_en ), // input wire
.din (r3 ), // input wire [WIDTH-1:0]
.rden (ready_i ), // input wire
.dout (dout ), // output reg [WIDTH-1:0]
.empty (empty ), // output wire
.usedw (usedw )
);
endmodule
如果不让使用fifo该怎么处理呢?
核心思想就是保证每一级流水的每一个寄存器的安全。将每一级寄存器当做深度为1的fifo,下一级有无数据可以看对应的valid信号。下一级无数据或者下一级已经准备好了,就可以向上一级取数据,本质上是pre-fetch结构。
跨级反压与逐级反压
上图中每一个模块内部都含有存储体,假设module3到达水线,那么有两种方式反压,跨级反压和逐级反压。
当每一个模块都有存储体的话,逐级反压会更好。
解释:如果采用逐级反压,方法就是module3到达水线的时候反压module2,module2反压module1.如果进行跨级反压,那么module3的存储体的深度=途1+途2+途3+waterline1+waterline2+waterline3,可以看到,存储体3变大并且计算复杂。