目录
(二)‘begin_keywords and ‘end_keywords
前言
近期学习了《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或1
- Wand,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: 用户定义类型,甚至是简单的向量声明,可以保证项目内的声明是一致的。使用用户定义类型可以防止意外在大小和类型的不匹配。