wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
WaveformSimulator.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 <utility>
7
8#include <QScopeGuard>
9
10#include "App/Core/Common.h"
13#include "App/Scene/Scene.h"
17#include "App/Wiring/Port.h"
18
20 : m_externalScene(externalScene)
21 , m_simulation(simulation)
22{
23}
24
25QVector<Status> WaveformSimulator::captureInputs(const QVector<GraphicElementInput *> &inputs, const int inputPorts)
26{
27 QVector<Status> oldValues(inputPorts);
28 int oldIndex = 0;
29
30 for (auto *input : std::as_const(inputs)) {
31 for (int port = 0; port < input->outputSize(); ++port) {
32 // Snapshot the live port state before the simulation sweep overwrites it
33 oldValues[oldIndex] = input->outputPort(port)->status();
34 ++oldIndex;
35 }
36 }
37
38 return oldValues;
39} // LCOV_EXCL_LINE -- compiler-generated QVector cleanup for an exception path captureInputs() never takes
40
41void WaveformSimulator::restoreInputs(const QVector<GraphicElementInput *> &inputs, const QVector<Status> &saved)
42{
43 qCDebug(zero) << "Restoring old values to inputs, prior to simulation.";
44
45 // `saved` is indexed by PORT (one entry per output port of each input element),
46 // not by element. A separate port-level index is required to avoid reading the wrong
47 // saved value for multi-port inputs (e.g. InputRotary).
48 int portIndex = 0;
49 for (auto *input : std::as_const(inputs)) {
50 for (int port = 0; port < input->outputSize(); ++port) {
51 const Status savedStatus = saved.at(portIndex++);
52
53 // A captured Status is four-state, and setOn() only speaks two: Status::Unknown is
54 // -1 and Status::Error is 2, so `static_cast<bool>` would make BOTH true and restore
55 // an undefined port ON -- a sweep turning an undriven input into a driven-high one in
56 // the live circuit. Put a non-definite capture back on the port directly, which is
57 // exactly what captureInputs() read, and leave the element's own on/off state alone:
58 // it was never meaningfully on or off.
59 if (savedStatus != Status::Active && savedStatus != Status::Inactive) {
60 if (auto *outPort = input->outputPort(port)) {
61 outPort->setStatus(savedStatus);
62 }
63 continue;
64 }
65
66 const bool oldValue = (savedStatus == Status::Active);
67 if (input->outputSize() > 1) {
68 // Multi-port inputs (e.g. InputRotary) are one-hot: selecting any port
69 // deselects the others, so calling setOn() for every port in sequence would
70 // just leave the last one selected regardless of what was captured. Only the
71 // port that was actually active needs to be (re-)selected.
72 if (oldValue) {
73 input->setOn(oldValue, port);
74 }
75 } else {
76 input->setOn(oldValue);
77 }
78 }
79 }
80}
81
82void WaveformSimulator::sweep(const QVector<DolphinModelBuilder::Row> &rows, const int columns,
83 const std::function<bool(int row, int col)> &readInput,
84 const std::function<void(int row, int col, int value)> &writeOutput) const
85{
86 // Block the live simulation timer while we drive the circuit manually column by column
87 qCDebug(zero) << "Creating class to pause main window simulator while creating waveform.";
88 SimulationBlocker simulationBlocker(m_simulation);
89 // Disable the visual refresh throttle so phases 3–4 (port-status updates) run on every
90 // update() call. Without this, output port statuses are stale for most columns and the
91 // waveform shows incorrect values.
92 SimulationThrottleDisabler throttleDisabler(m_simulation);
93
94 // Snapshot the live circuit before the reset below, and put it back on the way out. The
95 // sweep resets everything so its own results are reproducible, but it is a read-only
96 // QUESTION about the circuit -- without this the canvas would be left holding whatever the
97 // last column produced, so a read-only-looking create_waveform would mutate the user's
98 // flip-flops. Symmetrical with the reset, and it covers the state resetSimState() clears:
99 // outputs plus each sequential element's edge-detection history, recursing through ICs.
100 QVector<Status> liveState;
101 for (auto *elm : m_externalScene->elements()) {
102 if (elm && elm->type() == GraphicElement::Type) {
103 elm->saveSimState(liveState);
104 }
105 }
106 auto restoreLiveState = qScopeGuard([this, &liveState] {
107 int cursor = 0;
108 for (auto *elm : m_externalScene->elements()) {
109 if (elm && elm->type() == GraphicElement::Type) {
110 elm->restoreSimState(liveState, cursor);
111 }
112 }
113 });
114
115 // Reset every element's sequential state (Q/~Q outputs, edge-detection variables) to
116 // power-on defaults before the sweep so results are reproducible regardless of any
117 // prior simulation run that may have left flip-flops in a different state.
118 qCDebug(zero) << "Resetting simulation state of all elements.";
119 for (auto *elm : m_externalScene->elements()) {
120 if (elm && elm->type() == GraphicElement::Type) {
121 elm->resetSimState();
122 }
123 }
124
125 // --- Step through each time column and compute circuit outputs ---
126 // A row's index IS its position in `rows`, so the input rows and the output rows are
127 // walked by the same index the model and snapshot() use; there is no separately-computed
128 // offset that could disagree with them.
129 for (int column = 0; column < columns; ++column) {
130 qCDebug(four) << "Itr: " << column << ", rows: " << rows.size();
131
132 for (int row = 0; row < rows.size(); ++row) {
133 const auto &descriptor = rows.at(row);
134 if (descriptor.kind != DolphinModelBuilder::RowKind::Input) {
135 continue;
136 }
137 // Each input applies the cell value its own way (e.g. a rotary ignores lows).
138 auto *input = qobject_cast<GraphicElementInput *>(descriptor.element);
139 if (input) {
140 input->setWaveformValue(readInput(row, column), descriptor.port);
141 }
142 }
143
144 qCDebug(four) << "Updating the values of the circuit logic based on current input values.";
145 m_simulation->update();
146
147 // Write computed output port states back into the model's output rows
148 qCDebug(four) << "Setting the computed output values to the waveform results.";
149 for (int row = 0; row < rows.size(); ++row) {
150 const auto &descriptor = rows.at(row);
151 if (descriptor.kind != DolphinModelBuilder::RowKind::Output || !descriptor.element) {
152 continue;
153 }
154 const int value = static_cast<int>(descriptor.element->inputPort(descriptor.port)->status());
155 writeOutput(row, column, value);
156 }
157 }
158}
Common logging utilities, the Pandaception error type, and helper macros.
#define qCDebug(category)
Definition Common.h:29
Enums::Status Status
Definition Enums.h:106
Abstract base class for user-controllable input elements.
Abstract base class for all graphical circuit elements.
Port classes: Port (base), InputPort, and OutputPort.
Main circuit editing scene with undo/redo and user interaction.
RAII guard that temporarily stops the simulation while in scope.
RAII guard that disables the visual refresh throttle while in scope.
Synchronous cycle-based simulation engine with event-driven clock support.
Drives the wiRedPanda circuit simulation column-by-column for the waveform table.
Main circuit editing scene.
Definition Scene.h:56
RAII guard that stops the simulation on construction and restarts it on destruction.
RAII guard that disables the visual refresh throttle for the duration of its scope.
Manages the digital circuit simulation loop.
Definition Simulation.h:39
WaveformSimulator(Scene *externalScene, Simulation *simulation)
Constructs the driver for externalScene and its simulation.
static QVector< Status > captureInputs(const QVector< GraphicElementInput * > &inputs, int inputPorts)
Snapshots the live output-port state of every input element.
static void restoreInputs(const QVector< GraphicElementInput * > &inputs, const QVector< Status > &saved)
Restores input-port states previously captured by captureInputs().
void sweep(const QVector< DolphinModelBuilder::Row > &rows, int columns, const std::function< bool(int row, int col)> &readInput, const std::function< void(int row, int col, int value)> &writeOutput) const
Runs the simulation across columns time steps.