第4章:SystemVerilog基础:数据类型、接口、断言、随机化、功能覆盖率基础语法
好,咱们进入正题。SystemVerilog,说白了就是Verilog的超级升级版。我刚开始做验证那会儿,还在用纯Verilog写testbench,那叫一个痛苦。后来转到SV,感觉就像从自行车换成了小汽车。这一章,我把最核心的几个知识点给你捋一遍。
4.1 数据类型:不只是reg和wire
Verilog里你只有reg和wire,但SV给了你一大把新玩具。我个人最常用的是logic,它基本可以替代reg和wire,省心不少。
logic [7:0] data; // 可以当reg用,也可以当wire用
bit flag; // 二值逻辑,仿真快
int count; // 32位有符号整数
byte c; // 8位有符号
shortint s; // 16位
longint l; // 64位
嗯,这里要注意:bit和logic的区别。bit只有0和1,logic有四态(0,1,X,Z)。如果你只是做验证环境里的临时变量,用bit就够了,仿真速度快不少。我在项目中遇到过有人全用logic,结果仿真跑得巨慢,后来换成bit,速度直接翻倍。
4.2 接口(interface):把信号打包
接口是SV里我最喜欢的一个特性。你想想看,以前用Verilog连模块,信号线拉得跟蜘蛛网似的。接口就是把一堆相关的信号打包成一个整体,清爽多了。
interface axi_if (input clk, rst_n);
logic [31:0] awaddr;
logic awvalid;
logic awready;
logic [31:0] wdata;
logic wvalid;
logic wready;
logic [1:0] bresp;
logic bvalid;
logic bready;
modport master (
output awaddr, awvalid, wdata, wvalid, bready,
input awready, wready, bresp, bvalid
);
modport slave (
input awaddr, awvalid, wdata, wvalid, bready,
output awready, wready, bresp, bvalid
);
endinterface
看到modport了吗?它定义了不同角色的视角。master和slave看到的信号方向是相反的。我曾经因为没用好modport,导致验证环境里信号方向搞反,查了整整两天bug。从那以后,我每个接口都必加modport。
4.3 断言(assertion):让bug无处遁形
断言是什么?说白了就是给设计加个自动检查器。你告诉它「正常情况下这里应该怎样」,如果违反了,它就立刻报警。
SV里有两种断言:立即断言和并发断言。我重点说说并发断言,它才是真正的利器。
// 检查:当valid拉高时,data不能为X
property p_data_valid;
@(posedge clk) valid |-> !$isunknown(data);
endproperty
a_data_valid: assert property (p_data_valid)
else $error("data has X when valid is high!");
// 检查:req拉高后,2个周期内ack必须拉高
property p_req_ack;
@(posedge clk) $rose(req) |-> ##[1:2] $rose(ack);
endproperty
a_req_ack: assert property (p_req_ack)
else $error("req to ack timeout!");
4.4 随机化:让验证自动化
随机化是SV验证的灵魂。你手动写100个测试用例,不如让机器自动生成10000个随机测试。但随机不是乱来,得有约束。
class Transaction;
rand bit [7:0] addr;
rand bit [31:0] data;
rand bit write; // 0: read, 1: write
// 约束:地址范围0x00-0xFF,但避开0x80-0x8F
constraint addr_range {
addr inside {[0:255]};
!(addr inside {[128:143]});
}
// 约束:70%概率写,30%概率读
constraint write_prob {
write dist {0 := 30, 1 := 70};
}
endclass
Transaction tr;
tr = new();
assert(tr.randomize()); // 随机化,失败就报错
为什么用assert(tr.randomize())而不是直接tr.randomize()?因为randomize可能失败(比如约束冲突),如果不检查,你后面用的就是未初始化的数据,查bug查到哭。我刚开始就吃过这个亏。
4.5 功能覆盖率:你到底测了多少?
很多人觉得覆盖率就是跑个脚本看看百分比。其实不然。代码覆盖率告诉你「代码跑到了没」,功能覆盖率告诉你「功能测到了没」。后者才是验证的核心。
covergroup addr_cov @(posedge clk);
// 覆盖地址范围:分成16个bin
addr_bins: coverpoint addr {
bins low = {[0:63]};
bins mid = {[64:127]};
bins high = {[128:191]};
bins top = {[192:255]};
}
// 交叉覆盖:地址段 x 读写操作
cross addr_bins, write;
endgroup
addr_cov cov_inst = new();
// 在monitor里调用:cov_inst.sample();
| 覆盖率类型 | 作用 | 我一般怎么用 |
|---|---|---|
| 代码覆盖率 | 检查代码行、分支、状态机是否跑到 | 作为基础,但不够 |
| 功能覆盖率 | 检查设计功能点是否被覆盖 | 验证的核心指标 |
| 断言覆盖率 | 检查断言是否被触发过 | 辅助,看断言有没有白写 |
核心观点:功能覆盖率不是越高越好,而是越「有意义」越好。我见过有人为了凑100%覆盖率,写了大量无意义的bin。记住:覆盖率是指导你「哪里还没测到」的工具,不是KPI。
4.6 把这些串起来:一个简单的验证片段
最后,我给你看一个把这些知识点串起来的小例子。假设我们要验证一个FIFO:
interface fifo_if (input clk, rst_n);
logic [31:0] din;
logic wr_en;
logic [31:0] dout;
logic rd_en;
logic full, empty;
endinterface
class FifoTransaction;
rand bit [31:0] data;
rand bit wr_en;
rand bit rd_en;
// 不能同时读写
constraint no_rw_same_time {
!(wr_en && rd_en);
}
endclass
module fifo_test;
fifo_if vif (.*);
FifoTransaction tr;
covergroup fifo_cov @(posedge vif.clk);
full_cov: coverpoint vif.full;
empty_cov: coverpoint vif.empty;
cross full_cov, empty_cov;
endgroup
initial begin
tr = new();
fifo_cov cov = new();
repeat (1000) begin
assert(tr.randomize());
vif.din <= tr.data;
vif.wr_en <= tr.wr_en;
vif.rd_en <= tr.rd_en;
@(posedge vif.clk);
cov.sample();
end
end
endmodule
你看,接口、随机化、覆盖率、断言(这里用了assert检查randomize),全用上了。这就是SV验证的基本套路。你把这个框架理解了,后面学UVM就轻松多了。
嗯,这一章内容不少,但都是基本功。下一章我们开始讲UVM,那才是真正的重头戏。