当前位置:   article > 正文

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

fpga计数器复用

写在前面

在自己准备写verilog教程之前,参考了许多资料----FPGA Tutorial网站的这套verilog教程即是其一。这套教程写得不错,只是没有中文,在下只好斗胆翻译过来(加了自己的理解)分享给大家。

这是网站原文:https://fpgatutorial.com/verilog/

这是系列导航:Verilog教程系列文章导航


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

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

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

参数Parameter

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

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

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

  1. module <module_name> #(
  2. parameter <parameter_name> = <default_value> //声明参数
  3. )
  4. (
  5. //端口定义
  6. );

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

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

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

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

  1. //名称关联
  2. <module_name> # (
  3. .<parameter_name> (<parameter_value>)
  4. )
  5. <instance_name> (
  6. //端口连接
  7. );
  8. //位置关联
  9. <module_name> # (<parameter_values>)
  10. <instance_name> (
  11. //端口连接
  12. );

示例

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

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

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

  1. module counter #(
  2. parameter BITS = 8;
  3. )
  4. (
  5. input wire clock,
  6. input wire reset,
  7. output reg [BITS-1 : 0] count
  8. );

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

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

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

  1. counter # (
  2. .BITS (12)
  3. ) count_12 (
  4. .clock (clock),
  5. .reset (reset),
  6. .count (count_out)
  7. );

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

下面的代码片段就展示了如何使用位置关联方法来将 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 块的一般语法。

  1. //声明循环变量
  2. genvar <name>;
  3. //generate块的代码
  4. generate
  5. for (<initial_condition>; <stop_condition>; <increment>) begin
  6. //要重复执行的代码
  7. end
  8. endgenerate

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

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

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

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

  1. //使用for循环的方法
  2. always @(posedge clock) begin
  3. for (i = 0; i < 2; i = i + 1) begin
  4. sig_a[i] = 1'b0;
  5. end
  6. end
  7. //使用generate for循环的方法
  8. generate
  9. for (i = 0; i < 2; i = i + 1) begin
  10. always @(posedge clock) begin
  11. sig_a[i] = 1'b0;
  12. end
  13. end
  14. endgenerate

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

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

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

  1. always @(posedge clock) begin
  2. sig_a[0] = 1'b0;
  3. end
  4. always @(posedge clock) begin
  5. sig_a[1] = 1'b0;
  6. 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 语句对这个电路进行设计。

  1. wire [3:0] rd_data [2:0]; //读数据数组
  2. wire [2:0] enable; //使能信号构建的向量
  3. genvar i; //循环变量
  4. generate
  5. for (i=0; i<=2; i=i+1) begin
  6. ram ram_i (
  7. .clock (clock),
  8. .enable (enable[i]),
  9. .wr_en (wr_en),
  10. .addr (addr),
  11. .wr_data (wr_data),
  12. .rd_data (rd_data[i])
  13. );
  14. end
  15. endgenerate

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

Generate If语句

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

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

  1. generate
  2. if (<condition1>) begin
  3. //要执行的代码
  4. end
  5. else if (<condition2>) begin
  6. //要执行的代码
  7. end
  8. else begin
  9. //要执行的代码
  10. end
  11. endgenerate

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

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

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

示例

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

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

  1. parameter debug_build = 0;
  2. generate
  3. if (debug_build) begin
  4. always @(posedge clock, posedge reset) begin
  5. if (reset) begin
  6. count <= 4'h0;
  7. end
  8. else begin
  9. count <= count + 1;
  10. end
  11. end
  12. end
  13. else begin
  14. initial begin
  15. count <= 4'h0;
  16. end
  17. end
  18. endgenerate

当 debug_build 被设置为 1 时,综合工具会生成如下所示的电路----四位计数器电路。

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

Generate Case语句

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

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

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

  1. generate
  2. case (<variable>)
  3. <value1> : begin
  4. //当<variable> = <value1>执行这个分支语句
  5. end
  6. <value2> : begin
  7. //当<variable> = <value2>执行这个分支语句
  8. end
  9. default : begin
  10. //其他情况执行这个分支语句
  11. end
  12. endcase
  13. endgenerate

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

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

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

示例

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

  1. parameter debug_build = 0;
  2. generate
  3. case (debug_build)
  4. 1 : begin
  5. always @(posedge clock, posedge reset) begin
  6. if (reset) begin
  7. count <= 4'h0;
  8. end
  9. else begin
  10. count <= count + 1;
  11. end
  12. end
  13. end
  14. default : begin
  15. initial begin
  16. count <= 4'h0;
  17. end
  18. end
  19. endcase
  20. endgenerate

当 debug_build 被设置为 1 时,综合工具会生成如下所示的电路----四位计数器电路。

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


  • 声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/603096
推荐阅读
相关标签