本文首要对异步 FIFO 规划的重点难点进行剖析,最终给出具体代码。
一、FIFO简略解说
FIFO的实质是RAM, 先进先出
重要参数:fifo深度(简略来说便是需求存多少个数据)
fifo位宽(每个数据的位宽)
FIFO有同步和异步两种,同步即读写时钟相同,异步即读写时钟不相同
同步FIFO用的少,能够作为数据缓存
异步FIFO能够处理跨时钟域的问题,在应用时需依据实践状况考虑好fifo深度即可
本非必须规划一个异步FIFO,深度为8,位宽也是8.
代码是学习Simulation and Synthesis Techniques for Asynchronous FIFO Design Clifford E. Cummings, Sunburst Design, Inc.这篇文章的,百度搜搜很简略找到,虽然是英文的可是写的的确值得研讨。
下面我会对规划的关键进行剖析,也是对自己学习进程的一个总结,希望能和咱们沟通共同进步。
二、规划关键解析
1、读空信号怎么发生?写满信号怎么发生?
读空信号:复位的时分,读指针和写指针持平,读空信号有用(这儿所说的指针其实便是读地址、写地址)
当读指针赶上写指针的时分,写指针等于读指针意味着最终一个数据被读完,此刻读空信号有用
写满信号:当写指针比读指针多一圈时,写指针等于读指针意味着写满了,此刻写满信号有用
咱们会发现 读空的条件是写指针等于读指针,写满的条件也是写指针等于读指针,究竟怎么区别呢?
处理办法:将指针的位宽多界说一位
举个比方阐明:假定要规划深度为 8 的异步FIFO,此刻界说读写指针只需求 3 位(2^3=8)就够用了,
可是咱们在规划时将指针的位宽规划成 4 位,最高位的效果便是区别是读空仍是写满,具体理论 1 如下
当最高位相同,其他位相同认为是读空
当最高位不同,其他位相同认为是写满
留意:理论1试用的是二进制数之间的空满比较判别。
可是这篇文章中确不是这样比较的,而是用的理论2,这儿我解说一下
因为文章在规划中判别是读指针是否等于写指针的时分,用的是读写指针的格雷码方式(为什么用格雷码后边解说),此刻若用上面的理论1就会出问题,因为格雷码是镜像对称的,若只依据最高位是否相同来区别是读空仍是写满是有问题的,概况我会慢慢说,请看图 1
绿色框起来的是0–15的格雷码,用红线将格雷码分为上下两部分
经过调查格雷码相邻位每次只需1位发生改动,且上下两部分,除了最高位相反,其他位全都关于红线镜像对称,
7 –> 8 ,格雷码从 0100 –> 1100 ,只需最高位发生改动其他位相同
6 –> 9 , 格雷码从 0101 –> 1101 , 只需最高位发生改动其他位相同
以此类推,为什么要说镜像对称呢?
试想假如读指针指向 8,写指针指向 7 ,咱们能够知道此刻此刻并不是读空状况也不是写满状况
可是假如在此刻套用理论 1 来判别,看会呈现什么状况,咱们来套一下
7的格雷码与8的格雷码的最高位不同,其他位相同,所以判别出为写满。这就呈现误判了,相同套用在 6 和 9,5 和 10等也会呈现误判。
因而用格雷码判别是否为读空或写满时应运用理论 2,看最高位和次高位是否持平,具体如下:
当最高位和次高位相同,其他位相同认为是读空
当最高位和次高位不同,其他位相同认为是写满
补:理论2这个判别办法适用于用格雷码判别比较空满
在实践规划中假如不想用格雷码比较,就能够运用格雷码将读写地址同步到一个时钟域后再将格雷码再次转化成二进制数再用理论1进行比较就好了。。
图 1
2、由所以异步FIFO的规划,读写时钟纷歧样,在发生读空信号和写满信号时,会涉及到跨时钟域的问题,怎么处理?
跨时钟域的问题:上面咱们现已说到要经过比较读写指针来判别发生读空和写满信号
可是读指针是归于读时钟域的,写指针是归于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,
要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是过错的,因而咱们需求进行同步处理今后仔进行比较
处理办法:两级寄存器同步 + 格雷码
同步的进程有两个:
(1)将写时钟域的写指针同步到读时钟域,将同步后的写指针与读时钟域的读指针进行比较发生读空信号
(2)将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较发生写满信号
同步的思维便是用两级寄存器同步,简略说便是打两拍,信任有点根底的早都纯熟于心,就不再多做解说,不明白的能够看看代码结合了解。
仅仅这样简略的同步就能够了吗?no no no ,可怕的亚稳态还在等着你。
咱们假如直接用二进制编码的读写指针去完结上述的两种同步是不行的,运用格雷码更适宜,为什么呢?
因为二进制编码的指针在跳变的时分有或许是多位数据一同改动,如二进制的7–>8 即 0111 –> 1000 ,在跳变的进程中 4 位悉数发生了改动,这样很简略发生毛刺,例如:
异步FIFO的写指针和读指针分属纷歧起钟域,这样指针在进行同步进程中很简略犯错,比方写指针在从0111到1000跳变时4位一起改动,这样读时钟在进行写指针同步后得到的写指针或许是0000-1111的某个值,总共有2^4个或许的状况,而这些都是不行操控的,你并不能确认会呈现哪个值,那犯错的概率十分大,怎么办呢?到了格雷码发挥效果的时分了,而格雷码的编码特点是相邻位每次只需 1 位发生改动, 这样在进行指针同步的时分,只需两种或许呈现的状况:1.指针同步正确,正是咱们所要的;2.指针同步犯错,举例假定格雷码写指针从000->001,将写指针同步到读时钟域同步犯错,犯错的成果只或许是000->000,因为相邻位的格雷码每次只需一位改动,这个犯错成果实践上也便是写指针没有跳变坚持不变,咱们所关怀的便是这个过错会不会导致读空判别犯错?答案是不会,最多是让空标志在FIFO不是实在空的时分发生,而不会呈现空读的景象。所以gray码确保的是同步后的读写指针即便在犯错的景象下仍然能够确保FIFO功用的正确性。在同步进程中的亚稳态不或许消除,可是咱们只需确保它不会影响咱们的正常作业即可。
3、因为规划的时分读写指针用了至少两级寄存器同步,同步会耗费至少两个时钟周期,势必会使得判别空或满有所推迟,这会不会导致规划犯错呢?
异步FIFO经过比较读写指针进行满空判别,可是读写指针归于不同的时钟域,所以在比较之前需求先将读写指针进行同步处理,
将写指针同步到读时钟域再和读指针比较进行FIFO空状况判别,因为在同步写指针时需求时刻,而在这个同步的时刻内有或许还会写入新的数据,因而同步后的写指针一定是小于或许等于当时实践的写指针,所以此刻判别FIFO为空纷歧定是真空,这样愈加保存,总共不会呈现空读的状况,虽然会影响FIFO的功能,可是并不会犯错,同理将读指针同步到写时钟域再和写指针比较进行FIFO满状况判别,同步后的读指针一定是小于或许等于当时的读指针,所以此刻判别FIFO为满纷歧定是真满,这样更保存,这样能够确保FIFO的特性:FIFO空之后不能持续读取,FIFO满之后不能持续写入。总结来说异步逻辑转到同步逻辑不行避免需求额定的时钟开支,这会导致满空趋于保存,可是保存并不等于过错,这么写会略微有功能丢失,可是不会犯错。
举个比方:大多数景象下,异步FIFO两头的时钟不是同频的,或许读快写慢,或许读慢写快,慢的时钟域同步到快的时钟域不会呈现漏掉指针的状况,可是将指针从快的时钟域同步到慢的时钟域时或许会有指针遗失,举个比方以读慢写快为例,进行满标志判别的时分需求将读指针同步到写时钟域,因为读慢写快,所以不会有读指针遗失,同步耗费时钟周期,所以同步后的读指针滞后(小于等于)当时读地址,所以或许满标志会提早发生,满并非真满。进行空标志判别的时分需求将写指针同步到读指针 ,因为读慢写快,所以当读时钟同步写指针 的时分,必然会漏掉一部分写指针,咱们不必关怀那究竟会漏掉哪些写指针,咱们在乎的是漏掉的指针会对FIFO的空标志发生影响吗?比方写指针从0写到10,期间读时钟域只同步捕捉到了3、5、8这三个写指针而漏掉了其他指针。当同步到8这个写指针时,实在的写指针或许现已写到10 ,相当于在读时钟域还没来得及察觉的状况下,写时钟域或许悄悄写了数据到FIFO去,这样在判别它是不是空的时分会呈现不是实在空的状况,漏掉的指针也没有对FIFO的逻辑操作发生影响。
4、多位二进制码怎么转化为格雷码
二进制码转换成二进制格雷码,其法则是保存二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其他各位与次高位的求法相相似。
我再换种更简略的描绘
二进制数 1 0 1 1 0
二进制数右移1位,空位补0 0 1 0 1 1
异或运算 1 1 1 0 1
这样就能够完成二进制到格雷码的转换了,总结便是移位而且异或,verilog代码完成就一句:assign wgraynext = (wbinnext>>1) ^ wbinnext;
是不是十分简略。
三、代码解析
异步FIFO的信号接口:
wclk wrst_n winc wdata //写时钟、写复位、写恳求、写数据 这几个与写有关的悉数与wclk同步
rclk rrst_n rinc rdata //读时钟、读 复位、读 恳求、读 数据 这几个与读有关的悉数与rclk同步
wfull //写满 与wclk同步
rempty // 读空 与rclk同步
本次代码共分为6个module
1、fifo.v 是顶层模块,效果是将各个小模块例化联系起来
module fifo
#(
parameter DSIZE = 8,
parameter ASIZE = 4
)
(
output [DSIZE-1:0] rdata,
output wfull,
output rempty,
input [DSIZE-1:0] wdata,
input winc, wclk, wrst_n,
input rinc, rclk, rrst_n
);
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;
// synchronize the read pointer into the write-clock domain
sync_r2w sync_r2w
(
.wq2_rptr (wq2_rptr),
.rptr (rptr ),
.wclk (wclk ),
.wrst_n (wrst_n )
);
// synchronize the write pointer into the read-clock domain
sync_w2r sync_w2r
(
.rq2_wptr(rq2_wptr),
.wptr(wptr),
.rclk(rclk),
.rrst_n(rrst_n)
);
//this is the FIFO memory buffer that is accessed by both the write and read clock domains.
//This buffer is most likely an instantiated, synchronous dual-port RAM.
//Other memory styles can be adapted to function as the FIFO buffer.
fifomem
#(DSIZE, ASIZE)
fifomem
(
.rdata(rdata),
.wdata(wdata),
.waddr(waddr),
.raddr(raddr),
.wclken(winc),
.wfull(wfull),
.wclk(wclk)
);
//this module is completely synchronous to the read-clock domain and contains the FIFO read pointer and empty-flag logic.
rptr_empty
#(ASIZE)
rptr_empty
(
.rempty(rempty),
.raddr(raddr),
.rptr(rptr),
.rq2_wptr(rq2_wptr),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n)
);
//this module is completely synchronous to the write-clock domain and contains the FIFO write pointer and full-flag logic
wptr_full
#(ASIZE)
wptr_full
(
.wfull(wfull),
.waddr(waddr),
.wptr(wptr),
.wq2_rptr(wq2_rptr),
.winc(winc),
.wclk(wclk),
.wrst_n(wrst_n)
);
endmodule
2、fifomem.v 生成存储实体,FIFO 的实质是RAM,因而在规划存储实体的时分有两种办法:用数组存储数据或许调用RAM的IP核
module fifomem
#(
parameter DATASIZE = 8, // Memory data word width
parameter ADDRSIZE = 4 // 深度为8即地址为3位即可,这儿多界说一位的原因是用来判别是空仍是满,具体在后文讲到
) // Number of mem address bits
(
output [DATASIZE-1:0] rdata,
input [DATASIZE-1:0] wdata,
input [ADDRSIZE-1:0] waddr, raddr,
input wclken, wfull, wclk
);
`ifdef RAM //能够调用一个RAM IP核
// instantiation of a vendor’s dual-port RAM
my_ram mem
(
.dout(rdata),
.din(wdata),
.waddr(waddr),
.raddr(raddr),
.wclken(wclken),
.wclken_n(wfull),
.clk(wclk)
);
`else //用数组生成存储体
// RTL Verilog memory model
localparam DEPTH = 1 reg [DATASIZE-1:0] mem [0:DEPTH-1]; //生成2^4个位宽位8的数组
assign rdata = mem[raddr];
always @(posedge wclk) //当写使能有用且还未写满的时分将数据写入存储实体中,留意这儿是与wclk同步的
if (wclken !wfull)
mem[waddr] = wdata;
`endif
endmodule
3、sync_r2w.v 将 rclk 时钟域的格雷码方式的读指针同步到 wclk 时钟域,简略来讲便是用两级寄存器同步,即打两拍
module sync_r2w
#(
parameter ADDRSIZE = 4
)
(
output reg [ADDRSIZE:0] wq2_rptr, //读指针同步到写时钟域
input [ADDRSIZE:0] rptr, // 格雷码方式的读指针,格雷码的优点后边会细说
input wclk, wrst_n
);
reg [ADDRSIZE:0] wq1_rptr;
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) begin
wq1_rptr = 0;
wq2_rptr = 0;
end
else begin
wq1_rptr= rptr;
wq2_rptr=wq1_rptr;
end
endmodule
4、sync_w2r.v 将 wclk 时钟域的格雷码方式的写指针同步到 rclk 时钟域
module sync_w2r
#(parameter ADDRSIZE = 4)
(
output reg [ADDRSIZE:0] rq2_wptr, //写指针同步到读时钟域
input [ADDRSIZE:0] wptr, //格雷码方式的写指针
input rclk, rrst_n
);
reg [ADDRSIZE:0] rq1_wptr;
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)begin
rq1_wptr = 0;
rq2_wptr = 0;
end
else begin
rq1_wpt = wptr;
rq2_wptr = rq1_wptr;
end
endmodule
5、rptr_empty.v 将 sync_w2r.v 同步后的写指针与 rclk 时钟域的读指针进行比较生成都空信号
module rptr_empty
#(
parameter ADDRSIZE = 4
)
(
output reg rempty,
output [ADDRSIZE-1:0] raddr, //二进制方式的读指针
output reg [ADDRSIZE :0] rptr, //格雷码方式的读指针
input [ADDRSIZE :0] rq2_wptr, //同步后的写指针
input rinc, rclk, rrst_n
);
reg [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext, rbinnext;
// GRAYSTYLE2 pointer
//将二进制的读指针与格雷码进制的读指针同步
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) begin
rbin = 0;
rptr = 0;
end
else begin
rbin=rbinnext; //直接作为存储实体的地址
rptr=rgraynext;//输出到 sync_r2w.v模块,被同步到 wrclk 时钟域
end
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ADDRSIZE-1:0]; //直接作为存储实体的地址,比方连接到RAM存储实体的读地址端。
assign rbinnext = rbin + (rinc ~rempty); //不空且有读恳求的时分读指针加1
assign rgraynext = (rbinnext>>1) ^ rbinnext; //将二进制的读指针转为格雷码
// FIFO empty when the next rptr == synchronized wptr or on reset
assign rempty_val = (rgraynext == rq2_wptr); //当读指针等于同步后的写指针,则为空。
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
rempty = 1’b1;
else
rempty = rempty_val;
endmodule
6、wptr_full.v 将 sync_r2w.v 同步后的读指针与wclk 时钟域的写指针进行比较生成写满信号
module wptr_full
#(
parameter ADDRSIZE = 4
)
(
output reg wfull,
output [ADDRSIZE-1:0] waddr,
output reg [ADDRSIZE :0] wptr,
input [ADDRSIZE :0] wq2_rptr,
input winc, wclk, wrst_n
);
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext, wbinnext;
// GRAYSTYLE2 pointer
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
{wbin, wptr} = 0;
else
{wbin, wptr} = {wbinnext, wgraynext};
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc ~wfull);
assign wgraynext = (wbinnext>>1) ^ wbinnext; //二进制转为格雷码
//—————————————————————–
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]}); //当最高位和次高位不同其他位相一起则写指针超前于读指针一圈,即写满。后边会具体解说。
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
wfull = 1’b0;
else
wfull = wfull_val;
endmodule
7、测验文件
`timescale 1ns /1ns
module test();
reg [7:0] wdata;
reg winc, wclk, wrst_n;
reg rinc, rclk, rrst_n;
wire [7:0] rdata;
wire wfull;
wire rempty;
fifo
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
winc = 0;
rinc = 0;
end
else begin
winc = $random;
rinc = $random;
end
end
always @(posedge rclk or negedge rrst_n)begin
if(rrst_n==1’b0)begin
rinc = 0;
end
else begin
rinc = $random;
end
end
always@(*)begin
if(winc == 1)
wdata= $random ;
else
wdata = 0;
end
endmodule
8、仿真成果
因为截图篇幅的约束请自己验证仿真。