From 46b44a8ebcc7ca88ce16bf1db16810605587d5fa Mon Sep 17 00:00:00 2001 From: Alex Wilson Date: Fri, 12 Jul 2024 23:18:25 -0600 Subject: [PATCH] Make solve_order/toposort predictable Using solve_order() on a list could cause randomization of list members in an unpredictable order (see #221). This was due to toposort using sets and unordering the dictionary passed to it. This now appends the toposorted list fields in the same order that the randset had originally. Add testcase passing multiple lists in an ordered randset. --- src/vsc/model/rand_info_builder.py | 17 ++++++----- ve/unit/test_constraint_solve_order.py | 39 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/vsc/model/rand_info_builder.py b/src/vsc/model/rand_info_builder.py index 5b16be7..8b49e36 100644 --- a/src/vsc/model/rand_info_builder.py +++ b/src/vsc/model/rand_info_builder.py @@ -133,15 +133,14 @@ def build( if f in builder._order_m.keys(): rs_deps[f] = builder._order_m[f] - if len(rs_deps) > 0: - rs.rand_order_l = [] - for fs in list(toposort(rs_deps)): - field_l = [] - for fi in fs: - if fi in rs.fields(): - field_l.append(fi) - if len(field_l) > 0: - rs.rand_order_l.append(field_l) + if len(rs_deps) > 0: + rs.rand_order_l = [] + # Random stability warning: toposort uses sets and is unordered + for fs in list(toposort(rs_deps)): + # Add fields in predictable randset order + field_l = [fi for fi in rs.fields() if fi in fs] + if len(field_l) > 0: + rs.rand_order_l.append(field_l) # It's important to maintain a fixed order for the # unconstrained fields, since this affects their diff --git a/ve/unit/test_constraint_solve_order.py b/ve/unit/test_constraint_solve_order.py index 8fd36d4..19b1d37 100644 --- a/ve/unit/test_constraint_solve_order.py +++ b/ve/unit/test_constraint_solve_order.py @@ -323,3 +323,42 @@ def loop_c(self): self.assertNotEqual(0, num_of_nested_loop_hist[1]) self.assertNotEqual(0, num_of_nested_loop_hist[2]) + + def test_toposort_random_stability(self): + @vsc.randobj + class my_s(object): + + def __init__(self): + self.a = vsc.rand_bit_t(8) + self.list_0 = vsc.rand_list_t(vsc.rand_bit_t(8), 10) + self.list_1 = vsc.rand_list_t(vsc.rand_bit_t(8), 10) + + @vsc.constraint + def a_and_lists_c(self): + # Excercise rand_order/toposort on lists + vsc.solve_order(self.a, self.list_0) + vsc.solve_order(self.list_0, self.list_1) + + # Make lists unique to better detect ordering differences + vsc.unique(self.list_0) + vsc.unique(self.list_1) + + # Tie all variables into single randset + self.a > 10 + self.a < 20 + with vsc.foreach(self.list_0) as it: + it < self.a + with vsc.foreach(self.list_1) as it: + it < self.a + + first = my_s() + first.set_randstate(vsc.RandState.mkFromSeed(0)) + first.randomize() + + for _ in range(20): + repeat = my_s() + repeat.set_randstate(vsc.RandState.mkFromSeed(0)) + repeat.randomize() + self.assertEqual(first.a, repeat.a, "Mismatch on a") + self.assertListEqual(list(first.list_0), list(repeat.list_0), "Mismatch on list_0") + self.assertListEqual(list(first.list_1), list(repeat.list_1), "Mismatch on list_1")