需要回答几个问题:
1.什么是异步FIFO,异步FIFO有什么功能?
跨时钟域的数据交换,防止亚稳态。
2.在产生写满与读空信号时需要进行跨时钟域如何做的,且如何能正确指示空满状态?
寄存器打两拍+格雷码。
格雷码的具体作用1。
写读的地址是用二进制表示的,只是在将地址同步到对方的时钟域下得时候才会变成格雷码,因为格雷码相邻只有1位不同,即使在同步过程中同步错误,例如000->001,错误的结果仅仅为将原状态000同步过去,比如读同步到写,结果是在没满的时候会提前报写满,不会覆盖数据:<=满。读的情况一样。
3.异步FIFO的写满与读空信号如何利用格雷码正确产生?
格雷码的具体作用2。
另一方面就是如何判断空满,假设fifo空间有8个,0-7,假设读地址是0000,当写地址为8时,实际写地址为1000,对应格雷码是1100,这时候是写满了,读地址的格雷码0000,写地址格雷码1100,二者对应高位与次高位不同,其余位相同,标志写满。
读空是二者格雷码完全相同,这个好理解:读是跟在写后面的,读得再快也是跟上了写,故而‘相同’。
总结:格雷码的唯一目的就是即使在亚稳态进行读写指针抽样,也能进行正确的空满状态指示。
原理框图:
异步FIFO的Verilog设计[2]:
module for_practice#(
parameter N = 8,WD = 3
)(
input w_clk,
input r_clk,
input arst,
input [N-1:0] i_dat,
input i_wren,
input i_rden,
output [N-1:0] o_dat,
output reg empty,
output reg full
);
reg[N-1:0]fifo_mem[2**WD-1:0];
wire [WD-1:0]wadd,radd;
reg [WD:0] wbin,rbin,wgray,rgray;
wire[WD:0] wbin_next,rbin_next;
wire[WD:0] wgray_next,rgray_next;
/*******************************************************/
//产生写地址
assign wbin_next = wbin + (i_wren & ~full );
assign wgray_next = (wbin_next>>1)^wbin_next;
assign wadd = wbin[WD-1:0];
always@(posedge w_clk or posedge arst)
begin
if(arst) {wbin,wgray} <= 0;
else {wbin,wgray} <= {wbin_next,wgray_next};
end
//将读格雷地址同步到写时钟域下,进行写满判断
reg [WD:0] rgray1,rgray2;
always@(posedge w_clk or posedge arst)
begin
if(arst) {rgray2,rgray1} <= 0;
else {rgray2,rgray1} <= {rgray1,rgray};
end
wire fullw;
assign fullw = {wgray_next == {~rgray2[WD:WD-1],rgray2[WD-2:0]}};
always@(posedge w_clk or posedge arst)
if(arst) full <= 0;
else full <= fullw;
//写入数据
always@(posedge w_clk)
if(i_wren && !full)
fifo_mem[wadd] <= i_dat ;
/*******************************************************/
//产生读地址
assign rbin_next = rbin + (i_rden & ~empty );
assign rgray_next = (rbin_next>>1)^rbin_next;
assign radd = rbin[WD-1:0];
always@(posedge r_clk or posedge arst)
if(arst) {rbin,rgray} <= 0;
else {rbin,rgray} <= {rbin_next,rgray_next};
//将写格雷地址同步到读时钟域下,进行读空判断
reg [WD:0] wgray1,wgray2;
always@(posedge r_clk or posedge arst)
if(arst) {wgray2,wgray1} <= 0;
else {wgray2,wgray1} <= {wgray1,wgray};
wire emptyr;
assign emptyr = {rgray_next == wgray2};
always@(posedge r_clk or posedge arst)
if(arst) empty <= 0;
else empty <= emptyr;
//读数据
assign o_dat = fifo_mem[radd];
/*******************************************************/
endmodule
同步FIFO的Verilog设计
思路就是将异步FIFO的跨时钟域的两拍寄存器与格雷码的部分去掉即可。
//读写地址
//当最高位相同,其余位相同认为是读空
//当最高位不同,其余位相同认为是写满
//同步fifo
module syn_fifo#(
parameter N = 8,WD = 3
)(
input clk,
input arst,
input [N-1:0] i_dat,
input i_wren,
input i_rden,
output [N-1:0] o_dat,
output reg empty,
output reg full
);
reg[N-1:0]fifo_mem[2**WD-1:0];//可以在复位时进行初始化为0
reg [WD:0] wbin,rbin;
wire [WD:0] wbin_next,rbin_next;
wire [WD-1:0]wadd,radd;
wire fullw,emptyr;
//产生写地址
assign wbin_next = wbin +(i_wren & ~full );
assign wadd = wbin[WD-1:0];
always@(posedge clk , posedge arst)
if(arst) wbin <= 0;
else wbin <= wbin_next;
//写入数据
always@(posedge clk)
if(i_wren && !full) fifo_mem[wadd] <= i_dat ;
//产生读地址
assign rbin_next = rbin +(i_rden & ~empty );
assign radd = rbin[WD-1:0];
always@(posedge clk , posedge arst)
if(arst) rbin <= 0;
else rbin <= rbin_next;
//读数据
assign o_dat = fifo_mem[radd];
//空满判断
assign fullw = (wbin_next == {~rbin[WD],rbin[WD-1:0]});//注意是wbin_next 与现在的 rbin比
assign emptyr = (wbin == rbin_next); //注意是rbin_next 与现在的 wbin比
always@(posedge clk , posedge arst)
if(arst) { full,empty } <= 0;
else { full,empty } <= { fullw,emptyr };
endmodule
参考资料
[1]异步fifo的设计(FPGA)
[2]异步FIFO的Verilog实现