芯片基识 | 掰开揉碎讲 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深度默认为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.mfbz.cn/a/777321.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3D Web轻量化平台HOOPS Web Platform的功能与应用分析

随着3D技术在多个行业的广泛应用&#xff0c;对于3D模型轻量化的需求日益增长。HOOPS Web Platform作为一个先进的3D模型轻量化平台&#xff0c;为开发人员提供了一整套工具来构建和部署基于Web的工程应用程序。本文将分析HOOPS Web Platform的核心功能和它在不同领域的应用情况…

VBA初学:零件成本统计之一(任务汇总)

经过前期一年多对金蝶K3生产任务流程和操作的改造和优化&#xff0c;现在总算可以将零件加工各个环节的成本进行归集了。 原本想写存储过程&#xff0c;通过直接SQL报表做到K3中去的&#xff0c;但财务原本就是用EXCEL&#xff0c;可以方便调整和保存&#xff0c;加上还有一部分…

破解在制品管理不透明难题

在快节奏的现代工业浪潮中&#xff0c;每一个细微的管理环节都直接关系到企业的竞争力与盈利能力。在车间生产中&#xff0c;在制品管理流程不透明是一个常见问题&#xff0c;它可能导致生产效率低下、成本增加、库存积压以及沟通障碍等负面影响。 在制品管理流程不透明&#x…

ETAS工具导入Com Arxml修改步骤

文章目录 前言Confgen之前的更改Confgen之后的修改CANCanIfComComMEcuM修改CanNmCanSMDCMCanTp生成RTE过程报错修改DEXT-诊断文件修改Extract问题总结前言 通讯协议栈开发一般通过导入DBC实现,ETAS工具本身导入DBC也是生成arxml后执行cfggen,本文介绍直接导入客户提供的arxml…

8种数据迁移工具

前言 最近有些小伙伴问我&#xff0c;ETL数据迁移工具该用哪些。 ETL(是Extract-Transform-Load的缩写&#xff0c;即数据抽取、转换、装载的过程)&#xff0c;对于企业应用来说&#xff0c;我们经常会遇到各种数据的处理、转换、迁移的场景。 今天特地给大家汇总了一些目前…

迭代加深——AcWing 170. 加成序列

迭代加深 定义 迭代加深搜索&#xff08;Iterative Deepening Depth-First Search, IDS&#xff09;是一种结合了深度优先搜索&#xff08;DFS&#xff09;和广度优先搜索&#xff08;BFS&#xff09;特点的算法。它通过限制搜索树的深度来控制搜索范围&#xff0c;起初以较小…

CTFShow的RE题(一)

RE2 1.中文字符的显示 2.对文件的读取操作 3.RC4加密 &#xff08;有一点是魔改的&#xff09; 4.enflag.txt文件里面的密文是ASCII编码之后的数据(可以放ida中) 也可以放到 010 里&#xff08;推荐&#xff09; encDH~mqqvqxB^||zllJq~jkwpmvez{ key for i in enc:keychr…

程序员下班为什么不关电脑?难道在偷偷加班?!

不管是周围的程序员朋友还是网上的很多程序员朋友&#xff0c;在下班后都是习惯不关电脑的&#xff0c;关上显示器&#xff0c;拿上手机&#xff0c;快乐下班&#xff01; 那么&#xff0c;为什么程序员下班都不关电脑&#xff1f;难道他们在偷偷加班&#xff1f; 其实&#x…

elasticsearch源码分析-04集群状态发布

集群状态发布 cluster模块封装了在集群层面执行的任务&#xff0c;如集群健康、集群级元信息管理、分片分配给节点、节点管理等。集群任务执行之后可能会产生新的集群状态&#xff0c;如果产生新的集群状态主节点会将集群状态广播给其他节点。 集群状态封装在clusterState中&…

基于Qt实现的PDF阅读、编辑工具

记录一下实现pdf工具功能 语言&#xff1a;c、qt IDE&#xff1a;vs2017 环境&#xff1a;win10 一、功能演示&#xff1a; 二、功能介绍&#xff1a; 1.基于saribbon主体界面框架&#xff0c;该框架主要是为了实现类似word导航项 2.加载PDF放大缩小以及预览功能 3.pdf页面跳转…

Qt 网络编程 网络信息获取操作

学习目标&#xff1a;网络信息获取操作 前置环境 运行环境:qt creator 4.12 学习内容 一、Qt 网络编程基础 Qt 直接提供了网络编程模块,包括基于 TCP/IP 的客户端和服务器相关类,如 QTcpSocket/QTcpServer 和 QUdpSocket,以及实现 HTTP、FTP 等协议的高级类,如 QNetworkRe…

SPIN-Diffusion:自我博弈微调提升文本到图像扩散模型性能

扩散模型作为生成AI的关键实体&#xff0c;已经在多个领域展现出了卓越的能力。然而&#xff0c;现有的扩散模型&#xff0c;如Stable Diffusion和SDXL&#xff0c;通常在预训练阶段后需要进行微调以更好地符合人类偏好。最近&#xff0c;研究者们开始尝试使用强化学习&#xf…

矩阵键盘与密码锁

目录 1.矩阵键盘介绍​编辑 2.扫描的概念 3.代码演示&#xff08;读取矩阵键盘键码&#xff09; 4.矩阵键盘密码锁 1.矩阵键盘介绍 为了减少I/O口的占用&#xff0c;通常将按键排列成矩阵形式&#xff0c;采用逐行或逐列的 “扫描”&#xff0c;就可以读出任何位置按键的状态…

jenkins配置gitee源码地址连接不上

报错信息如下&#xff1a; 网上找了好多都没说具体原因&#xff0c;最后还是看jenkins控制台输出日志发现&#xff1a; ssh命令执行失败&#xff08;git环境有问题&#xff0c;可能插件没安装成功等其他问题&#xff09; 后面发现是jenkins配置git的地方git安装路径错了。新手…

帕金森病患者在选择运动疗法时应该注意哪些事项?

帕金森病患者在选择运动疗法时&#xff0c;应该遵循以下几点注意事项&#xff1a; 个性化运动处方&#xff1a;根据患者的病情、年龄、健康状况、以往运动能力等因素&#xff0c;制定个体化的运动处方。 避免运动负荷过大&#xff1a;运动时间不宜过长&#xff0c;注意控制心率…

机器学习 C++ 的opencv实现SVM图像二分类的测试 (三)【附源码】

机器学习 C 的opencv实现SVM图像二分类的测试 (三) 数据集合下载地址&#xff1a;https://download.csdn.net/download/hgaohr1021/89506900 根据上节得到的svm.xml&#xff0c;测试结果为&#xff1a; #include <stdio.h> #include <time.h> #include <o…

智慧生活新篇章,Vatee万腾平台领航前行

在21世纪的科技浪潮中&#xff0c;智慧生活已不再是一个遥远的梦想&#xff0c;而是正逐步成为我们日常生活的现实。从智能家居的温馨便捷&#xff0c;到智慧城市的高效运转&#xff0c;科技的每一次进步都在为我们的生活增添新的色彩。而在这场智慧生活的变革中&#xff0c;Va…

stm32定时器与pwm波

文章目录 4 TIM4.1 SysTick系统定时器4.2 TIM定时器中断与微秒级延时4.3 TIM使用PWM波4.3.1 PWM介绍4.3.2 无源蜂鸣器实现 4.4 TIM ,PWM常用函数 4 TIM 4.1 SysTick系统定时器 ​ Systick系统滴答&#xff0c;&#xff08;同时他有属于自己的中断&#xff0c;可以利用它来做看…

Star CCM+界面显示字体大小调整

前言 打开界面字体显示大小是默认的&#xff0c;软件内设置调整默认字体的大小是无法实现&#xff0c;需要在图标属性中进行设置&#xff0c;操作方法与中英文切换很类似&#xff0c;具体方法如下&#xff1a; 操作流程 1. 右击Star-CCM快捷⽅式&#xff0c;选择“属性”&…

【Mindspore进阶】-03.ShuffleNet实战

ShuffleNet图像分类 当前案例不支持在GPU设备上静态图模式运行&#xff0c;其他模式运行皆支持。 ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端&#xff0c;所以模型的设计目标就是利用有…