Correctly schedule combinational logic driven from DPI exports.

Fixes #3429.
This commit is contained in:
Geza Lore 2022-07-14 12:35:44 +01:00
parent ff1b9930fc
commit 6a7bda6910
15 changed files with 330 additions and 117 deletions

View File

@ -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

View File

@ -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()'.

View File

@ -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

View File

@ -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);
{

View File

@ -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

View File

@ -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);
}

View File

@ -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};
});
}
}

View File

@ -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(); }

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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