verilator/src/V3EmitCFunc.cpp

725 lines
30 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Emit C++ for tree
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
2025-01-01 14:30:25 +01:00
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "V3PchAstMT.h"
#include "V3EmitCFunc.h"
#include "V3TSP.h"
#include <map>
#include <vector>
// We use a static char array in VL_VALUE_STRING
constexpr int VL_VALUE_STRING_MAX_WIDTH = 8192;
//######################################################################
// EmitCFunc
bool EmitCFunc::emitSimpleOk(AstNodeExpr* nodep) {
// Can we put out a simple (A + B) instead of VL_ADD_III(A,B)?
if (nodep->emitSimpleOperator() == "") return false;
if (nodep->isWide()) return false;
if (nodep->op1p() && nodep->op1p()->isWide()) return false;
if (nodep->op2p() && nodep->op2p()->isWide()) return false;
if (nodep->op3p() && nodep->op3p()->isWide()) return false;
return true;
}
void EmitCFunc::emitOpName(AstNode* nodep, const string& format, AstNode* lhsp, AstNode* rhsp,
AstNode* thsp) {
// Look at emitOperator() format for term/uni/dual/triops,
// and write out appropriate text.
// %n* node
// %nq emitIQW on the [node]
// %nw width in bits
// %nW width in words
// %ni iterate
// %l* lhsp - if appropriate, then second char as above
// %r* rhsp - if appropriate, then second char as above
// %t* thsp - if appropriate, then second char as above
// %k Potential line break
// %P Wide temporary name
// , Commas suppressed if the previous field is suppressed
string nextComma;
bool needComma = false;
#define COMMA \
do { \
if (!nextComma.empty()) { \
puts(nextComma); \
nextComma = ""; \
} \
} while (false)
putnbs(nodep, "");
for (string::const_iterator pos = format.begin(); pos != format.end(); ++pos) {
if (pos[0] == ',') {
// Remember we need to add one, but don't do yet to avoid ",)"
if (needComma) {
if (pos[1] == ' ') {
nextComma = ", ";
} else {
nextComma = ",";
}
needComma = false;
}
if (pos[1] == ' ') ++pos; // Must do even if no nextComma
} else if (pos[0] == '%') {
++pos;
bool detail = false;
AstNode* detailp = nullptr;
switch (pos[0]) {
case '%': puts("%"); break;
case 'k': putbs(""); break;
case 'n':
detail = true;
detailp = nodep;
break;
case 'l':
detail = true;
detailp = lhsp;
break;
case 'r':
detail = true;
detailp = rhsp;
break;
case 't':
detail = true;
detailp = thsp;
break;
case 'P':
if (nodep->isWide()) {
UASSERT_OBJ(m_wideTempRefp, nodep,
"Wide Op w/ no temp, perhaps missing op in V3EmitC?");
COMMA;
if (!m_wideTempRefp->selfPointer().isEmpty()) {
emitDereference(m_wideTempRefp,
m_wideTempRefp->selfPointerProtect(m_useSelfForThis));
}
puts(m_wideTempRefp->varp()->nameProtect());
m_wideTempRefp = nullptr;
needComma = true;
}
break;
default: nodep->v3fatalSrc("Unknown emitOperator format code: %" << pos[0]); break;
}
if (detail) {
// Get next letter of %[nlrt]
++pos;
switch (pos[0]) {
case 'q': emitIQW(detailp); break;
case 'w':
COMMA;
puts(cvtToStr(detailp->widthMin()));
needComma = true;
break;
case 'W':
if (lhsp->isWide()) {
COMMA;
puts(cvtToStr(lhsp->widthWords()));
needComma = true;
}
break;
case 'i':
COMMA;
UASSERT_OBJ(detailp, nodep, "emitOperator() references undef node");
iterateAndNextConstNull(detailp);
needComma = true;
break;
default:
nodep->v3fatalSrc("Unknown emitOperator format code: %[nlrt]" << pos[0]);
break;
}
}
} else if (pos[0] == ')') {
nextComma = "";
puts(")");
} else if (pos[0] == '(') {
COMMA;
needComma = false;
puts("(");
} else {
// Normal text
if (std::isalnum(pos[0])) needComma = true;
COMMA;
string s;
s += pos[0];
puts(s);
}
}
}
void EmitCFunc::displayEmit(AstNode* nodep, bool isScan) {
if (m_emitDispState.m_format == ""
&& VN_IS(nodep, Display)) { // not fscanf etc, as they need to return value
// NOP
} else {
// Format
bool isStmt = false;
if (const AstFScanF* const dispp = VN_CAST(nodep, FScanF)) {
isStmt = false;
2024-01-28 17:05:38 +01:00
putns(nodep, "VL_FSCANF_INX(");
iterateConst(dispp->filep());
puts(",");
} else if (const AstSScanF* const dispp = VN_CAST(nodep, SScanF)) {
isStmt = false;
checkMaxWords(dispp->fromp());
putns(nodep, "VL_SSCANF_I");
emitIQW(dispp->fromp());
2024-01-28 17:05:38 +01:00
puts("NX(");
puts(cvtToStr(dispp->fromp()->widthMin()));
puts(",");
iterateConst(dispp->fromp());
puts(",");
} else if (const AstDisplay* const dispp = VN_CAST(nodep, Display)) {
isStmt = true;
if (dispp->filep()) {
2024-01-28 17:05:38 +01:00
putns(nodep, "VL_FWRITEF_NX(");
iterateConst(dispp->filep());
puts(",");
} else {
2024-01-28 17:05:38 +01:00
putns(nodep, "VL_WRITEF_NX(");
}
} else if (const AstSFormat* const dispp = VN_CAST(nodep, SFormat)) {
isStmt = true;
2024-01-28 17:05:38 +01:00
puts("VL_SFORMAT_NX(");
puts(cvtToStr(dispp->lhsp()->widthMin()));
putbs(",");
iterateConst(dispp->lhsp());
putbs(",");
} else if (VN_IS(nodep, SFormatF)) {
isStmt = false;
2024-01-28 17:05:38 +01:00
putns(nodep, "VL_SFORMATF_N_NX(");
} else {
nodep->v3fatalSrc("Unknown displayEmit node type");
}
ofp()->putsQuoted(m_emitDispState.m_format);
2024-01-28 17:05:38 +01:00
ofp()->puts(",0"); // MSVC++ requires va_args to not be off reference
// Arguments
for (unsigned i = 0; i < m_emitDispState.m_argsp.size(); i++) {
const char fmt = m_emitDispState.m_argsChar[i];
AstNode* const argp = m_emitDispState.m_argsp[i];
const string func = m_emitDispState.m_argsFunc[i];
if (func != "" || argp) {
puts(",");
ofp()->indentInc();
ofp()->putbs("");
if (func != "") {
puts(func);
} else if (argp) {
const bool addrof = isScan || (fmt == '@') || (fmt == 'p');
if (addrof) puts("&(");
iterateConst(argp);
if (!addrof) emitDatap(argp);
if (addrof) puts(")");
}
ofp()->indentDec();
}
}
// End
puts(")");
if (isStmt) {
puts(";\n");
} else {
puts(" ");
}
// Prep for next
m_emitDispState.clear();
}
}
void EmitCFunc::displayArg(AstNode* dispp, AstNode** elistp, bool isScan, const string& vfmt,
bool ignore, char fmtLetter) {
// Print display argument, edits elistp
AstNode* argp = nullptr;
if (!ignore) {
argp = *elistp;
if (VL_UNCOVERABLE(!argp)) { // LCOV_EXCL_START
// expectDisplay() checks this first, so internal error if found here
dispp->v3error("Internal: Missing arguments for $display-like format");
return;
} // LCOV_EXCL_STOP
// Prep for next parameter
*elistp = (*elistp)->nextp();
if (argp->widthMin() > VL_VALUE_STRING_MAX_WIDTH) {
2025-05-17 22:28:09 +02:00
dispp->v3warn(E_UNSUPPORTED, "Unsupported: Exceeded limit of "
+ cvtToStr(VL_VALUE_STRING_MAX_WIDTH)
+ " bits for any $display-like arguments");
}
if (argp->widthMin() > 8 && fmtLetter == 'c') {
// Technically legal, but surely not what the user intended.
argp->v3warn(WIDTHTRUNC, dispp->verilogKwd() << "of %c format of > 8 bit value");
}
}
// string pfmt = "%"+displayFormat(argp, vfmt, fmtLetter)+fmtLetter;
string pfmt;
if ((fmtLetter == '#' || fmtLetter == 'd') && !isScan
&& vfmt == "") { // Size decimal output. Spec says leading spaces, not zeros
const double mantissabits = ignore ? 0 : (argp->widthMin() - ((fmtLetter == 'd') ? 1 : 0));
// This is log10(2**mantissabits) as log2(2**mantissabits)/log2(10),
// + 1.0 rounding bias.
double dchars = mantissabits / 3.321928094887362 + 1.0;
if (fmtLetter == 'd') dchars++; // space for sign
const int nchars = int(dchars);
pfmt = "%"s + cvtToStr(nchars) + fmtLetter;
} else {
pfmt = "%"s + vfmt + fmtLetter;
}
m_emitDispState.pushFormat(pfmt);
if (!ignore) {
2022-05-15 15:29:15 +02:00
if (argp->dtypep()->basicp()
&& argp->dtypep()->basicp()->keyword() == VBasicDTypeKwd::STRING) {
// string in SystemVerilog is std::string in C++ which is not POD
m_emitDispState.pushArg(' ', nullptr, "-1");
} else {
m_emitDispState.pushArg(' ', nullptr, cvtToStr(argp->widthMin()));
}
m_emitDispState.pushArg(fmtLetter, argp, "");
if (fmtLetter == 't' || fmtLetter == '^') {
2025-09-01 20:40:22 +02:00
VTimescale timeunit = VTimescale::NONE;
if (const AstDisplay* const nodep = VN_CAST(dispp, Display)) {
2025-09-01 20:40:22 +02:00
timeunit = nodep->fmtp()->timeunit();
} else if (const AstSFormat* const nodep = VN_CAST(dispp, SFormat)) {
2025-09-01 20:40:22 +02:00
timeunit = nodep->fmtp()->timeunit();
} else if (const AstSScanF* const nodep = VN_CAST(dispp, SScanF)) {
timeunit = nodep->timeunit();
} else if (const AstSFormatF* const nodep = VN_CAST(dispp, SFormatF)) {
timeunit = nodep->timeunit();
}
2025-09-01 20:40:22 +02:00
UASSERT_OBJ(!timeunit.isNone(), dispp,
"Use of %t must be under AstDisplay, AstSFormat, or AstSFormatF, or "
"SScanF, and timeunit set");
m_emitDispState.pushArg(' ', nullptr, cvtToStr((int)timeunit.powerOfTen()));
}
} else {
m_emitDispState.pushArg(fmtLetter, nullptr, "");
}
}
void EmitCFunc::displayNode(AstNode* nodep, AstScopeName* scopenamep, const string& vformat,
AstNode* exprsp, bool isScan) {
AstNode* elistp = exprsp;
// Convert Verilog display to C printf formats
// "%0t" becomes "%d"
VL_RESTORER(m_emitDispState);
m_emitDispState.clear();
string vfmt;
string::const_iterator pos = vformat.begin();
bool inPct = false;
bool ignore = false;
for (; pos != vformat.end(); ++pos) {
// UINFO(1, "Parse '" << *pos << "' IP" << inPct << " List " << cvtToHex(elistp));
if (!inPct && pos[0] == '%') {
inPct = true;
ignore = false;
vfmt = "";
} else if (!inPct) { // Normal text
m_emitDispState.pushFormat(*pos);
} else { // Format character
inPct = false;
switch (std::tolower(pos[0])) {
case '0': // FALLTHRU
case '1': // FALLTHRU
case '2': // FALLTHRU
case '3': // FALLTHRU
case '4': // FALLTHRU
case '5': // FALLTHRU
case '6': // FALLTHRU
case '7': // FALLTHRU
case '8': // FALLTHRU
case '9': // FALLTHRU
case '.': // FALLTHRU
case '-':
// Digits, like %5d, etc.
vfmt += pos[0];
inPct = true; // Get more digits
break;
case '%':
m_emitDispState.pushFormat("%%"); // We're printf'ing it, so need to quote the %
break;
case '*':
vfmt += pos[0];
inPct = true; // Get more digits
ignore = true;
break;
// Special codes
case '~':
displayArg(nodep, &elistp, isScan, vfmt, ignore, 'd');
break; // Signed decimal
case '@':
displayArg(nodep, &elistp, isScan, vfmt, ignore, '@');
break; // Packed string
// Spec: h d o b c l
case 'b': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'b'); break;
case 'c': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'c'); break;
case 't': displayArg(nodep, &elistp, isScan, vfmt, ignore, 't'); break;
case 'd':
displayArg(nodep, &elistp, isScan, vfmt, ignore, '#');
break; // Unsigned decimal
case 'o': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'o'); break;
case 'h': // FALLTHRU
case 'x': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'x'); break;
case 'p': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'p'); break;
case 's': displayArg(nodep, &elistp, isScan, vfmt, ignore, 's'); break;
case 'e': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'e'); break;
case 'f': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'f'); break;
case 'g': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'g'); break;
case '^': displayArg(nodep, &elistp, isScan, vfmt, ignore, '^'); break; // Realtime
case 'v': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'v'); break;
case 'u': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'u'); break;
case 'z': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'z'); break;
case 'm': {
UASSERT_OBJ(scopenamep, nodep, "Display with %m but no AstScopeName");
const string suffix = scopenamep->scopePrettySymName();
if (suffix == "") {
m_emitDispState.pushFormat("%S");
} else {
m_emitDispState.pushFormat("%N"); // Add a . when needed
}
m_emitDispState.pushArg(' ', nullptr, "vlSymsp->name()");
m_emitDispState.pushFormat(suffix);
break;
}
case 'l': {
// Better than not compiling
m_emitDispState.pushFormat("----");
break;
}
default:
nodep->v3error("Unknown $display-like format code: '%" << pos[0] << "'");
break;
}
}
}
if (VL_UNCOVERABLE(elistp)) {
// expectFormat also checks this, and should have found it first, so internal
elistp->v3error("Internal: Extra arguments for $display-like format"); // LCOV_EXCL_LINE
}
displayEmit(nodep, isScan);
}
2023-10-16 14:02:29 +02:00
void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer,
bool inProcess) {
putns(nodep, "(");
bool comma = false;
if (nodep->funcp()->isLoose() && !nodep->funcp()->isStatic()) {
UASSERT_OBJ(!selfPointer.empty(), nodep, "Call to loose method without self pointer");
puts(selfPointer);
comma = true;
}
2023-06-01 16:02:08 +02:00
if (nodep->funcp()->needProcess()) {
if (comma) puts(", ");
if (VN_IS(nodep->backp(), CAwait) || !nodep->funcp()->isCoroutine()) {
2023-06-01 16:02:08 +02:00
puts("vlProcess");
2023-10-16 14:02:29 +02:00
} else if (inProcess) {
puts("std::make_shared<VlProcess>(vlProcess)");
2023-06-01 16:02:08 +02:00
} else {
puts("std::make_shared<VlProcess>()");
}
comma = true;
}
if (!nodep->argTypes().empty()) {
if (comma) puts(", ");
puts(nodep->argTypes());
comma = true;
}
2022-11-30 12:42:48 +01:00
putCommaIterateNext(nodep->argsp(), comma);
puts(")");
}
void EmitCFunc::emitDereference(AstNode* nodep, const string& pointer) {
if (pointer[0] == '(' && pointer[1] == '&') {
// remove "address of" followed by immediate dereference
// Note: this relies on only the form '(&OBJECT)' being used by Verilator
putns(nodep, pointer.substr(2, pointer.length() - 3));
puts(".");
} else {
if (pointer == "vlSelf" && m_usevlSelfRef) {
puts("vlSelfRef.");
} else {
putns(nodep, pointer);
puts("->");
}
}
}
void EmitCFunc::emitCvtPackStr(AstNode* nodep) {
if (const AstConst* const constp = VN_CAST(nodep, Const)) {
emitConstantString(constp);
} else if (VN_IS(nodep->dtypep(), StreamDType)) {
putnbs(nodep, "VL_CVT_PACK_STR_ND(");
iterateAndNextConstNull(nodep);
puts(")");
} else {
putnbs(nodep, "VL_CVT_PACK_STR_N");
emitIQW(nodep);
puts("(");
if (nodep->isWide()) {
// Note argument width, not node width (which is always 32)
puts(cvtToStr(nodep->widthWords()));
puts(", ");
}
iterateAndNextConstNull(nodep);
puts(")");
}
}
void EmitCFunc::emitCvtWideArray(AstNode* nodep, AstNode* fromp) {
putnbs(nodep, "VL_CVT_W_A(");
iterateConst(nodep);
puts(", ");
iterateConst(fromp);
putbs(".atDefault()"); // Not accessed; only to get the proper type of values
puts(")");
}
void EmitCFunc::emitConstant(AstConst* nodep) {
// Put out constant set to the specified variable, or given variable in a string
const V3Number& num = nodep->num();
if (num.isFourState()) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: 4-state numbers in this context");
return;
}
putns(nodep, num.emitC());
}
void EmitCFunc::emitConstantString(const AstConst* nodep) {
// Const might be a Verilog array-type string, but need to always output std::string
putnbs(nodep, "std::string{");
const string str = nodep->num().toString();
if (!str.empty()) putsQuoted(str);
puts("}");
}
void EmitCFunc::emitSetVarConstant(const string& assignString, AstConst* constp) {
puts(assignString);
puts(" = ");
emitConstant(constp);
puts(";\n");
}
void EmitCFunc::emitVarReset(AstVar* varp, bool constructing) {
// 'constructing' indicates that the object was just constructed, so no need to clear it also
AstNodeDType* const dtypep = varp->dtypep()->skipRefp();
const string vlSelf = VSelfPointerText::replaceThis(m_useSelfForThis, "this->");
const string varNameProtected = (VN_IS(m_modp, Class) || varp->isFuncLocal())
? varp->nameProtect()
: vlSelf + varp->nameProtect();
if (varp->isIO() && m_modp->isTop() && optSystemC()) {
// System C top I/O doesn't need loading, as the lower level subinst code does it.}
} else if (varp->isParam()) {
UASSERT_OBJ(varp->valuep(), varp, "No init for a param?");
// If a simple CONST value we initialize it using an enum
// If an ARRAYINIT we initialize it using an initial block similar to a signal
// puts("// parameter "+varp->nameProtect()+" = "+varp->valuep()->name()+"\n");
} else if (const AstInitArray* const initarp = VN_CAST(varp->valuep(), InitArray)) {
if (VN_IS(dtypep, AssocArrayDType)) {
if (initarp->defaultp()) {
emitSetVarConstant(varNameProtected + ".atDefault()",
VN_AS(initarp->defaultp(), Const));
}
if (!constructing) puts(varNameProtected + ".clear();");
const auto& mapr = initarp->map();
for (const auto& itr : mapr) {
AstNode* const valuep = itr.second->valuep();
emitSetVarConstant(varNameProtected + ".at(" + cvtToStr(itr.first) + ")",
VN_AS(valuep, Const));
}
} else if (VN_IS(dtypep, WildcardArrayDType)) {
if (initarp->defaultp()) {
emitSetVarConstant(varNameProtected + ".atDefault()",
VN_AS(initarp->defaultp(), Const));
}
if (!constructing) puts(varNameProtected + ".clear();");
const auto& mapr = initarp->map();
for (const auto& itr : mapr) {
AstNode* const valuep = itr.second->valuep();
emitSetVarConstant(varNameProtected + ".at(" + cvtToStr(itr.first) + ")",
VN_AS(valuep, Const));
}
} else if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
if (initarp->defaultp()) {
puts("for (int __Vi = 0; __Vi < " + cvtToStr(adtypep->elementsConst()));
puts("; ++__Vi) {\n");
emitSetVarConstant(varNameProtected + "[__Vi]", VN_AS(initarp->defaultp(), Const));
puts("}\n");
}
const auto& mapr = initarp->map();
for (const auto& itr : mapr) {
AstNode* const valuep = itr.second->valuep();
emitSetVarConstant(varNameProtected + "[" + cvtToStr(itr.first) + "]",
VN_AS(valuep, Const));
}
} else {
varp->v3fatalSrc("InitArray under non-arrayed var");
}
} else {
putns(varp, emitVarResetRecurse(varp, constructing, varNameProtected, dtypep, 0, ""));
}
}
string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing,
const string& varNameProtected, AstNodeDType* dtypep,
int depth, const string& suffix) {
dtypep = dtypep->skipRefp();
AstBasicDType* const basicp = dtypep->basicp();
// Returns string to do resetting, empty to do nothing (which caller should handle)
if (AstAssocArrayDType* const adtypep = VN_CAST(dtypep, AssocArrayDType)) {
// Access std::array as C array
const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : "");
const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n";
return pre
+ emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(),
depth + 1, suffix + ".atDefault()" + cvtarray);
} else if (AstWildcardArrayDType* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) {
// Access std::array as C array
const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : "");
const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n";
return pre
+ emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(),
depth + 1, suffix + ".atDefault()" + cvtarray);
2023-09-19 03:17:21 +02:00
} else if (VN_IS(dtypep, CDType)) {
return ""; // Constructor does it
} else if (VN_IS(dtypep, ClassRefDType)) {
return ""; // Constructor does it
2022-10-20 12:31:00 +02:00
} else if (VN_IS(dtypep, IfaceRefDType)) {
return varNameProtected + suffix + " = nullptr;\n";
} else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) {
// Access std::array as C array
const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : "");
const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n";
return pre
+ emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(),
depth + 1, suffix + ".atDefault()" + cvtarray);
} else if (const AstQueueDType* const adtypep = VN_CAST(dtypep, QueueDType)) {
// Access std::array as C array
const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : "");
const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n";
return pre
+ emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(),
depth + 1, suffix + ".atDefault()" + cvtarray);
2022-12-23 13:34:49 +01:00
} else if (VN_IS(dtypep, SampleQueueDType)) {
return "";
} else if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
UASSERT_OBJ(adtypep->hi() >= adtypep->lo(), varp,
"Should have swapped msb & lsb earlier.");
const string ivar = "__Vi"s + cvtToStr(depth);
const string pre = ("for (int " + ivar + " = " + cvtToStr(0) + "; " + ivar + " < "
+ cvtToStr(adtypep->elementsConst()) + "; ++" + ivar + ") {\n");
const string below
= emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(),
depth + 1, suffix + "[" + ivar + "]");
const string post = "}\n";
return below.empty() ? "" : pre + below + post;
2023-01-28 04:41:12 +01:00
} else if (VN_IS(dtypep, NodeUOrStructDType) && !VN_AS(dtypep, NodeUOrStructDType)->packed()) {
const auto* const sdtypep = VN_AS(dtypep, NodeUOrStructDType);
2022-12-21 01:22:42 +01:00
string literal;
for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) {
const std::string line = emitVarResetRecurse(
varp, constructing, varNameProtected + suffix + "." + itemp->nameProtect(),
itemp->dtypep(), depth + 1, "");
2022-12-21 01:22:42 +01:00
if (!line.empty()) literal += line;
}
return literal;
} else if (basicp && basicp->keyword() == VBasicDTypeKwd::STRING) {
if (constructing) return ""; // String's constructor deals with it
return varNameProtected + suffix + ".clear();\n";
Timing support (#3363) Adds timing support to Verilator. It makes it possible to use delays, event controls within processes (not just at the start), wait statements, and forks. Building a design with those constructs requires a compiler that supports C++20 coroutines (GCC 10, Clang 5). The basic idea is to have processes and tasks with delays/event controls implemented as C++20 coroutines. This allows us to suspend and resume them at any time. There are five main runtime classes responsible for managing suspended coroutines: * `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle` with move semantics and automatic cleanup. * `VlDelayScheduler`, for coroutines suspended by delays. It resumes them at a proper simulation time. * `VlTriggerScheduler`, for coroutines suspended by event controls. It resumes them if its corresponding trigger was set. * `VlForkSync`, used for syncing `fork..join` and `fork..join_any` blocks. * `VlCoroutine`, the return type of all verilated coroutines. It allows for suspending a stack of coroutines (normally, C++ coroutines are stackless). There is a new visitor in `V3Timing.cpp` which: * scales delays according to the timescale, * simplifies intra-assignment timing controls and net delays into regular timing controls and assignments, * simplifies wait statements into loops with event controls, * marks processes and tasks with timing controls in them as suspendable, * creates delay, trigger scheduler, and fork sync variables, * transforms timing controls and fork joins into C++ awaits There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`) that integrate static scheduling with timing. This involves providing external domains for variables, so that the necessary combinational logic gets triggered after coroutine resumption, as well as statements that need to be injected into the design eval function to perform this resumption at the correct time. There is also a function that transforms forked processes into separate functions. See the comments in `verilated_timing.h`, `verilated_timing.cpp`, `V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals documentation for more details. Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
} else if (basicp && basicp->isForkSync()) {
return "";
2023-06-01 16:02:08 +02:00
} else if (basicp && basicp->isProcessRef()) {
return "";
Timing support (#3363) Adds timing support to Verilator. It makes it possible to use delays, event controls within processes (not just at the start), wait statements, and forks. Building a design with those constructs requires a compiler that supports C++20 coroutines (GCC 10, Clang 5). The basic idea is to have processes and tasks with delays/event controls implemented as C++20 coroutines. This allows us to suspend and resume them at any time. There are five main runtime classes responsible for managing suspended coroutines: * `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle` with move semantics and automatic cleanup. * `VlDelayScheduler`, for coroutines suspended by delays. It resumes them at a proper simulation time. * `VlTriggerScheduler`, for coroutines suspended by event controls. It resumes them if its corresponding trigger was set. * `VlForkSync`, used for syncing `fork..join` and `fork..join_any` blocks. * `VlCoroutine`, the return type of all verilated coroutines. It allows for suspending a stack of coroutines (normally, C++ coroutines are stackless). There is a new visitor in `V3Timing.cpp` which: * scales delays according to the timescale, * simplifies intra-assignment timing controls and net delays into regular timing controls and assignments, * simplifies wait statements into loops with event controls, * marks processes and tasks with timing controls in them as suspendable, * creates delay, trigger scheduler, and fork sync variables, * transforms timing controls and fork joins into C++ awaits There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`) that integrate static scheduling with timing. This involves providing external domains for variables, so that the necessary combinational logic gets triggered after coroutine resumption, as well as statements that need to be injected into the design eval function to perform this resumption at the correct time. There is also a function that transforms forked processes into separate functions. See the comments in `verilated_timing.h`, `verilated_timing.cpp`, `V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals documentation for more details. Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
} else if (basicp && basicp->isDelayScheduler()) {
return "";
} else if (basicp && basicp->isTriggerScheduler()) {
return "";
} else if (basicp && basicp->isDynamicTriggerScheduler()) {
return "";
} else if (basicp && (basicp->isRandomGenerator() || basicp->isStdRandomGenerator())) {
return "";
} else if (basicp) {
const bool zeroit
= (varp->attrFileDescr() // Zero so we don't do file IO if never $fopen
|| varp->isFuncLocal() // Randomization too slow
|| (basicp && basicp->isZeroInit())
|| (v3Global.opt.underlineZero() && !varp->name().empty() && varp->name()[0] == '_')
|| (varp->isXTemp()
? (v3Global.opt.xAssign() != "unique")
: (v3Global.opt.xInitial() == "fast" || v3Global.opt.xInitial() == "0")));
const bool slow = !varp->isFuncLocal() && !varp->isClassMember();
splitSizeInc(1);
if (dtypep->isWide()) { // Handle unpacked; not basicp->isWide
string out;
if (varp->valuep()) {
const AstConst* const constp = VN_AS(varp->valuep(), Const);
UASSERT_OBJ(constp, varp, "non-const initializer for variable");
for (int w = 0; w < varp->widthWords(); ++w) {
out += varNameProtected + suffix + "[" + cvtToStr(w) + "] = ";
out += cvtToStr(constp->num().edataWord(w)) + "U;\n";
}
} else {
out += zeroit ? (slow ? "VL_ZERO_RESET_W(" : "VL_ZERO_W(")
: (varp->isXTemp() ? "VL_SCOPED_RAND_RESET_ASSIGN_W("
: "VL_SCOPED_RAND_RESET_W(");
out += cvtToStr(dtypep->widthMin());
out += ", " + varNameProtected + suffix;
if (!zeroit) {
emitVarResetScopeHash();
const uint64_t salt = VString::hashMurmur(varp->prettyName());
out += ", ";
out += m_classOrPackage ? m_classOrPackageHash : "__VscopeHash";
out += ", ";
out += std::to_string(salt);
out += "ull";
}
out += ");\n";
}
return out;
} else {
string out = varNameProtected + suffix;
Deprecate clocker attribute and --clk option (#6463) The only use for the clocker attribute and the AstVar::isUsedClock that is actually necessary today for correctness is to mark top level inputs of --lib-create blocks as being (or driving) a clock signal. Correctness of --lib-create (and hence hierarchical blocks) actually used to depend on having the right optimizations eliminate intermediate clocks (e.g.: V3Gate), when the top level port was not used directly in a sensitivity list, or marking top level signals manually via --clk or the clocker attribute. However V3Sched::partition already needs to trace through the logic to figure out what signals might drive a sensitivity list, so it can very easily mark all top level inputs as such. In this patch we remove the AstVar::attrClocker and AstVar::isUsedClock attributes, and replace them with AstVar::isPrimaryClock, automatically set by V3Sched::partition. This eliminates all need for manual annotation so we are deprecating the --clk/--no-clk options and the clocker/no_clocker attributes. This also eliminates the opportunity for any further mis-optimization similar to #6453. Regarding the other uses of the removed AstVar attributes: - As of 5.000, initial edges are triggered via a separate mechanism applied in V3Sched, so the use in V3EmitCFunc.cpp is redundant - Also as of 5.000, we can handle arbitrary sensitivity expressions, so the restriction on eliminating clock signals in V3Gate is unnecessary - Since the recent change when Dfg is applied after V3Scope, it does perform the equivalent of GateClkDecomp, so we can delete that pass.
2025-09-20 16:50:22 +02:00
if (zeroit) {
out += " = 0;\n";
} else {
emitVarResetScopeHash();
const uint64_t salt = VString::hashMurmur(varp->prettyName());
out += " = VL_SCOPED_RAND_RESET_";
if (varp->isXTemp()) out += "ASSIGN_";
out += dtypep->charIQWN();
out += "(" + cvtToStr(dtypep->widthMin()) + ", "
+ (m_classOrPackage ? m_classOrPackageHash : "__VscopeHash") + ", "
+ std::to_string(salt) + "ull);\n";
}
return out;
}
} else { // LCOV_EXCL_BR_LINE
v3fatalSrc("Unknown node type in reset generator: " << varp->prettyTypeName());
}
return "";
}
void EmitCFunc::emitVarResetScopeHash() {
if (VL_LIKELY(m_createdScopeHash)) { return; }
if (m_classOrPackage) {
m_classOrPackageHash
= std::to_string(VString::hashMurmur(m_classOrPackage->name())) + "ULL";
} else {
puts(string("const uint64_t __VscopeHash = VL_MURMUR64_HASH(")
+ (m_useSelfForThis ? "vlSelf" : "this") + "->name());\n");
}
m_createdScopeHash = true;
}