2. SystemVerilog基础:数据类型、操作符、过程块、接口与模块
各位同学,咱们今天聊聊SystemVerilog的基础。说实话,很多刚入行的朋友觉得这玩意儿就是Verilog的升级版,学学语法就完事了。但我在项目里摸爬滚打这么多年,发现真正用好它的人真不多。来,咱们一个一个掰扯清楚。
2.1 数据类型:从reg到logic的进化
先说说数据类型。我记得刚入行那会儿,公司还在用Verilog-95,那时候最头疼的就是reg和wire的区分。你写个组合逻辑,用reg吧,心里犯嘀咕——这玩意儿不是寄存器吗?用wire吧,又怕综合出来不对。
SystemVerilog直接给你整了个logic类型。说白了,它就是个万能类型。你写组合逻辑用它,写时序逻辑也用它。我现在的习惯是:只要不是多驱动场景,一律用logic。
| 类型 | 说明 | 我的建议 |
|---|---|---|
| logic | 四值逻辑(0,1,X,Z) | 日常首选,替代reg和wire |
| bit | 二值逻辑(0,1) | 验证环境用,RTL慎用 |
| int/byte | 有符号整型 | 循环变量、计数器 |
| string | 字符串类型 | 打印信息、文件路径 |
bit和int,仿真速度快。RTL设计里老老实实用logic,避免X态传播问题。
2.2 操作符:你可能会忽略的细节
操作符这块,大家都会用。但我想重点提两个:缩减操作符和流操作符。
缩减操作符长这样:&data、|data、^data。它能把一个向量缩减成1位。比如你想判断一个32位总线是否全为0,写if (~|data)比写if (data == 32'b0)简洁多了。
流操作符{>>{}}和{<<{}},说白了就是比特序重排。我在做DMA验证时遇到过一个问题:CPU写数据到内存,字节序搞反了。用流操作符一行代码搞定,省得写一堆for循环。
// 流操作符示例:比特序反转
logic [7:0] data_in = 8'hA5;
logic [7:0] data_out;
// 按位反转
data_out = {<<{data_in}}; // 结果是8'hA5 -> 8'hA5? 不对,是8'h5A
// 按字节反转
data_out = {>>{data_in}}; // 结果还是8'hA5,因为只有1个字节
data_out = {<<{data_in}} + 1;,结果综合出来完全不对。记住加括号:data_out = ({<<{data_in}}) + 1;
2.3 过程块:always_comb、always_ff、always_latch
嗯,这里要重点说说。Verilog时代就一个always,靠敏感列表区分组合和时序。SystemVerilog给了三个专用块:
- always_comb:组合逻辑专用。工具会自动推导敏感列表,你少写一个信号它也能发现。
- always_ff:时序逻辑专用。必须指定时钟沿和复位。
- always_latch:锁存器专用。说实话,我很少用这个——锁存器在ASIC里是个坑。
我个人习惯是:RTL代码里只用always_comb和always_ff。为什么?因为工具会帮你检查。比如你在always_comb里漏了赋值,工具直接报warning,省得你debug到半夜。
// 好的写法
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= '0;
else if (en)
cnt <= cnt + 1;
end
// 坏的写法(Verilog风格)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= '0;
else if (en)
cnt <= cnt + 1;
end
always_comb里写了#1延时,结果仿真和综合结果不一致。记住:always_comb里不允许用延时,这是语法规定。
2.4 接口:告别冗长的端口列表
接口(interface)是我最喜欢的SystemVerilog特性之一。你想想看,一个AHB总线有多少信号?地址、数据、控制、响应...少说20根。每个模块都要把这些信号列一遍,写起来烦,读起来更烦。
接口就是把这些信号打包成一个整体。我举个例子:
interface ahb_if (input logic hclk, input logic hrst_n);
logic [31:0] haddr;
logic [31:0] hwdata;
logic [31:0] hrdata;
logic hwrite;
logic [1:0] htrans;
logic hready;
// 可以定义modport
modport master (
output haddr, hwdata, hwrite, htrans,
input hrdata, hready
);
modport slave (
input haddr, hwdata, hwrite, htrans,
output hrdata, hready
);
endinterface
有了接口,模块例化就清爽多了:
module ahb_master (
ahb_if.master bus
);
// 直接用 bus.haddr、bus.hwdata
endmodule
2.5 模块:从黑盒到白盒
模块(module)是Verilog就有的概念,但SystemVerilog给它加了新玩法。比如你可以用import语句引入包,用parameter做参数化设计。
我重点说说参数化模块。做验证的都知道,DUT的位宽经常变。今天32位,明天64位。如果你写死了,改起来想哭。
module fifo #(
parameter DEPTH = 16,
parameter WIDTH = 8
)(
input logic clk,
input logic rst_n,
input logic wr_en,
input logic [WIDTH-1:0] wr_data,
input logic rd_en,
output logic [WIDTH-1:0] rd_data,
output logic full,
output logic empty
);
// 内部实现
endmodule
例化的时候直接改参数:
fifo #(.DEPTH(32), .WIDTH(16)) u_fifo (
.clk(clk),
.rst_n(rst_n),
// ...
);
2.6 知识体系总览
说了这么多,咱们用一张图把关系理清楚:
这张图把咱们今天讲的内容串起来了。你看,数据类型是砖瓦,操作符是工具,过程块是施工规范,接口和模块是设计蓝图。缺了哪一块,房子都盖不结实。
好了,今天就到这儿。记住我说的:多用logic、少用reg;接口一定要加modport;过程块选对类型。这些习惯养成了,后面写UVM、做覆盖率都会顺手很多。
可可精品资料库