diff --git a/assets/not.svg b/assets/not.svg new file mode 100644 index 0000000..82b755f --- /dev/null +++ b/assets/not.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 4c2bf39..293207e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -18,7 +18,6 @@ use crate::{ config::CanvasConfig, connection_manager::{Connection, ConnectionManager}, drag::Drag, - module::Module, }; pub const PANEL_BUTTON_MAX_HEIGHT: f32 = 50.0; @@ -325,6 +324,7 @@ impl App { } fn is_on(&self, pin: Pin) -> bool { + let pin = pin.is_passthrough(&self.db).unwrap_or(pin); let Some(v) = self.simulator.current.get(&pin) else { return false; }; @@ -449,6 +449,7 @@ impl App { self.draw_panel_button(ui, InstanceKind::Gate(GateKind::Nor)); self.draw_panel_button(ui, InstanceKind::Gate(GateKind::Xor)); self.draw_panel_button(ui, InstanceKind::Gate(GateKind::Xnor)); + self.draw_panel_button(ui, InstanceKind::Gate(GateKind::Not)); self.draw_panel_button(ui, InstanceKind::Power); self.draw_panel_button(ui, InstanceKind::Lamp); self.draw_panel_button(ui, InstanceKind::Clock); @@ -538,10 +539,7 @@ impl App { InstanceKind::Wire => self.db.circuit.new_wire(Wire::new_at(pos)), InstanceKind::Lamp => self.db.circuit.new_lamp(Lamp { pos }), InstanceKind::Clock => self.db.circuit.new_clock(Clock { pos }), - InstanceKind::Module(c) => self.db.new_module_with_flattening(Module { - pos, - definition_index: c, - }), + InstanceKind::Module(c) => self.db.new_module(c, pos), }; self.set_drag(Drag::Canvas(crate::drag::CanvasDrag::Single { id, @@ -556,7 +554,7 @@ impl App { { let mut ids = Vec::new(); for (id, m) in &self.circuit().modules { - if m.definition_index == i { + if m.definition_id == i { ids.push(id); } } @@ -730,7 +728,7 @@ impl App { Self::draw_grid(ui, canvas_rect, self.viewport_offset); let mouse_clicked_canvas = resp.clicked(); - let mouse_dragging_canvas = resp.dragged(); + let mouse_dragging_canvas = resp.dragged_by(egui::PointerButton::Primary); let double_clicked = ui.input(|i| { i.pointer .button_double_clicked(egui::PointerButton::Primary) @@ -953,7 +951,7 @@ impl App { } } - fn draw_instance_graphics_new( + fn draw_instance_graphics( &mut self, ui: &mut Ui, graphics: assets::InstanceGraphics, @@ -1028,7 +1026,7 @@ impl App { let gate = self.db.circuit.get_gate(id); (gate.pos, gate.kind) }; - self.draw_instance_graphics_new(ui, kind.graphics(), self.adjusted_pos(pos), id); + self.draw_instance_graphics(ui, kind.graphics(), self.adjusted_pos(pos), id); } fn draw_power(&mut self, ui: &mut Ui, id: InstanceId) { @@ -1036,7 +1034,7 @@ impl App { let power = self.db.circuit.get_power(id); (power.pos, power.graphics()) }; - self.draw_instance_graphics_new(ui, graphics, self.adjusted_pos(pos), id); + self.draw_instance_graphics(ui, graphics, self.adjusted_pos(pos), id); } fn draw_lamp(&mut self, ui: &mut Ui, id: InstanceId) { @@ -1062,7 +1060,7 @@ impl App { } } - self.draw_instance_graphics_new(ui, graphics, pos, id); + self.draw_instance_graphics(ui, graphics, pos, id); } fn draw_clock(&mut self, ui: &mut Ui, id: InstanceId) { @@ -1071,24 +1069,25 @@ impl App { (clock.pos, clock.graphics()) }; let pos = self.adjusted_pos(pos); - self.draw_instance_graphics_new(ui, graphics, pos, id); + self.draw_instance_graphics(ui, graphics, pos, id); } fn draw_module(&mut self, ui: &mut Ui, id: InstanceId) { let (pos, definition_index) = { let module = self.db.circuit.get_module(id); - (module.pos, module.definition_index) + (module.pos, module.definition_id) }; let screen_center = pos - self.viewport_offset; let (name, pins, pin_offsets) = { let definition = self.db.circuit.get_module(id).definition(&self.db); let name = definition.name.clone(); - let pins = definition.get_unconnected_pins(&self.db, id); - + let pins = self.db.circuit.get_module(id).pins(); let pin_offsets: Vec = pins .iter() - .map(|pin| definition.calculate_pin_offset(&self.db, pin, &self.canvas_config)) + .map(|pin| { + definition.calculate_pin_offset(&self.db, &pins, pin, &self.canvas_config) + }) .collect(); (name, pins, pin_offsets) @@ -1128,7 +1127,7 @@ impl App { })); } - for (pin_index, (&pin, &pin_offset)) in pins.iter().zip(pin_offsets.iter()).enumerate() { + for (&pin, &pin_offset) in pins.iter().zip(pin_offsets.iter()) { let pin_pos_world = pos + pin_offset; let pin_screen_pos = self.adjusted_pos(pin_pos_world); @@ -1140,7 +1139,7 @@ impl App { ui.painter() .circle_filled(pin_screen_pos, self.canvas_config.base_pin_size, pin_color); - let has_current = self.is_on(Pin::new(id, pin_index as u32, pin.kind)); + let has_current = self.is_on(pin); if has_current { ui.painter().circle_stroke( @@ -1155,15 +1154,12 @@ impl App { Vec2::splat(self.canvas_config.base_pin_size + PIN_HOVER_THRESHOLD), ); let pin_resp = ui.allocate_rect(pin_rect, Sense::drag()); - let pin_obj = Pin::new(id, pin_index as u32, pin.kind); if pin_resp.hovered() { - self.hovered = Some(Hover::Pin(pin_obj)); + self.hovered = Some(Hover::Pin(pin)); } if pin_resp.dragged() { self.selected.clear(); - self.set_drag(Drag::PinToWire { - source_pin: pin_obj, - }); + self.set_drag(Drag::PinToWire { source_pin: pin }); } } } @@ -1259,7 +1255,7 @@ impl App { && let Some(split_point) = self.wire_branching_action_point(mouse, id) { ui.painter().circle_filled( - split_point, + self.adjusted_pos(split_point), PIN_HOVER_THRESHOLD, COLOR_HOVER_PIN_TO_WIRE, ); @@ -1273,7 +1269,7 @@ impl App { && let Some(split_point) = self.wire_branching_action_point(mouse, id) { ui.painter().circle_filled( - split_point, + self.adjusted_pos(split_point), PIN_HOVER_THRESHOLD, COLOR_HOVER_PIN_TO_WIRE, ); @@ -1551,30 +1547,31 @@ impl App { fn debug_string(&self, ui: &Ui) -> String { let mut out = String::new(); + + // App state (compact) + writeln!(out, "======================================").ok(); + writeln!(out, " APP STATE").ok(); + writeln!(out, "======================================").ok(); let mouse_pos_world = self.mouse_pos_world(ui); writeln!(out, "mouse: {mouse_pos_world:?}").ok(); - writeln!(out, "hovered: {:?}", self.hovered).ok(); writeln!(out, "drag: {:?}", self.drag).ok(); - writeln!(out, "viewport_offset: {:?}", self.viewport_offset).ok(); - writeln!(out, "potential_conns: {}", self.potential_connections.len()).ok(); - writeln!(out, "clipboard: {:?}", self.clipboard).ok(); writeln!(out, "selected: {:?}", self.selected).ok(); - writeln!(out, "editing_label: {:?}", self.editing_label).ok(); - writeln!(out, "label_edit_buffer: {}", self.label_edit_buffer).ok(); writeln!(out, "viewing_module: {:?}", self.viewing_module).ok(); // Simulation status - writeln!(out, "\n=== Simulation Status ===").ok(); - writeln!(out, "needs update {}", self.current_dirty).ok(); + writeln!(out).ok(); + writeln!(out, "======================================").ok(); + writeln!(out, " SIMULATION").ok(); + writeln!(out, "======================================").ok(); match self.simulator.status { SimulationStatus::Stable { iterations } => { - writeln!(out, "Status: STABLE (after {iterations} iterations)").ok(); + writeln!(out, "Status: STABLE ({iterations} iters)").ok(); } SimulationStatus::Unstable { max_reached } => { if max_reached { let iters = self.simulator.last_iterations; - writeln!(out, "Status: UNSTABLE (max iterations: {iters})").ok(); + writeln!(out, "Status: UNSTABLE (max: {iters})").ok(); } else { writeln!(out, "Status: UNSTABLE").ok(); } @@ -1583,44 +1580,31 @@ impl App { writeln!(out, "Status: RUNNING...").ok(); } } - let iters = self.simulator.last_iterations; - writeln!(out, "Iterations: {iters}").ok(); - - // Clock controller state - writeln!(out, "\n--- Clock Controller ---").ok(); - writeln!(out, "State: {:?}", self.clock_controller.state).ok(); writeln!( out, - "Tick interval: {:.2}s", - self.clock_controller.tick_interval + "Clock: {:?}, interval: {:.2}s", + self.clock_controller.state, self.clock_controller.tick_interval ) .ok(); - writeln!(out, "Voltage: {}", self.clock_controller.voltage).ok(); - - if self.potential_connections.is_empty() { - writeln!(out, "\nPotential Connections: none").ok(); - } else { - writeln!(out, "\nPotential Connections:").ok(); - for c in &self.potential_connections { - writeln!(out, " {}", c.display(self.circuit())).ok(); - } - } - - writeln!(out, "\n{}", self.connection_manager.debug_info()).ok(); - - writeln!(out, "\n").ok(); - out.write_str(&self.circuit().display(&self.db)).ok(); + // Circuit instances (main content) + writeln!(out).ok(); + out.write_str(&self.circuit().display(&self.db, Some(&self.simulator))) + .ok(); + // Module definitions (at the bottom, summary only) if !self.db.module_definitions.is_empty() { - writeln!(out, "\nModule Def:").ok(); - let mut iter = self.db.module_definitions.iter(); - if let Some((id, first)) = iter.next() { - writeln!(out, " {}", first.display_definition(&self.db, id)).ok(); - } - for (id, m) in iter { - writeln!(out).ok(); - writeln!(out, " {}", m.display_definition(&self.db, id)).ok(); + writeln!(out).ok(); + writeln!(out, "======================================").ok(); + writeln!( + out, + " MODULE DEFINITIONS ({} total)", + self.db.module_definitions.len() + ) + .ok(); + writeln!(out, "======================================").ok(); + for (id, m) in &self.db.module_definitions { + writeln!(out, "{}", m.display_definition(&self.db, id)).ok(); } } @@ -1691,7 +1675,7 @@ impl App { } InstanceKind::Module(_) => { let cc = self.db.circuit.get_module(id); - object_pos.push(ClipBoardItem::Module(cc.definition_index, center - cc.pos)); + object_pos.push(ClipBoardItem::Module(cc.definition_id, center - cc.pos)); } } } @@ -1742,12 +1726,13 @@ impl App { self.selected.insert(id); } ClipBoardItem::Module(def_index, offset) => { - let id = self.db.new_module_with_flattening(Module { - pos: mouse - offset, - definition_index: def_index, - }); - self.connection_manager.mark_instance_dirty(id); - self.selected.insert(id); + // TODO: Modules + // let id = self.db.new_module_with_flattening(Module { + // pos: mouse - offset, + // definition_index: def_index, + // }); + // self.connection_manager.mark_instance_dirty(id); + // self.selected.insert(id); } ClipBoardItem::Lamp(offset) => { let id = self.db.circuit.new_lamp(Lamp { @@ -1886,7 +1871,7 @@ impl App { pos - self.viewport_offset } - fn mouse_pos_world(&self, ui: &Ui) -> Option { + pub fn mouse_pos_world(&self, ui: &Ui) -> Option { ui.ctx() .pointer_interact_pos() .map(|p| p + self.viewport_offset) diff --git a/src/assets.rs b/src/assets.rs index 70dfc70..2111759 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -1,3 +1,4 @@ +/// Pins sorted in order: First input pins from top to bottom then output pins. use std::fmt::Display; use egui::{ImageSource, Vec2, include_image}; @@ -35,6 +36,10 @@ pub static NAND_GRAPHICS: InstanceGraphics = InstanceGraphics { svg: include_image!("../assets/nand.svg"), // TODO: offset must be made from the base_gate_size otherwise it will be unaligned when gates resize pins: &[ + PinGraphics { + kind: PinKind::Input, + offset: Vec2::new(-37.0, -14.5), + }, PinGraphics { kind: PinKind::Input, offset: Vec2::new(-37.0, 14.5), @@ -43,16 +48,16 @@ pub static NAND_GRAPHICS: InstanceGraphics = InstanceGraphics { kind: PinKind::Output, offset: Vec2::new(40.0, 0.2), }, - PinGraphics { - kind: PinKind::Input, - offset: Vec2::new(-37.0, -14.5), - }, ], }; pub static AND_GRAPHICS: InstanceGraphics = InstanceGraphics { svg: include_image!("../assets/and.svg"), pins: &[ + PinGraphics { + kind: PinKind::Input, + offset: Vec2::new(-37.0, -14.5), + }, PinGraphics { kind: PinKind::Input, offset: Vec2::new(-37.0, 14.5), @@ -61,16 +66,16 @@ pub static AND_GRAPHICS: InstanceGraphics = InstanceGraphics { kind: PinKind::Output, offset: Vec2::new(40.0, 0.2), }, - PinGraphics { - kind: PinKind::Input, - offset: Vec2::new(-37.0, -14.5), - }, ], }; pub static OR_GRAPHICS: InstanceGraphics = InstanceGraphics { svg: include_image!("../assets/or.svg"), pins: &[ + PinGraphics { + kind: PinKind::Input, + offset: Vec2::new(-37.0, -14.5), + }, PinGraphics { kind: PinKind::Input, offset: Vec2::new(-37.0, 14.5), @@ -79,16 +84,16 @@ pub static OR_GRAPHICS: InstanceGraphics = InstanceGraphics { kind: PinKind::Output, offset: Vec2::new(40.0, 0.2), }, - PinGraphics { - kind: PinKind::Input, - offset: Vec2::new(-37.0, -14.5), - }, ], }; pub static NOR_GRAPHICS: InstanceGraphics = InstanceGraphics { svg: include_image!("../assets/nor.svg"), pins: &[ + PinGraphics { + kind: PinKind::Input, + offset: Vec2::new(-37.0, -14.5), + }, PinGraphics { kind: PinKind::Input, offset: Vec2::new(-37.0, 14.5), @@ -97,16 +102,16 @@ pub static NOR_GRAPHICS: InstanceGraphics = InstanceGraphics { kind: PinKind::Output, offset: Vec2::new(40.0, 0.2), }, - PinGraphics { - kind: PinKind::Input, - offset: Vec2::new(-37.0, -14.5), - }, ], }; pub static XOR_GRAPHICS: InstanceGraphics = InstanceGraphics { svg: include_image!("../assets/xor.svg"), pins: &[ + PinGraphics { + kind: PinKind::Input, + offset: Vec2::new(-37.0, -14.5), + }, PinGraphics { kind: PinKind::Input, offset: Vec2::new(-37.0, 14.5), @@ -115,16 +120,16 @@ pub static XOR_GRAPHICS: InstanceGraphics = InstanceGraphics { kind: PinKind::Output, offset: Vec2::new(40.0, 0.2), }, - PinGraphics { - kind: PinKind::Input, - offset: Vec2::new(-37.0, -14.5), - }, ], }; pub static XNOR_GRAPHICS: InstanceGraphics = InstanceGraphics { svg: include_image!("../assets/xnor.svg"), pins: &[ + PinGraphics { + kind: PinKind::Input, + offset: Vec2::new(-37.0, -14.5), + }, PinGraphics { kind: PinKind::Input, offset: Vec2::new(-37.0, 14.5), @@ -133,9 +138,19 @@ pub static XNOR_GRAPHICS: InstanceGraphics = InstanceGraphics { kind: PinKind::Output, offset: Vec2::new(40.0, 0.2), }, + ], +}; + +pub static NOT_GRAPHICS: InstanceGraphics = InstanceGraphics { + svg: include_image!("../assets/not.svg"), + pins: &[ PinGraphics { kind: PinKind::Input, - offset: Vec2::new(-37.0, -14.5), + offset: Vec2::new(-40.0, 0.0), + }, + PinGraphics { + kind: PinKind::Output, + offset: Vec2::new(40.0, 0.0), }, ], }; diff --git a/src/connection_manager.rs b/src/connection_manager.rs index a826ff9..318a1cd 100644 --- a/src/connection_manager.rs +++ b/src/connection_manager.rs @@ -8,22 +8,46 @@ use std::collections::{HashMap, HashSet}; const SPATIAL_INDEX_GRID_SIZE: f32 = 100.0; +#[derive(serde::Deserialize, serde::Serialize, Copy, PartialEq, Eq, Debug, Clone)] +pub enum ConnectionKind { + SINGLE, + // Bi directional connection is a special kind where both ends can be input or output. + BI, +} + // A normalized, order-independent connection between two pins #[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] pub struct Connection { pub a: Pin, pub b: Pin, + pub kind: ConnectionKind, } impl Connection { pub fn new(p1: Pin, p2: Pin) -> Self { - Self { a: p2, b: p1 } + Self { + a: p2, + b: p1, + kind: ConnectionKind::SINGLE, + } + } + + pub fn new_bi(p1: Pin, p2: Pin) -> Self { + Self { + a: p2, + b: p1, + kind: ConnectionKind::BI, + } } pub fn involves_instance(&self, id: InstanceId) -> bool { self.a.ins == id || self.b.ins == id } + pub fn involves_pin(&self, pin: &Pin) -> bool { + self.a == *pin || self.b == *pin + } + pub fn display(&self, circuit: &Circuit) -> String { format!( "{} <-> {}", @@ -32,6 +56,24 @@ impl Connection { ) } + /// Short display for connection list: "And[0v1].pin#0 -> Lamp[1v1].pin#0" + pub fn display_short(&self, circuit: &Circuit, db: &DB) -> String { + if self.kind == ConnectionKind::SINGLE { + format!( + "{} -> {}", + self.a.display_short(circuit, db), + self.b.display_short(circuit, db) + ) + } else { + format!( + "{} <-> {}", + self.a.display_short(circuit, db), + self.b.display_short(circuit, db) + ) + } + } + + // Returns a tuple with the given pin first and then second pin pub fn get_pin_first(&self, pin: Pin) -> Option<(Pin, Pin)> { if self.a == pin { Some((self.a, self.b)) @@ -41,6 +83,17 @@ impl Connection { None } } + + // Returns a tuple with the given pin first and then second pin + pub fn get_other_pin(&self, pin: Pin) -> Pin { + if self.a == pin { + self.b + } else if self.b == pin { + self.a + } else { + unreachable!(); + } + } } impl PartialEq for Connection { @@ -326,6 +379,16 @@ impl ConnectionManager { let mut connections_to_keep = HashSet::new(); for connection in &db.circuit.connections { + let is_module_connection = { + db.get_module_owner(connection.a.ins).is_some() + || db.get_module_owner(connection.b.ins).is_some() + }; + // Keep module connections always, as they're structural user cannot remove them. + if is_module_connection { + connections_to_keep.insert(*connection); + continue; + } + let keep_connection = !self.dirty_instances.contains(&connection.a.ins) && !self.dirty_instances.contains(&connection.b.ins); diff --git a/src/db.rs b/src/db.rs index 82eb44b..33cdf0c 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::fmt::Display; use std::hash::Hash; @@ -6,6 +6,7 @@ use egui::{Pos2, Vec2, pos2}; use slotmap::{SecondaryMap, SlotMap}; use crate::assets::PinKind; +use crate::connection_manager::ConnectionKind; use crate::{ assets::{self}, config::CanvasConfig, @@ -63,9 +64,6 @@ pub struct Circuit { pub connections: HashSet, // Labels pub labels: SlotMap, - // Pin mappings for modules: external pin -> internal pin - #[serde(skip)] - pub module_pin_mappings: SecondaryMap>, } impl Circuit { pub fn ty(&self, id: InstanceId) -> InstanceKind { @@ -131,11 +129,12 @@ impl Circuit { k } - pub fn new_module(&mut self, c: crate::module::Module) -> InstanceId { - let k = self.types.insert(InstanceKind::Module(c.definition_index)); - self.modules.insert(k, c); - k - } + // TODO: Module creation is messy cannot use this + // pub fn new_module(&mut self, c: crate::module::Module) -> InstanceId { + // let k = self.types.insert(InstanceKind::Module(c.definition_index)); + // self.modules.insert(k, c); + // k + // } pub fn get_gate(&self, id: InstanceId) -> &Gate { self.gates.get(id).expect("gate not found") @@ -225,131 +224,196 @@ impl Circuit { self.labels.keys().collect() } - pub fn display(&self, db: &DB) -> String { + pub fn display(&self, db: &DB, simulator: Option<&crate::simulator::Simulator>) -> String { let mut out = String::new(); use std::fmt::Write as _; - writeln!(out, "circuit.types:").ok(); - for (id, _) in &self.types { - writeln!(out, " {}: {:?}", id, self.ty(id)).ok(); - } - writeln!( - out, - "\ncounts: gates={}, powers={}, lamps={}, clocks={}, wires={}, modules={}, conns={}", - self.gates.len(), - self.powers.len(), - self.lamps.len(), - self.clocks.len(), - self.wires.len(), - self.modules.len(), - self.connections.len(), - ) - .ok(); - - if !self.gates.is_empty() { - writeln!(out, "\nGates:").ok(); - for (id, g) in &self.gates { - writeln!(out, " {}", g.display(id)).ok(); - for (i, pin) in g.kind.graphics().pins.iter().enumerate() { - let pin_offset = pin.offset; - let p = g.pos + pin_offset; - let pin_instance = Pin::new(id, i as u32, pin.kind); - writeln!( - out, - " {} at ({:.1},{:.1})", - pin_instance.display(self), - p.x, - p.y - ) - .ok(); - } - } - } - if !self.powers.is_empty() { - writeln!(out, "\nPowers:").ok(); - for (id, p) in &self.powers { - writeln!(out, " {}", p.display(id)).ok(); - for (i, pin) in p.graphics().pins.iter().enumerate() { - let pin_offset = pin.offset; - let pp = p.pos + pin_offset; - let pin_instance = Pin::new(id, i as u32, pin.kind); - writeln!( - out, - " {} at ({:.1},{:.1})", - pin_instance.display(self), - pp.x, - pp.y - ) - .ok(); - } + let total = self.types.len(); + writeln!(out, "======================================").ok(); + writeln!(out, " INSTANCES ({total} total)").ok(); + writeln!(out, "======================================").ok(); + + // Collect all instances with their display info + let instances: Vec<(InstanceId, InstanceKind)> = + self.types.iter().map(|(id, k)| (id, *k)).collect(); + let instance_count = instances.len(); + + for (idx, (id, kind)) in instances.iter().enumerate() { + let is_last_instance = idx == instance_count - 1; + let branch = if is_last_instance { "`-" } else { "|-" }; + let cont = if is_last_instance { " " } else { "| " }; + + // Instance header + let header = self.instance_header(*id, *kind, db); + writeln!(out, "{branch} {header}").ok(); + + // Get pins for this instance + let pins = self.pins_of(*id, db); + let pin_count = pins.len(); + + for (pin_idx, pin) in pins.iter().enumerate() { + let is_last_pin = pin_idx == pin_count - 1; + let pin_branch = if is_last_pin { "`-" } else { "|-" }; + + // Get connections for this pin + let connected = self.connected_pins(*pin); + let kind_str = match pin.kind { + PinKind::Input => "In", + PinKind::Output => "Out", + }; + let arrow = match pin.kind { + PinKind::Input => "<-", + PinKind::Output => "->", + }; + + let conn_str = if connected.is_empty() { + "(unconnected)".to_owned() + } else { + connected + .iter() + .map(|p| p.display_short(self, db)) + .collect::>() + .join(", ") + }; + + // Get pin state if simulator is available + let state_str = if let Some(sim) = simulator { + if let Some(&value) = sim.current.get(pin) { + match value { + crate::simulator::Value::One => " O", + crate::simulator::Value::Zero => " N", + crate::simulator::Value::X => " X", + } + } else { + "" + } + } else { + "" + }; + + writeln!( + out, + "{}{} #{} ({}) {} {}{}", + cont, pin_branch, pin.index, kind_str, arrow, conn_str, state_str + ) + .ok(); } - } - if !self.lamps.is_empty() { - writeln!(out, "\nLamps:").ok(); - for (id, lamp) in &self.lamps { - writeln!(out, " {}", lamp.display(id)).ok(); - for (i, pin) in lamp.graphics().pins.iter().enumerate() { - let pin_offset = pin.offset; - let p = lamp.pos + pin_offset; - let pin_instance = Pin::new(id, i as u32, pin.kind); - writeln!( - out, - " {} at ({:.1},{:.1})", - pin_instance.display(self), - p.x, - p.y - ) - .ok(); + // Show instance members for modules with indentation + if let InstanceKind::Module(_) = kind { + let module = self.get_module(*id); + let member_count = module.instance_members.len(); + for (member_idx, member_id) in module.instance_members.iter().enumerate() { + let is_last_member = member_idx == member_count - 1; + let member_branch = if is_last_member { "`-" } else { "|-" }; + let member_cont = if is_last_member { + format!("{cont} ") + } else { + format!("{cont}| ") + }; + + // Member header + let member_kind = self.ty(*member_id); + let member_header = self.instance_header(*member_id, member_kind, db); + writeln!(out, "{cont}{member_branch} {member_header}").ok(); + + // Get pins for this member + let member_pins = self.pins_of(*member_id, db); + let member_pin_count = member_pins.len(); + + for (member_pin_idx, member_pin) in member_pins.iter().enumerate() { + let is_last_member_pin = member_pin_idx == member_pin_count - 1; + let member_pin_branch = if is_last_member_pin { "`-" } else { "|-" }; + + // Get connections for this member pin + let member_connected = self.connected_pins(*member_pin); + let member_kind_str = match member_pin.kind { + PinKind::Input => "In", + PinKind::Output => "Out", + }; + let member_arrow = match member_pin.kind { + PinKind::Input => "<-", + PinKind::Output => "->", + }; + + let member_conn_str = if member_connected.is_empty() { + "(unconnected)".to_owned() + } else { + member_connected + .iter() + .map(|p| p.display_short(self, db)) + .collect::>() + .join(", ") + }; + + // Get pin state if simulator is available + let member_state_str = if let Some(sim) = simulator { + if let Some(&value) = sim.current.get(member_pin) { + match value { + crate::simulator::Value::One => " O", + crate::simulator::Value::Zero => " N", + crate::simulator::Value::X => " X", + } + } else { + "" + } + } else { + "" + }; + + writeln!( + out, + "{member_cont}{member_pin_branch} #{} ({}) {} {}{}", + member_pin.index, + member_kind_str, + member_arrow, + member_conn_str, + member_state_str + ) + .ok(); + } } } } - if !self.clocks.is_empty() { - writeln!(out, "\nClocks:").ok(); - for (id, clock) in &self.clocks { - writeln!(out, " {}", clock.display(id)).ok(); - for (i, pin) in clock.graphics().pins.iter().enumerate() { - let pin_offset = pin.offset; - let p = clock.pos + pin_offset; - let pin_instance = Pin::new(id, i as u32, pin.kind); - writeln!( - out, - " {} at ({:.1},{:.1})", - pin_instance.display(self), - p.x, - p.y - ) - .ok(); - } - } + writeln!(out).ok(); + writeln!(out, "======================================").ok(); + writeln!(out, " CONNECTIONS ({} total)", self.connections.len()).ok(); + writeln!(out, "======================================").ok(); + let mut sorted_connections: Vec<_> = self.connections.iter().collect(); + sorted_connections.sort_by_key(|c| { + let a_is_input = c.a.kind == PinKind::Input; + let b_is_input = c.b.kind == PinKind::Input; + (a_is_input, b_is_input) + }); + for c in sorted_connections { + writeln!(out, "{}", c.display_short(self, db)).ok(); } - if !self.wires.is_empty() { - writeln!(out, "\nWires:").ok(); - for (id, w) in &self.wires { - writeln!(out, " {}", w.display(id)).ok(); - for pin in self.pins_of(id, db) { - writeln!(out, " {}", pin.display_alone()).ok(); - } - } - } + out + } - if !self.modules.is_empty() { - writeln!(out, "\nModules:").ok(); - for (id, m) in &self.modules { - writeln!(out, " {id} (module instance)").ok(); + /// Short header for an instance (e.g., "AND [0v1]" or "Power [1v1] ON") + fn instance_header(&self, id: InstanceId, kind: InstanceKind, db: &DB) -> String { + match kind { + InstanceKind::Gate(gk) => format!("{gk:?} [{id}]"), + InstanceKind::Power => { + let p = self.get_power(id); + let state = if p.on { "ON" } else { "OFF" }; + format!("Power [{id}] {state}") } - } - - if !self.connections.is_empty() { - writeln!(out, "\nConnections:").ok(); - for c in &self.connections { - writeln!(out, " {}", c.display(self)).ok(); + InstanceKind::Wire => format!("Wire [{id}]"), + InstanceKind::Lamp => format!("Lamp [{id}]"), + InstanceKind::Clock => format!("Clock [{id}]"), + InstanceKind::Module(def_id) => { + let name = db + .module_definitions + .get(def_id) + .map(|d| d.name.as_str()) + .unwrap_or("?"); + format!("Module \"{name}\" [{id}]") } } - - out } // Connections @@ -389,6 +453,16 @@ impl Circuit { out } + pub fn connections_containing(&self, pin: Pin) -> Vec { + let mut res = Vec::new(); + for c in &self.connections { + if c.involves_pin(&pin) { + res.push(*c); + } + } + res + } + pub fn pins_of(&self, id: InstanceId, db: &DB) -> Vec { match self.ty(id) { InstanceKind::Gate(gk) => { @@ -450,10 +524,7 @@ impl Circuit { .map(|(i, p)| Pin::new(id, i as u32, p.kind)) .collect() } - InstanceKind::Module(def_id) => { - let module_def = db.get_module_def(def_id); - module_def.get_unconnected_pins(db, id) - } + InstanceKind::Module(def_id) => self.get_module(id).pins(), } } @@ -522,7 +593,8 @@ impl Circuit { } InstanceKind::Module(def_id) => { let module_def = db.get_module_def(def_id); - module_def.calculate_pin_offset(db, &pin, canvas_config) + let module = db.circuit.get_module(pin.ins); + module_def.calculate_pin_offset(db, &module.pins(), &pin, canvas_config) } } } @@ -637,9 +709,6 @@ pub struct DB { pub circuit: Circuit, // Definition of modules created by the user pub module_definitions: SlotMap, - // Maps instances to their parent module (if they're part of a module) - // Key: instance ID, Value: parent module ID - pub module_instances: SecondaryMap, } impl DB { @@ -652,24 +721,22 @@ impl DB { /// Returns the parent module ID if this instance is part of a module, /// or None if it's a top-level instance. pub fn get_module_owner(&self, id: InstanceId) -> Option { - self.module_instances.get(id).copied() + for (ins, module) in &self.circuit.modules { + if module.instance_members.contains(&id) { + return Some(ins); + } + } + None } /// Get all instances that belong to a specific module pub fn get_instances_for_module(&self, module_id: InstanceId) -> Vec { - self.module_instances - .iter() - .filter_map( - |(id, parent)| { - if *parent == module_id { Some(id) } else { None } - }, - ) - .collect() + self.circuit.get_module(module_id).instance_members.clone() } /// Check if an instance is hidden from UI (i.e., it's part of a module) pub fn is_hidden(&self, id: InstanceId) -> bool { - self.module_instances.contains_key(id) + self.get_module_owner(id).is_some() } /// Get all instances that are visible in UI (not hidden) @@ -693,48 +760,24 @@ impl DB { fn remove_single_instance(&mut self, id: InstanceId) { // Remove from circuit self.circuit.remove_single_instance(id); - // Remove from module_instances tracking - self.module_instances.remove(id); } /// Create a new module instance and automatically flatten its internal components - /// Creates connections between module boundary pins and internal component pins - /// - /// Note: This requires temporarily cloning the definition to avoid borrow checker issues. - /// In the future, we could optimize this with better data structure design. - pub fn new_module_with_flattening(&mut self, module: crate::module::Module) -> InstanceId { - let definition_id = module.definition_index; - - // Create the module instance first - let module_id = self.circuit.new_module(module); - - // Clone the definition to avoid borrow conflicts + pub fn new_module(&mut self, definition_id: ModuleDefId, pos: Pos2) -> InstanceId { + // TODO: Currently the way module is created is messy. First allocate ID and then flatten + // the module into circuit and then insert it in the circuit. It should just become one + // function. + let module_id = self + .circuit + .types + .insert(InstanceKind::Module(definition_id)); + // TODO: This requires temporarily cloning the definition to avoid borrow checker issues. + // In the future, we could optimize this with better data structure design. let definition = self.get_module_def(definition_id).clone(); - - // Clone module_definitions to avoid borrow conflict let module_defs = self.module_definitions.clone(); + let module = definition.flatten_into_circuit(definition_id, module_id, pos, self); - // Create a temporary DB with only the definitions we need - let temp_db = Self { - circuit: Circuit::default(), // Not used - module_definitions: module_defs, - module_instances: SecondaryMap::new(), - }; - - // Flatten it and store the pin mapping - let pin_mapping = definition.flatten_into_circuit( - &mut self.circuit, - module_id, - definition_id, - &temp_db, - &mut self.module_instances, - ); - - // Store the pin mapping - self.circuit - .module_pin_mappings - .insert(module_id, pin_mapping); - + self.circuit.modules.insert(module_id, module); module_id } @@ -907,6 +950,7 @@ pub enum GateKind { Nor, Xor, Xnor, + Not, } #[derive(serde::Deserialize, serde::Serialize, Copy, Debug, Clone)] @@ -924,6 +968,7 @@ impl GateKind { Self::Nor => assets::NOR_GRAPHICS.clone(), Self::Xor => assets::XOR_GRAPHICS.clone(), Self::Xnor => assets::XNOR_GRAPHICS.clone(), + Self::Not => assets::NOT_GRAPHICS.clone(), } } } @@ -1108,10 +1153,7 @@ impl Pin { } InstanceKind::Module(_) => format!("Module {}", self.ins), }; - format!( - "{:?} pin#{} in {} ", - self.kind, self.index, instance_display, - ) + format!("{:?} #{} in {} ", self.kind, self.index, instance_display,) } pub fn pos(&self, db: &DB, canvas_config: &CanvasConfig) -> Pos2 { @@ -1123,131 +1165,41 @@ impl Pin { format!("pin#{} {:?}", self.index, self.kind) } - pub fn new(ins: InstanceId, index: u32, kind: PinKind) -> Self { - Self { ins, index, kind } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use egui::Pos2; - - #[test] - fn test_new_module_with_automatic_flattening() { - // Create a module definition - let mut definition_circuit = Circuit::default(); - let gate = Gate { - pos: Pos2::ZERO, - kind: GateKind::And, - }; - let _gate_id = definition_circuit.new_gate(gate); - - let module_def = crate::module::ModuleDefinition { - name: "TestModule".to_owned(), - circuit: definition_circuit, - }; - - // Create DB and add definition - let mut db = DB::default(); - let def_id = db.module_definitions.insert(module_def); - - // Create module using new_module_with_flattening - let module = crate::module::Module { - pos: Pos2::ZERO, - definition_index: def_id, + /// Short display for connections: "And[0v1].pin#0" + pub fn display_short(&self, circuit: &Circuit, db: &DB) -> String { + let kind = circuit.ty(self.ins); + let type_name = match kind { + InstanceKind::Gate(gk) => format!("{gk:?}"), + InstanceKind::Power => "Power".to_owned(), + InstanceKind::Wire => "Wire".to_owned(), + InstanceKind::Lamp => "Lamp".to_owned(), + InstanceKind::Clock => "Clock".to_owned(), + InstanceKind::Module(def_id) => { + let name = db + .module_definitions + .get(def_id) + .map(|d| d.name.as_str()) + .unwrap_or("?"); + format!("Mod:{name}") + } }; - let module_id = db.new_module_with_flattening(module); - - // Check that flattening happened automatically - let internal_instances = db.get_instances_for_module(module_id); - assert_eq!( - internal_instances.len(), - 1, - "Should have auto-flattened 1 instance" - ); - - // Pin mapping should be stored - assert!(db.circuit.module_pin_mappings.contains_key(module_id)); - let pin_map = db - .circuit - .module_pin_mappings - .get(module_id) - .expect("module pin mappings should exist"); - assert_eq!(pin_map.len(), 3, "AND gate has 3 pins"); + format!("{}[{}]#{}", type_name, self.ins, self.index) } - #[test] - fn test_module_instances_tracking() { - // Create DB with a regular AND gate and a Module containing an OR gate - let mut db = DB::default(); - - // Add a regular AND gate (top-level, not part of any module) - let and_gate = Gate { - pos: Pos2::new(100.0, 100.0), - kind: GateKind::And, - }; - let and_gate_id = db.circuit.new_gate(and_gate); - - // Create a module definition with an OR gate inside - let mut definition_circuit = Circuit::default(); - let or_gate = Gate { - pos: Pos2::new(50.0, 50.0), - kind: GateKind::Or, - }; - let _or_gate_def_id = definition_circuit.new_gate(or_gate); - - let module_def = crate::module::ModuleDefinition { - name: "TestModule".to_owned(), - circuit: definition_circuit, - }; + pub fn new(ins: InstanceId, index: u32, kind: PinKind) -> Self { + Self { ins, index, kind } + } - // Add module definition to DB - let def_id = db.module_definitions.insert(module_def); + pub fn is_passthrough(&self, db: &DB) -> Option { + let conns = db.circuit.connections_containing(*self); - // Create module instance (this will flatten the OR gate as a hidden instance) - let module = crate::module::Module { - pos: Pos2::new(200.0, 200.0), - definition_index: def_id, - }; - let module_id = db.new_module_with_flattening(module); - - // Get the internal OR gate's instance ID - let module_internal_instances = db.get_instances_for_module(module_id); - assert_eq!( - module_internal_instances.len(), - 1, - "Module should contain 1 internal instance" - ); - let internal_or_gate_id = module_internal_instances[0]; - - // Verify it's actually an OR gate - match db.circuit.ty(internal_or_gate_id) { - InstanceKind::Gate(GateKind::Or) => {} // Expected - other => panic!("Expected OR gate, got {other:?}"), + for conn in conns { + if conn.kind != ConnectionKind::BI { + continue; + } + let connected_pin = conn.get_other_pin(*self); + return Some(connected_pin); } - - // Test the get_module_owner helper method - - // Top-level AND gate should not have a module owner - assert_eq!( - db.get_module_owner(and_gate_id), - None, - "Regular AND gate should not belong to any module" - ); - - // The module instance itself should not have a module owner - assert_eq!( - db.get_module_owner(module_id), - None, - "Module instance should not belong to any module" - ); - - // The internal OR gate should have the module as its owner - assert_eq!( - db.get_module_owner(internal_or_gate_id), - Some(module_id), - "Internal OR gate should belong to the module" - ); + None } } diff --git a/src/drag.rs b/src/drag.rs index 8e801db..54177c4 100644 --- a/src/drag.rs +++ b/src/drag.rs @@ -4,6 +4,7 @@ use egui::{CornerRadius, Pos2, Rect, Stroke, StrokeKind, Ui, Vec2, pos2}; use crate::app::{App, COLOR_HOVER_PIN_TO_WIRE, COLOR_SELECTION_BOX, MIN_WIRE_SIZE}; +use crate::assets::PinKind; use crate::db::{InstanceId, InstanceKind, LabelId, Pin, Wire}; #[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Copy)] @@ -172,26 +173,33 @@ impl App { self.connection_manager.mark_instance_dirty(id); } } - // TODO: There is an issue with non zero viewport_offset. It causes the wire to start - // from wrong place and distance to calculate wrong Some(Drag::PinToWire { source_pin }) => { - let start = self.adjusted_pos(source_pin.pos(&self.db, &self.canvas_config)); + let start = source_pin.pos(&self.db, &self.canvas_config); let end_abs = mouse; let drag_distance = start.distance(end_abs); if drag_distance >= MIN_WIRE_SIZE { - let wire = Wire::new(start, mouse); + // Start is input so if the current pin is input we need to reverse the wire + let (new_w_start, new_w_end) = if source_pin.kind == PinKind::Input { + (mouse, start) + } else { + (start, mouse) + }; + let wire = Wire::new(new_w_start, new_w_end); let wire_id = self.db.circuit.new_wire(wire); self.drag = Some(Drag::Resize { id: wire_id, - start: false, + start: source_pin.kind == PinKind::Input, }); self.connection_manager.mark_instance_dirty(wire_id); } else { - ui.painter() - .line_segment([start, mouse], Stroke::new(2.0, COLOR_HOVER_PIN_TO_WIRE)); + // TODO: There is an issue with non zero viewport_offset. + ui.painter().line_segment( + [self.adjusted_pos(start), self.adjusted_pos(mouse)], + Stroke::new(2.0, COLOR_HOVER_PIN_TO_WIRE), + ); } } Some(Drag::BranchWire { @@ -297,7 +305,11 @@ impl App { sel.insert(id); } } - self.selected = sel; + self.selected = sel + .iter() + .filter(|i| !self.db.is_hidden(**i)) + .copied() + .collect(); } Drag::Resize { id, start: _ } => { self.connection_manager.mark_instance_dirty(id); diff --git a/src/module.rs b/src/module.rs index fbba353..7ca9a10 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,34 +1,40 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + cmp::Ordering, + collections::{BTreeMap, HashMap, HashSet}, +}; use egui::{Pos2, Vec2}; -use slotmap::SecondaryMap; use crate::{ app::App, assets::PinKind, config::CanvasConfig, - db::{Circuit, DB, InstanceId, ModuleDefId, Pin}, + connection_manager::Connection, + db::{Circuit, DB, InstanceId, InstanceKind, ModuleDefId, Pin}, }; #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct ModuleDefinition { - pub name: String, - pub circuit: Circuit, +pub struct Module { + pub pos: Pos2, + pub definition_id: ModuleDefId, + pub instance_members: Vec, + // external pin to internal pin mapping + pub pins: BTreeMap, } #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct Module { - pub pos: Pos2, - pub definition_index: ModuleDefId, +pub struct ModuleDefinition { + pub name: String, + pub circuit: Circuit, } impl Module { pub fn name(&self, db: &DB) -> String { - db.get_module_def(self.definition_index).name.clone() + db.get_module_def(self.definition_id).name.clone() } pub fn definition<'a>(&self, db: &'a DB) -> &'a ModuleDefinition { - db.get_module_def(self.definition_index) + db.get_module_def(self.definition_id) } pub fn display(&self, db: &DB, id: InstanceId) -> String { @@ -36,22 +42,112 @@ impl Module { sb += &format!(" {id}"); sb } + + pub(crate) fn pins(&self) -> Vec { + self.pins.keys().copied().collect() + } } impl ModuleDefinition { - pub fn display_definition(&self, db: &DB, id: ModuleDefId) -> String { - let mut sb = format!("Module({:?}) {}\n", id, self.name); - let circuit_display = self.circuit.display(db); - for line in circuit_display.lines() { - if line.is_empty() { - sb.push('\n'); - } else { - sb.push_str(" "); - sb.push_str(line); - sb.push('\n'); + /// Flatten this module definition into a target circuit + /// Creates copies of all internal instances and marks them as hidden + /// Returns mapping from external (module) pins to internal (component) pins + pub fn flatten_into_circuit( + &self, + definition_id: ModuleDefId, + module_id: InstanceId, + pos: Pos2, + db: &mut DB, + ) -> Module { + let mut def_id_to_world_id: HashMap = HashMap::new(); + + // First, create all instances + for (definition_id, _) in &self.circuit.types { + let placed_id = match self.circuit.ty(definition_id) { + InstanceKind::Gate(kind) => { + let gate = *self.circuit.get_gate(definition_id); + db.circuit.new_gate(gate) + } + InstanceKind::Power => { + let power = *self.circuit.get_power(definition_id); + db.circuit.new_power(power) + } + InstanceKind::Wire => { + let wire = *self.circuit.get_wire(definition_id); + db.circuit.new_wire(wire) + } + InstanceKind::Lamp => { + let lamp = *self.circuit.get_lamp(definition_id); + db.circuit.new_lamp(lamp) + } + InstanceKind::Clock => { + let clock = *self.circuit.get_clock(definition_id); + db.circuit.new_clock(clock) + } + InstanceKind::Module(nested_def_id) => { + todo!("Module in module not implemented"); + } + }; + + def_id_to_world_id.insert(definition_id, placed_id); + } + + // Copy internal connections + for conn in &self.circuit.connections { + if let (Some(&new_a_id), Some(&new_b_id)) = ( + def_id_to_world_id.get(&conn.a.ins), + def_id_to_world_id.get(&conn.b.ins), + ) { + let conn = Connection::new( + Pin::new(new_a_id, conn.a.index, conn.a.kind), + Pin::new(new_b_id, conn.b.index, conn.b.kind), + ); + db.circuit.connections.insert(conn); } } - sb + + // Create connections from module pins to internal component pins + let instances_in_order = self.instances_in_order(); + let mut pins = BTreeMap::new(); + let mut last_pin_index = 0; + + for element_id in instances_in_order { + let element_world_id = def_id_to_world_id[&element_id]; + let item_pins = self.circuit.pins_of(element_id, db); + + for internal_pin in item_pins { + if self.circuit.connected_pins(internal_pin).is_empty() { + let kind = internal_pin.kind; + let external_pin = Pin::new(module_id, last_pin_index, kind); + let internal_pin = + Pin::new(element_world_id, internal_pin.index, internal_pin.kind); + pins.insert(external_pin, internal_pin); + let conn = Connection::new_bi(external_pin, internal_pin); + db.circuit.connections.insert(conn); + last_pin_index += 1; + } + } + } + + let instance_members = def_id_to_world_id.values().copied().collect(); + Module { + pos, + definition_id, + instance_members, + pins, + } + } + pub fn display_definition(&self, _db: &DB, id: ModuleDefId) -> String { + // Show only a summary, not the full internal circuit + format!( + "Module \"{}\" [{:?}] (internal: {} gates, {} powers, {} lamps, {} conns)", + self.name, + id, + self.circuit.gates.len(), + self.circuit.powers.len(), + self.circuit.lamps.len(), + self.circuit.connections.len(), + ) } /// Mapping of external pin to internal pin @@ -72,10 +168,50 @@ impl ModuleDefinition { m } + pub fn instances_in_order(&self) -> Vec { + let mut instances: Vec = self.circuit.types.keys().collect(); + instances.sort_by(|s, o| { + let self_id = *s; + let self_pos = match self.circuit.ty(self_id) { + InstanceKind::Gate(_) => self.circuit.get_gate(self_id).pos, + InstanceKind::Power => self.circuit.get_power(self_id).pos, + InstanceKind::Wire => self.circuit.get_wire(self_id).center(), + InstanceKind::Lamp => self.circuit.get_lamp(self_id).pos, + InstanceKind::Clock => self.circuit.get_clock(self_id).pos, + InstanceKind::Module(module_def_id) => self.circuit.get_module(self_id).pos, + }; + + let other_id = *o; + let other_pos = match self.circuit.ty(other_id) { + InstanceKind::Gate(_) => self.circuit.get_gate(other_id).pos, + InstanceKind::Power => self.circuit.get_power(other_id).pos, + InstanceKind::Wire => self.circuit.get_wire(other_id).center(), + InstanceKind::Lamp => self.circuit.get_lamp(other_id).pos, + InstanceKind::Clock => self.circuit.get_clock(other_id).pos, + InstanceKind::Module(_) => self.circuit.get_module(other_id).pos, + }; + + if self_pos.y > other_pos.y { + Ordering::Greater + } else if self_pos.y == other_pos.y { + if self_pos.x > other_pos.x { + Ordering::Greater + } else if self_pos.x == other_pos.x { + Ordering::Equal + } else { + Ordering::Less + } + } else { + Ordering::Less + } + }); + instances + } + pub fn get_unconnected_internal_pins(&self, db: &DB) -> Vec { let mut unconnected_pins = Vec::new(); - for (id, _) in &self.circuit.types { + for id in self.instances_in_order() { let pins = self.circuit.pins_of(id, db); for pin in pins { if self.circuit.connected_pins(pin).is_empty() { @@ -87,28 +223,17 @@ impl ModuleDefinition { unconnected_pins } - pub fn get_unconnected_pins(&self, db: &DB, module_id: InstanceId) -> Vec { - let mut unconnected_pins = Vec::new(); - for (last_pin_index, mut pin) in self - .get_unconnected_internal_pins(db) - .into_iter() - .enumerate() - { - pin.ins = module_id; - pin.index = last_pin_index as u32; - unconnected_pins.push(pin); - } - unconnected_pins - } - /// Calculates the offset of a pin from the module's center position. /// This matches the layout logic used in rendering (app.rs `draw_module`). /// Input pins are placed on the left side, outputs on the right. /// Multiple pins of the same kind are evenly spaced vertically. - pub fn calculate_pin_offset(&self, db: &DB, pin: &Pin, canvas_config: &CanvasConfig) -> Vec2 { - let pins = self.get_unconnected_pins(db, pin.ins); - - // Separate input and output indices + pub fn calculate_pin_offset( + &self, + db: &DB, + pins: &[Pin], + pin: &Pin, + canvas_config: &CanvasConfig, + ) -> Vec2 { let mut input_indices = vec![]; let mut output_indices = vec![]; for (i, pin) in pins.iter().enumerate() { @@ -155,103 +280,6 @@ impl ModuleDefinition { Vec2::new(x, y) } - - /// Flatten this module definition into a target circuit - /// Creates copies of all internal instances and marks them as hidden - /// Returns mapping from external (module) pins to internal (component) pins - pub fn flatten_into_circuit( - &self, - target_circuit: &mut Circuit, - module_id: InstanceId, - definition_id: ModuleDefId, - db: &DB, - module_instances: &mut SecondaryMap, - ) -> HashMap { - // Map old instance IDs to new ones - let mut id_map: HashMap = HashMap::new(); - - // Copy all instances from definition circuit to target circuit - for (old_id, _) in &self.circuit.types { - let new_id = match self.circuit.ty(old_id) { - crate::db::InstanceKind::Gate(kind) => { - let gate = *self.circuit.get_gate(old_id); - target_circuit.new_gate(gate) - } - crate::db::InstanceKind::Power => { - let power = *self.circuit.get_power(old_id); - target_circuit.new_power(power) - } - crate::db::InstanceKind::Wire => { - let wire = *self.circuit.get_wire(old_id); - target_circuit.new_wire(wire) - } - crate::db::InstanceKind::Lamp => { - let lamp = *self.circuit.get_lamp(old_id); - target_circuit.new_lamp(lamp) - } - crate::db::InstanceKind::Clock => { - let clock = *self.circuit.get_clock(old_id); - target_circuit.new_clock(clock) - } - crate::db::InstanceKind::Module(nested_def_id) => { - // Handle nested modules recursively - let nested_module = self.circuit.get_module(old_id).clone(); - let nested_id = target_circuit.new_module(nested_module); - - // Recursively flatten the nested module - let nested_def = db.get_module_def(nested_def_id); - let _nested_pin_map = nested_def.flatten_into_circuit( - target_circuit, - nested_id, - nested_def_id, - db, - module_instances, - ); - - nested_id - } - }; - - // Record that this instance belongs to the module - module_instances.insert(new_id, module_id); - id_map.insert(old_id, new_id); - } - - // Copy all internal connections with remapped IDs - for conn in &self.circuit.connections { - if let (Some(&new_a_id), Some(&new_b_id)) = - (id_map.get(&conn.a.ins), id_map.get(&conn.b.ins)) - { - let new_conn = crate::connection_manager::Connection::new( - Pin::new(new_a_id, conn.a.index, conn.a.kind), - Pin::new(new_b_id, conn.b.index, conn.b.kind), - ); - target_circuit.connections.insert(new_conn); - } - } - - // Build pin mapping: external pins -> internal pins - let mut pin_mapping = HashMap::new(); - for (external_pin_index, internal_pin) in self - .get_unconnected_internal_pins(db) - .into_iter() - .enumerate() - { - // External pin (on the module boundary) - let external_pin = Pin::new(module_id, external_pin_index as u32, internal_pin.kind); - - // Internal pin (on the actual component, with remapped instance ID) - let new_internal_id = *id_map - .get(&internal_pin.ins) - .expect("internal pin instance should be in id_map"); - let remapped_internal_pin = - Pin::new(new_internal_id, internal_pin.index, internal_pin.kind); - - pin_mapping.insert(external_pin, remapped_internal_pin); - } - - pin_mapping - } } impl App { @@ -264,11 +292,6 @@ impl App { return Err("No components selected".to_owned()); } - let mut internal_components = Vec::new(); - for instance in instances { - internal_components.push(self.db.circuit.ty(*instance)); - } - let mut circuit = Circuit::default(); let mut id_map = std::collections::HashMap::new(); @@ -295,8 +318,7 @@ impl App { circuit.new_clock(clock) } crate::db::InstanceKind::Module(def_id) => { - let module = self.db.circuit.get_module(old_id).clone(); - circuit.new_module(module) + todo!("Cloning modules is not yet supported"); } }; id_map.insert(old_id, new_id); @@ -321,293 +343,3 @@ impl App { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::db::{Gate, GateKind}; - use egui::pos2; - - #[test] - fn test_panic_lol() { - let mut app = App::default(); - - let gate = Gate { - pos: pos2(100.0, 100.0), - kind: GateKind::And, - }; - let gate_id = app.db.circuit.new_gate(gate); - - let mut instances = HashSet::new(); - instances.insert(gate_id); - let result = app.create_module_definition("module".to_owned(), &instances); - assert!(result.is_ok()); - - let module_def_id = app - .db - .module_definitions - .keys() - .next() - .expect("module def id not found"); - let module_id = app.db.circuit.new_module(Module { - pos: Pos2::ZERO, - definition_index: module_def_id, - }); - app.db.remove_instance(gate_id); - - assert!(app.db.circuit.gates.get(gate_id).is_none()); - - let pins = app.db.circuit.pins_of(module_id, &app.db); - - for pin in pins { - eprintln!("getting pin {}", pin.display(&app.db.circuit)); - app.db - .circuit - .pin_position(pin, &CanvasConfig::default(), &app.db); - } - - let module_def = app - .db - .module_definitions - .get(module_def_id) - .expect("module def not found"); - let display = module_def.display_definition(&app.db, module_def_id); - } - - #[test] - fn test_simple_module_flattening() { - // Create a module definition with a single AND gate - let mut definition_circuit = Circuit::default(); - let gate = crate::db::Gate { - pos: pos2(100.0, 100.0), - kind: crate::db::GateKind::And, - }; - let gate_id = definition_circuit.new_gate(gate); - - let module_def = ModuleDefinition { - name: "SimpleAND".to_owned(), - circuit: definition_circuit, - }; - - // Create DB and add definition - let mut db = DB::default(); - let def_id = db.module_definitions.insert(module_def); - - // Create a module instance in target circuit - let mut target_circuit = Circuit::default(); - let module = Module { - pos: Pos2::ZERO, - definition_index: def_id, - }; - let module_id = target_circuit.new_module(module); - - // Flatten the module - let module_def = db.get_module_def(def_id); - let mut module_instances = SecondaryMap::new(); - let pin_map = module_def.flatten_into_circuit( - &mut target_circuit, - module_id, - def_id, - &db, - &mut module_instances, - ); - - // Check that internal gate was created and recorded in module_instances - let internal_instances: Vec<_> = module_instances - .iter() - .filter_map(|(id, parent)| if *parent == module_id { Some(id) } else { None }) - .collect(); - assert_eq!( - internal_instances.len(), - 1, - "Should have 1 internal instance" - ); - - // Should have 2 total instances (module + internal gate) - assert_eq!(target_circuit.types.len(), 2); - - // Check that the internal instance is tracked correctly - assert!(module_instances.contains_key(internal_instances[0])); - - // Pin mapping should have 3 entries (2 inputs + 1 output for AND gate) - assert_eq!(pin_map.len(), 3, "AND gate has 3 unconnected pins"); - } - - #[test] - fn test_module_flattening_with_connections() { - // Create a module definition with two gates connected - let mut definition_circuit = Circuit::default(); - - let gate1 = crate::db::Gate { - pos: pos2(50.0, 100.0), - kind: crate::db::GateKind::And, - }; - let gate1_id = definition_circuit.new_gate(gate1); - - let gate2 = crate::db::Gate { - pos: pos2(150.0, 100.0), - kind: crate::db::GateKind::Or, - }; - let gate2_id = definition_circuit.new_gate(gate2); - - // Connect output of gate1 to input of gate2 - let gate1_output = Pin::new(gate1_id, 1, PinKind::Output); - let gate2_input = Pin::new(gate2_id, 0, PinKind::Input); - definition_circuit - .connections - .insert(crate::connection_manager::Connection::new( - gate1_output, - gate2_input, - )); - - let module_def = ModuleDefinition { - name: "TwoGates".to_owned(), - circuit: definition_circuit, - }; - - // Create DB and add definition - let mut db = DB::default(); - let def_id = db.module_definitions.insert(module_def); - - // Create a module instance in target circuit - let mut target_circuit = Circuit::default(); - let module = Module { - pos: Pos2::ZERO, - definition_index: def_id, - }; - let module_id = target_circuit.new_module(module); - - // Flatten the module - let module_def = db.get_module_def(def_id); - let mut module_instances = SecondaryMap::new(); - let _pin_map = module_def.flatten_into_circuit( - &mut target_circuit, - module_id, - def_id, - &db, - &mut module_instances, - ); - - // Should have 2 internal instances (both gates) - let internal_instances: Vec<_> = module_instances - .iter() - .filter_map(|(id, parent)| if *parent == module_id { Some(id) } else { None }) - .collect(); - assert_eq!(internal_instances.len(), 2); - - // Should have 3 total instances (module + 2 gates) - assert_eq!(target_circuit.types.len(), 3); - - // The internal connection should be copied - assert_eq!( - target_circuit.connections.len(), - 1, - "Internal connection should be copied" - ); - } - - #[test] - fn test_nested_module_flattening() { - // Create an inner module definition (just an AND gate) - let mut inner_circuit = Circuit::default(); - let inner_gate = crate::db::Gate { - pos: pos2(50.0, 50.0), - kind: crate::db::GateKind::And, - }; - let _inner_gate_id = inner_circuit.new_gate(inner_gate); - - let inner_def = ModuleDefinition { - name: "InnerModule".to_owned(), - circuit: inner_circuit, - }; - - // Create DB and add inner definition - let mut db = DB::default(); - let inner_def_id = db.module_definitions.insert(inner_def); - - // Create an outer module definition containing the inner module - let mut outer_circuit = Circuit::default(); - let inner_module_instance = Module { - pos: pos2(100.0, 100.0), - definition_index: inner_def_id, - }; - let _inner_module_id = outer_circuit.new_module(inner_module_instance); - - let outer_def = ModuleDefinition { - name: "OuterModule".to_owned(), - circuit: outer_circuit, - }; - - let outer_def_id = db.module_definitions.insert(outer_def); - - // Create an instance of the outer module in target circuit - let mut target_circuit = Circuit::default(); - let outer_module = Module { - pos: Pos2::ZERO, - definition_index: outer_def_id, - }; - let outer_module_id = target_circuit.new_module(outer_module); - - // Flatten the outer module (which should recursively flatten the inner module) - let outer_def = db.get_module_def(outer_def_id); - let mut module_instances = SecondaryMap::new(); - let _pin_map = outer_def.flatten_into_circuit( - &mut target_circuit, - outer_module_id, - outer_def_id, - &db, - &mut module_instances, - ); - - // Check what's internal to the outer module (should be just the inner module) - let outer_internals: Vec<_> = module_instances - .iter() - .filter_map(|(id, parent)| { - if *parent == outer_module_id { - Some(id) - } else { - None - } - }) - .collect(); - assert_eq!( - outer_internals.len(), - 1, - "Outer module should have 1 direct child (inner module)" - ); - - // The inner module should be in the outer's internal instances - let inner_module_id = outer_internals[0]; - assert!(matches!( - target_circuit.ty(inner_module_id), - crate::db::InstanceKind::Module(_) - )); - - // Check what's internal to the inner module (should be the inner gate) - let inner_internals: Vec<_> = module_instances - .iter() - .filter_map(|(id, parent)| { - if *parent == inner_module_id { - Some(id) - } else { - None - } - }) - .collect(); - assert_eq!( - inner_internals.len(), - 1, - "Inner module should have 1 internal instance (the gate)" - ); - - // Total instances: outer module + inner module (internal) + inner gate (internal to inner) - assert_eq!(target_circuit.types.len(), 3); - - // Check that all internal instances are tracked correctly in module_instances - assert_eq!( - module_instances.len(), - 2, - "Should have 2 entries: inner module and inner gate" - ); - } -} diff --git a/src/simulator.rs b/src/simulator.rs index 189c5ec..d95067f 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -7,7 +7,7 @@ use crate::{ db::{Circuit, DB, GateKind, InstanceId, InstanceKind, Pin}, }; -const MAX_ITERATIONS: usize = 1000; +const MAX_ITERATIONS: usize = 10; const STABILIZATION_THRESHOLD: usize = 3; #[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Copy, PartialEq, Eq)] @@ -80,8 +80,6 @@ impl Value { pub struct Simulator { /// Final result - maps each pin to its current value pub current: HashMap, - /// Assume the value for these pins always - pub assumed_pins: HashMap, /// Keep what has been already evaluated pub evaluated: HashSet, /// Number of iterations taken in last compute @@ -162,15 +160,6 @@ impl Simulator { fn evaluate(&mut self, db: &DB, circuit: &Circuit, id: InstanceId) { self.evaluated.insert(id); - // Debug: log evaluation of hidden instances - if db.is_hidden(id) { - log::debug!( - "Evaluating hidden instance: {:?}, type: {:?}", - id, - circuit.ty(id) - ); - } - match circuit.ty(id) { InstanceKind::Wire => { self.evaluate_wire(db, circuit, id); @@ -189,31 +178,10 @@ impl Simulator { self.current.insert(clock_output(id), Value::Zero); } } - InstanceKind::Module(_) => { - // Module evaluation: propagate values between external and internal pins - // External pins are connected to the outside world - // Internal pins are connected to the flattened components - if let Some(pin_mapping) = circuit.module_pin_mappings.get(id) { - for (external_pin, internal_pin) in pin_mapping { - // Propagate values based on pin direction - match external_pin.kind { - crate::assets::PinKind::Input => { - // Input: external -> internal - let external_value = self.get_pin_value(db, circuit, *external_pin); - self.current.insert(*internal_pin, external_value); - } - crate::assets::PinKind::Output => { - // Output: internal -> external - // Read directly from self.current to avoid circular redirect - let internal_value = self - .current - .get(internal_pin) - .copied() - .unwrap_or(Value::Zero); - self.current.insert(*external_pin, internal_value); - } - } - } + InstanceKind::Module(module_def_id) => { + for pin in circuit.get_module(id).pins() { + let v = self.get_pin_value(db, circuit, pin); + self.current.insert(pin, v); } } } @@ -254,6 +222,16 @@ impl Simulator { return; }; + // Not has one input so handle specially + if matches!(kind, GateKind::Not) { + let inp1 = gate_inp1(id); + let out = Pin::new(id, 1, PinKind::Output); + let a = self.get_pin_value(db, circuit, inp1); + let out_val = a.not(); + self.current.insert(out, out_val); + return; + } + let inp1 = gate_inp1(id); let inp2 = gate_inp2(id); let out = gate_output(id); @@ -268,6 +246,7 @@ impl Simulator { GateKind::Nor => a.or(b).not(), GateKind::Xor => a.xor(b), GateKind::Xnor => a.xnor(b), + GateKind::Not => unreachable!("Handled above"), }; self.current.insert(out, out_val); @@ -280,39 +259,17 @@ impl Simulator { } fn get_pin_value(&self, db: &DB, circuit: &Circuit, pin: Pin) -> Value { - // Check if this is an internal hidden pin that maps to a module boundary - let lookup_pin = if db.is_hidden(pin.ins) { - // Find if there's a module that has this as an internal pin - // Look through all module_pin_mappings to find reverse mapping - circuit - .module_pin_mappings - .iter() - .find_map(|(_module_id, mappings)| { - mappings - .iter() - .find_map(|(ext, int)| if int == &pin { Some(*ext) } else { None }) - }) - .unwrap_or(pin) // If not found, use original pin - } else { - pin - }; - - let mut connected = circuit.connected_pins(lookup_pin); - connected.push(lookup_pin); - connected.sort_unstable(); - connected.dedup(); - - if let Some(v) = self.assumed_pins.get(&lookup_pin) { - return *v; - } + let pin = pin.is_passthrough(db).unwrap_or(pin); + let conns = circuit.connections_containing(pin); let mut result = Value::Zero; - for other in connected { - if other.kind != PinKind::Output { + for conn in conns { + let connected_pin = conn.get_other_pin(pin); + if connected_pin.kind != PinKind::Output { continue; } - if let Some(&val) = self.current.get(&other) { + if let Some(&val) = self.current.get(&connected_pin) { result = result.or(val); } } @@ -321,47 +278,29 @@ impl Simulator { } } -pub fn gate_inp_n(id: InstanceId, n: u32) -> Pin { - assert!(n < 2, "Gates only have 2 inputs (0 and 1)"); - Pin::new(id, if n == 0 { 0 } else { 2 }, PinKind::Input) -} - pub fn gate_output_n(id: InstanceId, n: u32) -> Pin { assert!(n == 0, "Gates only have 1 output"); - Pin::new(id, 1, PinKind::Output) + Pin::new(id, 2, PinKind::Output) } pub fn gate_inp1(id: InstanceId) -> Pin { - gate_inp_n(id, 0) + Pin::new(id, 0, PinKind::Input) } pub fn gate_inp2(id: InstanceId) -> Pin { - gate_inp_n(id, 1) + Pin::new(id, 1, PinKind::Input) } pub fn gate_output(id: InstanceId) -> Pin { gate_output_n(id, 0) } -pub fn wire_pin_n(id: InstanceId, n: u32) -> Pin { - assert!(n < 2, "Wires only have 2 pins (0 and 1)"); - Pin::new( - id, - n, - if n == 0 { - PinKind::Input - } else { - PinKind::Output - }, - ) -} - pub fn wire_start(id: InstanceId) -> Pin { - wire_pin_n(id, 0) + Pin::new(id, 0, PinKind::Input) } pub fn wire_end(id: InstanceId) -> Pin { - wire_pin_n(id, 1) + Pin::new(id, 1, PinKind::Output) } pub fn power_output(id: InstanceId) -> Pin { @@ -375,441 +314,3 @@ pub fn lamp_input(id: InstanceId) -> Pin { pub fn clock_output(id: InstanceId) -> Pin { Pin::new(id, 0, PinKind::Output) } - -#[cfg(test)] -mod tests { - use super::*; - use crate::connection_manager::Connection; - use crate::db::{DB, Gate, GateKind, Lamp, Power}; - use crate::module::{Module, ModuleDefinition}; - use egui::Pos2; - - // Helper functions - fn new_lamp(db: &mut DB) -> InstanceId { - db.circuit.new_lamp(Lamp { pos: Pos2::ZERO }) - } - - fn new_power(db: &mut DB) -> InstanceId { - db.circuit.new_power(Power { - pos: Pos2::ZERO, - on: true, - }) - } - - fn new_power_off(db: &mut DB) -> InstanceId { - db.circuit.new_power(Power { - pos: Pos2::ZERO, - on: false, - }) - } - - fn new_gate(db: &mut DB, kind: GateKind) -> InstanceId { - db.circuit.new_gate(Gate { - pos: Pos2::ZERO, - kind, - }) - } - - fn add_connection(db: &mut DB, pin_a: Pin, pin_b: Pin) { - db.circuit.connections.insert(Connection::new(pin_a, pin_b)); - } - - // SR Latch Test Helpers - // Creates an SR latch using two NOR gates with cross-coupled feedback - // Standard SR NOR latch: - // Q = NOR(R, Q̄) - // Q̄ = NOR(S, Q) - // - // R (Reset)--[NOR1]--- Q - // ^ | - // | +------+ - // | | - // +---------v - // S (Set) ---[NOR2]--- Q̄ - fn create_sr_latch(db: &mut DB) -> (InstanceId, InstanceId, InstanceId, InstanceId) { - let s_power = db.circuit.new_power(Power { - pos: Pos2::ZERO, - on: false, - }); - let r_power = db.circuit.new_power(Power { - pos: Pos2::new(0.0, 50.0), - on: false, - }); - - let nor1 = new_gate(db, GateKind::Nor); - let nor2 = new_gate(db, GateKind::Nor); - - // R connects to NOR1 input 1 (Q gate) - let r_out = power_output(r_power); - let nor1_in1 = gate_inp1(nor1); - add_connection(db, r_out, nor1_in1); - - // S connects to NOR2 input 1 (Q̄ gate) - let s_out = power_output(s_power); - let nor2_in1 = gate_inp1(nor2); - add_connection(db, s_out, nor2_in1); - - // Cross-couple: NOR1 output (Q) -> NOR2 input 2 - let nor1_out = gate_output(nor1); - let nor2_in2 = gate_inp2(nor2); - add_connection(db, nor1_out, nor2_in2); - - // Cross-couple: NOR2 output (Q̄) -> NOR1 input 2 - let nor2_out = gate_output(nor2); - let nor1_in2 = gate_inp2(nor1); - add_connection(db, nor2_out, nor1_in2); - - (s_power, r_power, nor1, nor2) - } - - fn get_output(sim: &Simulator, id: InstanceId) -> Value { - sim.current[&gate_output(id)] - } - - #[test] - fn test_module_simulation_simple() { - // Create a module definition with a single AND gate - let mut def_circuit = crate::db::Circuit::default(); - let gate = Gate { - pos: Pos2::ZERO, - kind: GateKind::And, - }; - let gate_id = def_circuit.new_gate(gate); - - let module_def = ModuleDefinition { - name: "SimpleAND".to_owned(), - circuit: def_circuit, - }; - - // Create DB and add definition - let mut db = DB::default(); - let def_id = db.module_definitions.insert(module_def); - - // Create a module instance and flatten it - let module = Module { - pos: Pos2::ZERO, - definition_index: def_id, - }; - let module_id = db.new_module_with_flattening(module); - eprintln!("Module ID: {module_id:?}"); - - // Check internal instances - let internal_instances = db.get_instances_for_module(module_id); - eprintln!("Internal instances for module: {internal_instances:?}"); - for &hid in &internal_instances { - let ty = db.circuit.ty(hid); - eprintln!(" Hidden instance {hid:?} is type {ty:?}"); - } - - // Create two power sources and connect them to the module inputs - let power1 = Power { - pos: Pos2::ZERO, - on: true, - }; - let power1_id = db.circuit.new_power(power1); - - let power2 = Power { - pos: Pos2::ZERO, - on: true, - }; - let power2_id = db.circuit.new_power(power2); - - // Create a lamp and connect it to the module output - let lamp = Lamp { pos: Pos2::ZERO }; - let lamp_id = db.circuit.new_lamp(lamp); - - // Get module pins (should be 3: input, input, output for AND gate) - let module_pins = db.circuit.pins_of(module_id, &db); - assert_eq!(module_pins.len(), 3, "Module should have 3 pins"); - - // Connect power1 to module input 0 - let module_in0 = module_pins - .iter() - .find(|p| p.index == 0 && p.kind == PinKind::Input) - .expect("module should have input pin 0"); - db.circuit - .connections - .insert(Connection::new(power_output(power1_id), *module_in0)); - - // Connect power2 to module input 2 (second input of AND gate) - let module_in1 = module_pins - .iter() - .find(|p| p.index == 2 && p.kind == PinKind::Input) - .expect("module should have input pin 2"); - db.circuit - .connections - .insert(Connection::new(power_output(power2_id), *module_in1)); - - // Connect module output to lamp - let module_out = module_pins - .iter() - .find(|p| p.kind == PinKind::Output) - .expect("module should have output pin"); - db.circuit - .connections - .insert(Connection::new(*module_out, lamp_input(lamp_id))); - - // Debug: print all connections - eprintln!("\n=== Connections ==="); - for conn in &db.circuit.connections { - let a = conn.a; - let b = conn.b; - eprintln!("Connection: {a:?} <-> {b:?}"); - } - - // Run simulation - let mut sim = Simulator::new(); - sim.compute(&db, &db.circuit); - - // Debug: print all pin values - eprintln!("=== Simulation Results ==="); - for (pin, value) in &sim.current { - eprintln!("Pin {pin:?}: {value:?}"); - } - - // Debug: print module pin mappings - eprintln!("\n=== Module Pin Mappings ==="); - if let Some(mappings) = db.circuit.module_pin_mappings.get(module_id) { - for (ext, int) in mappings { - eprintln!("External {ext:?} -> Internal {int:?}"); - } - } - - // Check that the lamp is on (both inputs are 1, so AND gate outputs 1) - let lamp_value = sim - .current - .get(&lamp_input(lamp_id)) - .copied() - .unwrap_or(Value::X); - eprintln!("\nLamp value: {lamp_value:?}"); - assert_eq!( - lamp_value, - Value::One, - "Lamp should be on when both module inputs are high" - ); - - // Check that the simulation stabilized - assert!( - matches!(sim.status, SimulationStatus::Stable { .. }), - "Simulation should stabilize" - ); - } - - #[test] - fn test_power_to_lamp() { - let mut db = DB::default(); - let power = new_power(&mut db); - let lamp = new_lamp(&mut db); - - let power_out = power_output(power); - let lamp_in = lamp_input(lamp); - - add_connection(&mut db, power_out, lamp_in); - - let mut sim = Simulator::new(); - let result = sim.compute(&db, &db.circuit); - - assert!(result.contains(&power_out), "Power output should be on"); - assert!(result.contains(&lamp_in), "Lamp input should be on"); - } - - #[test] - fn test_power_off_to_lamp() { - let mut db = DB::default(); - let power = new_power_off(&mut db); - let lamp = new_lamp(&mut db); - - let power_out = power_output(power); - let lamp_in = lamp_input(lamp); - - add_connection(&mut db, power_out, lamp_in); - - let mut sim = Simulator::new(); - let result = sim.compute(&db, &db.circuit); - - assert!(!result.contains(&power_out), "Power output should be off"); - assert!(!result.contains(&lamp_in), "Lamp input should be off"); - } - - #[test] - fn test_power_gate_lamp() { - let mut db = DB::default(); - let power1 = new_power(&mut db); - let power2 = new_power(&mut db); - let gate = new_gate(&mut db, GateKind::And); - let lamp = new_lamp(&mut db); - - let power1_out = power_output(power1); - let power2_out = power_output(power2); - let gate_in1 = gate_inp1(gate); - let gate_in2 = gate_inp2(gate); - let gate_out = gate_output(gate); - let lamp_in = lamp_input(lamp); - - add_connection(&mut db, power1_out, gate_in1); - add_connection(&mut db, power2_out, gate_in2); - add_connection(&mut db, gate_out, lamp_in); - - let mut sim = Simulator::new(); - let result = sim.compute(&db, &db.circuit); - - assert!(result.contains(&gate_out), "AND gate output should be on"); - assert!(result.contains(&lamp_in), "Lamp should be on"); - } - - #[test] - fn sr_latch_set_state() { - let mut db = DB::default(); - let (s_power, _r_power, nor1, nor2) = create_sr_latch(&mut db); - - // Set S=1, R=0 - db.circuit.get_power_mut(s_power).on = true; - - let mut sim = Simulator::new(); - let _result = sim.compute(&db, &db.circuit); - - // Q should be high (1) - let q_output = gate_output(nor1); - let q_value = sim.current.get(&q_output).copied().unwrap_or(Value::X); - assert_eq!(q_value, Value::One, "Q should be 1 in SET state"); - - // Q̄ should be low (0) - let q_bar_output = gate_output(nor2); - let q_bar_value = sim.current.get(&q_bar_output).copied().unwrap_or(Value::X); - assert_eq!(q_bar_value, Value::Zero, "Q̄ should be 0 in SET state"); - - // Should stabilize quickly - assert!( - sim.last_iterations < 20, - "SR latch should stabilize in < 20 iterations, took {}", - sim.last_iterations - ); - } - - #[test] - fn sr_latch_reset_state() { - let mut db = DB::default(); - let (_s_power, r_power, nor1, nor2) = create_sr_latch(&mut db); - - // Set S=0, R=1 - db.circuit.get_power_mut(r_power).on = true; - - let mut sim = Simulator::new(); - let _result = sim.compute(&db, &db.circuit); - - // Q should be low (0) - let q_output = gate_output(nor1); - let q_value = sim.current.get(&q_output).copied().unwrap_or(Value::X); - assert_eq!(q_value, Value::Zero, "Q should be 0 in RESET state"); - - // Q̄ should be high (1) - let q_bar_output = gate_output(nor2); - let q_bar_value = sim.current.get(&q_bar_output).copied().unwrap_or(Value::X); - assert_eq!(q_bar_value, Value::One, "Q̄ should be 1 in RESET state"); - - // Should stabilize quickly - assert!( - sim.last_iterations < 20, - "SR latch should stabilize in < 20 iterations, took {}", - sim.last_iterations - ); - } - - #[test] - fn sr_latch_hold_state() { - let mut db = DB::default(); - let (s_power, _r_power, nor1, nor2) = create_sr_latch(&mut db); - - // First set the latch to SET state (S=1, R=0) - db.circuit.get_power_mut(s_power).on = true; - let mut sim = Simulator::new(); - sim.compute(&db, &db.circuit); - - assert_eq!(get_output(&sim, nor1), Value::One); - assert_eq!(get_output(&sim, nor2), Value::Zero); - - db.circuit.get_power_mut(s_power).on = false; - let _result = sim.compute(&db, &db.circuit); - - assert_eq!(get_output(&sim, nor1), Value::One, "Q should hold state"); - - assert_eq!( - get_output(&sim, nor2), - Value::Zero, - "Q inverse should hold state" - ); - } - - #[test] - fn sr_latch_forbidden_state() { - let mut db = DB::default(); - let (s_power, r_power, nor1, nor2) = create_sr_latch(&mut db); - - // Set S=1, R=1 (forbidden state) - db.circuit.get_power_mut(s_power).on = true; - db.circuit.get_power_mut(r_power).on = true; - - let mut sim = Simulator::new(); - let _result = sim.compute(&db, &db.circuit); - - // Both outputs should be 0 (since NOR with any input 1 gives 0) - let q_output = gate_output(nor1); - let q_value = sim.current.get(&q_output).copied().unwrap_or(Value::X); - - let q_bar_output = gate_output(nor2); - let q_bar_value = sim.current.get(&q_bar_output).copied().unwrap_or(Value::X); - - // In forbidden state, both Q and Q̄ are 0 (violates Q = !Q̄) - assert_eq!(q_value, Value::Zero, "Q should be 0 in forbidden state"); - assert_eq!(q_bar_value, Value::Zero, "Q̄ should be 0 in forbidden state"); - } - - #[test] - fn sr_latch_state_transition_set_to_reset() { - let mut db = DB::default(); - let (s_power, r_power, nor1, nor2) = create_sr_latch(&mut db); - - // Start in SET state (S=1, R=0) - db.circuit.get_power_mut(s_power).on = true; - let mut sim = Simulator::new(); - sim.compute(&db, &db.circuit); - - let q_output = gate_output(nor1); - let q_value = sim.current.get(&q_output).copied().unwrap_or(Value::X); - assert_eq!(q_value, Value::One, "Q should be 1 after SET"); - - // Transition to RESET state (S=0, R=1) - db.circuit.get_power_mut(s_power).on = false; - db.circuit.get_power_mut(r_power).on = true; - let mut sim2 = Simulator::new(); - sim2.compute(&db, &db.circuit); - - let q_value_after = sim2.current.get(&q_output).copied().unwrap_or(Value::X); - let q_bar_output = gate_output(nor2); - let q_bar_value = sim2.current.get(&q_bar_output).copied().unwrap_or(Value::X); - - assert_eq!(q_value_after, Value::Zero, "Q should be 0 after RESET"); - assert_eq!(q_bar_value, Value::One, "Q̄ should be 1 after RESET"); - } - - #[test] - fn sr_latch_stabilization_iterations() { - let mut db = DB::default(); - let (s_power, _r_power, _nor1, _nor2) = create_sr_latch(&mut db); - - // Set S=1, R=0 - db.circuit.get_power_mut(s_power).on = true; - - let mut sim = Simulator::new(); - sim.compute(&db, &db.circuit); - - // Should stabilize in reasonable number of iterations - // For a simple SR latch, should be < 10 iterations - assert!( - sim.last_iterations < 10, - "SR latch should stabilize quickly, took {} iterations", - sim.last_iterations - ); - assert!(sim.last_iterations > 0, "Should take at least 1 iteration"); - } -} diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..e69de29