split_var supports packed data.

This commit is contained in:
Yutetsu TAKATSUKASA 2019-12-31 17:50:03 +09:00
parent 3cbb72fd48
commit 20b59f381d
4 changed files with 371 additions and 34 deletions

View File

@ -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.

View File

@ -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
// </pre>
//
// 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
// </pre>
//
//
// 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 <algorithm> // sort
#include <vector>
#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<AstVar*, std::vector<AstArraySel*> > 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<AstArraySel*>()));
}
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<AstVar*, std::vector<AstArraySel*> >::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<AstVar*> 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<int, bool>& a, const std::pair<int, bool>& b) const {
if (a.first == b.first) return a.second < b.second;
return a.first < b.first;
}
};
std::vector<Entry> m_lhs, m_rhs;
public:
typedef std::vector<Entry>::iterator iterator;
typedef std::vector<Entry>::const_iterator const_iterator;
std::vector<Entry>& lhs() { return m_lhs; }
std::vector<Entry>& 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<SplitNewVar> splitPlan() const {
std::vector<SplitNewVar> plan;
std::vector<std::pair<int, bool> > points; // <bit location, is end>
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<AstVar*, PackedVarRef> 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<AstVar*, PackedVarRef>::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<SplitNewVar> 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<PackedVarRef::Entry>& 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<uint32_t, uint32_t> 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);
}

View File

@ -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);

View File

@ -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());