mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 18:13:50 +02:00
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.
1108 lines
51 KiB
C++
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
|