Coverage
A covergroup is a @vdc.dataclass that inherits vdc.Covergroup.
Coverpoints are fields annotated vdc.Coverpoint[T], and crosses are fields
annotated vdc.Cross. The coverage semantics are IEEE 1800-2023 §19, and
vdc covergroups report through the same model and UCIS export as classic ones
(PyVSC Coverage).
class Op(IntEnum):
ADD = 0
SUB = auto()
AND = auto()
OR = auto()
@vdc.dataclass
class AluCov(vdc.Covergroup):
# Push coverpoints: the synthesized sample() takes (op, mode) in order.
op: vdc.Coverpoint[Op] = vdc.coverpoint() # enum auto-bins
mode: vdc.Coverpoint[vdc.u2] = vdc.coverpoint(
bins=dict(lo=vdc.bin(0, 1), hi=vdc.bin(2, 3)))
op_x_mode: vdc.Cross = vdc.cross(lambda s: s.op, lambda s: s.mode)
cov = AluCov()
cov.sample(Op.ADD, 0) # push coverpoints, in declaration order
print(cov.get_coverage()) # cumulative (type) coverage
print(cov.get_inst_coverage()) # this instance
Coverpoints
- Push coverpoint
vdc.coverpoint()with noref. Its value is passed tosample()(by position, or by name), or set withcov.op.set(v).- Pull coverpoint
vdc.coverpoint(ref=lambda s: <expr>), evaluated from the covergroup’s state when it is sampled: an arithmetic expression, or a read through acg_arg(below).- Bins
bins=dict(name=vdc.bin(...), ...), withvdc.bin(v, ...)for values and ranges,vdc.bin_array(n, ...)fornbins over a range, andvdc.wildcard_bin(...). Withoutbins, bins are automatic from theCoverpoint[T]width or the enum’s members (option.auto_bin_maxcaps the width-derived ones).ignore_bins=andillegal_bins=take the same specs.iff=lambda s: ...guards a coverpoint.
Sampling inputs
vdc.cg_arg()A binding to an external object, given at construction:
TxnCov(txn=t). Pull coverpoints read it.vdc.sample_arg()A persistent sample formal that feeds pull coverpoints.
- A custom
sample() Override
sample()to extract values yourself, then callself.assign_from(src)andsuper().sample().
The synthesized sample() has the covergroup’s real signature (push
coverpoints in declaration order, then sample_arg fields), so
inspect.signature(cov.sample) and editor completion show them, and a wrong
call raises Python’s own TypeError. A formal you leave out keeps its
previous value. Static type checkers cannot see this generated signature; for
checker-visible code, set the typed sample_arg attributes and call
cov.sample(), or use assign_from(src).
Crosses
vdc.cross(lambda s: s.a, lambda s: s.b) crosses coverpoints; a cross of
crosses flattens to its component coverpoints.
A cross’s ignore_bins and illegal_bins select cross bins with
vdc.binsof (IEEE 1800 §19.6): vdc.binsof(s.a) selects a component, and
.intersect([...]) narrows it to values and (lo, hi) ranges. &,
| and ~ combine selections. Ignored cross bins are left out of the
coverage; illegal ones are left out and their hits are counted separately.
@vdc.dataclass
class AddrCov(vdc.Covergroup):
# Two address coverpoints, 4 uniform bins each over 0..255
# (bin k covers [k*64 .. k*64+63]); a 4x4 = 16-bin cross.
src: vdc.Coverpoint[vdc.u8] = vdc.coverpoint(
bins=dict(s=vdc.bin_array([4], (0, 255))))
dst: vdc.Coverpoint[vdc.u8] = vdc.coverpoint(
bins=dict(d=vdc.bin_array([4], (0, 255))))
src_x_dst: vdc.Cross = vdc.cross(
lambda s: s.src, lambda s: s.dst,
# Don't care about low-src/high-dst transfers: ignore that one cross bin.
ignore_bins=dict(
dontcare=lambda s: vdc.binsof(s.src).intersect([(0, 63)])
& vdc.binsof(s.dst).intersect([(192, 255)])),
# A transfer to itself in the top page is illegal: track if it ever happens.
illegal_bins=dict(
self_top=lambda s: vdc.binsof(s.src).intersect([(192, 255)])
& vdc.binsof(s.dst).intersect([(192, 255)])))
binsof must name a component of the same cross. Selecting a named bin
(binsof(s.a.lo)) is not supported yet; narrow with .intersect(...).
The randomize-and-sample loop
@vdc.dataclass
class Txn(vdc.RandClass):
op: vdc.u4 = vdc.rand()
addr: vdc.u8 = vdc.rand()
@vdc.constraint
def c(self):
self.op <= 3
self.addr < 64
@vdc.dataclass
class TxnCov(vdc.Covergroup):
txn: Txn = vdc.cg_arg() # read-through to the live object
op_cp: vdc.Coverpoint[vdc.u4] = vdc.coverpoint(
lambda s: s.txn.op, bins=dict(ops=vdc.bin_array([], [0, 3])))
addr_cp: vdc.Coverpoint[vdc.u8] = vdc.coverpoint(
lambda s: s.txn.addr,
bins=dict(lo=vdc.bin([0, 31]), hi=vdc.bin([32, 63])))
def main():
import random
random.seed(0)
txn = Txn()
cov = TxnCov(txn=txn)
n = 0
while cov.get_coverage() < 100 and n < 1000:
txn.randomize()
cov.sample()
n += 1
print(f"reached {cov.get_coverage()}% coverage after {n} transactions")
assert cov.get_coverage() == 100
if __name__ == "__main__":
main()