SystemVerilog
一、基础语法
1.1 timescale
设置时间单位和精度。
`timescale time_unit / time_precision
`timescale 10ns / 1ns如上面第二行的设置,#1表示10ns,#1.55表示16ns。
1.2 常用系统函数
$display()和$write():区别是 display 会换行。语法和 printf 类似。%0d的0表示左对齐。
二、数据类型与对象
2.1 数据类型

2.1.1 整数类型(integral)
| 整数类型 | state | 位宽 | 默认符号 | 说明 |
|---|---|---|---|---|
byte |
2 | 8 | signed | 相当于char |
shortint |
2 | 16 | signed | 相当于short |
int |
2 | 32 | signed | 相当于int |
longint |
2 | 64 | signed | 相当于long long |
bit |
2 | 1 | unsigned | 2值1bit数据 |
logic |
4 | 1 | unsigned | 4值1bit数据 |
reg |
4 | 1 | unsigned | 同logic,避免使用 |
integer |
4 | 32 | signed | 很少用 |
time |
4 | 64 | unsigned | 很少用 |
整数类型分为2-state类型和4-state类型:
2-state类型:只有
0和1两种取值(效率较高),且初始值为0。比如仿真时用bit定义clk比较方便。若需要定义为无符号数或有符号数可以加unsigned或signed。bit reset; bit [31:0] data; bit signed [31:0] value; byte array[8]; // [8]相当于[0:7]4-state类型:取值可以为
0、1、x(未知)、z(高阻)。logic类型不能有多个驱动源,线网默认是logic类型的。logic reset; logic [31:0] data;
2.1.2 实数类型
real:相当于double。shortreal:相当于float。realtime:同real。
实数赋值给整数会四舍五入。
2.1.3 字符串类型(string)
string是由byte构成的字符串,相当于char*。
string str1 = "system";
string str2 = "Verilog";
initial begin
$display(str1);
str1[0] = "S"; // 修改
$display(str1);
$display(str1 == str2); // 比较
$display(str2.len()); // 调用函数
$display("%s", {str1, str2}); // 拼接
end
// system
// System
// 0
// 7
// SystemVerilog2.1.4 枚举类型(enum)
未指定类型默认为int。枚举项的值默认从0开始,可以指定,如果指定第一个则后面的依次递增。
enum {GREEN, YELLOW, RED} light1, light2;
enum logic [1:0] {GOLD=2'd1, SILVER=2'd2, BRONZE=2'd3} medal = BRONZE;
typedef enum {S0, S1, S2} state_t;
typedef enum {S[3]} state_t; // 相当于{S0, S1, S2}
typedef enum {S[1:3]} state_t; // 相当于{S1, S2, S3}
state_t s = S1;2.2 数据对象
2.2.1 线网(net)
线网的值会根据驱动源的变化而按照指定的线网延迟而变化。线网必须是4-state类型的。
| 线网类型 | 说明 |
|---|---|
wire |
最常用,普通的连线 |
wand、wor |
线与、线或 |
| 其他 | 几乎用不到 |
wire n1;
wire logic n2; // 默认就是logic,加不加都行
wire [15:0] n3;
wire #5 n4; // 线网延迟
// 线与,多个驱动源会先连接到生成的与门再连接到n5
wand n5;
assign n5 = n1 & n2;
assign n5 = ~n4;2.2.2 变量
变量的值只有在赋值命令执行时才变化。只要不是线网类型就默认为变量。
var v1; // 默认是logic
var logic v2; // 最完整,但有些多余
bit clk;
logic reset;2.2.3 常量
parameter常量
elaboration-time(编译时)常量。必须在定义时指定值。
parameter:可以用于模块间参数传递。localparam:模块内使用。
parameter WIDTH = 4'd10; // 位宽4 parameter WIDTH32 = 12; // 位宽32 localparam left = 100;const常量
run-time(运行时)常量。运行时才给定值。如代码中直接使用的
4'b0101等。
2.3 初始值设置
位值扩展:
'0、'1、'x、'z可以将所有位设置为这些值。logic [7:0] a = '1; // 8'hff可以指定某一位的值和其余默认值。
logic [7:0] b = '{default: 1}; // 8'hff logic [7:0] c = '{7: 0, 3: 0, default: 1}; // 8'h77
2.4 数组
2.4.1 紧凑数组(packed)
名称前面具有紧凑维度的数组。声明为紧凑数组可以保证矢量的位在连续的区域中存储,可以进行分段操作。
logic [3:0][7:0] arr1;
initial begin
arr1 = '1;
arr1[1] = '0;
arr1[0][1] = '0;
$display("%h", arr1); // ffff00fd
arr1[2:1] = 16'habcd;
arr1[0][7:4] = 4'he;
$display("%h", arr1); // ffabcded
end2.4.2 非紧凑数组(unpacked)
名称后面具有非紧凑维度的数组。它的存储区域是不连续的。非紧凑数组只需指定大小,如下面的声明是等价的。
logic matrix [0:7][0:127];
logic matrix [8][128];不能进行分段操作。
logic [7:0] arr2 [3:0];
initial begin
// arr2 = '1; // error
arr2[1] = '0;
arr2[0][1] = '0;
// arr2[2:1] = 16'habcd; // error
arr2[0][7:4] = 4'he;
end2.4.3 动态数组
仅适用于非紧凑数组。
int array1[]; // 声明
logic [3:0] array2[] = '{4'h1, 4'h2, 4'h4, 4'h8}; // 声明并初始化
initial begin
array1 = new[5]; // 初始化
$display(array1); // '{0,0,0,0,0}
$display(array2); // '{1,2,4,8}
$display(array2.size()); // 返回数组大小 4
array1.delete(); // 清空数组
$display(array1); // '{}
end2.4.4 关联数组
类似python的字典。常用于字符串作为索引。
int data[string] = '{
"a": 100,
"b": 200
};
initial begin
$display(data); // '{"a":100,"b":200}
$display(data["a"]); // 100
data["c"] = 300; // 添加元素
data["a"] += 50; // 操作元素
$display(data); // '{"a":150,"b":200,"c":300}
end2.4.5 队列
int q1[$]; // 声明空队列
string q2[$] = {"a", "b", "c", "d"}; // 声明并赋初值,注意{}前没有'
int t;
initial begin
$display(q2.size()); // 返回大小 4
$display(q2); // '{"a","b","c","d"}
$display(q2[1:2]); // 指定区间 '{"b","c"}
q1.push_back(10); // 在队列最后面插入一个元素
q1.push_back(20);
q1.push_front(5); // 在队列最前面插入一个元素
$display(q1); // '{5,10,20}
t = q1.pop_back(); // 删除并返回最后面的元素
$display(t); // 20
t = q1.pop_front(); // 删除并返回最前面的元素
$display(t); // 5
$display(q1); // '{10}
end三、过程与进程
3.1 仿真过程
从仿真器中启动执行的电路仿真指令块,也叫过程语句。
3.1.1 initial 过程
仅在仿真开始时执行一次。
initial begin
reset = 0;
end3.1.2 always 过程
仿真开始时启动并重复执行。
敏感事件列表:电路依赖的所有信号必须被列举在敏感事件列表中。
通用 always 过程
always #5 clk = ~clk; always @(a) $display(a); always @(posedge clk) q <= b;always_comb 过程
用于描述组合逻辑电路。会自动生成敏感事件列表。
always_comb begin {c, s} = a + b; endalways @(*)也有类似的功能。有如下区别:always_comb会考虑子例程(如函数)中使用的信号,always @(*)不会;always_comb中左值不能在其他过程块中设定,always @(*)可以。
always_latch 过程
用于描述锁存电路。
always_latch begin if (sel) s = a; endalways_ff 过程
用于描述可综合的时序逻辑电路。需要有事件控制(用
posedge或negedge设置控制信号);不能使用阻塞赋值;左值不能在其他过程中被赋值。always_ff @(posedge clk) begin s <= a; end
3.1.3 final 过程
仿真结束时执行。不能描述需要电路耗时执行的指令,一般用来显示仿真结果。
final begin
$display(s);
end3.2 语句块
比如if-else语句,语法上要求后面只能用一条语句,这时就可以使用语句块将多条语句打包为一条语句。
可以在
begin或fork后面加上语句块的名称,便于引用。begin: name ...... end
3.2.1 顺序语句块(begin-end)
begin-end语句块中的语句从上到下依次执行。
3.2.2 并行语句块(fork-join)
fork-join语句块中的语句是并行执行的。
fork-join
join会阻塞父进程,直到全部子进程结束。function void print(input int value); $display("@%0t value=%0d", $time, value); endfunction initial begin fork #10 print(1); #20 print(2); #30 print(3); join $display("@%0t main completed", $time); end // @10000 value=1 // @20000 value=2 // @30000 value=3 // @30000 main completedfork-join_any
join_any会阻塞父进程,直到某一个子进程结束。initial begin fork #10 print(1); #20 print(2); #30 print(3); join_any $display("@%0t main completed", $time); end // @10000 value=1 // @10000 main completed // @20000 value=2 // @30000 value=3fork-join_none
join_none仅生成子进程(子进程不会立刻开始执行),父进程继续执行,直到父进程被阻塞时(如父进程使用#0)才执行子进程。initial begin fork print(1); #10 print(2); #20 print(3); join_none $display("@%0t scheduled", $time); #0 $display("@%0t main completed", $time); end // @0 scheduled // @0 value=1 // @0 main completed // @10000 value=2 // @20000 value=3
fork中生成的子进程在父进程被阻塞后才会开始执行,因此在必要的时候子进程需要保存父进程的环境(如使用automatic保存父进程的循环变量)。
3.3 定时控制
3.3.1 延时控制(#)
即#操作符。遇到延时控制时,不管延时的值为多少(如前面提到的#0),当前进程都会放弃其执行权。
3.3.2 事件控制(@)
即@操作符。,和or都可以作为事件的or使用,但更推荐,。
边缘敏感事件控制
事件 说明 posedge0到其他值,或x、z到1negedge1到其他值,或x、z到0edgeposedge 或 negedge 事件 always @(posedge clk) always @(posedge clk, negedge rst_n)状态敏感事件控制
always @a always @(a, b)
3.3.3 赋值定时控制
如果直接互相赋值会出现竞争冒险。改用赋值定时控制,会先暂时保存右边的值,事件发生后再将暂存的值赋值给左边。
fork
a = #5 b;
b = #5 a;
c = @(posedge clk) d;
join3.3.4 事件等待控制(wait)
wait语句。等待直到条件被满足时才执行。
initial begin
reset = 0;
#50 reset = 1;
end
initial begin
wait(reset);
$display("%0t", $time); // 50000
end四、赋值语句
4.1 连续赋值语句
即用assign修饰的赋值语句。连续赋值语句不断地将表达式右侧的值赋给左侧的线网/变量。
4.2 行为赋值语句
仅在语句执行条件满足时才为变量进行赋值。因此左侧只能是变量,而不能是线网,因为线网需要连续赋值。
即使是生成组合逻辑的 always_comb 左侧也不能是线网。
4.2.1 阻塞赋值语句
阻塞赋值的运算符包括:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=、<<<=、>>>=。
在顺序语句块中,一条语句执行完毕才执行后面的语句,即后面的语句被阻塞;而在并行语句块中,不会阻塞,同时执行。
4.2.2 非阻塞赋值语句
运算符为<=。用于多个没有依赖关系的变量同时赋值。时序逻辑电路必须使用非阻塞赋值语句。
五、运算符
5.1 优先级
记得一些常用的优先级就行:
- 一元运算符(如
-、!、~、&、++等)优先级最高 *、/、%是同一级;- 算术运算符高于比较运算符;
- 比较运算符高于逻辑运算符;
- 逻辑运算符中
&&高于||。
5.2 算术运算符
都是二元运算符,包括+、-、*、/、%、**。
a % b的结果符号和a的符号相同。
**是求幂,指数可以是小数,如x ** 0.5。
5.3 比较运算符
返回值:1bit的值。
大于小于这些就不说了,重点看一下相等的比较。
| 运算符 | 说明 |
|---|---|
a === b |
完全相等才返回1,包括对x和z的比较,结果不会变成x |
a !== b |
===的否定 |
a == b |
如果a或b包含x或z,则结果为x |
a != b |
==的否定 |
5.4 逻辑运算符
输入:非零值视为
1,零值视为0。返回值:1bit的值。
&&、||、!。如果判定非法则得到x。
5.5 位运算符
与(&)、或(|)、非(~)、异或(^)、同或(^~或~^)、逻辑移位(<<,>>),算术移位(<<<,>>>)。
5.6 单变量逻辑运算符
包括&、~&、|、~|、^、~^、^~。对操作数的位从左到右重复按位运算。如对于3位的a,&a = a[2] & a[1] & a[0]。
5.7 条件运算符
cond ? expr1 : expr2。
5.8 拼接运算符
拼接的表达式必须有固定的位数。
assign c = {a[2:1], 2'b11};
assign d = {a, {5{1'b0}}};5.9 比特流运算符
>>表示从左到右提取,<<表示从右到左提取。中间的数表示一次提取的位数,省略则为1,但对于>>来说没意义。可以用来反转或交换。
{ << {6'b001011}}) // 110100
{ << 2 {6'b001011}}) // 111000
{ << 4 {6'b001011}}) // 101100
{ >> {6'b001011}}) // 0010115.10 部分选择
vec[msb_expr:lsb_expr]就不说了。
logic [31:0] a_vec;
logic [0:31] b_vec;
a_vec[0 +: 8] // [7:0]
a_vec[15 -: 8] // [15:8]
b_vec[0 +: 8] // [0:7]
b_vec[15 -: 8] // [8:15]六、执行语句
6.1 条件语句
SystemVerilog中新增了几个关键字,用于 if 和 case 语句,适当使用可以优化综合结果。
| 修饰符 | 说明 | 综合结果 | 使用场景 |
|---|---|---|---|
| 无 | 普通if | 优先级编码器 | 简单场景 |
unique |
条件必须互斥;必须匹配一个分支 | 并行逻辑 | 互斥场景 |
unique0 |
条件必须互斥;可以不匹配任何分支 | 并行逻辑 | |
priority |
按优先级选择;必须匹配一个分支 | 优先级编码器 | 优先级场景 |
6.1.1 if 语句
// 比如sel只可能等于0,2,7
always_comb begin
unique if (sel == 0)
a = 0;
else if (sel == 2)
a = 1;
else if (sel == 7)
a = 2;
end6.1.2 case 语句
除了普通的case,还有casex和casez:
casez以z、?为通配符;casex以x、z、?为通配符。
6.2 循环语句
循环中可以使用break和continue语句。
6.2.1 for 语句
for循环的控制变量具有automatic属性。
initial begin
int array[];
array = new[10];
for (int i = 1; i <= array.size(); i++) begin
array[i] = i;
end
end6.2.2 foreach 语句
可用于所有的数组类型。只需指定数组名称和循环控制变量(写在[]中,编译器会自动创建)。
int q1[$] = {10, 20, 30};
initial begin
foreach (q1[i]) begin
$write("%0d ", q1[i]);
end
end6.2.3 repeat 语句
repeat语句按照指定的次数来执行循环,推荐不需要循环控制变量的循环使用repeat。
int q1[$];
initial begin
repeat (10) q1.push_back($urandom_range(0,7));
$display(q1); // '{3,3,4,6,5,1,3,0,5,7}
endrepeat(3) @(posedge clk) $display("%0t", $time);6.2.4 forever 语句
forever语句将进行无限次的循环。
forever #10 clk = ~clk;6.2.5 while 和 do-while 语句
和C语言同样的用法不再赘述。
6.3 generate 语句
在编译时会展开generate中的语句。
- 所有的表达式都必须是常量表达式。
- 可以在
generate中使用for、if和case语句。 - 循环变量必须使用
genvar声明(声明在 generate 内外都可以)。 generate中可以使用assign语句、always语句、实例化模块等。
generate
genvar i;
for (i = 0; i < 3; i++) begin
alu #(.WIDTH(8)) uut (.*, .sel(select), .c());
end
endgenerate七、任务和函数
| 特性 | 任务 | 函数 |
|---|---|---|
| 返回值 | 不返回数值,而是通过output或inout端口输出。 |
可以返回一个单一数值,通过return或函数名返回 |
| 消耗时间 | 可以包含任何定时控制语句 | 不能包含任何定时控制语句,不消耗仿真时间 |
| 用途 | 代表一个行为过程 | 类似数学函数,用于表达式 |
7.1 任务
任务相当于把一段语句进行了包装,当执行到任务时仿真器会跳转执行任务内部的语句,执行完毕后再跳转回来接着执行。除了ref,传参都是直接复制的。
端口方向
端口方向 说明 input值传递 inout值传递 output值传递 ref引用传递,可以在函数内直接修改外部变量 如果没有指定方向,则按以下规则:
- 如果第一个端口没有指定方向,则为
input; - 如果第二个及以后的端口没有指定方向,则与前一个端口相同。
- 如果第一个端口没有指定方向,则为
端口数据类型
端口都是变量,不能是线网。
如果没有指定数据类型,则按以下规则:
- 如果第一个端口没有指定数据类型,则为
logic; - 如果指定了方向而没有指定数据类型,则为
logic; - 否则和前一个端口相同。
- 如果第一个端口没有指定数据类型,则为
task reverse (input a, output b);
#10;
if (a == 0)
b = 1;
else
b = 0;
endtask
initial begin
x = 0;
y = 0;
reverse(x, y);
reverse(.b(y), .a(x)); // 可以指定端口
end7.2 函数
端口方向同样可以指定
input、inout、output、ref。如果没有指定方向或数据类型,规则同上。函数名会被定义为一个用于函数值返回的变量,因此可以直接对函数名赋值从而进行返回。除此也可以用
return返回。如果没有返回值则声明为void。函数可以设置参数默认值。
如下面的函数以动态数组
a[]为形参,可以接受各种数组类型。int a[5] = {1, 2, 3, 4, 5}; function int sum(input int a[], input int left=0, input int right=0); sum = 0; for (int i = left; i < right; i++) begin sum += a[i]; end endfunction initial begin $display(sum(a)); // 0 $display(sum(a, 0, 4)); // 10 $display(sum(.a(a) ,.left(1), .right(3))); // 5 $display(sum(a, .right(2))); // 3 end函数可以递归调用,此时需要声明为
automatic使得变量在堆栈区中分配。function automatic int factorial(input int n); factorial = n < 2 ? 1 : n * factorial(n - 1); endfunction initial begin $display(factorial(5)); // 120 end
八、模块
8.1 定义
module alu #(
WIDTH = 32
)(
input [WIDTH-1:0] a, b,
input [1:0] sel,
output [WIDTH-1:0] c
);
endmodule下面是Verilog风格的端口定义,不推荐。
module alu #(WIDTH = 32)(a, b, c, sel); input [WIDTH-1:0] a, b; input [1:0] sel; output [WIDTH-1:0] c; endmodule
8.2 端口信号列表
端口方向
端口方向可以为
input、inout、output、ref。如果没有指定方向,则按以下规则:- 如果第一个端口没有指定方向,则为
inout; - 如果第二个及以后的端口没有指定方向,则与前一个端口相同。
- 如果第一个端口没有指定方向,则为
端口信号类型
对于数据类型:如果省略了数据类型,默认为
logic。对于数据对象:可以是线网或变量。如果没有指定,则按以下规则:
- 对于
input和inout端口,设置为标准线网类型(wire); - 对于
output:- 如果指定了数据类型,则为变量;
- 否则为标准线网类型(仅指定了位宽也是线网);
- 此外
inout端口不能为变量,ref端口只能是变量。
- 对于
module mh(wire x); // inout wire logic
module mh(integer x); // inout wire integer
module mh(input x); // input wire logic
module mh(input var x); // input var logic
module mh([5:0] x); // inout wire logic
module mh(output x); // output wire logic
module mh(output var x); // output var logic
module mh(output logic x); // output var logic8.3 参数化模块
可以在端口信号列表前添加模块的参数(模块参数列表),这与在模块内部使用parameter效果是一样的。
如果使用了模块参数列表,后面定义的
parameter会被当成是localparam。如果端口中使用了这些参数则只能在模块参数列表定义。
模块参数列表中加不加
parameter都可以。
module alu #(WIDTH = 32)();
parameter DEPTH = 1; // =localparam
endmodule8.4 实例化模块
alu #(.WIDTH(8)) uut (a, b, sel, c);
alu #(.WIDTH(8)) uut (.a(a), .b(b), .sel(sel), .c(c));连线信号名称与端口信号名称一致时可以简写,对于不一致的端口单独写出即可。
alu #(.WIDTH(8)) uut (.a, .b, .sel, .c); // 简写
alu #(.WIDTH(8)) uut (.*); // 同上,更简写
alu #(.WIDTH(8)) uut (.*, .sel(select), .c()); // 单独指定不一样的