手写异步FIFO

以下内容摘自:《正点原子逻辑设计指南》

一、异步FIFO简介

异步 FIFO 有两个时钟信号,读和写接口分别采用不同时钟,这两个时钟可能时钟频率不同,也可能时钟相位不同,可能是同源时钟,也可能是不同源时钟。

在现代逻辑设计中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步 FIFO 是这个问题的一种简便、快捷的解决方案,使用异步 FIFO 可以在两个不同时钟系统之间快速而方便地传输实时数据。

异步 FIFO 指针的考虑:

为什么异步 FIFO 的指针需要特殊处理呢?因为异步 FIFO 的空满指示需要使用指针进行判断,如果空满判断直接用两个时钟域的信号做逻辑判断,会导致逻辑判断错误,如下图所示:读逻辑的时钟采样写地址的时候,由于路径延迟和两个时钟相位都不同,就会导致读逻辑采样到的写地址完全错误(如果采样的时候刚好写地址信号跳变,还可能会导致亚稳态产生),导致空标记错误,导致 FIFO 不能使用。
在这里插入图片描述
一般异步 FIFO 的地址传递需要使用格雷码,每次地址变化格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到 gray 码的转换电路,将地址值转换为相应的 gray 码,然后将该 gray 码同步到另一个时钟域进行对比,作为空满状态的检测。

使用 gray 码进行对比,如何判断“空”与“满”?

使用 gray 码解决了指针采样错误的问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。

  • 对于“空”的判断依然依据二者完全相等(包括 MSB);

  • 而对于“满”的判断,如下图,由于 gray 码除了 MSB 外,具有镜像对称的特点,当读指针指向 7,写指针指向 8 时,除了 MSB,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在 gray 码上判断为满必须同时满足以下 3 条:
    1、wptr 和同步过来的 rptr 的 MSB 不相等,因为 wptr 必须比 rptr 多折回一次。
    2、wptr 与 rptr 的次高位不相等,如下图位置 7 和位置 15,转化为二进制对应的是 0111 和 1111,MSB不同说明多折回一次,111 相同代表同一位置。
    3、 剩下的其余位完全相等。
    在这里插入图片描述
    下图即为异步FIFO的系统框图:
    在这里插入图片描述

二、程序设计

1、RTL代码

module AsyncFIFO
#(parameter ASIZE = 4, //地址位宽
  parameter DSIZE = 8) //数据位宽
( 
    input [DSIZE-1:0] wdata, 
    input winc, wclk, wrst_n, //写请求信号,写时钟,写复位
    input rinc, rclk, rrst_n, //读请求信号,读时钟,读复位
    output [DSIZE-1:0] rdata, 
    output wfull,
    output rempty
 );
    wire [ASIZE-1:0] waddr ;
    wire [ASIZE-1:0] raddr ;
    wire [ASIZE:0] wptr ;
    wire [ASIZE:0] rptr ;
    wire [ASIZE:0] wq2_rptr ;
    wire [ASIZE:0] rq2_wptr ;
 /************************************************************
 * In order to perform FIFO full and FIFO empty tests using 
 * this FIFO style, the read and write pointers must be
 * passed to the opposite clock domain for pointer comparison
 *************************************************************/
 //在检测“满”或“空”状态之前,需要将指针同步到其它时钟域时,
 //使用格雷码,可以降低同步过程中亚稳态出现的概率
sync_r2w I1_sync_r2w(
    .wq2_rptr(wq2_rptr),
    .rptr(rptr),
    .wclk(wclk),
    .wrst_n(wrst_n));

sync_w2r I2_sync_w2r (
    .rq2_wptr(rq2_wptr),
    .wptr(wptr),
    .rclk(rclk),
    .rrst_n(rrst_n));

/*
* DualRAM 
*/
DualRAM #(DSIZE, ASIZE) I3_DualRAM(
    .rdata(rdata),
    .wdata(wdata),
    .waddr(waddr),
    .raddr(raddr),
    .wclken(winc),
    .wclk(wclk));
/*
* 空、满比较逻辑
*/
rptr_empty #(ASIZE) I4_rptr_empty(
    .rempty(rempty),
    .raddr(raddr),
    .rptr(rptr),
    .rq2_wptr(rq2_wptr),
    .rinc(rinc),
    .rclk(rclk),
    .rrst_n(rrst_n));
wptr_full #(ASIZE) I5_wptr_full(
    .wfull(wfull),
    .waddr(waddr),
    .wptr(wptr),
    .wq2_rptr(wq2_rptr),
    .winc(winc),
    .wclk(wclk),
    .wrst_n(wrst_n));

endmodule

module DualRAM
#(
parameter DATA_SIZE = 8, // 数据位宽
parameter ADDR_SIZE = 4 // 地址位宽
)
(
    input wclken,wclk,
    input [ADDR_SIZE-1:0] raddr, //RAM read address
    input [ADDR_SIZE-1:0] waddr, //RAM write address
    input [DATA_SIZE-1:0] wdata, //data input
    output [DATA_SIZE-1:0] rdata //data output
); 

localparam RAM_DEPTH = 1 << ADDR_SIZE; //RAM 深度 = 2^ADDR_WIDTH
reg [DATA_SIZE-1:0] Mem[RAM_DEPTH-1:0];

always@(posedge wclk) begin
    if(wclken)
        Mem[waddr] <= wdata;
end

assign rdata = Mem[raddr];

endmodule

module sync_r2w 
#(parameter ADDRSIZE = 4)
(
    output reg [ADDRSIZE:0] wq2_rptr,
    input [ADDRSIZE:0] rptr,
    input wclk, wrst_n
);
 
 reg [ADDRSIZE:0] wq1_rptr;
 
 always @(posedge wclk or negedge wrst_n) begin
    if (!wrst_n)
        {
    
    wq2_rptr,wq1_rptr} <= 0;
    else
        {
    
    wq2_rptr,wq1_rptr} <= {
    
    wq1_rptr,rptr};
 end
 
 endmodule
 
 module sync_w2r 
 #(parameter ADDRSIZE = 4)
 (
    output reg [ADDRSIZE:0] rq2_wptr,
    input [ADDRSIZE:0] wptr,
    input rclk, rrst_n
 ); 
 
 reg [ADDRSIZE:0] rq1_wptr;
 
 always @(posedge rclk or negedge rrst_n)
    if (!rrst_n)
        {
    
    rq2_wptr,rq1_wptr} <= 0;
    else
        {
    
    rq2_wptr,rq1_wptr} <= {
    
    rq1_wptr,wptr};
 endmodule
 
 module rptr_empty 
 #(parameter ADDRSIZE = 4)
 (
    output reg rempty,
    output [ADDRSIZE-1:0] raddr,
    output reg [ADDRSIZE :0] rptr,
    input [ADDRSIZE :0] rq2_wptr,
    input rinc, rclk, rrst_n);
 
    reg [ADDRSIZE:0] rbin;
    wire [ADDRSIZE:0] rgraynext, rbinnext;
    wire rempty_val;
 
 //-------------------
 // GRAYSTYLE2 pointer: gray 码读地址指针
 //-------------------
 always @(posedge rclk or negedge rrst_n) begin
    if (!rrst_n) begin
        rbin <= 0;
        rptr <= 0;
    end
    else begin
        rbin <= rbinnext ;
        rptr <= rgraynext;
    end
 end
 
 // gray 码计数逻辑
 assign rbinnext = !rempty ? (rbin + rinc) : rbin;
 assign rgraynext = (rbinnext>>1) ^ rbinnext; //二进制到 gray 码的转换
 assign raddr = rbin[ADDRSIZE-1:0];
 
 //---------------------------------------------------------------
 // FIFO empty when the next rptr == synchronized wptr or on reset
 //---------------------------------------------------------------
 /*
 * 读指针是一个 n 位的 gray 码计数器,比 FIFO 寻址所需的位宽大一位
 * 当读指针和同步过来的写指针完全相等时(包括 MSB),说明二者折回次数一致,FIFO 为空
 */
 assign rempty_val = (rgraynext == rq2_wptr);
 
 always @(posedge rclk or negedge rrst_n)
    if (!rrst_n)
        rempty <= 1'b1;
    else
        rempty <= rempty_val;
 endmodule
 
 module wptr_full 
 #(
 parameter ADDRSIZE = 4
 )
 (
    output reg wfull,
    output [ADDRSIZE-1:0] waddr,
    output reg [ADDRSIZE :0] wptr,
    input [ADDRSIZE :0] wq2_rptr,
    input winc, wclk, wrst_n); 
 
    reg [ADDRSIZE:0] wbin;
    wire [ADDRSIZE:0] wgraynext, wbinnext;
    wire wfull_val;
 
 // GRAYSTYLE2 pointer
 always @(posedge wclk or negedge wrst_n) begin
    if (!wrst_n) begin
        wbin <= 0;
        wptr <= 0;
    end
    else begin
        wbin <= wbinnext;
        wptr <= wgraynext;
    end
 end
 
 //gray 码计数逻辑 
 assign wbinnext = !wfull ? wbin + winc : wbin;
 assign wgraynext = (wbinnext>>1) ^ wbinnext;
 assign waddr = wbin[ADDRSIZE-1:0];
 /*由于满标志在写时钟域产生,因此比较安全的做法是将读指针同步到写时钟域*/
 
 /**/
 //------------------------------------------------------------------
 // Simplified version of the three necessary full-tests:
 // assign wfull_val=((wgnext[ADDRSIZE] !=wq2_rptr[ADDRSIZE] ) &&
 // (wgnext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1]) &&
 // (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));
 //------------------------------------------------------------------
 assign wfull_val = (wgraynext== {
    
    ~wq2_rptr[ADDRSIZE:ADDRSIZE- 1],wq2_rptr[ADDRSIZE-2:0]});
 
 always @(posedge wclk or negedge wrst_n) begin
    if (!wrst_n)
        wfull <= 1'b0;
    else
        wfull <= wfull_val;
end
endmodule

2、测试代码

`timescale 1 ns/ 1 ps
module TB();
    reg rclk; 
    reg rinc; 
    reg rrst_n; 
    reg wclk; 
    reg [7:0] wdata; 
    reg winc; 
    reg wrst_n; 
    wire [7:0] rdata;
    wire rempty;
    wire wfull; 
 
 initial
 begin
    rrst_n = 0;
    wrst_n = 0;
    rclk = 0;
    wclk = 0;
    winc = 0;
    rinc = 0;
    #20 rrst_n = 1;
    wrst_n =1 ;
 end
 
 initial begin
    rclk = 'b0 ;
    wclk = 'b0 ;
 
 end
 
 always #10 rclk = ~ rclk;
 always #10 wclk = ~ wclk;
 
 always @(posedge rclk)
    rinc <= {
    
    $random} % 2;
 always @(posedge wclk)
    winc <= {
    
    $random} % 2;
 
 always @(negedge wclk)
    wdata <= {
    
    $random} % 256;
 
 AsyncFIFO U0_AsyncFIFO (
    .rclk(rclk),
    .rdata(rdata),
    .rempty(rempty),
    .rinc(rinc),
    .rrst_n(rrst_n),
    .wclk(wclk),
    .wdata(wdata),
    .wfull(wfull),
    .winc(winc),
    .wrst_n(wrst_n)
 );
 
 endmodule

3、仿真结果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_39507748/article/details/114587132