From 20b59f381dfc8948d33d88b296e4f1881f3adce5 Mon Sep 17 00:00:00 2001 From: Yutetsu TAKATSUKASA Date: Tue, 31 Dec 2019 17:50:03 +0900 Subject: [PATCH] split_var supports packed data. --- bin/verilator | 27 +++- src/V3SplitVar.cpp | 371 +++++++++++++++++++++++++++++++++++++++++---- src/V3SplitVar.h | 3 + src/Verilator.cpp | 4 + 4 files changed, 371 insertions(+), 34 deletions(-) diff --git a/bin/verilator b/bin/verilator index fc64ad870..75e5ba1d4 100755 --- a/bin/verilator +++ b/bin/verilator @@ -3237,19 +3237,34 @@ behavior. See the test_regress/t/t_dpi_display.v file for an example. =item /*verilator split_var*/ Attached to a variable or a net declaration. It breaks the variable into -multiple pieces. Only 1D unpacked array can be split at this moment. +multiple pieces. The following data types are supported. + + Packed array and packed struct consist of Integer types (reg, logic, bit, byte, int...) + 1D unpacked array of data type above Verilator will internally convert a variable with the metacomment such as: - logic [7:0] x [0:1]; /*verilator split_var*/ + logic [7:0] x [0:1]; /*verilator split_var*/ -to scalar variables below. +to packed variables below. - logic [7:0] x__BRA__0__KET__; - logic [7:0] x__BRA__1__KET__; + logic [7:0] x__BRA__0__KET__; /*verilator split_var*/ + logic [7:0] x__BRA__1__KET__; /*verilator split_var*/ + +Note that the generated packed variables still have split_var metacomment because +they will be split into further smaller pieces accorting to each access pattern. + +Packed variable will be split as the following example. + + logic [7:0] y; /*verilator split_var*/ + +to: + + logic [1:0] y__BRA__7_6__KET__; + logic [5:0] y__BRA__5_0__KET__; This operation helps optimization step to identify the dependencies -among variables and may resolve resolve UNOPTFLAT warning. +among variables and may resolve UNOPTFLAT warning. If the warning is resolved, simulation speed gets improved. Please also see the explanation of UNOPTFLAT below. diff --git a/src/V3SplitVar.cpp b/src/V3SplitVar.cpp index dc726cb16..0fc2345dd 100644 --- a/src/V3SplitVar.cpp +++ b/src/V3SplitVar.cpp @@ -37,6 +37,15 @@ // unpacked_array_var[2] = unpacked_array_var[1]; // unpacked_array_var[3] = unpacked_array_var[2]; // end +// logic [3:0] packed_var; /*verilator split_var*/ +// always_comb begin +// if (some_cond) begin +// packed_var = 4'b0; +// end else begin +// packed_var[3] = some_input0; +// packed_var[2:0] = some_input1; +// end +// end // // // is converted to @@ -51,12 +60,21 @@ // unpacked_array_var2 = unpacked_array_var1; // unpacked_array_var3 = unpacked_array_var2; // end +// logic packed_var__BRA__3__KET__; +// logic [2:0] packed_var__BRA__2_0__KET__; +// always_comb begin +// if (some_cond) begin +// {packed_var__BRA__3__KET__, packed_var__BRA__2_0__KET__} = 4'b0; +// end else begin +// packed_var__BRA__3__KET__ = some_input0; +// packed_var__BRA__2_0__KET__ = some_input1; +// end +// end // // // // Limitations: (planned to be resolved) -// - Only 1D unpacked array can be split -// - Splitting 2) - 5) will be supported later +// - Dimension of an unpacked array must be 1 or 0. // //************************************************************************* @@ -69,12 +87,24 @@ #include "V3SplitVar.h" #include "V3Stats.h" +#include // sort #include #include VL_INCLUDE_UNORDERED_MAP #include VL_INCLUDE_UNORDERED_SET +static AstConst* constifyIfNot(AstNode* nodep) { + AstConst* constp = VN_CAST(nodep, Const); + if (!constp) { + UINFO(4, nodep << " is expected to be constant, but not\n"); + AstNode* const constified = V3Const::constifyEdit(nodep); + UINFO(4, "After constified:" << constified << '\n'); + constp = VN_CAST(constified, Const); + } + return constp; +} + //###################################################################### -// +// Split Unpacked Variables class SplitUnpackedVarVisitor : public AstNVisitor { AstNodeModule* m_modp; // current module @@ -83,16 +113,6 @@ class SplitUnpackedVarVisitor : public AstNVisitor { // key:variable to be split. value:location where the variable is referenced. vl_unordered_map > m_refs; - static AstConst* constifyIfNot(AstNode* nodep) { - AstConst* constp = VN_CAST(nodep, Const); - if (!constp) { - UINFO(4, nodep << " is expected to be constant, but not\n"); - AstNode* const constified = V3Const::constifyEdit(nodep); - UINFO(4, "After constified:" << constified << '\n'); - constp = VN_CAST(constified, Const); - } - return constp; - } virtual void visit(AstNode* nodep) VL_OVERRIDE { iterateChildren(nodep); } virtual void visit(AstNodeModule* nodep) VL_OVERRIDE { UASSERT_OBJ(m_modp == NULL, m_modp, "Nested module declration"); @@ -105,20 +125,30 @@ class SplitUnpackedVarVisitor : public AstNVisitor { virtual void visit(AstVar* nodep) VL_OVERRIDE { m_lastVarp = nodep; } virtual void visit(AstPragma* pragmap) VL_OVERRIDE { if (pragmap->pragType() != AstPragmaType::SPLIT_VAR) return; // nothing to do + bool keepPragma = false; if (!m_lastVarp) { pragmap->v3warn(SPLITVAR, "Stray pragma of split_var is detected."); } else if (!canSplit(m_lastVarp)) { - pragmap->v3warn(SPLITVAR, - "Pragma split_var is specified on " - << m_lastVarp->prettyNameQ() - << " which type is not supported yet. " - "Only unpacked 1D array is supported by this version."); + // maybe packed variable which will be split later. + keepPragma = true; // SplitPackedVarVisitor will read this pragma again later. } else { // finally find a good candidate UINFO(4, m_lastVarp->name() << " is added to candidate list.\n"); m_refs.insert(std::make_pair(m_lastVarp, std::vector())); } - m_lastVarp = NULL; - pragmap->unlinkFrBack()->deleteTree(); VL_DANGLING(pragmap); // consume the pragma here anyway. + if (!keepPragma) { + m_lastVarp = NULL; + pragmap->unlinkFrBack()->deleteTree(); VL_DANGLING(pragmap); // consume the pragma here anyway. + } + } + virtual void visit(AstVarRef* nodep) VL_OVERRIDE { + AstVar* const varp = nodep->varp(); + if (m_refs.find(varp) == m_refs.end()) return; // variable without split_var pragma + + 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); } virtual void visit(AstArraySel* nodep) VL_OVERRIDE { AstVarRef* const vrefp = VN_CAST(nodep->fromp(), VarRef); @@ -138,18 +168,17 @@ class SplitUnpackedVarVisitor : public AstNVisitor { m_refs.erase(varp); } } - // The actual splitting operation is done in this function, so don't forget to call this function. - // The reason not doing things in dtor is to avoid throwing an exception in dtor. + // The actual splitting operation is done in this function. void split() { for (vl_unordered_map >::iterator it = m_refs.begin(), it_end = m_refs.end(); it != it_end; ++it) { UINFO(3, "In module " << m_modp->name() << " var " << it->first->prettyNameQ() << " which has " - << it->second.size() << " refs" - << " will be split.\n"); + << it->second.size() << " refs will be split.\n"); AstVar* varp = it->first; - AstUnpackArrayDType* const dtypep = VN_CAST(varp->subDTypep(), UnpackArrayDType); + AstNode* insertp = varp; + AstUnpackArrayDType* const dtypep = VN_CAST(varp->dtypep(), UnpackArrayDType); std::vector vars; // Add the split variables AstConst* const lsbp = constifyIfNot(dtypep->rangep()->lsbp()); @@ -161,7 +190,8 @@ class SplitUnpackedVarVisitor : public AstNVisitor { for (vlsint32_t i = 0; i <= msb - lsb; ++i) { const std::string name = varp->name() + "__BRA__" + cvtToStr(i) + "__KET__"; AstVar* const newp = new AstVar(varp->fileline(), varp->varType(), name, dtypep->subDTypep()); - m_modp->addStmtp(newp); + insertp->addNextHere(newp); + newp->addNextHere(insertp = new AstPragma(varp->fileline(), AstPragmaType::SPLIT_VAR)); vars.push_back(newp); } @@ -210,7 +240,284 @@ public: } }; +//###################################################################### +// Split packed variables +// Split variable +class SplitNewVar { + int m_lsb; // lsb in the original bitvector + int m_bitwidth; + AstVar* m_varp; // the LSB of this variable is always 0, not m_lsb +public: + SplitNewVar(int lsb, int bitwidth, AstVar* varp = NULL) + : m_lsb(lsb) + , m_bitwidth(bitwidth) + , m_varp(varp) {} + int lsb() const { return m_lsb; } + int msb() const { return m_lsb + m_bitwidth - 1; } + int bitwidth() const { return m_bitwidth; } + void varp(AstVar* vp) { + UASSERT_OBJ(!m_varp, m_varp, "must be NULL"); + m_varp = vp; + } + AstVar* varp() const { return m_varp; } + + struct Match { + bool operator()(int bit, const SplitNewVar& a) const { + return bit < a.m_lsb + a.m_bitwidth; + } + }; +}; + +// How a variable is used +class PackedVarRef { +public: + // one Entry instance for an AstVarRef instance + class Entry { + AstNode* m_nodep; // either AstSel or AstVarRef is expected. + int m_lsb; + int m_bitwidth; + + public: + Entry(AstSel* selp, int lsb, int bitwidth) + : m_nodep(selp) + , m_lsb(lsb) + , m_bitwidth(bitwidth) {} + Entry(AstVarRef* refp, int lsb, int bitwidth) + : m_nodep(refp) + , m_lsb(lsb) + , m_bitwidth(bitwidth) {} + AstNode* nodep() const { return m_nodep; } + int lsb() const { return m_lsb; } + int msb() const { return m_lsb + m_bitwidth - 1; } + int bitwidth() const { return m_bitwidth; } + void replaceNodeWith(AstNode* nodep) { + m_nodep->replaceWith(nodep); + m_nodep->deleteTree(); + VL_DANGLING(m_nodep); + } + }; +private: + struct SortByFirst { + bool operator()(const std::pair& a, const std::pair& b) const { + if (a.first == b.first) return a.second < b.second; + return a.first < b.first; + } + }; + std::vector m_lhs, m_rhs; + +public: + typedef std::vector::iterator iterator; + typedef std::vector::const_iterator const_iterator; + std::vector& lhs() { return m_lhs; } + std::vector& rhs() { return m_rhs; } + void append(const Entry& e, bool lvalue) { + if (lvalue) + m_lhs.push_back(e); + else + m_rhs.push_back(e); + } + // make a plan for variables after split + std::vector splitPlan() const { + std::vector plan; + + std::vector > points; // + points.reserve(m_lhs.size() * 2); // 2 points will be added per one Entry + for (const_iterator it = m_lhs.begin(), itend = m_lhs.end(); it != itend; ++it) { + points.push_back(std::make_pair(it->lsb(), false)); // start of a region + points.push_back(std::make_pair(it->msb() + 1, true)); // end of a region + } + std::sort(points.begin(), points.end(), SortByFirst()); + + // scan the sorted points and sub bitfields + int refcount = 0; + for (size_t i = 0; i + 1 < points.size(); ++i) { + const int bitwidth = points[i + 1].first - points[i].first; + if (points[i].second) + --refcount; // end of a region + else + ++refcount; // start of a region + UASSERT(refcount >= 0, "refcounut must not be negative"); + if (bitwidth == 0 || refcount == 0) continue; // vacant region + plan.push_back(SplitNewVar(points[i].first, bitwidth)); + } + + return plan; + } +}; + +class SplitPackedVarVisitor : public AstNVisitor { + AstNetlist* m_netp; + AstNodeModule* m_modp; // current module (just for log) + AstVar* m_lastVarp; // the most recently declared variable + bool m_isLhs; // true when traversing LHS of assignment + // key:variable to be split. value:location where the variable is referenced. + vl_unordered_map m_refs; + virtual void visit(AstNode* nodep) VL_OVERRIDE { iterateChildren(nodep); } + virtual void visit(AstAssign* nodep) VL_OVERRIDE { + UASSERT_OBJ(m_isLhs == false, nodep, "unexpected nested assign"); + m_isLhs = true; + iterate(nodep->lhsp()); + m_isLhs = false; + iterate(nodep->rhsp()); + } + virtual void visit(AstAssignW* nodep) VL_OVERRIDE { + UASSERT_OBJ(m_isLhs == false, nodep, "unexpected nested assign"); + m_isLhs = true; + iterate(nodep->lhsp()); + m_isLhs = false; + iterate(nodep->rhsp()); + } + virtual void visit(AstNodeModule* nodep) VL_OVERRIDE { + UASSERT_OBJ(m_modp == NULL, m_modp, "Nested module declration"); + m_modp = nodep; + m_lastVarp = NULL; + UINFO(3, "Start analyzing module " << nodep->prettyName() << '\n'); + iterateChildren(nodep); + split(); + m_modp = NULL; + } + virtual void visit(AstVar* nodep) VL_OVERRIDE { m_lastVarp = nodep; } + virtual void visit(AstPragma* pragmap) VL_OVERRIDE { + if (pragmap->pragType() != AstPragmaType::SPLIT_VAR) return; // nothing to do + UASSERT_OBJ(m_lastVarp, pragmap, + "Stray pragma must have been consumed in SplitUnpackedVarVisitor"); + if (!canSplit(m_lastVarp)) { + pragmap->v3warn(SPLITVAR, + "Pragma split_var is specified on a variable whose type is not supported. " + "Unpacked dimension must be 1D or none " + "and packed portion must be an aggregate type of bit or logic."); + } else { // finally find a good candidate + UINFO(3, m_lastVarp->prettyNameQ() << " is added to candidate list.\n"); + m_refs.insert(std::make_pair(m_lastVarp, PackedVarRef())); + } + m_lastVarp = NULL; + pragmap->unlinkFrBack()->deleteTree(); // consume the pragma here anyway. + } + virtual void visit(AstVarRef* nodep) VL_OVERRIDE { + AstVar* const varp = nodep->varp(); + if (m_refs.find(varp) == m_refs.end()) return; // variable without split_var pragma + PackedVarRef& refs = m_refs[varp]; + UASSERT_OBJ(nodep->lvalue() == m_isLhs, nodep, + (m_isLhs ? 'l' : 'r') << "value is expected"); + refs.append(PackedVarRef::Entry(nodep, 0, varp->width()), m_isLhs); + } + virtual void visit(AstSel* nodep) VL_OVERRIDE { + AstVarRef* const vrefp = VN_CAST(nodep->fromp(), VarRef); + if (!vrefp) return; + + AstVar* const varp = vrefp->varp(); + if (m_refs.find(varp) == m_refs.end()) return; // variable without split_var pragma + + AstConst* const consts[2] = {constifyIfNot(nodep->lsbp()), constifyIfNot(nodep->widthp())}; + + if (consts[0] && consts[1]) { // OK + PackedVarRef& refs = m_refs[varp]; + refs.append(PackedVarRef::Entry(nodep, consts[0]->toSInt(), consts[1]->toUInt()), m_isLhs); + } else { + nodep->v3warn(SPLITVAR, + "Variable " << vrefp->prettyNameQ() << " will not be split" + " because bit range cannot be determined statically."); + m_refs.erase(varp); + } + } + // extract necessary bit range from a newly created variable to meet ref + static AstNode* extractBits(const PackedVarRef::Entry& ref, const SplitNewVar& var, bool lvalue) { + AstVarRef* const refp = new AstVarRef(ref.nodep()->fileline(), var.varp(), lvalue); + if (ref.lsb() <= var.lsb() && var.msb() <= ref.msb()) { // use the entire bits + return refp; + } else { // use slice + const int lsb = std::max(ref.lsb(), var.lsb()); + const int msb = std::min(ref.msb(), var.msb()); + const int bitwidth = msb + 1 - lsb; + UINFO(4, var.varp()->prettyNameQ() << "[" << msb << ":" << lsb << "] used for " + << ref.nodep()->prettyNameQ() << '\n'); + // LSB of varp is always 0. "lsb - var.lsb()" means this. see also SplitNewVar + AstSel* const selp = new AstSel(ref.nodep()->fileline(), refp, lsb - var.lsb(), bitwidth); + return selp; + } + } + // The actual splitting operation is done in this function. + void split() { + for (vl_unordered_map::iterator it = m_refs.begin(), + it_end = m_refs.end(); + it != it_end; ++it) { + AstVar* const varp = it->first; + UINFO(3, "In module " << m_modp->name() << " var " << varp->prettyNameQ() + << " which has " << it->second.lhs().size() << " lhs refs and " + << it->second.rhs().size() << " rhs refs will be split.\n"); + typedef std::vector NewVars; // sorted by its lsb + NewVars vars = it->second.splitPlan(); + // Add the split variables + for (size_t i = 0; i < vars.size(); ++i) { + const int lsb = vars[i].lsb(); + const int msb = vars[i].msb(); + const std::string name = varp->name() + "__BRA__" + cvtToStr(msb) + '_' + cvtToStr(lsb) + "__KET__"; + + AstBasicDType* dtypep; + switch (varp->subDTypep()->basicp()->keyword().m_e) { + case AstBasicDTypeKwd::BIT: + dtypep = new AstBasicDType(varp->subDTypep()->fileline(), VFlagBitPacked(), msb - lsb + 1); + break; + case AstBasicDTypeKwd::LOGIC: + dtypep = new AstBasicDType(varp->subDTypep()->fileline(), VFlagLogicPacked(), msb - lsb + 1); + break; + default: + UASSERT_OBJ(false, varp->subDTypep()->basicp(), "Only bit and logic are allowed"); + } + vars[i].varp(new AstVar(varp->fileline(), varp->varType(), name, dtypep)); + m_netp->typeTablep()->addTypesp(dtypep); + varp->addNextHere(vars[i].varp()); + UINFO(4, vars[i].varp()->prettyNameQ() << " is added for " << varp->prettyNameQ() << '\n'); + } + + for (int lvalue = 0; lvalue <= 1; ++lvalue) { // refer the new split variables + std::vector& refs = lvalue ? it->second.lhs() : it->second.rhs(); + for (PackedVarRef::iterator refit = refs.begin(), refitend = refs.end(); + refit != refitend; ++refit) { + NewVars::const_iterator varit = std::upper_bound(vars.begin(), vars.end(), refit->lsb(), + SplitNewVar::Match()); + UASSERT_OBJ(varit != vars.end(), refit->nodep(), "Not found"); + UASSERT(!(varit->msb() < refit->lsb() || refit->msb() < varit->lsb()), "wrong search result"); + AstNode* prev = extractBits(*refit, *varit, lvalue); + for (int residue = refit->msb() - varit->msb(); residue > 0; residue -= varit->bitwidth()) { + ++varit; + UASSERT_OBJ(varit != vars.end(), refit->nodep(), "not enough split variables"); + AstNode* const bitsp = extractBits(*refit, *varit, lvalue); + prev = new AstConcat(refit->nodep()->fileline(), bitsp, prev); + } + refit->replaceNodeWith(prev); + UASSERT_OBJ(varit->msb() >= refit->msb(), varit->varp(), "Out of range"); + } + } + } + m_refs.clear(); // done + } + +public: + explicit SplitPackedVarVisitor(AstNetlist* nodep) + : m_netp(nodep) + , m_modp(NULL) + , m_lastVarp(NULL) + , m_isLhs(false) { + iterate(nodep); + } + ~SplitPackedVarVisitor() { UASSERT(m_refs.empty(), "Don't forget to call split()"); } + + // Check if the passed variable can be split. + // Even if this function returns true, the variable may not be split + // when the access to the variable cannot be determined statically. + static bool canSplit(const AstVar* nodep) { + if (AstBasicDType* const basicp = nodep->dtypep()->basicp()) { + // floating point, string are not supported + const std::pair dim = nodep->dtypep()->dimensions(false); + // 1D unpacked array will be split in SplitUnpackedVarVisitor() beforehand. + return dim.second <= 1 && basicp->isBitLogic() && !nodep->isSigPublic() && !nodep->isTrace(); + } + return false; + } + VL_DEBUG_FUNC; // Declare debug() +}; //###################################################################### // Split class functions @@ -222,6 +529,14 @@ void V3SplitVar::splitUnpackedVariable(AstNetlist* nodep) { V3Global::dumpCheckGlobalTree("split_var", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); } -bool V3SplitVar::canSplitVar(const AstVar* varp) { - return SplitUnpackedVarVisitor::canSplit(varp); +void V3SplitVar::splitPackedVariable(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ": " << endl); + { + SplitPackedVarVisitor visitor(nodep); + } // Destruct before checking + V3Global::dumpCheckGlobalTree("split_var", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); +} + +bool V3SplitVar::canSplitVar(const AstVar* varp) { + return SplitUnpackedVarVisitor::canSplit(varp) || SplitPackedVarVisitor::canSplit(varp); } diff --git a/src/V3SplitVar.h b/src/V3SplitVar.h index bd8daaf4e..0e45d8bea 100644 --- a/src/V3SplitVar.h +++ b/src/V3SplitVar.h @@ -31,6 +31,9 @@ public: // split variables marked with split_var pragma. static void splitUnpackedVariable(AstNetlist* nodep); + // split variables marked with split_var pragma. + static void splitPackedVariable(AstNetlist* nodep); + // returns true if the variable can be split. // This check is not perfect. static bool canSplitVar(const AstVar* varp); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 653b46627..af197d492 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -239,6 +239,10 @@ void process() { // Propagate constants into expressions V3Const::constifyAllLint(v3Global.rootp()); + // Split packed variables into multiple pieces to resolve UNOPTFLAT. + // should be after constifyAllLint() which flattens to 1D bit vector + V3SplitVar::splitPackedVariable(v3Global.rootp()); + if (!v3Global.opt.xmlOnly()) { // Remove cell arrays (must be between V3Width and scoping) V3Inst::dearrayAll(v3Global.rootp());