- 异步FIFO
异步FIFO和同步FIFO一样,也需要写使能和读使能来控制FIFO的读写,同样也可以利用读写指针来指示读写地址,每写入一个数据,写指针加一,同样的,每读出一个数据,读指针加一。与同步FIFO不同的是——同步FIFO中只需要对读写地址扩展一位,并将最高位作为指示位,其余位仍然表示读写地址,当最高位不同而其他位相同时表明读地址或写地址多跑了一圈,而显然读地址不可能比写地址多跑一圈,因此就是写地址多跑了一圈,也就表示此时FIFO已经写满了。
而当最高位相同其他位与相同时,读指针追上了写指针或者写指针追上了读指针,而显然写指针应该在前面(不然读啥),因此这种情况只能是读指针追上了写指针,也就表示FIFO被读空了。而异步FIFO中的“写满”或者“读空”的判断要复杂很多。
同步FIFO中利用的是二进制指针来判断“写满”或者“读空”,但异步FIFO中为了减少多bit跨时钟域传输带来的亚稳态问题采用格雷码形式的指针,此时如何判断“写满”或者“读空”呢?
要解决这个问题我们首先得要明白什么是格雷码。格雷码是一种循环二进制码或者叫作反射二进制码。格雷码的特点是从一个数变为相邻的一个数时,只有一个数据位发生跳变,由于这种特点,就可以避免二进制编码计数组合电路中出现的亚稳态。格雷码与二进制数的转换如下:
可以看到,如果用二进制形式的指针,指针从0111变为1000时四位指针都要发送变化,这无疑严重增加的出现亚稳态的可能,但是利用格雷码就明显减少了亚稳态的发生。回到刚才的问题,我们还是需要把指针扩展一位,但如果这时候再光看最高位是否相同时明显行不通了,比如5和10处理最高位不同其他几位均相同,但此时明显不能说FIFO写满了。这时候我们就需要观察最高位和次高位是否相等:
- 当最高位和次高位不同,其他位相同时则认为此时FIFO已经写满
- 当最高位和次高位相同,其他位也相同则认为此时FIFO已经读空
当然也可以把格雷码再转换为二进制码再比较最高位。
- FIFO中的同步问题
博主一开始学习异步FIFO的时候,看见资料说需要将读写指针进行同步,进而比较读写指针以判断FIFO的“空”“满”。而实现读写指针同步的方法就是把读指针在写时钟域下打两拍或者将写指针在读时钟域下打两拍。
那为什么打两拍就可以实现同步呢?为什么是打两拍呢?打一拍或者打三拍行不行呢?抱着这个疑问,博主开始广泛的查阅资料,终于找到了答案。下面是我的一知半解,如有不对的地方,欢迎大家批评指正。
其实打两拍的目的就是让信号变稳定,并且将输入信号同步化。比如写时钟的频率为100MHz,读时钟频率为50MHz,将读时钟域的读指针同步到写时钟域。第一拍让读指针在写时钟的频率下存入寄存器,而写指针就是在写时钟的频率下变化,因此可以将读指针与写指针进行比较,判断“空”“满”。第二个目的就是防止亚稳态的传递。按照上面的说法,只需要打一拍就可以实现同步了,为什么还要打一拍呢?我们知道信号在变化是很容易出现亚稳态,什么是亚稳态呢?
简单来说就是时钟上升沿到来时对信号进行采样,此时我们希望信号保持稳定,而我们知道信号的变化是需要时间的,时钟上升沿到来之前有一个Tsu,上升沿到来之后有一段Th,如果D号在这两个时间段内发生变化,Q就会出现一个未知状态,也就是亚稳态。为何打两拍将减少亚稳态的概率呢?请看下图
可以看到就算第一个寄存器中出现了亚稳态,第二个寄存器中亚稳态的概率也会大大降低,也就实现了我们的目的。
- FIFO深度计算
FIFO一般用于写快读慢的情况,倘若一直写入数据,则不管FIFO深度设置为多少最终都会被写满,因此必须为间断写入。假如写时钟为250MHz,读时钟位200MHz,想要保证数据不丢失,若一次写入的突发长度120写入一个数据需要的时间:1/250MHz=4ns;读取一个数据需要的时间:1/200MHz=5ns写入120个数据,需要的时间:120*4ns=480ns在写入全部数据所需的时间(480ns)内,可以读取出的数据数:480ns/5ns=96所以一次突发,一共需要写入120数据,在这段时间内可以被读出96数据,剩下的数据就是需要使用FIFO来缓存,所以FIFO的最小深度为120-96=24。
- 设计思路
跟同步FIFO的设计思路一样,总体分为三部分:读操作、写操作和判断“空”“满”,读写操作也和同步FIFO类似,每当读时钟或写时钟上升沿到来时,读写指针加一,并将数据读出或者写入,但还要加上读写指针步操作,即将读写指针分别在写时钟和读时钟下打两拍。然后就是利用最高位和次高位判断“空”“满”。此外还有二进制码与格雷码的相互转换。
二进制转格雷码的原理如下:二进制码的最高位作为格雷码的最高位,格雷码的次高位为二进制码的最高位与次高位相异或,其余位与次高位的求法相类似,原理图如下:
根据思路给出代码如下:
`timescale 1ns / 1ps
module asynch_fifo
#(parameter WIDTH = 8,
parameter DEPTH = 8
)
(
input wr_clk, //写时钟
input wr_en, //写使能
input wr_rst_n, //写复位
input rd_clk, //读时钟
input rd_en, //读使能
input rd_rst_n, //读复位
input [WIDTH-1:0] din, //写入数据
output reg[WIDTH-1:0] dout, //读出数据
output wire full, //写满信号
output wire empty //读空信号
);
reg [WIDTH-1:0] fifo_r[DEPTH-1:0]; //二维寄存器,用于存放数据
reg [$clog2(DEPTH) : 0] wr_ptr; //写指针
reg [$clog2(DEPTH) : 0] rd_ptr; //读指针
wire [$clog2(DEPTH)-1 : 0] wr_ptr_true; //真实写指针
wire [$clog2(DEPTH)-1 : 0] rd_ptr_true; //真实读指针
wire [$clog2(DEPTH) : 0] wr_ptr_gray; //写指针,格雷码
wire [$clog2(DEPTH) : 0] rd_ptr_gray; //读指针,格雷码
reg [$clog2(DEPTH) : 0] wr_ptr_gray_d1; //写指针在读时钟域下同步一拍
reg [$clog2(DEPTH) : 0] wr_ptr_gray_d2; //写指针在读时钟域下同步两拍
reg [$clog2(DEPTH) : 0] rd_ptr_gray_d1; //读指针在写时钟域下同步一拍
reg [$clog2(DEPTH) : 0] rd_ptr_gray_d2; //读指针在写时钟域下同步两拍
assign wr_ptr_gray = wr_ptr ^ (wr_ptr >> 1); //将二进制写指针转换为格雷码
assign rd_ptr_gray = rd_ptr ^ (rd_ptr >> 1); //将二进制读指针转换为格雷码
assign wr_ptr_true = wr_ptr[$clog2(DEPTH)-1 : 0]; //真实指针,用于指示数据写入位置
assign rd_ptr_true = rd_ptr[$clog2(DEPTH)-1 : 0]; //真实指针,用于指数数据读出位置
//写数据
always @(posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n)
wr_ptr <= 'd0;
else if(wr_en && !full)begin
wr_ptr <= wr_ptr + 1'b1;
fifo_r[wr_ptr_true] <= din;
end
else begin
wr_ptr <= wr_ptr;
end
end
//将读时钟同步到写时钟域
always @(posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n)begin
rd_ptr_gray_d1 <= 'd0;
rd_ptr_gray_d2 <= 'd0;
end
else begin
rd_ptr_gray_d1 <= rd_ptr_gray;
rd_ptr_gray_d2 <=rd_ptr_gray_d1;
end
end
//读数据
always @(posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n)begin
dout <= 'd0;
rd_ptr <= 'd0;
end
else if(rd_en && !empty)begin
rd_ptr <= rd_ptr + 1'b1;
dout <= fifo_r[rd_ptr_true];
end
else begin
rd_ptr <= rd_ptr;
dout <= dout;
end
end
//将写时钟同步到读时钟域
always @(posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n)begin
wr_ptr_gray_d1 <= 'd0;
wr_ptr_gray_d2 <= 'd0;
end
else begin
wr_ptr_gray_d1 <= wr_ptr_gray;
wr_ptr_gray_d2 <=wr_ptr_gray_d1;
end
end
assign full = (wr_ptr_gray == {~(rd_ptr_gray_d2[$clog2(DEPTH):$clog2(DEPTH) -1]),rd_ptr_gray_d2[$clog2(DEPTH) -2 : 0]});
assign empty = (wr_ptr_gray_d2 == rd_ptr_gray);
endmodule
`timescale 1ns / 1ps
module tb_asynch_fifo;
// asynch_fifo Parameters
parameter DEPTH = 8;
parameter WIDTH = 8;
// asynch_fifo Inputs
reg wr_clk = 0 ;
reg wr_en = 0 ;
reg wr_rst_n = 0 ;
reg rd_clk = 0 ;
reg rd_en = 0 ;
reg rd_rst_n = 0 ;
reg [WIDTH-1:0] din = 0 ;
// asynch_fifo Outputs
wire [WIDTH-1:0] dout ;
wire full ;
wire empty ;
asynch_fifo #(
.DEPTH ( DEPTH ))
u_asynch_fifo (
.wr_clk ( wr_clk ),
.wr_en ( wr_en ),
.wr_rst_n ( wr_rst_n ),
.rd_clk ( rd_clk ),
.rd_en ( rd_en ),
.rd_rst_n ( rd_rst_n ),
.din ( din ),
. dout ( dout ),
.full ( full ),
.empty ( empty )
);
initial wr_clk = 1'b1;
always#10 wr_clk = ~wr_clk;
initial rd_clk = 1'b1;
always#5 rd_clk = ~rd_clk;
initial
begin
wr_rst_n = 1'b0;
rd_rst_n = 1'b0;
wr_en = 1'b0;
rd_en = 1'b0;
din = 'd0;
#201;
wr_rst_n = 1'b1;
rd_rst_n = 1'b1;
#100;
repeat(7)begin
@(posedge wr_clk)begin
wr_en = 1'b1;
din = $random;
end
end
@(posedge wr_clk)
wr_en = 1'b0;
repeat(7)begin
@(posedge rd_clk)
rd_en = 1'b1;
end
@(posedge rd_clk)
rd_en = 1'b0;
repeat(3)begin
@(posedge wr_clk)begin
wr_en = 1'b1;
din = $random;
end
end
@(posedge rd_clk)
rd_en = 1'b1;
forever begin
@(posedge wr_clk)begin
wr_en = 1'b1;
din = $random;
end
end
$finish;
end
endmodule
本文仅用于记录学习,如有侵权,请联系博主。