大家好,又见面了,我是你们的朋友全栈君。
今天看别人的博客研究了一天的异步FIFO,中遇到了很多问题。很多人可能有过这样的经历,当你研究一个东西,可能你当时很清楚你是怎么想的,但是过后就忘记了当时的思路了。因此我写博客的主要目的就是为了回头查阅方便。IC基础可能会写很多篇,本篇异步FIFO就是此系列的第一篇。
一、FIFIO简介 FIFO是一种现先进先出的数据缓冲器,特点是没有外部的读写地址。由于没有外部的地址信号,所以只能顺序的读写,而不能跳读。FIFO的读写是根据满和空信号设计写使能和读使能来写/读FIFO,当FIFO满的时候不可以往里面写、当FIFO空的时候不能读数据。读FIFO时,内部的读指针自动的加一,当写FIFO时写指针自动的加一。 什么是异步FIFO,什么又是同步FIFO? 异步FIFO简单的来说就是读写时钟不相同,同步FIFO就是读写的时钟相同。
二、异步FIFO的用途 1、使用异步FIFO可以在两个不同的时钟域之间快速而方便的传输数据,起到跨时钟域处理的作用。经常用于处理跨时钟域的问题。 2、对于不同宽度的数据接口也可以采用FIFO进行缓冲,如8位输入,16位输出。(注:本文只简介输入输出位宽相同的情况)
三、FIFO的常见参数
- wfull: 满标志, 表示FIFO已经满,不能再写入数据。
- rempty:空标志,表示FIFO已经空,不能再读取数据。
- wclk: 写时钟
- rclk: 读时钟
- winc: 写使能
- rinc: 读使能
- wdata:写数据
- rdata: 读数据
- wrst_n: 写复位 10.rrst_n:读复位
四、读写指针的工作原理
- 读指针:总是指向下一个将要被写入的单元,复位时指向第一个单元。
- 写指针:总是指向当前要被读出的数据,复位时指向第一个单元。
也就是说,复位时读写指针都指向第一个单元。并且向FIFO写入一个数据,写指针加1。从FIFO中读出一个数据,都指针加1。
五、FIFO满空标志的产生 FIFO设计的关键是如何产生可靠的读写指针和满空信号。FIFO读写指针的工作原理如上第四点所述。
那么剩下的就是要讨论如何产生FIFO的满空信号了。FIFO什么时候为空呢?我们来思考一下,假设我从第一个单元写入数据,那么写指针从地址0—>1,读指针不变,此时FIFO中有一个数据。接着我把这个数据读出来,读指针从0—>1。此时写指针为1,读指针也为1,FIFO中没有数据了,因此FIFO为空。从中可以发现,判断FIFO为空很简单,只要读写指针相等就是空。但是事情好像也没那么简单,再想一下,假设一开始就复位,读写指针都在0地址,然后一直王FIFO中写入数据,当写满FIFO的时候,写指针刚好转了一圈回到了0地址,此时读写指针也相等,但是这时候FIFO是满的。因此得到下面的判空和判满条件。
判空:读指针追上写指针的时候,两者相等,为空。 判满:写指针追上读指针的时候,两者相等,为满。
突然发现两者相等的话不是空就是满,区别就是谁追上谁而已了。那么如何来区别是谁追上谁呢?
六、如何判断读写指针相等时,为空还是为满呢?
答案就是在表示读写指针的数据位宽上再加1位来区分是满还是空。比如FIFO的深度位8,那么需要3位二进制数来表地址,则需要再最高之前再加一位,变成4位。一开始读写都是0000,FIFO为空。当写指针增加并越过最后一个存储单元的时候,就将这个最高位取反,变成1000。这时是写地址追上了读地址,FIFO为满。同理,当读地址越过最后一个存储单元的时候把读地址的最高位也取反。可以看到,当最高位相同,并且剩下的位也相同的时候FIFO为空;当最高位不同,并且剩下的位相同时,为满。
七、异步时钟域下如何判断时空还是满?
在上述六中已经解释了如何判断FIFO的满空。但是如果在不同时钟域下,显然需要将读写指针进行同步化才可以进行判断。具体就是在判断空的时候,需要将写地址同步到读时钟域下进行判断。同理,在进行判断满的时候需要将读时钟域中的读指针同步到写时钟域进行判断。
八、使用格雷码来表示地址
其实在读时钟域中读指针的增加仍然是自然二进制,同理在写时钟域中写地址的增加也是按照自然二进制变化的。但是在将读指针发送到写时钟域下进行同步时,如果仍然采用自然二进制,那么就会面临地址同时有多位变化的情况。比如0111->1000,一次就变了四位。在数电的学习中我们知道,这种情况是要尽量避免的,因为这样容易引起亚稳态或者是毛刺(具体是亚稳态还是毛刺我还不太确定)。
但是采用格雷码每次就只有一个位变化。格雷码和自然二进制码如下。图片来自百度百科。
那么问题又来了,采用格雷码又要如何判断满和空呢?首先还是要记住:在读指针和写指针相等的时候进行判断。
举个例子:() 假如T1时刻,读指针的自然二进制为0111,写指针的自然二进制位1000。 简化如下: T1:读:0111(bin) 0100 (grey) 写:1000(bin) 1100(grey) 可以看到,此时此时格雷码 的最高位不同,剩下的都相同。那么可以判为满吗?显然是不行,可以冥想的知道,此时刚刚向FIFO中写入一个数,怎么就满了呢。
因此必须考虑用别的办法来比较。方法就是:
判满:格雷码的最高位和次高为不同,剩下的都同,就是满。 判空:格雷码完全相同,就是空。
二进制如何转换成格雷码:
二进制:10110 对应的格雷码:11101 转换方法:
简单说下就是,最高位不变,剩下的分别与自己左边的进行异或就可以得到当前位。用公式表示就是:
11101 = (10110>>1)^10110; 左移一位,再与自己异或。
九、模块说明
图片来源于网络,侵删。 上图上FIFO设计的总体框图,可以将其分为五个部分,分别如图中的虚线框所示。分别是FIFO判满模块、两个读写指针同步模块、FIFO判空模块。每个子模块的接口如图中所示,下面的程序就是按照这个总体框图来设计的。下面的程序来源于网络,不是我自己写的。可以说这个例子是很好的例子了,模块划分很清楚,非常利于学习。
十、代码和仿真图
代码语言:javascript复制顶层模块
module AsyncFIFO#(
parameter ADDR_SIZE = 4,
parameter DATA_SIZE = 8
)
(
input [DATA_SIZE-1:0] wdata,
input winc,
input wclk,
input wrst_n,
input rinc,
input rclk,
input rrst_n,
output [DATA_SIZE-1:0] rdata,
output wfull,
output rempty
);
wire [ADDR_SIZE-1:0] waddr,raddr;
wire [ADDR_SIZE:0] wptr,rptr,wq2_rptr,rq2_wptr;
sync_r2w #(
.ADDR_SIZE(ADDR_SIZE)
)
I1_sync_r2w(
.wq2_rptr(wq2_rptr),
.rptr(rptr),
.wclk(wclk),
.wrst_n(wrst_n)
);
sync_w2r #(
.ADDR_SIZE(ADDR_SIZE)
)I2_sync_w2r(
.rq2_wptr(rq2_wptr),
.wptr(wptr),
.rclk(rclk),
.rrst_n(rrst_n)
);
DualRAM #(
.ADDR_SIZE(ADDR_SIZE),
.DATA_SIZE(DATA_SIZE)
)I3_DualRAM(
.rdata(rdata),
.wdata(wdata),
.waddr(waddr),
.raddr(raddr),
.wclken(winc),
.wclk(wclk)
);
rptr_empty #(
.ADDR_SIZE(ADDR_SIZE)
)I4_rptr_empty(
.rempty(rempty),
.raddr(raddr),
.rptr(rptr),
.rq2_wptr(rq2_wptr),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n));
wptr_full #(
.ADDR_SIZE(ADDR_SIZE)
)I5_wptr_full(
.wfull(wfull),
.waddr(waddr),
.wptr(wptr),
.wq2_rptr(wq2_rptr),
.winc(winc),
.wclk(wclk),
.wrst_n(wrst_n));
endmodule
代码语言:javascript复制双口RAM模块
module DualRAM #(
parameter DATA_SIZE = 8,//数据位宽
parameter ADDR_SIZE = 4//FIFO地址宽度
)(
input wclken,
input wclk,
input [ADDR_SIZE-1:0] raddr,
input [ADDR_SIZE-1:0] waddr,
input [DATA_SIZE-1:0] wdata,
output [DATA_SIZE-1:0] rdata
);
localparam RAM_DEPTH = 1<<ADDR_SIZE;//RAM深度,1左移4位为16
reg [DATA_SIZE-1:0] mem [0:RAM_DEPTH-1];//开辟内存
always@(posedge wclk) begin
if(wclken==1'b1) begin
mem[waddr] <= wdata;
end
else begin
mem[waddr] <= mem[waddr];//保持
end
end
assign rdata = mem[raddr];//给地址直接出数据
endmodule
代码语言:javascript复制写指针同步到读时钟
module sync_w2r#(
parameter ADDR_SIZE = 4
)
(
input [ADDR_SIZE:0] wptr,
input rclk,
input rrst_n,
output reg [ADDR_SIZE:0] rq2_wptr
);
reg [ADDR_SIZE:0] rq1_wptr;
//D触发器,两级同步
always@(posedge rclk or negedge rrst_n) begin
if(!rrst_n) begin
{rq2_wptr,rq1_wptr} <=0;
end
else begin
{rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
end
end
endmodule
代码语言:javascript复制读指针同步到写时钟
module sync_r2w#(
parameter ADDR_SIZE = 4)
(
input [ADDR_SIZE:0] rptr,
input wclk,
input wrst_n,
output reg [ADDR_SIZE:0] wq2_rptr
);
reg [ADDR_SIZE:0] wq1_rptr;
//D触发器,两级同步
always@(posedge wclk or negedge wrst_n) begin
if(!wrst_n) begin
{wq2_rptr,wq1_rptr} <= 0;
end
else begin
{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
end
end
endmodule
代码语言:javascript复制判空模块
module rptr_empty#(
parameter ADDR_SIZE = 4
)
(
output reg rempty,
output [ADDR_SIZE-1:0] raddr,//输出到RAM的读地址
output reg [ADDR_SIZE:0] rptr,//输出到写时钟域的格雷码
input [ADDR_SIZE:0] rq2_wptr,
input rinc,
input rclk,
input rrst_n
);
reg [ADDR_SIZE:0] rbin;//二进制地址
wire [ADDR_SIZE:0] rgraynext,rbinnext;//二进制和格雷码地址
wire rempty_val;
//----------------------------
//地址逻辑
//----------------------------
always@(posedge rclk or negedge rrst_n)begin
if(!rrst_n)begin//
rbin <=0;
rptr <= 0;
end
else begin //
rbin <= rbinnext;
rptr <= rgraynext;
end
end
//地址产生逻辑
assign rbinnext = !rempty ?(rbin rinc):rbin;
assign rgraynext = (rbinnext>>1)^(rbinnext);
assign raddr = rbin[ADDR_SIZE-1:0];
//FIFO判空
assign rempty_val = (rgraynext==rq2_wptr) ;
always@(posedge rclk or negedge rrst_n)begin
if(!rrst_n)
rempty <= 1'b1;
else begin
rempty <= rempty_val;
end
end
endmodule
代码语言:javascript复制判满模块
module wptr_full#(
parameter ADDR_SIZE = 4
)
(
output reg wfull,
output [ADDR_SIZE-1:0] waddr,
output reg [ADDR_SIZE:0] wptr,
input [ADDR_SIZE:0] wq2_rptr,
input winc,
input wclk,
input wrst_n
);
reg [ADDR_SIZE:0] wbin;
wire [ADDR_SIZE:0] wbinnext;
wire [ADDR_SIZE:0] wgraynext;
wire wfull_val;
always@(posedge wclk or negedge wrst_n) begin
if(!wrst_n)begin
wbin <= 0;
wptr <= 0;
end
else begin
wbin <= wbinnext;
wptr <= wgraynext;
end
end
//地址逻辑
assign wbinnext = !wfull?(wbin winc):wbin;
assign wgraynext = (wbinnext>>1)^wbinnext;
assign waddr = wbin[ADDR_SIZE-1:0];
//判满
assign wfull_val = (wgraynext=={~wq2_rptr[ADDR_SIZE:ADDR_SIZE-1],wq2_rptr[ADDR_SIZE-2:0]});//最高两位取反,然后再判断
always@(posedge wclk or negedge wrst_n)begin
if(!wrst_n)
wfull <=0;
else begin
wfull <= wfull_val;
end
end
endmodule
测试文件:
代码语言:javascript复制测试文件
module test();
parameter DATA_SIZE = 16;
parameter ADDR_SIZE = 16;
reg [DATA_SIZE-1:0] wdata;
reg winc, wclk, wrst_n;
reg rinc, rclk, rrst_n;
wire [DATA_SIZE-1:0] rdata;
wire wfull;
wire rempty;
integer i=0;
AsyncFIFO #(
.ADDR_SIZE(ADDR_SIZE),
.DATA_SIZE(DATA_SIZE)
)
u_fifo (
.rdata(rdata),
.wfull(wfull),
.rempty(rempty),
.wdata (wdata),
.winc (winc),
.wclk (wclk),
.wrst_n(wrst_n),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n)
);
localparam CYCLE = 20;
localparam CYCLE1 = 40;
//ʱÖÓÖÜÆÚ£¬µ¥Î»Îªns£¬¿ÉÔÚ´ËÐÞ¸ÄʱÖÓÖÜÆÚ¡£
//Éú³É±¾µØʱÖÓ50M
initial begin
wclk = 0;
forever
#(CYCLE/2)
wclk=~wclk;
end
initial begin
rclk = 0;
forever
#(CYCLE1/2)
rclk=~rclk;
end
//²úÉú¸´Î»ÐźÅ
initial begin
wrst_n = 1;
#2;
wrst_n = 0;
#(CYCLE*3);
wrst_n = 1;
end
initial begin
rrst_n = 1;
#2;
rrst_n = 0;
#(CYCLE*3);
rrst_n = 1;
end
always @(posedge wclk or negedge wrst_n)begin
if(wrst_n==1'b0)begin
i <= 0;
end
else if(!wfull)begin
i = i 1;
end
else begin
i <= i;
end
end
always @(rempty or rrst_n)begin
if(rrst_n==1'b0)begin
rinc = 1'b0;
end
else if(!rempty)begin
rinc = 1'b1;
end
else
rinc = 1'b0;
end
always@(wfull or wrst_n)begin
if(wrst_n)
winc = 1'b0;
if(!wfull)
winc = 1'b1;
else
winc = 1'b0;
end
always@(*)begin
if(!wfull)
wdata= i;
else
wdata = 0;
end
endmodule
注意:测试文件中的测试时序一定要对,因为我在测试过程中发现有读出来的数据有漏掉的情况,我已经修改了。主要是读写使能一定要用组合逻辑产生,否则时序会对不上,产生错误。
仿真波形:
对于读写位宽不一致、FIFO深度不是2的幂次的FIFO还没有研究,有兴趣的欢迎再下方平论,共同探讨。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/170191.html原文链接:https://javaforall.cn