From a04dd92de39fe2eb9d12cec289b208306ebfe5c4 Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Wed, 1 Jul 2026 12:12:04 -0400 Subject: [PATCH 01/12] transactions need to relinquish ports on exit --- protocols/src/ir/lowering.rs | 51 +++++++++++++++------ protocols/src/ir/trace_lowering.rs | 1 + runt/waveform/runt.toml | 72 ++++++++++++++++++++++-------- scripts/generate_runt_configs.py | 7 +-- 4 files changed, 97 insertions(+), 34 deletions(-) diff --git a/protocols/src/ir/lowering.rs b/protocols/src/ir/lowering.rs index 467b8714..ff0697cd 100644 --- a/protocols/src/ir/lowering.rs +++ b/protocols/src/ir/lowering.rs @@ -49,6 +49,21 @@ impl<'a> Lowerer<'a> { node_id } + fn dont_care_expr(&mut self, tpe: PatronusType) -> ExprRef { + let next_dont_care = self.ir.dont_cares.len(); + let name = format!("dont_care_{}", next_dont_care); + let dont_care_expr = match tpe { + PatronusType::BV(width) => self.ir.expr_ctx.bv_symbol(&name, width), + PatronusType::Array(array_tpe) => { + self.ir + .expr_ctx + .array_symbol(&name, array_tpe.index_width, array_tpe.data_width) + } + }; + self.ir.dont_cares.insert(dont_care_expr); + dont_care_expr + } + fn lower_expr( &mut self, ast: &Protocol, @@ -58,19 +73,7 @@ impl<'a> Lowerer<'a> { match &ast[expr] { Expr::DontCare => { let tpe = expected.unwrap_or(PatronusType::BV(1)); - let next_dont_care = self.ir.dont_cares.len(); - // the name here is not relevant for anything other than debugging - let name = format!("dont_care_{}", next_dont_care); - let dont_care_expr = match tpe { - PatronusType::BV(width) => self.ir.expr_ctx.bv_symbol(&name, width), - PatronusType::Array(array_tpe) => self.ir.expr_ctx.array_symbol( - &name, - array_tpe.index_width, - array_tpe.data_width, - ), - }; - self.ir.dont_cares.insert(dont_care_expr); - dont_care_expr + self.dont_care_expr(tpe) } Expr::Const(bvv) => self.ir.expr_ctx.bv_lit(bvv), Expr::Sym(sym) => self.lower_symbol(*sym), @@ -289,6 +292,28 @@ impl<'a> Lowerer<'a> { self.ir.push_action(done, Action::new(true_id, done_op)); } + // relinquish all ports in the exit node + let dut = ast + .ctx + .type_param + .expect("protocol should have a DUT type parameter"); + + for input in self + .symbols + .get_children(&dut) + .into_iter() + .filter(|sym| self.symbols[*sym].is_in_port()) + { + let width = match self.symbols[input].tpe() { + FrontType::BitVec(width) => width, + other => panic!("input port type must be a BitVec: {other:?}"), + }; + let rhs = self.dont_care_expr(PatronusType::BV(width)); + let assign = self.ir.o(Op::Assign(input, rhs)); + self.ir + .push_action(done, Action::new(self.ir.true_id(), assign)); + } + // lower the protocol, which will add the new nodes to self.current_fragment_nodes let entry = self.lower_stmt(ast, ast.body, done); let nodes = std::mem::take(&mut self.current_fragment_nodes); diff --git a/protocols/src/ir/trace_lowering.rs b/protocols/src/ir/trace_lowering.rs index 8cb27fec..c5d8516c 100644 --- a/protocols/src/ir/trace_lowering.rs +++ b/protocols/src/ir/trace_lowering.rs @@ -169,6 +169,7 @@ pub fn lower_trace_to_ir( .ir .push_transition(entry_node, Transition::new(true_id, first.entry, false)); contract_edges(&mut lowerer.ir, symbols); + // normalize_assignments(&mut lowerer.ir, symbols); // pass in the initial IR with the first transaction and its graft points, and append_trace_transactions will lower the rest of the trace from here. let graft_points = lowerer.next_trace_graft_points(&first); diff --git a/runt/waveform/runt.toml b/runt/waveform/runt.toml index efb989d1..f3894b2b 100644 --- a/runt/waveform/runt.toml +++ b/runt/waveform/runt.toml @@ -18,6 +18,24 @@ expect_dir = "../../examples/picorv32/expects" expect_name = "unsigned_mul.waveform.expect" cmd = "cd ../.. && target/debug/graph-interp --transactions examples/picorv32/unsigned_mul.tx --respect-forks --determinize --ascii-waveform --verilog examples/picorv32/picorv32.v --protocol examples/picorv32/pcpi_mul.prot --module picorv32_pcpi_mul 2>/dev/null" +[[tests]] +name = "waveform.examples_serv_serv_regfile.serv_regfile_waveform.ast" +paths = [ + "../../examples/serv/serv_regfile.tx", +] +expect_dir = "../../examples/serv/expects" +expect_name = "serv_regfile.waveform.expect" +cmd = "cd ../.. && target/debug/protocols-interp --color never --transactions examples/serv/serv_regfile.tx --ascii-waveform --verilog examples/serv/rtl/serv_regfile.v --protocol examples/serv/serv_regfile.prot 2>/dev/null" + +[[tests]] +name = "waveform.examples_serv_serv_regfile.serv_regfile_waveform.graph" +paths = [ + "../../examples/serv/serv_regfile.tx", +] +expect_dir = "../../examples/serv/expects" +expect_name = "serv_regfile.waveform.expect" +cmd = "cd ../.. && target/debug/graph-interp --transactions examples/serv/serv_regfile.tx --respect-forks --determinize --ascii-waveform --verilog examples/serv/rtl/serv_regfile.v --protocol examples/serv/serv_regfile.prot 2>/dev/null" + [[tests]] name = "waveform.examples_tinyaes128_aes128.aes128_waveform.ast" paths = [ @@ -306,6 +324,42 @@ expect_dir = "../../tests/counters/expects" expect_name = "counter.waveform.expect" cmd = "cd ../.. && target/debug/graph-interp --transactions tests/counters/counter.tx --respect-forks --determinize --ascii-waveform --verilog tests/counters/counter.v --protocol tests/counters/counter.prot 2>/dev/null" +[[tests]] +name = "waveform.tests_fifo_fifo.fifo_waveform.ast" +paths = [ + "../../tests/fifo/fifo.tx", +] +expect_dir = "../../tests/fifo/expects" +expect_name = "fifo.waveform.expect" +cmd = "cd ../.. && target/debug/protocols-interp --color never --transactions tests/fifo/fifo.tx --ascii-waveform --verilog tests/fifo/bsg_mem_1rw_sync.v tests/fifo/bsg_mem_1rw_sync_synth.v tests/fifo/bsg_circular_ptr.v tests/fifo/bsg_fifo_1rw_large.v tests/fifo/fifo_wrapper.v --protocol tests/fifo/fifo.prot --module fifo_wrapper 2>/dev/null" + +[[tests]] +name = "waveform.tests_fifo_fifo.fifo_waveform.graph" +paths = [ + "../../tests/fifo/fifo.tx", +] +expect_dir = "../../tests/fifo/expects" +expect_name = "fifo.waveform.expect" +cmd = "cd ../.. && target/debug/graph-interp --transactions tests/fifo/fifo.tx --respect-forks --determinize --ascii-waveform --verilog tests/fifo/bsg_mem_1rw_sync.v tests/fifo/bsg_mem_1rw_sync_synth.v tests/fifo/bsg_circular_ptr.v tests/fifo/bsg_fifo_1rw_large.v tests/fifo/fifo_wrapper.v --protocol tests/fifo/fifo.prot --module fifo_wrapper 2>/dev/null" + +[[tests]] +name = "waveform.tests_fifo_push_pop_identity_ok.push_pop_identity_ok_waveform.ast" +paths = [ + "../../tests/fifo/push_pop_identity_ok.tx", +] +expect_dir = "../../tests/fifo/expects" +expect_name = "push_pop_identity_ok.waveform.expect" +cmd = "cd ../.. && target/debug/protocols-interp --color never --transactions tests/fifo/push_pop_identity_ok.tx --ascii-waveform --verilog tests/fifo/bsg_mem_1rw_sync.v tests/fifo/bsg_mem_1rw_sync_synth.v tests/fifo/bsg_circular_ptr.v tests/fifo/bsg_fifo_1rw_large.v tests/fifo/fifo_wrapper.v --protocol tests/fifo/fifo.prot --module fifo_wrapper 2>/dev/null" + +[[tests]] +name = "waveform.tests_fifo_push_pop_identity_ok.push_pop_identity_ok_waveform.graph" +paths = [ + "../../tests/fifo/push_pop_identity_ok.tx", +] +expect_dir = "../../tests/fifo/expects" +expect_name = "push_pop_identity_ok.waveform.expect" +cmd = "cd ../.. && target/debug/graph-interp --transactions tests/fifo/push_pop_identity_ok.tx --respect-forks --determinize --ascii-waveform --verilog tests/fifo/bsg_mem_1rw_sync.v tests/fifo/bsg_mem_1rw_sync_synth.v tests/fifo/bsg_circular_ptr.v tests/fifo/bsg_fifo_1rw_large.v tests/fifo/fifo_wrapper.v --protocol tests/fifo/fifo.prot --module fifo_wrapper 2>/dev/null" + [[tests]] name = "waveform.tests_identities_identity_d0_passthrough_combdep.passthrough_combdep_waveform.ast" paths = [ @@ -360,24 +414,6 @@ expect_dir = "../../tests/identities/identity_d2/expects" expect_name = "single_thread_passes.waveform.expect" cmd = "cd ../.. && target/debug/graph-interp --transactions tests/identities/identity_d2/single_thread_passes.tx --respect-forks --determinize --ascii-waveform --verilog tests/identities/identity_d2/identity_d2.v --protocol tests/identities/identity_d2/identity_d2.prot 2>/dev/null" -[[tests]] -name = "waveform.tests_identities_identity_d2_two_assignments_same_value.two_assignments_same_value_waveform.ast" -paths = [ - "../../tests/identities/identity_d2/two_assignments_same_value.tx", -] -expect_dir = "../../tests/identities/identity_d2/expects" -expect_name = "two_assignments_same_value.waveform.expect" -cmd = "cd ../.. && target/debug/protocols-interp --color never --transactions tests/identities/identity_d2/two_assignments_same_value.tx --ascii-waveform --verilog tests/identities/identity_d2/identity_d2.v --protocol tests/identities/identity_d2/identity_d2.prot 2>/dev/null" - -[[tests]] -name = "waveform.tests_identities_identity_d2_two_assignments_same_value.two_assignments_same_value_waveform.graph" -paths = [ - "../../tests/identities/identity_d2/two_assignments_same_value.tx", -] -expect_dir = "../../tests/identities/identity_d2/expects" -expect_name = "two_assignments_same_value.waveform.expect" -cmd = "cd ../.. && target/debug/graph-interp --transactions tests/identities/identity_d2/two_assignments_same_value.tx --respect-forks --determinize --ascii-waveform --verilog tests/identities/identity_d2/identity_d2.v --protocol tests/identities/identity_d2/identity_d2.prot 2>/dev/null" - [[tests]] name = "waveform.tests_multi_multi0_multi0.multi0_waveform.ast" paths = [ diff --git a/scripts/generate_runt_configs.py b/scripts/generate_runt_configs.py index 011e7293..d2f966b4 100644 --- a/scripts/generate_runt_configs.py +++ b/scripts/generate_runt_configs.py @@ -282,10 +282,11 @@ def waveform_cases(cases: list[dict]) -> list[dict]: # these cases are correct, but our ASCII diffing isn't good enough # for us to know they are the same xfailed = [ - "examples/serv/serv_regfile.tx", + #"examples/serv/serv_regfile.tx", "tests/adders/adder_d1/wait_and_add_correct.tx", - "tests/fifo/fifo.tx", - "tests/fifo/push_pop_identity_ok.tx", + "tests/identities/identity_d2/two_assignments_same_value.tx", + #"tests/fifo/fifo.tx", + #"tests/fifo/push_pop_identity_ok.tx", "tests/wishbone/wishbone.tx", ] From 22e9e9c0232a5b1e7e01a5d0fb807a06b793348d Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Thu, 2 Jul 2026 10:27:06 -0400 Subject: [PATCH 02/12] reaching definitions analysis --- protocols/src/ir/mod.rs | 2 +- protocols/src/ir/reaching_defs.rs | 68 +++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 protocols/src/ir/reaching_defs.rs diff --git a/protocols/src/ir/mod.rs b/protocols/src/ir/mod.rs index 58ba93ca..49be98d7 100644 --- a/protocols/src/ir/mod.rs +++ b/protocols/src/ir/mod.rs @@ -4,8 +4,8 @@ pub mod graph_interpreter; pub mod graphviz; pub mod lowering; pub mod proto_graph; +pub mod reaching_defs; pub mod trace_lowering; - // TODO: add a function to transform AST to IR // pub fn frontend( // ast : ast::Protocol diff --git a/protocols/src/ir/reaching_defs.rs b/protocols/src/ir/reaching_defs.rs new file mode 100644 index 00000000..9119badd --- /dev/null +++ b/protocols/src/ir/reaching_defs.rs @@ -0,0 +1,68 @@ +// Copyright 2026 Cornell University +// released under MIT License +// author: Nikil Shyamunder + +use crate::frontend::symbol::{SymbolId, SymbolTable}; +use crate::interpreter::Value as WaveValue; +use crate::ir::proto_graph::{Node, NodeId, ProtoGraph}; +use rustc_hash::FxHashMap; + +/// Nodes only store their outgoing transitions. This function precomputes and returns +/// the in-neighbors for each node that is reachable from the entry. +fn predecessors(pg : &ProtoGraph) -> FxHashMap> { + let mut predecessors : FxHashMap> = FxHashMap::default(); + let mut q = vec![pg.entry]; + + while let Some(n) = q.pop() { + // n is a predecessor of t + for t in pg[n].clone().transitions { + predecessors.entry(n).and_modify(|v| v.push(t.target)); + q.push(t.target); + } + } + + predecessors +} + +pub fn reaching_definitions( + pg: &ProtoGraph, + st: &SymbolTable, +) -> FxHashMap> { + let preds = predecessors(pg); + + let dut = pg.type_param.expect("protocol has no DUT"); + let input_ports: Vec = st + .get_children(&dut) + .into_iter() + .filter(|sym_id| st[*sym_id].is_in_port()) + .collect(); + + // by default, the entry node begins with + let mut in_defs: FxHashMap> = FxHashMap::default(); + in_defs.insert( + pg.entry, + input_ports + .iter() + .map(|sym_id| (*sym_id, WaveValue::DontCare)) + .collect(), + ); + + let out_defs : FxHashMap> = FxHashMap::default(); + + let mut worklist: Vec<(NodeId, &Node)> = pg.nodes().collect::>(); + + while let Some((id, _)) = worklist.pop() { + // run the merge function with its predecessors to get in_defs[n] + // merge function is just the union + let merged : Vec<(SymbolId, WaveValue)> = preds[&id].iter().map(|nid| out_defs[nid].clone()).flatten().collect(); + in_defs.insert(id, merged); + + // run the transfer function on it to get out_defs[n] + // if an assignment is definitely satisfiable, then remove all the other assignments + + // if out b changed, add the successors to the worklist + } + + + FxHashMap::default() +} From 43704ceca51fe0a1ddc18d49706c5f87bbcc37f0 Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Thu, 2 Jul 2026 11:25:49 -0400 Subject: [PATCH 03/12] change the assignment representation --- protocols/src/ir/edge_contract.rs | 360 ++++++++-------- protocols/src/ir/graph_interpreter.rs | 46 +- protocols/src/ir/graphviz.rs | 28 +- protocols/src/ir/lowering.rs | 16 +- protocols/src/ir/proto_graph.rs | 46 +- protocols/src/ir/trace_lowering.rs | 21 +- ...aphviz__tests__ir_graphviz_add_d0.snap.new | 186 +++++++++ ...aphviz__tests__ir_graphviz_add_d1.snap.new | 394 ++++++++++++++++++ ..._truncated_include_idle_send_data.snap.new | 95 +++++ ...__ir_graphviz_bounded_ready_valid.snap.new | 75 ++++ ...phviz__tests__ir_graphviz_counter.snap.new | 130 ++++++ 11 files changed, 1201 insertions(+), 196 deletions(-) create mode 100644 protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new create mode 100644 protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new create mode 100644 protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new create mode 100644 protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new create mode 100644 protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new diff --git a/protocols/src/ir/edge_contract.rs b/protocols/src/ir/edge_contract.rs index acebe0c9..c624294f 100644 --- a/protocols/src/ir/edge_contract.rs +++ b/protocols/src/ir/edge_contract.rs @@ -1,8 +1,8 @@ use std::collections::{BTreeSet, VecDeque}; use crate::frontend::symbol::{SymbolId, SymbolTable, Type as FrontType}; -use crate::ir::proto_graph::{Action, NodeId, Op, ProtoGraph, Transition}; -use patronus::expr::{Expr, ExprRef}; +use crate::ir::proto_graph::{Action, Assignment, NodeId, Op, ProtoGraph, Transition}; +use patronus::expr::ExprRef; fn symbol_expr(protocol: &mut ProtoGraph, symbols: &SymbolTable, symbol_id: SymbolId) -> ExprRef { if let Some(expr) = protocol.symbol_expr(symbol_id) { @@ -25,16 +25,6 @@ fn symbol_expr(protocol: &mut ProtoGraph, symbols: &SymbolTable, symbol_id: Symb expr } -/// Constrain `rhs` to `guard`, falling back to `fallback` outside it. An -/// unconditional (`true`) guard needs no `ite`. -fn lift(protocol: &mut ProtoGraph, guard: ExprRef, rhs: ExprRef, fallback: ExprRef) -> ExprRef { - if guard == protocol.true_id() { - rhs - } else { - protocol.expr_ctx.ite(guard, rhs, fallback) - } -} - fn same_assignment_target(symbols: &SymbolTable, lhs: SymbolId, rhs: SymbolId) -> bool { lhs == rhs || symbols[lhs].full_name(symbols) == symbols[rhs].full_name(symbols) } @@ -50,140 +40,118 @@ fn record_internal_assert( }); } -#[derive(Clone, Copy)] -struct AssignmentBranch { - guard: ExprRef, - rhs: ExprRef, - is_dont_care: bool, -} - -fn simplify_expr(protocol: &mut ProtoGraph, expr: ExprRef) -> ExprRef { - let (expr_ctx, simplifier) = (&mut protocol.expr_ctx, &mut protocol.simplifier); - simplifier.simplify(expr_ctx, expr) -} - -fn push_assignment_branch( +pub(crate) fn guard_assignment( protocol: &mut ProtoGraph, - branches: &mut Vec, + assignment: Assignment, guard: ExprRef, - rhs: ExprRef, -) { - let guard = simplify_expr(protocol, guard); - if guard == protocol.false_id() { - return; +) -> Assignment { + let dont_care = protocol.and_guard(guard, assignment.dont_care); + let concretes = assignment + .concretes + .into_iter() + .filter_map(|(branch_guard, rhs)| { + let guarded = protocol.and_guard(guard, branch_guard); + (guarded != protocol.false_id()).then_some((guarded, rhs)) + }) + .collect(); + + Assignment { + dont_care, + concretes, } +} - branches.push(AssignmentBranch { - guard, - rhs, - is_dont_care: protocol.dont_cares.contains(&rhs), - }); +fn concrete_coverage(protocol: &mut ProtoGraph, assignment: &Assignment) -> ExprRef { + let raw_coverage = assignment + .concretes + .iter() + .fold(protocol.false_id(), |guard, (concrete_guard, _)| { + protocol.or_guard(guard, *concrete_guard) + }); + let not_dont_care = protocol.not_guard(assignment.dont_care); + protocol.and_guard(not_dont_care, raw_coverage) } -// turns an ITE into a list of predicated assignments -fn flatten_assignment_rhs( +fn effective_concretes( protocol: &mut ProtoGraph, - rhs: ExprRef, - default_value: ExprRef, - action_guard: ExprRef, -) -> Vec { - // Assignment RHSs are either bare values or ITE chains whose true branches are bare values. - let mut branches = Vec::new(); - let mut path_guard = simplify_expr(protocol, action_guard); - let mut cursor = rhs; - - while path_guard != protocol.false_id() && cursor != default_value { - match protocol.expr_ctx[cursor].clone() { - Expr::BVIte { cond, tru, fals } => { - let branch_guard = protocol.and_guard(path_guard, cond); - push_assignment_branch(protocol, &mut branches, branch_guard, tru); - - let not_cond = protocol.not_guard(cond); - path_guard = protocol.and_guard(path_guard, not_cond); - cursor = fals; - } - _ => { - push_assignment_branch(protocol, &mut branches, path_guard, cursor); - break; - } + assignment: &Assignment, +) -> Vec<(ExprRef, ExprRef)> { + let mut prior = assignment.dont_care; + let mut effective = Vec::with_capacity(assignment.concretes.len()); + + for (guard, rhs) in &assignment.concretes { + let not_prior = protocol.not_guard(prior); + let effective_guard = protocol.and_guard(not_prior, *guard); + if effective_guard != protocol.false_id() { + effective.push((effective_guard, *rhs)); } + prior = protocol.or_guard(prior, *guard); } - branches + effective } -/// Turn branches back into an ITE -fn rebuild_assignment_rhs( +fn merge_ordered_assignment( protocol: &mut ProtoGraph, - branches: &[AssignmentBranch], - default_value: ExprRef, -) -> ExprRef { - branches - .iter() - .rev() - .fold(default_value, |fallback, branch| { - lift(protocol, branch.guard, branch.rhs, fallback) - }) -} - -/// Append the new `branches` to `output` -/// Iff DontCare is true, add `DontCare` branches -fn append_assignment_branches( - output: &mut Vec, - branches: &[AssignmentBranch], - dont_care: bool, -) { - output.extend( - branches - .iter() - .copied() - .filter(|branch| branch.is_dont_care == dont_care), - ); + existing: Assignment, + new: Assignment, +) -> Assignment { + // Ordered contraction preserves source order: the later action wins over + // the earlier action. Therefore the new summary's preferences come first. + let new_concretes = concrete_coverage(protocol, &new); + let not_new_concretes = protocol.not_guard(new_concretes); + let old_dont_care = protocol.and_guard(not_new_concretes, existing.dont_care); + let dont_care = protocol.or_guard(new.dont_care, old_dont_care); + + let mut concretes = new.concretes; + concretes.extend(existing.concretes); + + Assignment { + dont_care, + concretes, + } } -fn merge_unordered_assignment_rhs( +fn merge_unordered_assignment( protocol: &mut ProtoGraph, internal_assert_guard: &mut Option, - default_value: ExprRef, - existing_guard: ExprRef, - existing_rhs: ExprRef, - new_guard: ExprRef, - new_rhs: ExprRef, -) -> ExprRef { - // flatten the existing assignment in the node we're merging into - let existing_branches = - flatten_assignment_rhs(protocol, existing_rhs, default_value, existing_guard); - // flatten the new assignment we're merging in - let new_branches = flatten_assignment_rhs(protocol, new_rhs, default_value, new_guard); - - for existing_branch in existing_branches - .iter() - .filter(|branch| !branch.is_dont_care) - { - // find all the different concrete values and add internal assertions for their conjunction - for new_branch in new_branches.iter().filter(|branch| !branch.is_dont_care) { + existing: Assignment, + new: Assignment, +) -> Assignment { + let existing_effective = effective_concretes(protocol, &existing); + let new_effective = effective_concretes(protocol, &new); + + for (existing_guard, existing_rhs) in &existing_effective { + for (new_guard, new_rhs) in &new_effective { // assignments to the same value are totally legal - if existing_branch.rhs == new_branch.rhs { + if existing_rhs == new_rhs { continue; } // assignments to different concrete values are illegal - let overlap = protocol.and_guard(existing_branch.guard, new_branch.guard); + let overlap = protocol.and_guard(*existing_guard, *new_guard); if overlap != protocol.false_id() { record_internal_assert(protocol, internal_assert_guard, overlap); } } } - // add the existing concrete branches, new concrete branches, existing DontCare branches, new dontcare branches. Guarantees our Concrete assignments will be preferred to DontCares across nodes. - let mut merged_branches = Vec::with_capacity(existing_branches.len() + new_branches.len()); - append_assignment_branches(&mut merged_branches, &existing_branches, false); - append_assignment_branches(&mut merged_branches, &new_branches, false); - append_assignment_branches(&mut merged_branches, &existing_branches, true); - append_assignment_branches(&mut merged_branches, &new_branches, true); + let existing_concretes = concrete_coverage(protocol, &existing); + let new_concretes = concrete_coverage(protocol, &new); + + let not_new_concretes = protocol.not_guard(new_concretes); + let existing_dont_care = protocol.and_guard(not_new_concretes, existing.dont_care); + let not_existing_concretes = protocol.not_guard(existing_concretes); + let new_dont_care = protocol.and_guard(not_existing_concretes, new.dont_care); + let dont_care = protocol.or_guard(existing_dont_care, new_dont_care); + + let mut concretes = existing.concretes; + concretes.extend(new.concretes); - // turn it back into an ITE from branches - rebuild_assignment_rhs(protocol, &merged_branches, default_value) + Assignment { + dont_care, + concretes, + } } pub fn append_action( @@ -195,8 +163,12 @@ pub fn append_action( ordered: bool, ) { match protocol[action.op].clone() { - Op::Assign(symbol_id, rhs) => { - let default_value = symbol_expr(protocol, symbols, symbol_id); + Op::Assign(symbol_id, assignment) => { + assert_eq!( + action.guard, + protocol.true_id(), + "assignment action guards must be 1; path conditions belong in Assignment" + ); // By invariant, there can be at most one existing assignment for this symbol. let Some(idx) = actions.iter().position(|prior_action| { @@ -210,29 +182,28 @@ pub fn append_action( return; }; - let existing_guard = actions[idx].guard; - let existing_rhs = match protocol[actions[idx].op].clone() { - Op::Assign(_, existing_rhs) => existing_rhs, + assert_eq!( + actions[idx].guard, + protocol.true_id(), + "assignment action guards must be 1; path conditions belong in Assignment" + ); + let existing_assignment = match protocol[actions[idx].op].clone() { + Op::Assign(_, existing_assignment) => existing_assignment, _ => unreachable!(), }; - let (new_guard, new_rhs) = (action.guard, rhs); - let merged_rhs = if ordered { - let existing_else = lift(protocol, existing_guard, existing_rhs, default_value); - lift(protocol, new_guard, new_rhs, existing_else) + let merged_assignment = if ordered { + merge_ordered_assignment(protocol, existing_assignment, assignment) } else { - merge_unordered_assignment_rhs( + merge_unordered_assignment( protocol, internal_assert_guard, - default_value, - existing_guard, - existing_rhs, - new_guard, - new_rhs, + existing_assignment, + assignment, ) }; - let new_op = protocol.o(Op::Assign(symbol_id, merged_rhs)); + let new_op = protocol.o(Op::Assign(symbol_id, merged_assignment)); actions[idx].guard = protocol.true_id(); actions[idx].op = new_op; } @@ -326,8 +297,23 @@ pub fn contract_edges(protocol: &mut ProtoGraph, symbols: &SymbolTable) { next_path.insert(target_node_id); for action in target_actions { - let merged_action = - Action::with_guard(&action, protocol.and_guard(incoming_guard, action.guard)); + let merged_action = match protocol[action.op].clone() { + Op::Assign(symbol_id, assignment) => { + assert_eq!( + action.guard, + protocol.true_id(), + "assignment action guards must be 1; path conditions belong in Assignment" + ); + let guarded_assignment = + guard_assignment(protocol, assignment, incoming_guard); + let guarded_op = protocol.o(Op::Assign(symbol_id, guarded_assignment)); + Action::new(protocol.true_id(), guarded_op) + } + _ => Action::with_guard( + &action, + protocol.and_guard(incoming_guard, action.guard), + ), + }; append_action( protocol, symbols, @@ -401,7 +387,9 @@ pub fn normalize_assignments(protocol: &mut ProtoGraph, symbols: &SymbolTable) { for symbol_id in unassigned_inputs { // assign the symbol to its current expression (old value) let symbol_expr = symbol_expr(protocol, symbols, symbol_id); - let op = protocol.o(Op::Assign(symbol_id, symbol_expr)); + let assignment = + Assignment::concrete(protocol.false_id(), protocol.true_id(), symbol_expr); + let op = protocol.o(Op::Assign(symbol_id, assignment)); protocol.push_action(node_id, Action::new(protocol.true_id(), op)) } } @@ -415,6 +403,18 @@ mod tests { use cranelift_entity::EntityRef; use patronus::expr::SerializableIrNode; + fn concrete_assignment(protocol: &ProtoGraph, rhs: ExprRef) -> Assignment { + Assignment::concrete(protocol.false_id(), protocol.true_id(), rhs) + } + + fn dont_care_assignment(protocol: &ProtoGraph) -> Assignment { + Assignment::dont_care(protocol.true_id()) + } + + fn expr_str(protocol: &ProtoGraph, expr: ExprRef) -> String { + protocol.expr_ctx[expr].serialize_to_str(&protocol.expr_ctx) + } + #[test] fn allows_overlapping_dont_care_and_concrete_assignments() { let mut protocol = ProtoGraph::new(ProtocolContext::new("test".into(), ROOT_SCOPE)); @@ -425,8 +425,11 @@ mod tests { protocol.cache_symbol_expr(symbol_id, symbol); protocol.dont_cares.insert(dont_care); - let concrete_op = protocol.o(Op::Assign(symbol_id, concrete)); - let dont_care_op = protocol.o(Op::Assign(symbol_id, dont_care)); + let concrete_op = protocol.o(Op::Assign( + symbol_id, + concrete_assignment(&protocol, concrete), + )); + let dont_care_op = protocol.o(Op::Assign(symbol_id, dont_care_assignment(&protocol))); let mut actions = vec![Action::new(protocol.true_id(), concrete_op)]; let mut internal_assert_guard = None; let true_id = protocol.true_id(); @@ -445,13 +448,11 @@ mod tests { // the order in which the assigns are given is preserved (Concrete should not subsume DontCare) assert_eq!(actions.len(), 1); - let Op::Assign(_, rhs) = protocol[actions[0].op] else { + let Op::Assign(_, assignment) = protocol[actions[0].op].clone() else { panic!("expected assignment"); }; - assert_eq!( - protocol.expr_ctx[rhs].serialize_to_str(&protocol.expr_ctx), - "dont_care" - ); + assert_eq!(expr_str(&protocol, assignment.dont_care), "1'b1"); + assert_eq!(assignment.concretes, vec![(protocol.true_id(), concrete)]); } #[test] @@ -474,7 +475,7 @@ mod tests { let mut actions = Vec::new(); let mut internal_assert_guard = None; for (guard, rhs) in [(p, one), (q, two), (r, three)] { - let op = protocol.o(Op::Assign(symbol_id, rhs)); + let op = protocol.o(Op::Assign(symbol_id, concrete_assignment(&protocol, rhs))); append_action( &mut protocol, &SymbolTable::default(), @@ -486,15 +487,11 @@ mod tests { } assert_eq!(actions.len(), 1); - // we should now have an ITE form with [1] always as the guard - assert_eq!( - protocol.expr_ctx[actions[0].guard].serialize_to_str(&protocol.expr_ctx), - "1'b1" - ); + assert_ne!(actions[0].guard, protocol.false_id()); // assert any two guards being run together. (p and q) or (p and r) assert_eq!( - protocol.expr_ctx[internal_assert_guard.unwrap()].serialize_to_str(&protocol.expr_ctx), + expr_str(&protocol, internal_assert_guard.unwrap()), "or(or(and(p, q), and(p, r)), and(and(not(p), q), r))" ); } @@ -514,15 +511,24 @@ mod tests { let r = protocol.expr_ctx.bv_symbol("r", 1); let s = protocol.expr_ctx.bv_symbol("s", 1); - // Node A: [1] DUT.a := ite(s, X, ite(q, 3, DUT.a)) - // Node B: [1] DUT.a := ite(r, 4, DUT.a) + // Node A: [1] DUT.a := { X if s; 3 if q } + // Node B: [1] DUT.a := { 4 if r } - let q_three = protocol.expr_ctx.ite(q, three, symbol); - let node_a_rhs = protocol.expr_ctx.ite(s, dont_care, q_three); - let node_b_rhs = protocol.expr_ctx.ite(r, four, symbol); let true_id = protocol.true_id(); - let node_a_op = protocol.o(Op::Assign(symbol_id, node_a_rhs)); - let node_b_op = protocol.o(Op::Assign(symbol_id, node_b_rhs)); + let node_a_op = protocol.o(Op::Assign( + symbol_id, + Assignment { + dont_care: s, + concretes: vec![(q, three)], + }, + )); + let node_b_op = protocol.o(Op::Assign( + symbol_id, + Assignment { + dont_care: protocol.false_id(), + concretes: vec![(r, four)], + }, + )); let mut actions = vec![Action::new(true_id, node_a_op)]; let mut internal_assert_guard = None; @@ -537,32 +543,25 @@ mod tests { ); assert_eq!(actions.len(), 1); - assert_eq!( - protocol.expr_ctx[actions[0].guard].serialize_to_str(&protocol.expr_ctx), - "1'b1" - ); + assert_ne!(actions[0].guard, protocol.false_id()); // q and r are our concrete assignments to 3 and 4. assignment to 3 only triggers if // not s , so there is only a conflicting assignment under ((not s) and q and r) assert_eq!( - protocol.expr_ctx[internal_assert_guard.unwrap()].serialize_to_str(&protocol.expr_ctx), + expr_str(&protocol, internal_assert_guard.unwrap()), "and(and(not(s), q), r)" ); protocol.simplify_all_exprs(); - let Op::Assign(_, rhs) = protocol[actions[0].op] else { + let Op::Assign(_, assignment) = protocol[actions[0].op].clone() else { panic!("expected assignment"); }; - // the ordering of the concrete assignments is arbitrary, but should come before the - // DontCare. however, the assignment to 3 should only happen if the DontCare - // assignment is not triggered, since those are ordered within a node - // Thus: if q and not s => 3, if r => 4, if s => X, otherwise DUT.a - assert_eq!( - protocol.expr_ctx[rhs].serialize_to_str(&protocol.expr_ctx), - "ite(and(not(s), q), three, ite(r, four, ite(s, dont_care, DUT.a)))" - ); + // Concrete branches from both unordered sides are preferred over DontCare, but the + // original node-local DontCare still suppresses node A's own concrete branch. + assert_eq!(expr_str(&protocol, assignment.dont_care), "and(not(r), s)"); + assert_eq!(assignment.concretes, vec![(q, three), (r, four)]); } #[test] @@ -579,12 +578,21 @@ mod tests { let q = protocol.expr_ctx.bv_symbol("q", 1); let r = protocol.expr_ctx.bv_symbol("r", 1); - let q_three = protocol.expr_ctx.ite(q, three, symbol); - let node_a_rhs = protocol.expr_ctx.ite(p, dont_care, q_three); - let node_b_rhs = protocol.expr_ctx.ite(r, three, symbol); let true_id = protocol.true_id(); - let node_a_op = protocol.o(Op::Assign(symbol_id, node_a_rhs)); - let node_b_op = protocol.o(Op::Assign(symbol_id, node_b_rhs)); + let node_a_op = protocol.o(Op::Assign( + symbol_id, + Assignment { + dont_care: p, + concretes: vec![(q, three)], + }, + )); + let node_b_op = protocol.o(Op::Assign( + symbol_id, + Assignment { + dont_care: protocol.false_id(), + concretes: vec![(r, three)], + }, + )); let mut actions = vec![Action::new(true_id, node_a_op)]; let mut internal_assert_guard = None; diff --git a/protocols/src/ir/graph_interpreter.rs b/protocols/src/ir/graph_interpreter.rs index 53528fce..970a031b 100644 --- a/protocols/src/ir/graph_interpreter.rs +++ b/protocols/src/ir/graph_interpreter.rs @@ -10,7 +10,7 @@ use crate::dut::{PatronusSim, PortId}; use crate::frontend::serialize::serialize_bitvec; use crate::frontend::symbol::{SymbolId, SymbolTable, Type}; use crate::interpreter::Value as WaveValue; -use crate::ir::proto_graph::{Op, ProtoGraph}; +use crate::ir::proto_graph::{Assignment, Op, ProtoGraph}; enum GraphBinding { Sim(PortId), @@ -164,6 +164,26 @@ fn evaluate_input_value( } } +fn evaluate_assignment( + protocol: &ProtoGraph, + store: &SymbolValueStore, + assignment: &Assignment, +) -> InputValue { + if evaluate_guard(protocol, store, assignment.dont_care) { + return InputValue::DontCare; + } + + for (guard, rhs) in &assignment.concretes { + if evaluate_guard(protocol, store, *guard) { + return evaluate_input_value(protocol, store, *rhs); + } + } + + // TODO: make the an internal assert false + // TODO: these should all pass after our analysis pass + panic!("assignment did not match DontCare or any concrete branch") +} + fn evaluate_assert_equal( protocol: &ProtoGraph, store: &SymbolValueStore, @@ -259,19 +279,27 @@ pub fn interpret( // TODO: assignments should be evaluated in a topo order for action in &node.actions { - if let Op::Assign(symbol_id, _) = &pg[action.op] - && !assigned_inputs.insert(*symbol_id) - { - // panic!("multiple assigns to input {symbol_id} in one node"); + if let Op::Assign(symbol_id, _) = &pg[action.op] { + assert_eq!( + action.guard, + pg.true_id(), + "assignment action guards must be 1; path conditions belong in Assignment" + ); + if !assigned_inputs.insert(*symbol_id) { + // panic!("multiple assigns to input {symbol_id} in one node"); + } } } { for action in &node.actions { - if let Op::Assign(symbol_id, expr_ref) = &pg[action.op] - && evaluate_guard(pg, &store, action.guard) - { - let value = evaluate_input_value(pg, &store, *expr_ref); + if let Op::Assign(symbol_id, assignment) = &pg[action.op] { + assert_eq!( + action.guard, + pg.true_id(), + "assignment action guards must be 1; path conditions belong in Assignment" + ); + let value = evaluate_assignment(pg, &store, assignment); pending_inputs.insert(*symbol_id, value); } diff --git a/protocols/src/ir/graphviz.rs b/protocols/src/ir/graphviz.rs index b8b5f2fa..912933ee 100644 --- a/protocols/src/ir/graphviz.rs +++ b/protocols/src/ir/graphviz.rs @@ -4,7 +4,7 @@ use crate::frontend::serialize::serialize_bitvec; use crate::frontend::symbol::SymbolTable; -use crate::ir::proto_graph::{NodeId, Op, ProtoGraph}; +use crate::ir::proto_graph::{Assignment, NodeId, Op, ProtoGraph}; use baa::BitVecValue; use patronus::expr::{Expr, ExprRef}; use std::collections::{BTreeSet, VecDeque}; @@ -79,10 +79,10 @@ fn format_op( op_id: crate::ir::proto_graph::OpId, ) -> String { match &protocol[op_id] { - Op::Assign(symbol_id, expr_id) => format!( + Op::Assign(symbol_id, assignment) => format!( "{} := {}", symbols.full_name_from_symbol_id(symbol_id), - format_expr(protocol, *expr_id) + format_assignment(protocol, assignment) ), Op::AssertEq(lhs, rhs) => format!( "assert_eq({}, {})", @@ -95,6 +95,28 @@ fn format_op( } } +fn format_assignment(protocol: &ProtoGraph, assignment: &Assignment) -> String { + let mut parts = Vec::new(); + if assignment.dont_care != protocol.false_id() { + parts.push(format!( + "X if {}", + format_expr(protocol, assignment.dont_care) + )); + } + for (guard, rhs) in &assignment.concretes { + parts.push(format!( + "{} if {}", + format_expr(protocol, *rhs), + format_expr(protocol, *guard) + )); + } + if parts.is_empty() { + "internal_assert_false".to_string() + } else { + parts.join("; ") + } +} + fn format_expr(protocol: &ProtoGraph, expr_ref: ExprRef) -> String { if protocol.dont_cares.contains(&expr_ref) { "X".to_string() diff --git a/protocols/src/ir/lowering.rs b/protocols/src/ir/lowering.rs index ff0697cd..c1656640 100644 --- a/protocols/src/ir/lowering.rs +++ b/protocols/src/ir/lowering.rs @@ -176,7 +176,13 @@ impl<'a> Lowerer<'a> { ), }; let rhs_ref = self.lower_expr(ast, expr_id, Some(expected)); - let op_id = self.ir.o(Op::Assign(symbol_id, rhs_ref)); + let assignment = Assignment::from_rhs( + self.ir.false_id(), + self.ir.true_id(), + rhs_ref, + self.ir.dont_cares.contains(&rhs_ref), + ); + let op_id = self.ir.o(Op::Assign(symbol_id, assignment)); let true_id = self.ir.true_id(); self.ir.push_action(node_id, Action::new(true_id, op_id)); self.ir @@ -309,7 +315,13 @@ impl<'a> Lowerer<'a> { other => panic!("input port type must be a BitVec: {other:?}"), }; let rhs = self.dont_care_expr(PatronusType::BV(width)); - let assign = self.ir.o(Op::Assign(input, rhs)); + let assignment = Assignment::from_rhs( + self.ir.false_id(), + self.ir.true_id(), + rhs, + self.ir.dont_cares.contains(&rhs), + ); + let assign = self.ir.o(Op::Assign(input, assignment)); self.ir .push_action(done, Action::new(self.ir.true_id(), assign)); } diff --git a/protocols/src/ir/proto_graph.rs b/protocols/src/ir/proto_graph.rs index 65270dc4..3e23160f 100644 --- a/protocols/src/ir/proto_graph.rs +++ b/protocols/src/ir/proto_graph.rs @@ -80,9 +80,45 @@ impl Node { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Assignment { + pub dont_care: ExprRef, + pub concretes: Vec<(ExprRef, ExprRef)>, +} + +impl Assignment { + pub fn concrete(false_id: ExprRef, guard: ExprRef, rhs: ExprRef) -> Self { + Self { + dont_care: false_id, + concretes: vec![(guard, rhs)], + } + } + + pub fn dont_care(guard: ExprRef) -> Self { + Self { + dont_care: guard, + concretes: vec![], + } + } + + pub fn from_rhs(false_id: ExprRef, guard: ExprRef, rhs: ExprRef, is_dont_care: bool) -> Self { + if is_dont_care { + Self { + dont_care: guard, + concretes: vec![], + } + } else { + Self { + dont_care: false_id, + concretes: vec![(guard, rhs)], + } + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Op { - Assign(SymbolId, ExprRef), + Assign(SymbolId, Assignment), AssertEq(ExprRef, ExprRef), Fork, InternalAssertFalse, @@ -203,8 +239,12 @@ impl ProtoGraph { for (_, op) in self.ops.iter_mut() { match op { - Op::Assign(_, rhs) => { - *rhs = simplifier.simplify(expr_ctx, *rhs); + Op::Assign(_, assignment) => { + assignment.dont_care = simplifier.simplify(expr_ctx, assignment.dont_care); + for (guard, rhs) in &mut assignment.concretes { + *guard = simplifier.simplify(expr_ctx, *guard); + *rhs = simplifier.simplify(expr_ctx, *rhs); + } } Op::AssertEq(lhs, rhs) => { *lhs = simplifier.simplify(expr_ctx, *lhs); diff --git a/protocols/src/ir/trace_lowering.rs b/protocols/src/ir/trace_lowering.rs index c5d8516c..dd5c0b88 100644 --- a/protocols/src/ir/trace_lowering.rs +++ b/protocols/src/ir/trace_lowering.rs @@ -9,7 +9,7 @@ use rustc_hash::FxHashMap; use crate::Value; use crate::frontend::ast::Protocol; use crate::frontend::symbol::SymbolTable; -use crate::ir::edge_contract::{append_action, contract_edges}; +use crate::ir::edge_contract::{append_action, contract_edges, guard_assignment}; use crate::ir::lowering::{LoweredFragmentInfo, Lowerer, TraceArgSubst}; use crate::ir::proto_graph::{Action, NodeId, Op, ProtoGraph, Transition}; @@ -79,8 +79,23 @@ impl<'a> Lowerer<'a> { let mut internal_assert_guard = None; for action in entry_actions { - let guard = self.ir.and_guard(graft_guard, action.guard); - let guarded_action = action.with_guard(guard); + let guarded_action = match self.ir[action.op].clone() { + Op::Assign(symbol_id, assignment) => { + assert_eq!( + action.guard, + self.ir.true_id(), + "assignment action guards must be 1; path conditions belong in Assignment" + ); + let guarded_assignment = + guard_assignment(&mut self.ir, assignment, graft_guard); + let guarded_op = self.ir.o(Op::Assign(symbol_id, guarded_assignment)); + Action::new(self.ir.true_id(), guarded_op) + } + _ => { + let guard = self.ir.and_guard(graft_guard, action.guard); + action.with_guard(guard) + } + }; append_action( &mut self.ir, self.symbols, diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new new file mode 100644 index 00000000..8fd81424 --- /dev/null +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new @@ -0,0 +1,186 @@ +--- +source: protocols/src/ir/graphviz.rs +assertion_line: 234 +expression: content +--- +== pre-contract == +digraph "add_combinational_illegal_observation_in_conditional" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] DUT.a := X if 1"]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := X if 1"]; + node8 -> node7 [label="1"]; + node7 [label="[1] DUT.a := a if 1"]; + node7 -> node6 [label="1"]; + node6 [label=""]; + node6 -> node4 [label="eq(DUT.s, 0)"]; + node6 -> node5 [label="not(eq(DUT.s, 0))"]; + node4 [label=""]; + node4 -> node3 [label="1"]; + node5 [label=""]; + node5 -> node3 [label="1"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "add_combinational_illegal_observation_in_conditional" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1"]; + node0 -> node1 [label="eq(DUT.s, 0) / step"]; + node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "add_combinational_illegal_observation_in_conditional" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1"]; + node0 -> node1 [label="eq(DUT.s, 0) / step"]; + node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== pre-contract == +digraph "add_combinational_illegal_observation_in_assertion" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node6 [label="1"]; + node6 [label="[1] DUT.a := X if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] DUT.a := a if 1"]; + node4 -> node3 [label="1"]; + node3 [label="[1] assert_eq(DUT.s, s)"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "add_combinational_illegal_observation_in_assertion" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1\n[1] assert_eq(DUT.s, s)"]; + node0 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "add_combinational_illegal_observation_in_assertion" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1\n[1] assert_eq(DUT.s, s)"]; + node0 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== pre-contract == +digraph "add_combinational_legal_observation_illegal_assignment" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node6 [label="1"]; + node6 [label=""]; + node6 -> node4 [label="eq(DUT.s, 0)"]; + node6 -> node5 [label="not(eq(DUT.s, 0))"]; + node4 [label="[1] DUT.a := a if 1"]; + node4 -> node3 [label="1"]; + node5 [label=""]; + node5 -> node3 [label="1"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "add_combinational_legal_observation_illegal_assignment" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if eq(DUT.s, 0)"]; + node0 -> node1 [label="eq(DUT.s, 0) / step"]; + node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "add_combinational_legal_observation_illegal_assignment" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if eq(DUT.s, 0)\n[1] DUT.b := DUT.b if 1"]; + node0 -> node1 [label="eq(DUT.s, 0) / step"]; + node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== pre-contract == +digraph "add_combinational" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node5 [label="1"]; + node5 [label="[1] DUT.a := a if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] DUT.b := b if 1"]; + node4 -> node3 [label="1"]; + node3 [label="[1] assert_eq(s, DUT.s)"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "add_combinational" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)"]; + node0 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "add_combinational" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)"]; + node0 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new new file mode 100644 index 00000000..abed4e11 --- /dev/null +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new @@ -0,0 +1,394 @@ +--- +source: protocols/src/ir/graphviz.rs +assertion_line: 234 +expression: content +--- +== pre-contract == +digraph "add" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] DUT.a := a if 1"]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := b if 1"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := X if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] assert_eq(s, DUT.s)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "add" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "add" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== pre-contract == +digraph "add_fork_early" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] DUT.a := a if 1"]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := b if 1"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] fork"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.a := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] DUT.b := X if 1"]; + node4 -> node3 [label="1"]; + node3 [label="[1] assert_eq(s, DUT.s)"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "add_fork_early" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node6 [label="1 / step"]; + node6 [label="[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)"]; + node6 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "add_fork_early" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node6 [label="1 / step"]; + node6 [label="[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)"]; + node6 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== pre-contract == +digraph "add_incorrect" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] DUT.a := a if 1"]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := b if 1"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := a if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := b if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] assert_eq(s, DUT.s)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "add_incorrect" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "add_incorrect" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== pre-contract == +digraph "add_incorrect_implicit" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node7 [label="1"]; + node7 [label="[1] DUT.a := a if 1"]; + node7 -> node6 [label="1"]; + node6 [label="[1] DUT.b := b if 1"]; + node6 -> node5 [label="1"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node4 [label="[1] assert_eq(s, DUT.s)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "add_incorrect_implicit" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node4 [label="1 / step"]; + node4 [label="[1] assert_eq(s, DUT.s)\n[1] fork"]; + node4 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "add_incorrect_implicit" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node4 [label="1 / step"]; + node4 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := DUT.a if 1\n[1] DUT.b := DUT.b if 1"]; + node4 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== pre-contract == +digraph "wait_and_add" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label=""]; + node9 -> node8 [label="1 / step"]; + node8 [label="[1] fork"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := a if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := b if 1"]; + node5 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node3 [label="1 / step"]; + node3 [label="[1] assert_eq(s, DUT.s)"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "wait_and_add" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node8 [label="1 / step"]; + node8 [label="[1] fork"]; + node8 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node6 -> node3 [label="1 / step"]; + node3 [label="[1] assert_eq(s, DUT.s)"]; + node3 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "wait_and_add" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := DUT.a if 1\n[1] DUT.b := DUT.b if 1"]; + node0 -> node8 [label="1 / step"]; + node8 [label="[1] fork\n[1] DUT.a := DUT.a if 1\n[1] DUT.b := DUT.b if 1"]; + node8 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node6 -> node3 [label="1 / step"]; + node3 [label="[1] assert_eq(s, DUT.s)\n[1] DUT.a := DUT.a if 1\n[1] DUT.b := DUT.b if 1"]; + node3 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== pre-contract == +digraph "assign_after_observation" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node13 [label="1"]; + node13 [label="[1] DUT.a := a if 1"]; + node13 -> node12 [label="1"]; + node12 [label=""]; + node12 -> node10 [label="eq(DUT.a, 2)"]; + node12 -> node11 [label="not(eq(DUT.a, 2))"]; + node10 [label="[1] DUT.a := 0 if 1"]; + node10 -> node9 [label="1"]; + node11 [label=""]; + node11 -> node9 [label="1"]; + node9 [label=""]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := b if 1"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := X if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] assert_eq(s, DUT.s)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "assign_after_observation" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := 0 if eq(DUT.a, 2); a if 1\n[1] DUT.b := b if eq(DUT.a, 2); b if not(eq(DUT.a, 2))"]; + node0 -> node6 [label="eq(DUT.a, 2) / step"]; + node0 -> node6 [label="not(eq(DUT.a, 2)) / step"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "assign_after_observation" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := 0 if eq(DUT.a, 2); a if 1\n[1] DUT.b := b if eq(DUT.a, 2); b if not(eq(DUT.a, 2))"]; + node0 -> node6 [label="eq(DUT.a, 2) / step"]; + node0 -> node6 [label="not(eq(DUT.a, 2)) / step"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== pre-contract == +digraph "if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node12 [label="1"]; + node12 [label=""]; + node12 -> node10 [label="1"]; + node10 [label="[1] DUT.a := a1 if 1"]; + node10 -> node9 [label="1"]; + node9 [label=""]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := 0 if 1"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := X if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] assert_eq(a1, DUT.s)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a1 if 1\n[1] DUT.b := 0 if 1"]; + node0 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(a1, DUT.s)\n[1] fork"]; + node6 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a1 if 1\n[1] DUT.b := 0 if 1"]; + node0 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(a1, DUT.s)\n[1] fork"]; + node6 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new new file mode 100644 index 00000000..8fc3e891 --- /dev/null +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new @@ -0,0 +1,95 @@ +--- +source: protocols/src/ir/graphviz.rs +assertion_line: 234 +expression: content +--- +== pre-contract == +digraph "send_data" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node7 [label="1"]; + node7 [label="[1] D.m_axis_tdata := data if 1"]; + node7 -> node6 [label="1"]; + node6 [label="[1] D.m_axis_tvalid := 1 if 1"]; + node6 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node5 [label="not(D.m_axis_tready)"]; + node4 -> node3 [label="D.m_axis_tready"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; +} + +== post-contract == +digraph "send_data" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] D.m_axis_tdata := data if 1\n[1] D.m_axis_tvalid := 1 if 1"]; + node0 -> node1 [label="D.m_axis_tready / step"]; + node0 -> node4 [label="not(D.m_axis_tready) / step"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node4 [label=""]; + node4 -> node4 [label="not(D.m_axis_tready) / step"]; + node4 -> node1 [label="D.m_axis_tready / step"]; +} + +== post-normalize == +digraph "send_data" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] D.m_axis_tdata := data if 1\n[1] D.m_axis_tvalid := 1 if 1"]; + node0 -> node1 [label="D.m_axis_tready / step"]; + node0 -> node4 [label="not(D.m_axis_tready) / step"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node4 [label="[1] D.m_axis_tvalid := D.m_axis_tvalid if 1\n[1] D.m_axis_tdata := D.m_axis_tdata if 1"]; + node4 -> node4 [label="not(D.m_axis_tready) / step"]; + node4 -> node1 [label="D.m_axis_tready / step"]; +} + +== pre-contract == +digraph "idle" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node3 [label="1"]; + node3 [label="[1] D.m_axis_tvalid := 0 if 1"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; +} + +== post-contract == +digraph "idle" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] D.m_axis_tvalid := 0 if 1"]; + node0 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; +} + +== post-normalize == +digraph "idle" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] D.m_axis_tvalid := 0 if 1\n[1] D.m_axis_tdata := D.m_axis_tdata if 1"]; + node0 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; +} diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new new file mode 100644 index 00000000..395c76f7 --- /dev/null +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new @@ -0,0 +1,75 @@ +--- +source: protocols/src/ir/graphviz.rs +assertion_line: 234 +expression: content +--- +== pre-contract == +digraph "send_data" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node13 [label="1"]; + node13 [label="[1] D.data := data if 1"]; + node13 -> node12 [label="1"]; + node12 [label="[1] D.valid := 1 if 1"]; + node12 -> node11 [label="1"]; + node11 [label=""]; + node11 -> node9 [label="not(D.ready)"]; + node11 -> node10 [label="D.ready"]; + node9 [label=""]; + node9 -> node8 [label="1 / step"]; + node10 [label=""]; + node10 -> node3 [label="1"]; + node8 [label=""]; + node8 -> node6 [label="not(D.ready)"]; + node8 -> node7 [label="D.ready"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node6 [label=""]; + node6 -> node5 [label="1 / step"]; + node7 [label=""]; + node7 -> node4 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node5 [label="[1] assert_eq(D.ready, 1)"]; + node5 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node3 [label="1"]; + node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; +} + +== post-contract == +digraph "send_data" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] D.data := data if 1\n[1] D.valid := 1 if 1"]; + node0 -> node1 [label="D.ready / step"]; + node0 -> node8 [label="not(D.ready) / step"]; + node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; + node8 [label=""]; + node8 -> node5 [label="not(D.ready) / step"]; + node8 -> node1 [label="D.ready / step"]; + node5 [label="[1] assert_eq(D.ready, 1)"]; + node5 -> node1 [label="1 / step"]; +} + +== post-normalize == +digraph "send_data" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] D.data := data if 1\n[1] D.valid := 1 if 1"]; + node0 -> node1 [label="D.ready / step"]; + node0 -> node8 [label="not(D.ready) / step"]; + node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; + node8 [label="[1] D.valid := D.valid if 1\n[1] D.data := D.data if 1"]; + node8 -> node5 [label="not(D.ready) / step"]; + node8 -> node1 [label="D.ready / step"]; + node5 [label="[1] assert_eq(D.ready, 1)\n[1] D.valid := D.valid if 1\n[1] D.data := D.data if 1"]; + node5 -> node1 [label="1 / step"]; +} diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new new file mode 100644 index 00000000..0dbc43ca --- /dev/null +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new @@ -0,0 +1,130 @@ +--- +source: protocols/src/ir/graphviz.rs +assertion_line: 234 +expression: content +--- +== pre-contract == +digraph "count_up" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] dut.a := a if 1"]; + node9 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node8 [label="not(eq(dut.s, dut.a))"]; + node7 -> node6 [label="eq(dut.s, dut.a)"]; + node8 [label=""]; + node8 -> node7 [label="1 / step"]; + node6 [label=""]; + node6 -> node5 [label="1"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node4 [label="[1] assert_eq(dut.s, 0)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; +} + +== post-contract == +digraph "count_up" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] dut.a := a if 1"]; + node0 -> node4 [label="eq(dut.s, dut.a) / step"]; + node0 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; + node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork"]; + node4 -> node1 [label="1 / step"]; + node7 [label=""]; + node7 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; + node7 -> node4 [label="eq(dut.s, dut.a) / step"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; +} + +== post-normalize == +digraph "count_up" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] dut.a := a if 1"]; + node0 -> node4 [label="eq(dut.s, dut.a) / step"]; + node0 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; + node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := dut.a if 1"]; + node4 -> node1 [label="1 / step"]; + node7 [label="[1] dut.a := dut.a if 1"]; + node7 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; + node7 -> node4 [label="eq(dut.s, dut.a) / step"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; +} + +== pre-contract == +digraph "count_up_extra_assertion" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node10 [label="1"]; + node10 [label="[1] dut.a := a if 1"]; + node10 -> node8 [label="1"]; + node8 [label=""]; + node8 -> node9 [label="not(eq(dut.s, dut.a))"]; + node8 -> node7 [label="eq(dut.s, dut.a)"]; + node9 [label=""]; + node9 -> node8 [label="1 / step"]; + node7 [label=""]; + node7 -> node6 [label="1"]; + node6 [label="[1] assert_eq(dut.s, a)"]; + node6 -> node5 [label="1"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node4 [label="[1] assert_eq(dut.s, 0)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; +} + +== post-contract == +digraph "count_up_extra_assertion" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] dut.a := a if 1\n[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; + node0 -> node4 [label="eq(dut.s, dut.a) / step"]; + node0 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; + node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork"]; + node4 -> node1 [label="1 / step"]; + node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; + node8 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; + node8 -> node4 [label="eq(dut.s, dut.a) / step"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; +} + +== post-normalize == +digraph "count_up_extra_assertion" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] dut.a := a if 1\n[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; + node0 -> node4 [label="eq(dut.s, dut.a) / step"]; + node0 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; + node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := dut.a if 1"]; + node4 -> node1 [label="1 / step"]; + node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)\n[1] dut.a := dut.a if 1"]; + node8 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; + node8 -> node4 [label="eq(dut.s, dut.a) / step"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; +} From d4a60334875ccffd20014ed85b62b03af1ce43ff Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Thu, 2 Jul 2026 15:08:30 -0400 Subject: [PATCH 04/12] finish the transfer function --- graph-interp/src/main.rs | 14 ++ protocols/src/ir/determinize.rs | 10 +- protocols/src/ir/graphviz.rs | 21 +- protocols/src/ir/mod.rs | 1 + protocols/src/ir/propagate_assigns.rs | 34 +++ protocols/src/ir/reaching_defs.rs | 211 ++++++++++++++++-- ...rontend__serialize__tests__add_d1.snap.new | 106 +++++++++ ...aphviz__tests__ir_graphviz_add_d0.snap.new | 10 +- ...aphviz__tests__ir_graphviz_add_d1.snap.new | 76 ++++++- ..._truncated_include_idle_send_data.snap.new | 6 +- ...__ir_graphviz_bounded_ready_valid.snap.new | 4 +- ...phviz__tests__ir_graphviz_counter.snap.new | 6 +- tests/adders/adder_d1/add_d1.prot | 21 ++ 13 files changed, 489 insertions(+), 31 deletions(-) create mode 100644 protocols/src/ir/propagate_assigns.rs create mode 100644 protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap.new diff --git a/graph-interp/src/main.rs b/graph-interp/src/main.rs index 4d75dded..dcf0b279 100644 --- a/graph-interp/src/main.rs +++ b/graph-interp/src/main.rs @@ -12,6 +12,7 @@ use protocols::ir::graph_interpreter; use protocols::ir::graphviz::to_dot_string; use protocols::ir::lowering::lower_ast_to_ir; use protocols::ir::proto_graph::ProtoGraph; +use protocols::ir::reaching_defs::{all_ports_present, exists_conflicts, reaching_definitions}; use protocols::ir::trace_lowering::lower_trace_to_ir; use protocols::{PatronusSim, Value, frontend, transaction_frontend}; use rustc_hash::FxHashMap; @@ -61,6 +62,10 @@ struct Cli { /// Prints trace status lines and ASCII waveforms #[arg(long)] ascii_waveform: bool, + + /// Print reaching definition analysis results + #[arg(long)] + reaching_definitions: bool, } fn load_protocols(cli: &Cli) -> Ast { @@ -177,6 +182,15 @@ fn run_respect_forks( println!("{}", to_dot_string(&joint, st)); } + let rd = reaching_definitions(&mut joint, &st); + if exists_conflicts(&rd) { + println!("bleh") + } + + if !all_ports_present(&rd, &joint, &st) { + println!("bleh bleh") + } + // args are baked into the graph as constants, so we just pass in an empty map here let waveform = graph_interpreter::interpret(&joint, st, FxHashMap::default(), &mut sim); if cli.ascii_waveform { diff --git a/protocols/src/ir/determinize.rs b/protocols/src/ir/determinize.rs index 84741cc9..f6bb35a3 100644 --- a/protocols/src/ir/determinize.rs +++ b/protocols/src/ir/determinize.rs @@ -33,19 +33,23 @@ fn get_or_create_state( } /// Result of a (conservative) satisfiability check on a guard. -enum SatResult { +pub enum SatResult { DefinitelyUnsat, MaybeSat, + DefinitelySat, } // TODO: Strengthen this with a real SAT/SMT query to prune more aggressively. -fn check_sat(protocol: &mut ProtoGraph, guard: ExprRef) -> SatResult { +pub fn check_sat(protocol: &mut ProtoGraph, guard: ExprRef) -> SatResult { let simplified = { let (expr_ctx, simplifier) = (&mut protocol.expr_ctx, &mut protocol.simplifier); simplifier.simplify(expr_ctx, guard) }; + if simplified == protocol.false_id() { SatResult::DefinitelyUnsat + } else if simplified == protocol.true_id() { + SatResult::DefinitelySat } else { SatResult::MaybeSat } @@ -110,7 +114,7 @@ pub fn determinized(protocol: ProtoGraph, symbols: &SymbolTable) -> ProtoGraph { } match check_sat(&mut protocol, guard) { - SatResult::DefinitelyUnsat => continue, + SatResult::DefinitelyUnsat | SatResult::DefinitelySat => continue, SatResult::MaybeSat => { let target_id = get_or_create_state(targets, &mut state_ids, &mut worklist, &mut new_nodes); diff --git a/protocols/src/ir/graphviz.rs b/protocols/src/ir/graphviz.rs index 912933ee..ae823a33 100644 --- a/protocols/src/ir/graphviz.rs +++ b/protocols/src/ir/graphviz.rs @@ -117,7 +117,7 @@ fn format_assignment(protocol: &ProtoGraph, assignment: &Assignment) -> String { } } -fn format_expr(protocol: &ProtoGraph, expr_ref: ExprRef) -> String { +pub(crate) fn format_expr(protocol: &ProtoGraph, expr_ref: ExprRef) -> String { if protocol.dont_cares.contains(&expr_ref) { "X".to_string() } else { @@ -198,18 +198,24 @@ mod tests { use super::*; use crate::frontend; + use super::*; use crate::frontend::diagnostic::DiagnosticHandler; use crate::ir::edge_contract::{contract_edges, normalize_assignments}; use crate::ir::lowering::lower_ast_to_ir; + use crate::ir::reaching_defs::{ + AssignmentValue, ReachingDefs, exists_conflicts, format_reaching_defs, reaching_definitions, + }; use insta::Settings; + use rustc_hash::FxHashMap; + fn snap(name: &str, filename: &str) { let mut handler = DiagnosticHandler::default(); let ast = frontend(&[filename], &mut handler, false).unwrap(); let mut content = String::new(); let st = &ast.st; for proto_ast in ast.protos { - let ir: ProtoGraph = lower_ast_to_ir(proto_ast, st); + let mut ir: ProtoGraph = lower_ast_to_ir(proto_ast, &st); content += "== pre-contract ==\n"; content += &to_dot_string(&ir, st); content += "\n"; @@ -221,6 +227,16 @@ mod tests { content += &to_dot_string(&contracted_ir, st); content += "\n"; + // run the reaching definitions analysis + let reaching_defs = reaching_definitions(&mut ir, &st); + + // pretty print the reaching definitions + content += "== reaching-defs ==\n"; + // content += &format_reaching_defs(&ir, &symbols, &reaching_defs); + // content += "\n"; + content += exists_conflicts(&reaching_defs).to_string().as_str(); + content += "\n"; + let mut assignment_normalized_ir = contracted_ir.clone(); normalize_assignments(&mut assignment_normalized_ir, st); content += "== post-normalize ==\n"; @@ -234,7 +250,6 @@ mod tests { insta::assert_snapshot!(name, content); }); } - #[test] fn test_add_d1_dot_snapshot() { snap("ir_graphviz_add_d1", "../tests/adders/adder_d1/add_d1.prot"); diff --git a/protocols/src/ir/mod.rs b/protocols/src/ir/mod.rs index 49be98d7..e06fcc0c 100644 --- a/protocols/src/ir/mod.rs +++ b/protocols/src/ir/mod.rs @@ -3,6 +3,7 @@ pub mod edge_contract; pub mod graph_interpreter; pub mod graphviz; pub mod lowering; +mod propagate_assigns; pub mod proto_graph; pub mod reaching_defs; pub mod trace_lowering; diff --git a/protocols/src/ir/propagate_assigns.rs b/protocols/src/ir/propagate_assigns.rs new file mode 100644 index 00000000..917f5e8b --- /dev/null +++ b/protocols/src/ir/propagate_assigns.rs @@ -0,0 +1,34 @@ +use crate::frontend::symbol::{SymbolId, SymbolTable}; +use crate::ir::proto_graph::{Action, NodeId, Op, ProtoGraph}; +use crate::ir::reaching_defs::ReachingDefs; +use rustc_hash::{FxHashMap, FxHashSet}; + +pub fn propagate_assignments( + pg: &mut ProtoGraph, + st: &SymbolTable, + rd: &FxHashMap +) { + let input_ports: &Vec = &st + .get_children(&pg.proto_ctx.type_param.unwrap()) + .into_iter() + .filter(|sym_id| st[*sym_id].is_in_port()) + .collect(); + + for (id, node) in pg.nodes() { + let assigned : Vec = node.actions.iter().filter_map(|action| { + match pg[action.op] { + Op::Assign(sid, _) => Some(sid), + _ => None + } + }).collect(); + + for input in input_ports { + if !assigned.contains(input) { + // apply the input from the reaching definitions if only one exists + + } + } + + } + +} diff --git a/protocols/src/ir/reaching_defs.rs b/protocols/src/ir/reaching_defs.rs index 9119badd..76de2efa 100644 --- a/protocols/src/ir/reaching_defs.rs +++ b/protocols/src/ir/reaching_defs.rs @@ -3,21 +3,37 @@ // author: Nikil Shyamunder use crate::frontend::symbol::{SymbolId, SymbolTable}; -use crate::interpreter::Value as WaveValue; -use crate::ir::proto_graph::{Node, NodeId, ProtoGraph}; -use rustc_hash::FxHashMap; +use crate::ir::determinize::{SatResult, check_sat}; +use crate::ir::proto_graph::{NodeId, Op, ProtoGraph}; +use itertools::Itertools; +use patronus::expr::ExprRef; +use rustc_hash::{FxHashMap, FxHashSet}; + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub enum AssignmentValue { + DontCare, + Concrete(ExprRef), +} + +pub type ReachingDefs = FxHashMap>; /// Nodes only store their outgoing transitions. This function precomputes and returns /// the in-neighbors for each node that is reachable from the entry. -fn predecessors(pg : &ProtoGraph) -> FxHashMap> { - let mut predecessors : FxHashMap> = FxHashMap::default(); +fn predecessors(pg: &ProtoGraph) -> FxHashMap> { + let mut predecessors: FxHashMap> = FxHashMap::default(); let mut q = vec![pg.entry]; + let mut visited: FxHashSet = FxHashSet::default(); while let Some(n) = q.pop() { + visited.insert(n); + // n is a predecessor of t for t in pg[n].clone().transitions { - predecessors.entry(n).and_modify(|v| v.push(t.target)); - q.push(t.target); + predecessors.entry(t.target).or_default().push(n); + + if !visited.contains(&t.target) { + q.push(t.target); + } } } @@ -25,9 +41,11 @@ fn predecessors(pg : &ProtoGraph) -> FxHashMap> { } pub fn reaching_definitions( - pg: &ProtoGraph, + // it's a bit of a shame that we have to pass a mutable graph for an analysis, but the + // sat checker mutates expressions by simplifying them + pg: &mut ProtoGraph, st: &SymbolTable, -) -> FxHashMap> { +) -> FxHashMap { let preds = predecessors(pg); let dut = pg.type_param.expect("protocol has no DUT"); @@ -38,31 +56,184 @@ pub fn reaching_definitions( .collect(); // by default, the entry node begins with - let mut in_defs: FxHashMap> = FxHashMap::default(); + let mut in_defs: FxHashMap = FxHashMap::default(); in_defs.insert( pg.entry, input_ports .iter() - .map(|sym_id| (*sym_id, WaveValue::DontCare)) + .map(|sym_id| (*sym_id, FxHashSet::from_iter([AssignmentValue::DontCare]))) .collect(), ); - let out_defs : FxHashMap> = FxHashMap::default(); + let mut out_defs: FxHashMap = FxHashMap::default(); - let mut worklist: Vec<(NodeId, &Node)> = pg.nodes().collect::>(); + let mut worklist: Vec = pg.nodes().map(|(id, _)| id).collect(); - while let Some((id, _)) = worklist.pop() { + while let Some(id) = worklist.pop() { // run the merge function with its predecessors to get in_defs[n] // merge function is just the union - let merged : Vec<(SymbolId, WaveValue)> = preds[&id].iter().map(|nid| out_defs[nid].clone()).flatten().collect(); - in_defs.insert(id, merged); + let mut merged = ReachingDefs::default(); + for pred_id in preds.get(&id).into_iter().flatten() { + if let Some(pred_defs) = out_defs.get(pred_id) { + for (symbol_id, values) in pred_defs { + merged + .entry(*symbol_id) + .or_default() + .extend(values.iter().copied()); + } + } + } + // in defs for the entry is predefined + if id != pg.entry { + in_defs.insert(id, merged); + } + + // run the transfer function on it to get out_defs[n] + // TODO: I wonder if this is more confusing than just having a regular loop + let assignments = pg[id] + .actions + .iter() + .filter_map(|a| match pg[a.op].clone() { + Op::Assign(symbol_id, assignment) => Some((symbol_id, assignment)), + _ => None, + }) + .collect::>(); + + let mut out = in_defs.get(&id).cloned().unwrap_or_default(); + + for (symbol_id, assignment) in assignments { + // if an assignment is definitely satisfiable, then remove all the other assignments + match check_sat(pg, assignment.dont_care) { + SatResult::DefinitelySat => { + out.insert(symbol_id, FxHashSet::from_iter([AssignmentValue::DontCare])); + continue; + } + SatResult::MaybeSat => { + out.entry(symbol_id) + .or_default() + .insert(AssignmentValue::DontCare); + } + SatResult::DefinitelyUnsat => (), + } + + for (guard, val) in assignment.concretes { + match check_sat(pg, guard) { + SatResult::DefinitelySat => { + out.insert( + symbol_id, + FxHashSet::from_iter([AssignmentValue::Concrete(val)]), + ); + break; + } + SatResult::MaybeSat => { + out.entry(symbol_id) + .or_default() + .insert(AssignmentValue::Concrete(val)); + } + SatResult::DefinitelyUnsat => (), + } + } + } + + if out_defs.get(&id) != Some(&out) { + out_defs.insert(id, out); + + worklist.extend( + pg[id] + .clone() + .transitions + .into_iter() + .map(|t| t.target) + .unique(), + ); + } + } + + in_defs +} + +/// Returns `true` if there are multiple reachable assignments for any port for any node +pub fn exists_conflicts(reaching_defs: &FxHashMap) -> bool { + for rd in reaching_defs.values() { + for set in rd.values() { + if set.len() > 1 { + return true; + } + } + } + + false +} + +pub fn all_ports_present( + reaching_defs: &FxHashMap, + pg: &ProtoGraph, + st: &SymbolTable, +) -> bool { + // TODO: maybe make this a helper in pg context or something + let input_ports: &Vec = &st + .get_children(&pg.proto_ctx.type_param.unwrap()) + .into_iter() + .filter(|sym_id| st[*sym_id].is_in_port()) + .collect(); + + // all ports present + for rd in reaching_defs.values() { + for input in input_ports { + if !rd.contains_key(&input) { + return false; + } + } + } + + // all nodes present + for (id, _) in pg.nodes() { + if !reaching_defs.contains_key(&id) { + return false; + } + } + + true +} - // run the transfer function on it to get out_defs[n] - // if an assignment is definitely satisfiable, then remove all the other assignments +pub fn format_reaching_defs( + protocol: &ProtoGraph, + symbols: &SymbolTable, + reaching_defs: &FxHashMap, +) -> String { + let mut out = String::new(); + let mut node_ids = reaching_defs.keys().copied().collect::>(); + node_ids.sort(); - // if out b changed, add the successors to the worklist + for node_id in node_ids { + out.push_str(&format!("{node_id}:\n")); + let Some(defs) = reaching_defs.get(&node_id) else { + continue; + }; + + let mut symbol_defs = defs.iter().collect::>(); + symbol_defs.sort_by_key(|(symbol_id, _)| symbols.full_name_from_symbol_id(symbol_id)); + + for (symbol_id, values) in symbol_defs { + let mut values = values + .iter() + .map(|value| format_assignment_value(protocol, *value)) + .collect::>(); + values.sort(); + out.push_str(&format!( + " {}: {}\n", + symbols.full_name_from_symbol_id(symbol_id), + values.join(", ") + )); + } } + out +} - FxHashMap::default() +fn format_assignment_value(protocol: &ProtoGraph, value: AssignmentValue) -> String { + match value { + AssignmentValue::DontCare => "X".to_string(), + AssignmentValue::Concrete(expr) => crate::ir::graphviz::format_expr(protocol, expr), + } } diff --git a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap.new b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap.new new file mode 100644 index 00000000..ae51103e --- /dev/null +++ b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap.new @@ -0,0 +1,106 @@ +--- +source: protocols/src/frontend/serialize.rs +assertion_line: 427 +expression: content +--- +struct Adder { + in a: u32, + in b: u32, + out s: u32, +} + +fn add(a: u32, b: u32, s: u32) { + DUT.a := a; + DUT.b := b; + step(); + DUT.a := X; + DUT.b := X; + assert_eq(s, DUT.s); + fork(); + step(); +} + +fn add_fork_early(a: u32, b: u32, s: u32) { + DUT.a := a; + DUT.b := b; + step(); + fork(); + DUT.a := X; + DUT.b := X; + assert_eq(s, DUT.s); + step(); +} + +fn add_incorrect(a: u32, b: u32, s: u32) { + DUT.a := a; + DUT.b := b; + step(); + DUT.a := a; + DUT.b := b; + assert_eq(s, DUT.s); + fork(); + step(); +} + +fn add_incorrect_implicit(a: u32, b: u32, s: u32) { + DUT.a := a; + DUT.b := b; + step(); + assert_eq(s, DUT.s); + fork(); + step(); +} + +fn wait_and_add(a: u32, b: u32, s: u32) { + step(); + fork(); + step(); + DUT.a := a; + DUT.b := b; + step(); + assert_eq(s, DUT.s); + step(); +} + +fn assign_after_observation(a: u32, b: u32, s: u32) { + DUT.a := a; + if DUT.a == 2 { + DUT.a := 0; + } else { + } + DUT.b := b; + step(); + DUT.a := X; + DUT.b := X; + assert_eq(s, DUT.s); + fork(); + step(); +} + +fn better_if_else(a: u32, b: u32) { + DUT.a := a; + DUT.b := b; + step(); + if DUT.s == 0 { + DUT.a := X; + } else { + DUT.a := 5; + } + fork(); + step(); +} + +fn if_else(a1: u32, a2: u32) { + if 1 { + DUT.a := a1; + } else { + DUT.a := a2; + } + DUT.b := 0; + step(); + DUT.a := X; + DUT.b := X; + assert_eq(a1, DUT.s); + fork(); + step(); +} diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new index 8fd81424..31279017 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new @@ -1,6 +1,6 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 234 +assertion_line: 247 expression: content --- == pre-contract == @@ -43,6 +43,8 @@ digraph "add_combinational_illegal_observation_in_conditional" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "add_combinational_illegal_observation_in_conditional" { rankdir=LR; @@ -87,6 +89,8 @@ digraph "add_combinational_illegal_observation_in_assertion" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "add_combinational_illegal_observation_in_assertion" { rankdir=LR; @@ -132,6 +136,8 @@ digraph "add_combinational_legal_observation_illegal_assignment" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +true == post-normalize == digraph "add_combinational_legal_observation_illegal_assignment" { rankdir=LR; @@ -174,6 +180,8 @@ digraph "add_combinational" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "add_combinational" { rankdir=LR; diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new index abed4e11..3a819af5 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new @@ -1,6 +1,6 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 234 +assertion_line: 247 expression: content --- == pre-contract == @@ -43,6 +43,8 @@ digraph "add" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "add" { rankdir=LR; @@ -96,6 +98,8 @@ digraph "add_fork_early" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "add_fork_early" { rankdir=LR; @@ -149,6 +153,8 @@ digraph "add_incorrect" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "add_incorrect" { rankdir=LR; @@ -198,6 +204,8 @@ digraph "add_incorrect_implicit" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "add_incorrect_implicit" { rankdir=LR; @@ -255,6 +263,8 @@ digraph "wait_and_add" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "wait_and_add" { rankdir=LR; @@ -322,6 +332,8 @@ digraph "assign_after_observation" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +true == post-normalize == digraph "assign_after_observation" { rankdir=LR; @@ -336,6 +348,66 @@ digraph "assign_after_observation" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== pre-contract == +digraph "better_if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := a if 1"]; + node10 -> node9 [label="1"]; + node9 [label="[1] DUT.b := b if 1"]; + node9 -> node8 [label="1"]; + node8 [label=""]; + node8 -> node7 [label="1 / step"]; + node7 [label=""]; + node7 -> node5 [label="eq(DUT.s, 0)"]; + node7 -> node6 [label="not(eq(DUT.s, 0))"]; + node5 [label="[1] DUT.a := X if 1"]; + node5 -> node4 [label="1"]; + node6 [label="[1] DUT.a := 5 if 1"]; + node6 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "better_if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node7 [label="1 / step"]; + node7 [label="[1] DUT.a := X if eq(DUT.s, 0); 5 if not(eq(DUT.s, 0))\n[1] fork\n[0] internal_assert_false"]; + node7 -> node1 [label="not(eq(DUT.s, 0)) / step"]; + node7 -> node1 [label="eq(DUT.s, 0) / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== reaching-defs == +true +== post-normalize == +digraph "better_if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node7 [label="1 / step"]; + node7 [label="[1] DUT.a := X if eq(DUT.s, 0); 5 if not(eq(DUT.s, 0))\n[1] fork\n[0] internal_assert_false\n[1] DUT.b := DUT.b if 1"]; + node7 -> node1 [label="not(eq(DUT.s, 0)) / step"]; + node7 -> node1 [label="eq(DUT.s, 0) / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + == pre-contract == digraph "if_else" { rankdir=LR; @@ -380,6 +452,8 @@ digraph "if_else" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } +== reaching-defs == +true == post-normalize == digraph "if_else" { rankdir=LR; diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new index 8fc3e891..c6172210 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new @@ -1,6 +1,6 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 234 +assertion_line: 247 expression: content --- == pre-contract == @@ -42,6 +42,8 @@ digraph "send_data" { node4 -> node1 [label="D.m_axis_tready / step"]; } +== reaching-defs == +false == post-normalize == digraph "send_data" { rankdir=LR; @@ -83,6 +85,8 @@ digraph "idle" { node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "idle" { rankdir=LR; diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new index 395c76f7..1a078494 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new @@ -1,6 +1,6 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 234 +assertion_line: 247 expression: content --- == pre-contract == @@ -57,6 +57,8 @@ digraph "send_data" { node5 -> node1 [label="1 / step"]; } +== reaching-defs == +false == post-normalize == digraph "send_data" { rankdir=LR; diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new index 0dbc43ca..aa09a646 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new @@ -1,6 +1,6 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 234 +assertion_line: 247 expression: content --- == pre-contract == @@ -48,6 +48,8 @@ digraph "count_up" { node1 [label="[1] done\n[1] dut.a := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "count_up" { rankdir=LR; @@ -112,6 +114,8 @@ digraph "count_up_extra_assertion" { node1 [label="[1] done\n[1] dut.a := X if 1"]; } +== reaching-defs == +false == post-normalize == digraph "count_up_extra_assertion" { rankdir=LR; diff --git a/tests/adders/adder_d1/add_d1.prot b/tests/adders/adder_d1/add_d1.prot index cacf3ce0..bb677dcf 100644 --- a/tests/adders/adder_d1/add_d1.prot +++ b/tests/adders/adder_d1/add_d1.prot @@ -89,6 +89,27 @@ prot assign_after_observation(a: u32, b: u32, s: u32) { step(); } + +// conditional assignment +prot better_if_else(a: u32, b: u32) { + DUT.a := a; + DUT.b := b; + + step(); + + if (DUT.s == 32'b0){ + DUT.a := X; + } + else { + DUT.a := 32'd5; + } + +// assert_eq(, DUT.s); + + fork(); + step(); +} + // conditional assignment prot if_else(a1: u32, a2: u32) { if (1'b1) { From 859d60855d300e79bdf5f9a6e478bda564b59d3f Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Sat, 4 Jul 2026 11:48:43 -0400 Subject: [PATCH 05/12] track the assignment guards for propogtion --- graph-interp/src/main.rs | 20 ++++-- protocols/src/ir/graphviz.rs | 14 +++- protocols/src/ir/mod.rs | 2 +- protocols/src/ir/propagate_assigns.rs | 68 ++++++++++++++---- protocols/src/ir/reaching_defs.rs | 67 +++++++++++++---- ..._truncated_include_idle_send_data.snap.new | 49 +++++++++++-- ...__ir_graphviz_bounded_ready_valid.snap.new | 45 ++++++++++-- ...phviz__tests__ir_graphviz_counter.snap.new | 72 ++++++++++++++++--- 8 files changed, 278 insertions(+), 59 deletions(-) diff --git a/graph-interp/src/main.rs b/graph-interp/src/main.rs index dcf0b279..fff1b6a9 100644 --- a/graph-interp/src/main.rs +++ b/graph-interp/src/main.rs @@ -12,10 +12,11 @@ use protocols::ir::graph_interpreter; use protocols::ir::graphviz::to_dot_string; use protocols::ir::lowering::lower_ast_to_ir; use protocols::ir::proto_graph::ProtoGraph; -use protocols::ir::reaching_defs::{all_ports_present, exists_conflicts, reaching_definitions}; +use protocols::ir::reaching_defs::{all_ports_present, exists_conflicts, format_reaching_defs, reaching_definitions}; use protocols::ir::trace_lowering::lower_trace_to_ir; use protocols::{PatronusSim, Value, frontend, transaction_frontend}; use rustc_hash::FxHashMap; +use protocols::ir::propagate_assigns::propagate_assignments; #[derive(Parser, Debug)] struct Cli { @@ -133,8 +134,16 @@ fn run_classic( if cli.contract_edges { for (_, graph) in &mut graphs { contract_edges(graph, st); - normalize_assignments(graph, st); - graph.simplify_all_exprs(); + // normalize_assignments(graph, st); + let rd = reaching_definitions(graph, st); + + if cli.graphout { + propagate_assignments(graph, &st, &rd); + println!("{}", to_dot_string(graph, st).as_str()); + // let rd = reaching_definitions(graph, st); + // println!("{}", format_reaching_defs(graph, &st, &rd).as_str()); + } + // propagate_assignments(graph, &st, &rd); } } @@ -183,7 +192,7 @@ fn run_respect_forks( } let rd = reaching_definitions(&mut joint, &st); - if exists_conflicts(&rd) { + if exists_conflicts(&rd, &joint) { println!("bleh") } @@ -191,6 +200,9 @@ fn run_respect_forks( println!("bleh bleh") } + propagate_assignments(&mut joint, &st, &rd); + // println!("blah"); + // args are baked into the graph as constants, so we just pass in an empty map here let waveform = graph_interpreter::interpret(&joint, st, FxHashMap::default(), &mut sim); if cli.ascii_waveform { diff --git a/protocols/src/ir/graphviz.rs b/protocols/src/ir/graphviz.rs index ae823a33..19bd301b 100644 --- a/protocols/src/ir/graphviz.rs +++ b/protocols/src/ir/graphviz.rs @@ -208,6 +208,7 @@ mod tests { use insta::Settings; use rustc_hash::FxHashMap; + use crate::ir::propagate_assigns::propagate_assignments; fn snap(name: &str, filename: &str) { let mut handler = DiagnosticHandler::default(); @@ -231,11 +232,18 @@ mod tests { let reaching_defs = reaching_definitions(&mut ir, &st); // pretty print the reaching definitions - content += "== reaching-defs ==\n"; - // content += &format_reaching_defs(&ir, &symbols, &reaching_defs); + // content += "== reaching-defs ==\n"; + // // content += &format_reaching_defs(&ir, &symbols, &reaching_defs); + // // content += "\n"; + // content += exists_conflicts(&reaching_defs, &ir).to_string().as_str(); // content += "\n"; - content += exists_conflicts(&reaching_defs).to_string().as_str(); + + // print post-propagation of assignments + propagate_assignments(&mut contracted_ir, &symbols, &reaching_defs); + content += "== post-propagation ==\n"; + content += &to_dot_string(&ir, &symbols); content += "\n"; + let mut assignment_normalized_ir = contracted_ir.clone(); normalize_assignments(&mut assignment_normalized_ir, st); diff --git a/protocols/src/ir/mod.rs b/protocols/src/ir/mod.rs index e06fcc0c..952141ca 100644 --- a/protocols/src/ir/mod.rs +++ b/protocols/src/ir/mod.rs @@ -3,7 +3,7 @@ pub mod edge_contract; pub mod graph_interpreter; pub mod graphviz; pub mod lowering; -mod propagate_assigns; +pub mod propagate_assigns; pub mod proto_graph; pub mod reaching_defs; pub mod trace_lowering; diff --git a/protocols/src/ir/propagate_assigns.rs b/protocols/src/ir/propagate_assigns.rs index 917f5e8b..1c7c465b 100644 --- a/protocols/src/ir/propagate_assigns.rs +++ b/protocols/src/ir/propagate_assigns.rs @@ -1,34 +1,72 @@ use crate::frontend::symbol::{SymbolId, SymbolTable}; -use crate::ir::proto_graph::{Action, NodeId, Op, ProtoGraph}; -use crate::ir::reaching_defs::ReachingDefs; +use crate::ir::proto_graph::{Action, Assignment, NodeId, Op, ProtoGraph}; +use crate::ir::reaching_defs::{AssignmentValue, ReachingDefs, all_ports_present, exists_conflicts}; use rustc_hash::{FxHashMap, FxHashSet}; pub fn propagate_assignments( pg: &mut ProtoGraph, st: &SymbolTable, - rd: &FxHashMap + rd: &FxHashMap, ) { - let input_ports: &Vec = &st + // TODO: some of the assertions in here are overkill. + assert!( + !exists_conflicts(rd, pg), + "cannot propagate assignments with conflicting reaching definitions" + ); + assert!( + all_ports_present(rd, pg, st), + "cannot propagate assignments unless all ports are present at all nodes" + ); + + let input_ports: Vec = st .get_children(&pg.proto_ctx.type_param.unwrap()) .into_iter() .filter(|sym_id| st[*sym_id].is_in_port()) .collect(); - for (id, node) in pg.nodes() { - let assigned : Vec = node.actions.iter().filter_map(|action| { - match pg[action.op] { + let node_ids = pg.nodes().map(|(id, _)| id).collect::>(); + + for id in node_ids { + let assigned: FxHashSet = pg[id] + .actions + .iter() + .filter_map(|action| match pg[action.op] { Op::Assign(sid, _) => Some(sid), - _ => None + _ => None, + }) + .collect(); + + for input in &input_ports { + if assigned.contains(input) { + continue; } - }).collect(); - for input in input_ports { - if !assigned.contains(input) { - // apply the input from the reaching definitions if only one exists + // The caller should check !exists_conflicts and all_ports_present before propagation. + let values = rd + .get(&id) + .and_then(|defs| defs.get(input)) + .unwrap_or_else(|| panic!("missing reaching definition for {input} at {id}")); + assert_eq!( + values.len(), + 1, + "cannot propagate conflicting reaching definitions for {input} at {id}" + ); - } - } + let def = *values.iter().next().unwrap(); + assert_eq!( + def.guard, + pg.true_id(), + "cannot propagate a guarded reaching definition for {input} at {id}" + ); + let assignment = match def.value { + AssignmentValue::DontCare => Assignment::dont_care(pg.true_id()), + AssignmentValue::Concrete(expr) => { + Assignment::concrete(pg.false_id(), pg.true_id(), expr) + } + }; + let op = pg.o(Op::Assign(*input, assignment)); + pg.push_action(id, Action::new(pg.true_id(), op)); + } } - } diff --git a/protocols/src/ir/reaching_defs.rs b/protocols/src/ir/reaching_defs.rs index 76de2efa..2fcf38fe 100644 --- a/protocols/src/ir/reaching_defs.rs +++ b/protocols/src/ir/reaching_defs.rs @@ -15,7 +15,13 @@ pub enum AssignmentValue { Concrete(ExprRef), } -pub type ReachingDefs = FxHashMap>; +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct AssignmentDef { + pub guard: ExprRef, + pub value: AssignmentValue, +} + +pub type ReachingDefs = FxHashMap>; /// Nodes only store their outgoing transitions. This function precomputes and returns /// the in-neighbors for each node that is reachable from the entry. @@ -61,7 +67,15 @@ pub fn reaching_definitions( pg.entry, input_ports .iter() - .map(|sym_id| (*sym_id, FxHashSet::from_iter([AssignmentValue::DontCare]))) + .map(|sym_id| { + ( + *sym_id, + FxHashSet::from_iter([AssignmentDef { + guard: pg.true_id(), + value: AssignmentValue::DontCare, + }]), + ) + }) .collect(), ); @@ -105,13 +119,20 @@ pub fn reaching_definitions( // if an assignment is definitely satisfiable, then remove all the other assignments match check_sat(pg, assignment.dont_care) { SatResult::DefinitelySat => { - out.insert(symbol_id, FxHashSet::from_iter([AssignmentValue::DontCare])); + out.insert( + symbol_id, + FxHashSet::from_iter([AssignmentDef { + guard: pg.true_id(), + value: AssignmentValue::DontCare, + }]), + ); continue; } SatResult::MaybeSat => { - out.entry(symbol_id) - .or_default() - .insert(AssignmentValue::DontCare); + out.entry(symbol_id).or_default().insert(AssignmentDef { + guard: assignment.dont_care, + value: AssignmentValue::DontCare, + }); } SatResult::DefinitelyUnsat => (), } @@ -121,14 +142,18 @@ pub fn reaching_definitions( SatResult::DefinitelySat => { out.insert( symbol_id, - FxHashSet::from_iter([AssignmentValue::Concrete(val)]), + FxHashSet::from_iter([AssignmentDef { + guard: pg.true_id(), + value: AssignmentValue::Concrete(val), + }]), ); break; } SatResult::MaybeSat => { - out.entry(symbol_id) - .or_default() - .insert(AssignmentValue::Concrete(val)); + out.entry(symbol_id).or_default().insert(AssignmentDef { + guard, + value: AssignmentValue::Concrete(val), + }); } SatResult::DefinitelyUnsat => (), } @@ -152,13 +177,19 @@ pub fn reaching_definitions( in_defs } -/// Returns `true` if there are multiple reachable assignments for any port for any node -pub fn exists_conflicts(reaching_defs: &FxHashMap) -> bool { +/// Returns `true` if any port at any node has more than one reaching assignment, +/// or if the unique reaching assignment is not unconditional. +pub fn exists_conflicts(reaching_defs: &FxHashMap, pg: &ProtoGraph) -> bool { for rd in reaching_defs.values() { for set in rd.values() { if set.len() > 1 { return true; } + if let Some(def) = set.iter().next() { + if def.guard != pg.true_id() { + return true; + } + } } } @@ -217,7 +248,7 @@ pub fn format_reaching_defs( for (symbol_id, values) in symbol_defs { let mut values = values .iter() - .map(|value| format_assignment_value(protocol, *value)) + .map(|def| format_assignment_def(protocol, *def)) .collect::>(); values.sort(); out.push_str(&format!( @@ -231,9 +262,15 @@ pub fn format_reaching_defs( out } -fn format_assignment_value(protocol: &ProtoGraph, value: AssignmentValue) -> String { - match value { +fn format_assignment_def(protocol: &ProtoGraph, def: AssignmentDef) -> String { + let value = match def.value { AssignmentValue::DontCare => "X".to_string(), AssignmentValue::Concrete(expr) => crate::ir::graphviz::format_expr(protocol, expr), + }; + + if def.guard == protocol.true_id() { + value + } else { + format!("{value} if {}", crate::ir::graphviz::format_expr(protocol, def.guard)) } } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new index c6172210..6288b124 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new @@ -1,6 +1,6 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 247 +assertion_line: 255 expression: content --- == pre-contract == @@ -42,8 +42,30 @@ digraph "send_data" { node4 -> node1 [label="D.m_axis_tready / step"]; } -== reaching-defs == -false +== post-propagation == +digraph "send_data" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node7 [label="1"]; + node7 [label="[1] D.m_axis_tdata := data if 1"]; + node7 -> node6 [label="1"]; + node6 [label="[1] D.m_axis_tvalid := 1 if 1"]; + node6 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node5 [label="not(D.m_axis_tready)"]; + node4 -> node3 [label="D.m_axis_tready"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; +} + == post-normalize == digraph "send_data" { rankdir=LR; @@ -54,7 +76,7 @@ digraph "send_data" { node0 -> node1 [label="D.m_axis_tready / step"]; node0 -> node4 [label="not(D.m_axis_tready) / step"]; node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; - node4 [label="[1] D.m_axis_tvalid := D.m_axis_tvalid if 1\n[1] D.m_axis_tdata := D.m_axis_tdata if 1"]; + node4 [label="[1] D.m_axis_tvalid := 1 if 1\n[1] D.m_axis_tdata := data if 1"]; node4 -> node4 [label="not(D.m_axis_tready) / step"]; node4 -> node1 [label="D.m_axis_tready / step"]; } @@ -85,15 +107,28 @@ digraph "idle" { node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; } -== reaching-defs == -false +== post-propagation == +digraph "idle" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node3 [label="1"]; + node3 [label="[1] D.m_axis_tvalid := 0 if 1"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; +} + == post-normalize == digraph "idle" { rankdir=LR; node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.m_axis_tvalid := 0 if 1\n[1] D.m_axis_tdata := D.m_axis_tdata if 1"]; + node0 [label="[1] D.m_axis_tvalid := 0 if 1\n[1] D.m_axis_tdata := X if 1"]; node0 -> node1 [label="1 / step"]; node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new index 1a078494..36e26d03 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new @@ -1,6 +1,6 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 247 +assertion_line: 255 expression: content --- == pre-contract == @@ -57,8 +57,43 @@ digraph "send_data" { node5 -> node1 [label="1 / step"]; } -== reaching-defs == -false +== post-propagation == +digraph "send_data" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node13 [label="1"]; + node13 [label="[1] D.data := data if 1"]; + node13 -> node12 [label="1"]; + node12 [label="[1] D.valid := 1 if 1"]; + node12 -> node11 [label="1"]; + node11 [label=""]; + node11 -> node9 [label="not(D.ready)"]; + node11 -> node10 [label="D.ready"]; + node9 [label=""]; + node9 -> node8 [label="1 / step"]; + node10 [label=""]; + node10 -> node3 [label="1"]; + node8 [label=""]; + node8 -> node6 [label="not(D.ready)"]; + node8 -> node7 [label="D.ready"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node6 [label=""]; + node6 -> node5 [label="1 / step"]; + node7 [label=""]; + node7 -> node4 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node5 [label="[1] assert_eq(D.ready, 1)"]; + node5 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node3 [label="1"]; + node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; +} + == post-normalize == digraph "send_data" { rankdir=LR; @@ -69,9 +104,9 @@ digraph "send_data" { node0 -> node1 [label="D.ready / step"]; node0 -> node8 [label="not(D.ready) / step"]; node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; - node8 [label="[1] D.valid := D.valid if 1\n[1] D.data := D.data if 1"]; + node8 [label="[1] D.valid := 1 if 1\n[1] D.data := data if 1"]; node8 -> node5 [label="not(D.ready) / step"]; node8 -> node1 [label="D.ready / step"]; - node5 [label="[1] assert_eq(D.ready, 1)\n[1] D.valid := D.valid if 1\n[1] D.data := D.data if 1"]; + node5 [label="[1] assert_eq(D.ready, 1)\n[1] D.valid := 1 if 1\n[1] D.data := data if 1"]; node5 -> node1 [label="1 / step"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new index aa09a646..ded0a2ab 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new @@ -1,6 +1,6 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 247 +assertion_line: 255 expression: content --- == pre-contract == @@ -48,8 +48,34 @@ digraph "count_up" { node1 [label="[1] done\n[1] dut.a := X if 1"]; } -== reaching-defs == -false +== post-propagation == +digraph "count_up" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] dut.a := a if 1"]; + node9 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node8 [label="not(eq(dut.s, dut.a))"]; + node7 -> node6 [label="eq(dut.s, dut.a)"]; + node8 [label=""]; + node8 -> node7 [label="1 / step"]; + node6 [label=""]; + node6 -> node5 [label="1"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node4 [label="[1] assert_eq(dut.s, 0)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; +} + == post-normalize == digraph "count_up" { rankdir=LR; @@ -59,9 +85,9 @@ digraph "count_up" { node0 [label="[1] dut.a := a if 1"]; node0 -> node4 [label="eq(dut.s, dut.a) / step"]; node0 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; - node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := dut.a if 1"]; + node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := a if 1"]; node4 -> node1 [label="1 / step"]; - node7 [label="[1] dut.a := dut.a if 1"]; + node7 [label="[1] dut.a := a if 1"]; node7 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; node7 -> node4 [label="eq(dut.s, dut.a) / step"]; node1 [label="[1] done\n[1] dut.a := X if 1"]; @@ -114,8 +140,36 @@ digraph "count_up_extra_assertion" { node1 [label="[1] done\n[1] dut.a := X if 1"]; } -== reaching-defs == -false +== post-propagation == +digraph "count_up_extra_assertion" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node10 [label="1"]; + node10 [label="[1] dut.a := a if 1"]; + node10 -> node8 [label="1"]; + node8 [label=""]; + node8 -> node9 [label="not(eq(dut.s, dut.a))"]; + node8 -> node7 [label="eq(dut.s, dut.a)"]; + node9 [label=""]; + node9 -> node8 [label="1 / step"]; + node7 [label=""]; + node7 -> node6 [label="1"]; + node6 [label="[1] assert_eq(dut.s, a)"]; + node6 -> node5 [label="1"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node4 [label="[1] assert_eq(dut.s, 0)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; +} + == post-normalize == digraph "count_up_extra_assertion" { rankdir=LR; @@ -125,9 +179,9 @@ digraph "count_up_extra_assertion" { node0 [label="[1] dut.a := a if 1\n[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; node0 -> node4 [label="eq(dut.s, dut.a) / step"]; node0 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; - node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := dut.a if 1"]; + node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := a if 1"]; node4 -> node1 [label="1 / step"]; - node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)\n[1] dut.a := dut.a if 1"]; + node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)\n[1] dut.a := a if 1"]; node8 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; node8 -> node4 [label="eq(dut.s, dut.a) / step"]; node1 [label="[1] done\n[1] dut.a := X if 1"]; From 41ca8464a9d1c71a19ed61d5f4af2a86ed4200bc Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Sat, 4 Jul 2026 13:02:14 -0400 Subject: [PATCH 06/12] need to deal with hanging nodes --- graph-interp/src/main.rs | 19 +++-- protocols/src/ir/propagate_assigns.rs | 102 ++++++++++++++++------ protocols/src/ir/reaching_defs.rs | 118 +++++++++++++++++++------- 3 files changed, 173 insertions(+), 66 deletions(-) diff --git a/graph-interp/src/main.rs b/graph-interp/src/main.rs index fff1b6a9..c949b8c0 100644 --- a/graph-interp/src/main.rs +++ b/graph-interp/src/main.rs @@ -12,7 +12,7 @@ use protocols::ir::graph_interpreter; use protocols::ir::graphviz::to_dot_string; use protocols::ir::lowering::lower_ast_to_ir; use protocols::ir::proto_graph::ProtoGraph; -use protocols::ir::reaching_defs::{all_ports_present, exists_conflicts, format_reaching_defs, reaching_definitions}; +use protocols::ir::reaching_defs::{all_ports_present, exists_conflicts, reaching_definitions}; use protocols::ir::trace_lowering::lower_trace_to_ir; use protocols::{PatronusSim, Value, frontend, transaction_frontend}; use rustc_hash::FxHashMap; @@ -134,16 +134,16 @@ fn run_classic( if cli.contract_edges { for (_, graph) in &mut graphs { contract_edges(graph, st); - // normalize_assignments(graph, st); - let rd = reaching_definitions(graph, st); + + // if !exists_conflicts(&rd, graph) && all_ports_present(&rd, graph, st) { + // propagate_assignments(graph, st, &rd); + // } else { + // normalize_assignments(graph, st); + // } if cli.graphout { - propagate_assignments(graph, &st, &rd); println!("{}", to_dot_string(graph, st).as_str()); - // let rd = reaching_definitions(graph, st); - // println!("{}", format_reaching_defs(graph, &st, &rd).as_str()); } - // propagate_assignments(graph, &st, &rd); } } @@ -156,8 +156,11 @@ fn run_classic( .iter_mut() .find(|(n, _)| n == &name) .unwrap_or_else(|| panic!("unknown protocol {name}")); + + let rd = reaching_definitions(pg, st); + propagate_assignments(pg, st, &rd); + let args = build_arg_map(&pg.args, st, values); - // println!("{}", to_dot_string(pg, st)); graph_interpreter::interpret(pg, st, args, &mut sim); } print_trace_success(trace_index); diff --git a/protocols/src/ir/propagate_assigns.rs b/protocols/src/ir/propagate_assigns.rs index 1c7c465b..b01355a0 100644 --- a/protocols/src/ir/propagate_assigns.rs +++ b/protocols/src/ir/propagate_assigns.rs @@ -1,7 +1,54 @@ use crate::frontend::symbol::{SymbolId, SymbolTable}; use crate::ir::proto_graph::{Action, Assignment, NodeId, Op, ProtoGraph}; -use crate::ir::reaching_defs::{AssignmentValue, ReachingDefs, all_ports_present, exists_conflicts}; -use rustc_hash::{FxHashMap, FxHashSet}; +use crate::ir::reaching_defs::{ + AssignmentDef, AssignmentValue, ReachingDefs, all_ports_present, exists_conflicts, + reachable_nodes, +}; +use patronus::expr::ExprRef; +use rustc_hash::FxHashMap; + +fn raw_coverage(pg: &mut ProtoGraph, assignment: &Assignment) -> ExprRef { + assignment + .concretes + .iter() + .fold(assignment.dont_care, |coverage, (guard, _)| { + pg.or_guard(coverage, *guard) + }) +} + +fn assignment_from_reaching_def(pg: &ProtoGraph, def: AssignmentDef) -> Assignment { + assert_eq!( + def.guard, + pg.true_id(), + "cannot propagate a guarded reaching definition" + ); + + match def.value { + AssignmentValue::DontCare => Assignment::dont_care(pg.true_id()), + AssignmentValue::Concrete(expr) => Assignment::concrete(pg.false_id(), pg.true_id(), expr), + } +} + +fn add_fallback(pg: &mut ProtoGraph, mut assignment: Assignment, def: AssignmentDef) -> Assignment { + assert_eq!( + def.guard, + pg.true_id(), + "cannot propagate a guarded reaching definition" + ); + + match def.value { + AssignmentValue::DontCare => { + let coverage = raw_coverage(pg, &assignment); + let fallback_guard = pg.not_guard(coverage); + assignment.dont_care = pg.or_guard(assignment.dont_care, fallback_guard); + } + AssignmentValue::Concrete(expr) => { + assignment.concretes.push((pg.true_id(), expr)); + } + } + + assignment +} pub fn propagate_assignments( pg: &mut ProtoGraph, @@ -11,7 +58,7 @@ pub fn propagate_assignments( // TODO: some of the assertions in here are overkill. assert!( !exists_conflicts(rd, pg), - "cannot propagate assignments with conflicting reaching definitions" + "{}", format!("cannot propagate assignments in {} with conflicting reaching definitions", pg.proto_ctx.name), ); assert!( all_ports_present(rd, pg, st), @@ -24,27 +71,26 @@ pub fn propagate_assignments( .filter(|sym_id| st[*sym_id].is_in_port()) .collect(); - let node_ids = pg.nodes().map(|(id, _)| id).collect::>(); + let node_ids = reachable_nodes(pg).into_iter().collect::>(); for id in node_ids { - let assigned: FxHashSet = pg[id] + let Some(node_defs) = rd.get(&id) else { + continue; + }; + + let assigned: FxHashMap = pg[id] .actions .iter() - .filter_map(|action| match pg[action.op] { - Op::Assign(sid, _) => Some(sid), + .enumerate() + .filter_map(|(idx, action)| match pg[action.op] { + Op::Assign(sid, _) => Some((sid, idx)), _ => None, }) .collect(); for input in &input_ports { - if assigned.contains(input) { - continue; - } - - // The caller should check !exists_conflicts and all_ports_present before propagation. - let values = rd - .get(&id) - .and_then(|defs| defs.get(input)) + let values = node_defs + .get(input) .unwrap_or_else(|| panic!("missing reaching definition for {input} at {id}")); assert_eq!( values.len(), @@ -53,20 +99,20 @@ pub fn propagate_assignments( ); let def = *values.iter().next().unwrap(); - assert_eq!( - def.guard, - pg.true_id(), - "cannot propagate a guarded reaching definition for {input} at {id}" - ); - let assignment = match def.value { - AssignmentValue::DontCare => Assignment::dont_care(pg.true_id()), - AssignmentValue::Concrete(expr) => { - Assignment::concrete(pg.false_id(), pg.true_id(), expr) - } - }; - let op = pg.o(Op::Assign(*input, assignment)); - pg.push_action(id, Action::new(pg.true_id(), op)); + if let Some(action_idx) = assigned.get(input) { + let action = pg[id].actions[*action_idx].clone(); + let Op::Assign(_, assignment) = pg[action.op].clone() else { + unreachable!(); + }; + let assignment = add_fallback(pg, assignment, def); + let op = pg.o(Op::Assign(*input, assignment)); + pg.node_mut(id).actions[*action_idx].op = op; + } else { + let assignment = assignment_from_reaching_def(pg, def); + let op = pg.o(Op::Assign(*input, assignment)); + pg.push_action(id, Action::new(pg.true_id(), op)); + } } } } diff --git a/protocols/src/ir/reaching_defs.rs b/protocols/src/ir/reaching_defs.rs index 2fcf38fe..de9a6923 100644 --- a/protocols/src/ir/reaching_defs.rs +++ b/protocols/src/ir/reaching_defs.rs @@ -25,20 +25,33 @@ pub type ReachingDefs = FxHashMap>; /// Nodes only store their outgoing transitions. This function precomputes and returns /// the in-neighbors for each node that is reachable from the entry. -fn predecessors(pg: &ProtoGraph) -> FxHashMap> { - let mut predecessors: FxHashMap> = FxHashMap::default(); +pub(crate) fn reachable_nodes(pg: &ProtoGraph) -> FxHashSet { + let mut reachable = FxHashSet::default(); let mut q = vec![pg.entry]; - let mut visited: FxHashSet = FxHashSet::default(); while let Some(n) = q.pop() { - visited.insert(n); + if !reachable.insert(n) { + continue; + } - // n is a predecessor of t for t in pg[n].clone().transitions { - predecessors.entry(t.target).or_default().push(n); + q.push(t.target); + } + } + + reachable +} - if !visited.contains(&t.target) { - q.push(t.target); +fn predecessors( + pg: &ProtoGraph, + reachable: &FxHashSet, +) -> FxHashMap> { + let mut predecessors: FxHashMap> = FxHashMap::default(); + + for n in reachable { + for t in pg[n].clone().transitions { + if reachable.contains(&t.target) { + predecessors.entry(t.target).or_default().push((*n, t.guard)); } } } @@ -52,7 +65,8 @@ pub fn reaching_definitions( pg: &mut ProtoGraph, st: &SymbolTable, ) -> FxHashMap { - let preds = predecessors(pg); + let reachable = reachable_nodes(pg); + let preds = predecessors(pg, &reachable); let dut = pg.type_param.expect("protocol has no DUT"); let input_ports: Vec = st @@ -81,19 +95,35 @@ pub fn reaching_definitions( let mut out_defs: FxHashMap = FxHashMap::default(); - let mut worklist: Vec = pg.nodes().map(|(id, _)| id).collect(); + let mut worklist: Vec = reachable.iter().copied().collect(); while let Some(id) = worklist.pop() { // run the merge function with its predecessors to get in_defs[n] // merge function is just the union let mut merged = ReachingDefs::default(); - for pred_id in preds.get(&id).into_iter().flatten() { + for (pred_id, transition_guard) in preds.get(&id).into_iter().flatten() { if let Some(pred_defs) = out_defs.get(pred_id) { for (symbol_id, values) in pred_defs { - merged - .entry(*symbol_id) - .or_default() - .extend(values.iter().copied()); + for def in values { + let and_guard = pg.and_guard(def.guard, *transition_guard); + let guard = match check_sat(pg, and_guard) { + SatResult::DefinitelyUnsat => continue, + SatResult::DefinitelySat => pg.true_id(), + SatResult::MaybeSat => { + let not_def_guard = pg.not_guard(def.guard); + let transition_without_def = + pg.and_guard(*transition_guard, not_def_guard); + match check_sat(pg, transition_without_def) { + SatResult::DefinitelyUnsat => pg.true_id(), + SatResult::DefinitelySat | SatResult::MaybeSat => def.guard, + } + } + }; + merged.entry(*symbol_id).or_default().insert(AssignmentDef { + guard, + value: def.value, + }); + } } } } @@ -116,7 +146,10 @@ pub fn reaching_definitions( let mut out = in_defs.get(&id).cloned().unwrap_or_default(); for (symbol_id, assignment) in assignments { - // if an assignment is definitely satisfiable, then remove all the other assignments + let old_defs = out.remove(&symbol_id).unwrap_or_default(); + let mut new_defs = FxHashSet::default(); + let mut coverage = assignment.dont_care; + match check_sat(pg, assignment.dont_care) { SatResult::DefinitelySat => { out.insert( @@ -129,7 +162,7 @@ pub fn reaching_definitions( continue; } SatResult::MaybeSat => { - out.entry(symbol_id).or_default().insert(AssignmentDef { + new_defs.insert(AssignmentDef { guard: assignment.dont_care, value: AssignmentValue::DontCare, }); @@ -137,27 +170,50 @@ pub fn reaching_definitions( SatResult::DefinitelyUnsat => (), } + let mut prior = assignment.dont_care; for (guard, val) in assignment.concretes { - match check_sat(pg, guard) { + let effective_guard = { + let not_prior = pg.not_guard(prior); + pg.and_guard(not_prior, guard) + }; + + match check_sat(pg, effective_guard) { SatResult::DefinitelySat => { - out.insert( - symbol_id, - FxHashSet::from_iter([AssignmentDef { - guard: pg.true_id(), - value: AssignmentValue::Concrete(val), - }]), - ); + new_defs = FxHashSet::from_iter([AssignmentDef { + guard: pg.true_id(), + value: AssignmentValue::Concrete(val), + }]); + coverage = pg.true_id(); break; } SatResult::MaybeSat => { - out.entry(symbol_id).or_default().insert(AssignmentDef { - guard, + new_defs.insert(AssignmentDef { + guard: effective_guard, value: AssignmentValue::Concrete(val), }); } SatResult::DefinitelyUnsat => (), } + + prior = pg.or_guard(prior, guard); + coverage = pg.or_guard(coverage, guard); + } + + let not_covered = pg.not_guard(coverage); + for old_def in old_defs { + let fallback_guard = pg.and_guard(old_def.guard, not_covered); + let fallback_guard = match check_sat(pg, fallback_guard) { + SatResult::DefinitelyUnsat => continue, + SatResult::DefinitelySat => pg.true_id(), + SatResult::MaybeSat => fallback_guard, + }; + new_defs.insert(AssignmentDef { + guard: fallback_guard, + value: old_def.value, + }); } + + out.insert(symbol_id, new_defs); } if out_defs.get(&id) != Some(&out) { @@ -208,7 +264,9 @@ pub fn all_ports_present( .filter(|sym_id| st[*sym_id].is_in_port()) .collect(); - // all ports present + let reachable = reachable_nodes(pg); + + // all reachable nodes have all ports present for rd in reaching_defs.values() { for input in input_ports { if !rd.contains_key(&input) { @@ -217,8 +275,8 @@ pub fn all_ports_present( } } - // all nodes present - for (id, _) in pg.nodes() { + // all reachable nodes present + for id in reachable { if !reaching_defs.contains_key(&id) { return false; } From 23a50a8e49c5e0601c7ac11e6108fbcfa496516c Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Sat, 4 Jul 2026 15:55:13 -0400 Subject: [PATCH 07/12] handle wveform tests --- graph-interp/src/main.rs | 17 ++++++++++++++--- protocols/src/ir/determinize.rs | 11 +++++++---- ...is_truncated_include_idle_send_data.snap.new | 4 ++-- ...ts__ir_graphviz_bounded_ready_valid.snap.new | 2 +- ...raphviz__tests__ir_graphviz_counter.snap.new | 4 ++-- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/graph-interp/src/main.rs b/graph-interp/src/main.rs index c949b8c0..27f74808 100644 --- a/graph-interp/src/main.rs +++ b/graph-interp/src/main.rs @@ -134,7 +134,7 @@ fn run_classic( if cli.contract_edges { for (_, graph) in &mut graphs { contract_edges(graph, st); - + // if !exists_conflicts(&rd, graph) && all_ports_present(&rd, graph, st) { // propagate_assignments(graph, st, &rd); // } else { @@ -158,8 +158,8 @@ fn run_classic( .unwrap_or_else(|| panic!("unknown protocol {name}")); let rd = reaching_definitions(pg, st); - propagate_assignments(pg, st, &rd); - + propagate_assignments(pg, st, &rd); + let args = build_arg_map(&pg.args, st, values); graph_interpreter::interpret(pg, st, args, &mut sim); } @@ -184,6 +184,11 @@ fn run_respect_forks( let mut joint = lower_trace_to_ir(trace, &protos_by_name, st); + if cli.graphout { + println!("// joint graph for trace {trace_index}"); + println!("{}", to_dot_string(&joint, st)); + } + if cli.determinize { joint = determinized(joint, st); } @@ -206,10 +211,16 @@ fn run_respect_forks( propagate_assignments(&mut joint, &st, &rd); // println!("blah"); + if cli.graphout { + println!("// joint graph for trace {trace_index}"); + println!("{}", to_dot_string(&joint, st)); + } + // args are baked into the graph as constants, so we just pass in an empty map here let waveform = graph_interpreter::interpret(&joint, st, FxHashMap::default(), &mut sim); if cli.ascii_waveform { print_trace_success(trace_index); + // println!("ascii"); print_ascii_waveform( waveform, |port| sim.port_name(port).to_string(), diff --git a/protocols/src/ir/determinize.rs b/protocols/src/ir/determinize.rs index f6bb35a3..96317920 100644 --- a/protocols/src/ir/determinize.rs +++ b/protocols/src/ir/determinize.rs @@ -113,13 +113,16 @@ pub fn determinized(protocol: ProtoGraph, symbols: &SymbolTable) -> ProtoGraph { guard = protocol.and_guard(guard, lit); } - match check_sat(&mut protocol, guard) { - SatResult::DefinitelyUnsat | SatResult::DefinitelySat => continue, - SatResult::MaybeSat => { + let guard = match check_sat(&mut protocol, guard) { + SatResult::DefinitelyUnsat => continue, + SatResult::DefinitelySat => protocol.true_id(), + SatResult::MaybeSat => guard, + }; + + { let target_id = get_or_create_state(targets, &mut state_ids, &mut worklist, &mut new_nodes); new_trans.push(Transition::new(guard, target_id, true)); - } } } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new index 6288b124..86eea5ea 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new @@ -75,7 +75,7 @@ digraph "send_data" { node0 [label="[1] D.m_axis_tdata := data if 1\n[1] D.m_axis_tvalid := 1 if 1"]; node0 -> node1 [label="D.m_axis_tready / step"]; node0 -> node4 [label="not(D.m_axis_tready) / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1; 1 if 1\n[1] D.m_axis_tdata := X if 1; data if 1"]; node4 [label="[1] D.m_axis_tvalid := 1 if 1\n[1] D.m_axis_tdata := data if 1"]; node4 -> node4 [label="not(D.m_axis_tready) / step"]; node4 -> node1 [label="D.m_axis_tready / step"]; @@ -130,5 +130,5 @@ digraph "idle" { entry_marker -> node0; node0 [label="[1] D.m_axis_tvalid := 0 if 1\n[1] D.m_axis_tdata := X if 1"]; node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1; 0 if 1\n[1] D.m_axis_tdata := X if 1"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new index 36e26d03..e41b9e78 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new @@ -103,7 +103,7 @@ digraph "send_data" { node0 [label="[1] D.data := data if 1\n[1] D.valid := 1 if 1"]; node0 -> node1 [label="D.ready / step"]; node0 -> node8 [label="not(D.ready) / step"]; - node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; + node1 [label="[1] done\n[1] D.valid := X if 1; 1 if 1\n[1] D.data := X if 1; data if 1"]; node8 [label="[1] D.valid := 1 if 1\n[1] D.data := data if 1"]; node8 -> node5 [label="not(D.ready) / step"]; node8 -> node1 [label="D.ready / step"]; diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new index ded0a2ab..3df785fe 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new @@ -90,7 +90,7 @@ digraph "count_up" { node7 [label="[1] dut.a := a if 1"]; node7 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; node7 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1"]; + node1 [label="[1] done\n[1] dut.a := X if 1; a if 1"]; } == pre-contract == @@ -184,5 +184,5 @@ digraph "count_up_extra_assertion" { node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)\n[1] dut.a := a if 1"]; node8 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; node8 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1"]; + node1 [label="[1] done\n[1] dut.a := X if 1; a if 1"]; } From 34ecaf49ee1397514b49dea22e120208286ba0e6 Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Mon, 6 Jul 2026 09:42:51 -0400 Subject: [PATCH 08/12] modify the reaching def analysis --- graph-interp/src/main.rs | 9 +- protocols/src/ir/determinize.rs | 6 +- protocols/src/ir/edge_contract.rs | 17 +- protocols/src/ir/graph_interpreter.rs | 1 + protocols/src/ir/graphviz.rs | 7 +- protocols/src/ir/propagate_assigns.rs | 102 ++--- protocols/src/ir/proto_graph.rs | 2 +- protocols/src/ir/reaching_defs.rs | 406 +++++++++++------- ...aphviz__tests__ir_graphviz_add_d0.snap.new | 102 ++++- ..._truncated_include_idle_send_data.snap.new | 4 +- ...__ir_graphviz_bounded_ready_valid.snap.new | 2 +- ...phviz__tests__ir_graphviz_counter.snap.new | 4 +- 12 files changed, 410 insertions(+), 252 deletions(-) diff --git a/graph-interp/src/main.rs b/graph-interp/src/main.rs index 27f74808..d6736ed5 100644 --- a/graph-interp/src/main.rs +++ b/graph-interp/src/main.rs @@ -11,12 +11,12 @@ use protocols::ir::edge_contract::{contract_edges, normalize_assignments}; use protocols::ir::graph_interpreter; use protocols::ir::graphviz::to_dot_string; use protocols::ir::lowering::lower_ast_to_ir; +use protocols::ir::propagate_assigns::propagate_assignments; use protocols::ir::proto_graph::ProtoGraph; -use protocols::ir::reaching_defs::{all_ports_present, exists_conflicts, reaching_definitions}; +use protocols::ir::reaching_defs::{all_ports_present, format_reaching_defs, reaching_definitions}; use protocols::ir::trace_lowering::lower_trace_to_ir; use protocols::{PatronusSim, Value, frontend, transaction_frontend}; use rustc_hash::FxHashMap; -use protocols::ir::propagate_assigns::propagate_assignments; #[derive(Parser, Debug)] struct Cli { @@ -200,10 +200,9 @@ fn run_respect_forks( } let rd = reaching_definitions(&mut joint, &st); - if exists_conflicts(&rd, &joint) { - println!("bleh") + if cli.reaching_definitions { + println!("{}", format_reaching_defs(&joint, st, &rd)); } - if !all_ports_present(&rd, &joint, &st) { println!("bleh bleh") } diff --git a/protocols/src/ir/determinize.rs b/protocols/src/ir/determinize.rs index 96317920..a89de052 100644 --- a/protocols/src/ir/determinize.rs +++ b/protocols/src/ir/determinize.rs @@ -120,9 +120,9 @@ pub fn determinized(protocol: ProtoGraph, symbols: &SymbolTable) -> ProtoGraph { }; { - let target_id = - get_or_create_state(targets, &mut state_ids, &mut worklist, &mut new_nodes); - new_trans.push(Transition::new(guard, target_id, true)); + let target_id = + get_or_create_state(targets, &mut state_ids, &mut worklist, &mut new_nodes); + new_trans.push(Transition::new(guard, target_id, true)); } } diff --git a/protocols/src/ir/edge_contract.rs b/protocols/src/ir/edge_contract.rs index c624294f..ff0f07ac 100644 --- a/protocols/src/ir/edge_contract.rs +++ b/protocols/src/ir/edge_contract.rs @@ -25,7 +25,8 @@ fn symbol_expr(protocol: &mut ProtoGraph, symbols: &SymbolTable, symbol_id: Symb expr } -fn same_assignment_target(symbols: &SymbolTable, lhs: SymbolId, rhs: SymbolId) -> bool { +/// TODO: Better way to deal with this? +pub fn same_assignment_target(symbols: &SymbolTable, lhs: SymbolId, rhs: SymbolId) -> bool { lhs == rhs || symbols[lhs].full_name(symbols) == symbols[rhs].full_name(symbols) } @@ -40,7 +41,7 @@ fn record_internal_assert( }); } -pub(crate) fn guard_assignment( +pub fn guard_assignment( protocol: &mut ProtoGraph, assignment: Assignment, guard: ExprRef, @@ -61,7 +62,7 @@ pub(crate) fn guard_assignment( } } -fn concrete_coverage(protocol: &mut ProtoGraph, assignment: &Assignment) -> ExprRef { +pub fn concrete_coverage(protocol: &mut ProtoGraph, assignment: &Assignment) -> ExprRef { let raw_coverage = assignment .concretes .iter() @@ -72,7 +73,7 @@ fn concrete_coverage(protocol: &mut ProtoGraph, assignment: &Assignment) -> Expr protocol.and_guard(not_dont_care, raw_coverage) } -fn effective_concretes( +pub fn effective_concretes( protocol: &mut ProtoGraph, assignment: &Assignment, ) -> Vec<(ExprRef, ExprRef)> { @@ -91,7 +92,7 @@ fn effective_concretes( effective } -fn merge_ordered_assignment( +pub fn merge_ordered_assignment( protocol: &mut ProtoGraph, existing: Assignment, new: Assignment, @@ -112,7 +113,7 @@ fn merge_ordered_assignment( } } -fn merge_unordered_assignment( +pub fn merge_unordered_assignment( protocol: &mut ProtoGraph, internal_assert_guard: &mut Option, existing: Assignment, @@ -145,8 +146,8 @@ fn merge_unordered_assignment( let new_dont_care = protocol.and_guard(not_existing_concretes, new.dont_care); let dont_care = protocol.or_guard(existing_dont_care, new_dont_care); - let mut concretes = existing.concretes; - concretes.extend(new.concretes); + let mut concretes = existing_effective; + concretes.extend(new_effective); Assignment { dont_care, diff --git a/protocols/src/ir/graph_interpreter.rs b/protocols/src/ir/graph_interpreter.rs index 970a031b..48aad4cb 100644 --- a/protocols/src/ir/graph_interpreter.rs +++ b/protocols/src/ir/graph_interpreter.rs @@ -372,6 +372,7 @@ pub fn interpret( if t.consumes_step { record_waveform(&mut waveform, sim, ¤t_inputs, &mut rng); update_value_store(&mut store, pg, &bindings, sim, &mut rng); + // println!("stepping"); sim.step(); } curr = t.target; diff --git a/protocols/src/ir/graphviz.rs b/protocols/src/ir/graphviz.rs index 19bd301b..c055125d 100644 --- a/protocols/src/ir/graphviz.rs +++ b/protocols/src/ir/graphviz.rs @@ -117,7 +117,7 @@ fn format_assignment(protocol: &ProtoGraph, assignment: &Assignment) -> String { } } -pub(crate) fn format_expr(protocol: &ProtoGraph, expr_ref: ExprRef) -> String { +pub fn format_expr(protocol: &ProtoGraph, expr_ref: ExprRef) -> String { if protocol.dont_cares.contains(&expr_ref) { "X".to_string() } else { @@ -202,13 +202,13 @@ mod tests { use crate::frontend::diagnostic::DiagnosticHandler; use crate::ir::edge_contract::{contract_edges, normalize_assignments}; use crate::ir::lowering::lower_ast_to_ir; + use crate::ir::propagate_assigns::propagate_assignments; use crate::ir::reaching_defs::{ - AssignmentValue, ReachingDefs, exists_conflicts, format_reaching_defs, reaching_definitions, + ReachingDefs, exists_conflicts, format_reaching_defs, reaching_definitions, }; use insta::Settings; use rustc_hash::FxHashMap; - use crate::ir::propagate_assigns::propagate_assignments; fn snap(name: &str, filename: &str) { let mut handler = DiagnosticHandler::default(); @@ -243,7 +243,6 @@ mod tests { content += "== post-propagation ==\n"; content += &to_dot_string(&ir, &symbols); content += "\n"; - let mut assignment_normalized_ir = contracted_ir.clone(); normalize_assignments(&mut assignment_normalized_ir, st); diff --git a/protocols/src/ir/propagate_assigns.rs b/protocols/src/ir/propagate_assigns.rs index b01355a0..50991146 100644 --- a/protocols/src/ir/propagate_assigns.rs +++ b/protocols/src/ir/propagate_assigns.rs @@ -1,65 +1,16 @@ use crate::frontend::symbol::{SymbolId, SymbolTable}; -use crate::ir::proto_graph::{Action, Assignment, NodeId, Op, ProtoGraph}; +use crate::ir::edge_contract::{merge_ordered_assignment, same_assignment_target}; +use crate::ir::proto_graph::{Action, NodeId, Op, ProtoGraph}; use crate::ir::reaching_defs::{ - AssignmentDef, AssignmentValue, ReachingDefs, all_ports_present, exists_conflicts, - reachable_nodes, + ReachingDefs, all_ports_present, assignment_is_total, canonicalize_assignment, reachable_nodes, }; -use patronus::expr::ExprRef; use rustc_hash::FxHashMap; -fn raw_coverage(pg: &mut ProtoGraph, assignment: &Assignment) -> ExprRef { - assignment - .concretes - .iter() - .fold(assignment.dont_care, |coverage, (guard, _)| { - pg.or_guard(coverage, *guard) - }) -} - -fn assignment_from_reaching_def(pg: &ProtoGraph, def: AssignmentDef) -> Assignment { - assert_eq!( - def.guard, - pg.true_id(), - "cannot propagate a guarded reaching definition" - ); - - match def.value { - AssignmentValue::DontCare => Assignment::dont_care(pg.true_id()), - AssignmentValue::Concrete(expr) => Assignment::concrete(pg.false_id(), pg.true_id(), expr), - } -} - -fn add_fallback(pg: &mut ProtoGraph, mut assignment: Assignment, def: AssignmentDef) -> Assignment { - assert_eq!( - def.guard, - pg.true_id(), - "cannot propagate a guarded reaching definition" - ); - - match def.value { - AssignmentValue::DontCare => { - let coverage = raw_coverage(pg, &assignment); - let fallback_guard = pg.not_guard(coverage); - assignment.dont_care = pg.or_guard(assignment.dont_care, fallback_guard); - } - AssignmentValue::Concrete(expr) => { - assignment.concretes.push((pg.true_id(), expr)); - } - } - - assignment -} - pub fn propagate_assignments( pg: &mut ProtoGraph, st: &SymbolTable, rd: &FxHashMap, ) { - // TODO: some of the assertions in here are overkill. - assert!( - !exists_conflicts(rd, pg), - "{}", format!("cannot propagate assignments in {} with conflicting reaching definitions", pg.proto_ctx.name), - ); assert!( all_ports_present(rd, pg, st), "cannot propagate assignments unless all ports are present at all nodes" @@ -78,7 +29,7 @@ pub fn propagate_assignments( continue; }; - let assigned: FxHashMap = pg[id] + let assigned: Vec<(SymbolId, usize)> = pg[id] .actions .iter() .enumerate() @@ -89,28 +40,45 @@ pub fn propagate_assignments( .collect(); for input in &input_ports { - let values = node_defs - .get(input) + let fact = node_defs + .iter() + .find(|(symbol_id, _)| same_assignment_target(st, **symbol_id, *input)) + .map(|(_, fact)| fact) .unwrap_or_else(|| panic!("missing reaching definition for {input} at {id}")); - assert_eq!( - values.len(), - 1, - "cannot propagate conflicting reaching definitions for {input} at {id}" - ); - - let def = *values.iter().next().unwrap(); - if let Some(action_idx) = assigned.get(input) { + if let Some((assigned_symbol, action_idx)) = assigned + .iter() + .find(|(assigned_symbol, _)| same_assignment_target(st, *assigned_symbol, *input)) + { let action = pg[id].actions[*action_idx].clone(); let Op::Assign(_, assignment) = pg[action.op].clone() else { unreachable!(); }; - let assignment = add_fallback(pg, assignment, def); - let op = pg.o(Op::Assign(*input, assignment)); + let assignment_total = assignment_is_total(pg, &assignment); + assert!( + !fact.conflict || assignment_total, + "cannot propagate assignments in {} with conflicting reaching definitions", + pg.proto_ctx.name + ); + let assignment = merge_ordered_assignment(pg, fact.assignment.clone(), assignment); + let assignment = canonicalize_assignment(pg, assignment); + assert!( + assignment_is_total(pg, &assignment), + "cannot propagate partial assignment for {input} at {id}" + ); + let op = pg.o(Op::Assign(*assigned_symbol, assignment)); pg.node_mut(id).actions[*action_idx].op = op; } else { - let assignment = assignment_from_reaching_def(pg, def); - let op = pg.o(Op::Assign(*input, assignment)); + assert!( + !fact.conflict, + "cannot propagate assignments in {} with conflicting reaching definitions", + pg.proto_ctx.name + ); + assert!( + assignment_is_total(pg, &fact.assignment), + "cannot propagate partial assignment for {input} at {id}" + ); + let op = pg.o(Op::Assign(*input, fact.assignment.clone())); pg.push_action(id, Action::new(pg.true_id(), op)); } } diff --git a/protocols/src/ir/proto_graph.rs b/protocols/src/ir/proto_graph.rs index 3e23160f..ca2a5ae1 100644 --- a/protocols/src/ir/proto_graph.rs +++ b/protocols/src/ir/proto_graph.rs @@ -281,7 +281,7 @@ impl ProtoGraph { self.nodes.iter() } - pub(crate) fn node_mut(&mut self, node_id: NodeId) -> &mut Node { + pub fn node_mut(&mut self, node_id: NodeId) -> &mut Node { &mut self.nodes[node_id] } diff --git a/protocols/src/ir/reaching_defs.rs b/protocols/src/ir/reaching_defs.rs index de9a6923..2b0ecf19 100644 --- a/protocols/src/ir/reaching_defs.rs +++ b/protocols/src/ir/reaching_defs.rs @@ -4,29 +4,26 @@ use crate::frontend::symbol::{SymbolId, SymbolTable}; use crate::ir::determinize::{SatResult, check_sat}; -use crate::ir::proto_graph::{NodeId, Op, ProtoGraph}; +use crate::ir::edge_contract::{ + merge_ordered_assignment, merge_unordered_assignment, same_assignment_target, +}; +use crate::ir::proto_graph::{Assignment, NodeId, Op, ProtoGraph}; use itertools::Itertools; use patronus::expr::ExprRef; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap; -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub enum AssignmentValue { - DontCare, - Concrete(ExprRef), +#[derive(Clone, PartialEq, Eq)] +pub struct ReachingFact { + pub assignment: Assignment, + pub conflict: bool, } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub struct AssignmentDef { - pub guard: ExprRef, - pub value: AssignmentValue, -} - -pub type ReachingDefs = FxHashMap>; +pub type ReachingDefs = FxHashMap; /// Nodes only store their outgoing transitions. This function precomputes and returns /// the in-neighbors for each node that is reachable from the entry. -pub(crate) fn reachable_nodes(pg: &ProtoGraph) -> FxHashSet { - let mut reachable = FxHashSet::default(); +pub fn reachable_nodes(pg: &ProtoGraph) -> rustc_hash::FxHashSet { + let mut reachable = rustc_hash::FxHashSet::default(); let mut q = vec![pg.entry]; while let Some(n) = q.pop() { @@ -44,14 +41,17 @@ pub(crate) fn reachable_nodes(pg: &ProtoGraph) -> FxHashSet { fn predecessors( pg: &ProtoGraph, - reachable: &FxHashSet, + reachable: &rustc_hash::FxHashSet, ) -> FxHashMap> { let mut predecessors: FxHashMap> = FxHashMap::default(); for n in reachable { for t in pg[n].clone().transitions { if reachable.contains(&t.target) { - predecessors.entry(t.target).or_default().push((*n, t.guard)); + predecessors + .entry(t.target) + .or_default() + .push((*n, t.guard)); } } } @@ -59,6 +59,181 @@ fn predecessors( predecessors } +fn simplify_guard(pg: &mut ProtoGraph, guard: ExprRef) -> ExprRef { + let (expr_ctx, simplifier) = (&mut pg.expr_ctx, &mut pg.simplifier); + simplifier.simplify(expr_ctx, guard) +} + +fn empty_assignment(pg: &ProtoGraph) -> Assignment { + Assignment { + dont_care: pg.false_id(), + concretes: Vec::new(), + } +} + +fn is_empty_assignment(pg: &ProtoGraph, assignment: &Assignment) -> bool { + assignment.dont_care == pg.false_id() && assignment.concretes.is_empty() +} + +pub fn canonicalize_assignment(pg: &mut ProtoGraph, assignment: Assignment) -> Assignment { + let dont_care = simplify_guard(pg, assignment.dont_care); + if dont_care == pg.true_id() { + return Assignment { + dont_care, + concretes: Vec::new(), + }; + } + + let mut prior = dont_care; + let mut concretes: Vec<(ExprRef, ExprRef)> = Vec::new(); + + for (guard, rhs) in assignment.concretes { + let guard = simplify_guard(pg, guard); + let not_prior = pg.not_guard(prior); + let effective_guard = pg.and_guard(not_prior, guard); + let effective_guard = simplify_guard(pg, effective_guard); + + match check_sat(pg, effective_guard) { + SatResult::DefinitelyUnsat => {} + SatResult::DefinitelySat => { + merge_concrete_guard(pg, &mut concretes, pg.true_id(), rhs); + break; + } + SatResult::MaybeSat => { + merge_concrete_guard(pg, &mut concretes, effective_guard, rhs); + let next_prior = pg.or_guard(prior, effective_guard); + prior = simplify_guard(pg, next_prior); + } + } + + if prior == pg.true_id() { + break; + } + } + + Assignment { + dont_care, + concretes, + } +} + +fn merge_concrete_guard( + pg: &mut ProtoGraph, + concretes: &mut Vec<(ExprRef, ExprRef)>, + guard: ExprRef, + rhs: ExprRef, +) { + if let Some((existing_guard, _)) = concretes + .iter_mut() + .find(|(_, existing_rhs)| *existing_rhs == rhs) + { + let merged_guard = pg.or_guard(*existing_guard, guard); + *existing_guard = simplify_guard(pg, merged_guard); + } else { + concretes.push((guard, rhs)); + } +} + +fn assignment_coverage(pg: &mut ProtoGraph, assignment: &Assignment) -> ExprRef { + let coverage = assignment + .concretes + .iter() + .fold(assignment.dont_care, |coverage, (guard, _)| { + pg.or_guard(coverage, *guard) + }); + simplify_guard(pg, coverage) +} + +pub fn assignment_is_total(pg: &mut ProtoGraph, assignment: &Assignment) -> bool { + let coverage = assignment_coverage(pg, assignment); + match check_sat(pg, coverage) { + SatResult::DefinitelySat => true, + SatResult::DefinitelyUnsat | SatResult::MaybeSat => false, + } +} + +fn restrict_branch_to_edge( + pg: &mut ProtoGraph, + branch_guard: ExprRef, + transition_guard: ExprRef, +) -> Option { + let overlap = pg.and_guard(branch_guard, transition_guard); + let overlap = simplify_guard(pg, overlap); + match check_sat(pg, overlap) { + SatResult::DefinitelyUnsat => return None, + SatResult::DefinitelySat | SatResult::MaybeSat => {} + } + + let not_branch = pg.not_guard(branch_guard); + let transition_without_branch = pg.and_guard(transition_guard, not_branch); + match check_sat(pg, transition_without_branch) { + SatResult::DefinitelyUnsat => Some(pg.true_id()), + SatResult::DefinitelySat | SatResult::MaybeSat => Some(overlap), + } +} + +fn restrict_assignment_to_edge( + pg: &mut ProtoGraph, + assignment: Assignment, + transition_guard: ExprRef, +) -> Assignment { + let assignment = canonicalize_assignment(pg, assignment); + let dont_care = restrict_branch_to_edge(pg, assignment.dont_care, transition_guard) + .unwrap_or(pg.false_id()); + let concretes = assignment + .concretes + .into_iter() + .filter_map(|(guard, rhs)| { + restrict_branch_to_edge(pg, guard, transition_guard).map(|guard| (guard, rhs)) + }) + .collect(); + + canonicalize_assignment( + pg, + Assignment { + dont_care, + concretes, + }, + ) +} + +fn merge_fact(pg: &mut ProtoGraph, existing: ReachingFact, new: ReachingFact) -> ReachingFact { + let mut internal_assert_guard = None; + let assignment = merge_unordered_assignment( + pg, + &mut internal_assert_guard, + existing.assignment, + new.assignment, + ); + let has_conflict = internal_assert_guard + .map(|guard| !matches!(check_sat(pg, guard), SatResult::DefinitelyUnsat)) + .unwrap_or(false); + + ReachingFact { + assignment: canonicalize_assignment(pg, assignment), + conflict: existing.conflict || new.conflict || has_conflict, + } +} + +fn transfer_fact( + pg: &mut ProtoGraph, + existing: ReachingFact, + assignment: Assignment, +) -> ReachingFact { + let assignment_is_total = assignment_is_total(pg, &assignment); + let assignment = merge_ordered_assignment(pg, existing.assignment, assignment); + ReachingFact { + assignment: canonicalize_assignment(pg, assignment), + conflict: existing.conflict && !assignment_is_total, + } +} + +fn matching_key(defs: &ReachingDefs, st: &SymbolTable, symbol_id: SymbolId) -> Option { + defs.keys() + .copied() + .find(|key| same_assignment_target(st, *key, symbol_id)) +} + pub fn reaching_definitions( // it's a bit of a shame that we have to pass a mutable graph for an analysis, but the // sat checker mutates expressions by simplifying them @@ -75,7 +250,6 @@ pub fn reaching_definitions( .filter(|sym_id| st[*sym_id].is_in_port()) .collect(); - // by default, the entry node begins with let mut in_defs: FxHashMap = FxHashMap::default(); in_defs.insert( pg.entry, @@ -84,56 +258,49 @@ pub fn reaching_definitions( .map(|sym_id| { ( *sym_id, - FxHashSet::from_iter([AssignmentDef { - guard: pg.true_id(), - value: AssignmentValue::DontCare, - }]), + ReachingFact { + assignment: Assignment::dont_care(pg.true_id()), + conflict: false, + }, ) }) .collect(), ); let mut out_defs: FxHashMap = FxHashMap::default(); - let mut worklist: Vec = reachable.iter().copied().collect(); while let Some(id) = worklist.pop() { - // run the merge function with its predecessors to get in_defs[n] - // merge function is just the union let mut merged = ReachingDefs::default(); for (pred_id, transition_guard) in preds.get(&id).into_iter().flatten() { if let Some(pred_defs) = out_defs.get(pred_id) { - for (symbol_id, values) in pred_defs { - for def in values { - let and_guard = pg.and_guard(def.guard, *transition_guard); - let guard = match check_sat(pg, and_guard) { - SatResult::DefinitelyUnsat => continue, - SatResult::DefinitelySat => pg.true_id(), - SatResult::MaybeSat => { - let not_def_guard = pg.not_guard(def.guard); - let transition_without_def = - pg.and_guard(*transition_guard, not_def_guard); - match check_sat(pg, transition_without_def) { - SatResult::DefinitelyUnsat => pg.true_id(), - SatResult::DefinitelySat | SatResult::MaybeSat => def.guard, - } - } - }; - merged.entry(*symbol_id).or_default().insert(AssignmentDef { - guard, - value: def.value, - }); + for (symbol_id, fact) in pred_defs { + let assignment = + restrict_assignment_to_edge(pg, fact.assignment.clone(), *transition_guard); + if is_empty_assignment(pg, &assignment) { + continue; } + + let edge_fact = ReachingFact { + assignment, + conflict: fact.conflict, + }; + + let key = matching_key(&merged, st, *symbol_id).unwrap_or(*symbol_id); + merged + .entry(key) + .and_modify(|existing| { + *existing = merge_fact(pg, existing.clone(), edge_fact.clone()); + }) + .or_insert(edge_fact); } } } - // in defs for the entry is predefined + if id != pg.entry { in_defs.insert(id, merged); } - // run the transfer function on it to get out_defs[n] - // TODO: I wonder if this is more confusing than just having a regular loop let assignments = pg[id] .actions .iter() @@ -146,74 +313,12 @@ pub fn reaching_definitions( let mut out = in_defs.get(&id).cloned().unwrap_or_default(); for (symbol_id, assignment) in assignments { - let old_defs = out.remove(&symbol_id).unwrap_or_default(); - let mut new_defs = FxHashSet::default(); - let mut coverage = assignment.dont_care; - - match check_sat(pg, assignment.dont_care) { - SatResult::DefinitelySat => { - out.insert( - symbol_id, - FxHashSet::from_iter([AssignmentDef { - guard: pg.true_id(), - value: AssignmentValue::DontCare, - }]), - ); - continue; - } - SatResult::MaybeSat => { - new_defs.insert(AssignmentDef { - guard: assignment.dont_care, - value: AssignmentValue::DontCare, - }); - } - SatResult::DefinitelyUnsat => (), - } - - let mut prior = assignment.dont_care; - for (guard, val) in assignment.concretes { - let effective_guard = { - let not_prior = pg.not_guard(prior); - pg.and_guard(not_prior, guard) - }; - - match check_sat(pg, effective_guard) { - SatResult::DefinitelySat => { - new_defs = FxHashSet::from_iter([AssignmentDef { - guard: pg.true_id(), - value: AssignmentValue::Concrete(val), - }]); - coverage = pg.true_id(); - break; - } - SatResult::MaybeSat => { - new_defs.insert(AssignmentDef { - guard: effective_guard, - value: AssignmentValue::Concrete(val), - }); - } - SatResult::DefinitelyUnsat => (), - } - - prior = pg.or_guard(prior, guard); - coverage = pg.or_guard(coverage, guard); - } - - let not_covered = pg.not_guard(coverage); - for old_def in old_defs { - let fallback_guard = pg.and_guard(old_def.guard, not_covered); - let fallback_guard = match check_sat(pg, fallback_guard) { - SatResult::DefinitelyUnsat => continue, - SatResult::DefinitelySat => pg.true_id(), - SatResult::MaybeSat => fallback_guard, - }; - new_defs.insert(AssignmentDef { - guard: fallback_guard, - value: old_def.value, - }); - } - - out.insert(symbol_id, new_defs); + let key = matching_key(&out, st, symbol_id).unwrap_or(symbol_id); + let existing = out.remove(&key).unwrap_or_else(|| ReachingFact { + assignment: empty_assignment(pg), + conflict: false, + }); + out.insert(key, transfer_fact(pg, existing, assignment)); } if out_defs.get(&id) != Some(&out) { @@ -233,19 +338,16 @@ pub fn reaching_definitions( in_defs } -/// Returns `true` if any port at any node has more than one reaching assignment, -/// or if the unique reaching assignment is not unconditional. -pub fn exists_conflicts(reaching_defs: &FxHashMap, pg: &ProtoGraph) -> bool { +/// Returns `true` if any port at any node has conflicting reaching assignments. +pub fn exists_conflicts( + reaching_defs: &FxHashMap, + _pg: &mut ProtoGraph, +) -> bool { for rd in reaching_defs.values() { - for set in rd.values() { - if set.len() > 1 { + for fact in rd.values() { + if fact.conflict { return true; } - if let Some(def) = set.iter().next() { - if def.guard != pg.true_id() { - return true; - } - } } } @@ -257,8 +359,7 @@ pub fn all_ports_present( pg: &ProtoGraph, st: &SymbolTable, ) -> bool { - // TODO: maybe make this a helper in pg context or something - let input_ports: &Vec = &st + let input_ports: Vec = st .get_children(&pg.proto_ctx.type_param.unwrap()) .into_iter() .filter(|sym_id| st[*sym_id].is_in_port()) @@ -266,16 +367,17 @@ pub fn all_ports_present( let reachable = reachable_nodes(pg); - // all reachable nodes have all ports present for rd in reaching_defs.values() { - for input in input_ports { - if !rd.contains_key(&input) { + for input in &input_ports { + if !rd + .keys() + .any(|symbol_id| same_assignment_target(st, *symbol_id, *input)) + { return false; } } } - // all reachable nodes present for id in reachable { if !reaching_defs.contains_key(&id) { return false; @@ -303,16 +405,13 @@ pub fn format_reaching_defs( let mut symbol_defs = defs.iter().collect::>(); symbol_defs.sort_by_key(|(symbol_id, _)| symbols.full_name_from_symbol_id(symbol_id)); - for (symbol_id, values) in symbol_defs { - let mut values = values - .iter() - .map(|def| format_assignment_def(protocol, *def)) - .collect::>(); - values.sort(); + for (symbol_id, fact) in symbol_defs { + let conflict = if fact.conflict { " conflict" } else { "" }; out.push_str(&format!( - " {}: {}\n", + " {}: {}{}\n", symbols.full_name_from_symbol_id(symbol_id), - values.join(", ") + format_assignment(protocol, &fact.assignment), + conflict )); } } @@ -320,15 +419,24 @@ pub fn format_reaching_defs( out } -fn format_assignment_def(protocol: &ProtoGraph, def: AssignmentDef) -> String { - let value = match def.value { - AssignmentValue::DontCare => "X".to_string(), - AssignmentValue::Concrete(expr) => crate::ir::graphviz::format_expr(protocol, expr), - }; - - if def.guard == protocol.true_id() { - value +fn format_assignment(protocol: &ProtoGraph, assignment: &Assignment) -> String { + let mut parts = Vec::new(); + if assignment.dont_care != protocol.false_id() { + parts.push(format!( + "X if {}", + crate::ir::graphviz::format_expr(protocol, assignment.dont_care) + )); + } + for (guard, rhs) in &assignment.concretes { + parts.push(format!( + "{} if {}", + crate::ir::graphviz::format_expr(protocol, *rhs), + crate::ir::graphviz::format_expr(protocol, *guard) + )); + } + if parts.is_empty() { + "internal_assert_false".to_string() } else { - format!("{value} if {}", crate::ir::graphviz::format_expr(protocol, def.guard)) + parts.join("; ") } } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new index 31279017..48df73d0 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new @@ -1,6 +1,6 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 247 +assertion_line: 255 expression: content --- == pre-contract == @@ -43,8 +43,34 @@ digraph "add_combinational_illegal_observation_in_conditional" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } -== reaching-defs == -false +== post-propagation == +digraph "add_combinational_illegal_observation_in_conditional" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] DUT.a := X if 1"]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := X if 1"]; + node8 -> node7 [label="1"]; + node7 [label="[1] DUT.a := a if 1"]; + node7 -> node6 [label="1"]; + node6 [label=""]; + node6 -> node4 [label="eq(DUT.s, 0)"]; + node6 -> node5 [label="not(eq(DUT.s, 0))"]; + node4 [label=""]; + node4 -> node3 [label="1"]; + node5 [label=""]; + node5 -> node3 [label="1"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + == post-normalize == digraph "add_combinational_illegal_observation_in_conditional" { rankdir=LR; @@ -89,8 +115,27 @@ digraph "add_combinational_illegal_observation_in_assertion" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } -== reaching-defs == -false +== post-propagation == +digraph "add_combinational_illegal_observation_in_assertion" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node6 [label="1"]; + node6 [label="[1] DUT.a := X if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] DUT.a := a if 1"]; + node4 -> node3 [label="1"]; + node3 [label="[1] assert_eq(DUT.s, s)"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + == post-normalize == digraph "add_combinational_illegal_observation_in_assertion" { rankdir=LR; @@ -136,15 +181,35 @@ digraph "add_combinational_legal_observation_illegal_assignment" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } -== reaching-defs == -true +== post-propagation == +digraph "add_combinational_legal_observation_illegal_assignment" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node6 [label="1"]; + node6 [label=""]; + node6 -> node4 [label="eq(DUT.s, 0)"]; + node6 -> node5 [label="not(eq(DUT.s, 0))"]; + node4 [label="[1] DUT.a := a if 1"]; + node4 -> node3 [label="1"]; + node5 [label=""]; + node5 -> node3 [label="1"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + == post-normalize == digraph "add_combinational_legal_observation_illegal_assignment" { rankdir=LR; node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a if eq(DUT.s, 0)\n[1] DUT.b := DUT.b if 1"]; + node0 [label="[1] DUT.a := X if not(eq(DUT.s, 0)); a if eq(DUT.s, 0)\n[1] DUT.b := X if 1"]; node0 -> node1 [label="eq(DUT.s, 0) / step"]; node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; @@ -180,8 +245,25 @@ digraph "add_combinational" { node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } -== reaching-defs == -false +== post-propagation == +digraph "add_combinational" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node5 [label="1"]; + node5 [label="[1] DUT.a := a if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] DUT.b := b if 1"]; + node4 -> node3 [label="1"]; + node3 [label="[1] assert_eq(s, DUT.s)"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + == post-normalize == digraph "add_combinational" { rankdir=LR; diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new index 86eea5ea..6288b124 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new @@ -75,7 +75,7 @@ digraph "send_data" { node0 [label="[1] D.m_axis_tdata := data if 1\n[1] D.m_axis_tvalid := 1 if 1"]; node0 -> node1 [label="D.m_axis_tready / step"]; node0 -> node4 [label="not(D.m_axis_tready) / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1; 1 if 1\n[1] D.m_axis_tdata := X if 1; data if 1"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; node4 [label="[1] D.m_axis_tvalid := 1 if 1\n[1] D.m_axis_tdata := data if 1"]; node4 -> node4 [label="not(D.m_axis_tready) / step"]; node4 -> node1 [label="D.m_axis_tready / step"]; @@ -130,5 +130,5 @@ digraph "idle" { entry_marker -> node0; node0 [label="[1] D.m_axis_tvalid := 0 if 1\n[1] D.m_axis_tdata := X if 1"]; node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1; 0 if 1\n[1] D.m_axis_tdata := X if 1"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new index e41b9e78..36e26d03 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new @@ -103,7 +103,7 @@ digraph "send_data" { node0 [label="[1] D.data := data if 1\n[1] D.valid := 1 if 1"]; node0 -> node1 [label="D.ready / step"]; node0 -> node8 [label="not(D.ready) / step"]; - node1 [label="[1] done\n[1] D.valid := X if 1; 1 if 1\n[1] D.data := X if 1; data if 1"]; + node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; node8 [label="[1] D.valid := 1 if 1\n[1] D.data := data if 1"]; node8 -> node5 [label="not(D.ready) / step"]; node8 -> node1 [label="D.ready / step"]; diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new index 3df785fe..ded0a2ab 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new @@ -90,7 +90,7 @@ digraph "count_up" { node7 [label="[1] dut.a := a if 1"]; node7 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; node7 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1; a if 1"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; } == pre-contract == @@ -184,5 +184,5 @@ digraph "count_up_extra_assertion" { node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)\n[1] dut.a := a if 1"]; node8 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; node8 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1; a if 1"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; } From 374788a4499dcd26a5970f8124dd140f43b64de8 Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Mon, 6 Jul 2026 16:15:39 -0400 Subject: [PATCH 09/12] switch to returning and using out_defs --- graph-interp/src/main.rs | 6 +- protocols/src/ir/graphviz.rs | 5 +- protocols/src/ir/propagate_assigns.rs | 96 +++++++++++++-------------- protocols/src/ir/reaching_defs.rs | 51 +++++--------- 4 files changed, 64 insertions(+), 94 deletions(-) diff --git a/graph-interp/src/main.rs b/graph-interp/src/main.rs index d6736ed5..d54dab3c 100644 --- a/graph-interp/src/main.rs +++ b/graph-interp/src/main.rs @@ -13,7 +13,7 @@ use protocols::ir::graphviz::to_dot_string; use protocols::ir::lowering::lower_ast_to_ir; use protocols::ir::propagate_assigns::propagate_assignments; use protocols::ir::proto_graph::ProtoGraph; -use protocols::ir::reaching_defs::{all_ports_present, format_reaching_defs, reaching_definitions}; +use protocols::ir::reaching_defs::{format_reaching_defs, reaching_definitions}; use protocols::ir::trace_lowering::lower_trace_to_ir; use protocols::{PatronusSim, Value, frontend, transaction_frontend}; use rustc_hash::FxHashMap; @@ -203,12 +203,8 @@ fn run_respect_forks( if cli.reaching_definitions { println!("{}", format_reaching_defs(&joint, st, &rd)); } - if !all_ports_present(&rd, &joint, &st) { - println!("bleh bleh") - } propagate_assignments(&mut joint, &st, &rd); - // println!("blah"); if cli.graphout { println!("// joint graph for trace {trace_index}"); diff --git a/protocols/src/ir/graphviz.rs b/protocols/src/ir/graphviz.rs index c055125d..d6a4d476 100644 --- a/protocols/src/ir/graphviz.rs +++ b/protocols/src/ir/graphviz.rs @@ -203,11 +203,8 @@ mod tests { use crate::ir::edge_contract::{contract_edges, normalize_assignments}; use crate::ir::lowering::lower_ast_to_ir; use crate::ir::propagate_assigns::propagate_assignments; - use crate::ir::reaching_defs::{ - ReachingDefs, exists_conflicts, format_reaching_defs, reaching_definitions, - }; + use crate::ir::reaching_defs::reaching_definitions; use insta::Settings; - use rustc_hash::FxHashMap; fn snap(name: &str, filename: &str) { diff --git a/protocols/src/ir/propagate_assigns.rs b/protocols/src/ir/propagate_assigns.rs index 50991146..ddb89ba3 100644 --- a/protocols/src/ir/propagate_assigns.rs +++ b/protocols/src/ir/propagate_assigns.rs @@ -1,8 +1,8 @@ use crate::frontend::symbol::{SymbolId, SymbolTable}; -use crate::ir::edge_contract::{merge_ordered_assignment, same_assignment_target}; +use crate::ir::edge_contract::same_assignment_target; use crate::ir::proto_graph::{Action, NodeId, Op, ProtoGraph}; use crate::ir::reaching_defs::{ - ReachingDefs, all_ports_present, assignment_is_total, canonicalize_assignment, reachable_nodes, + ReachingDefs, all_ports_present, assignment_is_total, reachable_nodes, }; use rustc_hash::FxHashMap; @@ -29,58 +29,54 @@ pub fn propagate_assignments( continue; }; - let assigned: Vec<(SymbolId, usize)> = pg[id] - .actions - .iter() - .enumerate() - .filter_map(|(idx, action)| match pg[action.op] { - Op::Assign(sid, _) => Some((sid, idx)), - _ => None, - }) - .collect(); + retain_non_input_assignments(pg, st, id, &input_ports); for input in &input_ports { - let fact = node_defs - .iter() - .find(|(symbol_id, _)| same_assignment_target(st, **symbol_id, *input)) - .map(|(_, fact)| fact) + let fact = lookup_fact(node_defs, st, *input) .unwrap_or_else(|| panic!("missing reaching definition for {input} at {id}")); + assert!( + !fact.conflict, + "cannot propagate assignments in {} with conflicting reaching definitions", + pg.proto_ctx.name + ); + assert!( + assignment_is_total(pg, &fact.assignment), + "cannot propagate partial assignment for {input} at {id}" + ); - if let Some((assigned_symbol, action_idx)) = assigned - .iter() - .find(|(assigned_symbol, _)| same_assignment_target(st, *assigned_symbol, *input)) - { - let action = pg[id].actions[*action_idx].clone(); - let Op::Assign(_, assignment) = pg[action.op].clone() else { - unreachable!(); - }; - let assignment_total = assignment_is_total(pg, &assignment); - assert!( - !fact.conflict || assignment_total, - "cannot propagate assignments in {} with conflicting reaching definitions", - pg.proto_ctx.name - ); - let assignment = merge_ordered_assignment(pg, fact.assignment.clone(), assignment); - let assignment = canonicalize_assignment(pg, assignment); - assert!( - assignment_is_total(pg, &assignment), - "cannot propagate partial assignment for {input} at {id}" - ); - let op = pg.o(Op::Assign(*assigned_symbol, assignment)); - pg.node_mut(id).actions[*action_idx].op = op; - } else { - assert!( - !fact.conflict, - "cannot propagate assignments in {} with conflicting reaching definitions", - pg.proto_ctx.name - ); - assert!( - assignment_is_total(pg, &fact.assignment), - "cannot propagate partial assignment for {input} at {id}" - ); - let op = pg.o(Op::Assign(*input, fact.assignment.clone())); - pg.push_action(id, Action::new(pg.true_id(), op)); - } + let op = pg.o(Op::Assign(*input, fact.assignment.clone())); + pg.push_action(id, Action::new(pg.true_id(), op)); } } } + +fn lookup_fact<'a>( + defs: &'a ReachingDefs, + st: &SymbolTable, + input: SymbolId, +) -> Option<&'a crate::ir::reaching_defs::ReachingFact> { + defs.iter() + .find(|(symbol_id, _)| same_assignment_target(st, **symbol_id, input)) + .map(|(_, fact)| fact) +} + +fn retain_non_input_assignments( + pg: &mut ProtoGraph, + st: &SymbolTable, + id: NodeId, + input_ports: &[SymbolId], +) { + let actions = pg[id] + .actions + .iter() + .cloned() + .filter(|action| match pg[action.op] { + Op::Assign(symbol_id, _) => !input_ports + .iter() + .any(|input| same_assignment_target(st, symbol_id, *input)), + _ => true, + }) + .collect(); + + pg.node_mut(id).actions = actions; +} diff --git a/protocols/src/ir/reaching_defs.rs b/protocols/src/ir/reaching_defs.rs index 2b0ecf19..8a16de77 100644 --- a/protocols/src/ir/reaching_defs.rs +++ b/protocols/src/ir/reaching_defs.rs @@ -10,7 +10,7 @@ use crate::ir::edge_contract::{ use crate::ir::proto_graph::{Assignment, NodeId, Op, ProtoGraph}; use itertools::Itertools; use patronus::expr::ExprRef; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; #[derive(Clone, PartialEq, Eq)] pub struct ReachingFact { @@ -20,10 +20,10 @@ pub struct ReachingFact { pub type ReachingDefs = FxHashMap; -/// Nodes only store their outgoing transitions. This function precomputes and returns -/// the in-neighbors for each node that is reachable from the entry. -pub fn reachable_nodes(pg: &ProtoGraph) -> rustc_hash::FxHashSet { - let mut reachable = rustc_hash::FxHashSet::default(); + +// TODO: let's write a pruner before running the analysis? +pub fn reachable_nodes(pg: &ProtoGraph) -> FxHashSet { + let mut reachable = FxHashSet::default(); let mut q = vec![pg.entry]; while let Some(n) = q.pop() { @@ -39,9 +39,11 @@ pub fn reachable_nodes(pg: &ProtoGraph) -> rustc_hash::FxHashSet { reachable } +/// Nodes only store their outgoing transitions. This function precomputes and returns +/// the in-neighbors for each node that is reachable from the entry. fn predecessors( pg: &ProtoGraph, - reachable: &rustc_hash::FxHashSet, + reachable: &FxHashSet, ) -> FxHashMap> { let mut predecessors: FxHashMap> = FxHashMap::default(); @@ -64,16 +66,6 @@ fn simplify_guard(pg: &mut ProtoGraph, guard: ExprRef) -> ExprRef { simplifier.simplify(expr_ctx, guard) } -fn empty_assignment(pg: &ProtoGraph) -> Assignment { - Assignment { - dont_care: pg.false_id(), - concretes: Vec::new(), - } -} - -fn is_empty_assignment(pg: &ProtoGraph, assignment: &Assignment) -> bool { - assignment.dont_care == pg.false_id() && assignment.concretes.is_empty() -} pub fn canonicalize_assignment(pg: &mut ProtoGraph, assignment: Assignment) -> Assignment { let dont_care = simplify_guard(pg, assignment.dont_care); @@ -277,7 +269,9 @@ pub fn reaching_definitions( for (symbol_id, fact) in pred_defs { let assignment = restrict_assignment_to_edge(pg, fact.assignment.clone(), *transition_guard); - if is_empty_assignment(pg, &assignment) { + + // don't insert a ReachingFact for a fully empty assignment + if assignment.dont_care == pg.false_id() && assignment.concretes.is_empty() { continue; } @@ -315,7 +309,10 @@ pub fn reaching_definitions( for (symbol_id, assignment) in assignments { let key = matching_key(&out, st, symbol_id).unwrap_or(symbol_id); let existing = out.remove(&key).unwrap_or_else(|| ReachingFact { - assignment: empty_assignment(pg), + assignment: Assignment { + dont_care: pg.false_id(), + concretes: Vec::new(), + }, conflict: false, }); out.insert(key, transfer_fact(pg, existing, assignment)); @@ -335,23 +332,7 @@ pub fn reaching_definitions( } } - in_defs -} - -/// Returns `true` if any port at any node has conflicting reaching assignments. -pub fn exists_conflicts( - reaching_defs: &FxHashMap, - _pg: &mut ProtoGraph, -) -> bool { - for rd in reaching_defs.values() { - for fact in rd.values() { - if fact.conflict { - return true; - } - } - } - - false + out_defs } pub fn all_ports_present( From fcd1b8d33fc3a08b002f0ce8e3214b01ae011cfc Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Mon, 6 Jul 2026 16:41:18 -0400 Subject: [PATCH 10/12] switch to keying by full port name instead of choosing a canonical symbolid --- graph-interp/src/main.rs | 6 +-- protocols/src/ir/propagate_assigns.rs | 53 ++++++++++++++-------- protocols/src/ir/reaching_defs.rs | 64 +++++---------------------- 3 files changed, 48 insertions(+), 75 deletions(-) diff --git a/graph-interp/src/main.rs b/graph-interp/src/main.rs index d54dab3c..237d208d 100644 --- a/graph-interp/src/main.rs +++ b/graph-interp/src/main.rs @@ -7,7 +7,7 @@ use protocols::frontend::design::{Design, find_a_single_design}; use protocols::frontend::diagnostic::DiagnosticHandler; use protocols::frontend::symbol::SymbolTable; use protocols::ir::determinize::determinized; -use protocols::ir::edge_contract::{contract_edges, normalize_assignments}; +use protocols::ir::edge_contract::contract_edges; use protocols::ir::graph_interpreter; use protocols::ir::graphviz::to_dot_string; use protocols::ir::lowering::lower_ast_to_ir; @@ -199,12 +199,12 @@ fn run_respect_forks( println!("{}", to_dot_string(&joint, st)); } - let rd = reaching_definitions(&mut joint, &st); + let rd = reaching_definitions(&mut joint, st); if cli.reaching_definitions { println!("{}", format_reaching_defs(&joint, st, &rd)); } - propagate_assignments(&mut joint, &st, &rd); + propagate_assignments(&mut joint, st, &rd); if cli.graphout { println!("// joint graph for trace {trace_index}"); diff --git a/protocols/src/ir/propagate_assigns.rs b/protocols/src/ir/propagate_assigns.rs index ddb89ba3..1bb42916 100644 --- a/protocols/src/ir/propagate_assigns.rs +++ b/protocols/src/ir/propagate_assigns.rs @@ -1,11 +1,39 @@ use crate::frontend::symbol::{SymbolId, SymbolTable}; use crate::ir::edge_contract::same_assignment_target; use crate::ir::proto_graph::{Action, NodeId, Op, ProtoGraph}; -use crate::ir::reaching_defs::{ - ReachingDefs, all_ports_present, assignment_is_total, reachable_nodes, -}; +use crate::ir::reaching_defs::{ReachingDefs, assignment_is_total, reachable_nodes}; use rustc_hash::FxHashMap; +fn all_ports_present( + reaching_defs: &FxHashMap, + pg: &ProtoGraph, + st: &SymbolTable, +) -> bool { + let input_ports: Vec = st + .get_children(&pg.proto_ctx.type_param.unwrap()) + .into_iter() + .filter(|sym_id| st[*sym_id].is_in_port()) + .collect(); + + let reachable = reachable_nodes(pg); + + for rd in reaching_defs.values() { + for input in &input_ports { + if !rd.contains_key(&st.full_name_from_symbol_id(input).to_string()) { + return false; + } + } + } + + for id in reachable { + if !reaching_defs.contains_key(&id) { + return false; + } + } + + true +} + pub fn propagate_assignments( pg: &mut ProtoGraph, st: &SymbolTable, @@ -32,7 +60,8 @@ pub fn propagate_assignments( retain_non_input_assignments(pg, st, id, &input_ports); for input in &input_ports { - let fact = lookup_fact(node_defs, st, *input) + let fact = node_defs + .get(&st.full_name_from_symbol_id(input)) .unwrap_or_else(|| panic!("missing reaching definition for {input} at {id}")); assert!( !fact.conflict, @@ -50,16 +79,6 @@ pub fn propagate_assignments( } } -fn lookup_fact<'a>( - defs: &'a ReachingDefs, - st: &SymbolTable, - input: SymbolId, -) -> Option<&'a crate::ir::reaching_defs::ReachingFact> { - defs.iter() - .find(|(symbol_id, _)| same_assignment_target(st, **symbol_id, input)) - .map(|(_, fact)| fact) -} - fn retain_non_input_assignments( pg: &mut ProtoGraph, st: &SymbolTable, @@ -68,14 +87,12 @@ fn retain_non_input_assignments( ) { let actions = pg[id] .actions - .iter() - .cloned() - .filter(|action| match pg[action.op] { + .iter().filter(|&action| match pg[action.op] { Op::Assign(symbol_id, _) => !input_ports .iter() .any(|input| same_assignment_target(st, symbol_id, *input)), _ => true, - }) + }).cloned() .collect(); pg.node_mut(id).actions = actions; diff --git a/protocols/src/ir/reaching_defs.rs b/protocols/src/ir/reaching_defs.rs index 8a16de77..54dd20c0 100644 --- a/protocols/src/ir/reaching_defs.rs +++ b/protocols/src/ir/reaching_defs.rs @@ -4,9 +4,7 @@ use crate::frontend::symbol::{SymbolId, SymbolTable}; use crate::ir::determinize::{SatResult, check_sat}; -use crate::ir::edge_contract::{ - merge_ordered_assignment, merge_unordered_assignment, same_assignment_target, -}; +use crate::ir::edge_contract::{merge_ordered_assignment, merge_unordered_assignment}; use crate::ir::proto_graph::{Assignment, NodeId, Op, ProtoGraph}; use itertools::Itertools; use patronus::expr::ExprRef; @@ -18,8 +16,7 @@ pub struct ReachingFact { pub conflict: bool, } -pub type ReachingDefs = FxHashMap; - +pub type ReachingDefs = FxHashMap; // TODO: let's write a pruner before running the analysis? pub fn reachable_nodes(pg: &ProtoGraph) -> FxHashSet { @@ -66,7 +63,6 @@ fn simplify_guard(pg: &mut ProtoGraph, guard: ExprRef) -> ExprRef { simplifier.simplify(expr_ctx, guard) } - pub fn canonicalize_assignment(pg: &mut ProtoGraph, assignment: Assignment) -> Assignment { let dont_care = simplify_guard(pg, assignment.dont_care); if dont_care == pg.true_id() { @@ -220,12 +216,6 @@ fn transfer_fact( } } -fn matching_key(defs: &ReachingDefs, st: &SymbolTable, symbol_id: SymbolId) -> Option { - defs.keys() - .copied() - .find(|key| same_assignment_target(st, *key, symbol_id)) -} - pub fn reaching_definitions( // it's a bit of a shame that we have to pass a mutable graph for an analysis, but the // sat checker mutates expressions by simplifying them @@ -249,7 +239,7 @@ pub fn reaching_definitions( .iter() .map(|sym_id| { ( - *sym_id, + st.full_name_from_symbol_id(sym_id).to_string(), ReachingFact { assignment: Assignment::dont_care(pg.true_id()), conflict: false, @@ -266,7 +256,7 @@ pub fn reaching_definitions( let mut merged = ReachingDefs::default(); for (pred_id, transition_guard) in preds.get(&id).into_iter().flatten() { if let Some(pred_defs) = out_defs.get(pred_id) { - for (symbol_id, fact) in pred_defs { + for (symbol_name, fact) in pred_defs { let assignment = restrict_assignment_to_edge(pg, fact.assignment.clone(), *transition_guard); @@ -280,9 +270,8 @@ pub fn reaching_definitions( conflict: fact.conflict, }; - let key = matching_key(&merged, st, *symbol_id).unwrap_or(*symbol_id); merged - .entry(key) + .entry(symbol_name.clone()) .and_modify(|existing| { *existing = merge_fact(pg, existing.clone(), edge_fact.clone()); }) @@ -307,7 +296,7 @@ pub fn reaching_definitions( let mut out = in_defs.get(&id).cloned().unwrap_or_default(); for (symbol_id, assignment) in assignments { - let key = matching_key(&out, st, symbol_id).unwrap_or(symbol_id); + let key = st.full_name_from_symbol_id(&symbol_id).to_string(); let existing = out.remove(&key).unwrap_or_else(|| ReachingFact { assignment: Assignment { dont_care: pg.false_id(), @@ -335,42 +324,9 @@ pub fn reaching_definitions( out_defs } -pub fn all_ports_present( - reaching_defs: &FxHashMap, - pg: &ProtoGraph, - st: &SymbolTable, -) -> bool { - let input_ports: Vec = st - .get_children(&pg.proto_ctx.type_param.unwrap()) - .into_iter() - .filter(|sym_id| st[*sym_id].is_in_port()) - .collect(); - - let reachable = reachable_nodes(pg); - - for rd in reaching_defs.values() { - for input in &input_ports { - if !rd - .keys() - .any(|symbol_id| same_assignment_target(st, *symbol_id, *input)) - { - return false; - } - } - } - - for id in reachable { - if !reaching_defs.contains_key(&id) { - return false; - } - } - - true -} - pub fn format_reaching_defs( protocol: &ProtoGraph, - symbols: &SymbolTable, + _symbols: &SymbolTable, reaching_defs: &FxHashMap, ) -> String { let mut out = String::new(); @@ -384,13 +340,13 @@ pub fn format_reaching_defs( }; let mut symbol_defs = defs.iter().collect::>(); - symbol_defs.sort_by_key(|(symbol_id, _)| symbols.full_name_from_symbol_id(symbol_id)); + symbol_defs.sort_by_key(|(symbol_name, _)| *symbol_name); - for (symbol_id, fact) in symbol_defs { + for (symbol_name, fact) in symbol_defs { let conflict = if fact.conflict { " conflict" } else { "" }; out.push_str(&format!( " {}: {}{}\n", - symbols.full_name_from_symbol_id(symbol_id), + symbol_name, format_assignment(protocol, &fact.assignment), conflict )); From a2d9d05b77b999bfa51f6940f426f641f0bd9280 Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Mon, 6 Jul 2026 16:50:17 -0400 Subject: [PATCH 11/12] canonicalization improvements --- graph-interp/src/main.rs | 5 +- protocols/src/ir/graphviz.rs | 11 +- protocols/src/ir/propagate_assigns.rs | 83 ++-- protocols/src/ir/reaching_defs.rs | 252 ++++++---- ...s__frontend__serialize__tests__add_d1.snap | 13 + ...rontend__serialize__tests__add_d1.snap.new | 106 ---- ...__graphviz__tests__ir_graphviz_add_d0.snap | 148 ++++-- ...aphviz__tests__ir_graphviz_add_d0.snap.new | 276 ----------- ...__graphviz__tests__ir_graphviz_add_d1.snap | 435 +++++++++++++--- ...aphviz__tests__ir_graphviz_add_d1.snap.new | 468 ------------------ ...axis_truncated_include_idle_send_data.snap | 68 ++- ..._truncated_include_idle_send_data.snap.new | 134 ----- ...ests__ir_graphviz_bounded_ready_valid.snap | 56 ++- ...__ir_graphviz_bounded_ready_valid.snap.new | 112 ----- ..._graphviz__tests__ir_graphviz_counter.snap | 91 +++- ...phviz__tests__ir_graphviz_counter.snap.new | 188 ------- 16 files changed, 864 insertions(+), 1582 deletions(-) delete mode 100644 protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap.new delete mode 100644 protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new delete mode 100644 protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new delete mode 100644 protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new delete mode 100644 protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new delete mode 100644 protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new diff --git a/graph-interp/src/main.rs b/graph-interp/src/main.rs index 237d208d..9c4aff68 100644 --- a/graph-interp/src/main.rs +++ b/graph-interp/src/main.rs @@ -195,19 +195,20 @@ fn run_respect_forks( // normalize_assignments(&mut joint, st); if cli.graphout { - println!("// joint graph for trace {trace_index}"); + println!("// post-determinization joint graph for trace {trace_index}"); println!("{}", to_dot_string(&joint, st)); } let rd = reaching_definitions(&mut joint, st); if cli.reaching_definitions { + println!("// reaching definitions for trace {trace_index}"); println!("{}", format_reaching_defs(&joint, st, &rd)); } propagate_assignments(&mut joint, st, &rd); if cli.graphout { - println!("// joint graph for trace {trace_index}"); + println!("// post-propagation joint graph for trace {trace_index}"); println!("{}", to_dot_string(&joint, st)); } diff --git a/protocols/src/ir/graphviz.rs b/protocols/src/ir/graphviz.rs index d6a4d476..1013a0ec 100644 --- a/protocols/src/ir/graphviz.rs +++ b/protocols/src/ir/graphviz.rs @@ -228,17 +228,10 @@ mod tests { // run the reaching definitions analysis let reaching_defs = reaching_definitions(&mut ir, &st); - // pretty print the reaching definitions - // content += "== reaching-defs ==\n"; - // // content += &format_reaching_defs(&ir, &symbols, &reaching_defs); - // // content += "\n"; - // content += exists_conflicts(&reaching_defs, &ir).to_string().as_str(); - // content += "\n"; - // print post-propagation of assignments - propagate_assignments(&mut contracted_ir, &symbols, &reaching_defs); + propagate_assignments(&mut contracted_ir, &st, &reaching_defs); content += "== post-propagation ==\n"; - content += &to_dot_string(&ir, &symbols); + content += &to_dot_string(&ir, &st); content += "\n"; let mut assignment_normalized_ir = contracted_ir.clone(); diff --git a/protocols/src/ir/propagate_assigns.rs b/protocols/src/ir/propagate_assigns.rs index 1bb42916..9dcf1bdb 100644 --- a/protocols/src/ir/propagate_assigns.rs +++ b/protocols/src/ir/propagate_assigns.rs @@ -1,9 +1,35 @@ use crate::frontend::symbol::{SymbolId, SymbolTable}; -use crate::ir::edge_contract::same_assignment_target; use crate::ir::proto_graph::{Action, NodeId, Op, ProtoGraph}; -use crate::ir::reaching_defs::{ReachingDefs, assignment_is_total, reachable_nodes}; +use crate::ir::reaching_defs::{ReachingDefs, assignment_is_total}; +use cranelift_entity::EntitySet; use rustc_hash::FxHashMap; +// BFS from the entry +fn reachable_node_ids(pg: &ProtoGraph) -> Vec { + let mut visited: EntitySet = EntitySet::default(); + let mut nodes = Vec::new(); + let mut q = vec![pg.entry]; + + while let Some(nid) = q.pop() { + if !visited.insert(nid) { + continue; + } + + nodes.push(nid); + q.extend( + pg[nid] + .transitions + .iter() + .map(|t| t.target) + .filter(|target| !visited.contains(*target)), + ); + } + + nodes +} + +/// check the invariant that for all nodes reachable from the entry that +/// every node has a reaching definition for every port. fn all_ports_present( reaching_defs: &FxHashMap, pg: &ProtoGraph, @@ -15,9 +41,11 @@ fn all_ports_present( .filter(|sym_id| st[*sym_id].is_in_port()) .collect(); - let reachable = reachable_nodes(pg); + for nid in reachable_node_ids(pg) { + let Some(rd) = reaching_defs.get(&nid) else { + return false; + }; - for rd in reaching_defs.values() { for input in &input_ports { if !rd.contains_key(&st.full_name_from_symbol_id(input).to_string()) { return false; @@ -25,12 +53,6 @@ fn all_ports_present( } } - for id in reachable { - if !reaching_defs.contains_key(&id) { - return false; - } - } - true } @@ -50,50 +72,33 @@ pub fn propagate_assignments( .filter(|sym_id| st[*sym_id].is_in_port()) .collect(); - let node_ids = reachable_nodes(pg).into_iter().collect::>(); + let node_ids = reachable_node_ids(pg); for id in node_ids { let Some(node_defs) = rd.get(&id) else { continue; }; - retain_non_input_assignments(pg, st, id, &input_ports); + let actions = pg[id] + .actions + .iter() + .filter(|&action| !matches!(pg[action.op], Op::Assign(_, _))) + .cloned() + .collect(); + + pg.node_mut(id).actions = actions; for input in &input_ports { - let fact = node_defs + let assignment = node_defs .get(&st.full_name_from_symbol_id(input)) .unwrap_or_else(|| panic!("missing reaching definition for {input} at {id}")); assert!( - !fact.conflict, - "cannot propagate assignments in {} with conflicting reaching definitions", - pg.proto_ctx.name - ); - assert!( - assignment_is_total(pg, &fact.assignment), + assignment_is_total(pg, assignment), "cannot propagate partial assignment for {input} at {id}" ); - let op = pg.o(Op::Assign(*input, fact.assignment.clone())); + let op = pg.o(Op::Assign(*input, assignment.clone())); pg.push_action(id, Action::new(pg.true_id(), op)); } } } - -fn retain_non_input_assignments( - pg: &mut ProtoGraph, - st: &SymbolTable, - id: NodeId, - input_ports: &[SymbolId], -) { - let actions = pg[id] - .actions - .iter().filter(|&action| match pg[action.op] { - Op::Assign(symbol_id, _) => !input_ports - .iter() - .any(|input| same_assignment_target(st, symbol_id, *input)), - _ => true, - }).cloned() - .collect(); - - pg.node_mut(id).actions = actions; -} diff --git a/protocols/src/ir/reaching_defs.rs b/protocols/src/ir/reaching_defs.rs index 54dd20c0..d3a52ec6 100644 --- a/protocols/src/ir/reaching_defs.rs +++ b/protocols/src/ir/reaching_defs.rs @@ -10,48 +10,17 @@ use itertools::Itertools; use patronus::expr::ExprRef; use rustc_hash::{FxHashMap, FxHashSet}; -#[derive(Clone, PartialEq, Eq)] -pub struct ReachingFact { - pub assignment: Assignment, - pub conflict: bool, -} - -pub type ReachingDefs = FxHashMap; - -// TODO: let's write a pruner before running the analysis? -pub fn reachable_nodes(pg: &ProtoGraph) -> FxHashSet { - let mut reachable = FxHashSet::default(); - let mut q = vec![pg.entry]; - - while let Some(n) = q.pop() { - if !reachable.insert(n) { - continue; - } - - for t in pg[n].clone().transitions { - q.push(t.target); - } - } - - reachable -} +// Maps ports (by their `String` name`), to the existing Assignment for that port +pub type ReachingDefs = FxHashMap; /// Nodes only store their outgoing transitions. This function precomputes and returns /// the in-neighbors for each node that is reachable from the entry. -fn predecessors( - pg: &ProtoGraph, - reachable: &FxHashSet, -) -> FxHashMap> { +fn predecessors(pg: &ProtoGraph) -> FxHashMap> { let mut predecessors: FxHashMap> = FxHashMap::default(); - for n in reachable { + for (n, _) in pg.nodes() { for t in pg[n].clone().transitions { - if reachable.contains(&t.target) { - predecessors - .entry(t.target) - .or_default() - .push((*n, t.guard)); - } + predecessors.entry(t.target).or_default().push((n, t.guard)); } } @@ -63,6 +32,8 @@ fn simplify_guard(pg: &mut ProtoGraph, guard: ExprRef) -> ExprRef { simplifier.simplify(expr_ctx, guard) } +// TODO: I feel like this can be done at *merge-time* in edge-contract.rs instead +// TODO: of as a post-processing step pub fn canonicalize_assignment(pg: &mut ProtoGraph, assignment: Assignment) -> Assignment { let dont_care = simplify_guard(pg, assignment.dont_care); if dont_care == pg.true_id() { @@ -105,6 +76,9 @@ pub fn canonicalize_assignment(pg: &mut ProtoGraph, assignment: Assignment) -> A } } +/// if the `rhs` already exists in `concretes` with guard `old_guard` +/// `guard` should be an *effective* guard, so that performing +/// `old_guard || guard` does not violate merge semantics fn merge_concrete_guard( pg: &mut ProtoGraph, concretes: &mut Vec<(ExprRef, ExprRef)>, @@ -122,24 +96,25 @@ fn merge_concrete_guard( } } -fn assignment_coverage(pg: &mut ProtoGraph, assignment: &Assignment) -> ExprRef { +/// An assignment is *total* when at least one of it's guards must trigger. +/// That is, `NOT(dc_guard OR concrete_g1 OR ... OR concrete_gn)` is not satisfiable. +pub fn assignment_is_total(pg: &mut ProtoGraph, assignment: &Assignment) -> bool { let coverage = assignment .concretes .iter() .fold(assignment.dont_care, |coverage, (guard, _)| { pg.or_guard(coverage, *guard) }); - simplify_guard(pg, coverage) -} -pub fn assignment_is_total(pg: &mut ProtoGraph, assignment: &Assignment) -> bool { - let coverage = assignment_coverage(pg, assignment); match check_sat(pg, coverage) { SatResult::DefinitelySat => true, SatResult::DefinitelyUnsat | SatResult::MaybeSat => false, } } +/// Take `branch_guard`, i.e. a guard on an assignment and simplify it to `Some(g)` +/// under the assumption that `transition_guard` is true. If returns `None` +/// then the branch can be removed. fn restrict_branch_to_edge( pg: &mut ProtoGraph, branch_guard: ExprRef, @@ -147,19 +122,29 @@ fn restrict_branch_to_edge( ) -> Option { let overlap = pg.and_guard(branch_guard, transition_guard); let overlap = simplify_guard(pg, overlap); - match check_sat(pg, overlap) { - SatResult::DefinitelyUnsat => return None, - SatResult::DefinitelySat | SatResult::MaybeSat => {} + + // are `transition_guard` and `branch_guard` mutually exclusive? + if matches!(check_sat(pg, overlap), SatResult::DefinitelyUnsat) { + return None; } + // does `transition_guard` imply `branch_guard`? + // t => b == NOT t OR b == NOT(t AND not B). so check t AND not B is unsat. let not_branch = pg.not_guard(branch_guard); let transition_without_branch = pg.and_guard(transition_guard, not_branch); - match check_sat(pg, transition_without_branch) { - SatResult::DefinitelyUnsat => Some(pg.true_id()), - SatResult::DefinitelySat | SatResult::MaybeSat => Some(overlap), + if matches!( + check_sat(pg, transition_without_branch), + SatResult::DefinitelyUnsat + ) { + return Some(pg.true_id()); } + + // can't simplify, so we have to return the overlap + Some(overlap) } +/// For each branch of `assignment`, rewrite it under the assumption that +/// `transition_guard` is true. fn restrict_assignment_to_edge( pg: &mut ProtoGraph, assignment: Assignment, @@ -185,53 +170,45 @@ fn restrict_assignment_to_edge( ) } -fn merge_fact(pg: &mut ProtoGraph, existing: ReachingFact, new: ReachingFact) -> ReachingFact { - let mut internal_assert_guard = None; - let assignment = merge_unordered_assignment( - pg, - &mut internal_assert_guard, - existing.assignment, - new.assignment, - ); - let has_conflict = internal_assert_guard - .map(|guard| !matches!(check_sat(pg, guard), SatResult::DefinitelyUnsat)) - .unwrap_or(false); +/// Returns `true` if this assignment might produce multiple different values +fn has_multiple_outcomes(pg: &mut ProtoGraph, assignment: &Assignment) -> bool { + let canonicalized = canonicalize_assignment(pg, assignment.clone()); + canonicalized.concretes.len() + usize::from(canonicalized.dont_care != pg.false_id()) > 1 +} - ReachingFact { - assignment: canonicalize_assignment(pg, assignment), - conflict: existing.conflict || new.conflict || has_conflict, - } +// the binary merge function for reaching definitions analysis is just `merge_unordered_assignment` +fn merge_assignment(pg: &mut ProtoGraph, existing: Assignment, new: Assignment) -> Assignment { + let assignment = merge_unordered_assignment(pg, &mut None, existing, new); + canonicalize_assignment(pg, assignment) } -fn transfer_fact( +// the binary transfer function for reaching definitions analysis is just `merge_ordered_assignment` +fn transfer_assignment( pg: &mut ProtoGraph, - existing: ReachingFact, + existing: Assignment, assignment: Assignment, -) -> ReachingFact { - let assignment_is_total = assignment_is_total(pg, &assignment); - let assignment = merge_ordered_assignment(pg, existing.assignment, assignment); - ReachingFact { - assignment: canonicalize_assignment(pg, assignment), - conflict: existing.conflict && !assignment_is_total, - } +) -> Assignment { + let assignment = merge_ordered_assignment(pg, existing, assignment); + canonicalize_assignment(pg, assignment) } pub fn reaching_definitions( - // it's a bit of a shame that we have to pass a mutable graph for an analysis, but the - // sat checker mutates expressions by simplifying them + // TODO: it's a bit of a shame that we have to pass a mutable graph for an analysis, + // but the SAT checker mutates expressions by simplifying them pg: &mut ProtoGraph, st: &SymbolTable, ) -> FxHashMap { - let reachable = reachable_nodes(pg); - let preds = predecessors(pg, &reachable); + let preds = predecessors(pg); let dut = pg.type_param.expect("protocol has no DUT"); + // TODO: maybe this should be a helper in the SymbolTable let input_ports: Vec = st .get_children(&dut) .into_iter() .filter(|sym_id| st[*sym_id].is_in_port()) .collect(); + // Initially, all port are DontCare. So in_defs[entry] is {sym -> X} for all sym let mut in_defs: FxHashMap = FxHashMap::default(); in_defs.insert( pg.entry, @@ -240,42 +217,41 @@ pub fn reaching_definitions( .map(|sym_id| { ( st.full_name_from_symbol_id(sym_id).to_string(), - ReachingFact { - assignment: Assignment::dont_care(pg.true_id()), - conflict: false, - }, + Assignment::dont_care(pg.true_id()), ) }) .collect(), ); let mut out_defs: FxHashMap = FxHashMap::default(); - let mut worklist: Vec = reachable.iter().copied().collect(); + + // worklist by default is all reachable nodes. + // let mut worklist: Vec = reachable.iter().copied().collect(); + let mut worklist: Vec = pg.nodes().map(|(id, _)| id).collect(); while let Some(id) = worklist.pop() { let mut merged = ReachingDefs::default(); + let mut incoming_conflicts = FxHashSet::default(); for (pred_id, transition_guard) in preds.get(&id).into_iter().flatten() { if let Some(pred_defs) = out_defs.get(pred_id) { - for (symbol_name, fact) in pred_defs { + for (symbol_name, pred_assignment) in pred_defs { let assignment = - restrict_assignment_to_edge(pg, fact.assignment.clone(), *transition_guard); + restrict_assignment_to_edge(pg, pred_assignment.clone(), *transition_guard); - // don't insert a ReachingFact for a fully empty assignment + // don't insert a reaching definition for a fully empty assignment if assignment.dont_care == pg.false_id() && assignment.concretes.is_empty() { continue; } - let edge_fact = ReachingFact { - assignment, - conflict: fact.conflict, - }; - merged .entry(symbol_name.clone()) .and_modify(|existing| { - *existing = merge_fact(pg, existing.clone(), edge_fact.clone()); + *existing = merge_assignment(pg, existing.clone(), assignment.clone()); + if has_multiple_outcomes(pg, existing) { + incoming_conflicts.insert(symbol_name.clone()); + } }) - .or_insert(edge_fact); + .or_insert(assignment); } } } @@ -297,16 +273,19 @@ pub fn reaching_definitions( for (symbol_id, assignment) in assignments { let key = st.full_name_from_symbol_id(&symbol_id).to_string(); - let existing = out.remove(&key).unwrap_or_else(|| ReachingFact { - assignment: Assignment { - dont_care: pg.false_id(), - concretes: Vec::new(), - }, - conflict: false, + let existing = out.remove(&key).unwrap_or_else(|| Assignment { + dont_care: pg.false_id(), + concretes: Vec::new(), }); - out.insert(key, transfer_fact(pg, existing, assignment)); + incoming_conflicts.remove(&key); + out.insert(key, transfer_assignment(pg, existing, assignment)); } + assert!( + incoming_conflicts.is_empty(), + "reaching definition conflict remains after transfer at {id}" + ); + if out_defs.get(&id) != Some(&out) { out_defs.insert(id, out); @@ -342,13 +321,11 @@ pub fn format_reaching_defs( let mut symbol_defs = defs.iter().collect::>(); symbol_defs.sort_by_key(|(symbol_name, _)| *symbol_name); - for (symbol_name, fact) in symbol_defs { - let conflict = if fact.conflict { " conflict" } else { "" }; + for (symbol_name, assignment) in symbol_defs { out.push_str(&format!( - " {}: {}{}\n", + " {}: {}\n", symbol_name, - format_assignment(protocol, &fact.assignment), - conflict + format_assignment(protocol, assignment), )); } } @@ -377,3 +354,74 @@ fn format_assignment(protocol: &ProtoGraph, assignment: &Assignment) -> String { parts.join("; ") } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::frontend::ast::ProtocolContext; + use crate::frontend::symbol::ROOT_SCOPE; + + fn test_graph() -> ProtoGraph { + ProtoGraph::new(ProtocolContext::new("test".into(), ROOT_SCOPE)) + } + + fn concrete_assignment(pg: &ProtoGraph, guard: ExprRef, rhs: ExprRef) -> Assignment { + Assignment::concrete(pg.false_id(), guard, rhs) + } + + #[test] + fn merge_flags_multiple_reaching_concretes_even_when_guards_are_exclusive() { + let mut pg = test_graph(); + let p = pg.expr_ctx.bv_symbol("p", 1); + let not_p = pg.not_guard(p); + let one = pg.expr_ctx.bv_symbol("one", 1); + let zero = pg.expr_ctx.bv_symbol("zero", 1); + + let from_a = concrete_assignment(&pg, p, one); + let from_b = concrete_assignment(&pg, not_p, zero); + + let merged = merge_assignment(&mut pg, from_a, from_b); + + assert!(has_multiple_outcomes(&mut pg, &merged)); + assert_eq!(merged.dont_care, pg.false_id()); + assert_eq!(merged.concretes.len(), 2); + } + + #[test] + fn merge_flags_maybe_overlapping_reaching_definitions() { + let mut pg = test_graph(); + let p = pg.expr_ctx.bv_symbol("p", 1); + let q = pg.expr_ctx.bv_symbol("q", 1); + let one = pg.expr_ctx.bv_symbol("one", 1); + let zero = pg.expr_ctx.bv_symbol("zero", 1); + + let from_a = concrete_assignment(&pg, p, one); + let from_b = concrete_assignment(&pg, q, zero); + + let merged = merge_assignment(&mut pg, from_a, from_b); + + assert!(has_multiple_outcomes(&mut pg, &merged)); + assert_eq!(merged.dont_care, pg.false_id()); + assert_eq!(merged.concretes.len(), 2); + } + + #[test] + fn total_assignment_kills_incoming_reaching_definition_conflict() { + let mut pg = test_graph(); + let p = pg.expr_ctx.bv_symbol("p", 1); + let q = pg.expr_ctx.bv_symbol("q", 1); + let one = pg.expr_ctx.bv_symbol("one", 1); + let zero = pg.expr_ctx.bv_symbol("zero", 1); + let two = pg.expr_ctx.bv_symbol("two", 1); + + let from_a = concrete_assignment(&pg, p, one); + let from_b = concrete_assignment(&pg, q, zero); + let conflicted = merge_assignment(&mut pg, from_a, from_b); + assert!(has_multiple_outcomes(&mut pg, &conflicted)); + + let reassignment = concrete_assignment(&pg, pg.true_id(), two); + let transferred = transfer_assignment(&mut pg, conflicted, reassignment); + + assert!(assignment_is_total(&mut pg, &transferred)); + } +} diff --git a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap index 3b916297..6c8470fb 100644 --- a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap +++ b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap @@ -76,6 +76,19 @@ prot assign_after_observation(a: u32, b: u32, s: u32) { step(); } +prot better_if_else(a: u32, b: u32) { + DUT.a := a; + DUT.b := b; + step(); + if DUT.s == 32'd0 { + DUT.a := X; + } else { + DUT.a := 32'd5; + } + fork(); + step(); +} + prot if_else(a1: u32, a2: u32) { if 1'd1 { DUT.a := a1; diff --git a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap.new b/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap.new deleted file mode 100644 index ae51103e..00000000 --- a/protocols/src/tests/snapshots/protocols__frontend__serialize__tests__add_d1.snap.new +++ /dev/null @@ -1,106 +0,0 @@ ---- -source: protocols/src/frontend/serialize.rs -assertion_line: 427 -expression: content ---- -struct Adder { - in a: u32, - in b: u32, - out s: u32, -} - -fn add(a: u32, b: u32, s: u32) { - DUT.a := a; - DUT.b := b; - step(); - DUT.a := X; - DUT.b := X; - assert_eq(s, DUT.s); - fork(); - step(); -} - -fn add_fork_early(a: u32, b: u32, s: u32) { - DUT.a := a; - DUT.b := b; - step(); - fork(); - DUT.a := X; - DUT.b := X; - assert_eq(s, DUT.s); - step(); -} - -fn add_incorrect(a: u32, b: u32, s: u32) { - DUT.a := a; - DUT.b := b; - step(); - DUT.a := a; - DUT.b := b; - assert_eq(s, DUT.s); - fork(); - step(); -} - -fn add_incorrect_implicit(a: u32, b: u32, s: u32) { - DUT.a := a; - DUT.b := b; - step(); - assert_eq(s, DUT.s); - fork(); - step(); -} - -fn wait_and_add(a: u32, b: u32, s: u32) { - step(); - fork(); - step(); - DUT.a := a; - DUT.b := b; - step(); - assert_eq(s, DUT.s); - step(); -} - -fn assign_after_observation(a: u32, b: u32, s: u32) { - DUT.a := a; - if DUT.a == 2 { - DUT.a := 0; - } else { - } - DUT.b := b; - step(); - DUT.a := X; - DUT.b := X; - assert_eq(s, DUT.s); - fork(); - step(); -} - -fn better_if_else(a: u32, b: u32) { - DUT.a := a; - DUT.b := b; - step(); - if DUT.s == 0 { - DUT.a := X; - } else { - DUT.a := 5; - } - fork(); - step(); -} - -fn if_else(a1: u32, a2: u32) { - if 1 { - DUT.a := a1; - } else { - DUT.a := a2; - } - DUT.b := 0; - step(); - DUT.a := X; - DUT.b := X; - assert_eq(a1, DUT.s); - fork(); - step(); -} diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap index 2d38ed69..9a17a5ad 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap @@ -10,11 +10,11 @@ digraph "add_combinational_illegal_observation_in_conditional" { entry_marker -> node0; node0 [label=""]; node0 -> node9 [label="1"]; - node9 [label="[1] DUT.a := X"]; + node9 [label="[1] DUT.a := X if 1"]; node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := X"]; + node8 [label="[1] DUT.b := X if 1"]; node8 -> node7 [label="1"]; - node7 [label="[1] DUT.a := a"]; + node7 [label="[1] DUT.a := a if 1"]; node7 -> node6 [label="1"]; node6 [label=""]; node6 -> node4 [label="eq(DUT.s, 0)"]; @@ -27,7 +27,7 @@ digraph "add_combinational_illegal_observation_in_conditional" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -36,10 +36,38 @@ digraph "add_combinational_illegal_observation_in_conditional" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := X"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1"]; node0 -> node1 [label="eq(DUT.s, 0) / step"]; node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "add_combinational_illegal_observation_in_conditional" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] DUT.a := X if 1"]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := X if 1"]; + node8 -> node7 [label="1"]; + node7 [label="[1] DUT.a := a if 1"]; + node7 -> node6 [label="1"]; + node6 [label=""]; + node6 -> node4 [label="eq(DUT.s, 0)"]; + node6 -> node5 [label="not(eq(DUT.s, 0))"]; + node4 [label=""]; + node4 -> node3 [label="1"]; + node5 [label=""]; + node5 -> node3 [label="1"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -48,10 +76,10 @@ digraph "add_combinational_illegal_observation_in_conditional" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := X"]; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node1 [label="eq(DUT.s, 0) / step"]; node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == pre-contract == @@ -62,17 +90,17 @@ digraph "add_combinational_illegal_observation_in_assertion" { entry_marker -> node0; node0 [label=""]; node0 -> node6 [label="1"]; - node6 [label="[1] DUT.a := X"]; + node6 [label="[1] DUT.a := X if 1"]; node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := X"]; + node5 [label="[1] DUT.b := X if 1"]; node5 -> node4 [label="1"]; - node4 [label="[1] DUT.a := a"]; + node4 [label="[1] DUT.a := a if 1"]; node4 -> node3 [label="1"]; node3 [label="[1] assert_eq(DUT.s, s)"]; node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -81,9 +109,30 @@ digraph "add_combinational_illegal_observation_in_assertion" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := X\n[1] assert_eq(DUT.s, s)"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1\n[1] assert_eq(DUT.s, s)"]; node0 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "add_combinational_illegal_observation_in_assertion" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node6 [label="1"]; + node6 [label="[1] DUT.a := X if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] DUT.a := a if 1"]; + node4 -> node3 [label="1"]; + node3 [label="[1] assert_eq(DUT.s, s)"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -92,9 +141,9 @@ digraph "add_combinational_illegal_observation_in_assertion" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := X\n[1] assert_eq(DUT.s, s)"]; + node0 [label="[1] assert_eq(DUT.s, s)\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == pre-contract == @@ -108,7 +157,7 @@ digraph "add_combinational_legal_observation_illegal_assignment" { node6 [label=""]; node6 -> node4 [label="eq(DUT.s, 0)"]; node6 -> node5 [label="not(eq(DUT.s, 0))"]; - node4 [label="[1] DUT.a := a"]; + node4 [label="[1] DUT.a := a if 1"]; node4 -> node3 [label="1"]; node5 [label=""]; node5 -> node3 [label="1"]; @@ -116,7 +165,7 @@ digraph "add_combinational_legal_observation_illegal_assignment" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -125,10 +174,32 @@ digraph "add_combinational_legal_observation_illegal_assignment" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[eq(DUT.s, 0)] DUT.a := a"]; + node0 [label="[1] DUT.a := a if eq(DUT.s, 0)"]; node0 -> node1 [label="eq(DUT.s, 0) / step"]; node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "add_combinational_legal_observation_illegal_assignment" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node6 [label="1"]; + node6 [label=""]; + node6 -> node4 [label="eq(DUT.s, 0)"]; + node6 -> node5 [label="not(eq(DUT.s, 0))"]; + node4 [label="[1] DUT.a := a if 1"]; + node4 -> node3 [label="1"]; + node5 [label=""]; + node5 -> node3 [label="1"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -137,10 +208,10 @@ digraph "add_combinational_legal_observation_illegal_assignment" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[eq(DUT.s, 0)] DUT.a := a\n[1] DUT.b := DUT.b"]; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node1 [label="eq(DUT.s, 0) / step"]; node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == pre-contract == @@ -151,15 +222,15 @@ digraph "add_combinational" { entry_marker -> node0; node0 [label=""]; node0 -> node5 [label="1"]; - node5 [label="[1] DUT.a := a"]; + node5 [label="[1] DUT.a := a if 1"]; node5 -> node4 [label="1"]; - node4 [label="[1] DUT.b := b"]; + node4 [label="[1] DUT.b := b if 1"]; node4 -> node3 [label="1"]; node3 [label="[1] assert_eq(s, DUT.s)"]; node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -168,9 +239,28 @@ digraph "add_combinational" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := b\n[1] assert_eq(s, DUT.s)"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)"]; node0 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "add_combinational" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node5 [label="1"]; + node5 [label="[1] DUT.a := a if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] DUT.b := b if 1"]; + node4 -> node3 [label="1"]; + node3 [label="[1] assert_eq(s, DUT.s)"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -179,7 +269,7 @@ digraph "add_combinational" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := b\n[1] assert_eq(s, DUT.s)"]; + node0 [label="[1] assert_eq(s, DUT.s)\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new deleted file mode 100644 index 48df73d0..00000000 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap.new +++ /dev/null @@ -1,276 +0,0 @@ ---- -source: protocols/src/ir/graphviz.rs -assertion_line: 255 -expression: content ---- -== pre-contract == -digraph "add_combinational_illegal_observation_in_conditional" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node9 [label="1"]; - node9 [label="[1] DUT.a := X if 1"]; - node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := X if 1"]; - node8 -> node7 [label="1"]; - node7 [label="[1] DUT.a := a if 1"]; - node7 -> node6 [label="1"]; - node6 [label=""]; - node6 -> node4 [label="eq(DUT.s, 0)"]; - node6 -> node5 [label="not(eq(DUT.s, 0))"]; - node4 [label=""]; - node4 -> node3 [label="1"]; - node5 [label=""]; - node5 -> node3 [label="1"]; - node3 [label=""]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "add_combinational_illegal_observation_in_conditional" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1"]; - node0 -> node1 [label="eq(DUT.s, 0) / step"]; - node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-propagation == -digraph "add_combinational_illegal_observation_in_conditional" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node9 [label="1"]; - node9 [label="[1] DUT.a := X if 1"]; - node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := X if 1"]; - node8 -> node7 [label="1"]; - node7 [label="[1] DUT.a := a if 1"]; - node7 -> node6 [label="1"]; - node6 [label=""]; - node6 -> node4 [label="eq(DUT.s, 0)"]; - node6 -> node5 [label="not(eq(DUT.s, 0))"]; - node4 [label=""]; - node4 -> node3 [label="1"]; - node5 [label=""]; - node5 -> node3 [label="1"]; - node3 [label=""]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-normalize == -digraph "add_combinational_illegal_observation_in_conditional" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1"]; - node0 -> node1 [label="eq(DUT.s, 0) / step"]; - node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== pre-contract == -digraph "add_combinational_illegal_observation_in_assertion" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node6 [label="1"]; - node6 [label="[1] DUT.a := X if 1"]; - node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := X if 1"]; - node5 -> node4 [label="1"]; - node4 [label="[1] DUT.a := a if 1"]; - node4 -> node3 [label="1"]; - node3 [label="[1] assert_eq(DUT.s, s)"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "add_combinational_illegal_observation_in_assertion" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1\n[1] assert_eq(DUT.s, s)"]; - node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-propagation == -digraph "add_combinational_illegal_observation_in_assertion" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node6 [label="1"]; - node6 [label="[1] DUT.a := X if 1"]; - node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := X if 1"]; - node5 -> node4 [label="1"]; - node4 [label="[1] DUT.a := a if 1"]; - node4 -> node3 [label="1"]; - node3 [label="[1] assert_eq(DUT.s, s)"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-normalize == -digraph "add_combinational_illegal_observation_in_assertion" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1\n[1] assert_eq(DUT.s, s)"]; - node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== pre-contract == -digraph "add_combinational_legal_observation_illegal_assignment" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node6 [label="1"]; - node6 [label=""]; - node6 -> node4 [label="eq(DUT.s, 0)"]; - node6 -> node5 [label="not(eq(DUT.s, 0))"]; - node4 [label="[1] DUT.a := a if 1"]; - node4 -> node3 [label="1"]; - node5 [label=""]; - node5 -> node3 [label="1"]; - node3 [label=""]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "add_combinational_legal_observation_illegal_assignment" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if eq(DUT.s, 0)"]; - node0 -> node1 [label="eq(DUT.s, 0) / step"]; - node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-propagation == -digraph "add_combinational_legal_observation_illegal_assignment" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node6 [label="1"]; - node6 [label=""]; - node6 -> node4 [label="eq(DUT.s, 0)"]; - node6 -> node5 [label="not(eq(DUT.s, 0))"]; - node4 [label="[1] DUT.a := a if 1"]; - node4 -> node3 [label="1"]; - node5 [label=""]; - node5 -> node3 [label="1"]; - node3 [label=""]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-normalize == -digraph "add_combinational_legal_observation_illegal_assignment" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := X if not(eq(DUT.s, 0)); a if eq(DUT.s, 0)\n[1] DUT.b := X if 1"]; - node0 -> node1 [label="eq(DUT.s, 0) / step"]; - node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== pre-contract == -digraph "add_combinational" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node5 [label="1"]; - node5 [label="[1] DUT.a := a if 1"]; - node5 -> node4 [label="1"]; - node4 [label="[1] DUT.b := b if 1"]; - node4 -> node3 [label="1"]; - node3 [label="[1] assert_eq(s, DUT.s)"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "add_combinational" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)"]; - node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-propagation == -digraph "add_combinational" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node5 [label="1"]; - node5 [label="[1] DUT.a := a if 1"]; - node5 -> node4 [label="1"]; - node4 [label="[1] DUT.b := b if 1"]; - node4 -> node3 [label="1"]; - node3 [label="[1] assert_eq(s, DUT.s)"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-normalize == -digraph "add_combinational" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)"]; - node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap index 6420055b..58b86ade 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap @@ -1,6 +1,5 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 212 expression: content --- == pre-contract == @@ -11,15 +10,15 @@ digraph "add" { entry_marker -> node0; node0 [label=""]; node0 -> node9 [label="1"]; - node9 [label="[1] DUT.a := a"]; + node9 [label="[1] DUT.a := a if 1"]; node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := b"]; + node8 [label="[1] DUT.b := b if 1"]; node8 -> node7 [label="1"]; node7 [label=""]; node7 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X"]; + node6 [label="[1] DUT.a := X if 1"]; node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := X"]; + node5 [label="[1] DUT.b := X if 1"]; node5 -> node4 [label="1"]; node4 [label="[1] assert_eq(s, DUT.s)"]; node4 -> node3 [label="1"]; @@ -27,7 +26,7 @@ digraph "add" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -36,11 +35,38 @@ digraph "add" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := b"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X\n[1] DUT.b := X\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; node6 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "add" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] DUT.a := a if 1"]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := b if 1"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := X if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] assert_eq(s, DUT.s)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -49,11 +75,11 @@ digraph "add" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := b"]; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X\n[1] DUT.b := X\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := b if 1"]; node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == pre-contract == @@ -64,23 +90,23 @@ digraph "add_fork_early" { entry_marker -> node0; node0 [label=""]; node0 -> node9 [label="1"]; - node9 [label="[1] DUT.a := a"]; + node9 [label="[1] DUT.a := a if 1"]; node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := b"]; + node8 [label="[1] DUT.b := b if 1"]; node8 -> node7 [label="1"]; node7 [label=""]; node7 -> node6 [label="1 / step"]; node6 [label="[1] fork"]; node6 -> node5 [label="1"]; - node5 [label="[1] DUT.a := X"]; + node5 [label="[1] DUT.a := X if 1"]; node5 -> node4 [label="1"]; - node4 [label="[1] DUT.b := X"]; + node4 [label="[1] DUT.b := X if 1"]; node4 -> node3 [label="1"]; node3 [label="[1] assert_eq(s, DUT.s)"]; node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -89,11 +115,38 @@ digraph "add_fork_early" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := b"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node0 -> node6 [label="1 / step"]; - node6 [label="[1] fork\n[1] DUT.a := X\n[1] DUT.b := X\n[1] assert_eq(s, DUT.s)"]; + node6 [label="[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)"]; node6 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "add_fork_early" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] DUT.a := a if 1"]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := b if 1"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] fork"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.a := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] DUT.b := X if 1"]; + node4 -> node3 [label="1"]; + node3 [label="[1] assert_eq(s, DUT.s)"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -102,11 +155,11 @@ digraph "add_fork_early" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := b"]; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node6 [label="1 / step"]; - node6 [label="[1] fork\n[1] DUT.a := X\n[1] DUT.b := X\n[1] assert_eq(s, DUT.s)"]; + node6 [label="[1] fork\n[1] assert_eq(s, DUT.s)\n[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == pre-contract == @@ -117,15 +170,15 @@ digraph "add_incorrect" { entry_marker -> node0; node0 [label=""]; node0 -> node9 [label="1"]; - node9 [label="[1] DUT.a := a"]; + node9 [label="[1] DUT.a := a if 1"]; node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := b"]; + node8 [label="[1] DUT.b := b if 1"]; node8 -> node7 [label="1"]; node7 [label=""]; node7 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a"]; + node6 [label="[1] DUT.a := a if 1"]; node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := b"]; + node5 [label="[1] DUT.b := b if 1"]; node5 -> node4 [label="1"]; node4 [label="[1] assert_eq(s, DUT.s)"]; node4 -> node3 [label="1"]; @@ -133,7 +186,7 @@ digraph "add_incorrect" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -142,11 +195,38 @@ digraph "add_incorrect" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := b"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a\n[1] DUT.b := b\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; node6 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "add_incorrect" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] DUT.a := a if 1"]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := b if 1"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := a if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := b if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] assert_eq(s, DUT.s)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -155,11 +235,11 @@ digraph "add_incorrect" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := b"]; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a\n[1] DUT.b := b\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == pre-contract == @@ -170,9 +250,9 @@ digraph "add_incorrect_implicit" { entry_marker -> node0; node0 [label=""]; node0 -> node7 [label="1"]; - node7 [label="[1] DUT.a := a"]; + node7 [label="[1] DUT.a := a if 1"]; node7 -> node6 [label="1"]; - node6 [label="[1] DUT.b := b"]; + node6 [label="[1] DUT.b := b if 1"]; node6 -> node5 [label="1"]; node5 [label=""]; node5 -> node4 [label="1 / step"]; @@ -182,7 +262,7 @@ digraph "add_incorrect_implicit" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -191,11 +271,34 @@ digraph "add_incorrect_implicit" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := b"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node0 -> node4 [label="1 / step"]; node4 [label="[1] assert_eq(s, DUT.s)\n[1] fork"]; node4 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "add_incorrect_implicit" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node7 [label="1"]; + node7 [label="[1] DUT.a := a if 1"]; + node7 -> node6 [label="1"]; + node6 [label="[1] DUT.b := b if 1"]; + node6 -> node5 [label="1"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node4 [label="[1] assert_eq(s, DUT.s)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -204,11 +307,11 @@ digraph "add_incorrect_implicit" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a\n[1] DUT.b := b"]; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node4 [label="1 / step"]; - node4 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node4 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node4 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == pre-contract == @@ -225,9 +328,9 @@ digraph "wait_and_add" { node8 -> node7 [label="1"]; node7 [label=""]; node7 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a"]; + node6 [label="[1] DUT.a := a if 1"]; node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := b"]; + node5 [label="[1] DUT.b := b if 1"]; node5 -> node4 [label="1"]; node4 [label=""]; node4 -> node3 [label="1 / step"]; @@ -235,7 +338,7 @@ digraph "wait_and_add" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -248,11 +351,38 @@ digraph "wait_and_add" { node0 -> node8 [label="1 / step"]; node8 [label="[1] fork"]; node8 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a\n[1] DUT.b := b"]; + node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node6 -> node3 [label="1 / step"]; node3 [label="[1] assert_eq(s, DUT.s)"]; node3 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "wait_and_add" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label=""]; + node9 -> node8 [label="1 / step"]; + node8 [label="[1] fork"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := a if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := b if 1"]; + node5 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node3 [label="1 / step"]; + node3 [label="[1] assert_eq(s, DUT.s)"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -261,15 +391,15 @@ digraph "wait_and_add" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node8 [label="1 / step"]; - node8 [label="[1] fork\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node8 [label="[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node8 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a\n[1] DUT.b := b"]; + node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1"]; node6 -> node3 [label="1 / step"]; - node3 [label="[1] assert_eq(s, DUT.s)\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node3 [label="[1] assert_eq(s, DUT.s)\n[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node3 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == pre-contract == @@ -280,24 +410,24 @@ digraph "assign_after_observation" { entry_marker -> node0; node0 [label=""]; node0 -> node13 [label="1"]; - node13 [label="[1] DUT.a := a"]; + node13 [label="[1] DUT.a := a if 1"]; node13 -> node12 [label="1"]; node12 [label=""]; node12 -> node10 [label="eq(DUT.a, 2)"]; node12 -> node11 [label="not(eq(DUT.a, 2))"]; - node10 [label="[1] DUT.a := 0"]; + node10 [label="[1] DUT.a := 0 if 1"]; node10 -> node9 [label="1"]; node11 [label=""]; node11 -> node9 [label="1"]; node9 [label=""]; node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := b"]; + node8 [label="[1] DUT.b := b if 1"]; node8 -> node7 [label="1"]; node7 [label=""]; node7 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X"]; + node6 [label="[1] DUT.a := X if 1"]; node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := X"]; + node5 [label="[1] DUT.b := X if 1"]; node5 -> node4 [label="1"]; node4 [label="[1] assert_eq(s, DUT.s)"]; node4 -> node3 [label="1"]; @@ -305,7 +435,7 @@ digraph "assign_after_observation" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -314,12 +444,48 @@ digraph "assign_after_observation" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := ite(eq(DUT.a, 2), 0, a)\n[1] DUT.b := ite(eq(DUT.a, 2), b, ite(not(eq(DUT.a, 2)), b, DUT.b))"]; + node0 [label="[1] DUT.a := 0 if eq(DUT.a, 2); a if 1\n[1] DUT.b := b if eq(DUT.a, 2); b if not(eq(DUT.a, 2))"]; node0 -> node6 [label="eq(DUT.a, 2) / step"]; node0 -> node6 [label="not(eq(DUT.a, 2)) / step"]; - node6 [label="[1] DUT.a := X\n[1] DUT.b := X\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; node6 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "assign_after_observation" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node13 [label="1"]; + node13 [label="[1] DUT.a := a if 1"]; + node13 -> node12 [label="1"]; + node12 [label=""]; + node12 -> node10 [label="eq(DUT.a, 2)"]; + node12 -> node11 [label="not(eq(DUT.a, 2))"]; + node10 [label="[1] DUT.a := 0 if 1"]; + node10 -> node9 [label="1"]; + node11 [label=""]; + node11 -> node9 [label="1"]; + node9 [label=""]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := b if 1"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := X if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] assert_eq(s, DUT.s)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -328,12 +494,100 @@ digraph "assign_after_observation" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := ite(eq(DUT.a, 2), 0, a)\n[1] DUT.b := ite(eq(DUT.a, 2), b, ite(not(eq(DUT.a, 2)), b, DUT.b))"]; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node6 [label="eq(DUT.a, 2) / step"]; node0 -> node6 [label="not(eq(DUT.a, 2)) / step"]; - node6 [label="[1] DUT.a := X\n[1] DUT.b := X\n[1] assert_eq(s, DUT.s)\n[1] fork"]; + node6 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := b if 1"]; node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== pre-contract == +digraph "better_if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := a if 1"]; + node10 -> node9 [label="1"]; + node9 [label="[1] DUT.b := b if 1"]; + node9 -> node8 [label="1"]; + node8 [label=""]; + node8 -> node7 [label="1 / step"]; + node7 [label=""]; + node7 -> node5 [label="eq(DUT.s, 0)"]; + node7 -> node6 [label="not(eq(DUT.s, 0))"]; + node5 [label="[1] DUT.a := X if 1"]; + node5 -> node4 [label="1"]; + node6 [label="[1] DUT.a := 5 if 1"]; + node6 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-contract == +digraph "better_if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node0 -> node7 [label="1 / step"]; + node7 [label="[1] DUT.a := X if eq(DUT.s, 0); 5 if not(eq(DUT.s, 0))\n[1] fork\n[0] internal_assert_false"]; + node7 -> node1 [label="not(eq(DUT.s, 0)) / step"]; + node7 -> node1 [label="eq(DUT.s, 0) / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "better_if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := a if 1"]; + node10 -> node9 [label="1"]; + node9 [label="[1] DUT.b := b if 1"]; + node9 -> node8 [label="1"]; + node8 [label=""]; + node8 -> node7 [label="1 / step"]; + node7 [label=""]; + node7 -> node5 [label="eq(DUT.s, 0)"]; + node7 -> node6 [label="not(eq(DUT.s, 0))"]; + node5 [label="[1] DUT.a := X if 1"]; + node5 -> node4 [label="1"]; + node6 [label="[1] DUT.a := 5 if 1"]; + node6 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-normalize == +digraph "better_if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 -> node7 [label="1 / step"]; + node7 [label="[1] fork\n[0] internal_assert_false\n[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; + node7 -> node1 [label="not(eq(DUT.s, 0)) / step"]; + node7 -> node1 [label="eq(DUT.s, 0) / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == pre-contract == @@ -346,17 +600,17 @@ digraph "if_else" { node0 -> node12 [label="1"]; node12 [label=""]; node12 -> node10 [label="1"]; - node10 [label="[1] DUT.a := a1"]; + node10 [label="[1] DUT.a := a1 if 1"]; node10 -> node9 [label="1"]; node9 [label=""]; node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := 0"]; + node8 [label="[1] DUT.b := 0 if 1"]; node8 -> node7 [label="1"]; node7 [label=""]; node7 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X"]; + node6 [label="[1] DUT.a := X if 1"]; node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := X"]; + node5 [label="[1] DUT.b := X if 1"]; node5 -> node4 [label="1"]; node4 [label="[1] assert_eq(a1, DUT.s)"]; node4 -> node3 [label="1"]; @@ -364,7 +618,7 @@ digraph "if_else" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-contract == @@ -373,11 +627,42 @@ digraph "if_else" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a1\n[1] DUT.b := 0"]; + node0 [label="[1] DUT.a := a1 if 1\n[1] DUT.b := 0 if 1"]; node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X\n[1] DUT.b := X\n[1] assert_eq(a1, DUT.s)\n[1] fork"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(a1, DUT.s)\n[1] fork"]; node6 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; +} + +== post-propagation == +digraph "if_else" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node12 [label="1"]; + node12 [label=""]; + node12 -> node10 [label="1"]; + node10 [label="[1] DUT.a := a1 if 1"]; + node10 -> node9 [label="1"]; + node9 [label=""]; + node9 -> node8 [label="1"]; + node8 [label="[1] DUT.b := 0 if 1"]; + node8 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node6 [label="1 / step"]; + node6 [label="[1] DUT.a := X if 1"]; + node6 -> node5 [label="1"]; + node5 [label="[1] DUT.b := X if 1"]; + node5 -> node4 [label="1"]; + node4 [label="[1] assert_eq(a1, DUT.s)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } == post-normalize == @@ -386,9 +671,9 @@ digraph "if_else" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a1\n[1] DUT.b := 0"]; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X\n[1] DUT.b := X\n[1] assert_eq(a1, DUT.s)\n[1] fork"]; + node6 [label="[1] assert_eq(a1, DUT.s)\n[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := 0 if 1"]; node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := DUT.a\n[1] DUT.b := DUT.b"]; + node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new deleted file mode 100644 index 3a819af5..00000000 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap.new +++ /dev/null @@ -1,468 +0,0 @@ ---- -source: protocols/src/ir/graphviz.rs -assertion_line: 247 -expression: content ---- -== pre-contract == -digraph "add" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node9 [label="1"]; - node9 [label="[1] DUT.a := a if 1"]; - node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := b if 1"]; - node8 -> node7 [label="1"]; - node7 [label=""]; - node7 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X if 1"]; - node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := X if 1"]; - node5 -> node4 [label="1"]; - node4 [label="[1] assert_eq(s, DUT.s)"]; - node4 -> node3 [label="1"]; - node3 [label="[1] fork"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "add" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; - node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== reaching-defs == -false -== post-normalize == -digraph "add" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; - node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== pre-contract == -digraph "add_fork_early" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node9 [label="1"]; - node9 [label="[1] DUT.a := a if 1"]; - node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := b if 1"]; - node8 -> node7 [label="1"]; - node7 [label=""]; - node7 -> node6 [label="1 / step"]; - node6 [label="[1] fork"]; - node6 -> node5 [label="1"]; - node5 [label="[1] DUT.a := X if 1"]; - node5 -> node4 [label="1"]; - node4 [label="[1] DUT.b := X if 1"]; - node4 -> node3 [label="1"]; - node3 [label="[1] assert_eq(s, DUT.s)"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "add_fork_early" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node0 -> node6 [label="1 / step"]; - node6 [label="[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)"]; - node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== reaching-defs == -false -== post-normalize == -digraph "add_fork_early" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node0 -> node6 [label="1 / step"]; - node6 [label="[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)"]; - node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== pre-contract == -digraph "add_incorrect" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node9 [label="1"]; - node9 [label="[1] DUT.a := a if 1"]; - node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := b if 1"]; - node8 -> node7 [label="1"]; - node7 [label=""]; - node7 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a if 1"]; - node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := b if 1"]; - node5 -> node4 [label="1"]; - node4 [label="[1] assert_eq(s, DUT.s)"]; - node4 -> node3 [label="1"]; - node3 [label="[1] fork"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "add_incorrect" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; - node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== reaching-defs == -false -== post-normalize == -digraph "add_incorrect" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; - node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== pre-contract == -digraph "add_incorrect_implicit" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node7 [label="1"]; - node7 [label="[1] DUT.a := a if 1"]; - node7 -> node6 [label="1"]; - node6 [label="[1] DUT.b := b if 1"]; - node6 -> node5 [label="1"]; - node5 [label=""]; - node5 -> node4 [label="1 / step"]; - node4 [label="[1] assert_eq(s, DUT.s)"]; - node4 -> node3 [label="1"]; - node3 [label="[1] fork"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "add_incorrect_implicit" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node0 -> node4 [label="1 / step"]; - node4 [label="[1] assert_eq(s, DUT.s)\n[1] fork"]; - node4 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== reaching-defs == -false -== post-normalize == -digraph "add_incorrect_implicit" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node0 -> node4 [label="1 / step"]; - node4 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := DUT.a if 1\n[1] DUT.b := DUT.b if 1"]; - node4 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== pre-contract == -digraph "wait_and_add" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node9 [label="1"]; - node9 [label=""]; - node9 -> node8 [label="1 / step"]; - node8 [label="[1] fork"]; - node8 -> node7 [label="1"]; - node7 [label=""]; - node7 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a if 1"]; - node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := b if 1"]; - node5 -> node4 [label="1"]; - node4 [label=""]; - node4 -> node3 [label="1 / step"]; - node3 [label="[1] assert_eq(s, DUT.s)"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "wait_and_add" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node8 [label="1 / step"]; - node8 [label="[1] fork"]; - node8 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node6 -> node3 [label="1 / step"]; - node3 [label="[1] assert_eq(s, DUT.s)"]; - node3 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== reaching-defs == -false -== post-normalize == -digraph "wait_and_add" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := DUT.a if 1\n[1] DUT.b := DUT.b if 1"]; - node0 -> node8 [label="1 / step"]; - node8 [label="[1] fork\n[1] DUT.a := DUT.a if 1\n[1] DUT.b := DUT.b if 1"]; - node8 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node6 -> node3 [label="1 / step"]; - node3 [label="[1] assert_eq(s, DUT.s)\n[1] DUT.a := DUT.a if 1\n[1] DUT.b := DUT.b if 1"]; - node3 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== pre-contract == -digraph "assign_after_observation" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node13 [label="1"]; - node13 [label="[1] DUT.a := a if 1"]; - node13 -> node12 [label="1"]; - node12 [label=""]; - node12 -> node10 [label="eq(DUT.a, 2)"]; - node12 -> node11 [label="not(eq(DUT.a, 2))"]; - node10 [label="[1] DUT.a := 0 if 1"]; - node10 -> node9 [label="1"]; - node11 [label=""]; - node11 -> node9 [label="1"]; - node9 [label=""]; - node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := b if 1"]; - node8 -> node7 [label="1"]; - node7 [label=""]; - node7 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X if 1"]; - node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := X if 1"]; - node5 -> node4 [label="1"]; - node4 [label="[1] assert_eq(s, DUT.s)"]; - node4 -> node3 [label="1"]; - node3 [label="[1] fork"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "assign_after_observation" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := 0 if eq(DUT.a, 2); a if 1\n[1] DUT.b := b if eq(DUT.a, 2); b if not(eq(DUT.a, 2))"]; - node0 -> node6 [label="eq(DUT.a, 2) / step"]; - node0 -> node6 [label="not(eq(DUT.a, 2)) / step"]; - node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; - node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== reaching-defs == -true -== post-normalize == -digraph "assign_after_observation" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := 0 if eq(DUT.a, 2); a if 1\n[1] DUT.b := b if eq(DUT.a, 2); b if not(eq(DUT.a, 2))"]; - node0 -> node6 [label="eq(DUT.a, 2) / step"]; - node0 -> node6 [label="not(eq(DUT.a, 2)) / step"]; - node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(s, DUT.s)\n[1] fork"]; - node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== pre-contract == -digraph "better_if_else" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node10 [label="1"]; - node10 [label="[1] DUT.a := a if 1"]; - node10 -> node9 [label="1"]; - node9 [label="[1] DUT.b := b if 1"]; - node9 -> node8 [label="1"]; - node8 [label=""]; - node8 -> node7 [label="1 / step"]; - node7 [label=""]; - node7 -> node5 [label="eq(DUT.s, 0)"]; - node7 -> node6 [label="not(eq(DUT.s, 0))"]; - node5 [label="[1] DUT.a := X if 1"]; - node5 -> node4 [label="1"]; - node6 [label="[1] DUT.a := 5 if 1"]; - node6 -> node4 [label="1"]; - node4 [label=""]; - node4 -> node3 [label="1"]; - node3 [label="[1] fork"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "better_if_else" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node0 -> node7 [label="1 / step"]; - node7 [label="[1] DUT.a := X if eq(DUT.s, 0); 5 if not(eq(DUT.s, 0))\n[1] fork\n[0] internal_assert_false"]; - node7 -> node1 [label="not(eq(DUT.s, 0)) / step"]; - node7 -> node1 [label="eq(DUT.s, 0) / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== reaching-defs == -true -== post-normalize == -digraph "better_if_else" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; - node0 -> node7 [label="1 / step"]; - node7 [label="[1] DUT.a := X if eq(DUT.s, 0); 5 if not(eq(DUT.s, 0))\n[1] fork\n[0] internal_assert_false\n[1] DUT.b := DUT.b if 1"]; - node7 -> node1 [label="not(eq(DUT.s, 0)) / step"]; - node7 -> node1 [label="eq(DUT.s, 0) / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== pre-contract == -digraph "if_else" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node12 [label="1"]; - node12 [label=""]; - node12 -> node10 [label="1"]; - node10 [label="[1] DUT.a := a1 if 1"]; - node10 -> node9 [label="1"]; - node9 [label=""]; - node9 -> node8 [label="1"]; - node8 [label="[1] DUT.b := 0 if 1"]; - node8 -> node7 [label="1"]; - node7 [label=""]; - node7 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X if 1"]; - node6 -> node5 [label="1"]; - node5 [label="[1] DUT.b := X if 1"]; - node5 -> node4 [label="1"]; - node4 [label="[1] assert_eq(a1, DUT.s)"]; - node4 -> node3 [label="1"]; - node3 [label="[1] fork"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== post-contract == -digraph "if_else" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a1 if 1\n[1] DUT.b := 0 if 1"]; - node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(a1, DUT.s)\n[1] fork"]; - node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} - -== reaching-defs == -true -== post-normalize == -digraph "if_else" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] DUT.a := a1 if 1\n[1] DUT.b := 0 if 1"]; - node0 -> node6 [label="1 / step"]; - node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1\n[1] assert_eq(a1, DUT.s)\n[1] fork"]; - node6 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; -} diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap index 1b747a2e..07134da8 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap @@ -1,6 +1,5 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 212 expression: content --- == pre-contract == @@ -11,9 +10,9 @@ digraph "send_data" { entry_marker -> node0; node0 [label=""]; node0 -> node7 [label="1"]; - node7 [label="[1] D.m_axis_tdata := data"]; + node7 [label="[1] D.m_axis_tdata := data if 1"]; node7 -> node6 [label="1"]; - node6 [label="[1] D.m_axis_tvalid := 1"]; + node6 [label="[1] D.m_axis_tvalid := 1 if 1"]; node6 -> node4 [label="1"]; node4 [label=""]; node4 -> node5 [label="not(D.m_axis_tready)"]; @@ -24,7 +23,7 @@ digraph "send_data" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; } == post-contract == @@ -33,26 +32,50 @@ digraph "send_data" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.m_axis_tdata := data\n[1] D.m_axis_tvalid := 1"]; + node0 [label="[1] D.m_axis_tdata := data if 1\n[1] D.m_axis_tvalid := 1 if 1"]; node0 -> node1 [label="D.m_axis_tready / step"]; node0 -> node4 [label="not(D.m_axis_tready) / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; node4 [label=""]; node4 -> node4 [label="not(D.m_axis_tready) / step"]; node4 -> node1 [label="D.m_axis_tready / step"]; } +== post-propagation == +digraph "send_data" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node7 [label="1"]; + node7 [label="[1] D.m_axis_tdata := data if 1"]; + node7 -> node6 [label="1"]; + node6 [label="[1] D.m_axis_tvalid := 1 if 1"]; + node6 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node5 [label="not(D.m_axis_tready)"]; + node4 -> node3 [label="D.m_axis_tready"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; +} + == post-normalize == digraph "send_data" { rankdir=LR; node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.m_axis_tdata := data\n[1] D.m_axis_tvalid := 1"]; + node0 [label="[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; node0 -> node1 [label="D.m_axis_tready / step"]; node0 -> node4 [label="not(D.m_axis_tready) / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := D.m_axis_tvalid\n[1] D.m_axis_tdata := D.m_axis_tdata"]; - node4 [label="[1] D.m_axis_tvalid := D.m_axis_tvalid\n[1] D.m_axis_tdata := D.m_axis_tdata"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node4 [label="[1] D.m_axis_tvalid := 1 if 1\n[1] D.m_axis_tdata := data if 1"]; node4 -> node4 [label="not(D.m_axis_tready) / step"]; node4 -> node1 [label="D.m_axis_tready / step"]; } @@ -65,11 +88,11 @@ digraph "idle" { entry_marker -> node0; node0 [label=""]; node0 -> node3 [label="1"]; - node3 [label="[1] D.m_axis_tvalid := 0"]; + node3 [label="[1] D.m_axis_tvalid := 0 if 1"]; node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; } == post-contract == @@ -78,9 +101,24 @@ digraph "idle" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.m_axis_tvalid := 0"]; + node0 [label="[1] D.m_axis_tvalid := 0 if 1"]; node0 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; +} + +== post-propagation == +digraph "idle" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node3 [label="1"]; + node3 [label="[1] D.m_axis_tvalid := 0 if 1"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; } == post-normalize == @@ -89,7 +127,7 @@ digraph "idle" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.m_axis_tvalid := 0\n[1] D.m_axis_tdata := D.m_axis_tdata"]; + node0 [label="[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := D.m_axis_tvalid\n[1] D.m_axis_tdata := D.m_axis_tdata"]; + node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new deleted file mode 100644 index 6288b124..00000000 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap.new +++ /dev/null @@ -1,134 +0,0 @@ ---- -source: protocols/src/ir/graphviz.rs -assertion_line: 255 -expression: content ---- -== pre-contract == -digraph "send_data" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node7 [label="1"]; - node7 [label="[1] D.m_axis_tdata := data if 1"]; - node7 -> node6 [label="1"]; - node6 [label="[1] D.m_axis_tvalid := 1 if 1"]; - node6 -> node4 [label="1"]; - node4 [label=""]; - node4 -> node5 [label="not(D.m_axis_tready)"]; - node4 -> node3 [label="D.m_axis_tready"]; - node5 [label=""]; - node5 -> node4 [label="1 / step"]; - node3 [label=""]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; -} - -== post-contract == -digraph "send_data" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] D.m_axis_tdata := data if 1\n[1] D.m_axis_tvalid := 1 if 1"]; - node0 -> node1 [label="D.m_axis_tready / step"]; - node0 -> node4 [label="not(D.m_axis_tready) / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; - node4 [label=""]; - node4 -> node4 [label="not(D.m_axis_tready) / step"]; - node4 -> node1 [label="D.m_axis_tready / step"]; -} - -== post-propagation == -digraph "send_data" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node7 [label="1"]; - node7 [label="[1] D.m_axis_tdata := data if 1"]; - node7 -> node6 [label="1"]; - node6 [label="[1] D.m_axis_tvalid := 1 if 1"]; - node6 -> node4 [label="1"]; - node4 [label=""]; - node4 -> node5 [label="not(D.m_axis_tready)"]; - node4 -> node3 [label="D.m_axis_tready"]; - node5 [label=""]; - node5 -> node4 [label="1 / step"]; - node3 [label=""]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; -} - -== post-normalize == -digraph "send_data" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] D.m_axis_tdata := data if 1\n[1] D.m_axis_tvalid := 1 if 1"]; - node0 -> node1 [label="D.m_axis_tready / step"]; - node0 -> node4 [label="not(D.m_axis_tready) / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; - node4 [label="[1] D.m_axis_tvalid := 1 if 1\n[1] D.m_axis_tdata := data if 1"]; - node4 -> node4 [label="not(D.m_axis_tready) / step"]; - node4 -> node1 [label="D.m_axis_tready / step"]; -} - -== pre-contract == -digraph "idle" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node3 [label="1"]; - node3 [label="[1] D.m_axis_tvalid := 0 if 1"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; -} - -== post-contract == -digraph "idle" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] D.m_axis_tvalid := 0 if 1"]; - node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; -} - -== post-propagation == -digraph "idle" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node3 [label="1"]; - node3 [label="[1] D.m_axis_tvalid := 0 if 1"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; -} - -== post-normalize == -digraph "idle" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] D.m_axis_tvalid := 0 if 1\n[1] D.m_axis_tdata := X if 1"]; - node0 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; -} diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap index 19e366d3..8396815a 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap @@ -1,6 +1,5 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 212 expression: content --- == pre-contract == @@ -11,9 +10,9 @@ digraph "send_data" { entry_marker -> node0; node0 [label=""]; node0 -> node13 [label="1"]; - node13 [label="[1] D.data := data"]; + node13 [label="[1] D.data := data if 1"]; node13 -> node12 [label="1"]; - node12 [label="[1] D.valid := 1"]; + node12 [label="[1] D.valid := 1 if 1"]; node12 -> node11 [label="1"]; node11 [label=""]; node11 -> node9 [label="not(D.ready)"]; @@ -37,7 +36,7 @@ digraph "send_data" { node5 -> node4 [label="1"]; node4 [label=""]; node4 -> node3 [label="1"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; } == post-contract == @@ -46,10 +45,10 @@ digraph "send_data" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.data := data\n[1] D.valid := 1"]; + node0 [label="[1] D.data := data if 1\n[1] D.valid := 1 if 1"]; node0 -> node1 [label="D.ready / step"]; node0 -> node8 [label="not(D.ready) / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; node8 [label=""]; node8 -> node5 [label="not(D.ready) / step"]; node8 -> node1 [label="D.ready / step"]; @@ -57,19 +56,56 @@ digraph "send_data" { node5 -> node1 [label="1 / step"]; } +== post-propagation == +digraph "send_data" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node13 [label="1"]; + node13 [label="[1] D.data := data if 1"]; + node13 -> node12 [label="1"]; + node12 [label="[1] D.valid := 1 if 1"]; + node12 -> node11 [label="1"]; + node11 [label=""]; + node11 -> node9 [label="not(D.ready)"]; + node11 -> node10 [label="D.ready"]; + node9 [label=""]; + node9 -> node8 [label="1 / step"]; + node10 [label=""]; + node10 -> node3 [label="1"]; + node8 [label=""]; + node8 -> node6 [label="not(D.ready)"]; + node8 -> node7 [label="D.ready"]; + node3 [label=""]; + node3 -> node2 [label="1"]; + node6 [label=""]; + node6 -> node5 [label="1 / step"]; + node7 [label=""]; + node7 -> node4 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node5 [label="[1] assert_eq(D.ready, 1)"]; + node5 -> node4 [label="1"]; + node4 [label=""]; + node4 -> node3 [label="1"]; + node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; +} + == post-normalize == digraph "send_data" { rankdir=LR; node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.data := data\n[1] D.valid := 1"]; + node0 [label="[1] D.valid := X if 1\n[1] D.data := X if 1"]; node0 -> node1 [label="D.ready / step"]; node0 -> node8 [label="not(D.ready) / step"]; - node1 [label="[1] done\n[1] D.valid := D.valid\n[1] D.data := D.data"]; - node8 [label="[1] D.valid := D.valid\n[1] D.data := D.data"]; + node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; + node8 [label="[1] D.valid := 1 if 1\n[1] D.data := data if 1"]; node8 -> node5 [label="not(D.ready) / step"]; node8 -> node1 [label="D.ready / step"]; - node5 [label="[1] assert_eq(D.ready, 1)\n[1] D.valid := D.valid\n[1] D.data := D.data"]; + node5 [label="[1] assert_eq(D.ready, 1)\n[1] D.valid := 1 if 1\n[1] D.data := data if 1"]; node5 -> node1 [label="1 / step"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new deleted file mode 100644 index 36e26d03..00000000 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap.new +++ /dev/null @@ -1,112 +0,0 @@ ---- -source: protocols/src/ir/graphviz.rs -assertion_line: 255 -expression: content ---- -== pre-contract == -digraph "send_data" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node13 [label="1"]; - node13 [label="[1] D.data := data if 1"]; - node13 -> node12 [label="1"]; - node12 [label="[1] D.valid := 1 if 1"]; - node12 -> node11 [label="1"]; - node11 [label=""]; - node11 -> node9 [label="not(D.ready)"]; - node11 -> node10 [label="D.ready"]; - node9 [label=""]; - node9 -> node8 [label="1 / step"]; - node10 [label=""]; - node10 -> node3 [label="1"]; - node8 [label=""]; - node8 -> node6 [label="not(D.ready)"]; - node8 -> node7 [label="D.ready"]; - node3 [label=""]; - node3 -> node2 [label="1"]; - node6 [label=""]; - node6 -> node5 [label="1 / step"]; - node7 [label=""]; - node7 -> node4 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node5 [label="[1] assert_eq(D.ready, 1)"]; - node5 -> node4 [label="1"]; - node4 [label=""]; - node4 -> node3 [label="1"]; - node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; -} - -== post-contract == -digraph "send_data" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] D.data := data if 1\n[1] D.valid := 1 if 1"]; - node0 -> node1 [label="D.ready / step"]; - node0 -> node8 [label="not(D.ready) / step"]; - node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; - node8 [label=""]; - node8 -> node5 [label="not(D.ready) / step"]; - node8 -> node1 [label="D.ready / step"]; - node5 [label="[1] assert_eq(D.ready, 1)"]; - node5 -> node1 [label="1 / step"]; -} - -== post-propagation == -digraph "send_data" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node13 [label="1"]; - node13 [label="[1] D.data := data if 1"]; - node13 -> node12 [label="1"]; - node12 [label="[1] D.valid := 1 if 1"]; - node12 -> node11 [label="1"]; - node11 [label=""]; - node11 -> node9 [label="not(D.ready)"]; - node11 -> node10 [label="D.ready"]; - node9 [label=""]; - node9 -> node8 [label="1 / step"]; - node10 [label=""]; - node10 -> node3 [label="1"]; - node8 [label=""]; - node8 -> node6 [label="not(D.ready)"]; - node8 -> node7 [label="D.ready"]; - node3 [label=""]; - node3 -> node2 [label="1"]; - node6 [label=""]; - node6 -> node5 [label="1 / step"]; - node7 [label=""]; - node7 -> node4 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node5 [label="[1] assert_eq(D.ready, 1)"]; - node5 -> node4 [label="1"]; - node4 [label=""]; - node4 -> node3 [label="1"]; - node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; -} - -== post-normalize == -digraph "send_data" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] D.data := data if 1\n[1] D.valid := 1 if 1"]; - node0 -> node1 [label="D.ready / step"]; - node0 -> node8 [label="not(D.ready) / step"]; - node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; - node8 [label="[1] D.valid := 1 if 1\n[1] D.data := data if 1"]; - node8 -> node5 [label="not(D.ready) / step"]; - node8 -> node1 [label="D.ready / step"]; - node5 [label="[1] assert_eq(D.ready, 1)\n[1] D.valid := 1 if 1\n[1] D.data := data if 1"]; - node5 -> node1 [label="1 / step"]; -} diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap index aa174eba..60f9c776 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap @@ -1,6 +1,5 @@ --- source: protocols/src/ir/graphviz.rs -assertion_line: 212 expression: content --- == pre-contract == @@ -11,7 +10,7 @@ digraph "count_up" { entry_marker -> node0; node0 [label=""]; node0 -> node9 [label="1"]; - node9 [label="[1] dut.a := a"]; + node9 [label="[1] dut.a := a if 1"]; node9 -> node7 [label="1"]; node7 [label=""]; node7 -> node8 [label="not(eq(dut.s, dut.a))"]; @@ -28,7 +27,7 @@ digraph "count_up" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; } == post-contract == @@ -37,7 +36,7 @@ digraph "count_up" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] dut.a := a"]; + node0 [label="[1] dut.a := a if 1"]; node0 -> node4 [label="eq(dut.s, dut.a) / step"]; node0 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork"]; @@ -45,7 +44,35 @@ digraph "count_up" { node7 [label=""]; node7 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; node7 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; +} + +== post-propagation == +digraph "count_up" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node9 [label="1"]; + node9 [label="[1] dut.a := a if 1"]; + node9 -> node7 [label="1"]; + node7 [label=""]; + node7 -> node8 [label="not(eq(dut.s, dut.a))"]; + node7 -> node6 [label="eq(dut.s, dut.a)"]; + node8 [label=""]; + node8 -> node7 [label="1 / step"]; + node6 [label=""]; + node6 -> node5 [label="1"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node4 [label="[1] assert_eq(dut.s, 0)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; } == post-normalize == @@ -54,15 +81,15 @@ digraph "count_up" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] dut.a := a"]; + node0 [label="[1] dut.a := X if 1"]; node0 -> node4 [label="eq(dut.s, dut.a) / step"]; node0 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; - node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := dut.a"]; + node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := a if 1"]; node4 -> node1 [label="1 / step"]; - node7 [label="[1] dut.a := dut.a"]; + node7 [label="[1] dut.a := a if 1"]; node7 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; node7 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done\n[1] dut.a := dut.a"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; } == pre-contract == @@ -73,7 +100,7 @@ digraph "count_up_extra_assertion" { entry_marker -> node0; node0 [label=""]; node0 -> node10 [label="1"]; - node10 [label="[1] dut.a := a"]; + node10 [label="[1] dut.a := a if 1"]; node10 -> node8 [label="1"]; node8 [label=""]; node8 -> node9 [label="not(eq(dut.s, dut.a))"]; @@ -92,7 +119,7 @@ digraph "count_up_extra_assertion" { node3 -> node2 [label="1"]; node2 [label=""]; node2 -> node1 [label="1 / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; } == post-contract == @@ -101,7 +128,7 @@ digraph "count_up_extra_assertion" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] dut.a := a\n[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; + node0 [label="[1] dut.a := a if 1\n[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; node0 -> node4 [label="eq(dut.s, dut.a) / step"]; node0 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork"]; @@ -109,7 +136,37 @@ digraph "count_up_extra_assertion" { node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; node8 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; node8 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; +} + +== post-propagation == +digraph "count_up_extra_assertion" { + rankdir=LR; + node [shape=box]; + entry_marker [shape=plain,label="ENTRY"]; + entry_marker -> node0; + node0 [label=""]; + node0 -> node10 [label="1"]; + node10 [label="[1] dut.a := a if 1"]; + node10 -> node8 [label="1"]; + node8 [label=""]; + node8 -> node9 [label="not(eq(dut.s, dut.a))"]; + node8 -> node7 [label="eq(dut.s, dut.a)"]; + node9 [label=""]; + node9 -> node8 [label="1 / step"]; + node7 [label=""]; + node7 -> node6 [label="1"]; + node6 [label="[1] assert_eq(dut.s, a)"]; + node6 -> node5 [label="1"]; + node5 [label=""]; + node5 -> node4 [label="1 / step"]; + node4 [label="[1] assert_eq(dut.s, 0)"]; + node4 -> node3 [label="1"]; + node3 [label="[1] fork"]; + node3 -> node2 [label="1"]; + node2 [label=""]; + node2 -> node1 [label="1 / step"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; } == post-normalize == @@ -118,13 +175,13 @@ digraph "count_up_extra_assertion" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] dut.a := a\n[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; + node0 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)\n[1] dut.a := X if 1"]; node0 -> node4 [label="eq(dut.s, dut.a) / step"]; node0 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; - node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := dut.a"]; + node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := a if 1"]; node4 -> node1 [label="1 / step"]; - node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)\n[1] dut.a := dut.a"]; + node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)\n[1] dut.a := a if 1"]; node8 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; node8 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done\n[1] dut.a := dut.a"]; + node1 [label="[1] done\n[1] dut.a := X if 1"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new deleted file mode 100644 index ded0a2ab..00000000 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap.new +++ /dev/null @@ -1,188 +0,0 @@ ---- -source: protocols/src/ir/graphviz.rs -assertion_line: 255 -expression: content ---- -== pre-contract == -digraph "count_up" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node9 [label="1"]; - node9 [label="[1] dut.a := a if 1"]; - node9 -> node7 [label="1"]; - node7 [label=""]; - node7 -> node8 [label="not(eq(dut.s, dut.a))"]; - node7 -> node6 [label="eq(dut.s, dut.a)"]; - node8 [label=""]; - node8 -> node7 [label="1 / step"]; - node6 [label=""]; - node6 -> node5 [label="1"]; - node5 [label=""]; - node5 -> node4 [label="1 / step"]; - node4 [label="[1] assert_eq(dut.s, 0)"]; - node4 -> node3 [label="1"]; - node3 [label="[1] fork"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1"]; -} - -== post-contract == -digraph "count_up" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] dut.a := a if 1"]; - node0 -> node4 [label="eq(dut.s, dut.a) / step"]; - node0 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; - node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork"]; - node4 -> node1 [label="1 / step"]; - node7 [label=""]; - node7 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; - node7 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1"]; -} - -== post-propagation == -digraph "count_up" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node9 [label="1"]; - node9 [label="[1] dut.a := a if 1"]; - node9 -> node7 [label="1"]; - node7 [label=""]; - node7 -> node8 [label="not(eq(dut.s, dut.a))"]; - node7 -> node6 [label="eq(dut.s, dut.a)"]; - node8 [label=""]; - node8 -> node7 [label="1 / step"]; - node6 [label=""]; - node6 -> node5 [label="1"]; - node5 [label=""]; - node5 -> node4 [label="1 / step"]; - node4 [label="[1] assert_eq(dut.s, 0)"]; - node4 -> node3 [label="1"]; - node3 [label="[1] fork"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1"]; -} - -== post-normalize == -digraph "count_up" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] dut.a := a if 1"]; - node0 -> node4 [label="eq(dut.s, dut.a) / step"]; - node0 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; - node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := a if 1"]; - node4 -> node1 [label="1 / step"]; - node7 [label="[1] dut.a := a if 1"]; - node7 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; - node7 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1"]; -} - -== pre-contract == -digraph "count_up_extra_assertion" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node10 [label="1"]; - node10 [label="[1] dut.a := a if 1"]; - node10 -> node8 [label="1"]; - node8 [label=""]; - node8 -> node9 [label="not(eq(dut.s, dut.a))"]; - node8 -> node7 [label="eq(dut.s, dut.a)"]; - node9 [label=""]; - node9 -> node8 [label="1 / step"]; - node7 [label=""]; - node7 -> node6 [label="1"]; - node6 [label="[1] assert_eq(dut.s, a)"]; - node6 -> node5 [label="1"]; - node5 [label=""]; - node5 -> node4 [label="1 / step"]; - node4 [label="[1] assert_eq(dut.s, 0)"]; - node4 -> node3 [label="1"]; - node3 [label="[1] fork"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1"]; -} - -== post-contract == -digraph "count_up_extra_assertion" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] dut.a := a if 1\n[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; - node0 -> node4 [label="eq(dut.s, dut.a) / step"]; - node0 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; - node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork"]; - node4 -> node1 [label="1 / step"]; - node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; - node8 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; - node8 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1"]; -} - -== post-propagation == -digraph "count_up_extra_assertion" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label=""]; - node0 -> node10 [label="1"]; - node10 [label="[1] dut.a := a if 1"]; - node10 -> node8 [label="1"]; - node8 [label=""]; - node8 -> node9 [label="not(eq(dut.s, dut.a))"]; - node8 -> node7 [label="eq(dut.s, dut.a)"]; - node9 [label=""]; - node9 -> node8 [label="1 / step"]; - node7 [label=""]; - node7 -> node6 [label="1"]; - node6 [label="[1] assert_eq(dut.s, a)"]; - node6 -> node5 [label="1"]; - node5 [label=""]; - node5 -> node4 [label="1 / step"]; - node4 [label="[1] assert_eq(dut.s, 0)"]; - node4 -> node3 [label="1"]; - node3 [label="[1] fork"]; - node3 -> node2 [label="1"]; - node2 [label=""]; - node2 -> node1 [label="1 / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1"]; -} - -== post-normalize == -digraph "count_up_extra_assertion" { - rankdir=LR; - node [shape=box]; - entry_marker [shape=plain,label="ENTRY"]; - entry_marker -> node0; - node0 [label="[1] dut.a := a if 1\n[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; - node0 -> node4 [label="eq(dut.s, dut.a) / step"]; - node0 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; - node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := a if 1"]; - node4 -> node1 [label="1 / step"]; - node8 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)\n[1] dut.a := a if 1"]; - node8 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; - node8 -> node4 [label="eq(dut.s, dut.a) / step"]; - node1 [label="[1] done\n[1] dut.a := X if 1"]; -} From 950d0ff664ee793f73de565924b019ca1aa02c62 Mon Sep 17 00:00:00 2001 From: Nikil Shyamsunder Date: Tue, 7 Jul 2026 21:55:46 -0400 Subject: [PATCH 12/12] Default DontCare assignemnts for each lowered protocol --- protocols/src/ir/edge_contract.rs | 10 ++- protocols/src/ir/graph_interpreter.rs | 14 ++-- protocols/src/ir/graphviz.rs | 10 +-- protocols/src/ir/lowering.rs | 53 ++++++------ protocols/src/ir/propagate_assigns.rs | 7 ++ protocols/src/ir/proto_graph.rs | 62 +++++++++++--- protocols/src/ir/reaching_defs.rs | 25 +----- ...__graphviz__tests__ir_graphviz_add_d0.snap | 42 +++++++--- ...__graphviz__tests__ir_graphviz_add_d1.snap | 80 +++++++++++++------ ...axis_truncated_include_idle_send_data.snap | 24 ++++-- ...ests__ir_graphviz_bounded_ready_valid.snap | 12 ++- ..._graphviz__tests__ir_graphviz_counter.snap | 20 +++-- runt/waveform/runt.toml | 18 ----- scripts/generate_runt_configs.py | 7 +- 14 files changed, 232 insertions(+), 152 deletions(-) diff --git a/protocols/src/ir/edge_contract.rs b/protocols/src/ir/edge_contract.rs index ff0f07ac..96035761 100644 --- a/protocols/src/ir/edge_contract.rs +++ b/protocols/src/ir/edge_contract.rs @@ -476,13 +476,15 @@ mod tests { let mut actions = Vec::new(); let mut internal_assert_guard = None; for (guard, rhs) in [(p, one), (q, two), (r, three)] { - let op = protocol.o(Op::Assign(symbol_id, concrete_assignment(&protocol, rhs))); + let assignment = Assignment::concrete(protocol.false_id(), guard, rhs); + let op = protocol.o(Op::Assign(symbol_id, assignment)); + let true_id = protocol.true_id(); append_action( &mut protocol, &SymbolTable::default(), &mut actions, &mut internal_assert_guard, - Action::new(guard, op), + Action::new(true_id, op), false, ); } @@ -562,7 +564,9 @@ mod tests { // Concrete branches from both unordered sides are preferred over DontCare, but the // original node-local DontCare still suppresses node A's own concrete branch. assert_eq!(expr_str(&protocol, assignment.dont_care), "and(not(r), s)"); - assert_eq!(assignment.concretes, vec![(q, three), (r, four)]); + let not_s = protocol.not_guard(s); + let effective_q = protocol.and_guard(not_s, q); + assert_eq!(assignment.concretes, vec![(effective_q, three), (r, four)]); } #[test] diff --git a/protocols/src/ir/graph_interpreter.rs b/protocols/src/ir/graph_interpreter.rs index 48aad4cb..15810cf2 100644 --- a/protocols/src/ir/graph_interpreter.rs +++ b/protocols/src/ir/graph_interpreter.rs @@ -314,12 +314,14 @@ pub fn interpret( current_inputs.insert(port, WaveValue::Concrete(bvv)); } InputValue::DontCare => { - let width = match st[symbol_id].tpe() { - Type::BitVec(w) => w, - _ => panic!("expected BitVec type for input {symbol_id}"), - }; - let random_val = BitVecValue::random(&mut rng, width); - sim.set(port, &random_val); + if !matches!(current_inputs.get(&port), Some(WaveValue::DontCare)) { + let width = match st[symbol_id].tpe() { + Type::BitVec(w) => w, + _ => panic!("expected BitVec type for input {symbol_id}"), + }; + let random_val = BitVecValue::random(&mut rng, width); + sim.set(port, &random_val); + } current_inputs.insert(port, WaveValue::DontCare); } } diff --git a/protocols/src/ir/graphviz.rs b/protocols/src/ir/graphviz.rs index 1013a0ec..85d096ad 100644 --- a/protocols/src/ir/graphviz.rs +++ b/protocols/src/ir/graphviz.rs @@ -198,14 +198,12 @@ mod tests { use super::*; use crate::frontend; - use super::*; use crate::frontend::diagnostic::DiagnosticHandler; use crate::ir::edge_contract::{contract_edges, normalize_assignments}; use crate::ir::lowering::lower_ast_to_ir; use crate::ir::propagate_assigns::propagate_assignments; use crate::ir::reaching_defs::reaching_definitions; use insta::Settings; - use rustc_hash::FxHashMap; fn snap(name: &str, filename: &str) { let mut handler = DiagnosticHandler::default(); @@ -213,7 +211,7 @@ mod tests { let mut content = String::new(); let st = &ast.st; for proto_ast in ast.protos { - let mut ir: ProtoGraph = lower_ast_to_ir(proto_ast, &st); + let mut ir: ProtoGraph = lower_ast_to_ir(proto_ast, st); content += "== pre-contract ==\n"; content += &to_dot_string(&ir, st); content += "\n"; @@ -226,12 +224,12 @@ mod tests { content += "\n"; // run the reaching definitions analysis - let reaching_defs = reaching_definitions(&mut ir, &st); + let reaching_defs = reaching_definitions(&mut ir, st); // print post-propagation of assignments - propagate_assignments(&mut contracted_ir, &st, &reaching_defs); + propagate_assignments(&mut contracted_ir, st, &reaching_defs); content += "== post-propagation ==\n"; - content += &to_dot_string(&ir, &st); + content += &to_dot_string(&ir, st); content += "\n"; let mut assignment_normalized_ir = contracted_ir.clone(); diff --git a/protocols/src/ir/lowering.rs b/protocols/src/ir/lowering.rs index c1656640..f598f4b7 100644 --- a/protocols/src/ir/lowering.rs +++ b/protocols/src/ir/lowering.rs @@ -277,6 +277,25 @@ impl<'a> Lowerer<'a> { } } + fn add_input_dont_care_assignments(&mut self, ast: &Protocol, node_id: NodeId) { + let dut = ast + .ctx + .type_param + .expect("protocol should have a DUT type parameter"); + + for input in self + .symbols + .get_children(&dut) + .into_iter() + .filter(|sym| self.symbols[*sym].is_in_port()) + { + let assignment = Assignment::dont_care(self.ir.true_id()); + let assign = self.ir.o(Op::Assign(input, assignment)); + self.ir + .push_action(node_id, Action::new(self.ir.true_id(), assign)); + } + } + /// lowers an AST into fresh IR nodes which are unconnected to any existing IR nodes /// If `keep_done`, then the exit node has the Done action. /// Returns the nodes, entry, and exit of the lowered fragment @@ -299,35 +318,15 @@ impl<'a> Lowerer<'a> { } // relinquish all ports in the exit node - let dut = ast - .ctx - .type_param - .expect("protocol should have a DUT type parameter"); - - for input in self - .symbols - .get_children(&dut) - .into_iter() - .filter(|sym| self.symbols[*sym].is_in_port()) - { - let width = match self.symbols[input].tpe() { - FrontType::BitVec(width) => width, - other => panic!("input port type must be a BitVec: {other:?}"), - }; - let rhs = self.dont_care_expr(PatronusType::BV(width)); - let assignment = Assignment::from_rhs( - self.ir.false_id(), - self.ir.true_id(), - rhs, - self.ir.dont_cares.contains(&rhs), - ); - let assign = self.ir.o(Op::Assign(input, assignment)); - self.ir - .push_action(done, Action::new(self.ir.true_id(), assign)); - } + self.add_input_dont_care_assignments(ast, done); // lower the protocol, which will add the new nodes to self.current_fragment_nodes - let entry = self.lower_stmt(ast, ast.body, done); + let body_entry = self.lower_stmt(ast, ast.body, done); + let entry = self.n(Node::empty()); + self.add_input_dont_care_assignments(ast, entry); + let true_id = self.ir.true_id(); + self.ir + .push_transition(entry, Transition::new(true_id, body_entry, false)); let nodes = std::mem::take(&mut self.current_fragment_nodes); LoweredFragmentInfo { nodes, diff --git a/protocols/src/ir/propagate_assigns.rs b/protocols/src/ir/propagate_assigns.rs index 9dcf1bdb..8a56fc7d 100644 --- a/protocols/src/ir/propagate_assigns.rs +++ b/protocols/src/ir/propagate_assigns.rs @@ -46,6 +46,10 @@ fn all_ports_present( return false; }; + if nid == pg.entry && rd.is_empty() { + continue; + } + for input in &input_ports { if !rd.contains_key(&st.full_name_from_symbol_id(input).to_string()) { return false; @@ -78,6 +82,9 @@ pub fn propagate_assignments( let Some(node_defs) = rd.get(&id) else { continue; }; + if id == pg.entry && node_defs.is_empty() { + continue; + } let actions = pg[id] .actions diff --git a/protocols/src/ir/proto_graph.rs b/protocols/src/ir/proto_graph.rs index ca2a5ae1..f12df776 100644 --- a/protocols/src/ir/proto_graph.rs +++ b/protocols/src/ir/proto_graph.rs @@ -395,8 +395,8 @@ mod tests { assert_eq!(ir.name, "pipe"); assert_eq!(ir.args.len(), 2); - assert_eq!(ir.nodes.len(), 5); - assert_eq!(ir.ops.len(), 3); + assert_eq!(ir.nodes.len(), 6); + assert_eq!(ir.ops.len(), 5); let entry = &ir.nodes[ir.entry]; assert!(entry.actions.is_empty()); @@ -405,11 +405,24 @@ mod tests { entry.transitions[0], Transition { guard: ir.true_id(), - target: NodeId(4), + target: NodeId(5), consumes_step: false, } ); + let init = &ir.nodes[NodeId(5)]; + assert_eq!(init.actions.len(), 1); + assert_eq!(init.actions[0].guard, ir.true_id()); + assert!(matches!(ir.ops[init.actions[0].op], Op::Assign(_, _))); + assert_eq!( + init.transitions, + vec![Transition { + guard: ir.true_id(), + target: NodeId(4), + consumes_step: false, + }] + ); + let assign = &ir.nodes[NodeId(4)]; assert_eq!(assign.actions.len(), 1); assert_eq!(assign.actions[0].guard, ir.true_id()); @@ -451,9 +464,10 @@ mod tests { ); let exit = &ir.nodes[NodeId(1)]; - assert_eq!(exit.actions.len(), 1); + assert_eq!(exit.actions.len(), 2); assert_eq!(exit.actions[0].guard, ir.true_id()); assert!(matches!(ir.ops[exit.actions[0].op], Op::Done)); + assert!(matches!(ir.ops[exit.actions[1].op], Op::Assign(_, _))); assert!(exit.transitions.is_empty()); } @@ -472,8 +486,20 @@ mod tests { "#, ); - assert_eq!(ir.nodes.len(), 3); - assert_eq!(ir.ops.len(), 2); + assert_eq!(ir.nodes.len(), 4); + assert_eq!(ir.ops.len(), 4); + + let init = &ir.nodes[NodeId(3)]; + assert_eq!(init.actions.len(), 1); + assert!(matches!(ir.ops[init.actions[0].op], Op::Assign(_, _))); + assert_eq!( + init.transitions, + vec![Transition { + guard: ir.true_id(), + target: NodeId(2), + consumes_step: false, + }] + ); let fork = &ir.nodes[NodeId(2)]; assert_eq!(fork.actions.len(), 1); @@ -488,8 +514,9 @@ mod tests { ); let exit = &ir.nodes[NodeId(1)]; - assert_eq!(exit.actions.len(), 1); + assert_eq!(exit.actions.len(), 2); assert!(matches!(ir.ops[exit.actions[0].op], Op::Done)); + assert!(matches!(ir.ops[exit.actions[1].op], Op::Assign(_, _))); assert!(exit.transitions.is_empty()); } @@ -499,13 +526,26 @@ mod tests { assert_eq!(ir.name, "add"); assert_eq!(ir.args.len(), 3); - assert_eq!(ir.nodes.len(), 10); - assert_eq!(ir.ops.len(), 7); + assert_eq!(ir.nodes.len(), 11); + assert_eq!(ir.ops.len(), 11); let entry = &ir.nodes[ir.entry]; assert!(entry.actions.is_empty()); assert_eq!( entry.transitions, + vec![Transition { + guard: ir.true_id(), + target: NodeId(10), + consumes_step: false, + }] + ); + + let init = &ir.nodes[NodeId(10)]; + assert_eq!(init.actions.len(), 2); + assert!(matches!(ir.ops[init.actions[0].op], Op::Assign(_, _))); + assert!(matches!(ir.ops[init.actions[1].op], Op::Assign(_, _))); + assert_eq!( + init.transitions, vec![Transition { guard: ir.true_id(), target: NodeId(9), @@ -548,8 +588,10 @@ mod tests { ); let exit = &ir.nodes[NodeId(1)]; - assert_eq!(exit.actions.len(), 1); + assert_eq!(exit.actions.len(), 3); assert!(matches!(ir.ops[exit.actions[0].op], Op::Done)); + assert!(matches!(ir.ops[exit.actions[1].op], Op::Assign(_, _))); + assert!(matches!(ir.ops[exit.actions[2].op], Op::Assign(_, _))); assert!(exit.transitions.is_empty()); } } diff --git a/protocols/src/ir/reaching_defs.rs b/protocols/src/ir/reaching_defs.rs index d3a52ec6..a47f953d 100644 --- a/protocols/src/ir/reaching_defs.rs +++ b/protocols/src/ir/reaching_defs.rs @@ -2,7 +2,7 @@ // released under MIT License // author: Nikil Shyamunder -use crate::frontend::symbol::{SymbolId, SymbolTable}; +use crate::frontend::symbol::SymbolTable; use crate::ir::determinize::{SatResult, check_sat}; use crate::ir::edge_contract::{merge_ordered_assignment, merge_unordered_assignment}; use crate::ir::proto_graph::{Assignment, NodeId, Op, ProtoGraph}; @@ -199,30 +199,7 @@ pub fn reaching_definitions( st: &SymbolTable, ) -> FxHashMap { let preds = predecessors(pg); - - let dut = pg.type_param.expect("protocol has no DUT"); - // TODO: maybe this should be a helper in the SymbolTable - let input_ports: Vec = st - .get_children(&dut) - .into_iter() - .filter(|sym_id| st[*sym_id].is_in_port()) - .collect(); - - // Initially, all port are DontCare. So in_defs[entry] is {sym -> X} for all sym let mut in_defs: FxHashMap = FxHashMap::default(); - in_defs.insert( - pg.entry, - input_ports - .iter() - .map(|sym_id| { - ( - st.full_name_from_symbol_id(sym_id).to_string(), - Assignment::dont_care(pg.true_id()), - ) - }) - .collect(), - ); - let mut out_defs: FxHashMap = FxHashMap::default(); // worklist by default is all reachable nodes. diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap index 9a17a5ad..d168a320 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d0.snap @@ -9,7 +9,9 @@ digraph "add_combinational_illegal_observation_in_conditional" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node10 -> node9 [label="1"]; node9 [label="[1] DUT.a := X if 1"]; node9 -> node8 [label="1"]; node8 [label="[1] DUT.b := X if 1"]; @@ -49,7 +51,9 @@ digraph "add_combinational_illegal_observation_in_conditional" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node10 -> node9 [label="1"]; node9 [label="[1] DUT.a := X if 1"]; node9 -> node8 [label="1"]; node8 [label="[1] DUT.b := X if 1"]; @@ -76,7 +80,7 @@ digraph "add_combinational_illegal_observation_in_conditional" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1"]; node0 -> node1 [label="eq(DUT.s, 0) / step"]; node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; @@ -89,7 +93,9 @@ digraph "add_combinational_illegal_observation_in_assertion" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node6 [label="1"]; + node0 -> node7 [label="1"]; + node7 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node7 -> node6 [label="1"]; node6 [label="[1] DUT.a := X if 1"]; node6 -> node5 [label="1"]; node5 [label="[1] DUT.b := X if 1"]; @@ -121,7 +127,9 @@ digraph "add_combinational_illegal_observation_in_assertion" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node6 [label="1"]; + node0 -> node7 [label="1"]; + node7 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node7 -> node6 [label="1"]; node6 [label="[1] DUT.a := X if 1"]; node6 -> node5 [label="1"]; node5 [label="[1] DUT.b := X if 1"]; @@ -141,7 +149,7 @@ digraph "add_combinational_illegal_observation_in_assertion" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] assert_eq(DUT.s, s)\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := X if 1\n[1] assert_eq(DUT.s, s)"]; node0 -> node1 [label="1 / step"]; node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } @@ -153,7 +161,9 @@ digraph "add_combinational_legal_observation_illegal_assignment" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node6 [label="1"]; + node0 -> node7 [label="1"]; + node7 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node7 -> node6 [label="1"]; node6 [label=""]; node6 -> node4 [label="eq(DUT.s, 0)"]; node6 -> node5 [label="not(eq(DUT.s, 0))"]; @@ -174,7 +184,7 @@ digraph "add_combinational_legal_observation_illegal_assignment" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := a if eq(DUT.s, 0)"]; + node0 [label="[1] DUT.a := X if not(eq(DUT.s, 0)); a if eq(DUT.s, 0)\n[1] DUT.b := X if 1"]; node0 -> node1 [label="eq(DUT.s, 0) / step"]; node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; @@ -187,7 +197,9 @@ digraph "add_combinational_legal_observation_illegal_assignment" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node6 [label="1"]; + node0 -> node7 [label="1"]; + node7 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node7 -> node6 [label="1"]; node6 [label=""]; node6 -> node4 [label="eq(DUT.s, 0)"]; node6 -> node5 [label="not(eq(DUT.s, 0))"]; @@ -208,7 +220,7 @@ digraph "add_combinational_legal_observation_illegal_assignment" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := X if not(eq(DUT.s, 0)); a if eq(DUT.s, 0)\n[1] DUT.b := X if 1"]; node0 -> node1 [label="eq(DUT.s, 0) / step"]; node0 -> node1 [label="not(eq(DUT.s, 0)) / step"]; node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; @@ -221,7 +233,9 @@ digraph "add_combinational" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node5 [label="1"]; + node0 -> node6 [label="1"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node6 -> node5 [label="1"]; node5 [label="[1] DUT.a := a if 1"]; node5 -> node4 [label="1"]; node4 [label="[1] DUT.b := b if 1"]; @@ -251,7 +265,9 @@ digraph "add_combinational" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node5 [label="1"]; + node0 -> node6 [label="1"]; + node6 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node6 -> node5 [label="1"]; node5 [label="[1] DUT.a := a if 1"]; node5 -> node4 [label="1"]; node4 [label="[1] DUT.b := b if 1"]; @@ -269,7 +285,7 @@ digraph "add_combinational" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] assert_eq(s, DUT.s)\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1\n[1] assert_eq(s, DUT.s)"]; node0 -> node1 [label="1 / step"]; node1 [label="[1] done\n[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap index 58b86ade..bc21aeb5 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_add_d1.snap @@ -9,7 +9,9 @@ digraph "add" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node10 -> node9 [label="1"]; node9 [label="[1] DUT.a := a if 1"]; node9 -> node8 [label="1"]; node8 [label="[1] DUT.b := b if 1"]; @@ -49,7 +51,9 @@ digraph "add" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node10 -> node9 [label="1"]; node9 [label="[1] DUT.a := a if 1"]; node9 -> node8 [label="1"]; node8 [label="[1] DUT.b := b if 1"]; @@ -75,7 +79,7 @@ digraph "add" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node0 -> node6 [label="1 / step"]; node6 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := b if 1"]; node6 -> node1 [label="1 / step"]; @@ -89,7 +93,9 @@ digraph "add_fork_early" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node10 -> node9 [label="1"]; node9 [label="[1] DUT.a := a if 1"]; node9 -> node8 [label="1"]; node8 [label="[1] DUT.b := b if 1"]; @@ -129,7 +135,9 @@ digraph "add_fork_early" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node10 -> node9 [label="1"]; node9 [label="[1] DUT.a := a if 1"]; node9 -> node8 [label="1"]; node8 [label="[1] DUT.b := b if 1"]; @@ -155,7 +163,7 @@ digraph "add_fork_early" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node0 -> node6 [label="1 / step"]; node6 [label="[1] fork\n[1] assert_eq(s, DUT.s)\n[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node6 -> node1 [label="1 / step"]; @@ -169,7 +177,9 @@ digraph "add_incorrect" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node10 -> node9 [label="1"]; node9 [label="[1] DUT.a := a if 1"]; node9 -> node8 [label="1"]; node8 [label="[1] DUT.b := b if 1"]; @@ -209,7 +219,9 @@ digraph "add_incorrect" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node10 -> node9 [label="1"]; node9 [label="[1] DUT.a := a if 1"]; node9 -> node8 [label="1"]; node8 [label="[1] DUT.b := b if 1"]; @@ -235,7 +247,7 @@ digraph "add_incorrect" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node0 -> node6 [label="1 / step"]; node6 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node6 -> node1 [label="1 / step"]; @@ -249,7 +261,9 @@ digraph "add_incorrect_implicit" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node7 [label="1"]; + node0 -> node8 [label="1"]; + node8 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node8 -> node7 [label="1"]; node7 [label="[1] DUT.a := a if 1"]; node7 -> node6 [label="1"]; node6 [label="[1] DUT.b := b if 1"]; @@ -285,7 +299,9 @@ digraph "add_incorrect_implicit" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node7 [label="1"]; + node0 -> node8 [label="1"]; + node8 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node8 -> node7 [label="1"]; node7 [label="[1] DUT.a := a if 1"]; node7 -> node6 [label="1"]; node6 [label="[1] DUT.b := b if 1"]; @@ -307,7 +323,7 @@ digraph "add_incorrect_implicit" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node0 -> node4 [label="1 / step"]; node4 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node4 -> node1 [label="1 / step"]; @@ -321,7 +337,9 @@ digraph "wait_and_add" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node10 -> node9 [label="1"]; node9 [label=""]; node9 -> node8 [label="1 / step"]; node8 [label="[1] fork"]; @@ -347,7 +365,7 @@ digraph "wait_and_add" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label=""]; + node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; node0 -> node8 [label="1 / step"]; node8 [label="[1] fork"]; node8 -> node6 [label="1 / step"]; @@ -365,7 +383,9 @@ digraph "wait_and_add" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node10 -> node9 [label="1"]; node9 [label=""]; node9 -> node8 [label="1 / step"]; node8 [label="[1] fork"]; @@ -409,7 +429,9 @@ digraph "assign_after_observation" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node13 [label="1"]; + node0 -> node14 [label="1"]; + node14 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node14 -> node13 [label="1"]; node13 [label="[1] DUT.a := a if 1"]; node13 -> node12 [label="1"]; node12 [label=""]; @@ -459,7 +481,9 @@ digraph "assign_after_observation" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node13 [label="1"]; + node0 -> node14 [label="1"]; + node14 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node14 -> node13 [label="1"]; node13 [label="[1] DUT.a := a if 1"]; node13 -> node12 [label="1"]; node12 [label=""]; @@ -494,7 +518,7 @@ digraph "assign_after_observation" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := 0 if eq(DUT.a, 2); a if 1\n[1] DUT.b := b if eq(DUT.a, 2); b if not(eq(DUT.a, 2))"]; node0 -> node6 [label="eq(DUT.a, 2) / step"]; node0 -> node6 [label="not(eq(DUT.a, 2)) / step"]; node6 [label="[1] assert_eq(s, DUT.s)\n[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := b if 1"]; @@ -509,7 +533,9 @@ digraph "better_if_else" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node10 [label="1"]; + node0 -> node11 [label="1"]; + node11 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node11 -> node10 [label="1"]; node10 [label="[1] DUT.a := a if 1"]; node10 -> node9 [label="1"]; node9 [label="[1] DUT.b := b if 1"]; @@ -553,7 +579,9 @@ digraph "better_if_else" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node10 [label="1"]; + node0 -> node11 [label="1"]; + node11 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node11 -> node10 [label="1"]; node10 [label="[1] DUT.a := a if 1"]; node10 -> node9 [label="1"]; node9 [label="[1] DUT.b := b if 1"]; @@ -582,7 +610,7 @@ digraph "better_if_else" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node0 -> node7 [label="1 / step"]; node7 [label="[1] fork\n[0] internal_assert_false\n[1] DUT.a := a if 1\n[1] DUT.b := b if 1"]; node7 -> node1 [label="not(eq(DUT.s, 0)) / step"]; @@ -597,7 +625,9 @@ digraph "if_else" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node12 [label="1"]; + node0 -> node13 [label="1"]; + node13 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node13 -> node12 [label="1"]; node12 [label=""]; node12 -> node10 [label="1"]; node10 [label="[1] DUT.a := a1 if 1"]; @@ -641,7 +671,9 @@ digraph "if_else" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node12 [label="1"]; + node0 -> node13 [label="1"]; + node13 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node13 -> node12 [label="1"]; node12 [label=""]; node12 -> node10 [label="1"]; node10 [label="[1] DUT.a := a1 if 1"]; @@ -671,7 +703,7 @@ digraph "if_else" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] DUT.a := X if 1\n[1] DUT.b := X if 1"]; + node0 [label="[1] DUT.a := a1 if 1\n[1] DUT.b := 0 if 1"]; node0 -> node6 [label="1 / step"]; node6 [label="[1] assert_eq(a1, DUT.s)\n[1] fork\n[1] DUT.a := X if 1\n[1] DUT.b := 0 if 1"]; node6 -> node1 [label="1 / step"]; diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap index 07134da8..e831ebcc 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_axis_truncated_include_idle_send_data.snap @@ -9,7 +9,9 @@ digraph "send_data" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node7 [label="1"]; + node0 -> node8 [label="1"]; + node8 [label="[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node8 -> node7 [label="1"]; node7 [label="[1] D.m_axis_tdata := data if 1"]; node7 -> node6 [label="1"]; node6 [label="[1] D.m_axis_tvalid := 1 if 1"]; @@ -32,7 +34,7 @@ digraph "send_data" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.m_axis_tdata := data if 1\n[1] D.m_axis_tvalid := 1 if 1"]; + node0 [label="[1] D.m_axis_tvalid := 1 if 1\n[1] D.m_axis_tdata := data if 1"]; node0 -> node1 [label="D.m_axis_tready / step"]; node0 -> node4 [label="not(D.m_axis_tready) / step"]; node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; @@ -48,7 +50,9 @@ digraph "send_data" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node7 [label="1"]; + node0 -> node8 [label="1"]; + node8 [label="[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node8 -> node7 [label="1"]; node7 [label="[1] D.m_axis_tdata := data if 1"]; node7 -> node6 [label="1"]; node6 [label="[1] D.m_axis_tvalid := 1 if 1"]; @@ -71,7 +75,7 @@ digraph "send_data" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node0 [label="[1] D.m_axis_tvalid := 1 if 1\n[1] D.m_axis_tdata := data if 1"]; node0 -> node1 [label="D.m_axis_tready / step"]; node0 -> node4 [label="not(D.m_axis_tready) / step"]; node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; @@ -87,7 +91,9 @@ digraph "idle" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node3 [label="1"]; + node0 -> node4 [label="1"]; + node4 [label="[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node4 -> node3 [label="1"]; node3 [label="[1] D.m_axis_tvalid := 0 if 1"]; node3 -> node2 [label="1"]; node2 [label=""]; @@ -101,7 +107,7 @@ digraph "idle" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.m_axis_tvalid := 0 if 1"]; + node0 [label="[1] D.m_axis_tvalid := 0 if 1\n[1] D.m_axis_tdata := X if 1"]; node0 -> node1 [label="1 / step"]; node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; } @@ -113,7 +119,9 @@ digraph "idle" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node3 [label="1"]; + node0 -> node4 [label="1"]; + node4 [label="[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node4 -> node3 [label="1"]; node3 [label="[1] D.m_axis_tvalid := 0 if 1"]; node3 -> node2 [label="1"]; node2 [label=""]; @@ -127,7 +135,7 @@ digraph "idle" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; + node0 [label="[1] D.m_axis_tvalid := 0 if 1\n[1] D.m_axis_tdata := X if 1"]; node0 -> node1 [label="1 / step"]; node1 [label="[1] done\n[1] D.m_axis_tvalid := X if 1\n[1] D.m_axis_tdata := X if 1"]; } diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap index 8396815a..482dacae 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_bounded_ready_valid.snap @@ -9,7 +9,9 @@ digraph "send_data" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node13 [label="1"]; + node0 -> node14 [label="1"]; + node14 [label="[1] D.valid := X if 1\n[1] D.data := X if 1"]; + node14 -> node13 [label="1"]; node13 [label="[1] D.data := data if 1"]; node13 -> node12 [label="1"]; node12 [label="[1] D.valid := 1 if 1"]; @@ -45,7 +47,7 @@ digraph "send_data" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.data := data if 1\n[1] D.valid := 1 if 1"]; + node0 [label="[1] D.valid := 1 if 1\n[1] D.data := data if 1"]; node0 -> node1 [label="D.ready / step"]; node0 -> node8 [label="not(D.ready) / step"]; node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; @@ -63,7 +65,9 @@ digraph "send_data" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node13 [label="1"]; + node0 -> node14 [label="1"]; + node14 [label="[1] D.valid := X if 1\n[1] D.data := X if 1"]; + node14 -> node13 [label="1"]; node13 [label="[1] D.data := data if 1"]; node13 -> node12 [label="1"]; node12 [label="[1] D.valid := 1 if 1"]; @@ -99,7 +103,7 @@ digraph "send_data" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] D.valid := X if 1\n[1] D.data := X if 1"]; + node0 [label="[1] D.valid := 1 if 1\n[1] D.data := data if 1"]; node0 -> node1 [label="D.ready / step"]; node0 -> node8 [label="not(D.ready) / step"]; node1 [label="[1] done\n[1] D.valid := X if 1\n[1] D.data := X if 1"]; diff --git a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap index 60f9c776..4b265460 100644 --- a/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap +++ b/protocols/src/tests/snapshots/protocols__ir__graphviz__tests__ir_graphviz_counter.snap @@ -9,7 +9,9 @@ digraph "count_up" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] dut.a := X if 1"]; + node10 -> node9 [label="1"]; node9 [label="[1] dut.a := a if 1"]; node9 -> node7 [label="1"]; node7 [label=""]; @@ -54,7 +56,9 @@ digraph "count_up" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node9 [label="1"]; + node0 -> node10 [label="1"]; + node10 [label="[1] dut.a := X if 1"]; + node10 -> node9 [label="1"]; node9 [label="[1] dut.a := a if 1"]; node9 -> node7 [label="1"]; node7 [label=""]; @@ -81,7 +85,7 @@ digraph "count_up" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[1] dut.a := X if 1"]; + node0 [label="[1] dut.a := a if 1"]; node0 -> node4 [label="eq(dut.s, dut.a) / step"]; node0 -> node7 [label="not(eq(dut.s, dut.a)) / step"]; node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := a if 1"]; @@ -99,7 +103,9 @@ digraph "count_up_extra_assertion" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node10 [label="1"]; + node0 -> node11 [label="1"]; + node11 [label="[1] dut.a := X if 1"]; + node11 -> node10 [label="1"]; node10 [label="[1] dut.a := a if 1"]; node10 -> node8 [label="1"]; node8 [label=""]; @@ -146,7 +152,9 @@ digraph "count_up_extra_assertion" { entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; node0 [label=""]; - node0 -> node10 [label="1"]; + node0 -> node11 [label="1"]; + node11 [label="[1] dut.a := X if 1"]; + node11 -> node10 [label="1"]; node10 [label="[1] dut.a := a if 1"]; node10 -> node8 [label="1"]; node8 [label=""]; @@ -175,7 +183,7 @@ digraph "count_up_extra_assertion" { node [shape=box]; entry_marker [shape=plain,label="ENTRY"]; entry_marker -> node0; - node0 [label="[eq(dut.s, dut.a)] assert_eq(dut.s, a)\n[1] dut.a := X if 1"]; + node0 [label="[1] dut.a := a if 1\n[eq(dut.s, dut.a)] assert_eq(dut.s, a)"]; node0 -> node4 [label="eq(dut.s, dut.a) / step"]; node0 -> node8 [label="not(eq(dut.s, dut.a)) / step"]; node4 [label="[1] assert_eq(dut.s, 0)\n[1] fork\n[1] dut.a := a if 1"]; diff --git a/runt/waveform/runt.toml b/runt/waveform/runt.toml index f3894b2b..a0c4d3d6 100644 --- a/runt/waveform/runt.toml +++ b/runt/waveform/runt.toml @@ -252,24 +252,6 @@ expect_dir = "../../tests/brave_new_world/bit_truncation/expects" expect_name = "bit_truncation_sha_fix.waveform.expect" cmd = "cd ../.. && target/debug/graph-interp --transactions tests/brave_new_world/bit_truncation/bit_truncation_sha_fix.tx --respect-forks --determinize --ascii-waveform --verilog tests/brave_new_world/bit_truncation/bit_truncation_sha_fix.v --protocol tests/brave_new_world/bit_truncation/bit_truncation_sha.prot --module bit_truncation_sha_fixed 2>/dev/null" -[[tests]] -name = "waveform.tests_brave_new_world_failure_to_update_ftu_sha_fix.ftu_sha_fix_waveform.ast" -paths = [ - "../../tests/brave_new_world/failure_to_update/ftu_sha_fix.tx", -] -expect_dir = "../../tests/brave_new_world/failure_to_update/expects" -expect_name = "ftu_sha_fix.waveform.expect" -cmd = "cd ../.. && target/debug/protocols-interp --color never --transactions tests/brave_new_world/failure_to_update/ftu_sha_fix.tx --ascii-waveform --verilog tests/brave_new_world/failure_to_update/ftu_sha_fix.v --protocol tests/brave_new_world/failure_to_update/ftu_sha.prot --module fsm_update_fixed_gated 2>/dev/null" - -[[tests]] -name = "waveform.tests_brave_new_world_failure_to_update_ftu_sha_fix.ftu_sha_fix_waveform.graph" -paths = [ - "../../tests/brave_new_world/failure_to_update/ftu_sha_fix.tx", -] -expect_dir = "../../tests/brave_new_world/failure_to_update/expects" -expect_name = "ftu_sha_fix.waveform.expect" -cmd = "cd ../.. && target/debug/graph-interp --transactions tests/brave_new_world/failure_to_update/ftu_sha_fix.tx --respect-forks --determinize --ascii-waveform --verilog tests/brave_new_world/failure_to_update/ftu_sha_fix.v --protocol tests/brave_new_world/failure_to_update/ftu_sha.prot --module fsm_update_fixed_gated 2>/dev/null" - [[tests]] name = "waveform.tests_brave_new_world_signal_asynchrony_signal_async_fix.signal_async_fix_waveform.ast" paths = [ diff --git a/scripts/generate_runt_configs.py b/scripts/generate_runt_configs.py index d2f966b4..f5233389 100644 --- a/scripts/generate_runt_configs.py +++ b/scripts/generate_runt_configs.py @@ -282,12 +282,13 @@ def waveform_cases(cases: list[dict]) -> list[dict]: # these cases are correct, but our ASCII diffing isn't good enough # for us to know they are the same xfailed = [ - #"examples/serv/serv_regfile.tx", + # "examples/serv/serv_regfile.tx", "tests/adders/adder_d1/wait_and_add_correct.tx", "tests/identities/identity_d2/two_assignments_same_value.tx", - #"tests/fifo/fifo.tx", - #"tests/fifo/push_pop_identity_ok.tx", + # "tests/fifo/fifo.tx", + # "tests/fifo/push_pop_identity_ok.tx", "tests/wishbone/wishbone.tx", + "tests/brave_new_world/failure_to_update/ftu_sha_fix.tx", ] return list(filter(lambda c: c["path"] not in xfailed, graph_interp_cases(cases)))