看起来像软件
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 循环。