Verilog HDL 学习笔记(十五)| 第15章 高级验证技术

15.1 传统验证流程(Traditional Verification Flow)

一、传统验证的基本流程

在进入高级验证技术之前,先回顾一下我们一直在用的传统验证方法:

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        传统验证流程                                         │
│                                                                             │
│  1. 编写RTL设计                                                            │
│           ↓                                                                 │
│  2. 编写Testbench(激励 + 监视 + 检查)                                    │
│           ↓                                                                 │
│  3. 运行仿真                                                               │
│           ↓                                                                 │
│  4. 分析波形和输出                                                         │
│           ↓                                                                 │
│  5. 发现错误 → 修改RTL → 重新仿真(回到第1步)                            │
│           ↓                                                                 │
│  6. 仿真通过 → 覆盖率达标 → 完成                                           │
└─────────────────────────────────────────────────────────────────────────────┘

二、传统验证的核心方法:定向测试

定向测试(Directed Test) 是传统验证的主要手段------设计者根据功能规格,手写测试向量,逐一验证每个功能点。

复制代码
// 典型的定向测试:ALU加法功能验证
initial begin
    // 测试用例1:正数 + 正数
    a = 8'd10; b = 8'd20; op = OP_ADD;
    #20;
    if (out !== 8'd30)
        $display("ERROR: 10 + 20 = %d, expected 30", out);
    
    // 测试用例2:负数 + 正数
    a = 8'd200; b = 8'd10; op = OP_ADD;
    #20;
    if (out !== 8'd210)
        $display("ERROR: 200 + 10 = %d, expected 210", out);
    
    // 测试用例3:溢出
    a = 8'd127; b = 8'd1; op = OP_ADD;
    #20;
    if (overflow !== 1'b1)
        $display("ERROR: 127 + 1 should overflow");
    
    // ... 手写更多测试用例
end

三、传统验证的局限性

局限性 说明
覆盖率有限 只能测试设计者想到的场景,无法覆盖所有可能的输入组合
随机性不足 定向测试的输入模式是固定的,难以发现"意外"的错误
耗时巨大 手工编写大量测试向量,工作量大且容易遗漏
验证不充分 仿真通过≠没有Bug,只是"没测到Bug"
无法证明正确性 仿真只能证明"有错",不能证明"无错"

💡 一个经典问题:如果你的设计有32位输入,那么有 2^32 = 42亿 种输入组合。你不可能全部测完。传统仿真只能验证你测过的那部分。

四、传统验证的改进方向

面对这些局限,工业界发展出多种改进方法:

方法 核心思想
随机测试(Random Test) 用随机数生成测试向量,覆盖更多场景
覆盖率驱动验证(CDV) 用覆盖率指标衡量验证进度,直到达标
断言检查(Assertion) 在设计中嵌入"自动检查器"
形式化验证(Formal) 用数学方法证明设计正确性

15.2 断言检查(Assertion Checking)

一、什么是断言?

断言(Assertion) 是一种嵌入在设计中的检查器,用于验证设计是否满足特定的属性(Property)。

简单说:断言就是你对设计行为的"期望",用Verilog代码写出来,让工具在仿真过程中自动检查是否满足。

复制代码
// 断言示例:握手协议检查
// 期望:如果request为1,那么两个周期内acknowledge必须为1
property handshake_check;
    @(posedge clk) request |-> ##[1:2] acknowledge;
endproperty

assert property (handshake_check);

二、为什么需要断言?

传统Testbench vs 断言:

对比维度 传统Testbench 断言
检查方式 在Testbench中写检查逻辑 在设计中嵌入检查器
检查时机 仿真结束后分析输出 实时检查
定位错误 只知道输出错了 立即定位到错误发生的位置
复用性 检查代码与测试耦合 断言可复用于不同测试
覆盖范围 只检查测试用例覆盖的场景 只要仿真运行就会自动检查

断言的核心优势把"检查"从Testbench转移到设计中 ,实现"边仿真边检查"。你不用等仿真结束后再看波形,断言会在错误发生的瞬间立即报警

三、断言的类型

类型 特点 作用
立即断言(Immediate Assertion) 在过程块中执行,立即检查 检查当前时刻的条件
并发断言(Concurrent Assertion) 基于时钟周期,并行执行 检查时序行为(如"2个周期后")

立即断言示例:

复制代码
always @(posedge clk) begin
    // 检查:如果写使能有效,数据不应为x
    if (write_enable)
        assert (data !== 8'bxxxxxxxx) 
            else $error("Write data is unknown!");
end

并发断言示例(SystemVerilog):

复制代码
// 检查:如果request有效,acknowledge应在1-3个周期内有效
property req_ack;
    @(posedge clk) disable iff (reset)
    request |-> ##[1:3] acknowledge;
endproperty

assert property (req_ack)
    else $error("Handshake timeout!");

四、断言在实际设计中的应用

应用1:协议检查
复制代码
// 检查:SPI从设备片选有效时,时钟不应超过指定频率
property spi_cs_setup;
    @(posedge clk)
    !cs |-> ##[1:$] cs;  // cs先低后高
endproperty

property spi_cs_hold;
    @(posedge clk)
    cs |-> !cs within [1:10];  // cs在1-10周期内释放
endproperty

assert property (spi_cs_setup);
assert property (spi_cs_hold);
应用2:状态机安全检查
复制代码
// 检查:状态机永远不会进入非法状态
property no_illegal_state;
    @(posedge clk) disable iff (reset)
    state inside {IDLE, READ, WRITE, DONE};
endproperty

assert property (no_illegal_state)
    else $fatal("FSM entered illegal state!");
应用3:FIFO安全检查
复制代码
// 检查:FIFO空时不能读,满时不能写
property fifo_no_read_when_empty;
    @(posedge clk) disable iff (reset)
    (!empty) |-> (rd_en |=> !empty);
endproperty

property fifo_no_write_when_full;
    @(posedge clk) disable iff (reset)
    (!full) |-> (wr_en |=> !full);
endproperty

assert property (fifo_no_read_when_empty)
    else $error("FIFO read while empty!");
assert property (fifo_no_write_when_full)
    else $error("FIFO write while full!");

四、断言检查的工作流程

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                       断言检查工作流程                                      │
│                                                                             │
│  1. 在设计代码中嵌入断言                                                    │
│           ↓                                                                 │
│  2. 运行正常仿真(不需要额外的检查代码)                                    │
│           ↓                                                                 │
│  3. 仿真过程中,断言自动检查                                                │
│           ↓                                                                 │
│  4. 如果断言被违反 → 立即报错,仿真停止或记录                              │
│           ↓                                                                 │
│  5. 查看报错信息 → 定位错误位置 → 修改设计                                 │
└─────────────────────────────────────────────────────────────────────────────┘

五、断言的优缺点

优点 缺点
实时检查,错误立即暴露 需要在设计中嵌入额外代码
可复用性强,一次编写多次使用 断言编写需要专门的语法知识
定位精确,直接指出错误位置 复杂时序属性的断言编写较难
覆盖率高,只要仿真运行就检查 部分断言可能不可综合

15.3 形式化验证(Formal Verification)

一、什么是形式化验证?

形式化验证(Formal Verification) 是使用数学方法 来证明设计是否满足特定属性的技术。

💡 核心区别

  • 仿真 :给一组输入,看输出对不对------"测试"

  • 形式化验证 :用数学证明,对所有可能的输入,设计都满足属性------"证明"

经典类比:

方法 类比
仿真 抽查考试------只检查你抽到的题目
形式化验证 数学证明------证明"对所有x,P(x)成立"

二、形式化验证的两种主要形式

形式 说明 工具示例
等价性检查(Equivalence Checking) 证明两个设计的功能完全相同(如RTL vs 门级网表) Conformal、Formality
属性检查(Property Checking) 证明设计满足指定的属性(如"永远不会死锁") JasperGold、VC Formal

三、等价性检查

应用场景: 综合后,证明门级网表与RTL的功能完全等价

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        等价性检查                                           │
│                                                                             │
│  ┌─────────────┐                                                           │
│  │   RTL设计   │──────┐                                                    │
│  └─────────────┘      │                                                    │
│                       ▼                                                    │
│              ┌─────────────────┐                                          │
│              │  等价性检查工具 │──────▶ 等价 ✅ / 不等价 ❌                │
│              └─────────────────┘                                          │
│                       ▲                                                    │
│  ┌─────────────┐      │                                                    │
│  │  门级网表   │──────┘                                                    │
│  └─────────────┘                                                           │
│                                                                             │
│  数学证明:对所有输入,RTL输出 ≡ 门级网表输出                             │
└─────────────────────────────────────────────────────────────────────────────┘

等价性检查的优势:

优势 说明
完全性 数学证明,覆盖所有输入组合
速度快 比门级仿真快几个数量级
无需测试向量 不需要编写Testbench

等价性检查的局限:

局限 说明
只验证等价性 如果RTL本身有错,等价性检查无法发现
依赖综合设置 综合优化可能改变结构但不改变功能
状态空间爆炸 对于复杂设计(如含大存储器),可能无法完成

四、属性检查

应用场景: 证明设计满足特定的属性(Property)

属性示例:

属性类型 示例
安全性(Safety) "永远不会进入非法状态"
活性(Liveness) "请求最终会被响应"
互斥性(Mutual Exclusion) "两个信号不会同时为1"
无死锁(Deadlock Free) "系统永远不会卡死"

属性检查的工作流程:

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        属性检查流程                                         │
│                                                                             │
│  1. 编写RTL设计                                                             │
│           ↓                                                                 │
│  2. 编写属性(用SVA/PSL等属性描述语言)                                     │
│           ↓                                                                 │
│  3. 形式化工具自动搜索反例                                                  │
│           ↓                                                                 │
│  4. 结果:                                                                  │
│     ├── 证明通过 → 属性在所有情况下都成立 ✅                               │
│     └── 找到反例 → 属性在某些情况下不成立 ❌ → 分析反例                    │
└─────────────────────────────────────────────────────────────────────────────┘

形式化验证的核心思想:

形式化工具不需要 你提供测试向量。它会穷举所有可能的输入序列,自动搜索是否有违反属性的情况。如果找到反例,会给出具体的输入序列和波形,帮助你定位问题。

五、形式化验证 vs 仿真验证

对比维度 仿真验证 形式化验证
验证方法 测试(给输入,看输出) 证明(数学推理)
覆盖范围 只覆盖测试到的场景 覆盖所有可能的输入
是否需要Testbench ✅ 需要 ❌ 不需要(只需属性)
能否发现"未测试到"的Bug ❌ 不能 ✅ 能
验证速度 慢(需要跑很多向量) 快(数学求解)
状态空间 不受限 受限于状态空间大小
适用设计规模 大规模 中小规模(模块级)
调试难度 容易(看波形) 较难(需分析反例)
工业应用 全流程 模块级、关键路径

重要认识 :形式化验证不能完全替代仿真 。对于大规模设计(如完整CPU),形式化验证会因为"状态空间爆炸"而无法完成。但在模块级关键控制逻辑(如仲裁器、协议控制器、状态机)中,形式化验证非常有效。

15.4 三种验证方法的对比与选择

一、总览对比

验证方法 核心思想 覆盖范围 适用阶段 典型工具
传统仿真 给输入,看输出 测试到的场景 全流程 ModelSim、VCS
断言检查 嵌入检查器,实时报警 仿真运行的所有时刻 RTL仿真、门级仿真 SVA、PSL
形式化验证 数学证明 所有可能的输入 模块级、RTL阶段 JasperGold、VC Formal

二、实际项目中的验证策略

在实际芯片设计中,三种方法不是互斥的,而是互补的

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                      现代芯片验证策略                                       │
│                                                                             │
│  模块级验证                                                                 │
│  ├── 形式化验证(属性检查)→ 验证控制逻辑、协议                                │
│  ├── 断言检查 → 嵌入关键路径检查                                              │
│  └── 传统仿真 → 验证数据通路功能                                              │
│           ↓                                                                 │
│  子系统级验证                                                               │
│  ├── 断言检查 → 检查接口协议                                                  │
│  └── 传统仿真 → 系统级功能验证                                                │
│           ↓                                                                 │
│  全芯片验证                                                                 │
│  ├── 等价性检查 → RTL vs 门级网表                                            │
│  └── 传统仿真 → 全芯片功能验证                                                │
│           ↓                                                                 │
│  物理验证                                                                   │
│  └── 时序分析(STA)→ 验证时序约束                                            │
└─────────────────────────────────────────────────────────────────────────────┘

三、验证方法的选择原则

场景 推荐方法 原因
控制逻辑(状态机、仲裁器) 形式化验证 + 断言 状态空间小,适合形式化
数据通路(加法器、乘法器) 传统仿真 + 断言 输入空间大,形式化困难
接口协议(AXI、USB) 断言检查 协议规则明确,适合断言
RTL vs 门级 等价性检查 证明两者功能相同
存储器、大FIFO 传统仿真 状态空间太大,形式化不可行
相关推荐
国产化嵌入式平台解决方案3 小时前
从通用计算到实时协同:基于飞腾D3000M+FPGA的国产VPX计算平台设计
fpga开发·硬件架构·vpx6u
tju新生代魔迷4 小时前
Verilog HDL 学习笔记(十六)| 附录A:强度建模与高级线网类型
fpga开发
minglie117 小时前
j2b描述smp协议
fpga开发
FPGA开源工坊21 小时前
FPGA开发技巧–BRAM资源优化
fpga开发
泛联新安1 天前
InfinitPro多FPGA原型验证系统|提升芯片设计验证效率10-100倍
fpga开发
hahaha60162 天前
HLS高层次综合设计技巧--数组
开发语言·嵌入式硬件·算法·fpga开发
NamoAmitabha1282 天前
直接上手 UVM 例子
fpga开发
cmc10282 天前
301.modelsim自动仿真
fpga开发
Riwuarua3 天前
从近似0基础开始FPGA开发 -- part.10 verilog-ethernet开源UDP协议栈工程学习
fpga开发·udp·开源