async fifo
专栏:ExASIC Dec. 17, 2025, 8:29 a.m. 15 阅读
fifo

//==============================================================================
// 异步FIFO (Asynchronous FIFO)
// 功能:在不同时钟域之间安全传递数据
// 特性:
// - 双时钟域设计(写时钟和读时钟独立)
// - 格雷码指针减少亚稳态
// - 空满状态检测
// - 参数化配置
//==============================================================================

module async_fifo #(
parameter DATA_WIDTH = 8, // 数据位宽
parameter FIFO_DEPTH = 16, // FIFO深度(必须是2的幂次方)
parameter ADDR_WIDTH = 4 // 地址位宽(log2(FIFO_DEPTH))
)(
// 写时钟域
input wire wr_clk, // 写时钟
input wire wr_rst_n, // 写时钟域复位(低电平有效)
input wire wr_en, // 写使能
input wire [DATA_WIDTH-1:0] wr_data, // 写数据
output wire full, // FIFO满标志

// 读时钟域
input  wire                 rd_clk,     // 读时钟
input  wire                 rd_rst_n,   // 读时钟域复位(低电平有效)
input  wire                 rd_en,      // 读使能
output wire [DATA_WIDTH-1:0] rd_data,  // 读数据
output wire                  empty       // FIFO空标志

);

// 内部信号声明
reg [ADDR_WIDTH:0] wr_ptr_bin;     // 写指针(二进制)
reg [ADDR_WIDTH:0] wr_ptr_gray;    // 写指针(格雷码)
reg [ADDR_WIDTH:0] wr_ptr_gray_next;

reg [ADDR_WIDTH:0] rd_ptr_bin;     // 读指针(二进制)
reg [ADDR_WIDTH:0] rd_ptr_gray;    // 读指针(格雷码)
reg [ADDR_WIDTH:0] rd_ptr_gray_next;

wire [ADDR_WIDTH:0] wr_ptr_gray_sync1; // 写指针格雷码同步到读时钟域(第一级)
wire [ADDR_WIDTH:0] wr_ptr_gray_sync2; // 写指针格雷码同步到读时钟域(第二级)
wire [ADDR_WIDTH:0] rd_ptr_gray_sync1; // 读指针格雷码同步到写时钟域(第一级)
wire [ADDR_WIDTH:0] rd_ptr_gray_sync2; // 读指针格雷码同步到写时钟域(第二级)

wire full_int;    // 内部满标志
wire empty_int;   // 内部空标志

// FIFO存储器
reg [DATA_WIDTH-1:0] mem [0:FIFO_DEPTH-1];
reg [DATA_WIDTH-1:0] rd_data_reg;

//==========================================================
// 二进制转格雷码
//==========================================================
function [ADDR_WIDTH:0] bin_to_gray;
    input [ADDR_WIDTH:0] binary;
    begin
        bin_to_gray = binary ^ (binary >> 1);
    end
endfunction

//==========================================================
// 格雷码转二进制
//==========================================================
function [ADDR_WIDTH:0] gray_to_bin;
    input [ADDR_WIDTH:0] gray;
    integer i;
    begin
        gray_to_bin[ADDR_WIDTH] = gray[ADDR_WIDTH];
        for(i = ADDR_WIDTH-1; i >= 0; i = i-1) begin
            gray_to_bin[i] = gray_to_bin[i+1] ^ gray[i];
        end
    end
endfunction

//==========================================================
// 写时钟域逻辑
//==========================================================

// 写指针逻辑
always @(posedge wr_clk or negedge wr_rst_n) begin
    if (!wr_rst_n) begin
        wr_ptr_bin <= {ADDR_WIDTH+1{1'b0}};
        wr_ptr_gray <= {ADDR_WIDTH+1{1'b0}};
    end else if (wr_en && !full_int) begin
        wr_ptr_bin <= wr_ptr_bin + 1'b1;
        wr_ptr_gray <= bin_to_gray(wr_ptr_bin + 1'b1);
    end
end

// FIFO写操作
always @(posedge wr_clk) begin
    if (wr_en && !full_int) begin
        mem[wr_ptr_bin[ADDR_WIDTH-1:0]] <= wr_data;
    end
end

// 同步读指针到写时钟域
always @(posedge wr_clk or negedge wr_rst_n) begin
    if (!wr_rst_n) begin
        rd_ptr_gray_sync1 <= {ADDR_WIDTH+1{1'b0}};
        rd_ptr_gray_sync2 <= {ADDR_WIDTH+1{1'b0}};
    end else begin
        rd_ptr_gray_sync1 <= rd_ptr_gray;
        rd_ptr_gray_sync2 <= rd_ptr_gray_sync1;
    end
end

//==========================================================
// 读时钟域逻辑
//==========================================================

// 读指针逻辑
always @(posedge rd_clk or negedge rd_rst_n) begin
    if (!rd_rst_n) begin
        rd_ptr_bin <= {ADDR_WIDTH+1{1'b0}};
        rd_ptr_gray <= {ADDR_WIDTH+1{1'b0}};
    end else if (rd_en && !empty_int) begin
        rd_ptr_bin <= rd_ptr_bin + 1'b1;
        rd_ptr_gray <= bin_to_gray(rd_ptr_bin + 1'b1);
    end
end

// FIFO读操作
always @(posedge rd_clk) begin
    if (rd_en && !empty_int) begin
        rd_data_reg <= mem[rd_ptr_bin[ADDR_WIDTH-1:0]];
    end
end

// 同步写指针到读时钟域
always @(posedge rd_clk or negedge rd_rst_n) begin
    if (!rd_rst_n) begin
        wr_ptr_gray_sync1 <= {ADDR_WIDTH+1{1'b0}};
        wr_ptr_gray_sync2 <= {ADDR_WIDTH+1{1'b0}};
    end else begin
        wr_ptr_gray_sync1 <= wr_ptr_gray;
        wr_ptr_gray_sync2 <= wr_ptr_gray_sync1;
    end
end

//==========================================================
// 空满状态判断
//==========================================================

// 满状态判断(写指针和同步来的读指针的最高位和次高位相反,其余位相同)
assign full_int = (wr_ptr_gray == {~rd_ptr_gray_sync2[ADDR_WIDTH:ADDR_WIDTH-1], 
                                   rd_ptr_gray_sync2[ADDR_WIDTH-2:0]});

// 空状态判断(读指针和同步来的写指针相同)
assign empty_int = (rd_ptr_gray == wr_ptr_gray_sync2);

//==========================================================
// 输出信号
//==========================================================
assign full = full_int;
assign empty = empty_int;
assign rd_data = rd_data_reg;

endmodule

//==============================================================================
// 测试模块 (Testbench)
//==============================================================================
`timescale 1ns/1ps

module async_fifo_tb;

// 参数定义
parameter DATA_WIDTH = 8;
parameter FIFO_DEPTH = 16;
parameter ADDR_WIDTH = 4;

// 输入信号
reg                 wr_clk;
reg                 wr_rst_n;
reg                 wr_en;
reg  [DATA_WIDTH-1:0] wr_data;

reg                 rd_clk;
reg                 rd_rst_n;
reg                 rd_en;

// 输出信号
wire                full;
wire                empty;
wire [DATA_WIDTH-1:0] rd_data;

// 实例化DUT
async_fifo #(
    .DATA_WIDTH(DATA_WIDTH),
    .FIFO_DEPTH(FIFO_DEPTH),
    .ADDR_WIDTH(ADDR_WIDTH)
) u_async_fifo (
    .wr_clk(wr_clk),
    .wr_rst_n(wr_rst_n),
    .wr_en(wr_en),
    .wr_data(wr_data),
    .full(full),
    
    .rd_clk(rd_clk),
    .rd_rst_n(rd_rst_n),
    .rd_en(rd_en),
    .rd_data(rd_data),
    .empty(empty)
);

// 时钟生成
initial begin
    wr_clk = 0;
    forever #5 wr_clk = ~wr_clk;  // 100MHz写时钟
end

initial begin
    rd_clk = 0;
    forever #7 rd_clk = ~rd_clk;  // 71.4MHz读时钟
end

// 测试激励
initial begin
    // 初始化
    wr_rst_n = 0;
    rd_rst_n = 0;
    wr_en = 0;
    rd_en = 0;
    wr_data = 0;
    
    #100;
    
    // 释放复位
    wr_rst_n = 1;
    rd_rst_n = 1;
    #50;
    
    // 写数据测试
    $display("开始写入数据...");
    for (integer i = 0; i < 20; i = i + 1) begin
        @(negedge wr_clk);
        if (!full) begin
            wr_en = 1;
            wr_data = i % 256;
            $display("时间=%0t: 写入数据=%0d", $time, wr_data);
        end else begin
            wr_en = 0;
            $display("时间=%0t: FIFO满,无法写入", $time);
        end
    end
    wr_en = 0;
    #100;
    
    // 读数据测试
    $display("开始读取数据...");
    for (integer i = 0; i < 20; i = i + 1) begin
        @(negedge rd_clk);
        if (!empty) begin
            rd_en = 1;
            #2;
            $display("时间=%0t: 读取数据=%0d", $time, rd_data);
        end else begin
            rd_en = 0;
            $display("时间=%0t: FIFO空,无法读取", $time);
        end
    end
    rd_en = 0;
    #100;
    
    // 同时读写测试
    $display("开始同时读写测试...");
    fork
        begin
            for (integer i = 32; i < 48; i = i + 1) begin
                @(negedge wr_clk);
                if (!full) begin
                    wr_en = 1;
                    wr_data = i % 256;
                    $display("时间=%0t: 写入数据=%0d", $time, wr_data);
                end else begin
                    wr_en = 0;
                end
            end
            wr_en = 0;
        end
        
        begin
            #50;  // 延迟一段时间再开始读
            for (integer i = 0; i < 16; i = i + 1) begin
                @(negedge rd_clk);
                if (!empty) begin
                    rd_en = 1;
                    #2;
                    $display("时间=%0t: 读取数据=%0d", $time, rd_data);
                end else begin
                    rd_en = 0;
                end
            end
            rd_en = 0;
        end
    join
    
    #200;
    $display("测试完成");
    $finish;
end

// 监控FIFO状态
always @(posedge wr_clk or posedge rd_clk) begin
    if (full)
        $display("时间=%0t: FIFO满", $time);
    if (empty)
        $display("时间=%0t: FIFO空", $time);
end

endmodule

感谢阅读,更多文章点击这里:【专栏:ExASIC】
最新20篇 开设专栏