笔记一
本文没有任何有价值的信息,仅用于个人笔记
1 sv介绍
sv创造了一种新的工程语言————硬件描述及验证语言(hardware description and verification language,HDVL)
1.1 verilog三代单传
- 第一代:verilog-1995
- 第二代:verilog-2001
- 第三代:system verilog,所以sv是从3.0版本开始的,用于强调它是第三代verilog
2 sv变量声明的位置
verilog中一般只在module内声明变量,module间可以通过端口进行数据通信,通过设置参数可以增加module的复用性,sv通过几种方式扩展了verilog的声明空间。
- 包定义以及从包中导入定义
- $unit编译声明空间
- 未命名块中的声明
- 增强的时间单位定义
2.1 包(package)
verilog中,变量、net、任务、函数的声明都必须在模块内部(即局部声明),verilog也可以在命名块、任务、函数中定义局部变量,但是这些变量仍在模块内,仍然是局部的,外部想引用这些模块内的对象,可以通过(一般是在验证时)通过层次调用,但是这种引用是软件方式,并不代表硬件行为,是不可综合的。
verilog不能进行全局声明,如果一个函数在多个模块中使用,必须在每个块中都进行声明,这种冗余的声明,会给代码的后期维护带来困难,虽然可以通过include引入文件的方法避免重复声明,但是仍会产生编码错误和设计维护问题。
总结一下就是,对于可以只声明一次可以重复使用的对象,由于verilog只能进行局部定义,导致在每个模块中都要讲用到的对象重新定义一遍,这种冗余的局部定义效率是极低的。
2.1.1 包的定义
包的引入解决了上述问题,sv在package和endpackage之间定义包的内容,包中可包含的可综合结构有:
- parameter和localparam常量
- const变量
- typedef用户定义类型
- 全自动(automatic)task和function定义
- 从其他包中导入(import)的定义
- 操作符重载定义
包中还可以进行全局变量声明、静态任务、静态函数,但是这些都不可综合。
包的定义不需要在module中,下面是一个包定义的例子:
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
包中可以定义parameter(verilog常量)、localparam(verilog常量)、const(sv常量),在verilog中,每个实例可以对parameter常量重新定义赋值(端口参数的修改),但是不能对localparam重定义(localparam只用于模块内部使用)。但在sv中,无论是parameter和localparam都不能被实例重新定义,因为他们不是模块实例的一部分。(只有使用权没有所有权)
2.1.2 引用包的内容
模块和接口可以用四种方式引用包中的定义和声明:
- 用范围解析操作符"::"直接引用
- 将包中特定子项导入(import)到模块或接口中
- 用通配符"*"导入包中子项到模块或接口中
- 将包中子项导入到$unit声明域中
使用作用域解析操作符引用包的子项
在模块中通过<包名>::<包内子项名>的方式直接引用
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
需要注意的是,对于枚举类型的引用,需要显示引用枚举变量中的元素,definitions::opcodes_t或definitions::opcodes_t.ADD的方式都是错误的。
显示引用包中的内容有助于提高代码可读性,但是如果引用次数过多就显得很麻烦,我们更希望将包中的内容一次引入,然后长期使用,而不是每用一次就需要引用一次。
导入(import)包中特定子项
当使用import语句将包中特定子项导入到模块中时,这些特定子项在模块内部就是可见的,好像就是模块内的局部变量一样,这样在使用这些特定子项时就不需要显示引用包名了
module ALU (
input definitions::instruction_t IW,//端口列表仍然需要显示引用包名
input logic clock,
output logic [3:0] result
);
import definitions::ADD;
import definitions::SUB;
import definitions::MUL;
import definitions::multiplier;
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
同样,import definitions::opcode_t将不起作用,这只会使opcode_t在模块中可见,但是不会使opcode_t中的枚举元素可见。
使用import引用方便了对包中子项的使用,但是若需要引入的子项过多,仍然很麻烦,这时可以使用通配符导入更实用。
使用通配符导入包中子项
module ALU (
input definitions::instruction_t IW,//端口列表仍然需要显示引用包名
input logic clock,
output logic [3:0] result
);
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
无需逐个导入每个子项,使用通配符"*",就可让包中所有子项在模块或接口中可见,但是需要注意的是:通配符导入并不是自动导入了包中的所有内容,只有在模块或接口中使用的子项才会被导入,而没有使用过的子项是不会被导入的。使用通配符导入,只是将包添加到了标识符搜索路径中,当遇到一个变量,在模块中并没有声明过,就会在搜索路径中寻找是否有需要的变量,如果有再将变量引入。
现在,当需要使用包中子项时,我们可以使用通配符使包中所有子项在模块或接口中可见,当模块或接口需要使用某一个子项时,就可以像使用局部变量一样使用这些子项。但是我们注意到,使用import导入并不能简化在端口列表中对包中子项的引用(仍然需要用解析操作符显示引用),如果端口列表中要引用的包中用户自定义类型过多,还是带来书写麻烦的问题。而使用声明域$unit,就可以避免在端口列表中显示引用,有关$unit的内容在后面单独讲解。
2.1.3 小结
当模块应用一个包中定义的子项时,综合会复制该子项并把它看作已经在模块中定义了。为了能被综合,函数或任务必须声明为automatic,并且其中不能含有静态变量,因为自动任务或函数的存储区在每次调用时才会被分配,因此引用包中自动任务或函数的每个模块看到的是该任务或函数的唯一副本,并且不被其他模块共享。而静态变量在综合钱就已经分配了内存,这与"自动任务或函数在被调用时才被分配存储区相矛盾"。同样的道理,包中的变量(parameter、localparam、const等)会被导入该变量的所有模块共享,一个模块对变量的修改在另一个模块是可见的。
2.2 $unit编译单元声明
编译单元是同时编译的所有源文件,编译单元为软件工具提供了一种对整个设计的各个子块单独编译的方法。(可以理解为verilog中不能单独编译子块?)
sv允许在包、模块、接口和程序块的外部进行声明,这些外部声明在"编译单元域"中,并且对所有同时编译的模块是可见的。编译单元域可以包含:
- 时间单位和精度声明
- 变量声明
- net声明
- 常量声明
- 用户自定义数据类型typedef、enum或class
- 任务和函数定义
下面的例子说明了常量、变量、用户定义类型和函数的外部声明
/***********************外部声明,不包含在任何块中******************/
/****************************外部编译声明域************************/
parameter VERSION = "1.2a";//外部常量
reg resetN = 1;//外部变量
typedef struct packed {
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) begin
if(!resetN)//使用外部复位
q <= 0;
else
q <= d;
end
endmodule
编译单元域中的声明与全局声明不一样,全局声明的变量或函数,不管源文件是单独编译还是同时编译,都会被所有模块共享。而编译单元域只作用于同时编译的源文件,每次编译源文件,就创建一个唯一针对此次编译的编译单元域,未参与此次编译的模块不能共享单元域中的内容。
如果模块A和模块B都引用了外部声明变量reset,那么可能存在两种假设:
(1)如果两个模块同时编译,将只有一个编译单元域,外部声明的reset变量被A、B共享
(2)如果两个模块分别编译,将会有两个独立的编译单元域,可能会有两个不同的reset变量。所谓两个不同的变量,考虑下面情况,如果模块A对reset变量的使用在编译单元域中reset变量的声明之前,那么在编译时,由于reset声明在后而使用在前,编译器会将reset当成默认的线网型处理;而如果B编译成功,那么A的线网型reset和B的变量型reset用什么方式也不能连接在一起。因此,在使用一个变量前必须要事先声明。
2.2.1 编码指导
(1)为了防止出现单独编译时出现不同变量,不要在$unit空间进行任何声明!所有声明都要在包内进行。
(2)必要时,将包导入$unit中,这在模块或接口的多个端口使用用户自定义类型,而这些自定义类型又在包中时非常有用。
2.2.2 sv标识符搜索规则
对于sv对变量声明的扩展基本介绍完了,如果在设计中混合了多种类型的声明,那么这些声明的优先级顺序是什么呢?
sv定义了简单直观的搜索规则来引用标识符:
(1)搜索模块中的局部变量
(2)搜索import导入到当前作用域的声明,其中特定的导入(逐一导入)的优先级先于通配符导入
(3)搜索编译单元域中的声明,这些声明可以在组成编译单元的模块的任何地方引用
(4)搜索设计层次中的声明
2.2.3将包导入$unit的编码原则
单独编译时将包导入到$unit中
单独编译源文件时,要将包的导入放在文件的开头,这样做得目的是确保模块在引用包中子项时该子项已经被声明过了,否则该子项要么被当成默认线网型处理,要么因找不到该子项而报错。
多文件编译时将包导入到$unit中
由于单独编译时,每个文件都会在文件开头导入包,但是在多文件同时编译时,将相同的包导入到同一个$unit域中的非法的。我们希望的是,在编译第一个文件时,我们把包导入到$unit域,当编译剩余文件时,即使再碰到包导入语句也不再进行编译了。很明显,条件编译可以帮助我们解决这个问题,下面是在一个definitions.pkg(后缀名随便取)文件中定义的条件编译:
`ifndef DFFS_DONE//如果还没有已编译标志
`define DFF_DONE//设置该标志
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
import definitions::*;//将包导入到$unit中
`endif
在每个需要包中定义的设计或测试中都应将" `inculde "definitions.pkg" "放在文件开始,条件编译保证在多文件编译时,如果包还没有被编译和导入,就去完成这两个任务,如果包已经被编译并导入到当前$unit域中,该文件的编译将被忽略。而在单文件编译时,由于一次编译就对应一个$unit域,每个域中的包中子项都是独立的,也不会出现名称冲突问题。
2.2.4小结
在$unit中声明的可综合结构有
- typedef用户定义类型
- 自动函数
- 自动任务
- parameter和localparam常量
- 包导入
虽然$unit可以声明变量等,但是建议在包中声明,然后导入$unit域中使用。
2.3未命名语句块中的声明
verilog允许在命令的begin...end或fork...join块中声明局部变量,一般用于循环控制。这种局部变量可以通过层次化引用的方式进行访问,但是需要注意的是层次化引用是不可综合的,所以多用于仿真调试。
sv扩展了verilog,允许在未命名块中声明变量,由于命名块没有了命名,所以也就无法通过层次引用访问这些变量,即,由于没有层次路径,未命名块中声明的局部变量不能在未命名块以外的任何地方引用。
2.4 仿真时间单位和精度
verilog并不在时间值中指定时间单位,例如下面的用于产生时钟的语句:
forever #5 clock = ~clock;
这里的时间值5,代表的究竟是多久,5ns?5ps?单纯从数值上根本判断不出来。
2.4.1 verilog编译指令timescale
verilog使用编译指令 `timescale为软件工具指定时间单位,而不是在时间值中指定单位,这个指令包括两部分:时间单位和时间精度,如:
`timescale 1ns/1ps
使用timescale指令的一个问题是其作用范围并不限于指定的模块或文件,在遇到timescale后,指定的时间单位和精度一直保存有效直到遇到下一条timescale语句,这使得编译依赖于verilog源文件在EDA软件中的读取顺序。
2.4.2 包含时间单位的时间值
sv可以给时间值指定时间单位
forever #5ns clock = ~clock;//时间值和时间单位之间不能有空格
可用的时间单位有秒(s)、毫秒(ms)、微妙(us)、纳秒(ns)、皮秒(ps)、飞秒(fs)、step(软件工具使用的最小时间单位)。
2.4.3 范围级时间单位和精度
sv还允许指定局部性的时间单位和精度,作为模块、接口或程序块的一部分,而不是作为软件工具的指令。sv使用timeunit和timeprecision来指定模块内的时间单位和精度信息:
module chip(...);
timeunit 1ns;
timeprecision 10ps;
...
endmodule
需要注意的是,timeunit和timeprecision语句必须在其他任何声明或语句之前,紧随模块、接口或程序的声明之后。
除了特殊单位step(用于sv测试时钟模块),这两个关键字可以使用的单位和timescale相同。
2.4.4 编译单元的时间单位和精度
在编译单元中timeunit和timeprecision的声明可以用于所有模块、接口和程序块,前提是他们内部没有局部timeunit或timeprecision声明,并在编译时没有有效的verilog timescale指令。这里就引出了有关时间单位和精度的搜索次序。
时间单位和精度搜索次序
(1)使用时间值带的时间单位
(2)使用在模块、接口和程序块内部指定的局部时间单位和精度
(3)使用父模块或接口指定的时间单位和精度
(4)使用timescale时间单位和精度
(5)使用编译单元域中定义的时间单位和精度
(6)使用仿真器默认的时间单位和精度
用下面的例子进行详细的说明:
/********************时间单位和精度的混合声明(不可综合)*******************/
//外部声明的时间单位和精度
timeunit 1ns;
timeprecision 1ns;
module chip(...);
timeprecision 1ps;//局部精度(优先于外部精度)
always @(posedge data_request) begin
#2.5 send_packet;//使用外部单位和局部精度
#3.75ns check_crc;//使用指定的单位
end
task send_packet();
...
endtask
task check_crc();
...
endtask
endmodule
`timescale 1ps/1ps //优于外部单位和精度
module FSM(...);
timeunit 1ns;//局部声明,优于`timescale的指定
always @(state) begin
#1.2 case(state)//使用局部声明的单位和`timescale指定的精度
WAITE:#20ps...;//使用指定的单位
...
end
endmodule

浙公网安备 33010602011771号