2. SystemVerilog中的covergroup:定义、实例化与class的关系
2.1 什么是covergroup?
covergroup,说白了就是SystemVerilog里用来收集功能覆盖率的核心结构。它不像代码覆盖率那样自动就能拿到,你得自己定义——你想看哪些信号、哪些组合、哪些时序关系被覆盖到了。
我个人习惯把covergroup理解成一个“采样器”。你告诉它什么时候采样、采什么信号、怎么分组,它就会自动帮你统计。嗯,这里要注意:covergroup不是class,但它和class的关系非常密切。我们后面会细说。
2.2 covergroup的定义
先看一个最简单的例子:
covergroup cg_alu_op;
op_code: coverpoint alu.op;
endgroup
这个covergroup叫cg_alu_op,里面只有一个coverpoint,采样的是alu.op这个信号。每次调用sample()方法,它就会记录一次当前值。
你可能会问:就这么简单?对,定义covergroup就是这么简单。但实际项目中,我们通常会在里面加更多东西——比如bins、cross、option等等。不过那是后面章节的内容了。
我在项目中遇到过一种情况:有人把covergroup定义在module里,结果发现多个实例共享同一个covergroup,覆盖率数据全混在一起了。这个问题其实很常见,我们稍后会讲怎么避免。
2.3 covergroup的实例化
定义好covergroup之后,你得实例化它才能用。实例化的方式有两种:
方式一:直接实例化
cg_alu_op cg = new();
就这么一行。注意,covergroup的实例化用的是new(),和class一样。但covergroup不是class,它有自己的生命周期。
方式二:带参数的实例化
covergroup cg_bus @(posedge clk);
addr: coverpoint bus.addr;
data: coverpoint bus.data;
endgroup
cg_bus cg = new();
这里我加了一个时钟事件@(posedge clk)。什么意思呢?就是每次时钟上升沿,covergroup自动采样,不需要你手动调用sample()。我个人比较推荐这种方式,省心,不容易漏采。
2.4 covergroup与class的关系
这是很多人容易搞混的地方。covergroup和class到底什么关系?
我直接说结论:covergroup可以定义在class内部,也可以定义在class外部。但两者有本质区别。
场景一:covergroup定义在class外部
covergroup cg_packet;
len: coverpoint pkt.len;
endgroup
class packet;
rand bit [7:0] len;
// ...
endclass
这种情况下,covergroup是独立的。它需要你手动传入要采样的信号。说白了,它不认识任何class,你给它什么它就采什么。
场景二:covergroup定义在class内部
class packet;
rand bit [7:0] len;
rand bit [3:0] type;
covergroup cg_packet;
len: coverpoint len;
type: coverpoint type;
cross len, type;
endgroup
function new();
cg_packet = new();
endfunction
endclass
你看,covergroup直接定义在class里面,它就能直接访问class的成员变量。这样写的好处是:封装性好。每个packet对象都有自己的covergroup实例,互不干扰。
我在项目中遇到过这样的坑:有人把covergroup定义在class外部,然后在class里实例化。结果发现coverpoint里引用的信号是全局的,不是对象自己的。你想想看,多个对象共用一个covergroup,数据全乱了。
2.5 实例化时的注意事项
嗯,这里有几个要点,我用自己的血泪史总结的:
- 每个covergroup实例都是独立的:如果你创建了100个对象,每个对象都有自己的covergroup实例,覆盖率数据是分开的。最后报告会汇总所有实例的数据。
- 实例化时不能传参数:covergroup的
new()不支持参数传递。如果你想配置covergroup的行为,得用option或者config机制。 - 记得调用sample():如果你没有用事件触发,一定要在合适的地方手动调用
sample()。我见过有人只在仿真开始调了一次,结果覆盖率只有1%。
2.6 一个完整的例子
最后,我给大家看一个我在项目中实际用过的结构:
class ahb_transaction;
rand bit [31:0] addr;
rand bit [2:0] burst;
rand bit write;
covergroup cg_ahb @(posedge clk);
addr: coverpoint addr {
bins low = {[0:16'h0FFF]};
bins mid = {[16'h1000:16'h7FFF]};
bins high = {[16'h8000:16'hFFFF]};
}
burst: coverpoint burst;
write: coverpoint write;
cross addr, write;
endgroup
function new();
cg_ahb = new();
endfunction
endclass
这个例子把covergroup定义在class内部,用时钟事件自动采样。每个transaction对象都有自己的覆盖率数据,互不干扰。你想想看,这样是不是清晰多了?
好了,这一章就到这里。下一章我们会深入讲coverpoint和bins的定义,那才是真正体现覆盖率设计功底的地方。