异步FIFO
数字IC经典电路设计
经典电路设计是数字IC设计里基础中的基础,盖大房子的第一部是打造结实可靠的地基,每一篇笔者都会分门别类给出设计原理、设计方法、verilog代码、Testbench、仿真波形。然而实际的数字IC设计过程中考虑的问题远多于此,通过本系列希望大家对数字IC中一些经典电路的设计有初步入门了解。能力有限,纰漏难免,欢迎大家交流指正。快速导航链接如下:
1.数字分频器设计
2.序列检测器设计
3.序列发生器设计
4.序列模三检测器设计
5.奇偶校验器设计
6.自然二进制数与格雷码转换
7.线性反馈移位寄存器LFSR
8.四类九种移位寄存器总结
9.串并转换
10.七种常见计数器总结
11.异步复位同步释放
12.边沿检测
13.毛刺消除与输入消抖
14.三种不同RAM总结
FIFO即First In First Out,是一种先进先出数据存储、缓冲器,一般的存储器是用外部的读写地址来进行读写,而FIFO这种存储器的结构并不需要外部的读写地址,而是通过计数器实现地址自动的加一操作来控制读写,这也就决定了FIFO只能顺序的读写数据,而不能随机读写。
FIFO分为同步FIFO与异步FIFO。同步FIFO读写采用同一个时钟。它的作用一般是做交互数据的一个缓冲,当数据发生突发写入(即数据写入过快,并且间隔时间长)时,通过设置一定深度的FIFO,可以起到数据暂存的功能,防止数据丢失且使得后续处理流程平滑;异步FIFO读写采用不同的时钟,它主要有两个作用,一个是实现数据在不同时钟域进行传递,另一个作用就是实现不同数据宽度的数据通信。
一、异步FIFO结构
FIFO 是一种“先进先出队列”,数据从一头写入,从另一头读出,读出顺序和写入顺序一模一样。因为队列空间有限,因此一般把队列设计为环形。对于队列来说,最重要的事情是不能在队空的时候读数、不能在队满的时候写数。一般通过比较读写指针来获得“队空”和“队满”信息。异步FIFO常常用在高速数据跨时钟域的场景上。
异步FIFO主要由五部分组成:RAM、写控制端、读控制端、两个时钟同步端
双端口RAM:此处为伪双端口RAM进行数据存储与读出,有两组数据线、地址线、时钟线。
写控制端:写指针与满信号产生器,用于判断是否可以写入数据,写操作时,写使能有效且FIFO未满。
读控制端:读指针与空信号产生器,用于判断是否可以读取数据,读操作时,读使能有效且FIFO未空。
两个时钟同步端:读指针同步到写指针域进行“写满”判断,写指针同步到读指针域进行“读空”判断。
下文将会从双口RAM模块、读写指针(格雷码)转换与跨时钟域同步模块、空满信号判断与生成模块展开说明和设计。
二、异步FIFO模块原理
2.1 双口RAM ——读写数据控制模块
伪双口RAM可以具体阅读三种不同RAM总结,里面对单端口、伪双端口和真双端口RAM有更加详细地介绍。
要理解 FIFO(接下来的所有 FIFO 均指异步 FIFO),首先要理解 FIFO 所使用的存储器。存储器是存放数据的地方,FIFO可以从存储器中读数,也可以向存储器中写数,读数和写数这两个动作是彼此独立的。此处使用的是伪双口RAM。在 FPGA 设计中,往往使用厂商给的伪双口 RAM 的 IP;在 ASIC 设计中,往往用寄存器模拟伪双口 RAM。
什么是伪双口 RAM?
简单地说,伪双口 RAM是有两组地址线、两条时钟线、一组输入数据线、一组读出数据线和一条“写使能”线的 RAM,其有两组端口,读、写各占据一组,每组端口有各自的时钟(异步FIFO使用各自不同时钟)。在写时钟有效沿,若“写使能”有效,输入数据会被写到“写地址”所指示的RAM区;在读时钟有效沿,“读地址”所指示的 RAM 区的内容会被输出到读出数据线。读写动作彼此独立。
FIFO的工作特性——先入先出决定RAM不能随机寻址。因此在FIFO 中,每读写过一次,寄存器值递增一。为了寻址,指针必然是以“二进制码”的形式存在,以深度为8的 FIFO 为例,其寻址指针的递增方式是000-> 001 -> 010 -> 011 -> 100 -> 101 -> 110 -> 111 -> 000循环,依次寻址0号、1号、2号、3号、4号、5号、6号、7号、0号存储单元。
2.2 读写指针(格雷码)转换与跨时钟域同步模块
1.跨时钟域传输
读写指针生成后,下一步就是读写指针的比较和输出空满信号的判断。但是读写指针的比较并不像说起来那么简单,因为读写指针分别处于“读时钟域”、“写时钟域”,两者直接比较属于“跨时钟域”,所以直接比较两者往往会出问题。比如在比较的过程中其中一个指针突然发生变化,从而导致出现亚稳态。为了保证“比较”的安全,必须先把读写指针“同步”到对方的时钟域。一般把读指针同步到写时钟域,本地写指针和同步读指针作比较,以生成“满”信号;把写指针同步到读时钟域,而本地读指针则和同步写指针作比较,以生成“空”信号。
那么此时读写指针此时采用何种方式进行跨时钟域传输呢?
答案是通过增加两级触发器(即两级同步器),指针的每一位经过触发器同步后到达目标时钟域实现传输。而同步的过程中,在两级触发器之间有可能会产生亚稳态,二进制指针的跳变位都有可能出现错误值,甚至多位同时跳变,出现错误的、不可控的中间值,从而产生错误的空满信号!
2.格雷码转换
二进制的读写指针处于不同的时钟域,想要两者进行比较,要将指针同步到对方时钟域。而同步的过程中,在触发器之间有可能会产生亚稳态,二进制指针的跳变位都有可能出现错误值,甚至多位同时跳变,出现错误的、不可控的中间值,从而产生错误的空满信号!
下面我们举例说明:
当指针从地址3(3’b011)跳变至地址4(3’b100)时,3个bit位都会同时发生跳变。当同步时钟在此时采样时,亚稳态的存在导致将会采到错误的中间值,由于3个bit位同时跳变,出错概率极大增加!这个中间值可能是任意值(3’b000、3’b101、3’b110等),导致出现错误的空满信号,这种情况在FIFO设计过程中是应当避免的!
那么解决办法是什么呢?答案是采用格雷码进行同步处理。
这种编码每次从一个值变化到相邻的一个值时,有且仅有一位发生变化;由于格雷码的这种特性,我们就可以将多bit指针同步问题转化为单bit指针同步问题,通过简单的双触发器进行同步操作,可以极大减少传输出现错误编码的概率,而不用担心由于发生亚稳态而出现数据错误中间值的情形。
从自然二进制码到格雷码的转换具体方法是:从二进制的最低位起,依次起与相邻左边的一位数进行异或逻辑运算,并且作为对应格雷码该位的值,最高位保持不变。简而言之就是,将二进制码与逻辑右移的二进制码进行异或可得到格雷码。
Tips:格雷码与自然二进制的转换具体阅读自然二进制数与格雷码转换,里面对两者的相互转换有更加详细的介绍。
2.3 空满信号判断与生成模块
读写指针位于不同的时钟域,无法直接进行比较,所以利用两级触发器,将转换为格雷码后的指针同步到目标时钟域。full信号是用于防止写端将FIFO“写爆”,出现数据覆盖的现象,所以full信号位于写时钟域,需要将读指针同步到写时钟域后与写指针进行比较;而empty信号是用于防止读端将FIFO“读穿”,出现无数据可读的现象,所以empty信号位于读时钟域,需要将写指针同步到读时钟域后与读指针进行比较。
“队空”时,不能读;“队满”时,不能写,这是异步 FIFO 正确工作的基础。而想要判定“队空”“队满”,就要比较读写指针。因为队列成环形循环,所以当读指针等于写指针,队列既可能是空的,也可能是满的——FIFO初始都是先写才能后读,读指针追上写指针时队列空(此时两指针跑完的圈数相当),写指针追上读指针时队列满(此时写指针领先读指针一圈)。
可以发现读写指针是不是“处于同一圈”很重要,因此可以为读写指针增加1个“信息位”,该信息位在指针绕过最后一个单元并指回第0单元时翻转。当读写指针的信息位相等,就可以判断读写指针处于同一圈,此时若读指针等于写指针,则队列空;当读写指针的信息位不等,可以判断写指针超过读指针一圈,如果此时两者相等,则队列满。
读空时:可以理解为是读指针在追写指针
写满时:可以理解为是写指针在追读指针
1.判断读空
以深度为8的FIFO为例,指针地址位宽为3,信息位宽为1,合计完整的指针信息位宽为4。
如下图所示,以自然二进制构成的环,内圈为读外圈为写。读空时是读写指针应当指向同一个位置,此时,读地址应当和写地址完全相同。此时读写指针的二进制编码均为0010,转换为格雷码为0011,可以看出对于读空状态,无论是二进制还是格雷码均是所有位都相同。
2.判断写满
以深度为8的FIFO为例,指针地址位宽为3,信息位宽为1,合计完整的指针信息位宽为4。
如下图所示,以自然二进制构成的环,内圈为读外圈为写。读空时是读写指针应当指向同一个位置,此时,读地址应当和写地址不同。此时读指针二进制编码为0010,而写指针二进制编码为1010,对应的格雷码分别为0011和1111,可以穿出对于写满状态,无论是二进制还是格雷码均是所有位都不同。
那格雷码不同读写指针又怎么进行判断呢?两者存在一定的关系吗?
格雷码计数存在“镜像对称”现象,如下图所示,可以看到从0计数到15(格雷码从0000到1000),作为“信息位”的最高位除外,前八位格雷码与后八位格雷码的低三位呈现“镜像”对称。这给我们判断指针的“写满”是有帮助的。
通过总结规律可以总结得到:最高位和次高位不同,其余位相同时FIFO写满。例如,第一圈和第二圈第3个指针分别是是0-011和1-111;第一圈和第二圈第7个指针分别是是0-101和1-001。所以,判断读写指针是否写满时,先看高两位是否不同和其余位是否相同,若满足则写指针追上读指针(领先一圈),FIFO队列为“满”状态。
三、异步FIFO设计实例(verilog代码与实例)
要求:实现深度为8,数据位宽为8的异步FIFO,确保数据满足先入先出。
3.1 verilog代码
//深度为8,数据位宽为8的异步FIFO
module async_fifo #(
parameter DATA_DEPTH = 8, //深度为8
parameter DATA_WIDTH = 8, //数据位宽为8
parameter PTR_WIDTH = 3 //读写指针位宽为3
)(
input [DATA_WIDTH - 1 : 0] wr_data, //写数据
input wr_clk, //写时钟
input wr_rst_n, //写时钟复位
input wr_en, //写使能
input rd_clk, //读数据
input rd_rst_n, //读时钟复位
input rd_en, //读使能
output reg fifo_full, //“满”标志位
output reg fifo_empty, //“空”标志位
output reg [DATA_WIDTH - 1 : 0] rd_data //写时钟
);
/*-----------------------------------------------------------------
-----------------------------伪双口RAM模块--------------------------
------------------------------------------------------------------*/
//定义一个宽度为8,深度为DEPTH的8的RAM_FIFO
reg [DATA_WIDTH - 1 : 0] ram_fifo [DATA_DEPTH - 1 : 0];
//写指针计数
reg [PTR_WIDTH : 0] wr_ptr; //信息位+地址位所以指针位宽为4
always@ (posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n) begin
wr_ptr <= 0;
end
else if(wr_en && !fifo_full) begin
wr_ptr <= wr_ptr + 1;
end
else begin
wr_ptr <= wr_ptr;
end
end
//RAM写入数据
wire [PTR_WIDTH -1 : 0] wr_addr;
assign wr_addr = wr_ptr[PTR_WIDTH -1 : 0]; //RAM写数据只需要地址位不需要信息位,所以寻址地址位宽为3
always@ (posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n) begin
ram_fifo[wr_addr] <= 0; //复位
end
else if(wr_en && !fifo_full) begin
ram_fifo[wr_addr] <= wr_data; //数据写入
end
else begin
ram_fifo[wr_addr] <= ram_fifo[wr_addr]; //保持不变
end
end
//读指针计数
reg [PTR_WIDTH : 0] rd_ptr;
always@ (posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n) begin
rd_ptr <= 0;
end
else if(rd_en && !fifo_empty) begin
rd_ptr <= rd_ptr + 1;
end
else begin
rd_ptr <= rd_ptr;
end
end
//RAM读出数据
wire [PTR_WIDTH -1 : 0] rd_addr;
assign rd_addr = rd_ptr[PTR_WIDTH -1 : 0];//RAM读数据只需要地址位不需要信息位,所以寻址地址位宽为3
always@ (posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n) begin
rd_data <= 0; //复位
end
else if(rd_en && !fifo_empty) begin
rd_data <= ram_fifo[rd_addr]; //读数据
end
else begin
rd_data <= rd_data; //保持不变
end
end
/*--------------------------------------------------------------------------------------
---------------------------读写指针(格雷码)转换与跨时钟域同步模块------------------------
---------------------------------------------------------------------------------------*/
//读写指针转换成格雷码
wire [PTR_WIDTH : 0] wr_ptr_gray;
wire [PTR_WIDTH : 0] rd_ptr_gray;
assign wr_ptr_gray = wr_ptr ^ (wr_ptr >> 1);
assign rd_ptr_gray = rd_ptr ^ (rd_ptr >> 1);
//写指针同步到读时钟域
//打两拍
reg [PTR_WIDTH : 0] wr_ptr_gray_r1;
reg [PTR_WIDTH : 0] wr_ptr_gray_r2;
always@ (posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n) begin
wr_ptr_gray_r1 <= 0;
wr_ptr_gray_r2 <= 0;
end
else begin
wr_ptr_gray_r1 <= wr_ptr_gray;
wr_ptr_gray_r2 <= wr_ptr_gray_r1;
end
end
//读指针同步到写时钟域
//打两拍
reg [PTR_WIDTH : 0] rd_ptr_gray_r1;
reg [PTR_WIDTH : 0] rd_ptr_gray_r2;
always@ (posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n) begin
rd_ptr_gray_r1 <= 0;
rd_ptr_gray_r2 <= 0;
end
else begin
rd_ptr_gray_r1 <= rd_ptr_gray;
rd_ptr_gray_r2 <= rd_ptr_gray_r1;
end
end
/*--------------------------------------------------------------------------------------
--------------------------------------空满信号判断模块-----------------------------------
---------------------------------------------------------------------------------------*/
//组合逻辑判断写满
always@ (*) begin
if(!wr_rst_n) begin
fifo_full <= 0;
end
else if( wr_ptr_gray == { ~rd_ptr_gray_r2[PTR_WIDTH : PTR_WIDTH - 1],
rd_ptr_gray_r2[PTR_WIDTH - 2 : 0] }) begin
fifo_full <= 1;
end
else begin
fifo_full <= 0;
end
end
//组合逻辑判断读空
always@ (*) begin
if(!rd_rst_n) begin
fifo_empty <= 0;
end
else if(rd_ptr_gray == wr_ptr_gray_r2) begin
fifo_empty <= 1;
end
else begin
fifo_empty <= 0;
end
end
endmodule
3.2 Testbench
`timescale 1ns/1ps;//仿真时间单位1ns 仿真时间精度1ps
module async_fifo_tb #(
parameter DATA_DEPTH = 8,
parameter DATA_WIDTH = 8,
parameter PTR_WIDTH = 3
);
//信号申明
reg [DATA_WIDTH - 1 : 0] wr_data;
reg wr_clk;
reg wr_rst_n;
reg wr_en;
reg rd_clk;
reg rd_rst_n;
reg rd_en;
wire fifo_full;
wire fifo_empty;
wire [DATA_WIDTH - 1 : 0] rd_data;
//例化
async_fifo u_async_fifo (
.wr_clk (wr_clk),
.rd_clk (rd_clk),
.wr_rst_n (wr_rst_n),
.rd_rst_n (rd_rst_n),
.wr_en (wr_en),
.rd_en (rd_en),
.wr_data (wr_data),
.rd_data (rd_data),
.fifo_empty (fifo_empty),
.fifo_full (fifo_full)
);
//读写时钟信号生成
always #10 rd_clk = ~rd_clk;
always #5 wr_clk = ~wr_clk;
//信号初始化和赋值
initial begin
wr_clk = 0;
wr_rst_n = 1;
wr_en = 0;
rd_clk = 0;
rd_rst_n = 1;
rd_en = 0;
#10;
wr_rst_n = 0;
rd_rst_n = 0;
#10;
wr_rst_n = 1;
rd_rst_n = 1;
//only write
wr_en = 1;
rd_en = 0;
repeat(10) begin
@(negedge wr_clk) begin
wr_data = {$random}%30;
end
end
//only read
wr_en = 0;
rd_en = 1;
repeat(10) begin
@(negedge rd_clk);
end
rd_en =0;
//read and write
wr_en = 0;
rd_en = 0;
#80;
wr_en = 1;
rd_en = 1;
repeat(20) begin
@(negedge wr_clk) begin
wr_data = {$random}%30;
end
end
end
endmodule
3.3 仿真结果
四、异步FIFO问题与要点补充(重要!!!)
- 什么是虚空与虚满?由什么导致的?对FIFO工作有影响吗?FIFO能正常工作吗?
先给结论:由于两级同步器的原因,FIFO的“空”和“满”实际上都是提前出现的,是虚假的,并不是真正的“空满”,提前出现的“虚空”和“虚满”对FIFO的工作效率有影响,但是依然能正常工作。“FIFO不空仍不读”和“FIFO不满仍不写”虽然降低了FIFO的效率,但这并不是错误,比较保守的设计,因此只要保证FIFO不出现上溢(overflow)和下溢(underflow)就行。
在空满判断部分说过,读空或写满可以理解为:
读空时:可以理解为是读指针在追写指针
写满时:可以理解为是写指针在追读指针
如上图所示,以读空为例,读指针rd_ptr与同步到读指针域的同步写指针wr_ptr_r2进行比较,实际上同步写指针wr_ptr_r2是写指针的“残影”,是写指针两个读时钟周期前的状态。所以,真正的写指针位于什么位置我们不得而知,但是有一点可以确定的是,写指针至少位于同步写指针的当前位置(写指针要么原地不动,要么继续前进)。所以读指针与“虚假”的写指针进行比较,所得到的结果“读空”实际上是“虚空的”。同样的道理,写满实际上也是“虚满的”。
在这样的情况下,读指针可以更快地“追上”写指针,从而提前出现“空”状态。实际运行的时候,工作效率容易达不到设计目标效率。
- 既然有虚空与虚满,那有“真空”和“真满”吗?真空真满有什么意义?
先给结论:有“真空”和“真满”,但是没什么意义。
“虚空”与“虚满”:是将读指针同步到写时钟域来判断虚满,将写指针同步到读时钟域来判断虚空。
“真空”和“真满”:以读指针同步到写时钟域来判断真空;将写指针同步到读时钟域来判断真满。
根据上面的“虚空虚满”可以知道,两个指针实际上就是追逐的过程,由于同步导致的延迟,在“真空真满”同步后判断过程中,此时的空一定是空或者满,甚至已经空了或者满了一段时间,这样的空满标志显然是没有使用意义的。也就是说真空能实现,但是没实际使用意义。
- 假如FIFO的两个时钟域的时钟频率相差特别大,指针跨时钟域由慢到快和快到慢都采用二级同步器打两拍,快到慢会出现什么现象?对FIFO判断空满有影响吗?
先给结论:慢时钟采快时钟会漏采数据现象,会导致提前“写满”或者“读空”,影响FIFO效率但并不妨碍正常工作。
首先,慢到快采用二级同步器打拍子是没有任何影响的,快时钟域始终会采集到的慢时钟域的所有数据,这一是慢到快跨时钟域处理的经典办法。
但是,快到慢跨时钟域采用二级同步器传输是有问题的,因为慢时钟采样时会漏掉一些数据,但是这样影响FIFO“空满”判断吗?并不影响!例如,以读空为例,读时钟域为慢时钟域,写时钟域为快时钟域,写指针由快到慢的过程中传输分别是0到6的格雷码,但是在读时钟域(慢时钟域)采集到的却是0、1、3、5,由于采集到的同步指针永远在实际指针的前面,所以会提前“写空”(并且还有同步后两个读时钟周期的延迟也会导致提前写空),与“虚满”“虚空”一样。
- 如果深度不是非二次幂会有什么影响?怎么解决?试设计深度为5的FIFO
格雷码一个循环必须有2^n个,因为非二次幂深度的格雷码首位和末尾之间相差不止一位。如果不能保证两个相邻码元之间相差一位的条件,进行跨时钟域操作会因为亚稳态出现错误地传输,因此也就不是真正的格雷码,这也是去了我们采用格雷码而不是二进制编码的意义。
一般解决办法如下:假如深度设计的是5,则向上取深度为8的FIFO,由于镜像对称,然后舍弃掉对称的0-2和13-15,则剩余的10个格雷码依然呈现镜像对称,并且满足相邻的两个格雷码只有一位跳变。
- 读写侧数据位转换的异步FIFO应该怎么设计呢?又该需要考虑哪些注意事项呢?
输入数据和输出数据位宽可以不一致,但要保证写数据、写地址位宽与读数据、读地址位宽的一致性。例如8bit输入32bit输出:写数据位宽 8bit,写地址位宽为 6bit(64个数据)。如果输出数据位宽要求 32bit,则输出地址位宽应该为 4bit(16个数据)。依然采用64个指针,64个8bit输入,但是在输出时每四个指针组合一起输出16个32bit输出。
- 亚稳态真的消除了吗?如果真的出现亚稳态,对于判断空满有什么影响?FIFO还能正常工作吗?
先给结论:亚稳态不会被消除但是可以减少其出现的概率。即使出现亚稳态,由于相邻格雷码只有一位跳变的缘故,要么不变要么正常跳变,所以在空满判断时可能会提前产生“空满”,但不会对判断空满有什么影响,所以FIFO依然能正常工作。
指针同步正确,正是我们所要的;指针同步出错,例如,格雷码写指针从000->001,将写指针同步到读时钟域同步出错,出错的结果只可能是000->000,因为相邻位的格雷码每次只有一位变化,这个出错结果实际上也就是写指针没有跳变保持不变,我们所关心的就是这个错误会不会导致读空判断出错?答案是不会,最多是让空标志在FIFO不是真正空的时候产生,也就是说会提前产生“空满”的判断。所以格雷码保证的是同步后的读写指针即使在出错的情形下依然能够保证FIFO功能的正确性。在同步过程中的亚稳态不可能消除,但是我们只要保证它不会影响我们的正常工作即可。
- 异步FIFO深度计算与设计
先给公式,具体推导看下文:
D e p t h = b u r s t − b u r s t W r c l k × ( R d c l k × X Y ) Depth = burst - \frac{{burst}} {Wr_{clk}} \times(Rd_{clk }\times\frac{X}{Y}) Depth=burst−Wrclkburst×(Rdclk×YX)
首先,我们需要理解一个关键名词突发长度(burst length),我们把一段时间内传递的数据个数称为burst length。假如模块A不间断的往FIFO中写数据,模块B同样不间断的从FIFO中读数据,不同的是模块A写数据的时钟频率要大于模块B读数据的时钟频率,那么在一段时间内总是有一些数据没来得及被读走,如果系统一直在工作,那么那些没有被读走的数据会越累积越多,当数据个数到达FIFO的最大深度FIFO也就会无法写入导致数据的丢失,这是我们所不允许的!要确定FIFO的深度,关键在于计算出在突发读写这段时间内有多少个数据没有被读走。也就是说FIFO的最小深度就等于没有被读走的数据个数。
场景如下:假设FIFO的写时钟为100MHz,读时钟为80MHz。在FIFO输入侧,每100个写时钟,写入80个数据;读数据侧,假定每个时钟读走一个数据。请问FIFO深度设置多少可以保证FIFO不会上溢出和下溢出?
首先,考虑最坏的“背靠背”情况,即相邻的两个长度100的时钟的写入数据全部集中在第一段的尾部和第二段的尾部,这也是数据写入的最极限情况。如下图所示:
假设写入时为最坏情况(背靠背),即在160×(1/100)微秒内写入160个数据。以下是写入160个burst数据的时间计算方法:
T
b
u
r
s
t
=
b
u
r
s
t
W
r
c
l
k
=
160
100
T_{burst}=\frac{burst}{Wr_ {clk}}=\frac{160}{100}
Tburst=Wrclkburst=100160
在写入突发数据这段时间内只能读出160×(1/100)×80个数据
R
d
d
a
t
a
=
T
b
u
r
s
t
1
R
d
c
l
k
=
160
100
×
80
Rd_{data}=\frac{T_{burst}} {\frac{1}{Rd_{clk}}} = \frac{160}{100} \times 80
Rddata=Rdclk1Tburst=100160×80
那么设计最小深度必须大于这段时间后FIFO余下的数据量
D
e
p
t
h
=
b
u
r
s
t
−
R
d
d
a
t
a
=
160
−
160
100
×
80
=
32
Depth=burst - Rd_{data} = 160-\frac{160}{100} \times 80 =32
Depth=burst−Rddata=160−100160×80=32
将问题一般化,下面我们来推导FIFO深度的求解公式,假设:
写时钟频率:
W
r
c
l
k
Wr_{clk}
Wrclk;
读时钟频率:
R
d
c
l
k
Rd_{clk}
Rdclk;
写时每M个时钟周期内会有N个数据写入FIFO;
读时每Y个时钟周期内会有X个数据读出FIFO;
D e p t h = b u r s t − b u r s t W r c l k × ( R d c l k × X Y ) Depth = burst - \frac{{burst}} {Wr_{clk}} \times(Rd_{clk }\times\frac{X}{Y}) Depth=burst−Wrclkburst×(Rdclk×YX)
b
u
r
s
t
=
2
N
burst = 2N
burst=2N
其中
b
u
r
s
t
W
r
c
l
k
\frac {burst}{Wr_{clk}}
Wrclkburst表示这个burst的持续时间,
(
R
d
c
l
k
×
X
Y
)
(Rd_{clk }\times\frac{X}{Y})
(Rdclk×YX)表示读的实际速度,两者的乘积自然就是这段时间读出的数据量。burst表示这段时间写入的数据量。写入和读出两者之差为FIFO中残留的数据,这个也就是理论上的FIFO的最小深度。
五、总结
虽然异步FIFO小模块比较多,但是理解总结下来就三大部分:伪双口RAM模块、读写指针(格雷码)转换与跨时钟域同步模块、空满信号判断模块。
伪双口模块:伪双口RAM是数据存储读出的主体,在这里尤其注意RAM读写地址和FIFO读写指针地址位宽不同!读写指针有信息位。
Tips:伪双口RAM可以具体阅读三种不同RAM总结,里面对单端口、伪双端口和真双端口RAM有更加详细地介绍。
读写指针与跨时钟域同步模块:这一部分是FIFO能否正常运行的基础,主要包括格雷码的转换、读写指针的跨时钟域同步(两级同步器)。格雷码使用的目的是什么?读写指针跨时钟域(包含快到慢和慢到快)怎么解决?这些都需要重点关注。
Tips:格雷码与自然二进制的转换具体阅读自然二进制数与格雷码转换,里面对两者的相互转换更加详细的介绍。
空满信号判断模块:空满信号的判断是FIFO的重中之重,在上一部分我们将读写指针同步到了需要比较的时钟域,那么如何比较读写指针判断“空满”就是需要解决的问题。在这有几个问题一定需要弄清楚!
- 什么是虚空与虚满?由什么导致的?对FIFO工作有影响吗?FIFO能正常工作吗?
- 既然有虚空与虚满,那有“真空”和“真满”吗?真空真满有什么意义?
- 假如FIFO的两个时钟域的时钟频率相差特别大,指针跨时钟域由慢到快和快到慢都采用二级同步器打两拍,快到慢会出现什么现象?对FIFO判断空满有影响吗?
除此以外,还有深度计算等相关问题同样需要重点关注:
- 如果深度不是非二次幂会有什么影响?怎么解决?试设计深度为5的FIFO。
- 异步FIFO深度计算方法和计算公式推导。
更多可查看个人主页链接
软件版本:Modelsim 10.6c
不定期纠错补充,欢迎随时交流
最后修改日期:2023.6.5