有点迷茫

V1

2022/04/05阅读:11主题:默认主题

Verilog-同步FIFO

0 写在前面

FIFO可根据读写时钟是否为同一时钟域可分为同步FIFO和异步FIFO,本文主要介绍同步FIFO,异步FIFO将在下篇介绍

1 什么是FIFO

FIFO全称 First In First Out,即先进先出。

FIFO主要用于以为下几个方面:

  • 跨时钟域数据传输
  • 将数据发送到芯片外之前进行缓冲,如发送到DRAM或SRAM
  • 存储数据以备后用

FIFO是异步数据传输时常用的存储器,多bit数据异步传输时,无论是从快时钟域到慢时钟域,还是从慢时钟域到快时钟域,都可以使用FIFO处理。

2 重要参数

FIFO中重要的参数有深度、宽度、空标志、满标志、读时钟、读时针、写时钟和写时针

我看到过一个很形象的比喻:

把FIFO比作汽车进入一个单向行驶的隧道,隧道两端都有一个门进行控制,FIFO宽度就是这个隧道单向有几个车道,FIFO的深度就是一个车道能容纳多少辆车,当隧道内停满车辆时,这就是FIFO的满标志,当隧道内没有一辆车时,这便是空标志

读时钟:读操作所遵循的时钟,时钟沿到来时读取数据

写时钟:写操作所遵循的时钟,时钟沿到来时写入数据

读指针:指向下一个要读出的地址,读完自动加1

写指针:指向下一个要写入地址,写完自动加1

下面从FIFO接口开始说起,下图适用于任何FIFO的基本接口框图

FIFO基本接口
FIFO基本接口

FIFO可分为读数据一端和写数据一端

  • wr_enrd_en分别为写/读使能端,就像上面隧道的例子,两个使能端就好像是两边的门,只有门打开的时候才允许车辆进出
  • wr_datard_data分别是要写入FIFO的数据和要从FIFO中读取的数据
  • fifo_fullfifo_empty分别为FIFO的满/空标志位

3 FIFO设计的重要原则

  1. 任何FIFO都不要向满FIFO中写入数据(写溢出)
  2. 任何FIFO都不要从空FIFO中读取数据(读溢出)

FIFO设计的核心便是空满的判断,如何判断FIFO是否写满(或读空),这里我们可以利用地址指针,如下图:

每写入一次数据,写地址指针会加1,每读取一次数据,读地址指针会加1

就像上图所示,当读地址指针追上写地址指针,FIFO便是读空状态

同理,当写地址指针再次追上读地址指针,FIFO便是写满状态,就像下图

4 同步FIFO设计

先直接给出Verilog代码

module syn_fifo(clk, rstn, wr_en, rd_en, wr_data, rd_data, fifo_full, fifo_empty);

    //参数定义
    parameter   width = 8;
    parameter   depth = 8;
    parameter   addr  = 3;

    //输入信号
    input   clk;    //时钟信号
    input   rstn;   //下降沿复位
    input   wr_en;  //写入使能
    input   rd_en;  //读取使能

    //数据信号
    input   [width - 1 : 0] wr_data;    //写数据
    output  [width - 1 : 0] rd_data;    //读数据

    reg [width - 1 : 0] rd_data;

    //空满判断信号
    output  fifo_full;
    output  fifo_empty;

    //定义一个计数器,用于判断空满
    reg [$clog2(depth): 0] cnt;

    //定义读写地址
    reg [depth - 1 : 0] wr_ptr;
    reg [depth - 1 : 0] rd_ptr;

    //定义一个宽度为为width,深度为depth的fifo
    reg [width - 1 : 0] fifo [depth - 1 : 0];

    //写地址操作
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            wr_ptr <= 0;
        else if(wr_en && !fifo_full)    //写使能,且fifo未写满
            wr_ptr <= wr_ptr + 1;
        else
            wr_ptr <= wr_ptr;
    end

    //读地址操作
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            rd_ptr <= 0;
        else if(rd_en && !fifo_empty)   //读使能,且fifo不为空
            rd_ptr <= rd_ptr + 1;
        else
            rd_ptr <= rd_ptr;
    end

    //写数据
    integer i;

    always @ (posedge clk or negedge rstn) begin
        if(!rstn) begin //复位清空fifo
            for(i = 0; i < depth; i = i + 1)
                fifo[i] <= 0;
        end
        else if(wr_en)  //写使能时将数据写入fifo
            fifo[wr_ptr] <= wr_data;
        else    //否则保持
            fifo[wr_ptr] <= fifo[wr_ptr];
    end

    //读数据
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            rd_data <= 0;
        else if (rd_en)
            rd_data <= fifo[rd_ptr];    //从fifo中读取数据
        else
            rd_data <= rd_data;
    end

    //辅助计数,用于判断空满
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            cnt <= 0;
        else if (wr_en && !rd_en && !fifo_full) //有效的只写入
            cnt <= cnt + 1;
        else if (!wr_en && rd_en && !fifo_empty) //有效的只读取
            cnt <= cnt - 1;
        else 
            cnt <= cnt;
    end

    //空满判断
    assign fifo_full = (cnt == depth)? 1 : 0;
    assign fifo_empty = (cnt == 0) ? 1 : 0;
endmodule 

下面是tb测试文件

module syn_fifo_tb;
    reg clk, rstn;
    reg wr_en, rd_en;

    wire fifo_full, fifo_empty;

    reg [7 : 0] wr_data;
    wire    [7 : 0] rd_data;


    //生成波形
    initial begin
        $fsdbDumpfile("wave.fsdb");
        $fsdbDumpvars(0, myfifo);
        $fsdbDumpon();
    end

    //例化
    syn_fifo myfifo(
        .clk(clk),
        .rstn(rstn),
        .wr_en(wr_en),
        .rd_en(rd_en),
        .fifo_full(fifo_full),
        .fifo_empty(fifo_empty),
        .wr_data(wr_data),
        .rd_data(rd_data)
    );

    initial begin
        rstn = 1;
        wr_en = 0;
        rd_en = 0;


        repeat(2) @(negedge clk);  
        rstn = 0;

        @(negedge clk);  
        rstn = 1;

        @(negedge clk);  
        wr_data = {$random}%60;
        wr_en = 1;

        repeat(2) @ (negedge clk);
        wr_data = {$random}%60;

        @(negedge clk);
        wr_en = 0;
        rd_en = 1;
        
        repeat(4) @ (negedge clk);
        rd_en = 0;
        wr_en = 1;
        wr_data = {$random}%60;

        repeat(5) @ (negedge clk);
        wr_data = {$random}%60;
        
        repeat(2) @ (negedge clk);
        wr_en = 0;
        rd_en = 1;
        
        repeat(2) @ (negedge clk);
        rd_en = 0;
        wr_en = 1;
        wr_data = {$random}%60;

        repeat(3) @ (negedge clk);
        wr_en = 0;

        #50 $finish;
    end

    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end
endmodule

下面是仿真波形

仿真波形
仿真波形

下面开始逐段分析

在初始阶段,我们并未给出wr_data的数据,也并未向FIFO中写入任何数据,所以此时FIFO一直是空的(fifo_empty拉高)

随后,我们将wr_data赋值为8,并让wr_en有效,在时钟上升沿到来时,开始写入数据,此时fifo_empty被拉低,因为有数据被写入

数据8被写入两次后,将wr_data更改为39,且数据39仅被写入一次

拉低wr_en,拉高rd_en开始读取数据,此处可以看出,读取数据时,先读取出两个8,随后读取一个39,随后fifo_empty被拉高,因为此时FIFO中已经没有数据可供读取

分类:

其他

标签:

其他

作者介绍

有点迷茫
V1