从生成的文件头来看,主要实现了下面的几点功能:
首先,来看看模块参数,默认数据位宽8、fifo深度16(地址线位宽为4)。读写分成两组独立的接口,一组是写接口,包括写时钟、写复位、写使能、写数据和满标志(未满才能写)。读的接口类似,分别为读时钟、读复位、读使能、读数据和空标志(非空才能读)。
module async_fifo #(
parameter DATA_WIDTH = 8,
parameter FIFO_DEPTH = 16,
parameter ADDR_WIDTH = 4
)(
input wire wr_clk,
input wire wr_rst_n,
input wire wr_en,
input wire [DATA_WIDTH-1:0] wr_data,
output wire full,
input wire rd_clk,
input wire rd_rst_n,
input wire rd_en,
output wire [DATA_WIDTH-1:0] rd_data,
output wire empty
);
然后,定义一些内部信号。
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;
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
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
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