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