Functional Coverage Strategy
SystemVerilog's own functional coverage page covers covergroup/coverpoint/cross syntax. It doesn't cover the judgment calls that decide whether a covergroup is actually useful or just technically present — the same gap Testbench's own coverage integration page named explicitly and deferred here. This page is that judgment.
What's worth a coverpoint
Every coverpoint should trace back to a specific line in a verification plan (Section E covers the document itself) — a scenario the spec actually calls out as needing to be exercised, not a variable that happened to be convenient to sample. A coverpoint tracking a field with no meaningful legal/illegal distinction, or one whose every value is equally uninteresting, adds tracking overhead without adding a real signal anyone will act on.
What's worth a cross, and what it costs
A cross doesn't just track two coverpoints — it tracks every combination of their bins, which grows multiplicatively, not additively. Two coverpoints with 8 bins each cross into 64 combinations; three coverpoints with 8 bins each cross into 512. Crossing everything with everything is how a covergroup silently becomes a coverage-closure problem no one can actually finish — most of those combinations are either uninteresting or structurally illegal.
illegal_bins and ignore_bins exist specifically to keep a cross honest: excluding combinations that genuinely can't happen (illegal) or genuinely don't matter (ignore) before they show up as unreachable holes someone has to investigate later. Used reactively — added the moment a cross first reports a hole that turns out to be structurally impossible — this keeps the coverage database's own gaps meaningful, rather than diluted by combinations nobody ever intended to track.
Bin sizing: auto vs. explicit
Automatic bins (SystemVerilog's default, one bin per value in a coverpoint's range) work fine for small enumerations — an opcode field, a 2-bit mode select. They stop being useful the moment a field is wide enough that its automatic bin count becomes unreadable (a full 32-bit data bus would auto-generate billions of bins). Explicit bins — grouping a wide range into a small number of named, meaningful buckets (bins low = {[0:15]}; bins mid = {[16:239]}; bins high = {[240:255]};) — earn their extra syntax exactly when the natural value space is too large to track individually but still has a small number of behaviorally distinct regions worth telling apart.
The actual knob behind "automatic bins stop being useful"
The runaway auto-bin problem above isn't just a conceptual risk — SystemVerilog gives it a name and a real control: option.auto_bin_max, which caps how many automatic bins a coverpoint (or, via option.cross_auto_bin_max, a cross) is allowed to generate before the tool starts merging values together instead of creating one bin per value. The language default is 64. Left at the default on a coverpoint spanning a much larger range, the tool will silently fold many distinct values into shared bins rather than blowing up bin count — which can look like reasonable coverage while actually hiding exactly the kind of unreadable, uninformative bin sprawl this page warns about. Explicit bins exist precisely so the verification engineer decides the groupings on purpose, instead of leaving that decision to whatever auto_bin_max happens to be set to.
One hit isn't always enough: option.at_least
By default, a bin counts as "covered" the moment it's hit once — which is fine for most bins, but occasionally too weak a bar on its own. option.at_least (settable per-covergroup or per-coverpoint) raises that threshold: option.at_least = 3 means a bin only counts as covered after it's been hit 3 separate times, useful for bins where a single lucky hit doesn't build real confidence — a rare-but-legal error condition someone wants exercised repeatedly, not just observed once and never again. Like the other options on this page, it's a knob that trades stricter coverage for a harder-to-close number, so it earns its use case by case rather than as a blanket default.
Avoiding coverage inflation
A coverpoint or cross that's trivially easy to fill — one that any reasonable stimulus hits immediately, contributing nothing to the "have we actually exercised the hard cases" question — inflates the reported percentage without inflating real confidence. This is worth naming directly as a failure mode, not just a theoretical risk: a coverage number chased as a target in itself, rather than as a proxy for "did we exercise what the spec cares about," is trivially gameable by adding coverpoints designed to be easy to satisfy. The percentage should always be read alongside what it's measuring, never as a number that speaks for itself.
Before adding one, ask: if this coverpoint stayed at 0% for the whole project, would anyone notice, and would it change what gets tested? If the honest answer is no, it's tracking something nobody was going to act on either way.
What's next
With both structural (code) and semantic (functional) coverage covered, the next page addresses what the CDV loop deferred from the start: what actually happens when coverage stops climbing, and how a team decides whether a remaining gap is a real hole or something safe to accept.