是模拟器在我的SystemVerilog代码下的行为出错了吗?

摘要:

有时,System Verilog的使用者花费大量时间来调试意料之外的仿真结果。最后才发现是SystemVerilog语言参考手册(LRM,Language Reference Manual)所定义的规范与使用者所想的不同。在本文将探讨SystemVerilog使用者经常提出的一些问题。这些问题的回答将帮助SystemVerilog的使用者正确、准确地理解语言规范从而可能节省设计人员因意外的模拟结果所花费的大量调试时间

 

  1. 序言

2005年,SystemVerilog被批准为硬件描述语言和硬件验证语言的标准(IEEE-1800)。从那时起,我们看到 SystemVerilog 已被我们的客户参与的几乎所有项目所采用。当我们也不断被客户问及“是模拟器对我以下代码的模拟出错了吗?”。其中的一些问题被不同的用户在调试模拟器结果一段时间后一再提及,而且无法确定是代码捅了模拟器的一个bug,还是代码本身的问题。因此,对于希望在其设计和验证环境中使用SystemVerilog的设计人员而言,正确,准确地理解语言规范非常重要。这可以为设计人员节省因调试意外结果而花费大量的时间。

 

在本文中,我们将讨论其中的一些问题,每个问题都带有一些示例片段或问题描述。然后,我们将展示在与其相应的IEEE-1800 语言参考手册上的规范是什么。

 

以下是本文讨论的主题:

    • 通配符包的导入和导出
    • 运算符表达式短路
    • 随机稳定性
    • Always_comb块推断的敏感列表
    • 最长静态前缀
    • 通配符相等运算符(wildcard equality operator)
    • `b1和`1
    • 确定模块的端口类型、数据类型和方向
    • 对象构造函数的调用顺序
    • 使用无效索引访问数组

 

  1. 问题和相关的System Verilog规范

  • 包的导入和导出

示例:

 

对上述代码的通常期望是pkg1中的所有定义在模块m1中均可见或可用的。然而这是错误的。在System Verilog 语言参考手册 2005版本中,没有明确指定链式包导入的预期行为。在System Verilog 语言参考手册 2009和更高的版本中,为避免可能出现的歧义,指定了包的导出:“默认情况下,导入到包中的声明在该包的后续导入中是不可见的。包导出的声明允许包指定要导入的声明,使其在后续的导入中可见。”

 

而且SystemVerilog 语言参考手册(我们在本文的其余部分中将使用IEEE-1800-2012作为参考)也阐明了正确的通配符导入和导出行为:“利用package_name :: *的形式来导出将实现把所有实际上与package_name的导入有关联的导出包给导出。所有源于package_name的名称,无论是直接导入还是通过通配符导入,都是可用的。对于候选导入但没有实际导入的符号,则是无法使用的。”以下是语言参考手册中的示例:

基于上述规范,用户的原始代码中只有在m2模块中引用的定义是可见或可用的。

 

  • 运算符表达式短路

用下面的代码

a = obj.b && c;

用户得到了“访问对象为空(Null Object Access)”的运行错误,当把代码重写为

a = c && obj.b;

模拟器可以跑过这一行而且不报错。可能的原因会是什么呢?

 

SystemVerilog 语言参考手册章节“ 11.3.5 运算符表达式短路”中著有以下的规范:“一些运算符(&&,||,->和?:)应使用短路评估;换句话说,如果运算的最终值不取决于运算表达式中的部分表达,则不应该对这部分表达式求值。” 语言参考手册专门针对运算符“ &&”和“ ||”,在“ 11.4.7逻辑运算符”章节中说明:“ && 和 ||运算符应按以下方式使用短路评估:

    • 第一个操作数表达式应始终被求值。
    • 对于&&,如果第一个操作数的值为逻辑假,则不应计算第二个操作数。
    • 对于||,如果第一个操作数的值为逻辑真,则第二个操作数将不被评估。

所以对于上述的代码“ a = c && obj.b”,当“c”为逻辑假时,模拟器将不再需要计算“obj.b”。这就是模拟器不报“对象访问为空”错误的原因。

 

  • 随机稳定性

示例:

模拟结果如下:

top.m_inst1  98710838

top.m_inst2  98710838

top.m_inst3  98710838

为什么模拟器三次例化产生了相同的随机值?

 

SystemVerilog语言参考手册在第18.14节中详细说明了“随机稳定性”。 “ RNG(Random Number Generator 随机数生成器)为线程和对象被本地化了。因为线程或对象返回的随机值序列与其他线程或对象中的RNG无关,所以这种属性称为随机稳定性。

随机稳定性适用于以下情况:

    • 调用系统随机函数$urandom()和$urandom_range()
    • 对象和线程的随机种子方法,srandom()
    • 对象的随机化方法randomize()

在章节18.14.1中,还就模块实例及其静态过程(初始块是静态过程)如何获得RNG进行了以下说明:“初始化RNG。每个模块实例,接口实例,程序实例和程序包都有一个初始化RNG。每个初始化RNG都带有默认种子。默认种子是与实现相关的值。在创建静态进程和静态初始化程序时,应使用初始化RNG”,“创建静态进程时,其RNG会以模块实例,接口实例,程序实例或包含线程声明的程序包的初始化RNG中的下一个值作为种子。”使用这些规范,将使用相同的默认种子作为3个模块m实例的初始化RNG种子,并使用这些初始化RNG的下一个值作为3个初始块的RNG种子。因此,在调用$ urandom时,这3个初始块RNG具有相同的状态,因此生成相同的随机结果。

 

用户可以手动为$ urandom的调用指定不同的种子,以生成不同的随机结果。例如,如果使用系统时间作为种子,则用户可以使用SystemVerilog DPI调用来获取系统时间:

 

请注意,使用上述解决方案,用户在不同的运行中总是会得到不同的随机结果。如果用户希望在不同的运行之间保持“随机稳定性”,则以下代码显示了另一种可能的解决方案:

 

  • Always_comb块推断的敏感列表

示例:

以下是模拟结果:

0 comb: a is: x

0 f1 call x

1 comb: a is: 1

1 f1 call x

11 comb: a is: 1

11 f1 call 1

在11时刻,always_comb块似乎被触发的原因是什么呢?

 

除了Verilog中常规的Always块之外,SystemVerilog引入了其他几种Always过程,包括always_comb,always_ff和always_latch。 Always_comb是对组合逻辑行为进行建模的一种非常方便的方法。使用always_comb块时,用户无需提供敏感列表。相反,SystemVerilog 语言参考手册在第9.2.2.1节中将always_comb敏感度定义为:“ always_comb的隐式敏感度列表包括在该块内或该块内调用的任何函数中读取的每个变量或选择表达式的最长静态前缀的扩展……”。 语言参考手册在9.2.2.2.2章节中再次说明了这一点:“ System Verilog always_comb过程在以下方面不同于always @ *(请参见9.4.2.2):

    • always_comb在零时刻自动执行一次,而always @ *需要等待直到推断出的灵敏度列表中的信号发生变化才执行。
    • always_comb对函数内容中的更改敏感,而@@始终仅对函数参数的更改敏感。”

在上面的示例中,函数f1读取了变量“ b”,因此,当b被更新时,将触发always_comb块。并且在零时刻,尽管所有变量都没有更新,但always_comb块仍然根据语言参考手册的规范执行了一次。

 

  • 最长静态前缀

下面的代码是否有效?

 

在System Verilog中,一个变量可以由多个过程块中的一个或多个过程语句驱动,因此在上面的代码中,从两个always @(*)对变量“ a”的驱动是有效。最后一次写操作决定对变量“ a”的赋值。但是,对于always_comb块并非如此。 System Verilog 语言参考手册第9.2.2.2节中著有“写在赋值左侧的变量不得由任何其他过程写入。” 语言参考手册在非压缩数组赋值中进一步说明到“但是,只要一个变量的独立元素所带的最长静态前缀不重叠​​,则允许对这些独立元素其进行多次赋值(请参见11.5.3)。”例如,一个未压缩的结构体或数组可以具有由always_comb过程对其中的一位赋值,而另一个位连续赋值或另一个always_comb过程赋值,等等。”

 

静态前缀的定义是递归的,定义如下:

    • 标识符是静态前缀。
    • 对象的分层引用是静态前​​缀。
    • 引用线网或变量的包是静态前​​缀。
    • 如果字段域的前缀为静态前缀,则字段域为静态前缀。
    • 如果索引选择的前缀是静态前缀并且选择表达式是常量表达式,则索引选择是静态前缀。

以下是语言参考手册在同一章节的示例:

根据上述规范,在用户代码中,always_comb块1中的最长静态前缀为“ aa [0]”,而always_comb块2中的最长静态前缀为“ aa”。它们是重叠的。为了避免此错误,用户可以将代码修改为:

Localparam是一个常数表达式,因此最长的静态前缀现在在always_comb块2中为“ aa [1]”,它不再与“ aa [0]”重叠。

 

  • 通配相等运算符

示例:

输出是:

(2’b1x ==? 2’b10) is: x

(2’b10 ==? 2’b1x) is: 1

为何模拟器会给出不同的结果?

 

System Verilog 语言参考手册第11.4.6节中将通配相等运算符的行为指定为“通配相等运算符(==?)和不相等运算符(!=?)将其右操作数给定位置的x和z值视为通配符。左操作数中的x和z值不视为通配符。”。因此,在通配符相等运算符中,只有右侧操作数中的x和z被视为通配符。如果x和z在左操作数中,则不会将它们视为通配符。这就是上面示例中模拟器给出不同结果的原因。

 

SystemVerilog定义了不同种类的相等(不相等)运算符。对于逻辑相等(不等式)运算符:==和!=,如果两个操作数中都有x或z位,则该关系不明确,则结果应为1位未知值(x)。对于情况相等(不相等)运算符:===和!==,两个操作数中任意位的x或z都应包括在比较中,并且应匹配以使结果相等,因此这些运算符的结果应始终是一个已知值,可以是1或0。对于通配符等于(不等于)运算符:==?和!= ?,如果x或z位在左操作数中,则如果这些位与右操作数中的通配符进行比较不等,则结果可以是x。

 

  • ‘b1和‘1

示例:

仿真结果如下:

‘b0 passed

‘b1 failed

‘bx passed

‘bz passed

为何‘b1没通过但’bx/‘bz通过了呢?

 

System Verilog 语言参考手册第5.7.1节提供了有关整型字符常量的规范:“如果包含s指示符,则用基数格式指定的数字应被视为带符号的整数;如果仅使用基数格式,则应将其视为无符号的整数。”,“组成无大小数字(unsized number)的位数(即一个简单的十进制数字或带有基本说明符但没有大小说明的数字)至少应为32。高位未知(X或x)或三态(Z或z)的无大小的无符号字符常量应扩展到包含字符常量的表达式的大小。”。根据上述规范,“’b1”是不带大小的无符号整数,其值为1,不等于{127 {1’b1}}。对于“’bx === {127 {1’bx}}”,模拟器需要将’bx扩展为127位(127’bx),因此这种情况下相等运算的最终结果为“ TRUE”。

 

以下是语言参考手册的示例:

在同一章节中,语言参考手册指定了另一种整型字符:“可以通过在单比特值前加上撇号(’)来指定未定大小的单比特值,但不带基数说明符。未定值的所有位都应设置为指定位的值。”,即“ 1”定义一个所有位均为“ 1”的值。在这种情况下,如果x为{127 {1’b1}},则(’1 === x)将为“ 真”。

 

以下是语言参考手册的示例:

 

  • 确定模块的端口类型、数据类型和方向

如果将模块端口声明为“input logic in1”,则“ in1”应该是变量端口(variable port)还是线网端口(net port)呢?如果将模块端口声明为“output logic out1”,则“out1”应该是变量端口还是线网端口呢?

 

在SystemVerilog中,模块端口的声明有4个属性:

      • 端口方向(port_direction):例如输入,输出,inout,ref等。
      • 端口类型(port_kind):可以是任何线网类型的关键字,也可以是含var的关键字,它特指端口是可变的。
      • 数据类型(data_type):例如逻辑,整数等
      • 端口名称(port_name)

在Verilog和System Verilog中,线网数据和变量数据的行为非常不同:

      • 线网可以通过原始输出或模块端口来写入来进行一次或多次连续赋值。多个驱动的结果值由线网类型的解析函数确定。线网不能被过程化赋值。如果端口一侧的线网由另一侧的变量驱动,则意味着可以连续赋值。强制声明可以覆盖线网的值。释放后,线网回到解析值。
      • 变量可以由一个或多个过程语句(包括过程连续赋值)进行写入。最后一次写入将确定该值。或者,可以通过一个连续赋值或一个端口写入变量。

因此,了解端口是线网还是变量是非常重要的。 System Verilog 语言参考守则在第23.2.2.3节中指定了确定端口类型,数据类型和方向的规则。如果忽略了端口类型:

      • 对于输入和输入端口,该端口应默认为线网,并赋予默认的线网类型。可以使用`default_nettype这条编译器指令来更改默认线网类型。
      • 对于输出端口,默认端口类型取决于数据类型是如何被定义的:
          • 如果省略或使用隐式数据类型语法声明数据类型,则端口类型应默认为线网,并赋予默认的线网类型。
          • 如果使用显式data_type语法声明了数据类型,则端口类型默认为变量。
      • 引用端口始终是变量。

根据这些规范,“input logic in1”将in1声明为线网端口,“output logic out1”将out1声明为变量端口。

 

  • 对象构造函数的调用顺序

示例:

模拟结果是:

parent: var1 is           0

child: myvar1 is          10

父类中的变量var1被赋值为0,而不是10(myvar1)。

 

父类中“ this.var1”的结果取决于子类中“ super.new(myvar1)”调用的执行顺序和myvar1的初始化(“ int myvar1 = 0”)。 System Verilog 语言参考手册第8.7节对类的构造函数方法进行了以下规范:“派生类的new方法应首先调用其基类构造函数[super.new(),如8.15中所述]。基类构造函数调用(如果有)完成后,应将类中定义的每个属性初始化为其显式默认值,或者如果未提供默认值,则将其初始化为未初始化的值。在属性初始化之后,应评估用户定义的构造函数中的其余代码。”根据此规范,以上代码中的执行顺序为:

1.super.new(myvar1)

2.myvar1 初始化为10

3.$display(“myvar1 is %d”,myvar1);

当super.new(myvar1)被调用时,myvar1尚未被初始化,所以其赋予整型变量的默认值(0)。

 

  • 使用无效索引访问数组

另一个非常常见的问题是:当用无效索引访问数组时,我应该期待模拟器产生运行错误吗? System Verilog 语言参考手册 7.4.6和11.5.1注明了以下规范:“如果索引表达式超出范围,或者索引表达式中的任何位是x或z,则该索引将无效。从具有无效索引的任何类型的未压缩数组中读取,应返回表7-1中指定的值。写入具有无效索引的数组将不执行任何操作,但写入对象为队列的第[$ + 1]个元素(在7.10.1中进行了描述)和创建关联数组的新元素(在7.8.6中进行了描述)时除外。”。 “部分选择寻址的位范围完全超出向量、压缩数组、压缩结构、参数或串联的地址范围,或者为x或z的部分选择在读取时将产生值x并且不会影响写入时存储的数据。部分超出范围的部分选择在读取时应返回x表示超出范围的位,并且在写入时仅会影响范围内的位。”根据这些规范,Verilog和System Verilog用户不能依靠模拟器来检查压缩和未压缩数组上的无效索引。

 

  1. 结论

本文讨论了一些System Verilog问题以及相关的SystemVerilog 语言参考手册规范。正确理解这些规格将有助于System Verilog用户避免意外的模拟结果。

 

 

参考文献

[1] Stuart Sutherland, Don Mills “Synthesizing SystemVerilog Busting the Myth that SystemVerilog is only for Verification”,  SNUG Silicon Valley 2013

[2] IEEE Standard for SystemVerilog— Unified Hardware Design, Specification, and Verification Language  IEEE Std 1800™-2012 (Revision of  IEEE Std 1800-2009)

 

 

原文来自:DVCon2017_USA, 点击阅读原文去路科官网下载DVCon2017论文合集,还有更多资料等你来哦!

http://rockeric.com/resource/paper/

扫描上图二维码可直达课程页面,马上试听

 

往期精彩:

路科发布| 稳中带涨!25w成芯片校招薪资平均底!2020应届秋招数据全面分析!

相约今晚8点 2020 IC秋招经验分享与数据分析

V2Pro 2020秋M1 我对你的迷惑和选择都深以为然

V2Pro春季班普遍学撑了,秋季班7月报名你还敢来么

UVM RAL模型:用法和应用

我们准备做第二期线下培训,依旧认真且严肃

如果你突然被裁员了,你的Plan B是什么?

[彩虹糖带你入门UVM]

理解UVM-1.2到IEEE1800.2的变化,掌握这3点就够

 

 

发表评论

邮箱地址不会被公开。 必填项已用*标注

陕ICP备18003383号-1