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 no ref. Its value is passed to sample() (by position, or by name), or set with cov.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 a cg_arg (below).

Bins

bins=dict(name=vdc.bin(...), ...), with vdc.bin(v, ...) for values and ranges, vdc.bin_array(n, ...) for n bins over a range, and vdc.wildcard_bin(...). Without bins, bins are automatic from the Coverpoint[T] width or the enum’s members (option.auto_bin_max caps the width-derived ones). ignore_bins= and illegal_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 call self.assign_from(src) and super().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()