Verilog语言编写异步FIFO
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原语的复位说明。
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 : https://www.cnblogs.com/WenGalois123/ 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 : https://www.cnblogs.com/WenGalois123/ 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。这其实不影响,后面读写同时进行的时候会分析。
2.读操作:
根据上面的代码,我们可以知道在tb文件计数器累加到512之后使能读信号,由于读写时钟域不一样,所以这里看到的是11'h201=11'd513才开始。读完16个数据之后,这16个数据对应前面写进去的16个数据,而其他多写的数据丢失了。读完16个数据,empty信号拉高,表明当前FIFO已经空了。
3.同时读写:
图中可以看到数据计数到11'h3e8==11'd1000的时候写请求拉高,读请求也拉高,因此读写同时进行。看到蓝色箭头处为读写信号过程中满信号的变化,这里写时钟比读时钟快,所以读写请求保持的话,会导致FIFO溢出。