This page covers the code-level implementation of wiRedPanda's simulation engine: the four-state logic system, simulation loop phases, topological sorting, wiring data structures, and feedback loop handling. For a conceptual overview of the simulation model, see the [Simulation Guide](Simulation.md). --- ## Four-State Logic All signals in wiRedPanda use four states, defined in `App/Core/Enums.h`: | State | Value | Meaning | Visual Color | |------------|-------|------------------------------|--------------| | `Unknown` | -1 | Not yet resolved / floating | Gray | | `Inactive` | 0 | Logic LOW | Dark green (theme-dependent) | | `Active` | 1 | Logic HIGH | Green (theme-dependent) | | `Error` | 2 | Bus conflict / indeterminate | Red | `App/Core/StatusOps.h` provides logic operations that respect these states with **domination rules**: - **AND**: `Inactive` dominates — `AND(0, Unknown) = 0` because any false input makes the AND false. - **OR**: `Active` dominates — `OR(1, Unknown) = 1` because any true input makes the OR true. - **NOT**: Inverts `Active`/`Inactive`; passes `Unknown` and `Error` through unchanged. - **XOR**: Requires all inputs to be known; any `Unknown` yields `Unknown`. This matters for simulation correctness: a combinational gate can produce a determinate output even when some inputs are unconnected or unknown, which matches real-world digital logic behavior. ### Strict vs. Permissive Input Updates Elements choose which input-reading strategy to use: - **`simUpdateInputsAllowUnknown()`** (permissive) — Used by combinational gates. Allows `Unknown`/`Error` through so domination rules can resolve them. Only fails if a port is truly unconnected with no default. - **`simUpdateInputs()`** (strict) — Used by sequential elements. Any `Unknown` or `Error` input causes the element to propagate `Unknown` to all outputs and bail out. Sequential elements need complete, valid data. --- ## Simulation Loop The simulation (`App/Simulation/Simulation.cpp`) runs on a **1ms QTimer** tick. Each tick executes these phases in order: ```mermaid flowchart TD timer(["QTimer (1 ms)"]) clocks["0. updateClock()
Advance clock elements (real-time)"] step1["1. updateOutputs()
Propagate user inputs: buttons, switches"] step2["2. updateLogic()
Process elements in topological order:
gates (zero-delay), flip-flops (edge)"] feedback{"Feedback loop?"} run_logic["Run updateLogic()
on feedback elements"] settled{"Settled or
iter >= 10?"} step3["3. updatePort()
Propagate values via connections (out->in)"] step4["4. Update outputs
LEDs, displays, buzzers redraw"] wait(["Wait for next tick (1 ms)"]) timer --> clocks --> step1 --> step2 --> feedback feedback -- Yes --> run_logic --> settled settled -- No --> run_logic settled -- Yes --> step3 feedback -- No --> step3 step3 --> step4 --> wait ``` --- ## Topological Sorting Before simulation starts, the engine builds a dependency graph from wire connections and computes a **topological sort** (`App/Core/Priorities.h`): - Each element is assigned a **priority** = length of the longest path from it to any sink (output), plus one. - Elements with higher priority are evaluated first (sources before sinks). - This guarantees that when an element's `updateLogic()` runs, all its predecessors have already been updated this cycle. ``` Input -> AND -> OR -> LED 1st 2nd 3rd 4th ``` This is done during the `initialize()` phase, which builds the dependency graph from the scene's connections. --- ## Simulation Wiring During simulation initialization, physical wire connections are converted into a lightweight predecessor map: ```cpp struct SimInputConnection { GraphicElement *sourceElement = nullptr; int sourceOutputIndex = 0; }; ``` Each element stores a vector of these for its inputs. During `updateLogic()`, reading an input is just a direct pointer dereference — no graph traversal needed per tick. --- ## Feedback Loop Handling Some circuits have feedback (e.g., an SR latch where outputs feed back to inputs). The engine detects these using **Tarjan's strongly connected components** algorithm: 1. Any element in a cycle is marked as a **feedback node**. 2. Instead of a single-pass evaluation, the engine uses **iterative settling**: it runs `updateLogic()` on all elements, checks if any output changed, and repeats — up to 10 iterations or until convergence. 3. If the circuit does not converge (e.g., a ring oscillator), a warning is emitted via the status bar. --- ## See Also - [Simulation Guide](Simulation.md) — Conceptual overview of the simulation model - [Element System](Element-System.md) — How elements implement `updateLogic()` - [Architecture Overview](Architecture-Overview.md) — Where the simulation engine fits in the overall architecture