• 【数字IC验证快速入门】10、Verilog RTL设计必会的FIFO


    导读:作者有幸在中国电子信息领域的排头兵院校“电子科技大学”攻读研究生期间,接触到前沿的数字IC验证知识,旁听到诸如华为海思清华紫光联发科技等业界顶尖集成电路相关企业面授课程,对数字IC验证有了一些知识积累和学习心得。为帮助想入门前端IC验证的朋友,思忱一二后,特开此专栏,以期花最短的时间,走最少的弯路,学最多的IC验证技术知识。

    一、同步FIFO

    FIFO,First In First Out,先入先出的队列

    学会了同步FIFO后,再学习异步FIFO学起来就容易了。

    学习FIFO的要求,不要求要能够完全写出来,但是要懂得基本概念,FIFO是什么功能,设计FIFO基本要求等

    1.1、RAM(Random Access Memory)的设计

    设计实现一个16x8的双端口RAM

    • RAM宽度8bit
    • RAM深度16
    • ADDR位宽2^4,取值范围0~15

    在这里插入图片描述

    对应的代码

    module dp_ram(//dp = dual port
        input  wire                  write_clock,
        input  wire                  read_clock,
        input  wire                  write_allow,
        input  wire                  read_allow,
        input  wire [ADDR_WIDTH-1:0] write_addr,
        input  wire [ADDR_WIDTH-1:0] read_addr,
        input  wire [RAM_WIDTH-1:0 ] write_data,
        output reg  [RAM_WIDTH-1:0 ] read_data
    );
    
        parameter DLY       = 1;
        parameter RAM_WIDTH = 8;
        parameter RAM_EDPTH = 16;
        parameter ADDR_WIDTH= 4;
        
        reg [RAM_WIDTH-1:0 ] memory[RAM_EDPTH-1:0];
        
        always@(posedge write_clock) begin
            if(write_allow)
                memory[write_addr] <= #DLY write_data;
        end
        
        always@(posedge read_clock) begin
            if(read_allow)
                read_data <= #DLY memory[read_addr];
        end
    
    endmodule
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    1.2、同步FIFO的设计

    • FIFO原则:满不能写,空不能读
    • 关键:full和empty信号如何产生
      • 方法1:用长度计数器factor,执行一次写操作,factor加1;执行一次读操作,factor减1.【占用资源会多点,但是实现较容易】
      • 方法2:地址位扩展一位,用最高位来判断空满。【占用资源较少,实现稍复杂】

    1.2.1、方法1对应的

    module SYNCFIFO(
        input   wire                        Fifo_rst,
        input   wire                        Clock,
        input   wire                        Read_enable,
        input   wire                        Write_enable,
        input   wire    [DATA_WIDTH-1:0]    Write_data,
        
        output   reg    [DATA_WIDTH-1:0]    Read_data,
        output   reg                        Full,
        output   reg                        Empty,
        output   reg    [ADDR_WIDTH-1:0]    Fcounter
    
    );
    
        parameter DATA_WIDTH = 8;
        parameter ADDR_WIDTH = 9;
    
        reg [ADDR_WIDTH-1:0] Read_addr;
        reg [ADDR_WIDTH-1:0] Write_addr;
    
        wire Read_allow = (Read_enable && !Empty); //空不能读;最好不要这样写,容易出错,该写法等效于assign的那种!
        wire Write_allow = (Write_enable && !Full); //满不能写
    
        DUALRAM U_RAM(
            .Write_clock    (Clock),
            .Read_clock     (Clock ),
            .Write_allow    (Write_allow),
            .Read_allow     (Read_allow ),
            .Write_addr     (Write_addr ),
            .Read_addr      (Read_addr  ),
            .Write_data     (Write_data ),
            .Read_data      (Read_data  )
        );
    
        always@(posedge Clock or posedge Fifo_rst)
            if(Fifo_rst)
                Empty <= 'b1;
            else
                有点复杂化了,只要Fcounter[8:0]==9'h0,那么Empty就为1即可!
                Empty <= (!Write_enable && (Fcounter[8:1]==8'h0) && ((Fcounter[0] == 0) || Read_enable) ); 
            
        always@(posedge Clock or posedge Fifo_rst)
            if(Fifo_rst)
                Full <= 'b1;
            else
                //同样复杂化了,只要Fcounter[8:0]==9'b1_1111_1111,那么Full就为1即可!
                Full <= (!Read_enable && (Fcounter[8:1]==8'hFF) && ((Fcounter[0]==1) || Write_enable));
        
        always@(posedge clock or posedge Fifo_rst)
            if(Fifo_rst)
                Read_addr <= 'h0;
            else if(Read_allow)
                Read_addr <= Read_addr + 'b1;//最好加个边界限制,eg:if(Read_addr >= Depth-1) Read_addr <= 'h0之类的!
                
        always@(posedge clock or posedge Fifo_rst)
            if(Fifo_rst)
                Write_addr <= 'h0;
            else if(Write_allow)
                Write_addr <= Write_addr + 'b1;//同样最好加个边界限制
        
        always@(posedge clock or posedge Fifo_rst)
            if(Fifo_rst)
                Fcounter <= 'h0;
            else if((!Read_allow && Write_allow) || (Read_allow && !Write_allow))
            begin
                if(Write_allow) Fcounter <= Fcounter + 'b1;
                else    Fcounter <= Fcounter - 'b1;
            end
     
    endmodule
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 跟RAM相比,FIFO看不到地址,只看到读写使能,来顺序产生RAM地址。

    小知识:RTL代码不是程序,程序是编译成一条条指令和数据,放置到内存中,通过CPU取指令、译码、执行!RTL代码没有编译一说,综合映射成网表,体现实际硬件电路!

    1.2.2、方法2对应的Verilog代码

    • 深度为4的FIFO,实际位宽2bit就够了,拓展一位就变成了3bit。

    在这里插入图片描述

    • 场景1和3,读写最高位相等,即w[2] = r[2],且w[1:0] = r[1:0],empty有效,为空。
    • 场景2和4,读写最高位不等,即w[2] != r[2],且w[1:0] = r[1:0],full有效,为满。
    • 扩展为来判断场景!
    module sync_fifo(
        input   wire            clk,
        input   wire            rst,
        input   wire            wr_en,
        input   wire            rd_en,
        input   wire    [7:0]   data_in,
     
        output  wire            empty,
        output  wire            full,
        output  reg     [7:0]   data_out,
    
    );
    
    reg  [7:0]  mem[15:0];
    wire [3:0]  w_addr,r_addr;
    reg  [4:0]  r_addr_a, w_addr_a; //16个地址深度,4位就够了即[3:0],此处[4:0]扩展了1位
    
    
    assign r_addr = r_addr_a[3:0];
    assign w_addr = w_addr_a[3:0];
    
    always@(posedge clk or negedge rst)
        if(!rst)
            r_addr_a <= 5'b0;
        else begin
            if(rd_en==1 && empty==0) begin
                data_out <= mem[r_addr];
                r_addr_a <= r_addr_a + 1;
            end
        end
    
    always@(posedge clk or negedge rst)
        if(!rst)
            w_addr_a <= 5'b0;
        else begin
            if(wr_en==1 && full==0) begin
                mem[w_addr] <= data_in;
                w_addr_a <= w_addr_a + 1;
            end
        end
    
    assign empty = (r_addr_a == w_addr_a) ? 1 : 0;
    assign full  = (r_addr_a[4] != w_addr_a[4]  && r_addr_a[3:0] == w_addr_a[3:0]) ? 1 : 0; 
    
    endmodule
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    对应的TestBench

    在这里插入图片描述

    在这里插入图片描述

    二、异步FIFO

    • 使用扩展地址位的方式来判断空满
    • 读写信号时钟不同
    • 关键:格雷码的使用【异步FIFO:读写时钟域不一样】【格雷码:只有一个bit跳变】
      • 读写地址进行转换,转换之后再同步,这样可以避免多比特同时翻转引入的中间亚稳态!

    在这里插入图片描述

    格雷码转换模块对应的RTL代码

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    • 跟同步FIFO不一样的地方,要进行一个跨时钟域的地址采样,采样要经过一个格雷码的转换的,否则采样会出现问题。
    • 此处仅用了一个异步时钟cycle去采,而实际为了防止亚稳态,要采三拍的,这个地方写的不规范!
    • EDA仿真一般检查不出异步的问题。
  • 相关阅读:
    Springboot Security 前后端分离模式自由接口最小工作模型
    [Python数据可视化] Plotly:交互式数据可视化的强大工具
    机器学习:奇异值分解(SVD)详细讲解
    希尔排序详解
    网络的层次
    使用robot+selenium创建一个UI自动化测试用例
    基于hutool实现国密SM2的加解密,简直不要太简单!
    一起Talk Android吧(第四百二十五回:字节数组与String相互转换)
    express
    【Leetcode】1682. Longest Palindromic Subsequence II
  • 原文地址:https://blog.csdn.net/luoganttcc/article/details/125633812