1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
//! 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()
}
}