从底层结构开始学习FPGA----FIFO IP的定制与测试

本文转载自:孤独的单刀的CSDN博客

注:本文由作者授权转发,如需转载请联系作者本人

在定制一个FIFO IP核之前,强烈建议您先阅读:从底层结构开始学习FPGA----FIFO IP核及其关键参数介绍

在这篇文章中,已经对FIFO IP核的各个关键因素做了详细的讲解。

1、FIFO IP的定制
在新建一个工程后, 点击IP Catalog
点击后会出现 IP Catalog 页面,在 IP 核的搜索框中搜索 fifo
根据筛选,双击选择fifo核“FIFO Generator”

①、第一页

②、第二页

③、第三页

④、第四页

⑤、第五页

2、FIFO IP的例化与测试
2.1、例化一个FIFO IP核
按上述步骤生成IP后,复制 IP核自带的例化模板。


2.2、RTL
我们编写一个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

2.3、仿真测试与测试结果
由于测试逻辑都在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)整体

(2)PLL从不稳定到稳定

(3)FIFO复位完成后需要一定的时间才可以操作

(4)写FIFO操作

(5)读FIFO操作

写入数据0-14共15个数据,读出数据也是0-14共15个数据。写、读一致,功能验证无误。

2.4、下板实测
把代码下载到开发板,用ILA观察一下,观察结果和仿真结果是一致的,不赘述,只放几张图:

(1)PLL稳定及FIFO复位

(2)写FIFO操作,写入数据0-14

(3)读FIFO操作,读取数据0-14

3、总结与参考

  • FIFO的使用要注意实际深度
  • FIFO使用需要先复位,复位时间要足够长,且复位后需要一定的时间才能使用FIFO
  • 同步FIFO的使用会比较简单,因为其没有异步FIFO所需要的同步时钟域导致的延迟问题
  • 不同资源实现的FIFO在各个功能特性上会有细微的区别,使用之前一定要认真测试,总结时序
  • 异步FIFO的计数器不是精确的,只能作为大致的参考,以实现例如半空半满、1/4空满等判断
  • 可编程空满的实现是依赖计数器的,其值同样不够精确
  • 参考资料1:pg057-fifo-generator

    最新文章

    最新文章