Correctly schedule combinational logic driven from DPI exports.
Fixes #3429.
This commit is contained in:
parent
ff1b9930fc
commit
6a7bda6910
12
src/V3Ast.h
12
src/V3Ast.h
|
|
@ -266,7 +266,6 @@ public:
|
|||
ET_POSEDGE,
|
||||
ET_NEGEDGE,
|
||||
ET_EVENT, // VlEvent::isFired
|
||||
ET_DPIEXPORT, // Used exclusively to check the AstNetlist::dpiExportTriggerp()
|
||||
// Involving an expression
|
||||
ET_TRUE,
|
||||
//
|
||||
|
|
@ -287,7 +286,6 @@ public:
|
|||
true, // ET_POSEDGE
|
||||
true, // ET_NEGEDGE
|
||||
true, // ET_EVENT
|
||||
true, // ET_DPIEXPORT
|
||||
true, // ET_TRUE
|
||||
|
||||
false, // ET_COMBO
|
||||
|
|
@ -311,14 +309,14 @@ public:
|
|||
}
|
||||
const char* ascii() const {
|
||||
static const char* const names[]
|
||||
= {"%E-edge", "CHANGED", "BOTH", "POS", "NEG", "EVENT", "DPIEXPORT",
|
||||
"TRUE", "COMBO", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"};
|
||||
= {"%E-edge", "CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE",
|
||||
"COMBO", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"};
|
||||
return names[m_e];
|
||||
}
|
||||
const char* verilogKwd() const {
|
||||
static const char* const names[] = {
|
||||
"%E-edge", "[changed]", "edge", "posedge", "negedge", "[event]", "[dpiexport]",
|
||||
"[true]", "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"};
|
||||
static const char* const names[]
|
||||
= {"%E-edge", "[changed]", "edge", "posedge", "negedge", "[event]", "[true]",
|
||||
"*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"};
|
||||
return names[m_e];
|
||||
}
|
||||
// Return true iff this and the other have mutually exclusive transitions
|
||||
|
|
|
|||
|
|
@ -2001,6 +2001,7 @@ private:
|
|||
bool m_trace : 1; // Trace this variable
|
||||
bool m_isLatched : 1; // Not assigned in all control paths of combo always
|
||||
bool m_isForceable : 1; // May be forced/released externally from user C code
|
||||
bool m_isWrittenByDpi : 1; // This variable can be written by a DPI Export
|
||||
|
||||
void init() {
|
||||
m_ansi = false;
|
||||
|
|
@ -2040,6 +2041,7 @@ private:
|
|||
m_trace = false;
|
||||
m_isLatched = false;
|
||||
m_isForceable = false;
|
||||
m_isWrittenByDpi = false;
|
||||
m_attrClocker = VVarAttrClocker::CLOCKER_UNKNOWN;
|
||||
}
|
||||
|
||||
|
|
@ -2205,6 +2207,8 @@ public:
|
|||
void isLatched(bool flag) { m_isLatched = flag; }
|
||||
bool isForceable() const { return m_isForceable; }
|
||||
void setForceable() { m_isForceable = true; }
|
||||
bool isWrittenByDpi() const { return m_isWrittenByDpi; }
|
||||
void setWrittenByDpi() { m_isWrittenByDpi = true; }
|
||||
// METHODS
|
||||
virtual void name(const string& name) override { m_name = name; }
|
||||
virtual void tag(const string& text) override { m_tag = text; }
|
||||
|
|
@ -3638,17 +3642,6 @@ public:
|
|||
virtual bool brokeLhsMustBeLvalue() const override { return true; }
|
||||
};
|
||||
|
||||
class AstDpiExportUpdated final : public AstNodeStmt {
|
||||
// Denotes that the referenced variable may have been updated via a DPI Export
|
||||
public:
|
||||
AstDpiExportUpdated(FileLine* fl, AstVarScope* varScopep)
|
||||
: ASTGEN_SUPER_DpiExportUpdated(fl) {
|
||||
addOp1p(new AstVarRef{fl, varScopep, VAccess::WRITE});
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(DpiExportUpdated)
|
||||
AstVarScope* varScopep() const { return VN_AS(op1p(), VarRef)->varScopep(); }
|
||||
};
|
||||
|
||||
class AstExprStmt final : public AstNodeMath {
|
||||
// Perform a statement, often assignment inside an expression/math node,
|
||||
// the parent gets passed the 'resultp()'.
|
||||
|
|
|
|||
|
|
@ -433,6 +433,11 @@ public:
|
|||
// Operate on whole netlist
|
||||
iterate(nodep);
|
||||
|
||||
if (AstVarScope* const vscp = nodep->dpiExportTriggerp()) {
|
||||
vscp->user1Inc();
|
||||
vscp->varp()->user1Inc();
|
||||
}
|
||||
|
||||
deadCheckVar();
|
||||
// We only eliminate scopes when in a flattened structure
|
||||
// Otherwise we have no easy way to know if a scope is used
|
||||
|
|
|
|||
|
|
@ -184,10 +184,6 @@ private:
|
|||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstNetlist* nodep) override {
|
||||
nodep->dpiExportTriggerp(nullptr);
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
virtual void visit(AstNodeModule* nodep) override {
|
||||
VL_RESTORER(m_modp);
|
||||
{
|
||||
|
|
|
|||
|
|
@ -177,15 +177,8 @@ class OrderBuildVisitor final : public VNVisitor {
|
|||
AstUser1Allocator<AstVarScope, OrderUser> m_orderUser;
|
||||
|
||||
// STATE
|
||||
// The ordering graph built by this visitor
|
||||
OrderGraph* const m_graphp = new OrderGraph;
|
||||
// Singleton DPI Export trigger event vertex
|
||||
OrderVarVertex* const m_dpiExportTriggerVxp
|
||||
= v3Global.rootp()->dpiExportTriggerp()
|
||||
? getVarVertex(v3Global.rootp()->dpiExportTriggerp(), VarVertexType::STD)
|
||||
: nullptr;
|
||||
|
||||
OrderLogicVertex* m_logicVxp = nullptr; // Current loic block being analyzed
|
||||
OrderGraph* const m_graphp = new OrderGraph; // The ordering graph built by this visitor
|
||||
OrderLogicVertex* m_logicVxp = nullptr; // Current logic block being analyzed
|
||||
|
||||
// Map from Trigger reference AstSenItem to the original AstSenTree
|
||||
const std::unordered_map<const AstSenItem*, const AstSenTree*>& m_trigToSen;
|
||||
|
|
@ -411,25 +404,7 @@ class OrderBuildVisitor final : public VNVisitor {
|
|||
}
|
||||
}
|
||||
}
|
||||
virtual void visit(AstDpiExportUpdated* nodep) override {
|
||||
// This is under a logic block (AstAlways) sensitive to a change in the DPI export trigger.
|
||||
// We just need to add an edge to the enclosing logic vertex (the vertex for the
|
||||
// AstAlways).
|
||||
OrderVarVertex* const varVxp = getVarVertex(nodep->varScopep(), VarVertexType::STD);
|
||||
new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL);
|
||||
// Only used for ordering, so we can get rid of it here
|
||||
nodep->unlinkFrBack();
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
}
|
||||
virtual void visit(AstCCall* nodep) override {
|
||||
// Calls to 'context' imported DPI function may call DPI exported functions
|
||||
if (m_dpiExportTriggerVxp && nodep->funcp()->dpiImportWrapper()
|
||||
&& nodep->funcp()->dpiContext()) {
|
||||
UASSERT_OBJ(m_logicVxp, nodep, "Call not under logic");
|
||||
new OrderEdge(m_graphp, m_logicVxp, m_dpiExportTriggerVxp, WEIGHT_NORMAL);
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
virtual void visit(AstCCall* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
//--- Logic akin to SystemVerilog Processes (AstNodeProcedure)
|
||||
virtual void visit(AstInitial* nodep) override { // LCOV_EXCL_START
|
||||
|
|
|
|||
|
|
@ -264,7 +264,6 @@ class SenExprBuilder final {
|
|||
// STATE
|
||||
AstCFunc* const m_initp; // The initialization function
|
||||
AstScope* const m_scopeTopp; // Top level scope
|
||||
AstVarScope* const m_dpiExportTriggerp; // The DPI export trigger variable
|
||||
|
||||
std::vector<AstNodeStmt*> m_updates; // Update assignments
|
||||
|
||||
|
|
@ -362,13 +361,6 @@ class SenExprBuilder final {
|
|||
callp->dtypeSetBit();
|
||||
return {callp, false};
|
||||
}
|
||||
case VEdgeType::ET_DPIEXPORT: {
|
||||
// If the DPI export trigger is checked, always clear it after trigger computation
|
||||
UASSERT_OBJ(m_dpiExportTriggerp, senItemp, "ET_DPIEXPORT without trigger variable");
|
||||
AstVarRef* const refp = new AstVarRef{flp, m_dpiExportTriggerp, VAccess::WRITE};
|
||||
m_updates.push_back(new AstAssign{flp, refp, new AstConst{flp, AstConst::BitFalse{}}});
|
||||
return {currp(), false};
|
||||
}
|
||||
default: // LCOV_EXCL_START
|
||||
senItemp->v3fatalSrc("Unknown edge type");
|
||||
return {nullptr, false};
|
||||
|
|
@ -401,8 +393,7 @@ public:
|
|||
// CONSTRUCTOR
|
||||
SenExprBuilder(AstNetlist* netlistp, AstCFunc* initp)
|
||||
: m_initp{initp}
|
||||
, m_scopeTopp{netlistp->topScopep()->scopep()}
|
||||
, m_dpiExportTriggerp{netlistp->dpiExportTriggerp()} {}
|
||||
, m_scopeTopp{netlistp->topScopep()->scopep()} {}
|
||||
};
|
||||
|
||||
//============================================================================
|
||||
|
|
@ -446,6 +437,21 @@ struct TriggerKit {
|
|||
flp, callp,
|
||||
new AstEq{flp, new AstVarRef{flp, counterp, VAccess::READ}, new AstConst{flp, 0}}});
|
||||
}
|
||||
|
||||
// Utility to set then clear the dpiExportTrigger trigger
|
||||
void addDpiExportTriggerAssignment(AstVarScope* dpiExportTriggerVscp, uint32_t index) const {
|
||||
FileLine* const flp = dpiExportTriggerVscp->fileline();
|
||||
AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
|
||||
AstCMethodHard* const callp
|
||||
= new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, index}};
|
||||
callp->dtypeSetBit();
|
||||
callp->pure(true);
|
||||
AstNode* stmtp
|
||||
= new AstAssign{flp, callp, new AstVarRef{flp, dpiExportTriggerVscp, VAccess::READ}};
|
||||
stmtp->addNext(new AstAssign{flp, new AstVarRef{flp, dpiExportTriggerVscp, VAccess::WRITE},
|
||||
new AstConst{flp, AstConst::BitFalse{}}});
|
||||
m_funcp->stmtsp()->addHereThisAsNext(stmtp);
|
||||
}
|
||||
};
|
||||
|
||||
//============================================================================
|
||||
|
|
@ -743,15 +749,23 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde
|
|||
});
|
||||
}
|
||||
|
||||
// We have an extra trigger denoting this is the first iteration of the ico loop
|
||||
constexpr unsigned firstIterationTrigger = 0;
|
||||
constexpr unsigned extraTriggers = firstIterationTrigger + 1;
|
||||
// We have some extra trigger denoting external conditions
|
||||
AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp();
|
||||
|
||||
unsigned extraTriggers = 0;
|
||||
const unsigned firstIterationTrigger = extraTriggers++;
|
||||
const unsigned dpiExportTriggerIndex
|
||||
= dpiExportTriggerVscp ? extraTriggers++ : std::numeric_limits<unsigned>::max();
|
||||
|
||||
// Gather the relevant sensitivity expressions and create the trigger kit
|
||||
const auto& senTreeps = getSenTreesUsedBy({&logic});
|
||||
const TriggerKit& trig
|
||||
= createTriggers(netlistp, senExprBuilder, senTreeps, "ico", extraTriggers);
|
||||
|
||||
if (dpiExportTriggerVscp) {
|
||||
trig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
|
||||
}
|
||||
|
||||
// Remap sensitivities
|
||||
remapSensitivities(logic, trig.m_map);
|
||||
|
||||
|
|
@ -759,9 +773,13 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde
|
|||
std::unordered_map<const AstSenItem*, const AstSenTree*> trigToSen;
|
||||
invertAndMergeSenTreeMap(trigToSen, trig.m_map);
|
||||
|
||||
// First trigger is for pure combinational triggers (first iteration)
|
||||
// The trigger top level inputs (first iteration)
|
||||
AstSenTree* const inputChanged = trig.createTriggerSenTree(netlistp, firstIterationTrigger);
|
||||
|
||||
// The DPI Export trigger
|
||||
AstSenTree* const dpiExportTriggered
|
||||
= trig.createTriggerSenTree(netlistp, dpiExportTriggerIndex);
|
||||
|
||||
// Create and Order the body function
|
||||
AstCFunc* const icoFuncp
|
||||
= V3Order::order(netlistp, {&logic}, trigToSen, "ico", false, false,
|
||||
|
|
@ -769,6 +787,7 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde
|
|||
if (vscp->scopep()->isTop() && vscp->varp()->isNonOutput()) {
|
||||
out.push_back(inputChanged);
|
||||
}
|
||||
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered);
|
||||
});
|
||||
splitCheck(icoFuncp);
|
||||
|
||||
|
|
@ -964,10 +983,22 @@ void schedule(AstNetlist* netlistp) {
|
|||
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-ico");
|
||||
|
||||
// Step 8: Create the pre/act/nba triggers
|
||||
AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp();
|
||||
|
||||
unsigned extraTriggers = 0;
|
||||
// We may have an extra trigger for variable updated in DPI exports
|
||||
const unsigned dpiExportTriggerIndex
|
||||
= dpiExportTriggerVscp ? extraTriggers++ : std::numeric_limits<unsigned>::max();
|
||||
|
||||
const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, //
|
||||
&logicRegions.m_act, //
|
||||
&logicRegions.m_nba});
|
||||
const TriggerKit& actTrig = createTriggers(netlistp, senExprBuilder, senTreeps, "act", 0);
|
||||
const TriggerKit& actTrig
|
||||
= createTriggers(netlistp, senExprBuilder, senTreeps, "act", extraTriggers);
|
||||
|
||||
if (dpiExportTriggerVscp) {
|
||||
actTrig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
|
||||
}
|
||||
|
||||
AstTopScope* const topScopep = netlistp->topScopep();
|
||||
AstScope* const scopeTopp = topScopep->scopep();
|
||||
|
|
@ -1017,9 +1048,15 @@ void schedule(AstNetlist* netlistp) {
|
|||
invertAndMergeSenTreeMap(trigToSenAct, preTrigMap);
|
||||
invertAndMergeSenTreeMap(trigToSenAct, actTrigMap);
|
||||
|
||||
// The DPI Export trigger AstSenTree
|
||||
AstSenTree* const dpiExportTriggered
|
||||
= actTrig.createTriggerSenTree(netlistp, dpiExportTriggerIndex);
|
||||
|
||||
AstCFunc* const actFuncp = V3Order::order(
|
||||
netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct,
|
||||
"act", false, false);
|
||||
"act", false, false, [=](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
|
||||
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered);
|
||||
});
|
||||
splitCheck(actFuncp);
|
||||
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act");
|
||||
|
||||
|
|
@ -1033,9 +1070,11 @@ void schedule(AstNetlist* netlistp) {
|
|||
std::unordered_map<const AstSenItem*, const AstSenTree*> trigToSenNba;
|
||||
invertAndMergeSenTreeMap(trigToSenNba, nbaTrigMap);
|
||||
|
||||
AstCFunc* const nbaFuncp
|
||||
= V3Order::order(netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba,
|
||||
"nba", v3Global.opt.mtasks(), false);
|
||||
AstCFunc* const nbaFuncp = V3Order::order(
|
||||
netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, "nba",
|
||||
v3Global.opt.mtasks(), false, [=](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
|
||||
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered);
|
||||
});
|
||||
splitCheck(nbaFuncp);
|
||||
netlistp->evalNbap(nbaFuncp); // Remember for V3LifePost
|
||||
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba");
|
||||
|
|
@ -1045,6 +1084,8 @@ void schedule(AstNetlist* netlistp) {
|
|||
|
||||
splitCheck(initp);
|
||||
|
||||
netlistp->dpiExportTriggerp(nullptr);
|
||||
|
||||
V3Global::dumpCheckGlobalTree("sched", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -130,11 +130,22 @@ class SchedGraphBuilder final : public VNVisitor {
|
|||
AstSenTree* m_senTreep = nullptr; // AstSenTree of the current AstActive
|
||||
// Predicate for whether a read of the given variable triggers this block
|
||||
std::function<bool(AstVarScope*)> m_readTriggersThisLogic;
|
||||
// The DPI export trigger variable, if any
|
||||
AstVarScope* const m_dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp();
|
||||
|
||||
VL_DEBUG_FUNC;
|
||||
|
||||
SchedVarVertex* getVarVertex(AstVarScope* vscp) const {
|
||||
if (!vscp->user1p()) vscp->user1p(new SchedVarVertex{m_graphp, vscp});
|
||||
if (!vscp->user1p()) {
|
||||
SchedVarVertex* const vtxp = new SchedVarVertex{m_graphp, vscp};
|
||||
// If this variable can be written via a DPI export, add a source edge from the
|
||||
// DPI export trigger vertex. This ensures calls to DPI exports that might write a
|
||||
// clock end up in the 'act' region.
|
||||
if (vscp->varp()->isWrittenByDpi()) {
|
||||
new V3GraphEdge{m_graphp, getVarVertex(m_dpiExportTriggerp), vtxp, 1};
|
||||
}
|
||||
vscp->user1p(vtxp);
|
||||
}
|
||||
return vscp->user1u().to<SchedVarVertex*>();
|
||||
}
|
||||
|
||||
|
|
@ -154,8 +165,7 @@ class SchedGraphBuilder final : public VNVisitor {
|
|||
|
||||
// Connect up the variable references
|
||||
senItemp->sensp()->foreach<AstVarRef>([&](AstVarRef* refp) {
|
||||
SchedVarVertex* const varVtxp = getVarVertex(refp->varScopep());
|
||||
new V3GraphEdge{m_graphp, varVtxp, vtxp, 1};
|
||||
new V3GraphEdge{m_graphp, getVarVertex(refp->varScopep()), vtxp, 1};
|
||||
});
|
||||
|
||||
// Store back to hash map so we can find it next time
|
||||
|
|
@ -188,21 +198,20 @@ class SchedGraphBuilder final : public VNVisitor {
|
|||
// Add edges based on references
|
||||
nodep->foreach<AstVarRef>([=](const AstVarRef* vrefp) {
|
||||
AstVarScope* const vscp = vrefp->varScopep();
|
||||
SchedVarVertex* const varVtxp = getVarVertex(vscp);
|
||||
if (vrefp->access().isReadOrRW() && m_readTriggersThisLogic(vscp)) {
|
||||
new V3GraphEdge{m_graphp, varVtxp, logicVtxp, 10};
|
||||
new V3GraphEdge{m_graphp, getVarVertex(vscp), logicVtxp, 10};
|
||||
}
|
||||
if (vrefp->access().isWriteOrRW()) {
|
||||
new V3GraphEdge{m_graphp, logicVtxp, varVtxp, 10};
|
||||
new V3GraphEdge{m_graphp, logicVtxp, getVarVertex(vscp), 10};
|
||||
}
|
||||
});
|
||||
|
||||
// If the logic calls a DPI import, it might fire the DPI Export trigger
|
||||
if (AstVarScope* const dpiExporTrigger = v3Global.rootp()->dpiExportTriggerp()) {
|
||||
// If the logic calls a 'context' DPI import, it might fire the DPI Export trigger
|
||||
if (m_dpiExportTriggerp) {
|
||||
nodep->foreach<AstCCall>([=](const AstCCall* callp) {
|
||||
if (!callp->funcp()->dpiImportWrapper()) return;
|
||||
SchedVarVertex* const varVtxp = getVarVertex(dpiExporTrigger);
|
||||
new V3GraphEdge{m_graphp, logicVtxp, varVtxp, 10};
|
||||
if (!callp->funcp()->dpiContext()) return;
|
||||
new V3GraphEdge{m_graphp, logicVtxp, getVarVertex(m_dpiExportTriggerp), 10};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,8 @@ public:
|
|||
VarVertex(V3Graph* graphp, AstVarScope* vscp)
|
||||
: Vertex{graphp}
|
||||
, m_vscp{vscp} {
|
||||
if (isTopInput()) addDrivingRegions(INPUT);
|
||||
// Top level inputs are
|
||||
if (isTopInput() || varp()->isWrittenByDpi()) addDrivingRegions(INPUT);
|
||||
}
|
||||
AstVarScope* vscp() const { return m_vscp; }
|
||||
AstVar* varp() const { return m_vscp->varp(); }
|
||||
|
|
|
|||
|
|
@ -1055,7 +1055,7 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
AstVarScope* makeDpiExporTrigger() {
|
||||
AstVarScope* getDpiExporTrigger() {
|
||||
AstNetlist* const netlistp = v3Global.rootp();
|
||||
AstVarScope* dpiExportTriggerp = netlistp->dpiExportTriggerp();
|
||||
if (!dpiExportTriggerp) {
|
||||
|
|
@ -1271,52 +1271,37 @@ private:
|
|||
// 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.
|
||||
|
||||
// Gather non-local variables written by the exported function
|
||||
std::vector<AstVarScope*> writtenps;
|
||||
{
|
||||
const VNUser5InUse user5InUse; // AstVarScope::user5 -> Already added variable
|
||||
cfuncp->foreach<AstVarRef>([&writtenps](AstVarRef* refp) {
|
||||
if (refp->access().isReadOnly()) return; // Ignore read reference
|
||||
AstVarScope* const varScopep = refp->varScopep();
|
||||
if (varScopep->user5()) return; // Ignore already added variable
|
||||
varScopep->user5(true); // Mark as already added
|
||||
// Note: We are ignoring function locals as they should not be referenced
|
||||
// anywhere outside of the enclosing AstCFunc, and therefore they are
|
||||
// irrelevant for code ordering. This is an optimization to avoid adding
|
||||
// useless nodes to the ordering graph in V3Order.
|
||||
if (varScopep->varp()->isFuncLocal()) return;
|
||||
writtenps.push_back(varScopep);
|
||||
});
|
||||
}
|
||||
// Mark non-local variables written by the exported function
|
||||
bool writesNonLocals = false;
|
||||
cfuncp->foreach<AstVarRef>([&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 (!writtenps.empty()) {
|
||||
AstVarScope* const dpiExportTriggerp = makeDpiExporTrigger();
|
||||
FileLine* const fl = cfuncp->fileline();
|
||||
// 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{fl, new AstVarRef{fl, dpiExportTriggerp, VAccess::WRITE},
|
||||
new AstConst{fl, AstConst::BitTrue{}}};
|
||||
= 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);
|
||||
}
|
||||
|
||||
// Add an always block sensitive to the DPI export trigger flag, and add an
|
||||
// AstDpiExportUpdated node under it for each variable that are writen by the
|
||||
// exported function.
|
||||
AstAlways* const alwaysp = new AstAlways{
|
||||
fl, VAlwaysKwd::ALWAYS,
|
||||
new AstSenTree{
|
||||
fl, new AstSenItem{fl, VEdgeType::ET_DPIEXPORT,
|
||||
new AstVarRef{fl, dpiExportTriggerp, VAccess::READ}}},
|
||||
nullptr};
|
||||
for (AstVarScope* const varScopep : writtenps) {
|
||||
alwaysp->addStmtp(new AstDpiExportUpdated{fl, varScopep});
|
||||
}
|
||||
m_scopep->addActivep(alwaysp);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2022 by Geza Lore. This program is free software; you can
|
||||
// redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include <svdpi.h>
|
||||
|
||||
#include <Vt_order_dpi_export_6.h>
|
||||
#include <Vt_order_dpi_export_6__Dpi.h>
|
||||
|
||||
void toggle_other_clk(svBit val) { set_other_clk(val); }
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Vt_order_dpi_export_6* const tb = new Vt_order_dpi_export_6;
|
||||
tb->contextp()->commandArgs(argc, argv);
|
||||
bool clk = true;
|
||||
|
||||
while (!tb->contextp()->gotFinish()) {
|
||||
// Timeout
|
||||
if (tb->contextp()->time() > 100000) break;
|
||||
// Toggle and set main clock
|
||||
clk = !clk;
|
||||
tb->clk = clk;
|
||||
// Eval
|
||||
tb->eval();
|
||||
// Advance time
|
||||
tb->contextp()->timeInc(500);
|
||||
}
|
||||
|
||||
delete tb;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2003 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
|
||||
|
||||
scenarios(vlt_all => 1);
|
||||
|
||||
compile(
|
||||
make_top_shell => 0,
|
||||
make_main => 0,
|
||||
verilator_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"],
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
);
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Copyright 2022 by Geza Lore. 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
|
||||
|
||||
module testbench(
|
||||
/*AUTOARG*/
|
||||
// Inputs
|
||||
clk
|
||||
);
|
||||
|
||||
input clk; // Top level input clock
|
||||
logic other_clk; // Dependent clock set via DPI
|
||||
|
||||
export "DPI-C" function set_other_clk;
|
||||
function void set_other_clk(bit val);
|
||||
other_clk = val;
|
||||
endfunction;
|
||||
|
||||
bit even_other = 1;
|
||||
bit current_even_other = 1;
|
||||
import "DPI-C" context function void toggle_other_clk(bit val);
|
||||
always @(posedge clk) begin
|
||||
even_other <= ~even_other;
|
||||
current_even_other = even_other;
|
||||
toggle_other_clk(even_other);
|
||||
end
|
||||
|
||||
int n = 0;
|
||||
|
||||
always @(edge other_clk) begin
|
||||
// This always block needs to evaluate before the NBA to even_other
|
||||
// above is committed, as setting clocks via the set_other_clk uses
|
||||
// blocking assignment.
|
||||
if (even_other !== current_even_other) $stop;
|
||||
$display("t=%t n=%d", $time, n);
|
||||
if ($time != (2*n+1) * 500) $stop;
|
||||
if (n == 20) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
n += 1;
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2022 by Geza Lore. This program is free software; you can
|
||||
// redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include <svdpi.h>
|
||||
|
||||
#include <Vt_order_dpi_export_7.h>
|
||||
#include <Vt_order_dpi_export_7__Dpi.h>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Vt_order_dpi_export_7* const tb = new Vt_order_dpi_export_7;
|
||||
tb->contextp()->commandArgs(argc, argv);
|
||||
bool clk = true;
|
||||
|
||||
while (!tb->contextp()->gotFinish()) {
|
||||
// Timeout
|
||||
if (tb->contextp()->time() > 100000) break;
|
||||
// Toggle and set clock
|
||||
svSetScope(svGetScopeFromName("TOP.testbench"));
|
||||
clk = !clk;
|
||||
set_inputs(clk);
|
||||
// Eval
|
||||
tb->eval();
|
||||
// Advance time
|
||||
tb->contextp()->timeInc(500);
|
||||
}
|
||||
|
||||
delete tb;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2003 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
|
||||
|
||||
scenarios(vlt_all => 1);
|
||||
|
||||
compile(
|
||||
make_top_shell => 0,
|
||||
make_main => 0,
|
||||
verilator_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"],
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
);
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Copyright 2022 by Geza Lore. 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
|
||||
|
||||
module testbench;
|
||||
|
||||
logic clk;
|
||||
logic data;
|
||||
|
||||
export "DPI-C" function set_inputs;
|
||||
function void set_inputs(bit val);
|
||||
clk = val;
|
||||
data = val;
|
||||
endfunction;
|
||||
|
||||
// This needs to be in the 'ico' region. Written with $c1 to prevent
|
||||
// gate optimization.
|
||||
wire invdata = $c1(1) ^ data;
|
||||
|
||||
int n = 0;
|
||||
|
||||
always @(edge clk) begin
|
||||
// The combinational update needs to have take effect (in the 'ico'
|
||||
// region), before this always block is executed
|
||||
if (invdata != ~data) $stop;
|
||||
$display("t=%t n=%d", $time, n);
|
||||
if ($time != (1*n+1) * 500) $stop;
|
||||
if (n == 20) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
n += 1;
|
||||
end
|
||||
|
||||
endmodule
|
||||
Loading…
Reference in New Issue