本文转载自:<span id="profileBt"><a href="https://blog.csdn.net/wuzhikaidetb/article/details/125633983?spm=1001.2…;孤独的单刀的CSDN博客</a></span>
<font color="#FF8000">注:本文由作者授权转发,如需转载请联系作者本人</font>
在定制一个FIFO IP核之前,强烈建议您先阅读:<a href="http://xilinx.eetrend.com/blog/2022/100562136.html">从底层结构开始学习FPGA----FI… IP核及其关键参数介绍 </a>
在这篇文章中,已经对FIFO IP核的各个关键因素做了详细的讲解。
<strong>1、FIFO IP的定制</strong>
在新建一个工程后, 点击IP Catalog
点击后会出现 IP Catalog 页面,在 IP 核的搜索框中搜索 fifo
根据筛选,双击选择fifo核“FIFO Generator”
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
①、第一页
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
②、第二页
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
③、第三页
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
④、第四页
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
⑤、第五页
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
<strong>2、FIFO IP的例化与测试</strong>
<strong>2.1、例化一个FIFO IP核</strong>
按上述步骤生成IP后,复制 IP核自带的例化模板。
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
<strong>2.2、RTL</strong>
我们编写一个RTL代码来验证一下这个FIFO IP,并学习一下它的时序逻辑。
由于是异步FIFO,所以还例化了一个PLL来生成写时钟50M,读时钟75M。RTL代码使用了一个简单的状态机,以实现如下逻辑:
1. 等待PLL稳定
2. PLL稳定后对FIFO进行复位,时长10个周期以上
3. FIFO复位完成后,写入数据直到写满,写入数据分别为0,1,2···14,共15个数据
4. 写完数据后,从FIFO中读取数据,观察读取数据是否与写入数据一致
module fifot_test(
(* MARK_DEBUG="true" *) input sys_clk,
(* MARK_DEBUG="true" *) input sys_rst_n
);
(* MARK_DEBUG="true" *)reg fifo_rst ; //自己生成一个FIFO的复位信号
(* MARK_DEBUG="true" *)reg [3:0] din ;
(* MARK_DEBUG="true" *)reg [3:0] rst_cnt ; //复位计数器
(* MARK_DEBUG="true" *)reg wr_en ;
(* MARK_DEBUG="true" *)reg rd_en ;
(* MARK_DEBUG="true" *)reg [2:0] state ;
reg [2:0] state_rd1 ;
reg [2:0] state_rd2 ;
(* MARK_DEBUG="true" *)wire locked ;
(* MARK_DEBUG="true" *)wire rst_n ;
(* MARK_DEBUG="true" *)wire rd_clk ;
(* MARK_DEBUG="true" *)wire wr_clk ;
(* MARK_DEBUG="true" *)wire [3 : 0] dout ;
(* MARK_DEBUG="true" *)wire full ;
(* MARK_DEBUG="true" *)wire almost_full ;
(* MARK_DEBUG="true" *)wire wr_ack ;
(* MARK_DEBUG="true" *)wire overflow ;
(* MARK_DEBUG="true" *)wire empty ;
(* MARK_DEBUG="true" *)wire almost_empty ;
(* MARK_DEBUG="true" *)wire valid ;
(* MARK_DEBUG="true" *)wire underflow ;
(* MARK_DEBUG="true" *)wire [3 : 0] rd_data_count ;
(* MARK_DEBUG="true" *)wire [3 : 0] wr_data_count ;
(* MARK_DEBUG="true" *)wire prog_full ;
(* MARK_DEBUG="true" *)wire prog_empty ;
(* MARK_DEBUG="true" *)wire wr_rst_busy ;
(* MARK_DEBUG="true" *)wire rd_rst_busy ;
assign rst_n = sys_rst_n && locked; //在locked拉高之前一直复位
//PLL输出波形后,开始计数直到1111
always @(posedge sys_clk or negedge rst_n)begin
if(~rst_n)
rst_cnt <= 1'b0;
else if(&rst_cnt) //rst_cnt == 1111
rst_cnt <= rst_cnt;
else
rst_cnt <= rst_cnt + 1;
end
always @(posedge sys_clk or negedge rst_n)begin
if(~rst_n)
fifo_rst <= 1'b1;
else if(&rst_cnt)
fifo_rst <= 1'b0;
else
fifo_rst <= 1'b1;
end
always @(posedge sys_clk or negedge rst_n)begin
if(~rst_n)
state <= 3'd0;
else begin
case(state)
3'd0:begin
if(~fifo_rst)
state <= 3'd1;
else
state <= state;
end
3'd1:begin
if(~wr_rst_busy && ~full)
state <= 3'd2;
else
state <= state;
end
3'd2:begin
if(almost_full)
state <= 3'd3;
else
state <= state;
end
3'd3:begin
if(~rd_rst_busy && ~empty)
state <= 3'd4;
else
state <= state;
end
3'd4:begin
if(almost_empty)
state <= 3'd5;
else
state <= state;
end
3'd5:begin
state <= state;
end
default:state <= 3'd0;
endcase
end
end
//写使能
always @(posedge wr_clk or negedge rst_n)begin
if(~rst_n)
wr_en <= 1'b0;
else if(state == 3'd2)
wr_en <= 1'b1;
else
wr_en <= 1'b0;
end
//写数据
always @(posedge wr_clk or negedge rst_n)begin
if(~rst_n)
din <= 4'd0;
else if(wr_en)
din <= din + 1;
end
//把状态state同步到读时钟域
always @(posedge rd_clk or negedge rst_n)begin
if(~rst_n)begin
state_rd1 <= 3'd0;
state_rd2 <= 3'd0;
end
else begin
state_rd1 <= state;
state_rd2 <= state_rd1;
end
end
//读使能
always @(posedge rd_clk or negedge rst_n)begin
if(~rst_n)
rd_en <= 1'b0;
else if(state_rd2 == 3'd4)
rd_en <= 1'b1;
else
rd_en <= 1'b0;
end
clk_wiz_0 clk_wiz_0_inst
(
.clk_in1 (sys_clk ),
.clk_out1 (wr_clk ), //50M
.clk_out2 (rd_clk ), //75M
.resetn (sys_rst_n ),
.locked (locked )
);
//例化FIFO
fifo_w4_d16 fifo_w4_d16_inst(
.rst (fifo_rst),
.wr_clk (wr_clk),
.din (din),
.wr_en (wr_en),
.wr_rst_busy (wr_rst_busy),
.wr_data_count (wr_data_count),
.prog_full (prog_full),
.full (full),
.almost_full (almost_full),
.wr_ack (wr_ack),
.overflow (overflow),
.rd_clk (rd_clk),
.rd_en (rd_en),
.empty (empty),
.almost_empty (almost_empty),
.valid (valid),
.rd_data_count (rd_data_count),
.dout (dout),
.underflow (underflow),
.prog_empty (prog_empty),
.rd_rst_busy (rd_rst_busy)
);
endmodule
<strong>2.3、仿真测试与测试结果 </strong>
由于测试逻辑都在RTL中实现了,所以testbench就比较简单了,只需要例化被测模块和实现时钟和复位即可。
`timescale 1 ns / 1 ns
module tb_fifo_test();
reg sys_clk;
reg sys_rst_n;
initial begin
sys_clk = 0;
sys_rst_n = 0;
#60
sys_rst_n = 1;
#6000
$finish; //停止仿真
end
always #10 sys_clk = ~ sys_clk;
fifot_test fifot_test_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n )
);
endmodule
使用vivado自带的仿真工具simulator运行仿真,仿真结果如下:
(1)整体
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
(2)PLL从不稳定到稳定
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
(3)FIFO复位完成后需要一定的时间才可以操作
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
(4)写FIFO操作
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
(5)读FIFO操作
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
写入数据0-14共15个数据,读出数据也是0-14共15个数据。写、读一致,功能验证无误。
<strong>2.4、下板实测</strong>
把代码下载到开发板,用ILA观察一下,观察结果和仿真结果是一致的,不赘述,只放几张图:
(1)PLL稳定及FIFO复位
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
(2)写FIFO操作,写入数据0-14
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
(3)读FIFO操作,读取数据0-14
<center><img src="http://xilinx.eetrend.com/files/2022-07/%E5%8D%9A%E5%AE%A2/100562199-26…; alt=""></center>
<strong>3、总结与参考</strong>
<li>FIFO的使用要注意实际深度</li>
<li>FIFO使用需要先复位,复位时间要足够长,且复位后需要一定的时间才能使用FIFO</li>
<li>同步FIFO的使用会比较简单,因为其没有异步FIFO所需要的同步时钟域导致的延迟问题</li>
<li>不同资源实现的FIFO在各个功能特性上会有细微的区别,使用之前一定要认真测试,总结时序</li>
<li>异步FIFO的计数器不是精确的,只能作为大致的参考,以实现例如半空半满、1/4空满等判断</li>
<li>可编程空满的实现是依赖计数器的,其值同样不够精确</li>
<strong>参考资料1:<a href="https://docs.xilinx.com/v/u/en-US/pg057-fifo-generator" title="pg057-fifo-generator">pg057-fifo-generator</a></strong>