verilator/src/V3EmitCFunc.cpp

737 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
//
//*************************************************************************
//
2022-01-01 14:26:40 +01:00
// Copyright 2003-2022 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 "config_build.h"
#include "verilatedos.h"
#include "V3EmitCFunc.h"
#include "V3Global.h"
#include "V3String.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()) {
if (nodep->op1p()->isWide()) return false;
}
if (nodep->op2p()) {
if (nodep->op2p()->isWide()) return false;
}
if (nodep->op3p()) {
if (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)
putbs("");
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().empty()) {
emitDereference(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");
iterateAndNextNull(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 (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;
puts("VL_FSCANF_IX(");
iterate(dispp->filep());
puts(",");
} else if (const AstSScanF* const dispp = VN_CAST(nodep, SScanF)) {
isStmt = false;
checkMaxWords(dispp->fromp());
puts("VL_SSCANF_I");
emitIQW(dispp->fromp());
puts("X(");
puts(cvtToStr(dispp->fromp()->widthMin()));
puts(",");
iterate(dispp->fromp());
puts(",");
} else if (const AstDisplay* const dispp = VN_CAST(nodep, Display)) {
isStmt = true;
if (dispp->filep()) {
puts("VL_FWRITEF(");
iterate(dispp->filep());
puts(",");
} else {
puts("VL_WRITEF(");
}
} else if (const AstSFormat* const dispp = VN_CAST(nodep, SFormat)) {
isStmt = true;
puts("VL_SFORMAT_X(");
puts(cvtToStr(dispp->lhsp()->widthMin()));
putbs(",");
iterate(dispp->lhsp());
putbs(",");
} else if (VN_IS(nodep, SFormatF)) {
isStmt = false;
puts("VL_SFORMATF_NX(");
} else {
nodep->v3fatalSrc("Unknown displayEmit node type");
}
ofp()->putsQuoted(m_emitDispState.m_format);
// 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 == '@');
if (addrof) puts("&(");
iterate(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;
// Prep for next parameter
*elistp = (*elistp)->nextp();
if (VL_UNCOVERABLE(!argp)) {
// expectDisplay() checks this first, so internal error if found here
dispp->v3error(
"Internal: Missing arguments for $display-like format"); // LCOV_EXCL_LINE
return; // LCOV_EXCL_LINE
}
if (argp->widthMin() > VL_VALUE_STRING_MAX_WIDTH) {
dispp->v3error("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(WIDTH, 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 = string("%") + cvtToStr(nchars) + fmtLetter;
} else {
pfmt = string("%") + 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 == '^') {
const AstSFormatF* fmtp = nullptr;
if (const AstDisplay* const nodep = VN_CAST(dispp, Display)) {
fmtp = nodep->fmtp();
} else if (const AstSFormat* const nodep = VN_CAST(dispp, SFormat)) {
fmtp = nodep->fmtp();
} else {
fmtp = VN_CAST(dispp, SFormatF);
}
UASSERT_OBJ(fmtp, dispp,
"Use of %t must be under AstDisplay, AstSFormat, or AstSFormatF");
UASSERT_OBJ(!fmtp->timeunit().isNone(), fmtp, "timenunit must be set");
m_emitDispState.pushArg(' ', nullptr, cvtToStr((int)fmtp->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"
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) << endl);
if (!inPct && pos[0] == '%') {
inPct = true;
ignore = false;
vfmt = "";
} else if (!inPct) { // Normal text
m_emitDispState.pushFormat(*pos);
} else { // Format character
inPct = false;
switch (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 '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);
}
void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer) {
puts("(");
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;
}
if (!nodep->argTypes().empty()) {
if (comma) puts(", ");
puts(nodep->argTypes());
comma = true;
}
for (AstNode* subnodep = nodep->argsp(); subnodep; subnodep = subnodep->nextp()) {
if (comma) puts(", ");
iterate(subnodep);
comma = true;
}
puts(")");
}
void EmitCFunc::emitDereference(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
puts(pointer.substr(2, pointer.length() - 3));
puts(".");
} else {
puts(pointer);
puts("->");
}
}
void EmitCFunc::emitCvtPackStr(AstNode* nodep) {
if (const AstConst* const constp = VN_CAST(nodep, Const)) {
putbs("std::string{");
putsQuoted(constp->num().toString());
puts("}");
} else {
putbs("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(", ");
}
iterateAndNextNull(nodep);
puts(")");
}
}
void EmitCFunc::emitCvtWideArray(AstNode* nodep, AstNode* fromp) {
putbs("VL_CVT_W_A(");
iterate(nodep);
puts(", ");
iterate(fromp);
putbs(".atDefault()"); // Not accessed; only to get the proper type of values
puts(")");
}
void EmitCFunc::emitConstant(AstConst* nodep, AstVarRef* assigntop, const string& assignString) {
// Put out constant set to the specified variable, or given variable in a string
if (nodep->num().isNull()) {
puts("VlNull{}");
} else if (nodep->num().isFourState()) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: 4-state numbers in this context");
} else if (nodep->num().isString()) {
putbs("std::string{");
putsQuoted(nodep->num().toString());
puts("}");
} else if (nodep->isWide()) {
int upWidth = nodep->num().widthMin();
int chunks = 0;
if (upWidth > EMITC_NUM_CONSTW * VL_EDATASIZE) {
// Output e.g. 8 words in groups of e.g. 8
chunks = (upWidth - 1) / (EMITC_NUM_CONSTW * VL_EDATASIZE);
upWidth %= (EMITC_NUM_CONSTW * VL_EDATASIZE);
if (upWidth == 0) upWidth = (EMITC_NUM_CONSTW * VL_EDATASIZE);
}
{ // Upper e.g. 8 words
if (chunks) {
putbs("VL_CONSTHI_W_");
puts(cvtToStr(VL_WORDS_I(upWidth)));
puts("X(");
puts(cvtToStr(nodep->widthMin()));
puts(",");
puts(cvtToStr(chunks * EMITC_NUM_CONSTW * VL_EDATASIZE));
} else {
putbs("VL_CONST_W_");
puts(cvtToStr(VL_WORDS_I(upWidth)));
puts("X(");
puts(cvtToStr(nodep->widthMin()));
}
puts(",");
if (!assigntop) {
puts(assignString);
} else {
if (!assigntop->selfPointer().empty()) {
emitDereference(assigntop->selfPointerProtect(m_useSelfForThis));
}
puts(assigntop->varp()->nameProtect());
}
for (int word = VL_WORDS_I(upWidth) - 1; word >= 0; word--) {
// Only 32 bits - llx + long long here just to appease CPP format warning
ofp()->printf(",0x%08" PRIx64, static_cast<uint64_t>(nodep->num().edataWord(
word + chunks * EMITC_NUM_CONSTW)));
}
puts(")");
}
for (chunks--; chunks >= 0; chunks--) {
puts(";\n");
putbs("VL_CONSTLO_W_");
puts(cvtToStr(EMITC_NUM_CONSTW));
puts("X(");
puts(cvtToStr(chunks * EMITC_NUM_CONSTW * VL_EDATASIZE));
puts(",");
if (!assigntop) {
puts(assignString);
} else {
if (!assigntop->selfPointer().empty()) {
emitDereference(assigntop->selfPointerProtect(m_useSelfForThis));
}
puts(assigntop->varp()->nameProtect());
}
for (int word = EMITC_NUM_CONSTW - 1; word >= 0; word--) {
// Only 32 bits - llx + long long here just to appease CPP format warning
ofp()->printf(",0x%08" PRIx64, static_cast<uint64_t>(nodep->num().edataWord(
word + chunks * EMITC_NUM_CONSTW)));
}
puts(")");
}
} else if (nodep->isDouble()) {
if (int(nodep->num().toDouble()) == nodep->num().toDouble()
&& nodep->num().toDouble() < 1000 && nodep->num().toDouble() > -1000) {
ofp()->printf("%3.1f", nodep->num().toDouble()); // Force decimal point
} else {
// Not %g as will not always put in decimal point, so not obvious to compiler
// is a real number
ofp()->printf("%.17e", nodep->num().toDouble());
}
} else if (nodep->isQuad()) {
const uint64_t num = nodep->toUQuad();
if (num < 10) {
ofp()->printf("%" PRIu64 "ULL", num);
} else {
ofp()->printf("0x%" PRIx64 "ULL", num);
}
} else {
const uint32_t num = nodep->toUInt();
// Only 32 bits - llx + long long here just to appease CPP format warning
if (num < 10) {
puts(cvtToStr(num));
} else {
ofp()->printf("0x%" PRIx64, static_cast<uint64_t>(num));
}
// If signed, we'll do our own functions
// But must be here, or <= comparisons etc may end up signed
puts("U");
}
}
void EmitCFunc::emitSetVarConstant(const string& assignString, AstConst* constp) {
if (!constp->isWide()) {
puts(assignString);
puts(" = ");
}
emitConstant(constp, nullptr, assignString);
puts(";\n");
}
void EmitCFunc::emitVarReset(AstVar* varp) {
AstNodeDType* const dtypep = varp->dtypep()->skipRefp();
const string varNameProtected
= VN_IS(m_modp, Class) ? 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));
}
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));
}
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 {
puts(emitVarResetRecurse(varp, varNameProtected, dtypep, 0, ""));
}
}
string EmitCFunc::emitVarResetRecurse(const AstVar* varp, 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()" : "");
return emitVarResetRecurse(varp, 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()" : "");
return emitVarResetRecurse(varp, varNameProtected, adtypep->subDTypep(), depth + 1,
suffix + ".atDefault()" + cvtarray);
} 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()" : "");
return emitVarResetRecurse(varp, 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()" : "");
return emitVarResetRecurse(varp, varNameProtected, adtypep->subDTypep(), depth + 1,
suffix + ".atDefault()" + cvtarray);
} 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 = string("__Vi") + cvtToStr(depth);
const string pre = ("for (int " + ivar + " = " + cvtToStr(0) + "; " + ivar + " < "
+ cvtToStr(adtypep->elementsConst()) + "; ++" + ivar + ") {\n");
const string below = emitVarResetRecurse(varp, varNameProtected, adtypep->subDTypep(),
depth + 1, suffix + "[" + ivar + "]");
const string post = "}\n";
return below.empty() ? "" : pre + below + post;
} else if (basicp && basicp->keyword() == VBasicDTypeKwd::STRING) {
// String's constructor deals with it
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->isForkSync()) {
return "";
} else if (basicp && basicp->isDelayScheduler()) {
return "";
} else if (basicp && basicp->isTriggerScheduler()) {
return "";
} else if (basicp && basicp->isDynamicTriggerScheduler()) {
return "";
} else if (basicp) {
const bool zeroit
= (varp->attrFileDescr() // Zero so we don't do file IO if never $fopen
|| (basicp && basicp->isZeroInit())
|| (v3Global.opt.underlineZero() && !varp->name().empty() && varp->name()[0] == '_')
|| (v3Global.opt.xInitial() == "fast" || v3Global.opt.xInitial() == "0"));
splitSizeInc(1);
if (dtypep->isWide()) { // Handle unpacked; not basicp->isWide
string out;
if (varp->valuep()) {
const AstConst* const constp = VN_AS(varp->valuep(), Const);
if (!constp) varp->v3fatalSrc("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 ? "VL_ZERO_RESET_W(" : "VL_RAND_RESET_W(";
out += cvtToStr(dtypep->widthMin());
out += ", " + varNameProtected + suffix + ");\n";
}
return out;
} else {
string out = varNameProtected + suffix;
// If --x-initial-edge is set, we want to force an initial
// edge on uninitialized clocks (from 'X' to whatever the
// first value is). Since the class is instantiated before
// initial blocks are evaluated, this should not clash
// with any initial block settings.
if (zeroit || (v3Global.opt.xInitialEdge() && varp->isUsedClock())) {
out += " = 0;\n";
} else {
out += " = VL_RAND_RESET_";
out += dtypep->charIQWN();
out += "(" + cvtToStr(dtypep->widthMin()) + ");\n";
}
return out;
}
} else {
v3fatalSrc("Unknown node type in reset generator: " << varp->prettyTypeName());
}
return "";
}