异步FIFO原理与实现

近期参与了一项流片项目,里面用到了FIFO,但是只给了ram的IP,只能自己写一个FIFO了,趁此机会好好学习一下。

本文参考了一篇重要文献:Simulation and Synthesis Techniques for Asynchronous FIFO Design

貌似是FIFO的经典好文了,把异步FIFO的原理讲得很透彻。话不多说,上代码

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/04/04 11:28:01
// Design Name: 
// Module Name: afifo_1024x64
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module afifo_1024x64(
    input rst,
    input wr_clk,
    input rd_clk,
    input wr_en,
    input rd_en,
    input [63:0] wr_data,
    output [63:0] rd_data,
    output full,
    output empty,
    output [10:0] wr_data_count,
    output [10:0] rd_data_count
    );
parameter WIDTH = 64;
parameter DEPTH = 10;    


wire ram_wr_en,ram_rd_en;    
wire[DEPTH:0] rd_p_grey,wr_p_grey;
wire[DEPTH-1:0] ram_wr_addr,ram_rd_addr;
RA2SHD1024x64 bram(
        .QA       (),  
        .QB       (rd_data),
        .ADRA     (ram_wr_addr),
        .DA       (wr_data),
        .WEA      (ram_wr_en),
        .MEA      (1'b1),
        .CLKA     (wr_clk),
        .TEST1A   (1'b0),
        .RMEA     (1'b0),
        .RMA      (4'b0),
        .LS       (1'b0),
        .ADRB     (ram_rd_addr),
        .DB       (64'b0),
        .WEB      (1'b0),
        .MEB      (ram_rd_en),
        .CLKB     (rd_clk),
        .TEST1B   (1'b0),
        .RMEB     (1'b0),
        .RMB      (4'b0)
);   
//ram_16x64 ram_16x64_inst (
//  .clka(wr_clk),    // input wire clka
//  .ena(1'b1),      // input wire ena
//  .wea(ram_wr_en),      // input wire [0 : 0] wea
//  .addra(ram_wr_addr),  // input wire [3 : 0] addra
//  .dina(wr_data),    // input wire [63 : 0] dina
//  .douta(),  // output wire [63 : 0] douta
//  .clkb(rd_clk),    // input wire clkb
//  .enb(ram_rd_en),      // input wire enb
//  .web(1'b0),      // input wire [0 : 0] web
//  .addrb(ram_rd_addr),  // input wire [3 : 0] addrb
//  .dinb(64'b0),    // input wire [63 : 0] dinb
//  .doutb(rd_data)  // output wire [63 : 0] doutb
//);    
    
w_ctrl
#(
    .WIDTH(WIDTH),
    .DEPTH(DEPTH)
) w_ctrl_inst
(
     .rst                 (rst),
     .clk                 (wr_clk),
     .wr                  (wr_en),//fifo的wr_en
     .wr_en               (ram_wr_en),//ram的wr_en
     .rd_p_grey           (rd_p_grey),
     .full                (full),
     .ram_wr_addr         (ram_wr_addr),
     .wr_p_grey           (wr_p_grey),
     .wr_data_count       (wr_data_count)
);    

r_ctrl
# (
  .WIDTH(WIDTH),
  .DEPTH(DEPTH)
) r_ctrl_inst
(
     .rst                (rst),
     .clk                (rd_clk),
     .rd                 (rd_en), //fifo的rd_en
     .rd_en              (ram_rd_en),//ram的rd_en
     .wr_p_grey          (wr_p_grey),
     .empty              (empty),
     .ram_rd_addr        (ram_rd_addr),
     .rd_p_grey          (rd_p_grey),
     .rd_data_count      (rd_data_count)
    );
    
endmodule
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/04/04 11:24:46
// Design Name: 
// Module Name: w_ctrl
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module w_ctrl
#(
parameter WIDTH = 64,
parameter DEPTH = 10
)
(
    input rst,
    input clk,
    input wr,
    output wr_en,
    input [DEPTH:0] rd_p_grey,
    output full,
    output [DEPTH-1:0] ram_wr_addr,
    output [DEPTH:0] wr_p_grey,
    output [DEPTH:0] wr_data_count
);
    
    
reg [DEPTH:0] wr_p_r;  
wire [DEPTH:0] wr_p_gray_i;  

reg [DEPTH:0] rd_p_grey_r;
reg [DEPTH:0] rd_p_grey_r1;
reg [DEPTH:0] rd_p_g2b_r;

wire wr_en_i;
wire full_i;   
wire[DEPTH:0] p_xor;
reg[DEPTH:0] wr_data_count_r;  
wire[1:0] top_bit; 
integer i;
assign wr_en_i = wr&(~full_i); //ram的写使能信号,只有当fifo处于非满状态时才可以写入。当fifo写满时,则对外部的写信号不做响应,以保护内部源数据

always @ (posedge clk or posedge rst) begin
    if (rst) begin
        wr_p_r <= 'b0;
        rd_p_grey_r <= 'b0;
        rd_p_grey_r1 <= 'b0;
    end else begin
        if (wr_en_i) begin
            wr_p_r <= wr_p_r + 1'b1;
        end
        
        rd_p_grey_r <= rd_p_grey;
        rd_p_grey_r1 <= rd_p_grey_r;
    end
end    
//计算rd_data_count 
always @ (*) begin
    if (rst) begin
        rd_p_g2b_r = 'b0;
    end else begin
        rd_p_g2b_r[DEPTH] = rd_p_grey_r1[DEPTH];
        for(i=DEPTH-1;i>=0;i=i-1) begin
            rd_p_g2b_r[i] = rd_p_grey_r1[i] ^ rd_p_g2b_r[i+1];
        end
    end
end
assign top_bit = {wr_p_r[DEPTH],rd_p_g2b_r[DEPTH]};
always @ (posedge clk or negedge rst) begin
    if (rst) begin
        wr_data_count_r <= 'b0;
    end else begin
        case (top_bit)
            2'b00:begin
                wr_data_count_r <= wr_p_r - rd_p_g2b_r;
            end
            
            2'b11:begin
                wr_data_count_r <= wr_p_r - rd_p_g2b_r;
            end
            
            2'b10:begin
                wr_data_count_r <= wr_p_r - rd_p_g2b_r;
            end
            
            2'b01:begin
                wr_data_count_r <= {1'b1,wr_p_r[DEPTH-1:0]} - {1'b0,rd_p_g2b_r[DEPTH-1:0]};
            end
            
            default:begin
                wr_data_count_r <= wr_data_count_r;
            end
        endcase
    end
end

assign wr_p_gray_i = (wr_p_r>>1)^ wr_p_r;   //计算写指针的格雷码
assign p_xor = wr_p_gray_i^rd_p_grey_r1; //这个异或值用于判断fifo是否写满,即下一行
assign full_i = ((p_xor[DEPTH:DEPTH-1] == 2'b11 ) & (p_xor[DEPTH-2:0]== 0 ))? 1'b1:1'b0;//如果wr_p_gray_i^rd_p_grey_r1的结果是110000...0,那么就写满了,否则就没写满





//input & output
assign full = full_i; //fifo写满标志
assign wr_en = wr_en_i;   //ram的写使能,不要与fifo的外部写使能搞混
assign ram_wr_addr = wr_p_r[DEPTH-1 :0]; //ram的写地址,等于写指针去掉最高位
assign wr_p_grey = wr_p_gray_i;//当前写指针的格雷码,输出给r_ctrl
assign wr_data_count = wr_data_count_r[DEPTH:0];
endmodule
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/04/04 16:38:21
// Design Name: 
// Module Name: r_ctrl
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module r_ctrl
# (
parameter WIDTH = 64,
parameter DEPTH = 10
)
(
    input rst,
    input clk,
    input rd,
    output rd_en,
    input [DEPTH:0] wr_p_grey,
    output empty,
    output [DEPTH-1:0] ram_rd_addr,
    output [DEPTH:0] rd_p_grey,
    output [DEPTH : 0] rd_data_count 
    );
    
    
reg [DEPTH:0] rd_p_r;
wire [DEPTH:0] rd_p_grey_i;

reg [DEPTH:0] wr_p_grey_r;
reg [DEPTH:0] wr_p_grey_r1;
reg[DEPTH:0] wr_p_g2b_r; 

reg [DEPTH:0] rd_data_count_r;
wire[1:0] top_bit;
wire rd_en_i;
wire empty_i;
integer i;

assign rd_en_i = rd&(~empty_i);

always @ (posedge clk or posedge rst) begin
    if (rst) begin
        rd_p_r <= 'b0;
        wr_p_grey_r <= 'b0;
        wr_p_grey_r1 <= 'b0;

    end else begin
        if(rd_en_i) begin
            rd_p_r <= rd_p_r + 1'b1;
        end
        
        wr_p_grey_r <= wr_p_grey;
        wr_p_grey_r1 <= wr_p_grey_r;

    end
end

//always @ (*) begin
//    if(rst) begin
//        rd_data_count_r <= 'b0;
//    end else begin
        
//    end
//end
//将写指针的格雷码转换为二进制,与读指针的二进制进行比较,得到rd_data_count
always @ (*) begin //写指针格雷码转二进制
    if (rst) begin
        wr_p_g2b_r = 'b0;
    end else begin
        wr_p_g2b_r[DEPTH] = wr_p_grey_r1[DEPTH];
        for(i=DEPTH-1;i>=0;i=i-1) begin
            wr_p_g2b_r[i] = wr_p_grey_r1[i] ^ wr_p_g2b_r[i+1];    
        end
    end 
end 
//wr_p_g2b_r rd_p_r 
assign top_bit = {wr_p_g2b_r[DEPTH],rd_p_r[DEPTH]};
//计算rd_data_count_r ,将会比wr_p_grey_r1晚一个周期
always @ (posedge clk or posedge rst) begin
    if (rst) begin
        rd_data_count_r <='b0;
    end else begin
        case (top_bit)
        2'b00: begin
            rd_data_count_r <= wr_p_g2b_r - rd_p_r;
        end
        
        2'b11: begin
            rd_data_count_r <= wr_p_g2b_r - rd_p_r;
        end
        
        2'b10: begin
            rd_data_count_r <= wr_p_g2b_r - rd_p_r;
        end
        
        2'b01: begin
            rd_data_count_r <= {1'b1,wr_p_g2b_r[DEPTH-1 : 0]} - {1'b0,rd_p_r[DEPTH-1 :0]};
        end
        
        default: begin
            rd_data_count_r <= rd_data_count_r;
        end
        endcase
    end
end


assign rd_p_grey_i = (rd_p_r>>1)^(rd_p_r); //计算读指针的格雷码,用于与写指针的格雷码进行比较
assign empty_i = (rd_p_grey_i==wr_p_grey_r1)?1'b1:1'b0;//读指针的格雷码与写指针的格雷码进行比较,得到empty标志



//input & output
assign empty = empty_i;
assign rd_p_grey = rd_p_grey_i;
assign rd_en = rd_en_i;
assign ram_rd_addr = rd_p_r[DEPTH-1 : 0];
assign rd_data_count = rd_data_count_r[DEPTH:0];

endmodule

1.FIFO的空满判断

FIFO的空满的判断是基于写指针和读指针的。初始情况下,写指针和读指针都指向第一个存储单元,当写使能有效时,存储单元被写入新的数据,同时写指针移动到下一个存储单元;当读使能有效时,读指针指向的存储单元的值将被输出,同时读指针移动到下一个存储单元。指针走过所有存储单元后,将重新回到第一个存储单元。

以下是一些基本的要搞清楚的地方:

(1)一个深度为2^N次方的FIFO,存储单元的地址需要N bit,为什么写指针和读指针需要N+1位?

这是基于FIFO满状态的判断的需要。只有当写指针的值与读指针的值的差等于2^N时,才可以算是真正写满了,若指针只有N位,那么两者之差是不会大于2^N-1的。

举个例子吧,假如深度为2,有四个存储单元,如果读指针一直等于0,那么FIFO什么时候写满呢?

写指针=2’b11时算是写满了吗?并不是,因为此时地址为11的单元还未被写入,只有当写指针

等于4(100),那么此时00、01、10 、11的存储单元都被写过了,而00单元还没被读出,这时候

FIFO才算是被写满了。

(2)如何判断FIFO写满了或读空?

读指针与写指针的差值,最大为2^N,最小为0。由(1)我们已经知道了,指针最高位是判断满状态的关键。只有当写指针的值与读指针的值的差等于2^N时,FIFO才算写满,这个条件与下面的逻辑化表述等价:写指针与读指针的最高位不同,而其余位都相同。当写指针的值与读指针的值的差等于0时,FIFO读空,即写指针与读指针完全相同。

需要指出的是,FIFO满或者FIFO空时,读指针和写指针都指向同一个存储空间,只不过FIFO满的时候,读指针正好领先写指针整整一轮。

③为什么要用格雷码判断空满状态?

这是为了避免多比特同步所带来的隐患。满状态的判断是在写时钟域下进行的,空状态的判断是在读时钟域下进行的,因此需要将指针进行跨时钟域采样,这就可能会发生所谓的多比特同步问题:指针数据是多个比特的,当指针中有若干个比特同时翻转时,由于布局布线的不对称性,每个比特发生翻转的快慢是不同的,这就造成了数据在A到B之间的转换可能存在若干个中间状态。比如从00000000变到11111111,可能是00000000->00001010->11111010->11111111。如果碰巧采样到了中间变量,那么相当于指针的数据一下子跳到不知道哪个地方,会造成很大的不确定性。

格雷码由于相邻两个状态只有一个bit会发生变化,理论上是100%规避了上述的多比特同步问题。因为对格雷码采样的话,A->B的变化过程不存在中间变量,只会采样到A或者B,如果采样到了B,那么数据的更新没有延迟,如果采样到了A,那么数据的更新将会延迟一个时钟周期,这虽然不是我们希望看到的,但不会影响FIFO的安全性,反而会使电路更加安全,这一点稍后将会提到。

④格雷码的转换方法以及使用格雷码判断空满状态

二进制转格雷码:二进制数逻辑右移一位,再与原二进制数做异或,就得到对应的格雷码

格雷码转二进制:格雷码最高位等于二进制最高位,格雷码第k位与二进制第k+1位异或得到二进制第k位。先得到二进制的最高位,再依次向下迭代可以得到全部的二进制位。

使用格雷码判断满状态:可以由二进制判断满状态的方法得到如下规律:当读指针和写指针的格雷码最高位和次高位都不同,其余位都相同,那么FIFO处于满状态。

使用格雷码判断空状态:可以由二进制判断空状态的方法得到如下规律:当读指针和写指针的格雷码完全相同,那么FIFO处于空状态。

⑤问:如果读时钟远快于写时钟,那么读时钟采样到的写指针可能不会连续变化,那么写指针就算使用了格雷码,是不是还会存在多比特同步问题?

答:并不会,因为写指针每次都只有一个bit发生变化,写时钟采样读指针时,最坏的情况是单个比特采样错误,不会发生多比特同步问题。(只有在多个比特同时变化的时间窗口内,正好发生采样事件,才可能存在多比特同步问题)

⑥问:在写时钟域下判断满状态,需要将读指针(格雷码形式)同步到写时钟域,这造成了读指针的延后,这会不会导致FIFO已经写满而电路仍然认为FIFO未满,从而导致写溢出?

答:不会,反而进一步减小了溢出的可能性。任意一个时刻,用于判断满状态的读指针只可能会小于真实的读指针值,因此只可能会发生FIFO未满而被判断满。

同理地,在读时钟域,只可能会发生FIFO未空而被判断为空的情况发生,而不可能发生FIFO读空的情况。

⑦问:接⑥。你说有可能FIFO没有空而被判断为空,那最后一个数据会不会永远读不出来?

答:不会,FIFO没有空而被判断为空,原因在于真实的写指针还未被更新到读时钟域,这个情况在1~2个时钟周期内会消失,所以假空状态会马上消失。

话虽如此,在进行逻辑设计时,还是要尽量避免边界条件的发生,在FIFO内数据适中的时候进行读写,两端保持动态平衡,这是最好的。

另外,在某些情况下,读时钟和写时钟的频率差是有要求的,下一篇文章会就这一点做一些讨论。

本文为原创,如有错误,敬请指出,万分感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值