第3章 组合逻辑设计规范

组合逻辑是数字电路的肌肉,它负责数据的运算、选择、译码。但很多新手甚至老手都在组合逻辑上翻过车——生成锁存器、赋值混乱、综合前后行为不一致。今天咱们就把组合逻辑的规范彻底聊透。

组合逻辑设计规范 · 核心知识体系 组合逻辑规范 always @(*) 写法 阻塞/非阻塞赋值 避免锁存器(Latch) 组合逻辑敏感列表 避免反馈环路 组合逻辑用阻塞 时序逻辑用非阻塞 条件分支完整 默认赋值 三大支柱:正确的always块 + 正确的赋值方式 + 避免锁存器 三者缺一不可,否则综合网表与RTL仿真可能不一致

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。如果你需要保持值,请使用时序逻辑。

总结三条黄金法则:

  1. 组合逻辑 always 块用 @(*),内部用阻塞赋值 =。
  2. 每个 if 都有 else,每个 case 都有 default。
  3. 不要将输出赋值给自己(除非是时序逻辑)。