Skip to content

Options() per_instance seems to have no effect #27

@s3rg1o7

Description

@s3rg1o7

Hello, I am not sure if I am doing something wrong or if this is a real problem - or a functionality not yet implemented. I hope the post is clear enough to - at least - spark a conversation.
I have been trying to play with the per_instance option in PyVSC covegroup, but it seems to have no effect whatsoever - or I am using it wrongly.

Here's a minimal viable example that show my usage, expectations and results when using per_instance. All the files are available here
example.zip. Just unpack it, go to its directory and run make to reproduce the scenario


When per_instance = True

In my design I have a flip flop, and I defined two covergroup instances from the same class:

    cg0 = FlipFlopCG()
    cg1 = FlipFlopCG()

I then proceed to set the flip flop to 1'b0 and sample it with cg0, then set it to 1'b1 and sample it only with cg2 . I then expect that only cg1 to have reached 100% coverage but:

================================================================================
After setting DFF=1 and sampling ONLY cg1
================================================================================
DFF output q = 1
get_inst_coverage: False
cg0.get_coverage() = 100.0%
get_inst_coverage: True
cg0.get_inst_coverage() = 50.0%
get_inst_coverage: True
cg1.get_coverage() = 100.0%
get_inst_coverage: False
cg1.get_inst_coverage() = 50.0%

When per_instance = False

Viceversa, when setting self.options.per_instance = False I'd expect that vsc.write_coverage_db("coverage_output.xml", fmt="xml") results in aggregate coverage, without outputting each instance in the .xml file. This doesn't seem to be happening, and all the instances are always printed out:

<covergroupCoverage>
<cgInstance name="FlipFlopCG" key="0">
....
</cgInstance>

<cgInstance name="FlipFlopCG_1" key="0">
...
</cgInstance>
</covergroupCoverage>
</instanceCoverages>

Design file

// Simple D Flip-Flop for testing
module dff (
    input wire clk,
    input wire rst_n,
    input wire d,
    output reg q
);

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        q <= 1'b0;
    else
        q <= d;
end

endmodule

Makefile

# Makefile for cocotb simulation

# Defaults
SIM ?= icarus
TOPLEVEL_LANG ?= verilog

# Verilog source files
VERILOG_SOURCES = $(PWD)/dff.v

# Top-level module
TOPLEVEL = dff

# Python test module
MODULE = test_per_instance

# Include cocotb's make rules
include $(shell cocotb-config --makefiles)/Makefile.sim

Test with coverage

"""
Minimal reproducing example showing that per_instance option has no effect in PyVSC.

This test creates two covergroup instances (cg0 and cg1) from the same class.
- cg0 samples when flip-flop = 0
- cg1 samples when flip-flop = 1

With per_instance=False, we would expect:
- get_coverage() to return 100% (both bins covered across all instances)
- get_inst_coverage() to return 50% for each instance

However, the current implementation shows that per_instance has no effect.
"""

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, Timer
import vsc


@vsc.covergroup
class FlipFlopCG:
    """Covergroup with a single coverpoint that has two bins: 0 and 1"""

    def __init__(self):
        # Explicitly set per_instance=False to request type-level coverage aggregation
        self.options.per_instance = True

        # Use with_sample to declare the sample variable
        self.with_sample(dict(
            sample_val=vsc.bit_t(8)
        ))

        # Create a coverpoint with two bins
        self.cp = vsc.coverpoint(self.sample_val, bins={
            "zero": vsc.bin(0),
            "one": vsc.bin(1)
        })


@cocotb.test()
async def test_per_instance_no_effect(dut):
    """
    Test demonstrating that per_instance option has no effect.

    Expected behavior with per_instance=False:
    - Type-level coverage (get_coverage()) should aggregate across all instances
    - After sampling 0 in cg0 and 1 in cg1, type coverage should be 100%

    Actual behavior:
    - per_instance is ignored, coverage is always per-instance
    """

    # Start clock
    clock = Clock(dut.clk, 10, units="ns")
    cocotb.start_soon(clock.start())

    # Reset
    dut.rst_n.value = 0
    dut.d.value = 0
    await RisingEdge(dut.clk)
    dut.rst_n.value = 1
    await RisingEdge(dut.clk)

    # Create two covergroup instances from the same class
    cg0 = FlipFlopCG()
    cg1 = FlipFlopCG()

    print("\n" + "="*80)
    print("Initial state - No samples yet")
    print("="*80)
    print(f"cg0.options.per_instance = {cg0.options.per_instance}")
    print(f"cg1.options.per_instance = {cg1.options.per_instance}")
    print(f"cg0.get_coverage() = {cg0.get_coverage()}%")
    print(f"cg0.get_inst_coverage() = {cg0.get_inst_coverage()}%")
    print(f"cg1.get_coverage() = {cg1.get_coverage()}%")
    print(f"cg1.get_inst_coverage() = {cg1.get_inst_coverage()}%")

    # Set flip-flop to 0 and sample ONLY cg0
    dut.d.value = 0
    await RisingEdge(dut.clk)
    await Timer(1, units="ns")

    print("\n" + "="*80)
    print("After setting DFF=0 and sampling ONLY cg0")
    print("="*80)
    print(f"DFF output q = {dut.q.value}")
    cg0.sample(int(dut.q.value))

    print(f"cg0.get_coverage() = {cg0.get_coverage()}%")
    print(f"cg0.get_inst_coverage() = {cg0.get_inst_coverage()}%")
    print(f"cg1.get_coverage() = {cg1.get_coverage()}%")
    print(f"cg1.get_inst_coverage() = {cg1.get_inst_coverage()}%")

    # Set flip-flop to 1 and sample ONLY cg1
    dut.d.value = 1
    await RisingEdge(dut.clk)
    await Timer(1, units="ns")

    print("\n" + "="*80)
    print("After setting DFF=1 and sampling ONLY cg1")
    print("="*80)
    print(f"DFF output q = {dut.q.value}")
    cg1.sample(int(dut.q.value))

    print(f"cg0.get_coverage() = {cg0.get_coverage()}%")
    print(f"cg0.get_inst_coverage() = {cg0.get_inst_coverage()}%")
    print(f"cg1.get_coverage() = {cg1.get_coverage()}%")
    print(f"cg1.get_inst_coverage() = {cg1.get_inst_coverage()}%")

    print("\n" + "="*80)
    print("ANALYSIS")
    print("="*80)
    print("Expected behavior with per_instance=False:")
    print("  - cg0.get_coverage() should return TYPE coverage (aggregate) = 100%")
    print("  - cg1.get_coverage() should return TYPE coverage (aggregate) = 100%")
    print("  - cg0.get_inst_coverage() should return instance coverage = 50%")
    print("  - cg1.get_inst_coverage() should return instance coverage = 50%")
    print("")
    print("Actual behavior:")
    print(f"  - cg0.get_coverage() = {cg0.get_coverage()}%")
    print(f"  - cg1.get_coverage() = {cg1.get_coverage()}%")
    print(f"  - cg0.get_inst_coverage() = {cg0.get_inst_coverage()}%")
    print(f"  - cg1.get_inst_coverage() = {cg1.get_inst_coverage()}%")
    print("")

    # Check if per_instance has any effect
    type_cov_cg0 = cg0.get_coverage()
    type_cov_cg1 = cg1.get_coverage()

    if type_cov_cg0 == 100.0 and type_cov_cg1 == 100.0:
        print("✓ per_instance=False WORKS: Type coverage is aggregated (100%)")
    else:
        print("✗ per_instance HAS NO EFFECT: Type coverage is not aggregated")
        print("  This demonstrates that the per_instance option is ignored!")

    print("="*80 + "\n")

    # Additional verification: both instances together should give 100% coverage
    # if type-level aggregation worked
    assert type_cov_cg0 == type_cov_cg1, \
        "Both instances should report the same type coverage"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions