FPGA的设计艺术(23)parameter的使用建议与defparam的消亡史

李锐博恩 2021-04-10 01:44:48 6636

前言

创建可重用模型通常要求使用诸如尺寸,宽度和深度等可重定义的参数来编写通用模型。

关于可重用设计的话题,我们在前面提到了很多,例如:parameter与generate语句等,都一定程度上有利于可重用设计的实现。

今天,我们一方面回顾一下parameter,另一方面我们来一起来学习下defparam的历史,以及它是如何被弃用的。

parameter回顾

Verilog标准1995与新标准Verilog-2001对于parameter的使用有一定的区别,下面给出两个简单的参数化寄存器模块的例子:

//Verilog 1995 model style
module register (q, d, clk, rst_n); 
 parameter SIZE=8; 
 output [SIZE-1:0] q; 
 input [SIZE-1:0] d; 
 input  clk, rst_n; 
 reg  [SIZE-1:0] q; 
 always @(posedge clk or negedge rst_n) 
 if (!rst_n) q <= 0; 
 else  q <= d; 
endmodule 
//verilog 2001 model style
module register2001 #(parameter SIZE=8) 
 (output reg [SIZE-1:0] q, 
 input  [SIZE-1:0] d, 
 input  clk, rst_n); 
 always @(posedge clk, negedge rst_n) 
 if (!rst_n) q <= 0; 
 else  q <= d; 
endmodule 

可见,对于参数化模块,一个或多个参数声明通常在Verilog-1995样式模型中的端口声明之前。

在本例中,还可以看出,Verilog 2001使用了ANSI-C样式端口列表以及模块头参数列表,相较于Verilog1995有较强的可读性提升。

二者均可使用,因为大多数,几乎所有的fpga编译器都支持这两种标准,但Verilog 2001的部分改动更有利于代码的可读性等,可取二者之精华,结合自己的习惯使用。

对于参数化模块的调用,众所周期,有两种方式:基于位置的方式与基于信号的方式。
如下为基于位置的方式:

//基于位置的方式调用参数化模块
module two_regs1 (q, d, clk, rst_n); 
 output [15:0] q; 
 input [15:0] d; 
 input  clk, rst_n; 
 wire  [15:0] dx; 
 register #(16) r1 (.q(q), .d(dx), 
 .clk(clk), .rst_n(rst_n)); 
 register #(16) r2(.q(dx), .d(d), 
 .clk(clk), .rst_n(rst_n)); 
endmodule 

所有综合工具都支持这种形式的参数重新定义多年。这种类型的参数重新定义的最大问题是,参数必须按照所实例化模块中出现的顺序传递到实例化模块。这是肉眼可见的做大的弊端,可读性很差。

这里只定义了一个参数还好,如果有多个参数,例如:

//多个参数的例子
module myreg (q, d, clk, rst_n); 
 parameter Trst = 1, 
 Tckq = 1, 
 SIZE = 4, 
 VERSION = "1.1"; 
 output [SIZE-1:0] q; 
 input [SIZE-1:0] d;
 input  clk, rst_n;
 reg  [SIZE-1:0] q;
 always @(posedge clk or negedge rst_n) 
 if (!rst_n) q <= #Trst 0; 
 else  q <= #Tckq d; 
endmodule

MyReg模块具有四个参数,如果模块在实例化时需要仅更改第三个参数(例如大小参数),则无法使用一系列逗号来实例化模块,然后是新值。
换句话说,就是使用位置调用的方式无法跳跃前几个参数去改变中间的或者后面的参数,如下的使用方式:

//错误的参数化调用
module bad_wrapper (q, d, clk, rst_n); 
 output [7:0] q; 
 input [7:0] d; 
 input  clk, rst_n; 
 // illegal parameter passing example 
 myreg #(,,8) r1 (.q(q), .d(d), 
 .clk(clk), .rst_n(rst_n)); 
endmodule

这是错误的。

如何使用呢?
按参数位置顺序赋值调用,即使是没必要改变的参数,也得赋值,但最有一个或者最后连续结果可以不赋值。
这就意味着使用十分的笨拙,且可读性不高。调用的时候一连串的无意义的数字,不去翻调用模块,实在不知道这些参数是干嘛的,即便去翻,但参数多的时候也难以记忆。

给出正确的位置调用方式。

//正确的位置调用
module good_wrapper (q, d, clk, rst_n); 
 output [7:0] q; 
 input [7:0] d; 
 input  clk, rst_n; 
 // the first two parameters must be 
 // explicitly passed even though the 
 // values did not change 
 myreg #(1,1,8) r1 (.q(q), .d(d), 
 .clk(clk), .rst_n(rst_n)); 
endmodule 

下面回顾按名称调用参数的例子:

module myreg (q, d, clk, rst_n); 
 parameter Trst = 1, 
 Tckq = 1, 
 SIZE = 4, 
 VERSION = "1.1"; 
 output [SIZE-1:0] q; 
 input [SIZE-1:0] d;
 input  clk, rst_n;
 reg  [SIZE-1:0] q;
 always @(posedge clk or negedge rst_n) 
 if (!rst_n) q <= #Trst 0; 
 else  q <= #Tckq d; 
endmodule
//正确的位置调用
module good_wrapper (q, d, clk, rst_n); 
 output [7:0] q; 
 input [7:0] d; 
 input  clk, rst_n; 

 myreg #(.SIZE(8) ) r1 (.q(q), .d(d), 
 .clk(clk), .rst_n(rst_n)); 
endmodule 

可见,这种方式的使用十分方便,可以随意赋值调用某一个参数,其他未赋值的参数保持不变,且可读性得到了很大的提升。

上面介绍了两种参数的调用方式之后,孰优孰劣,不辩自明,大家在以后的设计实践中可以避免位置的方式调用参数化模块。

事实上,在某些老设计中还存在着一种parameter语法在代码中偶尔出现,这在很多地方都不在提了,可是还是不可避免的会有,就是defparam。

下面谈谈这个语法。

谈谈defparam

defparam 语句明确标识了实例和每个 defparam 语句要重新定义的各个参数。defparam 语句可以放在实例之前,也可以放在实例之后,或者放在文件的任何位置。

在2000年之前,Synopsys工具不允许使用defparam语句重新定义参数。Synopsys的这一限制是值得称赞的。不幸的是,Synopsys开发人员屈服于不知情的工程师的压力,在Synopsys工具的最新版本中增加了使用defparam语句的能力。

不幸的是,本意良好的 defparam 语句很容易被滥用。

(1) 使用defparam来分层次地改变模块的参数。

(2) 将 defparam 语句放在与被修改的实例分开的文件中。

(3) 在同一个文件中使用多条defparam语句来修改一个实例的参数。

(4) 在多个不同的文件中使用多个 defparam 语句来改变一个实例的参数。

层次化的defparams

使用 defparam 语句分层次地改变参数的值是合法的。

这意味着设计中的任何参数都可以从设计中的任何输入文件中改变。

潜在的滥用可以扩展到用defparam语句改变实例化模块的参数值,并将该参数传递给实例化模块,再由实例化模块再次重新修改实例化模块的参数等。

在下例中,testbench模块(tb_defparam)实例化一个模型,并将SIZE参数传递给寄存器模块(传递给WIDTH参数),寄存器模块将WIDTH参数传递给dff模块(传递给N参数)。dff模块有一个错误的层次defparam语句,将testbench SIZE参数从8改为1,该值再次向下传递,再次改变寄存器WIDTH和dff 的N值。

module tb_defparam; 
 parameter SIZE=8; 
 wire [SIZE-1:0] q; 
 reg [SIZE-1:0] d; 
 reg  clk, rst_n; 
 register2 #(SIZE) r1 
 (.q(q), .d(d), .clk(clk), 
 .rst_n(rst_n)); 
 // ... 
endmodule 

module register2 (q, d, clk, rst_n); 
 parameter WIDTH=8; 
 output [WIDTH-1:0] q; 
 input [WIDTH-1:0] d; 
 input   clk, rst_n; 
 dff #(WIDTH) d1 
 (.q(q), .d(d), .clk(clk), 
 .rst_n(rst_n)); 
endmodule 

module dff (q, d, clk, rst_n); 
 parameter N=1; 
 output [N-1:0] q; 
 input [N-1:0] d; 
 input  clk, rst_n; 
 reg  [N-1:0] q; 
 // dangerous, hierarchical defparam 
 defparam tb_defparam.SIZE = 1; 
 always @(posedge clk or negedge rst_n) 
 if (!rst_n) q <= 0; 
 else  q <= d; 
endmodule 

上例中设计中的所有端口和变量现在只有一个位宽,而register2和dff模块的综合将是8bit位宽。这种类型的defparam使用很容易逃避检测,造成设计和调试问题。

再举例:

module register3 (q, d, clk, rst_n); 
 parameter WIDTH=8; 
 output [WIDTH-1:0] q; 
 input [WIDTH-1:0] d; 
 input  clk, rst_n; 
 dff3 #(WIDTH) d1 
 (.q(q), .d(d), .clk(clk), 
 .rst_n(rst_n)); 
endmodule 

module dff3 (q, d, clk, rst_n); 
 parameter N=1; 
 output [N-1:0] q; 
 input [N-1:0] d; 
 input  clk, rst_n; 
 reg  [N-1:0] q; 
 // dangerous, hierarchical defparam 
 defparam register3.WIDTH = 1; 
 always @(posedge clk or negedge rst_n) 
 if (!rst_n) q <= 0; 
 else  q <= d; 
endmodule 

与上例类似,只是defparam重新定义了register3模型的总线宽度,显得自成一体。不幸的是,尽管这个模型会像1位宽的模型一样进行仿真,但它仍然会综合合成一个8位宽的模型。

defeparams在单独的文件中

通过将defparams放在与被修改的实例完全不同的文件中来滥用defparams的情况并不少见。遗憾的是,Verilog1995和Verilog-2001标准文档第12.2.1节中的以下注释半鼓励了这种做法。

defparam 语句对于将所有的参数值覆盖赋值集中在一个模块中特别有用。

上面的文字也许应该从Verilog-2001标准中删除,但它没有删除。应该指出的是,Verilog标准组(VSG)引入并鼓励在实例化模块时使用通过名称传递参数的优越能力,类似于通过名称传递端口。VSG希望工程师们能够利用这种新的能力,并希望defparam语句最终会被淘汰。

同一文件中的多个defeparams

通过在修改相同参数的同一文件中将多个defeparam放置多个defeparam来滥用defparams。 Verilog-2001标准定义了正确的行为:

在单个参数的多个defParam的情况下,该参数采用源文本中遇到的最后一个defparam语句的值。

在下例中,通过放置在相应的寄存器实例化之前,两个实例的大小参数设置为16。第三个defParam语句被放置在第二个寄存器实例化之后,错误地将第二个寄存器的大小更改为4到4。

module two_regs2 (q, d, clk, rst_n); 
 parameter SIZE = 16; 
 output [SIZE-1:0] q; 
 input [SIZE-1:0] d;
 input  clk, rst_n;
 wire  [SIZE-1:0] dx;
 defparam r1.SIZE=16; 

 register r1 (.q(q), .d(dx), .clk(clk), 
 .rst_n(rst_n)); 

 defparam r2.SIZE=16; 
 register r2 (.q(dx), .d(d), .clk(clk), 
 .rst_n(rst_n)); 
 defparam r2.SIZE=4; // Design error! 

endmodule

因为这是一个小的设计,因为编译器会发出“端口尺寸不匹配”警告,这种设计不会难以调试。

不幸的是,当第二个游离的 defparam 语句被错误地添加时,经常会被添加到一个有几页 RTL 代码的大型设计中,因为设计者没有注意到之前的 defparam 语句被用来重新定义同一个参数值。这种类型的设计通常比较混乱,调试起来也比较困难

分离文件中的多个defeparams

通过将它们放在多个不同的文件中,甚至滥用defparams。

将多个defparam语句放置在不同文件中,使分配给同一参数的不同文件非常有问题。
不同的供应商不同地处理多个defparam语句,因为此方案的行为从未在Verilog-1995标准中定义。

Verilog-2001标准组不想鼓励这种行为,因此我们将以下免责声明添加到Verilog-2001标准。

当在多个源文件中遇到defparam时,例如通过库搜索找到的,参数取值的defparam是未定义的。

Verilog-2001标准组基本上想完全阻止这种做法,所以我们没有对这种行为进行定义,并将这一事实记录下来,希望能阻止任何人要求供应商支持这种有缺陷的策略。

defparams和工具

由于defparams可以放置在设计中的任何地方,而且它们可以分层次地改变设计中任何模块的参数值,所以defparams在当前的形式下,无论是供应商工具还是内部工具,都很难准确地解析允许包含defparam语句的设计。

在所有Verilog输入文件被读取之前,Verilog编译器无法确定任何参数的实际值,因为最后读取的文件可能会改变设计中的每一个参数!

有些公司禁止使用defparam语句。有些公司为了方便创建有用的内部Verilog工具,禁止在Verilog代码中使用defparams。

我同意这种做法,并提出以下指南。准则:不要在任何Verilog设计中使用defparams。

支持的做法是使用parameter且使用parameter的名称调用方式。

弃用defparam

VSG并不是唯一一个希望defparam语句消亡的组织。IEEE Verilog Synthesis Interoperability Group投票决定不支持IEEE Verilog Synthesis标准中的defparam语句。

而在2002年4月,The SystemVerilog标准组一致投票(1票弃权),决定废止defparam语句(可能会从Verilog语言的未来版本中删除对defparam语句的支持)。

参考

  • New Verilog-2001 Techniques for Creating Parameterized Models (or Down With `define and Death of a defparam!)
  • The Verilog Hardware Description Language
  • Verilog-2001 Behavioral and Synthesis Enhancements
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 86 7 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
李锐博恩
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区