FPGA/IC笔试面试(一):异步FIFO最小深度计算

异步FIFO是一种常用的跨时钟域的设计,应用非常广泛。在平常设计中,我们都是调用IP核,设置深度和宽度就可以用了,但是很少关注资源利用率,即使用最小深度来达到设计效果。
异步FIFO原理及其实现

在进行FIFO最下深度计算之前,需要知道,并不是所有跨时钟域情况都可以使用异步FIFO。

假设写入时钟比读取时钟块,并且数据连续写入不停止,就算也连续读,无论FIFO设计多大,都会被写满,那么FIFO的深度就无穷大了。所以使用异步FIFO的情况是:数据突发写入(长度称为Burst length)。

突发数据长度(Burst Length)

因为输入不能连续写入,这样会导致FIFO无穷大,但是数据可以集中在一段时间内写入,然后停止一段时间 ,等待读取数据一段时间 Ta 后,然后再写入数据(不会导致数据丢失)。突发的意思就是数据集中在一段时间写入,然后等待数据读取一段时间,才能继续写入,使得数据不丢失。

假设需要从一个传感器采集一些数据到处理器处理,传感器采集数据的速度大于处理器处理数据的速度,这时候就需要使用异步FIFO。在最小深度的情况下,传感器开始采集数据写入到FIFO,同时处理器开始读取数据,当传感器采集完一次数据(n个)后,等待一段时间 Ta,处理器也读取完数据,这时候的FIFO为空。在 Ta 时间内不允许写入数据,这就叫突发。假设写入需要10s,读取需要30s,那么在30s之后才能再次向FIFO写入数据,不然就会导致数据丢失。 这个n就是突发数据长度(Burst length)。

常见FIFO深度计算情况

一、写时钟 > 读时钟(写比读快)

写时钟大于读时钟是异步FIFO最常用的使用情况

1、读写都没有空闲周期

假设:
写入时钟 wr_clk = 50MHZ,
读取时钟 rd_clk = 20MHZ,
突发数据长度 Burst length = 100,

分析:
写入一个数据需要 1/50MHZ = 20ns;写入100个数据需要 100 * 20ns = 2000ns;

在2000ns内,只能读取 2000/(1/20MHZ) = 40个数据;

所以FIFO_DEPTH(min) = 100 - 40 = 60。

公式:FIFO_DEPTH >= Burst length - ( Burst length * rd_clk / wr_clk)

如果FIFO_DEPTH为小数(35.5),则取大于FIFO_DEPTH 的整数(36)

2、读写有空闲周期

假设:
写入时钟 wr_clk = 50MHZ,
读取时钟 rd_clk = 10MHZ,
写空闲周期 = a,
读空闲周期 = b,
假设 a = 2,b = 1,
突发数据长度 Burst length = 100,

因为读写都有空闲周期,写一个数据,等2个周期再写下一个数据,相当于每3个时钟周期写一个数据;而读一样,读一个数据,等待1个周期再读下一个数据,相当于每2个时钟周期读一个数据;

注意:读写空闲周期必须满足:(1+a)/wr_clk < (1+b)/rd_clk ,这样才能保证写比读快

分析:
写入一个数据需要 3*(1/50MHZ) = 60ns,写入100个数据需要 100 * 60 = 6000ns;

在6000ns内可以读取 6000/(2*1/10MHZ) = 30

所以所以FIFO_DEPTH(min) = 100 - 30= 70。

公式:FIFO_DEPTH >= Burst length - ( Burst length * rd_clk / wr_clk * (1+a) / (1+b )

如果FIFO_DEPTH为小数(35.5),则取大于FIFO_DEPTH 的整数(36)


二、写时钟 = 读时钟(写读一样快)

1、读写没有空闲周期,且相位相等

这种情况不需要设计FIFO

2、读写没有空闲周期,相位不等

设计一个深度为 1 的FIFO就足够了

3、读写有空闲周期,无相位差

假设:
写入时钟 wr_clk = 50MHZ,
读取时钟 rd_clk = 50MHZ,
写空闲周期 = a,
读空闲周期 = b,
假设 a = 1,b = 2,
突发数据长度 Burst length = 100,

分析:这种情况就相当于读写时钟变化了,根据变化后的时钟计算

因为读写都有空闲周期,写一个数据,等1个周期再写下一个数据,相当于每2个时钟周期写一个数据;而读一样,读一个数据,等2个周期再读下一个数据,相当于每3个时钟周期读一个数据;

写入一个数据需要 2*(1/50MHZ) = 40ns,写入100个数据需要 100 * 40 = 4000ns;

在4000ns内可以读取 4000/(3*1/50MHZ) = 66.6

所以所以FIFO_DEPTH(min) = 100 - 66.6 = 34。

公式:FIFO_DEPTH >= Burst length - ( Burst length * (1+a) / (1+b )

三、写时钟 < 读时钟(读比写快)

1、读写没有空闲周期

因为读比写快,所以不会发生数据丢失的情况,不需要设计FIFO

2、读写有空闲周期

假设:
写入时钟 wr_clk = 20MHZ,
读取时钟 rd_clk = 50MHZ,
写空闲周期 = a,
读空闲周期 = b,
突发数据长度 Burst length = 100,

因为读写都有空闲周期,写一个数据,等a个周期再写下一个数据,相当于每1+a个时钟周期写一个数据;而读一样,读一个数据,等b个周期再读下一个数据,相当于每1+b个时钟周期读一个数据;

1、当(1+a)/wr_clk >= (1+b)/rd_clk时
相当于 读时钟大于等于写时钟,如果没有相位差就不需要设计FIFO,如果有相位差,设计深度为1的FIFO就足够了。

2、当(1+a)/wr_clk < (1+b)/rd_clk时
相当于 写时钟大于读时钟,写比读快
假设a = 1,b = 5
因为读写都有空闲周期,写一个数据,等1个周期再写下一个数据,相当于每2个时钟周期写一个数据;而读一样,读一个数据,等5个周期再读下一个数据,相当于每6个时钟周期读一个数据;

分析:
写入一个数据需要 2*(1/20MHZ) = 100ns,写入100个数据需要 100 * 100 = 10000ns;

在10000ns内可以读取 10000/(6*1/50MHZ) = 83.3

所以所以FIFO_DEPTH(min) = 100 - 83.3= 27。

公式:FIFO_DEPTH >= Burst length - ( Burst length * rd_clk / wr_clk * (1+a) / (1+b )

四、最坏情况(背靠背)

1、背靠背

之前的写入和读取,相当于 是均匀写入和读取,而当写入操作没有说每几个时钟周期写入一个数据,而是在几个时钟周期内写入多少数据;在几个时钟周期内读出多少数据的时候。

2、背靠背计算

假设:

写数据时钟频率fa=50MHz
读数据时钟频率fb=40MHz
在写时钟周期内,每80个周期就有40个数据写入FIFO
在读时钟周期内,每10个周期可以有6个数据读出FIFO

在最坏的情形中,为了得到更安全的FIFO深度,我们需要考虑最坏情况,以防数据丢失,读写的速率应该相差最大,也就是说需要找出最大的写速率最小的读速率,这样才能适配所有写入和读取的情况

可以看到,并没有给出突发长度,突发长度是40吗,其实不是,我们画个图就知道了:

写入的方式很多种,有均匀写入,有先写20再写20,方式很多。我们画出背靠背的情况
在这里插入图片描述
在背靠背(最坏)情况下,突发长度需要设置为 80,并且写速率设置为最大的50MHZ;而因为读时钟周期内,每10个周期可以有6个数据读出FIFO,假设均匀读出,所以读速率设置为最小的40MHZ * 6/10 = 24MHZ

由此可以根据上面各种情况分析:

写入 80个数据需要的时间: 80 * 1/50MHZ = 1600ns

在1600ns内可以读出 1600 / 1/24MHZ = 38.4个数据

所以所以FIFO_DEPTH(min) = 80- 38.4 = 42。

五、最常考(问)的情况

此处参考 https://blog.csdn.net/Reborn_Lee/article/details/100127937

在这里插入图片描述
条件不给完,需要自己计算得到各个参数(一般写比读快

假设:
CLKA = 25MHz,CLKB = 100MHz;
Ta= 40ns, Tb = 10ns;
时钟B的使能信号 en_B = 100 * Ta,占空比为1/4;

一般写比读快,我们认为B为写时钟,写使能时间为4000/4 = 1000ns,则突发写长度为1000/10 = 100个数据;
在1000ns内读出数据为1000/40 = 25个数据,所以FIFO最小深度为100 - 25 = 75

  • 18
    点赞
  • 106
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 以下是一个Verilog代码实现的异步FIFO: ``` module async_fifo #( parameter WIDTH = 8, parameter DEPTH = 16 ) ( input clk, input reset, input write_enable, input read_enable, input [WIDTH-1:0] din, output reg [WIDTH-1:0] dout, output full, output empty ); reg [WIDTH-1:0] mem [0:DEPTH-1]; reg [31:0] write_pointer, read_pointer; reg [31:0] count; always @(posedge clk) begin if (reset) begin write_pointer <= 0; read_pointer <= 0; count <= 0; end else if (write_enable && !full) begin mem[write_pointer] <= din; write_pointer <= (write_pointer == DEPTH-1) ? 0 : write_pointer + 1; count <= count + 1; end else if (read_enable && !empty) begin dout <= mem[read_pointer]; read_pointer <= (read_pointer == DEPTH-1) ? 0 : read_pointer + 1; count <= count - 1; end end assign full = (count == DEPTH); assign empty = (count == 0); endmodule ``` ### 回答2: 下面是一个简单的Verilog代码示例,实现了一个4位异步FIFO。 ```verilog module AsyncFIFO ( input wire clk, input wire reset, input wire wr_en, input wire rd_en, input wire [3:0] data_in, output wire [3:0] data_out, output wire empty, output wire full ); parameter WIDTH = 4; parameter DEPTH = 8; reg [WIDTH-1:0] mem [DEPTH-1:0]; reg [WIDTH-1:0] wr_ptr; reg [WIDTH-1:0] rd_ptr; reg empty, full; always @(posedge clk or posedge reset) begin if (reset) begin wr_ptr <= 0; rd_ptr <= 0; empty <= 1; full <= 0; end else if (wr_en && !full) begin mem[wr_ptr] <= data_in; wr_ptr <= wr_ptr + 1; empty <= 0; if (wr_ptr == (DEPTH - 1)) full <= 1; end else if (rd_en && !empty) begin data_out <= mem[rd_ptr]; rd_ptr <= rd_ptr + 1; full <= 0; if (rd_ptr == (DEPTH - 1)) empty <= 1; end end endmodule ``` 这个异步FIFO模块具有以下端口: - clk:时钟信号 - reset:复位信号 - wr_en:写使能信号 - rd_en:读使能信号 - data_in:输入数据信号 - data_out:输出数据信号 - empty:空状态信号 - full:满状态信号 当写使能信号wr_en有效且FIFO不满时,输入数据data_in被写入FIFO中,并且写指针wr_ptr递增。如果写指针达到FIFO深度的最大值,则FIFO被标记为满状态。当写使能信号不被激活时,数据不被写入。 当读使能信号rd_en有效且FIFO不为空时,FIFO中的数据被读取到输出端口data_out,并且读指针rd_ptr递增。如果读指针达到FIFO深度的最大值,则FIFO被标记为空状态。当读使能信号不被激活时,数据不被读取。 此外,在时钟上升沿和复位信号上升沿时,FIFO的各种状态被初始化或重置。 ### 回答3: 异步FIFO(First In First Out)是一种数据存储器件,用于在不同的时钟域之间传输数据。它可以在较快的时钟域中接收数据,并在较慢的时钟域中输出数据。下面是一个使用Verilog描述的异步FIFO的代码示例: module async_fifo ( input wire clk_a, // 输入时钟域A input wire clk_b, // 输入时钟域B input wire reset_a, // 输入时钟域A复位信号 input wire reset_b, // 输入时钟域B复位信号 input wire data_in, // 输入数据信号 input wire write_en, // 写使能信号 input wire read_en, // 读使能信号 output wire data_out, // 输出数据信号 output wire full, // FIFO已满标志位 output wire empty // FIFO为空标志位 ); reg [7:0] memory [0:255]; // 存储数据的内存 reg [7:0] read_ptr_a; // 时钟域A读指针 reg [7:0] write_ptr_a; // 时钟域A写指针 reg [7:0] read_ptr_b; // 时钟域B读指针 reg [7:0] write_ptr_b; // 时钟域B写指针 always @(posedge clk_a or posedge reset_a) begin if (reset_a) begin read_ptr_a <= 0; write_ptr_a <= 0; end else begin if (write_en && !full) begin memory[write_ptr_a] <= data_in; write_ptr_a <= write_ptr_a + 1; end if (read_en && !empty) begin data_out <= memory[read_ptr_a]; read_ptr_a <= read_ptr_a + 1; end if (write_ptr_a - read_ptr_a >= 256) begin full <= 1; end else begin full <= 0; end if (write_ptr_a == read_ptr_a) begin empty <= 1; end else begin empty <= 0; end end end always @(posedge clk_b or posedge reset_b) begin if (reset_b) begin read_ptr_b <= 0; write_ptr_b <= 0; end else begin if (write_en && !full) begin memory[write_ptr_b] <= data_in; write_ptr_b <= write_ptr_b + 1; end if (read_en && !empty) begin data_out <= memory[read_ptr_b]; read_ptr_b <= read_ptr_b + 1; end if (write_ptr_b - read_ptr_b >= 256) begin full <= 1; end else begin full <= 0; end if (write_ptr_b == read_ptr_b) begin empty <= 1; end else begin empty <= 0; end end end endmodule 以上代码描述了一个具有两个时钟域的异步FIFO。它包含一个8位存储器来存储数据,并通过读写指针来读写数据。当FIFO已满时,将设置full标志位,当FIFO为空时,将设置empty标志位。在时钟域A和时钟域B中,根据写使能和读使能信号,数据将被写入或读取。在每个时钟域中都会进行读写指针的增加和数据的传输。在复位信号下,读写指针将被重新设置为0。 注意:以上代码仅为示例,具体实现可能因具体需求而有所不同,并且可能需要进一步的调试和完善。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值