FIFO是一种先入先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常方便,但缺点是只能顺序的写入数据、读出数据,其内部地址是由内部读写指针自动加1完成,不能像普通存储器那样由地址线读取或者写入某个地址。
FIFO一般用于不同时钟域之间的数据传输,比如FIFO的一端是AD数据采集,另一端是计算机的PCI总线,假设AD采集的速率是16位100K sps,那么每秒的数据量为100K x 16bit=1.6Mbps,而PCI总线的速度为33MHZ,总线宽度是32bit,其最大传输速率为1056Mbps,在两个不同的时钟域间采用FIFO作为数据缓冲。另外对于不同宽度的数据接口也可以用FIFO,例如单片机的8位数据输出。
FIFO的分类根据FIFO工作的时钟域,分为同步FIFO和异步FIFO。同步FIFO是指读写时钟为同一时钟。在时钟沿来临时同时发生读写操作。异步FIFO是指读写时钟不一致,读写时钟相互独立。
FIFO设计的难点在于怎么判断FIFO的空满状态。为了保证数据正确的写入和读出,而不发生溢出或读空的状态出现,必须保证FIFO在满的情况下,不能进行写操作。在空的状态下不能进行读操作。
同步FIFO的实现代码:
1 module fifo( //信号定义---读、写、data_in,data_out,clk,复位,空,满 2 input [7:0] datain, 3 input rd,//读 4 input wr,//写 5 input rst, 6 input clk, 7 output [7:0] dataout, 8 output full, 9 output empty, 10 ); 11 12 wire [7:0] dataout;//输出信号定义为wire类型 ,输入默认为wire 13 reg full_in,empty_in; (空满寄存器) 14 reg [7:0] mem [15:0];//RAM的深度和宽度 15 16 reg [3:0] rp,wp; //读写指针的寄存 17 18 //空满输出 19 assign full = full_in; 20 assign empty = empty_in; 21 22 //read always@(posedge clk) begin if(rd && ~empty_in) dataout <= men[rp]; end
23 //assign dataout = mem[rp]; 24 25 //write 26 always@(posedge clk) begin 27 if(wr && ~full_in) mem[wp] <=datain; 28 end 29 30 // 读指针读完自增 31 always@(posedge clk or negedge rst) begin 32 if(!rst) rp<=0; 33 else begin 34 if(rd && ~empty_in) rp<=rp+1'b1; 35 end 36 //写指针自增 37 always@(posedge clk or negedge rst) begin 38 if (!rst) wp<=0; 39 else begin 40 if(wd && ~full_in) wp<=wp+1'b1; 41 end 42 43 //满状态生成 44 always@(posedge clk or negedge rst) begin 45 if(~rst) full_in<=0; //复位 46 else begin 47 if((~rd && wr)&& ((wp=rp-1)||(rp==4'0&&wp==4'hf))) //读指针在写指针之后,且下一个来临的是一个写信号,则写满 48 full_in<=1'b1; 49 else if (full_in && rd) full_in <=1'b0;//当满状态时来了读信号,则满状态拉低。 50 end 51 //空状态生成 52 always@(posedge clk or negedge rst)begin 53 if(~rst) empty_in<=1'b0; 54 else begin 55 if((rd && ~wr)&&(rp = wr -1 || (rp==4'hf&&wp==4'h0))) 56 empty_in<=0; 57 else if(empty_in && wr ) empty_in<=1'b0; 58 end 59 end 60 endmodule
异步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读写指针需要在数学上的操作和比较才能产生准确的空满标志位,但由于读写指针属于不同的时钟域及读写指针以及读写时钟相位不定的原因,同一模块采集另一时钟域的指针时,此指针有可能正处在跳变过程中,那么采集到的值很可能是不期望的值。当然,不期望的错误结果也会随着产生。
格雷码计数器
表现形式:最大的特点是在递增和递减过程中,每次只变化以为,这是它最大的优点。同时它也有自己的局限性,那就是循环计数深度必须是2的n次幂,否则就失去了每次只变化一位的特性。
二进制到格雷码的转换: gray = (b>>1)^b
实现