赞
踩
异步FIFO顾名思义,是写入FIFO中数据的速度和读取FIFO中数据的速度不同,简言之就是写数据的时钟和读数据的时钟不同。异步 FIFO 有两个时钟信号,读和写接口分别采用不同时钟,这两个时钟可能时钟频率不同,也可能时钟相位不同,可能是同源时钟,也可能是不同源时钟。在现代逻辑设计中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步 FIFO 是这个问题的一种简便、快捷的解决方案,使用异步 FIFO 可以在两个不同时钟系统之间快速而方便地传输实时数据。
接口名称 | 释义 |
---|---|
wr_clk | 写数据时钟 |
rd_clk | 读数据时钟 |
wr_rst | 写复位 |
rd_rst | 读复位 |
wr_en | 写数据使能信号,高电平有效 |
din | 数据输入 |
rd_en | 读数据使能信号,高电平有效 |
dout | 数据输出 |
full | 写满信号,高电平有效 |
empty | 读空信号,高电平有效 |
异步FIFO设计的难点在于:空信号和满信号如何产生?
在同步FIFO设计中,我们使用了计数器对FIFO内部的数据进行计数,用于空满信号的判断,这种方法是基于读写时钟一致的情况下,但是在读写时钟不一致的情况下,采用计数器计数,究竟是采用读时钟进行计数还是使用写时钟进行计数?答案是,谁的时钟都不能用,因为读写时钟不一致的情况下,其计数器计数是不准确的,同时也会造成亚稳态的产生,从而导致设计的异步FIFO无法使用。
因此,我们想到,使用高位扩展法来设计。那么什么是高位扩展法呢?高位扩展法就是在高位扩展以为宽数据。举例在深度为8的FIFO中,需要3bit的读写指针来分别指示读写地址3’b000-3’b111这8个地址。若将地址指针扩展1bit,则变成4bit的地址,而地址表示区间则变成了4’b0000-4’b1111。假设不看最高位的话,后面3位的表示区间仍然是3’b000-3’b111,也就意味着最高位可以拿来作为指示位。
读指针在读电路中维持着一个指针地址,写指针在写电路中维持着一个指针地址,简单而言,将这两个指针地址进行比较,就能得出FIFO当前状态是空还是满,但是,这牵扯到跨时钟域的问题,在进行读写指针的地址比较之前,还需要了解一下关键点:
显而易见,如果要判断是否写满,就需要将读指针同步到写时钟域中与读指针进行比较判断,为什么判断写满状态需要将读指针同步过来呢?因为写满状态是写电路所关心的问题,判断是否写满当然要在写时钟域下进行判断。同理,如果判断是否读空,需要将写指针同步到读时钟域下与读指针进行比较判断。
现在总结一下:
在异步FIFO设计中,还是因为时钟域的问题,我们不得不使用格雷码来表示读写指针,为什么要将读写指针的指针地址转换成格雷码呢?举个简单的例子,当写指针由7到8的递增时,其二进制码由4’b0111向4’b1000变化,这样同步到读时钟域时需要变化四位,这样会导致亚稳态的发生,如果发生了亚稳态问题,那么势必会造成写指针的不确定性,进而导致FIFO设计失败。
而格雷码面对数据变化每次只有其中某一位发生变化,这将大大减小了亚稳态发生的概率。比如写指针由7跳变为8时,其格雷码从0100跳转为1100,只有最高位发生了跳变。下图为十进制数与格雷码的对照。
那么如何判断空满状态呢?
前面介绍了格雷码,我们当然使用格雷码作为读写的指针地址参与空满状态的判断。
首先判断读空信号。读空信号的判断很简单,就是将同步到读时钟域的写指针地址与读指针地址进行比较,读写指针地址重合,完全相同就是读空状态,只要有一位不同就是不空,即:相同为空。
再判断写满信号。我们仔细观察格雷码,发现4位的格雷码的0-7和8-15除了最高位不同外,其余成镜像对称关系,也就关于红线对称;所以我们就不能仅仅依靠判断最高位来判断写满了。在这举个例子:假设一个深度位8的异步FIFO,其内部的RAM地址位0~7,使用高位拓展法将最高位拓展一位,那么读写指针的格雷码就有4位宽;在某一时刻读指针指向5(格雷码为0111),此时此刻写指针指向6(格雷码为0101),这种情况说明地址0-4中的数据均已读出,那么FIFO写满的情况是写指针地址加上8,由于高位拓展了一位,所以写指针地址指向13,即图中虚线的箭头处,其格雷码为1011,我们可以发现,此时写指针地址和读指针地址除了最高位和次高位不同外,其余为均相同,因此我们可以得到写满的判断条件。即:读指针地址的最高位和写指针地址的最高位不同,读指针地址的次高位和写指针地址的次高位不同,其余位相同。
根据以上可以设计异步FIFO的实现:
// An highlighted block
module async_fifo
#(
parameter DATA_WIDTH = 'd8 , //FIFO位宽
parameter DATA_DEPTH = 'd16 //FIFO深度
)
(
//写数据
input wr_clk , //写时钟
input wr_rst_n , //低电平有效的写复位信号
input wr_en , //写使能信号,高电平有效
input [DATA_WIDTH-1:0] data_in , //写入的数据
//读数据
input rd_clk , //读时钟
input rd_rst_n , //低电平有效的读复位信号
input rd_en , //读使能信号,高电平有效
output reg [DATA_WIDTH-1:0] data_out , //输出的数据
//状态标志
output empty , //空标志,高电平表示当前FIFO已被写满
output full //满标志,高电平表示当前FIFO已被读空
);
//reg define
//用二维数组实现RAM
reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0];
reg [$clog2(DATA_DEPTH) : 0] wr_ptr; //写地址指针,二进制
reg [$clog2(DATA_DEPTH) : 0] rd_ptr; //读地址指针,二进制
reg [$clog2(DATA_DEPTH) : 0] rd_ptr_g_d1; //读指针格雷码在写时钟域下同步1拍
reg [$clog2(DATA_DEPTH) : 0] rd_ptr_g_d2; //读指针格雷码在写时钟域下同步2拍
reg [$clog2(DATA_DEPTH) : 0] wr_ptr_g_d1; //写指针格雷码在读时钟域下同步1拍
reg [$clog2(DATA_DEPTH) : 0] wr_ptr_g_d2; //写指针格雷码在读时钟域下同步2拍
//wire define
wire [$clog2(DATA_DEPTH) : 0] wr_ptr_g; //写地址指针,格雷码
wire [$clog2(DATA_DEPTH) : 0] rd_ptr_g; //读地址指针,格雷码
wire [$clog2(DATA_DEPTH) - 1 : 0] wr_ptr_true; //真实写地址指针,作为写ram的地址
wire [$clog2(DATA_DEPTH) - 1 : 0] rd_ptr_true; //真实读地址指针,作为读ram的地址
//地址指针从二进制转换成格雷码
assign wr_ptr_g = wr_ptr ^ (wr_ptr >> 1);
assign rd_ptr_g = rd_ptr ^ (rd_ptr >> 1);
//读写RAM地址赋值
assign wr_ptr_true = wr_ptr [$clog2(DATA_DEPTH) - 1 : 0]; //写RAM地址等于写指针的低DATA_DEPTH位(去除最高位)
assign rd_ptr_true = rd_ptr [$clog2(DATA_DEPTH) - 1 : 0]; //读RAM地址等于读指针的低DATA_DEPTH位(去除最高位)
//写操作,更新写地址
always @ (posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n)
wr_ptr <= 0;
else if (!full && wr_en)begin //写使能有效且非满
wr_ptr <= wr_ptr + 1'd1;
fifo_buffer[wr_ptr_true] <= data_in;
end
end
//将读指针的格雷码同步到写时钟域,来判断是否写满
always @ (posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n)begin
rd_ptr_g_d1 <= 0; //寄存1拍
rd_ptr_g_d2 <= 0; //寄存2拍
end
else begin
rd_ptr_g_d1 <= rd_ptr_g; //寄存1拍
rd_ptr_g_d2 <= rd_ptr_g_d1; //寄存2拍
end
end
//读操作,更新读地址
always @ (posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n)
rd_ptr <= 'd0;
else if (rd_en && !empty)begin //读使能有效且非空
data_out <= fifo_buffer[rd_ptr_true];
rd_ptr <= rd_ptr + 1'd1;
end
end
//将写指针的格雷码同步到读时钟域,来判断是否读空
always @ (posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n)begin
wr_ptr_g_d1 <= 0; //寄存1拍
wr_ptr_g_d2 <= 0; //寄存2拍
end
else begin
wr_ptr_g_d1 <= wr_ptr_g; //寄存1拍
wr_ptr_g_d2 <= wr_ptr_g_d1; //寄存2拍
end
end
//更新指示信号
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign empty = ( wr_ptr_g_d2 == rd_ptr_g ) ? 1'b1 : 1'b0;
//当高位相反且其他位相等时,写指针超过读指针一圈,FIFO被写满
//同步后的读指针格雷码高两位取反,再拼接上余下位
assign full = ( wr_ptr_g == { ~(rd_ptr_g_d2[$clog2(DATA_DEPTH) : $clog2(DATA_DEPTH) - 1])
,rd_ptr_g_d2[$clog2(DATA_DEPTH) - 2 : 0]})? 1'b1 : 1'b0;
endmodule
接下来编写脚本对源码进行测试:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。