Menu

Verilog for 循环:在编译时展开

Verilog 的 for 循环和软件里的循环有什么不同:综合器会把它展开成并行的硬件,而不是在运行时一次次迭代。

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

看起来像软件

Verilog 的 for 循环照搬了 C 语言:

for (i = 0; i < 8; i = i + 1) begin
    // body
end

同样的三部分:初始化、条件、递增。只要条件成立,循环体就会重复执行。

在测试平台中,它的行为和你在软件中预期的完全一样。仿真器会依次执行每一次迭代:

四次迭代,四行输出。没有意外。

意外出现在你把 for 循环放进可综合代码的时候。

展开

可综合的 always 块中的 for 循环不会在硬件中变成运行时循环。综合器会在细化阶段把它展开:把循环扩展成 N 份循环体,N 就是迭代次数:

这看起来是一个循环。在仿真中,仿真器确实会循环执行八次。而在综合中,循环被展开成对 data[0] 到 data[7] 的八个并行检查,全部同时发生。综合器看到的是:

count = 0;
if (data[0]) count = count + 1;
if (data[1]) count = count + 1;
if (data[2]) count = count + 1;
...
if (data[7]) count = count + 1;

……然后把这串语句变成一棵加法树。运行时的行为是“同时查看全部 8 位,数出有几个 1”,在一次组合逻辑运算中完成。

这意味着:可综合 Verilog 中的 for 循环不是免费的。一个 64 次迭代的循环在硬件中就是 64 份循环体。如果循环体很复杂,你就造出了一个庞大的组合逻辑块。N 较小时(几次到几十次)再用循环。次数更大时,通常应该用时钟驱动的计数器加状态机。

边界必须是常量

只有在细化阶段知道 N,综合器才能展开循环。这意味着循环边界必须是常量:

// Works - bound is constant
for (i = 0; i < 8; i = i + 1) ...

// Works - bound is a parameter
for (i = 0; i < WIDTH; i = i + 1) ...

// Doesn't synthesize - bound depends on a runtime signal
for (i = 0; i < dynamic_count; i = i + 1) ...

最后一种写法在仿真中也许还能工作,但综合器会拒绝它。如果你确实需要运行时计数的循环,就用时钟驱动的状态机加一个计数器寄存器来实现:硬件不像软件那样有迭代次数可变的循环。

generate for 与过程性 for

另一个相关但不同的结构是 generate for,它使用 genvar,写在 always 块之外:

genvar i;
generate
    for (i = 0; i < 8; i = i + 1) begin : g
        bit_inverter inv(.x(in[i]), .y(out[i]));
    end
endgenerate

这会生成 8 个 bit_inverter 的实例(在 模块实例化 中讲解)。它是结构性的:你是在说“把这个子模块复制 8 份”,而不是在描述行为。

快速区分:

  • 过程性 for(在 always 内):在单个行为块中展开语句。
  • generate for(在 always 外):复制整个结构:实例、assign 语句、命名块。

根据你要复制的东西选用对应的一种。

for 的用武之地:向量运算

当你要对向量的每一位做相同的运算时,循环最能发挥作用。比如统计 1 的个数、奇偶校验、位序反转、生成查找表:

32 次迭代,每次做一位赋值,比手写 32 条线网赋值好读得多。综合器能干净地展开它。

while、repeat、forever

除了 for,Verilog 还有另外三种循环结构,主要用于测试平台:

// Run until a condition fails
while (~done) begin
    @(posedge clk);
    cycles = cycles + 1;
end

// Run N times - simpler than for when you don't need a counter
repeat (8) @(posedge clk);

// Run forever - clock generators, monitoring loops
always #5 clk = ~clk;
forever begin
    @(posedge clk);
    $display("count=%0d", count);
end

while、repeat 和 forever 只在很有限的情况下可综合(尤其是计数为常量、循环体由时钟驱动的 repeat)。在测试平台中它们很好用;在可综合的 RTL 中,优先使用计数式的 for 加显式的状态机。

测试平台中的过程性 for

在测试平台中,for 循环的行为和软件一样,可以放心使用:

嵌套循环遍历两个 2 位输入的所有组合。仿真器按顺序执行每次迭代。不用担心展开的问题:测试平台不会被综合。

常见错误

在可综合代码中使用边界不是常量的 for 循环。 综合器会拒绝它。如果边界在运行时才确定,就用计数器加状态机来实现。

忘了循环体会变成并行硬件。 一个循环体里有乘法器的 64 次迭代循环,就是 64 个并行的乘法器,这多半不是你想要的。对于宽数据通路,只造一个乘法器,然后按顺序给它送数据。

同时使用 integer i 和名为 i 的 reg。 两者处于不同的作用域,在循环内部以 integer 为准。起清晰的名字,避免混淆。

接下来

现在你已经掌握了 Verilog 提供的所有过程性结构。下一章把它们组合成数字设计师真正在用的写法:时序逻辑(触发器、寄存器和流水线),以及 有限状态机(任何具有多种工作模式的控制器的标准写法)。

常见问题

Verilog 中的 for 循环是如何工作的?

语法上它和 C 一样:for (i = 0; i < N; i = i + 1) statement;。但在可综合代码中,循环会在细化(elaboration)阶段被展开:综合器把它扩展成 N 份循环体。没有运行时的循环计数器,硬件中也不存在循环。在测试平台中,for 循环的行为和软件中的循环一样,因为仿真器可以按顺序逐次执行。

Verilog 中的 for 循环可以综合吗?

可以,但前提是循环边界是细化阶段就已知的常量。综合器会把循环展开成 N 份并行的循环体。如果边界取决于运行时的信号,循环就无法综合,你必须把它改写成时钟驱动的时序设计。

Verilog 中 for 和 generate for 有什么区别?

写在 always 块中的 for 循环是过程性结构,通过展开来综合。generate for 循环(配合 genvar)是显式的细化阶段结构,用来批量生成结构性硬件:多个模块实例、多根线网、多条 assign 语句。在过程块内部用 for;在过程块外部用 generate for 来复制结构。

Verilog 有 while 循环吗?

有:while (condition) statement;。只有当综合器能证明循环会在有限次迭代后结束时,它才可综合。实际上这种情况很少,所以 while 主要出现在测试平台和仅用于仿真的代码中。需要可综合的迭代时,请改用计数式的 for 循环。

Coddy 编程语言插图

和 Coddy 一起学编程

开始