Compare commits

...

205 Commits

Author SHA1 Message Date
Geza Lore 94a63dd407
Fix streaming concatenation uninitialized RMW access (#8143) 2026-08-18 12:03:51 +02:00
Geza Lore d9fb606869
Internals: Move DPI export AstScopeName out of statement list (#6280) (#8139)
AstScopeName is an AstNodeExpr, but V3Task stashed the DPI export scoping
marker into the enclosing AstCFunc's statement list (addStmtsp), placing an
expression in a statement position.

Give AstCFunc a dedicated 'scopeNamep' operand to hold the marker instead.
Part of #6280
2026-08-18 11:25:12 +02:00
Geza Lore 8baff6ae33
Internals: Don't wrap elaboration severity task in an AstInitial (#6280) (#8142)
An elaboration severity system task ($info/$warning/$error/$fatal used
as a module/generate/program/checker item), which is a module item,was
parsed as an AstInitial wrapping an AstElabDisplay. That put
AstElabDisplay (a non-statement) into the procedural statement list of
an AstInitial.

AstElabDisplay is evaluated by V3Width, which then deletes it. The
AstInitial wrapper is historic and has no effect.

Part of #6280
2026-08-18 11:19:50 +02:00
Geza Lore 7c6b225b7e
Internals: Make AstConstraintBefore an AstNodeStmt (#6280) (#8141)
AstConstraintBefore ('solve x before y') is a constraint item that appears
in constraint bodies alongside its siblings AstConstraintExpr,
AstConstraintUnique and AstConstraintIf, all of which are AstNodeStmt.
Part of #6280.
2026-08-18 11:19:40 +02:00
Geza Lore 2a9757bf38
Internals: Make AstCLocalScope an AstNodeStmt (#6280) (#8140)
AstCLocalScope wraps statements in an unnamed C++ scope ('{ ... }') and is
only ever placed in statement position. Part of #6280.
2026-08-18 11:19:27 +02:00
Kornel Uriasz 74d12c5b5e
Fix 'get-value' for empty constrained arrays (#8138)
Signed-off-by: Kornel Uriasz <kuriasz@antmicro.com>
2026-08-18 10:05:00 +02:00
Geza Lore d4a18d4dfb
Fix unordered data hazards in multi-threaded scheduling (#8133)
The OrderGraph used during V3Order step deliberately omits some variable
accesses from the dependency graph. E.g.: a read of a variable that is
in the reading block's own hybrid sensitivity list emits no edge, nor
does a read ignored due to a force/release, nor an access to a variable
marked 'ignoreSchedWrite' and friends. For serial mode that is fine, the
logic runs one block at a time. In parallel mode two such blocks can run
concurrently, and if one writes what the other reads, that is a data
race at runtime.

These accesses cannot be recovered from the graph edges. They are now
collected from the AST while the OrderGraph is built, and held by the
OrderLogicVertex performing them.

FixDataHazards is reworked around these access lists stored in
OrderLogicVertex, so it is now aware of all variable accesses the logic
makes, including those not encoded by the dependency graph edges. The
previous heuristic of fixing data hazards by merging same-rank MTasks is
removed. Additional edges are inserted instead to prescribe a fixed
ordering of conflicting MTasks. To insert edges without unduly
increasing the critical path, or introducing cycles, new edges are
added such that they preserve topological ordering, and they are
inserted between vertices sorted by critical path length. See algorithm
details in the code.

Also add a data hazard checker under '--debug-partition', reporting every
unordered accessor pair left in the final MTask graph.

This fixes the race demonstrated by t_sched_hybrid_hazard (#7913),
which is no longer expected to fail.

Under ThreadSanitizer over the vltmt tests: 17 failing before, 3 after,
with no regressions. The 3 remaining are different defects.
2026-08-18 08:50:50 +02:00
github action 96ea587df0 Apply 'make format' [ci skip] 2026-08-18 06:07:36 +00:00
Sunimali Rathnayake 2d8d8add42
Fix public table offsets larger than 32 bits (#8135) 2026-08-18 08:06:53 +02:00
Geza Lore 7dcd4e0b65
Improve MTask coarsening in multi-threaded scheduling (#8120)
This is a large refactor of the MTask graph and coarsening algorithm, in
prep for fixing the bug described in #7913, it can also improve the
resulting multi-threaded schedule.

Two major changes:

OrderMTaskGraph now maintains the critical paths of the MTasks through
mutation. There are 2 ways to mutate the graph, which are done via
methods on the graph itself: adding an edge (used during construction,
and will be used later during fixing data hazards), or merging an MTask
into another (used during contraction). All critical path measures are
automatically updated and propagated on any mutation, so no external
algorithm needs to maintain them explicitly.

The merge candidate scoreboard used during contraction is simplified to
remove deferral of updated scores. This simplifies the code and results
in a greedily more optimal schedule. (The previous tranched rescore was
an optimization to work around the previous std::set based scoreboard,
however since the algorithm now uses an efficient PairingHeap,
verilation time is not impacted by the more accurate scoring, while
yielding better results).

Combining these two into a single patch as the code is highly
interdependent and any one change without the other would be just a
noisy transit point with unclear performance implications. Together it
should be a clear improvement.

Also added a stronger validation step run with '--debug-partition',
which checks all invariants throughout the algorithms.
2026-08-17 23:14:52 +02:00
Yilou Wang ace84ef2b3
Fix simulation hang when the solver stops answering (Part2 of #7991) (#8129) 2026-08-17 13:01:41 +02:00
Matthew Ballance a3553e7bee
Add runtime support for covergroup cross-points (#7847) 2026-08-16 15:33:38 -04:00
Wilson Snyder e2f7755611 Tests: Fix whitespace 2026-08-15 07:08:53 -04:00
em2machine d2f62fda93
Fix use-after-free of captured interface typedef reference during parameter cloning (#8076) 2026-08-15 06:07:01 -04:00
em2machine 93401038c0
Fix reading another parameter resolves to 0 in cloned modules (#8086) (#8069) 2026-08-15 06:06:25 -04:00
github action 3334576b1d Apply 'make format' [ci skip] 2026-08-15 01:00:00 +00:00
Wilson Snyder 6d6d15e003 Tests: Fix side-effects in checkd randomize 2026-08-14 20:59:19 -04:00
Wilson Snyder d6b206cc7d Internals: Cleanup some VPI messages and fix test coverage 2026-08-14 19:47:45 -04:00
Wilson Snyder edd4cd4faf Tests: Cleanup false suppressions in t_dist_warn_coverage.py 2026-08-14 18:55:41 -04:00
Wilson Snyder 91216bdac1 Tests: Add uvm-2020-3.2-vlt 2026-08-14 18:31:23 -04:00
Wilson Snyder 92300ab675 Commentary: Changes update 2026-08-14 18:28:19 -04:00
Artur Bieniek 0b50390359
Fix COVERIGN on SVA goto repetition (#8118)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-08-14 13:52:10 -04:00
Geza Lore 621a67e819
Fix R/W references to random builtin function seeds (#8112) 2026-08-14 12:29:12 -04:00
Adam Kostrzewski 75a776c516
Fix invalid typedef linkdot (#8106)
Signed-off-by: Adam Kostrzewski <akostrzewski@internships.antmicro.com>
2026-08-14 12:28:35 -04:00
Demin Han 743e0f4a82
Fix the clang compilation using pch (#8105)
Signed-off-by: Demin Han <demin.han@qq.com>
2026-08-14 12:19:28 -04:00
Adam Kostrzewski 649598c74c
Fix unintended side-effect insertion on associative array read (#8077) 2026-08-14 15:23:44 +02:00
Geza Lore 0a3657517d
Optimize temporary insertion for single bit replicates in DFG (#8110)
A '{N{bit}}' mask expands to a bit vector where every word is the same
value, obtained by negating the replicated bit. Recomputing that at each
use is no more expensive than loading it
2026-08-14 14:58:31 +02:00
Kornel Uriasz 2957939d4b
Throw unsupported when reduction constraining dynamic subarray (#8111)
Signed-off-by: Kornel Uriasz <kuriasz@antmicro.com>
2026-08-14 07:56:31 -04:00
Yilou Wang abf199cf0e
Fix randc cycling in a class that also uses solve...before (Part1 of #7991) (#8055) 2026-08-14 07:16:44 -04:00
Marco Bartoli 7da43d8302
Support class handle covergroup arguments (#8071) 2026-08-13 20:41:39 -04:00
Wilson Snyder c8853d1507 Tests: Reformat some recent tests to mostly verilog-format standard. No test functional change. 2026-08-13 18:59:37 -04:00
Wilson Snyder edf43ae76f Tests: Rename `inf` to avoid VAMS errors 2026-08-13 18:59:37 -04:00
Wilson Snyder 63478f2946 Commentary: Changes update 2026-08-13 18:59:37 -04:00
Artur Bieniek 95721f5040
Optimize NFA ring buffer clear operation using lazy invalidation (#8101)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-08-13 18:54:57 -04:00
Geza Lore 1397d5d01e
Optimize VL_WORDS_I/VL_BYTES_I (#8095) 2026-08-13 14:44:04 -04:00
github action 098f375e9d Apply 'make format' [ci skip] 2026-08-12 23:13:40 +00:00
Sumanth Kadiyala 44fe96b2da
Add `+verilator+assert+lock` to ignore RTL assert control statements (#8086)
Co-authored-by: Sumanth Kadiyala <sumanth@hudson-trading.com>
2026-08-12 19:13:02 -04:00
Artur Bieniek 0ebb92da26
Fix hierarchical class scope resolution (#8094)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-08-12 15:29:04 -04:00
Artur Bieniek 73bcf5e0db
Fix NFA range ring outgoing bit selection (#8061 repair) (#8092)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-08-12 12:28:32 -04:00
Artur Bieniek e962efca12
Fix untyped dtype error on sampling functions with property arguments (#8060)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-08-12 05:16:48 -04:00
Wilson Snyder 0b51926fff Commentary: Changes update 2026-08-11 20:05:01 -04:00
Wilson Snyder 08cd5fe30b Tests: Reformat some recent tests to mostly verilog-format standard. No test functional change. 2026-08-11 20:04:45 -04:00
Nikolai Kumar da0c31926e
Fix forceable unpacked array element force with procedural assign (#8084) (#8085) 2026-08-11 17:09:33 -04:00
Todd Strader e96cfc5e56
Tests: More VPI simulator support (#8083) 2026-08-11 15:14:01 -04:00
Igor Zaworski 2ef131f7ac
Fix scheduling of variables written in non-inlined functions in suspendable processes (#7924)
Repair of reverted #7836

Signed-off-by: Igor Zaworski <izaworski@antmicro.com>
2026-08-11 13:18:18 +01:00
Igor Zaworski 945ea27fec
Fix undefined symbol solver error (#8080)
Signed-off-by: Igor Zaworski <izaworski@antmicro.com>
2026-08-11 07:33:37 -04:00
Artur Bieniek 490bd38962
Optimize bounded always properties using ring buffers (#8061)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-08-10 08:45:50 -04:00
Bartłomiej Chmiel 121fc62aff
Fix VPI cbValueChange for 1-bit select (#8063) 2026-08-10 07:52:18 -04:00
Paul Campbell 5d888d2ac0
Add SIMILARNAME warning when variables have names that only differ in lexical case (#7992) (#8020) 2026-08-10 06:57:58 -04:00
Kornel Uriasz 38259d11b6
Fix skipping non-constrained enum constraining when inside object of other class (#8075)
Signed-off-by: Kornel Uriasz <kuriasz@antmicro.com>
2026-08-10 06:52:14 -04:00
Tyrone Marhguy 86fa00416d
Fix Linux peak memory stat to use VmHWM (#8022) (#8070) 2026-08-09 15:37:57 -04:00
Marco Bartoli 645b8cdf24
Support embedded covergroup clocking events (#8028) 2026-08-09 13:55:04 -04:00
Yogish Sekhar dde6aa34ce
Fix FSM coverage on empty reset branches (#8005) (#8064) 2026-08-08 19:44:46 +01:00
Wilson Snyder 7601011bba Internals: Cleanup some user() comments. No functional change.
Fixes #8048.
2026-08-08 13:05:05 -04:00
Wilson Snyder 18844537bd Commentary: Changes update 2026-08-08 12:58:32 -04:00
Kamil Danecki 7e80ea6577
Support `default clocking` (#7984) 2026-08-07 13:21:18 +01:00
Nikolai Kumar 82dab79096
Fix wildcard equality against a 4-state constant in an assertion (#8056) (#8057) 2026-08-07 07:48:51 +01:00
Artur Bieniek 1a4bcac666
Support `weak`/`strong` keywords in property expressions (#8054)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-08-06 07:48:00 +01:00
Artur Bieniek e413e67ab3
Fix force helpers in multiply instantiated modules (#8047)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-08-05 16:47:45 -04:00
Artur Bieniek b1ec180a3b
NFA simplification: do not pass arguments which values are derivable (#8053)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-08-05 12:40:12 -04:00
Marco Brambilla 1f164d7ee0
Fix wait fork trigger temporary split across generated functions (#7985) (#7986)
splitCheck() cuts a function's top level statement list on node count
alone, ignoring AstVar declarations in that list. localizeVars() puts the
dynamic trigger temporaries there, which a 'wait fork' reaches, so the
declaration could land in one sub-function and its references in another.
Sub-functions are emitted as separate C++ functions, so the output failed
to compile:

    error: '__Vtrigprevexpr_h5d9da2ce__0' was not declared in this scope

V3InlineCFuncs could also inline the sub-function holding the declaration
and free the AstVar while other sub-functions still referenced it, which
--debug reports as a broken link and which segfaults an -O3 build.

The existing "Can't split function with local variables" assertion only
checked AstCFunc::varsp(), not declarations among the statements.

Only allow a sub-function boundary where it does not separate a local
declaration from a reference to it. This keeps the temporaries function
local, as #6859 requires, while restoring the guarantee #5822 made that
splitting cannot orphan them.

Signed-off-by: Marco Brambilla <marco@hairyotter.com>
2026-08-05 17:33:58 +01:00
Kornel Uriasz 3fa88ae7f3
Fix array size references in constraints (#8040)
Signed-off-by: Kornel Uriasz <kuriasz@antmicro.com>
2026-08-05 12:07:28 -04:00
Nikolai Kumar 351d67c81e
Fix pin to non-existent port with PINNOTFOUND suppressed (#8051) (#8052) 2026-08-05 12:00:32 -04:00
Sumanth Kadiyala 472a131df7
Fix nondeterminism in trace stage (#8046)
Co-authored-by: Sumanth Kadiyala <sumanth@hudson-trading.com>
2026-08-05 02:51:51 -04:00
Bartosz Skorowski e6c613f28d
Fix write access to dynamic arrays (#8043)
Signed-off-by: Bartosz Skorowski <bskorowski@internships.antmicro.com>
2026-08-04 13:27:08 -04:00
Artur Bieniek e4b6bac50a
Fix mid-range consecutive repetition rejects (#8041)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-08-04 17:50:33 +01:00
Pawel Klopotek 3c4ea47e02
Fix conditional expression with parameterized classes (#8039)
Signed-off-by: Pawel Klopotek <pklopotek@internships.antmicro.com>
2026-08-04 15:50:12 +01:00
github action 5bd8c49448 Apply 'make format' [ci skip] 2026-08-04 08:37:04 +00:00
JOTEGO ba8e4f9dec
Fix gate deduplication with function arguments (#8038) 2026-08-04 04:36:25 -04:00
Kornel Uriasz 15b75c7c68
Fix skipping optimization for unpacked data types (#8031)
Signed-off-by: Kornel Uriasz <kuriasz@antmicro.com>
2026-08-04 01:48:15 -04:00
Yilou Wang 925a6640d1
Fix queued $finish/$stop request thread ordering (#7946 prep) (#7952) 2026-08-04 01:46:54 -04:00
Geza Lore b42d7c2d9e Internals: Move critical path propagators to MTaskGraph from Contraction
Code movement only prep for further work. No functional change, output
should be identical.
2026-08-03 17:35:10 +01:00
Geza Lore d58f512a85
Internals: Remove stepCost use in MT scheduling (#8032)
MT scheduling used to round MTask costs to the nearest 5% to supposedly
reduce the amount of work required for propagating critical path change
updates. Profiling shows this doesn't actually save any verilation time,
as the floating point arithmetic itself is costly, and the propagation
savings only kick in when merging small tasks into large ones, which is
not the common case. (The float arithmetic also made this step
non-reproducible across host architectures and libm versions  due to
numerical variance.)

Remove and use precise costs instead.
2026-08-03 17:34:44 +01:00
Nick Brereton 1356c15e44
Fix use-after-free in V3LinkDotIfaceCapture (#7943) 2026-08-03 15:55:30 +01:00
dependabot[bot] 3c47e5735a
CI: Bump docker/login-action from 4.5.1 to 4.6.0 in the everything group (#8033) 2026-08-03 15:55:00 +01:00
Geza Lore 0750e18ad0
Internals: Remove ordering before MTask contraction (#8019)
As the removed comment suggests, this is dubious and RTLMeter suggests
it makes no measurable difference on average (some designs improve, some
regress small amounts, on average ~1.0x)

Prep for further fixes that will remove the pre ranking.
2026-08-03 10:44:51 +01:00
David Garau a24dd8b2dc
Fix generic interface param resolution in module/cell parameterization (#7970) (#7971) 2026-08-03 05:18:59 -04:00
Yilou Wang 2022bacd38
Fix timing controls in interface tasks called via virtual interface (#7959) 2026-08-03 05:15:07 -04:00
Aisha 29fd2cf90d
Add MULTIDRIVEN checks for clocking block outputs (#7987) 2026-08-03 04:54:43 -04:00
Josep Sans daf5826610
Fix assignment pattern key constant expressions (#8029) (#8030) 2026-08-02 12:53:43 +01:00
Krzysztof Bieganski 66808eec27
CI: Make light debug opt-in in CI build script (#8013) (#8027) 2026-08-02 02:34:38 -04:00
BRDR LIFE a3e7f5103d
Tests: Check dist bucket draw is per-element in foreach (#8023) 2026-08-01 22:37:55 +05:30
Marco Bartoli 7e116ca601
Support embedded covergroup member references (Fix #7749) (#8015) 2026-08-01 02:13:53 -04:00
github action 56accae45e Apply 'make format' [ci skip] 2026-08-01 03:38:21 +00:00
BRDR LIFE 967b1bae57
Fix unique constraint crash, guards, and size (#8018) (#8018) 2026-07-31 23:37:53 -04:00
BRDR LIFE 6b3aebfa32
Fix dist inside a foreach nested in a constraint if (#8016) 2026-07-31 23:35:01 -04:00
Yilou Wang 704c5a1c96
Fix gcov dump on warnings discarding later coverage counts (#8017) 2026-07-31 12:08:05 -04:00
Geza Lore 781f6d90bf
Internals: Refactor MT scheduling (#8012)
Prep for fixing test added in #7913.

This is a large scale no functional change refactor, however, MT output
is perturbed as tied scores will be broken differently due to ordering
changes (still deterministic).

Split multi-threaded scheduling out of the monolithic
V3OrderParallel.cpp, into relatively independent parts, simplify the
data structures, and drop redundant or unused code.

New translation units:
- V3OrderMTaskGraph.h/.cpp: OrderMTaskGraph, the graph of LogicMTask
  vertices and MTaskEdge edges. LogicMTask and MTaskEdge no longer
  depend on the coarsening algorithm's merge candidate types;
  per-algorithm auxiliary data is attached externally via the vertex and
  edge user pointers.
- V3OrderMTaskFixHazards.cpp: data hazard fixup, was FixDataHazards.
- V3OrderMTaskContraction.cpp: graph coarsening, was Partitioner
  together with PropagateCp and the merge candidate types.
- V3OrderParallel.cpp: now just the partitioning driver and ExecMTask
  graph construction.

Data structure changes:
- Delete V3Scoreboard.h/.cpp. The generic template had a single user, now
  a file-local MergeCandidateScoreboard in V3OrderMTaskContraction.cpp.
- Merge candidates are now MergeCandidate/SiblingMC/EdgeMC, distinguished
  by a bit in the candidate id rather than by a vtable, and allocated by
  the scoreboard, which owns their lifetime. This removes the multiple
  inheritance previously used by MTaskEdge.

Move `hashGraphDebug` which prints the hash of a graph's shape for debugging
to generic `V3Graph::hashGraphDebug`.

Removed (can be added back later):
- Unnecesasry self tests that force special data stucture requirements.
- Per stage --stats output under --debug. (Final figures still reported.)
- Various debug dumps
2026-07-31 15:03:26 +01:00
BRDR LIFE 40323cc02c
Fix dist weights ignored when an item is not a literal (#8003) 2026-07-31 10:54:38 +05:30
BRDR LIFE 9cddf46932
Fix internal error forcing an array element read at a run-time index (#8004) 2026-07-30 23:57:15 -04:00
24bit-xjkp 589c9b9433
Fix missing verilator_coverage in cmake install (#8008) (#8011)
Fixes #8008.
2026-07-30 23:13:07 -04:00
Yilou Wang 99b71a37b7
Internals: Refactor randomization next and nextPhased into shared helpers (#7991 prep) (#8009). No functional change intended. 2026-07-30 21:41:37 -04:00
Artur Bieniek b70696fc0a
Fix $finish continuing event loop (#7267) (#7950)
Fixes #7267,
2026-07-30 21:39:21 -04:00
Marco Bartoli 4b770cffb4
Fix cross module disable (Part 5 of #7857) (#8006) 2026-07-30 14:56:07 +01:00
github action 42e043cb01 Apply 'make format' [ci skip] 2026-07-30 10:15:15 +00:00
24bit-xjkp c5c3c45102
Internals: Remove static inline functions in headers (#7988) (#8007) 2026-07-30 15:44:40 +05:30
Marco Bartoli c3be3c8050
Fix nested named fork disable propagation (Part 4 of #7857) (#8001) 2026-07-29 14:49:34 +01:00
Gilberto Abram cac2c3df13
Fix MULTIDRIVEN/PROC warning suppression via lint_off (#8000) 2026-07-28 21:50:19 -04:00
BRDR LIFE 2e4dbb846c
Fix force not substituted into an unpacked array index (#8002) 2026-07-29 06:58:06 +05:30
Yilou Wang b5fa4212b0
Fix UNSATCONSTR reporting constraint indices above 9 (#7995) 2026-07-28 18:25:19 -04:00
github action c23b0ee972 Apply 'make format' [ci skip] 2026-07-28 21:32:05 +00:00
Marco Bartoli 48e575b885
Fix self-disable of named blocks with forks (Part 3 of #7857) (#7996) 2026-07-28 22:31:32 +01:00
BRDR LIFE 5fd1c93d43
Fix randomize() with shadowing a user variable named 'item' (#7993) (#7994)
Fixes #7993.
2026-07-28 12:29:45 -04:00
spomatasmd a6f4dd031f
Fix DfgPeephole miscompile of nested shifts with overflowing shift amount (#7977)
The REPLACE_SHIFTL_SHIFTL and REPLACE_SHIFTR_SHIFTR peephole optimizations
fold '(a << b) << c' into 'a << (b + c)' (and likewise for '>>'), but computed
'b + c' in the width of the shift-amount operand. When 'b + c' overflows that
width it wraps around, producing a too-small shift amount and a wrong result.

Compute the sum one bit wider than the amounts so it cannot overflow.

Fixes #7955
2026-07-28 10:42:51 +01:00
Marco Bartoli 73b3ca7d2c
Fix immediate disable of fork join branches (#7856) (#7931) 2026-07-28 10:34:02 +01:00
dependabot[bot] de4de1c3f8
CI: Bump the everything group with 2 updates (#7989) 2026-07-27 19:52:43 +05:30
Wilson Snyder f8fb1d6646 Fix SYNCASYNCNET false positive with changed non-edge logic (#7980).
Fixes #7980.
2026-07-24 09:53:36 -04:00
Wilson Snyder d2de6b42cf * Fix $fgets being mis-optimized away (#7976).
Fixes #7976.
2026-07-24 09:26:01 -04:00
Wilson Snyder 370a40348b Commentary: Changes update 2026-07-24 09:20:25 -04:00
Bartosz Skorowski ee152be17b
Fix lost writes when select width exceeds variable width (#7975)
Signed-off-by: Bartosz Skorowski <bskorowski@internships.antmicro.com>
2026-07-24 08:58:28 -04:00
github action a799fedea6 Apply 'make format' [ci skip] 2026-07-24 12:56:19 +00:00
Aisha 8f8b5ade55
Add MULTIDRIVENPROC warning for signals driven by multiple plain always blocks (#7968) 2026-07-24 08:54:09 -04:00
Jakub Michalski bb0300f605
Fix table optimization causing not contextually convertible to bool error (#7983)
Signed-off-by: Jakub Michalski <jmichalski@antmicro.com>
2026-07-24 08:50:03 -04:00
Kornel Uriasz 7d3021c34d
Fix varRef scope in unique on dynamic array (#7981)
Signed-off-by: Kornel Uriasz <kuriasz@antmicro.com>
2026-07-23 15:56:51 -04:00
Wilson Snyder e0e0fadacc Tests: Update gold files 2026-07-22 12:59:20 -04:00
Igor Zaworski f511f6c34d
Add FSM arc coverage to verilator_coverage .info output
Signed-off-by: Igor Zaworski <izaworski@antmicro.com>
2026-07-22 12:26:14 -04:00
Igor Zaworski 0a855fce47
Add FSM state coverage to verilator_coverage .info output (#7973) 2026-07-22 12:24:02 -04:00
Pawel Klopotek f149dd304c
Add error when scalar is passed to array task argument (#7948) 2026-07-22 07:54:58 -04:00
Philip Axer 8113f1df7a
Fix randomization of unreferenced rand members of unpacked structs (#7910) (#7911)
Fixes #7910.
2026-07-21 07:42:04 -04:00
Yilou Wang b34f685f70
Fix dist constraint on a frozen variable failing randomization (#7878) 2026-07-21 06:39:09 -04:00
Yilou Wang f22676eea9
Fix solve-before dropping all soft constraints in randomize() (#7963) 2026-07-20 14:55:49 -04:00
Patrick Creighton cdfff86c2d
Support delayed tristate gates (#7960) (#7961)
Fixes #7960.
2026-07-20 09:21:23 -04:00
Bartosz Skorowski 176e333774
Fix lambda parameter types in queue min and max (#7962)
Signed-off-by: Bartosz Skorowski <bskorowski@internships.antmicro.com>
2026-07-20 06:53:40 -04:00
Wilson Snyder 3924fe9c18 Fix uncaught type error leading to invalid C++ output (#7814).
Fixes #7814.
2026-07-19 13:32:53 -04:00
Wilson Snyder 3813249826 Commentary: Changes update 2026-07-19 13:31:48 -04:00
Mateusz Gancarz 185a49e628
Fix unlinked error with function call in derived class parameter (#7923)
Signed-off-by: Mateusz Gancarz <mgancarz@antmicro.com>
2026-07-19 09:55:29 -04:00
Yilou Wang f7ce38dbc1
Tests: Pre-pull for #7946 (#7953) 2026-07-18 21:01:53 -04:00
Yilou Wang 22050b97a7
Internals: Refactor dist constraint lowering helpers from lowerDistConstraints (#7878 prepull) (#7954) 2026-07-17 11:42:58 -04:00
Adam Kostrzewski 7e1efe6d76
Support dynamic containers in unique constraints (#7947)
Signed-off-by: Adam Kostrzewski <akostrzewski@internships.antmicro.com>
2026-07-17 10:55:14 -04:00
Eryk Szpotański f39c1799d2
Commentary: Fix the verilator_coverage -filter-type documentation (#7949)
Signed-off-by: Eryk Szpotanski <eszpotanski@antmicro.com>
2026-07-17 08:18:43 -04:00
Pawel Klopotek cf27c63b2d
Fix type resolution when a local name shadows a type name (#7915) 2026-07-17 08:10:23 -04:00
Artur Bieniek 796d5174f3
Fix sampled-value clocks in repeated assertions (#7944) 2026-07-16 13:07:53 -04:00
Patrick Creighton cc1b36bafd
Fix FSM detection of coverage-only branches (#7941) (#7942)
Fixes #7941
2026-07-16 09:48:36 -07:00
Artur Bieniek 255bcff3ac
Support [*N:$] consecutive repetition (#7940)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-07-16 09:29:30 -04:00
Philip Axer 877fc1e812
Fix dropping variable writes across opaque calls (#7921) (#7933)
Fixes #7921.
2026-07-15 11:03:04 -04:00
Andrii affe4cde57
Fix class parameter resolution (#7935) 2026-07-15 10:56:22 -04:00
Kornel Uriasz 232b2eb04c
Fix dynamic array handling in solve...before (#7922) 2026-07-15 10:48:54 -04:00
Nick Brereton baf012cf6a
Fix streaming concat as output-port lvalue into unpacked struct (#7918) 2026-07-15 10:46:25 -04:00
Nick Brereton 25377b20af
Optimize VPI symbol registration and scope construction (#7936) 2026-07-15 10:31:42 -04:00
Kornel Uriasz d9fdc20492
Internals: V3Randomize.cpp cleanup (#7937). No functional change. 2026-07-15 05:32:56 -07:00
Wilson Snyder 5ead758f28 Internals: Use addNextNull. No functional change intended. 2026-07-14 19:58:25 -04:00
Wilson Snyder 34603ebd60 Commentary: Changes update 2026-07-14 19:55:50 -04:00
Igor Zaworski 3b43b304a3
Add error on static virtual functions (#7932) 2026-07-14 05:12:26 -07:00
Marco Bartoli 3afb7e1a21
Internals: Refactor disable process queue plumbing (#7919) 2026-07-13 21:25:35 -04:00
Jeffrey Song e24efae5a0
Fix stream concatenation in init array patterns (#7890 repair) (#7928) 2026-07-13 20:29:19 -04:00
Jeffrey Song 4262aea87c
Internals: Use snprintf for random unique indexes (#7925) 2026-07-13 11:35:22 -07:00
Nick Brereton dde8de0a0d
Fix crash streaming an unpacked array of unpacked structs (#7917) 2026-07-11 20:31:01 -07:00
Yilou Wang 7ca78a75e7
Fix scoped randomize with array members under rand_mode (#7877) 2026-07-11 20:28:49 -07:00
Yilou Wang ec124a0905
Fix mid-window disable iff (#7792) (#7869)
Fixes #7792
2026-07-11 20:44:56 -04:00
Yilou Wang ca83bc50bf
Fix solve-before over array variables failing randomization (#7876) 2026-07-11 20:44:37 -04:00
Geza Lore 52287c025f
Tests: Add TSan and failing multi-threaded data race test (#7913)
PR #7902 uncovered a pre-existing bug in multi-threaded scheduling,
where we can end up with an un-ordered R-W hazard in the MTask graph,
resulting in non-deterministic runtime behaviour.

This is extremely hard to actually trigger on a small example, so using
ThreadSanitizer to flag it, which can identify the race reliably.

In this patch:
- Add configure and `verilator --get-supported TSAN` to check if the
  configured compiler supports ThreadSanitizer
- Add a --tsan option to the test driver.py which builds the test with
  thread sanitizer (similar idea to --gdbsim).
- Add a tests.enable_tsan() method to allow turning on TSan in the test
  Python file.
- Add a suppressions file that waives TSan errors in the runtime library
- Finally add `t_sched_hybrid_hazard` that demonstrates the data race
  triggered after #7902. This is currently expected failing, fix later.

With the suppression, there are 17 vltmt tests failing due races in the
generated code. (Using `driver.py --vltmt --tsan --quiet -j0`)
2026-07-10 14:34:51 +01:00
Yogish Sekhar 3c7727a8f4
Fix spurious FSM COVERIGN on datapath comparisons (#7900) (#7908)
Fixes #7900.
2026-07-10 09:00:17 -04:00
Wilson Snyder 3191d98391 Commentary: Changes update 2026-07-10 08:43:12 -04:00
Wilson Snyder 3bd04a70bd Commentary (#7916) 2026-07-10 08:38:01 -04:00
Artur Bieniek feb2fe0d94
Fix release of forced port nets (#7907) (#7901)
Fixes# 7901

Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-07-10 11:35:06 +01:00
Artur Bieniek b211af9d24
Fix independent force of multiply instantiated signals (#7905)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-07-10 11:31:08 +01:00
Kornel Uriasz 9740203485
Fix VL_TO_STRING function for array of structs (#7912)
Signed-off-by: Kornel Uriasz <kuriasz@antmicro.com>
2026-07-09 08:40:24 -07:00
Adam Kostrzewski a21bd8a417
Fix VlQueue falling into wrong template spec (#7914) 2026-07-09 07:33:28 -07:00
github action 1d42c45a34 Apply 'make format' [ci skip] 2026-07-09 12:46:08 +00:00
Jaeuk Lee 38895f8f29
Fix $display accepting streaming concat arguments (#7663) (#7890)
Fixes #7663.
2026-07-09 08:41:40 -04:00
Wilson Snyder f316ec0d66 Revert #7836: Fix scheduling of variables written in non-inlined functions in suspendable processes (#7836) 2026-07-08 14:45:05 -04:00
Artur Bieniek 671ff628ff
Tests: Fix t_force_release to reflect intent (#7906)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-07-08 19:29:49 +01:00
Patrick Creighton c7e8075972
Fix --coverage on labelled inline assert/cover property (#7898) (#7904)
Fixes #7898
2026-07-08 09:18:33 -07:00
Jakub Michalski 646dcd3838
Support user-provided DPI-C function declarations (#7626) (#7893)
Fixes #7626.
2026-07-08 05:22:24 -07:00
Jakub Michalski 93d0443998
Fix memory leak in VerilatedFst::close() (#7899)
Signed-off-by: Jakub Michalski <jmichalski@antmicro.com>
2026-07-08 08:48:17 +01:00
Artur Bieniek 6c20fdb7bd
Optimize assertion NFAs using bit-vector ring buffers (#7885)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-07-07 19:26:50 -04:00
Geza Lore e743838d09
Internals: Remove DfgGraph::clone (#7896)
Only used by V3DfgBreakCycles, which today either improves the graph, or
leaves it unchanged. Remove unnecessary complexity.
2026-07-07 20:51:40 +01:00
Yilou Wang f1a8192a36
Fix mixed-width inside and dist range bounds failing randomization (#7875) 2026-07-07 11:26:19 -07:00
Matthew Ballance 87d77f0133
Fix internal error for coverpoints that reference a covergroup formal parameter (#7853 partial) (#7889) 2026-07-07 11:42:39 -04:00
Igor Zaworski aca58ca90f
Fix scheduling of variables written in non-inlined functions in suspendable processes (#7836)
Signed-off-by: Igor Zaworski <izaworski@antmicro.com>
2026-07-07 12:34:24 +01:00
Artur Bieniek a5f4d40901
Fix range delays with parameter bounds (#7882)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
2026-07-06 13:13:11 -04:00
Yilou Wang 8ed0acf1ec
Support a sequence used as an event control (#7797) (#7846) 2026-07-06 13:12:01 -04:00
Geza Lore 39b646349f
Add --enable-light-debug configure option (#7886)
Change default debug build to use -O0 instead of -Og as the latter is
still not great debugging experience. Use -Og -gz, plus the new -ggdb1,
which together significantly cut down debug info size, to save CI cache
space, while still enabling -gdbbt tests to work.
2026-07-06 18:02:37 +01:00
Geza Lore 56b0b679c6
CI: Disable ccache on coverage jobs (#7887)
gcc --coverage is unsupported by ccache and results in a bypass. Don't
save unnecessary caches.
2026-07-06 17:05:14 +01:00
dependabot[bot] 6baea8d846
CI: Bump the everything group with 5 updates (#7884) 2026-07-06 09:50:16 -04:00
Geza Lore fcc7acbfc6
CI: Factor out artifact based caching as a reusable action (#7883) 2026-07-06 14:16:06 +01:00
Geza Lore bf4c72f93b
CI: Squash macos brew warning (#7881) 2026-07-06 11:43:55 +01:00
Geza Lore c78a342bdc CI: Don't Python make venv on cache hit 2026-07-06 11:30:59 +01:00
Geza Lore c2ed0aab00
CI: Use ci-install.bash for lint-py dependencies (#7880) 2026-07-06 11:27:58 +01:00
Geza Lore 5563b3b6f8
CI: Cache Python venv (#7879)
This is smallish and mostly static, should cache well, avoid setup cost.
2026-07-06 11:03:35 +01:00
Geza Lore 5750c888a6
CI: Improve ccache handling in build and test jobs (#7874)
Cache key isolation: Include the build flavour (ccwarn/asan/gcov) in the
build ccache key, so the coverage (gcov), asan, and RTLMeter
(ccwarn-off) configurations no longer share and thrash the single
'<runs-on>-<cc>' bucket, nor warm-start from each other's (near-useless)
caches.

Prune stale entries instead of a fixed size cap: After building/testing,
evict ccache entries this run did not touch (--evict-older-than, based
on the run's own duration).  This bounds the saved cache to the current
working set, so CCACHE_MAXSIZE and CCACHE_LIMIT_MULTIPLE are no longer
needed and are removed (ccache falls back to its 5G default ceiling, but
is realistically much smaller).

ccache configuration:
- Drop CCACHE_COMPRESS (already the default in ccache 4.x).
- Add CCACHE_COMPILERCHECK=content, so a changed compiler mtime on a rebuilt
  ephemeral runner image does not invalidate the whole restored cache.
- Hoist all ccache-related variables (CACHE_BASE_KEY, CCACHE_*, CXX) to the
  workflow-level env.

Remove dead ccache scaffolding:
- reusable-lint-py: the job never compiles or caches; drop its ccache env.
- reusable-rtlmeter-run: remove the disabled persistent-cache step and its
  storage variables; keep CCACHE_DISABLE=1, since caching must stay off to
  keep RTLMeter timings representative.

Store test job ccache as artifacts for reuse
2026-07-06 01:44:32 +01:00
Geza Lore 8b564f509b CI: Remove macos-15 gcc job
On macOS, gcc is an alias for clang, so this job was effectively the
same as the clang job.
2026-07-05 21:18:36 +01:00
Geza Lore c7c778f109 CI: Improve Pages job
Code coverage and RTLMeter complete (concluding 'skipped') on branch
pushes where they do no work, and each completion fires the Pages
workflow_run trigger. Rebuilding and redeploying master's pages for
these no-op events is wasteful.

Gate the build job so a workflow_run whose upstream conclusion is
'skipped' does nothing; deploy and notify need build, so they
cascade-skip.

Route such no-op runs to their own throwaway concurrency group. In the
shared 'pages' group they would, as the newest queued run, cancel a
pending real run and then deploy nothing (concurrency keeps only the
in-progress and newest pending run). Real runs keep sharing 'pages' and
serialize as before.

Add retry to deploy step to reduce flakyness due to the Pages back end.

Fix trigger on workflow file changes.
2026-07-05 21:11:45 +01:00
Geza Lore cbd78e689c
CI: Cleanup Windows build (#7873)
The Windows build never actually used ccache; the '.ccache' directory
only ever held the win_flex_bison install, and cmake/MSVC did no compiler
caching. Clean this up:

- Drop the CCACHE_* environment variables from the job (meaningless here).
- Store win_flex_bison in a dedicated 'win_flex_bison' directory instead of
  the misleadingly named '.ccache', and cache just that (static key, as it
  is a fixed third-party tool).
- Set WIN_FLEX_BISON inside ci-win-compile.ps1 as an absolute path, rather
  than in the workflow, making the script self-contained.
- Use the runner's processor count for build parallelism instead of a fixed
  '-j 3', in both ci-win-compile.ps1 and ci-win-test.ps1.
- Remove the verilator-win.zip archive and upload steps; nothing consumes
  that artifact.
- Build Verilator with the Ninja generator from an MSVC developer shell
  instead of MSBuild. MSBuild only parallelizes across projects, and
  Verilator is a single project, so it compiled serially; Ninja keeps all
  cores busy. Also compile with /Od: this job only checks that Verilator
  builds and can verilate an example, so an optimized binary is not needed
  and the otherwise-dominant optimizer time is wasted.
2026-07-05 19:15:09 +01:00
Geza Lore 8d0c75171c
CI: Unify regression and RTLMeter build jobs to one (#7872)
- Merge reusable-rtlmeter-build.yml into reusable-build.yml, selected by
  an 'install' flag: archive the installed Verilator for RTLMeter, or
  the whole repository tree for the test jobs.
- Align reusable-test.yml input arguments with  reusable-build.yml
- Pass build/test options as booleans instead of 0/1.
- Inline the single-entry build matrices, and hoist matrix keys that are
  constant across a job.
2026-07-05 18:52:55 +01:00
Geza Lore 6e06cd18da
CI: Decouple build and test into separate scripts, simplify workflows (#7871)
- Split the monolithic ci-script.bash into ci-build.bash and
  ci-test.bash, with shared setup in ci-common.bash.
- Pass job parameters as explicit command-line options rather than
  environment variables (--compiler, --prefix, --suite, --asan,
  --gcov, --reloc, --ccwarn).
- Detect the host OS and distribution at runtime (uname,
  /etc/os-release) instead of the legacy Travis-era CI_OS_NAME,
  CI_RUNS_ON and CI_BUILD_STAGE_NAME variables.
- Remove dead code: FreeBSD support, Windows wavediff install, the
  unused CI_COMMIT variable, and various redundant operations.
- Inline the single-entry build-job matrices.
- Rename osx to macOS
- Refuse to run these ci scripts outside GitHub Actions to protect
  developer checkouts.
2026-07-05 15:46:30 +01:00
Geza Lore 198cce29ba
CI: Restore relocation testing (#7870)
PR #7776 removed Ubuntu 22.04 clang from the test matrix, which was the
only one testing the relocation of an installation. Add back under
Ubuntu 24.04 clang.
2026-07-05 14:26:30 +01:00
Geza Lore a518efbedb
Internals: Prepare V3DfgBreakCycles to support multi-dimensional arrays (#7867)
This is prep for more array support later. Minor functional improvement
for tracing arrays, but no meaningful change.
2026-07-05 11:40:08 +01:00
Geza Lore 13236822d3
CI: Use faster zstd for repo archives (#7868) 2026-07-05 09:24:33 +01:00
Geza Lore 2285e5b6da
Optimize more always blocks in Dfg (#7775)
Optimize always blocks that:
- drive an unused variable (so logic can be pruned)
- write only a single variable (regardless of CFG size)
2026-07-05 09:24:01 +01:00
Geza Lore 1bab793c75 CI: Fix deleting pr notifications after multiple reruns 2026-07-04 22:46:19 +01:00
Geza Lore 566f4e6efc
Internals: Add new VL_RESTORER flavours (#7866)
Restrict VL_RESTORER to be usable only with trivially copyable types.
Introduce VL_RESTORER_COPY and VL_RESTORER_CLEAR, which are more
efficient versions usable for non trivially copyable types.

VL_RESTORER_COPY semantically behaves the same as VL_RESTORER, but only
does one copy at initialization, and on scope exit restores via a move.

VL_RESTORER_CLEAR swaps the variable with an new one constructed via
the no-args constructor (e.g. empty collection), which does not require
any copying at any point.

Static assertions enforce picking one of the new flavours when copying
might be expensive.
2026-07-04 17:27:07 +01:00
Wilson Snyder 83eb54ec7e Fix wild compare operators on strings 2026-07-04 11:05:34 -04:00
Wilson Snyder f82f59a024 Commentary: Changes update 2026-07-03 13:01:05 -04:00
Wilson Snyder 12fab5d5d7 Tests: Cleanup some cross-simulator test differences 2026-07-03 13:00:23 -04:00
Pawel Klopotek 7ece66d06e
Fix unique0 case side effects (#7787) 2026-07-03 12:49:21 -04:00
Eryk Szpotański a64234e897
Add comments as a branch description in coverage .info files (#7843)
Signed-off-by: Eryk Szpotanski <eszpotanski@antmicro.com>
2026-07-03 11:18:29 -04:00
Dragon-Git 58bd13b623
Fix heap-use-after-free in `VlRNG::VlRNG()` (#7865) 2026-07-03 06:38:48 -04:00
Pawel Kojma b97df914dd
Fix clang++ ambiguous overload of '==' operator (#7863) 2026-07-03 06:37:48 -04:00
Geza Lore 0e371d6e5c Fix cleaning purity cache after assertions 2026-07-02 17:47:09 +01:00
Geza Lore 1106e89c84 Optimize random initialization
Fetching a thread-local is relatively expensive. Random initializing
wides used to do it once or twice per word, and on short runs for
large designs can be noticeably expensive, so fetch once per variable
instead. Also remove unused VL_RAND_RESET_{Q,W} functions.
2026-07-02 16:38:14 +01:00
Jakub Michalski 506f703da5
Internals: Remove unused variables in verilated_funcs.h (#7862)
Signed-off-by: Jakub Michalski <jmichalski@antmicro.com>
2026-07-02 07:16:42 -04:00
Jakub Michalski 853ee5df17
Fix DFG misoptimizing bound checks (#7755)
Signed-off-by: Jakub Michalski <jmichalski@antmicro.com>
2026-07-02 11:33:38 +01:00
Wilson Snyder 964474837f Commentary 2026-07-01 19:37:12 -04:00
Wilson Snyder ce4be92a67 devel release 2026-07-01 18:31:23 -04:00
802 changed files with 129845 additions and 9718 deletions

View File

@ -0,0 +1,165 @@
---
# DESCRIPTION: Github actions composite action
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: Artifact cache
description: >-
Cache a directory across workflow runs using a GitHub Actions artifact instead
of actions/cache (useful where artifacts are preferable, e.g. no 10 GB limit
on public repos). The base 'key' is scoped automatically: per pull request
(<key>-pr-<N>) or per branch (<key>-branch-<name>), and on a PR restore falls
back to the target branch's cache. Call once with mode=restore before the work
and once with mode=save after, passing the same 'key' and 'path'.
Branch-scoped restores are provenance-checked (the artifact must come from a
same-repo push to that branch) - artifact names are a global, unauthenticated
namespace, so otherwise a fork PR could forge one.
mode=restore calls the GitHub API via 'gh', so the caller's job must grant
'actions: read'.
inputs:
mode:
description: "'restore' or 'save'"
required: true
path:
description: "Directory to cache (archived on save, extracted into on restore)"
required: true
key:
description: "Base artifact name; scoped per PR/branch automatically"
required: true
retention-days:
description: "Artifact retention in days (mode=save)"
required: false
default: '3'
token:
description: "Token for the GitHub API (mode=restore); defaults to the job's GITHUB_TOKEN"
required: false
default: ''
outputs:
key:
description: "Echoes the input 'key', so a later save can reuse it without repeating the expression"
value: ${{ inputs.key }}
cache-hit:
description: "'true' if an artifact was restored (mode=restore)"
value: ${{ steps.restore.outputs.cache-hit }}
runs:
using: composite
steps:
- name: Check mode
shell: bash
env:
MODE: ${{ inputs.mode }}
run: |
case "$MODE" in
restore|save) ;;
*) echo "::error::artifact-cache: 'mode' must be 'restore' or 'save' (got '$MODE')"; exit 1 ;;
esac
# Scope the base key: <key>-pr-<N> for pull requests (with the target branch
# as fallback), else <key>-branch-<name>. Refs come via env, not ${{ }}
# interpolation, so a branch name with shell metacharacters can't inject.
- name: Compute artifact name
id: name
shell: bash
env:
BASE_KEY: ${{ inputs.key }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
sanitize() { printf '%s' "$1" | tr '/' '-'; } # artifact names cannot contain '/'
if [ "$EVENT_NAME" = pull_request ]; then
{
echo "primary=${BASE_KEY}-pr-${PR_NUMBER}"
echo "primary-branch="
echo "fallback=${BASE_KEY}-branch-$(sanitize "$BASE_REF")"
echo "fallback-branch=$BASE_REF"
} >> "$GITHUB_OUTPUT"
else
{
echo "primary=${BASE_KEY}-branch-$(sanitize "$REF_NAME")"
echo "primary-branch=$REF_NAME"
echo "fallback="
echo "fallback-branch="
} >> "$GITHUB_OUTPUT"
fi
- name: Restore artifact cache
id: restore
if: ${{ inputs.mode == 'restore' }}
continue-on-error: true # a cold start is fine; a restore failure must not fail the caller
shell: bash
env:
GH_TOKEN: ${{ inputs.token || github.token }}
CACHE_PATH: ${{ inputs.path }}
PRIMARY: ${{ steps.name.outputs.primary }}
PRIMARY_BRANCH: ${{ steps.name.outputs.primary-branch }}
FALLBACK: ${{ steps.name.outputs.fallback }}
FALLBACK_BRANCH: ${{ steps.name.outputs.fallback-branch }}
run: |
set -euo pipefail
mkdir -p "$CACHE_PATH"
# Newest non-expired artifact named $1 (empty if none). With a non-empty
# $2 (branch), require provenance: a same-repo run (head_repository_id ==
# repository_id) whose head branch is $2, so a fork cannot forge it.
newest_run_id() {
want="$1" br="$2" gh api \
"/repos/$GITHUB_REPOSITORY/actions/artifacts?name=$1&per_page=100" \
--jq '.artifacts[]
| select(.expired == false and .name == env.want)
| select(env.br == ""
or (.workflow_run.head_branch == env.br
and .workflow_run.head_repository_id == .workflow_run.repository_id))
| [.created_at, (.workflow_run.id | tostring)] | @tsv' \
| sort | tail -n1 | cut -f2
}
restore() { # $1=name $2=branch(empty ok); returns 0 on a successful restore
local name="$1" br="$2" rid tarball
[ -n "$name" ] || return 1
rid="$(newest_run_id "$name" "$br")" || return 1
[ -n "$rid" ] || return 1
echo "Restoring '$CACHE_PATH' from artifact '$name' (run $rid)"
gh run download "$rid" -R "$GITHUB_REPOSITORY" --name "$name" --dir "$RUNNER_TEMP/artifact-cache-dl" || return 1
# Each artifact holds a single tarball; match by extension so the inner
# filename is not part of the contract.
tarball="$(find "$RUNNER_TEMP/artifact-cache-dl" -name '*.tar.zst' -print -quit)"
[ -n "$tarball" ] || return 1
tar -I zstd -x -f "$tarball" -C "$CACHE_PATH"
}
if restore "$PRIMARY" "$PRIMARY_BRANCH"; then
echo "Restored from primary key"
echo "cache-hit=true" >> "$GITHUB_OUTPUT"
elif restore "$FALLBACK" "$FALLBACK_BRANCH"; then
echo "Restored from fallback key"
echo "cache-hit=true" >> "$GITHUB_OUTPUT"
else
echo "No matching artifact found; starting cold"
echo "cache-hit=false" >> "$GITHUB_OUTPUT"
fi
- name: Pack artifact cache
id: pack
if: ${{ inputs.mode == 'save' }}
shell: bash
env:
CACHE_PATH: ${{ inputs.path }}
run: |
set -euo pipefail
packdir="$(mktemp -d)" # unique dir so concurrent saves cannot collide
tar -I 'zstd -T0' -cf "$packdir/cache.tar.zst" -C "$CACHE_PATH" .
echo "tarball=$packdir/cache.tar.zst" >> "$GITHUB_OUTPUT"
- name: Upload artifact cache
if: ${{ inputs.mode == 'save' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ steps.name.outputs.primary }}
path: ${{ steps.pack.outputs.tarball }}
retention-days: ${{ inputs.retention-days }}
overwrite: true
compression-level: 0 # tarball is already zstd-compressed

56
.github/actions/setup-venv/action.yml vendored Normal file
View File

@ -0,0 +1,56 @@
---
# DESCRIPTION: Github actions composite action
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: Set up Python venv
description: >-
Restore, create, and optionally save the make-managed Python venv in a GitHub
Actions cache. The cache key combines the venv path (venvs are not
relocatable), the host OS and version, the Python major.minor version, and
the python-dev-requirements.txt hash, so any of those changing invalidates
it. Requires the already-configured Verilator tree to be present in 'repo'.
inputs:
repo:
description: "Verilator checkout holding the Makefile and .venv"
required: false
default: repo
save:
description: "Save the cache on a miss (set true only on trusted branches)"
required: false
default: 'false'
runs:
using: composite
steps:
- name: Compute Python venv cache key
id: cachekey
shell: bash
working-directory: ${{ inputs.repo }}
run: |
source ci/ci-common.bash
host="${DISTRO_ID:-$HOST_OS}${DISTRO_VERSION:+-$DISTRO_VERSION}"
pyver="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')"
reqs="$(sha256sum python-dev-requirements.txt | cut -d' ' -f1)"
venv="${PWD#/}/.venv" # absolute venv path (leading '/' trimmed)
echo "key=venv-${host}-py${pyver}-${reqs}-${venv//\//-}" >> "$GITHUB_OUTPUT"
- name: Restore Python venv
id: venv
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ inputs.repo }}/.venv
key: ${{ steps.cachekey.outputs.key }}
- name: Create Python venv
if: ${{ steps.venv.outputs.cache-hit != 'true' }}
shell: bash
working-directory: ${{ inputs.repo }}
run: make venv
- name: Save Python venv
if: ${{ inputs.save == 'true' && steps.venv.outputs.cache-hit != 'true' }}
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ inputs.repo }}/.venv
key: ${{ steps.venv.outputs.cache-primary-key }}

View File

@ -16,6 +16,7 @@ on:
permissions:
contents: read
actions: read
defaults:
run:
@ -29,272 +30,141 @@ concurrency:
jobs:
build-2604-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
name: Build | 26.04 | gcc
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
runs-on: ubuntu-26.04
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-26.04, cc: gcc, asan: 0}
build-2604-clang:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
name: Build | 26.04 | clang
uses: ./.github/workflows/reusable-build.yml
with:
cc: clang
dev-asan: true # Build (and run) with address sanitizer
runs-on: ubuntu-26.04
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-26.04, cc: clang, asan: 1}
build-2404-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
name: Build | 24.04 | gcc
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
runs-on: ubuntu-24.04
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-24.04, cc: gcc, asan: 0}
build-2404-clang:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
name: Build | 24.04 | clang
uses: ./.github/workflows/reusable-build.yml
with:
cc: clang
runs-on: ubuntu-24.04
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-24.04, cc: clang, asan: 0}
build-2204-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
name: Build | 22.04 | gcc
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
runs-on: ubuntu-22.04
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-22.04, cc: gcc, asan: 0}
build-osx-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
build-macos-15-clang:
name: Build | macos-15 | clang
uses: ./.github/workflows/reusable-build.yml
with:
cc: clang
runs-on: macos-15
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: osx
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: macos-15, cc: gcc, asan: 0}
build-osx-clang:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
uses: ./.github/workflows/reusable-build.yml
with:
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: osx
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: macos-15, cc: clang, asan: 0}
build-windows:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- {os: windows-2025-vs2026, cc: msvc}
env:
CI_OS_NAME: win
CCACHE_COMPRESS: 1
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_LIMIT_MULTIPLE: 0.95
name: Build | windows-2025-vs2026 | msvc
runs-on: windows-2025-vs2026
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
path: repo
- name: Cache $CCACHE_DIR
- name: Cache win_flex_bison
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ env.CCACHE_DIR }}
key: msbuild-msvc-cmake
path: ${{ github.workspace }}/win_flex_bison
key: win_flex_bison
- name: compile
env:
WIN_FLEX_BISON: ${{ github.workspace }}/.ccache
run: ./ci/ci-win-compile.ps1
- name: test build
run: ./ci/ci-win-test.ps1
- name: Zip up repository
run: Compress-Archive -LiteralPath install -DestinationPath verilator.zip
- name: Upload zip archive
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
path: ${{ github.workspace }}/repo/verilator.zip
name: verilator-win.zip
test-2604-gcc:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
name: Test | 26.04 | gcc | ${{ matrix.suite }}
needs: build-2604-gcc
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2604-gcc.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
cc: gcc
runs-on: ubuntu-26.04
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 26.04 gcc
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-0}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-1}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-2}
suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2]
test-2604-clang:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
name: Test | 26.04 | clang | ${{ matrix.suite }}
needs: build-2604-clang
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2604-clang.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
cc: clang
runs-on: ubuntu-26.04
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 26.04 clang
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-0}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-1}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-2}
suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2]
test-2404-gcc:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
name: Test | 24.04 | gcc | ${{ matrix.suite }}
needs: build-2404-gcc
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2404-gcc.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
cc: gcc
runs-on: ubuntu-24.04
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 24.04 gcc
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-0}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-1}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-2}
suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2]
test-2404-clang:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
name: Test | 24.04 | clang | ${{ matrix.suite }}
needs: build-2404-clang
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2404-clang.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
cc: clang
reloc: true # Test with relocated installation
runs-on: ubuntu-24.04
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 24.04 clang
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-0}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-1}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-2}
suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2]
test-2204-gcc:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
name: Test | 22.04 | gcc | ${{ matrix.suite }}
needs: build-2204-gcc
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2204-gcc.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
cc: gcc
runs-on: ubuntu-22.04
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 22.04 gcc
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-0}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-1}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-2}
suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2]
lint-py:
name: Lint Python

View File

@ -16,5 +16,5 @@ jobs:
name: "'docs/CONTRIBUTORS' was signed"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- run: test_regress/t/t_dist_contributors.py

View File

@ -13,6 +13,7 @@ on:
permissions:
contents: read
actions: read
defaults:
run:
@ -38,15 +39,13 @@ jobs:
(github.event_name == 'workflow_dispatch')
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
dev-gcov: true
runs-on: ubuntu-24.04
# For pull requests, build the head of the pull request branch, not the
# merge commit, otherwise patch coverage would include the changes
# between the root of the pull request and the target branch
sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
os: ubuntu-24.04
os-name: linux
cc: gcc
dev-asan: 0
dev-gcov: 1
test:
name: Test | ${{ matrix.test }}${{ matrix.num }}
@ -54,11 +53,10 @@ jobs:
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build.outputs.archive }}
os: ubuntu-24.04
cc: gcc
reloc: 0
dev-gcov: true
runs-on: ubuntu-24.04
suite: ${{ matrix.test }}${{ matrix.num }}
dev-gcov: 1
strategy:
fail-fast: false
matrix:
@ -74,7 +72,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Download code coverage data
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
@ -121,7 +119,7 @@ jobs:
- name: Unpack repository archive
run: |
tar -x -z -f ${{ needs.build.outputs.archive }}
tar --zstd -x -f ${{ needs.build.outputs.archive }}
ls -lsha
- name: Download code coverage data

View File

@ -39,7 +39,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Extract context variables
run: |
@ -54,7 +54,7 @@ jobs:
- name: Docker meta
id: docker_meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
with:
images: |
${{ vars.DOCKER_HUB_NAMESPACE }}/${{ env.image_name }}
@ -64,21 +64,21 @@ jobs:
type=raw,value=latest,enable=${{ inputs.add_latest_tag == true }}
- name: Set up QEMU
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
with:
buildkitd-flags: --debug
- name: Login to Docker Hub
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and Push to Docker
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
with:
context: ${{ env.build_context }}

View File

@ -11,35 +11,44 @@ on:
permissions:
contents: write
defaults:
run:
working-directory: repo
jobs:
format:
runs-on: ubuntu-24.04
name: Ubuntu 24.04 | format
env:
CI_OS_NAME: linux
CI_RUNS_ON: ubuntu-24.04
CI_COMMIT: ${{ github.sha }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
path: repo
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install packages for build
env:
CI_BUILD_STAGE_NAME: build
run: |
sudo apt install clang-format-18 || \
sudo apt install clang-format-18
git config --global user.email "action@example.com"
git config --global user.name "github action"
- name: Format code
- name: Configure
run: |
autoconf
./configure
make venv
- name: Set up Python venv
uses: ./repo/.github/actions/setup-venv
with:
save: ${{ github.ref == 'refs/heads/master' }}
- name: Format code
run: |
source .venv/bin/activate
make -j 4 format CLANGFORMAT=clang-format-18
git status
- name: Push
run: |-
if [ -n "$(git status --porcelain)" ]; then

View File

@ -7,7 +7,7 @@ name: Pages
on:
push:
branches: [master]
paths: ["ci/**", ".github/workflows"]
paths: ["ci/**", ".github/workflows/**"]
workflow_dispatch:
workflow_run:
workflows: ["Code coverage", "RTLMeter"]
@ -22,8 +22,11 @@ permissions:
# Allow only one concurrent deployment, skipping runs queued between the run
# in-progress and latest queued. However, do NOT cancel in-progress runs as we
# want to allow these deployments to complete.
# A skipped-upstream run does no work (see the build job's if:), so put it in its
# own throwaway group: otherwise, as the newest queued run, it would cancel a
# pending real run and then deploy nothing.
concurrency:
group: "pages"
group: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'skipped') && format('pages-skip-{0}', github.run_id) || 'pages' }}
cancel-in-progress: false
defaults:
@ -34,11 +37,15 @@ jobs:
build:
name: Build content
runs-on: ubuntu-24.04
# A skipped upstream run (e.g. Code coverage / RTLMeter on a branch push)
# still fires workflow_run; don't rebuild and redeploy for it. deploy and
# notify need this job, so they cascade-skip too.
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion != 'skipped' }}
outputs:
pr-run-ids: ${{ steps.build.outputs.pr-run-ids }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Build pages
id: build
env:
@ -58,9 +65,16 @@ jobs:
runs-on: ubuntu-24.04
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
url: ${{ steps.deploy-2.outputs.page_url || steps.deploy-1.outputs.page_url }}
steps:
# GitHub's Pages backend intermittently fails a deployment mid-sync, retry
- name: Deploy to GitHub Pages
id: deploy-1
continue-on-error: true
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
- name: Deploy to GitHub Pages (retry)
id: deploy-2
if: steps.deploy-1.outcome == 'failure'
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
notify:
@ -70,7 +84,7 @@ jobs:
if: ${{ github.repository == 'verilator/verilator' }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
# Use the Verilator CI app to post the comment
- name: Generate access token
id: generate-token

View File

@ -7,37 +7,47 @@ name: reusable-build
on:
workflow_call:
inputs:
cc:
description: "Compiler to use: 'gcc' or 'clang'"
required: true
type: string
ccwarn:
description: "Build Verilator with warnings treated as errors (--enable-ccwarn)"
required: false
default: true
type: boolean
dev-asan:
description: "Build Verilator with the address sanitizer (--enable-dev-asan)"
required: false
default: false
type: boolean
dev-gcov:
description: "Build Verilator with gcov instrumentation (--enable-dev-gcov)"
required: false
default: false
type: boolean
install:
description: "Archive the Verilator installation, not the repo tree"
required: false
default: false
type: boolean
runs-on:
description: "Runner to build on, e.g. ubuntu-24.04"
required: true
type: string
sha:
description: "Commit SHA to build"
required: true
type: string
os: # e.g. ubuntu-24.04
required: true
type: string
cc: # 'clang' or 'gcc'
required: true
type: string
os-name: # 'linux' or 'osx'
required: true
type: string
dev-asan:
required: true
type: number
dev-gcov:
required: true
type: number
outputs:
archive:
description: "Name of the built repository archive artifact"
description: "Name of the built archive artifact"
value: ${{ jobs.build.outputs.archive }}
env:
CI_OS_NAME: ${{ inputs.os-name }}
CCACHE_COMPRESS: 1
CACHE_BASE_KEY: build-${{ inputs.runs-on }}-${{ inputs.cc }}${{ inputs.ccwarn && '-ccwarn' || '' }}${{ inputs.dev-asan && '-asan' || '' }}${{ inputs.dev-gcov && '-gcov' || '' }}
CCACHE_COMPILERCHECK: content
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_LIMIT_MULTIPLE: 0.95
INSTALL_DIR: ${{ github.workspace }}/install
RELOC_DIR: ${{ github.workspace }}/relloc
defaults:
run:
@ -48,26 +58,28 @@ jobs:
build:
name: Build
runs-on: ${{ inputs.os }}
runs-on: ${{ inputs.runs-on }}
outputs:
archive: ${{ steps.create-archive.outputs.archive }}
env:
CI_BUILD_STAGE_NAME: build
CI_DEV_ASAN: ${{ inputs.dev-asan }}
CI_DEV_GCOV: ${{ inputs.dev-gcov }}
CI_RUNS_ON: ${{ inputs.os }}
CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }}
CACHE_BASE_KEY: build-${{ inputs.os }}-${{ inputs.cc }}
CCACHE_MAXSIZE: 1000M # Per build matrix entry (* 5 = 5000M in total)
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
path: repo
ref: ${{ inputs.sha }}
fetch-depth: ${{ inputs.dev-gcov && '0' || '1' }} # Coverage flow needs full history
# Coverage needs full history; install needs it for the 'git describe'
# behind 'verilator --version'
fetch-depth: ${{ (inputs.install || inputs.dev-gcov) && '0' || '1' }}
- name: Configure ccache
run: |
# ccache is unreliable on macOS, and does nothing with 'gcc --coverage'
if [ "${{ startsWith(inputs.runs-on, 'macos') }}" = true ] || [ "${{ inputs.dev-gcov }}" = true ]; then
echo "CCACHE_DISABLE=1" >> "$GITHUB_ENV"
fi
- name: Cache $CCACHE_DIR
if: ${{ env.CCACHE_DISABLE != '1' }}
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
env:
CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache
@ -75,25 +87,44 @@ jobs:
path: ${{ env.CCACHE_DIR }}
key: ${{ env.CACHE_KEY }}-${{ inputs.sha }}
restore-keys: |
${{ env.CACHE_KEY }}-
${{ env.CACHE_KEY }}
- name: Install packages for build
run: ./ci/ci-install.bash
run: ./ci/ci-install.bash build
- name: Build
run: ./ci/ci-script.bash
run: |
./ci/ci-build.bash \
--prefix ${{ github.workspace }}/install \
--compiler ${{ inputs.cc }} \
--light-debug \
${{ inputs.ccwarn && '--ccwarn' || '' }} \
${{ inputs.dev-asan && '--asan' || '' }} \
${{ inputs.dev-gcov && '--gcov' || '' }}
- name: Create repository archive
- name: Install
if: ${{ inputs.install }}
run: make install
- name: Create archive
id: create-archive
working-directory: ${{ github.workspace }}
run: |
# Name of the archive must be unique based on the build parameters
ARCHIVE=verilator-${{ inputs.sha }}-${{ inputs.os }}-${{ inputs.cc }}-${{ inputs.dev-asan }}-${{ inputs.dev-gcov }}.tar.gz
tar --posix -c -z -f $ARCHIVE repo
# Archive name, unique per build; flavour tags mark non-default builds
ARCHIVE="verilator-${{ inputs.sha }}-${{ inputs.runs-on }}-${{ inputs.cc }}"
ARCHIVE="$ARCHIVE${{ inputs.ccwarn && '-ccwarn' || '' }}"
ARCHIVE="$ARCHIVE${{ inputs.dev-asan && '-asan' || '' }}"
ARCHIVE="$ARCHIVE${{ inputs.dev-gcov && '-gcov' || '' }}"
ARCHIVE="$ARCHIVE.tar.zst"
# zstd compresses faster than gzip and decompresses far faster in the many downstream jobs
ZSTD_NBTHREADS=0 tar --posix --zstd -c -f "$ARCHIVE" ${{ inputs.install && 'install' || 'repo' }}
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
- name: Upload repository archive
- name: Upload archive
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
path: ${{ github.workspace }}/${{ steps.create-archive.outputs.archive }}
name: ${{ steps.create-archive.outputs.archive }}
overwrite: true
# Archive is already zstd-compressed; skip upload-artifact's zip pass
compression-level: 0

View File

@ -7,14 +7,6 @@ name: reusable-lint-py
on:
workflow_call:
env:
CI_OS_NAME: linux
CI_BUILD_STAGE_NAME: build
CI_RUNS_ON: ubuntu-22.04
CCACHE_COMPRESS: 1
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_LIMIT_MULTIPLE: 0.95
defaults:
run:
shell: bash
@ -27,23 +19,22 @@ jobs:
name: Sub-lint | Python
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
path: repo
- name: Install packages for build
run: ./ci/ci-install.bash
- name: Install dependencies
run: ./ci/ci-install.bash lint-py
- name: Configure
run: |
autoconf
./configure --enable-longtests --enable-ccwarn
- name: Install python dependencies
run: |
sudo apt install python3-clang || \
sudo apt install python3-clang
make venv
- name: Set up Python venv
uses: ./repo/.github/actions/setup-venv
with:
save: ${{ github.ref == 'refs/heads/master' }}
- name: Lint
run: |-

View File

@ -1,96 +0,0 @@
---
# DESCRIPTION: Github actions config
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: reusable-rtlmeter-build
on:
workflow_call:
inputs:
runs-on:
description: "Runner to use, e.g.: ubuntu-24.04"
type: string
required: true
cc:
description: "Compiler to use: 'gcc' or 'clang'"
type: string
required: true
sha:
description: "Git SHA to build"
type: string
required: true
outputs:
archive:
description: "Name of the built installation archive artifact"
value: ${{ jobs.build.outputs.archive }}
defaults:
run:
shell: bash
env:
CCACHE_DIR: ${{ github.workspace }}/ccache
CCACHE_MAXSIZE: 512M
jobs:
build:
name: Build
runs-on: ${{ inputs.runs-on }}
outputs:
archive: ${{ steps.create-archive.outputs.archive }}
steps:
- name: Install dependencies
run: |
echo "path-exclude /usr/share/doc/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
echo "path-exclude /usr/share/man/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
sudo apt update || \
sudo apt update
sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev || \
sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev
- name: Use saved ccache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ccache
key: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: |
rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }}
rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}
rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
path: repo
ref: ${{ inputs.sha }}
fetch-depth: 0 # Required for 'git describe' used for 'verilator --version'
- name: Configure
working-directory: repo
run: |
autoconf
./configure --prefix=${{ github.workspace }}/install CXX=${{ inputs.cc == 'clang' && 'clang++' || 'g++' }}
- name: Make
working-directory: repo
run: make -j $(nproc)
- name: Install
working-directory: repo
run: make install
- name: Tar up installation
id: create-archive
run: |
SHA=$(git -C repo rev-parse HEAD)
ARCHIVE=verilator-$SHA-rtlmeter-${{ inputs.runs-on }}-${{ inputs.cc }}.tar.gz
tar --posix -c -z -f $ARCHIVE install
echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT
- name: Upload Verilator installation archive
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
path: ${{ steps.create-archive.outputs.archive }}
name: ${{ steps.create-archive.outputs.archive }}
overwrite: true

View File

@ -20,11 +20,11 @@ on:
type: string
required: true
verilator-archive-new:
description: "Name of the installation archive artifact from reusable-rtlmeter-build, new version"
description: "Name of the installation archive artifact from reusable-build, new version"
type: string
required: true
verilator-archive-old:
description: "Name of the installation archive artifact from reusable-rtlmeter-build, old version"
description: "Name of the installation archive artifact from reusable-build, old version"
type: string
required: false
default: ""
@ -53,8 +53,8 @@ defaults:
shell: bash
env:
CCACHE_DIR: ${{ github.workspace }}/ccache
CCACHE_MAXSIZE: 512M
# RTLMeter measures compile/execute times, so caching must stay off to keep
# timings representative; ccache is still on PATH but acts as a pass-through
CCACHE_DISABLE: 1
jobs:
@ -73,7 +73,7 @@ jobs:
sudo apt install ccache mold libfl-dev libjemalloc-dev libsystemc-dev
- name: Checkout RTLMeter
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
repository: "verilator/rtlmeter"
path: rtlmeter
@ -82,14 +82,6 @@ jobs:
working-directory: rtlmeter
run: make venv
- name: Use saved ccache
if: ${{ env.CCACHE_DISABLE == 0 }}
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ env.CCACHE_DIR }}
key: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }}
########################################################################
# Run with new Verilator
########################################################################
@ -101,7 +93,7 @@ jobs:
- name: Unpack Verilator installation archive - new
run: |
tar -x -z -f ${{ inputs.verilator-archive-new }}
tar --zstd -x -f ${{ inputs.verilator-archive-new }}
mv install verilator-new
- name: Compile cases - new
@ -164,7 +156,7 @@ jobs:
- name: Unpack Verilator installation archive - old
if: ${{ inputs.verilator-archive-old != '' }}
run: |
tar -x -z -f ${{ inputs.verilator-archive-old }}
tar --zstd -x -f ${{ inputs.verilator-archive-old }}
mv install verilator-old
- name: Compile cases - old

View File

@ -11,29 +11,37 @@ on:
description: "Name of the repository archive artifact from reusable-build"
required: true
type: string
os: # e.g. ubuntu-24.04
required: true
type: string
cc: # gcc or clang
required: true
type: string
reloc: # 0 or 1
required: true
type: number
suite: # e.g. dist-vlt-0
cc:
description: "Compiler to use: 'gcc' or 'clang'"
required: true
type: string
dev-gcov:
description: "Collect gcov coverage data from the test run"
required: false
default: false
type: boolean
reloc:
description: "Relocate the installation before testing"
required: false
default: false
type: boolean
runs-on:
description: "Runner to test on, e.g. ubuntu-24.04"
required: true
type: number
type: string
suite:
description: "Test suite to run, e.g. dist-vlt-0"
required: true
type: string
permissions:
contents: read
actions: read
env:
CI_OS_NAME: linux
CCACHE_COMPRESS: 1
CCACHE_COMPILERCHECK: content
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_LIMIT_MULTIPLE: 0.95
INSTALL_DIR: ${{ github.workspace }}/install
RELOC_DIR: ${{ github.workspace }}/relloc
CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }}
defaults:
run:
@ -43,15 +51,8 @@ defaults:
jobs:
test:
runs-on: ${{ inputs.os }}
runs-on: ${{ inputs.runs-on }}
name: Test
env:
CI_BUILD_STAGE_NAME: test
CI_RUNS_ON: ${{ inputs.os }}
CI_RELOC: ${{inputs.reloc }}
CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }}
CACHE_BASE_KEY: test-${{ inputs.os }}-${{ inputs.cc }}-${{inputs.reloc }}-${{ inputs.suite }}
CCACHE_MAXSIZE: 100M # Per build per suite (* 5 * 5 = 2500M in total)
steps:
- name: Download repository archive
@ -63,32 +64,43 @@ jobs:
- name: Unpack repository archive
working-directory: ${{ github.workspace }}
run: |
tar -x -z -f ${{ inputs.archive }}
tar --zstd -x -f ${{ inputs.archive }}
ls -lsha
- name: Cache $CCACHE_DIR
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
env:
CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache2
- name: Configure ccache
run: |
# ccache is unreliable on macOS, and does nothing with 'gcc --coverage'
if [ "${{ startsWith(inputs.runs-on, 'macos') }}" = true ] || [ "${{ inputs.dev-gcov }}" = true ]; then
echo "CCACHE_DISABLE=1" >> "$GITHUB_ENV"
fi
# Test-job ccache is stored as an artifact, not actions/cache
- name: Restore ccache
id: ccache
if: ${{ env.CCACHE_DISABLE != '1' }}
uses: ./repo/.github/actions/artifact-cache
with:
mode: restore
path: ${{ env.CCACHE_DIR }}
key: ${{ env.CACHE_KEY }}-${{ github.sha }}
restore-keys: |
${{ env.CACHE_KEY }}-
key: ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.suite }}
- name: Install test dependencies
run: |
./ci/ci-install.bash
make venv
./ci/ci-install.bash test
- name: Set up Python venv
uses: ./repo/.github/actions/setup-venv
with:
save: ${{ github.ref == 'refs/heads/master' }}
- name: Test
id: run-test
continue-on-error: true
env:
TESTS: ${{ inputs.suite }}
run: |
source .venv/bin/activate
./ci/ci-script.bash
./ci/ci-test.bash \
--suite ${{ inputs.suite }} \
${{ inputs.reloc && format('--reloc {0}/reloc', github.workspace) || '' }}
- name: Combine code coverage data
if: ${{ inputs.dev-gcov }}
@ -104,6 +116,16 @@ jobs:
path: ${{ github.workspace }}/repo/obj_coverage/verilator-${{ inputs.suite }}.info
name: code-coverage-${{ inputs.suite }}
- name: Save ccache
if: ${{ env.CCACHE_DISABLE != '1' && !cancelled() }}
uses: ./repo/.github/actions/artifact-cache
with:
mode: save
path: ${{ env.CCACHE_DIR }}
key: ${{ steps.ccache.outputs.key }}
# Keep master's cache around longer; PR/branch caches churn faster
retention-days: ${{ github.ref == 'refs/heads/master' && 7 || 3 }}
- name: Fail job if a test failed
if: ${{ steps.run-test.outcome == 'failure' && !cancelled() }}
run: |-

View File

@ -45,7 +45,7 @@ jobs:
cases: ${{ steps.cases.outputs.cases }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Startup
id: start
@ -65,39 +65,47 @@ jobs:
build-gcc-new:
name: Build New Verilator - GCC
needs: start
uses: ./.github/workflows/reusable-rtlmeter-build.yml
uses: ./.github/workflows/reusable-build.yml
with:
runs-on: ubuntu-24.04
cc: gcc
ccwarn: false
install: true
runs-on: ubuntu-24.04
sha: ${{ github.sha }}
build-clang-new:
name: Build New Verilator - Clang
needs: start
uses: ./.github/workflows/reusable-rtlmeter-build.yml
uses: ./.github/workflows/reusable-build.yml
with:
runs-on: ubuntu-24.04
cc: clang
ccwarn: false
install: true
runs-on: ubuntu-24.04
sha: ${{ github.sha }}
build-gcc-old:
name: Build Old Verilator - GCC
needs: start
if: ${{ needs.start.outputs.old-sha != '' }}
uses: ./.github/workflows/reusable-rtlmeter-build.yml
uses: ./.github/workflows/reusable-build.yml
with:
runs-on: ubuntu-24.04
cc: gcc
ccwarn: false
install: true
runs-on: ubuntu-24.04
sha: ${{ needs.start.outputs.old-sha }}
build-clang-old:
name: Build Old Verilator - Clang
needs: start
if: ${{ needs.start.outputs.old-sha != '' }}
uses: ./.github/workflows/reusable-rtlmeter-build.yml
uses: ./.github/workflows/reusable-build.yml
with:
runs-on: ubuntu-24.04
cc: clang
ccwarn: false
install: true
runs-on: ubuntu-24.04
sha: ${{ needs.start.outputs.old-sha }}
run-gcc:
@ -208,7 +216,7 @@ jobs:
run: echo "tags=$(jq -r 'keys | map(sub("^run-"; "")) | join(" ")' <<< '${{ toJSON(needs) }}')" >> "$GITHUB_OUTPUT"
- name: Checkout RTLMeter
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
repository: "verilator/rtlmeter"
path: rtlmeter
@ -298,7 +306,7 @@ jobs:
repositories: verilator-rtlmeter-results
permission-contents: write
- name: Checkout verilator-rtlmeter-results
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
repository: "verilator/verilator-rtlmeter-results"
token: ${{ steps.generate-token.outputs.token }}
@ -331,7 +339,7 @@ jobs:
actions: read
steps:
- name: Checkout RTLMeter
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
repository: "verilator/rtlmeter"
path: rtlmeter
@ -341,7 +349,7 @@ jobs:
run: make venv
- name: Checkout Verilator
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
path: verilator

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.050
VERSION 5.051
HOMEPAGE_URL https://verilator.org
LANGUAGES CXX
)
@ -155,11 +155,12 @@ install(
foreach(
program
verilator
verilator_gantt
verilator_ccache_report
verilator_coverage
verilator_difftree
verilator_profcfunc
verilator_gantt
verilator_includer
verilator_profcfunc
)
install(PROGRAMS bin/${program} TYPE BIN)
endforeach()

121
Changes
View File

@ -10,6 +10,125 @@ The changes in each Verilator version are described below. The
contributors that suggested or implemented a given issue are shown in []. Thanks!
Verilator 5.051 devel
==========================
**Important:**
* Verilator now supports UVM 2020-3.2; see (#1538) for limitations.
**Other:**
* Add comments as a branch description in coverage .info files (#7843). [Eryk Szpotanski]
* Add --enable-light-debug configure option (#7886). [Geza Lore, Testorrent USA, Inc.]
* Add user-provided DPI-C function declarations (#7626) (#7893). [Jakub Michalski]
* Add error on static virtual functions (#7932). [Igor Zaworski, Antmicro Ltd.]
* Add error when scalar is passed to array task argument (#7948). [Pawel Klopotek]
* Add MULTIDRIVENPROC warning for signals driven by multiple plain always blocks (#7968). [Aisha]
* Add FSM arc and state coverage to verilator_coverage .info output (#7972) (#7973). [Igor Zaworski, Antmicro Ltd.]
* Add SIMILARNAME warning when variables have names that only differ in lexical case (#7992) (#8020). [Paul Campbell]
* Add `+verilator+assert+lock` to ignore RTL assert control statements (#8086). [Sumanth Kadiyala]
* Support embedded covergroup member references (#7749) (#8015). [Marco Bartoli]
* Support a sequence used as an event control (#7797) (#7846). [Yilou Wang]
* Support `[\*N:$]` consecutive repetition (#7940). [Artur Bieniek, Antmicro Ltd.]
* Support dynamic containers in unique constraints (#7947). [Adam Kostrzewski, Antmicro Ltd.]
* Support delayed tristate gates (#7960) (#7961). [Patrick Creighton]
* Support `default clocking` (#7984). [Kamil Danecki, Antmicro Ltd.]
* Support embedded covergroup clocking events (#8028). [Marco Bartoli]
* Support `weak`/`strong` keywords in property expressions (#8054). [Artur Bieniek, Antmicro Ltd.]
* Support class handle covergroup arguments (#8071). [Marco Bartoli]
* Optimize random initialization. [Geza Lore, Testorrent USA, Inc.]
* Optimize more always blocks in DFG (#7775). [Geza Lore, Testorrent USA, Inc.]
* Optimize assertion NFAs using bit-vector ring buffers (#7885). [Artur Bieniek, Antmicro Ltd.]
* Optimize VPI symbol registration and scope construction (#7936). [Nick Brereton]
* Optimize bounded always properties using ring buffers (#8061) (#8092). [Artur Bieniek, Antmicro Ltd.]
* Optimize VL_WORDS_I/VL_BYTES_I (#8095). [Geza Lore, Testorrent USA, Inc.]
* Optimize temporary insertion for single bit replicates in DFG (#8110). [Geza Lore, Testorrent USA, Inc.]
* Fix $finish continuing event loop (#7267) (#7950). [Artur Bieniek, Antmicro Ltd.]
* Fix $display accepting streaming concat arguments (#7663) (#7890). [Jaeuk Lee]
* Fix DFG misoptimizing bound checks (#7755). [Jakub Michalski]
* Fix unique0 case side effects (#7787). [Pawel Klopotek]
* Fix mid-window disable iff (#7792) (#7869). [Yilou Wang]
* Fix cleaning purity cache after assertions. [Geza Lore, Testorrent USA, Inc.]
* Fix uncaught type error leading to invalid C++ output (#7814). [Geza Lore, Testorrent USA, Inc.]
* Fix internal error for coverpoints that reference a covergroup formal parameter (#7853 partial) (#7889). [Matthew Ballance]
* Fix immediate disable of fork join branches (#7856) (#7931). [Marco Bartoli]
* Fix self-disable of named blocks with forks (Part of #7857) (#7996) (#8006). [Marco Bartoli]
* Fix clang++ ambiguous overload of '==' operator (#7863). [Pawel Kojma, Antmicro Ltd.]
* Fix heap-use-after-free in `VlRNG::VlRNG()` (#7865). [Dragon-Git]
* Fix mixed-width inside and dist range bounds failing randomization (#7875). [Yilou Wang]
* Fix solve-before over array variables failing randomization (#7876). [Yilou Wang]
* Fix scoped randomize with array members under rand_mode (#7877). [Yilou Wang]
* Fix dist constraint on a frozen variable failing randomization (#7878). [Yilou Wang]
* Fix range delays with parameter bounds (#7882). [Artur Bieniek, Antmicro Ltd.]
* Fix --coverage on labeled inline assert/cover property (#7898) (#7904). [Patrick Creighton]
* Fix memory leak in VerilatedFst::close() (#7899). [Jakub Michalski]
* Fix spurious FSM COVERIGN on comparisons (#7900) (#7908). [Yogish Sekhar]
* Fix independent force of multiply instantiated signals (#7905). [Artur Bieniek, Antmicro Ltd.]
* Fix release of forced port nets (#7907) (#7901). [Artur Bieniek, Antmicro Ltd.]
* Fix randomization of unreferenced rand members of unpacked structs (#7910) (#7911). [Philip Axer]
* Fix VL_TO_STRING function for array of structs (#7912). [Kornel Uriasz, Antmicro Ltd.]
* Fix queues falling into wrong template spec (#7914). [Adam Kostrzewski, Antmicro Ltd.]
* Fix type resolution when a local name shadows a type name (#7915). [Pawel Klopotek]
* Fix crash streaming an unpacked array of unpacked structs (#7917). [Nick Brereton]
* Fix streaming concat as output-port lvalue into unpacked struct (#7918). [Nick Brereton]
* Fix dropping variable writes across opaque calls (#7921) (#7933). [Philip Axer]
* Fix dynamic array handling in solve...before (#7922). [Kornel Uriasz, Antmicro Ltd.]
* Fix unlinked error with function call in derived class parameter (#7923). [Mateusz Gancarz, Antmicro Ltd.]
* Fix scheduling of variables written in non-inlined functions in suspendable processes (#7924). [Igor Zaworski, Antmicro Ltd.]
* Fix class parameter resolution (#7935).
* Fix FSM detection of coverage-only branches (#7941) (#7942). [Patrick Creighton]
* Fix use-after-free in V3LinkDotIfaceCapture (#7943). [Nick Brereton]
* Fix sampled-value clocks in repeated assertions (#7944). [Artur Bieniek, Antmicro Ltd.]
* Fix queued $finish/$stop request thread ordering (#7946 prep) (#7952). [Yilou Wang]
* Fix DFG nested shifts with overflowing shift amount (#7955) (#7977).
* Fix timing controls in interface tasks called via virtual interface (#7959). [Yilou Wang]
* Fix lambda parameter types in queue min and max (#7962). [Bartosz Skorowski]
* Fix solve-before dropping all soft constraints in randomize() (#7963). [Yilou Wang]
* Fix generic interface param resolution in module/cell parameterization (#7970) (#7971). [David Garau]
* Fix lost writes when select width exceeds variable width (#7975). [Bartosz Skorowski]
* Fix $fgets being mis-optimized away (#7976). [G-A. Kamendje]
* Fix SYNCASYNCNET false positive with changed non-edge logic (#7980). [Oron Port]
* Fix variable scope in unique on dynamic array (#7981). [Kornel Uriasz, Antmicro Ltd.]
* Fix table optimization causing not contextually convertible to bool error (#7983). [Jakub Michalski]
* Fix wait fork trigger temporary split across generated functions (#7985) (#7986). [Marco Brambilla]
* Fix randc cycling in a class that also uses solve...before (#7991 partial) (#8055). [Yilou Wang]
* Fix randomize() with a user variable named 'item' (#7993) (#7994).
* Fix UNSATCONSTR reporting constraint indices above 9 (#7995). [Yilou Wang]
* Fix MULTIDRIVEN/PROC warning suppression via lint_off (#8000). [Gilberto Abram]
* Fix force not substituted into an unpacked array index (#8002).
* Fix dist weights ignored when an item is not a literal (#8003).
* Fix error when forcing an array element read at a run-time index (#8004).
* Fix FSM coverage on empty reset branches (#8005) (#8064). [Yogish Sekhar]
* Fix missing verilator_coverage in cmake install (#8008) (#8011).
* Fix dist inside a foreach nested in a constraint if (#8016).
* Fix gcov dump on warnings discarding later coverage counts (#8017). [Yilou Wang]
* Fix unique constraint crash, guards, and size (#8018).
* Fix Linux peak memory stat to use VmHWM (#8022) (#8070). [Tyrone Marhguy]
* Fix assignment pattern key constant expressions (#8029) (#8030). [Josep Sans]
* Fix skipping optimization for unpacked data types (#8031). [Kornel Uriasz, Antmicro Ltd.]
* Fix gate deduplication with function arguments (#8038). [JOTEGO]
* Fix conditional expression with parameterized classes (#8039). [Pawel Klopotek, Antmicro Ltd.]
* Fix array size references in constraints (#8040). [Kornel Uriasz, Antmicro Ltd.]
* Fix mid-range consecutive repetition rejects (#8041). [Artur Bieniek, Antmicro Ltd.]
* Fix write access to dynamic arrays (#8043). [Bartosz Skorowski, Antmicro Ltd.]
* Fix nondeterminism in trace stage (#8046). [Sumanth Kadiyala]
* Fix force helpers in multiply instantiated modules (#8047). [Artur Bieniek, Antmicro Ltd.]
* Fix pin to non-existent port with PINNOTFOUND suppressed (#8051) (#8052). [Nikolai Kumar]
* Fix wildcard equality against a 4-state constant in an assertion (#8056) (#8057). [Nikolai Kumar]
* Fix untyped datatype error on sampling functions with property arguments (#8060). [Artur Bieniek, Antmicro Ltd.]
* Fix VPI cbValueChange for 1-bit select (#8063). [Bartłomiej Chmiel, Antmicro Ltd.]
* Fix skipping non-constrained enum constraining when inside object of other class (#8075). [Kornel Uriasz, Antmicro Ltd.]
* Fix unintended side-effect insertion on associative array read (#8077). [Adam Kostrzewski, Antmicro Ltd.]
* Fix undefined symbol solver error (#8080). [Igor Zaworski, Antmicro Ltd.]
* Fix unpacked array element force with procedural assign (#8084) (#8085). [Nikolai Kumar]
* Fix hierarchical class scope resolution (#8094). [Artur Bieniek, Antmicro Ltd.]
* Fix the clang compilation using precompiled headers (#8105). [Demin Han]
* Fix invalid typedef (#8106). [Adam Kostrzewski, Antmicro Ltd.]
* Fix R/W references to random builtin function seeds (#8112). [Geza Lore, Testorrent USA, Inc.]
* Fix COVERIGN on SVA goto repetition (#8118). [Artur Bieniek, Antmicro Ltd.]
Verilator 5.050 2026-07-01
==========================
@ -5738,7 +5857,7 @@ Verilator 3.104 2003-04-30
**Major:**
* Indicate direction of ports with VL_IN and VL_OUT.
* Allow $c32, etc, to specify width of the $c statement for VCS.
* Allow $c32, etc, to specify width of the $c statement.
* Numerous performance improvements, worth about 25%
**Minor:**

View File

@ -536,6 +536,7 @@ PY_PROGRAMS = \
src/vlcovgen \
test_regress/*.py \
test_regress/t/*.pf \
test_regress/t/randomize_solver_tamper.py \
# Python files, subject to format but not lint
PY_FILES = \

View File

@ -636,6 +636,7 @@ description of these arguments.
=for VL_SPHINX_EXTRACT "_build/gen/args_verilated.rst"
+verilator+assert+lock Lock assertion status changes at startup
+verilator+coverage+file+<filename> Set coverage output filename
+verilator+debug Enable debugging
+verilator+debugi+<value> Enable debugging at a level

90
ci/ci-build.bash Executable file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env bash
# DESCRIPTION: Verilator: CI build job script
#
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
################################################################################
# Executed in the 'build' stage.
################################################################################
# Destructive to the checkout (reconfigures and rebuilds); never run locally
if [ "$GITHUB_ACTIONS" != "true" ]; then
echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2
exit 1
fi
set -e
set -x
source "$(dirname "$0")/ci-common.bash"
################################################################################
# Parse arguments
OPT_ASAN=0
OPT_CCWARN=0
OPT_GCOV=0
OPT_LIGHT_DEBUG=0
OPT_COMPILER=
OPT_PREFIX=
while [ $# -gt 0 ]; do
case "$1" in
--asan) OPT_ASAN=1 ;;
--ccwarn) OPT_CCWARN=1 ;;
--compiler)
[ $# -ge 2 ] || fatal "--compiler requires an argument"
OPT_COMPILER="$2"
shift
;;
--gcov) OPT_GCOV=1 ;;
--light-debug) OPT_LIGHT_DEBUG=1 ;;
--prefix)
[ $# -ge 2 ] || fatal "--prefix requires an argument"
OPT_PREFIX="$2"
shift
;;
*) fatal "Unknown option: '$1'" ;;
esac
shift
done
[ -n "$OPT_COMPILER" ] || fatal "--compiler is required"
[ -n "$OPT_PREFIX" ] || fatal "--prefix is required"
# Map the compiler name to the executable name configure CXX expects
case "$OPT_COMPILER" in
clang) CXX=clang++ ;;
gcc) CXX=g++ ;;
*) fatal "Unknown compiler: '$OPT_COMPILER'" ;;
esac
################################################################################
# Configure
CONFIGURE_ARGS="--prefix=$OPT_PREFIX --enable-longtests"
if [ "$OPT_CCWARN" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-ccwarn"
fi
if [ "$OPT_ASAN" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-asan"
CXX="$CXX -DVL_LEAK_CHECKS"
fi
if [ "$OPT_GCOV" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-gcov"
fi
if [ "$OPT_LIGHT_DEBUG" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-light-debug"
fi
autoconf
./configure $CONFIGURE_ARGS CXX="$CXX"
################################################################################
# Build
ccache -z
BUILD_START=$SECONDS
"$MAKE" -j "$NPROC" -k
ccache -svv
ccache --evict-older-than "$((SECONDS - BUILD_START + 60))s"
ccache -svv

35
ci/ci-common.bash Normal file
View File

@ -0,0 +1,35 @@
# DESCRIPTION: Verilator: CI common definitions, sourced by the stage scripts
#
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
################################################################################
# Common definitions sourced by the CI stage scripts; not executed directly.
################################################################################
fatal() {
echo "ERROR: $(basename "$0"): $1" >&2; exit 1;
}
case "$(uname -s)" in
Linux)
HOST_OS=linux
MAKE=make
NPROC=$(nproc)
# Distro id/version; subshell keeps os-release out of our namespace
if [ -r /etc/os-release ]; then
DISTRO_ID=$(. /etc/os-release; echo "$ID")
DISTRO_VERSION=$(. /etc/os-release; echo "$VERSION_ID")
fi
;;
Darwin)
HOST_OS=macOS
MAKE=make
NPROC=$(sysctl -n hw.logicalcpu)
;;
*)
fatal "Unknown host OS: '$(uname -s)'"
;;
esac
export MAKE

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
# DESCRIPTION: Verilator: CI dependency install script
#
# SPDX-FileCopyrightText: 2020 Geza Lore
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
################################################################################
@ -10,141 +10,168 @@
# required by the particular build stage.
################################################################################
# Installs system packages with sudo; never run locally
if [ "$GITHUB_ACTIONS" != "true" ]; then
echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2
exit 1
fi
set -e
set -x
cd $(dirname "$0")/..
source "$(dirname "$0")/ci-common.bash"
################################################################################
# Parse arguments
# Which stage to install dependencies for: 'build' or 'test'
STAGE="$1"
################################################################################
# Install dependencies
# Avoid occasional cpan failures "Issued certificate has expired."
export PERL_LWP_SSL_VERIFY_HOSTNAME=0
echo "check_certificate = off" >> ~/.wgetrc
fatal() {
echo "ERROR: $(basename "$0"): $1" >&2; exit 1;
}
if [ "$CI_OS_NAME" = "linux" ]; then
MAKE=make
elif [ "$CI_OS_NAME" = "osx" ]; then
MAKE=make
elif [ "$CI_OS_NAME" = "freebsd" ]; then
MAKE=gmake
else
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fi
if [ "$CI_OS_NAME" = "linux" ]; then
if [ "$HOST_OS" = "linux" ]; then
# Avoid slow "processing triggers for man db"
echo "path-exclude /usr/share/doc/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
echo "path-exclude /usr/share/man/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
elif [ "$HOST_OS" = "macOS" ]; then
# The macos runner image ships an untrusted third-party tap we don't use;
# untap it so brew stops emitting a tap-trust warning. Force + '|| true' since
# untap fails if a formula was installed from it, which is harmless here.
brew untap --force aws/tap || true
fi
install-wavediff() {
source ci/docker/buildenv/wavetools.conf
local _base_url="https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}"
local _platform
if [ "$CI_OS_NAME" = "linux" ]; then
if [ "$HOST_OS" = "linux" ]; then
_platform="linux-x86_64"
elif [ "$CI_OS_NAME" = "osx" ]; then
elif [ "$HOST_OS" = "macOS" ]; then
_platform="macos-arm64"
elif [ "$CI_OS_NAME" = "windows" ]; then
_platform="windows-x86_64"
else
echo "WARNING: No wavetools binary available for CI_OS_NAME=$CI_OS_NAME, skipping"
echo "WARNING: No wavetools binary available for HOST_OS=$HOST_OS, skipping"
return 0
fi
local _tmpdir
_tmpdir=$(mktemp -d)
local _archive="wavetools-${WAVETOOLS_VERSION}-${_platform}"
if [ "$CI_OS_NAME" = "windows" ]; then
wget -q -O "${_tmpdir}/${_archive}.zip" "${_base_url}/${_archive}.zip"
unzip -o "${_tmpdir}/${_archive}.zip" -d "${_tmpdir}"
else
wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz"
tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}"
fi
wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz"
tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}"
sudo cp "${_tmpdir}/${_archive}/wavediff" /usr/local/bin/wavediff
rm -rf "${_tmpdir}"
}
if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then
if [ "$STAGE" = "build" ]; then
##############################################################################
# Dependencies of jobs in the 'build' stage, i.e.: packages required to
# build Verilator
if [ "$CI_OS_NAME" = "linux" ]; then
sudo apt-get update ||
sudo apt-get update
sudo apt-get install --yes ccache help2man libfl-dev ||
sudo apt-get install --yes ccache help2man libfl-dev
if [[ ! "$CI_RUNS_ON" =~ "ubuntu-22.04" ]]; then
# Some conflict of libunwind verison on 22.04, can live without it for now
sudo apt-get install --yes libjemalloc-dev ||
sudo apt-get install --yes libjemalloc-dev
fi
if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then
if [[ ! "$CI_RUNS_ON" =~ "-riscv" ]]; then
sudo apt-get install --yes libsystemc libsystemc-dev ||
sudo apt-get install --yes libsystemc libsystemc-dev
if [ "$HOST_OS" = "linux" ]; then
if [ "$DISTRO_ID" = "ubuntu" ]; then
PACKAGES=(
bear
ccache
help2man
libfl-dev
libsystemc-dev
mold
)
# libunwind conflict on 22.04, can live without libjemalloc there
if [ "$DISTRO_VERSION" != "22.04" ]; then
PACKAGES+=(libjemalloc-dev)
fi
sudo apt-get update ||
sudo apt-get update
sudo apt-get install --yes "${PACKAGES[@]}" ||
sudo apt-get install --yes "${PACKAGES[@]}"
fi
if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then
sudo apt-get install --yes bear mold ||
sudo apt-get install --yes bear mold
fi
elif [ "$CI_OS_NAME" = "osx" ]; then
elif [ "$HOST_OS" = "macOS" ]; then
PACKAGES=(
autoconf
bison
ccache
flex
gperftools
help2man
perl
)
brew update ||
brew update
brew install ccache perl gperftools autoconf bison flex help2man ||
brew install ccache perl gperftools autoconf bison flex help2man
elif [ "$CI_OS_NAME" = "freebsd" ]; then
sudo pkg install -y autoconf bison ccache gmake perl5
brew install "${PACKAGES[@]}" ||
brew install "${PACKAGES[@]}"
else
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fatal "Unknown HOST_OS: '$HOST_OS'"
fi
if [ -n "$CCACHE_DIR" ]; then
mkdir -p "$CCACHE_DIR"
fi
elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then
elif [ "$STAGE" = "test" ]; then
##############################################################################
# Dependencies of jobs in the 'test' stage, i.e.: packages required to
# run the tests
if [ "$CI_OS_NAME" = "linux" ]; then
sudo apt-get update ||
sudo apt-get update
# libfl-dev needed for internal coverage's test runs
sudo apt-get install --yes gdb gtkwave lcov libfl-dev ccache jq z3 ||
sudo apt-get install --yes gdb gtkwave lcov libfl-dev ccache jq z3
# Required for test_regress/t/t_dist_attributes.py
if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then
sudo apt-get install --yes python3-clang mold ||
sudo apt-get install --yes python3-clang mold
if [ "$HOST_OS" = "linux" ]; then
if [ "$DISTRO_ID" = "ubuntu" ]; then
PACKAGES=(
ccache
gdb
jq
lcov
libfl-dev
libsystemc-dev
mold
python3-clang
z3
)
sudo apt-get update ||
sudo apt-get update
sudo apt-get install --yes "${PACKAGES[@]}" ||
sudo apt-get install --yes "${PACKAGES[@]}"
fi
if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then
if [[ ! "$CI_RUNS_ON" =~ "-riscv" ]]; then
sudo apt-get install --yes libsystemc libsystemc-dev ||
sudo apt-get install --yes libsystemc libsystemc-dev
fi
fi
elif [ "$CI_OS_NAME" = "osx" ]; then
elif [ "$HOST_OS" = "macOS" ]; then
PACKAGES=(
ccache
jq
perl
z3
)
brew update ||
brew update
# brew cask install gtkwave # fst2vcd hangs at launch, so don't bother
brew install ccache perl jq z3
elif [ "$CI_OS_NAME" = "freebsd" ]; then
# fst2vcd fails with "Could not open '<input file>', exiting."
sudo pkg install -y ccache gmake perl5 python3 jq z3
brew install "${PACKAGES[@]}" ||
brew install "${PACKAGES[@]}"
else
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fatal "Unknown HOST_OS: '$HOST_OS'"
fi
# Common installs
install-wavediff
# Workaround -fsanitize=address crash
sudo sysctl -w vm.mmap_rnd_bits=28
elif [ "$STAGE" = "lint-py" ]; then
# nodist/clang_check_attributes.
if [ "$HOST_OS" = "linux" ] && [ "$DISTRO_ID" = "ubuntu" ]; then
PACKAGES=(
python3-clang # Not run, but importers are linted
)
sudo apt-get update ||
sudo apt-get update
sudo apt-get install --yes "${PACKAGES[@]}" ||
sudo apt-get install --yes "${PACKAGES[@]}"
fi
else
##############################################################################
# Unknown build stage
fatal "Unknown CI_BUILD_STAGE_NAME: '$CI_BUILD_STAGE_NAME'"
fatal "Unknown stage '$STAGE' (expected 'build', 'test' or 'lint-py')"
fi
# Report where the tools we may have installed live (ok if some are missing)
set +x
echo "Tools:"
for bin in autoconf bear bison ccache flex gdb help2man jq lcov mold perl wavediff z3; do
echo -n " $bin: "
which "$bin" || echo "Not found"
done

View File

@ -34,9 +34,14 @@ for RUN_ID in ${PR_RUN_IDS//,/ }; do
cat ${ARTIFACTS_DIR}/body.txt
gh pr comment $(cat ${ARTIFACTS_DIR}/pr-number.txt) --body-file ${ARTIFACTS_DIR}/body.txt
# Get the artifact ID
ARTIFACT_ID=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id')
# Get the artifact IDs. Note there can be more than one artifact named
# 'pr-notification' for a single run, as the artifacts endpoint lists
# artifacts across all run attempts, and a re-run uploads a new one while
# keeping the previous attempt's artifact.
ARTIFACT_IDS=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id')
# Delete it, so we only notify once
gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}"
# Delete them all, so we only notify once
for ARTIFACT_ID in ${ARTIFACT_IDS}; do
gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}"
done
done

View File

@ -1,208 +0,0 @@
#!/usr/bin/env bash
# DESCRIPTION: Verilator: CI main job script
#
# SPDX-FileCopyrightText: 2020 Geza Lore
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
################################################################################
# This is the main script executed in the 'script' phase by all jobs. We use a
# single script to keep the CI setting simple. We pass job parameters via
# environment variables using 'env' keys.
################################################################################
set -e
set -x
fatal() {
echo "ERROR: $(basename "$0"): $1" >&2; exit 1;
}
if [ "$CI_OS_NAME" = "linux" ]; then
export MAKE=make
NPROC=$(nproc)
elif [ "$CI_OS_NAME" = "osx" ]; then
export MAKE=make
NPROC=$(sysctl -n hw.logicalcpu)
# Disable ccache, doesn't always work in GitHub Actions
export OBJCACHE=
elif [ "$CI_OS_NAME" = "freebsd" ]; then
export MAKE=gmake
NPROC=$(sysctl -n hw.ncpu)
else
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fi
NPROC=$(expr $NPROC '+' 1)
if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then
##############################################################################
# Build verilator
autoconf
CONFIGURE_ARGS="--enable-longtests --enable-ccwarn"
if [ "$CI_DEV_ASAN" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-asan"
CXX="$CXX -DVL_LEAK_CHECKS"
fi
if [ "$CI_DEV_GCOV" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-gcov"
fi
./configure $CONFIGURE_ARGS --prefix="$INSTALL_DIR"
ccache -z
"$MAKE" -j "$NPROC" -k
# 22.04: ccache -s -v
ccache -s
if [ "$CI_OS_NAME" = "osx" ]; then
file bin/verilator_bin
file bin/verilator_bin_dbg
md5 bin/verilator_bin
md5 bin/verilator_bin_dbg
stat bin/verilator_bin
stat bin/verilator_bin_dbg
fi
elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then
##############################################################################
# Run tests
export VERILATOR_TEST_NO_CONTRIBUTORS=1 # Separate workflow check
export VERILATOR_TEST_NO_LINT_PY=1 # Separate workflow check
if [ "$CI_OS_NAME" = "osx" ]; then
export VERILATOR_TEST_NO_GDB=1 # Pain to get GDB to work on OS X
# TODO below may no longer be required as configure checks for -pg
export VERILATOR_TEST_NO_GPROF=1 # Apple Clang has no -pg
# export PATH="/Applications/gtkwave.app/Contents/Resources/bin:$PATH" # fst2vcd
file bin/verilator_bin
file bin/verilator_bin_dbg
md5 bin/verilator_bin
md5 bin/verilator_bin_dbg
stat bin/verilator_bin
stat bin/verilator_bin_dbg
# For some reason, the dbg exe is corrupted by this point ('file' reports
# it as data rather than a Mach-O). Unclear if this is an OS X issue or
# CI's. Remove the file and re-link...
rm bin/verilator_bin_dbg
"$MAKE" -j "$NPROC" -k
elif [ "$CI_OS_NAME" = "freebsd" ]; then
export VERILATOR_TEST_NO_GDB=1 # Disable for now, ideally should run
# TODO below may no longer be required as configure checks for -pg
export VERILATOR_TEST_NO_GPROF=1 # gprof is a bit different on FreeBSD, disable
fi
TEST_REGRESS=test_regress
if [ "$CI_RELOC" == 1 ]; then
# Testing that the installation is relocatable.
"$MAKE" install
mkdir -p "$RELOC_DIR"
mv "$INSTALL_DIR" "$RELOC_DIR/relocated-install"
export VERILATOR_ROOT="$RELOC_DIR/relocated-install/share/verilator"
TEST_REGRESS="$RELOC_DIR/test_regress"
mv test_regress "$TEST_REGRESS"
NODIST="$RELOC_DIR/nodist"
mv nodist "$NODIST"
# Feeling brave?
find . -delete
ls -la .
fi
# Run the specified test
ccache -z
case $TESTS in
dist-vlt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=0/4
;;
dist-vlt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=1/4
;;
dist-vlt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=2/4
;;
dist-vlt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=3/4
;;
vltmt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=0/3
;;
vltmt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=1/3
;;
vltmt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=2/3
;;
coverage-dist)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist"
;;
coverage-vlt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=0/10
;;
coverage-vlt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=1/10
;;
coverage-vlt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=2/10
;;
coverage-vlt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=3/10
;;
coverage-vlt-4)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=4/10
;;
coverage-vlt-5)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=5/10
;;
coverage-vlt-6)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=6/10
;;
coverage-vlt-7)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=7/10
;;
coverage-vlt-8)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=8/10
;;
coverage-vlt-9)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=9/10
;;
coverage-vltmt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=0/10
;;
coverage-vltmt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=1/10
;;
coverage-vltmt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=2/10
;;
coverage-vltmt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=3/10
;;
coverage-vltmt-4)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=4/10
;;
coverage-vltmt-5)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=5/10
;;
coverage-vltmt-6)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=6/10
;;
coverage-vltmt-7)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=7/10
;;
coverage-vltmt-8)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=8/10
;;
coverage-vltmt-9)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=9/10
;;
*)
fatal "Unknown TESTS: $TESTS"
;;
esac
# To see load average (1 minute, 5 minute, 15 minute)
uptime
# 22.04: ccache -s -v
ccache -s
else
##############################################################################
# Unknown build stage
fatal "Unknown CI_BUILD_STAGE_NAME: '$CI_BUILD_STAGE_NAME'"
fi

169
ci/ci-test.bash Executable file
View File

@ -0,0 +1,169 @@
#!/usr/bin/env bash
# DESCRIPTION: Verilator: CI test job script
#
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
################################################################################
# Executed in the 'test' stage.
################################################################################
# Destructive to the checkout (wipes most of it); never run locally
if [ "$GITHUB_ACTIONS" != "true" ]; then
echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2
exit 1
fi
set -e
set -x
source "$(dirname "$0")/ci-common.bash"
################################################################################
# Parse arguments
OPT_RELOC=
OPT_SUITE=
while [ $# -gt 0 ]; do
case "$1" in
--reloc)
[ $# -ge 2 ] || fatal "--reloc requires an argument"
OPT_RELOC="$2"
shift
;;
--suite)
[ $# -ge 2 ] || fatal "--suite requires an argument"
OPT_SUITE="$2"
shift
;;
*) fatal "Unknown option: '$1'" ;;
esac
shift
done
[ -n "$OPT_SUITE" ] || fatal "--suite is required"
################################################################################
# Run tests
export VERILATOR_TEST_NO_CONTRIBUTORS=1 # Separate workflow check
export VERILATOR_TEST_NO_LINT_PY=1 # Separate workflow check
if [ "$HOST_OS" = "macOS" ]; then
export VERILATOR_TEST_NO_GDB=1 # No working GDB on macOS
fi
TEST_REGRESS=test_regress
if [ -n "$OPT_RELOC" ]; then
# Testing that the installation is relocatable.
"$MAKE" install
# Install prefix, as configured into the Makefile at build time
INSTALL_DIR=$(sed -n 's/^prefix = //p' Makefile)
mkdir -p "$OPT_RELOC"
mv "$INSTALL_DIR" "$OPT_RELOC/relocated-install"
export VERILATOR_ROOT="$OPT_RELOC/relocated-install/share/verilator"
TEST_REGRESS="$OPT_RELOC/test_regress"
mv test_regress "$TEST_REGRESS"
NODIST="$OPT_RELOC/nodist"
mv nodist "$NODIST"
# Delete everything else, but keep the CI infrastructure
find . -mindepth 1 -maxdepth 1 ! -name .github ! -name ci -exec rm -rf {} +
ls -la .
fi
# Run the specified suite
ccache -z
TEST_START=$SECONDS
case $OPT_SUITE in
dist-vlt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=0/4
;;
dist-vlt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=1/4
;;
dist-vlt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=2/4
;;
dist-vlt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=3/4
;;
vltmt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=0/3
;;
vltmt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=1/3
;;
vltmt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=2/3
;;
coverage-dist)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist"
;;
coverage-vlt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=0/10
;;
coverage-vlt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=1/10
;;
coverage-vlt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=2/10
;;
coverage-vlt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=3/10
;;
coverage-vlt-4)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=4/10
;;
coverage-vlt-5)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=5/10
;;
coverage-vlt-6)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=6/10
;;
coverage-vlt-7)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=7/10
;;
coverage-vlt-8)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=8/10
;;
coverage-vlt-9)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=9/10
;;
coverage-vltmt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=0/10
;;
coverage-vltmt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=1/10
;;
coverage-vltmt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=2/10
;;
coverage-vltmt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=3/10
;;
coverage-vltmt-4)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=4/10
;;
coverage-vltmt-5)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=5/10
;;
coverage-vltmt-6)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=6/10
;;
coverage-vltmt-7)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=7/10
;;
coverage-vltmt-8)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=8/10
;;
coverage-vltmt-9)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=9/10
;;
*)
fatal "Unknown suite: $OPT_SUITE"
;;
esac
ccache -svv
ccache --evict-older-than "$((SECONDS - TEST_START + 60))s"
ccache -svv
uptime # To see load average

View File

@ -6,19 +6,33 @@
Set-PSDebug -Trace 1
if (-Not (Test-Path $PWD/../.ccache/win_bison.exe)) {
$NPROC = $env:NUMBER_OF_PROCESSORS
# Absolute path for the win_flex_bison install; CMake reads this from the environment
$env:WIN_FLEX_BISON = "$(Resolve-Path ..)/win_flex_bison"
if (-Not (Test-Path $env:WIN_FLEX_BISON/win_bison.exe)) {
git clone --depth 1 https://github.com/lexxmark/winflexbison
cd winflexbison
mkdir build
cd build
cmake .. --install-prefix $PWD/../../../.ccache
cmake --build . --config Release -j 3
cmake --install . --prefix $PWD/../../../.ccache
cmake .. --install-prefix $env:WIN_FLEX_BISON
cmake --build . --config Release -j $NPROC
cmake --install . --prefix $env:WIN_FLEX_BISON
cd ../..
}
# Enter the MSVC developer shell so cl/link are on PATH for the Ninja generator
$VsPath = & "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" -latest -products * -property installationPath
Import-Module "$VsPath/Common7/Tools/Microsoft.VisualStudio.DevShell.dll"
Enter-VsDevShell -VsInstallPath $VsPath -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64"
mkdir build
cd build
cmake .. --install-prefix $PWD/../install
cmake --build . --config Release -j 3
# Ninja saturates all cores; the MSBuild generator only parallelizes across
# projects and Verilator is a single target, so it compiles serially. /Od skips
# optimization: this job only checks that Verilator builds and can verilate an
# example, so an optimized binary is not needed and the codegen time dominates.
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release --install-prefix $PWD/../install "-DCMAKE_CXX_FLAGS_RELEASE=/Od /DNDEBUG"
cmake --build .
cmake --install . --prefix $PWD/../install

View File

@ -7,13 +7,17 @@
Set-PSDebug -Trace 1
$NPROC = $env:NUMBER_OF_PROCESSORS
cd install
$Env:VERILATOR_ROOT=$PWD
cd examples/cmake_tracing_c
mkdir build
cd build
cmake ..
cmake --build . --config Release -j 3
# /Od skips optimization; this only checks the example verilates and builds, so
# an optimized binary is not needed (see ci-win-compile.ps1).
cmake .. "-DCMAKE_CXX_FLAGS_RELEASE=/Od /DNDEBUG"
cmake --build . --config Release -j $NPROC
# TODO put this back in, see issue# 5163
# Release/example.exe

View File

@ -12,7 +12,7 @@
# Then 'make maintainer-dist'
#AC_INIT([Verilator],[#.### YYYY-MM-DD])
#AC_INIT([Verilator],[#.### devel])
AC_INIT([Verilator],[5.050 2026-07-01],
AC_INIT([Verilator],[5.051 devel],
[https://verilator.org],
[verilator],[https://verilator.org])
@ -129,6 +129,21 @@ AC_ARG_ENABLE([dev-gcov],
AC_SUBST(CFG_WITH_DEV_GCOV)
AC_MSG_RESULT($CFG_WITH_DEV_GCOV)
# Flag to reduce the debug info in the debug executable to minimize its size
AC_MSG_CHECKING(whether to reduce debug info in the debug executable)
AC_ARG_ENABLE([light-debug],
[AS_HELP_STRING([--enable-light-debug],
[Reduce the amount of debug information in the debug
Verilator executable to minimize its size. Enable
slight optimization. Enough for backtraces only.])],
[case "${enableval}" in
yes) CFG_WITH_LIGHT_DEBUG=yes ;;
no) CFG_WITH_LIGHT_DEBUG=no ;;
*) AC_MSG_ERROR([bad value '${enableval}' for --enable-light-debug]) ;;
esac],
CFG_WITH_LIGHT_DEBUG=no)
AC_MSG_RESULT($CFG_WITH_LIGHT_DEBUG)
# Special Substitutions - CFG_WITH_DEFENV
AC_MSG_CHECKING(whether to use hardcoded paths)
AC_ARG_ENABLE([defenv],
@ -492,6 +507,11 @@ _MY_CXX_CHECK_CORO_SET(CFG_CXXFLAGS_COROUTINES,-std=gnu++20)
AC_SUBST(CFG_CXXFLAGS_COROUTINES)
AC_SUBST(HAVE_COROUTINES)
# Check if the C++ compiler supports ThreadSanitizer
_MY_CXX_CHECK_IFELSE(-fsanitize=thread,
[AC_DEFINE([HAVE_TSAN],[1],[Defined if ThreadSanitizer is supported by $CXX])])
AC_SUBST(HAVE_TSAN)
# Flags for compiling Verilator internals including parser always
if test "$CFG_WITH_DEV_ASAN" = "yes"; then
_MY_CXX_CHECK_IFELSE(-fsanitize=address -DVL_ASAN,
@ -518,15 +538,27 @@ _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_PARSER,-Wno-unused)
AC_SUBST(CFG_CXXFLAGS_PARSER)
# Flags for compiling the debug version of Verilator (in addition to above CFG_CXXFLAGS_SRC)
if test "$CFG_WITH_DEV_GCOV" = "no"; then # Do not optimize for the coverage build
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-Og)
if test "$CFG_WITH_LIGHT_DEBUG" = "yes"; then
# Slight optimization and minimal compressed debug info. This is enough for
# --gdb/--gdbbt backtraces, but omits the bulk of the debug info, which
# significantly reduces object file sizes. For CI or release builds.
if test "$CFG_WITH_DEV_GCOV" = "no"; then # Do not optimize for the coverage build
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-Og)
fi
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb1)
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-fdebug-info-for-profiling) # For fully-qualified names with clang
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-gz)
else
# Full debug: no optimization, full debug info, for development.
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-O0)
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb)
fi
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb)
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-gz)
AC_SUBST(CFG_CXXFLAGS_DBG)
# Flags for linking the debug version of Verilator (in addition to above CFG_LDFLAGS_SRC)
_MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_DBG,-gz)
if test "$CFG_WITH_LIGHT_DEBUG" = "yes"; then
_MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_DBG,-gz)
fi
AC_SUBST(CFG_LDFLAGS_DBG)
# Flags for compiling the optimized version of Verilator (in addition to above CFG_CXXFLAGS_SRC)

View File

@ -7,6 +7,7 @@ contribution terms including the AI policy in ``docs/CONTRIBUTING.rst``.
Please see the Verilator manual for 200+ additional contributors. Thanks to all.
24bit-xjkp
404allen404
Adam Bagley
Adam Kostrzewski
@ -14,6 +15,7 @@ Adrian Sampson
Adrien Le Masle
أحمد المحمودي (Ahmed El-Mahmoudy)
Aidan McNay
Aisha Salimgereyeva
Aleksander Kiryk
Alex Chadwick
Alex Solomatnikov
@ -26,16 +28,20 @@ Andrei Kostovski
Andrew Miloradovsky
Andrew Nolte
Andrew Voznytsa
Andrii Andrieiev
anonkey
Anthony Donlon
Anthony Moore
apocelipes
Arkadiusz Kozdra
Arthur Rosa
Artur Bieniek
AUDIY
Aylon Chaim Porat
Bartosz Skorowski
Bartłomiej Chmiel
Benjamin Collier
BRDR LIFE
Brian Li
Cameron Kirk
Cameron Waite
@ -53,27 +59,35 @@ Dan Ruelas-Petrisko
Daniel Bates
Danny Oler
Dave Sargeant
David Garau
David Horton
David Ledger
David Metz
David Stanford
David Turner
dependabot[bot]
Dercury
Demin Han
Diego Roux
Dominick Grochowina
Don Williamson
Dragon-Git
Drew Ranck
Drew Taussig
Driss Hafdi
Edgar E. Iglesias
em2machine
emmettifelts
Eric Mejdrich
Eric Müller
Eric Rippey
Eunseo Song
Eryk Szpotański
Ethan Sifferman
Eunseo Song
Eyck Jentzsch
Fabian Keßler-Schulz
Fan Shupei
february cozzocrea
Felix Neumärker
Felix Yan
Frans Skarman
@ -108,6 +122,8 @@ Ilya Barkov
Iru Cai
Ivan Vnučec
Iztok Jeras
JAEUK LEE
Jaeuk Lee (이재욱)
Jake Merdich
Jakub Michalski
Jakub Wasilewski
@ -122,6 +138,8 @@ Jamie Iles
Jan Van Winkel
Jean Berniolles
Jean-Nicolas Strauss
Jeffrey Song
jeffrey.song
Jens Yuechao Liu
Jeremy Bennett
Jesse Taube
@ -147,6 +165,7 @@ Josep Sans
Joseph Nwabueze
Josh Redford
Joshua Leahy
JOTEGO (Jose Tejada)
Julian Carrier
Julian Daube
Julie Schwartz
@ -183,6 +202,7 @@ Maarten De Braekeleer
Maciej Sobkowski
Marcel Chang
Marco Bartoli
Marco Brambilla
Marco Widmer
Mariusz Glebocki
Markus Krause
@ -208,6 +228,7 @@ Mladen Slijepcevic
Morten Borup Petersen
Mostafa Gamal
Moubarak Jeje
Muzaffer Kal
Nandu Raj
Natan Kreimer
Nathan Graybeal
@ -218,8 +239,10 @@ Nikolai Kumar
Nikolay Puzanov
Nolan Poe
Oleh Maksymenko
Patrick Creighton
Patrick Stewart
Paul Bowen-Huggett
Paul Campbell
Paul Swirhun
Paul Wright
Pawel Jewstafjew
@ -262,15 +285,19 @@ Sergey Chusov
Sergey Fedorov
Sergi Granell
Seth Pellegrino
Shashvat Prabhu
Shogo Yamazaki
Shou-Li Hsu
spomatasmd
Srinivasan Venkataramanan
Stefan Wallentowitz
Stephen Henry
Steven Hugg
Stuart Morris
sumpster
Szymon Gizler
Sören Tempel
Sumanth Kadiyala
Teng Huang
Thomas Aldrian
Thomas Brown
@ -291,6 +318,7 @@ Tracy Narine
Trung Nguyen
Tudor Timi
Tymoteusz Blazejczyk
Tyrone Marhguy
Udaya Raj Subedi
Udi Finkelstein
Unai Martinez-Corral
@ -308,9 +336,11 @@ Wolfgang Mayerwieser
Xi Zhang
Yan Xu
Yangyu Chen
Yilin Li
Yilou Wang
Yinan Xu
Yoda Lee
Yogish Sekhar
Yossi Nivin
Yu-Sheng Lin
Yuri Victorovich
@ -321,17 +351,7 @@ Zhen Yan
Zhou Shen
Zhouyi Shen
Zixi Li
apocelipes
dependabot[bot]
february cozzocrea
sumpster
em2machine
emmettifelts
Zubin Jain
Àlex Torregrosa
Ícaro Lima
Yogish Sekhar
24bit-xjkp
Zubin Jain
Muzaffer Kal
Yilin Li
Shashvat Prabhu
Sunimali Rathnayake

View File

@ -0,0 +1,6 @@
.. comment: generated by t_lint_multidriven_proc_bad
.. code-block:: sv
:linenos:
always @(posedge clk) q <= d;
always @(posedge clk) q <= ~d;

View File

@ -0,0 +1,8 @@
.. comment: generated by t_lint_multidriven_proc_bad
.. code-block::
%Warning-MULTIDRIVENPROC: example.v:1:25 Variable written to in always block also written by another always block: 'q'
: ... note: In instance 't'
example.v:1:25
16 | always @(posedge clk) q <= ~d;
| ^

View File

@ -0,0 +1,6 @@
.. comment: generated by t_lint_similarname_bad
.. code-block:: sv
:linenos:
reg i;
wire I;

View File

@ -0,0 +1,9 @@
.. comment: generated by t_lint_similarname_bad
.. code-block::
%Warning-SIMILARNAME: example.v:1:8 Declaration overlaps another with different case: 'I'
13 | wire I;
| ^
example.v:1:7 ... Location of original declaration
12 | reg i;
| ^

View File

@ -92,13 +92,17 @@ model.
Refer to ``examples/make_tracing_c`` in the distribution for a detailed
commented example.
Top level IO signals are read and written as members of the model. You call
the model's ``eval()`` method to evaluate the model. When the simulation is
complete call the model's ``final()`` method to execute any SystemVerilog
final blocks, and complete any assertions. If using :vlopt:`--timing`,
there are two additional functions for checking if there are any events
pending in the simulation due to delays, and for retrieving the simulation
time of the next delayed event. See :ref:`Evaluation Loop`.
Top level IO signals are read and written as members of the model. All
inputs must be sanitized, that is have no bits set above those
corresponding to the width of the Verilog construct;
:vlopt:`--runtime-debug` will assert this is correct.
Call the model's ``eval()`` method to evaluate the model. When the
simulation is complete call the model's ``final()`` method to execute any
SystemVerilog final blocks, and complete any assertions. If using
:vlopt:`--timing`, there are two additional functions for checking if there
are any events pending in the simulation due to delays, and for retrieving
the simulation time of the next delayed event. See :ref:`Evaluation Loop`.
Connecting to SystemC
@ -179,7 +183,7 @@ DPI Example
In the SYSTEMC example above, if you wanted to import C++ functions into
Verilog, put in our.v:
.. code-block:: sv
.. code-block::
import "DPI-C" function int add (input int a, input int b);
@ -211,7 +215,7 @@ Verilator extends the DPI format to allow using the same scheme to
efficiently add system functions. Use a dollar-sign prefixed system
function name for the import, but note it must be escaped.
.. code-block:: sv
.. code-block::
import "DPI-C" function integer \$myRand;

View File

@ -21,6 +21,13 @@ Summary:
Options:
.. option:: +verilator+assert+lock
Only allow command line options to disable / enable assertions.
Disables RTL from changing assertion handling via ``$asserton``,
``$assertoff``, and ``$assertcontrol``. Also prevents ``VerilatedContext*``
assertion control functions from updating assertion handling.
.. option:: +verilator+coverage+file+<filename>
When a model was Verilated using :vlopt:`--coverage`, sets the filename
@ -114,7 +121,7 @@ Options:
When a model was Verilated using :vlopt:`--x-initial unique
<--x-initial>`, sets the simulation runtime initialization technique. 0
= Reset to zeros. 1 = Reset to all-ones. 2 = Randomize. See
:ref:`Unknown States`.
:ref:`Unknown States`. Default is 0.
.. option:: +verilator+seed+<value>

View File

@ -936,7 +936,7 @@ Summary:
:file:`*.mk` files.
Feature may be one of the following: COROUTINES, DEV_ASAN, DEV_GCOV,
SYSTEMC.
SYSTEMC, TSAN.
.. option:: --getenv <variable>

View File

@ -133,7 +133,7 @@ verilator_coverage Arguments
.. option:: --filter-type <regex>
Skips records of coverage types that matches with <regex>
Keeps records of coverage types that matches with <regex>
Possible values are `toggle`, `line`, `branch`, `expr`, `covergroup`,
`user`, `fsm_state`, `fsm_arc` and a wildcard with `\*` or `?`. The
default value is `\*`.

View File

@ -294,6 +294,25 @@ or "`ifdef`"'s may break other tools.
(if appropriate :vlopt:`--coverage` flags are passed) after being
disabled earlier with :option:`/*verilator&32;coverage_off*/`.
.. option:: /*verilator&32;dpi_c_decl "<C function declaration>"*/
Specifies C function declaration that will be emitted for given DPI-C
function into the Verilator-generated __Dpi.h header, replacing the declaration
that Verilator would build by default using the Verilog function signature.
This enables use of C functions with types not specified in the
standard. For example, it enables use of functions that return ``char*``:
.. code-block:: sv
module t;
import "DPI-C" function string getenv(input string arg) /*verilator dpi_c_decl "char* getenv(const char*)"*/;
initial begin
$display("%s", getenv("HOME"));
end
endmodule
.. option:: /*verilator&32;fargs <arguments>*/
For Verilator developers only. When a source file containing these `fargs`

View File

@ -66,7 +66,7 @@ The information in this report is:
.. describe:: "allocated 123 MB"
Total memory used during simulation in megabytes.
Peak resident memory used during simulation in megabytes.
.. _benchmarking & optimization:

View File

@ -623,5 +623,5 @@ The information in this report is:
.. describe:: "allocated 123 MB"
Total memory used during build by Verilator executable (excludes
:vlopt:`--build` compiler's usage) in megabytes.
Peak resident memory used by the Verilator executable during build
(excludes :vlopt:`--build` compiler's usage) in megabytes.

View File

@ -1469,11 +1469,50 @@ List Of Warnings
q <= d;
end
A further case is when a signal named as a clocking block ``output`` is
also driven by a continuous assignment, or is named as an ``output`` of a
second clocking block. The clocking block drives the signal, so the design
and the testbench contend for it and the synchronous drive may be silently
lost. Declare the clocking block ``input`` if the intent is only to
observe the signal.
Ignoring this warning may hide clock domain crossing, timing, or
portability bugs. It may also cause longer simulation runtimes due to
reduced optimizations.
.. option:: MULTIDRIVENPROC
Warns that the whole of a variable is driven by more than one plain
``always`` block. Unlike the :option:`MULTIDRIVEN` cases, plain
``always`` blocks carry no ``always_comb``/``always_ff`` intent, so this
is legal SystemVerilog rather than an IEEE 1800 violation. It is,
however, typically a synthesis error: hardware cannot have a signal
driven by two separate sequential blocks, so the design usually will not
behave as the RTL simulation suggests.
Disabled by default as this is a code-style warning; it will simulate
correctly.
Faulty example:
.. include:: ../../docs/gen/ex_MULTIDRIVENPROC_faulty.rst
Results in:
.. include:: ../../docs/gen/ex_MULTIDRIVENPROC_msg.rst
Also warns when a signal named as a clocking block ``output`` is driven
by a plain ``always`` block. Driving a signal from both a clocking block
and a plain ``always`` block is a deliberate idiom in some testbenches,
so it is reported as MULTIDRIVENPROC rather than under the on-by-default
:option:`MULTIDRIVEN`.
To fix, drive the signal from a single ``always`` block, or use
``always_ff``/``always_comb`` if the intent is a single specialized
process.
.. option:: MULTITOP
.. TODO better example
@ -2089,6 +2128,25 @@ List Of Warnings
simulators.
.. option:: SIMILARNAME
Warns that a variable name only differs from another in lexical case.
Faulty example:
.. include:: ../../docs/gen/ex_SIMILARNAME_faulty.rst
Results in:
.. include:: ../../docs/gen/ex_SIMILARNAME_msg.rst
Disabled by default as this is a code-style warning; it will simulate
correctly.
This is a warning as some downstream VLSI tools do
not distinguish net and gate names with the same case.
.. option:: SPECIFYIGN
Warns that Verilator does not support certain constructs in

View File

@ -477,6 +477,7 @@ Syms
Synopsys
SystemC
SystemVerilog
Szpotanski
Takatsukasa
Tambe
Tarik
@ -537,6 +538,7 @@ Verilog
Vighnesh
Viktor
Vilp
VlRNG
VlWide
Vlip
Vm
@ -644,6 +646,7 @@ casez
casted
castro
cb
cbValueChange
ccache
ccall
cdc
@ -707,6 +710,7 @@ de
dearray
deassign
debugi
deduplication
defenv
defname
defparam
@ -947,6 +951,7 @@ misconnected
misconversion
misdetecting
misoptimized
misoptimizing
missized
mk
mno
@ -1174,6 +1179,7 @@ tcmalloc
tcmalloc
tenghtt
testbench
testbenches
threadsafe
threashold
timeInc

View File

@ -243,20 +243,32 @@ void vl_warn(const char* filename, int linenum, const char* hier, const char* ms
// Wrapper to call certain functions via messages when multithreaded
void VL_FINISH_MT(const char* filename, int linenum, const char* hier) VL_MT_SAFE {
VerilatedContext* const contextp = Verilated::threadContextp();
contextp->finishPendingInc();
VerilatedThreadMsgQueue::post(VerilatedMsg{[=]() { //
vl_finish(filename, linenum, hier);
contextp->finishPendingDec();
}});
}
void VL_STOP_MT(const char* filename, int linenum, const char* hier, bool maybe) VL_MT_SAFE {
// Classify now, so a queued request is pending from the moment it is posted
VerilatedContext* const contextp = Verilated::threadContextp();
const bool stop = contextp->stopRequestReserve(maybe);
if (stop) contextp->finishPendingInc();
VerilatedThreadMsgQueue::post(VerilatedMsg{[=]() { //
vl_stop_maybe(filename, linenum, hier, maybe);
contextp->stopRequestRelease();
if (stop) contextp->finishPendingDec();
}});
}
void VL_FATAL_MT(const char* filename, int linenum, const char* hier, const char* msg) VL_MT_SAFE {
VerilatedContext* const contextp = Verilated::threadContextp();
contextp->finishPendingInc();
VerilatedThreadMsgQueue::post(VerilatedMsg{[=]() { //
vl_fatal(filename, linenum, hier, msg);
contextp->finishPendingDec();
}});
}
@ -563,9 +575,10 @@ IData VL_URANDOM_SEEDED_II(IData seed) VL_MT_SAFE {
}
IData VL_SCOPED_RAND_RESET_I(int obits, uint64_t scopeHash, uint64_t salt) VL_MT_UNSAFE {
if (Verilated::threadContextp()->randReset() == 0) return 0;
const int randReset = Verilated::threadContextp()->randReset();
if (randReset == 0) return 0;
IData data = ~0;
if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize
if (randReset != 1) { // if 2, randomize
VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt};
data = rng.rand64();
}
@ -574,9 +587,10 @@ IData VL_SCOPED_RAND_RESET_I(int obits, uint64_t scopeHash, uint64_t salt) VL_MT
}
QData VL_SCOPED_RAND_RESET_Q(int obits, uint64_t scopeHash, uint64_t salt) VL_MT_UNSAFE {
if (Verilated::threadContextp()->randReset() == 0) return 0;
const int randReset = Verilated::threadContextp()->randReset();
if (randReset == 0) return 0;
QData data = ~0ULL;
if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize
if (randReset != 1) { // if 2, randomize
VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt};
data = rng.rand64();
}
@ -586,10 +600,17 @@ QData VL_SCOPED_RAND_RESET_Q(int obits, uint64_t scopeHash, uint64_t salt) VL_MT
WDataOutP VL_SCOPED_RAND_RESET_W(int obits, WDataOutP outwp, uint64_t scopeHash,
uint64_t salt) VL_MT_UNSAFE {
if (Verilated::threadContextp()->randReset() != 2) { return VL_RAND_RESET_W(obits, outwp); }
VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt};
for (int i = 0; i < VL_WORDS_I(obits) - 1; ++i) outwp[i] = rng.rand64();
outwp[VL_WORDS_I(obits) - 1] = rng.rand64() & VL_MASK_E(obits);
const int words = VL_WORDS_I(obits);
const int randReset = Verilated::threadContextp()->randReset();
if (randReset == 0) {
VL_MEMSET_ZERO_W(outwp, words);
} else if (randReset == 1) {
VL_MEMSET_ONES_W(outwp, words);
} else {
VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt};
for (int i = 0; i < words; ++i) outwp[i] = rng.rand64();
}
outwp[words - 1] &= VL_MASK_E(obits);
return outwp;
}
@ -614,30 +635,14 @@ WDataOutP VL_SCOPED_RAND_RESET_ASSIGN_W(int obits, WDataOutP outwp, uint64_t sco
}
IData VL_RAND_RESET_I(int obits) VL_MT_SAFE {
if (Verilated::threadContextp()->randReset() == 0) return 0;
const int randReset = Verilated::threadContextp()->randReset();
if (randReset == 0) return 0;
IData data = ~0;
if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize
data = VL_RANDOM_I();
}
if (randReset != 1) data = VL_RANDOM_I(); // if 2, randomize
data &= VL_MASK_I(obits);
return data;
}
QData VL_RAND_RESET_Q(int obits) VL_MT_SAFE {
if (Verilated::threadContextp()->randReset() == 0) return 0;
QData data = ~0ULL;
if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize
data = VL_RANDOM_Q();
}
data &= VL_MASK_Q(obits);
return data;
}
WDataOutP VL_RAND_RESET_W(int obits, WDataOutP outwp) VL_MT_SAFE {
for (int i = 0; i < VL_WORDS_I(obits) - 1; ++i) outwp[i] = VL_RAND_RESET_I(32);
outwp[VL_WORDS_I(obits) - 1] = VL_RAND_RESET_I(32) & VL_MASK_E(obits);
return outwp;
}
WDataOutP VL_ZERO_RESET_W(int obits, WDataOutP outwp) VL_MT_SAFE {
// Not inlined to speed up compilation of slowpath code
return VL_ZERO_W(obits, outwp);
@ -3087,6 +3092,7 @@ VerilatedContext::Serialized::Serialized() {
bool VerilatedContext::assertOn() const VL_MT_SAFE { return m_s.m_assertOn; }
void VerilatedContext::assertOn(bool flag) VL_MT_SAFE {
if (assertCtlsLocked()) return;
// Set all assert and directive types when true, clear otherwise.
m_s.m_assertOn = VL_MASK_I(ASSERT_ON_WIDTH) * flag;
}
@ -3105,16 +3111,22 @@ uint32_t VerilatedContext::assertOnMask(VerilatedAssertType_t types,
}
void VerilatedContext::assertOnSet(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_MT_SAFE {
if (assertCtlsLocked()) return;
m_s.m_assertOn |= assertOnMask(types, directives);
}
void VerilatedContext::assertOnClear(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_MT_SAFE {
if (assertCtlsLocked()) return;
m_s.m_assertOn &= ~assertOnMask(types, directives);
}
bool VerilatedContext::assertCtlsLocked() const VL_MT_SAFE { return m_ns.m_assertCtlsLocked; }
void VerilatedContext::assertCtlsLocked(bool flag) VL_MT_SAFE { m_ns.m_assertCtlsLocked = flag; }
void VerilatedContext::assertCtl(uint32_t controlType, VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_MT_SAFE {
// IEEE 1800-2023 Table 20-5 control_type. Lock freezes the On/Off state of the
// selected bits until Unlock; On/Off/Kill leave locked bits unchanged.
// +verilator+assert+lock freezes everything, including Lock/Unlock itself.
if (assertCtlsLocked()) return;
const uint32_t mask = assertOnMask(types, directives);
const uint32_t lockedMask = mask & ~m_s.m_assertLock;
switch (controlType) {
@ -3286,6 +3298,15 @@ void VerilatedContext::gotFinish(bool flag) VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
m_s.m_gotFinish = flag;
}
bool VerilatedContext::stopRequestReserve(bool maybe) VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
const int reserved = ++m_ns.m_stopReserved;
return !maybe || m_s.m_errorCount + reserved >= m_s.m_errorLimit;
}
void VerilatedContext::stopRequestRelease() VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
--m_ns.m_stopReserved;
}
bool VerilatedContext::executingFinal() const VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
return m_ns.m_executingFinal;
@ -3556,7 +3577,9 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) {
if (0 == std::strncmp(arg.c_str(), "+verilator+", std::strlen("+verilator+"))) {
std::string str;
uint64_t u64;
if (commandArgVlString(arg, "+verilator+coverage+file+", str)) {
if (arg == "+verilator+assert+lock") {
assertCtlsLocked(true);
} else if (commandArgVlString(arg, "+verilator+coverage+file+", str)) {
coverageFilename(str);
} else if (arg == "+verilator+debug") {
Verilated::debug(4);
@ -3575,7 +3598,8 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) {
logFilename(str);
logOutputToFile(false /* append */);
} else if (arg == "+verilator+noassert") {
assertOn(false);
// Set directly on to avoid conflicts with +verilator+assert+lock
m_s.m_assertOn = 0;
} else if (commandArgVlUint64(arg, "+verilator+prof+exec+start+", u64)) {
profExecStart(u64);
} else if (commandArgVlUint64(arg, "+verilator+prof+exec+window+", u64, 1)) {
@ -3891,7 +3915,7 @@ void Verilated::runFlushCallbacks() VL_MT_SAFE {
// When running internal code coverage (gcc --coverage, as opposed to
// verilator --coverage), dump coverage data to properly cover failing
// tests.
VL_GCOV_DUMP();
VL_GCOV_DUMP_RESET();
}
void Verilated::addExitCb(VoidPCb cb, void* datap) VL_MT_SAFE { addCbExit(cb, datap); }
@ -4142,6 +4166,50 @@ VerilatedVar* VerilatedScope::varInsert(const char* namep, void* datap, bool isP
return &(m_varsp->find(namep)->second);
}
void VerilatedScope::varsInsertFromTable(const VlVarTableEntry* entp, size_t n,
void* basep) VL_MT_UNSAFE {
// Table-driven equivalent of a run of varInsert()/varInsertSized() calls; see VlVarTableEntry.
if (!m_varsp) m_varsp = new VerilatedVarNameMap;
uint8_t* const base = static_cast<uint8_t*>(basep);
for (size_t i = 0; i < n; ++i) {
const VlVarTableEntry& e = entp[i];
void* const datap = base + e.byteOffset;
const VerilatedVarFlags vlflags = static_cast<VerilatedVarFlags>(e.vlflags);
VerilatedVar var{e.namep, datap, e.vltype, vlflags, e.udims, e.pdims, /*isParam=*/false};
for (int d = 0; d < e.udims; ++d) {
var.m_unpacked[d].m_left = e.dims[2 * d];
var.m_unpacked[d].m_right = e.dims[2 * d + 1];
}
for (int d = 0; d < e.pdims; ++d) {
var.m_packed[d].m_left = e.dims[2 * (e.udims + d)];
var.m_packed[d].m_right = e.dims[2 * (e.udims + d) + 1];
}
// Recompute the flattened DPI packed range now dims are known (see
// VerilatedVarProps::initPacked)
if (e.pdims == 1) {
var.m_packedDpi = var.m_packed.front();
} else if (e.pdims > 1) {
int packedSize = 1;
for (int d = 0; d < e.pdims; ++d) packedSize *= var.m_packed[d].elements();
var.m_packedDpi = VerilatedRange{packedSize - 1, 0};
}
m_varsp->emplace(e.namep, std::move(var));
}
}
void VerilatedScope::scopesConstructFromTable(const VlScopeTableEntry* entp, size_t n,
VerilatedSyms* symsp) VL_MT_UNSAFE {
// Table-driven equivalent of a run of 'new VerilatedScope{...}' statements; see
// VlScopeTableEntry. The generated Syms class derives VerilatedSyms as its sole primary
// base at offset 0, so symsp doubles as the base for the offsetof-baked member addresses.
uint8_t* const base = reinterpret_cast<uint8_t*>(symsp);
for (size_t i = 0; i < n; ++i) {
const VlScopeTableEntry& e = entp[i];
VerilatedScope** const slotp = reinterpret_cast<VerilatedScope**>(base + e.ptrOffset);
*slotp = new VerilatedScope{symsp, e.namep, e.identp, e.defnamep, e.timeunit, e.type};
}
}
VerilatedVar* VerilatedScope::varInsertSized(const char* namep, void* datap, bool isParam,
VerilatedVarType vltype, int vlflags, int udims,
uint32_t entSize...) VL_MT_UNSAFE {

View File

@ -160,6 +160,21 @@ enum VerilatedVarFlags : uint32_t {
VLVF_NET = (1 << 15) // Net object
};
// One VPI-visible variable, consumed by VerilatedScope::varsInsertFromTable();
// replaces per-variable varInsert() calls, which compiles faster at scale.
struct VlVarTableEntry final {
static constexpr int kMaxDims = 3; // Max packed+unpacked dims a table row holds
const char* namep; // VPI-facing (protected) variable name, string literal
size_t byteOffset; // offsetof of storage member from module instance base
VerilatedVarType vltype;
uint32_t vlflags; // Direction + flags (VLVD_*/VLVF_*)
uint8_t udims; // udims + pdims <= kMaxDims
uint8_t pdims;
// (left,right) pairs: unpacked dims first, then packed; int32_t since large
// unpacked memories exceed int16 range
int32_t dims[kMaxDims * 2];
};
// IEEE 1800-2023 Table 20-6
enum class VerilatedAssertType : uint8_t {
ASSERT_TYPE_CONCURRENT = (1 << 0),
@ -372,6 +387,8 @@ private:
static uint32_t assertOnMask(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_PURE;
static constexpr size_t ASSERT_CONTROL_SLOT_COUNT = ASSERT_ON_WIDTH - 1;
// No termination request has stamped m_finishPendingTime yet
static constexpr uint64_t TIME_UNSET = ~0ULL;
protected:
// TYPES
@ -430,6 +447,12 @@ protected:
struct NonSerialized final { // Non-serialized information
// These are reloaded from on command-line settings, so do not need to persist
// Fast path
// A worker queues $finish before the main thread callback can set m_gotFinish.
std::atomic<uint32_t> m_finishPending{0}; // Number of queued $finish callbacks
std::atomic<uint64_t> m_finishPendingTime{TIME_UNSET}; // Time of the first callback
std::atomic<bool> m_assertCtlsLocked{
false}; // When true, all assertion-control updates are ignored
int m_stopReserved = 0; // Posted $stop requests not yet executed
bool m_executingFinal = false; // Running generated final() code
uint64_t m_profExecStart = 1; // +prof+exec+start time
uint32_t m_profExecWindow = 2; // +prof+exec+window size
@ -510,6 +533,12 @@ public:
/// Clear enabled status for given assertion types
void assertOnClear(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_MT_SAFE;
/// Return if assertion-control updates are locked. When locked, RTL assert
// control statements ($asserton/$assertoff/$assertcontrol) are ignored, as
// are updates from the C++ API.
bool assertCtlsLocked() const VL_MT_SAFE;
/// Lock/unlock assertion-control updates.
void assertCtlsLocked(bool flag) VL_MT_SAFE;
/// Apply assertion control for given control, assertion, and directive types
void assertCtl(uint32_t controlType, VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_MT_SAFE;
@ -669,6 +698,27 @@ public:
// METHODS - public but for internal use only
// Internal: Track $finish/$stop callbacks queued by worker threads
bool finishPending() const VL_MT_SAFE { return m_ns.m_finishPending.load() != 0; }
void finishPendingInc() VL_MT_SAFE {
++m_ns.m_finishPending;
uint64_t unset = TIME_UNSET;
m_ns.m_finishPendingTime.compare_exchange_strong(unset, time());
}
void finishPendingDec() VL_MT_SAFE {
const uint32_t previous = m_ns.m_finishPending.fetch_sub(1);
assert(previous > 0);
if (previous == 1 && !gotFinish()) m_ns.m_finishPendingTime = TIME_UNSET;
}
// Internal: Time of the first termination request, else the current time
uint64_t finishPendingTime() const VL_MT_SAFE {
const uint64_t stamped = m_ns.m_finishPendingTime.load();
return stamped == TIME_UNSET ? time() : stamped;
}
// Internal: Reserve a posted $stop, returning true if it reaches the termination limit
bool stopRequestReserve(bool maybe) VL_MT_SAFE;
void stopRequestRelease() VL_MT_SAFE;
// Internal: access to implementation class
VerilatedContextImp* impp() VL_MT_SAFE { return reinterpret_cast<VerilatedContextImp*>(this); }
const VerilatedContextImp* impp() const VL_MT_SAFE {
@ -756,6 +806,8 @@ public: // But for internal use only
// Verilator scope information class
// Used for internal VPI implementation, and introspection into scopes
struct VlScopeTableEntry; // Defined below VerilatedScope; used by scopesConstructFromTable()
class VerilatedScope final {
public:
enum Type : uint8_t {
@ -792,6 +844,9 @@ public: // But internals only - called from verilated modules, VerilatedSyms
void* forceReadSignalData, const char* forceReadSignalName,
std::pair<VerilatedVar*, VerilatedVar*> forceControlSignals,
int udims, int pdims...) VL_MT_UNSAFE;
void varsInsertFromTable(const VlVarTableEntry* entp, size_t n, void* basep) VL_MT_UNSAFE;
static void scopesConstructFromTable(const VlScopeTableEntry* entp, size_t n,
VerilatedSyms* symsp) VL_MT_UNSAFE;
// ACCESSORS
const char* name() const VL_MT_SAFE_POSTINIT { return m_namep; }
const char* identifier() const VL_MT_SAFE_POSTINIT { return m_identifierp; }
@ -807,6 +862,17 @@ public: // But internals only - called from verilated modules, VerilatedSyms
Type type() const { return m_type; }
};
// One scope, consumed by VerilatedScope::scopesConstructFromTable(); replaces
// per-scope 'new VerilatedScope{...}' statements, which compiles faster at scale.
struct VlScopeTableEntry final {
size_t ptrOffset; // offsetof of the target __Vscopep_* member within the Syms object
const char* namep; // Scope suffix name (protected), string literal
const char* identp; // Identifier with escapes removed (protected)
const char* defnamep; // Definition name (SCOPE_MODULE only), else "<null>"
int8_t timeunit; // Timeunit in negative power-of-10
VerilatedScope::Type type;
};
class VerilatedHierarchy final {
public:
static void add(const VerilatedScope* fromp, const VerilatedScope* top);

View File

@ -43,7 +43,7 @@ CFG_CXXFLAGS_WEXTRA = @CFG_CXXFLAGS_WEXTRA@
# Compiler flags that enable coroutine support
CFG_CXXFLAGS_COROUTINES = @CFG_CXXFLAGS_COROUTINES@
# Compiler flags when creating a precompiled header
CFG_CXXFLAGS_PCH = -x c++-header
CFG_CXXFLAGS_PCH = -c -x c++-header
# Compiler option to put in front of filename to read precompiled header
CFG_CXXFLAGS_PCH_I = @CFG_CXXFLAGS_PCH_I@
# Compiler's filename prefix for precompiled headers, .gch if clang, empty if GCC

View File

@ -75,24 +75,24 @@ class VerilatedCovImp;
ccontextp->_insertp("hier", name, __VA_ARGS__); \
} while (false)
static inline void VL_COV_TOGGLE_CHG_ST_I(const int width, uint32_t* covp, const IData newData,
const IData oldData) {
inline void VL_COV_TOGGLE_CHG_ST_I(const int width, uint32_t* covp, const IData newData,
const IData oldData) {
const IData chgData = newData ^ oldData;
for (int i = 0; i < width; ++i) {
*(covp + 2 * i + ((newData >> i) & 1)) += (chgData >> i) & 1;
}
}
static inline void VL_COV_TOGGLE_CHG_ST_Q(const int width, uint32_t* covp, const QData newData,
const QData oldData) {
inline void VL_COV_TOGGLE_CHG_ST_Q(const int width, uint32_t* covp, const QData newData,
const QData oldData) {
const QData chgData = newData ^ oldData;
for (int i = 0; i < width; ++i) {
*(covp + 2 * i + ((newData >> i) & 1)) += (chgData >> i) & 1;
}
}
static inline void VL_COV_TOGGLE_CHG_ST_W(const int width, uint32_t* covp, WDataInP newData,
WDataInP oldData) {
inline void VL_COV_TOGGLE_CHG_ST_W(const int width, uint32_t* covp, WDataInP newData,
WDataInP oldData) {
for (int i = 0; i < VL_WORDS_I(width); ++i) {
const EData chgData = newData[i] ^ oldData[i];
if (chgData) {
@ -104,8 +104,8 @@ static inline void VL_COV_TOGGLE_CHG_ST_W(const int width, uint32_t* covp, WData
}
}
static inline void VL_COV_TOGGLE_CHG_MT_I(const int width, std::atomic<uint32_t>* covp,
const IData newData, const IData oldData) VL_MT_SAFE {
inline void VL_COV_TOGGLE_CHG_MT_I(const int width, std::atomic<uint32_t>* covp,
const IData newData, const IData oldData) VL_MT_SAFE {
const IData chgData = newData ^ oldData;
for (int i = 0; i < width; ++i) {
if (VL_BITISSET_I(chgData, i)) {
@ -114,8 +114,8 @@ static inline void VL_COV_TOGGLE_CHG_MT_I(const int width, std::atomic<uint32_t>
}
}
static inline void VL_COV_TOGGLE_CHG_MT_Q(const int width, std::atomic<uint32_t>* covp,
const QData newData, const QData oldData) VL_MT_SAFE {
inline void VL_COV_TOGGLE_CHG_MT_Q(const int width, std::atomic<uint32_t>* covp,
const QData newData, const QData oldData) VL_MT_SAFE {
const QData chgData = newData ^ oldData;
for (int i = 0; i < width; ++i) {
if (VL_BITISSET_Q(chgData, i)) {
@ -124,8 +124,8 @@ static inline void VL_COV_TOGGLE_CHG_MT_Q(const int width, std::atomic<uint32_t>
}
}
static inline void VL_COV_TOGGLE_CHG_MT_W(const int width, std::atomic<uint32_t>* covp,
WDataInP newData, WDataInP oldData) VL_MT_SAFE {
inline void VL_COV_TOGGLE_CHG_MT_W(const int width, std::atomic<uint32_t>* covp, WDataInP newData,
WDataInP oldData) VL_MT_SAFE {
for (int i = 0; i < VL_WORDS_I(width); ++i) {
const EData chgData = newData[i] ^ oldData[i];
if (chgData) {

View File

@ -41,9 +41,13 @@ enum class VlCovBinKind : uint8_t {
//=============================================================================
// VlCoverpointIf
/// Read-side view of a coverpoint. The writer queries bins by index; the
/// implementor computes names/kinds on demand. Bounded bin count, so random
/// access by index is the primary usage.
/// Read-side view of a coverpoint -- a named, index-addressable set of bins
/// with a coverage fraction. A cross is also a coverpoint from this view: its
/// auto cross bins (one per element of the Cartesian product of the feeding
/// coverpoints' Normal bins) are all Normal, and their names are built on
/// demand by concatenating the feeding coverpoints' bin names. The writer
/// queries bins by index; the implementor computes names/kinds on demand.
/// Bounded bin count, so random access by index is the primary usage.
class VlCoverpointIf VL_NOT_FINAL {
public:
@ -51,11 +55,11 @@ public:
virtual ~VlCoverpointIf() = default;
// METHODS
// All bins, across every set; index range [0, binCount())
virtual int binCount() const = 0;
// Bin name in declaration order (e.g. "myBin" or "b[3]")
virtual std::string binName(int i) const = 0;
virtual VlCovBinKind binKind(int i) const = 0;
// All bins, across every set; index range [0, binCount()).
virtual uint32_t binCount() const = 0;
// Bin name in declaration order (e.g. "myBin" or "b[3]"); for a cross,
// the concatenated cross bin name (e.g. "b1_x_b2_x_b3")
virtual std::string binName(uint32_t i) const = 0;
// Bins covered / effective total (Normal set only) for the coverage calc
virtual void coverageParts(double& covered, double& total) const = 0;
};

View File

@ -23,34 +23,57 @@
#include "verilated_covergroup.h"
#include "verilated.h"
// This file is compiled whenever covergroups are used, with or without
// "verilator --coverage" (see V3Global::verilatedCppFiles). Bin counts are
// members of the covergroup objects themselves, so sampling, bin naming, and
// coverage queries such as get_inst_coverage() all work with no coverage
// database present. VL_COVER_INSERT does not copy a count; it hands the
// database the address of a counter to read at write time. Only that
// publication step needs verilated_cov.cpp, which is compiled solely under
// --coverage, so only the registerBins() bodies are gated on VM_COVERAGE.
#if VM_COVERAGE
#include "verilated_cov.h"
#endif
void VlCoverpoint::init(const char* hier, uint32_t atLeast, int nBins) {
void VlCoverpoint::init(const char* hier, uint32_t atLeast, uint32_t nBins) {
m_hier = hier;
m_atLeast = atLeast;
m_total = nBins;
m_counts.assign(nBins, 0);
m_crossIdx.assign(nBins, -1);
m_crossToBin.clear();
}
void VlCoverpoint::addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name,
const char* file, int line, int col) {
void VlCoverpoint::addNamer(VlCovBinKind set, uint32_t count, VlCovBinNaming naming,
const char* name, const char* file, int line, int col) {
m_namers.emplace_back(set, count, m_nextBase, naming, name, file, line, col);
if (set == VlCovBinKind::KIND_NORMAL) {
// Assign each Normal bin a cross index, and record the inverse map.
for (uint32_t b = m_nextBase; b < m_nextBase + count; ++b) {
m_crossIdx[b] = static_cast<int>(m_crossToBin.size());
m_crossToBin.push_back(b);
}
m_normal += count;
}
m_nextBase += count;
if (set == VlCovBinKind::KIND_NORMAL) m_normal += count;
}
const VlCovNamer& VlCoverpoint::namerFor(int i) const {
// Namers are appended in ascending, contiguous index order covering [0, m_total),
// and i is always a valid bin index, so the matching namer always exists.
std::string VlCoverpoint::normalBinName(uint32_t crossIdx) const {
// Build the bin name based on the bin index
return binName(m_crossToBin[crossIdx]);
}
const VlCovNamer& VlCoverpoint::namerFor(uint32_t i) const {
// Namers are appended in ascending order covering [0, m_total),
for (const VlCovNamer& nm : m_namers) {
if (i < nm.base() + nm.count()) return nm;
}
VL_UNREACHABLE;
VL_UNREACHABLE; // LCOV_EXCL_LINE
}
std::string VlCoverpoint::binName(int i) const {
std::string VlCoverpoint::binName(uint32_t i) const {
const VlCovNamer& nm = namerFor(i);
std::string name = nm.name();
if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(i - nm.base()) + ']';
@ -59,7 +82,7 @@ std::string VlCoverpoint::binName(int i) const {
#if VM_COVERAGE
void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) {
for (int i = 0; i < binCount(); ++i) {
for (uint32_t i = 0; i < binCount(); ++i) {
const VlCovNamer& nm = namerFor(i);
const VlCovBinKind kind = binKind(i);
const std::string binp = binName(i);
@ -81,3 +104,89 @@ void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* pa
}
}
#endif // VM_COVERAGE
//=============================================================================
// VlCoverCross
void VlCoverCross::init(const char* hier, uint32_t dims, VlCoverpoint* const* cps,
const char* file, int line, int col) {
m_hier = hier;
m_file = file;
m_line = line;
m_col = col;
m_dims = dims;
m_cps.assign(cps, cps + dims);
m_cpBinCounts.resize(dims);
// Accumulate in 64 bits so the overflow check itself cannot overflow.
uint64_t product = 1;
for (uint32_t d = 0; d < dims; ++d) {
m_cpBinCounts[d] = cps[d]->normalBinCount();
product *= m_cpBinCounts[d];
if (VL_UNLIKELY(product > UINT32_MAX)) { // LCOV_EXCL_START
VL_FATAL_MT(file, line, "", "Cross has too many auto bins to represent");
} // LCOV_EXCL_STOP
}
m_numAutoBins = static_cast<uint32_t>(product);
// stride[d] = product of the Normal bin counts of all dimensions after d.
// Counts down with an offset so the unsigned index never wraps below zero.
m_stride.assign(dims, 1);
for (uint32_t d = dims; d > 1; --d) m_stride[d - 2] = m_stride[d - 1] * m_cpBinCounts[d - 1];
m_flatCounts.assign(m_numAutoBins, 0);
}
void VlCoverCross::iterateProduct(VlCoverpoint* const* cps, uint32_t dim, uint32_t baseIdx) {
const uint32_t hits = cps[dim]->hitCount();
const uint32_t* const list = cps[dim]->hitList();
const bool last = (dim == m_dims - 1);
const uint32_t stride = m_stride[dim];
for (uint32_t hit = 0; hit < hits; ++hit) {
const uint32_t idx = baseIdx + list[hit] * stride;
if (last) {
incrementTuple(idx);
} else {
iterateProduct(cps, dim + 1, idx);
}
}
}
void VlCoverCross::sample(VlCoverpoint* const* cps) {
// Fast path: if any dimension had no Normal-bin hit, the cross cannot hit.
for (uint32_t d = 0; d < m_dims; ++d) {
if (cps[d]->hitCount() == 0) return;
}
iterateProduct(cps, 0, 0);
}
std::string VlCoverCross::binName(uint32_t flat) const {
// Built on demand by concatenating each coverpoint's own bin name.
std::string name;
for (uint32_t d = 0; d < m_dims; ++d) {
const uint32_t crossIdx = (flat / m_stride[d]) % m_cpBinCounts[d];
if (d > 0) name += "_x_";
name += m_cps[d]->normalBinName(crossIdx);
}
return name;
}
#if VM_COVERAGE
void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* page) {
// Register every auto cross bin (zero-count bins included), so the report
// shows the full Cartesian product of cross bins. Names are built on the fly.
const std::string lineStr = std::to_string(m_line);
const std::string colStr = std::to_string(m_col);
for (uint32_t flat = 0; flat < binCount(); ++flat) {
const std::string bin = binName(flat); // "b1_x_b2_x_..."
// cross_bins metadata: the same components joined by ',' (not read by the report)
std::string crossBins;
for (uint32_t d = 0; d < m_dims; ++d) {
const uint32_t crossIdx = (flat / m_stride[d]) % m_cpBinCounts[d];
if (d > 0) crossBins += ",";
crossBins += m_cps[d]->normalBinName(crossIdx);
}
const std::string full = m_hier + "." + bin;
VL_COVER_INSERT(covcontextp, full.c_str(), &m_flatCounts[flat], "page", page, "filename",
m_file, "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin",
bin.c_str(), "cross", "1", "cross_bins", crossBins.c_str());
}
}
#endif // VM_COVERAGE

View File

@ -22,6 +22,9 @@
/// it in the constructor (init + add*Namer), increments bins from sample(),
/// and registers via registerBins().
///
/// Collection and coverage queries are always available; only registerBins(),
/// which publishes bin counters to the coverage database, requires VM_COVERAGE.
///
//=============================================================================
#ifndef VERILATOR_VERILATED_COVERGROUP_H_
@ -49,8 +52,8 @@ enum class VlCovBinNaming : uint8_t {
class VlCovNamer final {
// MEMBERS
VlCovBinKind m_set; // which set the bins belong to
int m_count; // bins this namer covers (1 for Single)
int m_base; // first bin index (declaration order), assigned on append
uint32_t m_count; // bins this namer covers (1 for Single)
uint32_t m_base; // first bin index (declaration order), assigned on append
VlCovBinNaming m_naming; // how bin names are built
const char* m_name; // bin name (Single) or array base name (Array)
const char* m_file; // declaration file
@ -59,8 +62,8 @@ class VlCovNamer final {
public:
// CONSTRUCTORS
VlCovNamer(VlCovBinKind set, int count, int base, VlCovBinNaming naming, const char* name,
const char* file, int line, int col)
VlCovNamer(VlCovBinKind set, uint32_t count, uint32_t base, VlCovBinNaming naming,
const char* name, const char* file, int line, int col)
: m_set{set}
, m_count{count}
, m_base{base}
@ -72,8 +75,8 @@ public:
// METHODS
VlCovBinKind set() const { return m_set; }
int count() const { return m_count; }
int base() const { return m_base; }
uint32_t count() const { return m_count; }
uint32_t base() const { return m_base; }
VlCovBinNaming naming() const { return m_naming; }
const char* name() const { return m_name; }
const char* file() const { return m_file; }
@ -87,19 +90,30 @@ public:
/// bin's set/name come from the owning namer. coverage() is computed on demand
/// by scanning bin counts, keeping the sample() hot path a plain counter bump.
class VlCoverpoint final : public VlCoverpointIf {
// MEMBERS
// Base coverpoint runtime (read side + collection logic, no hit-list storage).
// VlCoverpointT<MaxHits> adds the inline hit-list array and the incrementBin write
// path; the cross holds VlCoverpoint* and reads via hitCount()/hitList().
class VlCoverpoint VL_NOT_FINAL : public VlCoverpointIf {
protected:
// MEMBERS (protected so VlCoverpointT::incrementBin can update them)
std::string m_hier; // "covergroup.coverpoint"
uint32_t m_atLeast = 1; // option.at_least (coverpoint-wide)
int m_total = 0; // bins across all sets
int m_normal = 0; // Normal bins (coverage denominator)
int m_nextBase = 0; // running append cursor
uint32_t m_total = 0; // bins across all sets
uint32_t m_normal = 0; // Normal bins (coverage denominator)
uint32_t m_nextBase = 0; // running append cursor
std::vector<uint32_t> m_counts; // [m_total], one per bin
std::vector<VlCovNamer> m_namers; // appended in declaration order
// [m_total] full bin idx -> cross idx (Normal-only), -1 otherwise. The only
// signed index here: -1 marks a non-Normal bin, which incrementBin filters on.
std::vector<int> m_crossIdx;
// [m_normal] inverse of m_crossIdx: cross idx -> full bin idx, appended in cross-index order
std::vector<uint32_t> m_crossToBin;
uint32_t m_hitCount = 0; // entries valid in the hit list this sample
private:
// PRIVATE METHODS
const VlCovNamer& namerFor(int i) const; // obtain the bin-specific name producer
void addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name,
const VlCovNamer& namerFor(uint32_t i) const; // obtain the bin-specific name producer
void addNamer(VlCovBinKind set, uint32_t count, VlCovBinNaming naming, const char* name,
const char* file, int line, int col);
public:
@ -108,31 +122,44 @@ public:
// METHODS
// ---- configuration (from generated constructor) ----
void init(const char* hier, uint32_t atLeast, int nBins);
void init(const char* hier, uint32_t atLeast, uint32_t nBins);
void addSingleNamer(VlCovBinKind set, const char* name, const char* file, int line, int col) {
addNamer(set, 1, VlCovBinNaming::Single, name, file, line, col);
}
void addArrayNamer(VlCovBinKind set, int count, const char* name, const char* file, int line,
int col) {
void addArrayNamer(VlCovBinKind set, uint32_t count, const char* name, const char* file,
int line, int col) {
addNamer(set, count, VlCovBinNaming::Array, name, file, line, col);
}
void registerBins(VerilatedCovContext* covcontextp, const char* page);
// ---- hot path (from generated sample()) ----
void incrementBin(int i) { ++m_counts[i]; } // Normal bin: count only
void recordHit(int i) { ++m_counts[i]; } // Ignore/Illegal/Default: count only
// Clear the hit list at the start of each sample() for a cross-fed coverpoint.
void clearHitList() { m_hitCount = 0; }
// Ignore/Illegal/Default: count only; never propagates to cross coverage.
void recordHit(uint32_t i) { ++m_counts[i]; }
// incrementBin (Normal bin: count + hit-list append) lives in VlCoverpointT<MaxHits>,
// where MaxHits is the gen-time max per-sample bin overlap.
// ---- cross support (read by VlCoverCross) ----
uint32_t hitCount() const { return m_hitCount; }
virtual const uint32_t* hitList() const = 0; // provided by VlCoverpointT
uint32_t normalBinCount() const { return m_normal; } // cross dimension size (Normal bins)
std::string normalBinName(uint32_t crossIdx) const; // name of the crossIdx-th Normal bin
// ---- VlCoverpointIf ----
int binCount() const override { return m_total; }
std::string binName(int i) const override;
VlCovBinKind binKind(int i) const override { return namerFor(i).set(); }
uint32_t binCount() const override { return m_total; }
std::string binName(uint32_t i) const override;
// Deliberately not on VlCoverpointIf: only registerBins() needs it, via the
// concrete coverpoint. A cross has all-Normal bins and exposes no kind, so the
// interface omits it; add it back only if a writer needs it polymorphically.
VlCovBinKind binKind(uint32_t i) const { return namerFor(i).set(); }
void coverageParts(double& covered, double& total) const override {
// Count Normal bins that reached option.at_least on demand, so the hot
// path (incrementBin) stays a plain counter bump.
int numCovered = 0;
uint32_t numCovered = 0;
for (const VlCovNamer& nm : m_namers) {
if (nm.set() != VlCovBinKind::KIND_NORMAL) continue;
for (int i = nm.base(); i < nm.base() + nm.count(); ++i) {
for (uint32_t i = nm.base(); i < nm.base() + nm.count(); ++i) {
if (m_counts[i] >= m_atLeast) ++numCovered;
}
}
@ -141,4 +168,90 @@ public:
}
};
//=============================================================================
// VlCoverpointT
/// Concrete coverpoint with an inline hit-list array sized to MaxHits -- the
/// gen-time maximum number of Normal bins one sample value can match (1 for the
/// common non-overlapping case). The bound is a compile-time constant, so for
/// MaxHits == 1 incrementBin collapses to a single store. Generated code holds
/// the coverpoint as VlCoverpointT<K> and calls incrementBin via the concrete
/// type; the cross reads it polymorphically through VlCoverpoint*.
template <uint32_t MaxHits>
class VlCoverpointT final : public VlCoverpoint {
// MEMBERS
uint32_t m_hits[MaxHits]; // cross indices of Normal bins hit this sample
public:
// CONSTRUCTORS
VlCoverpointT() = default;
// METHODS
// Normal bin: bump count and append the bin's cross index to the hit list.
// m_hitCount can never exceed MaxHits (the gen-time overlap bound), so no hit
// is ever dropped; the bound check is a compile-time-folded safety net.
void incrementBin(uint32_t i) {
++m_counts[i];
// m_crossIdx is signed only to carry the -1 "not a Normal bin" marker;
// the >= 0 test below is what makes every stored hit index unsigned-safe.
const int cx = m_crossIdx[i];
if (cx >= 0 && m_hitCount < MaxHits) m_hits[m_hitCount++] = static_cast<uint32_t>(cx);
}
const uint32_t* hitList() const override { return m_hits; }
};
//=============================================================================
// VlCoverCross
/// Per-instance auto cross runtime. Holds flat uint32_t[] storage over the
/// Cartesian product of the feeding coverpoints' Normal bins. Each sample()
/// walks the coverpoint hit lists (O(hits), not O(product)). Bin names are
/// built on demand from the coverpoints, so no per-bin name is stored.
class VlCoverCross final : public VlCoverpointIf {
// MEMBERS
std::string m_hier; // "covergroup.cross"
const char* m_file = nullptr; // Cross declaration file (registration metadata)
int m_line = 0; // Cross declaration line
int m_col = 0; // Cross declaration column
uint32_t m_dims = 0; // Number of feeding coverpoints
// Cross bin indexes are unsigned, like the coverpoint bin indexes they are
// built from. init() fatals if the product would exceed UINT32_MAX, so every
// index computed here provably fits. That bound is far beyond anything
// storable anyway: m_flatCounts alone would need 16GB.
uint32_t m_numAutoBins = 0; // Product of per-dim Normal bin counts
uint32_t m_numCovered = 0; // Distinct bins hit >= 1 (maintained incrementally)
std::vector<uint32_t> m_cpBinCounts; // [m_dims] Normal bin count per dimension
std::vector<uint32_t> m_stride; // [m_dims] Flat-index stride per dimension
std::vector<uint32_t> m_flatCounts; // [m_numAutoBins] Per-bin hit counts
std::vector<VlCoverpoint*> m_cps; // Feeding coverpoints (the only name source)
// PRIVATE METHODS
void iterateProduct(VlCoverpoint* const* cps, uint32_t dim, uint32_t baseIdx);
void incrementTuple(uint32_t idx) {
if (m_flatCounts[idx]++ == 0) ++m_numCovered;
}
public:
// CONSTRUCTORS
VlCoverCross() = default;
// METHODS
// ---- configuration (from generated constructor, after coverpoints init'd) ----
void init(const char* hier, uint32_t dims, VlCoverpoint* const* cps, const char* file,
int line, int col);
void registerBins(VerilatedCovContext* covcontextp, const char* page);
// ---- hot path (from generated sample(), after all coverpoints sampled) ----
void sample(VlCoverpoint* const* cps);
// ---- VlCoverpointIf ----
// A cross is a coverpoint whose bins are the auto cross bins (all Normal).
uint32_t binCount() const override { return m_numAutoBins; }
std::string binName(uint32_t flat) const override;
void coverageParts(double& covered, double& total) const override {
covered = m_numCovered;
total = m_numAutoBins;
}
};
#endif // Guard

View File

@ -37,68 +37,68 @@
// SETTING OPERATORS
// Convert svBitVecVal to Verilator internal data
static inline void VL_SET_W_SVBV(int obits, WDataOutP owp, const svBitVecVal* lwp) VL_MT_SAFE {
inline void VL_SET_W_SVBV(int obits, WDataOutP owp, const svBitVecVal* lwp) VL_MT_SAFE {
const int words = VL_WORDS_I(obits);
for (int i = 0; i < words - 1; ++i) owp[i] = lwp[i];
owp[words - 1] = lwp[words - 1] & VL_MASK_I(obits);
}
static inline void VL_SET_Q_SVBV(int obits, QData& out, const svBitVecVal* lwp) VL_MT_SAFE {
inline void VL_SET_Q_SVBV(int obits, QData& out, const svBitVecVal* lwp) VL_MT_SAFE {
out = VL_MASK_Q(obits) & VL_SET_QII(lwp[1], lwp[0]);
}
static inline void VL_SET_I_SVBV(int obits, IData& out, const svBitVecVal* lwp) VL_MT_SAFE {
inline void VL_SET_I_SVBV(int obits, IData& out, const svBitVecVal* lwp) VL_MT_SAFE {
out = VL_MASK_I(obits) & lwp[0];
}
static inline void VL_SET_S_SVBV(int obits, SData& out, const svBitVecVal* lwp) VL_MT_SAFE {
inline void VL_SET_S_SVBV(int obits, SData& out, const svBitVecVal* lwp) VL_MT_SAFE {
out = VL_MASK_I(obits) & lwp[0];
}
static inline void VL_SET_C_SVBV(int obits, CData& out, const svBitVecVal* lwp) VL_MT_SAFE {
inline void VL_SET_C_SVBV(int obits, CData& out, const svBitVecVal* lwp) VL_MT_SAFE {
out = VL_MASK_I(obits) & lwp[0];
}
// Convert Verilator internal data to svBitVecVal
static inline void VL_SET_SVBV_W(int obits, svBitVecVal* owp, const WDataInP lwp) VL_MT_SAFE {
inline void VL_SET_SVBV_W(int obits, svBitVecVal* owp, const WDataInP lwp) VL_MT_SAFE {
const int words = VL_WORDS_I(obits);
for (int i = 0; i < words - 1; ++i) owp[i] = lwp[i];
owp[words - 1] = lwp[words - 1] & VL_MASK_I(obits);
}
static inline void VL_SET_SVBV_I(int, svBitVecVal* owp, const IData ld) VL_MT_SAFE { owp[0] = ld; }
static inline void VL_SET_SVBV_Q(int, svBitVecVal* owp, const QData ld) VL_MT_SAFE {
inline void VL_SET_SVBV_I(int, svBitVecVal* owp, const IData ld) VL_MT_SAFE { owp[0] = ld; }
inline void VL_SET_SVBV_Q(int, svBitVecVal* owp, const QData ld) VL_MT_SAFE {
VL_SET_WQ(WDataOutP::external(owp), ld);
}
// Convert svLogicVecVal to Verilator internal data
// Note these functions ignore X/Z in svLogicVecVal
static inline void VL_SET_W_SVLV(int obits, WDataOutP owp, const svLogicVecVal* lwp) VL_MT_SAFE {
inline void VL_SET_W_SVLV(int obits, WDataOutP owp, const svLogicVecVal* lwp) VL_MT_SAFE {
const int words = VL_WORDS_I(obits);
for (int i = 0; i < words - 1; ++i) owp[i] = lwp[i].aval;
owp[words - 1] = lwp[words - 1].aval & VL_MASK_I(obits);
}
static inline void VL_SET_Q_SVLV(int obits, QData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
inline void VL_SET_Q_SVLV(int obits, QData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
out = VL_MASK_Q(obits) & VL_SET_QII(lwp[1].aval, lwp[0].aval);
}
static inline void VL_SET_I_SVLV(int obits, IData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
inline void VL_SET_I_SVLV(int obits, IData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
out = VL_MASK_I(obits) & lwp[0].aval;
}
static inline void VL_SET_S_SVLV(int obits, SData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
inline void VL_SET_S_SVLV(int obits, SData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
out = VL_MASK_I(obits) & lwp[0].aval;
}
static inline void VL_SET_C_SVLV(int obits, CData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
inline void VL_SET_C_SVLV(int obits, CData& out, const svLogicVecVal* lwp) VL_MT_SAFE {
out = VL_MASK_I(obits) & lwp[0].aval;
}
// Convert Verilator internal data to svLogicVecVal
// Note these functions never create X/Z in svLogicVecVal
static inline void VL_SET_SVLV_W(int obits, svLogicVecVal* owp, const WDataInP lwp) VL_MT_SAFE {
inline void VL_SET_SVLV_W(int obits, svLogicVecVal* owp, const WDataInP lwp) VL_MT_SAFE {
const int words = VL_WORDS_I(obits);
for (int i = 0; i < words; ++i) owp[i].bval = 0;
for (int i = 0; i < words - 1; ++i) owp[i].aval = lwp[i];
owp[words - 1].aval = lwp[words - 1] & VL_MASK_I(obits);
}
static inline void VL_SET_SVLV_I(int, svLogicVecVal* owp, const IData ld) VL_MT_SAFE {
inline void VL_SET_SVLV_I(int, svLogicVecVal* owp, const IData ld) VL_MT_SAFE {
owp[0].aval = ld;
owp[0].bval = 0;
}
static inline void VL_SET_SVLV_Q(int, svLogicVecVal* owp, const QData ld) VL_MT_SAFE {
inline void VL_SET_SVLV_Q(int, svLogicVecVal* owp, const QData ld) VL_MT_SAFE {
VlWide<2> lwp;
VL_SET_WQ(lwp, ld);
owp[0].aval = lwp[0];

View File

@ -98,8 +98,10 @@ void VerilatedFst::close() VL_MT_SAFE_EXCLUDES(m_mutex) {
const VerilatedLockGuard lock{m_mutex};
Super::closeBase();
emitTimeChangeMaybe();
if (m_fst) m_fst->close(); // LCOV_EXCL_BR_LINE
m_fst = nullptr;
if (m_fst) {
m_fst->close();
VL_DO_CLEAR(delete m_fst, m_fst = nullptr);
}
}
void VerilatedFst::flush() VL_MT_SAFE_EXCLUDES(m_mutex) {

File diff suppressed because it is too large Load Diff

View File

@ -129,7 +129,7 @@ public:
void wait_report() {
if (m_pidExited) return;
#ifdef _VL_SOLVER_PIPE
if (waitpid(m_pid, &m_pidStatus, 0) != m_pid) return;
if (waitpid(m_pid, &m_pidStatus, WNOHANG) != m_pid) m_pidStatus = 0;
if (m_pidStatus) {
std::stringstream msg;
msg << "Subprocess command `" << m_cmd[0];
@ -208,8 +208,10 @@ public:
// Child
close(fd_stdin[P_WR]);
dup2(fd_stdin[P_RD], STDIN_FILENO);
close(fd_stdin[P_RD]);
close(fd_stdout[P_RD]);
dup2(fd_stdout[P_WR], STDOUT_FILENO);
close(fd_stdout[P_WR]);
execvp(cmd[0], const_cast<char* const*>(cmd));
std::stringstream msg;
msg << "VlRProcess::open: execvp(" << cmd[0] << ")";
@ -441,11 +443,14 @@ void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) {
os << ')';
}
size_t VlRandomizer::hashConstraints() const {
size_t VlRandomizer::hashConstraints(const std::vector<std::string>& extras) const {
size_t h = 0;
for (const auto& c : m_constraints) {
h ^= std::hash<std::string>{}(c) + 0x9e3779b9 + (h << 6) + (h >> 2);
}
for (const auto& c : extras) {
h ^= std::hash<std::string>{}(c) + 0x9e3779b9 + (h << 6) + (h >> 2);
}
return h;
}
@ -478,36 +483,20 @@ void VlRandomizer::recordRandcValues() {
}
}
bool VlRandomizer::next_check_only(VlRNG& rngr) {
m_checkOnly = true;
const bool result = next(rngr);
m_checkOnly = false;
return result;
}
bool VlRandomizer::next_check_only(VlRNG& rngr) { return nextRandomize(rngr, true); }
bool VlRandomizer::next(VlRNG& rngr) {
if (!m_checkOnly && m_vars.empty() && m_unique_arrays.empty()) return true;
if (m_checkOnly && m_vars.empty()) return true; // No rand members: trivially SAT
for (const std::string& baseName : m_unique_arrays) {
const auto it = m_vars.find(baseName);
const uint32_t size = m_unique_array_sizes.at(baseName);
bool VlRandomizer::next(VlRNG& rngr) { return nextRandomize(rngr, false); }
if (it != m_vars.end()) {
std::string distinctExpr = "(__Vbv (distinct";
for (uint32_t i = 0; i < size; ++i) {
char hexIdx[12];
sprintf(hexIdx, "#x%08x", i);
distinctExpr += " (select " + it->first + " " + hexIdx + ")";
}
distinctExpr += "))";
m_constraints.push_back(distinctExpr);
}
}
bool VlRandomizer::nextRandomize(VlRNG& rngr, bool checkOnly) {
if (!checkOnly && m_vars.empty() && m_unique_arrays.empty()) return true;
if (checkOnly && m_vars.empty()) return true; // No rand members: trivially SAT
m_checkOnly = checkOnly;
const std::vector<std::string> uniqueExprs = buildUniqueExprs();
// Randc exclusion-based cycling: exclude previously used values per randc var.
// When solver returns unsat (all values exhausted), clear history for new cycle.
if (!m_randcVarNames.empty()) {
const size_t currentHash = hashConstraints();
const size_t currentHash = hashConstraints(uniqueExprs);
// Invalidate history if constraints changed (e.g., constraint_mode toggled)
if (currentHash != m_randcConstraintHash) {
m_randcUsedValues.clear();
@ -516,83 +505,103 @@ bool VlRandomizer::next(VlRNG& rngr) {
}
// Pinned vars make phase ordering moot; skip phased path in check-only.
if (!m_checkOnly && !m_solveBefore.empty()) return nextPhased(rngr);
bool result;
if (!m_checkOnly && !m_solveBefore.empty()) {
result = nextPhased(rngr, uniqueExprs);
} else {
result = nextFlat(rngr, uniqueExprs);
}
m_checkOnly = false;
return result;
}
std::vector<std::string> VlRandomizer::buildUniqueExprs() const {
std::vector<std::string> exprs;
if (m_unique_arrays.empty()) return exprs;
const auto arrVarsp = std::make_shared<const ArrayInfoMap>(m_arr_vars);
for (const std::string& baseName : m_unique_arrays) {
const auto it = m_vars.find(baseName);
if (it == m_vars.end()) continue;
const VlRandomVar& var = *it->second;
// Select the elements the array actually holds now, by their own index
// or key, rather than by ordinal position
var.setArrayInfo(arrVarsp);
// 'distinct' needs at least two operands; fewer elements are trivially unique
if (var.countMatchingElements(*arrVarsp, baseName) < 2) continue;
std::ostringstream os;
os << "(__Vbv (distinct ";
var.emitGetValue(os);
os << "))";
exprs.push_back(os.str());
}
return exprs;
}
void VlRandomizer::emitDefines(std::ostream& os) const {
os << "(define-fun __Vbv ((b Bool)) (_ BitVec 1) (ite b #b1 #b0))\n";
os << "(define-fun __Vbool ((v (_ BitVec 1))) Bool (= #b1 v))\n";
}
void VlRandomizer::emitDeclares(std::ostream& os, bool pinCurrent) const {
for (const auto& var : m_vars) {
if (var.second->dimension() > 0) {
auto arrVarsp = std::make_shared<const ArrayInfoMap>(m_arr_vars);
var.second->setArrayInfo(arrVarsp);
}
os << "(declare-fun " << var.first << " () ";
var.second->emitType(os);
os << ")\n";
// Pin each var to its current value
if (pinCurrent) {
assert(var.second->dimension() == 0);
os << "(assert (= " << var.first << ' ';
var.second->emitConcreteValue(os);
os << "))\n";
}
}
}
void VlRandomizer::emitAsserts(std::ostream& os, const std::vector<std::string>& extras,
bool named) const {
int j = 0;
for (const std::string& constraint : m_constraints) {
if (named) {
os << "(assert (! (= #b1 " << constraint << ") :named cons" << j++ << "))\n";
} else {
os << "(assert (= #b1 " << constraint << "))\n";
}
}
for (const std::string& extra : extras) {
if (named) {
os << "(assert (! (= #b1 " << extra << ") :named cons" << j++ << "))\n";
} else {
os << "(assert (= #b1 " << extra << "))\n";
}
}
}
bool VlRandomizer::nextFlat(VlRNG& rngr, const std::vector<std::string>& uniqueExprs) {
// Randc retry: if unsat due to randc exhaustion, clear history and retry once
const bool hasRandc = !m_randcVarNames.empty();
for (int attempt = 0; attempt < (hasRandc ? 2 : 1); ++attempt) {
std::iostream& os = getSolver();
if (!os) return false;
// Soft constraint relaxation (IEEE 1800-2023 18.5.13, last-wins priority):
// Try hard + soft[0..N-1], then hard + soft[1..N-1], ..., then hard only.
// First SAT phase wins. If hard-only is UNSAT, report via unsat-core.
os << "(set-option :produce-models true)\n";
// Lets the scalar pin path learn which free-bit assumptions conflict.
os << "(set-option :produce-unsat-assumptions true)\n";
os << "(set-logic QF_ABV)\n";
os << "(define-fun __Vbv ((b Bool)) (_ BitVec 1) (ite b #b1 #b0))\n";
os << "(define-fun __Vbool ((v (_ BitVec 1))) Bool (= #b1 v))\n";
for (const auto& var : m_vars) {
if (var.second->dimension() > 0) {
auto arrVarsp = std::make_shared<const ArrayInfoMap>(m_arr_vars);
var.second->setArrayInfo(arrVarsp);
}
os << "(declare-fun " << var.first << " () ";
var.second->emitType(os);
os << ")\n";
// Pin each var to its current value: SAT iff the current values
// satisfy the constraints. V3Randomize rejects non-scalar rand
// members upstream, hence the assert.
if (m_checkOnly) {
assert(var.second->dimension() == 0);
os << "(assert (= " << var.first << ' ';
var.second->emitConcreteValue(os);
os << "))\n";
}
}
for (const std::string& constraint : m_constraints) {
os << "(assert (= #b1 " << constraint << "))\n";
}
emitDefines(os);
emitDeclares(os, m_checkOnly);
emitAsserts(os, uniqueExprs, false);
// randc exclusions vs. a pinned current value would make every check
// trivially UNSAT after the first cycle.
if (!m_checkOnly) emitRandcExclusions(os);
const size_t nSoft = m_softConstraints.size();
bool sat = false;
if (nSoft > 0) {
// Fast path: try all soft constraints at once
os << "(push 1)\n";
for (const auto& s : m_softConstraints) os << "(assert (= #b1 " << s << "))\n";
os << "(check-sat)\n";
sat = parseSolution(os, false);
if (!sat) {
// Some soft constraints conflict. Incrementally add from back
// (highest priority first), keeping only compatible ones.
// This preserves the maximum set of compatible soft constraints.
os << "(pop 1)\n";
for (int i = static_cast<int>(nSoft) - 1; i >= 0; --i) {
os << "(push 1)\n";
os << "(assert (= #b1 " << m_softConstraints[i] << "))\n";
os << "(check-sat)\n";
if (checkSat(os)) {
// Compatible -- keep this push level
} else {
// Incompatible -- remove this soft constraint
os << "(pop 1)\n";
}
}
// Read solution with remaining compatible soft constraints
os << "(check-sat)\n";
sat = parseSolution(os, false);
}
} else {
// No soft constraints -- hard-only
os << "(check-sat)\n";
sat = parseSolution(os, false);
}
relaxSoftConstraints(os);
os << "(check-sat)\n";
const bool sat = parseSolution(os);
if (!sat) {
os << "(reset)\n";
@ -606,116 +615,120 @@ bool VlRandomizer::next(VlRNG& rngr) {
// the solver's free assignment.
if (m_checkOnly) return false;
// Genuine unsat: report via unsat-core
os << "(set-option :produce-unsat-cores true)\n";
os << "(set-logic QF_ABV)\n";
os << "(define-fun __Vbv ((b Bool)) (_ BitVec 1) (ite b #b1 #b0))\n";
os << "(define-fun __Vbool ((v (_ BitVec 1))) Bool (= #b1 v))\n";
for (const auto& var : m_vars) {
if (var.second->dimension() > 0) {
auto arrVarsp = std::make_shared<const ArrayInfoMap>(m_arr_vars);
var.second->setArrayInfo(arrVarsp);
}
os << "(declare-fun " << var.first << " () ";
var.second->emitType(os);
os << ")\n";
}
int j = 0;
for (const std::string& constraint : m_constraints) {
os << "(assert (! (= #b1 " << constraint << ") :named cons" << j++ << "))\n";
}
os << "(check-sat)\n";
sat = parseSolution(os, true);
(void)sat;
reportUnsatSetup(os, uniqueExprs);
os << "(reset)\n";
return false;
}
if (!m_checkOnly) {
bool hasArray = false;
for (const auto& var : m_vars) {
if (var.second->dimension() > 0) {
hasArray = true;
break;
}
}
if (!hasArray) {
// Tie each free bit to a fresh random target via a boolean
// assumption literal a_k <=> (bit_k == target_k), then force the
// bits with (check-sat-assuming ...). If UNSAT,
// (get-unsat-assumptions) names the literals clashing with the
// feasible base; drop ONE per round so the maximal compatible
// set survives -- dropping a whole conflicting group at once
// would collapse the diversity of tightly coupled bits (one-hot,
// 2-value sets) onto the solver's fixed default. Assumptions are
// ephemeral, so rounds need no push/pop or re-asserting and the
// solver keeps its learned clauses. Each round drops >= 1 -> ends
// in <= npins rounds.
std::vector<bool> targets;
int npins = 0;
for (const auto& var : m_vars) {
const int w = var.second->totalWidth();
for (int b = 0; b < w; b++) {
const bool target = (VL_RANDOM_RNG_I(rngr) & 1);
targets.push_back(target);
os << "(declare-fun a" << npins << " () Bool)\n";
os << "(assert (= a" << npins << " (=";
var.second->emitExtract(os, b);
os << " #b" << (target ? '1' : '0') << ")))\n";
++npins;
}
}
std::vector<bool> dropped(npins, false);
for (int round = 0; round <= npins; ++round) {
os << "(check-sat-assuming (";
for (int k = 0; k < npins; k++)
if (!dropped[k]) os << " a" << k;
os << "))\n";
if (parseSolution(os, false)) break;
// get-unsat-assumptions only echoes still-active literals,
// so the first in-range index is a live conflicting bit.
const std::vector<int> core = readUnsatAssumptions(os);
for (const int idx : core)
if (idx < npins) {
dropped[idx] = true;
break;
}
}
} else {
// Array present: original XOR-rounds path.
for (int i = 0; i < _VL_SOLVER_HASH_LEN_TOTAL && sat; ++i) {
os << "(assert ";
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN);
os << ")\n";
os << "\n(check-sat)\n";
sat = parseSolution(os, false);
(void)sat;
}
}
solveDiversity(rngr, os);
// Check-only must not advance randc cycle state.
recordRandcValues();
}
// Check-only must not advance randc cycle state.
if (!m_checkOnly) recordRandcValues();
os << "(reset)\n";
return true;
}
return false; // Should not reach here
}
void VlRandomizer::solveDiversity(VlRNG& rngr, std::iostream& os) {
bool hasArray = false;
for (const auto& var : m_vars) {
if (var.second->dimension() > 0) {
hasArray = true;
break;
}
}
if (hasArray) {
solveDiversityXor(rngr, os);
} else {
solveDiversityPins(rngr, os);
}
}
void VlRandomizer::solveDiversityPins(VlRNG& rngr, std::iostream& os) {
// Tie each free bit to a random target via an assumption literal;
// drop one conflicting literal per round until compatible
int npins = 0;
for (const auto& var : m_vars) {
const int w = var.second->totalWidth();
for (int b = 0; b < w; ++b) {
const bool target = (VL_RANDOM_RNG_I(rngr) & 1);
os << "(declare-fun a" << npins << " () Bool)\n";
os << "(assert (= a" << npins << " (=";
var.second->emitExtract(os, b);
os << " #b" << (target ? '1' : '0') << ")))\n";
++npins;
}
}
std::vector<bool> dropped(npins, false);
for (int round = 0; round <= npins; ++round) {
os << "(check-sat-assuming (";
for (int k = 0; k < npins; ++k) {
if (!dropped[k]) os << " a" << k;
}
os << "))\n";
if (parseSolution(os)) return;
// get-unsat-assumptions only echoes still-active literals,
// so the first in-range index is a live conflicting bit.
const std::vector<int> core = readUnsatAssumptions(os);
for (const int idx : core) {
if (idx < npins) {
dropped[idx] = true;
break;
}
}
}
}
void VlRandomizer::solveDiversityXor(VlRNG& rngr, std::iostream& os) {
bool sat = true;
for (int i = 0; i < _VL_SOLVER_HASH_LEN_TOTAL && sat; ++i) {
os << "(assert ";
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN);
os << ")\n";
os << "\n(check-sat)\n";
sat = parseSolution(os);
}
}
// False once the solver is gone, so no reply loop can spin forever
static bool readNonBlankLine(std::istream& is, std::string& liner) {
do {
if (!std::getline(is, liner)) return false;
} while (liner.empty());
return true;
}
bool VlRandomizer::checkSat(std::iostream& os) {
std::string result;
do { std::getline(os, result); } while (result.empty());
if (!readNonBlankLine(os, result)) return false;
return result == "sat";
}
std::vector<int> VlRandomizer::readUnsatAssumptions(std::iostream& os) {
os << "(get-unsat-assumptions)\n";
std::string line;
do { std::getline(os, line); } while (line.empty());
// The response lists only "a<N>" literals; collect each full integer run.
void VlRandomizer::relaxSoftConstraints(std::iostream& os) {
// Re-add softs highest-priority first, dropping incompatible ones.
const size_t nSoft = m_softConstraints.size();
if (nSoft == 0) return;
os << "(push 1)\n";
for (const auto& s : m_softConstraints) os << "(assert (= #b1 " << s << "))\n";
os << "(check-sat)\n";
if (checkSat(os)) return;
os << "(pop 1)\n";
for (auto it = m_softConstraints.rbegin(); it != m_softConstraints.rend(); ++it) {
os << "(push 1)\n";
os << "(assert (= #b1 " << *it << "))\n";
os << "(check-sat)\n";
if (!checkSat(os)) os << "(pop 1)\n";
}
}
// Every complete run of digits in the reply, in order
static std::vector<int> scanIntRuns(const std::string& reply) {
std::vector<int> idxs;
std::string num;
for (const char c : line) {
for (const char c : reply) {
if (std::isdigit(static_cast<unsigned char>(c))) {
num += c;
} else if (!num.empty()) {
@ -727,60 +740,66 @@ std::vector<int> VlRandomizer::readUnsatAssumptions(std::iostream& os) {
return idxs;
}
bool VlRandomizer::parseSolution(std::iostream& os, bool log) {
std::string sat;
do { std::getline(os, sat); } while (sat == "");
if (sat == "unsat") {
if (!log) return false;
os << "(get-unsat-core) \n";
sat.clear();
std::getline(os, sat);
std::vector<int> numbers;
std::string currentNum;
for (const char c : sat) {
if (std::isdigit(c)) {
currentNum += c;
numbers.push_back(std::stoi(currentNum));
currentNum.clear();
}
}
if (Verilated::threadContextp()->warnUnsatConstr()) {
for (const int n : numbers) {
if (n < m_constraints_line.size()) {
const std::string& constraint_info = m_constraints_line[n];
// Parse "filename:linenum source" format
const size_t colon_pos = constraint_info.find(':');
if (colon_pos != std::string::npos) {
const std::string filename = constraint_info.substr(0, colon_pos);
const size_t space_pos = constraint_info.find(" ", colon_pos);
std::string linenum_str;
std::string source;
if (space_pos != std::string::npos) {
linenum_str
= constraint_info.substr(colon_pos + 1, space_pos - colon_pos - 1);
source = constraint_info.substr(space_pos + 3);
} else {
linenum_str = constraint_info.substr(colon_pos + 1);
}
const int linenum = std::stoi(linenum_str);
std::string msg = "UNSATCONSTR: Unsatisfied constraint";
if (!source.empty()) {
// Trim leading whitespace and add quotes
const size_t start = source.find_first_not_of(" \t");
if (start != std::string::npos) {
msg += ": '" + source.substr(start) + "'";
}
}
VL_WARN_MT(filename.c_str(), linenum, "", msg.c_str());
} else {
VL_PRINTF("%%Warning-UNSATCONSTR: Unsatisfied constraint: %s\n",
constraint_info.c_str());
}
std::vector<int> VlRandomizer::readUnsatAssumptions(std::iostream& os) {
os << "(get-unsat-assumptions)\n";
std::string line;
if (!readNonBlankLine(os, line)) return {};
// The response lists only "a<N>" literals; collect each full integer run.
return scanIntRuns(line);
}
// Re-solve with named asserts so an unsat core can name the failing constraints
void VlRandomizer::reportUnsatSetup(std::iostream& os,
const std::vector<std::string>& uniqueExprs) {
os << "(set-option :produce-unsat-cores true)\n";
os << "(set-logic QF_ABV)\n";
emitDefines(os);
emitDeclares(os, false);
emitAsserts(os, uniqueExprs, true);
os << "(check-sat)\n";
std::string status;
if (!readNonBlankLine(os, status)) return;
if (status == "unsat") reportUnsatCore(os);
}
void VlRandomizer::reportUnsatCore(std::iostream& os) {
os << "(get-unsat-core)\n";
std::string reply;
std::getline(os, reply);
const std::vector<int> numbers = scanIntRuns(reply);
if (Verilated::threadContextp()->warnUnsatConstr()) {
for (const int n : numbers) {
if (static_cast<size_t>(n) < m_constraints_line.size()) {
const std::string& constraint_info = m_constraints_line[n];
// Parse "filename:linenum source" format, parts optional
std::string filename;
int linenum = 0;
std::string source = constraint_info;
const size_t colon_pos = constraint_info.find(':');
if (colon_pos != std::string::npos) {
filename = constraint_info.substr(0, colon_pos);
const size_t space_pos = constraint_info.find(" ", colon_pos);
const size_t num_end
= space_pos == std::string::npos ? constraint_info.size() : space_pos;
linenum = std::atoi(
constraint_info.substr(colon_pos + 1, num_end - colon_pos - 1).c_str());
source = space_pos == std::string::npos
? ""
: constraint_info.substr(space_pos + 3);
}
std::string msg = "UNSATCONSTR: Unsatisfied constraint";
const size_t start = source.find_first_not_of(" \t");
if (start != std::string::npos) msg += ": '" + source.substr(start) + "'";
VL_WARN_MT(filename.c_str(), linenum, "", msg.c_str());
}
}
return false;
}
}
bool VlRandomizer::parseSolution(std::iostream& os) {
std::string sat;
if (!readNonBlankLine(os, sat)) return false;
if (sat == "unsat") return false;
if (sat != "sat") {
std::stringstream msg;
msg << "Internal: Solver error: " << sat;
@ -789,25 +808,29 @@ bool VlRandomizer::parseSolution(std::iostream& os, bool log) {
return false;
}
os << "(get-value (";
std::stringstream getValueStr;
for (const auto& var : m_vars) {
if (var.second->dimension() > 0) {
auto arrVarsp = std::make_shared<const ArrayInfoMap>(m_arr_vars);
var.second->setArrayInfo(arrVarsp);
}
var.second->emitGetValue(os);
var.second->emitGetValue(getValueStr);
}
os << "))\n";
if (getValueStr.str() == "") {
// Mark as m_checkOnly to skip generation of any subsequent solver calls
m_checkOnly = true;
return true;
}
os << "(get-value (" << getValueStr.str() << "))\n";
// Quasi-parse S-expression of the form ((x #xVALUE) (y #bVALUE) (z #xVALUE))
char c;
os >> c;
if (c != '(') {
if (!(os >> c) || c != '(') {
VL_WARN_MT(__FILE__, __LINE__, "randomize",
"Internal: Unable to parse solver's response: invalid S-expression");
return false;
}
while (true) {
os >> c;
if (!(os >> c)) return false;
if (c == ')') break;
if (c != '(') {
VL_WARN_MT(__FILE__, __LINE__, "randomize",
@ -912,6 +935,7 @@ void VlRandomizer::clearConstraints() {
m_constraints_line.clear();
m_solveBefore.clear();
m_softConstraints.clear();
m_unique_arrays.clear(); // Re-registered by constraint setup
// Keep m_vars for class member randomization
}
@ -930,13 +954,7 @@ void VlRandomizer::solveBefore(const std::string& beforeName, const std::string&
m_solveBefore.emplace_back(beforeName, afterName);
}
bool VlRandomizer::nextPhased(VlRNG& rngr) {
// Phased solving for solve...before constraints.
// Variables are solved in layers determined by topological sort of the
// solve-before dependency graph. Each layer is solved with ALL constraints
// (preserving the solution space) but earlier layers' values are pinned.
// Step 1: Build dependency graph (before -> {after vars})
bool VlRandomizer::buildSolveLayers(std::vector<std::vector<std::string>>& layersr) {
std::map<std::string, std::set<std::string>> graph;
std::map<std::string, int> inDegree;
std::set<std::string> solveBeforeVars;
@ -953,18 +971,12 @@ bool VlRandomizer::nextPhased(VlRNG& rngr) {
if (inDegree.find(after) == inDegree.end()) inDegree[after] = 0;
}
// Compute in-degrees (after depends on before, so edge is before->after,
// but for solving order: before has no incoming edge from after)
// Actually: "solve x before y" means x should be solved first.
// Dependency: y depends on x. Edge: x -> y. in-degree of y increases.
// "solve x before y": edge x -> y, in-degree of y increases
for (const auto& entry : graph) {
for (const auto& to : entry.second) { inDegree[to]++; }
}
// Step 2: Topological sort into layers (Kahn's algorithm)
std::vector<std::vector<std::string>> layers;
std::set<std::string> remaining = solveBeforeVars;
while (!remaining.empty()) {
std::vector<std::string> currentLayer;
for (const auto& var : remaining) {
@ -981,22 +993,38 @@ bool VlRandomizer::nextPhased(VlRNG& rngr) {
for (const auto& to : graph[var]) { inDegree[to]--; }
}
}
layers.push_back(std::move(currentLayer));
layersr.push_back(std::move(currentLayer));
}
return true;
}
// If only one layer, no phased solving needed -- fall through to normal path
// (all solve_before vars are independent, no actual ordering required)
if (layers.size() <= 1) {
// Clear solve_before temporarily and call normal next()
const auto saved = std::move(m_solveBefore);
m_solveBefore.clear();
const bool result = next(rngr);
m_solveBefore = std::move(saved);
return result;
const char* VlRandomizer::phasedLogic() const {
for (const auto& var : m_vars) {
if (var.second->dimension() == 0) continue;
if (!var.second->hasMatchingElements(m_arr_vars, var.second->name())) return "ALL";
}
return "QF_ABV";
}
// Step 3: Solve phase by phase
bool VlRandomizer::nextPhased(VlRNG& rngr, const std::vector<std::string>& uniqueExprs) {
// Solve layer by layer with ALL constraints, pinning earlier layers
std::vector<std::vector<std::string>> layers;
if (!buildSolveLayers(layers)) return false;
// One layer: all solve_before vars are independent, no ordering required
if (layers.size() <= 1) return nextFlat(rngr, uniqueExprs);
if (solvePhases(rngr, layers, uniqueExprs)) return true;
// Retry once with the randc cycle cleared, as nextFlat does
if (m_randcUsedValues.empty()) return false;
m_randcUsedValues.clear();
return solvePhases(rngr, layers, uniqueExprs);
}
bool VlRandomizer::solvePhases(VlRNG& rngr, const std::vector<std::vector<std::string>>& layers,
const std::vector<std::string>& uniqueExprs) {
std::map<std::string, std::string> solvedValues; // varName -> SMT value literal
const char* const logicp = phasedLogic();
for (size_t phase = 0; phase < layers.size(); phase++) {
const bool isFinalPhase = (phase == layers.size() - 1);
@ -1004,143 +1032,45 @@ bool VlRandomizer::nextPhased(VlRNG& rngr) {
std::iostream& os = getSolver();
if (!os) return false;
// Solver session setup
os << "(set-option :produce-models true)\n";
os << "(set-logic QF_ABV)\n";
os << "(define-fun __Vbv ((b Bool)) (_ BitVec 1) (ite b #b1 #b0))\n";
os << "(define-fun __Vbool ((v (_ BitVec 1))) Bool (= #b1 v))\n";
os << "(set-logic " << logicp << ")\n";
emitDefines(os);
emitDeclares(os, false);
// Declare ALL variables
for (const auto& var : m_vars) {
if (var.second->dimension() > 0) {
auto arrVarsp = std::make_shared<const ArrayInfoMap>(m_arr_vars);
var.second->setArrayInfo(arrVarsp);
}
os << "(declare-fun " << var.first << " () ";
var.second->emitType(os);
os << ")\n";
}
// Pin all previously solved variables
for (const auto& entry : solvedValues) {
os << "(assert (= " << entry.first << " " << entry.second << "))\n";
}
// Assert ALL constraints
for (const std::string& constraint : m_constraints) {
os << "(assert (= #b1 " << constraint << "))\n";
}
emitAsserts(os, uniqueExprs, false);
// Randc: exclude previously used values
emitRandcExclusions(os);
// Soft constraints participate in every phase, priority-ordered.
relaxSoftConstraints(os);
// Initial check-sat WITHOUT diversity (guaranteed sat if constraints are consistent)
os << "(check-sat)\n";
if (isFinalPhase) {
// Final phase: use parseSolution to write ALL values to memory
bool sat = parseSolution(os, true);
const bool sat = parseSolution(os);
if (!sat) {
if (!m_randcVarNames.empty()) m_randcUsedValues.clear();
os << "(reset)\n";
return false;
}
solveDiversityXor(rngr, os);
// Record solved randc values for future exclusion
recordRandcValues();
// Diversity loop (same as normal next())
for (int i = 0; i < _VL_SOLVER_HASH_LEN_TOTAL && sat; ++i) {
os << "(assert ";
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN);
os << ")\n";
os << "\n(check-sat)\n";
sat = parseSolution(os, false);
(void)sat;
}
os << "(reset)\n";
} else {
// Intermediate phase: extract values for current layer variables only
std::string satResponse;
do { std::getline(os, satResponse); } while (satResponse.empty());
if (satResponse != "sat") {
if (!checkSat(os)) {
os << "(reset)\n";
return false;
}
// Build get-value variable list for this layer
const auto& layerVars = layers[phase];
auto getValueCmd = [&]() {
os << "(get-value (";
for (const auto& varName : layerVars) {
if (m_vars.count(varName)) os << varName << " ";
}
os << "))\n";
};
// Helper to parse ((name1 value1) (name2 value2) ...) response
auto parseGetValue = [&]() -> bool {
char c;
os >> c; // outer '('
while (true) {
os >> c;
if (c == ')') break; // outer closing
if (c != '(') return false;
std::string name;
os >> name;
// Read value handling nested parens for (_ bvN W) format
os >> std::ws;
std::string value;
char firstChar;
os.get(firstChar);
if (firstChar == '(') {
// Compound value like (_ bv5 32)
value = "(";
int depth = 1;
while (depth > 0) {
os.get(c);
value += c;
if (c == '(')
depth++;
else if (c == ')')
depth--;
}
// Read closing ')' of the pair
os >> c;
} else {
// Atom value like #x00000005 or #b101
value += firstChar;
while (os.get(c) && c != ')') { value += c; }
// Trim trailing whitespace
const size_t end = value.find_last_not_of(" \t\n\r");
if (end != std::string::npos) value = value.substr(0, end + 1);
}
solvedValues[name] = value;
}
return true;
};
// Get baseline values (deterministic, always valid)
getValueCmd();
if (!parseGetValue()) {
if (!solvePhaseValues(os, rngr, layers[phase], solvedValues)) {
os << "(reset)\n";
return false;
}
// Try diversity: add random constraint, re-check. If sat, get
// updated (more diverse) values. If unsat, keep baseline values.
os << "(assert ";
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN);
os << ")\n";
os << "(check-sat)\n";
satResponse.clear();
do { std::getline(os, satResponse); } while (satResponse.empty());
if (satResponse == "sat") {
getValueCmd();
parseGetValue();
}
os << "(reset)\n";
}
}
@ -1148,6 +1078,86 @@ bool VlRandomizer::nextPhased(VlRNG& rngr) {
return true;
}
// Intermediate phase: extract this layer's values, then try one diversity round
bool VlRandomizer::solvePhaseValues(std::iostream& os, VlRNG& rngr,
const std::vector<std::string>& layerVars,
std::map<std::string, std::string>& solvedValuesr) {
const auto emitGetValueCmd = [&]() {
os << "(get-value (";
for (const auto& varName : layerVars) {
const auto it = m_vars.find(varName);
if (it->second->dimension() > 0) {
auto arrVarsp = std::make_shared<const ArrayInfoMap>(m_arr_vars);
it->second->setArrayInfo(arrVarsp);
// Enumerable arrays: query each element for a QF_ABV-safe pin.
if (it->second->hasMatchingElements(m_arr_vars, it->second->name())) {
it->second->emitGetValue(os);
continue;
}
}
os << varName << " ";
}
os << "))\n";
};
// Get baseline values (deterministic, always valid)
emitGetValueCmd();
if (!parsePhaseValues(os, solvedValuesr)) return false;
// Try diversity: add random constraint, re-check. If sat, get
// updated (more diverse) values. If unsat, keep baseline values.
os << "(assert ";
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN);
os << ")\n";
os << "(check-sat)\n";
if (checkSat(os)) {
emitGetValueCmd();
(void)parsePhaseValues(os, solvedValuesr);
}
return true;
}
bool VlRandomizer::parsePhaseValues(std::istream& is,
std::map<std::string, std::string>& solvedValuesr) {
// Parse ((name value) ...): one paren-depth counter drives every match.
char c = 0;
is >> c; // outer '('
if (c != '(') return false;
int depth = 1;
std::string tokens[2];
std::string cur;
int fields = 0;
const auto flush = [&]() {
if (cur.empty()) return;
if (fields < 2) tokens[fields] = cur;
++fields;
cur.clear();
};
while (depth > 0 && is.get(c)) {
if (c == '(') {
++depth;
if (depth >= 3) cur += c;
} else if (c == ')') {
--depth;
if (depth >= 2) {
cur += c;
} else if (depth == 1) {
flush();
if (fields == 2) solvedValuesr[tokens[0]] = tokens[1];
fields = 0;
}
} else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
if (depth >= 3) {
cur += c;
} else {
flush();
}
} else {
cur += c;
}
}
return true;
}
#ifdef VL_DEBUG
void VlRandomizer::dump() const {
for (const auto& var : m_vars) {

View File

@ -104,13 +104,32 @@ public:
count_cache[base_name] = count;
return count;
}
bool hasMatchingElements(const ArrayInfoMap& arr_vars, const std::string& base_name) const {
return arr_vars.find(base_name + "0") != arr_vars.end();
}
};
// SMT key width per associative-array level (string = 128, integral = 8 * sizeof).
template <typename T>
struct VlRandomAssocKeyWidths final {
static void push(std::vector<size_t>&) {}
};
template <typename T_Key, typename T_Value>
struct VlRandomAssocKeyWidths<VlAssocArray<T_Key, T_Value>> final {
static void push(std::vector<size_t>& widths) {
widths.push_back(std::is_same<T_Key, std::string>::value ? 128 : sizeof(T_Key) * 8);
VlRandomAssocKeyWidths<T_Value>::push(widths);
}
};
template <typename T>
class VlRandomArrayVarTemplate final : public VlRandomVar {
// Static key widths per level for the empty-array declaration fallback
const std::vector<size_t> m_fallbackIdxWidths;
public:
VlRandomArrayVarTemplate(const std::string& name, int width, void* datap, int dimension,
std::uint32_t randModeIdx)
: VlRandomVar{name, width, datap, dimension, randModeIdx} {}
std::uint32_t randModeIdx, const std::vector<size_t>& idxWidths = {})
: VlRandomVar{name, width, datap, dimension, randModeIdx}
, m_fallbackIdxWidths{idxWidths} {}
void* datap(int idx) const override {
const std::string indexed_name = name() + std::to_string(idx);
const auto it = m_arrVarsRefp->find(indexed_name);
@ -175,7 +194,13 @@ public:
}
} else {
if (dimension() > 0) {
for (int i = 0; i < dimension(); ++i) s << "(Array (_ BitVec 32) ";
// Empty array: declare from the static key widths, not a 32-bit default.
for (int i = 0; i < dimension(); ++i) {
const size_t idxWidth = i < static_cast<int>(m_fallbackIdxWidths.size())
? m_fallbackIdxWidths[i]
: 32;
s << "(Array (_ BitVec " << idxWidth << ") ";
}
s << "(_ BitVec " << width() << ")";
for (int i = 0; i < dimension(); ++i) s << ")";
} else {
@ -217,8 +242,7 @@ class VlRandomizer VL_NOT_FINAL {
std::set<std::string> m_disabledVars; // Variables with rand_mode off (skip write-back)
// variables
ArrayInfoMap m_arr_vars; // Tracks each element in array structures for iteration
std::vector<std::string> m_unique_arrays;
std::map<std::string, uint32_t> m_unique_array_sizes;
std::vector<std::string> m_unique_arrays; // Arrays whose elements must be distinct
const VlQueue<CData>* m_randmodep = nullptr; // rand_mode state;
const VlQueue<CData>* m_static_randmodep = nullptr; // Static rand_mode state (shared)
std::unordered_set<std::string> m_staticVars; // Names of static rand vars
@ -233,14 +257,37 @@ class VlRandomizer VL_NOT_FINAL {
// PRIVATE METHODS
void randomConstraint(std::ostream& os, VlRNG& rngr, int bits);
bool parseSolution(std::iostream& os, bool log = false);
bool parseSolution(std::iostream& os);
bool checkSat(std::iostream& os);
// Assert the maximal compatible soft-constraint set onto the open session.
void relaxSoftConstraints(std::iostream& os);
// Indices of the "a<N>" literals named by (get-unsat-assumptions).
std::vector<int> readUnsatAssumptions(std::iostream& os);
void reportUnsatSetup(std::iostream& os, const std::vector<std::string>& uniqueExprs);
void reportUnsatCore(std::iostream& os);
void emitRandcExclusions(std::ostream& os) const; // Emit randc exclusion constraints
void recordRandcValues(); // Record solved randc values for future exclusion
size_t hashConstraints() const;
bool nextPhased(VlRNG& rngr); // Phased solving for solve...before
size_t hashConstraints(const std::vector<std::string>& extras) const;
bool nextRandomize(VlRNG& rngr, bool checkOnly);
// "(distinct ...)" expression per unique-constrained array
std::vector<std::string> buildUniqueExprs() const;
void emitDefines(std::ostream& os) const;
void emitDeclares(std::ostream& os, bool pinCurrent) const;
void emitAsserts(std::ostream& os, const std::vector<std::string>& extras, bool named) const;
bool nextFlat(VlRNG& rngr, const std::vector<std::string>& uniqueExprs);
void solveDiversity(VlRNG& rngr, std::iostream& os);
void solveDiversityPins(VlRNG& rngr, std::iostream& os);
void solveDiversityXor(VlRNG& rngr, std::iostream& os);
// Layers of solve...before variables in dependency order
bool buildSolveLayers(std::vector<std::vector<std::string>>& layersr);
const char* phasedLogic() const;
bool nextPhased(VlRNG& rngr, const std::vector<std::string>& uniqueExprs);
bool solvePhases(VlRNG& rngr, const std::vector<std::vector<std::string>>& layers,
const std::vector<std::string>& uniqueExprs);
bool solvePhaseValues(std::iostream& os, VlRNG& rngr,
const std::vector<std::string>& layerVars,
std::map<std::string, std::string>& solvedValuesr);
bool parsePhaseValues(std::istream& is, std::map<std::string, std::string>& solvedValuesr);
public:
// CONSTRUCTORS
@ -377,12 +424,12 @@ public:
}
// Register queue of non-struct types
template <typename T>
template <typename T, size_t N_MaxSize>
typename std::enable_if<!VlContainsCustomStruct<T>::value, void>::type
write_var(VlQueue<T>& var, int width, const char* name, int dimension,
write_var(VlQueue<T, N_MaxSize>& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (m_vars.find(name) == m_vars.end()) {
m_vars[name] = std::make_shared<const VlRandomArrayVarTemplate<VlQueue<T>>>(
m_vars[name] = std::make_shared<const VlRandomArrayVarTemplate<VlQueue<T, N_MaxSize>>>(
name, width, &var, dimension, randmodeIdx);
}
if (dimension > 0) {
@ -394,9 +441,9 @@ public:
}
// Register queue of structs
template <typename T>
template <typename T, size_t N_MaxSize>
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
write_var(VlQueue<T>& var, int width, const char* name, int dimension,
write_var(VlQueue<T, N_MaxSize>& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (dimension > 0) record_struct_arr(var, name, dimension, {}, {});
}
@ -433,9 +480,11 @@ public:
write_var(VlAssocArray<T_Key, T_Value>& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (m_vars.find(name) == m_vars.end()) {
std::vector<size_t> keyWidths;
VlRandomAssocKeyWidths<VlAssocArray<T_Key, T_Value>>::push(keyWidths);
m_vars[name]
= std::make_shared<const VlRandomArrayVarTemplate<VlAssocArray<T_Key, T_Value>>>(
name, width, &var, dimension, randmodeIdx);
name, width, &var, dimension, randmodeIdx, keyWidths);
}
if (dimension > 0) {
m_index = 0;
@ -465,11 +514,10 @@ public:
++m_index;
}
// This is the "Sender" API for the generated code
void rand_unique(const std::string& name, uint32_t size) {
m_unique_arrays.push_back(name);
m_unique_array_sizes[name] = size;
}
// This is the "Sender" API for the generated code.
// The elements to make distinct are taken from the array element table at
// solve time, so a container resized by the solver is handled correctly.
void rand_unique(const std::string& name) { m_unique_arrays.push_back(name); }
// Recursively record all elements in an unpacked array
template <typename T, std::size_t N_Depth>
@ -488,8 +536,8 @@ public:
}
// Recursively record all elements in a queue
template <typename T>
void record_arr_table(VlQueue<T>& var, const std::string& name, int dimension,
template <typename T, size_t N_MaxSize>
void record_arr_table(VlQueue<T, N_MaxSize>& var, const std::string& name, int dimension,
std::vector<IData> indices, std::vector<size_t> idxWidths) {
if ((dimension > 0) && (var.size() != 0)) {
idxWidths.push_back(32);
@ -522,7 +570,8 @@ public:
idxWidths.push_back(idx_width);
indices.insert(indices.end(), integral_index.begin(), integral_index.end());
record_arr_table(var.at(key), indexed_name, dimension - 1, indices, idxWidths);
record_arr_table(var.atWrite(key), indexed_name, dimension - 1, indices,
idxWidths);
// Cleanup indices and widths
idxWidths.pop_back();
@ -562,8 +611,8 @@ public:
}
// Recursively process VlQueue of structs
template <typename T>
void record_struct_arr(VlQueue<T>& var, const std::string& name, int dimension,
template <typename T, size_t N_MaxSize>
void record_struct_arr(VlQueue<T, N_MaxSize>& var, const std::string& name, int dimension,
std::vector<IData> indices, std::vector<size_t> idxWidths) {
if ((dimension > 0) && (var.size() != 0)) {
idxWidths.push_back(32);
@ -596,7 +645,7 @@ public:
std::string result = oss.str();
result.insert(result.begin(), int(idx_width / 4) - result.size(), '0');
record_struct_arr(var.at(key), name + "." + result, dimension - 1, indices,
record_struct_arr(var.atWrite(key), name + "." + result, dimension - 1, indices,
idxWidths);
}
}
@ -724,11 +773,20 @@ public:
bool basicStdRandomization(VlAssocArray<T_Key, T_Value>& value, size_t width) {
T_Key key;
for (int exists = value.first(key); exists; exists = value.next(key)) {
basicStdRandomization(value.at(key), width);
basicStdRandomization(value.atWrite(key), width);
}
return true;
}
bool next() { return VlRandomizer::next(m_rng); }
};
//======================================================================
//Helper method for dynamic array handling in SMT expressions
inline std::string vlToSolverHex(const IData& value) {
std::ostringstream oss;
oss << std::hex << std::setfill('0') << std::setw(8) << value;
return oss.str();
}
#endif // Guard

View File

@ -327,7 +327,7 @@ VerilatedDeserialize& operator>>(VerilatedDeserialize& os, VlAssocArray<T_Key, T
T_Value value;
os >> index;
os >> value;
rhs.at(index) = value;
rhs.atWrite(index) = value;
}
return os;
}

View File

@ -449,7 +449,7 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::initLib(const std::string& name) VL_MT_
// All of these take a destination pointer where the string will be emitted,
// and a value to convert. There are a couple of variants for efficiency.
static inline void cvtCDataToStr(char* dstp, CData value) {
inline void cvtCDataToStr(char* dstp, CData value) {
#ifdef VL_HAVE_SSE2
// Similar to cvtSDataToStr but only the bottom 8 byte lanes are used
const __m128i a = _mm_cvtsi32_si128(value);
@ -471,7 +471,7 @@ static inline void cvtCDataToStr(char* dstp, CData value) {
#endif
}
static inline void cvtSDataToStr(char* dstp, SData value) {
inline void cvtSDataToStr(char* dstp, SData value) {
#ifdef VL_HAVE_SSE2
// We want each bit in the 16-bit input value to end up in a byte lane
// within the 128-bit XMM register. Note that x86 is little-endian and we
@ -507,7 +507,7 @@ static inline void cvtSDataToStr(char* dstp, SData value) {
#endif
}
static inline void cvtIDataToStr(char* dstp, IData value) {
inline void cvtIDataToStr(char* dstp, IData value) {
#ifdef VL_HAVE_AVX2
// Similar to cvtSDataToStr but the bottom 16-bits are processed in the
// top half of the YMM registers
@ -526,7 +526,7 @@ static inline void cvtIDataToStr(char* dstp, IData value) {
#endif
}
static inline void cvtQDataToStr(char* dstp, QData value) {
inline void cvtQDataToStr(char* dstp, QData value) {
cvtIDataToStr(dstp, value >> 32);
cvtIDataToStr(dstp + 32, value);
}

View File

@ -214,7 +214,7 @@ public:
};
static_assert(sizeof(WDataInP) == sizeof(EData*), "WDataInP should be a single pointer");
static int _vl_cmp_w(int words, WDataInP const lwp, WDataInP const rwp) VL_PURE;
inline int _vl_cmp_w(int words, WDataInP const lwp, WDataInP const rwp) VL_PURE;
template <std::size_t N_Words>
bool VlWide<N_Words>::operator<(const VlWide<N_Words>& rhs) const VL_PURE {
@ -337,6 +337,7 @@ public:
~VlProcess() {
if (m_parentp) m_parentp->detach(this);
if (t_currentp == this) t_currentp = m_parentp.get();
}
void attach(VlProcess* childp) { m_children.insert(childp); }
@ -931,7 +932,7 @@ public:
VlQueue min(T_Func with_func) const {
if (m_deque.empty()) return VlQueue{};
const auto it = std::min_element(m_deque.cbegin(), m_deque.cend(),
[&with_func](const IData& a, const IData& b) {
[&with_func](const T_Value& a, const T_Value& b) {
return with_func(0, a) < with_func(0, b);
});
return VlQueue::consV(*it);
@ -945,7 +946,7 @@ public:
VlQueue max(T_Func with_func) const {
if (m_deque.empty()) return VlQueue{};
const auto it = std::max_element(m_deque.cbegin(), m_deque.cend(),
[&with_func](const IData& a, const IData& b) {
[&with_func](const T_Value& a, const T_Value& b) {
return with_func(0, a) < with_func(0, b);
});
return VlQueue::consV(*it);
@ -1119,9 +1120,7 @@ public:
return 1;
}
// Setting. Verilog: assoc[index] = v
// Can't just overload operator[] or provide a "at" reference to set,
// because we need to be able to insert only when the value is set
T_Value& at(const T_Key& index) {
T_Value& atWrite(const T_Key& index) {
const auto it = m_map.find(index);
if (it == m_map.end()) {
std::pair<typename Map::iterator, bool> pit = m_map.emplace(index, m_defaultValue);
@ -1137,7 +1136,7 @@ public:
}
// Setting as a chained operation
VlAssocArray& set(const T_Key& index, const T_Value& value) {
at(index) = value;
atWrite(index) = value;
return *this;
}
VlAssocArray& setDefault(const T_Value& value) {
@ -1394,7 +1393,7 @@ void VL_READMEM_N(bool hex, int bits, const std::string& filename,
QData addr;
std::string data;
if (rmem.get(addr /*ref*/, data /*ref*/)) {
rmem.setData(&(obj.at(addr)), data);
rmem.setData(&(obj.atWrite(addr)), data);
} else {
break;
}
@ -2069,8 +2068,24 @@ struct VlNull final {
operator T*() const {
return nullptr;
}
template <class T>
bool operator==(T* rhs) const {
return !rhs;
}
template <class T>
bool operator==(const T* rhs) const {
return !rhs;
}
};
inline bool operator==(const void* ptr, VlNull) { return !ptr; }
template <class T>
inline bool operator==(T* lhs, VlNull) {
return !lhs;
}
template <class T>
inline bool operator==(const T* lhs, VlNull) {
return !lhs;
}
//===================================================================
// Verilog class reference container
@ -2223,7 +2238,7 @@ public:
};
template <typename T_Lhs, typename T_Out>
static inline bool VL_CAST_DYNAMIC(VlClassRef<T_Lhs> in, VlClassRef<T_Out>& outr) {
inline bool VL_CAST_DYNAMIC(VlClassRef<T_Lhs> in, VlClassRef<T_Out>& outr) {
if (!in) {
outr = VlNull{};
return true;
@ -2237,7 +2252,7 @@ static inline bool VL_CAST_DYNAMIC(VlClassRef<T_Lhs> in, VlClassRef<T_Out>& outr
}
template <typename T_Lhs>
static inline bool VL_CAST_DYNAMIC(VlNull, VlClassRef<T_Lhs>& outr) {
inline bool VL_CAST_DYNAMIC(VlNull, VlClassRef<T_Lhs>& outr) {
outr = VlNull{};
return true;
}

View File

@ -539,7 +539,7 @@ public:
for (auto idx : index()) m_fullname += "[" + std::to_string(idx) + "]";
return m_fullname.c_str();
}
void* prevDatap() const { return m_prevDatap; }
uint8_t* prevDatap() const { return m_prevDatap; }
void* varDatap() const override { return m_varDatap; }
void createPrevDatap() {
if (VL_UNLIKELY(!m_prevDatap)) {
@ -1027,6 +1027,26 @@ struct VerilatedVpiTimedCbsCmp final {
class VerilatedVpiError;
void vl_vpi_put_word(const VerilatedVpioVar* vop, QData word, size_t bitCount, size_t addOffset);
// Information about how to access packed array data.
// If underlying type is multi-word (VLVT_WDATA), the packed element might straddle word
// boundaries, in which case m_maskHi != 0.
template <typename T>
struct VarAccessInfo final {
T* m_datap; // Typed pointer to packed array base address
size_t m_bitOffset; // Data start location (bit offset)
size_t m_wordOffset; // Data start location (word offset, VLVT_WDATA only)
T m_maskLo; // Access mask for m_datap[m_wordOffset]
T m_maskHi; // Access mask for m_datap[m_wordOffset + 1] (VLVT_WDATA only)
};
template <typename T>
VarAccessInfo<T> vl_vpi_var_access_info(const VerilatedVpioVarBase* vop, size_t bitCount,
size_t addOffset);
template <typename T>
T vl_vpi_get_word_gen(VarAccessInfo<T> info);
template <typename T>
void vl_vpi_put_word_gen(VarAccessInfo<T> info, T word);
class VerilatedVpiImp final {
enum { CB_ENUM_MAX_VALUE = cbAtEndOfSimTime + 1 }; // Maximum callback reason
using VpioCbList = std::list<VerilatedVpiCbHolder>;
@ -1169,6 +1189,72 @@ public:
s().m_cbCallList.clear();
return called;
}
template <typename T>
static bool valueDiffersFromPrev(VerilatedVpioVar* varop) {
VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: value_test %s v[0]=%d/%d %p %p size=%d\n",
varop->fullname(), *(static_cast<CData*>(varop->varDatap())),
*(varop->prevDatap()), varop->varDatap(), varop->prevDatap(),
varop->entSize()););
if (varop->bitSize() == 1) {
T* const prevDatap = reinterpret_cast<T*>(
varop->prevDatap()); // Was malloced when we added the callback
const VarAccessInfo<T> currInfo
= vl_vpi_var_access_info<T>(varop, varop->bitSize(), 0);
VarAccessInfo<T> prevInfo = currInfo;
prevInfo.m_datap = prevDatap;
return vl_vpi_get_word_gen(currInfo) != vl_vpi_get_word_gen(prevInfo);
}
return std::memcmp(varop->prevDatap(), varop->varDatap(), varop->entSize()) != 0;
}
static bool valueDiffersFromPrev(VerilatedVpioVar* varop) {
switch (varop->varp()->vltype()) {
case VLVT_UINT8: return valueDiffersFromPrev<CData>(varop);
case VLVT_UINT16: return valueDiffersFromPrev<SData>(varop);
case VLVT_UINT32: return valueDiffersFromPrev<IData>(varop);
case VLVT_UINT64: return valueDiffersFromPrev<QData>(varop);
case VLVT_WDATA:
return valueDiffersFromPrev<EData>(varop);
// LCOV_EXCL_START - Would require earlier type check to not catch that
default:
const std::string msg
= "Unsupported type (" + std::to_string(varop->varp()->vltype()) + ")";
VL_FATAL_MT(__FILE__, __LINE__, "", msg.c_str());
return true;
// LCOV_EXCL_STOP
}
}
template <typename T>
static void updatePrev(const VerilatedVpioVar* const varop) {
if (varop->bitSize() == 1) {
const VarAccessInfo<T> currInfo
= vl_vpi_var_access_info<T>(varop, varop->bitSize(), 0);
VarAccessInfo<T> prevInfo = currInfo;
T* const prevDatap = reinterpret_cast<T*>(varop->prevDatap());
prevInfo.m_datap = prevDatap;
const T currWord = vl_vpi_get_word_gen(currInfo);
vl_vpi_put_word_gen(prevInfo, currWord);
assert(std::memcmp(varop->prevDatap(), varop->varDatap(), varop->entSize()) == 0);
} else {
std::memcpy(varop->prevDatap(), varop->varDatap(), varop->entSize());
}
}
static void updatePrev(const VerilatedVpioVar* const varop) {
switch (varop->varp()->vltype()) {
case VLVT_UINT8: updatePrev<CData>(varop); break;
case VLVT_UINT16: updatePrev<SData>(varop); break;
case VLVT_UINT32: updatePrev<IData>(varop); break;
case VLVT_UINT64: updatePrev<QData>(varop); break;
case VLVT_WDATA:
updatePrev<EData>(varop);
break;
// LCOV_EXCL_START - Would require earlier type check to not catch that
default:
const std::string msg
= "Unsupported type (" + std::to_string(varop->varp()->vltype()) + ")";
VL_FATAL_MT(__FILE__, __LINE__, "", msg.c_str());
// LCOV_EXCL_STOP
}
}
static bool callValueCbs() VL_MT_UNSAFE_ONE {
assertOneCheck();
VpioCbList& cbObjList = s().m_cbCurrentLists[cbValueChange];
@ -1187,15 +1273,10 @@ public:
VerilatedVpiCbHolder& ho = *it++;
VerilatedVpioVar* const varop
= reinterpret_cast<VerilatedVpioVar*>(ho.cb_datap()->obj);
void* const newDatap = varop->varDatap();
void* const prevDatap = varop->prevDatap(); // Was malloced when we added the callback
VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: value_test %s v[0]=%d/%d %p %p\n",
varop->fullname(), *(static_cast<CData*>(newDatap)),
*(static_cast<CData*>(prevDatap)), newDatap, prevDatap););
if (std::memcmp(prevDatap, newDatap, varop->entSize()) != 0) {
if (valueDiffersFromPrev(varop)) {
VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: value_callback %" PRId64 " %s v[0]=%d\n",
ho.id(), varop->fullname(),
*(static_cast<CData*>(newDatap))););
*(static_cast<CData*>(varop->varDatap()))););
update.insert(varop);
vpi_get_value(ho.cb_datap()->obj, ho.cb_datap()->value);
(ho.cb_rtnp())(ho.cb_datap());
@ -1203,9 +1284,7 @@ public:
}
if (was_last) break;
}
for (const VerilatedVpioVar* const ip : update) {
std::memcpy(ip->prevDatap(), ip->varDatap(), ip->entSize());
}
for (const VerilatedVpioVar* const varop : update) updatePrev(varop);
return called;
}
static void dumpCbs() VL_MT_UNSAFE_ONE;
@ -2410,7 +2489,7 @@ vpiHandle vpi_register_cb(p_cb_data cb_data_p) {
return vop->castVpiHandle();
}
default:
VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Unsupported callback type %s", __func__,
VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Unsupported callback type '%s'", __func__,
VerilatedVpiError::strFromVpiCallbackReason(reason));
return nullptr;
}
@ -2856,7 +2935,7 @@ vpiHandle vpi_handle(PLI_INT32 type, vpiHandle object) {
return (new VerilatedVpioConst{vop->rangep()->left()})->castVpiHandle();
}
VL_VPI_WARNING_(__FILE__, __LINE__,
"%s: Unsupported vpiHandle (%p) for type %s, nothing will be returned",
"%s: Unsupported vpiHandle '%p' for type '%s', nothing will be returned",
__func__, object, VerilatedVpiError::strFromVpiMethod(type));
return nullptr;
}
@ -2870,7 +2949,7 @@ vpiHandle vpi_handle(PLI_INT32 type, vpiHandle object) {
return (new VerilatedVpioConst{vop->rangep()->right()})->castVpiHandle();
}
VL_VPI_WARNING_(__FILE__, __LINE__,
"%s: Unsupported vpiHandle (%p) for type %s, nothing will be returned",
"%s: Unsupported vpiHandle '%p' for type '%s', nothing will be returned",
__func__, object, VerilatedVpiError::strFromVpiMethod(type));
return nullptr;
}
@ -3031,8 +3110,9 @@ PLI_INT32 vpi_get(PLI_INT32 property, vpiHandle object) {
[[fallthrough]];
}
default:
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported property %s, nothing will be returned",
__func__, VerilatedVpiError::strFromVpiProp(property));
VL_VPI_ERROR_(__FILE__, __LINE__,
"%s: Unsupported property '%s', nothing will be returned", __func__,
VerilatedVpiError::strFromVpiProp(property));
return vpiUndefined;
}
}
@ -3195,18 +3275,6 @@ static void vl_strprintf(std::string& buffer, char const* fmt, ...) {
va_end(args);
}
// Information about how to access packed array data.
// If underlying type is multi-word (VLVT_WDATA), the packed element might straddle word
// boundaries, in which case m_maskHi != 0.
template <typename T>
struct VarAccessInfo final {
T* m_datap; // Typed pointer to packed array base address
size_t m_bitOffset; // Data start location (bit offset)
size_t m_wordOffset; // Data start location (word offset, VLVT_WDATA only)
T m_maskLo; // Access mask for m_datap[m_wordOffset]
T m_maskHi; // Access mask for m_datap[m_wordOffset + 1] (VLVT_WDATA only)
};
template <typename T>
VarAccessInfo<T> vl_vpi_var_access_info(const VerilatedVpioVarBase* vop, size_t bitCount,
size_t addOffset) {
@ -3262,21 +3330,25 @@ VarAccessInfo<T> vl_vpi_var_access_info(const VerilatedVpioVarBase* vop, size_t
}
template <typename T>
T vl_vpi_get_word_gen(const VerilatedVpioVarBase* vop, size_t bitCount, size_t addOffset) {
T vl_vpi_get_word_gen(VarAccessInfo<T> info) {
const size_t wordBits = sizeof(T) * 8;
const VarAccessInfo<T> info = vl_vpi_var_access_info<T>(vop, bitCount, addOffset);
if (info.m_maskHi)
if (info.m_maskHi) {
return ((info.m_datap[info.m_wordOffset] & info.m_maskLo) >> info.m_bitOffset)
| ((info.m_datap[info.m_wordOffset + 1] & info.m_maskHi)
<< (wordBits - info.m_bitOffset));
}
return (info.m_datap[info.m_wordOffset] & info.m_maskLo) >> info.m_bitOffset;
}
template <typename T>
void vl_vpi_put_word_gen(const VerilatedVpioVar* vop, T word, size_t bitCount, size_t addOffset) {
const size_t wordBits = sizeof(T) * 8;
T vl_vpi_get_word_gen(const VerilatedVpioVarBase* vop, size_t bitCount, size_t addOffset) {
const VarAccessInfo<T> info = vl_vpi_var_access_info<T>(vop, bitCount, addOffset);
return vl_vpi_get_word_gen(info);
}
template <typename T>
void vl_vpi_put_word_gen(VarAccessInfo<T> info, T word) {
const size_t wordBits = sizeof(T) * 8;
if (info.m_maskHi) {
info.m_datap[info.m_wordOffset + 1]
= (info.m_datap[info.m_wordOffset + 1] & ~info.m_maskHi)
@ -3287,6 +3359,12 @@ void vl_vpi_put_word_gen(const VerilatedVpioVar* vop, T word, size_t bitCount, s
| ((word << info.m_bitOffset) & info.m_maskLo);
}
template <typename T>
void vl_vpi_put_word_gen(const VerilatedVpioVar* vop, T word, size_t bitCount, size_t addOffset) {
const VarAccessInfo<T> info = vl_vpi_var_access_info<T>(vop, bitCount, addOffset);
vl_vpi_put_word_gen(info, word);
}
// bitCount: maximum number of bits to read, will stop earlier if it reaches the var bounds
// addOffset: additional read bitoffset
QData vl_vpi_get_word(const VerilatedVpioVarBase* vop, size_t bitCount, size_t addOffset) {
@ -3500,7 +3578,7 @@ void vpi_get_value(vpiHandle object, p_vpi_value valuep) {
VerilatedVpiError::strFromVpiVal(valuep->format), vop->fullname());
return;
}
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle (%p)", __func__, object);
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle '%p'", __func__, object);
}
vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_p*/,
@ -3841,7 +3919,7 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_
__func__, vop->fullname());
return nullptr;
}
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle (%p)", __func__, object);
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle '%p'", __func__, object);
return nullptr;
}
@ -4236,13 +4314,13 @@ void vpi_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, PLI_IN
const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
if (VL_UNLIKELY(!vop)) {
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle (%p)", __func__, object);
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle '%p'", __func__, object);
return;
}
if (vop->type() != vpiRegArray) {
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported type (%p, %s)", __func__, object,
VerilatedVpiError::strFromVpiObjType(vop->type()));
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported type '%s' for '%s'", __func__,
VerilatedVpiError::strFromVpiObjType(vop->type()), vop->name());
return;
}
@ -4418,13 +4496,13 @@ void vpi_put_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, PLI_IN
const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
if (VL_UNLIKELY(!vop)) {
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle (%p)", __func__, object);
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle '%p'", __func__, object);
return;
}
if (vop->type() != vpiRegArray) {
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported type (%p, %s)", __func__, object,
VerilatedVpiError::strFromVpiObjType(vop->type()));
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle type '%s' for '%s'", __func__,
VerilatedVpiError::strFromVpiObjType(vop->type()), vop->name());
return;
}

View File

@ -332,10 +332,19 @@
#ifdef VL_GCOV
extern "C" void __gcov_dump();
extern "C" void __gcov_reset();
// Dump internal code coverage data before e.g. std::abort()
# define VL_GCOV_DUMP() __gcov_dump()
// Dump, then re-arm dumping; dumping is one-shot, so without the reset a dump
// on a nonfatal path would silently discard everything counted after it
# define VL_GCOV_DUMP_RESET() \
do { \
__gcov_dump(); \
__gcov_reset(); \
} while (false)
#else
# define VL_GCOV_DUMP()
# define VL_GCOV_DUMP_RESET()
#endif
//=========================================================================
@ -368,6 +377,7 @@ extern "C" void __gcov_dump();
#define __STDC_FORMAT_MACROS
// Now that C++ requires these standard types the vl types are deprecated
#include <cstddef> // offsetof (used by generated VlVarTableEntry tables)
#include <cstdint>
#include <cinttypes>
#include <cmath>
@ -460,6 +470,7 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read()
// Integer size macros
#define VL_BYTESIZE 8 ///< Bits in a CData / byte
#define VL_BYTESIZE_LOG2 3 ///< log2(VL_BYTESIZE)
#define VL_SHORTSIZE 16 ///< Bits in a SData / short
#define VL_IDATASIZE 32 ///< Bits in an IData / word
#define VL_QUADSIZE 64 ///< Bits in a QData / quadword
@ -472,9 +483,9 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read()
#endif
/// Return number of bytes argument-number of bits needs (1 bit=1 byte)
#define VL_BYTES_I(nbits) (((nbits) + (VL_BYTESIZE - 1)) / VL_BYTESIZE)
#define VL_BYTES_I(nbits) (((nbits) + (VL_BYTESIZE - 1)) >> VL_BYTESIZE_LOG2)
/// Return Words/EDatas in argument-number of bits needs (1 bit=1 word)
#define VL_WORDS_I(nbits) (((nbits) + (VL_EDATASIZE - 1)) / VL_EDATASIZE)
#define VL_WORDS_I(nbits) (((nbits) + (VL_EDATASIZE - 1)) >> VL_EDATASIZE_LOG2)
// Number of Words/EDatas a quad requires
#define VL_WQ_WORDS_E VL_WORDS_I(VL_QUADSIZE)
@ -536,10 +547,10 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read()
// #defines, to avoid requiring math.h on all compile runs
#ifdef _MSC_VER
static inline double VL_TRUNC(double n) {
inline double VL_TRUNC(double n) {
return (n < 0) ? std::ceil(n) : std::floor(n);
}
static inline double VL_ROUND(double n) {
inline double VL_ROUND(double n) {
return (n < 0) ? std::ceil(n-0.5) : std::floor(n + 0.5);
}
#else

View File

@ -169,17 +169,19 @@ void memUsageBytes(uint64_t& peakr, uint64_t& currentr) VL_MT_SAFE {
}
#else
// Highly unportable. Sorry
// Use VmHWM (peak resident), matching Windows PeakWorkingSetSize and macOS resident_size_max.
// VmHWM excludes pages swapped out before the peak; /proc has no peak-(RSS+Swap) counter.
std::ifstream is{"/proc/self/status"};
if (!is) return;
std::string line;
uint64_t vmPeak = 0;
uint64_t vmHwm = 0;
uint64_t vmRss = 0;
uint64_t vmSwap = 0;
std::string field;
while (std::getline(is, line)) {
if (line.rfind("VmPeak:", 0) == 0) {
if (line.rfind("VmHWM:", 0) == 0) {
std::stringstream ss{line};
ss >> field >> vmPeak;
ss >> field >> vmHwm;
} else if (line.rfind("VmRSS:", 0) == 0) {
std::stringstream ss{line};
ss >> field >> vmRss;
@ -188,7 +190,7 @@ void memUsageBytes(uint64_t& peakr, uint64_t& currentr) VL_MT_SAFE {
ss >> field >> vmSwap;
}
}
peakr = vmPeak * 1024;
peakr = vmHwm * 1024;
currentr = (vmRss + vmSwap) * 1024;
#endif
}

View File

@ -12,6 +12,8 @@
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: CC0-1.0
# These flags tested against verible-v0.0-4080-ga0a8d8eb
verible-verilog-format \
--inplace \
--wrap_end_else_clauses \

View File

@ -33,4 +33,4 @@ sphinxcontrib-spelling==8.0.2
yamlfix==1.19.1
yapf==0.43.0
git+https://github.com/antmicro/astsee.git
git+https://github.com/antmicro/astsee.git@222480a8ec13b312809ea4acc08c81b4c0f4da2f

View File

@ -149,6 +149,7 @@ set(HEADERS
V3OrderGraph.h
V3OrderInternal.h
V3OrderMoveGraph.h
V3OrderMTaskGraph.h
V3Os.h
V3PairingHeap.h
V3Param.h
@ -156,6 +157,7 @@ set(HEADERS
V3ParseImp.h
V3PchAstMT.h
V3PchAstNoMT.h
V3PoolAllocator.h
V3PreExpr.h
V3PreLex.h
V3PreProc.h
@ -170,7 +172,6 @@ set(HEADERS
V3Sampled.h
V3Sched.h
V3Scope.h
V3Scoreboard.h
V3SenExprBuilder.h
V3SenTree.h
V3Simulate.h
@ -319,6 +320,9 @@ set(COMMON_SOURCES
V3Order.cpp
V3OrderGraphBuilder.cpp
V3OrderMoveGraph.cpp
V3OrderMTaskContraction.cpp
V3OrderMTaskFixHazards.cpp
V3OrderMTaskGraph.cpp
V3OrderParallel.cpp
V3OrderProcessDomains.cpp
V3OrderSerial.cpp
@ -346,7 +350,6 @@ set(COMMON_SOURCES
V3SchedUtil.cpp
V3SchedVirtIface.cpp
V3Scope.cpp
V3Scoreboard.cpp
V3Slice.cpp
V3Split.cpp
V3SplitVar.cpp

View File

@ -309,6 +309,9 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Order.o \
V3OrderGraphBuilder.o \
V3OrderMoveGraph.o \
V3OrderMTaskContraction.o \
V3OrderMTaskFixHazards.o \
V3OrderMTaskGraph.o \
V3OrderParallel.o \
V3OrderProcessDomains.o \
V3OrderSerial.o \
@ -330,7 +333,6 @@ RAW_OBJS_PCH_ASTNOMT = \
V3SchedUtil.o \
V3SchedVirtIface.o \
V3Scope.o \
V3Scoreboard.o \
V3Slice.o \
V3Split.o \
V3SplitVar.o \

View File

@ -36,11 +36,13 @@ VL_DEFINE_DEBUG_FUNCTIONS;
// Active class functions
class ActiveTopVisitor final : public VNVisitor {
// NODE STATE
// AstVarScope::user1() // bool. Processed
// STATE
SenTreeFinder m_finder; // Find global sentree's / add them under the AstTopScope
// METHODS
static bool isInitial(AstNode* nodep) {
const VNUser1InUse user1InUse;
// Return true if no variables that read.

View File

@ -20,6 +20,7 @@
#include "V3AstUserAllocator.h"
#include "V3Stats.h"
#include "V3UniqueNames.h"
VL_DEFINE_DEBUG_FUNCTIONS;
@ -89,6 +90,58 @@ public:
explicit DefaultDisablePropagateVisitor(AstNetlist* nodep) { iterate(nodep); }
};
// Lower a sequence used as an event control ('@seq', IEEE 1800-2023 9.4.2.4) into a
// synthesized event fired by an internal 'cover sequence' on each end-of-match
class SeqEventLowerVisitor final : public VNVisitor {
// STATE
AstNodeModule* m_modp = nullptr; // Current module
V3UniqueNames m_names{"__VseqEvent"}; // Synthesized event names
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
m_modp = nodep;
iterateChildren(nodep);
}
void visit(AstSenItem* nodep) override {
AstFuncRef* const funcrefp = VN_CAST(nodep->sensp(), FuncRef);
if (funcrefp && VN_IS(funcrefp->taskp(), Sequence)) {
FileLine* const flp = nodep->fileline();
AstVar* const eventp = new AstVar{flp, VVarType::MODULETEMP, m_names.get(nodep),
m_modp->findBasicDType(VBasicDTypeKwd::EVENT)};
eventp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(eventp);
v3Global.setHasEvents();
funcrefp->unlinkFrBack();
nodep->sensp(new AstVarRef{flp, eventp, VAccess::READ});
const bool automaticActual = funcrefp->exists([](const AstNodeVarRef* refp) {
return refp->varp() && refp->varp()->lifetime().isAutomatic();
});
if (automaticActual) {
nodep->v3error("Arguments to a sequence used as an event control must be"
" static (IEEE 1800-2023 9.4.2.4)");
VN_AS(funcrefp->taskp(), Sequence)->isReferenced(false);
VL_DO_DANGLING(pushDeletep(funcrefp), funcrefp);
return;
}
AstFireEvent* const firep
= new AstFireEvent{flp, new AstVarRef{flp, eventp, VAccess::WRITE}, false};
AstCover* const coverp
= new AstCover{flp, new AstPropSpec{flp, nullptr, nullptr, funcrefp}, firep,
VAssertType::CONCURRENT};
coverp->isCoverSeq(true);
coverp->isSeqEvent(true);
m_modp->addStmtsp(coverp);
return;
}
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
explicit SeqEventLowerVisitor(AstNetlist* nodep) { iterate(nodep); }
};
} // namespace
void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) {
@ -96,6 +149,11 @@ void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) {
{ DefaultDisablePropagateVisitor{nodep}; }
}
void V3AssertCommon::lowerSequenceEvents(AstNetlist* nodep) {
{ SeqEventLowerVisitor{nodep}; }
V3Global::dumpCheckGlobalTree("assertseqevent", 0, dumpTreeEitherLevel() >= 3);
}
//######################################################################
// AssertDeFutureVisitor
// If any AstFuture, then move all non-future varrefs to be one cycle behind,
@ -228,6 +286,9 @@ class AssertVisitor final : public VNVisitor {
AstNode* m_failsp = nullptr; // Current fail statement
AstNodeCoverOrAssert* m_assertp = nullptr; // Current assertion
AstFinal* m_finalp = nullptr; // Current final block
VDouble0 m_statLiftedCaseExprs; // Count of purified case expressions
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
V3UniqueNames m_caseTempNames{"__VCase"};
// Map from (expression, senTree) to AstAlways that computes delayed values of the expression
std::unordered_map<VNRef<AstNodeExpr>, std::unordered_map<VNRef<AstSenTree>, AstAlways*>>
m_modExpr2Sen2DelayedAlwaysp;
@ -326,8 +387,7 @@ class AssertVisitor final : public VNVisitor {
}
}
AstSampled* newSampledExpr(AstNodeExpr* nodep) {
AstSampled* const sampledp = new AstSampled{nodep->fileline(), nodep, nodep->dtypep()};
return sampledp;
return new AstSampled{nodep->fileline(), nodep, nodep->dtypep(), true};
}
AstVarRef* newMonitorNumVarRefp(const AstNode* nodep, VAccess access) {
if (!m_monitorNumVarp) {
@ -542,14 +602,19 @@ class AssertVisitor final : public VNVisitor {
bool selfDestruct = false;
bool passspGated = false;
if (const AstCover* const snodep = VN_CAST(nodep, Cover)) {
const AstCover* const coverp = VN_CAST(nodep, Cover);
// A sequence event control is not an assertion directive; no assertion control
const bool seqEvent = coverp && coverp->isSeqEvent();
if (coverp) {
++m_statCover;
if (!v3Global.opt.coverageUser()) {
if (seqEvent) {
// Keep the event-fire action, with no coverage bucket
} else if (!v3Global.opt.coverageUser()) {
selfDestruct = true;
} else {
// V3Coverage assigned us a bucket to increment.
AstCoverInc* const covincp = VN_AS(snodep->coverincsp(), CoverInc);
UASSERT_OBJ(covincp, snodep, "Missing AstCoverInc under assertion");
AstCoverInc* const covincp = VN_AS(coverp->coverincsp(), CoverInc);
UASSERT_OBJ(covincp, coverp, "Missing AstCoverInc under assertion");
covincp->unlinkFrBackWithNext(); // next() might have AstAssign for trace
if (message != "") covincp->declp()->comment(message);
if (passsp) {
@ -593,7 +658,8 @@ class AssertVisitor final : public VNVisitor {
FileLine* const flp = nodep->fileline();
bool passspAlreadyGated = false;
if (passsp && VN_IS(passsp, If)) passspAlreadyGated = VN_AS(passsp, If)->user1();
if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr)) {
if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr)
&& !seqEvent) {
passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(),
/*vacuous=*/false);
}
@ -603,7 +669,7 @@ class AssertVisitor final : public VNVisitor {
AstNode* bodysp = assertBody(nodep, propExprp, passsp, failsp);
if (disablep) bodysp = new AstIf{flp, new AstLogNot{flp, disablep}, bodysp};
// Add assertOn check last, for better combining
bodysp = newIfAssertOn(bodysp, nodep->directive(), nodep->userType());
if (!seqEvent) bodysp = newIfAssertOn(bodysp, nodep->directive(), nodep->userType());
if (sentreep) bodysp = new AstAlways{flp, VAlwaysKwd::ALWAYS, sentreep, bodysp};
if (passsp && !passsp->backp()) VL_DO_DANGLING(pushDeletep(passsp), passsp);
@ -755,6 +821,28 @@ class AssertVisitor final : public VNVisitor {
//========== Case assertions
void visit(AstCase* nodep) override {
// Introduce temporary variable for AstCase if needed - it is done here and not in V3Case
// because this phase is before V3Scope and V3Case is not. Doing it before V3Scope ensures
// that V3Scope will take care of a scope creation
// We also need to do it before V3Begin, co that pragmas like `unique0` also work correctly
if (!nodep->exprp()->isPure()) {
++m_statLiftedCaseExprs;
FileLine* const fl = nodep->exprp()->fileline();
AstVar* const varp = new AstVar{fl, VVarType::BLOCKTEMP, m_caseTempNames.get(nodep),
nodep->exprp()->dtypep()};
AstNodeExpr* const origp = nodep->exprp()->unlinkFrBack();
nodep->addHereThisAsNext(
new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, origp});
nodep->exprp(new AstVarRef{fl, varp, VAccess::READ});
if (m_ftaskp) {
varp->funcLocal(true);
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
m_ftaskp->stmtsp()->addHereThisAsNext(varp);
} else {
m_modp->stmtsp()->addHereThisAsNext(varp);
}
VIsCached::clearCacheTree();
}
iterateChildren(nodep);
if (!nodep->user1SetOnce()) {
bool has_default = false;
@ -983,11 +1071,7 @@ class AssertVisitor final : public VNVisitor {
= new AstSenItem{fl, VEdgeType::ET_CHANGED,
// Clone so get VarRef or VarXRef as needed
varrefp->cloneTree(false)};
if (!monSenItemsp) {
monSenItemsp = senItemp;
} else {
monSenItemsp->addNext(senItemp);
}
monSenItemsp = AstNode::addNextNull(monSenItemsp, senItemp);
});
}
@ -1127,12 +1211,11 @@ class AssertVisitor final : public VNVisitor {
VL_RESTORER(m_modp);
VL_RESTORER(m_modPastNum);
VL_RESTORER(m_modStrobeNum);
VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp);
VL_RESTORER(m_finalp);
VL_RESTORER_CLEAR(m_modExpr2Sen2DelayedAlwaysp);
m_modp = nodep;
m_modPastNum = 0;
m_modStrobeNum = 0;
m_modExpr2Sen2DelayedAlwaysp.clear();
m_finalp = nullptr;
iterateChildren(nodep);
}
@ -1180,6 +1263,9 @@ public:
V3Stats::addStat("Assertions, $past variables", m_statPastVars);
V3Stats::addStat("Assertions, assertOn checks combined", m_statAssertOnCombined);
V3Stats::addStat("Assertions, assertOn checks hoisted", m_statAssertOnHoisted);
V3Stats::addStat("Assertions, lifted impure case expressions", m_statLiftedCaseExprs);
// Rewrites can change purity, e.g. by compiling out assertion statements with --no-assert
VIsCached::clearCacheTree();
}
};

View File

@ -27,6 +27,7 @@
class V3AssertCommon final {
public:
static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED;
static void lowerSequenceEvents(AstNetlist* nodep) VL_MT_DISABLED;
};
class V3Assert final {

File diff suppressed because it is too large Load Diff

View File

@ -79,6 +79,14 @@ private:
// METHODS
static void checkSamplingFuncDType(AstNodeExpr* nodep, const AstNode* exprp) {
const AstNodeDType* const dtypep = exprp->dtypep()->skipRefp();
if (!dtypep->isIntegralOrPacked()) {
nodep->v3error("Expected numeric type, but got a " << dtypep->prettyDTypeNameQ()
<< " data type");
}
}
AstSenTree* newSenTree(AstNode* nodep, AstSenTree* useTreep = nullptr,
AstNodeCoverOrAssert* cassertp = nullptr) {
// Create sentree based on clocked or default clock
@ -372,7 +380,7 @@ private:
// #1step means the value that is sampled is always the signal's last value
// before the clock edge (IEEE 1800-2023 14.4)
AstSampled* const sampledp
= new AstSampled{flp, exprp->cloneTreePure(false), exprp->dtypep()};
= new AstSampled{flp, exprp->cloneTreePure(false), exprp->dtypep(), true};
AstAssign* const assignp = new AstAssign{flp, refp, sampledp};
m_clockingp->addNextHere(new AstAlways{
flp, VAlwaysKwd::ALWAYS,
@ -683,6 +691,7 @@ private:
void visit(AstFalling* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
checkSamplingFuncDType(nodep, nodep->exprp());
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
@ -696,6 +705,7 @@ private:
void visit(AstFell* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
checkSamplingFuncDType(nodep, nodep->exprp());
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
@ -712,11 +722,13 @@ private:
void visit(AstFuture* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
checkSamplingFuncDType(nodep, nodep->exprp());
AstSenTree* const sentreep = nodep->sentreep();
if (sentreep) VL_DO_DANGLING(pushDeletep(sentreep->unlinkFrBack()), sentreep);
nodep->sentreep(newSenTree(nodep));
}
void visit(AstPast* nodep) override {
checkSamplingFuncDType(nodep, nodep->exprp());
if (nodep->sentreep()) return; // Already processed
iterateChildren(nodep);
nodep->sentreep(newSenTree(nodep));
@ -774,6 +786,7 @@ private:
void visit(AstRising* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
checkSamplingFuncDType(nodep, nodep->exprp());
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
@ -787,6 +800,7 @@ private:
void visit(AstRose* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
checkSamplingFuncDType(nodep, nodep->exprp());
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
@ -849,7 +863,8 @@ private:
// Assertion condition check
AstLoop* const loopp = new AstLoop{flp};
AstNodeExpr* const condp = new AstSampled{flp, nodep->exprp()->unlinkFrBack(), nullptr};
AstSampled* const condp
= new AstSampled{flp, nodep->exprp()->unlinkFrBack(), nullptr, true};
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLogNot{flp, condp}});
loopp->addStmtsp(new AstEventControl{flp, sentreep, nullptr});
@ -916,6 +931,7 @@ private:
void visit(AstStable* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
checkSamplingFuncDType(nodep, nodep->exprp());
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
AstSenTree* sentreep = nodep->sentreep();
@ -931,6 +947,7 @@ private:
void visit(AstSteady* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
checkSamplingFuncDType(nodep, nodep->exprp());
FileLine* const fl = nodep->fileline();
AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
@ -941,6 +958,10 @@ private:
nodep->replaceWith(exprp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstSampled* nodep) override {
iterateChildren(nodep);
if (!nodep->internal()) checkSamplingFuncDType(nodep, nodep->exprp());
}
// Validate repetition count: must be a non-negative elaboration-time constant.
// Shared by goto [->N] and nonconsecutive [=N] repetition.
@ -1203,11 +1224,7 @@ private:
AstNode* const nextp = stmtp->nextp();
if (AstAssign* const assignp = VN_CAST(stmtp, Assign)) {
assignp->unlinkFrBack();
if (!matchAssignsp) {
matchAssignsp = assignp;
} else {
matchAssignsp->addNext(assignp);
}
matchAssignsp = AstNode::addNextNull(matchAssignsp, assignp);
}
stmtp = nextp;
}
@ -1233,11 +1250,7 @@ private:
AstNodeExpr* const assignRhsp = assignp->rhsp()->unlinkFrBack();
AstAssignDly* const dlyp = new AstAssignDly{flp, assignLhsp, assignRhsp};
VL_DO_DANGLING(pushDeletep(assignp), assignp);
if (!matchAssignsp) {
matchAssignsp = dlyp;
} else {
matchAssignsp->addNext(dlyp);
}
matchAssignsp = AstNode::addNextNull(matchAssignsp, dlyp);
}
stmtp = nextp;
}
@ -1318,8 +1331,8 @@ private:
// this tick, p is not required. For s_until_with, p must be true on the q tick too.
AstNodeExpr* const rawLhsp = nodep->lhsp()->unlinkFrBack();
AstNodeExpr* const rawRhsp = nodep->rhsp()->unlinkFrBack();
AstSampled* const lhsp = new AstSampled{flp, rawLhsp, rawLhsp->dtypep()};
AstSampled* const rhsp = new AstSampled{flp, rawRhsp, rawRhsp->dtypep()};
AstSampled* const lhsp = new AstSampled{flp, rawLhsp, rawLhsp->dtypep(), true};
AstSampled* const rhsp = new AstSampled{flp, rawRhsp, rawRhsp->dtypep(), true};
AstNodeExpr* finalCondp = rhsp->cloneTreePure(false);
if (nodep->isOverlapping()) {
finalCondp = new AstLogAnd{flp, lhsp->cloneTreePure(false), finalCondp};
@ -1466,7 +1479,9 @@ private:
iterateAndNextNull(nodep->sensesp());
if (m_senip && m_senip != nodep->sensesp())
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Only one PSL clock allowed per assertion");
if (!nodep->disablep() && m_defaultDisablep) {
const AstCover* const coverp = VN_CAST(nodep->backp(), Cover);
const bool seqEvent = coverp && coverp->isSeqEvent();
if (!nodep->disablep() && m_defaultDisablep && !seqEvent) {
nodep->disablep(m_defaultDisablep->condp()->cloneTreePure(true));
}
m_disablep = nodep->disablep();
@ -1476,7 +1491,7 @@ private:
if (!VN_AS(nodep->backp(), NodeCoverOrAssert)->immediate()) {
const AstNodeDType* const propDtp = nodep->propp()->dtypep();
nodep->propp(new AstSampled{nodep->fileline(), nodep->propp()->unlinkFrBack(),
propDtp->dtypep()});
propDtp->dtypep(), true});
}
// cover counts non-vacuous matches only (IEEE 1800-2023 16.15.2), so an
// implication antecedent must hold; assert passes vacuously instead.

View File

@ -1542,14 +1542,14 @@ string AstNode::instanceStr() const {
return "";
}
void AstNode::v3errorEnd(const std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex) {
// Don't look for instance name when warning is disabled.
// In case of large number of warnings, this can
// take significant amount of time
const string instanceStrExtra
= m_fileline->warnIsOff(V3Error::s().errorCode()) ? "" : instanceStr();
if (!m_fileline) {
V3Error::v3errorEnd(str, instanceStrExtra, nullptr);
V3Error::v3errorEnd(str, "", nullptr);
} else {
// Don't look for instance name when warning is disabled.
// In case of large number of warnings, this can
// take significant amount of time
const string instanceStrExtra
= m_fileline->warnIsOff(V3Error::s().errorCode()) ? "" : instanceStr();
std::ostringstream nsstr;
nsstr << str.str();
if (debug()) {
@ -1735,12 +1735,15 @@ AstNodeDType* AstNode::getCommonClassTypep(AstNode* node1p, AstNode* node2p) {
if (castable == VCastable::DYNAMIC_CLASS) return node2p->dtypep();
}
AstClassRefDType* classDtypep1 = VN_CAST(node1p->dtypep(), ClassRefDType);
AstClassRefDType* classDtypep1 = VN_CAST(node1p->dtypep()->skipRefp(), ClassRefDType);
while (classDtypep1) {
const VCastable castable = computeCastable(classDtypep1, node2p->dtypep(), node2p);
if (castable == VCastable::COMPATIBLE) return classDtypep1;
const AstClassExtends* const extendsp = classDtypep1->classp()->extendsp();
classDtypep1 = extendsp ? VN_AS(extendsp->dtypep(), ClassRefDType) : nullptr;
AstClassExtends* const extendsp = classDtypep1->classp()->extendsp();
if (!extendsp) break;
AstNodeDType* const edtp
= extendsp->dtypep() ? extendsp->dtypep() : extendsp->childDTypep();
classDtypep1 = VN_AS(edtp->skipRefp(), ClassRefDType);
}
return nullptr;
}

View File

@ -730,6 +730,7 @@ public:
// ACCESSORS for specific types
// Alas these can't be virtual or they break when passed a nullptr
bool isDisableQueuePushSelfStmt();
inline bool isClassHandleValue() const;
inline bool isNull() const;
inline bool isZero() const;

View File

@ -299,6 +299,34 @@ constexpr VAssertType::en operator|(VAssertType::en lhs, VAssertType::en rhs) {
// ######################################################################
class VPropStrength final {
public:
enum en : uint8_t {
DEFAULT = 0, // Resolve from assertion/coverage context
WEAK,
STRONG,
};
enum en m_e;
// cppcheck-suppress noExplicitConstructor
constexpr VPropStrength(en _e)
: m_e{_e} {}
const char* ascii() const {
static const char* const names[] = {"default", "weak", "strong"};
return names[m_e];
}
};
constexpr bool operator==(const VPropStrength& lhs, const VPropStrength& rhs) {
return lhs.m_e == rhs.m_e;
}
constexpr bool operator==(const VPropStrength& lhs, VPropStrength::en rhs) {
return lhs.m_e == rhs;
}
constexpr bool operator!=(const VPropStrength& lhs, VPropStrength::en rhs) {
return lhs.m_e != rhs;
}
// ######################################################################
class VAttrType final {
public:
// clang-format off

View File

@ -584,6 +584,7 @@ class AstClassRefDType final : public AstNodeDType {
//
// @astgen ptr := m_classp : Optional[AstClass] // data type pointed to, BELOW the AstTypedef
// @astgen ptr := m_classOrPackagep : Optional[AstNodeModule] // Package hierarchy
bool m_rawPointer = false; // Emit as a non-owning C++ pointer rather than VlClassRef
public:
AstClassRefDType(FileLine* fl, AstClass* classp, AstPin* paramsp)
: ASTGEN_SUPER_ClassRefDType(fl)
@ -595,7 +596,8 @@ public:
// METHODS
bool sameNode(const AstNode* samep) const override {
const AstClassRefDType* const asamep = VN_DBG_AS(samep, ClassRefDType);
return (m_classp == asamep->m_classp && m_classOrPackagep == asamep->m_classOrPackagep);
return (m_classp == asamep->m_classp && m_classOrPackagep == asamep->m_classOrPackagep
&& m_rawPointer == asamep->m_rawPointer);
}
bool similarDTypeNode(const AstNodeDType* samep) const override;
void dump(std::ostream& str = std::cout) const override;
@ -613,6 +615,9 @@ public:
void classOrPackagep(AstNodeModule* nodep) { m_classOrPackagep = nodep; }
AstClass* classp() const VL_MT_STABLE { return m_classp; }
void classp(AstClass* nodep) { m_classp = nodep; }
bool rawPointer() const { return m_rawPointer; }
void rawPointer(bool flag) { m_rawPointer = flag; }
static void selfTest();
bool isCompound() const override { return true; }
};
class AstConstDType final : public AstNodeDType {
@ -968,7 +973,7 @@ class AstMemberDType final : public AstNodeDType {
string m_tag; // Holds the string of the verilator tag -- used in JSON output.
int m_lsb = -1; // Within this level's packed struct, the LSB of the first bit of the member
bool m_constrainedRand = false;
// UNSUP: int m_randType; // Randomization type (IEEE)
VRandAttr m_rand; // Randomizability of this member (rand, randc, etc)
public:
AstMemberDType(FileLine* fl, const string& name, VFlagChildDType, AstNodeDType* dtp,
AstNode* valuep)
@ -1025,6 +1030,8 @@ public:
}
bool isConstrainedRand() const { return m_constrainedRand; }
void markConstrainedRand(bool flag) { m_constrainedRand = flag; }
VRandAttr rand() const { return m_rand; }
void rand(const VRandAttr flag) { m_rand = flag; }
};
class AstNBACommitQueueDType final : public AstNodeDType {
// @astgen ptr := m_subDTypep : AstNodeDType // Type of the corresponding variable

View File

@ -905,7 +905,7 @@ public:
string name() const override VL_MT_STABLE { return m_name; } // * = Var name
// There's no classOrPackagep(); use classOrPackageNodep() to get Node,
// or iterating to package with classOrPackageSkipp()
AstNodeModule* classOrPackageSkipp() const;
AstNodeModule* classOrPackageSkipp(const bool doRefs = true) const;
AstNode* classOrPackageNodep() const { return m_classOrPackageNodep; }
void classOrPackageNodep(AstNode* nodep) { m_classOrPackageNodep = nodep; }
void classOrPackagep(AstNodeModule* nodep) {
@ -2262,24 +2262,25 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(false); }
};
class AstSConsRep final : public AstNodeExpr {
// Consecutive repetition [*N], [*N:M], [+], [*] (IEEE 1800-2023 16.9.2)
// Consecutive repetition [*N], [*N:M], [*N:$], [+], [*] (IEEE 1800-2023 16.9.2)
// op1 := exprp -- the repeated expression
// op2 := countp -- min repetition count (N); always a positive constant after V3Width
// op2 := countp -- min repetition count (N); always a non-negative constant after V3Width
// op3 := maxCountp -- max repetition count (M); nullptr when exact or unbounded
//
// Encoding:
// [*N]: countp=N, maxCountp=nullptr, unbounded=false
// [*N:M]: countp=N, maxCountp=M, unbounded=false
// [*N:$]: countp=N, maxCountp=nullptr, unbounded=true
// [+]: countp=1, maxCountp=nullptr, unbounded=true (= [*1:$])
// [*]: countp=0, maxCountp=nullptr, unbounded=true (= [*0:$])
//
// Lowering:
// Exact [*N] standalone: V3AssertPre saturating counter
// All other forms and all SExpr-contained forms: V3AssertProp PExpr loop
// All other forms and all SExpr-contained forms: V3AssertNfa
// @astgen op1 := exprp : AstNodeExpr
// @astgen op2 := countp : AstNodeExpr
// @astgen op3 := maxCountp : Optional[AstNodeExpr]
const bool m_unbounded = false; // True for [+] and [*] (upper bound is $)
const bool m_unbounded = false; // True when the upper bound is $
public:
// Exact [*N]
AstSConsRep(FileLine* fl, AstNodeExpr* exprp, AstNodeExpr* countp)
@ -2287,7 +2288,7 @@ public:
this->exprp(exprp);
this->countp(countp);
}
// Range [*N:M] or unbounded [+]/[*]
// Range [*N:M] or unbounded [*N:$]/[+]/[*]
AstSConsRep(FileLine* fl, AstNodeExpr* exprp, AstNodeExpr* countp, AstNodeExpr* maxCountp,
bool unbounded)
: ASTGEN_SUPER_SConsRep(fl)
@ -2554,19 +2555,26 @@ public:
class AstSampled final : public AstNodeExpr {
// Verilog $sampled
// @astgen op1 := exprp : AstNode<AstNodeExpr|AstPropSpec>
bool m_internal : 1; // Internally created, not from a source $sampled
public:
AstSampled(FileLine* fl, AstNode* exprp, AstNodeDType* dtypep)
: ASTGEN_SUPER_Sampled(fl) {
AstSampled(FileLine* fl, AstNode* exprp, AstNodeDType* dtypep, bool internal = false)
: ASTGEN_SUPER_Sampled(fl)
, m_internal{internal} {
this->exprp(exprp);
this->dtypep(dtypep);
}
ASTGEN_MEMBERS_AstSampled;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string emitVerilog() override { return "$sampled(%l)"; }
string emitC() override { V3ERROR_NA_RETURN(""); }
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return 0; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool sameNode(const AstNode* samep) const override {
return m_internal == VN_DBG_AS(samep, Sampled)->m_internal;
}
bool internal() const { return m_internal; }
bool isSystemFunc() const override { return true; }
};
class AstScopeName final : public AstNodeExpr {
@ -3258,6 +3266,7 @@ public:
bool sizeMattersRhs() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return widthInstrs() * 64; }
bool isPure() override { return false; } // SPECIAL: $display has 'visual' ordering
};
class AstFUngetC final : public AstNodeBiop {
public:
@ -4886,9 +4895,11 @@ public:
bool cleanRhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
bool isGateOptimizable() const override { return false; } // AssocSel creates on miss
bool isGateOptimizable() const override {
return !isLValue(); // AssocSel creates on miss
}
bool isPredictOptimizable() const override { return false; }
bool isPure() override { return false; } // AssocSel creates on miss
bool isPure() override { return !isLValue(); } // AssocSel creates on miss
bool sameNode(const AstNode* /*samep*/) const override { return true; }
int instrCount() const override { return widthInstrs(); }
};

View File

@ -94,6 +94,7 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode {
// @astgen op4 := scopeNamep : Optional[AstScopeName]
string m_name; // Name of task
string m_cname; // Name of task if DPI import
string m_dpiCDecl; // Custom DPI-C function declaration
string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8)
uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees
bool m_taskPublic : 1; // Public task
@ -199,6 +200,9 @@ public:
void dpiOpenChild(bool flag) { m_dpiOpenChild = flag; }
bool dpiTask() const { return m_dpiTask; }
void dpiTask(bool flag) { m_dpiTask = flag; }
bool dpiCDeclOverride() const { return !m_dpiCDecl.empty(); }
const string& dpiCDecl() const { return m_dpiCDecl; }
void dpiCDecl(const string& cDecl) { m_dpiCDecl = cDecl; }
bool isConstructor() const { return m_isConstructor; }
void isConstructor(bool flag) { m_isConstructor = flag; }
bool isHideLocal() const { return m_isHideLocal; }
@ -504,6 +508,7 @@ class AstCFunc final : public AstNode {
// @astgen op1 := argsp : List[AstVar] // Argument (and return value) variables
// @astgen op2 := varsp : List[AstVar] // Local variables
// @astgen op3 := stmtsp : List[AstNode]
// @astgen op4 := scopeNamep : Optional[AstScopeName] // Scoping context for DPI export
//
// @astgen ptr := m_scopep : Optional[AstScope] // Scope that function is under
string m_name;
@ -511,6 +516,7 @@ class AstCFunc final : public AstNode {
string m_rtnType; // void, bool, or other return type
string m_argTypes; // Argument types
string m_ifdef; // #ifdef symbol around this function
string m_cDecl; // Custom DPI-C function declaration
VBoolOrUnknown m_isConst; // Function is declared const (*this not changed)
bool m_isStatic : 1; // Function is static (no need for a 'this' pointer)
bool m_isTrace : 1; // Function is related to tracing
@ -640,6 +646,9 @@ public:
void dpiImportPrototype(bool flag) { m_dpiImportPrototype = flag; }
bool dpiImportWrapper() const { return m_dpiImportWrapper; }
void dpiImportWrapper(bool flag) { m_dpiImportWrapper = flag; }
bool dpiCDeclOverride() const { return !m_cDecl.empty(); }
const string& dpiCDecl() const { return m_cDecl; }
void dpiCDecl(const string& cDecl) { m_cDecl = cDecl; }
bool isCoroutine() const { return m_rtnType == "VlCoroutine"; }
void recursive(bool flag) { m_recursive = flag; }
bool recursive() const { return m_recursive; }
@ -650,20 +659,10 @@ public:
void cost(int cost) { m_cost = cost; }
// Special methods
bool emptyBody() const {
return !keepIfEmpty() && !argsp() && !varsp() && !stmtsp() && !isVirtual()
return !keepIfEmpty() && !argsp() && !varsp() && !stmtsp() && !scopeNamep() && !isVirtual()
&& !dpiImportPrototype();
}
};
class AstCLocalScope final : public AstNode {
// Pack statements into an unnamed scope when generating C++
// @astgen op1 := stmtsp : List[AstNode]
public:
AstCLocalScope(FileLine* fl, AstNode* stmtsp)
: ASTGEN_SUPER_CLocalScope(fl) {
addStmtsp(stmtsp);
}
ASTGEN_MEMBERS_AstCLocalScope;
};
class AstCUse final : public AstNode {
// C++ use of a class or #include; indicates need of forward declaration
// Parents: NODEMODULE
@ -844,7 +843,7 @@ class AstClocking final : public AstNode {
// @astgen op2 := itemsp : List[AstNode]
// @astgen op3 := eventp : Optional[AstVar]
std::string m_name; // Clocking block name
const bool m_isDefault; // True if default clocking
bool m_isDefault; // True if default clocking
const bool m_isGlobal; // True if global clocking
public:
@ -865,6 +864,7 @@ public:
bool isDefault() const { return m_isDefault; }
bool isGlobal() const { return m_isGlobal; }
AstVar* ensureEventp(bool childDType = false);
void makeDefault() { m_isDefault = true; }
};
class AstClockingItem final : public AstNode {
// Parents: CLOCKING
@ -1042,22 +1042,6 @@ public:
void isStatic(bool flag) { m_isStatic = flag; }
bool isStatic() const { return m_isStatic; }
};
class AstConstraintBefore final : public AstNode {
// Constraint solve before item
// @astgen op1 := lhssp : List[AstNodeExpr]
// @astgen op2 := rhssp : List[AstNodeExpr]
public:
AstConstraintBefore(FileLine* fl, AstNodeExpr* lhssp, AstNodeExpr* rhssp)
: ASTGEN_SUPER_ConstraintBefore(fl) {
addLhssp(lhssp);
addRhssp(rhssp);
}
ASTGEN_MEMBERS_AstConstraintBefore;
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstCoverBin final : public AstNode {
// Captures data for a coverpoint 'bins' declaration
// @astgen op1 := rangesp : List[AstNode]
@ -1222,6 +1206,16 @@ public:
ASTGEN_MEMBERS_AstDefParam;
bool sameNode(const AstNode*) const override { return true; }
};
class AstDefaultClocking final : public AstNode {
std::string m_name; // Clocking block name
public:
AstDefaultClocking(FileLine* fl, const std::string& name)
: ASTGEN_SUPER_DefaultClocking(fl)
, m_name{name} {}
ASTGEN_MEMBERS_AstDefaultClocking;
std::string name() const override VL_MT_STABLE { return m_name; }
};
class AstDefaultDisable final : public AstNode {
// @astgen op1 := condp : AstNodeExpr
@ -1679,17 +1673,24 @@ class AstPropSpec final : public AstNode {
// @astgen op1 := sensesp : Optional[AstSenItem]
// @astgen op2 := disablep : Optional[AstNodeExpr]
// @astgen op3 := propp : AstNode
VPropStrength m_propStrength = VPropStrength::DEFAULT;
public:
AstPropSpec(FileLine* fl, AstSenItem* sensesp, AstNodeExpr* disablep, AstNode* propp)
: ASTGEN_SUPER_PropSpec(fl) {
AstPropSpec(FileLine* fl, AstSenItem* sensesp, AstNodeExpr* disablep, AstNode* propp,
VPropStrength propStrength = VPropStrength::DEFAULT)
: ASTGEN_SUPER_PropSpec(fl)
, m_propStrength{propStrength} {
this->sensesp(sensesp);
this->disablep(disablep);
this->propp(propp);
}
ASTGEN_MEMBERS_AstPropSpec;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool hasDType() const override VL_MT_SAFE {
return true;
} // Used under Cover, which expects a bool child
VPropStrength propStrength() const { return m_propStrength; }
};
class AstPull final : public AstNode {
// @astgen op1 := lhsp : AstNodeExpr
@ -2889,6 +2890,7 @@ class AstClass final : public AstNodeModule {
// @astgen op4 := extendsp : List[AstClassExtends]
// MEMBERS
// @astgen ptr := m_classOrPackagep : Optional[AstClassPackage] // Package to be emitted with
// @astgen ptr := m_covergroupEnclosingClassp : Optional[AstClass] // Lexical enclosing class
uint32_t m_declTokenNum; // Declaration token number
VBaseOverride m_baseOverride; // BaseOverride (inital/final/extends)
bool m_covergroup = false; // Is covergroup (TODO perhaps make a new Ast node type for CG?)
@ -2913,6 +2915,10 @@ public:
bool timescaleMatters() const override { return false; }
AstClassPackage* classOrPackagep() const VL_MT_STABLE { return m_classOrPackagep; }
void classOrPackagep(AstClassPackage* classpackagep) { m_classOrPackagep = classpackagep; }
AstClass* covergroupEnclosingClassp() const VL_MT_STABLE {
return m_covergroupEnclosingClassp;
}
void covergroupEnclosingClassp(AstClass* classp) { m_covergroupEnclosingClassp = classp; }
AstNode* membersp() const VL_MT_STABLE { return stmtsp(); }
void addMembersp(AstNode* nodep) { addStmtsp(nodep); }
bool isCovergroup() const { return m_covergroup; }

View File

@ -297,6 +297,16 @@ public:
AstSenTree* sentreep() const { return m_sentreep; }
void clearSentreep() { m_sentreep = nullptr; }
};
class AstCLocalScope final : public AstNodeStmt {
// Pack statements into an unnamed scope when generating C++
// @astgen op1 := stmtsp : List[AstNode]
public:
AstCLocalScope(FileLine* fl, AstNode* stmtsp)
: ASTGEN_SUPER_CLocalScope(fl) {
addStmtsp(stmtsp);
}
ASTGEN_MEMBERS_AstCLocalScope;
};
class AstCReturn final : public AstNodeStmt {
// C++ return from a function
// @astgen op1 := lhsp : AstNodeExpr
@ -451,6 +461,21 @@ public:
bool sameNode(const AstNode* samep) const override { return true; } // Ignore name in comments
virtual bool showAt() const { return m_showAt; }
};
class AstConstraintBefore final : public AstNodeStmt {
// Constraint solve before item
// @astgen op1 := lhssp : List[AstNodeExpr]
// @astgen op2 := rhssp : List[AstNodeExpr]
public:
AstConstraintBefore(FileLine* fl, AstNodeExpr* lhssp, AstNodeExpr* rhssp)
: ASTGEN_SUPER_ConstraintBefore(fl) {
addLhssp(lhssp);
addRhssp(rhssp);
}
ASTGEN_MEMBERS_AstConstraintBefore;
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstConstraintExpr final : public AstNodeStmt {
// Constraint expression
// @astgen op1 := exprp : AstNodeExpr
@ -1568,15 +1593,23 @@ class AstFork final : public AstNodeBlock {
//
// @astgen op3 := forksp : List[AstBegin]
const VJoinType m_joinType; // Join keyword type
bool m_immediateStart = false; // Fork starts before its parent blocks or exits
public:
AstFork(FileLine* fl, VJoinType joinType, const string& name = "")
: ASTGEN_SUPER_Fork(fl, name)
, m_joinType{joinType} {}
ASTGEN_MEMBERS_AstFork;
bool sameNode(const AstNode* samep) const override {
const AstFork* const asamep = VN_DBG_AS(samep, Fork);
return joinType() == asamep->joinType() && immediateStart() == asamep->immediateStart();
}
bool isTimingControl() const override { return !joinType().joinNone(); }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
VJoinType joinType() const { return m_joinType; }
bool immediateStart() const { return m_immediateStart; }
void immediateStart(bool flag) { m_immediateStart = flag; }
};
// === AstNodeCoverOrAssert ===
@ -1612,6 +1645,8 @@ class AstCover final : public AstNodeCoverOrAssert {
// @astgen op3 := coverincsp: List[AstNode] // Coverage node
bool m_isCoverSeq = false; // 'cover sequence' (IEEE 1800-2023 16.14.3): fires per
// end-of-match, not per property success
bool m_isSeqEvent = false; // Synthesized for a sequence used as an event control
// (IEEE 1800-2023 9.4.2.4)
public:
ASTGEN_MEMBERS_AstCover;
AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type,
@ -1622,6 +1657,8 @@ public:
void dumpJson(std::ostream& str) const override;
bool isCoverSeq() const { return m_isCoverSeq; }
void isCoverSeq(bool flag) { m_isCoverSeq = flag; }
bool isSeqEvent() const { return m_isSeqEvent; }
void isSeqEvent(bool flag) { m_isSeqEvent = flag; }
};
class AstRestrict final : public AstNodeCoverOrAssert {
public:

View File

@ -108,6 +108,21 @@ int AstNodeSel::bitConst() const {
return (constp ? constp->toSInt() : 0);
}
bool AstNode::isDisableQueuePushSelfStmt() {
// Detect LinkJump-generated registration:
// __VprocessQueue_*.push_back(std::process::self())
AstStmtExpr* const stmtExprp = VN_CAST(this, StmtExpr);
if (!stmtExprp) return false;
AstCMethodHard* const methodp = VN_CAST(stmtExprp->exprp(), CMethodHard);
if (!methodp || methodp->name() != "push_back") return false;
AstNode* const basep = AstArraySel::baseFromp(methodp->fromp(), false);
if (AstVarRef* const refp = VN_CAST(basep, VarRef)) return refp->varp()->processQueue();
if (AstMemberSel* const selp = VN_CAST(basep, MemberSel)) {
return selp->varp() && selp->varp()->processQueue();
}
return false;
}
void AstNodeStmt::dump(std::ostream& str) const { this->AstNode::dump(str); }
void AstNodeStmt::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
@ -1179,7 +1194,8 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe
info.m_type = "VlSampleQueue<" + sub.m_type + ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, ClassRefDType)) {
UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union");
info.m_type = "VlClassRef<" + EmitCUtil::prefixNameProtect(adtypep) + ">";
const string className = EmitCUtil::prefixNameProtect(adtypep);
info.m_type = adtypep->rawPointer() ? className + "*" : "VlClassRef<" + className + ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, IfaceRefDType)) {
UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union");
info.m_type = EmitCUtil::prefixNameProtect(adtypep->ifaceViaCellp()) + "*";
@ -1401,6 +1417,9 @@ AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) {
} else if (VN_IS(nodep, WildcardSel)) {
nodep = VN_AS(nodep, WildcardSel)->fromp();
continue;
} else if (VN_IS(nodep, CMethodHard)) {
nodep = VN_AS(nodep, CMethodHard)->fromp();
continue;
} else if (overMembers && VN_IS(nodep, MemberSel)) {
nodep = VN_AS(nodep, MemberSel)->fromp();
continue;
@ -1977,6 +1996,18 @@ string AstBasicDType::prettyDTypeName(bool) const {
void AstNodeExpr::dump(std::ostream& str) const { this->AstNode::dump(str); }
void AstNodeExpr::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
void AstPropSpec::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (propStrength() != VPropStrength::DEFAULT) {
str << " [" << VString::upcase(propStrength().ascii()) << "]";
}
}
void AstPropSpec::dumpJson(std::ostream& str) const {
if (propStrength() != VPropStrength::DEFAULT)
dumpJsonStr(str, "strength", propStrength().ascii());
dumpJsonGen(str);
}
AstConst::~AstConst() {
// Only rare constants carry originating parameter-name metadata. For all other AstConst nodes,
// the V3Number bit keeps this destructor from touching AstNetlist's side table. When the bit
@ -2022,6 +2053,8 @@ bool AstNodeExpr::isLValue() const {
return varrefp->access().isWriteOrRW();
} else if (const AstMemberSel* const memberselp = VN_CAST(this, MemberSel)) {
return memberselp->access().isWriteOrRW();
} else if (const AstStructSel* const structselp = VN_CAST(this, StructSel)) {
return structselp->fromp()->isLValue();
} else if (const AstSel* const selp = VN_CAST(this, Sel)) {
return selp->fromp()->isLValue();
} else if (const AstNodeSel* const nodeSelp = VN_CAST(this, NodeSel)) {
@ -2181,8 +2214,22 @@ void AstClassRefDType::dump(std::ostream& str) const {
} else {
str << " -> UNLINKED";
}
if (rawPointer()) str << " [RAWPTR]";
}
void AstClassRefDType::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, rawPointer);
dumpJsonGen(str);
}
void AstClassRefDType::selfTest() {
FileLine* const fl = new FileLine{FileLine::commandLineFilename()};
AstClassRefDType* const owningp = new AstClassRefDType{fl, nullptr, nullptr};
AstClassRefDType* const rawp = new AstClassRefDType{fl, nullptr, nullptr};
rawp->rawPointer(true);
UASSERT_OBJ(!owningp->sameNode(rawp) && !rawp->sameNode(owningp) && rawp->sameNode(rawp), rawp,
"Raw class pointer must have distinct type identity");
VL_DO_DANGLING(owningp->deleteTree(), owningp);
VL_DO_DANGLING(rawp->deleteTree(), rawp);
}
void AstClassRefDType::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
void AstClassRefDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "class:" << name();
@ -2241,9 +2288,11 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const {
void AstCover::dump(std::ostream& str) const {
this->AstNodeCoverOrAssert::dump(str);
if (isCoverSeq()) str << " [COVERSEQ]";
if (isSeqEvent()) str << " [SEQEVENT]";
}
void AstCover::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, isCoverSeq);
dumpJsonBoolFuncIf(str, isSeqEvent);
this->AstNodeCoverOrAssert::dumpJson(str);
}
void AstClocking::dump(std::ostream& str) const {
@ -2486,12 +2535,14 @@ const char* AstLoopTest::broken() const {
void AstMemberDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
if (isConstrainedRand()) str << " [CONSTRAINEDRAND]";
if (rand().isRandomizable()) str << " [" << rand() << "]";
if (name() != "") str << " name=" << name();
if (tag() != "") str << " tag=" << tag();
}
void AstMemberDType::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, isConstrainedRand);
if (rand().isRandomizable()) dumpJsonStr(str, "rand", rand().ascii());
dumpJsonStrFunc(str, name);
dumpJsonStrFunc(str, tag);
dumpJsonGen(str);
@ -3032,6 +3083,14 @@ void AstSFormatF::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, exprFormat);
dumpJsonBoolFuncIf(str, optionalFormat);
}
void AstSampled::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
if (internal()) str << " [INTERNAL]";
}
void AstSampled::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, internal);
dumpJsonGen(str);
}
void AstSel::dump(std::ostream& str) const {
this->AstNodeBiop::dump(str);
str << " widthConst=" << this->widthConst();
@ -3401,7 +3460,7 @@ void AstClassOrPackageRef::dump(std::ostream& str) const {
}
}
void AstClassOrPackageRef::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
AstNodeModule* AstClassOrPackageRef::classOrPackageSkipp() const {
AstNodeModule* AstClassOrPackageRef::classOrPackageSkipp(const bool doRefs) const {
AstNode* foundp = m_classOrPackageNodep;
AstNode* lastp = nullptr;
while (foundp != lastp) {
@ -3409,11 +3468,12 @@ AstNodeModule* AstClassOrPackageRef::classOrPackageSkipp() const {
if (AstNodeDType* const anodep = VN_CAST(foundp, NodeDType)) {
foundp = anodep->skipRefOrNullp();
}
if (const AstTypedef* const anodep = VN_CAST(foundp, Typedef)) {
foundp = anodep->subDTypep();
}
if (const AstClassRefDType* const anodep = VN_CAST(foundp, ClassRefDType)) {
foundp = anodep->classp();
if (doRefs) {
if (const AstTypedef* const anodep = VN_CAST(foundp, Typedef)) {
foundp = anodep->subDTypep();
} else if (const AstClassRefDType* const anodep = VN_CAST(foundp, ClassRefDType)) {
foundp = anodep->classp();
}
}
}
return VN_CAST(foundp, NodeModule);
@ -3600,9 +3660,11 @@ void AstCoverInc::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
void AstFork::dump(std::ostream& str) const {
this->AstNodeBlock::dump(str);
str << " [" << joinType() << "]";
if (immediateStart()) str << " [IMMEDIATE]";
}
void AstFork::dumpJson(std::ostream& str) const {
dumpJsonStr(str, "joinType", joinType().ascii());
dumpJsonBoolFuncIf(str, immediateStart);
dumpJsonGen(str);
}
void AstStop::dump(std::ostream& str) const {
@ -3793,7 +3855,7 @@ void AstDelay::dumpJson(std::ostream& str) const {
}
const char* AstDisable::broken() const {
BROKEN_RTN((m_targetp && targetRefp()) || ((!m_targetp && !targetRefp())));
BROKEN_RTN(!m_targetp && !targetRefp());
return nullptr;
}
void AstDisable::dump(std::ostream& str) const {

View File

@ -137,12 +137,9 @@ class BeginVisitor final : public VNVisitor {
UINFO(8, " rename to " << nodep->name());
m_statep->userMarkChanged(nodep);
}
VL_RESTORER(m_displayScope);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
m_displayScope = "";
m_namedScope = "";
m_unnamedScope = "";
VL_RESTORER_CLEAR(m_displayScope);
VL_RESTORER_CLEAR(m_namedScope);
VL_RESTORER_CLEAR(m_unnamedScope);
iterateChildren(nodep);
}
void visit(AstNodeProcedure* nodep) override {
@ -163,14 +160,13 @@ class BeginVisitor final : public VNVisitor {
// naming; so that any begin's inside the function will rename
// inside the function.
// Process children
VL_RESTORER(m_displayScope);
VL_RESTORER_COPY(m_displayScope);
VL_RESTORER(m_ftaskp);
VL_RESTORER(m_liftedp);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
VL_RESTORER_CLEAR(m_namedScope);
VL_RESTORER_CLEAR(m_unnamedScope);
m_displayScope = dot(m_displayScope, nodep->name());
m_namedScope = "";
m_unnamedScope = "";
m_ftaskp = nodep;
m_liftedp = nullptr;
iterateChildren(nodep);
@ -191,9 +187,9 @@ class BeginVisitor final : public VNVisitor {
void visit(AstGenBlock* nodep) override {
// GenBlocks were only useful in variable creation, change names and delete
UINFO(8, " " << nodep);
VL_RESTORER(m_displayScope);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
VL_RESTORER_COPY(m_displayScope);
VL_RESTORER_COPY(m_namedScope);
VL_RESTORER_COPY(m_unnamedScope);
UASSERT_OBJ(!m_keepBegins, nodep, "Should be able to eliminate all AstGenBlock");
dotNames(nodep->name(), nodep->fileline(), "__BEGIN__");
iterateAndNextNull(nodep->itemsp());
@ -227,9 +223,9 @@ class BeginVisitor final : public VNVisitor {
void visit(AstBegin* nodep) override {
// Begin blocks were only useful in variable creation, change names and delete
UINFO(8, " " << nodep);
VL_RESTORER(m_displayScope);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
VL_RESTORER_COPY(m_displayScope);
VL_RESTORER_COPY(m_namedScope);
VL_RESTORER_COPY(m_unnamedScope);
{
VL_RESTORER(m_keepBegins);
m_keepBegins = false;
@ -261,9 +257,9 @@ class BeginVisitor final : public VNVisitor {
void visit(AstNodeBlock* nodep) override {
// Begin/Fork blocks were only useful in variable creation, change names and delete
UINFO(8, " " << nodep);
VL_RESTORER(m_displayScope);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
VL_RESTORER_COPY(m_displayScope);
VL_RESTORER_COPY(m_namedScope);
VL_RESTORER_COPY(m_unnamedScope);
{
VL_RESTORER(m_keepBegins);
m_keepBegins = VN_IS(nodep, Fork);
@ -451,7 +447,7 @@ class BeginRelinkVisitor final : public VNVisitorConst {
private:
// NODE STATE
// Input:
// AstNodeFTask::user1p // Node replaced, rename it
// AstNodeFTask::user1p // bool. Node replaced, rename it
// VISITORS
void visit(AstNodeFTaskRef* nodep) override {

View File

@ -252,18 +252,15 @@ private:
void visit(AstScope* nodep) override {
VL_RESTORER(m_inScope);
m_inScope = true;
VL_RESTORER(m_cFuncNames);
m_cFuncNames.clear();
VL_RESTORER_CLEAR(m_cFuncNames);
processAndIterate(nodep);
}
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_cFuncNames);
m_cFuncNames.clear();
VL_RESTORER_CLEAR(m_cFuncNames);
processAndIterate(nodep);
}
void visit(AstNodeUOrStructDType* nodep) override {
VL_RESTORER(m_cFuncNames);
m_cFuncNames.clear();
VL_RESTORER_CLEAR(m_cFuncNames);
processAndIterate(nodep);
}
void visit(AstNodeVarRef* nodep) override {

View File

@ -49,7 +49,7 @@ VL_DEFINE_DEBUG_FUNCTIONS;
class CastVisitor final : public VNVisitor {
// NODE STATE
// Entire netlist:
// AstNode::user1() // bool. Indicates node is of known size
// AstNode::user1() // bool. Node is of known size
const VNUser1InUse m_inuser1;
// STATE

View File

@ -114,7 +114,7 @@ class ClassVisitor final : public VNVisitor {
classScopep->aboveScopep(), classScopep->aboveCellp()};
packagep->addStmtsp(scopep);
// Iterate
VL_RESTORER(m_prefix);
VL_RESTORER_CLEAR(m_prefix);
VL_RESTORER(m_classPackagep);
VL_RESTORER(m_classScopep);
VL_RESTORER(m_packageScopep);
@ -129,7 +129,7 @@ class ClassVisitor final : public VNVisitor {
void visit(AstNodeModule* nodep) override {
// Visit for NodeModules that are not AstClass (AstClass is-a AstNodeModule)
// Classes are always under a Package (perhaps $unit) or a module
VL_RESTORER(m_prefix);
VL_RESTORER_CLEAR(m_prefix);
VL_RESTORER(m_modp);
m_modp = nodep;
m_prefix = nodep->name() + "__03a__03a"; // ::

View File

@ -35,7 +35,7 @@ VL_DEFINE_DEBUG_FUNCTIONS;
class CleanVisitor final : public VNVisitor {
// NODE STATE
// Entire netlist:
// AstNode::user() -> CleanState. For this node, 0==UNKNOWN
// AstNode::user1() -> CleanState. For this node, 0==UNKNOWN
// AstNode::user2() -> bool. True indicates widthMin has been propagated
// AstNodeDType::user3() -> AstNodeDType*. Alternative node with C size
const VNUser1InUse m_inuser1;

View File

@ -438,6 +438,7 @@ class ConstBitOpTreeVisitor final : public VNVisitorConst {
// Traverse down to see AstConst or AstVarRef
LeafInfo findLeaf(AstNode* nodep, bool expectConst) {
if (!nodep->dtypep()->skipRefp()->isIntegralOrPacked()) return LeafInfo{};
LeafInfo info{m_lsb};
{
VL_RESTORER(m_leafp);
@ -2780,9 +2781,8 @@ class ConstVisitor final : public VNVisitor {
streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(),
VSigning::UNSIGNED);
srcp = packedp;
}
if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)
|| VN_IS(srcDTypep, UnpackArrayDType))) {
} else if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)
|| VN_IS(srcDTypep, UnpackArrayDType))) {
if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) {
int blockSize = 1;
if (const AstConst* const constp = VN_CAST(streamp->rhsp(), Const)) {
@ -4585,6 +4585,10 @@ class ConstVisitor final : public VNVisitor {
// Custom
// Implied by AstIsUnbounded::numberOperate: V("AstIsUnbounded{$lhsp.castConst}", "replaceNum(nodep, 0)");
TREEOPV("AstIsUnbounded{$lhsp.castUnbounded}", "replaceNum(nodep, 1)");
// Sampled value functions of a constant.
// $rose/$fell/$stable/$changed are lowered to $past by V3AssertPre, so they fold via AstPast
TREEOPV("AstSampled{$exprp.castConst}", "replaceWChild(nodep, VN_AS(nodep->exprp(), NodeExpr))");
TREEOPV("AstPast{$exprp.castConst, !$ticksp}", "replaceWChild(nodep, nodep->exprp())");
// clang-format on
// Possible futures:

View File

@ -279,8 +279,8 @@ class CoverageVisitor final : public VNVisitor {
const AstNodeModule* const origModp = m_modp;
VL_RESTORER(m_modp);
VL_RESTORER(m_state);
VL_RESTORER(m_exprTempNames);
VL_RESTORER(m_funcTemps);
VL_RESTORER_COPY(m_exprTempNames);
VL_RESTORER_COPY(m_funcTemps);
createHandle(nodep);
m_modp = nodep;
m_state.m_inModOff = false; // Haven't made top shell, so tops are real tops
@ -294,8 +294,8 @@ class CoverageVisitor final : public VNVisitor {
void visit(AstClass* nodep) override {
VL_RESTORER(m_modp);
VL_RESTORER(m_state);
VL_RESTORER(m_exprTempNames);
VL_RESTORER(m_funcTemps);
VL_RESTORER_COPY(m_exprTempNames);
VL_RESTORER_COPY(m_funcTemps);
createHandle(nodep);
m_modp = nodep;
// Covergroup declarations are not executable statements; suppress line/expr/toggle
@ -356,8 +356,8 @@ class CoverageVisitor final : public VNVisitor {
void visit(AstNodeFTask* nodep) override {
VL_RESTORER(m_ftaskp);
VL_RESTORER(m_exprTempNames);
VL_RESTORER(m_funcTemps);
VL_RESTORER_COPY(m_exprTempNames);
VL_RESTORER_COPY(m_funcTemps);
m_ftaskp = nodep;
if (!nodep->dpiImport()) iterateProcedure(nodep);
}
@ -385,8 +385,7 @@ class CoverageVisitor final : public VNVisitor {
VL_RESTORER(m_state);
VL_RESTORER(m_exprStmtsp);
VL_RESTORER(m_inToggleOff);
// skip properties for expresison coverage
if (!VN_IS(nodep, Property)) m_exprStmtsp = nodep;
m_exprStmtsp = nodep;
m_inToggleOff = true;
createHandle(nodep);
iterateChildren(nodep);
@ -745,6 +744,11 @@ class CoverageVisitor final : public VNVisitor {
newCoverInc(nodep->fileline(), declp, m_beginHier + "_vlCoverageUserTrace"));
}
}
void visit(AstPropSpec* nodep) override {
VL_RESTORER(m_exprStmtsp);
m_exprStmtsp = nullptr;
iterateChildren(nodep);
}
void visit(AstStop* nodep) override {
UINFO(4, " STOP: " << nodep);
m_state.m_on = false;
@ -762,7 +766,7 @@ class CoverageVisitor final : public VNVisitor {
}
void visit(AstGenBlock* nodep) override {
// Similar to AstBegin
VL_RESTORER(m_beginHier);
VL_RESTORER_COPY(m_beginHier);
if (nodep->name() != "") {
m_beginHier = m_beginHier + (m_beginHier != "" ? "__DOT__" : "") + nodep->name();
}
@ -776,7 +780,7 @@ class CoverageVisitor final : public VNVisitor {
// generate blocks; each point should get separate consideration.
// (Currently ignored for line coverage, since any generate iteration
// covers the code in that line.)
VL_RESTORER(m_beginHier);
VL_RESTORER_COPY(m_beginHier);
VL_RESTORER(m_inToggleOff);
VL_RESTORER(m_exprStmtsp);
m_exprStmtsp = nodep;
@ -874,7 +878,7 @@ class CoverageVisitor final : public VNVisitor {
UASSERT_OBJ(m_exprs.empty(), nodep, "unexpected expression coverage garbage");
VL_RESTORER(m_seeking);
VL_RESTORER(m_objective);
VL_RESTORER(m_exprs);
VL_RESTORER_CLEAR(m_exprs); // Already asserted above it's empty.
m_seeking = SEEKING;
m_objective = false;

File diff suppressed because it is too large Load Diff

View File

@ -34,180 +34,6 @@ DfgGraph::~DfgGraph() {
forEachVertex([&](DfgVertex& vtx) { vtx.unlinkDelete(*this); });
}
std::unique_ptr<DfgGraph> DfgGraph::clone() const {
// Create the new graph
DfgGraph* const clonep = new DfgGraph{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::VarArray: {
cp = new DfgVarArray{*clonep, vp->vscp()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::VarPacked: {
cp = new DfgVarPacked{*clonep, vp->vscp()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
default: {
vtx.v3fatalSrc("Unhandled variable vertex type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
if (AstVarScope* const tmpForp = vp->tmpForp()) cp->tmpForp(tmpForp);
}
// Clone ast reference vertices
for (const DfgVertexAst& vtx : m_astVertices) { // LCOV_EXCL_START
switch (vtx.type()) {
case VDfgType::AstRd: {
const DfgAstRd* const vp = vtx.as<DfgAstRd>();
DfgAstRd* const cp = new DfgAstRd{*clonep, vp->exprp(), vp->inSenItem(), vp->inLoop()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
default: {
vtx.v3fatalSrc("Unhandled ast reference vertex type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
} // LCOV_EXCL_STOP
// Clone operation vertices
for (const DfgVertex& vtx : m_opVertices) {
switch (vtx.type()) {
#include "V3Dfg__gen_clone_cases.h" // From ./astgen
case VDfgType::CReset: { // LCOV_EXCL_START - No algorithm actually hits this today
DfgCReset* const cp = new DfgCReset{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
} // LCOV_EXCL_STOP
case VDfgType::MatchMasked: {
DfgMatchMasked* const cp = new DfgMatchMasked{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::Sel: {
DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtype()};
cp->lsb(vtx.as<DfgSel>()->lsb());
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::Rep: {
DfgRep* const cp = new DfgRep{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::UnitArray: {
DfgUnitArray* const cp = new DfgUnitArray{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::Mux: {
DfgMux* const cp = new DfgMux{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::SpliceArray: {
DfgSpliceArray* const cp = new DfgSpliceArray{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::SplicePacked: {
DfgSplicePacked* const cp = new DfgSplicePacked{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::Logic: {
vtx.v3fatalSrc("DfgLogic cannot be cloned");
VL_UNREACHABLE;
break;
}
case VDfgType::Unresolved: {
vtx.v3fatalSrc("DfgUnresolved cannot be cloned");
VL_UNREACHABLE;
break;
}
case VDfgType::AstRd: // LCOV_EXCL_START
case VDfgType::Const:
case VDfgType::VarArray:
case VDfgType::VarPacked: {
vtx.v3fatalSrc("Vertex should have been handled above: " + vtx.typeName());
VL_UNREACHABLE;
break;
} // LCOV_EXCL_STOP
}
}
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) {
DfgVertexVar* const cp = vtxp2clonep.at(&vtx)->as<DfgVertexVar>();
if (const DfgVertex* const srcp = vtx.srcp()) cp->srcp(vtxp2clonep.at(srcp));
if (const DfgVertex* const defp = vtx.defaultp()) cp->defaultp(vtxp2clonep.at(defp));
}
// Hook up inputs of cloned ast references
for (const DfgVertexAst& vtx : m_astVertices) { // LCOV_EXCL_START
switch (vtx.type()) {
case VDfgType::AstRd: {
const DfgAstRd* const vp = vtx.as<DfgAstRd>();
DfgAstRd* const cp = vtxp2clonep.at(&vtx)->as<DfgAstRd>();
if (const DfgVertex* const srcp = vp->srcp()) cp->srcp(vtxp2clonep.at(srcp));
break;
}
default: {
vtx.v3fatalSrc("Unhandled DfgVertexAst sub type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
} // LCOV_EXCL_STOP
// Hook up inputs of cloned operation vertices
for (const DfgVertex& vtx : m_opVertices) {
if (vtx.is<DfgVertexVariadic>()) {
switch (vtx.type()) {
case VDfgType::SpliceArray:
case VDfgType::SplicePacked: {
const DfgVertexSplice* const vp = vtx.as<DfgVertexSplice>();
DfgVertexSplice* const cp = vtxp2clonep.at(vp)->as<DfgVertexSplice>();
vp->foreachDriver([&](const DfgVertex& src, uint32_t lo, FileLine* flp) {
cp->addDriver(vtxp2clonep.at(&src), lo, flp);
return false;
});
break;
}
default: {
vtx.v3fatalSrc("Unhandled DfgVertexVariadic sub type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
} else {
DfgVertex* const cp = vtxp2clonep.at(&vtx);
for (size_t i = 0; i < vtx.nInputs(); ++i) {
cp->inputp(i, vtxp2clonep.at(vtx.inputp(i)));
}
}
}
return std::unique_ptr<DfgGraph>{clonep};
}
void DfgGraph::mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) {
if (otherps.empty()) return;
@ -620,6 +446,12 @@ DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataT
dfg.addVertex(*this);
}
bool DfgVertex::unsafe() const {
if (is<DfgMux>()) return true;
if (is<DfgArraySel>()) return !as<DfgArraySel>()->bitp()->is<DfgConst>();
return false;
}
void DfgVertex::typeCheck(const DfgGraph& dfg) const {
#define CHECK(cond, msg) \

View File

@ -195,6 +195,8 @@ public:
UASSERT_OBJ(m_dtype.isPacked(), this, "Non packed vertex has no 'width'");
return m_dtype.size();
}
// Has terminating side-effect
bool unsafe() const;
// Type check vertex (for debugging)
void typeCheck(const DfgGraph& dfg) const;
@ -485,9 +487,6 @@ public:
for (const DfgVertex& vtx : m_opVertices) f(vtx);
}
// Return an identical, independent copy of this graph. Vertex and edge order might differ.
std::unique_ptr<DfgGraph> clone() const VL_MT_DISABLED;
// Merge contents of other graphs into this graph. Deletes the other graphs.
// DfgVertexVar instances representing the same Ast variable are unified.
void mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) VL_MT_DISABLED;
@ -830,6 +829,13 @@ bool DfgVertex::isCheaperThanLoad() const {
const uint32_t msb = lsb + selp->width() - 1;
return VL_BITWORD_E(msb) == VL_BITWORD_E(lsb);
}
// Replication of a single cheap bit. Each word of the result is the same
// mask computed by negating that bit, so recomputing it at each use costs
// no more than the load it replaces.
if (const DfgRep* const repp = cast<DfgRep>()) {
const DfgVertex* const srcp = repp->srcp();
return srcp->width() == 1 && srcp->isCheaperThanLoad();
}
// Zero extend of a cheap vertex - Extend(_) was converted to Concat(0, _)
if (const DfgConcat* const catp = cast<DfgConcat>()) {
if (catp->width() > VL_QUADSIZE) return false;

View File

@ -173,6 +173,7 @@ class TraceDriver final : public DfgVisitor {
uint64_t m_component = 0;
uint32_t m_lsb = 0; // LSB to extract from the currently visited Vertex
uint32_t m_msb = 0; // MSB to extract from the currently visited Vertex
std::vector<uint32_t> m_idxs; // Indices to extract from the currently visited Vertex
// Result of tracing the currently visited Vertex. Use SET_RESULT below!
DfgVertex* m_resp = nullptr;
DfgVertex* m_defaultp = nullptr; // When tracing a variable, this is its 'defaultp', if any
@ -228,8 +229,7 @@ class TraceDriver final : public DfgVisitor {
return varp;
}
// Continue tracing drivers of the given vertex, at the given LSB.
// Every visitor should call this to continue the traversal.
// Trace drivers of the given packed vertex, at the given bit range.
DfgVertex* trace(DfgVertex* const vtxp, const uint32_t msb, const uint32_t lsb) {
UASSERT_OBJ(vtxp->isPacked(), vtxp, "Can only trace packed type vertices");
UASSERT_OBJ(vtxp->size() > msb, vtxp, "Traced Vertex too narrow");
@ -271,6 +271,7 @@ class TraceDriver final : public DfgVisitor {
// Otherwise visit the vertex to trace it
VL_RESTORER(m_msb);
VL_RESTORER(m_lsb);
VL_RESTORER_CLEAR(m_idxs);
VL_RESTORER(m_resp);
m_msb = msb;
m_lsb = lsb;
@ -285,6 +286,40 @@ class TraceDriver final : public DfgVisitor {
return respr;
}
// Trace drivers of the given array vertex, at the current m_idxs, m_msb, m_lsb.
DfgVertex* traceSameIdx(DfgVertex* const vtxp) {
UASSERT_OBJ(vtxp->isArray(), vtxp, "Should be an array vertex");
UASSERT_OBJ(!m_idxs.empty(), vtxp, "Should have a pending index");
VL_RESTORER(m_resp);
m_resp = nullptr;
iterate(vtxp); // Resolve the element, the array visitors set 'm_resp'
UASSERT_OBJ(m_resp, vtxp, "Tracing driver failed for " << vtxp->typeName());
UASSERT_OBJ(m_resp->width() == (m_msb - m_lsb + 1), vtxp, "Wrong result width");
return m_resp;
}
// Trace drivers of the given array vertex, pushing the given index, at current m_msb, m_lsb.
DfgVertex* tracePushIdx(DfgVertex* const vtxp, const uint32_t idx) {
const size_t nIdxs = m_idxs.size();
m_idxs.push_back(idx);
DfgVertex* const resp = traceSameIdx(vtxp);
m_idxs.pop_back();
UASSERT_OBJ(m_idxs.size() == nIdxs, vtxp, "Index stack size mismatch");
return resp;
}
// Trace drivers of the given vertex, popping the innermost index, at current m_msb, m_lsb.
DfgVertex* tracePopIdx(DfgVertex* const vtxp) {
UASSERT_OBJ(!m_idxs.empty(), vtxp, "Should have a pending index");
const size_t nIdxs = m_idxs.size();
const uint32_t idx = m_idxs.back();
m_idxs.pop_back();
DfgVertex* const resp = m_idxs.empty() ? trace(vtxp, m_msb, m_lsb) : traceSameIdx(vtxp);
m_idxs.push_back(idx);
UASSERT_OBJ(m_idxs.size() == nIdxs, vtxp, "Index stack size mismatch");
return resp;
}
template <typename Vertex>
Vertex* traceBitwiseBinary(Vertex* vtxp) {
static_assert(std::is_base_of<DfgVertexBinary, Vertex>::value,
@ -388,25 +423,26 @@ class TraceDriver final : public DfgVisitor {
// Gather terms
std::vector<DfgVertex*> termps;
uint32_t lsb = m_lsb;
for (const Driver& driver : drivers) {
// Driver is below the searched LSB, move on
if (m_lsb > driver.m_msb) continue;
if (lsb > driver.m_msb) continue;
// Driver is above the searched MSB, done
if (driver.m_lsb > m_msb) break;
// Gap below this driver, trace default to fill it
if (driver.m_lsb > m_lsb) {
if (driver.m_lsb > lsb) {
UASSERT_OBJ(m_defaultp, vtxp, "Should have a default driver if needs tracing");
termps.emplace_back(trace(m_defaultp, driver.m_lsb - 1, m_lsb));
m_lsb = driver.m_lsb;
termps.emplace_back(trace(m_defaultp, driver.m_lsb - 1, lsb));
lsb = driver.m_lsb;
}
// Driver covers searched range, pick the needed/available bits
const uint32_t lim = std::min(m_msb, driver.m_msb);
termps.emplace_back(trace(driver.m_vtxp, lim - driver.m_lsb, m_lsb - driver.m_lsb));
m_lsb = lim + 1;
termps.emplace_back(trace(driver.m_vtxp, lim - driver.m_lsb, lsb - driver.m_lsb));
lsb = lim + 1;
}
if (m_msb >= m_lsb) {
if (m_msb >= lsb) {
UASSERT_OBJ(m_defaultp, vtxp, "Should have a default driver if needs tracing");
termps.emplace_back(trace(m_defaultp, m_msb, m_lsb));
termps.emplace_back(trace(m_defaultp, m_msb, lsb));
}
// The earlier cheks cover the case when either a whole driver or the default covers
@ -425,46 +461,58 @@ class TraceDriver final : public DfgVisitor {
SET_RESULT(resp);
}
void visit(DfgVarPacked* vtxp) override {
UASSERT_OBJ(!vtxp->isVolatile(), vtxp, "Should not trace through volatile VarPacked");
void visit(DfgSpliceArray* vtxp) override {
// Explicit per-element driver (a UnitArray wrapping the element value)
if (DfgVertex* const driverp = vtxp->driverAt(m_idxs.back())) {
// Consume this index, then trace the element value
SET_RESULT(tracePopIdx(driverp->as<DfgUnitArray>()->srcp()));
return;
}
// TODO: this is unreachable, as syntheis can't create it today.
// // Element not driven explicitly, so it comes from the default array. Keep the
// // index pending (the default is the whole array, indexed the same way) and
// // continue tracing it.
// UASSERT_OBJ(m_defaultp, vtxp, "Independent array element should have a driver or
// default"); SET RESULT(traceSameIdx(m_defaultp));
}
void visit(DfgVertexVar* vtxp) override {
UASSERT_OBJ(!vtxp->isVolatile(), vtxp, "Should not trace through volatile variable");
VL_RESTORER(m_defaultp);
m_defaultp = vtxp->defaultp();
SET_RESULT(trace(vtxp->srcp(), m_msb, m_lsb));
DfgVertex* const drvp = vtxp->srcp() ? vtxp->srcp() : m_defaultp;
UASSERT_OBJ(drvp, vtxp, "Should not have to trace undriven variable");
// Packed variable: trace the driver. Array variable: continue navigating it at
// the pending element (both at the same bit range).
SET_RESULT(m_idxs.empty() ? trace(drvp, m_msb, m_lsb) : traceSameIdx(drvp));
}
void visit(DfgArraySel* vtxp) override {
// From a variable
const DfgVarArray* varp = vtxp->fromp()->cast<DfgVarArray>();
if (!varp) return;
// If index is not constant, independence was proven only if the 'fromp' is
// independent, so no need to trace that
if (!vtxp->bitp()->is<DfgConst>()) {
DfgArraySel* const resp = make<DfgArraySel>(vtxp, vtxp->width());
resp->fromp(vtxp->fromp());
resp->bitp(trace(vtxp->bitp(), vtxp->bitp()->width() - 1, 0));
DfgSel* const selp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
selp->fromp(resp);
selp->lsb(m_lsb);
SET_RESULT(selp);
// If constant index, push it and trace the selected element through the array
// structure of 'fromp'. This handles arbitrarily nested (multi-dimensional)
// arrays, as each nested ArraySel pushes a further index.
if (const DfgConst* const idxp = vtxp->bitp()->cast<DfgConst>()) {
SET_RESULT(tracePushIdx(vtxp->fromp(), idxp->toU32()));
return;
}
// Trace the relevant driver based on the static index
const DfgConst* const idxp = vtxp->bitp()->as<DfgConst>();
UASSERT_OBJ(!varp->isVolatile(), vtxp, "Should not trace through volatile VarArray");
// Skip through intermediate variables
while (varp->srcp() && varp->srcp()->is<DfgVarArray>()) {
varp = varp->srcp()->as<DfgVarArray>();
UASSERT_OBJ(!varp->isVolatile(), vtxp, "Should not trace through volatile VarArray");
}
// Find driver
const DfgVertex* srcp = varp->srcp();
if (const DfgSpliceArray* const splicep = srcp->cast<DfgSpliceArray>()) {
srcp = splicep->driverAt(idxp->toSizeT());
}
// Trace the driver, which at this point must be a UnitArray
SET_RESULT(trace(srcp->as<DfgUnitArray>()->srcp(), m_msb, m_lsb));
// If index is not constant, independence was proven only if the 'fromp' is
// independent, so no need to trace that, just reference it with the traced
// index. This only happens at the packed ArraySel leaf.
UASSERT_OBJ(m_idxs.empty(), vtxp, "Non-constant index below an outer array index");
DfgArraySel* const resp = make<DfgArraySel>(vtxp, vtxp->width());
resp->fromp(vtxp->fromp());
resp->bitp(trace(vtxp->bitp(), vtxp->bitp()->width() - 1, 0));
DfgSel* const selp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
selp->fromp(resp);
selp->lsb(m_lsb);
SET_RESULT(selp);
}
void visit(DfgUnitArray* vtxp) override {
// Single-element array adapter, the pending index must be 0, unwrap the element
UASSERT_OBJ(m_idxs.back() == 0, vtxp, "UnitArray element index should be 0");
SET_RESULT(tracePopIdx(vtxp->srcp()));
}
void visit(DfgConcat* vtxp) override {
@ -710,7 +758,7 @@ class TraceDriver final : public DfgVisitor {
DfgSel* const selp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
selp->fromp(resp);
selp->lsb(m_lsb);
SET_RESULT(resp);
SET_RESULT(selp);
}
#undef SET_RESULT
@ -741,6 +789,142 @@ public:
}
};
// A bit mask for each bit in a value, which can be either packed or aggregate type
class BitMask final {
// TYPES
const DfgDataType& m_dtype;
union {
V3Number m_num; // The bits, if packed
std::vector<BitMask> m_sub; // The per element masks, if aggregate
};
public:
// CONSTRUCTOR
BitMask(FileLine* flp, const DfgDataType& dtype)
: m_dtype{dtype} {
UASSERT_OBJ(!m_dtype.isNull(), flp, "Expected non-null data type");
if (m_dtype.isPacked()) {
new (&m_num) V3Number{flp, static_cast<int>(m_dtype.size()), 0};
} else {
new (&m_sub) std::vector<BitMask>{};
m_sub.reserve(m_dtype.size());
for (uint32_t i = 0; i < m_dtype.size(); ++i)
m_sub.emplace_back(flp, m_dtype.elemDtype());
}
}
BitMask(const DfgVertex& vtx)
: BitMask{vtx.fileline(), vtx.dtype()} {}
BitMask(const BitMask& other)
: m_dtype{other.m_dtype} {
if (m_dtype.isPacked()) {
new (&m_num) V3Number{other.m_num};
} else {
new (&m_sub) std::vector<BitMask>{other.m_sub};
}
}
BitMask& operator=(const BitMask& other) {
if (this != &other) {
UASSERT(m_dtype == other.m_dtype, "Expected same data type");
if (other.m_dtype.isPacked()) {
m_num = other.m_num;
} else {
m_sub = other.m_sub;
}
}
return *this;
}
~BitMask() {
if (m_dtype.isPacked()) {
m_num.~V3Number();
} else {
m_sub.~vector<BitMask>();
}
}
// METHODS
V3Number& num() {
UASSERT(m_dtype.isPacked(), "Expected packed data type");
return m_num;
}
const V3Number& num() const {
UASSERT(m_dtype.isPacked(), "Expected packed data type");
return m_num;
}
std::vector<BitMask>& sub() {
UASSERT(m_dtype.isArray(), "Expected array data type");
return m_sub;
}
bool isZero() const {
if (m_dtype.isPacked()) return m_num.isEqZero();
for (const BitMask& sub : m_sub) {
if (!sub.isZero()) return false;
}
return true;
}
bool isOnes() const {
if (m_dtype.isPacked()) return m_num.isEqAllOnes();
for (const BitMask& sub : m_sub) {
if (!sub.isOnes()) return false;
}
return true;
}
void setZero() {
if (m_dtype.isPacked()) {
m_num.setAllBits0();
} else {
for (BitMask& sub : m_sub) sub.setZero();
}
}
void setOnes() {
if (m_dtype.isPacked()) {
m_num.setAllBits1();
} else {
for (BitMask& sub : m_sub) sub.setOnes();
}
}
bool operator==(const BitMask& other) const {
UASSERT(m_dtype == other.m_dtype, "Expected same data type");
if (m_dtype.isPacked()) return m_num.isCaseEq(other.m_num);
return m_sub == other.m_sub;
}
bool operator!=(const BitMask& other) const { return !(*this == other); }
// 'this' has a clear bit where 'that' has a set bit, that is,
// 'this' has a proper subset of bits set compared to 'that'.
bool isContractionOf(const BitMask& that) const {
UASSERT(m_dtype == that.m_dtype, "Expected same data type");
if (m_dtype.isPacked()) {
const size_t words = VL_WORDS_I(m_dtype.size());
for (size_t i = 0; i < words; ++i) {
if (~m_num.edataWord(i) & that.m_num.edataWord(i)) return true;
}
return false;
}
UASSERT(m_dtype.isArray(), "Expected array data type");
for (size_t i = 0; i < m_sub.size(); ++i) {
if (m_sub[i].isContractionOf(that.m_sub[i])) return true;
}
return false;
}
std::string toString() const {
if (m_dtype.isPacked()) return "0x" + m_num.displayed(m_num.fileline(), "%x");
std::string result;
result += "{";
for (const BitMask& sub : m_sub) {
if (&sub != &m_sub.front()) result += ", ";
result += sub.toString();
}
result += "}";
return result;
}
};
class IndependentBits final : public DfgVisitor {
// TYPES
struct VertexState final {
@ -751,7 +935,7 @@ class IndependentBits final : public DfgVisitor {
// STATE
const SccInfo& m_sccInfo; // The SccInfo instance
// Vertex to current bit mask map. The mask is set for the bits that are independent of the SCC
std::unordered_map<const DfgVertex*, V3Number> m_vtxp2Mask;
std::unordered_map<const DfgVertex*, BitMask> m_vtxp2Mask;
// Work list for the traversal (min-queue of vertex RPO numbers)
std::priority_queue<size_t, std::vector<size_t>, std::greater<size_t>> m_workQueue;
std::unordered_map<const DfgVertex*, VertexState> m_vtxp2State; // State of each vertex
@ -761,21 +945,13 @@ class IndependentBits final : public DfgVisitor {
#endif
// METHODS
// Predicate to check if a vertex should be analysed directly
bool handledDirectly(const DfgVertex& vtx) const {
if (!vtx.isPacked()) return false;
if (vtx.is<DfgSplicePacked>()) return false;
return true;
}
// Retrieve the mask for the given vertex (create it with value 0 if needed)
V3Number& mask(const DfgVertex& vtx) {
UASSERT_OBJ(handledDirectly(vtx), &vtx, "Vertex should not be handled direclty");
// Look up (or create) mask for 'vtxp'
BitMask& mask(const DfgVertex& vtx) {
// Look up (or create) mask for 'vtx'
return m_vtxp2Mask
.emplace(std::piecewise_construct, //
std::forward_as_tuple(&vtx), //
std::forward_as_tuple(vtx.fileline(), static_cast<int>(vtx.width()), 0)) //
std::forward_as_tuple(&vtx), std::forward_as_tuple(vtx))
.first->second;
}
@ -784,7 +960,7 @@ class IndependentBits final : public DfgVisitor {
// TODO: Use C++20 std::source_location instead of a macro
#ifdef VL_DEBUG
#define MASK(vtxp) \
([this](const DfgVertex& vtx) -> V3Number& { \
([this](const DfgVertex& vtx) -> BitMask& { \
if (VL_UNLIKELY(m_lineCoverageFile.is_open())) m_lineCoverageFile << __LINE__ << '\n'; \
return mask(vtx); \
}(*vtxp))
@ -810,13 +986,24 @@ class IndependentBits final : public DfgVisitor {
}
}
void propagateFromDriver(V3Number& m, const DfgVertex* srcp) {
void propagateFromDriver(BitMask& m, const DfgVertex* srcp) {
// If there is no driver, we are done
if (!srcp) return;
// If it is driven by a splice, we need to combine the masks of the drivers
if (const DfgSplicePacked* const splicep = srcp->cast<DfgSplicePacked>()) {
splicep->foreachDriver([&](const DfgVertex& src, uint32_t lo) {
m.opSelInto(MASK(&src), lo, src.width());
m.num().opSelInto(MASK(&src).num(), lo, src.width());
return false;
});
return;
}
if (const DfgSpliceArray* const splicep = srcp->cast<DfgSpliceArray>()) {
splicep->foreachDriver([&](const DfgVertex& src, uint32_t lo) {
if (const DfgUnitArray* const uap = src.cast<DfgUnitArray>()) {
m.sub().at(lo) = MASK(uap->srcp());
} else {
// m.sub().at(lo) = Can't happen MASK(&src);
}
return false;
});
return;
@ -827,15 +1014,15 @@ class IndependentBits final : public DfgVisitor {
// VISITORS
void visit(DfgVertex* vtxp) override { // LCOV_EXCL_START
UASSERT_OBJ(handledDirectly(*vtxp), vtxp, "Vertex should be handled direclty");
UINFO(9, "IndependentBits - Unhandled vertex type: " << vtxp->typeName());
// Conservative assumption about all bits being dependent prevails
} // LCOV_EXCL_STOP
void visit(DfgVarPacked* vtxp) override {
void visit(DfgVertexVar* vtxp) override {
// We cannot trace through a volatile variable, so pretend all bits are dependent
if (vtxp->isVolatile()) return;
V3Number& m = MASK(vtxp);
BitMask& m = MASK(vtxp);
DfgVertex* const srcp = vtxp->srcp();
DfgVertex* const defaultp = vtxp->defaultp();
// If there is a default driver, we start from that
@ -844,81 +1031,62 @@ class IndependentBits final : public DfgVisitor {
propagateFromDriver(m, srcp);
}
void visit(DfgArraySel* vtxp) override {
// From a variable
const DfgVarArray* varp = vtxp->fromp()->cast<DfgVarArray>();
if (!varp) return;
void visit(DfgVertexSplice* vtxp) override {
propagateFromDriver(MASK(vtxp), vtxp); // Needed to continue traversal
}
// If index is not constant, independent only if the variable index
// is indenpendent and the array is independent. We don't track arrays,
// so we will assume an array is only independent if it has no drivers
// in the graph. TODO: could check all drivers.
if (!vtxp->bitp()->is<DfgConst>()) {
if (MASK(vtxp->bitp()).isEqAllOnes() && !varp->srcp() && !varp->defaultp()) {
MASK(vtxp).setAllBits1();
}
void visit(DfgUnitArray* vtxp) override { MASK(vtxp).sub().at(0) = MASK(vtxp->srcp()); }
void visit(DfgArraySel* vtxp) override {
DfgVertex* const fromp = vtxp->fromp();
// If constant index, copy mask of relevant element
if (const DfgConst* const idxp = vtxp->bitp()->cast<DfgConst>()) {
MASK(vtxp) = MASK(vtxp->fromp()).sub().at(idxp->toSizeT());
return;
}
// Trace the relevant driver based on the static index
const DfgConst* const idxp = vtxp->bitp()->as<DfgConst>();
// We cannot trace through a volatile variable, so pretend all bits are dependent
if (varp->isVolatile()) return;
// Skip through intermediate variables
while (varp->srcp() && varp->srcp()->is<DfgVarArray>()) {
varp = varp->srcp()->as<DfgVarArray>();
if (varp->isVolatile()) return;
}
// Find driver
const DfgVertex* srcp = varp->srcp();
if (!srcp) return;
if (const DfgSpliceArray* const splicep = srcp->cast<DfgSpliceArray>()) {
srcp = splicep->driverAt(idxp->toSizeT());
if (!srcp) return;
}
const DfgUnitArray* uap = srcp->cast<DfgUnitArray>();
if (!uap) return;
srcp = uap->srcp();
// Propagate from driver
propagateFromDriver(MASK(vtxp), srcp);
// If index is not constant, independent only if the index is indenpendent, and the array
// is independent. TODO: could relax by '&' reducing, not sure if worth it.
if (MASK(vtxp->bitp()).isOnes() && MASK(fromp).isOnes()) MASK(vtxp).setOnes();
}
void visit(DfgConcat* vtxp) override {
const DfgVertex* const rhsp = vtxp->rhsp();
const DfgVertex* const lhsp = vtxp->lhsp();
V3Number& m = MASK(vtxp);
m.opSelInto(MASK(rhsp), 0, rhsp->width());
m.opSelInto(MASK(lhsp), rhsp->width(), lhsp->width());
V3Number& m = MASK(vtxp).num();
m.opSelInto(MASK(rhsp).num(), 0, rhsp->width());
m.opSelInto(MASK(lhsp).num(), rhsp->width(), lhsp->width());
}
void visit(DfgRep* vtxp) override {
const uint32_t count = vtxp->count();
const DfgVertex* const srcp = vtxp->srcp();
const uint32_t sWidth = srcp->width();
V3Number& vMask = MASK(vtxp);
V3Number& sMask = MASK(srcp);
V3Number& vMask = MASK(vtxp).num();
V3Number& sMask = MASK(srcp).num();
for (uint32_t i = 0; i < count; ++i) vMask.opSelInto(sMask, i * sWidth, sWidth);
}
void visit(DfgSel* vtxp) override {
const uint32_t lsb = vtxp->lsb();
const uint32_t msb = lsb + vtxp->width() - 1;
MASK(vtxp).opSel(MASK(vtxp->fromp()), msb, lsb);
MASK(vtxp).num().opSel(MASK(vtxp->fromp()).num(), msb, lsb);
}
void visit(DfgExtend* vtxp) override {
const DfgVertex* const srcp = vtxp->srcp();
const uint32_t sWidth = srcp->width();
V3Number& s = MASK(srcp);
V3Number& m = MASK(vtxp);
V3Number& s = MASK(srcp).num();
V3Number& m = MASK(vtxp).num();
m.opSelInto(s, 0, sWidth);
m.opSetRange(sWidth, vtxp->width() - sWidth, '1');
}
void visit(DfgExtendS* vtxp) override {
const DfgVertex* const srcp = vtxp->srcp();
const uint32_t sWidth = srcp->width();
V3Number& s = MASK(srcp);
V3Number& m = MASK(vtxp);
V3Number& s = MASK(srcp).num();
V3Number& m = MASK(vtxp).num();
m.opSelInto(s, 0, sWidth);
m.opSetRange(sWidth, vtxp->width() - sWidth, s.bitIs0(sWidth - 1) ? '0' : '1');
}
@ -928,71 +1096,62 @@ class IndependentBits final : public DfgVisitor {
}
void visit(DfgAnd* vtxp) override { //
MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp()));
MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num());
}
void visit(DfgOr* vtxp) override { //
MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp()));
MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num());
}
void visit(DfgXor* vtxp) override { //
MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp()));
MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num());
}
void visit(DfgAdd* vtxp) override {
V3Number& m = MASK(vtxp);
m.opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp()));
V3Number& m = MASK(vtxp).num();
m.opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num());
floodTowardsMsb(m);
}
void visit(DfgSub* vtxp) override { // Same as Add: 2's complement (a - b) == (a + ~b + 1)
V3Number& m = MASK(vtxp);
m.opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp()));
V3Number& m = MASK(vtxp).num();
m.opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num());
floodTowardsMsb(m);
}
void visit(DfgRedAnd* vtxp) override { //
if (MASK(vtxp->lhsp()).isEqAllOnes()) { //
MASK(vtxp).setAllBits1();
}
void visit(DfgRedAnd* vtxp) override {
const bool independent = MASK(vtxp->lhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgRedOr* vtxp) override { //
if (MASK(vtxp->lhsp()).isEqAllOnes()) { //
MASK(vtxp).setAllBits1();
}
void visit(DfgRedOr* vtxp) override {
const bool independent = MASK(vtxp->lhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgRedXor* vtxp) override { //
if (MASK(vtxp->lhsp()).isEqAllOnes()) { //
MASK(vtxp).setAllBits1();
}
void visit(DfgRedXor* vtxp) override {
const bool independent = MASK(vtxp->lhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgEq* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgNeq* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgLt* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgLte* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgGt* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgGte* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgShiftRS* vtxp) override {
@ -1004,14 +1163,10 @@ class IndependentBits final : public DfgVisitor {
if (DfgConst* const rConstp = rhsp->cast<DfgConst>()) {
const uint32_t shiftAmount = rConstp->toU32();
if (shiftAmount >= width) {
if (MASK(lhsp).bitIs0(width - 1)) {
MASK(vtxp).setAllBits0();
} else {
MASK(vtxp).setAllBits1();
}
if (MASK(lhsp).num().bitIs1(width - 1)) { MASK(vtxp).setOnes(); }
} else {
V3Number& m = MASK(vtxp);
m.opShiftRS(MASK(lhsp), rConstp->num(), width);
V3Number& m = MASK(vtxp).num();
m.opShiftRS(MASK(lhsp).num(), rConstp->num(), width);
m.opSetRange(width - shiftAmount, shiftAmount, '1');
}
return;
@ -1020,9 +1175,9 @@ class IndependentBits final : public DfgVisitor {
// Otherwise, as the shift amount is non-negative, all independent
// and consecutive top bits in the lhs yield an independent result
// if the shift amount is independent.
if (MASK(rhsp).isEqAllOnes()) {
V3Number& m = MASK(vtxp);
m = MASK(lhsp);
if (MASK(rhsp).isOnes()) {
V3Number& m = MASK(vtxp).num();
m = MASK(lhsp).num();
floodTowardsLsb(m);
}
}
@ -1036,10 +1191,10 @@ class IndependentBits final : public DfgVisitor {
if (DfgConst* const rConstp = rhsp->cast<DfgConst>()) {
const uint32_t shiftAmount = rConstp->toU32();
if (shiftAmount >= width) {
MASK(vtxp).setAllBits1();
MASK(vtxp).setOnes();
} else {
V3Number& m = MASK(vtxp);
m.opShiftR(MASK(lhsp), rConstp->num());
V3Number& m = MASK(vtxp).num();
m.opShiftR(MASK(lhsp).num(), rConstp->num());
m.opSetRange(width - shiftAmount, shiftAmount, '1');
}
return;
@ -1048,9 +1203,9 @@ class IndependentBits final : public DfgVisitor {
// Otherwise, as the shift amount is non-negative, all independent
// and consecutive top bits in the lhs yield an independent result
// if the shift amount is independent.
if (MASK(rhsp).isEqAllOnes()) {
V3Number& m = MASK(vtxp);
m = MASK(lhsp);
if (MASK(rhsp).isOnes()) {
V3Number& m = MASK(vtxp).num();
m = MASK(lhsp).num();
floodTowardsLsb(m);
}
}
@ -1064,10 +1219,10 @@ class IndependentBits final : public DfgVisitor {
if (DfgConst* const rConstp = rhsp->cast<DfgConst>()) {
const uint32_t shiftAmount = rConstp->toU32();
if (shiftAmount >= width) {
MASK(vtxp).setAllBits1();
MASK(vtxp).setOnes();
} else {
V3Number& m = MASK(vtxp);
m.opShiftL(MASK(lhsp), rConstp->num());
V3Number& m = MASK(vtxp).num();
m.opShiftL(MASK(lhsp).num(), rConstp->num());
m.opSetRange(0, shiftAmount, '1');
}
return;
@ -1076,23 +1231,21 @@ class IndependentBits final : public DfgVisitor {
// Otherwise, as the shift amount is non-negative, all independent
// and consecutive bottom bits in the lhs yield an independent result
// if the shift amount is independent.
if (MASK(rhsp).isEqAllOnes()) {
V3Number& m = MASK(vtxp);
m = MASK(lhsp);
if (MASK(rhsp).isOnes()) {
V3Number& m = MASK(vtxp).num();
m = MASK(lhsp).num();
floodTowardsMsb(m);
}
}
void visit(DfgCond* vtxp) override {
if (MASK(vtxp->condp()).isEqAllOnes()) {
MASK(vtxp).opAnd(MASK(vtxp->thenp()), MASK(vtxp->elsep()));
if (MASK(vtxp->condp()).isOnes()) {
MASK(vtxp).num().opAnd(MASK(vtxp->thenp()).num(), MASK(vtxp->elsep()).num());
}
}
void visit(DfgMatchMasked* vtxp) override {
if (MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->matchp()).isEqAllOnes()) { //
MASK(vtxp).setAllBits1();
}
if (MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->matchp()).isOnes()) { MASK(vtxp).setOnes(); }
}
#undef MASK
@ -1102,11 +1255,6 @@ class IndependentBits final : public DfgVisitor {
vtx.foreachSink([&](DfgVertex& sink) {
// Ignore if sink is not part of an SCC, already has all bits marked independent
if (!m_sccInfo.get(sink)) return false;
// If a vertex is not handled directly, recursively enqueue its sinks instead
if (!handledDirectly(sink)) {
enqueueSinks(sink);
return false;
}
// Otherwise just enqueue it
VertexState& state = m_vtxp2State.at(&sink);
if (!state.m_isOnWorkList) {
@ -1169,18 +1317,15 @@ class IndependentBits final : public DfgVisitor {
// always assign constants bits, which are always independent (eg Extend/Shift)
// Enqueue sinks of all SCC vertices that have at least one independent bit
for (DfgVertex* const vtxp : rpoEnumeration) {
if (!handledDirectly(*vtxp)) continue;
if (m_sccInfo.get(*vtxp)) continue;
mask(*vtxp).setAllBits1();
mask(*vtxp).setOnes();
}
for (DfgVertex* const vtxp : rpoEnumeration) {
if (!handledDirectly(*vtxp)) continue;
if (!m_sccInfo.get(*vtxp)) continue;
iterate(vtxp);
UINFO(9, "Initial independent bits of "
<< vtxp << " " << vtxp->typeName()
<< " are: " << mask(*vtxp).displayed(vtxp->fileline(), "%b"));
if (!mask(*vtxp).isEqZero()) enqueueSinks(*vtxp);
UINFO(9, "Initial independent bits of " << vtxp << " " << vtxp->typeName()
<< " are: " << mask(*vtxp).toString());
if (!mask(*vtxp).isZero()) enqueueSinks(*vtxp);
}
// Propagate independent bits until no more changes are made
@ -1191,13 +1336,11 @@ class IndependentBits final : public DfgVisitor {
m_vtxp2State.at(currp).m_isOnWorkList = false;
// Should not enqueue vertices that are not in an SCC
UASSERT_OBJ(m_sccInfo.get(*currp), currp, "Vertex should be in an SCC");
// Should only enqueue packed vertices
UASSERT_OBJ(handledDirectly(*currp), currp, "Vertex should be handled directly");
// Grab current mask of item
const V3Number& currMask = mask(*currp);
const BitMask& currMask = mask(*currp);
// Remember current mask so we can check if it changed
const V3Number prevMask = currMask;
const BitMask prevMask = currMask;
UINFO(9, "Analyzing independent bits of " << currp << " " << currp->typeName());
@ -1205,19 +1348,16 @@ class IndependentBits final : public DfgVisitor {
iterate(currp);
// If mask changed, enqueue sinks
if (!prevMask.isCaseEq(currMask)) {
if (prevMask != currMask) {
UINFO(9, "Independent bits of " //
<< currp << " " << currp->typeName() << " changed" //
<< "\n form: " << prevMask.displayed(currp->fileline(), "%b")
<< "\n to: " << currMask.displayed(currp->fileline(), "%b"));
<< "\n form: " << prevMask.toString()
<< "\n to: " << currMask.toString());
enqueueSinks(*currp);
// Check the mask only ever expands (no bit goes 1 -> 0)
if (VL_UNLIKELY(v3Global.opt.debugCheck())) {
V3Number notCurr{currMask};
notCurr.opNot(currMask);
V3Number prevAndNotCurr{currMask};
prevAndNotCurr.opAnd(prevMask, notCurr);
UASSERT_OBJ(prevAndNotCurr.isEqZero(), currp, "Mask should only expand");
UASSERT_OBJ(!currMask.isContractionOf(prevMask), currp,
"Mask should only expand");
}
}
}
@ -1228,8 +1368,8 @@ public:
// returns a map from vertices to a bit mask, where a bit in the mask is
// set if the corresponding bit in that vertex is known to be independent
// of the values of vertices in the same SCC as the vertex resides in.
static std::unordered_map<const DfgVertex*, V3Number> apply(DfgGraph& dfg,
const SccInfo& sccInfo) {
static std::unordered_map<const DfgVertex*, BitMask> apply(DfgGraph& dfg,
const SccInfo& sccInfo) {
return std::move(IndependentBits{dfg, sccInfo}.m_vtxp2Mask);
}
};
@ -1239,21 +1379,21 @@ class FixUp final {
SccInfo& m_sccInfo; // The SccInfo instance
TraceDriver m_traceDriver{m_dfg, m_sccInfo};
// The independent bits map
const std::unordered_map<const DfgVertex*, V3Number> m_independentBits
const std::unordered_map<const DfgVertex*, BitMask> m_independentBits
= IndependentBits::apply(m_dfg, m_sccInfo);
size_t m_nImprovements = 0; // Number of improvements mde
size_t m_nImprovements = 0; // Number of improvements made
// Returns a bitmask set if that bit of 'vtx' is used (has a sink)
static V3Number computeUsedBits(DfgVertex& vtx) {
V3Number result{vtx.fileline(), static_cast<int>(vtx.width()), 0};
static BitMask computeUsedBits(DfgVertex& vtx) {
BitMask result{vtx};
vtx.foreachSink([&result](DfgVertex& sink) {
// If used via a Sel, mark the selected bits used
if (const DfgSel* const selp = sink.cast<DfgSel>()) {
result.opSetRange(selp->lsb(), selp->width(), '1');
result.num().opSetRange(selp->lsb(), selp->width(), '1');
return false;
}
// Used without a Sel, so all bits are used
result.setAllBits1();
result.setOnes();
return true;
});
return result;
@ -1306,20 +1446,18 @@ class FixUp final {
UASSERT_OBJ(!vtx.is<DfgSplicePacked>(), &vtx, "Should not be a splice");
// Get which bits of 'vtxp' are independent of the SCCs
const V3Number& indpBits = m_independentBits.at(&vtx);
UINFO(9, "Independent bits of '" << debugStr(vtx) << "' are "
<< indpBits.displayed(vtx.fileline(), "%b"));
const BitMask& indpBits = m_independentBits.at(&vtx);
UINFO(9, "Independent bits of '" << debugStr(vtx) << "' are " << indpBits.toString());
// Can't do anything if all bits are dependent
if (indpBits.isEqZero()) return;
if (indpBits.isZero()) return;
// Figure out which bits of 'vtxp' are used
const V3Number usedBits = computeUsedBits(vtx);
UINFO(9, "Used bits of '" << debugStr(vtx) << "' are "
<< usedBits.displayed(vtx.fileline(), "%b"));
const BitMask usedBits = computeUsedBits(vtx);
UINFO(9, "Used bits of '" << debugStr(vtx) << "' are " << usedBits.toString());
// Nothing to do if no used bits are independen (all used bits are dependent)
V3Number usedAndIndependent{vtx.fileline(), static_cast<int>(vtx.width()), 0};
usedAndIndependent.opAnd(usedBits, indpBits);
usedAndIndependent.opAnd(usedBits.num(), indpBits.num());
if (usedAndIndependent.isEqZero()) return;
// We are computing the terms to concatenate and replace 'vtxp' with
@ -1328,14 +1466,14 @@ class FixUp final {
// Iterate through consecutive used/unused ranges
FileLine* const flp = vtx.fileline();
const uint32_t width = vtx.width();
bool isUsed = usedBits.bitIs1(0); // Is current range used
bool isUsed = usedBits.num().bitIs1(0); // Is current range used
uint32_t lsb = 0; // LSB of current range
for (uint32_t msb = 0; msb < width; ++msb) {
const bool endRange = msb == width - 1 || isUsed != usedBits.bitIs1(msb + 1);
const bool endRange = msb == width - 1 || isUsed != usedBits.num().bitIs1(msb + 1);
if (!endRange) continue;
if (isUsed) {
// The range is used, compute the replacement terms
gatherTerms(termps, vtx, indpBits, msb, lsb);
gatherTerms(termps, vtx, indpBits.num(), msb, lsb);
} else {
// The range is not used, just use constant 0 as a placeholder
DfgConst* const constp = new DfgConst{m_dfg, flp, msb - lsb + 1, 0};
@ -1445,35 +1583,18 @@ public:
}
};
std::pair<std::unique_ptr<DfgGraph>, bool> //
breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
bool breakCycles(DfgGraph& dfg, V3DfgBreakCyclesContext& ctx) {
// Shorthand for dumping graph at given dump level
const auto dump = [&](int level, const DfgGraph& dfg, const std::string& name) {
if (dumpDfgLevel() >= level) dfg.dumpDotFilePrefixed("breakCycles-" + name);
};
// Can't do much with trivial things ('a = a' or 'a[1] = a[0]'), so bail
if (dfg.size() <= 2) {
UINFO(7, "Graph is trivial");
dump(9, dfg, "trivial");
++ctx.m_breakCyclesContext.m_nTrivial;
return {nullptr, false};
}
// AstNetlist/AstNodeModule user2 used as sequence numbers for temporaries
const VNUser2InUse user2InUse;
// Show input for debugging
dump(7, dfg, "input");
// We might fail to make any improvements, so first create a clone of the
// graph. This is what we will be working on, and return if successful.
// Do not touch the input graph.
std::unique_ptr<DfgGraph> resultp = dfg.clone();
// Just shorthand for code below
DfgGraph& res = *resultp;
dump(9, res, "clone");
// How many improvements have we made
size_t nImprovements = 0;
size_t prevNImprovements;
@ -1481,16 +1602,16 @@ breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
// Iterate while an improvement can be made and the graph is still cyclic
do {
// Compute SCCs
SccInfo sccInfo{res};
SccInfo sccInfo{dfg};
// Fix up independent ranges in vertices
UINFO(9, "New iteration after " << nImprovements << " improvements");
prevNImprovements = nImprovements;
const size_t nFixed = FixUp::apply(res, sccInfo);
const size_t nFixed = FixUp::apply(dfg, sccInfo);
if (nFixed) {
nImprovements += nFixed;
ctx.m_breakCyclesContext.m_nImprovements += nFixed;
dump(9, res, "FixUp");
ctx.m_nImprovements += nFixed;
dump(9, dfg, "FixUp");
}
// Validate SccInfo if in debug mode
@ -1499,42 +1620,38 @@ breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
// Congrats if it has become acyclic
if (!sccInfo.isCyclic()) {
UINFO(7, "Graph became acyclic after " << nImprovements << " improvements");
dump(7, res, "result-acyclic");
++ctx.m_breakCyclesContext.m_nFixed;
return {std::move(resultp), true};
dump(7, dfg, "result-acyclic");
++ctx.m_nFixed;
return true;
}
} while (nImprovements != prevNImprovements);
// Debug dump
if (dumpDfgLevel() >= 9) {
const SccInfo sccInfo{res};
res.dumpDotFilePrefixed("breakCycles-remaining", [&](const DfgVertex& vtx) {
const SccInfo sccInfo{dfg};
dfg.dumpDotFilePrefixed("breakCycles-remaining", [&](const DfgVertex& vtx) {
return sccInfo.get(vtx); //
});
}
// If an improvement was made, return the still cyclic improved graph
// Accounting
if (nImprovements) {
UINFO(7, "Graph was improved " << nImprovements << " times");
dump(7, res, "result-improved");
++ctx.m_breakCyclesContext.m_nImproved;
return {std::move(resultp), false};
dump(7, dfg, "result-improved");
++ctx.m_nImproved;
} else {
UINFO(7, "Graph NOT improved");
dump(7, dfg, "result-original");
++ctx.m_nUnchanged;
}
// No improvement was made
UINFO(7, "Graph NOT improved");
dump(7, res, "result-original");
++ctx.m_breakCyclesContext.m_nUnchanged;
return {nullptr, false};
return false;
}
} //namespace V3DfgBreakCycles
std::pair<std::unique_ptr<DfgGraph>, bool> //
V3DfgPasses::breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
auto pair = V3DfgBreakCycles::breakCycles(dfg, ctx);
if (pair.first) {
if (v3Global.opt.debugCheck()) V3DfgPasses::typeCheck(*pair.first);
V3DfgPasses::removeUnused(*pair.first);
}
return pair;
bool V3DfgPasses::breakCycles(DfgGraph& dfg, V3DfgContext& ctx) {
const bool res = V3DfgBreakCycles::breakCycles(dfg, ctx.m_breakCyclesContext);
if (v3Global.opt.debugCheck()) V3DfgPasses::typeCheck(dfg);
V3DfgPasses::removeUnused(dfg);
return res;
}

View File

@ -100,9 +100,8 @@ class V3DfgBreakCyclesContext final : public V3DfgSubContext {
public:
// STATE
VDouble0 m_nFixed; // Number of graphs that became acyclic
VDouble0 m_nImproved; // Number of graphs that were imporoved but still cyclic
VDouble0 m_nImproved; // Number of graphs that were improved 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
private:
@ -112,7 +111,6 @@ private:
addStat("made acyclic", m_nFixed);
addStat("improved", m_nImproved);
addStat("left unchanged", m_nUnchanged);
addStat("trivial", m_nTrivial);
addStat("changes applied", m_nImprovements);
}
};

View File

@ -126,19 +126,15 @@ class DataflowOptimize final {
std::vector<std::unique_ptr<DfgGraph>> madeAcyclicComponents;
if (v3Global.opt.fDfgBreakCycles()) {
for (auto it = cyclicComps.begin(); it != cyclicComps.end();) {
auto result = V3DfgPasses::breakCycles(**it, m_ctx);
if (!result.first) {
// No improvement, moving on.
const bool madeAcyclic = V3DfgPasses::breakCycles(**it, m_ctx);
// If not made acyclic, keep it in 'cyclicComps'
if (!madeAcyclic) {
++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 madeAcyclicComponents, delete original.
madeAcyclicComponents.emplace_back(std::move(result.first));
it = cyclicComps.erase(it);
continue;
}
// Otherwise move to 'madeAcyclicComponents'
madeAcyclicComponents.emplace_back(std::move(*it));
it = cyclicComps.erase(it);
}
}
// Merge those that were made acyclic back to the graph, this enables optimizing more

View File

@ -78,7 +78,13 @@ void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) {
&& !vVtxp->hasExtWrRefs() //
&& !vVtxp->hasModWrRefs();
VL_DO_DANGLING(vVtxp->unlinkDelete(dfg), vVtxp);
if (srcp) VL_DO_DANGLING(srcp->unlinkDelete(dfg), srcp);
if (srcp) {
srcp->foreachSource([&](DfgVertex& src) {
src.as<DfgLogic>()->setDrivesUnusedVars();
return false;
});
VL_DO_DANGLING(srcp->unlinkDelete(dfg), srcp);
}
if (delAst) {
VL_DO_DANGLING(vscp->unlinkFrBack()->deleteTree(), vscp);
++ctx.m_varsDeleted;

View File

@ -44,14 +44,9 @@ void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
// Remove redundant selects
void removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) 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&, V3DfgContext&) VL_MT_DISABLED;
// equivalent. Genuine combinational cycles can exist, so this might be
// unsuccessful. Returns true if the graph became acyclic, false otherwise.
bool breakCycles(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
// Construct binary to oneHot decoders
void binToOneHot(DfgGraph&, V3DfgBinToOneHotContext&) VL_MT_DISABLED;
// Common subexpression elimination

View File

@ -2269,7 +2269,7 @@ class V3DfgPeephole final : public DfgVisitor {
}
void visit(DfgLogAnd* const vtxp) override {
if (binary(vtxp)) return;
if (binary(vtxp) || vtxp->rhsp()->unsafe()) return;
DfgVertex* const lhsp = vtxp->lhsp();
DfgVertex* const rhsp = vtxp->rhsp();
@ -2287,11 +2287,11 @@ class V3DfgPeephole final : public DfgVisitor {
}
void visit(DfgLogIf* const vtxp) override {
if (binary(vtxp)) return;
if (binary(vtxp) || vtxp->rhsp()->unsafe()) return;
}
void visit(DfgLogOr* const vtxp) override {
if (binary(vtxp)) return;
if (binary(vtxp) || vtxp->rhsp()->unsafe()) return;
DfgVertex* const lhsp = vtxp->lhsp();
DfgVertex* const rhsp = vtxp->rhsp();
@ -2548,7 +2548,13 @@ class V3DfgPeephole final : public DfgVisitor {
if (DfgShiftL* const lShiftLp = lhsp->cast<DfgShiftL>()) {
if (!lShiftLp->hasMultipleSinks() && rhsp->dtype() == lShiftLp->rhsp()->dtype()) {
APPLYING(REPLACE_SHIFTL_SHIFTL) {
DfgAdd* const addp = make<DfgAdd>(rhsp, rhsp, lShiftLp->rhsp());
// Fold '(a << b) << c' to 'a << (b + c)'. Compute 'b + c'
// one bit wider than the amounts so it cannot overflow.
FileLine* const flp = vtxp->fileline();
const DfgDataType& sumDType = DfgDataType::packed(rhsp->width() + 1);
DfgVertex* const bp = make<DfgExtend>(flp, sumDType, lShiftLp->rhsp());
DfgVertex* const cp = make<DfgExtend>(flp, sumDType, rhsp);
DfgAdd* const addp = make<DfgAdd>(flp, sumDType, bp, cp);
replace(make<DfgShiftL>(vtxp, lShiftLp->lhsp(), addp));
return;
}
@ -2637,7 +2643,13 @@ class V3DfgPeephole final : public DfgVisitor {
if (DfgShiftR* const lShiftRp = lhsp->cast<DfgShiftR>()) {
if (!lShiftRp->hasMultipleSinks() && rhsp->dtype() == lShiftRp->rhsp()->dtype()) {
APPLYING(REPLACE_SHIFTR_SHIFTR) {
DfgAdd* const addp = make<DfgAdd>(rhsp, rhsp, lShiftRp->rhsp());
// Fold '(a >> b) >> c' to 'a >> (b + c)'. Compute 'b + c'
// one bit wider than the amounts so it cannot overflow.
FileLine* const flp = vtxp->fileline();
const DfgDataType& sumDType = DfgDataType::packed(rhsp->width() + 1);
DfgVertex* const bp = make<DfgExtend>(flp, sumDType, lShiftRp->rhsp());
DfgVertex* const cp = make<DfgExtend>(flp, sumDType, rhsp);
DfgAdd* const addp = make<DfgAdd>(flp, sumDType, bp, cp);
replace(make<DfgShiftR>(vtxp, lShiftRp->lhsp(), addp));
return;
}
@ -2890,13 +2902,13 @@ class V3DfgPeephole final : public DfgVisitor {
}
if (vtxp->dtype() == m_bitDType) {
if (isSame(condp, thenp)) { // a ? a : b becomes a | b
if (isSame(condp, thenp) && !elsep->unsafe()) { // a ? a : b becomes a | b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_COND) {
replace(make<DfgOr>(vtxp, condp, elsep));
return;
}
}
if (isSame(condp, elsep)) { // a ? b : a becomes a & b
if (isSame(condp, elsep) && !thenp->unsafe()) { // a ? b : a becomes a & b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_COND) {
replace(make<DfgAnd>(vtxp, condp, thenp));
return;
@ -2905,28 +2917,28 @@ class V3DfgPeephole final : public DfgVisitor {
}
if (vtxp->width() <= VL_QUADSIZE) {
if (isZero(thenp)) { // a ? 0 : b becomes ~a & b
if (isZero(thenp) && !elsep->unsafe()) { // a ? 0 : b becomes ~a & b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) {
DfgVertex* const maskp = replicate(vtxp, make<DfgNot>(condp, condp));
replace(make<DfgAnd>(vtxp, maskp, elsep));
return;
}
}
if (isOnes(thenp)) { // a ? 1 : b becomes a | b
if (isOnes(thenp) && !elsep->unsafe()) { // a ? 1 : b becomes a | b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) {
DfgVertex* const maskp = replicate(vtxp, condp);
replace(make<DfgOr>(vtxp, maskp, elsep));
return;
}
}
if (isZero(elsep)) { // a ? b : 0 becomes a & b
if (isZero(elsep) && !thenp->unsafe()) { // a ? b : 0 becomes a & b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) {
DfgVertex* const maskp = replicate(vtxp, condp);
replace(make<DfgAnd>(vtxp, maskp, thenp));
return;
}
}
if (isOnes(elsep)) { // a ? b : 1 becomes ~a | b
if (isOnes(elsep) && !thenp->unsafe()) { // a ? b : 1 becomes ~a | b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) {
DfgVertex* const maskp = replicate(vtxp, make<DfgNot>(condp, condp));
replace(make<DfgOr>(vtxp, maskp, thenp));
@ -2935,7 +2947,8 @@ class V3DfgPeephole final : public DfgVisitor {
}
if (DfgOr* const tOrp = thenp->cast<DfgOr>()) {
if (isSame(tOrp->lhsp(), elsep)) { // a ? b | c : b becomes b | (a & c)
if (isSame(tOrp->lhsp(), elsep)
&& !tOrp->rhsp()->unsafe()) { // a ? b | c : b becomes b | (a & c)
APPLYING(REPLACE_COND_THEN_OR_LHS) {
DfgVertex* const maskp = replicate(vtxp, condp);
DfgAnd* const andp = make<DfgAnd>(vtxp, maskp, tOrp->rhsp());
@ -2943,7 +2956,8 @@ class V3DfgPeephole final : public DfgVisitor {
return;
}
}
if (isSame(tOrp->rhsp(), elsep)) { // a ? b | c : c becomes c | (a & b)
if (isSame(tOrp->rhsp(), elsep)
&& !tOrp->lhsp()->unsafe()) { // a ? b | c : c becomes c | (a & b)
APPLYING(REPLACE_COND_THEN_OR_RHS) {
DfgVertex* const maskp = replicate(vtxp, condp);
DfgAnd* const andp = make<DfgAnd>(vtxp, maskp, tOrp->lhsp());

View File

@ -2005,6 +2005,11 @@ static void dfgSelectLogicForSynthesis(DfgGraph& dfg) {
for (DfgVertex& vtx : dfg.opVertices()) {
DfgLogic* const logicp = vtx.cast<DfgLogic>();
if (!logicp) continue;
// If drives an unused variable, synthesize it so the partial logic can be removed
if (logicp->drivesUnusedVars()) {
worklist.push_front(*logicp);
continue;
}
// Blocks corresponding to continuous assignments
if (logicp->nodep()->keyword() == VAlwaysKwd::CONT_ASSIGN) {
worklist.push_front(*logicp);
@ -2016,10 +2021,8 @@ static void dfgSelectLogicForSynthesis(DfgGraph& dfg) {
worklist.push_front(*logicp);
continue;
}
// Simple blocks driving exactly 1 variable, e.g if (rst) a = b else a = c;
if (!logicp->hasMultipleSinks() && cfg.nBlocks() <= 4 && cfg.nEdges() <= 4) {
worklist.push_front(*logicp);
}
// Blocks driving exactly 1 variable
if (!logicp->hasMultipleSinks()) worklist.push_front(*logicp);
}
// Now expand to cover all logic driving the same set of variables and mark

View File

@ -512,6 +512,7 @@ class DfgLogic final : public DfgVertexVariadic {
AstScope* const m_scopep; // The AstScope m_nodep is under, iff scoped
const std::unique_ptr<CfgGraph> m_cfgp;
std::vector<DfgVertex*> m_synth; // Vertices this logic was synthesized into
bool m_drivesUnusedVars = false; // Logic drives unused variables
bool m_selectedForSynthesis = false; // Logic selected for synthesis
bool m_nonSynthesizable = false; // Logic is not synthesizeable (by DfgSynthesis)
bool m_reverted = false; // Logic was synthesized (in part if non-synthesizable) then reverted
@ -538,6 +539,8 @@ public:
const CfgGraph& cfg() const { return *m_cfgp; }
std::vector<DfgVertex*>& synth() { return m_synth; }
const std::vector<DfgVertex*>& synth() const { return m_synth; }
bool drivesUnusedVars() const { return m_drivesUnusedVars; }
void setDrivesUnusedVars() { m_drivesUnusedVars = true; }
bool selectedForSynthesis() const { return m_selectedForSynthesis; }
void setSelectedForSynthesis() { m_selectedForSynthesis = true; }
bool nonSynthesizable() const { return m_nonSynthesizable; }

View File

@ -137,6 +137,10 @@ void EmitCBaseVisitorConst::emitCDefaultConstructor(const AstNodeModule* const m
void EmitCBaseVisitorConst::emitCFuncHeader(const AstCFunc* funcp, const AstNodeModule* modp,
bool withScope) {
if (funcp->slow()) putns(funcp, "VL_ATTR_COLD ");
if (funcp->dpiCDeclOverride()) {
putns(funcp, funcp->dpiCDecl() + ";\n");
return;
}
if (!funcp->isDestructor()) {
putns(funcp, funcp->rtnTypeVoid());
puts(" ");

View File

@ -485,7 +485,7 @@ void EmitCFunc::emitVarReset(const string& prefix, AstVar* varp, bool constructi
const auto& mapr = initarp->map();
for (const auto& itr : mapr) {
AstNode* const valuep = itr.second->valuep();
emitSetVarConstant(newPrefix + ".at(" + cvtToStr(itr.first) + ")",
emitSetVarConstant(newPrefix + ".atWrite(" + cvtToStr(itr.first) + ")",
VN_AS(valuep, Const));
}
} else if (VN_IS(dtypep, WildcardArrayDType)) {
@ -496,7 +496,7 @@ void EmitCFunc::emitVarReset(const string& prefix, AstVar* varp, bool constructi
const auto& mapr = initarp->map();
for (const auto& itr : mapr) {
AstNode* const valuep = itr.second->valuep();
emitSetVarConstant(newPrefix + ".at(" + cvtToStr(itr.first) + ")",
emitSetVarConstant(newPrefix + ".atWrite(" + cvtToStr(itr.first) + ")",
VN_AS(valuep, Const));
}
} else if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
@ -541,8 +541,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing,
depth + 1, suffix + ".atDefault()", nullptr);
} else if (VN_IS(dtypep, CDType)) {
return ""; // Constructor does it
} else if (VN_IS(dtypep, ClassRefDType)) {
return ""; // Constructor does it
} else if (const AstClassRefDType* const adtypep = VN_CAST(dtypep, ClassRefDType)) {
return adtypep->rawPointer() ? varNameProtected + suffix + " = nullptr;\n" : "";
} else if (VN_IS(dtypep, IfaceRefDType)) {
return varNameProtected + suffix + " = nullptr;\n";
} else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) {

View File

@ -732,7 +732,8 @@ public:
}
void visit(AstAssocSel* nodep) override {
iterateAndNextConstNull(nodep->fromp());
putnbs(nodep, ".at(");
const std::string atFunc = nodep->isLValue() ? ".atWrite(" : ".at(";
putnbs(nodep, atFunc);
AstAssocArrayDType* const adtypep
= VN_AS(nodep->fromp()->dtypep()->skipRefp(), AssocArrayDType);
UASSERT_OBJ(adtypep, nodep, "Associative select on non-associative type");
@ -741,7 +742,8 @@ public:
}
void visit(AstWildcardSel* nodep) override {
iterateAndNextConstNull(nodep->fromp());
putnbs(nodep, ".at(");
const std::string atFunc = nodep->isLValue() ? ".atWrite(" : ".at(";
putnbs(nodep, atFunc);
AstWildcardArrayDType* const adtypep
= VN_AS(nodep->fromp()->dtypep()->skipRefp(), WildcardArrayDType);
UASSERT_OBJ(adtypep, nodep, "Wildcard select on non-wildcard-associative type");

View File

@ -249,8 +249,37 @@ class EmitCHeader final : public EmitCConstInit {
if (const AstClass* const classp = VN_CAST(modp, Class)) {
if (!classp->isInterfaceClass() && !classp->isVirtual()) {
decorateFirst(first, section);
putns(classp, "VlClass* clone() const { return new "
+ EmitCUtil::prefixNameProtect(classp) + "(*this); }\n");
using EmbeddedCovergroupVar = std::pair<const AstClass*, const AstVar*>;
std::vector<EmbeddedCovergroupVar> embeddedCovergroupVars;
const auto hasEnclosingBackPointer = [](const AstClass* covergroupp) {
return covergroupp->exists([](const AstVar* const varp) {
const AstClassRefDType* const refp
= VN_CAST(varp->dtypep()->skipRefp(), ClassRefDType);
return refp && refp->rawPointer();
});
};
const_cast<AstClass*>(classp)->foreachMember(
[&](AstClass* const memberClassp, AstVar* const varp) {
const AstClassRefDType* const refp
= VN_CAST(varp->dtypep()->skipRefp(), ClassRefDType);
if (refp && refp->classp()->isCovergroup()
&& hasEnclosingBackPointer(refp->classp())) {
embeddedCovergroupVars.emplace_back(memberClassp, varp);
}
});
const string className = EmitCUtil::prefixNameProtect(classp);
if (embeddedCovergroupVars.empty()) {
putns(classp,
"VlClass* clone() const { return new " + className + "(*this); }\n");
} else {
putns(classp, "VlClass* clone() const { " + className + "* const clonep = new "
+ className + "(*this); ");
for (const EmbeddedCovergroupVar& item : embeddedCovergroupVars) {
puts("clonep->" + EmitCUtil::prefixNameProtect(item.first)
+ "::" + item.second->nameProtect() + " = VlNull{}; ");
}
puts("return clonep; }\n");
}
}
}
}

View File

@ -26,6 +26,7 @@
#include <algorithm>
#include <cstring>
#include <map>
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -87,6 +88,9 @@ class EmitCSyms final : EmitCBaseVisitorConst {
using ScopeModPair = std::pair<const AstScope*, AstNodeModule*>;
using ModVarPair = std::pair<const AstNodeModule*, const AstVar*>;
// Vars with more dims take the residual per-statement path.
static constexpr int VPI_TABLE_MAX_DIMS = 3;
// STATE
AstCFunc* m_cfuncp = nullptr; // Current function
AstNodeModule* m_modp = nullptr; // Current module
@ -108,10 +112,16 @@ class EmitCSyms final : EmitCBaseVisitorConst {
const bool m_dpiHdrOnly; // Only emit the DPI header
std::vector<std::string> m_splitFuncNames; // Split file names
VDouble0 m_statVarScopeBytes; // Statistic tracking
// name -> initializer rows, built in getSymCtorStmts()
std::vector<std::pair<std::string, std::vector<std::string>>> m_varTables;
// Single VlScopeTableEntry[] table for all scopes, built in getSymCtorStmts()
std::string m_scopeTableName;
std::vector<std::string> m_scopeTableRows;
// METHODS
void emitSymHdr();
void emitSymImpPreamble();
void emitVarTables();
void emitScopeHier(std::vector<std::string>& stmts, bool destroy);
void emitSymImp(const AstNetlist* netlistp);
void emitDpiHdr();
@ -176,42 +186,61 @@ class EmitCSyms final : EmitCBaseVisitorConst {
return pos != std::string::npos ? scpname.substr(pos + 1) : scpname;
}
static std::tuple<int, int, std::string> getDimensions(const AstNodeDType* const rootDtypep) {
int pdim = 0;
// Encounter order of one dtype-chain walk: unpacked (outer), then packed,
// then a ranged basic leaf.
struct VarDims final {
int pdim = 0; // Packed array dims plus a ranged basic leaf
int udim = 0;
std::string bounds;
std::vector<std::pair<int, int>> unpackedLR; // (left,right), outer-first
std::vector<std::pair<int, int>> packedLR; // (left,right), inner then leaf
// Shared order used by both boundsString() and the table-row dv[] fill.
std::vector<std::pair<int, int>> flatten() const {
std::vector<std::pair<int, int>> flat(unpackedLR);
flat.insert(flat.end(), packedLR.cbegin(), packedLR.cend());
return flat;
}
};
static VarDims getVarDims(const AstNodeDType* const rootDtypep) {
VarDims d;
// Range is always first, it's not in "C" order
for (const AstNodeDType* dtypep = rootDtypep; dtypep;) {
// Skip AstRefDType/AstTypedef, or return same node
dtypep = dtypep->skipRefp();
if (const AstNodeArrayDType* const adtypep = VN_CAST(dtypep, NodeArrayDType)) {
bounds += " ,";
bounds += std::to_string(adtypep->left());
bounds += ",";
bounds += std::to_string(adtypep->right());
if (VN_IS(dtypep, PackArrayDType))
pdim++;
else
udim++;
if (VN_IS(dtypep, PackArrayDType)) {
d.packedLR.emplace_back(adtypep->left(), adtypep->right());
++d.pdim;
} else {
d.unpackedLR.emplace_back(adtypep->left(), adtypep->right());
++d.udim;
}
dtypep = adtypep->subDTypep();
} else {
if (const AstBasicDType* const basicp = dtypep->basicp()) {
if (basicp->isRanged()) {
bounds += " ,";
bounds += std::to_string(basicp->left());
bounds += ",";
bounds += std::to_string(basicp->right());
pdim++;
d.packedLR.emplace_back(basicp->left(), basicp->right());
++d.pdim;
}
}
break; // Non-array leaf
}
}
return {pdim, udim, bounds};
return d;
}
static std::tuple<int, int, std::string> getDimensions(const AstVar* const varp) {
return getDimensions(varp->dtypep());
static VarDims dimsFor(const ScopeVarData& svd) { return getVarDims(svd.m_varp->dtypep()); }
// Only needed on the residual path; the table path uses dims.flatten() directly.
static std::string boundsString(const VarDims& d) {
std::string bounds;
for (const std::pair<int, int>& lr : d.flatten()) {
bounds += " ,";
bounds += std::to_string(lr.first);
bounds += ",";
bounds += std::to_string(lr.second);
}
return bounds;
}
static int getUnpackedElements(const AstNodeDType* rootDtypep) {
@ -310,6 +339,61 @@ class EmitCSyms final : EmitCBaseVisitorConst {
return stmt;
}
// tryBuildTableEntry() classifies each public var once, so the caller does
// not re-derive (and risk desyncing) the forceable-eligibility test.
enum class TableEntryKind : uint8_t {
TABLE_ROW, // Registered via a VlVarTableEntry[] table row
FORCEABLE_RESIDUAL, // Residual, via insertForceableVarStatement()
PLAIN_RESIDUAL, // Residual, via insertVarStatement() (+ struct/array expansion)
};
// Builds one VlVarTableEntry row for 'varp', or reports which residual
// path it must take instead.
TableEntryKind tryBuildTableEntry(const ScopeVarData& svd, const AstVar* const varp,
const AstScope* const scopep,
const std::string& modClassName, const VarDims& dims,
std::string& rowOut) const {
const int pdim = dims.pdim;
const int udim = dims.udim;
if (varp->isForceable() && forceControlSignalsAreValid(scopep, varp)) {
return TableEntryKind::FORCEABLE_RESIDUAL;
}
if (udim + pdim > VPI_TABLE_MAX_DIMS) return TableEntryKind::PLAIN_RESIDUAL;
const std::string vlEnumType = varp->vlEnumType();
if (needsEmittedEntSize(vlEnumType))
return TableEntryKind::PLAIN_RESIDUAL; // struct/union whole
// Params are often 'static constexpr' (offsetof is invalid on those);
// string params also need a runtime .c_str().
if (varp->isParam()) return TableEntryKind::PLAIN_RESIDUAL;
const std::string name = V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty));
// nameProtect() (not protect(name())) so the offsetof member matches the
// emitted struct field: a primary I/O port keeps its unprotected name
// under --protect-ids, whereas protect() would always hash it.
const std::string member = varp->nameProtect();
const std::string dir = varp->vlEnumDir();
// Flat dim (left,right) ints in varInsert() order: unpacked then packed.
std::vector<int> dv;
for (const std::pair<int, int>& lr : dims.flatten()) {
dv.push_back(lr.first);
dv.push_back(lr.second);
}
std::string row = "{\"" + name + "\", ";
row += "offsetof(" + modClassName + ", " + member + "), ";
row += vlEnumType + ", ";
row += "(" + dir + "), ";
row += std::to_string(udim) + ", " + std::to_string(pdim) + ", {";
for (int i = 0; i < VPI_TABLE_MAX_DIMS * 2; ++i) {
if (i) row += ", ";
row += std::to_string(i < static_cast<int>(dv.size()) ? dv[i] : 0);
}
row += "}}";
rowOut = row;
return TableEntryKind::TABLE_ROW;
}
static std::string insertDTypeVarStatement(const ScopeVarData& svd,
const AstScope* const scopep,
const std::string& prettyName,
@ -348,12 +432,9 @@ class EmitCSyms final : EmitCBaseVisitorConst {
const std::string prettyName
= prettyPrefix + "." + AstNode::vpiName(itemp->shortName());
const std::string cName = cPrefix + "." + itemp->nameProtect();
const std::tuple<int, int, std::string> dimensions = getDimensions(itemDTypep);
const int pdim = std::get<0>(dimensions);
const int udim = std::get<1>(dimensions);
const std::string bounds = std::get<2>(dimensions);
const VarDims dims = getVarDims(itemDTypep);
stmts.emplace_back(insertDTypeVarStatement(svd, scopep, prettyName, cName, itemDTypep,
udim, pdim, bounds)
dims.udim, dims.pdim, boundsString(dims))
+ ";");
if (const AstNodeUOrStructDType* const subp
= VN_CAST(itemDTypep->skipRefp(), NodeUOrStructDType)) {
@ -425,11 +506,9 @@ class EmitCSyms final : EmitCBaseVisitorConst {
const ScopeVarData& svd = itpair->second;
const AstScope* const scopep = svd.m_scopep;
const AstVar* const varp = svd.m_varp;
const std::tuple<int, int, std::string> dimensions = getDimensions(varp);
const int pdim = std::get<0>(dimensions);
const int udim = std::get<1>(dimensions);
const std::string bounds = std::get<2>(dimensions);
stmt += insertVarStatement(svd, scopep, varp, udim, pdim, bounds);
const VarDims dims = dimsFor(svd);
stmt
+= insertVarStatement(svd, scopep, varp, dims.udim, dims.pdim, boundsString(dims));
}
stmt += ",";
// Find __VforceVal
@ -449,11 +528,9 @@ class EmitCSyms final : EmitCBaseVisitorConst {
const ScopeVarData& svd = itpair->second;
const AstScope* const scopep = svd.m_scopep;
const AstVar* const varp = svd.m_varp;
const std::tuple<int, int, std::string> dimensions = getDimensions(varp);
const int pdim = std::get<0>(dimensions);
const int udim = std::get<1>(dimensions);
const std::string bounds = std::get<2>(dimensions);
stmt += insertVarStatement(svd, scopep, varp, udim, pdim, bounds);
const VarDims dims = dimsFor(svd);
stmt
+= insertVarStatement(svd, scopep, varp, dims.udim, dims.pdim, boundsString(dims));
}
stmt += "}";
@ -986,6 +1063,50 @@ void EmitCSyms::emitSymImpPreamble() {
needsNewLine = true;
}
if (needsNewLine) puts("\n");
// So split ctor sub-functions in other translation units can reference
// the VPI variable tables defined below.
if (!m_varTables.empty() || !m_scopeTableRows.empty()) {
for (const auto& kv : m_varTables) {
puts("extern const VlVarTableEntry " + kv.first + "[];\n");
}
if (!m_scopeTableRows.empty()) {
puts("extern const VlScopeTableEntry " + m_scopeTableName + "[];\n");
}
puts("\n");
}
}
void EmitCSyms::emitVarTables() {
if (m_varTables.empty() && m_scopeTableRows.empty()) return;
puts("\n// VPI VARIABLE/SCOPE TABLES\n");
// offsetof on the (non-standard-layout) generated module/Syms classes is well
// defined on all supported compilers but warns; suppress just here.
puts("#if defined(__GNUC__)\n");
puts("# pragma GCC diagnostic push\n");
puts("# pragma GCC diagnostic ignored \"-Winvalid-offsetof\"\n");
puts("#endif\n");
for (const auto& kv : m_varTables) {
puts("extern const VlVarTableEntry " + kv.first + "[] = {\n");
for (const std::string& row : kv.second) {
ofp()->putsNoTracking(" ");
ofp()->putsNoTracking(row);
ofp()->putsNoTracking(",\n");
}
puts("};\n");
}
if (!m_scopeTableRows.empty()) {
puts("extern const VlScopeTableEntry " + m_scopeTableName + "[] = {\n");
for (const std::string& row : m_scopeTableRows) {
ofp()->putsNoTracking(" ");
ofp()->putsNoTracking(row);
ofp()->putsNoTracking(",\n");
}
puts("};\n");
}
puts("#if defined(__GNUC__)\n");
puts("# pragma GCC diagnostic pop\n");
puts("#endif\n");
}
void EmitCSyms::emitScopeHier(std::vector<std::string>& stmts, bool destroy) {
@ -1110,21 +1231,30 @@ std::vector<std::string> EmitCSyms::getSymCtorStmts() {
+ protect("__Vconfigure") + "(" + (first ? "true" : "false") + ");");
}
add("// Setup scopes");
for (const auto& itpair : m_scopeNames) {
const ScopeData& sd = itpair.second;
std::string stmt;
stmt += protect("__Vscopep_" + sd.m_symName) + " = new VerilatedScope{this, \"";
stmt += V3OutFormatter::quoteNameControls(
VIdProtect::protectWordsIf(sd.m_prettyName, true));
stmt += "\", \"";
stmt += V3OutFormatter::quoteNameControls(protect(scopeDecodeIdentifier(sd.m_prettyName)));
stmt += "\", \"";
stmt += V3OutFormatter::quoteNameControls(sd.m_defName);
stmt += "\", ";
stmt += std::to_string(sd.m_timeunit);
stmt += ", VerilatedScope::" + sd.m_type + "};";
add(stmt);
// Every scope has the same construction shape, so all fold into one table with no
// residual; offsetof bakes the target __Vscopep_* member address into each row.
if (!m_scopeNames.empty()) {
add("// Setup scopes");
const std::string symClass = symClassName();
for (const auto& itpair : m_scopeNames) {
const ScopeData& sd = itpair.second;
std::string row
= "{offsetof(" + symClass + ", " + protect("__Vscopep_" + sd.m_symName) + "), \"";
row += V3OutFormatter::quoteNameControls(
VIdProtect::protectWordsIf(sd.m_prettyName, true));
row += "\", \"";
row += V3OutFormatter::quoteNameControls(
protect(scopeDecodeIdentifier(sd.m_prettyName)));
row += "\", \"";
row += V3OutFormatter::quoteNameControls(sd.m_defName);
row += "\", ";
row += std::to_string(sd.m_timeunit);
row += ", VerilatedScope::" + sd.m_type + "}";
m_scopeTableRows.emplace_back(std::move(row));
}
m_scopeTableName = symClass + "__VpiScopeTable";
add("VerilatedScope::scopesConstructFromTable(" + m_scopeTableName + ", "
+ std::to_string(m_scopeNames.size()) + ", this);");
}
emitScopeHier(stmts, false);
@ -1154,45 +1284,99 @@ std::vector<std::string> EmitCSyms::getSymCtorStmts() {
}
}
// It would be less code if each module inserted its own variables. Someday.
// Relies on m_scopeVars being sorted so each VPI scope's vars are
// contiguous. Tables are deduplicated by row content, not by scope, so
// distinct scopes mapping to the same C++ class (e.g. --public-flat-rw)
// still share a table when their rows are identical.
if (!m_scopeVars.empty()) {
add("// Setup public variables");
for (const auto& itpair : m_scopeVars) {
const ScopeVarData& svd = itpair.second;
const AstScope* const scopep = svd.m_scopep;
const AstVar* const varp = svd.m_varp;
const std::tuple<int, int, std::string> dimensions = getDimensions(varp);
const int pdim = std::get<0>(dimensions);
const int udim = std::get<1>(dimensions);
const std::string bounds = std::get<2>(dimensions);
// Keyed by '\0'-joined row text so identical rows share one table.
std::unordered_map<std::string, std::string> tableByRows;
int tableCounter = 0;
const std::pair<bool, std::string> isForceControlResult = isForceControlSignal(varp);
const bool currentSignalIsForceControlSignal = isForceControlResult.first;
const std::string baseSignalName = isForceControlResult.second;
if (currentSignalIsForceControlSignal && baseSignalIsPublic(scopep, baseSignalName)
&& baseSignalIsValid(scopep, varp, baseSignalName)) {
continue;
}
auto it = m_scopeVars.cbegin();
while (it != m_scopeVars.cend()) {
const std::string scopeName = it->second.m_scopeName;
const AstScope* const instScopep = it->second.m_scopep;
const AstNodeModule* const modp = it->second.m_modp;
const std::string modClassName = EmitCUtil::prefixNameProtect(modp);
if (varp->isForceable() && forceControlSignalsAreValid(scopep, varp)) {
const std::string stmt
= insertForceableVarStatement(svd, scopep, varp, udim, pdim, bounds) + ";";
add(stmt);
} else {
const std::string stmt
= insertVarStatement(svd, scopep, varp, udim, pdim, bounds) + ";";
add(stmt);
if (const AstNodeUOrStructDType* const sdtypep
= VN_CAST(varp->dtypeSkipRefp(), NodeUOrStructDType)) {
if (!sdtypep->packed()) {
addUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty,
protect(varp->name()), sdtypep);
std::vector<std::string> rows;
std::vector<std::string> residual;
for (; it != m_scopeVars.cend() && it->second.m_scopeName == scopeName; ++it) {
const ScopeVarData& svd = it->second;
const AstScope* const scopep = svd.m_scopep;
UASSERT(scopep == instScopep && svd.m_modp == modp,
"VPI scope '" << scopeName << "' spans multiple C++ instances");
const AstVar* const varp = svd.m_varp;
const VarDims dims = dimsFor(svd);
// Force-control signals fold into the base signal's forceable insert.
const std::pair<bool, std::string> fc = isForceControlSignal(varp);
if (fc.first && baseSignalIsPublic(scopep, fc.second)
&& baseSignalIsValid(scopep, varp, fc.second)) {
continue;
}
std::string row;
const TableEntryKind kind
= tryBuildTableEntry(svd, varp, scopep, modClassName, dims, row);
switch (kind) {
case TableEntryKind::TABLE_ROW: rows.emplace_back(row); break;
case TableEntryKind::FORCEABLE_RESIDUAL: {
const std::string bounds = boundsString(dims);
residual.emplace_back(insertForceableVarStatement(svd, scopep, varp, dims.udim,
dims.pdim, bounds)
+ ";");
break;
}
case TableEntryKind::PLAIN_RESIDUAL: {
const std::string bounds = boundsString(dims);
residual.emplace_back(
insertVarStatement(svd, scopep, varp, dims.udim, dims.pdim, bounds) + ";");
if (const AstNodeUOrStructDType* const sdtypep
= VN_CAST(varp->dtypeSkipRefp(), NodeUOrStructDType)) {
if (!sdtypep->packed()) {
addUOrStructMemberVars(residual, svd, scopep, svd.m_varBasePretty,
protect(varp->name()), sdtypep);
}
} else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) {
addUnpackedArrayUOrStructMemberVars(residual, svd, scopep,
svd.m_varBasePretty,
protect(varp->name()), varp->dtypep());
}
} else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) {
addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty,
protect(varp->name()), varp->dtypep());
break;
}
default: v3fatalSrc("Bad case");
}
}
if (!rows.empty()) {
const size_t rowCount = rows.size();
std::string key;
for (const std::string& r : rows) {
key += r;
key += '\0';
}
const auto itt = tableByRows.find(key);
std::string tableName;
if (itt != tableByRows.end()) {
tableName = itt->second;
} else {
tableName = modClassName + "__VpiVarTable" + std::to_string(tableCounter++);
tableByRows.emplace(std::move(key), tableName);
m_varTables.emplace_back(tableName, std::move(rows));
}
std::string call
= protect("__Vscopep_" + scopeName) + "->varsInsertFromTable(" + tableName
+ ", " + std::to_string(rowCount) + ", &("
+ VIdProtect::protectIf(instScopep->nameDotless(), instScopep->protect())
+ "));";
add(call);
}
for (const std::string& r : residual) add(r);
}
}
@ -1308,6 +1492,7 @@ void EmitCSyms::emitSymImp(const AstNetlist* netlistp) {
openNewOutputSourceFile(symClassName(), true, true, "Symbol table implementation internals");
emitSymImpPreamble();
emitVarTables();
// Constructor
const std::string ctorArgs
@ -1434,8 +1619,12 @@ void EmitCSyms::emitDpiHdr() {
if (!firstImp++) puts("\n// DPI IMPORTS\n");
putsDecoration(nodep, "// DPI import" + sourceLoc + "\n");
}
putns(nodep, "extern " + nodep->rtnTypeVoid() + " " + nodep->nameProtect() + "("
+ cFuncArgs(nodep) + ");\n");
if (nodep->dpiCDeclOverride()) {
putns(nodep, "extern " + nodep->dpiCDecl() + ";\n");
} else {
putns(nodep, "extern " + nodep->rtnTypeVoid() + " " + nodep->nameProtect() + "("
+ cFuncArgs(nodep) + ");\n");
}
}
puts("\n");

View File

@ -1050,8 +1050,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
if (nodep->packed()) puts("packed ");
{
puts("{\n");
VL_RESTORER(m_packedps);
m_packedps.clear();
VL_RESTORER_CLEAR(m_packedps);
for (AstMemberDType* itemp = nodep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) {
iterateConst(itemp);
@ -1131,7 +1130,12 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
}
puts(" ");
if (nodep->propStrength() != VPropStrength::DEFAULT) {
puts(nodep->propStrength().ascii());
puts("(");
}
iterateConstNull(nodep->propp());
if (nodep->propStrength() != VPropStrength::DEFAULT) puts(")");
puts("\n");
}
void visit(AstPExpr* nodep) override { iterateConst(nodep->bodyp()); }

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