Skip to content

Commit d651b61

Browse files
Copilotmballance
andauthored
Fix coverpoint sampling for nested rand_attr fields (#265)
* Initial plan * Add test cases for nested rand_attr coverpoint issue Co-authored-by: mballance <1340805+mballance@users.noreply.github.com> * Fix nested rand_attr coverpoint sampling by enabling indexed field references Co-authored-by: mballance <1340805+mballance@users.noreply.github.com> * Update test documentation to reflect that the fix is now implemented Co-authored-by: mballance <1340805+mballance@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mballance <1340805+mballance@users.noreply.github.com>
1 parent 5742e82 commit d651b61

File tree

2 files changed

+201
-1
lines changed

2 files changed

+201
-1
lines changed

src/vsc/rand_obj.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ def build_field_model(self, name):
179179
model = FieldCompositeModel(name, self._int_field_info.is_rand, self)
180180
model.typename = T.__qualname__
181181
self._int_field_info.model = model
182+
# Mark this field as composite so nested fields can be indexed correctly
183+
self._int_field_info.is_composite = True
182184

183185
# Iterate through the fields and constraints
184186
# First, assign IDs to each of the randomized fields
@@ -310,7 +312,9 @@ def _id_fields(self, it, parent):
310312
fid += 1
311313

312314
if fi.is_composite:
313-
self._id_fields(fo, fi)
315+
# Recursively process composite fields
316+
# Pass it._int_field_info as the parent, not fi
317+
self._id_fields(fo, it._int_field_info)
314318

315319
setattr(T, "__getattribute__", __getattribute__)
316320
setattr(T, "__setattr__", __setattr__)
@@ -407,6 +411,8 @@ def build_field_model(self, name):
407411
model = FieldCompositeModel(name, self._int_field_info.is_rand, self)
408412
model.typename = T.__name__
409413
self._int_field_info.model = model
414+
# Mark this field as composite so nested fields can be indexed correctly
415+
self._int_field_info.is_composite = True
410416

411417
# Iterate through the fields and constraints
412418
# First, assign IDs to each of the randomized fields
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
'''
2+
Test for coverage sampling on nested rand_attr objects
3+
4+
This test verifies that coverpoints correctly sample nested fields
5+
that are included via vsc.rand_attr in a parent object.
6+
7+
Issue: When a coverpoint targets fields within a nested object decorated with
8+
@vsc.randobj and included via vsc.rand_attr in the item, functional coverage
9+
is not correctly sampled when using direct field references (without lambda).
10+
'''
11+
12+
import enum
13+
import vsc
14+
from vsc_test_case import VscTestCase
15+
16+
17+
@enum.unique
18+
class Security(enum.IntEnum):
19+
SEC = 0
20+
NONSEC = 1
21+
ROOT = 2
22+
23+
24+
class TestCoverageNestedRandAttr(VscTestCase):
25+
26+
def test_nested_randattr_direct_reference_issue(self):
27+
"""
28+
Test that verifies direct field references on nested rand_attr objects
29+
now work correctly after the fix. Before the fix, direct references would
30+
bind to the initial value and only hit the first bin.
31+
"""
32+
33+
@vsc.randobj
34+
class Protection:
35+
def __init__(self):
36+
self.security = vsc.rand_enum_t(Security)
37+
38+
@vsc.randobj
39+
class MyItem:
40+
def __init__(self):
41+
self.protection = vsc.rand_attr(Protection())
42+
43+
@vsc.covergroup
44+
class simple_covergroup:
45+
def __init__(self):
46+
self.with_sample(dict(it=MyItem()))
47+
48+
# Direct reference - now works correctly after the fix
49+
self.security_cp_direct = vsc.coverpoint(
50+
self.it.protection.security,
51+
bins={s.name: vsc.bin(s.value) for s in Security}
52+
)
53+
54+
# Create covergroup and sample multiple times
55+
cg = simple_covergroup()
56+
item = MyItem()
57+
58+
# Track which values we've seen during randomization
59+
seen_values = set()
60+
61+
# Sample many times to ensure we hit all values
62+
for _ in range(100):
63+
item.randomize()
64+
cg.sample(item)
65+
seen_values.add(item.protection.security)
66+
67+
# We should have seen all three security values
68+
self.assertEqual(len(seen_values), 3,
69+
f"Expected to see all 3 Security values, but only saw: {seen_values}")
70+
71+
# Check coverage - each bin should have been hit
72+
model = cg.security_cp_direct.get_model()
73+
74+
# Get hit counts for each bin using the hit_l list
75+
bins_hit = 0
76+
for hit_count in model.hit_l:
77+
if hit_count > 0:
78+
bins_hit += 1
79+
80+
# All 3 bins should be hit with the fix
81+
self.assertEqual(bins_hit, 3,
82+
f"Expected all 3 bins to be hit, but only {bins_hit} were hit")
83+
84+
def test_nested_randattr_lambda_workaround(self):
85+
"""
86+
Test that verifies lambda expressions continue to work correctly
87+
for nested rand_attr coverpoints. Lambda expressions were the
88+
workaround before the fix and should continue to work after the fix.
89+
"""
90+
91+
@vsc.randobj
92+
class Protection:
93+
def __init__(self):
94+
self.security = vsc.rand_enum_t(Security)
95+
96+
@vsc.randobj
97+
class MyItem:
98+
def __init__(self):
99+
self.protection = vsc.rand_attr(Protection())
100+
101+
@vsc.covergroup
102+
class simple_covergroup:
103+
def __init__(self):
104+
self.with_sample(dict(it=MyItem()))
105+
106+
# Lambda reference - continues to work after the fix
107+
self.security_cp_lambda = vsc.coverpoint(
108+
lambda: self.it.protection.security,
109+
cp_t=vsc.enum_t(Security),
110+
bins={s.name: vsc.bin(s.value) for s in Security}
111+
)
112+
113+
# Create covergroup and sample multiple times
114+
cg = simple_covergroup()
115+
item = MyItem()
116+
117+
# Track which values we've seen during randomization
118+
seen_values = set()
119+
120+
# Sample many times to ensure we hit all values
121+
for _ in range(100):
122+
item.randomize()
123+
cg.sample(item)
124+
seen_values.add(item.protection.security)
125+
126+
# We should have seen all three security values
127+
self.assertEqual(len(seen_values), 3,
128+
f"Expected to see all 3 Security values, but only saw: {seen_values}")
129+
130+
# Check coverage - each bin should have been hit
131+
model = cg.security_cp_lambda.get_model()
132+
133+
# Get hit counts for each bin using the hit_l list
134+
bins_hit = 0
135+
for hit_count in model.hit_l:
136+
if hit_count > 0:
137+
bins_hit += 1
138+
139+
# All 3 bins should be hit with lambda
140+
self.assertEqual(bins_hit, 3,
141+
f"Expected all 3 bins to be hit, but only {bins_hit} were hit")
142+
143+
def test_non_nested_direct_reference_works(self):
144+
"""
145+
Test that direct references work correctly for non-nested fields.
146+
This should PASS and demonstrates that the issue is specific to nested fields.
147+
"""
148+
149+
@vsc.randobj
150+
class MyItem:
151+
def __init__(self):
152+
self.security = vsc.rand_enum_t(Security)
153+
154+
@vsc.covergroup
155+
class simple_covergroup:
156+
def __init__(self):
157+
self.with_sample(dict(it=MyItem()))
158+
159+
# Direct reference to non-nested field - works correctly
160+
self.security_cp = vsc.coverpoint(
161+
self.it.security,
162+
bins={s.name: vsc.bin(s.value) for s in Security}
163+
)
164+
165+
# Create covergroup and sample multiple times
166+
cg = simple_covergroup()
167+
item = MyItem()
168+
169+
# Track which values we've seen during randomization
170+
seen_values = set()
171+
172+
# Sample many times to ensure we hit all values
173+
for _ in range(100):
174+
item.randomize()
175+
cg.sample(item)
176+
# item.security is directly the Security enum value
177+
seen_values.add(item.security)
178+
179+
# We should have seen all three security values
180+
self.assertEqual(len(seen_values), 3,
181+
f"Expected to see all 3 Security values, but only saw: {seen_values}")
182+
183+
# Check coverage - each bin should have been hit
184+
model = cg.security_cp.get_model()
185+
186+
# Get hit counts for each bin using the hit_l list
187+
bins_hit = 0
188+
for hit_count in model.hit_l:
189+
if hit_count > 0:
190+
bins_hit += 1
191+
192+
# All 3 bins should be hit for non-nested fields
193+
self.assertEqual(bins_hit, 3,
194+
f"Expected all 3 bins to be hit, but only {bins_hit} were hit")

0 commit comments

Comments
 (0)