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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
use celox::Simulation;
#[test]
fn test_cascade_race_condition() {
// This design demonstrates a race condition in the current simulator implementation.
// cnt1 is incremented by clk.
// gclk is derived from clk (combinational cascade).
// cnt2 is incremented by cnt1, triggered by gclk.
// In a correct simulation, when clk rises, cnt2 should be incremented by the OLD value of cnt1.
// In the current implementation, clk domain is evaluated and updates cnt1 BEFORE gclk domain is evaluated.
let code = r#"
module Top (
clk: input '_ clock,
rst: input '_ reset_async_high,
cnt1_out: output logic<8>,
cnt2_out: output logic<8>
) {
var cnt1: logic<8>;
var cnt2: logic<8>;
let gclk: '_ clock = clk;
always_ff (clk, rst) {
if_reset {
cnt1 = 8'd0;
} else {
cnt1 = cnt1 + 8'd1;
}
}
always_ff (gclk, rst) {
if_reset {
cnt2 = 8'd0;
} else {
cnt2 = cnt2 + cnt1;
}
}
assign cnt1_out = cnt1;
assign cnt2_out = cnt2;
}
"#;
let mut sim = Simulation::builder(code, "Top").build().unwrap();
let cnt1_out = sim.signal("cnt1_out");
let cnt2_out = sim.signal("cnt2_out");
// Reset
sim.schedule("rst", 0, 1).unwrap();
sim.schedule("clk", 0, 0).unwrap();
sim.step().unwrap();
assert_eq!(sim.get(cnt1_out), 0u8.into());
assert_eq!(sim.get(cnt2_out), 0u8.into());
// Release reset
sim.schedule("rst", 10, 0).unwrap();
sim.step().unwrap();
// 1st tick:
// clk rises.
// cnt1: 0 -> 1
// gclk rises (cascaded).
// cnt2: 0 + cnt1(OLD=0) -> 0
sim.schedule("clk", 20, 1).unwrap();
sim.step().unwrap();
println!(
"Step 1: cnt1={}, cnt2={}",
sim.get(cnt1_out),
sim.get(cnt2_out)
);
// 2nd tick:
// clk falls
sim.schedule("clk", 30, 0).unwrap();
sim.step().unwrap();
// 3rd tick:
// clk rises.
// cnt1: 1 -> 2
// gclk rises (cascaded).
// cnt2: 0 + cnt1(OLD=1) -> 1
sim.schedule("clk", 40, 1).unwrap();
sim.step().unwrap();
println!(
"Step 2: cnt1={}, cnt2={}",
sim.get(cnt1_out),
sim.get(cnt2_out)
);
assert_eq!(sim.get(cnt1_out), 2u8.into());
assert_eq!(
sim.get(cnt2_out),
1u8.into(),
"Race condition detected: cnt2 should have used OLD value of cnt1"
);
}
#[test]
fn test_sequential_cascade_race_condition() {
// This design demonstrates a race condition with sequential cascade.
// clk drives cnt1, clk_div (external half-rate clock) drives cnt2.
// cnt2 reads cnt1, so the evaluation order matters.
let code = r#"
module Top (
clk: input '_ clock,
clk_div: input '_ clock,
rst: input '_ reset_async_high,
cnt_out: output logic<8>
) {
var cnt1: logic<8>;
var cnt2: logic<8>;
always_ff (clk, rst) {
if_reset {
cnt1 = 8'd0;
} else {
cnt1 = cnt1 + 8'd1;
}
}
// clk_div rises every other clk cycle
always_ff (clk_div, rst) {
if_reset {
cnt2 = 8'd0;
} else {
cnt2 = cnt2 + cnt1;
}
}
assign cnt_out = cnt2;
}
"#;
let mut sim = Simulation::builder(code, "Top").build().unwrap();
let cnt_out = sim.signal("cnt_out");
// Reset
sim.schedule("rst", 0, 1).unwrap();
sim.schedule("clk", 0, 0).unwrap();
sim.schedule("clk_div", 0, 0).unwrap();
sim.step().unwrap();
// Release reset
sim.schedule("rst", 10, 0).unwrap();
sim.step().unwrap();
// 1st tick: clk rises, clk_div rises (both rise together)
// cnt1: 0 -> 1
// cnt2: 0 + cnt1(OLD=0) -> 0
sim.schedule("clk", 20, 1).unwrap();
sim.schedule("clk_div", 20, 1).unwrap();
sim.step().unwrap();
println!("Seq Step 1: cnt={}", sim.get(cnt_out));
// 2nd tick: clk falls, clk_div falls
sim.schedule("clk", 30, 0).unwrap();
sim.schedule("clk_div", 30, 0).unwrap();
sim.step().unwrap();
// 3rd tick: clk rises, clk_div stays low (half-rate)
// cnt1: 1 -> 2
// cnt2: remains 0
sim.schedule("clk", 40, 1).unwrap();
sim.step().unwrap();
println!("Seq Step 2: cnt={}", sim.get(cnt_out));
// 4th tick: clk falls
sim.schedule("clk", 50, 0).unwrap();
sim.step().unwrap();
// 5th tick: clk rises, clk_div rises again
// cnt1: 2 -> 3
// cnt2: 0 + cnt1(OLD=2) -> 2
sim.schedule("clk", 60, 1).unwrap();
sim.schedule("clk_div", 60, 1).unwrap();
sim.step().unwrap();
println!("Seq Step 3: cnt={}", sim.get(cnt_out));
assert_eq!(
sim.get(cnt_out),
2u8.into(),
"Race condition detected in sequential cascade"
);
}