前言

System Verilog作为Verilog HDL的升级,对于我们开发FPGA是有非常大的帮助的,可以大大减轻码量,减少错误的产生。

由于龙芯杯的缘故,我们小组在商议后决定使用System Verilog来开发CPU,所以在此重拾计组,学习System Verilog并记录,方便后续翻看和为后来人抛砖引玉。

TIPS:学习这篇博客需要默认会Verilog HDL

logic

对verilog的初学者来说,我们常常面临两种数据类型的选择——wire和reg,也常常纠结于在特定情况下选择哪一种数据类型比较合适的问题。

并且reg变量好像暗指用时序逻辑的触发器搭建的硬件“寄存器”,然而实际上,reg变量跟推断出的硬件没有任何关系,这种不一致也常常有着误导作用。

System Verilog使用更直观的logic关键字来描述通用的针对硬件的数据类型。我们将会看到你可以在过去verilog中用reg型或是wire型的地方用logic型来代替。编译器可自动推断logic是reg还是wire。

即logic是对reg,wire数据类型的改进,使得它除了作为一个变量之外,还可以被连续赋值、门单元和模块所驱动,显然,logic是一个更合适的名字。

但以上的等同是在单驱动的情况下,因为logic只允许一个输入,wire则无此限制。所以如果是多驱动的情况下,logic和wire就不等同了。

省流:你可以简单认为logic是Verilog中wire和reg的平替。

新状态

两态:0和1.
四态:0,1,x和z.
在Verilog中,所有的变量和数据类型都是四态。
在System Verilog中,则作出了区分。
四态的变量或数据类型可以接受两态的变量或数据类型。
两态的变量或数据类型也可以接受四态的变量或数据类型,只不过x,z会变为0.

新变量

bit——1位两态整数。
byte——8位两态整数,类似于C语言的char。
shortint——16位两态整数,类似于C语言的short。
int——32位两态整数,类似于C语言的int。
longint——64位两态整数,类似于C语言的longlong。

always

在旧版本的Verilog中只有一个通用的always过程块,System Verilog中追加了三个具有更明确目的专用always块:always_ff, always_comb, always_latch.
与原始的always块一样,这三个新的东西也是无限循环过程块,即每一个仿真周期都执行。

那可能就有小问号要问了? 这System Verilog开发者是不是在梦游,这不多此一举吗?


那当然是有合理性的,三个新的always块是专门针对可综合性RTL逻辑建模而定义的:
always_comb用于可综合组合逻辑的建模
always_ff用于可综合时序逻辑的建模
always_latch用于锁存器的建模
这样从名字就可以清楚地知道设计者的设计意图。人和各种EDA工具都可以更准确地理解代码并作出正确而相互一致的判断。从而编译器可以根据你的always块的不同帮你判断是否存在一些问题。比如在always_comb里面失误被编译出了锁存器,这是为了你硬件设计少出问题的考虑。

省流:你可以简单认为always_comb是always @(*)的平替,always_ff @(posedge clk)是always @(posedge clk)的平替

改进的case语句

Verilog的case语句允许在多个选项中选择一个逻辑分支。
在System Verilog中,我们当然可以继续使用case语句,但是这里也引入了两种新的case语句:
unique case和priority case。

unique

该语句指定:
1.只有一个条件选项与条件表达式匹配。
2.必须有一个条件选项与条件表达式匹配。

这并不意味着我们不再需要default语句,相反,由于在复杂工程下我们很可能忘掉某些条件选项,所以default反而是必须的。这也同时为了防止锁存器的出现。


在这种语句下,每一个选项可以被并行执行判断,极大的提高了判断的效率。(简直是卷性能必备x)

priority

该语句指定:
1.至少有一个条件选项的值与条件表达式匹配。
2.如果有多个条件选项的值与条件表达式匹配,必须执行第一个匹配分支。
写法方面,由于这个和一般的case有所区别,所以给出样例:

1
2
3
4
5
6
7
always_comb
priority case (1'b1)
irq0: irq = 4'b0001;
irq1: irq = 4'b0010;
irq2: irq = 4'b0100;
irq3: irq = 4'b1000;
endcase

可以看出,常量取代了原来case中变量的位置,反而是变量取代了case中常量的位置。
这个可以取代对一个变量进行if,else if,else if,else的判断过程,(同样是卷性能必备x)

全面C语言化

System Verilog加入了很多十分类C的语句:

break

类似于C语言,会立即终止循环的执行。

continue

类似于C语言,会跳转到循环的结尾然后执行循环控制。

return

类似于C语言,用来从非空函数返回或从空函数或任务中跳出。

typedef

System Verilog新增内容
基本格式为: typedef 已有类型 新类型

1
2
3
typedef logic[31:0] word_t;
typedef logic[31:0] entry_t;
typedef entey_t[31:0] table_t; //数组来了

struct

System Verilog新增内容
结构体struct可以描述一组相关的数据。
以译码器为例,按以前Verilog写法,可能需要这样写:

1
2
3
4
5
6
logic [3:0] alufunc;
logic mem_read;
logic mem_write;
logic regwrite;
logic [6:0] control;
assign control = {alufunc,mem_read,mem_write,regwrite};

在System Verilog中,利用Struct我们可以这样写:
1
2
3
4
5
6
7
8
9
10
11
typedef struct packed {
logic [3:0] alufunc;
logic mem_read;
logic mem_write;
logic regwrite;
} control_t;

control_t control = '{4'b1111,1'b0,1'b1,1'b1}; //赋值
control_t control = '{alufenc : 4'b1111,mem_read : 1'b0, mem_write: 1'b1,regwrite : 1'b1}; //另一种赋值
logic regwrite;
assign regwrite = control.regwrite; //使用

从上面例子我们需要注意,System Verilog使用’{}符号包含数值列表,与{}拼接符作出区分。
第一种赋值方法需要我们按照结构体中元素定义顺序进行赋值,第二种则随意。

如果不用typedef进行包装struct,在System Verilog中则认为生成了一个这样的结构体变量,而不是定义。

enum

System Verilog新增内容
枚举类型用来帮助编码
以状态机为例,按以前Verilog写法,可能需要这样写:

1
2
3
4
5
6
7
8
9
10
11
`define start 4'd1
`define add 4'd2

case (State)
start: begin
//...
end
add: begin
//...
end
endcase

在System Verilog中,我们可以利用enum这样写:
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef enum logic [3:0] {
start,add
} State;

State state;
case (state)
start: begin
//...
end
add: begin
//...
end
endcase

enum类型的变量,赋值时只能用枚举项,枚举项名字不能相同。
enum类型定义时,如果不显示说明某一枚举项的值,则会从上到下从左到右对应0,1,2,3….

enum类型的变量,在Vivado仿真里会显示枚举项,比看magic number强多了。(debug必备啊)

union

System Verilog新增内容
用于多周期处理器,节省空间
但我个人感觉不管是在C还是在System Verilog里面,它都挺没存在感的。
使用方法样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef union packed {
struct packed {
logic zero;
logic [31:0] aluout;
} alu;
struct packed {
logic branch_taken;
logic [31:0] pcbranch;
} branch;
struct packed {
logic [31:0] addr;
logic mem_read;
} memory;
} result_t;

result_t res;
logic [31:0] addr,aluout;
assign addr = res.memory.addrl //assign addr = res[32:1];
assign aluout = res.alu.aluout //assign aluout = res[31:0];

需要注意的是,对每个Union里面的元素,必须要求位宽相同,这就是为什么在结构体alu里面我会补一个logic zero的原因。
对union类型的变量进行赋值时,要注意多驱动,因为赋值的可能是同一位。

结语

System Verilog还有很多很多零碎而又强大的功能提升,笔者以上所写为常用到的语法内容,更多的还请自行查阅,因为笔者也不会QAQ.当然,如果后面有好用的我还会更新的~

参考文献

《SystemVerilog 硬件设计及建模》,北航图书馆就有,不过目前都在我们小组手上(逃
bilibili大学