wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
ArduinoCodeGen.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 <functional>
8
9#include <QRegularExpression>
10#include <QSet>
11
13#include "App/Core/Common.h"
19#include "App/Element/IC.h"
20#include "App/Scene/Scene.h"
23#include "App/Wiring/Port.h"
24
25ArduinoCodeGen::ArduinoCodeGen(const QString &fileName, const QVector<GraphicElement *> &elements)
26 : m_file(fileName)
27 , m_elements(elements)
28{
29 if (!m_file.open(QIODevice::WriteOnly | QIODevice::Text)) {
30 throw PANDACEPTION("Could not open file for writing: %1", fileName);
31 }
32 m_stream.setDevice(&m_file);
33}
34
35QString ArduinoCodeGen::highLow(const Status val)
36{
37 return (val == Status::Active) ? "HIGH" : "LOW";
38}
39
40QString ArduinoCodeGen::removeForbiddenChars(const QString &input)
41{
42 return CodeGenUtils::removeForbiddenChars(input, true);
43}
44
45bool ArduinoCodeGen::isArduinoReserved(const QString &name)
46{
47 static const QSet<QString> reserved = {
48 // C++ keywords
49 "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit",
50 "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch",
51 "char", "char8_t", "char16_t", "char32_t", "class", "compl", "concept",
52 "const", "consteval", "constexpr", "constinit", "const_cast", "continue",
53 "co_await", "co_return", "co_yield", "decltype", "default", "delete", "do",
54 "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern",
55 "false", "float", "for", "friend", "goto", "if", "inline", "int", "long",
56 "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "or",
57 "or_eq", "private", "protected", "public", "reflexpr", "register",
58 "reinterpret_cast", "requires", "return", "short", "signed", "sizeof",
59 "static", "static_assert", "static_cast", "struct", "switch", "synchronized",
60 "template", "this", "thread_local", "throw", "true", "try", "typedef",
61 "typeid", "typename", "union", "unsigned", "using", "virtual", "void",
62 "volatile", "wchar_t", "while", "xor", "xor_eq",
63 // Arduino built-ins and functions (lowercase to match post-removeForbiddenChars names)
64 "high", "low", "input", "output", "input_pullup", "input_pulldown",
65 "led_builtin", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
66 "pinmode", "digitalwrite", "digitalread", "analogread", "analogwrite",
67 "analogreadresolution", "analogwriteresolution", "serial",
68 "tone", "notone", "delay", "millis", "micros", "setup", "loop"
69 };
70 return reserved.contains(name);
71}
72
73QString ArduinoCodeGen::otherPortName(Port *port)
74{
75 QSet<Port *> visited;
76 return otherPortNameImpl(port, visited);
77}
78
79QString ArduinoCodeGen::otherPortNameImpl(Port *port, QSet<Port *> &visited)
80{
81 if (!port) {
82 return "LOW";
83 }
84
85 // Cycle detection: if we've already visited this port, don't continue
86 if (visited.contains(port)) {
87 const QString mapped = m_varMap.value(port);
88 return mapped.isEmpty() ? "LOW" : mapped;
89 }
90
91 if (port->connections().isEmpty()) {
92 // Check m_varMap first — IC input boundary nodes have no connections but a mapped variable
93 const QString mapped = m_varMap.value(port);
94 if (!mapped.isEmpty()) {
95 return mapped;
96 }
97 // Wireless Rx: resolve via the Tx node's input (what drives the transmitter)
98 auto *elm = port->graphicElement();
99 if (elm && elm->wirelessMode() == WirelessMode::Rx && !elm->label().isEmpty()) {
100 auto *txInputPort = m_txInputPorts.value(elm->label(), nullptr);
101 if (txInputPort) {
102 return otherPortNameImpl(txInputPort, visited);
103 }
104 }
105 return highLow(port->defaultValue());
106 }
107
108 auto *connection = port->connections().constFirst();
109 if (!connection) {
110 return highLow(port->defaultValue());
111 }
112
113 auto *otherPort = connection->otherPort(port);
114 if (!otherPort) {
115 return highLow(port->defaultValue());
116 }
117
118 // Cycle detection: if we've already visited the connected port, don't continue
119 if (visited.contains(otherPort)) {
120 const QString mapped = m_varMap.value(otherPort);
121 return mapped.isEmpty() ? "LOW" : mapped;
122 }
123
124 // Mark both ports as visited
125 visited.insert(port);
126 visited.insert(otherPort);
127
128 const QString result = m_varMap.value(otherPort);
129 if (result.isEmpty()) {
130 return highLow(otherPort->defaultValue());
131 }
132 return result;
133}
134
136{
137 try {
138 m_txInputPorts = Scene::wirelessTxInputPorts(m_elements);
139 m_hasSequential = hasNativeMemory(m_elements);
140
141 int requiredInputPins = 0;
142 int requiredOutputPins = 0;
143 for (auto *elm : m_elements) {
144 const auto type = elm->elementType();
145 if (type == ElementType::InputButton || type == ElementType::InputSwitch) {
146 requiredInputPins++;
147 } else if (type == ElementType::InputRotary) {
148 requiredInputPins += elm->outputSize();
149 }
150 }
151 for (auto *elm : m_elements) {
152 if (elm->elementGroup() == ElementGroup::Output) {
153 requiredOutputPins += static_cast<int>(elm->inputs().size());
154 }
155 }
156 const int totalRequiredPins = requiredInputPins + requiredOutputPins;
157 m_totalRequiredPins = totalRequiredPins;
158 m_selectedBoard = selectBoard(totalRequiredPins);
159 m_availablePins = m_selectedBoard.availablePins;
160
161 m_stream << "// ==================================================================== //" << Qt::endl;
162 m_stream << "// ======= This code was generated automatically by wiRedPanda ======== //" << Qt::endl;
163 m_stream << "// ==================================================================== //" << Qt::endl;
164 m_stream << "//" << Qt::endl;
165 m_stream << QString("// Target Board: %1").arg(m_selectedBoard.name) << Qt::endl;
166 m_stream << QString("// Pin Usage: %1/%2 pins").arg(totalRequiredPins).arg(m_selectedBoard.maxPins()) << Qt::endl;
167 m_stream << "//" << Qt::endl;
168 m_stream << Qt::endl
169 << Qt::endl;
170 m_stream << "#include <elapsedMillis.h>" << Qt::endl;
171 declareInputs();
172 declareOutputs();
173 declareAuxVariables();
174 if (m_hasSequential) {
175 // Phase flag for the non-blocking tick driver: flip-flops sample only
176 // while true; the post-commit re-settle runs with it false.
177 m_stream << "bool g_sample = true;" << Qt::endl << Qt::endl;
178 }
179 setup();
180 emitComputeLogicFunction();
181 if (m_hasSequential) {
182 emitCommitFlipFlops();
183 }
184 loop();
185 } catch (...) {
186 m_file.close();
187 m_file.remove();
188 throw;
189 }
190}
191
192void ArduinoCodeGen::declareInputs()
193{
194 int counter = 1;
195 m_stream << "/* ========= Inputs ========== */" << Qt::endl;
196
197 for (auto *elm : m_elements) {
198 const auto type = elm->elementType();
199
200 if ((type == ElementType::InputButton) || (type == ElementType::InputSwitch)) {
201 // Check if we have available pins before assigning
202 if (m_availablePins.isEmpty()) {
203 throwPinOverflow();
204 }
205
206 QString varName = elm->objectName() + QString::number(counter);
207 const QString label = elm->label();
208
209 if (!label.isEmpty()) {
210 varName += "_" + label;
211 }
212
213 varName = removeForbiddenChars(varName);
214 if (isArduinoReserved(varName)) {
215 varName.append('_');
216 }
217 m_stream << QString("const int %1 = %2;").arg(varName, m_availablePins.constFirst()) << Qt::endl;
218 auto *outPort = elm->outputPort(0);
219 if (outPort) {
220 m_inputMap.append(MappedPin(elm, m_availablePins.constFirst(), varName, outPort, 0));
221 m_varMap[outPort] = varName + QString("_val");
222 }
223 m_availablePins.removeFirst();
224 ++counter;
225 } else if (type == ElementType::InputRotary) {
226 // Each rotary position maps to one digital input pin; exactly one is HIGH at a time.
227 const QString label = elm->label();
228 for (int i = 0; i < elm->outputSize(); ++i) {
229 if (m_availablePins.isEmpty()) {
230 throwPinOverflow();
231 }
232 QString varName = elm->objectName() + QString::number(counter) + "_pos" + QString::number(i);
233 if (!label.isEmpty()) {
234 varName = elm->objectName() + QString::number(counter) + "_" + label + "_pos" + QString::number(i);
235 }
236 varName = removeForbiddenChars(varName);
237 if (isArduinoReserved(varName)) {
238 varName.append('_');
239 }
240 m_stream << QString("const int %1 = %2;").arg(varName, m_availablePins.constFirst()) << Qt::endl;
241 auto *outPort = elm->outputPort(i);
242 if (outPort) {
243 m_inputMap.append(MappedPin(elm, m_availablePins.constFirst(), varName, outPort, i));
244 m_varMap[outPort] = varName + "_val";
245 }
246 m_availablePins.removeFirst();
247 }
248 ++counter;
249 }
250 }
251
252 m_stream << Qt::endl;
253}
254
255void ArduinoCodeGen::declareOutputs()
256{
257 int counter = 1;
258 m_stream << "/* ========= Outputs ========== */" << Qt::endl;
259 for (auto *elm : m_elements) {
260 if (elm->elementGroup() == ElementGroup::Output) {
261 QString label = elm->label();
262 for (int i = 0; i < elm->inputs().size(); ++i) {
263 // Check if we have available pins before assigning
264 if (m_availablePins.isEmpty()) {
265 throwPinOverflow();
266 }
267
268 QString varName = elm->objectName() + QString::number(counter);
269 if (!label.isEmpty()) {
270 varName = QString("%1_%2").arg(varName, label);
271 }
272 Port *port = elm->inputPort(i);
273 if (port && !port->name().isEmpty()) {
274 varName = QString("%1_%2").arg(varName, port->name());
275 }
276 varName = removeForbiddenChars(varName);
277 if (isArduinoReserved(varName)) {
278 varName.append('_');
279 }
280 m_stream << QString("const int %1 = %2;").arg(varName, m_availablePins.constFirst()) << Qt::endl;
281 m_outputMap.append(MappedPin(elm, m_availablePins.constFirst(), varName, port, i));
282 m_availablePins.removeFirst();
283 }
284 ++counter;
285 }
286 }
287 m_stream << Qt::endl;
288}
289
290void ArduinoCodeGen::declareAuxVariablesRec(const QVector<GraphicElement *> &elements, const bool isBox, const QString &icPrefix)
291{
292 int counter = 0;
293
294 for (auto *elm : elements) {
295 if (elm->elementType() == ElementType::IC) {
296 auto *ic = qobject_cast<IC *>(elm);
297 if (!ic) continue;
298
299 m_stream << "// IC: " << CodeGenUtils::sanitizeComment(ic->label()) << Qt::endl;
300
301 // Include the full ancestor path (icPrefix) so names stay globally
302 // unique across repeated/nested sub-IC instances (register files, RAM,
303 // CPUs). Without it, structurally-identical sub-trees collide.
304 QString baseVarName = QString("aux_%1%2_%3").arg(icPrefix, removeForbiddenChars(ic->label()), QString::number(counter++));
305
306 for (int i = 0; i < ic->outputSize(); ++i) {
307 Port *externalPort = ic->outputPort(i);
308 if (externalPort) {
309 QString portVarName = baseVarName;
310 const QString portName = externalPort->name();
311 if (!portName.isEmpty()) {
312 portVarName += "_" + removeForbiddenChars(portName);
313 if (i > 0) {
314 portVarName += "_" + QString::number(i);
315 }
316 } else {
317 portVarName += "_out" + QString::number(i);
318 }
319 m_varMap[externalPort] = portVarName;
320 if (!m_declaredVariables.contains(portVarName)) {
321 m_stream << "bool " << portVarName << " = " << highLow(externalPort->defaultValue()) << ";" << Qt::endl;
322 m_declaredVariables.append(portVarName);
323 }
324 }
325 }
326
327 if (!ic->internalElements().isEmpty()) {
328 const QString nestedPrefix = QString("%1%2_%3_").arg(icPrefix, removeForbiddenChars(ic->label()), QString::number(counter - 1));
329 declareAuxVariablesRec(ic->internalElements(), true, nestedPrefix);
330
331 for (int i = 0; i < ic->internalInputs().size(); ++i) {
332 Port *internalPort = ic->internalInputs().at(i);
333 if (m_varMap.value(internalPort).isEmpty()) {
334 const QString portVarName = QString("aux_ic_input_%1%2_%3_%4").arg(icPrefix, removeForbiddenChars(ic->label()), QString::number(counter - 1), QString::number(i));
335 m_varMap[internalPort] = portVarName;
336 if (!m_declaredVariables.contains(portVarName)) {
337 m_stream << "bool " << portVarName << " = LOW;" << Qt::endl;
338 m_declaredVariables.append(portVarName);
339 }
340 }
341 }
342 }
343
344 m_stream << "// End IC: " << CodeGenUtils::sanitizeComment(ic->label()) << Qt::endl;
345 continue;
346 }
347
348 const auto outputs = elm->outputs();
349
350 QString baseVarName;
351 if (!icPrefix.isEmpty()) {
352 baseVarName = QString("aux_%1%2_%3").arg(icPrefix, removeForbiddenChars(elm->objectName()), QString::number(counter++));
353 } else {
354 baseVarName = QString("aux_%1_%2").arg(removeForbiddenChars(elm->objectName()), QString::number(counter++));
355 }
356
357 if (outputs.size() == 1) {
358 Port *port = outputs.constFirst();
359
360 if (elm->elementType() == ElementType::InputVcc) {
361 m_varMap[port] = "HIGH";
362 continue;
363 }
364
365 if (elm->elementType() == ElementType::InputGnd) {
366 m_varMap[port] = "LOW";
367 continue;
368 }
369
370 if (m_varMap.value(port).isEmpty()) {
371 m_varMap[port] = baseVarName;
372 }
373 } else {
374 int portCounter = 0;
375 for (auto *port : outputs) {
376 QString portName = baseVarName + QString("_%1").arg(portCounter++);
377 if (!port->name().isEmpty()) {
378 portName += "_" + removeForbiddenChars(port->name());
379 }
380 m_varMap[port] = portName;
381 }
382 }
383
384 for (auto *port : outputs) {
385 const QString varName2 = m_varMap.value(port);
386 if (!m_declaredVariables.contains(varName2)) {
387 m_stream << "bool " << varName2 << " = " << highLow(port->defaultValue()) << ";" << Qt::endl;
388 m_declaredVariables.append(varName2);
389 }
390
391 // Staging variable for non-blocking sequential commit (see emitTickDriver).
392 if (elm->elementGroup() == ElementGroup::Memory) {
393 m_stream << "bool " << varName2 << "_next = " << highLow(port->defaultValue()) << ";" << Qt::endl;
394 }
395
396 switch (elm->elementType()) {
397 case ElementType::Clock: {
398 if (!isBox) {
399 auto *clk = qobject_cast<Clock *>(elm);
400 if (!clk) {
401 break;
402 }
403 m_stream << "elapsedMillis " << varName2 << "_elapsed = 0;" << Qt::endl;
404 m_stream << "int " << varName2 << "_interval = " << (std::max)(1, static_cast<int>(1000.0 / clk->frequency())) << ";" << Qt::endl;
405 }
406 break;
407 }
408 case ElementType::DFlipFlop: {
409 m_stream << "bool " << varName2 << "_inclk = LOW;" << Qt::endl;
410 m_stream << "bool " << varName2 << "_last = LOW;" << Qt::endl;
411 break;
412 }
413 case ElementType::TFlipFlop:
414 case ElementType::SRFlipFlop:
415 case ElementType::JKFlipFlop: {
416 m_stream << "bool " << varName2 << "_inclk = LOW;" << Qt::endl;
417 break;
418 }
419 case ElementType::And:
420 case ElementType::AudioBox:
421 case ElementType::Buzzer:
422 case ElementType::DLatch:
423 case ElementType::Demux:
424 case ElementType::Display14:
425 case ElementType::Display16:
426 case ElementType::Display7:
427 case ElementType::IC:
428 case ElementType::InputButton:
429 case ElementType::InputGnd:
430 case ElementType::InputRotary:
431 case ElementType::InputSwitch:
432 case ElementType::InputVcc:
433 case ElementType::JKLatch:
434 case ElementType::Led:
435 case ElementType::Line:
436 case ElementType::Mux:
437 case ElementType::Nand:
438 case ElementType::Node:
439 case ElementType::Nor:
440 case ElementType::Not:
441 case ElementType::Or:
442 case ElementType::SRLatch:
443 case ElementType::Text:
444 case ElementType::TruthTable:
445 case ElementType::Unknown:
446 case ElementType::Xnor:
447 case ElementType::Xor:
448 break;
449 }
450 }
451 }
452}
453
454void ArduinoCodeGen::declareSequentialStateRec(const QVector<GraphicElement *> &elements, const bool topLevel)
455{
456 for (auto *elm : elements) {
457 if (elm->elementType() == ElementType::IC) {
458 if (auto *ic = qobject_cast<IC *>(elm)) {
459 declareSequentialStateRec(ic->internalElements(), false);
460 }
461 continue;
462 }
463
464 const auto outputs = elm->outputs();
465 if (outputs.isEmpty()) {
466 continue;
467 }
468 // Staging variables for non-blocking sequential commit, one per output.
469 if (elm->elementGroup() == ElementGroup::Memory) {
470 for (auto *port : outputs) {
471 const QString v = m_varMap.value(port);
472 if (!v.isEmpty()) {
473 m_stream << "bool " << v << "_next = " << highLow(port->defaultValue()) << ";" << Qt::endl;
474 }
475 }
476 }
477
478 const QString varName = m_varMap.value(outputs.constFirst());
479 if (varName.isEmpty()) {
480 continue;
481 }
482
483 switch (elm->elementType()) {
484 case ElementType::Clock:
485 // Mirror declareAuxVariablesRec: only top-level clocks are time-driven.
486 if (topLevel) {
487 m_stream << "elapsedMillis " << varName << "_elapsed = 0;" << Qt::endl;
488 m_stream << "int " << varName << "_interval = 1000;" << Qt::endl;
489 }
490 break;
491 case ElementType::DFlipFlop:
492 m_stream << "bool " << varName << "_inclk = LOW;" << Qt::endl;
493 m_stream << "bool " << varName << "_last = LOW;" << Qt::endl;
494 break;
495 case ElementType::TFlipFlop:
496 case ElementType::SRFlipFlop:
497 case ElementType::JKFlipFlop:
498 m_stream << "bool " << varName << "_inclk = LOW;" << Qt::endl;
499 break;
500 case ElementType::And:
501 case ElementType::AudioBox:
502 case ElementType::Buzzer:
503 case ElementType::DLatch:
504 case ElementType::Demux:
505 case ElementType::Display14:
506 case ElementType::Display16:
507 case ElementType::Display7:
508 case ElementType::IC:
509 case ElementType::InputButton:
510 case ElementType::InputGnd:
511 case ElementType::InputRotary:
512 case ElementType::InputSwitch:
513 case ElementType::InputVcc:
514 case ElementType::JKLatch:
515 case ElementType::Led:
516 case ElementType::Line:
517 case ElementType::Mux:
518 case ElementType::Nand:
519 case ElementType::Node:
520 case ElementType::Nor:
521 case ElementType::Not:
522 case ElementType::Or:
523 case ElementType::SRLatch:
524 case ElementType::Text:
525 case ElementType::TruthTable:
526 case ElementType::Unknown:
527 case ElementType::Xnor:
528 case ElementType::Xor:
529 break;
530 }
531 }
532}
533
534bool ArduinoCodeGen::hasNativeMemory(const QVector<GraphicElement *> &elements)
535{
536 for (auto *elm : elements) {
537 if (elm->elementGroup() == ElementGroup::Memory) {
538 return true;
539 }
540 if (elm->elementType() == ElementType::IC) {
541 if (auto *ic = qobject_cast<IC *>(elm)) {
542 if (hasNativeMemory(ic->internalElements())) {
543 return true;
544 }
545 }
546 }
547 }
548 return false;
549}
550
551void ArduinoCodeGen::emitCommitFlipFlopsRec(const QVector<GraphicElement *> &elements)
552{
553 for (auto *elm : elements) {
554 if (elm->elementType() == ElementType::IC) {
555 if (auto *ic = qobject_cast<IC *>(elm)) {
556 emitCommitFlipFlopsRec(ic->internalElements());
557 }
558 continue;
559 }
560 if (elm->elementGroup() != ElementGroup::Memory) {
561 continue;
562 }
563 for (auto *port : elm->outputs()) {
564 const QString varName = m_varMap.value(port);
565 if (!varName.isEmpty()) {
566 m_stream << " " << varName << " = " << varName << "_next;" << Qt::endl;
567 }
568 }
569 }
570}
571
572void ArduinoCodeGen::emitCommitFlipFlops()
573{
574 m_stream << "void commitFlipFlops() {" << Qt::endl;
575 emitCommitFlipFlopsRec(m_elements);
576 m_stream << "}" << Qt::endl << Qt::endl;
577}
578
579void ArduinoCodeGen::emitTickDriver()
580{
581 // One simulation tick. Sequential sketches mirror the engine's non-blocking
582 // semantics: settle combinational logic while flip-flops sample into staging
583 // (_next) reading pre-edge state, commit all flip-flops at once, then settle
584 // again so combinational outputs reflect the new committed state.
585 if (m_hasSequential) {
586 m_stream << " g_sample = true;" << Qt::endl;
587 m_stream << " for (int s = 0; s < " << Simulation::kMaxSettleIterations << "; s++) { computeLogic(); }" << Qt::endl;
588 m_stream << " commitFlipFlops();" << Qt::endl;
589 m_stream << " g_sample = false;" << Qt::endl;
590 m_stream << " for (int s = 0; s < " << Simulation::kMaxSettleIterations << "; s++) { computeLogic(); }" << Qt::endl;
591 } else {
592 m_stream << " computeLogic();" << Qt::endl;
593 }
594}
595
596void ArduinoCodeGen::declareAuxVariables()
597{
598 m_stream << "/* ====== Aux. Variables ====== */" << Qt::endl;
599
600 for (const auto &pin : std::as_const(m_inputMap)) {
601 const QString valVarName = pin.m_varName + "_val";
602 if (!m_declaredVariables.contains(valVarName)) {
603 m_stream << "bool " << valVarName << " = LOW;" << Qt::endl;
604 m_declaredVariables.append(valVarName);
605 }
606 }
607
608 declareAuxVariablesRec(m_elements, false, {});
609 m_stream << Qt::endl;
610}
611
612void ArduinoCodeGen::emitFlipFlopBlock(GraphicElement *elm, const QString &typeName, const QString &firstOut,
613 const QString &secondOut, int clockInputIndex, int presetInputIndex,
614 int clearInputIndex, const std::function<void()> &edgeLogic,
615 const std::function<void()> &stateEpilogue)
616{
617 QString clk = otherPortName(elm->inputPort(clockInputIndex));
618 QString inclk = firstOut + "_inclk";
619 QString firstOutNext = firstOut + "_next";
620 QString secondOutNext = secondOut + "_next";
621
622 m_stream << QString(" //%1 FlipFlop").arg(typeName) << Qt::endl;
623 // Sample only during the settle phase (g_sample); the post-commit re-settle
624 // re-evaluates combinational logic without re-clocking. Outputs are staged to
625 // <out>_next and published together by commitFlipFlops() — non-blocking
626 // semantics so gated clocks read pre-edge state, matching the engine.
627 m_stream << QString(" if (g_sample) {") << Qt::endl;
628 m_stream << QString(" if (%1 && !%2) { ").arg(clk, inclk) << Qt::endl;
629
630 // Type-specific edge behavior (writes the _next staging variables).
631 edgeLogic();
632
633 m_stream << QString(" }") << Qt::endl;
634
635 // Preset/Clear logic (common to all edge-triggered flip-flops), staged.
636 QString prst = otherPortName(elm->inputPort(presetInputIndex));
637 QString clr = otherPortName(elm->inputPort(clearInputIndex));
638 m_stream << QString(" if (!%1 || !%2) { ").arg(prst, clr) << Qt::endl;
639 m_stream << QString(" %1 = !%2; //Preset").arg(firstOutNext, prst) << Qt::endl;
640 m_stream << QString(" %1 = !%2; //Clear").arg(secondOutNext, clr) << Qt::endl;
641 m_stream << QString(" }") << Qt::endl;
642
643 // Clock-level update for edge detection.
644 m_stream << " " << inclk << " = " << clk << ";" << Qt::endl;
645
646 // Per-sample epilogue (e.g. the one-tick data latch for D/T flip-flops).
647 if (stateEpilogue) {
648 stateEpilogue();
649 }
650
651 m_stream << QString(" }") << Qt::endl; // end if (g_sample)
652 m_stream << QString(" //End of %1 FlipFlop").arg(typeName) << Qt::endl;
653}
654
655void ArduinoCodeGen::setup()
656{
657 m_stream << "void setup() {" << Qt::endl;
658 for (const auto &pin : std::as_const(m_inputMap)) {
659 m_stream << " pinMode(" << pin.m_varName << ", INPUT);" << Qt::endl;
660 }
661 for (const auto &pin : std::as_const(m_outputMap)) {
662 m_stream << " pinMode(" << pin.m_varName << ", OUTPUT);" << Qt::endl;
663 }
664 m_stream << "}" << Qt::endl
665 << Qt::endl;
666}
667
668void ArduinoCodeGen::assignVariablesRec(const QVector<GraphicElement *> &elements)
669{
670 for (auto *elm : elements) {
671 if (elm->elementType() == ElementType::IC) {
672 auto *ic = qobject_cast<IC *>(elm);
673 if (!ic) continue;
674
675 m_stream << " // IC: " << CodeGenUtils::sanitizeComment(ic->label()) << Qt::endl;
676
677 for (int i = 0; i < ic->inputSize(); ++i) {
678 Port *externalPort = ic->inputPort(i);
679 Port *internalPort = ic->internalInputs().at(i);
680 const QString externalValue = otherPortName(externalPort);
681 const QString internalVar = m_varMap.value(internalPort);
682 m_stream << " " << internalVar << " = " << externalValue << ";" << Qt::endl;
683 }
684
685 if (!ic->internalElements().isEmpty()) {
686 IC *previousIC = m_currentIC;
687 m_currentIC = ic;
688 // Emit boundary input nodes before the rest so downstream gates
689 // never read a stale input on the first settle pass. The
690 // topological sort can place a boundary input after a consumer
691 // when the IC has feedback (its priorities take the legacy
692 // path), and a stale read transiently flips bistable latches —
693 // wrong, and divergent from the simulation, which excludes
694 // boundary inputs from its settle (IC::initializeSimulation).
695 QSet<GraphicElement *> boundaryInputs;
696 for (auto *port : ic->internalInputs()) {
697 if (auto *boundaryElement = port->graphicElement()) {
698 boundaryInputs.insert(boundaryElement);
699 }
700 }
701 auto sortedInternal = Scene::sortByTopology(ic->internalElements());
702 std::stable_partition(sortedInternal.begin(), sortedInternal.end(),
703 [&boundaryInputs](GraphicElement *e) { return boundaryInputs.contains(e); });
704 assignVariablesRec(sortedInternal);
705 m_currentIC = previousIC;
706 }
707
708 for (int i = 0; i < ic->outputSize(); ++i) {
709 Port *externalPort = ic->outputPort(i);
710 Port *internalPort = ic->internalOutputs().at(i);
711 const QString internalValue = m_varMap.value(internalPort);
712 const QString externalVar = m_varMap.value(externalPort);
713 m_stream << " " << externalVar << " = " << internalValue << ";" << Qt::endl;
714 }
715
716 m_stream << " // End IC: " << CodeGenUtils::sanitizeComment(ic->label()) << Qt::endl;
717 continue;
718 }
719
720 if (elm->inputs().isEmpty() || elm->outputs().isEmpty()) {
721 continue;
722 }
723
724 auto *outputPort0 = elm->outputPort(0);
725 if (!outputPort0) {
726 continue;
727 }
728 QString firstOut = m_varMap.value(outputPort0);
729 switch (elm->elementType()) {
730 case ElementType::DFlipFlop: emitDFlipFlop(elm, firstOut); break;
731 case ElementType::DLatch: emitDLatch(elm, firstOut); break;
732 case ElementType::JKFlipFlop: emitJKFlipFlop(elm, firstOut); break;
733 case ElementType::SRFlipFlop: emitSRFlipFlop(elm, firstOut); break;
734 case ElementType::TFlipFlop: emitTFlipFlop(elm, firstOut); break;
735 case ElementType::SRLatch: emitSRLatch(elm, firstOut); break;
736 case ElementType::Mux: emitMux(elm); break;
737 case ElementType::Demux: emitDemux(elm); break;
738 case ElementType::TruthTable: emitTruthTable(elm); break;
739 case ElementType::And:
740 case ElementType::Or:
741 case ElementType::Nand:
742 case ElementType::Nor:
743 case ElementType::Xor:
744 case ElementType::Xnor:
745 case ElementType::Not:
746 case ElementType::Node: assignLogicOperator(elm); break;
747 case ElementType::AudioBox:
748 case ElementType::Buzzer:
749 case ElementType::Clock:
750 case ElementType::Display14:
751 case ElementType::Display16:
752 case ElementType::Display7:
753 case ElementType::IC:
754 case ElementType::InputButton:
755 case ElementType::InputGnd:
756 case ElementType::InputRotary:
757 case ElementType::InputSwitch:
758 case ElementType::InputVcc:
759 case ElementType::JKLatch:
760 case ElementType::Led:
761 case ElementType::Line:
762 case ElementType::Text:
763 case ElementType::Unknown:
764 throw PANDACEPTION("Element type not supported: %1", elm->objectName());
765 }
766 }
767}
768
769void ArduinoCodeGen::emitDFlipFlop(GraphicElement *elm, const QString &firstOut)
770{
771 auto *outputPort1 = elm->outputPort(1);
772 if (!outputPort1) return;
773 QString secondOut = m_varMap.value(outputPort1);
774 QString firstOutNext = firstOut + "_next";
775 QString secondOutNext = secondOut + "_next";
776 QString data = otherPortName(elm->inputPort(0));
777 QString last = firstOut + "_last";
778 emitFlipFlopBlock(elm, "D", firstOut, secondOut, /*clk*/1, /*preset*/2, /*clear*/3,
779 [this, &last, &firstOutNext, &secondOutNext]() {
780 m_stream << QString(" %1 = %2;").arg(firstOutNext, last) << Qt::endl;
781 m_stream << QString(" %1 = !%2;").arg(secondOutNext, last) << Qt::endl;
782 },
783 [this, &last, &data]() {
784 m_stream << " " << last << " = " << data << ";" << Qt::endl;
785 });
786}
787
788void ArduinoCodeGen::emitDLatch(GraphicElement *elm, const QString &firstOut)
789{
790 auto *outputPort1 = elm->outputPort(1);
791 if (!outputPort1) return;
792 QString secondOut = m_varMap.value(outputPort1);
793 QString firstOutNext = firstOut + "_next";
794 QString secondOutNext = secondOut + "_next";
795 QString data = otherPortName(elm->inputPort(0));
796 QString clk = otherPortName(elm->inputPort(1));
797 m_stream << QString(" //D Latch") << Qt::endl;
798 m_stream << QString(" if (g_sample) {") << Qt::endl;
799 m_stream << QString(" if (%1) { ").arg(clk) << Qt::endl;
800 m_stream << QString(" %1 = %2;").arg(firstOutNext, data) << Qt::endl;
801 m_stream << QString(" %1 = !%2;").arg(secondOutNext, data) << Qt::endl;
802 m_stream << QString(" }") << Qt::endl;
803 m_stream << QString(" }") << Qt::endl;
804 m_stream << QString(" //End of D Latch") << Qt::endl;
805}
806
807void ArduinoCodeGen::emitJKFlipFlop(GraphicElement *elm, const QString &firstOut)
808{
809 auto *outputPort1 = elm->outputPort(1);
810 if (!outputPort1) return;
811 QString secondOut = m_varMap.value(outputPort1);
812 QString firstOutNext = firstOut + "_next";
813 QString secondOutNext = secondOut + "_next";
814 QString j = otherPortName(elm->inputPort(0));
815 QString k = otherPortName(elm->inputPort(2));
816 emitFlipFlopBlock(elm, "JK", firstOut, secondOut, /*clk*/1, /*preset*/3, /*clear*/4,
817 [this, &j, &k, &firstOut, &secondOut, &firstOutNext, &secondOutNext]() {
818 m_stream << QString(" if (%1 && %2) { ").arg(j, k) << Qt::endl;
819 m_stream << QString(" bool aux = %1;").arg(firstOut) << Qt::endl;
820 m_stream << QString(" %1 = %2;").arg(firstOutNext, secondOut) << Qt::endl;
821 m_stream << QString(" %1 = aux;").arg(secondOutNext) << Qt::endl;
822 m_stream << QString(" } else if (%1) {").arg(j) << Qt::endl;
823 m_stream << QString(" %1 = 1;").arg(firstOutNext) << Qt::endl;
824 m_stream << QString(" %1 = 0;").arg(secondOutNext) << Qt::endl;
825 m_stream << QString(" } else if (%1) {").arg(k) << Qt::endl;
826 m_stream << QString(" %1 = 0;").arg(firstOutNext) << Qt::endl;
827 m_stream << QString(" %1 = 1;").arg(secondOutNext) << Qt::endl;
828 m_stream << QString(" }") << Qt::endl;
829 });
830}
831
832void ArduinoCodeGen::emitSRFlipFlop(GraphicElement *elm, const QString &firstOut)
833{
834 auto *outputPort1 = elm->outputPort(1);
835 if (!outputPort1) return;
836 QString secondOut = m_varMap.value(outputPort1);
837 QString firstOutNext = firstOut + "_next";
838 QString secondOutNext = secondOut + "_next";
839 QString s = otherPortName(elm->inputPort(0));
840 QString r = otherPortName(elm->inputPort(2));
841 emitFlipFlopBlock(elm, "SR", firstOut, secondOut, /*clk*/1, /*preset*/3, /*clear*/4,
842 [this, &s, &r, &firstOutNext, &secondOutNext]() {
843 m_stream << QString(" if (%1 && %2) { ").arg(s, r) << Qt::endl;
844 m_stream << QString(" %1 = 1;").arg(firstOutNext) << Qt::endl;
845 m_stream << QString(" %1 = 1;").arg(secondOutNext) << Qt::endl;
846 m_stream << QString(" } else if (%1 != %2) {").arg(s, r) << Qt::endl;
847 m_stream << QString(" %1 = %2;").arg(firstOutNext, s) << Qt::endl;
848 m_stream << QString(" %1 = %2;").arg(secondOutNext, r) << Qt::endl;
849 m_stream << QString(" }") << Qt::endl;
850 });
851}
852
853void ArduinoCodeGen::emitTFlipFlop(GraphicElement *elm, const QString &firstOut)
854{
855 auto *outputPort1 = elm->outputPort(1);
856 if (!outputPort1) return;
857 QString secondOut = m_varMap.value(outputPort1);
858 QString firstOutNext = firstOut + "_next";
859 QString secondOutNext = secondOut + "_next";
860 QString t = otherPortName(elm->inputPort(0));
861 emitFlipFlopBlock(elm, "T", firstOut, secondOut, /*clk*/1, /*preset*/2, /*clear*/3,
862 [this, &t, &firstOut, &firstOutNext, &secondOutNext]() {
863 // Toggle reading the pre-edge Q: Q' = !Q, ~Q' = Q (old).
864 m_stream << QString(" if (%1) { ").arg(t) << Qt::endl;
865 m_stream << QString(" %1 = !%2;").arg(firstOutNext, firstOut) << Qt::endl;
866 m_stream << QString(" %1 = %2;").arg(secondOutNext, firstOut) << Qt::endl;
867 m_stream << QString(" }") << Qt::endl;
868 });
869}
870
871void ArduinoCodeGen::emitSRLatch(GraphicElement *elm, const QString &firstOut)
872{
873 auto *outputPort1 = elm->outputPort(1);
874 if (!outputPort1) return;
875 QString secondOut = m_varMap.value(outputPort1);
876 QString firstOutNext = firstOut + "_next";
877 QString secondOutNext = secondOut + "_next";
878 QString s = otherPortName(elm->inputPort(0));
879 QString r = otherPortName(elm->inputPort(1));
880 m_stream << QString(" //SR Latch") << Qt::endl;
881 m_stream << QString(" if (g_sample) {") << Qt::endl;
882 m_stream << QString(" if (%1 && %2) { ").arg(s, r) << Qt::endl;
883 m_stream << QString(" %1 = LOW;").arg(firstOutNext) << Qt::endl;
884 m_stream << QString(" %1 = LOW;").arg(secondOutNext) << Qt::endl;
885 m_stream << QString(" } else if (%1) { ").arg(s) << Qt::endl;
886 m_stream << QString(" %1 = HIGH;").arg(firstOutNext) << Qt::endl;
887 m_stream << QString(" %1 = LOW;").arg(secondOutNext) << Qt::endl;
888 m_stream << QString(" } else if (%1) { ").arg(r) << Qt::endl;
889 m_stream << QString(" %1 = LOW;").arg(firstOutNext) << Qt::endl;
890 m_stream << QString(" %1 = HIGH;").arg(secondOutNext) << Qt::endl;
891 m_stream << QString(" }") << Qt::endl;
892 m_stream << QString(" }") << Qt::endl;
893 m_stream << QString(" //End of SR Latch") << Qt::endl;
894}
895
896void ArduinoCodeGen::emitMux(GraphicElement *elm)
897{
898 const int totalInputs = elm->inputSize();
899 int numSelectLines = 1;
900 while (numSelectLines < 16 && (1 << numSelectLines) + numSelectLines < totalInputs) {
901 numSelectLines++;
902 }
903 const int numDataInputs = totalInputs - numSelectLines;
904 const QString output = m_varMap.value(elm->outputPort(0));
905 const QString selectValue = buildSelectExpression(elm, numDataInputs, numSelectLines);
906
907 m_stream << QString(" //Multiplexer") << Qt::endl;
908 for (int i = 0; i < numDataInputs; ++i) {
909 if (i == 0) {
910 m_stream << QString(" if ((%1) == %2) {").arg(selectValue).arg(i) << Qt::endl;
911 } else {
912 m_stream << QString(" } else if ((%1) == %2) {").arg(selectValue).arg(i) << Qt::endl;
913 }
914 m_stream << QString(" %1 = %2;").arg(output, otherPortName(elm->inputPort(i))) << Qt::endl;
915 }
916 m_stream << QString(" } else {") << Qt::endl;
917 m_stream << QString(" %1 = LOW;").arg(output) << Qt::endl;
918 m_stream << QString(" }") << Qt::endl;
919 m_stream << QString(" //End of Multiplexer") << Qt::endl;
920}
921
922void ArduinoCodeGen::emitDemux(GraphicElement *elm)
923{
924 const int numOutputs = elm->outputSize();
925 int numSelectLines = 1;
926 while (numSelectLines < 16 && (1 << numSelectLines) < numOutputs) {
927 numSelectLines++;
928 }
929 const QString dataInput = otherPortName(elm->inputPort(0));
930 const QString selectValue = buildSelectExpression(elm, 1, numSelectLines);
931
932 m_stream << QString(" //Demultiplexer") << Qt::endl;
933 for (int i = 0; i < numOutputs; ++i) {
934 m_stream << QString(" %1 = LOW;").arg(m_varMap.value(elm->outputPort(i))) << Qt::endl;
935 }
936 for (int i = 0; i < numOutputs; ++i) {
937 if (i == 0) {
938 m_stream << QString(" if ((%1) == %2) {").arg(selectValue).arg(i) << Qt::endl;
939 } else {
940 m_stream << QString(" } else if ((%1) == %2) {").arg(selectValue).arg(i) << Qt::endl;
941 }
942 m_stream << QString(" %1 = %2;").arg(m_varMap.value(elm->outputPort(i)), dataInput) << Qt::endl;
943 }
944 m_stream << QString(" }") << Qt::endl;
945 m_stream << QString(" //End of Demultiplexer") << Qt::endl;
946}
947
948void ArduinoCodeGen::emitTruthTable(GraphicElement *elm)
949{
950 auto *ttGraphic = dynamic_cast<TruthTable *>(elm);
951 if (!ttGraphic) return;
952
953 const QBitArray propositions = ttGraphic->key();
954 const int nInputs = elm->inputSize();
955 const int rows = 1 << nInputs;
956
957 QStringList inputSignalNames;
958 for (int i = 0; i < nInputs; ++i) {
959 QString signalName = otherPortName(elm->inputPort(i));
960 if (signalName == "LOW") {
961 signalName = "0";
962 } else if (signalName == "HIGH") {
963 signalName = "1";
964 } else if (signalName.isEmpty()) {
965 m_stream << "// WARNING: Input " << i << " of TruthTable '" << elm->objectName() << "' appears disconnected. Assuming LOW." << Qt::endl;
966 signalName = "0";
967 }
968 inputSignalNames << signalName;
969 }
970
971 QString indexCalculation;
972 for (int i = 0; i < nInputs; ++i) {
973 if (i == 0) {
974 indexCalculation = inputSignalNames[i];
975 } else {
976 indexCalculation = inputSignalNames[i] + " + (" + indexCalculation + " << 1)";
977 }
978 }
979
980 // One if/else chain per output (F19): output k reads key bits 256*k + row.
981 // Emitting per-output keeps the single-output text byte-identical to the
982 // historical form.
983 for (int out = 0; out < elm->outputSize(); ++out) {
984 const QString outputVarName = m_varMap.value(elm->outputPort(out));
985 if (outputVarName.isEmpty()) {
986 if (out == 0) {
987 throw PANDACEPTION("Output variable not mapped for TruthTable: %1", elm->objectName());
988 }
989 m_stream << "// TruthTable '" << elm->objectName() << "' output " << out << " is disconnected — no code emitted." << Qt::endl;
990 continue;
991 }
992
993 m_stream << QString(" //TruthTable") << Qt::endl;
994 for (int i = 0; i < rows; ++i) {
995 if (i == 0) {
996 m_stream << QString(" if ((%1) == %2) {").arg(indexCalculation).arg(i) << Qt::endl;
997 } else {
998 m_stream << QString(" } else if ((%1) == %2) {").arg(indexCalculation).arg(i) << Qt::endl;
999 }
1000 m_stream << QString(" %1 = %2;").arg(outputVarName, propositions.testBit(256 * out + i) ? "HIGH" : "LOW") << Qt::endl;
1001 }
1002 m_stream << QString(" } else {") << Qt::endl;
1003 m_stream << QString(" %1 = LOW;").arg(outputVarName) << Qt::endl;
1004 m_stream << QString(" }") << Qt::endl;
1005 m_stream << QString(" //End TruthTable") << Qt::endl;
1006 }
1007}
1008
1009QVector<ArduinoBoardConfig> ArduinoCodeGen::availableBoards() const
1010{
1011 static const QVector<ArduinoBoardConfig> boards = []() {
1012 QVector<ArduinoBoardConfig> b;
1013
1014 b.append({
1015 "Arduino UNO R3/R4",
1016 {"A0", "A1", "A2", "A3", "A4", "A5", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"},
1017 "Standard Arduino board with 20 I/O pins"
1018 });
1019
1020 b.append({
1021 "Arduino Nano",
1022 {"A0", "A1", "A2", "A3", "A4", "A5", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"},
1023 "Compact Arduino board with 20 I/O pins"
1024 });
1025
1026 QStringList megaPins;
1027 for (int i = 0; i <= 15; ++i) {
1028 megaPins.append(QString("A%1").arg(i));
1029 }
1030 for (int i = 2; i <= 53; ++i) {
1031 megaPins.append(QString::number(i));
1032 }
1033 b.append({
1034 "Arduino Mega 2560",
1035 megaPins,
1036 "High I/O count Arduino board with 70 I/O pins"
1037 });
1038
1039 b.append({
1040 "ESP32",
1041 {"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "A11", "A12", "A13", "A14", "A15", "A16", "A17",
1042 "2", "4", "5", "12", "13", "14", "15", "16", "17", "18", "19", "21", "22", "23", "25", "26", "27", "32", "33", "34"},
1043 "WiFi/Bluetooth enabled board with 36 I/O pins"
1044 });
1045
1046 return b;
1047 }();
1048 return boards;
1049}
1050
1051ArduinoBoardConfig ArduinoCodeGen::selectBoard(int requiredPins)
1052{
1053 const auto boards = availableBoards();
1054 for (const auto &board : boards) {
1055 if (board.maxPins() >= requiredPins) {
1056 return board;
1057 }
1058 }
1059 // None fits: fall back to the board with the most pins. Don't assume the list is ordered
1060 // by capacity — it isn't (ESP32's 38 < Mega's 68), so boards.last() could pick a smaller one.
1061 return *std::max_element(boards.cbegin(), boards.cend(),
1062 [](const ArduinoBoardConfig &a, const ArduinoBoardConfig &b) { return a.maxPins() < b.maxPins(); });
1063}
1064
1065void ArduinoCodeGen::throwPinOverflow() const
1066{
1067 throw PANDACEPTION("This circuit needs %1 I/O pins, but the largest supported board (%2) provides only %3. Reduce the number of inputs and outputs, or split the circuit.",
1068 QString::number(m_totalRequiredPins), m_selectedBoard.name, QString::number(m_selectedBoard.maxPins()));
1069}
1070
1071QString ArduinoCodeGen::buildSelectExpression(GraphicElement *elm, int startIndex, int numSelectLines)
1072{
1073 QString selectValue;
1074 for (int i = numSelectLines - 1; i >= 0; --i) {
1075 QString selectBit = otherPortName(elm->inputPort(startIndex + i));
1076 if (selectBit == "HIGH") selectBit = "1";
1077 else if (selectBit == "LOW") selectBit = "0";
1078 if (i == numSelectLines - 1) {
1079 selectValue = selectBit;
1080 } else {
1081 selectValue = selectBit + " + (" + selectValue + " << 1)";
1082 }
1083 }
1084 return selectValue;
1085}
1086
1087void ArduinoCodeGen::assignLogicOperator(GraphicElement *elm)
1088{
1089 bool negate = false;
1090 bool parentheses = true;
1091 QString logicOperator;
1092 switch (elm->elementType()) {
1093 case ElementType::And: {
1094 logicOperator = "&&";
1095 break;
1096 }
1097 case ElementType::Or: {
1098 logicOperator = "||";
1099 break;
1100 }
1101 case ElementType::Nand: {
1102 logicOperator = "&&";
1103 negate = true;
1104 break;
1105 }
1106 case ElementType::Nor: {
1107 logicOperator = "||";
1108 negate = true;
1109 break;
1110 }
1111 case ElementType::Xor: {
1112 logicOperator = "^";
1113 break;
1114 }
1115 case ElementType::Xnor: {
1116 logicOperator = "^";
1117 negate = true;
1118 break;
1119 }
1120 case ElementType::Not: {
1121 negate = true;
1122 parentheses = false;
1123 break;
1124 }
1125 case ElementType::Node: {
1126 if (elm->outputs().size() == 1 && elm->inputs().size() == 1) {
1127 auto *outputPort = elm->outputPort(0);
1128 auto *inputPort = elm->inputPort(0);
1129 if (outputPort && inputPort) {
1130 QString varName = m_varMap.value(outputPort);
1131 QString inputValue = otherPortName(inputPort);
1132 if (inputValue.isEmpty() && m_currentIC) {
1133 for (int i = 0; i < m_currentIC->internalInputs().size(); ++i) {
1134 if (m_currentIC->internalInputs().at(i) == inputPort) {
1135 inputValue = m_varMap.value(inputPort);
1136 break;
1137 }
1138 }
1139 }
1140 m_stream << " " << varName << " = " << inputValue << ";" << Qt::endl;
1141 }
1142 }
1143 return;
1144 }
1145 case ElementType::AudioBox:
1146 case ElementType::Buzzer:
1147 case ElementType::Clock:
1148 case ElementType::DFlipFlop:
1149 case ElementType::DLatch:
1150 case ElementType::Demux:
1151 case ElementType::Display14:
1152 case ElementType::Display16:
1153 case ElementType::Display7:
1154 case ElementType::IC:
1155 case ElementType::InputButton:
1156 case ElementType::InputGnd:
1157 case ElementType::InputRotary:
1158 case ElementType::InputSwitch:
1159 case ElementType::InputVcc:
1160 case ElementType::JKFlipFlop:
1161 case ElementType::JKLatch:
1162 case ElementType::Led:
1163 case ElementType::Line:
1164 case ElementType::Mux:
1165 case ElementType::SRFlipFlop:
1166 case ElementType::SRLatch:
1167 case ElementType::TFlipFlop:
1168 case ElementType::Text:
1169 case ElementType::TruthTable:
1170 case ElementType::Unknown:
1171 break;
1172 }
1173 if (elm->outputs().size() == 1) {
1174 auto *outputPort = elm->outputPort();
1175 if (!outputPort) return;
1176 QString varName = m_varMap.value(outputPort);
1177 Port *inPort = elm->inputPort();
1178 if (!inPort) return;
1179
1180 // Build the inner expression first
1181 QString innerExpr;
1182 innerExpr = otherPortName(inPort);
1183 for (int i = 1; i < elm->inputs().size(); ++i) {
1184 inPort = elm->inputs().at(i);
1185 innerExpr += " " + logicOperator + " ";
1186 innerExpr += otherPortName(inPort);
1187 }
1188
1189 QString finalExpr = innerExpr;
1190 if (negate) {
1191 if (parentheses) {
1192 finalExpr = "!(" + innerExpr + ")";
1193 } else {
1194 finalExpr = "!" + innerExpr;
1195 }
1196 }
1197
1198 m_stream << " " << varName << " = " << finalExpr << ";" << Qt::endl;
1199 } else {
1200 qWarning() << "assignLogicOperator: element" << elm->objectName() << "has" << elm->outputs().size() << "outputs (expected 1) — skipping";
1201 }
1202}
1203
1204void ArduinoCodeGen::emitComputeLogicFunction()
1205{
1206 m_stream << "void computeLogic() {" << Qt::endl;
1207 m_stream << " // Assigning aux variables. //" << Qt::endl;
1208 assignVariablesRec(m_elements);
1209 m_stream << "}" << Qt::endl
1210 << Qt::endl;
1211}
1212
1213void ArduinoCodeGen::loop()
1214{
1215 m_stream << "void loop() {" << Qt::endl;
1216 m_stream << " // Reading input data //." << Qt::endl;
1217 for (const auto &pin : std::as_const(m_inputMap)) {
1218 m_stream << QString(" %1_val = digitalRead(%1);").arg(pin.m_varName) << Qt::endl;
1219 }
1220 m_stream << Qt::endl;
1221 m_stream << " // Updating clocks. //" << Qt::endl;
1222 for (auto *elm : m_elements) {
1223 if (elm->elementType() == ElementType::Clock) {
1224 const auto elmOutputs = elm->outputs();
1225 if (elmOutputs.isEmpty()) {
1226 continue;
1227 }
1228 QString varName = m_varMap.value(elmOutputs.constFirst());
1229 m_stream << QString(" if (%1_elapsed > %1_interval) {").arg(varName) << Qt::endl;
1230 m_stream << QString(" %1_elapsed = 0;").arg(varName) << Qt::endl;
1231 m_stream << QString(" %1 = ! %1;").arg(varName) << Qt::endl;
1232 m_stream << QString(" }") << Qt::endl;
1233 }
1234 }
1235 m_stream << Qt::endl;
1236 emitTickDriver();
1237 m_stream << Qt::endl;
1238 m_stream << " // Writing output data. //" << Qt::endl;
1239 for (const auto &pin : std::as_const(m_outputMap)) {
1240 if (pin.m_elm->elementType() == ElementType::Buzzer) {
1241 auto *buzzer = qobject_cast<Buzzer *>(pin.m_elm);
1242 if (!buzzer) continue;
1243 const QString inputSignal = otherPortName(buzzer->inputPort(0));
1244 const int frequency = static_cast<int>(buzzer->frequency());
1245 m_stream << QString(" if (%1) {").arg(inputSignal) << Qt::endl;
1246 m_stream << QString(" tone(%1, %2);").arg(pin.m_varName).arg(frequency) << Qt::endl;
1247 m_stream << QString(" } else {") << Qt::endl;
1248 m_stream << QString(" noTone(%1);").arg(pin.m_varName) << Qt::endl;
1249 m_stream << QString(" }") << Qt::endl;
1250 continue;
1251 }
1252 QString varName = otherPortName(pin.m_port);
1253 if (varName.isEmpty()) {
1254 varName = highLow(pin.m_port->defaultValue());
1255 }
1256 m_stream << QString(" digitalWrite(%1, %2);").arg(pin.m_varName, varName) << Qt::endl;
1257 }
1258 m_stream << "}" << Qt::endl;
1259}
1260
1261void ArduinoCodeGen::generateTestbench(const QString &tbFileName, const QVector<TestVector> &vectors)
1262{
1263 QFile tbFile(tbFileName);
1264 if (!tbFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
1265 throw PANDACEPTION("Could not open testbench file: %1", tbFileName);
1266 }
1267
1268 QIODevice *savedDevice = m_stream.device();
1269 m_stream.setDevice(&tbFile);
1270 m_hasSequential = hasNativeMemory(m_elements);
1271
1272 try {
1273 m_stream << "// ============================================================ //" << Qt::endl;
1274 m_stream << "// ====== Testbench generated automatically by wiRedPanda ====== //" << Qt::endl;
1275 m_stream << "// ============================================================ //" << Qt::endl;
1276 m_stream << Qt::endl;
1277 m_stream << "#include <avr/sleep.h>" << Qt::endl;
1278 m_stream << "#include <elapsedMillis.h>" << Qt::endl;
1279 m_stream << Qt::endl;
1280
1281 // Input pin stubs (same variable names as production sketch; not used for I/O)
1282 m_stream << "/* ========= Inputs ========== */" << Qt::endl;
1283 for (const auto &pin : std::as_const(m_inputMap)) {
1284 m_stream << "const int " << pin.m_varName << " = 0;" << Qt::endl;
1285 }
1286 m_stream << Qt::endl;
1287
1288 // Output pin stubs
1289 m_stream << "/* ========= Outputs ========== */" << Qt::endl;
1290 for (const auto &pin : std::as_const(m_outputMap)) {
1291 m_stream << "const int " << pin.m_varName << " = 0;" << Qt::endl;
1292 }
1293 m_stream << Qt::endl;
1294
1295 // Aux variable declarations (same as production sketch)
1296 m_stream << "/* ====== Aux. Variables ====== */" << Qt::endl;
1297 for (const auto &varName : std::as_const(m_declaredVariables)) {
1298 m_stream << "bool " << varName << " = LOW;" << Qt::endl;
1299 }
1300 // Extra state variables for clocks and flip-flops, including those nested
1301 // inside ICs — computeLogic() recurses into them, so their _inclk/_last
1302 // state must be declared at every depth (not just top level).
1303 declareSequentialStateRec(m_elements, /*topLevel*/ true);
1304 if (m_hasSequential) {
1305 m_stream << "bool g_sample = true;" << Qt::endl;
1306 }
1307 m_stream << Qt::endl;
1308
1309 // computeLogic() function — identical to production sketch
1310 emitComputeLogicFunction();
1311 if (m_hasSequential) {
1312 emitCommitFlipFlops();
1313 }
1314
1315 // Build the list of output variable names (what feeds each output pin)
1316 QStringList outputVarNames;
1317 for (const auto &pin : std::as_const(m_outputMap)) {
1318 QString varName = otherPortName(pin.m_port);
1319 if (varName.isEmpty()) {
1320 varName = highLow(pin.m_port->defaultValue());
1321 }
1322 outputVarNames.append(varName);
1323 }
1324
1325 // Test vector table
1326 const int numInputs = static_cast<int>(m_inputMap.size());
1327 const int numOutputs = static_cast<int>(m_outputMap.size());
1328 const int numTests = static_cast<int>(vectors.size());
1329
1330 m_stream << "/* ====== Test Vectors ====== */" << Qt::endl;
1331 m_stream << "struct TestVec {" << Qt::endl;
1332 m_stream << " bool in[" << (std::max)(1, numInputs) << "];" << Qt::endl;
1333 m_stream << " bool out[" << (std::max)(1, numOutputs) << "];" << Qt::endl;
1334 m_stream << "};" << Qt::endl;
1335 m_stream << Qt::endl;
1336 m_stream << "const int NUM_TESTS = " << numTests << ";" << Qt::endl;
1337 m_stream << "const TestVec VECTORS[" << (std::max)(1, numTests) << "] = {" << Qt::endl;
1338 for (const auto &v : vectors) {
1339 m_stream << " {{";
1340 for (int j = 0; j < v.inputs.size(); ++j) {
1341 if (j > 0) m_stream << ", ";
1342 m_stream << (v.inputs[j] ? "HIGH" : "LOW");
1343 }
1344 m_stream << "}, {";
1345 for (int k = 0; k < v.outputs.size(); ++k) {
1346 if (k > 0) m_stream << ", ";
1347 m_stream << (v.outputs[k] ? "HIGH" : "LOW");
1348 }
1349 m_stream << "}}," << Qt::endl;
1350 }
1351 m_stream << "};" << Qt::endl;
1352 m_stream << Qt::endl;
1353
1354 // setup() — runs all test vectors, reports via Serial
1355 m_stream << "void setup() {" << Qt::endl;
1356 m_stream << " Serial.begin(9600);" << Qt::endl;
1357 m_stream << " bool allPassed = true;" << Qt::endl;
1358 m_stream << " for (int t = 0; t < NUM_TESTS; t++) {" << Qt::endl;
1359 for (int j = 0; j < numInputs; ++j) {
1360 m_stream << " " << m_inputMap.at(j).m_varName << "_val = VECTORS[t].in[" << j << "];" << Qt::endl;
1361 }
1362 // One simulation tick. For sequential circuits this mirrors the engine's
1363 // non-blocking commit: settle while flip-flops sample into _next (reading
1364 // pre-edge state), commit all at once, then re-settle so combinational
1365 // outputs reflect the new state. Combinational/gate-built circuits just
1366 // settle to a fixed point (same bound as Simulation::iterativeSettle).
1367 if (m_hasSequential) {
1368 m_stream << " g_sample = true;" << Qt::endl;
1369 m_stream << " for (int s = 0; s < " << Simulation::kMaxSettleIterations
1370 << "; s++) { computeLogic(); }" << Qt::endl;
1371 m_stream << " commitFlipFlops();" << Qt::endl;
1372 m_stream << " g_sample = false;" << Qt::endl;
1373 m_stream << " for (int s = 0; s < " << Simulation::kMaxSettleIterations
1374 << "; s++) { computeLogic(); }" << Qt::endl;
1375 } else {
1376 m_stream << " for (int s = 0; s < " << Simulation::kMaxSettleIterations
1377 << "; s++) { computeLogic(); }" << Qt::endl;
1378 }
1379 m_stream << " bool pass = true;" << Qt::endl;
1380 for (int k = 0; k < numOutputs; ++k) {
1381 m_stream << " pass = pass && (" << outputVarNames.at(k) << " == VECTORS[t].out[" << k << "]);" << Qt::endl;
1382 }
1383 m_stream << " if (!pass) {" << Qt::endl;
1384 m_stream << " allPassed = false;" << Qt::endl;
1385 m_stream << " Serial.print(\"FAIL vector \");" << Qt::endl;
1386 m_stream << " Serial.println(t);" << Qt::endl;
1387 m_stream << " }" << Qt::endl;
1388 m_stream << " }" << Qt::endl;
1389 m_stream << " Serial.println(allPassed ? \"ALL PASS\" : \"SOME FAILED\");" << Qt::endl;
1390 m_stream << " Serial.flush();" << Qt::endl;
1391 m_stream << " set_sleep_mode(SLEEP_MODE_PWR_DOWN);" << Qt::endl;
1392 m_stream << " sleep_mode();" << Qt::endl;
1393 m_stream << "}" << Qt::endl;
1394 m_stream << Qt::endl;
1395
1396 m_stream << "void loop() {}" << Qt::endl;
1397 } catch (...) {
1398 m_stream.setDevice(savedDevice);
1399 tbFile.close();
1400 throw;
1401 }
1402
1403 m_stream.setDevice(savedDevice);
1404 tbFile.close();
1405}
Arduino sketch code generator: translates a circuit into an uploadable .ino file.
Graphic element for the buzzer tone output.
Graphic element for the real-time clock input.
Shared string utilities used by all code generators.
Common logging utilities, the Pandaception error type, and helper macros.
#define PANDACEPTION(msg,...)
Definition Common.h:98
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.
Graphic element for the rotary switch input.
Port classes: Port (base), InputPort, and OutputPort.
Main circuit editing scene with undo/redo and user interaction.
Synchronous cycle-based simulation engine with event-driven clock support.
Graphic element for a user-programmable truth table.
void generate()
Generates the Arduino sketch and writes it to the output file.
void generateTestbench(const QString &tbFileName, const QVector< TestVector > &vectors)
ArduinoCodeGen(const QString &fileName, const QVector< GraphicElement * > &elements)
Constructs the code generator targeting fileName.
Abstract base class for all graphical circuit elements in wiRedPanda.
ElementType elementType() const
Returns the type identifier for this element.
int inputSize() const
Returns the current number of input ports.
InputPort * inputPort(const int index=0) const
Returns the input port at index (default 0).
int outputSize() const
Returns the current number of output ports.
OutputPort * outputPort(const int index=0) const
Returns the output port at index (default 0).
const QVector< OutputPort * > & outputs() const
Returns a const reference to the vector of all output ports.
const QVector< InputPort * > & inputs() const
Returns a const reference to the vector of all input ports.
Abstract base class for circuit element ports (connection endpoints).
Definition Port.h:39
GraphicElement * graphicElement()
Returns the graphic element that owns this port.
Definition Port.h:66
Status defaultValue() const
Returns the default status applied when the port is unconnected.
Definition Port.h:76
const QList< Connection * > & connections() const
Returns the list of wires attached to this port.
Definition Port.cpp:57
QString name() const
Returns the port's label text.
Definition Port.cpp:161
static QHash< QString, InputPort * > wirelessTxInputPorts(const QVector< GraphicElement * > &elements)
Returns a map from wireless channel label to the Tx node's input port.
Definition Scene.cpp:399
static QVector< GraphicElement * > sortByTopology(QVector< GraphicElement * > elements)
Returns elements sorted in topological dependency order (inputs first).
Definition Scene.cpp:371
static constexpr int kMaxSettleIterations
Definition Simulation.h:46
QString removeForbiddenChars(const QString &input, const bool stripFirst=false)
Converts input into a legal language identifier.
QString sanitizeComment(const QString &input)
Makes input safe to embed in a single-line "//" comment.
Describes an Arduino board's available GPIO pins.