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 | 传统仿真 | 状态空间太大,形式化不可行 |