一、异步FIFO结构图
具体代码直接按照结构图来写,分为读控制模块,写控制模块,同步模块,及顶层模块。
二、各模块代码
1、写控制模块
module wr_ctrl#(
parameter DEPTH = 16,
parameter ADDR_WIDTH = $clog2(DEPTH)
)(
input wire wr_clk,
input wire wr_rst_n,
input wire [ADDR_WIDTH : 0] rd_addr_gray,
input wire wr_en,
output wire [ADDR_WIDTH : 0] wr_addr_p_gray,
output wire [ADDR_WIDTH - 1 : 0] wr_addr,
output wire wr_full
);
reg [ADDR_WIDTH : 0] wr_addr_p;
reg [ADDR_WIDTH : 0] rd_addr_p;
always@(posedge wr_clk or negedge wr_rst_n)begin
if(!wr_rst_n)
wr_addr_p <= {ADDR_WIDTH+1{1'b0}};
else if(wr_en && (~wr_full))
wr_addr_p <= wr_addr_p + 1'b1;
end
assign wr_addr = wr_addr_p[ADDR_WIDTH - 1 : 0];
assign wr_addr_p_gray = {wr_addr_p[ADDR_WIDTH],wr_addr_p[ADDR_WIDTH:1] ^ wr_addr_p[ADDR_WIDTH-1:0]};
integer i ;
always@(*)begin
rd_addr_p[ADDR_WIDTH] = rd_addr_gray[ADDR_WIDTH];
for(i = ADDR_WIDTH - 1 ; i >= 0 ; i = i - 1)
rd_addr_p[i] = rd_addr_p[i+1] ^ rd_addr_gray[i];
end
assign wr_full = (wr_addr_p[ADDR_WIDTH] != rd_addr_p[ADDR_WIDTH]) && (wr_addr_p[ADDR_WIDTH-1:0] == rd_addr_p[ADDR_WIDTH-1:0]);
endmodule
写控制模块作用包括:产生写地址指针,写地址,将读时钟域的格雷码转换,产生写满标志位。
这里我格雷码转二进制用了for循环,因为考虑到模块复用的情况,地址宽度是变化的,我没有想到更好的方法。下面读控制同理。(有好的方法欢迎提出)
2、读控制模块
module rd_ctrl#(
parameter DEPTH = 16,
parameter ADDR_WIDTH = $clog2(DEPTH)
)(
input wire rd_clk,
input wire rd_rst_n,
input wire rd_en,
input wire [ADDR_WIDTH:0] wr_addr_gray,
output wire rd_empty,
output wire [ADDR_WIDTH-1:0] rd_addr,
output wire [ADDR_WIDTH:0] rd_addr_p_gray
);
reg [ADDR_WIDTH:0] rd_addr_p;
reg [ADDR_WIDTH:0] wr_addr_p;
always@(posedge rd_clk or negedge rd_rst_n)begin
if(!rd_rst_n)
rd_addr_p <= {ADDR_WIDTH+1{1'b0}};
else if(rd_en && (~rd_empty))
rd_addr_p <= rd_addr_p + 1'b1;
end
assign rd_addr = rd_addr_p[ADDR_WIDTH-1:0];
assign rd_addr_p_gray = {rd_addr_p[ADDR_WIDTH],rd_addr_p[ADDR_WIDTH:1] ^ rd_addr_p[ADDR_WIDTH-1:0]};
integer j ;
always@(*)begin
wr_addr_p[ADDR_WIDTH] = wr_addr_gray[ADDR_WIDTH];
for(j = ADDR_WIDTH - 1 ; j >= 0 ; j = j - 1)
wr_addr_p[j] = wr_addr_p[j+1] ^ wr_addr_gray[j];
end
assign rd_empty = wr_addr_p == rd_addr_p;
endmodule
读控制模块作用包括:产生读地址指针,读地址,将写时钟域的格雷码转换,产生读空标志位。
3、同步模块
module sync2#(
parameter DEPTH = 16,
parameter ADDR_WIDTH = $clog2(DEPTH)
)(
input wire clk,
input wire rst_n,
input wire [ADDR_WIDTH : 0] addr_in,
output wire [ADDR_WIDTH : 0] addr_out
);
reg [ADDR_WIDTH : 0] addr_d1;
reg [ADDR_WIDTH : 0] addr_d2;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
addr_d1 <= 0;
addr_d2 <= 0;
end
else begin
addr_d1 <= addr_in;
addr_d2 <= addr_d1;
end
end
assign addr_out = addr_d2;
endmodule
同步模块作用:格雷码传输,相邻格雷码间只有1位变化,使用两级同步器。
4、顶层模块
module async_fifo
#(
parameter DEPTH = 16,
parameter WIDTH = 8
)(
input wire wr_clk,
input wire wr_req,
input wire wr_rst_n,
input wire [WIDTH - 1:0] data_in,
output wire wr_full,
input wire rd_clk,
input wire rd_req,
input wire rd_rst_n,
output wire rd_empty,
output reg [WIDTH - 1:0] data_out
);
parameter ADDR_WIDTH = $clog2(DEPTH);
reg [WIDTH - 1 : 0] regs_array [DEPTH - 1 : 0];
wire [ADDR_WIDTH : 0] wptr,rptr;
wire [ADDR_WIDTH : 0] wptr_d2,rptr_d2;
wire [ADDR_WIDTH - 1 : 0] wr_addr,rd_addr;
always@(posedge wr_clk)begin
if(wr_req && (!wr_full))
regs_array[wr_addr] <= data_in;
end
always@(posedge rd_clk)begin
if(rd_req && (!rd_empty))
data_out <= regs_array[rd_addr];
end
wr_ctrl #(
.DEPTH (16)
)wr_ctrl_inst(
.wr_clk (wr_clk),
.wr_rst_n (wr_rst_n),
.wr_en (wr_req),
.rd_addr_gray (rptr_d2),
.wr_addr (wr_addr),
.wr_addr_p_gray (wptr),
.wr_full (wr_full)
);
rd_ctrl #(
.DEPTH (16)
)rd_ctrl_inst(
.rd_clk (rd_clk),
.rd_rst_n (rd_rst_n),
.rd_en (rd_req),
.wr_addr_gray (wptr_d2),
.rd_addr (rd_addr),
.rd_addr_p_gray (rptr),
.rd_empty (rd_empty)
);
sync2 #(
.DEPTH (16)
)sync2_inst1(
.clk (wr_clk),
.rst_n (wr_rst_n),
.addr_in (rptr),
.addr_out (rptr_d2)
);
sync2#(
.DEPTH (16)
)sync2_inst2(
.clk (rd_clk),
.rst_n (rd_rst_n),
.addr_in (wptr),
.addr_out (wptr_d2)
);
endmodule
顶层模块作用包括:例外各个子模块,定义存储器变量(图中双口ram),读写数据控制。
三、testbench
`timescale 1ns/1ns;
module async_fifo_tb();
parameter WIDTH = 8;
reg wr_clk;
reg wr_req;
reg wr_rst_n;
reg [WIDTH - 1:0] data_in;
reg rd_clk;
reg rd_req;
reg rd_rst_n;
wire wr_full;
wire rd_empty;
wire [WIDTH - 1:0] data_out;
reg init_done;
always #2 wr_clk = ~wr_clk;
always #4 rd_clk = ~rd_clk;
initial begin
wr_rst_n = 0;
rd_rst_n = 0;
wr_clk = 0;
rd_clk = 0;
wr_req = 0;
rd_req = 0;
data_in = 0;
init_done = 0;
#25;
wr_rst_n = 1;
rd_rst_n = 1;
init_done = 1;
#10000
$finish;
end
always@(*)begin
if(init_done)begin
if(wr_full)
wr_req = 0;
else
wr_req = 1;
end
end
always@(*)begin
if(init_done)begin
if(rd_empty)
rd_req = 0;
else
rd_req = 1;
end
end
always@(posedge wr_clk)begin
if(init_done)begin
if(!wr_full)
data_in <= data_in + 1'b1;
else
data_in <= data_in;
end
else
data_in <= 0;
end
async_fifo #(
.DEPTH (16),
.WIDTH (8)
)async_fifo_inst(
.wr_clk (wr_clk),
.wr_req (wr_req),
.wr_rst_n (wr_rst_n),
.data_in (data_in),
.wr_full (wr_full),
.rd_clk (rd_clk),
.rd_req (rd_req),
.rd_rst_n (rd_rst_n),
.rd_empty (rd_empty),
.data_out (data_out)
);
initial begin
$fsdbDumpfile("tb.fsdb");
$fsdbDumpvars;
end
endmodule
testbench作用为:给激励,产生读写数据,控制读写请求,例化顶层模块。
四、仿真结果
仿真了边读边写的情况,可以看到写满拉高,读写时钟域分开,方便观察中间信号。