异步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在这里每个口只用作一种用途(读或写)。

相关推荐
仲南音16 小时前
Xilinx FPGA——时钟IP核MMCM、PLL
fpga开发
I'm a winner1 天前
基于Xilinx FPGA的LVDS高速串行通信系统(三)--数据训练及握手机制【文末源码】
fpga开发
upper20201 天前
coremark移植坑之-nostartfiles -nostdlib参数
学习·fpga开发·coremark移植·riscv设计
Eloudy2 天前
Vivado 纯命令行构建 FPGA 项目教程
fpga开发
Rambo.xia2 天前
AXI跨时钟域与数据一致性——CDC FIFO深度不够、写后读冒险、多Master仲裁、乱序返回,出了Bug根本查不出来
fpga开发·bug
upper20202 天前
FPGA部署卷积神经网络识别MINST图片
fpga开发·卷积神经网络·minst·minst识别·部署神经网络·神经网络识别图片
你是我的解忧王子2 天前
嵌入式 ADC采样 原始数据分析上层(平均值 波动率 正态分布 线性补偿 系统误差)
fpga开发·数据挖掘·数据分析
9527华安3 天前
FPGA纯verilog代码ISP图像处理培训教程,基于IMX214,提供工程源码+视频教程+FPGA开发板
图像处理·fpga开发·isp·imx214·mipi
千寻xun3 天前
二、实战篇-NVME SSD控制之ZYNQ实现(六)-读写NVME SSD硬盘数据
fpga开发·nvme·nvme ssd
千寻xun3 天前
二、实战篇-NVME SSD控制之ZYNQ实现(三)
fpga开发·nvme ssd·nvme协议