Verilog中的FIFO设计

 

 

 

https://blog.csdn.net/2301_77482574/article/details/144309321

 

/***********************************************
@File     :  async_fifo.v
@Function :  Verilog 实现异步FIFO
            1、full与empty标志的判断
            2、跨时钟域的处理
            3、转格雷码再打拍避免亚稳态的产生
***********************************************/
//-----------------<模块及端口定义>----------------------
module  async_fifo #(
    parameter FIFO_WIDTH = 8  ,//fifo输入数据的位宽
    parameter FIFO_DEPTH = 256 //fifo的深度
)(
    wr_clk      ,//写时钟
    wrst_n      ,//写侧复位信号,低有效
    rd_clk      ,//读时钟
    rrst_n      ,//读侧复位信号,低有效
    //写时钟域                
    wr_en       ,//写使能
    wr_data     ,//写数据输入
    wr_full     ,//写侧满信号     
    wr_empty    ,//写侧空信号
    wr_usedw    ,//写时钟域下可读数据量   
    //读时钟域                  
    rd_en       ,//读使能
    rd_data     ,//读数据输出
    rd_full     ,//读侧满信号          
    rd_empty    ,//读侧空信号     
    rd_usedw     //读时钟域下可读数据量      
);
//-------------------<全局参数定义>----------------------
localparam DATA_W = FIFO_WIDTH        ,//数据位宽
           ADDR_W = $clog2(FIFO_DEPTH);//地址指针位宽
 
//-------------------<内部信号定义>----------------------
//输入输出端口定义-----------
    input                   wr_clk   ;//写时钟
    input                   wrst_n   ;//写侧复位信号,低有效
    input                   rd_clk   ;//读时钟
    input                   rrst_n   ;//读侧复位信号,低有效
    //写时钟域                
    input                   wr_en    ;//写使能
    input   [DATA_W-1:0]    wr_data  ;//写数据输入
    output                  wr_full  ;//写侧满信号     
    output                  wr_empty ;//写侧空信号
    output  [ADDR_W-1:0]    wr_usedw ;//写时钟域下可读数据量   
    //读时钟域                  
    input                   rd_en    ;//读使能
    output  [DATA_W-1:0]    rd_data  ;//读数据输出
    output                  rd_full  ;//读侧满信号          
    output                  rd_empty ;//读侧空信号     
    output  [ADDR_W-1:0]    rd_usedw ;//读时钟域下可读数据量   
    
//信号类型定义---------------
    reg     [DATA_W-1:0]    fifo_mem[FIFO_DEPTH-1:0];//FIFO存储阵列
 
 
    wire    [ADDR_W-1:0]            wr_addr     ;//写地址
    wire    [ADDR_W-1:0]            rd_addr     ;//读地址
    reg     [ADDR_W:0]              wr_ptr_b    ;//binary写指针
    reg     [ADDR_W:0]              rd_ptr_b    ;//binary读指针
 
    wire    [ADDR_W:0]              wr_ptr_g    ;//gary写指针
    reg     [ADDR_W:0]              wr_ptr_g1   ;//打两拍,同步
    reg     [ADDR_W:0]              wr_ptr_g2   ;
    wire    [ADDR_W:0]              rd_ptr_g    ;//gary读指针
    reg     [ADDR_W:0]              rd_ptr_g1   ;//打两拍,同步
    reg     [ADDR_W:0]              rd_ptr_g2   ;
 
    reg     [ADDR_W:0]              wr_gary2bin ;//将同步至写时钟域的格雷码写指针转换为二进制
    reg     [ADDR_W:0]              rd_gary2bin ;//将同步至读时钟域的格雷码读指针转换为二进制
    reg     [DATA_W-1:0]            rd_data_r   ;//数据输出寄存器
    reg     [ADDR_W-1:0]            wr_usedw_r  ;//写时钟域下可读数据量寄存器
    reg     [ADDR_W-1:0]            rd_usedw_r  ;//读时钟域下可读数据量寄存器
    integer i ;
/**************************************************************
                        读写指针
**************************************************************/
//wr_ptr_b
    always@(posedge wr_clk or negedge wrst_n)
        if(!wrst_n) begin
            wr_ptr_b <= 'd0;
        end
        else if(wr_en && ~wr_full) begin
            wr_ptr_b <=  wr_ptr_b + 1'b1;
        end
//rd_ptr_b
    always@(posedge rd_clk or negedge rrst_n)
        if(!rrst_n) begin
            rd_ptr_b <= 'd0;
        end
        else if(rd_en && ~rd_full) begin
            rd_ptr_b <=  rd_ptr_b + 1'b1;
        end
/**************************************************************
                        写地址和读地址分配
**************************************************************/
    assign wr_addr = wr_ptr_b[ADDR_W-1:0];
    assign rd_addr = rd_ptr_b[ADDR_W-1:0];
/**************************************************************
                        数据的存取与读出
**************************************************************/
//wr_data
    always@(posedge wr_clk or negedge wrst_n)
        if(!wrst_n) begin
            for(i=0;i<(1'b1<<ADDR_W);i=i+1)//使用for循环清空fifo_mem中的所有元素,(1'b1<<ADDR_W)表示计算FIFO的深度,即存储阵列的大小。
            fifo_mem[i] <= 'd0;
        end
        else if(wr_en && ~wr_full) begin   //写使能有效且FIFO非满
            fifo_mem[wr_addr] <= wr_data;  
        end
//rd_data
    always@(posedge rd_clk or negedge rrst_n)
        if(!rrst_n) begin
            rd_data_r <= 'd0;
        end
        else if(rd_en && ~rd_empty) begin  //读使能有效且FIFO非空
            rd_data_r <=  fifo_mem[rd_addr];
        end
/**************************************************************
                        二进制转格雷码
**************************************************************/
    assign wr_ptr_g = wr_ptr_b^(wr_ptr_b >> 1);//写指针格雷码
    assign rd_ptr_g = rd_ptr_b^(rd_ptr_b >> 1);//读指针格雷码
/**************************************************************
                        打拍同步
**************************************************************/
//读时钟域下同步写指针
    always@(posedge rd_clk or negedge rrst_n)
        if(!rrst_n) begin
            wr_ptr_g1 <= 'd0;
            wr_ptr_g2 <= 'd0;
        end
        else begin
            wr_ptr_g1 <= wr_ptr_g;
            wr_ptr_g2 <= wr_ptr_g1;
        end
//写时钟域下同步读指针
    always@(posedge wr_clk or negedge wrst_n)
        if(!wrst_n) begin
            rd_ptr_g1 <= 'd0;
            rd_ptr_g2 <= 'd0;
        end
        else begin
            rd_ptr_g1 <= rd_ptr_g;
            rd_ptr_g2 <= rd_ptr_g1;
        end
/**************************************************************
                        格雷码转二进制
**************************************************************/
//读时钟域下的格雷码写指针转换为二进制
    always@(*) begin
        wr_gary2bin[ADDR_W] = wr_ptr_g2[ADDR_W];
        for(i=ADDR_W-1;i>=0;i=i-1)
            wr_gary2bin[i] = wr_gary2bin[i+1]^wr_ptr_g2[i];
    end
//写时钟域下的格雷码读指针转换为二进制
    always@(*) begin
        rd_gary2bin[ADDR_W] = rd_ptr_g2[ADDR_W];
        for(i=ADDR_W-1;i>=0;i=i-1)
            rd_gary2bin[i] = rd_gary2bin[i+1]^rd_ptr_g2[i];
    end
/**************************************************************
                        可读数据量
**************************************************************/
//写侧可读数据量缓存
    always@(posedge wr_clk or negedge wrst_n)
        if(!wrst_n) begin
            wr_usedw_r <= 'd0;
        end
        else begin
            wr_usedw_r <= wr_ptr_b - rd_gary2bin; 
        end
//读侧可读数据量缓存
    always@(posedge rd_clk or negedge rrst_n)
        if(!rrst_n) begin
            rd_usedw_r <= 'd0;
        end
        else begin
            rd_usedw_r <= wr_gary2bin - rd_ptr_b; 
        end
/**************************************************************
                        输出
**************************************************************/
    assign wr_empty = (wr_ptr_b == rd_gary2bin);//写侧空标志
    assign rd_empty = (rd_ptr_b == wr_gary2bin);//读侧空标志
    assign wr_full  = (wr_ptr_b != rd_gary2bin) && (wr_ptr_b[ADDR_W-1:0] == rd_gary2bin[ADDR_W-1:0]);//写侧满标志
    assign rd_full  = (rd_ptr_b != wr_gary2bin) && (rd_ptr_b[ADDR_W-1:0] == wr_gary2bin[ADDR_W-1:0]);//读侧满标志
    assign rd_data  = rd_data_r ;//读出的数据
    assign wr_usedw = wr_usedw_r;//写侧可读数据量
    assign rd_usedw = rd_usedw_r;//读侧可读数据量
 
endmodule
async_fifo.v

一、FIFO是什么?
FIFO(First in First out)先进先出存储器。在FPGA中FIFO一般指的是对数据的存储具有先进先出特性的一个缓存器,被用于数据的缓存或高速异步数据的交互(跨时钟域信号的传递)。
与普通的存储器的区别:没有外部读写地址线,只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能由地址线决定读取或写入某个指定的地址
二、同步FIFO和异步FIFO
根据接入的时钟信号,可以分为同步FIFO和异步FIFO。
FIFO的应用场景:
同步FIFO(单时钟):主要用于数据的缓存,类似乒乓缓存思想,可以让后级不必等待前级过多时间。
异步FIFO(双时钟):跨时钟域传输数据,不同位宽的数据接口
三、FIFO重要参数
FIFO的宽度:即一次读写操作的数据位,,宽度是可以自己定义。
FIFO的深度:指的是FIFO可以存储多少个N位的数据(N是数据的位数),如一个8位的FIFO,若深度为8,它可以存储8个8位的数据。
FIFO的满标志:FIFO已满或将要满时,由FIFO的状态电路送出的一个信号,防止FIFO的写操作继续进行而造成溢出(overflow)。
FIFO的空标志:FIFO已空或将要空时,由FIFO的状态电路送出的一个信号,防止FIFO的读操作继续进行而造成无效的数据的读出(underflow)。
FIFO的读使能:读使能有效的时候读出数据。
FIFO的写使能:写使能有效的时候写入数据。

四、异步FIFO的实现
异步FIFO实质上是基于中间的双口RAM,外加一些读写控制电路组成得到,主要是实现不同时钟域之间的数据交互。异步FIFO读/写操作在两个不同的时钟域,这个过程会涉及到跨时钟域的处理,所以需要考虑跨时钟域会产生亚稳态的问题。此外,异步FIFO也需要通过空/满标志去衡量FIFO的使用情况,空/满标志的产生同样也需要考虑读/写时钟域,其产生的条件和方式也是需要重点考虑的。
示意图:

image

 

1)读/写指针
写指针:总是指向下一次要写的数据地址,写完后写指针自动加一;系统复位后,写指针指向0地址
读指针:总是指向下一次要读的数据地址,读完后读指针自动加一;系统复位后,读指针指向0地址
Skill:异步FIFO中的指针因为设计需要,位宽比地址多一位。
(2)空满标志判断
外部电路对异步FIFO进行读/写操作时,需要根据异步FIFO输出的空/满信号来判断是否能继续对异步FIFO进行读或者写的操作。

空标志:读指针追上写指针,即指针相等。

image

 

  • 满标志:写指针写完一轮后追上读指针,即读指针与写指针再次相等,读写指针最高位不同即说明再次追上。

image

 

异步FIFO的读写时钟不同,判断时需要将写指针同步到读时钟域,读指针同步到写时钟域再进行判断
(3)避免亚稳态的产生(打两拍+格雷码)
由于在异步FIFO的设计中,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?
直接将一个二进制数从一个时钟域同步到另外一个时钟域的时候就很容易出现问题,因为采用二进制数计数器时所有位都可能同时变化,在同一个时钟域下同步多个信号的变化可能会产生亚稳态,而使用格雷码就只有一位发生变化,格雷码在一个时钟域下同步一位的变化就不会产生亚稳态的问题。
单bit信号直接打两拍来避免亚稳态的效果较好,但是多bit传输,若多位发生变化时,变化的位都有可能产生亚稳态,所以多bit不直接用打拍的方式进行同步。
异步 FIFO 的地址指针每次变化都是加 1,将指针转化为格雷码后(相邻两位格雷码只有 1 位二进制发生变化),可直接进行打两拍来避免亚稳态。
(4)格雷码与二进制之间的相互转换
二进制转格雷码
二进制的最高位作为格雷码的最高位,次高位的格雷码为二进制的高位和次高位相异或得到,其他位与次高位类似。

image

 

  • 格雷码转二进制

使用格雷码的最高位作为二进制的最高位,二进制次高位产生过程是使用二进制的高位和次高位格雷码相异或得到,其他位的值与次高位产生过程类似

image

 

(5)Verilog代码实现

  • 模块输入输出端口:

    image

     

    顶层文件:

/***********************************************
@File     :  async_fifo.v
@Function :  Verilog 实现异步FIFO
            1、full与empty标志的判断
            2、跨时钟域的处理
            3、转格雷码再打拍避免亚稳态的产生
***********************************************/
//-----------------<模块及端口定义>----------------------
module  async_fifo #(
    parameter FIFO_WIDTH = 8  ,//fifo输入数据的位宽
    parameter FIFO_DEPTH = 256 //fifo的深度
)(
    wr_clk      ,//写时钟
    wrst_n      ,//写侧复位信号,低有效
    rd_clk      ,//读时钟
    rrst_n      ,//读侧复位信号,低有效
    //写时钟域                
    wr_en       ,//写使能
    wr_data     ,//写数据输入
    wr_full     ,//写侧满信号     
    wr_empty    ,//写侧空信号
    wr_usedw    ,//写时钟域下可读数据量   
    //读时钟域                  
    rd_en       ,//读使能
    rd_data     ,//读数据输出
    rd_full     ,//读侧满信号          
    rd_empty    ,//读侧空信号     
    rd_usedw     //读时钟域下可读数据量      
);
//-------------------<全局参数定义>----------------------
localparam DATA_W = FIFO_WIDTH        ,//数据位宽
           ADDR_W = $clog2(FIFO_DEPTH);//地址指针位宽
 
//-------------------<内部信号定义>----------------------
//输入输出端口定义-----------
    input                   wr_clk   ;//写时钟
    input                   wrst_n   ;//写侧复位信号,低有效
    input                   rd_clk   ;//读时钟
    input                   rrst_n   ;//读侧复位信号,低有效
    //写时钟域                
    input                   wr_en    ;//写使能
    input   [DATA_W-1:0]    wr_data  ;//写数据输入
    output                  wr_full  ;//写侧满信号     
    output                  wr_empty ;//写侧空信号
    output  [ADDR_W-1:0]    wr_usedw ;//写时钟域下可读数据量   
    //读时钟域                  
    input                   rd_en    ;//读使能
    output  [DATA_W-1:0]    rd_data  ;//读数据输出
    output                  rd_full  ;//读侧满信号          
    output                  rd_empty ;//读侧空信号     
    output  [ADDR_W-1:0]    rd_usedw ;//读时钟域下可读数据量   
    
//信号类型定义---------------
    reg     [DATA_W-1:0]    fifo_mem[FIFO_DEPTH-1:0];//FIFO存储阵列
 
 
    wire    [ADDR_W-1:0]            wr_addr     ;//写地址
    wire    [ADDR_W-1:0]            rd_addr     ;//读地址
    reg     [ADDR_W:0]              wr_ptr_b    ;//binary写指针
    reg     [ADDR_W:0]              rd_ptr_b    ;//binary读指针
 
    wire    [ADDR_W:0]              wr_ptr_g    ;//gary写指针
    reg     [ADDR_W:0]              wr_ptr_g1   ;//打两拍,同步
    reg     [ADDR_W:0]              wr_ptr_g2   ;
    wire    [ADDR_W:0]              rd_ptr_g    ;//gary读指针
    reg     [ADDR_W:0]              rd_ptr_g1   ;//打两拍,同步
    reg     [ADDR_W:0]              rd_ptr_g2   ;
 
    reg     [ADDR_W:0]              wr_gary2bin ;//将同步至写时钟域的格雷码写指针转换为二进制
    reg     [ADDR_W:0]              rd_gary2bin ;//将同步至读时钟域的格雷码读指针转换为二进制
    reg     [DATA_W-1:0]            rd_data_r   ;//数据输出寄存器
    reg     [ADDR_W-1:0]            wr_usedw_r  ;//写时钟域下可读数据量寄存器
    reg     [ADDR_W-1:0]            rd_usedw_r  ;//读时钟域下可读数据量寄存器
    integer i ;
/**************************************************************
                        读写指针
**************************************************************/
//wr_ptr_b
    always@(posedge wr_clk or negedge wrst_n)
        if(!wrst_n) begin
            wr_ptr_b <= 'd0;
        end
        else if(wr_en && ~wr_full) begin
            wr_ptr_b <=  wr_ptr_b + 1'b1;
        end
//rd_ptr_b
    always@(posedge rd_clk or negedge rrst_n)
        if(!rrst_n) begin
            rd_ptr_b <= 'd0;
        end
        else if(rd_en && ~rd_full) begin
            rd_ptr_b <=  rd_ptr_b + 1'b1;
        end
/**************************************************************
                        写地址和读地址分配
**************************************************************/
    assign wr_addr = wr_ptr_b[ADDR_W-1:0];
    assign rd_addr = rd_ptr_b[ADDR_W-1:0];
/**************************************************************
                        数据的存取与读出
**************************************************************/
//wr_data
    always@(posedge wr_clk or negedge wrst_n)
        if(!wrst_n) begin
            for(i=0;i<(1'b1<<ADDR_W);i=i+1)//使用for循环清空fifo_mem中的所有元素,(1'b1<<ADDR_W)表示计算FIFO的深度,即存储阵列的大小。
            fifo_mem[i] <= 'd0;
        end
        else if(wr_en && ~wr_full) begin   //写使能有效且FIFO非满
            fifo_mem[wr_addr] <= wr_data;  
        end
//rd_data
    always@(posedge rd_clk or negedge rrst_n)
        if(!rrst_n) begin
            rd_data_r <= 'd0;
        end
        else if(rd_en && ~rd_empty) begin  //读使能有效且FIFO非空
            rd_data_r <=  fifo_mem[rd_addr];
        end
/**************************************************************
                        二进制转格雷码
**************************************************************/
    assign wr_ptr_g = wr_ptr_b^(wr_ptr_b >> 1);//写指针格雷码
    assign rd_ptr_g = rd_ptr_b^(rd_ptr_b >> 1);//读指针格雷码
/**************************************************************
                        打拍同步
**************************************************************/
//读时钟域下同步写指针
    always@(posedge rd_clk or negedge rrst_n)
        if(!rrst_n) begin
            wr_ptr_g1 <= 'd0;
            wr_ptr_g2 <= 'd0;
        end
        else begin
            wr_ptr_g1 <= wr_ptr_g;
            wr_ptr_g2 <= wr_ptr_g1;
        end
//写时钟域下同步读指针
    always@(posedge wr_clk or negedge wrst_n)
        if(!wrst_n) begin
            rd_ptr_g1 <= 'd0;
            rd_ptr_g2 <= 'd0;
        end
        else begin
            rd_ptr_g1 <= rd_ptr_g;
            rd_ptr_g2 <= rd_ptr_g1;
        end
/**************************************************************
                        格雷码转二进制
**************************************************************/
//读时钟域下的格雷码写指针转换为二进制
    always@(*) begin
        wr_gary2bin[ADDR_W] = wr_ptr_g2[ADDR_W];
        for(i=ADDR_W-1;i>=0;i=i-1)
            wr_gary2bin[i] = wr_gary2bin[i+1]^wr_ptr_g2[i];
    end
//写时钟域下的格雷码读指针转换为二进制
    always@(*) begin
        rd_gary2bin[ADDR_W] = rd_ptr_g2[ADDR_W];
        for(i=ADDR_W-1;i>=0;i=i-1)
            rd_gary2bin[i] = rd_gary2bin[i+1]^rd_ptr_g2[i];
    end
/**************************************************************
                        可读数据量
**************************************************************/
//写侧可读数据量缓存
    always@(posedge wr_clk or negedge wrst_n)
        if(!wrst_n) begin
            wr_usedw_r <= 'd0;
        end
        else begin
            wr_usedw_r <= wr_ptr_b - rd_gary2bin; 
        end
//读侧可读数据量缓存
    always@(posedge rd_clk or negedge rrst_n)
        if(!rrst_n) begin
            rd_usedw_r <= 'd0;
        end
        else begin
            rd_usedw_r <= wr_gary2bin - rd_ptr_b; 
        end
/**************************************************************
                        输出
**************************************************************/
    assign wr_empty = (wr_ptr_b == rd_gary2bin);//写侧空标志
    assign rd_empty = (rd_ptr_b == wr_gary2bin);//读侧空标志
    assign wr_full  = (wr_ptr_b != rd_gary2bin) && (wr_ptr_b[ADDR_W-1:0] == rd_gary2bin[ADDR_W-1:0]);//写侧满标志
    assign rd_full  = (rd_ptr_b != wr_gary2bin) && (rd_ptr_b[ADDR_W-1:0] == wr_gary2bin[ADDR_W-1:0]);//读侧满标志
    assign rd_data  = rd_data_r ;//读出的数据
    assign wr_usedw = wr_usedw_r;//写侧可读数据量
    assign rd_usedw = rd_usedw_r;//读侧可读数据量
 
endmodule
async_fifo.v

 

Testbench测试文件:

//时间单位、时间精度
`timescale 1ns/1ps
 
module  tb_asyncfifo();
 
//重定义时钟周期
parameter CLK_CYCLE = 20;
 
//激励信号定义
reg         tb_wclk     ;
reg         tb_rclk     ;
reg         tb_wrst_n   ;
reg         tb_rrst_n   ;
reg         wr_en       ;
reg         rd_en       ;
reg [7:0]   wr_data     ;
 
//产生时钟信号
always #(CLK_CYCLE/2) tb_wclk = ~tb_wclk;//写时钟
always #(CLK_CYCLE/4) tb_rclk = ~tb_rclk;//读时钟
 
//时钟和复位的激励信号
initial begin
    tb_wclk = 1'b1;
    tb_rclk = 1'b1;
    tb_wrst_n = 1'b0;
    tb_rrst_n = 1'b0;
    wr_en  = 'd0;
    rd_en  = 'd0;
    wr_data= 'd0;
    #(CLK_CYCLE*5);
    tb_wrst_n = 1'b1;
    tb_rrst_n = 1'b1;
    //模拟五次写操作
    repeat(5) begin
        wr_en = 1'b1;
        wr_data = $random;
        #CLK_CYCLE;
        wr_en = 1'b0;
        #CLK_CYCLE;   
    end
    //模拟五次读操作
    #(CLK_CYCLE*5);
    repeat(5)begin
        rd_en = 1'b1;
        #(CLK_CYCLE/2);
        rd_en = 1'b0;
        #(CLK_CYCLE/2);
    end
    
    #(CLK_CYCLE*10);
    $stop;
end
//模块实例化
async_fifo  async_fifo_inst(
    /* input                    */.wr_clk      (tb_wclk     ),//写时钟
    /* input                    */.wrst_n      (tb_wrst_n   ),//写侧复位信号,低有效
    /* input                    */.rd_clk      (tb_rclk     ),//读时钟
    /* input                    */.rrst_n      (tb_rrst_n   ),//读侧复位信号,低有效              
    /* input                    */.wr_en       (wr_en       ),//写使能
    /* input   [DATA_W-1:0]     */.wr_data     (wr_data     ),//写数据输入
    /* output                   */.wr_full     ( ),//写侧满信号     
    /* output                   */.wr_empty    ( ),//写侧空信号
    /* output  [ADDR_W-1:0]     */.wr_usedw    ( ),//写时钟域下可读数据量                   
    /* input                    */.rd_en       (rd_en       ),//读使能
    /* output  [DATA_W-1:0]     */.rd_data     ( ),//读数据输出
    /* output                   */.rd_full     ( ),//读侧满信号          
    /* output                   */.rd_empty    ( ),//读侧空信号     
    /* output  [ADDR_W-1:0]     */.rd_usedw    ( ) //读时钟域下可读数据量      
);
endmodule
tb_asyncfifo

 

  • Modelsim仿真:

    image

     

 

原文链接:https://blog.csdn.net/2301_77482574/article/details/144309321

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

https://blog.csdn.net/qq_43244515/article/details/124825458

 

vivado中FIFO ip

https://blog.csdn.net/m0_66360845/article/details/145163327 

 

 

Verilog中的FIFO设计-同步FIFO篇

 

https://baijiahao.baidu.com/s?id=1723287516895080443

 

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 
syn_fifo

 

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
syn_fifo_tb

 

Verilog中的FIFO设计-异步FIFO篇

https://baijiahao.baidu.com/s?id=1724030588865450475

 

module asy_fifo#(
    parameter   WIDTH = 8,
    parameter   DEPTH = 8
)(
    input   [WIDTH - 1 : 0] wr_data,
    input                   wr_clk,
    input                   wr_rstn,
    input                   wr_en,
    input                   rd_clk,
    input                   rd_rstn,
    input                   rd_en,
    output                  fifo_full,
    output                  fifo_empty,
    output  [WIDTH - 1 : 0] rd_data
);
    //定义读写指针
    reg [$clog2(DEPTH) : 0]  wr_ptr, rd_ptr;
    
    //定义一个宽度为WIDTH,深度为DEPTH的fifo
    reg [WIDTH - 1 : 0] fifo    [DEPTH - 1 : 0];

    //定义读数据
    reg [WIDTH - 1 : 0] rd_data;

    //写操作
    always @ (posedge wr_clk or negedge wr_rstn) begin
        if(!wr_rstn)
            wr_ptr <= 0;
        else if(wr_en && !fifo_full) begin
            fifo[wr_ptr] <= wr_data;
            wr_ptr <= wr_ptr + 1;
        end
        else
            wr_ptr <= wr_ptr;
    end

    //读操作
    always @ (posedge rd_clk or negedge rd_rstn) begin
        if(!rd_rstn) begin
            rd_ptr <= 0;
            rd_data <= 0;
        end
        else if(rd_en && !fifo_empty) begin
            rd_data <= fifo[rd_ptr];
            rd_ptr <= rd_ptr + 1;
        end
        else
            rd_ptr <= rd_ptr;
    end

    //定义读写指针格雷码
    wire [$clog2(DEPTH) : 0] wr_ptr_g;
    wire [$clog2(DEPTH) : 0] rd_ptr_g;

    //读写指针转换成格雷码
    assign wr_ptr_g = wr_ptr ^ (wr_ptr >>> 1);
    assign rd_ptr_g = rd_ptr ^ (rd_ptr >>> 1);


    //定义打拍延迟格雷码
    reg [$clog2(DEPTH) : 0] wr_ptr_gr, wr_ptr_grr;
    reg [$clog2(DEPTH) : 0] rd_ptr_gr, rd_ptr_grr;
    
    //写指针同步到读时钟域
    always @ (posedge rd_clk or negedge rd_rstn) begin
        if(!rd_rstn) begin
            wr_ptr_gr <= 0;
            wr_ptr_grr <= 0;
        end
        else begin
            wr_ptr_gr <= wr_ptr_g;
            wr_ptr_grr <= wr_ptr_gr;
        end
    end

    //读指针同步到写时钟域
    always @ (posedge wr_clk or negedge wr_rstn) begin
        if(!wr_rstn) begin
            rd_ptr_gr <= 0;
            rd_ptr_grr <= 0;
        end
        else begin
            rd_ptr_gr <= rd_ptr_g;
            rd_ptr_grr <= rd_ptr_gr;
        end
    end

    //声明空满信号数据类型
    reg fifo_full;
    reg fifo_empty;

    //写满判断
    always @ (posedge wr_clk or negedge wr_rstn) begin
        if(!wr_rstn)
            fifo_full <= 0;
        else if((wr_ptr_g[$clog2(DEPTH)] != rd_ptr_grr[$clog2(DEPTH)]) && (wr_ptr_g[$clog2(DEPTH) - 1] != rd_ptr_grr[$clog2(DEPTH) - 1]) && (wr_ptr_g[$clog2(DEPTH) - 2 : 0] == rd_ptr_grr[$clog2(DEPTH) - 2 : 0]))
            fifo_full <= 1;
        else
            fifo_full <= 0;
    end

    //读空判断
    always @ (posedge rd_clk or negedge rd_rstn) begin
        if(!rd_rstn)
            fifo_empty <= 0;
        else if(wr_ptr_grr[$clog2(DEPTH) : 0] == rd_ptr_g[$clog2(DEPTH) : 0])
            fifo_empty <= 1;
        else
            fifo_empty <= 0;
    end
endmodule 
asy_fifo

 

module asy_fifo_tb;
    parameter   width = 8;
    parameter   depth = 8;

    reg wr_clk, wr_en, wr_rstn;
    reg rd_clk, rd_en, rd_rstn;
    
    reg [width - 1 : 0] wr_data;

    wire fifo_full, fifo_empty;

    wire [width - 1 : 0] rd_data;
    
    //实例化
        asy_fifo myfifo (
            .wr_clk(wr_clk),
            .rd_clk(rd_clk),
            .wr_rstn(wr_rstn),
            .rd_rstn(rd_rstn),
            .wr_en(wr_en),
            .rd_en(rd_en),
            .wr_data(wr_data),
            .rd_data(rd_data),
            .fifo_empty(fifo_empty),
            .fifo_full(fifo_full)
        );


    //时钟
    initial begin
        rd_clk = 0;
        forever #25 rd_clk = ~rd_clk;
    end
    
    initial begin
        wr_clk = 0;
        forever #30 wr_clk = ~wr_clk;
    end

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

    //赋值
    initial begin
        wr_en = 0;
        rd_en = 0;
        wr_rstn = 1;
        rd_rstn = 1;

        #10;
        wr_rstn = 0;
        rd_rstn = 0;
    
        #20;
        wr_rstn = 1;
        rd_rstn = 1;

        @(negedge wr_clk)
        wr_data = {$random}%30;
        wr_en = 1;

        repeat(7) begin
            @(negedge wr_clk)
            wr_data = {$random}%30;
        end

        @(negedge wr_clk)
        wr_en = 0;

        @(negedge rd_clk)
        rd_en = 1;

        repeat(7) begin
            @(negedge rd_clk);
        end

        @(negedge rd_clk)
        rd_en = 0;

        #150;

        @(negedge wr_clk)
        wr_en = 1;
        wr_data = {$random}%30;
        
        repeat(15) begin
            @(negedge wr_clk)
            wr_data = {$random}%30;
        end

        @(negedge wr_clk)
        wr_en = 0;

        #50;
        $finish;
    end

endmodule
asy_fifo_tb

 

 

跨时钟域

https://cloud.tencent.com/developer/article/2294982

posted on 2025-09-04 13:39  taylorrrrrrrrrr  阅读(76)  评论(0)    收藏  举报