前言

 

我们在verilog中使用generate语句在我们的设计中有条件地或迭代地生成代码块。

这使我们可以:

  • 有选择地包括或排除代码块,
  • 创建给定代码块的多个例化。

这很重要,也很方便,对于第一条,我们没必要删减代码就可以令某一个模块无效,或者有效;毕竟删掉了,就破坏代码结构了,这对代码迭代管理不利。

第二条就更重要了,例如例化多个模块,如果一个一个例化,少了还可以,如果多了的话,例化几十个,那么代码臃肿,简直了。使用generate语句,几行搞定。

generate 语句一定要正确使用,不是什么地方都可以使用的。关于此,我写过几篇类似的博客:

Verilog中关于for与generate for用法和区别的一点愚见

Verilog 中如何无误使用 generate for?

HDLBits 系列(7)对for循环以及generate for的各种实践

但这几篇文章只是针对generate的一种用法,那就是generate for,generate还有很多其他结构,后面讲到!

Verilog的generate语句

我们只能在并发Verilog代码块中使用generate语句。这意味着我们不能将其包含在Always块或初始块中。

除此之外,我们还必须将if语句,case语句或for循环与generate关键字一起使用。

我们使用if和case generate语句有条件地生成代码,而for generate语句则迭代生成代码。

我们可以编写在generate块内部需要的任何有效的verilog代码。这始终包括block,模块实例化和其他generate语句。

generate块在verilog 2001标准中引入。结果,我们不能在基于verilog 1995的设计中使用此构造。

让我们看一下我们可以在Verilog设计中使用的三种不同类型的generate块。

Verilog中的generate for语句

我们可以在generate块内使用verilog for循环来迭代创建一段代码的多个实例。

我们通常使用generate for 循环方法来描述具有规则和重复结构的硬件。

例如,我们可能希望描述由单个总线控制的多个RAM模块。

如果我们使用generate块而不是手动实例化所有模块,那么我们可以减少代码开销。

下面的代码段显示了verilog中generate for块的常规语法。

// Declare the loop variable

genvar <name>;

 

// Code for the

generate

  for (<initial_condition>; <stop_condition>; <increment>) begin

    // Code to execute

  end

endgenerate

从该示例可以看出,该方法的语法实际上与我们在verilog for 循环中看到的语法相同。

但是,此方法与常规for循环之间有两个重要区别。

  • 首先,我们必须使用genvar类型声明循环变量。

  • 第二个区别是,我们在generate块中声明了循环,而不是在常规程序块(例如verilog always块)中声明了循环。

这种差异很重要,因为它会改变代码的基本行为。

  • 当我们编写generate for块时,实际上是在告诉Verilog编译器创建代码块的多个实例。

  • 相反,当我们使用普通的for循环时,我们告诉Verilog编译器创建代码块的单个实例,但是执行多次。

作为示例,让我们看一个非常简单的用例,其中我们希望将数据分配给2位向量。

下面的Verilog代码显示了如何使用generate for和for循环来执行此操作。在这两种情况下,代码的功能都是相同的,但产生的结构却大不相同。

// Example using the for loop

always @(posedge clock) begin

  for (i = 0; i < 2; i = i + 1) begin

    sig_a[i] = 1'b0;

  end

end
// Example using the generate for block

generate
1
  for (i = 0; i < 2; i = i + 1) begin
1
    always @(posedge clock) begin
1
      sig_a[i] = 1'b0;

    end

  end

endgenerate

如果我们要展开for循环示例,我们将得到下面的代码。

always @(posedge clock) begin

  sig_a[0] = 1'b0;

  sig_a[1] = 1'b0;

end

相反,展开代码的生成将导致以下代码。

always @(posedge clock) begin

  sig_a[0] = 1'b0;

end

 

always @(posedge clock) begin

  sig_a[1] = 1'b0;

end

由此可见,for生成与for循环在本质上有何不同。

Verilog generate for示例

为了更好地说明verilog生成for语句的工作方式,让我们考虑一个基本示例。

在此示例中,我们将使用3个RAM模块的阵列,它们连接到同一条总线。

每个RAM模块都有一个写使能端口,一个4位地址输入和一个4位数据输入。这些信号都连接到同一总线。

此外,每个RAM都有一个4位数据输出总线和一个使能信号,它们对于每个RAM块都是独立的。

电路图显示了我们将要描述的电路。

FPGA的设计艺术(13)使用generate语句构建可重用的逻辑设计_for循环

电路图显示了连接到一条总线的三个RAM模块。

我们需要声明一个3位向量,该向量可用于连接到RAM使能端口。然后,我们可以根据循环变量的值将不同的位连接到每个RAM块。

对于数据输出总线,我们可以创建一个12位向量,并将读取的数据输出连接到该向量的不同4位片上。

但是,更优雅的解决方案是使用由3个4位向量组成的数组。同样,我们可以根据需要使用循环变量分配此数组的不同元素。

下面的Verilog代码片段显示了我们如何使用for generate语句对该电路进行编码。

// rd data array
wire [3:0] rd_data [2:0];

// vector for the enable signals

wire [2:0] enable;
   
// Genvar to use in the for loop

genvar i;
   

generate

  for (i=0; i<=2; i=i+1) begin

    ram ram_i (

      .clock    (clock),

      .enable   (enable[i]),

      .wr_en    (wr_en),

      .addr     (addr),

      .wr_data  (wr_data),

      .rd_data  (rd_data[i])

    );

  end

endgenerate

综合此代码后,我们得到如下所示的电路。

FPGA的设计艺术(13)使用generate语句构建可重用的逻辑设计_代码块_02

verilog中的generate if语句

我们使用verilog中的generate if块在我们的设计中有条件地包括verilog代码块。

当我们拥有仅在特定条件下使用的代码时,可以使用generate if语句。

这样的一个例子是当我们想要在我们的设计中包括专门用于测试的功能时。

我们可以使用generate if语句来确保仅在调试版本中包含此功能,而在生产版本中不包含此功能。

下面的代码段显示了verilog generate if语句的一般语法。

generate

  if (<condition1>) begin

    // Code to execute

  end

  else if (<condition2>) begin

    // Code to execute

  end

  else begin

    // Code to execute

  end
endgenerate

从该示例可以看出,该方法的语法实际上与我们在verilog if语句中看到的语法相同。

但是,这两种方法之间存在根本差异。

当我们编写generate if语句时,实际上是在告诉verilog编译器根据某种条件创建代码块的实例。

这意味着只编译了一个分支,而其他任何分支都从编译中排除。结果,在我们的设计中只能使用其中一个分支。

相反,当我们使用if语句时,整个if语句将被编译,并且该语句的每个分支都可以执行。

每次在仿真过程中触发if语句代码时,都会评估条件以确定要执行哪个分支。

Verilog generate if示例

为了更好地演示verilog if语句如何工作,让我们考虑一个基本示例。

对于此示例,我们将编写一个输出4位计数器值的测试函数。

由于这是一个测试功能,因此仅在使用调试版本时才需要将其激活。

构建代码的生产版本时,我们将计数器输出绑定到地面。

我们将使用参数来确定何时构建调试版本。

下面的代码片段显示了此示例的实现。

// Use a parameter to control our build

parameter debug_build = 0;

  
// Conditionally generate a counter

generate

  if (debug_build) begin

    // Code for the counter

    always @(posedge clock, posedge reset) begin

      if (reset) begin

        count <= 4'h0;

      end

      else begin

        count <= count + 1;

      end

    end

  end

  else begin

    initial begin

      count <= 4'h0;

    end

  end

endgenerate

当我们将debug_build变量设置为1时,综合器将产生如下所示的电路。在这种情况下,综合工具产生了一个四位计数器电路。

FPGA的设计艺术(13)使用generate语句构建可重用的逻辑设计_Verilog_03

但是,当我们将debug_build参数设置为0时,综合工具将产生如下所示的电路。在这种情况下,综合工具已将计数信号的所有位都接地。

FPGA的设计艺术(13)使用generate语句构建可重用的逻辑设计_sed_04

Verilog generate case语句

我们在verilog中使用generate case语句在我们的设计中有条件地包括verilog代码块。

本质上,generate case语句执行与generate if语句相同的功能。

这意味着当我们拥有只希望在特定条件下包括在设计中的代码时,我们也可以使用generate case语句。

例如,我们可以设计一个测试功能,只想将其包含在调试版本中。

然后,我们可以使用generate case语句来确定要构建哪个版本的代码。

下面的代码段显示了verilog中generate case语句的一般语法。

generate

  case (<variable>)

    <value1> : begin

      // This branch executes when <variable> = <value1>

    end

    <value2> : begin

      // This branch executes when <variable> = <value2>

    end

    default : begin

    // This branch executes in all other cases

    end

  endcase

endgenerate

从该示例中可以看出,此方法的语法实际上与我们在verilog case语句中所看到的语法相同。

但是,这两种方法之间存在根本差异。

当我们编写generate case语句时,实际上是在告诉verilog编译器根据给定条件创建代码块的实例。

这意味着只编译了一个分支,而其他任何分支都从编译中排除。结果,在我们的设计中只能使用其中一个分支。

相反,当我们使用case语句时,整个case语句将被编译,并且该语句的每个分支都可以执行

每次在仿真期间触发case语句代码时,都会评估条件以确定要执行哪个分支。

Verilog生成案例示例

为了更好地说明verilog生成case语句的工作方式,我们来看一个基本示例。

由于case语句执行与if语句相同的功能,因此我们将再次查看同一示例。

这意味着我们将编写一个输出值4位计数器的测试函数。

由于这是一个测试功能,因此仅在使用调试版本时才需要将其激活。

构建代码的生产版本时,我们将计数器输出绑定到地面。

我们将使用参数来确定何时构建调试版本。

下面的verilog代码使用generate case语句显示了此示例的实现。

// Use a parameter to control our build

parameter debug_build = 0;

  
// Conditionally generate a counter
generate

  case (debug_build)

    1 : begin

      // Code for the counter

      always @(posedge clock, posedge reset) begin

        if (reset) begin

          count <= 4'h0;

        end

        else begin

          count <= count + 1;

        end

      end

    end

    default : begin

      initial begin

        count <= 4'h0;

      end

    end

  endcase

endgenerate

当我们将debug_build变量设置为1时,合成器将产生如下所示的电路。在这种情况下,综合工具产生了一个四位计数器电路。
FPGA的设计艺术(13)使用generate语句构建可重用的逻辑设计_if语句_05

但是,当我们将debug_build参数设置为0时,综合工具将产生如下所示的电路。在这种情况下,综合工具已将计数信号的所有位都接地。

FPGA的设计艺术(13)使用generate语句构建可重用的逻辑设计_if语句_06

练习

  1. 使用参数化模块有什么好处?
  • 我们可以在实例化模块时配置其功能。这使我们可以使代码更易于重用。
  1. 写一个generate for块,实例化2个16位同步计数器。这两个计数器应该使用参数化模块示例
// Variable for the generate loop
genvar i;
 
// Array for the outputs

wire [15:0] count_out [1:0]
 
// Generate the two counters

generate

  for (i=0; i < 2, i = i+1) begin

    counter # (

      .BITS (16)

    ) count_12 (

      .clock  (clock),

      .reset  (reset),

      .count  (count_out[i])

    );

  end

endgenerate

  1. 编写一个generate for块,该块根据参数的值实例化一个8位计数器或一个16位计数器。这两个计数器应该使用本文前面的参数化模块示例。您可以使用generate case或generate if块来编写此代码。
// Parameter to contr, teh generate block
parameter COUNT_16 = 0;
 
// Using a generate case statement
generate

  case (COUNT_16)

    0 : begin

      counter # (

        .BITS (16)

      ) count_16 (

        .clock  (clock),

        .reset  (reset),

        .count  (count16_out)

      );

    end

    default : begin

      counter # (

        .BITS (8)

      ) count_8 (

        .clock  (clock),

        .reset  (reset),

        .count  (count8_out)

      );

    end

  endcase

endgenerate

 

// Using a generate if statement

generate

  if (COUNT_16) begin

    counter # (

      .BITS (16)

    ) count_16 (

      .clock  (clock),

      .reset  (reset),

      .count  (count16_out)

    );

  end

  else begin

    counter # (

      .BITS (8)

    ) count_8 (

      .clock  (clock),

      .reset  (reset),

      .count  (count8_out)

    );

  end

endgenerate