sv包的意义(package)

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

1 我们来看看这样一个实际的问题吧,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文件是一项好的选择。

2 接下来是关于定义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

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

你可能感兴趣的:(sv包的意义(package))