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:
Geza Lore 2025-08-19 15:06:38 +01:00 committed by GitHub
parent 6a67c0a0e5
commit 636a6b8cd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
95 changed files with 4511 additions and 1518 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

90
src/V3Cfg.cpp Normal file
View File

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

270
src/V3Cfg.h Normal file
View File

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

291
src/V3CfgBuilder.cpp Normal file
View File

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

240
src/V3CfgLiveVariables.cpp Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1696
src/V3DfgSynthesize.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,6 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.compile(verilator_flags2=["-fno-dfg"])
test.passes()

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.compile(verilator_flags2=["-fno-dfg"])
test.execute()

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.compile(verilator_flags2=["-fno-dfg"])
test.execute()

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.compile(verilator_flags2=["-fno-dfg"])
test.execute()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--stats'])
test.compile(verilator_flags2=['--stats', "-fno-dfg"])
test.execute()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.compile(verilator_flags2=["-fno-dfg"])
test.execute()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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