分享:FIFO 同步、异步以及Verilog代码实现
FIFO 很重要,之前参加的各类电子公司的逻辑设计的笔试几乎都会考到。
FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据, 其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO一般用于不同时钟域之间的数据传输,比如FIFO的一端是AD数据采集, 另一端是计算机的PCI总线,假设其AD采集的速率为16位 100K SPS,那么每秒的数据量为100K×16bit=1.6Mbps,而PCI总线的速度为33MHz,总线宽度32bit,其最大传输速率为 1056Mbps,在两个不同的时钟域间就可以采用FIFO来作为数据缓冲。另外对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而 DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
FIFO的分类根均FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。异步FIFO是指读写时钟不一致,读写时钟是互相独立的。
FIFO设计的难点 FIFO设计的难点在于怎样判断FIFO的空/满状态。为了保证数据正确的写入或读出,而不发生益处或读空的状态出现,必须保证FIFO在满的情况下,不 能进行写操作。在空的状态下不能进行读操作。怎样判断FIFO的满/空就成了FIFO设计的核心问题。
.........................................................................................................................................
同步FIFO的Verilog代码 之一
在modlesim中验证过。
module fifo(datain, rd, wr, rst, clk, dataout, full, empty);
input [7:0] datain;
input rd, wr, rst, clk;
output [7:0] dataout;
output full, empty;
wire [7:0] dataout;
reg full_in, empty_in;
reg [7:0] mem [15:0];
reg [3:0] rp, wp;
assign full = full_in;
assign empty = empty_in;
// memory read out
assign dataout = mem[rp];
// memory write in
always@(posedge clk) begin
if(wr && ~full_in) mem[wp]<=datain;
end
// memory write pointer increment
always@(posedge clk or negedge rst) begin
if(!rst) wp<=0;
else begin
if(wr && ~full_in) wp<= wp+1'b1;
end
end
// memory read pointer increment
always@(posedge clk or negedge rst)begin
if(!rst) rp <= 0;
else begin
if(rd && ~empty_in) rp <= rp + 1'b1;
end
end
// Full signal generate
always@(posedge clk or negedge rst) begin
if(!rst) full_in <= 1'b0;
else begin
if( (~rd && wr)&&((wp==rp-1)||(rp==4'h0&&wp==4'hf)))
full_in <= 1'b1;
else if(full_in && rd) full_in <= 1'b0;
end
end
// Empty signal generate
always@(posedge clk or negedge rst) begin
if(!rst) empty_in <= 1'b1;
else begin
if((rd&&~wr)&&(rp==wp-1 || (rp==4'hf&&wp==4'h0)))
empty_in<=1'b1;
else if(empty_in && wr) empty_in<=1'b0;
end
end
endmodule
...........................................................................................................................
同步FIFO的Verilog代码 之二
这一种设计的FIFO,是基于触发器的。宽度,深度的扩展更加方便,结构化跟强。以下代码在modelsim中验证过。
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
..........................................................................................................................
异步FIFO的Verilog代码 之一
这个是基于RAM的异步FIFO代码,个人认为代码结构简单易懂,非常适合于考试中填写。记得10月份参加威盛的笔试的时候,就考过异步FIFO的实现。想当初要是早点复习,可能就可以通过威盛的笔试了。
与之前的用RAM实现的同步FIFO的程序相比,异步更为复杂。增加了读写控制信号的跨时钟域的同步。此外,判空与判满的也稍有不同。
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
..........................................................................................................................
异步FIFO的Verilog代码 之二
与前一段异步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,当存在多个跨时钟域的控制信号时,我们必须特别注意这些信号,保证这些控制信号到达新的时钟域仍然能够保持正确的顺序。
module fifo2 (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;
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
module fifomem2 (rdata, wdata, waddr, raddr, wclken, wclk);
parameter DATASIZE = 8; // Memory data word width
parameter ADDRSIZE = 4; // Number of memory address bits
parameter DEPTH = 1<<ADDRSIZE; // DEPTH = 2**ADDRSIZE
output [DATASIZE-1:0] rdata;
input [DATASIZE-1:0] wdata;
input [ADDRSIZE-1:0] waddr, raddr;
input wclken, wclk;
`ifdef VENDORRAM
// instantiation of a vendor's dual-port RAM
VENDOR_RAM MEM (.dout(rdata), .din(wdata),
.waddr(waddr), .raddr(raddr),
.wclken(wclken), .clk(wclk));
`else
reg [DATASIZE-1:0] MEM [0:DEPTH-1];
assign rdata = MEM[raddr];
always @(posedge wclk)
if (wclken) MEM[waddr] <= wdata;
`endif
endmodule
module async_cmp (aempty_n, afull_n, wptr, rptr, wrst_n);
parameter ADDRSIZE = 4;
parameter N = ADDRSIZE-1;
output aempty_n, afull_n;
input [N:0] wptr, rptr;
input wrst_n;
reg direction;
wire high = 1'b1;
wire dirset_n = ~( (wptr[N]^rptr[N-1]) & ~(wptr[N-1]^rptr[N]));
wire dirclr_n = ~((~(wptr[N]^rptr[N-1]) & (wptr[N-1]^rptr[N])) |
~wrst_n);
always @(posedge high or negedge dirset_n or negedge dirclr_n)
if (!dirclr_n) direction <= 1'b0;
else if (!dirset_n) direction <= 1'b1;
else direction <= high;
//always @(negedge dirset_n or negedge dirclr_n)
//if (!dirclr_n) direction <= 1'b0;
//else direction <= 1'b1;
assign aempty_n = ~((wptr == rptr) && !direction);
assign afull_n = ~((wptr == rptr) && direction);
endmodule
module rptr_empty2 (rempty, rptr, aempty_n, rinc, rclk, rrst_n);
parameter ADDRSIZE = 4;
output rempty;
output [ADDRSIZE-1:0] rptr;
input aempty_n;
input rinc, rclk, rrst_n;
reg [ADDRSIZE-1:0] rptr, rbin;
reg rempty, rempty2;
wire [ADDRSIZE-1:0] rgnext, rbnext;
//---------------------------------------------------------------
// GRAYSTYLE2 pointer
//---------------------------------------------------------------
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) begin
rbin <= 0;
rptr <= 0;
end
else begin
rbin <= rbnext;
rptr <= rgnext;
end
//---------------------------------------------------------------
// increment the binary count if not empty
//---------------------------------------------------------------
assign rbnext = !rempty ? rbin + rinc : rbin;
assign rgnext = (rbnext>>1) ^ rbnext; // binary-to-gray conversion
always @(posedge rclk or negedge aempty_n)
if (!aempty_n) {rempty,rempty2} <= 2'b11;
else {rempty,rempty2} <= {rempty2,~aempty_n};
endmodule
module wptr_full2 (wfull, wptr, afull_n, winc, wclk, wrst_n);
parameter ADDRSIZE = 4;
output wfull;
output [ADDRSIZE-1:0] wptr;
input afull_n;
input winc, wclk, wrst_n;
reg [ADDRSIZE-1:0] wptr, wbin;
reg wfull, wfull2;
wire [ADDRSIZE-1:0] wgnext, wbnext;
//---------------------------------------------------------------
// GRAYSTYLE2 pointer
//---------------------------------------------------------------
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) begin
wbin <= 0;
wptr <= 0;
end
else begin
wbin <= wbnext;
wptr <= wgnext;
end
//---------------------------------------------------------------
// increment the binary count if not full
//---------------------------------------------------------------
assign wbnext = !wfull ? wbin + winc : wbin;
assign wgnext = (wbnext>>1) ^ wbnext; // binary-to-gray conversion
always @(posedge wclk or negedge wrst_n or negedge afull_n)
if (!wrst_n ) {wfull,wfull2} <= 2'b00;
else if (!afull_n) {wfull,wfull2} <= 2'b11;
else {wfull,wfull2} <= {wfull2,~afull_n};
endmodule
.............................................................