芯片基识 | 掰开揉碎讲 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.rhkb.cn/news/370053.html

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

相关文章

Spring boot 更改启动LOGO

在resources目录下创建banner.txt文件&#xff0c;然后编辑对应的图案即可 注释工具 Spring Boot Version: ${spring-boot.version},-.___,---.__ /|\ __,---,___,- \ -.____,- | -.____,- // -., | ~\ /~ | …

Go语言--工程管理、临时/永久设置GOPATH、main函数以及init函数

工作区 Go 代码必须放在工作区中。工作区其实就是一个对应于特定工程的目录&#xff0c;它应包含3个子目录:src 目录、pkg目录和bin 目录。 src 目录:用于以代码包的形式组织并保存 Go源码文件。(比如:.go.chs等)pkg 目录:用于存放经由 go install 命令构建安装后的代码包(包…

1119 胖达与盆盆奶

solution 递推&#xff1a;序列的每一位所需要计算的值都可以通过该位左右两侧的结果计算得到&#xff0c;就可以考虑所谓的“左右两侧的结果”是否能通过递推进行预处理来得到&#xff0c;以避免后续使用中的反复求解。 #include<iostream> using namespace std; cons…

Xilinx FPGA:vivado关于fifo的一些零碎知识

一、FIFO概念 先进先出&#xff0c;是一种组织和操作数据结构的方法。在硬件应用中&#xff0c;FIFO一般由一些读写指针&#xff0c;存储和控制的逻辑组成。 二、xilinx中生成的FIFO的存储类型 &#xff08;1&#xff09;shift register FIFO : 移位寄存器FIFO&#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…

CTF常用sql注入(三)无列名注入

0x06 无列名 适用于无法正确的查出结果&#xff0c;比如把information_schema给过滤了 join 联合 select * from users;select 1,2,3 union select * from users;列名被替换成了1,2,3&#xff0c; 我们再利用子查询和别名查 select 2 from (select 1,2,3 union select * f…

笔记12:if语句编程练习(打印输出三个数据中的最小值)

输入三个数&#xff0c;分别放入变量x&#xff0c;y&#xff0c;z中 打印输入数据中最小的那一个数 解决方案1 定义中间变量 t 1.比较x和y的大小关系&#xff0c;将较小的值赋值给t 2.比较t和z的大小关系&#xff0c;将较小的值赋值给t 3.t 中保存的就是3个数中的较小值 &am…

【数据结构】常见四类排序算法

1. 插入排序 1.1基本思想&#xff1a; 直接插入排序是一种简单的插入排序法&#xff0c;其基本思想是&#xff1a;把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为止&#xff0c;得到一个新的有序序列 。实际中我们…

十大排序:插入/希尔/选择/堆/冒泡/快速/归并/计数/基数/桶排序 汇总(C语言)

目录 前言非线性时间比较类插入排序(1) 直接插入排序(2) 希尔排序 选择排序(3) 选择排序优化版(4) 堆排序 交换排序(5) 冒泡排序(6) 快速排序hoare版本挖坑版前后指针版非递归版 归并排序(7) 归并排序递归版非递归版 线性时间比较类(8) 计数排序基数排序与桶排序 总结 前言 在计…

Redis哨兵和集群模式

特性哨兵模式集群模式高可用性是是数据分片否是水平扩展否是配置复杂度低高管理复杂度低高多键操作支持是否&#xff08;有限制&#xff09; 哨兵模式 原理&#xff1a; Redis 哨兵模式是一种高可用性解决方案&#xff0c;它通过监控 Redis 主从架构&#xff0c;自动执行故障…

STM32第十五课:LCD屏幕及应用

文章目录 需求一、LCD显示屏二、全屏图片三、数据显示1.显示欢迎词2.显示温湿度3.显示当前时间 四、需求实现代码 需求 1.在LCD屏上显示一张全屏图片。 2.在LCD屏上显示当前时间&#xff0c;温度&#xff0c;湿度。 一、LCD显示屏 液晶显示器&#xff0c;简称 LCD(Liquid Cry…

vulhub靶场之DEVGURU:1

1 信息收集 1.1 主机发现 arp-scan -l 发现主机IP地址为“192.168.1.11 1.2 端口发现 nmap -sS -sV -A -T5 -p- 192.168.1.11 发现端口为&#xff1a;22&#xff0c;80&#xff0c;8585 1.3 目录扫描 dirsearch -u 192.168.1.11 发现存在git泄露 2 文件和端口访问 2…

【JavaEE精炼宝库】文件操作(1)——基本知识 | 操作文件——打开实用性编程的大门

目录 一、文件的基本知识1.1 文件的基本概念&#xff1a;1.2 树型结构组织和目录&#xff1a;1.3 文件路径&#xff08;Path&#xff09;&#xff1a;1.4 二进制文件 VS 文本文件&#xff1a;1.5 其它&#xff1a; 二、Java 操作文件2.1 方法说明&#xff1a;2.2 使用演示&…

Python学习笔记29:进阶篇(十八)常见标准库使用之质量控制中的数据清洗

前言 本文是根据python官方教程中标准库模块的介绍&#xff0c;自己查询资料并整理&#xff0c;编写代码示例做出的学习笔记。 根据模块知识&#xff0c;一次讲解单个或者多个模块的内容。 教程链接&#xff1a;https://docs.python.org/zh-cn/3/tutorial/index.html 质量控制…

绿色金融相关数据合集(2007-2024年 具体看数据类型)

数据类型&#xff1a; 1.绿色债券数据&#xff1a;2014-2023 2.绿色信贷相关数据&#xff1a;2007-2022 3.全国各省及地级市绿色金融指数&#xff1a;1990-2022 4.碳排放权交易明细数据&#xff1a;2013-2024 5.绿色金融试点DID数据&#xff1a;2010-2023 数据来源&#…

基于图像处理的滑块验证码匹配技术

滑块验证码是一种常见的验证码形式&#xff0c;通过拖动滑块与背景图像中的缺口进行匹配&#xff0c;验证用户是否为真人。本文将详细介绍基于图像处理的滑块验证码匹配技术&#xff0c;并提供优化代码以提高滑块位置偏移量的准确度&#xff0c;尤其是在背景图滑块阴影较浅的情…

ES6模块化学习

1. 回顾&#xff1a;node.js 中如何实现模块化 node.js 遵循了 CommonJS 的模块化规范。其中&#xff1a; 导入其它模块使用 require() 方法 模块对外共享成员使用 module.exports 对象 模块化的好处&#xff1a; 大家都遵守同样的模块化规范写代码&#xff…

计算机网络体系结构详解:协议与分层

在学习计算机网络时&#xff0c;理解网络协议与分层体系结构是至关重要的。本文将详细介绍这些概念&#xff0c;帮助基础小白快速入门。 1. 什么是网络协议 网络协议是计算机网络中用于数据交换的规则和标准。这些规则规定了数据格式、时序以及发送和接收数据时的动作。网络协…

全新桌面编辑器

目录 前言 一、链接 ONLYOFFICE 8.1版本 官网下载链接&#xff1a; ONLYOFFICE 在线工具&#xff1a; 下载版本推荐&#xff1a; 二、使用体验 1. 界面设计&#xff1a; 2. 文档编辑功能&#xff1a; 3. 电子表格功能&#xff1a; 4. 演示文稿功能&#xff1a; 5.PDF编…

面向对象案例:电影院

TOC 思路 代码 结构 具体代码 Movie.java public class Movie {//一共七个private int id;private String name;private double price;private double score;private String director;private String actors;private String info;//get和setpublic int getId() {return id;…