Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions doc/source/coverage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,69 @@ compose the coverpoint cross.

self.cp1X2 = vsc.cross([self.cp1, self.cp2])

Nested Coverpoint Crosses
..........................

PyVSC supports nested crosses, where a cross can be defined using previously
defined crosses as target elements. This enables modular coverage definition
and prevents redundant code when building multi-dimensional coverage matrices.

When a cross is used as a target element in another cross, its component
coverpoints are automatically flattened into the resulting cross. This approach
is similar to SystemVerilog's cross-of-crosses functionality.

.. code-block:: python3

@vsc.covergroup
class nested_cross_cg(object):
def __init__(self):
self.with_sample(dict(
a = vsc.uint8_t(),
b = vsc.uint8_t(),
c = vsc.uint8_t()
))

self.cp_a = vsc.coverpoint(self.a, bins={"a_bins": vsc.bin_array([4], [0, 255])})
self.cp_b = vsc.coverpoint(self.b, bins={"b_bins": vsc.bin_array([4], [0, 255])})
self.cp_c = vsc.coverpoint(self.c, bins={"c_bins": vsc.bin_array([4], [0, 255])})

# Standard cross of two coverpoints (2D)
self.cross_ab = vsc.cross([self.cp_a, self.cp_b])

# Nested cross: cross of a cross and a coverpoint (3D)
# Automatically flattened to cross([cp_a, cp_b, cp_c])
self.cross_abc = vsc.cross([self.cross_ab, self.cp_c])

You can also create a cross from multiple crosses:

.. code-block:: python3

@vsc.covergroup
class multi_cross_cg(object):
def __init__(self):
self.with_sample(dict(
a = vsc.uint8_t(),
b = vsc.uint8_t(),
c = vsc.uint8_t(),
d = vsc.uint8_t()
))

self.cp_a = vsc.coverpoint(self.a, bins={"a_bins": vsc.bin_array([2], [0, 255])})
self.cp_b = vsc.coverpoint(self.b, bins={"b_bins": vsc.bin_array([2], [0, 255])})
self.cp_c = vsc.coverpoint(self.c, bins={"c_bins": vsc.bin_array([2], [0, 255])})
self.cp_d = vsc.coverpoint(self.d, bins={"d_bins": vsc.bin_array([2], [0, 255])})

# Two separate 2D crosses
self.cross_ab = vsc.cross([self.cp_a, self.cp_b])
self.cross_cd = vsc.cross([self.cp_c, self.cp_d])

# Cross of two crosses creates a 4D coverage matrix
# Automatically flattened to cross([cp_a, cp_b, cp_c, cp_d])
self.cross_abcd = vsc.cross([self.cross_ab, self.cross_cd])

Nested crosses can be arbitrarily deep, with each level being automatically
flattened to its constituent coverpoints.

Coverpoint Cross Ignore Bins
............................

Expand Down
29 changes: 25 additions & 4 deletions src/vsc/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -905,10 +905,31 @@ def __init__(self,
name=None,
iff=None,
ignore_bins=None):
for t in target_l:
if not isinstance(t, coverpoint):
raise Exception("Cross target \"" + str(t) + "\" is not a coverpoint")
self.target_l = target_l
# Recursively flatten nested crosses by extracting their coverpoints
def flatten_targets(targets):
"""Recursively flatten a list of coverpoints and crosses.

Args:
targets: List of coverpoint and/or cross objects

Returns:
List of coverpoint objects (flat list with all crosses expanded)

Raises:
Exception: If any target is neither a coverpoint nor a cross
"""
flattened = []
for t in targets:
if isinstance(t, cross):
# Recursively flatten cross objects
flattened.extend(flatten_targets(t.target_l))
elif isinstance(t, coverpoint):
flattened.append(t)
else:
raise Exception("Cross target \"" + str(t) + "\" is not a coverpoint or cross")
return flattened

self.target_l = flatten_targets(target_l)
self.bins = bins
self.options = Options()

Expand Down
159 changes: 159 additions & 0 deletions ve/unit/test_coverage_cross.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,162 @@ def __init__(self, v1,v2):
self.assertEqual(report.covergroups[0].coverpoints[1].coverage, 100)
self.assertEqual(report.covergroups[0].crosses[0].coverage, 50)

def test_nested_cross_basic(self):
"""Test basic nested cross: cross of (cross + coverpoint)"""

@vsc.covergroup
class nested_cross_cg(object):
def __init__(self):
self.with_sample(dict(
a = vsc.uint8_t(),
b = vsc.uint8_t(),
c = vsc.uint8_t()
))

self.cp_a = vsc.coverpoint(self.a, bins={"a_bins": vsc.bin_array([4], [0, 255])})
self.cp_b = vsc.coverpoint(self.b, bins={"b_bins": vsc.bin_array([4], [0, 255])})
self.cp_c = vsc.coverpoint(self.c, bins={"c_bins": vsc.bin_array([4], [0, 255])})

# Standard cross
self.cross_ab = vsc.cross([self.cp_a, self.cp_b])

# Nested cross: cross of a cross and a coverpoint
# This should create a 3D matrix (A x B x C)
self.cross_abc = vsc.cross([self.cross_ab, self.cp_c])

cg = nested_cross_cg()

# Sample various combinations (positional args match with_sample order)
cg.sample(10, 20, 30)
cg.sample(100, 150, 200)
cg.sample(200, 100, 50)

# Verify that the nested cross was created correctly
# The cross_abc should have 3 coverpoints (a, b, c) flattened
self.assertEqual(len(cg.cross_abc.target_l), 3)
self.assertEqual(cg.cross_abc.target_l[0], cg.cp_a)
self.assertEqual(cg.cross_abc.target_l[1], cg.cp_b)
self.assertEqual(cg.cross_abc.target_l[2], cg.cp_c)

def test_nested_cross_two_crosses(self):
"""Test cross of two crosses"""

@vsc.covergroup
class two_cross_cg(object):
def __init__(self):
self.with_sample(dict(
a = vsc.uint8_t(),
b = vsc.uint8_t(),
c = vsc.uint8_t(),
d = vsc.uint8_t()
))

self.cp_a = vsc.coverpoint(self.a, bins={"a_bins": vsc.bin_array([2], [0, 255])})
self.cp_b = vsc.coverpoint(self.b, bins={"b_bins": vsc.bin_array([2], [0, 255])})
self.cp_c = vsc.coverpoint(self.c, bins={"c_bins": vsc.bin_array([2], [0, 255])})
self.cp_d = vsc.coverpoint(self.d, bins={"d_bins": vsc.bin_array([2], [0, 255])})

# Two crosses
self.cross_ab = vsc.cross([self.cp_a, self.cp_b])
self.cross_cd = vsc.cross([self.cp_c, self.cp_d])

# Cross of crosses: creates 4D matrix (A x B x C x D)
self.cross_abcd = vsc.cross([self.cross_ab, self.cross_cd])

cg = two_cross_cg()

# Sample various combinations
cg.sample(10, 20, 30, 40)
cg.sample(200, 220, 230, 240)

# Verify that the nested cross flattened correctly
self.assertEqual(len(cg.cross_abcd.target_l), 4)
self.assertEqual(cg.cross_abcd.target_l[0], cg.cp_a)
self.assertEqual(cg.cross_abcd.target_l[1], cg.cp_b)
self.assertEqual(cg.cross_abcd.target_l[2], cg.cp_c)
self.assertEqual(cg.cross_abcd.target_l[3], cg.cp_d)

def test_nested_cross_deep(self):
"""Test deeply nested crosses (3+ levels)"""

@vsc.covergroup
class deep_cross_cg(object):
def __init__(self):
self.with_sample(dict(
a = vsc.uint8_t(),
b = vsc.uint8_t(),
c = vsc.uint8_t(),
d = vsc.uint8_t()
))

self.cp_a = vsc.coverpoint(self.a, bins={"a_bins": vsc.bin_array([2], [0, 255])})
self.cp_b = vsc.coverpoint(self.b, bins={"b_bins": vsc.bin_array([2], [0, 255])})
self.cp_c = vsc.coverpoint(self.c, bins={"c_bins": vsc.bin_array([2], [0, 255])})
self.cp_d = vsc.coverpoint(self.d, bins={"d_bins": vsc.bin_array([2], [0, 255])})

# Build nested crosses progressively
self.cross_ab = vsc.cross([self.cp_a, self.cp_b])
self.cross_abc = vsc.cross([self.cross_ab, self.cp_c])
self.cross_abcd = vsc.cross([self.cross_abc, self.cp_d])

cg = deep_cross_cg()

# Sample
cg.sample(10, 20, 30, 40)

# Verify deep flattening
self.assertEqual(len(cg.cross_abcd.target_l), 4)
self.assertEqual(cg.cross_abcd.target_l[0], cg.cp_a)
self.assertEqual(cg.cross_abcd.target_l[1], cg.cp_b)
self.assertEqual(cg.cross_abcd.target_l[2], cg.cp_c)
self.assertEqual(cg.cross_abcd.target_l[3], cg.cp_d)

def test_nested_cross_with_options(self):
"""Test nested cross with options"""

@vsc.covergroup
class nested_cross_options_cg(object):
def __init__(self):
self.with_sample(dict(
a = vsc.uint8_t(),
b = vsc.uint8_t(),
c = vsc.uint8_t()
))

self.cp_a = vsc.coverpoint(self.a, bins={"a_bins": vsc.bin_array([2], [0, 255])})
self.cp_b = vsc.coverpoint(self.b, bins={"b_bins": vsc.bin_array([2], [0, 255])})
self.cp_c = vsc.coverpoint(self.c, bins={"c_bins": vsc.bin_array([2], [0, 255])})

self.cross_ab = vsc.cross([self.cp_a, self.cp_b])

# Nested cross with options
self.cross_abc_nested = vsc.cross(
[self.cross_ab, self.cp_c],
options=dict(at_least=2)
)

# Non-nested cross with same options for comparison
self.cross_abc_flat = vsc.cross(
[self.cp_a, self.cp_b, self.cp_c],
options=dict(at_least=2)
)

cg = nested_cross_options_cg()
cg.sample(10, 20, 30)

# Verify structure of nested cross
self.assertEqual(len(cg.cross_abc_nested.target_l), 3)
# Verify options were set correctly on nested cross
self.assertIsNotNone(cg.cross_abc_nested.options)
self.assertEqual(cg.cross_abc_nested.options.at_least, 2)

# Verify structure of flat cross
self.assertEqual(len(cg.cross_abc_flat.target_l), 3)
# Verify options were set correctly on flat cross
self.assertIsNotNone(cg.cross_abc_flat.options)
self.assertEqual(cg.cross_abc_flat.options.at_least, 2)

# Verify both crosses have same structure (proving flattening works correctly)
for i in range(3):
self.assertEqual(cg.cross_abc_nested.target_l[i], cg.cross_abc_flat.target_l[i])

Loading