1use super::sva_to_proof::bounded_to_proof;
17use super::sva_to_verify::{BitVecBoundedOp, BoundedExpr};
18use logicaffeine_proof::bmc::{self, BmcOutcome, InductionOutcome};
19use logicaffeine_proof::sat::{find_model, ModelOutcome};
20use logicaffeine_proof::ProofExpr;
21use std::collections::HashMap;
22
23#[derive(Debug, Clone)]
26pub enum NextState {
27 Simple(BoundedExpr),
28 Ite { cond: BoundedExpr, then_: Box<NextState>, else_: Box<NextState> },
29}
30
31#[derive(Debug, Clone)]
33pub struct Register {
34 pub name: String,
35 pub width: u32,
37 pub init: Option<u64>,
40 pub next: NextState,
42}
43
44#[derive(Debug, Clone)]
46pub struct TransitionSystem {
47 pub registers: Vec<Register>,
48 pub property: BoundedExpr,
50}
51
52fn mask(width: u32) -> u64 {
53 if width >= 64 {
54 u64::MAX
55 } else {
56 (1u64 << width) - 1
57 }
58}
59
60impl TransitionSystem {
61 pub fn bmc(&self, max_k: u32) -> BmcOutcome {
63 if self.lowered_init().is_none()
64 || self.lowered_trans(0).is_none()
65 || self.lowered_property(0).is_none()
66 {
67 return BmcOutcome::Unsupported;
68 }
69 let init = self.lowered_init().unwrap();
70 bmc::find_counterexample(
71 &init,
72 &|t| self.lowered_trans(t).unwrap(),
73 &|t| self.lowered_property(t).unwrap(),
74 max_k,
75 )
76 }
77
78 pub fn prove_invariant(&self, k: u32) -> InductionOutcome {
80 if self.lowered_init().is_none()
81 || self.lowered_trans(0).is_none()
82 || self.lowered_property(0).is_none()
83 {
84 return InductionOutcome::Unsupported;
85 }
86 let init = self.lowered_init().unwrap();
87 bmc::prove_invariant(
88 &init,
89 &|t| self.lowered_trans(t).unwrap(),
90 &|t| self.lowered_property(t).unwrap(),
91 k,
92 )
93 }
94
95 pub fn witness_trace(&self, steps: u32) -> Option<Vec<(String, bool)>> {
101 let mut acc = self.lowered_init()?;
102 for t in 0..steps {
103 let tr = self.lowered_trans(t)?;
104 acc = ProofExpr::And(Box::new(acc), Box::new(tr));
105 }
106 match find_model(&acc) {
107 ModelOutcome::Sat(model) => Some(model),
108 _ => None,
109 }
110 }
111
112 fn lowered_init(&self) -> Option<ProofExpr> {
113 let parts: Vec<BoundedExpr> = self
115 .registers
116 .iter()
117 .filter_map(|r| r.init.map(|v| reg_init_eq(&r.name, r.width, v)))
118 .collect();
119 bounded_to_proof(&conj(parts))
120 }
121
122 fn lowered_trans(&self, t: u32) -> Option<ProofExpr> {
123 let parts = self.registers.iter().map(|r| reg_next_eq(r, t)).collect();
124 bounded_to_proof(&conj(parts))
125 }
126
127 fn lowered_property(&self, t: u32) -> Option<ProofExpr> {
128 bounded_to_proof(&time_index(&self.property, t))
129 }
130}
131
132fn reg_init_eq(name: &str, width: u32, init: u64) -> BoundedExpr {
134 let at0 = format!("{name}@0");
135 if width == 1 {
136 let v = BoundedExpr::Var(at0);
137 if init & 1 == 1 {
138 v
139 } else {
140 BoundedExpr::Not(Box::new(v))
141 }
142 } else {
143 BoundedExpr::BitVecBinary {
144 op: BitVecBoundedOp::Eq,
145 left: Box::new(BoundedExpr::BitVecVar(at0, width)),
146 right: Box::new(BoundedExpr::BitVecConst { width, value: init & mask(width) }),
147 }
148 }
149}
150
151fn reg_eq_value(name: &str, width: u32, t: u32, rhs: BoundedExpr) -> BoundedExpr {
153 let at_next = format!("{name}@{}", t + 1);
154 if width == 1 {
155 BoundedExpr::Eq(Box::new(BoundedExpr::Var(at_next)), Box::new(rhs))
156 } else {
157 BoundedExpr::BitVecBinary {
158 op: BitVecBoundedOp::Eq,
159 left: Box::new(BoundedExpr::BitVecVar(at_next, width)),
160 right: Box::new(rhs),
161 }
162 }
163}
164
165fn reg_next_eq(r: &Register, t: u32) -> BoundedExpr {
168 next_constraint(&r.name, r.width, t, &r.next)
169}
170
171fn next_constraint(name: &str, width: u32, t: u32, ns: &NextState) -> BoundedExpr {
172 match ns {
173 NextState::Simple(e) => reg_eq_value(name, width, t, time_index(e, t)),
174 NextState::Ite { cond, then_, else_ } => {
175 let c = time_index(cond, t);
176 BoundedExpr::And(
177 Box::new(BoundedExpr::Implies(
178 Box::new(c.clone()),
179 Box::new(next_constraint(name, width, t, then_)),
180 )),
181 Box::new(BoundedExpr::Implies(
182 Box::new(BoundedExpr::Not(Box::new(c))),
183 Box::new(next_constraint(name, width, t, else_)),
184 )),
185 )
186 }
187 }
188}
189
190fn conj(mut parts: Vec<BoundedExpr>) -> BoundedExpr {
191 match parts.len() {
192 0 => BoundedExpr::Bool(true),
193 1 => parts.pop().unwrap(),
194 _ => {
195 let mut acc = parts.pop().unwrap();
196 while let Some(p) = parts.pop() {
197 acc = BoundedExpr::And(Box::new(p), Box::new(acc));
198 }
199 acc
200 }
201 }
202}
203
204fn time_index(e: &BoundedExpr, t: u32) -> BoundedExpr {
206 let ri = |x: &BoundedExpr| Box::new(time_index(x, t));
207 match e {
208 BoundedExpr::Var(n) => BoundedExpr::Var(format!("{n}@{t}")),
209 BoundedExpr::BitVecVar(n, w) => BoundedExpr::BitVecVar(format!("{n}@{t}"), *w),
210 BoundedExpr::Bool(_) | BoundedExpr::Int(_) | BoundedExpr::BitVecConst { .. } => e.clone(),
211 BoundedExpr::Not(x) => BoundedExpr::Not(ri(x)),
212 BoundedExpr::And(a, b) => BoundedExpr::And(ri(a), ri(b)),
213 BoundedExpr::Or(a, b) => BoundedExpr::Or(ri(a), ri(b)),
214 BoundedExpr::Implies(a, b) => BoundedExpr::Implies(ri(a), ri(b)),
215 BoundedExpr::Eq(a, b) => BoundedExpr::Eq(ri(a), ri(b)),
216 BoundedExpr::Lt(a, b) => BoundedExpr::Lt(ri(a), ri(b)),
217 BoundedExpr::Gt(a, b) => BoundedExpr::Gt(ri(a), ri(b)),
218 BoundedExpr::Lte(a, b) => BoundedExpr::Lte(ri(a), ri(b)),
219 BoundedExpr::Gte(a, b) => BoundedExpr::Gte(ri(a), ri(b)),
220 BoundedExpr::BitVecBinary { op, left, right } => BoundedExpr::BitVecBinary {
221 op: op.clone(),
222 left: ri(left),
223 right: ri(right),
224 },
225 BoundedExpr::BitVecExtract { high, low, operand } => BoundedExpr::BitVecExtract {
226 high: *high,
227 low: *low,
228 operand: ri(operand),
229 },
230 BoundedExpr::BitVecConcat(a, b) => BoundedExpr::BitVecConcat(ri(a), ri(b)),
231 BoundedExpr::Comparison { op, left, right } => BoundedExpr::Comparison {
232 op: op.clone(),
233 left: ri(left),
234 right: ri(right),
235 },
236 other => other.clone(),
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct RtlParseError {
245 pub message: String,
246}
247
248fn perr<T>(msg: impl Into<String>) -> Result<T, RtlParseError> {
249 Err(RtlParseError { message: msg.into() })
250}
251
252#[derive(Debug, Clone, PartialEq)]
253enum Tok {
254 Ident(String),
255 Num(u64, Option<u32>),
257 Sym(String),
258}
259
260fn tokenize(src: &str) -> Result<Vec<Tok>, RtlParseError> {
261 let b = src.as_bytes();
262 let mut toks = Vec::new();
263 let mut i = 0;
264 while i < b.len() {
265 let c = b[i] as char;
266 if c.is_whitespace() {
267 i += 1;
268 continue;
269 }
270 if c == '/' && i + 1 < b.len() && b[i + 1] == b'/' {
271 while i < b.len() && b[i] != b'\n' {
272 i += 1;
273 }
274 continue;
275 }
276 if c == '/' && i + 1 < b.len() && b[i + 1] == b'*' {
277 i += 2;
278 while i + 1 < b.len() && !(b[i] == b'*' && b[i + 1] == b'/') {
279 i += 1;
280 }
281 i += 2;
282 continue;
283 }
284 if c.is_ascii_alphabetic() || c == '_' {
285 let start = i;
286 while i < b.len() && ((b[i] as char).is_ascii_alphanumeric() || b[i] == b'_') {
287 i += 1;
288 }
289 toks.push(Tok::Ident(src[start..i].to_string()));
290 continue;
291 }
292 if c.is_ascii_digit() {
293 let start = i;
294 while i < b.len() && (b[i] as char).is_ascii_digit() {
295 i += 1;
296 }
297 let size_digits = &src[start..i];
298 if i < b.len() && b[i] == b'\'' {
299 i += 1;
301 let base = if i < b.len() {
302 let ch = b[i] as char;
303 i += 1;
304 ch.to_ascii_lowercase()
305 } else {
306 return perr("malformed sized literal");
307 };
308 let vstart = i;
309 while i < b.len() && ((b[i] as char).is_ascii_alphanumeric() || b[i] == b'_') {
310 i += 1;
311 }
312 let vstr: String = src[vstart..i].chars().filter(|c| *c != '_').collect();
313 let radix = match base {
314 'b' => 2,
315 'o' => 8,
316 'd' => 10,
317 'h' => 16,
318 _ => return perr(format!("unsupported literal base '{base}'")),
319 };
320 let value = u64::from_str_radix(&vstr, radix)
321 .map_err(|_| RtlParseError { message: format!("bad literal value '{vstr}'") })?;
322 let width: u32 = size_digits
323 .parse()
324 .map_err(|_| RtlParseError { message: format!("bad literal size '{size_digits}'") })?;
325 toks.push(Tok::Num(value, Some(width)));
326 } else {
327 let value: u64 = size_digits
328 .parse()
329 .map_err(|_| RtlParseError { message: format!("bad number '{size_digits}'") })?;
330 toks.push(Tok::Num(value, None));
331 }
332 continue;
333 }
334 let two = if i + 1 < b.len() { &src[i..i + 2] } else { "" };
335 if matches!(two, "==" | "!=" | "<=" | ">=" | "&&" | "||" | "<<" | ">>") {
336 toks.push(Tok::Sym(two.to_string()));
337 i += 2;
338 continue;
339 }
340 let one = &src[i..i + 1];
341 if "()[]{};:@~&|^=<>!,+-*".contains(one) {
342 toks.push(Tok::Sym(one.to_string()));
343 i += 1;
344 continue;
345 }
346 return perr(format!("unexpected character '{c}'"));
347 }
348 Ok(toks)
349}
350
351#[derive(Clone)]
354enum Typed {
355 Bool(BoundedExpr),
356 Bv(BoundedExpr, u32),
357 Num(u64),
358}
359
360impl Typed {
361 fn as_width(self, w: u32) -> Result<BoundedExpr, RtlParseError> {
363 match self {
364 Typed::Bv(e, ew) if ew == w => Ok(e),
365 Typed::Bv(_, ew) => perr(format!("width mismatch: expected {w}, got {ew}")),
366 Typed::Num(v) if w == 1 => Ok(BoundedExpr::Bool(v & 1 == 1)),
367 Typed::Num(v) => Ok(BoundedExpr::BitVecConst { width: w, value: v & mask(w) }),
368 Typed::Bool(e) if w == 1 => Ok(e),
369 Typed::Bool(_) => perr(format!("expected a {w}-bit value, got a Boolean")),
370 }
371 }
372 fn as_bool(self) -> Result<BoundedExpr, RtlParseError> {
374 match self {
375 Typed::Bool(e) => Ok(e),
376 Typed::Num(v) => Ok(BoundedExpr::Bool(v != 0)),
377 Typed::Bv(e, w) => Ok(BoundedExpr::Not(Box::new(BoundedExpr::BitVecBinary {
378 op: BitVecBoundedOp::Eq,
379 left: Box::new(e),
380 right: Box::new(BoundedExpr::BitVecConst { width: w, value: 0 }),
381 }))),
382 }
383 }
384 fn width_hint(&self) -> Option<u32> {
385 match self {
386 Typed::Bv(_, w) => Some(*w),
387 Typed::Bool(_) => Some(1),
388 Typed::Num(_) => None,
389 }
390 }
391}
392
393fn bv_pair(a: Typed, b: Typed) -> Result<(BoundedExpr, BoundedExpr, u32), RtlParseError> {
395 let w = a.width_hint().or_else(|| b.width_hint());
396 let w = match w {
397 Some(w) if w >= 1 => w,
398 _ => return perr("ambiguous width: at least one operand must be sized"),
399 };
400 Ok((a.as_width(w)?, b.as_width(w)?, w))
401}
402
403fn bx(e: BoundedExpr) -> Box<BoundedExpr> {
404 Box::new(e)
405}
406
407struct Parser<'a> {
408 t: &'a [Tok],
409 i: usize,
410 widths: HashMap<String, u32>,
411}
412
413impl<'a> Parser<'a> {
414 fn peek(&self) -> Option<&Tok> {
415 self.t.get(self.i)
416 }
417 fn is_sym(&self, s: &str) -> bool {
418 matches!(self.peek(), Some(Tok::Sym(x)) if x == s)
419 }
420 fn is_kw(&self, k: &str) -> bool {
421 matches!(self.peek(), Some(Tok::Ident(x)) if x == k)
422 }
423 fn eat_sym(&mut self, s: &str) -> Result<(), RtlParseError> {
424 if self.is_sym(s) {
425 self.i += 1;
426 Ok(())
427 } else {
428 perr(format!("expected '{s}', found {:?}", self.peek()))
429 }
430 }
431 fn eat_kw(&mut self, k: &str) -> Result<(), RtlParseError> {
432 if self.is_kw(k) {
433 self.i += 1;
434 Ok(())
435 } else {
436 perr(format!("expected '{k}', found {:?}", self.peek()))
437 }
438 }
439 fn ident(&mut self) -> Result<String, RtlParseError> {
440 match self.peek() {
441 Some(Tok::Ident(n)) => {
442 let n = n.clone();
443 self.i += 1;
444 Ok(n)
445 }
446 other => perr(format!("expected identifier, found {other:?}")),
447 }
448 }
449 fn num(&mut self) -> Result<u64, RtlParseError> {
450 match self.peek() {
451 Some(Tok::Num(v, _)) => {
452 let v = *v;
453 self.i += 1;
454 Ok(v)
455 }
456 other => perr(format!("expected a number, found {other:?}")),
457 }
458 }
459
460 fn expr(&mut self) -> Result<Typed, RtlParseError> {
463 self.p_lor()
464 }
465 fn p_lor(&mut self) -> Result<Typed, RtlParseError> {
466 let mut a = self.p_land()?;
467 while self.is_sym("||") {
468 self.i += 1;
469 let b = self.p_land()?;
470 a = Typed::Bool(BoundedExpr::Or(bx(a.as_bool()?), bx(b.as_bool()?)));
471 }
472 Ok(a)
473 }
474 fn p_land(&mut self) -> Result<Typed, RtlParseError> {
475 let mut a = self.p_bor()?;
476 while self.is_sym("&&") {
477 self.i += 1;
478 let b = self.p_bor()?;
479 a = Typed::Bool(BoundedExpr::And(bx(a.as_bool()?), bx(b.as_bool()?)));
480 }
481 Ok(a)
482 }
483 fn p_bor(&mut self) -> Result<Typed, RtlParseError> {
484 let mut a = self.p_bxor()?;
485 while self.is_sym("|") {
486 self.i += 1;
487 let b = self.p_bxor()?;
488 a = self.bitwise(a, b, |x, y| BoundedExpr::Or(bx(x), bx(y)), BitVecBoundedOp::Or)?;
489 }
490 Ok(a)
491 }
492 fn p_bxor(&mut self) -> Result<Typed, RtlParseError> {
493 let mut a = self.p_band()?;
494 while self.is_sym("^") {
495 self.i += 1;
496 let b = self.p_band()?;
497 a = self.bitwise(
498 a,
499 b,
500 |x, y| BoundedExpr::Not(bx(BoundedExpr::Eq(bx(x), bx(y)))),
501 BitVecBoundedOp::Xor,
502 )?;
503 }
504 Ok(a)
505 }
506 fn p_band(&mut self) -> Result<Typed, RtlParseError> {
507 let mut a = self.p_eq()?;
508 while self.is_sym("&") {
509 self.i += 1;
510 let b = self.p_eq()?;
511 a = self.bitwise(a, b, |x, y| BoundedExpr::And(bx(x), bx(y)), BitVecBoundedOp::And)?;
512 }
513 Ok(a)
514 }
515 fn p_eq(&mut self) -> Result<Typed, RtlParseError> {
516 let mut a = self.p_rel()?;
517 while self.is_sym("==") || self.is_sym("!=") {
518 let neq = self.is_sym("!=");
519 self.i += 1;
520 let b = self.p_rel()?;
521 let eq = self.equality(a, b)?;
522 a = Typed::Bool(if neq { BoundedExpr::Not(bx(eq)) } else { eq });
523 }
524 Ok(a)
525 }
526 fn p_rel(&mut self) -> Result<Typed, RtlParseError> {
527 let mut a = self.p_add()?;
528 while self.is_sym("<") || self.is_sym(">") || self.is_sym("<=") || self.is_sym(">=") {
529 let op = match self.peek() {
530 Some(Tok::Sym(s)) => s.clone(),
531 _ => unreachable!(),
532 };
533 self.i += 1;
534 let b = self.p_add()?;
535 let (l, r, _w) = bv_pair(a, b)?;
536 let ult = |x: BoundedExpr, y: BoundedExpr| BoundedExpr::BitVecBinary {
538 op: BitVecBoundedOp::ULt,
539 left: bx(x),
540 right: bx(y),
541 };
542 let res = match op.as_str() {
543 "<" => ult(l, r),
544 ">" => ult(r, l),
545 "<=" => BoundedExpr::Not(bx(ult(r, l))),
546 ">=" => BoundedExpr::Not(bx(ult(l, r))),
547 _ => unreachable!(),
548 };
549 a = Typed::Bool(res);
550 }
551 Ok(a)
552 }
553 fn p_add(&mut self) -> Result<Typed, RtlParseError> {
554 let mut a = self.p_mul()?;
555 while self.is_sym("+") || self.is_sym("-") {
556 let sub = self.is_sym("-");
557 self.i += 1;
558 let b = self.p_mul()?;
559 let (l, r, w) = bv_pair(a, b)?;
560 a = Typed::Bv(
561 BoundedExpr::BitVecBinary {
562 op: if sub { BitVecBoundedOp::Sub } else { BitVecBoundedOp::Add },
563 left: bx(l),
564 right: bx(r),
565 },
566 w,
567 );
568 }
569 Ok(a)
570 }
571 fn p_mul(&mut self) -> Result<Typed, RtlParseError> {
572 let mut a = self.p_unary()?;
573 while self.is_sym("*") {
574 self.i += 1;
575 let b = self.p_unary()?;
576 let (l, r, w) = bv_pair(a, b)?;
577 a = Typed::Bv(
578 BoundedExpr::BitVecBinary { op: BitVecBoundedOp::Mul, left: bx(l), right: bx(r) },
579 w,
580 );
581 }
582 Ok(a)
583 }
584 fn p_unary(&mut self) -> Result<Typed, RtlParseError> {
585 if self.is_sym("!") {
586 self.i += 1;
587 return Ok(Typed::Bool(BoundedExpr::Not(bx(self.p_unary()?.as_bool()?))));
588 }
589 if self.is_sym("~") {
590 self.i += 1;
591 let inner = self.p_unary()?;
592 return Ok(match inner {
593 Typed::Bool(e) => Typed::Bool(BoundedExpr::Not(bx(e))),
594 Typed::Bv(e, w) => Typed::Bv(
595 BoundedExpr::BitVecBinary {
596 op: BitVecBoundedOp::Not,
597 left: bx(e.clone()),
598 right: bx(e),
599 },
600 w,
601 ),
602 Typed::Num(_) => return perr("'~' needs a sized operand"),
603 });
604 }
605 self.p_primary()
606 }
607 fn p_primary(&mut self) -> Result<Typed, RtlParseError> {
608 if self.is_sym("(") {
609 self.i += 1;
610 let e = self.expr()?;
611 self.eat_sym(")")?;
612 return Ok(e);
613 }
614 match self.peek() {
615 Some(Tok::Num(v, w)) => {
616 let (v, w) = (*v, *w);
617 self.i += 1;
618 Ok(match w {
619 None => Typed::Num(v),
620 Some(1) => Typed::Bool(BoundedExpr::Bool(v & 1 == 1)),
621 Some(w) => Typed::Bv(BoundedExpr::BitVecConst { width: w, value: v & mask(w) }, w),
622 })
623 }
624 Some(Tok::Ident(n)) => {
625 let n = n.clone();
626 self.i += 1;
627 match self.widths.get(&n).copied() {
628 Some(1) | None => Ok(Typed::Bool(BoundedExpr::Var(n))),
629 Some(w) => Ok(Typed::Bv(BoundedExpr::BitVecVar(n, w), w)),
630 }
631 }
632 other => perr(format!("expected an expression, found {other:?}")),
633 }
634 }
635
636 fn bitwise(
638 &self,
639 a: Typed,
640 b: Typed,
641 bool_op: fn(BoundedExpr, BoundedExpr) -> BoundedExpr,
642 bv_op: BitVecBoundedOp,
643 ) -> Result<Typed, RtlParseError> {
644 match (&a, &b) {
645 (Typed::Bv(..), _) | (_, Typed::Bv(..)) => {
646 let (l, r, w) = bv_pair(a, b)?;
647 Ok(Typed::Bv(BoundedExpr::BitVecBinary { op: bv_op, left: bx(l), right: bx(r) }, w))
648 }
649 _ => Ok(Typed::Bool(bool_op(a.as_bool()?, b.as_bool()?))),
650 }
651 }
652
653 fn equality(&self, a: Typed, b: Typed) -> Result<BoundedExpr, RtlParseError> {
655 match (&a, &b) {
656 (Typed::Bv(..), _) | (_, Typed::Bv(..)) => {
657 let (l, r, _w) = bv_pair(a, b)?;
658 Ok(BoundedExpr::BitVecBinary { op: BitVecBoundedOp::Eq, left: bx(l), right: bx(r) })
659 }
660 _ => Ok(BoundedExpr::Eq(bx(a.as_bool()?), bx(b.as_bool()?))),
661 }
662 }
663
664 fn skip_to_semi(&mut self) -> Result<(), RtlParseError> {
665 while !self.is_sym(";") {
666 if self.peek().is_none() {
667 return perr("expected ';' before end of input");
668 }
669 self.i += 1;
670 }
671 self.i += 1;
672 Ok(())
673 }
674 fn skip_balanced_parens(&mut self) -> Result<(), RtlParseError> {
675 self.eat_sym("(")?;
676 let mut depth = 1;
677 while depth > 0 {
678 match self.peek() {
679 None => return perr("unbalanced '(' in port list"),
680 Some(Tok::Sym(s)) if s == "(" => depth += 1,
681 Some(Tok::Sym(s)) if s == ")" => depth -= 1,
682 _ => {}
683 }
684 self.i += 1;
685 }
686 Ok(())
687 }
688
689 fn parse_range_width(&mut self) -> Result<u32, RtlParseError> {
691 self.eat_sym("[")?;
692 let hi = self.num()? as i64;
693 self.eat_sym(":")?;
694 let lo = self.num()? as i64;
695 self.eat_sym("]")?;
696 if hi < lo {
697 return perr("register range must be [hi:lo] with hi >= lo");
698 }
699 Ok((hi - lo + 1) as u32)
700 }
701
702 fn parse_block(&mut self) -> Result<HashMap<String, NextState>, RtlParseError> {
705 let mut map = HashMap::new();
706 if self.is_kw("begin") {
707 self.i += 1;
708 while !self.is_kw("end") {
709 if self.peek().is_none() {
710 return perr("missing 'end' in always block");
711 }
712 self.parse_stmt(&mut map)?;
713 }
714 self.i += 1;
715 } else {
716 self.parse_stmt(&mut map)?;
717 }
718 Ok(map)
719 }
720
721 fn parse_stmt(&mut self, map: &mut HashMap<String, NextState>) -> Result<(), RtlParseError> {
722 if self.is_kw("if") {
723 self.i += 1;
724 self.eat_sym("(")?;
725 let cond = self.expr()?.as_bool()?;
726 self.eat_sym(")")?;
727 let then_map = self.parse_block()?;
728 let else_map = if self.is_kw("else") {
729 self.i += 1;
730 self.parse_block()?
731 } else {
732 HashMap::new()
733 };
734 let mut names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
735 names.extend(then_map.keys().cloned());
736 names.extend(else_map.keys().cloned());
737 for n in names {
738 let w = self.widths.get(&n).copied().unwrap_or(1);
739 let hold = if w == 1 {
740 NextState::Simple(BoundedExpr::Var(n.clone()))
741 } else {
742 NextState::Simple(BoundedExpr::BitVecVar(n.clone(), w))
743 };
744 let prior = map.get(&n).cloned().unwrap_or(hold);
745 let then_ns = then_map.get(&n).cloned().unwrap_or_else(|| prior.clone());
746 let else_ns = else_map.get(&n).cloned().unwrap_or(prior);
747 map.insert(
748 n,
749 NextState::Ite {
750 cond: cond.clone(),
751 then_: Box::new(then_ns),
752 else_: Box::new(else_ns),
753 },
754 );
755 }
756 Ok(())
757 } else {
758 let n = self.ident()?;
759 let w = self.widths.get(&n).copied().unwrap_or(1);
760 if self.is_sym("<=") {
761 self.i += 1;
762 } else {
763 self.eat_sym("=")?;
764 }
765 let e = self.expr()?.as_width(w)?;
766 self.eat_sym(";")?;
767 map.insert(n, NextState::Simple(e));
768 Ok(())
769 }
770 }
771}
772
773pub fn parse_transition_system(src: &str) -> Result<TransitionSystem, RtlParseError> {
776 let toks = tokenize(src)?;
777 let is_decl_kw = |s: &str| matches!(s, "reg" | "input" | "output" | "wire");
782 let mut widths: HashMap<String, u32> = HashMap::new();
783 {
784 let mut j = 0;
785 while j < toks.len() {
786 if matches!(&toks[j], Tok::Ident(k) if is_decl_kw(k)) {
787 j += 1;
788 let mut w = 1u32;
789 if matches!(toks.get(j), Some(Tok::Sym(s)) if s == "[") {
790 let mut tmp = Parser { t: &toks, i: j, widths: HashMap::new() };
791 w = tmp.parse_range_width()?;
792 j = tmp.i;
793 }
794 loop {
797 match toks.get(j) {
798 Some(Tok::Ident(n)) if !is_decl_kw(n) => {
799 widths.insert(n.clone(), w);
800 j += 1;
801 }
802 _ => break,
803 }
804 if matches!(toks.get(j), Some(Tok::Sym(s)) if s == ",") {
805 j += 1;
806 continue;
807 }
808 break;
809 }
810 } else {
811 j += 1;
812 }
813 }
814 }
815
816 let mut p = Parser { t: &toks, i: 0, widths };
817 p.eat_kw("module")?;
818 let _name = p.ident()?;
819 if p.is_sym("(") {
820 p.skip_balanced_parens()?;
821 }
822 p.eat_sym(";")?;
823
824 let mut order: Vec<String> = Vec::new();
825 let mut init: HashMap<String, u64> = HashMap::new();
826 let mut next: HashMap<String, NextState> = HashMap::new();
827 let mut property: Option<BoundedExpr> = None;
828
829 loop {
830 match p.peek() {
831 None => return perr("unexpected end of input (missing 'endmodule')"),
832 Some(Tok::Ident(k)) if k == "endmodule" => {
833 p.i += 1;
834 break;
835 }
836 Some(Tok::Ident(k)) if k == "reg" => {
837 p.i += 1;
838 if p.is_sym("[") {
839 let _ = p.parse_range_width()?;
840 }
841 loop {
842 let n = p.ident()?;
843 if !order.contains(&n) {
844 order.push(n);
845 }
846 if p.is_sym(",") {
847 p.i += 1;
848 continue;
849 }
850 break;
851 }
852 p.eat_sym(";")?;
853 }
854 Some(Tok::Ident(k)) if k == "input" || k == "output" || k == "wire" => {
855 p.skip_to_semi()?;
856 }
857 Some(Tok::Ident(k)) if k == "initial" => {
858 p.i += 1;
859 let mut one = |p: &mut Parser| -> Result<(), RtlParseError> {
860 let n = p.ident()?;
861 p.eat_sym("=")?;
862 let v = p.num()?;
863 p.eat_sym(";")?;
864 init.insert(n, v);
865 Ok(())
866 };
867 if p.is_kw("begin") {
868 p.i += 1;
869 while !p.is_kw("end") {
870 if p.peek().is_none() {
871 return perr("missing 'end' for initial block");
872 }
873 one(&mut p)?;
874 }
875 p.i += 1;
876 } else {
877 one(&mut p)?;
878 }
879 }
880 Some(Tok::Ident(k)) if k == "always" => {
881 p.i += 1;
882 p.eat_sym("@")?;
883 p.eat_sym("(")?;
884 p.eat_kw("posedge")?;
885 let _clk = p.ident()?;
886 p.eat_sym(")")?;
887 let block = p.parse_block()?;
888 for (n, ns) in block {
889 next.insert(n, ns);
890 }
891 }
892 Some(Tok::Ident(k)) if k == "assert" => {
893 p.i += 1;
894 p.eat_kw("property")?;
895 p.eat_sym("(")?;
896 if p.is_sym("@") {
897 p.i += 1;
898 p.eat_sym("(")?;
899 p.eat_kw("posedge")?;
900 let _ = p.ident()?;
901 p.eat_sym(")")?;
902 }
903 let e = p.expr()?.as_bool()?;
904 p.eat_sym(")")?;
905 p.eat_sym(";")?;
906 property = Some(e);
907 }
908 Some(_) => p.skip_to_semi()?,
909 }
910 }
911
912 if order.is_empty() {
913 return perr("no registers found");
914 }
915 let property = match property {
916 Some(p) => p,
917 None => return perr("no 'assert property' found"),
918 };
919
920 let mut registers = Vec::with_capacity(order.len());
921 for name in &order {
922 let width = p.widths.get(name).copied().unwrap_or(1);
923 let init_val = init.get(name).map(|v| *v & mask(width));
925 let next_ns = next.remove(name).unwrap_or_else(|| {
926 if width == 1 {
928 NextState::Simple(BoundedExpr::Var(name.clone()))
929 } else {
930 NextState::Simple(BoundedExpr::BitVecVar(name.clone(), width))
931 }
932 });
933 registers.push(Register { name: name.clone(), width, init: init_val, next: next_ns });
934 }
935
936 Ok(TransitionSystem { registers, property })
937}
938
939#[cfg(test)]
940mod tests {
941 use super::*;
942
943 fn var(n: &str) -> BoundedExpr {
944 BoundedExpr::Var(n.to_string())
945 }
946 fn not(e: BoundedExpr) -> BoundedExpr {
947 BoundedExpr::Not(Box::new(e))
948 }
949 fn reg(name: &str, init: u64, next: BoundedExpr) -> Register {
950 Register {
951 name: name.to_string(),
952 width: 1,
953 init: Some(init),
954 next: NextState::Simple(next),
955 }
956 }
957
958 #[test]
959 fn bmc_finds_toggle_violation() {
960 let ts = TransitionSystem {
961 registers: vec![reg("q", 0, not(var("q")))],
962 property: not(var("q")),
963 };
964 match ts.bmc(5) {
965 BmcOutcome::CounterexampleAt { k, trace } => {
966 assert_eq!(k, 1);
967 assert!(trace.iter().any(|(n, v)| n == "q@1" && *v), "trace: {trace:?}");
968 }
969 other => panic!("expected a counterexample, got {other:?}"),
970 }
971 }
972
973 #[test]
974 fn k_induction_proves_latched_invariant() {
975 let ts = TransitionSystem {
976 registers: vec![reg("x", 1, var("x"))],
977 property: var("x"),
978 };
979 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
980 assert_eq!(ts.bmc(6), BmcOutcome::NoneWithin(6));
981 }
982
983 #[test]
984 fn k_induction_proves_two_register_mutex() {
985 let ts = TransitionSystem {
986 registers: vec![reg("a", 1, var("b")), reg("b", 0, var("a"))],
987 property: not(BoundedExpr::And(Box::new(var("a")), Box::new(var("b")))),
988 };
989 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
990 }
991
992 #[test]
995 fn verilog_toggle_violation_found() {
996 let src = r#"
997 module toggle(input clk);
998 reg q;
999 initial q = 0;
1000 always @(posedge clk) q <= ~q;
1001 assert property (@(posedge clk) ~q);
1002 endmodule
1003 "#;
1004 let ts = parse_transition_system(src).expect("parses");
1005 match ts.bmc(5) {
1006 BmcOutcome::CounterexampleAt { k, trace } => {
1007 assert_eq!(k, 1);
1008 assert!(trace.iter().any(|(n, v)| n == "q@1" && *v));
1009 }
1010 other => panic!("expected a counterexample, got {other:?}"),
1011 }
1012 }
1013
1014 #[test]
1015 fn verilog_latched_invariant_proven() {
1016 let src = r#"
1017 module latch(input clk);
1018 reg x;
1019 initial x = 1;
1020 always @(posedge clk) x <= x;
1021 assert property (x);
1022 endmodule
1023 "#;
1024 let ts = parse_transition_system(src).expect("parses");
1025 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
1026 }
1027
1028 #[test]
1029 fn verilog_mutex_swap_invariant_proven() {
1030 let src = r#"
1031 module mutex(input clk);
1032 reg a;
1033 reg b;
1034 initial begin a = 1; b = 0; end
1035 always @(posedge clk) begin
1036 a <= b;
1037 b <= a;
1038 end
1039 assert property (~(a & b));
1040 endmodule
1041 "#;
1042 let ts = parse_transition_system(src).expect("parses");
1043 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
1044 }
1045
1046 #[test]
1049 fn verilog_two_bit_counter_reaches_three() {
1050 let src = r#"
1052 module counter(input clk);
1053 reg [1:0] cnt;
1054 initial cnt = 0;
1055 always @(posedge clk) cnt <= cnt + 1;
1056 assert property (cnt != 2'd3);
1057 endmodule
1058 "#;
1059 let ts = parse_transition_system(src).expect("parses");
1060 assert_eq!(ts.registers[0].width, 2);
1061 match ts.bmc(6) {
1062 BmcOutcome::CounterexampleAt { k, .. } => assert_eq!(k, 3),
1063 other => panic!("expected a counterexample, got {other:?}"),
1064 }
1065 }
1066
1067 #[test]
1068 fn verilog_four_bit_counter_bound_is_invariant() {
1069 let src = r#"
1071 module counter(input clk);
1072 reg [3:0] cnt;
1073 initial cnt = 0;
1074 always @(posedge clk) cnt <= cnt + 1;
1075 assert property (cnt <= 4'd15);
1076 endmodule
1077 "#;
1078 let ts = parse_transition_system(src).expect("parses");
1079 assert_eq!(ts.registers[0].width, 4);
1080 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
1081 }
1082
1083 #[test]
1084 fn verilog_three_bit_counter_violates_a_false_bound() {
1085 let src = r#"
1087 module counter(input clk);
1088 reg [2:0] cnt;
1089 initial cnt = 0;
1090 always @(posedge clk) cnt <= cnt + 1;
1091 assert property (cnt < 3'd5);
1092 endmodule
1093 "#;
1094 let ts = parse_transition_system(src).expect("parses");
1095 match ts.bmc(8) {
1096 BmcOutcome::CounterexampleAt { k, .. } => assert_eq!(k, 5),
1097 other => panic!("expected a counterexample, got {other:?}"),
1098 }
1099 }
1100
1101 #[test]
1102 fn verilog_datapath_register_equality_invariant() {
1103 let src = r#"
1105 module mirror(input clk);
1106 reg [3:0] x;
1107 reg [3:0] y;
1108 initial begin x = 3; y = 3; end
1109 always @(posedge clk) begin
1110 x <= x + 1;
1111 y <= y + 1;
1112 end
1113 assert property (x == y);
1114 endmodule
1115 "#;
1116 let ts = parse_transition_system(src).expect("parses");
1117 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
1118 }
1119
1120 #[test]
1125 fn verilog_reset_mirrored_registers_stay_equal() {
1126 let src = r#"
1129 module mirror(input clk, input rst);
1130 reg a;
1131 reg b;
1132 initial begin a = 0; b = 0; end
1133 always @(posedge clk) begin
1134 if (rst) a <= 0; else a <= ~a;
1135 if (rst) b <= 0; else b <= ~b;
1136 end
1137 assert property (a == b);
1138 endmodule
1139 "#;
1140 let ts = parse_transition_system(src).expect("parses reset logic");
1141 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
1142 }
1143
1144 #[test]
1145 fn verilog_multibit_reset_clears_counter() {
1146 let src = r#"
1149 module counter(input clk, input rst);
1150 reg [3:0] cnt;
1151 initial cnt = 0;
1152 always @(posedge clk) if (rst) cnt <= 0; else cnt <= cnt + 1;
1153 assert property (cnt == 4'd0);
1154 endmodule
1155 "#;
1156 let ts = parse_transition_system(src).expect("parses");
1157 match ts.bmc(4) {
1158 BmcOutcome::CounterexampleAt { k, .. } => assert_eq!(k, 1, "rst low for one cycle ⇒ cnt=1"),
1159 other => panic!("expected a counterexample, got {other:?}"),
1160 }
1161 }
1162
1163 #[test]
1164 fn verilog_free_initial_state_is_explored() {
1165 let src = r#"
1168 module m(input clk);
1169 reg q;
1170 always @(posedge clk) q <= q;
1171 assert property (~q);
1172 endmodule
1173 "#;
1174 let ts = parse_transition_system(src).expect("parses without initial");
1175 assert!(ts.registers[0].init.is_none(), "q must have a free initial state");
1176 match ts.bmc(3) {
1177 BmcOutcome::CounterexampleAt { k, .. } => assert_eq!(k, 0, "a free q can start high"),
1178 other => panic!("expected a step-0 counterexample, got {other:?}"),
1179 }
1180 }
1181
1182 #[test]
1185 fn verilog_arbiter_mutual_exclusion_proven() {
1186 let src = r#"
1189 module arbiter(input clk, input r0, input r1);
1190 reg g0;
1191 reg g1;
1192 reg turn;
1193 initial begin g0 = 0; g1 = 0; turn = 0; end
1194 always @(posedge clk) begin
1195 if (r0 && (!r1 || turn == 0)) begin
1196 g0 <= 1;
1197 g1 <= 0;
1198 end else if (r1) begin
1199 g0 <= 0;
1200 g1 <= 1;
1201 end else begin
1202 g0 <= 0;
1203 g1 <= 0;
1204 end
1205 turn <= ~turn;
1206 end
1207 assert property (~(g0 & g1));
1208 endmodule
1209 "#;
1210 let ts = parse_transition_system(src).expect("parses");
1211 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
1212 }
1213
1214 #[test]
1215 fn verilog_naive_arbiter_double_grant_bug_found() {
1216 let src = r#"
1219 module bad_arbiter(input clk, input r0, input r1);
1220 reg g0;
1221 reg g1;
1222 initial begin g0 = 0; g1 = 0; end
1223 always @(posedge clk) begin
1224 if (r0) g0 <= 1; else g0 <= 0;
1225 if (r1) g1 <= 1; else g1 <= 0;
1226 end
1227 assert property (~(g0 & g1));
1228 endmodule
1229 "#;
1230 let ts = parse_transition_system(src).expect("parses");
1231 match ts.bmc(4) {
1232 BmcOutcome::CounterexampleAt { k, trace } => {
1233 assert_eq!(k, 1, "both requested at t0 ⇒ double grant at t1");
1234 assert!(trace.iter().any(|(n, v)| n == "g0@1" && *v));
1235 assert!(trace.iter().any(|(n, v)| n == "g1@1" && *v));
1236 }
1237 other => panic!("expected a double-grant counterexample, got {other:?}"),
1238 }
1239 }
1240
1241 #[test]
1242 fn verilog_fifo_occupancy_never_overflows_proven() {
1243 let src = r#"
1248 module fifo(input clk, input push, input pop);
1249 reg [3:0] count;
1250 initial count = 0;
1251 always @(posedge clk)
1252 if (push && (count < 4'd8) && !(pop && (count > 4'd0)))
1253 count <= count + 1;
1254 else if (pop && (count > 4'd0) && !(push && (count < 4'd8)))
1255 count <= count - 1;
1256 else
1257 count <= count;
1258 assert property (count <= 4'd8);
1259 endmodule
1260 "#;
1261 let ts = parse_transition_system(src).expect("parses");
1262 assert_eq!(ts.registers[0].width, 4);
1263 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
1264 }
1265
1266 const TRAFFIC_SAFE: &str = r#"
1272 module traffic(input clk);
1273 reg [1:0] ns;
1274 reg [1:0] ew;
1275 reg [1:0] nsl;
1276 reg [1:0] ewl;
1277 reg ped;
1278 reg [3:0] phase;
1279 reg [2:0] timer;
1280 initial begin ns=2'd0; ew=2'd0; nsl=2'd1; ewl=2'd0; ped=1'd0; phase=4'd0; timer=3'd1; end
1281 always @(posedge clk)
1282 if (timer == 3'd0) begin
1283 timer <= 3'd1;
1284 if (phase == 4'd0) begin phase<=4'd1; nsl<=2'd2; ns<=2'd0; ew<=2'd0; ewl<=2'd0; ped<=1'd0; end
1285 else if (phase == 4'd1) begin phase<=4'd2; nsl<=2'd0; ns<=2'd1; ew<=2'd0; ewl<=2'd0; ped<=1'd0; end
1286 else if (phase == 4'd2) begin phase<=4'd3; ns<=2'd2; nsl<=2'd0; ew<=2'd0; ewl<=2'd0; ped<=1'd0; end
1287 else if (phase == 4'd3) begin phase<=4'd4; ewl<=2'd1; ns<=2'd0; ew<=2'd0; nsl<=2'd0; ped<=1'd0; end
1288 else if (phase == 4'd4) begin phase<=4'd5; ewl<=2'd2; ns<=2'd0; ew<=2'd0; nsl<=2'd0; ped<=1'd0; end
1289 else if (phase == 4'd5) begin phase<=4'd6; ewl<=2'd0; ew<=2'd1; ns<=2'd0; nsl<=2'd0; ped<=1'd0; end
1290 else if (phase == 4'd6) begin phase<=4'd7; ew<=2'd2; ns<=2'd0; nsl<=2'd0; ewl<=2'd0; ped<=1'd0; end
1291 else if (phase == 4'd7) begin phase<=4'd8; ped<=1'd1; ns<=2'd0; ew<=2'd0; nsl<=2'd0; ewl<=2'd0; end
1292 else begin phase<=4'd0; nsl<=2'd1; ns<=2'd0; ew<=2'd0; ewl<=2'd0; ped<=1'd0; end
1293 end else
1294 timer <= timer - 1;
1295 assert property (~((ns != 2'd0) & (ew != 2'd0)) & ~((ped == 1'd1) & ((ns != 2'd0) | (ew != 2'd0) | (nsl != 2'd0) | (ewl != 2'd0))));
1296 endmodule
1297 "#;
1298
1299 #[test]
1300 fn verilog_complex_traffic_controller_is_proven_safe() {
1301 let ts = parse_transition_system(TRAFFIC_SAFE).expect("parses");
1304 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
1305 }
1306
1307 #[test]
1308 fn verilog_buggy_traffic_controller_crashes() {
1309 let buggy = TRAFFIC_SAFE.replace(
1312 "phase<=4'd8; ped<=1'd1; ns<=2'd0;",
1313 "phase<=4'd8; ped<=1'd1; ns<=2'd1;",
1314 );
1315 assert_ne!(buggy, TRAFFIC_SAFE, "the buggy variant must differ");
1316 let ts = parse_transition_system(&buggy).expect("parses");
1317 match ts.bmc(40) {
1318 BmcOutcome::CounterexampleAt { .. } => {}
1319 other => panic!("expected a pedestrian-vs-traffic conflict, got {other:?}"),
1320 }
1321 }
1322
1323 #[test]
1324 fn witness_trace_of_safe_controller_is_a_conflict_free_run() {
1325 let ts = parse_transition_system(TRAFFIC_SAFE).expect("parses");
1329 let steps = 16u32;
1330 let model = ts.witness_trace(steps).expect("a witness run exists");
1331 let m: HashMap<String, bool> = model.into_iter().collect();
1332 let bv = |name: &str, t: u32, width: u32| -> u64 {
1333 (0..width)
1334 .filter(|i| *m.get(&format!("{name}@{t}#{i}")).unwrap_or(&false))
1335 .fold(0u64, |acc, i| acc | (1 << i))
1336 };
1337 let bit = |name: &str, t: u32| -> bool { *m.get(&format!("{name}@{t}")).unwrap_or(&false) };
1338 assert_eq!(bv("nsl", 0, 2), 1, "init: NS-left should be green");
1340 assert_eq!(bv("ns", 0, 2), 0, "init: NS-through should be red");
1341 for t in 0..steps {
1342 let (ns, ew, nsl, ewl) = (bv("ns", t, 2), bv("ew", t, 2), bv("nsl", t, 2), bv("ewl", t, 2));
1343 let ped = bit("ped", t);
1344 let cross = ns != 0 && ew != 0;
1345 let ped_conflict = ped && (ns != 0 || ew != 0 || nsl != 0 || ewl != 0);
1346 assert!(
1347 !cross && !ped_conflict,
1348 "witness conflicts at step {t}: ns={ns} ew={ew} nsl={nsl} ewl={ewl} ped={ped}"
1349 );
1350 }
1351 }
1352
1353 #[test]
1354 fn verilog_onehot_fsm_stays_one_hot_proven() {
1355 let src = r#"
1358 module onehot(input clk);
1359 reg a;
1360 reg b;
1361 reg c;
1362 initial begin a = 1; b = 0; c = 0; end
1363 always @(posedge clk) begin
1364 a <= c;
1365 b <= a;
1366 c <= b;
1367 end
1368 assert property ((a | b | c) & ~(a & b) & ~(a & c) & ~(b & c));
1369 endmodule
1370 "#;
1371 let ts = parse_transition_system(src).expect("parses");
1372 assert_eq!(ts.prove_invariant(1), InductionOutcome::Proven);
1373 }
1374}