diff --git a/Changes b/Changes index e9cb910f9..d1b63d9dc 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,8 @@ Verilator 4.205 devel **Minor:** +* Merge const static data globally into a new constant pool (#3013). [Geza Lore] + Verilator 4.204 2021-06-12 diff --git a/bin/verilator b/bin/verilator index 61064a9b4..0b5f4b636 100755 --- a/bin/verilator +++ b/bin/verilator @@ -344,6 +344,7 @@ detailed descriptions of these arguments. --MMD Create .d dependency files --MP Create phony dependency targets --Mdir Name of output object directory + --no-merge-const-pool Disable merging of different types in const pool --mod-prefix Name to prepend to lower classes --no-clk Prevent marking specified signal as clock --no-decoration Disable comments and symbol decorations diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index d7eebe90d..0fa6c7ab5 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -633,6 +633,13 @@ Summary: The directory is created if it does not exist and the parent directories exist; otherwise manually create the Mdir before calling Verilator. +.. option:: --no-merge-const-pool + + Rarely needed. In order to minimize cache footprint, values of different + data type, that are yet emitted identically in C++ are merged in the + constant pool. This option disables this and causes every constant pool + entry with a distinct data type to be emitted separately. + .. option:: --mod-prefix Specifies the name to prepend to all lower level classes. Defaults to diff --git a/include/verilated_heavy.h b/include/verilated_heavy.h index 95622b4ad..c0b1d9938 100644 --- a/include/verilated_heavy.h +++ b/include/verilated_heavy.h @@ -89,19 +89,24 @@ public: }; //=================================================================== -/// Verilog wide unpacked bit container. +/// Verilog wide packed bit container. /// Similar to std::array, but lighter weight, only methods needed /// by Verilator, to help compile time. /// +/// A 'struct' as we want this to be an aggregate type that allows +/// static aggregate initialization. Consider data members private. +/// /// For example a Verilog "bit [94:0]" will become a VlWide<3> because 3*32 /// bits are needed to hold the 95 bits. The MSB (bit 96) must always be /// zero in memory, but during intermediate operations in the Verilated /// internals is unpredictable. -template class VlWide final { - EData m_storage[T_Words]; +template struct VlWide final { + // MEMBERS + // This should be the only data member, otherwise generated static initializers need updating + EData m_storage[T_Words]; // Contents of the packed array -public: + // CONSTRUCTORS // cppcheck-suppress uninitVar VlWide() = default; ~VlWide() = default; @@ -798,26 +803,21 @@ void VL_WRITEMEM_N(bool hex, int bits, const std::string& filename, } //=================================================================== -/// Verilog packed array container +/// Verilog unpacked array container /// For when a standard C++[] array is not sufficient, e.g. an /// array under a queue, or methods operating on the array. /// +/// A 'struct' as we want this to be an aggregate type that allows +/// static aggregate initialization. Consider data members private. +/// /// This class may get exposed to a Verilated Model's top I/O, if the top /// IO has an unpacked array. -template class VlUnpacked final { -private: - // TYPES - using Array = std::array; - -public: - using const_iterator = typename Array::const_iterator; - -private: +template struct VlUnpacked final { // MEMBERS - Array m_array; // Contents of the packed array + // This should be the only data member, otherwise generated static initializers need updating + T_Value m_storage[T_Depth]; // Contents of the unpacked array -public: // CONSTRUCTORS VlUnpacked() = default; ~VlUnpacked() = default; @@ -828,18 +828,18 @@ public: // METHODS // Raw access - WData* data() { return &m_array[0]; } - const WData* data() const { return &m_array[0]; } + WData* data() { return &m_storage[0]; } + const WData* data() const { return &m_storage[0]; } - T_Value& operator[](size_t index) { return m_array[index]; }; - const T_Value& operator[](size_t index) const { return m_array[index]; }; + T_Value& operator[](size_t index) { return m_storage[index]; }; + const T_Value& operator[](size_t index) const { return m_storage[index]; }; // Dumping. Verilog: str = $sformatf("%p", assoc) std::string to_string() const { std::string out = "'{"; std::string comma; for (int i = 0; i < T_Depth; ++i) { - out += comma + VL_TO_STRING(m_array[i]); + out += comma + VL_TO_STRING(m_storage[i]); comma = ", "; } return out + "} "; diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 5fc473131..9618840c6 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -180,6 +180,7 @@ RAW_OBJS = \ V3Descope.o \ V3DupFinder.o \ V3EmitC.o \ + V3EmitCConstPool.o \ V3EmitCInlines.o \ V3EmitCSyms.o \ V3EmitCMake.o \ diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index f8e0c1d79..22bfd5a85 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -18,16 +18,18 @@ #include "verilatedos.h" #include "V3Ast.h" +#include "V3EmitCBase.h" #include "V3File.h" #include "V3Global.h" #include "V3Graph.h" +#include "V3Hasher.h" #include "V3PartitionGraph.h" // Just for mtask dumping #include "V3String.h" -#include "V3EmitCBase.h" #include "V3AstNodes__gen_macros.h" // Generated by 'astgen' #include +#include #include //====================================================================== @@ -295,6 +297,14 @@ AstConst* AstConst::parseParamLiteral(FileLine* fl, const string& literal) { return nullptr; } +AstNetlist::AstNetlist() + : ASTGEN_SUPER_Netlist(new FileLine(FileLine::builtInFilename())) + , m_typeTablep{new AstTypeTable(fileline())} + , m_constPoolp{new AstConstPool(fileline())} { + addMiscsp(m_typeTablep); + addMiscsp(m_constPoolp); +} + void AstNetlist::timeprecisionMerge(FileLine*, const VTimescale& value) { VTimescale prec = v3Global.opt.timeComputePrec(value); if (prec.isNone() || prec == m_timeprecision) { @@ -899,6 +909,11 @@ bool AstSenTree::hasCombo() const { return false; } +AstTypeTable::AstTypeTable(FileLine* fl) + : ASTGEN_SUPER_TypeTable(fl) { + for (int i = 0; i < AstBasicDTypeKwd::_ENUM_MAX; ++i) m_basicps[i] = nullptr; +} + void AstTypeTable::clearCache() { // When we mass-change widthMin in V3WidthCommit, we need to correct the table. // Just clear out the maps; the search functions will be used to rebuild the map @@ -993,6 +1008,115 @@ AstBasicDType* AstTypeTable::findInsertSameDType(AstBasicDType* nodep) { return nodep; } +AstConstPool::AstConstPool(FileLine* fl) + : ASTGEN_SUPER_ConstPool(fl) + , m_modp{new AstModule(fl, "@CONST-POOL@")} + , m_scopep{new AstScope(fl, m_modp, "@CONST-POOL@", nullptr, nullptr)} { + addOp1p(m_modp); + m_modp->addStmtp(m_scopep); +} + +AstVarScope* AstConstPool::createNewEntry(const string& name, AstNode* initp) { + FileLine* const fl = initp->fileline(); + AstVar* const varp = new AstVar(fl, AstVarType::MODULETEMP, name, initp->dtypep()); + varp->isConst(true); + varp->isStatic(true); + varp->valuep(initp->cloneTree(false)); + m_modp->addStmtp(varp); + AstVarScope* const varScopep = new AstVarScope(fl, m_scopep, varp); + m_scopep->addVarp(varScopep); + return varScopep; +} + +static bool sameInit(const AstInitArray* ap, const AstInitArray* bp) { + // Unpacked array initializers must have equivalent values + // Note, sadly we can't just call ap->sameTree(pb), because both: + // - the dtypes might be different instances + // - the default/inititem children might be in different order yet still yield the same table + // See note in AstInitArray::same about the same. This function instead compares by initializer + // value, rather than by tree structure. + const AstUnpackArrayDType* const aDTypep = VN_CAST(ap->dtypep(), UnpackArrayDType); + const AstUnpackArrayDType* const bDTypep = VN_CAST(bp->dtypep(), UnpackArrayDType); + UASSERT_STATIC(aDTypep, "Bad type in array initializer"); + UASSERT_STATIC(bDTypep, "Bad type in array initializer"); + if (!aDTypep->subDTypep()->sameTree(bDTypep->subDTypep())) { // Element types differ + return false; + } + if (!aDTypep->rangep()->sameTree(bDTypep->rangep())) { // Ranges differ + return false; + } + // Compare initializer arrays by value. Note this is only called when they hash the same, + // so they likely run at most once per call to 'AstConstPool::findTable'. + const uint32_t size = aDTypep->elementsConst(); + for (uint32_t n = 0; n < size; ++n) { + const AstNode* const valAp = ap->getIndexDefaultedValuep(n); + const AstNode* const valBp = bp->getIndexDefaultedValuep(n); + if (!valAp->sameTree(valBp)) { return false; } + } + return true; +} + +AstVarScope* AstConstPool::findTable(AstInitArray* initp) { + AstNode* const defaultp = initp->defaultp(); + // Verify initializer is well formed + UASSERT_OBJ(VN_IS(initp->dtypep(), UnpackArrayDType), initp, + "Const pool table must have AstUnpackArrayDType dtype"); + UASSERT_OBJ(!defaultp || VN_IS(defaultp, Const), initp, + "Const pool table default must be Const"); + for (AstNode* nodep = initp->initsp(); nodep; nodep = nodep->nextp()) { + AstNode* const valuep = VN_CAST(nodep, InitItem)->valuep(); + UASSERT_OBJ(VN_IS(valuep, Const), valuep, "Const pool table entry must be Const"); + } + // Try to find an existing table with the same content + const uint32_t hash = V3Hasher::uncachedHash(initp).value(); + const auto& er = m_tables.equal_range(hash); + for (auto it = er.first; it != er.second; ++it) { + AstVarScope* const varScopep = it->second; + const AstInitArray* const init2p = VN_CAST(varScopep->varp()->valuep(), InitArray); + if (sameInit(initp, init2p)) { + return varScopep; // Found identical table + } + } + // No such table yet, create it. + string name = "TABLE_"; + name += cvtToHex(hash); + name += "_"; + name += cvtToStr(std::distance(er.first, er.second)); + AstVarScope* const varScopep = createNewEntry(name, initp); + m_tables.emplace(hash, varScopep); + return varScopep; +} + +static bool sameInit(const AstConst* ap, const AstConst* bp) { + // Similarly to the AstInitArray comparison, we ignore the dtype instance, so long as they + // are compatible. For now, we assume the dtype is not relevant, as we only call this from + // V3Prelim which is a late pass. + // Compare initializers by value. This checks widths as well. + return ap->num().isCaseEq(bp->num()); +} + +AstVarScope* AstConstPool::findConst(AstConst* initp, bool mergeDType) { + // Try to find an existing constant with the same value + const uint32_t hash = initp->num().toHash().value(); + const auto& er = m_consts.equal_range(hash); + for (auto it = er.first; it != er.second; ++it) { + AstVarScope* const varScopep = it->second; + const AstConst* const init2p = VN_CAST(varScopep->varp()->valuep(), Const); + if (sameInit(initp, init2p) + && (mergeDType || varScopep->dtypep()->sameTree(initp->dtypep()))) { + return varScopep; // Found identical constant + } + } + // No such constant yet, create it. + string name = "CONST_"; + name += cvtToHex(hash); + name += "_"; + name += cvtToStr(std::distance(er.first, er.second)); + AstVarScope* const varScopep = createNewEntry(name, initp); + m_consts.emplace(hash, varScopep); + return varScopep; +} + //====================================================================== // Special walking tree inserters diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 39b7d74b4..2ee8a64db 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -9082,10 +9082,7 @@ class AstTypeTable final : public AstNode { DetailedMap m_detailedMap; public: - explicit AstTypeTable(FileLine* fl) - : ASTGEN_SUPER_TypeTable(fl) { - for (int i = 0; i < AstBasicDTypeKwd::_ENUM_MAX; ++i) m_basicps[i] = nullptr; - } + explicit AstTypeTable(FileLine* fl); ASTNODE_NODE_FUNCS(TypeTable) AstNodeDType* typesp() const { return VN_CAST(op1p(), NodeDType); } // op1 = List of dtypes void addTypesp(AstNodeDType* nodep) { addOp1p(nodep); } @@ -9102,6 +9099,34 @@ public: virtual void dump(std::ostream& str = std::cout) const override; }; +class AstConstPool final : public AstNode { + // Container for const static data + std::unordered_multimap m_tables; // Constant tables (unpacked arrays) + std::unordered_multimap m_consts; // Constant tables (scalars) + AstModule* const m_modp; // The Module holding the Scope below ... + AstScope* const m_scopep; // Scope holding the constant variables + + AstVarScope* createNewEntry(const string& name, AstNode* initp); + +public: + explicit AstConstPool(FileLine* fl); + ASTNODE_NODE_FUNCS(ConstPool) + AstModule* modp() const { return m_modp; } + + // Find a table (unpacked array) within the constant pool which is initialized with the + // given value, or create one if one does not already exists. The returned VarScope *might* + // have a different dtype than the given initp->dtypep(), including a different element type, + // but it will always have the same size and element width. In contexts where this matters, + // the caller must handle the dtype difference as appropriate. + AstVarScope* findTable(AstInitArray* initp); + // Find a constant within the constant pool which is initialized with the given value, or + // create one if one does not already exists. If 'mergeDType' is true, then the returned + // VarScope *might* have a different type than the given initp->dtypep(). In contexts where + // this matters, the caller must handle the dtype difference as appropriate. If 'mergeDType' is + // false, the returned VarScope will have _->dtypep()->sameTree(initp->dtypep()) return true. + AstVarScope* findConst(AstConst* initp, bool mergeDType); +}; + //###################################################################### // Top @@ -9110,7 +9135,8 @@ class AstNetlist final : public AstNode { // Parents: none // Children: MODULEs & CFILEs private: - AstTypeTable* m_typeTablep = nullptr; // Reference to top type table, for faster lookup + AstTypeTable* const m_typeTablep; // Reference to top type table, for faster lookup + AstConstPool* const m_constPoolp; // Reference to constant pool, for faster lookup AstPackage* m_dollarUnitPkgp = nullptr; // $unit AstCFunc* m_evalp = nullptr; // The '_eval' function AstExecGraph* m_execGraphp = nullptr; // Execution MTask graph for threads>1 mode @@ -9118,8 +9144,7 @@ private: VTimescale m_timeprecision; // Global time precision bool m_timescaleSpecified = false; // Input HDL specified timescale public: - AstNetlist() - : ASTGEN_SUPER_Netlist(new FileLine(FileLine::builtInFilename())) {} + AstNetlist(); ASTNODE_NODE_FUNCS(Netlist) virtual const char* broken() const override { BROKEN_RTN(m_dollarUnitPkgp && !m_dollarUnitPkgp->brokeExists()); @@ -9140,10 +9165,7 @@ public: AstNode* miscsp() const { return op3p(); } // op3 = List of dtypes etc void addMiscsp(AstNode* nodep) { addOp3p(nodep); } AstTypeTable* typeTablep() { return m_typeTablep; } - void addTypeTablep(AstTypeTable* nodep) { - m_typeTablep = nodep; - addMiscsp(nodep); - } + AstConstPool* constPoolp() { return m_constPoolp; } AstPackage* dollarUnitPkgp() const { return m_dollarUnitPkgp; } AstPackage* dollarUnitPkgAddp() { if (!m_dollarUnitPkgp) { diff --git a/src/V3EmitC.cpp b/src/V3EmitC.cpp index 7f5b11006..7bfe41fe9 100644 --- a/src/V3EmitC.cpp +++ b/src/V3EmitC.cpp @@ -1131,7 +1131,10 @@ public: // Terminals virtual void visit(AstVarRef* nodep) override { const AstVar* const varp = nodep->varp(); - if (varp->isStatic()) { + if (isConstPoolMod(varp->user4p())) { + // Reference to constant pool variable + puts(topClassName() + "__ConstPool__"); + } else if (varp->isStatic()) { // Access static variable via the containing class puts(prefixNameProtect(varp->user4p()) + "::"); } else if (!nodep->classPrefix().empty()) { @@ -1399,6 +1402,16 @@ class EmitCLazyDecls final : public AstNVisitor { m_needsBlankLine = true; } + void lazyDeclareConstPoolVar(AstVar* varp) { + if (varp->user2SetOnce()) return; // Already declared + const string nameProtect + = m_emitter.topClassName() + "__ConstPool__" + varp->nameProtect(); + m_emitter.puts("extern const "); + m_emitter.puts(varp->dtypep()->cType(nameProtect, false, false)); + m_emitter.puts(";\n"); + m_needsBlankLine = true; + } + virtual void visit(AstNodeCCall* nodep) override { lazyDeclare(nodep->funcp()); iterateChildren(nodep); @@ -1420,6 +1433,12 @@ class EmitCLazyDecls final : public AstNVisitor { } } + virtual void visit(AstVarRef* nodep) override { + AstVar* const varp = nodep->varp(); + // Only constant pool symbols are lazy declared for now ... + if (EmitCBaseVisitor::isConstPoolMod(varp->user4p())) { lazyDeclareConstPoolVar(varp); } + } + virtual void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } VL_DEBUG_FUNC; @@ -4108,11 +4127,15 @@ public: static void setParentClassPointers() { // Set user4p in all CFunc and Var to point to the containing AstNodeModule - for (AstNode* modp = v3Global.rootp()->modulesp(); modp; modp = modp->nextp()) { + auto setAll = [](AstNodeModule* modp) -> void { for (AstNode* nodep = VN_CAST(modp, NodeModule)->stmtsp(); nodep; nodep = nodep->nextp()) { if (VN_IS(nodep, CFunc) || VN_IS(nodep, Var)) nodep->user4p(modp); } + }; + for (AstNode* modp = v3Global.rootp()->modulesp(); modp; modp = modp->nextp()) { + setAll(VN_CAST(modp, NodeModule)); } + setAll(v3Global.rootp()->constPoolp()->modp()); } void V3EmitC::emitc() { diff --git a/src/V3EmitC.h b/src/V3EmitC.h index 0e4207c6a..d5c053a9f 100644 --- a/src/V3EmitC.h +++ b/src/V3EmitC.h @@ -28,6 +28,7 @@ class V3EmitC final { public: static void emitc(); + static void emitcConstPool(); static void emitcInlines(); static void emitcSyms(bool dpiHdrOnly = false); static void emitcTrace(); diff --git a/src/V3EmitCBase.h b/src/V3EmitCBase.h index 6a3d471c1..4a82b6c81 100644 --- a/src/V3EmitCBase.h +++ b/src/V3EmitCBase.h @@ -86,6 +86,11 @@ public: static string topClassName() { // Return name of top wrapper module return v3Global.opt.prefix(); } + + static bool isConstPoolMod(AstNode* modp) { + return modp == v3Global.rootp()->constPoolp()->modp(); + } + static AstCFile* newCFile(const string& filename, bool slow, bool source) { AstCFile* cfilep = new AstCFile(v3Global.rootp()->fileline(), filename); cfilep->slow(slow); diff --git a/src/V3EmitCConstPool.cpp b/src/V3EmitCConstPool.cpp new file mode 100644 index 000000000..7e48520d9 --- /dev/null +++ b/src/V3EmitCConstPool.cpp @@ -0,0 +1,194 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Emit C++ for tree +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2021 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 +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3EmitC.h" +#include "V3EmitCBase.h" +#include "V3File.h" +#include "V3Global.h" +#include "V3String.h" +#include "V3Stats.h" + +#include +#include + +//###################################################################### +// Const pool emitter + +class EmitCConstPool final : EmitCBaseVisitor { + // MEMBERS + bool m_inUnpacked = false; + uint32_t m_unpackedWord = 0; + uint32_t m_outFileCount = 0; + int m_outFileSize = 0; + VDouble0 m_tablesEmitted; + VDouble0 m_constsEmitted; + + // METHODS + VL_DEBUG_FUNC; // Declare debug() + + V3OutCFile* newOutCFile() const { + const string fileName = v3Global.opt.makeDir() + "/" + topClassName() + "__ConstPool_" + + cvtToStr(m_outFileCount) + ".cpp"; + newCFile(fileName, /* slow: */ true, /* source: */ true); + V3OutCFile* const ofp = new V3OutCFile(fileName); + ofp->putsHeader(); + ofp->puts("// DESCRIPTION: Verilator output: Constant pool\n"); + ofp->puts("//\n"); + ofp->puts("\n"); + ofp->puts("#include \"verilated_heavy.h\"\n"); + return ofp; + } + + void maybeSplitCFile() { + if (v3Global.opt.outputSplit() && m_outFileSize < v3Global.opt.outputSplit()) return; + // Splitting file, so using parallel build. + v3Global.useParallelBuild(true); + // Close current file + VL_DO_DANGLING(delete m_ofp, m_ofp); + // Open next file + m_outFileSize = 0; + ++m_outFileCount; + m_ofp = newOutCFile(); + } + + void emitVars(const AstConstPool* poolp) { + std::vector varps; + for (AstNode* nodep = poolp->modp()->stmtsp(); nodep; nodep = nodep->nextp()) { + if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) { varps.push_back(varp); } + } + + if (varps.empty()) return; // Constant pool is empty, so we are done + + stable_sort(varps.begin(), varps.end(), [](const AstVar* ap, const AstVar* bp) { // + return ap->name() < bp->name(); + }); + + m_ofp = newOutCFile(); + + for (const AstVar* varp : varps) { + maybeSplitCFile(); + const string nameProtect = topClassName() + "__ConstPool__" + varp->nameProtect(); + puts("\n"); + puts("extern const "); + puts(varp->dtypep()->cType(nameProtect, false, false)); + puts(" = "); + iterate(varp->valuep()); + puts(";\n"); + // Keep track of stats + if (VN_IS(varp->dtypep(), UnpackArrayDType)) { + ++m_tablesEmitted; + } else { + ++m_constsEmitted; + } + } + + VL_DO_DANGLING(delete m_ofp, m_ofp); + } + + // VISITORS + virtual void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } + + virtual void visit(AstInitArray* nodep) override { + const AstUnpackArrayDType* const dtypep + = VN_CAST(nodep->dtypep()->skipRefp(), UnpackArrayDType); + UASSERT_OBJ(dtypep, nodep, "Array initializer has non-array dtype"); + const uint32_t size = dtypep->elementsConst(); + const uint32_t elemBytes = dtypep->subDTypep()->widthTotalBytes(); + const uint32_t tabMod = dtypep->subDTypep()->isString() + ? 1 // String + : elemBytes <= 2 + ? 8 // CData, SData + : elemBytes <= 4 ? 4 // IData + : elemBytes <= 8 ? 2 // QData + : 1; + VL_RESTORER(m_inUnpacked); + VL_RESTORER(m_unpackedWord); + m_inUnpacked = true; + // Note the double {{ initializer. The first { starts the initializer of the VlUnpacked, + // and the second starts the initializer of m_storage within the VlUnpacked. + puts("{"); + ofp()->putsNoTracking("{"); + puts("\n"); + for (uint32_t n = 0; n < size; ++n) { + m_unpackedWord = n; + if (n) puts(n % tabMod ? ", " : ",\n"); + iterate(nodep->getIndexDefaultedValuep(n)); + } + puts("\n"); + puts("}"); + ofp()->putsNoTracking("}"); + } + + virtual void visit(AstInitItem* nodep) override { // LCOV_EXCL_START + nodep->v3fatal("Handled by AstInitArray"); + } // LCOV_EXCL_END + + virtual void visit(AstConst* nodep) override { + const V3Number& num = nodep->num(); + UASSERT_OBJ(!num.isFourState(), nodep, "4-state value in constant pool"); + AstNodeDType* const dtypep = nodep->dtypep(); + m_outFileSize += 1; + if (num.isString()) { + // Note: putsQuoted does not track indentation, so we use this instead + puts("\""); + puts(num.toString()); + puts("\""); + m_outFileSize += 9; + } else if (dtypep->isWide()) { + const uint32_t size = dtypep->widthWords(); + m_outFileSize += size - 1; + // Note the double {{ initializer. The first { starts the initializer of the VlWide, + // and the second starts the initializer of m_storage within the VlWide. + puts("{"); + ofp()->putsNoTracking("{"); + if (m_inUnpacked) puts(" // VlWide " + cvtToStr(m_unpackedWord)); + puts("\n"); + for (uint32_t n = 0; n < size; ++n) { + if (n) puts(n % 4 ? ", " : ",\n"); + ofp()->printf("0x%08" PRIx32, num.edataWord(n)); + } + puts("\n"); + puts("}"); + ofp()->putsNoTracking("}"); + } else if (dtypep->isQuad()) { + ofp()->printf("0x%016" PRIx64, static_cast(num.toUQuad())); + } else if (dtypep->widthMin() > 16) { + ofp()->printf("0x%08" PRIx32, num.toUInt()); + } else if (dtypep->widthMin() > 8) { + ofp()->printf("0x%04" PRIx32, num.toUInt()); + } else { + ofp()->printf("0x%02" PRIx32, num.toUInt()); + } + } + +public: + explicit EmitCConstPool(AstConstPool* poolp) { + emitVars(poolp); + V3Stats::addStatSum("ConstPool, Tables emitted", m_tablesEmitted); + V3Stats::addStatSum("ConstPool, Constants emitted", m_constsEmitted); + } +}; + +//###################################################################### +// EmitC static functions + +void V3EmitC::emitcConstPool() { + UINFO(2, __FUNCTION__ << ": " << endl); + EmitCConstPool(v3Global.rootp()->constPoolp()); +} diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index a04288b68..a2f7142d1 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -278,6 +278,7 @@ class EmitCSyms final : EmitCBaseVisitor { if (!m_dpiHdrOnly) emitDpiImp(); } } + virtual void visit(AstConstPool* nodep) override {} // Ignore virtual void visit(AstNodeModule* nodep) override { nameCheck(nodep); VL_RESTORER(m_modp); diff --git a/src/V3EmitXml.cpp b/src/V3EmitXml.cpp index e02173e09..3b452ef15 100644 --- a/src/V3EmitXml.cpp +++ b/src/V3EmitXml.cpp @@ -117,6 +117,7 @@ class EmitXmlFileVisitor final : public AstNVisitor { iterateChildren(nodep); puts("\n"); } + virtual void visit(AstConstPool*) override {} virtual void visit(AstNodeModule* nodep) override { outputTag(nodep, ""); puts(" origName="); @@ -316,6 +317,7 @@ private: VL_DEBUG_FUNC; // Declare debug() // VISITORS + virtual void visit(AstConstPool*) override {} virtual void visit(AstNodeModule* nodep) override { if (nodep->level() >= 0 && nodep->level() <= 2) { // ==2 because we don't add wrapper when in XML mode diff --git a/src/V3Global.cpp b/src/V3Global.cpp index 73e78711a..f86106411 100644 --- a/src/V3Global.cpp +++ b/src/V3Global.cpp @@ -29,11 +29,7 @@ //###################################################################### // V3 Class -- top level -AstNetlist* V3Global::makeNetlist() { - AstNetlist* newp = new AstNetlist(); - newp->addTypeTablep(new AstTypeTable(newp->fileline())); - return newp; -} +AstNetlist* V3Global::makeNetlist() { return new AstNetlist(); } void V3Global::clear() { #ifdef VL_LEAK_CHECK diff --git a/src/V3Hash.h b/src/V3Hash.h index 6bb2963ba..b28b3bf1b 100644 --- a/src/V3Hash.h +++ b/src/V3Hash.h @@ -31,7 +31,7 @@ public: V3Hash() : m_value{0} {} explicit V3Hash(uint32_t val) - : m_value{val + 0x9e3779b9} {} // This is the same as 'V3Hash() + val' + : m_value{val} {} explicit V3Hash(int32_t val) : m_value{static_cast(val)} {} explicit V3Hash(size_t val) diff --git a/src/V3Hasher.cpp b/src/V3Hasher.cpp index 7562c0e16..775cee846 100644 --- a/src/V3Hasher.cpp +++ b/src/V3Hasher.cpp @@ -351,7 +351,17 @@ private: m_hash += hashNodeAndIterate(nodep, HASH_DTYPE, HASH_CHILDREN, [=]() {}); } virtual void visit(AstInitArray* nodep) override { - m_hash += hashNodeAndIterate(nodep, HASH_DTYPE, HASH_CHILDREN, [=]() {}); + // Hash unpacked array initializers by value, as the order of initializer nodes does not + // matter, and we want semantically equivalent initializers to map to the same hash. + AstUnpackArrayDType* const dtypep = VN_CAST(nodep->dtypep(), UnpackArrayDType); + m_hash += hashNodeAndIterate(nodep, HASH_DTYPE, /* hashChildren: */ !dtypep, [=]() { + if (dtypep) { + const uint32_t size = dtypep->elementsConst(); + for (uint32_t n = 0; n < size; ++n) { // + iterateNull(nodep->getIndexDefaultedValuep(n)); + } + } + }); } virtual void visit(AstPragma* nodep) override { m_hash += hashNodeAndIterate(nodep, HASH_DTYPE, HASH_CHILDREN, [=]() { // diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp index 0578e5bc7..2e6f5532b 100644 --- a/src/V3LinkCells.cpp +++ b/src/V3LinkCells.cpp @@ -193,6 +193,7 @@ private: << "' was not found in design."); } } + virtual void visit(AstConstPool* nodep) override {} virtual void visit(AstNodeModule* nodep) override { // Module: Pick up modnames, so we can resolve cells later VL_RESTORER(m_modp); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index d5d5d0748..102e92034 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -770,6 +770,7 @@ class LinkDotFindVisitor final : public AstNVisitor { } } virtual void visit(AstTypeTable*) override {} + virtual void visit(AstConstPool*) override {} virtual void visit(AstNodeModule* nodep) override { // Called on top module from Netlist, other modules from the cell creating them, // and packages @@ -1364,6 +1365,7 @@ private: // VISITs virtual void visit(AstTypeTable*) override {} + virtual void visit(AstConstPool*) override {} virtual void visit(AstNodeModule* nodep) override { UINFO(5, " " << nodep << endl); if (nodep->dead() || !nodep->user4()) { @@ -1530,6 +1532,7 @@ class LinkDotScopeVisitor final : public AstNVisitor { // Recurse..., backward as must do packages before using packages iterateChildrenBackwards(nodep); } + virtual void visit(AstConstPool*) override {} virtual void visit(AstScope* nodep) override { UINFO(8, " SCOPE " << nodep << endl); UASSERT_OBJ(m_statep->forScopeCreation(), nodep, @@ -1925,6 +1928,7 @@ private: iterateChildrenBackwards(nodep); } virtual void visit(AstTypeTable*) override {} + virtual void visit(AstConstPool*) override {} virtual void visit(AstNodeModule* nodep) override { if (nodep->dead()) return; checkNoDot(nodep); diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 52b24f349..8a5893a5b 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1141,6 +1141,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char } }); DECL_OPTION("-max-num-width", Set, &m_maxNumWidth); + DECL_OPTION("-merge-const-pool", OnOff, &m_mergeConstPool); DECL_OPTION("-mod-prefix", Set, &m_modPrefix); DECL_OPTION("-O", CbPartialMatch, [this](const char* optp) { diff --git a/src/V3Options.h b/src/V3Options.h index dff879acf..3f33c90d3 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -246,6 +246,7 @@ private: bool m_lintOnly = false; // main switch: --lint-only bool m_gmake = false; // main switch: --make gmake bool m_main = false; // main swithc: --main + bool m_mergeConstPool = true; // main switch: --merge-const-pool bool m_orderClockDly = true; // main switch: --order-clock-delay bool m_outFormatOk = false; // main switch: --cc, --sc or --sp was specified bool m_pedantic = false; // main switch: --Wpedantic @@ -454,6 +455,7 @@ public: bool traceStructs() const { return m_traceStructs; } bool traceUnderscore() const { return m_traceUnderscore; } bool main() const { return m_main; } + bool mergeConstPool() const { return m_mergeConstPool; } bool orderClockDly() const { return m_orderClockDly; } bool outFormatOk() const { return m_outFormatOk; } bool keepTempFiles() const { return (V3Error::debugDefault() != 0); } diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 82c2404ae..1f7aaf584 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -92,11 +92,9 @@ private: // AstNodeMath::user() -> bool. True if iterated already // AstShiftL::user2() -> bool. True if converted to conditional // AstShiftR::user2() -> bool. True if converted to conditional - // AstConst::user2p() -> Replacement static variable pointer // *::user4() -> See PremitAssignVisitor AstUser1InUse m_inuser1; AstUser2InUse m_inuser2; - // AstUser4InUse part of V3Hasher via V3DupFinder // STATE AstNodeModule* m_modp = nullptr; // Current module @@ -106,10 +104,7 @@ private: AstTraceInc* m_inTracep = nullptr; // Inside while loop, special statement additions bool m_assignLhs = false; // Inside assignment lhs, don't breakup extracts - V3DupFinder m_dupFinder; // Duplicate finder for static constants that can be reused - - VDouble0 m_staticConstantsExtracted; // Statistic tracking - VDouble0 m_staticConstantsReused; // Statistic tracking + VDouble0 m_extractedToConstPool; // Statistic tracking // METHODS VL_DEBUG_FUNC; // Declare debug() @@ -170,71 +165,48 @@ private: } void createDeepTemp(AstNode* nodep, bool noSubst) { - if (nodep->user1()) return; - if (debug() > 8) nodep->dumpTree(cout, "deepin:"); + if (nodep->user1SetOnce()) return; // Only add another assignment for this node - AstNRelinker linker; - nodep->unlinkFrBack(&linker); + AstNRelinker relinker; + nodep->unlinkFrBack(&relinker); + FileLine* const fl = nodep->fileline(); AstVar* varp = nullptr; - AstConst* const constp = VN_CAST(nodep, Const); - - const bool useStatic = constp && (constp->width() >= STATIC_CONST_MIN_WIDTH) - && !constp->num().isFourState(); - if (useStatic) { - // Extract as static constant - const auto& it = m_dupFinder.findDuplicate(constp); - if (it == m_dupFinder.end()) { - const string newvarname = string("__Vconst") + cvtToStr(m_modp->varNumGetInc()); - varp = new AstVar(nodep->fileline(), AstVarType::MODULETEMP, newvarname, - nodep->dtypep()); - varp->isConst(true); - varp->isStatic(true); - varp->valuep(constp); - m_modp->addStmtp(varp); - m_dupFinder.insert(constp); - nodep->user2p(varp); - ++m_staticConstantsExtracted; - } else { - varp = VN_CAST(it->second->user2p(), Var); - ++m_staticConstantsReused; - } + const bool useConstPool = constp // Is a constant + && (constp->width() >= STATIC_CONST_MIN_WIDTH) // Large enough + && !constp->num().isFourState() // Not four state + && !constp->num().isString(); // Not a string + if (useConstPool) { + // Extract into constant pool. + const bool merge = v3Global.opt.mergeConstPool(); + varp = v3Global.rootp()->constPoolp()->findConst(constp, merge)->varp(); + nodep->deleteTree(); + ++m_extractedToConstPool; } else { // Keep as local temporary - const string newvarname = string("__Vtemp") + cvtToStr(m_modp->varNumGetInc()); - varp - = new AstVar(nodep->fileline(), AstVarType::STMTTEMP, newvarname, nodep->dtypep()); + const string name = string("__Vtemp") + cvtToStr(m_modp->varNumGetInc()); + varp = new AstVar(fl, AstVarType::STMTTEMP, name, nodep->dtypep()); m_cfuncp->addInitsp(varp); - } - - if (noSubst) varp->noSubst(true); // Do not remove varrefs to this in V3Const - - // Replace node tree with reference to var - AstVarRef* newp = new AstVarRef(nodep->fileline(), varp, VAccess::READ); - linker.relink(newp); - - if (!useStatic) { // Put assignment before the referencing statement - AstAssign* assp = new AstAssign( - nodep->fileline(), new AstVarRef(nodep->fileline(), varp, VAccess::WRITE), nodep); - insertBeforeStmt(assp); - if (debug() > 8) assp->dumpTree(cout, "deepou:"); + insertBeforeStmt(new AstAssign(fl, new AstVarRef(fl, varp, VAccess::WRITE), nodep)); } - nodep->user1(true); // Don't add another assignment + // Do not remove VarRefs to this in V3Const + if (noSubst) varp->noSubst(true); + + // Replace node with VarRef to new Var + relinker.relink(new AstVarRef(fl, varp, VAccess::READ)); } // VISITORS virtual void visit(AstNodeModule* nodep) override { UINFO(4, " MOD " << nodep << endl); UASSERT_OBJ(m_modp == nullptr, nodep, "Nested modules ?"); - UASSERT_OBJ(m_dupFinder.empty(), nodep, "Statements outside module ?"); m_modp = nodep; m_cfuncp = nullptr; iterateChildren(nodep); m_modp = nullptr; - m_dupFinder.clear(); } virtual void visit(AstCFunc* nodep) override { VL_RESTORER(m_cfuncp); @@ -442,9 +414,8 @@ public: // CONSTRUCTORS explicit PremitVisitor(AstNetlist* nodep) { iterate(nodep); } virtual ~PremitVisitor() { - V3Stats::addStat("Optimizations, Prelim static constants extracted", - m_staticConstantsExtracted); - V3Stats::addStat("Optimizations, Prelim static constants reused", m_staticConstantsReused); + V3Stats::addStat("Optimizations, Prelim extracted value to ConstPool", + m_extractedToConstPool); } }; diff --git a/src/V3String.h b/src/V3String.h index 47c41e9ee..1b185ce04 100644 --- a/src/V3String.h +++ b/src/V3String.h @@ -22,9 +22,11 @@ // No V3 headers here - this is a base class for Vlc etc +#include #include #include #include +#include #include #include @@ -36,11 +38,18 @@ template std::string cvtToStr(const T& t) { os << t; return os.str(); } -template std::string cvtToHex(const T* tp) { +template +typename std::enable_if::value, std::string>::type cvtToHex(const T tp) { std::ostringstream os; os << static_cast(tp); return os.str(); } +template +typename std::enable_if::value, std::string>::type cvtToHex(const T t) { + std::ostringstream os; + os << std::hex << std::setw(sizeof(T) * 8 / 4) << std::setfill('0') << t; + return os.str(); +} inline uint32_t cvtToHash(const void* vp) { // We can shove a 64 bit pointer into a 32 bit bucket diff --git a/src/V3Table.cpp b/src/V3Table.cpp index eb29e3639..d233cd60e 100644 --- a/src/V3Table.cpp +++ b/src/V3Table.cpp @@ -38,11 +38,15 @@ // CONFIG // 1MB is max table size (better be lots of instructs to be worth it!) -static const double TABLE_MAX_BYTES = 1 * 1024 * 1024; +static constexpr int TABLE_MAX_BYTES = 1 * 1024 * 1024; // 64MB is close to max memory of some systems (256MB or so), so don't get out of control -static const double TABLE_TOTAL_BYTES = 64 * 1024 * 1024; -static const double TABLE_SPACE_TIME_MULT = 8; // Worth 8 bytes of data to replace a instruction -static const int TABLE_MIN_NODE_COUNT = 32; // If < 32 instructions, not worth the effort +static constexpr int TABLE_TOTAL_BYTES = 64 * 1024 * 1024; +// Worth no more than 8 bytes of data to replace an instruction +static constexpr int TABLE_SPACE_TIME_MULT = 8; +// If < 32 instructions, not worth the effort +static constexpr int TABLE_MIN_NODE_COUNT = 32; +// Assume an instruction is 4 bytes +static constexpr int TABLE_BYTES_PER_INST = 4; //###################################################################### @@ -62,6 +66,81 @@ public: virtual ~TableSimulateVisitor() override = default; }; +//###################################################################### +// Class for holding lookup table state during construction + +class TableBuilder final { + FileLine* const m_fl; // FileLine used during construction + AstInitArray* m_initp = nullptr; // The lookup table initializer values + AstVarScope* m_varScopep = nullptr; // The scoped variable holding the table + +public: + explicit TableBuilder(FileLine* fl) + : m_fl{fl} {} + + ~TableBuilder() { + if (m_initp) m_initp->deleteTree(); + } + + void setTableSize(AstNodeDType* elemDType, unsigned size) { + UASSERT_OBJ(!m_initp, m_fl, "Table size already set"); + UASSERT_OBJ(size > 0, m_fl, "Size zero"); + // TODO: Assert elemDType is a packed type + // Create data type + const int width = elemDType->width(); + AstNodeDType* const subDTypep + = elemDType->isString() + ? elemDType + : v3Global.rootp()->findBitDType(width, width, VSigning::UNSIGNED); + AstUnpackArrayDType* const tableDTypep + = new AstUnpackArrayDType(m_fl, subDTypep, new AstRange(m_fl, size, 0)); + v3Global.rootp()->typeTablep()->addTypesp(tableDTypep); + // Create table initializer (with default value 0) + AstConst* const defaultp = elemDType->isString() + ? new AstConst(m_fl, AstConst::String(), "") + : new AstConst(m_fl, AstConst::WidthedValue(), width, 0); + m_initp = new AstInitArray(m_fl, tableDTypep, defaultp); + } + + void addValue(unsigned index, const V3Number& value) { + UASSERT_OBJ(!m_varScopep, m_fl, "Table variable already created"); + // Default value is zero/empty string so don't add it + if (value.isString() ? value.toString().empty() : value.isEqZero()) return; + m_initp->addIndexValuep(index, new AstConst(m_fl, value)); + } + + AstVarScope* varScopep() { + if (!m_varScopep) { m_varScopep = v3Global.rootp()->constPoolp()->findTable(m_initp); } + return m_varScopep; + } +}; + +//###################################################################### +// Class for holding output variable state during table conversion of logic + +class TableOutputVar final { + AstVarScope* const m_varScopep; // The output variable + const unsigned m_ord; // Output ordinal number in this block + bool m_mayBeUnassigned = false; // If true, then this variable may be unassigned through + // some path through the block being table converted + TableBuilder m_tableBuilder; + +public: + TableOutputVar(AstVarScope* varScopep, unsigned ord) + : m_varScopep{varScopep} + , m_ord{ord} + , m_tableBuilder{varScopep->fileline()} {} + + AstVarScope* varScopep() const { return m_varScopep; } + string name() const { return varScopep()->varp()->name(); } + unsigned ord() const { return m_ord; } + void setMayBeUnassigned() { m_mayBeUnassigned = true; } + bool mayBeUnassigned() const { return m_mayBeUnassigned; } + void setTableSize(unsigned size) { m_tableBuilder.setTableSize(varScopep()->dtypep(), size); } + void addValue(unsigned index, const V3Number& value) { m_tableBuilder.addValue(index, value); } + AstVarScope* tabeVarScopep() { return m_tableBuilder.varScopep(); } +}; + //###################################################################### // Table class functions @@ -84,44 +163,54 @@ private: // State cleared on each always/assignw bool m_assignDly = false; // Consists of delayed assignments instead of normal assignments - int m_inWidth = 0; // Input table width - int m_outWidth = 0; // Output table width + unsigned m_inWidthBits = 0; // Input table width - in bits + unsigned m_outWidthBytes = 0; // Output table width - in bytes std::deque m_inVarps; // Input variable list - std::deque m_outVarps; // Output variable list - std::deque m_outNotSet; // True if output variable is not set at some point - - // When creating a table - std::deque m_tableVarps; // Table being created + std::vector m_outVarps; // Output variable list // METHODS VL_DEBUG_FUNC; // Declare debug() +public: + void simulateVarRefCb(AstVarRef* nodep) { + // Called by TableSimulateVisitor on each unique varref encountered + UINFO(9, " SimVARREF " << nodep << endl); + AstVarScope* vscp = nodep->varScopep(); + if (nodep->access().isWriteOrRW()) { + // We'll make the table with a separate natural alignment for each output var, so + // always have 8, 16 or 32 bit widths, so use widthTotalBytes + m_outWidthBytes += nodep->varp()->dtypeSkipRefp()->widthTotalBytes(); + m_outVarps.emplace_back(vscp, m_outVarps.size()); + } + if (nodep->access().isReadOrRW()) { + m_inWidthBits += nodep->varp()->width(); + m_inVarps.push_back(vscp); + } + } + +private: bool treeTest(AstAlways* nodep) { // Process alw/assign tree - m_inWidth = 0; - m_outWidth = 0; + m_inWidthBits = 0; + m_outWidthBytes = 0; m_inVarps.clear(); m_outVarps.clear(); - m_outNotSet.clear(); // Collect stats TableSimulateVisitor chkvis(this); chkvis.mainTableCheck(nodep); m_assignDly = chkvis.isAssignDly(); - // Also sets m_inWidth - // Also sets m_outWidth + // Also sets m_inWidthBits + // Also sets m_outWidthBytes // Also sets m_inVarps // Also sets m_outVarps // Calc data storage in bytes - size_t chgWidth = m_outVarps.size(); // Width of one change-it-vector - if (chgWidth < 8) chgWidth = 8; - double space = (std::pow(static_cast(2.0), static_cast(m_inWidth)) - * static_cast(m_outWidth + chgWidth)); + const size_t chgWidth = m_outVarps.size(); + const double space = std::pow(2.0, m_inWidthBits) * (m_outWidthBytes + chgWidth); // Instruction count bytes (ok, it's space also not time :) - double bytesPerInst = 4; - double time = ((chkvis.instrCount() * bytesPerInst + chkvis.dataCount()) - + 1); // +1 so won't div by zero + const double time // max(_, 1), so we won't divide by zero + = std::max(chkvis.instrCount() * TABLE_BYTES_PER_INST + chkvis.dataCount(), 1); if (chkvis.instrCount() < TABLE_MIN_NODE_COUNT) { chkvis.clearOptimizable(nodep, "Table has too few nodes involved"); } @@ -134,13 +223,13 @@ private: if (m_totalBytes > TABLE_TOTAL_BYTES) { chkvis.clearOptimizable(nodep, "Table out of memory"); } - if (!m_outWidth || !m_inWidth) { // + if (!m_outWidthBytes || !m_inWidthBits) { chkvis.clearOptimizable(nodep, "Table has no outputs"); } - UINFO(4, " Test: Opt=" << (chkvis.optimizable() ? "OK" : "NO") - << ", Instrs=" << chkvis.instrCount() - << " Data=" << chkvis.dataCount() << " inw=" << m_inWidth - << " outw=" << m_outWidth << " Spacetime=" << (space / time) << "(" + UINFO(4, " Test: Opt=" << (chkvis.optimizable() ? "OK" : "NO") << ", Instrs=" + << chkvis.instrCount() << " Data=" << chkvis.dataCount() + << " in width (bits)=" << m_inWidthBits << " out width (bytes)=" + << m_outWidthBytes << " Spacetime=" << (space / time) << "(" << space << "/" << time << ")" << ": " << nodep << endl); if (chkvis.optimizable()) { @@ -150,135 +239,51 @@ private: return chkvis.optimizable(); } -public: - void simulateVarRefCb(AstVarRef* nodep) { - // Called by TableSimulateVisitor on each unique varref encountered - UINFO(9, " SimVARREF " << nodep << endl); - AstVarScope* vscp = nodep->varScopep(); - if (nodep->access().isWriteOrRW()) { - m_outWidth += nodep->varp()->dtypeSkipRefp()->widthTotalBytes(); - m_outVarps.push_back(vscp); - } - if (nodep->access().isReadOrRW()) { - // We'll make the table with a separate natural alignment for each - // output var, so always have char, 16 or 32 bit widths, so use widthTotalBytes - m_inWidth += nodep->varp()->width(); // Space for var - m_inVarps.push_back(vscp); - } - } - -private: - void createTable(AstAlways* nodep) { + void replaceWithTable(AstAlways* nodep) { // We've determined this table of nodes is optimizable, do it. ++m_modTables; ++m_statTablesCre; - // Index into our table - AstVar* indexVarp - = new AstVar(nodep->fileline(), AstVarType::BLOCKTEMP, - "__Vtableidx" + cvtToStr(m_modTables), VFlagBitPacked(), m_inWidth); + FileLine* const fl = nodep->fileline(); + + // We will need a table index variable, create it here. + AstVar* const indexVarp + = new AstVar(fl, AstVarType::BLOCKTEMP, "__Vtableidx" + cvtToStr(m_modTables), + VFlagBitPacked(), m_inWidthBits); m_modp->addStmtp(indexVarp); - AstVarScope* indexVscp = new AstVarScope(indexVarp->fileline(), m_scopep, indexVarp); + AstVarScope* const indexVscp = new AstVarScope(indexVarp->fileline(), m_scopep, indexVarp); m_scopep->addVarp(indexVscp); - // Change it variable - FileLine* fl = nodep->fileline(); - AstNodeArrayDType* dtypep = new AstUnpackArrayDType( - fl, nodep->findBitDType(m_outVarps.size(), m_outVarps.size(), VSigning::UNSIGNED), - new AstRange(fl, VL_MASK_I(m_inWidth), 0)); - v3Global.rootp()->typeTablep()->addTypesp(dtypep); - AstVar* chgVarp = new AstVar(fl, AstVarType::MODULETEMP, - "__Vtablechg" + cvtToStr(m_modTables), dtypep); - chgVarp->isConst(true); - chgVarp->valuep(new AstInitArray(nodep->fileline(), dtypep, nullptr)); - m_modp->addStmtp(chgVarp); - AstVarScope* chgVscp = new AstVarScope(chgVarp->fileline(), m_scopep, chgVarp); - m_scopep->addVarp(chgVscp); + // The 'output assigned' table builder + TableBuilder outputAssignedTableBuilder(fl); + outputAssignedTableBuilder.setTableSize( + nodep->findBitDType(m_outVarps.size(), m_outVarps.size(), VSigning::UNSIGNED), + VL_MASK_I(m_inWidthBits)); - createTableVars(nodep); - AstNode* stmtsp = createLookupInput(nodep, indexVscp); - createTableValues(nodep, chgVscp); + // Set sizes of output tables + for (TableOutputVar& tov : m_outVarps) { tov.setTableSize(VL_MASK_I(m_inWidthBits)); } - // Collapse duplicate tables - chgVscp = findDuplicateTable(chgVscp); - for (auto& vscp : m_tableVarps) vscp = findDuplicateTable(vscp); + // Populate the tables + createTables(nodep, outputAssignedTableBuilder); - createOutputAssigns(nodep, stmtsp, indexVscp, chgVscp); + AstNode* stmtsp = createLookupInput(fl, indexVscp); + createOutputAssigns(nodep, stmtsp, indexVscp, outputAssignedTableBuilder.varScopep()); // Link it in. - if (AstAlways* nodeap = VN_CAST(nodep, Always)) { - // Keep sensitivity list, but delete all else - nodeap->bodysp()->unlinkFrBackWithNext()->deleteTree(); - nodeap->addStmtp(stmtsp); - if (debug() >= 6) nodeap->dumpTree(cout, " table_new: "); - } else { // LCOV_EXCL_LINE - nodep->v3fatalSrc("Creating table under unknown node type"); - } - - // Cleanup internal structures - m_tableVarps.clear(); + // Keep sensitivity list, but delete all else + nodep->bodysp()->unlinkFrBackWithNext()->deleteTree(); + nodep->addStmtp(stmtsp); + if (debug() >= 6) nodep->dumpTree(cout, " table_new: "); } - void createTableVars(AstNode* nodep) { - // Create table for each output - std::map namecounts; - for (const AstVarScope* outvscp : m_outVarps) { - AstVar* outvarp = outvscp->varp(); - FileLine* fl = nodep->fileline(); - AstNodeArrayDType* dtypep = new AstUnpackArrayDType( - fl, outvarp->dtypep(), new AstRange(fl, VL_MASK_I(m_inWidth), 0)); - v3Global.rootp()->typeTablep()->addTypesp(dtypep); - string name = "__Vtable" + cvtToStr(m_modTables) + "_" + outvarp->name(); - const auto nit = namecounts.find(name); - if (nit != namecounts.end()) { - // Multiple scopes can have same var name. We could append the - // scope name but that is very long, so just deduplicate. - name += "__dedup" + cvtToStr(++nit->second); - } else { - namecounts[name] = 0; - } - AstVar* tablevarp = new AstVar(fl, AstVarType::MODULETEMP, name, dtypep); - tablevarp->isConst(true); - tablevarp->isStatic(true); - tablevarp->valuep(new AstInitArray(nodep->fileline(), dtypep, nullptr)); - m_modp->addStmtp(tablevarp); - AstVarScope* tablevscp = new AstVarScope(tablevarp->fileline(), m_scopep, tablevarp); - m_scopep->addVarp(tablevscp); - m_tableVarps.push_back(tablevscp); - } - } - - AstNode* createLookupInput(AstNode* nodep, AstVarScope* indexVscp) { - // Concat inputs into a single temp variable (inside always) - // First var in inVars becomes the LSB of the concat - AstNode* concatp = nullptr; - for (AstVarScope* invscp : m_inVarps) { - AstVarRef* refp = new AstVarRef(nodep->fileline(), invscp, VAccess::READ); - if (concatp) { - concatp = new AstConcat(nodep->fileline(), refp, concatp); - } else { - concatp = refp; - } - } - - AstNode* stmtsp - = new AstAssign(nodep->fileline(), - new AstVarRef(nodep->fileline(), indexVscp, VAccess::WRITE), concatp); - return stmtsp; - } - - void createTableValues(AstAlways* nodep, AstVarScope* chgVscp) { + void createTables(AstAlways* nodep, TableBuilder& outputAssignedTableBuilder) { // Create table // There may be a simulation path by which the output doesn't change value. // We could bail on these cases, or we can have a "change it" boolean. // We've chosen the latter route, since recirc is common in large FSMs. - for (std::deque::iterator it = m_outVarps.begin(); it != m_outVarps.end(); - ++it) { - m_outNotSet.push_back(false); - } - uint32_t inValueNextInitArray = 0; TableSimulateVisitor simvis(this); - for (uint32_t inValue = 0; inValue <= VL_MASK_I(m_inWidth); inValue++) { + for (uint32_t i = 0; i <= VL_MASK_I(m_inWidthBits); ++i) { + const uint32_t inValue = i; // Make a new simulation structure so we can set new input values UINFO(8, " Simulating " << std::hex << inValue << endl); @@ -290,12 +295,12 @@ private: uint32_t shift = 0; for (AstVarScope* invscp : m_inVarps) { // LSB is first variable, so extract it that way - AstConst cnst(invscp->fileline(), AstConst::WidthedValue(), invscp->width(), - VL_MASK_I(invscp->width()) & (inValue >> shift)); + const AstConst cnst(invscp->fileline(), AstConst::WidthedValue(), invscp->width(), + VL_MASK_I(invscp->width()) & (inValue >> shift)); simvis.newValue(invscp, &cnst); shift += invscp->width(); - // We're just using32 bit arithmetic, because there's no - // way the input table can be 2^32 bytes! + // We are using 32 bit arithmetic, because there's no way the input table can be + // 2^32 bytes! UASSERT_OBJ(shift <= 32, nodep, "shift overflow"); UINFO(8, " Input " << invscp->name() << " = " << cnst.name() << endl); } @@ -306,104 +311,72 @@ private: "Optimizable cleared, even though earlier test run said not: " << simvis.whyNotMessage()); - // If a output changed, add it to table - int outnum = 0; - V3Number outputChgMask(nodep, m_outVarps.size(), 0); - for (AstVarScope* outvscp : m_outVarps) { - V3Number* outnump = simvis.fetchOutNumberNull(outvscp); - AstNode* setp; - if (!outnump) { - UINFO(8, " Output " << outvscp->name() << " never set\n"); - m_outNotSet[outnum] = true; - // Value in table is arbitrary, but we need something - setp = new AstConst(outvscp->fileline(), AstConst::WidthedValue(), - outvscp->width(), 0); + // Build output value tables and the assigned flags table + V3Number outputAssignedMask(nodep, m_outVarps.size(), 0); + for (TableOutputVar& tov : m_outVarps) { + if (V3Number* const outnump = simvis.fetchOutNumberNull(tov.varScopep())) { + UINFO(8, " Output " << tov.name() << " = " << *outnump << endl); + outputAssignedMask.setBit(tov.ord(), 1); // Mark output as assigned + tov.addValue(inValue, *outnump); } else { - UINFO(8, " Output " << outvscp->name() << " = " << *outnump << endl); - // m_tableVarps[inValue] = num; - // Mark changed bit, too - outputChgMask.setBit(outnum, 1); - setp = new AstConst(outnump->fileline(), *outnump); + UINFO(8, " Output " << tov.name() << " not set for this input\n"); + tov.setMayBeUnassigned(); } - // Note InitArray requires us to have the values in inValue order - VN_CAST(m_tableVarps[outnum]->varp()->valuep(), InitArray)->addValuep(setp); - outnum++; } - { // Set changed table - UASSERT_OBJ(inValue == inValueNextInitArray, nodep, - "InitArray requires us to have the values in inValue order"); - inValueNextInitArray++; - AstNode* setp = new AstConst(nodep->fileline(), outputChgMask); - VN_CAST(chgVscp->varp()->valuep(), InitArray)->addValuep(setp); - } + // Set changed table + outputAssignedTableBuilder.addValue(inValue, outputAssignedMask); } // each value } - AstVarScope* findDuplicateTable(AstVarScope* vsc1p) { - // See if another table we've created is identical, if so use it for both. - // (A more 'modern' way would be to instead use V3DupFinder::findDuplicate) - AstVar* var1p = vsc1p->varp(); - for (AstVarScope* vsc2p : m_modTableVscs) { - AstVar* var2p = vsc2p->varp(); - if (var1p->width() == var2p->width() - && (var1p->dtypep()->arrayUnpackedElements() - == var2p->dtypep()->arrayUnpackedElements())) { - const AstNode* init1p = VN_CAST(var1p->valuep(), InitArray); - const AstNode* init2p = VN_CAST(var2p->valuep(), InitArray); - if (init1p->sameGateTree(init2p)) { - UINFO(8, " Duplicate table var " << vsc2p << " == " << vsc1p << endl); - VL_DO_DANGLING(vsc1p->unlinkFrBack()->deleteTree(), vsc1p); - return vsc2p; - } + AstNode* createLookupInput(FileLine* fl, AstVarScope* indexVscp) { + // Concat inputs into a single temp variable (inside always) + // First var in inVars becomes the LSB of the concat + AstNode* concatp = nullptr; + for (AstVarScope* invscp : m_inVarps) { + AstVarRef* refp = new AstVarRef(fl, invscp, VAccess::READ); + if (concatp) { + concatp = new AstConcat(fl, refp, concatp); + } else { + concatp = refp; } } - m_modTableVscs.push_back(vsc1p); - return vsc1p; + + return new AstAssign(fl, new AstVarRef(fl, indexVscp, VAccess::WRITE), concatp); + } + + AstArraySel* select(FileLine* fl, AstVarScope* fromp, AstVarScope* indexp) { + AstVarRef* const fromRefp = new AstVarRef(fl, fromp, VAccess::READ); + AstVarRef* const indexRefp = new AstVarRef(fl, indexp, VAccess::READ); + return new AstArraySel(fl, fromRefp, indexRefp); } void createOutputAssigns(AstNode* nodep, AstNode* stmtsp, AstVarScope* indexVscp, - AstVarScope* chgVscp) { - // We walk through the changemask table, and if all ones know - // the output is set on all branches and therefore eliminate the - // if. If all uses of the changemask disappear, dead code - // elimination will remove it for us. - // Set each output from array ref into our table - int outnum = 0; - for (AstVarScope* outvscp : m_outVarps) { - AstNode* alhsp = new AstVarRef(nodep->fileline(), outvscp, VAccess::WRITE); - AstNode* arhsp = new AstArraySel( - nodep->fileline(), - new AstVarRef(nodep->fileline(), m_tableVarps[outnum], VAccess::READ), - new AstVarRef(nodep->fileline(), indexVscp, VAccess::READ)); - AstNode* outasnp - = (m_assignDly - ? static_cast(new AstAssignDly(nodep->fileline(), alhsp, arhsp)) - : static_cast(new AstAssign(nodep->fileline(), alhsp, arhsp))); - AstNode* outsetp = outasnp; + AstVarScope* outputAssignedTableVscp) { + FileLine* const fl = nodep->fileline(); + for (TableOutputVar& tov : m_outVarps) { + AstNode* const alhsp = new AstVarRef(fl, tov.varScopep(), VAccess::WRITE); + AstNode* const arhsp = select(fl, tov.tabeVarScopep(), indexVscp); + AstNode* outsetp = m_assignDly + ? static_cast(new AstAssignDly(fl, alhsp, arhsp)) + : static_cast(new AstAssign(fl, alhsp, arhsp)); - // Is the value set in only some branches of the table? - if (m_outNotSet[outnum]) { + // If this output is unassigned on some code paths, wrap the assignment in an If + if (tov.mayBeUnassigned()) { V3Number outputChgMask(nodep, m_outVarps.size(), 0); - outputChgMask.setBit(outnum, 1); - outsetp = new AstIf( - nodep->fileline(), - new AstAnd(nodep->fileline(), - new AstArraySel( - nodep->fileline(), - new AstVarRef(nodep->fileline(), chgVscp, VAccess::READ), - new AstVarRef(nodep->fileline(), indexVscp, VAccess::READ)), - new AstConst(nodep->fileline(), outputChgMask)), - outsetp, nullptr); + outputChgMask.setBit(tov.ord(), 1); + AstNode* const condp + = new AstAnd(fl, select(fl, outputAssignedTableVscp, indexVscp), + new AstConst(fl, outputChgMask)); + outsetp = new AstIf(fl, condp, outsetp, nullptr); } stmtsp->addNext(outsetp); - outnum++; } } // VISITORS - virtual void visit(AstNetlist* nodep) override { iterateChildren(nodep); } + virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } virtual void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); VL_RESTORER(m_modTables); @@ -425,16 +398,14 @@ private: UINFO(4, " ALWAYS " << nodep << endl); if (treeTest(nodep)) { // Well, then, I'll be a memory hog. - VL_DO_DANGLING(createTable(nodep), nodep); + replaceWithTable(nodep); } } - virtual void visit(AstAssignAlias*) override {} - virtual void visit(AstAssignW* nodep) override { + virtual void visit(AstNodeAssign* nodep) override { // It's nearly impossible to have a large enough assign to make this worthwhile // For now we won't bother. // Accelerated: no iterate } - virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 8b8605430..041a2705c 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -439,7 +439,7 @@ static void process() { // Make all math operations either 8, 16, 32 or 64 bits V3Clean::cleanAll(v3Global.rootp()); - // Move wide constants to BLOCK temps. + // Move wide constants to BLOCK temps / ConstPool. V3Premit::premitAll(v3Global.rootp()); } @@ -499,6 +499,7 @@ static void process() { // emitcInlines is first, as it may set needHInlines which other emitters read V3EmitC::emitcInlines(); V3EmitC::emitcSyms(); + V3EmitC::emitcConstPool(); V3EmitC::emitcTrace(); } else if (v3Global.opt.dpiHdrOnly()) { V3EmitC::emitcSyms(true); diff --git a/test_regress/t/t_extract_static_const.pl b/test_regress/t/t_extract_static_const.pl index 63299d45f..1f112f083 100755 --- a/test_regress/t/t_extract_static_const.pl +++ b/test_regress/t/t_extract_static_const.pl @@ -19,12 +19,11 @@ execute( expect_filename => $Self->{golden_filename}, ); -if ($Self->{vlt}) { - # Note, with vltmt this might be split differently, so only checking vlt - file_grep($Self->{stats}, qr/Optimizations, Prelim static constants extracted\s+(\d+)/i, +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Prelim extracted value to ConstPool\s+(\d+)/i, + 8); + file_grep($Self->{stats}, qr/ConstPool, Constants emitted\s+(\d+)/i, 1); - file_grep($Self->{stats}, qr/Optimizations, Prelim static constants reused\s+(\d+)/i, - 7); } ok(1); diff --git a/test_regress/t/t_extract_static_const.v b/test_regress/t/t_extract_static_const.v index 9cda8a26c..2a49caced 100755 --- a/test_regress/t/t_extract_static_const.v +++ b/test_regress/t/t_extract_static_const.v @@ -6,25 +6,35 @@ module t (/*AUTOARG*/); - wire [255:0] C = {32'h1111_1111, - 32'h2222_2222, - 32'h3333_3333, - 32'h4444_4444, - 32'h5555_5555, - 32'h6666_6666, - 32'h7777_7777, - 32'h8888_8888}; + wire bit [255:0] C = {32'h1111_1111, + 32'h2222_2222, + 32'h3333_3333, + 32'h4444_4444, + 32'h5555_5555, + 32'h6666_6666, + 32'h7777_7777, + 32'h8888_8888}; + + // Same values as above, but with different type + wire logic [255:0] D = {32'h1111_1111, + 32'h2222_2222, + 32'h3333_3333, + 32'h4444_4444, + 32'h5555_5555, + 32'h6666_6666, + 32'h7777_7777, + 32'h8888_8888}; initial begin // Note: Base index via $c to prevent optimizatoin by Verilator $display("0x%32x", C[$c(0*32)+:32]); - $display("0x%32x", C[$c(1*32)+:32]); + $display("0x%32x", D[$c(1*32)+:32]); $display("0x%32x", C[$c(2*32)+:32]); - $display("0x%32x", C[$c(3*32)+:32]); + $display("0x%32x", D[$c(3*32)+:32]); $display("0x%32x", C[$c(4*32)+:32]); - $display("0x%32x", C[$c(5*32)+:32]); + $display("0x%32x", D[$c(5*32)+:32]); $display("0x%32x", C[$c(6*32)+:32]); - $display("0x%32x", C[$c(7*32)+:32]); + $display("0x%32x", D[$c(7*32)+:32]); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_extract_static_const_multimodule.pl b/test_regress/t/t_extract_static_const_multimodule.pl index 6767e6bcc..1f112f083 100755 --- a/test_regress/t/t_extract_static_const_multimodule.pl +++ b/test_regress/t/t_extract_static_const_multimodule.pl @@ -19,12 +19,11 @@ execute( expect_filename => $Self->{golden_filename}, ); -if ($Self->{vlt}) { - # Note, with vltmt this might be split differently, so only checking vlt - file_grep($Self->{stats}, qr/Optimizations, Prelim static constants extracted\s+(\d+)/i, - 2); - file_grep($Self->{stats}, qr/Optimizations, Prelim static constants reused\s+(\d+)/i, - 6); +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Prelim extracted value to ConstPool\s+(\d+)/i, + 8); + file_grep($Self->{stats}, qr/ConstPool, Constants emitted\s+(\d+)/i, + 1); } ok(1); diff --git a/test_regress/t/t_extract_static_const_no_merge.pl b/test_regress/t/t_extract_static_const_no_merge.pl new file mode 100755 index 000000000..ff9a694d4 --- /dev/null +++ b/test_regress/t/t_extract_static_const_no_merge.pl @@ -0,0 +1,33 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt_all => 1); + +top_filename("t/t_extract_static_const.v"); +golden_filename("t/t_extract_static_const.out"); + +compile( + verilator_flags2 => ["--stats", "--no-merge-const-pool"], + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Prelim extracted value to ConstPool\s+(\d+)/i, + 8); + file_grep($Self->{stats}, qr/ConstPool, Constants emitted\s+(\d+)/i, + 2); +} + +ok(1); +1; diff --git a/test_regress/t/t_opt_table_enum.out b/test_regress/t/t_opt_table_enum.out new file mode 100644 index 000000000..40821f577 --- /dev/null +++ b/test_regress/t/t_opt_table_enum.out @@ -0,0 +1,9 @@ +cyle 0 = 0 +cyle 1 = 1 +cyle 2 = 2 +cyle 3 = 99 +cyle 4 = 4 +cyle 5 = 5 +cyle 6 = 99 +cyle 7 = 99 +*-* All Finished *-* diff --git a/test_regress/t/t_opt_table_enum.pl b/test_regress/t/t_opt_table_enum.pl new file mode 100755 index 000000000..a3f38c7f6 --- /dev/null +++ b/test_regress/t/t_opt_table_enum.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 1); + file_grep($Self->{stats}, qr/ConstPool, Tables emitted\s+(\d+)/i, 1); +} + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_opt_table_enum.v b/test_regress/t/t_opt_table_enum.v new file mode 100644 index 000000000..538cf3bea --- /dev/null +++ b/test_regress/t/t_opt_table_enum.v @@ -0,0 +1,45 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2021 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + enum { + CASE_0 = 0, + CASE_1 = 1, + CASE_2 = 2, + CASE_4 = 4, + CASE_5 = 5, + DEFAULT = 99 + } e; + + reg [2:0] cyc; + + initial cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + always @* begin + case (cyc) + 3'b000: e = CASE_0; + 3'b001: e = CASE_1; + 3'b010: e = CASE_2; + 3'b100: e = CASE_4; + 3'b101: e = CASE_5; + default: e = DEFAULT; + endcase + end + + always @(posedge clk) begin + $display("cyle %d = %d", cyc, e); + if (cyc == 7) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_table_packed_array.out b/test_regress/t/t_opt_table_packed_array.out new file mode 100644 index 000000000..af1c46563 --- /dev/null +++ b/test_regress/t/t_opt_table_packed_array.out @@ -0,0 +1,9 @@ +cyle 0 = { 3, 2, 1, 0 } +cyle 1 = { 4, 3, 2, 1 } +cyle 2 = { 5, 4, 3, 4 } +cyle 3 = { 15, 15, 15, 15 } +cyle 4 = { 7, 6, 5, 4 } +cyle 5 = { 8, 7, 6, 5 } +cyle 6 = { 15, 15, 15, 15 } +cyle 7 = { 15, 15, 15, 15 } +*-* All Finished *-* diff --git a/test_regress/t/t_opt_table_packed_array.pl b/test_regress/t/t_opt_table_packed_array.pl new file mode 100755 index 000000000..a3f38c7f6 --- /dev/null +++ b/test_regress/t/t_opt_table_packed_array.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 1); + file_grep($Self->{stats}, qr/ConstPool, Tables emitted\s+(\d+)/i, 1); +} + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_opt_table_packed_array.v b/test_regress/t/t_opt_table_packed_array.v new file mode 100644 index 000000000..9cd2d5508 --- /dev/null +++ b/test_regress/t/t_opt_table_packed_array.v @@ -0,0 +1,38 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2021 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic [3:0][3:0] a; + + reg [2:0] cyc; + + initial cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + always @* begin + case (cyc) + 3'b000: a = {4'd0, 4'd1, 4'd2, 4'd3}; + 3'b001: a = {4'd1, 4'd2, 4'd3, 4'd4}; + 3'b010: a = {4'd4, 4'd3, 4'd4, 4'd5}; + 3'b100: a = {4'd4, 4'd5, 4'd6, 4'd7}; + 3'b101: a = {4'd5, 4'd6, 4'd7, 4'd8}; + default: a = {4{4'hf}}; + endcase + end + + always @(posedge clk) begin + $display("cyle %d = { %d, %d, %d, %d }", cyc, a[0], a[1], a[2], a[3]); + if (cyc == 7) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_table_same.out b/test_regress/t/t_opt_table_same.out new file mode 100644 index 000000000..60045dcc7 --- /dev/null +++ b/test_regress/t/t_opt_table_same.out @@ -0,0 +1,9 @@ +cyle 0 = 0 0 +cyle 1 = 1 1 +cyle 2 = 2 2 +cyle 3 = 99 99 +cyle 4 = 4 4 +cyle 5 = 5 5 +cyle 6 = 99 99 +cyle 7 = 99 99 +*-* All Finished *-* diff --git a/test_regress/t/t_opt_table_same.pl b/test_regress/t/t_opt_table_same.pl new file mode 100755 index 000000000..2fc8ef4eb --- /dev/null +++ b/test_regress/t/t_opt_table_same.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 2); + file_grep($Self->{stats}, qr/ConstPool, Tables emitted\s+(\d+)/i, 1); +} + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_opt_table_same.v b/test_regress/t/t_opt_table_same.v new file mode 100644 index 000000000..c2317dce0 --- /dev/null +++ b/test_regress/t/t_opt_table_same.v @@ -0,0 +1,51 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2021 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + int i; + int j; + + reg [2:0] cyc; + + initial cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + always @* begin + case (cyc) + 3'b000: i = 0; + 3'b001: i = 1; + 3'b010: i = 2; + 3'b100: i = 4; + 3'b101: i = 5; + default: i = 99; + endcase + end + + // Equivalent to above + always @* begin + case (cyc) + 3'b101: j = 5; + 3'b100: j = 4; + 3'b010: j = 2; + 3'b001: j = 1; + 3'b000: j = 0; + default: j = 99; + endcase + end + + always @(posedge clk) begin + $display("cyle %d = %d %d", cyc, i, j); + if (cyc == 7) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_table_signed.out b/test_regress/t/t_opt_table_signed.out new file mode 100644 index 000000000..ab242a34a --- /dev/null +++ b/test_regress/t/t_opt_table_signed.out @@ -0,0 +1,9 @@ +cyle 0 = 0 +cyle 1 = -1 +cyle 2 = 2 +cyle 3 = -2147483648 +cyle 4 = -4 +cyle 5 = 5 +cyle 6 = -2147483648 +cyle 7 = -2147483648 +*-* All Finished *-* diff --git a/test_regress/t/t_opt_table_signed.pl b/test_regress/t/t_opt_table_signed.pl new file mode 100755 index 000000000..a3f38c7f6 --- /dev/null +++ b/test_regress/t/t_opt_table_signed.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 1); + file_grep($Self->{stats}, qr/ConstPool, Tables emitted\s+(\d+)/i, 1); +} + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_opt_table_signed.v b/test_regress/t/t_opt_table_signed.v new file mode 100644 index 000000000..8dbd80a98 --- /dev/null +++ b/test_regress/t/t_opt_table_signed.v @@ -0,0 +1,38 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2021 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + int i; + + reg [2:0] cyc; + + initial cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + always @* begin + case (cyc) + 3'b000: i = 0; + 3'b001: i = -1; + 3'b010: i = 2; + 3'b100: i = -4; + 3'b101: i = 5; + default: i = -1 << 31; + endcase + end + + always @(posedge clk) begin + $display("cyle %d = %d", cyc, i); + if (cyc == 7) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_table_sparse.out b/test_regress/t/t_opt_table_sparse.out new file mode 100644 index 000000000..c0e089a05 --- /dev/null +++ b/test_regress/t/t_opt_table_sparse.out @@ -0,0 +1,9 @@ +cyle 0 = 0 +cyle 1 = 1 +cyle 2 = 1 +cyle 3 = 99 +cyle 4 = 4 +cyle 5 = 5 +cyle 6 = 99 +cyle 7 = 99 +*-* All Finished *-* diff --git a/test_regress/t/t_opt_table_sparse.pl b/test_regress/t/t_opt_table_sparse.pl new file mode 100755 index 000000000..7a079bee2 --- /dev/null +++ b/test_regress/t/t_opt_table_sparse.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 1); + file_grep($Self->{stats}, qr/ConstPool, Tables emitted\s+(\d+)/i, 2); +} + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_opt_table_sparse.v b/test_regress/t/t_opt_table_sparse.v new file mode 100644 index 000000000..3145d3c19 --- /dev/null +++ b/test_regress/t/t_opt_table_sparse.v @@ -0,0 +1,40 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2021 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + int i; + + reg [2:0] cyc; + + initial cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + /* verilator lint_off LATCH */ + always @* begin + case (cyc) + 3'b000: i = 0; + 3'b001: i = 1; + 3'b010: ; // unset + 3'b100: i = 4; + 3'b101: i = 5; + default: i = 99; + endcase + end + /* verilator lint_on LATCH */ + + always @(posedge clk) begin + $display("cyle %d = %d", cyc, i); + if (cyc == 7) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_table_sparse_output_split.pl b/test_regress/t/t_opt_table_sparse_output_split.pl new file mode 100755 index 000000000..a88e82fbd --- /dev/null +++ b/test_regress/t/t_opt_table_sparse_output_split.pl @@ -0,0 +1,47 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +top_filename("t/t_opt_table_sparse.v"); +golden_filename("t/t_opt_table_sparse.out"); + +compile( + verilator_flags2 => ["--stats", "--output-split 1"], + ); + +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 1); + file_grep($Self->{stats}, qr/ConstPool, Tables emitted\s+(\d+)/i, 2); +} + +# Splitting should set VM_PARALLEL_BUILDS to 1 by default +file_grep("$Self->{obj_dir}/$Self->{VM_PREFIX}_classes.mk", qr/VM_PARALLEL_BUILDS\s*=\s*1/); + +check_splits(2); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; + +sub check_splits { + my $expected = shift; + my $n; + foreach my $file (glob("$Self->{obj_dir}/*.cpp")) { + if ($file =~ /__ConstPool_/) { + $n += 1; + } + } + $n == $expected or error("__ConstPool*.cpp not split: $n"); +} diff --git a/test_regress/t/t_opt_table_string.out b/test_regress/t/t_opt_table_string.out new file mode 100644 index 000000000..08927201a --- /dev/null +++ b/test_regress/t/t_opt_table_string.out @@ -0,0 +1,9 @@ +cyle 0 = case-0 +cyle 1 = case-1 +cyle 2 = case-2 +cyle 3 = default +cyle 4 = case-4 +cyle 5 = case-5 +cyle 6 = default +cyle 7 = default +*-* All Finished *-* diff --git a/test_regress/t/t_opt_table_string.pl b/test_regress/t/t_opt_table_string.pl new file mode 100755 index 000000000..a3f38c7f6 --- /dev/null +++ b/test_regress/t/t_opt_table_string.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 1); + file_grep($Self->{stats}, qr/ConstPool, Tables emitted\s+(\d+)/i, 1); +} + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_opt_table_string.v b/test_regress/t/t_opt_table_string.v new file mode 100644 index 000000000..11c6999ea --- /dev/null +++ b/test_regress/t/t_opt_table_string.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2021 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + string s; + reg [2:0] cyc; + + initial cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + always @* begin + case (cyc) + 3'b000: s = "case-0"; + 3'b001: s = "case-1"; + 3'b010: s = "case-2"; + 3'b100: s = "case-4"; + 3'b101: s = "case-5"; + default: s = "default"; + endcase + end + + always @(posedge clk) begin + $display("cyle %d = %s", cyc, s); + if (cyc == 7) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_table_struct.out b/test_regress/t/t_opt_table_struct.out new file mode 100644 index 000000000..c6a0b57b7 --- /dev/null +++ b/test_regress/t/t_opt_table_struct.out @@ -0,0 +1,9 @@ +cyle 0 = { 0, 1, 2 } +cyle 1 = { 1, 2, 3 } +cyle 2 = { 2, 3, 4 } +cyle 3 = { 0, 0, 0 } +cyle 4 = { 4, 5, 6 } +cyle 5 = { 5, 6, 7 } +cyle 6 = { 0, 0, 0 } +cyle 7 = { 0, 0, 0 } +*-* All Finished *-* diff --git a/test_regress/t/t_opt_table_struct.pl b/test_regress/t/t_opt_table_struct.pl new file mode 100755 index 000000000..a3f38c7f6 --- /dev/null +++ b/test_regress/t/t_opt_table_struct.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +if ($Self->{vlt_all}) { + file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 1); + file_grep($Self->{stats}, qr/ConstPool, Tables emitted\s+(\d+)/i, 1); +} + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_opt_table_struct.v b/test_regress/t/t_opt_table_struct.v new file mode 100644 index 000000000..f27f1eef6 --- /dev/null +++ b/test_regress/t/t_opt_table_struct.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2021 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + struct packed { + bit [31:0] a; + bit [15:0] b; + bit [ 7:0] c; + } s; + + reg [2:0] cyc; + + initial cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + always @* begin + case (cyc) + 3'b000: s = {32'd0, 16'd1, 8'd2}; + 3'b001: s = {32'd1, 16'd2, 8'd3}; + 3'b010: s = {32'd2, 16'd3, 8'd4}; + 3'b100: s = {32'd4, 16'd5, 8'd6}; + 3'b101: s = {32'd5, 16'd6, 8'd7}; + default: s = '0; + endcase + end + + always @(posedge clk) begin + $display("cyle %d = { %d, %d, %d }", cyc, s.a, s.b, s.c); + if (cyc == 7) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule