异步FIFO
1.序言
(1)什么是FIFO
FIFO(First in,First out),先入先出缓冲器,对数据起缓冲作用,常用在数据的跨时钟域传输。
(2)FIFO分类
FIFO由数据存取电路和外围的控制电路构成,通过外围控制电路将需要写入的数据写入存储器件,并在需要时将数据读出。
写入数据操作和读出数据操作分别在写数据时钟和读数据时钟的控制下进行。当这两个时钟为同一个时,我们称之为同步FIFO;当这两个时钟不是同一个时,我们称之为异步FIFO。(异步fifo涉及到跨时钟域数据传输问题,设计更为复杂)
2.异步FIFO
(1)序言
本文实现的是口异步fifo,具有异步读写数据功能。在输入和输出设备各自的时钟驱动下,在使能信号有效时,对数据进行写入和读出,并保证输入设备先写入的数据在读出端先读出。
(2)接口说明(异步fifo读写操作分别位于不同的时钟域)
端口名 | I/O | 描述 |
---|---|---|
端口名 | I/O | 描述 |
wclk | input | 写数据时钟,所有写数据操作在此时钟域下进行 |
wrst | input | 写数据复位,写数据操作的复位信号 |
wdata | input | 写入的数据 |
winc | input | 写入数据使能信号 |
wfull | output | 存储空间写满反馈信号 |
rclk | input | 读数据时钟,所有读数据操作在此时钟域下进行 |
rrst | input | 读数据复位,读数据操作的复位信号 |
rdata | output | 读出的数据 |
rinc | input | 读出数据使能信号 |
rempty | output | 存储空间读空反馈信号 |
(3)代码实现
A)序言
FIFO由双端口RAM存储器件和外围控制电路构成:
a)双端口RAM存储器件由存储空间配置和读写操作电路两部分实现;
b)外围控制电路由读写使能信号产生(wenc,renc)、读写操作地址产生(waddr,raddr)、读写操作地址指针产生(wptr_g,rptr_g)、地址跨时钟传输模块(wptr_r2w,rptr_r2w)和写满/读空信号产生(wfull,rempty)五部分实现。
(本文采用高位扩展法后续地址跨时钟传输模块会详细介绍)
B)存储空间配置
作用:根据需要存取数据的位宽和深度分配一个存储空间,用来存储写入的数据。
注意:为了适应不同的数据位宽和存储深度,我们通常将位宽和深度参数化,使其可配置。即在调用此fifo函数时,通过传参实现不同数据位宽和存储深度的fifo。需要注意的是,fifo存储深度通常给的参数是10进制,就需要根据10进制数计算所需要的二进制存储空间,使用系统函数**$clog2**,比如当存储深度为16时,我们所需要的数据地址位宽为log216-1,所以定义数据位宽:
input [$clog2(DEPTH)-1:0] waddr;
实现:
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
C)读写操作电路
作用:当读写使能信号有效时,根据当前需要读出或写入数据的地址,进行读写数据操作。
实现:
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
D)读写使能信号产生
作用:提供给读写操作电路读写数据操作有效信号(wenc,renc);
注意:
a)写入数据操作时:数据能够写入需要满足两个条件,第一个是写入数据有效(即写入数据使能信号winc为高);第二个是当前存储空间还有空位置,即存储空间没有被写满(full为低)。
b)读出数据操作时,数据能够读出需要满足两个条件:第一个是读出数据使能信号rinc为高;第二个是当前存储空间还有没有被读出的数据,即存储空间没有被读空(empty为低)。
实现:
//读写数据使能信号
wire wenc;
wire renc;
assign wenc = (winc && (!wfull))?1'b1:1'b0;
assign renc = (rinc && (!rempty))?1'b1:1'b0;
E)读写操作地址及指针产生
作用:提供读写数据的读写地址;
注意:
a)当读写操作有效信号有效,就会按照当前地址读写数据,说明地址是提前准备好的,所以读写一个数据的同时,需要准备好下一时刻的数据地址;
时序图:(以写数据操作为例)
b)为了方便产生写满信号和读空信号,将读/写数据地址扩展了一位(高位扩展法)构成地址指针,高位扩展法在下一步详细说明;
c)所以实际的数据地址应该为地址指针除去最高位。
d)写入数据和读出数据分别由对应时钟域的时钟和复位信号控制;
实现:
//读写操作地址产生
reg [$clog2(DEPTH):0] waddr_ptr_b;
reg [$clog2(DEPTH):0] raddr_ptr_b;
wire [$clog2(DEPTH)-1:0] waddr;
wire [$clog2(DEPTH)-1:0] raddr;
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
waddr_ptr_b <= 'd0;
else if(wenc)
waddr_ptr_b <= waddr_ptr_b + 1'b1;
else
waddr_ptr_b <= waddr_ptr_b;
end
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)
raddr_ptr_b <= 'd0;
else if(wenc)
raddr_ptr_b <= raddr_ptr_b + 1'b1;
else
raddr_ptr_b <= raddr_ptr_b;
end
//实际的读写操作地址
assign waddr = waddr_ptr_b[$clog2(DEPTH)-1:0];
assign raddr = raddr_ptr_b[$clog2(DEPTH)-1:0];
F)格雷编码和高位扩展法(产生写满和读空信号)
(a)跨时钟域数据传输与格雷编码
跨时钟域数据传输:在产生写满和读空信号时,需要比对当前写数据地址和读数据地址,但是异步fifo中这两个数据处于不同的时钟域(由不同时钟触发)。两个时钟域的数据不能直接比较,因为两个时钟互不相关,不知道哪个快哪个慢、两者之间的相位差,直接比较有可能出现亚稳态。所以我们在比较前会将地址转换到需要操作的时钟域下。这里我们使用的方法是######打两拍####,对于不同类型的数据有不同的跨时钟域数据传输方法,这里不再赘述;
亚稳态:在异步时序逻辑电路中,在进行跨时钟域同步时,由于不同时钟时钟域的时钟存在相位差,这个相位差是变化的,会导致采样时不能满足建立时间和保持时间,使电路输出处于一个未知的状态(可能是一个中间值或者是一个震荡的值);
格雷编码:格雷码的特点是相邻两个状态,只有一位发生变化,跨时钟域采样时,也降低了亚稳态的概率;所以我们进行写满和读空状态判断的地址指针,采用的是格雷编码,所以要将######地址指针变换为格雷码####;
(b)高位扩展法(以数据存储深度16为例)
数据写入和读出方式:
存储深度为16,对应16个存储单元,对应地址为0~15,需要用一个4位宽的二进制数进行指示。
在写入数据时,会从最小地址存储单元到最大地址存储单元依次写入数据,当写完最后一个存储单元后,又会从最小地址存储单元开始(前提是这个单元数据已经被读出了);
在读出数据时,会从最先写入数据的存储单元往后依次读出数据;
当读和写操作地址指针指向单元,对应着两种情况,第一种是写数据操作已经进行了一周,即写数据地址已经超过读数据地址一周了,即写满状态;第二种是所有被写入的数据均已经被读出去了,即读空状态;所以为了区别指示这两种状态,将地址指针拓展了一位。
格雷码地址指针表如下图所示:
写满和读空状态判断依据,总结表中第一列和第三列数据可以得到(格雷码相差地址指针相差一轮,即16):
当写数据地址指针,和读数据地址指针,最高两位不同,低三维相同时,为写满状态(写地址指针超前读地址指针一周);
当写数据地址指针和读数据地址指针完全相同时,为读空状态(读数据地址已经赶上了写数据地址);
©实现
步骤1:二进制编码变格雷码
方法:从最右边一位起,依次将每一位与左边一位异或(XOR),作为对应格雷码该位的值,最左边一位不变(0与任何值异或,这个值等于本身);
//二进制编码变格雷编码(最高位0异或任何数等于本身)
wire [$clog2(DEPTH):0] waddr_ptr_g;
wire [$clog2(DEPTH):0] raddr_ptr_g;
assign waddr_ptr_g = (waddr_ptr_b>>1) ^ waddr_ptr_b;
assign raddr_ptr_g = (raddr_ptr_b>>1) ^ raddr_ptr_b;
步骤2:跨时钟域数据传输(在被比较的时钟域打两拍)
//使用被比较的时钟域的时钟,打两拍
reg [$clog2(DEPTH):0] wptr_w2r_reg;
reg [$clog2(DEPTH):0] wptr_w2r;
reg [$clog2(DEPTH):0] rptr_r2w_reg;
reg [$clog2(DEPTH):0] rptr_r2w;
//写数据格雷码地址同步到读数据时钟域
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)begin
wptr_w2r_reg <= 'd0;
wptr_w2r <= 'd0;
end
else begin
wptr_w2r_reg <= waddr_ptr_g;
wptr_w2r <= wptr_w2r_reg;
end
end
//读数据格雷码地址同步到写数据时钟域
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)begin
rptr_r2w_reg <= 'd0;
rptr_r2w <= 'd0;
end
else begin
rptr_r2w_reg <= raddr_ptr_g;
rptr_r2w <= rptr_r2w_reg;
end
end
步骤3:写满和读空信号产生
assign wfull = ({(~rptr_r2w[$clog2(DEPTH):($clog2(DEPTH)-1)]),rptr_r2w[($clog2(DEPTH)-2):0]}==waddr_ptr_g)?1'b1:1'b0;
assign rempty = (wptr_w2r == raddr_ptr_g)?1'b1:1'b0;
G)两级D触发器的引入对fifo性能的影响
a)现象描述
写满信号判断时,需要将当前写数据地址指针和使用写数据时钟域的时钟打了两拍的读数据地址指针进行比较,即当前时刻的写数据地址指针和两个时钟之前的读数据地址指针进行比较;
读空信号判断时,需要将当前读数据地址指针和使用读数据时钟域的时钟打了两拍的写数据地址指针进行比较,即当前时刻的读数据地址指针和两个时钟之前的写数据地址指针进行比较;
b)结果
会降低fifo的效率,不会影响fifo的功能;
c)原因
写满信号判断时:这种情况一定是此时写数据时钟快于读数据时钟,读数据地址指针为两个写数据时钟之前的读数据地址指针,而实际的读数据地址指针是两个写数据时钟后的读数据地址指针(即多读了两拍写数据时钟的数据),所以这是一种将满的状态,把将满状态判断为已经写满状态,这时fifo的利用率就下降了也变慢了,但是这种情况能提前给前端设备提供写满信号,让其停止传输数据,用少传数据的方式来避免多传数据,因为多传数据,数据未存入存储空间,数据会丢失。
读空信号判断时:这种情况一定是此时读数据时钟快于写数据时钟,写数据地址指针为两个读数据时钟之前的写数据地址指针,而实际的写数据地址指针是两个读数据时钟后的写数据地址指针(即多写了两拍读数据时钟的数据),所以这是一种将空的状态,把将空状态判断为已经读空状态,这时fifo的利用率就下降了也变慢了,但是这种情况能提前给后端设备提供读空信号,让其停止读取数据,用少读数据的方式来避免多读数据,因为多读数据,有的数据被读了多次或者有的空间根本没有数据,输出数据会出现很多多余数据或乱码。少读的数据只是暂时不读,数据仍然在存储空间,后续存入新的数据后,这些数据会被正常读出。
3.异步FIFO完整代码
(1)完整代码(前面的说明也是基于此的)
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata = 0 //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
/***************************************AFIFO*****************************************/
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
//读写数据使能信号
wire wenc;
wire renc;
assign wenc = (winc && (!wfull))?1'b1:1'b0;
assign renc = (rinc && (!rempty))?1'b1:1'b0;
//读写操作地址产生
reg [$clog2(DEPTH):0] waddr_ptr_b;
reg [$clog2(DEPTH):0] raddr_ptr_b;
wire [$clog2(DEPTH)-1:0] waddr;
wire [$clog2(DEPTH)-1:0] raddr;
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
waddr_ptr_b <= 'd0;
else if(wenc)
waddr_ptr_b <= waddr_ptr_b + 1'b1;
else
waddr_ptr_b <= waddr_ptr_b;
end
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)
raddr_ptr_b <= 'd0;
else if(renc)
raddr_ptr_b <= raddr_ptr_b + 1'b1;
else
raddr_ptr_b <= raddr_ptr_b;
end
//实际的读写操作地址
assign waddr = waddr_ptr_b[$clog2(DEPTH)-1:0];
assign raddr = raddr_ptr_b[$clog2(DEPTH)-1:0];
//二进制编码变格雷编码(最高位0异或任何数等于本身)
wire [$clog2(DEPTH):0] waddr_ptr_g;
wire [$clog2(DEPTH):0] raddr_ptr_g;
assign waddr_ptr_g = (waddr_ptr_b>>1) ^ waddr_ptr_b;
assign raddr_ptr_g = (raddr_ptr_b>>1) ^ raddr_ptr_b;
// 牛客网代码需要修改,时域打一拍
// reg [$clog2(DEPTH):0] waddr_ptr_g;
// reg [$clog2(DEPTH):0] raddr_ptr_g;
// always@(posedge wclk or negedge wrstn)begin
// if(!wrstn)
// waddr_ptr_g <= 'd0;
// else
// waddr_ptr_g <= (waddr_ptr_b>>1) ^ waddr_ptr_b;
// end
// always@(posedge rclk or negedge rrstn)begin
// if(!rrstn)
// raddr_ptr_g <= 'd0;
// else
// raddr_ptr_g <= (raddr_ptr_b>>1) ^ raddr_ptr_b;
// end
//使用被比较的时钟域的时钟,打两拍
reg [$clog2(DEPTH):0] wptr_w2r_reg;
reg [$clog2(DEPTH):0] wptr_w2r;
reg [$clog2(DEPTH):0] rptr_r2w_reg;
reg [$clog2(DEPTH):0] rptr_r2w;
//写数据格雷码地址同步到读数据时钟域
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)begin
wptr_w2r_reg <= 'd0;
wptr_w2r <= 'd0;
end
else begin
wptr_w2r_reg <= waddr_ptr_g;
wptr_w2r <= wptr_w2r_reg;
end
end
//读数据格雷码地址同步到写数据时钟域
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)begin
rptr_r2w_reg <= 'd0;
rptr_r2w <= 'd0;
end
else begin
rptr_r2w_reg <= raddr_ptr_g;
rptr_r2w <= rptr_r2w_reg;
end
end
//写满和读空信号产生模块(组合逻辑测试先######)
assign wfull = ({(~rptr_r2w[$clog2(DEPTH):($clog2(DEPTH)-1)]),rptr_r2w[($clog2(DEPTH)-2):0]}==waddr_ptr_g)?1'b1:1'b0;
assign rempty = (wptr_w2r == raddr_ptr_g)?1'b1:1'b0;
//模块例化
dual_port_RAM #(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
)
u_dual_port_RAM(
.wclk (wclk),
.wenc (wenc),
.waddr (waddr),//深度对2取对数,得到地址的位宽。
.wdata (wdata), //数据写入
.rclk (rclk),
.renc (renc),
.raddr (raddr),//深度对2取对数,得到地址的位宽。
.rdata (rdata) //数据输出
);
endmodule
(2)牛客网通过提交代码
注意:
牛客网代码相较于前面分析的代码,操作有一个地方不同:就是将二进制地址指针转换为格雷码地址指针时,用的是时序逻辑电路,相当于延迟了一拍;
`timescale 1ns/1ns
/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
/***************************************AFIFO*****************************************/
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
//读写数据使能信号
wire wenc;
wire renc;
assign wenc = (winc && (!wfull))?1'b1:1'b0;
assign renc = (rinc && (!rempty))?1'b1:1'b0;
//读写操作地址产生
reg [$clog2(DEPTH):0] waddr_ptr_b;
reg [$clog2(DEPTH):0] raddr_ptr_b;
wire [$clog2(DEPTH)-1:0] waddr;
wire [$clog2(DEPTH)-1:0] raddr;
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
waddr_ptr_b <= 'd0;
else if(wenc)
waddr_ptr_b <= waddr_ptr_b + 1'b1;
else
waddr_ptr_b <= waddr_ptr_b;
end
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)
raddr_ptr_b <= 'd0;
else if(renc)
raddr_ptr_b <= raddr_ptr_b + 1'b1;
else
raddr_ptr_b <= raddr_ptr_b;
end
//实际的读写操作地址
assign waddr = waddr_ptr_b[$clog2(DEPTH)-1:0];
assign raddr = raddr_ptr_b[$clog2(DEPTH)-1:0];
// //二进制编码变格雷编码(最高位0异或任何数等于本身)
// wire [$clog2(DEPTH):0] waddr_ptr_g;
// wire [$clog2(DEPTH):0] raddr_ptr_g;
// assign waddr_ptr_g = (waddr_ptr_b>>1) ^ waddr_ptr_b;
// assign raddr_ptr_g = (raddr_ptr_b>>1) ^ raddr_ptr_b;
// 牛客网代码需要修改,时域打一拍
reg [$clog2(DEPTH):0] waddr_ptr_g;
reg [$clog2(DEPTH):0] raddr_ptr_g;
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)
waddr_ptr_g <= 'd0;
else
waddr_ptr_g <= (waddr_ptr_b>>1) ^ waddr_ptr_b;
end
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)
raddr_ptr_g <= 'd0;
else
raddr_ptr_g <= (raddr_ptr_b>>1) ^ raddr_ptr_b;
end
//使用被比较的时钟域的时钟,打两拍
reg [$clog2(DEPTH):0] wptr_w2r_reg;
reg [$clog2(DEPTH):0] wptr_w2r;
reg [$clog2(DEPTH):0] rptr_r2w_reg;
reg [$clog2(DEPTH):0] rptr_r2w;
//写数据格雷码地址同步到读数据时钟域
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)begin
wptr_w2r_reg <= 'd0;
wptr_w2r <= 'd0;
end
else begin
wptr_w2r_reg <= waddr_ptr_g;
wptr_w2r <= wptr_w2r_reg;
end
end
//读数据格雷码地址同步到写数据时钟域
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)begin
rptr_r2w_reg <= 'd0;
rptr_r2w <= 'd0;
end
else begin
rptr_r2w_reg <= raddr_ptr_g;
rptr_r2w <= rptr_r2w_reg;
end
end
//写满和读空信号产生模块(组合逻辑测试先######)
assign wfull = ({(~rptr_r2w[$clog2(DEPTH):($clog2(DEPTH)-1)]),rptr_r2w[($clog2(DEPTH)-2):0]}==waddr_ptr_g)?1'b1:1'b0;
assign rempty = (wptr_w2r == raddr_ptr_g)?1'b1:1'b0;
//模块例化
dual_port_RAM #(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
)
u_dual_port_RAM(
.wclk (wclk),
.wenc (wenc),
.waddr (waddr),//深度对2取对数,得到地址的位宽。
.wdata (wdata), //数据写入
.rclk (rclk),
.renc (renc),
.raddr (raddr),//深度对2取对数,得到地址的位宽。
.rdata (rdata) //数据输出
);
endmodule
4.异步FIFO深度计算
###注意:这个问题很重要,很多笔试、面试都会问##
(1)前提
保证在写入数据速率快于读出数据速率时,在突发传输的情况下,fifo 不会溢出(需要存储的数据量超出fifo的存储容量)。
(2)不采用握手机制
理论上,我们使用fifo缓存数据是基于握手机制,即当fifo被写满时,fifo向前端输入设备反馈写满信号,此时输入设备就会停止传输数据,直到fifo不处于写满状态时才会继续传入数据;但是,fifo深度计算考虑的是极端情况——不采用握手机制,即输入设备以突发的形式不断地传输数据,保证在整个数据传输过程中,fifo不会溢出。
(3)突发传输
原因:当写入数据速率一直快于读出数据速率时,如果数据一致连续不断地传,不采用握手机制,fifo一定会溢出,所以我们考虑的情况是突发传输,这与实际应用也更符合;
突发传输:相邻存储单元连续进行数据传输的方式,连续传输的周期数就是突发长度(数据个数有限)。
(4)问题的根本
只要保证在写入数据速率快于读出数据速率的前提下,以突发形式传输数据(连续传输有限个数据),在突发过程中不会出现fifo溢出的情况,即(三大表达式):
fifo被填满的时间 >= 突发传输的时间;
突发传输的时间 = 数据量/写入数据时钟速率;
FIFO被写满的时间 = FIFO深度/(写入数据速率 - 读出数据速率);
(5)例题
A)连续读写数据
写数据速率:fw = 60MHz;
读数据频率:fr = 50MHz;
突发传输长度:burst_l = 120;
解:
突发传输时间=120/60M;
fifo被写满的时间=fifo_deep/(60M-50M);
fifo被写满的时间 >= 突发传输时间;
解得:fifo_deep >= 20,所以fifo_deep = 20;
B)读写数据有空闲周期(前提实际的写数据速率大于实际的读数据速率)
写数据速率:fw = 40MHz
读数据速率:fs = 50MHz
突发长度:burst_l = 120
写入数据间隔一个时钟周期;
读出数据间隔3个时钟周期;
解:
实际的写数据速率:fw = 40MHz/2
实际的读数据速率:fs = 50MHz/4
突发传输时间=120/(40M/2);
fifo被写满的时间=fifo_deep/(40M/2-50/4);
fifo被写满的时间 >= 突发传输时间;
解得:fifo_deep >= 45,所以fifo_deep = 45;
5.仿真
(1)test_bench(为3(1)的testbench,牛客网的程序我没有写测试代码)
`timescale 1ns / 1ps
module asyn_fifo_tb();
reg [7:0] wdata ;
reg winc, wclk, wrstn ;
wire wfull ;
wire [7:0] rdata ;
reg rinc, rclk, rrstn ;
wire rempty ;
parameter WIDTH = 8 ;
parameter DEPTH = 16 ;
localparam CYCLE = 20 ;
localparam CYCLE1 = 80 ;
//时钟信号产生:写时钟远大于读时钟
initial begin
wclk = 0;
wdata = 0;
forever
#(CYCLE/2)
wclk=~wclk;
end
initial begin
rclk = 0;
forever
#(CYCLE1/2)
rclk=~rclk;
end
//复位信号
initial begin
wrstn = 1;
#2;
wrstn = 0;
#(CYCLE*3);
wrstn = 1;
end
initial begin
rrstn = 1;
#2;
rrstn = 0;
#(CYCLE*3);
rrstn = 1;
end
//模块例化
asyn_fifo #(
.WIDTH(WIDTH ),
.DEPTH(DEPTH )
)
u_asyn_fifo(
.wclk (wclk),
.rclk (rclk),
.wrstn (wrstn),
.rrstn (rrstn),
.winc (winc),
.rinc (rinc),
.wdata (wdata),
.wfull (wfull),
.rempty (rempty),
.rdata (rdata )
);
//读写使能信号产生
initial begin
winc=0;
#5 winc=1;
end
initial begin
rinc=0;
#5 rinc=1;
end
// #5 out_mem_rinc =1;
// always @(posedge wclk or negedge wrst_n)begin
// if(wrst_n==1'b0)begin
// winc <= 0;
// rinc <= 0;
// end
// else begin
// winc <= $random % (2^8);
// rinc <= $random % (2^8);
// end
// end
// always @(posedge rclk or negedge rrst_n)begin
// if(rrst_n==1'b0)begin
// rinc <= 0;
// end
// else begin
// rinc <= $random % (2^8);
// end
// end
// always@(*)begin
// if(winc == 1)
// in_mem_wdata= $random % (2^8);
// else
// in_mem_wdata = 0;
// end
//随机数的生成,产生写入的随机数
always #30 wdata= $random % (2^8) ;
endmodule
仿真结果:
仿真说明:
仿真的情况:写数据速率为读数据速率的4倍,fifo存储数据位宽为8,深度为16;
注意:写入的数据是使用系统函数$ramdoms产生,所以可能有的连续两三个写时钟周期值不变,即连续写入了多个相同的值,这也是为什么读出的数据连续几个时钟读出的值相同;
在仿真时序的后期:因为写数据速率快于读数据速率,所以后存储空间被写满了,只有读出一个数据才能写入一个数据;