634 lines
30 KiB
C++
634 lines
30 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Transform randsequence statements
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Copyright 2003-2024 by Wilson Snyder. This program is free software; you
|
|
// can redistribute it and/or modify it under the terms of either the GNU
|
|
// Lesser General Public License Version 3 or the Perl Artistic License
|
|
// Version 2.0.
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
// V3Randomize's Transformations:
|
|
//
|
|
// Convert each RandSequence production into a production task, and hookup.
|
|
// PROD({x}) -> TASK(__Vrs_{x}, ARG(__VrsBreak), arguments)
|
|
// JUMPBLOCK
|
|
//
|
|
// Every variable referenced in the RandSequence becomes an ARG passed
|
|
// around. (Alternative would be make a VARXREF's but the needed variable may
|
|
// be on the stack, subject to recursion, or otherwise.)
|
|
//
|
|
// A production becomes a conditional execution of the PRODLIST below
|
|
// for a single production list
|
|
// RSPROD(RSRULE(weight, stmt))
|
|
// -> IF(weight != 0, stmt)
|
|
// or RSPROD(RSRULE(weight1, stmt!), RSRULE(weight2, stmt2))
|
|
// -> RANDCASE(CASEITEM(weight1, stmt1), CASEITEM(weight2, stmt2))
|
|
//
|
|
// Return becomes a jump to end of production task
|
|
// RSRETURN -> JUMPBLOCK(..., JUMPGO)
|
|
//
|
|
// Break becomes a jump to end of production task, and sets a variable
|
|
// that the production call will check
|
|
// RSBREAK -> ASSIGH(__VrsBreak, 1)
|
|
// JUMPBLOCK(..., JUMPGO)
|
|
//
|
|
// Production calls become a function call
|
|
// call({x}) -> TASKCALL(__Vrs_{x}, ARG(__VrsBreak), arguments)
|
|
// IF(__VrsBreak) JUMPGO-break
|
|
//
|
|
// Rand join maps to a large complicated function, described in comments below
|
|
//
|
|
// If, Case, and Repeat map to normal non-randsequence nodes
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
#include "verilatedos.h"
|
|
|
|
#include "V3RandSequence.h"
|
|
|
|
#include "V3Ast.h"
|
|
#include "V3Error.h"
|
|
#include "V3FileLine.h"
|
|
#include "V3Global.h"
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
// ######################################################################
|
|
// Matcher classes (for suggestion matching)
|
|
|
|
class RSProdMatcher final : public VNodeMatcher {
|
|
public:
|
|
bool nodeMatch(const AstNode* nodep) const override { return VN_IS(nodep, RSProd); }
|
|
};
|
|
|
|
//######################################################################
|
|
// Visitor that marks classes needing a randomize() method
|
|
|
|
class RandSequenceVisitor final : public VNVisitor {
|
|
// STATE - global
|
|
std::map<AstRSProd*, AstNodeFTask*> m_prodFuncps; // Generated production functions
|
|
|
|
// STATE - for current visit position (use VL_RESTORER)
|
|
AstNodeModule* m_modp = nullptr; // Current module
|
|
AstRandSequence* m_rsp = nullptr; // Current randsequence
|
|
AstRSProd* m_startProdp = nullptr; // Starting production
|
|
AstNodeFTask* m_prodFuncp = nullptr; // Production function being built
|
|
AstJumpBlock* m_jumpBlockp = nullptr; // Building jumpblock for return/break
|
|
AstVar* m_breakVarp = nullptr; // Break variable
|
|
std::unordered_set<AstVar*> m_localizes; // Variables to become function inouts
|
|
std::map<std::string, AstVar*> m_localizeNames; // Ordered names of function inouts
|
|
std::unordered_map<const AstVar*, AstVar*> m_localizeRemaps; // New ports for old vars
|
|
|
|
// METHODS
|
|
void findLocalizes(AstRandSequence* nodep) {
|
|
std::set<AstVar*> localVars;
|
|
nodep->foreach([&](AstNode* const nodep) {
|
|
if (AstVarRef* const anodep = VN_CAST(nodep, VarRef)) {
|
|
m_localizes.emplace(anodep->varp());
|
|
} else if (AstVar* const anodep = VN_CAST(nodep, Var)) {
|
|
localVars.emplace(anodep);
|
|
}
|
|
});
|
|
// Clear every variable allocated in the RandSequence
|
|
for (AstVar* varp : localVars) m_localizes.erase(varp);
|
|
// Sort by name, so function arguments are stable
|
|
for (AstVar* varp : m_localizes) {
|
|
UINFO(9, "localize " << varp);
|
|
m_localizeNames.emplace(varp->name(), varp);
|
|
}
|
|
}
|
|
|
|
AstVar* newBreakVar(FileLine* fl, bool asOutput) {
|
|
AstVar* const varp = new AstVar{fl, VVarType::PORT, "__VrsBreak", VFlagBitPacked{}, 1};
|
|
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
|
varp->funcLocal(true);
|
|
if (asOutput) varp->direction(VDirection::OUTPUT);
|
|
AstNode* outp = varp;
|
|
outp->addNext(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
|
|
new AstConst{fl, AstConst::BitFalse{}}});
|
|
|
|
// Also add arguments as next's
|
|
for (auto& itr : m_localizeNames) {
|
|
const AstVar* const lvarp = itr.second;
|
|
AstVar* const iovarp
|
|
= new AstVar{fl, VVarType::PORT, "__Vrsarg_" + lvarp->name(), lvarp};
|
|
iovarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
|
iovarp->funcLocal(true);
|
|
iovarp->direction(VDirection::REF);
|
|
varp->addNext(iovarp);
|
|
m_localizeRemaps.emplace(lvarp, iovarp);
|
|
}
|
|
return varp;
|
|
}
|
|
|
|
AstTask* newStartFunc(AstRandSequence* nodep) {
|
|
// Create wrapper that is called by the sequence
|
|
AstTask* const taskp = new AstTask{m_startProdp->fileline(), m_rsp->name(), nullptr};
|
|
taskp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
|
m_modp->addStmtsp(taskp);
|
|
|
|
// Create local (not output) break variable
|
|
VL_RESTORER(m_localizeRemaps);
|
|
AstVar* breakVarp = newBreakVar(nodep->fileline(), false);
|
|
taskp->addStmtsp(breakVarp);
|
|
|
|
// Call the start production's task
|
|
taskp->addStmtsp(newProdFuncRef(nodep, m_startProdp, breakVarp));
|
|
|
|
UINFOTREE(9, taskp, "newStart", "");
|
|
return taskp;
|
|
}
|
|
|
|
AstNode* newProdFuncRef(AstNode* nodep, AstRSProd* prodp, AstVar* breakVarp) {
|
|
auto it = m_prodFuncps.find(prodp);
|
|
UASSERT_OBJ(it != m_prodFuncps.end(), nodep, "No production function made");
|
|
AstNodeFTask* const prodFuncp = it->second;
|
|
FileLine* const fl = nodep->fileline();
|
|
AstArg* const argsp
|
|
= new AstArg{fl, breakVarp->name(), new AstVarRef{fl, breakVarp, VAccess::WRITE}};
|
|
for (auto& itr : m_localizeNames) {
|
|
const AstVar* const lvarp = itr.second;
|
|
AstVar* const iovarp = m_localizeRemaps[lvarp];
|
|
UASSERT_OBJ(iovarp, nodep, "No new port variable for local variable" << lvarp);
|
|
argsp->addNext(new AstArg{nodep->fileline(), "__Vrsarg_" + lvarp->name(),
|
|
new AstVarRef{fl, iovarp, VAccess::READWRITE}});
|
|
}
|
|
AstNode* const newp
|
|
= new AstStmtExpr{fl, new AstTaskRef{fl, VN_AS(prodFuncp, Task), argsp}};
|
|
return newp;
|
|
}
|
|
|
|
void newProdFunc(AstRSProd* nodep) {
|
|
// Task, as time may pass
|
|
AstTask* const newp
|
|
= new AstTask{nodep->fileline(), m_rsp->name() + "_" + nodep->name(), nullptr};
|
|
m_modp->addStmtsp(newp);
|
|
m_prodFuncps.emplace(nodep, newp);
|
|
UINFOTREE(9, newp, "newProd", "");
|
|
}
|
|
|
|
AstNode* newProdRandJoin(AstRSProd* prodp, AstRSProdList* prodlistp) {
|
|
// For weight == 1.0 longer sequence (favor stay in a)
|
|
// For weight == 0.0 shorter squence (favor change a/b)
|
|
UASSERT_OBJ(prodlistp->weightp(), prodlistp, "Weight should have default CONST(0.5)");
|
|
AstNodeExpr* weightp = prodlistp->weightp()->unlinkFrBack();
|
|
|
|
// Build map of production lists and productions we need to join
|
|
// Must mainain the relative order of each sequence.
|
|
std::deque<AstRSProdItem*> lists;
|
|
std::unordered_map<AstRSProdItem*, std::deque<AstNodeStmt*>> listStmts;
|
|
for (AstRSProdItem* proditemp = VN_AS(prodlistp->prodsp(), RSProdItem); proditemp;
|
|
proditemp = VN_AS(proditemp->nextp(), RSProdItem)) {
|
|
lists.push_back(proditemp);
|
|
AstRSProd* const subProdp = proditemp->prodp();
|
|
if (!subProdp) continue;
|
|
if (!subProdp->rulesp()) continue;
|
|
if (!subProdp->rulesp()->prodlistsp()) continue;
|
|
for (AstNodeStmt* subStmtp
|
|
= VN_AS(subProdp->rulesp()->prodlistsp()->prodsp(), NodeStmt);
|
|
subStmtp; subStmtp = VN_AS(subStmtp->nextp(), NodeStmt)) {
|
|
listStmts[proditemp].push_back(subStmtp);
|
|
}
|
|
}
|
|
|
|
UINFO(9, "RandJoin productions called:");
|
|
for (AstRSProdItem* proditemp : lists) {
|
|
UINFO(9, " list " << proditemp);
|
|
for (AstNodeStmt* prodp : listStmts[proditemp]) UINFO(9, " calls " << prodp);
|
|
}
|
|
|
|
// Need to clone all nodes used
|
|
|
|
FileLine* fl = prodlistp->fileline();
|
|
AstNode* newp = nullptr;
|
|
|
|
// "bool _Vjoin_repick = true;" // Pick a new longer sequence
|
|
AstVar* repickVarp = new AstVar{fl, VVarType::VAR, "_Vjoin_repick", VFlagBitPacked{}, 1};
|
|
repickVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
|
repickVarp->funcLocal(true);
|
|
m_breakVarp->addNextHere(repickVarp); // Add to function top, not newp
|
|
newp = AstNode::addNext(newp,
|
|
new AstAssign{fl, new AstVarRef{fl, repickVarp, VAccess::WRITE},
|
|
new AstConst{fl, AstConst::BitTrue{}}});
|
|
|
|
// "int _Vjoin_nleft_0 = N(items);" // N(a) Number left to generate in list 0, starts at
|
|
// N(list0)
|
|
std::deque<AstVar*> nleftVarps;
|
|
int i = 0;
|
|
for (AstRSProdItem* proditemp : lists) {
|
|
// 'rand join arule arule arule' is legal, so need numbered, not non-unique named
|
|
// nleft's
|
|
AstVar* const varp = new AstVar{
|
|
fl, VVarType::VAR, "_Vjoin_nleft_" + std::to_string(i++), VFlagBitPacked{}, 32};
|
|
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
|
varp->funcLocal(true);
|
|
m_breakVarp->addNextHere(varp); // Add to function top, not newp
|
|
nleftVarps.push_back(varp);
|
|
|
|
newp = AstNode::addNext(
|
|
newp,
|
|
new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
|
|
new AstConst{fl, AstConst::WidthedValue{}, 32,
|
|
static_cast<uint32_t>(listStmts[proditemp].size())}});
|
|
}
|
|
|
|
// "int _Vjoin_picked_elist = 0;" // 0 = pick first eligible (non-finished) list, 1 = pick
|
|
// second, etc
|
|
AstVar* const pickedVarp
|
|
= new AstVar{fl, VVarType::VAR, "_Vjoin_picked_elist", VFlagBitPacked{}, 1};
|
|
pickedVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
|
pickedVarp->funcLocal(true);
|
|
m_breakVarp->addNextHere(pickedVarp); // Add to function top, not newp
|
|
|
|
// "while(1) {" // Generate until make complete sequence
|
|
AstJumpBlock* const whilep = new AstJumpBlock{fl, nullptr};
|
|
newp = AstNode::addNext(newp, new AstLoop{fl, whilep});
|
|
{
|
|
// "if ((std::rand() & 0xffffUL) > (0xffff * weight)) _Vjoin_repick = true;"
|
|
whilep->addStmtsp(new AstIf{
|
|
fl,
|
|
new AstGt{
|
|
fl, new AstRand{fl, AstRand::SRandomU32{}},
|
|
new AstRToIS{fl, new AstMulD{fl,
|
|
new AstConst{fl, AstConst::RealDouble{},
|
|
static_cast<double>(0xffffffffUL)},
|
|
weightp}}},
|
|
new AstAssign{fl, new AstVarRef{fl, repickVarp, VAccess::WRITE},
|
|
new AstConst{fl, AstConst::BitTrue{}}}});
|
|
|
|
// "if (_Vjoin_repick) {"
|
|
AstIf* ifp = new AstIf{fl, new AstVarRef{fl, repickVarp, VAccess::READ}};
|
|
whilep->addStmtsp(ifp);
|
|
{
|
|
// "// _Vjoin_nlists_eligible is how many lists eligable for picking"
|
|
// "int _Vjoin_nlists_eligible = 0;"
|
|
AstVar* const eligibleVarp = new AstVar{
|
|
fl, VVarType::VAR, "_Vjoin_nlists_eligible", VFlagBitPacked{}, 32};
|
|
eligibleVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
|
eligibleVarp->funcLocal(true);
|
|
m_breakVarp->addNextHere(eligibleVarp); // Add to function top, not newp
|
|
ifp->addThensp(new AstAssign{fl, new AstVarRef{fl, eligibleVarp, VAccess::WRITE},
|
|
new AstConst{fl, AstConst::WidthedValue{}, 32, 0}});
|
|
|
|
i = 0;
|
|
for (AstRSProdItem* proditemp : lists) {
|
|
(void)proditemp;
|
|
// "_Vjoin_nlists_eligible += _Vjoin_nleft_0 ? 1 : 0;"
|
|
ifp->addThensp(new AstAssign{
|
|
fl, new AstVarRef{fl, eligibleVarp, VAccess::WRITE},
|
|
new AstAdd{
|
|
fl, new AstVarRef{fl, eligibleVarp, VAccess::READ},
|
|
new AstCond{
|
|
fl,
|
|
new AstNeq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 0},
|
|
new AstVarRef{fl, nleftVarps[i], VAccess::READ}},
|
|
new AstConst{fl, AstConst::WidthedValue{}, 32, 1},
|
|
new AstConst{fl, AstConst::WidthedValue{}, 32, 0}}}});
|
|
++i;
|
|
}
|
|
|
|
// "if (!_Vjoin_nlists_eligible) break;" // Out of elements
|
|
ifp->addThensp(
|
|
new AstIf{fl,
|
|
new AstEq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 0},
|
|
new AstVarRef{fl, eligibleVarp, VAccess::READ}},
|
|
new AstJumpGo{fl, m_jumpBlockp}});
|
|
|
|
// "// +1 to simplify usage, so 0 = done"
|
|
// "_Vjoin_picked_elist = 1 + (std::rand() % _Vjoin_nlists_eligible);"
|
|
ifp->addThensp(new AstAssign{
|
|
fl, new AstVarRef{fl, pickedVarp, VAccess::WRITE},
|
|
new AstAdd{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 1},
|
|
new AstModDiv{fl, new AstRand{fl, AstRand::SRandomU32{}},
|
|
new AstVarRef{fl, eligibleVarp, VAccess::READ}}}});
|
|
|
|
// "_Vjoin_repick = false;
|
|
ifp->addThensp(new AstAssign{fl, new AstVarRef{fl, repickVarp, VAccess::WRITE},
|
|
new AstConst{fl, AstConst::BitFalse{}}});
|
|
}
|
|
|
|
// "// Number of eligible lists left before hit _Vjoin_pciked one"
|
|
// "int _Vjoin_sel_elist = _Vjoin_picked_elist;"
|
|
AstVar* const selVarp
|
|
= new AstVar{fl, VVarType::VAR, "_Vjoin_sel_elist", VFlagBitPacked{}, 32};
|
|
selVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
|
selVarp->funcLocal(true);
|
|
m_breakVarp->addNextHere(selVarp); // Add to function top, not newp
|
|
whilep->addStmtsp(new AstAssign{fl, new AstVarRef{fl, selVarp, VAccess::WRITE},
|
|
new AstVarRef{fl, pickedVarp, VAccess::READ}});
|
|
|
|
i = -1;
|
|
for (AstRSProdItem* proditemp : lists) {
|
|
++i;
|
|
// "if (_Vjoin_nleft_0) {"
|
|
ifp = new AstIf{fl,
|
|
new AstNeq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 0},
|
|
new AstVarRef{fl, nleftVarps[i], VAccess::READ}},
|
|
nullptr, nullptr};
|
|
whilep->addStmtsp(ifp);
|
|
{
|
|
// "--_Vjoin_sel_elist;"
|
|
ifp->addThensp(new AstAssign{
|
|
fl, new AstVarRef{fl, selVarp, VAccess::WRITE},
|
|
new AstSub{fl, new AstVarRef{fl, selVarp, VAccess::READ},
|
|
new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}});
|
|
|
|
// "if (_Vjoin_sel_elist == 0) {"
|
|
AstIf* const jIfp = new AstIf{
|
|
fl,
|
|
new AstEq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 0},
|
|
new AstVarRef{fl, selVarp, VAccess::READ}},
|
|
nullptr, nullptr};
|
|
ifp->addThensp(jIfp);
|
|
{
|
|
// "switch (_Vjoin_nleft_0) {"
|
|
// "case 2 / * N(a) * /: {statement}; break;"
|
|
// "case 1 / * N(a) - 1 * /: {statement}; break;"
|
|
uint32_t j = static_cast<uint32_t>(listStmts[proditemp].size());
|
|
for (AstNodeStmt* prodp : listStmts[proditemp]) {
|
|
jIfp->addThensp(new AstIf{
|
|
fl,
|
|
new AstEq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, j},
|
|
new AstVarRef{fl, nleftVarps[i], VAccess::READ}},
|
|
prodp->cloneTree(false)});
|
|
--j;
|
|
}
|
|
|
|
// "--_Vjoin_nleft_0;"
|
|
jIfp->addThensp(new AstAssign{
|
|
fl, new AstVarRef{fl, nleftVarps[i], VAccess::WRITE},
|
|
new AstSub{fl, new AstVarRef{fl, nleftVarps[i], VAccess::READ},
|
|
new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}});
|
|
|
|
// "if (0 == _Vjoin_nleft_0) _Vjoin_repick = true;"
|
|
jIfp->addThensp(new AstIf{
|
|
fl,
|
|
new AstEq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 0},
|
|
new AstVarRef{fl, nleftVarps[i], VAccess::READ}},
|
|
new AstAssign{fl, new AstVarRef{fl, repickVarp, VAccess::WRITE},
|
|
new AstConst{fl, AstConst::BitTrue{}}}});
|
|
|
|
// "continue;"
|
|
jIfp->addThensp(new AstJumpGo{fl, whilep});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UINFOTREE(9, newp, "ForkJoin new", "-");
|
|
return newp;
|
|
}
|
|
|
|
// VISITORS
|
|
|
|
void visit(AstNodeModule* nodep) override {
|
|
VL_RESTORER(m_modp);
|
|
m_modp = nodep;
|
|
iterateChildren(nodep);
|
|
}
|
|
|
|
void visit(AstRandSequence* nodep) override {
|
|
UINFO(9, "visit " << nodep);
|
|
UASSERT_OBJ(m_modp, nodep, "randsequence not under module");
|
|
if (m_rsp) {
|
|
// No examples found in the wild
|
|
nodep->v3warn(E_UNSUPPORTED, "Unsupported: randsequence under randsequence");
|
|
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
|
return;
|
|
}
|
|
|
|
UINFOTREE(9, nodep, "rsIn", "");
|
|
VL_RESTORER(m_rsp);
|
|
m_rsp = nodep;
|
|
VL_RESTORER(m_startProdp);
|
|
|
|
VL_RESTORER(m_localizes);
|
|
VL_RESTORER(m_localizeNames);
|
|
VL_RESTORER(m_localizeRemaps);
|
|
findLocalizes(nodep);
|
|
|
|
// Find first production
|
|
m_startProdp = nodep->prodsp();
|
|
if (nodep->prodp()) m_startProdp = nodep->prodp(); // Has a start production selected
|
|
|
|
// Build production functions
|
|
for (AstRSProd* prodp = nodep->prodsp(); prodp; prodp = VN_AS(prodp->nextp(), RSProd)) {
|
|
newProdFunc(prodp);
|
|
// Don't iterate yet, need to have tasks we can reference
|
|
}
|
|
|
|
// Iterate all of the new productions, moving guts into functions just made
|
|
// Must do those with 'rand join' first, as they need to see the raw AstRS* nodes
|
|
// before they are removed. (Alternative would be to keep them until the end).
|
|
UINFOTREE(9, nodep, "RS Tree pre-it", "-");
|
|
std::unordered_set<AstRSProd*> prodHasRandJoin;
|
|
for (AstRSProd* prodp = nodep->prodsp(); prodp; prodp = VN_AS(prodp->nextp(), RSProd)) {
|
|
prodp->foreach([&](AstRSProdList* const prodlistp) {
|
|
if (prodlistp->randJoin()) prodHasRandJoin.emplace(prodp);
|
|
});
|
|
}
|
|
for (AstRSProd *nextp, *prodp = nodep->prodsp(); prodp; prodp = nextp) {
|
|
nextp = VN_AS(prodp->nextp(), RSProd);
|
|
if (prodHasRandJoin.count(prodp)) VL_DO_DANGLING(iterate(prodp), prodp);
|
|
}
|
|
for (AstRSProd *nextp, *prodp = nodep->prodsp(); prodp; prodp = nextp) {
|
|
nextp = VN_AS(prodp->nextp(), RSProd);
|
|
if (!prodHasRandJoin.count(prodp)) VL_DO_DANGLING(iterate(prodp), prodp);
|
|
}
|
|
UINFOTREE(9, nodep, "RS Tree post-it", "-");
|
|
|
|
// Replace randsequence with call to randsequence start function
|
|
AstTask* const startFuncp = newStartFunc(nodep);
|
|
AstArg* argsp = nullptr;
|
|
for (auto& itr : m_localizeNames) {
|
|
AstVar* const lvarp = itr.second;
|
|
argsp = AstNode::addNext(
|
|
argsp, new AstArg{nodep->fileline(), "__Vrsarg_" + lvarp->name(),
|
|
new AstVarRef{nodep->fileline(), lvarp, VAccess::READWRITE}});
|
|
}
|
|
AstTaskRef* const callStartp
|
|
= new AstTaskRef{nodep->fileline(), startFuncp->name(), argsp};
|
|
callStartp->taskp(startFuncp);
|
|
nodep->replaceWith(new AstStmtExpr{nodep->fileline(), callStartp});
|
|
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
|
}
|
|
void visit(AstRSProd* nodep) override {
|
|
// Production, immediately under a randsequence
|
|
UINFO(9, "visit " << nodep);
|
|
UINFOTREE(9, nodep, "visit " << nodep, "-");
|
|
UASSERT_OBJ(!m_prodFuncp, nodep, "recursive production definition?");
|
|
VL_RESTORER(m_prodFuncp);
|
|
auto it = m_prodFuncps.find(nodep);
|
|
UASSERT_OBJ(it != m_prodFuncps.end(), nodep, "No production function made");
|
|
m_prodFuncp = it->second;
|
|
|
|
// Create a break variable
|
|
// TODO we could do this only if break exists in the downstream production,
|
|
// but as-is we'll optimize it away in most cases anyways
|
|
VL_RESTORER(m_breakVarp);
|
|
VL_RESTORER(m_localizeRemaps);
|
|
m_breakVarp = newBreakVar(nodep->fileline(), true);
|
|
m_prodFuncp->addStmtsp(m_breakVarp);
|
|
|
|
// Put JumpBlock immediately under the new function to support
|
|
// a future break/return. V3Const will rip it out if unneeded.
|
|
VL_RESTORER(m_jumpBlockp);
|
|
m_jumpBlockp = new AstJumpBlock{nodep->fileline(), nullptr};
|
|
m_prodFuncp->addStmtsp(m_jumpBlockp);
|
|
|
|
if (nodep->fvarp())
|
|
nodep->fvarp()->v3warn(E_UNSUPPORTED,
|
|
"Unsupported: randsequence production function variable");
|
|
if (nodep->portsp())
|
|
nodep->portsp()->v3warn(E_UNSUPPORTED,
|
|
"Unsupported: randsequence production function ports");
|
|
|
|
// Move children into m_prodFuncp, and iterate there
|
|
if (!nodep->rulesp()) { // Nothing to do
|
|
} else if (nodep->rulesp()->prodlistsp() && nodep->rulesp()->prodlistsp()->randJoin()) {
|
|
AstRSProdList* const prodlistp = nodep->rulesp()->prodlistsp();
|
|
AstNode* const itemsp = newProdRandJoin(nodep, prodlistp);
|
|
m_jumpBlockp->addStmtsp(itemsp);
|
|
iterateAndNextNull(itemsp);
|
|
} else if (!nodep->rulesp()->nextp()) { // Single rule/list, can just do it
|
|
// RSPROD(RSRULE(weight, stmt)) -> IF(weight != 0, stmt)
|
|
AstRSRule* const rulep = nodep->rulesp();
|
|
AstNode* itemsp = nullptr;
|
|
if (rulep->weightStmtsp()) itemsp = rulep->weightStmtsp()->unlinkFrBackWithNext();
|
|
if (rulep->prodlistsp())
|
|
itemsp = AstNode::addNext(itemsp, rulep->prodlistsp()->unlinkFrBackWithNext());
|
|
// Can ignore rulep->weightp() unless is zero, in which case noop, add if
|
|
if (rulep->weightp())
|
|
itemsp = new AstIf{rulep->weightp()->fileline(),
|
|
new AstNeq{rulep->weightp()->fileline(),
|
|
new AstConst{rulep->weightp()->fileline(), 0},
|
|
rulep->weightp()->unlinkFrBack()},
|
|
itemsp};
|
|
// Move prodlist to parent (m_prodFunc) and process
|
|
if (itemsp) {
|
|
UINFOTREE(9, itemsp, "additems", "-");
|
|
m_jumpBlockp->addStmtsp(itemsp);
|
|
iterateAndNextNull(itemsp);
|
|
UINFOTREE(9, m_jumpBlockp, "jumpBlockNow", "-");
|
|
}
|
|
} else {
|
|
// List of rules with "OR", need to random-weight them
|
|
// RSPROD(RSRULE(weight1, stmt!), RSRULE(weight2, stmt2))
|
|
// -> RANDCASE(CASEITEM(weight1, stmt1), CASEITEM(weight2, stmt2))
|
|
AstRandCase* const randcasep = new AstRandCase{nodep->fileline(), nullptr};
|
|
for (AstRSRule* rulep = nodep->rulesp(); rulep;
|
|
rulep = VN_AS(rulep->nextp(), RSRule)) {
|
|
AstNode* itemsp = nullptr;
|
|
if (rulep->weightStmtsp()) itemsp = rulep->weightStmtsp()->unlinkFrBackWithNext();
|
|
if (rulep->prodlistsp())
|
|
itemsp = AstNode::addNext(itemsp, rulep->prodlistsp()->unlinkFrBackWithNext());
|
|
if (itemsp) {
|
|
AstNodeExpr* const weightp = rulep->weightp()
|
|
? rulep->weightp()->unlinkFrBack()
|
|
: new AstConst{rulep->fileline(), 1};
|
|
randcasep->addItemsp(new AstCaseItem{rulep->fileline(), weightp, itemsp});
|
|
}
|
|
}
|
|
m_jumpBlockp->addStmtsp(randcasep);
|
|
iterateAndNextNull(randcasep);
|
|
}
|
|
|
|
// V3Task needs to know if we ended up making a recursive function
|
|
m_prodFuncp->foreach([&](AstNodeFTaskRef* const nodep) {
|
|
if (nodep->taskp() == m_prodFuncp) m_prodFuncp->recursive(true);
|
|
});
|
|
UINFOTREE(9, m_prodFuncp, "rsprod-task-done " << nodep, "-");
|
|
|
|
// Done with production, should have moved everything to new task
|
|
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
|
}
|
|
void visit(AstRSRule* nodep) override {
|
|
// Rule of what to produce (as a list) under a production
|
|
nodep->v3fatalSrc("randsequence rule should be iterated and deleted by AstRSProd");
|
|
}
|
|
void visit(AstRSProdList* nodep) override {
|
|
// List of productions to call, potentially with a weight
|
|
UINFO(9, "visit " << nodep);
|
|
UASSERT_OBJ(m_prodFuncp, nodep, "RSProdList not under production");
|
|
|
|
// Move prodlist to parent (m_prodFunc) and process
|
|
if (AstNode* const itemsp = nodep->prodsp()) {
|
|
UASSERT_OBJ(!nodep->randJoin(), nodep,
|
|
"rand join should have been handled in visit(RSRule)");
|
|
nodep->replaceWith(itemsp->unlinkFrBackWithNext());
|
|
// Will soon iterate itemsp as part of normal visit process
|
|
// These will become sequential in the generated task
|
|
} else {
|
|
nodep->unlinkFrBack();
|
|
}
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
|
}
|
|
void visit(AstRSProdItem* nodep) override {
|
|
// "call" to generate using another production
|
|
UINFO(9, "visit " << nodep);
|
|
UASSERT_OBJ(m_prodFuncp, nodep, "RSProdItem not under production");
|
|
AstRSProd* const foundp = nodep->prodp();
|
|
UASSERT_OBJ(foundp, nodep, "Unlinked production reference");
|
|
// Convert to task call
|
|
AstNode* const newp = newProdFuncRef(nodep, foundp, m_breakVarp);
|
|
// The production might have done a "break;", skip other steps if so
|
|
newp->addNext(new AstIf{nodep->fileline(),
|
|
new AstVarRef{nodep->fileline(), m_breakVarp, VAccess::READ},
|
|
new AstJumpGo{nodep->fileline(), m_jumpBlockp}});
|
|
|
|
nodep->replaceWith(newp);
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
|
}
|
|
|
|
void visit(AstRSBreak* nodep) override {
|
|
UASSERT_OBJ(m_jumpBlockp, nodep, "RSBreak not under production jump block");
|
|
UASSERT_OBJ(m_breakVarp, nodep, "no break variable");
|
|
AstNodeStmt* const newp = new AstAssign{
|
|
nodep->fileline(), new AstVarRef{nodep->fileline(), m_breakVarp, VAccess::WRITE},
|
|
new AstConst{nodep->fileline(), AstConst::BitTrue{}}};
|
|
newp->addNext(new AstJumpGo{nodep->fileline(), m_jumpBlockp});
|
|
nodep->replaceWith(newp);
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
|
}
|
|
void visit(AstRSReturn* nodep) override {
|
|
UASSERT_OBJ(m_jumpBlockp, nodep, "RSReturn not under production jump block");
|
|
nodep->replaceWith(new AstJumpGo{nodep->fileline(), m_jumpBlockp});
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
|
}
|
|
void visit(AstVarRef* nodep) override {
|
|
// Change references to variables inside randsequence to references to
|
|
// ref variables on the new task
|
|
const auto it = m_localizeRemaps.find(nodep->varp());
|
|
if (it != m_localizeRemaps.end()) {
|
|
AstVar* const iovarp = it->second;
|
|
UINFO(9, "VARREF remap " << nodep << " -> " << iovarp);
|
|
nodep->varp(iovarp);
|
|
}
|
|
}
|
|
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit RandSequenceVisitor(AstNetlist* nodep) { iterate(nodep); }
|
|
~RandSequenceVisitor() override = default;
|
|
};
|
|
|
|
//######################################################################
|
|
// RandSequence method class functions
|
|
|
|
void V3RandSequence::randSequenceNetlist(AstNetlist* nodep) {
|
|
UINFO(2, __FUNCTION__ << ":");
|
|
{ RandSequenceVisitor randSequenceVisitor{nodep}; }
|
|
V3Global::dumpCheckGlobalTree("randsequence", 0, dumpTreeEitherLevel() >= 3);
|
|
}
|