diff --git a/Changes b/Changes index 30937b5d8..9891e89cb 100644 --- a/Changes +++ b/Changes @@ -36,6 +36,7 @@ Verilator 4.202 2021-04-24 * Add PINNOTFOUND warning in place of error (#2868). [Udi Finkelstein] * Support overlaps in priority case statements (#2864). [Rupert Swarbrick] * Support for null ports (#2875). [Udi Finkelstein] +* Optimize large lookup tables to static data (#2925). [Geza Lore] * Fix class unpacked-array compile error (#2774). [Iru Cai] * Fix scope types in FST and VCD traces (#2805). [Alex Torregrosa] * Fix exceeding command-line ar limit (#2834). [Yinan Xu] diff --git a/src/V3EmitC.cpp b/src/V3EmitC.cpp index c6b6ed359..a8f4de8ea 100644 --- a/src/V3EmitC.cpp +++ b/src/V3EmitC.cpp @@ -1814,13 +1814,18 @@ class EmitCImp final : EmitCStmts { splitSizeInc(1); if (dtypep->isWide()) { // Handle unpacked; not basicp->isWide string out; - if (zeroit) { - out += "VL_ZERO_RESET_W("; + if (varp->valuep()) { + AstConst* const constp = VN_CAST(varp->valuep(), Const); + if (!constp) varp->v3fatalSrc("non-const initializer for variable"); + for (int w = 0; w < varp->widthWords(); ++w) { + out += varp->nameProtect() + suffix + "[" + cvtToStr(w) + "] = "; + out += cvtToStr(constp->num().edataWord(w)) + "U;\n"; + } } else { - out += "VL_RAND_RESET_W("; + out += zeroit ? "VL_ZERO_RESET_W(" : "VL_RAND_RESET_W("; + out += cvtToStr(dtypep->widthMin()); + out += ", " + varp->nameProtect() + suffix + ");\n"; } - out += cvtToStr(dtypep->widthMin()); - out += ", " + varp->nameProtect() + suffix + ");\n"; return out; } else { string out = varp->nameProtect() + suffix; diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 70be0ac19..a5f7491d9 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -1526,11 +1526,8 @@ bool V3Number::isCaseEq(const V3Number& rhs) const { if (isString()) return toString() == rhs.toString(); if (isDouble()) return toDouble() == rhs.toDouble(); if (this->width() != rhs.width()) return false; - - for (int bit = 0; bit < std::max(this->width(), rhs.width()); bit++) { - if (this->bitIs(bit) != rhs.bitIs(bit)) return false; - } - return true; + if (m_value != rhs.m_value) return false; + return m_valueX == rhs.m_valueX; } V3Number& V3Number::opCaseEq(const V3Number& lhs, const V3Number& rhs) { diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 97bb3357f..ebd47c626 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -30,17 +30,21 @@ #include "V3Global.h" #include "V3Premit.h" #include "V3Ast.h" +#include "V3Hashed.h" +#include "V3Stats.h" #include +constexpr int STATIC_CONST_MIN_WIDTH = 256; // Minimum size to extract to static constant + //###################################################################### // Structure for global state class PremitAssignVisitor final : public AstNVisitor { private: // NODE STATE - // AstVar::user4() // bool; occurs on LHS of current assignment - AstUser4InUse m_inuser4; + // AstVar::user3() // bool; occurs on LHS of current assignment + AstUser3InUse m_inuser3; // STATE bool m_noopt = false; // Disable optimization of variables in this block @@ -50,7 +54,7 @@ private: // VISITORS virtual void visit(AstNodeAssign* nodep) override { - // AstNode::user4ClearTree(); // Implied by AstUser4InUse + // AstNode::user3ClearTree(); // Implied by AstUser3InUse // LHS first as fewer varrefs iterateAndNextNull(nodep->lhsp()); // Now find vars marked as lhs @@ -59,9 +63,9 @@ private: virtual void visit(AstVarRef* nodep) override { // it's LHS var is used so need a deep temporary if (nodep->access().isWriteOrRW()) { - nodep->varp()->user4(true); + nodep->varp()->user3(true); } else { - if (nodep->varp()->user4()) { + if (nodep->varp()->user3()) { if (!m_noopt) UINFO(4, "Block has LHS+RHS var: " << nodep << endl); m_noopt = true; } @@ -88,9 +92,11 @@ 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 V3Hashed // STATE AstNodeModule* m_modp = nullptr; // Current module @@ -100,6 +106,11 @@ private: AstTraceInc* m_inTracep = nullptr; // Inside while loop, special statement additions bool m_assignLhs = false; // Inside assignment lhs, don't breakup extracts + V3Hashed m_hashed; // Hash set for static constants that can be reused + + VDouble0 m_staticConstantsExtracted; // Statistic tracking + VDouble0 m_staticConstantsReused; // Statistic tracking + // METHODS VL_DEBUG_FUNC; // Declare debug() @@ -139,14 +150,6 @@ private: } } - AstVar* getBlockTemp(AstNode* nodep) { - string newvarname = (string("__Vtemp") + cvtToStr(m_modp->varNumGetInc())); - AstVar* varp - = new AstVar(nodep->fileline(), AstVarType::STMTTEMP, newvarname, nodep->dtypep()); - m_cfuncp->addInitsp(varp); - return varp; - } - void insertBeforeStmt(AstNode* newp) { // Insert newp before m_stmtp if (m_inWhilep) { @@ -173,28 +176,66 @@ private: AstNRelinker linker; nodep->unlinkFrBack(&linker); - AstVar* varp = getBlockTemp(nodep); + 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 + m_hashed.hash(constp); + const auto& it = m_hashed.findDuplicate(constp); + if (it == m_hashed.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_hashed.hashAndInsert(constp); + nodep->user2p(varp); + ++m_staticConstantsExtracted; + } else { + varp = VN_CAST(it->second->user2p(), Var); + ++m_staticConstantsReused; + } + } else { + // Keep as local temporary + const string newvarname = string("__Vtemp") + cvtToStr(m_modp->varNumGetInc()); + varp + = new AstVar(nodep->fileline(), AstVarType::STMTTEMP, newvarname, 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); - // 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:"); + + 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:"); + } + nodep->user1(true); // Don't add another assignment } // VISITORS virtual void visit(AstNodeModule* nodep) override { UINFO(4, " MOD " << nodep << endl); - VL_RESTORER(m_modp); - { - m_modp = nodep; - m_cfuncp = nullptr; - iterateChildren(nodep); - } + UASSERT_OBJ(m_modp == nullptr, nodep, "Nested modules ?"); + UASSERT_OBJ(m_hashed.mmap().empty(), nodep, "Statements outside module ?"); + m_modp = nodep; + m_cfuncp = nullptr; + iterateChildren(nodep); + m_modp = nullptr; + m_hashed.clear(); } virtual void visit(AstCFunc* nodep) override { VL_RESTORER(m_cfuncp); @@ -401,7 +442,11 @@ private: public: // CONSTRUCTORS explicit PremitVisitor(AstNetlist* nodep) { iterate(nodep); } - virtual ~PremitVisitor() override = default; + virtual ~PremitVisitor() { + V3Stats::addStat("Optimizations, Prelim static constants extracted", + m_staticConstantsExtracted); + V3Stats::addStat("Optimizations, Prelim static constants reused", m_staticConstantsReused); + } }; //---------------------------------------------------------------------- diff --git a/test_regress/t/t_extract_static_const.out b/test_regress/t/t_extract_static_const.out new file mode 100644 index 000000000..6b9780e61 --- /dev/null +++ b/test_regress/t/t_extract_static_const.out @@ -0,0 +1,9 @@ +0x88888888 +0x77777777 +0x66666666 +0x55555555 +0x44444444 +0x33333333 +0x22222222 +0x11111111 +*-* All Finished *-* diff --git a/test_regress/t/t_extract_static_const.pl b/test_regress/t/t_extract_static_const.pl new file mode 100755 index 000000000..63299d45f --- /dev/null +++ b/test_regress/t/t_extract_static_const.pl @@ -0,0 +1,31 @@ +#!/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); + +compile( + verilator_flags2 => ["--stats"], + ); + +execute( + check_finished => 1, + 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, + 1); + file_grep($Self->{stats}, qr/Optimizations, Prelim static constants reused\s+(\d+)/i, + 7); +} + +ok(1); +1; diff --git a/test_regress/t/t_extract_static_const.v b/test_regress/t/t_extract_static_const.v new file mode 100755 index 000000000..9cda8a26c --- /dev/null +++ b/test_regress/t/t_extract_static_const.v @@ -0,0 +1,32 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2020 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +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}; + + 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", C[$c(2*32)+:32]); + $display("0x%32x", C[$c(3*32)+:32]); + $display("0x%32x", C[$c(4*32)+:32]); + $display("0x%32x", C[$c(5*32)+:32]); + $display("0x%32x", C[$c(6*32)+:32]); + $display("0x%32x", C[$c(7*32)+:32]); + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_extract_static_const_multimodule.out b/test_regress/t/t_extract_static_const_multimodule.out new file mode 100644 index 000000000..f739c8e51 --- /dev/null +++ b/test_regress/t/t_extract_static_const_multimodule.out @@ -0,0 +1,11 @@ +0x88888888 +0x66666666 +0x44444444 +0x22222222 +0x1111111122222222333333334444444455555555666666667777777788888888 +0x77777777 +0x55555555 +0x33333333 +0x11111111 +0x1111111122222222333333334444444455555555666666667777777788888888 +*-* All Finished *-* diff --git a/test_regress/t/t_extract_static_const_multimodule.pl b/test_regress/t/t_extract_static_const_multimodule.pl new file mode 100755 index 000000000..6767e6bcc --- /dev/null +++ b/test_regress/t/t_extract_static_const_multimodule.pl @@ -0,0 +1,31 @@ +#!/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); + +compile( + verilator_flags2 => ["--stats"], + ); + +execute( + check_finished => 1, + 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); +} + +ok(1); +1; diff --git a/test_regress/t/t_extract_static_const_multimodule.v b/test_regress/t/t_extract_static_const_multimodule.v new file mode 100755 index 000000000..0f959991a --- /dev/null +++ b/test_regress/t/t_extract_static_const_multimodule.v @@ -0,0 +1,102 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2020 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +// +// Constants should not be shared by different non-inlined modules +// + +module a( + input wire clk, + input wire trig_i, + output reg trig_o + ); + /* verilator no_inline_module */ + + // Same constant as in module b + 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}; + + always @(posedge clk) begin + trig_o <= 1'd0; + if (trig_i) begin + // Note: Base index via $c to prevent optimizatoin by Verilator + $display("0x%32x", C[$c(0*32)+:32]); + $display("0x%32x", C[$c(2*32)+:32]); + $display("0x%32x", C[$c(4*32)+:32]); + $display("0x%32x", C[$c(6*32)+:32]); + $display("0x%256x", C); + trig_o <= 1'd1; + end + end + +endmodule + +module b( + input wire clk, + input wire trig_i, + output reg trig_o + ); + /* verilator no_inline_module */ + + // Same constant as in module a + 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}; + + always @(posedge clk) begin + trig_o <= 1'd0; + if (trig_i) begin + // Note: Base index via $c to prevent optimizatoin by Verilator + $display("0x%32x", C[$c(1*32)+:32]); + $display("0x%32x", C[$c(3*32)+:32]); + $display("0x%32x", C[$c(5*32)+:32]); + $display("0x%32x", C[$c(7*32)+:32]); + $display("0x%256x", C); + trig_o <= 1'd1; + end + end + +endmodule + +module t (/*AUTOARG*/ + // Inputs + clk + ); + + input clk; + + integer cyc = 0; + + reg trig_i; + wire trig_ab; + wire trig_o; + + a a_inst(.clk(clk), .trig_i(trig_i), .trig_o(trig_ab)); + b b_inst(.clk(clk), .trig_i(trig_ab), .trig_o(trig_o)); + + always @(posedge clk) begin + trig_i <= cyc == 1; + + if (trig_o) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + cyc++; + end + +endmodule