FPGA手撕代码——异步FIFO

简介

异步FIFO只的是,将数据用一个时钟写入缓存区,再用另一个时钟,从同一个缓存区里读出,实现信号安全可靠的从一个时钟域到另一个时钟域。FIFO实现的方法有很多种,本文主要参考《Simulation and Synthesis Techniques for Asynchronous
FIFO Design Clifford E. Cummings, Sunburst Design, Inc》进行设计、回顾、巩固

多比特信号跨时钟域

同步FIFO指针

在设计中,有单bit信号跨时钟域处理,一般会使用打两拍,或者握手信号来处理;多bit信号跨时钟域,我们一般会用到FIFO来实现。
FIFO又可以分为同步和异步;同步FIFO的设计中,FIFO的空满信号会用一个计数器来计数,写入使计数器增加,读出使计数器减少,当计数器达到预设值就是写满,计数器达到零值时,就是读空。但是在两个时钟域中,这种写法是很难实现的,因为两个异步时钟对同一个计时器进行增减是不合理的,而我们大多数时候会用到异步FIFO。

异步FIFO指针

我们考虑三种情况,意味着写满或者读空(读写时,两个指针互相追逐,然后追平了)
第一种,reset状态下,读写指针相等,这是显而易见的;
第二种,读写指针相等时,读指针追上了写指针,意味着最后一个数据被读走,FIFO空;
第三种,写指针(已经在跑第二圈,并且追上了读指针)追上读指针,意味着FIFO被写满了,所有的数据已经装满了FIFO
解决方法
举例:一个深度为16的FIFO,定义读指针变量reg [3+1:0] rd_pionter,写指针变量reg [3+1:0] wr_pointer。注意此处的+1,理论上一个深度为16的FIFO,只需要4bit就能遍历整个内存,但是此处的+1我们用来区分上述第二和第三种情况;
多的这一个bit就可以表示写指针是否跑了第二圈,空信号就是两个指针相等(最高位一样,都在同一圈),满信号就是写指针的最高位(wr_pointer[4]为1,写指针已经在第二圈了)和读指针的最高位不同
reset : rd_pointer == 0 ; wr_pointer == 0
empty : rd_pointer == wr_pointer
full : {~wr_pointer[4], wr_pointer[3:0]} == rd_pointer
在这里插入图片描述

格雷码计数器

关于格雷码的介绍有很多,为什么在异步FIFO使用格雷码也可以看看这篇文章.
总结就是格雷码可以在跨时钟域中保持fifo空满信号准确
二进制转格雷码:
nbit二进制:Bn-1
对应的格雷码为Gn-1
最高位保留 : Gn-1 == Bn-1
其余各位 : Gi = Bi+1 ^ Bi , i = 0, 1, … , n-2

module bin2gray 
#(
	parameter WIDTH = 4
)
(
	input wire [WIDTH - 1 : 0] B,
	output wire [WIDTH - 1 : 0] G
);
assign G = B ^ (B >> 1);
endmodule

手撕代码

框图如下
RTL抗体
双口ram用于存储数据,读写端的二进制计数分别通过二进制转格雷码后打两拍到另一个时钟域,以此来判断空满
整个fifo分为6个模块
fifo_top.v : 模块顶层,实例化各个子模块
fifo_mem.v : 双口ram,存储数据
sync_w2r.v : 同步器,写指针打两拍同步到读时钟域
sync_r2w.v : 同步器,读指针打两拍同步到写时钟域
rptr_empty.v : 读指针和空标志产生
wptr_full.v: 写指针和满标志产生
直接贴代码,主要注意写满信号判断的时候,是格雷码之间的比较,不是二进制,二进制判断满和上面一样,格雷码就是 {~wr_pointer[n:n-1], wr_pointer[n-2:0]} == rd_pointer

module fifo_top
#(
	parameter D_WIDTH = 8,//数据位宽
	parameter A_WIDTH = 4//地址位宽
)
(
	input							clk_wr,
	input							rst_n_wr,
	input							wr_en,
	input							clk_rd,
	input							rst_n_rd,
	input							rd_en,

	input	[D_WIDTH - 1:0]			wr_data,
	output	[D_WIDTH - 1:0]			rd_data,

	output							empty,
	output							full
);
assign G = B ^ (B >> 1);
endmodule
module fifo_mem
#(
	parameter D_WIDTH = 8,//数据位宽
	parameter A_WIDTH = 4//地址位宽
)
(
	input								wclk,
	input								wr_en,
	
	input		[A_WIDTH - 1:0]	waddr,
	input		[A_WIDTH - 1:0]	raddr,
	input								full,

	input		[D_WIDTH - 1:0]	wdata,
	output	[D_WIDTH - 1:0]	rdata
);
localparam D_DEPTH = 1 << A_WIDTH;

reg [D_WIDTH -1 :0] mem [0 : D_DEPTH - 1];

always@ (posedge wclk)
	if (!full & wr_en) mem[waddr] <= wdata;
	
assign rdata = mem[raddr];

endmodule

module sync_r2w
#(
	parameter							A_WIDTH = 4

)
(
	input							wclk,
	
	input							wrst_n,
	input	[A_WIDTH:0]		rptr,
	output reg[A_WIDTH:0]		wq2_rptr
);

reg [A_WIDTH:0] wq1_rptr;

always@ (posedge wclk or negedge wrst_n)
	if (!wrst_n)
		{wq2_rptr, wq1_rptr} <= 0;
	else
		{wq2_rptr, wq1_rptr} <= {wq1_rptr, rptr};

endmodule

module sync_w2r
#(
	parameter							A_WIDTH = 4

)
(
	input							rclk,
	
	input							rrst_n,
	input	[A_WIDTH:0]		wptr,
	output reg[A_WIDTH:0]		rq2_wptr
);

reg [A_WIDTH:0] rq1_wptr;

always@ (posedge rclk or negedge rrst_n)
	if (!rrst_n)
		{rq2_wptr, rq1_wptr} <= 0;
	else
		{rq2_wptr, rq1_wptr} <= {rq1_wptr, wptr};

endmodule

module rptr_empty
#(
	parameter							A_WIDTH = 4

)
(
	input							rclk,
	input							rrst_n,
	
	input	[A_WIDTH:0]		rq2_wptr,
	input							rd_en,
	
	output[A_WIDTH:0]		raddr,
	output[A_WIDTH:0]		rptr,
	output reg empty
);

reg [A_WIDTH:0] rbin;
wire [A_WIDTH:0] rbin_next;
reg [A_WIDTH:0] rgray;
wire [A_WIDTH:0] rgray_next;

//mem地址
always@ (posedge rclk or negedge rrst_n)
begin
	if (!rrst_n)
		rbin <= 0;
	else
		rbin <= rbin_next;
end
assign rbin_next = rbin + (rd_en & (!empty));

assign raddr = rbin[A_WIDTH-1:0];

//格雷码
assign rgray_next = rbin_next ^ (rbin_next >> 1);
always@ (posedge rclk or negedge rrst_n)
begin
	if (!rrst_n)
		rgray <= 0;
	else
		rgray <= rgray_next;
end
assign rptr = rgray;
//空信号
always@ (posedge rclk or negedge rrst_n)
begin
	if (!rrst_n)
		empty <= 0;
	else
		empty <= (rq2_wptr == rgray_next);
end


endmodule

module wptr_full
#(
	parameter							A_WIDTH = 4

)
(
	input							wclk,
	input							wrst_n,
	
	input	[A_WIDTH:0]		wq2_rptr,
	input							wr_en,
	
	output[A_WIDTH:0]		waddr,
	output[A_WIDTH:0]		wptr,
	output reg full
);

reg [A_WIDTH:0] wbin;
wire [A_WIDTH:0] wbin_next;
reg [A_WIDTH:0] wgray;
wire [A_WIDTH:0] wgray_next;

//mem地址
always@ (posedge wclk or negedge wrst_n)
begin
	if (!wrst_n)
		wbin <= 0;
	else
		wbin <= wbin_next;
end
assign wbin_next = wbin + (wr_en & (!full));

assign waddr = wbin[A_WIDTH-1:0];

//格雷码
assign wgray_next = wbin_next ^ (wbin_next >> 1);
always@ (posedge wclk or negedge wrst_n)
begin
	if (!wrst_n)
		wgray <= 0;
	else
		wgray <= wgray_next;
end
assign wptr = wgray;
//空信号
always@ (posedge wclk or negedge wrst_n)
begin
	if (!wrst_n)
		full <= 0;
	else
		full <= (wgray_next == {~wq2_rptr[A_WIDTH:A_WIDTH-1],wq2_rptr[A_WIDTH-2:0]});//注意此处是格雷码比较
end


endmodule

仿真

0-255写数据
写时钟比读时钟快:
在这里插入图片描述

读比写快:
在这里插入图片描述

可以看到各个信号都是预期值

总结

异步fifo由于时钟域的不同,计数器需要用到格雷码

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一匹狼呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值