基于System verilog的异步FIFO实现

异步FIFO读写分别采用相互异步的不同时钟。在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步FIFO是这个问题的一种简便、快捷的解决方案,使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。
异步FIFO设计中,最重要的就是满和空信号的设计,具体的思路就是通过读写指针的比较,来生成相应的full或者empty信号,在之前同步FIFO的设计中已经讲到了这一判断方法,即:
在指针中添加一个额外的位(extra bit),当写指针增加并越过最后一个FIFO地址时,就将写指针这个未用的MSB加1,其它位回零。对读指针也进行同样的操作。当两指针重合时,如果两个指针的MSB不同,说明写指针比读指针多折回了一次,FIFO为满。如果两个指针的MSB相同,则说明两个指针折回的次数相等。其余位相等,说明FIFO为空。
然而,在异步FIFO中情况更加复杂,由于写指针和读指针所在的时钟域不同,因此两者在进行比较时,有可能因为建立时间、保持时间不满足,而生成错误的空满信号,从而使FIFO无法正常工作,因此,我们在比较之前,首先需要将写指针同步到读时钟域,再和读指针比较,生成empty信号;将读指针同步到写时钟域,再和写指针比较,生成full信号。
关于跨时钟域信号的同步,之前的博客也已经介绍过,单bit的信号通过打两拍的方式即可,而多bit信号,则可以采用格雷码的方式,转化为单bit信号的跨时钟域同步,在本博客中,我们就将读写指针转化为格雷码,然后通过打两拍的方式进行同步,最后比较生成相应的空或者满信号。其转化公式如下:
wr_ptr_gray=wr_ptr>>1^wr_ptr;
rd_ptr_gray=rd_ptr>>1^rd_ptr;
根据上式,我们不难得到格雷码的比较方式:
当wr_ptr_gray的高两位不同,其余位都相同时,FIFO为满;
当rd_ptr_gray的高两位相同,其余位也相同时,FIFO位空。

整个异步FIFO的System verilog代码如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/02/22 13:02:45
// Design Name: 
// Module Name: async_fifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module async_fifo
#(parameter DATA_WIDTH = 32,
  parameter FIFO_DEPTH = 32)
(
input logic rst,
//写端口
input logic wclk,
input logic [DATA_WIDTH-1:0] wdata,
input logic wr_en,
output logic full,
//读端口
input logic rclk,
input logic rd_en,
output logic [DATA_WIDTH-1:0] rdata,
output logic empty
    );
parameter ADDR_WIDTH=$clog2(FIFO_DEPTH);
logic [DATA_WIDTH-1:0] RAM [FIFO_DEPTH-1];

logic [ADDR_WIDTH-1:0] wr_addr;                  //RAM写地址(下一个将要写入的地址)
logic [ADDR_WIDTH-1:0] rd_addr;                  //RAM读地址(下一个将要读出的地址)
logic [ADDR_WIDTH:0] wr_ptr;                     //有额外标志位
logic [ADDR_WIDTH:0] rd_ptr;                     //有额外标志位
//
logic [ADDR_WIDTH:0] wr_ptr_gray;
logic [ADDR_WIDTH:0] wr_ptr_gray_d1;
logic [ADDR_WIDTH:0] wr_ptr_gray_d2;
//
logic [ADDR_WIDTH:0] rd_ptr_gray;
logic [ADDR_WIDTH:0] rd_ptr_gray_d1;
logic [ADDR_WIDTH:0] rd_ptr_gray_d2;
//wr_ptr
always_ff@(posedge wclk,posedge rst)
if(rst)
    wr_ptr<=0;
else if(wr_en&&~full)                    //写使能为高且fifo未满
    wr_ptr<=wr_ptr+1;                    //数据写入,地址自增1,指向下一个要写入的地址
//rd_ptr
always_ff@(posedge rclk,posedge rst)
if(rst)
    rd_ptr<=0;
else if(rd_en&&~empty)                   //写使能且fifo非空
    rd_ptr<=rd_ptr+1;                    //数据读出,地址自增1,指向下一个要读出的地址
//wr_addr,rd_addr
assign wr_addr=wr_ptr[ADDR_WIDTH-1:0];
assign rd_addr=rd_ptr[ADDR_WIDTH-1:0];               //实际的RAM地址,去掉指针最高位
//write
always_ff@(posedge wclk)
if(wr_en&&~full)
    RAM[wr_addr]<=wdata;                  //在写使能有效且fifo未满的时候数据写入
//read
always_ff@(posedge rclk)
if(rd_en&&~empty)
    rdata<=RAM[rd_addr];                   //数据读出
//格雷码转换
assign wr_ptr_gray=(wr_ptr >> 1) ^ wr_ptr;
assign rd_ptr_gray=(rd_ptr >> 1) ^ rd_ptr;              //右移一位后和自身异或
//格雷码同步,打两拍
always_ff@(posedge wclk,posedge rst)
if(rst)
begin
    rd_ptr_gray_d1<=0;
    rd_ptr_gray_d2<=0;
end
else
begin
    rd_ptr_gray_d1<=rd_ptr_gray;                            
    rd_ptr_gray_d2<=rd_ptr_gray_d1;                         //将转为格雷码后的读指针同步到写时钟域,由于格雷码的特性,可以按照单bit信号的同步方式进行
end
//将写指针同步到读时钟域
always_ff@(posedge rclk,posedge rst)
if(rst)
begin
    wr_ptr_gray_d1<=0;
    wr_ptr_gray_d2<=0;
end
else
begin
    wr_ptr_gray_d1<=wr_ptr_gray;
    wr_ptr_gray_d2<=wr_ptr_gray_d1;                         //将写指针同步到读时钟域,以便进行比较,生成empty信号
end
//empty full
assign full=(wr_ptr_gray=={~rd_ptr_gray_d2[ADDR_WIDTH:ADDR_WIDTH-1],rd_ptr_gray_d2[ADDR_WIDTH-2:0]})?1'b1:1'b0;       //高两位不同,其他相同
assign empty=(rd_ptr_gray==wr_ptr_gray_d2)?1'b1:1'b0;
endmodule

测试平台(可调整读时钟和写时钟的快慢)

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/02/22 00:27:44
// Design Name: 
// Module Name: test
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module test;
parameter DATA_WIDTH = 32;
parameter FIFO_DEPTH = 32;
parameter PERIOD_W =30;
parameter PERIOD_R=17;
logic rst;
logic wclk;
logic [DATA_WIDTH-1:0] wdata;
logic wr_en;
logic full;
//
logic rclk;
logic [DATA_WIDTH-1:0] rdata;
logic rd_en;
logic empty;
//
logic error;
logic wr_en_r;
logic rd_en_r;
logic rd_en_d1;
logic [DATA_WIDTH-1:0] ref_data;
//ref_data
always_ff@(posedge rclk,posedge rst)
if(rst)
    ref_data<=0;
else if(rd_en_d1)
    ref_data<=ref_data+1;
//rd_en_d1
always_ff@(posedge rclk)
    rd_en_d1<=rd_en;
//error
always_comb
if(rd_en_d1&&rdata!=ref_data)
    error=1;
else
    error=0;
//wclk
initial begin
    wclk=0;
    forever begin
        #(PERIOD_W/2) wclk=~wclk;
    end
end
//rclk
initial
begin
    rclk=0;
    forever
    begin
        #(PERIOD_R/2) rclk=~rclk;
    end
end
//rst
initial
begin
    rst=1;
    #50
    rst=0;
end
//wdata
always_ff@(posedge wclk,posedge rst)
if(rst)
    wdata<=0;
else if(wr_en&&~full)
    wdata<=wdata+1;
//wr_en_r
always_ff@(posedge wclk,posedge rst)
if(rst)
    wr_en_r<=0;
else
    wr_en_r=$random%2;
//rd_en_r
always_ff@(posedge rclk,posedge rst)
if(rst)
    rd_en_r<=0;
else
    rd_en_r<=$random%2;
//rd_en,wr_en
assign rd_en=(~empty)?rd_en_r:1'b0;
assign wr_en=(~full)?wr_en_r:1'b0;
//inst
async_fifo
#(
.DATA_WIDTH(32),
.FIFO_DEPTH(32)
)
U
(.*);
endmodule

仿真波形:
在这里插入图片描述
可以看到,error信号始终为0,表明设计无误!

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FPGA硅农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值