从这一部分开始我们将进入SystemVerilog的语言学习和应用。
在进入SV(SystemVerilog)之前,如果读者已经学习过Verilog语言,那么对我们接下来的的从Verilog到SV过渡的部分会容易一些;如果读者之前也没有接触过Verilog语言,也不需要担心。我们对于SV的三个篇章将会带你在学习完这三章之后,懂得如何搭建测试平台、以及掌握SV的核心语法、产生测试场景和完成数据比对。
之所以在Verilog的基础上扩展出新的语言SV,为的是构建一种专用的验证语言,而之前的硬件设计描述语言Verilog和VHDL并不具备如今SV在验证方面的语言特性优势,这些优势包括有:
-
抽象的数据结构描述可方便于更高层面的验证需要。
-
面向对象的软件编程方式提供了更好地模块性、封装性和复用性。
-
将用于验证部分的语言属性用完全软件化的构建方式实现,使得验证一侧独立于设计一侧。
-
约束化随机激励可提高递归测试的收益。
-
功能覆盖率收集可量化功能验证点使得验证进度更易于反映。
-
为属性检查提供专用分支语言属性。
上述的优点我们也将会在接下来的SystemVerilog围绕着DUT——MCDF的验证实例展开:
-
SV的环境构建篇
-
SV的组件实现篇
-
SV的系统集成篇
在构思SV的语言介绍过程中,我们认真翻阅了现有的中英文资料和教材,所见到的书籍介绍语言的方式都主要将编程语言作为一门学科进行语法上面的分类。这种方式对于初学者的入门会有帮助,而在具体实践的环节,好多书籍又无法给读者一个全面的轮廓和一种循序渐进的直观感受。所以,本书选择了在SV的介绍部分一直围绕着验证环境建立和实现的具体问题,将核心语言结合着具体场景,让读者可以边学边掌握该语法、特性可以运用的场景。最后在读者学完SV部分之后,可以利用所学的语言知识自助构建出验证平台。
我们在介绍语言的过程中,语言的语法特点仅仅是一部分,更重要地在于使得读者可以懂得该语言特性被应用的场景,实际疑难点在什么地方。因为入门容易精通难,如果读者手头已经有一本趁手的SV学习入门资料,且将本书关于SV的部分作为SV的进阶应用资料。
相比于Verilog将寄存器(register))类型reg和线网(nets)类型例如wire区分地如此清楚,在SV中新引入了一个数据类型logic。它们的区分和联系在于:
-
Verilog作为硬件描述语言,倾向于设计人员自身懂得所描述的电路中哪些变量应该被实现为寄存器,而哪些变量应该被实现为线网类型。这不但有利于后端综合工具,也更便于阅读和理解。
-
SV作为侧重于验证的语言,并不十分关切logic对应的逻辑应该被综合为寄存器还是线网,因为logic被使用的场景如果是验证环境,那么它只会作为单纯的变量进行赋值操作,而这些变量也只属于软件环境构建。
-
logic被推出的另外一个原因也是为了方便验证人员驱动和连接硬件模块、而省去考虑究竟该使用reg还是wire的精力。这既节省了时间,也避免了出错的可能。
与logic相对应的是bit类型,它们均可以构建矢量类型(vector),而它们的区别在于:
-
logic为四值逻辑,即可以表示0、1、X、Z。
-
bit为二值逻辑,只可以表示0和1。
那么SV为什么在已经有了四值逻辑的基础上还要再引入二值逻辑呢?那就是SV在一开始设计的时候,就期望将硬件的世界与软件的世界分离开。在这里,硬件的世界值得就是硬件设计,所以四值逻辑属于那里,而软件的世界即验证环境,更多的是二值逻辑。所以,有了二值逻辑,验证环境在进行数据运算的时候不但会提高效率,而且还会省去其它不必要考虑的问题。
在这里,我们将四值逻辑的类型和二值逻辑的类型分别摘列出来,请读者在使用时务必注意:
-
四值逻辑类型:integer、reg、logic、reg、net-type(例如wire、tri)
-
二值逻辑类型:byte、shortint、int、longint、bit
而通过logic和bit来声明的矢量均为无符号(unsigned)变量,例如:
可以从仿真器中得到的结果是:
如果按照有符号和无符号的类型进行划分,那么可以将常见的变量类型划分为:
-
有符号类型:byte、shortint、int、longint、integer
-
无符号类型:bit、logic、reg、net-type(例如wire、tri)
所以,读者在遇到常见的这些变量类型时,应该注意它们的逻辑类型和符号类型,因为在变量运算中,应该尽量避免两种不一致的变量进行操作,而导致意外的错误。
譬如从下面的例子里,我们可以看到有符号变量和无符号变量混用的运算结果会出乎意料:
仿真输出结果为:
我们这里来分析一下:
-
一开始 signed_vec 被赋值为 8'b1000_0000,表达为有符号十进制数值为-128。
-
在第一次赋值操作时 result_vec = signed_vec,右侧的有符号数值-128被赋值到左侧,并且需要从8位扩展为9位,且保证有符号数值不变的情况下,首先需要将8'h80扩展为9'h180(均为-128),进而在赋值到左侧。
-
在第二次赋值操作时,我们首先进行了类型转换操作 unsigned'(signed_vec),则转换结果应为十进制数值128,所以在赋值操作以后result_vec = unsigned'(signed_vec),result_vec同signed_vec就比特位的数值没有发生变化,但是实际表达的十进制数值则从-128被赋值为128。
所以,通过上面的例子我们可以发现,在编码时一定要注意操作符左右两侧的符号类型是否一致,如果不一致,应该首先将其转换为同一类型再进行运算。
对于转换方式,我们已经上面已经展示了一种转换方式——静态转换,即需要在转换的表达式前加上单引号即可,而该方式并不会对转换值做检查。如果发生转换失败,我们也无从得知,所以与之对应的动态转换$cast(tgt, src)也被经常运用到转换操作中。静态转换和动态转换均需要操作符号或者系统函数介入,我们统称为显式转换。动态转换的具体使用方法我们会在随后关于类和对象的章节着重介绍。
而不需要进行转换的一些操作,我们称之为隐式转换,例如下面的例子:
仿真结果为:
不难发现有两个问题:
-
被转换的变量为四值逻辑矢量,而被赋值的变量为二值逻辑矢量,且位宽不同。
-
在隐式转换中,x_vec[2:0]被保留下来,x_vec[3]则被丢弃,同时x_vec[0]的值'x'在转换过程中被转换为0值,即赋值的最终结果为b_vec = 'b110。
从上面的示例中,我们在不同数据类型进行操作时应该注意变量的:
-
逻辑数值类型
-
符号类型
-
矢量位宽
我们下一节将带大家进入模块定义和例化的实际应用。