一、亚稳态状态
对于使用上升沿触发的触发器来说,建立时间(Setup Time)是在时钟上升沿到来之前,触发器数据保持稳定的最小时间;而保持时间(Hold Time)是在时钟上升沿到来之后,触发器数据还应该保持的最小时间[2]。如图1所示,在时钟上升沿前后的这个窗口内数据应该保持不变,否则会使触发器工作在一个不确定的状态,即亚稳态。当触发器处于亚稳态,且处于亚稳态的时间超过了一个时钟周期时,这种不确定的状态将会影响到下一级的触发器,最终导致连锁反应,从而使整个系统功能失常。当一个信号跨越某个时钟域时,对新时钟域的电路来说它就是一个异步信号。由于异步信号之间的时序是毫无关系的,因此必然存在Setup Time/Hold Time冲突。为了避免亚稳态问题,采用如图2所示的双锁存器法[3],即在一个信号进入另一个时钟域前,将该信号用两个锁存器连续锁存两次,最后得到的采样结果就可以消除亚稳态。
消除亚稳态只是保证了信号电平的稳定,要在不同时钟域中准确传输数据还需要一个接口电路。异步FIFO就是一种不同时钟域之间传递多位数据的常用方法。
二、异步FIFO设计
FIFO设计的难点 是如何判断空/满标志
在涉及到触发器的电路中,亚稳态无法彻底消除,只能想办法将其发生的概率将到最低。其中的一个方法就是使用格雷码。格雷码在相邻的两个码元之间只由一位变换(二进制码在很多情况下是很多码元在同时变化)。这就会避免计数器与时钟同步的时候发生亚稳态现象。但是格雷码有个缺点就是只能定义2^n的深度,而不能像二进制码那样随意的定义FIFO的深度,因为格雷码必须循环一个2^n,否则就不能保证两个相邻码元之间相差一位的条件,因此也就不是真正的各雷码了。例如,读指针从011变化到100时,所有位都要变化,读指针的每一位在读时钟的作用下,跳变不一致,即产生毛刺。如果写时钟恰好在读指针的变化时刻采样,得到的采样信号可能是000~111中的任何一个,从而导致空/满信号判断错误。由实践可知,同步多个异步输入信号出现亚稳态的概率远远大于同步一个异步信号的概率[3]。解决这一问题的有效方法是采用格雷码。格雷码的主要特点是相邻的两个编码之间只有一位变化。图4是格雷码产生的逻辑框图。在读使能或写使能信号有效、并且空/满标志无效的情况下,读写指针开始累加,进行FIFO读或写操作。二进制码与格雷码的转换是一个“异或”运算:gnext=(bnext>>1)^bnext。格雷码gnext 经寄存器输出格雷码指针ptr。这种方法采用了两组寄存器,虽然面积较大,但是有助于提高系统的工作频率。
第二就是使用冗余的触发器,假设一个触发器发生亚稳态的概率为P,那么两个及联的触发器发生亚稳态的概率就为P的平方。但这回导致延时的增加。亚稳态的发生会使得FIFO出现错误,读/写时钟采样的地址指针会与真实的值之间不同,这就导致写入或读出的地址错误。由于考虑延时的作用,空/满标志的产生并不一定出现在FIFO真的空/满时才出现。可能FIFO还未空/满时就出现了空/满标志。这并没有什么不好,只要保证FIFO不出现overflow or underflow 就OK了。
很多关于FIFO的文章其实讨论的都是空/满标志的不同算法问题。
第一个算法:Clifford E. Cummings的文章中提到的STYLE #1,构造一个指针宽度为N+1,深度为2^N字节的FIFO(为便方比较将格雷码指针转换为二进制指针)。当指针的二进制码中最高位不一致而其它N位都相等时,FIFO为满(在Clifford E. Cummings的文章中以格雷码表示是前两位均不相同,而后两位LSB相同为满,这与换成二进制表示的MSB不同其他相同为满是一样的)。当指针完全相等时,FIFO为空。
这种方法思路非常明了,为了比较不同时钟产生的指针,需要把不同时钟域的信号同步到本时钟域中来,而使用Gray码的目的就是使这个异步同步化的过程发生亚稳态的机率最小,而为什么要构造一个N+1的指针,Clifford E. Cummings也阐述的很明白,有兴趣的读者可以看下作者原文是怎么论述的,Clifford E. Cummings的这篇文章有Rev1.1 Rev1.2两个版本,两者在比较Gray码指针时的方法略有不同,个Rev1.2版更为精简。
第二种算法:Clifford E. Cummings的文章中提到的STYLE #2。它将FIFO地址分成了4部分,每部分分别用高两位的MSB 00 、01、 11、 10决定FIFO是否为going full 或going empty (即将满或空)。如果写指针的高两位MSB小于读指针的高两位MSB则FIFO为“几乎满”,若写指针的高两位MSB大于读指针的高两位MSB则FIFO为“几乎空”。
它是利用将地址空间分成4个象限(也就是四个等大小的区域),然后观察两个指针的相对位置,如果写指针落后读指针一个象限(25%的距离,呵呵),则证明很可能要写满,反之则很可能要读空,这个时候分别设置两个标志位dirset和dirrst,然后在地址完全相等的情况下,如果dirset有效就是写满,如果dirrst有效就是读空。
这种方法对深度为2^N字节的FIFO只需N位的指针即可,处理的速度也较第一种方法快。
异步的多时钟设计应按以下几个原则进行设计:
1,尽可能的将多时钟的逻辑电路(非同步器)分割为多个单时钟的模块,这样有利于静态时序分析工具来进行时序验证。
2,同步器的实现应使得所有输入来自同一个时钟域,而使用另一个时钟域的异步时钟信号采样数据。
3,面向时钟信号的命名方式可以帮助我们确定那些在不同异步时钟域间需要处理的信号。
4,当存在多个跨时钟域的控制信号时,我们必须特别注意这些信号,保证这些控制信号到达新的时钟域仍然能够保持正确的顺序。
异步FIFO及verilog原码_续
两篇文章的原码
module fifo1(rdata, wfull, rempty, wdata, winc, wclk, wrst_n,rinc, rclk, rrst_n);
parameter DSIZE = 8; parameter ASIZE = 4;
output [DSIZE-1:0] rdata;
output wfull;
output rempty;
input [DSIZE-1:0] wdata;
input winc, wclk, wrst_n;
input rinc, rclk, rrst_n;
reg wfull,rempty;
reg [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr, wq1_rptr,rq1_wptr;
reg [ASIZE:0] rbin, wbin;
reg [DSIZE-1:0] mem[0:(1<<asize)-1];
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] rgraynext, rbinnext,wgraynext,wbinnext;
wire rempty_val,wfull_val;
//-----------------双口RAM存储器--------------------
assign rdata=mem[raddr];
always@(posedge wclk)
if (winc && !wfull) mem[waddr] <= wdata;
//-------------同步rptr指针-------------------------
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) {wq2_rptr,wq1_rptr} <= 0;
else {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
//-------------同步wptr指针---------------------------
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) {rq2_wptr,rq1_wptr} <= 0;
else {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
//-------------rempty产生与raddr产生-------------------
always @(posedge rclk or negedge rrst_n) // GRAYSTYLE2 pointer
begin
if (!rrst_n) {rbin, rptr} <= 0;
else {rbin, rptr} <= {rbinnext, rgraynext};
end
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ASIZE-1:0];
assign rbinnext = rbin + (rinc & ~rempty);
assign rgraynext = (rbinnext>>1) ^ rbinnext;
// FIFO empty when the next rptr == synchronized wptr or on reset
assign rempty_val = (rgraynext == rq2_wptr);
always @(posedge rclk or negedge rrst_n)
begin
if (!rrst_n) rempty <= 1'b1;
else rempty <= rempty_val;
end
//---------------wfull产生与waddr产生------------------------------
always @(posedge wclk or negedge wrst_n) // GRAYSTYLE2 pointer
if (!wrst_n) {wbin, wptr} <= 0;
else {wbin, wptr} <= {wbinnext, wgraynext};
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ASIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);
assign wgraynext = (wbinnext>>1) ^ wbinnext;
assign wfull_val = (wgraynext=={~wq2_rptr[ASIZE:ASIZE-1], wq2_rptr[ASIZE-2:0]}); //:ASIZE-1]
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) wfull <= 1'b0;
else wfull <= wfull_val;
endmodule
module fifo_cell (sys_clk, sys_rst_n, read_fifo, write_fifo, fifo_input_data,
next_cell_data, next_cell_full, last_cell_full, cell_data_out, cell_full);
parameter WIDTH =8;
parameter D = 2;
input sys_clk;
input sys_rst_n;
input read_fifo, write_fifo;
input [WIDTH-1:0] fifo_input_data;
input [WIDTH-1:0] next_cell_data;
input next_cell_full, last_cell_full;
output [WIDTH-1:0] cell_data_out;
output cell_full;
reg [WIDTH-1:0] cell_data_reg_array;
reg [WIDTH-1:0] cell_data_ld;
reg cell_data_ld_en;
reg cell_full;
reg cell_full_next;
assign cell_data_out=cell_data_reg_array;
always @(posedge sys_clk or negedge sys_rst_n)
if (!sys_rst_n)
cell_full <= #D 0;
else if (read_fifo || write_fifo)
cell_full <= #D cell_full_next;
always @(write_fifo or read_fifo or next_cell_full or last_cell_full or cell_full)
casex ({read_fifo, write_fifo})
2'b00: cell_full_next = cell_full;
2'b01: cell_full_next = next_cell_full;
2'b10: cell_full_next = last_cell_full;
2'b11: cell_full_next = cell_full;
endcase
always @(posedge sys_clk or negedge sys_rst_n)
if (!sys_rst_n)
cell_data_reg_array [WIDTH-1:0] <= #D 0;
else if (cell_data_ld_en)
cell_data_reg_array [WIDTH-1:0] <= #D cell_data_ld [WIDTH-1:0];
always @(write_fifo or read_fifo or cell_full or last_cell_full)
casex ({write_fifo,read_fifo,cell_full,last_cell_full})
4'bx1_xx: cell_data_ld_en = 1'b1;
4'b10_01: cell_data_ld_en = 1'b1;
default: cell_data_ld_en =1'b0;
endcase
always @(write_fifo or read_fifo or next_cell_full or cell_full or last_cell_full or fifo_input_data or next_cell_data)
casex ({write_fifo, read_fifo, next_cell_full, cell_full, last_cell_full})
5'b10_x01: cell_data_ld[WIDTH-1:0] = fifo_input_data[WIDTH-1:0];
5'b11_01x: cell_data_ld[WIDTH-1:0] = fifo_input_data[WIDTH-1:0];
default: cell_data_ld[WIDTH-1:0] = next_cell_data[WIDTH-1:0];
endcase
endmodule
module fifo_4cell(sys_clk, sys_rst_n, fifo_input_data, write_fifo, fifo_out_data,
read_fifo, full_cell0, full_cell1, full_cell2, full_cell3);
parameter WIDTH = 8;
parameter D = 2;
input sys_clk;
input sys_rst_n;
input [WIDTH-1:0] fifo_input_data;
output [WIDTH-1:0] fifo_out_data;
input read_fifo, write_fifo;
output full_cell0, full_cell1, full_cell2, full_cell3;
wire [WIDTH-1:0] dara_out_cell0, data_out_cell1, data_out_cell2,
data_out_cell3, data_out_cell4;
wire full_cell4;
fifo_cell #(WIDTH,D) cell0
( .sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.fifo_input_data (fifo_input_data[WIDTH-1:0]),
.write_fifo (write_fifo),
.next_cell_data (data_out_cell1[WIDTH-1:0]),
.next_cell_full (full_cell1),
.last_cell_full (1'b1),
.cell_data_out (fifo_out_data [WIDTH-1:0]),
.read_fifo (read_fifo),
.cell_full (full_cell0)
);
fifo_cell #(WIDTH,D) cell1
( .sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.fifo_input_data (fifo_input_data[WIDTH-1:0]),
.write_fifo (write_fifo),
.next_cell_data (data_out_cell2[WIDTH-1:0]),
.next_cell_full (full_cell2),
.last_cell_full (full_cell0),
.cell_data_out (data_out_cell1[WIDTH-1:0]),
.read_fifo (read_fifo),
.cell_full (full_cell1)
);
fifo_cell #(WIDTH,D) cell2
( .sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.fifo_input_data (fifo_input_data[WIDTH-1:0]),
.write_fifo (write_fifo),
.next_cell_data (data_out_cell3[WIDTH-1:0]),
.next_cell_full (full_cell3),
.last_cell_full (full_cell1),
.cell_data_out (data_out_cell2[WIDTH-1:0]),
.read_fifo (read_fifo),
.cell_full (full_cell2)
);
fifo_cell #(WIDTH,D) cell3
( .sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.fifo_input_data (fifo_input_data[WIDTH-1:0]),
.write_fifo (write_fifo),
.next_cell_data (data_out_cell4[WIDTH-1:0]),
.next_cell_full (full_cell4),
.last_cell_full (full_cell2),
.cell_data_out (data_out_cell3[WIDTH-1:0]),
.read_fifo (read_fifo),
.cell_full (full_cell3)
);
assign data_out_cell4[WIDTH-1:0] = {WIDTH{1'B0}};
assign full_cell4 = 1'b0;
endmodule
把第二个程序进行分块处理得到
modulefifo2(rdata,wfull,rempty,wdata,winc,wclk,wrst_n,rinc,rclk,rrst_n);
parameterDSIZE=8;
parameterASIZE=4;
output[DSIZE-1:0]rdata;
outputwfull;
outputrempty;
input[DSIZE-1:0]wdata;
inputwinc,wclk,wrst_n;
inputrinc,rclk,rrst_n;
wire[ASIZE-1:0]wptr,rptr;
wire[ASIZE-1:0]waddr,raddr;
async_cmp#(ASIZE)async_cmp(
.aempty_n(aempty_n),
.afull_n(afull_n),
.wptr(wptr),.rptr(rptr),
.wrst_n(wrst_n));
fifomem2#(DSIZE,ASIZE)fifomem2(
.rdata(rdata),
.wdata(wdata),
.waddr(wptr),
.raddr(rptr),
.wclken(winc),
.wclk(wclk));
rptr_empty2#(ASIZE)rptr_empty2(
.rempty(rempty),
.rptr(rptr),
.aempty_n(aempty_n),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n));
wptr_full2#(ASIZE)wptr_full2(
.wfull(wfull),
.wptr(wptr),
.afull_n(afull_n),
.winc(winc),
.wclk(wclk),
.wrst_n(wrst_n));
Endmodule
modulefifomem2(rdata,wdata,waddr,raddr,wclken,wclk);
parameterDATASIZE=8;//Memorydatawordwidth
parameterADDRSIZE=4;//Numberofmemoryaddressbits
parameterDEPTH=1<<addrsize; depth="2**ADDRSIZE<o:p">
output[DATASIZE-1:0]rdata;
input[DATASIZE-1:0]wdata;
input[ADDRSIZE-1:0]waddr,raddr;
inputwclken,wclk;
`ifdefVENDORRAM
//instantiationofavendor'sdual-port
RAMVENDOR_RAMMEM(.dout(rdata),.din(wdata),.waddr(waddr),.raddr(raddr),.wclken(wclken),.clk(wclk));
`else
reg[DATASIZE-1:0]MEM[0:DEPTH-1];
assignrdata=MEM[raddr];
always@(posedgewclk)
if(wclken)
MEM[waddr]<=wdata;
`endifendmodule
moduleasync_cmp(aempty_n,afull_n,wptr,rptr,wrst_n);
parameterADDRSIZE=4;
parameterN=ADDRSIZE-1;
outputaempty_n,afull_n;
input[N:0]wptr,rptr;
inputwrst_n;
regdirection;
wirehigh=1'b1;
wiredirset_n=~((wptr[N]^rptr[N-1])&~(wptr[N-1]^rptr[N]));
wiredirclr_n=~((~(wptr[N]^rptr[N-1])&(wptr[N-1]^rptr[N]))|~wrst_n);
always@(posedgehighornegedgedirset_nornegedgedirclr_n)
if(!dirclr_n)direction<=1'b0;
elseif(!dirset_n)direction<=1'b1;
elsedirection<=high;
//always@(negedgedirset_nornegedgedirclr_n)
//if(!dirclr_n)direction<=1'b0;
//elsedirection<=1'b1;
assignaempty_n=~((wptr==rptr)&&!direction);
assignafull_n=~((wptr==rptr)&&direction);
endmodule
modulerptr_empty2(rempty,rptr,aempty_n,rinc,rclk,rrst_n);
parameterADDRSIZE=4;
outputrempty;
output[ADDRSIZE-1:0]rptr;
inputaempty_n;
inputrinc,rclk,rrst_n;
reg[ADDRSIZE-1:0]rptr,rbin;
regrempty,rempty2;
wire[ADDRSIZE-1:0]rgnext,rbnext;
//---------------------------------------------------------------//GRAYSTYLE2pointer
//---------------------------------------------------------------always@(posedgerclkornegedgerrst_n)
if(!rrst_n)
begin
rbin<=0;
rptr<=0;
end
else
begin
rbin<=rbnext;
rptr<=rgnext;
end
//---------------------------------------------------------------//incrementthebinarycountifnotempty
//---------------------------------------------------------------assignrbnext=!rempty?rbin+rinc:rbin;
assignrgnext=(rbnext>>1)^rbnext;//binary-to-grayconversion
always@(posedgerclkornegedgeaempty_n)
if(!aempty_n){rempty,rempty2}<=2'b11;
else{rempty,rempty2}<={rempty2,~aempty_n};
endmodule
modulerptr_empty2(rempty,rptr,aempty_n,rinc,rclk,rrst_n);
parameterADDRSIZE=4;
outputrempty;
output[ADDRSIZE-1:0]rptr;
inputaempty_n;
inputrinc,rclk,rrst_n;
reg[ADDRSIZE-1:0]rptr,rbin;
regrempty,rempty2;
wire[ADDRSIZE-1:0]rgnext,rbnext;
//---------------------------------------------------------------//GRAYSTYLE2pointer
//---------------------------------------------------------------always@(posedgerclkornegedgerrst_n)
if(!rrst_n)
begin
rbin<=0;
rptr<=0;
end
else
begin
rbin<=rbnext;
rptr<=rgnext;
end
//---------------------------------------------------------------//incrementthebinarycountifnotempty
//---------------------------------------------------------------assignrbnext=!rempty?rbin+rinc:rbin;
assignrgnext=(rbnext>>1)^rbnext;//binary-to-grayconversion
always@(posedgerclkornegedgeaempty_n)
if(!aempty_n)
{rempty,rempty2}<=2'b11;
else
{rempty,rempty2}<={rempty2,~aempty_n};
endmodule