异步FIFO-1

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 触发器的建立时间和保持时间
  1. 建立时间(Tsu:set up time)
    是指在触发器的时钟信号上升沿到来以前,数据稳定不变的时间,如果建立时间不够,数据将不能在这个时钟上升沿被稳定的打入触发器,Tsu就是指这个最小的稳定时间
  2. 保持时间(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 格雷码
  1. 优点:可解决多bit跨时钟域的亚稳态问题
  2. 局限:连续改变的数据,且数据量为2的N次幂
1.2.6.3.2 打拍
  1. 优点:预防亚稳态的方法是将输入信号(单bit信号打拍,也就是在要使用的时钟域下,将信号寄存

单比特信号从慢速时钟域同步到快速时钟域

使用打两拍的方式消除亚稳态。

例如,对写满的判断,将读指针同步到写时钟域,读clk比写clk慢(慢速时钟域同步到快速时钟域)

rx(读)是相对于时钟域sys_clk(写clk)的异步信号
rx_reg1是rx在时钟域sys_clk打一拍(寄存一次、可以理解为延迟一个时钟周期 )
rx_reg2是rx在时钟域sys_clk打两拍(寄存一两次、可以理解为延迟两个时钟周期)
可以看到rx_reg1可能还存在低概率的亚稳态现象,当然rx_reg2虽然在示意图里是稳定的,不过实际过程中也仍然存在亚稳态发生的概率。
第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为 70%~80%左右,第二级寄存器可以稳定输出的概率为 99%左右。

  1. 局限:不能应用于多bit

在跨时钟域CDC多bit问题上没有使用打拍的方式
假设我们对一个2bit计数器打拍做同步,会有怎么样的结果呢?
假设一个数从11-00发生变化,出现亚稳态,可能的结果11-01,11-00,11-10,11-11,二级寄存器的输出的值就有一拍可能为这些值,显然同步后的数据都是不可预期的。在实际使用中,延时的任何情况都是有可能的,显然这会导致逻辑不可预期。
当bit数更多,可能的结果也就更多。这也是为什么多bit不可以使用打拍进行同步的原因。

1.2.6.3.3 握手
  1. 优点:

本质是负反馈,通俗来讲,就是先将被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. 局限:需要的时序开销很大

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值