wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
Clock.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#include <chrono>
8#include <cmath>
9
14#include "App/IO/VersionInfo.h"
15
16using namespace std::chrono_literals;
17
18template<>
21 .type = ElementType::Clock,
22 .group = ElementGroup::Input,
23 .minOutputSize = 1,
24 .maxOutputSize = 1,
25 .canChangeAppearance = true,
26 .hasFrequency = true,
27 .hasDelay = true,
28 .hasLabel = true,
29 .rotatesGraphic = false,
30 };
31 static_assert(validate(constraints));
32
34 {
36 meta.pixmapPath = []{ return QStringLiteral(":/Components/Input/clock1.svg"); };
37 meta.titleText = QT_TRANSLATE_NOOP("Clock", "CLOCK SIGNAL");
38 meta.translatedName = QT_TRANSLATE_NOOP("Clock", "Clock");
39 meta.trContext = "Clock";
40 meta.defaultAppearances = QStringList({
41 ":/Components/Input/clock0.svg",
42 ":/Components/Input/clock1.svg",
43 });
44 return meta;
45 }
46
47 static inline const bool registered = []() {
49 ElementFactory::registerCreator(constraints.type, [] { return new Clock(); });
50 return true;
51 }();
52};
53
54Clock::Clock(QGraphicsItem *parent)
56{
57 // Frequency, interval, delay, and locked state are all covered by member
58 // initializers; only the output state needs behavior, not initialization:
59 Clock::setOff(); // start LOW; resetClock() will start HIGH when simulation begins
60}
61
62void Clock::updateClock(std::chrono::steady_clock::time_point globalTime)
63{
64 if (m_locked) {
65 return;
66 }
67
68 const auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(globalTime - m_startTime);
69
70 // A gap this large means the wall clock jumped far ahead without an intervening
71 // Simulation::stop()/start() cycle (which already resyncs via resetClock() — see its call
72 // site) — e.g. the OS suspended the process while the simulation's QTimer stayed "active".
73 // Left to the single-step path below, replaying the backlog would toggle once per
74 // subsequent 1 ms tick until caught up: for a long gap, a burst of thousands of rapid,
75 // unrealistic toggles feeding into any connected sequential logic. Resync immediately
76 // instead, on this very first tick after the gap, so the burst never starts. The threshold
77 // is a fixed wall-clock duration, not a multiple of m_interval — scaling it to the
78 // half-period would fire this path on entirely ordinary jitter for high-frequency clocks
79 // (e.g. a 100 Hz clock's 5 ms interval means even a benign 20+ ms hiccup would look like a
80 // "large" multiple), which is not the gap this guards against.
81 constexpr auto kMaxCatchUpGap = std::chrono::seconds(5);
82 if (elapsed > kMaxCatchUpGap) {
83 resetClock(globalTime);
84 return;
85 }
86
87 // m_interval is the half-period (time per HIGH or LOW phase).
88 // Rather than resetting to globalTime we advance by exactly one interval so
89 // that accumulated drift doesn't skew the clock frequency over time.
90 if (elapsed > m_interval) {
91 m_startTime += m_interval;
92 setOn(!m_isOn);
93 }
94}
95
96bool Clock::isOn(const int port) const
97{
98 Q_UNUSED(port)
99 return m_isOn;
100}
101
103{
104 Clock::setOn(false);
105}
106
108{
109 Clock::setOn(true);
110}
111
112void Clock::save(QDataStream &stream, SerializationOptions options) const
113{
114 GraphicElement::save(stream, options);
115
116 const bool slim = (options.purpose == SerializationPurpose::PortableFile);
117
118 QMap<QString, QVariant> map;
119 // PortableFile streams omit defaults; fresh-loaded elements start there
120 // anyway. Snapshots write unconditionally (reloaded into live elements).
121 if (!slim || frequency() != kDefaultFrequency) {
122 map.insert("frequency", frequency());
123 }
124 if (!slim || delay() != 0.0) {
125 map.insert("delay", delay());
126 }
127 if (!slim || m_locked) {
128 map.insert("locked", m_locked);
129 }
130
131 stream << map;
132}
133
134void Clock::load(QDataStream &stream, SerializationContext &context)
135{
136 GraphicElement::load(stream, context);
137
138 if (!VersionInfo::hasClock(context.version)) {
139 // Clock serialization was introduced in v1.1; nothing to read in earlier files
140 return;
141 }
142
143 if (!VersionInfo::hasQMapFormat(context.version)) {
144 // v1.1–4.0 stored frequency as a bare float; locked state added in v3.1
145 float freq; stream >> freq;
146 setFrequency(static_cast<double>(freq));
147
148 if (VersionInfo::hasLockState(context.version)) {
149 stream >> m_locked;
150 }
151 }
152
153 if (VersionInfo::hasQMapFormat(context.version)) {
154 // v4.1+ uses a key-value map so new properties can be added without breaking old files
155 QMap<QString, QVariant> map = Serialization::readBoundedMetadata(stream);
156
157 if (map.contains("frequency")) {
158 setFrequency(map.value("frequency").toDouble());
159 }
160
161 if (map.contains("delay")) {
162 bool ok = false;
163 double delayValue = map.value("delay").toDouble(&ok);
164 if (!ok) {
165 delayValue = 0.0;
166 }
167
168 if (!VersionInfo::hasDelayFix(context.version)) {
169 // Discard old delay data from versions < 4.3
170 // The old implementation was incorrect and incompatible with the new period-fraction format
171 } else {
172 setDelay(delayValue);
173 }
174 }
175
176 if (map.contains("locked")) {
177 m_locked = map.value("locked").toBool();
178 }
179 }
180}
181
182double Clock::frequency() const
183{
184 return m_frequency;
185}
186
187void Clock::setFrequency(const double freq)
188{
189 if (!std::isfinite(freq) || qFuzzyIsNull(freq)) {
190 return;
191 }
192
193 const std::chrono::microseconds auxInterval = halfPeriod(freq);
194
195 // Guard against frequencies so high that the half-period rounds to zero
196 if (auxInterval.count() <= 0) {
197 return;
198 }
199
200 m_interval = auxInterval;
201 m_frequency = freq;
202}
203
204double Clock::delay() const
205{
206 return m_delay;
207}
208
209void Clock::setDelay(const double delay)
210{
211 if (!std::isfinite(delay)) {
212 return;
213 }
214 // m_delay is documented as a fraction of the period in [-1, 1]. Out-of-range
215 // finite values would multiply with fullPeriod.count() in resetClock() to a
216 // magnitude beyond microseconds::rep, which is UB on the cast (UBSan-trapped).
217 m_delay = std::clamp(delay, -1.0, 1.0);
218}
219
220void Clock::resetClock(std::chrono::steady_clock::time_point globalTime)
221{
222 // Start clocks in the HIGH state; the first transition happens after one interval
223 setOn();
224 // m_delay is a fraction of the period (-1 to 1).
225 // Negative delays advance the clock (trigger earlier), positive delays delay it.
226 // Shifting m_startTime backward by the delay fraction effectively phase-shifts the waveform.
227 // Full period is 2 * m_interval (since m_interval is the half-period).
228 const auto fullPeriod = 2 * m_interval;
229 const auto delayMicroseconds = static_cast<std::chrono::microseconds::rep>(-m_delay * static_cast<double>(fullPeriod.count()));
230 m_startTime = globalTime;
231 m_startTime -= std::chrono::microseconds(delayMicroseconds);
232}
233
235{
236 return QString::number(frequency()) + " Hz";
237}
238
239QList<std::pair<int, QString>> Clock::appearanceStates() const
240{
241 return {{0, tr("Low")}, {1, tr("High")}};
242}
Graphic element for the real-time clock input.
Singleton factory for all circuit element types.
Self-registering element trait template and compile-time constraint validation.
ElementMetadata metadataFromConstraints(const ElementConstraints &c)
Converts ElementConstraints to an ElementMetadata with all constraint-derived fields set.
Definition ElementInfo.h:80
constexpr bool validate(const ElementConstraints &c)
Validates element constraints at compile time.
Definition ElementInfo.h:48
Enums::ElementType ElementType
Definition Enums.h:107
Deserialization/serialization context structs passed through load()/save() call chains.
Circuit and waveform file serialization/deserialization utilities.
Named version predicates for file-format compatibility checks.
Event-driven real-time clock input element.
Definition Clock.h:23
static constexpr double kDefaultFrequency
Definition Clock.h:30
void save(QDataStream &stream, SerializationOptions options) const override
Definition Clock.cpp:112
bool isOn(const int port=0) const override
Returns true if the clock output is currently logic-high.
Definition Clock.cpp:96
void load(QDataStream &stream, SerializationContext &context) override
Definition Clock.cpp:134
double delay() const override
Definition Clock.cpp:204
void resetClock(std::chrono::steady_clock::time_point globalTime)
Resets the clock phase reference to globalTime.
Definition Clock.cpp:220
void setOn() override
Drives all output ports logic-high.
Definition Clock.cpp:107
void setOff() override
Drives all output ports logic-low.
Definition Clock.cpp:102
void setFrequency(const double freq) override
Sets the clock output frequency to freq Hz.
Definition Clock.cpp:187
QList< std::pair< int, QString > > appearanceStates() const override
Definition Clock.cpp:239
void updateClock(std::chrono::steady_clock::time_point globalTime)
Advances the clock state based on elapsed time since globalTime.
Definition Clock.cpp:62
double frequency() const override
Returns the clock frequency in Hz.
Definition Clock.cpp:182
QString genericProperties() override
Returns a summary string of the clock's current frequency and delay settings.
Definition Clock.cpp:234
Clock(QGraphicsItem *parent=nullptr)
Constructs the element with optional parent.
Definition Clock.cpp:54
static constexpr std::chrono::microseconds halfPeriod(const double freq)
Definition Clock.h:37
void setDelay(const double delay) override
Definition Clock.cpp:209
static void registerCreator(ElementType type, std::function< GraphicElement *()> creator)
Registers a creator lambda for type, used by buildElement().
static void registerMetadata(const ElementMetadata &meta)
Registers meta in the global map (called once per element type at startup).
GraphicElementInput(ElementType type, QGraphicsItem *parent=nullptr)
virtual void load(QDataStream &stream, SerializationContext &context)
Loads the graphic element through a binary data stream.
virtual void save(QDataStream &stream, SerializationOptions options) const
static QMap< QString, QVariant > readBoundedMetadata(QDataStream &stream)
Reads the file-level metadata QMap<QString,QVariant> from stream without calling QList::reserve() wit...
bool hasLockState(const QVersionNumber &v)
V3.1: Lock state for input elements; color for some display/button types.
Definition VersionInfo.h:51
bool hasClock(const QVersionNumber &v)
V1.1: Clock element added.
Definition VersionInfo.h:21
bool hasQMapFormat(const QVersionNumber &v)
V4.1: Format changed from flat binary to keyed QMap; port serial IDs; rotation fix.
Definition VersionInfo.h:60
bool hasDelayFix(const QVersionNumber &v)
V4.3: Clock delay format corrected.
Definition VersionInfo.h:66
Compile-time-validatable subset of ElementMetadata.
Definition ElementInfo.h:21
static const bool registered
Definition Clock.cpp:47
static ElementMetadata metadata()
Definition Clock.cpp:33
static constexpr ElementConstraints constraints
Definition Clock.cpp:20
Self-registering element information trait.
Compile-time-registered properties for one element type.
Bundles all per-deserialization state so that load() overrides receive it through one explicit parame...
QVersionNumber version
File-format version read from the stream header.
Options passed to GraphicElement::save() (and friends); the save-side counterpart of SerializationCon...
SerializationPurpose purpose
What this serialization is for; see SerializationPurpose. Mandatory, no default.