手撕代码——异步FIFO

手撕代码——异步FIFO

  • 一、异步FIFO原理与设计
    • 读写地址指针控制
    • 读写地址指针跨时钟处理与空满信号判断
    • 读写地址与读写操作
  • 二、完整代码与仿真文件
  • 三、仿真结果

一、异步FIFO原理与设计

  在FIFO的设计中,无论是同步FIFO,还是异步FIFO,最最最最重要的就是如何判断空信号empty与满信号full。在上文《手撕代码——同步FIFO》中,我们设计的同步FIFO,使用一个数据计数器cnt来计算当前FIFO中存储的数据个数,根据计数器cnt的值来判断FIFO的空信号empty与满信号full。如果计数器cnt=0,表示FIFO中当前存储的数据个数为0,则拉高空信号empty;如果计数器cnt=FIFO_DEPTH(FIFO深度),则表示拉高满信号full。那么,在异步FIFO的设计中,是否也可以使用计数器的方式判断空满信号呢?在异步FIFO的设计中,我们不用计数器的方式来判断空满,而是将读写指针地址的位宽增加一位冗余,用于判断空满信号。

读写地址指针控制

  首先,是读写地址指针的控制。在写时钟域,当写使能wr_en有效且满信号full为低电平时,表示FIFO中数据未写满,执行写操作,写地址指针wr_ptr自加1;在读时钟域,当读使能rd_en有效且空信号empty为低电平时,表示FIFO中数据未读空,执行读操作,读地址指针rd_ptr自加1。

//write pointer
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n)
        wr_ptr <= 'd0;
    else if(wr_en && !full)
        wr_ptr <= wr_ptr + 1'b1;
end

//read pointer
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n)
        rd_ptr <= 'd0;
    else if(rd_en && !empty)
        rd_ptr <= rd_ptr + 1'b1;
end

读写地址指针跨时钟处理与空满信号判断

  其次,就是使用读写地址指针进行判断空满信号。读地址指针rd_ptr是在读时钟域wr_clk内,空信号empty也是在读时钟域内产生的;而写地址指针wr_ptr是在写时钟域内,且满信号也是在写时钟域内产生的。
  那么,要使用读地址指针rd_ptr与写地址指针wr_ptr对比,产生空信号empty,可以直接对比吗?答案是不可以。因为这两个信号处于不同的时钟域内,要做跨时钟域CDC处理,而多bit信号做跨时钟域处理,常用的方法就是使用异步FIFO进行同步,可是我们不是在设计异步FIFO吗?于是,在这里设计异步FIFO,多bit跨时钟域处理的问题可以转化为单bit跨时钟域的处理,把读写地址指针转换为格雷码后再进行跨时钟域处理,因为无论多少比特的格雷码,每次加1,只改变1位。把读地址指针rd_ptr转换为格雷码,然后同步到写时钟域wr_clk,与写指针地址wr_ptr的格雷码表示进行对比判断,得到满信号full;同样的,把写地址指针wr_ptr转换为格雷码,然后同步到读时钟域rd_clk,与读地址指针rd_ptr的格雷码表示进行对比判断,得到空信号empty。
  所以,需要先把二进制码表示的读地址指针rd_ptr与写地址指针wr_ptr转换为格雷码(二进制码与格雷码的转换原理及设计参考《二进制码与格雷码的相互转换原理与Verilog实现》)。

//write pointer(binary to gray)
assign wr_ptr_gray = wr_ptr ^ (wr_ptr>>1);

//read pointer(binary to gray)
assign rd_ptr_gray = rd_ptr ^ (rd_ptr>>1);

   然后再对格雷码表示的读写地址指针做单bit跨时钟域处理,打上两拍。需要注意的是:写地址指针的格雷码wr_ptr_gray表示要同步到读时钟域,所以要使用读时钟域的时钟信号rd_clk;而读地址指针的格雷码表示rd_ptr_gray表示要同步到写时钟域,所以要使用写时钟域的时钟信号wr_clk。

//gray write pointer syncrous
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n) begin
        wr_ptr_gray_w2r_1 <= 'd0;
        wr_ptr_gray_w2r_2 <= 'd0;
    end
    else begin
        wr_ptr_gray_w2r_1 <= wr_ptr_gray;
        wr_ptr_gray_w2r_2 <= wr_ptr_gray_w2r_1;
    end
end 

//gray read pointer syncrous
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n) begin
        rd_ptr_gray_r2w_1 <= 'd0;
        rd_ptr_gray_r2w_2 <= 'd0;
    end
    else begin
        rd_ptr_gray_r2w_1 <= rd_ptr_gray;
        rd_ptr_gray_r2w_2 <= rd_ptr_gray_r2w_1;
    end
end 

  然后使用同步处理后的读写地址指针格雷码表示进行对比,得到空满信号。如果写地址指针的格雷码表示同步到读时钟域后,与写地址指针的格雷码表示一致,则表示读空;如果读地址指针的格雷码表示同步到写时钟域后,高两位与写地址指针的格雷码表示相反,而剩余的位与写地址指针的格雷码表示一致,则表示写满。

//full and empty
assign full = (wr_ptr_gray == {~rd_ptr_gray_r2w_2[ADDR_WIDTH:ADDR_WIDTH-1],rd_ptr_gray_r2w_2[ADDR_WIDTH-2:0]}) ? 1'b1 : 1'b0;
assign empty = (rd_ptr_gray == wr_ptr_gray_w2r_2) ? 1'b1 : 1'b0;

读写地址与读写操作

  读写地址指针中,我们设置了一位的冗余位宽,用于判断空满,那么,实际上读写地址应该为读写地址指针的次高位到第0位。取读写地址指针的次高位到第0位,得到读写地址,同时根据读写地址进行读写操作。

//write address and read address
assign wr_addr = wr_ptr[ADDR_WIDTH-1:0];
assign rd_addr = rd_ptr[ADDR_WIDTH-1:0];
//write
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n)
        for(i=0;i<FIFO_DEPTH;i=i+1) begin
            mem[i] <= 'd0;
        end
    else if(wr_en && !full)
        mem[wr_addr] <= din;
end

//read
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n)
        dout_r <= 'd0;
    else if(rd_en && !empty)
        dout_r <= mem[rd_addr];
end
assign dout = dout_r;

二、完整代码与仿真文件

  异步FIFO的完整代码如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/05/17 20:24:34
// Design Name: 
// Module Name: async_fifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 异步FIFO
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//

module async_fifo
#(
    parameter   FIFO_WIDTH = 8,
    parameter   FIFO_DEPTH = 8,
    parameter   ADDR_WIDTH = $clog2(FIFO_DEPTH)
)
(
    //write clock domain
    input                       wr_clk      ,
    input                       wr_rst_n    ,
    input                       wr_en       ,
    input   [FIFO_WIDTH-1:0]    din         ,
    output                      full        ,
    //read clock domain
    input                       rd_clk      ,
    input                       rd_rst_n    ,
    input                       rd_en       ,
    output  [FIFO_WIDTH-1:0]    dout        ,
    output                      empty        
);

//------------------------------------------------//
integer i;
//memory
reg     [FIFO_WIDTH-1:0]    mem     [FIFO_DEPTH-1:0];
//write address and read address
wire    [ADDR_WIDTH-1:0]    wr_addr;
wire    [ADDR_WIDTH-1:0]    rd_addr;
//write pointer
reg     [ADDR_WIDTH:0]      wr_ptr;
wire    [ADDR_WIDTH:0]      wr_ptr_gray;
reg     [ADDR_WIDTH:0]      wr_ptr_gray_w2r_1;
reg     [ADDR_WIDTH:0]      wr_ptr_gray_w2r_2;
//read pointer
reg     [ADDR_WIDTH:0]      rd_ptr;
wire    [ADDR_WIDTH:0]      rd_ptr_gray;
reg     [ADDR_WIDTH:0]      rd_ptr_gray_r2w_1;
reg     [ADDR_WIDTH:0]      rd_ptr_gray_r2w_2;
//
reg     [FIFO_WIDTH-1:0]    dout_r;

//------------------------------------------------//
//----------------------//
//write pointer
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n)
        wr_ptr <= 'd0;
    else if(wr_en && !full)
        wr_ptr <= wr_ptr + 1'b1;
end

//write pointer(binary to gray)
assign wr_ptr_gray = wr_ptr ^ (wr_ptr>>1);

//gray write pointer syncrous
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n) begin
        wr_ptr_gray_w2r_1 <= 'd0;
        wr_ptr_gray_w2r_2 <= 'd0;
    end
    else begin
        wr_ptr_gray_w2r_1 <= wr_ptr_gray;
        wr_ptr_gray_w2r_2 <= wr_ptr_gray_w2r_1;
    end
end 

//----------------------//
//read pointer
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n)
        rd_ptr <= 'd0;
    else if(rd_en && !empty)
        rd_ptr <= rd_ptr + 1'b1;
end

//read pointer(binary to gray)
assign rd_ptr_gray = rd_ptr ^ (rd_ptr>>1);

//gray read pointer syncrous
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n) begin
        rd_ptr_gray_r2w_1 <= 'd0;
        rd_ptr_gray_r2w_2 <= 'd0;
    end
    else begin
        rd_ptr_gray_r2w_1 <= rd_ptr_gray;
        rd_ptr_gray_r2w_2 <= rd_ptr_gray_r2w_1;
    end
end 

//----------------------//
//write address and read address
assign wr_addr = wr_ptr[ADDR_WIDTH-1:0];
assign rd_addr = rd_ptr[ADDR_WIDTH-1:0];

//full
assign full = (wr_ptr_gray == {~rd_ptr_gray_r2w_2[ADDR_WIDTH:ADDR_WIDTH-1],rd_ptr_gray_r2w_2[ADDR_WIDTH-2:0]}) ? 1'b1 : 1'b0;
//empty
assign empty = (rd_ptr_gray == wr_ptr_gray_w2r_2) ? 1'b1 : 1'b0;

//----------------------//
//write
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n)
        for(i=0;i<FIFO_DEPTH;i=i+1) begin
            mem[i] <= 'd0;
        end
    else if(wr_en && !full)
        mem[wr_addr] <= din;
end

//read
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n)
        dout_r <= 'd0;
    else if(rd_en && !empty)
        dout_r <= mem[rd_addr];
end
assign dout = dout_r;

endmodule

  仿真文件如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/05/17 21:18:22
// Design Name: 
// Module Name: tb_async_fifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module tb_async_fifo();
parameter   FIFO_WIDTH = 8;
parameter   FIFO_DEPTH = 8;
parameter   ADDR_WIDTH = $clog2(FIFO_DEPTH);

reg                        rst_n       ;
reg                        wr_clk      ;
reg                        wr_en       ;
reg    [FIFO_WIDTH-1:0]    din         ;
wire                       full        ;
reg                        rd_clk      ;
reg                        rd_en       ;
wire   [FIFO_WIDTH-1:0]    dout        ;
wire                       empty       ;


initial begin
  rst_n  <= 1'b0;
  wr_clk = 1'b1;
  rd_clk = 1'b1;
  wr_en  <= 1'b0;
  rd_en  <= 1'b0;
  din <= 8'b0;

  # 5 rst_n <= 1'b1;

end

initial begin
  #20 wr_en <= 1'b1;
      rd_en <= 1'b0;
  #40 wr_en <= 1'b0;
      rd_en <= 1'b1;
  #30 wr_en <= 1'b1;
      rd_en <= 1'b0;
  #13 rd_en <= 1'b1;
  #10
  repeat(100)
  begin
      #5 wr_en <= {$random}%2 ;
         rd_en <= {$random}%2 ;
  end

  #100
  $finish;

end

always #1.5 wr_clk = ~wr_clk ;
always #1   rd_clk = ~rd_clk ;
always #3   din <= {$random}%8'hFF;

async_fifo
#(
    .FIFO_WIDTH(FIFO_WIDTH),
    .FIFO_DEPTH(FIFO_DEPTH),
    .ADDR_WIDTH(ADDR_WIDTH)
)
async_fifo
(
    .wr_clk      (wr_clk      ),
    .wr_rst_n    (rst_n       ),
    .wr_en       (wr_en       ),
    .din         (din         ),
    .full        (full        ),
    .rd_clk      (rd_clk      ),
    .rd_rst_n    (rst_n       ),
    .rd_en       (rd_en       ),
    .dout        (dout        ),
    .empty       (empty       )
);

endmodule

三、仿真结果

  根据TestBench文件,对异步FIFO进行仿真。可以看到,在写时钟域wr_clk下,写使能wr_en有效,成功进行数据的写入操作,成功写入8个数据后,写满信号full拉高,在满信号full为高电平期间,即使写使能wr_en有效,也不进行数据的写操作。

手撕代码——异步FIFO_第1张图片

  同时可以看到写地址指针wr_ptr在写时钟域wr_clk下,当写使能wr_en有效时,自加1,同时通过打两拍把写指针地址的格雷码表示wr_ptr_gray同步到读时钟域。

手撕代码——异步FIFO_第2张图片

  可以看到FIFO读数据正确,在读完FIFO中所有数据后,拉高读空信号empty。

手撕代码——异步FIFO_第3张图片
手撕代码——异步FIFO_第4张图片
  综上,异步FIFO设计验证通过。

你可能感兴趣的:(#,手撕代码,fpga开发,异步FIFO,Verilog)