FIFO的使用
FIFO(First In First Out),即先进先出。 FPGA 或者 ASIC 中使用到的 FIFO 一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存或者高速异步数据的交互。它与普通存储器的区别是没有外部读写地址线,这样使用起来相对简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO 从大的情况来分,有两类结构:
FIFO 常见参数
实现 FIFO 的方法
单时钟fifo
单时钟 FIFO 具有一个独立的时钟端口 Clock,因此所有输入信号的读取都是在 Clock 的上升沿进行的,所有输出信号的变化也是在 Clock 信号的上升沿的控制下进行的,即单时钟 FIFO 的所有输入输出信号都是同步于 Clock 信号的。
单时钟 FIFO 常用于片内数据交互。例如:在 FPGA 的控制下从外部传感器读取到的一连串传感器数据,首先被写入 FIFO 中,然后再以 UART 串口波特率将数据依次发送出去。
在qurtus例化单时钟FIFO IP核并进行仿真
`timescale 1ns/1ns
`define clk_period 20
module tb_sc_fifo();
reg clk;
reg [15:0]data;
reg rdreq;
reg sclr;
reg wrreq;
wire almost_empty;
wire almost_full;
wire empty;
wire full;
wire [15:0]q;
wire [7:0]usedw;
sc_fifo sc_fifo_inst (
.clock ( clk ),
.data ( data ),
.rdreq ( rdreq ),
.sclr ( sclr ),
.wrreq ( wrreq ),
.almost_empty ( almost_empty ),
.almost_full ( almost_full ),
.empty ( empty ),
.full ( full ),
.q ( q ),
.usedw ( usedw )
);
initial clk = 1‘b1;
always#(`clk_period/2) clk = ~clk;
integer i = 0;
integer delay = 0;
initial begin
data = 0;
rdreq = 0;
wrreq = 0;
sclr = 0;
#2001;
//A,B,C,D四段程序每次仿真只打开一段
/*----A---sclr holds 10ns
for(i=0;i<=50;i=i+1)begin
wrreq = 1‘b1;
data = {$random}%65536;
#`clk_period;
end
wrreq = 1‘b0;
sclr = 1‘b1;
#10;//(`clk_period);
sclr = 1‘b0;
*/
/*----B---sclr holds 20ns
for(i=0;i<=50;i=i+1)begin
wrreq = 1‘b1;
data = {$random}%65536;
#`clk_period;
end
wrreq = 1‘b0;
sclr = 1‘b1;
#(`clk_period);
sclr = 1‘b0;
*/
/*----B--- no sclr
for(i=0;i<=50;i=i+1)begin
wrreq = 1‘b1;
data = {$random}%65536;
#`clk_period;
end
wrreq = 1‘b0;
*/
/*---D---test write and read
#1000;
for(i=0;i<10;i=i+1)begin
wrreq = 1‘b1;
data = i+1;
#`clk_period;
wrreq = 1‘b0;
#`clk_period;
delay = {$random}%8;
#(`clk_period*delay);
end
#500;
for(i=0;i<5;i=i+1)begin
rdreq = 1‘b1;
#`clk_period;
rdreq = 1‘b0;
#`clk_period;
delay = {$random}%8;
#(`clk_period*delay);
end
*/
#1000;
for(i=0;i<=255;i=i+1)begin
wrreq = 1‘b1;
data = {$random}%65536;
#`clk_period;
end
wrreq = 1‘b0;
#2000;
for(i=0;i<=255;i=i+1)begin
rdreq = 1‘b1;
#`clk_period;
end
rdreq = 1‘b0;
#2000;
$stop;
end
endmodule
A段仿真时序
B段仿真
3图的sclr信号维持时间没有超过一个时钟周期,没有被时钟上升沿采样,这样的操作会刷新fifo(flush the fifo),但是由于是同步清零的信号,没有被时钟采样,所以会产生问题,刚开始我也很疑惑,sclr信号有效且不被时钟沿采样会造成的后果是虽然刷新了fifo,但是只是将数据用0替代,并且将usedw变成零,而且被清零的部分不能存入新的数据。5图的sclr信号维持了1个周期,可以看出还是有很多不一样的,会使q的输出变成未知态,fifo的状态正常
C段仿真
使用C段仿真可以看出这个scfifo在数据溢出后就不再能写入了,会把后来的数据丢掉,
D段仿真
通过D段的仿真可以看出数据与usedw的关系,以及空满标志,almost full/almost empty 标志的变化
通过上面的仿真可以总结下图总结了四种方式fifo对应usedw上的数据
双时钟fifo
双时钟 FIFO 结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wrclk 的,所有与读相关的信号都是同步于读时钟 rdclk 的。在双时钟 FIFO 的符号图中,位于上部分的为与写相关的所有信号,位于中间部分的为与读相关的所有信号,位于下部的为异步清零信号
双时钟 FIFO 的一个典型应用就是异步数据的收发, 所谓异步数据是指数据的发送端和接收端分别使用不同的时钟域。 使用双时钟 FIFO 能够将不同时钟域中的数据同步到所需的时钟域系统中。例如:在一个高速数据采集系统中,实现将高速 ADC 采集的数据通过千兆以太网发送到 PC 机。
在quartus中例化双时钟FIFO IP核并进行实现
//顶层文件
`timescale 1ns/1ns
`define rdclk_period 10
`define wrclk_period 20
module tb_dc_fifo();
reg [15:0]data;
reg rdclk;
reg rdreq;
reg wrclk;
reg wrreq;
wire [7:0]q;
wire rdempty;
wire [8:0]rdusedw;
wire wrfull;
wire [7:0]wrusedw;
dc_fifo dc_fifo_inst (
.data ( data ),
.rdclk ( rdclk ),
.rdreq ( rdreq ),
.wrclk ( wrclk ),
.wrreq ( wrreq ),
.q ( q ),
.rdempty ( rdempty ),
.rdusedw ( rdusedw ),
.wrfull ( wrfull ),
.wrusedw ( wrusedw )
);
initial rdclk = 1‘b1;
always#(`rdclk_period/2) rdclk = ~rdclk;
initial wrclk = 1‘b1;
always#(`wrclk_period/2) wrclk = ~wrclk;
integer i=0;
reg [7:0]data_h;
reg [7:0]data_l;
initial begin
data = 0;
data_h = 0;
data_l = 0;
rdreq = 0;
wrreq = 0;
#201
for(i=0;i<=255;i=i+1)begin
data_h = {$random}%256;
data_l = {$random}%256;
data = {data_h,data_l};
wrreq = 1‘b1;
#`wrclk_period;
end
wrreq = 1‘b0;
#2000
for(i=0;i<=511;i=i+1)begin
rdreq = 1‘b1;
#`rdclk_period;
end
rdreq = 1‘b0;
#200;
$stop;
end
endmodule
对比3,4两个图,可以看出写入的数据为16‘h2481;先读出地位的8’h81,再读出高位8‘h24;
溢出的规律与scfifo相同,如果fifo内空间只剩下1个8bit的位置,写入时会丢弃整个16bit的数据,不会只写入低八位;读数据有多少就能读出多少
wrusedw会滞后一个写时钟周期,在被读出时也会按照些写时钟变化,rdusedw在写入数据时会按照写时钟变化一次递增两个,读出时按照读时钟变化,每次减少一个
FIFO的使用——quartus的 fifo ip 核使用细节
原文:https://www.cnblogs.com/HLhello/p/13066479.html