libttl 0.1.1

A library for simulating TTL logic chips
Documentation
//! A simple circuit simulator using libttl chips.

use crate::chips::{Chip, PinType};
use crate::logic_level::LogicLevel;
use std::collections::{HashMap, HashSet};
use std::fmt;

/// Represents a connection between two chip pins.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Connection {
    /// Index of the source chip in the `Circuit::chips` vector.
    pub source_chip_idx: usize,
    /// 1-based pin number on the source chip.
    pub source_pin: usize,
    /// Index of the destination chip in the `Circuit::chips` vector.
    pub dest_chip_idx: usize,
    /// 1-based pin number on the destination chip.
    pub dest_pin: usize,
}

/// Represents errors that can occur during circuit construction or simulation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CircuitError {
    InvalidChipIndex(usize),
    InvalidPinNumber(usize, usize), // chip_idx, pin
    PinNotAnOutput(usize, usize),   // chip_idx, pin
    PinNotAnInput(usize, usize),    // chip_idx, pin
    InputPinAlreadyDriven(usize, usize), // chip_idx, pin
    CannotConnectToVccGnd(usize, usize), // chip_idx, pin (destination)
    CannotGetLevelFromVccGndNc(usize, usize), // chip_idx, pin
    CannotSetLevelOnNonInput(usize, usize), // chip_idx, pin
    CannotSetDrivenInput(usize, usize), // chip_idx, pin (input driven by wire)
}

impl fmt::Display for CircuitError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            CircuitError::InvalidChipIndex(idx) => write!(f, "Invalid chip index: {}", idx),
            CircuitError::InvalidPinNumber(idx, pin) => {
                write!(f, "Invalid pin number: {} on chip index {}", pin, idx)
            }
            CircuitError::PinNotAnOutput(idx, pin) => {
                write!(f, "Source pin {} on chip {} is not an Output or Vcc", pin, idx)
            }
            CircuitError::PinNotAnInput(idx, pin) => {
                write!(f, "Destination pin {} on chip {} is not an Input", pin, idx)
            }
            CircuitError::InputPinAlreadyDriven(idx, pin) => {
                write!(f, "Input pin {} on chip {} is already driven by another connection", pin, idx)
            }
            CircuitError::CannotConnectToVccGnd(idx, pin) => {
                 write!(f, "Cannot connect a wire to Vcc/Gnd pin {} on chip {}", pin, idx)
            }
            CircuitError::CannotGetLevelFromVccGndNc(idx, pin) => {
                write!(f, "Cannot get logic level directly from Vcc/Gnd/Nc pin {} on chip {}", pin, idx)
            }
            CircuitError::CannotSetLevelOnNonInput(idx, pin) => {
                write!(f, "Cannot set external level on non-input pin {} on chip {}", pin, idx)
            }
            CircuitError::CannotSetDrivenInput(idx, pin) => {
                 write!(f, "Cannot set external level on input pin {} on chip {}, as it is driven by a wire", pin, idx)
            }
        }
    }
}

impl std::error::Error for CircuitError {}

/// Represents a digital circuit composed of TTL chips and connections.
#[derive(Clone, Debug)]
pub struct Circuit {
    chips: Vec<Box<dyn Chip>>,
    connections: Vec<Connection>,
    /// Keeps track of which input pins are driven by connections to prevent conflicts.
    /// Key: (chip_idx, dest_pin)
    driven_inputs: HashSet<(usize, usize)>,
}

impl Circuit {
    /// Creates a new, empty circuit.
    pub fn new() -> Self {
        Circuit {
            chips: Vec::new(),
            connections: Vec::new(),
            driven_inputs: HashSet::new(),
        }
    }

    /// Adds a chip to the circuit.
    ///
    /// Returns the index (ID) of the added chip, which can be used for connections.
    pub fn add_chip(&mut self, chip: Box<dyn Chip>) -> usize {
        let index = self.chips.len();
        self.chips.push(chip);
        index
    }

    /// Adds a wire connecting an output/Vcc pin of one chip to an input pin of another.
    ///
    /// Pin numbers are 1-based.
    ///
    /// # Errors
    /// Returns `CircuitError` if:
    /// - Chip indices are invalid.
    /// - Pin numbers are invalid for the respective chips.
    /// - The source pin is not an `Output` or `Vcc`.
    /// - The destination pin is not an `Input`.
    /// - The destination input pin is already connected to another output.
    pub fn add_wire(
        &mut self,
        source_chip_idx: usize,
        source_pin: usize,
        dest_chip_idx: usize,
        dest_pin: usize,
    ) -> Result<(), CircuitError> {
        // --- Validation ---
        let source_chip = self.chips.get(source_chip_idx)
            .ok_or(CircuitError::InvalidChipIndex(source_chip_idx))?;
        let dest_chip = self.chips.get(dest_chip_idx)
            .ok_or(CircuitError::InvalidChipIndex(dest_chip_idx))?;

        // Validate pin numbers (using internal helper relies on Chip panicking, let's check explicitly)
        if source_pin == 0 || source_pin > source_chip.pin_count() {
            return Err(CircuitError::InvalidPinNumber(source_chip_idx, source_pin));
        }
         if dest_pin == 0 || dest_pin > dest_chip.pin_count() {
            return Err(CircuitError::InvalidPinNumber(dest_chip_idx, dest_pin));
        }

        let source_pin_type = source_chip.get_pin_type(source_pin);
        let dest_pin_type = dest_chip.get_pin_type(dest_pin);

        // Source must be Output or Vcc (allow driving from power rail)
        if source_pin_type != PinType::Output && source_pin_type != PinType::Vcc && source_pin_type != PinType::Gnd {
             return Err(CircuitError::PinNotAnOutput(source_chip_idx, source_pin));
        }

        // Destination must be Input
        if dest_pin_type != PinType::Input {
            // Special check: Disallow connecting *to* Vcc/Gnd pins
            if dest_pin_type == PinType::Vcc || dest_pin_type == PinType::Gnd {
                 return Err(CircuitError::CannotConnectToVccGnd(dest_chip_idx, dest_pin));
            }
            return Err(CircuitError::PinNotAnInput(dest_chip_idx, dest_pin));
        }

        // Destination input must not already be driven
        let dest_key = (dest_chip_idx, dest_pin);
        if self.driven_inputs.contains(&dest_key) {
            return Err(CircuitError::InputPinAlreadyDriven(dest_chip_idx, dest_pin));
        }

        // --- Add Connection ---
        self.connections.push(Connection {
            source_chip_idx,
            source_pin,
            dest_chip_idx,
            dest_pin,
        });
        self.driven_inputs.insert(dest_key);

        Ok(())
    }

    /// Sets the logic level of an external input pin.
    ///
    /// This should only be used for input pins that are *not* connected
    /// to the output of another chip via `add_wire`.
    ///
    /// # Errors
    /// Returns `CircuitError` if:
    /// - Chip index is invalid.
    /// - Pin number is invalid.
    /// - The specified pin is not an `Input` pin.
    /// - The specified input pin is already driven by a wire connection.
    pub fn set_external_input(
        &mut self,
        chip_idx: usize,
        pin: usize,
        level: LogicLevel,
    ) -> Result<(), CircuitError> {
        let chip = self.chips.get_mut(chip_idx)
            .ok_or(CircuitError::InvalidChipIndex(chip_idx))?;

        if pin == 0 || pin > chip.pin_count() {
             return Err(CircuitError::InvalidPinNumber(chip_idx, pin));
        }

        let pin_type = chip.get_pin_type(pin);
        if pin_type != PinType::Input {
            return Err(CircuitError::CannotSetLevelOnNonInput(chip_idx, pin));
        }

        // Check if this input is driven by a wire
        if self.driven_inputs.contains(&(chip_idx, pin)) {
            return Err(CircuitError::CannotSetDrivenInput(chip_idx, pin));
        }

        // Use the chip's method to set the input
        chip.set_input(pin, level);
        Ok(())
    }

    /// Gets the current logic level of a specific pin.
    ///
    /// Can be used to read the state of inputs or outputs.
    /// Note: For output pins, this reflects the state *after* the last `tick()`.
    ///
    /// # Errors
    /// Returns `CircuitError` if:
    /// - Chip index is invalid.
    /// - Pin number is invalid.
    /// - Attempting to read directly from Vcc, Gnd, or Nc pins (use fixed High/Low).
    pub fn get_pin_level(&self, chip_idx: usize, pin: usize) -> Result<LogicLevel, CircuitError> {
        let chip = self.chips.get(chip_idx)
            .ok_or(CircuitError::InvalidChipIndex(chip_idx))?;

        if pin == 0 || pin > chip.pin_count() {
             return Err(CircuitError::InvalidPinNumber(chip_idx, pin));
        }

        let pin_type = chip.get_pin_type(pin);
        match pin_type {
            PinType::Input => {
                // Reading an input gives its currently set value
                // We need a way to access the chip's internal pin_state for this.
                // Let's add a helper method to the Chip trait or access pin_state directly if possible.
                // *** Modification Needed in libttl::chips::common or chip impls ***
                // For now, let's *assume* we can get the input state.
                // A temporary workaround: get_output panics on input, so we can't use it.
                // We will *simulate* this by looking if it's driven or relying on last set_input.
                // A better way is required for a robust implementation.
                 // Let's directly access the internal state if we modify the chip structure/trait.
                 // --- WORKAROUND START ---
                 // For now, we can't perfectly read inputs back without modifying Chip trait/impls.
                 // We'll return Low as a placeholder if not an output.
                 // A proper implementation would require changes in libttl.
                 // However, since we primarily *get* outputs and *set* inputs, this is less critical
                 // for simulation itself, but important for probing.
                 // Let's refine the get_output in chips to maybe NOT panic for inputs, but return state.
                 // *Revised approach*: The `Chip` trait's `get_output` is specifically for *output* pins.
                 // Reading inputs isn't part of that contract. We'd need a separate `get_input_level`
                 // or access to the internal `pin_state` array. Let's stick to the defined interface
                 // for now and only allow getting output levels. Users can track external inputs separately.
                 // **Decision:** For this implementation, `get_pin_level` will only work for Output pins.
                 Err(CircuitError::CannotGetLevelFromVccGndNc(chip_idx, pin)) // Re-use error for now
                 // --- WORKAROUND END ---

            }
            PinType::Output => Ok(chip.get_output(pin)),
            PinType::Vcc => Ok(LogicLevel::High), // Vcc is always High
            PinType::Gnd => Ok(LogicLevel::Low),   // Gnd is always Low
            PinType::Nc => Err(CircuitError::CannotGetLevelFromVccGndNc(chip_idx, pin)),
        }
    }

     /// Gets the current logic level of an OUTPUT pin specifically.
     /// This is a clearer alternative to get_pin_level based on the current Chip trait limitations.
     ///
     /// # Errors
     /// Returns `CircuitError` if:
     /// - Chip index is invalid.
     /// - Pin number is invalid or not an Output pin.
    pub fn get_output_level(&self, chip_idx: usize, pin: usize) -> Result<LogicLevel, CircuitError> {
        let chip = self.chips.get(chip_idx)
            .ok_or(CircuitError::InvalidChipIndex(chip_idx))?;

        if pin == 0 || pin > chip.pin_count() {
             return Err(CircuitError::InvalidPinNumber(chip_idx, pin));
        }
         let pin_type = chip.get_pin_type(pin);
         if pin_type != PinType::Output {
            // Use PinNotAnOutput error, even though it usually applies to source pins in add_wire
            return Err(CircuitError::PinNotAnOutput(chip_idx, pin));
         }
         // get_output will panic if it's not an output, but we checked type already
         Ok(chip.get_output(pin))
    }


    /// Simulates one clock cycle or propagation delay step for the entire circuit.
    ///
    /// This performs a two-phase update:
    /// 1. Evaluate: Determines the next state of all input pins based on the
    ///    *current* state of the output pins they are connected to.
    /// 2. Commit & Update: Applies the calculated input levels to the chips
    ///    and then calls `update()` on each chip to recalculate internal logic
    ///    and output pin states.
    pub fn tick(&mut self) {
        // Phase 1: Evaluate connections and determine next input states
        let mut next_input_levels: HashMap<(usize, usize), LogicLevel> = HashMap::new();

        for conn in &self.connections {
            if let Some(source_chip) = self.chips.get(conn.source_chip_idx) {
                 // Determine source level (handle Vcc/Gnd directly)
                 let source_level = match source_chip.get_pin_type(conn.source_pin) {
                    PinType::Output => source_chip.get_output(conn.source_pin),
                    PinType::Vcc => LogicLevel::High,
                    PinType::Gnd => LogicLevel::Low,
                    _ => {
                        // This shouldn'hui happen due to add_wire validation, but safer to handle
                        eprintln!(
                            "Warning: Connection source pin {} on chip {} is not Output/Vcc/Gnd. Skipping.",
                            conn.source_pin, conn.source_chip_idx
                        );
                        continue; // Skip this connection if source is invalid type somehow
                    }
                 };

                // Store the level intended for the destination input pin
                next_input_levels.insert((conn.dest_chip_idx, conn.dest_pin), source_level);
            } else {
                 eprintln!("Warning: Source chip index {} in connection is invalid. Skipping.", conn.source_chip_idx);
            }
        }

        // Phase 2: Commit input levels
        for ((chip_idx, pin), level) in next_input_levels {
            if let Some(dest_chip) = self.chips.get_mut(chip_idx) {
                // set_input will panic if pin is invalid or not an input,
                // but add_wire should have prevented this.
                dest_chip.set_input(pin, level);
            }
             else {
                 eprintln!("Warning: Destination chip index {} for input commit is invalid. Skipping.", chip_idx);
            }
        }

        // Phase 3: Update all chips
        for chip in self.chips.iter_mut() {
            chip.update();
        }
    }

    /// Runs the simulation for a specified number of ticks.
    pub fn run(&mut self, ticks: usize) {
        for _ in 0..ticks {
            self.tick();
        }
    }

    /// Prints the current state of all relevant pins (inputs and outputs).
    pub fn print_state(&self) {
        println!("--- Circuit State ---");
        for (idx, chip) in self.chips.iter().enumerate() {
            println!("Chip {}: {} ({})", idx, chip.name(), chip.pin_count());
            for pin in 1..=chip.pin_count() {
                let pin_type = chip.get_pin_type(pin);
                match pin_type {
                    PinType::Input => {
                        let is_driven = self.driven_inputs.contains(&(idx, pin));
                        // Cannot directly read input state easily with current trait.
                        // We could store external inputs separately in Circuit if needed.
                        println!(
                            "  Pin {:2}: Input  (Driven: {})", // Value requires modification to libttl
                            pin,
                            if is_driven { "Yes" } else { "No" }
                        );
                    }
                    PinType::Output => {
                         match self.get_output_level(idx, pin) {
                            Ok(level) => println!("  Pin {:2}: Output = {:?}", pin, level),
                            Err(e) => println!("  Pin {:2}: Output = Error reading ({})", pin, e),
                         }

                    }
                    PinType::Vcc => println!("  Pin {:2}: Vcc    = High", pin),
                    PinType::Gnd => println!("  Pin {:2}: Gnd    = Low", pin),
                    PinType::Nc => println!("  Pin {:2}: NC", pin),
                }
            }
        }
        println!("---------------------");
    }
}

impl Default for Circuit {
    fn default() -> Self {
        Self::new()
    }
}