logicaffeine_compile/codegen_sva/
controller_gen.rs1use super::signal_design::{Intersection, PhasePlan};
12use std::fmt::Write as _;
13
14pub fn generate_controller(it: &Intersection, plan: &PhasePlan) -> String {
17 let n = it.movements.len();
18 let k = plan.num_phases.max(1);
19 let groups = plan.groups();
20 let bits = phase_bits(k);
21
22 let mut v = String::new();
23 let _ = writeln!(v, "module controller(input clk);");
24 let _ = writeln!(v, " reg [{}:0] phase;", bits - 1);
25 for i in 0..n {
26 let _ = writeln!(v, " reg mv{i};");
27 }
28 let _ = writeln!(
29 v,
30 " initial begin phase = {bits}'d0; {} end",
31 config_assigns(&groups, 0, n, true)
32 );
33 let _ = writeln!(v, " always @(posedge clk)");
34 if k == 1 {
35 let _ = writeln!(
37 v,
38 " begin phase <= {bits}'d0; {} end",
39 config_assigns(&groups, 0, n, false)
40 );
41 } else {
42 for p in 0..(k - 1) {
43 let kw = if p == 0 { "if" } else { "else if" };
44 let _ = writeln!(
45 v,
46 " {kw} (phase == {bits}'d{p}) begin phase <= {bits}'d{}; {} end",
47 p + 1,
48 config_assigns(&groups, p + 1, n, false)
49 );
50 }
51 let _ = writeln!(
53 v,
54 " else begin phase <= {bits}'d0; {} end",
55 config_assigns(&groups, 0, n, false)
56 );
57 }
58 let _ = writeln!(v, " assert property ({});", conflict_free_property(it));
59 let _ = writeln!(v, "endmodule");
60 v
61}
62
63fn config_assigns(groups: &[Vec<usize>], p: usize, n: usize, blocking: bool) -> String {
66 let op = if blocking { "=" } else { "<=" };
67 let served: std::collections::HashSet<usize> =
68 groups.get(p).map(|g| g.iter().copied().collect()).unwrap_or_default();
69 (0..n)
70 .map(|i| format!("mv{i} {op} 1'd{};", usize::from(served.contains(&i))))
71 .collect::<Vec<_>>()
72 .join(" ")
73}
74
75fn conflict_free_property(it: &Intersection) -> String {
78 if it.conflicts.is_empty() {
79 "mv0 | ~mv0".to_string()
80 } else {
81 it.conflicts
82 .iter()
83 .map(|&(a, b)| format!("~(mv{a} & mv{b})"))
84 .collect::<Vec<_>>()
85 .join(" & ")
86 }
87}
88
89fn phase_bits(k: usize) -> usize {
91 let mut bits = 1;
92 while (1usize << bits) < k {
93 bits += 1;
94 }
95 bits
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::codegen_sva::rtl::parse_transition_system;
102 use crate::codegen_sva::signal_design::design_phase_plan;
103 use logicaffeine_proof::bmc::InductionOutcome;
104
105 fn graph(n: usize, conflicts: &[(usize, usize)]) -> Intersection {
106 Intersection {
107 movements: (0..n).map(|i| format!("m{i}")).collect(),
108 conflicts: conflicts.iter().map(|&(a, b)| (a.min(b), a.max(b))).collect(),
109 }
110 }
111
112 #[test]
114 fn generated_controller_is_proven_conflict_free() {
115 let graphs = [
116 graph(3, &[]), graph(2, &[(0, 1)]), graph(3, &[(0, 1), (1, 2), (0, 2)]), graph(4, &[(0, 1), (1, 2), (2, 3), (3, 0)]), graph(5, &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]), graph(4, &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), graph(5, &[(0, 1), (0, 2), (0, 3), (0, 4)]), ];
124 for g in graphs {
125 let plan = design_phase_plan(&g).unwrap();
126 let verilog = generate_controller(&g, &plan);
127 let ts = parse_transition_system(&verilog)
128 .unwrap_or_else(|e| panic!("generated controller did not parse: {}\n{verilog}", e.message));
129 assert_eq!(
130 ts.prove_invariant(1),
131 InductionOutcome::Proven,
132 "generated controller must be PROVEN conflict-free:\n{verilog}"
133 );
134 }
135 }
136
137 #[test]
138 fn generated_verilog_is_well_formed() {
139 let g = graph(3, &[(0, 1), (1, 2), (0, 2)]);
140 let plan = design_phase_plan(&g).unwrap();
141 let v = generate_controller(&g, &plan);
142 assert!(v.contains("module controller(input clk);"), "{v}");
143 assert!(v.contains("endmodule"));
144 assert!(v.contains("assert property"));
145 for i in 0..3 {
146 assert!(v.contains(&format!("reg mv{i};")), "missing mv{i}:\n{v}");
147 }
148 assert!(v.contains("~(mv0 & mv1)") && v.contains("~(mv1 & mv2)") && v.contains("~(mv0 & mv2)"), "{v}");
150 }
151
152 #[test]
153 fn a_deliberately_unsafe_controller_is_caught() {
154 let g = graph(2, &[(0, 1)]);
157 let plan = design_phase_plan(&g).unwrap();
158 let good = generate_controller(&g, &plan);
159 let bad = good
161 .replace("mv0 <= 1'd0;", "mv0 <= 1'd1;")
162 .replace("mv1 <= 1'd0;", "mv1 <= 1'd1;")
163 .replace("mv0 = 1'd0;", "mv0 = 1'd1;")
164 .replace("mv1 = 1'd0;", "mv1 = 1'd1;");
165 assert_ne!(bad, good, "the mangle must change something");
166 let ts = parse_transition_system(&bad).expect("still parses");
167 assert_ne!(
168 ts.prove_invariant(1),
169 InductionOutcome::Proven,
170 "a both-green controller must NOT prove safe:\n{bad}"
171 );
172 }
173
174 #[test]
175 fn phase_bits_are_correct() {
176 assert_eq!(phase_bits(1), 1);
177 assert_eq!(phase_bits(2), 1);
178 assert_eq!(phase_bits(3), 2);
179 assert_eq!(phase_bits(4), 2);
180 assert_eq!(phase_bits(5), 3);
181 }
182
183 #[test]
184 fn single_movement_controller_is_valid_and_proven() {
185 let g = graph(1, &[]);
186 let plan = design_phase_plan(&g).unwrap();
187 let v = generate_controller(&g, &plan);
188 let ts = parse_transition_system(&v)
189 .unwrap_or_else(|e| panic!("single-movement controller did not parse: {}\n{v}", e.message));
190 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven, "{v}");
191 }
192
193 #[test]
194 fn six_movement_controller_is_proven() {
195 let g = graph(6, &[(0, 1), (1, 2), (0, 2), (3, 4), (4, 5), (3, 5)]);
197 let plan = design_phase_plan(&g).unwrap();
198 assert_eq!(plan.num_phases, 3, "two disjoint triangles still need 3 phases");
199 let v = generate_controller(&g, &plan);
200 let ts = parse_transition_system(&v)
201 .unwrap_or_else(|e| panic!("did not parse: {}\n{v}", e.message));
202 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven, "{v}");
203 }
204}