> 文档中心 > 【Verilog】同步FIFO原理及verilog实现(参数化)

【Verilog】同步FIFO原理及verilog实现(参数化)

        旨在学习理解,项目中还是用成熟IP靠谱~


目录

一、FIFO原理

二、同步FIFO设计

2.1 位宽和深度

2.2 空、满标志

2.3 FIFO计数

2.4 ram模型

2.5 读/写操作

三、​​​​​​​verilog代码

四、仿真验证

后记


一、FIFO原理

FIFO( First Input First Output)是指先进先出。模型如下:

 

        FIFO存储器是系统的缓冲环节,如果没有FIFO存储器,整个系统就不可能正常工作,它主要有几方面的功能:

        1)对连续的数据流进行缓存,防止在进机和存储操作时丢失数据;

        2)数据集中起来进行进机和存储,可避免频繁的总线操作,减轻CPU的负担;

        3)允许系统进行DMA操作,提高数据的传输速度。这是至关重要的一点,如果不采用DMA操作,数据传输将达不到传输要求,而且大大增加CPU的负担,无法同时完成数据的存储工作。

        根据FIFO工作的时钟域分为同步/异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟在时钟沿来临时同时发生读写。异步FIFO读写时钟不一致,读写相互独立。所以存在跨时钟域的处理。

        本文先考虑同步FIFO设计

        为了定位读取和写入的位置需要进行读写指针的设置。

        读指针:总是指向下一个将要读取的单元,复位时指向第一个单元(编号为0)。

        写指针:总是指向当前要被读出的数据,复位时指向第一个单元(编号为0)。

        而FIFO设计中最重要的则是空(Empty)、满(Full)信号的判断。

        当第一次读写指针相等时,表明FIFO为空,这种情况发生在复位操作时或者当读指针读出FIFO中最后一个字 后,追赶上写指针时,此时读空信号有效。

        当读写指针再次相等时,表明FIFO为满,这种情况发生在,当写指针转了一圈折回来(wrapped around)又追上了读指针。

        两种方式都是以读写指针相等作为判断标志,所以我们需要寻找其它的方法进行判断。

        在同步FIFO中,我们可以很容易的使用计数来判断FIFO中还剩下多少可读的数据,从而可以判断空、满。

        下面我们开始按步骤进行同步FIFO设计。

二、同步FIFO设计

2.1 位宽和深度

        位宽 WIDTH:表示FIFO里面每个数据的大小

        深度 DEPTH:表示FIFO能存多少个数据

        本文全部采用参数化设计,另外将空、满也设计为参数化:

parameter WIDTH = 16;parameter DEPTH = 1024;parameter PROG_EMPTY = 100;parameter PROG_FULL = 800;

2.2空、满标志

  • 空信号empty

        下一次读地址 = 写地址,且只执行读操作;

  • 满信号full

        下一次写地址 = 读地址,且只执行写操作;

2.3FIFO计数

        fifo_cnt :  只写不读,fifo_cnt ++; 只读不写,fifo_cnt --;其余情况保持不变。

2.4ram模型

        verilog 2001标准支持多维数组,所有直接使用二维数组来定义memory数据类型:

reg [WIDTH-1 : 0]  ram  [DEPTH-1 : 0];

2.5读/写操作

  1. 读操作 : 读使能 && 非空,才能成功完成一次读操作:读地址+1,且从ram 中读出数据,给到输出。
  2. 写操作 : 写使能 && 非满,才能成功完成一次写操作:写地址+1,且将输入数据写入ram。

​​​​​​​​​​​​​​三、​​​​​​​verilog代码

        

module syn_fifo#(parameter WIDTH=16,parameter DEPTH=1024,parameter ADDR_WIDTH=clogb2(DEPTH),parameter PROG_EMPTY=100,parameter PROG_FULL=800)(input sys_clk,input sys_rst,input[WIDTH-1:0]din,input wr_en,input rd_en,output reg[WIDTH-1:0]dout,output reg full,output reg empty,output reg prog_full,output reg prog_empty,output reg[ADDR_WIDTH-1:0]fifo_cnt);//==========================================================\//========== Define Parameter and Internal signals =========//==========================================================/reg[WIDTH-1:0]ram[DEPTH-1:0];reg[ADDR_WIDTH-1:0]wr_addr;reg[ADDR_WIDTH-1:0]rd_addr;//==========================================================\//===================== Main  Code =========================//==========================================================/function integer clogb2;input[31:0]value;beginvalue=value-1;for(clogb2=0;value>0;clogb2=clogb2+1)value=value>>1;endendfunction//readalways@(posedge sys_clk or posedge sys_rst)begin if(sys_rst)rd_addr<={ADDR_WIDTH{1'b0}};else if(rd_en && !empty)beginrd_addr<=rd_addr+1'd1;dout<=ram[rd_addr];endelse beginrd_addr<=rd_addr; dout<=dout;endend//writealways@(posedge sys_clk or posedge sys_rst)begin if(sys_rst)wr_addr<={ADDR_WIDTH{1'b0}};else if(wr_en && !full) beginwr_addr<=wr_addr+1'd1;ram[wr_addr]<=din;endelsewr_addr<=wr_addr;end//fifo_cntalways@(posedge sys_clk or posedge sys_rst)begin if(sys_rst)fifo_cnt<={ADDR_WIDTH{1'b0}};else if(wr_en && !full && !rd_en)fifo_cnt<=fifo_cnt + 1'd1;else if(rd_en && !empty && !wr_en)fifo_cnt<=fifo_cnt - 1'd1;else fifo_cnt<=fifo_cnt;end//empty always@(posedge sys_clk or posedge sys_rst)beginif(sys_rst)empty<=1'b1;//reset:1elseempty<=(!wr_en && (fifo_cnt[ADDR_WIDTH-1:1] == 'b0))&&((fifo_cnt[0] == 1'b0) || rd_en);end//fullalways@(posedge sys_clk or posedge sys_rst)beginif(sys_rst)full<=1'b1;//reset:1elsefull<=(!rd_en&&(fifo_cnt[ADDR_WIDTH-1:1]=={(ADDR_WIDTH-1){1'b1}})) && ((fifo_cnt[0] == 1'b1) || wr_en);end//prog_fullalways@(posedge sys_clk or posedge sys_rst)beginif(sys_rst)prog_full PROG_FULL)prog_full<=1'b1;elseprog_full<=1'b0;end//prog_emptyalways@(posedge sys_clk or posedge sys_rst)begin if(sys_rst)prog_empty<=1'b1;//reset:1else if(fifo_cnt < PROG_EMPTY)prog_empty<=1'b1;elseprog_empty<=1'b0;endendmodule

四、仿真验证

        对上面设计的FIFO进行写入/读出操作。

        为了验证程序健壮性,测试会进行写溢出和读空。

test  bench

`timescale 1ns/1nsmodule tb;parameter WIDTH=16;parameter DEPTH=512    ;parameter ADDR_WIDTH=9;parameter PROG_EMPTY=100     ;parameter PROG_FULL=400     ;reg sys_clk;reg sys_rst;reg  [WIDTH-1:0]din    ;reg wr_en  ;reg rd_en  ;reg [11:0]cnt;wire [WIDTH-1:0]dout;wire full ;wire empty;wire prog_full   ;wire prog_empty  ;wire [ADDR_WIDTH-1:0]fifo_cnt   ;initialbeginsys_clk=0;sys_rst=1;#100sys_rst=0;endalways #5sys_clk=~sys_clk;always @ (posedge sys_clk or posedge sys_rst)beginif(sys_rst)cnt<='b0;elsecnt<=cnt + 1'd1;endalways @ (posedge sys_clk or posedge sys_rst)beginif(sys_rst)beginwr_en<=1'b0;rd_en<=1'b0;din= 'd10 && cnt <= DEPTH + 'd40)beginwr_en<=1'b1;rd_en<=1'b0;din= 'd100 + DEPTH) && (cnt <= 2*DEPTH + 'd140))beginwr_en<=1'b0;rd_en<=1'b1;din<='b0;endelse beginwr_en<=1'b0;rd_en<=1'b0;din<='b0;endendsyn_fifo#(.WIDTH(WIDTH),.DEPTH(DEPTH),.ADDR_WIDTH(ADDR_WIDTH),.PROG_EMPTY(PROG_EMPTY),.PROG_FULL(PROG_FULL))u_syn_fifo(.sys_clk(sys_clk),.sys_rst (sys_rst     ),.din     (din  ),.wr_en   (wr_en),.rd_en   (rd_en),.dout    (dout ),.full    (full ),.empty   (empty),.prog_full      (prog_full   ),.prog_empty     (prog_empty  ),.fifo_cnt(fifo_cnt    ));endmodule

仿真结果:

写入:

 写满:

 读出:

 读空:

验证成功!

后记

问:

  1. 如何实现FWFT模式? 即读数据时不寄存输出,读使能有效的同一时钟周期,FIFO数据即出现在输出数据总线上。
  2. 异步FIFO如何实现?

咱们下期见。

书本网