Asynchronous FIFO with gray code(异步FIFO verilog设计理念)

代码来自asic world 和paper“ Simulation and Synthesis Techniques for Asynchronous FIFO Design”,文章解说均为自己的理解,如果有错误欢迎纠正~

What is FIFO? 

FIFO 是一个满足“先进先出”的储存数据的结构,如果我以“A->B->C”的顺序存入数据,由于我先存入的C,那么我先读取的内容依然是C,最后读取到A。

Introduction:

异步fifo可以满足不同时钟域的数据读写问题,在fpga等时钟不止一个的系统里,当数据从(faster domain)快时钟域传递到慢的时钟域时(slow domain),需要fifo来满足数据的降频率读。所以异步fifo的设计非常重要。

对于同步FIFO(FIFO的读和写都发生于同一个时钟沿0。用一个counter计算写入的数量:递增(FIFO仅写入),递减(FIFO仅读)或保持(不写入和读取,或同时写入和读取操作)。当FIFO计数器达到预定的满值时,将FIFO的信号置为“满”,当FIFO计数器为零时FIFO为空。由于读和写都发生在相同的时钟,所以可以当作普通的储存器使用。

对于异步FIFO设计,则不能使用递增 - 递减FIFO计数器,因为它需要两个不同的异步时钟来控制计数器。要确定异步FIFO设计的满和空状态,必须比较写和读指针。

Principle:

要想理解FIFO设计,需先了解FIFO指针的工作原理。写指针总是指向要写入的下一个字;因此,在复位时,读写指针都清零,这也恰好是要写入的下一个FIFO字位置。在FIFO写操作中,写指针所指向的存储单元被写入,然后写指针递增以指向要写入的下一个位置。 类似地,读指针总是指向要读取的当前FIFO字。再次复位时,两个指针都复位为零,FIFO为空,读指针指向无效数据(因为FIFO为空,空标志置位)。一旦第一个数据字写入FIFO,写指针就会递增,空标志被清除,此时正在寻址第一个FIFO存储字内容的读指针会立即将第一个有效字传到FIFO的数据输出端口,由接收器逻辑读取。

但是,当读指针和写指针都相等时,FIFO有可能为空,也有可能为满:当两个指针在复位操作期间被复位为零时,或者当读指针赶上写指针时,读取了FIFO中的最后一个字,fifo空。当写指针写了一圈并再次赶上读指针,与其相等时,fifo为满。

扫描二维码关注公众号,回复: 3352932 查看本文章

这里介绍了两种常见的异步fifo设计方法。第一种的代码比较简单,第二种的综合出的电路更加安全。

设计理念一:当读指针读到最后一位,且马上追上写指针时,将“预设空”变量设为1。 当写指针写到最后一位,且马上追上读指针时,写指针已经多跑了一圈了,将“预设满”变量设为1。原理如下图:

如果写指针指向“1000”,读指针指向“0000”,此时写指针最高位“1”和读指针次高位“0”不同,写指针次高位“0”和读指针最高位“0”相同时,写指针马上追上读指针,即将达到fifio“预设满”状态。反之如果读指针此时为1000, 写指针此时为0000, 那么读指针即将追上写指针,fifo内容马上被读完,此时将“预设空”变量设为1。实现代码如下:

    assign Set_Status = (pNextWordToWrite[ADDRESS_WIDTH-2] ~^ pNextWordToRead[ADDRESS_WIDTH-1]) &
                         (pNextWordToWrite[ADDRESS_WIDTH-1] ^  pNextWordToRead[ADDRESS_WIDTH-2]);                         
    assign Rst_Status = (pNextWordToWrite[ADDRESS_WIDTH-2] ^  pNextWordToRead[ADDRESS_WIDTH-1]) &
                         (pNextWordToWrite[ADDRESS_WIDTH-1] ~^ pNextWordToRead[ADDRESS_WIDTH-2]);

设计中指针的译码使用的是格雷码,因为二进制码递增可能要改变好几位,格雷码只允许每个时钟转换一位改变,消除了在同一时钟边沿尝试同步多个改变信号的问题。 比如在二进制中,由1变为2需要经历两个阶段"0001"->"0000"->"0010",这就导致采样的时候可能得到0000而不是0010,从而引发问题。

Design Code:

module asynFifo
  #(parameter    DATA_WIDTH    = 8,
                 ADDRESS_WIDTH = 4,
                 FIFO_DEPTH    = (1 << ADDRESS_WIDTH))
     //Reading port
    (output reg  [DATA_WIDTH-1:0]        Data_out, 
     output reg                          Empty_out,
     input wire                          ReadEn_in,
     input wire                          RClk,        
     //Writing port.	 
     input wire  [DATA_WIDTH-1:0]        Data_in,  
     output reg                          Full_out,
     input wire                          WriteEn_in,
     input wire                          WClk,	 

     input wire                          Clear_in
    );

    reg   [DATA_WIDTH-1:0]              Mem [FIFO_DEPTH-1:0];             //memory
    wire  [ADDRESS_WIDTH-1:0]           pNextWordToWrite, pNextWordToRead; //r/w pointers
    wire                                EqualAddresses;
    wire                                NextWriteAddressEn, NextReadAddressEn;
    wire                                Set_Status, Rst_Status;
    reg                                 Status;
    wire                                PresetFull, PresetEmpty;
    
    //(Uses a dual-port RAM).
    //read data logic:
    always @ (posedge RClk)
        if (ReadEn_in & !Empty_out)     //读信号,fifo不是空的             
            Data_out <= Mem[pNextWordToRead];   //输出指针指向的内容
            
    //write data logic:
    always @ (posedge WClk)
        if (WriteEn_in & !Full_out)     //写信号,fifo不是满的
            Mem[pNextWordToWrite] <= Data_in;  //指针指向的地址处写入

    //Fifo addresses support logic: 
    //'Next Addresses' enable logic:
    assign NextWriteAddressEn = WriteEn_in & ~Full_out;   //写信号&fifo空
    assign NextReadAddressEn  = ReadEn_in  & ~Empty_out;  //读信号&fifo满
           
    //Addreses (Gray counters) logic:
    GrayCounter GrayCounter_pWr
       (.GrayCount_out(pNextWordToWrite),   
        .Enable_in(NextWriteAddressEn),
        .Clear_in(Clear_in),       
        .Clk(WClk)
       );
       
    GrayCounter GrayCounter_pRd
       (.GrayCount_out(pNextWordToRead),
        .Enable_in(NextReadAddressEn),
        .Clear_in(Clear_in),
        .Clk(RClk)
       );
     

    //'EqualAddresses' logic:
    assign EqualAddresses = (pNextWordToWrite == pNextWordToRead);

    //'Quadrant selectors' logic:
    assign Set_Status = (pNextWordToWrite[ADDRESS_WIDTH-2] ~^ pNextWordToRead[ADDRESS_WIDTH-1]) &
                         (pNextWordToWrite[ADDRESS_WIDTH-1] ^  pNextWordToRead[ADDRESS_WIDTH-2]);
                            
    assign Rst_Status = (pNextWordToWrite[ADDRESS_WIDTH-2] ^  pNextWordToRead[ADDRESS_WIDTH-1]) &
                         (pNextWordToWrite[ADDRESS_WIDTH-1] ~^ pNextWordToRead[ADDRESS_WIDTH-2]);
                         
    //'Status' latch logic:
    always @ (Set_Status, Rst_Status, Clear_in) //D Latch w/ Asynchronous Clear & Preset.
        if (Rst_Status | Clear_in)
            Status = 0;  //Going 'Empty'.
        else if (Set_Status)
            Status = 1;  //Going 'Full'.
            
    //'Full_out' logic for the writing port:
    assign PresetFull = Status & EqualAddresses;  //'Full' Fifo.
    
    always @ (posedge WClk, posedge PresetFull) //D Flip-Flop w/ Asynchronous Preset.
        if (PresetFull)
            Full_out <= 1;
        else
            Full_out <= 0;
            
    //'Empty_out' logic for the reading port:
    assign PresetEmpty = ~Status & EqualAddresses;  //'Empty' Fifo.
    
    always @ (posedge RClk, posedge PresetEmpty)  //D Flip-Flop w/ Asynchronous Preset.
        if (PresetEmpty)
            Empty_out <= 1;
        else
            Empty_out <= 0;
            
endmodule
module GrayCounter
   #(parameter   COUNTER_WIDTH = 4) 
    (output reg  [COUNTER_WIDTH-1:0]    GrayCount_out,  //'Gray' code count output.    
     input wire                         Enable_in,  //Count enable.
     input wire                         Clear_in,   //Count reset.    
     input wire                         Clk);

    /////////Internal connections & variables///////
    reg    [COUNTER_WIDTH-1:0]         BinaryCount;

    /////////Code///////////////////////   
    always @ (posedge Clk)
        if (Clear_in) begin
            BinaryCount   <= {COUNTER_WIDTH{1'b 0}} + 1;  //Gray count begins @ '1' with
            GrayCount_out <= {COUNTER_WIDTH{1'b 0}};      // first 'Enable_in'.
        end
        else if (Enable_in) begin
            BinaryCount   <= BinaryCount + 1;
            GrayCount_out <= {BinaryCount[COUNTER_WIDTH-1],
                              BinaryCount[COUNTER_WIDTH-2:0] ^ BinaryCount[COUNTER_WIDTH-1:1]};
        end
    
endmodule

设计理念二:

来自paper“Simulation and Synthesis Techniques for Asynchronous FIFO Design” ---Clifford E. Cummings, Sunburst Design, Inc

用“MSBs”来区分满和空:当写指针超过最后的FIFO地址的值时,写指针将使未使用的MSB递增,同时将其余的位设为0。读指针也是如此。 如果两个指针的MSB不同,则代表写指针已经游历了整个FIFO,多跑了一圈且又赶上了读指针,此时FIFO为满,否则为空。如果fifo地址位宽为n-1,那么指针的位宽为n。

这篇paper介绍了一种改进的“格雷码”,不同于传统意义的格雷码,它把第一位当作msb位来区分空和满,后三位做地址位。这种做法会导致传统格雷码的“7”和“8”出现问题,因为他两的后三位是一样的,而第一位msb却不同,算法将会认为这是“满”,而其实fifo并未满。所以为了解决这个问题,作者把“8”往后的三位从“镜面”反射“0”到“7”的后三位,改为了复制从“0”到“7”的后三位。改后的新格雷码如图:

这个格雷码(假设为rgraynext)可以通过二进制数(rbinnext)右移一位与自身异或实现:

assign rgraynext=(rbinnext>>1)^rbinnext;

由于这个伪格雷码没有严格意义上的每次只变化一位,所以整个fifo设计用了二进制和伪格雷码两种译码方式。用伪格雷码来比较读指针和写指针,用二进制码来查找dual ram中的地址。保证了fifo运行过程中不会出错。

记住,由于首位用于“msb”比较,所以读写指针的位数比寻址数多一位。

整个FIFO设计如下:

代码可以在“http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf” 里找到。

猜你喜欢

转载自blog.csdn.net/weixin_40038116/article/details/81161300