1. 参考
1.1 SPEC
- 同步FIFO是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。异步FIFO是指读写时钟不一致,读写时钟是互相独立的。
- 同步 FIFO 常用于同步时钟的数据缓存,异步 FIFO 常用于跨时钟域的数据信号的传递。
- 在现代逻辑设计中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步FIFO是这个问题的一种简便、快捷的解决方案,使用异步 FIFO 可以在两个不同时钟系统之间快速而方便地传输实时数据。
- 例如时钟域 A 下的数据 data1 传递给异步时钟域 B,当 data1 为连续变化信号时,如果直接传递给时钟域 B 则可能会导致收非所送的情况,即在采集过程中会出现包括亚稳态问题在内的一系列问题,使用异步 FIFO 能够将不同时钟域中的数据同步到所需的时钟域中。
- 空标志:对于双时钟 FIFO 又分为读空标志 rdempty 和写空标志 wrempty。FIFO 已空或将要空时由 FIFO的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO中读出数据而造成无效数据的读出。
- 满标志:对于双时钟 FIFO 又分为读满标志 rdfull 和写满标志 wrfull。FIFO已满或将要写满时由 FIFO的状态电路送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出。
1.2 跨时钟域问题解决思路
1.2.1 从同步FIFO到异步FIFO
高位扩展法:将地址指针高位扩展1位,来作为指示位。通过对比读写指针的值(也就是比对读写指针的位置)来判断读空或者写满。需要解决读写指针的跨时钟域问题,需要将其同步到同一时钟域。
rd_ptr <= rd_ptr + 1'd1;
wr_ptr <= wr_ptr + 1'd1;
//当所有位相等时,读指针追到到了写指针,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;
假设深度为8的FIFO
- 写了8个数据,又读了8个数据 ||| 对应写指针为0111,读指针0111 → 读空W-R=0
- 写了11个数据,又读了2个数据 ||| 对应写指针为1010,读指针0010 → 写满W-R-1=深度
1.2.2 合适的异步FIFO设计
异步FIFO设计的关键点是产生合适的“写满”和“读空”信号,那么何谓“合适”?
该报的时候没报算合适吗?当然不算合适。不该报的时候报了算不算合适?答案是算。
假设一个深度为8的FIFO
- 在写到第6个数据的时候就报了“写满”,会引起什么后果?答案是不会造成功能错误,只会造成性能损失(2%),大不了FIFO的深度我少用一点点就是的。事实上这还可以算是某种程度上的保守设计(安全)。
- 在读到还剩2个数据的时候就报了“读空”,会引起什么后果?答案是不会造成功能错误,只会造成性能损失(2%),大不了我先不读了,等数据多了再读就是的。事实上这还可以算是某种程度上的保守设计(安全)。
- 功能错误的情况:多读W-R<0;多写W-R-1>深度
1.2.3 同步到同一时钟域 哪个时钟域
1.2.3.1 第三方时钟域
- 比如说实际上读写时针都指向4(且最高位相同),那么这种情况实际上是出现了读空的情况。 但是同步到第三方时钟域后,假设这个时间是T,那么经过T时间后,由于读写时钟不一致,原来的读写时针增加(也可能不变)的量是不一致。可能写指针成了6,而读指针变成了8(读时钟比写时钟快),那么在这种情况下FIFO就不会报“读空”,从而造成功能错乱。所以该种方法不可取。
1.2.3.2 同步到写时钟域:
- 读指针同步到写时钟域需要时间T,在经过T时间后,可能原来的读指针会增加或者不变【可能在T时间内有新的读操作,FIFO DUT中,读操作为读地址+1】,也就是说同步后的读指针一定是小于等于原来的读指针的【假设,R同步=4 ≤R实际=5/6/7…】。写指针也可能发生变化,但是写指针本来就在这个时钟域,所以是不需要同步的,也就意味着进行对比的写指针就是真实的写指针。
- 进行写满的判断:也就是写指针超过了同步后的读指针一圈。但是原来的读指针是大于等于同步后的读指针的,所以实际上这个时候写指针其实是没有超过读指针一圈的,也就是说这种情况是“假写满”。
写了11个数据,又读了2个数据 → 对应写指针为1010,读指针0010 → 写满 11-2-1=8 W同步=11,R同步=2 ≤ R实际=3/4… → 同步11-2-1=8写满 → 实际11-3-1=7没写满 → “假写满”
- 进行读空的判断:也就是同步后的读指针追上了写指针。但是原来的读指针是大于等于同步后的读指针的,所以实际上这个时候读指针实际上是超过了写指针。这种情况意味着已经发生了“读空”,却仍然有错误数据读出。所以这种情况就造成了FIFO的功能错误。
W同步=8,R同步=8≤ R实际=9/10… → 同步8-8=0 读空 → 实际8-9<0 多读 → 功能错误
1.2.3.3 同步到读时钟域:
- 写指针同步到读时钟域需要时间T,在经过T时间后,可能原来的读指针会增加或者不变【可能在T时间内有新的写操作,FIFO DUT中,写操作为写地址+1】,也就是说同步后的写指针一定是小于等于原来的写指针的【假设,W同步=4 ≤W实际=5/6/7…】。读指针也可能发生变化,但是读指针本来就在这个时钟域,所以是不需要同步的,也就意味着进行对比的读指针就是真实的读指针。
- 进行写满的判断:也就是同步后的写指针超过了读指针一圈。但是原来的写指针是大于等于同步后的写指针的,所以实际上这个时候写指针已经超过了读指针不止一圈,这种情况意味着已经发生了“写满”,却仍然数据被覆盖写入。所以这种情况就造成了FIFO的功能错误。
R同步=2,W同步=11≤ W实际=12/13… → 同步11-2-1=8 写满 → 实际12-2-1<0 多写 → 功能错误
- 进行读空的判断:也就是读指针追上了同步后的指针。但是原来的写指针是大于等于同步后的写指针的,所以实际上这个时候读指针其实还没有追上写指针,也就是说这种情况是“假读空”。
R同步=8,W同步=8 ≤ W实际=9/10… → 同步8-8=0读空→ 实际9-8>0 没读空 → “假读空”
1.2.3.4 同步时钟域
- “写满”的判断:需要将读指针同步到写时钟域,再与写指针判断
- “读空”的判断:需要将写指针同步到读时钟域,再与读指针判断
1.2.4 怎么同步时钟域
1.2.4.1 格雷码解决亚稳态
- 格雷码是一种非权重码,每次变化位数只有一位,不会读到错误的数,只会读到历史的指针,就不会出现指针乱飞的情况,这就有效的避免了在跨时钟域情况下亚稳态问题发生的概率。举个例子,二进制多位变化出现错误的可能性加大,7(0111)跳转到8(1000),4位都会发生变化,所以发生亚稳态的概率就比较大。而格雷码的跳转就只有一位(从0100–1100,仅第四位发生变化)会发生变化,有效地减小亚稳态发生的可能性。
- 格雷码和二进制码的相互转化
//二进制转格雷码
gray = (bin >> 1) ^ bin//格雷码转二进制
bin[data_width - 1] = gray[data_width - 1]; //最高位直接相等
for(i = 0; i <= data_width-2; i = i + 1)
begin: gray //需要有名字
assign bin[i] = bin[i + 1] ^ gray[i];
end
- Verilog和SystemVerilog不允许使用变量索引范围进行部分选择,因此上述示例的代码虽然在概念上是正确的,但不会编译。
//实现格雷码到二进制转换的方法是对有效格雷码比特进行异或,并填充0
bin[0] = gray[3] ^ gray[2] ^ gray[1] ^ gray[0] ; // gray>>0
bin[1] = 1'b0 ^ gray[3] ^ gray[2] ^ gray[1] ; // gray>>1
bin[2] = 1'b0 ^ 1'b0 ^ gray[3] ^ gray[2] ; // gray>>2
bin[3] = 1'b0 ^ 1'b0 ^ 1'b0 ^ gray[3] ; // gray>>3
//SV模型
module gray2bin #(parameter SIZE = 4)
(output logic [SIZE-1:0] bin,
input logic [SIZE-1:0] gray);
always_comb
for (int i=0; i<SIZE; i++)
bin[i] = ^(gray>>i); //位异或
endmodule
1.4.2.2 格雷码如何判断空满状态
- 二进制的指针:指针向高位拓展一位,这是为了判断写指针是否超过读指针一圈。然后通过对比除了最高位的其余位来判断读写指针是否重合。
- 格雷码的指针:格雷码是镜像对称,看最高位和次高位是否相等
当读的最高位和次高位与写的相同,其余位相同认为是读空
当写的最高位和次高位与读的不同,其余位相同认为是写满 - 格雷码只适用于地址空间为2的幂次方的FIFO
1.2.5 总结
解决异步FIFO跨时钟域问题,同时消除亚稳态影响
判断空满的计数器:
高位扩展一位,采用格雷码
判断是否写满,把读指针同步到写时间域,当最高位和次高位不同,其余位相同认为是写满
判断是否读空,把写指针同步到读时间域,当最高位和次高位相同,其余位相同认为是读空
1.2.6 亚稳态扩展
1.2.6.1 触发器的建立时间和保持时间
- 建立时间(Tsu:set up time)
是指在触发器的时钟信号上升沿到来以前,数据稳定不变的时间,如果建立时间不够,数据将不能在这个时钟上升沿被稳定的打入触发器,Tsu就是指这个最小的稳定时间 - 保持时间(Th:hold time)
是指在触发器的时钟信号上升沿到来以后,数据稳定不变的时间,如果保持时间不够,数据同样不能被稳定的打入触发器,Th就是指这个最小的保持时间
1.2.6.2 亚稳态
如果数据传输中不满足触发器的Tsu和Th不满足,就可能产生亚稳态。
原本属于时钟域A的信号a,需要传入到另一时钟域B来对其进行操作,这一操作则称之为跨时钟域,对应Clock Domain Crossing,CDC。由于双方时钟频率、相位等的差异,导致原本属于时钟域A下的同步信号a变成了一个时钟域B下的异步信号。异步信号是有概率无法满足触发器的建立时间要求和保持时间要求。
1.2.6.3 亚稳态解决方法
1.2.6.3.1 格雷码
- 优点:可解决多bit跨时钟域的亚稳态问题
- 局限:连续改变的数据,且数据量为2的N次幂
1.2.6.3.2 打拍
- 优点:预防亚稳态的方法是将输入信号(单bit信号)打拍,也就是在要使用的时钟域下,将信号寄存
单比特信号从慢速时钟域同步到快速时钟域
例如,对写满的判断,将读指针同步到写时钟域,读clk比写clk慢(慢速时钟域同步到快速时钟域)使用打两拍的方式消除亚稳态。
rx(读)是相对于时钟域sys_clk(写clk)的异步信号
rx_reg1是rx在时钟域sys_clk打一拍(寄存一次、可以理解为延迟一个时钟周期 )
rx_reg2是rx在时钟域sys_clk打两拍(寄存一两次、可以理解为延迟两个时钟周期)
可以看到rx_reg1可能还存在低概率的亚稳态现象,当然rx_reg2虽然在示意图里是稳定的,不过实际过程中也仍然存在亚稳态发生的概率。
第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为 70%~80%左右,第二级寄存器可以稳定输出的概率为 99%左右。
- 局限:不能应用于多bit
在跨时钟域CDC多bit问题上没有使用打拍的方式
假设我们对一个2bit计数器打拍做同步,会有怎么样的结果呢?
假设一个数从11-00发生变化,出现亚稳态,可能的结果11-01,11-00,11-10,11-11,二级寄存器的输出的值就有一拍可能为这些值,显然同步后的数据都是不可预期的。在实际使用中,延时的任何情况都是有可能的,显然这会导致逻辑不可预期。
当bit数更多,可能的结果也就更多。这也是为什么多bit不可以使用打拍进行同步的原因。
1.2.6.3.3 握手
- 优点:
本质是负反馈,通俗来讲,就是先将被CDC信号展宽,展宽后将其同步到目的时钟域,在目的时钟域生成指示信号,该指示信号用来指示此时信号已经被目的时钟域接收,然后将指示信号反馈到源时钟域(反馈过程),源时钟域接收到这个反馈信号后将被CDC信号拉低,从而确定了展宽长度,也通过”发送–反馈–操作“这一握手过程完成了一次CDC传输。
- 典型的握手过程来进行CDC:
源时钟域aclk下的信号adt1信号是要进行CDC的信号;
adt1先是在源时钟域aclk下被展宽,然后通过两级同步器被同步到目的时钟域bclk下,分别为bq1_dat,bq2_dat;
bq2_dat作为指示信号(反馈信号,也可以通过bq1_dat和bq2_dat来生成新的指示信号)又被反馈到了目的时钟域aclk下,并进行同步,分别为aq1_dat,aq2_dat;
aq2_dat的拉高则说明反馈信号的同步完成,此时可以将adt1拉低(结束展宽过程);
adt1拉低(结束展宽过程)后表示一次CDC操作结束。
- 局限:需要的时序开销很大
1.3 RTL设计
1. http://t.csdn.cn/8s46d
//异步FIFO
module async_fifo
#(
parameter DATA_WIDTH = 'd8 , //FIFO位宽
parameter DATA_DEPTH = 'd16 //FIFO深度
)
(
//写数据
input wr_clk , //写时钟
input wr_rst_n , //低电平有效的写复位信号
input wr_en , //写使能信号,高电平有效
input [DATA_WIDTH-1:0] data_in , //写入的数据
//读数据
input rd_clk , //读时钟
input rd_rst_n , //低电平有效的读复位信号
input rd_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; //读地址指针,二进制
reg [$clog2(DATA_DEPTH) : 0] rd_ptr_g_d1; //读指针格雷码在写时钟域下同步1拍
reg [$clog2(DATA_DEPTH) : 0] rd_ptr_g_d2; //读指针格雷码在写时钟域下同步2拍
reg [$clog2(DATA_DEPTH) : 0] wr_ptr_g_d1; //写指针格雷码在读时钟域下同步1拍
reg [$clog2(DATA_DEPTH) : 0] wr_ptr_g_d2; //写指针格雷码在读时钟域下同步2拍
//wire define
wire [$clog2(DATA_DEPTH) : 0] wr_ptr_g; //写地址指针,格雷码
wire [$clog2(DATA_DEPTH) : 0] rd_ptr_g; //读地址指针,格雷码
wire [$clog2(DATA_DEPTH) - 1 : 0] wr_ptr_true; //真实写地址指针,作为写ram的地址
wire [$clog2(DATA_DEPTH) - 1 : 0] rd_ptr_true; //真实读地址指针,作为读ram的地址
//地址指针从二进制转换成格雷码
assign wr_ptr_g = wr_ptr ^ (wr_ptr >> 1);
assign rd_ptr_g = rd_ptr ^ (rd_ptr >> 1);
//读写RAM地址赋值
assign wr_ptr_true = wr_ptr [$clog2(DATA_DEPTH) - 1 : 0]; //写RAM地址等于写指针的低DATA_DEPTH位(去除最高位)
assign rd_ptr_true = rd_ptr [$clog2(DATA_DEPTH) - 1 : 0]; //读RAM地址等于读指针的低DATA_DEPTH位(去除最高位)
//写操作,更新写地址
always @ (posedge wr_clk or negedge wr_rst_n) begin
if (!wr_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
//!!!将读指针的格雷码同步到写时钟域,来判断是否写满
always @ (posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n)begin
rd_ptr_g_d1 <= 0; //寄存1拍
rd_ptr_g_d2 <= 0; //寄存2拍
end
else begin
rd_ptr_g_d1 <= rd_ptr_g; //寄存1拍
rd_ptr_g_d2 <= rd_ptr_g_d1; //寄存2拍
end
end
//读操作,更新读地址
always @ (posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n)
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 rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n)begin
wr_ptr_g_d1 <= 0; //寄存1拍
wr_ptr_g_d2 <= 0; //寄存2拍
end
else begin
wr_ptr_g_d1 <= wr_ptr_g; //寄存1拍
wr_ptr_g_d2 <= wr_ptr_g_d1; //寄存2拍
end
end
//!!!更新指示信号
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign empty = ( wr_ptr_g_d2 == rd_ptr_g ) ? 1'b1 : 1'b0;
//当高位相反且其他位相等时,写指针超过读指针一圈,FIFO被写满
//同步后的读指针格雷码高两位取反,再拼接上余下位
assign full = ( wr_ptr_g == { ~(rd_ptr_g_d2[$clog2(DATA_DEPTH) : $clog2(DATA_DEPTH) - 1])
,rd_ptr_g_d2[$clog2(DATA_DEPTH) - 2 : 0]})? 1'b1 : 1'b0;
endmodule
2.分模块设计
asyn_fifo.v 是顶层模块,在它里面例化了前 5 个模块。
wptr_full.v 是产生写地址和写指针的模块
rptr_empty.v 是产生读地址和读指针的模块
sync_w2r.v 是将写时钟域传来的指针同步进入读时钟域的模块
sync_r2w.v 是将读时钟域传来的指针同步进入写时钟域的模块
fifomem.v 是存储部分
2.实践
1.RTL设计
1.asyn_fifo.v
顶层模块,在它里面例化了前 5 个模块。
实例化其他组件,将端口预留出来
`timescale 1ns/1ns
module async_fifo
#(
parameter fifo_width =16,
parameter fifo_depth =8,
parameter addr_width =$clog2(fifo_depth)
)
(
input wclk,
input rclk,
input w_en,
input r_en,
input w_rstn,
input r_rstn,
input [fifo_width-1:0]wdata,
output [fifo_width-1:0]rdata,
output full,
output empty
);
wire [addr_width-1:0]w_addr_true; //bin
wire [addr_width-1:0]r_addr_true; //bin
wire [addr_width:0] w_addr_p; //bin
wire [addr_width:0] r_addr_p; //bin
wire [addr_width:0]w_addr_pg; //gray
wire [addr_width:0]r_addr_pg; //gray
wire [addr_width:0]w_addr_d1,w_addr_d2; //gray
wire [addr_width:0]r_addr_d1,r_addr_d2; //gray
fifo_mem fifo_mem_u1
(
.wdata(wdata),
.rdata(rdata),
.wclk(wclk),
.w_en(w_en),
.full(full),
.w_addr_true(w_addr_true),
.r_addr_true(r_addr_true)
);
sync_r2w sync_r2w_u1
(
.wclk(wclk),
.w_rstn(w_rstn),
.r_addr_pg(r_addr_pg),
.w_addr_d2(w_addr_d2)
);
sync_w2r sync_w2r_u1
(
.rclk(rclk),
.r_rstn(r_rstn),
.w_addr_pg(w_addr_pg),
.r_addr_d2(r_addr_d2)
);
w_full w_full_u1
(
.wclk(wclk),
.w_rstn(w_rstn),
.w_en(w_en),
.w_addr_d2(w_addr_d2),
.w_addr_true(w_addr_true),
.w_addr_p(w_addr_p),
.w_addr_pg(w_addr_pg),
.full(full)
);
r_empty r_empty_u1
(
.rclk(rclk),
.r_rstn(r_rstn),
.r_en(r_en),
.r_addr_d2(r_addr_d2),
.r_addr_true(r_addr_true),
.r_addr_p(r_addr_p),
.r_addr_pg(r_addr_pg),
.empty(empty)
);
endmodule
2.fifomem.v
存储部分
是可由写时钟域和读时钟域访问的FIFO存储器缓冲区
该缓冲区很可能是实例化的同步双端口RAM。可以调整其他存储器类型以用作FIFO缓冲器。
`timescale 1ns/1ns
module fifo_mem
#(
parameter fifo_width =16,
parameter fifo_depth =8,
parameter addr_width =$clog2(fifo_depth)
)
(
input [fifo_width-1:0] wdata,
output [fifo_width -1:0]rdata,
input wclk,
input w_en,
input full,
input [addr_width-1:0]w_addr_true,
input [addr_width-1:0]r_addr_true
);
reg [fifo_width-1:0]mem[fifo_width-1:0]; //创建二维数组
assign rdata=mem[r_addr_true]; //读数
always@(posedge wclk)
begin
if(w_en && !full) //满足读允许和没写满条件下可以写入数据
mem[w_addr_true]<=wdata;
end
endmodule
3.sync_w2r.v
将写时钟域传来的指针同步进入读时钟域的模块
`timescale 1ns/1ns
module sync_w2r
#(
parameter fifo_width =16,
parameter fifo_depth =8,
parameter addr_width =$clog2(fifo_depth)
)
(
input rclk,
input r_rstn,
input [addr_width:0]w_addr_pg,
output reg [addr_width:0]r_addr_d2
);
reg [addr_width:0]r_addr_d1;
always@(posedge rclk or negedge r_rstn)
if(!r_rstn)
begin
r_addr_d1<=0;
r_addr_d2<=0;
end
else begin
r_addr_d1<=w_addr_pg; //在读时钟下,写格雷码指针打一拍
r_addr_d2<=r_addr_d1; //打第二拍
end
endmodule
4.sync_r2w.v
将读时钟域传来的指针同步进入写时钟域的模块
`timescale 1ns/1ns
module sync_r2w
#(
parameter fifo_width =16,
parameter fifo_depth =8,
parameter addr_width =$clog2(fifo_depth)
)
(
input wclk,
input w_rstn,
input [addr_width:0]r_addr_pg,
output reg [addr_width:0]w_addr_d2
);
reg [addr_width:0]w_addr_d1;
always@(posedge wclk or negedge w_rstn)
if(!w_rstn)
begin
w_addr_d1<=0;
w_addr_d2<=0;
end
else begin
w_addr_d1<=r_addr_pg; //在写时钟下,读格雷码指针打一拍
w_addr_d2<=w_addr_d1; //打第二拍
end
endmodule
5.wptr_full.v
产生写地址和写指针的模块
`timescale 1ns/1ns
module w_full
#(
parameter fifo_width =16,
parameter fifo_depth =8,
parameter addr_width =$clog2(fifo_depth)
)
(
input wclk,
input w_rstn,
input w_en,
input [addr_width:0]w_addr_d2,
output wire[addr_width-1:0]w_addr_true,
output reg[addr_width:0]w_addr_p,
output wire [addr_width:0]w_addr_pg,
output reg full
);
wire wfull;
//bin2gray二进制计数器地址指针转格雷码
assign w_addr_pg=w_addr_p^(w_addr_p>>1);
assign w_addr_true=w_addr_p[addr_width-1:0]; //实际地址是去掉最高位的计数器地址
always@(posedge wclk or negedge w_rstn)
begin
if(!w_rstn)
w_addr_p<=0;
else if(w_en&&!full)begin
w_addr_p<=w_addr_p+1'b1; //写时钟下,满足写使能和未写满的条件下,计数器地址指针+1
end
end
//判断是否写满
//在r2w模块中,读地址打拍到写时钟下的信号为w_addr_d2
//写满的条件为 写地址和读地址的最高位和次高位不同,其余位相同
//比较的是格雷码地址
assign wfull=((w_addr_d2[addr_width]!=w_addr_pg[addr_width])&&(w_addr_d2[addr_width-1]!=w_addr_pg[addr_width-1])&&(w_addr_d2[addr_width-2:0]==w_addr_pg[addr_width-2:0]))?1:0;
always@(posedge wclk or negedge w_rstn)
begin
if(!w_rstn)begin
full<=0;
end
else
full<=wfull;
end
endmodule
6.rptr_empty.v
产生读地址和读指针的模块
`timescale 1ns/1ns
module r_empty
#(
parameter fifo_width =16,
parameter fifo_depth =8,
parameter addr_width =$clog2(fifo_depth)
)
(
input rclk,
input r_rstn,
input r_en,
input [addr_width:0]r_addr_d2,
output wire [addr_width-1:0]r_addr_true,
output reg [addr_width:0]r_addr_p,
output wire [addr_width:0]r_addr_pg,
output reg empty
);
wire rempty;
//bin2gray
assign r_addr_pg=r_addr_p^(r_addr_p>>1);
assign r_addr_true=r_addr_p[addr_width-1:0];
always@(posedge rclk or negedge r_rstn)
begin
if(!r_rstn)
r_addr_p<=0;
else if(r_en&&!empty)begin
r_addr_p<=r_addr_p+1'b1;
end
end
//判断是否读空
//在w2r模块中,写地址打拍到读时钟下的信号为r_addr_d2
//读空的条件为 写地址和读地址相同
//比较的是格雷码地址
assign rempty=(r_addr_d2==r_addr_pg)?1:0;
always@(posedge rclk or negedge r_rstn)
begin
if(!r_rstn)begin
empty<=1;
end
else
empty<=rempty;
end
endmodule