1、简介
根据 FIFO 工作的时钟域,可以将 FIFO 分为同步 FIFO 和异步 FIFO。同步 FIFO 是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作。异步 FIFO 是指读写时钟不一致,读写时钟是互相独立的。
2、实验任务
本节的实验任务是使用 Pango 生成 FIFO IP 核,并实现以下功能: 当 FIFO 为空时,向 FIFO 中写入数据,写入的数据量和 FIFO 深度一致,即 FIFO 被写满;然后从 FIFO 中读出数据,直到 FIFO 被读空为止。
3、程序设计
实验一共分为fifo IP 核、写 fifo 模块、读 fifo 模块以及顶层例化模块实现前三个模块的信号交互。
顶层模块ip_fifo.v:顶层模块主要是对 FIFO IP 核、写 FIFO 模块、 读 FIFO 模块进行例化。
//****************************************Copyright (c)**********************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//-----------------------------------------------------------------------------
// File name: ip_fifo
// Last modified Date: 2019/05/10 11:31:26
// Last Version: V1.0
// Descriptions: FIFO实验顶层模块
//-----------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/05/10 11:31:51
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------
//***************************************************************************//
module ip_fifo(
input sys_clk , // 时钟信号
input sys_rst_n // 复位信号
);
//wire define
wire fifo_wr_en /* synthesis syn_keep=1 */;// FIFO写使能信号
wire fifo_rd_en /* synthesis syn_keep=1 */;// FIFO读使能信号
wire [7:0] fifo_din /* synthesis syn_keep=1 */;// 写入到FIFO的数据
wire [7:0] fifo_dout /* synthesis syn_keep=1 */;// 从FIFO读出的数据
wire almost_full /* synthesis syn_keep=1 */;// FIFO将满信号
wire almost_empty /* synthesis syn_keep=1 */;// FIFO将空信号
wire fifo_full /* synthesis syn_keep=1 */;// FIFO满信号
wire fifo_empty /* synthesis syn_keep=1 */;// FIFO空信号
wire [7:0] fifo_wr_data_count /* synthesis syn_keep=1 */;// FIFO写时钟域的数据计数
wire [7:0] fifo_rd_data_count /* synthesis syn_keep=1 */;// FIFO读时钟域的数据计数
//*****************************************************
//** main code
//*****************************************************
reg almost_empty_d0 ; //almost_empty 延迟一拍
reg almost_empty_d1 ; //almost_empty 延迟两拍
reg almost_empty_syn ; //almost_empty 延迟三拍
reg almost_full_d0 ; //almost_full 延迟一拍
reg almost_full_d1 ; //almost_full 延迟两拍
reg almost_full_syn ; //almost_full 延迟三拍
//因为 almost_empty 信号是属于FIFO读时钟域的
//所以要将其同步到写时钟域中
always@( posedge sys_clk ) begin
if( !sys_rst_n ) begin
almost_empty_d0 <= 1'b0 ;
almost_empty_syn <= 1'b0 ;
almost_empty_d1 <= 1'b0 ;
end
else begin
almost_empty_d0 <= almost_empty ;
almost_empty_d1 <= almost_empty_d0 ;
almost_empty_syn <= almost_empty_d1 ;
end
end
//因为 almost_full 信号是属于FIFO读时钟域的
//所以要将其同步到写时钟域中
always@( posedge sys_clk ) begin
if( !sys_rst_n ) begin
almost_full_d0 <= 1'b0 ;
almost_full_syn <= 1'b0 ;
almost_full_d1 <= 1'b0 ;
end
else begin
almost_full_d0 <= almost_full ;
almost_full_d1 <= almost_full_d0 ;
almost_full_syn <= almost_full_d1 ;
end
end
fifo_generator_0 u_fifo_generator_0 (
.wr_clk (sys_clk ), // input
.wr_rst (~sys_rst_n ), // input
.wr_en (fifo_wr_en ), // input
.wr_data (fifo_din ), // input [7:0]
.wr_full (fifo_full ), // output
.wr_water_level (fifo_wr_data_count), // output [8:0]
.almost_full (almost_full ), // output
.rd_clk (sys_clk ), // input
.rd_rst (~sys_rst_n ), // input
.rd_en (fifo_rd_en ), // input
.rd_data (fifo_dout ), // output [7:0]
.rd_empty (fifo_empty ), // output
.rd_water_level (fifo_rd_data_count), // output [8:0]
.almost_empty (almost_empty ) // output
);
//例化写FIFO模块
fifo_wr u_fifo_wr(
.clk ( sys_clk ), // 写时钟
.rst_n ( sys_rst_n ), // 复位信号
.fifo_wr_en ( fifo_wr_en ), // fifo写请求
.fifo_wr_data ( fifo_din ), // 写入FIFO的数据
.almost_empty ( almost_empty_syn ), // fifo空信号
.almost_full ( almost_full_syn ) // fifo满信号
);
//例化读FIFO模块
fifo_rd u_fifo_rd(
.clk ( sys_clk ), // 读时钟
.rst_n ( sys_rst_n ), // 复位信号
.fifo_rd_en ( fifo_rd_en ), // fifo读请求
.fifo_dout ( fifo_dout ), // 从FIFO输出的数据
.almost_empty ( almost_empty_syn ), // fifo空信号
.almost_full ( almost_full_syn ) // fifo满信号
);
endmodule
写 FIFO 模块 fifo_wr.v:fifo_wr 模块的核心部分是一个不断进行状态循环的小状态机,如果检测到 FIFO 为空,则先延时 10拍,这里注意,由于 FIFO 的内部信号的更新比实际的数据读/写操作有所延时,所以延时 10 拍的目的是等待 FIFO 的空/满状态信号、数据计数信号等信号的更新完毕之后再进行 FIFO 写操作,如果写满,则回到状态 0,即等待 FIFO 被读空,以进行下一轮的写操作。
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: fifo_wr
// Last modified Date: 2019/05/10 13:38:04
// Last Version: V1.0
// Descriptions: 写FIFO模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/05/10 13:38:14
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module fifo_wr(
//mudule clock
input clk , // 时钟信号
input rst_n , // 复位信号
//FIFO interface
input almost_empty, // FIFO将空信号
input almost_full , // FIFO将满信号
output reg fifo_wr_en , // FIFO写使能
output reg [7:0] fifo_wr_data // 写入FIFO的数据
);
//reg define
reg [1:0] state ; //动作状态
reg [3:0] dly_cnt ; //延迟计数器
//*****************************************************
//** main code
//*****************************************************
//向FIFO中写入数据
always @(posedge clk ) begin
if(!rst_n) begin
fifo_wr_en <= 1'b0;
fifo_wr_data <= 8'd0;
state <= 2'd0;
dly_cnt <= 4'd0;
end
else begin
case(state)
2'd0: begin
if(almost_empty) begin //如果检测到FIFO将被读空
state <= 2'd1; //就进入延时状态
end
else
state <= state;
end
2'd1: begin
if(dly_cnt == 10) begin //延时10拍
//原因是FIFO IP核内部状态信号的更新存在延时
//延迟10拍以等待状态信号更新完毕
dly_cnt <= 4'd0;
state <= 2'd2; //开始写操作
fifo_wr_en <= 1'b1; //打开写使能
end
else begin
dly_cnt <= dly_cnt + 4'd1;
end
end
2'd2: begin
if(almost_full) begin //等待FIFO将被写满
fifo_wr_en <= 1'b0; //关闭写使能
fifo_wr_data <= 8'd0;
state <= 2'd0; //回到第一个状态
end
else begin //如果FIFO没有被写满
fifo_wr_en <= 1'b1; //则持续打开写使能
fifo_wr_data <= fifo_wr_data + 1'd1; //且写数据值持续累加
end
end
default : state <= 2'd0;
endcase
end
end
endmodule
读 FIFO 模块 fifo_rd.v:读模块的代码结构与写模块几乎一样,也是使用一个不断进行状态循环的小的状态机来控制操作过程。
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: fifo_wr
// Last modified Date: 2019/05/10 13:38:04
// Last Version: V1.0
// Descriptions: 读FIFO模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/05/10 13:38:14
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module fifo_rd(
//system clock
input clk , // 时钟信号
input rst_n , // 复位信号
//FIFO interface
input [7:0] fifo_dout , // 从FIFO读出的数据
input almost_full , // FIFO将满信号
input almost_empty, // FIFO将空信号
output reg fifo_rd_en // FIFO读使能
);
//reg define
reg [1:0] state ; // 动作状态
reg almost_full_d0 ; // fifo_full 延迟一拍
reg almost_full_syn ; // fifo_full 延迟两拍
reg [3:0] dly_cnt ; //延迟计数器
//*****************************************************
//** main code
//*****************************************************
//因为 fifo_full 信号是属于FIFO写时钟域的
//所以要将其同步到读时钟域中
always@( posedge clk ) begin
if( !rst_n ) begin
almost_full_d0 <= 1'b0 ;
almost_full_syn <= 1'b0 ;
end
else begin
almost_full_d0 <= almost_full ;
almost_full_syn <= almost_full_d0 ;
end
end
//读出FIFO的数据
always @(posedge clk ) begin
if(!rst_n) begin
fifo_rd_en <= 1'b0;
state <= 2'd0;
dly_cnt <= 4'd0;
end
else begin
case(state)
2'd0: begin
if(almost_full_syn) //如果检测到FIFO将被写满
state <= 2'd1; //就进入延时状态
else
state <= state;
end
2'd1: begin
if(dly_cnt == 4'd10) begin //延时10拍
//原因是FIFO IP核内部状态信号的更新存在延时
//延迟10拍以等待状态信号更新完毕
dly_cnt <= 4'd0;
state <= 2'd2; //开始读操作
end
else
dly_cnt <= dly_cnt + 4'd1;
end
2'd2: begin
if(almost_empty) begin //等待FIFO将被读空
fifo_rd_en <= 1'b0; //关闭读使能
state <= 2'd0; //回到第一个状态
end
else //如果FIFO没有被读空
fifo_rd_en <= 1'b1; //则持续打开读使能
end
default : state <= 2'd0;
endcase
end
end
endmodule
TestBench文件tb_ip_fifo中只要送出时钟的复位信号即可:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2019/05/08 15:25:39
// Design Name:
// Module Name: tb_ip_ram
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module tb_ip_fifo;
reg grs_n;
//GTP_GRS I_GTP_GRS(
GTP_GRS GRS_INST(
.GRS_N (grs_n)
);
initial begin
grs_n = 1'b0;
#5000 grs_n = 1'b1;
end
// Inputs
reg sys_clk;
reg sys_rst_n;
// Instantiate the Unit Under Test (UUT)
ip_fifo u_ip_fifo (
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n)
);
//Genarate the clk
parameter PERIOD = 20;
always begin
sys_clk = 1'b0;
#(PERIOD/2) sys_clk = 1'b1;
#(PERIOD/2);
end
initial begin
// Initialize Inputs
sys_rst_n = 0;
// Wait 100 ns for global reset to finish
#100 ;
sys_rst_n = 1;
// Add stimulus here
end
endmodule
仿真波形:实际上仿真并没有像历程里面说的那样从0-256写入数据,而是0-128-0的方式写入和读出。