基于vivado设计一个参数化的同步FIFO_vivado设计同步fifo流程
-----大三生初步接触 仅供参考-----
一、题目描述:
设计一个同步FIFO模块。该FIFO应具有可配置的数据宽度(DATA_WIDTH)和深度(DEPTH)。FIFO的操作应严格遵循时钟信号,并提供“满”(full)和“空”(empty)状态指示信号。
设计要求:
- 参数化:
- 使用 parameter 定义数据宽度 DATA_WIDTH (默认值 8)。
- 使用 parameter 定义FIFO深度 DEPTH (默认值 16)。假设 DEPTH 总是2的幂次方,以便简化地址指针逻辑。
- 接口(Ports):
端口名
方向
位宽
描述
clk
input
1
时钟信号
rst_n
input
1
异步复位信号,低电平有效
wr_en
input
1
写使能信号
w_data
input
DATA_WIDTH
写入FIFO的数据
rd_en
input
1
读使能信号
r_data
output
DATA_WIDTH
从FIFO读出的数据
full
output
1
FIFO满状态标志 (1 = 满, 0 = 未满)
empty
output
1
FIFO空状态标志 (1 = 空, 0 = 非空)
3.编程语言:基于verilog-2001和verilog-95,有适当的注释。
4.Testbench Verilog代码 (fifo_tb.v):编写一个测试平台来验证你的FIFO设计。要求交付测试计划,测试计划中包含要测试哪些功能和收集哪些覆盖率;按照测试计划完成测试;输出测试结果文档(说明针对计划的结果)。
5.基于Xilinx的Vivado进行设计和验证,任选FPGA型号。
二、设计思路:
1.确定模块端口
根据题目要求,我先确定了模块的输入输出端口:
(1)时钟(clk)和复位(rst_n)
(2)读写控制:写使能(wr_en)和写数据(w_data),读控制:读使能(rd_en)和读数据(r_data)
(3)状态指示:空(empty)和满(full)信号
2. 内部结构设计
存储部分:使用二维寄存器数组mem
来存储数据,宽度为DATA_WIDTH,深度为DEPTH(8位宽16深,就是16个8位寄存器)
读写指针:读写指针需要能循环遍历整个存储空间
计数器:用一个简单的计数器来跟踪FIFO中的数据量进行空满判断(count==0为空,count==DEPTH为满)
4. 关键逻辑实现
(1)写操作
在时钟上升沿,如果写使能且不满,就把数据写入当前写指针位置
写完后指针加1,自动回绕(因为深度是2的幂次方)
(2)读操作
在时钟上升沿,如果读使能且不空,就从当前读指针位置读出数据
读完后指针加1,自动回绕
(3)空满判断
空:计数器为0
满:计数器等于DEPTH
同时读写时计数器不变(一进一出)
用组合逻辑assign实现,实时反映状态
4.测试文件构思
(1)写满测试:连续写入直到FIFO满
(2)读空测试:连续读出直到FIFO空
(3)并发读写:同时进行读写操作
(4)随机测试:随机读写组合
三、程序源码
1.程序源码
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// 同步FIFO模块 - 参数化设计
// 文件名:fifo420.v
// 说明:支持可配置数据宽度和深度,深度需为2的幂次方
module fifo420 #(
parameter DATA_WIDTH = 8,//参数设置
parameter DEPTH = 16
)(
//input总是wire型
//output中wire:用于连接模块或由assign驱动的信号。reg:用于在always或initial块中被赋值的信号。
input wire clk, // 全局时钟
input wire rst_n, // 复位信号
input wire wr_en, // 写使能
input wire [DATA_WIDTH-1:0] w_data, // 写入数据
input wire rd_en, // 读使能
output reg [DATA_WIDTH-1:0] r_data, // 读出数据
output wire full, // FIFO满标志
output wire empty // FIFO空标志
);
localparam ADDR_WIDTH = $clog2(DEPTH); // 地址位宽
reg [ADDR_WIDTH:0] wr_ptr, rd_ptr; // 读写指针(多1位用于满标志判断)
reg [DATA_WIDTH-1:0] mem [0:DEPTH-1]; // 存储器
//DATA_WIDTH是每个数据的位数(比如8位),DEPTH是FIFO能存储的数据个数(比如16个)
//所以这是一个宽度为DATA_WIDTH,深度为DEPTH的存储阵列
// FIFO计数器
reg [$clog2(DEPTH):0] count;
// 空满标志逻辑
assign empty = (count == 0);
assign full = (count == DEPTH);
//用 assign?empty 和 full 是组合逻辑信号:它们的值直接由当前 count 的值决定,不需要时钟触发。
// 写入逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= 0;
end else if (wr_en && !full) begin
mem[wr_ptr[$clog2(DEPTH)-1:0]] <= w_data;
wr_ptr <= wr_ptr + 1;
end
end
// 读出逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_ptr <= 0;
r_data <= 0;
end else if (rd_en && !empty) begin
r_data <= mem[rd_ptr[$clog2(DEPTH)-1:0]];
rd_ptr <= rd_ptr + 1;
end else begin
r_data <= r_data; // 保持r_data不变(实际上默认就是这样)
end
end
// 计数器逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
count <= 0;
end else begin
case ({wr_en, rd_en})
2\'b01: if (!empty) count <= count - 1; // 只读
2\'b10: if (!full) count <= count + 1; // 只写
2\'b11: if(empty)count <= 1; // 同时读写,计数不变
default: count <= count;
endcase
end
end
endmodule
2.tb文件源码
`timescale 1ns / 1ps
module tb_fifo420;
// 参数定义
parameter DATA_WIDTH = 8;
parameter DEPTH = 16;
// 信号定义
reg clk;
reg rst_n;
reg wr_en;
reg rd_en;
reg [DATA_WIDTH-1:0] w_data;
wire [DATA_WIDTH-1:0] r_data;
wire full;
wire empty;
// 实例化 DUT(Device Under Test)
fifo420 #(
.DATA_WIDTH(DATA_WIDTH),
.DEPTH(DEPTH)
) uut (
.clk(clk),
.rst_n(rst_n),
.wr_en(wr_en),
.w_data(w_data),
.rd_en(rd_en),
.r_data(r_data),
.full(full),
.empty(empty)
);
// 生成时钟,10ns周期(100MHz)
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 复位过程
task reset_fifo;
begin
rst_n = 0;
wr_en = 0;
rd_en = 0;
w_data = 0;
#20;
rst_n = 1;
#20;
end
endtask
// 写满测试
task write_full_test;
begin
$display(\"=== 写满测试开始 ===\");
wr_en = 1;
rd_en = 0;
for (integer i = 0; i < DEPTH + 5; i = i + 1)
begin
w_data = i;
@(posedge clk); // 关键修复:等待时钟边沿!
$display(\"写入数据:%h\", w_data);
if (full) $display(\"FIFO 已满!\");
end
wr_en = 0;
end
endtask
// 读空测试
task read_empty_test;
begin
$display(\"=== 读空测试开始 ===\");
rd_en = 1;
wr_en = 0;
for (integer i = 0; i < DEPTH + 5; i = i + 1)
begin
@(posedge clk);
$display(\"读出数据[%0d] = %h\", i, r_data);
end
rd_en = 0;
@(posedge clk);
$display(\"=== 读空测试完成 ===\");
end
endtask
// 并发读写测试
task concurrent_read_write_test;
begin
$display(\"=== 并发读写测试开始 ===\");
wr_en = 1;
rd_en = 1;
for (integer i = 0; i < DEPTH + 5; i = i + 1) begin
w_data = $random;
@(posedge clk);
$display(\"写入:%h,读出:%h\", w_data, r_data);
@(posedge clk);
end
wr_en = 0;
rd_en = 0;
@(posedge clk);
$display(\"=== 并发读写测试完成 ===\");
end
endtask
// 随机读写测试
task random_test;
begin
$display(\"=== 随机读写测试开始 ===\");
for (integer i = 0; i < 50; i = i + 1) begin
wr_en = $random % 2;
rd_en = $random % 2;
w_data = $random;
@(posedge clk);
$display(\"time=%0t wr_en=%b, rd_en=%b, w_data=%h, r_data=%h, full=%b, empty=%b\", $time, wr_en, rd_en, w_data, r_data, full, empty);
end
wr_en = 0;
rd_en = 0;
@(posedge clk);
$display(\"=== 随机读写测试完成 ===\");
end
endtask
// 主仿真流程
initial begin
// 初始化信号
wr_en = 0;
rd_en = 0;
w_data = 0;
// 测试顺序
reset_fifo;
write_full_test;
read_empty_test;
concurrent_read_write_test;
random_test;
$display(\"=== 所有测试完成 ===\");
$finish;
end
endmodule
四、过程bug解决
1.在运行仿真过程中,有时候修改tb文件的内容后仿真波形并没有变化。网上没也看到这种情况,发现仿真之后需要点击close simulation才能更新。否则跑出来一直是之前的波形。
2.不显示已满
for (integer i = 0; i < DEPTH + 5; i = i + 1) begin w_data = i; $display(\"写入数据:%h\", w_data); if (full) $display(\"FIFO 已满!\"); // 问题点:缺少 @(posedge clk)!end
分析:缺少 @(posedge clk)
:full
是同步信号,需在时钟边沿检查。当前代码在同一个时间点连续写入,无法正确触发 full
。修改在问题点前加上 @(posedge clk)即可
3.读空后再同时读写时,empty一直为1导致无法正常读取
考虑特殊情况修改代码:2\'b11: if(empty)count <= 1; // 同时读写,计数不变
4.覆盖率测试
参考网页
怎么用Vivado做覆盖率分析_vivado覆盖率统计-CSDN博客
5.修改建议:
(1)用my_clog2函数代替$clog2
系统函数
(2)增加count变量看波形
(3)再根据coverage进行修改