FIFO在FPGA实际开发中使用非常的频繁,各个FPGA厂商也都会有配套的FIFO IP核。FIFO在缓存数据以及做跨时钟域处理起到了非常重要的作用,FIFO先进先出的模式,没有地址信号,这与rom,ram是有区别的,而且FIFO是读出多少就少多少。

  我们使用FIFO正常情况都是用官方IP核或者原语,遇到不懂得地方可以直接查询相关得手册。但是这些官方得IP使用得多,而不去了解FIFO实现得原理,这样会削弱开发能力。很多公司的笔试需要手撕代码,如果不知道原理,那无论IP核用得多好,写不出代码来也无法通过笔试。下面我就贴上之前写好的异步FIFO模块代码,用了挺多次目前还未发现问题。该代码也是参考了网上其他博主的源码,其实只要原理差不多,代码怎样个写法都无所谓,因人而异。异步FIFO其实也可以做为同步FIFO使用,只要读写时钟一致便可作为同步FIFO使用。FIFO如果写满了还往里头写数据就会丢失,空了还往外读就会读出无效数据,这些都是FIFO的特性。自己写FIFO代码还有一个好处便是方便移植,我们使用不同厂商的FPGA,需要移植工程的时候需要重新修改里头的FIFO IP核或原语调用,而且要重新测试,看看是否符合。

  还有一点是非常重要的,因为在实际项目中确实遇到,是个大坑;在调用FIFO IP核或者原语的时候FIFO的复位信号最好写同步,不然就很可能出现亚稳态,导致FIFO整个失效。其实原语里头是有说明的,所以最好留意,我这的代码是异步复位所以无所谓。

下图是xilinx官方文档ug974对异步FIFO原语的复位说明。

  

TableFunction eval 异步 异步fifoverilog代码_Async

  Async_FIFO.v代码:

  这里的DEPTH是FIFO的深度系数,实际要通过计算2**DEPTH的结果才是FIFO深度,DEPTH为4则深度为16。还有就是该FIFO代码块是FWFT模式。代码比较复杂的地方在于格雷码转换那,其他其实就是一个简单的双口ram。

1 //**************************************************************************
  2 // *** file name      : Async_FIFO.v
  3 // *** version        : 1.0
  4 // *** Description    : Async_FIFO 
  5 // *** Blogs          : 
  6 // *** Author         : Galois_V
  7 // *** Date           : 2022.4.14        
  8 // *** Changes        : Initial
  9 //**************************************************************************
 10 `timescale 1ns/1ps
 11 module Async_FIFO
 12 #(
 13     parameter                WIDTH = 8,        //data width
 14     parameter                DEPTH = 3         //data depth,2**DEPTH
 15 )
 16 (
 17     input                          i_rstn,
 18     input                          i_wr_clk,
 19     input                          i_wr_req,
 20     input            [WIDTH-1:0]   i_wr_data,
 21     output    reg                  o_wr_full,
 22     output    reg    [DEPTH-1:0]   o_wr_cnt,
 23     input                          i_rd_clk,
 24     input                          i_rd_req,
 25     output           [WIDTH-1:0]   o_rd_data,
 26     output    reg                  o_rd_empty,
 27     output    reg    [DEPTH-1:0]   o_rd_cnt
 28 );
 29 
 30     wire    [DEPTH-1:0]      w_wr_addr;
 31     wire    [DEPTH-1:0]      w_rd_addr;
 32     wire    [DEPTH:0]        w_wr_binnext;
 33     wire    [DEPTH:0]        w_wr_graynext;
 34     wire    [DEPTH:0]        w_rd_binnext;
 35     wire    [DEPTH:0]        w_rd_graynext;
 36     wire                     w_wr_full;
 37     wire                     w_rd_empty;
 38     reg      [WIDTH-1:0]     map[0:(1<<DEPTH)-1];
 39     reg      [DEPTH:0]       r_wr_bin;
 40     reg      [DEPTH:0]       r_rd_bin;
 41     reg      [DEPTH:0]       r_wr_index;
 42     reg      [DEPTH:0]       r_rd_index;
 43     reg      [DEPTH:0]       r_wr_index1;
 44     reg      [DEPTH:0]       r_rd_index1;
 45     reg      [DEPTH:0]       r_wr_index2;
 46     reg      [DEPTH:0]       r_rd_index2;
 47     reg      [DEPTH:0]       r_addr_diff;
 48 /******************************************************************************\
 49 Dual port ram 
 50 \******************************************************************************/
 51     assign o_rd_data = map[w_rd_addr];
 52     
 53     always@(posedge i_wr_clk)
 54     begin
 55         if(~o_wr_full&i_wr_req)
 56         begin
 57             map[w_wr_addr] <= i_wr_data;
 58         end
 59     end
 60 /******************************************************************************\
 61 Sync index 
 62 \******************************************************************************/    
 63     always@(posedge i_wr_clk or negedge i_rstn)
 64     begin
 65         if(~i_rstn)
 66         begin
 67             r_wr_index1 <= 'd0;
 68             r_wr_index2 <= 'd0;
 69         end
 70         else
 71         begin
 72             r_wr_index1 <= r_rd_index;
 73             r_wr_index2 <= r_wr_index1;
 74         end
 75     end
 76     always@(posedge i_rd_clk or negedge i_rstn)
 77     begin
 78         if(~i_rstn)
 79         begin
 80             r_rd_index1 <= 'd0;
 81             r_rd_index2 <= 'd0;
 82         end
 83         else
 84         begin
 85             r_rd_index1 <= r_wr_index;
 86             r_rd_index2 <= r_rd_index1;
 87         end
 88     end
 89 /******************************************************************************\
 90 Generate full signal
 91 \******************************************************************************/    
 92     assign w_wr_addr     = r_wr_bin[DEPTH-1:0];
 93     assign w_wr_binnext  = r_wr_bin + (~o_wr_full & i_wr_req);
 94     assign w_wr_graynext = w_wr_binnext ^ (w_wr_binnext>>1);
 95     assign w_wr_full     = (w_wr_graynext == {~r_wr_index2[DEPTH:DEPTH-1], r_wr_index2[DEPTH-2:0]}); 
 96     
 97     always@(posedge i_wr_clk or negedge i_rstn)
 98     begin
 99         if(~i_rstn)
100         begin
101             r_wr_bin     <= 'd0;
102             r_wr_index     <= 'd0;
103         end
104         else
105         begin
106             r_wr_bin    <= w_wr_binnext;
107             r_wr_index    <= w_wr_graynext;
108         end
109     end
110     always@(posedge i_wr_clk or negedge i_rstn)
111     begin
112         if(~i_rstn)
113         begin
114             o_wr_full <= 'd0;
115         end
116         else 
117         begin
118             o_wr_full <= w_wr_full;
119         end
120     end
121 /******************************************************************************\
122 Generate  empty signal
123 \******************************************************************************/
124     assign w_rd_addr     = r_rd_bin[DEPTH-1:0];
125     assign w_rd_binnext  = r_rd_bin + (~o_rd_empty & i_rd_req);
126     assign w_rd_graynext = w_rd_binnext ^ (w_rd_binnext>>1);
127     assign w_rd_empty      = (w_rd_graynext == r_rd_index2); 
128     
129     always@(posedge i_rd_clk or negedge i_rstn)
130     begin
131         if(~i_rstn)
132         begin
133             r_rd_bin     <= 'd0;
134             r_rd_index     <= 'd0;
135         end
136         else
137         begin
138             r_rd_bin    <= w_rd_binnext;
139             r_rd_index    <= w_rd_graynext;
140         end
141     end
142     always@(posedge i_rd_clk or negedge i_rstn)
143     begin
144         if(~i_rstn)
145         begin
146             o_rd_empty <= 1'b1;
147         end
148         else 
149         begin
150             o_rd_empty <= w_rd_empty;
151         end
152     end
153 
154 /******************************************************************************\
155 Write and read data cnt
156 \******************************************************************************/
157 
158     always @(*)
159     begin
160         if(~i_rstn)
161         begin
162             r_addr_diff = 'd0;
163         end
164         else if(w_wr_addr > w_rd_addr)
165         begin
166             r_addr_diff = w_wr_addr - w_rd_addr;
167         end
168         else
169         begin
170             r_addr_diff = {1'b0,{(DEPTH - 1){1'b0}}} - w_rd_addr + w_wr_addr;
171         end
172     end
173 
174     always@(posedge i_wr_clk or negedge i_rstn)
175     begin
176         if(~i_rstn)
177         begin
178             o_wr_cnt <= 'd0;
179         end
180         else
181         begin
182             o_wr_cnt <= r_addr_diff[DEPTH-1:0];
183         end
184     end
185     
186     always@(posedge i_rd_clk or negedge i_rstn)
187     begin
188         if(~i_rstn)
189         begin
190             o_rd_cnt <= 'd0;
191         end
192         else
193         begin
194             o_rd_cnt <= r_addr_diff[DEPTH-1:0];
195         end
196     end
197 endmodule

  Async_FIFO_tb.sv

这里仿真文件就直接用Verilog写,因为测试用例比较容易产生,用system Verilog也行,不过需要花些时间。

1 //**************************************************************************
  2 // *** file name     : Async_FIFO_tb.sv
  3 // *** version       : 1.0
  4 // *** Description   : Async_FIFO testbech 
  5 // *** Blogs         : 
  6 // *** Author        : Galois_V
  7 // *** Date          : 2022.4.14        
  8 // *** Changes       : Initial
  9 //**************************************************************************
 10 `timescale 1ns/1ps
 11 module Async_FIFO_tb();
 12 
 13     reg                wclk;
 14     reg                rclk;
 15     reg                rst_n;
 16     
 17     reg    [7:0]       r_wr_data;
 18     reg                r_wr_req;
 19     reg                r_rd_req;
 20     initial
 21     begin
 22         rst_n = 1'b0;
 23         #2000;
 24         rst_n = 1'b1;
 25     end
 26 
 27     initial
 28     begin
 29         wclk = 1'b0;
 30         rclk = 1'b0;
 31     end
 32 
 33     always #5  wclk = ~wclk;
 34     always #10 rclk = ~rclk;
 35     
 36     Async_FIFO
 37     #(
 38         .WIDTH            (8           ),
 39         .DEPTH            (4           )
 40     )u_Async_FIFO
 41     (
 42         .i_rstn           (rst_n       ),
 43         .i_wr_clk         (wclk        ),
 44         .i_wr_req         (r_wr_req    ),
 45         .i_wr_data        (r_wr_data   ),
 46         .o_wr_full        (            ),
 47         .o_wr_cnt         (            ),
 48         .i_rd_clk         (rclk        ),
 49         .i_rd_req         (r_rd_req    ),
 50         .o_rd_data        (            ),
 51         .o_rd_empty       (            ),
 52         .o_rd_cnt         (            )
 53     );
 54     
 55 /******************************************************************************\
 56 
 57 \******************************************************************************/
 58     reg        [10:0]        r_wr_cnt;
 59     always@(posedge wclk or negedge rst_n)
 60     begin
 61         if(~rst_n)
 62         begin
 63             r_wr_cnt <= 'd0;
 64         end
 65         else if(r_wr_cnt == 2000)
 66         begin
 67             r_wr_cnt <= 'd0;
 68         end
 69         else
 70         begin
 71             r_wr_cnt <= r_wr_cnt + 1'b1;
 72         end
 73     end
 74     always@(posedge wclk or negedge rst_n)
 75     begin
 76         if(~rst_n)
 77         begin
 78             r_wr_data <= 'd0;
 79             r_wr_req <= 'd0;
 80         end
 81         else if((r_wr_cnt <= 21 && r_wr_cnt>= 1)||(r_wr_cnt >= 1000 && r_wr_cnt <= 1090))//逻辑或前面的是只写,后面的是同时读写
 82         begin
 83             r_wr_data <= r_wr_cnt[7:0];
 84             r_wr_req <= 1'b1;
 85         end
 86         else
 87         begin
 88             r_wr_data <= 'd0;
 89             r_wr_req <= 'd0;
 90         end
 91     end
 92     
 93     
 94     always@(posedge rclk or negedge rst_n)
 95     begin
 96         if(~rst_n)
 97         begin
 98             r_rd_req <= 'd0;
 99         end
100         else if(r_wr_cnt[9])//写计数超过512的时候开始读
101         begin
102             r_rd_req <= 1'b1;
103         end
104         else
105         begin
106             r_rd_req <= 'd0;
107         end
108     end
109 endmodule

  1.写操作:

先复位,正常情况我们要复位之后十几个时钟之后再往里头读写数据,我这里只是测试用,时序就随便点。可以看到当写操作写满16个数据时,full信号就拉高了。这里可以看到前几个数据写进去的时候FIFO的empty信号还没响应,还为1。这其实不影响,后面读写同时进行的时候会分析。

TableFunction eval 异步 异步fifoverilog代码_sed_02

  2.读操作:

TableFunction eval 异步 异步fifoverilog代码_IP_03

  根据上面的代码,我们可以知道在tb文件计数器累加到512之后使能读信号,由于读写时钟域不一样,所以这里看到的是11'h201=11'd513才开始。读完16个数据之后,这16个数据对应前面写进去的16个数据,而其他多写的数据丢失了。读完16个数据,empty信号拉高,表明当前FIFO已经空了。

  3.同时读写:

TableFunction eval 异步 异步fifoverilog代码_IP_04

  图中可以看到数据计数到11'h3e8==11'd1000的时候写请求拉高,读请求也拉高,因此读写同时进行。看到蓝色箭头处为读写信号过程中满信号的变化,这里写时钟比读时钟快,所以读写请求保持的话,会导致FIFO溢出。