基本概念
FIFO的英文全称为First In First Out,即先进先出。FPGA使用的FIFO一般指的是对数据的存储具有先进先出的特性的一个缓存器,常被用于数据的缓存或者高速异步数据的交互,也即所谓的跨时钟域信号传递(发送和接收不是同一个时钟)。
它与FPGA内部的RAM和ROM的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像RAM和ROM那样可以由地址线决定读取或写入某个指定的地址
创建IP
先做同步时钟(单时钟FIFO)情况下
设置FIFO的位宽,即传输数据的位宽,深度为传输128字节的数据,选择单时钟FIFO
设置引脚
full为数据满时的标志信号,empty为空数据标志信号,usedw[]为剩余的可用数据量;
almost empty/almost full 将满/将空 标志信号,当剩余数据量将满/将空时,对应信号拉高;
Asynchronous clear 异步清零;
Synchronous clear同步清零;
一般使用异步清零。
选择读请求信号的类型,此处选择正常模式的FIFO,即数据在读请求发出后有效;
选择存储的数据类型,此处以自动Auto为例。
不需要用更多空间换取最大的性能,但需要电能保护。
仍然生成.v文件
再创建一个前写模式s_fifo_ah.v,存储数据256字节,选择另一种模式,在读请求之前数据有效,生成.v文件。
打开生成的inst文件
将其复制到test_ip文件里,并修改端口连接信息
测试代码
顶层test_ip.v
module test_ip (
input rst_n,
input clk,
input [7:0] no_data ,
input no_rdreq ,
input no_wrreq ,
output [7:0] no_q ,
input [7:0] ah_data ,
input ah_rdreq ,
input ah_wrreq ,
output [7:0] ah_q
);
wire no_empty ;
wire no_full ;
wire [6:0] no_usedw ;
wire ah_empty ;
wire ah_full ;
wire [7:0] ah_usedw ;
s_fifo s_fifo_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( no_data ),
.rdreq ( no_rdreq ),
.wrreq ( no_wrreq ),
.empty ( no_empty ),
.full ( no_full ),
.q ( no_q ),
.usedw ( no_usedw )
);
s_fifo_ah s_fifo_ah_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( ah_data ),
.rdreq ( ah_rdreq ),
.wrreq ( ah_wrreq ),
.empty ( ah_empty ),
.full ( ah_full ),
.q ( ah_q ),
.usedw ( ah_usedw )
);
endmodule
测试文件
`timescale 1ns/1ps
module test_tb();
reg rst_n ;
reg clk ;
reg [7:0] no_data ;
reg no_rdreq ;
reg no_wrreq ;
wire [7:0] no_q ;
reg [7:0] ah_data ;
reg ah_rdreq ;
reg ah_wrreq ;
wire [7:0] ah_q ;
parameter CYCLE = 20;
test_ip u_test_ip (
.rst_n ( rst_n ),
.clk ( clk ),
.no_data (no_data ),
.no_rdreq (no_rdreq ),
.no_wrreq (no_wrreq ),
.no_q (no_q ),
.ah_data (ah_data ),
.ah_rdreq (ah_rdreq ),
.ah_wrreq (ah_wrreq ),
.ah_q (ah_q )
);
integer i ;
initial begin
clk = 1'b1;
rst_n = 1'b1;
#(2*CYCLE);
#5;
rst_n = 1'b0;
no_data = 0 ;//复位给定信号初值
no_rdreq = 0 ;
no_wrreq = 0 ;
ah_data = 0 ;
ah_rdreq = 0 ;
ah_wrreq = 0 ;
#(5*CYCLE);
rst_n = 1'b1;//复位释放
#(CYCLE*10);//延迟10个时钟是为了打开时钟锁得到稳定的时钟信号
//写数据
for(i=0;i<32;i=i+1) begin
no_wrreq = 1'b1 ;
no_data = i + 1 ;//data给1到64
ah_wrreq = 1'b1 ;
ah_data = i + 1 ;
#(CYCLE) ;//保证一个时钟周期写写完一个数据
end
no_wrreq = 1'b0 ;//写完数据拉低
ah_wrreq = 1'b0 ;
#(5*CYCLE) ;
//读数据
for (i=0;i<16;i=i+1) begin
no_rdreq = 1'b1 ;
ah_rdreq = 1'b1 ;//写数据读地址
#(CYCLE*2) ;//保证一个是时钟周期读完一个数据
end //这个for循环,内部三行代码,在时钟上升沿到来时且读请求拉高时读一个数据,持续两个时钟周期读2个数据,相当于执行依次循环读2个数据,for循环要经历16次,所以循环内读了32个数据
no_rdreq = 1'b0 ;
ah_rdreq = 1'b0 ; //读完数据拉低
#(20*CYCLE) ;
$stop;
end
always #(CYCLE/2) clk = ~clk ;//50M
endmodule
test.do文件(修改自己的对应文件路径)
vlib work
vmap work work
#编译testbench文件
vlog test_tb.v
#编译 设计文件
vlog ../ip/s_fifo.v
vlog ../ip/s_fifo_ah.v
vlog ../rtl/test_ip.v
vlog altera_mf.v
#指定仿真顶层
vsim -novopt work.test_tb
#添加信号到波形窗
add wave -position insertpoint sim:/test_tb//*
run -all
另外,由于正常模式和前写模式fifo生成的ip文件里没有用到eccstatus端口,需要注释掉
仿真分析
全局:
先分析正常模式下,
上图定位出可以看出,写写请求拉高时有了第一个数据,而当在下一个始终上升沿时,此时已经有了数据,空数据标志拉低(它是在数据写入的同时拉低,表明可能是组合逻辑),可用数据量为1,q端无变化。
然后观察读数据波形:
当读请求拉高时,在下一个时钟上升沿读出数据1,之前写入了32个数据,读出1个数后,剩余可用数据量为31个
到最后读完时,输出32个数据,剩余可用数据为0,与此同时空信号标志拉高,数据读完后读请求拉低。
然后分析前写模式:
对比两种模式下,正常模式是在读请求拉高后才有数据,而前写模式fifo在读请求拉高之前就已经输出了一个数据,并知道拉高之后再输出第二个数据。