一、前置知识

FIFO 的英文全称是 First In First Out,即先进先出。FPGA 使用的 FIFO 一般指的是对数据的存储具有先进先出特性的缓存器,常被用于数据的缓存或者高速异步数据的交互,也即所谓的跨时钟域信号传递。FIFO 采用顺序读取、顺序写入的方式,没有外部读写地址。
FIFO 分类

  • 同步FIFO:SCFIFO 读时钟和写时钟是同一个时钟
  • 异步FIFO:DCFIFO 读写时钟不一致,读写时钟互相独立
  • 异步混合位宽FIFO:DCFIFO_MIXED_WIDTHS:读写时钟不一致并且读写数据位宽不一致。

image.png
image.png

引脚 必要 说明
data 向FIFO写入的数据
q 从FIFO读出的数据
wrreq/edreq 写请求和读请求,拉高时可进行读写操作。
clock 读写同步时钟
sclr/aclr 同步复位和异步复位
full/almost_full FIFO满信号和FIFO超过设定值信号,高电平有效
empty/almost_empty FIFO空信号和FIFO低于设定值信号,高电平有效
usedw FIFO中保持的数据量
eccstatus 纠错状态端口

image.png

引脚 必要 说明
data 向FIFO写入的数据
q 从FIFO读出的数据
wrclk/rdclk 写时钟和读时钟
wrreq/edreq 写请求和读请求,拉高时可进行读写操作。
clock 读写同步时钟
aclr 异步复位
wrfull/rdfull 写时钟侧的满信号和读时钟侧的满信号
wrempty/rdempty 写时钟侧的空信号和读时钟侧的空信号
wrusedw/rdusedw 写时钟侧检查FIFO中存储的数据量 和 读时钟侧检查FIFO中存储的数据量
eccstatus 纠错状态端口

二、原理分析

image.png
image.png
顶层模块例化四个模块:PLL时钟模块、FIFO模块、读FIFO模块、写FIFO模块
使用异步FIFO,分别接收PLL模块产生的读写时钟。
写FIFO模块向FIFO模块写入数据,接收FIFO模块的空满信号,空时写入,满时停止写入。
读FIFO模块从FIFO模块读取数据,接收FIFO模块的空满信号,满时读取,空时停止读取。
程序编写完成后生成的RTL视图如下:
10_ip_fifo.pdf

三、代码编写

3.1 创建 FIFO IP 核

3.1.1 创建工程

工程名:ip_fifo
image.png

3.1.2 创建 IP 核

image.png
IP核名称:async_fifo.v
IP核路径:X:/FPGA/Projects/10_ip_fifo/par/ipcore/async_fifo/async_fifo.v
image.png

3.1.3 配置 IP 核

  1. 通用配置
  • 数据输入输出位宽:8bit
  • FIFO数据深度:256words
  • 使用异步读写时钟,为每一个时钟配置满空信号

image.png

  1. 性能优化

选择不同的优化模式会消耗不同的资源,从而也会得到不同的性能。
image.png

  1. 端口配置
  • 读侧配置满、空、数据量端口
  • 写侧配置满、空、数据量端口
  • 额外的MSB位用来防止数据满后usedw溢出为0,导致不好判断空或满,增加一位可以防止溢出。这里不需要,因为可以直接用满空信号来做判断。
  • 添加异步复位信号

image.png

  1. 模式选择
  • 选择正常模式,输出数据会有一个时钟的延迟
  • 前显模式不会有一个时钟的数据延迟
  • 块类型自动选择即可

image.png

  1. 保护电路

FIFO IP核自带数据保护电路,即数据满或空之后不再继续写或读数据。可以在此页面禁用保护电路,这里不需要禁用。
image.png

  1. EDA界面

提示仿真需要的仿真库:altera_mf.v
image.png

  1. 产生的文件、FIFO样式、资源占用

image.png
image.png

3.2 创建 PLL IP 核

PLL IP核与上一节RAM IP核实验中多用到的一样,即PLL产生两个时钟:写时钟为50MHz,读时钟为25MHz。因此直接将上一节实验中所用到的IP核代码拷贝添加到本次实验中即可。
image.png
image.png
image.png

3.3 编写写模块代码

写模块用来向FIFO模块写入数据。通过判断满空信号,当满时写请求拉低;当空时写请求拉高,可以控制写请求状态。当写请求拉高时,改变待写入的数据,从而向FIFO写入数据。
需要注意的是,当写入数据时,利用时序逻辑判断写请求wr_req,会导致在第一次写满信号拉高时,写请求wr_req无法检测到写满信号的拉高状态(有一个时钟的延迟),进而导致写满数据后又写入了一次数据(写不进去),下一次时钟才能停止写入数据。解决办法可以利用组合逻辑,将当前写请求信号与满信号取反相与的结果作为最终的写请求信号。

  1. module fifo_wr(
  2. input clk,
  3. input rst_n,
  4. input wr_full,
  5. input wr_empty,
  6. output wr_req,
  7. output reg [7:0] wr_data
  8. );
  9. reg wr_req_temp;
  10. // 写满后写请求信号立即被拉低
  11. assign wr_req = wr_req_temp & (~wr_full);
  12. // 写请求控制
  13. always @(posedge clk or negedge rst_n) begin
  14. if(!rst_n)
  15. wr_req_temp <= 1'b0;
  16. else if(wr_empty)
  17. wr_req_temp <= 1'b1;
  18. else id(wr_full)
  19. wr_req_temp <= 1'b0;
  20. else
  21. wr_req_temp <= wr_req_temp;
  22. end
  23. // 生成待写入的数据
  24. always @(posedge clk or negedge rst_n) begin
  25. if(!rst_n)
  26. wr_data <= 8'd0;
  27. else if(wr_req)
  28. wr_data <= wr_data + 1'b1;
  29. else
  30. wr_data <= 8'd0;
  31. end
  32. endmodule

3.4 编写读模块代码

读模块用来从FIFO模块读取数据。通过判断满空信号,当满时读请求拉高;当空时读请求拉低,可以控制读请求的状态。当读请求拉高时,从FIFO读取数据。
需要注意的是,与写入数据时一样,当读取数据时,利用时序逻辑判断读请求rd_req,也会导致在第一次空信号拉高时,读请求rd_req无法检测到空信号的拉高状态(有一个时钟的延迟),进而导致数据为空后又读取了一次数据(读不出来),下一次时钟才能停止读取数据。解决办法可以利用组合逻辑,将当前读请求信号与空信号取反相与的结果作为最终的读请求信号。

  1. module fifo_rd(
  2. input clk,
  3. input rst_n,
  4. input rd_full,
  5. input rd_empty,
  6. input [7:0] rd_data,
  7. output rd_req
  8. );
  9. reg rd_req_temp;
  10. // FIFO为空后读请求信号立即被拉低
  11. assign rd_req = rd_req_temp & (~rd_empty);
  12. // 读请求控制
  13. always @(posedge clk or negedge rst_n) begin
  14. if(!rst_n)
  15. rd_req_temp <= 1'b0;
  16. else if(rd_full)
  17. rd_req_temp <= 1'b1;
  18. else id(rd_empty)
  19. rd_req_temp <= 1'b0;
  20. else
  21. rd_req_temp <= rd_req_temp;
  22. end
  23. endmodule

3.5 编写顶层模块代码

顶层模块代码只需要将这些信号线束连接起来即可。

  1. module ip_fifo(
  2. input sys_clk,
  3. input sys_rst_n
  4. );
  5. wire wr_clk_50m ;
  6. wire rd_clk_25m ;
  7. wire locked_sig ;
  8. wire wr_full ;
  9. wire wr_empty ;
  10. wire wr_req ;
  11. wire [7:0] wr_data ;
  12. wire rd_full ;
  13. wire rd_empty ;
  14. wire [7:0] rd_data ;
  15. wire rd_req ;
  16. wire [7:0] rdusedw ;
  17. wire [7:0] wrusedw ;
  18. wire rst_n ;
  19. assign rst_n = sys_rst_n & locked_sig;
  20. pll_clk u_pll_clk (
  21. .areset ( ~sys_rst_n ),
  22. .inclk0 ( sys_clk ),
  23. .c0 ( wr_clk_50m ),
  24. .c1 ( rd_clk_25m ),
  25. .locked ( locked_sig )
  26. );
  27. fifo_wr u_fifo_wr (
  28. .clk ( wr_clk_50m ),
  29. .rst_n ( rst_n ),
  30. .wr_full ( wr_full ),
  31. .wr_empty ( wr_empty ),
  32. .wr_req ( wr_req ),
  33. .wr_data ( wr_data )
  34. );
  35. async_fifo u_async_fifo (
  36. .aclr ( ~rst_n ),
  37. .data ( wr_data ),
  38. .rdclk ( rd_clk_25m ),
  39. .rdreq ( rd_req ),
  40. .wrclk ( wr_clk_50m ),
  41. .wrreq ( wr_req ),
  42. .q ( rd_data ),
  43. .rdempty ( rd_empty ),
  44. .rdfull ( rd_full ),
  45. .rdusedw ( rdusedw ),
  46. .wrempty ( wr_empty ),
  47. .wrfull ( wr_full ),
  48. .wrusedw ( wrusedw )
  49. );
  50. fifo_rd u_fifo_rd(
  51. .clk ( rd_clk_25m ),
  52. .rst_n ( rst_n ),
  53. .rd_full ( rd_full ),
  54. .rd_empty ( rd_empty ),
  55. .rd_data ( rd_data ),
  56. .rd_req ( rd_req )
  57. );
  58. endmodule

四、代码仿真

4.1 编写激励文件

生成一个激励文件模板,并在此基础上进行修改。

  1. // Copyright (C) 2019 Intel Corporation. All rights reserved.
  2. // Your use of Intel Corporation's design tools, logic functions
  3. // and other software and tools, and any partner logic
  4. // functions, and any output files from any of the foregoing
  5. // (including device programming or simulation files), and any
  6. // associated documentation or information are expressly subject
  7. // to the terms and conditions of the Intel Program License
  8. // Subscription Agreement, the Intel Quartus Prime License Agreement,
  9. // the Intel FPGA IP License Agreement, or other applicable license
  10. // agreement, including, without limitation, that your use is for
  11. // the sole purpose of programming logic devices manufactured by
  12. // Intel and sold by Intel or its authorized distributors. Please
  13. // refer to the applicable agreement for further details, at
  14. // https://fpgasoftware.intel.com/eula.
  15. // *****************************************************************************
  16. // This file contains a Verilog test bench template that is freely editable to
  17. // suit user's needs .Comments are provided in each section to help the user
  18. // fill out necessary details.
  19. // *****************************************************************************
  20. // Generated on "04/29/2022 12:52:01"
  21. // Verilog Test Bench template for design : ip_fifo
  22. //
  23. // Simulation tool : ModelSim-Altera (Verilog)
  24. //
  25. `timescale 1 ns/ 1 ns
  26. module ip_fifo_vt();
  27. // constants
  28. // general purpose registers
  29. //reg eachvec;
  30. // test vector input registers
  31. reg sys_clk;
  32. reg sys_rst_n;
  33. // wires
  34. parameter T = 20;
  35. // assign statements (if any)
  36. ip_fifo i1 (
  37. // port map - connection between master ports and signals/registers
  38. .sys_clk(sys_clk),
  39. .sys_rst_n(sys_rst_n)
  40. );
  41. initial
  42. begin
  43. // code that executes only once
  44. // insert code here --> begin
  45. sys_clk = 1'b0;
  46. sys_rst_n = 1'b0;
  47. #(5*T) sys_rst_n = 1'b1;
  48. #(500*T) $stop;
  49. // --> end
  50. //$display("Running testbench");
  51. end
  52. always
  53. // optional sensitivity list
  54. // @(event1 or event2 or .... eventn)
  55. begin
  56. // code executes for every event on sensitivity list
  57. // insert code here --> begin
  58. #(T/2) sys_clk = ~sys_clk;
  59. //@eachvec;
  60. // --> end
  61. end
  62. endmodule

4.2 配置 ModelSim 工程

4.2.1 创建项目

项目名称:ip_fifo
项目路径:D:/Projects/FPGA/Projects/10_ip_fifo/sim/tb
image.png

4.2.2 添加仿真文件

  • ip_fifo.vt:X:/Projects/10_ip_fifo/par/simulation/modelsim/ip_fifo.vt
  • ip_fifo.v:X:/Projects/10_ip_fifo/rtl/ip_fifo.v
  • fifo_rd.v:X:/Projects/10_ip_fifo/rtl/fifo_rd.v
  • fifo_wr.v:X:/Projects/10_ip_fifo/rtl/fifo_wr.v
  • async_fifo.v:X:/Projects/10_ip_fifo/par/ipcore/async_fifo/async_fifo.v
  • pll_clk.v:X:/Projects/10_ip_fifo/par/ipcore/pll_clk/pll_clk.v
  • altera_mf.v:D:/Quartus/install/quartus/eda/sim_lib/altera_mf.v

    4.3 开始仿真

    编译完成后,选择ip_fifo_vt开始仿真。
    image.png
    大致看一下,读写过程中的信号波形没有问题。具体看一下读写的数据:
    image.png
  1. 一开始时写空信号wr_empty为高,待数据稳定后(即locked_sig拉高后),写请求信号拉高,开始写数据,wr_data开始累加。
  2. 一开始读空信号是高电平,所以读请求信号为低,不读数据。

image.png

  1. 再写入256个数据(255)之后,FIFO满,写满信号wr_full被拉高,写请求拉低,不再继续写入数据。
  2. 在读到满数据后,读满信号rd_full拉高,从而读请求rd_req拉高,开始读数据。

image.png

  1. 再读到第256个数据(255)后,FIFO中没有数据了,读空信号被拉高,与此同时读请求信号rd_req被拉低,不再读取数据。
  2. FIFO数据为空后,写空信号wr_empty拉高,从而写请求信号wr_req拉高,开始写入数据。
  3. 这样,在写满后开始读数据,数据被读空后再写入数据,以此循环。

波形文件:10_ip_fifo.do10_ip_fifo.wlf详见工程代码10_ip_fifo\sim\tb目录。