FPGA 连续实战 · 01 RTL 验证与工程契约
用同一个可综合自检器贯穿软件仿真和板上运行,建立 64 笔事务的可复现基线。
先修知识掌握同步 RTL、非阻塞赋值与 FIFO;按六章顺序完成前置交付。
适用环境Basys 3 / xc7a35tcpg236-1 / 100 MHz;Vivado Tcl 参考 2024.1;Python 3、Icarus Verilog。
验证状态RTL 正常与故障仿真已验证;Vivado 综合、实现、下载和 ILA 采集须在目标环境验证。
先定义工程完成的含义
本工程采用四深度、八位宽同步 FIFO,板上自检器生成六十四笔数据并逐笔检查。数据公式为序号乘十七加三再截取低八位,只有请求有效且 FIFO 允许接收时才推进序号。这样软件与硬件共用激励规则,却由 Testbench 再独立检查一次结果,避免仅凭板上完成灯判断数据正确。
读写契约与复位边界
FIFO 使用沿前的满空状态决定是否接受读写,满时即使同周期读也不接受写,空时即使同周期写也不接受读。读取数据在时钟沿后进入寄存器,因此检查器用 read_pending 延后一拍比较,不能在发起读的同一拍检查旧输出。按钮先经过两级同步,释放后等待稳定计数结束;仿真将计数宽度缩短为四位,实际硬件为二十位。
| 信号/状态 | 判据 |
|---|---|
| accepted write | wr_en 且非 full,sent 加一 |
| accepted read | rd_en 且非 empty,下一拍检查 |
| 正常结束 | received=64,done=1,fail=0 |
| 故障结束 | done=1,fail 保持为 1,直到复位 |
完整可综合检查器;与 ZIP 中同名文件一致。
`timescale 1ns/1ps
module fifo_selftest #(parameter INJECT_ERROR=0)(input wire clk,rst,
output reg done,fail,output wire [63:0] debug_bus);
reg [6:0] sent,received;
reg [15:0] cycles;
wire [7:0] din=(sent*8'd17)+8'd3;
wire wr_en=!done && sent<64 && cycles[1:0]!=3;
wire rd_en=!done && cycles>=12 && cycles[2:0]!=0;
wire [7:0] dout;
wire full,empty;
wire [2:0] count;
reg read_pending,checked_valid;
reg [7:0] checked_data,checked_expected;
wire [7:0] expected=(received*8'd17)+8'd3;
wire [7:0] observed=dout ^ (INJECT_ERROR ? 8'h01 : 8'h00);
course_fifo u_fifo(clk,rst,wr_en,rd_en,din,dout,full,empty,count);
always @(posedge clk) begin
if(rst) begin
sent<=0;received<=0;cycles<=0;done<=0;fail<=0;
read_pending<=0;checked_valid<=0;checked_data<=0;checked_expected<=0;
end else if(!done) begin
cycles<=cycles+1'b1;
if(wr_en && !full) sent<=sent+1'b1;
read_pending<=rd_en && !empty;
checked_valid<=read_pending;
if(read_pending) begin
checked_data<=observed;checked_expected<=expected;
if(observed!=expected) fail<=1;
received<=received+1'b1;
if(received==63) done<=1;
end
end else checked_valid<=0;
end
// probe0 map documented in README and decode.py; registered check values align.
assign debug_bus={cycles,sent,received,count,wr_en,rd_en,full,empty,
din,checked_data,checked_expected,checked_valid,2'b00};
endmodule
执行基线并证明检查器能报错
先安装 Icarus Verilog 并确认 Python 能找到 iverilog 与 vvp,再运行工程自带入口。正常测试包含冷启动和一次按钮复位后的重新运行,每次必须检查六十四笔。故障模式故意翻转检查输入的最低位,预期失败由外层脚本识别后返回成功;它证明负向测试通过,绝不表示故障 RTL 功能正确。
- 检查工具
预期与判定:iverilog 与 vvp 均在 PATH 中。
- 运行正常版
预期与判定:两个 run 均为 checked=64,LED 二进制为 01。
- 运行故障版
预期与判定:第一处差异必须是序号零、3 对 2。
- 保存基线
预期与判定:记录工具版本、源码与两次运行日志,进入约束章节。
在解压后的 fpga-course 目录执行;Vivado 步骤需本机安装并配置 PATH。
python3 flow.py sim
python3 flow.py sim --fault
# 实测关键输出:
# FPGA_PASS run=0 checked=64 cycles=95 led=01
# FPGA_PASS run=1 checked=64 cycles=95 led=01
# FPGA_SUITE_PASS: cold start and button reset
# 故障:FPGA_MISMATCH seq=0 expected=3 actual=2
# FPGA_FAULT_DETECTED: intentional failure, software check passed典型故障:检查器错一拍
如果每笔结果都像是上一笔数据,先检查接口时序而不是修改数据公式。非阻塞赋值在当前时钟事件末更新寄存器,同一 always 块读到的仍是旧值;本工程将有效标志延迟一拍,且在负沿由 Testbench 观察稳定的检查结果。修复时必须让数据、预期值和有效位保持一致,不能仅用延时语句掩盖问题。
完成检查
- 两次正常运行各检查 64 笔。
- 故障注入精确报告第一笔 3 对 2。
- 说明注册读输出与同步复位的周期关系。
延伸阅读与来源
本文为黄河电子原创教学说明。产品命令与支持范围请以实际安装版本的官方文档为准。
- Digilent Basys 3 Master XDC
板卡引脚参考;使用前核对实物版本。
- AMD UG906 · Check Timing
区分内部未约束路径与异步外部端口。
- AMD UG908 · Programming and Debugging
Hardware Manager、ILA 与采集窗口。
继续阅读
FPGA从 RTL 到板上运行的完整流程
建立约束、实现、下载与观测的最小工程闭环。
第一个工程的引脚与时钟约束
区分电气连接与时间要求,避免“能生成位流就算正确”。
如何读懂时序报告
从起点、终点、时钟和裕量找出失败路径的原因。
黄河电子