实现功能:调用FIFO lP核,并进行不同形式的配置,通过仿真来验证其接口时序
1. FIFO
First In First Out,即先进先出。FPGA或者ASIC中使用到的FIFO一般指对数据的存储具有先进先出特性的存储器,常被用于数据的缓存或高速异步数据的交互。
单时钟 FIFO :SCFIFO——single-clock FIFO
双时钟FIFO:普通双时钟DCFIFO——dual-clock FIFO(supports same port widths for input and output data),和混合宽度双时钟DCFIFO_MIXED_WIDTHS (supports different port widths for inputand output data)。
data[7:0]: 数据写入端口。wreq : 写请求信号。rdreq : 读请求信号。sclr :同步清零。aclr :异步清零。q[7:0] 数据读取端口。full : FIFO内数据写满状态信号。almost_full : 数据即将写满信号。empty : FIFO数据空信号。almost_empty : 即将数据全部读取完成信号。usedw[7…0] : 可用数据个数。
2. 应用
单时钟FIFO:常用于片内数据交互。eg:在 FPGA 控制下从外部传感器读取一连串传感器数据,先写入FIFO中,再以UART串口将数据依次发送出去。由于传感器的单次读取数据可能很快,但并不是时刻都需要采集数据,例如某传感器使用SPI接口的协议,FPGA 以2M的SPI数据速率从该传感器中读取20个数据,然后以9600 的波特率通过串口发送出去。此过程每秒钟执行一次。因为2M的数据速率远高于串口9600 的波特率,因此需要将从传感器中采集到的数据首先用FIFO缓存起来,然后再以串口的数据速率缓慢发送出去。由于【传感器数据的读取和串口数据的发送都是可以同步于同一个时钟,都可以由系统时钟分频得到】,因此可以使用单时钟结构的FIFO来实现此功能。
双时钟FIFO:常用于异步数据的收发。eg:在一个高速数据采集系统中,实现将高速ADC采集的数据通过千兆以太网发送到pc机。ADC采样时钟(CLK1)由外部专用锁相环产生,FPGA内部工作时钟(CLK2)由独立的时钟芯片加片上锁相环产生的,则CLK1 和 CLK2 是两个不同域的时钟,其频率和相位没有必然的联系。假如 CLK1 为 65M,CLK2 为 125M,那么就不能使用 125M 的数据来直接采集 65M 速率的数据,因为两者数据速率不匹配,在采集过程中会出现异步信号亚稳态。低速可以使用2级D触发器同步,高速使用一个具备双时钟结构的FIFO来进行异步数据的收发,进而避免出现亚稳态情况。
ADC的数据位宽为8位,基于UDP协议的以太网发送模块所需的数据也是8位,因此使用的是非混合宽度的双时钟FIFО结构。假如 CLK1的频率为20M,ADC的数据位宽为16位,则可以使用混合宽度的双时钟 FIFO,在实现异步时钟域数据收发的同时,实现数据位宽的转换。通过设置双时钟FIFO的写入位宽为16位,读取位宽为8位,则可以实现将16位的ADC数据转换为以太网支持的8位发送数据,然后通过以太网发送到pc机。
3. 配置FIFO IP核及仿真验证
配置见文档
单时钟FIFO:数据位宽为 16bits,数据深度为 256words,almost full为可用数据>=254,almost empty为可用数据<=2。
仿真测试文件
`timescale 1ns/1ps
`define clk_period 20
module fifo_tb;
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;//取值0-255,取值为0表示FIFO中没有可用数据或者表示有256个可用数据
integer i;
fifo fifo(
.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;
initial begin
wrreq = 1'b0;//开始写入请求为无效
data = 16'd0;
rdreq = 1'b0;
#(`clk_period*20 + 1'b1);
for(i=0;i<=255;i=i+1'b1)begin
wrreq = 1'b1;
data = i;
#(`clk_period);
end
wrreq = 1'b0;
#(`clk_period*20);
for(i=0;i<=255;i=i+1'b1)begin
rdreq = 1'b1;
#(`clk_period);
end
$stop;
end
endmodule
双时钟FIFO:
写位宽16,读位宽8,没写入一个需要读两次
`timescale 1ns/1ps
`define wrclk_period 20//写16位,50M
`define rdclk_period 10//读8位,100M
module mydcfifo_tb;
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;
integer i;
mydcfifo mydcfifo(
.data(data),
.rdclk(rdclk),
.rdreq(rdreq),
.wrclk(wrclk),
.wrreq(wrreq),
.q(q),
.rdempty(rdempty),
.rdusedw(rdusedw),
.wrfull(wrfull),
.wrusedw(wrusedw)
);
initial wrclk = 1'b1;
always#(`wrclk_period/2) wrclk = ~wrclk;
initial rdclk = 1'b1;
always#(`rdclk_period/2) rdclk = ~rdclk;
initial begin
data = 16'd0;
rdreq = 1'b0;
wrreq = 1'b0;
#(`wrclk_period*20 + 1'b1)
for(i=0;i<=255;i=i+1'b1)begin
wrreq = 1'b1;
data = i;
#(`wrclk_period);
end
wrreq = 1'b0;
#(`rdclk_period*20);
for(i=0;i<=512;i=i+1'b1)begin
rdreq = 1'b1;
#(`rdclk_period);
end
rdreq = 1'b0;
$stop;
end
endmodule
在wrreq被检测到后,下一个时钟wruseddw才变化