摘要:FIFO是FPGA应用当中非常重要的模块,广泛应用于数据的缓存,跨时钟域数据处理。学好FIFO是FPGA的关键,灵活运用好FIFO是一个FPGA工程师必备的技能。
赛灵思提供的FIFO IP核进行读写测试。
一、实验原理
FIFO:First in,First out代表先进的数据先出,后进的数据后出。
需要在Xilinx提供的FIFO的IP核实例化一个FIFO,根据读写时序写入和读取FIFO中存储的数据。
结构
FIFO分为读和写两部分,另外就算是状态信号,空和满信号,同时还有数据的数量状态信号,没有地址线,不能随机地址读取数据。
初始状态下,读写地址都为0,向FIFO中写入一个数据后,写地址加1,从FIFO中读出一个数据后,读地址加1.此时FIFO的状态为空,因为写了一个数据,又读了一个数据。
根据读写时钟,可以分为同步FIFO(读写时钟相同)和异步FIFO(读写时钟不同)。
读时钟75Mhz写时钟100Mhz。采用Vivado集成的逻辑分析仪ila,从中观察FIFO读写时序和从FIFO中读取数据。
二、创建fifo_test工程,然后添加FIFO IP核。
在弹出的窗口中设置Component Name为fifo_ip,设置FIFO Implementation为Independent Clocks Block RAM
切换到Native Ports栏目下,选择数据位宽16;FIFO深512,Read Mode有两种,一个Standard FIFO,也就是平时常见的FIFO,数据滞后读信号一个周期,还有一种方式是First Word Fall Through,数据预取模式,简称FWFT模式。也就是FIFO会预先取出一个数据,当读信号有效时,相应的数据也有效。
在Data Counts栏下,使能Write Data Count和Read Data Count,这样就可以通过两个值来看FIFO内部的数据多少。
点击OK,Generate生成FIFO IP核。
FIFO的端口定义与时序
FIFO的数据写入和读出都是按时钟的上升沿操作的,当wr_en信号为高时写入FIFO数据,当almost_full信号有效时,表示FIFO只能再写入一个数据,一旦写入一个数据,full信号就会拉高,如果在full的情况下wr_en任然有效,也就是继续向FIFO写数据,则FIFO的overflow就会有效,表示溢出。
当rd_en信号为高时读FIFO,数据在下一个周期有效,valid为数据有效信号,almost_empty表示还有一个数据读,当再读一个数据,empty信号有效,如果继续读,则underflow有效,表示下溢,此时读出的数据无效。
FWFT FIFO读时序
而从FWFT模式读数据时序图可以看出,rd_en信号有效时,有效数据D0已经在数据线上准备好有效了,不会再延后一个周期。这就是标准FIFO的不同之处。
三、FIFO的测试代码
按照异步FIFO进行设计,用PLL产生两路时钟,分别100M hz和75Mhz,用于写时钟和读时钟,也就是写时钟频率高于读时钟频率。
fifo_test.v
`timescale 1ns / 1ps
//
module fifo_test
(
input clk, //50MHz时钟
input rst_n //复位信号,低电平有效
);
reg [15:0] w_data ; //FIFO写数据
wire wr_en ; //FIFO写使能
wire rd_en ; //FIFO读使能
wire [15:0] r_data ; //FIFO读数据
wire full ; //FIFO满信号
wire empty ; //FIFO空信号
wire [8:0] rd_data_count ; //可读数据数量
wire [8:0] wr_data_count ; //已写入数据数量
wire clk_100M ; //PLL产生100MHz时钟
wire clk_75M ; //PLL产生100MHz时钟
wire locked ; //PLL lock信号,可作为系统复位信号,高电平表示lock住
wire fifo_rst_n ; //fifo复位信号, 低电平有效
wire wr_clk ; //写FIFO时钟
wire rd_clk ; //读FIFO时钟
reg [7:0] wcnt ; //写FIFO复位后等待计数器
reg [7:0] rcnt ; //读FIFO复位后等待计数器
//例化PLL,产生100MHz和75MHz时钟
clk_wiz_0 fifo_pll
(
// Clock out ports
.clk_out1(clk_100M), // output clk_out1
.clk_out2(clk_75M), // output clk_out2
// Status and control signals
.reset(~rst_n), // input reset
.locked(locked), // output locked
// Clock in ports
.clk_in1(clk) // input clk_in1
);
assign fifo_rst_n = locked ; //将PLL的LOCK信号赋值给fifo的复位信号
assign wr_clk = clk_100M ; //将100MHz时钟赋值给写时钟
assign rd_clk = clk_75M ; //将75MHz时钟赋值给读时钟
/* 写FIFO状态机 */
localparam W_IDLE = 1 ;
localparam W_FIFO = 2 ;
reg[2:0] write_state;
reg[2:0] next_write_state;
always@(posedge wr_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
write_state <= W_IDLE;
else
write_state <= next_write_state;
end
always@(*)
begin
case(write_state)
W_IDLE:
begin
if(wcnt == 8'd79) //复位后等待一定时间,safety circuit模式下的最慢时钟60个周期
next_write_state <= W_FIFO;
else
next_write_state <= W_IDLE;
end
W_FIFO:
next_write_state <= W_FIFO; //一直在写FIFO状态
default:
next_write_state <= W_IDLE;
endcase
end
//在IDLE状态下,也就是复位之后,计数器计数
always@(posedge wr_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
wcnt <= 8'd0;
else if (write_state == W_IDLE)
wcnt <= wcnt + 1'b1 ;
else
wcnt <= 8'd0;
end
//在写FIFO状态下,如果不满就向FIFO中写数据
assign wr_en = (write_state == W_FIFO) ? ~full : 1'b0;
//在写使能有效情况下,写数据值加1
always@(posedge wr_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
w_data <= 16'd1;
else if (wr_en)
w_data <= w_data + 1'b1;
end
/* 读FIFO状态机 */
localparam R_IDLE = 1 ;
localparam R_FIFO = 2 ;
reg[2:0] read_state;
reg[2:0] next_read_state;
///产生FIFO读的数据
always@(posedge rd_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
read_state <= R_IDLE;
else
read_state <= next_read_state;
end
always@(*)
begin
case(read_state)
R_IDLE:
begin
if (rcnt == 8'd59) //复位后等待一定时间,safety circuit模式下的最慢时钟60个周期
next_read_state <= R_FIFO;
else
next_read_state <= R_IDLE;
end
R_FIFO:
next_read_state <= R_FIFO ; //一直在读FIFO状态
default:
next_read_state <= R_IDLE;
endcase
end
//在IDLE状态下,也就是复位之后,计数器计数
always@(posedge rd_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
rcnt <= 8'd0;
else if (write_state == W_IDLE)
rcnt <= rcnt + 1'b1 ;
else
rcnt <= 8'd0;
end
//在读FIFO状态下,如果不空就从FIFO中读数据
assign rd_en = (read_state == R_FIFO) ? ~empty : 1'b0;
//-----------------------------------------------------------
//实例化FIFO
fifo_ip fifo_ip_inst
(
.rst (~fifo_rst_n ), // input rst
.wr_clk (wr_clk ), // input wr_clk
.rd_clk (rd_clk ), // input rd_clk
.din (w_data ), // input [15 : 0] din
.wr_en (wr_en ), // input wr_en
.rd_en (rd_en ), // input rd_en
.dout (r_data ), // output [15 : 0] dout
.full (full ), // output full
.empty (empty ), // output empty
.rd_data_count (rd_data_count ), // output [8 : 0] rd_data_count
.wr_data_count (wr_data_count ) // output [8 : 0] wr_data_count
);
//写通道逻辑分析仪
ila_m0 ila_wfifo (
.clk (wr_clk ),
.probe0 (w_data ),
.probe1 (wr_en ),
.probe2 (full ),
.probe3 (wr_data_count )
);
//读通道逻辑分析仪
ila_m0 ila_rfifo (
.clk (rd_clk ),
.probe0 (r_data ),
.probe1 (rd_en ),
.probe2 (empty ),
.probe3 (rd_data_count )
);
endmodule
添加PLL、FIFO、ILA的IP核,然后综合布线实例化
添加锁相环倍频分别100Mhz和50Mhz
FIFO
ILA逻辑分析仪
添加仿真的simulation 文件
vtf_fifo_tv.v
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2019/10/29 09:06:34
// Design Name:
// Module Name: vtf_fifo_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module vtf_fifo_tb;
// Inputs
reg clk;
reg rst_n;
// Instantiate the Unit Under Test (UUT)
fifo_test uut (
.clk (clk),
.rst_n (rst_n)
);
initial
begin
// Initialize Inputs
clk = 0;
rst_n = 0;
// Wait 100 ns for global reset to finish
#100;
rst_n = 1;
end
然后综合,管脚约束
然后点击Run Simulation进行在线仿真
写逻辑
读逻辑
点击Gnenerate Bitstream生成bit文件