Verilog代码优化之 for 语句
下面谈谈for语句,Verilog中的for语句虽然也是可综合的,但在RTL级的代码中基本不用。一方面是因为for语句的使用是很占用硬件资源的,另一方面是因为在设计中往往是采用时序逻辑设计,用到for循环的地方不多。
在Verilog中,for循环确实是可以综合的,但通常不建议在描述硬件行为的寄存器传输级(RTL)代码中过度使用for循环,特别是在可综合的模块中。原因主要有以下几点:
硬件资源消耗:每个for循环迭代在硬件中可能需要其自己的逻辑来实现。这可能导致大量的重复逻辑,从而消耗大量的硬件资源。
并行与顺序:Verilog描述的是并行的硬件行为,而for循环是顺序的。因此,使用for循环可能会导致设计的行为不如预期,特别是当循环体内部的操作依赖于之前的迭代结果时。
可读性:过度使用for循环可能会降低代码的可读性,使得其他工程师难以理解你的设计意图。
在一些情况下,使用for循环可能是合理的,比如初始化数组或生成查找表。但即使在这些情况下,也应该仔细考虑其对硬件资源的影响。
下面是一个使用for循环的示例,以及它可能的综合结果:
module for_loop_example( input wire clk, input wire reset, output reg [7:0] result ); reg [7:0] array[0:7]; initial begin // 这是一个非综合的初始块,用于示例 for(int i = 0; i < 8; i = i + 1) begin array[i] = i; // 初始化数组 end end always @(posedge clk or posedge reset) begin if(reset) begin // 重置逻辑 result <= 0; end else begin // 这里我们不会使用for循环,因为它在时序逻辑中不合适 // 但如果我们真的用了,它可能会为每个迭代生成一个硬件块 end end // 假设我们真的在时序逻辑中使用了for循环(这通常是不推荐的) // always @(posedge clk) begin // for(int i = 0; i < 8; i = i + 1) begin // // 一些依赖于i的操作... // // 这将为每个i值生成一个硬件块 // end // end Endmodule
在上面的示例中,initial块中的for循环用于初始化数组,但它是一个非综合的块,只在仿真中执行。在always块中,我们没有使用for循环,因为它在时序逻辑中通常是不合适的。但是,如果我们真的在时序逻辑中使用了for循环(如注释所示),那么它可能会为每个迭代生成一个硬件块。
这段代码的用意是在一个时钟周期内计算出13路脉冲信号为高电平的个数,一般人都会感觉这个任务交给for循环来做再合适不过了,但是for 循环能完成这个任务吗?来看看如图所示的仿真波形。
在Verilog中,使用非阻塞赋值(<=)是在描述并行硬件行为时常见的做法。在always块中,当使用非阻塞赋值时,所有右边的表达式计算都将在单个时间单位(如时钟周期)内并行完成,然后这些值会在该时间单位的末尾同时更新到左侧的寄存器或变量。
然而,如果你试图在always块中使用阻塞赋值(=),你通常会遇到问题,因为阻塞赋值会阻止always块中的后续代码在当前赋值完成之前执行。在硬件描述语言中,这通常不是期望的行为,因为硬件是并行的,而不是顺序的。
但是,如果你确实需要在某个特定的上下文中使用阻塞赋值(例如,在初始化或测试平台代码中),你可以这样做,但你应该清楚这不是描述硬件并行性的标准方法。
下面是一个使用阻塞赋值在initial块中初始化变量的例子(这不会影响到硬件的综合,因为initial块是不可综合的,只用于仿真):
module test_blocking_assignment; reg [31:0] num; initial begin for (int i = 0; i < 10; i = i + 1) begin // 使用阻塞赋值初始化变量 num = num + 1; // 在这里,num的值会在每次循环迭代中立即更新 #1; // 添加一个时间延迟以便在仿真中看到变化(可选) end // 此时,num的值将是9(如果初始值为0) end // ... 其他硬件描述代码 ... Endmodule
但是,在always块中,你应该避免使用阻塞赋值,因为它会改变always块中代码的执行顺序,这可能导致意外的行为或不可综合的代码。
如果你需要在每个时钟周期递增一个寄存器,你应该使用非阻塞赋值,如下所示:
module counter; input wire clk, reset; output reg [31:0] num; always @(posedge clk or posedge reset) begin if (reset) begin // 如果复位信号为高,则重置计数器 num <= 0; end else begin // 否则,在每个时钟上升沿递增计数器 num <= num + 1; end end // ... 其他硬件描述代码 ... Endmodule
在这个例子中,num在每个时钟上升沿递增,而不管always块中的其他代码如何执行,因为所有非阻塞赋值都会在时钟边缘同时更新。