导言
在数字电路设计领域,通常我们认为Verilog是一种设计语言,而SystemVerilog是专门用于验证的语言,不能用于设计。然而,这种观念是不准确的!事实上,SystemVerilog同样适用于设计,在某些方面甚至比Verilog更为便利。SystemVerilog在其设计之初,主要目标之一是更准确地创建可综合(synthesizable)的复杂硬件设计模型,并且使用更少的代码行数来实现这一目标。SystemVerilog-2005 并不是一种独立的语言,它只是 Verilog-2005 之上的一组扩展。IEEE在2009年,已将"Verilog"正式更名为"SystemVerilog"。
1SV的优劣
优势
-
大多数主流EDA工具中都被支持,如Vivado、Quarturs、VCS、Modelsim都支持SystemVerilog设计。
-
SystemVerilog是Verilog的扩展,因此兼容Verilog。这意味着现有的Verilog代码可以逐步迁移到SystemVerilog,而且系统设计团队可以逐步采用SystemVerilog,无需彻底改变整个设计流程。
-
SystemVerilog引入了许多高级建模特性,如类、接口、和多态性。这些特性使得对复杂硬件结构进行更抽象和结构化的建模变得更为容易,减少了代码量,提高了可读性和可维护性。
-
SystemVerilog在某种程度上可以帮助减少编码时的拼写错误(coding typo)。
劣势
- 一些SystemVerilog特性可能会导致生成的硬件设计占用更多的资源。使用某些高级特性可能增加逻辑的复杂性,从而影响实际的硬件资源消耗。
总体分析
总的来说,虽然SystemVerilog存在一些被批评的缺点,我们可以通过在设计上引入一些限制来避免这些问题,就像在编写Verilog时会避免使用for循环来设计电路一样。同时,由于SystemVerilog具备向下兼容性,意味着verilog不需要改任何代码,我们可以轻松地从".v"文件切换到".sv"文件,这为团队提供了更灵活的选择。
2SV可综合语法介绍
logic
传统的Verilog对于信号类型有严格的限制,通过reg和wire来描述一个信号。有时候,由于笔误或者混淆,我们可能会误将reg和wire搞混,从而导致编译错误。而SystemVerilog简化了这一过程,所有的信号都可以使用logic进行声明,使得代码更加清晰和一致。
lua
module chip
(input wire in1,
input wire in2,
output reg out1,
output wire out2
);
lua
module chip
(input logic in1,
input logic in2,
output logic out1,
output logic out2
);
enum
枚举类型在SystemVerilog中有一些严格的规则:
-
标签值必须与变量相同大小:枚举类型在定义时会指定每个标签(即枚举值)的二进制表示的大小。在使用时,赋给枚举变量的值必须与该大小一致。这确保了枚举变量的合法取值范围。
-
只能被赋予枚举列表中的标签:枚举变量只能被赋予在枚举类型定义中列举的标签值。这防止了变量被赋予无效或不相关的值。
-
可以赋值为相同枚举类型变量的标签值:允许将一个枚举变量赋值为同一枚举类型的另一个变量的标签值。这有助于在程序中传递和比较枚举状态。
-
其他赋值操作是非法的:除了上述规定的情况,任何其他的赋值操作都是非法的。这种限制确保了枚举类型的严格性,防止不同类型之间的混淆或错误的赋值。
在设计状态机时,需要定义状态参数。在状态参数的定义中,如果存在重复,普通的Verilog编写是无法在编译时检测出的。这种问题可能在仿真或者上板测试之后才会被发现。然而,使用enum(枚举)类型可以在编译阶段及时发现这类问题,从而提高了代码的可靠性和调试的效率。类似下面的代码,WAIT和DONE的值是相同的,显然这是错误的,但是verilog是无法检测出的,而使用enum,在编译时或者通过语法检查工具能立刻检查出来。
ini
parameter [2:0] WAIT = 3'b001,
LOAD = 3'b010,
DONE = 3'b001;
parameter [1:0] READY = 3'b101,
SET = 3'b010,
GO = 3'b110;
reg [2:0] state, next_state;
reg [2:0] mode_control;
always @(posedge clk or negedge rstN)
if (!resetN)
state <= 0;
else
state <= next_state;
always @(state) // next state decoder
case (state)
WAIT : next_state = state + 1;
LOAD : next_state = state + 1;
DONE : next_state = state + 1;
endcase
always @(state) // output decoder
case (state)
WAIT : mode_control = READY;
LOAD : mode_control = SET;
DONE : mode_control = DONE;
endcase
ini
enum logic [2:0] {
WAIT = 3'b001,
LOAD = 3'b010,
DONE = 3'b001
}state, next_state;
enum logic [1:0]{
READY = 3'b101,
SET = 3'b010,
GO = 3'b110
}mode_control;
always_ff @(posedge clk or negedge rstN)
if (!resetN)
state <= 0;
else
state <= next_state;
always_comb // next state decoder
case (state)
WAIT : next_state = state + 1;
LOAD : next_state = state + 1;
DONE : next_state = state + 1;
endcase
always_comb // output decoder
case (state)
WAIT : mode_control = READY;
LOAD : mode_control = SET;
DONE : mode_control = DONE;
endcase
struct
结构体在SystemVerilog中的作用是将多个变量捆绑在一起,形成一个单一的结构。通过结构体,我们可以将相关信号组织在一个统一的名称下,提高了代码的清晰度和可读性。这种方式可以大幅减少代码行数,使得代码更为简洁。同时,结构体的使用也降低了因为声明不匹配而引起的错误的风险。结构体在设计周期较晚才能发现的一些错误,比如模块间不匹配或者遗漏的赋值,也可以得到有效的排除。这些优势共同使得结构体成为SystemVerilog中一种强大的工具。
ini
struct {
logic [ 7:0] opcode;
logic [31:0] data;
logic status;
} operation;
operation = '{8'h55, 1024, 1'b0};
operation.data = 32'hFEEDFACE;
typedef
SystemVerilog引入了用户自定义类型(User-Defined Types)的概念,以扩展Verilog的类型系统。使用typedef关键字可以定义新的类型,这些类型可以基于内建类型或其他用户自定义类型。然后,可以将变量和网络声明为这些用户自定义类型。通过使用typedef,可以一次性地定义复杂的数据类型,然后在代码中多次使用这个新类型。这提高了代码的可重用性,降低了代码的冗余度,使得代码更加简洁。使用用户自定义类型有助于确保在模块内部使用的数据类型是一致的。这有助于防止因为使用不一致的数据类型而引起的错误,提高了代码的可读性和可维护性。
arduino
typedef logic [31:0] bus32_t;
typedef enum [7:0] {ADD, SUB, MULT, DIV, SHIFT, ROT, XOR, NOP} opcodes_t;
typedef enum logic {FALSE, TRUE} boolean_t;
typedef struct {
opcodes_t opcode;
bus32_t data;
boolean_t status;
} operation_t;
arduino
module ALU (
input operation_t operation,
output bus32_t result
);
operation_t registered_op;
...
endmodule
数组
「打包数组」 打包数组(packed array)是一种允许存储多个元素的数据结构,其中元素被紧密地打包在一起。
css
logic [3:0][7:0] b;
「非打包数组」 非打包数组不再仅限于基本数据类型,还可以包含结构体、用户自定义类型等复杂的数据结构。这使得数组能够更灵活地存储和组织各种类型的数据。然而,需要注意的是,不建议使用非打包数组,因为在仿真过程中,非打包数组无法一次记录所有波形,可能会导致波形显示不完整的情况。
ini
logic [7:0] a1 [0:1][0:3];
logic [7:0] a2 [2][4];
a1 = '{'{7,3,0,5},'{default:'1}};
a2 = a1;
package
package 主要用于封装相关的功能、任务、函数、变量等,并提供了一种结构化的组织方式。package 定义了一个独立的命名空间,防止命名冲突,使得在不同的包中可以使用相同的标识符而不发生冲突。通过 package,可以将相关功能块打包起来,以便在不同的设计中重用,提高了代码的可重用性。package 提供了封装代码的能力,可以将内部实现细节隐藏起来,只暴露外部接口,有助于降低代码的复杂性。
ini
package design_types;
typedef struct {
logic [ 3:0] GFC;
logic [ 7:0] VPI;
logic [15:0] VCI;
logic CLP;
logic [ 2:0] T;
logic [ 7:0] HEC;
logic [ 7:0] Payload [48];
} uni_t; // UNI cell definition
endpackage
lua
module transmit_reg (
output design_types::uni_t data_reg,
input design_types::uni_t data_packet,
input logic clock, resetN
);
always @(posedge clock or negedge resetN)
if (!resetN)
data_reg <= '{default:0};
else
data_reg <= data_packet;
endmodule
interface
在SystemVerilog中,接口(interfaces)是一种复合的、多信号端口的机制。接口能够捆绑任意数量的信号(网络和变量),将它们有机地组合在一起,形成一个单一的接口。除了包含信号外,接口还可以捆绑"方法"(任务和函数),从而将信号与相关的功能一起组合。另外,接口还支持捆绑断言检查,将断言与信号关联,用于执行设计中的一些必要检查。
通过将多个信号、方法和断言捆绑到接口中,可以显著简化复杂总线的定义以及模块之间的互连。这种结构化的设计方法使得整体设计更加模块化和易于理解。使用接口有助于确保在整个设计中使用的总线和互连是一致的。由于所有相关的信号和方法都被有序地组织在接口中,可以有效减少在设计中引入错误的可能性。这使得接口成为处理大型系统中复杂总线和模块间通信的一种有力工具。
ini
interface chip_bus;
logic [31:0] data, address;
logic request, grant,
boolean_t ready;
endinterface
module CPU
( chip_bus bus ,
input logic clk ,
input logic reset
);
...
4总结
本文主要介绍一些SystemVerilog可综合的基本语法,未来将继续深入探讨一些更高级的逻辑块。
5往期回顾
FPGA------FIFO千万不要这样操作!赛灵思FIFO跑飞问题记录
术语摘录------集成电路中的断言(assert)是什么意思?
网络QoS系列:带你了解IP数据报TOS字段的前世今生(从IP优先级到DSCP)
6写在最后
创作不易,如果觉得这篇文章对您有用的话,记得点赞关注哦~
个人创作难免纰漏,如有问题欢迎交流
END
本文由本账号所属公众号AdriftCoreFpga提供,欢迎关注获取第一时间更新