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