从生成的文件头来看,主要实现了下面的几点功能:
//==============================================================================
// 异步FIFO (Asynchronous FIFO)
// 功能:在不同时钟域之间安全传递数据
// 特性:
// - 双时钟域设计(写时钟和读时钟独立)
// - 格雷码指针减少亚稳态
// - 空满状态检测
// - 参数化配置
//==============================================================================
首先,来看看模块参数,默认数据位宽8、fifo深度16(地址线位宽为4)。读写分成两组独立的接口,一组是写接口,包括写时钟、写复位、写使能、写数据和满标志(未满才能写)。读的接口类似,分别为读时钟、读复位、读使能、读数据和空标志(非空才能读)。
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方便调用。
//==========================================================
// 二进制转格雷码
//==========================================================
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
接下来是写时钟域的写逻辑。二进制的写指针每写一个数据就加1,直到fifo满了。根据二进制写指针,把写的数据存到mem数组里。格雷码的写指针只是二进制写指针的格式转换。同时,把格雷码的读指针(读时钟域)用写时钟打两拍,同步到写时钟域。
//==========================================================
// 写时钟域逻辑
//==========================================================
// 写指针逻辑
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
然后,是读时钟域的读逻辑。非空时,把二进制的读指针指向的数据读到rd_data_reg里。同时二进制读指针加1。格雷码写指针也会有读时钟打两拍,作空标志判断用。
//==========================================================
// 读时钟域逻辑
//==========================================================
// 读指针逻辑
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
分享数字集成电路设计中的经验和方法。分享让工作更轻松。
