1use std::collections::VecDeque;
12
13use logicaffeine_proof::bmc::InductionOutcome;
14
15use super::rtl::{NextState, Register, TransitionSystem};
16use super::sva_to_verify::{BitVecBoundedOp, BoundedExpr};
17
18fn count_bits(n: usize) -> u32 {
20 (usize::BITS - n.leading_zeros()).max(1)
21}
22
23fn index_bits(n: usize) -> u32 {
25 if n <= 1 {
26 1
27 } else {
28 (usize::BITS - (n - 1).leading_zeros()).max(1)
29 }
30}
31
32fn pad_to(s: &str, col: usize) -> String {
33 let mut out = s.to_string();
34 while out.len() < col {
35 out.push(' ');
36 }
37 out
38}
39
40pub fn emit_fifo_verilog(name: &str, depth: usize, width: usize) -> String {
44 assert!(depth >= 1 && width >= 1);
45 let cw = count_bits(depth);
46 let pw = index_bits(depth);
47 let pow2 = depth.is_power_of_two();
48 let w1 = width - 1;
49 let mut s = String::new();
50 s.push_str(&format!(
51 "// {name} — the language Pipe (bounded FIFO channel) in silicon.\n"
52 ));
53 s.push_str("// FIFO order, bounded (full refuses a sender, empty refuses a receiver) — the discipline of\n");
54 s.push_str("// logicaffeine_runtime::channel::Chan. Proven ≡ the software channel by chan_fifo_matches_the_runtime_channel.\n");
55 s.push_str(&format!("// DEPTH = {depth}, WIDTH = {width}.\n"));
56 s.push_str(&format!("module {name}(\n"));
57 let port = |kind: &str, range: &str, ident: &str, tail: &str| -> String {
58 let head = if range.is_empty() {
59 format!(" {kind}")
60 } else {
61 format!(
62 " {kind}{}{range}",
63 if kind == "input" { " " } else { " " }
64 )
65 };
66 format!("{}{ident}{tail}\n", pad_to(&head, 20))
67 };
68 s.push_str(&port("input", "", "clk", ","));
69 s.push_str(&port("input", "", "rst", ","));
70 s.push_str(&port("input", "", "push", ","));
71 s.push_str(&port("input", &format!("[{w1}:0]"), "din", ","));
72 s.push_str(&port("input", "", "pop", ","));
73 s.push_str(&port(
74 "output",
75 &format!("[{w1}:0]"),
76 "dout",
77 ", // combinational: the oldest buffered value (FIFO front)",
78 ));
79 s.push_str(&port(
80 "output",
81 "",
82 "pushed",
83 ", // this cycle's push was accepted (buffer had room)",
84 ));
85 s.push_str(&port(
86 "output",
87 "",
88 "popped",
89 ", // this cycle's pop was accepted (buffer was non-empty)",
90 ));
91 s.push_str(&port("output", "", "empty", ","));
92 s.push_str(&port("output", "", "full", ","));
93 s.push_str(&port("output", &format!("[{}:0]", cw - 1), "count", ""));
94 s.push_str(");\n");
95 let mem_decl = format!(" reg [{w1}:0] ");
96 let reg_decl = |bits: u32| pad_to(&format!(" reg [{}:0]", bits - 1), mem_decl.len());
97 s.push_str(&format!("{mem_decl}mem [0:{}];\n", depth - 1));
98 s.push_str(&format!(
99 "{}// 0..{depth}\n",
100 pad_to(&format!("{}cnt;", reg_decl(cw)), 30)
101 ));
102 let wrap = if pow2 {
103 format!("wrap mod {depth}")
104 } else {
105 format!("wrap at {depth} (explicit compare)")
106 };
107 s.push_str(&format!(
108 "{}// {wrap}\n",
109 pad_to(&format!("{}head, tail;", reg_decl(pw)), 30)
110 ));
111 s.push_str(&format!(" assign empty = (cnt == {cw}'d0);\n"));
112 s.push_str(&format!(" assign full = (cnt == {cw}'d{depth});\n"));
113 s.push_str(" assign popped = pop && !empty;\n");
114 s.push_str(" assign pushed = push && !full;\n");
115 s.push_str(" assign dout = mem[head];\n");
116 s.push_str(" assign count = cnt;\n");
117 s.push_str(" always @(posedge clk) begin\n");
118 s.push_str(" if (rst) begin\n");
119 s.push_str(&format!(
120 " cnt <= {cw}'d0; head <= {pw}'d0; tail <= {pw}'d0;\n"
121 ));
122 s.push_str(" end else begin\n");
123 if pow2 {
124 s.push_str(&format!(" if (popped) head <= head + {pw}'d1;\n"));
125 s.push_str(&format!(
126 " if (pushed) begin mem[tail] <= din; tail <= tail + {pw}'d1; end\n"
127 ));
128 } else {
129 let last = depth - 1;
130 s.push_str(&format!(
131 " if (popped) head <= (head == {pw}'d{last}) ? {pw}'d0 : head + {pw}'d1;\n"
132 ));
133 s.push_str(&format!(" if (pushed) begin mem[tail] <= din; tail <= (tail == {pw}'d{last}) ? {pw}'d0 : tail + {pw}'d1; end\n"));
134 }
135 s.push_str(&format!(
136 " cnt <= cnt - (popped ? {cw}'d1 : {cw}'d0) + (pushed ? {cw}'d1 : {cw}'d0);\n"
137 ));
138 s.push_str(" end\n end\nendmodule\n");
139 s
140}
141
142pub fn fifo_bound_certified(depth: usize, width: usize) -> bool {
145 let _ = width;
146 let cw = count_bits(depth);
147 let cnt = || Box::new(BoundedExpr::BitVecVar("cnt".into(), cw));
148 let k = |v: u64| {
149 Box::new(BoundedExpr::BitVecConst {
150 width: cw,
151 value: v,
152 })
153 };
154 let bv = |op, l: Box<BoundedExpr>, r: Box<BoundedExpr>| {
155 Box::new(BoundedExpr::BitVecBinary {
156 op,
157 left: l,
158 right: r,
159 })
160 };
161 let eqk = |v: u64| *bv(BitVecBoundedOp::Eq, cnt(), k(v));
162 let and = |a: BoundedExpr, b: BoundedExpr| BoundedExpr::And(Box::new(a), Box::new(b));
163 let not = |a: BoundedExpr| BoundedExpr::Not(Box::new(a));
164 let popped = and(BoundedExpr::Var("pop".into()), not(eqk(0)));
165 let pushed = and(BoundedExpr::Var("push".into()), not(eqk(depth as u64)));
166 let next = NextState::Ite {
167 cond: popped,
168 then_: Box::new(NextState::Ite {
169 cond: pushed.clone(),
170 then_: Box::new(NextState::Simple(*cnt())),
171 else_: Box::new(NextState::Simple(*bv(BitVecBoundedOp::Sub, cnt(), k(1)))),
172 }),
173 else_: Box::new(NextState::Ite {
174 cond: pushed,
175 then_: Box::new(NextState::Simple(*bv(BitVecBoundedOp::Add, cnt(), k(1)))),
176 else_: Box::new(NextState::Simple(*cnt())),
177 }),
178 };
179 let bound_ok = not(*bv(BitVecBoundedOp::ULt, k(depth as u64), cnt()));
180 let ts = TransitionSystem {
181 registers: vec![Register {
182 name: "cnt".into(),
183 width: cw,
184 init: Some(0),
185 next,
186 }],
187 property: bound_ok,
188 };
189 ts.prove_invariant(1) == InductionOutcome::Proven
190}
191
192fn splitmix(state: &mut u64) -> u64 {
193 *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
194 let mut z = *state;
195 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
196 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
197 z ^ (z >> 31)
198}
199
200fn hex_value(state: &mut u64, width: usize) -> String {
202 let digits = width.div_ceil(4);
203 let mut s = String::new();
204 for i in 0..digits {
205 let nib = (splitmix(state) & 0xF) as u32;
206 let nib = if i == 0 {
207 let top_bits = width - (digits - 1) * 4;
208 nib & ((1u32 << top_bits) - 1)
209 } else {
210 nib
211 };
212 s.push(std::char::from_digit(nib, 16).unwrap());
213 }
214 s
215}
216
217pub fn emit_fifo_trace_testbench(
220 name: &str,
221 depth: usize,
222 width: usize,
223 steps: usize,
224 seed: u64,
225) -> String {
226 let mut st = seed.wrapping_mul(0xA24B_AED4_963E_E407).wrapping_add(1);
227 let mut model: VecDeque<String> = VecDeque::new();
228 let mut lines = Vec::new();
229 let w1 = width - 1;
230 let cw = count_bits(depth);
231 for _ in 0..steps {
232 let r = splitmix(&mut st);
233 let push = (r & 3) != 0;
234 let pop = ((r >> 2) & 3) == 0 || ((r >> 4) & 1) == 1 && model.len() > depth / 2;
235 let din = hex_value(&mut st, width);
236 let popped = pop && !model.is_empty();
237 let pushed = push && model.len() < depth;
238 let count_before = model.len();
241 let dout = if popped {
242 model.pop_front().unwrap()
243 } else {
244 String::new()
245 };
246 if pushed {
247 model.push_back(din.clone());
248 }
249 lines.push((push, pop, din, pushed, popped, dout, count_before));
250 }
251 let mut s = String::new();
252 s.push_str(&format!("`timescale 1ns/1ps\nmodule tb_{name};\n reg clk = 0, rst = 1, push = 0, pop = 0;\n reg [{w1}:0] din = 0;\n wire [{w1}:0] dout;\n wire pushed, popped, empty, full;\n wire [{}:0] count;\n", cw - 1));
253 s.push_str(&format!(" {name} dut(.clk(clk), .rst(rst), .push(push), .din(din), .pop(pop), .dout(dout), .pushed(pushed), .popped(popped), .empty(empty), .full(full), .count(count));\n"));
254 s.push_str(" integer fails = 0;\n always #5 clk = ~clk;\n");
255 s.push_str(" task step(input p, input q, input [{w}:0] d, input ep, input eq, input [{w}:0] ed, input integer ec, input integer cyc);\n".replace("{w}", &w1.to_string()).as_str());
256 s.push_str(" begin\n push = p; pop = q; din = d;\n #1;\n");
257 s.push_str(" if (pushed !== ep || popped !== eq || count !== ec[{c}:0] || (eq && dout !== ed)) begin\n $display(\"FAIL at cycle %0d: pushed=%b/%b popped=%b/%b count=%0d/%0d dout=%h/%h\", cyc, pushed, ep, popped, eq, count, ec, dout, ed);\n fails = fails + 1;\n end\n".replace("{c}", &(cw - 1).to_string()).as_str());
258 s.push_str(" @(posedge clk); #1;\n end\n endtask\n");
259 s.push_str(" initial begin\n @(posedge clk); #1 rst = 0;\n");
260 for (i, (push, pop, din, pushed, popped, dout, count_before)) in lines.iter().enumerate() {
261 let ed = if dout.is_empty() {
262 "0".to_string()
263 } else {
264 dout.clone()
265 };
266 s.push_str(&format!(" step(1'b{}, 1'b{}, {width}'h{din}, 1'b{}, 1'b{}, {width}'h{ed}, {count_before}, {i});\n", *push as u8, *pop as u8, *pushed as u8, *popped as u8));
267 }
268 s.push_str(" if (fails == 0) $display(\"PASS: fifo trace\"); else $display(\"FAIL: %0d mismatches\", fails);\n $finish;\n end\nendmodule\n");
269 s
270}
271
272pub fn emit_semaphore_verilog(name: &str, width: usize) -> String {
276 let w1 = width - 1;
277 let mut s = String::new();
278 s.push_str(&format!(
279 "// {name} — a counting semaphore ({width} bits): set wins over inc in the same cycle.\n"
280 ));
281 s.push_str(&format!("module {name}(\n input clk,\n input rst,\n input inc,\n input [{w1}:0] inc_n,\n input set,\n input [{w1}:0] set_val,\n input [{w1}:0] thresh,\n output [{w1}:0] count,\n output ge\n);\n"));
282 s.push_str(&format!(
283 " reg [{w1}:0] cnt;\n assign count = cnt;\n assign ge = (cnt >= thresh);\n"
284 ));
285 s.push_str(&format!(" always @(posedge clk) begin\n if (rst) cnt <= {width}'d0;\n else if (set) cnt <= set_val;\n else if (inc) cnt <= cnt + inc_n;\n end\nendmodule\n"));
286 s
287}
288
289pub fn emit_semaphore_trace_testbench(name: &str, width: usize) -> String {
291 let w1 = width - 1;
292 let mask: u64 = if width >= 64 {
293 u64::MAX
294 } else {
295 (1u64 << width) - 1
296 };
297 let mut st = 0x5EED_u64;
298 let mut cnt: u64 = 0;
299 let mut script = Vec::new();
300 for i in 0..120 {
301 let r = splitmix(&mut st);
302 let (set, set_val, inc, inc_n, thresh) = match r % 5 {
303 0 => (true, (r >> 8) & 7, false, 0, (r >> 16) & 7),
304 1 | 2 => (false, 0, true, 1 + ((r >> 8) & 3), (r >> 16) & 7),
305 3 => (true, 0, true, 1, (r >> 16) & 7),
306 _ => (false, 0, false, 0, (r >> 16) & 7),
307 };
308 let ge_before = cnt >= thresh;
309 if set {
310 cnt = set_val & mask;
311 } else if inc {
312 cnt = (cnt + inc_n) & mask;
313 }
314 script.push((i, set, set_val, inc, inc_n, thresh, ge_before, cnt));
315 }
316 let mut s = String::new();
317 s.push_str(&format!("`timescale 1ns/1ps\nmodule tb_{name};\n reg clk = 0, rst = 1, inc = 0, set = 0;\n reg [{w1}:0] inc_n = 0, set_val = 0, thresh = 0;\n wire [{w1}:0] count;\n wire ge;\n"));
318 s.push_str(&format!(" {name} dut(.clk(clk), .rst(rst), .inc(inc), .inc_n(inc_n), .set(set), .set_val(set_val), .thresh(thresh), .count(count), .ge(ge));\n"));
319 s.push_str(" integer fails = 0;\n always #5 clk = ~clk;\n initial begin\n @(posedge clk); #1 rst = 0;\n");
320 for (i, set, set_val, inc, inc_n, thresh, ge_before, after) in script {
321 s.push_str(&format!(" set = {}; set_val = {set_val}; inc = {}; inc_n = {inc_n}; thresh = {thresh}; #1;\n", set as u8, inc as u8));
322 s.push_str(&format!(" if (ge !== 1'b{}) begin $display(\"FAIL step {i}: ge=%b expected {}\", ge); fails = fails + 1; end\n", ge_before as u8, ge_before as u8));
323 s.push_str(&format!(" @(posedge clk); #1;\n if (count !== {width}'d{after}) begin $display(\"FAIL step {i}: count=%0d expected {after}\", count); fails = fails + 1; end\n"));
324 }
325 s.push_str(" if (fails == 0) $display(\"PASS: semaphore trace\"); else $display(\"FAIL: %0d mismatches\", fails);\n $finish;\n end\nendmodule\n");
326 s
327}
328
329pub fn emit_barrier_verilog(name: &str, n: usize) -> String {
332 assert!(n >= 1);
333 let n1 = n - 1;
334 let mut s = String::new();
335 s.push_str(&format!("// {name} — an arrive-all barrier over {n} parties (sticky arrivals, one-cycle released, re-arms).\n"));
336 s.push_str(&format!("module {name}(\n input clk,\n input rst,\n input [{n1}:0] arrive,\n output released\n);\n"));
337 s.push_str(&format!(" reg [{n1}:0] seen;\n reg rel;\n wire all_in = &(seen | arrive);\n assign released = rel;\n"));
338 s.push_str(&format!(" always @(posedge clk) begin\n if (rst) begin seen <= {n}'d0; rel <= 1'b0; end\n else if (all_in) begin seen <= {n}'d0; rel <= 1'b1; end\n else begin seen <= seen | arrive; rel <= 1'b0; end\n end\nendmodule\n"));
339 s
340}
341
342pub fn emit_barrier_trace_testbench(name: &str, n: usize) -> String {
345 let n1 = n - 1;
346 let mut st = 0xBA77_u64;
347 let mut s = String::new();
348 s.push_str(&format!("`timescale 1ns/1ps\nmodule tb_{name};\n reg clk = 0, rst = 1;\n reg [{n1}:0] arrive = 0;\n wire released;\n {name} dut(.clk(clk), .rst(rst), .arrive(arrive), .released(released));\n integer fails = 0;\n always #5 clk = ~clk;\n initial begin\n @(posedge clk); #1 rst = 0;\n"));
349 for round in 0..4 {
350 let mut seen: u64 = 0;
352 let full: u64 = if n >= 64 { u64::MAX } else { (1u64 << n) - 1 };
353 let mut order: Vec<usize> = (0..n).collect();
354 for i in (1..n).rev() {
355 let j = (splitmix(&mut st) as usize) % (i + 1);
356 order.swap(i, j);
357 }
358 let mut idx = 0;
359 while seen != full {
360 let r = splitmix(&mut st);
362 let mut arrive: u64 = 0;
363 let how_many = if r % 4 == 0 {
364 0
365 } else if r % 4 == 1 && idx + 1 < n {
366 2
367 } else {
368 1
369 };
370 for _ in 0..how_many {
371 if idx < n {
372 arrive |= 1u64 << order[idx];
373 idx += 1;
374 }
375 }
376 let completes = (seen | arrive) == full;
377 s.push_str(&format!(" arrive = {n}'h{arrive:X}; #1;\n if (released !== 1'b0) begin $display(\"FAIL round {round}: early released\"); fails = fails + 1; end\n @(posedge clk); #1;\n"));
378 if completes {
379 s.push_str(&format!(" arrive = {n}'d0; if (released !== 1'b1) begin $display(\"FAIL round {round}: no released after the last arrival\"); fails = fails + 1; end\n @(posedge clk); #1;\n if (released !== 1'b0) begin $display(\"FAIL round {round}: released did not drop\"); fails = fails + 1; end\n"));
380 seen = full;
381 } else {
382 seen |= arrive;
383 s.push_str(&format!(" if (released !== 1'b0) begin $display(\"FAIL round {round}: released before all arrived\"); fails = fails + 1; end\n"));
384 }
385 }
386 }
387 s.push_str(" if (fails == 0) $display(\"PASS: barrier trace\"); else $display(\"FAIL: %0d mismatches\", fails);\n $finish;\n end\nendmodule\n");
388 s
389}
390
391pub fn emit_mcast_bus_verilog(name: &str, width: usize, n: usize) -> String {
394 let (w1, n1) = (width - 1, n - 1);
395 let mut s = String::new();
396 s.push_str(&format!(
397 "// {name} — a one-writer multicast bus to {n} receivers ({width} bits), registered.\n"
398 ));
399 s.push_str(&format!("module {name}(\n input clk,\n input rst,\n input send,\n input [{w1}:0] din,\n output [{n1}:0] push,\n output [{w1}:0] dout\n);\n"));
400 s.push_str(&format!(" reg strobe;\n reg [{w1}:0] data;\n assign push = {{{n}{{strobe}}}};\n assign dout = data;\n"));
401 s.push_str(&format!(" always @(posedge clk) begin\n if (rst) begin strobe <= 1'b0; data <= {width}'d0; end\n else begin strobe <= send; if (send) data <= din; end\n end\nendmodule\n"));
402 s
403}
404
405pub fn emit_mcast_bus_trace_testbench(name: &str, width: usize, n: usize) -> String {
407 let (w1, n1) = (width - 1, n - 1);
408 let mut st = 0xB05_u64;
409 let mut s = String::new();
410 s.push_str(&format!("`timescale 1ns/1ps\nmodule tb_{name};\n reg clk = 0, rst = 1, send = 0;\n reg [{w1}:0] din = 0;\n wire [{n1}:0] push;\n wire [{w1}:0] dout;\n {name} dut(.clk(clk), .rst(rst), .send(send), .din(din), .push(push), .dout(dout));\n integer fails = 0;\n always #5 clk = ~clk;\n initial begin\n @(posedge clk); #1 rst = 0;\n"));
411 let all_ones = format!(
412 "{n}'h{:X}",
413 if n >= 64 { u64::MAX } else { (1u64 << n) - 1 }
414 );
415 let mut last_sent: Option<String> = None;
416 for i in 0..60 {
417 let r = splitmix(&mut st);
418 let send = r % 3 != 0;
419 let din = hex_value(&mut st, width);
420 s.push_str(&format!(
421 " send = {}; din = {width}'h{din};\n @(posedge clk); #1;\n",
422 send as u8
423 ));
424 if send {
425 s.push_str(&format!(" if (push !== {all_ones} || dout !== {width}'h{din}) begin $display(\"FAIL step {i}: push=%h dout=%h\", push, dout); fails = fails + 1; end\n"));
426 last_sent = Some(din);
427 } else {
428 s.push_str(&format!(" if (push !== {n}'d0) begin $display(\"FAIL step {i}: spurious push\"); fails = fails + 1; end\n"));
429 if let Some(prev) = &last_sent {
430 s.push_str(&format!(" if (dout !== {width}'h{prev}) begin $display(\"FAIL step {i}: dout drifted\"); fails = fails + 1; end\n"));
431 }
432 }
433 }
434 s.push_str(" if (fails == 0) $display(\"PASS: mcast bus trace\"); else $display(\"FAIL: %0d mismatches\", fails);\n $finish;\n end\nendmodule\n");
435 s
436}