1. 问题现象与背景解析
最近在使用Verilator进行RTL仿真时,遇到了一个典型的错误提示:"Error-BLKANDNBLK"。这个错误通常发生在混合使用阻塞赋值(=)和非阻塞赋值(<=)的场景下。作为硬件描述语言(HDL)仿真器,Verilator对代码风格有着严格的要求,特别是对赋值语句的使用规范。
在实际工程中,我遇到的具体情况是在一个时钟触发的always块中,同时出现了阻塞和非阻塞赋值。Verilator抛出这个错误时,仿真会立即终止,控制台输出类似这样的信息:
code复制%Error: filename.v:42:混合使用阻塞和非阻塞赋值
%Error: 请检查信号'signal_name'的赋值方式
2. 阻塞与非阻塞赋值的本质区别
2.1 语法层面的差异
阻塞赋值使用等号(=),而非阻塞赋值使用小于等于号(<=)。这不仅是符号上的区别,更代表了完全不同的硬件行为模型。
阻塞赋值的特点是:
- 立即执行,右侧表达式计算完成后立即更新左侧变量
- 同一always块中后续语句能看到本次赋值的结果
- 行为类似于软件编程中的顺序执行
非阻塞赋值的特点是:
- 赋值操作被"调度"到当前仿真时间步结束时执行
- 同一always块中后续语句看不到本次赋值的结果
- 更接近真实寄存器行为的并行更新特性
2.2 硬件实现对应关系
从电路实现角度理解:
- 非阻塞赋值对应边沿触发的寄存器(DFF)
- 阻塞赋值对应组合逻辑的门级传播
在时序逻辑中混用两种赋值方式,相当于尝试将组合逻辑和时序逻辑的特性强行融合,这会导致仿真结果与综合后硬件行为不一致。
3. Verilator报错的具体原因分析
3.1 典型违规场景示例
以下代码会触发BLKANDNBLK错误:
verilog复制always @(posedge clk) begin
reg_a = data_in; // 阻塞赋值
reg_b <= reg_a; // 非阻塞赋值
end
3.2 Verilator的严格检查机制
Verilator作为lint工具和仿真器,会执行以下验证:
- 扫描always块中的赋值语句类型
- 检测是否存在混合使用的情况
- 对时钟触发的always块,要求统一使用非阻塞赋值
- 对组合逻辑always块,要求统一使用阻塞赋值
这种检查基于IEEE Verilog标准的最佳实践建议,目的是避免出现难以调试的仿真与综合不匹配问题。
4. 问题解决方案与代码修正
4.1 基本修正原则
根据always块类型采用统一的赋值风格:
- 时序逻辑(带时钟触发):全部使用非阻塞赋值(<=)
- 组合逻辑(无时钟触发):全部使用阻塞赋值(=)
修正后的正确写法:
verilog复制always @(posedge clk) begin
reg_a <= data_in; // 统一非阻塞
reg_b <= reg_a; // 统一非阻塞
end
4.2 复杂场景处理技巧
当需要在时序逻辑中实现组合运算时,推荐的做法是:
- 使用单独的always块处理组合逻辑
- 使用时序always块仅进行寄存器采样
示例:
verilog复制// 组合逻辑部分
always @(*) begin
comb_out = a & b; // 阻塞赋值
end
// 时序逻辑部分
always @(posedge clk) begin
reg_out <= comb_out; // 非阻塞赋值
end
5. 验证与调试方法
5.1 静态检查技巧
在运行仿真前,可以使用Verilator的lint模式单独检查:
bash复制verilator --lint-only design.v
5.2 波形调试建议
当怀疑赋值方式导致问题时:
- 在测试平台中添加$display语句显示关键信号
- 使用--trace选项生成波形文件
- 重点观察时钟边沿时刻的信号变化
5.3 自动化检查脚本
可以创建预检查脚本,自动捕获BLKANDNBLK错误:
bash复制#!/bin/bash
if verilator --lint-only $1 | grep -q "BLKANDNBLK"; then
echo "发现混合赋值错误!"
exit 1
fi
6. 深入理解赋值语义
6.1 仿真器视角的赋值处理
主流仿真器对两种赋值的处理流程差异:
| 特性 | 阻塞赋值 | 非阻塞赋值 |
|---|---|---|
| 执行时机 | 立即执行 | 时间步结束时执行 |
| 更新顺序 | 顺序影响结果 | 并行更新 |
| 可见性 | 后续语句可见 | 下个时间步才可见 |
| 硬件对应 | 组合逻辑 | 时序逻辑 |
6.2 综合工具的处理差异
综合工具通常会对混合赋值做如下处理:
- 产生警告而非错误
- 可能按非阻塞语义处理
- 导致RTL仿真与门级仿真不一致
7. 工程实践建议
7.1 代码风格规范
建议团队统一采用以下规范:
- 所有时钟触发的always块只使用<=
- 所有组合逻辑always块只使用=
- 避免在同一个always块中同时处理组合和时序逻辑
7.2 常见误用模式
需要特别注意这些易错场景:
- 在时序always块中初始化信号
- 在组合always块中创建锁存器
- 跨always块的信号依赖
7.3 Verilator进阶参数
对于大型设计,可以使用:
bash复制verilator --no-timing --assert # 关闭时序检查但保留断言
verilator --Wno-BLKANDNBLK # 不推荐!会掩盖问题
8. 相关错误扩展
8.1 类似错误代码
Verilator还会检查这些相关错误:
- BLKSEQ:时序逻辑中使用阻塞赋值
- NONBLK:组合逻辑中使用非阻塞赋值
- SYNCASYN:混合使用同步和异步复位
8.2 SystemVerilog改进
SystemVerilog引入的always_comb/always_ff可以更明确地表达设计意图:
systemverilog复制always_ff @(posedge clk) begin
out <= in; // 编译器会确保只能用非阻塞
end
always_comb begin
out = in; // 只能用阻塞赋值
end
在实际项目中,我通常会建立代码模板库,预置符合规范的always块结构,新工程师可以直接调用这些模板而避免低级错误。同时建议在CI流程中加入Verilator的lint检查,确保代码质量的一致性。