From dcb9c0d7af91ccbd189f46bb282e433a0f82eafe Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 25 Aug 2026 13:09:07 +0200 Subject: [PATCH] rtlil: track where a case is selected and where a switch reads A CaseRule's own src is where its body is; compare_src is where the case is selected. A SwitchRule's own src is the whole statement; signal_src is where its expression is. Both survive a clone and a cross-pool copy. Also root the srcs a process holds outside its AttrObjects - one per assignment, plus the two above - so garbage collection cannot free the twines they point at. --- kernel/rtlil.cc | 56 ++++++++++++++++++++++++++++++++++++-- kernel/rtlil.h | 6 ++++ tests/proc/proc_mux_src.v | 22 +++++++++++++++ tests/proc/proc_mux_src.ys | 13 +++++++-- 4 files changed, 92 insertions(+), 5 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 06301e1f7..15e7a0255 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -757,6 +757,28 @@ namespace { } } } + + // The srcs a process holds outside its AttrObjects: one per assignment, + // one per case selector, one per switch expression. + template + void walk_process_srcs(const RTLIL::Process *process, F visit) { + std::vector case_stack{const_cast(&process->root_case)}; + while (!case_stack.empty()) { + RTLIL::CaseRule *cs = case_stack.back(); + case_stack.pop_back(); + visit(cs->compare_src); + for (auto &action : cs->actions) + visit(action.src); + for (auto *sw : cs->switches) { + visit(sw->signal_src); + for (auto *case_ : sw->cases) + case_stack.push_back(case_); + } + } + for (auto *sync : process->syncs) + for (auto &action : sync->actions) + visit(action.src); + } } @@ -770,13 +792,21 @@ size_t RTLIL::Design::gc_twines() }; pool live_srcs; + auto src_root = [&](SrcRef ref) { + if (ref != SrcRef::Null) + live_srcs.insert(ref); + }; + walk_attr_objects(this, [&](const RTLIL::AttrObject *obj) { for (auto &attr : obj->attributes) root(attr.first); - if (obj->src_ != SrcRef::Null) - live_srcs.insert(obj->src_); + src_root(obj->src_); }); + for (auto &[_, module] : modules_) + for (auto &[_, process] : module->processes) + walk_process_srcs(process, src_root); + root(selected_active_module); for (auto &[name, module] : modules_) { @@ -3254,6 +3284,22 @@ namespace { return dst_design->twines.copy_from(src_design->twines, id); } + SrcRef migrate_src(SrcRef src, const RTLIL::Design *src_design, RTLIL::Design *dst_design) + { + if (src == SrcRef::Null || src_design == dst_design) + return src; + return dst_design->srcs.copy_from(src_design->srcs, src); + } + + void migrate_actions_src(const std::vector &src_actions, + std::vector &dst_actions, + const RTLIL::Design *src_design, RTLIL::Design *dst_design) + { + log_assert(src_actions.size() == dst_actions.size()); + for (size_t i = 0; i < src_actions.size(); i++) + dst_actions[i].src = migrate_src(src_actions[i].src, src_design, dst_design); + } + void migrate_process_tree_src(const RTLIL::Process *src, const RTLIL::Design *src_design, RTLIL::Process *dst, RTLIL::Design *dst_design) { @@ -3265,11 +3311,14 @@ namespace { auto [s_cs, d_cs] = case_stack.back(); case_stack.pop_back(); copy_src_into(s_cs, src_design, d_cs, dst_design); + d_cs->compare_src = migrate_src(s_cs->compare_src, src_design, dst_design); + migrate_actions_src(s_cs->actions, d_cs->actions, src_design, dst_design); log_assert(s_cs->switches.size() == d_cs->switches.size()); for (size_t i = 0; i < s_cs->switches.size(); i++) { const auto *s_sw = s_cs->switches[i]; auto *d_sw = d_cs->switches[i]; copy_src_into(s_sw, src_design, d_sw, dst_design); + d_sw->signal_src = migrate_src(s_sw->signal_src, src_design, dst_design); log_assert(s_sw->cases.size() == d_sw->cases.size()); for (size_t j = 0; j < s_sw->cases.size(); j++) case_stack.emplace_back(s_sw->cases[j], d_sw->cases[j]); @@ -3279,6 +3328,7 @@ namespace { for (size_t i = 0; i < src->syncs.size(); i++) { const auto *s_sync = src->syncs[i]; auto *d_sync = dst->syncs[i]; + migrate_actions_src(s_sync->actions, d_sync->actions, src_design, dst_design); log_assert(s_sync->mem_write_actions.size() == d_sync->mem_write_actions.size()); for (size_t j = 0; j < s_sync->mem_write_actions.size(); j++) { copy_src_into(&s_sync->mem_write_actions[j], src_design, @@ -6012,6 +6062,7 @@ RTLIL::CaseRule *RTLIL::CaseRule::clone() const new_caserule->compare = compare; new_caserule->actions = actions; new_caserule->attributes = attributes; + new_caserule->compare_src = compare_src; for (auto &it : switches) new_caserule->switches.push_back(it->clone()); return new_caserule; @@ -6033,6 +6084,7 @@ RTLIL::SwitchRule *RTLIL::SwitchRule::clone() const RTLIL::SwitchRule *new_switchrule = new RTLIL::SwitchRule; new_switchrule->signal = signal; new_switchrule->attributes = attributes; + new_switchrule->signal_src = signal_src; for (auto &it : cases) new_switchrule->cases.push_back(it->clone()); return new_switchrule; diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 82eb2f2c2..fe2472af6 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -2110,6 +2110,9 @@ struct RTLIL::CaseRule : public RTLIL::AttrObject std::vector compare; std::vector actions; std::vector switches; + // Where the case is selected, as opposed to `src_`, which is where its + // body is. + SrcRef compare_src = SrcRef::Null; ~CaseRule(); @@ -2123,6 +2126,9 @@ struct RTLIL::CaseRule : public RTLIL::AttrObject struct RTLIL::SwitchRule : public RTLIL::AttrObject { RTLIL::SigSpec signal; + // Where the switch expression is, as opposed to `src_`, which is the + // whole statement. + SrcRef signal_src = SrcRef::Null; std::vector cases; ~SwitchRule(); diff --git a/tests/proc/proc_mux_src.v b/tests/proc/proc_mux_src.v index 17d6da104..2856a4280 100644 --- a/tests/proc/proc_mux_src.v +++ b/tests/proc/proc_mux_src.v @@ -90,3 +90,25 @@ module tiny2( endcase end endmodule + +module ifelse(input clk, + input cond1, + input cond2, + output reg [1:0] out +); + + always @(posedge clk) begin + if (cond1) begin + out <= 0; + end else begin + if(cond2) + begin + out <= 1; + end + else + begin + out <= 2; + end + end + end +endmodule diff --git a/tests/proc/proc_mux_src.ys b/tests/proc/proc_mux_src.ys index 8c146db70..1f41f7f5f 100644 --- a/tests/proc/proc_mux_src.ys +++ b/tests/proc/proc_mux_src.ys @@ -1,10 +1,10 @@ read_verilog proc_mux_src.v proc -noopt check -assert -# eq refer to the values compared against +# eq refer to both sides of the comparison of switch signal vs case value select -assert-count 2 tiny2/t:$eq -select -assert-count 1 tiny2/t:$eq a:src=proc_mux_src.v:81.4-81.10 %i -select -assert-count 1 tiny2/t:$eq a:src=proc_mux_src.v:84.4-84.10 %i +select -assert-count 1 tiny2/t:$eq a:src=proc_mux_src.v:80.9-80.11|proc_mux_src.v:81.4-81.10 %i +select -assert-count 1 tiny2/t:$eq a:src=proc_mux_src.v:80.9-80.11|proc_mux_src.v:84.4-84.10 %i # Flops cover the assigned to wire and whole process select -assert-count 1 tiny2/t:$dff select -assert-count 1 tiny2/t:$dff a:src=proc_mux_src.v:76.19-76.22|proc_mux_src.v:78.2-91.5 @@ -28,3 +28,10 @@ select -assert-count 5 nested/t:$pmux select -assert-count 1 nested/t:$pmux a:src=proc_mux_src.v:21.5-21.20|proc_mux_src.v:26.5-26.20|proc_mux_src.v:32.5-45.12|proc_mux_src.v:48.5-48.19 %i # No nesting for output reg arith select -assert-count 1 nested/t:$pmux a:src=proc_mux_src.v:23.5-23.18|proc_mux_src.v:28.5-28.18|proc_mux_src.v:31.5-31.18|proc_mux_src.v:50.5-50.18 %i + +# if/elseif can't be turned into a $pmux +# Otherwise, behaves as expected +select -assert-count 0 ifelse/t:$pmux +select -assert-count 2 ifelse/t:$mux +select -assert-count 1 ifelse/t:$mux a:src=proc_mux_src.v:102.4-102.12|proc_mux_src.v:103.11-112.6 %i +select -assert-count 1 ifelse/t:$mux a:src=proc_mux_src.v:106.6-106.14|proc_mux_src.v:110.6-110.14 %i \ No newline at end of file