verilator/src/V3AstNodes.cpp

2321 lines
83 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Ast node structures
//
2019-11-08 04:33:59 +01:00
// 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 "V3Ast.h"
#include "V3EmitCBase.h"
#include "V3File.h"
#include "V3Global.h"
#include "V3Graph.h"
#include "V3Hasher.h"
#include "V3PartitionGraph.h" // Just for mtask dumping
#include "V3String.h"
#include "V3Ast__gen_macros.h" // Generated by 'astgen'
#include <iomanip>
#include <iterator>
#include <vector>
//======================================================================
// Special methods
// We need these here, because the classes they point to aren't defined when we declare the class
const char* AstIfaceRefDType::broken() const {
BROKEN_RTN(m_ifacep && !m_ifacep->brokeExists());
BROKEN_RTN(m_cellp && !m_cellp->brokeExists());
BROKEN_RTN(m_modportp && !m_modportp->brokeExists());
return nullptr;
}
AstIface* AstIfaceRefDType::ifaceViaCellp() const {
return ((m_cellp && m_cellp->modp()) ? VN_AS(m_cellp->modp(), Iface) : m_ifacep);
}
const char* AstNodeFTaskRef::broken() const {
BROKEN_RTN(m_taskp && !m_taskp->brokeExists());
BROKEN_RTN(m_classOrPackagep && !m_classOrPackagep->brokeExists());
return nullptr;
}
void AstNodeFTaskRef::cloneRelink() {
if (m_taskp && m_taskp->clonep()) m_taskp = m_taskp->clonep();
if (m_classOrPackagep && m_classOrPackagep->clonep()) {
m_classOrPackagep = m_classOrPackagep->clonep();
}
}
bool AstNodeFTaskRef::isGateOptimizable() const { return m_taskp && m_taskp->isGateOptimizable(); }
const char* AstNodeVarRef::broken() const {
BROKEN_RTN(m_varp && !m_varp->brokeExists());
BROKEN_RTN(m_varScopep && !m_varScopep->brokeExists());
BROKEN_RTN(m_classOrPackagep && !m_classOrPackagep->brokeExists());
return nullptr;
}
void AstNodeVarRef::cloneRelink() {
if (m_varp && m_varp->clonep()) m_varp = m_varp->clonep();
if (m_varScopep && m_varScopep->clonep()) m_varScopep = m_varScopep->clonep();
if (m_classOrPackagep && m_classOrPackagep->clonep()) {
m_classOrPackagep = m_classOrPackagep->clonep();
}
}
string AstNodeVarRef::selfPointerProtect(bool useSelfForThis) const {
const string& sp
= useSelfForThis ? VString::replaceWord(selfPointer(), "this", "vlSelf") : selfPointer();
return VIdProtect::protectWordsIf(sp, protect());
}
void AstAddrOfCFunc::cloneRelink() {
if (m_funcp && m_funcp->clonep()) m_funcp = m_funcp->clonep();
}
const char* AstAddrOfCFunc::broken() const {
BROKEN_RTN(m_funcp && !m_funcp->brokeExists());
return nullptr;
}
int AstNodeSel::bitConst() const {
const AstConst* const constp = VN_AS(bitp(), Const);
return (constp ? constp->toSInt() : 0);
}
void AstNodeUOrStructDType::repairMemberCache() {
clearCache();
for (AstMemberDType* itemp = membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) {
if (m_members.find(itemp->name()) != m_members.end()) {
itemp->v3error("Duplicate declaration of member name: " << itemp->prettyNameQ());
} else {
m_members.emplace(itemp->name(), itemp);
}
}
}
const char* AstNodeUOrStructDType::broken() const {
std::unordered_set<AstMemberDType*> exists;
for (AstMemberDType* itemp = membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) {
exists.insert(itemp);
}
for (MemberNameMap::const_iterator it = m_members.begin(); it != m_members.end(); ++it) {
if (VL_UNCOVERABLE(exists.find(it->second) == exists.end())) {
this->v3error("Internal: Structure member broken: " << it->first);
return "member broken";
}
}
return nullptr;
}
void AstNodeStmt::dump(std::ostream& str) const { this->AstNode::dump(str); }
void AstNodeCCall::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
if (funcp()) {
str << " " << funcp()->name() << " => ";
funcp()->dump(str);
} else {
str << " " << name();
}
}
void AstNodeCCall::cloneRelink() {
if (m_funcp && m_funcp->clonep()) m_funcp = m_funcp->clonep();
}
const char* AstNodeCCall::broken() const {
BROKEN_RTN(m_funcp && !m_funcp->brokeExists());
return nullptr;
}
bool AstNodeCCall::isPure() const { return funcp()->pure(); }
string AstCCall::selfPointerProtect(bool useSelfForThis) const {
const string& sp
= useSelfForThis ? VString::replaceWord(selfPointer(), "this", "vlSelf") : selfPointer();
return VIdProtect::protectWordsIf(sp, protect());
}
void AstNodeCond::numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
const V3Number& ths) {
if (lhs.isNeqZero()) {
out.opAssign(rhs);
} else {
out.opAssign(ths);
}
}
void AstBasicDType::init(VBasicDTypeKwd kwd, VSigning numer, int wantwidth, int wantwidthmin,
AstRange* rangep) {
// wantwidth=0 means figure it out, but if a widthmin is >=0
// we allow width 0 so that {{0{x}},y} works properly
// wantwidthmin=-1: default, use wantwidth if it is non zero
m.m_keyword = kwd;
// Implicitness: // "parameter X" is implicit and sized from initial
// value, "parameter reg x" not
if (keyword() == VBasicDTypeKwd::LOGIC_IMPLICIT) {
if (rangep || wantwidth) m.m_keyword = VBasicDTypeKwd::LOGIC;
}
if (numer == VSigning::NOSIGN) {
if (keyword().isSigned()) {
numer = VSigning::SIGNED;
} else if (keyword().isUnsigned()) {
numer = VSigning::UNSIGNED;
}
}
numeric(numer);
if (!rangep && (wantwidth || wantwidthmin >= 0)) { // Constant width
if (wantwidth > 1) m.m_nrange.init(wantwidth - 1, 0, false);
const int wmin = wantwidthmin >= 0 ? wantwidthmin : wantwidth;
widthForce(wantwidth, wmin);
} else if (!rangep) { // Set based on keyword properties
// V3Width will pull from this width
if (keyword().width() > 1 && !isOpaque()) {
m.m_nrange.init(keyword().width() - 1, 0, false);
}
widthForce(keyword().width(), keyword().width());
} else {
widthForce(rangep->elementsConst(),
rangep->elementsConst()); // Maybe unknown if parameters underneath it
}
this->rangep(rangep);
this->dtypep(this);
}
void AstBasicDType::cvtRangeConst() {
if (rangep() && VN_IS(rangep()->leftp(), Const) && VN_IS(rangep()->rightp(), Const)) {
m.m_nrange = VNumRange{rangep()->leftConst(), rangep()->rightConst()};
rangep()->unlinkFrBackWithNext()->deleteTree();
rangep(nullptr);
}
}
int AstBasicDType::widthAlignBytes() const {
if (width() <= 8) {
return 1;
} else if (width() <= 16) {
return 2;
} else if (isQuad()) {
return 8;
} else {
return 4;
}
}
int AstBasicDType::widthTotalBytes() const {
if (width() <= 8) {
return 1;
} else if (width() <= 16) {
return 2;
} else if (isQuad()) {
return 8;
} else {
return widthWords() * (VL_EDATASIZE / 8);
}
}
int AstNodeUOrStructDType::widthTotalBytes() const {
if (width() <= 8) {
return 1;
} else if (width() <= 16) {
return 2;
} else if (isQuad()) {
return 8;
} else {
return widthWords() * (VL_EDATASIZE / 8);
}
}
int AstNodeUOrStructDType::widthAlignBytes() const {
// Could do max across members but that would be slow,
// instead intuit based on total structure size
if (width() <= 8) {
return 1;
} else if (width() <= 16) {
return 2;
} else if (width() <= 32) {
return 4;
} else {
return 8;
}
}
2014-01-21 03:59:53 +01:00
AstNodeBiop* AstEq::newTyped(FileLine* fl, AstNode* lhsp, AstNode* rhsp) {
if (lhsp->isString() && rhsp->isString()) {
return new AstEqN(fl, lhsp, rhsp);
} else if (lhsp->isDouble() && rhsp->isDouble()) {
return new AstEqD(fl, lhsp, rhsp);
2014-01-21 03:59:53 +01:00
} else {
return new AstEq(fl, lhsp, rhsp);
2014-01-21 03:59:53 +01:00
}
}
AstNodeBiop* AstEqWild::newTyped(FileLine* fl, AstNode* lhsp, AstNode* rhsp) {
if (lhsp->isString() && rhsp->isString()) {
return new AstEqN(fl, lhsp, rhsp);
} else if (lhsp->isDouble() && rhsp->isDouble()) {
return new AstEqD(fl, lhsp, rhsp);
} else {
return new AstEqWild(fl, lhsp, rhsp);
}
}
2014-01-21 03:59:53 +01:00
AstExecGraph::AstExecGraph(FileLine* fileline, const string& name)
: ASTGEN_SUPER_ExecGraph(fileline)
, m_depGraphp{new V3Graph}
, m_name{name} {}
AstExecGraph::~AstExecGraph() { VL_DO_DANGLING(delete m_depGraphp, m_depGraphp); }
AstNode* AstInsideRange::newAndFromInside(AstNode* exprp, AstNode* lhsp, AstNode* rhsp) {
AstNode* const ap = new AstGte(fileline(), exprp->cloneTree(true), lhsp);
AstNode* const bp = new AstLte(fileline(), exprp->cloneTree(true), rhsp);
ap->fileline()->modifyWarnOff(V3ErrorCode::UNSIGNED, true);
bp->fileline()->modifyWarnOff(V3ErrorCode::CMPCONST, true);
AstNode* const newp = new AstAnd(fileline(), ap, bp);
return newp;
}
AstConst* AstConst::parseParamLiteral(FileLine* fl, const string& literal) {
bool success = false;
if (literal[0] == '"') {
// This is a string
const string v = literal.substr(1, literal.find('"', 1) - 1);
return new AstConst(fl, AstConst::VerilogStringLiteral(), v);
} else if (literal.find_first_of(".eEpP") != string::npos) {
// This may be a real
const double v = VString::parseDouble(literal, &success);
if (success) return new AstConst(fl, AstConst::RealDouble(), v);
}
if (!success) {
// This is either an integer or an error
// We first try to convert it as C literal. If strtol returns
// 0 this is either an error or 0 was parsed. But in any case
// we will try to parse it as a verilog literal, hence having
// the false negative for 0 is okay. If anything remains in
// the string after the number, this is invalid C and we try
// the Verilog literal parser.
char* endp;
const int v = strtol(literal.c_str(), &endp, 0);
if ((v != 0) && (endp[0] == 0)) { // C literal
return new AstConst(fl, AstConst::Signed32(), v);
} else { // Try a Verilog literal (fatals if not)
return new AstConst(fl, AstConst::StringToParse(), literal.c_str());
}
}
return nullptr;
}
AstNetlist::AstNetlist()
: ASTGEN_SUPER_Netlist(new FileLine(FileLine::builtInFilename()))
, m_typeTablep{new AstTypeTable(fileline())}
, m_constPoolp{new AstConstPool(fileline())} {
addMiscsp(m_typeTablep);
addMiscsp(m_constPoolp);
}
void AstNetlist::timeprecisionMerge(FileLine*, const VTimescale& value) {
const VTimescale prec = v3Global.opt.timeComputePrec(value);
if (prec.isNone() || prec == m_timeprecision) {
} else if (m_timeprecision.isNone()) {
m_timeprecision = prec;
} else if (prec < m_timeprecision) {
m_timeprecision = prec;
}
}
bool AstVar::isSigPublic() const {
return (m_sigPublic || (v3Global.opt.allPublic() && !isTemp() && !isGenVar()));
}
bool AstVar::isScQuad() const { return (isSc() && isQuad() && !isScBv() && !isScBigUint()); }
bool AstVar::isScBv() const {
return ((isSc() && width() >= v3Global.opt.pinsBv()) || m_attrScBv);
}
bool AstVar::isScUint() const {
return ((isSc() && v3Global.opt.pinsScUint() && width() >= 2 && width() <= 64) && !isScBv());
}
bool AstVar::isScBigUint() const {
return ((isSc() && v3Global.opt.pinsScBigUint() && width() >= 65 && width() <= 512)
&& !isScBv());
}
void AstVar::combineType(VVarType type) {
// These flags get combined with the existing settings of the flags.
// We don't test varType for certain types, instead set flags since
// when we combine wires cross-hierarchy we need a union of all characteristics.
m_varType = type;
// These flags get combined with the existing settings of the flags.
if (type == VVarType::TRIWIRE || type == VVarType::TRI0 || type == VVarType::TRI1) {
m_tristate = true;
}
if (type == VVarType::TRI0) m_isPulldown = true;
if (type == VVarType::TRI1) m_isPullup = true;
}
string AstVar::verilogKwd() const {
if (isIO()) {
return direction().verilogKwd();
} else if (isTristate()) {
return "tri";
} else if (varType() == VVarType::WIRE) {
return "wire";
} else if (varType() == VVarType::WREAL) {
return "wreal";
} else if (varType() == VVarType::IFACEREF) {
return "ifaceref";
} else {
return dtypep()->name();
}
}
string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string& namespc,
bool asRef) const VL_MT_SAFE {
UASSERT_OBJ(!forReturn, this,
"Internal data is never passed as return, but as first argument");
string ostatic;
if (isStatic() && namespc.empty()) ostatic = "static ";
const bool isRef = isDpiOpenArray()
|| (forFunc && (isWritable() || direction().isRefOrConstRef())) || asRef;
if (forFunc && isReadOnly() && isRef) ostatic = ostatic + "const ";
string oname;
if (named) {
if (!namespc.empty()) oname += namespc + "::";
oname += VIdProtect::protectIf(name(), protect());
}
return ostatic + dtypep()->cType(oname, forFunc, isRef);
}
string AstVar::vlEnumType() const {
string arg;
const AstBasicDType* const bdtypep = basicp();
const bool strtype = bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING;
if (bdtypep && bdtypep->keyword() == VBasicDTypeKwd::CHARPTR) {
return "VLVT_PTR";
} else if (bdtypep && bdtypep->keyword() == VBasicDTypeKwd::SCOPEPTR) {
return "VLVT_PTR";
} else if (strtype) {
arg += "VLVT_STRING";
} else if (widthMin() <= 8) {
arg += "VLVT_UINT8";
} else if (widthMin() <= 16) {
arg += "VLVT_UINT16";
} else if (widthMin() <= VL_IDATASIZE) {
arg += "VLVT_UINT32";
} else if (isQuad()) {
arg += "VLVT_UINT64";
} else if (isWide()) {
arg += "VLVT_WDATA";
}
// else return "VLVT_UNKNOWN"
return arg;
}
string AstVar::vlEnumDir() const {
string out;
if (isInoutish()) {
out = "VLVD_INOUT";
} else if (isWritable()) {
out = "VLVD_OUT";
} else if (isNonOutput()) {
out = "VLVD_IN";
} else {
out = "VLVD_NODIR";
}
//
if (isSigUserRWPublic()) {
out += "|VLVF_PUB_RW";
} else if (isSigUserRdPublic()) {
out += "|VLVF_PUB_RD";
}
//
if (const AstBasicDType* const bdtypep = basicp()) {
if (bdtypep->keyword().isDpiCLayout()) out += "|VLVF_DPI_CLAY";
}
return out;
}
string AstVar::vlPropDecl(const string& propName) const {
string out;
std::vector<int> ulims; // Unpacked dimension limits
for (const AstNodeDType* dtp = dtypep(); dtp;) {
dtp = dtp->skipRefp(); // Skip AstRefDType/AstTypedef, or return same node
if (const AstNodeArrayDType* const adtypep = VN_CAST(dtp, NodeArrayDType)) {
ulims.push_back(adtypep->declRange().left());
ulims.push_back(adtypep->declRange().right());
dtp = adtypep->subDTypep();
} else {
break; // AstBasicDType - nothing below
}
}
if (!ulims.empty()) {
out += "static const int " + propName + "__ulims[";
out += cvtToStr(ulims.size());
out += "] = {";
auto it = ulims.cbegin();
out += cvtToStr(*it);
while (++it != ulims.cend()) {
out += ", ";
out += cvtToStr(*it);
}
out += "};\n";
}
out += "static const VerilatedVarProps ";
out += propName;
out += "(";
out += vlEnumType(); // VLVT_UINT32 etc
out += ", " + vlEnumDir(); // VLVD_IN etc
if (const AstBasicDType* const bdtypep = basicp()) {
out += ", VerilatedVarProps::Packed()";
out += ", " + cvtToStr(bdtypep->left());
out += ", " + cvtToStr(bdtypep->right());
}
if (!ulims.empty()) {
out += ", VerilatedVarProps::Unpacked()";
out += ", " + cvtToStr(ulims.size() / 2);
out += ", " + propName + "__ulims";
}
out += ");\n";
return out;
}
string AstVar::cPubArgType(bool named, bool forReturn) const {
if (forReturn) named = false;
string arg;
if (isWide() && isReadOnly()) arg += "const ";
const bool isRef = !forReturn && (isWritable() || direction().isRefOrConstRef());
if (VN_IS(dtypeSkipRefp(), BasicDType) && !dtypeSkipRefp()->isDouble()
&& !dtypeSkipRefp()->isString()) {
// Backward compatible type declaration
if (widthMin() == 1) {
arg += "bool";
} else if (widthMin() <= VL_IDATASIZE) {
arg += "uint32_t";
} else if (widthMin() <= VL_QUADSIZE) {
arg += "uint64_t";
} else {
arg += "uint32_t"; // []'s added later
}
if (isWide()) {
if (forReturn) {
v3warn(E_UNSUPPORTED, "Unsupported: Public functions with >64 bit outputs; "
"make an output of a public task instead");
}
arg += " (& " + name();
arg += ")[" + cvtToStr(widthWords()) + "]";
} else {
if (isRef) arg += "&";
if (named) arg += " " + name();
}
} else {
// Newer internal-compatible types
arg += dtypep()->cType((named ? name() : std::string{}), true, isRef);
}
return arg;
}
class dpiTypesToStringConverter VL_NOT_FINAL {
public:
virtual string openArray(const AstVar*) const { return "const svOpenArrayHandle"; }
virtual string bitLogicVector(const AstVar* /*varp*/, bool isBit) const {
return isBit ? "svBitVecVal" : "svLogicVecVal";
}
virtual string primitive(const AstVar* varp) const {
string type;
const VBasicDTypeKwd keyword = varp->basicp()->keyword();
if (keyword.isDpiUnsignable() && !varp->basicp()->isSigned()) type = "unsigned ";
type += keyword.dpiType();
return type;
}
string convert(const AstVar* varp) const {
if (varp->isDpiOpenArray()) {
return openArray(varp);
} else if (const AstBasicDType* const basicp = varp->basicp()) {
if (basicp->isDpiBitVec() || basicp->isDpiLogicVec()) {
return bitLogicVector(varp, basicp->isDpiBitVec());
} else {
return primitive(varp);
}
} else {
return "UNKNOWN";
}
}
};
string AstVar::dpiArgType(bool named, bool forReturn) const {
if (forReturn) {
return dpiTypesToStringConverter{}.convert(this);
} else {
class converter final : public dpiTypesToStringConverter {
string bitLogicVector(const AstVar* varp, bool isBit) const override {
return string(varp->isReadOnly() ? "const " : "")
+ dpiTypesToStringConverter::bitLogicVector(varp, isBit) + '*';
}
string primitive(const AstVar* varp) const override {
string type = dpiTypesToStringConverter::primitive(varp);
if (varp->isWritable() || VN_IS(varp->dtypep()->skipRefp(), UnpackArrayDType)) {
if (!varp->isWritable() && varp->basicp()->keyword() != VBasicDTypeKwd::STRING)
type = "const " + type;
type += "*";
}
return type;
}
};
string arg = converter{}.convert(this);
if (named) arg += " " + name();
return arg;
}
}
string AstVar::dpiTmpVarType(const string& varName) const {
class converter final : public dpiTypesToStringConverter {
const string m_name;
string arraySuffix(const AstVar* varp, size_t n) const {
if (AstUnpackArrayDType* const unpackp
= VN_CAST(varp->dtypep()->skipRefp(), UnpackArrayDType)) {
// Convert multi dimensional unpacked array to 1D array
if (n == 0) n = 1;
n *= unpackp->arrayUnpackedElements();
return '[' + cvtToStr(n) + ']';
} else if (n > 0) {
return '[' + cvtToStr(n) + ']';
} else {
return "";
}
}
string openArray(const AstVar* varp) const override {
return dpiTypesToStringConverter::openArray(varp) + ' ' + m_name
+ arraySuffix(varp, 0);
}
string bitLogicVector(const AstVar* varp, bool isBit) const override {
string type = dpiTypesToStringConverter::bitLogicVector(varp, isBit);
type += ' ' + m_name + arraySuffix(varp, varp->widthWords());
return type;
}
string primitive(const AstVar* varp) const override {
string type = dpiTypesToStringConverter::primitive(varp);
if (varp->isWritable() || VN_IS(varp->dtypep()->skipRefp(), UnpackArrayDType)) {
if (!varp->isWritable() && varp->basicp()->keyword() == VBasicDTypeKwd::CHANDLE)
type = "const " + type;
}
type += ' ' + m_name + arraySuffix(varp, 0);
return type;
}
public:
explicit converter(const string& name)
: m_name(name) {}
};
return converter{varName}.convert(this);
}
string AstVar::scType() const {
if (isScBigUint()) {
return (string("sc_biguint<") + cvtToStr(widthMin())
+ "> "); // Keep the space so don't get >>
} else if (isScUint()) {
return (string("sc_uint<") + cvtToStr(widthMin())
+ "> "); // Keep the space so don't get >>
} else if (isScBv()) {
return (string("sc_bv<") + cvtToStr(widthMin()) + "> "); // Keep the space so don't get >>
} else if (widthMin() == 1) {
return "bool";
} else if (widthMin() <= VL_IDATASIZE) {
if (widthMin() <= 8 && v3Global.opt.pinsUint8()) {
return "uint8_t";
} else if (widthMin() <= 16 && v3Global.opt.pinsUint8()) {
return "uint16_t";
} else {
return "uint32_t";
}
} else {
return "uint64_t";
}
}
AstVar* AstVar::scVarRecurse(AstNode* nodep) {
// See if this is a SC assignment; if so return that type
// Historically sc variables are identified by a variable
// attribute. TODO it would better be a data type attribute.
if (AstVar* const anodep = VN_CAST(nodep, Var)) {
if (anodep->isSc()) {
return anodep;
} else {
return nullptr;
}
} else if (AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) {
if (vrefp->varp()->isSc()) {
return vrefp->varp();
} else {
return nullptr;
}
} else if (AstArraySel* const arraySelp = VN_CAST(nodep, ArraySel)) {
if (AstVar* const p = scVarRecurse(arraySelp->fromp())) return p;
if (AstVar* const p = scVarRecurse(arraySelp->bitp())) return p;
}
return nullptr;
}
bool AstNodeDType::isFourstate() const { return basicp()->isFourstate(); }
class AstNodeDType::CTypeRecursed final {
public:
string m_type; // The base type, e.g.: "Foo_t"s
string m_dims; // Array dimensions, e.g.: "[3][2][1]"
string render(const string& name, bool isRef) const VL_MT_SAFE {
string out;
out += m_type;
if (!name.empty()) out += " ";
if (isRef) {
if (!m_dims.empty()) out += "(";
out += "&";
out += name;
if (!m_dims.empty()) out += ")";
} else {
out += name;
}
out += m_dims;
return out;
}
};
string AstNodeDType::cType(const string& name, bool /*forFunc*/, bool isRef) const VL_MT_SAFE {
const CTypeRecursed info = cTypeRecurse(false);
return info.render(name, isRef);
}
AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const {
// Legacy compound argument currently just passed through and unused
CTypeRecursed info;
const AstNodeDType* const dtypep = this->skipRefp();
if (const auto* const adtypep = VN_CAST(dtypep, AssocArrayDType)) {
const CTypeRecursed key = adtypep->keyDTypep()->cTypeRecurse(true);
const CTypeRecursed val = adtypep->subDTypep()->cTypeRecurse(true);
info.m_type = "VlAssocArray<" + key.m_type + ", " + val.m_type + ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) {
const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true);
info.m_type = "VlAssocArray<std::string, " + sub.m_type + ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, DynArrayDType)) {
const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true);
info.m_type = "VlQueue<" + sub.m_type + ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, QueueDType)) {
const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true);
info.m_type = "VlQueue<" + sub.m_type;
// + 1 below as VlQueue uses 0 to mean unlimited, 1 to mean size() max is 1
if (adtypep->boundp()) info.m_type += ", " + cvtToStr(adtypep->boundConst() + 1);
info.m_type += ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, ClassRefDType)) {
info.m_type = "VlClassRef<" + EmitCBaseVisitor::prefixNameProtect(adtypep) + ">";
2022-10-20 12:31:00 +02:00
} else if (const auto* const adtypep = VN_CAST(dtypep, IfaceRefDType)) {
info.m_type = EmitCBaseVisitor::prefixNameProtect(adtypep->ifaceViaCellp()) + "*";
} else if (const auto* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
2020-12-13 01:19:16 +01:00
if (adtypep->isCompound()) compound = true;
const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(compound);
info.m_type = "VlUnpacked<" + sub.m_type;
info.m_type += ", " + cvtToStr(adtypep->declRange().elements());
info.m_type += ">";
} else if (const AstBasicDType* const bdtypep = dtypep->basicp()) {
// We don't print msb()/lsb() as multidim packed would require recursion,
// and may confuse users as C++ data is stored always with bit 0 used
const string bitvec = (!bdtypep->isOpaque() && !v3Global.opt.protectIds())
? "/*" + cvtToStr(dtypep->width() - 1) + ":0*/"
: "";
if (bdtypep->keyword() == VBasicDTypeKwd::CHARPTR) {
info.m_type = "const char*";
} else if (bdtypep->keyword() == VBasicDTypeKwd::SCOPEPTR) {
info.m_type = "const VerilatedScope*";
} else if (bdtypep->keyword().isDouble()) {
info.m_type = "double";
} else if (bdtypep->keyword().isString()) {
info.m_type = "std::string";
} else if (bdtypep->keyword().isMTaskState()) {
info.m_type = "VlMTaskVertex";
} else if (bdtypep->isTriggerVec()) {
info.m_type = "VlTriggerVec<" + cvtToStr(dtypep->width()) + ">";
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 (bdtypep->isDelayScheduler()) {
info.m_type = "VlDelayScheduler";
} else if (bdtypep->isTriggerScheduler()) {
info.m_type = "VlTriggerScheduler";
} else if (bdtypep->isDynamicTriggerScheduler()) {
info.m_type = "VlDynamicTriggerScheduler";
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 (bdtypep->isForkSync()) {
info.m_type = "VlForkSync";
} else if (bdtypep->isEvent()) {
info.m_type = "VlEvent";
} else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width
info.m_type = "CData" + bitvec;
} else if (dtypep->widthMin() <= 16) {
info.m_type = "SData" + bitvec;
} else if (dtypep->widthMin() <= VL_IDATASIZE) {
info.m_type = "IData" + bitvec;
} else if (dtypep->isQuad()) {
info.m_type = "QData" + bitvec;
} else if (dtypep->isWide()) {
info.m_type = "VlWide<" + cvtToStr(dtypep->widthWords()) + ">" + bitvec;
}
} else {
v3fatalSrc("Unknown data type in var type emitter: " << dtypep->prettyName());
}
UASSERT_OBJ(!compound || info.m_dims.empty(), this, "Declaring C array inside compound type");
return info;
}
uint32_t AstNodeDType::arrayUnpackedElements() {
uint32_t entries = 1;
for (AstNodeDType* dtypep = this; dtypep;) {
dtypep = dtypep->skipRefp(); // Skip AstRefDType/AstTypedef, or return same node
if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
entries *= adtypep->elementsConst();
dtypep = adtypep->subDTypep();
} else {
// AstBasicDType - nothing below, 1
break;
}
}
return entries;
}
std::pair<uint32_t, uint32_t> AstNodeDType::dimensions(bool includeBasic) {
// How many array dimensions (packed,unpacked) does this Var have?
uint32_t packed = 0;
uint32_t unpacked = 0;
for (AstNodeDType* dtypep = this; dtypep;) {
dtypep = dtypep->skipRefp(); // Skip AstRefDType/AstTypedef, or return same node
if (const AstNodeArrayDType* const adtypep = VN_CAST(dtypep, NodeArrayDType)) {
if (VN_IS(adtypep, PackArrayDType)) {
++packed;
} else {
++unpacked;
}
dtypep = adtypep->subDTypep();
continue;
} else if (const AstQueueDType* const qdtypep = VN_CAST(dtypep, QueueDType)) {
unpacked++;
dtypep = qdtypep->subDTypep();
continue;
} else if (const AstBasicDType* const adtypep = VN_CAST(dtypep, BasicDType)) {
if (includeBasic && (adtypep->isRanged() || adtypep->isString())) packed++;
} else if (VN_IS(dtypep, StructDType)) {
packed++;
}
break;
}
return std::make_pair(packed, unpacked);
}
int AstNodeDType::widthPow2() const {
// I.e. width 30 returns 32, width 32 returns 32.
const uint32_t width = this->width();
for (int p2 = 30; p2 >= 0; p2--) {
if (width > (1UL << p2)) return (1UL << (p2 + 1));
}
return 1;
}
bool AstNodeDType::isLiteralType() const VL_MT_SAFE {
if (const auto* const dtypep = VN_CAST(skipRefp(), BasicDType)) {
return dtypep->keyword().isLiteralType();
} else if (const auto* const dtypep = VN_CAST(skipRefp(), UnpackArrayDType)) {
return dtypep->basicp()->isLiteralType();
} else if (const auto* const dtypep = VN_CAST(skipRefp(), StructDType)) {
// Currently all structs are packed, later this can be expanded to
// 'forall members _.isLiteralType()'
return dtypep->packed();
} else {
return false;
}
}
/// What is the base variable (or const) this dereferences?
AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) {
// Else AstArraySel etc; search for the base
while (nodep) {
if (VN_IS(nodep, ArraySel)) {
nodep = VN_AS(nodep, ArraySel)->fromp();
continue;
} else if (VN_IS(nodep, Sel)) {
nodep = VN_AS(nodep, Sel)->fromp();
continue;
} else if (overMembers && VN_IS(nodep, MemberSel)) {
nodep = VN_AS(nodep, MemberSel)->fromp();
continue;
}
// AstNodeSelPre stashes the associated variable under an ATTROF
// of VAttrType::VAR_BASE/MEMBER_BASE so it isn't constified
else if (VN_IS(nodep, AttrOf)) {
nodep = VN_AS(nodep, AttrOf)->fromp();
continue;
} else if (VN_IS(nodep, NodePreSel)) {
if (VN_AS(nodep, NodePreSel)->attrp()) {
nodep = VN_AS(nodep, NodePreSel)->attrp();
} else {
nodep = VN_AS(nodep, NodePreSel)->fromp();
}
continue;
} else {
break;
}
}
return nodep;
}
const char* AstJumpBlock::broken() const {
BROKEN_RTN(!labelp()->brokeExistsBelow());
return nullptr;
}
void AstJumpBlock::cloneRelink() {
if (m_labelp->clonep()) m_labelp = m_labelp->clonep();
}
const char* AstScope::broken() const {
BROKEN_RTN(m_aboveScopep && !m_aboveScopep->brokeExists());
BROKEN_RTN(m_aboveCellp && !m_aboveCellp->brokeExists());
BROKEN_RTN(!m_modp);
BROKEN_RTN(m_modp && !m_modp->brokeExists());
return nullptr;
}
void AstScope::cloneRelink() {
if (m_aboveScopep && m_aboveScopep->clonep()) m_aboveScopep->clonep();
if (m_aboveCellp && m_aboveCellp->clonep()) m_aboveCellp->clonep();
if (m_modp && static_cast<AstNode*>(m_modp)->clonep()) {
static_cast<AstNode*>(m_modp)->clonep();
}
}
string AstScope::nameDotless() const {
2009-12-05 16:38:49 +01:00
string out = shortName();
string::size_type pos;
while ((pos = out.find('.')) != string::npos) out.replace(pos, 1, "__");
2009-12-05 16:38:49 +01:00
return out;
}
AstVarScope* AstScope::createTemp(const string& name, unsigned width) {
FileLine* const flp = fileline();
AstVar* const varp
= new AstVar{flp, VVarType::MODULETEMP, name, VFlagBitPacked{}, static_cast<int>(width)};
modp()->addStmtsp(varp);
AstVarScope* const vscp = new AstVarScope{flp, this, varp};
addVarsp(vscp);
return vscp;
}
AstVarScope* AstScope::createTemp(const string& name, AstNodeDType* dtypep) {
FileLine* const flp = fileline();
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
modp()->addStmtsp(varp);
AstVarScope* const vscp = new AstVarScope{flp, this, varp};
addVarsp(vscp);
return vscp;
}
AstVarScope* AstScope::createTempLike(const string& name, AstVarScope* vscp) {
return createTemp(name, vscp->dtypep());
}
2015-06-17 01:27:18 +02:00
string AstScopeName::scopePrettyNameFormatter(AstText* scopeTextp) const {
2009-12-05 16:38:49 +01:00
string out;
for (AstText* textp = scopeTextp; textp; textp = VN_AS(textp->nextp(), Text)) {
out += textp->text();
2009-12-05 16:38:49 +01:00
}
// TOP will be replaced by top->name()
if (out.substr(0, 10) == "__DOT__TOP") out.replace(0, 10, "");
if (out.substr(0, 7) == "__DOT__") out.replace(0, 7, "");
if (out.substr(0, 1) == ".") out.replace(0, 1, "");
2009-12-05 16:38:49 +01:00
return AstNode::prettyName(out);
}
2015-06-17 01:27:18 +02:00
string AstScopeName::scopeNameFormatter(AstText* scopeTextp) const {
2009-12-05 16:38:49 +01:00
string out;
for (AstText* textp = scopeTextp; textp; textp = VN_AS(textp->nextp(), Text)) {
out += textp->text();
}
if (out.substr(0, 10) == "__DOT__TOP") out.replace(0, 10, "");
if (out.substr(0, 7) == "__DOT__") out.replace(0, 7, "");
if (out.substr(0, 1) == ".") out.replace(0, 1, "");
string::size_type pos;
while ((pos = out.find('.')) != string::npos) out.replace(pos, 1, "__");
while ((pos = out.find("__DOT__")) != string::npos) out.replace(pos, 7, "__");
return out;
}
bool AstSenTree::hasClocked() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isClocked()) return true;
}
return false;
}
bool AstSenTree::hasStatic() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isStatic()) return true;
}
return false;
}
bool AstSenTree::hasInitial() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isInitial()) return true;
}
return false;
}
bool AstSenTree::hasFinal() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isFinal()) return true;
}
return false;
}
bool AstSenTree::hasCombo() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isCombo()) return true;
}
return false;
}
bool AstSenTree::hasHybrid() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isHybrid()) return true;
}
return false;
}
AstTypeTable::AstTypeTable(FileLine* fl)
: ASTGEN_SUPER_TypeTable(fl) {
for (int i = 0; i < VBasicDTypeKwd::_ENUM_MAX; ++i) m_basicps[i] = nullptr;
}
void AstTypeTable::clearCache() {
// When we mass-change widthMin in V3WidthCommit, we need to correct the table.
// Just clear out the maps; the search functions will be used to rebuild the map
for (auto& itr : m_basicps) itr = nullptr;
m_detailedMap.clear();
// Clear generic()'s so dead detection will work
for (AstNode* nodep = typesp(); nodep; nodep = nodep->nextp()) {
if (AstBasicDType* const bdtypep = VN_CAST(nodep, BasicDType)) bdtypep->generic(false);
}
}
void AstTypeTable::repairCache() {
// After we mass-change widthMin in V3WidthCommit, we need to correct the table.
clearCache();
for (AstNode* nodep = typesp(); nodep; nodep = nodep->nextp()) {
if (AstBasicDType* const bdtypep = VN_CAST(nodep, BasicDType)) {
(void)findInsertSameDType(bdtypep);
}
}
}
AstEmptyQueueDType* AstTypeTable::findEmptyQueueDType(FileLine* fl) {
if (VL_UNLIKELY(!m_emptyQueuep)) {
AstEmptyQueueDType* const newp = new AstEmptyQueueDType{fl};
addTypesp(newp);
m_emptyQueuep = newp;
}
return m_emptyQueuep;
}
2019-12-01 17:52:48 +01:00
AstVoidDType* AstTypeTable::findVoidDType(FileLine* fl) {
if (VL_UNLIKELY(!m_voidp)) {
AstVoidDType* const newp = new AstVoidDType{fl};
2019-12-01 17:52:48 +01:00
addTypesp(newp);
m_voidp = newp;
}
return m_voidp;
}
AstQueueDType* AstTypeTable::findQueueIndexDType(FileLine* fl) {
if (VL_UNLIKELY(!m_queueIndexp)) {
AstQueueDType* const newp = new AstQueueDType(fl, AstNode::findUInt32DType(), nullptr);
addTypesp(newp);
m_queueIndexp = newp;
}
return m_queueIndexp;
}
AstBasicDType* AstTypeTable::findBasicDType(FileLine* fl, VBasicDTypeKwd kwd) {
if (m_basicps[kwd]) return m_basicps[kwd];
//
AstBasicDType* const new1p = new AstBasicDType(fl, kwd);
// Because the detailed map doesn't update this map,
// check the detailed map for this same node
// Also adds this new node to the detailed map
AstBasicDType* const newp = findInsertSameDType(new1p);
if (newp != new1p) {
VL_DO_DANGLING(new1p->deleteTree(), new1p);
} else {
addTypesp(newp);
}
//
m_basicps[kwd] = newp;
return newp;
}
AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd, int width,
2020-04-20 03:19:09 +02:00
int widthMin, VSigning numeric) {
AstBasicDType* const new1p = new AstBasicDType(fl, kwd, numeric, width, widthMin);
AstBasicDType* const newp = findInsertSameDType(new1p);
if (newp != new1p) {
VL_DO_DANGLING(new1p->deleteTree(), new1p);
} else {
addTypesp(newp);
}
return newp;
}
AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd,
const VNumRange& range, int widthMin,
VSigning numeric) {
AstBasicDType* const new1p = new AstBasicDType(fl, kwd, numeric, range, widthMin);
AstBasicDType* const newp = findInsertSameDType(new1p);
if (newp != new1p) {
VL_DO_DANGLING(new1p->deleteTree(), new1p);
} else {
addTypesp(newp);
}
return newp;
}
AstBasicDType* AstTypeTable::findInsertSameDType(AstBasicDType* nodep) {
const VBasicTypeKey key(nodep->width(), nodep->widthMin(), nodep->numeric(), nodep->keyword(),
nodep->nrange());
DetailedMap& mapr = m_detailedMap;
const auto it = mapr.find(key);
if (it != mapr.end()) return it->second;
mapr.emplace(key, nodep);
nodep->generic(true);
// No addTypesp; the upper function that called new() is responsible for adding
return nodep;
}
AstConstPool::AstConstPool(FileLine* fl)
: ASTGEN_SUPER_ConstPool(fl)
, m_modp{new AstModule(fl, "@CONST-POOL@")}
, m_scopep{new AstScope(fl, m_modp, "@CONST-POOL@", nullptr, nullptr)} {
this->modulep(m_modp);
m_modp->addStmtsp(m_scopep);
}
const char* AstConstPool::broken() const {
BROKEN_RTN(m_modp && !m_modp->brokeExists());
BROKEN_RTN(m_scopep && !m_scopep->brokeExists());
return nullptr;
}
AstVarScope* AstConstPool::createNewEntry(const string& name, AstNode* initp) {
FileLine* const fl = initp->fileline();
AstVar* const varp = new AstVar(fl, VVarType::MODULETEMP, name, initp->dtypep());
varp->isConst(true);
varp->isStatic(true);
varp->valuep(initp->cloneTree(false));
m_modp->addStmtsp(varp);
AstVarScope* const varScopep = new AstVarScope(fl, m_scopep, varp);
m_scopep->addVarsp(varScopep);
return varScopep;
}
static bool sameInit(const AstInitArray* ap, const AstInitArray* bp) {
// Unpacked array initializers must have equivalent values
// Note, sadly we can't just call ap->sameTree(pb), because both:
// - the dtypes might be different instances
// - the default/inititem children might be in different order yet still yield the same table
// See note in AstInitArray::same about the same. This function instead compares by initializer
// value, rather than by tree structure.
if (const AstAssocArrayDType* const aDTypep = VN_CAST(ap->dtypep(), AssocArrayDType)) {
const AstAssocArrayDType* const bDTypep = VN_CAST(bp->dtypep(), AssocArrayDType);
if (!bDTypep) return false;
if (!aDTypep->subDTypep()->sameTree(bDTypep->subDTypep())) return false;
if (!aDTypep->keyDTypep()->sameTree(bDTypep->keyDTypep())) return false;
UASSERT_OBJ(ap->defaultp(), ap, "Assoc InitArray should have a default");
UASSERT_OBJ(bp->defaultp(), bp, "Assoc InitArray should have a default");
if (!ap->defaultp()->sameTree(bp->defaultp())) return false;
// Compare initializer arrays by value. Note this is only called when they hash the same,
// so they likely run at most once per call to 'AstConstPool::findTable'.
// This assumes that the defaults are used in the same way.
// TODO when buinding the AstInitArray, remove any values matching the default
const auto& amapr = ap->map();
const auto& bmapr = bp->map();
const auto ait = amapr.cbegin();
const auto bit = bmapr.cbegin();
while (ait != amapr.cend() || bit != bmapr.cend()) {
if (ait == amapr.cend() || bit == bmapr.cend()) return false; // Different size
if (ait->first != bit->first) return false; // Different key
if (ait->second->sameTree(bit->second)) return false; // Different value
}
} else if (const AstUnpackArrayDType* const aDTypep
= VN_CAST(ap->dtypep(), UnpackArrayDType)) {
const AstUnpackArrayDType* const bDTypep = VN_CAST(bp->dtypep(), UnpackArrayDType);
if (!bDTypep) return false;
if (!aDTypep->subDTypep()->sameTree(bDTypep->subDTypep())) return false;
if (!aDTypep->rangep()->sameTree(bDTypep->rangep())) return false;
// Compare initializer arrays by value. Note this is only called when they hash the same,
// so they likely run at most once per call to 'AstConstPool::findTable'.
const uint64_t size = aDTypep->elementsConst();
for (uint64_t n = 0; n < size; ++n) {
const AstNode* const valAp = ap->getIndexDefaultedValuep(n);
const AstNode* const valBp = bp->getIndexDefaultedValuep(n);
if (!valAp->sameTree(valBp)) return false;
}
}
return true;
}
AstVarScope* AstConstPool::findTable(AstInitArray* initp) {
const AstNode* const defaultp = initp->defaultp();
// Verify initializer is well formed
UASSERT_OBJ(VN_IS(initp->dtypep(), AssocArrayDType)
|| VN_IS(initp->dtypep(), UnpackArrayDType),
initp, "Const pool table must have array dtype");
UASSERT_OBJ(!defaultp || VN_IS(defaultp, Const), initp,
"Const pool table default must be Const");
for (AstNode* nodep = initp->initsp(); nodep; nodep = nodep->nextp()) {
const AstNode* const valuep = VN_AS(nodep, InitItem)->valuep();
UASSERT_OBJ(VN_IS(valuep, Const), valuep, "Const pool table entry must be Const");
}
// Try to find an existing table with the same content
const V3Hash hash = V3Hasher::uncachedHash(initp);
const auto& er = m_tables.equal_range(hash.value());
for (auto it = er.first; it != er.second; ++it) {
AstVarScope* const varScopep = it->second;
const AstInitArray* const init2p = VN_AS(varScopep->varp()->valuep(), InitArray);
if (sameInit(initp, init2p)) {
return varScopep; // Found identical table
}
}
// No such table yet, create it.
string name = "TABLE_";
name += hash.toString();
name += "_";
name += cvtToStr(std::distance(er.first, er.second));
AstVarScope* const varScopep = createNewEntry(name, initp);
m_tables.emplace(hash.value(), varScopep);
return varScopep;
}
static bool sameInit(const AstConst* ap, const AstConst* bp) {
// Similarly to the AstInitArray comparison, we ignore the dtype instance, so long as they
// are compatible. For now, we assume the dtype is not relevant, as we only call this from
// V3Prelim which is a late pass.
// Compare initializers by value. This checks widths as well.
return ap->num().isCaseEq(bp->num());
}
AstVarScope* AstConstPool::findConst(AstConst* initp, bool mergeDType) {
// Try to find an existing constant with the same value
const V3Hash hash = initp->num().toHash();
const auto& er = m_consts.equal_range(hash.value());
for (auto it = er.first; it != er.second; ++it) {
AstVarScope* const varScopep = it->second;
const AstConst* const init2p = VN_AS(varScopep->varp()->valuep(), Const);
if (sameInit(initp, init2p)
&& (mergeDType || varScopep->dtypep()->sameTree(initp->dtypep()))) {
return varScopep; // Found identical constant
}
}
// No such constant yet, create it.
string name = "CONST_";
name += hash.toString();
name += "_";
name += cvtToStr(std::distance(er.first, er.second));
AstVarScope* const varScopep = createNewEntry(name, initp);
m_consts.emplace(hash.value(), varScopep);
return varScopep;
}
//======================================================================
// Special walking tree inserters
void AstNode::addBeforeStmt(AstNode* newp, AstNode*) {
UASSERT_OBJ(backp(), newp, "Can't find current statement to addBeforeStmt");
// Look up; virtual call will find where to put it
this->backp()->addBeforeStmt(newp, this);
}
void AstNode::addNextStmt(AstNode* newp, AstNode*) {
UASSERT_OBJ(backp(), newp, "Can't find current statement to addNextStmt");
// Look up; virtual call will find where to put it
this->backp()->addNextStmt(newp, this);
}
void AstNodeStmt::addBeforeStmt(AstNode* newp, AstNode*) {
// Insert newp before current node
this->addHereThisAsNext(newp);
}
void AstNodeStmt::addNextStmt(AstNode* newp, AstNode*) {
// Insert newp after current node
this->addNextHere(newp);
}
void AstWhile::addBeforeStmt(AstNode* newp, AstNode* belowp) {
// Special, as statements need to be put in different places
// Belowp is how we came to recurse up to this point
// Preconditions insert first just before themselves (the normal rule
// for other statement types)
if (belowp == precondsp()) {
// Must have been first statement in precondsp list, so newp is new first statement
belowp->addHereThisAsNext(newp);
} else if (belowp == condp()) {
// Goes before condition, IE in preconditions
addPrecondsp(newp);
} else if (belowp == stmtsp()) {
// Was first statement in body, so new front
belowp->addHereThisAsNext(newp);
} else {
belowp->v3fatalSrc("Doesn't look like this was really under the while");
}
}
void AstWhile::addNextStmt(AstNode* newp, AstNode* belowp) {
// Special, as statements need to be put in different places
// Belowp is how we came to recurse up to this point
// Preconditions insert first just before themselves (the normal rule
// for other statement types)
if (belowp == precondsp()) {
// Next in precond list
belowp->addNextHere(newp);
} else if (belowp == condp()) {
// Becomes first statement in body, body may have been empty
if (stmtsp()) {
stmtsp()->addHereThisAsNext(newp);
} else {
addStmtsp(newp);
}
} else if (belowp == stmtsp()) {
// Next statement in body
belowp->addNextHere(newp);
} else {
belowp->v3fatalSrc("Doesn't look like this was really under the while");
}
}
//======================================================================
// Per-type Debugging
// Render node address for dumps. By default this is just the address
// printed as hex, but with --dump-tree-addrids we map addresses to short
// strings with a bijection to aid human readability. Observe that this might
// not actually be a unique identifier as the address can get reused after a
// node has been freed.
static std::string nodeAddr(const AstNode* nodep) {
return v3Global.opt.dumpTreeAddrids() ? v3Global.ptrToId(nodep) : cvtToHex(nodep);
}
void AstNode::dump(std::ostream& str) const {
str << typeName() << " " << nodeAddr(this)
#ifdef VL_DEBUG
<< " <e" << std::dec << editCount() << ((editCount() >= editCountLast()) ? "#>" : ">")
#endif
<< " {" << fileline()->filenameLetters() << std::dec << fileline()->lastLineno()
<< fileline()->firstColumnLetters() << "}";
if (user1p()) str << " u1=" << nodeAddr(user1p());
if (user2p()) str << " u2=" << nodeAddr(user2p());
if (user3p()) str << " u3=" << nodeAddr(user3p());
if (user4p()) str << " u4=" << nodeAddr(user4p());
if (user5p()) str << " u5=" << nodeAddr(user5p());
if (hasDType()) {
// Final @ so less likely to by accident read it as a nodep
if (dtypep() == this) {
str << " @dt=this@";
} else {
str << " @dt=" << nodeAddr(dtypep()) << "@";
}
if (AstNodeDType* const dtp = dtypep()) dtp->dumpSmall(str);
} else { // V3Broken will throw an error
if (dtypep()) str << " %Error-dtype-exp=null,got=" << nodeAddr(dtypep());
}
if (name() != "") {
if (VN_IS(this, Const)) {
str << " " << name(); // Already quoted
} else {
str << " " << V3OutFormatter::quoteNameControls(name());
}
}
}
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
void AstNodeProcedure::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (isSuspendable()) str << " [SUSP]";
}
void AstAlways::dump(std::ostream& str) const {
this->AstNodeProcedure::dump(str);
if (keyword() != VAlwaysKwd::ALWAYS) str << " [" << keyword().ascii() << "]";
2013-05-01 04:55:28 +02:00
}
void AstAttrOf::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [" << attrType().ascii() << "]";
}
void AstBasicDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
str << " kwd=" << keyword().ascii();
if (isRanged() && !rangep()) str << " range=[" << left() << ":" << right() << "]";
}
string AstBasicDType::prettyDTypeName() const {
std::ostringstream os;
os << keyword().ascii();
if (isRanged() && !rangep() && keyword().width() <= 1) {
os << "[" << left() << ":" << right() << "]";
}
return os.str();
}
void AstNodeExpr::dump(std::ostream& str) const { this->AstNode::dump(str); }
void AstNodeUniop::dump(std::ostream& str) const { this->AstNodeExpr::dump(str); }
void AstCCast::dump(std::ostream& str) const {
this->AstNodeUniop::dump(str);
str << " sz" << size();
}
void AstCell::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (recursive()) str << " [RECURSIVE]";
if (modp()) {
str << " -> ";
modp()->dump(str);
} else {
str << " ->UNLINKED:" << modName();
}
}
const char* AstCell::broken() const {
BROKEN_RTN(m_modp && !m_modp->brokeExists());
return nullptr;
}
void AstCellInline::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " -> " << origModName();
str << " [scopep=" << reinterpret_cast<const void*>(scopep()) << "]";
}
const char* AstCellInline::broken() const {
BROKEN_RTN(m_scopep && !m_scopep->brokeExists());
return nullptr;
}
const char* AstClassPackage::broken() const {
BROKEN_BASE_RTN(AstNodeModule::broken());
BROKEN_RTN(m_classp && !m_classp->brokeExists());
return nullptr;
}
void AstClassPackage::cloneRelink() {
if (m_classp && m_classp->clonep()) m_classp = m_classp->clonep();
}
void AstClass::insertCache(AstNode* nodep) {
const bool doit = (VN_IS(nodep, Var) || VN_IS(nodep, EnumItemRef)
|| (VN_IS(nodep, NodeFTask) && !VN_AS(nodep, NodeFTask)->isExternProto())
|| VN_IS(nodep, CFunc));
2020-08-23 01:46:21 +02:00
if (doit) {
if (m_members.find(nodep->name()) != m_members.end()) {
nodep->v3error("Duplicate declaration of member name: " << nodep->prettyNameQ());
} else {
m_members.emplace(nodep->name(), nodep);
}
}
}
void AstClass::repairCache() {
clearCache();
for (auto* itemp = membersp(); itemp; itemp = itemp->nextp()) {
if (const auto* const scopep = VN_CAST(itemp, Scope)) {
for (auto* blockp = scopep->blocksp(); blockp; blockp = blockp->nextp()) {
insertCache(blockp);
}
} else {
insertCache(itemp);
}
}
}
bool AstClass::isClassExtendedFrom(const AstClass* refClassp, const AstClass* baseClassp) {
// TAIL RECURSIVE
if (!refClassp || !baseClassp) return false;
if (refClassp == baseClassp) return true;
2020-11-30 00:26:06 +01:00
if (!refClassp->extendsp()) return false;
return isClassExtendedFrom(refClassp->extendsp()->classp(), baseClassp);
}
void AstClass::dump(std::ostream& str) const {
2021-12-23 02:06:04 +01:00
this->AstNodeModule::dump(str);
if (isExtended()) str << " [EXT]";
if (isVirtual()) str << " [VIRT]";
}
const char* AstClass::broken() const {
BROKEN_BASE_RTN(AstNodeModule::broken());
BROKEN_RTN(m_classOrPackagep && !m_classOrPackagep->brokeExists());
return nullptr;
}
void AstClass::cloneRelink() {
AstNodeModule::cloneRelink();
if (m_classOrPackagep && m_classOrPackagep->clonep()) {
m_classOrPackagep = m_classOrPackagep->clonep();
}
}
AstClass* AstClassExtends::classp() const {
const AstClassRefDType* refp = VN_CAST(dtypep(), ClassRefDType);
2020-11-26 17:06:59 +01:00
if (VL_UNLIKELY(!refp)) { // LinkDot uses this for 'super.'
refp = VN_AS(childDTypep(), ClassRefDType);
2020-11-26 17:06:59 +01:00
}
UASSERT_OBJ(refp, this, "class extends non-ref");
return refp->classp();
}
void AstClassRefDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
2021-12-23 02:06:04 +01:00
if (classOrPackagep()) str << " cpkg=" << nodeAddr(classOrPackagep());
if (classp()) {
str << " -> ";
classp()->dump(str);
} else {
str << " -> UNLINKED";
}
}
void AstClassRefDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "class:" << name();
}
const char* AstClassRefDType::broken() const {
BROKEN_RTN(m_classp && !m_classp->brokeExists());
BROKEN_RTN(m_classOrPackagep && !m_classOrPackagep->brokeExists());
return nullptr;
}
void AstClassRefDType::cloneRelink() {
if (m_classp && m_classp->clonep()) m_classp = m_classp->clonep();
if (m_classOrPackagep && m_classOrPackagep->clonep()) {
m_classOrPackagep = m_classOrPackagep->clonep();
}
}
string AstClassRefDType::name() const { return classp() ? classp()->name() : "<unlinked>"; }
void AstNodeCoverOrAssert::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
if (immediate()) str << " [IMMEDIATE]";
}
void AstDisplay::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
// str << " " << displayType().ascii();
}
2022-11-19 19:23:28 +01:00
void AstEnumDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
str << " enum";
}
void AstEnumDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "enum";
}
void AstEnumItemRef::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
str << " -> ";
if (itemp()) {
itemp()->dump(str);
} else {
str << "UNLINKED";
}
2009-12-27 14:29:55 +01:00
}
const char* AstEnumItemRef::broken() const {
BROKEN_RTN(m_itemp && !m_itemp->brokeExists());
BROKEN_RTN(m_classOrPackagep && !m_classOrPackagep->brokeExists());
return nullptr;
}
void AstIfaceRefDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
if (cellName() != "") str << " cell=" << cellName();
if (ifaceName() != "") str << " if=" << ifaceName();
if (modportName() != "") str << " mp=" << modportName();
if (cellp()) {
str << " -> ";
cellp()->dump(str);
} else if (ifacep()) {
str << " -> ";
ifacep()->dump(str);
} else {
str << " -> UNLINKED";
}
}
void AstIfaceRefDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "iface";
}
void AstIfaceRefDType::cloneRelink() {
if (m_cellp && m_cellp->clonep()) m_cellp = m_cellp->clonep();
if (m_ifacep && m_ifacep->clonep()) m_ifacep = m_ifacep->clonep();
if (m_modportp && m_modportp->clonep()) m_modportp = m_modportp->clonep();
}
void AstInitArray::dump(std::ostream& str) const {
this->AstNode::dump(str);
int n = 0;
const auto& mapr = map();
for (const auto& itr : mapr) {
if (n++ > 5) {
str << " ...";
break;
}
str << " [" << itr.first << "]=" << reinterpret_cast<const void*>(itr.second);
}
}
const char* AstInitArray::broken() const {
for (KeyItemMap::const_iterator it = m_map.begin(); it != m_map.end(); ++it) {
BROKEN_RTN(!it->second);
BROKEN_RTN(!it->second->brokeExists());
}
return nullptr;
}
void AstInitArray::cloneRelink() {
for (KeyItemMap::iterator it = m_map.begin(); it != m_map.end(); ++it) {
if (it->second->clonep()) it->second = it->second->clonep();
}
}
void AstInitArray::addIndexValuep(uint64_t index, AstNode* newp) {
const auto it = m_map.find(index);
if (it != m_map.end()) {
it->second->valuep(newp);
} else {
AstInitItem* const itemp = new AstInitItem(fileline(), newp);
m_map.emplace(index, itemp);
addInitsp(itemp);
}
}
AstNode* AstInitArray::getIndexValuep(uint64_t index) const {
const auto it = m_map.find(index);
if (it == m_map.end()) {
return nullptr;
} else {
return it->second->valuep();
}
}
AstNode* AstInitArray::getIndexDefaultedValuep(uint64_t index) const {
AstNode* valuep = getIndexValuep(index);
if (!valuep) valuep = defaultp();
return valuep;
}
void AstJumpGo::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
str << " -> ";
if (labelp()) {
labelp()->dump(str);
} else {
str << "%Error:UNLINKED";
}
}
const char* AstJumpGo::broken() const {
BROKEN_RTN(!labelp()->brokeExistsBelow());
return nullptr;
}
void AstJumpGo::cloneRelink() {
if (m_labelp->clonep()) m_labelp = m_labelp->clonep();
}
void AstJumpLabel::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
str << " -> ";
if (blockp()) {
blockp()->dump(str);
} else {
str << "%Error:UNLINKED";
}
}
void AstLogOr::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
if (sideEffect()) str << " [SIDE]";
}
void AstMemberSel::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
str << " -> ";
if (varp()) {
varp()->dump(str);
} else {
str << "%Error:UNLINKED";
}
}
void AstMemberSel::cloneRelink() {
if (m_varp && m_varp->clonep()) m_varp = m_varp->clonep();
}
const char* AstMemberSel::broken() const {
BROKEN_RTN(m_varp && !m_varp->brokeExists());
return nullptr;
}
void AstMethodCall::dump(std::ostream& str) const {
this->AstNodeFTaskRef::dump(str);
str << " -> ";
if (taskp()) {
taskp()->dump(str);
} else {
str << " -> UNLINKED";
}
}
void AstModportFTaskRef::dump(std::ostream& str) const {
2013-12-21 12:51:15 +01:00
this->AstNode::dump(str);
if (isExport()) str << " EXPORT";
if (isImport()) str << " IMPORT";
if (ftaskp()) {
str << " -> ";
ftaskp()->dump(str);
} else {
str << " -> UNLINKED";
}
2013-12-21 12:51:15 +01:00
}
const char* AstModportFTaskRef::broken() const {
BROKEN_RTN(m_ftaskp && !m_ftaskp->brokeExists());
return nullptr;
}
void AstModportFTaskRef::cloneRelink() {
if (m_ftaskp && m_ftaskp->clonep()) m_ftaskp = m_ftaskp->clonep();
}
void AstModportVarRef::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (direction().isAny()) str << " " << direction();
if (varp()) {
str << " -> ";
varp()->dump(str);
} else {
str << " -> UNLINKED";
}
}
const char* AstModportVarRef::broken() const {
BROKEN_RTN(m_varp && !m_varp->brokeExists());
return nullptr;
}
void AstModportVarRef::cloneRelink() {
if (m_varp && m_varp->clonep()) m_varp = m_varp->clonep();
}
void AstPin::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (modVarp()) {
str << " -> ";
modVarp()->dump(str);
} else {
str << " ->UNLINKED";
}
if (svImplicit()) str << " [.SV]";
}
const char* AstPin::broken() const {
BROKEN_RTN(m_modVarp && !m_modVarp->brokeExists());
BROKEN_RTN(m_modPTypep && !m_modPTypep->brokeExists());
return nullptr;
}
string AstPin::prettyOperatorName() const {
return modVarp()
? ((modVarp()->direction().isAny() ? modVarp()->direction().prettyName() + " " : "")
+ "port connection " + modVarp()->prettyNameQ())
: "port connection";
}
void AstPrintTimeScale::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
str << " " << timeunit();
}
void AstNodeTermop::dump(std::ostream& str) const { this->AstNodeExpr::dump(str); }
void AstTime::dump(std::ostream& str) const {
this->AstNodeTermop::dump(str);
str << " " << timeunit();
}
void AstTimeD::dump(std::ostream& str) const {
this->AstNodeTermop::dump(str);
str << " " << timeunit();
}
void AstTimeImport::dump(std::ostream& str) const {
this->AstNodeUniop::dump(str);
str << " " << timeunit();
}
void AstTypedef::dump(std::ostream& str) const {
2014-11-07 13:50:11 +01:00
this->AstNode::dump(str);
if (attrPublic()) str << " [PUBLIC]";
if (subDTypep()) {
str << " -> ";
subDTypep()->dump(str);
}
2014-11-07 13:50:11 +01:00
}
void AstNodeRange::dump(std::ostream& str) const { this->AstNode::dump(str); }
void AstRange::dump(std::ostream& str) const {
this->AstNodeRange::dump(str);
if (littleEndian()) str << " [LITTLE]";
}
void AstParamTypeDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
if (subDTypep()) {
str << " -> ";
subDTypep()->dump(str);
} else {
str << " -> UNLINKED";
}
}
void AstRefDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
if (typedefp() || subDTypep()) {
2019-10-06 14:20:02 +02:00
static bool s_recursing = false;
2019-01-12 15:33:57 +01:00
if (!s_recursing) { // Prevent infinite dump if circular typedefs
s_recursing = true;
str << " -> ";
if (const auto subp = typedefp()) {
subp->dump(str);
} else if (const auto subp = subDTypep()) {
subp->dump(str);
}
2019-01-12 15:33:57 +01:00
s_recursing = false;
}
} else {
str << " -> UNLINKED";
2019-01-12 15:33:57 +01:00
}
2009-11-07 05:16:06 +01:00
}
2022-11-19 19:23:28 +01:00
void AstRefDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "ref";
}
const char* AstRefDType::broken() const {
BROKEN_RTN(m_typedefp && !m_typedefp->brokeExists());
BROKEN_RTN(m_refDTypep && !m_refDTypep->brokeExists());
BROKEN_RTN(m_classOrPackagep && !m_classOrPackagep->brokeExists());
return nullptr;
}
void AstRefDType::cloneRelink() {
if (m_typedefp && m_typedefp->clonep()) m_typedefp = m_typedefp->clonep();
if (m_refDTypep && m_refDTypep->clonep()) m_refDTypep = m_refDTypep->clonep();
if (m_classOrPackagep && m_classOrPackagep->clonep()) {
m_classOrPackagep = m_classOrPackagep->clonep();
}
}
AstNodeDType* AstRefDType::subDTypep() const {
if (typedefp()) return typedefp()->subDTypep();
return refDTypep(); // Maybe nullptr
}
void AstNodeUOrStructDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
if (packed()) str << " [PACKED]";
if (isFourstate()) str << " [4STATE]";
}
void AstNodeDType::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (generic()) str << " [GENERIC]";
if (AstNodeDType* const dtp = virtRefDTypep()) {
str << " refdt=" << nodeAddr(dtp);
dtp->dumpSmall(str);
}
}
void AstNodeDType::dumpSmall(std::ostream& str) const {
str << "(" << (generic() ? "G/" : "") << ((isSigned() && !isDouble()) ? "s" : "")
<< (isNosign() ? "n" : "") << (isDouble() ? "d" : "") << (isString() ? "str" : "");
if (!isDouble() && !isString()) str << "w" << (widthSized() ? "" : "u") << width();
if (!widthSized()) str << "/" << widthMin();
str << ")";
}
void AstNodeArrayDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
if (auto* const adtypep = VN_CAST(this, UnpackArrayDType)) {
2020-12-13 01:19:16 +01:00
// uc = packed compound object, u = unpacked POD
str << (adtypep->isCompound() ? "uc" : "u");
} else {
2020-12-13 01:19:16 +01:00
str << "p";
}
str << declRange();
}
void AstNodeArrayDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
2020-12-13 01:19:16 +01:00
if (isCompound()) str << " [COMPOUND]";
str << " " << declRange();
}
string AstPackArrayDType::prettyDTypeName() const {
std::ostringstream os;
if (const auto subp = subDTypep()) os << subp->prettyDTypeName();
os << declRange();
return os.str();
}
string AstUnpackArrayDType::prettyDTypeName() const {
std::ostringstream os;
string ranges = cvtToStr(declRange());
// Unfortunately we need a single $ for the first unpacked, and all
// dimensions shown in "reverse" order
AstNodeDType* subp = subDTypep()->skipRefp();
while (AstUnpackArrayDType* adtypep = VN_CAST(subp, UnpackArrayDType)) {
ranges += cvtToStr(adtypep->declRange());
subp = adtypep->subDTypep()->skipRefp();
}
os << subp->prettyDTypeName() << "$" << ranges;
return os.str();
}
std::vector<AstUnpackArrayDType*> AstUnpackArrayDType::unpackDimensions() {
std::vector<AstUnpackArrayDType*> dims;
for (AstUnpackArrayDType* unpackp = this; unpackp;) {
dims.push_back(unpackp);
if (AstNodeDType* const subp = unpackp->subDTypep()) {
unpackp = VN_CAST(subp, UnpackArrayDType);
} else {
unpackp = nullptr;
}
}
return dims;
}
void AstNetlist::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [" << timeunit() << "/" << timeprecision() << "]";
}
const char* AstNetlist::broken() const {
BROKEN_RTN(m_typeTablep && !m_typeTablep->brokeExists());
BROKEN_RTN(m_constPoolp && !m_constPoolp->brokeExists());
BROKEN_RTN(m_dollarUnitPkgp && !m_dollarUnitPkgp->brokeExists());
BROKEN_RTN(m_evalp && !m_evalp->brokeExists());
BROKEN_RTN(m_dpiExportTriggerp && !m_dpiExportTriggerp->brokeExists());
BROKEN_RTN(m_topScopep && !m_topScopep->brokeExists());
2022-09-15 14:17:00 +02:00
BROKEN_RTN(m_delaySchedulerp && !m_delaySchedulerp->brokeExists());
return nullptr;
}
AstPackage* AstNetlist::dollarUnitPkgAddp() {
if (!m_dollarUnitPkgp) {
m_dollarUnitPkgp = new AstPackage(fileline(), AstPackage::dollarUnitName());
// packages are always libraries; don't want to make them a "top"
m_dollarUnitPkgp->inLibrary(true);
m_dollarUnitPkgp->modTrace(false); // may reconsider later
m_dollarUnitPkgp->internal(true);
addModulesp(m_dollarUnitPkgp);
}
return m_dollarUnitPkgp;
}
void AstNetlist::createTopScope(AstScope* scopep) {
UASSERT(scopep, "Must not be nullptr");
UASSERT_OBJ(!m_topScopep, scopep, "TopScope already exits");
m_topScopep = new AstTopScope{scopep->modp()->fileline(), scopep};
scopep->modp()->addStmtsp(v3Global.rootp()->topScopep());
}
void AstNodeModule::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " L" << level();
if (modPublic()) str << " [P]";
if (inLibrary()) str << " [LIB]";
if (dead()) str << " [DEAD]";
if (recursiveClone()) {
str << " [RECURSIVE-CLONE]";
} else if (recursive()) {
str << " [RECURSIVE]";
}
str << " [" << timeunit() << "]";
}
void AstPackageExport::dump(std::ostream& str) const {
2017-09-21 03:04:59 +02:00
this->AstNode::dump(str);
str << " -> " << packagep();
2017-09-21 03:04:59 +02:00
}
const char* AstPackageExport ::broken() const {
BROKEN_RTN(!m_packagep || !m_packagep->brokeExists());
return nullptr;
}
void AstPackageExport::cloneRelink() {
if (m_packagep && m_packagep->clonep()) m_packagep = m_packagep->clonep();
}
void AstPackageImport::dump(std::ostream& str) const {
2009-11-10 01:07:59 +01:00
this->AstNode::dump(str);
str << " -> " << packagep();
2009-11-10 01:07:59 +01:00
}
const char* AstPackageImport::broken() const {
BROKEN_RTN(!m_packagep || !m_packagep->brokeExists());
return nullptr;
}
void AstPackageImport::cloneRelink() {
if (m_packagep && m_packagep->clonep()) m_packagep = m_packagep->clonep();
}
void AstPatMember::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
if (isDefault()) str << " [DEFAULT]";
}
void AstNodeTriop::dump(std::ostream& str) const { this->AstNodeExpr::dump(str); }
void AstSel::dump(std::ostream& str) const {
this->AstNodeTriop::dump(str);
if (declRange().ranged()) {
str << " decl" << declRange() << "]";
if (declElWidth() != 1) str << "/" << declElWidth();
}
}
void AstSliceSel::dump(std::ostream& str) const {
this->AstNodeTriop::dump(str);
if (declRange().ranged()) str << " decl" << declRange();
}
void AstMTaskBody::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " ";
m_execMTaskp->dump(str);
}
void AstTypeTable::dump(std::ostream& str) const {
this->AstNode::dump(str);
for (int i = 0; i < static_cast<int>(VBasicDTypeKwd::_ENUM_MAX); ++i) {
if (AstBasicDType* const subnodep = m_basicps[i]) {
str << '\n'; // Newline from caller, so newline first
str << "\t\t" << std::setw(8) << VBasicDTypeKwd{i}.ascii();
str << " -> ";
subnodep->dump(str);
}
}
{
const DetailedMap& mapr = m_detailedMap;
for (const auto& itr : mapr) {
AstBasicDType* const dtypep = itr.second;
str << '\n'; // Newline from caller, so newline first
str << "\t\tdetailed -> ";
dtypep->dump(str);
}
}
// Note get newline from caller too.
}
2019-12-01 17:52:48 +01:00
void AstAssocArrayDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "[assoc-" << reinterpret_cast<const void*>(keyDTypep()) << "]";
2019-12-01 17:52:48 +01:00
}
string AstAssocArrayDType::prettyDTypeName() const {
return subDTypep()->prettyDTypeName() + "[" + keyDTypep()->prettyDTypeName() + "]";
}
2020-03-07 16:24:27 +01:00
void AstDynArrayDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "[]";
2020-03-07 16:24:27 +01:00
}
string AstDynArrayDType::prettyDTypeName() const { return subDTypep()->prettyDTypeName() + "[]"; }
void AstQueueDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "[queue]";
}
string AstQueueDType::prettyDTypeName() const {
string str = subDTypep()->prettyDTypeName() + "[$";
if (boundConst()) str += ":" + cvtToStr(boundConst());
return str + "]";
}
void AstWildcardArrayDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "[*]";
}
bool AstWildcardArrayDType::same(const AstNode* samep) const {
const AstNodeArrayDType* const asamep = static_cast<const AstNodeArrayDType*>(samep);
if (!asamep->subDTypep()) return false;
return (subDTypep() == asamep->subDTypep());
}
bool AstWildcardArrayDType::similarDType(AstNodeDType* samep) const {
const AstNodeArrayDType* const asamep = static_cast<const AstNodeArrayDType*>(samep);
return type() == samep->type() && asamep->subDTypep()
&& subDTypep()->skipRefp()->similarDType(asamep->subDTypep()->skipRefp());
}
void AstUnsizedArrayDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "[]";
}
bool AstUnsizedArrayDType::same(const AstNode* samep) const {
const AstNodeArrayDType* const asamep = static_cast<const AstNodeArrayDType*>(samep);
if (!asamep->subDTypep()) return false;
return (subDTypep() == asamep->subDTypep());
}
bool AstUnsizedArrayDType::similarDType(AstNodeDType* samep) const {
const AstNodeArrayDType* const asamep = static_cast<const AstNodeArrayDType*>(samep);
return type() == samep->type() && asamep->subDTypep()
&& subDTypep()->skipRefp()->similarDType(asamep->subDTypep()->skipRefp());
}
void AstEmptyQueueDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "emptyq";
}
2019-12-01 17:52:48 +01:00
void AstVoidDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "void";
2019-12-01 17:52:48 +01:00
}
void AstVarScope::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (isTrace()) str << " [T]";
if (scopep()) str << " [scopep=" << reinterpret_cast<const void*>(scopep()) << "]";
if (varp()) {
str << " -> ";
varp()->dump(str);
} else {
str << " ->UNLINKED";
}
}
void AstNodeVarRef::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
if (classOrPackagep()) str << " pkg=" << nodeAddr(classOrPackagep());
str << " " << access().arrow() << " ";
}
void AstVarXRef::dump(std::ostream& str) const {
this->AstNodeVarRef::dump(str);
str << ".=" << dotted() << " ";
if (inlinedDots() != "") str << " inline.=" << inlinedDots() << " - ";
if (varScopep()) {
varScopep()->dump(str);
} else if (varp()) {
varp()->dump(str);
} else {
str << "UNLINKED";
}
}
void AstVarRef::dump(std::ostream& str) const {
this->AstNodeVarRef::dump(str);
if (varScopep()) {
varScopep()->dump(str);
} else if (varp()) {
varp()->dump(str);
} else {
str << "UNLINKED";
}
}
bool AstVarRef::same(const AstNode* samep) const {
return same(static_cast<const AstVarRef*>(samep));
}
int AstVarRef::instrCount() const {
// Account for the target of hard-coded method calls as just an address computation
if (const AstCMethodHard* const callp = VN_CAST(backp(), CMethodHard)) {
if (callp->fromp() == this) return 1;
}
// Otherwise as a load/store
return widthInstrs() * (access().isReadOrRW() ? INSTR_COUNT_LD : 1);
}
void AstVar::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (isSc()) str << " [SC]";
if (isPrimaryIO()) str << (isInoutish() ? " [PIO]" : (isWritable() ? " [PO]" : " [PI]"));
if (isIO()) str << " " << direction().ascii();
if (isConst()) str << " [CONST]";
if (isPullup()) str << " [PULLUP]";
if (isPulldown()) str << " [PULLDOWN]";
if (isUsedClock()) str << " [CLK]";
if (isSigPublic()) str << " [P]";
if (isLatched()) str << " [LATCHED]";
if (isUsedLoopIdx()) str << " [LOOP]";
if (attrIsolateAssign()) str << " [aISO]";
if (attrFileDescr()) str << " [aFD]";
if (isFuncReturn()) {
str << " [FUNCRTN]";
} else if (isFuncLocal()) {
str << " [FUNC]";
}
if (isDpiOpenArray()) str << " [DPIOPENA]";
if (!attrClocker().unknown()) str << " [" << attrClocker().ascii() << "] ";
if (!lifetime().isNone()) str << " [" << lifetime().ascii() << "] ";
str << " " << varType();
}
void AstScope::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [abovep=" << reinterpret_cast<const void*>(aboveScopep()) << "]";
str << " [cellp=" << reinterpret_cast<const void*>(aboveCellp()) << "]";
str << " [modp=" << reinterpret_cast<const void*>(modp()) << "]";
}
void AstScopeName::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
if (dpiExport()) str << " [DPIEX]";
if (forFormat()) str << " [FMT]";
}
void AstSenTree::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (isMulti()) str << " [MULTI]";
}
void AstSenItem::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [" << edgeType().ascii() << "]";
}
void AstStrengthSpec::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " (" << m_s0.ascii() << ", " << m_s1.ascii() << ")";
}
void AstParseRef::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [" << expect().ascii() << "]";
}
void AstClassOrPackageRef::dump(std::ostream& str) const {
this->AstNode::dump(str);
2020-12-05 22:23:20 +01:00
if (classOrPackageNodep()) str << " cpkg=" << nodeAddr(classOrPackageNodep());
str << " -> ";
2020-12-05 22:23:20 +01:00
if (classOrPackageNodep()) {
classOrPackageNodep()->dump(str);
} else {
str << "UNLINKED";
}
}
AstNodeModule* AstClassOrPackageRef::classOrPackagep() const {
AstNode* foundp = m_classOrPackageNodep;
if (auto* const anodep = VN_CAST(foundp, Typedef)) foundp = anodep->subDTypep();
if (auto* const anodep = VN_CAST(foundp, NodeDType)) foundp = anodep->skipRefp();
if (auto* const anodep = VN_CAST(foundp, ClassRefDType)) foundp = anodep->classp();
return VN_CAST(foundp, NodeModule);
}
void AstDot::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (colon()) str << " [::]";
}
void AstActive::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " => ";
if (sensesp()) {
sensesp()->dump(str);
} else {
str << "UNLINKED";
}
}
const char* AstActive::broken() const {
BROKEN_RTN(m_sensesp && !m_sensesp->brokeExists());
return nullptr;
}
void AstActive::cloneRelink() {
2022-09-15 14:17:00 +02:00
if (m_sensesp->clonep()) m_sensesp = m_sensesp->clonep();
}
void AstNodeFTaskRef::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
if (classOrPackagep()) str << " pkg=" << nodeAddr(classOrPackagep());
str << " -> ";
if (dotted() != "") str << ".=" << dotted() << " ";
if (taskp()) {
taskp()->dump(str);
} else {
str << "UNLINKED";
}
}
void AstNodeFTask::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (classMethod()) str << " [METHOD]";
if (dpiExport()) str << " [DPIX]";
if (dpiImport()) str << " [DPII]";
if (dpiOpenChild()) str << " [DPIOPENCHILD]";
if (dpiOpenParent()) str << " [DPIOPENPARENT]";
if (prototype()) str << " [PROTOTYPE]";
if (recursive()) str << " [RECURSIVE]";
if (taskPublic()) str << " [PUBLIC]";
if ((dpiImport() || dpiExport()) && cname() != name()) str << " [c=" << cname() << "]";
}
void AstNodeBlock::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (unnamed()) str << " [UNNAMED]";
}
void AstBegin::dump(std::ostream& str) const {
this->AstNodeBlock::dump(str);
if (generate()) str << " [GEN]";
if (genforp()) str << " [GENFOR]";
if (implied()) str << " [IMPLIED]";
}
void AstCoverDecl::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
2020-06-01 15:08:50 +02:00
if (!page().empty()) str << " page=" << page();
if (!linescov().empty()) str << " lc=" << linescov();
2008-12-12 21:34:02 +01:00
if (this->dataDeclNullp()) {
str << " -> ";
this->dataDeclNullp()->dump(str);
2008-12-12 21:34:02 +01:00
} else {
if (binNum()) str << " bin" << std::dec << binNum();
2008-12-12 21:34:02 +01:00
}
}
void AstCoverInc::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
str << " -> ";
if (declp()) {
declp()->dump(str);
} else {
str << "%Error:UNLINKED";
}
}
void AstFork::dump(std::ostream& str) const {
this->AstNodeBlock::dump(str);
if (!joinType().join()) str << " [" << joinType() << "]";
}
2022-04-11 01:40:27 +02:00
void AstTraceDecl::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
if (code()) str << " [code=" << code() << "]";
}
void AstTraceInc::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
str << " -> ";
if (declp()) {
declp()->dump(str);
} else {
str << "%Error:UNLINKED";
}
}
void AstNodeText::dump(std::ostream& str) const {
2009-12-05 16:38:49 +01:00
this->AstNode::dump(str);
string out = text();
string::size_type pos;
if ((pos = out.find('\n')) != string::npos) {
out.erase(pos, out.length() - pos);
out += "...";
2009-12-05 16:38:49 +01:00
}
str << " \"" << out << "\"";
2009-12-05 16:38:49 +01:00
}
void AstNodeFile::dump(std::ostream& str) const { this->AstNode::dump(str); }
void AstVFile::dump(std::ostream& str) const { this->AstNodeFile::dump(str); }
void AstCFile::dump(std::ostream& str) const {
this->AstNodeFile::dump(str);
if (source()) str << " [SRC]";
if (slow()) str << " [SLOW]";
}
void AstCFunc::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (slow()) str << " [SLOW]";
if (pure()) str << " [PURE]";
if (isStatic()) str << " [STATIC]";
if (dpiExportDispatcher()) str << " [DPIED]";
if (dpiExportImpl()) str << " [DPIEI]";
if (dpiImportPrototype()) str << " [DPIIP]";
if (dpiImportWrapper()) str << " [DPIIW]";
if (dpiContext()) str << " [DPICTX]";
if (isConstructor()) str << " [CTOR]";
if (isDestructor()) str << " [DTOR]";
if (isVirtual()) str << " [VIRT]";
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
if (isCoroutine()) str << " [CORO]";
}
const char* AstCAwait::broken() const {
BROKEN_RTN(m_sensesp && !m_sensesp->brokeExists());
return nullptr;
}
void AstCAwait::cloneRelink() {
if (m_sensesp && m_sensesp->clonep()) m_sensesp = m_sensesp->clonep();
}
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
void AstCAwait::dump(std::ostream& str) const {
this->AstNodeUniop::dump(str);
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
if (sensesp()) {
str << " => ";
sensesp()->dump(str);
}
}
int AstCMethodHard::instrCount() const {
if (AstBasicDType* const basicp = fromp()->dtypep()->basicp()) {
// TODO: add a more structured description of library methods, rather than using string
// matching. See #3715.
if (basicp->isTriggerVec() && m_name == "at") {
// This is an important special case for scheduling so we compute it precisely,
// it is simply a load.
return INSTR_COUNT_LD;
}
}
return 0;
}
const char* AstCFunc::broken() const {
BROKEN_RTN((m_scopep && !m_scopep->brokeExists()));
return nullptr;
}
void AstCFunc::cloneRelink() {
if (m_scopep && m_scopep->clonep()) m_scopep = m_scopep->clonep();
}
void AstCUse::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [" << useType() << "]";
}
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
AstAlways* AstAssignW::convertToAlways() {
const bool hasTimingControl = isTimingControl();
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
AstNode* const lhs1p = lhsp()->unlinkFrBack();
AstNode* const rhs1p = rhsp()->unlinkFrBack();
AstNode* const controlp = timingControlp() ? timingControlp()->unlinkFrBack() : nullptr;
FileLine* const flp = fileline();
AstNode* bodysp = new AstAssign{flp, lhs1p, rhs1p, controlp};
if (hasTimingControl) {
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
// If there's a timing control, put the assignment in a fork..join_none. This process won't
// get marked as suspendable and thus will be scheduled normally
auto* forkp = new AstFork{flp, "", bodysp};
forkp->joinType(VJoinType::JOIN_NONE);
bodysp = forkp;
}
AstAlways* const newp = new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, bodysp};
replaceWith(newp); // User expected to then deleteTree();
return newp;
}