组合逻辑少写一个敏感信号,仿真结果就会失真
组合逻辑少写一个敏感信号,仿真结果就会失真

组合逻辑表达式没写错,综合报告也没有锁存器,RTL仿真里输出却会莫名保持旧值:某个输入变化后毫无反应,等另一个信号动一下才一起更新。这里常见的根因不是门级电路有记忆,而是组合always块漏写了敏感信号。仿真器只在列表中的事件发生时重新执行过程;综合器看到过程内完整的布尔依赖,仍可能实现成正常组合逻辑,于是两边出现行为差异。
敏感列表决定过程何时醒来
在传统Verilog中,`always @(a or b)`只会在a或b变化时执行。若过程内部还读取c,c单独变化时过程不会重算,输出继续保留上一次仿真值。等a或b后来发生事件,过程再次执行,c的新值才被带进去,看起来像输出延迟或拥有记忆。这个“旧值”来自事件调度,不等于综合出了触发器。敏感列表描述的是仿真触发条件,组合逻辑的所有读入量都必须被覆盖。

图1 FPGA器件实物用于区分RTL仿真模型与最终可编程逻辑资源。
综合器和仿真器看的问题不同
综合器解析过程里的赋值与条件,推导输出依赖哪些输入,通常会把漏掉敏感项的代码综合为组合门网络。仿真器则严格按语言事件模型运行,不替作者猜测触发项。结果可能是RTL仿真错、门级仿真和硬件对,最危险的是验证环境依据错误RTL行为建立了错误预期。也有工具会给警告,但不能把警告是否出现当成安全保证。代码审查需要把敏感列表不完整作为功能错误,而非风格问题。

图2 开发板与逻辑器件照片说明,综合后的硬件会持续响应输入,而不是等待软件式调用。
自动敏感列表能消掉一类手工遗漏
Verilog-2001的`always @*`会由工具收集过程内读取的信号,SystemVerilog的`always_comb`还带来更明确的组合过程语义与额外检查。新代码优先使用这些形式,函数内部读取、参数化路径和后续维护新增信号时更不容易漏项。迁移旧代码仍要检查工具支持与函数边界,不能只做字符串替换。时序逻辑继续使用明确边沿触发,并用`always_ff`表达意图,避免组合与时序写法混在一个过程里。

图3 FPGA板卡局部结构用于对应输入、组合逻辑和寄存器之间的边界。
别把两种“保持旧值”混为一谈
敏感列表遗漏造成的是仿真没有执行;组合过程分支赋值不完整,则会让综合器真正推导锁存器。排查时先让所有读入信号逐个单独翻转,观察过程是否被触发;再检查每条控制路径是否都给所有输出赋值。组合过程常在开头给默认值,再由条件覆盖,可减少漏赋值。lint应同时打开敏感列表、锁存器、阻塞与非阻塞赋值规则。修复后跑RTL、综合网表或等价检查,确保不是只让某一个测试用例看起来正常。

图4 代码与波形对照图展示漏触发和真实锁存器在事件轨迹上的差别。
用断言把组合关系钉在每一次输入变化上
对关键组合模块,可以在测试环境中建立参考函数,只要任一输入变化,就在同一仿真时间步的稳定区比较输出与参考结果。若过程未被触发,断言会立刻报出,而不是等待后续系统级症状。覆盖率还应确认每个输入都曾独立变化,避免测试向量总是把多个信号绑在一起,掩盖遗漏。多人维护的旧工程可先用lint批量找出显式敏感列表,再按风险分级替换;每次替换都配合回归和综合差异检查。对于含有函数调用的组合块,确认工具能把函数内读取的外部信号纳入自动敏感集合,必要时消除隐式全局依赖。敏感列表问题还会藏在仿真函数和层级引用里。过程表面只读取一个函数返回值,但函数内部又读取模块级变量,部分工具对这类隐式依赖的自动收集方式可能不同。更稳妥的写法是把所有依赖作为函数输入参数,让数据流显式可见。测试平台中的组合过程也要遵守同样规则,否则参考模型自己不更新,会反过来给设计判错。对跨时钟域信号,输出延后不是敏感列表遗漏就能解释的,应先经过同步器并按时序逻辑验证。把所有“输出保持旧值”都归为一个原因,会让真正的CDC、锁存器或初始化问题被漏掉。修复提交中应附上最小复现波形,证明遗漏信号单独变化时输出已经立即更新。修复后还应观察零延迟组合环路与仿真收敛。`always_comb`会帮助暴露多驱动等问题,但不能替代清晰的模块边界。一个输出只由一个组合过程驱动,输入依赖全部显式化,波形和综合报告才更容易互相解释。
组合逻辑是否正确,要同时满足“输入变化会触发重算”和“每条路径都完成赋值”。一个管仿真事件,一个管硬件存储,排查时分开验证才能避免误判。
你们的RTL规范里,是已经统一使用always @*或always_comb,还是仍允许手写敏感列表?
声明:
本文由凡亿教育整理,转载请注明来源!
投稿/招聘/广告/课程合作/资源置换 请加微信:13237418207






哔哩哔哩
微信公众号
小红书
抖音
知乎
西瓜视频
头条
微信视频号
