-
Notifications
You must be signed in to change notification settings - Fork 25
Element System
This is the most important subsystem of the project. All circuit components (logic gates, flip-flops, displays, etc.) are elements. Their code lives in App/Element/.
QGraphicsObject
└── GraphicElement (abstract base — all circuit components)
├── GraphicElementInput (abstract — user-interactive inputs)
│ ├── InputSwitch (toggle on/off)
│ ├── InputButton (momentary press)
│ ├── InputRotary (multi-position rotary switch)
│ └── Clock (frequency-driven square wave)
├── InputVcc (constant HIGH — inherits GraphicElement directly)
├── InputGnd (constant LOW — inherits GraphicElement directly)
├── And, Or, Not, ... (combinational logic gates)
├── DFlipFlop, SRLatch... (sequential logic)
├── Led, Buzzer, Display (output elements)
├── Mux, Demux (multiplexing)
├── IC (sub-circuit / integrated circuit)
└── Node, Line, Text (structural/annotation)
App/Element/GraphicElement.h is the abstract base class for all graphic elements in the circuit. It inherits from:
-
QGraphicsObject: visual representation in the Qt scene -
ItemWithId: stable numeric identity (for serialization and undo/redo)
Responsibilities:
- Port management (input and output)
- Rendering with pixmaps/skins
- Serialization to QDataStream (save/load)
- Grid-snapping (alignment to grid)
- Label display and keyboard triggers
- Selection highlighting
Key methods that subclasses implement:
// Simulation logic — REQUIRED for functional elements
virtual void updateLogic() = 0;
// Custom serialization (optional)
virtual void save(QDataStream &stream) const;
virtual void load(QDataStream &stream, SerializationContext &context);App/Element/ElementFactory.h is a singleton that creates elements from their ElementType:
// Create a new AND element
GraphicElement *andGate = ElementFactory::buildElement(ElementType::And);
// Check if a type is registered
bool exists = ElementFactory::hasCreator(ElementType::And);
// Type <-> string conversion
ElementType type = ElementFactory::textToType("And");
QString name = ElementFactory::typeToText(ElementType::And);The factory does not manage IDs — that is the Scene's responsibility.
wiRedPanda uses a self-registering template pattern. Each element specializes ElementInfo<T>, which:
- Defines compile-time
ElementConstraints(input/output count, group, features). - Provides a
metadata()function returning display info (icon path, translated name). - Contains a
static inline const bool registeredlambda that runs at program startup to register the element with bothElementMetadataRegistryandElementFactory.
This means: no manual factory registration calls. Just defining the ElementInfo<T> specialization in the .cpp file is enough to make the element available everywhere — in the palette, in serialization, and in tests.
The ElementConstraints are validated at compile time by the constexpr validate() function. A misconfigured element will not compile.
ElementConstraints defines the restrictions for an element:
| Field | Description |
|---|---|
type |
Element type (ElementType) |
group |
Palette group (ElementGroup) |
minInputSize |
Minimum number of input ports |
maxInputSize |
Maximum number of input ports |
minOutputSize |
Minimum number of output ports |
maxOutputSize |
Maximum number of output ports |
canChangeAppearance |
Allows the user to assign a custom SVG appearance |
hasColors |
Has color option (e.g., LED) |
hasAudio |
Produces sound (e.g., Buzzer) |
hasAudioBox |
Has audio box capability (e.g., AudioBox) |
hasTrigger |
Can be triggered via keyboard |
hasFrequency |
Configurable frequency (e.g., Clock) |
hasDelay |
Has configurable delay |
hasLabel |
Displays a text label |
hasTruthTable |
Based on a truth table |
hasVolume |
Has volume control (e.g., Buzzer, AudioBox) |
rotatable |
Can be rotated |
ElementMetadata extends this with visual information: pixmap paths, translated names, and defaultAppearances/alternativeAppearances lists.
The 31 concrete elements live in App/Element/GraphicElements/:
| Element | File | Inputs | Description |
|---|---|---|---|
| AND | And.cpp |
2-8 | Output 1 if all inputs are 1 |
| OR | Or.cpp |
2-8 | Output 1 if any input is 1 |
| NOT | Not.cpp |
1 | Inverts the signal |
| NAND | Nand.cpp |
2-8 | Inverted AND |
| NOR | Nor.cpp |
2-8 | Inverted OR |
| XOR | Xor.cpp |
2-8 | Exclusive OR |
| XNOR | Xnor.cpp |
2-8 | Inverted exclusive OR |
| Element | File | Description |
|---|---|---|
| D Flip-Flop | DFlipFlop.cpp |
Stores data on clock edge |
| JK Flip-Flop | JKFlipFlop.cpp |
Versatile flip-flop with J, K, clk |
| SR Flip-Flop | SRFlipFlop.cpp |
Set-Reset with clock edge |
| T Flip-Flop | TFlipFlop.cpp |
Toggle on clock edge |
| D Latch | DLatch.cpp |
Level-sensitive latch |
| SR Latch | SRLatch.cpp |
Level-sensitive set-reset |
| Element | File | Description |
|---|---|---|
| InputButton | InputButton.cpp |
Momentary button (active while pressed) |
| InputSwitch | InputSwitch.cpp |
Toggle switch (on/off) |
| InputRotary | InputRotary.cpp |
Rotary switch with N positions |
| InputVCC | InputVCC.cpp |
Constant HIGH source (1) |
| InputGND | InputGND.cpp |
Constant LOW source (0) |
| Element | File | Description |
|---|---|---|
| Led | Led.cpp |
LED indicator (with color option) |
| Display7 | Display7.cpp |
7-segment display |
| Display14 | Display14.cpp |
14-segment display (alphanumeric) |
| Display16 | Display16.cpp |
16-segment display |
| Buzzer | Buzzer.cpp |
Audio buzzer with configurable freq |
| AudioBox | AudioBox.cpp |
Audio player |
| Element | File | Description |
|---|---|---|
| Mux | Mux.cpp |
Multiplexer: selects 1 of N inputs |
| Demux | Demux.cpp |
Demultiplexer: routes input to 1 of N |
| Element | File | Description |
|---|---|---|
| Clock | Clock.cpp |
Clock generator with adjustable freq |
| Node | Node.cpp |
Wire junction / wireless node (Tx/Rx) |
| Line | Line.cpp |
Decorative line (no logic function) |
| Text | Text.cpp |
Text annotation |
| TruthTable | TruthTable.cpp |
Element defined by a truth table |
Here is the complete AND gate, the simplest element to understand:
App/Element/GraphicElements/And.h:
class And : public GraphicElement {
Q_OBJECT
public:
explicit And(QGraphicsItem *parent = nullptr);
void updateLogic() override;
};App/Element/GraphicElements/And.cpp:
#include "And.h"
// 1. Registration: define constraints and metadata
template<>
struct ElementInfo<And> {
static constexpr ElementConstraints constraints{
.type = ElementType::And,
.group = ElementGroup::Gate,
.minInputSize = 2, .maxInputSize = 8,
.minOutputSize = 1, .maxOutputSize = 1,
.canChangeAppearance = true,
};
static_assert(validate(constraints));
static ElementMetadata metadata() {
auto meta = metadataFromConstraints(constraints);
meta.pixmapPath = [] { return QStringLiteral(":/Components/Logic/and.svg"); };
meta.titleText = QT_TRANSLATE_NOOP("And", "AND");
meta.translatedName = QT_TRANSLATE_NOOP("And", "And");
meta.trContext = "And";
meta.defaultAppearances = {":/Components/Logic/and.svg"};
return meta;
}
static inline const bool registered = [] {
ElementMetadataRegistry::registerMetadata(metadata());
ElementFactory::registerCreator(constraints.type, [] { return new And; });
return true;
}();
};
// 2. Constructor: just calls parent with the element type
And::And(QGraphicsItem *parent) : GraphicElement(ElementType::And, parent) {}
// 3. Logic: the one method that matters
void And::updateLogic() {
if (!simUpdateInputsAllowUnknown()) return;
setOutputValue(StatusOps::statusAndAll(simInputs()));
}What happens in updateLogic():
-
simUpdateInputsAllowUnknown()reads the current value from each connected predecessor's output port (or uses the port's default if unconnected). Returnsfalseif a required input has no value — the element does nothing this cycle. -
simInputs()returns aQVector<Status>snapshot of all input values. -
StatusOps::statusAndAll()ANDs all inputs together using the four-state logic rules. -
setOutputValue()writes the result to output port 0. If the value changed, the element is flagged for downstream propagation.
All other combinational gates (Or, Not, Xor, Nand, Nor, Xnor) follow this exact same three-part pattern. The only difference is the StatusOps function called.
Sequential elements like flip-flops store internal state and respond to clock edges:
D Flip-Flop (DFlipFlop.cpp, simplified):
void DFlipFlop::updateLogic() {
if (!simUpdateInputs()) return; // Strict: needs ALL inputs valid
Status q0 = simOutputs().at(0); // Current Q
Status q1 = simOutputs().at(1); // Current ~Q
const Status D = simInputs().at(0); // Data input
const Status clk = simInputs().at(1); // Clock input
const Status prst = simInputs().at(2); // Async preset (active-low)
const Status clr = simInputs().at(3); // Async clear (active-low)
// Rising edge detection
if (clk == Status::Active && m_simLastClk == Status::Inactive) {
q0 = m_simLastValue; // Latch D on rising edge
q1 = (m_simLastValue == Status::Active) ? Status::Inactive : Status::Active;
}
// Asynchronous overrides (active-low, jointly evaluated)
if (prst == Status::Inactive || clr == Status::Inactive) {
q0 = (prst == Status::Inactive) ? Status::Active : Status::Inactive;
q1 = (clr == Status::Inactive) ? Status::Active : Status::Inactive;
}
m_simLastClk = clk;
m_simLastValue = D;
setOutputValue(0, q0);
setOutputValue(1, q1);
}Key differences from combinational gates:
- Uses
simUpdateInputs()(strict) instead ofsimUpdateInputsAllowUnknown(). - Stores
m_simLastClkandm_simLastValueacross cycles for edge detection. - Has custom port layout via
updatePortsProperties()(Clock pin at bottom-left, Preset at top, etc.).
Input elements like InputSwitch are driven by user interaction, not by upstream logic:
// No updateLogic() — state is set by mouse events
void InputSwitch::mousePressEvent(QGraphicsSceneMouseEvent *event) {
if (!m_locked && event->button() == Qt::LeftButton) {
setOn(!m_isOn); // Toggle the switch
event->accept();
}
QGraphicsItem::mousePressEvent(event);
}
// setOn(bool, int) is inherited from GraphicElementInput:
void GraphicElementInput::setOn(const bool value, const int port) {
m_isOn = value;
setPixmap(static_cast<int>(m_isOn)); // Visual update
outputPort()->setStatus(static_cast<Status>(m_isOn)); // Electrical update
}The Clock element is special: it is driven by wall-clock time via updateClock(), comparing elapsed time against a configurable frequency interval.
The IC system allows reusing circuits as sub-components:
-
IC.cpp/h: the element representing an IC in the scene (a "black box" with ports) -
ICRegistry.cpp/h: central registry that monitors.pandafiles and auto-updates ICs when the file changes
ICs support arbitrary hierarchy — an IC can contain other ICs inside it. The tests include up to 9 levels of complexity, culminating in complete CPU implementations.
- Element Creation Guide — Step-by-step walkthrough for adding new elements
- Architecture Overview — Where the element layer fits in the overall architecture
- Simulation Engine Internals — How elements are evaluated during simulation
- Testing and CI — How to write tests for elements
Made with ❤️ by the wiRedPanda Community
Licensed under GPL-3.0 | © 2026 GIBIS-UNIFESP