From 69b0b0c4de74e8d759b0b24a13b3ff6063c8e1a5 Mon Sep 17 00:00:00 2001 From: Daniel-Penas Date: Thu, 25 Sep 2025 12:42:04 -0400 Subject: [PATCH 1/2] Add replace_output_uses API for DrivenNet --- src/netlist.rs | 45 +++++++ tests/learning_tests.rs | 230 ++++++++++++++++++++++++++++++++++ tests/multi_output_replace.rs | 47 +++++++ 3 files changed, 322 insertions(+) create mode 100644 tests/learning_tests.rs create mode 100644 tests/multi_output_replace.rs diff --git a/src/netlist.rs b/src/netlist.rs index ce67f62..62a1468 100644 --- a/src/netlist.rs +++ b/src/netlist.rs @@ -1334,6 +1334,14 @@ where Ok(netref.unwrap().borrow().get().clone()) } + /// Replace uses of one driven net with another (single-output nodes). + /// Inputs/constants are fine. Panics on multi-output nodes. + pub fn replace_net_uses_driven(&self, of: DrivenNet, with: &DrivenNet) -> Result, String> { + // Convert the borrowed DrivenNet to a NetRef without consuming it + let with_nr = with.clone().unwrap(); + self.replace_net_uses(of.unwrap(), &with_nr) + } + /// Replaces the uses of a circuit node with another circuit node. The [Object] stored at `of` is returned. /// Panics if `of` and `with` are not single-output nodes. pub fn replace_net_uses(&self, of: NetRef, with: &NetRef) -> Result, String> { @@ -1369,6 +1377,43 @@ where Ok(of.unwrap().borrow().get().clone()) } + + /// Replace uses of a specific driven output with another specific driven output. + /// This supports inputs, single-output, and multi-output instances by preserving + /// the exact output port indices encoded in the provided [DrivenNet]s. + pub fn replace_output_uses(&self,of: DrivenNet, with: DrivenNet) -> Result, String> { + // Build precise operands (keeps multi-output indices) + let old_index = of.get_operand(); + let new_index = with.get_operand(); + + // Guard against lingering external references to the source node. + // Consume `of` to avoid creating extra Rc clones when obtaining the inner handle. + let of_rc = of.unwrap().unwrap(); // DrivenNet -> NetRef -> Rc<...> + if Rc::strong_count(&of_rc) > 3 { + return Err("Cannot replace. References still exist on this node".to_string()); + } + // Remap operands across all objects + let objects = self.objects.borrow(); + for oref in objects.iter() { + let operands = &mut oref.borrow_mut().operands; + for operand in operands.iter_mut() { + if let Some(op) = operand && *op == old_index { + *operand = Some(new_index.clone()); + } + } + } + // Update output mapping table + let already_mapped = self.outputs.borrow().contains_key(&new_index); + let old_mapping = self.outputs.borrow_mut().remove(&old_index); + + if already_mapped { + self.outputs.borrow_mut().remove(&old_index); + } else if let Some(v) = old_mapping { + self.outputs.borrow_mut().insert(new_index, v.clone()); + } + Ok(of_rc.borrow().get().clone()) + } + } impl Netlist diff --git a/tests/learning_tests.rs b/tests/learning_tests.rs new file mode 100644 index 0000000..e5deaef --- /dev/null +++ b/tests/learning_tests.rs @@ -0,0 +1,230 @@ +use safety_net::netlist::Netlist; +use safety_net::netlist::Gate; +use safety_net::format_id; +use safety_net::attribute::Parameter; +use safety_net::circuit::{Identifier, Instantiable, Net}; +use safety_net::logic::Logic; +use bitvec::vec::BitVec; + +fn and_gate() -> Gate { + Gate::new_logical("AND".into(), vec!["A".into(), "B".into()], "Y".into()) +} + +fn or_gate() -> Gate { + Gate::new_logical("OR".into(), vec!["A".into(), "B".into()], "Y".into()) +} + +fn not_gate() -> Gate { + Gate::new_logical("NOT".into(), vec!["A".into()], "Y".into()) +} +#[test] +fn test_stage_1(){ + let netlist = Netlist::new("example".to_string()); + let a = netlist.insert_input("a".into()); + let b = netlist.insert_input("b".into()); + let c = netlist.insert_input("c".into()); + let instance = netlist.insert_gate(and_gate(), "inst_0".into(), &[a,b]).unwrap(); + let and_out = instance.get_output(0); + let not_instance = netlist.insert_gate(not_gate(), + "inst_1".into(), &[and_out]).unwrap(); + let not_out = not_instance.get_output(0); + let final_instance = + netlist.insert_gate(or_gate(), + "inst_3".into(), &[c, not_out]).unwrap(); + final_instance.expose_with_name("y".into()); + not_instance.expose_with_name("n".into()); + +} + +#[test] +fn test_stage_2() { + let netlist = Netlist::new("stage2".into()); + + // input + let a = netlist.insert_input("a".into()); + + // not gate + let n_instance = netlist.insert_gate(not_gate(), "inst_0".into(), &[a]).unwrap(); + let n_out = n_instance.get_output(0); + + // constant 1 + let vdd = netlist.insert_constant(Logic::True, "const1".into()).unwrap(); + + // and gate: (NOT a) & 1 + let and_instance = netlist.insert_gate(and_gate(), "inst_1".into(), &[n_out, vdd]).unwrap(); + and_instance.set_attribute("dont_touch".into()); + // expose output + and_instance.expose_with_name("y".into()); + + + // optional: check / print + assert!(netlist.verify().is_ok()); + println!("{}", netlist.to_string()); +} + +#[test] +fn test_stage_3() { + let netlist = Netlist::new("stage3".into()); + let bus = netlist.insert_input_escaped_logic_bus("input_bus".to_string(), 4); + let bit0 = bus[0].clone(); + let n_instance = netlist.insert_gate(not_gate(), "inst_0".into(), &[bit0]).unwrap(); + let n_out = n_instance.get_output(0); + n_instance.expose_with_name("y".into()); + println!("{}", netlist.to_string()); +} + +// Implementing a LUT from the code params.rs + +#[derive(Debug, Clone)] +struct Lut { + lookup_table: BitVec, + id: Identifier, + inputs: Vec, + output: Net, +} + +impl Lut { + fn new(k: usize, lookup_table: usize) -> Self { + let mut bv: BitVec = BitVec::from_element(lookup_table); + bv.truncate(1 << k); + Lut { + lookup_table: bv, + id: format_id!("LUT{k}"), + inputs: (0..k).map(|i| Net::new_logic(format_id!("I{i}"))).collect(), + output: Net::new_logic("O".into()), + } + } + + fn invert(&mut self) { + self.lookup_table = !self.lookup_table.clone(); + } +} + +impl Instantiable for Lut { + fn get_name(&self) -> &Identifier { + &self.id + } + + fn get_input_ports(&self) -> impl IntoIterator { + &self.inputs + } + + fn get_output_ports(&self) -> impl IntoIterator { + std::slice::from_ref(&self.output) + } + + fn has_parameter(&self, id: &Identifier) -> bool { + *id == Identifier::new("INIT".to_string()) + } + + fn get_parameter(&self, id: &Identifier) -> Option { + if self.has_parameter(id) { + Some(Parameter::BitVec(self.lookup_table.clone())) + } else { + None + } + } + + fn set_parameter(&mut self, id: &Identifier, val: Parameter) -> Option { + if !self.has_parameter(id) { + return None; + } + + let old = Some(Parameter::BitVec(self.lookup_table.clone())); + + if let Parameter::BitVec(bv) = val { + self.lookup_table = bv; + } else { + panic!("Invalid parameter type for INIT"); + } + + old + } + + fn parameters(&self) -> impl Iterator { + std::iter::once(( + Identifier::new("INIT".to_string()), + Parameter::BitVec(self.lookup_table.clone()), + )) + } + + fn from_constant(val: Logic) -> Option { + match val { + Logic::True => Some(Self { + lookup_table: BitVec::from_element(1), + id: "VDD".into(), + inputs: vec![], + output: "Y".into(), + }), + Logic::False => Some(Self { + lookup_table: BitVec::from_element(0), + id: "GND".into(), + inputs: vec![], + output: "Y".into(), + }), + _ => None, + } + } + + fn get_constant(&self) -> Option { + match self.id.to_string().as_str() { + "VDD" => Some(Logic::True), + "GND" => Some(Logic::False), + _ => None, + } + } +} + +// to do +#[test] +fn test_stage_4(){ + let netlist: std::rc::Rc> = Netlist::new("example".to_string()); + + // Add the the two inputs + let a = netlist.insert_input("a".into()); + let b = netlist.insert_input("b".into()); + let instance = netlist + .insert_gate(Lut::new(2, 7), "inst_0".into(), &[a, b]) + .unwrap(); + + + // Let's make it an AND gate by inverting the lookup table + instance.get_instance_type_mut().unwrap().invert(); + + // Make this LUT an output + instance.expose_with_name("y".into()); + println!("{netlist}"); + +} + + +// key functions for mutating netlists: +// replace_net_uses(old_net, new_net) +// delete_net_uses(net) +// clean() +// Name management + +#[test] +fn replace_input_with_constant() { + let netlist = Netlist::new("demo".into()); + + // input a, input b + let a = netlist.insert_input("a".into()); + let b = netlist.insert_input("b".into()); + + // AND gate + let and_inst = netlist.insert_gate(and_gate(), "inst_0".into(), &[a.clone(), b.clone()]).unwrap(); + + // constant 1 + let vdd = netlist.insert_constant(Logic::True, "const1".into()).unwrap(); + + // Old API: panics here because `a` is not a DrivenNet + //netlist.replace_net_uses( a.unwrap(), &vdd.unwrap()).unwrap(); + + // New API: works, because we check dynamically + netlist.replace_net_uses_driven(a, &vdd).unwrap(); + + and_inst.expose_with_name("y".into()); + assert!(netlist.verify().is_ok()); + println!("{}", netlist.to_string()); +} diff --git a/tests/multi_output_replace.rs b/tests/multi_output_replace.rs new file mode 100644 index 0000000..dfbe62c --- /dev/null +++ b/tests/multi_output_replace.rs @@ -0,0 +1,47 @@ +use safety_net::netlist::{Gate, GateNetlist, Netlist}; + +fn full_adder() -> Gate { + Gate::new_logical_multi( + "FA".into(), + vec!["CIN".into(), "A".into(), "B".into()], + vec!["S".into(), "COUT".into()], + ) +} + +#[test] +fn replace_multi_output_port_usage() { + let netlist: std::rc::Rc = Netlist::new("multi_replace".to_string()); + + // Inputs + let a = netlist.insert_input("a".into()); + let b = netlist.insert_input("b".into()); + let cin = netlist.insert_input("cin".into()); + + // Instantiate a multi-output gate (FA with S and COUT) + let fa = netlist + .insert_gate(full_adder(), "inst_0".into(), &[cin, a, b]) + .unwrap(); + + // Expose SUM (output 0) as a top-level output initially + fa.expose_net(&fa.get_net(0)).unwrap(); + print!("{}", netlist); + + // Sanity: only SUM is a top-level output now + assert!(fa.get_output(0).is_top_level_output()); + assert!(!fa.get_output(1).is_top_level_output()); + + // Replace uses of SUM with CARRY using the multi-output safe API + // Drop the FA netref to avoid extra outstanding references + let sum = fa.get_output(0); + let carry = fa.get_output(1); + drop(fa); + netlist.replace_output_uses(sum, carry).unwrap(); + + // Re-grab the instance to check the outputs (should be unchanged) + let fa = netlist.last().unwrap(); + // SUM should still be the top-level output; CARRY should not + assert!(fa.get_output(0).is_top_level_output()); + assert!(!fa.get_output(1).is_top_level_output()); + + assert!(netlist.verify().is_ok()); +} From 84b7b516807146b9466509d0a7e1bc38c75f72dd Mon Sep 17 00:00:00 2001 From: Daniel-Penas Date: Thu, 2 Oct 2025 23:39:28 -0400 Subject: [PATCH 2/2] Just created some test for testing nnew functionality of replace_net_uses --- src/netlist.rs | 61 ++------- tests/analysis.rs | 2 +- tests/api.rs | 4 +- tests/edits.rs | 126 ++++++++++++++++++- tests/learning_tests.rs | 230 ---------------------------------- tests/multi_output_replace.rs | 47 ------- 6 files changed, 136 insertions(+), 334 deletions(-) delete mode 100644 tests/learning_tests.rs delete mode 100644 tests/multi_output_replace.rs diff --git a/src/netlist.rs b/src/netlist.rs index 62a1468..06feca7 100644 --- a/src/netlist.rs +++ b/src/netlist.rs @@ -805,7 +805,7 @@ where .owner .upgrade() .expect("NetRef is unlinked from netlist"); - netlist.replace_net_uses(self, other) + netlist.replace_net_uses(self.into(), &other.clone().into()) } /// Clears the attribute with the given key on this circuit node. @@ -1334,26 +1334,19 @@ where Ok(netref.unwrap().borrow().get().clone()) } - /// Replace uses of one driven net with another (single-output nodes). - /// Inputs/constants are fine. Panics on multi-output nodes. - pub fn replace_net_uses_driven(&self, of: DrivenNet, with: &DrivenNet) -> Result, String> { - // Convert the borrowed DrivenNet to a NetRef without consuming it - let with_nr = with.clone().unwrap(); - self.replace_net_uses(of.unwrap(), &with_nr) - } - /// Replaces the uses of a circuit node with another circuit node. The [Object] stored at `of` is returned. /// Panics if `of` and `with` are not single-output nodes. - pub fn replace_net_uses(&self, of: NetRef, with: &NetRef) -> Result, String> { - let unwrapped = of.clone().unwrap(); + pub fn replace_net_uses(&self, of: DrivenNet, with: &DrivenNet) -> Result, String> { + let unwrapped = of.clone().unwrap().unwrap(); + // Rc (1) - Netlist Owner + // Rc (2) - Argument + // Rc (3) - Unwrapped Counter Checker if Rc::strong_count(&unwrapped) > 3 { return Err("Cannot replace. References still exist on this node".to_string()); } - let old_tag: DrivenNet = of.clone().into(); - let old_index = old_tag.get_operand(); - let new_tag: DrivenNet = with.clone().into(); - let new_index = new_tag.get_operand(); + let old_index = of.get_operand(); + let new_index = with.get_operand(); let objects = self.objects.borrow(); for oref in objects.iter() { let operands = &mut oref.borrow_mut().operands; @@ -1375,44 +1368,8 @@ where self.outputs.borrow_mut().insert(new_index, v.clone()); } - Ok(of.unwrap().borrow().get().clone()) + Ok(of.unwrap().unwrap().borrow().get().clone()) } - - /// Replace uses of a specific driven output with another specific driven output. - /// This supports inputs, single-output, and multi-output instances by preserving - /// the exact output port indices encoded in the provided [DrivenNet]s. - pub fn replace_output_uses(&self,of: DrivenNet, with: DrivenNet) -> Result, String> { - // Build precise operands (keeps multi-output indices) - let old_index = of.get_operand(); - let new_index = with.get_operand(); - - // Guard against lingering external references to the source node. - // Consume `of` to avoid creating extra Rc clones when obtaining the inner handle. - let of_rc = of.unwrap().unwrap(); // DrivenNet -> NetRef -> Rc<...> - if Rc::strong_count(&of_rc) > 3 { - return Err("Cannot replace. References still exist on this node".to_string()); - } - // Remap operands across all objects - let objects = self.objects.borrow(); - for oref in objects.iter() { - let operands = &mut oref.borrow_mut().operands; - for operand in operands.iter_mut() { - if let Some(op) = operand && *op == old_index { - *operand = Some(new_index.clone()); - } - } - } - // Update output mapping table - let already_mapped = self.outputs.borrow().contains_key(&new_index); - let old_mapping = self.outputs.borrow_mut().remove(&old_index); - - if already_mapped { - self.outputs.borrow_mut().remove(&old_index); - } else if let Some(v) = old_mapping { - self.outputs.borrow_mut().insert(new_index, v.clone()); - } - Ok(of_rc.borrow().get().clone()) - } } diff --git a/tests/analysis.rs b/tests/analysis.rs index 2b3c3c6..5a900ef 100644 --- a/tests/analysis.rs +++ b/tests/analysis.rs @@ -41,7 +41,7 @@ fn test_detect_cycles() { let inverted = netlist .insert_gate(inverter, "inst_0".into(), std::slice::from_ref(&input)) .unwrap(); - assert!(netlist.replace_net_uses(input.unwrap(), &inverted).is_ok()); + assert!(netlist.replace_net_uses(input, &inverted.get_output(0)).is_ok()); // Now there is a cycle. // We replaced the inverter input with invert output. diff --git a/tests/api.rs b/tests/api.rs index 7a80894..c766275 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -363,7 +363,7 @@ fn test_replace_gate_bad() { let or_gate = netlist .insert_gate(or_gate, "inst_0".into(), &inputs) .unwrap(); - assert!(netlist.replace_net_uses(and_gate, &or_gate).is_ok()); + assert!(netlist.replace_net_uses(and_gate.into(), &or_gate.into()).is_ok()); // Both the AND and OR gate are driving the same wire name (subtle). // The instance name has to be different, or the user has to manualy rename it. // Will need to consider how to make this more user-friendly. @@ -379,7 +379,7 @@ fn test_replace_gate() { let or_gate = netlist .insert_gate(or_gate, "inst_0".into(), &inputs) .unwrap(); - assert!(netlist.replace_net_uses(and_gate, &or_gate).is_ok()); + assert!(netlist.replace_net_uses(and_gate.into(), &or_gate.clone().into()).is_ok()); assert!(netlist.clean().is_err()); or_gate.set_instance_name("inst_1".into()); or_gate.as_net_mut().set_identifier("inst_1_Y".into()); diff --git a/tests/edits.rs b/tests/edits.rs index 0b25c01..148fc49 100644 --- a/tests/edits.rs +++ b/tests/edits.rs @@ -8,6 +8,19 @@ fn and_gate() -> Gate { Gate::new_logical("AND".into(), vec!["A".into(), "B".into()], "Y".into()) } +fn or_gate() -> Gate { + Gate::new_logical("OR".into(), vec!["A".into(), "B".into()], "Y".into()) +} + +fn two_out_gate() -> Gate { + // Simple 2-output primitive: one input "I" and two outputs "O0", "O1" + Gate::new_logical_multi( + "DUP".into(), + vec!["I".into()], + vec!["O0".into(), "O1".into()], + ) +} + fn get_simple_example() -> Rc { let netlist = Netlist::new("example".to_string()); @@ -48,7 +61,7 @@ fn test_replace() { let inverted = netlist .insert_gate(inverter, "inst_0".into(), std::slice::from_ref(&input)) .unwrap(); - assert!(netlist.replace_net_uses(input.unwrap(), &inverted).is_ok()); + assert!(netlist.replace_net_uses(input, &inverted.into()).is_ok()); assert_verilog_eq!( netlist.to_string(), "module example ( @@ -89,7 +102,7 @@ fn test_replace2() { // This errors, because input is not safe to delete. No replace is done. assert!( netlist - .replace_net_uses(input.clone().unwrap(), &inverted) + .replace_net_uses(input.clone(), &inverted.clone().into()) .is_err() ); inverted.find_input(&"I".into()).unwrap().connect(input); @@ -121,3 +134,112 @@ fn test_replace2() { endmodule\n" ); } +// Testing edits for replace_net_uses using single and multiple output netrefs that have a +// DrivenNet that is fed as an argument +// TEST 1: SINGLE , SINGLE +// TEST2: SINGLE, MULTIPLE +// TEST3 MULTIPLE, SINGLE +// TEST4 MULTOPLE, MULTIPLE + +#[test] +fn test_replace_single_single(){ + let netlist = Netlist::new("example".into()); + let a = netlist.insert_input("a".into()); + let b = netlist.insert_input("b".into()); + let and_inst = netlist.insert_gate(and_gate(), "and_0".into(), &[a.clone(), b]).unwrap(); + let and_out = and_inst.get_output(0); // DrivenNet from a net ref of single output + // Ensure the netlist has a top-level output for verification + let and_out = and_out.expose_with_name("y".into()); + // Drop the source node handle to satisfy the strong-count guard + drop(and_inst); + assert!(netlist.replace_net_uses(and_out, &a.clone()).is_ok()); + assert!(netlist.verify().is_ok()); + println!("{}", netlist.to_string()); +} + +#[test] +fn test_replace_single_single_v2(){ + let netlist = Netlist::new("example".into()); + let a = netlist.insert_input("a".into()); + let b = netlist.insert_input("b".into()); + let and_inst = netlist.insert_gate(and_gate(), "and_0".into(), &[a.clone(), b.clone()]).unwrap(); + let or_inst = netlist.insert_gate(or_gate(),"or_0".into(), &[a.clone(),b.clone()]).unwrap(); + let and_out = and_inst.get_output(0); + drop(and_inst); + assert!(netlist.replace_net_uses(and_out, &or_inst.clone().into()).is_ok()); + or_inst.get_output(0).expose_with_name("y".into()); + assert!(netlist.verify().is_ok()); + println!("{}", netlist.to_string()); +} +#[test] +fn test_replace_single_multiple(){ + let netlist = Netlist::new("example".into()); + let a = netlist.insert_input("a".into()); + let b = netlist.insert_input("b".into()); + + let and_inst = netlist + .insert_gate(and_gate(), "and_0".into(), &[a.clone(), b.clone()]) + .unwrap(); + + let dup = netlist + .insert_gate(two_out_gate(), "dup0".into(), &[a.clone()]) + .unwrap(); + + dup.get_output(1).expose_with_name("y".into()); + + let and_out = and_inst.get_output(0); + let dup_out1 = dup.get_output(1); + drop(dup); + netlist.replace_net_uses(dup_out1, &and_out.clone()).unwrap(); + assert!(netlist.verify().is_ok()); + println!("{}", netlist); +} +#[test] +fn test_replace_multiple_single(){ + let netlist = Netlist::new("example".into()); + let a = netlist.insert_input("a".into()); + let b = netlist.insert_input("b".into()); + + let and_inst = netlist + .insert_gate(and_gate(), "and_0".into(), &[a.clone(), b.clone()]) + .unwrap(); + + let dup = netlist + .insert_gate(two_out_gate(), "dup0".into(), &[a.clone()]) + .unwrap(); + + and_inst.get_output(0).expose_with_name("y".into()); + + let and_out = and_inst.get_output(0); + let dup_out0 = dup.get_output(0); + drop(and_inst); + netlist.replace_net_uses(and_out, &dup_out0.clone()).unwrap(); + assert!(netlist.verify().is_ok()); + println!("{}", netlist); + +} + +#[test] +fn test_replace_multiple_multiple(){ + let netlist = Netlist::new("example".into()); + let a = netlist.insert_input("a".into()); + + let dup1 = netlist + .insert_gate(two_out_gate(), "dup1".into(), &[a.clone()]) + .unwrap(); + + let dup2 = netlist + .insert_gate(two_out_gate(), "dup2".into(), &[a.clone()]) + .unwrap(); + + let dup1_out0 = dup1.get_output(0); + dup1_out0.clone().expose_with_name("y".into()); + let dup2_out1 = dup2.get_output(1); + + drop(dup2); + netlist.replace_net_uses(dup2_out1, &dup1_out0.clone()).unwrap(); + assert!(netlist.verify().is_ok()); + println!("{}", netlist); + +} + diff --git a/tests/learning_tests.rs b/tests/learning_tests.rs deleted file mode 100644 index e5deaef..0000000 --- a/tests/learning_tests.rs +++ /dev/null @@ -1,230 +0,0 @@ -use safety_net::netlist::Netlist; -use safety_net::netlist::Gate; -use safety_net::format_id; -use safety_net::attribute::Parameter; -use safety_net::circuit::{Identifier, Instantiable, Net}; -use safety_net::logic::Logic; -use bitvec::vec::BitVec; - -fn and_gate() -> Gate { - Gate::new_logical("AND".into(), vec!["A".into(), "B".into()], "Y".into()) -} - -fn or_gate() -> Gate { - Gate::new_logical("OR".into(), vec!["A".into(), "B".into()], "Y".into()) -} - -fn not_gate() -> Gate { - Gate::new_logical("NOT".into(), vec!["A".into()], "Y".into()) -} -#[test] -fn test_stage_1(){ - let netlist = Netlist::new("example".to_string()); - let a = netlist.insert_input("a".into()); - let b = netlist.insert_input("b".into()); - let c = netlist.insert_input("c".into()); - let instance = netlist.insert_gate(and_gate(), "inst_0".into(), &[a,b]).unwrap(); - let and_out = instance.get_output(0); - let not_instance = netlist.insert_gate(not_gate(), - "inst_1".into(), &[and_out]).unwrap(); - let not_out = not_instance.get_output(0); - let final_instance = - netlist.insert_gate(or_gate(), - "inst_3".into(), &[c, not_out]).unwrap(); - final_instance.expose_with_name("y".into()); - not_instance.expose_with_name("n".into()); - -} - -#[test] -fn test_stage_2() { - let netlist = Netlist::new("stage2".into()); - - // input - let a = netlist.insert_input("a".into()); - - // not gate - let n_instance = netlist.insert_gate(not_gate(), "inst_0".into(), &[a]).unwrap(); - let n_out = n_instance.get_output(0); - - // constant 1 - let vdd = netlist.insert_constant(Logic::True, "const1".into()).unwrap(); - - // and gate: (NOT a) & 1 - let and_instance = netlist.insert_gate(and_gate(), "inst_1".into(), &[n_out, vdd]).unwrap(); - and_instance.set_attribute("dont_touch".into()); - // expose output - and_instance.expose_with_name("y".into()); - - - // optional: check / print - assert!(netlist.verify().is_ok()); - println!("{}", netlist.to_string()); -} - -#[test] -fn test_stage_3() { - let netlist = Netlist::new("stage3".into()); - let bus = netlist.insert_input_escaped_logic_bus("input_bus".to_string(), 4); - let bit0 = bus[0].clone(); - let n_instance = netlist.insert_gate(not_gate(), "inst_0".into(), &[bit0]).unwrap(); - let n_out = n_instance.get_output(0); - n_instance.expose_with_name("y".into()); - println!("{}", netlist.to_string()); -} - -// Implementing a LUT from the code params.rs - -#[derive(Debug, Clone)] -struct Lut { - lookup_table: BitVec, - id: Identifier, - inputs: Vec, - output: Net, -} - -impl Lut { - fn new(k: usize, lookup_table: usize) -> Self { - let mut bv: BitVec = BitVec::from_element(lookup_table); - bv.truncate(1 << k); - Lut { - lookup_table: bv, - id: format_id!("LUT{k}"), - inputs: (0..k).map(|i| Net::new_logic(format_id!("I{i}"))).collect(), - output: Net::new_logic("O".into()), - } - } - - fn invert(&mut self) { - self.lookup_table = !self.lookup_table.clone(); - } -} - -impl Instantiable for Lut { - fn get_name(&self) -> &Identifier { - &self.id - } - - fn get_input_ports(&self) -> impl IntoIterator { - &self.inputs - } - - fn get_output_ports(&self) -> impl IntoIterator { - std::slice::from_ref(&self.output) - } - - fn has_parameter(&self, id: &Identifier) -> bool { - *id == Identifier::new("INIT".to_string()) - } - - fn get_parameter(&self, id: &Identifier) -> Option { - if self.has_parameter(id) { - Some(Parameter::BitVec(self.lookup_table.clone())) - } else { - None - } - } - - fn set_parameter(&mut self, id: &Identifier, val: Parameter) -> Option { - if !self.has_parameter(id) { - return None; - } - - let old = Some(Parameter::BitVec(self.lookup_table.clone())); - - if let Parameter::BitVec(bv) = val { - self.lookup_table = bv; - } else { - panic!("Invalid parameter type for INIT"); - } - - old - } - - fn parameters(&self) -> impl Iterator { - std::iter::once(( - Identifier::new("INIT".to_string()), - Parameter::BitVec(self.lookup_table.clone()), - )) - } - - fn from_constant(val: Logic) -> Option { - match val { - Logic::True => Some(Self { - lookup_table: BitVec::from_element(1), - id: "VDD".into(), - inputs: vec![], - output: "Y".into(), - }), - Logic::False => Some(Self { - lookup_table: BitVec::from_element(0), - id: "GND".into(), - inputs: vec![], - output: "Y".into(), - }), - _ => None, - } - } - - fn get_constant(&self) -> Option { - match self.id.to_string().as_str() { - "VDD" => Some(Logic::True), - "GND" => Some(Logic::False), - _ => None, - } - } -} - -// to do -#[test] -fn test_stage_4(){ - let netlist: std::rc::Rc> = Netlist::new("example".to_string()); - - // Add the the two inputs - let a = netlist.insert_input("a".into()); - let b = netlist.insert_input("b".into()); - let instance = netlist - .insert_gate(Lut::new(2, 7), "inst_0".into(), &[a, b]) - .unwrap(); - - - // Let's make it an AND gate by inverting the lookup table - instance.get_instance_type_mut().unwrap().invert(); - - // Make this LUT an output - instance.expose_with_name("y".into()); - println!("{netlist}"); - -} - - -// key functions for mutating netlists: -// replace_net_uses(old_net, new_net) -// delete_net_uses(net) -// clean() -// Name management - -#[test] -fn replace_input_with_constant() { - let netlist = Netlist::new("demo".into()); - - // input a, input b - let a = netlist.insert_input("a".into()); - let b = netlist.insert_input("b".into()); - - // AND gate - let and_inst = netlist.insert_gate(and_gate(), "inst_0".into(), &[a.clone(), b.clone()]).unwrap(); - - // constant 1 - let vdd = netlist.insert_constant(Logic::True, "const1".into()).unwrap(); - - // Old API: panics here because `a` is not a DrivenNet - //netlist.replace_net_uses( a.unwrap(), &vdd.unwrap()).unwrap(); - - // New API: works, because we check dynamically - netlist.replace_net_uses_driven(a, &vdd).unwrap(); - - and_inst.expose_with_name("y".into()); - assert!(netlist.verify().is_ok()); - println!("{}", netlist.to_string()); -} diff --git a/tests/multi_output_replace.rs b/tests/multi_output_replace.rs deleted file mode 100644 index dfbe62c..0000000 --- a/tests/multi_output_replace.rs +++ /dev/null @@ -1,47 +0,0 @@ -use safety_net::netlist::{Gate, GateNetlist, Netlist}; - -fn full_adder() -> Gate { - Gate::new_logical_multi( - "FA".into(), - vec!["CIN".into(), "A".into(), "B".into()], - vec!["S".into(), "COUT".into()], - ) -} - -#[test] -fn replace_multi_output_port_usage() { - let netlist: std::rc::Rc = Netlist::new("multi_replace".to_string()); - - // Inputs - let a = netlist.insert_input("a".into()); - let b = netlist.insert_input("b".into()); - let cin = netlist.insert_input("cin".into()); - - // Instantiate a multi-output gate (FA with S and COUT) - let fa = netlist - .insert_gate(full_adder(), "inst_0".into(), &[cin, a, b]) - .unwrap(); - - // Expose SUM (output 0) as a top-level output initially - fa.expose_net(&fa.get_net(0)).unwrap(); - print!("{}", netlist); - - // Sanity: only SUM is a top-level output now - assert!(fa.get_output(0).is_top_level_output()); - assert!(!fa.get_output(1).is_top_level_output()); - - // Replace uses of SUM with CARRY using the multi-output safe API - // Drop the FA netref to avoid extra outstanding references - let sum = fa.get_output(0); - let carry = fa.get_output(1); - drop(fa); - netlist.replace_output_uses(sum, carry).unwrap(); - - // Re-grab the instance to check the outputs (should be unchanged) - let fa = netlist.last().unwrap(); - // SUM should still be the top-level output; CARRY should not - assert!(fa.get_output(0).is_top_level_output()); - assert!(!fa.get_output(1).is_top_level_output()); - - assert!(netlist.verify().is_ok()); -}