简介
异步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
手撕代码
框图如下
双口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由于时钟域的不同,计数器需要用到格雷码