文章目录
SystemVerilog 进程控制与事件触发类语句全面解析一、核心语句分类总览(思维导图)二、关键语句详解与对比✅ 1. 事件触发 vs 条件等待:`@` 与 `wait` 的根本区别✅ 2. `always_comb` / `always_ff` vs 传统 `always @(…)`✅ 3. `iff` 与 `if` 的本质区别✅ 4. `fork…join` 系列语句对比
三、IC 验证中的典型应用场景(附代码)场景 1:条件时钟采样(避免无效计数)场景 2:事件同步(避免竞争)场景 3:超时控制(`fork…join_any` + `disable fork`)场景 4:断言中使用 `disable iff` 避免复位误报场景 5:事务级通信(事件 + 信箱)场景 6:状态机验证(`wait_state`)
四、总结与建议✅ 核心语句分类速查表📌 最佳实践建议
SystemVerilog 进程控制与事件触发类语句全面解析
在 SystemVerilog(SV)中,、
@、
wait 和
always @ 等关键字属于进程控制与事件触发类语句,主要用于控制仿真行为、同步多线程、响应信号变化等。这些机制是构建高效、可靠验证环境的核心基础。
iff
本文将对这些语句进行系统性梳理,包括其分类、用法、区别联系以及在实际 IC 验证中的典型应用场景,并提供可直接复用的代码示例。
一、核心语句分类总览(思维导图)
SystemVerilog 进程控制与事件触发
├─ 1. 事件触发与敏感列表类
│ ├─ @(event) - 事件触发,阻塞直到事件发生
│ ├─ @(*) - 自动推断所有输入信号为敏感列表(组合逻辑)
│ ├─ always_comb - 专用组合逻辑块(自动敏感)
│ ├─ always_ff - 专用时序逻辑块(推荐用于寄存器建模)
│ └─ always_latch - 专用锁存器逻辑块
│
├─ 2. 条件等待与阻塞控制类
│ ├─ wait(condition) - 电平敏感阻塞,条件为真时继续
│ ├─ wait fork - 等待所有 fork 子进程结束
│ ├─ wait_order - 断言多个事件按指定顺序触发
│ ├─ disable fork - 终止所有后台子进程
│ └─ wait_state (IEEE 1800-2017) - 等待状态机进入特定状态
│
├─ 3. 事件限定与过滤类
│ ├─ iff(condition) - 条件限定事件(仅当 condition 为真才触发)
│ ├─ if (...) - 普通过程内条件判断
│ └─ unique if / priority if - 带优先级检查的条件语句
│
├─ 4. 进程启动与并发控制类
│ ├─ fork ... join - 启动并发进程,父线程阻塞直至全部完成
│ ├─ fork ... join_any - 父线程在任意子线程完成后继续
│ ├─ fork ... join_none - 异步启动,不等待子线程(常用于后台任务)
│ └─ begin ... end - 顺序执行块
│
├─ 5. 断言与采样事件控制类
│ ├─ @(event) - 在断言中定义时钟事件
│ ├─ disable iff(cond) - 断言禁用条件(如复位期间忽略检查)
│ ├─ throughout(expr) - 表达式在整个区间保持成立
│ ├─ intersect - 两个序列同时匹配
│ └─ within - 一个序列在另一个序列范围内
│
└─ 6. 定时与延迟控制类
├─ #delay - 时间延迟(过程级)
├─ ##delay - 在断言中表示周期延迟
├─ -> event - 触发命名事件(阻塞式)
└─ ->> event - 非阻塞式事件触发(NBA区域执行)
✅ 提示:以上六大类覆盖了 SV 中几乎所有与“时间”、“事件”、“同步”相关的控制语句。
二、关键语句详解与对比
✅ 1. 事件触发 vs 条件等待:
@ 与
wait 的根本区别
@
wait
| 特性 | 操作符 |
语句 |
|---|---|---|
| 敏感类型 | 边沿敏感(等待变化) | 电平敏感(等待为真) |
| 是否检查当前值 | ❌ 不检查,只等下一次变化 | ✅ 检查当前值,若已满足立即继续 |
| 竞争风险 | ⚠️ 高(可能错过同一时间步的事件) | ✅ 低(更可靠) |
| 典型用法 | |
|
示例对比:
// 可能失败:@ 在事件触发后才开始等待
initial begin
-> ev;
@(ev); // 错过!因为 ev 已触发
end
// 更安全:wait 检查事件是否已被触发
initial begin
-> ev;
wait(ev.triggered); // 成功!即使先触发也能捕获
end
📌 最佳实践:优先使用
或
wait(condition),避免使用
wait(event.triggered)。
@(condition)
✅ 2.
always_comb /
always_ff vs 传统
always @(...)
always_comb
always_ff
always @(...)
| 写法 | 是否自动推断敏感列表 | 是否易出错 | 推荐度 |
|---|---|---|---|
|
✅ 是 | ⚠️ 但部分工具支持不一致 | ❌ |
|
❌ 否 | ✅ 易遗漏信号 | ❌ |
|
✅ 是(组合逻辑) | ✅ 编译器强制检查 | ✅✅✅ |
|
✅ 是(仅时钟/复位) | ✅ 专用于时序逻辑 | ✅✅✅ |
推荐写法:
always_comb y = a & b; // 自动敏感 a, b
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= 0;
else q <= d;
end
📌 建议:永远不要手写敏感列表!使用
/
always_comb替代传统
always_ff。
always
✅ 3.
iff 与
if 的本质区别
iff
if
:仅用于 事件触发 或 断言 中,表示“仅当条件成立时才采样或触发”。
iff
@(posedge clk iff enable); // 仅当 enable==1 时才响应 clk 上升沿
assert property (@(posedge clk) disable iff(!rst_n) (req iff valid));
:通用条件语句,用于过程块内部逻辑控制。
if
if (enable) data <= new_data;
⚠️ 注意:
不能出现在普通
iff或
always块中作为控制语句。
initial
✅ 4.
fork...join 系列语句对比
fork...join
| 语句 | 行为 | 适用场景 |
|---|---|---|
|
父线程阻塞,直到所有子线程结束 | 需要完全同步(如测试结束前等待所有任务) |
|
父线程在任一子线程结束后继续 | 超时控制、快速响应 |
|
父线程立即继续,子线程后台运行 | 启动监控器、周期性任务 |
配合 /
wait fork 可实现精细控制。
disable fork
三、IC 验证中的典型应用场景(附代码)
场景 1:条件时钟采样(避免无效计数)
// ✅ 推荐写法:使用 always_ff + 条件判断
always_ff @(posedge clk) begin
if (!rst_n) count <= 0;
else if (enable) count <= count + 1; // 仅 enable 有效时计数
end
// ❌ 不推荐:混合敏感列表 + 条件
always @(posedge clk iff enable) ... // 可读性差,调试困难
场景 2:事件同步(避免竞争)
event data_ready;
// 发送端
initial begin
#20;
-> data_ready; // 触发事件
end
// 接收端(✅ 安全)
initial begin
wait(data_ready.triggered);
$display("Data ready at %0t", $time);
end
// ❌ 危险写法(可能死锁)
initial begin
@(data_ready); // 若事件已触发,则永远等待
end
场景 3:超时控制(
fork...join_any +
disable fork)
fork...join_any
disable fork
event task_done;
initial begin
fork
begin // 主任务
#50;
-> task_done;
end
begin // 超时监控
#100;
if (!task_done.triggered) begin
$error("Task timeout!");
disable fork; // 终止所有子线程
end
end
join_any
wait fork; // 等待剩余线程清理
end
场景 4:断言中使用
disable iff 避免复位误报
disable iff
// ✅ 正确:复位期间禁用断言
assert property (
@(posedge clk)
disable iff (!rst_n)
(req |-> ##[1:3] ack)
) else $error("ACK not received in time");
// ❌ 错误:复位期间可能误报
assert property (@(posedge clk) req |-> ##[1:3] ack);
场景 5:事务级通信(事件 + 信箱)
mailbox #(transaction) mb;
event trans_sent;
class transaction;
rand bit [7:0] data;
endclass
// 生成器
task generator();
forever begin
transaction t = new();
t.randomize();
mb.put(t);
-> trans_sent; // 通知接收方
#10;
end
endtask
// 接收器
task receiver();
forever begin
wait(trans_sent.triggered);
transaction t;
mb.get(t);
$display("Received: %0d", t.data);
end
endtask
initial begin
fork
generator();
receiver();
join_none
end
场景 6:状态机验证(
wait_state)
wait_state
typedef enum {IDLE, BUSY, DONE} state_t;
state_t fsm_state;
// DUT 中的状态机更新...
// 验证平台
initial begin
wait_state(fsm_state == DONE); // IEEE 1800-2017 新特性
$display("FSM reached DONE at %0t", $time);
end
注:若工具不支持
,可用
wait_state替代。
wait(fsm_state == DONE)
四、总结与建议
✅ 核心语句分类速查表
| 类别 | 关键语句 |
|---|---|
| 事件触发 | , , |
| 进程敏感 | , , |
| 条件等待 | , , |
| 事件过滤 | , , |
| 并发控制 | , |
| 时序控制 | , , , |
| 断言专用 | , , |
📌 最佳实践建议
用 代替
wait(condition):避免时间步竞争。用
@(condition) /
always_comb 代替
always_ff:杜绝敏感列表遗漏。在断言中使用
always @(...):防止复位期间误报。事件等待优先用
disable iff(rst):比
wait(ev.triggered) 更可靠。超时控制用
@ev:结构清晰,易于维护。复杂同步考虑
fork...join_any + disable fork /
semaphore:比纯事件更安全(尤其在 OOP 环境中)。
mailbox
💡 结语:
SystemVerilog 的进程控制机制看似简单,实则蕴含丰富的并发与同步语义。掌握这些语句的本质区别与适用场景,是构建高可靠性、高可维护性验证平台的关键一步。希望本文能成为您日常验证开发中的实用参考手册!
参考资料:IEEE 1800-2017 SystemVerilog 标准、CSDN/Sohu 技术社区


