By default, a coverpoint creates one automatic bin per legal value of whatever it's tracking. The bins construct lets you take control of that instead — grouping values into a single bin, splitting a range across a fixed number of bins, or catching anything left over with a default bin.
7 min read | Intermediate Level
What You'll Learn
- The different bin types: manual single-value bins, automatic bins over a whole range, a fixed number of automatic bins, and a default catch-all bin
- How coverage percentage is calculated once a coverpoint has multiple bins
- How SystemVerilog splits a range across a fixed number of automatic bins, including uneven splits
- How array bins (
[]) differ from a single named bin covering the same range
Bin Syntax Reference
Here's the full range of ways a bins block can be written, all in one place:
coverpoint mode {
// Manually create a separate bin for each value
bins zero = {0};
bins one = {1};
// Allow SystemVerilog to automatically create separate bins for each value
// Values from 0 to maximum possible value is split into separate bins
bins range[] = {[0:$]};
// Create automatic bins for both the given ranges
bins c[] = { [2:3], [5:7]};
// Use fixed number of automatic bins. Entire range is broken up into 4 bins
bins range[4] = {[0:$]};
// If the number of bins cannot be equally divided for the given range, then
// the last bin will include remaining items; Here there are 13 values to be
// distributed into 4 bins which yields:
// [1,2,3] [4,5,6] [7,8,9] [10, 1, 3, 6]
bins range[4] = {[1:10], 1, 3, 6};
// A single bin to store all other values that don't belong to any other bin
bins others = default;
}
The rest of this article walks through four of these styles with a working example and real coverage results for each, using a 3-bit variable mode (legal values 0 through 7) sampled at the same five points in every example: 4, 1, 1, 3, 5.
Manually Declaring Individual Bins
The simplest case: name a bin and give it exactly one value to watch for.
module tb;
bit [2:0] mode;
// This covergroup does not get sample automatically because
// the sample event is missing in declaration
covergroup cg;
coverpoint mode {
bins one = {1};
bins five = {5};
}
endgroup
// Stimulus : Simply randomize mode to have different values and
// manually sample each time
initial begin
cg cg_inst = new();
for (int i = 0; i < 5; i++) begin
#10 mode = $random;
$display ("[%0t] mode = 0x%0h", $time, mode);
cg_inst.sample();
end
$display ("Coverage = %0.2f %%", cg_inst.get_inst_coverage());
end
endmodule
Output
ncsim> run [10] mode = 0x4 [20] mode = 0x1 [30] mode = 0x1 [40] mode = 0x3 [50] mode = 0x5 Coverage = 100.00 % ncsim: *W,RNQUIE: Simulation is complete.
Both one (hit when mode is 1) and five (hit when mode is 5) were sampled at least once across the 5 samples, so both of the coverpoint's 2 bins are covered — 2/2 = 100%.
Quick Check
Q: If a third bin, bins nine = {9};, were added to the example above, what would the new coverage percentage be (given the same 5 sampled values)?
Show Answer
66.67% — there are now 3 bins total, and nine is never hit since 9 isn't a legal value for a 3-bit variable anyway (0–7), so only 2 of the 3 bins are covered.
Automatic Bins Across a Full Range
Adding [] after a bin name tells SystemVerilog to automatically create one bin per value across the given range, instead of one bin for the whole range:
covergroup cg;
coverpoint mode {
// Declares a separate bin for each values -> Here there will 8 bins
bins range[] = {[0:$]};
}
endgroup
Output
ncsim> run [10] mode = 0x4 [20] mode = 0x1 [30] mode = 0x1 [40] mode = 0x3 [50] mode = 0x5 Coverage = 50.00 % ncsim: *W,RNQUIE: Simulation is complete.
4 out of the total possible 8 values (0 through 7) have been sampled — 0x4, 0x1, 0x3, and 0x5 — and hence coverage is 4/8 = 50%.
A Fixed Number of Automatic Bins
Instead of one bin per value, you can ask for a specific number of bins and let SystemVerilog split the range between them:
covergroup cg;
coverpoint mode {
// Declares 4 bins for the total range of 8 values
// So bin0->[0:1] bin1->[2:3] bin2->[4:5] bin3->[6:7]
bins range[4] = {[0:$]};
}
endgroup
Output
ncsim> run [10] mode = 0x4 [20] mode = 0x1 [30] mode = 0x1 [40] mode = 0x3 [50] mode = 0x5 Coverage = 75.00 % ncsim: *W,RNQUIE: Simulation is complete.
mode never got a value of 6 or 7, so the bin covering [6:7] is never hit. The other three bins — [0:1], [2:3], and [4:5] — each caught at least one of the sampled values, so 3 out of 4 bins are covered: 75%.
Splitting a Fixed Number of Bins Across a Custom Value List
A fixed number of bins doesn't have to divide a plain contiguous range — it can just as easily divide a mixed list of ranges and individual values:
covergroup cg;
coverpoint mode {
// Defines 3 bins
// 5 total values (1,2,3,4 from the range, plus 7) split across 3 bins:
// the first 2 bins get 1 value each, and the last bin absorbs the rest
// bin1->1 bin2->2, bin3->[3,4,7]
bins range[3] = {[1:4], 7};
}
endgroup
Output
ncsim> run [10] mode = 0x4 [20] mode = 0x1 [30] mode = 0x1 [40] mode = 0x3 [50] mode = 0x5 Coverage = 66.67 % ncsim: *W,RNQUIE: Simulation is complete.
Only 2 of the 3 bins were hit: bin1 (just the value 1) caught the sampled 1s, and bin3 (values 3, 4, and 7 — the leftover items from the uneven split) caught the sampled 3 and 4. bin2, watching only the value 2, was never hit — so coverage is still 2/3 = 66.67%.
Both this example and the reference snippet at the top of this article follow the exact same rule: the first N−1 bins each get an equal, floor-divided share of the values, and the last bin absorbs whatever's left over. With 5 values split 3 ways that's 1, 1, 3; with 13 values split 4 ways (the reference snippet) it's 3, 3, 3, 4. The split is only even when the total divides cleanly by the bin count — otherwise the last bin ends up noticeably larger than the rest.
Quick Check
Q: Using bins range[] = {[0:$]} versus bins range = {[0:$]} (no square brackets) on the same 3-bit variable — how many bins does each create?
Show Answer
range[] creates 8 separate bins, one per legal value. range without the brackets creates just 1 bin covering the entire range — it's hit as soon as mode takes any value from 0 to 7.
Where Do Custom Bins Matter?
Custom bins are what make coverage practical on wide fields — grouping a 32-bit address into a handful of named legal windows (like low_region, high_region, reserved) gives a meaningful, reviewable coverage report, where one automatic bin per address value would be both impractical to create and useless to read.
Common Beginner Mistakes
Mistake #1: Forgetting the [] for one bin per value
bins name = {[0:7]}; creates a single bin covering the entire range, while bins name[] = {[0:7]}; creates 8 separate bins, one per value. Why? It's a one-character difference with a big effect on what gets measured — the first version is hit by any value 0 through 7, while the second requires every individual value to be seen before it's fully covered.
Mistake #2: Assuming a fixed number of bins always splits a range evenly
The first N−1 bins each get an equal, floor-divided share of the values; the last bin always absorbs whatever's left over. Why? If the total number of values doesn't divide cleanly by the bin count, the last bin ends up noticeably larger than the others — work out that division before assuming a specific, even split.
Mistake #3: Treating a default bin as an error catch
bins others = default; simply collects any legal value that isn't claimed by one of the coverpoint's other explicit bins. Why? It has nothing to do with detecting invalid or illegal values — for that, SystemVerilog has a separate illegal_bins construct; a plain default bin just quietly picks up whatever's left.
Practice Exercise
Challenge: Split a 4-Bit Variable Into 2 Automatic Bins
Given a 4-bit variable addr (legal values 0 through 15), write a coverpoint with a fixed number of automatic bins that splits the full range into exactly 2 bins.
Hint
Use the same bins name[N] = {[0:$]}; pattern shown in the "Fixed Number of Automatic Bins" example, just with a different bin count.
Solution
covergroup cg;
coverpoint addr {
bins range[2] = {[0:$]};
}
endgroup
With 16 legal values split evenly across 2 bins, you'd expect bin0 to cover [0:7] and bin1 to cover [8:15] — each bin is hit as soon as any one value in its half of the range has been sampled.
Summary
In this tutorial, you learned:
- A manually declared bin (
bins name = {value};) tracks exactly one value - Adding
[]to a bin name (bins name[] = {range};) creates one automatic bin per value in that range, instead of a single bin for the whole range - A fixed number of bins (
bins name[N] = {range};) splits a range or value list acrossNbins, though the split isn't always perfectly even - A
defaultbin catches any legal value not claimed by another explicit bin — it's not related to error or illegal-value detection - Coverage percentage is always bins hit ÷ total bins, once you know how many bins a coverpoint actually has
Continuing the Functional Coverage Chapter
Continue with SystemVerilog Cross Coverage to track combinations of values across multiple coverpoints at once.