verilator/src/V3Task.cpp

2030 lines
94 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add temporaries, such as for task nodes
//
2019-11-08 04:33:59 +01:00
// Code available from: https://verilator.org
//
//*************************************************************************
//
2025-01-01 14:30:25 +01:00
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// V3Task's Transformations:
//
// Each module:
// Look for TASKREF
// Insert task's statements into the referrer
// Look for TASKs
// Remove them, they're inlined
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Task.h"
#include "V3Const.h"
#include "V3Control.h"
#include "V3EmitCBase.h"
#include "V3Graph.h"
#include "V3Stats.h"
#include <tuple>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Graph subclasses
class TaskBaseVertex VL_NOT_FINAL : public V3GraphVertex {
AstNode* m_impurep = nullptr; // Node causing impure function w/ outside references
bool m_noInline = false; // Marked with pragma
public:
2015-10-04 04:33:06 +02:00
explicit TaskBaseVertex(V3Graph* graphp)
: V3GraphVertex{graphp} {}
~TaskBaseVertex() override = default;
bool pure() const { return m_impurep == nullptr; }
AstNode* impureNode() const { return m_impurep; }
void impure(AstNode* nodep) { m_impurep = nodep; }
bool noInline() const { return m_noInline; }
void noInline(bool flag) { m_noInline = flag; }
};
class TaskFTaskVertex final : public TaskBaseVertex {
// Every task gets a vertex, and we link tasks together based on funcrefs.
AstNodeFTask* const m_nodep;
AstCFunc* m_cFuncp = nullptr;
public:
TaskFTaskVertex(V3Graph* graphp, AstNodeFTask* nodep)
: TaskBaseVertex{graphp}
, m_nodep{nodep} {}
~TaskFTaskVertex() override = default;
AstNodeFTask* nodep() const VL_MT_STABLE { return m_nodep; }
string name() const override VL_MT_STABLE { return nodep()->name(); }
string dotColor() const override { return pure() ? "black" : "red"; }
AstCFunc* cFuncp() const { return m_cFuncp; }
void cFuncp(AstCFunc* nodep) { m_cFuncp = nodep; }
};
class TaskCodeVertex final : public TaskBaseVertex {
// Top vertex for all calls not under another task
public:
2015-10-04 04:33:06 +02:00
explicit TaskCodeVertex(V3Graph* graphp)
: TaskBaseVertex{graphp} {}
~TaskCodeVertex() override = default;
string name() const override VL_MT_STABLE { return "*CODE*"; }
string dotColor() const override { return "green"; }
};
class TaskEdge final : public V3GraphEdge {
VL_RTTI_IMPL(TaskEdge, V3GraphEdge)
public:
TaskEdge(V3Graph* graphp, TaskBaseVertex* fromp, TaskBaseVertex* top)
: V3GraphEdge{graphp, fromp, top, 1, false} {}
~TaskEdge() override = default;
string dotLabel() const override { return "w" + cvtToStr(weight()); }
};
//######################################################################
class TaskStateVisitor final : public VNVisitor {
// NODE STATE
// Output:
// AstNodeFTask::user3p // AstScope* this FTask is under
// AstNodeFTask::user4p // GraphFTaskVertex* this FTask is under
// AstVar::user4p // GraphFTaskVertex* this variable is declared in
const VNUser3InUse m_inuser3;
const VNUser4InUse m_inuser4;
// TYPES
using VarToScopeMap = std::map<std::pair<AstScope*, AstVar*>, AstVarScope*>;
using FuncToClassMap = std::unordered_map<const AstNodeFTask*, AstClass*>;
// MEMBERS
VarToScopeMap m_varToScopeMap; // Map for Var -> VarScope mappings
2020-08-24 01:37:56 +02:00
FuncToClassMap m_funcToClassMap; // Map for ctor func -> class
AstNodeFTask* m_ctorp = nullptr; // Class constructor
2020-08-24 01:37:56 +02:00
AstClass* m_classp = nullptr; // Current class
V3Graph m_callGraph; // Task call graph
TaskBaseVertex* m_curVxp; // Current vertex we're adding to
std::vector<AstInitialAutomatic*> m_initialps; // Initial blocks to move
public:
// METHODS
AstScope* getScope(AstNodeFTask* nodep) {
AstScope* const scopep = VN_AS(nodep->user3p(), Scope);
UASSERT_OBJ(scopep, nodep, "No scope for function");
return scopep;
}
AstVarScope* findVarScope(AstScope* scopep, AstVar* nodep) {
const auto iter = m_varToScopeMap.find(std::make_pair(scopep, nodep));
UASSERT_OBJ(iter != m_varToScopeMap.end(), nodep, "No scope for var");
return iter->second;
}
2020-08-24 01:37:56 +02:00
AstClass* getClassp(AstNodeFTask* nodep) {
AstClass* const classp = m_funcToClassMap[nodep];
2020-08-24 01:37:56 +02:00
UASSERT_OBJ(classp, nodep, "No class for ctor func");
return classp;
}
void remapFuncClassp(AstNodeFTask* nodep, AstNodeFTask* newp) {
m_funcToClassMap[newp] = getClassp(nodep);
}
bool ftaskNoInline(AstNodeFTask* nodep) {
return !v3Global.opt.fInlineFuncs() || getFTaskVertex(nodep)->noInline();
}
AstCFunc* ftaskCFuncp(AstNodeFTask* nodep) { return getFTaskVertex(nodep)->cFuncp(); }
void ftaskCFuncp(AstNodeFTask* nodep, AstCFunc* cfuncp) {
getFTaskVertex(nodep)->cFuncp(cfuncp);
}
void checkPurity(AstNodeFTask* nodep) { checkPurity(nodep, getFTaskVertex(nodep)); }
private:
void checkPurity(AstNodeFTask* nodep, TaskBaseVertex* vxp) {
if (nodep->recursive()) return; // Impure, but no warning
if (!vxp->pure()) {
nodep->v3warn(
IMPURE, "Unsupported: External variable referenced by non-inlined function/task: "
<< nodep->prettyNameQ() << '\n'
<< nodep->warnContextPrimary() << '\n'
<< vxp->impureNode()->warnOther()
<< "... Location of the external reference: "
<< vxp->impureNode()->prettyNameQ() << '\n'
<< vxp->impureNode()->warnContextSecondary());
}
// And, we need to check all tasks this task calls
2024-03-26 00:06:25 +01:00
for (V3GraphEdge& edge : vxp->outEdges()) {
checkPurity(nodep, static_cast<TaskBaseVertex*>(edge.top()));
}
}
TaskFTaskVertex* getFTaskVertex(AstNodeFTask* nodep) {
2022-11-20 23:40:38 +01:00
if (!nodep->user4p()) nodep->user4p(new TaskFTaskVertex{&m_callGraph, nodep});
return static_cast<TaskFTaskVertex*>(nodep->user4u().toGraphVertex());
}
// VISITORS
void visit(AstScope* nodep) override {
// Each FTask is unique per-scope, so AstNodeFTaskRefs do not need
// pointers to what scope the FTask is to be invoked under.
// However, to create variables, we need to track the scopes involved.
// Find all var->varscope mappings, for later cleanup
for (AstNode* stmtp = nodep->varsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVarScope* const vscp = VN_CAST(stmtp, VarScope)) {
if (vscp->varp()->isFuncLocal() || vscp->varp()->isUsedLoopIdx()) {
UINFO(9, " funcvsc " << vscp);
2023-10-28 12:24:04 +02:00
m_varToScopeMap.emplace(std::make_pair(nodep, vscp->varp()), vscp);
}
}
}
// Likewise, all FTask->scope mappings
for (AstNode* stmtp = nodep->blocksp(); stmtp; stmtp = stmtp->nextp()) {
if (AstNodeFTask* const taskp = VN_CAST(stmtp, NodeFTask)) taskp->user3p(nodep);
}
iterateChildren(nodep);
}
void visit(AstNodeFTaskRef* nodep) override {
// We make multiple edges if a task is called multiple times from another task.
UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked task");
TaskFTaskVertex* const taskVtxp = getFTaskVertex(nodep->taskp());
new TaskEdge{&m_callGraph, m_curVxp, taskVtxp};
// Do we have to disable inlining the function?
const V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp());
if (!taskVtxp->noInline()) { // Else short-circuit below
for (const auto& itr : tconnects) {
const AstVar* const portp = itr.first;
const AstArg* const argp = itr.second;
if (const AstNodeExpr* const pinp = argp->exprp()) {
if ((portp->isRef() || portp->isConstRef()) && !VN_IS(pinp, VarRef)) {
UINFO(9, "No function inline due to ref " << pinp);
taskVtxp->noInline(true);
}
}
}
}
}
void visit(AstNodeFTask* nodep) override {
UINFO(9, " TASK " << nodep);
VL_RESTORER(m_curVxp);
m_curVxp = getFTaskVertex(nodep);
if (nodep->dpiImport()) m_curVxp->noInline(true);
if (nodep->classMethod()) m_curVxp->noInline(true); // Until V3Task supports it
if (nodep->recursive()) m_curVxp->noInline(true);
if (nodep->isConstructor()) {
m_curVxp->noInline(true);
m_ctorp = nodep;
UASSERT_OBJ(m_classp, nodep, "Ctor not under class");
m_funcToClassMap[nodep] = m_classp;
2020-04-19 02:20:17 +02:00
}
iterateChildren(nodep);
}
void visit(AstPragma* nodep) override {
if (nodep->pragType() == VPragmaType::NO_INLINE_TASK) {
// Just mark for the next steps, and we're done with it.
m_curVxp->noInline(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else {
iterateChildren(nodep);
}
}
void visit(AstVar* nodep) override {
iterateChildren(nodep);
nodep->user4p(m_curVxp); // Remember what task it's under
}
void visit(AstVarRef* nodep) override {
iterateChildren(nodep);
if (nodep->varp()->user4u().toGraphVertex() != m_curVxp) {
if (m_curVxp->pure() && !nodep->varp()->isXTemp()) m_curVxp->impure(nodep);
}
}
void visit(AstClass* nodep) override {
2020-04-19 02:20:17 +02:00
// Move initial statements into the constructor
VL_RESTORER(m_initialps);
VL_RESTORER(m_ctorp);
VL_RESTORER(m_classp);
2020-04-19 02:20:17 +02:00
m_initialps.clear();
m_ctorp = nullptr;
2020-08-24 01:37:56 +02:00
m_classp = nodep;
2020-04-19 02:20:17 +02:00
{ // Find m_initialps, m_ctor
iterateChildren(nodep);
}
UASSERT_OBJ(m_ctorp, nodep, "class constructor missing"); // LinkDot always makes it
for (AstInitialAutomatic* initialp : m_initialps) {
if (AstNode* const newp = initialp->stmtsp()) {
2020-04-19 02:20:17 +02:00
newp->unlinkFrBackWithNext();
if (!m_ctorp->stmtsp()) {
m_ctorp->addStmtsp(newp);
} else {
m_ctorp->stmtsp()->addHereThisAsNext(newp);
}
}
VL_DO_DANGLING(pushDeletep(initialp->unlinkFrBack()), initialp);
}
m_initialps.clear();
}
void visit(AstInitialAutomatic* nodep) override {
2020-04-19 02:20:17 +02:00
m_initialps.push_back(nodep);
iterateChildren(nodep);
}
//--------------------
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
2019-09-12 13:22:22 +02:00
// CONSTRUCTORS
explicit TaskStateVisitor(AstNetlist* nodep) {
2022-11-20 23:40:38 +01:00
m_curVxp = new TaskCodeVertex{&m_callGraph};
//
iterate(nodep);
//
m_callGraph.removeRedundantEdgesSum(&TaskEdge::followAlwaysTrue);
if (dumpGraphLevel()) m_callGraph.dumpDotFilePrefixed("task_call");
}
~TaskStateVisitor() override = default;
2020-01-09 01:33:47 +01:00
VL_UNCOPYABLE(TaskStateVisitor);
};
//######################################################################
// DPI related utility functions
struct TaskDpiUtils final {
// Returns a vector of ('elements', 'stride') pairs for each unpacked dimension
static std::vector<std::pair<int, int>> unpackDimsAndStrides(AstVar* varp) {
AstNodeDType* const dtypep = varp->dtypep()->skipRefp();
std::vector<std::pair<int, int>> dimStrides;
AstUnpackArrayDType* const unpackp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType);
if (!unpackp) return dimStrides;
const std::vector<AstUnpackArrayDType*> dims = unpackp->unpackDimensions();
const size_t nDims = dims.size();
dimStrides.resize(nDims);
// Stride of fastest varying dimension is 1.
dimStrides[nDims - 1].first = dims.back()->elementsConst();
dimStrides[nDims - 1].second = 1;
// Rest are densly packed
for (ssize_t i = nDims - 2; i >= 0; --i) {
dimStrides[i].first = dims[i]->elementsConst();
dimStrides[i].second = dimStrides[i + 1].first * dimStrides[i + 1].second;
}
return dimStrides;
}
// Returns the prefix of a function-call like statement used to convert
// from IEEE DPI data types to internal types, and a bool that is true
// if type uses svBitVecVal/svLogicVecVal and ther result is returned via
// an output parameter, o false if uses C primitive type and result is the
// return value.
static std::pair<std::string, bool> dpiToInternalCvtStmt(AstVar* varp) {
AstBasicDType* const basicp = varp->basicp();
// DPI types using Primitive C types
if (basicp->keyword() == VBasicDTypeKwd::CHANDLE) return {"VL_CVT_VP_Q(", false};
if (basicp->keyword() == VBasicDTypeKwd::STRING) return {"VL_CVT_N_CSTR(", false};
if (basicp->isDpiPrimitive()) return {"(", false};
// DPI types using svBitVecVal/svLogicVecVal
UASSERT_OBJ(basicp->isDpiBitVec() || basicp->isDpiLogicVec(), varp,
"Should use svBitVecVal/svLogicVecVal");
const std::string vecType = basicp->isDpiBitVec() ? "SVBV" : "SVLV";
const AstNodeDType* const dtypep = varp->dtypep()->skipRefp();
const char sizeChar = dtypep->width() <= 8 ? 'C'
: dtypep->width() <= 16 ? 'S'
: *dtypep->charIQWN();
const std::string& size = std::to_string(dtypep->width());
return {"VL_SET_"s + sizeChar + "_" + vecType + "(" + size + ", ", true};
}
};
//######################################################################
// Task state, as a visitor of each AstNode
class TaskVisitor final : public VNVisitor {
// NODE STATE
// Each module:
2020-08-24 01:37:56 +02:00
// AstNodeFTask::user1 // True if its been expanded
// Each funccall
// to 'relink' function:
// AstVar::user2p // AstVarScope* to replace varref with
const VNUser1InUse m_inuser1;
const VNUser2InUse m_inuser2;
// TYPES
using DpiCFuncs = std::map<const string, std::tuple<AstNodeFTask*, std::string, AstCFunc*>>;
// STATE
TaskStateVisitor* const m_statep; // Common state between visitors
AstNodeModule* m_modp = nullptr; // Current module
AstTopScope* const m_topScopep = v3Global.rootp()->topScopep(); // The AstTopScope
AstScope* m_scopep = nullptr; // Current scope
AstNode* m_insStmtp = nullptr; // Where to insert statement
bool m_inSensesp = false; // Are we under a senitem?
bool m_inNew = false; // Are we under a constructor?
int m_modNCalls = 0; // Incrementing func # for making symbols
// STATE - across all visitors
DpiCFuncs m_dpiNames; // Map of all created DPI functions
VDouble0 m_statInlines; // Statistic tracking
VDouble0 m_statHierDpisWithCosts; // Statistic tracking
// METHODS
2009-01-21 22:56:50 +01:00
2009-12-20 14:27:00 +01:00
AstVarScope* createFuncVar(AstCFunc* funcp, const string& name, AstVar* examplep) {
2022-11-20 23:40:38 +01:00
AstVar* const newvarp = new AstVar{funcp->fileline(), VVarType::BLOCKTEMP, name, examplep};
newvarp->funcLocal(true);
funcp->addVarsp(newvarp);
2022-11-20 23:40:38 +01:00
AstVarScope* const newvscp = new AstVarScope{funcp->fileline(), m_scopep, newvarp};
m_scopep->addVarsp(newvscp);
return newvscp;
2009-12-20 14:27:00 +01:00
}
AstVarScope* createInputVar(AstCFunc* funcp, const string& name, VBasicDTypeKwd kwd) {
AstVar* const newvarp
2022-11-20 23:40:38 +01:00
= new AstVar{funcp->fileline(), VVarType::BLOCKTEMP, name, funcp->findBasicDType(kwd)};
newvarp->funcLocal(true);
newvarp->direction(VDirection::INPUT);
funcp->addArgsp(newvarp);
2022-11-20 23:40:38 +01:00
AstVarScope* const newvscp = new AstVarScope{funcp->fileline(), m_scopep, newvarp};
m_scopep->addVarsp(newvscp);
return newvscp;
2009-12-05 16:38:49 +01:00
}
AstVarScope* createVarScope(AstVar* invarp, const string& name) {
2021-07-22 22:09:24 +02:00
if (invarp->isParam() && VN_IS(invarp->valuep(), InitArray)) {
// Move array params in functions into constant pool
return v3Global.rootp()->constPoolp()->findTable(VN_AS(invarp->valuep(), InitArray));
2021-07-22 22:09:24 +02:00
} else {
// We could create under either the ref's scope or the ftask's scope.
// It shouldn't matter, as they are only local variables.
// We choose to do it under whichever called this function, which results
// in more cache locality.
AstVar* const newvarp
= new AstVar{invarp->fileline(), VVarType::BLOCKTEMP, name, invarp};
2021-07-22 22:09:24 +02:00
newvarp->funcLocal(false);
newvarp->propagateAttrFrom(invarp);
m_modp->addStmtsp(newvarp);
AstVarScope* const newvscp = new AstVarScope{newvarp->fileline(), m_scopep, newvarp};
m_scopep->addVarsp(newvscp);
2021-07-22 22:09:24 +02:00
return newvscp;
}
}
// Replace varrefs with new var pointer
void relink(AstNode* nodep) {
nodep->foreachAndNext([](AstVarRef* refp) {
if (refp->varp()->user2p()) { // It's being converted to an alias.
AstVarScope* const newvscp = VN_AS(refp->varp()->user2p(), VarScope);
refp->varScopep(newvscp);
refp->varp(refp->varScopep()->varp());
}
});
}
AstAssign* connectPortMakeInAssign(AstNodeExpr* pinp, AstVarScope* newvscp, bool pureCheck) {
// Create input assignment to go in FRONT of function call
AstNodeExpr* inPinp = pinp;
if (AstResizeLValue* sinPinp = VN_CAST(inPinp, ResizeLValue)) inPinp = sinPinp->lhsp();
AstNodeExpr* const inPinClonep
= pureCheck ? inPinp->cloneTreePure(true) : inPinp->cloneTree(true);
AstAssign* const assp = new AstAssign{
pinp->fileline(), new AstVarRef{newvscp->fileline(), newvscp, VAccess::WRITE},
inPinClonep};
assp->fileline()->modifyWarnOff(V3ErrorCode::BLKSEQ, true); // Ok if in <= block
return assp;
}
AstAssign* connectPortMakeOutAssign(AstVar* portp, AstNodeExpr* pinp, AstVarScope* newvscp,
bool pureCheck) {
// If needed, remap size of function to caller's output size
AstNodeExpr* outPinp = pinp;
AstNodeExpr* postRhsp = new AstVarRef{newvscp->fileline(), newvscp, VAccess::READ};
if (AstResizeLValue* soutPinp = VN_CAST(outPinp, ResizeLValue)) {
outPinp = soutPinp->lhsp();
if (AstNodeUniop* aoutPinp = VN_CAST(outPinp, Extend)) {
outPinp = aoutPinp->lhsp();
} else if (AstNodeUniop* aoutPinp = VN_CAST(outPinp, ExtendS)) {
outPinp = aoutPinp->lhsp();
} else if (AstSel* aoutPinp = VN_CAST(outPinp, Sel)) {
outPinp = aoutPinp->fromp();
} else {
outPinp->v3fatalSrc("Inout pin resizing should have had extend or select");
}
if (outPinp->width() < portp->width()) {
postRhsp = new AstSel{pinp->fileline(), postRhsp, 0, pinp->width()};
} else { // pin width > port width
if (pinp->isSigned() && postRhsp->isSigned()) {
postRhsp = new AstExtendS{pinp->fileline(), postRhsp};
} else {
postRhsp = new AstExtend{pinp->fileline(), postRhsp};
}
}
postRhsp->dtypeFrom(outPinp);
}
// Put output assignment AFTER function call
AstNodeExpr* const outPinClonep
= pureCheck ? outPinp->cloneTreePure(true) : outPinp->cloneTree(true);
AstAssign* const assp = new AstAssign{pinp->fileline(), outPinClonep, postRhsp};
assp->fileline()->modifyWarnOff(V3ErrorCode::BLKSEQ, true); // Ok if in <= block
return assp;
}
void connectPort(AstVar* portp, AstArg* argp, const string& namePrefix, AstNode* beginp,
bool inlineTask) {
AstNodeExpr* const pinp = argp->exprp();
if (inlineTask) {
portp->unlinkFrBack();
pushDeletep(portp); // Remove it from the clone (not original)
}
if (!pinp) {
// Too few arguments in function call
} else {
UINFO(9, " Port " << portp);
UINFO(9, " pin " << pinp);
if (inlineTask) {
2024-03-23 23:12:43 +01:00
pushDeletep(pinp->unlinkFrBack()); // Cloned in assignment below
VL_DO_DANGLING(argp->unlinkFrBack()->deleteTree(), argp); // Args no longer needed
}
if (portp->isWritable() && VN_IS(pinp, Const)) {
pinp->v3error("Function/task " + portp->direction().prettyName() // e.g. "output"
+ " connected to constant instead of variable: "
+ portp->prettyNameQ());
} else if (portp->isRef() || portp->isConstRef()) {
bool refArgOk = false;
if (VN_IS(pinp, VarRef) || VN_IS(pinp, MemberSel) || VN_IS(pinp, StructSel)
|| VN_IS(pinp, ArraySel)) {
refArgOk = true;
} else if (AstCMethodHard* const cMethodp = VN_CAST(pinp, CMethodHard)) {
if (VN_IS(cMethodp->fromp()->dtypep()->skipRefp(), QueueDType)) {
refArgOk = cMethodp->method() == VCMethod::DYN_AT_WRITE_APPEND
|| cMethodp->method() == VCMethod::DYN_AT_WRITE_APPEND_BACK;
2024-10-24 15:40:54 +02:00
} else {
refArgOk = cMethodp->method() == VCMethod::ARRAY_AT
|| cMethodp->method() == VCMethod::ARRAY_AT_BACK;
}
}
if (refArgOk) {
if (AstVarRef* const varrefp = VN_CAST(pinp, VarRef)) {
varrefp->access(VAccess::READWRITE);
}
} else {
pinp->v3error("Function/task ref argument is not of allowed type");
}
if (inlineTask) {
if (AstVarRef* const varrefp = VN_CAST(pinp, VarRef)) {
// Connect to this exact variable
AstVarScope* const localVscp = varrefp->varScopep();
UASSERT_OBJ(localVscp, varrefp, "Null var scope");
portp->user2p(localVscp);
} else {
pinp->v3fatalSrc("ref argument should have caused non-inline of function");
}
}
2024-11-26 00:25:36 +01:00
} else if (portp->isInout()) {
// UINFOTREE(9, pinp, "", "pinrsize-");
AstVarScope* const newvscp
= createVarScope(portp, namePrefix + "__" + portp->shortName());
portp->user2p(newvscp);
2024-03-23 23:12:43 +01:00
if (!inlineTask) {
pinp->replaceWith(
new AstVarRef{newvscp->fileline(), newvscp, VAccess::READWRITE});
2024-03-23 23:12:43 +01:00
pushDeletep(pinp); // Cloned by connectPortMakeInAssign
}
// Put input assignment in FRONT of all other statements
AstAssign* const preassp = connectPortMakeInAssign(pinp, newvscp, true);
if (AstNode* const afterp = beginp->nextp()) {
afterp->unlinkFrBackWithNext();
AstNode::addNext<AstNode, AstNode>(preassp, afterp);
}
beginp->addNext(preassp);
AstAssign* const postassp = connectPortMakeOutAssign(portp, pinp, newvscp, true);
beginp->addNext(postassp);
// if (debug() >= 9) beginp->dumpTreeAndNext(cout, "-pinrsize-out- ");
} else if (portp->isWritable()) {
// Even if it's referencing a varref, we still make a temporary
// Else task(x,x,x) might produce incorrect results
AstVarScope* const newvscp
= createVarScope(portp, namePrefix + "__" + portp->shortName());
portp->user2p(newvscp);
2024-03-23 23:12:43 +01:00
if (!inlineTask) {
pinp->replaceWith(new AstVarRef{newvscp->fileline(), newvscp, VAccess::WRITE});
2024-03-23 23:12:43 +01:00
pushDeletep(pinp); // Cloned by connectPortMakeOutAssign
}
AstAssign* const postassp = connectPortMakeOutAssign(portp, pinp, newvscp, false);
// Put assignment BEHIND of all other statements
beginp->addNext(postassp);
} else if (inlineTask && portp->isNonOutput()) {
// Make input variable
AstVarScope* const newvscp
= createVarScope(portp, namePrefix + "__" + portp->shortName());
portp->user2p(newvscp);
AstAssign* const preassp = connectPortMakeInAssign(pinp, newvscp, false);
// Put assignment in FRONT of all other statements
if (AstNode* const afterp = beginp->nextp()) {
afterp->unlinkFrBackWithNext();
AstNode::addNext<AstNode, AstNode>(preassp, afterp);
}
beginp->addNext(preassp);
}
}
}
AstNode* createInlinedFTask(AstNodeFTaskRef* refp, const string& namePrefix,
AstVarScope* outvscp) {
// outvscp is the variable for functions only, if nullptr, it's a task
UASSERT_OBJ(refp->taskp(), refp, "Unlinked?");
AstNode* const newbodysp
= refp->taskp()->stmtsp() ? refp->taskp()->stmtsp()->cloneTree(true) : nullptr;
AstNode* const beginp
= new AstComment{refp->fileline(), "Function: "s + refp->name(), true};
if (newbodysp) beginp->addNext(newbodysp);
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- newbegi: ");
//
// Create input variables
AstNode::user2ClearTree();
{
const V3TaskConnects tconnects = V3Task::taskConnects(refp, beginp);
for (const auto& itr : tconnects) {
AstVar* const portp = itr.first;
AstArg* const argp = itr.second;
connectPort(portp, argp, namePrefix, beginp, true);
}
}
UASSERT_OBJ(!refp->pinsp(), refp, "Pin wasn't removed by above loop");
{
AstNode* nextstmtp;
for (AstNode* stmtp = beginp; stmtp; stmtp = nextstmtp) {
nextstmtp = stmtp->nextp();
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
// Any I/O variables that fell out of above loop were already linked
if (!portp->user2p()) {
// Move it to a new localized variable
AstVarScope* const localVscp
= createVarScope(portp, namePrefix + "__" + portp->shortName());
portp->user2p(localVscp);
if (portp->needsCReset() && portp->lifetime().isAutomatic()
&& !portp->valuep()) {
// Reset automatic var to its default, on each invocation of function
AstVarRef* const vrefp
= new AstVarRef{portp->fileline(), portp, VAccess::WRITE};
portp->replaceWith(new AstCReset{portp->fileline(), vrefp, false});
} else {
portp->unlinkFrBack();
}
pushDeletep(portp); // Remove it from the clone (not original)
}
}
}
}
// Create function output variables
if (outvscp) {
// UINFO(0, "setflag on " << funcp->fvarp() << " to " << outvscp);
refp->taskp()->fvarp()->user2p(outvscp);
}
// Replace variable refs
relink(beginp);
//
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- iotask: ");
return beginp;
}
AstNode* createNonInlinedFTask(AstNodeFTaskRef* refp, const string& namePrefix,
2020-04-13 00:57:12 +02:00
AstVarScope* outvscp, AstCNew*& cnewpr) {
// outvscp is the variable for functions only, if nullptr, it's a task
UASSERT_OBJ(refp->taskp(), refp, "Unlinked?");
AstCFunc* const cfuncp = m_statep->ftaskCFuncp(refp->taskp());
UASSERT_OBJ(cfuncp, refp, "No non-inline task associated with this task call?");
//
AstNode* const beginp
= new AstComment{refp->fileline(), "Function: "s + refp->name(), true};
AstNodeCCall* ccallp;
if (VN_IS(refp, New)) {
2022-11-20 23:40:38 +01:00
AstCNew* const cnewp = new AstCNew{refp->fileline(), cfuncp};
2020-04-13 00:57:12 +02:00
cnewp->dtypep(refp->dtypep());
ccallp = cnewp;
// Parent AstNew will replace with this CNew
cnewpr = cnewp;
} else if (const AstMethodCall* const mrefp = VN_CAST(refp, MethodCall)) {
2022-11-20 23:40:38 +01:00
ccallp = new AstCMethodCall{refp->fileline(), mrefp->fromp()->unlinkFrBack(), cfuncp};
ccallp->dtypeSetVoid();
beginp->addNext(ccallp->makeStmt());
} else {
2022-11-20 23:40:38 +01:00
ccallp = new AstCCall{refp->fileline(), cfuncp};
ccallp->dtypeSetVoid();
beginp->addNext(ccallp->makeStmt());
}
2023-10-24 15:51:46 +02:00
if (const AstFuncRef* const funcRefp = VN_CAST(refp, FuncRef)) {
ccallp->superReference(funcRefp->superReference());
} else if (const AstTaskRef* const taskRefp = VN_CAST(refp, TaskRef)) {
ccallp->superReference(taskRefp->superReference());
}
// Convert complicated outputs to temp signals
{
const V3TaskConnects tconnects = V3Task::taskConnects(refp, refp->taskp()->stmtsp());
for (const auto& itr : tconnects) {
AstVar* const portp = itr.first;
AstArg* const argp = itr.second;
connectPort(portp, argp, namePrefix, beginp, false);
}
}
// First argument is symbol table, then output if a function
const bool needSyms = !refp->taskp()->dpiImport() || v3Global.opt.profExec();
if (needSyms) ccallp->argTypes("vlSymsp");
if (refp->taskp()->dpiContext()) {
AstScopeName* const snp = refp->scopeNamep()->unlinkFrBack();
UASSERT_OBJ(snp, refp, "Missing scoping context");
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
FileLine* const flp = refp->fileline();
// __Vscopep
ccallp->addArgsp(snp);
// __Vfilenamep
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
ccallp->addArgsp(new AstCExpr{flp, "\"" + flp->filenameEsc() + "\"", 64});
// __Vlineno
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
ccallp->addArgsp(new AstConst(flp, flp->lineno()));
}
// Create connections
AstNode* nextpinp;
for (AstNode* pinp = refp->pinsp(); pinp; pinp = nextpinp) {
nextpinp = pinp->nextp();
// Move pin to the CCall, removing all Arg's
AstNodeExpr* const exprp = VN_AS(pinp, Arg)->exprp();
exprp->unlinkFrBack();
ccallp->addArgsp(exprp);
}
2009-12-20 14:27:00 +01:00
2022-11-20 23:40:38 +01:00
if (outvscp) ccallp->addArgsp(new AstVarRef{refp->fileline(), outvscp, VAccess::WRITE});
2009-12-20 14:27:00 +01:00
if (debug() >= 9) beginp->dumpTreeAndNext(cout, "- nitask: ");
return beginp;
}
string dpiSignature(AstNodeFTask* nodep, AstVar* rtnvarp) const {
// Return fancy signature for DPI function. Variable names are not included so differences
// in only argument names will not matter (as required by the standard).
string dpiproto;
if (nodep->dpiPure()) dpiproto += "pure ";
if (nodep->dpiContext()) dpiproto += "context ";
dpiproto += rtnvarp ? rtnvarp->dpiArgType(true, true) : "void";
dpiproto += " " + nodep->cname() + " (";
string args;
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (const AstVar* const portp = VN_CAST(stmtp, Var)) {
if (portp->isIO() && !portp->isFuncReturn() && portp != rtnvarp) {
if (args != "") {
args += ", ";
dpiproto += ", ";
}
// Include both the Verilator and C type names, as if either
// differ we may get C compilation problems later
const std::string dpiType = portp->dpiArgType(false, false);
dpiproto += dpiType;
const std::string vType = portp->dtypep()->prettyDTypeName(false);
if (!portp->isDpiOpenArray() && dpiType != vType) {
dpiproto += " /* " + vType + " */ ";
}
args += portp->name(); // Leftover so ,'s look nice
}
}
}
dpiproto += ")";
return dpiproto;
2009-12-20 14:27:00 +01:00
}
static void checkLegalCIdentifier(AstNode* nodep, const string& name) {
if (name.end() != std::find_if(name.begin(), name.end(), [](char c) {
return !std::isalnum(c) && c != '_';
})) {
nodep->v3error("DPI function has illegal characters in C identifier name: " << name);
}
}
static AstNode* createDpiTemp(AstVar* portp, const string& suffix) {
const string stmt = portp->dpiTmpVarType(portp->name() + suffix) + ";";
2022-11-20 23:40:38 +01:00
return new AstCStmt{portp->fileline(), stmt};
2009-12-20 14:27:00 +01:00
}
void unlinkAndClone(AstNodeFTask* funcp, AstNode* nodep, bool withNext) {
UASSERT_OBJ(nodep, funcp, "null in function object clone");
if (funcp->recursive()) {
VNRelinker relinkHandle;
if (withNext) {
nodep->unlinkFrBackWithNext(&relinkHandle);
} else {
nodep->unlinkFrBack(&relinkHandle);
}
// Recursive functions require the original argument list to
// still be live for linking purposes.
// The old function gets clone, so that node pointers are mostly
// retained through the V3Task transformations
AstNode* const newp = nodep->cloneTree(withNext);
relinkHandle.relink(newp);
} else {
if (withNext) {
nodep->unlinkFrBackWithNext();
} else {
nodep->unlinkFrBack();
}
}
}
static AstNode* createAssignInternalToDpi(AstVar* portp, bool isPtr, const string& frSuffix,
const string& toSuffix) {
const string stmt = V3Task::assignInternalToDpi(portp, isPtr, frSuffix, toSuffix);
2022-11-20 23:40:38 +01:00
return new AstCStmt{portp->fileline(), stmt};
2009-12-20 14:27:00 +01:00
}
AstNodeStmt* createAssignDpiToInternal(AstVarScope* vscp, const std::string& rhsName) {
// Create assignment from DPI temporary into internal format
// DPI temporary is scalar or 1D array (if unpacked array)
// Internal representation is scalar, 1D, or multi-dimensional array (similar to SV)
AstVar* const varp = vscp->varp();
FileLine* const flp = vscp->fileline();
std::string cvt;
bool useSvVec;
std::tie(cvt, useSvVec) = TaskDpiUtils::dpiToInternalCvtStmt(varp);
const std::vector<std::pair<int, int>> strides = TaskDpiUtils::unpackDimsAndStrides(varp);
// Total number of elements in unpacked array
const int total = strides.empty() ? 1 : strides[0].first * strides[0].second;
// Number of words per element/primitive type
const int widthWords = varp->basicp()->widthWords();
// Number of bits in the C expression result, for masking.
const int cwidth = widthWords * VL_EDATASIZE;
// The resulting list of statements
AstNodeStmt* stmtsp = nullptr;
for (int i = 0; i < total; ++i) {
AstNodeExpr* lhsp = new AstVarRef{flp, vscp, VAccess::WRITE};
// Extract a scalar from multi-dimensional array (internal format)
for (const auto& stride : strides) {
lhsp = new AstArraySel(flp, lhsp, (i / stride.second) % stride.first);
}
// Extract a scalar from DPI temporary var that is scalar or 1D array
if (useSvVec) {
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
AstCStmt* const cstmtp = new AstCStmt{flp};
cstmtp->add(cvt);
cstmtp->add(lhsp);
cstmtp->add(", " + rhsName + " + " + std::to_string(i * widthWords) + ");");
stmtsp = AstNode::addNext(stmtsp, cstmtp);
} else {
const std::string elem = strides.empty() ? "" : "[" + std::to_string(i) + "]";
AstNodeExpr* rhsp = new AstCExpr{flp, cvt + rhsName + elem + ")", cwidth};
rhsp = new AstSel{flp, rhsp, 0, varp->width()};
stmtsp = AstNode::addNext(stmtsp, new AstAssign{flp, lhsp, rhsp});
}
}
return stmtsp;
2009-12-20 14:27:00 +01:00
}
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
// Create dispatch wrapper
AstCFunc* makeDpiExportDispatcher(AstNodeFTask* const nodep, AstVar* const rtnvarp) {
// Verilog name has __ conversion and other tricks, to match DPI C code, back that out
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
FileLine* const flp = nodep->fileline();
const std::string cname = AstNode::prettyName(nodep->cname());
checkLegalCIdentifier(nodep, cname);
const char* const tmpSuffixp = V3Task::dpiTemporaryVarSuffix();
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
const std::string rtnType = rtnvarp ? rtnvarp->dpiArgType(true, true) : "";
// The function we are building
AstCFunc* const funcp = new AstCFunc{flp, cname, m_scopep, rtnType};
funcp->dpiExportDispatcher(true);
funcp->dpiContext(nodep->dpiContext());
funcp->dontCombine(true);
funcp->entryPoint(true);
funcp->isStatic(true);
funcp->protect(false);
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
funcp->cname(cname);
// Add DPI Export to top, since it's a global function
m_topScopep->scopep()->addBlocksp(funcp);
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
// Note this function may dispatch on a different class.
// Thus we need to be careful not to assume a particular function layout.
//
// Func numbers must be the same for each function, even when there are
// completely different models with the same function name.
// Thus we can't just use a constant computed at Verilation time.
// We could use 64-bits of a MD5/SHA hash rather than a string here,
// but the compare is only done on first call then memoized, so
// it's not worth optimizing.
// Peramble - fetch the exproted function from the scope table
AstCStmt* const prep = new AstCStmt{flp};
funcp->addStmtsp(prep);
// Static doesn't need save-restore as if below will re-fill proper value
prep->add("static int __Vfuncnum = -1;\n");
// First time init (faster than what the compiler does if we did a singleton
prep->add("if (VL_UNLIKELY(__Vfuncnum == -1)) {\n");
prep->add("__Vfuncnum = Verilated::exportFuncNum(\"" + nodep->cname() + "\");\n");
prep->add("}\n");
// If the find fails, it will throw an error
prep->add("const VerilatedScope* const __Vscopep = Verilated::dpiScope();\n");
// If 'dpiScope()' fails and '__Vscopep' is null; the exportFind function throws an error
// If __Vcb is null the exportFind function throws and error
const std::string cbtype
= VIdProtect::protect(v3Global.opt.prefix() + "__Vcb_" + nodep->cname() + "_t");
prep->add(cbtype + " __Vcb = reinterpret_cast<" + cbtype
+ ">(VerilatedScope::exportFind(__Vscopep, __Vfuncnum));");
// Convert input/inout DPI arguments to Internal types, and construct the call
AstCStmt* const callp = new AstCStmt{flp};
const auto addFuncArg = [&](AstVar* portp) -> AstVarScope* {
// No createDpiTemp; we make a real internal variable instead
AstVarScope* const vscp = createFuncVar(funcp, portp->name() + tmpSuffixp, portp);
// No information exposure; is already visible in import/export func template
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
vscp->varp()->protect(false);
portp->protect(false);
// Add argument to call
const VAccess access = portp->isWritable() ? VAccess::WRITE : VAccess::READ;
callp->add(", ");
callp->add(new AstVarRef{portp->fileline(), vscp, access});
return vscp;
};
// Call callback
callp->add("(*__Vcb)(");
// First argument is the Syms
callp->add("(" + EmitCUtil::symClassName() + "*)(__Vscopep->symsp())");
// Add function arguments
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
AstVar* const portp = VN_CAST(stmtp, Var);
if (!portp) continue;
if (!portp->isIO()) continue;
if (portp->isFuncReturn()) continue;
if (portp == rtnvarp) continue; // Handled below
// Add argument to call
AstVarScope* const outvscp = addFuncArg(portp);
if (!portp->isNonOutput()) continue;
// Convert input/inout arguments to dpi type
const std::string deref
= portp->isInout() //
&& portp->basicp()->isDpiPrimitive() //
&& portp->dtypep()->skipRefp()->dimensions(false).second == 0
? "*"
: "";
funcp->addStmtsp(createAssignDpiToInternal(outvscp, deref + portp->name()));
}
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
// Return value argument goes last
if (rtnvarp) addFuncArg(rtnvarp);
// Close statement
callp->add(");");
// Call the user function
funcp->addStmtsp(callp);
// Convert output/inout arguments back to internal type
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
if (portp->isIO() && portp->isWritable() && !portp->isFuncReturn()) {
funcp->addStmtsp(createAssignInternalToDpi(portp, true, tmpSuffixp, ""));
}
}
}
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
// Convert return value
if (rtnvarp) {
funcp->addStmtsp(createDpiTemp(rtnvarp, ""));
funcp->addStmtsp(createAssignInternalToDpi(rtnvarp, false, tmpSuffixp, ""));
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
const std::string index = rtnvarp->basicp()->isDpiPrimitive() ? "" : "[0]";
funcp->addStmtsp(new AstCStmt{flp, "return " + rtnvarp->name() + index + ";"});
}
if (!makePortList(nodep, funcp)) return nullptr;
return funcp;
2009-12-20 14:27:00 +01:00
}
AstCFunc* makeDpiImportPrototype(AstNodeFTask* nodep, AstVar* rtnvarp) {
// Verilog name has __ conversion and other tricks, to match DPI C code, back that out
const string name = AstNode::prettyName(nodep->cname());
checkLegalCIdentifier(nodep, name);
// Tasks (but not void functions) return a boolean 'int' indicating disabled
const string rtnType = rtnvarp ? rtnvarp->dpiArgType(true, true)
: nodep->dpiTask() ? "int"
: "";
AstCFunc* const funcp = new AstCFunc{nodep->fileline(), name, m_scopep, rtnType};
funcp->dpiContext(nodep->dpiContext());
funcp->dpiImportPrototype(true);
funcp->dontCombine(true);
funcp->entryPoint(false);
funcp->isMethod(false);
funcp->protect(false);
funcp->dpiPure(nodep->dpiPure());
const int cost = static_cast<int>(V3Control::getProfileData(funcp->name()));
m_statHierDpisWithCosts += (cost != 0);
funcp->cost(cost);
// Add DPI Import to top, since it's a global function
m_topScopep->scopep()->addBlocksp(funcp);
if (!makePortList(nodep, funcp)) return nullptr;
return funcp;
}
AstCFunc* getDpiFunc(AstNodeFTask* nodep, AstVar* rtnvarp) {
UASSERT_OBJ(nodep->dpiImport() || nodep->dpiExport(), nodep, "Not a DPI function");
// Compute unique signature of this DPI function
const string signature = dpiSignature(nodep, rtnvarp);
// Only create one DPI Import prototype or DPI Export entry point for each unique cname as
// it is illegal for the user to attach multiple tasks with different signatures to one DPI
// cname.
const auto pair = m_dpiNames.emplace(std::piecewise_construct, //
std::forward_as_tuple(nodep->cname()),
std::forward_as_tuple(nodep, signature, nullptr));
if (pair.second) {
// First time encountering this cname. Create Import prototype / Export entry point
AstCFunc* const funcp = nodep->dpiExport() ? makeDpiExportDispatcher(nodep, rtnvarp)
: makeDpiImportPrototype(nodep, rtnvarp);
std::get<2>(pair.first->second) = funcp;
return funcp;
} else {
// Seen this cname import before. Check if it's the same prototype.
const AstNodeFTask* firstNodep;
string firstSignature;
AstCFunc* firstFuncp;
std::tie(firstNodep, firstSignature, firstFuncp) = pair.first->second;
if (signature != firstSignature) {
// Different signature, so error.
nodep->v3error("Duplicate declaration of DPI function with different signature: '"
<< nodep->cname() << "'\n"
<< nodep->warnContextPrimary() << '\n'
<< nodep->warnMore() //
<< "... New signature: " << signature << '\n' //
<< firstNodep->warnOther()
<< "... Original signature: " << firstSignature << '\n' //
<< firstNodep->warnContextSecondary());
return nullptr;
} else {
// Same signature, return the previously created CFunc
return firstFuncp;
}
}
}
static bool makePortList(AstNodeFTask* nodep, AstCFunc* dpip) {
bool allOk = true;
// Copy nodep's list of function I/O to the new dpip c function
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
if (portp->isIO()) {
// Move it to new function
AstVar* const newPortp = portp->cloneTree(false);
newPortp->funcLocal(true);
dpip->addArgsp(newPortp);
if (!portp->basicp()) {
allOk = false;
portp->v3warn(
E_UNSUPPORTED,
"Unsupported: DPI argument of type "
<< portp->dtypep()->prettyTypeName() << '\n'
<< portp->warnMore()
<< "... For best portability, use bit, byte, int, or longint");
// We don't warn on logic either, although the 4-stateness is lost.
// That's what other simulators do.
}
}
}
}
return allOk;
}
void bodyDpiImportFunc(AstNodeFTask* nodep, AstVarScope* rtnvscp, AstCFunc* cfuncp,
AstCFunc* dpiFuncp) {
const char* const tmpSuffixp = V3Task::dpiTemporaryVarSuffix();
if (v3Global.opt.profExec())
cfuncp->addStmtsp(
new AstCStmt{nodep->fileline(),
"VL_EXEC_TRACE_ADD_RECORD(vlSymsp).sectionPush(\"dpiimports\");"});
// Convert input/inout arguments to DPI types
string args;
for (AstNode* stmtp = cfuncp->argsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
AstVarScope* const portvscp
= VN_AS(portp->user2p(), VarScope); // Remembered when we created it earlier
if (portp->isIO() && !portp->isFuncReturn() && portvscp != rtnvscp
&& portp->name() != "__Vscopep" // Passed to dpiContext, not callee
&& portp->name() != "__Vfilenamep" && portp->name() != "__Vlineno") {
if (args != "") args += ", ";
if (portp->isDpiOpenArray()) {
AstNodeDType* const dtypep = portp->dtypep()->skipRefp();
UASSERT_OBJ(!VN_IS(dtypep, DynArrayDType) && !VN_IS(dtypep, QueueDType),
portp,
"Passing dynamic array or queue as actual argument to DPI "
"open array is not yet supported");
// Ideally we'd make a table of variable
// characteristics, and reuse it wherever we can
// At least put them into the module's CTOR as static?
const string propName = portp->name() + "__Vopenprops";
const string propCode = portp->vlPropDecl(propName);
2022-11-20 23:40:38 +01:00
cfuncp->addStmtsp(new AstCStmt{portp->fileline(), propCode});
//
// At runtime we need the svOpenArrayHandle to
// point to this task & thread's data, in addition
// to static info about the variable
const string name = portp->name() + "__Vopenarray";
const string varCode
= ("VerilatedDpiOpenVar "
// NOLINTNEXTLINE(performance-inefficient-string-concatenation)
+ name + " (&" + propName + ", &" + portp->name() + ");\n");
2022-11-20 23:40:38 +01:00
cfuncp->addStmtsp(new AstCStmt{portp->fileline(), varCode});
args += "&" + name;
} else {
if (portp->isWritable() && portp->basicp()->isDpiPrimitive()) {
if (!VN_IS(portp->dtypep()->skipRefp(), UnpackArrayDType)) args += "&";
}
args += portp->name() + tmpSuffixp;
cfuncp->addStmtsp(createDpiTemp(portp, tmpSuffixp));
if (portp->isNonOutput()) {
cfuncp->addStmtsp(
createAssignInternalToDpi(portp, false, "", tmpSuffixp));
}
}
}
}
}
// Store context, if needed
if (nodep->dpiContext()) {
const string stmt = "Verilated::dpiContext(__Vscopep, __Vfilenamep, __Vlineno);";
2022-11-20 23:40:38 +01:00
cfuncp->addStmtsp(new AstCStmt{nodep->fileline(), stmt});
}
{ // Call the imported function
2022-11-20 23:40:38 +01:00
AstCCall* const callp = new AstCCall{nodep->fileline(), dpiFuncp};
callp->dtypeSetVoid();
callp->argTypes(args);
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
if (rtnvscp) {
// If it has a return value, capture it
cfuncp->addStmtsp(createDpiTemp(rtnvscp->varp(), tmpSuffixp));
const std::string sel = rtnvscp->varp()->basicp()->isDpiPrimitive() ? "" : "[0]";
AstCStmt* const cstmtp = new AstCStmt{nodep->fileline()};
cstmtp->add(rtnvscp->varp()->name() + tmpSuffixp + sel); // LHS
cstmtp->add(" = ");
cstmtp->add(callp); // RHS
cstmtp->add(";");
cfuncp->addStmtsp(cstmtp);
} else {
// Othervise just call it
cfuncp->addStmtsp(callp->makeStmt());
}
}
// Convert output/inout arguments back to internal type
for (AstNode* stmtp = cfuncp->argsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
portp->protect(false); // No additional exposure - already part of shown proto
if (portp->isIO() && (portp->isWritable() || portp->isFuncReturn())
&& !portp->isDpiOpenArray()) {
AstVarScope* const portvscp = VN_AS(
portp->user2p(), VarScope); // Remembered when we created it earlier
cfuncp->addStmtsp(
createAssignDpiToInternal(portvscp, portp->name() + tmpSuffixp));
}
}
}
if (v3Global.opt.profExec())
cfuncp->addStmtsp(new AstCStmt{nodep->fileline(),
"VL_EXEC_TRACE_ADD_RECORD(vlSymsp).sectionPop();"});
}
AstVarScope* getDpiExporTrigger() {
AstNetlist* const netlistp = v3Global.rootp();
AstVarScope* dpiExportTriggerp = netlistp->dpiExportTriggerp();
if (!dpiExportTriggerp) {
// Create the global DPI export trigger flag the first time we encounter a DPI export.
// This flag is set any time a DPI export is invoked, and cleared at the end of eval.
FileLine* const fl = m_topScopep->fileline();
const string name{"__Vdpi_export_trigger"};
AstVar* const varp = new AstVar{fl, VVarType::VAR, name, VFlagBitPacked{}, 1};
m_topScopep->scopep()->modp()->addStmtsp(varp);
dpiExportTriggerp = new AstVarScope{fl, m_topScopep->scopep(), varp};
m_topScopep->scopep()->addVarsp(dpiExportTriggerp);
netlistp->dpiExportTriggerp(dpiExportTriggerp);
}
return dpiExportTriggerp;
}
AstCFunc* makeUserFunc(AstNodeFTask* nodep, bool ftaskNoInline) {
// Given a already cloned node, make a public C function, or a non-inline C function
// Probably some of this work should be done later, but...
// should the type of the function be bool/uint32/64 etc (based on lookup) or IData?
AstNode::user2ClearTree();
AstVar* rtnvarp = nullptr;
if (nodep->isFunction()) {
AstVar* const portp = VN_AS(nodep->fvarp(), Var);
UASSERT_OBJ(portp, nodep, "function without function output variable");
UASSERT_OBJ(portp->isFuncReturn(), nodep, "Not marked as function return var");
if (nodep->dpiImport() || nodep->dpiExport()) {
AstBasicDType* const bdtypep = portp->dtypep()->basicp();
if (!bdtypep->isDpiPrimitive()) {
if (bdtypep->isDpiBitVec() && portp->width() > 32) {
portp->v3error("DPI function may not return a > 32 bits wide type "
"other than basic types.\n"
2023-01-05 23:59:51 +01:00
+ portp->warnMore()
+ "... Suggest make it an output argument instead?");
}
if (bdtypep->isDpiLogicVec()) {
portp->v3error("DPI function may not return a 4-state type "
"other than a single 'logic' (IEEE 1800-2023 35.5.5)");
}
}
} else if (nodep->taskPublic()) {
if (portp->isWide()) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Public functions with return > 64 bits wide.\n"
2023-01-05 23:59:51 +01:00
+ nodep->warnMore()
+ "... Suggest make it an output argument instead?");
}
}
if (ftaskNoInline || nodep->dpiExport()) {
portp->funcReturn(false); // Converting return to 'outputs'
}
unlinkAndClone(nodep, portp, false);
rtnvarp = portp;
rtnvarp->funcLocal(true);
rtnvarp->name(rtnvarp->name()
+ "__Vfuncrtn"); // Avoid conflict with DPI function name
if (nodep->dpiImport() || nodep->dpiExport()) rtnvarp->protect(false);
}
// Create/pick up the DPI CFunc for DPI Import/ DPI Export.
AstCFunc* dpiFuncp = nullptr;
if (nodep->dpiImport() || nodep->dpiExport()) {
UASSERT_OBJ(!(nodep->dpiOpenParent() && nodep->dpiOpenChild()), nodep,
"DPI task should not be both parent and child");
dpiFuncp = getDpiFunc(nodep, rtnvarp);
2025-09-10 23:42:45 +02:00
if (!dpiFuncp || (nodep->dpiImport() && nodep->dpiOpenParent())) {
// No need to make more than just the DPI Import prototype, the children will
// create the wrapper implementations.
2025-09-10 23:42:45 +02:00
if (rtnvarp) VL_DO_DANGLING(pushDeletep(rtnvarp), rtnvarp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return nullptr;
}
}
AstVarScope* rtnvscp = nullptr;
if (rtnvarp) {
2022-11-20 23:40:38 +01:00
rtnvscp = new AstVarScope{rtnvarp->fileline(), m_scopep, rtnvarp};
m_scopep->addVarsp(rtnvscp);
rtnvarp->user2p(rtnvscp);
}
string prefix;
if (nodep->dpiImport()) {
prefix = "__Vdpiimwrap_";
} else if (nodep->dpiExport()) {
prefix = "__Vdpiexp_";
} else if (ftaskNoInline) {
prefix = "__VnoInFunc_";
}
// Unless public, v3Descope will not uniquify function names even if duplicate per-scope,
// so make it unique now.
string suffix; // So, make them unique
2020-08-24 02:27:25 +02:00
if (!nodep->taskPublic() && !nodep->classMethod()) suffix = "_" + m_scopep->nameDotless();
const string name = ((nodep->name() == "new") ? "new" : prefix + nodep->name() + suffix);
2022-11-20 23:40:38 +01:00
AstCFunc* const cfuncp = new AstCFunc{
nodep->fileline(), name, m_scopep,
2022-11-20 23:40:38 +01:00
((nodep->taskPublic() && rtnvarp) ? rtnvarp->cPubArgType(true, true) : "")};
// It's ok to combine imports because this is just a wrapper;
// duplicate wrappers can get merged.
cfuncp->dontCombine(!nodep->dpiImport());
cfuncp->entryPoint(!nodep->dpiImport());
cfuncp->funcPublic(nodep->taskPublic());
cfuncp->dpiContext(nodep->dpiContext());
cfuncp->dpiExportImpl(nodep->dpiExport());
cfuncp->dpiImportWrapper(nodep->dpiImport());
cfuncp->recursive(nodep->recursive());
if (nodep->dpiImport() || nodep->dpiExport()) {
cfuncp->isStatic(true);
cfuncp->isLoose(true);
} else {
cfuncp->isStatic(false);
}
2020-08-24 02:27:25 +02:00
cfuncp->isVirtual(nodep->isVirtual());
cfuncp->dpiPure(nodep->dpiPure());
if (nodep->name() == "new") cfuncp->isConstructor(true);
if (cfuncp->dpiExportImpl()) cfuncp->cname(nodep->cname());
if (cfuncp->dpiImportWrapper()) cfuncp->cname(nodep->cname());
const bool needSyms
= (!nodep->dpiImport() && !nodep->taskPublic()) || v3Global.opt.profExec();
if (needSyms) cfuncp->argTypes(EmitCUtil::symClassVar());
if (!nodep->dpiImport() && !nodep->taskPublic()) {
// Need symbol table
if (cfuncp->name() == "new") {
const string stmt = VIdProtect::protect("_ctor_var_reset") + "(vlSymsp);";
cfuncp->addStmtsp(new AstCStmt{nodep->fileline(), stmt});
}
}
if (nodep->dpiContext()) {
// First three args go to dpiContext call
createInputVar(cfuncp, "__Vscopep", VBasicDTypeKwd::SCOPEPTR);
createInputVar(cfuncp, "__Vfilenamep", VBasicDTypeKwd::CHARPTR);
createInputVar(cfuncp, "__Vlineno", VBasicDTypeKwd::INT);
}
if (nodep->dpiExport()) {
AstScopeName* const snp = nodep->scopeNamep();
UASSERT_OBJ(snp, nodep, "Missing scoping context");
// The AstScopeName is really a statement(ish) for tracking, not a function
snp->dpiExport(true);
snp->unlinkFrBack();
cfuncp->addStmtsp(snp);
}
// Create list of arguments and move to function
for (AstNode *nextp, *stmtp = nodep->stmtsp(); stmtp; stmtp = nextp) {
nextp = stmtp->nextp();
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
2021-07-22 22:09:24 +02:00
if (portp->isParam() && VN_IS(portp->valuep(), InitArray)) {
// Move array parameters in functions into constant pool
portp->unlinkFrBack();
2021-07-22 22:09:24 +02:00
pushDeletep(portp);
AstNode* const tablep = v3Global.rootp()->constPoolp()->findTable(
VN_AS(portp->valuep(), InitArray));
2021-07-22 22:09:24 +02:00
portp->user2p(tablep);
} else {
2021-07-22 22:09:24 +02:00
if (portp->isIO()) {
// Move it to new function
unlinkAndClone(nodep, portp, false);
2021-07-22 22:09:24 +02:00
portp->funcLocal(true);
cfuncp->addArgsp(portp);
} else {
// "Normal" variable, mark inside function
portp->funcLocal(true);
}
AstVarScope* const newvscp
= new AstVarScope{portp->fileline(), m_scopep, portp};
m_scopep->addVarsp(newvscp);
2021-07-22 22:09:24 +02:00
portp->user2p(newvscp);
}
}
}
// Fake output variable if was a function. It's more efficient to
// have it last, rather than first, as the C compiler can sometimes
// avoid copying variables when calling shells if argument 1
// remains argument 1 (which it wouldn't if a return got added).
if (rtnvarp) cfuncp->addArgsp(rtnvarp);
// Move body
AstNode* bodysp = nodep->stmtsp();
if (bodysp) {
unlinkAndClone(nodep, bodysp, true);
AstBegin* const tempp
= new AstBegin{nodep->fileline(), "[EditWrapper]", bodysp, false};
VL_DANGLING(bodysp);
// If we cloned due to recursion, now need to rip out the ports
// (that remained in place) then got cloned
for (AstNode *nextp, *stmtp = tempp->stmtsp(); stmtp; stmtp = nextp) {
nextp = stmtp->nextp();
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
2025-09-10 23:42:45 +02:00
if (portp->isIO()) VL_DO_DANGLING(pushDeletep(portp->unlinkFrBack()), portp);
}
}
if (tempp->stmtsp()) cfuncp->addStmtsp(tempp->stmtsp()->unlinkFrBackWithNext());
VL_DO_DANGLING(tempp->deleteTree(), tempp);
}
if (nodep->dpiImport()) bodyDpiImportFunc(nodep, rtnvscp, cfuncp, dpiFuncp);
// Return statement
if (rtnvscp && nodep->taskPublic()) {
cfuncp->addStmtsp(new AstCReturn{
2022-11-20 23:40:38 +01:00
rtnvscp->fileline(), new AstVarRef{rtnvscp->fileline(), rtnvscp, VAccess::READ}});
}
// Replace variable refs
relink(cfuncp);
if (cfuncp->dpiExportImpl()) {
// Mark all non-local variables written by the DPI exported function as being updated
// by DPI exports. This ensures correct ordering and change detection later.
// Mark non-local variables written by the exported function
bool writesNonLocals = false;
cfuncp->foreach([&writesNonLocals](AstVarRef* refp) {
if (refp->access().isReadOnly()) return; // Ignore read reference
AstVar* const varp = refp->varScopep()->varp();
// We are ignoring function locals as they should not be referenced anywhere
// outside the enclosing AstCFunc, hence they are irrelevant for code ordering.
if (varp->isFuncLocal()) return;
// Mark it as written by DPI export
varp->setWrittenByDpi();
// Remember we had some
writesNonLocals = true;
});
// If this DPI export writes some non-local variables, set the DPI Export Trigger flag
// in the function.
if (writesNonLocals) {
AstVarScope* const dpiExportTriggerp = getDpiExporTrigger();
FileLine* const flp = cfuncp->fileline();
// Set DPI export trigger flag every time the DPI export is called.
AstAssign* const assignp
= new AstAssign{flp, new AstVarRef{flp, dpiExportTriggerp, VAccess::WRITE},
new AstConst{flp, AstConst::BitTrue{}}};
// Add as first statement (to avoid issues with early returns) to exported function
if (cfuncp->stmtsp()) {
cfuncp->stmtsp()->addHereThisAsNext(assignp);
} else {
cfuncp->addStmtsp(assignp);
}
}
}
2023-06-01 16:02:08 +02:00
// Mark the fact that this function allocates std::process
if (nodep->needProcess()) cfuncp->setNeedProcess();
2023-06-01 16:02:08 +02:00
// Delete rest of cloned task and return new func
VL_DO_DANGLING(pushDeletep(nodep), nodep);
UINFOTREE(9, cfuncp, "", "userFunc");
return cfuncp;
}
void iterateIntoFTask(AstNodeFTask* nodep) {
// Iterate into the FTask we are calling. Note it may be under a different
// scope then the caller, so we need to restore state.
VL_RESTORER(m_scopep);
VL_RESTORER(m_insStmtp);
m_scopep = m_statep->getScope(nodep);
iterate(nodep);
}
void insertBeforeStmt(AstNode* nodep, AstNode* newp) {
UINFOTREE(9, nodep, "", "newstmt");
UASSERT_OBJ(m_insStmtp, nodep, "Function call not underneath a statement");
UINFOTREE(9, newp, "", "newfunc");
m_insStmtp->addHereThisAsNext(newp);
}
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
VL_RESTORER(m_modNCalls);
m_modp = nodep;
m_modNCalls = 0;
iterateChildren(nodep);
UASSERT_OBJ(!m_insStmtp, nodep, "Didn't finish out last statement");
}
void visit(AstWith* nodep) override {
if (nodep->user1SetOnce()) {
// Make sure that the return expression is converted only once
return;
}
AstNodeExpr* const withExprp = VN_AS(nodep->exprp()->unlinkFrBack(), NodeExpr);
nodep->addExprp(new AstCReturn{withExprp->fileline(), withExprp});
iterateChildren(nodep);
}
void visit(AstScope* nodep) override {
VL_RESTORER(m_scopep);
m_scopep = nodep;
iterateChildren(nodep);
UASSERT_OBJ(!m_insStmtp, nodep, "Didn't finish out last statement");
}
void visit(AstCNew* nodep) override {
VL_RESTORER(m_inNew);
m_inNew = true;
iterateChildren(nodep);
}
void visit(AstNodeFTaskRef* nodep) override {
if (m_inSensesp && !nodep->isPure()) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Impure function calls in sensitivity lists");
nodep->taskp(nullptr); // So V3Broken doesn't complain
return;
}
2020-04-13 00:57:12 +02:00
// Includes handling AstMethodCall, AstNew
UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked?");
iterateIntoFTask(nodep->taskp()); // First, do hierarchical funcs
UINFO(4, " FTask REF " << nodep);
UINFOTREE(9, nodep, "", "inlfunc");
UASSERT_OBJ(m_scopep, nodep, "func ref not under scope");
const string namePrefix = ((VN_IS(nodep, FuncRef) ? "__Vfunc_" : "__Vtask_")
+ nodep->taskp()->shortName() + "__" + cvtToStr(m_modNCalls++));
// Create output variable
AstVarScope* outvscp = nullptr;
if (nodep->taskp()->isFunction()) {
// Not that it's a FUNCREF, but that we're calling a function (perhaps as a task)
outvscp
= createVarScope(VN_AS(nodep->taskp()->fvarp(), Var), namePrefix + "__Vfuncout");
}
// Create cloned statements
AstNode* beginp;
AstCNew* cnewp = nullptr;
if (m_statep->ftaskNoInline(nodep->taskp())) {
// This may share VarScope's with a public task, if any. Yuk.
2020-04-13 00:57:12 +02:00
beginp = createNonInlinedFTask(nodep, namePrefix, outvscp, cnewp /*ref*/);
} else {
beginp = createInlinedFTask(nodep, namePrefix, outvscp);
++m_statInlines;
}
2025-07-27 16:31:15 +02:00
if (VN_IS(nodep, New)) { // New not legal as while() condition
insertBeforeStmt(nodep, beginp);
2020-04-13 00:57:12 +02:00
UASSERT_OBJ(cnewp, nodep, "didn't create cnew for new");
nodep->replaceWith(cnewp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
} else if (VN_IS(nodep->backp(), NodeAssign)) {
UASSERT_OBJ(nodep->taskp()->isFunction(), nodep,
"funcref-like assign to non-function");
insertBeforeStmt(nodep, beginp);
2022-11-20 23:40:38 +01:00
AstVarRef* const outrefp = new AstVarRef{nodep->fileline(), outvscp, VAccess::READ};
nodep->replaceWith(outrefp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
} else if (!VN_IS(nodep->backp(), StmtExpr)) {
UASSERT_OBJ(nodep->taskp()->isFunction(), nodep,
"funcref-like expression to non-function");
AstVarRef* const outrefp = new AstVarRef{nodep->fileline(), outvscp, VAccess::READ};
AstExprStmt* lambdap = new AstExprStmt{nodep->fileline(), beginp, outrefp};
if (m_inNew) {
AstVar* varp = outvscp->varp();
varp->funcLocal(true);
// Create a new var that will be inside the lambda
AstVar* newvarp = varp->cloneTree(false);
// Replace all references so they point to the new var
lambdap->stmtsp()->foreachAndNext([varp, newvarp](AstVarRef* refp) {
if (refp->varp() == varp) refp->varp(newvarp);
});
// Add variable initialization
lambdap->stmtsp()->addHereThisAsNext(newvarp);
lambdap->hasResult(false);
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
// Add return statement - TODO: Why not VarRef(outvscp)?
AstCExpr* const exprp = new AstCExpr{nodep->fileline(), varp->name()};
exprp->dtypeSetString();
lambdap->addStmtsp(new AstCReturn{nodep->fileline(), exprp});
}
// AstExprStmt is currently treated as impure, so clear the cached purity of its
// parents
nodep->replaceWith(lambdap);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
VIsCached::clearCacheTree();
} else { // VN_IS(nodep->backp(), StmtExpr)
insertBeforeStmt(nodep, beginp);
if (nodep->taskp()->isFunction()
&& !nodep->backp()->fileline()->warnIsOff(V3ErrorCode::IGNOREDRETURN)) {
nodep->v3warn(
IGNOREDRETURN,
"Ignoring return value of non-void function (IEEE 1800-2023 13.4.1)");
2019-03-10 19:57:01 +01:00
}
nodep->unlinkFrBack();
VL_DO_DANGLING(nodep->deleteTree(), nodep);
2019-03-10 19:57:01 +01:00
}
UINFO(4, " FTask REF Done.");
}
void visit(AstNodeFTask* nodep) override {
UINFO(4, " visitFTask " << nodep);
VL_RESTORER(m_insStmtp);
m_insStmtp = nodep->stmtsp(); // Might be null if no statements, but we won't use it
if (!nodep->user1SetOnce()) { // Just one creation needed per function
// Expand functions in it
int modes = 0;
if (nodep->dpiImport()) modes++;
if (nodep->dpiExport()) modes++;
if (nodep->taskPublic()) modes++;
if (nodep->classMethod()) modes++;
if (v3Global.opt.protectIds() && nodep->taskPublic()) {
// We always call protect() on names, we don't check if public or not
// Hence any external references wouldn't be able to find the refed public object.
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Using --protect-ids with public function");
}
if (modes > 1) {
nodep->v3error("Cannot mix DPI import, DPI export, class methods, and/or public "
"on same function: "
<< nodep->prettyNameQ());
}
if (nodep->dpiImport() || nodep->dpiExport() || nodep->taskPublic()
|| m_statep->ftaskNoInline(nodep)) {
// Clone it first, because we may have later FTaskRef's that still need
// the original version.
if (m_statep->ftaskNoInline(nodep) && !nodep->classMethod()) {
m_statep->checkPurity(nodep);
}
AstNodeFTask* const clonedFuncp = nodep->cloneTree(false);
2020-08-24 01:37:56 +02:00
if (nodep->isConstructor()) m_statep->remapFuncClassp(nodep, clonedFuncp);
AstCFunc* const cfuncp = makeUserFunc(clonedFuncp, m_statep->ftaskNoInline(nodep));
if (cfuncp) {
nodep->addNextHere(cfuncp);
if (nodep->dpiImport() || m_statep->ftaskNoInline(nodep)) {
m_statep->ftaskCFuncp(nodep, cfuncp);
}
iterateIntoFTask(clonedFuncp); // Do the clone too
}
}
// Any variables inside the function still have varscopes pointing to them.
// We're going to delete the vars, so delete the varscopes.
if (nodep->isFunction()) {
if (AstVar* const portp = VN_CAST(nodep->fvarp(), Var)) {
AstVarScope* const vscp = m_statep->findVarScope(m_scopep, portp);
UINFO(9, " funcremovevsc " << vscp);
VL_DO_DANGLING(pushDeletep(vscp->unlinkFrBack()), vscp);
}
}
for (AstNode *nextp, *stmtp = nodep->stmtsp(); stmtp; stmtp = nextp) {
nextp = stmtp->nextp();
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
AstVarScope* const vscp = m_statep->findVarScope(m_scopep, portp);
UINFO(9, " funcremovevsc " << vscp);
VL_DO_DANGLING(pushDeletep(vscp->unlinkFrBack()), vscp);
}
}
// Just push for deletion, as other references to func may
// remain until visitor exits
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
void visit(AstNodeForeach* nodep) override { // LCOV_EXCL_LINE
nodep->v3fatalSrc(
"Foreach statements should have been converted to while statements in V3Begin.cpp");
}
void visit(AstNodeStmt* nodep) override {
VL_RESTORER(m_insStmtp);
m_insStmtp = nodep;
iterateChildren(nodep);
}
void visit(AstStmtExpr* nodep) override {
VL_RESTORER(m_insStmtp);
m_insStmtp = nodep;
iterateChildren(nodep);
if (!nodep->exprp()) VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
2022-09-16 17:15:10 +02:00
void visit(AstSenItem* nodep) override {
UASSERT_OBJ(!m_inSensesp, nodep, "Senitem under senitem?");
VL_RESTORER(m_inSensesp);
m_inSensesp = true;
iterateChildren(nodep);
}
//--------------------
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
2019-09-12 13:22:22 +02:00
// CONSTRUCTORS
TaskVisitor(AstNetlist* nodep, TaskStateVisitor* statep)
: m_statep{statep} {
iterate(nodep);
}
~TaskVisitor() {
V3Stats::addStat("Optimizations, Functions inlined", m_statInlines);
V3Stats::addStat("Optimizations, Hierarchical DPI wrappers with costs",
m_statHierDpisWithCosts);
}
};
//######################################################################
// Task class functions
const char* const V3Task::s_dpiTemporaryVarSuffix = "__Vcvt";
V3TaskConnects V3Task::taskConnects(AstNodeFTaskRef* nodep, AstNode* taskStmtsp,
V3TaskConnectState* statep, bool makeChanges) {
// Output list will be in order of the port declaration variables (so
// func calls are made right in C)
// Missing pin/expr? We return (pinvar, nullptr)
// Extra pin/expr? We clean it up
UINFO(9, "taskConnects " << nodep);
std::map<const std::string, int> nameToIndex;
V3TaskConnects tconnects;
UASSERT_OBJ(nodep->taskp(), nodep, "unlinked");
// Find ports
int tpinnum = 0;
AstVar* sformatp = nullptr;
for (AstNode* stmtp = taskStmtsp; stmtp; stmtp = stmtp->nextp()) {
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
if (portp->isIO()) {
2023-10-28 12:24:04 +02:00
tconnects.emplace_back(portp, static_cast<AstArg*>(nullptr));
nameToIndex.emplace(portp->name(), tpinnum); // For name based connections
++tpinnum;
if (portp->attrSFormat()) {
sformatp = portp;
} else if (sformatp) {
portp->v3error("/*verilator sformat*/ can only be applied to last argument of "
"a function");
}
}
}
}
// Find pins
int ppinnum = 0;
bool reorganize = false;
for (AstNode *nextp, *pinp = nodep->pinsp(); pinp; pinp = nextp) {
nextp = pinp->nextp();
if (VN_IS(pinp, With)) continue;
AstArg* const argp = VN_AS(pinp, Arg);
UASSERT_OBJ(argp, pinp, "Non-arg under ftask reference");
if (argp->name() != "") {
// By name
const auto it = nameToIndex.find(argp->name());
if (it == nameToIndex.end()) {
if (makeChanges) {
pinp->v3error("No such argument " << argp->prettyNameQ()
<< " in function call to "
<< nodep->taskp()->prettyTypeName());
// We'll just delete it; seems less error prone than making a false argument
VL_DO_DANGLING(pinp->unlinkFrBack()->deleteTree(), pinp);
}
} else {
if (tconnects[it->second].second && makeChanges) {
pinp->v3error("Duplicate argument " << argp->prettyNameQ()
<< " in function call to "
<< nodep->taskp()->prettyTypeName());
2025-09-11 13:01:36 +02:00
tconnects[it->second].second->unlinkFrBack()->deleteTree();
}
tconnects[it->second].second = argp;
reorganize = true;
}
} else { // By pin number
if (nodep->taskp()->prettyName() == "randomize") {
// Arguments to randomize() are special, will be handled in V3Randomize
} else if (ppinnum >= tpinnum) {
if (sformatp) {
2023-10-28 12:24:04 +02:00
tconnects.emplace_back(sformatp, static_cast<AstArg*>(nullptr));
tconnects[ppinnum].second = argp;
++tpinnum;
} else if (makeChanges) {
pinp->v3error("Too many arguments in function call to "
<< nodep->taskp()->prettyTypeName());
// We'll just delete it; seems less error prone than making a false argument
VL_DO_DANGLING(pinp->unlinkFrBack()->deleteTree(), pinp);
}
} else {
tconnects[ppinnum].second = argp;
}
}
++ppinnum;
}
if (!makeChanges) return tconnects;
// Connect missing ones
std::set<const AstVar*> argWrap; // Which ports are defaulted, forcing arg wrapper creation
for (int i = 0; i < tpinnum; ++i) {
AstVar* const portp = tconnects[i].first;
if (!tconnects[i].second || !tconnects[i].second->exprp()) {
AstNodeExpr* newvaluep = nullptr;
if (!portp->valuep()) {
nodep->v3error("Missing argument on non-defaulted argument "
<< portp->prettyNameQ() << " in function call to "
<< nodep->taskp()->prettyTypeName());
2022-11-20 23:40:38 +01:00
newvaluep = new AstConst{nodep->fileline(), AstConst::Unsized32{}, 0};
} else if (AstFuncRef* const funcRefp = VN_CAST(portp->valuep(), FuncRef)) {
const AstNodeFTask* const funcp = funcRefp->taskp();
if (funcp->classMethod() && funcp->isStatic()) newvaluep = funcRefp;
} else if (AstConst* const constp = VN_CAST(portp->valuep(), Const)) {
newvaluep = constp;
}
if (!newvaluep) {
// The default value for this port might be a constant
// expression that hasn't been folded yet. Try folding it
// now; we don't have much to lose if it fails.
newvaluep = V3Const::constifyEdit(VN_AS(portp->valuep(), NodeExpr));
if (!VN_IS(newvaluep, Const)) {
if (statep) {
portp->pinNum(i + 1); // Make sure correct, will use to build name
UINFO(9, "taskConnects arg wrapper needed " << portp->valuep());
argWrap.emplace(portp);
} else { // statep = nullptr, called too late or otherwise to handle args
// Problem otherwise is we might have a varref, task
// call, or something else that only makes sense in the
// domain of the function (or class containing the method),
// versus that of the callee.
nodep->v3warn(
E_UNSUPPORTED,
"Unsupported: Non-constant default value in missing argument "
<< portp->prettyNameQ() << " in function call to "
<< nodep->taskp()->prettyTypeName());
newvaluep = new AstConst{nodep->fileline(), AstConst::Unsized32{}, 0};
}
}
}
2025-09-09 23:39:44 +02:00
newvaluep = newvaluep->backp() ? newvaluep->cloneTree(true) : newvaluep;
// To avoid problems with callee needing to know to deleteTree
// or not, we make this into a pin
UINFO(9, "Default pin for " << portp);
2022-11-20 23:40:38 +01:00
AstArg* const newp = new AstArg{nodep->fileline(), portp->name(), newvaluep};
if (tconnects[i].second) { // Have a "nullptr" pin already defined for it
VL_DO_CLEAR(tconnects[i].second->unlinkFrBack()->deleteTree(),
tconnects[i].second = nullptr);
}
tconnects[i].second = newp;
reorganize = true;
}
if (tconnects[i].second) {
UINFO(9, "Connect " << portp << " -> " << tconnects[i].second);
} else {
UINFO(9, "Connect " << portp << " -> NONE");
}
}
for (const auto& tconnect : tconnects) {
AstArg* const argp = tconnect.second;
argp->name(""); // Can forget name as will add back in pin order
}
if (reorganize) {
// To simplify downstream, put argument list back into pure pinnumber ordering
while (nodep->pinsp()) {
// Must unlink each pin, not all pins linked together as one list
nodep->pinsp()->unlinkFrBack();
}
for (int i = 0; i < tpinnum; ++i) {
AstArg* const argp = tconnects[i].second;
UASSERT_OBJ(argp, nodep, "Lost argument in func conversion");
nodep->addPinsp(argp);
}
}
2020-05-17 17:06:14 +02:00
if (debug() >= 9) { // LCOV_EXCL_START
nodep->dumpTree("- ftref-out: ");
for (int i = 0; i < tpinnum; ++i) {
UINFO(0, " pin " << i << " pin=" << cvtToHex(tconnects[i].first)
<< " conn=" << cvtToHex(tconnects[i].second));
}
} // LCOV_EXCL_STOP
if (!argWrap.empty()) {
UINFO(9, "Arg wrapper generation " << nodep);
// Create wrapper function with default argument settings.
// Needed because the default needs symbol table of the called function.
taskConnectWrap(nodep, tconnects, statep, argWrap);
// Regenerate all connections, this time connecting to the wrapper
return taskConnects(nodep, nodep->taskp()->stmtsp(),
// statep null, so can't recurse forever
nullptr);
}
return tconnects;
}
void V3Task::taskConnectWrap(AstNodeFTaskRef* nodep, const V3TaskConnects& tconnects,
V3TaskConnectState* statep, const std::set<const AstVar*>& argWrap) {
statep->setDidWrap();
// Make wrapper name such that is same iff same args are defaulted
std::string newname = nodep->name() + "__Vtcwrap";
for (const AstVar* varp : argWrap) newname += "_" + cvtToStr(varp->pinNum());
const auto pair = statep->wrapMap().emplace(std::piecewise_construct,
std::forward_as_tuple(nodep->taskp(), newname),
std::forward_as_tuple(nullptr));
if (pair.second) {
pair.first->second = taskConnectWrapNew(nodep->taskp(), newname, tconnects, argWrap);
}
AstNodeFTask* const newTaskp = pair.first->second;
// Remove the defaulted arguments from original outside call
for (const auto& tconnect : tconnects) {
const AstVar* const portp = tconnect.first;
AstArg* const argp = tconnect.second;
if (argWrap.find(portp) != argWrap.end()) { // Removed arg
statep->pushDeletep(argp->unlinkFrBack());
}
}
// Change outside call to connect to new function
nodep->taskp(newTaskp);
nodep->name(newTaskp->name());
// UINFOTREE(9, nodep, "", "taskConnectWrap-call");
}
AstNodeFTask* V3Task::taskConnectWrapNew(AstNodeFTask* taskp, const string& newname,
const V3TaskConnects& tconnects,
const std::set<const AstVar*>& argWrap) {
std::map<const AstVar*, AstVar*> oldNewVars; // Old -> new var mappings
AstNodeFTask* const newTaskp = taskp->cloneType(newname);
newTaskp->propagateAttrFrom(taskp);
taskp->addNextHere(newTaskp);
AstNodeFTaskRef* newCallp = nullptr;
AstNode* newCallInsertp = nullptr;
if (VN_IS(taskp, Func)) {
AstVar* const fvarp = VN_AS(taskp->fvarp(), Var);
UASSERT_OBJ(fvarp, taskp, "FuncRef without fvar");
AstVar* const newFVarp = fvarp->cloneTree(true);
oldNewVars.emplace(fvarp, newFVarp);
newFVarp->name(newTaskp->name());
newTaskp->fvarp(newFVarp);
newTaskp->dtypeFrom(newFVarp);
newCallp = new AstFuncRef{taskp->fileline(), VN_AS(taskp, Func), nullptr};
newCallInsertp
= new AstAssign{taskp->fileline(),
new AstVarRef{fvarp->fileline(), newFVarp, VAccess::WRITE}, newCallp};
newCallInsertp->dtypeFrom(newFVarp);
} else if (VN_IS(taskp, Task)) {
newCallp = new AstTaskRef{taskp->fileline(), VN_AS(taskp, Task), nullptr};
newCallInsertp = new AstStmtExpr{taskp->fileline(), newCallp};
} else {
taskp->v3fatalSrc("Unsupported: Non-constant default value in missing argument in a "
<< taskp->prettyTypeName());
}
// Create wrapper's ports matching original's
for (const auto& tconnect : tconnects) {
AstVar* const portp = tconnect.first;
AstVar* newPortp;
if (argWrap.find(portp) == argWrap.end()) { // Not removed arg
newPortp = new AstVar{portp->fileline(), portp->varType(), portp->name(), portp};
newPortp->propagateWrapAttrFrom(portp);
newPortp->funcLocal(true);
if (newPortp->valuep()) newPortp->valuep()->unlinkFrBack()->deleteTree();
newTaskp->addStmtsp(newPortp);
} else { // Defaulting arg
AstNodeExpr* const valuep = VN_AS(portp->valuep(), NodeExpr);
// Create local temporary
newPortp = new AstVar{portp->fileline(), VVarType::BLOCKTEMP, portp->name(),
portp->dtypep()};
newPortp->propagateAttrFrom(portp);
newPortp->funcLocal(true);
newTaskp->addStmtsp(newPortp);
// Runtime-assign it to the default
if (!VN_IS(valuep, EmptyQueue)) {
AstAssign* const newAssignp
= new AstAssign{valuep->fileline(),
new AstVarRef{valuep->fileline(), newPortp, VAccess::WRITE},
valuep->cloneTree(true)};
newTaskp->addStmtsp(newAssignp);
}
}
oldNewVars.emplace(portp, newPortp);
const VAccess pinAccess = portp->isWritable() ? VAccess::WRITE : VAccess::READ;
AstArg* const newArgp = new AstArg{portp->fileline(), portp->name(),
new AstVarRef{portp->fileline(), newPortp, pinAccess}};
newCallp->addPinsp(newArgp);
}
// Create wrapper call to original, passing arguments, adding setting of return value
newTaskp->addStmtsp(newCallInsertp);
// Replace any varref's to original to new ports (e.g. in argument equations)
newTaskp->foreach([=](AstVarRef* refp) {
const auto it = oldNewVars.find(refp->varp());
if (it != oldNewVars.end()) refp->varp(it->second);
});
// UINFOTREE(9, newTaskp, "", "taskConnectWrap-new");
return newTaskp;
}
string V3Task::assignInternalToDpi(AstVar* portp, bool isPtr, const string& frSuffix,
const string& toSuffix, const string& frPrefix) {
// Create assignment from internal format into DPI temporary
// Internal representation is scalar, 1D, or multi-dimensional array (similar to SV)
// DPI temporary is scalar or 1D array (if unpacked array)
string stmt;
string ket;
// Someday we'll have better type support, and this can make variables and casts.
// But for now, we'll just text-bash it.
const string frName = frPrefix + portp->name() + frSuffix;
const string toName = portp->name() + toSuffix;
const string idx = portp->name() + "__Vidx";
size_t unpackSize = 1; // non-unpacked array is treated as size 1
int unpackDim = 0;
if (AstUnpackArrayDType* const unpackp
= VN_CAST(portp->dtypep()->skipRefp(), UnpackArrayDType)) {
unpackSize = unpackp->arrayUnpackedElements();
unpackDim = unpackp->dimensions(false).second;
if (unpackDim > 0) UASSERT_OBJ(unpackSize > 0, portp, "size must be greater than 0");
}
if (portp->basicp()->isDpiBitVec() || portp->basicp()->isDpiLogicVec()) {
const bool isBit = portp->basicp()->isDpiBitVec();
const bool needsFor = unpackSize > 1;
if (needsFor) {
stmt = "for (size_t " + idx + " = 0; " + idx + " < " + cvtToStr(unpackSize) + "; ++"
+ idx + ") ";
}
stmt += (isBit ? "VL_SET_SVBV_"s : "VL_SET_SVLV_"s)
+ portp->dtypep()->skipRefp()->charIQWN() + "(" + cvtToStr(portp->width()) + ", ";
stmt += toName;
if (needsFor) {
stmt += " + " + cvtToStr(portp->dtypep()->skipRefp()->widthWords()) + " * " + idx;
}
stmt += ", ";
if (unpackDim > 0) { // Access multi-dimensional array as a 1D array
stmt += "(&" + frName;
for (int i = 0; i < unpackDim; ++i) stmt += "[0]";
stmt += ")[" + (needsFor ? idx : "0") + "])";
} else {
stmt += frName + ")";
}
} else {
const bool isChandle
= portp->basicp() && portp->basicp()->keyword() == VBasicDTypeKwd::CHANDLE;
const bool isString
= portp->basicp() && portp->basicp()->keyword() == VBasicDTypeKwd::STRING;
const string unpackAt = unpackSize > 1 ? "[" + idx + "]" : "[0]";
if (unpackDim > 0) {
const string forStmt = unpackSize > 1
? "for (size_t " + idx + " = 0; " + idx + " < "
+ cvtToStr(unpackSize) + "; ++" + idx + ") "
: "";
stmt += forStmt + toName + unpackAt;
} else {
if (isPtr) stmt += "*"; // DPI outputs are pointers
stmt += toName;
}
stmt += " = ";
if (isChandle) {
stmt += "VL_CVT_Q_VP(";
ket += ")";
}
if (unpackDim > 0) {
stmt += "(&" + frName;
for (int i = 0; i < unpackDim; ++i) stmt += "[0]";
stmt += ")" + unpackAt;
} else {
stmt += frName;
}
if (isString) stmt += ".c_str()";
}
stmt += ket + ";";
return stmt;
}
// Create assignment from DPI temporary into internal format
// DPI temporary is scalar or 1D array (if unpacked array)
// Internal representation is scalar, 1D, or multi-dimensional array (similar to SV)
std::string V3Task::assignDpiToInternal(const std::string& lhsName, AstVar* varp) {
std::string cvt;
bool useSvVec;
std::tie(cvt, useSvVec) = TaskDpiUtils::dpiToInternalCvtStmt(varp);
const std::vector<std::pair<int, int>> strides = TaskDpiUtils::unpackDimsAndStrides(varp);
const int total = strides.empty() ? 1 : strides[0].first * strides[0].second;
const int widthWords = varp->basicp()->widthWords();
string statements;
for (int i = 0; i < total; ++i) {
std::string lhs = lhsName;
// extract a scalar from multi-dimensional array (internal format)
for (const auto& stride : strides) {
lhs += "[" + std::to_string((i / stride.second) % stride.first) + "]";
}
// extract a scalar from DPI temporary var that is scalar or 1D array
if (useSvVec) {
const std::string offset = std::to_string(i * widthWords);
statements += cvt + " " + lhs + ", " + varp->name() + " + " + offset + ");\n";
} else {
const std::string elem = strides.empty() ? "" : "[" + std::to_string(i) + "]";
statements += lhs + " = " + cvt + varp->name() + elem + ")" + ";\n";
}
}
return statements;
}
void V3Task::taskAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{
TaskStateVisitor visitors{nodep};
const TaskVisitor visitor{nodep, &visitors};
} // Destruct before checking
V3Global::dumpCheckGlobalTree("task", 0, dumpTreeEitherLevel() >= 3);
}