Compare commits

...

34 Commits

Author SHA1 Message Date
Igor Zaworski f259a50031 Introduction of a test
Signed-off-by: Igor Zaworski <izaworski@internships.antmicro.com>
2025-07-21 08:29:45 +02:00
Igor Zaworski 3aa7d7dfbc [#80101] Getting param dependent type linking error fix
Signed-off-by: Igor Zaworski <izaworski@internships.antmicro.com>
2025-07-21 08:29:45 +02:00
Artur Bieniek abd509ce53
Support delays in emitted Verilog (#6177) 2025-07-16 11:52:56 -04:00
Ryszard Rozak 1bf24c7eb4
Add support for disabling begin just under fork from outside that begin (#5432 partial) (#6183) 2025-07-16 16:04:17 +02:00
Wilson Snyder 1f0357ba93 Add NOEFFECT warning, replacing previous `foreach` error. 2025-07-16 08:18:57 -04:00
Wilson Snyder db6b17fdb4 Fix error message 2025-07-15 17:41:08 -04:00
Artur Bieniek f3e109d8c5
Support covergroup extends, etc. (#6160) 2025-07-15 09:31:08 -04:00
Wilson Snyder 371ac07c6f Fix CVTREAL not being able to be disabled in e.g. primitive terminals. 2025-07-14 20:08:44 -04:00
Wilson Snyder caf3522364 Support implicit enum declarations with packed dimensions 2025-07-14 19:50:02 -04:00
Geza Lore 03e0d49d99
Optimize DFG partial assignments (#6176)
This is mostly a refactoring, but also enables handling some more
UNOPTFLAT, when the variable is only partially assigned in the cycle.

Previously the way partial assignments to variables were handled were
through the DfgVerexVar types themselves, which kept track of all
drivers. This has been replaced by DfgVertexSplice (which always drives
a DfgVeretexVar), and all DfgVertexVar now only have a single source,
either a DfgVertexSplice, if partially assigned, or an arbitrary
DfgVertex when updated as a whole.
2025-07-14 17:09:34 -04:00
Ryszard Rozak e2e5d9eaf1
Support disabling a fork from outside that fork (#6174) 2025-07-14 06:51:58 -04:00
Wilson Snyder 2f199f20cf Add ENUMITEMWIDTH error, and apply to X-extended and ranged values. 2025-07-12 14:14:17 -04:00
Wilson Snyder cefe1845df Commentary: Changes update 2025-07-11 21:53:29 -04:00
Yilou Wang 1044398f95
Support member-level triggers for virtual interfaces (#5166) (#6148) 2025-07-11 21:04:51 -04:00
Geza Lore 77180c4020
Optimize more cycles in DFG (#6173)
Added a second algorithm to break cycles in DFG by identifying which
bits of a circular variable are actually independent of the variable,
then reuse the existing (but extended) driver tracing algorithm to
eliminate them.

This can fix up things like: `assign gray = binary ^ (gray >> 1)`
2025-07-11 14:19:09 -04:00
github action 2fc12d951e Apply 'make format' 2025-07-11 17:11:31 +00:00
Petr Nohavica 0982260d3b
Fix constructor parameters in inheritance hierarchies (#6036) (#6070) 2025-07-11 13:10:36 -04:00
Artur Bieniek 58b867c39c
Support multiple variables on RHS of a `force` assignment (#6163) 2025-07-10 21:12:44 -04:00
Igor Zaworski 4e8a8a0398
Fix param-dependent class typedef linking (#6171) 2025-07-10 21:11:09 -04:00
Artur Bieniek 4dc6a31276
Fix omitting error when assigning to an input (#6169) 2025-07-10 20:37:55 -04:00
Geza Lore ce77bac99a
Break some combinational cycles in DFG (#6168)
Added an algorithm that can break some combinational cycles in DFG, by
attempting to trace driver logic until we escape the cycle. This can
eliminate a decent portion of UNOPTFLAT warnings. E.g. due to this code:

```systemverilog
assign a[0] = .....;
assign a[1] = ~a[0];
```
2025-07-10 18:46:45 +01:00
Igor Zaworski 31c279a7b3
Support randomize() on class member selects (#6161) 2025-07-10 04:59:05 -04:00
Bartłomiej Chmiel 9ad0de1efd
Fix uninitialized thread PGO counters (#6167) 2025-07-10 04:56:14 -04:00
Wilson Snyder d89df33fcd Change control file `public_flat_*` and other signal attributes to support __ in names (#6140). 2025-07-09 20:48:00 -04:00
Martin Stadler d1462f3120
Fix cmake `-Wno` compiler flag testing (#6145) 2025-07-09 19:31:42 -04:00
Wilson Snyder 9fc7143fce Fix genvar error with `-O0` (#6165). 2025-07-09 19:11:48 -04:00
Wilson Snyder 597b973f7b Internals: Fix some style issues. No functional change. 2025-07-09 18:52:26 -04:00
Wilson Snyder a84c5d2010 Commentary: Changes update 2025-07-09 18:51:49 -04:00
Igor Zaworski dbfbc657e1
Fix class extends dotted error (#6162) 2025-07-09 17:12:11 -04:00
Ryszard Rozak 8b3a6ba542
Support disable dotted references (#6154) 2025-07-09 16:59:26 -04:00
Wilson Snyder 8ba7cec15b devel release 2025-07-08 23:22:58 -04:00
Wilson Snyder f037ac50b4 Version bump 2025-07-08 23:02:26 -04:00
Wilson Snyder 1f0e767b61 Commentary: Changes update 2025-07-08 17:37:21 -04:00
Igor Zaworski 5777ab75c7
Fix crash with --dumpi-V3LinkDot without --debug (#6159) 2025-07-08 10:28:17 -04:00
186 changed files with 5687 additions and 1058 deletions

View File

@ -16,7 +16,7 @@ cmake_minimum_required(VERSION 3.15)
cmake_policy(SET CMP0091 NEW) # Use MSVC_RUNTIME_LIBRARY to select the runtime
project(
Verilator
VERSION 5.037
VERSION 5.039
HOMEPAGE_URL https://verilator.org
LANGUAGES CXX
)

26
Changes
View File

@ -8,7 +8,29 @@ The changes in each Verilator version are described below. The
contributors that suggested or implemented a given issue are shown in []. Thanks!
Verilator 5.037 devel
Verilator 5.039 devel
==========================
**Other:**
* Add ENUMITEMWIDTH error, and apply to X-extended and ranged values.
* Add NOEFFECT warning, replacing previous `foreach` error.
* Support member-level triggers for virtual interfaces (#5166) (#6148). [Yilou Wang]
* Support disable dotted references (#6154). [Ryszard Rozak, Antmicro Ltd.]
* Support randomize() on class member selects (#6161). [Igor Zaworski, Antmicro Ltd.]
* Support multiple variables on RHS of a `force` assignment (#6163). [Artur Bieniek, Antmicro Ltd.]]
* Change control file `public_flat_*` and other signal attributes to support __ in names (#6140).
* Fix constructor parameters in inheritance hierarchies (#6036) (#6070). [Petr Nohavica]
* Fix cmake `-Wno` compiler flag testing (#6145). [Martin Stadler]
* Fix class extends dotted error (#6162). [Igor Zaworski, Antmicro Ltd.]
* Fix genvar error with `-O0` (#6165). [Max Wipfli]
* Fix uninitialized thread PGO counters (#6167). [Bartłomiej Chmiel, Antmicro Ltd.]
* Fix additional UNOPTFLAT combinational cycles automatically in DFG (#6168) (#6173). [Geza Lore]
* Fix omitting error when assigning to an input (#6169). [Artur Bieniek, Antmicro Ltd.]]
* Fix param-dependent class typedef linking (#6171). [Igor Zaworski, Antmicro Ltd.]
Verilator 5.038 2025-07-08
==========================
**Important:**
@ -47,6 +69,7 @@ Verilator 5.037 devel
* Optimize DFG variable elimination (#6091). [Geza Lore]
* Optimize DFG PUSH_SEL_THROUGH_CONCAT pattern (#6092). [Geza Lore]
* Optimize DFG before V3Gate (#6141). [Geza Lore]
* Optimize DFG peephole patterns (#6149). [Geza Lore]
* Optimize constification within Expand and Subst stages (#6111). [Geza Lore]
* Fix --x-initial and --x-assign random stability (#2662) (#5958) (#6018) (#6025) (#6075). [Todd Strader]
* Fix trace hierarchical-name runtime errors (#5668) (#6076). [Paul Swirhun]
@ -95,6 +118,7 @@ Verilator 5.037 devel
* Fix wide non-blocking assignment mis-optimization (#6150) (#6152) (#6155). [Todd Strader]
* Fix interface array connections with non-zero low declaration index.
* Fix developer build error on MacOS/Flex2.6.4 (#6153). [Paul Swirhun]
* Fix crash with --dumpi-V3LinkDot without --debug (#6159). [Igor Zaworski, Antmicro Ltd.]
Verilator 5.036 2025-04-27

View File

@ -10,7 +10,7 @@
# Then 'make maintainer-dist'
#AC_INIT([Verilator],[#.### YYYY-MM-DD])
#AC_INIT([Verilator],[#.### devel])
AC_INIT([Verilator],[5.037 devel],
AC_INIT([Verilator],[5.039 devel],
[https://verilator.org],
[verilator],[https://verilator.org])

View File

@ -24,6 +24,7 @@ Anthony Donlon
Anthony Moore
Arkadiusz Kozdra
Arthur Rosa
Artur Bieniek
Aylon Chaim Porat
Bartłomiej Chmiel
Brian Li
@ -83,6 +84,7 @@ Huang Rui
Huanghuang Zhou
HungMingWu
HyungKi Jeong
Igor Zaworski
Ilya Barkov
Iru Cai
Ivan Vnučec

View File

@ -606,6 +606,10 @@ Summary:
optimizer. Alias for :vlopt:`-fno-dfg-pre-inline`,
:vlopt:`-fno-dfg-post-inline` and :vlopt:`-fno-dfg-scoped`.
.. option:: -fno-dfg-break-cycles
Rarely needed. Disable breaking combinational cycles during DFG.
.. option:: -fno-dfg-peephole
Rarely needed. Disable the DFG peephole optimizer.

View File

@ -709,6 +709,28 @@ List Of Warnings
missing."
.. option:: ENUMITEMWIDTH
An error that an enum item value is being assigned from a value which
would be truncated (similar to :option:`WIDTHTRUNC`), or from a sized
literal constant with a different bit width (similar to
:option:`WIDTHTRUNC` or :option:`WIDTHEXPAND`). IEEE requires this
error, but it may be disabled.
Faulty example:
.. code-block:: sv
:linenos:
:emphasize-lines: 2
typedef enum [3:0] {
WRONG_WIDTH = 33'h3 //<--- Warning
} enum_t;
To repair, correct the size of the item's value directly, or use a cast,
so the resulting width matches the enum's width.
.. option:: ENUMVALUE
An error that an enum data type value is being assigned from another data
@ -1286,6 +1308,21 @@ List Of Warnings
simulate correctly.
.. option:: NOEFFECT
Warns that the statement will have no effect and is roughly equivalent
to not being present. This is only issued when it is "non-obvious",
e.g. a :code:`if (0)` will not result in this warning.
Faulty example:
.. code-block:: sv
foreach (array[]) begin ... end //<--- Warning
For a fix, remove the statement.
.. option:: NOLATCH
.. TODO better example

View File

@ -247,6 +247,7 @@ Lesik
Libenzi
Licker
Liland
LinkDot
Liu
Liwei
Lockhart
@ -480,6 +481,7 @@ Wfuture
Whatson
Wildman
Wim
Wipfli
Wmisleading
Wno
Wojciech

View File

@ -218,7 +218,7 @@ class VlPgoProfiler final {
};
// Counters are stored packed, all together to reduce cache effects
std::array<uint64_t, N_Entries> m_counters; // Time spent on this record
std::array<uint64_t, N_Entries> m_counters{}; // Time spent on this record
std::vector<Record> m_records; // Record information
public:

View File

@ -175,6 +175,14 @@ package std;
`endif
endtask
static task killQueue(ref process processQueue[$]);
`ifdef VERILATOR_TIMING
while (processQueue.size() > 0) begin
processQueue.pop_back().kill();
end
`endif
endtask
// Two process references are equal if the different classes' containing
// m_process are equal. Can't yet use <=> as the base class template
// comparisons doesn't define <=> as they don't yet require --timing and C++20.

View File

@ -227,6 +227,7 @@ set(COMMON_SOURCES
V3Descope.cpp
V3Dfg.cpp
V3DfgAstToDfg.cpp
V3DfgBreakCycles.cpp
V3DfgCache.cpp
V3DfgDecomposition.cpp
V3DfgDfgToAst.cpp

View File

@ -240,6 +240,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Descope.o \
V3Dfg.o \
V3DfgAstToDfg.o \
V3DfgBreakCycles.o \
V3DfgCache.o \
V3DfgDecomposition.o \
V3DfgDfgToAst.o \

View File

@ -2222,6 +2222,9 @@ public:
// ACCESSORS
virtual string name() const VL_MT_STABLE { return ""; }
virtual string origName() const { return ""; }
string prettyOrigOrName() const {
return prettyName(origName().empty() ? name() : origName());
}
virtual void name(const string& name) {
this->v3fatalSrc("name() called on object without name() method");
}

View File

@ -46,6 +46,7 @@ protected:
public:
ASTGEN_MEMBERS_AstNodeBlock;
bool maybePointedTo() const override VL_MT_SAFE { return true; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return m_name; } // * = Block name
@ -1973,7 +1974,7 @@ class AstVar final : public AstNode {
bool m_isConst : 1; // Table contains constant data
bool m_isContinuously : 1; // Ever assigned continuously (for force/release)
bool m_hasStrengthAssignment : 1; // Is on LHS of assignment with strength specifier
bool m_isStatic : 1; // Static C variable (for Verilog see instead isAutomatic)
bool m_isStatic : 1; // Static C variable (for Verilog see instead lifetime())
bool m_isPulldown : 1; // Tri0
bool m_isPullup : 1; // Tri1
bool m_isIfaceParent : 1; // dtype is reference to interface present in this module
@ -3096,14 +3097,18 @@ public:
bool isCycleDelay() const { return m_isCycle; }
};
class AstDisable final : public AstNodeStmt {
string m_name; // Name of block
// @astgen op1 := targetRefp : Optional[AstNodeExpr] // Reference to link in V3LinkDot
// @astgen ptr := m_targetp : Optional[AstNode] // Task or block after V3LinkDot
public:
AstDisable(FileLine* fl, const string& name)
: ASTGEN_SUPER_Disable(fl)
, m_name{name} {}
AstDisable(FileLine* fl, AstNodeExpr* targetRefp)
: ASTGEN_SUPER_Disable(fl) {
this->targetRefp(targetRefp);
}
ASTGEN_MEMBERS_AstDisable;
string name() const override VL_MT_STABLE { return m_name; } // * = Block name
void name(const string& flag) override { m_name = flag; }
const char* broken() const override;
void dump(std::ostream& str) const override;
void targetp(AstNode* nodep) { m_targetp = nodep; }
AstNode* targetp() const { return m_targetp; }
bool isBrancher() const override {
return true; // SPECIAL: We don't process code after breaks
}

View File

@ -2604,6 +2604,8 @@ void AstVar::dump(std::ostream& str) const {
if (isPulldown()) str << " [PULLDOWN]";
if (isUsedClock()) str << " [CLK]";
if (isSigPublic()) str << " [P]";
if (isSigUserRdPublic()) str << " [PRD]";
if (isSigUserRWPublic()) str << " [PWR]";
if (isInternal()) str << " [INTERNAL]";
if (isLatched()) str << " [LATCHED]";
if (isUsedLoopIdx()) str << " [LOOP]";
@ -3177,6 +3179,20 @@ void AstDelay::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isCycleDelay);
dumpJsonGen(str);
}
const char* AstDisable::broken() const {
BROKEN_RTN((m_targetp && targetRefp()) || ((!m_targetp && !targetRefp())));
return nullptr;
}
void AstDisable::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
str << " -> ";
if (targetp()) {
targetp()->dump(str);
} else {
str << "UNLINKED";
}
}
const char* AstAnd::widthMismatch() const VL_MT_STABLE {
BROKEN_RTN(lhsp()->widthMin() != rhsp()->widthMin());
BROKEN_RTN(lhsp()->widthMin() != widthMin());

View File

@ -530,7 +530,11 @@ AstNode* V3Begin::convertToWhile(AstForeach* nodep) {
}
// The parser validates we don't have "foreach (array[,,,])"
AstNode* const bodyp = nodep->stmtsp();
UASSERT_OBJ(newp, nodep, "foreach has no non-empty loop variable");
if (!newp) {
nodep->v3warn(NOEFFECT, "foreach with no loop variable has no effect");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return nullptr;
}
if (bodyp) {
bodyPointp->replaceWith(bodyp->unlinkFrBackWithNext());
} else {

View File

@ -57,28 +57,18 @@ class ClassVisitor final : public VNVisitor {
// METHODS
bool recurseImplements(AstClass* nodep, bool setit) {
// Returns true to set useVirtualPublic().
// If there's an implements of an interface class then we have
// multiple classes that point to same object, that need same
// VlClass (the diamond problem). C++ will require we use 'virtual
// public' for VlClass. So, we need the interface class, and all
// classes above, and any below using any implements to use
// 'virtual public' via useVirtualPublic().
if (nodep->useVirtualPublic()) return true; // Short-circuit
if (nodep->isInterfaceClass()) setit = true;
void recurseImplements(AstClass* nodep) {
// In SystemVerilog, we have two inheritance chains:
// - extends of concrete clasess: mapped to non-virtual C++ inheritance
// as there is only single ancestor allowed
// - implements of concrete classes / extends of interface classes: mapped
// to virtual inheritance to allow diamond patterns with multiple ancestors
if (nodep->useVirtualPublic()) return; // Short-circuit to exit diamond cycles
if (nodep->isInterfaceClass()) { nodep->useVirtualPublic(true); }
for (const AstClassExtends* extp = nodep->extendsp(); extp;
extp = VN_AS(extp->nextp(), ClassExtends)) {
if (recurseImplements(extp->classp(), setit)) setit = true;
recurseImplements(extp->classp());
}
if (setit) {
nodep->useVirtualPublic(true);
for (const AstClassExtends* extp = nodep->extendsp(); extp;
extp = VN_AS(extp->nextp(), ClassExtends)) {
(void)recurseImplements(extp->classp(), true);
}
}
return setit;
}
// VISITORS
@ -89,7 +79,7 @@ class ClassVisitor final : public VNVisitor {
nodep->name(m_prefix + nodep->name());
nodep->unlinkFrBack();
v3Global.rootp()->addModulesp(nodep);
(void)recurseImplements(nodep, false);
recurseImplements(nodep);
// Make containing package
// Note origName is the same as the class origName so errors look correct
AstClassPackage* const packagep

View File

@ -276,7 +276,7 @@ public:
const VPragmaType pragma = VPragmaType::COVERAGE_BLOCK_OFF;
if (!nodep->unnamed()) {
for (const string& i : m_coverageOffBlocks) {
if (VString::wildmatch(nodep->name(), i)) {
if (VString::wildmatch(nodep->prettyOrigOrName(), i)) {
nodep->addStmtsp(new AstPragma{nodep->fileline(), pragma});
}
}
@ -724,49 +724,50 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta
void V3Control::applyCase(AstCase* nodep) {
const string& filename = nodep->fileline()->filename();
V3ControlFile* filep = V3ControlResolver::s().files().resolve(filename);
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
if (filep) filep->applyCase(nodep);
}
void V3Control::applyCoverageBlock(AstNodeModule* modulep, AstBegin* nodep) {
const string& filename = nodep->fileline()->filename();
V3ControlFile* filep = V3ControlResolver::s().files().resolve(filename);
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
if (filep) filep->applyBlock(nodep);
const string& modname = modulep->name();
V3ControlModule* modp = V3ControlResolver::s().modules().resolve(modname);
const string& modname = modulep->prettyOrigOrName();
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modname);
if (modp) modp->applyBlock(nodep);
}
void V3Control::applyIgnores(FileLine* filelinep) {
const string& filename = filelinep->filename();
V3ControlFile* filep = V3ControlResolver::s().files().resolve(filename);
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
if (filep) filep->applyIgnores(filelinep);
}
void V3Control::applyModule(AstNodeModule* modulep) {
const string& modname = modulep->origName();
V3ControlModule* modp = V3ControlResolver::s().modules().resolve(modname);
const string& modname = modulep->prettyOrigOrName();
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modname);
if (modp) modp->apply(modulep);
}
void V3Control::applyFTask(AstNodeModule* modulep, AstNodeFTask* ftaskp) {
const string& modname = modulep->name();
V3ControlModule* modp = V3ControlResolver::s().modules().resolve(modname);
const string& modname = modulep->prettyOrigOrName();
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modname);
if (!modp) return;
const V3ControlFTask* const ftp = modp->ftasks().resolve(ftaskp->name());
const V3ControlFTask* const ftp = modp->ftasks().resolve(ftaskp->prettyOrigOrName());
if (ftp) ftp->apply(ftaskp);
}
void V3Control::applyVarAttr(AstNodeModule* modulep, AstNodeFTask* ftaskp, AstVar* varp) {
V3ControlVar* vp;
V3ControlModule* modp = V3ControlResolver::s().modules().resolve(modulep->name());
V3ControlModule* const modp
= V3ControlResolver::s().modules().resolve(modulep->prettyOrigOrName());
if (!modp) return;
if (ftaskp) {
V3ControlFTask* ftp = modp->ftasks().resolve(ftaskp->name());
V3ControlFTask* const ftp = modp->ftasks().resolve(ftaskp->prettyOrigOrName());
if (!ftp) return;
vp = ftp->vars().resolve(varp->name());
vp = ftp->vars().resolve(varp->prettyOrigOrName());
} else {
vp = modp->vars().resolve(varp->name());
vp = modp->vars().resolve(varp->prettyOrigOrName());
}
if (vp) vp->apply(varp);
}
@ -797,7 +798,7 @@ bool V3Control::containsMTaskProfileData() {
}
bool V3Control::waive(FileLine* filelinep, V3ErrorCode code, const string& message) {
V3ControlFile* filep = V3ControlResolver::s().files().resolve(filelinep->filename());
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filelinep->filename());
if (!filep) return false;
return filep->waive(code, message);
}

View File

@ -34,6 +34,147 @@ DfgGraph::~DfgGraph() {
forEachVertex([](DfgVertex& vtxp) { delete &vtxp; });
}
std::unique_ptr<DfgGraph> DfgGraph::clone() const {
const bool scoped = !modulep();
DfgGraph* const clonep = new DfgGraph{modulep(), name()};
// Map from original vertex to clone
std::unordered_map<const DfgVertex*, DfgVertex*> vtxp2clonep(size() * 2);
// Clone constVertices
for (const DfgConst& vtx : m_constVertices) {
DfgConst* const cp = new DfgConst{*clonep, vtx.fileline(), vtx.num()};
vtxp2clonep.emplace(&vtx, cp);
}
// Clone variable vertices
for (const DfgVertexVar& vtx : m_varVertices) {
const DfgVertexVar* const vp = vtx.as<DfgVertexVar>();
DfgVertexVar* cp = nullptr;
switch (vtx.type()) {
case VDfgType::atVarArray: {
if (scoped) {
cp = new DfgVarArray{*clonep, vp->varScopep()};
} else {
cp = new DfgVarArray{*clonep, vp->varp()};
}
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::atVarPacked: {
if (scoped) {
cp = new DfgVarPacked{*clonep, vp->varScopep()};
} else {
cp = new DfgVarPacked{*clonep, vp->varp()};
}
vtxp2clonep.emplace(&vtx, cp);
break;
}
default: {
vtx.v3fatalSrc("Unhandled variable vertex type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
if (vp->hasDfgRefs()) cp->setHasDfgRefs();
if (vp->hasModRefs()) cp->setHasModRefs();
if (vp->hasExtRefs()) cp->setHasExtRefs();
}
// Clone operation vertices
for (const DfgVertex& vtx : m_opVertices) {
switch (vtx.type()) {
#include "V3Dfg__gen_clone_cases.h" // From ./astgen
case VDfgType::atSel: {
DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtypep()};
cp->lsb(vtx.as<DfgSel>()->lsb());
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::atMux: {
DfgMux* const cp = new DfgMux{*clonep, vtx.fileline(), vtx.dtypep()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::atSpliceArray: {
DfgSpliceArray* const cp = new DfgSpliceArray{*clonep, vtx.fileline(), vtx.dtypep()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::atSplicePacked: {
DfgSplicePacked* const cp = new DfgSplicePacked{*clonep, vtx.fileline(), vtx.dtypep()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
default: {
vtx.v3fatalSrc("Unhandled operation vertex type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
}
UASSERT(size() == clonep->size(), "Size of clone should be the same");
// Constants have no inputs
// Hook up inputs of cloned variables
for (const DfgVertexVar& vtx : m_varVertices) {
// All variable vertices are unary
if (DfgVertex* const srcp = vtx.srcp()) {
vtxp2clonep.at(&vtx)->as<DfgVertexVar>()->srcp(vtxp2clonep.at(srcp));
}
}
// Hook up inputs of cloned operation vertices
for (const DfgVertex& vtx : m_opVertices) {
if (vtx.is<DfgVertexVariadic>()) {
switch (vtx.type()) {
case VDfgType::atSpliceArray: {
const DfgSpliceArray* const vp = vtx.as<DfgSpliceArray>();
DfgSpliceArray* const cp = vtxp2clonep.at(vp)->as<DfgSpliceArray>();
vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) {
if (DfgVertex* const srcp = edge.sourcep()) {
cp->addDriver(vp->driverFileLine(i), //
vp->driverIndex(i), //
vtxp2clonep.at(srcp));
}
});
break;
}
case VDfgType::atSplicePacked: {
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));
}
});
break;
}
default: {
vtx.v3fatalSrc("Unhandled DfgVertexVariadic sub type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
} else {
DfgVertex* const cp = vtxp2clonep.at(&vtx);
const auto oSourceEdges = vtx.sourceEdges();
auto cSourceEdges = cp->sourceEdges();
UASSERT_OBJ(oSourceEdges.second == cSourceEdges.second, &vtx,
"Mismatched source count");
for (size_t i = 0; i < oSourceEdges.second; ++i) {
if (DfgVertex* const srcp = oSourceEdges.first[i].sourcep()) {
cSourceEdges.first[i].relinkSource(vtxp2clonep.at(srcp));
}
}
}
}
return std::unique_ptr<DfgGraph>{clonep};
}
void DfgGraph::addGraph(DfgGraph& other) {
m_size += other.m_size;
other.m_size = 0;
@ -188,6 +329,25 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
return;
}
if (vtx.is<DfgVertexSplice>()) {
os << toDotId(vtx);
os << " [label=\"" << vtx.typeName() << "\n";
if (const DfgSpliceArray* const sp = vtx.cast<DfgSpliceArray>()) {
const int elements = VN_AS(sp->dtypep(), UnpackArrayDType)->elementsConst();
os << "_[" << elements << "]";
} else {
os << "W" << vtx.width();
}
os << " / F" << vtx.fanout() << '"';
if (vtx.hasMultipleSinks()) {
os << ", shape=doubleoctagon";
} else {
os << ", shape=octagon";
}
os << "]\n";
return;
}
os << toDotId(vtx);
os << " [label=\"" << vtx.typeName() << "\nW" << vtx.width() << " / F" << vtx.fanout() << '"';
if (vtx.hasMultipleSinks()) {
@ -211,7 +371,7 @@ static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx)
vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { //
if (edge.sourcep()) {
string headLabel;
if (vtx.arity() > 1 || vtx.is<DfgVertexVar>()) headLabel = vtx.srcName(idx);
if (vtx.arity() > 1 || vtx.is<DfgVertexSplice>()) headLabel = vtx.srcName(idx);
dumpDotEdge(os, edge, headLabel);
}
});
@ -396,24 +556,34 @@ bool DfgVertex::selfEquals(const DfgVertex& that) const { return true; }
V3Hash DfgVertex::selfHash() const { return V3Hash{}; }
bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const {
// If same vertex, then equal
if (this == &that) return true;
// If different type, then not equal
if (this->type() != that.type()) return false;
// If different data type, then not equal
if (this->dtypep() != that.dtypep()) return false;
// If different number of inputs, then not equal
auto thisPair = this->sourceEdges();
const DfgEdge* const thisSrcEdgesp = thisPair.first;
const size_t thisArity = thisPair.second;
auto thatPair = that.sourceEdges();
const DfgEdge* const thatSrcEdgesp = thatPair.first;
const size_t thatArity = thatPair.second;
if (thisArity != thatArity) return false;
// Check vertex specifics
if (!this->selfEquals(that)) return false;
// Check sources
const auto key = (this < &that) ? EqualsCache::key_type{this, &that} //
: EqualsCache::key_type{&that, this};
// Note: the recursive invocation can cause a re-hash but that will not invalidate references
uint8_t& result = cache[key];
if (!result) {
result = 2; // Assume equals
auto thisPair = this->sourceEdges();
const DfgEdge* const thisSrcEdgesp = thisPair.first;
const size_t thisArity = thisPair.second;
auto thatPair = that.sourceEdges();
const DfgEdge* const thatSrcEdgesp = thatPair.first;
const size_t thatArity = thatPair.second;
UASSERT_OBJ(thisArity == thatArity, this, "Same type vertices must have same arity!");
for (size_t i = 0; i < thisArity; ++i) {
const DfgVertex* const thisSrcVtxp = thisSrcEdgesp[i].m_sourcep;
const DfgVertex* const thatSrcVtxp = thatSrcEdgesp[i].m_sourcep;
@ -436,7 +606,12 @@ V3Hash DfgVertex::hash() {
// variables, which we rely on.
if (!is<DfgVertexVar>()) {
hash += m_type;
hash += width(); // Currently all non-variable vertices are packed, so this is safe
if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep(), UnpackArrayDType)) {
hash += adtypep->elementsConst();
// TODO: maybe include sub-dtype, but not hugely important at the moment
} else {
hash += width();
}
const auto pair = sourceEdges();
const DfgEdge* const edgesp = pair.first;
const size_t arity = pair.second;
@ -454,19 +629,16 @@ uint32_t DfgVertex::fanout() const {
return result;
}
DfgVarPacked* DfgVertex::getResultVar() {
UASSERT_OBJ(!this->is<DfgVarArray>(), this, "Arrays are not supported by " << __FUNCTION__);
DfgVertexVar* DfgVertex::getResultVar() {
// It's easy if the vertex is already a variable ...
if (DfgVarPacked* const varp = this->cast<DfgVarPacked>()) return varp;
if (DfgVertexVar* const varp = this->cast<DfgVertexVar>()) return varp;
// Inspect existing variables fully written by this vertex, and choose one
DfgVarPacked* resp = nullptr;
// Inspect existing variables written by this vertex, and choose one
DfgVertexVar* resp = nullptr;
// cppcheck-has-bug-suppress constParameter
this->forEachSink([&resp](DfgVertex& sink) {
DfgVarPacked* const varp = sink.cast<DfgVarPacked>();
DfgVertexVar* const varp = sink.cast<DfgVertexVar>();
if (!varp) return;
if (!varp->isDrivenFullyByDfg()) return;
// Ignore SystemC variables, they cannot participate in expressions or
// be assigned rvalue expressions.
if (varp->varp()->isSc()) return;
@ -569,6 +741,42 @@ bool DfgSel::selfEquals(const DfgVertex& that) const { return lsb() == that.as<D
V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; }
// DfgSpliceArray ----------
bool DfgSpliceArray::selfEquals(const DfgVertex& that) const {
const DfgSpliceArray* const thatp = that.as<DfgSpliceArray>();
const size_t arity = this->arity();
for (size_t i = 0; i < arity; ++i) {
if (driverIndex(i) != thatp->driverIndex(i)) return false;
}
return true;
}
V3Hash DfgSpliceArray::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;
}
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;
}
// DfgVertexVar ----------
bool DfgVertexVar::selfEquals(const DfgVertex& that) const {

View File

@ -238,8 +238,7 @@ public:
// Width of result
uint32_t width() const {
// This is a hot enough function that this is an expensive check, so in debug build only.
UDEBUGONLY(UASSERT_OBJ(VN_IS(dtypep(), BasicDType), this, "non-packed has no 'width()'"););
UASSERT_OBJ(VN_IS(dtypep(), BasicDType), this, "non-packed has no 'width()'");
return dtypep()->width();
}
@ -283,7 +282,7 @@ public:
// Return a canonical variable vertex that holds the value of this vertex,
// or nullptr if no such variable exists in the graph. This is O(fanout).
DfgVarPacked* getResultVar() VL_MT_DISABLED;
DfgVertexVar* getResultVar() VL_MT_DISABLED;
// Cache type for 'scopep' below
using ScopeCache = std::unordered_map<const DfgVertex*, AstScope*>;
@ -572,7 +571,7 @@ class DfgVertexVariadic VL_NOT_FINAL : public DfgVertex {
protected:
DfgVertexVariadic(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep,
uint32_t initialCapacity = 1)
uint32_t initialCapacity)
: DfgVertex{dfg, type, flp, dtypep}
, m_srcsp{allocSources(initialCapacity)}
, m_srcCap{initialCapacity} {}
@ -706,6 +705,9 @@ public:
// 'const' variant of 'forEachVertex'. No mutation allowed.
inline void forEachVertex(std::function<void(const DfgVertex&)> f) const;
// Return an identical, independent copy of this graph. Vertex and edge order might differ.
std::unique_ptr<DfgGraph> clone() const VL_MT_DISABLED;
// Add contents of other graph to this graph. Leaves other graph empty.
void addGraph(DfgGraph& other) VL_MT_DISABLED;
@ -911,15 +913,14 @@ bool DfgVertex::isOnes() const {
// Inline method definitions - for DfgVertexVar
//------------------------------------------------------------------------------
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp, uint32_t initialCapacity)
: DfgVertexVariadic{dfg, type, varp->fileline(), dtypeFor(varp), initialCapacity}
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp)
: DfgVertexUnary{dfg, type, varp->fileline(), dtypeFor(varp)}
, m_varp{varp}
, m_varScopep{nullptr} {
UASSERT_OBJ(dfg.modulep(), varp, "Un-scoped DfgVertexVar created in scoped DfgGraph");
}
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp,
uint32_t initialCapacity)
: DfgVertexVariadic{dfg, type, vscp->fileline(), dtypeFor(vscp), initialCapacity}
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp)
: DfgVertexUnary{dfg, type, vscp->fileline(), dtypeFor(vscp)}
, m_varp{vscp->varp()}
, m_varScopep{vscp} {
UASSERT_OBJ(!dfg.modulep(), vscp, "Scoped DfgVertexVar created in un-scoped DfgGraph");

View File

@ -183,49 +183,37 @@ class AstToDfgVisitor final : public VNVisitor {
return m_foundUnhandled;
}
// Build DfgEdge representing the LValue assignment. Returns false if unsuccessful.
bool convertAssignment(FileLine* flp, AstNode* nodep, DfgVertex* vtxp) {
DfgVertexSplice* convertLValue(AstNode* nodep) {
FileLine* const flp = nodep->fileline();
if (AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) {
m_foundUnhandled = false;
visit(vrefp);
// cppcheck-has-bug-suppress knownConditionTrueFalse
if (m_foundUnhandled) return false;
getVertex(vrefp)->template as<DfgVarPacked>()->addDriver(flp, 0, vtxp);
return true;
}
if (AstSel* const selp = VN_CAST(nodep, Sel)) {
AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef);
const AstConst* const lsbp = VN_CAST(selp->lsbp(), Const);
if (!vrefp || !lsbp) {
++m_ctx.m_nonRepLhs;
return false;
if (m_foundUnhandled) return nullptr;
DfgVertexVar* const vtxp = getVertex(vrefp)->template as<DfgVertexVar>();
// Ensure driving splice vertex exists
if (!vtxp->srcp()) {
if (VN_IS(vtxp->dtypep(), UnpackArrayDType)) {
vtxp->srcp(new DfgSpliceArray{*m_dfgp, flp, vtxp->dtypep()});
} else {
vtxp->srcp(new DfgSplicePacked{*m_dfgp, flp, vtxp->dtypep()});
}
}
m_foundUnhandled = false;
visit(vrefp);
// cppcheck-has-bug-suppress knownConditionTrueFalse
if (m_foundUnhandled) return false;
getVertex(vrefp)->template as<DfgVarPacked>()->addDriver(flp, lsbp->toUInt(), vtxp);
return true;
}
if (AstArraySel* const selp = VN_CAST(nodep, ArraySel)) {
AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef);
const AstConst* const idxp = VN_CAST(selp->bitp(), Const);
if (!vrefp || !idxp) {
++m_ctx.m_nonRepLhs;
return false;
}
m_foundUnhandled = false;
visit(vrefp);
// cppcheck-has-bug-suppress knownConditionTrueFalse
if (m_foundUnhandled) return false;
getVertex(vrefp)->template as<DfgVarArray>()->addDriver(flp, idxp->toUInt(), vtxp);
return true;
return vtxp->srcp()->as<DfgVertexSplice>();
}
++m_ctx.m_nonRepLhs;
return nullptr;
}
// Build DfgEdge representing the LValue assignment. Returns false if unsuccessful.
bool convertAssignment(FileLine* flp, AstNode* nodep, DfgVertex* vtxp) {
// Concatenation on the LHS. Select parts of the driving 'vtxp' then convert each part
if (AstConcat* const concatp = VN_CAST(nodep, Concat)) {
AstNode* const lhsp = concatp->lhsp();
AstNode* const rhsp = concatp->rhsp();
{
{ // Convet LHS of concat
FileLine* const lFlp = lhsp->fileline();
DfgSel* const lVtxp = new DfgSel{*m_dfgp, lFlp, DfgVertex::dtypeFor(lhsp)};
lVtxp->fromp(vtxp);
@ -233,7 +221,7 @@ class AstToDfgVisitor final : public VNVisitor {
if (!convertAssignment(flp, lhsp, lVtxp)) return false;
}
{
{ // Convert RHS of concat
FileLine* const rFlp = rhsp->fileline();
DfgSel* const rVtxp = new DfgSel{*m_dfgp, rFlp, DfgVertex::dtypeFor(rhsp)};
rVtxp->fromp(vtxp);
@ -241,7 +229,37 @@ class AstToDfgVisitor final : public VNVisitor {
return convertAssignment(flp, rhsp, rVtxp);
}
}
++m_ctx.m_nonRepLhs;
if (AstSel* const selp = VN_CAST(nodep, Sel)) {
AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef);
const AstConst* const lsbp = VN_CAST(selp->lsbp(), Const);
if (!vrefp || !lsbp) {
++m_ctx.m_nonRepLhs;
return false;
}
if (DfgVertexSplice* const splicep = convertLValue(vrefp)) {
splicep->template as<DfgSplicePacked>()->addDriver(flp, lsbp->toUInt(), vtxp);
return true;
}
} else if (AstArraySel* const selp = VN_CAST(nodep, ArraySel)) {
AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef);
const AstConst* const idxp = VN_CAST(selp->bitp(), Const);
if (!vrefp || !idxp) {
++m_ctx.m_nonRepLhs;
return false;
}
if (DfgVertexSplice* const splicep = convertLValue(vrefp)) {
splicep->template as<DfgSpliceArray>()->addDriver(flp, idxp->toUInt(), vtxp);
return true;
}
} else if (VN_IS(nodep, VarRef)) {
if (DfgVertexSplice* const splicep = convertLValue(nodep)) {
splicep->template as<DfgSplicePacked>()->addDriver(flp, 0, vtxp);
return true;
}
} else {
++m_ctx.m_nonRepLhs;
}
return false;
}
@ -315,18 +333,23 @@ class AstToDfgVisitor final : public VNVisitor {
for (DfgVarPacked* const varp : m_varPackedps) {
// Delete variables with no sinks nor sources (this can happen due to reverting
// uncommitted vertices, which does not remove variables)
if (!varp->hasSinks() && varp->arity() == 0) {
if (!varp->hasSinks() && !varp->srcp()) {
VL_DO_DANGLING(varp->unlinkDelete(*m_dfgp), varp);
continue;
}
// Nothing to do for un-driven (input) variables
if (!varp->srcp()) continue;
DfgSplicePacked* const splicep = varp->srcp()->as<DfgSplicePacked>();
// Gather (and unlink) all drivers
std::vector<Driver> drivers;
drivers.reserve(varp->arity());
varp->forEachSourceEdge([this, varp, &drivers](DfgEdge& edge, size_t idx) {
drivers.reserve(splicep->arity());
splicep->forEachSourceEdge([this, splicep, &drivers](DfgEdge& edge, size_t idx) {
DfgVertex* const driverp = edge.sourcep();
UASSERT(driverp, "Should not have created undriven sources");
addDriver(varp->driverFileLine(idx), varp->driverLsb(idx), driverp, drivers);
addDriver(splicep->driverFileLine(idx), splicep->driverLsb(idx), driverp, drivers);
edge.unlinkSource();
});
@ -424,10 +447,10 @@ class AstToDfgVisitor final : public VNVisitor {
}
// Reinsert drivers in order
varp->resetSources();
splicep->resetSources();
for (const Driver& driver : drivers) {
if (!driver.m_vtxp) break; // Stop at end of compacted list
varp->addDriver(driver.m_fileline, driver.m_lsb, driver.m_vtxp);
splicep->addDriver(driver.m_fileline, driver.m_lsb, driver.m_vtxp);
}
// Prune vertices potentially unused due to resolving multiple drivers.
@ -442,6 +465,15 @@ class AstToDfgVisitor final : public VNVisitor {
vtxp->forEachSource([&](DfgVertex& src) { prune.emplace(&src); });
vtxp->unlinkDelete(*m_dfgp);
}
// If the whole variable is driven, remove the splice node
if (splicep->arity() == 1 //
&& splicep->driverLsb(0) == 0 //
&& splicep->source(0)->width() == varp->width()) {
varp->srcp(splicep->source(0));
varp->driverFileLine(splicep->driverFileLine(0));
splicep->unlinkDelete(*m_dfgp);
}
}
}
@ -450,7 +482,7 @@ class AstToDfgVisitor final : public VNVisitor {
for (DfgVarArray* const varp : m_varArrayps) {
// Delete variables with no sinks nor sources (this can happen due to reverting
// uncommitted vertices, which does not remove variables)
if (!varp->hasSinks() && varp->arity() == 0) {
if (!varp->hasSinks() && !varp->srcp()) {
VL_DO_DANGLING(varp->unlinkDelete(*m_dfgp), varp);
}
}

1066
src/V3DfgBreakCycles.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -341,32 +341,20 @@ class ExtractCyclicComponents final {
return *clonep;
}
// Fix up non-variable sources of a DfgVertexVar that are in a different component,
// using the provided 'relink' callback
template <typename T_Vertex>
void fixSources(T_Vertex& vtx, std::function<void(T_Vertex&, DfgVertex&, size_t)> relink) {
static_assert(std::is_base_of<DfgVertexVar, T_Vertex>::value,
"'Vertex' must be a 'DfgVertexVar'");
// Fix edges that cross components
void fixEdges(DfgVertexVar& vtx) {
const size_t component = state(vtx).component;
vtx.forEachSourceEdge([&](DfgEdge& edge, size_t idx) {
DfgVertex& source = *edge.sourcep();
// DfgVertexVar sources are fixed up by `fixSinks` on those sources
if (source.is<DfgVertexVar>()) return;
const size_t sourceComponent = state(source).component;
// Same component is OK
if (sourceComponent == component) return;
// Unlink the source edge (source is reconnected by 'relink'
edge.unlinkSource();
// Apply the fixup
// cppcheck-has-bug-suppress constVariable
DfgVertexVar& clone = getClone(vtx, sourceComponent);
relink(*(clone.as<T_Vertex>()), source, idx);
});
}
// Fix up sinks of given variable vertex that are in a different component
void fixSinks(DfgVertexVar& vtx) {
const size_t component = state(vtx).component;
// All variable vertices have at most a single source, and only variable
// vertices can have multiple sinks, therefore the source must be either:
// - in the same component as the variable vertex
// - be a variable vertex itself, which might be in a different component
// The later case will be fixed up when handling the source variable
DfgVertex* const srcp = vtx.srcp();
UASSERT_OBJ(!srcp || srcp->is<DfgVertexVar>() || state(*srcp).component == component, &vtx,
"Driver of DfgVertexVar must be in the same component");
// Fix up sinks in a differetn component
vtx.forEachSinkEdge([&](DfgEdge& edge) {
const size_t sinkComponent = state(*edge.sinkp()).component;
// Same component is OK
@ -376,49 +364,6 @@ class ExtractCyclicComponents final {
});
}
// Fix edges that cross components
void fixEdges(DfgVertexVar& vtx) {
if (DfgVarPacked* const vvtxp = vtx.cast<DfgVarPacked>()) {
fixSources<DfgVarPacked>(
*vvtxp, [&](DfgVarPacked& clone, DfgVertex& driver, size_t driverIdx) {
clone.addDriver(vvtxp->driverFileLine(driverIdx), //
vvtxp->driverLsb(driverIdx), &driver);
});
fixSinks(*vvtxp);
return;
}
if (DfgVarArray* const vvtxp = vtx.cast<DfgVarArray>()) {
fixSources<DfgVarArray>( //
*vvtxp, [&](DfgVarArray& clone, DfgVertex& driver, size_t driverIdx) {
clone.addDriver(vvtxp->driverFileLine(driverIdx), //
vvtxp->driverIndex(driverIdx), &driver);
});
fixSinks(*vvtxp);
return;
}
}
static void packSources(DfgGraph& dfg) {
// Remove undriven variable sources
for (DfgVertexVar* const vtxp : dfg.varVertices().unlinkable()) {
if (DfgVarPacked* const varp = vtxp->cast<DfgVarPacked>()) {
varp->packSources();
if (!varp->hasSinks() && varp->arity() == 0) {
VL_DO_DANGLING(varp->unlinkDelete(dfg), varp);
}
continue;
}
if (DfgVarArray* const varp = vtxp->cast<DfgVarArray>()) {
varp->packSources();
if (!varp->hasSinks() && varp->arity() == 0) {
VL_DO_DANGLING(varp->unlinkDelete(dfg), varp);
}
continue;
}
}
}
template <typename Vertex>
void moveVertices(DfgVertex::List<Vertex>& list) {
for (DfgVertex* const vtxp : list.unlinkable()) {
@ -446,11 +391,6 @@ class ExtractCyclicComponents final {
UASSERT_OBJ(component == state(snk).component, &vtx,
"Edge crossing components without variable involvement");
});
if (const DfgVertexVar* const vtxp = vtx.cast<DfgVertexVar>()) {
vtxp->forEachSourceEdge([](const DfgEdge& edge, size_t) {
UASSERT_OBJ(edge.sourcep(), edge.sinkp(), "Missing source on variable vertex");
});
}
});
}
@ -489,11 +429,6 @@ class ExtractCyclicComponents final {
if (&vtx == lastp) break;
}
// Pack sources of variables to remove the now undriven inputs
// (cloning might have unlinked some of the inputs),
packSources(m_dfg);
for (const auto& dfgp : m_components) packSources(*dfgp);
// Check results for consistency
if (VL_UNLIKELY(m_doExpensiveChecks)) {
checkEdges(m_dfg);

View File

@ -201,45 +201,52 @@ class DfgToAstVisitor final : DfgVisitor {
++m_ctx.m_resultEquations;
}
void convertVarDriver(const DfgVarPacked* dfgVarp) {
if (dfgVarp->isDrivenFullyByDfg()) {
// Whole variable is driven. Render driver and assign directly to whole variable.
FileLine* const flp = dfgVarp->driverFileLine(0);
AstVarRef* const lhsp = new AstVarRef{flp, getNode(dfgVarp), VAccess::WRITE};
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(dfgVarp->source(0));
addResultEquation(dfgVarp, flp, lhsp, rhsp);
} else {
void convertPackedDriver(const DfgVarPacked* dfgVarp) {
if (DfgSplicePacked* const splicep = dfgVarp->srcp()->cast<DfgSplicePacked>()) {
// Variable is driven partially. Render each driver as a separate assignment.
dfgVarp->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) {
splicep->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) {
UASSERT_OBJ(edge.sourcep(), dfgVarp, "Should have removed undriven sources");
// Render the rhs expression
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(edge.sourcep());
// Create select LValue
FileLine* const flp = dfgVarp->driverFileLine(idx);
FileLine* const flp = splicep->driverFileLine(idx);
AstVarRef* const refp = new AstVarRef{flp, getNode(dfgVarp), VAccess::WRITE};
AstConst* const lsbp = new AstConst{flp, dfgVarp->driverLsb(idx)};
AstConst* const lsbp = new AstConst{flp, splicep->driverLsb(idx)};
const int width = static_cast<int>(edge.sourcep()->width());
AstSel* const lhsp = new AstSel{flp, refp, lsbp, width};
// Add assignment of the value to the selected bits
addResultEquation(dfgVarp, flp, lhsp, rhsp);
});
return;
}
// Whole variable is driven. Render driver and assign directly to whole variable.
FileLine* const flp
= dfgVarp->driverFileLine() ? dfgVarp->driverFileLine() : dfgVarp->fileline();
AstVarRef* const lhsp = new AstVarRef{flp, getNode(dfgVarp), VAccess::WRITE};
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(dfgVarp->srcp());
addResultEquation(dfgVarp, flp, lhsp, rhsp);
}
void convertArrayDiver(const DfgVarArray* dfgVarp) {
// Variable is driven partially. Assign from parts of the canonical var.
dfgVarp->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) {
UASSERT_OBJ(edge.sourcep(), dfgVarp, "Should have removed undriven sources");
// Render the rhs expression
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(edge.sourcep());
// Create select LValue
FileLine* const flp = dfgVarp->driverFileLine(idx);
AstVarRef* const refp = new AstVarRef{flp, getNode(dfgVarp), VAccess::WRITE};
AstConst* const idxp = new AstConst{flp, dfgVarp->driverIndex(idx)};
AstArraySel* const lhsp = new AstArraySel{flp, refp, idxp};
// Add assignment of the value to the selected bits
addResultEquation(dfgVarp, flp, lhsp, rhsp);
});
if (DfgSpliceArray* const splicep = dfgVarp->srcp()->cast<DfgSpliceArray>()) {
// Variable is driven partially. Assign from parts of the canonical var.
splicep->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) {
UASSERT_OBJ(edge.sourcep(), dfgVarp, "Should have removed undriven sources");
// Render the rhs expression
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(edge.sourcep());
// Create select LValue
FileLine* const flp = splicep->driverFileLine(idx);
AstVarRef* const refp = new AstVarRef{flp, getNode(dfgVarp), VAccess::WRITE};
AstConst* const idxp = new AstConst{flp, splicep->driverIndex(idx)};
AstArraySel* const lhsp = new AstArraySel{flp, refp, idxp};
// Add assignment of the value to the selected bits
addResultEquation(dfgVarp, flp, lhsp, rhsp);
});
return;
}
UASSERT_OBJ(false, dfgVarp, "Should not have wholly driven arrays in Dfg");
}
// VISITORS
@ -285,11 +292,11 @@ class DfgToAstVisitor final : DfgVisitor {
// The graph must have been regularized, so we only need to render assignments
for (DfgVertexVar& vtx : dfg.varVertices()) {
// If there is no driver (this vertex is an input to the graph), then nothing to do.
if (!vtx.isDrivenByDfg()) continue;
if (!vtx.srcp()) continue;
// Render packed variable assignments
if (const DfgVarPacked* const dfgVarp = vtx.cast<DfgVarPacked>()) {
convertVarDriver(dfgVarp);
convertPackedDriver(dfgVarp);
continue;
}

View File

@ -240,16 +240,34 @@ static void process(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
// 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
// small cycles.
const std::vector<std::unique_ptr<DfgGraph>>& cyclicComponents
std::vector<std::unique_ptr<DfgGraph>> cyclicComponents
= dfg.extractCyclicComponents("cyclic");
// Split the remaining acyclic DFG into [weakly] connected components
const std::vector<std::unique_ptr<DfgGraph>>& acyclicComponents
= dfg.splitIntoComponents("acyclic");
std::vector<std::unique_ptr<DfgGraph>> acyclicComponents = dfg.splitIntoComponents("acyclic");
// Quick sanity check
UASSERT_OBJ(dfg.size() == 0, dfg.modulep(), "DfgGraph should have become empty");
// Attempt to convert cyclic components into acyclic ones
if (v3Global.opt.fDfgBreakCyckes()) {
for (auto it = cyclicComponents.begin(); it != cyclicComponents.end();) {
auto result = V3DfgPasses::breakCycles(**it, ctx);
if (!result.first) {
// No improvement, moving on.
++it;
} else if (!result.second) {
// Improved, but still cyclic. Replace the original cyclic component.
*it = std::move(result.first);
++it;
} else {
// Result became acyclic. Move to acyclicComponents, delete original.
acyclicComponents.emplace_back(std::move(result.first));
it = cyclicComponents.erase(it);
}
}
}
// For each acyclic component
for (auto& component : acyclicComponents) {
if (dumpDfgLevel() >= 7) component->dumpDotFilePrefixed(ctx.prefix() + "source");

View File

@ -30,6 +30,16 @@ V3DfgBinToOneHotContext::~V3DfgBinToOneHotContext() {
m_decodersCreated);
}
V3DfgBreakCyclesContext::~V3DfgBreakCyclesContext() {
V3Stats::addStat("Optimizations, DFG " + m_label + " BreakCycles, made acyclic", m_nFixed);
V3Stats::addStat("Optimizations, DFG " + m_label + " BreakCycles, improved", m_nImproved);
V3Stats::addStat("Optimizations, DFG " + m_label + " BreakCycles, left unchanged",
m_nUnchanged);
V3Stats::addStat("Optimizations, DFG " + m_label + " BreakCycles, trivial", m_nTrivial);
V3Stats::addStat("Optimizations, DFG " + m_label + " BreakCycles, changes applied",
m_nImprovements);
}
V3DfgCseContext::~V3DfgCseContext() {
V3Stats::addStat("Optimizations, DFG " + m_label + " CSE, expressions eliminated",
m_eliminated);
@ -162,7 +172,7 @@ void V3DfgPasses::inlineVars(DfgGraph& dfg) {
// Don't inline SystemC variables, as SystemC types are not interchangeable with
// internal types, and hence the variables are not interchangeable either.
if (varp->hasSinks() && varp->isDrivenFullyByDfg() && !varp->varp()->isSc()) {
DfgVertex* const driverp = varp->source(0);
DfgVertex* const driverp = varp->srcp();
// We must keep the original driver in certain cases, when swapping them would
// not be functionally or technically (implementation reasons) equivalent:
@ -371,12 +381,14 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
// The index variable
DfgVarPacked* const idxVtxp = [&]() {
// If there is an existing result variable, use that, otherwise create a new variable
DfgVarPacked* varp = srcp->getResultVar();
if (!varp) {
DfgVarPacked* varp = nullptr;
if (DfgVertexVar* const vp = srcp->getResultVar()) {
varp = vp->as<DfgVarPacked>();
} else {
const std::string name = dfg.makeUniqueName("BinToOneHot_Idx", nTables);
varp = dfg.makeNewVar(flp, name, idxDTypep, nullptr)->as<DfgVarPacked>();
varp->varp()->isInternal(true);
varp->addDriver(flp, 0, srcp);
varp->srcp(srcp);
}
varp->setHasModRefs();
return varp;
@ -544,9 +556,9 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
if (!varp->hasModRefs()) {
// If it is only referenced in this DFG, it can be removed
++ctx.m_varsRemoved;
varp->replaceWith(varp->source(0));
varp->replaceWith(varp->srcp());
varp->nodep()->unlinkFrBack()->deleteTree();
} else if (DfgVarPacked* const driverp = varp->source(0)->cast<DfgVarPacked>()) {
} else if (DfgVarPacked* const driverp = varp->srcp()->cast<DfgVarPacked>()) {
// If it's driven from another variable, it can be replaced by that. However, we do not
// want to propagate SystemC variables into the design.
if (driverp->varp()->isSc()) continue;

View File

@ -40,6 +40,20 @@ public:
~V3DfgBinToOneHotContext() VL_MT_DISABLED;
};
class V3DfgBreakCyclesContext final {
const std::string m_label; // Label to apply to stats
public:
VDouble0 m_nFixed; // Number of graphs that became acyclic
VDouble0 m_nImproved; // Number of graphs that were imporoved but still cyclic
VDouble0 m_nUnchanged; // Number of graphs that were left unchanged
VDouble0 m_nTrivial; // Number of graphs that were not changed
VDouble0 m_nImprovements; // Number of changes made to graphs
explicit V3DfgBreakCyclesContext(const std::string& label)
: m_label{label} {}
~V3DfgBreakCyclesContext() VL_MT_DISABLED;
};
class V3DfgCseContext final {
const std::string m_label; // Label to apply to stats
@ -93,6 +107,7 @@ public:
VDouble0 m_resultEquations; // Number of result combinational equations
V3DfgBinToOneHotContext m_binToOneHotContext{m_label};
V3DfgBreakCyclesContext m_breakCyclesContext{m_label};
V3DfgCseContext m_cseContext0{m_label + " 1st"};
V3DfgCseContext m_cseContext1{m_label + " 2nd"};
V3DfgPeepholeContext m_peepholeContext{m_label};
@ -120,6 +135,16 @@ DfgGraph* astToDfg(AstModule&, V3DfgOptimizationContext&) VL_MT_DISABLED;
// Same as above, but for the entire netlist, after V3Scope
DfgGraph* astToDfg(AstNetlist&, V3DfgOptimizationContext&) 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
// on the input graph, with at least some cycles eliminated. The returned
// graph is always independent of the original. If an imporoved graph is
// returned, then the returned 'bool' flag indicated if the returned graph is
// acyclic (flag 'true'), or still cyclic (flag 'false').
std::pair<std::unique_ptr<DfgGraph>, bool> breakCycles(const DfgGraph&,
V3DfgOptimizationContext&) VL_MT_DISABLED;
// Optimize the given DfgGraph
void optimize(DfgGraph&, V3DfgOptimizationContext&) VL_MT_DISABLED;

View File

@ -1202,11 +1202,15 @@ 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>()) {
const size_t idx = idxp->toSizeT();
if (DfgVertex* const driverp = varp->driverAt(idx)) {
APPLYING(INLINE_ARRAYSEL) {
replace(vtxp, driverp);
return;
if (varp->srcp()) {
if (DfgSpliceArray* const splicep = varp->srcp()->cast<DfgSpliceArray>()) {
const size_t idx = idxp->toSizeT();
if (DfgVertex* const driverp = splicep->driverAt(idx)) {
APPLYING(INLINE_ARRAYSEL) {
replace(vtxp, driverp);
return;
}
}
}
}
}

View File

@ -45,12 +45,23 @@ 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>();
});
return hasNonVarSink;
}
// Anything that drives an SC variable needs an intermediate,
// as we can only assign simple variables to SC variables at runtime.
const bool hasScSink = vtx.findSink<DfgVertexVar>([](const DfgVertexVar& var) { //
return var.varp()->isSc();
});
if (hasScSink) return true;
// // Splice vertices always need a variable as they represent partial updates
// if (vtx.is<DfgVertexSplice>()) return true;
// Operations without multiple sinks need no variables
if (!vtx.hasMultipleSinks()) return false;
// Array selects need no variables, they are just memory references
@ -63,26 +74,21 @@ class DfgRegularize final {
// This is an op that requires a result variable. Ensure it is
// assigned to one, and redirect other sinks read that variable.
if (DfgVarPacked* const varp = vtx.getResultVar()) {
// A variable already exists
UASSERT_OBJ(varp->arity() == 1, varp, "Result variable with multiple drivers");
FileLine* const flp = varp->driverFileLine(0);
varp->sourceEdge(0)->unlinkSource();
varp->resetSources();
if (DfgVertexVar* const varp = vtx.getResultVar()) {
varp->sourceEdge<0>()->unlinkSource();
vtx.replaceWith(varp);
varp->addDriver(flp, 0, &vtx);
varp->srcp(&vtx);
} else {
// Need to create an intermediate variable
const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps);
FileLine* const flp = vtx.fileline();
AstScope* const scopep = scoped ? vtx.scopep(scopeCache) : nullptr;
DfgVarPacked* const newp
= m_dfg.makeNewVar(flp, name, vtx.dtypep(), scopep)->as<DfgVarPacked>();
DfgVertexVar* const newp = m_dfg.makeNewVar(flp, name, vtx.dtypep(), scopep);
++m_nTmps;
++m_ctx.m_temporariesIntroduced;
// Replace vertex with the variable and add back driver
vtx.replaceWith(newp);
newp->addDriver(vtx.fileline(), 0, &vtx);
newp->srcp(&vtx);
}
}
}

View File

@ -38,22 +38,24 @@
// === Abstract base node types (DfgVertex*) ===================================
class DfgVertexVar VL_NOT_FINAL : public DfgVertexVariadic {
class DfgVertexVar VL_NOT_FINAL : public DfgVertexUnary {
AstVar* const m_varp; // The AstVar associated with this vertex (not owned by this vertex)
AstVarScope* const m_varScopep; // The AstVarScope associated with this vertex (not owned)
bool m_hasDfgRefs = false; // This AstVar is referenced in a different DFG of the module
bool m_hasModRefs = false; // This AstVar is referenced outside the DFG, but in the module
bool m_hasExtRefs = false; // This AstVar is referenced from outside the module
// Location of driver of this variable. Only used for converting back to Ast. Might be nullptr.
FileLine* m_driverFileLine = nullptr;
bool selfEquals(const DfgVertex& that) const final VL_MT_DISABLED;
V3Hash selfHash() const final VL_MT_DISABLED;
public:
inline DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp, uint32_t initialCapacity);
inline DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp, uint32_t initialCapacity);
inline DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp);
inline DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp);
ASTGEN_MEMBERS_DfgVertexVar;
bool isDrivenByDfg() const { return arity() > 0; }
const std::string srcName(size_t) const override { return ""; }
AstVar* varp() const { return m_varp; }
AstVarScope* varScopep() const { return m_varScopep; }
@ -68,6 +70,13 @@ public:
void setHasExtRefs() { m_hasExtRefs = true; }
bool hasNonLocalRefs() const { return hasDfgRefs() || hasModRefs() || hasExtRefs(); }
FileLine* driverFileLine() const { return m_driverFileLine; }
void driverFileLine(FileLine* flp) { m_driverFileLine = flp; }
bool isDrivenFullyByDfg() const {
return srcp() && !srcp()->is<DfgVertexSplice>() && !varp()->isForced();
}
// Variable cannot be removed, even if redundant in the DfgGraph (might be used externally)
bool keep() const {
// Keep if referenced outside this module
@ -82,6 +91,12 @@ public:
return false;
}
};
class DfgVertexSplice VL_NOT_FINAL : public DfgVertexVariadic {
public:
DfgVertexSplice(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep)
: DfgVertexVariadic{dfg, type, flp, dtypep, 1u} {}
ASTGEN_MEMBERS_DfgVertexSplice;
};
// === Concrete node types =====================================================
@ -184,14 +199,49 @@ class DfgVarArray final : public DfgVertexVar {
public:
DfgVarArray(DfgGraph& dfg, AstVar* varp)
: DfgVertexVar{dfg, dfgType(), varp, 4u} {
: DfgVertexVar{dfg, dfgType(), varp} {
UASSERT_OBJ(VN_IS(dtypep(), UnpackArrayDType), varp, "Non array DfgVarArray");
}
DfgVarArray(DfgGraph& dfg, AstVarScope* vscp)
: DfgVertexVar{dfg, dfgType(), vscp, 4u} {
: DfgVertexVar{dfg, dfgType(), vscp} {
UASSERT_OBJ(VN_IS(dtypep(), UnpackArrayDType), vscp, "Non array DfgVarArray");
}
ASTGEN_MEMBERS_DfgVarArray;
};
class DfgVarPacked final : public DfgVertexVar {
friend class DfgVertex;
friend class DfgVisitor;
public:
DfgVarPacked(DfgGraph& dfg, AstVar* varp)
: DfgVertexVar{dfg, dfgType(), varp} {
UASSERT_OBJ(!VN_IS(dtypep(), UnpackArrayDType), varp, "Array DfgVarPacked");
}
DfgVarPacked(DfgGraph& dfg, AstVarScope* vscp)
: DfgVertexVar{dfg, dfgType(), vscp} {
UASSERT_OBJ(!VN_IS(dtypep(), UnpackArrayDType), vscp, "Array DfgVarPacked");
}
ASTGEN_MEMBERS_DfgVarPacked;
};
// === DfgVertexSplice ===
class DfgSpliceArray final : public DfgVertexSplice {
friend class DfgVertex;
friend class DfgVisitor;
using DriverData = std::pair<FileLine*, uint32_t>;
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);
@ -203,27 +253,6 @@ public:
DfgVertexVariadic::resetSources();
}
// Remove undriven sources
void packSources() {
// Grab and reset the driver data
std::vector<DriverData> driverData;
driverData.swap(m_driverData);
// Grab and unlink the sources
std::vector<DfgVertex*> sources{arity()};
forEachSourceEdge([&](DfgEdge& edge, size_t idx) {
sources[idx] = edge.sourcep();
edge.unlinkSource();
});
DfgVertexVariadic::resetSources();
// Add back the driven sources
for (size_t i = 0; i < sources.size(); ++i) {
if (!sources[i]) continue;
addDriver(driverData[i].first, driverData[i].second, sources[i]);
}
}
FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; }
uint32_t driverIndex(size_t idx) const { return m_driverData[idx].second; }
@ -234,26 +263,27 @@ public:
return edgep ? edgep->sourcep() : nullptr;
}
const string srcName(size_t idx) const override { return cvtToStr(driverIndex(idx)); }
const std::string srcName(size_t idx) const override {
return std::to_string(driverIndex(idx));
}
};
class DfgVarPacked final : public DfgVertexVar {
class DfgSplicePacked final : public DfgVertexSplice {
friend class DfgVertex;
friend class DfgVisitor;
using DriverData = std::pair<FileLine*, uint32_t>;
std::vector<DriverData> m_driverData; // Additional data associate with each driver
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:
DfgVarPacked(DfgGraph& dfg, AstVar* varp)
: DfgVertexVar{dfg, dfgType(), varp, 1u} {}
DfgVarPacked(DfgGraph& dfg, AstVarScope* vscp)
: DfgVertexVar{dfg, dfgType(), vscp, 1u} {}
ASTGEN_MEMBERS_DfgVarPacked;
bool isDrivenFullyByDfg() const {
return arity() == 1 && source(0)->dtypep() == dtypep() && !varp()->isForced();
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);
@ -265,33 +295,10 @@ public:
DfgVertexVariadic::resetSources();
}
// Remove undriven sources
void packSources() {
// Grab and reset the driver data
std::vector<DriverData> driverData;
driverData.swap(m_driverData);
// Grab and unlink the sources
std::vector<DfgVertex*> sources{arity()};
forEachSourceEdge([&](DfgEdge& edge, size_t idx) {
sources[idx] = edge.sourcep();
edge.unlinkSource();
});
DfgVertexVariadic::resetSources();
// Add back the driven sources
for (size_t i = 0; i < sources.size(); ++i) {
if (!sources[i]) continue;
addDriver(driverData[i].first, driverData[i].second, sources[i]);
}
}
FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; }
uint32_t driverLsb(size_t idx) const { return m_driverData[idx].second; }
const string srcName(size_t idx) const override {
return isDrivenFullyByDfg() ? "" : cvtToStr(driverLsb(idx));
}
const std::string srcName(size_t idx) const override { return std::to_string(driverLsb(idx)); }
};
#endif

View File

@ -181,7 +181,7 @@ void EmitCBaseVisitorConst::emitCFuncDecl(const AstCFunc* funcp, const AstNodeMo
void EmitCBaseVisitorConst::emitVarDecl(const AstVar* nodep, bool asRef) {
const AstBasicDType* const basicp = nodep->basicp();
bool refNeedParens = VN_IS(nodep->dtypeSkipRefp(), UnpackArrayDType);
const bool refNeedParens = VN_IS(nodep->dtypeSkipRefp(), UnpackArrayDType);
const auto emitDeclArrayBrackets = [this](const AstVar* nodep) -> void {
// This isn't very robust and may need cleanup for other data types

View File

@ -261,33 +261,57 @@ public:
if (const AstCNew* const cnewp = getSuperNewCallRecursep(nodep->nextp())) return cnewp;
return nullptr;
}
void putConstructorSubinit(const AstClass* classp, AstCFunc* cfuncp, bool top,
std::set<AstClass*>& doneClassesr) {
void putConstructorSubinit(const AstClass* classp, AstCFunc* cfuncp) {
// Virtual bases in depth-first left-to-right order
std::vector<AstClass*> virtualBases;
std::unordered_set<AstClass*> doneClasses;
collectVirtualBasesRecursep(classp, virtualBases);
for (AstClass* vbase : virtualBases) {
if (doneClasses.count(vbase)) continue;
puts(doneClasses.empty() ? "" : "\n , ");
doneClasses.emplace(vbase);
puts(prefixNameProtect(vbase));
if (constructorNeedsProcess(vbase)) {
puts("(vlProcess, vlSymsp)");
} else {
puts("(vlSymsp)");
}
}
const AstCNew* const superNewCallp = getSuperNewCallRecursep(cfuncp->stmtsp());
// Direct non-virtual bases in declaration order
for (const AstClassExtends* extp = classp->extendsp(); extp;
extp = VN_AS(extp->nextp(), ClassExtends)) {
if (extp->classp()->useVirtualPublic()) {
// It's a c++ virtual class (diamond relation)
// Must get the subclasses initialized first
putConstructorSubinit(extp->classp(), cfuncp, false, doneClassesr);
}
// Diamond pattern with same base class twice?
if (doneClassesr.find(extp->classp()) != doneClassesr.end()) continue;
puts(doneClassesr.empty() ? "" : "\n , ");
doneClassesr.emplace(extp->classp());
if (extp->classp()->useVirtualPublic()) continue;
if (doneClasses.count(extp->classp())) continue;
puts(doneClasses.empty() ? "" : "\n , ");
doneClasses.emplace(extp->classp());
puts(prefixNameProtect(extp->classp()));
if (constructorNeedsProcess(extp->classp())) {
puts("(vlProcess, vlSymsp");
} else {
puts("(vlSymsp");
}
if (top) {
const AstCNew* const superNewCallp = getSuperNewCallRecursep(cfuncp->stmtsp());
UASSERT_OBJ(superNewCallp, cfuncp, "super.new call not found");
// Handle super.new() args for the concrete parent
if (!extp->classp()->isInterfaceClass() && superNewCallp) {
putCommaIterateNext(superNewCallp->argsp(), true);
}
puts(")");
top = false;
}
}
void collectVirtualBasesRecursep(const AstClass* classp,
std::vector<AstClass*>& virtualBases) {
std::set<const AstClass*> visited;
collectVirtualBasesRecursep(classp, virtualBases /*ref*/, visited /*ref*/);
}
void collectVirtualBasesRecursep(const AstClass* classp, std::vector<AstClass*>& virtualBases,
std::set<const AstClass*>& visited) {
if (visited.count(classp)) return;
visited.emplace(classp);
for (const AstClassExtends* extp = classp->extendsp(); extp;
extp = VN_AS(extp->nextp(), ClassExtends)) {
// Depth-first: recurse into this base first
collectVirtualBasesRecursep(extp->classp(), virtualBases, visited);
if (extp->classp()->useVirtualPublic()) { virtualBases.push_back(extp->classp()); }
}
}
@ -314,8 +338,7 @@ public:
const AstClass* const classp = VN_CAST(nodep->scopep()->modp(), Class);
if (nodep->isConstructor() && classp && classp->extendsp()) {
puts("\n : ");
std::set<AstClass*> doneClasses;
putConstructorSubinit(classp, nodep, true, doneClasses /*ref*/);
putConstructorSubinit(classp, nodep);
}
}
puts(" {\n");

View File

@ -109,7 +109,8 @@ class EmitCHeader final : public EmitCConstInit {
// Emit variables in consecutive anon and non-anon batches
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* const varp = VN_CAST(nodep, Var)) {
if (varp->isIO() || varp->isSignal() || varp->isClassMember() || varp->isTemp()) {
if (varp->isIO() || varp->isSignal() || varp->isClassMember() || varp->isTemp()
|| varp->isGenVar()) {
const bool anon = isAnonOk(varp);
if (anon != lastAnon) emitCurrentList();
lastAnon = anon;
@ -563,16 +564,20 @@ class EmitCHeader final : public EmitCConstInit {
if (!VN_IS(modp, Class)) puts("alignas(VL_CACHE_LINE_BYTES) ");
puts(prefixNameProtect(modp));
if (const AstClass* const classp = VN_CAST(modp, Class)) {
const string virtpub = classp->useVirtualPublic() ? "virtual public " : "public ";
puts(" : " + virtpub);
puts(" : ");
if (classp->extendsp()) {
bool needComma = false;
for (const AstClassExtends* extp = classp->extendsp(); extp;
extp = VN_AS(extp->nextp(), ClassExtends)) {
if (needComma) puts(", ");
// Use virtual only for interfaces for class inheritance
// (extends)
puts(extp->classp()->useVirtualPublic() ? "virtual public " : "public ");
putns(extp, prefixNameProtect(extp->classp()));
if (extp->nextp()) puts(", " + virtpub);
needComma = true;
}
} else {
puts("VlClass");
puts("public virtual VlClass");
}
} else {
puts(" final : public VerilatedModule");

View File

@ -743,13 +743,13 @@ void EmitCSyms::emitSymImp() {
if (!optSystemC()) {
puts("\nvoid " + symClassName() + "::_traceDump() {\n");
// Caller checked for __Vm_dumperp non-nullptr
puts("const VerilatedLockGuard lock(__Vm_dumperMutex);\n");
puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n");
puts("__Vm_dumperp->dump(VL_TIME_Q());\n");
puts("}\n");
}
puts("\nvoid " + symClassName() + "::_traceDumpOpen() {\n");
puts("const VerilatedLockGuard lock(__Vm_dumperMutex);\n");
puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n");
puts("if (VL_UNLIKELY(!__Vm_dumperp)) {\n");
puts("__Vm_dumperp = new " + v3Global.opt.traceClassLang() + "();\n");
puts("__Vm_modelp->trace(__Vm_dumperp, 0, 0);\n");
@ -760,7 +760,7 @@ void EmitCSyms::emitSymImp() {
puts("}\n");
puts("\nvoid " + symClassName() + "::_traceDumpClose() {\n");
puts("const VerilatedLockGuard lock(__Vm_dumperMutex);\n");
puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n");
puts("__Vm_dumping = false;\n");
puts("VL_DO_CLEAR(delete __Vm_dumperp, __Vm_dumperp = nullptr);\n");
puts("}\n");

View File

@ -922,6 +922,20 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public EmitCBaseVisitorConst {
m_sensesp = nodep->sensesp();
iterateAndNextConstNull(nodep->stmtsp());
}
void visit(AstDelay* nodep) override {
puts(""); // this is for proper alignment
puts("#");
iterateConst(nodep->lhsp());
puts(";\n");
iterateAndNextConstNull(nodep->stmtsp());
}
void visit(AstCAwait* nodep) override {
AstCMethodHard* methodp = VN_CAST(nodep->exprp(), CMethodHard);
UASSERT_OBJ(methodp, nodep, "AstCAwait expression must be an AstCMethodHard");
puts(""); // this is for proper alignment
puts("#");
iterateConst(methodp->pinsp());
}
void visit(AstParseRef* nodep) override { puts(nodep->prettyName()); }
void visit(AstNodeText*) override {}
void visit(AstVarScope*) override {}

View File

@ -99,6 +99,7 @@ public:
DEPRECATED, // Feature will be deprecated
ENCAPSULATED, // Error: local/protected violation
ENDLABEL, // End lable name mismatch
ENUMITEMWIDTH, // Error: enum item width mismatch
ENUMVALUE, // Error: enum type needs explicit cast
EOFNEWLINE, // End-of-file missing newline
GENCLK, // Generated Clock. Historical, never issued.
@ -124,6 +125,7 @@ public:
MULTIDRIVEN, // Driven from multiple blocks
MULTITOP, // Multiple top level modules
NEWERSTD, // Newer language standard required
NOEFFECT, // Statement has no effect
NOLATCH, // No latch detected in always_latch block
NONSTD, // Non-standard feature present in other sims
NULLPORT, // Null port detected in module definition
@ -189,40 +191,33 @@ public:
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
constexpr operator en() const VL_MT_SAFE { return m_e; }
const char* ascii() const VL_MT_SAFE {
// clang-format off
static const char* const names[] = {
// Leading spaces indicate it can't be disabled.
" MIN", " INFO", " FATAL", " FATALMANY", " FATALSRC", " ERROR", " FIRST_NAMED",
// Boolean
" I_CELLDEFINE", " I_COVERAGE", " I_DEF_NETTYPE_WIRE", " I_LINT", " I_TIMING", " I_TRACING", " I_UNUSED",
" I_CELLDEFINE", " I_COVERAGE", " I_DEF_NETTYPE_WIRE", " I_LINT", " I_TIMING",
" I_TRACING", " I_UNUSED",
// Errors
"LIFETIME", "NEEDTIMINGOPT", "NOTIMING", "PORTSHORT", "TASKNSVAR", "UNSUPPORTED",
// Warnings
" EC_FIRST_WARN",
"ALWCOMBORDER", "ASCRANGE", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA", "BADVLTPRAGMA",
"BLKANDNBLK", "BLKLOOPINIT", "BLKSEQ", "BSSPACE",
"CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA",
"CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG", "COVERIGN",
"DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED",
"ENCAPSULATED", "ENDLABEL", "ENUMVALUE", "EOFNEWLINE", "GENCLK",
"GENUNNAMED", "HIERBLOCK",
"IFDEPTH", "IGNOREDRETURN",
"IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE",
"INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE",
"LATCH", "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING",
"MULTIDRIVEN", "MULTITOP", "NEWERSTD", "NOLATCH", "NONSTD", "NULLPORT", "PINCONNECTEMPTY",
"PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL", "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE",
"PROFOUTOFDATE", "PROTECTED", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY",
"SELRANGE", "SHORTREAL", "SIDEEFFECT", "SPLITVAR",
"STATICVAR", "STMTDLY", "SYMRSVDWORD", "SYNCASYNCNET",
"TICKCOUNT", "TIMESCALEMOD",
"UNDRIVEN", "UNOPT", "UNOPTFLAT", "UNOPTTHREADS",
"UNPACKED", "UNSIGNED", "UNUSEDGENVAR", "UNUSEDLOOP" ,"UNUSEDPARAM", "UNUSEDSIGNAL",
"USERERROR", "USERFATAL", "USERINFO", "USERWARN",
"VARHIDDEN", "WAITCONST", "WIDTH", "WIDTHCONCAT", "WIDTHEXPAND", "WIDTHTRUNC", "WIDTHXZEXPAND", "ZERODLY", "ZEROREPL",
" MAX"
};
// clang-format on
" EC_FIRST_WARN", "ALWCOMBORDER", "ASCRANGE", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA",
"BADVLTPRAGMA", "BLKANDNBLK", "BLKLOOPINIT", "BLKSEQ", "BSSPACE", "CASEINCOMPLETE",
"CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA", "CMPCONST",
"COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG", "COVERIGN", "DECLFILENAME",
"DEFOVERRIDE", "DEFPARAM", "DEPRECATED", "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH",
"ENUMVALUE", "EOFNEWLINE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "IFDEPTH",
"IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE",
"INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "LATCH", "LITENDIAN",
"MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN", "MULTITOP",
"NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NULLPORT", "PINCONNECTEMPTY",
"PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL", "PREPROCZERO", "PROCASSINIT",
"PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED", "RANDC", "REALCVT", "REDEFMACRO",
"RISEFALLDLY", "SELRANGE", "SHORTREAL", "SIDEEFFECT", "SPLITVAR", "STATICVAR",
"STMTDLY", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN",
"UNOPT", "UNOPTFLAT", "UNOPTTHREADS", "UNPACKED", "UNSIGNED", "UNUSEDGENVAR",
"UNUSEDLOOP", "UNUSEDPARAM", "UNUSEDSIGNAL", "USERERROR", "USERFATAL", "USERINFO",
"USERWARN", "VARHIDDEN", "WAITCONST", "WIDTH", "WIDTHCONCAT", "WIDTHEXPAND",
"WIDTHTRUNC", "WIDTHXZEXPAND", "ZERODLY", "ZEROREPL", " MAX"};
return names[m_e];
}
// Warnings that default to off
@ -249,9 +244,9 @@ public:
bool pretendError() const VL_MT_SAFE {
return (m_e == ASSIGNIN || m_e == BADSTDPRAGMA || m_e == BADVLTPRAGMA || m_e == BLKANDNBLK
|| m_e == BLKLOOPINIT || m_e == CONTASSREG || m_e == ENCAPSULATED
|| m_e == ENDLABEL || m_e == ENUMVALUE || m_e == IMPURE || m_e == MODMISSING
|| m_e == PINNOTFOUND || m_e == PKGNODECL || m_e == PROCASSWIRE
|| m_e == ZEROREPL // Says IEEE
|| m_e == ENDLABEL || m_e == ENUMITEMWIDTH || m_e == ENUMVALUE || m_e == IMPURE
|| m_e == MODMISSING || m_e == PINNOTFOUND || m_e == PKGNODECL
|| m_e == PROCASSWIRE || m_e == ZEROREPL // Says IEEE
);
}
// Warnings to mention manual

View File

@ -369,6 +369,10 @@ public:
&& m_lastLineno == rhs.m_lastLineno && m_lastColumn == rhs.m_lastColumn
&& m_filenameno == rhs.m_filenameno && m_msgEnIdx == rhs.m_msgEnIdx);
}
bool equalFirstLineCol(const FileLine& rhs) const {
return (m_filenameno == rhs.m_filenameno && m_firstLineno == rhs.m_firstLineno
&& m_firstColumn == rhs.m_firstColumn);
}
// Returns -1 if (*this) should come before rhs after sorted. 1 for the opposite case. 0 for
// equivalent.
int operatorCompare(const FileLine& rhs) const {

View File

@ -150,9 +150,11 @@ private:
// AstVarRef::user2 -> Flag indicating not to replace reference
// AstVarScope::user3 -> AstVarScope*, a `valVscp` force component for each VarScope of
// forced RHS
// AstVarScope::user4p -> AstNodeExpr*, the RHS expression
const VNUser1InUse m_user1InUse;
const VNUser2InUse m_user2InUse;
const VNUser3InUse m_user3InUse;
const VNUser4InUse m_user4InUse;
AstUser1Allocator<AstVar, ForceComponentsVar> m_forceComponentsVar;
AstUser1Allocator<AstVarScope, ForceComponentsVarScope> m_forceComponentsVarScope;
@ -185,6 +187,12 @@ public:
ForceComponentsVarScope* tryGetForceComponents(AstVarRef* nodep) const {
return m_forceComponentsVarScope.tryGet(nodep->varScopep());
}
void setValVscpRhsExpr(AstVarScope* valVscp, AstNodeExpr* rhsExpr) {
valVscp->user4p(rhsExpr);
}
AstNodeExpr* getValVscpRhsExpr(AstVarScope* valVscp) const {
return VN_CAST(valVscp->user4p(), NodeExpr);
}
};
class ForceConvertVisitor final : public VNVisitor {
@ -233,9 +241,8 @@ class ForceConvertVisitor final : public VNVisitor {
= new AstAssign{flp, lhsp->cloneTreePure(false), rhsp->cloneTreePure(false)};
transformWritenVarScopes(setValp->lhsp(), [this, rhsp](AstVarScope* vscp) {
AstVarScope* const valVscp = m_state.getForceComponents(vscp).m_valVscp;
// TODO support multiple VarRefs on RHS
if (AstVarRef* const refp = VN_CAST(rhsp, VarRef))
ForceState::setValVscp(refp, valVscp);
m_state.setValVscpRhsExpr(valVscp, rhsp->cloneTreePure(false));
rhsp->foreach([valVscp](AstVarRef* refp) { ForceState::setValVscp(refp, valVscp); });
return valVscp;
});
@ -267,11 +274,14 @@ class ForceConvertVisitor final : public VNVisitor {
transformWritenVarScopes(resetEnp->lhsp(), [this](AstVarScope* vscp) {
return m_state.getForceComponents(vscp).m_enVscp;
});
// IEEE 1800-2023 10.6.2: If this is a net, and not a variable, then reset the read
// signal directly as well, in case something in the same process reads it later. Also, if
// it is a variable, and not a net, set the original signal to the forced value, as it
// needs to retain the forced value until the next procedural update, which might happen on
// a later eval. Luckily we can do all this in a single assignment.
// IEEE 1800-2023 10.6.2: When released, then if the variable is not driven by a continuous
// assignment and does not currently have an active procedural continuous assignment, the
// variable shall not immediately change value and shall maintain its current value until
// the next procedural assignment to the variable is executed. Releasing a variable that is
// driven by a continuous assignment or currently has an active assign procedural
// continuous assignment shall reestablish that assignment and schedule a reevaluation in
// the continuous assignment's scheduling region.
AstAssign* const resetRdp
= new AstAssign{flp, lhsp->cloneTreePure(false), lhsp->unlinkFrBack()};
// Replace write refs on the LHS
@ -376,10 +386,12 @@ class ForceReplaceVisitor final : public VNVisitor {
if (AstVarScope* const valVscp = ForceState::getValVscp(nodep)) {
FileLine* const flp = nodep->fileline();
AstVarRef* const valp = new AstVarRef{flp, valVscp, VAccess::WRITE};
AstVarRef* const rhsp = new AstVarRef{flp, nodep->varScopep(), VAccess::READ};
AstNodeExpr* rhsp = m_state.getValVscpRhsExpr(valVscp);
UASSERT_OBJ(rhsp, flp, "RHS of force/release must be an AstNodeExpr");
rhsp = rhsp->cloneTreePure(false);
ForceState::markNonReplaceable(valp);
ForceState::markNonReplaceable(rhsp);
rhsp->foreach([](AstVarRef* refp) { ForceState::markNonReplaceable(refp); });
m_stmtp->addNextHere(new AstAssign{flp, valp, rhsp});
}

View File

@ -2365,6 +2365,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
bool m_unresolvedClass; // Unresolved class reference, needs help from V3Param
bool m_genBlk; // Contains gen block reference
AstNode* m_unlinkedScopep; // Unresolved scope, needs corresponding VarXRef
AstDisable* m_disablep; // Disable statement under which the reference is
bool m_dotErr; // Error found in dotted resolution, ignore upwards
string m_dotText; // String of dotted names found in below parseref
DotStates() { init(nullptr); }
@ -2380,6 +2381,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
m_unresolvedClass = false;
m_genBlk = false;
m_unlinkedScopep = nullptr;
m_disablep = nullptr;
}
string ascii() const {
static const char* const names[]
@ -2646,6 +2648,46 @@ class LinkDotResolveVisitor final : public VNVisitor {
m_ds.init(m_curSymp);
iterateNull(nodep);
}
static const AstNodeDType* getElemDTypep(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();
while (true) {
if (const AstBracketArrayDType* const adtypep = VN_CAST(dtypep, BracketArrayDType)) {
dtypep = adtypep->childDTypep()->skipRefp();
} else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) {
dtypep = adtypep->childDTypep()->skipRefp();
} else if (const AstQueueDType* const adtypep = VN_CAST(dtypep, QueueDType)) {
dtypep = adtypep->childDTypep()->skipRefp();
} else {
break;
}
}
return dtypep;
}
static const AstNodeDType* getExprDTypep(const AstNodeExpr* selp) {
while (const AstNodePreSel* const sp = VN_CAST(selp, NodePreSel)) selp = sp->fromp();
if (const AstMemberSel* const sp = VN_CAST(selp, MemberSel)) {
if (const AstNodeDType* dtypep = getExprDTypep(sp->fromp())) {
if (const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType)) {
const AstClass* const classp = classRefp->classp();
const bool found = classp->existsMember(
[&dtypep, name = selp->name()](const AstClass*, const AstVar* nodep) {
dtypep = nodep->childDTypep();
return nodep->name() == name;
});
if (found) return getElemDTypep(dtypep);
selp->v3error("Class " << classRefp->prettyNameQ()
<< " does not contain field " << selp->prettyNameQ());
} else {
selp->v3fatalSrc("Member selection on expression of type "
<< dtypep->prettyDTypeNameQ()
<< ", which is not a class type");
}
}
} else if (const AstNodeVarRef* const varRefp = VN_CAST(selp, NodeVarRef)) {
return getElemDTypep(varRefp->varp()->childDTypep());
}
return nullptr;
}
#define LINKDOT_VISIT_START() \
VL_RESTORER(m_indent); \
@ -2762,7 +2804,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
if (nodep->name() == "__paramNumber1" && m_cellp
&& VN_IS(m_cellp->modp(), Primitive)) {
// Primitive parameter is really a delay we can just ignore
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
} else {
const string suggest
@ -3084,7 +3126,11 @@ class LinkDotResolveVisitor final : public VNVisitor {
bool allowVar = false;
bool allowFTask = false;
bool staticAccess = false;
if (m_ds.m_dotPos == DP_PACKAGE) {
if (m_ds.m_disablep) {
allowScope = true;
allowFTask = true;
expectWhat = "block/task";
} else if (m_ds.m_dotPos == DP_PACKAGE) {
// {package-or-class}::{a}
AstNodeModule* classOrPackagep = nullptr;
expectWhat = "scope/variable/func";
@ -3185,7 +3231,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
}
if (!foundp) {
} else if (VN_IS(foundp->nodep(), Cell) || VN_IS(foundp->nodep(), Begin)
} else if (VN_IS(foundp->nodep(), Cell) || VN_IS(foundp->nodep(), NodeBlock)
|| VN_IS(foundp->nodep(), Netlist) // for $root
|| VN_IS(foundp->nodep(), Module)) { // if top
if (allowScope) {
@ -3193,8 +3239,20 @@ class LinkDotResolveVisitor final : public VNVisitor {
m_ds.m_dotText = VString::dot(m_ds.m_dotText, ".", nodep->name());
m_ds.m_dotSymp = foundp;
m_ds.m_dotPos = DP_SCOPE;
if (m_ds.m_disablep && VN_IS(foundp->nodep(), NodeBlock)) {
// Possibly it is not the final link. If we are under dot and not in its
// last component, `targetp()` field will be overwritten by next components
m_ds.m_disablep->targetp(foundp->nodep());
}
if (const AstBegin* const beginp = VN_CAST(foundp->nodep(), Begin)) {
if (beginp->generate()) m_ds.m_genBlk = true;
if (beginp->generate()) {
m_ds.m_genBlk = true;
if (m_ds.m_disablep) {
m_ds.m_disablep->v3warn(
E_UNSUPPORTED,
"Unsupported: Generate block referenced by disable");
}
}
}
// Upper AstDot visitor will handle it from here
} else if (VN_IS(foundp->nodep(), Cell) && allowVar) {
@ -3231,7 +3289,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
taskrefp = new AstFuncRef{nodep->fileline(), nodep->name(), nullptr};
}
nodep->replaceWith(taskrefp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
m_ds = lastStates;
return;
} else if (AstVar* const varp = foundToVarp(foundp, nodep, VAccess::READ)) {
@ -3438,7 +3496,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
m_ds.m_dotPos = DP_MEMBER;
} else {
// Cells/interfaces can't be implicit
const bool checkImplicit = (!m_ds.m_dotp && m_ds.m_dotText == "" && !foundp);
const bool checkImplicit
= (!m_ds.m_dotp && m_ds.m_dotText == "" && !m_ds.m_disablep && !foundp);
const bool err
= !(checkImplicit && m_statep->implicitOk(m_modp, nodep->name()));
if (err) {
@ -3493,9 +3552,6 @@ class LinkDotResolveVisitor final : public VNVisitor {
UINFO(9, indent() << m_ds.ascii());
VL_RESTORER(m_usedPins);
m_usedPins.clear();
UASSERT_OBJ(m_statep->forPrimary() || VN_IS(nodep->classOrPackageNodep(), ParamTypeDType)
|| nodep->classOrPackageSkipp(),
nodep, "ClassRef has unlinked class");
UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp(), nodep,
"class reference parameter not removed by V3Param");
{
@ -3506,6 +3562,10 @@ class LinkDotResolveVisitor final : public VNVisitor {
m_statep->resolveClassOrPackage(m_ds.m_dotSymp, nodep, m_ds.m_dotPos != DP_PACKAGE,
false, ":: reference");
}
UASSERT_OBJ(m_statep->forPrimary()
|| VN_IS(nodep->classOrPackageNodep(), ParamTypeDType)
|| nodep->classOrPackageSkipp(),
nodep, "ClassRef has unlinked class");
// ClassRef's have pins, so track
if (nodep->classOrPackageSkipp()) {
@ -3665,7 +3725,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
AstVarRef* const newrefp
= new AstVarRef{nodep->fileline(), nodep->varp(), nodep->access()};
nodep->replaceWith(newrefp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
}
@ -3693,7 +3753,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
AstVarRef* const newvscp
= new AstVarRef{nodep->fileline(), vscp, nodep->access()};
nodep->replaceWith(newvscp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
UINFO(9, indent() << "new " << newvscp); // Also prints taskp
}
}
@ -3727,17 +3787,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
if (!fromDtp) {
if (const AstNodeVarRef* const varRefp = VN_CAST(nodep->fromp(), NodeVarRef)) {
fromDtp = varRefp->varp()->subDTypep();
} else if (const AstNodeSel* const selp = VN_CAST(nodep->fromp(), NodeSel)) {
if (const AstNodeVarRef* const varRefp
= VN_CAST(selp->fromp(), NodeVarRef)) {
fromDtp = varRefp->varp()->dtypeSkipRefp()->subDTypep();
}
} else if (const AstNodePreSel* const selp
= VN_CAST(nodep->fromp(), NodePreSel)) {
if (const AstNodeVarRef* const varRefp
= VN_CAST(selp->fromp(), NodeVarRef)) {
fromDtp = varRefp->varp()->dtypeSkipRefp()->subDTypep();
}
} else {
fromDtp = getExprDTypep(nodep->fromp());
}
if (!fromDtp) {
if (VN_IS(nodep->pinsp(), With)) {
@ -3845,7 +3896,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
// HERE function() . method_called_on_function_return_value()
m_ds.m_dotPos = DP_MEMBER;
m_ds.m_dotText = "";
} else {
} else if (!m_ds.m_disablep) {
// visit(AstDisable*) setup the dot handling
checkNoDot(nodep);
}
if (nodep->classOrPackagep() && nodep->taskp()) {
@ -3983,7 +4035,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
AstNode* addp = pinp;
if (AstArg* const argp = VN_CAST(pinp, Arg)) {
addp = argp->exprp()->unlinkFrBack();
VL_DO_DANGLING(pinp->deleteTree(), pinp);
VL_DO_DANGLING(pushDeletep(pinp), pinp);
}
outp = AstNode::addNext(outp, addp);
}
@ -3991,7 +4043,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
newp->dtypep(nodep->dtypep());
}
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
} else {
VSpellCheck speller;
@ -4213,6 +4265,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
if (nodep->classOrNullp()) return;
LINKDOT_VISIT_START();
if (m_statep->forPrimary()) {
if (nodep->childDTypep()) return;
AstNode* cprp = nodep->classOrPkgsp();
VSymEnt* lookSymp = m_curSymp;
if (AstDot* const dotp = VN_CAST(cprp, Dot)) {
@ -4419,6 +4472,12 @@ class LinkDotResolveVisitor final : public VNVisitor {
nodep->classOrPackagep(cpackagerefp->classOrPackageSkipp());
if (!VN_IS(nodep->classOrPackagep(), Class)
&& !VN_IS(nodep->classOrPackagep(), Package)) {
if (m_statep->forPrimary()) {
// It may be a type that comes from parameter class that is not
// instantioned yet
iterate(cpackagep);
return;
}
// Likely impossible, as error thrown earlier
cpackagerefp->v3error( // LCOV_EXCL_LINE
"'::' expected to reference a class/package but referenced '"
@ -4431,7 +4490,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
cpackagep->v3warn(E_UNSUPPORTED,
"Unsupported: Multiple '::' package/class reference");
}
VL_DO_DANGLING(cpackagep->unlinkFrBack()->deleteTree(), cpackagep);
VL_DO_DANGLING(pushDeletep(cpackagep->unlinkFrBack()), cpackagep);
}
if (m_ds.m_dotp && (m_ds.m_dotPos == DP_PACKAGE || m_ds.m_dotPos == DP_SCOPE)) {
UASSERT_OBJ(VN_IS(m_ds.m_dotp->lhsp(), ClassOrPackageRef), m_ds.m_dotp->lhsp(),
@ -4476,7 +4535,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
= new AstClassRefDType{nodep->fileline(), defp, paramsp};
newp->classOrPackagep(foundp->classOrPackagep());
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
} else if (m_insideClassExtParam) {
return;
@ -4508,25 +4567,49 @@ class LinkDotResolveVisitor final : public VNVisitor {
taskp->dpiExport(true);
if (nodep->cname() != "") taskp->cname(nodep->cname());
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstDisable* nodep) override {
LINKDOT_VISIT_START();
checkNoDot(nodep);
VL_RESTORER(m_ds);
m_ds.init(m_curSymp);
m_ds.m_dotPos = DP_FIRST;
m_ds.m_disablep = nodep;
iterateChildren(nodep);
if (nodep->targetRefp()) {
if (AstTaskRef* const taskRefp = VN_CAST(nodep->targetRefp(), TaskRef)) {
nodep->targetp(taskRefp->taskp());
} else if (!VN_IS(nodep->targetRefp(), ParseRef)) {
// If it is a ParseRef, either it couldn't be linked or it is linked to a block
nodep->v3warn(E_UNSUPPORTED, "Node of type "
<< nodep->targetRefp()->prettyTypeName()
<< " referenced by disable");
pushDeletep(nodep->unlinkFrBack());
}
if (nodep->targetp()) {
// If the target is already linked, there is no need to store reference as child
VL_DO_DANGLING(nodep->targetRefp()->unlinkFrBack()->deleteTree(), nodep);
}
}
}
void visit(AstPackageImport* nodep) override {
// No longer needed
LINKDOT_VISIT_START();
checkNoDot(nodep);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstPackageExport* nodep) override {
// No longer needed
LINKDOT_VISIT_START();
checkNoDot(nodep);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstPackageExportStarStar* nodep) override {
// No longer needed
LINKDOT_VISIT_START();
checkNoDot(nodep);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstCellRef* nodep) override {
LINKDOT_VISIT_START();
@ -4648,7 +4731,7 @@ void V3LinkDot::linkDotGuts(AstNetlist* rootp, VLinkDotStep step) {
state.computeIfaceVarSyms();
state.computeScopeAliases();
state.dumpSelf();
{ LinkDotResolveVisitor{rootp, &state}; }
LinkDotResolveVisitor visitor{rootp, &state};
state.dumpSelf();
}

View File

@ -35,6 +35,7 @@
#include "V3AstUserAllocator.h"
#include "V3Error.h"
#include "V3UniqueNames.h"
#include <vector>
@ -60,6 +61,8 @@ class LinkJumpVisitor final : public VNVisitor {
int m_modRepeatNum = 0; // Repeat counter
VOptionBool m_unrollFull; // Pragma full, disable, or default unrolling
std::vector<AstNodeBlock*> m_blockStack; // All begin blocks above current node
V3UniqueNames m_queueNames{
"__VprocessQueue"}; // Names for queues needed for 'disable' handling
// METHODS
AstJumpLabel* findAddLabel(AstNode* nodep, bool endOfIter) {
@ -159,6 +162,79 @@ class LinkJumpVisitor final : public VNVisitor {
if (AstNode* const refp = nodep->op4p()) addPrefixToBlocksRecurse(prefix, refp);
if (AstNode* const refp = nodep->nextp()) addPrefixToBlocksRecurse(prefix, refp);
}
static AstNode* getMemberp(const AstNodeModule* const nodep, const std::string& name) {
for (AstNode* itemp = nodep->stmtsp(); itemp; itemp = itemp->nextp()) {
if (itemp->name() == name) return itemp;
}
return nullptr;
}
bool existsBlockAbove(const std::string& name) const {
for (const AstNodeBlock* const stackp : vlstd::reverse_view(m_blockStack)) {
if (stackp->name() == name) return true;
}
return false;
}
static AstStmtExpr* getQueuePushProcessSelfp(AstVarRef* const queueRefp) {
// Constructs queue.push_back(std::process::self()) statement
FileLine* const fl = queueRefp->fileline();
AstClass* const processClassp
= VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
AstFunc* const selfMethodp = VN_AS(getMemberp(processClassp, "self"), Func);
AstFuncRef* const processSelfp = new AstFuncRef{fl, selfMethodp, nullptr};
processSelfp->classOrPackagep(processClassp);
return new AstStmtExpr{
fl, new AstMethodCall{fl, queueRefp, "push_back", new AstArg{fl, "", processSelfp}}};
}
void handleDisableOnFork(AstDisable* const nodep, const std::vector<AstBegin*>& forks) {
// The support is limited only to disabling a fork from outside that fork.
// It utilizes the process::kill()` method. For each `disable` a queue of processes is
// declared. At the beginning of each fork that can be disabled, its process handle is
// pushed to the queue. `disable` statement is replaced with calling `kill()` method on
// each element of the queue.
FileLine* const fl = nodep->fileline();
const std::string targetName = nodep->targetp()->name();
if (existsBlockAbove(targetName)) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: disabling fork from within same fork");
}
if (m_ftaskp) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: disabling fork from task / function");
}
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
AstClass* const processClassp
= VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
// Declare queue of processes (as a global variable for simplicity)
AstVar* const processQueuep = new AstVar{
fl, VVarType::VAR, m_queueNames.get(targetName), VFlagChildDType{},
new AstQueueDType{fl, VFlagChildDType{},
new AstClassRefDType{fl, processClassp, nullptr}, nullptr}};
processQueuep->lifetime(VLifetime::STATIC);
topPkgp->addStmtsp(processQueuep);
AstVarRef* const queueWriteRefp
= new AstVarRef{fl, topPkgp, processQueuep, VAccess::WRITE};
AstStmtExpr* pushCurrentProcessp = getQueuePushProcessSelfp(queueWriteRefp);
for (AstBegin* const beginp : forks) {
if (pushCurrentProcessp->backp()) {
pushCurrentProcessp = pushCurrentProcessp->cloneTree(false);
}
if (beginp->stmtsp()) {
// There is no need to add it to empty block
beginp->stmtsp()->addHereThisAsNext(pushCurrentProcessp);
}
}
AstVarRef* const queueRefp = new AstVarRef{fl, topPkgp, processQueuep, VAccess::READWRITE};
AstTaskRef* const killQueueCall
= new AstTaskRef{fl, VN_AS(getMemberp(processClassp, "killQueue"), Task),
new AstArg{fl, "", queueRefp}};
killQueueCall->classOrPackagep(processClassp);
nodep->addNextHere(new AstStmtExpr{fl, killQueueCall});
}
static bool directlyUnderFork(const AstNode* const nodep) {
if (nodep->backp()->nextp() == nodep) return directlyUnderFork(nodep->backp());
if (VN_IS(nodep->backp(), Fork)) return true;
return false;
}
// VISITORS
void visit(AstNodeModule* nodep) override {
@ -337,33 +413,52 @@ class LinkJumpVisitor final : public VNVisitor {
}
void visit(AstDisable* nodep) override {
UINFO(8, " DISABLE " << nodep);
iterateChildren(nodep);
AstNodeBlock* blockp = nullptr;
for (AstNodeBlock* const stackp : vlstd::reverse_view(m_blockStack)) {
UINFO(9, " UNDERBLK " << stackp);
if (stackp->name() == nodep->name()) {
blockp = stackp;
break;
AstNode* const targetp = nodep->targetp();
FileLine* const fl = nodep->fileline();
UASSERT_OBJ(targetp, nodep, "Unlinked disable statement");
if (VN_IS(targetp, Task)) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: disabling task by name");
} else if (AstFork* const forkp = VN_CAST(targetp, Fork)) {
std::vector<AstBegin*> forks;
for (AstNode* forkItemp = forkp->stmtsp(); forkItemp; forkItemp = forkItemp->nextp()) {
// Further handling of disable stmt requires all forks to be begin blocks
AstBegin* beginp = VN_CAST(forkItemp, Begin);
if (!beginp) {
beginp = new AstBegin{fl, "", nullptr};
forkItemp->replaceWith(beginp);
beginp->addStmtsp(forkItemp);
// In order to continue the iteration
forkItemp = beginp;
}
forks.push_back(beginp);
}
}
// if (debug() >= 9) { UINFO(0, "\n"); blockp->dumpTree("- labeli: "); }
if (!blockp) {
nodep->v3warn(E_UNSUPPORTED,
"disable isn't underneath a begin with name: " << nodep->prettyNameQ());
} else if (AstBegin* const beginp = VN_CAST(blockp, Begin)) {
if (beginp->user3()) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: disabling block that contains a fork");
handleDisableOnFork(nodep, forks);
} else if (AstBegin* const beginp = VN_CAST(targetp, Begin)) {
if (directlyUnderFork(beginp)) {
std::vector<AstBegin*> forks{beginp};
handleDisableOnFork(nodep, forks);
} else {
// Jump to the end of the named block
AstJumpLabel* const labelp = findAddLabel(beginp, false);
nodep->addNextHere(new AstJumpGo{nodep->fileline(), labelp});
const std::string targetName = beginp->name();
if (existsBlockAbove(targetName)) {
if (beginp->user3()) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: disabling block that contains a fork");
} else {
// Jump to the end of the named block
AstJumpLabel* const labelp = findAddLabel(beginp, false);
nodep->addNextHere(new AstJumpGo{nodep->fileline(), labelp});
}
} else {
nodep->v3warn(E_UNSUPPORTED, "disable isn't underneath a begin with name: '"
<< targetName << "'");
}
}
} else {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: disabling fork by name");
nodep->v3fatalSrc("Disable linked with node of unhandled type "
<< targetp->prettyTypeName());
}
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
// if (debug() >= 9) { UINFO(0, "\n"); beginp->dumpTree("- labelo: "); }
}
void visit(AstVarRef* nodep) override {
if (m_loopInc && nodep->varp()) nodep->varp()->usedLoopIdx(true);

View File

@ -267,16 +267,17 @@ class LinkParseVisitor final : public VNVisitor {
const int left = nodep->rangep()->leftConst();
const int right = nodep->rangep()->rightConst();
const int increment = (left > right) ? -1 : 1;
int offset_from_init = 0;
uint32_t offset_from_init = 0;
AstEnumItem* addp = nullptr;
FileLine* const flp = nodep->fileline();
for (int i = left; i != (right + increment); i += increment, offset_from_init++) {
for (int i = left; i != (right + increment); i += increment, ++offset_from_init) {
const string name = nodep->name() + cvtToStr(i);
AstNodeExpr* valuep = nullptr;
if (nodep->valuep()) {
// V3Width looks for Adds with same fileline as the EnumItem
valuep
= new AstAdd{flp, nodep->valuep()->cloneTree(true),
new AstConst(flp, AstConst::Unsized32{}, offset_from_init)};
new AstConst{flp, AstConst::Unsized32{}, offset_from_init}};
}
addp = AstNode::addNext(addp, new AstEnumItem{flp, name, nullptr, valuep});
}
@ -577,7 +578,7 @@ class LinkParseVisitor final : public VNVisitor {
} else if (VN_IS(bracketp, SelLoopVars)) {
// Ok
} else {
nodep->v3error("Syntax error; foreach missing bracketed loop variable"
nodep->v3error("Foreach missing bracketed loop variable is no-operation"
" (IEEE 1800-2023 12.7.3)");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;

View File

@ -1188,7 +1188,7 @@ uint32_t V3Number::countOnes() const {
uint32_t V3Number::mostSetBitP1() const {
for (int bit = width() - 1; bit >= 0; bit--) {
if (bitIs1(bit)) return bit + 1;
if (!bitIs0(bit)) return bit + 1;
}
return 0;
}

View File

@ -1336,6 +1336,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
m_fDfgPostInline = flag;
m_fDfgScoped = flag;
});
DECL_OPTION("-fdfg-break-cycles", FOnOff, &m_fDfgBreakCycles);
DECL_OPTION("-fdfg-peephole", FOnOff, &m_fDfgPeephole);
DECL_OPTION("-fdfg-peephole-", CbPartialMatch, [this](const char* optp) { //
m_fDfgPeepholeDisabled.erase(optp);

View File

@ -420,6 +420,7 @@ private:
bool m_fConstBitOpTree; // main switch: -fno-const-bit-op-tree constant bit op tree
bool m_fConstEager = true; // main switch: -fno-const-eagerly run V3Const during passes
bool m_fDedupe; // main switch: -fno-dedupe: logic deduplication
bool m_fDfgBreakCycles = true; // main switch: -fno-dfg-break-cycles
bool m_fDfgPeephole = true; // main switch: -fno-dfg-peephole
bool m_fDfgPreInline; // main switch: -fno-dfg-pre-inline and -fno-dfg
bool m_fDfgPostInline; // main switch: -fno-dfg-post-inline and -fno-dfg
@ -735,6 +736,7 @@ public:
bool fConstBitOpTree() const { return m_fConstBitOpTree; }
bool fConstEager() const { return m_fConstEager; }
bool fDedupe() const { return m_fDedupe; }
bool fDfgBreakCyckes() const { return m_fDfgBreakCycles; }
bool fDfgPeephole() const { return m_fDfgPeephole; }
bool fDfgPreInline() const { return m_fDfgPreInline; }
bool fDfgPostInline() const { return m_fDfgPostInline; }

View File

@ -1116,7 +1116,7 @@ class ParamVisitor final : public VNVisitor {
} else {
cellp->v3fatalSrc("Expected module parameterization");
}
UASSERT_OBJ(srcModp, cellp, "Unlinked class ref");
if (!srcModp) continue;
// Update path
string someInstanceName = modp->someInstanceName();

View File

@ -293,12 +293,12 @@ public:
void dumpInputsFile() VL_MT_DISABLED;
void dumpTokensAhead(int line) VL_MT_DISABLED;
static void candidatePli(VSpellCheck* spellerp) VL_MT_DISABLED;
void importIfInStd(FileLine* fileline, const string& id);
private:
void preprocDumps(std::ostream& os);
void lexFile(const string& modname) VL_MT_DISABLED;
void yylexReadTok() VL_MT_DISABLED;
void importIfInStd(FileLine* fileline, const string& id);
void tokenPull() VL_MT_DISABLED;
void tokenPipeline() VL_MT_DISABLED; // Internal; called from tokenToBison
int tokenPipelineId(int token) VL_MT_DISABLED;

View File

@ -1189,6 +1189,18 @@ VirtIfaceTriggers::makeIfaceToSensMap(AstNetlist* const netlistp, size_t vifTrig
return ifaceToSensMap;
}
VirtIfaceTriggers::IfaceMemberSensMap
VirtIfaceTriggers::makeMemberToSensMap(AstNetlist* const netlistp, size_t vifTriggerIndex,
AstVarScope* trigVscp) const {
IfaceMemberSensMap memberToSensMap;
for (const auto& p : m_memberTriggers) {
memberToSensMap.emplace(
std::make_pair(p.first, createTriggerSenTree(netlistp, trigVscp, vifTriggerIndex)));
++vifTriggerIndex;
}
return memberToSensMap;
}
//============================================================================
// Top level entry-point to scheduling

View File

@ -153,17 +153,55 @@ public:
};
class VirtIfaceTriggers final {
// Represents a specific member in a virtual interface
struct IfaceMember final {
const AstIface* m_ifacep; // Interface type
const AstVar* m_memberVarp; // pointer to member field
IfaceMember(const AstIface* ifacep, const AstVar* memberVarp)
: m_ifacep(ifacep)
, m_memberVarp(memberVarp) {}
bool operator<(const IfaceMember& other) const {
if (m_ifacep != other.m_ifacep) return m_ifacep < other.m_ifacep;
return m_memberVarp < other.m_memberVarp;
}
};
using IfaceMemberTrigger = std::pair<IfaceMember, AstVarScope*>;
using IfaceMemberTriggerVec = std::vector<IfaceMemberTrigger>;
using IfaceMemberSensMap = std::map<IfaceMember, AstSenTree*>;
using IfaceTrigger = std::pair<const AstIface*, AstVarScope*>;
using IfaceTriggerVec = std::vector<IfaceTrigger>;
using IfaceSensMap = std::map<const AstIface*, AstSenTree*>;
IfaceTriggerVec m_triggers;
IfaceMemberTriggerVec m_memberTriggers;
IfaceTriggerVec m_ifaceTriggers;
public:
void emplace_back(IfaceTrigger&& p) { m_triggers.emplace_back(std::move(p)); }
IfaceTriggerVec::const_iterator begin() const { return m_triggers.begin(); }
IfaceTriggerVec::const_iterator end() const { return m_triggers.end(); }
void addMemberTrigger(const AstIface* ifacep, const AstVar* memberVarp,
AstVarScope* triggerVscp) {
m_memberTriggers.emplace_back(IfaceMember(ifacep, memberVarp), triggerVscp);
}
AstVarScope* findMemberTrigger(const AstIface* ifacep, const AstVar* memberVarp) const {
IfaceMember target{ifacep, memberVarp};
for (const auto& pair : m_memberTriggers) {
if (!(pair.first < target) && !(target < pair.first)) return pair.second;
}
return nullptr;
}
IfaceMemberSensMap makeMemberToSensMap(AstNetlist* netlistp, size_t vifTriggerIndex,
AstVarScope* trigVscp) const;
void emplace_back(IfaceTrigger&& p) { m_ifaceTriggers.emplace_back(std::move(p)); }
IfaceTriggerVec::const_iterator begin() const { return m_ifaceTriggers.begin(); }
IfaceTriggerVec::const_iterator end() const { return m_ifaceTriggers.end(); }
IfaceSensMap makeIfaceToSensMap(AstNetlist* netlistp, size_t vifTriggerIndex,
AstVarScope* trigVscp) const;
VL_UNCOPYABLE(VirtIfaceTriggers);
VirtIfaceTriggers() = default;
VirtIfaceTriggers(VirtIfaceTriggers&&) = default;

View File

@ -49,12 +49,15 @@ private:
// TYPES
using OnWriteToVirtIface = std::function<void(AstVarRef*, AstIface*)>;
using OnWriteToVirtIfaceMember
= std::function<void(AstVarRef*, AstIface*, const std::string&)>;
// STATE
AstNetlist* const m_netlistp; // Root node
AstAssign* m_trigAssignp = nullptr; // Previous/current trigger assignment
AstIface* m_trigAssignIfacep = nullptr; // Interface type whose trigger is assigned
// by m_trigAssignp
AstVar* m_trigAssignMemberVarp; // Member pointer whose trigger is assigned
V3UniqueNames m_vifTriggerNames{"__VvifTrigger"}; // Unique names for virt iface
// triggers
VirtIfaceTriggers m_triggers; // Interfaces and corresponding trigger vars
@ -73,6 +76,25 @@ private:
}
});
}
// For each write across a virtual interface boundary (member-level tracking)
static void foreachWrittenVirtIfaceMember(
AstNode* const nodep, const std::function<void(AstVarRef*, AstIface*, AstVar*)>& onWrite) {
nodep->foreach([&](AstVarRef* const refp) {
if (refp->access().isReadOnly()) return;
if (AstIfaceRefDType* const dtypep = VN_CAST(refp->varp()->dtypep(), IfaceRefDType)) {
if (dtypep->isVirtual()) {
if (AstMemberSel* const memberSelp = VN_CAST(refp->firstAbovep(), MemberSel)) {
// Extract the member varp from the MemberSel node
AstVar* memberVarp = memberSelp->varp();
onWrite(refp, dtypep->ifacep(), memberVarp);
}
}
} else if (AstIface* const ifacep = refp->varp()->sensIfacep()) {
AstVar* memberVarp = refp->varp();
onWrite(refp, ifacep, memberVarp);
}
});
}
// Returns true if there is a write across a virtual interface boundary
static bool writesToVirtIface(const AstNode* const nodep) {
return nodep->exists([](const AstVarRef* const refp) {
@ -103,12 +125,31 @@ private:
return new AstVarRef{flp, VN_AS(ifacep->user1p(), VarScope), VAccess::WRITE};
}
// Create trigger reference for a specific interface member
AstVarRef* createVirtIfaceMemberTriggerRefp(FileLine* const flp, AstIface* ifacep,
const AstVar* memberVarp) {
// Check if we already have a trigger for this specific member
AstVarScope* existingTrigger = m_triggers.findMemberTrigger(ifacep, memberVarp);
if (!existingTrigger) {
AstScope* const scopeTopp = m_netlistp->topScopep()->scopep();
// Create a unique name for this member trigger
const std::string triggerName
= m_vifTriggerNames.get(ifacep) + "_Vtrigm_" + memberVarp->name();
AstVarScope* const vscp = scopeTopp->createTemp(triggerName, 1);
m_triggers.addMemberTrigger(ifacep, memberVarp, vscp);
existingTrigger = vscp;
}
return new AstVarRef{flp, existingTrigger, VAccess::WRITE};
}
// VISITORS
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_trigAssignp);
m_trigAssignp = nullptr;
VL_RESTORER(m_trigAssignIfacep);
m_trigAssignIfacep = nullptr;
VL_RESTORER(m_trigAssignMemberVarp);
m_trigAssignMemberVarp = nullptr;
iterateChildren(nodep);
}
void visit(AstCFunc* nodep) override {
@ -116,6 +157,8 @@ private:
m_trigAssignp = nullptr;
VL_RESTORER(m_trigAssignIfacep);
m_trigAssignIfacep = nullptr;
VL_RESTORER(m_trigAssignMemberVarp);
m_trigAssignMemberVarp = nullptr;
iterateChildren(nodep);
}
void visit(AstAssignW* nodep) override {
@ -140,11 +183,13 @@ private:
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateAndNextNull(nodep->thensp());
}
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateAndNextNull(nodep->elsesp());
}
if (v3Global.usesTiming()) {
@ -152,6 +197,7 @@ private:
// branch
m_trigAssignp = nullptr;
m_trigAssignIfacep = nullptr;
m_trigAssignMemberVarp = nullptr;
}
}
void visit(AstWhile* nodep) override {
@ -161,18 +207,21 @@ private:
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateAndNextNull(nodep->stmtsp());
}
if (v3Global.usesTiming()) {
// Clear the trigger assignment, as there could have been timing controls in the loop
m_trigAssignp = nullptr;
m_trigAssignIfacep = nullptr;
m_trigAssignMemberVarp = nullptr;
}
}
void visit(AstJumpBlock* nodep) override {
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
VL_RESTORER(m_trigAssignMemberVarp);
iterateChildren(nodep);
}
if (v3Global.usesTiming()) {
@ -180,29 +229,48 @@ private:
// block
m_trigAssignp = nullptr;
m_trigAssignIfacep = nullptr;
m_trigAssignMemberVarp = nullptr;
}
}
void visit(AstNodeStmt* nodep) override {
if (v3Global.usesTiming()
&& nodep->exists([](AstNode* nodep) { return nodep->isTimingControl(); })) {
m_trigAssignp = nullptr; // Could be after a delay - need new trigger assignment
m_trigAssignp = nullptr;
m_trigAssignIfacep = nullptr;
// No restorer, as following statements should not reuse the old assignment
m_trigAssignMemberVarp = nullptr;
}
FileLine* const flp = nodep->fileline();
foreachWrittenVirtIface(nodep, [&](AstVarRef*, AstIface* ifacep) {
if (ifacep != m_trigAssignIfacep) {
// Write to different interface type than before - need new trigger assignment
// No restorer, as following statements should not reuse the old assignment
foreachWrittenVirtIfaceMember(nodep, [&](AstVarRef*, AstIface* ifacep,
AstVar* memberVarp) {
if (ifacep != m_trigAssignIfacep || memberVarp != m_trigAssignMemberVarp) {
// Write to different interface member than before - need new trigger assignment
m_trigAssignIfacep = ifacep;
m_trigAssignMemberVarp = memberVarp;
m_trigAssignp = nullptr;
}
if (!m_trigAssignp) {
m_trigAssignp = new AstAssign{flp, createVirtIfaceTriggerRefp(flp, ifacep),
new AstConst{flp, AstConst::BitTrue{}}};
m_trigAssignp
= new AstAssign{flp, createVirtIfaceMemberTriggerRefp(flp, ifacep, memberVarp),
new AstConst{flp, AstConst::BitTrue{}}};
nodep->addNextHere(m_trigAssignp);
}
});
// Fallback to whole-interface tracking if no member-specific assignments found
if (!m_trigAssignp) {
foreachWrittenVirtIface(nodep, [&](AstVarRef*, AstIface* ifacep) {
if (ifacep != m_trigAssignIfacep) {
m_trigAssignIfacep = ifacep;
m_trigAssignMemberVarp = nullptr;
m_trigAssignp = nullptr;
}
if (!m_trigAssignp) {
m_trigAssignp = new AstAssign{flp, createVirtIfaceTriggerRefp(flp, ifacep),
new AstConst{flp, AstConst::BitTrue{}}};
nodep->addNextHere(m_trigAssignp);
}
});
}
}
void visit(AstNodeExpr*) override {} // Accelerate
void visit(AstNode* nodep) override { iterateChildren(nodep); }

View File

@ -2461,11 +2461,11 @@ class WidthVisitor final : public VNVisitor {
// if (debug() >= 9) nodep->dumpTree("- VRout: ");
if (nodep->access().isWriteOrRW() && nodep->varp()->direction() == VDirection::CONSTREF) {
nodep->v3error("Assigning to const ref variable: " << nodep->prettyNameQ());
} else if (!nodep->varp()->isForced() && nodep->access().isWriteOrRW()
&& nodep->varp()->isInput() && !nodep->varp()->isFuncLocal()
&& nodep->varp()->isReadOnly() && (!m_ftaskp || !m_ftaskp->isConstructor())
&& !VN_IS(m_procedurep, InitialAutomatic)
&& !VN_IS(m_procedurep, InitialStatic)) {
} else if (nodep->access().isWriteOrRW() && nodep->varp()->isInput()
&& !nodep->varp()->isFuncLocal() && nodep->varp()->isReadOnly()
&& (!m_ftaskp || !m_ftaskp->isConstructor())
&& !VN_IS(m_procedurep, InitialAutomatic) && !VN_IS(m_procedurep, InitialStatic)
&& !VN_IS(nodep->abovep(), AssignForce) && !VN_IS(nodep->abovep(), Release)) {
nodep->v3warn(ASSIGNIN, "Assigning to input/const variable: " << nodep->prettyNameQ());
} else if (nodep->access().isWriteOrRW() && nodep->varp()->isConst() && !m_paramsOnly
&& (!m_ftaskp || !m_ftaskp->isConstructor())
@ -2553,9 +2553,16 @@ class WidthVisitor final : public VNVisitor {
// Default type is int, but common to assign narrower values, so minwidth from value
userIterateAndNext(nodep->valuep(), WidthVP{CONTEXT_DET, PRELIM}.p());
bool warnOn = true;
if (const AstConst* const constp = VN_CAST(nodep->valuep(), Const)) {
AstNodeExpr* valuep = nodep->valuep();
if (const AstAdd* const anodep = VN_CAST(valuep, Add)) {
// If constructed by V3LinkParse due to "enumitem[N_REPEATS] value"
if (anodep->fileline()->equalFirstLineCol(*(nodep->fileline())))
valuep = anodep->lhsp();
}
if (const AstConst* const constp = VN_CAST(valuep, Const)) {
if (static_cast<int>(constp->num().mostSetBitP1()) > nodep->width()) {
constp->v3error("Enum value exceeds width of enum type (IEEE 1800-2023 6.19)");
constp->v3warn(ENUMITEMWIDTH,
"Enum value exceeds width of enum type (IEEE 1800-2023 6.19)");
warnOn = false; // Prevent normal WIDTHTRUNC
}
}
@ -7668,11 +7675,11 @@ class WidthVisitor final : public VNVisitor {
// SIGNED/DOUBLE METHODS
AstNodeExpr* checkCvtUS(AstNodeExpr* nodep) {
if (nodep && nodep->isDouble()) {
nodep->v3error("Expected integral (non-" << nodep->dtypep()->prettyDTypeNameQ()
<< ") input to "
<< nodep->backp()->prettyTypeName());
nodep = spliceCvtS(nodep, true, 32);
if (nodep && nodep->dtypep()->skipRefp()->isDouble()) {
nodep->v3warn(REALCVT,
"Implicit conversion of real to integer; expected integral input to "
<< nodep->backp()->prettyTypeName());
nodep = spliceCvtS(nodep, false, 32);
}
return nodep;
}

View File

@ -1219,6 +1219,29 @@ def write_dfg_auto_classes(filename):
fh.write("\n")
def write_dfg_clone_cases(filename):
with open_file(filename) as fh:
def emitBlock(pattern, **fmt):
fh.write(textwrap.dedent(pattern).format(**fmt))
for node in DfgVertexList:
# Only generate code for automatically derived leaf nodes
if (node.file is not None) or not node.isLeaf:
continue
emitBlock('''\
case VDfgType::at{t}: {{
Dfg{t}* const cp = new Dfg{t}{{*clonep, vtx.fileline(), vtx.dtypep()}};
vtxp2clonep.emplace(&vtx, cp);
break;
}}
''',
t=node.name,
s=node.superClass.name)
fh.write("\n")
def write_dfg_ast_to_dfg(filename):
with open_file(filename) as fh:
for node in DfgVertexList:
@ -1408,6 +1431,7 @@ if Args.classes:
write_type_tests("Dfg", DfgVertexList)
write_dfg_macros("V3Dfg__gen_macros.h")
write_dfg_auto_classes("V3Dfg__gen_auto_classes.h")
write_dfg_clone_cases("V3Dfg__gen_clone_cases.h")
write_dfg_ast_to_dfg("V3Dfg__gen_ast_to_dfg.h")
write_dfg_dfg_to_ast("V3Dfg__gen_dfg_to_ast.h")

View File

@ -2226,10 +2226,11 @@ data_typeNoRef<nodeDTypep>: // ==IEEE: data_type, excluding class_ty
new AstDefImplicitDType{$1->fileline(),
"__typeimpsu" + cvtToStr(GRAMMARP->s_typeImpNum++),
VFlagChildDType{}, $1}, $2, true); }
| enumDecl
{ $$ = new AstDefImplicitDType{$1->fileline(),
"__typeimpenum" + cvtToStr(GRAMMARP->s_typeImpNum++),
VFlagChildDType{}, $1}; }
| enumDecl packed_dimensionListE
{ $$ = GRAMMARP->createArray(
new AstDefImplicitDType{$1->fileline(),
"__typeimpenum" + cvtToStr(GRAMMARP->s_typeImpNum++),
VFlagChildDType{}, $1}, $2, true); }
| ySTRING
{ $$ = new AstBasicDType{$1, VBasicDTypeKwd::STRING}; }
| yCHANDLE
@ -3753,10 +3754,10 @@ statement_item<nodep>: // IEEE: statement_item
//
// // IEEE: disable_statement
| yDISABLE yFORK ';' { $$ = new AstDisableFork{$1}; }
| yDISABLE idAny/*UNSUP: hierarchical_identifier-task_or_block*/ ';'
{ $$ = new AstDisable{$1, *$2}; }
| yDISABLE idAny '.' idDottedSel ';'
{ $$ = nullptr; BBUNSUP($4, "Unsupported: disable with '.'"); }
| yDISABLE idDottedSel ';'
{ $$ = new AstDisable{$1, $2};
PARSEP->importIfInStd($1, "process");
}
// // IEEE: event_trigger
| yP_MINUSGT expr ';'
{ $$ = new AstFireEvent{$1, $2, false}; }
@ -4180,6 +4181,7 @@ loop_variables<nodep>: // IEEE: loop_variables
parseRefBase { $$ = $1; }
| loop_variables ',' parseRefBase { $$ = $1->addNext($3); }
| ',' parseRefBase { $$ = new AstEmpty{$1}; $$->addNext($2); }
| ',' { $$ = new AstEmpty{$1}; }
;
//************************************************
@ -6118,6 +6120,8 @@ idArrayedForeach<nodeExprp>: // IEEE: id + select (under foreach expression)
| idArrayed '[' expr yP_MINUSCOLON constExpr ']' { $$ = new AstSelMinus{$2, $1, $3, $5}; }
// // IEEE: loop_variables (under foreach expression)
// // To avoid conflicts we allow expr as first element, must post-check
| idArrayed '[' ']'
{ $$ = new AstSelLoopVars{$2, $1, new AstEmpty{$3}}; }
| idArrayed '[' expr ',' loop_variables ']'
{ $$ = new AstSelLoopVars{$2, $1, addNextNull(static_cast<AstNode*>($3), $5)}; }
| idArrayed '[' ',' loop_variables ']'
@ -6821,21 +6825,38 @@ covergroup_declaration<nodep>: // ==IEEE: covergroup_declaration
| covergroup_declarationFront '(' tf_port_listE ')'
/*cont*/ coverage_eventE ';' coverage_spec_or_optionListE
/*cont*/ yENDGROUP endLabelE
{ $$ = $1;
{ AstFunc* const newp = new AstFunc{$<fl>1, "new", nullptr, nullptr};
newp->classMethod(true);
newp->isConstructor(true);
newp->dtypep($1->dtypep());
newp->addStmtsp($3);
$1->addMembersp(newp);
$$ = $1;
GRAMMARP->endLabel($<fl>9, $1, $9); }
// // IEEE 1800-2023 added:
| covergroup_declarationFront yEXTENDS idAny/*covergroup_identifier*/
/*cont*/ ';' coverage_spec_or_optionListE
/*cont*/ yENDGROUP endLabelE
{ $$ = $1;
GRAMMARP->endLabel($<fl>7, $1, $7); }
;
covergroup_extendsE<fl>: // IEEE: Part of covergroup_declaration
/* empty */ { $$ = nullptr; }
| yEXTENDS { $$ = $1; }
;
covergroup_declarationFront<classp>: // IEEE: part of covergroup_declaration
yCOVERGROUP idAny
{ $$ = new AstClass{$<fl>2, *$2, PARSEP->libname()};
yCOVERGROUP covergroup_extendsE idAny
{
$$ = new AstClass{$<fl>3, *$3, PARSEP->libname()};
AstFunc* const sample = new AstFunc{$<fl>1, "sample", nullptr, nullptr};
sample->classMethod(true);
sample->dtypep(sample->findVoidDType());
$$->addMembersp(sample);
AstFunc* const getCoverage = new AstFunc{$<fl>1, "get_coverage", nullptr, nullptr};
getCoverage->classMethod(true);
getCoverage->dtypep(getCoverage->findVoidDType());
$$->addMembersp(getCoverage);
BBCOVERIGN($<fl>1, "Ignoring unsupported: covergroup"); }
;
;
cgexpr<nodeExprp>: // IEEE-2012: covergroup_expression, before that just expression
expr { $$ = $1; }
@ -7005,7 +7026,7 @@ cross_itemList<nodep>: // IEEE: part of list_of_cross_items
;
cross_item<nodep>: // ==IEEE: cross_item
idAny/*cover_point_identifier or variable_identifier*/ { $$ = nullptr; /*UNSUP*/ }
idDotted/*cover_point_identifier or variable_identifier*/ { $1->deleteTree(); $$ = nullptr; /*UNSUP*/ }
;
cross_body<nodep>: // ==IEEE: cross_body
@ -7551,7 +7572,12 @@ class_item<nodep>: // ==IEEE: class_item
| class_declaration { $$ = $1; }
| timeunits_declaration { $$ = $1; }
| covergroup_declaration
{ $$ = nullptr; BBCOVERIGN($1, "Ignoring unsupported: covergroup within class"); }
{
const string cgName = $1->name();
$1->name("__vlAnonCG_" + cgName);
AstVar* const newp = new AstVar{$<fl>1, VVarType::VAR, cgName, VFlagChildDType{}, new AstRefDType($<fl>1, $1->name())};
$$ = addNextNull($1, newp);
}
// // local_parameter_declaration under parameter_declaration
| parameter_declaration ';' { $$ = $1; }
| ';' { $$ = nullptr; }
@ -8089,8 +8115,10 @@ vltDModule<strp>: // --module <arg>
;
vltDModuleE<strp>: // [--module <arg>]
/* empty */ { static string unit = "__024unit"; $$ = &unit; }
| vltDModule { $$ = $1; }
/* empty */
{ static string unit = "$unit"; $$ = &unit; } // .vlt uses prettyName
| vltDModule
{ $$ = $1; }
;
vltDScope<strp>: // --scope <arg>

View File

@ -0,0 +1,18 @@
#!/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('simulator')
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,88 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Petr Nohavica
// SPDX-License-Identifier: CC0-1.0
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
interface class IBottomMid;
pure virtual function void moo(int i);
endclass
interface class IBottom;
pure virtual function bit foo();
endclass
interface class IMid extends IBottomMid;
pure virtual function string bar();
endclass
class bottom_class implements IBottom;
string name;
function new(string name);
this.name = name;
endfunction
virtual function bit foo();
$display("%s", name);
endfunction
endclass
class middle_class extends bottom_class implements IMid, IBottom;
function new(string name);
super.new($sformatf("middle %0s", name));
endfunction
virtual function bit foo();
$display("%s", name);
return 0;
endfunction
virtual function void moo(int i);
$display("moo: %d", i);
endfunction
virtual function string bar();
return name;
endfunction
endclass
class top_class extends middle_class;
int i;
function new(string name, int i);
super.new($sformatf("%0s %0d", name, i));
this.i = i;
endfunction
endclass
class sky_class extends top_class;
function new(string name);
super.new(name, 42);
endfunction
endclass
module t;
initial begin
sky_class s = new("ahoj");
bottom_class b = s;
top_class t = s;
IMid im;
`checks( b.name, "middle ahoj 42" );
`checks( s.name, "middle ahoj 42" );
`checks( t.name, "middle ahoj 42" );
`checkh( t.i, 42);
`checks(s.bar(), "middle ahoj 42");
im = s;
im.moo(42);
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/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('simulator')
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,26 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
class Class1 #(type T);
static function int get_p();
return 7;
endfunction
endclass
class Class2 #(type T) extends Class1 #(T);
static function int get_p2;
return T::get_p();
endfunction
endclass
module t;
initial begin
typedef Class2#(Class1#(int)) Class;
if (Class::get_p2() != Class1#(int)::get_p()) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/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('simulator')
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,26 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
class Class1 #(type T);
static function int get();
return T::Helper::getter();
endfunction
endclass
class Class2;
typedef Class2 Helper;
static function int getter();
return 13;
endfunction
endclass
module t;
initial begin
if (Class1#(Class2)::get() != 13) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,16 @@
#!/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')
test.compile()
test.passes()

View File

@ -0,0 +1,19 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
/* verilator lint_off COVERIGN */
module t;
covergroup cgArgs(int var1, int var2=42);
endgroup
cgArgs cov1 = new(69, 77);
cgArgs cov2 = new(69);
function x();
cov1.sample();
cov2.get_coverage();
endfunction;
endmodule

View File

@ -0,0 +1,16 @@
#!/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')
test.compile()
test.passes()

View File

@ -0,0 +1,39 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
/* verilator lint_off COVERIGN */
module t;
class base;
enum {red, green, blue} color;
covergroup g1 (bit [3:0] a) with function sample(bit b);
option.weight = 10;
option.per_instance = 1;
coverpoint a;
coverpoint b;
c: coverpoint color;
endgroup
function new();
g1 = new(0);
endfunction
endclass
class derived extends base;
bit d;
covergroup extends g1;
option.weight = 1; // overrides the weight from base g1
// uses per_instance = 1 from base g1
c: coverpoint color // overrides the c coverpoint in base g1
{
ignore_bins ignore = {blue};
}
coverpoint d; // adds new coverpoint
cross a, d; // crosses new coverpoint with inherited one
endgroup :g1
function new();
super.new();
endfunction
endclass
endmodule

View File

@ -0,0 +1,16 @@
#!/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')
test.compile()
test.passes()

View File

@ -0,0 +1,39 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
/* verilator lint_off COVERIGN */
module t;
class base;
function new();
g1 = new(0);
endfunction
enum {red, green, blue} color;
covergroup g1 (bit [3:0] a) with function sample(bit b);
option.weight = 10;
option.per_instance = 1;
coverpoint a;
coverpoint b;
c: coverpoint color;
endgroup
endclass
class derived extends base;
bit d;
function new();
super.new();
endfunction
covergroup extends g1;
option.weight = 1; // overrides the weight from base g1
// uses per_instance = 1 from base g1
c: coverpoint color // overrides the c coverpoint in base g1
{
ignore_bins ignore = {blue};
}
coverpoint d; // adds new coverpoint
cross a, d; // crosses new coverpoint with inherited one
endgroup :g1
endclass
endmodule

View File

@ -0,0 +1,5 @@
%Error: t/t_covergroup_func_override_bad.v:10:5: syntax error, unexpected function
10 | function sample();
| ^~~~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -0,0 +1,20 @@
#!/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('simulator')
if test.vlt_all:
test.lint(fails=True, expect_filename=test.golden_filename)
else:
test.compile(nc_flags2=["-coverage", "functional"])
test.execute()
test.passes()

View File

@ -0,0 +1,17 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
/* verilator lint_off COVERIGN */
module t();
covergroup cg;
function sample();
endfunction
function get_coverage();
endfunction
endgroup
endmodule

View File

@ -0,0 +1,16 @@
#!/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')
test.compile()
test.passes()

View File

@ -0,0 +1,18 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
/* verilator lint_off COVERIGN */
class myClass;
covergroup embeddedCg;
endgroup
function new();
embeddedCg = new();
embeddedCg.sample();
embeddedCg.get_coverage();
endfunction
endclass

View File

@ -0,0 +1,16 @@
#!/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')
test.compile()
test.passes()

View File

@ -0,0 +1,30 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
/* verilator lint_off COVERIGN */
class myClass;
covergroup embeddedCg;
endgroup
function new();
embeddedCg = new();
embeddedCg.sample();
embeddedCg.get_coverage();
endfunction
endclass
class secondClass;
covergroup embeddedCg;
endgroup
function new();
embeddedCg = new();
embeddedCg.sample();
embeddedCg.get_coverage();
endfunction
endclass

View File

@ -0,0 +1,8 @@
%Error: t/t_covergroup_in_class_duplicate_bad.v:13:16: Duplicate declaration of CLASS '__vlAnonCG_embeddedCg': '__vlAnonCG_embeddedCg'
13 | covergroup embeddedCg;
| ^~~~~~~~~~
t/t_covergroup_in_class_duplicate_bad.v:9:16: ... Location of original declaration
9 | covergroup embeddedCg;
| ^~~~~~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/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('simulator')
test.compile(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,16 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
/* verilator lint_off COVERIGN */
class myClass;
covergroup embeddedCg;
endgroup
covergroup embeddedCg;
endgroup
endclass

View File

@ -0,0 +1,5 @@
%Error: t/t_covergroup_new_override_bad.v:10:5: syntax error, unexpected function
10 | function new();
| ^~~~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -0,0 +1,20 @@
#!/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('simulator')
if test.vlt_all:
test.lint(fails=True, expect_filename=test.golden_filename)
else:
test.compile(nc_flags2=["-coverage", "functional"])
test.execute()
test.passes()

View File

@ -0,0 +1,14 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
/* verilator lint_off COVERIGN */
module t();
covergroup cg;
function new();
endfunction
endgroup
endmodule

View File

@ -385,28 +385,21 @@
136 | cross a, b {
| ^~~~~
%Warning-COVERIGN: t/t_covergroup_unsup.v:154:4: Ignoring unsupported: covergroup
154 | covergroup cg_more extends cg_empty;
154 | covergroup cgArgs(int cg_lim);
| ^~~~~~~~~~
%Warning-COVERIGN: t/t_covergroup_unsup.v:157:4: Ignoring unsupported: covergroup
157 | covergroup cg_args(int cg_lim);
| ^~~~~~~~~~
%Warning-COVERIGN: t/t_covergroup_unsup.v:164:7: Ignoring unsupported: covergroup
164 | covergroup cov1 @m_z;
%Warning-COVERIGN: t/t_covergroup_unsup.v:161:7: Ignoring unsupported: covergroup
161 | covergroup cov1 @m_z;
| ^~~~~~~~~~
%Warning-COVERIGN: t/t_covergroup_unsup.v:164:23: Ignoring unsupported: coverage clocking event
164 | covergroup cov1 @m_z;
%Warning-COVERIGN: t/t_covergroup_unsup.v:161:23: Ignoring unsupported: coverage clocking event
161 | covergroup cov1 @m_z;
| ^
%Warning-COVERIGN: t/t_covergroup_unsup.v:165:10: Ignoring unsupported: coverpoint
165 | coverpoint m_x;
%Warning-COVERIGN: t/t_covergroup_unsup.v:162:10: Ignoring unsupported: coverpoint
162 | coverpoint m_x;
| ^~~~~~~~~~
%Warning-COVERIGN: t/t_covergroup_unsup.v:166:10: Ignoring unsupported: coverpoint
166 | coverpoint m_y;
%Warning-COVERIGN: t/t_covergroup_unsup.v:163:10: Ignoring unsupported: coverpoint
163 | coverpoint m_y;
| ^~~~~~~~~~
%Warning-COVERIGN: t/t_covergroup_unsup.v:164:18: Ignoring unsupported: covergroup within class
164 | covergroup cov1 @m_z;
| ^~~~
%Error: t/t_covergroup_unsup.v:169:23: Can't find definition of variable: 'cov1'
169 | function new(); cov1 = new; endfunction
| ^~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Warning-COVERIGN: t/t_covergroup_unsup.v:171:7: Ignoring unsupported: covergroup
171 | covergroup extends cg_empty;
| ^~~~~~~~~~
%Error: Exiting due to

View File

@ -151,10 +151,7 @@ module t (/*AUTOARG*/
}
endgroup
covergroup cg_more extends cg_empty;
endgroup
covergroup cg_args(int cg_lim);
covergroup cgArgs(int cg_lim);
endgroup
class CgCls;
@ -170,10 +167,15 @@ module t (/*AUTOARG*/
`endif
endclass
class CgEmb;
covergroup extends cg_empty;
endgroup
endclass
always @(posedge clk) begin
cg_more cov1 = new;
cg_empty cov1 = new;
`ifndef T_COVERGROUP_UNSUP_IGN
cg_args cov2 = new(2);
cgArgs cov2 = new(2);
`endif
if (cyc == 10) begin
$write("*-* All Finished *-*\n");

View File

@ -74,6 +74,8 @@ module Vt_debug_emitv_t;
if (the_ifaces[2].ifsig) begin
$write("");
end
#64'h1;
$write("After #1 delay");
end
end
bit [6:5] [4:3] [2:1] arraymanyd[10:11][12:13][14:15];

View File

@ -14,7 +14,7 @@ test.scenarios("vlt")
test.lint(
# We also have dump-tree turned on, so hit a lot of AstNode*::dump() functions
# Likewise XML
v_flags=["--lint-only --dumpi-tree 9 --dumpi-V3EmitV 9 --debug-emitv"])
v_flags=["--lint-only --dumpi-tree 9 --dumpi-V3EmitV 9 --debug-emitv --timing"])
output_vs = test.glob_some(test.obj_dir + "/" + test.vm_prefix + "_*_width.tree.v")

View File

@ -86,6 +86,7 @@ module t (/*AUTOARG*/
if (|downto_32[55+:3]) $write("");
if (|downto_32[60-:7]) $write("");
if (the_ifaces[2].ifsig) $write("");
#1 $write("After #1 delay");
end
bit [6:5][4:3][2:1] arraymanyd[10:11][12:13][14:15];

View File

@ -15,6 +15,6 @@ test.top_filename = "t/t_debug_emitv.v"
test.lint(
# We also have dump-tree turned on, so hit a lot of AstNode*::dump() functions
# Likewise XML
v_flags=["--lint-only --dumpi-tree 9 --dump-tree-addrids"])
v_flags=["--lint-only --dumpi-tree 9 --dump-tree-addrids --timing"])
test.passes()

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,118 @@
#!/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")
# Read expected source lines hit
expectedLines = set()
with open(root + "/src/V3DfgBreakCycles.cpp", 'r', encoding="utf8") as fd:
for lineno, line in enumerate(fd, 1):
line = line.split("//")[0]
if re.match(r'^[^#]*SET_RESULT', line):
expectedLines.add(lineno)
if re.match(r'^[^#]*MASK', line):
expectedLines.add(lineno)
if not expectedLines:
test.error("Failed to read expected source line numbers")
# 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"
nExpectedCycles = 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:
line = line.split("//")[0]
m = re.search(r'`signal\((\w+),', line)
if not m:
continue
nExpectedCycles += 1
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-break-cycles",
"-fno-dfg-post-inline",
"-fno-dfg-scoped",
"+incdir+" + test.obj_dir,
"-Mdir", test.obj_dir + "/obj_ref",
"--prefix", "Vref",
"-Wno-UNOPTFLAT"
]) # yapf:disable
# Check we got the expected number of circular logic warnings
test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt",
r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles)
# Compile optimized - also builds executable
test.compile(verilator_flags2=[
"--stats",
"--build",
"-fno-dfg-post-inline",
"-fno-dfg-scoped",
"--exe",
"+incdir+" + test.obj_dir,
"-Mdir", test.obj_dir + "/obj_opt",
"--prefix", "Vopt",
"-Werror-UNOPTFLAT",
"--dumpi-V3DfgBreakCycles", "9", # To fill code coverage
"-CFLAGS \"-I .. -I ../obj_ref\"",
"../obj_ref/Vref__ALL.a",
"../../t/" + test.name + ".cpp"
]) # yapf:disable
# Check all source lines hit
coveredLines = set()
def readCovered(fileName):
with open(fileName, 'r', encoding="utf8") as fd:
for line in fd:
coveredLines.add(int(line.strip()))
readCovered(test.obj_dir + "/obj_opt/Vopt__V3DfgBreakCycles-TraceDriver-line-coverage.txt")
readCovered(test.obj_dir + "/obj_opt/Vopt__V3DfgBreakCycles-IndependentBits-line-coverage.txt")
if coveredLines != expectedLines:
for n in sorted(expectedLines - coveredLines):
test.error_keep_going(f"V3DfgBreakCycles.cpp line {n} not covered")
for n in sorted(coveredLines - expectedLines):
test.error_keep_going(f"V3DfgBreakCycles.cpp line {n} covered but not expected")
# Execute test to check equivalence
test.execute(executable=test.obj_dir + "/obj_opt/Vopt")
test.passes()

View File

@ -0,0 +1,114 @@
// 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, width) wire [width-1:0] name;
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;
//////////////////////////////////////////////////////////////////////////
// Interesting user code to cover
//////////////////////////////////////////////////////////////////////////
`signal(GRAY_SEL, 3);
assign GRAY_SEL = rand_a[2:0] ^ 3'(GRAY_SEL[2:1]);
`signal(GRAY_SHIFT, 3);
assign GRAY_SHIFT = rand_a[2:0] ^ (GRAY_SHIFT >> 1);
`signal(GRAY_REV_SEL, 3);
assign GRAY_REV_SEL = rand_a[2:0] ^ {GRAY_REV_SEL[1:0], 1'b0};
`signal(GRAY_REV_SHIFT, 3);
assign GRAY_REV_SHIFT = rand_a[2:0] ^ (GRAY_REV_SHIFT << 1);
//////////////////////////////////////////////////////////////////////////
// Fill coverage
//////////////////////////////////////////////////////////////////////////
`signal(CONCAT_RHS, 2);
assign CONCAT_RHS[0] = rand_a[0];
assign CONCAT_RHS[1] = CONCAT_RHS[0];
`signal(CONCAT_LHS, 2);
assign CONCAT_LHS[0] = CONCAT_LHS[1];
assign CONCAT_LHS[1] = rand_a[1];
`signal(CONCAT_MID, 3);
assign CONCAT_MID[0] = |CONCAT_MID[2:1];
assign CONCAT_MID[2:1] = {rand_a[2], ~rand_a[2]};
`signal(SEL, 3);
assign SEL[0] = rand_a[4];
assign SEL[1] = SEL[0];
assign SEL[2] = SEL[1];
`signal(EXTEND, 8);
assign EXTEND[0] = rand_a[3];
assign EXTEND[3:1] = 3'(EXTEND[0]);
assign EXTEND[4] = EXTEND[1];
assign EXTEND[6:5] = EXTEND[2:1];
assign EXTEND[7] = EXTEND[3];
`signal(NOT, 3);
assign NOT = ~(rand_a[2:0] ^ 3'(NOT[2:1]));
`signal(AND, 3);
assign AND = rand_a[2:0] & 3'(AND[2:1]);
`signal(OR, 3);
assign OR = rand_a[2:0] | 3'(OR[2:1]);
`signal(SHIFTR, 14);
assign SHIFTR = {
SHIFTR[6:5], // 13:12
SHIFTR[7:6], // 11:10
SHIFTR[5:4], // 9:8
SHIFTR[3:0] >> 2, // 7:4
rand_a[3:0] // 3:0
};
`signal(SHIFTR_VARIABLE, 2);
assign SHIFTR_VARIABLE = rand_a[1:0] ^ ({1'b0, SHIFTR_VARIABLE[1]} >> rand_b[0]);
`signal(SHIFTL, 14);
assign SHIFTL = {
SHIFTL[6:5], // 13:12
SHIFTL[7:6], // 11:10
SHIFTL[5:4], // 9:8
SHIFTL[3:0] << 2, // 7:4
rand_a[3:0] // 3:0
};
`signal(SHIFTL_VARIABLE, 2);
assign SHIFTL_VARIABLE = rand_a[1:0] ^ ({SHIFTL_VARIABLE[0], 1'b0} << rand_b[0]);
`signal(VAR_A, 2);
wire logic [1:0] VAR_B;
assign VAR_A = {rand_a[0], VAR_B[0]};
assign VAR_B = (VAR_A >> 1) ^ 2'(VAR_B[1]);
`signal(REPLICATE, 4);
assign REPLICATE = rand_a[3:0] ^ ({2{REPLICATE[3:2]}} >> 2);
`signal(PARTIAL, 4);
assign PARTIAL[0] = rand_a[0];
// PARTIAL[1] intentionally unconnected
assign PARTIAL[3:2] = rand_a[3:2] ^ {PARTIAL[2], PARTIAL[0]};
endmodule

View File

@ -0,0 +1,9 @@
%Warning-UNOPTFLAT: t/t_dfg_true_cycle_bad.v:10:23: Signal unoptimizable: Circular combinational logic: 'o'
10 | output wire [9:0] o
| ^
... 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_dfg_true_cycle_bad.v:10:23: Example path: o
t/t_dfg_true_cycle_bad.v:12:22: Example path: ASSIGNW
t/t_dfg_true_cycle_bad.v:10:23: Example path: o
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/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')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,16 @@
// 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
`default_nettype none
module t(
output wire [9:0] o
);
assign o[1:0] = o[9:8];
assign o[3:2] = {o[0], o[1]};
assign o[7:4] = 4'(o[3:2]);
assign o[9:8] = o[5:4];
endmodule

View File

@ -1,4 +1,4 @@
%Error-UNSUPPORTED: t/t_disable.v:11:10: Unsupported: disabling fork by name
%Error-UNSUPPORTED: t/t_disable.v:11:10: Unsupported: disabling fork from within same fork
11 | disable foo;
| ^~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest

View File

@ -0,0 +1,8 @@
%Error: t/t_disable_bad.v:9:15: Can't find definition of block/task: 'abcd'
9 | disable abcd;
| ^~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Internal Error: t/t_disable_bad.v:9:7: ../V3LinkJump.cpp:#: Unlinked disable statement
9 | disable abcd;
| ^~~~~~~
... This fatal error may be caused by the earlier error(s); resolve those first.

16
test_regress/t/t_disable_bad.py Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,11 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
module t;
initial begin
disable abcd;
end
endmodule: t

View File

@ -0,0 +1,5 @@
%Error-UNSUPPORTED: t/t_disable_empty.v:12:7: disable isn't underneath a begin with name: 'block'
12 | disable block;
| ^~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.lint(verilator_flags2=['--timing'], fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,17 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
module t(/*AUTOARG*/);
initial begin
if (0) begin : block
end
disable block;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(timing_loop=True, verilator_flags2=["--timing"])
test.execute()
test.passes()

View File

@ -0,0 +1,28 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
module t (/*AUTOARG*/);
initial begin
begin : blk
int x = 0;
fork : fork_blk
begin
end
begin
x = 1;
#2;
x = 2;
end
join_none
#1;
disable fork_blk;
#2;
if (x != 1) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -0,0 +1,5 @@
%Error-UNSUPPORTED: t/t_disable_func_bad.v:19:13: Node of type FUNCREF 'increment_x' referenced by disable
19 | #1 disable increment_x;
| ^~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.lint(verilator_flags2=['--lint-only --timing'],
fails=True,
expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,23 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Antmicro.
// SPDX-License-Identifier: CC0-1.0
int x = 0;
function int increment_x;
x++;
return x;
endfunction
module t(/*AUTOARG*/);
initial begin
fork
increment_x();
#1 disable increment_x;
join
end
endmodule

Some files were not shown because too many files have changed in this diff Show More