异步FIFO的Verilog HDL设计实现

1.FIFO简介

  • 按指针顺序读写数据

    FIFO是“First In First Out的简称,是一种根据“先写入的数据则先读出来”的规则进行数据吞吐的数据缓存器。与其它的数据存储器不同,FIFO没有数据地址线,所以数据只能顺序写入和顺序写出。

  • 读/写时钟域可以不一样

    异步FIFO的数据写入和数据读出是两个独立的操作,分别由两个不同的时钟进行同步操作:将数据写入FIFO是一个时钟域,将数据从FIFO读出时需要另外一个时钟域。

    虽然两个时钟域是独立的,但是对于FIFO的数据读写是可以同时发生的。因为这个特性的存在,使得异步FIFO通常作为2个不同时钟域模块之间的数据缓冲。

  • 空/满信号

    对于异步FIFO来说,空满标志的产生尤其重要:FIFO要在装满数据的时候及时拉高fifo_full信号,否则之前的数据会被覆盖;FIFO要在内部没有数据的时候及时拉高fifo_empty信号,否则将读出错误的数据。

  • 空/满信号的产生

    当写指针追上读指针的时候,意味着FIFO已经写满了,此时fifo_full应立即拉高;而当读指针追上了写指针的时候,意味着FIFO已经读空了,此时fifo_empty应立即拉高。

    由于FIFO是异步的,所以空满信号的产生具有一定的要求:满信号fifo_full需要在写时钟域产生;空信号fifo_empty需要在读时钟域产生。

    满信号fifo_full需要在写时钟域产生,且判断该信号的时候需要参考读时钟域的读指针。若直接将写时钟域的写指针wr_ptr和读时钟域的读指针rd_ptr进行比较,由于时钟域的不同,很容易发生数据覆盖和数据错误读取的情况发生。

    例如,当写时钟wr_clk频率很快的时候,若直接将写指针和读指针进行比较来判断fifo_full信号,则可能已经有多个数据写进了FIFO后fifo_full信号才拉高。

    解决这个问题的方法在于将读指针rd_ptr从读时钟域同步至写时钟域,然后再与写指针wr_ptr进行比较,以判断fifo_full是否需要拉高。这样的话,写指针能够在写时钟的下一个周期立马得到读指针的数值,能够在FIFO写满的时候立马将满信号fifo_full置高。

    同理,空信号fifo_empty亦是如此。

  • 亚稳态问题和格雷码

    在跨时钟域同步的过程中,若直接将二进制的指针进行传递,那么在指针发生变化的时候,会有多个比特的数据发生变化,例如0111->1000,这样很容易产生亚稳态。

    亚稳态是指触发器在其建立时间和保持时间阶段输出不能够保持稳定,而当其从亚稳态中脱离后,输出值可能早已发生了错误。亚稳态是可以传递给下一级触发器的,所以这种错误也会一直传递下去。

  • 使用格雷码避免亚稳态

    可以使用格雷码解决亚稳态的问题:相邻格雷码之间只有1位不同,这也就意味着格雷码在自增的时候,只有1位数据会发生变化,这将极大地避免亚稳态的产生。

    对于一个普通二进制数,可以通过异或操作很方便地得到其格雷码值,例如:

    十进制表示 普通二进制表示 移位操作结果 格雷码表示
    1 0001 0000 0001
    2 0010 0001 0011
    3 0011 0001 0010
    4 0100 0010 0110

    用VerilogHDL代码获取写指针wr_ptr的格雷码可以写为:

    assign wr_ptr_gray = wr_ptr^(wr_ptr>>1);//由于wr_ptr是一个无符号整数,所以右移操作后最高位补的是0
    

    将格雷码应用于异步FIFO中的方法:先将二进制的指针通过组合逻辑电路转化为格雷码的形式,随后再进行跨时钟域的同步,同步的时候可以用寄存器打一拍(或者两拍)以保持数据的稳定,例如:

    //get Gray code of rd_ptr
    always @(*) begin
        rd_ptr_gray = rd_ptr^(rd_ptr>>1);
    end
    
    //sync rd_ptr from rd_clk to wr_clk
    always @(posedge wr_clk or negedge rst_n) begin
        if(rst_n) begin
            sync_rd_ptr_gray_dff <= 'b0;
            sync_rd_ptr_gray <= 'b0;
        end
        else begin
          sync_rd_ptr_gray_dff <= rd_ptr_gray;
            sync_rd_ptr_gray <= sync_rd_ptr_gray_dff;
        end
    end
    

2.代码实现

  • 基于SystemVerilog的异步FIFO设计实现:
module sv_test #(
    parameter AWIDTH = 4,
    parameter DWIDTH = 8,
    parameter DEPTH  = 1 << AWIDTH
)(
    input logic rst_n,
    input logic wr_clk,
    input logic rd_clk,
    input logic wr_en,
    input logic rd_en,
    input logic [DWIDTH-1:0] din,
    output logic fifo_full,
    output logic fifo_empty,
    output logic dout_valid,
    output logic [DWIDTH-1:0] dout
);

    logic is_in, is_out;
    logic [AWIDTH:0] wptr_bin, wptr_gray, next_wptr_bin, next_wptr_gray, sync_wptr_bin, sync_wptr_gray, sync_wptr_gray_dff;
    logic [AWIDTH:0] rptr_bin, rptr_gray, next_rptr_bin, next_rptr_gray, sync_rptr_bin, sync_rptr_gray, sync_rptr_gray_dff;
    logic [AWIDTH-1:0] raddr, waddr;
    logic [DWIDTH-1:0] ram [DEPTH];

    assign is_in = wr_en & (~fifo_full);//judge if data should be written in async_fifo
    assign is_out = rd_en & (~fifo_empty);//judge if data should be read from async_fifo

    always @(posedge wr_clk or negedge rst_n) begin
        if(!rst_n) begin
            wptr_bin <= 'b0;
        end
        else if(is_in) begin
            wptr_bin <= wptr_bin + 1'b1;
        end
    end

    always @(posedge rd_clk or negedge rst_n) begin
        if(!rst_n) begin
            rptr_bin <= 'b0;
        end
        else if(is_out) begin
            rptr_bin <= rptr_bin + 1'b1;
        end
    end

    assign wptr_gray = wptr_bin ^ (wptr_bin>>1);
    assign rptr_gray = rptr_bin ^ (rptr_bin>>1);
    assign next_wptr_bin = wptr_bin + 1'b1;
    assign next_rptr_bin = rptr_bin + 1'b1;
    assign next_wptr_gray = next_wptr_bin ^ (next_wptr_bin>>1);
    assign next_rptr_gray = next_rptr_bin ^ (next_rptr_bin>>1);

    //wptr_gray should be stable in rd_clk
    always @(posedge rd_clk or negedge rst_n) begin
        if(!rst_n) begin
            sync_wptr_gray_dff <= 'b0;
            sync_wptr_gray <= 'b0;
        end
        else begin
            sync_wptr_gray_dff <= wptr_gray;
            sync_wptr_gray <= sync_wptr_gray_dff;
        end
    end

    //rptr_gray should be stable in wr_clk
    always @(posedge wr_clk or negedge rst_n) begin
        if(!rst_n) begin
            sync_rptr_gray_dff <= 'b0;
            sync_rptr_gray <= 'b0;
        end
        else begin
            sync_rptr_gray_dff <= rptr_gray;
            sync_rptr_gray <= sync_rptr_gray_dff;
        end
    end

    //judge if fifo_full signal should be pull up
    assign fifo_full = ((sync_rptr_gray[AWIDTH] != wptr_gray[AWIDTH])
                && ((sync_rptr_gray[AWIDTH]^sync_rptr_gray[AWIDTH-1]) == (wptr_gray[AWIDTH]^wptr_gray[AWIDTH-1]))
                && (sync_rptr_gray[AWIDTH-2:0] == (wptr_gray[AWIDTH-2:0])));

    //judge if fifo_empty signal should be pull up
    assign fifo_empty = (sync_wptr_gray == rptr_gray); 

    always @(posedge wr_clk or negedge rst_n) begin
        if(!rst_n) begin
            waddr <= 'b0;
        end
        else if(is_in) begin
            waddr <= next_wptr_bin[AWIDTH-1:0];
        end
    end

    always @(posedge rd_clk or negedge rst_n) begin
        if(!rst_n) begin
            raddr <= 'b0;
        end
        else if(is_out) begin
            raddr <= next_rptr_bin[AWIDTH-1:0];
        end
    end

    always @(posedge wr_clk or negedge rst_n) begin
        if(is_in) begin
            ram[waddr] <= din;
        end
    end

    always @(posedge rd_clk or negedge rst_n) begin
        if(!rst_n) begin
            dout_valid <= 'b0;
        end
        else if(is_out) begin
            dout_valid <= 'b1;
            dout <= ram[raddr];
        end
        else begin
            dout_valid <= 'b0;
        end
    end
endmodule
  • 一个可供参考的测试代码:
module tb_sv_test #(
    parameter AWIDTH = 4,
    parameter DWIDTH = 8,
    parameter DEPTH  = 1 << AWIDTH
)();
    logic rst_n;
    logic wr_clk;
    logic rd_clk;
    logic wr_en;
    logic rd_en;
    logic [DWIDTH-1:0] din;
    logic fifo_full;
    logic fifo_empty;
    logic dout_valid;
    logic [DWIDTH-1:0] dout;

    sv_test my_top( .rst_n(rst_n),
                    .wr_clk(wr_clk),
                    .rd_clk(rd_clk),
                    .wr_en(wr_en),
                    .rd_en(rd_en),
                    .din(din),
                    .fifo_full(fifo_full),
                    .fifo_empty(fifo_empty),
                    .dout_valid(dout_valid),
                    .dout(dout)
                    );

    initial begin
        wr_clk = 0;
        rd_clk = 0;
        fork
            forever begin
                #5 wr_clk = ~wr_clk;
            end
            forever begin
                #20 rd_clk = ~rd_clk;
            end
        join_none
    end

    initial begin
        wr_en = 0;
        rd_en = 0;
        rst_n = 0;
        #25;
        rst_n = 1;
        #10;
        fork
            forever begin
                din = $urandom_range(0,255);
                #2;
                #0;
                wr_en = 1;
                #10;
                #0;
                wr_en = 0;
            end
        join_none
        #500;
        fork
            forever begin
                #0;
                rd_en = 1;
                #40;
                rd_en = 0;
                #2;
            end
        join_none
    end

    initial begin
        #3000;
        $finish;
    end

    // dump fsdb
    `ifdef DUMP
    initial begin//do_not_remove 
        $fsdbAutoSwitchDumpfile(1000, "./fsdb/test_fsdb.fsdb", 10);//do_not_remove 
        $fsdbDumpvars(0, my_top);//do_not_remove 
        $fsdbDumpMDA(1000, my_top);//do_not_remove 
        // $fsdbDumpflush();//do_not_remove 
        //$fsdbDumpvars("+all");//do_not_remove 
    end//do_not_remove 
    `endif

endmodule
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343