可综合system verilog语法及优势总结
目录
前言
一、数据类型
(一)Value sets
(二)Net 类型
(三)变量类型
(四)矢量声明(packed arrays)
(五)Arrays(unpacked arrays)
(六)用户定义数据类型
二、参数化模型
三、共享声明空间—packages 和$unit
(一)Packages
(二)$unit(不推荐使用)
四、RTL 编程
(一)过程块
(二)操作数
(三)类型转换运算符
五、模块接口(internal to a module)
六、网表
七、Interface
八、Misc 可综合SV construct
(一)Ending names
(二)‘begin_keywords and ‘end_keywords
(三)vector fill token
(四)Const variables(const)
(五)Timeunit and timeprecision
(六)表达式大小函数($clog2,$bits)
九、Summary
前言
近期学习了《Synthesizing SystemVerilog Busting the Myth that SystemVerilog is only for Verification》这篇文档,发现对于IC 设计工程师,用SV 做RTL 设计,优势比verilog强大很多。首先纠正一个误区,SystemVerilog-2005 不是一种独立的语言——它只是一组Verilog-2005 之上的扩展。从2009年开始进行了合并,就只有一个syster verilog标准,SV 相当于verilog plus版本。二者的关系和语法差异如图1 所示。
图1 Verilog to SystemVerilog growth chart
此外SystemVerilog 标准扩展了验证和硬件Verilog 的建模能力,SV 不仅可以用于功能仿真,可综合的语法还可用于电路设计。这篇文章总结了可综合SV 的语法及其优势。
一、数据类型
数据类型包括:net types,variable types, 用户定义类型。
(一)Value sets
Verilog 有4值逻辑,每个vector 的bit 位可以是0,1,z或者x。sv 增加了2值逻辑,vector 的每个bit可以是0或1。关键字分别为:bit 和 logic,bit : 2值逻辑, logic: 4值逻辑。2值和4值逻辑对仿真器是有差别的,但对于综合器没有差别。
在不显式定义的情况下,可以通过推断的方式定义节点和变量。关键字bit 总是推断为变量,关键字logic根据上下文推断出变量,或者input ,output ,inout接口。
SV中net类型,例如wire类型,仅使用logic 4态值;一些变量类型可以是4态,也可以是2态。
关键字bit 可以指一个变量,关键字logic在很多场景下可以指一个变量,但是也可以用于模块的input和inout 接口定义。
如下实例:
module M (
// module ports with inferred types
input i1, // infers a 4-state net
input logic i2, // infers a 4-state net
input bit i3, // infers a 2-state variable
output o1, // infers a 4-state net
output logic o2, // infers a 4-state variable
output bit o3 // infers a 2-state variable
);
// internal signals with inferred and explicit types
bit clock; // infers a 2-state variable
logic reset; // infers a 4-state variable
logic [7:0] data; // infers a 4-state variable
wire [7:0] n1; // explicitly declares a net, infers 4-state logic
wire logic [7:0] n2; // explicitly declares a 4-state net
var [7:0] v1; // explicitly declares a variable, infers logic
var logic [7:0] v2; // explicitly declares a 4-state variable
...
endmodule
SV 优势1:不需要区分port是wire还是reg型(net还是variable型),使用SV可以将模块port和 local 信号定义为logic即可。
注:但对于testbench的验证代码,应当将随机测试值定义成bit 的2值逻辑(2-状态),而不是logic(4-状态)。
(二)Net 类型
可综合的net 类型包括以下3种:
Wire 和tri:允许和解析多个驱动程序的互连网络Supply0和supply1:相互连接的网,分别具有常数0或1Wand,triand wor和trior :互连的网络和或或多个驱动器在一起
SV 优势2: SystemVerilog也有一个对设计工作非常有益的有线网络类型,但目前还不支持综合。
(三)变量类型
变量通常用于程序code,即always 块中。Verilog/SV 需要过程赋值的左侧必须是变量类型。SV中可综合的变量类型是:
Reg——用户定义的通用四状态变量矢量大小的Integer—— 一个32位 4态变量Logic——除模块输入/输出端口外,推导出用户定义的矢量大小的通用四状态变量Bit——推断用户定义的矢量大小的通用2态变量byte, shortint, int, longint——分别指2态的 8bit、16bit、32bit和64bit 的向量大小
注:避免使用2态类型,防止仿真和综合结果不一致,唯一的例外是在for循环中使用int变量作为迭代器变量。
(四)矢量声明(packed arrays)
wire [31:0] a; // 32-bit vector, little endian
logic [1:32] b; // 32-bit vector, big endian
SV 推荐矢量为packed array类型以确保位数组是连续存储的。
SystemVerilog增加的一个重要增强语法是能够通过使用多个范围将矢量声明划分为子字段,例如:
logic [3:0][7:0] a; // 32-bit vector, divided into 4 8-bit subfields
a[2] = 8’hFF; // assign to subfield 2 of the vector
a[1][0] = 1’b1; // select a single bit of subfield 1
(五)Arrays(unpacked arrays)
SystemVerilog允许声明网络、变量和用户定义类型的一维和多维数组(参见2.6节),数组维度在数组名称之后声明。
logic [7:0] LUT [0:255]; // one-dimensional array of 256 bytes
logic [7:0] RGB [0:15][0:15][0:15]; // three-dimensional array of bytes
使用索引号选择数组元素:
data = LUT[7]; // select the byte at address 7 from the array
RGB[0][0][0] = 8’h1A; // assign to address 0,0,0 of the array
C 代码风格的声明:
logic [7:0] LUT [256]; // one-dimensional array of 256 bytes
logic [7:0] RGB [16][16][16]; // three-dimensional array of bytes
对数组的复制:
Verilog 允许在一个时间仅访问单个元素。SystemVerilog允许将数组复制为单个赋值语句。可以复制整个数组或部分数组,数组复制赋值要求赋值的两边的维数和每个维中的元素数相同。例如:
logic [31:0] big_array [0:255]; // array with 256 32-bit elements
logic [31:0] small_array [0:15]; // array with 16 32-bit elements
assign small_array = big_array[16:31]; // copy 16 elements of big_array
对数组进行赋值:
数组的所有或多个元素都可以使用包含在“{}”中的值列表来赋值。该列表可以包含单个数组元素的值,也可以包含整个数组的默认值。
logic [7:0] a, b, c;
logic [7:0] d_array [0:3]; // array with 4 32-bit elements
always_ff @(posedge clock or negedge rstN)
if (!rstN)
d_array <= ’{default:0}; // reset all elements of the array
else
d_array <= ’{8’h00, c, b, a}; // load the array
通过模块 port 将数组传递给task和functions:
typedef logic [31:0] d_array_t [0:7][0:255];
module block_data (input d_array_t d_in, // input is an array
output d_array_t q_out, // output is an array
input logic clock, rstN);
function d_array_t transform (input d_array_t d); // input is an array // ... perform operations on all elements of d
return d; // return is an array
endfunction
always_ff @(posedge clock or negedge rstN)
if (!rstN)
q_out <= ’{default:0}; // reset entire q_out array
else
q_out <= transform(d_in); // transform and store entire array
endmodule
注意,SystemVerilog要求通过数组端口或任务/函数参数传递的值具有相同的维数,并且每个元素具有相同的矢量大小和兼容的类型。
阵列查询系统功能
可综合的数组查询功能,如$left(),$right(),$low(), $high(),$increment(),$size(),$dimension(),$unpacked_dimensions()
typedef logic [31:0] d_array_t [0:15][0:15];
function d_array_t transform (input d_array_t d);
for (int i = $low(d,1); i <= $high(d,1); i++) begin: outer_loop
for (int j = $low(d,2); j <= $high(d,2); j++) begin: inner_loop // ... perform some sort of operation on each element of d
end: inner_loop
end: outer_loop
return d; // function return is an array
endfunction
注:使用foreach循环可以大大简化这个例子。不幸的是,DC或Synplify-Pro不支持foreach。
SV 扩展了verilog 数组,但是存在一些不可综合的verilog 数组。主要包括foreach 数组迭代循环器,数组操作函数,数组定位函数,数组排序函数,数组位流转换。
(六)用户定义数据类型
最初的Verilog语言只有内置的数据类型,变量和net都可以声明为用户定义的类型,可综合的用户定义类型为:
enum-枚举类型 :具有合法值的枚举变量或网线型
struct-结构体:由多个网或变量组成的结构体
union-联合体:可以在不同时间表示不同类型的变量
typedef-定义类型
枚举类型
基本语法:
enum {WAITE, LOAD, DONE} State; // a variable that has 3 legal value
枚举类型有一个基本的数据类型,默认为int型(2-态,32bit 类型)。如上,State 是int 类型,WAITE, LOAD 和 DONE will have 32-bit int values.。枚举列表中的标签是具有关联逻辑值的常量
设计人员可以指定显式的基类型,允许枚举类型更具体地建模硬件。设计器可以为枚举列表中的任何或所有标签指定显式值。
// Two 3-bit, 4-state enumerated variables with one-hot values
enum logic [2:0] {WAITE = 3’b001,
LOAD = 3’b010,
DONE = 3’b100} State, NextState;
枚举类型枚举类型比内置变量和网络具有更强的规则检查,这些规则包括:
枚举列表中每个标签的值必须是唯一的变量大小和标签值的大小必须相同枚举变量只能被赋值: 1)从其枚举列表中获取一个标签;2)来自同一枚举定义的另一个枚举类型的值
与传统Verilog相比,更强的枚举类型规则提供了显著的优势。下面的两个例子对比了在Verilog和SystemVerilog中建模的简单状态机:
以下两个示例存在代码error,例如:
// Names for state machine states (one-hot encoding)
parameter [2:0] WAITE=3'b001, LOAD=3'b010, DONE=3'b001; // FUNCTIONAL BUG
// Names for mode_control output values
parameter [1:0] READY=3'b101, SET=3'b010, GO=3'b110; // FUNCTIONAL BUG
// State and next state variables
reg [2:0] state, next_state, mode_control;
// State Sequencer
always @(posedge clock or negedge resetN)
if (!resetN)
state <= 0; // FUNCTIONAL BUG
else
state <= next_state;
// Next State Decoder (sequentially cycle through the three states)
always @(state)
case (state)
WAITE: next_state = state + 1; // DANGEROUS CODE
LOAD : next_state = state + 1; // FUNCTIONAL BUG
DONE : next_state = state + 1; // FUNCTIONAL BUG
endcase
// Output Decoder
always @(state)
case (state)
WAITE: mode_control = READY;
LOAD : mode_control = SET;
DONE : mode_control = DONE; // FUNCTIONAL BUG
endcase
endmodule
这6个bug在程序示例中都是语法合法的,仿真可以编译和运行,验证代码可以找到功能问题,综合可能会爆出一些code error。但是有些bug最终还是会出现在设计的门级实现中。
在这个例子中,DC不会捕捉第二个语法错误,但是VCS会检测到它。
下面的示例展示了相同的编码bug,但是使用枚举类型代替 Verilog参数和reg变量(该示例还使用了本文后面介绍的其他一些SystemVerilog结构)。
module bad_fsm_systemverilog_style (...); // only relevant code shown
enum logic [2:0] {WAITE=3'b001, LOAD=3'b010, DONE=3'b001} // SYNTAX ERROR
state, next_state;
enum logic [1:0] {READY=3'b101, SET=3'b010, GO=3'b110} // SYNTAX ERROR
mode_control;
// State Sequencer
always_ff @(posedge clock or negedge resetN)
if (!resetN)
state <= 0; // SYNTAX ERROR
else
state <= next_state;
// Next State Decoder (sequentially cycle through the three states)
always_comb
case (state)
WAITE: next_state = state + 1; // SYNTAX ERROR
LOAD : next_state = state + 1; // SYNTAX ERROR
DONE : next_state = state + 1; // SYNTAX ERROR
endcase
// Output Decoder
always_comb
case (state)
WAITE: mode_control = READY;
LOAD : mode_control = SET;
DONE : mode_control = DONE; // SYNTAX ERROR
endcase
endmodule
DC 工具不能识别第二种语法错误,但是VCS 可以检测到。
Systemverilog 也提供了几种用于枚举类型的方法,可综合的方法是:.first,.last,.next,.prev 和.num,顺序是基于枚举列表的声明顺序。
通过使用枚举方法可以更简洁地编写前面示例中的轮循next状态解码器:
always_comb
next_state = state.next; // tranistions from WAITE to LOAD, from LOAD to
// DONE, and from DONE back to WAITE
尽管枚举方法在某些情况下可以简化代码,但它们在实际设计中的应用还是有一定限制的。分配枚举标签是一种更好的编码风格,而不是使用枚举方法。使用标签使代码更具自文档性,并在状态机分支中提供了更大的灵活性。
SV 优势3: 枚举类型可以防止代码error 难以检测和debug,当变量或网络只能具有有限的合法值集时,请使用枚举类型。
结构体
结构体提供了一种将多种变量聚合在一个公用名字之下的机制,是可综合的。
struct {
logic [31:0] source_address;
logic [31:0] destination_address;
logic [63:0] data;
logic [3:0] ecc;
} packet;
可以使用点操作符访问结构体的各个成员(.)
packet.source_address = 32’h0000dead;
也可以对结构体中的所有成员赋上一系列值,列表可以包括单个结构成员的值,或者结构体的默认值。
always_ff @(posedge clock or negedge rstN)
if (!rstN)
packet <= ’{default:0}; // reset all members of packet
else
packet <= ’{old_addr, new_addr, data_in, ecc_func(data_in)};
设计人员可以通过将结构声明为packed类型来控制结构成员的存储方式,存储时按照从最左侧开始连续填充。
struct packed { // members will be stored contiguously
logic [ 1:0] parity;
logic [63:0] data;
} data_word;
SV 优势4: 使用struct 结构体将相关变量收集到一起,可以作为group传输到其他模块,以减少代码行和确保一致性。
Unions
Unions 用单个存储空间代表多个存储形式。System Verilog 有三种类型的unions :简单联合、打包联合和标记联合。只有打包联合是可综合的,打包联合要求联合内的所有表示都是相同位数的打包类型。由于打包联合中的所有成员大小相同,因此写入联合的一个成员(格式)并从另一个成员回读数据是合法的。
以下示例代表了一个可以存储数据包或者指令包的 64bit 硬件寄存器:
union packed {
struct packed {
logic [31:0] data;
logic [31:0] address;
} data_packet;
struct packed {
logic [31:0] data;
logic [31:0] operation;
} instruction_packet;
} packet_u;
always_ff @(posedge clock or negedge rstN)
if (!rstN)
packet_u <= {’0, ’0}; // reset
else if (op_type == DATA)
packet_u.data_packet <= {d_in, addr};
else
packet_u.instruction_packet <= {d_in, instr};
将值列表赋值到结构中在语法上也是合法的,并且是首选的编码风格,但是DC不支持将值列表赋值到包含联合成员的结构中。
Type 定义(typedef)
新的数据类型是由内建类型组成的,其他的用户定义类型使用typedef,和C语言一样。示例如下:
typedef logic [31:0] bus64_t; // 64-bit bus
typedef struct { // typed structure
logic [ 1:0] parity;
logic [63:0] data;
} data_t;
typedef enum logic {FALSE=1’b0, TRUE=1’b1} bool_t;
module D (input data_t a, b,
output bus64_t result,
output bool_t aok );
...
endmodule
SystemVerilog还提供了一个包构造来封装类型定义和其他定义。
SV 优势5: 用户定义类型,甚至是简单的向量声明,可以保证项目内的声明是一致的。使用用户定义类型可以防止意外在大小和类型的不匹配。
二、参数化模型
Verilog 可以使用参数和参数化定义让模型可以配置和可扩展。SV 扩展了verilog 参数定义,允许参数化数据类型,例如:
module adder #(parameter type dtype = logic [0:0]) // default is 1-bit size
(input dtype a, b,
output dtype sum);
assign sum = a + b;
endmodule
module top (
input logic [15:0] a, b,
input logic [31:0] c, d,
output logic [15:0] r1,
output logic [31:0] r2);
adder #(.dtype(logic [15:0])) i1 (a, b, r1); // 16 bit adder
adder #(.dtype(logic signed [31:0])) i2 (c, c, r2); // 32-bit signed adder
endmodule
参数化数据类型是可综合的,注意,当使用#(…)模块参数列表时,SystemVerilog-2009使parameter关键字成为可选的,但是DC仍然需要parameter关键字。
三、共享声明空间—packages 和$unit
(一)Packages
在verilog 中,如果在多个模块内公用的参数、task 和需要function 定义,需要work around,通常通过`ifdef 和`include 编译指示。 在SV 中用户定义的类型、面向对象的类定义和随机化约束使得缺乏共享声明空间成为一个严重的问题。
SystemVerilog通过添加用户定义的包来解决Verilog的缺点。包提供了一个声明空间,可以从任何设计模块以及验证代码中引用。包中可以包含的可合成项有:
parameter和localparameterconstant variable 定义typedef 用户定义类型fully automatic 和function 定义从其他包导入的statement为包链接导出的statement
示例如下:
package alu_types;
localparam DELAY = 1;
typedef logic [31:0] bus32_t;
typedef logic [63:0] bus64_t;
typedef enum logic [3:0] {ADD, SUB, ...} opcode_t;
typedef struct {
bus32_t i0, i1;
opcode_t opcode;
} instr_t;
function automatic logic parity_gen(input d);
return ^d;
endfunction
endpackage
注意,在包中定义的参数不能被重新定义,并且被视为与localparam相同。还要注意,合成要求将包中定义的任务和函数声明为自动的。
引用package 定义
包中的定义可以在设计块(即模块或接口)中以三种方式中的任何一种使用,所有这些都是可综合的。
显示包引用显示导入语句通配符导入语句‘’
显示包引用示例(::)
module alu
(input alu_types::instr_t instruction, // use package item in port list
output alu_types::bus64_t result );
alu_types::bus64_t temp; // use package item within module
...
endmodule
对包项的显式引用不会使该项在模块的其他地方可见。每次在模块中使用定义时都必须使用显式包引用。
显示导入语句
包项的显式导入使用import语句。一旦导入,该项可以在模块内被引用任意次
module alu
import alu_types::bus64_t;
(input alu_types::instr_t instruction, // explicit package reference
output bus64_t result ); // bus64_t has been imported
bus64_t temp; // bus64_t has been imported
...
endmodule
通配符导入语句
通配符导入使用星号表示包中的所有定义。通配符导入使包的所有项在模块中可见。
module alu
import alu_types::*;
(input instr_t instruction, // instr_t has been imported
output bus64_t result ); // bus64_t has been imported
bus64_t temp; // bus64_t has been imported
...
endmodule
SV 优势6:package可以消除重复的代码,在设计的不同块中不匹配的风险,以及维护重复代码的困难。
推荐:使用package,package为在整个设计和验证项目中重用任务、函数和用户定义类型的定义提供了一种干净而简单的方法。
import 声明的位置
在sv-2005版本中,import 声明只能出现在模块接口列表之后;sv-2009 支持出现在模块接口列表和parameter之前,但EDA 工具还未支持。
注:DC支持在端口列表之前导入包,但需要使用set hdlin_sverilog_std2009,以实现这种支持,Synplify-Pro还不支持模块端口列表之前的包导入。
将一个package导入另一个package
包还可以引用另一个包中的定义,并可以从另一个包导入定义。将包导入到其他包中是可合成的。SystemVerilog还允许包链接,这简化了从其他包中引用项的包的使用。
注:DC不支持包链接。
Package 编译顺序
SystemVerilog语法要求在引用包定义之前对其进行编译。这意味着在编译包和模块时存在文件顺序依赖。这也意味着引用包项的模块不能独立编译;包必须与模块一起编译(或者已经预编译,如果工具支持增量编译)。
(二)$unit(不推荐使用)
$unit:伪全局命名空间。
命名声明空间之外的任何声明都在$unit包中定义。在下面的示例中,bool_t的定义在这两个模块之外,因此在$unit声明空间中。
typedef enum bit {FALSE, TRUE} bool_t;
module alu (...);
bool_t success_flag;
...
endmodule
module decoder (...);
bool_t a_ok;
...
endmodule
$unit可以包含与命名包相同类型的用户定义,并且具有相同的综合限制。
注意:$unit是一个充满危险的共享名称空间。简单地说,使用$unit的一些危险是:
$unit中的定义可能分散在许多文件中,使代码维护成为一场噩梦。当$unit空间中的定义位于多个文件中时,这些文件必须按照特定的顺序编译,以便每个定义在被引用之前被编译。编译器的每次调用都启动一个新的$unit空间,该$unit空间不共享其他$unit空间中的声明。因此,一次编译多个文件的编译器(如VCS)将看到单个$unit空间,而可以独立编译每个文件的编译器(如DC)将看到多个断开的$unit sp在SystemVerilog中,在相同的名称空间中多次定义相同的名称是非法的。因此,如果一个文件在$unit空间中定义了bool_t类型,而另一个文件也在$unit空间中定义了bool_t类型,那么如果这两个文件一起编译,就会发生编译或精化错误。命名包可以导入到$unit中,但必须注意不要多次导入同一个包。将同一个包多次导入到同一个名称空间是非法的。
四、RTL 编程
SV 增加了大量相对于传统verilog 的编码,优势如下:
减少编码行数减少设计中功能错误的风险同时帮助确保模拟和合成以相同的方式解释设计功能
(一)过程块
always_comb
always @(a or b or sel) begin
if (sel)
y = a;
else
y = b;
end
always_comb begin
if (sel)
y = a;
else
y = b;
end
always@(*)可以自动推断完整的敏感列表,但这种结构不完美,在一些corner 场景下,仿真和综合推断出不同的列表。
另一个重要的好处是,因为软件工具知道推断是表示组合逻辑,所以工具可以验证这个推断是否得到满足。
module always_comb_test
(input logic a, b,
output logic c);
always_comb
if (a)
c = b;
endmodule: always_comb_test
DC 综合工具会将该寄存器类型推断为“latch”,elaboragte 时也会报出warning。
always_latch(不使用)
特殊的always_latch过程块与always_comb非常相似,不同之处在于它记录了设计者表示latch行为的意图。然后,工具可以验证这个意图是否得到满足。下一个代码示例使用always_latch,但对组合逻辑进行建模。
module always_latch_test
(input a, b, c,
output logic out);
always_latch
if (a)
out = b;
else
out = c;
endmodule: always_latch_test
DC 工具会报出以下warning:
Warning: /test.sv:7: Netlist for always_latch block does not contain a latch. (ELAB-975)
当将前面的代码示例读入合成工具时,会发出一个elaborate警告,指出没有从always_latch块建模闩锁。
always_ff
always_ff过程块记录了设计者表示触发器行为的意图。
module always_ff_test
(input a, b, c,
output logic out);
always_ff @(a, b, c)
if (a)
out = b;
else
out = c;
endmodule: always_ff_test
软件工具仍然可以验证触发器行为的意图是否在程序块的主体中得到满足。在下面的示例中,always_ff用于不实际模拟触发器的代码。DC发出的警告如下所示。
Warning: test.sv:5: Netlist for always_ff block does not contain a flipflop. (ELAB-976)
SystemVerilog过程块提供了其他规则检查和特性,帮助确保RTL代码将合成为预期的门级实现:
赋值的LHS上的变量不能被任何其他进程写入Always_comb和always_latch将在仿真的时间零点执行一次,确保块内赋值左侧的变量正确反映右侧的值Always_comb和always_latch对过程块调用的函数中的信号变化敏感,而不仅仅是函数参数,这是always @(*)的一个bug
SV 优势7: 使用always_comb,always_latch和always_ff 可以防止出现严重的建模错误。
建议-在所有RTL代码中使用always_comb、always_latch和always_ff。只在不打算综合的模型中使用通用的过程,例如总线功能模型、抽象RAM模型和验证测试平台。
(二)操作数
原始Verilog语言提供了许多运算符,其中大多数是可综合的。本文列出了这些操作符,但没有详细说明它们的使用及其综合规则。
位运算符
~
&
|
^
^~
~^
一元约减运算符
~
&
|
^
~&
~|
~|
~^
逻辑运算符
&&
||
等式关系运算符
= =
!=
<
<=
>
>=
移位运算符
<<
<<<
>>
>>>
连接运算符
{ }
{n{ }}
条件运算符
?:
算术运算符
+
-
*
/
%
**
SystemVerilog在传统Verilog的基础上增加了许多可综合的操作符
大小写相等运算符(==?)! = ?) :大小写相等操作符,也称为通配符相等操作符,比较两个值的位,并能够屏蔽特定的位。操作符令牌是:==?和! = ?. 这些操作符允许从比较中排除特定的位,类似于Verilog casex语句。排除的位在第二个操作数中使用逻辑X、Z或?指定。
if (address ==? 16’hFF??) // lower 8 bits are masked from the comparison
这些操作符的合成与==和!=相同,但在比较器中忽略掩码位,遵循与Verilog casex语句相同的合成规则和限制。
设置成员操作符(内部):
内部集合成员操作符将一个值与{}中包含的其他值的列表进行比较。值列表可以是[]之间的范围,也可以是存储在数组中的值。内部集合成员操作符允许在比较中屏蔽值列表中的位,其方式与大小写相等操作符相同。
if (data inside {[0:255]}) ... // if data is between 0 to 255, inclusive
if (data inside {3'b1?1}) ... // if data is 3'b101, 3'b111, 3'b1x1, 3'b1z1
注意:{}集合中包含的值必须是常量表达式,以便内部操作符可以合成。内部操作符还有其他不可综合的用法。
流操作符(即打包和解包)(<<,>>):
流操作符从串行流中的向量中取出或放入一组位。流操作符既可用于将数据打包到vector中,也可用于将数据从vector中解包。
•{< • {>>M{N}} — 从 N 流式传输 M 大小的块,从最左侧的块到最右侧的块 当在赋值操作的右侧使用流操作符时,会发生打包操作。该操作将从右侧表达式提取块作为串行流,并将该流打包到左侧的向量中。取出的比特可以是任意数量的比特组。如果未指定大小,则默认为每次1位。 logic [ 7:0] a; logic [ 7:0] b = 8'b00110101; always_comb a = { << { b }} // sets a to the value 8'b10101100 (bit reverse of b) 当在赋值操作的左侧使用流操作符时,将发生解包操作。将位块从右侧表达式中取出,并将其赋值给流操作符中的表达式。 logic [ 7:0] a [0:3]; logic [31:0] e = 32'AABBCCDD; always_comb {>>8{a}} = e; // sets a[0]=AA, a[1]=BB, a[2]=CC, a[3]=DD 注意:DC不支持使用流操作符来选择位块,如上面的字节重排序示例。 SystemVerilog流操作符可以通过多种创造性的方式来打包和解包存储在矢量、数组和结构中的数据。运算符是可综合的。 自增/自减和赋值操作符(++、——、+=、-=等) ++和——自增和自减是C语言中最常用的两个操作符,然而Verilog语言没有这些操作符。SystemVerilog添加了这些操作符。自增/自减操作符对其操作数执行阻塞赋值。可在赋值语句的右侧使用++和——自增和自减操作符。例如: always_comb begin y1 = a++; // same as: y1 = a; a = a + 1; y2 = a; end 注:DC不支持在赋值语句的右侧使用自增和自减操作符, SystemVerilog还在Verilog语言中添加了类似c的赋值操作符。这些操作符结合了操作和对变量的赋值。操作符是:+= -= /= %= &= ^= != |= <<= >>= <<<= >>>= 。 always_comb accumulator += b; // same as: accumulator = accumulator + b; always_ff @(posedge clk, negedge rstn) if(!rstN) cnt <= '0; else if (en) cnt++; // same as cnt = cnt + 1, which is a potential race 阻塞赋值行为限制了这些运算符在 RTL 代码中的有用性,现实情况是: ++ 和 += 等运算符主要仅用作 for 循环步骤赋值。 for (int i; i<=7; i++)... // same as i = i + 1 建议-不要在时钟边缘更新的功能中使用自增、自减和赋值操作符。 (三)类型转换运算符 System Verilog 增加了‘()类型转换操作符,有3种可综合的类型转换操作数。 类型转换 : sum = int’(r * 3.1415)Size 转换:sum = 16’(a + 5);Sign 转换: s = signed’(a) + signed’(b); 强制转换的一个用途是消除那些恼人的lint检查器“尺寸不匹配”警告消息。 logic [31:0] a, y; logic [ 5:0] b; always_comb y = {a,a} >> b; // rotate a by b times, assign 64-bit result to 32-bit LHS y = logic [31:0]'({a,a} >> b); // cast result to 32 bits before assigning 建议-使用强制转换来消除类型或大小不匹配的警告消息。转换还用于记录所要更改的大小类型。 请注意,SystemVerilog还添加了一个$cast()动态强制转换系统函数,这是不可综合的。 Task 和functions(可综合) Task 和Function 之间的差别: 一个task可以有输入、输出和输入参数;函数只能有输入参数Task可以有延迟(执行阻塞,直到模拟时间提前);函数必须在零时间内执行任务将结果分配给输出或输入参数;函数返回一个结果在过程代码中,任务就像语句一样使用;函数像表达式一样使用(函数返回值是表达式的值) 带有输出和输入形式参数的function 使用传统的Verilog,函数只能有输入参数。SystemVerilog允许函数也有输出和输入参数,就像任务一样。当与void函数结合使用时,这种增强变得非常重要。 Void functions SystemVerilog增加了将函数声明为void的功能,表示该函数没有返回值(但可以分配给输出参数)。void函数的使用方式与任务相同,但要求它必须在零时间内执行(函数不能包含任何可能阻塞的构造,直到模拟时间前进)。 function void ripple_add (input [31:0] a, b, output [31:0] sum, output co); ... // adder algorithm must execute in zero time endfunction always_comb ripple_add(in1, in2, result, carry); // function is called as a statement SV 优势11:在RTL代码中使用void函数可以帮助确保子程序能够合成。这是因为函数不能有延迟,这也是一般的综合限制。 建议-在RTL模型中使用空函数代替将要合成的任务。这可以防止在使用任务时出现一个常见的问题,即模型在模拟中工作,但随后任务将无法综合。这个问题在void函数中不太可能出现。 在任务/函数调用中按名称传递 在任务或函数中定义形式参数的顺序将值传入或传出。一个无意的编码错误,即两个参数以错误的顺序传递,可能导致难以调试的细微设计错误。 SystemVerilog允许任务或函数调用使用形式参数的名称将值传递给任务或函数。语法与模块实例和使用名称连接相同。使用名称传递使任务或函数调用更具自文档性,并且可以预防传递过程中出错。 function void ripple_add (input [31:0] a, b, output [31:0] sum, output co); ... endfunction always_comb ripple_add(.sum(result), .co(carry), .a(in1), .b(in2) ); 使用return指定函数返回值 传统Verilog通过给函数名赋值来返回函数的值,类似于Pascal语言。SystemVerilog添加了更常规的、类似于c的方式,即使用return关键字来指定函数返回值。 function [31:0] adder ([31:0] a, b); adder = a + b; // Verilog style endfunction function [31:0] adder ([31:0] a, b); return a + b; // SystemVerilog style endfunction 使用静态类的参数化任务/函数参数 参数化模块是Verilog中一个强大且广泛使用的功能。可以为模块的每个实例重新定义参数,使模块易于配置和重用。在传统的在Verilog中,形式参数的大小和类型不能像模块那样被参数化。这降低了编写可重用、可配置模型的能力。SystemVerilog提供了一种绕过此限制的方法。该技术是在参数化类中使用静态任务或函数。每次调用任务或函数时,都可以重新定义类参数: virtual class Functions #(parameter SIZE=32); static function [SIZE-1:0] adder (input [SIZE-1:0] a, b); return a+b; // defaults to a 32-bit adder endfunction endclass module top (input logic [63:0] a, b, output logic [63:0] y) ; always_comb y = Functions #(.SIZE(64))::adder(a,b); // reconfigure to 64-bit adder endmodule 使用参数化的任务和函数,可以只创建和维护任务或函数的一个版本,而不必使用不同的数据类型、向量宽度或其他特征定义几个版本。DC对参数化类设置了两个SystemVerilog限制:该类必须声明为虚类,并且该类必须在$unit声明空间中定义。 不支持的task/function 特性 对传统Verilog任务和函数有两个重要的增强,它们目前还不能综合带有默认值的输入端口和ref参数。 五、模块接口(internal to a module) SystemVerilog放宽了Verilog模块端口声明和可以通过端口传递的数据类型的规则。可合成的新港口声明规则有: 连接到模块输入端口的内部数据类型可以是变量类型。阵列和阵列切片可以通过端口传递。类型化结构、类型化联合和用户定义类型可以通过端口传递。 六、网表 SystemVerilog在使用显式命名的端口将信号连接到模块或接口的实例时提供了两个快捷方式,称为点名称和点星号端口连接。 当端口名称和信号名称相同时,“name”快捷方式简化了网络列表。只有端口需要显式命名;所连接的信号的名称可以省略。该快捷方式推断出与端口名称相同的信号将连接到该实例。. name快捷方式的合成与Verilog显式端口连接相同。 dff i1 (.q, .d, .clk(mclk), .rst); * 快捷方式是一个通配符,它推断所有相同名称的端口和信号都连接在一起。请注意,要综合*快捷方式,需要同时编译包含实例的模块和被实例化的模块或接口的端口定义。 dff i1 (.*, .clk(mclk)); 要连接的网络必须显式声明。点名和点星并不意味着一个网络,这可以防止传统Verilog的隐式网络有时出现的网络列表错误。端口和网的大小必须相同。传统的Verilog允许连接大小不匹配,这通常是错误的网络声明或缺失的网络声明的结果。必须显式声明未连接的端口(仅点星号)。传统的Verilog会推断出模块实例中未显式列出的任何端口都打算保持未连接状态。然而,未连接的端口通常是无意的,并且是编码错误。点星号快捷方式要求显式列出所有未连接的端口。请注意,. name语法没有这种额外的检查 SV 优势12: netlist中的.name 和.*可以极大简化逻辑,并减少设计error。 七、Interface 软件中的一般编码准则是,只要在多个地方需要相同的代码,就使用子模块,而不是重复该代码。考虑到这一点,请考虑以下传统的Verilog代码片段。 module top; wire sig1, sig2, sig3, sig4; wire clock, reset; mod_a u1 (.sig1(sig1), .sig2(sig2), .sig3(sig3), .sig4(sig4), .clk(clock), .rstN(reset) ); mod_b u2 (.sig1(sig1), .sig2(sig2), .sig3(sig3), .sig4(sig4), .clk(clock), .rstN(reset) ); endmodule module mod_a (input sig1, sig2, input clk, rstN, output sig3, sig4); ... endmodule module mod_b (input sig3, sig4, input clk, rstN, output sig1, sig2); ... endmodule 在这个基本示例中,在mod_a和mod_b之间遍历的信号列出了7次。如果发生设计更改,并且需要在两个模块实例之间添加额外的信号,则需要在七个不同的位置修改代码,并且可能在不同的源代码文件中修改代码。类似地,如果更改端口大小的规范,则需要进行七次更改。 SystemVerilog接口可用于将信号声明信息封装在一个地方。下面的示例使用接口将上面示例中的信号封装到单个位置。现在,如果需要在两个模块之间添加信号,或者需要更改矢量宽度,则只需要修改单个源代码位置。 interface intf_1; wire sig1, sig2, sig3, sig4; endinterface: intf_1 module top; wire clock, reset; intf_1 i1(); mod_a u1 (.a1(i1), .clk(clock), .rstN(reset) ); mod_b u2 (.b1(i1), .clk(clock), .rstN(reset) ); endmodule: top module mod_a (interface a1, input clk, rstN); ... endmodule: mod_a module mod_b (intf_1 b1, input clk, rstN); ... endmodule: mod_b 在上面的mod_a示例中,接口端口a1被声明为接口端口类型,而不是传统的输入、输出或输入端口方向。这被称为“通用接口端口”。将任何接口定义的实例连接到通用接口端口都是合法的。在mod_b示例中,接口b1被声明为intf_1端口类型。这被称为“特定类型的接口端口”。只有将intf_1接口的实例连接到这个特定类型的接口端口才是合法的。 建议:在设计模型中只使用特定类型的接口端口。设计是为了期望接口端口内的特定信号而编写的,特定类型的接口端口确保预期的接口及其内部信号将连接到该端口。 接口的主要目的是将以某种方式相关的信号捆绑在一起,例如组成AMBA总线的所有信号,或组成USB总线的所有信号。尽管在语法上是可能的,但接口并不打算将模块的所有端口捆绑到一个端口中。 上面示例中的intf_1接口是可合成的,因为接口中的所有信号都是净类型。对于合成,如果接口中的任何信号是可变类型,则需要一个modport来指定端口方向,如下所示: interface intf_2; wire sig1, sig3; var logic sig2, sig4; modport a_ports (input sig1, output sig2); modport b_ports (input sig3, output sig4); endinterface: intf_2 接口可以用与模块相同的方式参数化,接口的每个实例的参数重新定义使得配置接口以匹配具有参数化端口大小的模块成为可能。 可以使用接口阵列,如下两个示例所示。第一个示例以一种直接的方式使用数组。第二个示例使用生成块将接口映射到模块。 module top1; intf_3 i1 [1:0](); mod_a u1 (.a1(i1[0]), .clk(clock), .rstN(reset) ); mod_b u2 (.b1(i1[0]), .clk(clock), .rstN(reset) ); mod_a u3 (.a1(i1[1]), .clk(clock), .rstN(reset) ); mod_b u4 (.b1(i1[1]), .clk(clock), .rstN(reset) ); endmodule: top1 module top2; intf_3 i1 [1:0](); genvar i; generate for (i=0; i<=1; i++) begin:m mod_a u1 (.a1(i1[i]), .clk(clock), .rstN(reset) ); mod_b u2 (.b1(i1[i]), .clk(clock), .rstN(reset) ); end endgenerate endmodule: top2 接口可以以与模块相同的方式包含功能,例如始终块、连续分配、任务和函数(任务和函数必须是自动的)。 interface intf_4; parameter SIZE = 16; wire [SIZE-1:0] sig1, sig3; var logic [SIZE-1:0] sig2, sig4; ... // modport declarations function automatic logic [7:0] ecc_f (input i); ... endfunction assign sig4 = ecc_f(sig1); endinterface: intf_4 为了可综合,接口中的任何功能代码都必须遵守可合成的RTL编码规则。这可能需要将时钟和复位等信号引入接口,这可能会限制该接口的可重用性。作者建议将接口中的任何功能限制为函数、简单组合逻辑和断言。 SV 优势13: -接口,如果使用得当,可以大大减少设计中的冗余声明。这使得代码更容易维护,也更容易在其他项目中重用。 建议-使用接口将相关信号捆绑在一起。避免捆绑不相关的信号,如时钟和复位。 八、Misc 可综合SV construct (一)Ending names 在任何命名的代码组的末尾指定名称。结束名称必须与块名称匹配;不匹配将作为错误报告。命名端点帮助记录代码,极大地帮助代码调试、维护和重用。 module FSM (...); ... always_ff @(posedge clock) begin: Sequencer case (SquatState) 2'b01: begin: rx_valid_state Rxready <= '1; for (int j=0; j for (int i=0; i if (Rxvalid[i]) begin: match ... // receive data end: match end: loop2 end: loop1 end: rx_valid_state ... // decode other states endcase end: Sequencer task get_data(...); ... endtask: get_data endmodule: FSM (二)‘begin_keywords and ‘end_keywords SystemVerilog提供了一种标准的方式来指定Verilog或SystemVerilog源的语言版本,使用编译器指令对:' begin_keywords " “endkeywords。例如: module test; wire priority; // "priority" is a legal name in Verilog (not a keyword) ... endmodule `end_keywords `begin_keywords "1800-2005" module decoder (...); always_comb priority case (...); // "priority" is a keyword in SystemVerilog ... endmodule `end_keywords `begin_keywords指令作为代码文档,以及软件工具的控件。该指令准确地显示了当编写设计代码时使用的是什么版本的Verilog或SystemVerilog。'begin_keywords '的效果一直有效,直到其对应的遇到end_keywords。' begin_keywords的效果是叠加的,允许在读入源代码时嵌套调用指令。 SV 优势14: - ' begin_keywords指令记录了代码编写时使用的语言版本,并有助于确保SystemVerilog标准的未来版本(或相关标准,如SystemVerilog- ams)的向前兼容性。 建议-在每个源代码文件的开头使用' begin_keywords,并在每个源代码文件的末尾使用一个匹配的' end_keywords。 (三)vector fill token always_ff @(posedge clk or negedge setN) if (!setN) q <= ’1; // set all bits of q to 1, regardless of the size of q else q <= d; (四)Const variables(const) SystemVerilog允许通过在变量声明中添加const来声明任何变量的常量值。const常量的一种用法是在自动函数中。 function automatic int do_magic (a, b); const int magic_num = 86; ... endfunction: do_magic (五)Timeunit and timeprecision Verilog的时间单位和时间精度在合成中没有意义,但在模拟中是必不可少的。 综合编译器忽略了时间规范,但由于它们在仿真中的重要性,本文中提到了它们。传统的Verilog使用' timescale编译器指令来指定时间信息。“时间刻度”的一个危险是它不受模块或文件的约束。该指令从编译器遇到它的时候起一直有效,直到遇到替代指令为止。这可以按照编译器读取源代码的顺序创建依赖项。 module alu (...); timeunit 1ns; timeprecision 1ns; ... endmodule: alu 单位和精度也可以用一条语句指定,使用时间单位1ns/1ns; SV 优势15 -这些时间单元和时间精度关键字消除了“时间刻度指令”的危害,并且应该在每个模块或接口的开头使用,即使其中的代码没有指定任何延迟。 (六)表达式大小函数($clog2,$bits) package my_types; localparam MAX_PAYLOAD = 64; typedef struct { logic [63:0] start_address; logic [$clog2(MAX_PAYLOAD)-1:0] xfer_size; // vector width scales logic [ 7:0] payload [0:MAX_PAYLOAD-1]; } packet_t; endpackage: my_types $bits系统函数,用于返回网络名或变量名或表达式中包含的位数。基本用法是: • $bits(data_type) • $bits(expression) 其中data_type可以是任何语言定义的数据类型或用户定义的类型,表达式可以是任何值,包括操作或未打包的数组 对于综合,$bits可以用于端口声明、变量声明和常量定义(parameter, localparam, const)。下面的例子使用$bits来声明寄存器的大小。在前面的包示例中,寄存器将自动缩放到有效负载的大小。 module my_chip (input logic clk, input my_types::packet_t in, output logic [$bits(in.payload)-1:0] out ); // self-scaling vector size always_ff @(posedge clk) out <= { << { in.payload }}; // pack the payload array into out reg endmodule: my_chip Assertions SystemVerilog在原始Verilog语言中添加了两种类型的断言:即时断言和并发断言。 module mux41 (input a, b, c, d, input [1:0] sel, output logic out); always_comb begin assert (!$isunknown(sel)) else $error("%m : case_Sel = X"); 这行代码将在问题发生的时间和地点捕获与模块的sel输入有关的任何问题,而不是希望验证将在逻辑和时间上检测到下游的功能问题。 九、Summary 使用logic 定义模块接口和大部分内部信号使用uwire网络类型来检查和执行单驱动程序逻辑对具有有限合法值的变量使用枚举类型使用结构将相关变量集合在一起使用用户定义的类型确保设计中的声明一致对在整个设计中共享的声明使用包使用always_comb、always_latch和always_ff过程块用例…Inside而不是casez和casex。使用priority, unique0, unique来代替full_case, parallel_case。使用priority, unique0, unique with if…适当时使用其他方法在RTL代码中使用void函数代替task使用.name和.*网表快捷方式使用接口对相关的总线信号、函数(如果合适)和断言进行分组。使用' begin_keywords指定使用的语言版本使用本地声明的时间单位而不是“时间刻度”。