mirror of
https://github.com/verilator/verilator.git
synced 2025-02-01 11:14:03 +00:00
2026 lines
94 KiB
C++
2026 lines
94 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Add temporaries, such as for task nodes
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// 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 "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:
|
|
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:
|
|
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
|
|
FuncToClassMap m_funcToClassMap; // Map for ctor func -> class
|
|
AstAssignW* m_assignwp = nullptr; // Current assignment
|
|
AstNodeFTask* m_ctorp = nullptr; // Class constructor
|
|
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;
|
|
}
|
|
AstClass* getClassp(AstNodeFTask* nodep) {
|
|
AstClass* const classp = m_funcToClassMap[nodep];
|
|
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
|
|
for (V3GraphEdge& edge : vxp->outEdges()) {
|
|
checkPurity(nodep, static_cast<TaskBaseVertex*>(edge.top()));
|
|
}
|
|
}
|
|
TaskFTaskVertex* getFTaskVertex(AstNodeFTask* nodep) {
|
|
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 << endl);
|
|
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(AstAssignW* nodep) override {
|
|
VL_RESTORER(m_assignwp);
|
|
m_assignwp = nodep;
|
|
VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep.
|
|
}
|
|
void visit(AstNodeFTaskRef* nodep) override {
|
|
// Includes handling AstMethodCall, AstNew
|
|
if (m_assignwp) {
|
|
// Wire assigns must become always statements to deal with insertion
|
|
// of multiple statements. Perhaps someday make all wassigns into always's?
|
|
UINFO(5, " IM_WireRep " << m_assignwp << endl);
|
|
m_assignwp->convertToAlways();
|
|
VL_DO_CLEAR(pushDeletep(m_assignwp), m_assignwp = nullptr);
|
|
}
|
|
// 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 << endl);
|
|
taskVtxp->noInline(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void visit(AstNodeFTask* nodep) override {
|
|
UINFO(9, " TASK " << nodep << endl);
|
|
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;
|
|
}
|
|
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 {
|
|
// Move initial statements into the constructor
|
|
m_initialps.clear();
|
|
m_ctorp = nullptr;
|
|
m_classp = nodep;
|
|
{ // 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()) {
|
|
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();
|
|
m_ctorp = nullptr;
|
|
m_classp = nullptr;
|
|
}
|
|
void visit(AstInitialAutomatic* nodep) override {
|
|
m_initialps.push_back(nodep);
|
|
iterateChildren(nodep);
|
|
}
|
|
//--------------------
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit TaskStateVisitor(AstNetlist* nodep) {
|
|
m_curVxp = new TaskCodeVertex{&m_callGraph};
|
|
//
|
|
iterate(nodep);
|
|
//
|
|
m_callGraph.removeRedundantEdgesSum(&TaskEdge::followAlwaysTrue);
|
|
if (dumpGraphLevel()) m_callGraph.dumpDotFilePrefixed("task_call");
|
|
}
|
|
~TaskStateVisitor() override = default;
|
|
VL_UNCOPYABLE(TaskStateVisitor);
|
|
};
|
|
|
|
//######################################################################
|
|
// DPI related utility functions
|
|
|
|
struct TaskDpiUtils final {
|
|
static std::vector<std::pair<AstUnpackArrayDType*, int>>
|
|
unpackDimsAndStrides(AstNodeDType* dtypep) {
|
|
std::vector<std::pair<AstUnpackArrayDType*, int>> dimStrides;
|
|
if (AstUnpackArrayDType* const unpackp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType)) {
|
|
const std::vector<AstUnpackArrayDType*> dims = unpackp->unpackDimensions();
|
|
dimStrides.resize(dims.size(), {nullptr, 0});
|
|
dimStrides.back() = {dims.back(), 1};
|
|
for (ssize_t i = dims.size() - 2; i >= 0; --i) {
|
|
dimStrides[i].first = dims[i];
|
|
dimStrides[i].second = dimStrides[i + 1].second * dims[i + 1]->elementsConst();
|
|
}
|
|
}
|
|
return dimStrides;
|
|
}
|
|
static bool dpiToInternalFrStmt(AstVar* portp, const string& frName, string& frstmt,
|
|
string& ket) {
|
|
ket.clear();
|
|
if (portp->basicp() && portp->basicp()->keyword() == VBasicDTypeKwd::CHANDLE) {
|
|
frstmt = "VL_CVT_VP_Q(" + frName;
|
|
ket = ")";
|
|
} else if (portp->basicp() && portp->basicp()->keyword() == VBasicDTypeKwd::STRING) {
|
|
frstmt = "VL_CVT_N_CSTR(" + frName;
|
|
ket = ")";
|
|
} else if ((portp->basicp() && portp->basicp()->isDpiPrimitive())) {
|
|
frstmt = frName;
|
|
} else {
|
|
const string frSvType = portp->basicp()->isDpiBitVec() ? "SVBV" : "SVLV";
|
|
if (portp->isWide()) {
|
|
// Need to convert to wide, using special function
|
|
frstmt = "VL_SET_W_" + frSvType + "(" + cvtToStr(portp->width()) + ",";
|
|
return true;
|
|
} else {
|
|
const AstNodeDType* const dtypep = portp->dtypep()->skipRefp();
|
|
frstmt = "VL_SET_" + string{dtypep->charIQWN()} + "_" + frSvType + "(";
|
|
if (VN_IS(dtypep, UnpackArrayDType)) frstmt += "&";
|
|
frstmt += frName;
|
|
ket = ")";
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
//######################################################################
|
|
// Task state, as a visitor of each AstNode
|
|
|
|
class TaskVisitor final : public VNVisitor {
|
|
// NODE STATE
|
|
// Each module:
|
|
// 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?
|
|
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
|
|
|
|
// METHODS
|
|
|
|
AstVarScope* createFuncVar(AstCFunc* funcp, const string& name, AstVar* examplep) {
|
|
AstVar* const newvarp = new AstVar{funcp->fileline(), VVarType::BLOCKTEMP, name, examplep};
|
|
newvarp->funcLocal(true);
|
|
funcp->addInitsp(newvarp);
|
|
AstVarScope* const newvscp = new AstVarScope{funcp->fileline(), m_scopep, newvarp};
|
|
m_scopep->addVarsp(newvscp);
|
|
return newvscp;
|
|
}
|
|
AstVarScope* createInputVar(AstCFunc* funcp, const string& name, VBasicDTypeKwd kwd) {
|
|
AstVar* const newvarp
|
|
= new AstVar{funcp->fileline(), VVarType::BLOCKTEMP, name, funcp->findBasicDType(kwd)};
|
|
newvarp->funcLocal(true);
|
|
newvarp->direction(VDirection::INPUT);
|
|
funcp->addArgsp(newvarp);
|
|
AstVarScope* const newvscp = new AstVarScope{funcp->fileline(), m_scopep, newvarp};
|
|
m_scopep->addVarsp(newvscp);
|
|
return newvscp;
|
|
}
|
|
AstVarScope* createVarScope(AstVar* invarp, const string& name) {
|
|
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));
|
|
} 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};
|
|
newvarp->funcLocal(false);
|
|
newvarp->propagateAttrFrom(invarp);
|
|
m_modp->addStmtsp(newvarp);
|
|
AstVarScope* const newvscp = new AstVarScope{newvarp->fileline(), m_scopep, newvarp};
|
|
m_scopep->addVarsp(newvscp);
|
|
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* soutPinp = VN_CAST(outPinp, Extend)) {
|
|
outPinp = soutPinp->lhsp();
|
|
} else if (AstNodeUniop* soutPinp = VN_CAST(outPinp, ExtendS)) {
|
|
outPinp = soutPinp->lhsp();
|
|
} else if (AstSel* soutPinp = VN_CAST(outPinp, Sel)) {
|
|
outPinp = soutPinp->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 << endl);
|
|
UINFO(9, " pin " << pinp << endl);
|
|
if (inlineTask) {
|
|
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->name() == "atWriteAppend"
|
|
|| cMethodp->name() == "atWriteAppendBack";
|
|
} else {
|
|
refArgOk = cMethodp->name() == "at" || cMethodp->name() == "atBack";
|
|
}
|
|
}
|
|
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");
|
|
}
|
|
}
|
|
} else if (portp->isInout()) {
|
|
// if (debug() >= 9) pinp->dumpTree("-pinrsize- ");
|
|
|
|
AstVarScope* const newvscp
|
|
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
|
portp->user2p(newvscp);
|
|
if (!inlineTask) {
|
|
pinp->replaceWith(
|
|
new AstVarRef{newvscp->fileline(), newvscp, VAccess::READWRITE});
|
|
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);
|
|
if (!inlineTask) {
|
|
pinp->replaceWith(new AstVarRef{newvscp->fileline(), newvscp, VAccess::WRITE});
|
|
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
|
|
portp->unlinkFrBack();
|
|
pushDeletep(portp); // Remove it from the clone (not original)
|
|
AstVarScope* const localVscp
|
|
= createVarScope(portp, namePrefix + "__" + portp->shortName());
|
|
portp->user2p(localVscp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Create function output variables
|
|
if (outvscp) {
|
|
// UINFO(0, "setflag on " << funcp->fvarp() << " to " << outvscp << endl);
|
|
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,
|
|
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)) {
|
|
AstCNew* const cnewp = new AstCNew{refp->fileline(), cfuncp};
|
|
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)) {
|
|
ccallp = new AstCMethodCall{refp->fileline(), mrefp->fromp()->unlinkFrBack(), cfuncp};
|
|
ccallp->dtypeSetVoid();
|
|
beginp->addNext(ccallp->makeStmt());
|
|
} else {
|
|
ccallp = new AstCCall{refp->fileline(), cfuncp};
|
|
ccallp->dtypeSetVoid();
|
|
beginp->addNext(ccallp->makeStmt());
|
|
}
|
|
|
|
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();
|
|
if (needSyms) ccallp->argTypes("vlSymsp");
|
|
|
|
if (refp->taskp()->dpiContext()) {
|
|
// __Vscopep
|
|
AstScopeName* const snp = refp->scopeNamep()->unlinkFrBack();
|
|
UASSERT_OBJ(snp, refp, "Missing scoping context");
|
|
ccallp->addArgsp(snp);
|
|
// __Vfilenamep
|
|
ccallp->addArgsp(new AstCExpr{refp->fileline(),
|
|
"\"" + refp->fileline()->filename() + "\"", 64, true});
|
|
// __Vlineno
|
|
ccallp->addArgsp(new AstConst(refp->fileline(), refp->fileline()->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);
|
|
}
|
|
|
|
if (outvscp) ccallp->addArgsp(new AstVarRef{refp->fileline(), outvscp, VAccess::WRITE});
|
|
|
|
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;
|
|
}
|
|
|
|
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) + ";\n";
|
|
return new AstCStmt{portp->fileline(), stmt};
|
|
}
|
|
|
|
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);
|
|
return new AstCStmt{portp->fileline(), stmt};
|
|
}
|
|
|
|
AstNode* createAssignDpiToInternal(AstVarScope* portvscp, const string& frName) {
|
|
// 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 portp = portvscp->varp();
|
|
string frstmt;
|
|
string ket;
|
|
const bool useSetWSvlv = TaskDpiUtils::dpiToInternalFrStmt(portp, frName, frstmt, ket);
|
|
// Use a AstCExpr, as we want V3Clean to mask off bits that don't make sense.
|
|
int cwidth = VL_IDATASIZE;
|
|
if (!useSetWSvlv && portp->basicp()) {
|
|
if (portp->basicp()->keyword().isBitLogic()) {
|
|
cwidth = VL_EDATASIZE * portp->widthWords();
|
|
} else {
|
|
cwidth = portp->basicp()->keyword().width();
|
|
}
|
|
}
|
|
|
|
const std::vector<std::pair<AstUnpackArrayDType*, int>> dimStrides
|
|
= TaskDpiUtils::unpackDimsAndStrides(portp->dtypep());
|
|
const int total = dimStrides.empty() ? 1
|
|
: dimStrides.front().first->elementsConst()
|
|
* dimStrides.front().second;
|
|
AstNode* newp = nullptr;
|
|
const int widthWords = portp->basicp()->widthWords();
|
|
for (int i = 0; i < total; ++i) {
|
|
AstNodeExpr* srcp = new AstVarRef{portvscp->fileline(), portvscp, VAccess::WRITE};
|
|
// extract a scalar from multi-dimensional array (internal format)
|
|
for (auto&& dimStride : dimStrides) {
|
|
const size_t dimIdx = (i / dimStride.second) % dimStride.first->elementsConst();
|
|
srcp = new AstArraySel(portvscp->fileline(), srcp, dimIdx);
|
|
}
|
|
AstNode* stmtp = nullptr;
|
|
// extract a scalar from DPI temporary var that is scalar or 1D array
|
|
if (useSetWSvlv) {
|
|
AstNode* const linesp = new AstText{portvscp->fileline(), frstmt + ket};
|
|
linesp->addNext(srcp);
|
|
linesp->addNext(
|
|
new AstText{portvscp->fileline(),
|
|
"," + frName + " + " + cvtToStr(i * widthWords) + ");\n"});
|
|
stmtp = new AstCStmt{portvscp->fileline(), linesp};
|
|
} else {
|
|
string from = frstmt;
|
|
if (!dimStrides.empty()) {
|
|
// e.g. time is 64bit svLogicVector
|
|
const int coef = portp->basicp()->isDpiLogicVec() ? widthWords : 1;
|
|
from += "[" + cvtToStr(i * coef) + "]";
|
|
}
|
|
from += ket;
|
|
AstNodeExpr* const rhsp = new AstSel{
|
|
portp->fileline(), new AstCExpr{portp->fileline(), from, cwidth, false}, 0,
|
|
portp->width()};
|
|
stmtp = new AstAssign{portp->fileline(), srcp, rhsp};
|
|
}
|
|
if (i > 0) {
|
|
newp->addNext(stmtp);
|
|
} else {
|
|
newp = stmtp;
|
|
}
|
|
}
|
|
return newp;
|
|
}
|
|
|
|
AstCFunc* makeDpiExportDispatcher(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);
|
|
const char* const tmpSuffixp = V3Task::dpiTemporaryVarSuffix();
|
|
AstCFunc* const funcp = new AstCFunc{nodep->fileline(), name, m_scopep,
|
|
(rtnvarp ? rtnvarp->dpiArgType(true, true) : "")};
|
|
funcp->dpiExportDispatcher(true);
|
|
funcp->dpiContext(nodep->dpiContext());
|
|
funcp->dontCombine(true);
|
|
funcp->entryPoint(true);
|
|
funcp->isStatic(true);
|
|
funcp->protect(false);
|
|
funcp->cname(name);
|
|
// Add DPI Export to top, since it's a global function
|
|
m_topScopep->scopep()->addBlocksp(funcp);
|
|
|
|
{ // Create dispatch wrapper
|
|
// Note this function may dispatch to myfunc 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.
|
|
string stmt;
|
|
// Static doesn't need save-restore as if below will re-fill proper value
|
|
stmt += "static int __Vfuncnum = -1;\n";
|
|
// First time init (faster than what the compiler does if we did a singleton
|
|
stmt += "if (VL_UNLIKELY(__Vfuncnum == -1)) __Vfuncnum = Verilated::exportFuncNum(\""
|
|
+ nodep->cname() + "\");\n";
|
|
// If the find fails, it will throw an error
|
|
stmt += "const VerilatedScope* __Vscopep = Verilated::dpiScope();\n";
|
|
// If dpiScope is fails and is null; the exportFind function throws and error
|
|
const string cbtype
|
|
= VIdProtect::protect(v3Global.opt.prefix() + "__Vcb_" + nodep->cname() + "_t");
|
|
stmt += cbtype + " __Vcb = (" + cbtype
|
|
+ ")(VerilatedScope::exportFind(__Vscopep, __Vfuncnum));\n"; // Can't use
|
|
// static_cast
|
|
// If __Vcb is null the exportFind function throws and error
|
|
funcp->addStmtsp(new AstCStmt{nodep->fileline(), stmt});
|
|
}
|
|
|
|
// Convert input/inout DPI arguments to Internal types
|
|
string args;
|
|
args += ("(" + EmitCBase::symClassName()
|
|
+ "*)(__Vscopep->symsp())"); // Upcast w/o overhead
|
|
AstNode* argnodesp = nullptr;
|
|
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
|
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
|
|
if (portp->isIO() && !portp->isFuncReturn() && portp != rtnvarp) {
|
|
// No createDpiTemp; we make a real internal variable instead
|
|
// SAME CODE BELOW
|
|
args += ", ";
|
|
if (args != "") {
|
|
argnodesp = argnodesp->addNext(new AstText{portp->fileline(), args, true});
|
|
args = "";
|
|
}
|
|
AstVarScope* const outvscp
|
|
= createFuncVar(funcp, portp->name() + tmpSuffixp, portp);
|
|
// No information exposure; is already visible in import/export func template
|
|
outvscp->varp()->protect(false);
|
|
portp->protect(false);
|
|
AstVarRef* const refp
|
|
= new AstVarRef{portp->fileline(), outvscp,
|
|
portp->isWritable() ? VAccess::WRITE : VAccess::READ};
|
|
argnodesp = argnodesp->addNext(refp);
|
|
|
|
if (portp->isNonOutput()) {
|
|
std::string frName
|
|
= portp->isInout() && portp->basicp()->isDpiPrimitive()
|
|
&& portp->dtypep()->skipRefp()->arrayUnpackedElements() == 1
|
|
? "*"
|
|
: "";
|
|
frName += portp->name();
|
|
funcp->addStmtsp(createAssignDpiToInternal(outvscp, frName));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rtnvarp) {
|
|
AstVar* const portp = rtnvarp;
|
|
// SAME CODE ABOVE
|
|
args += ", ";
|
|
if (args != "") {
|
|
argnodesp = argnodesp->addNext(new AstText{portp->fileline(), args, true});
|
|
args = "";
|
|
}
|
|
AstVarScope* const outvscp = createFuncVar(funcp, portp->name() + tmpSuffixp, portp);
|
|
// No information exposure; is already visible in import/export func template
|
|
outvscp->varp()->protect(false);
|
|
AstVarRef* const refp = new AstVarRef{
|
|
portp->fileline(), outvscp, portp->isWritable() ? VAccess::WRITE : VAccess::READ};
|
|
argnodesp = argnodesp->addNext(refp);
|
|
}
|
|
|
|
{ // Call the user function
|
|
// Add the variables referenced as VarRef's so that lifetime analysis
|
|
// doesn't rip up the variables on us
|
|
args += ");\n";
|
|
AstCStmt* const newp = new AstCStmt{nodep->fileline(), "(*__Vcb)("};
|
|
newp->addExprsp(argnodesp);
|
|
VL_DANGLING(argnodesp);
|
|
newp->addExprsp(new AstText{nodep->fileline(), args, true});
|
|
funcp->addStmtsp(newp);
|
|
}
|
|
|
|
// 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, ""));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rtnvarp) {
|
|
funcp->addStmtsp(createDpiTemp(rtnvarp, ""));
|
|
funcp->addStmtsp(createAssignInternalToDpi(rtnvarp, false, tmpSuffixp, ""));
|
|
string stmt = "return " + rtnvarp->name();
|
|
stmt += rtnvarp->basicp()->isDpiPrimitive() ? ";\n" : "[0];\n";
|
|
funcp->addStmtsp(new AstCStmt{nodep->fileline(), stmt});
|
|
}
|
|
makePortList(nodep, funcp);
|
|
return funcp;
|
|
}
|
|
|
|
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());
|
|
// Add DPI Import to top, since it's a global function
|
|
m_topScopep->scopep()->addBlocksp(funcp);
|
|
makePortList(nodep, funcp);
|
|
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 void makePortList(AstNodeFTask* nodep, AstCFunc* dpip) {
|
|
// 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()) {
|
|
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.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void bodyDpiImportFunc(AstNodeFTask* nodep, AstVarScope* rtnvscp, AstCFunc* cfuncp,
|
|
AstCFunc* dpiFuncp) {
|
|
const char* const tmpSuffixp = V3Task::dpiTemporaryVarSuffix();
|
|
// 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);
|
|
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");
|
|
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);\n";
|
|
cfuncp->addStmtsp(new AstCStmt{nodep->fileline(), stmt});
|
|
}
|
|
|
|
{ // Call the imported function
|
|
if (rtnvscp) { // isFunction will no longer work as we unlinked the return var
|
|
cfuncp->addStmtsp(createDpiTemp(rtnvscp->varp(), tmpSuffixp));
|
|
string stmt = rtnvscp->varp()->name();
|
|
stmt += tmpSuffixp;
|
|
stmt += rtnvscp->varp()->basicp()->isDpiPrimitive() ? " = " : "[0] = ";
|
|
cfuncp->addStmtsp(new AstText{nodep->fileline(), stmt, /* tracking: */ true});
|
|
}
|
|
AstCCall* const callp = new AstCCall{nodep->fileline(), dpiFuncp};
|
|
callp->dtypeSetVoid();
|
|
callp->argTypes(args);
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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"
|
|
+ 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"
|
|
+ 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);
|
|
if (!dpiFuncp) return nullptr; // There was an error, so bail
|
|
if (nodep->dpiImport() && nodep->dpiOpenParent()) {
|
|
// No need to make more than just the DPI Import prototype, the children will
|
|
// create the wrapper implementations.
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
AstVarScope* rtnvscp = nullptr;
|
|
if (rtnvarp) {
|
|
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
|
|
if (!nodep->taskPublic() && !nodep->classMethod()) suffix = "_" + m_scopep->nameDotless();
|
|
const string name = ((nodep->name() == "new") ? "new" : prefix + nodep->name() + suffix);
|
|
AstCFunc* const cfuncp = new AstCFunc{
|
|
nodep->fileline(), name, m_scopep,
|
|
((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);
|
|
}
|
|
cfuncp->isVirtual(nodep->isVirtual());
|
|
cfuncp->dpiPure(nodep->dpiPure());
|
|
if (nodep->name() == "new") cfuncp->isConstructor(true);
|
|
if (cfuncp->dpiExportImpl()) cfuncp->cname(nodep->cname());
|
|
|
|
if (!nodep->dpiImport() && !nodep->taskPublic()) {
|
|
// Need symbol table
|
|
cfuncp->argTypes(EmitCBase::symClassVar());
|
|
if (cfuncp->name() == "new") {
|
|
const string stmt = VIdProtect::protect("_ctor_var_reset") + "(vlSymsp);\n";
|
|
cfuncp->addInitsp(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");
|
|
snp->dpiExport(
|
|
true); // The AstScopeName is really a statement(ish) for tracking, not a function
|
|
snp->unlinkFrBack();
|
|
cfuncp->addInitsp(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)) {
|
|
if (portp->isParam() && VN_IS(portp->valuep(), InitArray)) {
|
|
// Move array parameters in functions into constant pool
|
|
portp->unlinkFrBack();
|
|
pushDeletep(portp);
|
|
AstNode* const tablep = v3Global.rootp()->constPoolp()->findTable(
|
|
VN_AS(portp->valuep(), InitArray));
|
|
portp->user2p(tablep);
|
|
} else {
|
|
if (portp->isIO()) {
|
|
// Move it to new function
|
|
unlinkAndClone(nodep, portp, false);
|
|
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);
|
|
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};
|
|
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)) {
|
|
if (portp->isIO()) portp->unlinkFrBack();
|
|
}
|
|
}
|
|
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->addFinalsp(new AstCReturn{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mark the fact that this function allocates std::process
|
|
if (nodep->needProcess()) cfuncp->setNeedProcess();
|
|
|
|
// Delete rest of cloned task and return new func
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
|
if (debug() >= 9) cfuncp->dumpTree("- 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) {
|
|
if (debug() >= 9) nodep->dumpTree("- newstmt: ");
|
|
UASSERT_OBJ(m_insStmtp, nodep, "Function call not underneath a statement");
|
|
if (debug() >= 9) newp->dumpTree("- newfunc: ");
|
|
m_insStmtp->addHereThisAsNext(newp);
|
|
}
|
|
|
|
// VISITORS
|
|
void visit(AstNodeModule* nodep) override {
|
|
VL_RESTORER(m_modp);
|
|
VL_RESTORER(m_modNCalls);
|
|
m_modp = nodep;
|
|
m_insStmtp = nullptr;
|
|
m_modNCalls = 0;
|
|
iterateChildren(nodep);
|
|
}
|
|
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 {
|
|
m_scopep = nodep;
|
|
m_insStmtp = nullptr;
|
|
iterateChildren(nodep);
|
|
m_scopep = nullptr;
|
|
}
|
|
void visit(AstNodeFTaskRef* nodep) override {
|
|
if (m_inSensesp) {
|
|
nodep->v3warn(E_UNSUPPORTED, "Unsupported: function calls in sensitivity lists");
|
|
nodep->taskp(nullptr); // So V3Broken doesn't complain
|
|
return;
|
|
}
|
|
// Includes handling AstMethodCall, AstNew
|
|
UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked?");
|
|
iterateIntoFTask(nodep->taskp()); // First, do hierarchical funcs
|
|
UINFO(4, " FTask REF " << nodep << endl);
|
|
if (debug() >= 9) nodep->dumpTree("- 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.
|
|
beginp = createNonInlinedFTask(nodep, namePrefix, outvscp, cnewp /*ref*/);
|
|
} else {
|
|
beginp = createInlinedFTask(nodep, namePrefix, outvscp);
|
|
++m_statInlines;
|
|
}
|
|
|
|
if (VN_IS(nodep, New)) {
|
|
insertBeforeStmt(nodep, beginp);
|
|
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);
|
|
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};
|
|
beginp = new AstExprStmt{nodep->fileline(), beginp, outrefp};
|
|
// AstExprStmt is currently treated as impure, so clear the cached purity of its
|
|
// parents
|
|
nodep->replaceWith(beginp);
|
|
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
|
VIsCached::clearCacheTree();
|
|
} else {
|
|
insertBeforeStmt(nodep, beginp);
|
|
if (nodep->taskp()->isFunction()) {
|
|
nodep->v3warn(
|
|
IGNOREDRETURN,
|
|
"Ignoring return value of non-void function (IEEE 1800-2023 13.4.1)");
|
|
}
|
|
nodep->unlinkFrBack();
|
|
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
|
}
|
|
UINFO(4, " FTask REF Done.\n");
|
|
}
|
|
void visit(AstNodeFTask* nodep) override {
|
|
UINFO(4, " visitFTask " << nodep << endl);
|
|
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);
|
|
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 << endl);
|
|
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 << endl);
|
|
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(AstWhile* nodep) override {
|
|
// Special, as statements need to be put in different places
|
|
// Preconditions insert first just before themselves (the normal
|
|
// rule for other statement types)
|
|
m_insStmtp = nullptr; // First thing should be new statement
|
|
iterateAndNextNull(nodep->precondsp());
|
|
// Conditions insert first at end of precondsp.
|
|
m_insStmtp = nodep;
|
|
iterateAndNextNull(nodep->condp());
|
|
// Body insert just before themselves
|
|
m_insStmtp = nullptr; // First thing should be new statement
|
|
iterateAndNextNull(nodep->stmtsp());
|
|
iterateAndNextNull(nodep->incsp());
|
|
// Done the loop
|
|
m_insStmtp = nullptr; // Next thing should be new statement
|
|
}
|
|
void visit(AstNodeForeach* nodep) override { // LCOV_EXCL_LINE
|
|
nodep->v3fatalSrc(
|
|
"Foreach statements should have been converted to while statements in V3Begin.cpp");
|
|
}
|
|
void visit(AstNodeFor* nodep) override { // LCOV_EXCL_LINE
|
|
nodep->v3fatalSrc(
|
|
"For statements should have been converted to while statements in V3Begin.cpp");
|
|
}
|
|
void visit(AstNodeStmt* nodep) override {
|
|
m_insStmtp = nodep;
|
|
iterateChildren(nodep);
|
|
m_insStmtp = nullptr; // Next thing should be new statement
|
|
}
|
|
void visit(AstStmtExpr* nodep) override {
|
|
m_insStmtp = nodep;
|
|
iterateChildren(nodep);
|
|
if (!nodep->exprp()) VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
|
m_insStmtp = nullptr; // Next thing should be new statement
|
|
}
|
|
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:
|
|
// CONSTRUCTORS
|
|
TaskVisitor(AstNetlist* nodep, TaskStateVisitor* statep)
|
|
: m_statep{statep} {
|
|
iterate(nodep);
|
|
}
|
|
~TaskVisitor() { V3Stats::addStat("Optimizations, Functions inlined", m_statInlines); }
|
|
};
|
|
|
|
//######################################################################
|
|
// 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 << endl);
|
|
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()) {
|
|
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());
|
|
}
|
|
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) {
|
|
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());
|
|
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() << endl);
|
|
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};
|
|
}
|
|
}
|
|
}
|
|
newvaluep = newvaluep->cloneTree(true);
|
|
// To avoid problems with callee needing to know to deleteTree
|
|
// or not, we make this into a pin
|
|
UINFO(9, "Default pin for " << portp << endl);
|
|
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 << endl);
|
|
} else {
|
|
UINFO(9, "Connect " << portp << " -> NONE" << endl);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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) << endl);
|
|
}
|
|
} // LCOV_EXCL_STOP
|
|
|
|
if (!argWrap.empty()) {
|
|
UINFO(9, "Arg wrapper generation " << nodep << endl);
|
|
// 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());
|
|
// if (debug() >= 9) nodep->dumpTree("-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(fvarp, "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);
|
|
});
|
|
// if (debug() >= 9) newTaskp->dumpTree("-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;
|
|
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 string idx = portp->name() + "__Vidx";
|
|
stmt = "for (size_t " + idx + " = 0; " + idx + " < " + cvtToStr(unpackSize) + "; ++" + idx
|
|
+ ") ";
|
|
stmt += (isBit ? "VL_SET_SVBV_" : "VL_SET_SVLV_")
|
|
+ string{portp->dtypep()->skipRefp()->charIQWN()} + "(" + cvtToStr(portp->width())
|
|
+ ", ";
|
|
stmt += toName + " + " + cvtToStr(portp->dtypep()->skipRefp()->widthWords()) + " * " + idx
|
|
+ ", ";
|
|
if (unpackDim > 0) { // Access multi-dimensional array as a 1D array
|
|
stmt += "(&" + frName;
|
|
for (int i = 0; i < unpackDim; ++i) stmt += "[0]";
|
|
stmt += ")[" + idx + "])";
|
|
} 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 idx = portp->name() + "__Vidx";
|
|
stmt = "for (size_t " + idx + " = 0; " + idx + " < " + cvtToStr(unpackSize) + "; ++" + idx
|
|
+ ") ";
|
|
if (unpackDim > 0) {
|
|
stmt += toName + "[" + idx + "]";
|
|
} 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 += ")[" + idx + "]";
|
|
} else {
|
|
stmt += frName;
|
|
}
|
|
if (isString) stmt += ".c_str()";
|
|
}
|
|
stmt += ket + ";\n";
|
|
return stmt;
|
|
}
|
|
|
|
string V3Task::assignDpiToInternal(const string& lhsName, AstVar* varp) {
|
|
// 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)
|
|
const string frName = varp->name();
|
|
string frstmt;
|
|
string ket;
|
|
const bool useSetWSvlv = TaskDpiUtils::dpiToInternalFrStmt(varp, frName, frstmt, ket);
|
|
|
|
const std::vector<std::pair<AstUnpackArrayDType*, int>> dimStrides
|
|
= TaskDpiUtils::unpackDimsAndStrides(varp->dtypep());
|
|
const int total = dimStrides.empty()
|
|
? 1
|
|
: dimStrides.front().first->elementsConst() * dimStrides.front().second;
|
|
const int widthWords = varp->basicp()->widthWords();
|
|
string statements;
|
|
for (int i = 0; i < total; ++i) {
|
|
string lhs = lhsName;
|
|
// extract a scalar from multi-dimensional array (internal format)
|
|
for (auto&& dimStride : dimStrides) {
|
|
const size_t dimIdx = (i / dimStride.second) % dimStride.first->elementsConst();
|
|
lhs += "[" + cvtToStr(dimIdx) + "]";
|
|
}
|
|
// extract a scalar from DPI temporary var that is scalar or 1D array
|
|
if (useSetWSvlv) {
|
|
statements += frstmt + ket + " " + lhs + ", " + frName + " + "
|
|
+ cvtToStr(i * widthWords) + ");\n";
|
|
} else {
|
|
string rhs = frstmt;
|
|
if (!dimStrides.empty()) {
|
|
// e.g. time is 64bit svLogicVector
|
|
const int coef = varp->basicp()->isDpiLogicVec() ? widthWords : 1;
|
|
rhs += "[" + cvtToStr(i * coef) + "]";
|
|
}
|
|
rhs += ket;
|
|
statements += lhs + " = " + rhs + ";\n";
|
|
}
|
|
}
|
|
return statements;
|
|
}
|
|
|
|
void V3Task::taskAll(AstNetlist* nodep) {
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
|
{
|
|
TaskStateVisitor visitors{nodep};
|
|
const TaskVisitor visitor{nodep, &visitors};
|
|
} // Destruct before checking
|
|
V3Global::dumpCheckGlobalTree("task", 0, dumpTreeEitherLevel() >= 3);
|
|
}
|