理论知识
FIFO(First In First Out, 先入先出 ),是一种数据缓冲器,用来实现数据先入先出的读写方式。数据按顺序写入 FIFO,先被写入的数据同样在读取的时候先被读出,所以 FIFO存储器没有地址线,有一个写端口和一个读端口。
FIFO 存储器主要是作为缓存,应用在同步时钟系统和异步时钟系统中,分为 SCFIFO(同步 FIFO)和 DCFIFO(异步 FIFO)。后面实例中如:多比特数据做跨时钟域的转换、前后带宽不同步等都用到了FIFO。
同步FIFO-SCFIFO
full:写满标志位,有效表示 FIFO 已经存储满了,此时禁止再往FIFO中写入数据,防止数据溢出丢失。当写入数据量达到FIFO设置的最大空间时,时钟上升沿写入最后一个数据同时full拉高;读取数据时随时钟上升沿触发同时拉低。
empty:读空标志位,有效表示 FIFO 中已经没有数据了,此时禁止FIFO继续再读出数据,否则读出的将是无效数据。写入数据同时拉低;读到最后一个数据同时拉高。
usedw:显示当前FIFO中已存数据个数,写第一个数据时就置1,空或满时值为0(满是因为寄存器溢出)。
almost full:几乎满标志信号,我们可以控制FIFO快要被写满的时候和full信号的作用一样。
almost empty:几乎空标志信号,我们可以控制FIFO快要被读空的时候和empty信号的作用一样。
Asynchronous clear:异步复位信号,用于清空FIFO。
Synchronous clear:同步复位信号,用于清空FIFO。
后面三个没有使用
我们需要写一个顶层模块,并通过testbench定义激励来观察信号变化,验证SCFIFO IP核。我们可以看到生成的模块文件中有以下几个端口,顶层模块需要进行实例化
输入信号有:sys_clk、输入256个8bit的数据pi_data(值为十进制0~255),输入数据有效的标志信号pi_flag,写请求信号rdreq。输出信号有:读取的数据po_data、空标志信号empty、满标志信号full、指示FIFO中存在数据个数的信号usedw。
编写代码
module fifo(
input wire sys_clk ,
input wire [7:0] pi_data ,
input wire pi_flag ,
input wire rdreq ,
output wire [7:0] po_data ,
output wire empty ,
output wire full ,
output wire [7:0] usedw
);
scfifo_256x8 scfifo_256x8_inst(
.clock (sys_clk ),
.data (pi_data ),
.rdreq (rdreq ),
.wrreq (pi_flag ),
.empty (empty ),
.full (full ),
.q (po_data ),
.usedw (usedw )
);
endmodule
编写testbench
//reg define
reg sys_clk ;
reg [7:0] pi_data ;
reg pi_flag ;
reg rdreq ;
reg sys_rst_n ;
reg [1:0] cnt_baud ;
//wire define
wire [7:0] po_data ;
wire empty ;
wire full ;
wire [7:0] usedw ;
//初始化系统时钟、复位
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#100;
sys_rst_n <= 1'b1;
end
//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
always #10 sys_clk = ~sys_clk;
//cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_baud <= 2'b0;
else if(&cnt_baud == 1'b1)
cnt_baud <= 2'b0;
else
cnt_baud <= cnt_baud + 1'b1;
//pi_flag:输入数据有效标志信号,也作为FIFO的写请求信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_flag <= 1'b0;
else if((cnt_baud == 2'd0) && (rdreq == 1'b0))
pi_flag <= 1'b1;
else
pi_flag <= 1'b0;
//pi_data:输入顶层模块的数据,要写入到FIFO中的数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_data <= 8'b0;
else if((pi_data == 8'd255) && (pi_flag == 1'b1))
pi_data <= 8'b0;
else if(pi_flag == 1'b1)
pi_data <= pi_data + 1'b1;
//rdreq:FIFO读请求信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rdreq <= 1'b0;
else if(full == 1'b1)
rdreq <= 1'b1;
else if(empty == 1'b1)
rdreq <= 1'b0;
fifo fifo_inst(
.sys_clk (sys_clk ),
.pi_data (pi_data ),
.pi_flag (pi_flag ),
.rdreq (rdreq ),
.po_data (po_data ),
.empty (empty ),
.full (full ),
.usedw (usedw )
);
endmodule
初始化:初始时钟为高电平,复位有效,延迟100ns后复位释放
模拟时钟:每隔10ns翻转,时钟周期20ns,频率50MHz
输入间隔计数器cnt_baud:从0-3计数,复位和溢出归0,其他情况+1,这里溢出判断条件(cnt_baud=11)用的是位与为1
输入有效标志信号pi_flag:也是写请求信号,变化条件是时钟上升沿和复位下降沿。复位有效时归0;计数0且没有读请求时拉高相当于四个时钟周期产生一次;其他情况归0
输入数据pi_data:是要写到FIFO中的数据,变化条件是时钟上升沿和复位下降沿。复位有效时归0;输入255且pi_flag有效时归0;其他情况+1,意味着输入数据从0-255循环
读请求rdreq:变化条件是时钟上升沿和复位下降沿。复位有效时归0;full信号拉高时也拉高说明存满该读了;empty拉高时就归0说明读空禁读了
实例化
波形变化
我们选择的是普通模式,读出数据比读使能晚一拍
对比一下"先出数据FIFO模式"的波形,就可以看出延迟不延迟一拍的区别,这里没有演示,实际上只需要在下面这一步时选择先出数据模式即可
异步FIFO-SCFIFO
命名为"dcfifo_256x8to128x16",我们调用的dcfifo是输入256个深度8位宽、输出128个深度16位宽
我们需要写一个顶层模块,并通过testbench定义激励来观察信号变化,验证DCFIFO IP核。我们可以看到生成的模块文件中有以下几个端口,顶层模块需要进行实例化
输入信号 :50MHz写时钟wrclk,输入256个8bit的数据pi_data(值为十进制0~255),输入数据有效的标志信号pi_flag,25MHz的读时钟rdclk,写请求信号rdreq。 输出信号 :同步于wrclk的空标志信号wrempty,同步于wrclk的满标志信号wrfull,同步于wrclk指示FIFO中存在数据个数的信号wrusedw,从FIFO中读取的数据po_data,同步于rdclk的FIFO空标志信号rdempty,同步于rdclk 的FIFO满标志信号rdfull,同步于rdclk指示FIFO中存在数据个数的信号rdusedw。
编写代码
module fifo
(
//同步于FIFO写时钟
input wire wrclk ,
input wire [7:0] pi_data ,
input wire pi_flag ,
//同步于FIFO读时钟
input wire rdclk ,
input wire rdreq ,
//同步于FIFO写时钟
output wire wrempty ,
output wire wrfull ,
output wire [7:0] wrusedw ,
//同步于FIFO读时钟
output wire [15:0] po_data ,
output wire rdempty ,
output wire rdfull ,
output wire [6:0] rdusedw
);
dcfifo_256x8to128x16 dcfifo_256x8to128x16_inst
(
.data (pi_data), //input [7:0] data
.rdclk (rdclk ), //input rdclk
.rdreq (rdreq ), //input rdreq
.wrclk (wrclk ), //input wrclk
.wrreq (pi_flag), //input wrreq
.q (po_data), //output [15:0] q
.rdempty(rdempty), //output rdempty
.rdfull (rdfull ), //output rdfull
.rdusedw(rdusedw), //output [6:0] rdusedw
.wrempty(wrempty), //output wrempty
.wrfull (wrfull ), //output wrfull
.wrusedw(wrusedw) //output [7:0] wrusedw
);
endmodule
编写testbench
`timescale 1ns/1ns
module tb_fifo();
//reg define
reg wrclk ;
reg [7:0] pi_data ;
reg pi_flag ;
reg rdclk ;
reg rdreq ;
reg sys_rst_n ;
reg [1:0] cnt_baud ;
reg wrfull_reg0 ;
reg wrfull_reg1 ;
//wire define
wire wrempty ;
wire wrfull ;
wire [7:0] wrusedw ;
wire [15:0] po_data ;
wire rdempty ;
wire rdfull ;
wire [6:0] rdusedw ;
//初始化时钟、复位
initial begin
wrclk = 1'b1;
rdclk = 1'b1;
sys_rst_n <= 1'b0;
#100;
sys_rst_n <= 1'b1;
end
//wrclk:模拟FIFO的写时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
always #10 wrclk = ~wrclk;
//rdclk:模拟FIFO的读时钟,每20ns电平翻转一次,周期为40ns,频率为25MHz
always #20 rdclk = ~rdclk;
//cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔
always@(posedge wrclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_baud <= 2'b0;
else if(&cnt_baud == 1'b1)
cnt_baud <= 2'b0;
else
cnt_baud <= cnt_baud + 1'b1;
//pi_flag:输入数据有效标志信号,也作为FIFO的写请求信号
always@(posedge wrclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_flag <= 1'b0;
else if((cnt_baud == 2'd0) && (rdreq == 1'b0))
pi_flag <= 1'b1;
else
pi_flag <= 1'b0;
//pi_data:输入顶层模块的数据,要写入到FIFO中的数据
always@(posedge wrclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_data <= 8'b0;
else if((pi_data == 8'd255) && (pi_flag == 1'b1))
pi_data <= 8'b0;
else if(pi_flag == 1'b1)
pi_data <= pi_data + 1'b1;
//将同步于rdclk时钟的写满标志信号wrfull在rdclk时钟下打两拍
always@(posedge rdclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
wrfull_reg0 <= 1'b0;
wrfull_reg1 <= 1'b0;
end
else
begin
wrfull_reg0 <= wrfull;
wrfull_reg1 <= wrfull_reg0;
end
//rdreq:FIFO读请求信号同步于rdclk时钟
always@(posedge rdclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rdreq <= 1'b0;
//如果wrfull信号有效就立刻读,则不会看到rd_full信号拉高,
//所以此处使用wrfull在rdclk时钟下打两拍后的信号
else if(wrfull_reg1 == 1'b1)
rdreq <= 1'b1;
else if(rdempty == 1'b1)//当FIFO中的数据被读空时停止读取FIFO中的数据
rdreq <= 1'b0;
fifo fifo_inst(
.wrclk (wrclk ),
.pi_data(pi_data),
.pi_flag(pi_flag),
.rdclk (rdclk ),
.rdreq (rdreq ),
.wrempty(wrempty),
.wrfull (wrfull ),
.wrusedw(wrusedw),
.po_data(po_data),
.rdempty(rdempty),
.rdfull (rdfull ),
.rdusedw(rdusedw)
);
endmodule
初始化:和同步fifo的区别在于读写时钟是异步的,需要初始化两个时钟
时钟模拟:写时钟50MHz每隔10ns翻转一次,读时钟25MHz每隔20ns翻转一次
cnt_baud:计数器是基于写时钟的,将变化条件中时钟上升沿改为写时钟的上升沿
pi_flag:输入数据有效标志信号,基于写时钟,同上
pi_data:输入数据,基于写时钟,同上
wrfull:写满标志信号,基于读时钟,变化条件是读时钟上升沿和复位下降沿。wrfull在读时钟下打两拍,(always块中的语句是顺序执行的,使用两个寄存器b,c,令b=输入a,c=b,并输出c,那么c相对于a而言波形不会产生变化,只是有两个时钟周期的延迟,这个就叫打拍,使用几个寄存器就是延迟几个周期就是打几拍),这里打两拍的原因是如果wrfull有效立刻就读,就看不到rd_full拉高
rdreq:读请求信号,基于读时钟,变化条件是读时钟上升沿和复位下降沿。复位时归0;写满信号打两拍之后的信号拉高就拉高表示写满需要读,读空信号拉高就拉低表示读空不能读
波形变化
可以看到当pi_flag为高且pi_data为255的同时wrfull满标志信号先拉高了,延后一段时间rdfull满标志信号也拉高了,说明FIFO的存储空间已经满了。wrfull满标志信号和rdfull满标志信号同步于 不同的时钟 ,所以拉高的时间不同步。
还可以看到wrusedw信号是8位的计数到255,而rdusedw信号是7位的计数到127,因为输入是8btit的,输出是16bit的,8256=16128。wrusedw信号从255变成了0、rdusedw信号从127变成0的原因和SCFIFO中的情况一样,都是因为数据存储满了,FIFO内部的计数器溢出所导致的。
另外可以看出读出的16bit数据,是两次的输入8bit数据拼凑而成,先输入的在低位,后输入的在高位,例如输入00,01,02,03...,两次输入的数据拼凑为01_00,03_02进行输出。
如果不打两拍会发生什么呢?我们来看波形
全部0条评论
快来发表一下你的评论吧 !