4. SystemVerilog基础(上):数据类型、数组、结构体、枚举、过程语句
各位同学,欢迎来到第四章。从这一章开始,我们正式进入SystemVerilog的世界。
说实话,我刚从Verilog转到SystemVerilog时,第一反应是:「这玩意儿也太香了吧!」 以前写验证环境,光是定义数据类型、连线、写状态机,就能把人逼疯。SystemVerilog把这些痛点全解决了。
今天我们先啃下前半部分:数据类型、数组、结构体、枚举,还有两个最重要的过程语句——always_comb和always_ff。
核心思想:SystemVerilog不是一门新语言,它是Verilog的超级增强版。你已有的Verilog知识全部有效,只是多了很多「偷懒」的工具。
4.1 数据类型:从wire/reg到logic
先问大家一个问题:Verilog里wire和reg的区别,你当年背了多久?
我记得刚学Verilog时,导师跟我说:「wire用于连续赋值,reg用于过程赋值。」我点点头,然后写代码时还是经常搞混。明明都是存一个值,为什么要分两种?
SystemVerilog的设计者显然也受够了。他们引入了一个新类型——logic。
// Verilog时代
wire clk;
reg [7:0] data;
assign data = 8'hFF; // 等等,data是reg,不能用assign?
// SystemVerilog时代
logic clk; // 可以是wire,也可以是reg
logic [7:0] data;
assign data = 8'hFF; // 没问题!
always_ff @(posedge clk) data <= 8'hAA; // 也没问题!
我的建议:从今天起,忘掉wire和reg。在验证环境里,90%的场景用logic就够了。只有当你需要多驱动源(比如双向总线)时,才回头用wire。
除了logic,SystemVerilog还引入了双状态数据类型:bit、int、byte等。它们只有0和1,没有X和Z。为什么需要这个?
我在项目中遇到过一个问题:仿真跑了一整天,结果因为某个信号莫名其妙变成了X,整个验证环境崩了。后来发现,那个信号根本不需要X态——它只是用来计数的。从那以后,所有计数器、索引变量,我全用int或bit。
int loop_count; // 循环计数,不需要X态
bit [3:0] state; // 状态编码,也不需要X态
logic [31:0] address; // 地址总线,可能有X,用logic
4.2 数组:定宽、动态、关联、队列
数组这块,SystemVerilog可以说是「鸟枪换炮」。以前Verilog只有定宽数组,现在四种数组任你选。
4.2.1 定宽数组
这个大家熟,就是Verilog的老朋友。不过SystemVerilog给了个新语法——合并数组和非合并数组。
// 合并数组:所有元素连续存放,像一个向量
logic [7:0] [3:0] mem_merged; // 8个4bit元素,连续存放
// 非合并数组:每个元素独立存放
logic [3:0] mem_unmerged [7:0]; // 8个4bit元素,独立存放
嗯,这里要注意:合并数组可以整体赋值、整体比较,非合并数组不行。我一般只在建模寄存器文件时用合并数组,其他场景用非合并。
4.2.2 动态数组
这个是我最喜欢的。以前写验证环境,数组大小必须在编译前确定。现在?运行时想多大就多大。
int dyn[]; // 声明一个动态数组
dyn = new[100]; // 分配100个元素
dyn = new[200](dyn);// 重新分配为200个,保留前100个的值
dyn.delete(); // 清空
避坑指南:我曾经在循环里反复new动态数组,结果仿真越跑越慢。后来才发现,每次new都会分配新内存,旧内存虽然会被回收,但频繁分配/释放会影响性能。建议:如果能预估最大大小,一次性分配到位。
4.2.3 关联数组
说白了就是哈希表。当你需要按名字或特殊索引查找数据时,用它准没错。
// 按名字查找
string name_to_addr [string];
name_to_addr["uart"] = "0x1000";
name_to_addr["spi"] = "0x2000";
// 遍历
foreach (name_to_addr[name]) begin
$display("%s -> %s", name, name_to_addr[name]);
end
4.2.4 队列
队列是动态数组的升级版。它支持在头部和尾部快速插入/删除。
int q[$]; // 声明一个队列
q.push_back(1); // 尾部插入
q.push_front(2);// 头部插入
q.pop_back(); // 尾部弹出
q.pop_front(); // 头部弹出
你想想看,如果你用动态数组实现队列,每次头部插入都要移动所有元素,多麻烦。队列就是为这种场景设计的。
4.3 结构体:把相关信号打包
以前写Verilog,如果有个AXI接口,你得定义几十个wire信号。每次例化都要一个个连,手都酸了。
SystemVerilog的结构体就是来解决这个问题的。
// 定义一个AXI地址通道的结构体
typedef struct packed {
logic [31:0] addr;
logic [3:0] id;
logic [7:0] len;
logic [2:0] size;
logic [1:0] burst;
} axi_addr_t;
// 使用
axi_addr_t addr_chan;
addr_chan.addr = 32'h1000;
addr_chan.len = 8'h0F;
addr_chan.burst = 2'b01;
关键点:struct packed会把所有成员按bit连续打包,保证内存布局和硬件一致。如果你只是做软件建模,用struct(不加packed)就行,更灵活。
我在项目中习惯把整个接口协议定义成一个结构体。比如AXI的五个通道,每个通道一个结构体。这样模块之间的连线瞬间从几十根变成几根。代码可读性提升不止一个档次。
4.4 枚举:让状态机不再神秘
枚举,说白了就是给数字起个名字。但别小看这个功能,它能让你的代码自文档化。
// 定义状态
typedef enum logic [2:0] {
IDLE = 3'b000,
READ = 3'b001,
WRITE = 3'b010,
WAIT = 3'b011,
DONE = 3'b100
} state_t;
state_t current_state, next_state;
为什么我推荐用枚举?因为仿真器会帮你检查。如果你给current_state赋了一个不在枚举列表里的值,仿真器会报错。这比你自己手写3'b101然后祈祷不出错要靠谱得多。
我的习惯:枚举值一定要显式指定编码。虽然不指定也能用,但指定了以后,综合工具会按你的编码方式实现,不会自作主张优化成独热码或其他格式。
4.5 过程语句:always_comb和always_ff
终于到了过程语句。这部分我要多说两句,因为太多人在这里翻车了。
4.5.1 always_comb:组合逻辑的正确打开方式
Verilog时代,我们写组合逻辑用always @(*)。但有个坑:如果敏感列表写漏了,仿真和综合结果就不一致。
always_comb完美解决了这个问题。它自动推断敏感列表,而且仿真器会检查你是否在块内写了锁存器。
// 老方法
always @(*) begin
if (sel)
out = a;
else if (!sel)
out = b;
// 如果sel是X,out保持原值?综合会生成锁存器!
end
// 新方法
always_comb begin
if (sel)
out = a;
else
out = b;
// 所有分支都覆盖了,没问题
end
避坑指南:我曾经在always_comb里写了一个for循环,循环里对同一个变量多次赋值。仿真没问题,但综合工具报错。后来发现,always_comb要求每个变量在每个仿真时刻只被赋值一次。如果你需要循环赋值,用always加automatic。
4.5.2 always_ff:时序逻辑的标准化
always_ff是专门用来描述触发器的。它的语法强制你指定时钟沿和复位方式。
// 标准写法
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 0;
else
q <= d;
end
// 或者只带时钟沿(异步复位单独写)
always_ff @(posedge clk) begin
q <= d;
end
你可能会问:这和always @(posedge clk)有什么区别?
区别在于:always_ff会检查你是否在块内使用了阻塞赋值(=)。如果你不小心写了q = d,仿真器会报错。这个检查在大型项目中特别有用——我见过太多因为阻塞/非阻塞混用导致的仿真与综合不一致的bug了。
4.6 小结
今天的内容不少,但都是基本功。总结一下:
- 数据类型:用
logic代替wire/reg,用int/bit代替不需要X态的场景 - 数组:定宽数组打基础,动态数组灵活,关联数组查表,队列做缓冲
- 结构体:把相关信号打包,接口连线瞬间清爽
- 枚举:状态机、命令码,用枚举代替魔法数字
- 过程语句:组合逻辑用
always_comb,时序逻辑用always_ff
这些知识点,每一个我都在实际项目中踩过坑、填过坑。希望你们能站在我的肩膀上,少走弯路。