本文转载自:孤独的单刀的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、总结与参考
参考资料1:pg057-fifo-generator