wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
Simulation.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <algorithm>
7
8#include <QGraphicsView>
9#include <QGuiApplication>
10#include <QScreen>
11
13#include "App/Core/Common.h"
14#include "App/Core/Priorities.h"
18#include "App/Element/IC.h"
20#include "App/Wiring/Port.h"
21
22using namespace std::chrono_literals;
23
25 : QObject(parent)
26 , m_host(host)
27{
28 // 1ms tick drives the simulation at ~1000 steps/second — fast enough for
29 // human perception while keeping CPU load predictable.
30 m_timer.setInterval(1ms);
31 // Guarded at the CONNECTION, not inside update(): update() is also public API called
32 // directly (BeWavedDolphin's sweep, MCP, tests), and those callers must keep seeing an
33 // exception rather than a silently swallowed tick. Only the timer-driven invocation
34 // crosses Qt's signal-slot dispatch, which is where throwing is undefined behaviour --
35 // on macOS the unwinder reaches std::terminate mid-stack and Application::notify's
36 // backstop never runs (see Application::guardedSlot). update() runs arbitrary
37 // updateLogic() code 1000x/s, so it is exactly the kind of body that doc means.
38 connect(&m_timer, &QTimer::timeout, this, [this] {
39 Application::guardedSlot(this, [this] { update(); });
40 });
41
42 // Derive the visual refresh interval from the monitor's refresh rate so
43 // we match the display without wasting repaints. Falls back to 60 Hz.
44 if (auto *screen = QGuiApplication::primaryScreen()) {
45 const qreal hz = screen->refreshRate();
46 if (hz > 0) {
47 m_visualTickInterval = (std::max)(1, static_cast<int>(1000.0 / hz));
48 }
49 }
50}
51
53{
54 m_visualThrottleEnabled = enabled;
55 if (enabled) {
56 m_visualTickCount = 0; // start fresh so throttle resumes cleanly
57 }
58}
59
61{
62 // Lazily build the simulation layer on the first tick after a restart so
63 // that circuit edits made while stopped are always reflected when the
64 // simulation resumes.
65 if (!m_initialized && !initialize()) {
66 return;
67 }
68
69 // Bug 5 invariant: the H2 cluster fix established that m_initialized=true
70 // implies the topology vectors reflect the current scene. Any future change
71 // that touches m_initialized without rebuilding the vectors trips here in
72 // debug/asan/ubsan builds — much earlier than a tick-time crash.
73 Q_ASSERT(m_initialized);
74
75 // Snapshot the topology vectors before iterating: if restart() is invoked
76 // reentrantly while this tick is mid-flight, it clears and rebuilds
77 // m_clocks/m_inputs/m_sequentialElements/m_sortedElements/m_outputs, which
78 // would invalidate any in-flight range-for iterator over the members
79 // themselves. Iterating local copies keeps this tick's iteration valid for
80 // its remainder even if the members mutate underneath.
81 const auto clocks = m_clocks;
82 const auto inputs = m_inputs;
83 const auto sequentialElements = m_sequentialElements;
84 const auto elements = m_sortedElements;
85 const auto outputs = m_outputs;
86
87 // Clock elements are the only truly time-driven components; all other logic
88 // is combinational and responds immediately to their values.
89 if (m_timer.isActive()) {
90 const auto globalTime = std::chrono::steady_clock::now();
91
92 for (auto *clock : clocks) {
93 if (clock) {
94 clock->updateClock(globalTime);
95 }
96 }
97 }
98
99 // Phase 1: propagate user-controlled inputs (switches, buttons, etc.)
100 for (auto *inputElm : inputs) {
101 if (inputElm) {
102 inputElm->updateOutputs();
103 }
104 }
105
106 // Advance the visual throttle on every tick (skipped or not) so the phase 3-4
107 // cadence stays time-based. Non-interactive callers (tests, BeWavedDolphin's
108 // throttle disabler) flush on every tick, as before.
109 const bool visualsDue = !(m_visualThrottleEnabled && Application::interactiveMode)
110 || (++m_visualTickCount >= m_visualTickInterval);
111 if (visualsDue) {
112 m_visualTickCount = 0;
113 }
114
115 // Skip provably-idle ticks. A completed sweep whose settle passes converged is a
116 // fixed point of the deterministic element functions; it can only be left by a
117 // clock flip, an input-element change (both flagged through setOutputValue()'s
118 // change detection -- user toggles included, they write the same way), or a
119 // structural edit (restart() clears m_atFixedPoint). Everything else recomputes
120 // bit-identical outputs 1000x/s, which on large clocked circuits is almost every
121 // tick. The flags are cleared as they are read so one flip triggers one sweep.
122 bool sourceChanged = false;
123 for (auto *clock : clocks) {
124 if (clock && clock->outputChanged()) {
125 sourceChanged = true;
126 clock->clearOutputChanged();
127 }
128 }
129 for (auto *inputElm : inputs) {
130 if (inputElm && inputElm->outputChanged()) {
131 sourceChanged = true;
132 inputElm->clearOutputChanged();
133 }
134 }
135
136 if (!sourceChanged && m_atFixedPoint) {
137 if (visualsDue && m_visualsDirty) {
138 pushVisualStatuses(elements, outputs);
139 m_visualsDirty = false;
140 }
141 return;
142 }
143
144 // Phase 2: update all GraphicElements in topological order.
145 //
146 // Synchronous sequential elements (flip-flops, latches — ElementGroup::Memory,
147 // collected across the whole IC hierarchy) get non-blocking semantics: their
148 // outputs are staged during the pass and committed together afterwards, so any
149 // combinational logic between them (gated clocks especially) reads the
150 // pre-tick state. This matches real synchronous hardware and the exported
151 // SystemVerilog's non-blocking (<=) model. Gate-built feedback latches are
152 // ElementGroup::Gate, not Memory, so they are never deferred and settle
153 // through the existing path unchanged.
154 for (auto *element : sequentialElements) {
155 if (element) {
156 element->beginDeferredCommit();
157 }
158 }
159
160 // A plain topological sweep reaches its fixed point in one pass by construction;
161 // only feedback settling can fail to converge (oscillating circuits).
162 bool sweepConverged = true;
163
164 if (m_simHasFeedbackElements) {
165 // Use iterative settling for circuits with feedback loops.
166 sweepConverged = updateWithIterativeSettling(elements);
167 } else {
168 // Phase 2: update all logic elements in topologically sorted order so
169 // every gate sees its inputs before computing its output.
170 for (auto *element : elements) {
171 if (element) {
172 element->updateLogic();
173 }
174 }
175 }
176
177 // Publish every staged sequential output simultaneously (global commit).
178 bool anySequentialChanged = false;
179 for (auto *element : sequentialElements) {
180 if (element) {
181 element->clearOutputChanged();
182 element->commitDeferredOutputs();
183 if (element->outputChanged()) {
184 anySequentialChanged = true;
185 }
186 }
187 }
188
189 // Post-edge settle (the second half of a clock cycle / a bounded delta
190 // cycle): once flip-flops have committed, re-propagate their new outputs
191 // through combinational logic and IC output boundaries so the end-of-tick
192 // state is a true fixed point — and so asynchronous overrides (preset/clear)
193 // surface immediately rather than lagging a tick behind. Sequential elements
194 // are skipped here so they are not re-clocked. Only needed on ticks where an
195 // edge or async override actually changed sequential state; steady-state
196 // ticks pay nothing. Feed-forward logic settles in one topological sweep;
197 // iterate only when combinational feedback is present.
198 if (anySequentialChanged) {
199 const int maxPasses = m_simHasFeedbackElements ? kMaxSettleIterations : 1;
200 for (int pass = 0; pass < maxPasses; ++pass) {
201 bool changed = false;
202 for (auto *element : elements) {
203 if (element && element->elementGroup() != ElementGroup::Memory) {
204 element->clearOutputChanged();
205 element->resettleCombinational();
206 changed = changed || element->outputChanged();
207 }
208 }
209 if (!changed) {
210 break;
211 }
212 if (pass == maxPasses - 1) {
213 // Exhausted the budget while still changing: not a fixed point.
214 sweepConverged = false;
215 }
216 }
217 }
218
219 // This sweep's result is a fixed point unless a settle pass ran out of budget while
220 // still changing (an oscillating feedback circuit, which must keep sweeping).
221 m_atFixedPoint = sweepConverged;
222 m_visualsDirty = true;
223
224 // Visual updates only need to run at display-refresh rate, not at simulation
225 // rate (1000 Hz) — skipping most ticks avoids dirtying QGraphicsItems that would
226 // be overwritten before the next repaint. In non-interactive (test) mode every
227 // tick flushes so tests see immediate visual state after each step.
228 if (visualsDue) {
229 pushVisualStatuses(elements, outputs);
230 m_visualsDirty = false;
231 }
232}
233
234void Simulation::pushVisualStatuses(const QVector<GraphicElement *> &elements, const QVector<GraphicElement *> &outputs)
235{
236 // Phase 3: push computed logic values onto all output port visuals.
237 // Iterating elements (not connections) ensures unconnected output ports
238 // (e.g. -Q of a flip-flop with no wire attached) are also updated.
239 // setStatus() fans out through any attached connections automatically.
240 for (auto *element : elements) {
241 if (element) {
242 for (auto *outputPort : element->outputs()) {
243 updatePort(outputPort);
244 }
245 }
246 }
247
248 // Phase 4: refresh output element visuals (LEDs, buzzers, etc.) using their input ports
249 for (auto *outputElm : outputs) {
250 if (outputElm) {
251 for (auto *inputPort : outputElm->inputs()) {
252 if (inputPort) {
253 updatePort(inputPort);
254 }
255 }
256 }
257 }
258}
259
260void Simulation::updatePort(OutputPort *port)
261{
262 if (!port) {
263 return;
264 }
265
266 auto *element = port->graphicElement();
267 if (!element) { // LCOV_EXCL_LINE — every Port has a non-null owner set unconditionally by ElementPorts::addPort() at construction (the "port always has an owner" invariant established throughout this sweep).
268 port->setStatus(Status::Unknown); // LCOV_EXCL_LINE — see above.
269 return; // LCOV_EXCL_LINE — see above.
270 }
271
272 port->setStatus(element->outputValue(port->index()));
273}
274
275void Simulation::updatePort(InputPort *port)
276{
277 if (!port) {
278 return;
279 }
280
281 const auto &conns = port->connections();
282 const Status status = (!conns.isEmpty() && conns.first()->startPort())
283 ? conns.first()->startPort()->status()
284 : port->defaultValue();
285 port->setStatus(status);
286
287 // Output elements (LEDs, buzzers) need an explicit repaint to show the new state.
288 auto *elm = port->graphicElement();
289 if (elm && elm->elementGroup() == ElementGroup::Output) {
290 elm->refresh();
291 }
292}
293
295{
296 // Invalidate the cached topology. Clearing the flag alone is not
297 // enough: update() iterates m_sortedElements/m_clocks/m_inputs/m_outputs
298 // before a re-initialize can run (for instance when Application::notify()
299 // spins a QMessageBox nested event loop), and any entry that refers to an
300 // element we've already freed faults on its vtable read. Drop every
301 // reference so the next tick's initialize() can rebuild them cleanly.
302 m_initialized = false;
303 // A structural edit invalidates the fixed point: the next tick must sweep, and its
304 // visuals must flush even if the throttle boundary lands on a later skipped tick.
305 m_atFixedPoint = false;
306 m_visualsDirty = true;
307 m_sortedElements.clear();
308 m_sequentialElements.clear();
309 m_clocks.clear();
310 m_inputs.clear();
311 m_outputs.clear();
312 // Bug 4 postcondition: any future cached state added to Simulation must be
313 // cleared above. This assert documents the invariant for future maintainers
314 // and trips immediately if a new vector is forgotten.
315 Q_ASSERT(!m_initialized);
316 Q_ASSERT(m_sortedElements.isEmpty() && m_sequentialElements.isEmpty()
317 && m_clocks.isEmpty() && m_inputs.isEmpty() && m_outputs.isEmpty());
318}
319
321{
322 return m_timer.isActive();
323}
324
326{
327 return m_simFeedbackNodes.contains(element);
328}
329
331{
332 // Record when the pause began (only when actually running) so start() can shift the
333 // clocks' phase reference by the pause duration instead of resetting them.
334 if (m_timer.isActive()) {
335 m_pausedAt = std::chrono::steady_clock::now();
336 m_hasPausedAt = true;
337 }
338 m_timer.stop();
339 if (m_host) {
340 m_host->setMuted(true);
341 }
342}
343
345{
346 qCDebug(zero) << "Starting simulation.";
347
348 if (!m_initialized) {
349 initialize();
350 } else if (m_hasPausedAt) {
351 // Resuming after a stop(): the wall clock advanced while paused, so shift each
352 // clock's phase reference by the pause duration. This prevents the burst of missed
353 // toggles a stale reference would cause, WITHOUT resetting the clocks — resetClock()
354 // forces the output HIGH and restarts the phase, which would feed a spurious rising
355 // edge into every clock-driven circuit on each SimulationBlocker cycle (every
356 // UpdateCommand redo/undo, including a plain InputSwitch click). Level and phase
357 // survive any pause/resume; only initialize() (Restart, rebuilds) gives clocks a
358 // fresh HIGH start via resetClock().
359 const auto pause = std::chrono::steady_clock::now() - m_pausedAt;
360 for (auto *clock : std::as_const(m_clocks)) {
361 if (clock) {
362 clock->shiftClock(pause);
363 }
364 }
365 m_hasPausedAt = false; // consumed; a repeated start() must not double-shift
366 } else {
367 // Initialized but with no recorded pause (e.g. initialize() ran directly via a
368 // structural edit while stopped): the clocks' references are stale by an unknown
369 // amount, so reset them to now.
370 const auto globalTime = std::chrono::steady_clock::now();
371 for (auto *clock : std::as_const(m_clocks)) {
372 if (clock) {
373 clock->resetClock(globalTime);
374 }
375 }
376 }
377
378 m_timer.start();
379 if (m_host) {
380 m_host->setMuted(m_userMuted);
381 }
382 qCDebug(zero) << "Simulation started.";
383}
384
385void Simulation::setUserMuted(const bool muted)
386{
387 m_userMuted = muted;
388 if (m_host) {
389 m_host->setMuted(muted);
390 }
391}
392
394{
395 return m_userMuted;
396}
397
398bool Simulation::updateWithIterativeSettling(const QVector<GraphicElement *> &elements)
399{
400 const bool converged = iterativeSettle(elements);
401 if (!converged && !m_convergenceWarned) {
402 m_convergenceWarned = true;
403 qDebug() << "Feedback circuit did not converge after 10 iterations";
404 emit simulationWarning(tr("Warning: feedback circuit did not converge — the circuit may be oscillating."));
405 }
406 return converged;
407}
408
410{
411 if (!m_host) {
412 return false;
413 }
414
415 // Rebuild all categorised lists from scratch so stale pointers from
416 // a previous circuit state don't linger after undo/redo or file load.
417 m_convergenceWarned = false;
418 m_clocks.clear();
419 m_outputs.clear();
420 m_inputs.clear();
421 m_sortedElements.clear();
422 m_sequentialElements.clear();
423
424 QVector<GraphicElement *> elements;
425 auto items = m_host->simulationItems();
426
427 // Sort items by position coordinates for consistent ordering between runs.
428 // QGraphicsScene::items() returns items in an unspecified Z/stacking order;
429 // stabilising on (Y, X) gives deterministic wire-update sequences across
430 // sessions and makes test results reproducible.
431 std::stable_sort(items.begin(), items.end(), [](const auto &a, const auto &b) {
432 if (!a || !b) {
433 return a != nullptr;
434 }
435 // Sort by Y coordinate first, then X coordinate for consistent 2D ordering
436 if (qFuzzyCompare(a->y(), b->y())) {
437 return a->x() < b->x();
438 }
439 return a->y() < b->y();
440 });
441
442 // A scene with only one item is the scene border/background rectangle;
443 // there is no circuit yet, so building a simulation graph would be pointless.
444 if (items.size() == 1) {
445 return false;
446 }
447
448 qCDebug(two) << "GENERATING SIMULATION LAYER.";
449
450 const auto globalTime = std::chrono::steady_clock::now();
451
452 for (auto *item : std::as_const(items)) {
453 if (!item) {
454 continue;
455 }
456
457 if (item->type() == GraphicElement::Type) {
458 auto *element = qgraphicsitem_cast<GraphicElement *>(item);
459 if (!element) { // LCOV_EXCL_LINE — item->type() == GraphicElement::Type is GraphicElement's own hardcoded type() override, so the cast can never fail for an item reporting exactly that type.
460 continue; // LCOV_EXCL_LINE — see above.
461 }
462 elements.append(element);
463
464 if (element->elementType() == ElementType::Clock) {
465 auto *clock = qobject_cast<Clock *>(element);
466 if (clock) {
467 m_clocks.append(clock);
468 clock->resetClock(globalTime);
469 }
470 }
471
472 if (element->elementGroup() == ElementGroup::Input) {
473 auto *input = qobject_cast<GraphicElementInput *>(element);
474 if (input) {
475 m_inputs.append(input);
476 }
477 }
478
479 if (element->elementGroup() == ElementGroup::Output) {
480 m_outputs.append(element);
481 }
482 }
483 }
484
485 qCDebug(zero) << "Elements read: " << elements.size();
486
487 // Every clock was just reset to "now" above, so any pause recorded by stop() is stale —
488 // a later start() must not additionally shift the fresh references by the old pause.
489 m_hasPausedAt = false;
490
491 if (elements.empty()) {
492 return false;
493 }
494
495 // Initialize simulation vectors on all scene-level elements
496 for (auto *elm : std::as_const(elements)) {
497 elm->initSimulationVectors(elm->inputSize(), elm->outputSize());
498 }
499
500 // Build connection graph
501 buildConnectionGraph(elements);
502 connectWirelessElements(elements);
503
504 // Initialize IC internal simulation graphs
505 for (auto *elm : std::as_const(elements)) {
506 if (elm->elementType() == ElementType::IC) {
507 static_cast<IC *>(elm)->initializeSimulation();
508 }
509 }
510
511 // Topological sort with feedback detection
512 sortSimElements(elements);
513
514 // Collect every synchronous sequential element (across the IC hierarchy) so
515 // Phase 2 can give them non-blocking commit semantics.
516 collectSequentialElements(elements);
517
518 m_initialized = true;
519
520 qCDebug(zero) << "Finished simulation layer.";
521 return true;
522}
523
524// --- Simulation graph building ---
525
526void Simulation::buildConnectionGraph(const QVector<GraphicElement *> &elements)
527{
528 for (auto *elm : std::as_const(elements)) {
529 for (int i = 0; i < elm->inputSize(); ++i) {
530 auto *inputPort = elm->inputPort(i);
531 const auto &connections = inputPort->connections();
532
533 if (connections.size() == 1) {
534 auto *connection = connections.constFirst();
535 if (!connection) { // LCOV_EXCL_LINE — Port::attachConnection() guards against a null argument (returns immediately without inserting), so m_connections can never contain a null entry.
536 continue; // LCOV_EXCL_LINE — see above.
537 }
538 if (auto *outputPort = connection->startPort()) {
539 auto *sourceElement = outputPort->graphicElement();
540 if (sourceElement) {
541 elm->connectPredecessor(i, sourceElement, outputPort->index());
542 }
543 }
544 }
545 }
546 }
547}
548
549void Simulation::connectWirelessElements(const QVector<GraphicElement *> &elements)
550{
551 const auto txMap = buildTxMap(elements);
552
553 // Wire each Rx node's input to the matching Tx node's output.
554 // connectPredecessor() overwrites whatever buildConnectionGraph() set,
555 // so the topological sort will see the true wireless dependency.
556 for (auto *elm : std::as_const(elements)) {
557 if (elm->wirelessMode() != WirelessMode::Rx || elm->label().isEmpty()) {
558 continue;
559 }
560 if (auto *txElement = txMap.value(elm->label(), nullptr)) {
561 elm->connectPredecessor(0, txElement, 0);
562 }
563 }
564}
565
566QHash<QString, GraphicElement *> Simulation::buildTxMap(const QVector<GraphicElement *> &elements)
567{
568 QHash<QString, GraphicElement *> txMap;
569 for (auto *elm : std::as_const(elements)) {
570 if (elm->wirelessMode() == WirelessMode::Tx && !elm->label().isEmpty()) {
571 if (!txMap.contains(elm->label())) {
572 txMap.insert(elm->label(), elm);
573 }
574 }
575 }
576 return txMap;
577} // LCOV_EXCL_LINE — recurring pattern 1: compiler-generated cleanup for the returned QHash<QString, GraphicElement *>, never reached after the return above.
578
579QHash<GraphicElement *, QVector<GraphicElement *>> Simulation::buildSuccessorGraph(
580 const QVector<GraphicElement *> &elements,
581 const QHash<QString, GraphicElement *> &txMap)
582{
583 QHash<GraphicElement *, QVector<GraphicElement *>> successors;
584
585 // Build successor edges from physical connections
586 for (auto *elm : std::as_const(elements)) {
587 for (auto *outputPort : elm->outputs()) {
588 for (auto *conn : outputPort->connections()) {
589 if (auto *endPort = conn->endPort()) {
590 auto *successor = endPort->graphicElement();
591 if (successor) {
592 auto &vec = successors[elm];
593 if (!vec.contains(successor)) {
594 vec.append(successor);
595 }
596 }
597 }
598 }
599 }
600 }
601
602 // Add wireless Tx→Rx edges.
603 // connectWirelessElements() already set predecessors for simulation input routing,
604 // but those don't create Connection objects, so the connection-walking loop above
605 // doesn't see wireless dependencies. We must add them explicitly here for correct
606 // topological ordering.
607 for (auto *elm : std::as_const(elements)) {
608 if (elm->wirelessMode() == WirelessMode::Rx && !elm->label().isEmpty()) {
609 if (auto *tx = txMap.value(elm->label(), nullptr)) {
610 auto &txVec = successors[tx];
611 if (!txVec.contains(elm)) {
612 txVec.append(elm);
613 }
614 }
615 }
616 }
617
618 return successors;
619} // LCOV_EXCL_LINE — recurring pattern 1: compiler-generated cleanup for the returned QHash<GraphicElement *, QVector<GraphicElement *>>, never reached after the return above.
620
622 const QVector<GraphicElement *> &elements,
623 const QHash<GraphicElement *, QVector<GraphicElement *>> &successors)
624{
625 SortResult result;
626
627 QVector<GraphicElement *> rawPtrs(elements);
628 calculatePriorities(rawPtrs, successors, result.priorities);
629 result.feedbackNodes = findFeedbackNodes(rawPtrs, successors);
630
631 result.sorted = elements;
632 std::stable_sort(result.sorted.begin(), result.sorted.end(),
633 [&result](const auto *a, const auto *b) {
634 return result.priorities.value(const_cast<GraphicElement *>(a), -1)
635 > result.priorities.value(const_cast<GraphicElement *>(b), -1);
636 });
637
638 return result;
639}
640
641bool Simulation::iterativeSettle(const QVector<GraphicElement *> &elements, const int maxIterations)
642{
643 for (int iteration = 0; iteration < maxIterations; ++iteration) {
644 for (auto *element : std::as_const(elements)) {
645 if (!element) {
646 continue;
647 }
648 element->clearOutputChanged();
649 element->updateLogic();
650 }
651
652 const bool converged = std::none_of(elements.cbegin(), elements.cend(),
653 [](const auto *element) { return element && element->outputChanged(); });
654
655 if (converged) {
656 return true;
657 }
658 }
659 return false;
660}
661
662void Simulation::sortSimElements(const QVector<GraphicElement *> &elements)
663{
664 const auto txMap = buildTxMap(elements);
665 const auto successors = buildSuccessorGraph(elements, txMap);
666 const auto result = topologicalSort(elements, successors);
667
668 m_simPriorities.clear();
669 m_simFeedbackNodes.clear();
670 for (auto *elm : std::as_const(elements)) {
671 m_simPriorities[elm] = result.priorities.value(elm, -1);
672 if (result.feedbackNodes.contains(elm)) {
673 m_simFeedbackNodes.insert(elm);
674 }
675 }
676 m_simHasFeedbackElements = !m_simFeedbackNodes.isEmpty();
677 m_sortedElements = result.sorted;
678}
679
680void Simulation::collectSequentialElements(const QVector<GraphicElement *> &elements)
681{
682 for (auto *elm : std::as_const(elements)) {
683 if (!elm) {
684 continue;
685 }
686 if (elm->elementGroup() == ElementGroup::Memory) {
687 m_sequentialElements.append(elm);
688 }
689 if (elm->elementType() == ElementType::IC) {
690 collectSequentialElements(static_cast<IC *>(elm)->internalElements());
691 }
692 }
693}
Custom QApplication subclass with exception handling and main-window access.
Graphic element for the real-time clock input.
Common logging utilities, the Pandaception error type, and helper macros.
#define qCDebug(category)
Definition Common.h:29
Connection: a wire that connects an output port to an input port in the circuit scene.
Enums::Status Status
Definition Enums.h:106
Abstract base class for all graphical circuit elements.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
Port classes: Port (base), InputPort, and OutputPort.
Graph algorithms for topological priority assignment and cycle detection.
QSet< T * > findFeedbackNodes(const QVector< T * > &elements, const QHash< T *, QVector< T * > > &successors)
Finds all nodes that participate in feedback loops (cycles).
Definition Priorities.h:29
void calculatePriorities(const QVector< T * > &elements, const QHash< T *, QVector< T * > > &successors, QHash< T *, int > &outPriorities)
Priority calculation for directed graphs.
Definition Priorities.h:232
Interface exposing the narrow slice of Scene that Simulation depends on.
Synchronous cycle-based simulation engine with event-driven clock support.
static void guardedSlot(const QObject *context, Body &&body) noexcept
Wraps a slot body in try/catch and reports any exception synchronously, inside the slot's own stack f...
static bool interactiveMode
Definition Application.h:81
Abstract base class for all graphical circuit elements in wiRedPanda.
Graphic element representing an Integrated Circuit (sub-circuit) box.
Definition IC.h:31
A port that receives a signal (the destination end of a wire).
Definition Port.h:234
void setStatus(const Status status) override
Definition Port.cpp:307
A port that drives a signal (the source end of a wire).
Definition Port.h:266
void setStatus(const Status status) override
Definition Port.cpp:361
GraphicElement * graphicElement()
Returns the graphic element that owns this port.
Definition Port.h:71
int index() const
Returns the port's visual/logical index within the element.
Definition Port.cpp:138
Status defaultValue() const
Returns the default status applied when the port is unconnected.
Definition Port.h:81
const QList< Connection * > & connections() const
Returns the list of wires attached to this port.
Definition Port.cpp:57
Narrow interface letting Simulation reach its host scene without naming the concrete Scene class.
void update()
Executes one simulation step (used by tests to advance the simulation manually).
static SortResult topologicalSort(const QVector< GraphicElement * > &elements, const QHash< GraphicElement *, QVector< GraphicElement * > > &successors)
Topologically sorts elements using the successor graph, detects feedback loops.
void setVisualThrottleEnabled(bool enabled)
Simulation(SimulationHost *host, QObject *parent=nullptr)
Constructs a Simulation bound to host.
static bool iterativeSettle(const QVector< GraphicElement * > &elements, int maxIterations=kMaxSettleIterations)
static QHash< QString, GraphicElement * > buildTxMap(const QVector< GraphicElement * > &elements)
Builds a label→element map for wireless Tx nodes. First Tx per label wins.
void setUserMuted(bool muted)
Sets whether the user has explicitly muted audio; persists across stop/start cycles.
void simulationWarning(const QString &message)
Emitted (at most once per initialize()) when a feedback circuit fails to converge.
void restart()
static void buildConnectionGraph(const QVector< GraphicElement * > &elements)
bool initialize()
Builds the simulation graph from the current scene elements.
static void connectWirelessElements(const QVector< GraphicElement * > &elements)
static QHash< GraphicElement *, QVector< GraphicElement * > > buildSuccessorGraph(const QVector< GraphicElement * > &elements, const QHash< QString, GraphicElement * > &txMap)
Builds a successor adjacency list from connection graph + wireless Tx→Rx edges.
static constexpr int kMaxSettleIterations
Definition Simulation.h:48
void start()
Starts the 1 ms simulation timer.
bool isRunning()
Returns true if the simulation timer is currently running.
bool isUserMuted() const
Returns true if the user has explicitly muted audio.
bool isInFeedbackLoop(const GraphicElement *element) const
Returns true if element is part of a combinational feedback loop.
void stop()
Stops the simulation timer.
Result of topological sort with feedback detection.
Definition Simulation.h:125
QSet< GraphicElement * > feedbackNodes
Elements in feedback loops.
Definition Simulation.h:128
QVector< GraphicElement * > sorted
Elements in priority order (highest first).
Definition Simulation.h:126
QHash< GraphicElement *, int > priorities
Priority per element.
Definition Simulation.h:127