文章目录
FIFO简介
先进先出的数据缓存器,与普通存储器的区别是没有外部读写地址线,使用方便,缺点是只能顺序读写,不能随机读写。其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
使用场景
1、数据缓冲:也就是数据写入过快,并且间隔时间长,也就是突发写入。那么通过设置一定深度的FIFO,可以起到数据暂存的功能,且使得后续处理流程平滑。
2、时钟域的隔离:主要用异步FIFO。对于不同时钟域的数据传输,可以通过FIFO进行隔离,避免跨时钟域的数据传输带来的设计和约束上的复杂度。比如FIFO的一端是AD,另一端是PCI;AD的采集速率是16位100KSPS,每秒的数据量是1.6Mbps。而PCI总线的速度是33MHz,总线宽度是32位
3、用于不同宽度的数据接口
类别
同步FIFO
读写时钟为同一个时钟
异步FIFO
读写时钟不为同一个时钟
参数
FIFO宽度
FIFO一次读写操作的数据位
FIFO深度
指FIFO可以存储多少个N位的数据(如果宽度为N)
满指标
FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)
空指标
FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)
读时钟
读操作所遵循的时钟,在每个时钟沿来临时读数据
写时钟
写操作所遵循的时钟,在每个时钟沿来临时写数据
实现
同步FIFO实现
FIFO 读写指针(读写指针就是读写地址)的工作原理:
- 写指针:总是指向下一个将要被写入的单元,复位时,指向第 1 个单元(编号为 0)
- 读指针:总是指向当前要被读出的数据,复位时,指向第 1 个单元(编号为 0) FIFO 的“空”/“满”检测
FIFO 设计的关键:产生可靠的 FIFO 读写指针和生成 FIFO“空”/“满”状态标志。
空
当读写指针相等时,表明 FIFO 为空,这种情况发生在复位操作时,或者当读指针读出 FIFO 中最后一 个字后,追赶上了写指针时。
满
当读写指针再次相等时,表明 FIFO 为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around) 又追上了读指针。
1、计数器法
构建一个计数器,该计数器(fifo_cnt)用于指示当前 FIFO 中数据的个数:
- 复位时,该计数器为0,FIFO中的数据个数为0
- 当读写使能信号均有效时,说明又读又写,计数器不变,FIFO中的数据个数无变化
- 当写使能有效且 full=0,则 fifo_cnt +1;表示写操作且 FIFO 未满时候,FIFO 中的数据个数增加了 1
- 当读使能有效且 empty=0,则 fifo_cnt -1;表示读操作且 FIFO 未满时候,FIFO 中的数据个数减少了 1
- fifo_cnt =0 的时候,表示 FIFO 空,需要设置 empty=1;fifo_cnt = fifo的深度 的时候,表示 FIFO 现在已经满,需要设置 full=1
计数器法verilog代码
//计数器法实现同步FIFO
module sync_fifo_cnt
#(
parameter DATA_WIDTH = 8 , //FIFO位宽
parameter DATA_DEPTH = 16 //FIFO深度
)
(
input clk , //系统时钟
input rst_n , //低电平有效的复位信号
input [DATA_WIDTH-1:0] data_in , //写入的数据
input rd_en , //读使能信号,高电平有效
input wr_en , //写使能信号,高电平有效
output reg [DATA_WIDTH-1:0] data_out, //输出的数据
output empty , //空标志,高电平表示当前FIFO已被写满
output full , //满标志,高电平表示当前FIFO已被读空
output reg [$clog2(DATA_DEPTH) : 0] fifo_cnt //$clog2是以2为底取对数
);
//reg define
reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0]; //用二维数组实现RAM
reg [$clog2(DATA_DEPTH) - 1 : 0] wr_addr; //写地址
reg [$clog2(DATA_DEPTH) - 1 : 0] rd_addr; //读地址
//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
rd_addr <= 0;
else if (!empty && rd_en)begin //读使能有效且非空
rd_addr <= rd_addr + 1'd1;
data_out <= fifo_buffer[rd_addr];
end
end
//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
wr_addr <= 0;
else if (!full && wr_en)begin //写使能有效且非满
wr_addr <= wr_addr + 1'd1;
fifo_buffer[wr_addr]<=data_in;
end
end
//更新计数器
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
fifo_cnt <= 0;
else begin
case({wr_en,rd_en}) //拼接读写使能信号进行判断
2'b00:fifo_cnt <= fifo_cnt; //不读不写
2'b01: //仅仅读
if(fifo_cnt != 0) //fifo没有被读空
fifo_cnt <= fifo_cnt - 1'b1; //fifo个数-1
2'b10: //仅仅写
if(fifo_cnt != DATA_DEPTH) //fifo没有被写满
fifo_cnt <= fifo_cnt + 1'b1; //fifo个数+1
2'b11:fifo_cnt <= fifo_cnt; //读写同时
default:;
endcase
end
end
//依据计数器状态更新指示信号
//依据不同阈值还可以设计半空、半满 、几乎空、几乎满
assign full = (fifo_cnt == DATA_DEPTH) ? 1'b1 : 1'b0; //空信号
assign empty = (fifo_cnt == 0)? 1'b1 : 1'b0; //满信号
endmodule
其中计数器和读写地址以为为底去对数的原因在于用多少位的二进制表示深度。
2、高位扩展法
采取扩展最高位与读写地址位共同判断是为满或为空。
- 当最高位不同,且其他位相同,则表示读指针或者写指针多跑了一圈,而显然不会让读指针多跑一圈(多跑一圈读啥?),所以可能出现的情况只能是写指针多跑了一圈,与就意味着FIFO被写满了
- 当最高位相同,且其他位相同,则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了
高位扩展法示意图:
高位扩展法verilog代码
module sync_fifo_ptr
#(
parameter DATA_WIDTH = 'd8 , //FIFO位宽
parameter DATA_DEPTH = 'd16 //FIFO深度
)
(
input clk , //系统时钟
input rst_n , //低电平有效的复位信号
input [DATA_WIDTH-1:0] data_in , //写入的数据
input rd_en , //读使能信号,高电平有效
input wr_en , //写使能信号,高电平有效
output reg [DATA_WIDTH-1:0] data_out, //输出的数据
output empty , //空标志,高电平表示当前FIFO已被写满
output full //满标志,高电平表示当前FIFO已被读空
);
//reg define
//用二维数组实现RAM
reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0];
reg [$clog2(DATA_DEPTH) : 0] wr_ptr; //写地址指针,位宽多一位
reg [$clog2(DATA_DEPTH) : 0] rd_ptr; //读地址指针,位宽多一位
//wire define
wire [$clog2(DATA_DEPTH) - 1 : 0] wr_ptr_true; //真实写地址指针
wire [$clog2(DATA_DEPTH) - 1 : 0] rd_ptr_true; //真实读地址指针
wire wr_ptr_msb; //写地址指针地址最高位
wire rd_ptr_msb; //读地址指针地址最高位
assign {wr_ptr_msb,wr_ptr_true} = wr_ptr; //将最高位与其他位拼接
assign {rd_ptr_msb,rd_ptr_true} = rd_ptr; //将最高位与其他位拼接
//读操作,更新读地址
always @ (posedge clk or negedge rst_n) begin
if (rst_n == 1'b0)
rd_ptr <= 'd0;
else if (rd_en && !empty)begin //读使能有效且非空
data_out <= fifo_buffer[rd_ptr_true];
rd_ptr <= rd_ptr + 1'd1;
end
end
//写操作,更新写地址
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
wr_ptr <= 0;
else if (!full && wr_en)begin //写使能有效且非满
wr_ptr <= wr_ptr + 1'd1;
fifo_buffer[wr_ptr_true] <= data_in;
end
end
//更新指示信号
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign empty = ( wr_ptr == rd_ptr ) ? 1'b1 : 1'b10;
//当最高位不同但是其他位相等时,写指针超过读指针一圈,FIFO被写满
assign full = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0;
endmodule
实验仿真
TestBench代码
`timescale 1ns / 1ps
module tb_sync_fifo_ptr;
// sync_fifo_ptr Parameters
parameter PERIOD = 5 ;
parameter DATA_WIDTH = 'd8 ;
parameter DATA_DEPTH = 'd16;
// sync_fifo_ptr Inputs
reg clk = 0 ;
reg rst_n = 0 ;
reg [DATA_WIDTH-1:0] data_in = 0 ;
reg rd_en = 0 ;
reg wr_en = 0 ;
// sync_fifo_ptr Outputs
wire [DATA_WIDTH-1:0] data_out ;
wire empty ;
wire full ;
initial
begin
forever #(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2) rst_n = 1;
end
sync_fifo_ptr #(
.DATA_WIDTH ( DATA_WIDTH ),
.DATA_DEPTH ( DATA_DEPTH ))
u_sync_fifo_ptr (
.clk ( clk ),
.rst_n ( rst_n ),
.data_in ( data_in [DATA_WIDTH-1:0] ),
.rd_en ( rd_en ),
.wr_en ( wr_en ),
.data_out ( data_out [DATA_WIDTH-1:0] ),
.empty ( empty ),
.full ( full )
);
reg [7:0] tempdata = 0;
initial
begin
$dumpfile("wave.vcd"); //生成的vcd文件名称
$dumpvars(0, tb_sync_fifo_ptr); //tb模块名称
clk = 0;
rst_n = 0;
wr_en = 0;
rd_en = 0;
data_in = 0;
#10
rst_n = 1;
push(1);
fork
push(2);
pop(tempdata);
join //push and pop together
push(10);
push(20);
push(30);
push(40);
push(50);
push(60);
push(70);
push(80);
push(90);
push(100);
push(110);
push(120);
push(130);
pop(tempdata);
push(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
push(140);
pop(tempdata);
push(tempdata);//
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
pop(tempdata);
push(5);
pop(tempdata);
$finish;
end
task push (input [7:0] data);
if(full)
$display("---Cannot push %d: Buffer Full---",data);
else begin
$display("Push",data);
data_in = data;
wr_en = 1;
@(posedge clk);
#5 wr_en = 0;
end
endtask
task pop(output[7:0] data);
if(empty)
$display("---Cannot Pop: Buffer Empty---");
else begin
rd_en = 1;
@(posedge clk);
#3 rd_en = 0;
data = data_out;
$display("------Poped:",data);
end
endtask
endmodule
波形示意图
异步FIFO实现
异步产生亚稳态问题
异步FIFO需要考虑的是亚稳态的问题,如果采用二进制数的计数值从一个时钟域到另一个时钟域的时候就会出问题,因为二进制计数器的所有位都有可能发生变化。使用格雷码的话只有一位变化,在两个时钟域间的同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换成相应的gray码,然后将gray码同步到另一个时钟域进行对比,作为空满状态的检测。
gray码判断空满问题
空判断
对于“空”的判断依然依据读写指针二者完全相等(包括MSB);
满判断
因为gray码的特性,与同步不同的是MSB最高位不同时,其他位相同,不能作为FIFO满的判断。
在gray码上判断为满必须同时满足以下3条:
- wptr和同步过来的rptr的MSB不相等,因为wptr必须比rptr多折回一次。
- wptr与rptr的次高位不相等,如上图位置7和位置15,转化为二进制对应的是0111和1111,MSB不同说明多折回一次,111相同代表同一位置。
- 剩下的其余位完全相等。
设计总体实现
代码:
module AsyncFIFO#(
parameter ASIZE = 4, //地址位宽
parameter DSIZE = 8 //数据位宽
)(
input [DSIZE-1:0] wdata,
input winc,wclk,wrst_n, //写请求信号,写时钟,写复位
input rinc,rclk,rrst_n, //读请求信号,读时钟,读复位
output [DSIZE-1:0] rdata,
output wfull,
output rempty
);
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;
/*在检测“满”或“空”状态之前,需要将指针同步到其它时钟域时,使用格雷码,可以降低同步过程中亚稳态出现的概率*/
sync_r2w#(
.ADDRSIZE ( 4 )
)u_sync_r2w(
.wq2_rptr ( wq2_rptr ), //out
.rptr ( rptr ),
.wclk ( wclk ),
.wrst_n ( wrst_n )
);
sync_w2r#(
.ADDRSIZE ( 4 )
)u_sync_w2r(
.rq2_wptr ( rq2_wptr ),
.wptr ( wptr ),
.rclk ( rclk ),
.rrst_n ( rrst_n )
);
fifomem#(
.DATASIZE ( 8 ),
.ADDRSIZE ( 4 )
)u_fifomem(
.wclken ( wclken ),
.wfull ( wfull ),
.wclk ( wclk ),
.wdata ( wdata ),
.waddr ( waddr ),
.raddr ( raddr ),
.rdata ( rdata )
);
rptr_empty#(
.ADDRSIZE ( 4 )
)u_rptr_empty(
.rempty ( rempty ),
.raddr ( raddr ),
.rptr ( rptr ),
.rq2_wptr ( rq2_wptr ),
.rinc ( rinc ),
.rclk ( rclk ),
.rrst_n ( rrst_n )
);
wptr_full#(
.ADDRSIZE ( 4 )
)u_wptr_full(
.wfull ( wfull ),
.waddr ( waddr ),
.wptr ( wptr ),
.wq2_rptr ( wq2_rptr ),
.winc ( winc ),
.wclk ( wclk ),
.wrst_n ( wrst_n )
);
endmodule
顶层设计
信号描述
信号名称 | 位宽 | 信号描述 |
---|---|---|
Wdata | 数据位宽 | 写入数据 |
Wfull | 1 | 写满信号 |
Winc | 1 | 写请求信号(写使能信号) |
Wclk | 1 | 写时钟 |
Wrst_n | 1 | 写复位信号(低电平有效 |
Rdata | 数据位宽 | 读出数据 |
Rempty | 1 | 读空信号 |
Rinc | 1 | 读请求信号(读使能信号) |
Rrst_n | 1 | 读复位信号(低电平有效) |
RAM存储器模块
信号描述:
信号名称 | 位宽 | 信号描述 |
---|---|---|
wclken | 1 | 写使能信号 |
wclk | 1 | 写时钟信号 |
raddr | 地址位宽 | 读地址 |
waddr | 地址位宽 | 写地址 |
wdata | 数据位宽 | 写入的数据 |
rdata | 数据位宽 | 读数据 |
wfull | 1 | 写满信号 |
代码:
根据上面RAM的总概图,定义的一个宽度为8位,深度为8的RAM(即定义了8个寄存器,每个寄存器的宽度为8)
module fifomem
#(
parameter DATASIZE = 8, // Memory data word width
parameter ADDRSIZE = 4 // 指针地址位宽设置为4,因为8=2^3,应该加一判断写满或读空
) // Number of mem address bits
(
input wclken, wfull, wclk,
input [DATASIZE-1:0] wdata, //write data
input [ADDRSIZE-1:0] waddr, raddr,
output [DATASIZE-1:0] rdata //read data
);
// RTL Verilog memory model
localparam DEPTH = 1<<ADDRSIZE; // leftshift is equal divide two
reg [DATASIZE-1:0] mem [0:DEPTH-1];
always @(posedge wclk) begin //当使能信号有效且还未写满的时候将数据写入实体中,与wclk时钟信号同步
if (wclken && !wfull)
mem[waddr] <= wdata;
end
assign rdata = mem[raddr];
endmodule
FIFO写地址以及写满判断模块
主要是控制是否可以写入数据,写指针与写满的顶层模块图如图所示:
信号描述:
信号名称 | 位宽 | 信号描述 |
---|---|---|
Winc | 1 | 写请求信号 |
Wclk | 1 | 写时钟信号 |
Wrst_n | 1 | 写复位信号 |
Wq2_rptr | 地址位宽+1 | 同步之后的读指针(格雷码形式) |
Wfull | 1 | 写满信号 |
Waddr | 地址位宽 | 二进制形式的写地址 |
Wptr | 地址位宽+1 | 格雷码形式的写指针 |
模块作用:当时钟信号来临时且写请求信号有效时,写一组数据,并且同时写地址向下加一位,然后讲写地址转化为格雷码,判断是否写满
写满的判断:写地址指针再次追上读地址指针
二进制转格雷码:
二进制的最右一位(最低位)起,依次将每一位与左边一为进行异或操作,作为格雷码该位的值,而最左一位不变。
代码:
module wptr_full#(
parameter ADDRSIZE = 4
) (
output reg wfull,
output [ADDRSIZE-1:0] waddr,//二进制形式的写地址
output reg [ADDRSIZE:0] wptr, //格雷码形式的写指针
input [ADDRSIZE:0] wq2_rptr,//同步后的读指针
input winc, wclk, wrst_n
);
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext, wbinnext;
wire wfull_val;
// GRAYSTYLE2 pointer
always @(posedge wclk or negedge wrst_n) begin
if (!wrst_n)
{wbin, wptr} <= 0;
else
{wbin, wptr} <= {wbinnext, wgraynext};
end
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);//写请求且没写满时,地址加一
assign wgraynext = (wbinnext>>1) ^ wbinnext; //将二进制码转换为格雷码
//最高位表示多这一次,但由于格雷码特性,次高位也需要不同,其余位需要相同
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});
always @(posedge wclk or negedge wrst_n) begin
if (!wrst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
end
endmodule
FIFO读地址以及读空判断模块
读控制端口主要用于是否可以读取数据,读指针与读空的顶层模块图如下图:
信号描述:
信号名称 | 位宽 | 信号描述 |
---|---|---|
Rinc | 1 | 读请求信号 |
Rclk | 1 | 读时钟信号 |
Rrst_n | 1 | 读复位信号 |
Rq2_rptr | 地址位宽+1 | 同步之后的写指针(格雷码形式) |
Rempty | 1 | 读空信号 |
Raddr | 地址位宽 | 二进制形式的读地址 |
Rptr | 地址位宽+1 | 格雷码形式的读指针 |
**作用:**当时钟信号来且读请求信号有效时,读出一组数据,并且同时读地址向下加一位。然后将读地址转换为格雷码,判断是否为读空。
代码:
1、寄存二进制地址,方便转换成格雷码
2、根据读使能以及是否为空,赋值下一地址
3、每到上升沿采样将下一拍地址传给当前地址变量
4、当同步之后的写指针与读指针(格雷码形式)相同时说明为空
module rptr_empty #(
parameter ADDRSIZE = 4
)(
output reg rempty,
output [ADDRSIZE-1:0] raddr,
output reg [ADDRSIZE :0] rptr,
input [ADDRSIZE :0] rq2_wptr,
input rinc, rclk, rrst_n
);
reg [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext, rbinnext;
wire rempty_val;
//-------------------
// GRAYSTYLE2 pointer: gray码读地址指针
//-------------------
always @(posedge rclk or negedge rrst_n) begin
if (!rrst_n)
begin
rbin <= 0;
rptr <= 0;
end
else
begin
rbin <= rbinnext ;
rptr <= rgraynext;
end
end
// gray码计数逻辑
assign raddr = rbin[ADDRSIZE-1:0];
assign rbinnext = rbin + (rinc & ~rempty); //不空且有读请求的时候读地址加1
assign rgraynext = (rbinnext>>1) ^ rbinnext; //二进制到gray码的转换
//---------------------------------------------------------------
// FIFO empty when the next rptr == synchronized wptr or on reset
//---------------------------------------------------------------
/*
* 读指针是一个n位的gray码计数器,比FIFO寻址所需的位宽大一位
* 当读指针和同步过来的写指针完全相等时(包括MSB),说明二者折回次数一致,FIFO为空
*
*/
assign rempty_val = (rgraynext == rq2_wptr);
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;
endmodule
FIFOF写时钟同步到读时钟模块
示意图如下:
信号名称 | 位宽 | 信号描述 |
---|---|---|
Rclk | 1 | 读时钟信号 |
Rrst_n | 1 | 读复位信号 |
Wptr | 地址位宽+1 | 写指针地址 |
Rq2_wptr | 地址位宽+1 | 同步写指针地址 |
代码:
module sync_w2r #(
parameter ADDRSIZE = 4
)(
output reg [ADDRSIZE:0] rq2_wptr,
input [ADDRSIZE:0] wptr,
input rclk, rrst_n
);
reg [ADDRSIZE:0] rq1_wptr;
always @(posedge rclk or negedge rrst_n) begin
if (!rrst_n)
{rq2_wptr,rq1_wptr} <= 0;
else begin // {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
rq1_wptr <= wptr;
rq2_wptr <= rq1_wptr;
end
end
endmodule
FIFO读时钟同步到写时钟模块
示意图如下:
信号名称 | 位宽 | 信号描述 |
---|---|---|
Wclk | 1 | 写时钟信号 |
Wrst_n | 1 | 写复位信号 |
Rptr | 地址位宽+1 | 读指针地址 |
Wq2_rptr | 地址位宽+1 | 同步读指针地址 |
代码:
module sync_r2w #(
parameter ADDRSIZE = 4
)(
output reg [ADDRSIZE:0] wq2_rptr,
input [ADDRSIZE:0] rptr,
input wclk, wrst_n
);
reg [ADDRSIZE:0] wq1_rptr;
always @(posedge wclk or negedge wrst_n) begin
if (!wrst_n)
{wq2_rptr,wq1_rptr} <= 0;
else
{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
end
endmodule
实验仿真
1、输入信号初始化
2、FIFO复位
3、写读时钟
4、读写控制(写数据、读数据)
TestBench代码:
`timescale 1ns / 1ps
module tb_AsyncFIFO;
// AsyncFIFO Parameters
parameter ASIZE = 4;
parameter DSIZE = 8;
// AsyncFIFO Inputs
reg [DSIZE-1:0] wdata = 0 ;
reg winc = 0 ;
reg wclk = 0 ;
reg wrst_n = 0 ;
reg rinc = 0 ;
reg rclk = 0 ;
reg rrst_n = 0 ;
// AsyncFIFO Outputs
wire [DSIZE-1:0] rdata ;
wire wfull ;
wire rempty ;
initial
begin
//输入信号初始化
wrst_n = 1;
rrst_n = 1;
wclk = 0;
rclk = 0;
winc = 0;
rinc = 0;
wdata = 0;
//raddr = 0;
//复位信号
# 30
wrst_n = 0;
rrst_n = 0;
#30
wrst_n = 1;
rrst_n = 1;
end
always //写时钟
#2 wclk = ~wclk;
always //读时钟
#4 rclk = ~rclk;
always @(*) begin
if(!wfull) begin
winc = 1;
end
else begin
winc =0;
end
end
always @(*) begin
if(!rempty) begin
rinc = 1;
end
else begin
rinc =0;
end
end
always @(posedge wclk) begin
if(!wfull) begin
wdata <= wdata + 1;
end
else begin
wdata <= wdata;
end
end
AsyncFIFO #(
.ASIZE ( ASIZE ),
.DSIZE ( DSIZE ))
u_AsyncFIFO (
.wdata ( wdata ),
.winc ( winc ),
.wclk ( wclk ),
.wrst_n ( wrst_n ),
.rinc ( rinc ),
.rclk ( rclk ),
.rrst_n ( rrst_n ),
.rdata ( rdata ),
.wfull ( wfull ),
.rempty ( rempty )
);
initial
begin
$dumpfile("wave_test.vcd"); //生成的vcd文件名称
$dumpvars(0, tb_AsyncFIFO); //tb模块名称
#1000 $stop;
//$finish;
end
endmodule
仿真的波形图:
总结
异步fifo的重点,第一是对读写信号的同步,主要用两级缓存对读写信号的同步,第二是对写满信号的判断需要二进制到格雷码之间的转换