//==============================================================================
// 异步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);
endendmodule