Optimize large lookup tables to static data (#2926)

Implements #2925
This commit is contained in:
Geza Lore 2021-05-08 20:04:56 +01:00 committed by GitHub
parent 37d68d39c8
commit f6c0108c86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 300 additions and 36 deletions

View File

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

View File

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

View File

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

View File

@ -30,17 +30,21 @@
#include "V3Global.h"
#include "V3Premit.h"
#include "V3Ast.h"
#include "V3Hashed.h"
#include "V3Stats.h"
#include <algorithm>
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);
}
};
//----------------------------------------------------------------------

View File

@ -0,0 +1,9 @@
0x88888888
0x77777777
0x66666666
0x55555555
0x44444444
0x33333333
0x22222222
0x11111111
*-* All Finished *-*

View File

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

View File

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

View File

@ -0,0 +1,11 @@
0x88888888
0x66666666
0x44444444
0x22222222
0x1111111122222222333333334444444455555555666666667777777788888888
0x77777777
0x55555555
0x33333333
0x11111111
0x1111111122222222333333334444444455555555666666667777777788888888
*-* All Finished *-*

View File

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

View File

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