异步FIFO设计

同步FIFO设计思路

  • 方法1:使用计数器记录FIFO有效数据,从而产生空满信号
  • 方法2:指针空间扩大一倍,读写指针最高位相同为空,最高位不同,剩下数据位相同为满

异步FIFO设计

  • 读写指针分别在各自的时钟域进行维护
  • 读空的时候需要在读时钟域进行判断,写满的时候需要在写时钟域进行判断,所以需要将读写指针同步给对方
  • 读指针同步到写时钟域,写指针同步到读时钟域?读写指针都是多bit信号,所以不能直接通过直接打两拍的方式进行同步,使用格雷码进行实现
  • 不使用格雷码,有些bit同步快,有些bit同步慢,所以会产生中间状态,影响电路功能

使用格雷码进行转换

  • 相邻两个数之间格雷码只有1bit发生变化
  • 格雷码产生:gray_code = din ^ (din >> 1),原数据异或右移1位的数据
  • 用指针的格雷码判断空满情况:如果写指针和读指针最高两位不等,其余位全相等表示满,如果读指针和写指针的全相等表示空

RTL

fifo.v
  wr_full.v
  rd_empty.v
  sync_2_stage.v
  fifo_mem.v

fifo.v


定义参数

  • ADDRSIZE - 地址位宽(和FIFO的深度有关)
  • DATASIZE - 数据位宽
  • SD_SLAVE_FIFO_DEPTH - FIFO深度
  • ahb_soft_rst_n - 是同步复位
  • ahb_fifo_rst_n和sd_fifo_rst_n是异步复位信号
  • blk_len输入的作用:当前fifo是1k(256深度*数据位宽32bit)的容量,sd host传输数据一个block length的容量是512byte,也就是半满状态就可以认为是fifo满了,所以需要block length产生判断半满的信号
  • bist测试 - 因为使用的是双端口的RAM,所以BIST逻辑分为a,b两端
    中间信号根据coding过程进行书写

sync_two_stage.v

  • 两级同步器,将读写指针转变为格雷码表示之后,经过两级同步器给到读时钟域和写时钟域
module sync_two_stage
#(
    parameter UDLY = 1,
    parameter ADDRSIZE = 8,
    parameter DATASIZE = 32,
    parameter SD_SLAVE_FIFO_DEPTH = 256
)
(
    input   wire                    clk     ,
    input   wire                    rst_n   ,
    input   wire                    clr     ,
    input   wire    [ADDRSIZE:0]    ptr_reg ,

    output  reg     [ADDRSIZE:0]    ptr_s2_reg         
);

    reg [ADDRSIZE:0] ptr_s1_reg;

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            ptr_s1_reg <= #UDLY 0;
            ptr_s2_reg <= #UDLY 0;
        end
        else if(clr) begin
            ptr_s1_reg <= #UDLY 0;
            ptr_s2_reg <= #UDLY 0;
        end
        else begin
            ptr_s1_reg <= #UDLY ptr_reg;
            ptr_s2_reg <= #UDLY ptr_s1_reg;
        end
            
    end
endmodule
  • 在sd host项目中,因为fifo模块两端都是可以进行读写操作的,所以需要四个同步器,例化四个两级同步器,同步读写指针信号
  • clr信号就是复位信号,由fifo.v模块产生之后输入
  • 需要同步的指针由rd_empty.v模块产生

rd_empty.v

  • 在读时钟域下产生读空信号,并且维护读指针给到RAM进行读取数据

  • 当读使能有效的时候并且没有读空的时候,读指针加1
  • 将读指针的二进制数转成格雷码的形式,将格雷码形式的读地址(读指针)输出
  • 空信号判断:转变为格雷码之后的读指针和输入的写指针地址是否一致,一致表示空,否则表示满
module rd_empty
#(
    parameter UDLY = 1,
    parameter ADDRSIZE = 8,
    parameter DATASIZE = 32,
    parameter SD_SLAVE_FIFO_DEPTH = 256
)
(
    input   wire                      clk             ,
    input   wire                      rst_n           ,
    input   wire                      clr             ,
    input   wire                      rinc            ,
    input   wire    [ADDRSIZE:0]      wptr_s2_reg     ,
    output  wire    [ADDRSIZE-1:0]    raddr           ,
    output  reg     [ADDRSIZE:0]      rptr_reg        ,
    output  reg                       rd_empty
);

    reg [ADDRSIZE:0] rptr_bin;
    
    wire             empty_val;

    wire [ADDRSIZE:0] rptr_bin_nxt;  // binary pointer
    wire [ADDRSIZE:0] rptr_gray_nxt; // gray code pointer
    wire [ADDRSIZE:0] rptr_gray_temp_nxt;

    // 产生二进制读指针

    assign rptr_bin_nxt = rptr_bin_nxt + {8'd0,(rinc && !rd_empty)};

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            rptr_bin <= 0;
        else if(clr)
            rptr_bin <= 0;
        else 
            rptr_bin <= rptr_bin_nxt;
    end

    assign rptr_gray_nxt = (rptr_bin_nxt >> 1) ^ rptr_bin_nxt;

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            rptr_reg <= 0;
        else if(clr)
            rptr_reg <= 0;
        else
            rptr_reg <= rptr_gray_nxt;
    end

    // 读地址
    assign raddr = rptr_bin[ADDRSIZE-1:0];

    assign empty_val = (rptr_gray_nxt == wptr_s2_reg);

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            rd_empty <= 0;
        else if(clr)
            rd_empty <= 0;
        else
            rd_empty <= empty_val;
    end

endmodule

wr_full.v

module wr_full
#(
    parameter UDLY = 1,
    parameter ADDRSIZE = 8,
    parameter DATASIZE = 32,
    parameter SD_SLAVE_FIFO_DEPTH = 256
)
(
    input   wire                      clk             ,
    input   wire                      rst_n           ,
    input   wire                      clr             ,
    input   wire                      winc            ,
    input   wire    [ADDRSIZE:0]      rptr_s2_reg     ,
    input   wire    [ADDRSIZE:0]      blk_len         , // wfull半满即为满
    output  wire    [ADDRSIZE-1:0]    waddr           ,
    output  reg     [ADDRSIZE:0]      wptr_reg        ,
    output  reg                       wr_full
);

    reg [ADDRSIZE:0] wptr_bin;

    wire             full_val;
    wire [ADDRSIZE:0] wptr_bin_nxt;  // binary pointer
    wire [ADDRSIZE:0] wptr_gray_nxt; // gray code pointer
    wire [ADDRSIZE:0] wptr_gray_temp_nxt;
    wire [ADDRSIZE:0] wptr_temp;

    // 产生二进制读指针

    assign wptr_bin_nxt = wptr_bin_nxt + {8'd0,(winc && !wr_full)};

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            wptr_bin <= 0;
        else if(clr)
            wptr_bin <= 0;
        else 
            wptr_bin <= wptr_bin_nxt;
    end

    // fifo深度 - block length
    assign wptr_temp = wptr_bin_nxt + (SD_SLAVE_FIFO_DEPTH - blk_len);

    // wptr_gray_nxt用于产生写指针,发送给另一个时钟域
    assign wptr_gray_nxt = (wptr_bin_nxt >> 1) ^ wptr_bin_nxt; 

    // wptr_gray_temp_nxt - 用于判断是否写满
    assign wptr_gray_temp_nxt = (wptr_temp >>1) ^ wptr_temp;  //wptr_gray_nxt

    // 输出写指针同步到读时钟域
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            wptr_reg <= 0;
        else if(clr)
            wptr_reg <= 0;
        else
            wptr_reg <= wptr_gray_nxt;
    end

    // 输出写地址给到RAM
    assign waddr = wptr_bin[ADDRSIZE-1:0];


    // 判断满信号,并输出出去
    assign full_val = (wptr_gray_temp_nxt == {~rptr_s2_reg[ADDRSIZE:ADDRSIZE-1],rptr_s2_reg[ADDRSIZE-2:0]});

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            wr_full <= 1'b0;
        else if(clr)
            wr_full <= 1'b0;
        else    
            wr_full <= full_val;
    end


endmodule

fifo_mem.v

  • fifo模块中例化存储体,将时钟复位\读写使能\读写数据的端口连接起来
posted @ 2024-01-14 13:42  Icer_Newer  阅读(146)  评论(0编辑  收藏  举报