异步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,表明设计无误!