一、异步FIFO
- 数据写入FIFO的时钟和数据读出FIFO的时钟是异步的(asynchronous), 异步是指相位不同,频率可能不同,不同时钟上升沿之间关系不确定,相位关系不确定,常用于跨时钟域数据的存储读写。
1、亚稳态
以单bit信号为例:
说明:
- 亚稳态:上图Din是DFF1的输入由上一个DFF0在时钟域clka下输出的单bit信号,当DFF0输出到clkb时钟域时,由于两个时钟域是异步的,所以无法保证采样到的DFF1的输出Ds是稳定的,因为信号跨时钟域,它要在clkb时钟域稳定下来需要一定的时间(这段时间包括触发器内部电路的时间、导线,导线中间组合逻辑电路的时间),这段稳定时间中的输出Ds是不稳定的,叫这个不稳定的状态为亚稳态,但经过一段时间最终输出Ds会达到稳定状态0或1。
2、单bit信号Din慢时钟a同步到快时钟b
若单bit信号Din慢时钟a到快时钟b采用打两拍的方法,打的拍数越多亚稳态消除的越好(信号抖动时间不是确定的,随时钟变化)
- 第一拍:b时钟下同步Din,减小采到亚稳态的概率,虽采到随机值,但最终会达到一个正确的稳定值(随机值最终不会到一个错误的值), 所以输出信号的Ds基本稳定了,但不能用这个信号,因为在时钟b下,仍然有可能会采样到亚稳态,采样到的亚稳态可能为1或0,是不确定的。
- 第二拍:第二次b时钟下同步Din,此时采到Ds信号基本稳定已消除了亚稳态,再输出Dout信号
3、单bit信号Din快时钟b同步到慢时钟a
若单bit信号Din快时钟b同步到慢时钟a,一定可以采到稳定的信号,但可能会出现重复采样问题即出现了多个快时钟周期内同一个数据的信号。
4、多bit信号为什么不能采用打两拍的方法进行跨时钟域同步
- 假设从时钟域A垮到时钟域B,是域A从00变到11,在时域B还能采样到从00变到11吗?在时域B中,时钟的上升沿来的时间是肯定一致的,但是各个bit从时钟域A到达时钟域B的时间是不一样的,亚稳态恢复到稳态的时间也是不一样的。即00中的两个0并不是同时变成1的,因此时钟域B中可能采到中间态01或10。
- 单比特发送亚稳态的概率变成多bit后概率会大很多,不直接打两拍是可以降低亚稳态发生的概率的。
二、异步FIFO设计:用于跨时钟域数据的存储读写
说明:
- 写指针(wptr)指向的位置表示还未写,将要写,系统复位后(FIFO为空),写指针指向0,每写入一笔数据,写指针加1;
- 读指针(rptr)指向的位置表示还未读,将要读,系统复位后(FIFO为空),读指针指向0,每读入一笔数据,读指针加1;
- 空标志(empty):当读指针和写指针相等时,空标志=1,复位时,两指针都为0,或者当读指针追赶上了写指针
- 满标志(full):当写指针和读指针相等时,满标志=1,写指针写了一圈后,又追上了读指针
1、为什么多bit要使用格雷码?
以深度为8的FIFO为例:
- 在异步FIFO中,读写指针多出一个高bit信号来表示读写指针是否已经跑完一圈,自然二进制跑完一圈,例如rptr是1,wptr是9,从高位可以看出FIFO为满,若wptr也是1,说明FIFO为空,但使用自然二进制不好,因为按自然二进制读写指针的变化从3跳到4,变化了3个bit,多bit很容易采到亚稳态,所以使指针转换到格雷码来判断FIFO空满更方便,相邻格雷码只有一个bit不同,可以采取打两拍的方式同步。
- 例如rptr是1,wptr是9,对应格雷码是0001,1101,即wptr格雷码的高两位取反后与rptr格雷码相同,表示FIFO满,若对应格雷码直接相同,则表示FIFO为空。
- full信号产生:与写时钟域有关,需要把转换为格雷码的读指针同步到写时钟域。
- empty信号产生:与读时钟域有关,需要把转换为格雷码的写指针同步到读时钟域。
2、自然二进制转格雷码
//法一
module bin2gry(Gry,Bin);
parameter length = 8;
output [length-1:0] Gry;
input [length-1:0] Bin;
reg [length-1:0] Gry;
integer i;
always @(Bin)
begin
for(i=0;i<length-1;i=i+1)
Gry[i]=Bin[i]^Bin[i+1];
Gry[i]=Bin[i];
end
//法二
assign Gray = (Bin >>1)^Bin;
endmodule
3、格雷码转自然二进制
module gry2bin(Gry,Bin);
parameter length = 8;
input [length-1:0] Gry;
output [length-1:0] Bin;
reg [length-1:0] Bin;
integer i;
always @(Gry)
begin
Bin[length-1]=Gry[length-1];
for(i=length-2;i>=O;i=i-1)
Bin[i]=Bin[i+1]^Gry[i];
end
endmodule
说明:
- 格雷码转二进制可以用来判断几乎空几乎满,此验证项目没有验证这一点。
验证DUT使用的RTL代码:
1、fifo1.v
用来将各个模块端口连接在一起
module fifo1 #(
parameter DSIZE = 8, //DSIZE:表示FIFO的数据位宽
parameter ASIZE = 4)(//ASIZE:表示读写指针的位宽
input wclk ,
input wrst_n,
input winc ,//写数据有效信号
input [DSIZE-1:0] wdata ,
input rclk ,
input rrst_n,
input rinc , //读数据有效信号
output [DSIZE-1:0] rdata ,
output wfull ,
output rempty
);
wire [ASIZE-1:0] waddr,raddr;//表示mem的读写地址
wire [ASIZE:0] wptr,rptr,wq2_rptr,rq2_wptr;//多设置一bit,表示空满
sync_r2w sync_r2w (
.wq2_rptr(wq2_rptr),
.rptr (rptr ),
.wclk (wclk ),
.wrst_n (wrst_n )
);
sync_w2r sync_w2r (
.rq2_wptr(rq2_wptr),
.wptr (wptr ),
.rclk (rclk ),
.rrst_n (rrst_n )
);
fifomem #(
.DATASIZE(DSIZE),
.ADDRSIZE(ASIZE))
fifomem(
.rdata (rdata),
.wdata (wdata),
.waddr (waddr),
.raddr (raddr),
.wclken(winc ),
.wfull (wfull),
.rempty(rempty),
.wclk (wclk ),
.wrst_n(wrst_n)
);
rptr_empty #(
.ADDRSIZE(ASIZE))
rptr_empty(
.rempty (rempty ),
.raddr (raddr ),
.rptr (rptr ),
.rq2_wptr(rq2_wptr),
.rinc (rinc ),
.rclk (rclk ),
.rrst_n (rrst_n )
);
wptr_full #(
.ADDRSIZE(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
module fifomem #(
parameter DATASIZE = 8,//Memory data word width
parameter ADDRSIZE = 4)(//Number of mem address bits
input [DATASIZE-1:0] wdata ,
input [ADDRSIZE-1:0] waddr ,
input [ADDRSIZE-1:0] raddr ,
input wclken,
input wfull ,
input rempty,
input wclk ,
input wrst_n,
output [DATASIZE-1:0] rdata
);
`ifdef VENDORRAM
// instantiation of a vendor's dual-port RAM
vendor_ram mem(
.dout (rdata ),
.din (wdata ),
.waddr (waddr ),
.raddr (raddr ),
.wclken (wclken),
.wclken_n(wfull ),
.clk (wclk ),
.rst_n (wrst_n)
);
`else
//用mem模拟双端口RAM
localparam DEPTH = 1 << ADDRSIZE;//10000=2^ADDRSIZE,表示FIFO深度
reg [DATASIZE-1:0] mem [0:DEPTH-1];//即FIFO的数据宽度与深度
assign rdata = !rempty ? mem[raddr] : 8'b0;
integer i;
always @(posedge wclk or negedge wrst_n)
begin
if (!wrst_n)
for(i=0;i<=DEPTH-1;i=i+1)//循环不可综合,会被分成16个清零代码,费空间
mem[i] <= {(DATASIZE){1'b0}};
else if(wclken && !wfull)//FIFO写有效且未写满
mem[waddr] <= wdata;
end
`endif
endmodule
3、rptr_empty.v
module rptr_empty #(
parameter ADDRSIZE = 4)(
input [ADDRSIZE:0] rq2_wptr,//rq2_wptr是在读时钟域同步后的写指针
input rinc ,
input rclk ,
input rrst_n ,
output reg rempty ,
output [ADDRSIZE-1:0] raddr ,
output reg [ADDRSIZE :0] rptr
);
reg [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext;//读指针的格雷码
wire [ADDRSIZE:0] rbinnext;//自然二进制读指针
assign raddr = rbin[ADDRSIZE-1:0]; //把读指针的低ADDRSIZE位地址给mem
assign rbinnext = rbin + (rinc & ~rempty); //读指针在读有效且非空条件下加1
assign rgraynext = (rbinnext>>1)^rbinnext; //读指针转换为格雷码地址
assign rempty_val = (rgraynext ==rq2_wptr);
//rbin,rgraynext直接输出的格雷码会有毛刺,当前时钟域打一拍,dff输出确保没有毛刺
always @(posedge rclk or negedge rrst_n)
begin
if (!rrst_n)
{rbin,rptr} <= 0;
else
{rbin,rptr} <= {rbinnext,rgraynext};//注意拼接符的使用
end
always @(posedge rclk or negedge rrst_n)
begin
if (!rrst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;
end
endmodule
4、 sync_w2r
module sync_w2r #(
parameter ADDRSIZE = 4)(
input [ADDRSIZE:0] wptr ,
input rclk ,
input rrst_n ,
output reg [ADDRSIZE:0] rq2_wptr //rq2_wptr是在读时钟域同步后的写指针
);
reg [ADDRSIZE:0] rq1_wptr;
//打两拍,同步写指针到读时钟域,使用rclk来同步
always @(posedge rclk or negedge rrst_n)
begin
if (!rrst_n)
{rq2_wptr,rq1_wptr} <= 0;
else
{rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
end
endmodule
5、wptr_full.v
module wptr_full #(
parameter ADDRSIZE = 4)(
input [ADDRSIZE:0] wq2_rptr,//wq2_rptr是在写时钟域同步后的读指针
input winc ,
input wclk ,
input wrst_n ,
output reg wfull ,
output [ADDRSIZE-1:0] waddr ,
output reg [ADDRSIZE:0] wptr
);
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext;
wire [ADDRSIZE:0] wbinnext;
assign waddr = wbin[ADDRSIZE-1:0]; //把写指针的低ADDRSIZE位地址给mem
assign wbinnext = wbin +(winc & ~wfull); //写指针在写有效且非满条件下加1
assign wgraynext = (wbinnext>>1)^wbinnext; //写指针转换为格雷码地址
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});
//wbin,wgraynext直接输出的格雷码会有毛刺,当前时钟域打一拍,dff输出确保没有毛刺
always @(posedge wclk or negedge wrst_n)
begin
if (!wrst_n)
{wbin,wptr} <= 0;
else
{wbin,wptr} <= {wbinnext,wgraynext};
end
always @(posedge wclk or negedge wrst_n)
begin
if (!wrst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
end
endmodule
6、 sync_r2w
module sync_r2w #(
parameter ADDRSIZE = 4)(
input [ADDRSIZE:0] rptr ,
input wclk ,
input wrst_n ,
output reg [ADDRSIZE:0] wq2_rptr //wq2_rptr是在写时钟域同步后的读指针
);
reg [ADDRSIZE:0] wq1_rptr;
//打两拍,同步读指针到写时钟域,在wclk下同步
always @(posedge wclk or negedge wrst_n)
begin
if (!wrst_n)
{wq2_rptr,wq1_rptr} <= 0;
else
{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
end
endmodule