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