路科验证的个人空间 https://blog.eetop.cn/rockeric [收藏] [复制] [分享] [RSS]

空间首页 动态 记录 日志 相册 主题 分享 留言板 个人资料

日志

SV系统集成篇之一:包的意义

热度 1已有 6031 次阅读| 2017-5-22 23:51 |个人分类:验证系统思想|系统分类:芯片设计

从上一篇《SV组件实现篇》来看,verifier梅、尤、娄和董四位验证师已经做完了模块验证,进而转向了验证环境的集成和验证环节。由于verifier董负责验证环境的持续集成,路桑跟他做了一次交谈,问了问这位新手面临哪些困境。
路桑: 董亲,大家的模块验证环境都已经准备好了是吗?
Verifier董:是啊。准备倒是准备好了,不过每个人的组件名称都有不小的区别,模块环境也是“各抒己见”。
路桑:那你准备怎么整合一下它们呢?
Verifier董:我打算先用package将每个模块自属的验证环境集合到一起,通过package来区分不同的模块。
路桑:这看起来是在装修大房子之前的清洁步骤,先把各种材料都放整齐啊。
Verifier董:是啊,接下来就是从不同的材料包里挑拣有用的出来,布置这个大的验证房间了。
路桑:看起来,你也发现有一些材料用不到是吗?
Verifier董:那当然,比如arbiter的stimulator就用不到啊,因为它已经在MCDF集成中了,它的上行数据是从slave流出来的。
路桑:恩,有见解。那这么多不同的材料你打算一股脑儿地堆在一起,亲自料理,还是它们事先已经有了封装好的环境,你可以做更高层的集成?
Verifier董:我倒是希望是这样的,不过其它verifier有的喜欢将stimulator、monitor和checker放在一个环境environment中做testbench顶层的例化,有的则喜欢直接在testbench中对这三个对象进行例化。
路桑:那从你的观点来看,你更喜欢其他人给你提供哪一种呢?
Verifier董:我倾向于第一种,就是给我一个整合好的环境,我只需要做顶层集成,那要轻松一些。对于有的子环境中,如果不需要stimulator,那我可以通过顶层的配置,忽略它的例化。
路桑:董亲,你提到的“配置”是想做什么?
Verifier董:哦,就是在顶部环境做出合适的配置,来决定验证环境的层次啊。这样,我就不需要修改模块environment中的代码,保持它们的封闭性,不修改它们的源代码,而只需要在我顶层的环境中做出新的配置,就可以简单地配置验证结构了。
路桑:蛮有想法的。那你一旦对环境做出了合适的配置,你还需要考虑什么?
Verifier董:应该就剩下如何产生激励了吧。除了我们需要实现考虑MCDF在子系统级别的验证功能点之后,就需要考虑怎么产生合适的激励。有了贴合的场景,才能用来检查是否有相应的测试场景产生啊。
路桑:不过想想你要面对register、三个slave还有一个formatter的stimulator,怎么能把这个“小乐队”指挥好,还是考验你的协调能力啊。
Verifier董:没错。我认为,验证计划就同一个琴谱,只有心中有谱,才可能组织这么多的stimulator最终奏出动听的音乐啊。
路桑:看起来你已经胸有成竹了呢。
Verifier董:别这么说,路桑,实际上我对于生成的激励能不能测试到相应的功能点还是心中打鼓呢……
路桑:哦,看起来,你需要一个可以量化的东西来增强你对验证质量的信心啊。


从上面的谈话,看得出来verifier董对于怎么集成验证环境已经有了初步的想法,他在对话中也已经点明了集成验证环境需要注意的几个问题:
  • 相关类的集合
  • 验证环境的组装
  • 激励场景的调度生成
  • 环境结构的配置

这几个问题我们也将在接下来的章节中一一作出论述,带领大家跟随verifier董的步伐,看看他一步步实现验证环境的思路和如何克服遇到的问题。只是在上面谈话的最后结束部分,verifier董面对验证质量还有一丝疑虑,因为作为一名工程师,他需要牢固的数据来表明验证计划中列出的功能点确实被验证到了。而这一疑惑,也将是我们接下来的《SV量化验证篇》的核心内容。



SV语言提供了一种在多个module、interface和program之中共享parameter、data、type、task、function、class等等的方法,即利用package(包)的方式来实现。如果用上面装修一个大房子(MCDF testbench)来看的话,我们喜欢将不同模块的类定义归整到不同的package中。这么做的好处在于将同一簇相关的类组织在了单一的名字空间(namespace)下,使得分属于不同模块验证环境的类首先来自于不同的package,这样可以通过package来区别类的归属问题。

我们来看看这样一个实际的问题吧,register和arbiter的verifier给自己的package定义是这样的。

package regs_pkg;
`include "stimulator.sv"
`include "monitor.sv"
`include "chker.sv"
`include "env.sv"
endpackage

package arb_pkg;
`include "stimulator.sv"
`include "monitor.sv"
`include "chker.sv"
`include "env.sv"
endpackage

两位verifier在各自的package regs_pkg和arb_pkg中都定义了4个与模块验证相关的类即stimulator、monitor、checker和env。而在这两个package中同名的类,实际上内容是不相同的,实现的也是不同的功能。如果我们将这些重名的类归属到不同的package中编译,有没有问题呢?会不会发生重名的编译冲突?读者不需要为此担心,package允许你做的,就是将单一的全局名字空间分隔开来,这样如果要使用不同package中的同名类,他们需要强调要使用哪一个package中的。譬如下面这个例子:

module mcdf_tb;
regs_pkg::monitor mon1 = new();
arb_pkg::monitor mon2 = new();
endmodule

尽管在regs_pkg和arb_pkg中都存在着一个名字为monitor的类,我们可以在引用类名的时候通过域名索引“::”操作符的方式来显式指出所引用的类monitor具体来自于哪一个package,这样便能很好地通过不同名的package来管理同名的类。从这个简单的例子来看,package这个容器可以对类名做一个隔离的作用。那么,有的读者可能会在pakcage(包)和library(库)之间有混淆,我们来看看它们的联系和区别
  • package更多的意义在于将软件(类、类型、方法等)封装在不同的命名空间中,以此来与全局的命名空间进行隔离。package需要额外定义,容纳各种数据、方法和类。
  • library是编译的产物,在没有介绍软件之前,硬件(module、interface、program)都会编译到库中,如果不指定编译库的话,会被编译进入默认的库中。从容纳的类型来看,库既可以容纳硬件类型,也可以容纳软件类型,例如类和方法,也包括package。
  • 从上面的联系来看,不同package之间可能存在着同名的类,不同的library之间也可能存在着同名的module(硬件)或者package(软件)。如果像上面的例子,遇到了不同package中同名的类,那么用户可以通过“::”来显式调用具体哪个package中的类;如果遇到的是不同library中,有同名的module或者package等,那么应该怎么解决呢?常见的方式是通过hdl语言的config文件(VHDL、verilog、SV均有该特性)来指定具体模块从哪个library中选取。默认情况下,采取的是“就近原则”,即调用该重名模块的上层模块位于哪个library,则仿真器会优先从该library中寻找该模块。这个原则,对于package也是适用的,即使用该重名package的module或者interface会优先寻找它们所在的library。如果用户想让其优先搜寻非默认的库,那么需要在config文件中指定寻找的库名和顺序。
  • 在编译package时,需要注意的是,不应该有同名的package存在。那么,当类和方法并没有被封装在某个package时,它们会被编译到哪儿去了呢?实际上,仿真器仍然会将它们编译到默认的package中。但是,只有与该文件放置在一起的module才能识别和引用到它,至于该文件之外的module要想办法引用到这些类和方法,则没有什么好的办法了。所以,如果像让你的类和方法被更多的人使用,更方便的共享,一个基本方式就是首先将它们组织到一个package中
  • 此外,对于编译的module、interface和package这些硬件和软件,会进入哪一个library呢?如果没有额外的指定,它们都会被编译到默认library(work)中。因此,在默认库中,各个module是互相认识的,当然module也认识同一个library中的package。如果要使用其他library中的module或者package,那么一个config文件是一项好的选择。

接下来是关于定义package的一些好的建议
  • 在创建package的时候,已经在指定包名称的时候隐含地指定了包的默认路径,即包文件所在的路径。如果有其它要被包含在包内的文件在默认路径之外,需要在编译包的时候加上额外制定的搜寻路径选项“+incdir+PATH”。
  • 如果遵循package的命名习惯,不但我们要求定义的package名称独一无二,其内部定义的类也应该尽可能地独一无二。例如,上面的例子中,regs_pkg和arb_pkg中有同名的类,这些类如果携带类名的前缀,那么后面的处理会变得更容易一些。从下面这个例子可以发现,如果不同package中定义的类名也不相同时,在顶层的引用也可以通过“import pkg_name::*”的形式,来表示在module mcdf_tb中引用的类如果在当前域(mcdf内部)中没有定义的话,会搜寻regs_pkg和arb_pkg中定义的所有类型,又由于它们各自包含的类名不相同,因此也无需担心下面的搜寻会遇到同名类发生冲突的问题。
package regs_pkg;
`include "regs_stm.sv"
`include "regs_mon.sv"
`include "regs_chk.sv"
`include "regs_env.sv"
endpackage

package arb_pkg;
`include "arb_stm.sv"
`include "arb_mon.sv"
`include "arb_chk.sv"
`include "arb_env.sv"
endpackage

module mcdf_tb;
import regs_pkg::*;
import arb_pkg::*;
regs_mon mon1= new();
arb_mon mon2 = new();
endmodule

本文最后的部分是关于使用package的一些注意事项
  • 用户可以在module、interface或者program中来引用package
  • 如果是”import pkg_name::*“,则代表的是该package中定义的类型可能会在module等内部有效可见。只有当module等无法在内部索引到正确地类型时,才会转而去package中去搜寻,如果索引到了那么该package中的这个类型则变得在module中可见。

package a_pkg;
class mon;
endclass
endpackage

module module1;
class mon;
endclass
import a_pkg::*;
mon mon1 = new(); // 已经有内部mon定义,因此不会搜寻a_pkg
endmodule

  • 如果用户使用了“import pkg_name::type_name”,则表示直接让package_name::type_name类型在module等内部变为可见,那么此时需要注意的是,module内部不应该再有其它同名的类型定义,避免发生同名类型定义的冲突。

package a_pkg;
class mon;
endclass
endpackage

module module1;
class mon;
endclass
import a_pkg::mon;
mon mon1 = new(); // 同时有内部mon定义和引入a_pkg::mon,发生同名类型冲突
endmodule

  • 如果在package a_pkg中import了package b_pkg::type_b,那么在module1中import a_pkg::*时,无法引用到type_b。因为a_pkg只是使得b_pkg::type_b在a_pkg域中可见并加以使用,并未定义在a_pkg中。所以,用户需要牢记一点的是,import操作使得类型可见的域只是调用该import时当前的域。例如下面的例子中,a_pkg中可见b_pkg::b_mon,但是module1则无法可见a_pkg::b_mon。
package b_pkg;
class b_mon;
endclass
endpackage

package a_pkg;
import b_pkg::b_mon;
class a_mon;
endclass
endpackage

module module1;
import a_pkg::*;
a_mon mon1 = new(); // a_mon可见
b_mon mon2 = new(); // b_mon不可见
endmodule
  • 要解决上面的问题,用户可以使用export来让b_mon在a_pkg中得到二次定义。从下面这个例子中可以发现,a_pkg中需要额外使用export来让b_pkg::b_mon在a_pkg得到定义。因此,在module1中import a_pkg::*,可以搜寻到a_pkg中的a_mon和b_mon两种类型。
package b_pkg;
class b_mon;
endclass
endpackage

package a_pkg;
import b_pkg::b_mon;
export b_pkg::b_mon;
class a_mon;
endclass
endpackage

module module1;
import a_pkg::*;
a_mon mon1 = new();
b_mon mon2 = new();
endmodule
  • 用户使用到的系统函数和任务,例如$stop()、$randomize()等等凡是带有“$”符号的方法,另外一种调用的方式是std::method,例如std::randomize()。这隐含地是所有的系统方法都是预定义在一个称之为std包中的。用户只能使用这些包内的方法和类型,无法二次对std包做出修改和添加。


至此,verifier董已经将建筑材料都归纳到不同的package中,准备看起来有条不紊。到了下一节,verifier董将考虑如何搭建MCDF的子系统验证环境,我们一起再来看看他会遇到哪些问题,又是如何去解决的呢?

谢谢你对路科验证的关注,也欢迎你分享和转发真正的技术价值,你的支持是我们保持前行的动力。

1

点赞

刚表态过的朋友 (1 人)

评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 注册

  • 关注TA
  • 加好友
  • 联系TA
  • 6

    周排名
  • 14

    月排名
  • 0

    总排名
  • 0

    关注
  • 274

    粉丝
  • 25

    好友
  • 41

    获赞
  • 45

    评论
  • 33753

    访问数
关闭

站长推荐 上一条 /2 下一条


小黑屋| 手机版| 关于我们| 联系我们| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-12-26 10:40 , Processed in 0.015932 second(s), 8 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
返回顶部