wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
SystemVerilogCodeGen.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 <QFileInfo>
7#include <QRegularExpression>
8#include <QSet>
9
11#include "App/Core/Common.h"
15#include "App/Element/IC.h"
16#include "App/Scene/Scene.h"
18#include "App/Wiring/Port.h"
19
22static QString icModuleKey(const IC *ic)
23{
24 if (ic->isEmbedded()) {
25 return QStringLiteral("embedded:") + ic->blobName();
26 }
27 QString key = QFileInfo(ic->file()).canonicalFilePath();
28 return key.isEmpty() ? ic->file() : key;
29}
30
31SystemVerilogCodeGen::SystemVerilogCodeGen(const QString &fileName, const QVector<GraphicElement *> &elements)
32 : m_file(fileName)
33 , m_elements(elements)
34{
35 if (!m_file.open(QIODevice::WriteOnly | QIODevice::Text)) {
36 // Same contract as ArduinoCodeGen: a silent return would leave the
37 // stream device-less, generate() would write into the void, and the
38 // UI would report success with no file on disk.
39 throw PANDACEPTION("Could not open file for writing: %1", fileName);
40 }
41 m_stream.setDevice(&m_file);
42
43 // [QUALITY-10] Fixed: sw2 was duplicated as sw1 in original code
44 m_availablePins = QStringList{
45 "sw0",
46 "sw1",
47 "sw2",
48 "sw3",
49 "sw4",
50 "sw5",
51 "sw6",
52 "sw7",
53 "sw8",
54 "sw9",
55 "sw10",
56 "sw11",
57 "sw12",
58 "sw13",
59 };
60
61 QFileInfo info(fileName);
62 m_fileName = info.completeBaseName();
63}
64
65QString SystemVerilogCodeGen::highLow(const Status val)
66{
67 // POWER-ON-SAFE, deliberately two-state: a non-definite status becomes 1'b0. This mirrors
68 // ElementSimState::reset(), which coerces an Unknown power-on default to Inactive, so the
69 // generated module starts where the engine starts -- which is what the iverilog differential
70 // compares. It is also what keeps a seeded feedback `reg` from x-locking instead of settling.
71 // For a value that must stay undefined, use fourState() below.
72 return (val == Status::Active) ? "1'b1" : "1'b0";
73}
74
75QString SystemVerilogCodeGen::fourState(const Status val)
76{
77 // SystemVerilog is four-state, so unlike every other place a Status meets something narrower,
78 // nothing here forces a collapse. Used where Unknown genuinely means "nothing drives this" --
79 // an unconnected input's default, whose Unknown the engine propagates as Unknown (only
80 // reset() coerces, and only at power-on). Emitting 1'b0 there would export a disconnected
81 // input as a confident logic 0.
82 switch (val) {
83 case Status::Active: return "1'b1";
84 case Status::Inactive: return "1'b0";
85 default: return "1'bx"; // Unknown and Error alike are "not a known level"
86 }
87}
88
89// [QUALITY-14] Ensures result is a valid SystemVerilog identifier (starts with letter/underscore, non-empty)
90QString SystemVerilogCodeGen::removeForbiddenChars(const QString &input)
91{
93}
94
95// [BUG-1] Check whether a string is a simple SystemVerilog identifier (no operators/expressions)
96bool SystemVerilogCodeGen::isSimpleIdentifier(const QString &expr)
97{
98 if (expr.isEmpty()) return false;
99 static QRegularExpression re("^[a-zA-Z_][a-zA-Z0-9_]*$");
100 return re.match(expr).hasMatch();
101}
102
103// [BUG-1] Ensure a signal is usable in a sensitivity list.
104// If the expression is complex (not a simple identifier), creates an intermediate wire.
105QString SystemVerilogCodeGen::ensureSimpleSignal(const QString &expr)
106{
107 if (expr.isEmpty() || expr == "1'b0" || expr == "1'b1") return expr;
108 if (isSimpleIdentifier(expr)) return expr;
109
110 // Create an intermediate wire for the complex expression
111 QString wireName = QString("aux_async_%1").arg(m_globalCounter++);
112 m_stream << " wire " << wireName << " = " << expr << ";" << Qt::endl;
113 return wireName;
114}
115
116QString SystemVerilogCodeGen::otherPortName(Port *port)
117{
118 QSet<Port *> visited;
119 return otherPortNameImpl(port, visited);
120}
121
122QString SystemVerilogCodeGen::otherPortNameImpl(Port *port, QSet<Port *> &visited)
123{
124 if (!port) return "1'b0";
125
126 // Cycle detection: if we've already visited this port, don't inline
127 if (visited.contains(port)) {
128 QString mapped = m_varMap.value(port);
129 return mapped.isEmpty() ? "1'b0" : mapped;
130 }
131
132 if (port->connections().isEmpty()) {
133 QString mapped = m_varMap.value(port);
134 if (!mapped.isEmpty()) return mapped;
135 // Wireless Rx: resolve via the Tx node's input (what drives the transmitter)
136 auto *elm = port->graphicElement();
137 if (elm && elm->wirelessMode() == WirelessMode::Rx && !elm->label().isEmpty()) {
138 auto *txInputPort = m_txInputPorts.value(elm->label(), nullptr);
139 if (txInputPort) {
140 return otherPortNameImpl(txInputPort, visited);
141 }
142 }
143 return fourState(port->defaultValue());
144 }
145
146 auto *otherPort = port->connections().constFirst()->otherPort(port);
147 if (!otherPort) return fourState(port->defaultValue());
148
149 // Cycle detection: if we've already visited the connected port, don't inline.
150 // Unreachable given this app's wiring model: every Connection joins one
151 // OutputPort to one InputPort (never same-kind), every otherPortName() call
152 // site starts from an InputPort, and the only recursive re-entry
153 // (generateLogicExpressionImpl's Not/Node cases) also always passes an
154 // InputPort — so `visited` can only ever contain InputPorts, while
155 // `otherPort` here is always an OutputPort. The entry-point check above
156 // (`visited.contains(port)`) is what actually catches every real cycle.
157 if (visited.contains(otherPort)) {
158 QString mapped = m_varMap.value(otherPort); // LCOV_EXCL_LINE
159 return mapped.isEmpty() ? "1'b0" : mapped; // LCOV_EXCL_LINE
160 } // LCOV_EXCL_LINE
161
162 // Mark this port as visited to detect cycles
163 visited.insert(port);
164
165 auto *elm = otherPort->graphicElement();
166 // Unreachable: every Port is bound to its owning GraphicElement via
167 // setGraphicElement() at construction time, before it could ever be wired
168 // into a Connection — a connected port's graphicElement() is never null.
169 if (!elm) {
170 QString mapped = m_varMap.value(otherPort); // LCOV_EXCL_LINE
171 return mapped.isEmpty() ? fourState(port->defaultValue()) : mapped; // LCOV_EXCL_LINE
172 } // LCOV_EXCL_LINE
173
174 // Check m_varMap first — if a wire/variable was declared for this port, use it
175 // directly instead of inlining. This prevents fan-out collision (multiple gates
176 // sharing an upstream output) and enables IC-internal gate wire references.
177 QString mapped = m_varMap.value(otherPort);
178 if (!mapped.isEmpty()) {
179 return mapped;
180 }
181
182 if (elm->elementType() == ElementType::And ||
183 elm->elementType() == ElementType::Or ||
184 elm->elementType() == ElementType::Nand ||
185 elm->elementType() == ElementType::Nor ||
186 elm->elementType() == ElementType::Xor ||
187 elm->elementType() == ElementType::Xnor ||
188 elm->elementType() == ElementType::Not ||
189 elm->elementType() == ElementType::Node) {
190
191 return generateLogicExpressionImpl(elm, visited);
192 }
193
194 // Unreachable: declareAuxVariablesRec() pre-populates m_varMap for every
195 // element type reaching this point except the 8 gate types checked above —
196 // top-level gates get inlined here instead (by design), and IC-internal
197 // gates are pre-declared as wires (m_generatingICModule branch) — so
198 // `mapped` above is never empty for a non-gate `elm`.
199 return "1'b0"; // LCOV_EXCL_LINE
200}
201
202// SystemVerilog reserved words that cannot be used as identifiers.
203static bool isSystemVerilogReserved(const QString &name)
204{
205 static const QSet<QString> reserved = {
206 "always", "and", "assign", "automatic", "begin", "buf", "bufif0", "bufif1",
207 "case", "casex", "casez", "cell", "cmos", "config", "deassign", "default",
208 "defparam", "design", "disable", "edge", "else", "end", "endcase",
209 "endconfig", "endfunction", "endgenerate", "endmodule", "endprimitive",
210 "endspecify", "endtable", "endtask", "event", "for", "force", "forever",
211 "fork", "function", "generate", "genvar", "highz0", "highz1", "if",
212 "ifnone", "incdir", "include", "initial", "inout", "input",
213 "instance", "integer", "join", "large", "liblist", "library", "localparam",
214 "macromodule", "medium", "module", "nand", "negedge", "nmos", "nor",
215 "noshowcancelled", "not", "notif0", "notif1", "or", "output", "parameter",
216 "pmos", "posedge", "primitive", "pull0", "pull1", "pulldown", "pullup",
217 "pulsestyle_onevent", "pulsestyle_ondetect", "rcmos", "real", "realtime",
218 "reg", "release", "repeat", "rnmos", "rpmos", "rtran", "rtranif0",
219 "rtranif1", "scalared", "showcancelled", "signed", "small", "specify",
220 "specparam", "strong0", "strong1", "supply0", "supply1", "table", "task",
221 "time", "tran", "tranif0", "tranif1", "tri", "tri0", "tri1", "triand",
222 "trior", "trireg", "unsigned", "use", "uwire", "vectored", "wait",
223 "wand", "weak0", "weak1", "while", "wire", "wor", "xnor", "xor"
224 };
225 return reserved.contains(name);
226}
227
228// Recursively discover all unique IC types used in the circuit.
229void SystemVerilogCodeGen::collectICTypes(const QVector<GraphicElement *> &elements)
230{
231 for (auto *elm : elements) {
232 if (elm->elementType() != ElementType::IC) {
233 continue;
234 }
235
236 auto *ic = qobject_cast<IC *>(elm);
237 if (!ic) continue;
238
239 // Use canonical file path as key for identity
240 QString key = icModuleKey(ic);
241
242 if (m_icModules.contains(key)) {
243 continue;
244 }
245
246 ICModuleInfo info;
247 info.sourceFile = key;
248 info.prototypeIC = ic;
249
250 // Derive module name from file basename
251 QString baseName = ic->isEmbedded() ? ic->blobName() : QFileInfo(ic->file()).baseName();
252 info.moduleName = removeForbiddenChars(CodeGenUtils::stripAccents(baseName));
254 info.moduleName = "m_" + info.moduleName;
255 }
256
257 // Build input port names from IC external port labels
258 QSet<QString> usedNames;
259 for (int i = 0; i < ic->inputSize(); ++i) {
260 QString portLabel = ic->inputPort(i)->name();
261 QString portName = removeForbiddenChars(CodeGenUtils::stripAccents(portLabel));
262 // Unreachable: buildPortLabels()/ICLoader::loadBoundaryPorts() falls
263 // back to the boundary element's class name (e.g. "InputSwitch")
264 // whenever its label is empty, even an explicitly-blanked one — a
265 // loaded IC's port name is never genuinely empty by the time it
266 // reaches here.
267 if (portName.isEmpty() || portName == "_unnamed") {
268 portName = QString("in_%1").arg(i); // LCOV_EXCL_LINE
269 }
270 // Escape SystemVerilog reserved words
271 if (isSystemVerilogReserved(portName)) {
272 portName = "p_" + portName;
273 }
274 // Deduplicate
275 QString original = portName;
276 int suffix = 1;
277 while (usedNames.contains(portName)) {
278 portName = QString("%1_%2").arg(original).arg(suffix++);
279 }
280 usedNames.insert(portName);
281 info.inputNames.append(portName);
282 }
283
284 // Build output port names
285 for (int i = 0; i < ic->outputSize(); ++i) {
286 QString portLabel = ic->outputPort(i)->name();
287 QString portName = removeForbiddenChars(CodeGenUtils::stripAccents(portLabel));
288 // Unreachable for the same reason as the input loop above.
289 if (portName.isEmpty() || portName == "_unnamed") {
290 portName = QString("out_%1").arg(i); // LCOV_EXCL_LINE
291 }
292 // Escape SystemVerilog reserved words
293 if (isSystemVerilogReserved(portName)) {
294 portName = "p_" + portName;
295 }
296 QString original = portName;
297 int suffix = 1;
298 while (usedNames.contains(portName)) {
299 portName = QString("%1_%2").arg(original).arg(suffix++);
300 }
301 usedNames.insert(portName);
302 info.outputNames.append(portName);
303 }
304
305 m_icModules.insert(key, info);
306
307 // Recurse into internal elements to discover nested IC types
308 collectICTypes(ic->internalElements());
309 }
310}
311
312// Returns the internal gate elements that sit on a combinational feedback loop
313// (cross-coupled NOR/NAND latches). A plain `assign` comb loop x-locks at
314// power-on in SystemVerilog and trips Verilator's UNOPTFLAT; these nodes are
315// instead emitted as `reg` + `always @(*)` with a seed, so the simulator settles
316// them. Detection is a reachability check over the gate graph: an element is on a
317// loop iff it can reach itself following output→input edges.
318QSet<GraphicElement *> SystemVerilogCodeGen::findFeedbackElements(const QVector<GraphicElement *> &elements)
319{
320 const QSet<GraphicElement *> elementSet(elements.cbegin(), elements.cend());
321
322 auto isGate = [](GraphicElement *e) {
323 switch (e->elementType()) {
324 case ElementType::And: case ElementType::Or: case ElementType::Nand:
325 case ElementType::Nor: case ElementType::Xor: case ElementType::Xnor:
326 case ElementType::Not: case ElementType::Node:
327 return true;
328 default:
329 return false;
330 }
331 };
332
333 // Gates driven by elm's outputs (restricted to this IC's internal gates).
334 auto successors = [&](GraphicElement *elm) {
335 QVector<GraphicElement *> succ;
336 for (auto *outPort : elm->outputs()) {
337 const auto conns = outPort->connections();
338 for (auto *conn : conns) {
339 if (!conn) continue;
340 Port *other = conn->otherPort(outPort);
341 if (!other) continue;
342 GraphicElement *h = other->graphicElement();
343 if (h && elementSet.contains(h) && isGate(h)) {
344 succ.append(h);
345 }
346 }
347 }
348 return succ;
349 }; // LCOV_EXCL_LINE -- compiler-generated cleanup for the returned QVector on an exception-unwind path never taken
350
351 QSet<GraphicElement *> feedback;
352 for (auto *start : elements) {
353 if (!isGate(start)) continue;
354 // Depth-first: can `start` reach itself?
355 QSet<GraphicElement *> seen;
356 QVector<GraphicElement *> stack = successors(start);
357 while (!stack.isEmpty()) {
358 GraphicElement *n = stack.takeLast();
359 if (n == start) {
360 feedback.insert(start);
361 break;
362 }
363 if (seen.contains(n)) continue;
364 seen.insert(n);
365 stack += successors(n);
366 }
367 }
368 return feedback;
369}
370
371// Generate IC modules in topological order (leaves first).
372void SystemVerilogCodeGen::generateICModules()
373{
374 if (m_icModules.isEmpty()) {
375 return;
376 }
377
378 // Sort keys for deterministic module emission order.
379 QStringList sortedKeys = m_icModules.keys();
380 std::sort(sortedKeys.begin(), sortedKeys.end());
381
382 bool progress = true;
383 while (progress) {
384 progress = false;
385 for (const QString &key : std::as_const(sortedKeys)) {
386 ICModuleInfo &info = m_icModules[key];
387 if (info.generated) {
388 continue;
389 }
390
391 // Check if all nested IC types are already generated
392 bool allDepsReady = true;
393 for (auto *elm : std::as_const(info.prototypeIC->internalElements())) {
394 if (elm->elementType() != ElementType::IC) {
395 continue;
396 }
397 auto *nestedIC = qobject_cast<IC *>(elm);
398 if (!nestedIC) continue;
399
400 QString nestedKey = icModuleKey(nestedIC);
401 if (!m_icModules.value(nestedKey).generated) {
402 allDepsReady = false;
403 break;
404 }
405 }
406
407 if (allDepsReady) {
408 generateSingleICModule(info);
409 info.generated = true;
410 progress = true;
411 }
412 }
413 }
414}
415
416// Generate a complete SystemVerilog module for one IC type.
417void SystemVerilogCodeGen::generateSingleICModule(ICModuleInfo &info)
418{
419 // Save context
420 QHash<Port *, QString> savedVarMap = m_varMap;
421 QHash<IC *, QString> savedInstanceNames = m_instanceNames;
422 int savedCounter = m_globalCounter;
423
424 m_varMap.clear();
425 m_instanceNames.clear();
426 m_globalCounter = 1;
427 m_generatingICModule = true;
428
429 IC *ic = info.prototypeIC;
430
431 // Structural (gate-level) emission: every IC is translated from its actual
432 // internal gate netlist — there is no behavioral/port-signature shortcut, so
433 // the export can never drift from what the circuit does. Cross-coupled
434 // feedback gates are handled by findFeedbackElements() (reg + always @(*)).
435
436 // Emit module header
437 const QString source = ic->isEmbedded() ? ic->blobName() : QFileInfo(ic->file()).fileName();
438 m_stream << "// Module for " << CodeGenUtils::sanitizeComment(ic->label()) << " (generated from " << source << ")" << Qt::endl;
439 m_stream << "module " << info.moduleName << " (" << Qt::endl;
440
441 // Emit port list
442 QStringList portDecls;
443 for (int i = 0; i < info.inputNames.size(); ++i) {
444 portDecls << QString(" input %1").arg(info.inputNames[i]);
445 }
446 for (int i = 0; i < info.outputNames.size(); ++i) {
447 portDecls << QString(" output %1").arg(info.outputNames[i]);
448 }
449 m_stream << portDecls.join(",\n") << Qt::endl;
450 m_stream << ");" << Qt::endl;
451
452 // Map boundary input ports: for each IC input, the Node's output port
453 // feeds internal logic, so map it to the module input name.
454 // Also build a list of non-boundary elements for processing.
455 QSet<GraphicElement *> boundaryNodes;
456 for (int i = 0; i < ic->internalInputs().size(); ++i) {
457 // m_internalInputs[i] is the Node's input port. Get the Node element.
458 auto *nodeElm = ic->internalInputs()[i]->graphicElement();
459 // Unreachable: ICLoader::loadBoundaryElement() always populates
460 // internalInputs() with a real proxy Node's port, and every Port is
461 // bound to its owning element at construction.
462 if (!nodeElm) {
463 continue; // LCOV_EXCL_LINE
464 }
465 // The Node's output port is what internal elements connect to.
466 m_varMap[nodeElm->outputPort(0)] = info.inputNames[i];
467 boundaryNodes.insert(nodeElm);
468 }
469
470 // Map boundary output ports: the output Node's output port gets the output name.
471 // The assign for these is emitted separately at end of module.
472 for (int i = 0; i < ic->internalOutputs().size(); ++i) {
473 auto *nodeElm = ic->internalOutputs()[i]->graphicElement();
474 // Unreachable for the same reason as the internalInputs() loop above.
475 if (!nodeElm) {
476 continue; // LCOV_EXCL_LINE
477 }
478 m_varMap[nodeElm->outputPort(0)] = info.outputNames[i];
479 boundaryNodes.insert(nodeElm);
480 }
481
482 // Build list of internal (non-boundary) elements
483 QVector<GraphicElement *> internalElements;
484 for (auto *elm : std::as_const(ic->internalElements())) {
485 if (!boundaryNodes.contains(elm)) {
486 internalElements.append(elm);
487 }
488 }
489
490 // Identify cross-coupled feedback gates so they're emitted as settling
491 // `reg` + `always @(*)` instead of x-locking comb-loop `assign`s.
492 m_feedbackElements = findFeedbackElements(internalElements);
493 const bool hasFeedback = !m_feedbackElements.isEmpty();
494
495 // Declare internal variables
496 m_stream << Qt::endl;
497 if (hasFeedback) {
498 m_stream << "/* verilator lint_off UNOPTFLAT */ // intentional latch feedback" << Qt::endl;
499 }
500 declareAuxVariablesRec(internalElements);
501 m_stream << Qt::endl;
502
503 // Assign internal logic
504 m_stream << "// Internal logic" << Qt::endl;
505 assignVariablesRec(internalElements);
506
507 // Emit output assignments: trace what drives each IC output node
508 m_stream << Qt::endl;
509 for (int i = 0; i < ic->internalOutputs().size(); ++i) {
510 // m_internalOutputs[i] is the Node's output port. Get the Node element.
511 auto *nodeElm = ic->internalOutputs()[i]->graphicElement();
512 // Unreachable for the same reason as the internalInputs() loop above.
513 if (!nodeElm) {
514 continue; // LCOV_EXCL_LINE
515 }
516 // The Node's input port receives from internal logic
517 QString value = otherPortName(nodeElm->inputPort(0));
518 m_stream << "assign " << info.outputNames[i] << " = " << value << ";" << Qt::endl;
519 }
520
521 if (hasFeedback) {
522 m_stream << "/* verilator lint_on UNOPTFLAT */" << Qt::endl;
523 }
524 m_feedbackElements.clear();
525
526 m_stream << "endmodule" << Qt::endl;
527 m_stream << Qt::endl;
528
529 // Restore context
530 m_varMap = savedVarMap;
531 m_instanceNames = savedInstanceNames;
532 m_globalCounter = savedCounter;
533 m_generatingICModule = false;
534}
535
537{
538 m_txInputPorts = Scene::wirelessTxInputPorts(m_elements);
539
540 m_stream << "// ==================================================================== //" << Qt::endl;
541 m_stream << "// ======= This code was generated automatically by wiRedPanda ======== //" << Qt::endl;
542 m_stream << "// ==================================================================== //" << Qt::endl;
543 m_stream << Qt::endl
544 << Qt::endl;
545
546 // Discover and generate IC modules (bottom-up)
547 collectICTypes(m_elements);
548
549 // Resolve module name collisions: ensure no IC module name matches the top-level
550 // module name, and no two IC modules share the same name.
551 // Iterate in sorted key order for deterministic renaming.
552 QSet<QString> usedModuleNames;
553 usedModuleNames.insert(m_fileName);
554 QStringList collisionKeys = m_icModules.keys();
555 std::sort(collisionKeys.begin(), collisionKeys.end());
556 for (const QString &key : std::as_const(collisionKeys)) {
557 QString name = m_icModules[key].moduleName;
558 while (usedModuleNames.contains(name)) {
559 name += "_ic";
560 }
561 m_icModules[key].moduleName = name;
562 usedModuleNames.insert(name);
563 }
564
565 generateICModules();
566
567 // Feedback detection for the TOP-LEVEL netlist. generateSingleICModule() sets
568 // m_feedbackElements for the IC it is emitting and clears it again on the way out, so
569 // without this the set would be empty by the time declareAuxVariables() runs and no
570 // top-level element could be recognised as a loop member. A feedback circuit drawn directly
571 // on the canvas -- a cross-coupled NAND latch, say -- would then be emitted as plain `wire`
572 // + `assign`, and otherPortNameImpl()'s cycle guard would substitute 1'b0 for the feedback
573 // edge, since top-level gates are inlined and have no m_varMap entry. That is worse than an
574 // x-locking comb loop: a silently constant-folded expression with the memory removed, which
575 // simulates cleanly and computes the wrong function.
576 m_feedbackElements = findFeedbackElements(m_elements);
577
578 // Top-level module
579 m_stream << "module " << m_fileName << " (" << Qt::endl;
580 // Declare input and output pins
581 declareInputs();
582 declareOutputs();
583 declareAuxVariables();
584
585 // Logic section
586 loop();
587}
588
589// [ISSUE-9] Inputs are mapped directly by name (no redundant _val alias)
590void SystemVerilogCodeGen::declareInputs()
591{
592 int counter = 1;
593
594 // Count total inputs/outputs for comma placement
595 int totalOutputs = 0;
596 int currentOutput = 0;
597 for (auto *elm : m_elements) {
598 const auto type = elm->elementType();
599 if (elm->elementGroup() == ElementGroup::Output) {
600 totalOutputs = INT_MAX;
601 break;
602 } else if ((type == ElementType::InputButton) || (type == ElementType::InputSwitch) || (type == ElementType::Clock) || (type == ElementType::InputRotary)) {
603 totalOutputs += static_cast<int>(elm->outputs().size());
604 }
605 }
606
607 m_stream << "/* ========= Inputs ========== */" << Qt::endl;
608
609 for (auto *elm : m_elements) {
610 const auto type = elm->elementType();
611
612 if ((type == ElementType::InputButton) || (type == ElementType::InputSwitch) || (type == ElementType::Clock) || (type == ElementType::InputRotary)) {
613 QString baseName = elm->objectName() + QString::number(counter);
614 const QString label = elm->label();
615
616 if (!label.isEmpty()) {
617 baseName += "_" + label;
618 }
619
620 baseName = CodeGenUtils::stripAccents(baseName);
621 baseName = removeForbiddenChars(baseName);
622
623 // One module input per output port. Button/Switch/Clock have a
624 // single port and keep their unsuffixed name; a rotary (F23)
625 // contributes one one-hot input per position — previously its
626 // ports got undriven aux wires and the module floated.
627 for (int port = 0; port < elm->outputSize(); ++port) {
628 QString varName = (elm->outputSize() > 1) ? QString("%1_%2").arg(baseName).arg(port) : baseName;
629
630 currentOutput++;
631 if (currentOutput < totalOutputs) {
632 m_stream << QString("input %1,").arg(varName) << Qt::endl;
633 } else {
634 m_stream << QString("input %1").arg(varName) << Qt::endl;
635 }
636
637 m_inputMap.append(MappedPinSystemVerilog(elm, "", varName, elm->outputPort(port), port));
638 // [ISSUE-9] Map directly to input name, no _val indirection
639 m_varMap[elm->outputPort(port)] = varName;
640 }
641 ++counter;
642 }
643 }
644
645 m_stream << Qt::endl;
646}
647
648void SystemVerilogCodeGen::declareOutputs()
649{
650 int counter = 1;
651
652 int totalOutputs = 0;
653 int currentOutput = 0;
654 for (auto *elm : m_elements) {
655 if (elm->elementGroup() == ElementGroup::Output) {
656 totalOutputs += static_cast<int>(elm->inputs().size());
657 }
658 }
659
660 // [QUALITY-13] Outputs are declared as plain 'output' (wire by default).
661 // This is correct because all outputs are driven by continuous 'assign' statements,
662 // not by 'always' blocks. Internal reg variables handle the sequential logic.
663 m_stream << "/* ========= Outputs ========== */" << Qt::endl;
664 for (auto *elm : m_elements) {
665 if (elm->elementGroup() == ElementGroup::Output) {
666 QString label = elm->label();
667 for (int i = 0; i < elm->inputs().size(); ++i) {
668 currentOutput++;
669 QString varName = elm->objectName() + QString::number(counter);
670 if (!label.isEmpty()) {
671 varName = QString("%1_%2").arg(varName, label);
672 }
673 Port *port = elm->inputPort(i);
674 if (!port->name().isEmpty()) {
675 varName = QString("%1_%2").arg(varName, port->name());
676 }
677 varName = CodeGenUtils::stripAccents(varName);
678 varName = removeForbiddenChars(varName);
679 if (currentOutput < totalOutputs) {
680 m_stream << QString("output %1,").arg(varName) << Qt::endl;
681 } else {
682 m_stream << QString("output %1").arg(varName) << Qt::endl;
683 }
684 m_outputMap.append(MappedPinSystemVerilog(elm, "", varName, port, i));
685 }
686 }
687 ++counter;
688 }
689 m_stream << ");" << Qt::endl;
690}
691
692void SystemVerilogCodeGen::declareAuxVariablesRec(const QVector<GraphicElement *> &elements)
693{
694 for (auto *elm : elements) {
695 if (elm->elementType() == ElementType::IC) {
696 auto *ic = qobject_cast<IC *>(elm);
697 if (!ic) continue;
698
699 // Look up IC module info
700 QString key = icModuleKey(ic);
701 const ICModuleInfo &info = m_icModules.value(key);
702
703 // Generate unique instance name
704 QString instanceName = QString("%1_inst_%2").arg(info.moduleName).arg(m_globalCounter++);
705 m_instanceNames[ic] = instanceName;
706
707 m_stream << "// IC instance: " << CodeGenUtils::sanitizeComment(ic->label()) << " (" << info.moduleName << ")" << Qt::endl;
708
709 // Declare output wires for this IC instance
710 for (int i = 0; i < ic->outputSize(); ++i) {
711 QString wireName = QString("w_%1_%2").arg(instanceName, info.outputNames.value(i, QString("out_%1").arg(i)));
712 m_varMap[ic->outputPort(i)] = wireName;
713 m_stream << "wire " << wireName << ";" << Qt::endl;
714 }
715
716 // Do NOT recurse into ic->internalElements() — the IC module handles its internals
717 } else {
718 // [ISSUE-9] Skip top-level input elements — they're already declared as module ports.
719 // IC-internal inputs won't have pre-existing map entries, so they proceed normally.
720 const auto type = elm->elementType();
721 if ((type == ElementType::InputButton ||
722 type == ElementType::InputSwitch ||
723 type == ElementType::Clock ||
724 type == ElementType::InputRotary) &&
725 !m_varMap.value(elm->outputPort()).isEmpty()) {
726 continue;
727 }
728
729 // Skip wire declarations for top-level logic gates (their expressions
730 // are inlined). Inside IC modules (m_generatingICModule), declare wires so that
731 // feedback loops produce proper circular assign references instead of 1'b0.
732 //
733 // A top-level FEEDBACK member is the exception, for exactly the reason that comment
734 // gives. Inlining walks otherPortNameImpl(), whose cycle guard returns 1'b0 when the
735 // revisited port has no m_varMap entry -- and skipping the declaration is what would
736 // leave it without one, yielding not an x-locking comb loop but a silently
737 // constant-folded expression: a canvas-drawn cross-coupled NAND latch with its memory
738 // removed. Declaring it lets the seeded `reg` + `always @(*)` path below treat it the
739 // same way an IC-internal loop is already treated.
740 if (!m_generatingICModule && !m_feedbackElements.contains(elm) &&
741 (type == ElementType::And ||
742 type == ElementType::Or ||
743 type == ElementType::Nand ||
744 type == ElementType::Nor ||
745 type == ElementType::Xor ||
746 type == ElementType::Xnor ||
747 type == ElementType::Not ||
748 type == ElementType::Node)) {
749 continue;
750 }
751
752 QString varName = QString("aux_%1_%2").arg(removeForbiddenChars(CodeGenUtils::stripAccents(elm->objectName()))).arg(m_globalCounter++);
753 const auto outputs = elm->outputs();
754
755 // Track which ports were already pre-mapped (e.g., IC module boundary ports)
756 QSet<Port *> preMapped;
757
758 if (outputs.size() == 1) {
759 Port *port = outputs.constFirst();
760
761 if (elm->elementType() == ElementType::InputVcc) {
762 m_varMap[port] = "1'b1";
763 continue;
764 }
765
766 if (elm->elementType() == ElementType::InputGnd) {
767 m_varMap[port] = "1'b0";
768 continue;
769 }
770
771 if (m_varMap.value(port).isEmpty()) {
772 m_varMap[port] = varName;
773 } else {
774 // Unreachable: this function runs once, in a single pass over
775 // `elements`, for every top-level or IC-internal element list —
776 // the only ports pre-populated beforehand (top-level Inputs via
777 // declareInputs(), IC boundary Nodes via generateSingleICModule())
778 // are already filtered out before reaching this branch, so a
779 // fresh (non-IC, non-boundary) element's own output port can
780 // never already be mapped here.
781 preMapped.insert(port); // LCOV_EXCL_LINE
782 }
783 } else {
784 int portCounter = 0;
785
786 for (auto *port : outputs) {
787 // Unreachable body for the same reason as the single-output branch above.
788 if (!m_varMap.value(port).isEmpty()) {
789 preMapped.insert(port); // LCOV_EXCL_LINE
790 portCounter++; // LCOV_EXCL_LINE
791 continue; // LCOV_EXCL_LINE
792 }
793
794 QString portName = varName;
795 portName.append(QString("_%1").arg(portCounter++));
796
797 if (!port->name().isEmpty()) {
798 portName.append(QString("_%1").arg(removeForbiddenChars(CodeGenUtils::stripAccents(port->name()))));
799 }
800
801 m_varMap[port] = portName;
802 }
803 }
804 int aux = 0; // Initial values for flip-flop outputs (Q=0, Q̄=1)
805 for (auto *port : outputs) {
806 // Skip wire declaration for ports already mapped (e.g., module input/output
807 // ports) — unreachable in practice since `preMapped` is always empty (see above).
808 if (preMapped.contains(port)) {
809 aux++; // LCOV_EXCL_LINE
810 continue; // LCOV_EXCL_LINE
811 }
812 QString varName2 = m_varMap.value(port);
813
814 switch (elm->elementType()) {
815
816 case ElementType::DLatch:
817 case ElementType::SRLatch:
818 case ElementType::SRFlipFlop:
819 case ElementType::DFlipFlop:
820 case ElementType::TFlipFlop:
821 case ElementType::JKFlipFlop: {
822 m_stream << "reg " << varName2 << QString(" = 1'b%1;").arg(aux) << Qt::endl;
823 aux++;
824 break;
825 }
826
827 case ElementType::TruthTable:
828 if (!outputs.isEmpty()) {
829 Port *outputPort = outputs.constFirst();
830 QString ttVarName = QString("%1_output").arg(removeForbiddenChars(elm->objectName()));
831 m_varMap[outputPort] = ttVarName;
832 m_stream << QString("reg ") << ttVarName << ";" << Qt::endl;
833
834 continue;
835 }
836 break; // LCOV_EXCL_LINE -- unreachable: TruthTable::minOutputSize() is 1, so outputs is never empty
837
838 case ElementType::Mux:
839 case ElementType::Demux: {
840 // Mux and Demux use always @(*) blocks, so outputs must be reg, not wire
841 m_stream << "reg " << varName2 << " = 1'b0;" << Qt::endl;
842 break;
843 }
844
845 case ElementType::And:
846 case ElementType::AudioBox:
847 case ElementType::Buzzer:
848 case ElementType::Clock:
849 case ElementType::Display14:
850 case ElementType::Display16:
851 case ElementType::Display7:
852 case ElementType::IC:
853 case ElementType::InputButton:
854 case ElementType::InputGnd:
855 case ElementType::InputRotary:
856 case ElementType::InputSwitch:
857 case ElementType::InputVcc:
858 case ElementType::JKLatch:
859 case ElementType::Led:
860 case ElementType::Line:
861 case ElementType::Nand:
862 case ElementType::Node:
863 case ElementType::Nor:
864 case ElementType::Not:
865 case ElementType::Or:
866 case ElementType::Text:
867 case ElementType::Unknown:
868 case ElementType::Xnor:
869 case ElementType::Xor:
870 if (m_feedbackElements.contains(elm)) {
871 // Cross-coupled feedback node: a `reg` driven by
872 // `always @(*)` (below), seeded so it settles from a
873 // defined power-on state instead of x-locking.
874 m_stream << "reg " << varName2 << " = " << highLow(port->status()) << ";" << Qt::endl;
875 } else {
876 m_stream << "wire " << varName2 << ";" << Qt::endl;
877 }
878 break;
879 }
880 }
881 }
882 }
883}
884
885void SystemVerilogCodeGen::declareAuxVariables()
886{
887 m_stream << "/* ====== Aux. Variables ====== */" << Qt::endl;
888 declareAuxVariablesRec(m_elements);
889 m_stream << Qt::endl;
890}
891
892// [QUALITY-11] Shared helper for all edge-triggered flip-flop types.
893// [BUG-1] Uses ensureSimpleSignal() to guarantee valid sensitivity list identifiers.
894// [BUG-5] Uses ~ (bitwise NOT) consistently instead of ! (logical NOT).
895// [BUG-6] Uses ~(expr) with parentheses for complex expressions.
896void SystemVerilogCodeGen::emitSequentialBlock(
897 const QString &typeName,
898 const QString &clk, const QString &rawPrst, const QString &rawClr,
899 const std::function<void()> &emitPresetBody,
900 const std::function<void()> &emitClearBody,
901 const std::function<void()> &emitNormalBody)
902{
903 bool hasPrst = (rawPrst != "1'b1" && rawPrst != "1'b0");
904 bool hasClr = (rawClr != "1'b1" && rawClr != "1'b0");
905
906 // [BUG-1] Ensure signals are simple identifiers for the sensitivity list.
907 // Complex expressions like ~signal are invalid in always @(...) event controls.
908 QString prst = hasPrst ? ensureSimpleSignal(rawPrst) : rawPrst;
909 QString clr = hasClr ? ensureSimpleSignal(rawClr) : rawClr;
910
911 m_stream << " //" << typeName << Qt::endl;
912
913 // Generate sensitivity list
914 if (hasPrst && hasClr) {
915 m_stream << " always @(posedge " << clk << " or negedge " << prst << " or negedge " << clr << ")" << Qt::endl;
916 } else if (hasPrst) {
917 m_stream << " always @(posedge " << clk << " or negedge " << prst << ")" << Qt::endl;
918 } else if (hasClr) {
919 m_stream << " always @(posedge " << clk << " or negedge " << clr << ")" << Qt::endl;
920 } else {
921 m_stream << " always @(posedge " << clk << ")" << Qt::endl;
922 }
923
924 m_stream << " begin" << Qt::endl;
925
926 if (!hasPrst && !hasClr) {
927 // No async signals — just emit normal clock-edge logic
928 emitNormalBody();
929 } else {
930 bool needsElse = false;
931
932 if (hasPrst) {
933 m_stream << " if (~" << prst << ")" << Qt::endl;
934 m_stream << " begin" << Qt::endl;
935 emitPresetBody();
936 m_stream << " end" << Qt::endl;
937 needsElse = true;
938 }
939
940 if (hasClr) {
941 if (needsElse) {
942 m_stream << " else if (~" << clr << ")" << Qt::endl;
943 } else {
944 m_stream << " if (~" << clr << ")" << Qt::endl;
945 }
946 m_stream << " begin" << Qt::endl;
947 emitClearBody();
948 m_stream << " end" << Qt::endl;
949 }
950
951 m_stream << " else" << Qt::endl;
952 m_stream << " begin" << Qt::endl;
953 emitNormalBody();
954 m_stream << " end" << Qt::endl;
955 }
956
957 m_stream << " end" << Qt::endl;
958 m_stream << " //End of " << typeName << Qt::endl;
959}
960
961void SystemVerilogCodeGen::assignVariablesRec(const QVector<GraphicElement *> &elements)
962{
963 for (auto *elm : elements) {
964 if (elm->elementType() == ElementType::IC) {
965 auto *ic = qobject_cast<IC *>(elm);
966 if (!ic) continue;
967
968 // Look up IC module info and instance name
969 QString key = icModuleKey(ic);
970 const ICModuleInfo &info = m_icModules.value(key);
971 QString instanceName = m_instanceNames.value(ic);
972
973 // Emit module instantiation
974 m_stream << info.moduleName << " " << instanceName << " (" << Qt::endl;
975
976 // Connect input ports
977 for (int i = 0; i < ic->inputSize(); ++i) {
978 QString inputValue = otherPortName(ic->inputPort(i));
979 m_stream << " ." << info.inputNames.value(i, QString("in_%1").arg(i))
980 << "(" << inputValue << ")";
981 if (i < ic->inputSize() - 1 || ic->outputSize() > 0) {
982 m_stream << ",";
983 }
984 m_stream << Qt::endl;
985 }
986
987 // Connect output ports
988 for (int i = 0; i < ic->outputSize(); ++i) {
989 QString outputWire = m_varMap.value(ic->outputPort(i));
990 m_stream << " ." << info.outputNames.value(i, QString("out_%1").arg(i))
991 << "(" << outputWire << ")";
992 if (i < ic->outputSize() - 1) {
993 m_stream << ",";
994 }
995 m_stream << Qt::endl;
996 }
997
998 m_stream << ");" << Qt::endl;
999 continue;
1000 }
1001 if (elm->inputs().isEmpty() || elm->outputs().isEmpty()) {
1002 continue;
1003 }
1004
1005 // Logic gates: propagate expressions directly (top-level) or emit
1006 // assign statements (IC module internal, where wires were declared).
1007 if (elm->elementType() == ElementType::And ||
1008 elm->elementType() == ElementType::Or ||
1009 elm->elementType() == ElementType::Nand ||
1010 elm->elementType() == ElementType::Nor ||
1011 elm->elementType() == ElementType::Xor ||
1012 elm->elementType() == ElementType::Xnor ||
1013 elm->elementType() == ElementType::Not ||
1014 elm->elementType() == ElementType::Node) {
1015
1016 QString expr = generateLogicExpression(elm);
1017 const bool isFeedback = m_feedbackElements.contains(elm);
1018 for (auto *port : elm->outputs()) {
1019 QString existingVar = m_varMap.value(port);
1020 // Emit an assignment whenever this port actually HAS a declared variable --
1021 // inside an IC module, or at top level when the element is a feedback member and
1022 // declareAuxVariablesRec() therefore gave it a seeded reg. Gating on
1023 // m_generatingICModule alone would send top-level feedback gates down the inline
1024 // branch below, overwriting the declared name with an expression and leaving the
1025 // reg at its seed forever.
1026 if (!existingVar.isEmpty() && (m_generatingICModule || isFeedback)) {
1027 if (isFeedback) {
1028 // Cross-coupled feedback node: drive the seeded `reg`
1029 // combinationally so the loop settles (vs. a comb-loop
1030 // `assign` that x-locks / trips Verilator UNOPTFLAT).
1031 m_stream << "always @(*) " << existingVar << " = " << expr << ";" << Qt::endl;
1032 } else {
1033 // IC module internal: wire was declared, emit assign statement
1034 m_stream << "assign " << existingVar << " = " << expr << ";" << Qt::endl;
1035 }
1036 } else {
1037 // Top-level: inline the expression
1038 m_varMap[port] = expr;
1039 }
1040 }
1041 }
1042 // Flip-flops and other stateful elements use auxiliary variables
1043 else {
1044 QString firstOut = m_varMap.value(elm->outputPort(0));
1045 switch (elm->elementType()) {
1046 case ElementType::DLatch: {
1047 QString secondOut = m_varMap.value(elm->outputPort(1));
1048 QString data = otherPortName(elm->inputPort(0));
1049 QString enable = otherPortName(elm->inputPort(1));
1050 m_stream << QString(" //D Latch") << Qt::endl;
1051 m_stream << QString(" always @(*)") << Qt::endl;
1052 m_stream << QString(" begin") << Qt::endl;
1053 m_stream << QString(" if (%1)").arg(enable) << Qt::endl;
1054 m_stream << QString(" begin") << Qt::endl;
1055 // [BUG-5] Use ~ (bitwise NOT) instead of ! (logical NOT)
1056 m_stream << QString(" %1 = %2;").arg(firstOut, data) << Qt::endl;
1057 QString dataBar = data.startsWith("~") ? data.mid(1) : ("~" + data);
1058 m_stream << QString(" %1 = %2;").arg(secondOut, dataBar) << Qt::endl;
1059 m_stream << QString(" end") << Qt::endl;
1060 m_stream << QString(" end") << Qt::endl;
1061 m_stream << QString(" //End of D Latch") << Qt::endl;
1062
1063 break;
1064 }
1065 case ElementType::SRLatch: {
1066 QString secondOut = m_varMap.value(elm->outputPort(1));
1067 QString s = otherPortName(elm->inputPort(0));
1068 QString r = otherPortName(elm->inputPort(1));
1069
1070 m_stream << QString(" //SR Latch") << Qt::endl;
1071 m_stream << QString(" always @(*)") << Qt::endl;
1072 m_stream << QString(" begin") << Qt::endl;
1073 m_stream << QString(" if (%1 && %2)").arg(s, r) << Qt::endl;
1074 m_stream << QString(" begin") << Qt::endl;
1075 m_stream << QString(" %1 = 1'b0;").arg(firstOut) << Qt::endl;
1076 m_stream << QString(" %1 = 1'b0;").arg(secondOut) << Qt::endl;
1077 m_stream << QString(" end") << Qt::endl;
1078 m_stream << QString(" else if (%1 != %2)").arg(s, r) << Qt::endl;
1079 m_stream << QString(" begin") << Qt::endl;
1080 m_stream << QString(" %1 = %2;").arg(firstOut, s) << Qt::endl;
1081 m_stream << QString(" %1 = %2;").arg(secondOut, r) << Qt::endl;
1082 m_stream << QString(" end") << Qt::endl;
1083 m_stream << QString(" end") << Qt::endl;
1084 m_stream << QString(" //End of SR Latch") << Qt::endl;
1085
1086 break;
1087 }
1088
1089 // [QUALITY-11] All edge-triggered flip-flops use the shared emitSequentialBlock helper.
1090 // This fixes BUG-1 (sensitivity lists), BUG-5 (!/~), BUG-6 (!expr) in one place.
1091
1092 case ElementType::SRFlipFlop: {
1093 QString secondOut = m_varMap.value(elm->outputPort(1));
1094 QString s = otherPortName(elm->inputPort(0));
1095 QString clk = otherPortName(elm->inputPort(1));
1096 QString r = otherPortName(elm->inputPort(2));
1097 QString prst = otherPortName(elm->inputPort(3));
1098 QString clr = otherPortName(elm->inputPort(4));
1099
1100 emitSequentialBlock("SR FlipFlop", clk, prst, clr,
1101 [&]() { // Preset: Q=1, Q̄=0
1102 m_stream << " " << firstOut << " <= 1'b1;" << Qt::endl;
1103 m_stream << " " << secondOut << " <= 1'b0;" << Qt::endl;
1104 },
1105 [&]() { // Clear: Q=0, Q̄=1
1106 m_stream << " " << firstOut << " <= 1'b0;" << Qt::endl;
1107 m_stream << " " << secondOut << " <= 1'b1;" << Qt::endl;
1108 },
1109 [&]() { // Normal: SR logic on clock edge
1110 m_stream << " if (" << s << " && ~" << r << ")" << Qt::endl;
1111 m_stream << " begin" << Qt::endl;
1112 m_stream << " " << firstOut << " <= 1'b1;" << Qt::endl;
1113 m_stream << " " << secondOut << " <= 1'b0;" << Qt::endl;
1114 m_stream << " end" << Qt::endl;
1115 m_stream << " else if (~" << s << " && " << r << ")" << Qt::endl;
1116 m_stream << " begin" << Qt::endl;
1117 m_stream << " " << firstOut << " <= 1'b0;" << Qt::endl;
1118 m_stream << " " << secondOut << " <= 1'b1;" << Qt::endl;
1119 m_stream << " end" << Qt::endl;
1120 }
1121 );
1122
1123 break;
1124 }
1125 case ElementType::DFlipFlop: {
1126 QString secondOut = m_varMap.value(elm->outputPort(1));
1127 QString data = otherPortName(elm->inputPort(0));
1128 QString clk = otherPortName(elm->inputPort(1));
1129 QString prst = otherPortName(elm->inputPort(2));
1130 QString clr = otherPortName(elm->inputPort(3));
1131
1132 emitSequentialBlock("D FlipFlop", clk, prst, clr,
1133 [&]() { // Preset: Q=1, Q̄=0
1134 m_stream << " " << firstOut << " <= 1'b1;" << Qt::endl;
1135 m_stream << " " << secondOut << " <= 1'b0;" << Qt::endl;
1136 },
1137 [&]() { // Clear: Q=0, Q̄=1
1138 m_stream << " " << firstOut << " <= 1'b0;" << Qt::endl;
1139 m_stream << " " << secondOut << " <= 1'b1;" << Qt::endl;
1140 },
1141 [&]() { // Normal: capture data on clock edge
1142 m_stream << " " << firstOut << " <= " << data << ";" << Qt::endl;
1143 QString dataBar = data.startsWith("~") ? data.mid(1) : ("~" + data);
1144 m_stream << " " << secondOut << " <= " << dataBar << ";" << Qt::endl;
1145 }
1146 );
1147
1148 break;
1149 }
1150 case ElementType::JKFlipFlop: {
1151 QString secondOut = m_varMap.value(elm->outputPort(1));
1152 QString j = otherPortName(elm->inputPort(0));
1153 QString clk = otherPortName(elm->inputPort(1));
1154 QString k = otherPortName(elm->inputPort(2));
1155 QString prst = otherPortName(elm->inputPort(3));
1156 QString clr = otherPortName(elm->inputPort(4));
1157
1158 emitSequentialBlock("JK FlipFlop", clk, prst, clr,
1159 [&]() { // Preset: Q=1, Q̄=0
1160 m_stream << " " << firstOut << " <= 1'b1;" << Qt::endl;
1161 m_stream << " " << secondOut << " <= 1'b0;" << Qt::endl;
1162 },
1163 [&]() { // Clear: Q=0, Q̄=1
1164 m_stream << " " << firstOut << " <= 1'b0;" << Qt::endl;
1165 m_stream << " " << secondOut << " <= 1'b1;" << Qt::endl;
1166 },
1167 [&]() { // Normal: JK logic on clock edge
1168 m_stream << " if (" << j << " && " << k << ")" << Qt::endl;
1169 m_stream << " begin" << Qt::endl;
1170 m_stream << " " << firstOut << " <= " << secondOut << ";" << Qt::endl;
1171 m_stream << " " << secondOut << " <= " << firstOut << ";" << Qt::endl;
1172 m_stream << " end" << Qt::endl;
1173 m_stream << " else if (" << j << " && ~" << k << ")" << Qt::endl;
1174 m_stream << " begin" << Qt::endl;
1175 m_stream << " " << firstOut << " <= 1'b1;" << Qt::endl;
1176 m_stream << " " << secondOut << " <= 1'b0;" << Qt::endl;
1177 m_stream << " end" << Qt::endl;
1178 m_stream << " else if (~" << j << " && " << k << ")" << Qt::endl;
1179 m_stream << " begin" << Qt::endl;
1180 m_stream << " " << firstOut << " <= 1'b0;" << Qt::endl;
1181 m_stream << " " << secondOut << " <= 1'b1;" << Qt::endl;
1182 m_stream << " end" << Qt::endl;
1183 }
1184 );
1185
1186 break;
1187 }
1188 case ElementType::TFlipFlop: {
1189 QString secondOut = m_varMap.value(elm->outputPort(1));
1190 QString t = otherPortName(elm->inputPort(0));
1191 QString clk = otherPortName(elm->inputPort(1));
1192 QString prst = otherPortName(elm->inputPort(2));
1193 QString clr = otherPortName(elm->inputPort(3));
1194
1195 emitSequentialBlock("T FlipFlop", clk, prst, clr,
1196 [&]() { // Preset: Q=1, Q̄=0
1197 m_stream << " " << firstOut << " <= 1'b1;" << Qt::endl;
1198 m_stream << " " << secondOut << " <= 1'b0;" << Qt::endl;
1199 },
1200 [&]() { // Clear: Q=0, Q̄=1
1201 m_stream << " " << firstOut << " <= 1'b0;" << Qt::endl;
1202 m_stream << " " << secondOut << " <= 1'b1;" << Qt::endl;
1203 },
1204 [&]() { // Normal: toggle on clock edge if T is high
1205 m_stream << " if (" << t << ")" << Qt::endl;
1206 m_stream << " begin" << Qt::endl;
1207 m_stream << " " << firstOut << " <= " << secondOut << ";" << Qt::endl;
1208 m_stream << " " << secondOut << " <= " << firstOut << ";" << Qt::endl;
1209 m_stream << " end" << Qt::endl;
1210 }
1211 );
1212
1213 break;
1214 }
1215 case ElementType::Mux: {
1216 // [ISSUE-7] Cleaner mux select line calculation.
1217 // Solves: 2^select + select = totalInputs
1218 int totalInputs = elm->inputSize();
1219 int numSelectLines = 1;
1220 while ((1 << numSelectLines) + numSelectLines < totalInputs) {
1221 numSelectLines++;
1222 }
1223 int numDataInputs = totalInputs - numSelectLines;
1224
1225 QString output = m_varMap.value(elm->outputPort(0));
1226 m_stream << QString(" //Multiplexer") << Qt::endl;
1227 m_stream << QString(" always @(*)") << Qt::endl;
1228 m_stream << QString(" begin") << Qt::endl;
1229 m_stream << QString(" case({");
1230
1231 // Build select signal concatenation
1232 for (int i = numSelectLines - 1; i >= 0; --i) {
1233 m_stream << otherPortName(elm->inputPort(numDataInputs + i));
1234 if (i > 0) m_stream << ", ";
1235 }
1236 m_stream << "})" << Qt::endl;
1237
1238 // Generate case statements for each select value
1239 for (int i = 0; i < numDataInputs; ++i) {
1240 m_stream << QString(" %1'd%2: %3 = %4;").arg(numSelectLines).arg(i).arg(output).arg(otherPortName(elm->inputPort(i))) << Qt::endl;
1241 }
1242 m_stream << QString(" default: %1 = 1'b0;").arg(output) << Qt::endl;
1243 m_stream << QString(" endcase") << Qt::endl;
1244 m_stream << QString(" end") << Qt::endl;
1245 m_stream << QString(" //End of Multiplexer") << Qt::endl;
1246
1247 break;
1248 }
1249 case ElementType::Demux: {
1250 // Demultiplexer: 1 data input + select lines -> N outputs
1251 int numOutputs = elm->outputSize();
1252 int numSelectLines = 1;
1253 while ((1 << numSelectLines) < numOutputs) {
1254 numSelectLines++;
1255 }
1256
1257 QString dataInput = otherPortName(elm->inputPort(0));
1258 m_stream << QString(" //Demultiplexer") << Qt::endl;
1259 m_stream << QString(" always @(*)") << Qt::endl;
1260 m_stream << QString(" begin") << Qt::endl;
1261
1262 // Initialize all outputs to 0
1263 for (int i = 0; i < numOutputs; ++i) {
1264 m_stream << QString(" %1 = 1'b0;").arg(m_varMap.value(elm->outputPort(i))) << Qt::endl;
1265 }
1266
1267 // Build select signal concatenation for case
1268 m_stream << QString(" case({");
1269 for (int i = numSelectLines - 1; i >= 0; --i) {
1270 m_stream << otherPortName(elm->inputPort(1 + i));
1271 if (i > 0) m_stream << ", ";
1272 }
1273 m_stream << "})" << Qt::endl;
1274
1275 // Generate case statements for each select value
1276 for (int i = 0; i < numOutputs; ++i) {
1277 m_stream << QString(" %1'd%2: %3 = %4;").arg(numSelectLines).arg(i).arg(m_varMap.value(elm->outputPort(i))).arg(dataInput) << Qt::endl;
1278 }
1279 m_stream << QString(" endcase") << Qt::endl;
1280 m_stream << QString(" end") << Qt::endl;
1281 m_stream << QString(" //End of Demultiplexer") << Qt::endl;
1282
1283 break;
1284 }
1285 case ElementType::TruthTable: {
1286 auto *ttGraphic = dynamic_cast<TruthTable *>(elm);
1287 if (!ttGraphic) break;
1288
1289 QBitArray propositions = ttGraphic->key();
1290 const int nInputs = elm->inputSize();
1291 const int rows = 1 << nInputs;
1292
1293 // Resolve input signal names
1294 QStringList inputSignalNames;
1295 for (int i = 0; i < nInputs; ++i) {
1296 Port *ttInputPort = elm->inputPort(i);
1297 QString signalName = otherPortName(ttInputPort);
1298
1299 // The bodies below are unreachable: otherPortName()/otherPortNameImpl()
1300 // never returns the literal strings "LOW"/"HIGH" (that's ArduinoCodeGen's
1301 // convention; this file's highLow() returns "1'b1"/"1'b0") and never
1302 // returns an empty string (every return path yields a tied-off constant,
1303 // a mapped variable, or a recursively-computed expression).
1304 if (signalName == "LOW") {
1305 signalName = "0"; // LCOV_EXCL_LINE
1306 } else if (signalName == "HIGH") {
1307 signalName = "1"; // LCOV_EXCL_LINE
1308 } else if (signalName.isEmpty()) {
1309 m_stream << "// WARNING: Input " << i << " of TruthTable '" << elm->objectName() << "' appears disconnected. Assuming LOW." << Qt::endl; // LCOV_EXCL_LINE
1310 signalName = "0"; // LCOV_EXCL_LINE
1311 }
1312 inputSignalNames << signalName;
1313 }
1314
1315 // Build concatenated input expression for case selector
1316 QStringList bitExpressions;
1317 bitExpressions << "{";
1318 for (int i = 0; i < nInputs; ++i) {
1319 if (i < nInputs - 1) {
1320 bitExpressions << QString("%1, ").arg(inputSignalNames[i]);
1321 } else {
1322 bitExpressions << QString("%1}").arg(inputSignalNames[i]);
1323 }
1324 }
1325
1326 QString indexCalculation = bitExpressions.join("");
1327
1328 // One always block per output (F19): output k reads key bits 256*k + row.
1329 // Emitting per-output keeps the single-output text byte-identical to the
1330 // historical form.
1331 for (int out = 0; out < elm->outputSize(); ++out) {
1332 QString outputVarName = m_varMap.value(elm->outputPort(out));
1333 // Unreachable: declareAuxVariablesRec()'s TruthTable case (see above)
1334 // always maps output 0 to a name, and the multi-output pre-population
1335 // pass (also in declareAuxVariablesRec) maps every other output too —
1336 // outputVarName is never empty here.
1337 if (outputVarName.isEmpty()) {
1338 if (out == 0) { // LCOV_EXCL_LINE
1339 throw PANDACEPTION("Output variable not mapped for TruthTable: %1", elm->objectName()); // LCOV_EXCL_LINE
1340 } // LCOV_EXCL_LINE
1341 m_stream << "// TruthTable '" << elm->objectName() << "' output " << out << " is disconnected — no code emitted." << Qt::endl; // LCOV_EXCL_LINE
1342 continue; // LCOV_EXCL_LINE
1343 }
1344
1345 m_stream << QString(" //TruthTable") << Qt::endl;
1346 m_stream << QString(" always @(*)") << Qt::endl;
1347 m_stream << QString(" begin") << Qt::endl;
1348 m_stream << QString(" case(%1)").arg(indexCalculation) << Qt::endl;
1349
1350 for (int i = 0; i < rows; ++i) {
1351 m_stream << QString(" %1'b").arg(nInputs) << QString::number(i, 2).rightJustified(nInputs, '0') << ": " << outputVarName << " = 1'b" << (propositions.testBit(256 * out + i) ? "1" : "0") << ";" << Qt::endl;
1352 }
1353 // [ISSUE-8] Defensive default for X/Z input states
1354 m_stream << QString(" default: %1 = 1'b0;").arg(outputVarName) << Qt::endl;
1355 m_stream << QString(" endcase") << Qt::endl;
1356 m_stream << QString(" end") << Qt::endl;
1357 m_stream << QString(" //End TruthTable") << Qt::endl;
1358 }
1359
1360 break;
1361 }
1362
1363 // Unreachable: every type listed below is already intercepted before this
1364 // switch — IC by the dedicated branch at the top of this function, the 8
1365 // gate types by the "top-level gate" if-branch above, and every remaining
1366 // Input-/Output-group element by the inputs().isEmpty()/outputs().isEmpty()
1367 // continue at the top of this loop. ElementType::JKLatch is unreachable for a
1368 // different, stronger reason: it's a deprecated enum value with no live
1369 // GraphicElement implementation at all (ElementFactory::hasCreator(JKLatch) is
1370 // false), so no real `elm` can ever report it.
1371 // LCOV_EXCL_START
1372 case ElementType::And:
1373 case ElementType::AudioBox:
1374 case ElementType::Buzzer:
1375 case ElementType::Clock:
1376 case ElementType::Display14:
1377 case ElementType::Display16:
1378 case ElementType::Display7:
1379 case ElementType::IC:
1380 case ElementType::InputButton:
1381 case ElementType::InputGnd:
1382 case ElementType::InputRotary:
1383 case ElementType::InputSwitch:
1384 case ElementType::InputVcc:
1385 case ElementType::JKLatch:
1386 case ElementType::Led:
1387 case ElementType::Line:
1388 case ElementType::Nand:
1389 case ElementType::Node:
1390 case ElementType::Nor:
1391 case ElementType::Not:
1392 case ElementType::Or:
1393 case ElementType::Text:
1394 case ElementType::Unknown:
1395 case ElementType::Xnor:
1396 case ElementType::Xor:
1397 throw PANDACEPTION("Element type not supported: %1", elm->objectName());
1398 // LCOV_EXCL_STOP
1399 }
1400 }
1401 }
1402}
1403
1404QString SystemVerilogCodeGen::generateLogicExpression(GraphicElement *elm)
1405{
1406 QSet<Port *> visited;
1407 return generateLogicExpressionImpl(elm, visited);
1408}
1409
1410// [BUG-2] Cancels double negations: ~~expr -> expr
1411QString SystemVerilogCodeGen::generateLogicExpressionImpl(GraphicElement *elm, QSet<Port *> &visited)
1412{
1413 bool negate = false;
1414 QString logicOperator;
1415
1416 switch (elm->elementType()) {
1417 case ElementType::And: logicOperator = "&"; break;
1418 case ElementType::Or: logicOperator = "|"; break;
1419 case ElementType::Nand: logicOperator = "&"; negate = true; break;
1420 case ElementType::Nor: logicOperator = "|"; negate = true; break;
1421 case ElementType::Xor: logicOperator = "^"; break;
1422 case ElementType::Xnor: logicOperator = "^"; negate = true; break;
1423 case ElementType::Not: {
1424 QString inner = otherPortNameImpl(elm->inputPort(0), visited);
1425 // [BUG-2] Cancel double negation: ~~x -> x
1426 if (inner.startsWith("~")) {
1427 return inner.mid(1);
1428 }
1429 return "~" + inner;
1430 }
1431 case ElementType::Node: return otherPortNameImpl(elm->inputPort(0), visited);
1432 // Unreachable: this function is only ever reached for the 8 gate types handled
1433 // above/below — its two callers (generateLogicExpression(), used by
1434 // assignVariablesRec's top-level-gate branch, and otherPortNameImpl()'s own
1435 // gate-type dispatch) both restrict `elm` to exactly that set. ElementType::JKLatch
1436 // is unreachable for a different, stronger reason: it's a deprecated enum value with
1437 // no live GraphicElement implementation at all (ElementFactory::hasCreator(JKLatch)
1438 // is false), so no real `elm` can ever report it.
1439 // LCOV_EXCL_START
1440 case ElementType::AudioBox:
1441 case ElementType::Buzzer:
1442 case ElementType::Clock:
1443 case ElementType::DFlipFlop:
1444 case ElementType::DLatch:
1445 case ElementType::Demux:
1446 case ElementType::Display14:
1447 case ElementType::Display16:
1448 case ElementType::Display7:
1449 case ElementType::IC:
1450 case ElementType::InputButton:
1451 case ElementType::InputGnd:
1452 case ElementType::InputRotary:
1453 case ElementType::InputSwitch:
1454 case ElementType::InputVcc:
1455 case ElementType::JKFlipFlop:
1456 case ElementType::JKLatch:
1457 case ElementType::Led:
1458 case ElementType::Line:
1459 case ElementType::Mux:
1460 case ElementType::SRFlipFlop:
1461 case ElementType::SRLatch:
1462 case ElementType::TFlipFlop:
1463 case ElementType::Text:
1464 case ElementType::TruthTable:
1465 case ElementType::Unknown:
1466 return "";
1467 // LCOV_EXCL_STOP
1468 }
1469
1470 QString expr;
1471 // Unreachable: And/Or/Nand/Nor/Xor/Xnor (the only types reaching this point —
1472 // Not/Node return early above) all have minInputSize()==2, so inputs().size()
1473 // is never 1 here.
1474 if (elm->inputs().size() == 1) {
1475 expr = otherPortNameImpl(elm->inputPort(0), visited); // LCOV_EXCL_LINE
1476 } else {
1477 // Group multiple inputs with parentheses
1478 expr = "(";
1479 for (int i = 0; i < elm->inputs().size(); ++i) {
1480 if (i > 0) expr += " " + logicOperator + " ";
1481 expr += otherPortNameImpl(elm->inputPort(i), visited);
1482 }
1483 expr += ")";
1484 }
1485
1486 if (negate) {
1487 // [BUG-2] Cancel double negation: ~(~expr) -> expr without outer ~
1488 // The single-input branch above is dead (see its own comment), so `expr`
1489 // always comes from the multi-input "(" ... ")" branch and can never itself
1490 // start with "~" — only the "else" (add the negation) is reachable.
1491 if (expr.startsWith("~")) {
1492 expr = expr.mid(1); // LCOV_EXCL_LINE
1493 } else {
1494 expr = "~" + expr;
1495 }
1496 }
1497
1498 return expr;
1499}
1500
1501void SystemVerilogCodeGen::loop()
1502{
1503 // [ISSUE-9] No redundant _val assigns — inputs are used directly by name
1504
1505 m_stream << "\n// Assigning aux variables. //" << Qt::endl;
1506 assignVariablesRec(m_elements);
1507
1508 m_stream << "\n// Writing output data. //" << Qt::endl;
1509 for (const auto &pin : std::as_const(m_outputMap)) {
1510 QString expr = otherPortName(pin.m_port);
1511 // Unreachable: otherPortName() never returns an empty string (see its own
1512 // documented exclusions above).
1513 if (expr.isEmpty()) {
1514 expr = fourState(pin.m_port->defaultValue()); // LCOV_EXCL_LINE
1515 }
1516 m_stream << QString("assign %1 = %2;").arg(pin.m_varName, expr) << Qt::endl;
1517 }
1518 m_stream << "endmodule" << Qt::endl;
1519}
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.
Port classes: Port (base), InputPort, and OutputPort.
Main circuit editing scene with undo/redo and user interaction.
static bool isSystemVerilogReserved(const QString &name)
static QString icModuleKey(const IC *ic)
SystemVerilog code generator: translates a circuit into a synthesisable module.
Graphic element for a user-programmable truth table.
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.
QString label() const
Returns the user-visible label text for this element.
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< InputPort * > & inputs() const
Returns a const reference to the vector of all input ports.
Graphic element representing an Integrated Circuit (sub-circuit) box.
Definition IC.h:31
const QVector< Port * > & internalInputs() const
Definition IC.h:85
const QVector< Port * > & internalOutputs() const
Definition IC.h:86
bool isEmbedded() const override
Returns true if this element is an embedded IC (not file-backed). Base returns false.
Definition IC.h:82
const QString & blobName() const override
Returns the blob name for embedded ICs, empty if file-backed.
Definition IC.h:80
const QVector< GraphicElement * > & internalElements() const
Definition IC.h:84
const QString & file() const
Definition IC.h:73
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:71
Status status() const
Returns the current logical status (Active/Inactive/Unknown/Error).
Definition Port.h:84
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
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
SystemVerilogCodeGen(const QString &fileName, const QVector< GraphicElement * > &elements)
Constructs the code generator for the given output fileName and circuit elements.
void generate()
Generates the SystemVerilog output file for the circuit.
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.
QString stripAccents(const QString &input)
Strips Unicode diacritic marks (accents) from input using NFC → NFD decomposition.
Metadata for a single IC module during SystemVerilog hierarchical generation.
QString sourceFile
Original .panda file path (or blob name).
QString moduleName
Generated module identifier.
QVector< QString > inputNames
Port names for the module's inputs.
QVector< QString > outputNames
Port names for the module's outputs.
IC * prototypeIC
Representative IC element (used to inspect sub-circuit).
bool generated
True once the module body has been emitted.