3. 同步FIFO计数器设计
任务目标
使用计数器方式实现同步FIFO的满/空判断逻辑,并与指针差值判断法进行对比验证。计数器法通过记录当前有效数据个数,在写入时加1、读出时减1,从而直接获得空满状态。
设计原理
同步FIFO中,读写指针均随时钟更新。计数器法额外维护一个count寄存器:
- 写使能且未满时,
count <= count + 1; - 读使能且未空时,
count <= count - 1; - 同时读写时,
count保持不变。
空标志为count == 0,满标志为count == DEPTH。指针法则通过比较读写指针是否相等判断空,通过写指针反推一位后与读指针比较判断满。两种方法在功能上等价,但计数器法无需额外指针位宽扩展。
Verilog实现
module sync_fifo_counter #(
parameter DATA_WIDTH = 8,
parameter DEPTH = 16,
parameter ADDR_WIDTH = 4 // log2(DEPTH)
)(
input wire clk,
input wire rst_n,
input wire wr_en,
input wire rd_en,
input wire [DATA_WIDTH-1:0] din,
output reg [DATA_WIDTH-1:0] dout,
output wire full,
output wire empty,
output wire [ADDR_WIDTH:0] count // 计数器宽度比地址宽1位
);
reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
reg [ADDR_WIDTH-1:0] wr_ptr;
reg [ADDR_WIDTH-1:0] rd_ptr;
reg [ADDR_WIDTH:0] count_reg;
assign count = count_reg;
assign full = (count_reg == DEPTH);
assign empty = (count_reg == 0);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= 0;
rd_ptr <= 0;
count_reg <= 0;
end else begin
// 写操作
if (wr_en && !full) begin
mem[wr_ptr] <= din;
wr_ptr <= wr_ptr + 1'b1;
end
// 读操作
if (rd_en && !empty) begin
dout <= mem[rd_ptr];
rd_ptr <= rd_ptr + 1'b1;
end
// 计数器更新
case ({wr_en && !full, rd_en && !empty})
2'b10: count_reg <= count_reg + 1'b1; // 仅写
2'b01: count_reg <= count_reg - 1'b1; // 仅读
default: count_reg <= count_reg; // 同时读写或均无效
endcase
end
end
endmodule
对比验证testbench
module tb_sync_fifo;
parameter DEPTH = 16;
reg clk = 0, rst_n = 0;
reg wr_en = 0, rd_en = 0;
reg [7:0] din = 0;
wire [7:0] dout;
wire full, empty;
wire [4:0] count;
// 指针法参考模型
reg [3:0] ref_wr_ptr = 0, ref_rd_ptr = 0;
reg [4:0] ref_count = 0;
wire ref_full = (ref_wr_ptr == ref_rd_ptr) && (ref_count == DEPTH);
wire ref_empty = (ref_wr_ptr == ref_rd_ptr) && (ref_count == 0);
sync_fifo_counter #(.DATA_WIDTH(8), .DEPTH(DEPTH)) uut (
.clk(clk), .rst_n(rst_n), .wr_en(wr_en), .rd_en(rd_en),
.din(din), .dout(dout), .full(full), .empty(empty), .count(count)
);
always #5 clk = ~clk;
// 参考模型:用指针差值法计算空满
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ref_wr_ptr <= 0; ref_rd_ptr <= 0; ref_count <= 0;
end else begin
if (wr_en && !ref_full) begin
ref_wr_ptr <= ref_wr_ptr + 1;
ref_count <= ref_count + 1;
end
if (rd_en && !ref_empty) begin
ref_rd_ptr <= ref_rd_ptr + 1;
ref_count <= ref_count - 1;
end
end
end
// 对比断言
always @(posedge clk) begin
if (rst_n) begin
if (full !== ref_full) $display("FAIL: full mismatch at t=%0t", $time);
if (empty !== ref_empty) $display("FAIL: empty mismatch at t=%0t", $time);
if (count !== ref_count) $display("FAIL: count mismatch at t=%0t", $time);
end
end
integer i;
initial begin
// 复位
#20 rst_n = 1;
// 写入8个数据
for (i = 0; i < 8; i = i + 1) begin
@(posedge clk);
wr_en = 1; din = i;
end
// 连续读写4次(同时读写)
for (i = 0; i < 4; i = i + 1) begin
@(posedge clk);
wr_en = 1; rd_en = 1; din = i + 100;
end
// 只读8次,清空FIFO
for (i = 0; i < 8; i = i + 1) begin
@(posedge clk);
rd_en = 1; wr_en = 0;
end
// 写满测试
for (i = 0; i < 20; i = i + 1) begin
@(posedge clk);
wr_en = 1; rd_en = 0; din = i;
end
#20;
$display("Test completed. Check FAIL messages above.");
$finish;
end
initial begin
$monitor("t=%0t count=%0d full=%b empty=%b ref_full=%b ref_empty=%b",
$time, count, full, empty, ref_full, ref_empty);
end
endmodule
运行与验证
- 仿真运行:使用Vivado/ModelSim/Verilator等工具编译两个文件并运行testbench。
- 通过标准:仿真过程中无任何
FAIL消息输出,且count值始终与参考模型一致。 - 预期行为:写入8个数据后
count=8;同时读写时count不变;写满16个后full=1,继续写无效;读空后empty=1,继续读无效。
关键点说明
- 计数器宽度必须为
ADDR_WIDTH+1,否则无法表示满状态DEPTH。 - 同时读写时计数器不变,但指针各自递增,这是与指针法行为一致的关键。
- 指针法需要额外1位宽度区分满/空,计数器法则直接比较计数值,逻辑更直观。