
在数字电路设计领域,通常我们认为Verilog是一种设计语言,而SystemVerilog是专门用于验证的语言,不能用于设计。然而,这种观念是不准确的!事实上,SystemVerilog同样适用于设计,在某些方面甚至比Verilog更为便利。SystemVerilog在其设计之初,主要目标之一是更准确地创建可综合(synthesizable)的复杂硬件设计模型,并且使用更少的代码行数来实现这一目标。SystemVerilog-2005 并不是一种独立的语言,它只是 Verilog-2005 之上的一组扩展。IEEE在2009年,已将Verilog正式更名为SystemVerilog。

一些SystemVerilog特性可能会导致生成的硬件设计占用更多的资源。使用某些高级特性可能增加逻辑的复杂性,从而影响实际的硬件资源消耗。
总的来说,虽然SystemVerilog存在一些被批评的缺点,我们可以通过在设计上引入一些限制来避免这些问题,就像在编写Verilog时会避免使用for循环来设计电路一样。同时,由于SystemVerilog具备向下兼容性,意味着verilog不需要改任何代码,我们可以轻松地从“.v”文件切换到“.sv”文件,这为团队提供了更灵活的选择。
logic 传统的Verilog对于信号类型有严格的限制,通过reg和wire来描述一个信号。有时候,由于笔误或者混淆,我们可能会误将reg和wire搞混,从而导致编译错误。而SystemVerilog简化了这一过程,所有的信号都可以使用logic进行声明,使得代码更加清晰和一致。
module chip(
input wire in1,
input wire in2,
output reg out1,
output wire out2
);module chip(
input logic in1,
input logic in2,
output logic out1,
output logic out2
);结构体在SystemVerilog中的作用是将多个变量捆绑在一起,形成一个单一的结构。通过结构体,我们可以将相关信号组织在一个统一的名称下,提高了代码的清晰度和可读性。这种方式可以大幅减少代码行数,使得代码更为简洁。同时,结构体的使用也降低了因为声明不匹配而引起的错误的风险。结构体在设计周期较晚才能发现的一些错误,比如模块间不匹配或者遗漏的赋值,也可以得到有效的排除。这些优势共同使得结构体成为SystemVerilog中一种强大的工具。
struct {
logic [ 7:0] opcode;
logic [31:0] data;
logic status;
} operation;
operation = ’{8’h55, 1024, 1’b0};
operation.data = 32’hFEEDFACE;在设计状态机时,需要定义状态参数。在状态参数的定义中,如果存在重复,普通的Verilog编写是无法在编译时检测出的。这种问题可能在仿真或者上板测试之后才会被发现。然而,使用enum(枚举)类型可以在编译阶段及时发现这类问题,从而提高了代码的可靠性和调试的效率。
枚举(enum)类型在SystemVerilog中有一些严格的规则:
类似下面的代码,WAIT和DONE的值是相同的,显然这是错误的,但是verilog是无法检测出的,而使用enum,在编译时或者通过语法检查工具能立刻检查出来。
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 ornegedge 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;
endcaseenum logic [2:0] {
WAIT = 3'b001,
LOAD = 3'b010,
DONE = 3'b001
}state, next_state;
enumlogic [1:0]{
READY = 3'b101,
SET = 3'b010,
GO = 3'b110
}mode_control;
always_ff @(posedge clk ornegedge 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在 Verilog 中,多驱(multiple drivers)的问题通常只能在综合后才能被发现,这会延迟问题的暴露,增加调试成本。而在SystemVerilog中,借助其更严格的类型系统和增强的编译检查机制,我们可以在仿真或编译阶段就及时捕捉到多驱错误,极大提升了设计的安全性和开发效率。
module multi_driver_sv;
logic a;
initial a = 1'b0;
always #5 a = ~a; // 第一个驱动
always #10 a = 1'b1; // 第二个驱动(编译或仿真直接报错)
endmoduleSystemVerilog 引入了 用户自定义类型(User-Defined Types) 的概念,进一步扩展了 Verilog 的类型系统。通过 typedef 关键字,开发者可以基于内建类型或其他自定义类型创建新的类型别名,从而实现更灵活、可读性更强的数据建模方式。
简化复杂类型:只需定义一次,即可在多个位置引用,避免重复代码。
增强代码可维护性:修改类型时只需改动 typedef 处,便于统一管理。
保证类型一致性:在模块内保持数据类型一致,减少因类型不一致导致的设计或仿真错误。
提升代码可读性:使用具备语义的类型名(如 addr_t, pkt_t 等),比使用裸类型更直观。
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;module ALU (
input operation_t operation,
output bus32_t result
);
operation_t registered_op;
...
endmodule在 SystemVerilog 中,数组是构建模块接口、并行信号组、缓存结构等场景中不可或缺的工具。根据数据的排列与存储方式不同,数组可以分为打包数组(packed array)和非打包数组(unpacked array) ,各有特点与适用场景。
打包数组(packed array)是指将多个元素紧密排列在一个连续的向量中,在位级上表现为一个连续的比特流。这使得它特别适合表示位宽固定、结构紧凑的数据,比如数据总线、寄存器堆等。
logic [3:0][7:0] b; // 表示 4 个 8-bit 宽的数据,总共 32 位实际上,这会被等效视为logic[31:0],即4个打包的8-bit子字段。这种结构在综合和仿真中都更高效,波形显示也更清晰。
非打包数组(unpacked array)允许你存储更复杂的数据类型,如结构体、枚举、甚至是用户自定义类型,是表达「多个对象」时非常自然的选择。
logic [7:0] a [3:0]; // 表示 4 个 8-bit 宽的元素,非打包数组每个元素在内存中独立存在,更像是C语言中的数组。
import:
在 SystemVerilog 中,interface就像是一个多功能插座板,你可以把很多根线(信号)统一插进去,然后只用一个插头(接口)连接到模块上,大大简化了模块之间的连线。
传统方式下,如果模块之间要传输很多信号,得一根根地接线(一个端口一个信号),不仅容易出错,维护起来也很麻烦。而有了interface,就像提前打好一束线缆,把相关信号、功能甚至断言都打包在一起,插上即用,清晰又高效。
此外,接口还有一个特别实用的功能叫modport,它可以规定这个插座的插头和插孔哪些能输入、哪些能输出,避免接反,让设计更安全。
// 定义一个 interface,打包常用总线信号
interface bus_if;
logic clk;
logic rst_n;
logic [31:0] addr;
logic [31:0] data;
logic valid;
logic ready;
endinterface
// 生产模块
module producer(bus_if bus);
always_ff @(posedge bus.clkornegedge bus.rst_n) begin
if (!bus.rst_n)
bus.valid <= 0;
elsebegin
bus.addr <= 32'h1000;
bus.data <= 32'hDEADBEEF;
bus.valid <= 1;
end
end
endmodule
// 消费模块
module consumer(bus_if bus);
always_ff @(posedge bus.clk) begin
if (bus.valid) begin
$display("Received addr: %h, data: %h", bus.addr, bus.data);
end
end
endmodule
// 顶层模块实例化
module top;
logic clk, rst_n;
bus_if bus(); // 创建 interface 实例
assign bus.clk = clk;
assign bus.rst_n = rst_n;
producer u_prod(.bus(bus));
consumer u_cons(.bus(bus));
endmoduleinterface bus_if;
logic clk;
logic [7:0] data;
modport master (input clk, output data); // 主端写数据
modport slave (input clk, input data); // 从端只读数据
endinterface
module master(bus_if.master bus);
always_ff @(posedge bus.clk)
bus.data <= $random;
endmodule
module slave(bus_if.slave bus);
always_ff @(posedge bus.clk)
$display("Received data: %0d", bus.data);
endmodule在SystemVerilog中,package 是一种用于组织共享定义的机制,能够提升代码的一致性、复用性和可维护性,即便在可综合设计中也广泛适用。
你可以将package看作是更智能的头文件,不仅能存放常量、类型、函数、参数等定义,还能提供清晰的作用域管理,不会像 \include 那样引入重复、难以管理的代码。
package 提供命名空间(namespace)机制,你可以选择性地引入其中的内容,避免命名冲突。
而 Verilog 的 \include` 文件是直接将文本插入源文件,所有定义会全局可见,容易与其他文件中的名称冲突。
// file1.vh
typedef enum logic [1:0] {
IDLE, RUN, STOP
} state_t;
typedef enum logic [1:0] {
IDLE, RUN, STOP
} state_n;// file2.vh
typedef enum logic [1:0] {
RED, GREEN, BLUE
} state_t;
typedef enum logic [1:0] {
RED, GREEN, BLUE
} state_n;// top.sv
`include "file1.vh"
`include "file2.vh"
module top;
state_t current_state; // 编译报错:state_t 重复定义或冲突
endmodule// pkg1.sv
package pkg1;
typedefenumlogic [1:0] {
IDLE, RUN, STOP
} state_t;
typedefenumlogic [1:0] {
IDLE, RUN, STOP
} state_n;
endpackage// pkg2.sv
package pkg2;
typedefenumlogic [1:0] {
RED, GREEN, BLUE
} state_t;
typedefenumlogic [1:0] {
RED, GREEN, BLUE
} state_n;
endpackage// top.sv
import pkg1::state_t;
import pkg2::state_n;
module top;
state_t current_state; // 使用 pkg1 的 state_t,pkg2 的无干扰
endmodule你可以把一组常量、类型、函数、任务打包在一个 package 中,这就像一个逻辑模块,方便组织和复用。
比如创建一个用于 AXI 接口的工具包:
package axi_pkg;
typedef enum logic [1:0] {
OKAY, SLVERR, DECERR
} axi_resp_t;
function logic [31:0] align_addr(input logic [31:0] addr);
return addr & 32'hFFFFFFFC;
endfunction
endpackage你可以在任何地方这样用:
import axi_pkg::*;
axi_resp_t resp;
logic [31:0] addr_aligned = align_addr(addr);// file: my_defs_pkg.sv
package my_defs_pkg;
// 可综合常量
parameterint DATA_WIDTH = 32;
parameterint ADDR_WIDTH = 16;
// 类型定义
typedefenumlogic [1:0] {
IDLE,
READ,
WRITE,
ERROR
} fsm_state_t;
// 组合逻辑函数
functionlogic is_even(inputlogic [3:0] val);
return ~val[0];
endfunction
endpackage在模块中使用:
import my_defs_pkg::*;
module my_module #(parameter WIDTH = DATA_WIDTH) (...);
fsm_state_t state;
always_comb begin
if (is_even(addr))
// ...
end
endmodule在传统 Verilog 中,always @(*) 是组合逻辑的标准写法,always @(posedge clk) 是时序逻辑的写法,但这些语句本身并不能明确表达“这段代码是组合逻辑”还是“时序逻辑”,综合工具必须猜测工程师的意图,可能导致不符合预期的综合结果。
为了解决这个问题,SystemVerilog 引入了三种专门面向硬件设计的过程块:
always_comb:专用于组合逻辑
always_ff:专用于时序逻辑(如触发器)
always_latch:专用于锁存器,在设计中不推荐
这段代码中工具需要推断是组合逻辑还是锁存器逻辑,容易出错。
always @(mode)
if (!mode)
o1 = a + b;
else
o2 = a - b;现在工具知道你在写组合逻辑,并会检查你是否遗漏了分支、定义错误、阻塞赋值是否合理等组合逻辑规则。
always_comb
if (!mode)
o1 = a + b;
else
o2 = a - b;在传统 Verilog 中,使用casex和casez时,x/z/? 可能被错误地当作don’t care,这会导致危险的匹配错误,尤其在表达式本身含有未知值时,容易产生不可预期的行为,严重影响仿真和综合一致性。
为了解决这一隐患,SystemVerilog 引入了更安全的匹配方式
case () inside:
casex和casez的使用你可能希望它只在 sel 为 1x00(即 1000 或 1100)时才匹配Match A,但如果 sel 是 4'bxx00 呢?
casex 会把表达式中的 x 也当成 don't care,结果是:0x00 也会被错误匹配上
logic [3:0] sel = 4'b1x00;
casex (sel)
4'b1x00: $display("Match A"); // 你可能以为只会匹配1x00
4'b1000: $display("Match B");
default: $display("No Match");
endcasecase() inside 只允许 case项中有 don’t care,表达式的 x 仍被当作真实值,所以,如果 sel = 4'bxx00,它根本不会匹配 4'b1?00,而是触发default
logic [3:0] sel = 4'b1x00;
case (sel) inside
4'b1?00: $display("Match A"); // 只匹配高位为1的情况
4'b1000: $display("Match B");
default: $display("No Match");
endcase在 SystemVerilog 中,unique、unique0 和 priority 是用于增强 case 语句决策安全性的关键字。它们提供一种更清晰、更可综合的写法,用于取代传统的 parallel_case 和 full_case 合成指令(pragma)。相比传统 pragma,这些关键字具有如下优势:
使用 unique / unique0 / priority 能显著增强 RTL 的可读性、综合可控性和仿真一致性,推荐在正式设计中逐步替代 full_case 和 parallel_case 指令。
always_comb
unique case (state)
RDY: ...;
SET: ...;
GO : ...;
endcase表示所有分支(case 或 if-else)互斥,且必须命中其中一个。也就是说,在任意给定输入下,只能有一个分支为真,否则仿真将产生运行时错误。
logic [1:0] sel;
logic out;
always_comb
unique case (sel)
2'b00: out = 1;
2'b01: out = 0;
2'b1?: out = 1; // 这会覆盖 10 和 11
endcase如果 sel = 2'b10 和 sel = 2'b11 都同时匹配两个分支(某种条件下被误写成重复匹配),仿真时会 报错,帮助你发现设计上的问题。
与 unique 类似,但放宽了一个限制:允许没有分支命中(即所有条件都不成立) 。这在某些状态下,允许无操作或空操作的场景下很有用。
logic [1:0] sel;
logic out;
always_comb
unique0 case (sel)
2'b00: out = 1;
2'b01: out = 0;
2'b10: out = 1;
// 2'b11 不匹配任何分支,但不会报错
endcasesel = 2'b00、2'b01、2'b10:命中一个分支,正常;
sel = 2'b11:不命中任何分支,不会报错,但 out 值不确定(可能保留旧值),建议补充default或完善 case。
表示所有判断是优先级顺序执行的,如果多个条件为真,只执行第一个满足条件的分支,不会报错。也就是说,允许多个条件为真,但只取最上面的。
logic [1:0] sel;
logic out;
always_comb
priority case (sel)
2'b1?: out = 1; // 优先匹配,只要最高位为1,无论01/11 都命中
2'b01: out = 0; // 即使也匹配,也不会执行(被上面盖住)
2'b00: out = 0;
endcasesel = 2'b10 or 2'b11:首先命中 2'b1?,优先级高; sel = 2'b01:命中第二个分支; sel = 2'b00:命中最后一个;