第3章 组合逻辑设计规范
组合逻辑是数字电路的肌肉,它负责数据的运算、选择、译码。但很多新手甚至老手都在组合逻辑上翻过车——生成锁存器、赋值混乱、综合前后行为不一致。今天咱们就把组合逻辑的规范彻底聊透。
1. always块(组合逻辑)的正确写法
写组合逻辑时,我习惯用 always @(*) 或 always_comb(SystemVerilog)。敏感列表用 * 最省心,综合工具会自动推断。你想想看,如果手动列敏感信号,漏了一个,仿真和综合结果就不一样了——这种坑我踩过不止一次。
推荐写法:
// 组合逻辑:多路选择器
always @(*) begin
if (sel)
out = a;
else
out = b;
end
注意:组合逻辑的 always 块里,所有被赋值的变量都必须被完整赋值。如果某个分支没赋值,综合工具就会给你生成一个锁存器。嗯,这里要特别小心。
2. 阻塞赋值与非阻塞赋值的区别
说白了,阻塞赋值 = 是“立刻生效”,非阻塞赋值 <= 是“等所有语句执行完再统一更新”。组合逻辑里必须用阻塞赋值,时序逻辑里必须用非阻塞赋值。这个规则几乎成了数字设计的铁律。
我曾经在组合逻辑里不小心用了 <=,结果仿真波形看起来对,但综合后网表功能全乱。查了两天才发现是赋值方式用错了。所以我的建议是:写组合逻辑时,始终用 =。
| 赋值类型 | 适用场景 | 执行特点 | 典型错误 |
|---|---|---|---|
阻塞赋值 = | 组合逻辑 | 立即更新,语句顺序影响结果 | 在时序块中使用导致仿真与综合不匹配 |
非阻塞赋值 <= | 时序逻辑 | 并行更新,所有右值同时计算 | 在组合逻辑中使用产生意外锁存器 |
个人技巧: 我写代码时会在 always 块开头加注释 // combinational 或 // sequential,提醒自己用哪种赋值。代码审查时一眼就能看出来。
3. 避免锁存器(Latch)的生成
锁存器是组合逻辑的头号公敌。为什么?因为它对毛刺敏感,时序分析困难,而且很多工具默认不检查 latch 的时序。我见过一个项目,因为 latch 导致芯片在高温下功能失效,最后花了两周才定位到。
锁存器生成的三大原因:
- 条件分支不完整:if 没有 else,或者 case 没有 default。
- 组合逻辑反馈:输出赋值给自身(没有时钟边沿控制)。
- 敏感列表不完整:虽然
@(*)能避免,但手动列表漏信号也会产生 latch。
避坑指南: 我曾经在 case 语句里只写了两个分支,忘了 default,综合出来一个 8bit latch。Lint 工具报了 warning 我还没在意,结果后仿直接挂掉。从那以后,我写 case 必带 default,哪怕只是 default: out = '0;。
来看一个会生成 latch 的坏例子:
// 坏例子:缺少 else,生成 latch
always @(*) begin
if (en)
q = d;
// 没有 else,en=0 时 q 保持原值 → latch
end
正确的写法是给所有分支赋值:
// 好例子:完整条件
always @(*) begin
if (en)
q = d;
else
q = 1'b0; // 或 q = '0;
end
另外,组合逻辑里不要出现 q = q 这样的自我赋值,那会直接生成 latch。如果你需要保持值,请使用时序逻辑。
总结三条黄金法则:
- 组合逻辑 always 块用
@(*),内部用阻塞赋值=。 - 每个 if 都有 else,每个 case 都有 default。
- 不要将输出赋值给自己(除非是时序逻辑)。