From a63a858e56989cfec93e2248d9e228c26b4ec595 Mon Sep 17 00:00:00 2001 From: Hyeonuk Jeong <40197569+maikjjeong@users.noreply.github.com> Date: Thu, 17 Sep 2026 09:48:43 +0900 Subject: [PATCH] Fix range trimming for fully excluded coverage bins --- src/vsc/model/rangelist_model.py | 10 +++--- ve/unit/test_coverage_igore_bins.py | 48 +++++++++++++++++++++++++++++ ve/unit/test_rangelist_model.py | 35 +++++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 ve/unit/test_rangelist_model.py diff --git a/src/vsc/model/rangelist_model.py b/src/vsc/model/rangelist_model.py index 6d86370..06d445a 100644 --- a/src/vsc/model/rangelist_model.py +++ b/src/vsc/model/rangelist_model.py @@ -72,15 +72,17 @@ def intersect(self, other): if len(self.range_l) == 0 or len(other.range_l) == 0: return - rng_i=0 - while rng_i < len(self.range_l): - for r in other.range_l: + # Apply each exclusion to every surviving range. _intersect can + # remove the current range and rewind the index for the next one. + for r in other.range_l: + rng_i=0 + while rng_i < len(self.range_l): rng_i = self._intersect( self.range_l, rng_i, self.range_l[rng_i], r) - rng_i += 1 + rng_i += 1 def _intersect(self, ranges, diff --git a/ve/unit/test_coverage_igore_bins.py b/ve/unit/test_coverage_igore_bins.py index 8dccda0..a4a3a35 100644 --- a/ve/unit/test_coverage_igore_bins.py +++ b/ve/unit/test_coverage_igore_bins.py @@ -8,6 +8,54 @@ #from coverage import covergroup class TestCoverageIgnoreBins(VscTestCase): + + def test_ignore_fully_excluded_bin(self): + self._check_fully_excluded_bin("ignore_bins") + + def test_illegal_fully_excluded_bin(self): + self._check_fully_excluded_bin("illegal_bins") + + def _check_fully_excluded_bin(self, exclusion_kind): + import vsc + + @vsc.covergroup + class cg_t(object): + def __init__(self): + self.with_sample(dict(a=vsc.uint8_t())) + exclusions = dict(zero=vsc.bin(0), one=vsc.bin(1)) + self.cp = vsc.coverpoint(self.a, + bins=dict( + min=vsc.bin(0), + mid=vsc.bin([1, 254]), + max=vsc.bin(255)), + **{exclusion_kind: exclusions}) + + cg = cg_t() + cp = cg.cp.get_model() + self.assertEqual(cp.get_n_bins(), 2) + self.assertEqual( + [cp.get_bin_name(i) for i in range(cp.get_n_bins())], + ["mid", "max"]) + + # Excluded values must not hit the surviving normal bins. + cg.sample(0) + cg.sample(1) + self.assertEqual([cp.get_bin_hits(i) for i in range(2)], [0, 0]) + if exclusion_kind == "ignore_bins": + self.assertEqual(cp.get_n_ignore_bins(), 2) + self.assertEqual( + [cp.get_ignore_bin_hits(i) for i in range(2)], [1, 1]) + else: + self.assertEqual(cp.get_n_illegal_bins(), 2) + self.assertEqual( + [cp.get_illegal_bin_hits(i) for i in range(2)], [1, 1]) + + # The partially trimmed bin and the unaffected bin still sample. + cg.sample(2) + cg.sample(254) + cg.sample(255) + self.assertEqual([cp.get_bin_hits(i) for i in range(2)], [2, 1]) + self.assertEqual(cp.get_inst_coverage(), 100.0) def test_smoke(self): import vsc diff --git a/ve/unit/test_rangelist_model.py b/ve/unit/test_rangelist_model.py new file mode 100644 index 0000000..900cd2a --- /dev/null +++ b/ve/unit/test_rangelist_model.py @@ -0,0 +1,35 @@ +from unittest import TestCase + +from vsc.model.rangelist_model import RangelistModel + + +class TestRangelistModel(TestCase): + + def test_intersect_exclusions(self): + cases = [ + # Removing a range must not leave an invalid index for the + # next exclusion, including when no ranges remain. + ([0], [0, 1], []), + ([0, 1], [0, 1], []), + ([0, 2, 4], [2, 4], [[0, 0]]), + ([0, 1, 2], [0, 1], [[2, 2]]), + # Splits and trims must still apply every exclusion to all + # surviving ranges, regardless of exclusion order. + ([[0, 10]], [5, [0, 4], [6, 8]], [[9, 10]]), + ([[0, 10]], [[6, 8], [0, 4], 5], [[9, 10]]), + ([[0, 10]], [[2, 4], [6, 8]], [[0, 1], [5, 5], [9, 10]]), + ([[0, 2], [4, 6]], [[1, 5]], [[0, 0], [6, 6]]), + ([], [0, 1], []), + ([[0, 2]], [], [[0, 2]]), + ] + + for ranges, exclusions, expected in cases: + with self.subTest(ranges=ranges, exclusions=exclusions): + model = RangelistModel(ranges) + other = RangelistModel(exclusions) + original_exclusions = other.clone() + + model.intersect(other) + + self.assertEqual(model.range_l, expected) + self.assertTrue(other.equals(original_exclusions))