Optimize complex combinational logic in DFG (#6298)
This patch adds DfgLogic, which is a vertex that represents a whole, arbitrarily complex combinational AstAlways or AstAssignW in the DfgGraph. Implementing this requires computing the variables live at entry to the AstAlways (variables read by the block), so there is a new ControlFlowGraph data structure and a classical data-flow analysis based live variable analysis to do that at the variable level (as opposed to bit/element level). The actual CFG construction and live variable analysis is best effort, and might fail for currently unhandled constructs or data types. This can be extended later. V3DfgAstToDfg is changed to convert the Ast into an initial DfgGraph containing only DfgLogic, DfgVertexSplice and DfgVertexVar vertices. The DfgLogic are then subsequently synthesized into primitive operations by the new V3DfgSynthesize pass, which is a combination of the old V3DfgAstToDfg conversion and new code to handle AstAlways blocks with complex flow control. V3DfgSynthesize by default will synthesize roughly the same constructs as V3DfgAstToDfg used to handle before, plus any logic that is part of a combinational cycle within the DfgGraph. This enables breaking up these cycles, for which there are extensions to V3DfgBreakCycles in this patch as well. V3DfgSynthesize will then delete all non synthesized or non synthesizable DfgLogic vertices and the rest of the Dfg pipeline is identical, with minor changes to adjust for the changed representation. Because with this change we can now eliminate many more UNOPTFLAT, DFG has been disabled in all the tests that specifically target testing the scheduling and reporting of circular combinational logic.
This commit is contained in:
parent
6a67c0a0e5
commit
636a6b8cd2
|
|
@ -1,5 +1,5 @@
|
|||
.. comment: generated by t_lint_didnotconverge_bad
|
||||
.. code-block::
|
||||
|
||||
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a)
|
||||
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b)
|
||||
%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge.
|
||||
|
|
|
|||
|
|
@ -564,6 +564,10 @@ Summary:
|
|||
Any :code:`$VAR`, :code:`$(VAR)`, or :code:`${VAR}` will be replaced
|
||||
with the specified environment variable.
|
||||
|
||||
.. option:: -fdfg-synthesize-all
|
||||
|
||||
Rarely needed. Attempt to synthesize all combinational logic in DFG.
|
||||
|
||||
.. option:: -FI <file>
|
||||
|
||||
Force include of the specified C++ header file. All generated C++ files
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ set(HEADERS
|
|||
V3CUse.h
|
||||
V3Case.h
|
||||
V3Cast.h
|
||||
V3Cfg.h
|
||||
V3Class.h
|
||||
V3Clean.h
|
||||
V3Clock.h
|
||||
|
|
@ -213,6 +214,9 @@ set(COMMON_SOURCES
|
|||
V3CUse.cpp
|
||||
V3Case.cpp
|
||||
V3Cast.cpp
|
||||
V3Cfg.cpp
|
||||
V3CfgBuilder.cpp
|
||||
V3CfgLiveVariables.cpp
|
||||
V3Class.cpp
|
||||
V3Clean.cpp
|
||||
V3Clock.cpp
|
||||
|
|
@ -238,6 +242,7 @@ set(COMMON_SOURCES
|
|||
V3DfgPasses.cpp
|
||||
V3DfgPeephole.cpp
|
||||
V3DfgRegularize.cpp
|
||||
V3DfgSynthesize.cpp
|
||||
V3DiagSarif.cpp
|
||||
V3DupFinder.cpp
|
||||
V3EmitCBase.cpp
|
||||
|
|
|
|||
|
|
@ -226,6 +226,9 @@ RAW_OBJS_PCH_ASTNOMT = \
|
|||
V3CUse.o \
|
||||
V3Case.o \
|
||||
V3Cast.o \
|
||||
V3Cfg.o \
|
||||
V3CfgBuilder.o \
|
||||
V3CfgLiveVariables.o \
|
||||
V3Class.o \
|
||||
V3Clean.o \
|
||||
V3Clock.o \
|
||||
|
|
@ -249,6 +252,7 @@ RAW_OBJS_PCH_ASTNOMT = \
|
|||
V3DfgPasses.o \
|
||||
V3DfgPeephole.o \
|
||||
V3DfgRegularize.o \
|
||||
V3DfgSynthesize.o \
|
||||
V3DiagSarif.o \
|
||||
V3DupFinder.o \
|
||||
V3EmitCMain.o \
|
||||
|
|
|
|||
|
|
@ -1873,6 +1873,7 @@ class AstVar final : public AstNode {
|
|||
bool m_ignorePostRead : 1; // Ignore reads in 'Post' blocks during ordering
|
||||
bool m_ignorePostWrite : 1; // Ignore writes in 'Post' blocks during ordering
|
||||
bool m_ignoreSchedWrite : 1; // Ignore writes in scheduling (for special optimizations)
|
||||
bool m_dfgMultidriven : 1; // Singal is multidriven, used by DFG to avoid repeat processing
|
||||
|
||||
void init() {
|
||||
m_ansi = false;
|
||||
|
|
@ -1921,6 +1922,7 @@ class AstVar final : public AstNode {
|
|||
m_ignorePostRead = false;
|
||||
m_ignorePostWrite = false;
|
||||
m_ignoreSchedWrite = false;
|
||||
m_dfgMultidriven = false;
|
||||
m_attrClocker = VVarAttrClocker::CLOCKER_UNKNOWN;
|
||||
}
|
||||
|
||||
|
|
@ -2084,6 +2086,8 @@ public:
|
|||
void setIgnorePostWrite() { m_ignorePostWrite = true; }
|
||||
bool ignoreSchedWrite() const { return m_ignoreSchedWrite; }
|
||||
void setIgnoreSchedWrite() { m_ignoreSchedWrite = true; }
|
||||
bool dfgMultidriven() const { return m_dfgMultidriven; }
|
||||
void setDfgMultidriven() { m_dfgMultidriven = true; }
|
||||
|
||||
// METHODS
|
||||
void name(const string& name) override { m_name = name; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Control flow graph (CFG) implementation
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2025 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
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Control flow graph (CFG) implementation
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "V3Cfg.h"
|
||||
|
||||
#include "V3Ast.h"
|
||||
#include "V3EmitV.h"
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
//######################################################################
|
||||
// ControlFlowGraph method definitions
|
||||
|
||||
bool ControlFlowGraph::containsLoop() const {
|
||||
for (const V3GraphVertex& vtx : vertices()) {
|
||||
const BasicBlock& current = static_cast<const BasicBlock&>(vtx);
|
||||
for (const V3GraphEdge& edge : current.outEdges()) {
|
||||
const BasicBlock& successor = *static_cast<const BasicBlock*>(edge.top());
|
||||
// IDs are the reverse post-order numbering, so easy to check for a back-edge
|
||||
if (successor.id() < current.id()) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// BasicBlock method definitions
|
||||
|
||||
std::string BasicBlock::name() const {
|
||||
std::stringstream ss;
|
||||
ss << "BB " + std::to_string(id()) + ":\n";
|
||||
for (AstNode* nodep : m_stmtps) {
|
||||
if (const AstIf* const ifp = VN_CAST(nodep, If)) {
|
||||
ss << "if (";
|
||||
V3EmitV::debugVerilogForTree(ifp->condp(), ss);
|
||||
ss << ") ...";
|
||||
} else if (const AstWhile* const whilep = VN_CAST(nodep, While)) {
|
||||
ss << "while (";
|
||||
V3EmitV::debugVerilogForTree(whilep->condp(), ss);
|
||||
ss << ") ...";
|
||||
} else {
|
||||
V3EmitV::debugVerilogForTree(nodep, ss);
|
||||
}
|
||||
}
|
||||
std::string text = VString::replaceSubstr(ss.str(), "\n", "\\l ");
|
||||
if (inEmpty()) text = "**ENTER**\n" + text;
|
||||
if (outEmpty()) text = text + "\n**EXIT**";
|
||||
return text;
|
||||
}
|
||||
std::string BasicBlock::dotShape() const { return "rect"; }
|
||||
std::string BasicBlock::dotRank() const {
|
||||
if (inEmpty()) return "source";
|
||||
if (outEmpty()) return "sink";
|
||||
return "";
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// ControlFlowEdge method definitions
|
||||
|
||||
std::string ControlFlowEdge::dotLabel() const {
|
||||
std::string label = "E" + std::to_string(id());
|
||||
const BasicBlock& source = *fromp()->as<BasicBlock>();
|
||||
const ControlFlowEdge* const untknp = source.untknEdgep();
|
||||
if (this == untknp) {
|
||||
label += " / F";
|
||||
} else if (untknp) {
|
||||
label += " / T";
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Control flow graph (CFG)
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2025 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
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Control flow graph (CFG) and related data structures.
|
||||
//
|
||||
// A control flow graph is a directed graph with with basic blocks as
|
||||
// vertices connected by control flow edges. A basic block is a sequence
|
||||
// of statements that are always executed as a unit (there are no branches
|
||||
// targeting the middle of a basic block). The last statement in a basic
|
||||
// block is called the terminator statemet. The terminator statements are
|
||||
// the control flow transfer statements (branches) in the program. In this
|
||||
// implementation, an unconditoinal jump (goto) is implicit and not stored
|
||||
// in the basic block. A consequence of the implicit jump is that a basic
|
||||
// block might be empty (not contain any statements). Conditional branches
|
||||
// are represented by storing the corresponding conditional AstNodeStmt as
|
||||
// the last statement in the basic block. Most importantly, only the actual
|
||||
// condition check (e.g.: the 'condp' part of an AstIf) and branch is
|
||||
// executed in the host basic block, but not any of the body statements of
|
||||
// the conditoinal. The control flow graph has 2 unique basic blocks. The
|
||||
// 'enter' block is the unique entry point of the represented program. It
|
||||
// has no predecessors and itself dominates all other basic blocks. The
|
||||
// 'exit' block is the unique exit point. It has no successors, and itself
|
||||
// post-dominates all other basic blocks.
|
||||
//
|
||||
// The current implementation is designed to be immutable after
|
||||
// construction. This can be relaxed in the future if necessary, however
|
||||
// it is unlikely to be necessary as we cannot as of now convert a control
|
||||
// flow graph back to Ast or any other form of output. We can also only
|
||||
// represent 2-way conditionals, therefore all basic blocks have up to 2
|
||||
// successors. The exit block has 0, blocks terminated by an unconditional
|
||||
// implicit jump have exactly 1, and blocks terminated by a conditional
|
||||
// branch have exactly 2 successors.
|
||||
//
|
||||
// Basic blocks have a unique ID, these are assigned based on the reverse
|
||||
// post-ordering of the basic blocks within the control flow graph, and
|
||||
// therefore they define a topological ordering of the basic blocks. The
|
||||
// basic blocks within the graph are stored in this order. This means that
|
||||
// in control flow graphs without back edges (loops), a basic block is
|
||||
// always stored after its predecessors.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#ifndef VERILATOR_V3CFG_H_
|
||||
#define VERILATOR_V3CFG_H_
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "V3Ast.h"
|
||||
#include "V3Graph.h"
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
//######################################################################
|
||||
// DenseMap - This can be made generic if needed
|
||||
|
||||
// Map from unique non-negative integers (IDs) to generic values. As opposed
|
||||
// to a std::map/std::unordered_map, in DenseMap, all entries are value
|
||||
// initialized at construction, and the map is always dense. This can be
|
||||
// improved if necessary but is sufficient for our current purposes.
|
||||
template <typename T_Key, size_t (T_Key::*Index)() const, typename T_Value>
|
||||
class DenseMap final {
|
||||
std::vector<T_Value> m_map; // The map, stored as a vector
|
||||
|
||||
public:
|
||||
// CONSTRUCTOR
|
||||
explicit DenseMap(size_t size)
|
||||
: m_map{size} {}
|
||||
|
||||
T_Value& operator[](const T_Key& key) { return m_map.at((key.*Index)()); }
|
||||
const T_Value& operator[](const T_Key& key) const { return m_map.at((key.*Index)()); }
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// ControlFlowGraph data structure
|
||||
|
||||
class ControlFlowGraph;
|
||||
class BasicBlock;
|
||||
class ControlFlowEdge;
|
||||
|
||||
// A basic block (verticies of the control flow graph)
|
||||
class BasicBlock final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(BasicBlock, V3GraphVertex)
|
||||
friend class CfgBuilder;
|
||||
|
||||
// STATE - Immutable after construction, set by CfgBuilder
|
||||
// V3GraphEdge::user() is set to the unique id by CfgBuilder
|
||||
std::vector<AstNodeStmt*> m_stmtps; // statements in this BasicBlock
|
||||
|
||||
// CONSTRUCTOR - via CfgBuilder only
|
||||
inline explicit BasicBlock(ControlFlowGraph* graphp);
|
||||
~BasicBlock() override = default;
|
||||
|
||||
public:
|
||||
// METHODS - all const
|
||||
|
||||
// Statements in this BasicBlock
|
||||
const std::vector<AstNodeStmt*>& stmtps() const { return m_stmtps; }
|
||||
// Unique ID of this BasicBlock - defines topological ordering
|
||||
size_t id() const { return V3GraphVertex::user(); }
|
||||
|
||||
// The edge corresponding to the terminator branch being taken (including unonditoinal goto)
|
||||
inline const ControlFlowEdge* takenEdgep() const;
|
||||
// The edge corresponding to the terminator branch being not taken (or nullptr if goto)
|
||||
inline const ControlFlowEdge* untknEdgep() const;
|
||||
// Same as takenpEdgep/untknEdgep but returns the successor basic blocks
|
||||
inline const BasicBlock* takenSuccessorp() const;
|
||||
inline const BasicBlock* untknSuccessorp() const;
|
||||
|
||||
void forEachSuccessor(std::function<void(const BasicBlock&)> f) const {
|
||||
for (const V3GraphEdge& edge : outEdges()) f(*edge.top()->as<BasicBlock>());
|
||||
}
|
||||
|
||||
void forEachPredecessor(std::function<void(const BasicBlock&)> f) const {
|
||||
for (const V3GraphEdge& edge : inEdges()) f(*edge.fromp()->as<BasicBlock>());
|
||||
}
|
||||
|
||||
FileLine* fileline() const override {
|
||||
return !m_stmtps.empty() ? m_stmtps.front()->fileline() : nullptr;
|
||||
}
|
||||
|
||||
// For Graphviz dumps only
|
||||
std::string name() const override;
|
||||
std::string dotShape() const override;
|
||||
std::string dotRank() const override;
|
||||
};
|
||||
|
||||
// A control flow graph edge
|
||||
class ControlFlowEdge final : public V3GraphEdge {
|
||||
VL_RTTI_IMPL(ControlFlowEdge, V3GraphEdge)
|
||||
friend class CfgBuilder;
|
||||
|
||||
// STATE - Immutable after construction, set by CfgBuilder
|
||||
// V3GraphEdge::user() is set to the unique id by CfgBuilder
|
||||
|
||||
// CONSTRUCTOR - via CfgBuilder only
|
||||
inline ControlFlowEdge(ControlFlowGraph* graphp, BasicBlock* srcp, BasicBlock* dstp);
|
||||
~ControlFlowEdge() override = default;
|
||||
|
||||
public:
|
||||
// METHODS - all const
|
||||
|
||||
// Unique ID of this ControlFlowEdge - no particular meaning
|
||||
size_t id() const { return user(); }
|
||||
|
||||
// Source/destination BasicBlock
|
||||
const BasicBlock& src() const { return *static_cast<BasicBlock*>(fromp()); }
|
||||
const BasicBlock& dst() const { return *static_cast<BasicBlock*>(top()); }
|
||||
|
||||
// For Graphviz dumps only
|
||||
std::string dotLabel() const override;
|
||||
};
|
||||
|
||||
template <typename T_Value>
|
||||
using BasicBlockMap = DenseMap<BasicBlock, &BasicBlock::id, T_Value>;
|
||||
|
||||
template <typename T_Value>
|
||||
using ControlFlowEdgeMap = DenseMap<ControlFlowEdge, &ControlFlowEdge::id, T_Value>;
|
||||
|
||||
// The control flow graph
|
||||
class ControlFlowGraph final : public V3Graph {
|
||||
friend class CfgBuilder;
|
||||
|
||||
// STATE - Immutable after construction, set by CfgBuilder
|
||||
BasicBlock* m_enterp = nullptr; // The singular entry vertex
|
||||
BasicBlock* m_exitp = nullptr; // The singular exit vertex
|
||||
size_t m_nBasicBlocks = 0; // Number of BasicBlocks in this ControlFlowGraph
|
||||
size_t m_nEdges = 0; // Number of ControlFlowEdges in this ControlFlowGraph
|
||||
|
||||
// CONSTRUCTOR - via CfgBuilder only
|
||||
ControlFlowGraph() = default;
|
||||
|
||||
public:
|
||||
~ControlFlowGraph() override = default;
|
||||
|
||||
// METHODS
|
||||
void foreach(std::function<void(const BasicBlock&)> f) const {
|
||||
for (const V3GraphVertex& vtx : vertices()) f(*vtx.as<BasicBlock>());
|
||||
}
|
||||
|
||||
// Accessors
|
||||
const BasicBlock& enter() const { return *m_enterp; }
|
||||
const BasicBlock& exit() const { return *m_exitp; }
|
||||
|
||||
// Number of basic blocks in this graph
|
||||
size_t nBasicBlocks() const { return m_nBasicBlocks; }
|
||||
// Number of control flow edges in this graph
|
||||
size_t nEdges() const { return m_nEdges; }
|
||||
|
||||
// Create a BasicBlock map for this graph
|
||||
template <typename T_Value>
|
||||
BasicBlockMap<T_Value> makeBasicBlockMap() const {
|
||||
return BasicBlockMap<T_Value>{nBasicBlocks()};
|
||||
}
|
||||
// Create a ControlFlowEdgeMap map for this graph
|
||||
template <typename T_Value>
|
||||
ControlFlowEdgeMap<T_Value> makeEdgeMap() const {
|
||||
return ControlFlowEdgeMap<T_Value>{nEdges()};
|
||||
}
|
||||
|
||||
// Returns true iff the graph contains a loop (back-edge)
|
||||
bool containsLoop() const;
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Inline method definitions
|
||||
|
||||
BasicBlock::BasicBlock(ControlFlowGraph* cfgp)
|
||||
: V3GraphVertex{cfgp} {}
|
||||
|
||||
const ControlFlowEdge* BasicBlock::takenEdgep() const {
|
||||
// It's always the first edge
|
||||
const V3GraphEdge* const frontp = outEdges().frontp();
|
||||
return static_cast<const ControlFlowEdge*>(frontp);
|
||||
}
|
||||
|
||||
const ControlFlowEdge* BasicBlock::untknEdgep() const {
|
||||
// It's always the second (last) edge
|
||||
const V3GraphEdge* const frontp = outEdges().frontp();
|
||||
const V3GraphEdge* const backp = outEdges().backp();
|
||||
return backp != frontp ? static_cast<const ControlFlowEdge*>(backp) : nullptr;
|
||||
}
|
||||
|
||||
const BasicBlock* BasicBlock::takenSuccessorp() const {
|
||||
const ControlFlowEdge* const edgep = takenEdgep();
|
||||
return edgep ? static_cast<const BasicBlock*>(edgep->top()) : nullptr;
|
||||
}
|
||||
|
||||
const BasicBlock* BasicBlock::untknSuccessorp() const {
|
||||
const ControlFlowEdge* const edgep = untknEdgep();
|
||||
return edgep ? static_cast<const BasicBlock*>(edgep->top()) : nullptr;
|
||||
}
|
||||
|
||||
ControlFlowEdge::ControlFlowEdge(ControlFlowGraph* graphp, BasicBlock* srcp, BasicBlock* dstp)
|
||||
: V3GraphEdge{graphp, srcp, dstp, 1, false} {}
|
||||
|
||||
//######################################################################
|
||||
// ControlFlowGraph functions
|
||||
|
||||
namespace V3Cfg {
|
||||
// Build control flow graph for given node
|
||||
std::unique_ptr<const ControlFlowGraph> build(const AstNodeProcedure*);
|
||||
|
||||
// Compute AstVars live on entry to given CFG. That is, variables that might
|
||||
// be read before wholly assigned in the CFG. Returns nullptr if the analysis
|
||||
// failed due to unhandled statements or data types involved in the CFG.
|
||||
// On success, returns a vector of AstVar or AstVarScope nodes live on entry.
|
||||
std::unique_ptr<std::vector<AstVar*>> liveVars(const ControlFlowGraph&);
|
||||
|
||||
// Same as liveVars, but return AstVarScopes insted
|
||||
std::unique_ptr<std::vector<AstVarScope*>> liveVarScopes(const ControlFlowGraph&);
|
||||
} //namespace V3Cfg
|
||||
|
||||
#endif // VERILATOR_V3CFG_H_
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Control flow graph (CFG) builder
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2025 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
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Control flow graph (CFG) builder
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "V3Ast.h"
|
||||
#include "V3Cfg.h"
|
||||
#include "V3EmitV.h"
|
||||
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
class CfgBuilder final : VNVisitorConst {
|
||||
// STATE
|
||||
// The graph being built, or nullptr if failed to build one
|
||||
std::unique_ptr<ControlFlowGraph> m_cfgp{new ControlFlowGraph};
|
||||
// Current basic block to add statements to
|
||||
BasicBlock* m_currBBp = nullptr;
|
||||
// Continuation block for given JumpBlock
|
||||
std::unordered_map<AstJumpBlock*, BasicBlock*> m_jumpBlockContp;
|
||||
|
||||
// METHODS
|
||||
|
||||
// Create new Basicblock block in the CFG
|
||||
BasicBlock& addBasicBlock() { return *new BasicBlock{m_cfgp.get()}; }
|
||||
|
||||
// Create new taken (or unconditional) ControlFlowEdge in the CFG
|
||||
void addTakenEdge(BasicBlock& src, BasicBlock& dst) {
|
||||
UASSERT_OBJ(src.outEmpty(), &src, "Taken edge should be added first");
|
||||
new ControlFlowEdge{m_cfgp.get(), &src, &dst};
|
||||
}
|
||||
|
||||
// Create new untaken ControlFlowEdge in the CFG
|
||||
void addUntknEdge(BasicBlock& src, BasicBlock& dst) {
|
||||
UASSERT_OBJ(src.outSize1(), &src, "Untaken edge shold be added second");
|
||||
UASSERT_OBJ(src.outEdges().frontp()->top() != &dst, &src,
|
||||
"Untaken branch targets the same block as the taken branch");
|
||||
new ControlFlowEdge{m_cfgp.get(), &src, &dst};
|
||||
}
|
||||
|
||||
// Add the given statement to the current BasicBlock
|
||||
void addStmt(AstNodeStmt* nodep) { m_currBBp->m_stmtps.emplace_back(nodep); }
|
||||
|
||||
// Used to handle statements not representable in the CFG
|
||||
void nonRepresentable(AstNodeStmt*) {
|
||||
if (!m_cfgp) return;
|
||||
m_cfgp.reset();
|
||||
}
|
||||
|
||||
// Used to handle simple (non-branching) statements in the CFG
|
||||
void simpleStatement(AstNodeStmt* nodep, bool representable = true) {
|
||||
if (!m_cfgp) return;
|
||||
// If non-representable, reset graph
|
||||
if (!representable) {
|
||||
m_cfgp.reset();
|
||||
return;
|
||||
}
|
||||
// Just add to current block
|
||||
addStmt(nodep);
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
|
||||
// Eventually we should handle all procedural statements, however, what
|
||||
// is a procedural statemen is a bit unclear (#6280), so in the first
|
||||
// instance we will only handle select statemetns that cover the requied
|
||||
// use cases, and in the base case we conservatively assume the statement
|
||||
// is non-representable. More visits can be added case by case if needed.
|
||||
void visit(AstNode* nodep) override {
|
||||
if (!m_cfgp) return;
|
||||
UINFO(9, "Unhandled AstNode type " << nodep->typeName());
|
||||
m_cfgp.reset();
|
||||
}
|
||||
|
||||
// Non-representable statements
|
||||
void visit(AstAssignDly* nodep) override { nonRepresentable(nodep); }
|
||||
void visit(AstCase* nodep) override { nonRepresentable(nodep); } // V3Case will eliminate
|
||||
void visit(AstCReset* nodep) override { nonRepresentable(nodep); }
|
||||
void visit(AstDelay* nodep) override { nonRepresentable(nodep); }
|
||||
|
||||
// Representable non control-flow statements
|
||||
void visit(AstAssign* nodep) override { simpleStatement(nodep, !nodep->timingControlp()); }
|
||||
void visit(AstComment*) override {} // ignore entirely
|
||||
void visit(AstDisplay* nodep) override { simpleStatement(nodep); }
|
||||
void visit(AstFinish* nodep) override { simpleStatement(nodep); }
|
||||
void visit(AstStmtExpr* nodep) override { simpleStatement(nodep); }
|
||||
void visit(AstStop* nodep) override { simpleStatement(nodep); }
|
||||
|
||||
// Representable control flow statements
|
||||
void visit(AstIf* nodep) override {
|
||||
if (!m_cfgp) return;
|
||||
|
||||
// Add terminator statement to current block - semantically the condition check only ...
|
||||
addStmt(nodep);
|
||||
|
||||
// Create then/else/continuation blocks
|
||||
BasicBlock& thenBB = addBasicBlock();
|
||||
BasicBlock& elseBB = addBasicBlock();
|
||||
BasicBlock& contBB = addBasicBlock();
|
||||
addTakenEdge(*m_currBBp, thenBB);
|
||||
addUntknEdge(*m_currBBp, elseBB);
|
||||
|
||||
// Build then branch
|
||||
m_currBBp = &thenBB;
|
||||
iterateAndNextConstNull(nodep->thensp());
|
||||
if (!m_cfgp) return;
|
||||
if (m_currBBp) addTakenEdge(*m_currBBp, contBB);
|
||||
|
||||
// Build else branch
|
||||
m_currBBp = &elseBB;
|
||||
iterateAndNextConstNull(nodep->elsesp());
|
||||
if (!m_cfgp) return;
|
||||
if (m_currBBp) addTakenEdge(*m_currBBp, contBB);
|
||||
|
||||
// Set continuation
|
||||
m_currBBp = &contBB;
|
||||
}
|
||||
void visit(AstWhile* nodep) override {
|
||||
if (!m_cfgp) return;
|
||||
|
||||
// Create the header block
|
||||
BasicBlock& headBB = addBasicBlock();
|
||||
addTakenEdge(*m_currBBp, headBB);
|
||||
|
||||
// The While goes in the header block - semantically the condition check only ...
|
||||
m_currBBp = &headBB;
|
||||
addStmt(nodep);
|
||||
|
||||
// Create the body/continuation blocks
|
||||
BasicBlock& bodyBB = addBasicBlock();
|
||||
BasicBlock& contBB = addBasicBlock();
|
||||
addTakenEdge(headBB, bodyBB);
|
||||
addUntknEdge(headBB, contBB);
|
||||
|
||||
// Build the body
|
||||
m_currBBp = &bodyBB;
|
||||
iterateAndNextConstNull(nodep->stmtsp());
|
||||
iterateAndNextConstNull(nodep->incsp());
|
||||
if (!m_cfgp) return;
|
||||
if (m_currBBp) addTakenEdge(*m_currBBp, headBB);
|
||||
|
||||
// Set continuation
|
||||
m_currBBp = &contBB;
|
||||
}
|
||||
void visit(AstJumpBlock* nodep) override {
|
||||
if (!m_cfgp) return;
|
||||
|
||||
// Don't acutally need to add this 'nodep' to any block - but we could later if needed
|
||||
|
||||
// Create continuation block
|
||||
BasicBlock& contBB = addBasicBlock();
|
||||
const bool newEntry = m_jumpBlockContp.emplace(nodep, &contBB).second;
|
||||
UASSERT_OBJ(newEntry, nodep, "AstJumpBlock visited twice");
|
||||
|
||||
// Build the body
|
||||
iterateAndNextConstNull(nodep->stmtsp());
|
||||
if (!m_cfgp) return;
|
||||
if (m_currBBp) addTakenEdge(*m_currBBp, contBB);
|
||||
|
||||
// Set continuation
|
||||
m_currBBp = &contBB;
|
||||
}
|
||||
void visit(AstJumpGo* nodep) override {
|
||||
if (!m_cfgp) return;
|
||||
|
||||
// Non-representable if not last in statement list (V3Const will fix this later)
|
||||
if (nodep->nextp()) {
|
||||
m_cfgp.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't acutally need to add this 'nodep' to any block - but we could later if needed
|
||||
|
||||
// Make current block go to the continuation of the JumpBlock
|
||||
addTakenEdge(*m_currBBp, *m_jumpBlockContp.at(nodep->blockp()));
|
||||
|
||||
// There should be no statements after a JumpGo!
|
||||
m_currBBp = nullptr;
|
||||
}
|
||||
|
||||
// CONSTRUCTOR
|
||||
explicit CfgBuilder(const AstNodeProcedure* nodep) {
|
||||
// Build the graph, starting from the entry block
|
||||
m_currBBp = &addBasicBlock();
|
||||
m_cfgp->m_enterp = m_currBBp;
|
||||
// Visit each statement to build the control flow graph
|
||||
iterateAndNextConstNull(nodep->stmtsp());
|
||||
if (!m_cfgp) return;
|
||||
// The final block is the exit block
|
||||
m_cfgp->m_exitp = m_currBBp;
|
||||
|
||||
// Remove empty blocks - except enter/exit
|
||||
for (V3GraphVertex* const vtxp : m_cfgp->vertices().unlinkable()) {
|
||||
if (vtxp == &m_cfgp->enter()) continue;
|
||||
if (vtxp == &m_cfgp->exit()) continue;
|
||||
BasicBlock* const bbp = vtxp->as<BasicBlock>();
|
||||
if (!bbp->stmtps().empty()) continue;
|
||||
UASSERT(bbp->outSize1(), "Empty block should have a single successor");
|
||||
BasicBlock* const succp = const_cast<BasicBlock*>(bbp->takenSuccessorp());
|
||||
for (V3GraphEdge* const edgep : bbp->inEdges().unlinkable()) edgep->relinkTop(succp);
|
||||
vtxp->unlinkDelete(m_cfgp.get());
|
||||
}
|
||||
// Remove redundant entry block
|
||||
while (m_cfgp->enter().stmtps().empty() && m_cfgp->enter().outSize1()) {
|
||||
BasicBlock* const succp = m_cfgp->enter().outEdges().frontp()->top()->as<BasicBlock>();
|
||||
if (!succp->inSize1()) break;
|
||||
m_cfgp->m_enterp->unlinkDelete(m_cfgp.get());
|
||||
m_cfgp->m_enterp = succp;
|
||||
}
|
||||
// Remove redundant exit block
|
||||
while (m_cfgp->exit().stmtps().empty() && m_cfgp->exit().inSize1()) {
|
||||
BasicBlock* const prep = m_cfgp->exit().inEdges().frontp()->fromp()->as<BasicBlock>();
|
||||
if (!prep->outSize1()) break;
|
||||
m_cfgp->m_exitp->unlinkDelete(m_cfgp.get());
|
||||
m_cfgp->m_exitp = prep;
|
||||
}
|
||||
|
||||
// Compute reverse post-order enumeration and sort blocks, assign IDs
|
||||
{
|
||||
// Simple recursive algorith will do ...
|
||||
std::vector<BasicBlock*> postOrderEnumeration;
|
||||
std::unordered_set<BasicBlock*> visited;
|
||||
const std::function<void(BasicBlock*)> visitBasicBlock = [&](BasicBlock* bbp) {
|
||||
// Mark and skip if already visited
|
||||
if (!visited.emplace(bbp).second) return;
|
||||
// Visit successors
|
||||
for (const V3GraphEdge& e : bbp->outEdges()) {
|
||||
visitBasicBlock(static_cast<BasicBlock*>(e.top()));
|
||||
}
|
||||
// Add to post order enumeration
|
||||
postOrderEnumeration.emplace_back(bbp);
|
||||
};
|
||||
visitBasicBlock(m_cfgp->m_enterp);
|
||||
const uint32_t n = postOrderEnumeration.size();
|
||||
UASSERT_OBJ(n == m_cfgp->vertices().size(), nodep, "Inconsistent enumeration size");
|
||||
|
||||
// Set size in graph
|
||||
m_cfgp->m_nBasicBlocks = n;
|
||||
|
||||
// Assign ids equal to the reverse post order number and sort vertices
|
||||
for (uint32_t i = 0; i < postOrderEnumeration.size(); ++i) {
|
||||
BasicBlock* const bbp = postOrderEnumeration[n - 1 - i];
|
||||
bbp->user(i);
|
||||
m_cfgp->vertices().unlink(bbp);
|
||||
m_cfgp->vertices().linkBack(bbp);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign IDs to edges
|
||||
{
|
||||
size_t nEdges = 0;
|
||||
for (V3GraphVertex& v : m_cfgp->vertices()) {
|
||||
for (V3GraphEdge& e : v.outEdges()) e.user(nEdges++);
|
||||
}
|
||||
// Set size in graph
|
||||
m_cfgp->m_nEdges = nEdges;
|
||||
}
|
||||
|
||||
if (dumpGraphLevel() >= 9) m_cfgp->dumpDotFilePrefixed("cfgbuilder");
|
||||
}
|
||||
|
||||
public:
|
||||
static std::unique_ptr<ControlFlowGraph> apply(const AstNodeProcedure* nodep) {
|
||||
return std::move(CfgBuilder{nodep}.m_cfgp);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<const ControlFlowGraph> V3Cfg::build(const AstNodeProcedure* nodep) {
|
||||
return CfgBuilder::apply(nodep);
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: CFG liveness analysis
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2025 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
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Classical data flow analysis computing live variables input to a CFG
|
||||
// https://en.wikipedia.org/wiki/Live-variable_analysis
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "V3Ast.h"
|
||||
#include "V3Cfg.h"
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
template <bool T_Scoped>
|
||||
class CfgLiveVariables final : VNVisitorConst {
|
||||
// TYPES
|
||||
using Variable = std::conditional_t<T_Scoped, AstVarScope, AstVar>;
|
||||
|
||||
// State associted with each basic block
|
||||
struct BlockState final {
|
||||
// Variables used in block, before a complete assignment in the same block
|
||||
std::unordered_set<Variable*> m_gen;
|
||||
// Variables that are assigned a complete value in the basic block
|
||||
std::unordered_set<Variable*> m_kill;
|
||||
std::unordered_set<Variable*> m_liveIn; // Variables live on entry to the block
|
||||
std::unordered_set<Variable*> m_liveOut; // Variables live on exit from the block
|
||||
bool m_isOnWorkList = false; // Block is on work list
|
||||
bool m_wasProcessed = false; // Already processed at least once
|
||||
};
|
||||
|
||||
// STATE
|
||||
const ControlFlowGraph& m_cfg; // The CFG beign analysed
|
||||
// State for each block
|
||||
BasicBlockMap<BlockState> m_blockState = m_cfg.makeBasicBlockMap<BlockState>();
|
||||
BlockState* m_currp = nullptr; // State of current block being analysed
|
||||
bool m_abort = false; // Abort analysis - unhandled construct
|
||||
|
||||
// METHODS
|
||||
static Variable* getTarget(const AstNodeVarRef* refp) {
|
||||
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
|
||||
if VL_CONSTEXPR_CXX17 (T_Scoped) {
|
||||
return reinterpret_cast<Variable*>(refp->varScopep());
|
||||
} else {
|
||||
return reinterpret_cast<Variable*>(refp->varp());
|
||||
}
|
||||
}
|
||||
|
||||
// This is to match DFG, but can be extended independently - eventually should handle all
|
||||
static bool isSupportedPackedDType(const AstNodeDType* dtypep) {
|
||||
dtypep = dtypep->skipRefp();
|
||||
if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
|
||||
return typep->keyword().isIntNumeric();
|
||||
}
|
||||
if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) {
|
||||
return isSupportedPackedDType(typep->subDTypep());
|
||||
}
|
||||
if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
|
||||
return typep->packed();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check and return if variable is incompatible
|
||||
bool incompatible(Variable* varp) {
|
||||
if (!isSupportedPackedDType(varp->dtypep())) return true;
|
||||
AstVar* astVarp = nullptr;
|
||||
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
|
||||
if VL_CONSTEXPR_CXX17 (T_Scoped) {
|
||||
astVarp = reinterpret_cast<AstVarScope*>(varp)->varp();
|
||||
} else {
|
||||
astVarp = reinterpret_cast<AstVar*>(varp);
|
||||
}
|
||||
if (astVarp->ignoreSchedWrite()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void updateGen(AstNode* nodep) {
|
||||
UASSERT_OBJ(!m_abort, nodep, "Doing useless work");
|
||||
m_abort = nodep->exists([&](const AstNodeVarRef* refp) {
|
||||
// Cross reference is ambiguous
|
||||
if (VN_IS(refp, VarXRef)) return true;
|
||||
// Only care about reads
|
||||
if (refp->access().isWriteOnly()) return false;
|
||||
// Grab referenced variable
|
||||
Variable* const tgtp = getTarget(refp);
|
||||
// Bail if not a compatible type
|
||||
if (incompatible(tgtp)) return true;
|
||||
// Add to gen set, if not killed - assume whole variable read
|
||||
if (m_currp->m_kill.count(tgtp)) return false;
|
||||
m_currp->m_gen.emplace(tgtp);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void updateKill(AstNode* nodep) {
|
||||
UASSERT_OBJ(!m_abort, nodep, "Doing useless work");
|
||||
m_abort = nodep->exists([&](const AstNodeVarRef* refp) {
|
||||
// Cross reference is ambiguous
|
||||
if (VN_IS(refp, VarXRef)) return true;
|
||||
// Only care about writes
|
||||
if (refp->access().isReadOnly()) return false;
|
||||
// Grab referenced variable
|
||||
Variable* const tgtp = getTarget(refp);
|
||||
// Bail if not a compatible type
|
||||
if (incompatible(tgtp)) return true;
|
||||
// If whole written, add to kill set
|
||||
if (refp->nextp()) return false;
|
||||
if (VN_IS(refp->abovep(), Sel)) return false;
|
||||
m_currp->m_kill.emplace(tgtp);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void single(AstNode* nodep) {
|
||||
// Assume all reads hapen before any writes
|
||||
if (m_abort) return;
|
||||
updateGen(nodep);
|
||||
if (m_abort) return;
|
||||
updateKill(nodep);
|
||||
}
|
||||
|
||||
// Apply transfer function of block, return true if changed
|
||||
bool transfer(const BasicBlock& bb) {
|
||||
BlockState& state = m_blockState[bb];
|
||||
|
||||
// liveIn = gen union (liveOut - kill)
|
||||
std::unordered_set<Variable*> liveIn = state.m_gen;
|
||||
for (Variable* const varp : state.m_liveOut) {
|
||||
if (state.m_kill.count(varp)) continue;
|
||||
liveIn.insert(varp);
|
||||
}
|
||||
if (liveIn == state.m_liveIn) return false;
|
||||
std::swap(liveIn, state.m_liveIn);
|
||||
return true;
|
||||
}
|
||||
|
||||
// VISIT
|
||||
void visit(AstNode* nodep) override { //
|
||||
UASSERT_OBJ(!m_abort, nodep, "Repeat traversal after abort");
|
||||
m_abort = true;
|
||||
UINFO(9, "Unhandled AstNode type " << nodep->typeName());
|
||||
}
|
||||
|
||||
void visit(AstAssign* nodep) override { single(nodep); }
|
||||
void visit(AstDisplay* nodep) override { single(nodep); }
|
||||
void visit(AstFinish* nodep) override { single(nodep); }
|
||||
void visit(AstStmtExpr* nodep) override { single(nodep); }
|
||||
void visit(AstStop* nodep) override { single(nodep); }
|
||||
|
||||
// Only the condition check belongs to the terminated basic block
|
||||
void visit(AstIf* nodep) override { single(nodep->condp()); }
|
||||
void visit(AstWhile* nodep) override { single(nodep->condp()); }
|
||||
|
||||
// CONSTRUCTOR
|
||||
explicit CfgLiveVariables(const ControlFlowGraph& cfg)
|
||||
: m_cfg{cfg} {
|
||||
// For each basic block, compute the gen and kill set via visit
|
||||
cfg.foreach([&](const BasicBlock& bb) {
|
||||
if (m_abort) return;
|
||||
VL_RESTORER(m_currp);
|
||||
m_currp = &m_blockState[bb];
|
||||
for (AstNode* const stmtp : bb.stmtps()) iterateConst(stmtp);
|
||||
});
|
||||
if (m_abort) return;
|
||||
|
||||
// Perform the flow analysis
|
||||
std::deque<const BasicBlock*> workList;
|
||||
const auto enqueue = [&](const BasicBlock& bb) {
|
||||
BlockState& state = m_blockState[bb];
|
||||
if (state.m_isOnWorkList) return;
|
||||
state.m_isOnWorkList = true;
|
||||
workList.emplace_back(&bb);
|
||||
};
|
||||
|
||||
enqueue(cfg.exit());
|
||||
|
||||
while (!workList.empty()) {
|
||||
const BasicBlock* const currp = workList.front();
|
||||
workList.pop_front();
|
||||
BlockState& state = m_blockState[*currp];
|
||||
state.m_isOnWorkList = false;
|
||||
|
||||
// Compute meet (liveOut = union liveIn of successors)
|
||||
currp->forEachSuccessor([&](const BasicBlock& bb) {
|
||||
auto& liveIn = m_blockState[bb].m_liveIn;
|
||||
state.m_liveOut.insert(liveIn.begin(), liveIn.end());
|
||||
});
|
||||
|
||||
// Apply transfer function of block
|
||||
const bool changed = transfer(*currp);
|
||||
// Enqueue predecessors
|
||||
if (changed || !state.m_wasProcessed) currp->forEachPredecessor(enqueue);
|
||||
// Mark as done with first visit
|
||||
state.m_wasProcessed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
static std::unique_ptr<std::vector<Variable*>> apply(const ControlFlowGraph& cfg) {
|
||||
CfgLiveVariables analysis{cfg};
|
||||
// If failed, return nullptr
|
||||
if (analysis.m_abort) return nullptr;
|
||||
// Gather variables live in to the entry blockstd::unique_ptr<std::vector<Variable*>>
|
||||
const std::unordered_set<Variable*>& lin = analysis.m_blockState[cfg.enter()].m_liveIn;
|
||||
std::vector<Variable*>* const resultp = new std::vector<Variable*>{lin.begin(), lin.end()};
|
||||
// Sort for stability
|
||||
std::stable_sort(resultp->begin(), resultp->end(), [](Variable* ap, Variable* bp) { //
|
||||
return ap->name() < bp->name();
|
||||
});
|
||||
return std::unique_ptr<std::vector<Variable*>>{resultp};
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<std::vector<AstVar*>> V3Cfg::liveVars(const ControlFlowGraph& cfg) {
|
||||
return CfgLiveVariables</* T_Scoped: */ false>::apply(cfg);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<AstVarScope*>> V3Cfg::liveVarScopes(const ControlFlowGraph& cfg) {
|
||||
return CfgLiveVariables</* T_Scoped: */ true>::apply(cfg);
|
||||
}
|
||||
242
src/V3Dfg.cpp
242
src/V3Dfg.cpp
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "V3Dfg.h"
|
||||
|
||||
#include "V3EmitV.h"
|
||||
#include "V3File.h"
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
|
@ -77,6 +78,8 @@ std::unique_ptr<DfgGraph> DfgGraph::clone() const {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (AstNode* const tmpForp = vp->tmpForp()) cp->tmpForp(tmpForp);
|
||||
}
|
||||
// Clone operation vertices
|
||||
for (const DfgVertex& vtx : m_opVertices) {
|
||||
|
|
@ -88,6 +91,11 @@ std::unique_ptr<DfgGraph> DfgGraph::clone() const {
|
|||
vtxp2clonep.emplace(&vtx, cp);
|
||||
break;
|
||||
}
|
||||
case VDfgType::atUnitArray: {
|
||||
DfgUnitArray* const cp = new DfgUnitArray{*clonep, vtx.fileline(), vtx.dtypep()};
|
||||
vtxp2clonep.emplace(&vtx, cp);
|
||||
break;
|
||||
}
|
||||
case VDfgType::atMux: {
|
||||
DfgMux* const cp = new DfgMux{*clonep, vtx.fileline(), vtx.dtypep()};
|
||||
vtxp2clonep.emplace(&vtx, cp);
|
||||
|
|
@ -103,6 +111,16 @@ std::unique_ptr<DfgGraph> DfgGraph::clone() const {
|
|||
vtxp2clonep.emplace(&vtx, cp);
|
||||
break;
|
||||
}
|
||||
case VDfgType::atLogic: {
|
||||
vtx.v3fatalSrc("DfgLogic cannot be cloned");
|
||||
VL_UNREACHABLE;
|
||||
break;
|
||||
}
|
||||
case VDfgType::atUnresolved: {
|
||||
vtx.v3fatalSrc("DfgUnresolved cannot be cloned");
|
||||
VL_UNREACHABLE;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
vtx.v3fatalSrc("Unhandled operation vertex type: " + vtx.typeName());
|
||||
VL_UNREACHABLE;
|
||||
|
|
@ -130,7 +148,7 @@ std::unique_ptr<DfgGraph> DfgGraph::clone() const {
|
|||
vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) {
|
||||
if (DfgVertex* const srcp = edge.sourcep()) {
|
||||
cp->addDriver(vp->driverFileLine(i), //
|
||||
vp->driverIndex(i), //
|
||||
vp->driverLo(i), //
|
||||
vtxp2clonep.at(srcp));
|
||||
}
|
||||
});
|
||||
|
|
@ -140,10 +158,14 @@ std::unique_ptr<DfgGraph> DfgGraph::clone() const {
|
|||
const DfgSplicePacked* const vp = vtx.as<DfgSplicePacked>();
|
||||
DfgSplicePacked* const cp = vtxp2clonep.at(vp)->as<DfgSplicePacked>();
|
||||
vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) {
|
||||
if (DfgVertex* const srcp = edge.sourcep()) {
|
||||
cp->addDriver(vp->driverFileLine(i), //
|
||||
vp->driverLsb(i), //
|
||||
vtxp2clonep.at(srcp));
|
||||
if (DfgVertex* const srcVp = edge.sourcep()) {
|
||||
DfgVertex* const srcCp = vtxp2clonep.at(srcVp);
|
||||
UASSERT_OBJ(!srcCp->is<DfgLogic>(), srcCp, "Cannot clone DfgLogic");
|
||||
if (srcVp == vp->defaultp()) {
|
||||
cp->defaultp(srcCp);
|
||||
} else {
|
||||
cp->addDriver(vp->driverFileLine(i), vp->driverLo(i), srcCp);
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
|
@ -268,8 +290,11 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
|||
AstNode* const nodep = varVtxp->nodep();
|
||||
AstVar* const varp = varVtxp->varp();
|
||||
os << toDotId(vtx);
|
||||
os << " [label=\"" << nodep->name() << '\n';
|
||||
os << " [label=\"" << nodep->prettyName() << '\n';
|
||||
os << cvtToHex(varVtxp) << '\n';
|
||||
if (AstNode* const tmpForp = varVtxp->tmpForp()) {
|
||||
os << "temporary for: " << tmpForp->prettyName() << "\n";
|
||||
}
|
||||
varVtxp->dtypep()->dumpSmall(os);
|
||||
os << " / F" << varVtxp->fanout() << '"';
|
||||
|
||||
|
|
@ -285,6 +310,8 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
|||
os << ", shape=box, style=filled, fillcolor=darkorange1"; // Orange
|
||||
} else if (varVtxp->hasDfgRefs()) {
|
||||
os << ", shape=box, style=filled, fillcolor=gold2"; // Yellow
|
||||
} else if (varVtxp->tmpForp()) {
|
||||
os << ", shape=box, style=filled, fillcolor=gray80";
|
||||
} else {
|
||||
os << ", shape=box";
|
||||
}
|
||||
|
|
@ -296,8 +323,11 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
|||
AstNode* const nodep = arrVtxp->nodep();
|
||||
AstVar* const varp = arrVtxp->varp();
|
||||
os << toDotId(vtx);
|
||||
os << " [label=\"" << nodep->name() << '\n';
|
||||
os << " [label=\"" << nodep->prettyName() << '\n';
|
||||
os << cvtToHex(arrVtxp) << '\n';
|
||||
if (AstNode* const tmpForp = arrVtxp->tmpForp()) {
|
||||
os << "temporary for: " << tmpForp->prettyName() << "\n";
|
||||
}
|
||||
arrVtxp->dtypep()->dumpSmall(os);
|
||||
os << " / F" << arrVtxp->fanout() << '"';
|
||||
if (varp->direction() == VDirection::INPUT) {
|
||||
|
|
@ -312,6 +342,8 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
|||
os << ", shape=box3d, style=filled, fillcolor=darkorange1"; // Orange
|
||||
} else if (arrVtxp->hasDfgRefs()) {
|
||||
os << ", shape=box3d, style=filled, fillcolor=gold2"; // Yellow
|
||||
} else if (arrVtxp->tmpForp()) {
|
||||
os << ", shape=box3d, style=filled, fillcolor=gray80";
|
||||
} else {
|
||||
os << ", shape=box3d";
|
||||
}
|
||||
|
|
@ -354,7 +386,7 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (vtx.is<DfgVertexSplice>()) {
|
||||
if (vtx.is<DfgVertexSplice>() || vtx.is<DfgUnitArray>() || vtx.is<DfgUnresolved>()) {
|
||||
os << toDotId(vtx);
|
||||
os << " [label=\"" << vtx.typeName() << '\n';
|
||||
os << cvtToHex(&vtx) << '\n';
|
||||
|
|
@ -369,6 +401,19 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (const DfgLogic* const logicp = vtx.cast<DfgLogic>()) {
|
||||
os << toDotId(vtx);
|
||||
std::stringstream ss;
|
||||
V3EmitV::debugVerilogForTree(logicp->nodep(), ss);
|
||||
os << " [label=\"";
|
||||
os << VString::replaceSubstr(VString::replaceSubstr(ss.str(), "\n", "\\l"), "\"", "\\\"");
|
||||
os << "\\n" << cvtToHex(&vtx);
|
||||
os << "\"\n";
|
||||
os << ", shape=box, style=\"rounded,filled\", fillcolor=cornsilk, nojustify=true";
|
||||
os << "]\n";
|
||||
return;
|
||||
}
|
||||
|
||||
os << toDotId(vtx);
|
||||
os << " [label=\"" << vtx.typeName() << '\n';
|
||||
os << cvtToHex(&vtx) << '\n';
|
||||
|
|
@ -384,7 +429,7 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
|||
|
||||
// Dump one DfgEdge in Graphviz format
|
||||
static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, size_t idx) {
|
||||
if (!edge.sourcep()) return;
|
||||
UASSERT(edge.sourcep(), "Can't dump unconnected DfgEdge");
|
||||
const DfgVertex& sink = *edge.sinkp(); // sink is never nullptr
|
||||
os << toDotId(*edge.sourcep()) << " -> " << toDotId(sink);
|
||||
if (sink.arity() > 1 || sink.is<DfgVertexSplice>()) {
|
||||
|
|
@ -399,30 +444,47 @@ void DfgGraph::dumpDot(std::ostream& os, const std::string& label,
|
|||
|
||||
// Header
|
||||
os << "digraph dfg {\n";
|
||||
os << "graph [label=\"" << name();
|
||||
if (!label.empty()) os << "-" << label;
|
||||
os << "\", labelloc=t, labeljust=l]\n";
|
||||
os << "graph [rankdir=LR]\n";
|
||||
os << "rankdir=LR\n";
|
||||
|
||||
if (!p) {
|
||||
// Emit all vertices and edges
|
||||
forEachVertex([&](const DfgVertex& vtx) {
|
||||
dumpDotVertex(os, vtx);
|
||||
vtx.forEachSourceEdge([&](const DfgEdge& e, size_t i) { dumpDotEdge(os, e, i); });
|
||||
// If predicate not given, dump everything
|
||||
if (!p) p = [](const DfgVertex&) { return true; };
|
||||
|
||||
std::unordered_set<const DfgVertex*> emitted;
|
||||
// Emit all vertices associated with a DfgLogic
|
||||
forEachVertex([&](const DfgVertex& vtx) {
|
||||
const DfgLogic* const logicp = vtx.cast<DfgLogic>();
|
||||
if (!logicp) return;
|
||||
if (logicp->synth().empty()) return;
|
||||
if (!p(vtx)) return;
|
||||
os << "subgraph cluster_" << cvtToHex(logicp) << " {\n";
|
||||
dumpDotVertex(os, *logicp);
|
||||
emitted.insert(logicp);
|
||||
for (DfgVertex* const vtxp : logicp->synth()) {
|
||||
if (!p(*vtxp)) continue;
|
||||
dumpDotVertex(os, *vtxp);
|
||||
emitted.insert(vtxp);
|
||||
}
|
||||
os << "}\n";
|
||||
});
|
||||
// Emit all remaining vertices
|
||||
forEachVertex([&](const DfgVertex& vtx) {
|
||||
if (emitted.count(&vtx)) return;
|
||||
if (!p(vtx)) return;
|
||||
dumpDotVertex(os, vtx);
|
||||
});
|
||||
// Emit all edges
|
||||
forEachVertex([&](const DfgVertex& vtx) { //
|
||||
if (!p(vtx)) return;
|
||||
vtx.forEachSourceEdge([&](const DfgEdge& e, size_t i) { //
|
||||
if (!e.sourcep() || !p(*e.sourcep())) return;
|
||||
dumpDotEdge(os, e, i);
|
||||
});
|
||||
} else {
|
||||
// Emit vertices that satify the predicate 'p'
|
||||
forEachVertex([&](const DfgVertex& vtx) {
|
||||
if (!p(vtx)) return;
|
||||
dumpDotVertex(os, vtx);
|
||||
vtx.forEachSourceEdge([&](const DfgEdge& e, size_t i) {
|
||||
if (!e.sourcep() || !p(*e.sourcep())) return;
|
||||
dumpDotEdge(os, e, i);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Footer
|
||||
os << "label=\"" << name() + (label.empty() ? "" : "-" + label) << "\"\n";
|
||||
os << "labelloc=t\n";
|
||||
os << "labeljust=l\n";
|
||||
os << "}\n";
|
||||
}
|
||||
|
||||
|
|
@ -448,51 +510,65 @@ void DfgGraph::dumpDotFilePrefixed(const std::string& label,
|
|||
dumpDotFile(v3Global.debugFilename(filename) + ".dot", label, p);
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START // Debug functions for developer use only
|
||||
void DfgGraph::dumpDotUpstreamCone(const std::string& fileName, const DfgVertex& vtx,
|
||||
const std::string& name) const {
|
||||
// Open output file
|
||||
const std::unique_ptr<std::ofstream> os{V3File::new_ofstream(fileName)};
|
||||
if (os->fail()) v3fatal("Can't write file: " << fileName);
|
||||
|
||||
// Header
|
||||
*os << "digraph dfg {\n";
|
||||
if (!name.empty()) *os << "graph [label=\"" << name << "\", labelloc=t, labeljust=l]\n";
|
||||
*os << "graph [rankdir=LR]\n";
|
||||
|
||||
// Work queue for depth first traversal starting from this vertex
|
||||
std::vector<const DfgVertex*> queue{&vtx};
|
||||
|
||||
template <bool T_SinksNotSources>
|
||||
static std::unique_ptr<std::unordered_set<const DfgVertex*>>
|
||||
dfgGraphCollectCone(const std::vector<const DfgVertex*> vtxps) {
|
||||
// Work queue for traversal starting from all the seed vertices
|
||||
std::vector<const DfgVertex*> queue = vtxps;
|
||||
// Set of already visited vertices
|
||||
std::unordered_set<const DfgVertex*> visited;
|
||||
|
||||
std::unordered_set<const DfgVertex*>* const resp = new std::unordered_set<const DfgVertex*>{};
|
||||
// Depth first traversal
|
||||
while (!queue.empty()) {
|
||||
// Pop next work item
|
||||
const DfgVertex* const vtxp = queue.back();
|
||||
queue.pop_back();
|
||||
|
||||
// Mark vertex as visited
|
||||
const bool isFirstEncounter = visited.insert(vtxp).second;
|
||||
|
||||
// If we have already visited this vertex during the traversal, then move on.
|
||||
if (!isFirstEncounter) continue;
|
||||
|
||||
// Enqueue all sources of this vertex.
|
||||
vtxp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); });
|
||||
|
||||
// Emit this vertex and all of its source edges
|
||||
dumpDotVertex(*os, *vtxp);
|
||||
vtxp->forEachSourceEdge([&](const DfgEdge& e, size_t i) { dumpDotEdge(*os, e, i); });
|
||||
// Mark vertex as visited, move on if already visited
|
||||
if (!resp->insert(vtxp).second) continue;
|
||||
// Enqueue all siblings of this vertex.
|
||||
if VL_CONSTEXPR_CXX17 (T_SinksNotSources) {
|
||||
vtxp->forEachSink([&](const DfgVertex& sink) { queue.push_back(&sink); });
|
||||
} else {
|
||||
vtxp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); });
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
*os << "}\n";
|
||||
|
||||
// Done
|
||||
os->close();
|
||||
return std::unique_ptr<std::unordered_set<const DfgVertex*>>{resp};
|
||||
}
|
||||
|
||||
std::unique_ptr<std::unordered_set<const DfgVertex*>>
|
||||
DfgGraph::sourceCone(const std::vector<const DfgVertex*> vtxps) const {
|
||||
return dfgGraphCollectCone<false>(vtxps);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::unordered_set<const DfgVertex*>>
|
||||
DfgGraph::sinkCone(const std::vector<const DfgVertex*> vtxps) const {
|
||||
return dfgGraphCollectCone<true>(vtxps);
|
||||
}
|
||||
|
||||
// predicate for supported data types
|
||||
static bool dfgGraphIsSupportedDTypePacked(const AstNodeDType* dtypep) {
|
||||
dtypep = dtypep->skipRefp();
|
||||
if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
|
||||
return typep->keyword().isIntNumeric();
|
||||
}
|
||||
if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) {
|
||||
return dfgGraphIsSupportedDTypePacked(typep->subDTypep());
|
||||
}
|
||||
if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
|
||||
return typep->packed();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DfgGraph::isSupported(const AstNodeDType* dtypep) {
|
||||
dtypep = dtypep->skipRefp();
|
||||
// Support 1 dimensional unpacked arrays of packed types
|
||||
if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) {
|
||||
return dfgGraphIsSupportedDTypePacked(typep->subDTypep());
|
||||
}
|
||||
// Support packed types
|
||||
return dfgGraphIsSupportedDTypePacked(dtypep);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// DfgEdge
|
||||
|
|
@ -658,6 +734,11 @@ DfgVertexVar* DfgVertex::getResultVar() {
|
|||
if (!resp->hasModRdRefs()) resp = varp;
|
||||
return;
|
||||
}
|
||||
// Prefer real variabels over temporaries
|
||||
if (!resp->tmpForp() != !varp->tmpForp()) {
|
||||
if (resp->tmpForp()) resp = varp;
|
||||
return;
|
||||
}
|
||||
// Prefer the earlier one in source order
|
||||
const FileLine& oldFlp = *(resp->fileline());
|
||||
const FileLine& newFlp = *(varp->fileline());
|
||||
|
|
@ -742,39 +823,26 @@ bool DfgSel::selfEquals(const DfgVertex& that) const { return lsb() == that.as<D
|
|||
|
||||
V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; }
|
||||
|
||||
// DfgSpliceArray ----------
|
||||
// DfgVertexSplice ----------
|
||||
|
||||
bool DfgSpliceArray::selfEquals(const DfgVertex& that) const {
|
||||
const DfgSpliceArray* const thatp = that.as<DfgSpliceArray>();
|
||||
bool DfgVertexSplice::selfEquals(const DfgVertex& that) const {
|
||||
const DfgVertexSplice* const thatp = that.as<DfgVertexSplice>();
|
||||
if (!defaultp() != !thatp->defaultp()) return false;
|
||||
const size_t arity = this->arity();
|
||||
for (size_t i = 0; i < arity; ++i) {
|
||||
if (driverIndex(i) != thatp->driverIndex(i)) return false;
|
||||
if (i == 0 && defaultp()) continue;
|
||||
if (driverLo(i) != thatp->driverLo(i)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
V3Hash DfgSpliceArray::selfHash() const {
|
||||
V3Hash DfgVertexSplice::selfHash() const {
|
||||
V3Hash hash;
|
||||
const size_t arity = this->arity();
|
||||
for (size_t i = 0; i < arity; ++i) hash += driverIndex(i);
|
||||
return hash;
|
||||
}
|
||||
|
||||
// DfgSplicePacked ----------
|
||||
|
||||
bool DfgSplicePacked::selfEquals(const DfgVertex& that) const {
|
||||
const DfgSplicePacked* const thatp = that.as<DfgSplicePacked>();
|
||||
const size_t arity = this->arity();
|
||||
for (size_t i = 0; i < arity; ++i) {
|
||||
if (driverLsb(i) != thatp->driverLsb(i)) return false;
|
||||
if (i == 0 && defaultp()) continue;
|
||||
hash += driverLo(i);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
V3Hash DfgSplicePacked::selfHash() const {
|
||||
V3Hash hash;
|
||||
const size_t arity = this->arity();
|
||||
for (size_t i = 0; i < arity; ++i) hash += driverLsb(i);
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
|
|
|||
182
src/V3Dfg.h
182
src/V3Dfg.h
|
|
@ -34,6 +34,7 @@
|
|||
#include "verilatedos.h"
|
||||
|
||||
#include "V3Ast.h"
|
||||
#include "V3Cfg.h"
|
||||
#include "V3Error.h"
|
||||
#include "V3Global.h"
|
||||
#include "V3Hash.h"
|
||||
|
|
@ -140,7 +141,7 @@ protected:
|
|||
DfgGraph* m_graphp; // The containing DfgGraph
|
||||
const VDfgType m_type; // Vertex type tag
|
||||
uint32_t m_userCnt = 0; // User data generation number
|
||||
UserDataStorage m_userDataStorage; // User data storage
|
||||
UserDataStorage m_userDataStorage = nullptr; // User data storage
|
||||
|
||||
// CONSTRUCTOR
|
||||
DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep) VL_MT_DISABLED;
|
||||
|
|
@ -168,59 +169,15 @@ private:
|
|||
virtual V3Hash selfHash() const VL_MT_DISABLED;
|
||||
|
||||
public:
|
||||
// Supported packed types
|
||||
static bool isSupportedPackedDType(const AstNodeDType* dtypep) {
|
||||
dtypep = dtypep->skipRefp();
|
||||
if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
|
||||
return typep->keyword().isIntNumeric();
|
||||
}
|
||||
if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) {
|
||||
return isSupportedPackedDType(typep->subDTypep());
|
||||
}
|
||||
if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
|
||||
return typep->packed();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// The data type of the result of the vertex
|
||||
AstNodeDType* dtypep() const { return m_dtypep; }
|
||||
|
||||
// Returns true if an AstNode with the given 'dtype' can be represented as a DfgVertex
|
||||
static bool isSupportedDType(const AstNodeDType* dtypep) {
|
||||
dtypep = dtypep->skipRefp();
|
||||
// Support unpacked arrays of packed types
|
||||
if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) {
|
||||
return isSupportedPackedDType(typep->subDTypep());
|
||||
}
|
||||
// Support packed types
|
||||
return isSupportedPackedDType(dtypep);
|
||||
}
|
||||
|
||||
// Return data type used to represent any packed value of the given 'width'. All packed types
|
||||
// of a given width use the same canonical data type, as the only interesting information is
|
||||
// the total width.
|
||||
static AstNodeDType* dtypeForWidth(uint32_t width) {
|
||||
return v3Global.rootp()->typeTablep()->findLogicDType(width, width, VSigning::UNSIGNED);
|
||||
}
|
||||
|
||||
// Return data type used to represent the type of 'nodep' when converted to a DfgVertex
|
||||
static AstNodeDType* dtypeFor(const AstNode* nodep) {
|
||||
const AstNodeDType* const dtypep = nodep->dtypep()->skipRefp();
|
||||
UDEBUGONLY(UASSERT_OBJ(isSupportedDType(dtypep), nodep, "Unsupported dtype"););
|
||||
// For simplicity, all packed types are represented with a fixed type
|
||||
if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) {
|
||||
AstNodeDType* const adtypep = new AstUnpackArrayDType{
|
||||
typep->fileline(), dtypeForWidth(typep->subDTypep()->width()),
|
||||
typep->rangep()->cloneTree(false)};
|
||||
v3Global.rootp()->typeTablep()->addTypesp(adtypep);
|
||||
return adtypep;
|
||||
}
|
||||
return dtypeForWidth(dtypep->width());
|
||||
}
|
||||
// Is it a packed type (instead of an array)
|
||||
bool isPacked() const { return VN_IS(dtypep(), BasicDType); }
|
||||
|
||||
// Source location
|
||||
FileLine* fileline() const { return m_filelinep; }
|
||||
// The data type of the result of the nodes
|
||||
AstNodeDType* dtypep() const { return m_dtypep; }
|
||||
void dtypep(AstNodeDType* nodep) { m_dtypep = nodep; }
|
||||
|
||||
// The type of this vertex
|
||||
VDfgType type() const { return m_type; }
|
||||
|
||||
|
|
@ -244,10 +201,16 @@ public:
|
|||
|
||||
// Width of result
|
||||
uint32_t width() const {
|
||||
UASSERT_OBJ(VN_IS(dtypep(), BasicDType), this, "non-packed has no 'width()'");
|
||||
UASSERT_OBJ(isPacked(), this, "non-packed has no 'width()'");
|
||||
return dtypep()->width();
|
||||
}
|
||||
|
||||
// Number of sub-elements in result vertex
|
||||
uint32_t size() const {
|
||||
if (isPacked()) return dtypep()->width();
|
||||
return VN_AS(dtypep(), UnpackArrayDType)->elementsConst();
|
||||
}
|
||||
|
||||
// Cache type for 'equals' below
|
||||
using EqualsCache = std::unordered_map<std::pair<const DfgVertex*, const DfgVertex*>, uint8_t>;
|
||||
|
||||
|
|
@ -307,6 +270,9 @@ public:
|
|||
return m_sinksp && !m_sinksp->m_nextp ? m_sinksp->m_sinkp : nullptr;
|
||||
}
|
||||
|
||||
// First sink of the vertex, if any, otherwise nullptr
|
||||
DfgVertex* firtsSinkp() const { return m_sinksp ? m_sinksp->m_sinkp : nullptr; }
|
||||
|
||||
// Unlink from container (graph or builder), then delete this vertex
|
||||
void unlinkDelete(DfgGraph& dfg) VL_MT_DISABLED;
|
||||
|
||||
|
|
@ -598,12 +564,20 @@ protected:
|
|||
m_srcCnt = 0;
|
||||
}
|
||||
|
||||
public:
|
||||
void clearSources() {
|
||||
for (uint32_t i = 0; i < m_srcCnt; ++i) {
|
||||
UASSERT_OBJ(m_srcsp[i].sourcep(), this, "Unconnected source");
|
||||
m_srcsp[i].unlinkSource();
|
||||
}
|
||||
m_srcCnt = 0;
|
||||
}
|
||||
|
||||
ASTGEN_MEMBERS_DfgVertexVariadic;
|
||||
|
||||
DfgEdge* sourceEdge(size_t idx) const { return &m_srcsp[idx]; }
|
||||
DfgVertex* source(size_t idx) const { return m_srcsp[idx].sourcep(); }
|
||||
|
||||
public:
|
||||
std::pair<DfgEdge*, size_t> sourceEdges() override { return {m_srcsp, m_srcCnt}; }
|
||||
std::pair<const DfgEdge*, size_t> sourceEdges() const override { return {m_srcsp, m_srcCnt}; }
|
||||
};
|
||||
|
|
@ -744,6 +718,9 @@ public:
|
|||
std::vector<std::unique_ptr<DfgGraph>>
|
||||
extractCyclicComponents(std::string label) VL_MT_DISABLED;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Debug dumping
|
||||
|
||||
// Dump graph in Graphviz format into the given stream 'os'. 'label' is added to the name of
|
||||
// the graph which is included in the output.
|
||||
// If the predicate function 'p' is provided, only those vertices are dumped that satifty it.
|
||||
|
|
@ -763,10 +740,70 @@ public:
|
|||
void dumpDotFilePrefixed(const std::string& label,
|
||||
std::function<bool(const DfgVertex&)> p = {}) const VL_MT_DISABLED;
|
||||
|
||||
// Dump upstream (source) logic cone starting from given vertex into a file with the given
|
||||
// 'filename'. 'name' is the name of the graph, which is included in the output.
|
||||
void dumpDotUpstreamCone(const std::string& filename, const DfgVertex& vtx,
|
||||
const std::string& name = "") const VL_MT_DISABLED;
|
||||
// Returns the set of vertices in the upstream cones of the given vertices
|
||||
std::unique_ptr<std::unordered_set<const DfgVertex*>>
|
||||
sourceCone(const std::vector<const DfgVertex*>) const VL_MT_DISABLED;
|
||||
// Returns the set of vertices in the downstream cones of the given vertices
|
||||
std::unique_ptr<std::unordered_set<const DfgVertex*>>
|
||||
sinkCone(const std::vector<const DfgVertex*>) const VL_MT_DISABLED;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Static methods for data types
|
||||
|
||||
// Some data types are interned, in order to facilitate type comparison
|
||||
// via pointer compariosn. These are functoins to construct the canonical
|
||||
// DFG data types
|
||||
|
||||
// Returns data type used to represent any packed value of the given 'width'.
|
||||
static AstNodeDType* dtypePacked(uint32_t width) {
|
||||
return v3Global.rootp()->typeTablep()->findLogicDType(width, width, VSigning::UNSIGNED);
|
||||
}
|
||||
|
||||
// Returns data type used to represent any array with the given type and number of elements.
|
||||
static AstNodeDType* dtypeArray(AstNodeDType* subDtypep, uint32_t size) {
|
||||
UASSERT_OBJ(isSupported(subDtypep), subDtypep, "Unsupported element type");
|
||||
FileLine* const flp = subDtypep->fileline();
|
||||
AstRange* const rangep = new AstRange{flp, static_cast<int>(size - 1), 0};
|
||||
AstNodeDType* const dtypep = new AstUnpackArrayDType{flp, subDtypep, rangep};
|
||||
v3Global.rootp()->typeTablep()->addTypesp(dtypep);
|
||||
return dtypep;
|
||||
}
|
||||
|
||||
// Return data type used to represent the type of 'nodep' when converted to a DfgVertex
|
||||
static AstNodeDType* toDfgDType(const AstNodeDType* dtypep) {
|
||||
dtypep = dtypep->skipRefp();
|
||||
UASSERT_OBJ(isSupported(dtypep), dtypep, "Unsupported dtype");
|
||||
// For simplicity, all packed types are represented with a fixed type
|
||||
if (const AstUnpackArrayDType* const uatp = VN_CAST(dtypep, UnpackArrayDType)) {
|
||||
return dtypeArray(toDfgDType(uatp->subDTypep()), uatp->elementsConst());
|
||||
}
|
||||
return dtypePacked(dtypep->width());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Static methods for compatibility tests
|
||||
|
||||
// Returns true if the given data type can be represented in the graph
|
||||
static bool isSupported(const AstNodeDType* dtypep) VL_MT_DISABLED;
|
||||
|
||||
// Returns true if variable can be represented in the graph
|
||||
static bool isSupported(const AstVar* varp) {
|
||||
if (varp->isIfaceRef()) return false; // Cannot handle interface references
|
||||
if (varp->delayp()) return false; // Cannot handle delayed variables
|
||||
if (varp->isSc()) return false; // SystemC variables are special and rare, we can ignore
|
||||
if (varp->dfgMultidriven()) return false; // Discovered as multidriven on earlier DFG run
|
||||
return isSupported(varp->dtypep());
|
||||
}
|
||||
|
||||
// Returns true if variable can be represented in the graph
|
||||
static bool isSupported(const AstVarScope* vscp) {
|
||||
// If the variable is not in a regular module, then we do not support it.
|
||||
// This is especially needed for variabels in interfaces which might be
|
||||
// referenced via virtual intefaces, which cannot be resovled statically.
|
||||
if (!VN_IS(vscp->scopep()->modp(), Module)) return false;
|
||||
// Check the AstVar
|
||||
return isSupported(vscp->varp());
|
||||
}
|
||||
};
|
||||
|
||||
// Specializations of privateTypeTest
|
||||
|
|
@ -921,12 +958,41 @@ bool DfgVertex::isOnes() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Inline method definitions - for DfgConst
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
DfgConst::DfgConst(DfgGraph& dfg, FileLine* flp, const V3Number& num)
|
||||
: DfgVertex{dfg, dfgType(), flp, DfgGraph::dtypePacked(num.width())}
|
||||
, m_num{num} {}
|
||||
DfgConst::DfgConst(DfgGraph& dfg, FileLine* flp, uint32_t width, uint32_t value)
|
||||
: DfgVertex{dfg, dfgType(), flp, DfgGraph::dtypePacked(width)}
|
||||
, m_num{flp, static_cast<int>(width), value} {}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Inline method definitions - for DfgVertexSplice
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
DfgVertex* DfgVertexSplice::wholep() const {
|
||||
if (defaultp()) return nullptr;
|
||||
if (arity() != 1) return nullptr;
|
||||
if (driverLo(0) != 0) return nullptr;
|
||||
DfgVertex* const srcp = DfgVertexVariadic::source(1);
|
||||
if (srcp->size() != size()) return nullptr;
|
||||
if (DfgUnitArray* const uap = srcp->cast<DfgUnitArray>()) {
|
||||
if (DfgVertexSplice* sp = uap->srcp()->cast<DfgVertexSplice>()) {
|
||||
if (!sp->wholep()) return nullptr;
|
||||
}
|
||||
}
|
||||
return srcp;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Inline method definitions - for DfgVertexVar
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp)
|
||||
: DfgVertexUnary{dfg, type, varp->fileline(), dtypeFor(varp)}
|
||||
: DfgVertexUnary{dfg, type, varp->fileline(), DfgGraph::toDfgDType(varp->dtypep())}
|
||||
, m_varp{varp}
|
||||
, m_varScopep{nullptr} {
|
||||
UASSERT_OBJ(dfg.modulep(), varp, "Un-scoped DfgVertexVar created in scoped DfgGraph");
|
||||
|
|
@ -936,7 +1002,7 @@ DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp)
|
|||
UASSERT_OBJ((varp->user1() >> 4) > 0, varp, "Reference count overflow");
|
||||
}
|
||||
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp)
|
||||
: DfgVertexUnary{dfg, type, vscp->fileline(), dtypeFor(vscp)}
|
||||
: DfgVertexUnary{dfg, type, vscp->fileline(), DfgGraph::toDfgDType(vscp->varp()->dtypep())}
|
||||
, m_varp{vscp->varp()}
|
||||
, m_varScopep{vscp} {
|
||||
UASSERT_OBJ(!dfg.modulep(), vscp, "Scoped DfgVertexVar created in un-scoped DfgGraph");
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -20,6 +20,7 @@
|
|||
#include "V3DfgPasses.h"
|
||||
#include "V3Hash.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
|
|
@ -116,7 +117,7 @@ class TraceDriver final : public DfgVisitor {
|
|||
// Vertex is DfgConst, in which case this code is unreachable ...
|
||||
using Vtx = typename std::conditional<std::is_same<DfgConst, Vertex>::value, DfgSel,
|
||||
Vertex>::type;
|
||||
AstNodeDType* const dtypep = DfgVertex::dtypeForWidth(width);
|
||||
AstNodeDType* const dtypep = DfgGraph::dtypePacked(width);
|
||||
Vtx* const vtxp = new Vtx{m_dfg, refp->fileline(), dtypep};
|
||||
vtxp->template setUser<uint64_t>(0);
|
||||
m_newVtxps.emplace_back(vtxp);
|
||||
|
|
@ -281,18 +282,94 @@ class TraceDriver final : public DfgVisitor {
|
|||
}
|
||||
|
||||
void visit(DfgSplicePacked* vtxp) override {
|
||||
// Proceed with the driver that wholly covers the searched bits
|
||||
struct Driver final {
|
||||
DfgVertex* m_vtxp;
|
||||
uint32_t m_lsb; // LSB of driven range (internal, not Verilog)
|
||||
uint32_t m_msb; // MSB of driven range (internal, not Verilog)
|
||||
Driver() = delete;
|
||||
Driver(DfgVertex* vtxp, uint32_t lsb, uint32_t msb)
|
||||
: m_vtxp{vtxp}
|
||||
, m_lsb{lsb}
|
||||
, m_msb{msb} {}
|
||||
};
|
||||
std::vector<Driver> drivers;
|
||||
DfgVertex* const defaultp = vtxp->defaultp();
|
||||
|
||||
// Look at all the drivers, one might cover the whole range, but also gathe all drivers
|
||||
const auto pair = vtxp->sourceEdges();
|
||||
bool tryWholeDefault = defaultp;
|
||||
for (size_t i = 0; i < pair.second; ++i) {
|
||||
DfgVertex* const srcp = pair.first[i].sourcep();
|
||||
const uint32_t lsb = vtxp->driverLsb(i);
|
||||
if (srcp == defaultp) continue;
|
||||
|
||||
const uint32_t lsb = vtxp->driverLo(i);
|
||||
const uint32_t msb = lsb + srcp->width() - 1;
|
||||
// If it does not cover the searched bit range, move on
|
||||
drivers.emplace_back(srcp, lsb, msb);
|
||||
// Check if this driver covers any of the bits, then we can't use whole default
|
||||
if (m_msb >= lsb && msb >= m_lsb) tryWholeDefault = false;
|
||||
// If it does not cover the whole searched bit range, move on
|
||||
if (m_lsb < lsb || msb < m_msb) continue;
|
||||
// Trace this driver
|
||||
// Driver covers whole search range, trace that and we are done
|
||||
SET_RESULT(trace(srcp, m_msb - lsb, m_lsb - lsb));
|
||||
return;
|
||||
}
|
||||
|
||||
// Trace the default driver if no other drivers cover the searched range
|
||||
if (defaultp && tryWholeDefault) {
|
||||
SET_RESULT(trace(defaultp, m_msb, m_lsb));
|
||||
return;
|
||||
}
|
||||
|
||||
// Hard case: We need to combine multiple drivers to produce the searched bit range
|
||||
|
||||
// Sort ragnes (they are non-overlapping)
|
||||
std::sort(drivers.begin(), drivers.end(),
|
||||
[](const Driver& a, const Driver& b) { return a.m_lsb < b.m_lsb; });
|
||||
|
||||
// Gather terms
|
||||
std::vector<DfgVertex*> termps;
|
||||
for (const Driver& driver : drivers) {
|
||||
// Driver is below the searched LSB, move on
|
||||
if (m_lsb > driver.m_msb) continue;
|
||||
// Driver is above the searched MSB, done
|
||||
if (driver.m_lsb > m_msb) break;
|
||||
// Gap below this driver, trace default to fill it
|
||||
if (driver.m_lsb > m_lsb) {
|
||||
if (!defaultp) return;
|
||||
DfgVertex* const termp = trace(defaultp, driver.m_lsb - 1, m_lsb);
|
||||
if (!termp) return;
|
||||
termps.emplace_back(termp);
|
||||
m_lsb = driver.m_lsb;
|
||||
}
|
||||
// Driver covers searched range, pick the needed/available bits
|
||||
uint32_t lim = std::min(m_msb, driver.m_msb);
|
||||
DfgVertex* const termp
|
||||
= trace(driver.m_vtxp, lim - driver.m_lsb, m_lsb - driver.m_lsb);
|
||||
if (!termp) return;
|
||||
termps.emplace_back(termp);
|
||||
m_lsb = lim + 1;
|
||||
}
|
||||
if (m_msb >= m_lsb) {
|
||||
if (!defaultp) return;
|
||||
DfgVertex* const termp = trace(defaultp, m_msb, m_lsb);
|
||||
if (!termp) return;
|
||||
termps.emplace_back(termp);
|
||||
}
|
||||
|
||||
// The earlier cheks cover the case when either a whole driver or the default covers
|
||||
// the whole range, so there should be at least 2 terms required here.
|
||||
UASSERT_OBJ(termps.size() >= 2, vtxp, "Should have returned in special cases");
|
||||
|
||||
// Concatenate all terms and set result
|
||||
DfgVertex* resp = termps.front();
|
||||
for (size_t i = 1; i < termps.size(); ++i) {
|
||||
DfgVertex* const termp = termps[i];
|
||||
DfgConcat* const catp = make<DfgConcat>(termp, resp->width() + termp->width());
|
||||
catp->rhsp(resp);
|
||||
catp->lhsp(termp);
|
||||
resp = catp;
|
||||
}
|
||||
SET_RESULT(resp);
|
||||
}
|
||||
|
||||
void visit(DfgVarPacked* vtxp) override {
|
||||
|
|
@ -315,12 +392,24 @@ class TraceDriver final : public DfgVisitor {
|
|||
}
|
||||
// Find driver
|
||||
if (!varp->srcp()) return;
|
||||
DfgSpliceArray* const splicep = varp->srcp()->cast<DfgSpliceArray>();
|
||||
if (!splicep) return;
|
||||
DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT());
|
||||
if (!driverp) return;
|
||||
|
||||
// Driver might be a splice
|
||||
if (DfgSpliceArray* const splicep = varp->srcp()->cast<DfgSpliceArray>()) {
|
||||
DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT());
|
||||
if (!driverp) return;
|
||||
DfgUnitArray* const uap = driverp->cast<DfgUnitArray>();
|
||||
if (!uap) return;
|
||||
// Trace the driver
|
||||
SET_RESULT(trace(uap->srcp(), m_msb, m_lsb));
|
||||
return;
|
||||
}
|
||||
|
||||
// Or a unit array
|
||||
DfgUnitArray* const uap = varp->srcp()->cast<DfgUnitArray>();
|
||||
if (!uap) return;
|
||||
// Trace the driver
|
||||
SET_RESULT(trace(driverp, m_msb, m_lsb));
|
||||
UASSERT_OBJ(idxp->toSizeT() == 0, vtxp, "Array Index out of range");
|
||||
SET_RESULT(trace(uap->srcp(), m_msb, m_lsb));
|
||||
}
|
||||
|
||||
void visit(DfgConcat* vtxp) override {
|
||||
|
|
@ -629,9 +718,12 @@ class IndependentBits final : public DfgVisitor {
|
|||
void visit(DfgSplicePacked* vtxp) override {
|
||||
// Combine the masks of all drivers
|
||||
V3Number& m = MASK(vtxp);
|
||||
DfgVertex* const defaultp = vtxp->defaultp();
|
||||
if (defaultp) m = MASK(defaultp);
|
||||
vtxp->forEachSourceEdge([&](DfgEdge& edge, size_t i) {
|
||||
const DfgVertex* const srcp = edge.sourcep();
|
||||
m.opSelInto(MASK(srcp), vtxp->driverLsb(i), srcp->width());
|
||||
if (srcp == defaultp) return;
|
||||
m.opSelInto(MASK(srcp), vtxp->driverLo(i), srcp->width());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -656,8 +748,10 @@ class IndependentBits final : public DfgVisitor {
|
|||
if (!splicep) return;
|
||||
DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT());
|
||||
if (!driverp) return;
|
||||
DfgUnitArray* const uap = driverp->cast<DfgUnitArray>();
|
||||
if (!uap) return;
|
||||
// Update mask
|
||||
MASK(vtxp) = MASK(driverp);
|
||||
MASK(vtxp) = MASK(uap->srcp());
|
||||
}
|
||||
|
||||
void visit(DfgConcat* vtxp) override {
|
||||
|
|
@ -829,7 +923,9 @@ class IndependentBits final : public DfgVisitor {
|
|||
if (VN_IS(currp->dtypep(), UnpackArrayDType)) {
|
||||
// For an unpacked array vertex, just enque it's sinks.
|
||||
// (There can be no loops through arrays directly)
|
||||
currp->forEachSink([&](DfgVertex& vtx) { workList.emplace_back(&vtx); });
|
||||
currp->forEachSink([&](DfgVertex& vtx) {
|
||||
if (vtx.getUser<uint64_t>() == m_component) workList.emplace_back(&vtx);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -843,7 +939,9 @@ class IndependentBits final : public DfgVisitor {
|
|||
|
||||
// If mask changed, enqueue sinks
|
||||
if (!prevMask.isCaseEq(maskCurr)) {
|
||||
currp->forEachSink([&](DfgVertex& vtx) { workList.emplace_back(&vtx); });
|
||||
currp->forEachSink([&](DfgVertex& vtx) {
|
||||
if (vtx.getUser<uint64_t>() == m_component) workList.emplace_back(&vtx);
|
||||
});
|
||||
|
||||
// Check the mask only ever contrects (no bit goes 0 -> 1)
|
||||
if (VL_UNLIKELY(v3Global.opt.debugCheck())) {
|
||||
|
|
@ -998,7 +1096,7 @@ class FixUpIndependentRanges final {
|
|||
}
|
||||
// Fall back on using the part of the variable (if dependent, or trace failed)
|
||||
if (!termp) {
|
||||
AstNodeDType* const dtypep = DfgVertex::dtypeForWidth(width);
|
||||
AstNodeDType* const dtypep = DfgGraph::dtypePacked(width);
|
||||
DfgSel* const selp = new DfgSel{dfg, vtxp->fileline(), dtypep};
|
||||
// Same component as 'vtxp', as reads 'vtxp' and will replace 'vtxp'
|
||||
selp->setUser<uint64_t>(vtxp->getUser<uint64_t>());
|
||||
|
|
@ -1079,7 +1177,7 @@ class FixUpIndependentRanges final {
|
|||
for (size_t i = 1; i < termps.size(); ++i) {
|
||||
DfgVertex* const termp = termps[i];
|
||||
const uint32_t catWidth = replacementp->width() + termp->width();
|
||||
AstNodeDType* const dtypep = DfgVertex::dtypeForWidth(catWidth);
|
||||
AstNodeDType* const dtypep = DfgGraph::dtypePacked(catWidth);
|
||||
DfgConcat* const catp = new DfgConcat{dfg, flp, dtypep};
|
||||
catp->rhsp(replacementp);
|
||||
catp->lhsp(termp);
|
||||
|
|
|
|||
|
|
@ -69,45 +69,23 @@ class V3DfgAstToDfgContext final : public V3DfgSubContext {
|
|||
|
||||
public:
|
||||
// STATE
|
||||
VDouble0 m_coalescedAssignments; // Number of partial assignments coalesced
|
||||
VDouble0 m_inputEquations; // Number of input combinational equations
|
||||
VDouble0 m_inputs; // Number of input processes (logic constructs)
|
||||
VDouble0 m_representable; // Number of combinational equations representable
|
||||
VDouble0 m_nonRepDType; // Equations non-representable due to data type
|
||||
VDouble0 m_nonRepImpure; // Equations non-representable due to impure node
|
||||
VDouble0 m_nonRepTiming; // Equations non-representable due to timing control
|
||||
VDouble0 m_nonRepLhs; // Equations non-representable due to lhs
|
||||
VDouble0 m_nonRepNode; // Equations non-representable due to node type
|
||||
VDouble0 m_nonRepUnknown; // Equations non-representable due to unknown node
|
||||
VDouble0 m_nonRepVarRef; // Equations non-representable due to variable reference
|
||||
VDouble0 m_nonRepWidth; // Equations non-representable due to width mismatch
|
||||
VDouble0 m_nonRepCfg; // Non-representable due to failing to build CFG
|
||||
VDouble0 m_nonRepLive; // Non-representable due to failing liveness analysis
|
||||
VDouble0 m_nonRepVar; // Non-representable due to unsupported variable properties
|
||||
|
||||
private:
|
||||
V3DfgAstToDfgContext(V3DfgContext& ctx, const std::string& label)
|
||||
: V3DfgSubContext{ctx, label, "AstToDfg"} {}
|
||||
~V3DfgAstToDfgContext() {
|
||||
addStat("coalesced assignments", m_coalescedAssignments);
|
||||
addStat("input equations", m_inputEquations);
|
||||
addStat("input processes", m_inputs);
|
||||
addStat("representable", m_representable);
|
||||
addStat("non-representable (dtype)", m_nonRepDType);
|
||||
addStat("non-representable (impure)", m_nonRepImpure);
|
||||
addStat("non-representable (timing)", m_nonRepTiming);
|
||||
addStat("non-representable (lhs)", m_nonRepLhs);
|
||||
addStat("non-representable (node)", m_nonRepNode);
|
||||
addStat("non-representable (unknown)", m_nonRepUnknown);
|
||||
addStat("non-representable (var ref)", m_nonRepVarRef);
|
||||
addStat("non-representable (width)", m_nonRepWidth);
|
||||
|
||||
addStat("non-representable (cfg)", m_nonRepCfg);
|
||||
addStat("non-representable (live)", m_nonRepLive);
|
||||
addStat("non-representable (var)", m_nonRepVar);
|
||||
// Check the stats are consistent
|
||||
UASSERT(m_representable //
|
||||
+ m_nonRepDType //
|
||||
+ m_nonRepImpure //
|
||||
+ m_nonRepTiming //
|
||||
+ m_nonRepLhs //
|
||||
+ m_nonRepNode //
|
||||
+ m_nonRepUnknown //
|
||||
+ m_nonRepVarRef //
|
||||
+ m_nonRepWidth //
|
||||
== m_inputEquations,
|
||||
UASSERT(m_representable + m_nonRepCfg + m_nonRepLive + m_nonRepVar == m_inputs,
|
||||
"Inconsistent statistics");
|
||||
}
|
||||
};
|
||||
|
|
@ -167,12 +145,18 @@ class V3DfgDfgToAstContext final : public V3DfgSubContext {
|
|||
|
||||
public:
|
||||
// STATE
|
||||
VDouble0 m_outputVariables; // Number of output variables
|
||||
VDouble0 m_outputVariablesWithDefault; // Number of outptu variables with a default driver
|
||||
VDouble0 m_resultEquations; // Number of result combinational equations
|
||||
|
||||
private:
|
||||
V3DfgDfgToAstContext(V3DfgContext& ctx, const std::string& label)
|
||||
: V3DfgSubContext{ctx, label, "DfgToAst"} {}
|
||||
~V3DfgDfgToAstContext() { addStat("result equations", m_resultEquations); }
|
||||
~V3DfgDfgToAstContext() {
|
||||
addStat("output variables", m_outputVariables);
|
||||
addStat("output variables with default driver", m_outputVariablesWithDefault);
|
||||
addStat("result equations", m_resultEquations);
|
||||
}
|
||||
};
|
||||
class V3DfgEliminateVarsContext final : public V3DfgSubContext {
|
||||
// Only V3DfgContext can create an instance
|
||||
|
|
@ -223,6 +207,115 @@ private:
|
|||
: V3DfgSubContext{ctx, label, "Regularize"} {}
|
||||
~V3DfgRegularizeContext() { addStat("temporaries introduced", m_temporariesIntroduced); }
|
||||
};
|
||||
class V3DfgSynthesisContext final : public V3DfgSubContext {
|
||||
// Only V3DfgContext can create an instance
|
||||
friend class V3DfgContext;
|
||||
|
||||
public:
|
||||
// STATE
|
||||
|
||||
// Stats for conversion
|
||||
struct {
|
||||
// Inputs
|
||||
VDouble0 inputAssignments; // Number of input assignments
|
||||
VDouble0 inputExpressions; // Number of input equations
|
||||
// Successful
|
||||
VDouble0 representable; // Number of representable constructs
|
||||
// Unsuccessful
|
||||
VDouble0 nonRepImpure; // Non representable: impure
|
||||
VDouble0 nonRepDType; // Non representable: unsupported data type
|
||||
VDouble0 nonRepLValue; // Non representable: unsupported LValue form
|
||||
VDouble0 nonRepVarRef; // Non representable: unsupported var reference
|
||||
VDouble0 nonRepNode; // Non representable: unsupported AstNode type
|
||||
VDouble0 nonRepUnknown; // Non representable: unhandled AstNode type
|
||||
} m_conv;
|
||||
|
||||
// Stats for synthesis
|
||||
struct {
|
||||
// Inputs
|
||||
VDouble0 inputAlways; // Number of always blocks attempted
|
||||
VDouble0 inputAssign; // Number of continuous assignments attempted
|
||||
// Successful
|
||||
VDouble0 synthAlways; // Number of always blocks successfully synthesized
|
||||
VDouble0 synthAssign; // Number of continuous assignments successfully synthesized
|
||||
// Unsuccessful
|
||||
VDouble0 nonSynConv; // Non synthesizable: non representable (above)
|
||||
VDouble0 nonSynExtWrite; // Non synthesizable: has externally written variable
|
||||
VDouble0 nonSynLoop; // Non synthesizable: loop in CFG
|
||||
VDouble0 nonSynStmt; // Non synthesizable: unsupported statement
|
||||
VDouble0 nonSynMultidrive; // Non synthesizable: multidriven value within statement
|
||||
VDouble0 nonSynArray; // Non synthesizable: array type unhandled
|
||||
VDouble0 nonSynLatch; // Non synthesizable: maybe latch
|
||||
VDouble0 nonSynJoinInput; // Non synthesizable: needing to join input variable
|
||||
VDouble0 nonSynFalseWrite; // Non synthesizable: does not write output
|
||||
// Reverted
|
||||
VDouble0 revertNonSyn; // Reverted due to being driven from non-synthesizable vertex
|
||||
VDouble0 revertMultidrive; // Reverted due to multiple drivers
|
||||
|
||||
} m_synt;
|
||||
|
||||
private:
|
||||
V3DfgSynthesisContext(V3DfgContext& ctx, const std::string& label)
|
||||
: V3DfgSubContext{ctx, label, "Synthesis"} {}
|
||||
~V3DfgSynthesisContext() {
|
||||
// Conversion statistics
|
||||
addStat("conv / input assignments", m_conv.inputAssignments);
|
||||
addStat("conv / input expressions", m_conv.inputExpressions);
|
||||
addStat("conv / representable inputs", m_conv.representable);
|
||||
addStat("conv / non-representable (impure)", m_conv.nonRepImpure);
|
||||
addStat("conv / non-representable (dtype)", m_conv.nonRepDType);
|
||||
addStat("conv / non-representable (lhs)", m_conv.nonRepLValue);
|
||||
addStat("conv / non-representable (varref)", m_conv.nonRepVarRef);
|
||||
addStat("conv / non-representable (node)", m_conv.nonRepNode);
|
||||
addStat("conv / non-representable (unknown)", m_conv.nonRepUnknown);
|
||||
VDouble0 nConvNonRep;
|
||||
nConvNonRep += m_conv.nonRepImpure;
|
||||
nConvNonRep += m_conv.nonRepDType;
|
||||
nConvNonRep += m_conv.nonRepLValue;
|
||||
nConvNonRep += m_conv.nonRepVarRef;
|
||||
nConvNonRep += m_conv.nonRepNode;
|
||||
nConvNonRep += m_conv.nonRepUnknown;
|
||||
VDouble0 nConvExpect;
|
||||
nConvExpect += m_conv.inputAssignments;
|
||||
nConvExpect += m_conv.inputExpressions;
|
||||
nConvExpect -= m_conv.representable;
|
||||
UASSERT(nConvNonRep == nConvExpect, "Inconsistent statistics / conv");
|
||||
|
||||
// Synthesis statistics
|
||||
addStat("synt / always blocks considered", m_synt.inputAlways);
|
||||
addStat("synt / always blocks synthesized", m_synt.synthAlways);
|
||||
addStat("synt / continuous assignments considered", m_synt.inputAssign);
|
||||
addStat("synt / continuous assignments synthesized", m_synt.synthAssign);
|
||||
addStat("synt / non-synthesizable (conv)", m_synt.nonSynConv);
|
||||
addStat("synt / non-synthesizable (ext write)", m_synt.nonSynExtWrite);
|
||||
addStat("synt / non-synthesizable (loop)", m_synt.nonSynLoop);
|
||||
addStat("synt / non-synthesizable (stmt)", m_synt.nonSynStmt);
|
||||
addStat("synt / non-synthesizable (multidrive)", m_synt.nonSynMultidrive);
|
||||
addStat("synt / non-synthesizable (array)", m_synt.nonSynArray);
|
||||
addStat("synt / non-synthesizable (latch)", m_synt.nonSynLatch);
|
||||
addStat("synt / non-synthesizable (join input)", m_synt.nonSynJoinInput);
|
||||
addStat("synt / non-synthesizable (false write)", m_synt.nonSynFalseWrite);
|
||||
addStat("synt / reverted (non-synthesizable)", m_synt.revertNonSyn);
|
||||
addStat("synt / reverted (multidrive)", m_synt.revertMultidrive);
|
||||
|
||||
VDouble0 nSyntNonSyn;
|
||||
nSyntNonSyn += m_synt.nonSynConv;
|
||||
nSyntNonSyn += m_synt.nonSynExtWrite;
|
||||
nSyntNonSyn += m_synt.nonSynLoop;
|
||||
nSyntNonSyn += m_synt.nonSynStmt;
|
||||
nSyntNonSyn += m_synt.nonSynMultidrive;
|
||||
nSyntNonSyn += m_synt.nonSynArray;
|
||||
nSyntNonSyn += m_synt.nonSynLatch;
|
||||
nSyntNonSyn += m_synt.nonSynJoinInput;
|
||||
nSyntNonSyn += m_synt.nonSynFalseWrite;
|
||||
VDouble0 nSyntExpect;
|
||||
nSyntExpect += m_synt.inputAlways;
|
||||
nSyntExpect += m_synt.inputAssign;
|
||||
nSyntExpect -= m_synt.synthAlways;
|
||||
nSyntExpect -= m_synt.synthAssign;
|
||||
UASSERT(nSyntNonSyn == nSyntExpect, "Inconsistent statistics / synt");
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Top level V3DfgContext
|
||||
|
|
@ -248,6 +341,7 @@ public:
|
|||
V3DfgEliminateVarsContext m_eliminateVarsContext{*this, m_label};
|
||||
V3DfgPeepholeContext m_peepholeContext{*this, m_label};
|
||||
V3DfgRegularizeContext m_regularizeContext{*this, m_label};
|
||||
V3DfgSynthesisContext m_synthContext{*this, m_label};
|
||||
|
||||
// Node pattern collector
|
||||
V3DfgPatternStats m_patternStats;
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ class ExtractCyclicComponents final {
|
|||
}
|
||||
UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexVar' sub-type");
|
||||
clonep->setUser<uint64_t>(component);
|
||||
clonep->tmpForp(vtx.tmpForp());
|
||||
}
|
||||
return *clonep;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,12 +93,23 @@ class DfgToAstVisitor final : DfgVisitor {
|
|||
|
||||
// TYPES
|
||||
using VariableType = std::conditional_t<T_Scoped, AstVarScope, AstVar>;
|
||||
struct Assignment final {
|
||||
FileLine* m_flp;
|
||||
AstNodeExpr* m_lhsp;
|
||||
AstNodeExpr* m_rhsp;
|
||||
Assignment() = delete;
|
||||
Assignment(FileLine* flp, AstNodeExpr* lhsp, AstNodeExpr* m_rhsp)
|
||||
: m_flp{flp}
|
||||
, m_lhsp{lhsp}
|
||||
, m_rhsp{m_rhsp} {}
|
||||
};
|
||||
|
||||
// STATE
|
||||
|
||||
AstModule* const m_modp; // The parent/result module - This is nullptr when T_Scoped
|
||||
V3DfgDfgToAstContext& m_ctx; // The context for stats
|
||||
AstNodeExpr* m_resultp = nullptr; // The result node of the current traversal
|
||||
std::vector<Assignment> m_assignments; // Assignments to currently rendered variable
|
||||
std::vector<Assignment> m_defaults; // Default assignments to currently rendered variable
|
||||
|
||||
// METHODS
|
||||
|
||||
|
|
@ -147,57 +158,69 @@ class DfgToAstVisitor final : DfgVisitor {
|
|||
return resultp;
|
||||
}
|
||||
|
||||
void convertDriver(AstScope* scopep, FileLine* flp, AstNodeExpr* lhsp, DfgVertex* driverp) {
|
||||
if (!driverp->is<DfgVertexSplice>()) {
|
||||
// Base case: assign vertex to current lhs
|
||||
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(driverp);
|
||||
AstAssignW* const assignp = new AstAssignW{flp, lhsp, rhsp};
|
||||
lhsp->foreach([flp](AstNode* nodep) { nodep->fileline(flp); });
|
||||
if VL_CONSTEXPR_CXX17 (T_Scoped) {
|
||||
// Add it to the scope holding the target variable
|
||||
getCombActive(scopep)->addStmtsp(assignp);
|
||||
} else {
|
||||
// Add it to the parend module of the DfgGraph
|
||||
m_modp->addStmtsp(assignp);
|
||||
}
|
||||
++m_ctx.m_resultEquations;
|
||||
return;
|
||||
}
|
||||
|
||||
void convertDriver(std::vector<Assignment>& assignments, FileLine* flp, AstNodeExpr* lhsp,
|
||||
DfgVertex* driverp) {
|
||||
if (DfgSplicePacked* const sPackedp = driverp->cast<DfgSplicePacked>()) {
|
||||
// Partial assignment of packed value
|
||||
// Render defaults first
|
||||
if (DfgVertex* const defaultp = sPackedp->defaultp()) {
|
||||
convertDriver(m_defaults, flp, lhsp, defaultp);
|
||||
}
|
||||
// Render partial assignments of packed value
|
||||
sPackedp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) {
|
||||
UASSERT_OBJ(edge.sourcep(), sPackedp, "Should have removed undriven sources");
|
||||
DfgVertex* const srcp = edge.sourcep();
|
||||
if (srcp == sPackedp->defaultp()) return;
|
||||
// Create Sel
|
||||
FileLine* const dflp = sPackedp->driverFileLine(i);
|
||||
AstConst* const lsbp = new AstConst{dflp, sPackedp->driverLsb(i)};
|
||||
const int width = static_cast<int>(edge.sourcep()->width());
|
||||
AstConst* const lsbp = new AstConst{dflp, sPackedp->driverLo(i)};
|
||||
const int width = static_cast<int>(srcp->width());
|
||||
AstSel* const nLhsp = new AstSel{dflp, lhsp->cloneTreePure(false), lsbp, width};
|
||||
// Convert source
|
||||
convertDriver(scopep, dflp, nLhsp, edge.sourcep());
|
||||
// Delete Sel if not consumed
|
||||
if (!nLhsp->backp()) VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp);
|
||||
convertDriver(assignments, dflp, nLhsp, srcp);
|
||||
// Delete Sel - was cloned
|
||||
VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (DfgSpliceArray* const sArrayp = driverp->cast<DfgSpliceArray>()) {
|
||||
UASSERT_OBJ(!sArrayp->defaultp(), flp, "Should not have a default assignment yet");
|
||||
// Partial assignment of array variable
|
||||
sArrayp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) {
|
||||
UASSERT_OBJ(edge.sourcep(), sArrayp, "Should have removed undriven sources");
|
||||
DfgVertex* const driverp = edge.sourcep();
|
||||
UASSERT_OBJ(driverp, sArrayp, "Should have removed undriven sources");
|
||||
UASSERT_OBJ(driverp->size() == 1, driverp, "We only handle single elements");
|
||||
// Create ArraySel
|
||||
FileLine* const dflp = sArrayp->driverFileLine(i);
|
||||
AstConst* const idxp = new AstConst{dflp, sArrayp->driverIndex(i)};
|
||||
AstConst* const idxp = new AstConst{dflp, sArrayp->driverLo(i)};
|
||||
AstArraySel* const nLhsp = new AstArraySel{dflp, lhsp->cloneTreePure(false), idxp};
|
||||
// Convert source
|
||||
convertDriver(scopep, dflp, nLhsp, edge.sourcep());
|
||||
// Delete ArraySel if not consumed
|
||||
if (!nLhsp->backp()) VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp);
|
||||
if (DfgUnitArray* const uap = driverp->cast<DfgUnitArray>()) {
|
||||
convertDriver(assignments, dflp, nLhsp, uap->srcp());
|
||||
} else {
|
||||
convertDriver(assignments, dflp, nLhsp, driverp);
|
||||
}
|
||||
// Delete ArraySel - was cloned
|
||||
VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
driverp->v3fatalSrc("Unhandled DfgVertexSplice sub-type"); // LCOV_EXCL_LINE
|
||||
if (DfgUnitArray* const uap = driverp->cast<DfgUnitArray>()) {
|
||||
// Single element array being assigned a unit array. Needs an ArraySel.
|
||||
AstConst* const idxp = new AstConst{flp, 0};
|
||||
AstArraySel* const nLhsp = new AstArraySel{flp, lhsp->cloneTreePure(false), idxp};
|
||||
// Convert source
|
||||
convertDriver(assignments, flp, nLhsp, uap->srcp());
|
||||
// Delete ArraySel - was cloned
|
||||
VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp);
|
||||
return;
|
||||
}
|
||||
|
||||
// Base case: assign vertex to current lhs
|
||||
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(driverp);
|
||||
assignments.emplace_back(flp, lhsp->cloneTreePure(false), rhsp);
|
||||
++m_ctx.m_resultEquations;
|
||||
return;
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
|
|
@ -245,13 +268,53 @@ class DfgToAstVisitor final : DfgVisitor {
|
|||
// If there is no driver (this vertex is an input to the graph), then nothing to do.
|
||||
if (!vtx.srcp()) continue;
|
||||
|
||||
++m_ctx.m_outputVariables;
|
||||
|
||||
// Render variable assignments
|
||||
FileLine* const flp = vtx.driverFileLine() ? vtx.driverFileLine() : vtx.fileline();
|
||||
AstScope* const scopep = T_Scoped ? vtx.varScopep()->scopep() : nullptr;
|
||||
AstVarRef* const lhsp = new AstVarRef{flp, getNode(&vtx), VAccess::WRITE};
|
||||
convertDriver(scopep, flp, lhsp, vtx.srcp());
|
||||
// convetDriver clones and might not use up the original lhsp
|
||||
if (!lhsp->backp()) VL_DO_DANGLING(lhsp->deleteTree(), lhsp);
|
||||
convertDriver(m_assignments, flp, lhsp, vtx.srcp());
|
||||
// convetDriver always clones lhsp
|
||||
VL_DO_DANGLING(lhsp->deleteTree(), lhsp);
|
||||
|
||||
if (m_defaults.empty()) {
|
||||
// If there are no default assignments, render each driver as an AssignW
|
||||
for (const Assignment& a : m_assignments) {
|
||||
AstAssignW* const assignp = new AstAssignW{a.m_flp, a.m_lhsp, a.m_rhsp};
|
||||
a.m_lhsp->foreach([&a](AstNode* nodep) { nodep->fileline(a.m_flp); });
|
||||
if VL_CONSTEXPR_CXX17 (T_Scoped) {
|
||||
// Add it to the scope holding the target variable
|
||||
getCombActive(vtx.varScopep()->scopep())->addStmtsp(assignp);
|
||||
} else {
|
||||
// Add it to the parent module of the DfgGraph
|
||||
m_modp->addStmtsp(assignp);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
++m_ctx.m_outputVariablesWithDefault;
|
||||
// If there are default assignments, render all drivers under an AstAlways
|
||||
AstAlways* const alwaysp
|
||||
= new AstAlways{vtx.fileline(), VAlwaysKwd::ALWAYS_COMB, nullptr, nullptr};
|
||||
if VL_CONSTEXPR_CXX17 (T_Scoped) {
|
||||
// Add it to the scope holding the target variable
|
||||
getCombActive(vtx.varScopep()->scopep())->addStmtsp(alwaysp);
|
||||
} else {
|
||||
// Add it to the parent module of the DfgGraph
|
||||
m_modp->addStmtsp(alwaysp);
|
||||
}
|
||||
for (const Assignment& a : m_defaults) {
|
||||
AstAssign* const assignp = new AstAssign{a.m_flp, a.m_lhsp, a.m_rhsp};
|
||||
a.m_lhsp->foreach([&a](AstNode* nodep) { nodep->fileline(a.m_flp); });
|
||||
alwaysp->addStmtsp(assignp);
|
||||
}
|
||||
for (const Assignment& a : m_assignments) {
|
||||
AstAssign* const assignp = new AstAssign{a.m_flp, a.m_lhsp, a.m_rhsp};
|
||||
a.m_lhsp->foreach([&a](AstNode* nodep) { nodep->fileline(a.m_flp); });
|
||||
alwaysp->addStmtsp(assignp);
|
||||
}
|
||||
}
|
||||
m_assignments.clear();
|
||||
m_defaults.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -252,7 +252,11 @@ class DataflowOptimize final {
|
|||
V3DfgContext m_ctx; // The context holding values that need to persist across multiple graphs
|
||||
|
||||
void optimize(DfgGraph& dfg) {
|
||||
if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "whole-input");
|
||||
if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "dfg-in");
|
||||
|
||||
// Synthesize DfgLogic vertices
|
||||
V3DfgPasses::synthesize(dfg, m_ctx);
|
||||
if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "synth");
|
||||
|
||||
// Extract the cyclic sub-graphs. We do this because a lot of the optimizations assume a
|
||||
// DAG, and large, mostly acyclic graphs could not be optimized due to the presence of
|
||||
|
|
@ -310,7 +314,7 @@ class DataflowOptimize final {
|
|||
// Merge back under the main DFG (we will convert everything back in one go)
|
||||
dfg.mergeGraphs(std::move(cyclicComponents));
|
||||
|
||||
if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "whole-optimized");
|
||||
if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "dfg-out");
|
||||
}
|
||||
|
||||
DataflowOptimize(AstNetlist* netlistp, const string& label)
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
|
|||
DfgVertex* const sentinelp = reinterpret_cast<DfgVertex*>(&dfg);
|
||||
DfgVertex* workListp = sentinelp;
|
||||
|
||||
// Add all unused vertices to the work list. This also allocates all DfgVertex::user.
|
||||
// Add all unused operation vertices to the work list. This also allocates all DfgVertex::user.
|
||||
for (DfgVertex& vtx : dfg.opVertices()) {
|
||||
if (vtx.hasSinks()) {
|
||||
// This vertex is used. Allocate user, but don't add to work list.
|
||||
|
|
@ -115,6 +115,19 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
|
|||
}
|
||||
}
|
||||
|
||||
// Also add all unused temporaries created during synthesis
|
||||
for (DfgVertexVar& vtx : dfg.varVertices()) {
|
||||
if (!vtx.tmpForp()) continue;
|
||||
if (vtx.hasSinks() || vtx.hasDfgRefs()) {
|
||||
// This vertex is used. Allocate user, but don't add to work list.
|
||||
vtx.setUser<DfgVertex*>(nullptr);
|
||||
} else {
|
||||
// This vertex is unused. Add to work list.
|
||||
vtx.setUser<DfgVertex*>(workListp);
|
||||
workListp = &vtx;
|
||||
}
|
||||
}
|
||||
|
||||
// Process the work list
|
||||
while (workListp != sentinelp) {
|
||||
// Pick up the head
|
||||
|
|
@ -123,12 +136,23 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
|
|||
workListp = vtxp->getUser<DfgVertex*>();
|
||||
// Prefetch next item
|
||||
VL_PREFETCH_RW(workListp);
|
||||
// This item is now off the work list
|
||||
vtxp->setUser<DfgVertex*>(nullptr);
|
||||
// DfgLogic should have been synthesized or removed
|
||||
UASSERT_OBJ(!vtxp->is<DfgLogic>(), vtxp, "Should not be DfgLogic");
|
||||
// If used, then nothing to do, so move on
|
||||
if (vtxp->hasSinks()) continue;
|
||||
// If temporary used in another graph, we need to keep it
|
||||
if (const DfgVertexVar* const varp = vtxp->cast<DfgVertexVar>()) {
|
||||
UASSERT_OBJ(varp->tmpForp(), varp, "Non-temporary variable should not be visited");
|
||||
if (varp->hasDfgRefs()) continue;
|
||||
}
|
||||
// Add sources of unused vertex to work list
|
||||
vtxp->forEachSource([&](DfgVertex& src) {
|
||||
// We only remove actual operation vertices in this loop
|
||||
if (src.is<DfgConst>() || src.is<DfgVertexVar>()) return;
|
||||
// We only remove actual operation vertices and synthesis temporaries in this loop
|
||||
if (src.is<DfgConst>()) return;
|
||||
const DfgVertexVar* const varp = src.cast<DfgVertexVar>();
|
||||
if (varp && !varp->tmpForp()) return;
|
||||
// If already in work list then nothing to do
|
||||
if (src.getUser<DfgVertex*>()) return;
|
||||
// Actually add to work list.
|
||||
|
|
@ -282,7 +306,7 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
|
|||
|
||||
// Required data types
|
||||
AstNodeDType* const idxDTypep = srcp->dtypep();
|
||||
AstNodeDType* const bitDTypep = DfgVertex::dtypeForWidth(1);
|
||||
AstNodeDType* const bitDTypep = DfgGraph::dtypePacked(1);
|
||||
AstUnpackArrayDType* const tabDTypep = new AstUnpackArrayDType{
|
||||
flp, bitDTypep, new AstRange{flp, static_cast<int>(nBits - 1), 0}};
|
||||
v3Global.rootp()->typeTablep()->addTypesp(tabDTypep);
|
||||
|
|
@ -453,8 +477,13 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
|
|||
DfgVarPacked* const varp = vtxp->cast<DfgVarPacked>();
|
||||
if (!varp) continue;
|
||||
|
||||
// Can't remove if it has external drivers
|
||||
if (!varp->isDrivenFullyByDfg()) continue;
|
||||
if (!varp->tmpForp()) {
|
||||
// Can't remove regular variable if it has external drivers
|
||||
if (!varp->isDrivenFullyByDfg()) continue;
|
||||
} else {
|
||||
// Can't remove partially driven used temporaries
|
||||
if (!varp->isDrivenFullyByDfg() && varp->hasSinks()) continue;
|
||||
}
|
||||
|
||||
// Can't remove if referenced external to the module/netlist
|
||||
if (varp->hasExtRefs()) continue;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ std::unique_ptr<DfgGraph> astToDfg(AstModule&, V3DfgContext&) VL_MT_DISABLED;
|
|||
// Same as above, but for the entire netlist, after V3Scope
|
||||
std::unique_ptr<DfgGraph> astToDfg(AstNetlist&, V3DfgContext&) VL_MT_DISABLED;
|
||||
|
||||
// Synthesize DfgLogic vertices into primitive operations.
|
||||
// Removes all DfgLogic (even those that were not synthesized).
|
||||
void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
|
||||
|
||||
// Attempt to make the given cyclic graph into an acyclic, or "less cyclic"
|
||||
// equivalent. If the returned pointer is null, then no improvement was
|
||||
// possible on the input graph. Otherwise the returned graph is an improvement
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
// STATE
|
||||
DfgGraph& m_dfg; // The DfgGraph being visited
|
||||
V3DfgPeepholeContext& m_ctx; // The config structure
|
||||
AstNodeDType* const m_bitDType = DfgVertex::dtypeForWidth(1); // Common, so grab it up front
|
||||
AstNodeDType* const m_bitDType = DfgGraph::dtypePacked(1); // Common, so grab it up front
|
||||
// Head of work list. Note that we want all next pointers in the list to be non-zero (including
|
||||
// that of the last element). This allows as to do two important things: detect if an element
|
||||
// is in the list by checking for a non-zero next pointer, and easy prefetching without
|
||||
|
|
@ -205,7 +205,7 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
}
|
||||
|
||||
// Shorthand
|
||||
static AstNodeDType* dtypeForWidth(uint32_t width) { return DfgVertex::dtypeForWidth(width); }
|
||||
static AstNodeDType* dtypePacked(uint32_t width) { return DfgGraph::dtypePacked(width); }
|
||||
|
||||
// Create a 32-bit DfgConst vertex
|
||||
DfgConst* makeI32(FileLine* flp, uint32_t val) { return new DfgConst{m_dfg, flp, 32, val}; }
|
||||
|
|
@ -365,7 +365,7 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
// Concatenation dtypes need to be fixed up, other associative nodes preserve
|
||||
// types
|
||||
if VL_CONSTEXPR_CXX17 (std::is_same<DfgConcat, Vertex>::value) {
|
||||
childDtyptp = dtypeForWidth(bp->width() + cp->width());
|
||||
childDtyptp = dtypePacked(bp->width() + cp->width());
|
||||
}
|
||||
|
||||
Vertex* const childp = make<Vertex>(vtxp->fileline(), childDtyptp, bp, cp);
|
||||
|
|
@ -820,10 +820,10 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
const uint32_t lSelWidth = width - rSelWidth;
|
||||
|
||||
// The new Lhs vertex
|
||||
DfgSel* const newLhsp = make<DfgSel>(flp, dtypeForWidth(lSelWidth), lhsp, 0U);
|
||||
DfgSel* const newLhsp = make<DfgSel>(flp, dtypePacked(lSelWidth), lhsp, 0U);
|
||||
|
||||
// The new Rhs vertex
|
||||
DfgSel* const newRhsp = make<DfgSel>(flp, dtypeForWidth(rSelWidth), rhsp, lsb);
|
||||
DfgSel* const newRhsp = make<DfgSel>(flp, dtypePacked(rSelWidth), rhsp, lsb);
|
||||
|
||||
// The replacement Concat vertex
|
||||
DfgConcat* const newConcat
|
||||
|
|
@ -912,6 +912,38 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sel from a partial temporary
|
||||
if (DfgVarPacked* const varp = fromp->cast<DfgVarPacked>()) {
|
||||
if (varp->tmpForp() && varp->srcp()) {
|
||||
// Must be a splice, otherwise it would have been inlined
|
||||
DfgSplicePacked* const splicep = varp->srcp()->as<DfgSplicePacked>();
|
||||
|
||||
const auto pair = splicep->sourceEdges();
|
||||
for (size_t i = 0; i < pair.second; ++i) {
|
||||
DfgVertex* const driverp = pair.first[i].sourcep();
|
||||
// Ignore default, we won't select into that for now ...
|
||||
if (driverp == splicep->defaultp()) continue;
|
||||
|
||||
const uint32_t dLsb = splicep->driverLo(i);
|
||||
const uint32_t dMsb = dLsb + driverp->width() - 1;
|
||||
// If it does not cover the whole searched bit range, move on
|
||||
if (lsb < dLsb || dMsb < msb) continue;
|
||||
|
||||
// Replace with sel from driver
|
||||
APPLYING(PUSH_SEL_THROUGH_SPLICE) {
|
||||
DfgSel* const replacementp = make<DfgSel>(vtxp, driverp, lsb - dLsb);
|
||||
replace(vtxp, replacementp);
|
||||
// Special case just for this pattern: delete temporary if became unsued
|
||||
if (!varp->hasSinks() && !varp->hasDfgRefs()) {
|
||||
addToWorkList(splicep); // So it can be delete itself if unused
|
||||
VL_DO_DANGLING(varp->unlinkDelete(m_dfg), varp); // Delete it
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
|
|
@ -1179,20 +1211,40 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
}
|
||||
|
||||
void visit(DfgArraySel* vtxp) override {
|
||||
if (DfgConst* const idxp = vtxp->bitp()->cast<DfgConst>()) {
|
||||
if (DfgVarArray* const varp = vtxp->fromp()->cast<DfgVarArray>()) {
|
||||
if (varp->srcp() && !varp->varp()->isForced()) {
|
||||
if (DfgSpliceArray* const splicep = varp->srcp()->cast<DfgSpliceArray>()) {
|
||||
if (DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT())) {
|
||||
if (!driverp->is<DfgVertexSplice>()) {
|
||||
APPLYING(INLINE_ARRAYSEL) {
|
||||
replace(vtxp, driverp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DfgConst* const idxp = vtxp->bitp()->cast<DfgConst>();
|
||||
if (!idxp) return;
|
||||
DfgVarArray* const varp = vtxp->fromp()->cast<DfgVarArray>();
|
||||
if (!varp) return;
|
||||
if (varp->varp()->isForced()) return;
|
||||
DfgVertex* const srcp = varp->srcp();
|
||||
if (!srcp) return;
|
||||
|
||||
if (DfgSpliceArray* const splicep = srcp->cast<DfgSpliceArray>()) {
|
||||
DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT());
|
||||
if (!driverp) return;
|
||||
DfgUnitArray* const uap = driverp->cast<DfgUnitArray>();
|
||||
if (!uap) return;
|
||||
if (uap->srcp()->is<DfgVertexSplice>()) return;
|
||||
// If driven by a variable that had a Driver in DFG, it is partial
|
||||
if (DfgVertexVar* const dvarp = uap->srcp()->cast<DfgVertexVar>()) {
|
||||
if (dvarp->srcp()) return;
|
||||
}
|
||||
APPLYING(INLINE_ARRAYSEL_SPLICE) {
|
||||
replace(vtxp, uap->srcp());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (DfgUnitArray* const uap = srcp->cast<DfgUnitArray>()) {
|
||||
UASSERT_OBJ(idxp->toSizeT() == 0, vtxp, "Array index out of range");
|
||||
if (uap->srcp()->is<DfgSplicePacked>()) return;
|
||||
// If driven by a variable that had a Driver in DFG, it is partial
|
||||
if (DfgVertexVar* const dvarp = uap->srcp()->cast<DfgVertexVar>()) {
|
||||
if (dvarp->srcp()) return;
|
||||
}
|
||||
APPLYING(INLINE_ARRAYSEL_UNIT) {
|
||||
replace(vtxp, uap->srcp());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1257,8 +1309,7 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
if (lSelp->lsb() == rSelp->lsb() + rSelp->width()) {
|
||||
// Two consecutive Sels, make a single Sel.
|
||||
const uint32_t width = lSelp->width() + rSelp->width();
|
||||
return make<DfgSel>(flp, dtypeForWidth(width), rSelp->fromp(),
|
||||
rSelp->lsb());
|
||||
return make<DfgSel>(flp, dtypePacked(width), rSelp->fromp(), rSelp->lsb());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@
|
|||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_SEL) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_UNARY) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL_SPLICE) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL_UNIT) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \
|
||||
|
|
@ -47,6 +48,7 @@
|
|||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_NOT) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REPLICATE) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SHIFTL) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SPLICE) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_AND_WITH_ONES) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_AND_WITH_SELF) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_CONCAT_OF_ADJOINING_SELS) \
|
||||
|
|
|
|||
|
|
@ -45,15 +45,11 @@ class DfgRegularize final {
|
|||
// Ensure intermediate values used multiple times are written to variables
|
||||
for (DfgVertex& vtx : m_dfg.opVertices()) {
|
||||
const bool needsIntermediateVariable = [&]() {
|
||||
// Splice vertices represent partial assignments, so they need a variable
|
||||
// iff and only if they have a non-variable sink.
|
||||
if (vtx.is<DfgVertexSplice>()) {
|
||||
const bool hasNonVarSink
|
||||
= vtx.findSink<DfgVertex>([](const DfgVertex& snk) { //
|
||||
return !snk.is<DfgVertexVar>() && !snk.is<DfgVertexSplice>();
|
||||
});
|
||||
return hasNonVarSink;
|
||||
}
|
||||
// Splice vertices represent partial assignments. The must flow
|
||||
// into variables, so they should never need a temporary.
|
||||
if (vtx.is<DfgVertexSplice>()) return false;
|
||||
// Smilarly to splice, UnitArray should never need one eitehr
|
||||
if (vtx.is<DfgUnitArray>()) return false;
|
||||
// Operations without multiple sinks need no variables
|
||||
if (!vtx.hasMultipleSinks()) return false;
|
||||
// Array selects need no variables, they are just memory references
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -43,6 +43,8 @@ class DfgVertexVar VL_NOT_FINAL : public DfgVertexUnary {
|
|||
AstVarScope* const m_varScopep; // The AstVarScope associated with this vertex (not owned)
|
||||
// Location of driver of this variable. Only used for converting back to Ast. Might be nullptr.
|
||||
FileLine* m_driverFileLine = nullptr;
|
||||
// If this DfgVertexVar is a synthesized temporary, this is the Var/VarScope it stands for.
|
||||
AstNode* m_tmpForp = nullptr;
|
||||
|
||||
bool selfEquals(const DfgVertex& that) const final VL_MT_DISABLED;
|
||||
V3Hash selfHash() const final VL_MT_DISABLED;
|
||||
|
|
@ -64,6 +66,9 @@ public:
|
|||
FileLine* driverFileLine() const { return m_driverFileLine; }
|
||||
void driverFileLine(FileLine* flp) { m_driverFileLine = flp; }
|
||||
|
||||
AstNode* tmpForp() const { return m_tmpForp; }
|
||||
void tmpForp(AstNode* nodep) { m_tmpForp = nodep; }
|
||||
|
||||
bool isDrivenFullyByDfg() const {
|
||||
return srcp() && !srcp()->is<DfgVertexSplice>() && !varp()->isForced();
|
||||
}
|
||||
|
|
@ -100,10 +105,104 @@ public:
|
|||
}
|
||||
};
|
||||
class DfgVertexSplice VL_NOT_FINAL : public DfgVertexVariadic {
|
||||
protected:
|
||||
struct DriverData final {
|
||||
FileLine* m_flp; // Location of this driver
|
||||
uint32_t m_lo; // Low index of range driven by this driver
|
||||
DriverData() = delete;
|
||||
DriverData(FileLine* flp, uint32_t lo)
|
||||
: m_flp{flp}
|
||||
, m_lo{lo} {}
|
||||
};
|
||||
std::vector<DriverData> m_driverData; // Additional data associated with each driver
|
||||
|
||||
bool selfEquals(const DfgVertex& that) const override VL_MT_DISABLED;
|
||||
V3Hash selfHash() const override VL_MT_DISABLED;
|
||||
|
||||
public:
|
||||
DfgVertexSplice(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep)
|
||||
: DfgVertexVariadic{dfg, type, flp, dtypep, 1u} {}
|
||||
: DfgVertexVariadic{dfg, type, flp, dtypep, 2u} {
|
||||
// Add optional source for 'defaultp'
|
||||
addSource();
|
||||
}
|
||||
ASTGEN_MEMBERS_DfgVertexSplice;
|
||||
|
||||
std::pair<const DfgEdge*, size_t> sourceEdges() const override {
|
||||
const std::pair<const DfgEdge*, size_t> pair = DfgVertexVariadic::sourceEdges();
|
||||
UASSERT_OBJ(pair.second > 0, this, "default driver edge is missing");
|
||||
// If it has a default driver that's it
|
||||
if (pair.first->sourcep()) return pair;
|
||||
// Otherwise there is one less source
|
||||
return {pair.first + 1, pair.second - 1};
|
||||
}
|
||||
std::pair<DfgEdge*, size_t> sourceEdges() override {
|
||||
const auto pair = const_cast<const DfgVertexSplice*>(this)->sourceEdges();
|
||||
return {const_cast<DfgEdge*>(pair.first), pair.second};
|
||||
}
|
||||
|
||||
// Named getter/setter for optional default driver
|
||||
DfgVertex* defaultp() const { return DfgVertexVariadic::source(0); }
|
||||
void defaultp(DfgVertex* vtxp) {
|
||||
UASSERT_OBJ(!vtxp->is<DfgLogic>(), vtxp, "default driver can't be a DfgLogic");
|
||||
const bool found = findSourceEdge([vtxp](const DfgEdge& e, size_t) -> bool { //
|
||||
return e.sourcep() == vtxp;
|
||||
});
|
||||
UASSERT_OBJ(!found, this, "adding existing driver as default");
|
||||
DfgVertexVariadic::sourceEdge(0)->relinkSource(vtxp);
|
||||
}
|
||||
|
||||
// Add resolved driver
|
||||
void addDriver(FileLine* flp, uint32_t lo, DfgVertex* vtxp) {
|
||||
UASSERT_OBJ(!vtxp->is<DfgLogic>(), vtxp, "addDriver called with DfgLogic");
|
||||
UASSERT_OBJ(vtxp != defaultp(), this, "adding default driver as resolved");
|
||||
m_driverData.emplace_back(flp, lo);
|
||||
DfgVertexVariadic::addSource()->relinkSource(vtxp);
|
||||
}
|
||||
|
||||
FileLine* driverFileLine(size_t idx) const {
|
||||
UASSERT_OBJ(!defaultp() || idx > 0, this, "'driverFileLine' called on default driver");
|
||||
if (defaultp()) --idx;
|
||||
return m_driverData.at(idx).m_flp;
|
||||
}
|
||||
|
||||
uint32_t driverLo(size_t idx) const {
|
||||
UASSERT_OBJ(!defaultp() || idx > 0, this, "'driverLo' called on default driver");
|
||||
if (defaultp()) --idx;
|
||||
const DriverData& dd = m_driverData.at(idx);
|
||||
return dd.m_lo;
|
||||
}
|
||||
|
||||
DfgVertex* driverAt(size_t idx) const {
|
||||
const DfgEdge* const edgep = findSourceEdge([this, idx](const DfgEdge& e, size_t i) { //
|
||||
// Don't pick the default driver
|
||||
if (i == 0 && defaultp()) return false;
|
||||
return driverLo(i) == idx;
|
||||
});
|
||||
return edgep ? edgep->sourcep() : nullptr;
|
||||
}
|
||||
|
||||
// If drives the whole result explicitly (not through defaultp), this is
|
||||
// the actual driver this DfgVertexSplice can be replaced with.
|
||||
inline DfgVertex* wholep() const;
|
||||
|
||||
void resetSources() {
|
||||
m_driverData.clear();
|
||||
// Unlink default driver
|
||||
DfgVertex* const dp = defaultp();
|
||||
DfgVertexVariadic::sourceEdge(0)->unlinkSource();
|
||||
// Reset DfgVertexVariadic sources
|
||||
DfgVertexVariadic::resetSources();
|
||||
// Add back the default driver if present
|
||||
DfgEdge* const edgep = DfgVertexVariadic::addSource();
|
||||
if (dp) edgep->relinkSource(dp);
|
||||
}
|
||||
|
||||
const std::string srcName(size_t idx) const override {
|
||||
if (idx == 0 && defaultp()) return "default";
|
||||
const uint32_t lo = driverLo(idx);
|
||||
const uint32_t hi = lo + DfgVertexVariadic::source(idx + !defaultp())->size() - 1;
|
||||
return '[' + std::to_string(hi) + ':' + std::to_string(lo) + ']';
|
||||
}
|
||||
};
|
||||
|
||||
// === Concrete node types =====================================================
|
||||
|
|
@ -119,12 +218,8 @@ class DfgConst final : public DfgVertex {
|
|||
V3Hash selfHash() const override VL_MT_DISABLED;
|
||||
|
||||
public:
|
||||
DfgConst(DfgGraph& dfg, FileLine* flp, const V3Number& num)
|
||||
: DfgVertex{dfg, dfgType(), flp, dtypeForWidth(num.width())}
|
||||
, m_num{num} {}
|
||||
DfgConst(DfgGraph& dfg, FileLine* flp, uint32_t width, uint32_t value = 0)
|
||||
: DfgVertex{dfg, dfgType(), flp, dtypeForWidth(width)}
|
||||
, m_num{flp, static_cast<int>(width), value} {}
|
||||
inline DfgConst(DfgGraph& dfg, FileLine* flp, const V3Number& num);
|
||||
inline DfgConst(DfgGraph& dfg, FileLine* flp, uint32_t width, uint32_t value = 0);
|
||||
ASTGEN_MEMBERS_DfgConst;
|
||||
|
||||
V3Number& num() { return m_num; }
|
||||
|
|
@ -174,6 +269,7 @@ public:
|
|||
};
|
||||
|
||||
// === DfgVertexUnary ===
|
||||
|
||||
class DfgSel final : public DfgVertexUnary {
|
||||
// AstSel is ternary, but the 'widthp' is always constant and is hence redundant, and
|
||||
// 'lsbp' is very often constant. As AstSel is fairly common, we special case as a DfgSel for
|
||||
|
|
@ -196,6 +292,20 @@ public:
|
|||
const string srcName(size_t) const override { return "fromp"; }
|
||||
};
|
||||
|
||||
class DfgUnitArray final : public DfgVertexUnary {
|
||||
// This is a type adapter for modeling arrays. It's a single element array,
|
||||
// with the value of the single element being the source operand.
|
||||
public:
|
||||
DfgUnitArray(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep)
|
||||
: DfgVertexUnary{dfg, dfgType(), flp, dtypep} {
|
||||
UASSERT_OBJ(this->dtypep(), flp, "Non array DfgUnitArray");
|
||||
UASSERT_OBJ(this->size() == 1, flp, "DfgUnitArray must have a single element");
|
||||
}
|
||||
ASTGEN_MEMBERS_DfgUnitArray;
|
||||
|
||||
const std::string srcName(size_t) const override { return ""; }
|
||||
};
|
||||
|
||||
// === DfgVertexVar ===
|
||||
class DfgVarArray final : public DfgVertexVar {
|
||||
friend class DfgVertex;
|
||||
|
|
@ -228,94 +338,78 @@ public:
|
|||
ASTGEN_MEMBERS_DfgVarPacked;
|
||||
};
|
||||
|
||||
// === DfgVertexVariadic ===
|
||||
class DfgLogic final : public DfgVertexVariadic {
|
||||
// Generic vertex representing a whole combinational process
|
||||
AstNode* const m_nodep; // The Ast logic represented by this vertex
|
||||
const std::unique_ptr<const ControlFlowGraph> m_cfgp;
|
||||
// Vertices this logic was synthesized into. Excluding variables
|
||||
std::vector<DfgVertex*> m_synth;
|
||||
|
||||
public:
|
||||
DfgLogic(DfgGraph& dfg, AstAssignW* nodep)
|
||||
: DfgVertexVariadic{dfg, dfgType(), nodep->fileline(), nullptr, 1u}
|
||||
, m_nodep{nodep}
|
||||
, m_cfgp{nullptr} {}
|
||||
|
||||
DfgLogic(DfgGraph& dfg, AstAlways* nodep, std::unique_ptr<const ControlFlowGraph> cfgp)
|
||||
: DfgVertexVariadic{dfg, dfgType(), nodep->fileline(), nullptr, 1u}
|
||||
, m_nodep{nodep}
|
||||
, m_cfgp{std::move(cfgp)} {}
|
||||
|
||||
ASTGEN_MEMBERS_DfgLogic;
|
||||
|
||||
void addInput(DfgVertexVar* varp) { addSource()->relinkSource(varp); }
|
||||
|
||||
AstNode* nodep() const { return m_nodep; }
|
||||
const ControlFlowGraph& cfg() const { return *m_cfgp; }
|
||||
std::vector<DfgVertex*>& synth() { return m_synth; }
|
||||
const std::vector<DfgVertex*>& synth() const { return m_synth; }
|
||||
|
||||
const std::string srcName(size_t) const override { return ""; }
|
||||
};
|
||||
|
||||
class DfgUnresolved final : public DfgVertexVariadic {
|
||||
// Represents a collection of unresolved variable drivers before synthesis
|
||||
|
||||
public:
|
||||
DfgUnresolved(DfgGraph& dfg, DfgVertexVar* vtxp)
|
||||
: DfgVertexVariadic{dfg, dfgType(), vtxp->fileline(), vtxp->dtypep(), 1u} {}
|
||||
ASTGEN_MEMBERS_DfgUnresolved;
|
||||
|
||||
// Can only be driven by DfgLogic or DfgVertexSplice
|
||||
void addDriver(DfgLogic* vtxp) { addSource()->relinkSource(vtxp); }
|
||||
void addDriver(DfgVertexSplice* vtxp) { addSource()->relinkSource(vtxp); }
|
||||
|
||||
void clearSources() { DfgVertexVariadic::clearSources(); }
|
||||
|
||||
DfgVertex* singleSource() const { return arity() == 1 ? source(0) : nullptr; }
|
||||
|
||||
const std::string srcName(size_t) const override { return ""; }
|
||||
};
|
||||
|
||||
// === DfgVertexSplice ===
|
||||
class DfgSpliceArray final : public DfgVertexSplice {
|
||||
friend class DfgVertex;
|
||||
friend class DfgVisitor;
|
||||
|
||||
struct DriverData final {
|
||||
FileLine* m_flp; // Location of this driver
|
||||
uint32_t m_index; // Array index driven by this driver (or low index of range)
|
||||
DriverData() = delete;
|
||||
DriverData(FileLine* flp, uint32_t index)
|
||||
: m_flp{flp}
|
||||
, m_index{index} {}
|
||||
};
|
||||
|
||||
std::vector<DriverData> m_driverData; // Additional data associated with each driver
|
||||
|
||||
bool selfEquals(const DfgVertex& that) const override VL_MT_DISABLED;
|
||||
V3Hash selfHash() const override VL_MT_DISABLED;
|
||||
|
||||
public:
|
||||
DfgSpliceArray(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep)
|
||||
: DfgVertexSplice{dfg, dfgType(), flp, dtypep} {
|
||||
UASSERT_OBJ(VN_IS(dtypep, UnpackArrayDType), flp, "Non array DfgSpliceArray");
|
||||
}
|
||||
ASTGEN_MEMBERS_DfgSpliceArray;
|
||||
|
||||
void addDriver(FileLine* flp, uint32_t index, DfgVertex* vtxp) {
|
||||
m_driverData.emplace_back(flp, index);
|
||||
DfgVertexVariadic::addSource()->relinkSource(vtxp);
|
||||
}
|
||||
|
||||
void resetSources() {
|
||||
m_driverData.clear();
|
||||
DfgVertexVariadic::resetSources();
|
||||
}
|
||||
|
||||
FileLine* driverFileLine(size_t i) const { return m_driverData.at(i).m_flp; }
|
||||
uint32_t driverIndex(size_t i) const { return m_driverData.at(i).m_index; }
|
||||
|
||||
DfgVertex* driverAt(size_t idx) const {
|
||||
const DfgEdge* const edgep = findSourceEdge([this, idx](const DfgEdge&, size_t i) { //
|
||||
return driverIndex(i) == idx;
|
||||
});
|
||||
return edgep ? edgep->sourcep() : nullptr;
|
||||
}
|
||||
|
||||
const std::string srcName(size_t idx) const override {
|
||||
return std::to_string(driverIndex(idx));
|
||||
}
|
||||
};
|
||||
class DfgSplicePacked final : public DfgVertexSplice {
|
||||
friend class DfgVertex;
|
||||
friend class DfgVisitor;
|
||||
|
||||
struct DriverData final {
|
||||
FileLine* m_flp; // Location of this driver
|
||||
uint32_t m_lsb; // LSB of range driven by this driver
|
||||
DriverData() = delete;
|
||||
DriverData(FileLine* flp, uint32_t lsb)
|
||||
: m_flp{flp}
|
||||
, m_lsb{lsb} {}
|
||||
};
|
||||
std::vector<DriverData> m_driverData; // Additional data associated with each driver
|
||||
|
||||
bool selfEquals(const DfgVertex& that) const override VL_MT_DISABLED;
|
||||
V3Hash selfHash() const override VL_MT_DISABLED;
|
||||
|
||||
public:
|
||||
DfgSplicePacked(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep)
|
||||
: DfgVertexSplice{dfg, dfgType(), flp, dtypep} {
|
||||
UASSERT_OBJ(!VN_IS(dtypep, UnpackArrayDType), flp, "Array DfgSplicePacked");
|
||||
}
|
||||
ASTGEN_MEMBERS_DfgSplicePacked;
|
||||
|
||||
void addDriver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) {
|
||||
m_driverData.emplace_back(flp, lsb);
|
||||
DfgVertexVariadic::addSource()->relinkSource(vtxp);
|
||||
}
|
||||
|
||||
void resetSources() {
|
||||
m_driverData.clear();
|
||||
DfgVertexVariadic::resetSources();
|
||||
}
|
||||
|
||||
FileLine* driverFileLine(size_t i) const { return m_driverData.at(i).m_flp; }
|
||||
uint32_t driverLsb(size_t i) const { return m_driverData.at(i).m_lsb; }
|
||||
|
||||
const std::string srcName(size_t idx) const override { return std::to_string(driverLsb(idx)); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1018,8 +1018,8 @@ class EmitVStreamVisitor final : public EmitVBaseVisitorConst {
|
|||
void putqs(AstNode*, const string& str) override { putbs(str); }
|
||||
|
||||
public:
|
||||
EmitVStreamVisitor(const AstNode* nodep, std::ostream& os, bool tracking)
|
||||
: EmitVBaseVisitorConst{false, false}
|
||||
EmitVStreamVisitor(const AstNode* nodep, std::ostream& os, bool tracking, bool suppressUnknown)
|
||||
: EmitVBaseVisitorConst{false, suppressUnknown}
|
||||
, m_os{os, V3OutFormatter::LA_VERILOG}
|
||||
, m_tracking{tracking} {
|
||||
iterateConst(const_cast<AstNode*>(nodep));
|
||||
|
|
@ -1031,7 +1031,11 @@ public:
|
|||
// EmitV class functions
|
||||
|
||||
void V3EmitV::verilogForTree(const AstNode* nodep, std::ostream& os) {
|
||||
{ EmitVStreamVisitor{nodep, os, /* tracking: */ false}; }
|
||||
{ EmitVStreamVisitor{nodep, os, /* tracking: */ false, false}; }
|
||||
}
|
||||
|
||||
void V3EmitV::debugVerilogForTree(const AstNode* nodep, std::ostream& os) {
|
||||
{ EmitVStreamVisitor{nodep, os, /* tracking: */ true, true}; }
|
||||
}
|
||||
|
||||
void V3EmitV::emitvFiles() {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class AstSenTree;
|
|||
class V3EmitV final {
|
||||
public:
|
||||
static void verilogForTree(const AstNode* nodep, std::ostream& os = std::cout);
|
||||
static void debugVerilogForTree(const AstNode* nodep, std::ostream& os);
|
||||
static void emitvFiles();
|
||||
static void debugEmitV(const string& filename);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1347,6 +1347,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
|
|||
DECL_OPTION("-fdfg-pre-inline", FOnOff, &m_fDfgPreInline);
|
||||
DECL_OPTION("-fdfg-post-inline", FOnOff, &m_fDfgPostInline);
|
||||
DECL_OPTION("-fdfg-scoped", FOnOff, &m_fDfgScoped);
|
||||
DECL_OPTION("-fdfg-synthesize-all", FOnOff, &m_fDfgSynthesizeAll);
|
||||
DECL_OPTION("-fexpand", FOnOff, &m_fExpand);
|
||||
DECL_OPTION("-ffunc-opt", CbFOnOff, [this](bool flag) { //
|
||||
m_fFuncSplitCat = flag;
|
||||
|
|
|
|||
|
|
@ -425,6 +425,7 @@ private:
|
|||
bool m_fDfgPreInline; // main switch: -fno-dfg-pre-inline and -fno-dfg
|
||||
bool m_fDfgPostInline; // main switch: -fno-dfg-post-inline and -fno-dfg
|
||||
bool m_fDfgScoped; // main switch: -fno-dfg-scoped and -fno-dfg
|
||||
bool m_fDfgSynthesizeAll = false; // main switch: -fdfg-synthesize-all
|
||||
bool m_fDeadAssigns; // main switch: -fno-dead-assigns: remove dead assigns
|
||||
bool m_fDeadCells; // main switch: -fno-dead-cells: remove dead cells
|
||||
bool m_fExpand; // main switch: -fno-expand: expansion of C macros
|
||||
|
|
@ -741,6 +742,7 @@ public:
|
|||
bool fDfgPreInline() const { return m_fDfgPreInline; }
|
||||
bool fDfgPostInline() const { return m_fDfgPostInline; }
|
||||
bool fDfgScoped() const { return m_fDfgScoped; }
|
||||
bool fDfgSynthesizeAll() const { return m_fDfgSynthesizeAll; }
|
||||
bool fDfgPeepholeEnabled(const std::string& name) const {
|
||||
return !m_fDfgPeepholeDisabled.count(name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1266,9 +1266,10 @@ def write_dfg_ast_to_dfg(filename):
|
|||
" Dfg{t}* const vtxp = makeVertex<Dfg{t}>(nodep, m_dfg);\n".format(t=node.name))
|
||||
fh.write(" if (!vtxp) {\n")
|
||||
fh.write(" m_foundUnhandled = true;\n")
|
||||
fh.write(" ++m_ctx.m_nonRepNode;\n")
|
||||
fh.write(" ++m_ctx.m_conv.nonRepNode;\n")
|
||||
fh.write(" return;\n")
|
||||
fh.write(" }\n\n")
|
||||
fh.write(" m_logicp->synth().emplace_back(vtxp);")
|
||||
for i in range(node.arity):
|
||||
fh.write(
|
||||
" vtxp->relinkSource<{i}>(nodep->op{j}p()->user2u().to<DfgVertex*>());\n".
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
// any use, without warranty, 2024 by Wilson Snyder.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilator lint_off UNOPTFLAT
|
||||
|
||||
module t(i, o);
|
||||
localparam N = 2000; // Deliberately not multiple of 32
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ module t (clk);
|
|||
wire [2:0] q3;
|
||||
wire [7:0] q8;
|
||||
|
||||
// verilator lint_off UNOPTFLAT
|
||||
reg ena;
|
||||
// verilator lint_on UNOPTFLAT
|
||||
|
||||
condff #(12) condff
|
||||
(.clk(clk), .sen(1'b0), .ena(ena),
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ test.scenarios('vlt_all')
|
|||
test.compile(
|
||||
#make_top_shell = False,
|
||||
make_main=False,
|
||||
v_flags2=["--exe", test.pli_filename])
|
||||
v_flags2=["--exe", test.pli_filename, "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ test.scenarios('vlt_all')
|
|||
test.compile(
|
||||
#make_top_shell = False,
|
||||
make_main=False,
|
||||
v_flags2=["--exe", test.pli_filename])
|
||||
v_flags2=["--exe", test.pli_filename, "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('vlt_all')
|
||||
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT"])
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "-fno-dfg"])
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT"])
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT"])
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT -Wno-WIDTH"])
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "-Wno-WIDTH", "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
import vltest_bootstrap
|
||||
|
||||
import os
|
||||
|
||||
test.scenarios('vlt_all')
|
||||
test.sim_time = 2000000
|
||||
|
||||
|
|
@ -65,8 +67,6 @@ test.compile(verilator_flags2=[
|
|||
"--stats",
|
||||
"--build",
|
||||
"-fno-dfg-break-cycles",
|
||||
"-fno-dfg-post-inline",
|
||||
"-fno-dfg-scoped",
|
||||
"+incdir+" + test.obj_dir,
|
||||
"-Mdir", test.obj_dir + "/obj_ref",
|
||||
"--prefix", "Vref",
|
||||
|
|
@ -100,6 +100,9 @@ coveredLines = set()
|
|||
|
||||
|
||||
def readCovered(fileName):
|
||||
if not os.path.exists(fileName):
|
||||
test.error_keep_going("Missing coverage file: " + fileName)
|
||||
return
|
||||
with open(fileName, 'r', encoding="utf8") as fd:
|
||||
for line in fd:
|
||||
coveredLines.add(int(line.strip()))
|
||||
|
|
|
|||
|
|
@ -194,4 +194,37 @@ module t (
|
|||
`signal(COND_COND, 3); // UNOPTFLAT
|
||||
assign COND_COND = {rand_a[0], (COND_COND >> 2) == 3'b001 ? rand_b[3:2] : rand_b[1:0]};
|
||||
|
||||
// verilator lint_off ALWCOMBORDER
|
||||
logic [3:0] always_0;
|
||||
always_comb begin
|
||||
always_0[3] = ~always_0[1];
|
||||
always_0[2] = always_0[1];
|
||||
always_0[0] = rand_a[0];
|
||||
end
|
||||
assign always_0[1] = ~always_0[0];
|
||||
`signal(ALWAYS_0, 4); // UNOPTFLAT
|
||||
assign ALWAYS_0 = always_0;
|
||||
// verilator lint_on ALWCOMBORDER
|
||||
|
||||
// verilator lint_off ALWCOMBORDER
|
||||
logic [4:0] always_1;
|
||||
always_comb begin
|
||||
always_1[4] = always_1[0];
|
||||
always_1[0] = rand_a[0];
|
||||
always_1[3:2] = always_1[1:0];
|
||||
end
|
||||
assign always_1[1] = always_1[0];
|
||||
`signal(ALWAYS_1, 5); // UNOPTFLAT
|
||||
assign ALWAYS_1 = always_1;
|
||||
// verilator lint_on ALWCOMBORDER
|
||||
|
||||
// verilator lint_off ALWCOMBORDER
|
||||
logic [3:0] always_2;
|
||||
always_comb begin
|
||||
always_2[2:0] = 3'((always_2 << 1) | 4'(rand_a[0]));
|
||||
always_2[3] = rand_a[0];
|
||||
end
|
||||
`signal(ALWAYS_2, 4); // UNOPTFLAT
|
||||
assign ALWAYS_2 = always_2;
|
||||
// verilator lint_on ALWCOMBORDER
|
||||
endmodule
|
||||
|
|
|
|||
|
|
@ -1,75 +1,120 @@
|
|||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [3:1] of signal 'a' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:45:18: Bit [1] of signal 'y' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:17:19: ... Location of first driver
|
||||
17 | assign a[3:0] = i[3:0];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:18:19: ... Location of other driver
|
||||
18 | assign a[4:1] = ~i[4:1];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:16:18: ... Only the first driver will be respected
|
||||
t/t_dfg_multidriver_dfg_bad.v:48:24: ... Location of offending driver
|
||||
48 | {y[1:0], y[2:1]} = i[3:0] + 4'd5;
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:48:24: ... Location of offending driver
|
||||
48 | {y[1:0], y[2:1]} = i[3:0] + 4'd5;
|
||||
| ^
|
||||
... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest
|
||||
... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message.
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [3:3] of signal 'a' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [3:1] of signal 'a' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:17:19: ... Location of first driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:17:19: ... Location of offending driver
|
||||
17 | assign a[3:0] = i[3:0];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:19:17: ... Location of other driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:18:19: ... Location of offending driver
|
||||
18 | assign a[4:1] = ~i[4:1];
|
||||
| ^
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bit [3] of signal 'a' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:17:19: ... Location of offending driver
|
||||
17 | assign a[3:0] = i[3:0];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:19:17: ... Location of offending driver
|
||||
19 | assign a[3] = ~i[3];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:16:18: ... Only the first driver will be respected
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [7:6] of signal 'a' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [7:6] of signal 'a' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:20:19: ... Location of first driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:20:19: ... Location of offending driver
|
||||
20 | assign a[8:5] = i[8:5];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:21:19: ... Location of other driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:21:19: ... Location of offending driver
|
||||
21 | assign a[7:6] = ~i[7:6];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:16:18: ... Only the first driver will be respected
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [9:9] of signal 'a' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bit [9] of signal 'a' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:22:17: ... Location of first driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:22:17: ... Location of offending driver
|
||||
22 | assign a[9] = i[9];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:23:19: ... Location of other driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:23:17: ... Location of offending driver
|
||||
23 | assign a[9] = ~i[9];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:16:18: ... Only the first driver will be respected
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:26:18: Elements [3:0] of signal 'u' have multiple combinational drivers
|
||||
| ^
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:26:18: Elements [3:0] of signal 'u' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:27:14: ... Location of first driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:27:14: ... Location of offending driver
|
||||
27 | assign u = j;
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:28:14: ... Location of other driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:28:14: ... Location of offending driver
|
||||
28 | assign u = k;
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:26:18: ... Only the first driver will be respected
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:30:18: Elements [1:1] of signal 'v' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:30:18: Element [1] of signal 'v' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:31:14: ... Location of first driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:31:14: ... Location of offending driver
|
||||
31 | assign v = j;
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:32:13: ... Location of other driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:32:13: ... Location of offending driver
|
||||
32 | assign v[1] = i;
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:30:18: ... Only the first driver will be respected
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:34:18: Elements [0:0] of signal 'w' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:34:18: Element [0] of signal 'w' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:35:13: ... Location of first driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:35:13: ... Location of offending driver
|
||||
35 | assign w[0] = i;
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:36:14: ... Location of other driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:36:14: ... Location of offending driver
|
||||
36 | assign w = j;
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:34:18: ... Only the first driver will be respected
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:38:18: Bits [3:2] of signal 'x[3]' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:38:18: Bits [3:2] of signal 'x[3]' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:39:17: ... Location of first driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:39:17: ... Location of offending driver
|
||||
39 | assign x[3] = i;
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:40:22: ... Location of other driver
|
||||
t/t_dfg_multidriver_dfg_bad.v:40:22: ... Location of offending driver
|
||||
40 | assign x[3][3:2] = ~i[1:0];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:38:18: ... Only the first driver will be respected
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:51:18: Bits [2:1] of signal 'z' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:53:14: ... Location of offending driver
|
||||
53 | z[2:0] = i[2:0];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:58:17: ... Location of offending driver
|
||||
58 | z[3:1] = i[3:1];
|
||||
| ^
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:51:18: Bits [6:5] of signal 'z' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:57:14: ... Location of offending driver
|
||||
57 | z[6:4] = i[6:4];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:54:14: ... Location of offending driver
|
||||
54 | z[7:5] = i[7:5];
|
||||
| ^
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:51:18: Bit [7] of signal 'z' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_dfg_multidriver_dfg_bad.v:54:14: ... Location of offending driver
|
||||
54 | z[7:5] = i[7:5];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:60:20: ... Location of offending driver
|
||||
60 | assign z[10:7] = i[10:7];
|
||||
| ^
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:74:18: Bits [5:2] of signal 't.sub_1.a' have multiple combinational drivers. This can cause performance degradation.
|
||||
t/t_dfg_multidriver_dfg_bad.v:63:20: ... Location of offending driver
|
||||
63 | assign sub_1.a = i;
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:75:19: ... Location of offending driver
|
||||
75 | assign a[5:2] = i[5:2];
|
||||
| ^
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:74:18: Bits [3:2] of signal 't.sub_2.a' have multiple combinational drivers. This can cause performance degradation.
|
||||
t/t_dfg_multidriver_dfg_bad.v:67:25: ... Location of offending driver
|
||||
67 | assign sub_2.a[3:0] = i[3:0];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:75:19: ... Location of offending driver
|
||||
75 | assign a[5:2] = i[5:2];
|
||||
| ^
|
||||
%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:74:18: Bit [5] of signal 't.sub_2.a' have multiple combinational drivers. This can cause performance degradation.
|
||||
t/t_dfg_multidriver_dfg_bad.v:75:19: ... Location of offending driver
|
||||
75 | assign a[5:2] = i[5:2];
|
||||
| ^
|
||||
t/t_dfg_multidriver_dfg_bad.v:66:26: ... Location of offending driver
|
||||
66 | assign sub_2.a[10:5] = i[10:5];
|
||||
| ^
|
||||
%Error: Exiting due to
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('vlt')
|
||||
|
||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
||||
test.lint(verilator_flags2=["-fdfg-synthesize-all", "-fno-const-before-dfg"],
|
||||
fails=True,
|
||||
expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -42,6 +42,35 @@ module t(
|
|||
assign x[2][3:2] = ~i[1:0];
|
||||
assign x[2][1:0] = ~i[1:0];
|
||||
|
||||
assign o = a ^ u[3] ^ v[3] ^ w[3] ^ x[3];
|
||||
logic [10:0] y;
|
||||
always_comb begin
|
||||
y = i;
|
||||
{y[1:0], y[2:1]} = i[3:0] + 4'd5;
|
||||
end
|
||||
|
||||
logic [10:0] z;
|
||||
always_comb begin
|
||||
z[2:0] = i[2:0];
|
||||
z[7:5] = i[7:5];
|
||||
end
|
||||
always_comb begin
|
||||
z[6:4] = i[6:4];
|
||||
z[3:1] = i[3:1];
|
||||
end
|
||||
assign z[10:7] = i[10:7];
|
||||
|
||||
sub sub_1(i);
|
||||
assign sub_1.a = i;
|
||||
|
||||
sub sub_2(i);
|
||||
assign sub_2.a[10:5] = i[10:5];
|
||||
assign sub_2.a[3:0] = i[3:0];
|
||||
|
||||
assign o = a ^ u[3] ^ v[3] ^ w[3] ^ x[3] ^ y ^ z ^ sub_1.a ^ sub_2.a;
|
||||
|
||||
endmodule
|
||||
|
||||
module sub(input wire [10:0] i);
|
||||
logic [10:0] a;
|
||||
assign a[5:2] = i[5:2];
|
||||
endmodule
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ test.compile(verilator_flags2=[
|
|||
"-Mdir", test.obj_dir + "/obj_opt",
|
||||
"--prefix", "Vopt",
|
||||
"-fno-const-before-dfg", # Otherwise V3Const makes testing painful
|
||||
"-fdfg-synthesize-all",
|
||||
"--dump-dfg", # To fill code coverage
|
||||
"-CFLAGS \"-I .. -I ../obj_ref\"",
|
||||
"../obj_ref/Vref__ALL.a",
|
||||
|
|
|
|||
|
|
@ -36,12 +36,17 @@ module t (
|
|||
wire logic signed [63:0] sconst_a;
|
||||
wire logic signed [63:0] sconst_b;
|
||||
logic [63:0] array [3:0];
|
||||
logic [63:0] unitArrayWhole [0:0];
|
||||
logic [63:0] unitArrayParts [0:0];
|
||||
assign array[0] = (rand_a << 32) | (rand_a >> 32);
|
||||
assign array[1] = (rand_a << 16) | (rand_a >> 48);
|
||||
assign array[2][3:0] = rand_a[3:0];
|
||||
always @(rand_b) begin // Intentional non-combinational partial driver
|
||||
array[2][7:4] = rand_a[7:4];
|
||||
end
|
||||
assign unitArrayWhole[0] = rand_a;
|
||||
assign unitArrayParts[0][1] = rand_a[1];
|
||||
assign unitArrayParts[0][9] = rand_a[9];
|
||||
|
||||
`signal(FOLD_UNARY_LogNot, !const_a[0]);
|
||||
`signal(FOLD_UNARY_Negate, -const_a);
|
||||
|
|
@ -188,8 +193,10 @@ module t (
|
|||
`signal(REPLACE_COND_WITH_THEN_BRANCH_ONES, rand_a[0] ? 1'd1 : rand_a[1]);
|
||||
`signal(REPLACE_COND_WITH_ELSE_BRANCH_ZERO, rand_a[0] ? rand_a[1] : 1'd0);
|
||||
`signal(REPLACE_COND_WITH_ELSE_BRANCH_ONES, rand_a[0] ? rand_a[1] : 1'd1);
|
||||
`signal(INLINE_ARRAYSEL, array[0]);
|
||||
`signal(NO_INLINE_ARRAYSEL_PARTIAL, array[2]);
|
||||
`signal(INLINE_ARRAYSEL_SPLICE, array[0]);
|
||||
`signal(NO_INLINE_ARRAYSEL_SPLICE_PARTIAL, array[2]);
|
||||
`signal(INLINE_ARRAYSEL_UNIT, unitArrayWhole[0]);
|
||||
`signal(NO_INLINE_ARRAYSEL_UNIT_PARTIAL, unitArrayParts[0]);
|
||||
`signal(PUSH_BITWISE_THROUGH_REDUCTION_AND, (&(rand_a + 64'd105)) & (&(rand_b + 64'd108)));
|
||||
`signal(PUSH_BITWISE_THROUGH_REDUCTION_OR, (|(rand_a + 64'd106)) | (|(rand_b + 64'd109)));
|
||||
`signal(PUSH_BITWISE_THROUGH_REDUCTION_XOR, (^(rand_a + 64'd107)) ^ (^(rand_b + 64'd110)));
|
||||
|
|
@ -229,6 +236,15 @@ module t (
|
|||
`signal(PUSH_SEL_THROUGH_SHIFTL, sel_from_shiftl[20:0]);
|
||||
`signal(REPLACE_SEL_FROM_SEL, sel_from_sel[4:3]);
|
||||
|
||||
logic [2:0] sel_from_partial_tmp;;
|
||||
always_comb begin
|
||||
sel_from_partial_tmp[1:0] = 2'd0;
|
||||
if (rand_a[0]) begin
|
||||
sel_from_partial_tmp[0] = rand_b[0];
|
||||
end
|
||||
end
|
||||
`signal(PUSH_SEL_THROUGH_SPLICE, sel_from_partial_tmp[1:0]);
|
||||
|
||||
// Asscending ranges
|
||||
`signal(ASCENDNG_SEL, arand_a[0:4]);
|
||||
// verilator lint_off ASCRANGE
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// DESCRIPTION: Verilator: DFG optimizer equivalence testing
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2022 by Geza Lore.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
//
|
||||
|
||||
#include <verilated.h>
|
||||
#include <verilated_cov.h>
|
||||
|
||||
#include <Vopt.h>
|
||||
#include <Vref.h>
|
||||
#include <iostream>
|
||||
|
||||
void rngUpdate(uint64_t& x) {
|
||||
x ^= x << 13;
|
||||
x ^= x >> 7;
|
||||
x ^= x << 17;
|
||||
}
|
||||
|
||||
int main(int, char**) {
|
||||
// Create contexts
|
||||
VerilatedContext ctx;
|
||||
|
||||
// Create models
|
||||
Vref ref{&ctx};
|
||||
Vopt opt{&ctx};
|
||||
|
||||
uint64_t rand_a = 0x5aef0c8dd70a4497;
|
||||
uint64_t rand_b = 0xf0c0a8dd75ae4497;
|
||||
uint64_t srand_a = 0x00fa8dcc7ae4957;
|
||||
uint64_t srand_b = 0x0fa8dc7ae3c9574;
|
||||
|
||||
for (size_t n = 0; n < 200000; ++n) {
|
||||
// Update rngs
|
||||
rngUpdate(rand_a);
|
||||
rngUpdate(rand_b);
|
||||
rngUpdate(srand_a);
|
||||
rngUpdate(srand_b);
|
||||
|
||||
// Assign inputs
|
||||
ref.rand_a = opt.rand_a = rand_a;
|
||||
ref.rand_b = opt.rand_b = rand_b;
|
||||
ref.srand_a = opt.srand_a = srand_a;
|
||||
ref.srand_b = opt.srand_b = srand_b;
|
||||
|
||||
// Evaluate both models
|
||||
ref.eval();
|
||||
opt.eval();
|
||||
|
||||
// Check equivalence
|
||||
#include "checks.h"
|
||||
|
||||
// increment time
|
||||
ctx.timeInc(1);
|
||||
}
|
||||
|
||||
std::cout << "*-* All Finished *-*\n";
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2025 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
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt_all')
|
||||
test.sim_time = 2000000
|
||||
|
||||
root = ".."
|
||||
|
||||
if not os.path.exists(root + "/.git"):
|
||||
test.skip("Not in a git repository")
|
||||
|
||||
# Generate the equivalence checks and declaration boilerplate
|
||||
rdFile = test.top_filename
|
||||
plistFile = test.obj_dir + "/portlist.vh"
|
||||
pdeclFile = test.obj_dir + "/portdecl.vh"
|
||||
checkFile = test.obj_dir + "/checks.h"
|
||||
nAlwaysSynthesized = 0
|
||||
nAlwaysNotSynthesized = 0
|
||||
nAlwaysReverted = 0
|
||||
with open(rdFile, 'r', encoding="utf8") as rdFh, \
|
||||
open(plistFile, 'w', encoding="utf8") as plistFh, \
|
||||
open(pdeclFile, 'w', encoding="utf8") as pdeclFh, \
|
||||
open(checkFile, 'w', encoding="utf8") as checkFh:
|
||||
for line in rdFh:
|
||||
if re.search(r'^\s*always.*//\s*nosynth$', line):
|
||||
nAlwaysNotSynthesized += 1
|
||||
elif re.search(r'^\s*always.*//\s*revert$', line):
|
||||
nAlwaysReverted += 1
|
||||
elif re.search(r'^\s*always', line):
|
||||
nAlwaysSynthesized += 1
|
||||
line = line.split("//")[0]
|
||||
m = re.search(r'`signal\((\w+),', line)
|
||||
if not m:
|
||||
continue
|
||||
sig = m.group(1)
|
||||
plistFh.write(sig + ",\n")
|
||||
pdeclFh.write("output " + sig + ";\n")
|
||||
checkFh.write("if (ref." + sig + " != opt." + sig + ") {\n")
|
||||
checkFh.write(" std::cout << \"Mismatched " + sig + "\" << std::endl;\n")
|
||||
checkFh.write(" std::cout << \"Ref: 0x\" << std::hex << (ref." + sig +
|
||||
" + 0) << std::endl;\n")
|
||||
checkFh.write(" std::cout << \"Opt: 0x\" << std::hex << (opt." + sig +
|
||||
" + 0) << std::endl;\n")
|
||||
checkFh.write(" std::exit(1);\n")
|
||||
checkFh.write("}\n")
|
||||
|
||||
# Compile un-optimized
|
||||
test.compile(verilator_flags2=[
|
||||
"--stats",
|
||||
"--build",
|
||||
"-fno-dfg",
|
||||
"+incdir+" + test.obj_dir,
|
||||
"-Mdir", test.obj_dir + "/obj_ref",
|
||||
"--prefix", "Vref",
|
||||
"-Wno-UNOPTFLAT"
|
||||
]) # yapf:disable
|
||||
|
||||
test.file_grep_not(test.obj_dir + "/obj_ref/Vref__stats.txt", r'DFG.*Synthesis')
|
||||
|
||||
# Compile optimized - also builds executable
|
||||
test.compile(verilator_flags2=[
|
||||
"--stats",
|
||||
"--build",
|
||||
"--fdfg-synthesize-all",
|
||||
"-fno-dfg-post-inline",
|
||||
"-fno-dfg-scoped",
|
||||
"--exe",
|
||||
"+incdir+" + test.obj_dir,
|
||||
"-Mdir", test.obj_dir + "/obj_opt",
|
||||
"--prefix", "Vopt",
|
||||
"-fno-const-before-dfg", # Otherwise V3Const makes testing painful
|
||||
"--debug", "--debugi", "0", "--dumpi-tree", "0",
|
||||
"-CFLAGS \"-I .. -I ../obj_ref\"",
|
||||
"../obj_ref/Vref__ALL.a",
|
||||
"../../t/" + test.name + ".cpp"
|
||||
]) # yapf:disable
|
||||
|
||||
test.file_grep(test.obj_dir + "/obj_opt/Vopt__stats.txt",
|
||||
r'DFG pre inline Synthesis, synt / always blocks considered\s+(\d+)$',
|
||||
nAlwaysSynthesized + nAlwaysReverted + nAlwaysNotSynthesized)
|
||||
test.file_grep(test.obj_dir + "/obj_opt/Vopt__stats.txt",
|
||||
r'DFG pre inline Synthesis, synt / always blocks synthesized\s+(\d+)$',
|
||||
nAlwaysSynthesized + nAlwaysReverted)
|
||||
test.file_grep(test.obj_dir + "/obj_opt/Vopt__stats.txt",
|
||||
r'DFG pre inline Synthesis, synt / reverted \(multidrive\)\s+(\d)$',
|
||||
nAlwaysReverted)
|
||||
|
||||
# Execute test to check equivalence
|
||||
test.execute(executable=test.obj_dir + "/obj_opt/Vopt")
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2025 by Geza Lore.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
`define signal(name, expr) wire [$bits(expr)-1:0] ``name = expr
|
||||
|
||||
module t (
|
||||
`include "portlist.vh" // Boilerplate generated by t_dfg_break_cycles.py
|
||||
rand_a, rand_b, srand_a, srand_b
|
||||
);
|
||||
|
||||
`include "portdecl.vh" // Boilerplate generated by t_dfg_break_cycles.py
|
||||
|
||||
input rand_a;
|
||||
input rand_b;
|
||||
input srand_a;
|
||||
input srand_b;
|
||||
wire logic [63:0] rand_a;
|
||||
wire logic [63:0] rand_b;
|
||||
wire logic signed [63:0] srand_a;
|
||||
wire logic signed [63:0] srand_b;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
logic [2:0] simple;
|
||||
always_comb begin
|
||||
simple[0] = rand_a[0];
|
||||
simple[1] = rand_a[1];
|
||||
simple[2] = rand_a[2];
|
||||
end
|
||||
`signal(SIMPLE, simple);
|
||||
|
||||
logic [1:0] reassign;
|
||||
always_comb begin
|
||||
reassign[0] = rand_a[0];
|
||||
reassign[0] = ~rand_a[0];
|
||||
reassign[1] = rand_a[1];
|
||||
reassign[1] = ~rand_a[1];
|
||||
end
|
||||
`signal(REASSIGN, reassign);
|
||||
|
||||
logic [1:0] use_intermediate_a;
|
||||
logic [1:0] use_intermediate_b;
|
||||
always_comb begin
|
||||
use_intermediate_a[0] = rand_a[0];
|
||||
use_intermediate_b[0] = ~use_intermediate_a[0];
|
||||
use_intermediate_a[1] = rand_a[1];
|
||||
use_intermediate_a[0] = ~rand_a[0];
|
||||
use_intermediate_b[1] = ~use_intermediate_a[1];
|
||||
use_intermediate_a[1] = ~rand_a[1];
|
||||
end
|
||||
`signal(USE_INTERMEDIATE, {use_intermediate_a, use_intermediate_b});
|
||||
|
||||
logic [2:0] self_circular;
|
||||
always_comb begin
|
||||
self_circular[0] = rand_a[0];
|
||||
self_circular[1] = ~self_circular[0];
|
||||
self_circular[2] = ~self_circular[1];
|
||||
end
|
||||
`signal(SELF_CIRCULAR, self_circular);
|
||||
|
||||
logic [2:0] part_circular;
|
||||
always_comb begin
|
||||
part_circular[0] = rand_a[0];
|
||||
part_circular[1] = ~part_circular[0];
|
||||
end
|
||||
// part_circular[2] deliberately undriven!
|
||||
`signal(PART_CIRCULAR, part_circular);
|
||||
|
||||
logic [3:0] split_circular;
|
||||
always_comb begin
|
||||
split_circular[0] = rand_a[0];
|
||||
split_circular[2] = rand_a[1];
|
||||
end
|
||||
always_comb begin
|
||||
split_circular[1] = ~split_circular[0];
|
||||
split_circular[3] = ~split_circular[2];
|
||||
end
|
||||
`signal(SPLIT_CIRCULAR, split_circular);
|
||||
|
||||
logic [3:0] conditional_a;
|
||||
always_comb begin
|
||||
conditional_a = 4'd0;
|
||||
if (rand_a[0]) begin
|
||||
conditional_a = rand_b[3:0];
|
||||
end else begin
|
||||
conditional_a = ~rand_b[3:0];
|
||||
end
|
||||
end
|
||||
`signal(CONDITONAL_A, conditional_a);
|
||||
|
||||
logic [3:0] conditional_b;
|
||||
always_comb begin
|
||||
conditional_b = 4'd0;
|
||||
if (rand_a[0]) begin
|
||||
conditional_b = rand_b[3:0];
|
||||
end
|
||||
end
|
||||
`signal(CONDITONAL_B, conditional_b);
|
||||
|
||||
// verilator lint_off LATCH
|
||||
logic [3:0] conditional_c;
|
||||
always_comb begin // nosynth
|
||||
if (rand_a[0]) begin
|
||||
conditional_c = rand_b[3:0];
|
||||
end
|
||||
if (~rand_a[0]) begin
|
||||
conditional_c = ~rand_b[3:0];
|
||||
end
|
||||
end
|
||||
`signal(CONDITONAL_C, conditional_c);
|
||||
// verilator lint_on LATCH
|
||||
|
||||
logic [3:0] conditional_d;
|
||||
always_comb begin
|
||||
if (rand_a[0]) begin
|
||||
conditional_d = rand_b[3:0];
|
||||
end else if (rand_a[1]) begin
|
||||
conditional_d = ~rand_b[3:0];
|
||||
end else begin
|
||||
conditional_d = rand_b[7:4];
|
||||
end
|
||||
end
|
||||
`signal(CONDITONAL_D, conditional_d);
|
||||
|
||||
logic [3:0] conditional_e;
|
||||
always_comb begin
|
||||
conditional_e = 4'd0;
|
||||
if (rand_a[0]) begin
|
||||
conditional_e = rand_b[3:0];
|
||||
end else begin
|
||||
if (rand_a[1]) begin
|
||||
conditional_e = rand_b[3:0];
|
||||
end else begin
|
||||
conditional_e = rand_b[7:4];
|
||||
end
|
||||
conditional_e = ~conditional_e;
|
||||
end
|
||||
end
|
||||
`signal(CONDITONAL_E, conditional_e);
|
||||
|
||||
logic condigional_f;
|
||||
always_comb begin
|
||||
if (rand_b[0]) begin
|
||||
condigional_f = 1'h1;
|
||||
if (rand_b[1]) begin
|
||||
condigional_f = rand_a[0];
|
||||
end
|
||||
end else begin
|
||||
condigional_f = 1'b0;
|
||||
end
|
||||
end
|
||||
`signal(CONDITONAL_F, condigional_f);
|
||||
|
||||
logic [7:0] partial_conditional_a;
|
||||
always_comb begin
|
||||
partial_conditional_a[1:0] = 2'd0;
|
||||
if (rand_a[0]) begin
|
||||
partial_conditional_a[0] = rand_b[0];
|
||||
end else begin
|
||||
partial_conditional_a[1] = rand_b[1];
|
||||
end
|
||||
partial_conditional_a[4:3] = rand_b[4:3];
|
||||
end
|
||||
`signal(PARTIAL_CONDITONAL_A, partial_conditional_a);
|
||||
|
||||
logic [3:0] partial_conditional_b;
|
||||
always_comb begin
|
||||
partial_conditional_b[1:0] = 2'd0;
|
||||
if (rand_a[0]) begin
|
||||
partial_conditional_b[0] = rand_b[0];
|
||||
end
|
||||
if (rand_a[1]) begin
|
||||
partial_conditional_b[1] = rand_b[1];
|
||||
end
|
||||
end
|
||||
`signal(PARTIAL_CONDITONAL_B, partial_conditional_b);
|
||||
|
||||
logic [3:0] becomes_full;
|
||||
always_comb begin
|
||||
becomes_full[2:0] = rand_a[2:0];
|
||||
becomes_full[3] = ~rand_a[3];
|
||||
if (rand_b[0]) begin
|
||||
becomes_full = ~becomes_full;
|
||||
end
|
||||
end
|
||||
`signal(BECOMES_FULL, becomes_full);
|
||||
|
||||
// verilator lint_off LATCH
|
||||
logic [3:0] latch_a;
|
||||
logic [3:0] latch_b;
|
||||
always_comb begin // nosynth
|
||||
if (rand_b[0]) begin
|
||||
latch_a[3:1] = ~rand_a[3:1];
|
||||
end
|
||||
latch_b = latch_a;
|
||||
end
|
||||
assign latch_a[0] = rand_a[0];
|
||||
`signal(LATCH, latch_b);
|
||||
// verilator lint_on LATCH
|
||||
|
||||
// verilator lint_off MULTIDRIVEN
|
||||
logic static_temporary_a;
|
||||
logic static_temporary_b;
|
||||
logic static_temporary_tmp;
|
||||
always_comb begin // revert
|
||||
static_temporary_tmp = rand_a[0];
|
||||
static_temporary_a = ~static_temporary_tmp;
|
||||
end
|
||||
always_comb begin // revert
|
||||
static_temporary_tmp = static_temporary_a;
|
||||
static_temporary_b = ~static_temporary_tmp;
|
||||
end
|
||||
// verilator lint_on MULTIDRIVEN
|
||||
`signal(STATIC_TEMPORARY, {static_temporary_tmp, static_temporary_b, static_temporary_a, rand_a[0]});
|
||||
|
||||
logic [2:0] partial_temporary_a;
|
||||
logic [2:0] partial_temporary_tmp;
|
||||
always_comb begin
|
||||
partial_temporary_tmp[2] = rand_a[3];
|
||||
partial_temporary_tmp[1] = rand_a[2];
|
||||
partial_temporary_tmp[0] = rand_a[1];
|
||||
partial_temporary_a = partial_temporary_tmp;
|
||||
end
|
||||
`signal(PARTIAL_TEMPORARY, partial_temporary_a);
|
||||
endmodule
|
||||
|
|
@ -17,6 +17,7 @@ Waivers = [
|
|||
'+verilator+prof+threads+file+', # Deprecated
|
||||
'+verilator+prof+threads+start+', # Deprecated
|
||||
'+verilator+prof+threads+window+', # Deprecated
|
||||
'-fdfg-synthesize-all', # Mostly used for testing
|
||||
'-fno-', # Documented differently
|
||||
'-no-lineno', # Deprecated
|
||||
'-no-order-clock-delay', # Deprecated
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import vltest_bootstrap
|
|||
test.scenarios('simulator')
|
||||
|
||||
test.compile(v_flags2=["t/t_dpi_qw_c.cpp"],
|
||||
verilator_flags2=["-Wall -Wno-DECLFILENAME -Wno-UNOPTFLAT -no-l2name"])
|
||||
verilator_flags2=["-Wall -Wno-DECLFILENAME -no-l2name"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,6 @@ test.compile(
|
|||
test.execute()
|
||||
|
||||
if test.vlt:
|
||||
test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 3888)
|
||||
test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 3434)
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -35,12 +35,10 @@ module t (/*AUTOARG*/
|
|||
`ifdef PROTLIB_TOP
|
||||
secret i_secred(.clk(clk));
|
||||
`else
|
||||
/* verilator lint_off UNOPTFLAT */
|
||||
wire [7:0] out0;
|
||||
wire [7:0] out1;
|
||||
wire [7:0] out2;
|
||||
wire [7:0] out3;
|
||||
/* verilator lint_on UNOPTFLAT */
|
||||
wire [7:0] out3_2;
|
||||
wire [7:0] out5;
|
||||
wire [7:0] out6;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ test.compile(
|
|||
benchmarksim=1,
|
||||
v_flags2=[
|
||||
config_file, "+define+SIM_CYCLES=" + str(test.cycles), "--hierarchical", "--stats",
|
||||
"-Wno-UNOPTFLAT",
|
||||
(f"-DWORKERS={HIER_BLOCK_THREADS}" if test.vltmt and HIER_BLOCK_THREADS > 1 else ""),
|
||||
(f"--hierarchical-threads {HIER_THREADS}" if test.vltmt and HIER_THREADS > 1 else "")
|
||||
],
|
||||
|
|
@ -33,9 +32,9 @@ test.compile(
|
|||
|
||||
if test.vltmt:
|
||||
test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt",
|
||||
r'Optimizations, Thread schedule count\s+(\d+)', 2)
|
||||
r'Optimizations, Thread schedule count\s+(\d+)', 3)
|
||||
test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt",
|
||||
r'Optimizations, Thread schedule total tasks\s+(\d+)', 3)
|
||||
r'Optimizations, Thread schedule total tasks\s+(\d+)', 4)
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -48,40 +48,36 @@
|
|||
t/t_lint_always_comb_multidriven_bad.v:40:16: ... Location of other write
|
||||
40 | always_comb out6 = d;
|
||||
| ^~~~
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:17:15: Bits [0:0] of signal 'out2' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:17:15: Bit [0] of signal 'out2' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_always_comb_multidriven_bad.v:28:16: ... Location of first driver
|
||||
t/t_lint_always_comb_multidriven_bad.v:28:16: ... Location of offending driver
|
||||
28 | assign out2 = d;
|
||||
| ^
|
||||
t/t_lint_always_comb_multidriven_bad.v:29:21: ... Location of other driver
|
||||
t/t_lint_always_comb_multidriven_bad.v:29:21: ... Location of offending driver
|
||||
29 | always_comb out2 = 1'b0;
|
||||
| ^
|
||||
t/t_lint_always_comb_multidriven_bad.v:17:15: ... Only the first driver will be respected
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:19:15: Bits [0:0] of signal 'out4' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:19:15: Bit [0] of signal 'out4' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_always_comb_multidriven_bad.v:34:21: ... Location of first driver
|
||||
t/t_lint_always_comb_multidriven_bad.v:34:21: ... Location of offending driver
|
||||
34 | always_comb out4 = 1'b0;
|
||||
| ^
|
||||
t/t_lint_always_comb_multidriven_bad.v:35:16: ... Location of other driver
|
||||
t/t_lint_always_comb_multidriven_bad.v:35:16: ... Location of offending driver
|
||||
35 | assign out4 = d;
|
||||
| ^
|
||||
t/t_lint_always_comb_multidriven_bad.v:19:15: ... Only the first driver will be respected
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:20:15: Bits [0:0] of signal 'out5' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:20:15: Bit [0] of signal 'out5' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_always_comb_multidriven_bad.v:37:21: ... Location of first driver
|
||||
t/t_lint_always_comb_multidriven_bad.v:37:21: ... Location of offending driver
|
||||
37 | always_comb out5 = 1'b0;
|
||||
| ^
|
||||
t/t_lint_always_comb_multidriven_bad.v:38:21: ... Location of other driver
|
||||
t/t_lint_always_comb_multidriven_bad.v:38:21: ... Location of offending driver
|
||||
38 | always_comb out5 = d;
|
||||
| ^
|
||||
t/t_lint_always_comb_multidriven_bad.v:20:15: ... Only the first driver will be respected
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:21:15: Bits [0:0] of signal 'out6' have multiple combinational drivers
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:21:15: Bit [0] of signal 'out6' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_always_comb_multidriven_bad.v:40:21: ... Location of first driver
|
||||
t/t_lint_always_comb_multidriven_bad.v:40:21: ... Location of offending driver
|
||||
40 | always_comb out6 = d;
|
||||
| ^
|
||||
t/t_lint_always_comb_multidriven_bad.v:41:21: ... Location of other driver
|
||||
t/t_lint_always_comb_multidriven_bad.v:41:21: ... Location of offending driver
|
||||
41 | always_comb out6 = 1'b0;
|
||||
| ^
|
||||
t/t_lint_always_comb_multidriven_bad.v:21:15: ... Only the first driver will be respected
|
||||
%Error: Exiting due to
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:26:16: Variable written to in always_comb also written by other process (IEEE 1800-2023 9.2.2.2): 'out1'
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_always_comb_multidriven_bad.v:26:16:
|
||||
26 | always_comb out1 = d;
|
||||
| ^~~~
|
||||
t/t_lint_always_comb_multidriven_bad.v:25:11: ... Location of other write
|
||||
25 | assign out1 = 1'b0;
|
||||
| ^~~~
|
||||
... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest
|
||||
... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message.
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:29:16: Variable written to in always_comb also written by other process (IEEE 1800-2023 9.2.2.2): 'out2'
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_always_comb_multidriven_bad.v:29:16:
|
||||
29 | always_comb out2 = 1'b0;
|
||||
| ^~~~
|
||||
t/t_lint_always_comb_multidriven_bad.v:28:11: ... Location of other write
|
||||
28 | assign out2 = d;
|
||||
| ^~~~
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:32:11: Variable also written to in always_comb (IEEE 1800-2023 9.2.2.2): 'out3'
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_always_comb_multidriven_bad.v:32:11:
|
||||
32 | assign out3 = 1'b0;
|
||||
| ^~~~
|
||||
t/t_lint_always_comb_multidriven_bad.v:31:16: ... Location of always_comb write
|
||||
31 | always_comb out3 = d;
|
||||
| ^~~~
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:35:11: Variable also written to in always_comb (IEEE 1800-2023 9.2.2.2): 'out4'
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_always_comb_multidriven_bad.v:35:11:
|
||||
35 | assign out4 = d;
|
||||
| ^~~~
|
||||
t/t_lint_always_comb_multidriven_bad.v:34:16: ... Location of always_comb write
|
||||
34 | always_comb out4 = 1'b0;
|
||||
| ^~~~
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:38:16: Variable written to in always_comb also written by other process (IEEE 1800-2023 9.2.2.2): 'out5'
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_always_comb_multidriven_bad.v:38:16:
|
||||
38 | always_comb out5 = d;
|
||||
| ^~~~
|
||||
t/t_lint_always_comb_multidriven_bad.v:37:16: ... Location of other write
|
||||
37 | always_comb out5 = 1'b0;
|
||||
| ^~~~
|
||||
%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:41:16: Variable written to in always_comb also written by other process (IEEE 1800-2023 9.2.2.2): 'out6'
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_always_comb_multidriven_bad.v:41:16:
|
||||
41 | always_comb out6 = 1'b0;
|
||||
| ^~~~
|
||||
t/t_lint_always_comb_multidriven_bad.v:40:16: ... Location of other write
|
||||
40 | always_comb out6 = d;
|
||||
| ^~~~
|
||||
%Error: Exiting due to
|
||||
|
|
@ -14,6 +14,6 @@ test.top_filename = "t/t_lint_always_comb_multidriven_bad.v"
|
|||
|
||||
test.lint(verilator_flags2=['--public-flat-rw --lint-only'],
|
||||
fails=True,
|
||||
expect_filename="t/t_lint_always_comb_multidriven_bad.out")
|
||||
expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a)
|
||||
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b)
|
||||
%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge.
|
||||
Aborting...
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile()
|
||||
test.compile(verilator_flags2=["-fno-dfg"])
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -62,9 +62,7 @@ module t (/*AUTOARG*/
|
|||
end
|
||||
|
||||
reg sepassign_in;
|
||||
// verilator lint_off UNOPTFLAT
|
||||
wire [3:0] sepassign;
|
||||
// verilator lint_on UNOPTFLAT
|
||||
|
||||
// verilator lint_off UNOPT
|
||||
assign #0.1 sepassign[0] = 0,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ test.scenarios('simulator')
|
|||
# closely enough to pass the same test?
|
||||
# If not -- probably we should switch this to be vlt-only.
|
||||
|
||||
test.compile(verilator_flags2=["--trace-vcd"])
|
||||
test.compile(verilator_flags2=["--trace-vcd", "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile()
|
||||
test.compile(verilator_flags2=["-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile()
|
||||
test.compile(verilator_flags2=["-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile()
|
||||
test.compile(verilator_flags2=["-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,7 @@ module t_netlist (/*AUTOARG*/
|
|||
|
||||
// This entire module should optimize to nearly nothing...
|
||||
|
||||
// verilator lint_off UNOPTFLAT
|
||||
reg [4:0] a,a2,b,c,d,e;
|
||||
// verilator lint_on UNOPTFLAT
|
||||
|
||||
initial a=5'd1;
|
||||
|
||||
|
|
@ -42,13 +40,11 @@ module t_netlist (/*AUTOARG*/
|
|||
c <= b+5'd1; // Better for ordering if this moves before previous statement
|
||||
end
|
||||
|
||||
// verilator lint_off UNOPT
|
||||
always @ (d or /*AS*/a or c) begin
|
||||
e = d+5'd1;
|
||||
a2 = a+5'd1; // This can be pulled out of the middle of the always
|
||||
d = c+5'd1; // Better for ordering if this moves before previous statement
|
||||
end
|
||||
// verilator lint_on UNOPT
|
||||
|
||||
always @ (posedge also_fastclk) begin
|
||||
if (_mode==5) begin
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ module t (/*AUTOARG*/
|
|||
h = {g[15:0], g[31:16]};
|
||||
end
|
||||
|
||||
// verilator lint_off UNOPTFLAT
|
||||
reg [31:0] e2,f2,g2,h2;
|
||||
always @ (/*AS*/f2, g2) begin
|
||||
h2 = {g2[15:0], g2[31:16]};
|
||||
|
|
@ -33,7 +32,6 @@ module t (/*AUTOARG*/
|
|||
f2 = {e2[15:0], e2[31:16]};
|
||||
e2 = in_a;
|
||||
end
|
||||
// verilator lint_on UNOPTFLAT
|
||||
|
||||
integer cyc; initial cyc=1;
|
||||
always @ (posedge clk) begin
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(make_top_shell=False, make_main=False, verilator_flags2=["--exe", test.pli_filename])
|
||||
test.compile(make_top_shell=False,
|
||||
make_main=False,
|
||||
verilator_flags2=["--exe", test.pli_filename, "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(fails=test.vlt_all)
|
||||
test.compile(fails=test.vlt_all, verilator_flags2=["-fno-dfg"])
|
||||
|
||||
# Used to be %Error: t/t_order_wireloop.v:\d+: Wire inputs its own output, creating circular logic .wire x=x.
|
||||
# However we no longer gate optimize this
|
||||
# Can't use expect_filename here as unstable output
|
||||
test.file_grep(
|
||||
test.compile_log_filename,
|
||||
r"%Warning-UNOPTFLAT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Circular combinational logic: \'bar\'"
|
||||
r"%Warning-UNOPTFLAT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Circular combinational logic: \'t.foo\'"
|
||||
)
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ test.scenarios('simulator')
|
|||
# CI environment offers 2 VCPUs, 2 thread setting causes the following warning.
|
||||
# %Warning-UNOPTTHREADS: Thread scheduler is unable to provide requested parallelism; consider asking for fewer threads.
|
||||
# So use 6 threads here though it's not optimal in performance, but ok.
|
||||
test.compile(verilator_flags2=['--stats', test.t_dir + "/t_split_var_0.vlt"],
|
||||
test.compile(verilator_flags2=['--stats', test.t_dir + "/t_split_var_0.vlt", "-fno-dfg"],
|
||||
threads=(6 if test.vltmt else 1))
|
||||
|
||||
test.execute()
|
||||
|
|
|
|||
|
|
@ -351,13 +351,10 @@ module unpack2pack #(parameter WIDTH = 8)
|
|||
return tmp;
|
||||
endfunction
|
||||
|
||||
/* verilator lint_off UNOPTFLAT*/
|
||||
task automatic to_packed1(input logic in[1:0] /*verilator split_var*/, output logic [1:0] out /*verilator split_var*/);
|
||||
out[1] = in[1];
|
||||
out[0] = in[0];
|
||||
endtask
|
||||
/* verilator lint_on UNOPTFLAT*/
|
||||
|
||||
|
||||
generate
|
||||
for (genvar i = 4; i < WIDTH; i += 4) begin
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(fails=True, verilator_flags2=['--stats'], expect_filename=test.golden_filename)
|
||||
test.lint(fails=True,
|
||||
verilator_flags2=['--stats', "-fno-dfg"],
|
||||
expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ test.top_filename = "t/t_split_var_0.v"
|
|||
# CI environment offers 2 VCPUs, 2 thread setting causes the following warning.
|
||||
# %Warning-UNOPTTHREADS: Thread scheduler is unable to provide requested parallelism; consider asking for fewer threads.
|
||||
# So use 6 threads here though it's not optimal in performance, but ok.
|
||||
test.compile(verilator_flags2=['--cc --trace-vcd --stats +define+TEST_ATTRIBUTES'],
|
||||
threads=(6 if test.vltmt else 1))
|
||||
test.compile(
|
||||
verilator_flags2=['--cc', '--trace-vcd', '--stats', '+define+TEST_ATTRIBUTES', "-fno-dfg"],
|
||||
threads=(6 if test.vltmt else 1))
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--stats'])
|
||||
test.compile(verilator_flags2=['--stats', "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--stats', '-DENABLE_SPLIT_VAR=1'])
|
||||
test.compile(verilator_flags2=['--stats', '-DENABLE_SPLIT_VAR=1', "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import vltest_bootstrap
|
|||
test.scenarios('simulator')
|
||||
test.top_filename = "t/t_split_var_4.v"
|
||||
|
||||
test.compile(verilator_flags2=['--stats'])
|
||||
test.compile(verilator_flags2=['--stats', "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -37,14 +37,12 @@ module t;
|
|||
`endif
|
||||
end
|
||||
|
||||
// verilator lint_off UNOPTFLAT
|
||||
int c1 = 0;
|
||||
int c2 = 0;
|
||||
always @(b2, c1) begin
|
||||
c2 = c1 >> 3;
|
||||
c1 = b2 << 3;
|
||||
end
|
||||
// verilator lint_on UNOPTFLAT
|
||||
|
||||
always @(posedge clk1) if (a2 != a1 << 1) $stop;
|
||||
always @(posedge clk2) #1 if (b2 != b1 << 2) $stop;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT"])
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ import vltest_bootstrap
|
|||
test.scenarios('vlt_all')
|
||||
test.top_filename = "t/t_unopt_array.v"
|
||||
|
||||
test.compile(v_flags2=["--trace-vcd --output-split 1 --output-split-cfuncs 1 -Wno-UNOPTFLAT"])
|
||||
test.compile(v_flags2=[
|
||||
"--trace-vcd", "--output-split", "1", "--output-split-cfuncs", "1", "-Wno-UNOPTFLAT",
|
||||
"-fno-dfg"
|
||||
])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import vltest_bootstrap
|
|||
test.scenarios('simulator')
|
||||
test.top_filename = "t/t_unopt_array.v"
|
||||
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT +define+USE_TYPEDEF"])
|
||||
test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "+define+USE_TYPEDEF", "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile()
|
||||
test.compile(verilator_flags2=["-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(v_flags2=['+define+ALLOW_UNOPT'])
|
||||
test.compile(v_flags2=['+define+ALLOW_UNOPT', "-fno-dfg"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest
|
||||
... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message.
|
||||
t/t_unopt_combo.v:23:25: Example path: t.b
|
||||
t/t_unopt_combo.v:137:14: Example path: ASSIGNW
|
||||
t/t_unopt_combo.v:124:4: Example path: ALWAYS
|
||||
t/t_unopt_combo.v:24:25: Example path: t.c
|
||||
t/t_unopt_combo.v:81:4: Example path: ALWAYS
|
||||
t/t_unopt_combo.v:23:25: Example path: t.b
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import vltest_bootstrap
|
|||
test.scenarios('simulator')
|
||||
test.top_filename = "t/t_unopt_combo.v"
|
||||
|
||||
test.compile(v_flags2=['+define+ATTRIBUTES'],
|
||||
test.compile(v_flags2=['+define+ATTRIBUTES', "-fno-dfg"],
|
||||
fails=test.vlt_all,
|
||||
expect_filename=test.golden_filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ test.top_filename = "t/t_unopt_combo.v"
|
|||
|
||||
out_filename = test.obj_dir + "/V" + test.name + ".tree.json"
|
||||
|
||||
test.compile(verilator_flags2=["--no-json-edit-nums +define+ISOLATE --stats"])
|
||||
test.compile(verilator_flags2=["--no-json-edit-nums", "+define+ISOLATE", "--stats", "-fno-dfg"])
|
||||
|
||||
if test.vlt_all:
|
||||
test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+3')
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ test.top_filename = "t/t_unopt_combo.v"
|
|||
|
||||
out_filename = test.obj_dir + "/V" + test.name + ".tree.json"
|
||||
|
||||
test.compile(
|
||||
verilator_flags2=["--no-json-edit-nums --stats", test.t_dir + "/t_unopt_combo_isolate.vlt"])
|
||||
test.compile(verilator_flags2=[
|
||||
"--no-json-edit-nums", "--stats", test.t_dir + "/t_unopt_combo_isolate.vlt", "-fno-dfg"
|
||||
])
|
||||
|
||||
if test.vlt_all:
|
||||
test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+3')
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import vltest_bootstrap
|
|||
test.scenarios('vlt_all')
|
||||
test.top_filename = "t/t_unopt_combo.v"
|
||||
|
||||
test.compile(v_flags2=['+define+ATTRIBUTES', "t/t_unopt_combo.vlt"],
|
||||
test.compile(v_flags2=['+define+ATTRIBUTES', "t/t_unopt_combo.vlt", "-fno-dfg"],
|
||||
# Passes, as we waived
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import vltest_bootstrap
|
|||
test.scenarios('simulator')
|
||||
test.top_filename = "t/t_unopt_converge_initial.v"
|
||||
|
||||
test.compile(v_flags2=['+define+ALLOW_UNOPT --output-split 0'])
|
||||
test.compile(v_flags2=['+define+ALLOW_UNOPT', '--output-split 0', "-fno-dfg"])
|
||||
|
||||
if test.vlt_all:
|
||||
test.execute(fails=True, expect_filename=test.golden_filename)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import vltest_bootstrap
|
|||
test.scenarios('simulator')
|
||||
test.top_filename = "t/t_unopt_converge.v"
|
||||
|
||||
test.compile(v_flags2=['+define+ALLOW_UNOPT'], make_flags=['CPPFLAGS_ADD=-UVL_DEBUG'])
|
||||
test.compile(v_flags2=['+define+ALLOW_UNOPT', "-fno-dfg"], make_flags=['CPPFLAGS_ADD=-UVL_DEBUG'])
|
||||
|
||||
if test.vlt_all:
|
||||
test.execute(fails=True, expect_filename=test.golden_filename)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ test.scenarios('simulator')
|
|||
test.top_filename = "t/t_unopt_converge.v"
|
||||
#test.verilated_debug = 1
|
||||
|
||||
test.compile(v_flags2=['+define+ALLOW_UNOPT --output-split 0'],
|
||||
test.compile(v_flags2=['+define+ALLOW_UNOPT', '--output-split 0', "-fno-dfg"],
|
||||
make_flags=['CPPFLAGS_ADD=-DVL_DEBUG'])
|
||||
|
||||
if test.vlt_all:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import vltest_bootstrap
|
|||
test.scenarios('simulator')
|
||||
test.top_filename = "t/t_unopt_converge.v"
|
||||
|
||||
test.compile(v_flags2=['+define+ALLOW_UNOPT --output-split 0'])
|
||||
test.compile(v_flags2=['+define+ALLOW_UNOPT', '--output-split 0', "-fno-dfg"])
|
||||
|
||||
if test.vlt_all:
|
||||
test.execute(fails=True, expect_filename=test.golden_filename)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest
|
||||
... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message.
|
||||
t/t_unopt_converge.v:19:11: Example path: x
|
||||
t/t_unopt_converge.v:23:9: Example path: ASSIGNW
|
||||
t/t_unopt_converge.v:22:4: Example path: ALWAYS
|
||||
t/t_unopt_converge.v:19:11: Example path: x
|
||||
%Error: Exiting due to
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ import vltest_bootstrap
|
|||
test.scenarios('vlt_all')
|
||||
test.top_filename = "t/t_unopt_converge.v"
|
||||
|
||||
test.compile(fails=True, expect_filename=test.golden_filename)
|
||||
test.compile(verilator_flags2=["-fno-dfg"], fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest
|
||||
... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message.
|
||||
t/t_unoptflat_simple_2.v:16:15: Example path: t.x
|
||||
t/t_unoptflat_simple_2.v:13:10: Example path: ASSIGNW
|
||||
t/t_unoptflat_simple_2.v:19:18: Example path: ASSIGNW
|
||||
t/t_unoptflat_simple_2.v:16:15: Example path: t.x
|
||||
... Widest variables candidate to splitting:
|
||||
t/t_unoptflat_simple_2.v:16:15: t.x, width 3, circular fanout 1, can split_var
|
||||
t/t_unoptflat_simple_2.v:16:15: t.x, width 3, circular fanout 2, can split_var
|
||||
... Candidates with the highest fanout:
|
||||
t/t_unoptflat_simple_2.v:16:15: t.x, width 3, circular fanout 1, can split_var
|
||||
t/t_unoptflat_simple_2.v:16:15: t.x, width 3, circular fanout 2, can split_var
|
||||
... Suggest add /*verilator split_var*/ or /*verilator isolate_assignments*/ to appropriate variables above.
|
||||
%Error: Exiting due to
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ test.top_filename = "t/t_unoptflat_simple_2.v"
|
|||
|
||||
# Compile only
|
||||
test.compile(verilator_flags3=[],
|
||||
verilator_flags2=["--report-unoptflat", "-fno-dfg-break-cycles"],
|
||||
verilator_flags2=["--report-unoptflat", "-fno-dfg"],
|
||||
fails=True,
|
||||
expect_filename=test.golden_filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ test.scenarios('simulator')
|
|||
test.top_filename = "t/t_unoptflat_simple_3.v"
|
||||
|
||||
# Compile only
|
||||
test.compile(fails=True)
|
||||
test.compile(verilator_flags2=["-fno-dfg"], fails=True)
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ test.scenarios('simulator')
|
|||
test.top_filename = "t/t_unoptflat_simple.v"
|
||||
|
||||
# Compile only
|
||||
test.compile(fails=True)
|
||||
test.compile(verilator_flags2=["-fno-dfg"], fails=True)
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
Loading…
Reference in New Issue