芯片基识 | 掰开揉碎讲 FIFO(同步FIFO和异步FIFO)

文章目录

  • 一、什么是FIFO
  • 二、为什么要用FIFO
  • 三、什么时候用FIFO
  • 四、FIFO分类
  • 五、同步FIFO
    • 1. 同步FIFO电路框图
    • 2. 同步FIFO空满判断
    • 3. 同步FIFO设计代码
    • 4. 同步FIFO仿真结果
  • 六、异步FIFO
    • 1、异步FIFO的电路框图
    • 2 、亚稳态
    • 3、打两拍
    • 4、格雷码
    • 5、如何判断异步FIFO的空满
      • (1)空判断
      • (2)满判断
      • (3)虚空、虚满
    • 6、如何选择FIFO深度
    • 7、异步FIFO的设计代码
      • (1)顶层模块
      • (2)双端口RAM模块
      • (3)同步模块1
      • (4)同步模块2
      • (5)空判断模块
      • (6)满判断模块
    • 8、 仿真
      • (1)异步FIFO仿真文件
      • (2)异步FIFO仿真结果
  • 七、FIFO应用实例 (ADC)
    • 一、实验前提
    • 二、FIFO IP调用
  • 原文链接

一、什么是FIFO

FIFO 是 First In First Out 的简称。

是指在FPGA内部用逻辑资源实现的能对数据的存储具有先进先出特性的一种缓存器。

FIFO 与 FPGA 内部的 RAM 和 ROM 的区别是 FIFO 没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,其数据地址由内部读写指针自动加1完成。

FIFO 使用起来简单方便,由此带来的缺点是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。

二、为什么要用FIFO

FPGA内的程序实现的电路实际上是由一个个独立的功能模块组成的,各个模块又通过相关信号关联在一起,当存在模块间处理数据速度不同(有快有慢)时,处理得快的模块就需要等一等处理得慢的模块,这个等待其实就是缓存的实现

我们可以采用FIFO来解决数据的缓存。

打个比方,就像水龙头放水慢(输入慢),但我们人提水的时候是一次处理一桶水(输出快),所以需要一个水桶作为缓存,等存满一桶水,再一次被人提走。

又或者像我们人喝水,一次接一杯水(输入快), 渴的时候喝两口(输出慢)。这里,杯子作为缓存。

另外,在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,此时,异步时钟之间的接口电路的设计将成为关键。

使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。

三、什么时候用FIFO

  • 数据缓存、
  • 协议处理、
  • 串并转换、
  • 跨时钟域数据处理。

四、FIFO分类

FIFO根据读写时钟是否为同一时钟分为同步FIFO和异步FIFO。

  • 同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿来临时可同时发生读写操作。

  • 异步FIFO是指读写时钟不一致,读写时钟是互相独立的2个时钟。

  • 同步FIFO在实际应用中比较少见,常用的是异步FIFO,但基于学习的目的,下文对两种FIFO都进行讲解。

五、同步FIFO

1. 同步FIFO电路框图

简单来说,同步FIFO其实就是一个双口RAM加上两个读写控制模块。FIFO的常见参数和信号如下:

  • FIFO的宽度:即FIFO一次读写操作的数据位;
  • FIFO的深度:指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
  • 满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
  • 空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
  • 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。(同步FIFO 读写只有一个时钟)
  • 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。(异步FIFO读写时钟分开)
  • 读指针:总是指向下一个将要被写入的单元,写完后自动加1,复位时,指向第1个单元(编号为0)。
  • 写指针:总是指向下一个将要被读出的单元,读完后自动加1,复位时,指向第1个单元(编号为0)

其实可以把FIFO比作一个单向行驶的隧道,隧道两端都有一个门进行控制,FIFO宽度就是这个隧道单向有几个车道,FIFO的深度就是一个车道能容纳多少辆车,当隧道内停满车辆时,这就是FIFO的写满状态,当隧道内没有一辆车时,这便是FIFO的读空状态。

2. 同步FIFO空满判断

FIFO 的设计原则是任何时候都不能向满FIFO中写入数据(写溢出),任何时候都不能从空FIFO中读取数据(读溢出)。

FIFO 设计的核心是空满判断。FIFO设置读,写地址指针,FIFO初始化的时候 读指针和写指针都指向地址为0的位置, 当往FIFO里面每写一个数据,写地址指针自动加1指向下一个要写入的地址。

当从FIFO里面每读一个数据,读地址指针自动加1指向下一个要读出的地址,最后通过比较读地址指针和写地址指针的大小来确定空满状态。

当读地址指针追上写地址指针,写地址指针跟读地址指针相等,此时FIFO是读空状态。

当写地址指针再次追上读地址指针,写指针跟读地址指针再次相等的时候,此时FIFO是写满状态。

可以设置一个计数器,当写使能有效的时候计数器加一;

当读使能有效的时候,计数器减一,将计数器与FIFO的size进行比较来判断FIFO的空满状态。

这种方法设计比较简单,但是需要的额外的计数器,就会产生额外的资源,而且当FIFO比较大时,会降低FIFO最终可以达到的速度。

3. 同步FIFO设计代码

同步FIFO基本接口:

信号描述
clk系统时钟
rstn系统复位信号
wr_en写使能端
wr_dataFIFO写数据
fifo_fullFIFO的满标志位
rd_en读使能端
rd_dataFIFO读数据
fifo_emptyFIFO的空标志位

同步FIFO实现代码如下:

module sync_fifo#(parameter BUF_SIZE=8, BUF_WIDTH=8) (//FIFO的数据位宽默认为8bit//FIFO深度默认为8input                     i_clk,//输入时钟input                      i_rst,//复位信号input                      i_w_en,//写使能信号input                      i_r_en,//读使能信号input      [BUF_WIDTH-1:0] i_data,//写入数据output reg [BUF_WIDTH-1:0] o_data,//读出数据output                     o_buf_empty,//FIFO空标志output                     o_buf_full );//FIFO满标志reg [3:0] fifo_cnt;  //记录FIFO数据个数reg [$clog2(BUF_SIZE)-1:0] r_ptr,w_ptr;  //数据指针为3位宽度,0-7索引,8个数据深度,循环指针0-7-0-7reg [BUF_WIDTH-1:0] buf_mem[0:BUF_SIZE-1]; //定义FIFO大小//判断空满assign o_buf_empty=(fifo_cnt==4'd0)?1'b1:1'b0;assign o_buf_full=(fifo_cnt==4'd8)?1'b1:1'b0;always@(posedge i_clk or posedge i_rst) //用于修改计数器beginif(i_rst)fifo_cnt<=4'd0;else if((!o_buf_full&&i_w_en)&&(!o_buf_empty&&i_r_en)) //同时读写,计数器不变fifo_cnt<=fifo_cnt;else if(!o_buf_full&&i_w_en) //写数据,计数器加1fifo_cnt<=fifo_cnt+1;else if(!o_buf_empty&&i_r_en) //读数据,计数器减1fifo_cnt<=fifo_cnt-1;elsefifo_cnt <= fifo_cnt; //其他情况,计数器不变endalways@(posedge i_clk or posedge i_rst) //读数据beginif(i_rst)o_data<=8'd0;else if(!o_buf_empty&&i_r_en)o_data<=buf_mem[r_ptr];endalways@(posedge i_clk)  //写数据beginif(!o_buf_full&&i_w_en)buf_mem[w_ptr]<=i_data;endalways@(posedge i_clk or posedge i_rst) //读写地址指针变化beginif(i_rst) beginw_ptr <= 0;r_ptr <= 0;endelse beginif(!o_buf_full&&i_w_en) // 写数据,地址加1,溢出后自动回到0开始w_ptr <= w_ptr + 1;if(!o_buf_empty&&i_r_en) // 读数据,地址加1,溢出后自动回到0开始r_ptr <= r_ptr + 1;endendendmodule

4. 同步FIFO仿真结果

同步FIFO仿真测试文件

`timescale 1ns/1nsmodule sync_fifo_tb;reg i_clk,i_rst;reg i_w_en,i_r_en;reg [7:0] i_data;wire [7:0] o_data;wire o_buf_empty,o_buf_full;sync_fifo dut(.i_clk(i_clk),.i_rst(i_rst),.i_data(i_data),.i_w_en(i_w_en),.i_r_en(i_r_en),.o_buf_empty(o_buf_empty),.o_buf_full(o_buf_full),.o_data(o_data));initial begin#30;forever #10 i_clk = ~i_clk; //时钟endreg [7:0] r_data=8'd0;initial begini_clk=1'b0;i_rst=1'b0;i_w_en=1'b0;i_r_en=1'b0;i_data=8'd0;#5 i_rst=1'b1;#10 i_rst=1'b0;push(1);fork  //同时执行push和poppush(2);pop(r_data);joinpush(3);push(4);push(5);push(6);push(7);push(8);push(9);push(10);push(11);push(12);push(13);push(14);push(15);push(16);push(17);pop(r_data);push(18);pop(r_data);pop(r_data);pop(r_data);pop(r_data);push(19);pop(r_data);push(20);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);push(21);pop(r_data);pop(r_data);pop(r_data);pop(r_data);#100 $stop;endtask push (input [7:0] data);if(o_buf_full)$display("Cannot push %d: Buffer Full",data);else begin$display("Push",,data);i_data=data;i_w_en=1;@(posedge i_clk) #4 i_w_en= 0; //时钟上升沿后4ns,写使能清零endendtasktask pop(output[7:0] data);if(o_buf_empty)$display("Cannot Pop: Buffer Empty");else begini_r_en=1;@(posedge i_clk) #4 i_r_en= 0; //时钟上升沿4ns后,读使能清零data = o_data;$display("Pop:",,data);endendtask
endmodule

采用Modelsim仿真得到如下波形:

可以在Modelsim的View——Transcript窗口看到有如下打印信息:

# run -all
# Push   1
# Push   2
# Pop:   1
# Push   3
# Push   4
# Push   5
# Push   6
# Push   7
# Push   8
# Push   9
# Cannot push  10: Buffer Full
# Cannot push  11: Buffer Full
# Cannot push  12: Buffer Full
# Cannot push  13: Buffer Full
# Cannot push  14: Buffer Full
# Cannot push  15: Buffer Full
# Cannot push  16: Buffer Full
# Cannot push  17: Buffer Full
# Pop:   2
# Push  18
# Pop:   3
# Pop:   4
# Pop:   5
# Pop:   6
# Push  19
# Pop:   7
# Push  20
# Pop:   8
# Pop:   9
# Pop:  18
# Pop:  19
# Pop:  20
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Push  21
# Pop:  21
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty

六、异步FIFO

虽然各大厂商都有自己的现成 FIFO IP 可供调用, 而且自己设计异步FIFO是比较复杂的。

但是我们仍然需要学习FIFO的设计原理,这样我们在设计或移植的过程中查找问题起来将有据可循。

所以掌握异步FIFO设计原理是一名合格FPGA工程师的基本功。

1、异步FIFO的电路框图

异步FIFO有两个时钟信号,读和写接口分别采用不同时钟,这两个时钟可能时钟频率不同,也可能时钟相位不同,可能是同源时钟,也可能是不同源时钟。

下面是异步FIFO的系统框图:

可以看到异步FIFO实质上也是基于中间的双口RAM,外加一些读写控制电路组成的。

因为这里读写用的是两个不同的时钟。这将涉及到跨时钟域问题。跨时钟域的电路会带来亚稳态。

2 、亚稳态

外部电路对异步FIFO进行读写操作时,需要根据异步FIFO输出的空满信号来判断是否能继续对异步FIFO进行读或者写的操作。

那么FIFO是如何输出空满信号的呢?

异步FIFO跟同步FIFO一样设置读写指针, 同样也是当读地址指针追上写地址指针(写地址指针跟读地址指针相等),此时FIFO是读空状态。

当写地址指针再次追上读地址指针(写指针跟读地址指针再次相等的时候),此时FIFO是写满状态。

同步FIFO里面指针的比较直接采用了一个额外的计数器统计FIFO里面还剩多少数据。

但异步FIFO是读写时钟不同步的,只能将读时钟域的读地址指针传输到写时钟域然后与写地址指针进行比较判断FIFO是否为满,将写时钟域的写地址指针传输到读时钟域然后与读地址指针进行比较判断FIFO是否为空。

这种跨时钟域的处理就会产生亚稳态。下面举个例子:

同步时钟:

假设数据从0跳变到1,一般数据的跳变不是立马跳变,而是有一个上升时间,有个斜坡。

如果是同步时钟采集数据则不会有什么影响。

第一个时钟周期采集到的是0, 第二个周期电平已经稳定到1。

异步时钟:

如果是异步时钟,比如数据跟clk1是同步的,第2个时钟比第1个时钟滞后一点点,那么第2个时钟在采集数据的时候有可能时钟上升沿正好对应在数据跳变的阶段,那此时读到的数据可能是0, 可能是1, 也可能输出中间级电平,或者是处于振荡状态。

这就是出现了亚稳态。这种不确定的电平输出会沿着信号通道上的电路继续传递下去,对电路造成很大危害,极有可能让整个系统挂死。

亚稳态不可完全避免, 只能通过一些手段如 引入同步机制(打2拍) 以及 格雷码等来降低亚稳态出现的机率。

3、打两拍

异步FIFO的跨时钟域处理所带来的亚稳态可以通过同步机制(打两拍)来降低亚稳态发生的概率。

如下图,A时钟域的数据Q1传递给B时钟域, 当B时钟上升沿来时,可能恰好数据Q1从0跳变到1,这样Q2极有可能出现亚稳态。

如果我们将Q2的值直接拿来用,将会导致亚稳态传播下去。

所以后面再设置一个D触发器继续对Q2进行采样得到Q3。

可能Q2会产生亚稳态,但等到Q3时候电平就会稳定到0或者1(也有可能继续是亚稳态,但一个电路出现亚稳态概率非常低, 然后连续两次出现亚稳态的概率更低, 低到我们可以忽略, 因此我们可以假设打两拍以后Q3 不存在亚稳态了,因此打两拍可以解决亚稳态传播的问题)。

Q1经过B时钟打两拍同步以后的数据Q3才能在B时钟域被使用。

当然,可能有人会问,如果Q1当时跳变为1时却被识别为0 ,对电路就没有影响吗?

答案是,如果只是一个地方判断错误不会有太大影响。怕就怕亚稳态一直被传播下去。

4、格雷码

格雷码是一种相邻数据只有1bit变化的码制。

十进制数自然二进制码格雷码
000000000
100010001
200100011
300110010
401000110
501010111
601100101
701110100
810001100
910011101
1010101111
1110111110
1211001010
1311011011
1411101001
1511111000

如果地址采用二进制码,地址从3(0011)跳变到4(0100),有3个bit发生了变化, 每个bit 都有可能发生亚稳态,那么此时亚稳态出现的几率是1bit 跳变的3倍。

因为格雷码每次跳变只有一个bit,所以采用格雷码将大大降低了亚稳态发生的概率。

格雷码是二进制码右移1位再与原码相异或的结果。

二进制码转格雷码的Verilog代码实现如下:

graycode = (bincode>>1) ^ bincode;

如果二进制变化没有任何规律,那么采用格雷码也可能发生多 bit 的跳变,而 FIFO 设计中的读写地址都是连续变化的,因此格雷码适用于 FIFO 的地址处理。

5、如何判断异步FIFO的空满

(1)空判断

当读地址指针追上写地址指针,写地址指针跟读地址指针相等,此时FIFO是读空状态。

(2)满判断

当写地址指针再次追上读地址指针,写指针跟读地址指针再次相等的时候,此时FIFO是写满状态。

(3)虚空、虚满

当发现计数器不准。

当写地址同步到读时钟域时,这个地址需要在读时钟域打两拍,而这两拍的过程中写控制端还可以继续向FIFO里面写数据,如果此时判断FIFO为空的话,这个空属于虚空。

当读地址同步到写时钟域时 这个地址需要在写时钟域打两拍,而这两拍的过程中读控制端还可以继续从FIFO里面读取 数据,如果此时判断FIFO为满的话,这个满属于虚满。

虚空虚满不会产生错误, 只是影响FIFO 效率。 理解这些原理后,分析问题就知道去哪里分析。

6、如何选择FIFO深度

写比读快

第一种情况,已知连续写数据的长度(Burst Length),那么只需要考虑这段时间内最多会写进多少个数,以及会读走多少个数,二者只差就是FIFO的深度。

读比写快

FIFO的深度为1就可以了。

读写一样

FIFO的深度为1就可以了。

7、异步FIFO的设计代码

(1)顶层模块

module async_fifo#(parameter BUF_SIZE=8, BUF_WIDTH=8)
//FIFO深度默认为8
//FIFO的数据位宽默认为8bit(input  [BUF_WIDTH-1:0] i_wdata,input              i_w_en, i_wclk, i_wrst_n,  //写请求信号,写时钟,写复位input              i_r_en, i_rclk, i_rrst_n,  //读请求信号,读时钟,读复位output [BUF_WIDTH-1:0] o_rdata,output             o_buf_full,output             o_buf_empty);
wire [$clog2(BUF_SIZE)-1:0] waddr, raddr;
wire [$clog2(BUF_SIZE):0]   wptr, rptr, wq2_rptr, rq2_wptr;/*在检测“满”或“空”状态之前,需要将指针同步到其它时钟域时,使用格雷码,可以降低同步过程中亚稳态出现的概率*/sync_r2w I1_sync_r2w(.wq2_rptr(wq2_rptr),.rptr(rptr),.wclk(i_wclk),.wrst_n(i_wrst_n));
sync_w2r I2_sync_w2r (.rq2_wptr(rq2_wptr),.wptr(wptr),.rclk(i_rclk),.rrst_n(i_rrst_n));/* DualRAM */dualram #(BUF_WIDTH, BUF_SIZE) I3_DualRAM(.rdata(o_rdata),.wdata(i_wdata),.waddr(waddr),.raddr(raddr),.wclken(i_w_en),.wclk(i_wclk));/*空、满比较逻辑*/rptr_empty #(BUF_SIZE) I4_rptr_empty(.rempty(o_buf_empty),.raddr(raddr),.rptr(rptr),.rq2_wptr(rq2_wptr),.rinc(i_r_en),.rclk(i_rclk),.rrst_n(i_rrst_n));
wptr_full #(BUF_SIZE) I5_wptr_full(.wfull(o_buf_full),.waddr(waddr),.wptr(wptr),.wq2_rptr(wq2_rptr),.winc(i_w_en),.wclk(i_wclk),.wrst_n(i_wrst_n));
endmodule

(2)双端口RAM模块

双端口RAM模块用于存储数据。

module dualram
#(parameter BUF_WIDTH = 8,   // 数据位宽parameter BUF_SIZE = 8   // FIFO深度
)
(input                       wclken,wclk,input      [$clog2(BUF_SIZE)-1:0]  raddr,     //RAM 读地址input      [$clog2(BUF_SIZE)-1:0]  waddr,     //RAM 写地址input      [BUF_WIDTH-1:0]  wdata,    //写数据output     [BUF_WIDTH-1:0]  rdata      //读数据
);reg [BUF_WIDTH-1:0] Mem[BUF_SIZE-1:0];always@(posedge wclk)beginif(wclken)Mem[waddr] <= wdata;endassign rdata =  Mem[raddr];endmodule

(3)同步模块1

sync_r2w 模块用于读地址同步到写控制端。

module sync_r2w
#(parameter BUF_SIZE = 8)
(output reg [$clog2(BUF_SIZE):0] wq2_rptr,input      [$clog2(BUF_SIZE):0] rptr,input                       wclk, wrst_n
);
reg [$clog2(BUF_SIZE):0] wq1_rptr;always @(posedge wclk or negedge wrst_n) beginif (!wrst_n){wq2_rptr,wq1_rptr} <= 0;else{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};// 将写时钟域传过来的地址打两拍endendmodule

(4)同步模块2

sync_w2r模块用于写地址同步到读控制端。

module sync_w2r
#(parameter BUF_SIZE = 8)
(output reg  [$clog2(BUF_SIZE)+1:0] rq2_wptr,input         [$clog2(BUF_SIZE)+1:0] wptr,input         rclk, rrst_n
);        reg [$clog2(BUF_SIZE)+1:0] rq1_wptr;always @(posedge rclk or negedge rrst_n) beginif (!rrst_n){rq2_wptr,rq1_wptr} <= 0;else{rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
end
endmodule

(5)空判断模块

空判断模块用于判断是否可以读取数据。

读操作时,读使能rinc有效且FIFO未空。

module rptr_empty
#(parameter BUF_SIZE = 8)
(output reg rempty,                          //输出空信号output     [$clog2(BUF_SIZE)-1:0] raddr,   //输出读数据地址output reg [$clog2(BUF_SIZE):0]  rptr,  //读数据指针input       [$clog2(BUF_SIZE):0] rq2_wptr, //写数据指针的格雷码经过打两拍后输入input       rinc, rclk, rrst_n);
reg  [$clog2(BUF_SIZE):0] rbin;
wire [$clog2(BUF_SIZE):0] rgraynext, rbinnext;
wire  rempty_val;always @(posedge rclk or negedge rrst_n)if (!rrst_n)beginrbin <= 0;rptr <= 0;endelsebeginrbin <= rbinnext ;rptr <= rgraynext;end
// gray码计数逻辑
assign rbinnext = !rempty ? (rbin + rinc) : rbin; //如果为空,则指针不变,如果不为空,指针+1
assign rgraynext = (rbinnext>>1) ^ rbinnext;      //二进制到gray码的转换assign raddr = rbin[$clog2(BUF_SIZE)-1:0];/*读指针是一个n位的gray码计数器,比FIFO寻址所需的位宽大一位
当系统复位或者读指针和同步过来的写指针完全相等时(包括MSB),说明二者折回次数一致,FIFO为空*/
assign rempty_val = (rgraynext == rq2_wptr);always @(posedge rclk or negedge rrst_n)
if (!rrst_n)rempty <= 1'b1;
elserempty <= rempty_val;
endmodule

(6)满判断模块

满判断模块用于判断是否可以写入数据。

写操作时,写使能winc有效且FIFO未满。

module wptr_full
#(parameter BUF_SIZE = 8
)
(output reg                wfull,                     //输出满信号output     [$clog2(BUF_SIZE)-1:0] waddr, //输出写地址output reg [$clog2(BUF_SIZE):0]  wptr,   //输出写指针input      [$clog2(BUF_SIZE):0]  wq2_rptr, //读指针的格雷码打两拍后输入input                     winc, wclk, wrst_n);reg  [$clog2(BUF_SIZE):0] wbin;
wire [$clog2(BUF_SIZE):0] wgraynext, wbinnext;
wire wfull_val;
// GRAYSTYLE2 pointer
always @(posedge wclk or negedge wrst_n)if (!wrst_n)beginwbin <= 0;wptr <= 0;endelsebeginwbin <= wbinnext;wptr <= wgraynext;end
//gray 码计数逻辑
assign wbinnext  = !wfull ? (wbin + winc) : wbin;
assign wgraynext = (wbinnext>>1) ^ wbinnext;assign waddr = wbin[$clog2(BUF_SIZE)-1:0];/*由于满标志在写时钟域产生,因此比较安全的做法是将读指针同步到写时钟域*/assign wfull_val = (wgraynext=={~wq2_rptr[$clog2(BUF_SIZE):$clog2(BUF_SIZE)-1],wq2_rptr[$clog2(BUF_SIZE)-2:0]});
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)wfull <= 1'b0;
elsewfull <= wfull_val;
endmodule

异步FIFO设计的整体RTL Viewer如下图所示:

8、 仿真

(1)异步FIFO仿真文件

`timescale 1 ps/ 1 ps
module async_fifo_vlg_tst();reg i_r_en;reg i_rclk;reg i_rrst_n;reg i_w_en;reg i_wclk;reg i_wrst_n;reg [7:0] i_wdata;wire o_buf_empty;wire o_buf_full;wire [7:0]  o_rdata;async_fifo i1 (.i_r_en(i_r_en),.i_rclk(i_rclk),.i_rrst_n(i_rrst_n),.i_w_en(i_w_en),.i_wclk(i_wclk),.i_wdata(i_wdata),.i_wrst_n(i_wrst_n),.o_buf_empty(o_buf_empty),.o_buf_full(o_buf_full),.o_rdata(o_rdata)
);always #10 i_wclk = ~i_wclk;always #5 i_rclk = ~i_rclk;reg [7:0] r_data=8'd0;initial begini_wclk=1'b0;i_rclk=1'b0;i_wrst_n=1'b1;i_rrst_n=1'b1;i_w_en=1'b0;i_r_en=1'b0;i_wdata=8'd0;#1 i_wrst_n=1'b0;i_rrst_n=1'b0;#1 i_wrst_n=1'b1;i_rrst_n=1'b1;#20 push(1);push(2);//pop(r_data);push(3);push(4);push(5);push(6);push(7);push(8);push(9);pop(r_data);push(10);push(11);push(12);push(13);push(14);push(15);push(16);pop(r_data);push(17);pop(r_data);push(18);pop(r_data);pop(r_data);pop(r_data);pop(r_data);push(19);pop(r_data);push(20);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);pop(r_data);push(21);pop(r_data);pop(r_data);pop(r_data);pop(r_data);#100 $stop;endtask push (input [7:0] data);if(o_buf_full)$display("Cannot push %d: Buffer Full",data);else begin$display("Push",,data);i_wdata=data;i_w_en=1;@(posedge i_wclk) #4 i_w_en= 0; //时钟上升沿后4ns,写使能清零endendtasktask pop(output[7:0] data);if(o_buf_empty)$display("Cannot Pop: Buffer Empty");else begindata = o_rdata;$display("Pop:",,data);i_r_en=1;@(posedge i_rclk) #4 i_r_en= 0; //时钟上升沿4ns后,读使能清零endendtaskendmodule

这里选择的是读写频率相同,但读是在时钟下降沿, 写在时钟的上升沿。

(2)异步FIFO仿真结果

打开Quartus 的 菜单栏的 Tools——Run Simulation Tool——RTL Simulation看到波形如下:


当复位撤销(复位信号低有效)之后,在写使能 i_w_en 拉高有效之后,写数据也开始变化:


empty 空标记也开始在几拍之后变为非空(有一个写到读侧的异步转换,打了两拍):

当读使能i_ r_en 拉高有效之后,读数据在下一拍也开始变化:

可以在Modelsim的View——Transcript窗口看到有如下打印信息:

# run -all
# Push   1
# Push   2
# Push   3
# Push   4
# Push   5
# Push   6
# Push   7
# Push   8
# Cannot push   9: Buffer Full
# Pop:   1
# Cannot push  10: Buffer Full
# Cannot push  11: Buffer Full
# Cannot push  12: Buffer Full
# Cannot push  13: Buffer Full
# Cannot push  14: Buffer Full
# Cannot push  15: Buffer Full
# Cannot push  16: Buffer Full
# Pop:   2
# Cannot push  17: Buffer Full
# Pop:   3
# Cannot push  18: Buffer Full
# Pop:   4
# Pop:   5
# Pop:   6
# Pop:   7
# Push  19
# Pop:   8
# Push  20
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Push  21
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# Cannot Pop: Buffer Empty
# ** Note: $stop    :

七、FIFO应用实例 (ADC)

一、实验前提

内容参考:LTC2308 ADC器件解读以及LTC2308控制器代码解读

二、FIFO IP调用

自己设计FIFO的目的一般是为了学习一下FIFO的结构,设计思路等,如果是一般的项目设计 ,建议可以直接调用厂商提供的FIFO IP 进行简单配置会不容易出错一点。

使用Quartus II软件提供的免费FIFO IP核,Quartus II软件为用户提供了友好的图形化界面方便用户对FIFO的各种参数和结构进行配置,生成的FIFO IP核针对Altera不同系列的器件,还可以实现结构上的优化。

Quartus 里面提供的FIFO可分为两种结构:单时钟FIFO(SCFIFO)和双时钟FIFO(DCFIFO), 本实验我们调用DCFIFO。

原文链接

  • 掰开揉碎讲 FIFO(同步FIFO和异步FIFO)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/41764.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

react v18 less使用(craco)

方案一、弹出配置&#xff08;不推荐&#xff09; 安装依赖&#xff1a;yarn add less less-loader 首先 执行 yarn eject 弹出配置项文件&#xff08;注意&#xff1a;弹出配置不可逆&#xff01;&#xff09; 在 config 文件夹中 找到 webpack.config.js&#xff0c;在如图…

爆破片和安全阀

一、爆破片介绍 爆破片是一种用于安全释放压力的结构&#xff0c;通常应用于压力容器、管道和设备中&#xff0c;以防止由于压力过高而导致的灾难性故障。在压力超过设定值时&#xff0c;爆破片会破裂&#xff0c;从而迅速将过压泄放&#xff0c;保护设备和人员安全 爆破片通常…

java Web 优秀本科毕业论文系统用eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 JSP 优秀本科毕业论文系统是一套完善的web设计系统&#xff0c;对理解JSP java serlvet 编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0&a…

Selenium的这些自动化测试技巧你知道几个?

Selenium自动化测试技巧 与以前瀑布式开发模式不同&#xff0c;现在软件测试人员具有使用自动化工具执行测试用例套件的优势&#xff0c;而以前&#xff0c;测试人员习惯于通过测试脚本执行来完成测试。 但自动化测试的目的不是完全摆脱手动测试&#xff0c;而是最大程度地减少…

24.【C语言】getchar putchar的使用

1.基本作用 用户输入字符&#xff0c;getchar()获取字符&#xff08;含\n:即键入的Enter&#xff09;&#xff08;字符本质上是以ASCII值或EOF&#xff08;-1&#xff09;存储的&#xff09;&#xff08;与scanf有区别&#xff09; putchar() 打印字符&#xff08;把得到的A…

嵌入式系统中状态机实现详解

嵌入式开发中系统经常处于某种状态,如何处理呢?接下来分析一下,状态机的实现无非就是 3 个要素:状态、事件、响应。转换成具体的行为就 3 句话。 发生了什么事? 现在系统处在什么状态? 在这样的状态下发生了这样的事,系统要干什么? 用 C 语言实现状态机主要有 3 种方法…

LeetCode 60.排序排列(dfs暴力)

给出集合 [1,2,3,...,n]&#xff0c;其所有元素共有 n! 种排列。 按大小顺序列出所有排列情况&#xff0c;并一一标记&#xff0c;当 n 3 时, 所有排列如下&#xff1a; "123""132""213""231""312""321" 给定…

机器学习——岭回归

1、岭回归与线性回归的区别 岭回归&#xff08;Ridge Regression&#xff09;和线性回归&#xff08;Linear Regression&#xff09;都是用于回归分析的统计方法&#xff0c;但它们在处理方式和应用场景上有一些关键的区别&#xff1a; a)基本概念 线性回归&#xff1a;目标是…

在pycharm里如何使用Jetbrains AI Assistant

ai assistant激活成功后&#xff0c;如图 ai assistant渠道&#xff1a;https://web.52shizhan.cn/activity/ai-assistant 在去年五月份的 Google I/O 2023 上&#xff0c;Google 为 Android Studio 推出了 Studio Bot 功能&#xff0c;使用了谷歌编码基础模型 Codey,Codey 是…

ABAP 发送正文含图片邮件

背景&#xff1a; 客户要求系统发送的邮件内容中含logo图片 解决&#xff1a; 参考outlook中带图片的邮件&#xff0c;有两种形式&#xff0c;一种为url链接&#xff0c;需要点击下载才展示图片&#xff0c;一种为直接显示&#xff1b;如果使用ABAP发送该类型的邮件&#xff0…

【密码学】RSA公钥加密算法

文章目录 RSA定义RSA加密与解密加密解密 生成密钥对一个例子密钥对生成加密解密 对RSA的攻击通过密文来求得明文通过暴力破解来找出D通过E和N求出D对N进行质因数分解通过推测p和q进行攻击 中间人攻击 一些思考公钥密码比对称密码的机密性更高&#xff1f;对称密码会消失&#x…

SQL使用join查询方式找出没有分类的电影id以及名称

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 现有电影信息…

分库分表真的适合你的系统吗?

曾几何时&#xff0c;“并发高就分库&#xff0c;数据大就分表”已经成了处理 MySQL 数据增长问题的圣经。 面试官喜欢问&#xff0c;博主喜欢写&#xff0c;候选人也喜欢背&#xff0c;似乎已经形成了一个闭环。 但你有没有思考过&#xff0c;分库分表真的适合你的系统吗&am…

QCustomPlot+ vs2022+ qt

零、printSupport 步骤一&#xff1a;下载QCustomPlot 访问QCustomPlot的官网 QCustomPlot 下载最新版本的源代码。 步骤二&#xff1a;配置项目 创建新的Qt项目&#xff1a; 打开VS2022&#xff0c;创建一个新的Qt Widgets Application项目。 将QCustomPlot源代码添加到项目…

场景管理分析平台介绍

在数字化浪潮的推动下&#xff0c;数据已成为企业决策的重要依据。特别是在智能驾驶、虚拟现实和物联网等领域&#xff0c;场景数据的高效管理和利用至关重要。在智能驾驶领域面对海量的场景数据&#xff0c;如何高效处理、精准分析&#xff0c;并将其转化为有价值的决策支持&a…

第一次作业

作业1 1.代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head&…

玩转Easysearch语法

Elasticsearch 是一个基于Apache Lucene的开源分布式搜索和分析引擎&#xff0c;广泛应用于全文搜索、结构化搜索、分析等多种场景。 Easysearch 作为Elasticsearch 的国产化替代方案&#xff0c;不仅保持了与原生Elasticsearch 的高度兼容性&#xff0c;还在功能、性能、稳定性…

Maven 分模块设计与开发 继承

介绍 在 Maven 中进行分模块设计&#xff08;multi-module project&#xff09;&#xff0c;可以帮助将一个大型项目分解为更小、更易管理的模块。这种设计方式有助于提高项目的可维护性、复用性和团队协作效率。 继承关系 目录结构 引入父Maven 父坐标 在子项目中引入父亲…

第一百四十五节 Java数据类型教程 - Java字符串类型

Java数据类型教程 - Java字符串类型 零个或多个字符的序列称为字符串。 在Java程序中&#xff0c;字符串由java.lang.String类的对象表示。 String类是不可变的。 String对象的内容在创建后无法修改。 String类有两个伴随类&#xff0c;java.lang.StringBuilder和java.lang.…

python-开关灯(赛氪OJ)

[题目描述] 假设有 N 盏灯&#xff08;N 为不大于 5000 的正整数&#xff09;&#xff0c;从 1 到到 N 按顺序依次编号&#xff0c;初始时全部处于开启状态&#xff1b;第一个人&#xff08; 1 号&#xff09;将灯全部关闭&#xff0c;第二个人&#xff08; 2 号&#xff09;将…