美文网首页
异步FIFO的Verilog HDL设计实现

异步FIFO的Verilog HDL设计实现

作者: 一条摸水鱼 | 来源:发表于2020-07-02 00:30 被阅读0次

    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
    

    相关文章

      网友评论

          本文标题:异步FIFO的Verilog HDL设计实现

          本文链接:https://www.haomeiwen.com/subject/sznkqktx.html