一、核心思想
FIFO即先进先出,不能随机读写随即地址的数据,只能挨个读写。异步fifo是指读写时钟不同,读和写分别在各自的时钟域下,适用于不同时钟域下的数据传输。缓冲等。
二、读写时序
1、写操作
当写信号有效且fifo非满就写到写地址对应的位
2、读操作
当读信号有效且fifo非空既可以进行读地址对应数据
三、设计难点
1、如何判断FIFO的空满
- 根据网上的例子,常规方法都是在读写地址指针加一个额外的状态位。当读或写够一轮状态位就取反。
- 当写地址指针与读地址指针低八位完全相等,而写地址领先读地址一轮,则表示写满了。(二进制情况下)
- 当写地址指针与读地址指针低八位完全相等,而读地址追上了写地址,则表示读空了。(二进制与格雷码情况下)
- 但是将二进制转换成格雷码后判断满的标志则为,格雷码状态下高两位相反,其余相同
2、如何解决不同时钟下两个地址指针的比较,降低亚稳态
- 利用格雷码进行地址指针的判断:因为格雷码每加1,只有一位在变。
- 加两级寄存器,将读地址转换为格雷码再同步到写时钟域下,判断fifo是否为满;将写地址转化为格雷码同步到读时钟域下,判断fifo是否为空。
四、代码
fifo_async.v
module fifo_async#(parameter dwidth = 8,awidth = 4)(
input wclk ,
input rclk ,
input alrst ,//全局复位 读写指针归0,数据归0
input wr_req ,
input rd_req ,
input [dwidth-1:0] wr_data ,
output reg [dwidth-1:0] rd_data ,
output empty ,
output full ,
output [awidth:0] cnt
);
reg [awidth:0] wr_addr_p;//9位的写地址指针
reg [awidth:0] rd_addr_p;//9位的读地址指针
reg [awidth:0] rd_addr_p0;
reg [awidth:0] rd_addr_p1;
wire [awidth-1:0] wr_addr ;//8位写地址
wire [awidth-1:0] rd_addr ;//8位读地址
wire [awidth:0] wr_addr_p_gray;
reg [awidth:0] wr_addr_p_gray0;//同步到读时钟域下的格雷码写地址指针
reg [awidth:0] wr_addr_p_gray1;
wire [awidth:0] rd_addr_p_gray;//同步到写时钟域下的读地址指针
reg [awidth:0] rd_addr_p_gray0;
reg [awidth:0] rd_addr_p_gray1;
wire [awidth:0] fifo_depth ;
reg [dwidth-1:0] fifo_ram[{(awidth){1'b1}}:0];
integer i ;
//fifo_depth
assign fifo_depth = {(awidth){1'b1}}+1'b1;//计算出fifo的深度,以便后面计算
//wr_addr_p
always @(posedge wclk or posedge alrst)begin
if(alrst)begin
wr_addr_p <= 1'b0;
end
else if(wr_req&&!full)begin//当写请求有效且非满就可以进行写操作
wr_addr_p <= wr_addr_p + 1'b1;
end
else
wr_addr_p <= wr_addr_p;
end
//rd_addr_p
always @(posedge rclk or posedge alrst)begin
if(alrst)begin
rd_addr_p <= 1'b0;
end
else if(rd_req&&!empty)begin
rd_addr_p <= rd_addr_p + 1'b1;//当读请求有效且非空就可以进行读操作
end
else
rd_addr_p <= rd_addr_p;
end
//wr_addr
assign wr_addr = wr_addr_p[awidth-1:0];//把低位给地址
//rd_addr
assign rd_addr = rd_addr_p[awidth-1:0];//把低位给地址
//格雷码转换
assign wr_addr_p_gray = (wr_addr_p>>1)^wr_addr_p;
assign rd_addr_p_gray = (rd_addr_p>>1)^rd_addr_p;
//将读指针同步到写指针域下来进行full判断
always @(posedge wclk or posedge alrst)begin
if(alrst)begin
rd_addr_p_gray0 <= 'b0;
rd_addr_p_gray1 <= 'b0;
end
else
rd_addr_p_gray0 <= rd_addr_p_gray;
rd_addr_p_gray1 <= rd_addr_p_gray0;
end
//将写指针同步到读指针域下来进行empty判断
always @(posedge rclk or posedge alrst)begin
if(alrst)begin
wr_addr_p_gray0 <= 1'b0;
wr_addr_p_gray1 <= 1'b0;
end
else
wr_addr_p_gray0 <= wr_addr_p_gray;
wr_addr_p_gray1 <= wr_addr_p_gray0;
end
//将读指针同步到写时钟下
always @(posedge wclk or posedge alrst)begin
if(alrst)begin
rd_addr_p0 <= 1'b0;
rd_addr_p1 <= 1'b0;
end
else
rd_addr_p0 <= rd_addr_p;
rd_addr_p1 <= rd_addr_p0;
end
//在读时钟域下计算写指针与读指针的差即为数据量
assign cnt = wr_addr_p - rd_addr_p1;
//当写领先读一轮,则说明满了;当读赶上写,则说明空了。
assign full = (wr_addr_p_gray=={~rd_addr_p_gray1[awidth-:2],rd_addr_p_gray1[awidth-2:0]})?1:0;
assign empty = (rd_addr_p_gray==wr_addr_p_gray1)?1:0;
//wr_data
always @(posedge wclk or posedge alrst)begin
if(alrst)begin
for(i=0;i<=fifo_depth;i=i+1)
fifo_ram[i] <= {(dwidth){1'b0}};
end
else if(wr_req&&!full)begin
fifo_ram[wr_addr] <= wr_data;
end
end
//rd_data
always @(posedge rclk or posedge alrst)begin
if(alrst)begin
rd_data <= 1'b0;
end
else if(rd_req&&!empty)begin
rd_data <= fifo_ram[rd_addr];
end
end
endmodule
fifo_async_tb.v
`timescale 1ns/1ps
module fifo_async_tb();
reg tb_wclk ;
reg tb_rclk ;
reg tb_alrst ;
reg tb_wr_req ;
reg tb_rd_req ;
reg [7:0] tb_wr_data ;
wire [7:0] tb_rd_data ;
wire tb_empty ;
wire tb_full ;
wire [4:0] tb_cnt ;
fifo_async u_fifo_async(
.wclk (tb_wclk ),
.rclk (tb_rclk ),
.alrst (tb_alrst ),
.wr_req (tb_wr_req ),
.rd_req (tb_rd_req ),
.wr_data (tb_wr_data ),
.rd_data (tb_rd_data ),//数据量
.empty (tb_empty ),
.full (tb_full ),
.cnt (tb_cnt )
);
defparam u_fifo_async.dwidth = 8,
u_fifo_async.awidth = 4;
parameter WR_CLOCK_CYCLE = 20,//50Mhz的写时钟
RD_CLOCK_CYCLE = 10;//100Mhz的读时钟
initial tb_wclk = 1'b0;//初始写化时钟
always #(WR_CLOCK_CYCLE/2) tb_wclk = ~tb_wclk;
initial tb_rclk = 1'b1;//初始读化时钟
always #(RD_CLOCK_CYCLE/2) tb_rclk = ~tb_rclk;
integer j=0;
initial begin
tb_alrst = 1'b0;
tb_wr_req = 1'b0;
tb_rd_req = 1'b0;
tb_wr_data = 8'd0;
#(20*WR_CLOCK_CYCLE);
tb_alrst = 1'b1;
#(20*WR_CLOCK_CYCLE);
tb_alrst = 1'b0;
repeat(10)begin
for(j=0;j<10;j = j+1)begin
#2;
tb_wr_req = {$random};
tb_wr_data = {$random}%255;
#(WR_CLOCK_CYCLE*2);
end
for(j=0;j<20;j = j+1)begin
#2;
tb_rd_req = {$random};
#(RD_CLOCK_CYCLE*2);
end
end
$stop;
end
endmodule
五、总结
1、异步时钟下的数据交互和对比很容易产生亚稳态,在这里先将二进制转换为格雷码在进行跨时钟域同步,并且是加了两级寄存器来进行同步,能很好得消除亚稳态。
2、当二进制最高位相反,其余位相同时,其对应的格雷码是高两位相反,其余相同。这一点要注意。!!!
3、仿真还是不太靠谱,可能会有隐藏的问题没看出来。
4、tb文件写的不太合理,当fifo的空满标志位拉高时,还在送数据进去。
5、数据余量那边算的不太对,应该用打拍后的格雷码回转成二进制再进行计算的,后续会改正---------2021.6.20