When split unpacked arrays, use SplitUnpackedVarVisitor just once

This commit is contained in:
Yutetsu TAKATSUKASA 2020-01-18 11:37:22 +09:00
parent 9dfaa9fb01
commit 74edc5cff4
5 changed files with 1082 additions and 1184 deletions

View File

@ -128,10 +128,17 @@ public:
class SplitUnpackedVarVisitor : public AstNVisitor {
AstNodeModule* m_modp; // current module
int m_numSplit; // total number of split variable
bool m_firstRun; // true for the first pass
int m_numSplit; // total number of split variable
// key:variable to be split. value:location where the variable is referenced.
vl_unordered_map<AstVar*, std::vector<AstArraySel*> > m_refs;
typedef vl_unordered_set<AstNodeAssign*> AssignSet;
AssignSet m_assigns; // at least LHS or RHS will be split
// used when traversing LHS and RHS of assignment. true if func/task is included
bool m_taskFuncFound;
// used when traversing LHS and RHS of assignment. added to this set.
typedef vl_unordered_set<AstVar*> TargetVarSet;
TargetVarSet m_foundTargetVar;
// This visitor is used before V3Const::constifyAllLint(),
// some parameters need to be resolved here, but don't abuse this function.
@ -152,8 +159,15 @@ class SplitUnpackedVarVisitor : public AstNVisitor {
}
virtual void visit(AstNode* nodep) VL_OVERRIDE { iterateChildren(nodep); }
virtual void visit(AstNodeFTaskRef* nodep) VL_OVERRIDE {
m_taskFuncFound = true;
iterateChildren(nodep);
}
virtual void visit(AstNodeModule* nodep) VL_OVERRIDE {
UASSERT_OBJ(m_modp == NULL, m_modp, "Nested module declration");
UASSERT_OBJ(m_refs.empty(), nodep, "The last module didn't finish split()");
m_modp = nodep;
std::vector<std::pair<AstPragma*, AstVar*> > vars = ScanPragmaVisitor::scan(nodep);
for (size_t i = 0; i < vars.size(); ++i) {
@ -173,35 +187,17 @@ class SplitUnpackedVarVisitor : public AstNVisitor {
VL_DO_DANGLING(vars[i].first->unlinkFrBack()->deleteTree(), vars[i].first);
}
}
if (!vars.empty()) { // need to check this module only when split_var pragma exists in this module.
if (!m_refs.empty()) { // need to check this module only when split_var pragma exists in this module.
iterateChildren(nodep);
split();
}
m_modp = NULL;
}
virtual void visit(AstVarRef* nodep) VL_OVERRIDE {
AstVar* varp = nodep->varp();
if (m_refs.find(varp) == m_refs.end()) return; // variable without split_var pragma
if (m_firstRun)
nodep->v3warn(
SPLITVAR,
"Variable " << varp->prettyNameQ()
<< " will not be split because the entire unpacked array is referred."
" Such access is not supported yet.\n");
m_refs.erase(varp);
}
static void splitSimpleAssign(AstNodeAssign* asnp, AstVarRef* lhsp, AstVarRef* rhsp,
int lstart, int rstart, int num) {
for (int i = 0; i < num; ++i) {
AstVarRef* lrefp = new AstVarRef(lhsp->fileline(), lhsp->varp(), true);
AstVarRef* rrefp = new AstVarRef(rhsp->fileline(), rhsp->varp(), false);
AstArraySel* lselp = new AstArraySel(lhsp->fileline(), lrefp, lstart + i);
AstArraySel* rselp = new AstArraySel(rhsp->fileline(), rrefp, rstart + i);
// the added new assignment statement will be visited later.
asnp->addNext(asnp->cloneType(lselp, rselp));
}
m_foundTargetVar.insert(varp);
}
// Unroll assignments of SliceSel or entire unpacked array to multiple assignment
@ -210,34 +206,34 @@ class SplitUnpackedVarVisitor : public AstNVisitor {
AstSliceSel* rsel = VN_CAST(nodep->rhsp(), SliceSel);
AstVarRef* lhsp = VN_CAST(lsel ? lsel->fromp() : nodep->lhsp(), VarRef);
AstVarRef* rhsp = VN_CAST(rsel ? rsel->fromp() : nodep->rhsp(), VarRef);
// unless simple assignment, nothing to do in this function
if (!lhsp || !rhsp) {
// at least either LHS or RHS must be VarRef or SliceSel to unroll this assignment
if (!lhsp && !rhsp) {
iterateChildren(nodep);
return;
}
m_taskFuncFound = false;
m_foundTargetVar.clear();
iterateChildren(nodep);
// if nodep is a simple assignment of variables without split_var pragma, quick exit
if (m_refs.find(lhsp->varp()) == m_refs.end() && m_refs.find(rhsp->varp()) == m_refs.end())
// If LHS or RHS includes function, this assign statement can not be split/unrolled
// because the function may have side effect.
// e.g. unpacked_array[some_func()] = rhs; or unpacked_array = some_func();
if (m_taskFuncFound) {
for (TargetVarSet::iterator it = m_foundTargetVar.begin(),
it_end = m_foundTargetVar.end();
it != it_end; ++it) {
(*it)->v3warn(
SPLITVAR,
"Variable " << (*it)->prettyNameQ()
<< " will not be split because it is used with function or task.");
m_refs.erase(*it);
}
m_foundTargetVar.clear();
return;
}
int lstart, lnum, rstart, rnum;
if (lsel) {
lstart = lsel->declRange().lo();
lnum = lsel->declRange().elements();
} else { // LHS is entire array
lstart = 0;
lnum = outerMostSizeOfUnpackedArray(lhsp->varp());
}
if (rsel) {
rstart = rsel->declRange().lo();
rnum = rsel->declRange().elements();
} else { // RHS is entire array
rstart = 0;
rnum = outerMostSizeOfUnpackedArray(rhsp->varp());
}
if (lnum != rnum) return; // strange. V3Slice will show proper diagnosis
splitSimpleAssign(nodep, lhsp, rhsp, lstart, rstart, lnum);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
// If an unpacked array with split_var pragma is found, this assignment needs to be split.
if (!m_foundTargetVar.empty()) m_assigns.insert(nodep);
}
virtual void visit(AstArraySel* nodep) VL_OVERRIDE {
@ -250,24 +246,87 @@ class SplitUnpackedVarVisitor : public AstNVisitor {
AstVar* varp = vrefp->varp();
if (m_refs.find(varp) == m_refs.end()) return; // variable without split_var pragma
// nodep->bitp() is sometimes AstSel consists of AstAdd/Sub and parameters.
// constify can solve it.
AstConst* indexp = constifyIfNot(nodep->bitp());
if (indexp) { // OK
m_refs[varp].push_back(nodep);
} else {
if (m_firstRun)
nodep->bitp()->v3warn(
SPLITVAR,
"Variable "
<< vrefp->prettyNameQ()
<< " will not be split because index cannot be determined statically.");
nodep->bitp()->v3warn(
SPLITVAR,
"Variable "
<< vrefp->prettyNameQ()
<< " will not be split because index cannot be determined statically.");
m_refs.erase(varp);
}
}
// The actual splitting operation is done in this function.
void split() {
for (vl_unordered_map<AstVar*, std::vector<AstArraySel*> >::iterator it = m_refs.begin(),
it_end = m_refs.end();
static AstArraySel* createArraySel(AstNode* fromp, int idx) {
if (AstSliceSel* selp = VN_CAST(fromp, SliceSel)) { // fuse
// if fromp is AstSliceSel, fuse ArraySel and AstSliceSel.
return new AstArraySel(selp->fileline(), selp->fromp()->cloneTree(false),
idx + selp->declRange().lo());
}
return new AstArraySel(fromp->fileline(), fromp->cloneTree(false), idx);
}
void splitSimpleAssign(AstNodeAssign* asnp, int lstart, int rstart, int num) {
AstNode* lhsp = asnp->lhsp();
AstNode* rhsp = asnp->rhsp();
for (int i = 0; i < num; ++i) {
AstArraySel* lselp = createArraySel(lhsp, lstart + i);
AstArraySel* rselp = createArraySel(rhsp, rstart + i);
AstNodeAssign* newAssignp = VN_CAST(asnp->cloneType(lselp, rselp), NodeAssign);
asnp->addNext(newAssignp);
// Add new ArraySels which may be made inside cloneTree()
iterateChildren(newAssignp);
}
}
void splitAssign(const AssignSet& assigns) {
for (AssignSet::const_iterator it = assigns.begin(), it_end = assigns.end(); it != it_end;
++it) {
AstNodeAssign* nodep = *it;
AstSliceSel* lsel = VN_CAST(nodep->lhsp(), SliceSel);
AstSliceSel* rsel = VN_CAST(nodep->rhsp(), SliceSel);
AstVarRef* lhsp = VN_CAST(lsel ? lsel->fromp() : nodep->lhsp(), VarRef);
AstVarRef* rhsp = VN_CAST(rsel ? rsel->fromp() : nodep->rhsp(), VarRef);
int lstart = 0, lnum = -1, rstart = 0, rnum = -1;
if (lsel) {
lstart = lsel->declRange().lo();
lnum = lsel->declRange().elements();
} else if (lhsp) { // LHS is entire array
lstart = 0;
lnum = outerMostSizeOfUnpackedArray(lhsp->varp());
}
if (rsel) {
rstart = rsel->declRange().lo();
rnum = rsel->declRange().elements();
} else if (rhsp) { // RHS is entire array
rstart = 0;
rnum = outerMostSizeOfUnpackedArray(rhsp->varp());
}
UASSERT_OBJ(lnum >= 0 || rnum >= 0, nodep,
"At least either side must be VarRef or SliceSel");
splitSimpleAssign(nodep, lstart, rstart, std::max(lnum, rnum));
// don't delete here because ArraySel linked form nodep may be in m_refs
// delete everything after replacement has done
nodep->unlinkFrBack();
}
}
int collapseUnpackedArray() {
AssignSet toBeDeleted;
m_assigns.swap(toBeDeleted); // now m_assigns is empty
splitAssign(toBeDeleted);
vl_unordered_map<AstVar*, std::vector<AstArraySel*> > curRefs;
m_refs.swap(curRefs);
// now m_refs is empty. split var will be added to m_refs for the next iteration.
int numSplit = 0;
for (vl_unordered_map<AstVar*, std::vector<AstArraySel*> >::iterator it = curRefs.begin(),
it_end = curRefs.end();
it != it_end; ++it) {
UINFO(3, "In module " << m_modp->name() << " var " << it->first->prettyNameQ()
<< " which has " << it->second.size()
@ -275,20 +334,27 @@ class SplitUnpackedVarVisitor : public AstNVisitor {
AstVar* varp = it->first;
AstNode* insertp = varp;
AstUnpackArrayDType* dtypep = VN_CAST(varp->dtypep(), UnpackArrayDType);
AstNodeDType* subTypep = dtypep->subDTypep();
const bool needNext = VN_IS(subTypep, UnpackArrayDType); // still unpacked array.
std::vector<AstVar*> vars;
std::vector<AstArraySel*> newSels;
// Add the split variables
for (vlsint32_t i = 0; i <= dtypep->msb() - dtypep->lsb(); ++i) {
// const std::string name = varp->name() + "__BRA__" + AstNode::encodeNumber(i + dtypep->lsb()) + "__KET__";
// unpacked array is traced as var(idx).
const std::string name = varp->name() + AstNode::encodeName('(' + cvtToStr(i + dtypep->lsb()) + ')');
AstVar* newp = new AstVar(varp->fileline(), varp->varType(), name, dtypep->subDTypep());
AstVar* newp = new AstVar(varp->fileline(), varp->varType(), name, subTypep);
newp->trace(varp->isTrace());
insertp->addNextHere(newp);
if (newp->width() == 1) { // no need to try splitting
insertp = newp;
} else {
} else if (!needNext) {
newp->addNextHere(insertp = new AstPragma(varp->fileline(), AstPragmaType::SPLIT_VAR));
}
if (needNext) {
m_refs.insert(std::make_pair(newp, std::vector<AstArraySel*>())); // split in the next round
UINFO(5, "In module " << m_modp->name() << " var " << newp->prettyNameQ() << " is added\n");
}
vars.push_back(newp);
}
@ -303,25 +369,49 @@ class SplitUnpackedVarVisitor : public AstNVisitor {
AstVarRef* new_vref = new AstVarRef(selp->fileline(), vars.at(idx), vrefp->lvalue());
selp->replaceWith(new_vref);
VL_DO_DANGLING(selp->deleteTree(), selp);
// it's safe to visit again because m_refs and m_assigns are already cleared.
if (needNext) {
UINFO(5, "In module " << m_modp->name() << " var " << new_vref->backp() << " is tracing\n");
iterate(new_vref->backp());
}
}
VL_DO_DANGLING(varp->unlinkFrBack()->deleteTree(), varp);
++m_numSplit;
++numSplit;
}
m_refs.clear(); // done
// it's time to delete all assigns
for (AssignSet::iterator it = toBeDeleted.begin(), it_end = toBeDeleted.end();
it != it_end; ++it) {
(*it)->deleteTree();
}
toBeDeleted.clear(); // instead of VL_DANGLING
return numSplit;
}
// The actual splitting operation is done in this function.
void split() {
int numSplit = -1;
do {
const int n = collapseUnpackedArray();
if (numSplit < 0) // update m_numSplit in the first iteration of this module
m_numSplit += n;
numSplit = n;
} while (numSplit > 0);
}
public:
SplitUnpackedVarVisitor(AstNetlist* nodep, bool firstRun)
explicit SplitUnpackedVarVisitor(AstNetlist* nodep)
: m_modp(NULL)
, m_numSplit(0)
, m_firstRun(firstRun) {
, m_refs()
, m_assigns()
, m_taskFuncFound(false)
, m_foundTargetVar() {
iterate(nodep);
}
~SplitUnpackedVarVisitor() {
UASSERT(m_refs.empty(), "Don't forget to call split()");
if (m_firstRun) V3Stats::addStat("SplitVar, Split unpacked arrays", m_numSplit);
V3Stats::addStat("SplitVar, Split unpacked arrays", m_numSplit);
}
int numSplit() const { return m_numSplit; }
VL_DEBUG_FUNC; // Declare debug()
// Check if the passed variable can be split.
@ -474,6 +564,7 @@ class SplitPackedVarVisitor : public AstNVisitor {
}
virtual void visit(AstNodeModule* nodep) VL_OVERRIDE {
UASSERT_OBJ(m_modp == NULL, m_modp, "Nested module declration");
UASSERT_OBJ(m_refs.empty(), nodep, "The last module didn't finish split()");
m_modp = nodep;
UINFO(3, "Start analyzing module " << nodep->prettyName() << '\n');
std::vector<std::pair<AstPragma*, AstVar*> > vars = ScanPragmaVisitor::scan(nodep);
@ -495,7 +586,7 @@ class SplitPackedVarVisitor : public AstNVisitor {
// consume the pragma here anyway.
VL_DO_DANGLING(pragmap->unlinkFrBack()->deleteTree(), vars[i].first);
}
if (!vars.empty()) { // need to check this module only when split_var pragma exists in this module.
if (!m_refs.empty()) { // need to check this module only when split_var pragma exists in this module.
iterateChildren(nodep);
split();
}
@ -529,8 +620,10 @@ class SplitPackedVarVisitor : public AstNVisitor {
<< vrefp->prettyNameQ()
<< " will not be split"
" because bit range cannot be determined statically.");
if(!consts[0]) UINFO(4, "LSB " << nodep->lsbp() << " is expected to be constant, but not\n");
if(!consts[1]) UINFO(4, "WIDTH " << nodep->widthp() << " is expected to be constant, but not\n");
if (!consts[0])
UINFO(4, "LSB " << nodep->lsbp() << " is expected to be constant, but not\n");
if (!consts[1])
UINFO(4, "WIDTH " << nodep->widthp() << " is expected to be constant, but not\n");
m_refs.erase(varp);
}
}
@ -671,15 +764,10 @@ public:
// Split class functions
void V3SplitVar::splitUnpackedVariable(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ": " << endl);
// SplitUnpackedVarVisitor collapses one-dimension per one scan. so repeat until nothing to do.
for (int trial = 0, done = 0; done == 0; ++trial) {
{
SplitUnpackedVarVisitor visitor(nodep, trial == 0);
UINFO(3, visitor.numSplit() << " variables are split in trial " << trial << '\n');
if (visitor.numSplit() == 0) done = 1; // nothing to do anymore
} // Destruct before checking
V3Global::dumpCheckGlobalTree("split_var", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 9);
{
SplitUnpackedVarVisitor visitor(nodep);
}
V3Global::dumpCheckGlobalTree("split_var", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 9);
}
void V3SplitVar::splitPackedVariable(AstNetlist* nodep) {

View File

@ -20,7 +20,7 @@ execute(
check_finished => 1,
);
file_grep($Self->{stats}, qr/SplitVar,\s+Split packed variables\s+(\d+)/i, 84);
file_grep($Self->{stats}, qr/SplitVar,\s+Split unpacked arrays\s+(\d+)/i, 13);
file_grep($Self->{stats}, qr/SplitVar,\s+Split packed variables\s+(\d+)/i, 156);
file_grep($Self->{stats}, qr/SplitVar,\s+Split unpacked arrays\s+(\d+)/i, 15);
ok(1);
1;

View File

@ -85,6 +85,13 @@ module barshift_2d_unpacked #(parameter depth = 2, localparam width = 2**depth)
reg [width-1:0]tmp8[depth+offset+3:offset-1][offset:offset+n-1]; /*verilator split_var*/
reg [width-1:0]tmp9[depth+offset+3:offset+3][offset:offset+n-1]; /*verilator split_var*/
reg [width-1:0]tmp10[depth+offset:offset][offset:offset+n-1]; /*verilator split_var*/
// because tmp11 is not split for testing mixture usage of split_var and no-spliv_ar,
// UNOPTFLAT appears, but it's fine.
/*verilator lint_off UNOPTFLAT*/
reg [width-1:0]tmp11[-1:1][depth+offset:offset][offset:offset+n-1];
/*verilator lint_on UNOPTFLAT*/
reg [width-1:0]tmp12[-1:0][depth+offset:offset][offset:offset+n-1]; /*verilator split_var*/
reg [width-1:0]tmp13[depth+offset:offset][offset:offset+n-1]; /*verilator split_var*/
generate
for(genvar i = 0; i < depth; ++i) begin
@ -112,7 +119,11 @@ module barshift_2d_unpacked #(parameter depth = 2, localparam width = 2**depth)
assign tmp8[depth+offset+1:offset+1] = tmp7;
assign tmp9 = tmp8[depth+offset+1:offset+1];
assign tmp10[depth+offset:offset] = tmp9[depth+offset+3:offset+3];
assign out = tmp10[depth+offset][offset];
assign tmp11[1] = tmp10;
assign tmp11[-1] = tmp11[1];
assign tmp11[0] = tmp11[-1];
assign tmp12 = tmp11[0:1];
assign out = tmp12[1][depth+offset][offset];
endmodule

File diff suppressed because it is too large Load Diff

View File

@ -22,8 +22,8 @@ execute(
);
vcd_identical("$Self->{obj_dir}/simx.vcd", $Self->{golden_filename});
file_grep($Self->{stats}, qr/SplitVar,\s+Split packed variables\s+(\d+)/i, 96);
file_grep($Self->{stats}, qr/SplitVar,\s+Split unpacked arrays\s+(\d+)/i, 13);
file_grep($Self->{stats}, qr/SplitVar,\s+Split packed variables\s+(\d+)/i, 180);
file_grep($Self->{stats}, qr/SplitVar,\s+Split unpacked arrays\s+(\d+)/i, 15);
ok(1);
1;