$unit编译单元声明
SystemVerilog含有编译单元。
相比Verilog,SystemVerilog增加了编译单元的概念。编译单元是同时编译的所有源文件。编译单元为软件工具提供了一种对整个设计子块单独编译的方法。一个子块可能包含第一个或多个模块(module)。这下模块可能包含在一个或多个文件中。设计的子块可能还包含接口块和测试程序块。
编译单元域包含外部声明。
SystemVerilog允许在包、模块、接口和程序块的外部进行声明扩展Verilog的声明域。这些外部声明在“编译单元域”中,并且对所有同时编译的模块都是可见的。
编译单元域可以包含:
(1)时间单元和精度声明
(2)变量声明
(3)net声明
(4)常量声明
(5)用户定义数据类型,使用typedef、enum、或class
(6)任务和函数定义
编译单元域中的外部声明(不可综合)
///外部声明/
parameter VERSION = "1.2a"; //外部常量
reg resetN = 1; //外部变量(低有效)
typedef struct packes{ //外部用户定义类型
reg [31:0] address;
reg [31:0] data;
reg [31:0] opcode;
}instruction_word_t;
function automatic int log2 (input int n);//外部函数
if (n <= 1) return (1);
log2 = 0;
while( n > 1)
begin
n = n/2;
log2++;
end
return(log2);
endfunction
/模块定义
//用外部声明定义端口类型
module register(
output instruction_word_t q,
input instruction_word_t d,
input wire clock
);
always @(posedge clock ,negedge resetN)
if(!resetN) q <= 0;//使用外部复位
else q <= d;
endmodule
注意:外部编译单元域声明不是全局的
编译单元域中的声明与全局声明不一样。真正的全局声明,如全局变量或函数,不管源文件是单独还是同时编译,都会被设计的所有模块共享。
SystemVerilog的编译单元域只作用于同时编译的源文件。每次编译源文件,就创建一个唯一仅针对此次编译的编译单元域。例如,如果模块CPU和模块controller都引用外部声明的变量regset,那么就可能存才两种假设:
(1)如果两个模块同时编译,将只要一个编译单元域。外部声明的reset变量将被这两个模块共用。
(2)如果两个模块分别编译,将会有两个编译单元域,可能有两个不同的reset变量。
在后一种假设下,包含外部reset声明的编译看上去没有问题。另一个文件在单独编译时会有自己唯一的$unit编译域,不会看到前一个编译中的reset声明。取决于reset使用的环境,第二个编译可能会由于未声明变量而失败,也可能使reset成为隐式net而编译成功。这种可能性是很危险的。如果第二个编译通过使reset成为隐式net而成功,那么现在就会有两个叫做reset的信号,每个编译中一个。这两个不同的reset信号用什么方式都不能连在一起.
$unit只能用于导入包
(1)不要在 $unit空间进行任何声明!所有的声明都要在命名包内进行。
(2)必要时可以将包导入到 $unit中。这在模块或接口的多个端口使用用户自定义类型,而这个类型定义又在包中时非常有用。
在 $unit编译单元域中直接声明对象会在文件单独编译时导致设计出错。如果声明分散在多个文件中,还会产生spaghetti代码,难以维护、重用或调试声明错位。(spaghetti代码指结构混乱、逻辑性差、难于调试的代码)
SystemVerilog标识符搜索规则
编译单元域中的声明可以在组成编译单元的模块的任何层次引用。
SystemVerilog定义了简单的搜索规则来引用标识符:
(1)搜索那些按IEEE 1364Verilog标准定义的局部声明
(2)搜索统配符导入到当前作用域的包中的声明
(3)搜索编译单元域中是声明
(4)搜索设计层次中的声明,遵循IEEE1364Verilog搜索规则
注意:数据标识符和类型标识符必须在引用前声明
未声明的标识符隐为net类型
外部声明必须在使用前定义
编译单元域中的变量和net
当使用外部声明时有一点需要着重考虑。Verilog支持隐式声明,即在特定环境下,为声明的标识符假定为net类型(通常为wire类型)。当从上下文不能推断出隐式类型或者希望使用默认net类型以外的类型时,Verilog要求在标识符引用之前要显式声明标识符类型。
隐式类型声明规则影响编译单元域中的变量和net声明。软件工具必须在标识符引用之前找到外部声明。否则,这个名称将被看做未声明的标识符并遵循Verilog隐式类型规则。
module parity_gen(input wire [63:0] data);
assign parity = ^data; //parity是一个隐式局部net
endmodule
reg parity;//因为声明在被parity_gen引用之后出现,因此外部声明没被模块parity_gen使用
module parity_check(input wire [63:0] data
output logic err);
assign err = (^data != parity);//parity是$unit变量
endmodule
将包导入$unit的编码原则
SystemVerilog允许将模块端口声明为用户定义类型。
module ALU
( input definitions::instruction_t IW,
input logic clock,
output logic[31:0] result
);
当许多模块端口都是用户自定义类型时,像上面那样显式地引用包中就会显得繁琐了。一种可选择的风格是在模块声明之前将包导入到$unit编译单元域中。
//将包中特定子项导入到$unit中
import definitions::instruction_t;
module ALU
( input definitions::instruction_t IW,
input logic clock,
output logic[31:0] result
);
包还可以通过通配符导入到$unit域中。注意通配符导入实际上不能导入包中所有子项。它只是将包加到SystemVerilog源路径中。
//将包中特定子项导入到$unit中
import definitions::*;
module ALU
( input definitions::instruction_t IW,
input logic clock,
output logic[31:0] result
);
使用单独编译将包导入到$unit中
文件顺序编译依赖性
当子项从包中导入(包中特定子项导入或者是通配符导入)时,import语句必须出现在包中子项被引用之前。如果包导入语句与模块或接口不在同一文件中,而模块和接口文件又要引用包中子项,那么包含import文件必须在文件比编译顺序中首先列出来。如果文件顺序不正确,模块或接口的编译要么失败,要么因看不到包中子项而错误地指定为隐式net。
多文件编译与单文件编译
可以读入Verilog和SystemVerilog源代码的综合编译器、代码检测器、一些仿真器和其他可能的工具通常可以一次编译一个或多个文件。当多个文件同时进行编译时,只要一个$unit域。包导入(不管是包中特定子项导入或者是通配符导入)到 $unit域中使包中子项对导入语之后读入的所有模块和接口都可见。但是,如果这些文件单独编译,将会产生多个不同的 $unit编译单元。一个 $unit中导入的包对另一个 $unit是不可见的。
在每个文件中使用导入语句
对于将包中子项导入到 $unit编译单元域中出现的这两个问题,解决办法就是将import语句放到每个文件模块或接口定义之前。这个解决方法在每个文件单独编译时很奏效。但是,当多个文件同时进行编译时,还要当心。将同样的包中子项多次导入到同一 $unit域中是非法的。(这与在同一名称空间中两次声明同一变量一样是非法的)
用 $unit包导入的条件编译
可以使用C语言常用的编程技巧,使我们在单个文件编译和多个文件编译时都可以将包中子项导入到 $unit域中。这个技巧是利用条件编译,使第一次遇到导入语句时将其编译到 $unit中,而再次出现则不编译这些语句。为了说明导入语句在当前 $unit域中是否已经编译完,在导入语句第一次编译时设置`define标志。
带条件编译的包
`ifndef DFFS_DONE //如果已编译标志没设置
`define DFFS_DONE //设置该标志
package definitions;
paramter 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);
//用户定义的32位乘法
return a*b; //抽象乘法(无错误检测)
endfunction
endpackage
import definitions::*; //将包导入到 $unit中
`endif
每个需要包中定义的设计或测试平台都有应该将
`include "definitions.pkg"
放在文件的开始。当设计或测试平台文件被编译时,都会将包和导入语包含进去。definitions.pkg文件中的条件编译将保证如果包还没被编译和导入,就去完成这两个任务。如果包已经编译并导入到当前 $unit域中,该文件的编译将被忽略。
注意:对于这种编码风格,包文件应该用`include编译命令间接传递给软件工具编译器。
这种条件编译风格使用Verilog的`include命令把definitions.pkg文件当作其他文件的一部分进行编译。这样做时为了保证definitions.pkg文件末尾的导入语将包导入到设计或测试文件,编译正在使用的同一 $unit域中。如果definitions.pkg文件直接出现在软件工具编译器的命令行中,包和导入语句就会被编译到另一个 $unit空间,而不是设计或测试平台块正在使用 $unit域中。
//包含条件编译包文件的设计文件
`include "definitions.pkg" //编译包文件
module ALU
( input definitions::instruction_t IW,
input logic clock,
output logic[31:0] result
);
always_ff @(posedge clock)
begin
case(IW.opcode)
definitions::ADD : result = IW.a + IW.b;
definitions::SUB : result = IW.a - IW.b;
definitions::MUL : result = definitions::multiplier(IW.a,IW.b);
endcase
end
endmodule
//包含条件编译包文件的测试平台文件
`include "definitions.pkg" //编译包文件
module test;
instruction_t test_word;
logic [31:0] alu_out;
logic clock = 0;
ALU dut (
.IW(test_word),
.clock(clock ),
. result(alu_out)
);
always #10 clock = ~clock;
initial
begin
@(posedge clock)
test_word.a = 5;
test_word.b =7;
test_word.opcode = ADD;
@(negedge clock)
$display ("alu_out = %d (expected 12)", alu_out);
$finish;
end
endmodule
`include对单文件和多文件编译
进行单文件编译时,包将被编译并导入到每个 $unit编译单元中。这就保证了每个 $unit都能看到相同的包中子项。由于每个 $unit都是唯一的,不会在多次编译包的过程中出现名称冲突。
进行多文件编译时,条件保证包只进行一次编译并导入到所有模块的公共 $unit编译域中。不管设计或测试平台文件那个先编译,都会导入包,保证包中子项对所有文件都是可见的
包中变量是共享变量(不可综合)
包中可以包含变量声明。包中变量为所有导入变量的设计块(和测试块)共享。包中变量的行为在单文件编译和多文件编译中截然不同。在多文件编译中,包被导入到同一 $unit编译域中。每个设计块或测试块将看到相同的包中变量。一个块写到包中变量的值将对其他所有块可见。在单文件编译中,每个 $unit域都有一个唯一的变量,它恰好与另一个不同的 $unit域中的变量同名。一个设计块或测试块对包中变量写入的值对其他设计或测试块不可见。
包中的静态任务和函数是不可综合
静态任务和函数或者含静态储存的自动任务和函数,也具有同样的潜在问题。在多文件编译中,只有一个 $unit域,它将导入任务和函数的一个实例。任务或函数中的静态储存对所有设计和验证块都是可见的。在单文件编译中,每一个独立的 $unit导入的任务或函数实例不同。任务或函数的静态储存不会在设计和测试块中共享。
这个关于条件编译导入语句到 $unit中的限制在可综合的模型中没有问题,因为综合不支持包中的变量声明、静态任务和函数。
使用包而不用 $unit是更好的编码风格
可在编译单元域(所有模块和接口定义的外部)中声明的可综合结构有
(1)typedef用户定义类型定义
(2)自动函数
(3)自动任务
(4)parameter和localparam常量
(5)包导入
虽然在编译单元域定义用户定义类型不是一种好的风格,但它可综合的。更好的风格是吧用户定义类型的定义放在有名称的包中,这可以降低出现spaghetti代码和文件顺序依懒性的风险。
外部任务和函数必须是自动的
在 $unit编译单元域中声明任务和函数也不是一种好的编码风格。但是,在 $unit中定义的任务和函数是可综合的。当模块引用编译单元域中定义的任务和函数时,综合将复制该任务或函数代码并把它看成在模块中定义的一样。为了综合,编译单元域中定义的任务和函数必须声明为automatic,并且不能包含静态变量。这还是因为自动任何和函数的储存区在每次调用时才会实际分配。因此,每个模块引用的自动任务或函数的引用的综合前仿真行为与综合后行为相同(综合后任务或函数的功能已经在模块内实现了)。
编译单元域中定义的parameter常量不能重新定义,因为它不是模块实例的一部分。综合会将编译单元域中声明的常量看作文本值。在 $unit域中声明参数不是一种好的建模风格,因为常量声明文件与模板文件分开编译时,这些常量对模块是不可见的。