diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 6c60718b2..f2ef8b557 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -1267,6 +1267,8 @@ Summary: Creates a dump file with statistics on the design in :file:`__stats.txt`. + Also dumps DFG patterns to + :file:`__stats_dfg_patterns__*.txt`. .. option:: --stats-vars diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index f209351a9..12bbe3b62 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -25,6 +25,8 @@ #include "V3AstUserAllocator.h" #include "V3Dfg.h" #include "V3DfgPasses.h" +#include "V3DfgPatternStats.h" +#include "V3File.h" #include "V3Graph.h" #include "V3UniqueNames.h" @@ -252,6 +254,8 @@ void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) { V3DfgOptimizationContext ctx{label}; + V3DfgPatternStats patternStats; + // Run the optimization phase for (AstNode* nodep = netlistp->modulesp(); nodep; nodep = nodep->nextp()) { // Only optimize proper modules @@ -295,10 +299,34 @@ void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) { dfg->addGraph(*component); } + // Accumulate patterns from the optimized graph for reporting + if (v3Global.opt.stats()) patternStats.accumulate(*dfg); + // Convert back to Ast if (dumpDfgLevel() >= 8) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-optimized"); AstModule* const resultModp = V3DfgPasses::dfgToAst(*dfg, ctx); UASSERT_OBJ(resultModp == modp, modp, "Should be the same module"); } + + // Print the collected patterns + if (v3Global.opt.stats()) { + // Label to lowercase, without spaces + std::string ident = label; + std::transform(ident.begin(), ident.end(), ident.begin(), [](unsigned char c) { // + return c == ' ' ? '_' : std::tolower(c); + }); + + // File to dump to + const std::string filename = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + + "__stats_dfg_patterns__" + ident + ".txt"; + + // Open, write, close + std::ofstream* const ofp = V3File::new_ofstream(filename); + if (ofp->fail()) v3fatal("Can't write " << filename); + patternStats.dump(label, *ofp); + ofp->close(); + VL_DO_DANGLING(delete ofp, ofp); + } + V3Global::dumpCheckGlobalTree("dfg-optimize", 0, dumpTreeEitherLevel() >= 3); } diff --git a/src/V3DfgPatternStats.h b/src/V3DfgPatternStats.h new file mode 100644 index 000000000..595823dce --- /dev/null +++ b/src/V3DfgPatternStats.h @@ -0,0 +1,197 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Implementations of simple passes over DfgGraph +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2024 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 "V3PchAstNoMT.h" + +#include "V3Dfg.h" +#include "V3DfgPasses.h" + +#include +#include +#include + +class V3DfgPatternStats final { + static constexpr uint32_t MIN_PATTERN_DEPTH = 1; + static constexpr uint32_t MAX_PATTERN_DEPTH = 4; + + std::map m_internedConsts; // Interned constants + std::map m_internedVars; // Interned variables + std::map m_internedSelLsbs; // Interned lsb value for selects + std::map m_internedWordWidths; // Interned widths + std::map m_internedWideWidths; // Interned widths + std::map m_internedVertices; // Interned vertices + + // Maps from pattern to the number of times it appears, for each pattern depth + std::vector> m_patterCounts{MAX_PATTERN_DEPTH + 1}; + + static std::string toLetters(size_t value, bool lowerCase = false) { + const char base = lowerCase ? 'a' : 'A'; + std::string s; + do { s += static_cast(base + value % 26); } while (value /= 26); + return s; + } + + const std::string& internConst(const DfgConst& vtx) { + const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "c"); + if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1); + return pair.first->second; + } + + const std::string& internVar(const DfgVertexVar& vtx) { + const auto pair = m_internedVars.emplace(vtx.varp(), "v"); + if (pair.second) pair.first->second += toLetters(m_internedVars.size() - 1); + return pair.first->second; + } + + const std::string& internSelLsb(uint32_t value) { + const auto pair = m_internedSelLsbs.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1); + return pair.first->second; + } + + const std::string& internWordWidth(uint32_t value) { + const auto pair = m_internedWordWidths.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true); + return pair.first->second; + } + + const std::string& internWideWidth(uint32_t value) { + const auto pair = m_internedWideWidths.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1); + return pair.first->second; + } + + const std::string& internVertex(const DfgVertex& vtx) { + const auto pair = m_internedVertices.emplace(&vtx, "_"); + if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1); + return pair.first->second; + } + + // Render the vertx into ss, and return true if the recursion reached the given depth, + // meaning an S-expression with that nesting level has been rendered. + bool render(std::ostringstream& ss, const DfgVertex& vtx, uint32_t depth) { + bool deep = depth == 0; + + if (const DfgConst* const constp = vtx.cast()) { + // Base case 1: constant + if (constp->isZero()) { + ss << "'0"; + } else if (constp->isOnes()) { + ss << "'1"; + } else { + ss << internConst(*constp); + } + } else if (const DfgVertexVar* const varp = vtx.cast()) { + // Base case 2: variable + ss << internVar(*varp); + } else if (depth == 0) { + // Base case 3: deep vertex + ss << internVertex(vtx); + } else { + // Recursively print an S-expression for the vertex + + // S-expression begin + ss << '('; + // Name + ss << vtx.typeName(); + // Specials + if (const DfgSel* const selp = vtx.cast()) { + ss << '@'; + if (selp->lsb() == 0) { + ss << '0'; + } else { + ss << internSelLsb(selp->lsb()); + } + } + + // Operands + vtx.forEachSource([&](const DfgVertex& src) { + ss << ' '; + if (render(ss, src, depth - 1)) deep = true; + }); + // S-expression end + ss << ')'; + + // Mark it if it has multiple sinks + if (vtx.hasMultipleSinks()) ss << '*'; + } + + // Annotate type + ss << ':'; + const AstNodeDType* const dtypep = vtx.dtypep(); + if (!VN_IS(dtypep, BasicDType)) { + dtypep->dumpSmall(ss); + } else { + const uint32_t width = dtypep->width(); + if (width == 1) { + ss << '1'; + } else if (width <= VL_QUADSIZE) { + ss << internWordWidth(width); + } else { + ss << internWideWidth(width); + } + } + + // Done + return deep; + } + +public: + V3DfgPatternStats() = default; + + void accumulate(const DfgGraph& dfg) { + dfg.forEachVertex([&](const DfgVertex& vtx) { + for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { + std::ostringstream ss; + if (render(ss, vtx, i)) m_patterCounts[i][ss.str()] += 1; + m_internedConsts.clear(); + m_internedVars.clear(); + m_internedSelLsbs.clear(); + m_internedWordWidths.clear(); + m_internedWideWidths.clear(); + m_internedVertices.clear(); + } + }); + } + + void dump(const std::string& stage, std::ostream& os) { + using Line = std::pair; + for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { + os << "DFG '" << stage << "' patterns with depth " << i << '\n'; + + // Pick up pattern accumulators with given depth + const auto& patternCounts = m_patterCounts[i]; + + // Sort patterns, first by descending frequency, then lexically + std::vector lines; + lines.reserve(patternCounts.size()); + for (const auto& pair : patternCounts) lines.emplace_back(pair); + std::sort(lines.begin(), lines.end(), [](const Line& a, const Line& b) { + if (a.second != b.second) return a.second > b.second; + return a.first < b.first; + }); + + // Print each pattern + for (const auto& line : lines) { + os << ' ' << std::setw(12) << std::right << line.second; + os << ' ' << std::left << line.first << '\n'; + } + + // Trailing new-line to separate sections + os << '\n'; + } + } +}; diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 13cac8ba3..1388a27b8 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -279,7 +279,7 @@ static void process() { if (v3Global.opt.fDfgPreInline()) { // Pre inline DFG optimization - V3DfgOptimizer::optimize(v3Global.rootp(), " pre inline"); + V3DfgOptimizer::optimize(v3Global.rootp(), "pre inline"); } if (!(v3Global.opt.serializeOnly() && !v3Global.opt.flatten())) { diff --git a/test_regress/t/t_dfg_stats_patterns.v b/test_regress/t/t_dfg_stats_patterns.v new file mode 100644 index 000000000..3494a4dda --- /dev/null +++ b/test_regress/t/t_dfg_stats_patterns.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, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + + +module t ( + input wire [3:0] a, + input wire [3:0] b, + input wire [3:0] c, + output wire [3:0] x, + output wire [3:0] y, + output wire [3:0] z, + output wire [ 0:0] w1, + output wire [ 7:0] w8, + output wire [15:0] w16, + output wire [31:0] w32, + output wire [63:0] w64a, + output wire [63:0] w64b, + output wire [62:0] w63, + output wire [95:0] w96 +); + + assign x = ~a & ~b; + assign y = ~b & ~c; + assign z = ~c & ~a; + assign w1 = x[0]; + assign w8 = {8{x[1]}}; + assign w16 = {2{w8}}; + assign w32 = {2{w16}}; + assign w64a = {2{w32}}; + assign w64b = {2{~w32}}; + assign w63 = 63'({2{~w32}}); + assign w96 = 96'(w64a); + +endmodule diff --git a/test_regress/t/t_dfg_stats_patterns_post_inline.out b/test_regress/t/t_dfg_stats_patterns_post_inline.out new file mode 100644 index 000000000..10b836e67 --- /dev/null +++ b/test_regress/t/t_dfg_stats_patterns_post_inline.out @@ -0,0 +1,50 @@ +DFG 'post inline' patterns with depth 1 + 3 (NOT vA:a)*:a + 2 (AND _A:a _B:a):a + 2 (REPLICATE _A:a cA:a)*:b + 1 (AND _A:a _B:a)*:a + 1 (CONCAT '0:a _A:b):A + 1 (NOT _A:a):a + 1 (REPLICATE _A:1 cA:a)*:b + 1 (REPLICATE _A:a cA:b)*:b + 1 (REPLICATE _A:a cA:b)*:c + 1 (SEL@0 _A:a):1 + 1 (SEL@0 _A:a):b + 1 (SEL@A _A:a):1 + +DFG 'post inline' patterns with depth 2 + 2 (AND (NOT vA:a)*:a (NOT vB:a)*:a):a + 1 (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a + 1 (CONCAT '0:a (REPLICATE _A:a cA:a)*:b):A + 1 (NOT (REPLICATE _A:a cA:b)*:b):b + 1 (REPLICATE (NOT _A:a):a cA:a)*:b + 1 (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c + 1 (REPLICATE (REPLICATE _A:a cA:b)*:b cA:b)*:c + 1 (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b + 1 (REPLICATE (SEL@A _A:a):1 cA:b)*:c + 1 (SEL@0 (AND _A:a _B:a)*:a):1 + 1 (SEL@0 (REPLICATE _A:a cA:a)*:b):c + 1 (SEL@A (AND _A:a _B:a)*:a):1 + +DFG 'post inline' patterns with depth 3 + 1 (CONCAT '0:a (REPLICATE (REPLICATE _A:b cA:a)*:a cA:a)*:c):A + 1 (NOT (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b):b + 1 (REPLICATE (NOT (REPLICATE _A:a cA:b)*:b):b cA:b)*:c + 1 (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a + 1 (REPLICATE (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b cA:b)*:d + 1 (REPLICATE (REPLICATE (SEL@A _A:a):1 cA:b)*:c cB:b)*:d + 1 (REPLICATE (SEL@A (AND _A:a _B:a)*:a):1 cA:b)*:c + 1 (SEL@0 (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1 + 1 (SEL@0 (REPLICATE (NOT _A:a):a cA:a)*:b):c + 1 (SEL@A (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1 + +DFG 'post inline' patterns with depth 4 + 1 (CONCAT '0:a (REPLICATE (REPLICATE (REPLICATE _A:b cA:a)*:c cA:a)*:a cA:a)*:d):A + 1 (NOT (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a):a + 1 (REPLICATE (NOT (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b):b cA:b)*:d + 1 (REPLICATE (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a cB:a)*:d + 1 (REPLICATE (REPLICATE (REPLICATE (SEL@A _A:a):1 cA:b)*:c cB:b)*:d cB:b)*:b + 1 (REPLICATE (REPLICATE (SEL@A (AND _A:a _B:a)*:a):1 cA:b)*:c cB:b)*:d + 1 (REPLICATE (SEL@A (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1 cA:b)*:c + 1 (SEL@0 (REPLICATE (NOT (REPLICATE _A:a cA:b)*:b):b cA:b)*:c):d + diff --git a/test_regress/t/t_dfg_stats_patterns_post_inline.pl b/test_regress/t/t_dfg_stats_patterns_post_inline.pl new file mode 100755 index 000000000..d417bbaee --- /dev/null +++ b/test_regress/t/t_dfg_stats_patterns_post_inline.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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 => 1); + +top_filename("t/t_dfg_stats_patterns.v"); + +compile( + verilator_flags2 => ["--stats --no-skip-identical -fno-dfg-pre-inline"], + ); + +my $f = glob_one("$Self->{obj_dir}/$Self->{vm_prefix}__stats_dfg_patterns*"); +files_identical($f, $Self->{golden_filename}); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_stats_patterns_pre_inline.out b/test_regress/t/t_dfg_stats_patterns_pre_inline.out new file mode 100644 index 000000000..0b817d93c --- /dev/null +++ b/test_regress/t/t_dfg_stats_patterns_pre_inline.out @@ -0,0 +1,50 @@ +DFG 'pre inline' patterns with depth 1 + 3 (NOT vA:a)*:a + 2 (AND _A:a _B:a):a + 2 (REPLICATE _A:a cA:a)*:b + 1 (AND _A:a _B:a)*:a + 1 (CONCAT '0:a _A:b):A + 1 (NOT _A:a):a + 1 (REPLICATE _A:1 cA:a)*:b + 1 (REPLICATE _A:a cA:b)*:b + 1 (REPLICATE _A:a cA:b)*:c + 1 (SEL@0 _A:a):1 + 1 (SEL@0 _A:a):b + 1 (SEL@A _A:a):1 + +DFG 'pre inline' patterns with depth 2 + 2 (AND (NOT vA:a)*:a (NOT vB:a)*:a):a + 1 (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a + 1 (CONCAT '0:a (REPLICATE _A:a cA:a)*:b):A + 1 (NOT (REPLICATE _A:a cA:b)*:b):b + 1 (REPLICATE (NOT _A:a):a cA:a)*:b + 1 (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c + 1 (REPLICATE (REPLICATE _A:a cA:b)*:b cA:b)*:c + 1 (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b + 1 (REPLICATE (SEL@A _A:a):1 cA:b)*:c + 1 (SEL@0 (AND _A:a _B:a)*:a):1 + 1 (SEL@0 (REPLICATE _A:a cA:a)*:b):c + 1 (SEL@A (AND _A:a _B:a)*:a):1 + +DFG 'pre inline' patterns with depth 3 + 1 (CONCAT '0:a (REPLICATE (REPLICATE _A:b cA:a)*:a cA:a)*:c):A + 1 (NOT (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b):b + 1 (REPLICATE (NOT (REPLICATE _A:a cA:b)*:b):b cA:b)*:c + 1 (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a + 1 (REPLICATE (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b cA:b)*:d + 1 (REPLICATE (REPLICATE (SEL@A _A:a):1 cA:b)*:c cB:b)*:d + 1 (REPLICATE (SEL@A (AND _A:a _B:a)*:a):1 cA:b)*:c + 1 (SEL@0 (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1 + 1 (SEL@0 (REPLICATE (NOT _A:a):a cA:a)*:b):c + 1 (SEL@A (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1 + +DFG 'pre inline' patterns with depth 4 + 1 (CONCAT '0:a (REPLICATE (REPLICATE (REPLICATE _A:b cA:a)*:c cA:a)*:a cA:a)*:d):A + 1 (NOT (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a):a + 1 (REPLICATE (NOT (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b):b cA:b)*:d + 1 (REPLICATE (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a cB:a)*:d + 1 (REPLICATE (REPLICATE (REPLICATE (SEL@A _A:a):1 cA:b)*:c cB:b)*:d cB:b)*:b + 1 (REPLICATE (REPLICATE (SEL@A (AND _A:a _B:a)*:a):1 cA:b)*:c cB:b)*:d + 1 (REPLICATE (SEL@A (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1 cA:b)*:c + 1 (SEL@0 (REPLICATE (NOT (REPLICATE _A:a cA:b)*:b):b cA:b)*:c):d + diff --git a/test_regress/t/t_dfg_stats_patterns_pre_inline.pl b/test_regress/t/t_dfg_stats_patterns_pre_inline.pl new file mode 100755 index 000000000..c457e5470 --- /dev/null +++ b/test_regress/t/t_dfg_stats_patterns_pre_inline.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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 => 1); + +top_filename("t/t_dfg_stats_patterns.v"); + +compile( + verilator_flags2 => ["--stats --no-skip-identical -fno-dfg-post-inline"], + ); + +my $f = glob_one("$Self->{obj_dir}/$Self->{vm_prefix}__stats_dfg_patterns*"); +files_identical($f, $Self->{golden_filename}); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_unhandled.pl b/test_regress/t/t_dfg_unhandled.pl index 6364d038e..f37a397d3 100755 --- a/test_regress/t/t_dfg_unhandled.pl +++ b/test_regress/t/t_dfg_unhandled.pl @@ -14,7 +14,7 @@ compile( verilator_flags2 => ["--stats"], ); -file_grep($Self->{stats}, qr/Optimizations, DFG pre inline Ast2Dfg, non-representable \(impure\)\s+(\d+)/i, 1); +file_grep($Self->{stats}, qr/Optimizations, DFG pre inline Ast2Dfg, non-representable \(impure\)\s+(\d+)/i, 1); ok(1); 1;