流控,简单来说就是控制数据流停止发送。常见的流控机制分为带内流控和带外流控。
FIFO的流水反压机制
一般来说,每一个fifo都有一个将满阈值afull_value(almost full)。当fifo内的数据量达到或超过afull_value时,将满信号afull从0跳变为1。上游发送模块感知到afull为1时,则停止发送数据。在afull跳变成1后,fifo需要能够缓存路径上的data以及上游发送模块停止发流之前发出的所有data。这就是fifo的流控机制。下图是fifo流控机制的示意图。
如下图所示,数据data和有效信号vld从模块A产生,经过N拍延时后,输入到FIFO,FIFO产生将满信号afull,经过M拍延时反馈到模块A。假设模块A接收到afull=1时,立即停止发送数据。假设FIFO深度为fifo_depth,每拍为一个时钟周期。
则,我们考虑以下问题:
- 为了保证FIFO不发生溢出,将满阈值afull_value至少应该设置成多少?
- 为了充分发挥FIFO的性能,FIFO深度depth应该为多少?
首先考虑第一个问题,即FIFO将满阈值如何设置:
当FIFO中的数据为afull_value时,产生afull=1,afull=1经过M拍到达模块A,此时FIFO中至多可以有(afull_value+M)个数据。afull=1到达模块A时,模块A立即停止发送数据,此时电路中还存在N拍数据将陆续送到FIFO中,所以最后FIFO中应该为(afull_value+M+N)个数据。
为了保证数据不会溢出,应满足公式fifo_depth >= afull_value+M+N,因此,将满阈值应该至多为depth_fifo - (M+N)。
为了验证上述结论,我们编写了如下代码进行实验:
delayed.sv
module delayed
#(
parameter N = 5,
parameter DW = 1
)
(
input logic clk,
input logic rst_n,
input logic [DW-1:0] din,
output logic [DW-1:0] dout
);
logic [N*DW-1:0] data_reg;
always_ff@(posedge clk or negedge rst_n) begin
if(~rst_n) begin
data_reg <= (N*DW)'(0);
end
else begin
data_reg <= {data_reg[N*DW-DW-1:0], din};
end
end
assign dout = data_reg[N*DW-1:N*DW-DW];
endmodule
top.sv
module top
#(parameter DATA_WIDTH = 32,
parameter DEPTH = 32,
parameter M = 5,
parameter N = 10,
parameter AF_VALUE = (M+N-1)
)
(
input logic clk,
input logic rst_n,
input logic wr_en,
input logic [DATA_WIDTH-1:0] wr_data,
output logic afull,
output logic [DATA_WIDTH-1:0] rd_data,
output logic empty,
input logic rd_en
);
logic wr_en_dly;
logic [DATA_WIDTH-1:0] wr_data_dly;
logic almost_full;
logic error;
logic full;
//
delayed
#
(.N (N ),
.DW(1 )
)
wr_en_delay_inst
(
.clk (clk ),
.rst_n(rst_n ),
.din (wr_en ),
.dout (wr_en_dly)
);
//
delayed
#
(.N (N ),
.DW (DATA_WIDTH)
)
wr_data_delay_inst
(
.clk (clk ),
.rst_n (rst_n ),
.din (wr_data ),
.dout (wr_data_dly)
);
//
delayed
#
(.N (M),
.DW(1)
)
almost_full_delay_inst
(
.clk (clk ),
.rst_n(rst_n ),
.din (almost_full),
.dout (afull )
);
//
DW_fifo_s1_sf
#
(.width (DATA_WIDTH ),
.depth (DEPTH ),
.ae_level(1 ),
.af_level(AF_VALUE ),
.err_mode(1 ),
.rst_mode(0 )
)
U (.clk (clk ),
.rst_n (rst_n ),
.push_req_n (~wr_en_dly ),
.pop_req_n (~rd_en ),
.diag_n (1 ),
.data_in (wr_data_dly ),
.empty (empty ),
.almost_empty ( ),
.half_full ( ),
.almost_full (almost_full ),
.full (full ),
.error (error ),
.data_out (rd_data )
);
endmodule
tb.sv
module tb;
parameter DATA_WIDTH = 32;
parameter DEPTH = 32;
parameter M = 5;
parameter N = 10;
parameter AF_VALUE = M + N;
logic rst_n;
logic clk;
logic rd_en;
logic rd_en_r;
logic wr_en;
logic wr_en_r;
logic empty;
logic afull;
logic [DATA_WIDTH-1:0] wr_data;
logic [DATA_WIDTH-1:0] rd_data;
logic [DATA_WIDTH-1:0] ref_data;
logic error;
assign error = (rd_en && ~empty && (ref_data != rd_data)) ? 1'b1 : 1'b0;
always_ff@(posedge clk or negedge rst_n) begin
if(~rst_n) begin
ref_data <= '0;
end
else if(rd_en && ~empty) begin
ref_data <= ref_data + 1'b1;
end
end
//rd_en
always_ff@(posedge clk or negedge rst_n) begin
if(~rst_n) begin
rd_en_r <= 1'b0;
end
else if($urandom % 100 < 1) begin
rd_en_r <= 1'b1;
end
else begin
rd_en_r <= 1'b0;
end
end
assign rd_en = rd_en_r && ~empty;
//wr_data
always_ff@(posedge clk or negedge rst_n) begin
if(~rst_n) begin
wr_data <= '0;
end
else if(wr_en && ~afull) begin
wr_data <= wr_data + 1'b1;
end
end
//wr_en
always_ff@(posedge clk or negedge rst_n) begin
if(~rst_n) begin
wr_en_r <= 1'b0;
end
else if($urandom % 100 < 100) begin
wr_en_r <= 1'b1;
end
else begin
wr_en_r <= 1'b0;
end
end
assign wr_en = wr_en_r && ~afull;
//clk
initial
begin
clk = 1'b0;
forever begin
#5 clk = ~clk;
end
end
//rst
initial
begin
rst_n = 1'b0;
#100
rst_n = 1'b1;
end
//
initial
begin
#20000
$finish;
end
//
initial begin
$fsdbDumpfile("./top.fsdb");
$fsdbDumpvars(0);
end
//inst
top
#(.DATA_WIDTH(DATA_WIDTH),
.DEPTH (DEPTH ),
.M (M ),
.N (N ),
.AF_VALUE (AF_VALUE )
)
U(.*);
endmodule
以及makefile脚本:
all: listfile com sim verdi clean
listfile:
find -name "*.sv" > filelist.f
com:
vcs -full64 -cpp g++-4.8 -cc gcc-4.8 -LDFLAGS -Wl,--no-as-needed -sverilog -debug_access -timescale=1ns/10ps \
-f filelist.f -l com.log -kdb -lca -y ${SYNOPSYS}/dw/sim_ver +libext+.v +incdir+${SYNOPSYS}/dw/sim_ver+
sim:
./simv -l sim.log
verdi:
verdi -sv -f filelist.f -ssf *.fsdb -nologo &
clean:
rm -rf csrc *.log *.key *simv* *.vpd *DVE*
rm -rf verdiLog *.fsdb *.bak *.conf *.rc *.f
当设置fifo的将满阈值为M+N时,fifo不会丢失数据,流控正确。(注:dw fifo中的将满阈值afull的定义为:当fifo中还有小于等于afull个空位置时,拉高afull信号)
当设置fifo的将满阈值为M+N-1时,fifo会丢失数据,流控出错。如下图所示:
现在考虑第二个问题,即FIFO深度depth应该为多少?
分析:若fifo_depth过小,afull有效之后,fifo中存储的数据将很快被下游读取,而新的数据又无法及时到达FIFO,因此会造成流水气泡,影响电路的性能。
假设M=5, N=10,且fifo_depth=20,则afull_value=5。在T时刻,fifo中存了5个数据后afull=1会变为有效,在之后的15个周期内会陆续存入15个数据。假设下游模块B每个周期读取FIFO中的一个数据,因为当FIFO内的数据data_cnt小于5时,afull才会无效(为0),因此在T+15和T+30的时刻内,下游电路B只能读5个数据,从而造成数据断流,影响电路性能。
为了保证电路性能,在T+15到T+30这个时间段内应该有15个数据可读,因此afull_value应该不小于15(=M+N)。故FIFO深度应该不小于2*(M+N)。