包的定义
如果两个模块(例如A、B模块)重名,就会有命名冲突的问题,尽管对于这两个同名模块,编译的时候不会出现错误 ,但是下一个编译的模块会覆盖上一个编译的模块。
在大型的验证项目中,很容易出现模块重名的情况。怎么处理?
- 对于重名的硬件模块:将它们置入到不同编译的库中。
- 对于重名的软件类、方法等:将它们置入到不同的包中。
包(package)的使用情况
1、我们可能使用不同的验证IP,我们也无法预测这些类名是否可能重名。 通过包(package)可以将关联的类和方法并入到同一个逻辑集合中。
2、为了使得可以在多个模块(硬件)或者类(软件)之间共享用户定义的类型,SV添加了包(package) 。 (该点与库的概念类似,但是我们无法在代码中容易的指出调用哪个库的类型。但是SV很容易支持调用哪个包的类型)
包的定义语法:
package
. . .
endpackage
用户自定义的类型譬如类、方法、变量、结构体、枚举类等都可以在包中定义。
示例:
这个包在编译时, 会将其中所有的类型一同编译到definition包中,而这个包最终被编译到某一个库中。
package definitions;
parameter VERSION = "1.1";
typedef enum {ADD,SUB,MUL] opcodes_t;
typedef struct {
logic [31:0] a, b;
opcodes_t opcode;
}instruction_t;
function automatic [31:0] multiplier(input[31:0] a, b);
return a * b;//抽象的乘法器
endfunction
endpackage
导出包的内容
module、interface、 class等可以使用包中定义或者声明的内容。
(1)导出包的方法 :可以通过域的索引符::号直接引用。
直接通过索引,使用definition包中的 参数、结构体类型。
definitions::parameter
definitions::instruction_t inst
这种使用方式,在代码较多时,较为繁琐,一般在使用直接引用方式时,是为了特意强调某些类型或者某些变量来自于某一个包。避免了重名,以及方便代码阅读
(2)指定索引一些需要的包中定义的类型到指定的域中。
先将类型导出到指定的类中,再加以使用。
使用import将需要的类型从包中导出到指定的域,以供代码中的类型识别和使用
module M;
import definitions::instruction_t;
instruction_t inst;
endmodule
(3)通过通配符*来将包中所有的类别导入到指定的域中。(该方法使用简便、易用)
如果包中定义的类型比较多,且需要导出的类型比较多时,通过通配符* ,将包中所有类型导出到指定的域中。
module M;
import definitions::*;
instruction_t inst;
endmodule
举例
(1)可以通过域的索引符::号直接引用。
module ALU(input definitions::instruction_t lw,
input logic clock,
output logic [31:0] result
);
always_ff @(posedge clock) begin
case (lw.opcode)
definitions::ADD : result = lW.a + lW.b;
definitions::SUB : result = lW.a - lW.b;
definitions::MUL : result =
definitions::multiplier(lW.a,lW.b);
endcase
end
endmodule
(2)可以指定索引一些需要的包中定义的类型到指定的域中。
从包中导入了枚举类型ADD SUB MUL,在下面就使用这些变量。
module ALU(...);
import definitions::ADD;
import definitions::SUB;
import definitions: :MUL;
import definitions::multiplier ;
always_comb begin
case (lW.opcode)
ADD : result = lW.a + lW.b;
SUB : result = lW.a - lW.b;
MUL : result = multiplier(lW.a,lW.b);
endcase
endmodule
(3)通过通配符*来将包中所有的类别导入到指定域中。
导入所有类型,只要在 ALU模块中使用任何包中的类型,都可以识别
module ALU ( ... ) ;
import definitions::*; //通配符导入always_comb begin
case (Iw.opcode)
ADD : result =Iw.a + Iw.b;
sUB : result =Iw.a - Iw.b;
MUL : result = multiplier (Iw.a,Iw.b) ;
endcase
end
endmodule
包的应用
在验证MCDT的环境中,包含两个来自channel和arbiter的模块验证包chnl_pkg和arb_pkg。
它们分别定义了同名的 stimulator、 monitor 、 chker 、 env类型。
然而这两个package中同名的类,它们的内容是不相同的,实现的也是不同的功能。
channel和arbiter的验证工程师package定义是这样的:
package chnl_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
由于我们将这些重名的类归属到不同的package中编译,这样如果要使用不同package中的同名类,只需要注明要使用哪一个package中的。
module mcdf_tb;
chnl_pkg::monitor mon1 = new() ;
arb_pkg::monitor mon2 = new() ;
endmodule
//在mcdf_tb模块中,monitor分别来自 chnl_pkg和arb_pkg
//尽管它们同名,但是借助于将它们集合在不同的包内,这避免了类型名冲突的问题
包的命名规则
尽管之前的方法可以解决类型名冲突的问题,但是由于类名本身有冲突,使得在引用类的时候,不得不通过直接索引的方式。这在从两个包中 导出多个类型的时候,不方便。
在实际代码中建议,包中的类型名称带有包名的前缀 。
#更新前的代码
package chnl_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
#包中的类型名称带有包名的前缀
#更新后的代码
package chnl _pkg;
'include "chnl_stm.sv"
'include "chnl_mon.sv"
'include "chnl_chk.sv"
'include "chnl_env.sv"
endpackage
package arb_pkg;
'include "arb_stm.sv"
'include "arb_mon.sv"
'include "arb_chk.sv"
'include "arb_env.sv"
endpackage
#引用两个monitor时,就不用担心类名冲突的问题
#通过通配符* ,将包中所有类型导出到指定的域中。使用更简便
module mcdf_tb;
import chnl_pkg::* ;
import arb_pkg:: *;
chnl_mon mon1= new () ;
arb_mon mon2 = new () ;
endmodule
包与库的区分
从这个简单的例子来看,package这个容器可以对类型做一个隔离的作用。
package的意义在于将软件(类、类型、方法等)封装在不同的域中,以此来与全局的域进行隔离。
库是编译的产物,硬件(module、interface、program)都会编译到库中,如果不指定编译库的话,会被编译进入默认的库中。
库可以容纳硬件类型,也可以容纳软件类型,例如类、方法和包。
包只能容纳软件类型例如类、方法和参数。