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

news2025/6/24 13:34:58

文章目录

  • 一、什么是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深度默认为8


    input                     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-7
   reg [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) //用于修改计数器
        begin
            if(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) //写数据,计数器加1
                fifo_cnt<=fifo_cnt+1;
            else if(!o_buf_empty&&i_r_en) //读数据,计数器减1
                fifo_cnt<=fifo_cnt-1;
            else
                fifo_cnt <= fifo_cnt; //其他情况,计数器不变
        end

    always@(posedge i_clk or posedge i_rst) //读数据
        begin
            if(i_rst)
                o_data<=8'd0;
            else if(!o_buf_empty&&i_r_en)
                o_data<=buf_mem[r_ptr];
        end

    always@(posedge i_clk)  //写数据
        begin
            if(!o_buf_full&&i_w_en)
                buf_mem[w_ptr]<=i_data;
        end

    always@(posedge i_clk or posedge i_rst) //读写地址指针变化
        begin
            if(i_rst) begin
                w_ptr <= 0;
                r_ptr <= 0;
            end
            else begin
                if(!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;
            end
        end

endmodule

4. 同步FIFO仿真结果

同步FIFO仿真测试文件

`timescale 1ns/1ns

module 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; //时钟
    end
    reg [7:0] r_data=8'd0;
    initial begin
          i_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和pop
              push(2);
               pop(r_data);
        join
           push(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;
    end

    task 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,写使能清零
        end
    endtask

    task pop(output[7:0] data);
        if(o_buf_empty)
            $display("Cannot Pop: Buffer Empty");
        else begin
            i_r_en=1;
            @(posedge i_clk) #4 i_r_en= 0; //时钟上升沿4ns后,读使能清零
            data = o_data;
            $display("Pop:",,data);
        end
    endtask
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)
        begin
            if(wclken)
                Mem[waddr] <= wdata;
        end

    assign 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) begin
    if (!wrst_n)
        {wq2_rptr,wq1_rptr} <= 0;
    else
        {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};// 将写时钟域传过来的地址打两拍

end

endmodule

(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) begin
    if (!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)
        begin
            rbin <= 0;
            rptr <= 0;
        end
    else
        begin
            rbin <= 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;
else
    rempty <= 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)
    begin
        wbin <= 0;
        wptr <= 0;
    end
    else
    begin
        wbin <= 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;
else
    wfull <= 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 begin
          i_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;
    end

    task 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,写使能清零
        end
    endtask

    task pop(output[7:0] data);
        if(o_buf_empty)
            $display("Cannot Pop: Buffer Empty");
        else begin
            data = o_rdata;
            $display("Pop:",,data);
            i_r_en=1;
            @(posedge i_rclk) #4 i_r_en= 0; //时钟上升沿4ns后,读使能清零
        end
    endtask


            endmodule

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

(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.coloradmin.cn/o/1900537.html

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

相关文章

Selenium的自动化测试技巧有多少?【建议收藏】

Selenium是一个用于自动化Web应用程序测试的工具。它提供了一组API&#xff0c;允许用户与Web浏览器进行交互&#xff0c;来执行各种自动化测试任务。本文将从零开始&#xff0c;详细介绍Selenium的自动化测试技巧。 第一步&#xff1a;安装Selenium 首先&#xff0c;您需要安…

一站式天气预报解决方案,API接口轻松接入

天气对我们的日常生活有着重要的影响&#xff0c;无论是出门旅行还是安排工作&#xff0c;都需要提前了解天气情况。WAPI平台提供了一站式天气预报解决方案&#xff0c;通过简单的API接口&#xff0c;轻松获取各类天气预报数据。 这个API接口提供了丰富的天气预报信息&#xf…

mac怎么压缩pdf文件,mac压缩pdf文件大小不改变清晰度

在数字化时代&#xff0c;pdf文件因其良好的兼容性和稳定性&#xff0c;已经成为我们日常办公和学习中不可或缺的文件格式。然而&#xff0c;随着文件内容的增多&#xff0c;pdf文件的体积也往往会变得越来越大&#xff0c;给文件的传输和存储带来不便。本文将为你介绍几种简单…

WACV2023论文速览域迁移Domain相关

Paper1 CellTranspose: Few-Shot Domain Adaptation for Cellular Instance Segmentation 摘要原文: Automated cellular instance segmentation is a process utilized for accelerating biological research for the past two decades, and recent advancements have produc…

vue中自定义设置多语言(包括使用vue-i18n),并且运行js脚本自动生成多语言文件

在项目中需要进行多个国家语言的切换时&#xff0c;可以用到下面方法其中一个 一、自定义设置多语言 方法一: 可以自己编写一个设置多语言文件 在项目新建js文件&#xff0c;命名为&#xff1a;language.js&#xff0c;代码如下 // language.js 文档 let languagePage {CN…

深入分析 Android BroadcastReceiver (八)

文章目录 深入分析 Android BroadcastReceiver (八)1. 系统与自定义实现1.1 系统广播机制1.1.1 系统广播的实现原理1.1.2 系统广播的源码分析 1.2 自定义广播机制1.2.1 自定义广播的实现步骤1.2.2 自定义广播的源码分析 2. 广播机制设计的初衷与优势2.1 设计初衷2.2 优势 3. 总…

(七)[重制]C++命名空间与标准模板库(STL)

​ 引言 在专栏C教程的第六篇C中的结构体与联合体中&#xff0c;介绍了C中的结构体和联合体&#xff0c;包括它们的定义、初始化、内存布局和对齐&#xff0c;以及作为函数参数和返回值的应用。在专栏C教程的第七篇中&#xff0c;我们将深入了解C中的命名空间&#xff08;nam…

leetcode判断二分图

判断二分图 图的问题肯定要用到深度优先遍历或者广度优先遍历&#xff0c;但又不是单纯的深度优先遍历算法和广度优先遍历算法&#xff0c;而是需要在遍历的过程中加入与解决题目相关的逻辑。 题干中说了&#xff0c;这个图可能不是连通图&#xff0c;这个提示有什么作用呢&a…

x.java => 字节码文件x.class => 运行

使用javac.exe对.java进行编译&#xff0c;编译成.class字节码文件 使用java.exe启动java虚拟机执行.class字节码 JVM是一个系统进程&#xff0c;这个进程运行会读取.class字节码文件&#xff0c;一旦他抢到了CPU的执行权&#xff0c;就会以那个类的main方法来执行程序逻辑。

JVM专题之垃圾收集算法

标记清除算法 第一步:标记 (找出内存中需要回收的对象,并且把它们标记出来) 第二步:清除 (清除掉被标记需要回收的对象,释放出对应的内存空间) 缺点: 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需 要分配较大对象时,无法找到…

1119 胖达与盆盆奶

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

Ubuntu 20版本安装Redis教程

第一步 切换到root用户&#xff0c;使用su命令&#xff0c;进行切换。 输入&#xff1a; su - 第二步 使用apt命令来搜索redis的软件包&#xff0c;输入命令&#xff1a;apt search redis 第三步 选择需要的redis版本进行安装&#xff0c;本次选择默认版本&#xff0c;redis5.…

多点mGRE over IPsecVPN 配置及NHRP的使用

一、实验目的及拓扑 1、实验思路&#xff1a;FW1为总部固定IP&#xff0c;FW2和FW3为分支动态地址&#xff0c;通过mGRE over IPsec实现&#xff0c;并加载NHR解决多点隧道目的地址问题 2、网络拓扑 二、基本配置 &#xff08;一&#xff09;配置相关接口地址&#xff0c;并…

项目实战--Spring Boot与PageHelper的集成及线程污染解决

一、PageHelper使用背景 公司要做个简单管理系统&#xff0c;要我搭建Spring BootMyBatisPageHelperRedis的项目框架然后交i给实习生来开发。这个其实很简单&#xff0c;但是遇到搭建和使用过程中PageHelper有好多小坑&#xff0c;就记录一下&#xff0c;避免再踩。 版本选择&…

短视频博主:成都柏煜文化传媒有限公司

短视频博主&#xff1a;数字时代的新星&#xff0c;创意与梦想的舞台 在移动互联网的浪潮中&#xff0c;短视频以其独特的魅力迅速崛起&#xff0c;成为连接亿万用户、展现生活百态的重要窗口。成都柏煜文化传媒有限公司 而在这片充满无限可能的土地上&#xff0c;短视频博主…

【Python机器学习】处理文本数据——多个单词的词袋(n元分词)

使用词袋表示的主要缺点之一就是完全舍弃了单词顺序。因此“its bad&#xff0c;not good at all”和“its good&#xff0c;not bad at all”这两个字符串的词袋表示完全相同&#xff0c;尽管它们的含义相反。幸运的是&#xff0c;使用词袋表示时有一种获取上下文的方法&#…

Fastjson首字母大小写问题

1、问题 使用Fastjson转json之后发现首字母小写。实体类如下&#xff1a; Data public class DataIdentity {private String BYDBSM;private String SNWRSSJSJ;private Integer CJFS 20; } 测试代码如下&#xff1a; public static void main(String[] args) {DataIdentit…

C# Application.DoEvents()的作用

文章目录 1、详解 Application.DoEvents()2、示例处理用户事件响应系统事件控制台输出游戏和多媒体应用与操作系统的交互 3、注意事项总结 Application.DoEvents() 是 .NET 框架中的一个方法&#xff0c;它主要用于处理消息队列中的事件。在 Windows 应用程序中&#xff0c;当一…

Node.js实现一个文章生成器

前言 本文将从零开始&#xff0c;讲解如何使用Node.js来实现一个文章生成器 node里面有很多优秀的模块&#xff0c;现在我们就借助node的fs模块来操控文本&#xff0c;来实现我们想要的效果 效果展示 体验 fs 首先我们先创建一个json文件 里面放一些内容 接下来我们书写代码…

java花店管理系统eclipse开发mysql数据库

1 绪论 1.1 系统开发目的 随着人们物质生活水平和经济水平的不断提高&#xff0c;室内绿化布置、家庭园艺装饰、礼仪鲜花等日益受到重视和青睐&#xff0c;以及送鲜花给亲朋好友来表达自己的情谊。传统的花店对于信息的管理的主要方式是基于文本、表格等纸质手工处理&#xf…