Files
verilator/src/V3SchedTrigger.cpp
T
Geza Lore 8546d5db06 Internals: Move the eval loop into the runtime library (#8225)
The loops modelling the SystemVerilog scheduling regions are no longer
generated. They now live in 'VerilatedEvalLoop' in the runtime library.
The generated model holds one as a member, passing itself to it, and
exposes each evaluation entry point to it as a pure virtual method on
VerilatedModel. The model's 'eval' and 'eval_step' remain the top level
entry points, and are backward compatible.

V3Sched no longer emits '_eval' or '_eval_settle', etc.. Instead every
evaluation entry point called from the runtime is enumerated by 'VEval',

Scheduling creates all entry points, for all scheduling regions, even if
they are empty, and the runtime eval loop calls everything
unconditionally. If regions are empty, this is simply a call to an empty
function. This will hurt performance on very small models, but should
not be noticeable on anything meaningful, so it is likely best to keep
to reduce complexity.

A scheduling entry points evaluate a single iteration and returns
whether it did any work, they are effectively the previous
`_eval_phase_*` functions.
2026-08-27 07:53:02 -04:00

1108 lines
51 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Create triggers for code scheduling
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
//
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Const.h"
#include "V3EmitCBase.h"
#include "V3EmitV.h"
#include "V3Order.h"
#include "V3Sched.h"
#include "V3SenExprBuilder.h"
#include "V3Stats.h"
VL_DEFINE_DEBUG_FUNCTIONS;
namespace V3Sched {
namespace {
using util::newArgument;
AstVarScope* newLocal(AstCFunc* funcp, AstNodeDType* dtypep, const std::string& name) {
FileLine* const flp = funcp->fileline();
AstScope* const scopep = funcp->scopep();
AstVar* const varp = new AstVar{flp, VVarType::BLOCKTEMP, name, dtypep};
varp->funcLocal(true);
varp->noReset(true);
funcp->addVarsp(varp);
AstVarScope* const vscp = new AstVarScope{flp, scopep, varp};
scopep->addVarsp(vscp);
return vscp;
}
} // namespace
AstCFunc* TriggerKit::createDumpExtFunc() const {
UASSERT(m_nPreWords, "Just call the regular dumping function if there are no pre triggers");
AstNetlist* const netlistp = v3Global.rootp();
FileLine* const flp = netlistp->topScopep()->fileline();
AstNodeDType* const u32DTypep = netlistp->findUInt32DType();
AstNodeDType* const strDtypep = netlistp->findStringDType();
// Dumping function always slow
const std::string name = "_dump_triggers__" + m_name + "_ext";
AstCFunc* const funcp = util::makeSubFunction(netlistp, name, true);
funcp->isStatic(true);
funcp->ifdef("VL_DEBUG");
// Add argument
AstVarScope* const eVscp = newArgument(funcp, m_trigExtDTypep, "ext", VDirection::CONSTREF);
AstVarScope* const tVscp = newArgument(funcp, strDtypep, "tag", VDirection::CONSTREF);
// Creates read/write reference
const auto rd = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
const auto wr = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::WRITE}; };
// This is a slow function, only for dumping, so we can just copy to locals
// Copy the vec part, dump it
{
AstVarScope* const vVscp = newLocal(funcp, m_trigVecDTypep, "vec");
AstVarScope* const iVscp = newLocal(funcp, u32DTypep, "i");
funcp->addStmtsp(util::setVar(iVscp, 0));
// Add loop
AstLoop* const loopp = new AstLoop{flp};
funcp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(vVscp), rd(iVscp)};
AstNodeExpr* const rhsp = new AstArraySel{flp, rd(eVscp), rd(iVscp)};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, m_nVecWords};
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(iVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(iVscp), limp}});
// Use the vec dumping function
AstCCall* const callp = new AstCCall{flp, m_dumpp};
callp->dtypeSetVoid();
callp->addArgsp(rd(vVscp));
callp->addArgsp(rd(tVscp));
funcp->addStmtsp(callp->makeStmt());
}
// Copy the pre part, zero top bits, dump it
{
AstVarScope* const pVscp = newLocal(funcp, m_trigVecDTypep, "pre");
AstVarScope* const jVscp = newLocal(funcp, u32DTypep, "j");
funcp->addStmtsp(util::setVar(jVscp, 0));
// Copy pre words
{
// Add loop
AstLoop* const loopp = new AstLoop{flp};
funcp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(pVscp), rd(jVscp)};
AstNodeExpr* const rhsp = new AstArraySel{flp, rd(eVscp), rd(jVscp)};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, m_nPreWords};
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(jVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(jVscp), limp}});
}
// Zero the rest
{
// Add loop - copy
AstLoop* const loopp = new AstLoop{flp};
funcp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(pVscp), rd(jVscp)};
AstNodeExpr* const rhsp = new AstConst{flp, AstConst::DTyped{}, m_wordDTypep};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, m_nVecWords};
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(jVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(jVscp), limp}});
}
// Use the vec dumping function
AstCCall* const callp = new AstCCall{flp, m_dumpp};
callp->dtypeSetVoid();
callp->addArgsp(rd(pVscp));
callp->addArgsp(
new AstConcatN{flp, rd(tVscp), new AstConst{flp, AstConst::String{}, " pre"}});
funcp->addStmtsp(callp->makeStmt());
}
return funcp;
}
AstCFunc* TriggerKit::createAnySetFunc(AstUnpackArrayDType* const dtypep) const {
AstNetlist* const netlistp = v3Global.rootp();
FileLine* const flp = netlistp->topScopep()->fileline();
AstNodeDType* const u32DTypep = netlistp->findUInt32DType();
// Create function
std::string name = "_trigger_anySet__" + m_name;
name += dtypep == m_trigVecDTypep ? "" : "_ext";
AstCFunc* const funcp = util::makeSubFunction(netlistp, name, m_slow);
funcp->isStatic(true);
funcp->rtnType("bool");
// Add argument
AstVarScope* const iVscp = newArgument(funcp, dtypep, "in", VDirection::CONSTREF);
// Add loop counter variable
AstVarScope* const nVscp = newLocal(funcp, u32DTypep, "n");
// Creates read reference
const auto rd = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
// Function body
AstLoop* const loopp = new AstLoop{flp};
funcp->addStmtsp(util::setVar(nVscp, 0));
funcp->addStmtsp(loopp);
funcp->addStmtsp(new AstCReturn{flp, new AstConst{flp, AstConst::BitFalse{}}});
// Loop body
const uint32_t nWords = dtypep->elementsConst();
AstNodeExpr* const condp = new AstArraySel{flp, rd(iVscp), rd(nVscp)};
AstNodeStmt* const thenp = new AstCReturn{flp, new AstConst{flp, AstConst::BitTrue{}}};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, nWords};
loopp->addStmtsp(new AstIf{flp, condp, thenp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
// Done
return funcp;
}
AstCFunc* TriggerKit::createClearFunc() const {
AstNetlist* const netlistp = v3Global.rootp();
FileLine* const flp = netlistp->topScopep()->fileline();
AstNodeDType* const u32DTypep = netlistp->findUInt32DType();
// Create function
AstCFunc* const funcp = util::makeSubFunction(netlistp, "_trigger_clear__" + m_name, m_slow);
funcp->isStatic(true);
// Add arguments
AstVarScope* const oVscp = newArgument(funcp, m_trigVecDTypep, "out", VDirection::OUTPUT);
// Add loop counter variable
AstVarScope* const nVscp = newLocal(funcp, u32DTypep, "n");
// Creates read/write reference
const auto rd = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
const auto wr = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::WRITE}; };
// Function body
AstLoop* const loopp = new AstLoop{flp};
funcp->addStmtsp(util::setVar(nVscp, 0));
funcp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(oVscp), rd(nVscp)};
AstNodeExpr* const rhsp = new AstConst{flp, AstConst::DTyped{}, m_wordDTypep};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, m_nVecWords};
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
// Done
return funcp;
}
AstCFunc* TriggerKit::createOrIntoFunc(AstUnpackArrayDType* const oDtypep,
AstUnpackArrayDType* const iDtypep) const {
AstNetlist* const netlistp = v3Global.rootp();
FileLine* const flp = netlistp->topScopep()->fileline();
AstNodeDType* const u32DTypep = netlistp->findUInt32DType();
// Create function
std::string name = "_trigger_orInto__" + m_name;
name += iDtypep == m_trigVecDTypep ? "_vec" : "_ext";
name += oDtypep == m_trigVecDTypep ? "_vec" : "_ext";
AstCFunc* const funcp = util::makeSubFunction(netlistp, name, m_slow);
funcp->isStatic(true);
// Add arguments
AstVarScope* const oVscp = newArgument(funcp, oDtypep, "out", VDirection::INOUT);
AstVarScope* const iVscp = newArgument(funcp, iDtypep, "in", VDirection::CONSTREF);
// Add loop counter variable
AstVarScope* const nVscp = newLocal(funcp, u32DTypep, "n");
// Creates read/write reference
const auto rd = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
const auto wr = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::WRITE}; };
// Function body
AstLoop* const loopp = new AstLoop{flp};
funcp->addStmtsp(util::setVar(nVscp, 0));
funcp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(oVscp), rd(nVscp)};
AstNodeExpr* const oWordp = new AstArraySel{flp, rd(oVscp), rd(nVscp)};
AstNodeExpr* const iWordp = new AstArraySel{flp, rd(iVscp), rd(nVscp)};
AstNodeExpr* const rhsp = new AstOr{flp, oWordp, iWordp};
AstConst* const outputRangeLeftp = VN_AS(oDtypep->rangep()->leftp(), Const);
AstConst* const inputRangeLeftp = VN_AS(iDtypep->rangep()->leftp(), Const);
AstNodeExpr* const limp = outputRangeLeftp->num().toSInt() < inputRangeLeftp->num().toSInt()
? outputRangeLeftp->cloneTreePure(false)
: inputRangeLeftp->cloneTreePure(false);
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLte{flp, rd(nVscp), limp}});
// Done
return funcp;
}
AstNodeExpr* TriggerKit::newAnySetCall(AstVarScope* const vscp) const {
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
if (!m_nVecWords) return new AstConst{flp, AstConst::BitFalse{}};
AstCFunc* funcp = nullptr;
if (vscp->dtypep() == m_trigVecDTypep) {
if (!m_anySetVecp) m_anySetVecp = createAnySetFunc(m_trigVecDTypep);
funcp = m_anySetVecp;
} else if (vscp->dtypep() == m_trigExtDTypep) {
if (!m_anySetExtp) m_anySetExtp = createAnySetFunc(m_trigExtDTypep);
funcp = m_anySetExtp;
} else {
vscp->v3fatalSrc("Bad trigger vector type");
}
AstCCall* const callp = new AstCCall{flp, funcp};
callp->addArgsp(new AstVarRef{flp, vscp, VAccess::WRITE});
callp->dtypeSetBit();
return callp;
}
AstNodeStmt* TriggerKit::newClearCall(AstVarScope* const vscp) const {
if (!m_nVecWords) return nullptr;
UASSERT_OBJ(vscp->dtypep() == m_trigVecDTypep, vscp, "Bad trigger vector type");
if (!m_clearp) m_clearp = createClearFunc();
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, m_clearp};
callp->addArgsp(new AstVarRef{flp, vscp, VAccess::WRITE});
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstNodeStmt* TriggerKit::newOrIntoCall(AstVarScope* const oVscp, AstVarScope* const iVscp) const {
if (!m_nVecWords) return nullptr;
UASSERT_OBJ(iVscp->dtypep() == m_trigVecDTypep || iVscp->dtypep() == m_trigExtDTypep, iVscp,
"Bad input trigger vector type");
UASSERT_OBJ(oVscp->dtypep() == m_trigVecDTypep || oVscp->dtypep() == m_trigExtDTypep, oVscp,
"Bad output trigger vector type");
const size_t mask
= ((oVscp->dtypep() == m_trigExtDTypep) << 1) | (iVscp->dtypep() == m_trigExtDTypep);
AstCFunc*& funcp = m_orIntoVecps[mask];
if (!funcp) {
funcp = createOrIntoFunc(VN_AS(oVscp->dtypep(), UnpackArrayDType),
VN_AS(iVscp->dtypep(), UnpackArrayDType));
}
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, funcp};
callp->addArgsp(new AstVarRef{flp, oVscp, VAccess::WRITE});
callp->addArgsp(new AstVarRef{flp, iVscp, VAccess::READ});
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstNodeStmt* TriggerKit::newCompBaseCall() const {
if (!m_nVecWords) return nullptr;
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, m_compVecp};
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstNodeStmt* TriggerKit::newCompExtCall(AstVarScope* vscp) const {
if (!m_nPreWords) return nullptr;
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, m_compExtp};
callp->addArgsp(new AstVarRef{flp, vscp, VAccess::READ});
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstNodeStmt* TriggerKit::newDumpCall(AstVarScope* const vscp, const std::string& tag,
bool debugOnly) const {
if (!m_nVecWords) return nullptr;
AstCFunc* funcp = nullptr;
if (vscp->dtypep() == m_trigVecDTypep) {
funcp = m_dumpp;
} else if (vscp->dtypep() == m_trigExtDTypep) {
if (!m_dumpExtp) m_dumpExtp = createDumpExtFunc();
funcp = m_dumpExtp;
} else {
vscp->v3fatalSrc("Bad trigger vector type");
}
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, funcp};
callp->addArgsp(new AstVarRef{flp, vscp, VAccess::READ});
callp->addArgsp(new AstConst{flp, AstConst::String{}, tag});
callp->dtypeSetVoid();
AstCStmt* const cstmtp = new AstCStmt{flp};
cstmtp->add("#ifdef VL_DEBUG\n");
if (debugOnly) {
cstmtp->add("if (VL_UNLIKELY(vlSymsp->_vm_contextp__->debug())) {\n");
cstmtp->add(callp->makeStmt());
cstmtp->add("}\n");
} else {
cstmtp->add(callp->makeStmt());
}
cstmtp->add("#endif");
return cstmtp;
}
AstVarScope* TriggerKit::newTrigVec(const std::string& name) const {
if (!m_nVecWords) return nullptr;
AstScope* const scopep = v3Global.rootp()->topScopep()->scopep();
return scopep->createTemp("__V" + name + "Triggered", m_trigVecDTypep);
}
AstSenTree* TriggerKit::newTriggerSenTree(AstVarScope* const vscp,
const std::vector<uint32_t>& indices) const {
AstNetlist* const netlistp = v3Global.rootp();
AstTopScope* const topScopep = netlistp->topScopep();
FileLine* const flp = topScopep->fileline();
AstSenTree* const senTreep = new AstSenTree{flp, nullptr};
topScopep->addSenTreesp(senTreep);
for (const uint32_t index : indices) {
UASSERT(index <= (m_nVecWords + m_nPreWords) * WORD_SIZE, "Invalid trigger index");
const uint32_t wordIndex = index / WORD_SIZE;
const uint32_t bitIndex = index % WORD_SIZE;
AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ};
AstNodeExpr* const aselp = new AstArraySel{flp, refp, static_cast<int>(wordIndex)};
// Use a mask & _ to extract the bit, V3Const can optimize this to combine terms
AstConst* const maskp
= new AstConst{flp, AstConst::WidthedValue{}, static_cast<int>(WORD_SIZE), 0};
maskp->num().setBit(bitIndex, '1');
AstNodeExpr* const termp = new AstAnd{flp, maskp, aselp};
senTreep->addSensesp(new AstSenItem{flp, VEdgeType::ET_TRUE, termp});
}
return senTreep;
}
AstSenTree* TriggerKit::newExtraTriggerSenTree(AstVarScope* vscp, uint32_t index) const {
UASSERT(index <= m_nExtraWords * WORD_SIZE, "Invalid external trigger index");
return newTriggerSenTree(vscp, {index + m_nSenseWords * WORD_SIZE});
}
AstNodeStmt* TriggerKit::newExtraTriggerAssignment(AstVarScope* vscp, uint32_t index) const {
index += m_nSenseWords * WORD_SIZE;
const uint32_t wordIndex = index / WORD_SIZE;
const uint32_t bitIndex = index % WORD_SIZE;
FileLine* const flp = vscp->fileline();
AstVarRef* const refp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
AstNodeExpr* const wordp = new AstArraySel{flp, refp, static_cast<int>(wordIndex)};
AstNodeExpr* const trigLhsp = new AstSel{flp, wordp, static_cast<int>(bitIndex), 1};
AstNodeExpr* const trigRhsp = new AstVarRef{flp, vscp, VAccess::READ};
return new AstAssign{flp, trigLhsp, trigRhsp};
}
void TriggerKit::addExtraTriggerAssignment(AstVarScope* vscp, uint32_t index) const {
FileLine* const flp = vscp->fileline();
// Set the trigger bit, then clear the input variable
AstNode* const setp = newExtraTriggerAssignment(vscp, index);
setp->addNext(new AstAssign{flp, new AstVarRef{flp, vscp, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}});
if (AstNode* const nodep = m_compVecp->stmtsp()) {
setp->addNext(setp, nodep->unlinkFrBackWithNext());
}
m_compVecp->addStmtsp(setp);
}
// Value-change detection for a single interface member VarScope written through a VIF.
// Creates: trigger[bit] = (vscp != prev); prev = vscp;
void TriggerKit::addValueChangeTriggerAssignment(AstNetlist* netlistp, AstCFunc* initFuncp,
AstVarScope* instVscp, uint32_t index) const {
index += m_nSenseWords * WORD_SIZE;
const uint32_t wordIndex = index / WORD_SIZE;
const uint32_t bitIndex = index % WORD_SIZE;
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
FileLine* const flp = netlistp->fileline();
AstNodeDType* const dtypep = instVscp->dtypep()->skipRefp();
// Reject types that do not support value-change comparison
if (const AstBasicDType* const bdtypep = VN_CAST(dtypep, BasicDType)) {
if (bdtypep->isEvent() || bdtypep->isString()
|| bdtypep->keyword() == VBasicDTypeKwd::CHANDLE) {
instVscp->v3warn(E_UNSUPPORTED, "Unsupported: virtual interface trigger on "
<< dtypep->prettyDTypeNameQ() << " type");
return;
}
} else if (!VN_IS(dtypep, PackArrayDType) && !VN_IS(dtypep, UnpackArrayDType)
&& !VN_IS(dtypep, NodeUOrStructDType) && !VN_IS(dtypep, ClassRefDType)) {
instVscp->v3warn(E_UNSUPPORTED, "Unsupported: virtual interface trigger on "
<< dtypep->prettyDTypeNameQ() << " type");
return;
}
const auto rdInst = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
const auto wrPrev = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::WRITE}; };
const auto rdPrev = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
// Create prev variable
const std::string prevName = "__Vtrigprevvif_" + m_name + "_"
+ instVscp->scopep()->nameDotless() + "__"
+ instVscp->varp()->name();
AstVarScope* const prevVscp = scopeTopp->createTemp(prevName, instVscp->dtypep());
// Initialize prev = inst
if (VN_IS(dtypep, UnpackArrayDType)) {
AstCMethodHard* const cmhp = new AstCMethodHard{
flp, wrPrev(prevVscp), VCMethod::UNPACKED_ASSIGN, rdInst(instVscp)};
cmhp->dtypeSetVoid();
initFuncp->addStmtsp(cmhp->makeStmt());
} else {
initFuncp->addStmtsp(new AstAssign{flp, wrPrev(prevVscp), rdInst(instVscp)});
}
// Build comparison: inst != prev
AstNodeExpr* neqp;
if (VN_IS(dtypep, UnpackArrayDType)) {
AstCMethodHard* const cmhp
= new AstCMethodHard{flp, rdPrev(prevVscp), VCMethod::UNPACKED_NEQ, rdInst(instVscp)};
cmhp->dtypeSetBit();
neqp = cmhp;
} else {
neqp = new AstNeq{flp, rdInst(instVscp), rdPrev(prevVscp)};
}
// Build post-update: prev = inst
AstNode* updp;
if (VN_IS(dtypep, UnpackArrayDType)) {
AstCMethodHard* const cmhp = new AstCMethodHard{
flp, wrPrev(prevVscp), VCMethod::UNPACKED_ASSIGN, rdInst(instVscp)};
cmhp->dtypeSetVoid();
updp = cmhp->makeStmt();
} else {
updp = new AstAssign{flp, wrPrev(prevVscp), rdInst(instVscp)};
}
// Set trigger bit
AstVarRef* const refp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
AstNodeExpr* const wordp = new AstArraySel{flp, refp, static_cast<int>(wordIndex)};
AstNodeExpr* const trigLhsp = new AstSel{flp, wordp, static_cast<int>(bitIndex), 1};
AstNode* const setp = new AstAssign{flp, trigLhsp, neqp};
// Chain: set trigger bit -> update prev
setp->addNext(updp);
// Prepend before existing statements
if (AstNode* const nodep = m_compVecp->stmtsp()) {
setp->addNext(setp, nodep->unlinkFrBackWithNext());
}
m_compVecp->addStmtsp(setp);
}
TriggerKit::TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords,
uint32_t nExtraWords, uint32_t nPreWords,
std::unordered_map<VNRef<const AstSenItem>, size_t> senItem2TrigIdx,
bool useAcc)
: m_name{name}
, m_slow{slow}
, m_nSenseWords{nSenseWords}
, m_nExtraWords{nExtraWords}
, m_nPreWords{nPreWords}
, m_senItem2TrigIdx{std::move(senItem2TrigIdx)} {
// If no triggers, we don't need to generate anything
if (!m_nVecWords) return;
// Othewise construc the parts of the kit
AstNetlist* const netlistp = v3Global.rootp();
AstScope* const scopep = netlistp->topScopep()->scopep();
FileLine* const flp = scopep->fileline();
// Data type of a single trigger word
m_wordDTypep = netlistp->findBitDType(WORD_SIZE, WORD_SIZE, VSigning::UNSIGNED);
// Data type of trigger vector
AstRange* const rp = new AstRange{flp, static_cast<int>(m_nVecWords - 1), 0};
m_trigVecDTypep = new AstUnpackArrayDType{flp, m_wordDTypep, rp};
netlistp->typeTablep()->addTypesp(m_trigVecDTypep);
// Data type of extended trigger vector, which only differs if there are pre triggers
if (m_nPreWords) {
AstRange* const ep = new AstRange{flp, static_cast<int>(m_nVecWords + m_nPreWords - 1), 0};
m_trigExtDTypep = new AstUnpackArrayDType{flp, m_wordDTypep, ep};
netlistp->typeTablep()->addTypesp(m_trigExtDTypep);
m_compExtp = util::makeSubFunction(netlistp, "_eval_triggers_ext__" + m_name, m_slow);
} else {
m_trigExtDTypep = m_trigVecDTypep;
}
// The AstVarScope representing the extended trigger vector
m_vscp = scopep->createTemp("__V" + m_name + "Triggered", m_trigExtDTypep);
m_vscp->varp()->isInternal(true);
// The trigger computation function
m_compVecp = util::makeSubFunction(netlistp, "_eval_triggers_vec__" + m_name, m_slow);
// The debug dump function, always 'slow'
m_dumpp = util::makeSubFunction(netlistp, "_dump_triggers__" + m_name, true);
m_dumpp->isStatic(true);
m_dumpp->ifdef("VL_DEBUG");
if (useAcc) {
m_vscAccp = scopep->createTemp("__V" + m_name + "TriggeredAcc", m_trigVecDTypep);
m_vscAccp->varp()->isInternal(true);
}
}
AstAssign* TriggerKit::createSenTrigVecAssignment(AstVarScope* const target,
std::vector<AstNodeExpr*>& trigps) {
FileLine* const flp = target->fileline();
AstAssign* trigStmtsp = nullptr;
// Assign sense triggers vector one word at a time
for (size_t i = 0; i < trigps.size(); i += WORD_SIZE) {
// Concatenate all bits in this trigger word using a balanced
for (uint32_t level = 0; level < WORD_SIZE_LOG2; ++level) {
const uint32_t stride = 1 << level;
for (uint32_t j = 0; j < WORD_SIZE; j += 2 * stride) {
trigps[i + j] = new AstConcat{trigps[i + j]->fileline(), trigps[i + j + stride],
trigps[i + j]};
trigps[i + j + stride] = nullptr;
}
}
// Set the whole word in the trigger vector
const int wordIndex = static_cast<int>(i / WORD_SIZE);
AstArraySel* const aselp
= new AstArraySel{flp, new AstVarRef{flp, target, VAccess::WRITE}, wordIndex};
trigStmtsp = AstNode::addNext(trigStmtsp, new AstAssign{flp, aselp, trigps[i]});
}
return trigStmtsp;
}
TriggerKit TriggerKit::create(AstNetlist* netlistp, //
AstCFunc* const initFuncp, //
SenExprBuilder& senExprBuilder, //
const std::vector<const AstSenTree*>& preTreeps, //
const std::vector<const AstSenTree*>& senTreeps, //
const string& name, //
const ExtraTriggers& extraTriggers, //
bool slow, //
bool useAcc) {
// Need to gather all the unique SenItems under the given SenTrees
// List of unique SenItems used by all 'senTreeps'
std::vector<const AstSenItem*> senItemps;
// Map from SenItem to trigger bit standing for that SenItem. There might
// be duplicate SenItems, we map all of them to the same index.
std::unordered_map<VNRef<const AstSenItem>, size_t> senItem2TrigIdx;
// Process the 'pre' trees first, so they are at the begining of the vector
for (const AstSenTree* const senTreep : preTreeps) {
for (const AstSenItem *itemp = senTreep->sensesp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), SenItem);
UASSERT_OBJ(itemp->isClocked() || itemp->isHybrid(), itemp,
"Cannot create trigger expression for non-clocked sensitivity");
const auto pair = senItem2TrigIdx.emplace(*itemp, senItemps.size());
if (pair.second) senItemps.push_back(itemp);
}
}
const uint32_t nPreSenItems = senItemps.size();
V3Stats::addStat("Scheduling, '" + name + "' pre triggers", nPreSenItems);
// Number of pre triggers, rounded up to a full word.
const uint32_t nPreTriggers = vlstd::roundUpToMultipleOf<WORD_SIZE>(senItemps.size());
// Pad 'senItemps' to nSenseTriggers with nullptr
senItemps.resize(nPreTriggers);
// Number of words for pre triggers
const uint32_t nPreWords = nPreTriggers / WORD_SIZE;
// Process the rest of the trees
for (const AstSenTree* const senTreep : senTreeps) {
for (const AstSenItem *itemp = senTreep->sensesp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), SenItem);
UASSERT_OBJ(itemp->isClocked() || itemp->isHybrid(), itemp,
"Cannot create trigger expression for non-clocked sensitivity");
const auto pair = senItem2TrigIdx.emplace(*itemp, senItemps.size());
if (pair.second) senItemps.push_back(itemp);
}
}
const uint32_t nSenItems = senItemps.size() - nPreTriggers;
V3Stats::addStat("Scheduling, '" + name + "' sense triggers", nSenItems + nPreSenItems);
// Number of sense triggers, rounded up to a full word
const uint32_t nSenseTriggers = vlstd::roundUpToMultipleOf<WORD_SIZE>(senItemps.size());
// Pad 'senItemps' to nSenseTriggers with nullptr
senItemps.resize(nSenseTriggers);
// Number of words sense triggers (inclued pre)
const uint32_t nSenseWords = nSenseTriggers / WORD_SIZE;
// Allocate space for the extra triggers
V3Stats::addStat("Scheduling, '" + name + "' extra triggers", extraTriggers.size());
// Number of extra triggers, rounded up to a full word.
const uint32_t nExtraTriggers = vlstd::roundUpToMultipleOf<WORD_SIZE>(extraTriggers.size());
const uint32_t nExtraWords = nExtraTriggers / WORD_SIZE;
// We can now construct the trigger kit - this constructs all items that will be kept
TriggerKit kit{name, slow, nSenseWords, nExtraWords, nPreWords, senItem2TrigIdx, useAcc};
// If there are no triggers we are done
if (!kit.m_nVecWords) return kit;
FileLine* const flp = netlistp->topScopep()->fileline();
// Creates read/write reference
const auto rd = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
const auto wr = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::WRITE}; };
// Construct the comp and dump functions
// Add arguments to the dump function. The trigger vector is passed into
// the dumping function via reference so one dump function can dump all
// different copies of the trigger vector. To do so, it also needs the tag
// string at runtime, which is the second argument.
AstVarScope* const dumpTrgp
= newArgument(kit.m_dumpp, kit.m_trigVecDTypep, "triggers", VDirection::CONSTREF);
AstVarScope* const dumpTagp
= newArgument(kit.m_dumpp, netlistp->findStringDType(), "tag", VDirection::CONSTREF);
// Add a print to the dumping function if there are no triggers pending
{
AstIf* const ifp = new AstIf{flp, new AstLogNot{flp, kit.newAnySetCall(dumpTrgp)}};
kit.m_dumpp->addStmtsp(ifp);
AstCStmt* const cstmtp = new AstCStmt{flp};
ifp->addThensp(cstmtp);
cstmtp->add("VL_DBG_MSGS(\" No '\" + ");
cstmtp->add(rd(dumpTagp));
cstmtp->add(" + \"\' region triggers active\\n\");");
}
// Adds a debug dumping statement for this trigger
const auto addDebug = [&](uint32_t index, const string& text) {
const int wrdIndex = static_cast<int>(index / WORD_SIZE);
const int bitIndex = static_cast<int>(index % WORD_SIZE);
AstNodeExpr* const aselp = new AstArraySel{flp, rd(dumpTrgp), wrdIndex};
AstNodeExpr* const condp = new AstSel{flp, aselp, bitIndex, 1};
AstIf* const ifp = new AstIf{flp, condp};
kit.m_dumpp->addStmtsp(ifp);
AstCStmt* const cstmtp = new AstCStmt{flp};
ifp->addThensp(cstmtp);
cstmtp->add("VL_DBG_MSGS(\" '\" + ");
cstmtp->add(rd(dumpTagp));
cstmtp->add(" + \"' region trigger index " + std::to_string(index) + " is active: " + text
+ "\\n\");");
};
// Add sense trigger computation
// List of trigger computation expressions
std::vector<AstNodeExpr*> trigps;
trigps.reserve(nSenseTriggers);
// Statements to exectue at initialization time to fire initial triggers
AstNodeStmt* initialTrigsp = nullptr;
for (size_t i = 0; i < senItemps.size(); ++i) {
const AstSenItem* const senItemp = senItemps[i];
// If this is just paddign, use constant zero
if (!senItemp) {
trigps.emplace_back(new AstConst{flp, AstConst::BitFalse{}});
continue;
}
// Create the trigger computation expression
const auto& pair = senExprBuilder.build(senItemp);
trigps.emplace_back(pair.first);
// Add initialization time trigger
if (pair.second || v3Global.opt.xInitialEdge()) {
const int wrdIndex = static_cast<int>(i / WORD_SIZE);
const int bitIndex = static_cast<int>(i % WORD_SIZE);
AstNodeExpr* const wordp = new AstArraySel{flp, wr(kit.m_vscp), wrdIndex};
AstNodeExpr* const lhsp = new AstSel{flp, wordp, bitIndex, 1};
AstNodeExpr* const rhsp = new AstConst{flp, AstConst::BitTrue{}};
if (useAcc) {
initFuncp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
} else {
initialTrigsp = AstNode::addNext(initialTrigsp, new AstAssign{flp, lhsp, rhsp});
}
}
// Add a debug statement for this trigger
std::stringstream ss;
ss << "@(";
V3EmitV::verilogForTree(senItemp, ss);
ss << ")";
std::string desc = VString::quoteBackslash(ss.str());
desc = VString::replaceSubstr(desc, "\n", "\\n");
addDebug(i, desc);
}
UASSERT(trigps.size() == nSenseTriggers, "Inconsistent number of trigger expressions");
AstAssign* const trigStmtsp = createSenTrigVecAssignment(kit.m_vscp, trigps);
// Add a print for each of the extra triggers
for (unsigned i = 0; i < extraTriggers.size(); ++i) {
addDebug(nSenseTriggers + i,
"Internal '" + name + "' trigger - " + extraTriggers.m_descriptions.at(i));
}
// Construct the maps from old SenTrees to new SenTrees
{
std::vector<uint32_t> indices;
indices.reserve(32);
// Map regular SenTrees to the Sense triggers
for (const AstSenTree* const senTreep : senTreeps) {
indices.clear();
for (const AstSenItem *itemp = senTreep->sensesp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), SenItem);
indices.push_back(senItem2TrigIdx.at(*itemp));
}
kit.m_mapVec[senTreep] = kit.newTriggerSenTree(kit.m_vscp, indices);
}
// Map Pre SenTrees to the Pre triggers
for (const AstSenTree* const senTreep : preTreeps) {
indices.clear();
for (const AstSenItem *itemp = senTreep->sensesp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), SenItem);
indices.push_back(senItem2TrigIdx.at(*itemp) + kit.m_nVecWords * WORD_SIZE);
}
kit.m_mapPre[senTreep] = kit.newTriggerSenTree(kit.m_vscp, indices);
}
}
// Get the SenExprBuilder results
const SenExprBuilder::Results senResults = senExprBuilder.getResultsAndClearUpdates();
// Add the SenExprBuilder init statements to the static initialization functino
for (AstNodeStmt* const nodep : senResults.m_inits) initFuncp->addStmtsp(nodep);
// Assemble the base trigger computation function
AstScope* const scopep = netlistp->topScopep()->scopep();
{
AstCFunc* const fp = kit.m_compVecp;
// Trigger computation
for (AstNodeStmt* const nodep : senResults.m_preUpdates) fp->addStmtsp(nodep);
fp->addStmtsp(trigStmtsp);
for (AstNodeStmt* const nodep : senResults.m_postUpdates) fp->addStmtsp(nodep);
// Add the initialization time triggers
if (initialTrigsp) {
AstVarScope* const initVscp = scopep->createTemp("__V" + name + "DidInit", 1);
AstIf* const ifp = new AstIf{flp, new AstNot{flp, rd(initVscp)}};
fp->addStmtsp(ifp);
ifp->branchPred(VBranchPred::BP_UNLIKELY);
ifp->addThensp(util::setVar(initVscp, 1));
ifp->addThensp(initialTrigsp);
}
util::splitCheck(fp);
};
// If there are 'pre' triggers, compute them
if (kit.m_nPreWords) {
AstCFunc* const fp = kit.m_compExtp;
// Add an argument to the function that takes the latched values
AstVarScope* const latchedp
= newArgument(fp, kit.m_trigVecDTypep, "latched", VDirection::CONSTREF);
// Add loop counter variable - this can't be local because we call util::splitCheck
AstVarScope* const nVscp = scopep->createTemp("__V" + name + "TrigPreLoopCounter", 32);
nVscp->varp()->noReset(true);
// Add a loop to compute the pre words
AstLoop* const loopp = new AstLoop{flp};
fp->addStmtsp(util::setVar(nVscp, 0));
fp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const offsetp = new AstConst{flp, kit.m_nVecWords};
AstNodeExpr* const lIdxp = new AstAdd{flp, rd(nVscp), offsetp};
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(kit.m_vscp), lIdxp};
AstNodeExpr* const aWordp = new AstArraySel{flp, rd(kit.m_vscp), rd(nVscp)};
AstNodeExpr* const bWordp = new AstArraySel{flp, rd(latchedp), rd(nVscp)};
AstNodeExpr* const rhsp = new AstAnd{flp, aWordp, new AstNot{flp, bWordp}};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, nPreWords};
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
util::splitCheck(fp);
}
// Done with the trigger computation function, split as might be large
// The debug code might leak signal names, so simply delete it when using --protect-ids
if (v3Global.opt.protectIds()) kit.m_dumpp->stmtsp()->unlinkFrBackWithNext()->deleteTree();
// Done with the trigger dump function, split as might be large
util::splitCheck(kit.m_dumpp);
return kit;
}
// Find all CAwaits, clear SenTrees inside them, generate before-trigger functions (functions that
// shall be called before awaiting for a VCMethod::SCHED_TRIGGER) and add thier calls before
// proper CAwaits
class AwaitBeforeTrigVisitor final : public VNVisitor {
const VNUser1InUse m_user1InUse;
/**
* AstCAwait::user1() -> bool. True if node has been visited
* AstSenTree::user1p() -> AstCFunc*. Function that has to be called before awaiting
* for CAwait pointing to this SenTree
* AstCFunc::user1p() -> AstVarScope* Function's local temporary extended trigger
* vector variable scope
*/
// Netlist - needed for using util::makeSubFunction()
AstNetlist* const m_netlistp;
// Trigger kit - for accessing trigger vectors and mapping senItems to thier indexes
const TriggerKit& m_trigKit;
// Expression builder - for building expressions from SenItems
SenExprBuilder& m_senExprBuilder;
// Generator of unique names for before-trigger function
V3UniqueNames m_beforeTriggerFuncUniqueName;
// Vector containing every generated CFuncs and related SenTree
std::vector<std::pair<AstCFunc*, AstSenTree*>> m_generatedFuncs;
// Vector containing SenTrees and coresponding scheduler
std::vector<std::pair<AstSenTree*, AstNodeExpr*>> m_senTreeToSched;
// Map containing vectors of SenItems that share the same prevValue variable
std::unordered_map<VNRef<AstNode>, std::vector<AstSenItem*>> m_senExprToSenItem;
// Returns node which is used for grouping SenItems in `m_senExprToSenItem`
static AstNode* getSenHashNode(const AstSenItem* const nodep) {
if (AstVarRef* const varRefp = VN_CAST(nodep->sensp(), VarRef)) return varRefp;
return nodep->sensp();
}
// Populates `m_senExprToSenItem` with every group of SenItems that share the same prevValue
// variable. Groups that contain only one type of an edge are omitted.
void fillSenExprToSenItem() {
for (auto senTreeSched : m_senTreeToSched) {
AstSenTree* const senTreep = senTreeSched.first;
for (AstSenItem* senItemp = senTreep->sensesp(); senItemp;
senItemp = VN_AS(senItemp->nextp(), SenItem)) {
const VEdgeType edge = senItemp->edgeType();
if (edge.anEdge() || edge == VEdgeType::ET_CHANGED
|| edge == VEdgeType::ET_HYBRID) {
m_senExprToSenItem[*getSenHashNode(senItemp)].push_back(senItemp);
}
}
}
std::vector<VNRef<AstNode>> toRemove;
for (const auto& senExprToSenTree : m_senExprToSenItem) {
std::vector<AstSenItem*> senItemps = senExprToSenTree.second;
toRemove.push_back(senExprToSenTree.first);
for (size_t i = 1; i < senItemps.size(); ++i) {
if (senItemps[i]->edgeType() != senItemps[i - 1]->edgeType()) {
toRemove.pop_back();
break;
}
}
}
for (const VNRef<AstNode> it : toRemove) m_senExprToSenItem.erase(it);
}
// For set of bits indexes (of sensitivity vector) return map from those indexes to set
// of schedulers sensitive to these indexes. Indices are split into word index and bit
// masking this index within given word
std::map<size_t, std::map<size_t, std::vector<AstNodeExpr*>>>
getUsedTriggersToTrees(const std::set<size_t>& usedTriggers) {
std::map<size_t, std::map<size_t, std::vector<AstNodeExpr*>>> usedTrigsToUsingTrees;
for (auto senTreeSched : m_senTreeToSched) {
const AstSenTree* const senTreep = senTreeSched.first;
AstNodeExpr* const shedp = senTreeSched.second;
// Find all common SenItem indexes for `senTreep` and `usedTriggers`
for (AstSenItem* senItemp = senTreep->sensesp(); senItemp;
senItemp = VN_AS(senItemp->nextp(), SenItem)) {
const size_t idx = m_trigKit.senItem2TrigIdx(senItemp);
if (usedTriggers.find(idx) != usedTriggers.end()) {
usedTrigsToUsingTrees[idx / TriggerKit::WORD_SIZE]
[size_t{1} << (idx % TriggerKit::WORD_SIZE)]
.push_back(shedp);
}
}
}
if (VL_UNLIKELY(v3Global.opt.debugCheck())) {
for (const auto& triggersToTrees : usedTrigsToUsingTrees) {
for (const auto& bitsToTrees : triggersToTrees.second) {
const std::set<const AstNodeExpr*> exprps{bitsToTrees.second.begin(),
bitsToTrees.second.end()};
UASSERT(bitsToTrees.second.size() == exprps.size(),
"There is a SenTree with two SenItems indicating to the same bit");
}
}
}
return usedTrigsToUsingTrees;
}
// Returns a CCall to a before-trigger function for a given SenTree,
// Constructs such a function if it doesn't exist yet
AstCCall* getBeforeTriggerStmt(AstSenTree* const senTreep) {
FileLine* const flp = senTreep->fileline();
if (!senTreep->user1p()) {
AstCFunc* const funcp = util::makeSubFunction(
m_netlistp, m_beforeTriggerFuncUniqueName.get(senTreep), false);
senTreep->user1p(funcp);
// Create a local temporary extended vector
AstVarScope* const vscAccp = m_trigKit.vscAccp();
AstVarScope* const tmpp = vscAccp->scopep()->createTempLike("__VTmp", vscAccp);
AstVar* const tmpVarp = tmpp->varp()->unlinkFrBack();
funcp->user1p(tmpp);
funcp->addVarsp(tmpVarp);
// This function can be called multiple times, and accesses model state, which
// violates the assumption made in V3Life that there is no such function.
funcp->noLife(true);
tmpVarp->funcLocal(true);
tmpVarp->noReset(true);
AstVar* const argp = new AstVar{flp, VVarType::BLOCKTEMP, "__VeventDescription",
senTreep->findBasicDType(VBasicDTypeKwd::CHARPTR)};
argp->funcLocal(true);
argp->direction(VDirection::INPUT);
funcp->addArgsp(argp);
// Scope is created in the constructor after iterate finishes
m_generatedFuncs.emplace_back(funcp, senTreep);
}
AstCCall* const callp = new AstCCall{flp, VN_AS(senTreep->user1p(), CFunc)};
callp->dtypeSetVoid();
return callp;
}
void visit(AstCAwait* const nodep) override {
if (nodep->user1SetOnce()) return;
// Check whether it is a CAwait for a VCMethod::SCHED_TRIGGER
if (const AstCMethodHard* const cMethodHardp = VN_CAST(nodep->exprp(), CMethodHard)) {
if (cMethodHardp->method() == VCMethod::SCHED_TRIGGER) {
AstCCall* const beforeTrigp = getBeforeTriggerStmt(nodep->sentreep());
// Add eventDescription argument value to a CCall - it is used for --runtime-debug
AstNode* const pinp = cMethodHardp->pinsp()->nextp()->nextp();
UASSERT_OBJ(pinp, cMethodHardp, "No event description");
beforeTrigp->addArgsp(VN_AS(pinp, NodeExpr)->cloneTree(false));
// Call the before-trigger function before the CAwait
nodep->addHereThisAsNext(beforeTrigp->makeStmt());
m_senTreeToSched.emplace_back(nodep->sentreep(), cMethodHardp->fromp());
}
}
nodep->clearSentreep(); // Clear as these sentrees will get deleted later
iterate(nodep);
}
void visit(AstNode* const nodep) override { iterateChildren(nodep); }
public:
AwaitBeforeTrigVisitor(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
const TriggerKit& trigKit)
: m_netlistp{netlistp}
, m_trigKit{trigKit}
, m_senExprBuilder{senExprBuilder}
, m_beforeTriggerFuncUniqueName{"__VbeforeTrig"} {
iterate(netlistp);
fillSenExprToSenItem();
std::vector<AstNodeExpr*> trigps;
std::set<size_t> usedTriggers;
// In each of before-trigger functions check if anything was triggered and mark as ready
// triggered schedulers
for (const auto& funcToUsedTriggers : m_generatedFuncs) {
AstCFunc* const funcp = funcToUsedTriggers.first;
AstVarScope* const vscp = VN_AS(funcp->user1p(), VarScope);
FileLine* const flp = funcp->fileline();
// Generate trigger evaluation
{
AstSenTree* const senTreep = funcToUsedTriggers.second;
// Puts `exprp` at `pos` and makes sure that trigps.size() is multiple of
// TriggerKit::WORD_SIZE
const auto emplaceAt
= [flp, &trigps, &usedTriggers](AstNodeExpr* const exprp, const size_t pos) {
const size_t targetSize
= vlstd::roundUpToMultipleOf<TriggerKit::WORD_SIZE>(pos + 1);
if (trigps.capacity() < targetSize) trigps.reserve(targetSize * 2);
while (trigps.size() < targetSize) {
trigps.push_back(new AstConst{flp, AstConst::BitFalse{}});
}
trigps[pos]->deleteTree();
trigps[pos] = exprp;
usedTriggers.insert(pos);
};
// Find all trigger indexes of SenItems inside `senTreep`
// and add them to `trigps` and `usedTriggers`
for (const AstSenItem* itemp = senTreep->sensesp(); itemp;
itemp = VN_AS(itemp->nextp(), SenItem)) {
const size_t idx = m_trigKit.senItem2TrigIdx(itemp);
emplaceAt(m_senExprBuilder.build(itemp).first, idx);
auto iter = m_senExprToSenItem.find(*getSenHashNode(itemp));
if (iter != m_senExprToSenItem.end()) {
for (AstSenItem* const additionalItemp : iter->second) {
const size_t idx = m_trigKit.senItem2TrigIdx(additionalItemp);
emplaceAt(m_senExprBuilder.build(additionalItemp).first, idx);
}
}
}
// Fill the function with neccessary statements
const SenExprBuilder::Results results
= m_senExprBuilder.getResultsAndClearUpdates();
for (AstNodeStmt* const stmtsp : results.m_inits) funcp->addStmtsp(stmtsp);
for (AstNodeStmt* const stmtsp : results.m_preUpdates) funcp->addStmtsp(stmtsp);
funcp->addStmtsp(TriggerKit::createSenTrigVecAssignment(vscp, trigps));
trigps.clear();
for (AstNodeStmt* const stmtsp : results.m_postUpdates) funcp->addStmtsp(stmtsp);
}
const std::map<size_t, std::map<size_t, std::vector<AstNodeExpr*>>>
usedTrigsToUsingTrees = getUsedTriggersToTrees(usedTriggers);
usedTriggers.clear();
// Helper returning expression getting array index `idx` from `scocep` with access
// `access`
const auto getIdx = [flp](AstVarScope* const scocep, VAccess access, size_t idx) {
return new AstArraySel{flp, new AstVarRef{flp, scocep, access},
new AstConst{flp, AstConst::Unsized64{}, idx}};
};
// Get eventDescription argument
AstVarScope* const argpVscp = new AstVarScope{flp, funcp->scopep(), funcp->argsp()};
funcp->scopep()->addVarsp(argpVscp);
// Mark as ready triggered schedulers
for (const auto& triggersToTrees : usedTrigsToUsingTrees) {
const size_t word = triggersToTrees.first;
for (const auto& bitsToTrees : triggersToTrees.second) {
const size_t bit = bitsToTrees.first;
const auto& schedulers = bitsToTrees.second;
// Check if given bit is fired - single bits are checked since
// usually there is only a few of them (only one most of the times as we await
// only for one event)
AstConst* const maskConstp = new AstConst{flp, AstConst::Unsized64{}, bit};
AstAnd* const condp
= new AstAnd{flp, getIdx(vscp, VAccess::READ, word), maskConstp};
AstIf* const ifp = new AstIf{flp, condp};
// Call ready() on each scheduler sensitive to `condp`
for (AstNodeExpr* const schedp : schedulers) {
AstCMethodHard* const callp = new AstCMethodHard{
flp, schedp->cloneTree(false), VCMethod::SCHED_READY};
callp->dtypeSetVoid();
callp->addPinsp(new AstVarRef{flp, argpVscp, VAccess::READ});
ifp->addThensp(callp->makeStmt());
}
funcp->addStmtsp(ifp);
}
}
AstVarScope* const vscAccp = m_trigKit.vscAccp();
// Add touched values to accumulator
for (const auto& triggersToTrees : usedTrigsToUsingTrees) {
const size_t word = triggersToTrees.first;
funcp->addStmtsp(new AstAssign{flp, getIdx(vscAccp, VAccess::WRITE, word),
new AstOr{flp, getIdx(vscAccp, VAccess::READ, word),
getIdx(vscp, VAccess::READ, word)}});
}
}
}
~AwaitBeforeTrigVisitor() override = default;
};
void beforeTrigVisitor(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
const TriggerKit& trigKit) {
AwaitBeforeTrigVisitor{netlistp, senExprBuilder, trigKit};
}
} // namespace V3Sched