1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Regression test for duplicate VarPath crash.
//
// When multiple scoped variables share the same VarPath (e.g., `var avail: logic;`
// declared in different `for` loop scopes within the same `always_comb`), the old
// `module_variables` implementation used VarPath as the HashMap key, silently
// overwriting entries and losing VarIds. This caused a "no entry found for key"
// panic in the Cranelift translator's memory layout lookup.
//
// The fix uses VarId as the primary key and maintains a separate path index that
// detects ambiguous paths.
use celox::Simulator;
#[path = "test_utils/mod.rs"]
#[macro_use]
#[allow(unused_macros)]
mod test_utils;
all_backends! {
// Minimal reproduction: two `for` loops in `always_comb` each declare `var tmp: logic`.
// The Veryl analyzer assigns different VarIds but identical VarPaths to these scoped
// variables. Without the fix this panics during JIT compilation.
fn test_duplicate_scoped_var_in_always_comb(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
sel : input logic ,
a : input logic<4>,
b : input logic<4>,
o : output logic<4>,
) {
var result: logic<4>;
always_comb {
result = 0 as 4;
for i in 0..2 {
var tmp: logic<4>;
tmp = a + i as 4;
if sel && i == 1 {
result = tmp;
}
}
for j in 0..2 {
var tmp: logic<4>;
tmp = b + j as 4;
if !sel && j == 1 {
result = tmp;
}
}
}
assign o = result;
}
"#; }
@build Simulator::builder(code, "Top");
let sel = sim.signal("sel");
let a = sim.signal("a");
let b = sim.signal("b");
let o = sim.signal("o");
// sel=1: result = a + 1
sim.modify(|io| {
io.set(sel, 1u8);
io.set(a, 5u8);
io.set(b, 0u8);
})
.unwrap();
assert_eq!(sim.get(o), 6u64.into()); // 5 + 1
// sel=0: result = b + 1
sim.modify(|io| {
io.set(sel, 0u8);
io.set(a, 0u8);
io.set(b, 10u8);
})
.unwrap();
assert_eq!(sim.get(o), 11u64.into()); // 10 + 1
}
// Generate-for with same-named scoped variables in always_comb.
// Mirrors the original AdcGroup MRE: generate-for creates instances with
// internal vars, and always_comb inside uses `var flag: logic;` in multiple
// for-loop scopes.
fn test_duplicate_scoped_var_with_generate_for(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
clk : input clock ,
rst : input reset ,
i_data: input logic<8>,
o_a : output logic<8>,
o_b : output logic<8>,
) {
for g in 0..2 :g_ch {
var mem: logic<8>;
always_ff (clk, rst) {
if_reset { mem = 0; }
else { mem = i_data + g as 8; }
}
always_comb {
for k in 0..2 {
var flag: logic;
flag = k == g;
if flag {
if g == 0 { o_a = mem; }
if g == 1 { o_b = mem; }
}
}
}
}
}
"#; }
@build Simulator::builder(code, "Top");
let clk = sim.event("clk");
let rst = sim.signal("rst");
let i_data = sim.signal("i_data");
let o_a = sim.signal("o_a");
let o_b = sim.signal("o_b");
// Reset (AsyncLow: active when rst=0)
sim.modify(|io| io.set(rst, 0u8)).unwrap();
sim.tick(clk).unwrap();
// Deactivate reset, set input
sim.modify(|io| {
io.set(rst, 1u8);
io.set(i_data, 10u8);
})
.unwrap();
sim.tick(clk).unwrap();
// g_ch[0].mem = 10 + 0 = 10, g_ch[1].mem = 10 + 1 = 11
assert_eq!(sim.get(o_a), 10u64.into());
assert_eq!(sim.get(o_b), 11u64.into());
}
}