FIFO: First in, First out。代表先进的数据先出 ,后进的数据后出。FIFO按读数据和写数据工作的时钟域是否相同分为同步FIFO和异步FIFO。由于同步FIFO设计较于简单,故本文仅讨论异步FIFO的设计。
一、异步FIFO结构简述
由图可见,异步FIFO的核心部件就是一个 双端口 RAM ;左右两边的长条矩形是地址控制器,负责控制地址自增、将二进制地址转为格雷码以及产生空满信号;SYN模块为同步器,由两个D触发器构成,负责将写地址同步至读时钟域、将读地址同步至写时钟域。
二、双端口RAM设计
module RAM #(
parameter DEPTH=256, //RAM存储深度
parameter WIDTH_A=8, //RAM地址总线宽度
parameter WIDTH_D=16 //RAM数据总线宽度
)
(
input r_clk,
input w_clk,
input rst_n,
input [WIDTH_A-1:0] w_addr, //写地址
input [WIDTH_D-1:0] w_data, //写数据
input w_en, //写使能
input [WIDTH_A-1:0] r_addr, //读地址
output reg [WIDTH_D-1:0] r_data, //读数据
input r_en //读使能
);
reg [WIDTH_D-1:0] mem [DEPTH-1:0]; //16x256存储器
//RAM写数据
always @ (posedge w_clk or negedge rst_n)begin
if (!rst_n)
mem<='{default:0};
else if (w_en)
mem[w_addr]<=w_data;
end
//RAM读数据
always @ (posedge r_clk or negedge rst_n)begin
if (!rst_n)
r_data<=0;
else if (r_en)
r_data<=mem[r_addr];
end
endmodule
三、二进制转格雷码设计
module bin_to_gray #(
parameter WIDTH_D=8
)
(
input [WIDTH_D:0] bin_c, //输入二进制码
output reg [WIDTH_D:0] gray_c, //输出格雷码
input rst_n
);
//二进制转格雷码
always @ (*)begin
if (!rst_n)
gray_c=0;
else
gray_c={bin_c[WIDTH_D],bin_c[WIDTH_D:1]^bin_c[WIDTH_D-1:0]};
//格雷码最高位与二进制码相同,次高位为二进码高位与次高位异或
end
endmodule
四、同步器设计
module syn #(
parameter WIDTH=8
)
(
input syn_clk, //同步时钟
input rst_n,
input [WIDTH:0] data_in, //待同步数据
output reg [WIDTH:0] data_out //同步后数据
);
reg [WIDTH:0] data1;
//打两拍同步
always @(posedge syn_clk or negedge rst_n) begin
if (!rst_n)begin
data_out<=0;
data1<=0;
end
else begin
data1<=data_in;
data_out<=data1;
end
end
endmodule
五、写控制模块设计
module write_part #(
parameter WIDTH_D =8
)
(
input w_clk,
input rst_n,
input w_en,
output w_full,
input [WIDTH_D:0] r_gaddr, //格雷码读地址同步到写时钟域
output reg [WIDTH_D:0] w_addr,
output reg [WIDTH_D:0] w_gaddr
);
//二进制写地址递增
always @ (posedge w_clk or negedge rst_n) begin
if (!rst_n)
w_addr<=0;
else if (w_en && (!w_full))
w_addr<=w_addr+1;
end
//二进制读地址转格雷码
wire [WIDTH_D:0] w_gaddr_w;
bin_to_gray #(
.WIDTH_D(WIDTH_D)
)
bin_to_gray_inst(
.bin_c(w_addr),
.gray_c(w_gaddr_w), //写地址中间格雷码值
.rst_n(rst_n)
);
//跨时钟域之前要求数据从寄存器送出,所以这里要打一拍
always @ (posedge w_clk or negedge rst_n) begin
if (!rst_n)
w_gaddr<=0;
else if (w_en && (!w_full))
w_gaddr<=w_gaddr_w; //写地址输出格雷码值
end
//产生写满信号:
//二进制地址下判断:写地址和读地址最高位相反,其余为都相等,也就是写地址比读地址多跑了一圈
//格雷码地址下判断:写地址和读地址最高两位位相反,其余为都相等,
assign w_full=({~w_gaddr_w[WIDTH_D],~w_gaddr_w[WIDTH_D-1],w_gaddr_w[WIDTH_D-2:0]}==r_gaddr)? 1'b1:1'b0;
endmodule
六、读控制模块设计
module read_part #(
parameter WIDTH_D =8
)
(
input r_clk,
input rst_n,
input r_en,
output r_empty,
input [WIDTH_D:0] w_gaddr, //格雷码写地址同步到读时钟域
output reg [WIDTH_D:0] r_addr,
output reg [WIDTH_D:0] r_gaddr
);
//二进制读地址递增
always @ (posedge r_clk or negedge rst_n) begin
if (!rst_n)
r_addr<=0;
else if (r_en && (!r_empty))
r_addr<=r_addr+1;
end
//二进制读地址转格雷码
wire [WIDTH_D:0] r_gaddr_r;
bin_to_gray #(
.WIDTH_D(WIDTH_D)
)
bin_to_gray_inst(
.bin_c(r_addr),
.gray_c(r_gaddr_r), //读地址中间格雷码值
.rst_n(rst_n)
);
//跨时钟域之前要求数据从寄存器送出,所以这里要打一拍
always @ (posedge r_clk or negedge rst_n) begin
if (!rst_n)
r_gaddr<=0;
else if (r_en && (!r_empty))
r_gaddr<=r_gaddr_r; //写地址输出格雷码值
end
//产生读满信号:读地址等于写地址
assign r_empty=(w_gaddr==r_gaddr)? 1'b1:1'b0;
endmodule
七、整体代码
module asyn_fifo #(
parameter DEPTH=256, //FIFO存储深度
parameter WIDTH_A=8, //RAM地址总线宽度
parameter WIDTH_D=16 //FIFO数据总线宽度
)
(
input w_clk, // 写时钟
input w_en, // 写使能
input rst_n, // 复位信号
input r_clk, // 读时钟
input r_en, // 写使能
output w_full, // 写满标志
output r_empty, // 读空标志
input [WIDTH_D-1:0] w_data, // 写数据
output[WIDTH_D-1:0] r_data // 读数据
);
wire [WIDTH_A:0] w_addr; // 二进制写地址
wire [WIDTH_A:0] r_gaddr_syn; // 同步到写时钟域的格雷码读地址
wire [WIDTH_A:0] w_gaddr; // 格雷码写地址
wire [WIDTH_A:0] r_addr; // 二进制读地址
wire [WIDTH_A:0] w_gaddr_syn; // 同步到读时钟域的格雷码写地址
wire [WIDTH_A:0] r_gaddr; // 格雷码读地址
// 写控制模块例化
write_part #(
.WIDTH_D(WIDTH_A)
)
write_part_isnt(
.w_clk(w_clk),
.rst_n(rst_n),
.w_en(w_en),
.r_gaddr(r_gaddr_syn),
.w_full(w_full),
.w_addr(w_addr),
.w_gaddr(w_gaddr)
);
// 双端口RAM模块例化
RAM #(
.DEPTH(DEPTH),
.WIDTH_A(WIDTH_A),
.WIDTH_D(WIDTH_D)
)
inst (
.r_clk(r_clk),
.w_clk(w_clk),
.rst_n(rst_n),
.w_addr(w_addr[WIDTH_A-1:0]),
.w_data(w_data),
.w_en(w_en&(!w_full)),
.r_addr(r_addr[WIDTH_A-1:0]),
.r_data(r_data),
.r_en(r_en&(!r_empty))
);
// 写地址同步到读时钟域
syn #(
.WIDTH(WIDTH_A)
)
syn_w_2_r
(
.syn_clk(r_clk), // 输入时钟为读时钟
.rst_n(rst_n),
.data_in(w_gaddr),
.data_out(w_gaddr_syn)
);
// 读地址同步到写时钟域
syn #(
.WIDTH(WIDTH_A)
)
syn_r_2_w
(
.syn_clk(w_clk), // 输入时钟为写时钟
.rst_n(rst_n),
.data_in(r_gaddr),
.data_out(r_gaddr_syn)
);
// 读控制模块例化
read_part #(
.WIDTH_D(WIDTH_A)
)
read_part_isnt(
.r_clk(r_clk),
.rst_n(rst_n),
.r_en(r_en),
.w_gaddr(w_gaddr_syn),
.r_empty(r_empty),
.r_addr(r_addr),
.r_gaddr(r_gaddr)
);
endmodule
八、测试代码及仿真波形
module asyn_fifo_tb;
logic w_clk;
logic w_en;
logic w_full;
logic [15:0] w_data;
logic r_clk;
logic r_en;
logic r_empty;
logic [15:0] r_data;
logic rst_n;
asyn_fifo #(
.DEPTH(256),
.WIDTH_D(16),
.WIDTH_A(8)
)
inst (
.w_clk(w_clk),
.w_en(w_en),
.rst_n(rst_n),
.r_clk(r_clk),
.r_en(r_en),
.w_full(w_full),
.r_empty(r_empty),
.w_data(w_data),
.r_data(r_data)
);
always #2 w_clk=~w_clk;
always #8 r_clk=~r_clk;
initial begin
w_clk=0;
r_clk=0;
rst_n=0;
w_en=0;
r_en=0;
w_data=0;
#30;
rst_n=1;
#20;
w_en=1;
r_en=1;
repeat (512) begin
@(posedge w_clk)
if (!w_full)
w_data=w_data+1'b1;
end
w_en=0;
repeat (512)
@(posedge r_clk);
$finish;
end
//产生波形文件
initial begin
$dumpfile("test.fsdb");
$dumpvars(0,asyn_fifo_tb);
end
endmodule