Verilog Tutorial(10)如何实现可复用的Verilog设计?

Posted 孤独的单刀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Verilog Tutorial(10)如何实现可复用的Verilog设计?相关的知识,希望对你有一定的参考价值。

本文将讨论可以用来实现代码可复用性的参数parameter和generate语句(生成语句)。

与大多数编程语言一样,设计者也应该尽量使verilog代码尽可能地具备可复用性----这能够减少未来项目的开发时间,因为设计者可以更轻松地将代码从一个设计移植到另一个设计。

在 verilog 中有两种语法可以帮助设计者编写可复用的代码——参数parameter和generate语句。这两种语法都允许设计者创建更通用的代码,以便在例化组件时可以通过修改代码的方式来满足其他的设计需求。

参数Parameter

参数parameter是常量(constant)的局部形式,它可以在例化模块时为其赋值。由于参数的作用范围有限,设计者可以多次调用同一个模块,并为参数赋不同的值。

在编写模块时必须定义一个模块接口,然后可以使用这个接口在FPGA 设计中与许多不同的模块实现互连。接口可以声明参数以及模块的输入和输出。

下面的 verilog 代码片段展示了在模块中声明参数的方法。当这样在 verilog 模块中声明参数时,我们称其为参数化模块(a parameterized module)。

module <module_name> #(
  parameter <parameter_name> = <default_value>    //声明参数
)
(
  //端口定义
);

上面代码中的 <parameter_name> 用于为参数提供标识符。设计者可以使用此标识符在代码中调用参数值--就像使用普通变量一样。

代码中的<default_value> 将为参数分配一个默认值,这很有用,因为它允许设计者例化组件而无需专门为参数分配值。

在例化一个模块时可以使用命名关联(named association)或位置关联(positional association)的方法来为参数赋值,这与直接将信号分配给模块上的输入或输出的作用完全相同。但是在使用verilog-1995标准编写代码时,只能使用位置关联方法来为参数赋值。

下面的 verilog 代码片段展示了在例化模块时用于为参数赋值的方法。

//名称关联
<module_name> # (
  .<parameter_name> (<parameter_value>)
)
<instance_name> (
  //端口连接
);
 
//位置关联
<module_name> # (<parameter_values>)
<instance_name> (
  //端口连接
);

示例

为了更好地理解如何在 verilog 中使用参数,请考虑一个基本示例:设计两个同步计数器,一个是 8 位宽,另一个是 12 位宽。

为了实现这个电路可以设计两个具有不同宽度的不同计数器,但这显然是一种低效的编码方式。相反,如果设计一个计数器并使用一个参数来更改输出中的位数,则会更加高效。

下面的 verilog 代码片段展示了如何为参数化计数器模块编写接口。

module counter #(
  parameter BITS = 8;
)
(
  input wire clock,
  input wire reset,
  output reg [BITS-1 : 0] count
);

这个例子展示了如何使用参数来调整 verilog 中信号的位宽。示例并不是使用固定数字来声明端口信号的位宽,而是将参数值替换到端口声明中。这是 verilog 中一个最常见的参数示例。

上面的verilog代码中定义了BITS参数的默认值为8。因此,如果想要一个不是 8 位的输出时,设计者只需要为参数重新赋值。

下面的代码片段展示了当需要 12 位输出时如何例化此模块。这种情况必须在例化 verilog 模块时重写参数的默认值。

counter # (
  .BITS (12)
) count_12 (
  .clock  (clock),
  .reset  (reset),
  .count  (count_out)
);

虽然上面的例子中只使用了命名关联方法,但其实也可以使用位置关联方法来为参数赋值。

下面的代码片段就展示了如何使用位置关联方法来将 12 赋给参数 BITS 。

counter # (12) count_12 (clock, reset, count_out);

Generate语句

generate 语句可以被用来在设计中生成条件的(Conditional)或迭代的(iterative)代码块。这允许设计可以有选择地包含或排除代码块或创建特定代码块的多个实例。

只能在并发verilog代码块(concurrent verilog code blocks)中使用 generate 语句,这意味着它不能被包含在always blocks或initial blocks中。此外,generate 关键字还必须结合if语句、case语句或者for循环语句来使用。

generate if 和 generate case 语句被用来来有条件地生成代码,而 generate for 语句则迭代地生成代码。generate块中可以编写需要的任何有效的 verilog 代码,包括always blocks、模块例化和其他generate语句。

generate语句是在verilog-2001标准中引入的,所以设计者不能在基于verilog-1995标准的设计中使用它。

Generate For 语句

设计者可以在generate块中使用verilog中的for循环来迭代地创建一段代码的多个实例。 generate for loop 语法通常被用来使用描述有规则的和重复的结构的硬件。

例如,设计者可能想要使用单个总线控制多个RAM模块。如果使用generate块而不是手动例化所有模块,那么就可以有效地减少代码行数。

下面的代码片段展示了generate for 块的一般语法。

//声明循环变量
genvar <name>;
 
//generate块的代码
generate
  for (<initial_condition>; <stop_condition>; <increment>) begin
    //要重复执行的代码
  end
endgenerate

从这里可以看出来,这种语句实际上可for循环语句是很类似的,但是两者之间仍有两个很大的区别。首先,设计者必须使用 genvar 类型来声明循环变量。其次,设计者应在 generate 块中声明循环,而不是像在always块这样的普通过程块中,这种差异很重要,因为它改变了代码的基本行为。

编写一个 generate for block 实际上是让编译工具创建代码的多个实例。相反,使用普通的 for 循环则是让编译工具创建代码的单个实例但要多次执行它。

作为示例,请看一个非常简单的用例:将数据分配给 2 bits向量。

下面的 verilog 代码展示了如何使用 generate for 和 for 循环来做到这一点。在这两种情况下,代码的功能相同,但生成的结构却大不相同。

//使用for循环的方法
always @(posedge clock) begin
  for (i = 0; i < 2; i = i + 1) begin
    sig_a[i] = 1'b0;
  end
end

//使用generate for循环的方法
generate
  for (i = 0; i < 2; i = i + 1) begin
    always @(posedge clock) begin
      sig_a[i] = 1'b0;
    end
  end
endgenerate

如果将for循环的代码展开,那么将得到如下所示的代码。

always @(posedge clock) begin
  sig_a[0] = 1'b0;
  sig_a[1] = 1'b0;
end

相反,将generate for的代码展开将如下所示。

always @(posedge clock) begin
  sig_a[0] = 1'b0;
end

always @(posedge clock) begin
  sig_a[1] = 1'b0;
end

由此可以看出 generate for 与 for 循环有何根本不同。

示例

为了更好地演示 verilog generate for 语句的工作原理,请考虑一个基本示例:连接到同一总线的 3 个 RAM 模块的数组,每个 RAM 模块都有一个写使能端口、一个 4 位地址输入和 4 位数据输入。这些信号都被连接到同一总线。此外,每个 RAM 都有一个 4 位数据输出总线和一个使能信号,它们对于每个 RAM 块都是独立的。下面的电路图展示了将要设计的电路。

为此需要声明一个 3 位的向量,可以使用它来连接到 RAM 使能端口,庵后可以根据循环变量的值将不同的位连接到每个 RAM。

对于数据输出总线,可以创建一个 12 位向量并将读取的数据输出连接到该向量的不同 4 位向量。然而,更高效的解决方案是使用由 3 个 4 位向量组成的数组。同样也可以使用循环变量根据需要赋值该数组的不同元素。

下面的 verilog 代码片段展示了如何使用 for generate 语句对这个电路进行设计。


wire [3:0] rd_data [2:0];    //读数据数组
wire [2:0] enable;           //使能信号构建的向量


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

综合这段代码后,将得到如下所示的电路。

Generate If语句

generate if语句可以在设计中有条件地生成 verilog 代码。当设计者只想将代码在特定条件下使用时,可以使用 generate if 语句。与此有关的一个例子是当设计者想在设计中包含一个专门用于测试的功能时,为此可以使用 generate if 语句来确保仅将此函数包含在调试版本的代码中,而不会包含在生产版本代码中。

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

generate
  if (<condition1>) begin
    //要执行的代码
  end
  else if (<condition2>) begin
    //要执行的代码
  end
  else begin
    //要执行的代码
  end
endgenerate

从这个示例可以看到,该语法虽然与verilog中的if语法是类似的,但这两种方法之间存在根本区别。

编写 generate if 语句时,实际上是让编译工具根据某些条件创建代码块的实例。这意味着只有一个分支会被编译,而任何其他分支都将被排除在编译之外。

相反,使用 if 语句时,整个 if 语句将被编译并且可以执行语句的每个分支。每次在仿真期间触发 if 语句时,都会判断条件以确定执行哪个分支。

示例

为了更好地演示generate if 语句的工作原理,请考虑一个基本示例:编写一个输出 4 位计数器值的测试电路。由于这是一个测试功能,所以只需要在使用调试版本时激活它;构建生产版本代码时,将计数器输出接地。为此将使用一个参数来确定何时应该构建调试版本的电路。

下面的代码片段展示了如何实现这个示例。

parameter debug_build = 0;

generate
  if (debug_build) begin
    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 时,综合工具会生成如下所示的电路----四位计数器电路。

但当 debug_build 被设置为 0 时,综合工具则会生成如下所示的电路----计数信号的所有位均接地。

Generate Case语句

generate case 语句可以被用来有条件地在设计中包含 verilog 代码块。generate case 语句基本上与 generate if 语句功能相同,但语法不同。这意味着如果只想在特定条件下将某部分包含在设计中时,设计者也可以使用 generate case 语句。

例如,如果设计者想要设计一个只想包含在调试版本中的测试电路,就可以使用 generate case 语句来确定构建哪个版本的代码。

下面的代码片段展示了generate case 语句的一般语法。

generate
  case (<variable>)
    <value1> : begin
      //当<variable> = <value1>执行这个分支语句
    end
    <value2> : begin
      //当<variable> = <value2>执行这个分支语句
    end
    default : begin
    //其他情况执行这个分支语句
    end
  endcase
endgenerate

从这个示例可以看到,该语法虽然与verilog中的case语法是类似的,但这两种方法之间存在根本区别。

编写 generate case 语句时,实际上是让编译工具根据某些条件创建代码块的实例。这意味着只有一个分支会被编译,而任何其他分支都将被排除在编译之外。

相反,使用 case 语句时,整个 case 语句将被编译并且可以执行语句的每个分支。每次在仿真期间触发 case语句时,都会判断条件以确定执行哪个分支。

示例

由于generate case语句与 if generate 语句功能类似,所以仍以generate if章节中使用的示例为例。下面的 verilog 代码展示了如何使用 generate case 语句来实现这个例子。

parameter debug_build = 0;

generate
  case (debug_build)
    1 : begin
      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 时,综合工具会生成如下所示的电路----四位计数器电路。

但当 debug_build 被设置为 0 时,综合工具则会生成如下所示的电路----计数信号的所有位均接地。


  • 📣您有任何问题,都可以在评论区和我交流📃

  • 📣您的支持是我持续创作的最大动力!如果本文对您有帮助,还请多多点赞👍、评论💬和收藏


verilog 二维数组是如何初始化的啊?

我现在是要将matlab中已实现的算法用verilog实现,其中,我在matlab中e=[1;zeros(n-1,1)],在verilog中我不知道如何比较好的初始化,我现在想得是先定义reg signed [20:0] e[n-1:0];再对e赋值,这样行吗?还有怎样赋值呢?是用for循环吗?

你说的这种情况我在前几天的毕设中也遇到过,这种情况只能用fpga中的寄存器或者RAM块来实现.
具体的话,你说的这种定义是不可行的,可以参考上面几位说的用lpm_rom ip核来实现--就是在FPGA中实现一个订制ROM或者RAM,选用哪种看实际情况.像你这种算法处理的话应该用RAM来反复进行算法的计算.
还有一点需要注意的是FPGA实现算法尽量用流水线来实现.因为FPGA的寄存器资源有限.数据的反复计算再存储可能需要大量的寄存器,这样可能造成实现困难,解决方法要看你实现的算法能不能进行一些优化.比如像FFTip里用到的一个小技巧一组数据计算后又存到原来的寄存器组中.
自己的一点小经验希望能帮到你

参考资料:FPGA verilog语法 eda

参考技术A 您说的二维数组在verilog中表示一个储存器,ram或rom。
建立这个储存器两种初始化方法一种就是:有规律的数据用for是可以赋值的

但是如果是很多无规律的数据需要您建立lpm_rom,并将数据写入.mif文件后加载到rom中,

欢迎追问。
参考技术B module test(
input wire rst_n // Reset, Active Low
,input wire clk
//
// Add other inputs and outputs here
//
);
integer k;
parameter n = 10; // set the value of n here
reg signed [20:0] e[0:n-1];
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
for(k=0;k<n;k=k+1) e[k] <= 21'h0; // set initial value of matrix to all zero here
end
else begin
// change the value of e here normally
end
end

endmodule本回答被提问者和网友采纳
参考技术C 你说的二维数组实际上是一个memory类型的变量,verilog里是没有数组的。这个有很多方式,楼上的方法可以,可以用initial. 还可以用文件来初始化:$readmemb 或 $readmemh。看你想干什么用,设计的时候要对应硬件模型

以上是关于Verilog Tutorial(10)如何实现可复用的Verilog设计?的主要内容,如果未能解决你的问题,请参考以下文章

verilog中如何调用fifo做50个信号延迟

如何写好Verilog状态机

cordic算法原理及verilog实现

如何采用Verilog文本方式实现全加器?

verilog 上下边沿同时触发 可综合代码实现

FPGA教程案例40通信案例10——基于FPGA的简易OFDM系统verilog实现