异步FIFO的实现

异步FIFO是verilog中常见的设计,通常用于不同时钟域下的数据同步。

在实现 FIFO 时,无论是同步 FIFO 还是异步 FIFO ,通常会通过双口 RAM ( Dual Port RAM )并添加一些必要的逻辑来实现。双口 RAM的设计如下:

cpp 复制代码
//双口RAM,注意没有读写同步,可能会发生对同一地址的读写冲突问题
//使用时一口仅用于读,另一口仅用于写

module full_dp_ram
#(
	parameter DW = 8,       //数据位宽
	parameter AW = 4,       //地址位宽
	parameter SZ = 2**AW    //数据深度
)
(
	input           clk_a,
	input           wen_a,
	input           ren_a,
	input  [AW-1:0] addr_a,
	input  [DW-1:0] wdata_a,
	output [DW-1:0] rdata_a,
	input           clk_b,
	input           wen_b,
	input           ren_b,
	input  [AW-1:0] addr_b,
	input  [DW-1:0] wdata_b,
	output [DW-1:0] rdata_b
);
	reg [DW-1:0] mem [SZ-1:0];
	reg [DW-1:0] q_a;
	always @ (posedge clk_a) begin
		if (wen_a) begin
			mem[addr_a] <= wdata_a;
		end
		if (ren_a) begin
			q_a <= mem[addr_a];
		end
	end
	reg [DW-1:0] q_b;
	always @ (posedge clk_b) begin
		if (wen_b) begin
			mem[addr_b] <= wdata_b;
		end
		if (ren_b) begin
			q_b <= mem[addr_b];
		end
	end
	assign rdata_a = q_a;
	assign rdata_b = q_b;
endmodule

在异步FIFO的框图中,只需要加入读、写控制逻辑即可。在写逻辑中,用于产生写地址和写满信号; 在读逻辑中,用于产生读地址和读空信号。 读写控制逻辑还需要受到读写使能信号的控制。

空: 读空,读地址追上写地址;

满: 写满,写地址追上读地址。

问题来了: 怎么判地址断追上了呢? 如果地址相等那应该是追上了,即 raadr == waddr 或者 wddr == raddr 。 如果按照这种判断,显然这两个地址追上对方的判断是等效的,无法区分出来到底是写追上读还是读追上写。

因此一种方式是可以考虑: 使用 1 个标志位 flag 来额外指示写追上读还是读追上写。

以一个 4 深度的 FIFO 实例来说明, 4 深度原本需要 2 bit 的读写地址,现在扩展成 3 bit 。

使用低 2 位来进行双口 RAM 的地址索引,高位用于判断空满。 对于空信号,可以知道当 FIFO 里没有待读出的数据时产生。 也就是说,此时读追上了写,把之前写的数据刚刚全部都出,读地址和写地址此时指向相同的位置,即raddr == waddr

对于写满信号,当写入后还没被读出的数据恰好是 FIFO 深度的时候,产生满信号,即写地址 - 读地址 = FIFO 深度 = 4 。 对照下图可以发现,此时对于双口 RAM 的 2 bit 的地址来说,读写地址一致; 对于最高位来所,写是 1 而读是 0 。

再考虑下图所示的一种情况,写入待读出的数据仍然是 4 个,此时也是 4 深度的 FIFO 已经满了。 读写地址的低位相同,高位是写 0 读 1 。

异步FIFO设计代码如下所示:

cpp 复制代码
//利用双口RAM实现异步FIFO

module async_fifo
#(
	parameter DW = 8,        //数据宽度
	parameter AW = 4         //数据深度
)
(
	input           wclk,      
	input           rclk,  
	input           wrstn,
	input           rrstn,   
	input           wen,  
	input [DW-1:0]  wdata,  
	output          wfull,   //写满信号
	input           ren,  
	output [DW-1:0] rdata,
	output          rempty   //读空信号
);

	reg [AW:0] waddr;
	reg [AW:0] raddr;

	//sync_w2r
	wire [AW:0] wptr = waddr ^ {1'b0, waddr[AW:1]}; //转换为Gray Code
	reg  [AW:0] w2r_wptr1, w2r_wptr2;
	always @ (posedge rclk or negedge rrstn) begin  //用读时钟来同步写端信号
		if (!rrstn) begin
			w2r_wptr1 <= 0;
			w2r_wptr2 <= 0;
		end
		else begin
			w2r_wptr1 <= wptr;
			w2r_wptr2 <= w2r_wptr1;
		end
	end

	//sync_r2w
	wire [AW:0] rptr = raddr ^ {1'b0, raddr[AW:1]};  //将二进制转换为Gray Code
	reg  [AW:0] r2w_rptr1, r2w_rptr2;
	always @ (posedge wclk or negedge wrstn) begin   //用写时钟来同步读端信号
		if (!wrstn) begin
			r2w_rptr1 <= 0;
			r2w_rptr2 <= 0;
		end
		else begin
			r2w_rptr1 <= rptr;
			r2w_rptr2 <= r2w_rptr1;
		end
	end

	//status
	assign rempty = (w2r_wptr2 == rptr);    
	assign wfull  = (wptr == {~r2w_rptr2[AW:AW-1], r2w_rptr2[AW-2:0]});  //写指针等于读指针最高位取反时表示满

	wire wr_flag = !wfull & wen;     //FIFO非满且写使能
	wire rd_flag = !rempty & ren;    //FIFO非空且读使能

	always @ (posedge wclk or negedge wrstn) begin
		if (!wrstn)
			waddr <= 0;
		else if (wr_flag)
			waddr <= waddr + 1'b1;
		else
			waddr <= waddr;
	end

	always @ (posedge rclk or negedge rrstn) begin
		if (!rrstn)
			raddr <= 0;
		else if (rd_flag)
			raddr <= raddr + 1'b1;
		else
			raddr <= raddr;
	end


	full_dp_ram #(
			.DW          (DW),
			.AW          (AW)
		) ram (
			.clk_a       (wclk),
			.wen_a       (wr_flag),
			.ren_a       (1'b0),                //A口仅用于写
			.addr_a      (waddr[AW-1:0]),
			.wdata_a     (wdata),
			.rdata_a     (),
			.clk_b       (rclk),
			.wen_b       (1'b0),                //B口仅用于读
			.ren_b       (rd_flag),
			.addr_b      (raddr[AW-1:0]),
			.wdata_b     (0),
			.rdata_b     (rdata)
		);

endmodule

由于涉及异步时钟,因此需要读端到写端信号的同步和写端到读端的信号同步。异步FIFO在这里每个口只用作一种用途(读或写)。

相关推荐
whik11942 小时前
FPGA开发中的团队协作:构建高效协同的关键路径
fpga开发
南棱笑笑生2 小时前
20250117在Ubuntu20.04.6下使用灵思FPGA的刷机工具efinity刷机
fpga开发
我爱C编程4 小时前
基于FPGA的BPSK+costas环实现,包含testbench,分析不同信噪比对costas环性能影响
fpga开发·verilog·锁相环·bpsk·costas环
移知14 小时前
备战春招—数字IC、FPGA笔试题(2)
fpga开发·数字ic
楠了个难1 天前
以太网实战AD采集上传上位机——FPGA学习笔记27
笔记·学习·fpga开发
博览鸿蒙1 天前
FPGA工程师有哪些?(设计、验证与应用)
fpga开发
Major_pro1 天前
Xilinx FPGA :开发使用及 Tips 总结
fpga开发
Major_pro2 天前
Quartus:开发使用及 Tips 总结
fpga开发
一条九漏鱼2 天前
消除抖动模块code
fpga开发
cckkppll2 天前
FPGA 时钟约束
fpga开发