fifo知识整理
说明:均为网上资源整理,如有版权问题请联系删除
同步FIFO
21.跨时钟域
时钟的时序特性包括时钟周期、时钟占空比、时钟转换时间、时钟延迟、时钟偏斜和时钟抖动。
21.1格雷码与二进制转换
格雷码转二进制:
n位的二进制:Bn, Bn-1, Bn-2 … B2, B1, B0;
n位的格雷码:Gn, Gn-1, Gn-2 … G2, G1, G0;
转换公式: Bn =Gn;
Bi-1 = Bi ^ Gi-1;( i=0,1,2,n-1;
产生任意深度的 FIFO:
(1)格雷码是对称的,去掉中间几位格雷码,可以达到首尾相差 1 位,深度为偶数。
(2)深度为一般值,自行设计逻辑电路,或查找表。格雷码的 bus skew 不能超过一个周期,否则格雷码多位数据跳变失去作用。格雷码是对称的,去掉中间的或者去掉两头可以产生循环码。
Gray = Bin^(Bin>>1);错位异或的结果,(二进制转格雷码)
错位异或^ 异或:相同为0,相异为1
21.2同步fifo
同步fifo:同一时钟域下的,同时用于写入和读取操作。同步FIFO用于临时存储数据,此时写人和读取操作可以同时发生,也可发生在不同时刻。
clk : 该时钟为同步FIFO读写操作的工作时钟。
rst_n : 该信号为同步FIFO的复位信号,低电平有效。
wren : 该信号为同步FIFO的写使能。
rden : 该信号为同步FIFO的读使能。
wdata : 该总线为写数据总线。
rdata : 该总线为读数据总线。
full : 该信号为FIFO已满标志。如果 FIFO 为满状态,则禁止再写数据。
empty : 该信号为FIFO已空标志。如果 FIFO 为空状态,则禁止再读数据
同步FIFO结构:memory、写控制逻辑、读控制逻辑、空满标志判断
在 FIFO 中常用的RAM包括单口RAM、简单双口RAM、真双口RAM、单口ROM、双口ROM这5种类型的RAM,也可以使用寄存器来实现FIFO的存储器。
wclk : 该时钟为双口RAM写操作的工作时钟。
wren : 该信号为双口RAM的写使能。
waddr : 该总线为双口RAM的写地址总线。
wdata : 该总线为双口RAM的写数据总线。
rclk : 该时钟为双口RAM读操作的工作时钟。
rden : 该信号为双口RAM的读使能。
raddr : 该总线为双口RAM的读地址总线。
rdata: 该总线为双口RAM的读数据总线。
写控制逻辑----------------------------------------------------------------------------
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
waddr <= {ADDR_WIDTH{1’b0}};
else if (wren && !wfull) //只有写使能并且没写满的时候才能操作写地址
begin
if(waddr == DEPTH - 1) //如果快要计数到最大,则清零重新给读地址。
waddr <= {ADDR_WIDTH{1’b0}};
else
waddr <= waddr + 1’d1;
end
else
waddr <= waddr;
end
读控制逻辑----------------------------------------------------------------------------
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
raddr <= {ADDR_WIDTH{1’b0}};
else if (rden && !rempty)//只有读使能并且没读空的时候才能操作读地址
begin
if(raddr == DEPTH - 1)//如果快要计数到最大,则清零重新给读地址。
raddr <= {ADDR_WIDTH{1’b0}};
else
raddr <= raddr + 1’d1;
end
else
raddr <= raddr;
end
空满标志产生逻辑(两种解法)----------------------------------------------------------------------------
一:用地址计数器addr_cnt,在执行一次写操作时addr_cnt加1,执行一次读操作时addr_cnt减1。
1.如何操作地址计数器
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) //复位时为0
addr_cout <= {(ADDR_WIDTH){1’b0}};
else if(wren && !rden && !wfull && (addr_cout < DEPTH-1))
//条件为写使能,不读使能,并且没写满时,地址<深度减一
防止在读写使能同时有效,FIFO已经存满时读使能无效,造成地址计数器向上溢出,产生错误的读空标志。
addr_cout <= addr_cout + 1’d1;
else if(!wren&& rden && !rempty && (addr_cout > 0))
//条件不写使能,读使能,并且没读空时,地址>0
防止在读写使能同时有效,FIFO只有1位时(此时地址计数器值为0),写使能无效,造成地址计数器向下溢出,产生错误的写满标志.
addr_cout <= addr_cout - 1’d1;
else
addr_cout <= addr_cout;
End
2.写满标志位产生
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
wfull <= 1’b0;
else
wfull <= ((!rinc) && (((addr_cout== DEPTH-2)&&winc)||(addr_cout== DEPTH-1)));
end
当读使能无效,地址计数器计数值等于FIFO的深度减1时,写满标志有效。
当读使能无效,地址计数器计数值等于FIFO的深度减2,写使能有效时。写满标志有效。
3.读空标志位产生
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rempty <= 1’b1;
else
rempty <= ((!winc) && (((addr_cout== 1)&&rinc)||(addr_cout==0)));
end
当写使能无效,地址计数器计数值等于0时,读空标志有效。
当写使能无效,地址计数器计数值等于1时,读使能有效时。读空标志有效。
module sync_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk , //读写时钟
input rst_n , //异步复位
input wren , //写使能
input rden , //读使能
input [WIDTH-1:0] wdata , //写数据
output wire wfull , //写满信号
output wire rempty , //读空信号
output wire [WIDTH-1:0] rdata //读数据
);
/********************** 内部信号声明 **********************/
localparam ADDR_WIDTH = $clog2(DEPTH); //地址位宽
wire wenc ; //双端口RAM写使能
wire renc ; //双端口RAM读使能
reg [ADDR_WIDTH:0] waddr ; //写地址寄存器
reg [ADDR_WIDTH:0] raddr ; //读地址寄存器
/*************************功能定义*************************/
assign wenc = wren && !wfull;
assign renc = rden && !rempty;
//双端口RAM
dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH))
dual_port_RAM_U1
(
.wclk (clk ), //写数据时钟
.wenc (wenc ), //写使能
.waddr (waddr[ADDR_WIDTH-1:0]), //写地址
.wdata (wdata ), //输入数据
.rclk (clk ), //读数据时钟
.renc (renc ), //读使能
.raddr (raddr[ADDR_WIDTH-1:0]), //读地址
.rdata (rdata ) //输出数据
);
//写控制逻辑
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
waddr <= {(ADDR_WIDTH+1){1'b0}};
else if (wren && !wfull)
waddr <= waddr + 1'd1;
else
waddr <= waddr;
end
//读控制逻辑
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
raddr <= {(ADDR_WIDTH+1){1'b0}};
else if (rden && !rempty)
raddr <= raddr + 1'd1;
else
raddr <= raddr;
end
// 写满标志判断
assign wfull = (raddr == {~waddr[ADDR_WIDTH],waddr[ADDR_WIDTH-1:0]})? 1'b1:1'b0;
// 空标志判断
assign rempty = (raddr == waddr)? 1'b1:1'b0;
endmodule
module tb_sync_fifo;
// Parameters
parameter WIDTH = 8;
parameter DEPTH = 16;
// Signals
reg clk;
reg rst_n;
reg wren;
reg rden;
reg [WIDTH-1:0] wdata;
wire [WIDTH-1:0] rdata;
wire wfull;
wire rempty;
// Instantiate the FIFO
sync_fifo #(
.WIDTH(WIDTH),
.DEPTH(DEPTH)
) uut (
.clk(clk),
.rst_n(rst_n),
.wren(wren),
.rden(rden),
.wdata(wdata),
.wfull(wfull),
.rempty(rempty),
.rdata(rdata)
);
// Clock generation
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// Test sequence
initial begin
// Initialize signals
rst_n = 0;
wren = 0;
rden = 0;
wdata = 0;
// Reset the FIFO
#10;
rst_n = 1;
// Write data to FIFO
#10;
wren = 1;
wdata = 8'hAA;
#10;
wren = 0;
// Write another data to FIFO
#10;
wren = 1;
wdata = 8'h55;
#10;
wren = 0;
// Read data from FIFO
#10;
rden = 1;
#10;
rden = 0;
// Read another data from FIFO
#10;
rden = 1;
#10;
rden = 0;
// Finish simulation
#100;
$finish;
end
endmodule
另一种方法的同步fifo
module sync_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk , //读写时钟
input rst_n , //异步复位
input wren , //写使能
input rden , //写使能
input [WIDTH-1:0] wdata , //写数据
output reg wfull , //写满信号
output reg rempty , //读空信号
output wire [WIDTH-1:0] rdata //读数据
);
/********************参数定义********************/
/*********************IO 说明********************/
/********************** 内部信号声明 **********************/
localparam ADDR_WIDTH = $clog2(DEPTH); //地址位宽
wire wenc ; //双端口RAM写使能
wire renc ; //双端口RAM读使能
reg [ADDR_WIDTH-1:0] waddr ; //写地址寄存器
reg [ADDR_WIDTH-1:0] raddr ; //读地址寄存器
reg [ADDR_WIDTH-1:0] addr_cout; //地址计数器
/*************************功能定义*************************/
assign wenc = wren && !wfull;
assign renc = rden && !rempty;
//双端口RAM
dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH))
dual_port_RAM_U1
(
.wclk (clk ), //写数据时钟
.wenc (wenc ), //写使能
.waddr (waddr), //写地址
.wdata (wdata), //输入数据
.rclk (clk ), //读数据时钟
.renc (renc ), //读使能
.raddr (raddr), //读地址
.rdata (rdata) //输出数据
);
//写控制逻辑
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
waddr <= {ADDR_WIDTH{1'b0}};
else if (wren && !wfull)
begin
if(waddr == DEPTH - 1)
waddr <= {ADDR_WIDTH{1'b0}};
else
waddr <= waddr + 1'd1;
end
else
waddr <= waddr;
end
//读控制逻辑
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
raddr <= {ADDR_WIDTH{1'b0}};
else if (rden && !rempty)
begin
if(raddr == DEPTH - 1)
raddr <= {ADDR_WIDTH{1'b0}};
else
raddr <= raddr + 1'd1;
end
else
raddr <= raddr;
end
//地址计数器(计算当前读写地址之间的差值)
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
addr_cout <= {(ADDR_WIDTH){1'b0}};
else if(wren && !rden && !wfull && (addr_cout < DEPTH-1))
addr_cout <= addr_cout + 1'd1;
else if(!wren && rden && !rempty && (addr_cout > 0))
addr_cout <= addr_cout - 1'd1;
else
addr_cout <= addr_cout;
end
// 满标志判断
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
wfull <= 1'b0;
else
wfull <= ((!rden) && (((addr_cout== DEPTH-2)&&wren)||(addr_cout== DEPTH-1)));
end
// 空标志判断
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rempty <= 1'b1;
else
rempty <= ((!wren) && (((addr_cout== 1)&&rden)||(addr_cout==0)));
end
endmodule
module tb_sync_fifo;
// Parameters
parameter WIDTH = 8;
parameter DEPTH = 16;
// Signals
reg clk;
reg rst_n;
reg wren;
reg rden;
reg [WIDTH-1:0] wdata;
wire [WIDTH-1:0] rdata;
wire wfull;
wire rempty;
// Instantiate the FIFO
sync_fifo #(
.WIDTH(WIDTH),
.DEPTH(DEPTH)
) uut (
.clk(clk),
.rst_n(rst_n),
.wren(wren),
.rden(rden),
.wdata(wdata),
.wfull(wfull),
.rempty(rempty),
.rdata(rdata)
);
// Clock generation
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// Test sequence
initial begin
// Initialize signals
rst_n = 0;
wren = 0;
rden = 0;
wdata = 0;
// Reset the FIFO
#10;
rst_n = 1;
// Write data to FIFO
#10;
wren = 1;
wdata = 8'hAA;
#10;
wren = 0;
// Write another data to FIFO
#10;
wren = 1;
wdata = 8'h55;
#10;
wren = 0;
// Read data from FIFO
#10;
rden = 1;
#10;
rden = 0;
// Read another data from FIFO
#10;
rden = 1;
#10;
rden = 0;
// Finish simulation
#100;
$finish;
end
endmodule
异步fifo
其中同步FIFO的输入与输出采用相同的时钟,而异步FIFO的读写时钟是互相独立的。
21.3.1用途:
跨时钟域:异步FIFO读写分别采用相互异步的不同时钟。
位宽变换:对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
其中FIFO Memory相当于一个双端口RAM,两个格雷码计数器用于计数读写指针,两个同步模块分别用于将写指针同步到读时钟域判断读空以及将读指针同步到写时钟域判断写满。
21.3.2两级触发同步器
问题:由于异步FIFO的读写时钟不同步,如果按照同步FIFO一样直接把读写指针进行比较,会带来很多亚稳态问题,比如在比较时写指针正处于亚稳态,会造成比较出现错误。因此就需要将读写指针同步到相同的时钟域进行比较。
同步器是一种对异步信号采样并输出具有与本地或采样时钟同步的转换信号的设备,最常用的同步器是双触发器同步器,
第二个阶段触发器,其预期目标是,阶段 2 信号现在是一个稳定且有效的信号,已同步并准备好在新时钟域内分发。
但使用这样的方法仍然存在问题,如果读写指针采用二进制计数器,每次计数时都会有多个位同时变化,如0111到1000,所有位都会进行翻转。如果恰巧在翻转的亚稳态过程中进行采样,就会出现问题。所以在对读写指针进行跨时钟域同步的时候,最好将二进制码转换成格雷码。
21.3.3使用格雷码判断空 / 满
对于“空”的判断依然依据二者完全相等(包括MSB);
而对于“满”的判断,如下图,由于gray码除了MSB外,具有镜像对称的特点,当读指针指向7,写指针指向8时,除了MSB,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下3条:
1.过来的rptr的MSB不相等,因为wptr必须比rptr多折回一次。
2.wptr与rptr的次高位不相等,如上图位置7和位置15,转化为二进制对应的是0111和1111,MSB不同说明多折回一次,111相同代表同一位置。
3.剩下的其余位完全相等。
判断读空 :将写指针同步到读时钟域,与读指针进行比较,每一位都完全相同才判断为读空;
判断写满 :将读指针同步到写时钟域,与写指针进行比较,最高两位不相等,其余各位完全相同则写满。
21.3.4判断同步方向
异步FIFO的读、写指针是不同时钟域的信号,那么就不能直接对比,而是需要将其同步到同一时钟域才能进行对比。保守设计(安全
判断读空或写满都要在本时钟域进行,也就是说判断读空在读时钟域,判断写满在写时钟域。那么假读空会不会造成有数据在FIFO里面读不出来?答案是不会,同步时虽然延迟了两个信号,但是最终还是会同步到跨时钟域,所以假读空信号不会一直有效,还是会在延后几个周期把数据读出来。
同步到写时钟域:
读指针同步到写时钟域需要时间T,在经过T时间后,可能原来的读指针会增加或者不变,也就是说同步后的读指针一定是小于等于原来的读指针的。写指针也可能发生变化,但是写指针本来就在这个时钟域,所以是不需要同步的,也就意味着进行对比的写指针就是真实的写指针。
**写满判断:**写指针超过同步后的读指针一圈,但是原来的读指针是大于等于同步后的读指针的,所以实际上这个时候写指针其实是没有超过读指针一圈的,也就是说这种情况是“假写满”。
同步到读时钟域:
写指针同步到读时钟域需要时间T,在经过T时间后,可能原来的写指针会增加或者不变,也就是说同步后的写指针一定是小于等于原来的写指针的。读指针也可能发生变化,但是读指针本来就在这个时钟域,所以是不需要同步的,也就意味着进行对比的读指针就是真实的读指针。
**读空判断:**也就是读指针追上了同步后的指针。但是原来的写指针是大于等于同步后的写指针的,所以实际上这个时候读指针其实还没有追上写指针,也就是说这种情况是“假读空”。
怎么得到真满空?
之前是在写时钟域判断full信号,在读时钟域判断empty信号,得到了假满空。如果我们在写时钟域判断empty信号,在读时钟域判断full信号,得到的就是真满空!假如说,在写时钟域,通过滞后的read_pointer_syn都得到了空信号,那说明实际的read pointer必然真的赶上了write pointer,所以FIFO此刻绝对空了。在读时钟域,通过滞后的write_pointer_syn都得到了满信号,那说明实际的write pointer必然真的超过了read pointer一圈,所以FIFO此刻绝对满了
问题:同步会消耗两个时钟周期,是否会导致设计出错?
会虚空、虚满,但不出错。读空、写满经过两级触发器同步后是一种保守的判断,会有额外的开销,但不会导致错误。在写指针同步到读时钟域,写时钟域可能还会写入新的数据,导致判断的读空不一定是真的空。这样更保守,为设计留了设计余量,不会空读。同样,写满时,读指针可能读出了数据,不会溢出。这些做会有性能损失,但不会出错。
问题:读写时钟频率相差大
假设写时钟快,读时钟慢,采不齐写指针。慢的读指针被同步到写时钟域,写满判断不会出错。较快的写指针同步到读时钟域会出现数据丢失情况。
21.3.5 快采慢和慢采快
(1)读慢写快
进行写满判断的时候需要将读指针同步到写时钟域,因为读慢写快,所以不会有读指针遗漏,同步消耗时钟周期,所以同步后的读指针滞后(小于等于)当前读地址,所以可能写满会提前产生,并非真写满。
进行读空判断的时候需要将写指针同步到读指针 ,因为读慢写快,所以当读时钟同步写指针的时候,必然会漏掉一部分写指针。相当于在读时钟域还没来得及觉察的情况下,写时钟域可能写了数据到FIFO去,这样在判断它是不是空的时候会出现不是真正空的情况,漏掉的指针也没有对FIFO的逻辑操作产生影响。
(2)读快写慢
进行读空判断的时候需要将写指针同步到读指针 ,因为读快写慢,所以不会有写指针遗漏,同步消耗时钟周期,所以同步后的写指针滞后(小于等于)当前写地址,所以可能读空会提前产生,并非真读空。
进行写满判断的时候需要将读指针同步到写时钟域,因为读快写慢,所以当写时钟同步读指针的时候,必然会漏掉一部分读指针。相当于在写时钟域还没来得及觉察的情况下,读时钟域可能从FIFO读了数据出来,这样在判断它是不是满的时候会出现不是真正满的情况,漏掉的指针也没有对FIFO的逻辑操作产生影响。
现在我们会发现,所谓的空满信号实际上是不准确的,在还没有空、满的时钟就已经输出了空满信号。假空、假满信号本质上是一种保守设计,也不会使得我们的设计出错,但是会降低效率。
21.3.6非2次幂深度的FIFO
2次幂深度的FIFO可以直接加一个标志位像现在这样比较,非二次幂需要重新设计格雷码比较空满的规则,空标志只用判断读、写指针是否全部相等即可,但是满标志就需要找其他规律了。
module async_fifo #(
parameter DATA_WIDTH = 32,
parameter DATA_DEPTH = 8,
parameter PTR_WIDTH = $clog2(DATA_DEPTH)
)(
input wire clk_rd_i,
input wire rst_n_rd_i,
input wire clk_wr_i,
input wire rst_n_wr_i,
// write interface
input wire wr_en_i,
input wire [DATA_WIDTH-1:0] wr_data_i,
// read interface
input wire rd_en_i,
output reg [DATA_WIDTH-1:0] rd_data_o,
// flags_o
output wire full_o,
output wire empty_o
);
reg [DATA_WIDTH-1:0] FIFO[0:DATA_DEPTH-1];
reg [PTR_WIDTH:0] rd_ptr;
reg [PTR_WIDTH:0] rd_ptr_d1;
reg [PTR_WIDTH:0] rd_ptr_gray_d2;
wire [PTR_WIDTH:0] rd_ptr_gray;
wire [PTR_WIDTH-1:0] rd_ptr_true;
reg [PTR_WIDTH:0] wr_ptr;
reg [PTR_WIDTH:0] wr_ptr_d1;
reg [PTR_WIDTH:0] wr_ptr_gray_d2;
wire [PTR_WIDTH:0] wr_ptr_gray;
wire [PTR_WIDTH-1:0] wr_ptr_true;
/*---------------------------------------------------\
------------- rd_ptr++ and wr_ptr++ --------------
\---------------------------------------------------*/
always @(posedge clk_rd_i or negedge rst_n_rd_i) begin
if(!rst_n_rd_i)
rd_ptr <= {(PTR_WIDTH+1){1'b0}};
else if(rd_en_i==1'b1 && empty_o==1'b0)
rd_ptr <= rd_ptr + 1;
end
always @(posedge clk_wr_i or negedge rst_n_wr_i) begin
if(!rst_n_wr_i)
wr_ptr <= {(PTR_WIDTH+1){1'b0}};
else if(wr_en_i==1'b1 && full_o==1'b0)
wr_ptr <= wr_ptr + 1;
end
assign rd_ptr_true = rd_ptr[PTR_WIDTH-1:0];
assign wr_ptr_true = wr_ptr[PTR_WIDTH-1:0];
/*---------------------------------------------------\
---------- data read and data write ---------------
\---------------------------------------------------*/
always @(posedge clk_rd_i or negedge rst_n_rd_i) begin
if(!rst_n_rd_i)
rd_data_o <= {(DATA_WIDTH){1'b0}};
else if(rd_en_i==1'b1 && empty_o==1'b0)
rd_data_o <= FIFO[rd_ptr_true];
end
integer i;
always @(posedge clk_wr_i or negedge rst_n_wr_i) begin
if(!rst_n_wr_i)
for(i=0;i<DATA_DEPTH;i=i+1)
FIFO[i] <= {(DATA_WIDTH){1'b0}};
else if(wr_en_i==1'b1 && full_o==1'b0)
FIFO[wr_ptr_true] <= wr_data_i;
end
/*---------------------------------------------------\
--------- binary to gray and pointer sync ---------
\---------------------------------------------------*/
assign rd_ptr_gray = rd_ptr ^ (rd_ptr>>1);
assign wr_ptr_gray = wr_ptr ^ (wr_ptr>>1);
always @(posedge clk_rd_i or negedge rst_n_rd_i) begin
if(!rst_n_rd_i) begin
wr_ptr_d1 <= {(PTR_WIDTH+1){1'b0}};
wr_ptr_gray_d2 <= {(PTR_WIDTH+1){1'b0}};
end
else begin
wr_ptr_d1 <= wr_ptr_gray;
wr_ptr_gray_d2 <= wr_ptr_d1;
end
end
always @(posedge clk_wr_i or negedge rst_n_wr_i) begin
if(!rst_n_rd_i) begin
rd_ptr_d1 <= {(PTR_WIDTH+1){1'b0}};
rd_ptr_gray_d2 <= {(PTR_WIDTH+1){1'b0}};
end
else begin
rd_ptr_d1 <= rd_ptr_gray;
rd_ptr_gray_d2 <= rd_ptr_d1;
end
end
/*---------------------------------------------------\
------------ full_o and empty_o ------------------
\---------------------------------------------------*/
assign full_o = (wr_ptr_gray=={~rd_ptr_gray_d2[PTR_WIDTH:PTR_WIDTH-1], rd_ptr_gray_d2[PTR_WIDTH-2:0]}) ? 1'b1 : 1'b0;
assign empty_o = (rd_ptr_gray==wr_ptr_gray_d2) ? 1'b1 : 1'b0;
endmodule
testbench可以尝试这个简单的
module tb_async_fifo;
// Parameters
parameter DATA_WIDTH = 32;
parameter DATA_DEPTH = 8;
parameter PTR_WIDTH = $clog2(DATA_DEPTH);
// Signals
reg clk_rd_i;
reg rst_n_rd_i;
reg clk_wr_i;
reg rst_n_wr_i;
reg wr_en_i;
reg [DATA_WIDTH-1:0] wr_data_i;
reg rd_en_i;
wire [DATA_WIDTH-1:0] rd_data_o;
wire full_o;
wire empty_o;
// Instantiate the FIFO
async_fifo #(
.DATA_WIDTH(DATA_WIDTH),
.DATA_DEPTH(DATA_DEPTH),
.PTR_WIDTH(PTR_WIDTH)
) uut (
.clk_rd_i(clk_rd_i),
.rst_n_rd_i(rst_n_rd_i),
.clk_wr_i(clk_wr_i),
.rst_n_wr_i(rst_n_wr_i),
.wr_en_i(wr_en_i),
.wr_data_i(wr_data_i),
.rd_en_i(rd_en_i),
.rd_data_o(rd_data_o),
.full_o(full_o),
.empty_o(empty_o)
);
// Clock generation
initial begin
clk_rd_i = 0;
forever #5 clk_rd_i = ~clk_rd_i;
end
initial begin
clk_wr_i = 0;
forever #7 clk_wr_i = ~clk_wr_i;
end
// Test sequence
initial begin
// Initialize signals
rst_n_rd_i = 0;
rst_n_wr_i = 0;
wr_en_i = 0;
wr_data_i = 0;
rd_en_i = 0;
// Reset the FIFO
#10;
rst_n_rd_i = 1;
rst_n_wr_i = 1;
// Write data to FIFO
#10;
wr_en_i = 1;
wr_data_i = 32'hA5A5A5A5;
#14;
wr_en_i = 0;
// Write another data to FIFO
#10;
wr_en_i = 1;
wr_data_i = 32'h5A5A5A5A;
#14;
wr_en_i = 0;
// Read data from FIFO
#10;
rd_en_i = 1;
#10;
rd_en_i = 0;
// Read another data from FIFO
#10;
rd_en_i = 1;
#10;
rd_en_i = 0;
// Finish simulation
#100;
$finish;
end
endmodule