Menu

Verilog if-else:过程块中的条件逻辑

if/else 在 always 块中如何工作,初学者常踩的锁存器陷阱,以及链式 else if 生成的优先级编码器硬件。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

熟悉的语法,不同的思维模型

if/else 看起来和 C 完全一样:

if (condition) begin
    // ... statements ...
end else begin
    // ... statements ...
end

但因为 Verilog 不是软件,规则也不一样。有两点需要记住:

  1. if/else 只能存在于过程块内部。你不能在模块层级写一个独立的 if。
  2. if/else 会综合成什么,取决于块的类型。在组合逻辑块中,它会变成多路选择器或优先级编码器。在时钟驱动的块中,它会变成带条件更新逻辑的触发器。

在组合逻辑块中

只要 a、b 或 c 发生变化,组合逻辑 always @(*) 块就会重新执行。if/else 链选中一个分支,给 max 赋值,然后块执行结束。由于 max 总是被赋值(每条路径上都有赋值),综合器会生成纯组合逻辑,不会产生锁存器。

注意,虽然硬件中并没有触发器,max 仍然声明为 reg。规则还是那条:在 always 中被赋值的东西都必须是 reg。

锁存器陷阱

这是初学者编写组合逻辑时最常见的 bug:

// WRONG - infers a latch
always @(*) begin
    if (enable)
        out = data;
    // no else! when `enable` is low, what does `out` do?
end

综合器读到“当 enable 为低时,out 没有被赋值”,于是认定 out 必须记住它之前的值。记住一个值需要存储单元,所以工具插入了一个锁存器。在同步设计中,锁存器会带来时序问题,难以复位,而且几乎从来都不是你想要的。

有两种修复方法:

两者生成的组合逻辑硬件相同:一个二选一多路选择器。当同一个信号有很多条件赋值时,“开头设默认值”的写法更容易扩展。

在时钟驱动的块中

注意和组合逻辑的情况相比有哪些不同:

  • 块是 always @(posedge clk),属于触发器的地盘。
  • 赋值使用 <=(非阻塞)。
  • “既不复位也不使能”的情况没有 else。 这没有问题。在时钟驱动的块中,当没有分支被触发时,触发器只是保持原来的值,而这正是触发器在物理上做的事。由于这个信号本来就是寄存器,所以不会推断出锁存器。

这是唯一一个省略 else 也安全的地方。在时钟驱动的块之外,务必处理每一条路径。

链式 else if:优先级编码器

一串 else if 语句隐含着优先级:前面的条件比后面的优先:

requests[0] 的优先级最高:只要它被置位,不管编号更高的位是什么,grant 都是 0。综合器把这条链变成级联的多路选择器:先检查第 0 位,再检查第 1 位、第 2 位、第 3 位。每多一级,就多一点延迟。

如果各条件互斥(比如对独热码输入进行译码),case 语句(下一篇文档)生成的硬件会比 else if 链更扁平、更快。没有真正的优先级需求时,就用 case 的写法。

时序代码中没有 else 的 if

时钟驱动的块不需要 else,因为“保持原值”就是默认行为。使能就是这样实现的:

always @(posedge clk) begin
    if (load) target <= incoming;
    // no else: when load is low, target keeps its value
end

这是一个带加载使能的寄存器。大多数流水线寄存器、计数器和配置寄存器都使用这种写法。

begin/end 与单条语句

和 C 一样,单条语句可以省略 begin/end:

if (a) out = 1;
else   out = 0;

超过一条语句时,就要用语句块:

if (a) begin
    out = 1;
    flag = 1;
end else begin
    out = 0;
    flag = 0;
end

两种写法可以自由混用。编码规范一般建议始终使用 begin/end,这样以后添加第二条语句时就不会出问题。

接下来

下一篇文档 case 语句 讲解 case,它是多路译码(状态机、操作码分派、ROM 表)的合适工具。之后是 for 循环,它和软件中的循环有微妙的区别,因为它会在细化阶段被展开。

常见问题

Verilog 中的 if 语句是如何工作的?

if (cond) statement; 在 cond 不为零时执行 statement。多条语句可以用 begin ... end 包起来。加上 else statement; 处理另一个分支,或者用 else if (other_cond) ... 串联更多条件。if/else 只能出现在过程块(initial 或 always)内部,不能写在模块的顶层。

Verilog 中推断出的锁存器是什么?

是综合工具在你没有要求的情况下生成的锁存器,原因是你的组合逻辑 always 块没有在每条路径上都给某个信号赋值。工具看到“如果 a 就 out = 1”却没有 else,就认定未赋值的情况必须记住之前的值,于是生成了一个锁存器。锁存器几乎总是错的;解决办法是在块的开头给每个信号一个默认值,或者写出明确的 else。

Verilog 中如何避免推断出锁存器?

在组合逻辑 always @(*) 块中,确保每个输出 reg 在每条代码路径上都被赋值。最干净的写法是在块开头设置默认值,再按条件覆盖。编译器推断出锁存器时通常会给出警告,请把这个警告当作错误对待。

Verilog 中 if-else 链会综合成什么?

一个优先级编码器。第一个 if 的优先级最高,只有它为假时才会检查下一个 else if,依此类推。在硬件中,这会变成一串固化了优先级顺序的多路选择器。如果各条件互斥,用相同逻辑写成的 case 语句通常能综合出更扁平的硬件,读起来也更清楚。

Coddy 编程语言插图

和 Coddy 一起学编程

开始