Compare commits

...
282 Commits
Author SHA1 Message Date
Geza Lore e4cc6cadca Fix %m in hierarchical models (#8575)
The model of a hierarchical block, or of a --lib-create library, is
named after the %m of its instance in the parent model, but %m within it
also used to include the name of its top module (l2-name within the
partition), adding an extra level per block. %m now matches across
non-hierarchical, --hierarchical and manual --lib-create builds.

before: top.t.u_sub.sub.u_leaf.leaf
after:  top.t.u_sub.u_leaf
2026-10-02 14:20:41 +01:00
Kornel Uriasz 93e9660ea5 Internals: V3Randomize cleanup. No functional change intended (#8574)
Signed-off-by: Kornel Uriasz <[email protected]>
2026-10-02 07:54:51 -04:00
Geza Lore 5ba2a4836d Tests: Fix DFG tests using same name for models 2026-10-02 09:32:27 +01:00
Wilson Snyder bdfb2e8db1 Fix --hierarchical parameter specialization name collisions (#8555) (#8565)
Fixes #8565.
2026-10-01 08:04:56 -04:00
Geza Lore c5b6f4c80f CI: Add HammerBlade to RTLMeter runs 2026-10-01 12:49:21 +01:00
Nick Brereton 0fc4310048 Fix VPI rows for dynamic and class type variables (#8564) 2026-10-01 07:48:56 -04:00
Wilson Snyder e04f350109 Revert: Fix --hierarchical parameter specialization name collisions (#8555 revert) (#8565). 2026-10-01 07:48:36 -04:00
verilator-bot[bot] 33a6d05382 Apply 'make format' 2026-10-01 11:03:14 +00:00
Nazar Zibilyuk ea536a54b8 Fix waking --sc wrapper when DPI/delay makes work runnable (#8394) (#8534)
Fixes #8394
2026-10-01 12:01:17 +01:00
Wilson Snyder 71c6638ccf Commentary: Changes update 2026-09-30 20:33:45 -04:00
Wilson Snyder 5ac4a7eb22 Commentary 2026-09-30 20:33:39 -04:00
Wilson Snyder f8bf67111e Tests: Fix some scenarios labels. No test coverage change. 2026-09-30 20:25:54 -04:00
Nick Brereton 368f59d5f2 Optimize exhaustive case if-chains with an unconditional else (#8554) 2026-09-30 20:17:00 -04:00
Marco Bartoli 2a82047e4e Fix --hierarchical parameter specialization name collisions (#8555) 2026-09-30 20:15:47 -04:00
Ethan Sifferman 6438d977fa Fix dehash of a suffix after a hashed name (#8556) 2026-09-30 19:51:19 -04:00
Nick Brereton af1c668b99 Fix vpi_put_value crash on NULL value pointer (#8553) 2026-09-30 18:44:09 -04:00
Saqib Khan 5458bd2e77 Fix circular class inheritance hang (#8324) (#8545) 2026-09-30 18:42:58 -04:00
Martijn Wobbes 61289bc80b Fix cascading errors after unsupported class dot reference in parameter value (#8551)
Signed-off-by: Martijn Wobbes <[email protected]>
2026-09-30 17:53:42 -04:00
Aditya Shevade 1edf8f986a Fix real values and unenforced rand/randc type-eligibility rules in constraints (#8273) (#8275) (#8276) 2026-09-30 17:52:43 -04:00
Nick Brereton 226cf2f850 Fix stack depth calculations over calls (#8544) 2026-09-30 13:28:55 -04:00
Artur Bieniek d4e4b1d795 Optimize assertion control queries (#8547) 2026-09-30 11:32:56 -04:00
Kristof Marien 055b870e11 Fix derived class construction resets inherited per-instance rand_mode state (#8527) (#8528) 2026-09-30 11:07:49 -04:00
Marco Bartoli c686a25f54 Fix V3Const__gen.o not rebuilt when headers change (#8550) 2026-09-30 11:07:22 -04:00
Krzysztof Bieganski 1f0e80dbd6 Optimize sched-initial/static/final: call splitCheck once per sub-function (#8549)
Signed-off-by: Krzysztof Bieganski <[email protected]>
2026-09-30 10:26:54 -04:00
Wilson Snyder a419e91574 Fix $c() inside a DPI export (#8402). [Nazar Zibilyuk]
Fixes #8402.
2026-09-29 19:17:34 -04:00
Nick Brereton 0993179e7e Tests: Add VPI tests for combinational, cross-scope and savable access (#8533) 2026-09-29 19:16:22 -04:00
Wilson Snyder 9637c62dfe Tests: Repair new test (#8538 repair) 2026-09-29 17:45:58 -04:00
Wilson Snyder 39796ca954 Commentary: Changes update 2026-09-29 17:40:07 -04:00
Noam Gallmann 7f9a8695c1 Fix crash on class handle passed to string port (#8536) (#8538) 2026-09-29 17:25:52 -04:00
Kristof Marien feb1f64be8 Fix extern class method rejects matching virtual interface argument type #8525 (#8526) 2026-09-29 16:53:54 -04:00
Marco Bartoli 68439fbb72 Fix nonblocking event trigger never firing in suspendable processes (#8485) 2026-09-29 16:48:09 -04:00
Marco Bartoli 21bae44b03 Fix verilator_coverage report to align with get_coverage() (#8522) (#8524) (#8531) 2026-09-29 16:36:33 -04:00
em2machine bbdc2cce08 Internals: fixDeadRefs, findLiveCloneOf, TypeTableDeadRefVisitor removal (#8492 partial) (#8537) 2026-09-29 16:33:17 -04:00
Hongseok Choi c2479ce807 Fix function reference into generate case block (#5312) (#8532) 2026-09-29 12:59:23 -04:00
Igor Zaworski 3fd0948cf2 Fix of queue pop return value access (#8529)
Fixes #8511,  #8380 repair

Signed-off-by: Igor Zaworski <[email protected]>
2026-09-29 16:45:28 +01:00
Kornel Uriasz 38f011ed2c Fix enums inside randomize() with (#8517)
Signed-off-by: Kornel Uriasz <[email protected]>
2026-09-29 10:01:43 -04:00
em2machine 58fc34e6df Internals: Remove AstRefDType ledger parallel scaffolding in favor of AstRefDType tags (#8492 partial) (#8513) 2026-09-29 09:03:16 -04:00
Saqib Khan 9d9d367302 Support waiting on clocking block event through modport (#8403) (#8530) 2026-09-29 08:50:57 -04:00
Wilson Snyder 647b2056ed Fix verilator_coverage reports rounding incomplete coverage to 100.0% (#8523).
Fixes #8523.
2026-09-29 08:50:20 -04:00
Wilson Snyder 2c32da73c3 Commentary: Changes update 2026-09-29 08:37:08 -04:00
Geza Lore b3139df707 Fix emitting public parameters (#8520)
Fixes #8520, #8512 repair.
2026-09-29 12:23:40 +01:00
Geza Lore da4903f721 Fix bear version detection if uninstalled
Fallout from b8068fad46 The CI 'test' jobs
has no 'bear' installed but attempted to run due to caching the result
of the version check on disk. Check every time instead of caching.
2026-09-29 11:20:45 +01:00
Kristof Marien 640607a0d0 Fix rand_mode(0) not preserving dynamic array of struct values used in a dist constraint (#8508) (#8509) 2026-09-28 22:23:01 -04:00
Wilson Snyder f83ed971e2 Use reversable base64 for -generate-key 2026-09-28 20:11:31 -04:00
Wilson Snyder b8068fad46 CI: Make twice to see errors easier 2026-09-28 19:59:13 -04:00
Wilson Snyder bafdae1847 Internals: Favor constexpr for constants. No functional change intended. (#8521) 2026-09-28 19:58:56 -04:00
Marco Bartoli bde1b98ee0 Support coverpoint bin 'with' filters (#8518) 2026-09-28 18:29:54 -04:00
Kamil Danecki a949674a6b Support nested array and associative array element member access in constraint 3/4 - associative arrays (#8244)
Signed-off-by: Kamil Danecki <[email protected]>
2026-09-28 08:08:57 -04:00
Ethan Sifferman 510ae7b38b Fix duplicate struct declaration when inlining (#8515) 2026-09-28 09:41:56 +01:00
verilator-bot[bot] f5f9ddef9c Apply 'make format' 2026-09-28 01:06:50 +00:00
Marco Bartoli 4ab229e659 Support wildcard bins (#8516) 2026-09-27 21:06:09 -04:00
Michael Bedford Taylor 2c441d853c Optimize DFG temporary sharing across module instances (#8456) 2026-09-28 00:46:27 +01:00
Wilson Snyder c30604222f Tests: Fix stale file reference 2026-09-27 14:31:42 -04:00
Marco Bartoli a933e723c2 Support covergroup fixed-size bin arrays (#8514) 2026-09-27 13:57:38 -04:00
Wilson Snyder 6956befd29 Tests: Reformat some tests to mostly verilog-format standard. No test functional change. 2026-09-27 13:41:11 -04:00
Wilson Snyder f691cd1936 Change --protect to use SHA512 per IEEE 1735. 2026-09-27 13:34:11 -04:00
Wilson Snyder 626333df02 Commentary: Changes update 2026-09-27 13:28:00 -04:00
Geza Lore 43c3c88c38 Internals: AstCoverToggle is not an AstNodeStmt (#6280)
It is more akin to an AstAlways, being a module item.
2026-09-27 13:46:58 +01:00
Geza Lore 9e2c30faab CI: Move Code coverage to 26.04 2026-09-27 13:35:03 +01:00
Geza Lore 348b9b6209 Internals: Make the constant pool a regular package (#8512)
The constant pool is now an ordinary package, created with the netlist
and instantiated under $root like any other package, and most special
handling has been removed, including AstConstPool. Lookups go through
V3ConstPool, which has a singleton instance owned by V3Global. Static
methods on V3Common form the public interfce to add/find constant pool
entries.

With that, the constant pool is usable at any stage during compilation,
so enum and dimension tables created by V3Width, and enum value tables
created by V3Randomize now also live in the constant pool instead of
$unit, so identical tables are shared.

Associative array constants are handled separately from unpacked
tables, which used to be broken but unused.

Emitted constant pool variables use direct initialization, and
`constinit` with C++20 where the type allows it. This ensures we don't
change a run-time in a way that would result in unintended code size
increase.

-fno-merge-const-pool, which was introduced years ago but never prompted
a bug report is deprecated and has no effect.

This is also prep for future work.
2026-09-27 13:25:41 +01:00
Geza Lore d234caafc1 Optimize through package variables in Dfg 2026-09-26 15:01:32 +01:00
Francesco Urbani eec929e40e Support empty queue literals as function and task arguments (#8501) (#8502) 2026-09-26 08:11:49 -04:00
verilator-bot[bot] d1eb78f4e8 Apply 'make format' 2026-09-26 11:03:09 +00:00
Geza Lore 4c4d9cc7d2 CI: Fix previous commit 2026-09-26 12:01:57 +01:00
Geza Lore 98250f6cd2 CI: Format as the Verilator bot when running on main repo 2026-09-26 11:56:58 +01:00
Geza Lore f2114d3dc3 Internals: Make node type hashes stable with Ast changes
Instead of hashing node types by type id (which can change when
adding/removing Ast node types), astgen a stable hash based on node
name. This makes output more stable through Ast node
additions/deletions. Largely to help with 'make test-diff', should have
no user noticeable impact.
2026-09-26 10:37:56 +01:00
Geza Lore 33d63ac0b0 Internals: Identify $unit package by netlist pointer 2026-09-26 07:48:56 +01:00
em2machine 435af6afe6 Internals: Coverage cleanups before UAF fix (#8494) 2026-09-25 18:54:53 -04:00
Ethan Sifferman f6ff5b4cfb Fix elaboration of classes reached through deferred references (#8440) (#8510) 2026-09-25 17:34:47 -04:00
Marco Bartoli 4e29ecbf44 Fix disable fork and wait fork affecting pending NBA updates (#8483) 2026-09-25 16:35:51 -04:00
Deanyone Su 8b663f9c84 Optimize modport resolution with many interface instances (#8496) (#8497) 2026-09-25 14:38:18 -04:00
Marco Bartoli 3a76b29cb3 Add --coverage-max-bins and --coverage-max-real-bins and optimize covergroup bin generation (#8499) 2026-09-25 13:23:46 -04:00
Geza Lore 2e5e34d426 Internals: Set fabricated variable lifetimes
Set implicit net and traced coverage var lifetimes. No functional change.
2026-09-25 16:33:53 +01:00
Ryszard Rozak 0e0ce2be52 Fix mis-setting of process state from FINISHED to KILLED (#8506)
Signed-off-by: Ryszard Rozak <[email protected]>
2026-09-25 09:43:55 -04:00
Ryszard Rozak 765d05acf8 Fix semaphore waiting on killed process (#8505)
Signed-off-by: Ryszard Rozak <[email protected]>
2026-09-25 09:27:36 -04:00
Ryszard Rozak ba8e0a1437 Set status of finished process immediately (#8503)
Signed-off-by: Ryszard Rozak <[email protected]>
2026-09-25 09:26:22 -04:00
Geza Lore 84dec259c8 Fix constant oversized shifts in V3Premit
- Arithmetic right shift used the MSB of the C++ type as the sign bit,
  instead of the MSB at the Verilog width.
- Constant shift amounts with their top bit set were treated as
  negative, so the oversized shift was emitted as undefined behavior.
- Shifts of an impure operand were replaced by zero, discarding the
  side effects of the operand.
2026-09-25 14:12:00 +01:00
Geza Lore f2dbf210a8 Fix masking of right shifted values with dirty upper bits (#8500)
Fixes #8500
2026-09-25 11:12:30 +01:00
Geza Lore 1c94fbb5d6 Internals: Remove code dead after recent refactoring (#8498) 2026-09-25 08:30:34 +02:00
Kristof Marien 4202862472 Fix disabled random dynamic array of structs referenced in a constraint (#8454) (#8455) 2026-09-24 20:44:09 -04:00
Geza Lore 02546791da Internals: Remove V3LinkDot::linkDotArrayed (#8495)
Follow up to #8476 and #8493, now that elaboration fully expands
instance arrays, linkDotArrayed's only remaining purpose was to name
AstBegin blocks created after V3Width.

Fix up earlier passes to name all Begin blocks they create, or avoid
creating AstBegin in the first place if possible.

One warning about assigning to a modport input of a generic interface
port had to move into LinkLValue (previously this only used to be
reported by linkDotArrayed, as the type/modportness only resolved in
V3Param, and the lvalueness is subsequently only known in LinkLValue).

Then remove linkDotArrayed and simplify V3LinkDot.cpp
2026-09-24 22:41:24 +01:00
Geza Lore 9e03fed56b Support multi-dim interface instance arrays and interface array ports (#8493)
Companion to #8476, and this now moves all instance array and interface
array/modport array port expansion into elaboration. Fixes #2675.

Arrays of interface instances and interface array ports are now split
into their elements during elaboration, instead of after V3Width in
V3Inst::dearrayAll, which is removed.

Background: next to every interface instance 'intf ifs()', V3LinkCells
creates a companion variable 'ifs__Viftop' of type IfaceRefDType
referring to the instance, so the instantiating module can use the
interface as a value (in pin connections, virtual interface assignments,
arguments, ...) just like a module referring to it through an interface
port. For an array of instances 'intf ifs[N]()', this variable is an
unpacked array of IfaceRefDType, but an IfaceRefDType can only refer to
a single instance, so it must be split into a variable for each element,
and every use of the whole array replaced.

V3Param expands interface instance arrays like module instance arrays,
also splitting the __Viftop variable referencing the whole array into a
variable for each element cell ('ifs__BRA__i__KET____Viftop', the
companion of element cell 'ifs__BRA__i__KET__'), and splits each
interface array port into a variable for each element. The whole array
variables remain until all references to them are replaced, so V3LinkDot
can link hierarchical references to the correct specialization.

In V3Width, every reference to a whole interface array, including
hierarchical ones, is replaced by an AstInitArray of references to its
element variables, typed as the whole array, e.g. 'ifs' becomes
'{ifs[0], ifs[1], ...}. The AstInitArray keeps its own type, so its uses
are checked as those of the whole array. The whole array variables are
removed at the end of V3Width.

V3Width then treats a whole interface array replaced by an AstInitArray
as an ordinary unpacked array value:
- A constant select yields the element, a slice the selected elements.
- A pin of an element of a module instance array selects its part of an
  interface array connection like any other connection.
- A pin of an interface array port is split into a pin for each element
  port, connected left index to left index.
- A member of an element is referenced through the interface, as for
  any other interface.

This supports slices of interface arrays, and interface ports of
multi-dimensional module instance arrays connected to interface arrays.
2026-09-24 19:10:33 +01:00
Marco Bartoli 1aa5ab2e3a Support weight in coverage (#8472) 2026-09-24 14:01:45 -04:00
David Harris 88082a4e96 Support automatic bins for enum coverpoints (#8462) (#8464) 2026-09-24 09:58:22 -04:00
Geza Lore 976ec63531 Support multi dimensional module instance arrays (#2675) (#8476)
Arrays of module instances are now expanded into their element cells
during elaboration in V3Param, instead of after V3Width in
V3Inst::dearrayAll. Element cells are named as before
(name__BRA__i__KET__...), are ordered from the left index to the right
index, and hierarchical references to them resolve in linkDotParamed.

Arrays of interface instances are still expanded by V3Inst. They will
move in a follow up patch.

Algorithmically:
- V3Praram replaces an arrayed AstCell, with a scalar Cell for each
  element. linkDotParamed can resolve against these as normal.
- V3Width then fixes up the AstPins of each expanded Cell according to
  the LRM rules. Either leaving the alone to connect to all instances,
  or inserting ArraySel/Sel to connect the relevant part to the relevant
  instance.

Partial #2675
2026-09-24 10:59:41 +01:00
Marco Bartoli 26dd04c020 Fix ##0 synchronous drive overriding later drives (#8482) 2026-09-23 21:32:14 -04:00
Wilson Snyder 3d386f8fa5 Internals: Add VL_DO_DANGLING in some missing places. No functional change intended. 2026-09-23 18:27:40 -04:00
Bartłomiej Chmiel 7fd68b58ca Fix VPI discoverability of forceable unpacked struct members (#8445) 2026-09-23 14:54:55 -04:00
David Harris 729a51e26e Fix unsized condition not reduced to one bit (#8465) (#8466) 2026-09-23 14:50:16 -04:00
em2machine f82cdbeaa7 Fix typedef through a parameterized sub-interface (#7943 repair) (#8124) (#8128) 2026-09-23 14:32:23 -04:00
David Harris e6ecf3a014 Fix embedded covergroup calling an enclosing class method (#8461) (#8463) 2026-09-23 14:26:03 -04:00
Artur Bieniek e9f60f3282 Fix model failing when host has fewer cores than --threads (#8475)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-23 13:52:47 -04:00
Geza Lore f458e40060 Move module inlining after V3Scope (#8453)
This moves V3Inline after V3Scope. Not doing #2226 yet as it has some
fallout. The inlining heuristic is identical to before, but the node
counts might have shifted, so output can be perturbed.

There is a wart needing to be fixed up with how Fsm coverage is
collected, which inserts declarations into the MODULE for each instance
of that module, but we can fix it up.

Otherwise it's fairly straight forward translation of the old algorithm,
there are some quirks due to V3Scope, but nothing drastic. Moving it far
later might be hard though, because of the Fsm-like rewrites, but that's
not something we want to do anyway.

Fixes #1539
2026-09-23 18:15:46 +01:00
Noam Gallmann ceb7dde938 Fix V3OutJsonFile comma emission for nested JSON scopes (#8469) (#8474) 2026-09-23 13:03:45 -04:00
David Harris a99ced0ed4 Fix covergroup '0/'1 bin values not filling to coverpoint width (#8458) (#8460) 2026-09-23 12:50:39 -04:00
Todd Strader e9a24647d8 CI: Fix regress wording (#8471) 2026-09-23 08:29:06 -04:00
Wilson Snyder 9401876de5 Commentary: Changes update 2026-09-23 08:16:58 -04:00
Wilson Snyder 492a8f7b67 Commentary 2026-09-23 08:16:51 -04:00
David Harris fe4b3bdec4 Fix $sscanf/$fscanf returning -1 after a partial match (#8457) (#8459) 2026-09-23 05:35:17 -04:00
Marco Bartoli 39e2d8e853 Support dynamic cross layouts and fix covergroup bin exclusions priority (#8424) 2026-09-22 20:44:25 -04:00
David Harris 525d1fade3 Fix internal error on covergroup in a specialized parameterized class (#8450) (#8452) 2026-09-22 19:00:20 -04:00
Geza Lore 6eced23e84 Optimize instance port connections by aliasing rather than by assignment (#8427)
Use AstAlias instead of AstAssignW in V3Inst port connection lowering,
the same way as V3Inline would do if the instance is inlined. This
closes some of the gap between -finline and -fno-inline behaviour, and
is also required for moving V3Inline post scope, where it could not
create aliases anymore (hence required for future patch to maintain
current behaviour).

This is also a partial fix to #4698 (only when the connected expression
is a plain VarRef)
2026-09-22 20:27:21 +01:00
Tynan McAuley 0ab3915de6 Fix data race in AstTypeTable by serializing V3VariableOrder (#8451) 2026-09-22 12:49:23 -04:00
Patrick Creighton d637b665ce Synthesize successive whole-element array assignments (#8316)
Preserve previous whole-element drivers when synthesizing successive
constant-index assignments to unpacked arrays. Retain earlier values
through their assignment temporaries and keep the default while some
elements remain uncovered.

Complete the array-default tracing path exposed by this representation.
Keep public-write checks, control-flow restrictions, and scheduling
unchanged, and cover propagation and conservative fallbacks in the
existing DFG regressions.

Part of #7964; array-only follow-up to #7966.
2026-09-22 17:46:38 +01:00
Artur Bieniek f04eb8de81 Fix compound types in anonymous structs (#8449)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-22 12:41:11 -04:00
Artur Bieniek 08c9032927 Fix internal error when task without CFunc is left in the tree (#8437)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-22 11:36:20 -04:00
Wilson Snyder 4110d13131 Tests: Also check for brace-init comments 2026-09-22 09:13:42 -04:00
Wilson Snyder 5dab8ab576 Commentary 2026-09-22 09:13:31 -04:00
Wilson Snyder e63ca70d92 Tests: Add memers commented test 2026-09-22 07:21:32 -04:00
Wilson Snyder f883b1c858 Commentary. No functional change 2026-09-22 07:20:46 -04:00
Ryszard Rozak dc179243eb Tests: Fix t_constraint_global_cls_arr (#8446)
Signed-off-by: Ryszard Rozak <[email protected]>
2026-09-22 06:35:43 -04:00
Kornel Uriasz a1e6252e77 Internals: V3Randomize cleanup. No functional change intended (#8443)
Signed-off-by: Kornel Uriasz <[email protected]>
2026-09-22 06:34:32 -04:00
Jonathan Drolet e71d4e7fe5 Fix force read update sensitivity quadratic (#8431) (#8433) 2026-09-21 23:17:39 -04:00
Artur Bieniek 9e6af08c84 Fix false MULTIDRIVEN for writes in tasks never executed (#8401) 2026-09-21 20:57:40 -04:00
Kristof Marien a50aea7709 Fix streaming dynamic arrays with different element types failing to compile (#8434) (#8435) 2026-09-21 20:57:03 -04:00
Kristof Marien 9ca90f414c Fix nested random dynamic arrays internal error (#8438) (#8439) 2026-09-21 20:50:37 -04:00
Kornel Uriasz ba1961c70b Fix unique constraint with multiple arrays (#8378)
Signed-off-by: Kornel Uriasz <[email protected]>
2026-09-21 17:47:18 -04:00
dependabot[bot] 5883a5a40d CI: Bump the everything group with 4 updates (#8432) 2026-09-21 17:45:59 -04:00
Wilson Snyder d321a5fa5d Update IEEE references 2026-09-21 08:46:58 -04:00
Wilson Snyder 2180cfd635 Tests: Less aggressive waiting message 2026-09-21 08:11:21 -04:00
Wilson Snyder 232d3ecc0c Commentary: Changes update 2026-09-21 08:11:21 -04:00
Wilson Snyder 3884b3ddf3 Tests: skip t_forceable_unpacked_large (#8431 disable) 2026-09-21 08:11:21 -04:00
Kamil Danecki f954f4fd1d Support nested array element member access in constraint (#8243) 2026-09-21 08:10:53 -04:00
Geza Lore 46774fded9 Optimize away dead ports (#8426) 2026-09-21 02:02:59 +01:00
Geza Lore 3c05eef216 Fix losing coverage declarations if first in a module body (#8425) 2026-09-21 00:47:04 +01:00
Geza Lore e5384c7b68 CI: Fix yaml format 2026-09-20 23:56:59 +01:00
Geza Lore 33d1e4e685 CI: Fix more unintended cancellations 2026-09-20 23:38:41 +01:00
Geza Lore 652495a932 Fix interface modport resolution against non-scope symbol tables (#8420)
LinkDotFindVisitor queued an AstIface into m_ifaceModSyms once for every
symbol table entry it was reached through. When creating scopes,
LinkDotIfaceVisitor resolves the modport items against the AstVarScopes,
which LinkDotScopeVisitor only inserts under the entry of an AstScope. A
hierarchical block wrapper builds a second copy of the instance tree in
the symbol table, and an interface reached through that copy has no
AstVarScopes to resolve against.

Resolving is destructive (the AstModportVarRef is deleted once resolved),
so this only worked when a resolvable entry happened to be processed
first, and failed with "Modport item not found" otherwise. When creating
scopes, register from the AstScopes instead, as those are exactly the
entries that hold the AstVarScopes.

Reproduces on t_hier_block with -fno-inline, added as a test.
2026-09-20 20:52:17 +01:00
Geza Lore bef6c8afa0 CI: Fix imprecise 'onMaster' checks 2026-09-20 20:40:22 +01:00
Geza Lore 77e9938188 CI: Prevent concurrent format jobs on same ref 2026-09-20 20:40:22 +01:00
Geza Lore 8f0bd99449 CI: Fold pr-conflicting into pr-automation 2026-09-20 20:40:22 +01:00
Kaleb Barrett c4957ef206 Fix MSYS header include search to be after toolchain headers (#8422) 2026-09-20 15:14:11 -04:00
Geza Lore 2ee6f7435d CI: Fix unlabeling closed PRs canceling unrelated jobs 2026-09-20 20:08:41 +01:00
Shriyans Sahoo e71f6f3b7a Fix cache of boundary vertices in Dfg cycle breaking (#8308) 2026-09-20 17:05:56 +01:00
Geza Lore c545552cc1 Fix FSM coverage hierarchy being emitted as an absolute path (#8421)
AstNodeCoverDecl::hier() is relative to the scope the declaration is
emitted from, as V3EmitCImp builds the reported path as 'vlNamep + hierp',
and that is how V3Coverage uses it. V3FsmDetect instead set it to the
absolute scope name, so the instance path was counted twice.

This was masked whenever the owning module was inlined, as the declaration
then ended up in the top scope. With inlining disabled, an FSM in an
instance reported 'top.t.forced_wide_u.t.forced_wide_u'. In the inlined
case it reported 'top.TOP', leaking the internal top wrapper name into
user visible coverage output, rather than plain 'top'.
2026-09-20 16:21:40 +01:00
Geza Lore fcb2a22d8e Tests: Skip t_forceable_unpacked_large with dev-coverage
This test takes over 1 hour to run with code coverage collection.
2026-09-20 16:12:29 +01:00
Geza Lore 8f6d6cd13b Fix placing Always into List[VarScope] in LinkDot (#8419)
Updated order dependent tests.
2026-09-20 14:15:41 +01:00
Geza Lore dfa4ace3dd Optimize V3Scope to move nodes on the last instance of a module (#8417)
Nodes were always cloned under the new scope, with the originals deleted
at the end of the pass. Count total module instantiations up front, then
move the nodes instead of cloning them when scoping the last instance.

Required to avoid a memory regression in a follow up, but also faster
and less peak memory overall.

Output is identical.
2026-09-20 14:15:29 +01:00
Geza Lore d30487e0f7 Optimize V3Scope to gather variable reference fixups in a vector (#8418)
Each AstVarRef is visited exactly once (blocks are iterated under
their per-scope clone), and the fixups are independent, so no need
for the ordered set.
2026-09-20 14:15:07 +01:00
Marco Bartoli dc946aee2b Support enclosing cross identifiers in bin selections (#8416) 2026-09-20 08:57:14 -04:00
Geza Lore 9e3ec98461 CI: Don't skip CI on format (#8408) 2026-09-20 07:14:47 -04:00
Wilson Snyder c8b8c79ee9 Commentary: Changes update 2026-09-19 21:33:08 -04:00
Wilson Snyder 67e6d09768 Commentary 2026-09-19 21:31:14 -04:00
Wilson Snyder 3444d57929 Tests: Add VtOs.system 2026-09-19 21:31:14 -04:00
Geza Lore 8c6dd7a164 CI: Auto label on RTLMeter and Code coverage failure 2026-09-19 21:45:20 +01:00
Marco Bartoli 18ad801887 Fix covergroup constructor initialization (#8410) 2026-09-19 16:44:04 -04:00
Geza Lore 87983308c7 CI: Add job to block PR merges with pr-blocked: labels (#8415) 2026-09-19 21:18:29 +01:00
Geza Lore 23a4c8ef46 CI: Label as the CI app to trigger workflows 2026-09-19 20:50:50 +01:00
Geza Lore acb1333a85 CI: Auto label PRs with pr-blocked: improve-coverage 2026-09-19 19:59:34 +01:00
Kristof Marien c266da882d Fix crash when widthing a covergroup option through a forward class (#8351) (#8352) 2026-09-19 14:47:07 -04:00
Geza Lore ba68360819 CI: Auto label PRs with pr-blocked: fix-regression 2026-09-19 19:33:25 +01:00
Jonathan Drolet 6999fe0f29 Fix O(N^2) compile-time blowup forcing large unpacked arrays (#8395) 2026-09-19 14:22:38 -04:00
Kaleb Barrett 8fbfc82cc2 Fix issues with configuring flex during build on MacOS (#8412) 2026-09-19 13:34:30 -04:00
Geza Lore b0b4714a9d CI: Make regressions label driven (#8413) 2026-09-19 18:31:58 +01:00
Geza Lore ee51f322c7 CI: Fix previous commit 2026-09-19 18:00:57 +01:00
Geza Lore f6801633f8 CI: Add Regression passed job for status checks 2026-09-19 17:57:43 +01:00
Geza Lore f2fcacfe3d CI: Rename build-test.yml to regression.yml 2026-09-19 17:04:09 +01:00
Geza Lore 0b661a00c1 CI: Also mop up after the Regression job 2026-09-19 16:59:19 +01:00
Geza Lore 7400d28e6d CI: Don't restart RTLMeter/Coverage on unrelated label changes (#8411) 2026-09-19 16:52:27 +01:00
Geza Lore 36aa3e88c7 CI: Add PR automation job (#8203) 2026-09-19 16:30:50 +01:00
Geza Lore a9f959ff28 CI: Fix yaml format 2026-09-19 15:51:59 +01:00
Geza Lore b75ceeb121 CI: Delete skipped PR workflows 2026-09-19 15:48:24 +01:00
Kaleb Barrett d797c2009b CI: Move macOS build from 15 to 26 #8409 (#8409) 2026-09-19 13:02:48 +01:00
Marco Bartoli 014c9820de Warn on not yet implemented and ignored iff in default or transition bins (#8406) (#8407) 2026-09-18 21:34:54 -04:00
Marco Bartoli 1cefe95c85 Support iff in covergroup bins (#8406) 2026-09-18 20:29:22 -04:00
Nick Brereton a93317b6cc tests: Add VM_PREFIX_INCLUDE_DPI (#8386) 2026-09-18 17:29:53 -04:00
Jesús Alberto López Soto 724a42e31d Fix reevaluation of method calls in wait conditions (#8391) 2026-09-18 15:41:01 -04:00
Marco Bartoli 830cb633e5 Optimize covergroup bin metadata lookup (#8404) 2026-09-18 14:25:37 -04:00
Geza Lore ccb4ab9fe3 Optimize Dfg vertex input edge storage (#8313)
Store input edges of fixed arity vertices inline in the vertex class.
This reduces heap allocations, memory fragmentation, and pointer chasing
and speeds up Dfg passes. The extra branch introduced in inputEdgep() is
well predictable and profiling shows branchless alternatives are a loss.
2026-09-18 18:19:53 +01:00
Geza Lore 079422d6ae Fix Dfg driver tracing of partial assignments (#8377) (#8397)
Fixes #8377
2026-09-18 17:51:54 +01:00
Kornel Uriasz ce482bb087 Fix weighted dist constraints in presence of other constraints (#8392)
Signed-off-by: Kornel Uriasz <[email protected]>
2026-09-18 10:30:39 -04:00
Andrii Andrieiev 064ecbcb82 Fix error on non-existent interface ports (#8393)
Signed-off-by: Andrii Andrieiev <[email protected]>
2026-09-18 08:26:56 -04:00
Ryszard Rozak c25f287779 Fix copy constructor of classes with constraints (#8381) 2026-09-18 07:47:25 -04:00
Igor Zaworski 5f38b797f3 Fix access types of refs and inouts (#8380) 2026-09-18 07:46:56 -04:00
Nick Brereton 3fd16ed498 Internals: Hoist vpi_put_value_array argument validation to entry point. (#8384) 2026-09-17 18:08:16 -04:00
Geza Lore 1f9c9681a7 CI: Move RTLMeter jobs to 26.04 (#8387)
Fixes #8379
2026-09-17 22:47:06 +01:00
Geza Lore 1c4c6c9e9d Improve V3Split (#8385)
Various cleanups, simplifications and improvements:
- Build the output bottom up, eliminating AstSplitPlaceholder
- Replace the 'ignore step' pruning with removing the vertices and edges
- Treat an NBA written variable as a block input, enabling more splits
- Move instead of clone leaf statements

Overall V3Split is faster, uses less memory, can do more splits, is
simpler algorithmically, and has half the lines of code in V3Split

Most notably this can now be split, which could not be before:

```systemverilog
always @(posedge clk) begin
    a <= !a;
    if (a) b <= c;
end
```

t_x_rand_mt_stability* pin $random values, but there is one random seed
per C thread, so they depend on which thread runs the block. More
splitting moved that block onto a different thread, so force a single
MTask in those tests to keep them stable.

Some property/assert test changed hit counts due to races, but are now
closer to what might be expected.
2026-09-17 23:11:26 +02:00
Geza Lore 2d0eaab5df Add -fno-slp-vectorize if C++ compiler is clang 18 (#8383)
Partial #8379
2026-09-17 18:38:39 +01:00
Nick Brereton bc7fd47908 Internals: Prepare VPI runtime for lazy public signals. No functional change. (#8382) 2026-09-17 13:00:19 -04:00
Marco Bartoli 1aa87913c8 Support illegal_bins and ignore_bins for cross bins (#8372) 2026-09-16 17:53:15 -04:00
Artur Bieniek e2caf971de Fix NFA rings for bounded SVA delays (#8371)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-16 17:52:28 -04:00
Marco Bartoli 08cadc034d Reland and fix "Fix wide enum names" (#8344) (#8362) (#8365) (#8366) 2026-09-16 17:47:51 -04:00
Geza Lore 9f66980355 Revert "Internals: Improve V3Split (#8373)" (#8374)
This reverts commit d8a3208bea.
2026-09-16 21:44:19 +01:00
Geza Lore d8a3208bea Internals: Improve V3Split (#8373) 2026-09-16 16:25:36 -04:00
Wilson Snyder 40c102b4f8 Fix docker compilation with --trace-fst (#8370).
Fixes #8370.
2026-09-16 12:36:14 -04:00
Geza Lore 573c2e61a6 Internals: Make V3LiftExpr non optional (#8369) 2026-09-16 12:29:56 -04:00
Pawel Kojma 282b898b21 Use VIsCached for checking purity of arguments in AstCMethodHard (#8350) 2026-09-16 11:23:34 -04:00
Wilson Snyder 195d0db009 Commentary: Changes update 2026-09-15 21:36:01 -04:00
Wilson Snyder 45dbc90343 Tests: Reformat some tests to mostly verilog-format standard. No test functional change. 2026-09-15 21:35:50 -04:00
Edmund Lam 529d98918a Fix deferring parameters for class::localparam access (#7746) (#7748) 2026-09-15 21:17:26 -04:00
Artur Bieniek c9e3f59f26 Fix stale reads of continuously assigned forceable signals (#8223)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-15 21:15:37 -04:00
Bartosz Skorowski 3e30f4476a Support constant expressions in bins (#8309)
Signed-off-by: Bartosz Skorowski <[email protected]>
2026-09-15 21:15:25 -04:00
Drew Risinger e0e0011b36 Optimize recursion depth in Dfg circular driver tracing (#8272) 2026-09-15 23:23:54 +01:00
Wilson Snyder a9ddb4fab3 Revert "Fix wide enum names" (#8344) (#8362) 2026-09-15 13:42:35 -04:00
Geza Lore 3343f4d6c8 Fix V3InstrCount with recursive functions (#8361)
Fixes #8327
2026-09-15 18:30:16 +01:00
Geza Lore 0f3a84643b CI: Fix pr-notification deletion 2026-09-15 17:01:45 +01:00
Marco Bartoli 253f51f1dc Fix %p format on class members (#8360) 2026-09-15 09:43:12 -04:00
Mike Murunov 270c528afd Fix missing implicit net for bufif/notif/nmos/pmos data input (#8353) (#8354) 2026-09-14 17:31:51 -04:00
Marco Bartoli c65f312d6e Fix wide enum names (#8344) 2026-09-14 16:29:42 -04:00
Dragon-Git 377865e2a0 Fix class-qualified self reference to a typedef in a parameterized class (#8348) (#8349) 2026-09-14 10:54:15 -04:00
Artur Bieniek 6a57cf10f4 Obtimize NBA assertions by keeping all writers before readers (#8326)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-14 10:29:27 -04:00
CatalpaEel 96e6bf02c4 Optimize wide four-state constant operations in V3Number (#8333) 2026-09-14 10:12:30 -04:00
Marco Bartoli 0bad0faca6 Fix ignoring cross_auto_bin_max instead of hard error (#8340) 2026-09-13 14:59:42 -04:00
Wilson Snyder 547eb6a836 Remove deprecated --structs-packed. 2026-09-13 14:54:34 -04:00
Wilson Snyder e172e94b30 Fix pattern type matching against nested aggregates (#7304).
Fixes #7304.
2026-09-13 14:48:07 -04:00
Wilson Snyder e871c3657e Commentary: Changes update 2026-09-13 14:00:43 -04:00
Wilson Snyder 3200946bb9 Tests: Improve t_string_to_bit 2026-09-13 13:41:53 -04:00
Marco Bartoli fb8bcdc0df Fix format of signed enums (#8338) 2026-09-13 13:34:10 -04:00
Marco Bartoli 47ead4e89d Support logic and set operations on binsof (#8306) 2026-09-13 12:59:10 -04:00
Kristof Marien 298922d887 Fix parameterized class nested in a parameterized interface (#8328) (#8329) 2026-09-12 15:31:10 -04:00
Wilson Snyder 4fedb791ed Add -fno-dead-members and optimize dead class member functions. (#8330) 2026-09-12 15:22:42 -04:00
Wilson Snyder 3bf0018354 Optimize away __Vconfigure if empty function 2026-09-12 14:36:45 -04:00
Wilson Snyder d523500b9b Optimize UVM reporting functions as branch-unlikely 2026-09-12 13:54:15 -04:00
Wilson Snyder a8a50bd8a0 Commentary 2026-09-12 12:22:14 -04:00
Wilson Snyder c7b1151f13 Fix unlinked VlProcess error 2026-09-12 11:00:29 -04:00
Wilson Snyder 73957647cc Add IEEE reference to some warnings 2026-09-12 09:06:09 -04:00
Wilson Snyder d048b62c13 Fix marking inherited virtual functions (part 2) 2026-09-12 08:55:45 -04:00
Wilson Snyder c8a75e9b4d Tests: Reformat some tests to mostly verilog-format standard. No test functional change. 2026-09-12 08:38:51 -04:00
Wilson Snyder 012b647833 Fix marking inherited virtual functions 2026-09-12 08:32:53 -04:00
Wilson Snyder 005b33faa1 Commentary: Changes update 2026-09-12 08:21:01 -04:00
Todd Strader 0c574618d0 Support VPI interface references (#8081) 2026-09-12 05:38:20 -04:00
Marco Bartoli 4aeb3dbc5a Fix wide typed pattern formatting primitives (#8325) 2026-09-11 18:11:14 -04:00
Wilson Snyder bd32c0e4c1 Internals: Refactor V3Dead ftask removal to use graph (#8322) 2026-09-11 06:52:25 -04:00
Wilson Snyder 72523c3be1 Tests: Fix coverage.dat in wrong directory 2026-09-10 21:21:04 -04:00
Andrii 2421c7b73b Fix property and sequence line coverage (#8314)
Signed-off-by: Andrii Andrieiev <[email protected]>
2026-09-10 18:00:24 -04:00
Nick Brereton dada720bca Internals: Inline VerilatedContext::assertCtlGet (#8320) 2026-09-10 15:48:58 -04:00
Artur Bieniek b06bd46dc9 Avoid duplicate lookup in NFA property recursion guard (#8321)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-10 20:46:54 +02:00
Artur Bieniek 5b24226b3b Avoid unnecessary per-vertex allocations in NFA lowering (#8319)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-10 20:21:37 +02:00
Artur Bieniek 09fd15af7b Narrow NBA shadow copies for partial writes (#8266)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-10 11:38:46 -04:00
Kamil Danecki ee792e235a Support rand dynamic arrays used inside with clause of randomize (#8246)
Signed-off-by: Kamil Danecki <[email protected]>
Signed-off-by: Igor Zaworski <[email protected]>
2026-09-10 10:40:30 -04:00
Artur Bieniek 557c21a337 Support multiplicity in SVA shapes that use NFA rings (#8267)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-10 10:39:35 -04:00
Wilson Snyder 24692ad3ef Internals: Change user1-4 storage to uint64 (#8317) 2026-09-10 06:45:54 -04:00
Wilson Snyder 3c8630744d Internals: V3Dead refactor and test update. No functional change. 2026-09-10 06:43:47 -04:00
Wilson Snyder 0dd45238a7 Internals: Update verilog_format flags for recent Verible 2026-09-10 06:39:43 -04:00
Geza Lore 1d2781091c Internals: Color densely in V3Graph::weaklyConnected, and return the count
No algorithm depends on this, so can be changed, later improvement will.
No functional change.
2026-09-09 21:11:23 +01:00
Geza Lore 5bf74e34b1 Internals: Move SplitReorderBaseVisitor next to SplitVisitor
Pure code movement to minimize change in subsequent patch.
2026-09-09 13:02:42 +01:00
Wilson Snyder 599f8b8e35 Tests: Reformat some tests to mostly verilog-format standard. No test functional change. 2026-09-08 21:26:11 -04:00
Geza Lore da39cea1bd Tests: Add -fno-exceptions to t_verilated_all (#8312)
Fixes #6771
2026-09-09 00:33:36 +01:00
Drew Risinger 3e874fb4d1 Fix CMake to call Verilator perl wrapper (#8269) (#8270) (#8311)
This reimplements commit c1c19494c4.
2026-09-08 18:29:09 -04:00
Geza Lore e4f210eb38 Optimize Dfg algorithms with an open addressing hash table (#8307)
This patch introduces V3HashTable.h, which defines an open addressing,
linear probing hash table. The table implement the public V3HashSet and
V3HashMap templates, which are generic containers. The benefit of this
over std::unordered_map and std::unordered_set is far better memory
locality during lookup. (The STL containers use chaining and require a
new heap allocation for every insertion, similarly probing involves
pointer chasing on collisions).

The new data structure is use in V3DfgCache, and V3DfgCse and yields a
significant speed improvement of those passes on large designs.
2026-09-08 23:21:13 +01:00
Wilson Snyder 5bb0e9b216 Commentary: Changes update 2026-09-08 18:09:39 -04:00
Wilson Snyder 5ce1abc2b4 Tests: Fix cleaning t_a6_examples (#8270 comment) 2026-09-08 18:07:12 -04:00
Geza Lore b835308a31 Optimize AstNodeDType::skipRefp to not track the type chain (#8310) 2026-09-08 18:00:08 -04:00
Geza Lore fa30985cda Internals: Exclude UDEBUGONLY from branch coverage 2026-09-08 21:49:10 +01:00
Kornel Uriasz f8c296ce2e Support unique constraint inside std::randomize (#8241)
Signed-off-by: Kornel Uriasz <[email protected]>
2026-09-08 11:08:34 -04:00
Kornel Uriasz 47b9fba5e5 Fix array-size constraint on arrays inside base cass (#8219)
Signed-off-by: Kornel Uriasz <[email protected]>
2026-09-08 09:11:02 -04:00
Martijn Wobbes d70d1e5cd0 Fix DECLFILENAME warning on nested declarations (#8302) 2026-09-08 07:15:37 -04:00
Artur Bieniek 76df8b4a7e Fix multidimensional packed array type handling (#8235)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-07 14:04:17 -04:00
Matthew Ballance 048d18a84f Add covergroup runtime registry (#8134) 2026-09-07 14:02:03 -04:00
Artur Bieniek cd2475fbd4 Support non-overlapped property implications correctly (#8268)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-07 13:50:13 -04:00
Igor Zaworski 9c6126b834 [#99994] Fix of detecting non-inlined awaits in V3Delayed.cpp (#7982)
Signed-off-by: Igor Zaworski <[email protected]>
2026-09-07 13:46:30 -04:00
dependabot[bot] 2bffeec8b9 CI: Bump the everything group with 2 updates (#8304) 2026-09-07 10:58:37 -04:00
Marco Frank 63bcfff80a Fix array slice crash when used as a bare value (#5132) (#8262) 2026-09-06 20:57:44 -04:00
Marco Bartoli 9da900a5e2 Add initial support for solo binsof (#8298) 2026-09-06 19:51:31 -04:00
Geza Lore bbbb9c3e7c Optimize away netlist teardown when about to exit (#8294) 2026-09-06 18:08:34 -04:00
Geza Lore 849cc5aeca Internals: Move wrapTop before V3Coverage (#8291) 2026-09-06 17:34:40 -04:00
github action 8213c0ea98 Apply 'make format' [ci skip] 2026-09-06 21:19:40 +00:00
Geza Lore 62bec1bffc Internals: Use auto in UASSERT_SELFTEST (#8299) 2026-09-06 17:16:08 -04:00
Marco Bartoli 0b824f160f Fix escape strings in pattern formatting (#8297) 2026-09-06 13:22:03 -04:00
Nick Brereton 4d1f208cf0 Fix assignment pattern default fill of an array of unpacked structs (#8289) 2026-09-06 11:44:44 -04:00
Geza Lore 7cf8c5cca6 Optimize more selects in DFG (#8292) 2026-09-05 19:57:26 -04:00
Kamil Danecki 73708c55c3 Support nested array and associative array element member access in constraint 1/4 - pre-cleanup (#8237)
Signed-off-by: Kamil Danecki <[email protected]>
2026-09-05 15:33:24 -04:00
Sumanth KadiyalaandSumanth Kadiyala 3990376c57 Internals: resolved incorrect array bounds guarding (#8224)
Co-authored-by: Sumanth Kadiyala <[email protected]>
2026-09-05 15:33:06 -04:00
Geza Lore d04647f459 Fix library method argument references (#8252)
Each VCMethod now carries a signature describing the access required of
the references passed as arguments to the call. 'r' if the argument is
read, 'w' if it is fully assigned so the old value does not matter, 'm'
if it is modified (or only conditionally assigned), with a trailing '+'
repeating the preceding entry for all remaining arguments.  Signatures
are validated at compile time, and V3Broken checks the arguments of
every AstCMethodHard against them.

The incorrect references this found and that are easy to fix are
repaired in this patch. "TODO" marks method that are currently broken or
not yet fit the scheme, these will be fixed in follow up patches.

Also renames SCHED_COMMIT and SCHED_ENQUEUE to NBA_COMMIT and
NBA_ENQUEUE, and remove unused methods.
2026-09-05 19:48:07 +01:00
Matthew Ballance 338c6c2885 Fix covergroup-internal references multi-threaded ordering (#7779) (#8255) 2026-09-05 14:08:03 -04:00
Aditya Shevade a6e21e0eb7 Fix phased solve...before diversity (#8189) (#8194) 2026-09-05 14:06:57 -04:00
Geza Lore f2fe2b6f2e Optimize Dfg synthesis of simple continuous assignments (#8286) 2026-09-05 13:07:53 -04:00
Geza Lore bcf613c4fe Optimize redundant memoization in DFG CSE (#8285) 2026-09-05 12:13:11 -04:00
Geza Lore b907a1a75d Optimize DFG vertex iteration callbacks (#8284) 2026-09-05 12:12:46 -04:00
Geza Lore 87108ff943 Optimize redundant vertex cache lookups in DFG peephole (#8283) 2026-09-05 11:13:30 -04:00
Geza Lore fa59f8f302 Optimize DFG synthesis symbol table ordering (#8282) 2026-09-05 11:13:09 -04:00
Wilson Snyder 35c1a244a8 Internals: Introduce Ast{class}::Super and cleanup. No functional change intended (but possible if was wrong base used earlier) 2026-09-05 10:45:39 -04:00
Wilson Snyder 3493e409b8 Internals: Sort Ast{class} functions in src/V3AstNodes.cpp, and enforce. No functional change. 2026-09-05 10:38:18 -04:00
Wilson Snyder 4f557a4b0c Internals: Reorganize non-Ast{class} functions in src/V3AstNodes.cpp. No functional change. 2026-09-05 10:32:27 -04:00
Wilson Snyder 13c60bfede Commentary: Changes update 2026-09-05 10:10:10 -04:00
Wilson Snyder d6441c9cd3 Change JSON dumps to suppress empty strings, etc 2026-09-05 10:09:25 -04:00
Wilson Snyder 32695cf8be Tests: Enforce Ast*::dump/dumpJson member dumps 2026-09-05 10:06:14 -04:00
Wilson Snyder 5ab54f3c01 Commentary. (Restore CONTRIBUTORS from earlier rev) 2026-09-05 10:05:47 -04:00
Wilson Snyder c1c19494c4 Revert 81fafe237e: Fix CMake to call Verilator perl wrapper (#8269 revert) (#8270 revert) 2026-09-05 09:59:16 -04:00
Wilson Snyder e7b30e214e Internals: Add missing classes to dumps(), enforce with new test. No simulation change. (#8293) 2026-09-05 09:50:13 -04:00
Drew Risinger 81fafe237e Fix CMake to call Verilator perl wrapper (#8269) (#8270) 2026-09-05 09:47:15 -04:00
Wilson Snyder 80de081af0 devel release 2026-09-05 08:45:33 -04:00
1228 changed files with 59174 additions and 17268 deletions
@@ -0,0 +1,62 @@
---
# DESCRIPTION: Github actions composite action
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: Update PR label
description: >-
Add or remove a label on a pull request. Does nothing unless the pull
request is still open and still at the commit the result is for, so that a
result superseded by a push does not label/unlabel the work that replaced it.
inputs:
token:
description: "Token to label with"
required: true
pr-number:
description: "Number of the pull request"
required: true
head-sha:
description: "Commit the result is for"
required: true
label:
description: "Label to set"
required: true
set:
description: "'true' to add the label, 'false' to remove it"
required: true
runs:
using: composite
steps:
- name: Set the label
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
REPO: ${{ github.repository }}
PR: ${{ inputs.pr-number }}
SHA: ${{ inputs.head-sha }}
LABEL: ${{ inputs.label }}
SET: ${{ inputs.set }}
run: |-
# The state and head of the pull request as they are now, and whether
# it carries the label already
read -r STATE HEAD LABELLED < <(gh pr view "${PR}" \
--repo "${REPO}" \
--json state,headRefOid,labels \
--jq '"\(.state) \(.headRefOid) \(any(.labels[]; .name == env.LABEL))"')
# Only act on a change, so a run does not reapply what is already set
if [ "${STATE}" != OPEN ]; then
# Only label open PRs
echo "PR #${PR} is ${STATE}"
elif [ "${HEAD}" != "${SHA}" ]; then
# Pushed to since, so this result is stale and a new run will decide
echo "PR #${PR} has moved on from ${SHA}"
elif [ "${SET}" = true ] && [ "${LABELLED}" = false ]; then
echo "Adding '${LABEL}' to PR #${PR}"
gh pr edit "${PR}" --repo "${REPO}" --add-label "${LABEL}"
elif [ "${SET}" = false ] && [ "${LABELLED}" = true ]; then
echo "Removing '${LABEL}' from PR #${PR}"
gh pr edit "${PR}" --repo "${REPO}" --remove-label "${LABEL}"
else
echo "No change to PR #${PR}"
fi
+71 -12
View File
@@ -20,28 +20,77 @@ defaults:
shell: bash
concurrency:
# At most 1 job per branch. Auto cancel all but scheduled jobs
group: ${{ github.workflow }}-${{ github.ref }}
# At most 1 job per branch. Auto cancel all but scheduled jobs.
# Of the pull request events, only one that starts a run, or that removes the
# 'pr: dev-coverage' label to cancel one, shares the group. The rest are
# skipped (a change of another label, or a payload that does not carry the
# label, like an 'opened' delivered after it was added), and a skipped run
# must not cancel the one in progress, so they are given a unique group.
# This way adding the label starts a run, removing it cancels the in-progress
# run, and anything else has no effect. Note such an event still creates a
# run of this workflow, which is skipped, and would then hide the one in
# progress on the pull request. 'pr-run-cleanup.yml' deletes those.
# Key pull request runs on the number: 'github.ref' is the base branch once
# the pull request is closed, so unlabel events would cancel base runs.
# NOTE: The last part of the group expression must be kept in sync with the
# 'start' job condition below: it maps to 'shared' exactly those events that
# satisfy that condition, plus the 'unlabeled' event of the gating label.
group: |-
${{
github.workflow
}}-${{
(github.event_name == 'pull_request')
&& format('pr-{0}', github.event.number) || github.ref
}}-${{
(github.event_name == 'pull_request'
&& (((github.event.action == 'labeled' || github.event.action == 'unlabeled')
&& github.event.label.name != 'pr: dev-coverage')
|| (github.event.action != 'labeled'
&& github.event.action != 'unlabeled'
&& !contains(github.event.pull_request.labels.*.name, 'pr: dev-coverage'))))
&& github.run_id || 'shared'
}}
cancel-in-progress: ${{ github.event_name != 'schedule' }}
jobs:
build:
name: Build
start:
name: Start
# Only run scheduled jobs if explicitly enabled for that repo (e.g.: not on forks)
# Only run pull request jobs if labelled as needing an coverage run
# Only run pull request jobs if labelled as needing a coverage run
# Always run workflow dispatch jobs
if: |
(github.event_name == 'schedule'
&& vars.ENABLE_SCHEDULED_JOBS == 'true') ||
(github.event_name == 'pull_request'
&& contains(github.event.pull_request.labels.*.name, 'pr: dev-coverage')) ||
&& ((github.event.action == 'labeled'
&& github.event.label.name == 'pr: dev-coverage')
|| (github.event.action != 'labeled'
&& github.event.action != 'unlabeled'
&& contains(github.event.pull_request.labels.*.name, 'pr: dev-coverage')))) ||
(github.event_name == 'workflow_dispatch')
runs-on: ubuntu-slim
steps:
- name: Save the pull request number
if: ${{ github.event_name == 'pull_request' }}
run: echo "${{ github.event.number }}" > pr-number.txt
- name: Upload the pull request number
if: ${{ github.event_name == 'pull_request' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: pr-number
path: pr-number.txt
overwrite: true
build:
name: Build
needs: start
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
dev-gcov: true
runs-on: ubuntu-24.04
runs-on: ubuntu-26.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
@@ -55,7 +104,7 @@ jobs:
archive: ${{ needs.build.outputs.archive }}
cc: gcc
dev-gcov: true
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
suite: ${{ matrix.test }}${{ matrix.num }}
strategy:
fail-fast: false
@@ -69,7 +118,7 @@ jobs:
name: Publish results to codecov.io
needs: test
if: ${{ contains(needs.*.result, 'success') && !cancelled() }}
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -88,7 +137,7 @@ jobs:
find obj_coverage -type f | paste -sd, | sed "s/^/files=/" >> "$GITHUB_OUTPUT"
- name: Upload to codecov.io
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7
with:
disable_file_fixes: true
disable_search: true
@@ -102,7 +151,7 @@ jobs:
name: Prepare HTML report
needs: [build, test]
if: ${{ contains(needs.*.result, 'success') && !cancelled() }}
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
- name: Download repository archive
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
@@ -150,6 +199,16 @@ jobs:
path: report-artifact
name: coverage-report
- name: Upload coverage status
if: ${{ github.event_name == 'pull_request' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
path: repo/coverage-status.txt
name: coverage-status
overwrite: true
# Not written if the coverage could not be read, see the script above
if-no-files-found: ignore
- name: Upload PR notification
if: ${{ github.event_name == 'pull_request' }}
uses: ./repo/.github/actions/upload-pr-notification
@@ -163,7 +222,7 @@ jobs:
name: Create issue on failure
needs: [publish-codecov, prepare-report]
if: ${{ github.event_name == 'schedule' && github.repository == 'verilator/verilator' && github.run_attempt == 1 && failure() && !cancelled() }}
runs-on: ubuntu-24.04
runs-on: ubuntu-slim
steps:
# Creating issues requires elevated privilege
- name: Generate access token
+3 -3
View File
@@ -64,10 +64,10 @@ jobs:
type=raw,value=latest,enable=${{ inputs.add_latest_tag == true }}
- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
with:
buildkitd-flags: --debug
@@ -78,7 +78,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and Push to Docker
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
with:
context: ${{ env.build_context }}
+33 -4
View File
@@ -15,16 +15,36 @@ defaults:
run:
working-directory: repo
concurrency:
# At most 1 run per branch: the job pushes what 'make format' changed, so
# concurrent runs on the same branch race on that push. Cancel the run in
# progress, as the newer one formats a tree that already holds its commits.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
format:
runs-on: ubuntu-24.04
name: Ubuntu 24.04 | format
steps:
# On the main repository, push as the Verilator CI app.
# Forks do not have the app, so fall back to GITHUB_TOKEN there.
- name: Generate access token
id: generate-token
if: ${{ github.repository == 'verilator/verilator' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
owner: verilator
repositories: verilator
permission-contents: write
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
path: repo
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
- name: Install packages for build
uses: ./repo/.github/actions/install-deps
@@ -32,9 +52,18 @@ jobs:
stage: format
- name: Configure git identity
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APP_SLUG: ${{ steps.generate-token.outputs.app-slug }}
run: |
git config --global user.email "[email protected]"
git config --global user.name "github action"
if [ -n "$APP_SLUG" ]; then
user_id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
git config --global user.email "${user_id}+${APP_SLUG}[bot]@users.noreply.github.com"
git config --global user.name "${APP_SLUG}[bot]"
else
git config --global user.email "[email protected]"
git config --global user.name "github action"
fi
- name: Configure
run: |
@@ -55,6 +84,6 @@ jobs:
- name: Push
run: |-
if [ -n "$(git status --porcelain)" ]; then
git commit . -m "Apply 'make format' [ci skip]" &&
git commit . -m "Apply 'make format'" &&
git push origin
fi
+2 -2
View File
@@ -69,11 +69,11 @@ jobs:
- name: Deploy to GitHub Pages
id: deploy-1
continue-on-error: true
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5
- name: Deploy to GitHub Pages (retry)
id: deploy-2
if: steps.deploy-1.outcome == 'failure'
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5
notify:
name: Notify
+281
View File
@@ -0,0 +1,281 @@
---
# DESCRIPTION: Github actions config
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
# Drives the automation of a pull request.
name: Maintenance - PR automation
on:
pull_request_target: # 'pull_request' can have no write permissions
types: [opened, synchronize, reopened, closed]
workflow_run: # To react to the result of a workflow on a pull request
workflows: ["Code coverage", "RTLMeter", "Regression"]
types: [completed]
push: # A pull request can start conflicting when its base branch moves
branches: [master]
permissions:
contents: read # To check out the local action, everything else uses the CI app token
defaults:
run:
shell: bash
# One job per event source, to avoid startup churn. Use separate gated steps for actions
jobs:
pr-event:
name: PR event
if: |
github.repository == 'verilator/verilator'
&& github.event_name == 'pull_request_target'
runs-on: ubuntu-slim
steps:
# Label as the CI app, and not with 'github.token', as events from the
# latter do not create workflow runs, so the labels would drive nothing
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
permission-pull-requests: write
- name: "Add 'pr: regression' label on open"
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
if: ${{ github.event.action == 'opened' || github.event.action == 'reopened' }}
run: |-
gh pr edit "${{ github.event.number }}" \
--repo "${{ github.repository }}" \
--add-label 'pr: regression'
- name: "Optionally add 'pr: dev-coverage' label on open"
if: ${{ github.event.action == 'opened' || github.event.action == 'reopened' }}
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |-
# Grab changed files
FILES=$(gh api --paginate \
"repos/${{ github.repository }}/pulls/${{ github.event.number }}/files" \
--jq '.[].filename')
echo "Files changed:"
echo "${FILES}"
# Add label if src or include chagned
if grep -q -E '^(src|include)/' <<< "${FILES}"; then
gh pr edit "${{ github.event.number }}" \
--repo "${{ github.repository }}" \
--add-label 'pr: dev-coverage'
fi
- name: "Remove all 'pr*' labels on close"
if: ${{ github.event.action == 'closed' }}
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |-
# Filter in 'jq', so an empty result is not an error, as it is with 'grep'
LABELS=$(gh api \
"repos/${{ github.repository }}/issues/${{ github.event.number }}/labels?per_page=100" \
--jq '[.[].name | select(startswith("pr"))] | join(",")')
if [ -n "${LABELS}" ]; then
echo "Removing labels: ${LABELS}"
gh pr edit "${{ github.event.number }}" \
--repo "${{ github.repository }}" \
--remove-label "${LABELS}"
fi
conflicting:
name: Conflict check
# A pull request can start conflicting when its own head moves, or when the
# base branch moves under it, so sweep the open pull requests on both.
if: |
github.repository == 'verilator/verilator'
&& (github.event_name == 'push'
|| (github.event_name == 'pull_request_target'
&& github.event.action == 'synchronize'))
runs-on: ubuntu-slim
steps:
# Label as the CI app, and not with 'github.token', as events from the
# latter do not create workflow runs, so the labels would drive nothing
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
permission-pull-requests: write
- name: "Update the 'pr-blocked: needs-rebase' label"
uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # 3.1.0
with:
dirtyLabel: "pr-blocked: needs-rebase"
repoToken: ${{ steps.generate-token.outputs.token }}
regression-complete:
name: Regression complete
if: |
github.repository == 'verilator/verilator'
&& github.event_name == 'workflow_run'
&& github.event.workflow_run.name == 'Regression'
&& github.event.workflow_run.event == 'pull_request'
&& (github.event.workflow_run.conclusion == 'success'
|| github.event.workflow_run.conclusion == 'failure')
runs-on: ubuntu-slim
steps:
# Label as the CI app, and not with 'github.token', as events from the
# latter do not create workflow runs, so the labels would drive nothing
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
permission-actions: read
permission-pull-requests: write
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
sparse-checkout: .github/actions/update-pr-label
- name: Get the pull request number
id: pr
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |-
# The run tells us which pull request it was for
if ! gh run download "${{ github.event.workflow_run.id }}" --name pr-number; then
echo "Run has no 'pr-number' artifact"
exit 0
fi
echo "number=$(cat pr-number.txt)" >> "$GITHUB_OUTPUT"
- name: "Update the 'pr-blocked: fix-regression' label"
if: ${{ steps.pr.outputs.number }}
uses: ./.github/actions/update-pr-label
with:
token: ${{ steps.generate-token.outputs.token }}
pr-number: ${{ steps.pr.outputs.number }}
head-sha: ${{ github.event.workflow_run.head_sha }}
label: "pr-blocked: fix-regression"
set: ${{ github.event.workflow_run.conclusion == 'failure' }}
rtlmeter-complete:
name: RTLMeter complete
if: |
github.repository == 'verilator/verilator'
&& github.event_name == 'workflow_run'
&& github.event.workflow_run.name == 'RTLMeter'
&& github.event.workflow_run.event == 'pull_request'
&& (github.event.workflow_run.conclusion == 'success'
|| github.event.workflow_run.conclusion == 'failure')
runs-on: ubuntu-slim
steps:
# Label as the CI app, and not with 'github.token', as events from the
# latter do not create workflow runs, so the labels would drive nothing
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
permission-actions: read
permission-pull-requests: write
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
sparse-checkout: .github/actions/update-pr-label
- name: Get the pull request number
id: pr
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |-
# The run tells us which pull request it was for
if ! gh run download "${{ github.event.workflow_run.id }}" --name pr-number; then
echo "Run has no 'pr-number' artifact"
exit 0
fi
echo "number=$(cat pr-number.txt)" >> "$GITHUB_OUTPUT"
- name: "Update the 'pr-blocked: fix-rtlmeter' label"
if: ${{ steps.pr.outputs.number }}
uses: ./.github/actions/update-pr-label
with:
token: ${{ steps.generate-token.outputs.token }}
pr-number: ${{ steps.pr.outputs.number }}
head-sha: ${{ github.event.workflow_run.head_sha }}
label: "pr-blocked: fix-rtlmeter"
set: ${{ github.event.workflow_run.conclusion == 'failure' }}
coverage-complete:
name: Coverage complete
if: |
github.repository == 'verilator/verilator'
&& github.event_name == 'workflow_run'
&& github.event.workflow_run.name == 'Code coverage'
&& github.event.workflow_run.event == 'pull_request'
&& (github.event.workflow_run.conclusion == 'success'
|| github.event.workflow_run.conclusion == 'failure')
runs-on: ubuntu-slim
steps:
# Label as the CI app, and not with 'github.token', as events from the
# latter do not create workflow runs, so the labels would drive nothing
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.VERILATOR_CI_ID }}
private-key: ${{ secrets.VERILATOR_CI_KEY }}
permission-actions: read
permission-pull-requests: write
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
sparse-checkout: .github/actions/update-pr-label
- name: Get the pull request number
id: pr
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |-
# The run tells us which pull request it was for
if ! gh run download "${{ github.event.workflow_run.id }}" --name pr-number; then
echo "Run has no 'pr-number' artifact"
exit 0
fi
echo "number=$(cat pr-number.txt)" >> "$GITHUB_OUTPUT"
- name: "Update the 'pr-blocked: fix-dev-coverage' label"
if: ${{ steps.pr.outputs.number }}
uses: ./.github/actions/update-pr-label
with:
token: ${{ steps.generate-token.outputs.token }}
pr-number: ${{ steps.pr.outputs.number }}
head-sha: ${{ github.event.workflow_run.head_sha }}
label: "pr-blocked: fix-dev-coverage"
set: ${{ github.event.workflow_run.conclusion == 'failure' }}
- name: Get the line coverage
id: coverage
if: ${{ github.event.workflow_run.conclusion == 'success' }}
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |-
# Whether every line the patch touches is covered
if ! gh run download "${{ github.event.workflow_run.id }}" --name coverage-status; then
echo "Run has no 'coverage-status' artifact"
exit 0
fi
echo "covered=$(cat coverage-status.txt)" >> "$GITHUB_OUTPUT"
- name: "Update the 'pr-blocked: improve-coverage' label"
if: ${{ steps.pr.outputs.number && steps.coverage.outputs.covered }}
uses: ./.github/actions/update-pr-label
with:
token: ${{ steps.generate-token.outputs.token }}
pr-number: ${{ steps.pr.outputs.number }}
head-sha: ${{ github.event.workflow_run.head_sha }}
label: "pr-blocked: improve-coverage"
set: ${{ steps.coverage.outputs.covered != 'true' }}
+52
View File
@@ -0,0 +1,52 @@
---
# DESCRIPTION: Github actions config
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
# Fails while a pull request carries a 'pr-blocked:*' label, so that it can be
# used as a required status check to hold up the merge. The labels themselves
# are set by hand, or by 'pr-automation.yml'.
#
# Note this runs from the pull request's own merge commit, so a pull request
# can change the check itself, but any such change shows up in its review.
#
# It also runs on a push, and not only on a label change, as a check run is
# reported against the head commit, and so every head needs one of its own.
name: Maintenance - PR blocked
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
permissions:
pull-requests: read
defaults:
run:
shell: bash
concurrency:
group: ${{ github.workflow }}-${{ github.event.number }}
cancel-in-progress: true
jobs:
not-blocked:
name: Not blocked
runs-on: ubuntu-slim
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: "Check for 'pr-blocked:*' labels"
run: |-
# Note these are the labels the pull request has now, and not the ones
# in 'github.event', which is a snapshot that can be stale when
# several are changed in one go
BLOCKED=$(gh pr view "${{ github.event.number }}" \
--repo "${{ github.repository }}" \
--json labels \
--jq '[.labels[].name | select(startswith("pr-blocked:"))] | join(", ")')
if [ -n "${BLOCKED}" ]; then
echo "::error::${BLOCKED}"
exit 1
fi
echo "Not blocked"
-28
View File
@@ -1,28 +0,0 @@
---
# Build and push verilator docker image when tags are pushed to the repository.
# The following variable(s) must be configured in the github repository:
# DOCKER_HUB_NAMESPACE: docker hub namespace.
# The following secrets must be configured in the github repository:
# DOCKER_HUB_USER: user name for logging into docker hub
# DOCKER_HUB_ACCESS_TOKEN: docker hub access token.
name: Maintainance - PR conflict check
on:
workflow_dispatch:
push:
pull_request_target:
types: [synchronize]
permissions: {}
jobs:
main:
runs-on: ubuntu-26.04
permissions:
contents: read
pull-requests: write
steps:
- name: check if prs are dirty
uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # 3.1.0
with:
dirtyLabel: "pr-blocked: needs-rebase"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
+42
View File
@@ -0,0 +1,42 @@
---
# DESCRIPTION: Github actions config
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
# Deletes the skipped runs of the label gated workflows on pull requests.
#
# GitHub shows only the latest run of a workflow on a pull request, so a run
# that was skipped hides an earlier run that is actually still in progress, and
# the pull request then reads as though that workflow had finished and
# everything is green. Every label change creates a run of every workflow that
# triggers on label events, and label events cannot be filtered by label name
# under 'on', so these runs cannot be prevented. Deleting them restores the
# in-progress run in the UI.
name: Maintenance - PR run cleanup
on:
workflow_run:
workflows: ["Code coverage", "Regression", "RTLMeter"]
types: [completed]
permissions:
actions: write
defaults:
run:
shell: bash
jobs:
delete-skipped-run:
name: Delete skipped run
if: |
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'skipped'
runs-on: ubuntu-slim
steps:
- name: Delete run
env:
GH_TOKEN: ${{ github.token }}
run: |-
echo "Deleting skipped '${{ github.event.workflow_run.name }}' run #${{ github.event.workflow_run.run_number }}"
gh api --method DELETE "repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}"
@@ -2,13 +2,14 @@
# DESCRIPTION: Github actions config
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: Regressions
name: Regression
on:
push:
branches-ignore:
- 'dependabot/**' # Avoid duplicates: only run the PR, not the push
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
workflow_dispatch:
schedule:
- cron: '0 0 * * 0' # weekly
@@ -22,14 +23,77 @@ defaults:
working-directory: repo
concurrency:
# At most 1 job per branch. Auto cancel on pull requests and on all forks
group: ${{ github.workflow }}-${{ github.ref }}
# At most 1 job per branch. Auto cancel on pull requests and on all forks.
# Of the pull request events, only one that starts a run, or that removes the
# 'pr: regression' label to cancel one, shares the group. The rest are
# skipped (a change of another label, or a payload that does not carry the
# label, like an 'opened' delivered after it was added), and a skipped run
# must not cancel the one in progress, so they are given a unique group.
# This way adding the label starts a run, removing it cancels the in-progress
# run, and anything else has no effect. Note such an event still creates a
# run of this workflow, which is skipped, and would then hide the one in
# progress on the pull request. 'pr-run-cleanup.yml' deletes those.
# Key pull request runs on the number: 'github.ref' is the base branch once
# the pull request is closed, so unlabel events would cancel base runs.
# NOTE: The last part of the group expression must be kept in sync with the
# 'start' job condition below: it maps to 'shared' exactly those events that
# satisfy that condition, plus the 'unlabeled' event of the gating label.
group: |-
${{
github.workflow
}}-${{
(github.event_name == 'pull_request')
&& format('pr-{0}', github.event.number) || github.ref
}}-${{
(github.event_name == 'pull_request'
&& (((github.event.action == 'labeled' || github.event.action == 'unlabeled')
&& github.event.label.name != 'pr: regression')
|| (github.event.action != 'labeled'
&& github.event.action != 'unlabeled'
&& !contains(github.event.pull_request.labels.*.name, 'pr: regression'))))
&& github.run_id || 'shared'
}}
cancel-in-progress: ${{ github.event_name == 'pull_request' || github.repository != 'verilator/verilator' }}
jobs:
start:
name: Start
# Only run pull request jobs if labelled as needing a regression run.
# Always run push, workflow dispatch and scheduled jobs
if: |
(github.event_name == 'pull_request'
&& ((github.event.action == 'labeled'
&& github.event.label.name == 'pr: regression')
|| (github.event.action != 'labeled'
&& github.event.action != 'unlabeled'
&& contains(github.event.pull_request.labels.*.name, 'pr: regression')))) ||
(github.event_name == 'push') ||
(github.event_name == 'workflow_dispatch') ||
(github.event_name == 'schedule')
runs-on: ubuntu-slim
defaults:
run:
working-directory: . # The workflow default is not checked out here
steps:
- name: Enabled
run: echo "Regression is enabled for this run"
- name: Save the pull request number
if: ${{ github.event_name == 'pull_request' }}
run: echo "${{ github.event.number }}" > pr-number.txt
- name: Upload the pull request number
if: ${{ github.event_name == 'pull_request' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: pr-number
path: pr-number.txt
overwrite: true
build-2604-gcc:
name: Build | 26.04 | gcc
needs: start
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
@@ -38,6 +102,7 @@ jobs:
build-2604-clang:
name: Build | 26.04 | clang
needs: start
uses: ./.github/workflows/reusable-build.yml
with:
cc: clang
@@ -47,6 +112,7 @@ jobs:
build-2404-gcc:
name: Build | 24.04 | gcc
needs: start
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
@@ -55,6 +121,7 @@ jobs:
build-2404-clang:
name: Build | 24.04 | clang
needs: start
uses: ./.github/workflows/reusable-build.yml
with:
cc: clang
@@ -63,22 +130,25 @@ jobs:
build-2204-gcc:
name: Build | 22.04 | gcc
needs: start
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
runs-on: ubuntu-22.04
sha: ${{ github.sha }}
build-macos-15-clang:
name: Build | macos-15 | clang
build-macos-26-clang:
name: Build | macos-26 | clang
needs: start
uses: ./.github/workflows/reusable-build.yml
with:
cc: clang
runs-on: macos-15
runs-on: macos-26
sha: ${{ github.sha }}
build-windows:
name: Build | windows-2025-vs2026 | msvc
needs: start
runs-on: windows-2025-vs2026
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -167,4 +237,29 @@ jobs:
lint-py:
name: Lint Python
needs: start
uses: ./.github/workflows/reusable-lint-py.yml
passed:
name: Regression tests passed # This job is used as a required status check on PRs
if: ${{ !cancelled() && needs.start.result != 'skipped' }}
needs:
- start
- build-2604-gcc
- build-2604-clang
- build-2404-gcc
- build-2404-clang
- build-2204-gcc
- build-macos-26-clang
- build-windows
- test-2604-gcc
- test-2604-clang
- test-2404-gcc
- test-2404-clang
- test-2204-gcc
- lint-py
runs-on: ubuntu-slim
steps:
- name: Check all needed jobs succeeded
working-directory: .
run: jq -e 'all(.result == "success")' <<< '${{ toJSON(needs) }}'
+2 -1
View File
@@ -36,7 +36,8 @@ jobs:
- name: Set up Python venv
uses: ./repo/.github/actions/setup-venv
with:
save: ${{ github.ref == 'refs/heads/master' }}
# Check the event too: 'github.ref' is the base branch on a closed pull request
save: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/master' }}
- name: Lint
run: |-
+3 -2
View File
@@ -92,7 +92,8 @@ jobs:
- name: Set up Python venv
uses: ./repo/.github/actions/setup-venv
with:
save: ${{ github.ref == 'refs/heads/master' }}
# Check the event too: 'github.ref' is the base branch on a closed pull request
save: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/master' }}
- name: Test
id: run-test
@@ -125,7 +126,7 @@ jobs:
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 }}
retention-days: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/master' && 7 || 3 }}
- name: Fail job if a test failed
if: ${{ steps.run-test.outcome == 'failure' && !cancelled() }}
+59 -15
View File
@@ -22,8 +22,36 @@ defaults:
shell: bash
concurrency:
# At most 1 job per branch. Auto cancel all but scheduled jobs
group: ${{ github.workflow }}-${{ github.ref }}
# At most 1 job per branch. Auto cancel all but scheduled jobs.
# Of the pull request events, only one that starts a run, or that removes the
# 'pr: rtlmeter' label to cancel one, shares the group. The rest are
# skipped (a change of another label, or a payload that does not carry the
# label, like an 'opened' delivered after it was added), and a skipped run
# must not cancel the one in progress, so they are given a unique group.
# This way adding the label starts a run, removing it cancels the in-progress
# run, and anything else has no effect. Note such an event still creates a
# run of this workflow, which is skipped, and would then hide the one in
# progress on the pull request. 'pr-run-cleanup.yml' deletes those.
# Key pull request runs on the number: 'github.ref' is the base branch once
# the pull request is closed, so unlabel events would cancel base runs.
# NOTE: The last part of the group expression must be kept in sync with the
# 'start' job condition below: it maps to 'shared' exactly those events that
# satisfy that condition, plus the 'unlabeled' event of the gating label.
group: |-
${{
github.workflow
}}-${{
(github.event_name == 'pull_request')
&& format('pr-{0}', github.event.number) || github.ref
}}-${{
(github.event_name == 'pull_request'
&& (((github.event.action == 'labeled' || github.event.action == 'unlabeled')
&& github.event.label.name != 'pr: rtlmeter')
|| (github.event.action != 'labeled'
&& github.event.action != 'unlabeled'
&& !contains(github.event.pull_request.labels.*.name, 'pr: rtlmeter'))))
&& github.run_id || 'shared'
}}
cancel-in-progress: ${{ github.event_name != 'schedule' }}
jobs:
@@ -36,14 +64,30 @@ jobs:
(github.event_name == 'schedule'
&& vars.ENABLE_SCHEDULED_JOBS == 'true') ||
(github.event_name == 'pull_request'
&& contains(github.event.pull_request.labels.*.name, 'pr: rtlmeter')) ||
&& ((github.event.action == 'labeled'
&& github.event.label.name == 'pr: rtlmeter')
|| (github.event.action != 'labeled'
&& github.event.action != 'unlabeled'
&& contains(github.event.pull_request.labels.*.name, 'pr: rtlmeter')))) ||
(github.event_name == 'workflow_dispatch') ||
(github.event_name == 'push')
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
outputs:
old-sha: ${{ steps.start.outputs.old-sha }}
cases: ${{ steps.cases.outputs.cases }}
steps:
- name: Save the pull request number
if: ${{ github.event_name == 'pull_request' }}
run: echo "${{ github.event.number }}" > pr-number.txt
- name: Upload the pull request number
if: ${{ github.event_name == 'pull_request' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: pr-number
path: pr-number.txt
overwrite: true
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -70,7 +114,7 @@ jobs:
cc: gcc
ccwarn: false
install: true
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
sha: ${{ github.sha }}
build-clang-new:
@@ -81,7 +125,7 @@ jobs:
cc: clang
ccwarn: false
install: true
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
sha: ${{ github.sha }}
build-gcc-old:
@@ -93,7 +137,7 @@ jobs:
cc: gcc
ccwarn: false
install: true
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
sha: ${{ needs.start.outputs.old-sha }}
build-clang-old:
@@ -105,7 +149,7 @@ jobs:
cc: clang
ccwarn: false
install: true
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
sha: ${{ needs.start.outputs.old-sha }}
run-gcc:
@@ -121,7 +165,7 @@ jobs:
uses: ./.github/workflows/reusable-rtlmeter-run.yml
with:
tag: gcc
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
cc: gcc
verilator-archive-new: ${{ needs.build-gcc-new.outputs.archive }}
verilator-archive-old: ${{ needs.build-gcc-old.outputs.archive }}
@@ -148,7 +192,7 @@ jobs:
uses: ./.github/workflows/reusable-rtlmeter-run.yml
with:
tag: clang
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
cc: clang
verilator-archive-new: ${{ needs.build-clang-new.outputs.archive }}
verilator-archive-old: ${{ needs.build-clang-old.outputs.archive }}
@@ -175,7 +219,7 @@ jobs:
uses: ./.github/workflows/reusable-rtlmeter-run.yml
with:
tag: gcc-hier
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
cc: gcc
verilator-archive-new: ${{ needs.build-gcc-new.outputs.archive }}
verilator-archive-old: ${{ needs.build-gcc-old.outputs.archive }}
@@ -200,7 +244,7 @@ jobs:
(github.event_name == 'pull_request' && (contains(needs.*.result, 'success') && !contains(needs.*.result, 'failure')))
|| (github.event_name != 'pull_request' && (contains(needs.*.result, 'success') || contains(needs.*.result, 'failure')))
)}}
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
outputs:
# The run tags, derived from the 'run-*' dependency job names
run-tags: ${{ steps.tags.outputs.tags }}
@@ -283,7 +327,7 @@ jobs:
# dashboard UI currently assumes there is only one data point per
# calendar day. Results from reruns can be imported manually if needed.
if: ${{ github.event_name == 'schedule' && github.repository == 'verilator/verilator' && github.run_attempt == 1 && contains(needs.*.result, 'success') && !cancelled() }}
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
- name: Download combined results
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
@@ -334,7 +378,7 @@ jobs:
name: Prepare Pull Request results
needs: combine-results
if: ${{ github.event_name == 'pull_request' && github.repository == 'verilator/verilator' && needs.combine-results.result == 'success' }}
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
permissions:
actions: read
steps:
@@ -391,7 +435,7 @@ jobs:
name: Create issue on failure
needs: publish-scheduled-results
if: ${{ github.event_name == 'schedule' && github.repository == 'verilator/verilator' && github.run_attempt == 1 && failure() && !cancelled() }}
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
steps:
# Creating issues requires elevated privilege
- name: Generate access token
+7 -6
View File
@@ -72,7 +72,7 @@ top-of-file comment.
- Build in the source tree: `autoconf && ./configure && make -j8`. Configure with
`--enable-ccwarn` so a new compiler warning stops the build.
- Run one test from the repository root: `test_regress/t/t_<name>.py`.
- Run the full regression with `make test`. The complete suite requires
- Run the full regression tests with `make test`. The complete suite requires
configuring with `--enable-longtests` (works on every OS, including macOS).
______________________________________________________________________
@@ -93,8 +93,8 @@ ______________________________________________________________________
- [ ] Ran `make format` (clang-format), `make cppcheck`, and `make lint-py`;
self-reviewed the diff for leftover debug code, stale comments, and
copy-paste errors.
- [ ] Ran the full regression on at least one OS before submitting. Partial runs
are fine during development, but the submitted PR is expected to pass every
- [ ] Ran the full regression tests on at least one OS before submitting. Partial
runs are fine during development, but the submitted PR is expected to pass every
test.
- [ ] Validated the code changes against recommendations in the AGENTS.md files.
- [ ] Did not edit `docs/CONTRIBUTORS` (humans only) or `Changes` (maintainer
@@ -143,6 +143,7 @@ The API you choose determines which test must accompany the change.
## Commits
- Subject line is short and imperative and conventionally ends with the PR number:
`Support property case (#7721)`. A body is optional and common for non-trivial
changes.
- Subject line is short and imperative and conventionally ends with the
issue number being fixed. No need for the PR number this will be added
during git squash. e.g `Support property case (#7721)`. The commit body message is
optional and common for non-trivial changes.
+31 -2
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.052
VERSION 5.053
HOMEPAGE_URL https://verilator.org
LANGUAGES CXX
)
@@ -93,7 +93,36 @@ if(OBJCACHE_ENABLED)
endif()
find_package(BISON)
find_package(FLEX)
# Prefer the header installed with the selected generator, including through
# symlinks such as Homebrew's bin/flex. Explicit cache entries take precedence.
find_program(
FLEX_EXECUTABLE
NAMES flex win-flex win_flex
DOC "Path to the flex executable"
)
if(NOT FLEX_EXECUTABLE)
message(
FATAL_ERROR
"Cannot find Flex; set FLEX_EXECUTABLE to its executable path"
)
endif()
get_filename_component(flex_real_executable "${FLEX_EXECUTABLE}" REALPATH)
get_filename_component(flex_bin_dir "${flex_real_executable}" DIRECTORY)
find_path(
FLEX_INCLUDE_DIR
FlexLexer.h
PATHS "${flex_bin_dir}/../include" "${flex_bin_dir}"
NO_DEFAULT_PATH
DOC "Path to the flex headers"
)
find_path(FLEX_INCLUDE_DIR FlexLexer.h)
if(NOT FLEX_INCLUDE_DIR)
message(
FATAL_ERROR
"Cannot find FlexLexer.h; set FLEX_INCLUDE_DIR to its directory"
)
endif()
mark_as_advanced(FLEX_EXECUTABLE FLEX_INCLUDE_DIR)
# Build
#set_property(GLOBAL PROPERTY JOB_POOLS one_job=1)
+151 -3
View File
@@ -10,6 +10,154 @@ The changes in each Verilator version are described below. The
contributors that suggested or implemented a given issue are shown in []. Thanks!
Verilator 5.053 devel
==========================
**Other:**
* Add covergroup runtime registry (#8134). [Matthew Ballance]
* Add initial support for solo `binsof` (#8298). [Marco Bartoli]
* Add `-fno-dead-members` and optimize dead class member functions (#8830).
* Add `--coverage-max-bins` and `--coverage-max-real-bins` (#8499). [Marco Bartoli]
* Remove deprecated `--structs-packed`.
* Change internal module inlining to after V3Scope (#1539) (#8453). [Geza Lore, Testorrent USA, Inc.]
* Change JSON dumps to suppress empty strings, etc.
* Change `--protect` to use SHA512 per IEEE 1735.
* Support multi-dimensional module instance arrays (#2675) (#8476). [Geza Lore, Testorrent USA, Inc.]
* Support VPI interface references (#8081). [Todd Strader]
* Support nested array and associative array element member access in constraint 1/4 - pre-cleanup (#8237). [Kamil Danecki, Antmicro Ltd.]
* Support unique constraint inside std::randomize (#8241). [Kornel Uriasz, Antmicro Ltd.]
* Support nested array and associative array element member access in constraint (#8244). [Kamil Danecki, Antmicro Ltd.]
* Support rand dynamic arrays used inside `with` clause of randomize (#8246). [Kamil Danecki, Antmicro Ltd.]
* Support multiplicity in SVA shapes that use NFA rings (#8267). [Artur Bieniek, Antmicro Ltd.]
* Support non-overlapped property implications correctly (#8268). [Artur Bieniek, Antmicro Ltd.]
* Support logic and set operations on binsof (#8306). [Marco Bartoli]
* Support `illegal_bins` and `ignore_bins` for cross bins (#8372). [Marco Bartoli]
* Support `iff` in covergroup bins (#8406) (#8407). [Marco Bartoli]
* Support enclosing cross identifiers in bin selections (#8416). [Marco Bartoli]
* Support dynamic cross layouts and fix covergroup bin exclusions priority (#8424). [Marco Bartoli]
* Support automatic bins for enum coverpoints (#8462) (#8464). [David Harris]
* Support empty queue literals as function and task arguments (#8501) (#8502). [Francesco Urbani]
* Support covergroup fixed-size bin arrays (#8514). [Marco Bartoli]
* Support wildcard bins (#8516). [Marco Bartoli]
* Support coverpoint bin `with` filters (#8518). [Marco Bartoli]
* Support waiting on clocking block event through modport (#8403) (#8530). [Saqib Khan]
* Optimize NBA shadow copies for partial writes (#8266). [Artur Bieniek, Antmicro Ltd.]
* Optimize recursion depth in DFG circular driver tracing (#8272). [Drew Risinger]
* Optimize DFG synthesis symbol table ordering (#8282). [Geza Lore, Testorrent USA, Inc.]
* Optimize redundant vertex cache lookups in DFG peephole (#8283). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG vertex iteration callbacks (#8284). [Geza Lore, Testorrent USA, Inc.]
* Optimize redundant memoization in DFG CSE (#8285). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG synthesis of simple continuous assignments (#8286). [Geza Lore, Testorrent USA, Inc.]
* Optimize more selects in DFG (#8292). [Geza Lore, Testorrent USA, Inc.]
* Optimize netlist teardown when about to exit (#8294). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG algorithms with an open addressing hash table (#8307). [Geza Lore, Testorrent USA, Inc.]
* Optimize data-type type chain recursion (#8310). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG vertex input edge storage (#8313). [Geza Lore, Testorrent USA, Inc.]
* Optimize successive whole-element array assignments (#8316). [Patrick Creighton]
* Optimize unnecessary per-vertex allocations in NFA lowering (#8319). [Artur Bieniek, Antmicro Ltd.]
* Optimize duplicate lookup in NFA property recursion guard (#8321). [Artur Bieniek, Antmicro Ltd.]
* Optimize NBA assertions by keeping all writers before readers (#8326). [Artur Bieniek, Antmicro Ltd.]
* Optimize wide four-state constant operations in V3Number (#8333). [CatalpaEel]
* Optimize split pass (#8385). [Geza Lore, Testorrent USA, Inc.]
* Optimize covergroup bin metadata lookup (#8404). [Marco Bartoli]
* Optimize dead ports (#8426). [Geza Lore, Testorrent USA, Inc.]
* Optimize instance port connections by aliasing rather than by assignment (#8427). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG temporary sharing across module instances (#8456). [Michael Bedford Taylor]
* Optimize modport resolution with many interface instances (#8496) (#8497). [Deanyone Su]
* Optimize covergroup bin generation (#8499). [Marco Bartoli]
* Optimize through-package variables in DFG. [Geza Lore, Testorrent USA, Inc.]
* Optimize assertion control queries (#8547). [Artur Bieniek, Antmicro Ltd.]
* Optimize sched-initial/static/final split to once per sub-function (#8549). [Krzysztof Bieganski, Antmicro Ltd.]
* Optimize exhaustive case if-chains with an unconditional else (#8554). [Nick Brereton]
* Move module inlining after V3Scope (#8453) (#1539). [Geza Lore, Testorrent USA, Inc.]
* Fix array slice crash when used as a bare value (#5132) (#8262). [Marco Frank]
* Fix function reference into generate case block (#5312) (#8532). [Hongseok Choi]
* Fix pattern type matching against nested aggregates (#7304).
* Fix covergroup-internal references multi-threaded ordering (#7779) (#8255). [Matthew Ballance]
* Fix typedef through a parameterized sub-interface (#7943 repair) (#8124) (#8128). [em2machine]
* Fix detecting non-inlined awaits in V3Delayed.cpp (#7982). [Igor Zaworski, Antmicro Ltd.]
* Fix phased `solve...before` diversity (#8189) (#8194). [Aditya Shevade]
* Fix array-size constraint on arrays inside base class (#8219). [Kornel Uriasz, Antmicro Ltd.]
* Fix multidimensional packed array type handling (#8235). [Artur Bieniek, Antmicro Ltd.]
* Fix library method argument references (#8252). [Geza Lore, Testorrent USA, Inc.]
* Fix CMake to call Verilator Perl wrapper (#8269) (#8270) (#8311). [Drew Risinger]
* Fix real values and unenforced rand/randc type-eligibility rules in constraints (#8273) (#8275) (#8276). [Aditya Shevade]
* Fix assignment pattern default fill of an array of unpacked structs (#8289). [Nick Brereton]
* Fix escape strings in pattern formatting (#8297). [Marco Bartoli]
* Fix DECLFILENAME warning on nested declarations (#8302). [Martijn Wobbes]
* Fix cache of boundary vertices in DFG cycle-breaking (#8308). [Shriyans Sahoo]
* Fix property and sequence line coverage (#8314). [Andrii, Antmicro Ltd.]
* Fix wide typed pattern formatting primitives (#8325). [Marco Bartoli]
* Fix circular class inheritance hang (#8324) (#8545). [Saqib Khan]
* Fix V3InstrCount with recursive functions (#8327) (#8361). [Geza Lore, Testorrent USA, Inc.]
* Fix parameterized class nested in a parameterized interface (#8328) (#8329). [Kristof Marien]
* Fix printing signed enums and wide enum names (#8338). [Marco Bartoli]
* Fix ignoring cross_auto_bin_max instead of hard error (#8340). [Marco Bartoli]
* Fix wide enum names (#8344) (#8362) (#8365) (#8366). [Marco Bartoli]
* Fix class-qualified self reference to a typedef in a parameterized class (#8348) (#8349). [Dragon-Git]
* Fix crash when elaborating a covergroup option through a forward class (#8351) (#8352). [Kristof Marien]
* Fix missing implicit net for bufif/notif/nmos/pmos data input (#8353) (#8354). [Mike Murunov]
* Fix %p format on class members (#8360). [Marco Bartoli]
* Fix Docker compilation with --trace-fst (#8370). [Giuseppe Manzoni]
* Fix NFA rings for bounded SVA delays (#8371). [Artur Bieniek, Antmicro Ltd.]
* Fix DFG driver tracing of partial assignments (#8377) (#8397). [Geza Lore, Testorrent USA, Inc.]
* Fix unique constraint with multiple arrays (#8378). [Kornel Uriasz, Antmicro Ltd.]
* Fix access types of refs and inouts (#8380). [Igor Zaworski, Antmicro Ltd.]
* Fix copy constructor of classes with constraints (#8381). [Ryszard Rozak, Antmicro Ltd.]
* Fix C++ clang 18 compiler with -fno-slp-vectorize (#8383). [Geza Lore, Testorrent USA, Inc.]
* Fix reevaluation of method calls in wait conditions (#8391). [Jesús Alberto López Soto]
* Fix weighted dist constraints in presence of other constraints (#8392). [Kornel Uriasz, Antmicro Ltd.]
* Fix error on non-existent interface ports (#8393). [Andrii Andrieiev, Antmicro Ltd.]
* Fix compile-time blowup forcing large unpacked arrays (#8395). [Jonathan Drolet]
* Fix false MULTIDRIVEN for writes in tasks never executed (#8401). [Artur Bieniek, Antmicro Ltd.]
* Fix $c() inside a DPI export (#8402). [Nazar Zibilyuk]
* Fix covergroup constructor initialization (#8410). [Marco Bartoli]
* Fix issues with configuring flex during build on MacOS (#8412). [Kaleb Barrett]
* Fix interface modport resolution against non-scope symbol tables (#8420). [Geza Lore, Testorrent USA, Inc.]
* Fix FSM coverage hierarchy being emitted as an absolute path (#8421). [Geza Lore, Testorrent USA, Inc.]
* Fix MSYS header include search to be after toolchain headers (#8422). [Kaleb Barrett]
* Fix losing coverage declarations if first in a module body (#8425). [Geza Lore, Testorrent USA, Inc.]
* Fix force read update sensitivity performance (#8431) (#8433). [Jonathan Drolet]
* Fix streaming dynamic arrays with different element types failing to compile (#8434) (#8435). [Kristof Marien]
* Fix internal error when task without C function is left in the tree (#8437). [Artur Bieniek, Antmicro Ltd.]
* Fix nested random dynamic arrays internal error (#8438) (#8439). [Kristof Marien]
* Fix elaboration of classes reached through deferred references (#8440) (#8510). [Ethan Sifferman]
* Fix VPI discoverability of forceable unpacked struct members (#8445). [Bartłomiej Chmiel, Antmicro Ltd.]
* Fix compound types in anonymous structs (#8449). [Artur Bieniek, Antmicro Ltd.]
* Fix internal error on covergroup in a specialized parameterized class (#8450) (#8452). [David Harris]
* Fix data race in type handling (#8451). [Tynan McAuley]
* Fix $sscanf/$fscanf returning -1 after a partial match (#8457) (#8459). [David Harris]
* Fix covergroup '0/'1 bin values not filling to coverpoint width (#8458) (#8460). [David Harris]
* Fix embedded covergroup calling an enclosing class method (#8461) (#8463). [David Harris]
* Fix unsized condition not reduced to one bit (#8465) (#8466). [David Harris]
* Fix comma emission for nested JSON scopes (#8469) (#8474). [Noam Gallmann]
* Fix model failing when host has fewer cores than --threads (#8475). [Artur Bieniek, Antmicro Ltd.]
* Fix `##0` synchronous drive overriding later drives (#8482). [Marco Bartoli]
* Fix disable fork and wait fork affecting pending NBA updates (#8483). [Marco Bartoli]
* Fix nonblocking event trigger never firing in suspendable processes (#8485). [Marco Bartoli]
* Fix setting status of finished process immediately (#8503). [Ryszard Rozak, Antmicro Ltd.]
* Fix constant oversized shifts in V3Premit. [Geza Lore, Testorrent USA, Inc.]
* Fix semaphore waiting on killed process (#8505). [Ryszard Rozak, Antmicro Ltd.]
* Fix mis-setting of process state from FINISHED to KILLED (#8506). [Ryszard Rozak, Antmicro Ltd.]
* Fix rand_mode(0) not preserving dynamic array of struct values used in a dist constraint (#8508) (#8509). [Kristof Marien]
* Fix duplicate struct declaration when inlining (#8515). [Ethan Sifferman]
* Fix bear version detection if uninstalled. [Geza Lore, Testorrent USA, Inc.]
* Fix enums inside randomize() with (#8517). [Kornel Uriasz, Antmicro Ltd.]
* Fix emitting public parameters (#8520). [Geza Lore, Testorrent USA, Inc.]
* Fix verilator_coverage report to align with get_coverage() (#8522) (#8524) (#8531). [Marco Bartoli]
* Fix verilator_coverage reports rounding incomplete coverage to 100.0% (#8523). [Jordan Carlin]
* Fix extern class method rejects matching virtual interface argument type (#8525) (#8526). [Kristof Marien]
* Fix of queue pop return value access (#8529) (#8511). [Igor Zaworski, Antmicro Ltd.]
* Fix derived class construction resets inherited per-instance rand_mode state (#8527) (#8528). [Kristof Marien]
* Fix crash on class handle passed to string port (#8536) (#8538). [Noam Gallmann]
* Fix stack depth calculations over calls (#8544). [Nick Brereton]
* Fix cascading errors after unsupported class dot reference in parameter value (#8551). [Martijn Wobbes, Antmicro Ltd.]
* Fix vpi_put_value crash on NULL value pointer (#8553). [Nick Brereton]
* Fix --hierarchical parameter specialization name collisions (#8555) (#8565). [Marco Bartoli]
* Fix dehash of a suffix after a hashed name (#8556). [Ethan Sifferman]
Verilator 5.052 2026-09-05
==========================
@@ -1855,7 +2003,7 @@ Verilator 5.016 2023-09-16
* Optimize Verilator executable size by refactoring error reporting routines (#4446). [Anthony Donlon]
* Optimize Verilation runtime pointers and graphs (#4396) (#4397) (#4398). [Krzysztof Bieganski, Antmicro Ltd]
* Optimize preparations towards multithreaded Verilation (#4291) (#4463) (#4476) (#4477) (#4479). [Kamil Rakoczy, Antmicro Ltd]
* Fix Windows filename format, etc (#3873) (#4421). [Anthony Donlon].
* Fix Windows filename format, etc (#3873) (#4421). [Anthony Donlon]
* Fix t_dist_cppstyle Perl performance issue (#4085). [Srinivasan Venkataramanan]
* Fix using type in parameterized classes without #() (#4281) (#4440). [Anthony Donlon]
* Fix false INFINITELOOP on forever..mailbox.get() (#4323). [Srinivasan Venkataramanan]
@@ -3640,7 +3788,7 @@ Verilator 3.880 2015-12-19
* Fix model restore crash. (#1013) [Jason McMullan]
* Fix arrayed instances to unpacked of same size. (#1015) [Varun Koyyalagunta]
* Fix slices of unpacked arrays with non-zero LSBs.
* Fix ternary operation with unpacked array. (#1017) [Varun Koyyalagunta].
* Fix ternary operation with unpacked array. (#1017) [Varun Koyyalagunta]
Verilator 3.878 2015-11-01
@@ -3730,7 +3878,7 @@ Verilator 3.870 2015-02-12
* Suppress COMBDLY when inside always_latch. (#864) [Iztok Jeras]
* Support cast operator with expression size. (#865) [Iztok Jeras]
* Add warning on slice selection out of bounds. (#875) [Cong Van Nguyen].
* Add warning on slice selection out of bounds. (#875) [Cong Van Nguyen]
* Fix member select error broke in 3.868. (#867) [Iztok Jeras]
* Fix $sccanf from string. (#866) [David Pierce]
* Fix VM_PARALLEL_BUILDS broke in 3.868. (#870) [Hiroki Honda]
+2
View File
@@ -747,6 +747,7 @@ FASTCOV_OPT += BROKEN_RTN
FASTCOV_OPT += NUM_ASSERT
FASTCOV_OPT += SELF_CHECK
FASTCOV_OPT += UASSERT
FASTCOV_OPT += UDEBUGONLY
FASTCOV_OPT += UINFO
FASTCOV_OPT += assert
FASTCOV_OPT += 'if (VL_UNCOVERABLE'
@@ -762,6 +763,7 @@ GENHTML_OPT += --rc branch_coverage=1
GENHTML_OPT += --rc genhtml_hi_limit=100
GENHTML_OPT += --ignore-errors negative
GENHTML_OPT += --ignore-errors inconsistent
GENHTML_OPT += --ignore-errors empty
ifeq ($(COVERAGE_REF_BASE),)
GENHTML_OPT += --header-title "Code coverage for Verilator $(shell git describe --dirty)"
else
+11 -11
View File
@@ -23,8 +23,8 @@
.. |badge5| image:: https://img.shields.io/docker/pulls/verilator/verilator
:target: https://hub.docker.com/r/verilator/verilator
.. |badge7| image:: https://img.shields.io/github/actions/workflow/status/verilator/verilator/build-test.yml?branch=master&label=regressions
:target: https://github.com/verilator/verilator/actions/workflows/build-test.yml
.. |badge7| image:: https://img.shields.io/github/actions/workflow/status/verilator/verilator/regression.yml?branch=master&label=regression
:target: https://github.com/verilator/verilator/actions/workflows/regression.yml
.. |badge8| image:: https://img.shields.io/github/actions/workflow/status/verilator/verilator/rtlmeter.yml?branch=master&event=schedule&label=benchmarks
:target: https://verilator.github.io/verilator-rtlmeter-results
@@ -35,7 +35,7 @@ Welcome to Verilator
.. list-table::
- - **Welcome to Verilator, the fastest Verilog/SystemVerilog simulator.**
- Accepts Verilog or SystemVerilog
- Accepts Verilog or SystemVerilog, including UVM
- Performs lint code-quality checks
- Compiles into multithreaded C++, or SystemC
- Creates JSON to front-end your own tools
@@ -75,14 +75,14 @@ instantiate the model. The resulting Verilated executable performs the
design simulation. Verilator also supports linking Verilator-generated
libraries, optionally encrypted, into other simulators.
Verilator supports all design constructs, most verification constructs,
intra-assignment delays (e.g, `#10`), and events. Tristate-bus (`z`) and
unknowns (`x`) are handled in limited contexts, in a special manor for
performance. It currently may not be the best choice if you are expecting a
full-featured replacement for a closed-source Verilog simulator, performing
SDF annotation, or mixed-signal simulation. However, if you are looking for
a path to migrate SystemVerilog to C++/SystemC, or want high-speed
simulation, Verilator is the tool for you.
Verilator supports all design constructs, most verification constructs
including UVM, intra-assignment delays (e.g, `#10`), and events.
Tristate-bus (`z`) and unknowns (`x`) are handled in limited contexts, in a
special manor for performance. It currently may not be the best choice if
you are expecting a full-featured replacement for a closed-source Verilog
simulator, performing SDF annotation, or mixed-signal simulation. However,
if you are looking for a path to migrate SystemVerilog to C++/SystemC, or
want high-speed simulation, Verilator is the tool for you.
Performance
===========
+2
View File
@@ -419,6 +419,8 @@ detailed descriptions of these arguments.
--coverage-expr-max <value> Maximum permutations allowed for an expression
--coverage-fsm Enable FSM state/arc coverage
--coverage-line Enable line coverage
--coverage-max-bins <value> Maximum bins of an integral coverpoint bins declaration
--coverage-max-real-bins <value> Maximum bins of a real coverpoint bins declaration
--coverage-max-width <width> Maximum array depth for coverage
--coverage-per-instance Enable per-instance coverage counters
--coverage-toggle Enable toggle coverage
+10
View File
@@ -79,6 +79,13 @@ fi
if [ "$OPT_LIGHT_DEBUG" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-light-debug"
fi
if [ "$(uname -s)" = Darwin ]; then
# Force the use of the Homebrew flex over the one included as part of the
# Apple SDK that's preinstalled on the MacOS Github Action runners.
FLEX_PREFIX=$(brew --prefix flex)
export LEX="$FLEX_PREFIX/bin/flex"
export CPPFLAGS="${CPPFLAGS:+$CPPFLAGS }-I$FLEX_PREFIX/include"
fi
autoconf
./configure $CONFIGURE_ARGS CXX="$CXX"
@@ -88,6 +95,9 @@ autoconf
ccache -z
BUILD_START=$SECONDS
"$MAKE" -j "$NPROC" -k
# Second make to get failures at bottom, to avoid needing to
# scroll up GitHub action logs (to see first errors half-way up)
"$MAKE" -j "$NPROC" -k
ccache -svv
ccache --evict-older-than "$((SECONDS - BUILD_START + 60))s"
ccache -svv
+33
View File
@@ -98,3 +98,36 @@ SUMMARY_TEMPLATE
echo "Workflow [#${RUN_NUM}](${RUN_URL}) report: [${RUN_ID}](${REPORT_URL})" > ${NOTIFICATION_DIR}/hist.txt
fi
###############################################################################
# Create the coverage status
###############################################################################
# 'pr-automation.yml' labels the pull request from this. Note it is only
# written when the coverage could actually be determined, so that a report we
# could not read leaves the labels of the pull request alone.
if [ -f ${COVERAGE_DIR}/empty-patch ]; then
# Nothing to cover, so count it as covered
COVERED=true
else
# Take the counts, and not the percentage, which is rounded. The line reads
# for example " lines......: 100.00% (60 of 60 lines)"
COUNTS=$(sed -nE 's/^ *lines\.*: *[0-9.]+% \(([0-9]+) of ([0-9]+) lines\).*/\1 \2/p' \
${MAKE_LOG} | tail -n 1)
HIT=${COUNTS%% *}
TOTAL=${COUNTS##* }
if [ -z "${TOTAL}" ]; then
echo "Could not read the line coverage from ${MAKE_LOG}" >&2
COVERED=
elif [ "${HIT}" = "${TOTAL}" ]; then
COVERED=true
else
COVERED=false
fi
fi
if [ -n "${COVERED}" ]; then
echo "Line coverage complete: ${COVERED}"
echo "${COVERED}" > coverage-status.txt
fi
+1 -1
View File
@@ -158,7 +158,7 @@ NEXTHIST_TEMPLATE
# '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')
ARTIFACT_IDS=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts?name=pr-notification" --jq '.artifacts[].id')
# Delete them all, so we only notify once
for ARTIFACT_ID in ${ARTIFACT_IDS}; do
+8
View File
@@ -25,6 +25,8 @@ CASES = {
("BlackParrot:2x2:*", 14),
("BlackParrot:4x4:*", 25),
("Caliptra:default:*", 19),
("HammerBlade:16x8:*", 5),
("HammerBlade:2x1:*", 3),
("HummingbirdV2-E203:default:*", 3),
("HummingbirdV2-E203:nodelay:*", 3),
("NVDLA:*", 29),
@@ -59,6 +61,8 @@ CASES = {
("BlackParrot:2x2:*", 9),
("BlackParrot:4x4:*", 12),
("Caliptra:default:*", 16),
("HammerBlade:16x8:*", 4),
("HammerBlade:2x1:*", 2),
("HummingbirdV2-E203:default:*", 10),
("HummingbirdV2-E203:nodelay:*", 10),
("NVDLA:*", 28),
@@ -110,6 +114,8 @@ CASES_PR = {
("BlackParrot:1x1:*", 5),
("BlackParrot:4x4:*", 25),
("Caliptra:default:*", 19),
("HammerBlade:16x8:*", 5),
("HammerBlade:2x1:*", 3),
("NVDLA:*", 29),
("HummingbirdV2-E203:default:*", 3),
("HummingbirdV2-E203:nodelay:*", 3),
@@ -142,6 +148,8 @@ CASES_PR = {
("BlackParrot:1x1:*", 5),
("BlackParrot:4x4:*", 12),
("Caliptra:default:*", 16),
("HammerBlade:16x8:*", 4),
("HammerBlade:2x1:*", 2),
("NVDLA:*", 28),
("HummingbirdV2-E203:default:*", 10),
("HummingbirdV2-E203:nodelay:*", 10),
+1
View File
@@ -29,6 +29,7 @@ RUN apt-get update \
python3 \
zlib1g \
zlib1g-dev \
liblz4-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
+18 -1
View File
@@ -12,7 +12,7 @@
# Then 'make maintainer-dist'
#AC_INIT([Verilator],[#.### YYYY-MM-DD])
#AC_INIT([Verilator],[#.### devel])
AC_INIT([Verilator],[5.052 2026-09-05],
AC_INIT([Verilator],[5.053 devel],
[https://verilator.org],
[verilator],[https://verilator.org])
@@ -264,6 +264,7 @@ fi
python3_version=$($PYTHON3 --version | head -1)
AC_MSG_RESULT([$PYTHON3 --version = $python3_version])
AC_ARG_VAR([LEX], [Flex executable])
AC_CHECK_PROG(LEX,flex,flex)
if test "x$LEX" = "x" ; then
AC_MSG_ERROR([Cannot find "flex" in your PATH, please install it])
@@ -271,6 +272,22 @@ fi
flex_version=$($LEX --version | head -1)
AC_MSG_RESULT([$LEX --version = $flex_version])
# Use the header installed with the selected generator.
flex_include=$($PYTHON3 -c '
import pathlib
import shutil
import sys
print(pathlib.Path(shutil.which(sys.argv@<:@1@:>@) or sys.argv@<:@1@:>@).resolve().parent.parent / "include")
' "$LEX")
if test -f "$flex_include/FlexLexer.h"; then
if test -n "$MSYSTEM"; then
# Search MSYS Flex headers after the active toolchain's system headers.
CPPFLAGS="$CPPFLAGS -idirafter $flex_include"
else
CPPFLAGS="$CPPFLAGS -I$flex_include"
fi
fi
AC_CHECK_PROG(YACC,bison,bison)
if test "x$YACC" = "x" ; then
AC_MSG_ERROR([Cannot find "bison" in your PATH, please install it])
+22 -1
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.
00>> Please insert into sorted position, and see header as to agreement
24bit-xjkp
404allen404
Adam Bagley
@@ -46,6 +47,7 @@ BRDR LIFE
Brian Li
Cameron Kirk
Cameron Waite
CatalpaEel
Chih-Mao Chen
Chris Bachhuber
Chris Randall
@@ -61,11 +63,13 @@ Daniel Bates
Danny Oler
Dave Sargeant
David Garau
David Harris
David Horton
David Ledger
David Metz
David Stanford
David Turner
Deanyone Su
dependabot[bot]
Dercury
Demin Han
@@ -74,9 +78,11 @@ Dominick Grochowina
Don Williamson
Dragon-Git
Drew Ranck
Drew Risinger
Drew Taussig
Driss Hafdi
Edgar E. Iglesias
Edmund Lam
Eddie Rydell
em2machine
emmettifelts
@@ -92,6 +98,7 @@ Fan Shupei
february cozzocrea
Felix Neumärker
Felix Yan
Francesco Urbani
Frans Skarman
Fuad Ismail
Furqan Nadir
@@ -113,6 +120,7 @@ Han Qi
Harald Heckmann
Hennadii Chernyshchyk
hjsanjana
Hongseok Choi
Howard Su
Huang Rui
Huanghuang Zhou
@@ -145,6 +153,7 @@ jeffrey.song
Jens Yuechao Liu
Jeremy Bennett
Jesse Taube
Jesús Alberto López Soto
Jevin Sweval
Jiacheng Qian
Jiamin Zhu
@@ -182,6 +191,7 @@ Keith Colbert
Kevin Kiningham
Kevin Nygaard
Kornel Uriasz
Kristof Marien
Kritik Bhimani
Krzysztof Bieganski
Krzysztof Boronski
@@ -205,11 +215,13 @@ Maciej Sobkowski
Marcel Chang
Marco Bartoli
Marco Brambilla
Marco Frank
Marco Widmer
Mariusz Glebocki
Markus Krause
Marlon James
Marshal Qiao
Martijn Wobbes
Martin Schmidt
Martin Stadler
Mateusz Gancarz
@@ -224,6 +236,7 @@ Michael Rogenmoser
Michal Czyz
Michaël Lefebvre
Miguel Perez Andrade
Mike Murunov
Mike Popoloski
Miodrag Milanović
Mladen Slijepcevic
@@ -236,9 +249,11 @@ Natan Kreimer
Nathan Graybeal
Nathan Kohagen
Nathan Myers
Nazar Zibilyuk
Nick Brereton
Nikolai Kumar
Nikolay Puzanov
Noam Gallmann
Nolan Poe
Oleh Maksymenko
Patrick Creighton
@@ -281,6 +296,7 @@ Ryan Ziegler
Ryszard Rozak
Saksham Gupta
Samuel Riedel
Saqib Khan
Sean Cross
Sebastien Van Cauwenberghe
Secturion
@@ -291,6 +307,7 @@ Seth Pellegrino
Shashvat Prabhu
Shogo Yamazaki
Shou-Li Hsu
Shriyans Sahoo
spomatasmd
Srinivasan Venkataramanan
Stefan Wallentowitz
@@ -299,8 +316,10 @@ Steven Hugg
Stuart Morris
sumpster
Szymon Gizler
Szymon Jędras
Sören Tempel
Sumanth Kadiyala
Sunimali Rathnayake
Teng Huang
Thomas Aldrian
Thomas Brown
@@ -321,9 +340,11 @@ Tracy Narine
Trung Nguyen
Tudor Timi
Tymoteusz Blazejczyk
Tynan McAuley
Tyrone Marhguy
Udaya Raj Subedi
Udi Finkelstein
UnsignedByte
Unai Martinez-Corral
Valentin Atepalikhin
Varun Koyyalagunta
@@ -357,4 +378,4 @@ Zixi Li
Zubin Jain
Àlex Torregrosa
Ícaro Lima
Sunimali Rathnayake
ÍÍ>> Please insert into sorted position, and see header as to agreement
+1 -1
View File
@@ -1,5 +1,5 @@
.. comment: generated by t_lint_didnotconverge_bad
.. code-block::
-V{t#,#} 'stl' region trigger index 0 is active: @([hybrid] a)
-V{t#,#} 'stl' region trigger index 0 is active: @([hybrid] b)
%Error-DIDNOTCONVERGE: Settle region did not converge after '--converge-limit' of 10000 tries
+27
View File
@@ -40,6 +40,30 @@ model:
internals, including ``/* verilator public_flat */`` items.
.. _model header include macros:
Model Header Include Macros
---------------------------
Code compiled with the generated :file:`{prefix}.mk`, or with the CMake
``verilate()`` command, gets preprocessor macros that expand to the model
header includes, so user C++ need not hard-code the :vlopt:`--prefix`:
.. code-block:: C++
#include VM_PREFIX_INCLUDE // e.g. <Vtop.h>
#include VM_PREFIX_INCLUDE_DPI // e.g. <Vtop__Dpi.h>
``VM_PREFIX_INCLUDE_DPI`` is defined only when Verilator generates the
:file:`{prefix}__Dpi.h` header, which is the case for designs with DPI and
also for public-signal access such as :vlopt:`--public-flat-rw`. Using it
otherwise is a compile-time error.
A target built from several models defines each macro once, naming the
first model, as one target has one set of preprocessor definitions. Code
that must reach a second model's header names it directly.
.. _porting from pre 4.210:
Model interface changes in version 4.210
@@ -207,6 +231,9 @@ command line, or the link), you'd then:
#include "Vour__Dpi.h"
int add(int a, int b) { return a+b; }
Rather than hard-coding the prefix, ``#include VM_PREFIX_INCLUDE_DPI`` may
be used instead; see :ref:`Model Header Include Macros`.
DPI System Task/Functions
-------------------------
-8
View File
@@ -18,14 +18,6 @@ C++14 compiler support
the Ubuntu LTS versions of GCC and clang use C++20 by default, estimated
May 2028.)
`--structs-packed` option
The :vlopt:`--structs-packed` option was introduced when Verilator was
first implementing unpacked structs. That feature has been stable now
for multiple years, so :vlopt:`--structs-packed` should no longer be
used. Thus :vlopt:`--structs-packed` will change to a no-operation flag
and the related :option:`UNPACKED` warning will never be issued no
sooner than September 2026.
tcmalloc support
Verilator currently supports the default malloc, tcmalloc, or jemalloc.
As jemalloc has better performance, support for tcmalloc may be removed
+44 -7
View File
@@ -316,6 +316,33 @@ Summary:
Enables basic block line coverage analysis. See :ref:`Line Coverage`.
.. option:: --coverage-max-bins <value>
Rarely needed. Specifies the maximum number of bins one covergroup bins
declaration of an integral coverpoint may create: an array of bins such
as ``bins b[] = {[0:$]}``, automatic bins ``bins auto[N]``, or the
automatic bins of a coverpoint from ``option.auto_bin_max``. Defaults to
1024, and may be at most 4294967295 (``2**32 - 1``).
A larger array of bins is ignored with a :option:`COVERIGN` warning, a
larger ``bins auto[N]`` is an error, and a larger
``option.auto_bin_max`` is reduced to this limit with a
:option:`COVERIGN` warning. Increasing the limit increases the memory
the model uses for the bins. See also
:vlopt:`--coverage-max-real-bins`.
.. option:: --coverage-max-real-bins <value>
Rarely needed. Specifies the maximum number of values of one array of
bins of a coverpoint of a ``real`` expression, such as
``bins b[] = {[1:10]}``, each of which is a bin compared separately.
Defaults to 1024, and may be at most 4294967295 (``2**32 - 1``).
A larger array of bins is ignored with a :option:`COVERIGN` warning.
Increasing the limit increases the size of the generated code, which
grows with the number of these bins. See also
:vlopt:`--coverage-max-bins`.
.. option:: --coverage-max-width <width>
Rarely needed. Specify the maximum bit width of a signal subject to
@@ -706,6 +733,8 @@ Summary:
.. option:: -fno-dead-cells
.. option:: -fno-dead-methods
.. option:: -fno-dedup
.. option:: -fno-dfg
@@ -811,6 +840,10 @@ Summary:
.. option:: -fno-lift-expr
Deprecated and has no effect (ignored).
In versions before 5.054: Disable lifting of expressions out of statements.
.. option:: -fno-localize
.. option:: -fno-merge-cond
@@ -819,6 +852,11 @@ Summary:
.. option:: -fno-merge-const-pool
Deprecated and has no effect (ignored).
In versions before 5.054: Disable merging constant pool entries that have
the same value but different data types.
.. option:: -fno-reloop
.. option:: -fno-reorder
@@ -1790,12 +1828,11 @@ Summary:
.. option:: --structs-packed
Deprecated; discontinue use of this option.
Removed in 5.054.
Converts all unpacked structures to packed structures, and issues an
:option:`UNPACKED` warning. Specifying this option allows for backward
compatibility with versions before Verilator 5.006, when Verilator would
always pack unpacked structures.
Converted all unpacked structures to packed structures, Specifying this
option allowed for backward compatibility with versions before Verilator
5.006, when Verilator would always pack unpacked structures.
.. option:: -sv
@@ -2267,7 +2304,7 @@ Summary:
If using `--x-assign unique`, use the
:vlopt:`+verilator+rand+reset+2 <+verilator+rand+reset+\<value\>>`
runtime option, and seed the runtime random number generator such that
each regression run gets a different randomization sequence with
each regression test run gets a different randomization sequence with
:vlopt:`+verilator+seed+\<value\>`. You'll probably also want to print
any seeds selected, and code to enable rerunning with that same seed,
so you can reproduce bugs.
@@ -2296,7 +2333,7 @@ Summary:
If using `--x-initial unique`, use the
:vlopt:`+verilator+rand+reset+2 <+verilator+rand+reset+\<value\>>`
runtime option, and seed the runtime random number generator such that
each regression run gets a different randomization sequence with
each regression test run gets a different randomization sequence with
:vlopt:`+verilator+seed+\<value\>`. You'll probably also want to print
any seeds selected, and code to enable rerunning with that same seed,
so you can reproduce bugs.
+1 -1
View File
@@ -28,7 +28,7 @@ Quick Install
and install.
#. For build on Windows using MSVC set environment variable WIN_FLEX_BISON
to install directory. For build on Windows/Linux/OS-X using ninja set
the environment variable FLEX_INCLUDE to the directory containing
the CMake variable FLEX_INCLUDE_DIR to the directory containing
FlexLexer.h and ensure that flex/bison is available within the PATH.
To obtain Verilator sources download
+1 -1
View File
@@ -174,7 +174,7 @@ once, after ``configure``:
# Install dependencies
sudo apt install python3-pip
# Create Python virutal environment in .venv:
# Create Python virtual environment in .venv:
make venv
# Or alternatively, to put it somewhere else:
+1 -1
View File
@@ -153,7 +153,7 @@ experience depends on the use case, and some experimentation can pay
dividends. For a speedy debug cycle during development, especially on large
designs where C++ compilation speed can dominate, consider using lower
optimization to get to an executable faster. For throughput-oriented use
cases, for example, regressions, it is usually worth spending extra
cases, for example, regression testing, it is usually worth spending extra
compilation time to reduce total CPU time.
If you will be running many simulations on a single model, you can
+7 -7
View File
@@ -592,6 +592,10 @@ List Of Warnings
``covergroup``, ``coverpoint``, and coverage options, and the
construct was ignored.
This includes crosses whose normal-bin Cartesian product exceeds
``2**32 - 1`` tuples. The limit is checked during Verilation for both
automatic and explicit cross bins.
Disabling the :option:`UNSUPPORTED` error also disables this warning.
Ignoring this warning may make Verilator ignore lint checking on the
@@ -2499,14 +2503,10 @@ List Of Warnings
.. option:: UNPACKED
Warns that unpacked structs and unions are not supported because
:vlopt:`--structs-packed` was used, or by up through version 5.004.
Historical, never issued since version 5.004.
Ignoring this warning will make Verilator treat the structure as packed,
which may make Verilator simulations differ from other simulators. This
downgrading may also result in what would typically be a legal unpacked
struct/array inside an unpacked struct/array becoming an illegal
unpacked struct/array inside a packed struct/array.
Warned that unpacked structs and unions were not supported, or disabled
by the since-removed `--structs-packed` option.
.. option:: UNSATCONSTR
+2 -11
View File
@@ -1031,15 +1031,6 @@ Profile-guided optimization make this a bit better, by adjusting mtask
scheduling, but this does not yet guide the packing into mtasks.
Performance Regression
++++++++++++++++++++++
It would be nice if we had a regression of large designs, with some
diversity of design styles, to test on both single- and multithreaded
modes. This would help to avoid performance regressions, and also to
evaluate the optimizations while minimizing the impact of parasitic noise.
Per-Instance Classes
++++++++++++++++++++
@@ -1645,7 +1636,7 @@ There are some traps to avoid when running regression tests
Hat/Fedora/Centos offer *perl-Pod-Perldoc*', while Debian/Ubuntu/Linux
Mint offer \`perl-doc'.
- Running regression may exhaust resources on some Linux systems,
- Running regression tests may exhaust resources on some Linux systems,
particularly file handles and user processes. Increase these to
respectively 16,384 and 4,096. The method of doing this is
system-dependent, but on Fedora Linux it would require editing the
@@ -2422,7 +2413,7 @@ driver.py Non-Scenario Arguments
.. option:: --quiet
Suppress all output except for failures and progress messages every 15
seconds. Intended for use only in automated regressions. See also
seconds. Intended for use only in automated regression testing. See also
``--rerun``, and ``--verbose`` which is not the opposite of ``--quiet``.
.. option:: --rerun
+10
View File
@@ -305,6 +305,7 @@ Mahmoudy
Makefile
Makefiles
Maksymenko
Manzoni
Marche
Markley
Marquet
@@ -346,6 +347,7 @@ Narcis
Nassim
Nauticus
Nayak
Nazar
Nenakhov
Newgard
Nigam
@@ -588,6 +590,7 @@ Yurii
Zaruba
Zhang
Zhi
Zibilyuk
abirkmanis
accessor
accessors
@@ -627,6 +630,7 @@ benchmarking
biguint
binToOneHot
bindir
binsof
biops
bisonpre
bitOpTree
@@ -718,6 +722,7 @@ deduplication
defenv
defname
defparam
dehash
demangling
dep
deparametrized
@@ -737,6 +742,7 @@ devel
difftree
dir
disambiguates
discoverability
displayb
distcc
doxygen
@@ -947,6 +953,7 @@ makefile
makefiles
malloc
manpages
memoization
metacomment
metacomments
miree
@@ -1132,6 +1139,7 @@ shortint
shortreal
signame
signedness
slp
sp
specparam
splitme
@@ -1182,6 +1190,7 @@ systemc
taskify
tcmalloc
tcmalloc
teardown
tenghtt
testbench
testbenches
@@ -1255,6 +1264,7 @@ vcddiff
vcoverage
vdhotre
vec
vectorize
ventana
venv
ver
+147 -95
View File
@@ -1178,19 +1178,29 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc,
} else if (formatAttr == VL_VFORMATATTR_STRING) {
thingp = va_arg(ap, std::string*);
if (fmt != 'p' && fmt != 'x') fmt = 's'; // Override
} else if (formatAttr == VL_VFORMATATTR_ENUM) {
// Always <= VL_QUADSIZE; emit uses non-ENUM format for wider enums
} else if (formatAttr == VL_VFORMATATTR_ENUM
|| formatAttr == VL_VFORMATATTR_ENUM_SIGNED) {
const int numericAttr = formatAttr == VL_VFORMATATTR_ENUM_SIGNED
? VL_VFORMATATTR_SIGNED
: VL_VFORMATATTR_UNSIGNED;
lbits = va_arg(ap, int);
ld = VL_VA_ARG_Q_(ap, lbits);
strwide.resize(2);
WDataOutP strwidep = WDataOutP::external(strwide.data());
VL_SET_WQ(strwidep, ld);
lwp = strwidep;
if (lbits <= VL_QUADSIZE) {
ld = VL_VA_ARG_Q_(ap, lbits);
strwide.resize(2);
WDataOutP strwidep = WDataOutP::external(strwide.data());
VL_SET_WQ(strwidep, ld);
lwp = strwidep;
} else {
lwp = WDataInP::external(va_arg(ap, const EData*));
ld = VL_SET_QW(lwp);
}
lsb = lbits - 1;
++argn; // Enum value is followed by the generated name string argument
static_cast<void>(va_arg(ap, int)); // VL_VFORMATATTR_STRING
enump = va_arg(ap, std::string*);
if (enump && !enump->empty()) {
if (fmt != 'p' && fmt != 's') {
formatAttr = numericAttr;
} else if (enump && !enump->empty()) {
formatAttr = (fmt == 'p') ? VL_VFORMATATTR_COMPLEX : VL_VFORMATATTR_STRING;
thingp = const_cast<std::string*>(enump);
} else if (fmt == 'p' && widthSet && width == 0) {
@@ -1201,7 +1211,7 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc,
if (fmt == 'p') width = 0;
widthSet = true;
fmt = 'd';
formatAttr = VL_VFORMATATTR_UNSIGNED;
formatAttr = numericAttr;
}
if (widthSet && width == 0) {
while (lsb && !VL_BITISSET_W(lwp, lsb)) --lsb;
@@ -1282,7 +1292,7 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc,
output += t_tmp;
} else if (formatAttr == VL_VFORMATATTR_STRING) {
const std::string* const strp = static_cast<const std::string*>(thingp);
output += '"' + *strp + '"';
output += VL_TO_STRING(*strp);
} else if (formatAttr == VL_VFORMATATTR_COMPLEX) {
const std::string* const strp = static_cast<const std::string*>(thingp);
output += *strp;
@@ -1822,8 +1832,8 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
return got;
done:
// Scan stopped early, return parsed or EOF
if (_vl_vsss_eof(fp, floc)) return -1;
// Scan stopped early; EOF only if input ended before the first conversion
if (got == 0 && _vl_vsss_eof(fp, floc)) return -1;
return got;
}
@@ -2488,6 +2498,30 @@ std::string VL_TO_STRING(QData lhs) {
std::string VL_TO_STRING(double lhs) {
return VL_SFORMATF_N_NX("%g", 1, VL_VFORMATATTR_DOUBLE, lhs);
}
std::string VL_TO_STRING(const std::string& obj) VL_PURE {
std::string out{"\""};
out.reserve(obj.size() + 2);
for (const unsigned char ch : obj) {
switch (ch) {
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
default:
if (std::isprint(ch)) {
out += static_cast<char>(ch);
} else {
out += '\\';
out += static_cast<char>('0' + ((ch >> 6) & 3));
out += static_cast<char>('0' + ((ch >> 3) & 7));
out += static_cast<char>('0' + (ch & 7));
}
break;
}
}
return out + '"';
}
std::string VL_TO_STRING_W(int words, const WDataInP obj) {
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, words * VL_EDATASIZE, obj);
}
@@ -3094,41 +3128,16 @@ 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;
if (m_ns.m_assertCtlsLocked) return;
// Set all assert and directive types when true, clear otherwise.
m_s.m_assertOn = VL_MASK_I(ASSERT_ON_WIDTH) * flag;
}
bool VerilatedContext::assertOnGet(VerilatedAssertType_t type,
VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE {
return assertCtlGet(VerilatedAssertCtlQuery::ASSERT_CTL_ON, type, directive);
}
uint32_t VerilatedContext::assertOnMask(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_PURE {
// Place the directive bits at each selected assertion type's 3-bit group.
uint32_t mask = 0;
for (int i = 0; i < std::numeric_limits<VerilatedAssertType_t>::digits; ++i) {
if (VL_BITISSET_I(types, i)) mask |= directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH);
}
return mask;
}
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;
if (m_ns.m_assertCtlsLocked) return;
const uint32_t mask = assertOnMask(types, directives);
const uint32_t lockedMask = mask & ~m_s.m_assertLock;
switch (controlType) {
@@ -3178,26 +3187,6 @@ void VerilatedContext::assertCtl(uint32_t controlType, VerilatedAssertType_t typ
.c_str());
}
}
uint32_t
VerilatedContext::assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type,
VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE {
const uint32_t mask = assertOnMask(type, directive);
if (!mask) return 0;
switch (query) { // LCOV_EXCL_BR_LINE
case VerilatedAssertCtlQuery::ASSERT_CTL_ON: return (m_s.m_assertOn & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_KILL:
assert(mask && (mask & (mask - 1)) == 0);
return m_s.m_assertKill[VL_CLOG2_I(mask)];
case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_VACUOUS:
return (m_s.m_assertPassOnVacuous & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_NONVACUOUS:
return (m_s.m_assertPassOnNonvacuous & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_FAIL_ON: return (m_s.m_assertFailOn & mask) != 0;
default: // LCOV_EXCL_START
VL_FATAL_MT("", 0, "", "Internal: Bad assertCtlGet query");
VL_UNREACHABLE;
} // LCOV_EXCL_STOP
}
void VerilatedContext::calcUnusedSigs(bool flag) VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
m_s.m_calcUnusedSigs = flag;
@@ -3381,6 +3370,15 @@ const char* VerilatedContext::timeprecisionString() const VL_MT_SAFE {
return vl_time_str(timeprecision());
}
static void warnThreadsOversubscribed(unsigned threads) VL_MT_SAFE {
const unsigned threadsAvailableToProcess = VlOs::getProcessDefaultParallelism();
if (threads > threadsAvailableToProcess) {
VL_PRINTF_MT("%%Warning: Process has %u hardware threads available, but simulation thread "
"count set to %u. This will likely cause significant slowdown.\n",
threadsAvailableToProcess, threads);
}
}
void VerilatedContext::threads(unsigned n) {
if (n == 0) VL_FATAL_MT(__FILE__, __LINE__, "", "Simulation threads must be >= 1");
@@ -3391,14 +3389,10 @@ void VerilatedContext::threads(unsigned n) {
}
m_useNumaAssign = true;
m_threadsSet = true;
if (m_threads == n) return; // To avoid unnecessary warnings
m_threads = n;
const unsigned threadsAvailableToProcess = VlOs::getProcessDefaultParallelism();
if (m_threads > threadsAvailableToProcess) {
VL_PRINTF_MT("%%Warning: Process has %u hardware threads available, but simulation thread "
"count set to %u. This will likely cause significant slowdown.\n",
threadsAvailableToProcess, m_threads);
}
warnThreadsOversubscribed(m_threads);
}
void VerilatedContext::useNumaAssign(bool flag) { m_useNumaAssign = flag; }
@@ -3464,7 +3458,15 @@ void VerilatedContext::addModel(const VerilatedModel* modelp) {
}
}
VerilatedVirtualBase* VerilatedContext::threadPoolp() {
VerilatedVirtualBase* VerilatedContext::threadPoolp(unsigned modelThreads) {
// The thread count defaults to the number of threads available to the process, which may be
// fewer than a model uses, e.g. a model Verilated with --threads 4 on a single core machine.
// Oversubscribing is slow but works, so grow to fit the model, unless the user picked the
// thread count themselves, in which case addModel() reports the mismatch instead.
if (VL_UNLIKELY(modelThreads > m_threads) && !m_threadsSet && !m_threadPool) {
m_threads = modelThreads;
warnThreadsOversubscribed(m_threads);
}
if (m_threads == 1) return nullptr;
if (!m_threadPool) m_threadPool.reset(new VlThreadPool{this, m_threads - 1});
return m_threadPool.get();
@@ -3580,7 +3582,7 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) {
std::string str;
uint64_t u64;
if (arg == "+verilator+assert+lock") {
assertCtlsLocked(true);
m_ns.m_assertCtlsLocked = true;
} else if (commandArgVlString(arg, "+verilator+coverage+file+", str)) {
coverageFilename(str);
} else if (arg == "+verilator+debug") {
@@ -3745,11 +3747,17 @@ void VerilatedContext::statsPrintSummary() VL_MT_UNSAFE {
// VerilatedContext:: Methods - scopes
void VerilatedContext::scopesDump() const VL_MT_SAFE {
const VerilatedLockGuard lock{m_impdatap->m_nameMutex};
VL_PRINTF_MT(" scopesDump:\n");
for (const auto& i : m_impdatap->m_nameMap) {
const VerilatedScope* const scopep = i.second;
scopep->scopeDump();
{
const VerilatedLockGuard lock{m_impdatap->m_nameMutex};
VL_PRINTF_MT(" scopesDump:\n");
for (const auto& i : m_impdatap->m_nameMap) {
const VerilatedScope* const scopep = i.second;
scopep->scopeDump();
}
}
{
const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex};
for (const auto& i : m_impdatap->m_ifaceRefMap) i.second.ifaceRefDump();
}
VL_PRINTF_MT("\n");
}
@@ -3779,6 +3787,31 @@ const VerilatedScopeNameMap* VerilatedContext::scopeNameMap() VL_MT_SAFE {
return &(impp()->m_impdatap->m_nameMap);
}
void VerilatedContextImp::ifaceRefInsert(const VerilatedIfaceRef& ifaceRef) VL_MT_SAFE {
// Slow ok - called once/interface-reference at construction
const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex};
m_impdatap->m_ifaceRefMap.emplace(ifaceRef.fullname(), ifaceRef);
}
void VerilatedContextImp::ifaceRefErase(const std::string& fullname,
const VerilatedScope* scopep) VL_MT_SAFE {
// Slow ok - called once/interface-reference at destruction
const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex};
const auto it = m_impdatap->m_ifaceRefMap.find(fullname);
// Models sharing an instance name collide on the key; only erase our own,
// so tearing one down leaves another's live reference registered
if (it != m_impdatap->m_ifaceRefMap.end() && it->second.scopep() == scopep) {
m_impdatap->m_ifaceRefMap.erase(it);
}
}
const VerilatedIfaceRef*
VerilatedContext::ifaceRefFind(const char* namep) const VL_MT_SAFE_POSTINIT {
// Thread safe only assuming this is called only after model construction completed
const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex};
const auto& it = m_impdatap->m_ifaceRefMap.find(namep);
if (VL_UNLIKELY(it == m_impdatap->m_ifaceRefMap.end())) return nullptr;
return &it->second;
}
//======================================================================
// VerilatedContext:: Methods - trace
@@ -4149,28 +4182,6 @@ std::unique_ptr<VerilatedTraceConfig> VerilatedModel::traceConfig() const { retu
//======================================================================
// VerilatedVar:: Methods
// cppcheck-suppress unusedFunction // Used by applications
uint32_t VerilatedVarProps::entSize() const VL_MT_SAFE {
if (m_entSize) return m_entSize;
uint32_t size = 1;
switch (vltype()) {
case VLVT_PTR: size = sizeof(void*); break;
case VLVT_UINT8: size = sizeof(CData); break;
case VLVT_UINT16: size = sizeof(SData); break;
case VLVT_UINT32: size = sizeof(IData); break;
case VLVT_UINT64: size = sizeof(QData); break;
case VLVT_WDATA: size = VL_WORDS_I(entBits()) * sizeof(IData); break;
default: size = 0; break; // LCOV_EXCL_LINE
}
return size;
}
size_t VerilatedVarProps::totalSize() const {
size_t size = entSize();
for (int udim = 0; udim < udims(); ++udim) size *= m_unpacked[udim].elements();
return size;
}
void* VerilatedVarProps::datapAdjustIndex(void* datap, int dim, int indx) const VL_MT_SAFE {
if (VL_UNLIKELY(dim <= 0 || dim > udims())) return nullptr;
if (VL_UNLIKELY(indx < low(dim) || indx > high(dim))) return nullptr;
@@ -4312,6 +4323,41 @@ void VerilatedScope::scopesConstructFromTable(const VlScopeTableEntry* entp, siz
}
}
// Prefix with the model instance name, as VerilatedScope's constructor does
static std::string vl_ifaceRefFullname(const VerilatedSyms* symsp, const char* suffixp) {
const char* const prefixp = symsp->name();
std::string out{prefixp};
if (*prefixp && *suffixp) out += '.';
out += suffixp;
return out;
}
void VerilatedScope::ifaceRefsInsertFromTable(const VlIfaceRefTableEntry* entp, size_t n,
VerilatedSyms* symsp) VL_MT_UNSAFE {
// Use the model's own context; at destruction threadContextp() may be another's
VerilatedContextImp* const impp = symsp->_vm_contextp__->impp();
uint8_t* const base = reinterpret_cast<uint8_t*>(symsp);
for (size_t i = 0; i < n; ++i) {
const VlIfaceRefTableEntry& e = entp[i];
const VerilatedScope* const scopep
= *reinterpret_cast<VerilatedScope**>(base + e.ptrOffset);
impp->ifaceRefInsert(
VerilatedIfaceRef{scopep, e.namep, vl_ifaceRefFullname(symsp, e.suffixp), e.modportp});
}
}
void VerilatedScope::ifaceRefsEraseFromTable(const VlIfaceRefTableEntry* entp, size_t n,
const VerilatedSyms* symsp) VL_MT_UNSAFE {
VerilatedContextImp* const impp = symsp->_vm_contextp__->impp();
uint8_t* const base = reinterpret_cast<uint8_t*>(const_cast<VerilatedSyms*>(symsp));
for (size_t i = 0; i < n; ++i) {
const VlIfaceRefTableEntry& e = entp[i];
const VerilatedScope* const scopep
= *reinterpret_cast<VerilatedScope**>(base + e.ptrOffset);
impp->ifaceRefErase(vl_ifaceRefFullname(symsp, e.suffixp), scopep);
}
}
VerilatedVar* VerilatedScope::varInsertSized(const char* namep, void* datap, bool isParam,
VerilatedVarType vltype, int vlflags, int udims,
uint32_t entSize...) VL_MT_UNSAFE {
@@ -4429,7 +4475,7 @@ void* VerilatedScope::exportFindNullError(int funcnum) VL_MT_SAFE {
// Slowpath - Called only when find has failed
const std::string msg = ("Testbench C called '"s + VerilatedImp::exportName(funcnum)
+ "' but scope wasn't set, perhaps due to dpi import call without "
+ "'context', or missing svSetScope. See IEEE 1800-2023 35.5.3.");
+ "'context', or missing svSetScope (IEEE 1800-2023 35.5.3)");
VL_FATAL_MT("unknown", 0, "", msg.c_str());
return nullptr;
}
@@ -4457,6 +4503,12 @@ void VerilatedScope::scopeDump() const {
}
}
void VerilatedIfaceRef::ifaceRefDump() const VL_MT_SAFE_POSTINIT {
VL_PRINTF_MT(" IFACEREF %p: %s -> %s", this, fullname(), scopep()->name());
if (hasModport()) VL_PRINTF_MT(".%s", modport());
VL_PRINTF_MT("\n");
}
void VerilatedHierarchy::add(const VerilatedScope* fromp, const VerilatedScope* top) {
VerilatedImp::hierarchyAdd(fromp, top);
}
+141 -42
View File
@@ -100,6 +100,9 @@ class VerilatedFstC;
class VerilatedFstSc;
class VerilatedScope;
class VerilatedScopeNameMap;
class VerilatedIfaceRef;
class VerilatedIfaceRefMap;
struct VlIfaceRefTableEntry;
template <typename, typename>
class VerilatedTrace;
class VerilatedTraceBaseC;
@@ -109,6 +112,7 @@ class VerilatedVarNameMap;
class VerilatedVcd;
class VerilatedVcdC;
class VerilatedVcdSc;
class VlCovRegistry;
//=========================================================================
// Basic types
@@ -257,7 +261,7 @@ class VL_SCOPED_CAPABILITY VerilatedLockGuard final {
VL_UNCOPYABLE(VerilatedLockGuard);
private:
VerilatedMutex& m_mutexr;
VerilatedMutex& m_mutexr; // Mutex protecting the guard
public:
/// Construct and hold given mutex lock until destruction or unlock()
@@ -301,6 +305,34 @@ public:
#endif
};
// Internals: VlFileLineDebug stores a SystemVerilog source code location. Used in
// VlCoroutineHandle for debugging purposes, and to locate covergroup weight errors.
class VlFileLineDebug final {
// MEMBERS
#ifdef VL_DEBUG
const char* m_filename = nullptr; // Filename from sources, nullptr for unlnown
int m_lineno = 0; // Line number from sources
#endif
public:
// CONSTRUCTORS
VlFileLineDebug() = default;
VlFileLineDebug(const char* filename, int lineno)
#ifdef VL_DEBUG
: m_filename{filename}
, m_lineno{lineno}
#endif
{
}
// METHODS
#ifdef VL_DEBUG
const char* filename() const { return m_filename; }
int lineno() const { return m_lineno; }
#endif
};
class VlExecutionProfilerBase;
//=========================================================================
@@ -376,8 +408,7 @@ class VerilatedEvalLoop final {
const uint32_t m_convergeLimit; // --converge-limit from compiler command line
// Where to record --prof-exec sections, or null if not profiling
VlExecutionProfilerBase* m_profilerp = nullptr;
// Whether this is the top level model during profiling
bool m_profTopLevel = false;
bool m_profTopLevel = false; // Top level model during profiling
public:
// CONSTRUCTORS
@@ -479,8 +510,8 @@ private:
= ASSERT_DIRECTIVE_TYPE_MASK_WIDTH * std::numeric_limits<VerilatedAssertType_t>::digits
+ 1;
// Build the assertion-control bit mask for the given assertion x directive types.
static uint32_t assertOnMask(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_PURE;
static constexpr 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;
@@ -498,20 +529,18 @@ protected:
// No std::strings or pointers or will serialize badly!
// Fast path
uint64_t m_time = 0; // Current $time (unscaled), 0=at zero, or legacy
std::atomic<uint32_t> m_assertOn{
std::numeric_limits<uint32_t>::max()}; // Enabled assertions,
// for each VerilatedAssertType we store
// 3-bits, one for each directive type. Last
// bit guards internal directive types.
// Enabled assertions, for each VerilatedAssertType we store 3-bits, one for each directive
// type. Last bit guards internal directive types.
std::atomic<uint32_t> m_assertOn{std::numeric_limits<uint32_t>::max()};
std::atomic<uint32_t> m_assertLock{0}; // Locked assertion bits (IEEE 1800-2023 20.11
// Lock/Unlock); same layout as m_assertOn. While
// a bit is locked, On/Off/Kill leave it unchanged.
std::atomic<uint32_t> m_assertPassOnVacuous{
std::numeric_limits<uint32_t>::max()}; // Enabled vacuous pass actions
std::atomic<uint32_t> m_assertPassOnNonvacuous{
std::numeric_limits<uint32_t>::max()}; // Enabled nonvacuous pass actions
std::atomic<uint32_t> m_assertFailOn{
std::numeric_limits<uint32_t>::max()}; // Enabled fail actions
// Enabled vacuous pass actions
std::atomic<uint32_t> m_assertPassOnVacuous{std::numeric_limits<uint32_t>::max()};
// Enabled nonvacuous pass actions
std::atomic<uint32_t> m_assertPassOnNonvacuous{std::numeric_limits<uint32_t>::max()};
// Enabled fail actions
std::atomic<uint32_t> m_assertFailOn{std::numeric_limits<uint32_t>::max()};
std::array<std::atomic<uint32_t>, ASSERT_CONTROL_SLOT_COUNT> m_assertKill{};
bool m_calcUnusedSigs = false; // Waves file on, need all signals calculated
bool m_fatalOnError = true; // Fatal on $stop/non-fatal error
@@ -545,8 +574,7 @@ protected:
// 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
std::atomic<bool> m_assertCtlsLocked{false}; // 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
@@ -580,6 +608,8 @@ protected:
const std::unique_ptr<VerilatedContextImpData> m_impdatap;
// Number of threads to use for simulation (size of m_threadPool + 1 for main thread)
unsigned m_threads = VlOs::getProcessDefaultParallelism();
// True if m_threads was set by the user, rather than being the default
bool m_threadsSet = false;
// Use numa automatic CPU-to-thread assignment
bool m_useNumaAssign = false;
// Number of threads in added models
@@ -590,6 +620,9 @@ protected:
std::unique_ptr<VerilatedVirtualBase> m_executionProfiler;
// Coverage access
std::unique_ptr<VerilatedVirtualBase> m_coveragep; // Pointer for coveragep()
// Covergroup type/instance nodes. Covergroup data is always collected,
// independent of whether coverage data is recorded (--coverage).
std::unique_ptr<VerilatedVirtualBase> m_covergroupsp; // Pointer for covergroupRegistryp()
// File I/O
// Not serialized
@@ -600,9 +633,8 @@ protected:
// List of free descriptors in the MCT region [4, 32)
std::vector<IData> m_fdFreeMct VL_GUARDED_BY(m_fdMutex);
// Magic to check for bad construction
static constexpr uint64_t MAGIC = 0xC35F9A6E5298EE6EULL; // SHA256 "VerilatedContext"
uint64_t m_magic = MAGIC;
uint64_t m_magic = MAGIC; // Magic to check for bad construction
private:
// CONSTRUCTORS
@@ -619,28 +651,9 @@ public:
bool assertOn() const VL_MT_SAFE;
/// Enable all assertion types
void assertOn(bool flag) VL_MT_SAFE;
/// Get enabled status for given assertion types
bool assertOnGet(VerilatedAssertType_t type,
VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE;
/// Set enabled status for given assertion types
void assertOnSet(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_MT_SAFE;
/// 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;
/// Get assertion-control runtime state. Boolean queries return 0/1, Kill returns
/// the generation count.
uint32_t assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type,
VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE;
/// Return if calculating of unused signals (for traces)
bool calcUnusedSigs() const VL_MT_SAFE { return m_s.m_calcUnusedSigs; }
/// Enable calculation of unused signals (for traces)
@@ -659,6 +672,8 @@ public:
/// Return VerilatedCovContext, allocate if needed
/// Note if get unresolved reference then likely forgot to link verilated_cov.cpp
VerilatedCovContext* coveragep() VL_MT_SAFE;
/// Returns VlCovRegistry. Allocated on-demand
VlCovRegistry* covergroupRegistryp() VL_MT_SAFE;
/// Return debug level
static inline int debug() VL_MT_SAFE; /// Set debug level
/// Debug is currently global, but for forward compatibility have a per-context method
@@ -814,6 +829,10 @@ public:
bool stopRequestReserve(bool maybe) VL_MT_SAFE;
void stopRequestRelease() VL_MT_SAFE;
// Internal: assertCtlGet() for generated code, with constant type and directive
template <VerilatedAssertType_t T_Type, VerilatedAssertDirectiveType_t T_Directive>
VL_ATTR_ALWINLINE uint32_t assertCtlGet(VerilatedAssertCtlQuery query) const 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 {
@@ -822,7 +841,9 @@ public:
// Internal: Model and thread setup
void addModel(const VerilatedModel* modelp);
VerilatedVirtualBase* threadPoolp();
// Get the thread pool, creating it if needed. 'modelThreads' is the parallelism of the model
// being constructed, so that the context can grow to the number of threads it requires.
VerilatedVirtualBase* threadPoolp(unsigned modelThreads = 1);
void prepareClone();
VerilatedVirtualBase* threadPoolpOnClone();
VerilatedVirtualBase*
@@ -868,6 +889,9 @@ public:
const VerilatedScope* scopeFind(const char* namep) const VL_MT_SAFE;
const VerilatedScopeNameMap* scopeNameMap() VL_MT_SAFE;
// Internal: Find interface reference by fully qualified path
const VerilatedIfaceRef* ifaceRefFind(const char* namep) const VL_MT_SAFE_POSTINIT;
// Internal: Serialization setup
static constexpr size_t serialized1Size() VL_PURE { return sizeof(m_s); }
void* serialized1Ptr() VL_MT_UNSAFE { return &m_s; }
@@ -897,6 +921,32 @@ public: // But for internal use only
virtual const char* name() const = 0;
};
// An interface reference port, and the concrete interface it is connected to.
// Used for VPI; references are not scopes, so are not in VerilatedScopeNameMap.
class VerilatedIfaceRef final {
const VerilatedScope* m_scopep = nullptr; // Concrete interface referred to
const char* m_namep = ""; // Name of the reference port
// Fully qualified path; owned, as the instance name prefix is set at construction
std::string m_fullname;
const char* m_modportp = ""; // Modport name, or "" if none
public:
VerilatedIfaceRef() = default;
VerilatedIfaceRef(const VerilatedScope* scopep, const char* namep, const std::string& fullname,
const char* modportp)
: m_scopep{scopep}
, m_namep{namep}
, m_fullname{fullname}
, m_modportp{modportp} {}
~VerilatedIfaceRef() = default;
// ACCESSORS
const VerilatedScope* scopep() const VL_MT_SAFE_POSTINIT { return m_scopep; }
const char* name() const VL_MT_SAFE_POSTINIT { return m_namep; }
const char* fullname() const VL_MT_SAFE_POSTINIT { return m_fullname.c_str(); }
const char* modport() const VL_MT_SAFE_POSTINIT { return m_modportp; }
bool hasModport() const VL_MT_SAFE_POSTINIT { return m_modportp[0] != '\0'; }
void ifaceRefDump() const VL_MT_SAFE_POSTINIT;
};
//===========================================================================
// Verilator scope information class
// Used for internal VPI implementation, and introspection into scopes
@@ -908,8 +958,9 @@ public:
enum Type : uint8_t {
SCOPE_MODULE,
SCOPE_OTHER,
SCOPE_PACKAGE
}; // Type of a scope, currently only module and package are interesting
SCOPE_PACKAGE,
SCOPE_INTERFACE
}; // Type of a scope, currently only module, package and interface are interesting
private:
// Fastpath:
VerilatedSyms* const m_symsp; // Symbol table
@@ -942,6 +993,10 @@ public: // But internals only - called from verilated modules, VerilatedSyms
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;
static void ifaceRefsInsertFromTable(const VlIfaceRefTableEntry* entp, size_t n,
VerilatedSyms* symsp) VL_MT_UNSAFE;
static void ifaceRefsEraseFromTable(const VlIfaceRefTableEntry* entp, size_t n,
const 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; }
@@ -958,6 +1013,15 @@ public: // But internals only - called from verilated modules, VerilatedSyms
VerilatedContext* contextp() const { return m_symsp->_vm_contextp__; }
};
// One interface reference, consumed by VerilatedScope::ifaceRefsInsertFromTable()
struct VlIfaceRefTableEntry final {
uint32_t ptrOffset; // offsetof of the referred-to __Vscopep_* member within the Syms object
const char* namep; // Name of the reference port
// Path within the model; as VlScopeTableEntry::namep, instance name prepended at construction
const char* suffixp;
const char* modportp; // Modport name, or "" if none
};
// One scope, consumed by VerilatedScope::scopesConstructFromTable(); replaces
// per-scope 'new VerilatedScope{...}' statements, which compiles faster at scale.
struct VlScopeTableEntry final {
@@ -1276,5 +1340,40 @@ void VerilatedContext::timeprecision(int value) VL_MT_SAFE {
#endif
}
// Defined here, not in-class: VL_CLOG2_I / VL_FATAL_MT (verilated_funcs.h) are not yet in scope
constexpr uint32_t
VerilatedContext::assertOnMask(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_PURE {
// Place the directive bits at each selected assertion type's 3-bit group.
uint32_t mask = 0;
for (int i = 0; i < std::numeric_limits<VerilatedAssertType_t>::digits; ++i) {
if (VL_BITISSET_I(types, i)) mask |= directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH);
}
return mask;
}
template <VerilatedAssertType_t T_Type, VerilatedAssertDirectiveType_t T_Directive>
uint32_t VerilatedContext::assertCtlGet(VerilatedAssertCtlQuery query) const VL_MT_SAFE {
// A constexpr local forces compile-time evaluation of the mask, which a plain
// assertOnMask() call does not get from GCC at -Os
constexpr uint32_t mask = assertOnMask(T_Type, T_Directive);
if (!mask) return 0;
// Explicit load(): G++ -Os inlines it but not the implicit conversion.
switch (query) { // LCOV_EXCL_BR_LINE
case VerilatedAssertCtlQuery::ASSERT_CTL_ON: return (m_s.m_assertOn.load() & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_KILL:
assert(mask && (mask & (mask - 1)) == 0);
return m_s.m_assertKill[VL_CLOG2_I(mask)].load();
case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_VACUOUS:
return (m_s.m_assertPassOnVacuous.load() & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_NONVACUOUS:
return (m_s.m_assertPassOnNonvacuous.load() & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_FAIL_ON:
return (m_s.m_assertFailOn.load() & mask) != 0;
default: // LCOV_EXCL_START
VL_FATAL_MT("", 0, "", "Internal: Bad assertCtlGet query");
VL_UNREACHABLE;
} // LCOV_EXCL_STOP
}
#undef VERILATOR_VERILATED_H_INTERNAL_
#endif // Guard
+10
View File
@@ -99,6 +99,16 @@ VK_CPPFLAGS_ALWAYS += \
-DVM_VPI=$(VM_VPI) \
$(CFG_CXXFLAGS_NO_UNUSED) \
# Left undefined when there is no generated model, so misuse fails at compile time
ifneq ($(VM_PREFIX_INCLUDE),)
VK_CPPFLAGS_ALWAYS += -DVM_PREFIX_INCLUDE="$(VM_PREFIX_INCLUDE)"
endif
# Left undefined when the design has no DPI, so misuse fails at compile time
ifneq ($(VM_PREFIX_INCLUDE_DPI),)
VK_CPPFLAGS_ALWAYS += -DVM_PREFIX_INCLUDE_DPI="$(VM_PREFIX_INCLUDE_DPI)"
endif
ifeq ($(CFG_WITH_CCWARN),yes) # Local... Else don't burden users
VK_CPPFLAGS_WALL += -Wall $(CFG_CXXFLAGS_WEXTRA) -Werror
endif
+13 -1
View File
@@ -39,26 +39,34 @@ VLCOVGEN_ITEM("'name':'per_instance','short':'P', 'group':1, 'default':0, 'd
VLCOVGEN_ITEM("'name':'thresh', 'short':'s', 'group':1, 'default':None, 'descr':'Number of hits to consider covered (aka at_least)'")
VLCOVGEN_ITEM("'name':'type', 'short':'t', 'group':1, 'default':'', 'descr':'Type of coverage (block, line, fsm, etc)'")
// Bin attributes
VLCOVGEN_ITEM("'name':'cross_bins', 'short':'Cb', 'group':0, 'default':'', 'descr':'Comma-separated per-dimension bin names for cross coverage points'")
VLCOVGEN_ITEM("'name':'bin', 'short':'B', 'group':0, 'default':'', 'descr':'Bin name for covergroup coverage points'")
VLCOVGEN_ITEM("'name':'bin_type', 'short':'Bt', 'group':0, 'default':'', 'descr':'Kind of a covergroup bin that is not coverable: ignore, illegal, or default'")
VLCOVGEN_ITEM("'name':'comment', 'short':'o', 'group':0, 'default':'', 'descr':'Textual description for the item'")
VLCOVGEN_ITEM("'name':'cross', 'short':'C', 'group':0, 'default':0, 'descr':'True for cross coverage points'")
VLCOVGEN_ITEM("'name':'cross_bins', 'short':'Cb', 'group':0, 'default':'', 'descr':'Comma-separated per-dimension bin names for cross coverage points'")
VLCOVGEN_ITEM("'name':'fsm_from', 'short':'Ff', 'group':0, 'default':'', 'descr':'FSM source state name for structured FSM coverage points'")
VLCOVGEN_ITEM("'name':'fsm_tag', 'short':'Fg', 'group':0, 'default':'', 'descr':'FSM point tag such as reset, reset_include, or default'")
VLCOVGEN_ITEM("'name':'fsm_to', 'short':'Ft', 'group':0, 'default':'', 'descr':'FSM destination state name for structured FSM coverage points'")
VLCOVGEN_ITEM("'name':'fsm_var', 'short':'Fv', 'group':0, 'default':'', 'descr':'FSM state variable name for structured FSM coverage points'")
VLCOVGEN_ITEM("'name':'group_weight','short':'Gw', 'group':0, 'default':None, 'descr':'For totaling covergroups, type_option.weight of the covergroup of this item'")
VLCOVGEN_ITEM("'name':'hier', 'short':'h', 'group':0, 'default':'', 'descr':'Hierarchy path name for the item'")
VLCOVGEN_ITEM("'name':'lineno', 'short':'l', 'group':0, 'default':0, 'descr':'Line number for the item'")
VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'descr':'For totaling items, weight of this item'")
// clang-format on
// VLCOVGEN_CIK_AUTO_EDIT_BEGIN
#define VL_CIK_BIN "B"
#define VL_CIK_BIN_TYPE "Bt"
#define VL_CIK_COLUMN "n"
#define VL_CIK_COMMENT "o"
#define VL_CIK_CROSS "C"
#define VL_CIK_CROSS_BINS "Cb"
#define VL_CIK_FILENAME "f"
#define VL_CIK_FSM_FROM "Ff"
#define VL_CIK_FSM_TAG "Fg"
#define VL_CIK_FSM_TO "Ft"
#define VL_CIK_FSM_VAR "Fv"
#define VL_CIK_GROUP_WEIGHT "Gw"
#define VL_CIK_HIER "h"
#define VL_CIK_LINENO "l"
#define VL_CIK_LINESCOV "S"
@@ -77,14 +85,18 @@ public:
// Return the short key code for a given a long coverage key
static std::string shortKey(const std::string& key) VL_PURE {
// VLCOVGEN_SHORT_AUTO_EDIT_BEGIN
if (key == "bin") return VL_CIK_BIN;
if (key == "bin_type") return VL_CIK_BIN_TYPE;
if (key == "column") return VL_CIK_COLUMN;
if (key == "comment") return VL_CIK_COMMENT;
if (key == "cross") return VL_CIK_CROSS;
if (key == "cross_bins") return VL_CIK_CROSS_BINS;
if (key == "filename") return VL_CIK_FILENAME;
if (key == "fsm_from") return VL_CIK_FSM_FROM;
if (key == "fsm_tag") return VL_CIK_FSM_TAG;
if (key == "fsm_to") return VL_CIK_FSM_TO;
if (key == "fsm_var") return VL_CIK_FSM_VAR;
if (key == "group_weight") return VL_CIK_GROUP_WEIGHT;
if (key == "hier") return VL_CIK_HIER;
if (key == "lineno") return VL_CIK_LINENO;
if (key == "linescov") return VL_CIK_LINESCOV;
+11
View File
@@ -28,6 +28,8 @@
#include <cstdint>
#include <string>
class VlFileLineDebug;
// Per-bin classification. A bin's kind is which set it lives in (structural),
// not a per-bin field. Only Normal feeds coverage(); the rest are recorded.
// Enumerators are 'KIND_'-prefixed because the bare LRM terms collide with
@@ -50,6 +52,9 @@ enum class VlCovBinKind : uint8_t {
/// Bounded bin count, so random access by index is the primary usage.
class VlCoverpointIf VL_NOT_FINAL {
// MEMBERS
int32_t m_weight = 1; // option.weight; never negative
public:
// CONSTRUCTORS
virtual ~VlCoverpointIf() = default;
@@ -62,6 +67,12 @@ public:
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;
/// Weight of this item in its covergroup instance's coverage (option.weight,
/// IEEE 1800-2023 19.11)
int32_t weight() const { return m_weight; }
/// Load option.weight, as evaluated by the covergroup constructor. A negative
/// weight is reported as an error, and counts as zero.
void weight(uint32_t value, VlFileLineDebug fileline);
};
#endif // Guard
File diff suppressed because it is too large Load Diff
+585 -39
View File
@@ -32,10 +32,16 @@
#include "verilatedos.h"
#include "verilated.h"
#include "verilated_cov_model.h"
#include <array>
#include <cstdint>
#include <initializer_list>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
class VerilatedCovContext;
@@ -44,6 +50,15 @@ class VerilatedCovContext;
enum class VlCovBinNaming : uint8_t {
Single, // "<name>" one bin
Array, // "<name>[i]" bins b[N] value array
Numbered, // "<name>_<i>" automatic bins of a coverpoint without bins
Values, // "<name>[v]" bins b[] with a 'with' filter, a bin per value v
};
// How the bins of a 'with' filter (IEEE 1800-2023 19.5.1.1) hold the values it keeps
enum class VlCovBinGrouping : uint8_t {
Single, // bins b = ...: one bin
Values, // bins b[] = ...: a bin for each value, in value order
Fixed, // bins b[N] = ...: distributed over N bins, as a sized array's
};
// Specifies the naming scheme for a range of bins, allowing the
@@ -94,6 +109,10 @@ public:
// 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 {
struct ValueData;
std::unique_ptr<ValueData> m_valuesp; // Optional value metadata and exclusion state
friend class VlCoverCrossDyn;
protected:
// MEMBERS (protected so VlCoverpointT::incrementBin can update them)
std::string m_hier; // "covergroup.coverpoint"
@@ -110,15 +129,27 @@ protected:
std::vector<uint32_t> m_crossToBin;
uint32_t m_hitCount = 0; // entries valid in the hit list this sample
// PROTECTED METHODS
// Normal bin: VlCoverpointT::incrementBin(), for the bins sizedSample() finds
virtual void incrementNormalBin(uint32_t i) = 0;
private:
// PRIVATE METHODS
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);
// Declared bin index of the i-th bin reported through VlCoverpointIf
uint32_t reportedBin(uint32_t i) const;
std::string declaredBinName(uint32_t bin) const; // Name of a declared bin index
bool liveBin(uint32_t bin) const; // Normal bin keeps a value outside the exclusions
// Count a sample, if enabled, in a bin of a sized array holding the value, unless it is
// 'last', the bin found before; set 'last'
void sizedHit(VlCovBinKind kind, uint32_t bin, bool enabled, uint32_t& last);
public:
// CONSTRUCTORS
VlCoverpoint() = default;
VlCoverpoint();
~VlCoverpoint() override;
// METHODS
// ---- configuration (from generated constructor) ----
@@ -130,13 +161,78 @@ public:
int line, int col) {
addNamer(set, count, VlCovBinNaming::Array, name, file, line, col);
}
void registerBins(VerilatedCovContext* covcontextp, const char* page);
void addNumberedNamer(VlCovBinKind set, uint32_t count, const char* name, const char* file,
int line, int col) {
addNamer(set, count, VlCovBinNaming::Numbered, name, file, line, col);
}
/// Register the bins in the coverage database, with what verilator_coverage needs to
/// compute coverage (IEEE 1800-2023 19.11): option.at_least, and the weights of the
/// coverpoint, itemWeight, and of its covergroup, groupWeight. The weights are those of
/// every instance, as the database merges the instances.
void registerBins(VerilatedCovContext* covcontextp, const char* page, uint32_t itemWeight,
uint32_t groupWeight);
/// Configure construction-time value metadata for exclusions and cross selections.
void valueType(uint32_t bits, bool isSigned);
/// Describe bin values as {bin, low words, high words} entries, without enumerating them.
void valueRanges(std::initializer_list<EData> entries);
/// Describe runs of bins as {first bin, count, low words, span words, high words} entries:
/// bin k of a run holds [low + k * (span + 1), low + k * (span + 1) + span], and its last
/// bin extends to high.
void valueRuns(std::initializer_list<EData> entries);
/// Describe wildcard patterns as {bin, value words, mask words, low words, high words}.
void valuePatterns(std::initializer_list<EData> entries);
/// State exclusions do not remove values from these transition bins.
void valueTransitions(std::initializer_list<uint32_t> bins);
/// Apply exclusions and freeze the live Normal-bin index space used by crosses.
void valueFinalize();
/// Drop the per-bin values once every cross has been built; sampling needs only exclusions.
void valueRelease();
/// Test state exclusions independently of sampling-time iff guards.
bool valueExcluded(QData value) const;
bool valueExcludedW(WDataInP valuep) const;
/// Add the coverpoint values lo..hi of the next range list element of a sized array of
/// bins, in declaration order.
void sizedRange(QData lo, QData hi);
void sizedRangeW(WDataInP lop, WDataInP hip);
/// Distribute the values sizedRange() added over the bins of the sized array 'name[count]'
/// (IEEE 1800-2023 19.5.1). 'positive' is false for a count below one, which is invalid.
/// At most 'limit' bins may hold values. Needs valueType(); bins append after those of
/// init().
void sizedFinish(VlCovBinKind kind, QData count, bool positive, uint32_t limit,
const char* name, const char* file, int line, int col);
/// Declared bins [sizedFirst(), sizedEnd()) of the sized array 'sized', counted in
/// sizedFinish() order, for cross selections.
uint32_t sizedFirst(uint32_t sized) const;
uint32_t sizedEnd(uint32_t sized) const;
/// Begin the bins of a 'with' filter (IEEE 1800-2023 19.5.1.1), whose candidates are the
/// values sizedRange() added, and whose bins then count as a sized array's. At most
/// 'limit' bins, or runs of values kept.
void withBegin(VlCovBinGrouping grouping, uint32_t limit);
/// Advance to the next run of candidates, withLo() to withHi(); false after the last, or
/// once too many values are kept
bool withNext();
QData withLo() const;
void withLoW(WDataOutP valuep) const;
QData withHi() const;
void withHiW(WDataOutP valuep) const;
/// Keep the values lo..hi, in the order the filter kept them; false once too many are
bool withRun(QData lo, QData hi);
bool withRunW(WDataInP lop, WDataInP hip);
/// Make the bins of the values kept, a sized array 'name[count]' for Fixed grouping (see
/// sizedFinish())
void withFinish(VlCovBinKind kind, QData count, bool positive, const char* name,
const char* file, int line, int col);
// ---- hot path (from generated sample()) ----
// 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]; }
/// Count a sample in the bins of the sized array 'sized' holding the value, once each,
/// if 'enabled'. True if a bin holds the value, enabled or not.
bool sizedSample(uint32_t sized, QData value, bool enabled);
bool sizedSampleW(uint32_t sized, WDataInP valuep, bool enabled);
// incrementBin (Normal bin: count + hit-list append) lives in VlCoverpointT<MaxHits>,
// where MaxHits is the gen-time max per-sample bin overlap.
@@ -147,21 +243,17 @@ public:
std::string normalBinName(uint32_t crossIdx) const; // name of the crossIdx-th Normal bin
// ---- VlCoverpointIf ----
uint32_t binCount() const override { return m_total; }
/// Bins removed for having no value (IEEE 1800-2023 19.11.1) are not reported.
uint32_t binCount() const override;
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(); }
// Deliberately not on VlCoverpointIf: only coverage-database registration needs it.
VlCovBinKind binKind(uint32_t i) const { return namerFor(reportedBin(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.
uint32_t numCovered = 0;
for (const VlCovNamer& nm : m_namers) {
if (nm.set() != VlCovBinKind::KIND_NORMAL) continue;
for (uint32_t i = nm.base(); i < nm.base() + nm.count(); ++i) {
if (m_counts[i] >= m_atLeast) ++numCovered;
}
for (const uint32_t bin : m_crossToBin) {
if (m_counts[bin] >= m_atLeast) ++numCovered;
}
covered = numCovered;
total = m_normal;
@@ -198,16 +290,75 @@ public:
if (cx >= 0 && m_hitCount < MaxHits) m_hits[m_hitCount++] = static_cast<uint32_t>(cx);
}
const uint32_t* hitList() const override { return m_hits; }
protected:
void incrementNormalBin(uint32_t i) override { incrementBin(i); }
};
//=============================================================================
// VlCoverCross
/// Per-instance auto cross runtime. Holds flat uint32_t[] storage over the
/// Per-instance 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.
/// walks only hit tuples, not the entire product. Bin names are
/// built on demand for automatic bins; explicit bins select sets of tuples
/// and replace the corresponding automatic cross bins. Explicit selections
/// are intersected with hit-tuple words once per sample.
/// VlCoverCrossT and VlCoverCrossDyn own their storage. This shared core does not allocate bin
/// storage, and its borrowed storage pointers remain valid for the instance.
class VlCoverCross final : public VlCoverpointIf {
class VlCoverCross VL_NOT_FINAL : public VlCoverpointIf {
protected:
struct Dimension final {
VlCoverpoint* cpp; // Feeding coverpoint
const uint32_t* hitsp; // Hit list cached for Cartesian traversal
uint32_t bins; // Normal bin count
uint32_t stride; // Flat-index stride
};
struct Bin final {
const uint64_t* selectionp; // Slice of the cross's selection storage
const char* namep; // Explicit bin name
const char* filep; // Bin declaration file
const uint32_t* wordIndicesp = nullptr; // Slice of the packed selection-word indices
int line; // Bin declaration line
int col; // Bin declaration column
VlCovBinKind kind = VlCovBinKind::KIND_NORMAL; // Normal, ignore, or illegal bin
uint32_t count = 0; // Samples matching the selection and guard
uint32_t numWords = 0; // Number of nonzero selection-word indices
uint32_t iffIndex = 0; // Original guard index, including bins removed during finalization
};
struct Word final {
uint64_t autoExcluded = 0; // Tuples replaced by explicit bins
uint64_t hitBits = 0; // Selected hit tuples, cleared after each sample
uint32_t touchedWord = 0; // Flat word ID, stored by touched-list position
};
template <typename T>
class View final {
T* m_beginp; // First element of the viewed slice
T* m_endp; // One past the last element of the viewed slice
public:
View(T* datap, uint64_t size)
: m_beginp{datap}
, m_endp{datap ? datap + size : nullptr} {}
T& operator[](uint64_t i) const { return m_beginp[i]; }
uint64_t size() const { return m_beginp == m_endp ? 0 : m_endp - m_beginp; }
bool empty() const { return m_beginp == m_endp; }
T* begin() const { return m_beginp; }
T* end() const { return m_endp; }
};
struct Explicit final {
View<Bin> bins; // Explicit bins in declaration order
Word* wordsp; // Masks use flat word indices; touchedWord uses a dense prefix
View<uint32_t> autoBins; // Retained flat indices
View<uint32_t> binWords; // Nonzero selection words, grouped by bin
uint64_t* selectionp; // [bins.size() * ceil(m_numAutoBins / 64)]
uint32_t numBins = 0; // Bins configured by addBin()
uint32_t normalBins = 0; // Explicit bins contributing to coverage
uint32_t minBinWords = 0; // Minimum nonzero-word count across explicit bins
uint32_t numTouchedWords = 0; // Active prefix of wordsp[].touchedWord
};
private:
// MEMBERS
std::string m_hier; // "covergroup.cross"
const char* m_file = nullptr; // Cross declaration file (registration metadata)
@@ -217,41 +368,436 @@ class VlCoverCross final : public VlCoverpointIf {
// 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.
// storable anyway: m_flatCountsp 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)
Dimension* m_dimensionsp = nullptr; // [m_dims], owned by the concrete cross runtime
uint32_t* m_flatCountsp = nullptr; // [m_numAutoBins] Per-bin hit counts
Explicit* m_explicitp = nullptr; // Absent for automatic-only crosses
// PRIVATE METHODS
void iterateProduct(VlCoverpoint* const* cps, uint32_t dim, uint32_t baseIdx);
bool hasExplicitBins() const { return m_explicitp != nullptr; }
template <bool T_Explicit, bool T_RecordHits = true>
void iterateProduct(uint32_t dim, uint32_t baseIdx);
void incrementAuto(uint32_t idx) {
if (m_flatCountsp[idx]++ == 0) ++m_numCovered;
}
void incrementBin(Bin& bin);
template <bool T_RecordHits>
void incrementTuple(uint32_t idx) {
if (m_flatCounts[idx]++ == 0) ++m_numCovered;
Explicit& data = *m_explicitp;
const uint32_t wordIdx = idx / 64;
Word& word = data.wordsp[wordIdx];
if ((word.autoExcluded >> (idx % 64)) & 1U) {
if (T_RecordHits) {
if (!word.hitBits) { data.wordsp[data.numTouchedWords++].touchedWord = wordIdx; }
word.hitBits |= uint64_t{1} << (idx % 64);
}
// Explicit selections consume automatic tuples independently of iff.
return;
}
incrementAuto(idx);
}
template <bool T_ApplyIffs>
void sampleSingleTuple(uint32_t idx, const bool* binIffs);
template <bool T_ApplyIffs, uint32_t T_Touched, bool T_Dense>
void sampleBins(const bool* binIffs);
template <bool T_ApplyIffs, bool T_Dense>
void sampleHitWords(const bool* binIffs);
uint32_t autoIndex(uint32_t i) const {
return hasExplicitBins() ? m_explicitp->autoBins[i] : i;
}
std::string autoBinName(uint32_t flat) const;
protected:
// CONSTRUCTORS
VlCoverCross(uint32_t dims, uint32_t tuples)
: m_dims{dims}
, m_numAutoBins{tuples} {}
void bindStorage(Dimension* dimensionsp, uint32_t* countsp, Explicit* explicitp = nullptr) {
m_dimensionsp = dimensionsp;
m_flatCountsp = countsp;
m_explicitp = explicitp;
}
void shape(uint32_t dims, uint32_t tuples) {
m_dims = dims;
m_numAutoBins = tuples;
}
void addBinImpl(VlCovBinKind kind, const uint64_t* selectionp, uint32_t words,
const char* namep, const char* filep, int line, int col, uint32_t iffIndex);
public:
VL_UNCOPYABLE(VlCoverCross);
// METHODS
// ---- configuration (from generated constructor, after coverpoints init'd) ----
virtual void init(const char* hier, uint32_t dims, VlCoverpoint* const* cps, const char* file,
int line, int col);
/// Add a cross bin using a verilation-time bitmap of selected Normal-bin tuples.
void addBin(VlCovBinKind kind, std::initializer_list<uint64_t> selection, const char* namep,
const char* filep, int line, int col);
/// Retain only automatic cross bins not selected by any explicit bin.
virtual void finalizeBins();
/// Register the bins in the coverage database; see VlCoverpoint::registerBins().
void registerBins(VerilatedCovContext* covcontextp, const char* page, uint32_t itemWeight,
uint32_t groupWeight);
// ---- hot path (from generated sample(), after all coverpoints sampled) ----
/// Sample automatic and explicit bins, optionally applying per-bin iff guards.
/// Reads the feeding coverpoints saved by init(), so the caller passes no coverpoints.
void sample(const bool* binIffs = nullptr);
// ---- VlCoverpointIf ----
// Explicit bins (including ignore/illegal) precede retained automatic bins.
uint32_t binCount() const override {
return hasExplicitBins()
? static_cast<uint32_t>(m_explicitp->bins.size() + m_explicitp->autoBins.size())
: m_numAutoBins;
}
std::string binName(uint32_t i) const override;
void coverageParts(double& covered, double& total) const override {
covered = m_numCovered;
total = hasExplicitBins() ? m_explicitp->normalBins + m_explicitp->autoBins.size()
: m_numAutoBins;
}
};
//=============================================================================
// VlCoverCrossT
/// Cross storage with verilation-time dimensions and bin capacities. All bin
/// data stays at the registry-owned object's address; no per-buffer allocations
/// or per-shape copies of the sampling algorithm are needed.
template <uint32_t Dims, uint32_t Tuples, uint32_t Bins, uint32_t AutoBins, uint64_t BinWords>
class VlCoverCrossT final : public VlCoverCross {
static constexpr uint32_t WORDS = Tuples / 64 + (Tuples % 64 != 0);
static_assert(Bins > 0, "Explicit cross storage requires bins");
std::array<Dimension, Dims> m_dimensions;
std::array<uint32_t, Tuples> m_counts{};
std::array<Bin, Bins> m_bins;
std::array<Word, WORDS> m_words{};
std::array<uint32_t, AutoBins> m_autoBins;
std::array<uint32_t, BinWords> m_binWords;
std::array<uint64_t, static_cast<uint64_t>(Bins) * WORDS> m_selections;
Explicit m_explicit;
public:
VlCoverCrossT()
: VlCoverCross{Dims, Tuples}
, m_explicit{{m_bins.data(), Bins},
m_words.data(),
{m_autoBins.data(), AutoBins},
{m_binWords.data(), BinWords},
m_selections.data()} {
bindStorage(m_dimensions.data(), m_counts.data(), &m_explicit);
}
};
/// Automatic-only crosses omit every explicit-bin array and its bookkeeping.
template <uint32_t Dims, uint32_t Tuples>
class VlCoverCrossT<Dims, Tuples, 0, 0, 0> final : public VlCoverCross {
std::array<Dimension, Dims> m_dimensions;
std::array<uint32_t, Tuples> m_counts{};
public:
VlCoverCrossT()
: VlCoverCross{Dims, Tuples} {
bindStorage(m_dimensions.data(), m_counts.data());
}
};
// Construction-time cross layout over finalized coverpoints, sharing the sampling core.
class VlCoverCrossDyn final : public VlCoverCross {
class Layout;
std::unique_ptr<Layout> m_layoutp; // Owned cross storage and construction-time selections
public:
// CONSTRUCTORS
VlCoverCrossDyn();
~VlCoverCrossDyn() override;
// METHODS
/// Initialize after all feeding coverpoints have finalized their live bins.
void init(const char* hier, uint32_t dims, VlCoverpoint* const* cps, const char* file,
int line, int col) override;
/// Build cross-bin selections in postfix order.
void selectAll();
/// Start a binsof term over the live bins declared in [first, end) of dimension 'dim'.
void selectDim(uint32_t dim, uint32_t first, uint32_t end, bool negated, bool intersect);
void selectRange(QData lo, QData hi);
void selectRangeW(WDataInP lop, WDataInP hip);
void selectDimEnd();
void selectAnd();
void selectOr();
// Save a selection without renumbering guards when empty bins are removed.
void selectBin(VlCovBinKind kind, const char* namep, const char* filep, int line, int col,
uint32_t iffIndex);
/// Apply cross exclusions and bind finalized storage to the sampling core.
void finalizeBins() override;
};
class VlCovergroupType;
//=============================================================================
// VlCovergroupInst
/// One covergroup instance: owns the coverpoint/cross runtimes created by one
/// SV 'new'. The generated class holds borrowed pointers to them, so the bins
/// outlive the SV object -- the coverage database registers raw count pointers
/// and reads them at write() time, long after the object may have been freed.
///
/// Attach-counted: every VlCovInstHandle bound here holds one count, and the
/// node is retired (see VlCovergroupType::retire) when the last one drops.
class VlCovergroupInst final {
// MEMBERS
// Coverpoint and cross runtimes of this instance; creation == declaration order
std::vector<std::unique_ptr<VlCoverpointIf>> m_items;
VlCovergroupType* const m_typep; // Owning type; outlives this node
const uint32_t m_instId; // Stable identity across churn; NOT the slot
#if !VM_COVERAGE
// Only retire()'s free path uses this; under VM_COVERAGE the node is never
// unlinked, so the slot would be dead. VlCovergroupType sets it.
uint32_t m_slot = 0; // Index into m_typep->m_insts; unlink-by-swap rewrites
#endif
uint32_t m_attachCount = 1; // SV handles bound here; 1 from construction
// option.weight of the SV object that created this node, while that object
// lives; borrowed through VlCovInstHandle::lendWeight().
const IData* m_weightp = nullptr;
IData m_loadedWeight = 1; // Last option.weight loaded through m_weightp
int32_t m_weight = 1; // Weight in use, never negative; kept once the object is gone
VlFileLineDebug m_fileline; // Covergroup declaration, where a negative weight is reported
bool m_retained = false; // VM_COVERAGE: dead, but kept for registered count pointers
// Reads m_items to fold the residue; owns m_slot and m_retained.
friend class VlCovergroupType;
public:
// CONSTRUCTORS
VlCovergroupInst(VlCovergroupType* typep, uint32_t instId)
: m_typep{typep}
, m_instId{instId} {}
VL_UNCOPYABLE(VlCovergroupInst);
// METHODS
// ---- construction (from the generated covergroup constructor) ----
template <uint32_t MaxHits>
VlCoverpointT<MaxHits>* addCoverpoint() {
VlCoverpointT<MaxHits>* const cpp = new VlCoverpointT<MaxHits>{};
m_items.emplace_back(cpp);
return cpp; // borrowed by the generated class
}
template <uint32_t Dims, uint32_t Tuples, uint32_t Bins, uint32_t AutoBins, uint64_t BinWords>
VlCoverCrossT<Dims, Tuples, Bins, AutoBins, BinWords>* addCross() {
auto* const cxp = new VlCoverCrossT<Dims, Tuples, Bins, AutoBins, BinWords>{};
m_items.emplace_back(cxp);
return cxp; // borrowed by the generated class
}
VlCoverCrossDyn* addCrossDyn();
// ---- attach counting (from VlCovInstHandle) ----
void attachInc() { ++m_attachCount; }
// Drops one handle; true if it was the last and the caller must retire the
// node. Retiring is the caller's job because VlCovergroupType is incomplete
// here, and because it frees 'this'.
bool attachDec() { return --m_attachCount == 0; }
// ---- instance weight (from VlCovInstHandle) ----
void lendWeight(const IData* weightp, VlFileLineDebug fileline) {
m_weightp = weightp;
m_fileline = fileline;
loadWeight();
}
// The lending object is being destroyed. Its members may already be gone, so
// the weight is not read again; the last loaded value stays in effect.
void unlendWeight(const IData* weightp) {
if (m_weightp == weightp) m_weightp = nullptr;
}
/// Load option.weight from the lending object. SV writes the member directly
/// (assignments, ref and output arguments, $value$plusargs, ...), so this is
/// where a new value is seen, and checked once: a negative weight is reported as
/// an error, and counts as zero.
void loadWeight();
/// Weight of this instance in its type's coverage (option.weight, IEEE
/// 1800-2023 19.11.3), as last loaded; never negative.
int32_t weight() const { return m_weight; }
// ---- introspection ----
VlCovergroupType* typep() const { return m_typep; }
uint32_t instId() const { return m_instId; }
// True once retired but kept alive because the coverage database holds raw
// pointers into this node's bin counts (VM_COVERAGE); see retire().
bool retained() const { return m_retained; }
/// IEEE 1800-2023 19.11 sums over the items whose coverage has a nonzero
/// denominator: {the sum of each item's option.weight times its coverage
/// (0..100), the sum of those weights}.
std::pair<double, double> coverageSums() const;
/// Instance coverage, as returned by get_inst_coverage(), in 0..100.
double coverage();
};
//=============================================================================
// VlCovRetiredAvg
/// Per-type residue: what survives an instance's death. Fixed size, so it does
/// not grow with churn. Each instance contributes with its option.weight.
struct VlCovRetiredAvg final {
uint64_t count = 0; // Retired instances that contributed (nonzero denominator)
double sumCoverage = 0.0; // Sigma of per-instance weight * coverage (0..100)
double sumWeight = 0.0; // Sigma of per-instance weight
};
//=============================================================================
// VlCovergroupType
/// One covergroup type: owns its live instances, in creation order, plus the
/// residue of the ones that have died.
class VlCovergroupType final {
// MEMBERS
// Live nodes, and -- under VM_COVERAGE -- retired-but-retained ones. Slot
// order is creation order only until the first unlink-by-swap.
std::vector<std::unique_ptr<VlCovergroupInst>> m_insts;
uint32_t m_createdInsts = 0; // Instances ever created; never decremented
uint32_t m_nextInstId = 0; // Monotonic; slots are reused, ids never are
VlCovRetiredAvg m_retired; // Contribution of every instance that has died
IData m_loadedTypeWeight = 1; // Last type_option.weight loaded by coverage()
int32_t m_typeWeight = 1; // type_option.weight in use, never negative
// PRIVATE METHODS
// Harvest instp's contribution into m_retired. Must run before instp is
// unlinked: it reads the instance's items.
void foldResidue(VlCovergroupInst* instp);
public:
// CONSTRUCTORS
VlCovergroupType() = default;
VL_UNCOPYABLE(VlCovergroupType);
// METHODS
VlCovergroupInst* newInstance();
// Called when the last handle to instp drops. Folds the residue, then
// unlinks and frees the node -- except under VM_COVERAGE, where the coverage
// database still holds raw pointers into it and it is only marked retained.
void retire(VlCovergroupInst* instp);
// True if any node here still has an SV handle bound to it, and so can be
// retired again after the registry is destroyed. See ~VlCovRegistry.
bool anyAttached() const;
/// Type coverage, as returned by get_coverage(), in 0..100: the average of
/// every instance's coverage, weighted by its option.weight (IEEE 1800-2023
/// 19.11.3, type_option.merge_instances false). typeWeight is
/// type_option.weight, which decides the result when no instance contributes;
/// like option.weight, it is checked as it is loaded.
double coverage(IData typeWeight, VlFileLineDebug fileline);
// ---- introspection ----
// Test and debug only; generated code never calls these, and SV reaches them
// only via explicit $c. They let a regression test pin node accumulation
// (otherwise visible only as memory growth) and the residue fold.
//
// Instance nodes still reachable from SV. Under VM_COVERAGE this is smaller
// than m_insts.size(), which also holds retained (dead) nodes.
uint32_t liveInstanceCount() const;
// Instances ever created, live or not. Wraps after 4G instances, which no
// introspection use cares about.
uint32_t createdInstanceCount() const { return m_createdInsts; }
// Instances that have died and contributed to the residue.
uint32_t retiredInstanceCount() const { return static_cast<uint32_t>(m_retired.count); }
// Weighted mean coverage over the retired instances only, in 0..100; -1.0 if
// none contributed or their weights sum to zero.
double retiredCoverage() const;
};
//=============================================================================
// VlCovRegistry
/// Every covergroup type and instance in one VerilatedContext. Owned by the
/// VerilatedContext (not by the coverage database, which is only linked under
/// --coverage and is a *consumer* of this data), reached through
/// VerilatedContext::covergroupRegistryp().
class VlCovRegistry final : public VerilatedVirtualBase {
// MEMBERS
std::vector<std::unique_ptr<VlCovergroupType>> m_types; // Creation order
std::unordered_map<std::string, VlCovergroupType*> m_byName; // Lookup, borrowed
// PRIVATE METHODS
VlCovergroupType* findType(const char* typeName) const; // nullptr if unknown
VlCovergroupType* findOrCreateType(const char* typeName);
public:
// CONSTRUCTORS
VlCovRegistry() = default;
~VlCovRegistry() override;
VL_UNCOPYABLE(VlCovRegistry);
// METHODS
// Find-or-create the type node, then add an instance to it. typeName is the
// generated covergroup class name, already --protect-ids obfuscated, and is
// the same string that keys the coverage database's hier/page.
VlCovergroupInst* newCovergroupInst(const char* typeName);
/// Type coverage of a covergroup type (get_coverage()); see
/// VlCovergroupType::coverage(). typeWeight is its type_option.weight.
double typeCoverage(const char* typeName, IData typeWeight, VlFileLineDebug fileline);
// ---- introspection (see VlCovergroupType) ----
// typeName is the obfuscated generated name, so a test using these under
// --protect-ids must pass the obfuscated string; the no-argument form does not.
uint32_t liveInstanceCount() const; // Summed over every type
uint32_t createdInstanceCount() const; // Summed over every type
uint32_t liveInstanceCount(const char* typeName) const; // 0 if type unknown
uint32_t createdInstanceCount(const char* typeName) const; // 0 if type unknown
uint32_t retiredInstanceCount(const char* typeName) const; // 0 if type unknown
double retiredCoverage(const char* typeName) const; // -1.0 if type unknown or none
};
//=============================================================================
// VlCovInstHandle
/// The generated covergroup class's link to its instance node. Attach-counting:
/// the registry owns the node, but the handles are what keep it reachable, and
/// the last one to go retires it.
///
/// Must stay copyable: every generated clone() copy-constructs. A copy shares
/// the node, and so the bin counts -- pre-existing covergroup-copy aliasing.
/// Attach counting makes that lifetime-safe, not correct.
class VlCovInstHandle final {
// MEMBERS
VlCovergroupInst* m_p = nullptr; // Attach-counted; the registry owns the node
const IData* m_weightp = nullptr; // Owning object's option.weight, if lent to m_p
// PRIVATE METHODS
// Drop one attach count, retiring the node if that was the last handle.
// Nothing may touch instp afterwards: retire() may have freed it.
static void release(VlCovergroupInst* instp, const IData* weightp) {
if (VL_UNCOVERABLE(!instp)) return; // Never attach()ed; codegen always does
instp->unlendWeight(weightp);
if (instp->attachDec()) instp->typep()->retire(instp);
}
public:
// CONSTRUCTORS
VlCoverCross() = default;
VlCovInstHandle() = default;
// The copy's owning object lends no weight; the node keeps reading the lender's.
VlCovInstHandle(const VlCovInstHandle& o)
: m_p{o.m_p} {
if (VL_UNCOVERABLE(!m_p)) return; // Unbound source; see release above
m_p->attachInc();
}
// Deleted, not implemented: nothing generates an assignment, and the
// implicit one would copy m_p raw -- no attachInc, no release.
VlCovInstHandle& operator=(const VlCovInstHandle&) = delete;
~VlCovInstHandle() { release(m_p, m_weightp); }
// 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;
// Bind to a freshly created node, taking over the attach count of 1 it was
// created with. Called once, from the generated covergroup constructor.
void attach(VlCovergroupInst* p) { m_p = p; }
// Let the node read the owning object's option.weight until this handle is
// destroyed. Called once, from the generated constructor, after attach().
void lendWeight(const IData* weightp, VlFileLineDebug fileline) {
m_weightp = weightp;
m_p->lendWeight(weightp, fileline);
}
VlCovergroupInst* p() const { return m_p; }
};
#endif // Guard
+4 -4
View File
@@ -105,9 +105,9 @@ private:
int m_msb; // Inclusive upper bit for scalar path or element index for unpacked
int m_rhsLsb; // Destination index that maps to RHS index 0
const void* m_rhsDatap; // Pointer to RHS storage
int m_bitLsb = 0;
int m_bitMsb = 0;
int m_elemWidth = 0;
int m_bitLsb = 0; // Bit's LSB
int m_bitMsb = 0; // Bit's MSB
int m_elemWidth = 0; // Element width
};
std::vector<Entry> m_entries; // Sorted by msb, non-overlapping
@@ -303,7 +303,7 @@ public:
}
template <typename T>
T readIndex(T origVal, int index) const {
T readIndex(const T origVal, int index) const {
if (m_entries.empty()) return origVal;
T result = origVal;
+26 -10
View File
@@ -1438,8 +1438,8 @@ inline void _vl_insert_WI(WDataOutP iowp, IData ld, int hbit, int lbit, int rbit
const int nbitsonright = VL_EDATASIZE - loffset; // bits that end up in lword
iowp[lword] = (iowp[lword] & ~linsmask) | ((lde << loffset) & linsmask);
// Prevent unsafe write where lword was final writable location and hword is
// out-of-bounds.
if (VL_LIKELY(!(hword == rword && roffset == 0))) {
// out-of-bounds. rbits==0 means the caller guarantees bounds.
if (VL_LIKELY(!(rbits && hword >= VL_WORDS_I(rbits)))) {
iowp[hword]
= (iowp[hword] & ~hinsmask) | ((lde >> nbitsonright) & (hinsmask & cleanmask));
}
@@ -2948,8 +2948,8 @@ inline void VL_ZERO_INIT_QUEUE_ELEM(VlWide<N_Words>& elem) {
for (size_t j = 0; j < N_Words; ++j) { elem.at(j) = 0; }
}
// This specialization works for both VlQueue<CData> (and similar) as well
// as VlQueue<VlWide<N>>.
// These overloads work for both VlQueue<CData> (and similar) as well
// as VlQueue<VlWide<N>>, including queues with different element types.
template <typename T>
inline void VL_COPY_Q(VlQueue<T>& q, const VlQueue<T>& from, int /*lbits*/, int srcElementBits,
int dstElementBits) {
@@ -2971,16 +2971,32 @@ inline void VL_COPY_Q(VlQueue<T>& q, const VlQueue<T>& from, int /*lbits*/, int
}
}
// This specialization works for both VlQueue<CData> (and similar) as well
// as VlQueue<VlWide<N>>.
template <typename T>
inline void VL_REVCOPY_Q(VlQueue<T>& q, const VlQueue<T>& from, int lbits, int srcElementBits,
int dstElementBits) {
// Keep the same-type overload separate for its direct-copy optimization and alias handling;
// differently typed queues cannot alias.
template <typename T_Dst, typename T_Src>
inline typename std::enable_if<!std::is_same<T_Dst, T_Src>::value>::type
VL_COPY_Q(VlQueue<T_Dst>& q, const VlQueue<T_Src>& from, int /*lbits*/, int srcElementBits,
int dstElementBits) {
const size_t srcTotalBits = from.size() * srcElementBits;
const size_t dstSize = (srcTotalBits + dstElementBits - 1) / dstElementBits;
q.renew(dstSize);
for (size_t i = 0; i < dstSize; ++i) { VL_ZERO_INIT_QUEUE_ELEM(q.atWrite(i)); }
for (size_t bitIndex = 0; bitIndex < srcTotalBits; ++bitIndex) {
VL_SET_QUEUE_BIT(q, dstElementBits, bitIndex,
VL_GET_QUEUE_BIT(from, srcElementBits, bitIndex));
}
}
// This works for both VlQueue<CData> (and similar) as well as VlQueue<VlWide<N>>,
// including queues with different element types.
template <typename T_Dst, typename T_Src>
inline void VL_REVCOPY_Q(VlQueue<T_Dst>& q, const VlQueue<T_Src>& from, int lbits,
int srcElementBits, int dstElementBits) {
const size_t srcTotalBits = from.size() * srcElementBits;
const size_t dstSize = (srcTotalBits + dstElementBits - 1) / dstElementBits;
// Always make a copy to handle the case where q and from are the same queue
VlQueue<T> srcCopy = from;
VlQueue<T_Src> srcCopy = from;
// Initialize all elements to zero using appropriate method
q.renew(dstSize);
+9
View File
@@ -207,6 +207,11 @@ protected:
// Used by scopeInsert, scopeFind, scopeErase, scopeNameMap
mutable VerilatedMutex m_nameMutex; // Protect m_nameMap
VerilatedScopeNameMap m_nameMap VL_GUARDED_BY(m_nameMutex);
// Map of <interface_reference_name, interface reference>
// Used by ifaceRefInsert, ifaceRefFind, ifaceRefErase, ifaceRefMap
mutable VerilatedMutex m_ifaceRefMutex; // Protect m_ifaceRefMap
VerilatedIfaceRefMap m_ifaceRefMap VL_GUARDED_BY(m_ifaceRefMutex);
};
//======================================================================
@@ -270,6 +275,10 @@ public: // But only for verilated*.cpp
void scopeInsert(const VerilatedScope* scopep) VL_MT_SAFE;
void scopeErase(const VerilatedScope* scopep) VL_MT_SAFE;
// METHODS - interface references - INTERNAL only for verilated*.cpp
void ifaceRefInsert(const VerilatedIfaceRef& ifaceRef) VL_MT_SAFE;
void ifaceRefErase(const std::string& fullname, const VerilatedScope* scopep) VL_MT_SAFE;
// METHODS - file IO - INTERNAL only for verilated*.cpp
IData fdNewMcd(const char* filenamep) VL_MT_SAFE_EXCLUDES(m_fdMutex) {
+1 -1
View File
@@ -169,7 +169,7 @@ class VlExecutionProfiler final : public VlExecutionProfilerBase {
// STATE
VerilatedContext& m_context; // The context this profiler is under
static thread_local ExecutionTrace t_trace; // thread-local trace buffers
mutable VerilatedMutex m_mutex;
mutable VerilatedMutex m_mutex; // Mutex protecting traces
// Map from thread id to &t_trace of given thread
std::map<uint32_t, ExecutionTrace*> m_traceps VL_GUARDED_BY(m_mutex);
+59 -23
View File
@@ -674,10 +674,38 @@ void VlRandomVar::set(const std::string& idx, const std::string& val) const {
}
}
void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) {
const IData hash = VL_RANDOM_RNG_I(rngr) & ((1 << bits) - 1);
void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits,
const std::vector<std::string>* layerVarsp) {
// layerVarsp scopes sampling to the current phase's own layer, so a
// phased solve's diversity constraint can't be built entirely out of a
// later phase's (still-unsolved) variable instead of this one's.
std::vector<const VlRandomVar*>& vars = m_randomConstraintVars;
vars.clear();
int varBits = 0;
for (const auto& var : m_vars) varBits += var.second->totalWidth();
if (layerVarsp) {
for (const auto& name : *layerVarsp) {
const auto it = m_vars.find(name);
// buildSolveLayers() only ever adds a name after confirming
// it's already in m_vars.
assert(it != m_vars.end());
vars.push_back(it->second.get());
varBits += it->second->totalWidth();
}
} else {
for (const auto& var : m_vars) {
vars.push_back(var.second.get());
varBits += var.second->totalWidth();
}
}
if (varBits == 0) {
// Nothing to sample (e.g. a still-unsized queue/dynamic array, in a
// layer or the whole class) -- tautology instead of the degenerate
// empty-operand expression the loop below would otherwise build.
os << "(= #b1 #b1)";
return;
}
const IData hash = VL_RANDOM_RNG_I(rngr) & ((1 << bits) - 1);
os << "(= #b";
for (int i = bits - 1; i >= 0; i--) os << (VL_BITISSET_I(hash, i) ? '1' : '0');
if (bits > 1) os << " (concat";
@@ -685,11 +713,12 @@ void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) {
IData varBitsLeft = varBits;
IData varBitsWant = (varBits + 1) / 2;
if (varBits > 2) os << " (bvxor";
for (const auto& var : m_vars) {
for (int j = 0; j < var.second->totalWidth(); j++, varBitsLeft--) {
for (const auto& varp : vars) { // LCOV_EXCL_BR_LINE - reservoir-sampling below
// always forces its last pick before exhausting vars
for (int j = 0; j < varp->totalWidth(); j++, varBitsLeft--) {
const bool doEmit = (VL_RANDOM_RNG_I(rngr) % varBitsLeft) < varBitsWant;
if (doEmit) {
var.second->emitExtract(os, j);
varp->emitExtract(os, j);
if (--varBitsWant == 0) break;
}
}
@@ -779,7 +808,7 @@ bool VlRandomizer::next_check_only(VlRNGReseeds& rngr) { return nextRandomize(rn
bool VlRandomizer::next(VlRNGReseeds& rngr) { return nextRandomize(rngr, false); }
bool VlRandomizer::nextRandomize(VlRNGReseeds& rngr, bool checkOnly) {
if (!checkOnly && m_vars.empty() && m_unique_arrays.empty()) return true;
if (!checkOnly && m_vars.empty() && m_uniqueArrays.empty()) return true;
if (checkOnly && m_vars.empty()) return true; // No rand members: trivially SAT
VlSolverSession& sess = s_solverSession;
const VerilatedLockGuard lock{sess.m_mutex};
@@ -814,21 +843,27 @@ bool VlRandomizer::nextRandomize(VlRNGReseeds& rngr, bool checkOnly) {
std::vector<std::string> VlRandomizer::buildUniqueExprs() const {
std::vector<std::string> exprs;
if (m_unique_arrays.empty()) return exprs;
if (m_uniqueArrays.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;
for (const auto& [key, arrNameVec] : m_uniqueArrays) {
uint32_t matchingElementsCount = 0;
std::ostringstream varsString;
for (const auto& baseName : arrNameVec) {
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
matchingElementsCount += var.countMatchingElements(*arrVarsp, baseName);
var.emitGetValue(varsString);
}
if (matchingElementsCount < 2) continue;
std::ostringstream os;
os << "(__Vbv (distinct ";
var.emitGetValue(os);
os << "))";
os << "(__Vbv (distinct " << varsString.str() << "))";
exprs.push_back(os.str());
}
return exprs;
@@ -1577,7 +1612,7 @@ void VlRandomizer::clearConstraints() {
m_constraints_line.clear();
m_solveBefore.clear();
m_softConstraints.clear();
m_unique_arrays.clear(); // Re-registered by constraint setup
m_uniqueArrays.clear(); // Re-registered by constraint setup
// Keep m_vars for class member randomization
}
@@ -1590,7 +1625,7 @@ void VlRandomizer::clearAll() {
m_randcConstraintHash = 0;
}
void VlRandomizer::markRandc(const char* name) { m_randcVarNames.insert(name); }
void VlRandomizer::markRandc(const std::string& name) { m_randcVarNames.insert(name); }
void VlRandomizer::solveBefore(const std::string& beforeName, const std::string& afterName) {
m_solveBefore.emplace_back(beforeName, afterName);
@@ -1748,8 +1783,9 @@ bool VlRandomizer::solvePhaseValues(VlSolverSession& sess, VlRNG& rngr,
// Try diversity: add random constraint, re-check. If sat, get
// updated (more diverse) values. If unsat, keep baseline values.
// Scoped to this layer's own vars -- see randomConstraint's comment.
os << "(assert ";
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN);
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN, &layerVars);
os << ")\n";
os << "(check-sat)\n";
if (sess.readStatus() == VlSolverStatus::SAT) {
+208 -21
View File
@@ -259,10 +259,13 @@ class VlRandomizer VL_NOT_FINAL {
m_constraints_line; // fileline content of the constraint for unsat constraints
std::vector<std::string> m_softConstraints; // Soft constraints
std::map<std::string, std::shared_ptr<const VlRandomVar>> m_vars; // Solver-dependent
// Scratch buffer for randomConstraint(), reused across calls
std::vector<const VlRandomVar*> m_randomConstraintVars;
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; // Arrays whose elements must be distinct
// Arrays whose elements must be distinct grouped by unique constraint
std::map<uint32_t, std::unordered_set<std::string>> m_uniqueArrays;
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
@@ -279,7 +282,8 @@ class VlRandomizer VL_NOT_FINAL {
bool hasFrozenVar() const; // true if any var is currently rand_mode(0)-frozen
// PRIVATE METHODS
void randomConstraint(std::ostream& os, VlRNG& rngr, int bits);
void randomConstraint(std::ostream& os, VlRNG& rngr, int bits,
const std::vector<std::string>* layerVarsp = nullptr);
// Fetch the model and write it into the registered variables.
bool applyModel(VlSolverSession& sess);
bool parseModel(std::istream& is, size_t requested);
@@ -466,16 +470,16 @@ public:
// Mark a variable as rand_mode-disabled: solver keeps it in m_vars
// (so constraints still reference it) but skips write-back after solving.
void set_var_disabled(const char* name) { m_disabledVars.insert(name); }
void set_var_disabled(const std::string& name) { m_disabledVars.insert(name); }
// Clear disabled state for a variable
void clear_var_disabled(const char* name) { m_disabledVars.erase(name); }
void clear_var_disabled(const std::string& name) { m_disabledVars.erase(name); }
// --- write_var to register variables ---
// Register scalar variable (non-struct, basic type)
template <typename T>
typename std::enable_if<!VlContainsCustomStruct<T>::value && !IsVlUnpacked<T>::value,
void>::type
write_var(T& var, int width, const char* name, int dimension,
write_var(T& var, int width, const std::string& name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (m_vars.find(name) != m_vars.end()) return;
// TODO: make_unique once VlRandomizer is per-instance not per-ref
@@ -488,7 +492,7 @@ public:
typename std::enable_if<VlIsCustomStruct<T>::value, void>::type
write_var(T& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
modifyMembers(var, var.memberIndices(), name);
modifyMembers(var, var.memberIndices(), name, randmodeIdx);
}
// Register queue of non-struct types
@@ -513,7 +517,7 @@ public:
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
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, {}, {});
record_struct_arr(var, name, dimension, {}, {}, randmodeIdx);
}
// Register unpacked array of non-struct types
template <typename T, std::size_t N_Depth>
@@ -539,7 +543,7 @@ public:
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
write_var(VlUnpacked<T, N_Depth>& 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, {}, {});
record_struct_arr(var, name, dimension, {}, {}, randmodeIdx);
}
// Register associative array of non-struct types
@@ -567,7 +571,7 @@ public:
typename std::enable_if<VlContainsCustomStruct<T_Value>::value, void>::type
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 (dimension > 0) record_struct_arr(var, name, dimension, {}, {});
record_struct_arr(var, name, dimension, {}, {}, randmodeIdx);
}
// --- Record Arrays: flat and struct ---
@@ -582,10 +586,23 @@ public:
++m_index;
}
template <typename T>
typename std::enable_if<!std::is_class<T>::value || VlIsVlWide<T>::value, void>::type
update_arr_table(T& var, const std::string& name, int /*dimension*/) {
const std::string key = generateKey(name, m_index++);
const auto it = m_arr_vars.find(key);
assert(it != m_arr_vars.end());
const ArrayInfo& arrayInfo = *it->second;
it->second = std::make_shared<const ArrayInfo>(arrayInfo.m_name, &var, arrayInfo.m_index,
arrayInfo.m_indices, arrayInfo.m_idxWidths);
}
// 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); }
void rand_unique(const std::string& name, const uint32_t key) {
m_uniqueArrays[key].insert(name);
}
// Recursively record all elements in an unpacked array
template <typename T, std::size_t N_Depth>
@@ -603,6 +620,15 @@ public:
}
}
// Recursively update pointers to all elements in an unpacked array
template <typename T, std::size_t N_Depth>
void update_arr_table(VlUnpacked<T, N_Depth>& var, const std::string& name, int dimension) {
assert(dimension > 0);
for (size_t i = 0; i < N_Depth; ++i) {
update_arr_table(var[i], name + "[" + std::to_string(i) + "]", dimension - 1);
}
}
// Recursively record all elements in a queue
template <typename T, size_t N_MaxSize>
void record_arr_table(VlQueue<T, N_MaxSize>& var, const std::string& name, int dimension,
@@ -618,6 +644,15 @@ public:
}
}
// Recursively update pointers to all elements in a queue
template <typename T, size_t N_MaxSize>
void update_arr_table(VlQueue<T, N_MaxSize>& var, const std::string& name, int dimension) {
assert(dimension > 0);
for (size_t i = 0; i < var.size(); ++i) {
update_arr_table(var.atWrite(i), name + "[" + std::to_string(i) + "]", dimension - 1);
}
}
// Recursively record all elements in an associative array
template <typename T_Key, typename T_Value>
void record_arr_table(VlAssocArray<T_Key, T_Value>& var, const std::string& name,
@@ -648,11 +683,27 @@ public:
}
}
// Recursively update pointers to all elements in an associative array
template <typename T_Key, typename T_Value>
void update_arr_table(VlAssocArray<T_Key, T_Value>& var, const std::string& name,
int dimension) {
assert(dimension > 0);
for (auto it = var.begin(); it != var.end(); ++it) {
const T_Key& key = it->first;
std::string indexed_name;
std::vector<size_t> integral_index;
size_t idx_width = 0;
process_key(key, indexed_name, integral_index, name, idx_width);
update_arr_table(var.atWrite(key), indexed_name, dimension - 1);
}
}
// Register a single structArray element via write_var
template <typename T>
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
record_struct_arr(T& var, const std::string& name, int /*dimension*/,
std::vector<IData> indices, std::vector<size_t> idxWidths) {
std::vector<IData> indices, std::vector<size_t> idxWidths,
std::uint32_t randmodeIdx) {
std::ostringstream oss;
for (size_t i = 0; i < indices.size(); ++i) {
oss << std::hex << std::setw(int(idxWidths[i] / 4)) << std::setfill('0')
@@ -660,43 +711,75 @@ public:
if (i < indices.size() - 1) oss << ".";
}
write_var(var, 1ULL,
oss.str().length() > 0 ? (name + "." + oss.str()).c_str() : name.c_str(), 1ULL);
oss.str().length() > 0 ? (name + "." + oss.str()).c_str() : name.c_str(), 1ULL,
randmodeIdx);
}
template <typename T>
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
update_struct_arr(T& var, const std::string& name) {
update_var(var, name.c_str());
}
// Recursively process VlUnpacked of structs
template <typename T, std::size_t N_Depth>
void record_struct_arr(VlUnpacked<T, N_Depth>& var, const std::string& name, int dimension,
std::vector<IData> indices, std::vector<size_t> idxWidths) {
std::vector<IData> indices, std::vector<size_t> idxWidths,
std::uint32_t randmodeIdx) {
if (dimension > 0 && N_Depth != 0) {
constexpr size_t idx_width = 1 << VL_CLOG2_CE_Q(VL_CLOG2_CE_Q(N_Depth) + 1);
idxWidths.push_back(idx_width);
for (size_t i = 0; i < N_Depth; ++i) {
indices.push_back(i);
record_struct_arr(var.operator[](i), name, dimension - 1, indices, idxWidths);
record_struct_arr(var.operator[](i), name, dimension - 1, indices, idxWidths,
randmodeIdx);
indices.pop_back();
}
}
}
template <typename T, std::size_t N_Depth>
void update_struct_arr(VlUnpacked<T, N_Depth>& var, const std::string& name) {
constexpr size_t idx_width = 1 << VL_CLOG2_CE_Q(VL_CLOG2_CE_Q(N_Depth) + 1);
for (size_t i = 0; i < N_Depth; ++i) {
std::ostringstream oss;
oss << name << "." << std::hex << std::setw(int(idx_width / 4)) << std::setfill('0')
<< static_cast<int>(i);
update_struct_arr(var[i], oss.str());
}
}
// Recursively process VlQueue of structs
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) {
std::vector<IData> indices, std::vector<size_t> idxWidths,
std::uint32_t randmodeIdx) {
if ((dimension > 0) && (var.size() != 0)) {
idxWidths.push_back(32);
for (size_t i = 0; i < var.size(); ++i) {
indices.push_back(i);
record_struct_arr(var.atWrite(i), name, dimension - 1, indices, idxWidths);
record_struct_arr(var.atWrite(i), name, dimension - 1, indices, idxWidths,
randmodeIdx);
indices.pop_back();
}
}
}
template <typename T, size_t N_MaxSize>
void update_struct_arr(VlQueue<T, N_MaxSize>& var, const std::string& name) {
for (size_t i = 0; i < var.size(); ++i) {
std::ostringstream oss;
oss << name << "." << std::hex << std::setw(8) << std::setfill('0')
<< static_cast<int>(i);
update_struct_arr(var.atWrite(i), oss.str());
}
}
// Recursively process associative arrays of structs
template <typename T_Key, typename T_Value>
void record_struct_arr(VlAssocArray<T_Key, T_Value>& var, const std::string& name,
int dimension, const std::vector<IData>& indices,
const std::vector<size_t>& idxWidths) {
const std::vector<size_t>& idxWidths, std::uint32_t randmodeIdx) {
if ((dimension > 0) && (!var.empty())) {
for (auto it = var.begin(); it != var.end(); ++it) {
const T_Key& key = it->first;
@@ -714,23 +797,127 @@ public:
std::string result = oss.str();
result.insert(result.begin(), int(idx_width / 4) - result.size(), '0');
record_struct_arr(var.atWrite(key), name + "." + result, dimension - 1, indices,
idxWidths);
idxWidths, randmodeIdx);
}
}
}
template <typename T_Key, typename T_Value>
void update_struct_arr(VlAssocArray<T_Key, T_Value>& var, const std::string& name) {
for (auto it = var.begin(); it != var.end(); ++it) {
const T_Key& key = it->first;
std::string indexed_name;
std::vector<size_t> integral_index;
size_t idx_width = 0;
process_key(key, indexed_name, integral_index, name, idx_width);
std::ostringstream oss;
for (size_t i = 0; i < integral_index.size(); ++i) {
oss << std::hex << static_cast<int>(integral_index[i]);
}
std::string result = oss.str();
result.insert(result.begin(), int(idx_width / 4) - result.size(), '0');
update_struct_arr(var.atWrite(key), name + "." + result);
}
}
// --- Helper functions ---
// Helper: Register all members of a user-defined struct
template <typename T, std::size_t... I>
void modifyMembers(T& obj, std::index_sequence<I...>, const std::string& baseName) {
void modifyMembers(T& obj, std::index_sequence<I...>, const std::string& baseName,
std::uint32_t randmodeIdx) {
// Use the indices to access each member via std::get
(void)std::initializer_list<int>{
(write_var(std::get<I>(obj.getMembers(obj)), obj.memberWidth()[I],
(baseName + "." + obj.memberNames()[I]).c_str(), obj.memberDimension()[I]),
(baseName + "." + obj.memberNames()[I]).c_str(), obj.memberDimension()[I],
randmodeIdx),
0)...};
}
template <typename T, std::size_t... I>
void updateMembers(T& obj, std::index_sequence<I...>, const std::string& baseName) {
(void)std::initializer_list<int>{
(update_var(std::get<I>(obj.getMembers(obj)),
(baseName + "." + obj.memberNames()[I]).c_str()),
0)...};
}
template <typename T>
typename std::enable_if<!VlContainsCustomStruct<T>::value && !IsVlUnpacked<T>::value,
void>::type
update_var(T& var, const char* name) {
auto it = m_vars.find(name);
assert(it != m_vars.end());
it->second = std::make_shared<const VlRandomVar>(
name, it->second->width(), &var, it->second->dimension(), it->second->randModeIdx());
}
template <typename T>
typename std::enable_if<VlIsCustomStruct<T>::value, void>::type update_var(T& var,
const char* name) {
updateMembers(var, var.memberIndices(), name);
}
template <typename T, size_t N_MaxSize>
typename std::enable_if<!VlContainsCustomStruct<T>::value, void>::type
update_var(VlQueue<T, N_MaxSize>& var, const char* name) {
auto it = m_vars.find(name);
assert(it != m_vars.end());
const int dimension = it->second->dimension();
it->second = std::make_shared<const VlRandomArrayVarTemplate<VlQueue<T, N_MaxSize>>>(
name, it->second->width(), &var, dimension, it->second->randModeIdx());
m_index = 0;
update_arr_table(var, name, dimension);
}
template <typename T, size_t N_MaxSize>
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
update_var(VlQueue<T, N_MaxSize>& var, const char* name) {
update_struct_arr(var, name);
}
template <typename T, std::size_t N_Depth>
typename std::enable_if<!VlContainsCustomStruct<T>::value, void>::type
update_var(VlUnpacked<T, N_Depth>& var, const char* name) {
auto it = m_vars.find(name);
assert(it != m_vars.end());
const int dimension = it->second->dimension();
it->second = std::make_shared<const VlRandomArrayVarTemplate<VlUnpacked<T, N_Depth>>>(
name, it->second->width(), &var, dimension, it->second->randModeIdx());
m_index = 0;
update_arr_table(var, name, dimension);
}
template <typename T, std::size_t N_Depth>
typename std::enable_if<VlContainsCustomStruct<T>::value, void>::type
update_var(VlUnpacked<T, N_Depth>& var, const char* name) {
update_struct_arr(var, name);
}
template <typename T_Key, typename T_Value>
typename std::enable_if<!VlContainsCustomStruct<T_Value>::value, void>::type
update_var(VlAssocArray<T_Key, T_Value>& var, const char* name) {
auto it = m_vars.find(name);
assert(it != m_vars.end());
const int dimension = it->second->dimension();
std::vector<size_t> keyWidths;
VlRandomAssocKeyWidths<VlAssocArray<T_Key, T_Value>>::push(keyWidths);
it->second
= std::make_shared<const VlRandomArrayVarTemplate<VlAssocArray<T_Key, T_Value>>>(
name, it->second->width(), &var, dimension, it->second->randModeIdx(), keyWidths);
m_index = 0;
update_arr_table(var, name, dimension);
}
template <typename T_Key, typename T_Value>
typename std::enable_if<VlContainsCustomStruct<T_Value>::value, void>::type
update_var(VlAssocArray<T_Key, T_Value>& var, const char* name) {
update_struct_arr(var, name);
}
// Helper: Generate unique variable key from name and index
static std::string generateKey(const std::string& name, int idx) {
if (!name.empty() && name[0] == '\\') {
@@ -765,7 +952,7 @@ public:
void disable_soft(const std::string& varName);
void clearConstraints();
void clearAll(); // Clear both constraints and variables
void markRandc(const char* name); // Mark variable as randc for cyclic tracking
void markRandc(const std::string& name); // Mark variable as randc for cyclic tracking
void solveBefore(const std::string& beforeName,
const std::string& afterName); // Register solve-before ordering
void set_randmode(const VlQueue<CData>& randmode) { m_randmodep = &randmode; }
+56 -46
View File
@@ -99,46 +99,6 @@ package std;
endfunction
endclass
// IEEE 1800-specified standard "semaphore"
class semaphore;
protected int m_keyCount;
protected int m_nextKeyCount = '1;
protected longint unsigned m_ticket = 0;
protected longint unsigned m_nextTicket = 0;
function new(int keyCount = 0);
m_keyCount = keyCount;
endfunction
function void put(int keyCount = 1);
m_keyCount += keyCount;
endfunction
task get(int keyCount = 1);
`ifdef VERILATOR_TIMING
longint unsigned ticket;
// Fast path: take if keys fit AND either no one is queued, or
// the head still doesn't fit (so we're not stealing its keys).
if (m_keyCount >= keyCount && m_nextKeyCount > m_keyCount) begin
m_keyCount -= keyCount;
return;
end
ticket = m_nextTicket++;
wait (m_ticket == ticket);
m_nextKeyCount = keyCount;
wait (m_keyCount >= keyCount);
m_keyCount -= keyCount;
m_ticket++;
`endif
endtask
function int try_get(int keyCount = 1);
if (m_keyCount < keyCount) return 0;
m_keyCount -= keyCount;
return 1;
endfunction
endclass
// IEEE 1800-specified standard "process"
class process;
typedef enum {
@@ -243,6 +203,56 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc
endfunction
endclass
// IEEE 1800-specified standard "semaphore"
class semaphore;
typedef struct {
process proc;
int reqKeys;
} procKeys;
procKeys m_queue[$];
protected int m_keyCount;
local task dropKilled;
// Drop killed processes from the queue
`ifdef VERILATOR_TIMING
while (m_queue.size() > 0 && m_queue[0].proc.status() == process::KILLED) m_queue.pop_front();
`endif
endtask
function new(int keyCount = 0);
m_keyCount = keyCount;
endfunction
function void put(int keyCount = 1);
m_keyCount += keyCount;
endfunction
task get(int keyCount = 1);
`ifdef VERILATOR_TIMING
dropKilled();
// Fast path: take if keys fit AND either no one is queued, or
// the head still doesn't fit (so we're not stealing its keys).
if (m_keyCount >= keyCount && (m_queue.size() == 0 || m_queue[0].reqKeys > keyCount)) begin
m_keyCount -= keyCount;
return;
end
m_queue.push_back('{process::self(), keyCount});
while (m_queue[0].proc != process::self()) begin
wait (m_queue[0].proc.status() == process::KILLED || m_queue[0].proc == process::self());
dropKilled();
end
wait (m_keyCount >= keyCount);
m_keyCount -= keyCount;
m_queue.pop_front();
`endif
endtask
function int try_get(int keyCount = 1);
if (m_keyCount < keyCount) return 0;
m_keyCount -= keyCount;
return 1;
endfunction
endclass
// IEEE 1800-specified standard "std::randomize"
function int randomize();
randomize = 0;
@@ -253,7 +263,7 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc
// them as such currently, so named with a unique prefix
typedef struct {
string name;
int weight;
int weight = 1;
int goal;
string comment;
int at_least;
@@ -266,7 +276,7 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc
} vl_covergroup_options_t;
typedef struct {
int weight;
int weight = 1;
int goal;
string comment;
int at_least;
@@ -275,7 +285,7 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc
} vl_coverpoint_options_t;
typedef struct {
int weight;
int weight = 1;
int goal;
string comment;
int at_least;
@@ -284,7 +294,7 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc
} vl_cross_options_t;
typedef struct {
int weight;
int weight = 1;
int goal;
string comment;
bit strobe;
@@ -294,14 +304,14 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc
} vl_covergroup_type_options_t;
typedef struct {
int weight;
int weight = 1;
int goal;
string comment;
real real_interval;
} vl_coverpoint_type_options_t;
typedef struct {
int weight;
int weight = 1;
int goal;
string comment;
} vl_cross_type_options_t;
+20 -5
View File
@@ -38,8 +38,8 @@
// See also V3Ast::VNumRange
class VerilatedRange final {
int m_left = 0;
int m_right = 0;
int m_left = 0; // Left side of range (pre-':')
int m_right = 0; // Right side of range (post-':')
protected:
friend class VerilatedVarProps;
@@ -158,7 +158,18 @@ public:
VerilatedVarFlags vldir() const {
return static_cast<VerilatedVarFlags>(static_cast<int>(m_vlflags) & VLVF_MASK_DIR);
}
uint32_t entSize() const VL_MT_SAFE;
uint32_t entSize() const VL_MT_SAFE {
if (m_entSize) return m_entSize;
switch (vltype()) {
case VLVT_PTR: return sizeof(void*);
case VLVT_UINT8: return sizeof(CData);
case VLVT_UINT16: return sizeof(SData);
case VLVT_UINT32: return sizeof(IData);
case VLVT_UINT64: return sizeof(QData);
case VLVT_WDATA: return VL_WORDS_I(entBits()) * sizeof(IData);
default: return 0; // LCOV_EXCL_LINE
}
}
uint32_t entBits() const VL_MT_SAFE {
uint32_t bits = 1;
for (auto it : m_packed) bits *= it.elements();
@@ -214,7 +225,11 @@ public:
: 0;
}
// Total size in bytes (note DPI limited to 4GB)
size_t totalSize() const;
size_t totalSize() const {
size_t size = entSize();
for (int udim = 0; udim < udims(); ++udim) size *= m_unpacked[udim].elements();
return size;
}
// Adjust a data pointer to access a given array element, NULL if something goes bad
void* datapAdjustIndex(void* datap, int dim, int indx) const VL_MT_SAFE;
};
@@ -268,7 +283,7 @@ class VerilatedVar final : public VerilatedVarProps {
m_forceControlSignals; // Force control signals
protected:
const bool m_isParam;
const bool m_isParam; // From a parameter
friend class VerilatedScope;
// CONSTRUCTORS
VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype,
+9
View File
@@ -72,4 +72,13 @@ public:
~VerilatedHierarchyMap() = default;
};
// Map of sorted interface reference names to the concrete interface they refer to
// Keyed by value, as the full name is built at construction
// This is a class instead of typedef/using to allow forward declaration in verilated.h
class VerilatedIfaceRefMap final : public std::map<std::string, VerilatedIfaceRef, std::less<>> {
public:
VerilatedIfaceRefMap() = default;
~VerilatedIfaceRefMap() = default;
};
#endif // Guard
+1 -1
View File
@@ -140,7 +140,7 @@ class VlWorkerThread final {
};
// MEMBERS
mutable VerilatedMutex m_mutex;
mutable VerilatedMutex m_mutex; // Protection mutex
std::condition_variable_any m_cv;
// Only notify the condition_variable if the worker is waiting
bool m_waiting VL_GUARDED_BY(m_mutex) = false;
+5 -2
View File
@@ -296,8 +296,11 @@ void VlProcess::forkSyncOnKillClear(VlForkSyncState* forkSyncp) {
}
void VlProcess::state(int s) {
if (s == KILLED && m_state != KILLED && m_state != FINISHED && m_forkSyncOnKillp
&& !m_forkSyncOnKillDone) {
if (completed()) {
// We can't change the sttaus of completed process
return;
}
if (s == KILLED && m_forkSyncOnKillp && !m_forkSyncOnKillDone) {
m_forkSyncOnKillDone = true;
m_state = s;
m_forkSyncOnKillp->done();
+1 -31
View File
@@ -64,36 +64,6 @@
// Placeholder for compiling with --protect-ids
#define VL_UNKNOWN "<unknown>"
//=============================================================================
// VlFileLineDebug stores a SystemVerilog source code location. Used in VlCoroutineHandle for
// debugging purposes.
class VlFileLineDebug final {
// MEMBERS
#ifdef VL_DEBUG
const char* m_filename = nullptr;
int m_lineno = 0;
#endif
public:
// CONSTRUCTORS
// Construct
VlFileLineDebug() = default;
VlFileLineDebug(const char* filename, int lineno)
#ifdef VL_DEBUG
: m_filename{filename}
, m_lineno{lineno}
#endif
{
}
// METHODS
#ifdef VL_DEBUG
const char* filename() const { return m_filename; }
int lineno() const { return m_lineno; }
#endif
};
//=============================================================================
// VlCoroutineHandle is a non-copyable (but movable) coroutine handle. On resume, the handle is
// cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got
@@ -431,7 +401,7 @@ class VlForkSyncState final {
public:
size_t m_counter = 0; // When reaches 0, resume suspended coroutine
VlCoroutineHandle m_susp; // Coroutine to resume
bool m_inited = false;
bool m_inited = false; // Initialization complete
size_t m_pendingDones = 0; // done() calls seen before init() (e.g. early killed branch)
bool m_inDone = false; // Guard against re-entrant resume recursion from nested kills
bool m_resumePending = false; // Join reached zero again while inside done()
+3 -3
View File
@@ -147,9 +147,9 @@ private:
struct CallbackRecord final {
union { // The callback
const initCb_t m_initCb;
const dumpCb_t m_dumpCb;
const cleanupCb_t m_cleanupCb;
const initCb_t m_initCb; // Init-callback constructor
const dumpCb_t m_dumpCb; // Dump-callback constructor
const cleanupCb_t m_cleanupCb; // Cleanup-callback constructor
};
const uint32_t m_fidx; // The index of the tracing function
void* const m_userp; // The user pointer to pass to the callback (the symbol table)
+19 -1
View File
@@ -32,6 +32,7 @@
#include <array>
#include <atomic>
#include <deque>
#include <initializer_list>
#include <map>
#include <memory>
#include <set>
@@ -231,7 +232,7 @@ extern std::string VL_TO_STRING(SData lhs);
extern std::string VL_TO_STRING(IData lhs);
extern std::string VL_TO_STRING(QData lhs);
extern std::string VL_TO_STRING(double lhs);
inline std::string VL_TO_STRING(const std::string& obj) { return "\"" + obj + "\""; }
extern std::string VL_TO_STRING(const std::string& obj) VL_PURE;
template <std::size_t N_Words>
inline std::string VL_TO_STRING(const VlWide<N_Words>& obj) {
return VL_TO_STRING_W(N_Words, obj);
@@ -1084,6 +1085,15 @@ public:
// CONSTRUCTORS
// m_defaultValue isn't defaulted. Caller's constructor must do it.
VlAssocArray() = default;
// Construct with the given entries
explicit VlAssocArray(std::initializer_list<std::pair<const T_Key, T_Value>> init)
: m_map{init}
, m_defaultValue{} {}
// Construct with the given default value and entries
explicit VlAssocArray(const T_Value& defaultValue,
std::initializer_list<std::pair<const T_Key, T_Value>> init)
: m_map{init}
, m_defaultValue{defaultValue} {}
~VlAssocArray() = default;
VlAssocArray(const VlAssocArray&) = default;
VlAssocArray(VlAssocArray&&) = default;
@@ -1468,6 +1478,14 @@ public:
constexpr std::size_t size() const { return N_Depth; }
// Runtime slice v[loIdx +: N_Out], loIdx being an index into m_storage
template <std::size_t N_Out>
VlUnpacked<T_Value, N_Out> slice(int32_t loIdx) const {
VlUnpacked<T_Value, N_Out> out;
for (std::size_t i = 0; i < N_Out; ++i) out.m_storage[i] = m_storage[loIdx + i];
return out;
}
void fill(const T_Value& value) {
std::fill(std::begin(m_storage), std::end(m_storage), value);
}
+307 -137
View File
@@ -253,34 +253,34 @@ public:
if (m_fullname.empty()) m_fullname = std::string{m_scopep->name()} + '.' + m_varp->name();
return m_fullname.c_str();
}
virtual void* varDatap() const { return m_varp->datap(); }
CData* varCDatap() const {
virtual void* readDatap() const { return m_varp->datap(); }
CData* readCDatap() const {
VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_UINT8););
return reinterpret_cast<CData*>(varDatap());
return static_cast<CData*>(readDatap());
}
SData* varSDatap() const {
SData* readSDatap() const {
VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_UINT16););
return reinterpret_cast<SData*>(varDatap());
return static_cast<SData*>(readDatap());
}
IData* varIDatap() const {
IData* readIDatap() const {
VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_UINT32););
return reinterpret_cast<IData*>(varDatap());
return static_cast<IData*>(readDatap());
}
QData* varQDatap() const {
QData* readQDatap() const {
VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_UINT64););
return reinterpret_cast<QData*>(varDatap());
return static_cast<QData*>(readDatap());
}
EData* varEDatap() const {
EData* readEDatap() const {
VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_WDATA););
return reinterpret_cast<EData*>(varDatap());
return static_cast<EData*>(readDatap());
}
double* varRealDatap() const {
double* readRealDatap() const {
VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_REAL););
return reinterpret_cast<double*>(varDatap());
return static_cast<double*>(readDatap());
}
std::string* varStringDatap() const {
std::string* readStringDatap() const {
VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_STRING););
return reinterpret_cast<std::string*>(varDatap());
return static_cast<std::string*>(readDatap());
}
virtual uint32_t bitOffset() const { return 0; }
};
@@ -363,11 +363,13 @@ protected:
public:
explicit VerilatedVpioScope(const VerilatedScope* scopep)
: m_scopep{scopep} {
m_fullname = m_scopep->name();
if (std::strncmp(m_fullname, "TOP.", 4) == 0) m_fullname += 4;
m_fullname = vpiFullnamep(m_scopep->name());
m_name = m_scopep->identifier();
m_defname = m_scopep->defname();
}
static const char* vpiFullnamep(const char* namep) VL_PURE {
return (std::strncmp(namep, "TOP.", 4) == 0) ? namep + 4 : namep;
}
~VerilatedVpioScope() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioScope* castp(vpiHandle h) {
@@ -410,6 +412,9 @@ public:
m_name = name;
m_fullNameOverride = fullname;
}
VerilatedVpioVar(const VerilatedVar* varp, const VerilatedScope* scopep,
const std::string& name, const std::string& fullname)
: VerilatedVpioVar{varp, scopep, varp->datap(), name, fullname} {}
explicit VerilatedVpioVar(const VerilatedVpioVar* vop)
: VerilatedVpioVarBase{vop} {
if (vop) {
@@ -515,7 +520,7 @@ public:
const std::string localName = _vl_vpi_member_local_name(memberVarp->name());
return new VerilatedVpioVar{memberVarp, scopep(),
static_cast<uint8_t*>(varDatap()) + offset, localName,
static_cast<uint8_t*>(readDatap()) + offset, localName,
std::string{fullname()} + memberName.substr(parentLen)};
}
uint32_t type() const override {
@@ -540,7 +545,7 @@ public:
return m_fullname.c_str();
}
uint8_t* prevDatap() const { return m_prevDatap; }
void* varDatap() const override { return m_varDatap; }
void* readDatap() const override { return m_varDatap; }
void createPrevDatap() {
if (VL_UNLIKELY(!m_prevDatap)) {
m_prevDatap = new uint8_t[entSize()];
@@ -721,6 +726,17 @@ public:
uint32_t type() const override { return vpiModule; }
};
class VerilatedVpioInterface final : public VerilatedVpioScope {
public:
explicit VerilatedVpioInterface(const VerilatedScope* scopep)
: VerilatedVpioScope{scopep} {}
// cppcheck-suppress duplInheritedMember
static VerilatedVpioInterface* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioInterface*>(reinterpret_cast<VerilatedVpio*>(h));
}
uint32_t type() const override { return vpiInterface; }
};
class VerilatedVpioModuleIter final : public VerilatedVpio {
const std::vector<const VerilatedScope*>* m_vec;
std::vector<const VerilatedScope*>::const_iterator m_it;
@@ -778,6 +794,8 @@ public:
return (new VerilatedVpioScope{modp})->castVpiHandle();
} else if (itype == VerilatedScope::SCOPE_MODULE) {
return (new VerilatedVpioModule{modp})->castVpiHandle();
} else if (itype == VerilatedScope::SCOPE_INTERFACE) {
return (new VerilatedVpioInterface{modp})->castVpiHandle();
}
}
}
@@ -803,6 +821,90 @@ public:
uint32_t type() const override { return vpiPackage; }
};
class VerilatedVpioModport final : public VerilatedVpio {
const VerilatedScope* const m_scopep; // Interface the modport is within
const char* const m_name; // Modport name
const std::string m_fullname; // Interface full name + "." + modport name
public:
VerilatedVpioModport(const VerilatedScope* scopep, const char* namep)
: m_scopep{scopep}
, m_name{namep}
, m_fullname{std::string{VerilatedVpioScope::vpiFullnamep(scopep->name())} + "." + namep} {
}
~VerilatedVpioModport() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioModport* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioModport*>(reinterpret_cast<VerilatedVpio*>(h));
}
uint32_t type() const override { return vpiModport; }
const VerilatedScope* scopep() const { return m_scopep; }
const char* name() const override { return m_name; }
const char* fullname() const override { return m_fullname.c_str(); }
// IEEE 1800-2023 37.15
const char* defname() const override { return m_name; }
};
class VerilatedVpioIfaceRef final : public VerilatedVpio {
// Held by value, as a handle may outlive the model that registered it
const VerilatedIfaceRef m_ifaceRef;
public:
explicit VerilatedVpioIfaceRef(const VerilatedIfaceRef& ifaceRef)
: m_ifaceRef{ifaceRef} {}
~VerilatedVpioIfaceRef() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioIfaceRef* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioIfaceRef*>(reinterpret_cast<VerilatedVpio*>(h));
}
uint32_t type() const override { return vpiRefObj; }
const VerilatedIfaceRef* ifaceRefp() const { return &m_ifaceRef; }
const char* name() const override { return m_ifaceRef.name(); }
const char* fullname() const override {
return VerilatedVpioScope::vpiFullnamep(m_ifaceRef.fullname());
}
// IEEE 1800-2023 37.15: modport name, else the interface definition name
const char* defname() const override {
return m_ifaceRef.hasModport() ? m_ifaceRef.modport() : m_ifaceRef.scopep()->defname();
}
vpiHandle actual() const {
if (m_ifaceRef.hasModport()) {
return (new VerilatedVpioModport{m_ifaceRef.scopep(), m_ifaceRef.modport()})
->castVpiHandle();
}
return (new VerilatedVpioInterface{m_ifaceRef.scopep()})->castVpiHandle();
}
};
class VerilatedVpioInterfaceIter final : public VerilatedVpio {
const std::vector<const VerilatedScope*>* m_vec;
std::vector<const VerilatedScope*>::const_iterator m_it;
public:
explicit VerilatedVpioInterfaceIter(const std::vector<const VerilatedScope*>& vec)
: m_vec{&vec} {
m_it = m_vec->begin();
}
~VerilatedVpioInterfaceIter() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioInterfaceIter* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioInterfaceIter*>(reinterpret_cast<VerilatedVpio*>(h));
}
uint32_t type() const override { return vpiIterator; }
vpiHandle dovpi_scan() override {
while (true) {
if (m_it == m_vec->end()) {
delete this; // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle
return nullptr;
}
const VerilatedScope* const scopep = *m_it++;
if (scopep->type() == VerilatedScope::SCOPE_INTERFACE) {
return (new VerilatedVpioInterface{scopep})->castVpiHandle();
}
}
}
};
class VerilatedVpioInstanceIter final : public VerilatedVpio {
const std::vector<const VerilatedScope*>* m_vec;
std::vector<const VerilatedScope*>::const_iterator m_it;
@@ -995,22 +1097,13 @@ public:
case vpiOctStrVal: // FALLTHRU
case vpiDecStrVal: // FALLTHRU
case vpiHexStrVal: // FALLTHRU
case vpiStringVal: {
if (VL_UNLIKELY(!valuep->value.str)) return false;
break;
}
case vpiStringVal: // FALLTHRU
case vpiScalarVal: // FALLTHRU
case vpiIntVal: // FALLTHRU
case vpiRealVal: break;
case vpiVectorVal: {
if (VL_UNLIKELY(!valuep->value.vector)) return false;
break;
case vpiRealVal: // FALLTHRU
case vpiVectorVal: return true;
default: return false;
}
default: {
return false;
}
}
return true;
}
};
@@ -1192,8 +1285,8 @@ public:
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->fullname(), *(static_cast<CData*>(varop->readDatap())),
*(varop->prevDatap()), varop->readDatap(), varop->prevDatap(),
varop->entSize()););
if (varop->bitSize() == 1) {
T* const prevDatap = reinterpret_cast<T*>(
@@ -1204,7 +1297,7 @@ public:
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;
return std::memcmp(varop->prevDatap(), varop->readDatap(), varop->entSize()) != 0;
}
static bool valueDiffersFromPrev(VerilatedVpioVar* varop) {
switch (varop->varp()->vltype()) {
@@ -1233,9 +1326,9 @@ public:
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);
assert(std::memcmp(varop->prevDatap(), varop->readDatap(), varop->entSize()) == 0);
} else {
std::memcpy(varop->prevDatap(), varop->varDatap(), varop->entSize());
std::memcpy(varop->prevDatap(), varop->readDatap(), varop->entSize());
}
}
static void updatePrev(const VerilatedVpioVar* const varop) {
@@ -1276,7 +1369,7 @@ public:
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*>(varop->varDatap()))););
*(static_cast<CData*>(varop->readDatap()))););
update.insert(varop);
vpi_get_value(ho.cb_datap()->obj, ho.cb_datap()->value);
(ho.cb_rtnp())(ho.cb_datap());
@@ -1627,9 +1720,9 @@ VerilatedVpiImp::getForceControlSignals(const VerilatedVpioVar* const baseSignal
// assert(forceEnableSignalVop->entSize() == baseSignalVop->entSize());
assert(forceValueSignalVop->entSize() == baseSignalVop->entSize());
assert(forceReadSignalVop->entSize() == baseSignalVop->entSize());
assert(forceEnableSignalVop->varDatap() == forceEnableSignalVarp->datap());
assert(forceValueSignalVop->varDatap() == forceValueSignalVarp->datap());
assert(forceReadSignalVop->varDatap() == forceReadSignalVarp->datap());
assert(forceEnableSignalVop->readDatap() == forceEnableSignalVarp->datap());
assert(forceValueSignalVop->readDatap() == forceValueSignalVarp->datap());
assert(forceReadSignalVop->readDatap() == forceReadSignalVarp->datap());
#endif // VL_DEBUG
return VerilatedVpiImp::ForceControlSignalVops{
@@ -1659,9 +1752,9 @@ double VerilatedVpiImp::getReadDataWord(const VerilatedVpioVar* baseSignalVop,
const VerilatedVpioVar* forceEnableSignalVop,
const VerilatedVpioVar* forceValueSignalVop,
size_t /*bitCount*/, size_t /*bitOffset*/) {
const double baseSignalData = *baseSignalVop->varRealDatap();
const bool forceEnableData = *forceEnableSignalVop->varCDatap();
const double forceValueData = *forceValueSignalVop->varRealDatap();
const double baseSignalData = *baseSignalVop->readRealDatap();
const bool forceEnableData = *forceEnableSignalVop->readCDatap();
const double forceValueData = *forceValueSignalVop->readRealDatap();
const double readData = forceEnableData ? forceValueData : baseSignalData;
return readData;
}
@@ -1999,6 +2092,9 @@ const char* VerilatedVpiError::strFromVpiMethod(PLI_INT32 vpiVal) VL_PURE {
"vpiStmt"
};
// clang-format on
// SystemVerilog relations are numbered far above the Verilog ones
if (vpiVal == vpiActual) return "vpiActual";
if (vpiVal >= vpiPackage && vpiVal <= vpiPropFormalDecl) return strFromVpiObjType(vpiVal);
if (vpiVal > vpiStmt || vpiVal < vpiCondition) return "*undefined*";
return names[vpiVal - vpiCondition];
}
@@ -2402,6 +2498,8 @@ void VerilatedVpiError::selfTest() VL_MT_UNSAFE_ONE {
SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiCondition);
SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiStmt);
SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiActual);
SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiInterface);
SELF_CHECK_ENUM_STR(strFromVpiCallbackReason, cbValueChange);
SELF_CHECK_ENUM_STR(strFromVpiCallbackReason, cbAtEndOfSimTime);
@@ -2701,7 +2799,7 @@ _vl_vpi_handle_indexed_member_from_scope(const VerilatedScope* const scopep,
VerilatedVpioVar* baseVop
= fullnameOverride.empty()
? new VerilatedVpioVar{baseVarp, varScopep}
: new VerilatedVpioVar{baseVarp, varScopep, baseVarp->datap(),
: new VerilatedVpioVar{baseVarp, varScopep,
_vl_vpi_member_local_name(baseVarp->name()),
fullnameOverride};
VerilatedVpioVar* vop = _vl_vpi_handle_apply_indices(baseVop, indices);
@@ -2771,6 +2869,10 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) {
const VerilatedVpioScope* const voScopep = VerilatedVpioScope::castp(scope);
const VerilatedVpioVar* const voVarp = VerilatedVpioVar::castp(scope);
// Not scopes, so no name resolves relative to them; must not fall through to
// the unprefixed lookup below, which would resolve from the top level
if (VerilatedVpioIfaceRef::castp(scope) || VerilatedVpioModport::castp(scope)) return nullptr;
if (0 == std::strncmp(scopeAndName.c_str(), "$root.", std::strlen("$root."))) {
scopeAndName.erase(0, std::strlen("$root."));
} else if (voScopep) {
@@ -2798,8 +2900,15 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) {
if (scopep->type() == VerilatedScope::SCOPE_PACKAGE) {
return (new VerilatedVpioPackage{scopep})->castVpiHandle();
}
if (scopep->type() == VerilatedScope::SCOPE_INTERFACE) {
return (new VerilatedVpioInterface{scopep})->castVpiHandle();
}
return (new VerilatedVpioScope{scopep})->castVpiHandle();
}
if (const VerilatedIfaceRef* const ifaceRefp
= Verilated::threadContextp()->ifaceRefFind(scopeAndName.c_str())) {
return (new VerilatedVpioIfaceRef{*ifaceRefp})->castVpiHandle();
}
std::string basename = scopeAndName;
std::string scopename;
std::string::size_type prevpos = std::string::npos;
@@ -2862,10 +2971,9 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) {
if (varp->isParam()) {
resultHandle = (new VerilatedVpioParam{varp, scopep})->castVpiHandle();
} else if (!fullnameOverride.empty()) {
resultHandle
= (new VerilatedVpioVar{varp, scopep, varp->datap(),
_vl_vpi_member_local_name(varp->name()), fullnameOverride})
->castVpiHandle();
resultHandle = (new VerilatedVpioVar{varp, scopep, _vl_vpi_member_local_name(varp->name()),
fullnameOverride})
->castVpiHandle();
} else {
resultHandle = (new VerilatedVpioVar{varp, scopep})->castVpiHandle();
}
@@ -2959,6 +3067,24 @@ vpiHandle vpi_handle(PLI_INT32 type, vpiHandle object) {
const int32_t val = vop->index().back();
return (new VerilatedVpioConst{val})->castVpiHandle();
}
case vpiActual: {
if (const VerilatedVpioIfaceRef* const vop = VerilatedVpioIfaceRef::castp(object)) {
return vop->actual();
}
VL_VPI_WARNING_(__FILE__, __LINE__,
"%s: Unsupported vpiHandle '%p' for type '%s', nothing will be returned",
__func__, object, VerilatedVpiError::strFromVpiMethod(type));
return nullptr;
}
case vpiInterface: {
if (const VerilatedVpioModport* const vop = VerilatedVpioModport::castp(object)) {
return (new VerilatedVpioInterface{vop->scopep()})->castVpiHandle();
}
VL_VPI_WARNING_(__FILE__, __LINE__,
"%s: Unsupported vpiHandle '%p' for type '%s', nothing will be returned",
__func__, object, VerilatedVpiError::strFromVpiMethod(type));
return nullptr;
}
case vpiScope: {
const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object);
if (VL_UNLIKELY(!vop)) return nullptr;
@@ -3025,6 +3151,15 @@ vpiHandle vpi_iterate(PLI_INT32 type, vpiHandle object) {
if (it == map->end()) return nullptr;
return ((new VerilatedVpioModuleIter{it->second})->castVpiHandle());
}
case vpiInterface: {
// IEEE 1800-2023 37.5: interfaces are a one-to-many of a module
const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object);
const VerilatedHierarchyMap* const map = VerilatedImp::hierarchyMap();
const VerilatedScope* const modp = vop ? vop->scopep() : nullptr;
const auto it = vlstd::as_const(map)->find(const_cast<VerilatedScope*>(modp));
if (it == map->end()) return nullptr;
return ((new VerilatedVpioInterfaceIter{it->second})->castVpiHandle());
}
case vpiInternalScope: {
const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object);
const VerilatedHierarchyMap* const map = VerilatedImp::hierarchyMap();
@@ -3235,6 +3370,18 @@ bool vl_check_format(const VerilatedVpioVarBase* vop, const p_vpi_value valuep,
return false;
}
static const char* vl_put_value_null_member(const p_vpi_value valuep) {
switch (valuep->format) {
case vpiBinStrVal: // FALLTHRU
case vpiOctStrVal: // FALLTHRU
case vpiDecStrVal: // FALLTHRU
case vpiHexStrVal: // FALLTHRU
case vpiStringVal: return valuep->value.str ? nullptr : "str";
case vpiVectorVal: return valuep->value.vector ? nullptr : "vector";
default: return nullptr;
}
}
// Get a VPI format that can be used to fully represent a signal of the given type
PLI_INT32 vl_get_vltype_format(VerilatedVarType vlType) {
switch (vlType) {
@@ -3294,7 +3441,7 @@ VarAccessInfo<T> vl_vpi_var_access_info(const VerilatedVpioVarBase* vop, size_t
bitCount, varBits - addOffset});
VarAccessInfo<T> info;
info.m_datap = reinterpret_cast<T*>(vop->varDatap());
info.m_datap = static_cast<T*>(vop->readDatap());
if (vop->varp()->vltype() == VLVT_WDATA) {
assert(sizeof(T) == sizeof(EData));
assert(bitCount <= wordBits);
@@ -3342,8 +3489,7 @@ T vl_vpi_get_word_gen(VarAccessInfo<T> info) {
template <typename T>
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);
return vl_vpi_get_word_gen(vl_vpi_var_access_info<T>(vop, bitCount, addOffset));
}
template <typename T>
@@ -3361,8 +3507,7 @@ void vl_vpi_put_word_gen(VarAccessInfo<T> info, T word) {
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);
vl_vpi_put_word_gen(vl_vpi_var_access_info<T>(vop, bitCount, addOffset), word);
}
// bitCount: maximum number of bits to read, will stop earlier if it reaches the var bounds
@@ -3399,7 +3544,7 @@ void vl_vpi_put_word(const VerilatedVpioVar* vop, QData word, size_t bitCount, s
void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) {
const VerilatedVar* const varp = vop->varp();
void* const varDatap = vop->varDatap();
void* const varDatap = vop->readDatap();
if (!vl_check_format(vop, valuep, true)) return;
// string data type is dynamic and may vary in size during simulation
@@ -3440,7 +3585,7 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) {
return;
} else if (valuep->format == vpiBinStrVal) {
t_outDynamicStr.resize(varBits);
const CData* datap = reinterpret_cast<CData*>(varDatap);
const CData* datap = static_cast<CData*>(varDatap);
for (size_t i = 0; i < varBits; ++i) {
const size_t pos = i + vop->bitOffset();
const char val = (datap[pos >> 3] >> (pos & 7)) & 1;
@@ -3485,10 +3630,10 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) {
} else if (valuep->format == vpiStringVal) {
if (varp->vltype() == VLVT_STRING) {
if (varp->isParam()) {
valuep->value.str = reinterpret_cast<char*>(varDatap);
valuep->value.str = static_cast<char*>(varDatap);
return;
}
t_outDynamicStr = *vop->varStringDatap();
t_outDynamicStr = *vop->readStringDatap();
valuep->value.str = const_cast<char*>(t_outDynamicStr.c_str());
return;
} else {
@@ -3506,7 +3651,7 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) {
valuep->value.integer = vl_vpi_get_word(vop, 32, 0);
return;
} else if (valuep->format == vpiRealVal) {
valuep->value.real = *(vop->varRealDatap());
valuep->value.real = *(vop->readRealDatap());
return;
} else if (valuep->format == vpiScalarVal) {
valuep->value.scalar = vl_vpi_get_word(vop, 32, 0) ? vpi1 : vpi0;
@@ -3597,7 +3742,7 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_
baseSignalVop->fullname(), valuep->format,
valuep->value.integer);
VL_DBG_MSGF("- vpi: varp=%p putatp=%p\n",
baseSignalVop->varp()->datap(), baseSignalVop->varDatap()););
baseSignalVop->varp()->datap(), baseSignalVop->readDatap()););
if (VL_UNLIKELY(!baseSignalVop->varp()->isPublicRW())) {
VL_VPI_ERROR_(__FILE__, __LINE__,
@@ -3616,6 +3761,16 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_
return nullptr;
}
if (!vl_check_format(baseSignalVop, valuep, false)) return nullptr;
// On release valuep is only written
if (forceFlag != vpiReleaseFlag) {
if (const char* const memberp = vl_put_value_null_member(valuep)) {
VL_VPI_WARNING_(__FILE__, __LINE__,
"%s: Ignoring nullptr value.%s with format %s for '%s'", __func__,
memberp, VerilatedVpiError::strFromVpiVal(valuep->format),
baseSignalVop->fullname());
return nullptr;
}
}
if (delay_mode == vpiInertialDelay) {
if (!VerilatedVpiPutHolder::canInertialDelay(valuep)) {
VL_VPI_WARNING_(
@@ -3675,7 +3830,7 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_
if (baseSignalVop->varp()->vltype() == VLVT_REAL) {
const double readData = VerilatedVpiImp::getReadDataWord<double>(
baseSignalVop, forceEnableSignalVop, forceValueSignalVop, 64, 0);
*forceReadSignalVop->varRealDatap() = readData;
*forceReadSignalVop->readRealDatap() = readData;
return;
}
@@ -3780,7 +3935,6 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_
}
if (valuep->format == vpiVectorVal) {
if (VL_UNLIKELY(!valuep->value.vector)) return nullptr;
if (valueVop->varp()->vltype() == VLVT_WDATA) {
const int words = VL_WORDS_I(varBits);
for (int i = 0; i < words; ++i)
@@ -3797,7 +3951,7 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_
}
} else if (valuep->format == vpiBinStrVal) {
const int len = std::strlen(valuep->value.str);
CData* const datap = reinterpret_cast<CData*>(valueVop->varDatap());
CData* const datap = static_cast<CData*>(valueVop->readDatap());
for (int i = 0; i < varBits; ++i) {
const bool set = (i < len) && (valuep->value.str[len - i - 1] == '1');
const size_t pos = valueVop->bitOffset() + i;
@@ -3879,7 +4033,7 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_
} else if (valuep->format == vpiStringVal) {
if (valueVop->varp()->vltype() == VLVT_STRING) {
// Does not use valueVop, because strings are not forceable anyway
*(baseSignalVop->varStringDatap()) = valuep->value.str;
*(baseSignalVop->readStringDatap()) = valuep->value.str;
return object;
}
const int chars = VL_BYTES_I(varBits);
@@ -3896,7 +4050,7 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_
return object;
} else if (valuep->format == vpiRealVal) {
if (valueVop->varp()->vltype() == VLVT_REAL) {
*(valueVop->varRealDatap()) = valuep->value.real;
*(valueVop->readRealDatap()) = valuep->value.real;
if (baseSignalVop->varp()->isForceable()) updateVforceRd();
return object;
}
@@ -3941,7 +4095,7 @@ bool vl_check_array_format(const VerilatedVar* varp, const p_vpi_arrayvalue arra
case VLVT_UINT8:
case VLVT_UINT16:
case VLVT_UINT32: return true;
default:; // LCOV_EXCL_LINE
default:;
}
break;
case vpiRawTwoStateVal:
@@ -3959,7 +4113,7 @@ bool vl_check_array_format(const VerilatedVar* varp, const p_vpi_arrayvalue arra
switch (varp->vltype()) {
case VLVT_UINT8:
case VLVT_UINT16: return true;
default:; // LCOV_EXCL_LINE
default:;
}
break;
case vpiLongIntVal:
@@ -3968,7 +4122,7 @@ bool vl_check_array_format(const VerilatedVar* varp, const p_vpi_arrayvalue arra
case VLVT_UINT16:
case VLVT_UINT32:
case VLVT_UINT64: return true;
default:; // LCOV_EXCL_LINE
default:;
}
break;
default:;
@@ -4167,10 +4321,10 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
if (varp->vltype() == VLVT_UINT8) {
vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
vop->varCDatap(), shortintsp);
vop->readCDatap(), shortintsp);
} else if (varp->vltype() == VLVT_UINT16) {
vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
vop->varSDatap(), shortintsp);
vop->readSDatap(), shortintsp);
}
return;
@@ -4182,13 +4336,13 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
if (varp->vltype() == VLVT_UINT8) {
vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
vop->varCDatap(), integersp);
vop->readCDatap(), integersp);
} else if (varp->vltype() == VLVT_UINT16) {
vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
vop->varSDatap(), integersp);
vop->readSDatap(), integersp);
} else if (varp->vltype() == VLVT_UINT32) {
vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
vop->varIDatap(), integersp);
vop->readIDatap(), integersp);
}
return;
@@ -4200,16 +4354,16 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
if (varp->vltype() == VLVT_UINT8) {
vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
vop->varCDatap(), longintsp);
vop->readCDatap(), longintsp);
} else if (varp->vltype() == VLVT_UINT16) {
vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
vop->varSDatap(), longintsp);
vop->readSDatap(), longintsp);
} else if (varp->vltype() == VLVT_UINT32) {
vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
vop->varIDatap(), longintsp);
vop->readIDatap(), longintsp);
} else if (varp->vltype() == VLVT_UINT64) {
vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
vop->varQDatap(), longintsp);
vop->readQDatap(), longintsp);
}
return;
@@ -4221,19 +4375,19 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
if (varp->vltype() == VLVT_UINT8) {
vl_get_value_array_vectors(index, num, size, varp->entBits(), leftIsLow,
vop->varCDatap(), vectorsp);
vop->readCDatap(), vectorsp);
} else if (varp->vltype() == VLVT_UINT16) {
vl_get_value_array_vectors(index, num, size, varp->entBits(), leftIsLow,
vop->varSDatap(), vectorsp);
vop->readSDatap(), vectorsp);
} else if (varp->vltype() == VLVT_UINT32) {
vl_get_value_array_vectors(index, num, size, varp->entBits(), leftIsLow,
vop->varIDatap(), vectorsp);
vop->readIDatap(), vectorsp);
} else if (varp->vltype() == VLVT_UINT64) {
vl_get_value_array_vectors(index, num, size, varp->entBits(), leftIsLow,
vop->varQDatap(), vectorsp);
vop->readQDatap(), vectorsp);
} else if (varp->vltype() == VLVT_WDATA) {
vl_get_value_array_vectors(index, num, size, varp->entBits(), leftIsLow,
vop->varEDatap(), vectorsp);
vop->readEDatap(), vectorsp);
}
return;
@@ -4245,19 +4399,19 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
if (varp->vltype() == VLVT_UINT8) {
vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true,
vop->varCDatap(), valuep);
vop->readCDatap(), valuep);
} else if (varp->vltype() == VLVT_UINT16) {
vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true,
vop->varSDatap(), valuep);
vop->readSDatap(), valuep);
} else if (varp->vltype() == VLVT_UINT32) {
vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true,
vop->varIDatap(), valuep);
vop->readIDatap(), valuep);
} else if (varp->vltype() == VLVT_UINT64) {
vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true,
vop->varQDatap(), valuep);
vop->readQDatap(), valuep);
} else if (varp->vltype() == VLVT_WDATA) {
vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true,
vop->varEDatap(), valuep);
vop->readEDatap(), valuep);
}
return;
@@ -4269,19 +4423,19 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
if (varp->vltype() == VLVT_UINT8) {
vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false,
vop->varCDatap(), valuep);
vop->readCDatap(), valuep);
} else if (varp->vltype() == VLVT_UINT16) {
vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false,
vop->varSDatap(), valuep);
vop->readSDatap(), valuep);
} else if (varp->vltype() == VLVT_UINT32) {
vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false,
vop->varIDatap(), valuep);
vop->readIDatap(), valuep);
} else if (varp->vltype() == VLVT_UINT64) {
vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false,
vop->varQDatap(), valuep);
vop->readQDatap(), valuep);
} else if (varp->vltype() == VLVT_WDATA) {
vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false,
vop->varEDatap(), valuep);
vop->readEDatap(), valuep);
}
return;
@@ -4344,17 +4498,8 @@ void vpi_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, PLI_IN
void vl_put_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const PLI_INT32* index_p,
PLI_UINT32 num) {
const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
if (!vl_check_array_format(vop->varp(), arrayvalue_p, vop->fullname())) return;
const VerilatedVar* const varp = vop->varp();
const int size = vop->size();
if (VL_UNCOVERABLE(num > size)) {
VL_VPI_ERROR_(__FILE__, __LINE__,
"%s: Requested elements to set (%u) exceed array size (%u)", __func__, num,
size);
return;
}
const bool leftIsLow = vop->rangep()->left() == vop->rangep()->low();
const int index
@@ -4366,114 +4511,129 @@ void vl_put_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
if (varp->vltype() == VLVT_UINT8) {
vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, shortintsp,
vop->varCDatap());
vop->readCDatap());
return;
} else if (varp->vltype() == VLVT_UINT16) {
vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, shortintsp,
vop->varSDatap());
vop->readSDatap());
return;
}
return;
} else if (arrayvalue_p->format == vpiIntVal) {
const PLI_UINT32* integersp = reinterpret_cast<PLI_UINT32*>(arrayvalue_p->value.integers);
if (varp->vltype() == VLVT_UINT8) {
vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, integersp,
vop->varCDatap());
vop->readCDatap());
return;
} else if (varp->vltype() == VLVT_UINT16) {
vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, integersp,
vop->varSDatap());
vop->readSDatap());
return;
} else if (varp->vltype() == VLVT_UINT32) {
vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, integersp,
vop->varIDatap());
vop->readIDatap());
return;
}
return;
} else if (arrayvalue_p->format == vpiLongIntVal) {
const PLI_UINT64* longintsp = reinterpret_cast<PLI_UINT64*>(arrayvalue_p->value.longints);
if (varp->vltype() == VLVT_UINT8) {
vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, longintsp,
vop->varCDatap());
vop->readCDatap());
return;
} else if (varp->vltype() == VLVT_UINT16) {
vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, longintsp,
vop->varSDatap());
vop->readSDatap());
return;
} else if (varp->vltype() == VLVT_UINT32) {
vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, longintsp,
vop->varIDatap());
vop->readIDatap());
return;
} else if (varp->vltype() == VLVT_UINT64) {
vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, longintsp,
vop->varQDatap());
vop->readQDatap());
return;
}
return;
} else if (arrayvalue_p->format == vpiVectorVal) {
const p_vpi_vecval vectorsp = arrayvalue_p->value.vectors;
if (varp->vltype() == VLVT_UINT8) {
vl_put_value_array_vectors(index, num, size, varp->entBits(), leftIsLow, true,
vectorsp, vop->varCDatap());
vectorsp, vop->readCDatap());
return;
} else if (varp->vltype() == VLVT_UINT16) {
vl_put_value_array_vectors(index, num, size, varp->entBits(), leftIsLow, true,
vectorsp, vop->varSDatap());
vectorsp, vop->readSDatap());
return;
} else if (varp->vltype() == VLVT_UINT32) {
vl_put_value_array_vectors(index, num, size, varp->entBits(), leftIsLow, true,
vectorsp, vop->varIDatap());
vectorsp, vop->readIDatap());
return;
} else if (varp->vltype() == VLVT_UINT64) {
vl_put_value_array_vectors(index, num, size, varp->entBits(), leftIsLow, true,
vectorsp, vop->varQDatap());
vectorsp, vop->readQDatap());
return;
} else if (varp->vltype() == VLVT_WDATA) {
vl_put_value_array_vectors(index, num, size, varp->entBits(), leftIsLow, true,
vectorsp, vop->varEDatap());
vectorsp, vop->readEDatap());
return;
}
return;
} else if (arrayvalue_p->format == vpiRawFourStateVal) {
const PLI_UBYTE8* valuep = reinterpret_cast<PLI_UBYTE8*>(arrayvalue_p->value.rawvals);
if (varp->vltype() == VLVT_UINT8) {
vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true, valuep,
vop->varCDatap());
vop->readCDatap());
return;
} else if (varp->vltype() == VLVT_UINT16) {
vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true, valuep,
vop->varSDatap());
vop->readSDatap());
return;
} else if (varp->vltype() == VLVT_UINT32) {
vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true, valuep,
vop->varIDatap());
vop->readIDatap());
return;
} else if (varp->vltype() == VLVT_UINT64) {
vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true, valuep,
vop->varQDatap());
vop->readQDatap());
return;
} else if (varp->vltype() == VLVT_WDATA) {
vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true, valuep,
vop->varEDatap());
vop->readEDatap());
return;
}
return;
} else if (arrayvalue_p->format == vpiRawTwoStateVal) {
const PLI_UBYTE8* valuep = reinterpret_cast<PLI_UBYTE8*>(arrayvalue_p->value.rawvals);
if (varp->vltype() == VLVT_UINT8) {
vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false, valuep,
vop->varCDatap());
vop->readCDatap());
return;
} else if (varp->vltype() == VLVT_UINT16) {
vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false, valuep,
vop->varSDatap());
vop->readSDatap());
return;
} else if (varp->vltype() == VLVT_UINT32) {
vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false, valuep,
vop->varIDatap());
vop->readIDatap());
return;
} else if (varp->vltype() == VLVT_UINT64) {
vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false, valuep,
vop->varQDatap());
vop->readQDatap());
return;
} else if (varp->vltype() == VLVT_WDATA) {
vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false, valuep,
vop->varEDatap());
vop->readEDatap());
return;
}
return;
}
// Reached only if vl_check_array_format and this dispatch drift apart
// LCOV_EXCL_START
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported format (%s) as requested for '%s'",
__func__, VerilatedVpiError::strFromVpiVal(arrayvalue_p->format),
vop->fullname());
// LCOV_EXCL_STOP
}
void vpi_put_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, PLI_INT32* index_p,
@@ -4528,6 +4688,16 @@ void vpi_put_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, PLI_IN
return;
}
if (!vl_check_array_format(vop->varp(), arrayvalue_p, vop->fullname())) return;
const unsigned size = vop->size();
if (VL_UNLIKELY(num > size)) {
VL_VPI_ERROR_(__FILE__, __LINE__,
"%s: Requested elements to set (%u) exceed array size (%u)", __func__, num,
size);
return;
}
if (num == 0) return;
vl_put_value_array(object, arrayvalue_p, index_p, num);
}
+16 -2
View File
@@ -318,6 +318,17 @@
# define VL_CONSTEXPR_CXX17
#endif
//=========================================================================
// C++-2020
#if __cplusplus >= 202002L
# define VL_CONSTINIT_CXX20 constinit
# define VL_NO_UNIQUE_ADDRESS_CXX20 [[no_unique_address]]
#else
# define VL_CONSTINIT_CXX20
# define VL_NO_UNIQUE_ADDRESS_CXX20
#endif
//=========================================================================
// Optimization
@@ -452,7 +463,8 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read()
#define VL_VFORMATATTR_SIGNED '~' // (int widthMin, IData/VlWide/etc) Signed number; for %d showing sign
#define VL_VFORMATATTR_COMPLEX '!' // (std::string*); for non-POD; e.g. struct, requires %p typically
#define VL_VFORMATATTR_DOUBLE 'D' // (double); promote %p to %f
#define VL_VFORMATATTR_ENUM 'E' // (width, IData/QData, std::string* name); <= 64 bit enum with runtime %p/%s
#define VL_VFORMATATTR_ENUM 'E' // (width, IData/QData/const EData*, STRING, std::string* name)
#define VL_VFORMATATTR_ENUM_SIGNED 'F' // Same arguments as ENUM, with a signed numeric value
#define VL_VFORMATATTR_SCOPE 'M' // (char* name, char* scope); for scopes
#define VL_VFORMATATTR_STRING 'S' // (char* name, char* scope); for scopes // (std::string*); for %p/%s
#define VL_VFORMATATTR_TIMEUNIT 'T' // (int timeunit); timeunits passed from V3Emit to runtime
@@ -531,10 +543,12 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read()
#define VL_EUL(n) VL_UL(n) // Make constant number EData sized
#define VL_BITWORD_I(bit) ((bit) / VL_IDATASIZE) ///< Word number for sv DPI vectors
#define VL_BITWORD_Q(bit) ((bit) / VL_QUADSIZE) ///< Quadword number for a bit
#define VL_BITWORD_E(bit) ((bit) >> VL_EDATASIZE_LOG2) ///< Word number for a wide quantity
#define VL_BITBIT_I(bit) ((bit) & VL_SIZEBITS_I) ///< Bit number for a bit in a long
#define VL_BITBIT_Q(bit) ((bit) & VL_SIZEBITS_Q) ///< Bit number for a bit in a quad
#define VL_BITBIT_E(bit) ((bit) & VL_SIZEBITS_E) ///< Bit number for a bit in an EData
#define VL_BIT_Q(bit) (1ULL << VL_BITBIT_Q(bit)) ///< Quad with only the given bit set
// Return true if data[bit] set; not 0/1 return, but 0/non-zero return.
#define VL_BITISSET_I(data, bit) ((data) & (VL_UL(1) << VL_BITBIT_I(bit)))
@@ -736,7 +750,7 @@ namespace vlstd {
template <typename T>
struct reverse_wrapper final {
const T& m_v;
const T& m_v; // Pre-wrapped iterator
explicit reverse_wrapper(const T& a_v)
: m_v(a_v) {} // Need () constructor
+3 -1
View File
@@ -25,8 +25,10 @@ def message_section(msg: str) -> int:
return 50
if re.match(r'^Support', msg, flags=re.IGNORECASE):
return 60
if re.match(r'^Fix', msg, flags=re.IGNORECASE):
if re.match(r'^Optimize', msg, flags=re.IGNORECASE):
return 70
if re.match(r'^Fix', msg, flags=re.IGNORECASE):
return 80
if re.match(r'^(Internals|CI|Tests)', msg, flags=re.IGNORECASE):
return -1
if re.match(r'^Bump.* from .* to .*', msg, flags=re.IGNORECASE): # dependabot
+5 -2
View File
@@ -12,20 +12,23 @@
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: CC0-1.0
# These flags tested against verible-v0.0-4080-ga0a8d8eb
# These flags tested against verible-v0.0-4192-g682a1d50
verible-verilog-format \
--inplace \
--wrap_end_else_clauses \
\
--assignment_statement_alignment=flush-left \
--case_items_alignment=flush-left \
--class_member_variable_alignment=flush-left \
--distribution_items_alignment=flush-left \
--enum_assignment_statement_alignment=flush-left \
--expand_coverpoints \
--formal_parameters_alignment=flush-left \
--module_net_variable_alignment=flush-left \
--named_parameter_alignment=flush-left \
--named_port_alignment=flush-left \
--parameter_declaration_alignment=flush-left \
--port_declarations_alignment=flush-left \
--struct_union_members_alignment=flush-left \
--wrap_end_else_clauses \
$*
+15 -1
View File
@@ -68,6 +68,7 @@ set(HEADERS
V3Combine.h
V3Common.h
V3Const.h
V3ConstPool.h
V3Container.h
V3Control.h
V3Coverage.h
@@ -114,6 +115,7 @@ set(HEADERS
V3GraphPathChecker.h
V3GraphStream.h
V3Hash.h
V3HashTable.h
V3Hasher.h
V3HierBlock.h
V3Inline.h
@@ -236,6 +238,7 @@ set(COMMON_SOURCES
V3Clock.cpp
V3Combine.cpp
V3Common.cpp
V3ConstPool.cpp
V3Control.cpp
V3Const__gen.cpp
V3Coverage.cpp
@@ -292,6 +295,7 @@ set(COMMON_SOURCES
V3GraphPathChecker.cpp
V3GraphTest.cpp
V3Hash.cpp
V3HashTable.cpp
V3Hasher.cpp
V3HierBlock.cpp
V3Inline.cpp
@@ -345,6 +349,7 @@ set(COMMON_SOURCES
V3Sampled.cpp
V3Sched.cpp
V3SchedAcyclic.cpp
V3SchedCovergroup.cpp
V3SchedPartition.cpp
V3SchedReplicate.cpp
V3SchedTiming.cpp
@@ -562,9 +567,18 @@ set_target_properties(
MSVC_RUNTIME_LIBRARY MultiThreaded$<IF:$<CONFIG:Release>,,DebugDLL>
#JOB_POOL_LINK one_job # Linking takes lots of resources
INTERPROCEDURAL_OPTIMIZATION_RELEASE $<IF:MINGW,FALSE,TRUE>
INCLUDE_DIRECTORIES ${FLEX_INCLUDE_DIR}
)
if(MINGW)
# Search MSYS Flex headers after the active toolchain's system headers.
target_compile_options(
${verilator}
PRIVATE -idirafter "${FLEX_INCLUDE_DIR}"
)
else()
target_include_directories(${verilator} PRIVATE "${FLEX_INCLUDE_DIR}")
endif()
add_dependencies(
${verilator}
V3Lexer_yy_cpp${CMAKE_BUILD_TYPE}
+4 -19
View File
@@ -35,25 +35,10 @@ endif
UNDER_GIT = $(wildcard ${srcdir}/../.git/logs/HEAD)
ifeq (,$(wildcard obj_dbg/bear.o))
ifneq (, $(shell which bear 2>/dev/null))
BEAR := $(shell which bear)
ifeq (, $(shell $(BEAR) --output obj_dbg/comptest.json -- true))
$(shell which bear 2>/dev/null >obj_dbg/bear.o)
else
# unsupported version
BEAR :=
endif
endif
else
BEAR := $(shell cat obj_dbg/bear.o)
endif
ifneq ($(BEAR),)
BEAR_OBJ_OPT := $(BEAR) --append --output obj_dbg/compile_commands.json --
else
BEAR_OBJ_OPT :=
endif
# Use bear if installed, and new enough to support the '--output FILE --' syntax
BEAR := $(shell bear=$$(which bear 2>/dev/null) && tmp=$$(mktemp -d 2>/dev/null) \
&& { "$$bear" --output "$$tmp/probe.json" -- true >/dev/null 2>&1 && echo "$$bear"; rm -rf "$$tmp"; })
BEAR_OBJ_OPT := $(if $(BEAR),$(BEAR) --append --output obj_dbg/compile_commands.json --)
#*********************************************************************
+8 -5
View File
@@ -187,7 +187,6 @@ maintainer-copy::
#### Top executable
RAW_OBJS = \
V3Const__gen.o \
V3Error.o \
V3FileLine.o \
V3Graph.o \
@@ -196,6 +195,7 @@ RAW_OBJS = \
V3GraphPathChecker.o \
V3GraphTest.o \
V3Hash.o \
V3HashTable.o \
V3OptionParser.o \
V3Os.o \
V3ParseGrammar.o \
@@ -212,6 +212,7 @@ RAW_OBJS_PCH_ASTMT = \
V3Ast.o \
V3AstNodes.o \
V3Broken.o \
V3ConstPool.o \
V3Control.o \
V3EmitCBase.o \
V3EmitCConstPool.o \
@@ -252,6 +253,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Clock.o \
V3Combine.o \
V3Common.o \
V3Const__gen.o \
V3Coverage.o \
V3Covergroup.o \
V3CoverageJoin.o \
@@ -330,6 +332,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Sampled.o \
V3Sched.o \
V3SchedAcyclic.o \
V3SchedCovergroup.o \
V3SchedPartition.o \
V3SchedReplicate.o \
V3SchedTiming.o \
@@ -408,8 +411,6 @@ $(TGT): $(PREDEP_H) $(OBJS)
%__gen.cpp: %.cpp $(ASTGEN) $(AST_DEFS) $(DFG_DEFS)
$(PYTHON3) $(ASTGEN) $(ASTGENFLAGS) $*.cpp
.SECONDARY:
%.gch: %
$(OBJCACHE) ${CXX} ${CXXFLAGS} ${CXXFLAGSOBJ} ${CPPFLAGSWALL} ${CFG_CXXFLAGS_PCH} -c $< -o $@
%.o: %.cpp
@@ -429,7 +430,7 @@ V3PreProc.o: V3PreProc.cpp V3PreLex.yy.cpp
define CXX_ASTMT_template
$(1): $(basename $(1)).cpp V3PchAstMT.h.gch
$(OBJCACHE) ${CXX} ${CXXFLAGS} ${CXXFLAGSOBJ} ${CPPFLAGSWALL} ${CFG_CXXFLAGS_PCH_I} V3PchAstMT.h${CFG_GCH_IF_CLANG} -c $(srcdir)/$(basename $(1)).cpp -o $(1)
$(OBJCACHE) ${CXX} ${CXXFLAGS} ${CXXFLAGSOBJ} ${CPPFLAGSWALL} ${CFG_CXXFLAGS_PCH_I} V3PchAstMT.h${CFG_GCH_IF_CLANG} -c $$< -o $(1)
endef
@@ -437,7 +438,7 @@ $(foreach obj,$(RAW_OBJS_PCH_ASTMT),$(eval $(call CXX_ASTMT_template,$(obj))))
define CXX_ASTNOMT_template
$(1): $(basename $(1)).cpp V3PchAstNoMT.h.gch
$(OBJCACHE) ${CXX} ${CXXFLAGS} ${CXXFLAGSOBJ} ${CPPFLAGSWALL} ${CFG_CXXFLAGS_PCH_I} V3PchAstNoMT.h${CFG_GCH_IF_CLANG} -c $(srcdir)/$(basename $(1)).cpp -o $(1)
$(OBJCACHE) ${CXX} ${CXXFLAGS} ${CXXFLAGSOBJ} ${CPPFLAGSWALL} ${CFG_CXXFLAGS_PCH_I} V3PchAstNoMT.h${CFG_GCH_IF_CLANG} -c $$< -o $(1)
endef
@@ -487,6 +488,8 @@ header_cc: $(addsuffix __header_cc.o, $(basename $(HEADER_CC_H)))
%__header_cc.cpp: %.h
$(PYTHON3) $(srcdir)/../bin/verilator_includer -DVL_MT_DISABLED_CODE_UNIT=1 $^ > $@
.SECONDARY: V3Const__gen.cpp $(addsuffix __header_cc.cpp, $(basename $(HEADER_CC_H)))
.SUFFIXES:
######################################################################
+28 -22
View File
@@ -154,6 +154,13 @@ void V3AssertCommon::lowerSequenceEvents(AstNetlist* nodep) {
V3Global::dumpCheckGlobalTree("assertseqevent", 0, dumpTreeEitherLevel() >= 3);
}
string V3AssertCommon::assertCtlGetCall(const char* query, VAssertType type,
VAssertDirectiveType directiveType) {
// Template arguments let the runtime build the type/directive mask at C++ compile time
return "vlSymsp->_vm_contextp__->assertCtlGet<"s + std::to_string(type) + ", "s
+ std::to_string(directiveType) + ">(VerilatedAssertCtlQuery::"s + query + ")"s;
}
//######################################################################
// AssertDeFutureVisitor
// If any AstFuture, then move all non-future varrefs to be one cycle behind,
@@ -324,6 +331,7 @@ class AssertVisitor final : public VNVisitor {
VDouble0 m_statLiftedCaseExprs; // Count of purified case expressions
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
V3UniqueNames m_caseTempNames{"__VCase"};
V3UniqueNames m_matchCountNames{"__VnfaRemainingMatchCount"}; // Match replay counter names
// Maps from (expression, senTree) to the AstAlways that computes its delayed values.
std::unordered_map<VNRef<AstNodeExpr>, std::unordered_map<VNRef<AstSenTree>, AstAlways*>>
m_modExpr2Sen2DelayedAlwaysp;
@@ -334,11 +342,6 @@ class AssertVisitor final : public VNVisitor {
std::unordered_map<const AstAlways*, AstVar*> m_delayedAlways2TickTimep;
// METHODS
static string assertCtlGetCall(const char* query, VAssertType type,
VAssertDirectiveType directiveType) {
return "vlSymsp->_vm_contextp__->assertCtlGet(VerilatedAssertCtlQuery::"s + query + ", "s
+ std::to_string(type) + ", "s + std::to_string(directiveType) + ")"s;
}
static const char* assertPassOnQuery(bool vacuous) {
static constexpr const char* queries[2]
= {"ASSERT_CTL_PASS_ON_NONVACUOUS", "ASSERT_CTL_PASS_ON_VACUOUS"};
@@ -362,8 +365,9 @@ class AssertVisitor final : public VNVisitor {
case VAssertDirectiveType::COVER:
case VAssertDirectiveType::ASSUME: {
if (v3Global.opt.assertOn()) {
return new AstCExpr{fl, AstCExpr::Pure{},
assertCtlGetCall("ASSERT_CTL_ON", type, directiveType), 1};
return new AstCExpr{
fl, AstCExpr::Pure{},
V3AssertCommon::assertCtlGetCall("ASSERT_CTL_ON", type, directiveType), 1};
}
return new AstConst{fl, AstConst::BitFalse{}};
}
@@ -390,15 +394,17 @@ class AssertVisitor final : public VNVisitor {
VAssertDirectiveType directiveType, bool vacuous) {
if (!isControlled(directiveType)) return new AstConst{fl, AstConst::BitTrue{}};
if (!v3Global.opt.assertOn()) return new AstConst{fl, AstConst::BitFalse{}};
return new AstCExpr{fl, AstCExpr::Pure{},
assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType), 1};
return new AstCExpr{
fl, AstCExpr::Pure{},
V3AssertCommon::assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType), 1};
}
static AstNodeExpr* assertFailOnCond(FileLine* fl, VAssertType type,
VAssertDirectiveType directiveType) {
if (!isControlled(directiveType)) return new AstConst{fl, AstConst::BitTrue{}};
if (!v3Global.opt.assertOn()) return new AstConst{fl, AstConst::BitFalse{}};
return new AstCExpr{fl, AstCExpr::Pure{},
assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType), 1};
return new AstCExpr{
fl, AstCExpr::Pure{},
V3AssertCommon::assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType), 1};
}
string assertDisplayMessage(const AstNode* nodep, const string& prefix, const string& message,
VDisplayType severity) {
@@ -433,20 +439,20 @@ class AssertVisitor final : public VNVisitor {
if (!m_monitorNumVarp) {
m_monitorNumVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorNum",
nodep->findUInt64DType()};
v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorNumVarp);
v3Global.rootp()->dollarUnitPkgp()->addStmtsp(m_monitorNumVarp);
}
AstVarRef* const varrefp = new AstVarRef{nodep->fileline(), m_monitorNumVarp, access};
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgp());
return varrefp;
}
AstVarRef* newMonitorOffVarRefp(const AstNode* nodep, VAccess access) {
if (!m_monitorOffVarp) {
m_monitorOffVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorOff",
nodep->findBitDType()};
v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorOffVarp);
v3Global.rootp()->dollarUnitPkgp()->addStmtsp(m_monitorOffVarp);
}
AstVarRef* const varrefp = new AstVarRef{nodep->fileline(), m_monitorOffVarp, access};
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgp());
return varrefp;
}
static AstIf* newIfAssertOn(AstNode* bodyp, VAssertDirectiveType directiveType,
@@ -749,11 +755,11 @@ class AssertVisitor final : public VNVisitor {
// reaches zero.
matchCountp->unlinkFrBack();
AstVar* const remainingp = new AstVar{
flp, VVarType::BLOCKTEMP, "__VnfaRemainingMatchCount", matchCountp->dtypep()};
remainingp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
AstBegin* const replayp = new AstBegin{flp, "", remainingp, true};
replayp->addStmtsp(
new AstAssign{flp, new AstVarRef{flp, remainingp, VAccess::WRITE}, matchCountp});
flp, VVarType::MODULETEMP, m_matchCountNames.get(""), matchCountp->dtypep()};
remainingp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(remainingp);
AstNode* const replaysp
= new AstAssign{flp, new AstVarRef{flp, remainingp, VAccess::WRITE}, matchCountp};
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(
new AstLoopTest{flp, loopp, new AstVarRef{flp, remainingp, VAccess::READ}});
@@ -763,8 +769,8 @@ class AssertVisitor final : public VNVisitor {
new AstSub{flp, new AstVarRef{flp, remainingp, VAccess::READ},
new AstConst{flp, AstConst::WidthedValue{},
remainingp->dtypep()->width(), 1}}});
replayp->addStmtsp(loopp);
passsp = replayp;
replaysp->addNext(loopp);
passsp = replaysp;
}
AstNode* bodysp = assertBody(nodep, propExprp, passsp, failsp);
if (disablep) bodysp = new AstIf{flp, new AstLogNot{flp, disablep}, bodysp};
+2
View File
@@ -28,6 +28,8 @@ class V3AssertCommon final {
public:
static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED;
static void lowerSequenceEvents(AstNetlist* nodep) VL_MT_DISABLED;
static string assertCtlGetCall(const char* query, VAssertType type,
VAssertDirectiveType directiveType) VL_MT_DISABLED;
};
class V3Assert final {
+438 -228
View File
File diff suppressed because it is too large Load Diff
+27 -29
View File
@@ -68,6 +68,7 @@ private:
V3UniqueNames m_gotoRepNames{"__VgotoRep"}; // Goto repetition counter name generator
V3UniqueNames m_nonConsRepNames{"__VnonConsRep"}; // Nonconsecutive rep name generator
V3UniqueNames m_disableCntNames{"__VdisableCnt"}; // Disable condition counter name generator
V3UniqueNames m_blockNames{"__VassertBlock"}; // Names of blocks with temporaries
V3UniqueNames m_propVarNames{"__Vpropvar"}; // Property-local variable name generator
V3UniqueNames m_activeNames{"__VassertsActive"}; // Active asserts map name generator
bool m_inAssign = false; // True if in an AssignNode
@@ -414,6 +415,7 @@ private:
flp, new AstVarRef{flp, queueVarp, VAccess::READWRITE}, VCMethod::DYN_POP,
new AstTime{nodep->fileline(), m_modp->timeunit()}};
popp->addPinsp(skewp->unlinkFrBack());
refp->access(VAccess::READWRITE); // Only conditionally assigned
popp->addPinsp(refp);
popp->dtypeSetVoid();
m_clockingp->addNextHere(
@@ -561,8 +563,7 @@ private:
}
if (m_disableSeqIfp && remainp) {
AstIf* const disableSeqIfp = m_disableSeqIfp->cloneTree(false);
// Keep continuation statements in a proper statement-list container.
disableSeqIfp->addThensp(new AstBegin{flp, "", remainp, true});
disableSeqIfp->addThensp(remainp);
remainp = disableSeqIfp;
}
if (remainp) {
@@ -659,9 +660,9 @@ private:
iterate(nodep->lhsp());
}
iterate(nodep->rhsp());
if (nodep->timingControlp()) {
iterate(nodep->timingControlp());
} else if (m_inSynchDrive) {
if (nodep->timingControlp()) iterate(nodep->timingControlp());
// A drive without a cycle delay, also after removing '##0', updates the clockvar now
if (m_inSynchDrive && !nodep->timingControlp()) {
AstAssign* const assignp = new AstAssign{
nodep->fileline(), nodep->lhsp()->unlinkFrBack(), nodep->rhsp()->unlinkFrBack()};
assignp->user1(true);
@@ -877,7 +878,7 @@ private:
= getProcessAssocArrayDelete(new AstVarRef{flp, activep, VAccess::WRITE});
// Main assertion block
AstBegin* const bodyp = new AstBegin{flp, "", nullptr, true};
AstBegin* const bodyp = new AstBegin{flp, m_blockNames.get(""), nullptr, true};
bodyp->addStmtsp(incrementp);
bodyp->addStmtsp(loopp);
bodyp->addStmtsp(clausep);
@@ -906,7 +907,7 @@ private:
new AstConst{flp, 1}}});
// Final assertion block
AstBegin* const finalp = new AstBegin{flp, "", nullptr, true};
AstBegin* const finalp = new AstBegin{flp, m_blockNames.get(""), nullptr, true};
finalp->addStmtsp(activeCountp);
finalp->addStmtsp(initActiveCountp);
finalp->addStmtsp(finalLoopp);
@@ -1072,15 +1073,13 @@ private:
windowp->addStmtsp(new AstLoopTest{
flp, windowp, new AstNot{flp, new AstVarRef{flp, doneVarp, VAccess::READ}}});
// if (expr) { fail; done = 1; } -- window closed, expr true again
AstBegin* const failBlockp = new AstBegin{flp, "", nullptr, true};
failBlockp->addStmtsp(new AstPExprClause{flp, false});
failBlockp->addStmtsp(setDone());
windowp->addStmtsp(new AstIf{flp, exprp->cloneTreePure(false), failBlockp});
AstNode* const failsp = new AstPExprClause{flp, false};
failsp->addNext(setDone());
windowp->addStmtsp(new AstIf{flp, exprp->cloneTreePure(false), failsp});
// if (rhs) { pass; done = 1; } -- consequent true at this !expr endpoint
AstBegin* const passBlockp = new AstBegin{flp, "", nullptr, true};
passBlockp->addStmtsp(new AstPExprClause{flp, true});
passBlockp->addStmtsp(setDone());
windowp->addStmtsp(new AstIf{flp, rhsp, passBlockp});
AstNode* const passsp = new AstPExprClause{flp, true};
passsp->addNext(setDone());
windowp->addStmtsp(new AstIf{flp, rhsp, passsp});
// @(clk) -- advance to next cycle in window
windowp->addStmtsp(
new AstEventControl{flp, new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr});
@@ -1294,7 +1293,7 @@ private:
// */ }
AstBegin* const bodyp = pexprp->bodyp();
AstNode* const origStmtsp = bodyp->stmtsp()->unlinkFrBackWithNext();
AstIf* const guardp = new AstIf{flp, condp, new AstBegin{flp, "", origStmtsp, true}};
AstIf* const guardp = new AstIf{flp, condp, origStmtsp};
bodyp->addStmtsp(guardp);
nodep->replaceWith(pexprp);
// Don't iterate pexprp here -- it was already iterated when created
@@ -1368,10 +1367,9 @@ private:
loopp->addStmtsp(new AstLoopTest{
flp, loopp, new AstLogNot{flp, new AstVarRef{flp, donep, VAccess::READ}}});
{
AstBegin* const passp = new AstBegin{flp, "", nullptr, true};
passp->addStmtsp(new AstPExprClause{flp});
passp->addStmtsp(decrementVar);
passp->addStmtsp(setDone());
AstNode* const passp = new AstPExprClause{flp};
passp->addNext(decrementVar);
passp->addNext(setDone());
AstNodeExpr* passCondp = rhsp;
if (nodep->isOverlapping()) {
passCondp = new AstLogAnd{flp, lhsp->cloneTreePure(false), passCondp};
@@ -1379,10 +1377,9 @@ private:
loopp->addStmtsp(new AstIf{flp, passCondp, passp});
}
{
AstBegin* const failp = new AstBegin{flp, "", nullptr, true};
failp->addStmtsp(new AstPExprClause{flp, false});
failp->addStmtsp(decrementVar->cloneTree(false));
failp->addStmtsp(setDone());
AstNode* const failp = new AstPExprClause{flp, false};
failp->addNext(decrementVar->cloneTree(false));
failp->addNext(setDone());
loopp->addStmtsp(new AstIf{
flp,
new AstLogAnd{flp,
@@ -1396,7 +1393,7 @@ private:
flp, new AstLogNot{flp, new AstVarRef{flp, donep, VAccess::READ}}, delayp});
// Main assertion block
AstBegin* const bodyp = new AstBegin{flp, "", nullptr, true};
AstBegin* const bodyp = new AstBegin{flp, m_blockNames.get(""), nullptr, true};
bodyp->addStmtsp(donep);
bodyp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, donep, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}});
@@ -1428,7 +1425,7 @@ private:
new AstConst{flp, 1}}});
// Final assertion block
AstBegin* const finalp = new AstBegin{flp, "", nullptr, true};
AstBegin* const finalp = new AstBegin{flp, m_blockNames.get(""), nullptr, true};
finalp->addStmtsp(activeCountp);
finalp->addStmtsp(initActiveCountp);
finalp->addStmtsp(finalLoopp);
@@ -1459,7 +1456,7 @@ private:
AstNodeExpr* const passCondp
= nodep->isOverlapping() ? new AstLogAnd{flp, lhsp->cloneTreePure(false), rhsCopyp}
: rhsCopyp;
AstBegin* const beginp = new AstBegin{flp, "", loopp, true};
AstBegin* const beginp = new AstBegin{flp, m_blockNames.get(""), loopp, true};
beginp->addStmtsp(
new AstIf{flp, passCondp, new AstPExprClause{flp}, new AstPExprClause{flp, false}});
@@ -1548,7 +1545,7 @@ private:
"Unsupported: $sampled inside disabled condition of a sequence");
m_disablep = new AstConst{m_disablep->fileline(), AstConst::BitFalse{}};
// always a copy is used, so remove it now
pushDeletep(m_disablep);
pushDeletep(m_disablep); // m_disablep used later
}
FileLine* const flp = nodep->fileline();
// Add counter which counts times the condition turned true
@@ -1577,6 +1574,7 @@ private:
= new AstAssign{flp, new AstVarRef{flp, initialCntp, VAccess::WRITE},
readCntRefp->cloneTree(false)};
// Prepend to the sequence body to keep statement list structure valid.
UASSERT_OBJ(!bodyp->name().empty(), bodyp, "Sequence body should be named");
AstNode* const origStmtsp = bodyp->stmtsp()->unlinkFrBackWithNext();
bodyp->addStmtsp(initialCntp);
initialCntp->addNextHere(assignp);
@@ -1586,7 +1584,7 @@ private:
= new AstIf{flp, new AstEq{flp, new AstVarRef{flp, initialCntp, VAccess::READ},
readCntRefp->cloneTree(false)}};
// Delete it, because it is always copied before insetion to the AST
pushDeletep(m_disableSeqIfp);
pushDeletep(m_disableSeqIfp); // m_disableSeqIfp used later
}
iterateChildren(nodep);
}
+5 -1
View File
@@ -66,7 +66,7 @@ VCMethod VCMethod::arrayMethod(const string& name) {
std::string VNUser::dumpStr(std::string (*fmtAddrp)(const void*)) const {
#ifdef VL_USER_TYPE_CHECKS
if (const int* const uip = std::get_if<int>(&m_u)) return "#"s + cvtToStr(*uip);
if (const uint64_t* const uip = std::get_if<uint64_t>(&m_u)) return "#"s + cvtToStr(*uip);
if (void* const* const upp = std::get_if<void*>(&m_u)) return fmtAddrp(*upp);
return "";
#else
@@ -208,6 +208,10 @@ string AstNode::dedotName(const string& namein) {
return pretty;
}
string AstNode::nameNoArray(const string& namein) {
return namein.substr(0, namein.find("__BRA__"));
}
string AstNode::vcdName(const string& namein) {
// VCD tracing expects space to separate hierarchy
// Dots are reserved for dots the user put in the name
+41 -40
View File
@@ -56,6 +56,7 @@
// Forward declarations
class V3Graph;
class ExecMTask;
class VIfaceCaptureTag;
//######################################################################
@@ -151,11 +152,11 @@ class VNUser final {
#ifdef VL_USER_TYPE_CHECKS
// monostate is an unwritten / cleared slot. It can be read as either form
// and yields nullptr/0.
std::variant<std::monostate, int, void*> m_u;
std::variant<std::monostate, uint64_t, void*> m_u;
#else
union {
void* up;
int ui;
uint64_t uq;
} m_u;
#endif
@@ -164,7 +165,7 @@ public:
VNUser() = default;
// non-explicit:
// cppcheck-suppress noExplicitConstructor
VNUser(int i) {
VNUser(uint64_t i) {
// VNUser{0} represents the monostate
if (i) m_u = i;
}
@@ -178,22 +179,22 @@ public:
typename std::enable_if<std::is_pointer<T>::value, T>::type to() const VL_MT_SAFE {
if (std::holds_alternative<std::monostate>(m_u)) return nullptr;
void* const* const upp = std::get_if<void*>(&m_u);
UASSERT_STATIC(upp, "AstNode user() slot written as int, read as pointer");
UASSERT_STATIC(upp, "AstNode user() slot written as uint64_t, read as pointer");
return reinterpret_cast<T>(*upp);
}
int toInt() const {
uint64_t toUQuad() const {
if (std::holds_alternative<std::monostate>(m_u)) return 0;
const int* const uip = std::get_if<int>(&m_u);
UASSERT_STATIC(uip, "AstNode user() slot written as pointer, read as int");
const uint64_t* const uip = std::get_if<uint64_t>(&m_u);
UASSERT_STATIC(uip, "AstNode user() slot written as pointer, read as uint64_t");
return *uip;
}
#else
VNUser() = default;
// non-explicit:
// cppcheck-suppress noExplicitConstructor
VNUser(int i) {
VNUser(uint64_t i) {
m_u.up = nullptr;
m_u.ui = i;
m_u.uq = i;
}
explicit VNUser(void* p) { m_u.up = p; }
~VNUser() = default;
@@ -202,7 +203,7 @@ public:
typename std::enable_if<std::is_pointer<T>::value, T>::type to() const VL_MT_SAFE {
return reinterpret_cast<T>(m_u.up);
}
int toInt() const { return m_u.ui; }
uint64_t toUQuad() const { return m_u.uq; }
#endif
VSymEnt* toSymEnt() const { return to<VSymEnt*>(); }
AstNode* toNodep() const VL_MT_SAFE { return to<AstNode*>(); }
@@ -391,7 +392,7 @@ protected:
RELINK_OP4
};
AstNode* m_oldp = nullptr; // The old node that was linked to this point in the tree
AstNode* m_backp = nullptr;
AstNode* m_backp = nullptr; // Saved AstNode::m_backp of the unlinked node, to relink
AstNode** m_iterpp = nullptr;
RelinkWhatEn m_chg = RELINK_BAD;
@@ -492,14 +493,14 @@ class AstNode VL_NOT_FINAL {
// This member ordering both allows 64 bit alignment and puts associated data together
// (under VL_USER_TYPE_CHECKS a VNUser is larger than 64 bits, so this packing no
// longer holds; that build trades node size for catching int/pointer confusion)
VNUser m_user1u{0}; // Contains any information the user iteration routine wants
VNUser m_user1u{nullptr}; // Contains any information the user iteration routine wants
uint32_t m_user1Cnt = 0; // Mark of when userp was set
uint32_t m_user2Cnt = 0; // Mark of when userp was set
VNUser m_user2u{0}; // Contains any information the user iteration routine wants
VNUser m_user3u{0}; // Contains any information the user iteration routine wants
VNUser m_user2u{nullptr}; // Contains any information the user iteration routine wants
VNUser m_user3u{nullptr}; // Contains any information the user iteration routine wants
uint32_t m_user3Cnt = 0; // Mark of when userp was set
uint32_t m_user4Cnt = 0; // Mark of when userp was set
VNUser m_user4u{0}; // Contains any information the user iteration routine wants
VNUser m_user4u{nullptr}; // Contains any information the user iteration routine wants
// METHODS
void op1p(AstNode* nodep) {
@@ -658,6 +659,7 @@ public:
string origNameProtect() const; // origName with --protect-id applied
string shortName() const; // Name with __PVT__ removed for concatenating scopes
static string dedotName(const string& namein); // Name with dots removed
static string nameNoArray(const string& namein); // Name with any array index removed
static string prettyName(const string& namein) VL_PURE; // Name for printing out to the user
static string vpiName(const string& namein); // Name for vpi access
static string prettyNameQ(const string& namein) { // Quoted pretty name (for errors)
@@ -708,61 +710,61 @@ public:
VNUser user1u() const VL_MT_STABLE {
// Slows things down measurably, so disabled by default
//UASSERT_STATIC(VNUser1InUse::s_userBusy, "user1p used without AstUserInUse");
return ((m_user1Cnt == VNUser1InUse::s_userCntGbl) ? m_user1u : VNUser{0});
return ((m_user1Cnt == VNUser1InUse::s_userCntGbl) ? m_user1u : VNUser{nullptr});
}
AstNode* user1p() const VL_MT_STABLE { return user1u().toNodep(); }
void user1u(const VNUser& user) { m_user1u = user; m_user1Cnt = VNUser1InUse::s_userCntGbl; }
void user1p(void* userp) { user1u(VNUser{userp}); }
void user1(int val) { user1u(VNUser{val}); }
int user1() const { return user1u().toInt(); }
int user1Inc(int val = 1) { const int v = user1(); user1(v + val); return v; }
int user1Or(int val) { const int v = user1(); user1(v | val); return v; }
int user1SetOnce() { const int v = user1(); if (!v) user1(1); return v; } // Better for cache than user1Inc()
void user1(uint64_t val) { user1u(VNUser{val}); }
uint64_t user1() const { return user1u().toUQuad(); }
uint64_t user1Inc(uint64_t val = 1) { const uint64_t v = user1(); user1(v + val); return v; }
uint64_t user1Or(uint64_t val) { const uint64_t v = user1(); user1(v | val); return v; }
uint64_t user1SetOnce() { const uint64_t v = user1(); if (!v) user1(1); return v; } // Better for cache than user1Inc()
static void user1ClearTree() { VNUser1InUse::clear(); } // Clear userp()'s across the entire tree
VNUser user2u() const VL_MT_STABLE {
// Slows things down measurably, so disabled by default
//UASSERT_STATIC(VNUser2InUse::s_userBusy, "user2p used without AstUserInUse");
return ((m_user2Cnt == VNUser2InUse::s_userCntGbl) ? m_user2u : VNUser{0});
return ((m_user2Cnt == VNUser2InUse::s_userCntGbl) ? m_user2u : VNUser{nullptr});
}
AstNode* user2p() const VL_MT_STABLE { return user2u().toNodep(); }
void user2u(const VNUser& user) { m_user2u = user; m_user2Cnt = VNUser2InUse::s_userCntGbl; }
void user2p(void* userp) { user2u(VNUser{userp}); }
void user2(int val) { user2u(VNUser{val}); }
int user2() const { return user2u().toInt(); }
int user2Inc(int val = 1) { const int v = user2(); user2(v + val); return v; }
int user2Or(int val) { const int v = user2(); user2(v | val); return v; }
int user2SetOnce() { const int v = user2(); if (!v) user2(1); return v; } // Better for cache than user2Inc()
void user2(uint64_t val) { user2u(VNUser{val}); }
uint64_t user2() const { return user2u().toUQuad(); }
uint64_t user2Inc(uint64_t val = 1) { const uint64_t v = user2(); user2(v + val); return v; }
uint64_t user2Or(uint64_t val) { const uint64_t v = user2(); user2(v | val); return v; }
uint64_t user2SetOnce() { const uint64_t v = user2(); if (!v) user2(1); return v; } // Better for cache than user2Inc()
static void user2ClearTree() { VNUser2InUse::clear(); } // Clear userp()'s across the entire tree
VNUser user3u() const VL_MT_STABLE {
// Slows things down measurably, so disabled by default
//UASSERT_STATIC(VNUser3InUse::s_userBusy, "user3p used without AstUserInUse");
return ((m_user3Cnt == VNUser3InUse::s_userCntGbl) ? m_user3u : VNUser{0});
return ((m_user3Cnt == VNUser3InUse::s_userCntGbl) ? m_user3u : VNUser{nullptr});
}
AstNode* user3p() const VL_MT_STABLE { return user3u().toNodep(); }
void user3u(const VNUser& user) { m_user3u = user; m_user3Cnt = VNUser3InUse::s_userCntGbl; }
void user3p(void* userp) { user3u(VNUser{userp}); }
void user3(int val) { user3u(VNUser{val}); }
int user3() const { return user3u().toInt(); }
int user3Inc(int val = 1) { const int v = user3(); user3(v + val); return v; }
int user3Or(int val) { const int v = user3(); user3(v | val); return v; }
int user3SetOnce() { const int v = user3(); if (!v) user3(1); return v; } // Better for cache than user3Inc()
void user3(uint64_t val) { user3u(VNUser{val}); }
uint64_t user3() const { return user3u().toUQuad(); }
uint64_t user3Inc(uint64_t val = 1) { const uint64_t v = user3(); user3(v + val); return v; }
uint64_t user3Or(uint64_t val) { const uint64_t v = user3(); user3(v | val); return v; }
uint64_t user3SetOnce() { const uint64_t v = user3(); if (!v) user3(1); return v; } // Better for cache than user3Inc()
static void user3ClearTree() { VNUser3InUse::clear(); } // Clear userp()'s across the entire tree
VNUser user4u() const VL_MT_STABLE {
// Slows things down measurably, so disabled by default
//UASSERT_STATIC(VNUser4InUse::s_userBusy, "user4p used without AstUserInUse");
return ((m_user4Cnt == VNUser4InUse::s_userCntGbl) ? m_user4u : VNUser{0});
return ((m_user4Cnt == VNUser4InUse::s_userCntGbl) ? m_user4u : VNUser{nullptr});
}
AstNode* user4p() const VL_MT_STABLE { return user4u().toNodep(); }
void user4u(const VNUser& user) { m_user4u = user; m_user4Cnt = VNUser4InUse::s_userCntGbl; }
void user4p(void* userp) { user4u(VNUser{userp}); }
void user4(int val) { user4u(VNUser{val}); }
int user4() const { return user4u().toInt(); }
int user4Or(int val) { const int v = user4(); user4(v | val); return v; }
int user4Inc(int val = 1) { const int v = user4(); user4(v + val); return v; }
int user4SetOnce() { const int v = user4(); if (!v) user4(1); return v; } // Better for cache than user4Inc()
void user4(uint64_t val) { user4u(VNUser{val}); }
uint64_t user4() const { return user4u().toUQuad(); }
uint64_t user4Or(uint64_t val) { const uint64_t v = user4(); user4(v | val); return v; }
uint64_t user4Inc(uint64_t val = 1) { const uint64_t v = user4(); user4(v + val); return v; }
uint64_t user4SetOnce() { const uint64_t v = user4(); if (!v) user4(1); return v; } // Better for cache than user4Inc()
static void user4ClearTree() { VNUser4InUse::clear(); } // Clear userp()'s across the entire tree
// clang-format on
@@ -1035,7 +1037,6 @@ protected:
AstNode* iterateSubtreeReturnEdits(VNVisitor& v);
static void dumpJsonNum(std::ostream& os, const std::string& name, int64_t val);
static void dumpJsonBool(std::ostream& os, const std::string& name, bool val);
static void dumpJsonBoolIf(std::ostream& os, const std::string& name, bool val);
static void dumpJsonStr(std::ostream& os, const std::string& name, const std::string& val);
static void dumpJsonPtr(std::ostream& os, const std::string& name, const AstNode* const valp);
+400 -265
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -149,6 +149,9 @@ bool AstActive::hasCombo() const { return m_sentreep->hasCombo(); }
bool AstActive::hasInitial() const { return m_sentreep->hasInitial(); }
bool AstActive::hasStatic() const { return m_sentreep->hasStatic(); }
bool AstNodeModule::isDollarUnit() const { return this == v3Global.rootp()->dollarUnitPkgp(); }
bool AstNodeModule::isConstPool() const { return this == v3Global.rootp()->constPoolPkgp(); }
AstAlways::AstAlways(AstAssignW* assignp)
: ASTGEN_SUPER_Always(assignp->fileline(), assignp)
, m_keyword{VAlwaysKwd::CONT_ASSIGN} {}
@@ -165,10 +168,10 @@ bool AstVar::sameNode(const AstNode* samep) const {
&& covergroupRefMember() == asamep->covergroupRefMember();
}
AstMatchMasked::AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarScope* matchp)
AstMatchMasked::AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarRef* matchp)
: ASTGEN_SUPER_MatchMasked(fl) {
this->lhsp(lhsp);
this->matchp(new AstVarRef{fl, matchp, VAccess::READ});
this->matchp(matchp);
dtypeSetUInt32();
}
+108 -22
View File
@@ -34,10 +34,14 @@ class AstNodeDType VL_NOT_FINAL : public AstNode {
// Ideally width() would migrate to BasicDType as that's where it makes sense,
// but it's currently so prevalent in the code we leave it here.
// Note the below members are included in AstTypeTable::Key lookups
// dist-ast-dump-suppress // Part of dumpSmall
int m_width = 0; // (also in AstTypeTable::Key) Bit width of operation
int m_widthMin
= 0; // (also in AstTypeTable::Key) If unsized, bitwidth of minimum implementation
// (also in AstTypeTable::Key) If unsized, bitwidth of minimum implementation
// dist-ast-dump-suppress // Part of dumpSmall
int m_widthMin = 0;
// dist-ast-dump-suppress // Part of dumpSmall
VSigning m_numeric; // (also in AstTypeTable::Key) Node is signed
// dist-ast-dump-suppress // Part of dumpSmall
bool m_generic = false; // Simple globally referenced type, don't garbage collect
// Unique number assigned to each dtype during creation for IEEE matching
static int s_uniqueNum;
@@ -246,8 +250,8 @@ class AstNodeUOrStructDType VL_NOT_FINAL : public AstNodeDType {
//
// @astgen ptr := m_classOrPackagep : Optional[AstNodeModule] // Package emitted with
string m_name; // Name from upper typedef, if any
const int m_uniqueNum;
bool m_packed;
const int m_uniqueNum; // Unique ID distinguishing this dtype instance, for hashing/naming
bool m_packed; // Packed struct/union, else unpacked
bool m_isFourstate = false; // V3Width computes; true if any member is 4-state
bool m_constrainedRand = false; // True if struct has constraint expression
bool m_emitToString = false; // Generate to_string() for this struct/union if set
@@ -315,7 +319,7 @@ public:
class AstEnumItem final : public AstNode {
// @astgen op1 := rangep : Optional[AstRange] // Range for name appending
// @astgen op2 := valuep : Optional[AstNodeExpr]
string m_name;
string m_name; // Name of item
public:
// Parents: ENUM
@@ -405,6 +409,7 @@ class AstBasicDType final : public AstNodeDType {
// @astgen op1 := rangep : Optional[AstRange] // Range of variable
struct Members final {
VBasicDTypeKwd m_keyword; // (also in VBasicTypeKey) What keyword created basic type
// dist-ast-dump-suppress // Part of dumpSmall
VNumRange m_nrange; // (also in VBasicTypeKey) Numeric msb/lsb (if non-opaque keyword)
bool operator==(const Members& rhs) const {
return rhs.m_keyword == m_keyword && rhs.m_nrange == m_nrange;
@@ -493,6 +498,9 @@ public:
bool isStdRandomGenerator() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::RANDOM_STDGENERATOR;
}
bool isCovergroupInstHandle() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::COVERGROUP_INSTHANDLE;
}
bool isOpaque() const VL_MT_SAFE { return keyword().isOpaque(); }
bool isString() const VL_MT_STABLE { return keyword().isString(); }
bool isZeroInit() const { return keyword().isZeroInit(); }
@@ -682,13 +690,89 @@ public:
int widthTotalBytes() const override { return 1; }
bool isCompound() const override { return false; }
};
class AstCoverCrossDType final : public AstNodeDType {
// Borrowed pointer to VlCoverCrossT<...> or construction-time VlCoverCrossDyn.
const uint32_t m_dimensions;
const uint32_t m_tuples; // Fixed capacity number of cross tuples (VlCoverCrossT's Tuples)
const uint32_t m_bins; // Fixed capacity number of explicit bins (VlCoverCrossT's Bins)
const uint32_t m_autoBins; // Fixed capacity number of auto bins (VlCoverCrossT's AutoBins)
const uint64_t m_binWords; // Fixed capacity selection words (VlCoverCrossT's BinWords)
const bool m_dynamic; // Bin layout is determined at covergroup construction
public:
AstCoverCrossDType(FileLine* fl, uint32_t dimensions, uint32_t tuples, uint32_t bins,
uint32_t autoBins, uint64_t binWords, bool dynamic = false)
: ASTGEN_SUPER_CoverCrossDType(fl)
, m_dimensions{dimensions}
, m_tuples{tuples}
, m_bins{bins}
, m_autoBins{autoBins}
, m_binWords{binWords}
, m_dynamic{dynamic} {
dtypep(this);
}
ASTGEN_MEMBERS_AstCoverCrossDType;
const char* broken() const override {
BROKEN_RTN(m_dimensions == 0);
return nullptr;
}
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
const AstCoverCrossDType* const sp = VN_DBG_AS(samep, CoverCrossDType);
return dimensions() == sp->dimensions() && tuples() == sp->tuples() && bins() == sp->bins()
&& autoBins() == sp->autoBins() && binWords() == sp->binWords()
&& isDynamic() == sp->isDynamic();
} // LCOV_EXCL_STOP
bool similarDTypeNode(const AstNodeDType* samep) const override { return this == samep; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
void dumpSmall(std::ostream& str) const override;
uint32_t dimensions() const { return m_dimensions; }
uint32_t tuples() const { return m_tuples; }
uint32_t bins() const { return m_bins; }
uint32_t autoBins() const { return m_autoBins; }
uint64_t binWords() const { return m_binWords; }
bool isDynamic() const { return m_dynamic; }
string cppTemplateArgs() const;
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
int widthAlignBytes() const override { return sizeof(void*); }
int widthTotalBytes() const override { return sizeof(void*); }
bool isCompound() const override { return true; }
};
class AstCoverpointDType final : public AstNodeDType {
// Borrowed pointer to a covergroup coverpoint runtime, 'VlCoverpointT<hitBound>*'.
// Follows pattern of AstQueueDType in capturing the compile-time max bin overlap
// template argument.
uint32_t m_hitBound; // VlCoverpointT<> template argument; hit list size, >= 1
public:
AstCoverpointDType(FileLine* fl, uint32_t hitBound)
: ASTGEN_SUPER_CoverpointDType(fl)
, m_hitBound{hitBound} {
dtypep(this);
}
ASTGEN_MEMBERS_AstCoverpointDType;
const char* broken() const override {
BROKEN_RTN(m_hitBound < 1);
return nullptr;
}
// V3Covergroup interns these one-per-hitBound into the type table, so identity is
// equality; there is never a second node with the same bound to compare against.
bool similarDTypeNode(const AstNodeDType* samep) const override { return this == samep; }
void dumpSmall(std::ostream& str) const override;
// ACCESSORS
uint32_t hitBound() const { return m_hitBound; }
// METHODS
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
int widthAlignBytes() const override { return sizeof(void*); }
int widthTotalBytes() const override { return sizeof(void*); }
bool isCompound() const override { return true; }
};
class AstDefImplicitDType final : public AstNodeDType {
// For parsing enum/struct/unions that are declared with a variable rather than typedef
// This allows "var enum {...} a,b" to share the enum definition for both variables
// After link, these become typedefs
// @astgen op1 := childDTypep : Optional[AstNodeDType]
string m_name;
const int m_uniqueNum;
string m_name; // Data type name
const int m_uniqueNum; // Unique ID distinguishing this dtype instance, for hashing/naming
public:
AstDefImplicitDType(FileLine* fl, const string& name, VFlagChildDType, AstNodeDType* dtp)
@@ -798,13 +882,8 @@ class AstEnumDType final : public AstNodeDType {
// @astgen op2 := itemsp : List[AstEnumItem]
//
// @astgen ptr := m_refDTypep : Optional[AstNodeDType] // Elements of this type (post-width)
public:
using TableMap = std::map<VAttrType, AstVar*>;
private:
string m_name; // Name from upper typedef, if any
const int m_uniqueNum;
TableMap m_tableMap; // Created table for V3Width only to remove duplicates
const int m_uniqueNum; // Unique ID distinguishing this dtype instance, for hashing/naming
public:
AstEnumDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, AstEnumItem* itemsp)
@@ -852,13 +931,12 @@ public:
return count;
}
bool isCompound() const override { return false; }
TableMap& tableMap() { return m_tableMap; }
const TableMap& tableMap() const { return m_tableMap; }
};
class AstIfaceGenericDType final : public AstNodeDType {
// Generic interface that will be replaced with AstIfaceRefDType
FileLine* m_modportFileline; // Where modport token was
// dist-ast-dump-suppress // Part of name()
string m_modportName; // "" = no modport
public:
explicit AstIfaceGenericDType(FileLine* fl)
@@ -872,8 +950,6 @@ public:
dtypep(this);
}
ASTGEN_MEMBERS_AstIfaceGenericDType;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
void dumpSmall(std::ostream& str) const override;
bool hasDType() const override VL_MT_SAFE { return true; }
bool maybePointedTo() const override VL_MT_SAFE { return true; }
@@ -903,7 +979,6 @@ class AstIfaceRefDType final : public AstNodeDType {
string m_cellName; // "" = no cell, such as when connects to 'input' iface
string m_ifaceName; // Interface name
string m_modportName; // "" = no modport
bool m_portDecl = false; // Interface_port_declaration
bool m_virtual = false; // True if virtual interface
public:
AstIfaceRefDType(FileLine* fl, const string& cellName, const string& ifaceName)
@@ -935,11 +1010,16 @@ public:
void dumpJson(std::ostream& str = std::cout) const override;
void dumpSmall(std::ostream& str) const override;
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
bool similarDTypeNode(const AstNodeDType* samep) const override { return this == samep; }
bool similarDTypeNode(const AstNodeDType* samep) const override {
// Each occurrence of a virtual interface type parses to its own node,
// so pointer identity is insufficient; compare the referenced
// interface instead, same equivalence as AstNode::computeCastable uses.
const AstIfaceRefDType* const asamep = VN_DBG_AS(samep, IfaceRefDType);
return ifaceViaCellp() && ifaceViaCellp() == asamep->ifaceViaCellp()
&& modportName() == asamep->modportName() && isVirtual() == asamep->isVirtual();
}
int widthAlignBytes() const override { return 0; }
int widthTotalBytes() const override { return 0; }
bool isPortDecl() const { return m_portDecl; }
void isPortDecl(bool flag) { m_portDecl = flag; }
bool isVirtual() const { return m_virtual; }
void isVirtual(bool flag) {
m_virtual = flag;
@@ -974,7 +1054,7 @@ class AstMemberDType final : public AstNodeDType {
string m_name; // Name of variable
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;
bool m_constrainedRand = false; // Member has a constraint expression
VRandAttr m_rand; // Randomizability of this member (rand, randc, etc)
public:
AstMemberDType(FileLine* fl, const string& name, VFlagChildDType, AstNodeDType* dtp,
@@ -1195,6 +1275,9 @@ class AstRefDType final : public AstNodeDType {
// @astgen ptr := m_refDTypep : Optional[AstNodeDType] // Data type references
// @astgen ptr := m_classOrPackagep : Optional[AstNodeModule] // Class/package defined in
string m_name; // Name of an AstTypedef
// Interface capture tag, owned by V3LinkDotIfaceCapture; cloning copies it
const VIfaceCaptureTag* m_captureTagp = nullptr;
public:
AstRefDType(FileLine* fl, const string& name)
: ASTGEN_SUPER_RefDType(fl)
@@ -1221,6 +1304,7 @@ public:
bool similarDTypeNode(const AstNodeDType* samep) const override {
return subDTypep()->similarDType(samep->subDTypep());
}
const char* broken() const override;
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
void dumpSmall(std::ostream& str) const override;
@@ -1245,6 +1329,8 @@ public:
void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
AstNodeModule* classOrPackagep() const { return m_classOrPackagep; }
void classOrPackagep(AstNodeModule* nodep) { m_classOrPackagep = nodep; }
const VIfaceCaptureTag* captureTagp() const { return m_captureTagp; }
void captureTagp(const VIfaceCaptureTag* tagp) { m_captureTagp = tagp; }
bool isCompound() const override {
v3fatalSrc("call isCompound on subdata type, not reference");
return false;
+45 -16
View File
@@ -66,7 +66,18 @@ public:
bool isOpaque() const { return VN_IS(this, CvtPackString); }
// True for SVA multi-cycle sequence nodes (SExpr, SConsRep, etc.)
virtual bool isMultiCycleSva() const { return false; }
bool isLValue() const;
const AstNodeExpr* getVAccessTargetRecurse() const;
AstNodeExpr* getVAccessTargetRecurse() {
return const_cast<AstNodeExpr*>( // casting constness away is safe since this function is
// non-const itself therefore, caller guarantees that
// this object is non-const
static_cast<const AstNodeExpr*>(this)->getVAccessTargetRecurse());
}
VAccess getVAccessRecurse() const;
// TODO: this actually means it's a write or RW, not that it's an LValue
bool isLValue() const { return getVAccessRecurse().isWriteOrRW(); }
// Return base var (or const) nodep dereferences
AstNode* baseFromp(bool overMembers);
// Wrap This expression into an AstStmtExpr to denote it occurs in statement position
inline AstStmtExpr* makeStmt();
@@ -189,6 +200,7 @@ class AstNodeCCall VL_NOT_FINAL : public AstNodeExpr {
// @astgen op2 := argsp : List[AstNodeExpr] // Note: op1 used by some sub-types only
//
// @astgen ptr := m_funcp : AstCFunc // Function being called
// dist-ast-dump-suppress // Too verbose
string m_argTypes;
bool m_superReference = false; // Called with super reference
@@ -216,7 +228,7 @@ public:
void funcp(AstCFunc* funcp) { m_funcp = funcp; }
string argTypes() const { return m_argTypes; }
void argTypes(const string& str) { m_argTypes = str; }
bool isUnlikely() const override { return m_funcp && m_funcp->isUnlikely(); }
string emitVerilog() final override { V3ERROR_NA_RETURN(""); }
string emitC() final override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const final override { return true; }
@@ -271,6 +283,7 @@ public:
bool superReference() const { return m_superReference; }
void superReference(bool flag) { m_superReference = flag; }
bool isPure() override;
bool isUnlikely() const override { return m_taskp && m_taskp->isUnlikely(); }
bool sameNode(const AstNode* samep) const override {
const AstNodeFTaskRef* const asamep = VN_DBG_AS(samep, NodeFTaskRef);
return taskp() == asamep->taskp() //
@@ -519,7 +532,6 @@ public:
}
AstNodeModule* classOrPackagep() const { return m_classOrPackagep; }
void classOrPackagep(AstNodeModule* nodep) { m_classOrPackagep = nodep; }
static AstNodeVarRef* varRefLValueRecurse(AstNode* nodep);
};
// === Concrete node types =====================================================
@@ -556,6 +568,7 @@ class AstWith final : public AstNode {
private:
// 'with (identifier_list) {...}' restricted form (IEEE 1800-2023 18.7).
bool m_restricted = false;
// dist-ast-dump-suppress // V3LinkDot temporary use only
bool m_validated = false; // identifier_list typo / unused checks already run
std::set<std::string> m_restrictedNames;
@@ -739,7 +752,7 @@ class AstCMethodHard final : public AstNodeExpr {
// @astgen op2 := pinsp : List[AstNodeExpr] // Arguments
// @astgen op3 := withp : Optional[AstWith] // With clause
VCMethod m_method; // Which method to call
bool m_pure = false; // Pure optimizable
VIsCached m_purity; // Pure optimizable
bool m_usePtr = false; // Use '->' not '.'
public:
AstCMethodHard(FileLine* fl, AstNodeExpr* fromp, VCMethod method, AstNodeExpr* pinsp = nullptr)
@@ -747,15 +760,19 @@ public:
, m_method{method} {
this->fromp(fromp);
addPinsp(pinsp);
setPurity();
}
ASTGEN_MEMBERS_AstCMethodHard;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return method().ascii(); }
bool sameNode(const AstNode* samep) const override {
const AstCMethodHard* const asamep = VN_DBG_AS(samep, CMethodHard);
return (m_method == asamep->m_method);
}
bool isPure() override { return m_pure; }
bool isPure() override {
if (!m_purity.isCached()) m_purity.set(getPurity());
return m_purity.get();
}
int instrCount() const override;
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -766,7 +783,7 @@ public:
void method(VCMethod value) { m_method = value; }
private:
void setPurity();
bool getPurity();
};
class AstCReset final : public AstNodeExpr {
// Reset variable at startup
@@ -1458,7 +1475,7 @@ class AstExprStmt final : public AstNodeExpr {
// @astgen op1 := stmtsp : List[AstNode]
// @astgen op2 := resultp : AstNodeExpr
private:
bool m_hasResult = true;
bool m_hasResult = true; // Returns result via resultp()
public:
AstExprStmt(FileLine* fl, AstNode* stmtsp, AstNodeExpr* resultp)
@@ -1469,6 +1486,8 @@ public:
}
ASTGEN_MEMBERS_AstExprStmt;
// METHODS
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { return true; }
@@ -1746,7 +1765,7 @@ public:
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstImplication final : public AstNodeExpr {
// Implication |-> |=> (IEEE 1800-2023 16.12.6) and followed-by #-# #=#
// Implication |-> |=> (IEEE 1800-2023 16.12.7) and followed-by #-# #=#
// (IEEE 1800-2023 16.12.9). Antecedent-miss is vacuous-pass for implication
// and non-vacuous-fail for followed-by, hence the separate flag.
// @astgen op1 := lhsp : AstNodeExpr
@@ -1774,7 +1793,7 @@ public:
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool isMultiCycleSva() const override { return m_isFollowedBy; }
bool isMultiCycleSva() const override { return m_isFollowedBy || !m_isOverlapped; }
bool isOverlapped() const { return m_isOverlapped; }
bool isFollowedBy() const { return m_isFollowedBy; }
};
@@ -1793,6 +1812,7 @@ public:
using KeyItemMap = std::map<uint64_t, AstInitItem*>;
private:
// dist-ast-dump-suppress // Dumped using dumpInitList
KeyItemMap m_map; // Node value for each array index
// METHODS
void dumpInitList(std::ostream& str) const;
@@ -1866,6 +1886,8 @@ public:
, m_name{name}
, m_index{index} {}
ASTGEN_MEMBERS_AstLambdaArgRef;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool sameNode(const AstNode* /*samep*/) const override { return true; }
string emitVerilog() override { return name(); }
string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -1885,7 +1907,7 @@ class AstMatchMasked final : public AstNodeExpr {
// @astgen op1 := lhsp : AstNodeExpr
// @astgen op2 := matchp : AstVarRef
public:
inline AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarScope* matchp);
inline AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarRef* matchp);
ASTGEN_MEMBERS_AstMatchMasked;
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { return "VL_MATCHMASKED_%lq(%lw, %li, %ri)"; }
@@ -2013,7 +2035,7 @@ class AstParseRef final : public AstNodeExpr {
// @astgen op1 := lhsp : Optional[AstNodeExpr]
// @astgen op2 := ftaskrefp : Optional[AstNodeFTaskRef]
string m_name;
string m_name; // Name of the variable/function/task
public:
AstParseRef(FileLine* fl, const string& name, AstNodeExpr* lhsp = nullptr,
@@ -2386,6 +2408,7 @@ class AstSFormatArg final : public AstNodeExpr {
// used to pass to (potentially) runtime decoding of format arguments
// PARENT: SFormatF (or next list of expressions)
// @astgen op1 := exprp : AstNodeExpr
// @astgen op2 := namep : Optional[AstNodeExpr] // Runtime enum name lookup
VFormatAttr m_formatAttr; // How to format expression
public:
@@ -2408,6 +2431,7 @@ public:
bool cleanOut() const override { return true; }
const char* broken() const override {
BROKEN_RTN(!VN_IS(backp(), SFormatF) && firstAbovep()); // In list under SFormatF
BROKEN_RTN(formatAttr().isEnum() != static_cast<bool>(namep()));
return nullptr;
}
VFormatAttr formatAttr() const { return m_formatAttr; }
@@ -2613,7 +2637,7 @@ class AstScopeName final : public AstNodeExpr {
// For display %m and DPI context imports
// Parents: AstSFormatF, AstNodeFTaskRef, AstNodeFTask
std::string m_scopeAttr;
std::string m_scopeEntr;
std::string m_scopeEntr; // Scope path for the DPI import/export context name
bool m_dpiExport = false; // Is for dpiExport
const bool m_forFormat; // Is for a format %m
static std::string scopeNameFormatter(const std::string& text);
@@ -3035,6 +3059,8 @@ public:
addConstraintsp(constraintsp);
}
ASTGEN_MEMBERS_AstWithParse;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool restricted() const { return m_restricted; }
void restricted(bool flag) { m_restricted = flag; }
@@ -4911,9 +4937,6 @@ public:
bool isPredictOptimizable() const override { return true; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
int instrCount() const override { return widthInstrs(); }
// Special operators
// Return base var (or const) nodep dereferences
static AstNode* baseFromp(AstNode* nodep, bool overMembers);
};
class AstAssocSel final : public AstNodeSel {
void init(const AstNode* fromp) {
@@ -5166,6 +5189,8 @@ public:
"not coded to create after dtypes resolved");
}
ASTGEN_MEMBERS_AstSelBit;
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
VAccess access() const { return m_access; }
void access(const VAccess& flag) { m_access = flag; }
};
@@ -5430,6 +5455,7 @@ public:
enum FmtType : int { ATOI = 10, ATOHEX = 16, ATOOCT = 8, ATOBIN = 2, ATOREAL = -1 };
private:
// dist-ast-dump-suppress // Part of name()
const FmtType m_fmtType; // Operation type
public:
AstAtoN(FileLine* fl, AstNodeExpr* lhsp, FmtType fmtType)
@@ -6438,6 +6464,7 @@ class AstVarXRef final : public AstNodeVarRef {
string m_dotted; // Dotted part of scope the name()'ed reference is under or ""
string m_inlinedDots; // Dotted hierarchy flattened out
bool m_containsGenBlock = false; // Contains gen block reference
bool m_readOnlyModport = false; // Linked via an input-only modport, until V3LinkLValue
public:
AstVarXRef(FileLine* fl, const string& name, const string& dotted, const VAccess& access)
: ASTGEN_SUPER_VarXRef(fl, nullptr, access)
@@ -6455,6 +6482,8 @@ public:
void inlinedDots(const string& flag) { m_inlinedDots = flag; }
bool containsGenBlock() const { return m_containsGenBlock; }
void containsGenBlock(const bool flag) { m_containsGenBlock = flag; }
bool readOnlyModport() const { return m_readOnlyModport; }
void readOnlyModport(const bool flag) { m_readOnlyModport = flag; }
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { return true; }
+230 -84
View File
@@ -44,6 +44,7 @@ class AstNodeCoverDecl VL_NOT_FINAL : public AstNode {
// Coverage counters are emitted in each module object, so duplicate
// no-inline instances can keep independent counts for forcePerInstance.
int m_localBinNum = 0; // Per-module coverage bin offset
bool m_perInstance = false; // Don't clone during inlining, already per instance
public:
AstNodeCoverDecl(VNType t, FileLine* fl, const string& page, const string& comment)
: AstNode(t, fl)
@@ -65,6 +66,8 @@ public:
void binNum(int flag) { m_binNum = flag; }
int localBinNum() const { return m_localBinNum; }
void localBinNum(int flag) { m_localBinNum = flag; }
bool perInstance() const { return m_perInstance; }
void perInstance(bool flag) { m_perInstance = flag; }
virtual int size() const = 0;
const string& comment() const { return m_text; } // text to insert in code
const string& page() const { return m_page; }
@@ -74,7 +77,8 @@ public:
bool sameNode(const AstNode* samep) const override {
const AstNodeCoverDecl* const asamep = VN_DBG_AS(samep, NodeCoverDecl);
return (fileline() == asamep->fileline() && hier() == asamep->hier()
&& comment() == asamep->comment() && page() == asamep->page());
&& comment() == asamep->comment() && page() == asamep->page()
&& perInstance() == asamep->perInstance());
}
bool isPredictOptimizable() const override { return false; }
void dataDeclp(AstNodeCoverDecl* nodep) { m_dataDeclp = nodep; }
@@ -94,6 +98,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
// dist-ast-dump-suppress // Not dumped due to verbosity
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
@@ -112,6 +117,8 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode {
bool m_isHideLocal : 1; // Verilog local
bool m_isHideProtected : 1; // Verilog protected
bool m_dpiPure : 1; // DPI import pure (vs. virtual pure)
bool m_keepAlive : 1; // Disable dead function elimination
bool m_needsSyms : 1; // Requires vlSymsp argument
bool m_pureVirtual : 1; // Pure virtual
bool m_recursive : 1; // Recursive or part of recursion
bool m_static : 1; // Static method in class
@@ -144,6 +151,8 @@ protected:
, m_isHideLocal{false}
, m_isHideProtected{false}
, m_dpiPure{false}
, m_keepAlive{false}
, m_needsSyms{true}
, m_pureVirtual{false}
, m_recursive{false}
, m_static{false}
@@ -211,6 +220,10 @@ public:
void isHideProtected(bool flag) { m_isHideProtected = flag; }
bool dpiPure() const { return m_dpiPure; }
void dpiPure(bool flag) { m_dpiPure = flag; }
bool keepAlive() const { return m_keepAlive; }
void keepAlive(bool flag) { m_keepAlive = flag; }
bool needsSyms() const { return m_needsSyms; }
void needsSyms(bool flag) { m_needsSyms = flag; }
bool pureVirtual() const { return m_pureVirtual; }
void pureVirtual(bool flag) { m_pureVirtual = flag; }
bool recursive() const { return m_recursive; }
@@ -293,8 +306,8 @@ class AstNodeModule VL_NOT_FINAL : public AstNode {
// @astgen op2 := stmtsp : List[AstNode]
string m_name; // Name of the module
const string m_origName; // Name of the module, ignoring name() changes, for dot lookup
// dist-ast-dump-suppress // For some user errors messages only, visible where used
string m_someInstanceName; // Hierarchical name of some arbitrary instance of this module.
// Used for user messages only.
string m_libname; // Work library
int m_depth = 0; // 1=top module, 2=cell off top, shared things low, for -depth options
int m_level = 0; // 1=top module, 2=cell off top, shared things have high number
@@ -312,7 +325,6 @@ class AstNodeModule VL_NOT_FINAL : public AstNode {
bool m_hasParameterList : 1; // Has #() for parameter declaration
bool m_hierBlock : 1; // Hierarchical Block marked by HIER_BLOCK pragma
bool m_hierParams : 1; // Block containing params for parameterized hier blocks
bool m_internal : 1; // Internally created
bool m_recursive : 1; // Recursive module
bool m_recursiveClone : 1; // If recursive, what module it clones, otherwise nullptr
bool m_parameterizedTemplate : 1; // True when at least one specialized clone exists;
@@ -334,7 +346,6 @@ protected:
, m_hasParameterList{false}
, m_hierBlock{false}
, m_hierParams{false}
, m_internal{false}
, m_recursive{false}
, m_recursiveClone{false}
, m_parameterizedTemplate{false}
@@ -347,6 +358,8 @@ public:
bool maybePointedTo() const override VL_MT_SAFE { return true; }
string name() const override VL_MT_STABLE { return m_name; }
virtual bool timescaleMatters() const = 0;
inline bool isDollarUnit() const; // Is the $unit package
inline bool isConstPool() const; // Is the constant pool package
// ACCESSORS
void name(const string& name) override { m_name = name; }
string origName() const override { return m_origName; }
@@ -378,8 +391,6 @@ public:
void hierBlock(bool flag) { m_hierBlock = flag; }
bool hierParams() const { return m_hierParams; }
void hierParams(bool flag) { m_hierParams = flag; }
bool internal() const { return m_internal; }
void internal(bool flag) { m_internal = flag; }
bool recursive() const { return m_recursive; }
void recursive(bool flag) { m_recursive = flag; }
void recursiveClone(bool flag) { m_recursiveClone = flag; }
@@ -543,6 +554,7 @@ class AstCFunc final : public AstNode {
bool m_dpiImportWrapper : 1; // Wrapper for invoking DPI import prototype from generated code
bool m_needProcess : 1; // Needs access to VlProcess of the caller
bool m_recursive : 1; // Recursive or part of recursion
bool m_unlikely : 1; // Unlikely to get called (though still optimize unlike slow())
bool m_noLife : 1; // Disable V3Life on this function - has multiple calls, and reads Syms
// state
bool m_isCovergroupSample : 1; // Automatic covergroup sample() function
@@ -575,6 +587,7 @@ public:
m_dpiImportPrototype = false;
m_dpiImportWrapper = false;
m_recursive = false;
m_unlikely = false;
m_noLife = false;
m_isCovergroupSample = false;
m_cost = v3Global.opt.instrCountDpi(); // As proxy for unknown general DPI cost
@@ -653,6 +666,8 @@ public:
bool isCoroutine() const { return m_rtnType == "VlCoroutine"; }
void recursive(bool flag) { m_recursive = flag; }
bool recursive() const { return m_recursive; }
void unlikely(bool flag) { m_unlikely = flag; }
bool isUnlikely() const override { return m_unlikely; } // Note virtual override
void noLife(bool flag) { m_noLife = flag; }
bool noLife() const { return m_noLife; }
bool isCovergroupSample() const { return m_isCovergroupSample; }
@@ -685,15 +700,18 @@ class AstCell final : public AstNode {
// A instantiation cell or interface call (don't know which until link)
// @astgen op1 := pinsp : List[AstPin] // List of port assignments
// @astgen op2 := paramsp : List[AstPin] // List of parameter assignments
// @astgen op3 := rangep : List[AstRange] // Range(s) for arrayed instances; multi-dim chains
// via nextp()
// @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing
// Pre V3Param: Range(s) for arrayed instances.
// From V3Param to V3Width: ranges of the arrayed cell this element cell was expanded from
// After V3Width: nullptr
// @astgen op3 := rangep : List[AstRange]
// @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing/VPI
//
// @astgen ptr := m_modp : Optional[AstNodeModule] // [AfterLink] Pointer to module instanced
FileLine* m_modNameFileline; // Where module the cell instances token was
string m_name; // Cell name
string m_origName; // Original name before dot addition
string m_modName; // Module the cell instances
int m_arrayIdx = -1; // Row-major position in instance array, from the left (V3Param-V3Width)
bool m_hasIfaceVar : 1; // True if a Var has been created for this cell
bool m_recursive : 1; // Self-recursive module
bool m_trace : 1; // Trace this cell
@@ -728,6 +746,8 @@ public:
FileLine* modNameFileline() const { return m_modNameFileline; }
AstNodeModule* modp() const { return m_modp; } // [AfterLink] = Pointer to module instantiated
void modp(AstNodeModule* nodep) { m_modp = nodep; }
int arrayIdx() const { return m_arrayIdx; }
void arrayIdx(int idx) { m_arrayIdx = idx; }
bool hasIfaceVar() const { return m_hasIfaceVar; }
void hasIfaceVar(bool flag) { m_hasIfaceVar = flag; }
void trace(bool flag) { m_trace = flag; }
@@ -781,6 +801,9 @@ public:
string name() const override VL_MT_STABLE { return m_cellp->name(); }
bool maybePointedTo() const override VL_MT_SAFE { return true; }
AstScope* scopep() const VL_MT_STABLE { return m_scopep; } // Pointer to scope it's under
void scopep(AstScope* nodep) { m_scopep = nodep; }
AstCellInline* cellp() const VL_MT_STABLE { return m_cellp; } // Pointer to the CellInline
void cellp(AstCellInline* nodep) { m_cellp = nodep; }
string origModName() const {
return m_cellp->origModName();
} // * = modp()->origName() before inlining
@@ -889,6 +912,8 @@ public:
}
}
ASTGEN_MEMBERS_AstClockingItem;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
VDirection direction() const { return m_direction; }
AstClockingItem* outputp() const { return m_outputp; }
void outputp(AstClockingItem* outputp) { m_outputp = outputp; }
@@ -907,11 +932,11 @@ public:
, m_libname{libname}
, m_configname{cellname} {}
ASTGEN_MEMBERS_AstConfig;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
std::string name() const override VL_MT_STABLE { return m_libname + "." + m_configname; }
std::string libname() const VL_MT_STABLE { return m_libname; }
std::string configname() const VL_MT_STABLE { return m_configname; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
};
class AstConfigCell final : public AstNode {
// Parents: CONFIGRULE
@@ -970,40 +995,6 @@ public:
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
};
class AstConstPool final : public AstNode {
// Container for const static data
// @astgen op1 := modulep : AstModule // m_modp below TODO: fix this mess
//
// @astgen ptr := m_modp : AstModule // The Module holding the Scope below ...
// @astgen ptr := m_scopep : AstScope // Scope holding the constant variables
std::unordered_multimap<uint32_t, AstVarScope*> m_tables; // Constant tables (unpacked arrays)
std::unordered_multimap<uint32_t, AstVarScope*> m_consts; // Constant tables (scalars)
AstVarScope* createNewEntry(const string& name, AstNodeExpr* initp);
public:
explicit AstConstPool(FileLine* fl);
ASTGEN_MEMBERS_AstConstPool;
bool maybePointedTo() const override VL_MT_SAFE { return true; }
void cloneRelink() override { V3ERROR_NA; } // Not cloneable
AstModule* modp() const { return m_modp; }
AstScope* scopep() const { return m_scopep; }
// Find a table (unpacked array) within the constant pool which is initialized with the
// given value, or create one if one does not already exists. The returned VarScope *might*
// have a different dtype than the given initp->dtypep(), including a different element type,
// but it will always have the same size and element width. In contexts where this matters,
// the caller must handle the dtype difference as appropriate.
AstVarScope* findTable(AstInitArray* initp);
// Find a constant within the constant pool which is initialized with the given value, or
// create one if one does not already exists. If 'mergeDType' is true, then the returned
// VarScope *might* have a different type than the given initp->dtypep(). In contexts where
// this matters, the caller must handle the dtype difference as appropriate. If 'mergeDType' is
// false, the returned VarScope will have _->dtypep()->sameTree(initp->dtypep()) return true.
AstVarScope* findConst(AstConst* initp, bool mergeDType);
// Rebuild hashes and missing variable scopes after potential removals
void rebuildVarScopesAndCache();
};
class AstConstraint final : public AstNode {
// Constraint
// @astgen op1 := itemsp : List[AstNode]
@@ -1051,12 +1042,13 @@ class AstCoverBin final : public AstNode {
// @astgen op4 := transp : List[AstCoverTransSet]
const string m_name; // Base name of the bin
const VCoverBinsType m_binsType; // Bin type (eg AUTO, IGNORE, ILLEGAL)
bool m_isArray = false; // Bin is either an auto-sized array of values or transitions
bool m_isArray = false; // Bin is an array: of a bin per value or transition, or of
// arraySizep() bins
bool m_isWildcard = false; // Bin uses wildcard matching (independent of ignore/illegal)
public:
AstCoverBin(FileLine* fl, const string& name, AstNode* rangesp, bool isIgnore, bool isIllegal,
bool isWildcard = false)
bool isWildcard = false, AstNodeExpr* iffp = nullptr)
: ASTGEN_SUPER_CoverBin(fl)
, m_name{name}
, m_binsType{isIllegal ? VCoverBinsType::BINS_ILLEGAL
@@ -1065,12 +1057,14 @@ public:
: VCoverBinsType::BINS_USER))}
, m_isWildcard{isWildcard} {
addRangesp(rangesp);
this->iffp(iffp);
}
// Constructor for automatic bins
AstCoverBin(FileLine* fl, const string& name, AstNodeExpr* arraySizep)
AstCoverBin(FileLine* fl, const string& name, AstNodeExpr* arraySizep,
VCoverBinsType type = VCoverBinsType::BINS_AUTO)
: ASTGEN_SUPER_CoverBin(fl)
, m_name{name}
, m_binsType{VCoverBinsType::BINS_AUTO}
, m_binsType{type}
, m_isArray{true} {
this->arraySizep(arraySizep);
}
@@ -1098,21 +1092,138 @@ public:
bool isArray() const { return m_isArray; }
void isArray(bool flag) { m_isArray = flag; }
};
class AstCoverBinsof final : public AstNode {
// A binsof selection of a coverpoint or one of its named bins
// @astgen op1 := pointp : AstCoverpointRef
// @astgen op2 := rangesp : List[AstNode] // Optional intersect value ranges
string m_name; // Selected bin name, or empty for all bins of the coverpoint
const bool m_isNegated; // Complement the selection within the cross product
public:
AstCoverBinsof(FileLine* fl, AstCoverpointRef* pointp, bool isNegated = false,
AstNode* rangesp = nullptr)
: ASTGEN_SUPER_CoverBinsof(fl)
, m_isNegated{isNegated} {
this->pointp(pointp);
addRangesp(rangesp);
}
ASTGEN_MEMBERS_AstCoverBinsof;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return m_name; }
void name(const string& name) override { m_name = name; }
bool isNegated() const { return m_isNegated; }
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
const AstCoverBinsof* const asamep = VN_DBG_AS(samep, CoverBinsof);
return m_name == asamep->m_name && m_isNegated == asamep->m_isNegated;
} // LCOV_EXCL_STOP
};
class AstCoverCrossBin final : public AstNode {
// A named cross bin and its selection expression
// @astgen op1 := selectp : Optional[AstNode] // Null for unsupported selections
// @astgen op2 := iffp : Optional[AstNodeExpr]
const string m_name; // Declared cross bin name
// dist-ast-dump-suppress // Bin kind is shown by verilogKwd() in emitted Verilog.
const VCoverBinsType m_binsType; // Normal, ignore, or illegal bin
public:
AstCoverCrossBin(FileLine* fl, const string& name, AstNode* selectp, AstNodeExpr* iffp,
VCoverBinsType binsType = VCoverBinsType::BINS_USER)
: ASTGEN_SUPER_CoverCrossBin(fl)
, m_name{name}
, m_binsType{binsType} {
this->selectp(selectp);
this->iffp(iffp);
}
ASTGEN_MEMBERS_AstCoverCrossBin;
string name() const override VL_MT_STABLE { return m_name; }
string verilogKwd() const override;
VCoverBinsType binsType() const { return m_binsType; }
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
const AstCoverCrossBin* const asamep = VN_DBG_AS(samep, CoverCrossBin);
return m_name == asamep->m_name && m_binsType.m_e == asamep->m_binsType.m_e;
} // LCOV_EXCL_STOP
};
class AstCoverCrossRef final : public AstNode {
// The complete bin-tuple space of the enclosing cross
const string m_name; // Referenced cross name
public:
AstCoverCrossRef(FileLine* fl, const string& name)
: ASTGEN_SUPER_CoverCrossRef(fl)
, m_name{name} {}
ASTGEN_MEMBERS_AstCoverCrossRef;
string name() const override VL_MT_STABLE { return m_name; }
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
return m_name == VN_DBG_AS(samep, CoverCrossRef)->m_name;
} // LCOV_EXCL_STOP
};
class AstCoverCrossSelect final : public AstNode {
// Intersection or union of two cross-bin selections
// @astgen op1 := lhsp : Optional[AstNode] // Null for an unsupported selection
// @astgen op2 := rhsp : Optional[AstNode] // Null for an unsupported selection
const bool m_isOr; // Union (||), rather than intersection (&&)
public:
AstCoverCrossSelect(FileLine* fl, AstNode* lhsp, AstNode* rhsp, bool isOr)
: ASTGEN_SUPER_CoverCrossSelect(fl)
, m_isOr{isOr} {
this->lhsp(lhsp);
this->rhsp(rhsp);
}
ASTGEN_MEMBERS_AstCoverCrossSelect;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool isOr() const { return m_isOr; }
string verilogKwd() const override { return isOr() ? "||" : "&&"; }
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
return m_isOr == VN_DBG_AS(samep, CoverCrossSelect)->m_isOr;
} // LCOV_EXCL_STOP
};
class AstCoverOption final : public AstNode {
// Coverage-option assignment
// @astgen op1 := valuep : AstNodeExpr
const VCoverOptionType m_optType; // Option being assigned
const bool m_typeOption; // type_option (vs option)
public:
AstCoverOption(FileLine* fl, VCoverOptionType optType, AstNodeExpr* valuep)
AstCoverOption(FileLine* fl, bool typeOption, VCoverOptionType optType, AstNodeExpr* valuep)
: ASTGEN_SUPER_CoverOption(fl)
, m_optType{optType} {
, m_optType{optType}
, m_typeOption{typeOption} {
this->valuep(valuep);
}
ASTGEN_MEMBERS_AstCoverOption;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
VCoverOptionType optType() const { return m_optType; }
bool typeOption() const { return m_typeOption; }
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
const AstCoverOption* const asamep = VN_DBG_AS(samep, CoverOption);
return m_optType.m_e == asamep->m_optType.m_e && m_typeOption == asamep->m_typeOption;
} // LCOV_EXCL_STOP
};
class AstCoverToggle final : public AstNode {
// Toggle analysis of given signal
// Parents: MODULE
// @astgen op1 := incp : AstCoverInc
// @astgen op2 := origp : AstNodeExpr
// @astgen op3 := changep : AstNodeExpr
public:
AstCoverToggle(FileLine* fl, AstCoverInc* incp, AstNodeExpr* origp, AstNodeExpr* changep)
: ASTGEN_SUPER_CoverToggle(fl) {
this->incp(incp);
this->origp(origp);
this->changep(changep);
}
ASTGEN_MEMBERS_AstCoverToggle;
int instrCount() const override { return 3 + INSTR_COUNT_BRANCH + INSTR_COUNT_LD; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return true; }
bool isOutputter() override {
return false; // Though the AstCoverInc under this is an outputter
}
};
class AstCoverTransItem final : public AstNode {
// Represents a single transition item: value or value[*N] or value[->N] or value[=N]
@@ -1143,6 +1254,20 @@ public:
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
};
class AstCoverWith final : public AstNode {
// The values of a coverpoint bin for which a 'with' filter is true (IEEE 1800-2023
// 19.5.1.1), the only range list element of its AstCoverBin
// @astgen op1 := subp : List[AstNode] // Range list, or AstCoverpointRef for all values
// @astgen op2 := filterp : AstNodeExpr
// @astgen op3 := itemp : Optional[AstVar] // Candidate value 'item', from V3LinkParse
public:
AstCoverWith(FileLine* fl, AstNode* subp, AstNodeExpr* filterp)
: ASTGEN_SUPER_CoverWith(fl) {
addSubp(subp);
this->filterp(filterp);
}
ASTGEN_MEMBERS_AstCoverWith;
};
class AstCovergroup final : public AstNode {
// Represents a covergroup declaration. V3LinkParse transforms this
// into an AstClass with isCovergroup==true and attaches the clocking
@@ -1258,6 +1383,8 @@ public:
BROKEN_RTN(!fmtp());
return nullptr;
}
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
string verilogKwd() const override { return "$"s + string{displayType().ascii()}; }
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
@@ -1320,13 +1447,21 @@ public:
};
class AstIntfRef final : public AstNode {
// An interface reference
string m_name; // Name of the reference
string m_name; // Hierarchical path of the reference
string m_baseName; // Final component of m_name, i.e. the reference port name
string m_modportName; // "" = no modport, else name of the modport referenced
public:
AstIntfRef(FileLine* fl, const string& name)
AstIntfRef(FileLine* fl, const string& name, const string& baseName, const string& modportName)
: ASTGEN_SUPER_IntfRef(fl)
, m_name{name} {}
string name() const override VL_MT_STABLE { return m_name; }
, m_name{name}
, m_baseName{baseName}
, m_modportName{modportName} {}
ASTGEN_MEMBERS_AstIntfRef;
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
string name() const override VL_MT_STABLE { return m_name; }
string baseName() const { return m_baseName; }
string modportName() const { return m_modportName; }
};
class AstLibrary final : public AstNode {
// Parents: NETLIST
@@ -1440,7 +1575,7 @@ class AstNetlist final : public AstNode {
// @astgen op3 := miscsp : List[AstNode]
//
// @astgen ptr := m_typeTablep : AstTypeTable // Reference to type table, for faster lookup
// @astgen ptr := m_constPoolp : AstConstPool // Reference to constant pool, for faster lookup
// @astgen ptr := m_constPoolPkgp : AstPackage // Constant pool package
// @astgen ptr := m_dollarUnitPkgp : Optional[AstPackage] // $unit
// @astgen ptr := m_stdPackagep : Optional[AstPackage] // SystemVerilog std package
// @astgen ptr := m_stdPackageProcessp : Optional[AstClass] // SystemVerilog std process class
@@ -1461,6 +1596,8 @@ class AstNetlist final : public AstNode {
// AstConst itself, as AstConst is a very common node and only a small fraction carry this
// name.
std::unordered_map<const AstConst*, string> m_constOrigParamNames;
// Module each node is in, only good while the tree holds still
std::unordered_map<const AstNode*, const AstNodeModule*> m_containingModules;
// The model's evaluation entry point functions
std::array<AstCFunc*, VEval::_ENUM_END> m_evalFuncps{};
// The trigger dump function of each region if exists, otherwise nullptr
@@ -1482,12 +1619,16 @@ public:
return modulesp(); // First one in the list, for now
}
AstTypeTable* typeTablep() { return m_typeTablep; }
AstConstPool* constPoolp() { return m_constPoolp; }
string astConstOrigParamName(const AstConst* nodep) const;
void astConstOrigParamName(const AstConst* nodep, const string& name);
void astConstOrigParamNameErase(const AstConst* nodep);
// Find the module a node is in, remembering what it passed on the way.
const AstNodeModule* containingModule(const AstNode* nodep);
// Forget remembered modules, as the tree has moved.
void clearContainingModules() { m_containingModules.clear(); }
AstPackage* constPoolPkgp() const { return m_constPoolPkgp; }
AstPackage* dollarUnitPkgp() const { return m_dollarUnitPkgp; }
AstPackage* dollarUnitPkgAddp();
void dollarUnitPkgp(AstPackage* const packagep) { m_dollarUnitPkgp = packagep; }
AstCFunc* evalFuncp(VEval eval) const { return m_evalFuncps[eval]; }
void evalFuncp(VEval eval, AstCFunc* funcp) { m_evalFuncps[eval] = funcp; }
AstCFunc* dumpTriggersFuncp(VEval eval) const { return m_dumpTriggersFuncps[eval]; }
@@ -1758,7 +1899,9 @@ public:
AstNodeModule* modp() const { return m_modp; }
//
AstScope* aboveScopep() const VL_MT_SAFE { return m_aboveScopep; }
void aboveScopep(AstScope* nodep) { m_aboveScopep = nodep; }
AstCell* aboveCellp() const { return m_aboveCellp; }
void aboveCellp(AstCell* nodep) { m_aboveCellp = nodep; }
bool isTop() const VL_MT_SAFE { return aboveScopep() == nullptr; } // At top of hierarchy
// Create new MODULETEMP variable under this scope
AstVarScope* createTemp(const string& name, unsigned width);
@@ -1915,8 +2058,11 @@ public:
class AstTextBlock final : public AstNode {
// Text block emitted into output, with some arbitrary nodes interspersed
// @astgen op1 := nodesp : List[AstNode] // Nodes to print
// dist-ast-dump-suppress // Omitting text blocks due to verbosity
const std::string m_prefix; // Prefix to print before first element in 'nodesp'
// dist-ast-dump-suppress // Omitting text blocks due to verbosity
const std::string m_separator; // Separator to print between each element in 'nodesp'
// dist-ast-dump-suppress // Omitting text blocks due to verbosity
const std::string m_suffix; // Suffix to pring after last element in 'nodesp'
public:
explicit AstTextBlock(FileLine* fl, //
@@ -1965,6 +2111,7 @@ class AstTypeTable final : public AstNode {
AstBasicDType* m_basicps[VBasicDTypeKwd::_ENUM_MAX]{};
//
using DetailedMap = std::map<VBasicTypeKey, AstBasicDType*>;
// dist-ast-dump-suppress // Link to other nodes
DetailedMap m_detailedMap;
public:
@@ -1993,10 +2140,10 @@ class AstTypedef final : public AstNode {
// @astgen op1 := childDTypep : Optional[AstNodeDType]
// @astgen op4 := attrsp : List[AstNode] // Attributes during early parse
string m_name;
string m_name; // Name of the typedef
string m_tag; // Holds the string of the verilator tag -- used in JSON output.
uint32_t m_declTokenNum; // Declaration token number
bool m_attrPublic = false;
bool m_attrPublic = false; // Marked with public; keep even if unused
bool m_isHideLocal : 1; // Verilog local
bool m_isHideProtected : 1; // Verilog protected
bool m_isUnderClass : 1; // Underneath class
@@ -2162,7 +2309,6 @@ class AstVar final : public AstNode {
bool m_attrSFormat : 1; // User sformat attribute
bool m_attrSplitVar : 1; // declared with split_var metacomment
bool m_attrFsmState : 1; // declared with fsm_state metacomment
bool m_attrFsmRegisterWrapper : 1; // connected to an fsm_register_wrapper instance
bool m_attrFsmResetArc : 1; // declared with fsm_reset_arc metacomment
bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment
bool m_constPoolEntry : 1; // Constant pool variable
@@ -2178,6 +2324,8 @@ class AstVar final : public AstNode {
bool m_isPulldown : 1; // Tri0
bool m_isPullup : 1; // Tri1
bool m_isIfaceParent : 1; // dtype is reference to interface present in this module
bool m_isIfaceArraySplit : 1; // Interface array that was split into element variables by
// V3Param, until V3Width removes it
bool m_isInternal : 1; // Internal state, don't add to method pinter
bool m_isIfaceParam : 1; // Parameter belongs to an interface/modport
bool m_isDpiOpenArray : 1; // DPI import open array
@@ -2227,7 +2375,6 @@ class AstVar final : public AstNode {
m_attrSFormat = false;
m_attrSplitVar = false;
m_attrFsmState = false;
m_attrFsmRegisterWrapper = false;
m_attrFsmResetArc = false;
m_attrFsmArcInclCond = false;
m_constPoolEntry = false;
@@ -2243,6 +2390,7 @@ class AstVar final : public AstNode {
m_isPulldown = false;
m_isPullup = false;
m_isIfaceParent = false;
m_isIfaceArraySplit = false;
m_isInternal = false;
m_isIfaceParam = false;
m_isDpiOpenArray = false;
@@ -2358,7 +2506,7 @@ public:
string vlArgType(bool named, bool forReturn, bool forFunc, const string& namespc = "",
bool asRef = false, bool constRef = false) const;
string vlEnumType() const; // Return VerilatorVarType: VLVT_UINT32, etc
string vlEnumDir() const; // Return VerilatorVarDir: VLVD_INOUT, etc
string vlEnumDir(bool forMember = false) const; // Return VerilatorVarDir: VLVD_INOUT, etc
string vlPropDecl(const string& propName) const; // Return VerilatorVarProps declaration
void combineType(VVarType type);
AstNodeDType* getChildDTypep() const override { return childDTypep(); }
@@ -2378,7 +2526,6 @@ public:
void attrSFormat(bool flag) { m_attrSFormat = flag; }
void attrSplitVar(bool flag) { m_attrSplitVar = flag; }
void attrFsmState(bool flag) { m_attrFsmState = flag; }
void attrFsmRegisterWrapper(bool flag) { m_attrFsmRegisterWrapper = flag; }
void attrFsmResetArc(bool flag) { m_attrFsmResetArc = flag; }
void attrFsmArcInclCond(bool flag) { m_attrFsmArcInclCond = flag; }
bool constPoolEntry() const { return m_constPoolEntry; }
@@ -2405,6 +2552,7 @@ public:
void isContinuously(bool flag) { m_isContinuously = flag; }
void isStatic(bool flag) { m_isStatic = flag; }
void isIfaceParent(bool flag) { m_isIfaceParent = flag; }
void isIfaceArraySplit(bool flag) { m_isIfaceArraySplit = flag; }
void isInternal(bool flag) { m_isInternal = flag; }
void isIfaceParam(bool flag) { m_isIfaceParam = flag; }
void funcLocal(bool flag) {
@@ -2492,6 +2640,7 @@ public:
m_varType = VVarType::IFACEREF;
}
bool isIfaceParent() const { return m_isIfaceParent; }
bool isIfaceArraySplit() const { return m_isIfaceArraySplit; }
bool isInternal() const { return m_isInternal; }
bool isIfaceParam() const { return m_isIfaceParam; }
bool isSignal() const { return varType().isSignal(); }
@@ -2547,7 +2696,6 @@ public:
bool attrSFormat() const { return m_attrSFormat; }
bool attrSplitVar() const { return m_attrSplitVar; }
bool attrFsmState() const { return m_attrFsmState; }
bool attrFsmRegisterWrapper() const { return m_attrFsmRegisterWrapper; }
bool attrFsmResetArc() const { return m_attrFsmResetArc; }
bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; }
AstIface* sensIfacep() const { return m_sensIfacep; }
@@ -2571,13 +2719,6 @@ public:
lifetime(fromp->lifetime());
}
void combineType(const AstVar* otherp);
void inlineAttrReset(const string& name) {
if (direction() == VDirection::INOUT && varType() == VVarType::WIRE) {
m_varType = VVarType::TRIWIRE;
}
m_direction = VDirection::NONE;
m_name = name;
}
bool needsCReset() const {
return !isIfaceParent() && !isIfaceRef() && !noReset() && !isParam() && !isStatementTemp()
&& !noCReset() && !(basicp() && basicp()->isEvent());
@@ -2620,6 +2761,7 @@ public:
bool sameNode(const AstNode* samep) const override;
bool hasDType() const override VL_MT_SAFE { return true; }
AstVar* varp() const VL_MT_STABLE { return m_varp; } // [After Link] Pointer to variable
void varp(AstVar* nodep) { m_varp = nodep; }
AstScope* scopep() const VL_MT_STABLE { return m_scopep; } // Pointer to scope it's under
void scopep(AstScope* nodep) { m_scopep = nodep; }
bool isTrace() const { return m_trace; }
@@ -2633,10 +2775,10 @@ class AstCoverOtherDecl final : public AstNodeCoverDecl {
// Coverage analysis point declaration
// Used for other than toggle types of coverage
string m_linescov;
string m_fsmVar;
string m_fsmFrom;
string m_fsmTo;
string m_fsmTag;
string m_fsmVar; // FSM state variable name
string m_fsmFrom; // FSM source state label
string m_fsmTo; // FSM destination state label
string m_fsmTag; // FSM arc kind tag (e.g. reset, reset_include, default)
int m_offset; // Offset column numbers to uniq-ify IFs
public:
AstCoverOtherDecl(FileLine* fl, const string& page, const string& comment,
@@ -2662,7 +2804,7 @@ public:
const string& fsmTag() const { return m_fsmTag; }
bool sameNode(const AstNode* samep) const override {
const AstCoverOtherDecl* const asamep = VN_DBG_AS(samep, CoverOtherDecl);
return AstNodeCoverDecl::sameNode(samep) && linescov() == asamep->linescov();
return Super::sameNode(samep) && linescov() == asamep->linescov();
}
};
class AstCoverToggleDecl final : public AstNodeCoverDecl {
@@ -2684,7 +2826,7 @@ public:
const VNumRange& range() const { return m_range; }
bool sameNode(const AstNode* samep) const override {
const AstCoverToggleDecl* const asamep = VN_DBG_AS(samep, CoverToggleDecl);
return AstNodeCoverDecl::sameNode(samep) && range() == asamep->range();
return Super::sameNode(samep) && range() == asamep->range();
}
};
@@ -2802,8 +2944,8 @@ public:
class AstCoverCross final : public AstNodeFuncCovItem {
// @astgen op1 := itemsp : List[AstCoverpointRef]
// @astgen op2 := optionsp : List[AstCoverOption] // post-LinkParse only
// @astgen op3 := rawBodyp : List[AstNode] // Parse: raw cross_body items;
// // post-LinkParse: empty
// @astgen op3 := binsp : List[AstNode] // Parse: mixed cross bins/options;
// // post-LinkParse: AstCoverCrossBin only
// @astgen op4 := iffp : Optional[AstNodeExpr] // Conditional sampling guard
public:
AstCoverCross(FileLine* fl, const string& name, AstCoverpointRef* itemsp,
@@ -2915,6 +3057,8 @@ class AstClass final : public AstNodeModule {
// @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_hasRandVarsUpdate = false; // Has updateRandVars method,
// which updates pointers to rand variables in clone()
bool m_covergroup = false; // Is covergroup (TODO perhaps make a new Ast node type for CG?)
bool m_extended = false; // Is extension or extended by other classes
bool m_interfaceClass = false; // Interface class
@@ -2943,6 +3087,8 @@ public:
void covergroupEnclosingClassp(AstClass* classp) { m_covergroupEnclosingClassp = classp; }
AstNode* membersp() const VL_MT_STABLE { return stmtsp(); }
void addMembersp(AstNode* nodep) { addStmtsp(nodep); }
bool hasRandVarsUpdate() const { return m_hasRandVarsUpdate; }
void hasRandVarsUpdate(bool flag) { m_hasRandVarsUpdate = flag; }
bool isCovergroup() const { return m_covergroup; }
void isCovergroup(bool flag) { m_covergroup = flag; }
bool isExtended() const { return m_extended; }
@@ -3031,9 +3177,11 @@ public:
AstIface(FileLine* fl, const string& name, const string& libname)
: ASTGEN_SUPER_Iface(fl, name, libname) {}
ASTGEN_MEMBERS_AstIface;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string verilogKwd() const override { return "interface"; }
// Interfaces have `timescale applicability but lots of code seems to
// get false warnings if we enable this
string verilogKwd() const override { return "interface"; }
bool timescaleMatters() const override { return false; }
bool hasVirtualRef() const { return m_hasVirtualRef; }
void setHasVirtualRef() { m_hasVirtualRef = true; }
@@ -3087,9 +3235,7 @@ public:
: ASTGEN_SUPER_Package(fl, name, libname) {}
ASTGEN_MEMBERS_AstPackage;
string verilogKwd() const override { return "package"; }
bool timescaleMatters() const override { return !isDollarUnit(); }
static string dollarUnitName() { return AstNode::encodeName("$unit"); }
bool isDollarUnit() const { return name() == dollarUnitName(); }
bool timescaleMatters() const override { return !isDollarUnit() && !isConstPool(); }
};
class AstPrimitive final : public AstNodeModule {
// A primitive declaration
+13 -41
View File
@@ -413,16 +413,16 @@ class AstCase final : public AstNodeStmt {
// @astgen op1 := exprp : AstNodeExpr // Condition (scurtinee) expression
// @astgen op2 := itemsp : List[AstCaseItem]
// @astgen op3 := notParallelp : List[AstNode] // assertion code for non-full case's
VCaseType m_casex; // 0=case, 1=casex, 2=casez
VCaseType m_caseType; // 0=case, 1=casex, 2=casez
bool m_fullPragma = false; // Synthesis full_case
bool m_parallelPragma = false; // Synthesis parallel_case
bool m_uniquePragma = false; // unique case
bool m_unique0Pragma = false; // unique0 case
bool m_priorityPragma = false; // priority case
public:
AstCase(FileLine* fl, VCaseType casex, AstNodeExpr* exprp, AstCaseItem* itemsp)
AstCase(FileLine* fl, VCaseType caseType, AstNodeExpr* exprp, AstCaseItem* itemsp)
: ASTGEN_SUPER_Case(fl)
, m_casex{casex} {
, m_caseType{caseType} {
this->exprp(exprp);
addItemsp(itemsp);
}
@@ -432,15 +432,15 @@ public:
int instrCount() const override { return INSTR_COUNT_BRANCH; }
string verilogKwd() const override { return casez() ? "casez" : casex() ? "casex" : "case"; }
bool sameNode(const AstNode* samep) const override {
return m_casex == VN_DBG_AS(samep, Case)->m_casex;
return m_caseType == VN_DBG_AS(samep, Case)->m_caseType;
}
bool casex() const { return m_casex == VCaseType::CT_CASEX; }
bool casez() const { return m_casex == VCaseType::CT_CASEZ; }
bool caseInside() const { return m_casex == VCaseType::CT_CASEINSIDE; }
bool caseMatches() const { return m_casex == VCaseType::CT_CASEMATCHES; }
bool caseSimple() const { return m_casex == VCaseType::CT_CASE; }
void caseInsideSet() { m_casex = VCaseType::CT_CASEINSIDE; }
void caseMatchesSet() { m_casex = VCaseType::CT_CASEMATCHES; }
bool casex() const { return m_caseType == VCaseType::CT_CASEX; }
bool casez() const { return m_caseType == VCaseType::CT_CASEZ; }
bool caseInside() const { return m_caseType == VCaseType::CT_CASEINSIDE; }
bool caseMatches() const { return m_caseType == VCaseType::CT_CASEMATCHES; }
bool caseSimple() const { return m_caseType == VCaseType::CT_CASE; }
void caseInsideSet() { m_caseType = VCaseType::CT_CASEINSIDE; }
void caseMatchesSet() { m_caseType = VCaseType::CT_CASEMATCHES; }
bool fullPragma() const { return m_fullPragma; }
void fullPragma(bool flag) { m_fullPragma = flag; }
bool parallelPragma() const { return m_parallelPragma; }
@@ -549,29 +549,7 @@ public:
bool isOutputter() override { return true; }
bool isPure() override { return false; }
AstNodeCoverDecl* declp() const { return m_declp; } // Where defined
};
class AstCoverToggle final : public AstNodeStmt {
// Toggle analysis of given signal
// Parents: MODULE
// @astgen op1 := incp : AstCoverInc
// @astgen op2 := origp : AstNodeExpr
// @astgen op3 := changep : AstNodeExpr
public:
AstCoverToggle(FileLine* fl, AstCoverInc* incp, AstNodeExpr* origp, AstNodeExpr* changep)
: ASTGEN_SUPER_CoverToggle(fl) {
this->incp(incp);
this->origp(origp);
this->changep(changep);
}
ASTGEN_MEMBERS_AstCoverToggle;
int instrCount() const override { return 3 + INSTR_COUNT_BRANCH + INSTR_COUNT_LD; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return true; }
bool isOutputter() override {
return false; // Though the AstCoverInc under this is an outputter
}
// but isPure() true
void declp(AstNodeCoverDecl* nodep) { m_declp = nodep; }
};
class AstDeassign final : public AstNodeStmt {
// Procedural 'deassign' statement
@@ -1221,13 +1199,6 @@ public:
ASTGEN_MEMBERS_AstSetuphold;
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstSplitPlaceholder final : public AstNodeStmt {
public:
// Dummy node used within V3Split; never exists outside of V3Split.
explicit AstSplitPlaceholder(FileLine* fl)
: ASTGEN_SUPER_SplitPlaceholder(fl) {}
ASTGEN_MEMBERS_AstSplitPlaceholder;
};
class AstStackTraceT final : public AstNodeStmt {
// $stacktrace used as task
public:
@@ -1340,6 +1311,7 @@ class AstTraceDecl final : public AstNodeStmt {
const VVarType m_varType; // Type of variable (for localparam vs. param)
const VDirection m_declDirection; // Declared direction input/output etc
const bool m_inDtypeFunc; // Trace decl inside type init function
// dist-ast-dump-suppress // Not stable and of low value
int m_codeInc{0}; // Code increment for type
public:
AstTraceDecl(FileLine* fl, const string& showname,
+3220 -3280
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -26,6 +26,8 @@
#include "V3Broken.h"
#include "V3ConstPool.h"
#include <unordered_set>
VL_DEFINE_DEBUG_FUNCTIONS;
@@ -227,6 +229,45 @@ private:
}
return false;
}
static void checkArgRefs(AstNodeExpr* nodep, const char* descrp, AstNodeExpr* argsp) {
if (!std::strcmp(descrp, "TODO")) return; // Skip if not yet checked
// Check each argument
const char* dp = descrp;
for (AstNodeExpr* argp = argsp; argp; argp = VN_AS(argp->nextp(), NodeExpr)) {
if (argp->fileline()->erroringOn()) return; // Intentionally skip all checks
const AstNodeExpr* const lvalp = argp->getVAccessTargetRecurse();
const VAccess access
= lvalp ? lvalp->getVAccessRecurse() // Since we call it on a result of above call
// there should be no recursion at all
: VAccess{VAccess::READ};
if (dp[0] == '+') --dp; // Repeats the entry before it
switch (dp[0]) {
case 'r':
UASSERT_OBJ(access.isReadOnly(), argp,
"Input argument of library call is not a read-only expression");
break;
case 'w':
UASSERT_OBJ(lvalp, argp, //
"Output argument of library call is not a valid C++ LValue");
UASSERT_OBJ(access.isWriteOnly(), argp,
"Output argument of library call is not write-only");
break;
case 'm':
UASSERT_OBJ(lvalp, argp, //
"Inout argument of library call is not a valid C++ LValue");
UASSERT_OBJ(access.isRW(), argp,
"Inout argument of library call is not read-write");
break;
default:
UASSERT_OBJ(dp[0], argp, "Unexpected trailing arguments to library call");
break; // LCOV_EXCL_LINE
}
++dp;
}
UASSERT_OBJ(!dp[0] || dp[0] == '+', nodep, "Insufficient arguments to library call");
}
// VISITORS
void visit(AstNodeAssign* nodep) override {
processEnter(nodep);
@@ -291,6 +332,7 @@ private:
void visit(AstCMethodHard* nodep) override {
++m_nCalls;
processAndIterate(nodep);
checkArgRefs(nodep, nodep->method().args(), nodep->pinsp());
}
void visit(AstNodeFTaskRef* nodep) override {
++m_nCalls;
@@ -392,6 +434,10 @@ void V3Broken::brokenAll(AstNetlist* nodep) {
// Check every node in tree
const BrokenCheckVisitor cvisitor{nodep};
// Check the constant pool lookup cache refers only to nodes in the tree
const char* const whyp = V3ConstPool::broken();
UASSERT_OBJ(!whyp, nodep, "Broken constant pool cache: " << whyp);
s_allocTable.checkForLeaks();
s_linkableTable.clear();
s_brokenCntGlobal.inc();
+3 -3
View File
@@ -152,6 +152,7 @@ class CCtorsVisitor final : public VNVisitor {
// VISITORS
void visit(AstNodeModule* nodep) override {
if (nodep->isConstPool()) return; // Special emit rules
if (const AstClass* const classp = VN_CAST(nodep, Class)) {
// Interface class may only have pure virtuals and params which do not need cctor reset
if (classp->isInterfaceClass()) return;
@@ -167,12 +168,12 @@ class CCtorsVisitor final : public VNVisitor {
if (v3Global.opt.coverage()) {
V3CCtorsBuilder configure_coverage{nodep, "_configure_coverage", VCtorType::COVERAGE};
for (AstNode* np = nodep->stmtsp(); np; np = np->nextp()) {
for (AstNode *np = nodep->stmtsp(), *nextp; np; np = nextp) {
nextp = np->nextp();
if (AstNodeCoverDecl* const coverp = VN_CAST(np, NodeCoverDecl)) {
// ... else we don't have a static VlSym to be able to coverage insert
UASSERT_OBJ(!VN_IS(nodep, Class), coverp,
"NodeCoverDecl should be in class's package, not class itself");
np = coverp->backp();
configure_coverage.add(coverp->unlinkFrBack());
}
}
@@ -219,7 +220,6 @@ class CCtorsVisitor final : public VNVisitor {
}
}
void visit(AstConstPool*) override {}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
+2
View File
@@ -86,6 +86,7 @@ class CUseVisitor final : public VNVisitorConst {
}
void visit(AstCell* nodep) override {
if (nodep->user1SetOnce()) return; // Process once
if (nodep->modp()->isConstPool()) return; // Special emit rules
// Currently no IMP_INCLUDE because we include __Syms which has them all
addNewUse(nodep, VUseType::INT_FWD_CLASS, nodep->modp()->name());
iterateChildrenConst(nodep);
@@ -115,6 +116,7 @@ void V3CUse::cUseAll() {
// Call visitor separately for each module, so visitor state is cleared
for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp;
modp = VN_AS(modp->nextp(), NodeModule)) {
if (modp->isConstPool()) continue; // Special emit rules
// Insert under this module; someday we should e.g. make Ast
// for each output file and put under that
CUseVisitor{modp};
+24 -16
View File
@@ -38,6 +38,7 @@
#include "V3Case.h"
#include "V3ConstPool.h"
#include "V3Stats.h"
VL_DEFINE_DEBUG_FUNCTIONS;
@@ -738,18 +739,15 @@ class CaseVisitor final : public VNVisitor {
}
}
// Create the table in the constant pool, unless using an inline table
AstVarScope* const tableVscp = [&]() -> AstVarScope* {
if (isTinyTable) return nullptr;
AstVarScope* vscp = v3Global.rootp()->constPoolp()->findConst(tablep, true);
// Create the lookup table reference, in the constant pool unless using an inline table
AstNodeExpr* const tableRefp = [&]() -> AstNodeExpr* {
if (isTinyTable) return tablep;
AstVarRef* const refp = V3ConstPool::findConst(tablep);
VL_DO_DANGLING(tablep->deleteTree(), tablep); // findConst clones
return vscp;
return refp;
}();
// Create the lookup table reference and index
AstNodeExpr* const tableRefp
= tableVscp ? static_cast<AstNodeExpr*>(new AstVarRef{flp, tableVscp, VAccess::READ})
: static_cast<AstNodeExpr*>(tablep);
// Create the lookup index
AstNodeExpr* const caseExprp
= new AstExtend{flp, nodep->exprp()->cloneTreePure(false), 32};
AstNodeExpr* const scalep = new AstConst{flp, entryWidth};
@@ -855,15 +853,14 @@ class CaseVisitor final : public VNVisitor {
}
// Create the tables
AstVarScope* const matchVscp = v3Global.rootp()->constPoolp()->findConst(matchp, true);
AstVarScope* const tableVscp = v3Global.rootp()->constPoolp()->findTable(tablep);
AstVarRef* const matchRefp = V3ConstPool::findConst(matchp);
AstVarRef* const tableRefp = V3ConstPool::findTable(tablep);
VL_DO_DANGLING(matchp->deleteTree(), matchp);
VL_DO_DANGLING(tablep->deleteTree(), tablep);
// AstMatchMasked produces the index of the matching entry
AstNodeExpr* const tableRefp = new AstVarRef{flp, tableVscp, VAccess::READ};
AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false);
AstMatchMasked* const indexp = new AstMatchMasked{flp, caseExprp, matchVscp};
AstMatchMasked* const indexp = new AstMatchMasked{flp, caseExprp, matchRefp};
AstNodeExpr* const entryp = new AstArraySel{flp, tableRefp, indexp};
return connectDecoderOutputs(nodep, entryp, "__VcaseDecoderOut");
@@ -1004,11 +1001,22 @@ class CaseVisitor final : public VNVisitor {
itemp->addCondsp(newCondp);
}
// If there was no default, add a empty one, this greatly simplifies below code
// If there was no default, make one up, this greatly simplifies below code
// and constant propagation will just eliminate it for us later.
if (!hasDefault) {
nodep->addItemsp(new AstCaseItem{
nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr});
if (m_caseDetailsValid && m_caseDetails.exhaustive
&& !m_caseDetails.exhaustiveOverEnumOnly) {
// Case is proven exhaustive, so the last is reached only when its test holds,
// make it unconditional
AstCaseItem* const lastp = VN_AS(nodep->itemsp()->lastp(), CaseItem);
pushDeletep(lastp->condsp()->unlinkFrBackWithNext());
lastp->addCondsp(new AstConst{lastp->fileline(), AstConst::BitTrue{}});
} else {
// Otherwise needs an explicit empty default
nodep->addItemsp(new AstCaseItem{
nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}},
nullptr});
}
}
// Now build the IF statement tree
+21 -12
View File
@@ -18,7 +18,8 @@
// Each module:
// For each expression, if it requires a clean operand,
// and the operand is dirty, insert a CLEAN node.
// Resize operands (but not variables or variable selects) to C++ 32/64/wide types.
// Resize operands (but not variables, variable selects, or formatting metadata)
// to C++ 32/64/wide types.
// Copy all width() values to widthMin() so RANGE, etc can still see orig widths
//
//*************************************************************************
@@ -84,6 +85,7 @@ class CleanVisitor final : public VNVisitor {
|| VN_IS(nodep, ConsPackMember) //
|| VN_IS(nodep, NodeDType) // Don't want to change variable widths!
|| VN_IS(nodep, NodeSel) // Array selects should reflect variable widths
|| VN_IS(nodep, SFormatArg) // Retain the logical argument type, not storage width
|| VN_IS(nodep->dtypep()->skipRefp(), AssocArrayDType) // Or arrays
|| VN_IS(nodep->dtypep()->skipRefp(), WildcardArrayDType)
|| VN_IS(nodep->dtypep()->skipRefp(), DynArrayDType)
@@ -91,6 +93,8 @@ class CleanVisitor final : public VNVisitor {
|| VN_IS(nodep->dtypep()->skipRefp(), QueueDType)
|| VN_IS(nodep->dtypep()->skipRefp(), StreamDType)
|| VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)
|| VN_IS(nodep->dtypep()->skipRefp(), CoverCrossDType)
|| VN_IS(nodep->dtypep()->skipRefp(), CoverpointDType)
|| VN_IS(nodep->dtypep()->skipRefp(), VoidDType)) {
} else {
const AstNodeUOrStructDType* const dtypep
@@ -138,12 +142,12 @@ class CleanVisitor final : public VNVisitor {
computeCppWidth(nodep);
if (!isClean(nodep)) insertClean(nodep);
}
void ensureCleanAndNext(AstNodeExpr* nodep) {
void ensureCleanAndNext(AstNode* nodep) {
// Editing list, careful looping!
for (AstNodeExpr* exprp = nodep; exprp;) {
AstNodeExpr* const nextp = VN_AS(exprp->nextp(), NodeExpr);
ensureClean(exprp);
exprp = nextp;
for (AstNode* argp = nodep; argp;) {
AstNode* const nextp = argp->nextp();
if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp);
argp = nextp;
}
}
@@ -247,9 +251,7 @@ class CleanVisitor final : public VNVisitor {
setClean(nodep, false);
// We always clean, as we don't trust those pesky users.
if (!VN_IS(nodep->backp(), And)) insertClean(nodep);
for (AstNode* argp = nodep->nodesp(); argp; argp = argp->nextp()) {
if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp);
}
ensureCleanAndNext(nodep->nodesp());
}
void visit(AstTraceDecl* nodep) override {} // Nothing to do here
void visit(AstTraceInc* nodep) override {
@@ -280,16 +282,23 @@ class CleanVisitor final : public VNVisitor {
iterateChildren(nodep);
ensureClean(nodep->condp());
}
void visit(AstSFormatArg* nodep) override {
iterateChildren(nodep);
ensureCleanAndNext(nodep->exprp());
setClean(nodep, true);
}
void visit(AstSFormatF* nodep) override {
iterateChildren(nodep);
ensureCleanAndNext(nodep->exprsp());
setClean(nodep, true); // generates a string, so not relevant
}
void visit(AstCStmt* nodep) override {
iterateChildren(nodep);
ensureCleanAndNext(nodep->nodesp());
}
void visit(AstCStmtUser* nodep) override {
iterateChildren(nodep);
for (AstNode* argp = nodep->nodesp(); argp; argp = argp->nextp()) {
if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp);
}
ensureCleanAndNext(nodep->nodesp());
}
void visit(AstNodeCCall* nodep) override {
iterateChildren(nodep);
+1 -1
View File
@@ -97,7 +97,7 @@ class ClockVisitor final : public VNVisitor {
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstSenTree* nodep) override {
pushDeletep(nodep->unlinkFrBack()); // No longer needed
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); // No longer needed
}
//========== Move sampled assignments
+2 -2
View File
@@ -44,8 +44,8 @@ class CombineVisitor final : VNVisitor {
// TYPES
using funcit_t = std::list<AstCFunc*>::iterator;
struct CFuncs final {
std::list<AstCFunc*> m_fast;
std::list<AstCFunc*> m_slow;
std::list<AstCFunc*> m_fast; // Functions that are not slow
std::list<AstCFunc*> m_slow; // Functions marked for slow files
};
// STATE
-1
View File
@@ -318,7 +318,6 @@ class ToStringVisitor final : public VNVisitorConst {
}
}
}
void visit(AstConstPool*) override {} // Accelerate
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
public:
+26 -18
View File
@@ -110,12 +110,12 @@ class ConstBitOpTreeVisitor final : public VNVisitorConst {
class LeafInfo final { // Leaf node (either AstConst or AstVarRef)
// MEMBERS
bool m_polarity = true;
bool m_polarity = true; // Invert result due to NOT
int m_lsb = 0; // LSB of actually used bit of m_refp->varp()
int m_msb = 0; // MSB of actually used bit of m_refp->varp()
int m_wordIdx = -1; // -1 means AstWordSel is not used.
AstVarRef* m_refp = nullptr;
const AstConst* m_constp = nullptr;
AstVarRef* m_refp = nullptr; // Leaf's variable reference
const AstConst* m_constp = nullptr; // Leaf's constant
public:
// CONSTRUCTORS
@@ -187,9 +187,9 @@ class ConstBitOpTreeVisitor final : public VNVisitorConst {
};
struct BitPolarityEntry final { // Found bit polarity during iterate()
LeafInfo m_info;
bool m_polarity = false;
int m_bit = 0;
LeafInfo m_info; // Leaf (variable or constant) bit polarity was found on
bool m_polarity = false; // Polarity the bit must have to match
int m_bit = 0; // Bit index within the leaf that was tested
BitPolarityEntry(const LeafInfo& info, bool pol, int bit)
: m_info{info}
, m_polarity{pol}
@@ -198,8 +198,8 @@ class ConstBitOpTreeVisitor final : public VNVisitorConst {
};
struct FrozenNodeInfo final { // Context when a frozen node is found
bool m_polarity;
int m_lsb;
bool m_polarity; // Polarity the frozen node must match
int m_lsb; // LSB position of the frozen node
bool operator<(const FrozenNodeInfo& other) const {
if (m_lsb != other.m_lsb) return m_lsb < other.m_lsb;
return m_polarity < other.m_polarity;
@@ -207,12 +207,12 @@ class ConstBitOpTreeVisitor final : public VNVisitorConst {
};
class Restorer final { // Restore the original state unless disableRestore() is called
ConstBitOpTreeVisitor& m_visitor;
const size_t m_polaritiesSize;
const size_t m_frozenSize;
const unsigned m_ops;
const bool m_polarity;
bool m_restore = true;
ConstBitOpTreeVisitor& m_visitor; // Visitor whose state is saved and restored
const size_t m_polaritiesSize; // Saved m_visitor.m_bitPolarities size to truncate back to
const size_t m_frozenSize; // Saved m_visitor.m_frozenNodes size to truncate back to
const unsigned m_ops; // Saved m_visitor.m_ops to restore
const bool m_polarity; // Saved m_visitor.m_polarity to restore
bool m_restore = true; // Whether the destructor still needs to restore state
public:
explicit Restorer(ConstBitOpTreeVisitor& visitor)
@@ -1463,11 +1463,12 @@ class ConstVisitor final : public VNVisitor {
}
if (const AstShiftR* const shiftp = VN_CAST(rhsp, ShiftR)) {
// 'a >> S' forces the high S bits to zero. Check against the width of the shifted
// operand, V3Expand can create shifts wider than their inputs
// operand, V3Expand can create shifts wider than their inputs. Must use width(),
// not widthMin(), as bits above widthMin() are not guaranteed to be zero.
if (AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) {
return scp->num().fitsInUInt()
&& (lsb + scp->num().toUInt()
>= static_cast<uint32_t>(shiftp->lhsp()->widthMin()));
>= static_cast<uint32_t>(shiftp->lhsp()->width()));
}
}
if (const AstMul* const mulp = VN_CAST(rhsp, Mul)) {
@@ -3969,7 +3970,12 @@ class ConstVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return true;
}
void visit(AstSFormatArg* nodep) override { iterateChildren(nodep); }
void visit(AstSFormatArg* nodep) override {
// Skip namep(): requiring its runtime lookup to be constant can reject valid
// enum-valued constant-function calls in parameters. displayedEnum() resolves
// the name from the folded exprp() value and enum dtype instead.
iterateAndNextNull(nodep->exprp());
}
void visit(AstSFormatF* nodep) override {
// Substitute constants into displays. The main point of this is to
// simplify assertion methodologies which call functions with display's.
@@ -4033,7 +4039,9 @@ class ConstVisitor final : public VNVisitor {
: VFormatAttr{};
if (VN_IS(subargp, Const)) { // Convert it
const string out
= constNumV(subargp).displayed(nodep, fmt, formatAttr);
= formatAttr.isEnum()
? constNumV(subargp).displayedEnum(fargp, fmt)
: constNumV(subargp).displayed(nodep, fmt, formatAttr);
UINFO(9, " DispConst: " << fmt << " -> " << out << " for "
<< subargp);
// fmt = out w/ replace % with %% as it must later when
+220
View File
@@ -0,0 +1,220 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Constant pool
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "V3PchAstMT.h"
#include "V3ConstPool.h"
//######################################################################
// V3ConstPool
bool V3ConstPool::equalMap(const AstInitArray* ap, const AstInitArray* bp) {
// Associative array initializers must have equivalent types, defaults, and entries.
// As in 'equalTable', compare by value, rather than by tree structure.
const AstAssocArrayDType* const aDTypep = VN_AS(ap->dtypep(), AssocArrayDType);
const AstAssocArrayDType* const bDTypep = VN_AS(bp->dtypep(), AssocArrayDType);
if (!aDTypep->subDTypep()->sameTree(bDTypep->subDTypep())) return false;
if (!aDTypep->keyDTypep()->sameTree(bDTypep->keyDTypep())) return false;
// The default is optional, but must be the same if present
const AstNode* const aDefaultp = ap->defaultp();
const AstNode* const bDefaultp = bp->defaultp();
UASSERT_OBJ(!aDefaultp || VN_IS(aDefaultp, Const), ap, "Const pool map default not Const");
UASSERT_OBJ(!bDefaultp || VN_IS(bDefaultp, Const), bp, "Const pool map default not Const");
if (!aDefaultp != !bDefaultp) return false;
if (aDefaultp && !aDefaultp->sameTree(bDefaultp)) return false;
// Compare the entries, which are ordered by key. Note an entry explicitly set to the
// default value makes maps compare different, which is safe, but misses merging them.
const AstInitArray::KeyItemMap& aMap = ap->map();
const AstInitArray::KeyItemMap& bMap = bp->map();
if (aMap.size() != bMap.size()) return false;
auto bIt = bMap.cbegin();
for (const auto& aItem : aMap) {
const AstNode* const aValuep = aItem.second->valuep();
const AstNode* const bValuep = bIt->second->valuep();
UASSERT_OBJ(VN_IS(aValuep, Const), aValuep, "Const pool map item not Const");
UASSERT_OBJ(VN_IS(bValuep, Const), bValuep, "Const pool map item not Const");
if (aItem.first != bIt->first) return false;
if (!aValuep->sameTree(bValuep)) return false;
++bIt;
}
return true;
}
bool V3ConstPool::equalTable(const AstInitArray* ap, const AstInitArray* bp) {
// Unpacked array initializers must have equivalent values
// Note, sadly we can't just call ap->sameTree(pb), because both:
// - the dtypes might be different instances
// - the default/inititem children might be in different order yet still yield the same table
// See note in AstInitArray::same about the same. This function instead compares by initializer
// value, rather than by tree structure.
const AstUnpackArrayDType* const aDTypep = VN_AS(ap->dtypep(), UnpackArrayDType);
const AstUnpackArrayDType* const bDTypep = VN_AS(bp->dtypep(), UnpackArrayDType);
if (!aDTypep->subDTypep()->sameTree(bDTypep->subDTypep())) return false;
// Compare the range by value, as the bounds might be differently typed constants
if (!(aDTypep->declRange() == bDTypep->declRange())) return false;
// Compare initializer arrays by value. Note this is only called when they hash the same.
const uint64_t size = aDTypep->elementsConst();
for (uint64_t n = 0; n < size; ++n) {
const AstNode* const valAp = ap->getIndexDefaultedValuep(n);
const AstNode* const valBp = bp->getIndexDefaultedValuep(n);
UASSERT_OBJ(VN_IS(valAp, Const), valAp, "Const pool table item not Const");
UASSERT_OBJ(VN_IS(valBp, Const), valBp, "Const pool table item not Const");
if (!valAp->sameTree(valBp)) return false;
}
return true;
}
size_t V3ConstPool::hashTable(const AstInitArray* initp) {
// Hash what 'equalTable' compares: the element type, the range by value, and the values
const AstUnpackArrayDType* const dtypep = VN_AS(initp->dtypep(), UnpackArrayDType);
V3Hash hash = V3Hasher::uncachedHash(dtypep->subDTypep());
hash += dtypep->left();
hash += dtypep->right();
const uint64_t size = dtypep->elementsConst();
for (uint64_t n = 0; n < size; ++n) {
hash += V3Hasher::uncachedHash(initp->getIndexDefaultedValuep(n));
}
return hash.value();
}
template <typename T_Set, typename T_Init>
AstVarRef* V3ConstPool::findOrCreate(T_Set& set, uint32_t& nextr, const char* prefixp,
T_Init* initp) {
// Rebuild the cache if invalidated
if (VL_UNLIKELY(!m_cacheValid)) {
m_cacheValid = true;
m_scopep = nullptr;
m_consts.clear();
m_maps.clear();
m_tables.clear();
m_varScopes.clear();
AstPackage* const pkgp = v3Global.rootp()->constPoolPkgp();
for (AstNode* nodep = pkgp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (AstScope* const scopep = VN_CAST(nodep, Scope)) {
if (m_stage == Stage::SCOPED) m_scopep = scopep;
continue;
}
// Otherwise must be a variable
AstVar* const varp = VN_AS(nodep, Var);
UASSERT_OBJ(varp->constPoolEntry(), varp, "Unmarked variable in constant pool");
if (VN_IS(varp->valuep(), Const)) {
m_consts.insert(varp);
} else if (const AstInitArray* const arrayp = VN_CAST(varp->valuep(), InitArray)) {
if (VN_IS(arrayp->dtypep(), AssocArrayDType)) {
m_maps.insert(varp);
} else {
m_tables.insert(varp);
}
}
}
if (m_scopep) {
for (AstVarScope* vscp = m_scopep->varsp(); vscp;
vscp = VN_AS(vscp->nextp(), VarScope)) {
m_varScopes.insert({vscp->varp(), vscp});
}
}
}
FileLine* const flp = initp->fileline();
AstNodeDType* const dtypep = initp->dtypep();
// Look up/create the variable with this value
const auto pair = set.insertLazy(initp, [&]() {
const std::string name = prefixp + std::to_string(nextr++);
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
varp->setConstPoolEntry();
varp->lifetime(VLifetime::STATIC_EXPLICIT);
varp->isConst(true);
varp->isStatic(true);
varp->valuep(initp->cloneTree(false));
v3Global.rootp()->constPoolPkgp()->addStmtsp(varp);
return varp;
});
AstVar* const varp = *pair.first;
// Create a read reference to the entry, of the right form for the current stage
AstVarRef* refp = nullptr;
switch (m_stage) {
case Stage::UNSCOPED: {
// Before V3Scope, reference via the package, as any other package variable
AstPackage* const pkgp = v3Global.rootp()->constPoolPkgp();
refp = new AstVarRef{flp, pkgp, varp, VAccess::READ};
break;
}
case Stage::SCOPED: {
// While scoped, reference the VarScope, creating it for a new entry, or if removed
const auto vscpPair = m_varScopes.insertLazy(varp, [&]() {
AstVarScope* const vscp = new AstVarScope{varp->fileline(), m_scopep, varp};
m_scopep->addVarsp(vscp);
return std::pair<const AstVar*, AstVarScope*>{varp, vscp};
});
refp = new AstVarRef{flp, vscpPair.first->second, VAccess::READ};
break;
}
case Stage::DESCOPED: {
// After V3Descope, no VarScope is needed
refp = new AstVarRef{flp, varp, VAccess::READ};
break;
}
}
// The entry might have been created with a different, but compatible dtype
refp->dtypep(dtypep);
return refp;
}
AstVarRef* V3ConstPool::findConst(AstConst* initp) {
V3ConstPool& self = instance();
return self.findOrCreate(self.m_consts, self.m_nextConst, "CONST_", initp);
}
AstVarRef* V3ConstPool::findMap(AstInitArray* initp) {
UASSERT_OBJ(VN_IS(initp->dtypep(), AssocArrayDType), initp,
"Const pool map must have associative array dtype");
V3ConstPool& self = instance();
return self.findOrCreate(self.m_maps, self.m_nextMap, "MAP_", initp);
}
AstVarRef* V3ConstPool::findTable(AstInitArray* initp) {
UASSERT_OBJ(VN_IS(initp->dtypep(), UnpackArrayDType), initp,
"Const pool table must have unpacked array dtype");
V3ConstPool& self = instance();
return self.findOrCreate(self.m_tables, self.m_nextTable, "TABLE_", initp);
}
AstVarRef* V3ConstPool::find(AstNodeExpr* initp) {
if (AstConst* const constp = VN_CAST(initp, Const)) return findConst(constp);
if (AstInitArray* const arrayp = VN_CAST(initp, InitArray)) {
if (VN_IS(arrayp->dtypep(), AssocArrayDType)) return findMap(arrayp);
if (VN_IS(arrayp->dtypep(), UnpackArrayDType)) return findTable(arrayp);
}
initp->v3fatalSrc("Unhandled constant pool initializer");
return nullptr; // LCOV_EXCL_LINE
}
const char* V3ConstPool::broken() {
const V3ConstPool& self = instance();
if (!self.m_cacheValid) return nullptr;
BROKEN_RTN(self.m_scopep && !self.m_scopep->brokeExists());
for (const AstVar* const varp : self.m_consts) BROKEN_RTN(!varp->brokeExists());
for (const AstVar* const varp : self.m_maps) BROKEN_RTN(!varp->brokeExists());
for (const AstVar* const varp : self.m_tables) BROKEN_RTN(!varp->brokeExists());
for (const auto& pair : self.m_varScopes) {
BROKEN_RTN(!pair.first->brokeExists());
BROKEN_RTN(!pair.second->brokeExists());
}
return nullptr;
}
+163
View File
@@ -0,0 +1,163 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Constant pool
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// V3ConstPool finds or creates entries in the constant pool package
// (AstNetlist::constPoolPkgp()), which holds read-only static data, i.e.:
// constants, maps and tables, shared across the whole design. Entries are
// ordinary package variables, deduplicated by value.
//
//*************************************************************************
#ifndef VERILATOR_V3CONSTPOOL_H_
#define VERILATOR_V3CONSTPOOL_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3HashTable.h"
#include "V3Hasher.h"
//============================================================================
class V3ConstPool final {
friend class V3Global; // Owns the instance, needs constructor access
// TYPES
// Scoping stage of the design, which determines how entries are referenced
enum class Stage : uint8_t {
UNSCOPED, // Before V3Scope: reference via the package
SCOPED, // Between V3Scope and V3Descope: reference via the VarScope
DESCOPED // After V3Descope: plain reference
};
// Hash and equality of constant entries in 'm_consts'
struct ConstHash final {
size_t operator()(const AstVar* varp) const {
return (*this)(VN_AS(varp->valuep(), Const));
}
size_t operator()(const AstConst* initp) const { return initp->num().toHash().value(); }
};
struct ConstEqual final {
bool operator()(const AstVar* ap, const AstVar* bp) const { // LCOV_EXCL_START
return (*this)(ap, VN_AS(bp->valuep(), Const)); // Only on collision while rebuild
} // LCOV_EXCL_STOP
bool operator()(const AstVar* varp, const AstConst* initp) const {
// Compare by value, which also checks the width. The dtype is ignored.
return VN_AS(varp->valuep(), Const)->num().isCaseEq(initp->num());
}
};
// Hash and equality of associative array entries in 'm_maps'
struct MapHash final {
size_t operator()(const AstVar* varp) const {
return (*this)(VN_AS(varp->valuep(), InitArray));
}
size_t operator()(const AstInitArray* initp) const {
return V3Hasher::uncachedHash(initp).value();
}
};
struct MapEqual final {
bool operator()(const AstVar* ap, const AstVar* bp) const { // LCOV_EXCL_START
return (*this)(ap, VN_AS(bp->valuep(), InitArray)); // Only on collision while rebuild
} // LCOV_EXCL_STOP
bool operator()(const AstVar* varp, const AstInitArray* initp) const {
return equalMap(VN_AS(varp->valuep(), InitArray), initp);
}
};
// Hash and equality of unpacked array entries in 'm_tables'
struct TableHash final {
size_t operator()(const AstVar* varp) const {
return (*this)(VN_AS(varp->valuep(), InitArray));
}
size_t operator()(const AstInitArray* initp) const { //
return hashTable(initp);
}
};
struct TableEqual final {
bool operator()(const AstVar* ap, const AstVar* bp) const { // LCOV_EXCL_START
return (*this)(ap, VN_AS(bp->valuep(), InitArray)); // Only on collision while rebuild
} // LCOV_EXCL_STOP
bool operator()(const AstVar* varp, const AstInitArray* initp) const {
return equalTable(VN_AS(varp->valuep(), InitArray), initp);
}
};
// MEMBERS
Stage m_stage = Stage::UNSCOPED; // Scoping stage of the design, see setScoped()/setDescoped()
bool m_cacheValid = false; // Cache below is up to date
AstScope* m_scopep = nullptr; // Scope of the constant pool, between V3Scope and V3Descope
V3HashSet<AstVar*, ConstHash, ConstEqual> m_consts; // Packed constants
V3HashSet<AstVar*, MapHash, MapEqual> m_maps; // Associative array constants (maps)
V3HashSet<AstVar*, TableHash, TableEqual> m_tables; // Unpacked array constants (tables)
V3HashMap<const AstVar*, AstVarScope*> m_varScopes; // VarScope of each entry iff m_scopep
uint32_t m_nextConst = 0; // Sequence number for naming
uint32_t m_nextMap = 0; // Sequence number for naming
uint32_t m_nextTable = 0; // Sequence number for naming
// METHODS
static V3ConstPool& instance() { return *v3Global.constPoolp(); }
static bool equalMap(const AstInitArray* ap, const AstInitArray* bp);
static bool equalTable(const AstInitArray* ap, const AstInitArray* bp);
static size_t hashTable(const AstInitArray* initp);
// Find an entry equal to the given initializer in the given set, or create one named from
// the given prefix and sequence number, and return a read reference to it
template <typename T_Set, typename T_Init>
AstVarRef* findOrCreate(T_Set& set, uint32_t& nextr, const char* prefixp, T_Init* initp);
// CONSTRUCTORS - only V3Global creates and deletes the instance
V3ConstPool() = default;
~V3ConstPool() = default;
VL_UNCOPYABLE(V3ConstPool);
VL_UNMOVABLE(V3ConstPool);
public:
// STATIC METHODS
// Find a constant packed variable with the given value, or create one if one does not
// already exist, and return a read reference to it.
// The reference has the same dtype as initp.
// The referenced variable *might* have a different, but compatible dtype.
static AstVarRef* findConst(AstConst* initp);
// Find a constant associative array (map) with the given value, or create one if one does
// not already exist, and return a read reference to it.
// The reference has the same dtype as initp.
// The referenced variable *might* have a different, but compatible dtype.
static AstVarRef* findMap(AstInitArray* initp);
// Find a constant unpacked array (table) with the given value, or create one if one does
// not already exist, and return a read reference to it.
// The reference has the same dtype as initp.
// The referenced variable *might* have a different, but compatible dtype.
static AstVarRef* findTable(AstInitArray* initp);
// Dispatch to the above based on the kind of initializer.
static AstVarRef* find(AstNodeExpr* initp);
// Invalidate the lookup cache. This must be called after deleting any Vars from the pool.
static void invalidateCache() { instance().m_cacheValid = false; }
// Notify that the design has been scoped, called by V3Scope
static void setScoped() {
V3ConstPool& self = instance();
UASSERT(self.m_stage == Stage::UNSCOPED, "Constant pool scoped twice");
self.m_stage = Stage::SCOPED;
invalidateCache();
}
// Notify that the design has been descoped, called by V3Descope
static void setDescoped() {
V3ConstPool& self = instance();
UASSERT(self.m_stage == Stage::SCOPED, "Constant pool descoped when not scoped");
self.m_stage = Stage::DESCOPED;
invalidateCache();
}
// Check the lookup cache refers only to existing nodes, for V3Broken
static const char* broken();
};
#endif // Guard
+10
View File
@@ -41,6 +41,15 @@ public:
if (itFoundPair.second) m_keys.push_back(key);
return itFoundPair.second;
}
template <typename T>
void insert(T begin, const T end) {
static_assert(std::is_same<typename std::iterator_traits<T>::value_type, T_Key>::value,
"T must be an iterator with value type T_Key");
while (begin != end) {
if (m_keySet.insert(*begin).second) m_keys.push_back(*begin);
++begin;
}
}
void clear() {
m_keys.clear();
m_keySet.clear();
@@ -48,6 +57,7 @@ public:
// ACCESSORS
bool empty() const { return m_keys.empty(); }
size_t size() const { return m_keys.size(); }
bool exists(const T_Key& key) const { return m_keySet.find(key) != m_keySet.end(); }
// ITERATORS
+36 -36
View File
@@ -491,41 +491,41 @@ public:
IgnIndices results;
int nextChange = 0;
tree.find(0, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 0);
UASSERT_SELFTEST(const int, nextChange, 10);
UASSERT_SELFTEST(results.size(), 0);
UASSERT_SELFTEST(nextChange, 10);
tree.find(10, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 2);
UASSERT_SELFTEST(const int, results[0], 0);
UASSERT_SELFTEST(const int, results[1], 3);
UASSERT_SELFTEST(const int, nextChange, 11);
UASSERT_SELFTEST(results.size(), 2);
UASSERT_SELFTEST(results[0], 0);
UASSERT_SELFTEST(results[1], 3);
UASSERT_SELFTEST(nextChange, 11);
tree.find(11, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 1);
UASSERT_SELFTEST(const int, results[0], 3);
UASSERT_SELFTEST(const int, nextChange, 15); // Center, or would be 20
UASSERT_SELFTEST(results.size(), 1);
UASSERT_SELFTEST(results[0], 3);
UASSERT_SELFTEST(nextChange, 15); // Center, or would be 20
tree.find(20, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 3);
UASSERT_SELFTEST(const int, results[0], 1);
UASSERT_SELFTEST(const int, results[1], 3);
UASSERT_SELFTEST(const int, results[2], 4);
UASSERT_SELFTEST(const int, nextChange, 21);
UASSERT_SELFTEST(results.size(), 3);
UASSERT_SELFTEST(results[0], 1);
UASSERT_SELFTEST(results[1], 3);
UASSERT_SELFTEST(results[2], 4);
UASSERT_SELFTEST(nextChange, 21);
tree.find(21, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 2);
UASSERT_SELFTEST(const int, results[0], 3);
UASSERT_SELFTEST(const int, results[1], 4);
UASSERT_SELFTEST(const int, nextChange, 25); // Center, or would be 30
UASSERT_SELFTEST(results.size(), 2);
UASSERT_SELFTEST(results[0], 3);
UASSERT_SELFTEST(results[1], 4);
UASSERT_SELFTEST(nextChange, 25); // Center, or would be 30
tree.find(30, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 2);
UASSERT_SELFTEST(const int, results[0], 3);
UASSERT_SELFTEST(const int, results[1], 4);
UASSERT_SELFTEST(const int, nextChange, 31);
UASSERT_SELFTEST(results.size(), 2);
UASSERT_SELFTEST(results[0], 3);
UASSERT_SELFTEST(results[1], 4);
UASSERT_SELFTEST(nextChange, 31);
tree.find(40, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 2);
UASSERT_SELFTEST(const int, results[0], 2);
UASSERT_SELFTEST(const int, results[1], 4);
UASSERT_SELFTEST(const int, nextChange, 41);
UASSERT_SELFTEST(results.size(), 2);
UASSERT_SELFTEST(results[0], 2);
UASSERT_SELFTEST(results[1], 4);
UASSERT_SELFTEST(nextChange, 41);
tree.find(41, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 0);
UASSERT_SELFTEST(const int, nextChange, std::numeric_limits<int>::max());
UASSERT_SELFTEST(results.size(), 0);
UASSERT_SELFTEST(nextChange, std::numeric_limits<int>::max());
//
points = {{0, 0}};
for (const auto& it : points) {
@@ -536,8 +536,8 @@ public:
tree.build(data);
//
tree.find(50, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 1);
UASSERT_SELFTEST(const int, results[0], 5);
UASSERT_SELFTEST(results.size(), 1);
UASSERT_SELFTEST(results[0], 5);
}
};
@@ -709,8 +709,8 @@ public:
// Tracks what matches are known to hit against V3ControlScopeTraceEntries
class V3ControlScopeTraceEntryMatch final {
public:
const V3ControlScopeTraceEntry* m_entryp;
const string m_scopepart;
const V3ControlScopeTraceEntry* m_entryp; // Entry the match refers to
const string m_scopepart; // Scope prefix being matched against the entry
V3ControlScopeTraceEntryMatch(const V3ControlScopeTraceEntry* entryp, const string& scopepart)
: m_entryp{entryp}
, m_scopepart{scopepart} {}
@@ -780,8 +780,8 @@ public:
// Resolve modules and files in the design
class V3ControlResolverHierWorkerEntry final {
const int m_workers;
FileLine* const m_flp;
const int m_workers; // Number of workers requested for the hierarchical block
FileLine* const m_flp; // Location the worker count was specified at
public:
explicit V3ControlResolverHierWorkerEntry(int workers, FileLine* flp)
@@ -798,9 +798,9 @@ class V3ControlResolver final {
V3ControlScopeTraceResolver m_scopeTraces; // Regexp to trace enables
std::unordered_map<string, std::unordered_map<string, uint64_t>>
m_profileData; // Access to profile_data records
uint8_t m_mode = NONE;
uint8_t m_mode = NONE; // Kind of profile_data records is currently active
std::unordered_map<string, V3ControlResolverHierWorkerEntry> m_hierWorkers;
FileLine* m_profileFileLine = nullptr;
FileLine* m_profileFileLine = nullptr; // Location profile_data was read from
V3ControlResolver() = default;
~V3ControlResolver() = default;
+13 -2
View File
@@ -199,6 +199,7 @@ class CoverageVisitor final : public VNVisitor {
AstVar* const varp = new AstVar{fl_nowarn, VVarType::MODULETEMP, trace_var_name,
incp->findUInt32DType()};
varp->setIgnoreSchedWrite(); // Ignore the increment output, so no UNOPTFLAT
varp->lifetime(VLifetime::STATIC_IMPLICIT);
varp->trace(true);
m_modp->addStmtsp(varp);
UINFO(5, "New coverage trace: " << varp);
@@ -283,7 +284,7 @@ class CoverageVisitor final : public VNVisitor {
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
m_state.m_inModOff = nodep->isTop(); // Already made top shell, no coverage for it
if (!origModp) {
// No blocks cross (non-nested) modules, so save some memory
m_varnames.clear();
@@ -1053,7 +1054,7 @@ class CoverageVisitor final : public VNVisitor {
unrolledp = new T_Oper{fl, selp, unrolledp};
}
iterate(unrolledp);
pushDeletep(unrolledp);
VL_DO_DANGLING(pushDeletep(unrolledp), unrolledp);
} else {
iterateChildren(nodep);
lineTrack(nodep);
@@ -1114,6 +1115,16 @@ class CoverageVisitor final : public VNVisitor {
}
// VISITORS - BOTH
void visit(AstProperty* nodep) override {
VL_RESTORER(m_state);
m_state.m_on = false;
iterateChildren(nodep);
}
void visit(AstSequence* nodep) override {
VL_RESTORER(m_state);
m_state.m_on = false;
iterateChildren(nodep);
}
void visit(AstNode* nodep) override {
iterateChildren(nodep);
lineTrack(nodep);
+2907 -574
View File
File diff suppressed because it is too large Load Diff
+322 -72
View File
@@ -37,6 +37,8 @@
#include "V3Dead.h"
#include "V3ConstPool.h"
#include "V3Graph.h"
#include "V3Stats.h"
#include <queue>
@@ -44,6 +46,199 @@
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Dead tracking graph
class DeadVertex final : public V3GraphVertex {
VL_RTTI_IMPL(DeadVertex, V3GraphVertex)
AstNode* const m_nodep; // Node that created vertex
uint64_t m_workPos = 0; // Position on DeadWorkList, 0 = not on list
bool m_removable = false; // Subject to dead removal
bool m_isVirtual = false; // Virtual node, indirection
public:
DeadVertex(V3Graph* graphp, AstNode* nodep)
: V3GraphVertex{graphp}
, m_nodep{nodep} {}
~DeadVertex() override = default;
string dotShape() const override {
return isVirtual() ? "diamond" : removable() ? "rectangle" : "ellipse";
}
AstNode* nodep() const VL_MT_STABLE { return m_nodep; }
string name() const override VL_MT_STABLE {
return (isVirtual() ? "[VIRT] "
: removable() ? "[R] "s
: "[NR] ")
+ nodep()->typeName() + ' ' + cvtToHex(nodep()) + ' ' + nodep()->name();
}
bool removable() const { return m_removable; }
void removable(bool flag) { m_removable = flag; }
bool isVirtual() const { return m_isVirtual; }
void isVirtual(bool flag) { m_isVirtual = flag; }
uint64_t workPos() const { return m_workPos; }
void workPos(uint64_t value) { m_workPos = value; }
};
// Work list which keeps ordering of elements (so stable), adds always to
// the end of the queue, and allows arbitrary removal
class DeadWorkList final {
// MEMBERS
static uint64_t s_sequence; // Sequence number, inc each push
std::map<uint64_t, DeadVertex*> m_works; // Work list by sequence
public:
// METHODS
bool empty() const { return m_works.empty(); }
void push(DeadVertex* vtxp) {
if (vtxp->workPos()) return; // Already on list
const uint64_t id = ++s_sequence;
vtxp->workPos(id);
m_works.emplace(id, vtxp);
UINFO(9, "Worklist.push " << vtxp);
}
void erase(DeadVertex* vtxp) {
const uint64_t id = vtxp->workPos();
if (!id) return;
UINFO(9, "Worklist.erase " << vtxp);
const auto it = m_works.find(id);
UASSERT_OBJ(it != m_works.end(), vtxp->nodep(),
"vertex thought to be on work list but not");
m_works.erase(it);
vtxp->workPos(0);
}
DeadVertex* getPopFront() {
UDEBUGONLY(UASSERT(!empty(), "Front invalid to call on empty list"););
const auto it = m_works.begin();
DeadVertex* const vtxp = it->second;
m_works.erase(it);
vtxp->workPos(0);
UINFO(9, "Worklist.getPopFront " << vtxp);
return vtxp;
}
};
uint64_t DeadWorkList::s_sequence = 0;
class DeadGraph final : public V3Graph {
// NODE STATE
// AstNodeFTask::user2p() -> DeadVertex* for this node
// See const VNUser2InUse m_inuser2; inside DeadVisitor
// MEMBERS
DeadWorkList m_funcs; // Functions eligble for deletion
// Each virtual vertex by the ftask name()
std::unordered_map<std::string, DeadVertex*> m_virtualVtxsp;
void newEdge(DeadVertex* fromp, DeadVertex* top) {
new V3GraphEdge{this, fromp, top, 1, false};
if (top->removable()) m_funcs.erase(top); // Now has an input edge
}
void pushWorkMaybe(DeadVertex* vtxp, bool allowSize1) {
if (vtxp->removable()) {
if (VN_IS(vtxp->nodep(), NodeFTask)
&& (vtxp->inEmpty() || (allowSize1 && vtxp->inSize1())))
m_funcs.push(vtxp);
}
}
public:
// METHODS
DeadGraph() = default;
~DeadGraph() override = default;
DeadVertex* findNewVertex(AstNode* nodep) {
DeadVertex* vtxp = nodep->user2u().to<DeadVertex*>();
if (!vtxp) {
vtxp = new DeadVertex{this, nodep};
nodep->user2p(vtxp);
pushWorkMaybe(vtxp, false);
}
UASSERT_OBJ(vtxp->nodep() == nodep, nodep, "Vertex points at different node");
return vtxp;
}
DeadVertex* findVirtualVertex(AstNode* nodep) {
const auto it = m_virtualVtxsp.find(nodep->name());
DeadVertex* vtxp;
if (it != m_virtualVtxsp.end()) {
vtxp = it->second;
} else {
vtxp = new DeadVertex{this, nodep};
vtxp->removable(true);
vtxp->isVirtual(true);
pushWorkMaybe(vtxp, false);
m_virtualVtxsp.emplace(nodep->name(), vtxp);
}
return vtxp;
}
void findNewRemovableVertex(AstNode* nodep, bool removable) {
DeadVertex* const vtxp = findNewVertex(nodep);
// Wasn't removable before (due to earlier insert), make removable now
if (removable && !vtxp->removable()) {
vtxp->removable(true);
pushWorkMaybe(vtxp, false);
}
}
void deleteNodeVertex(AstNode* nodep) {
if (DeadVertex* const vtxp = nodep->user2u().to<DeadVertex*>()) deleteVertex(vtxp);
}
void deleteVertex(DeadVertex* vtxp) {
UINFO(9, "Delete vertex " << vtxp->name());
// Mark all about-to-empty downstream vertices onto worklist
for (const V3GraphEdge& oedge : vtxp->outEdges()) {
DeadVertex* const toVtxp = static_cast<DeadVertex*>(oedge.top());
pushWorkMaybe(toVtxp, true); // size1 ok as about to delete below
}
// Node shouldn't be looking at user2p later as node being deleting, but in case
if (!vtxp->isVirtual()) vtxp->nodep()->user2p(nullptr);
if (vtxp->removable()) m_funcs.erase(vtxp);
VL_DO_DANGLING(vtxp->unlinkDelete(this), vtxp);
}
// This only tracks usage dependancy, not "containership",
// When all needs disappear the related node is eligble for deletion
void needs(AstNode* nodep, AstNode* parentp) {
if (parentp == nodep) return; // No need for tracking needs itself (recursion)
UINFO(9, "Edge node " << nodep << " -> " << parentp);
DeadVertex* const parentVtxp = findNewVertex(parentp);
DeadVertex* const nodeVtxp = findNewVertex(nodep);
UINFO(9, "Edge need " << parentVtxp << " -> " << nodeVtxp);
newEdge(parentVtxp, nodeVtxp);
}
void needsVirtual(AstNodeFTask* nodep, AstNode* parentp) {
// Virtual call can be to any function in the call hierarchy.
// For simplicity rather than tracking possible multiple base
// classes (due to 'implements' classes there can be more than
// one), we simply assume all virtual functions of the same name
// can call any other virtual function of the same name
// Track via an intermediate node.
// All calling parents' verticies -> Virtual Vertex -> all ftasks verticies
if (parentp == nodep) return; // No need for tracking needs itself (recursion)
DeadVertex* const parentVtxp = findNewVertex(parentp);
DeadVertex* const virtualVtxp = findVirtualVertex(nodep);
UINFO(9, "Edge needVirtual " << parentVtxp << " -> " << virtualVtxp);
newEdge(parentVtxp, virtualVtxp);
}
void funcVirtual(AstNodeFTask* nodep) {
// Virtual Vertex -> all ftasks verticies
DeadVertex* const virtualVtxp = findVirtualVertex(nodep);
DeadVertex* const nodeVtxp = findNewVertex(nodep);
UINFO(9, "Edge funcvirtual " << virtualVtxp << " -> " << nodeVtxp);
newEdge(virtualVtxp, nodeVtxp);
}
bool funcsEmpty() const { return m_funcs.empty(); }
AstNode* funcsGetPopFront() {
DeadVertex* const vtxp = m_funcs.getPopFront();
UASSERT_OBJ(vtxp->inEmpty(), vtxp->nodep(), "Non-empty node on work list");
UASSERT_OBJ(vtxp->removable(), vtxp->nodep(), "Non-removable node on work list");
if (vtxp->isVirtual()) {
// Emptied (no inbound edge) virtual wrapper; all
// destinations verticies are now unused too (e.g. all virtual
// functions of this name may be deleted)
UINFO(9, "Removing virtual " << vtxp);
deleteVertex(vtxp);
return nullptr; // Caller will search again
}
AstNode* const nodep = vtxp->nodep();
return nodep;
}
};
//######################################################################
// Dead state, as a visitor of each AstNode
@@ -52,10 +247,11 @@ class DeadVisitor final : public VNVisitor {
// Entire Netlist:
// AstNodeModule::user1() -> int. Count of number of cells referencing this module.
// AstVar::user1() -> int. Count of number of references
// AstVar::user2() -> bool. Is a formal function argument
// AstVarScope::user1() -> int. Count of number of references
// AstNodeDType::user1() -> int. Count of number of references
// AstNodeFTask::user1() -> int. Count of number of references (via AstNodeFTaskRefs)
const VNUser1InUse m_inuser1;
const VNUser2InUse m_inuser2; // For usage information see DeadGraph
// TYPES
using AssignMap = std::multimap<AstVarScope*, AstNodeAssign*>;
@@ -64,6 +260,9 @@ class DeadVisitor final : public VNVisitor {
const bool m_elimUserVars; // Allow removal of user's vars
const bool m_elimDTypes; // Allow removal of DTypes
const bool m_elimCells; // Allow removal of Cells
DeadGraph m_graph; // Tracking graph
// List of all encountered to avoid another loop through tree
std::vector<AstVar*> m_varsp;
std::vector<AstNode*> m_dtypeElimsp; // Data types might eliminate
@@ -73,7 +272,6 @@ class DeadVisitor final : public VNVisitor {
std::vector<AstCell*> m_cellsp;
std::vector<AstClass*> m_classesp;
std::vector<AstTypedef*> m_typedefsp;
std::queue<AstNodeFTask*> m_tasksp; // All the tasks that could be removed if not called
AssignMap m_assignMap; // List of all simple assignments for each variable
bool m_sideEffect = false; // Side effects discovered in assign RHS
@@ -82,14 +280,20 @@ class DeadVisitor final : public VNVisitor {
AstNodeDType* m_curDTypep = nullptr; // Current NodeDType
AstNodeModule* m_modp = nullptr; // Current module
AstForeachHeader* m_foreachHeaderp = nullptr; // Current foreach header
AstNode* m_containingFTaskRefp = nullptr; // Parent of ftaskref (e.g. task/module)
bool m_inFTask = false; // Under an AstNodeFTask
// STATE - Statistic tracking
VDouble0 m_statFTasksDeadified;
VDouble0 m_statFTasksDemoted;
VDouble0 m_statFTasksMDeadified;
VDouble0 m_statFTasksNMDeadified;
VDouble0 m_statFTasksVirtDeadified;
// METHODS
void deleting(AstNode* nodep) {
UINFO(9, " deleting " << nodep);
m_graph.deleteNodeVertex(nodep);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
@@ -118,12 +322,22 @@ class DeadVisitor final : public VNVisitor {
if (AstNode* const subnodep = nodep->virtRefDTypep()) subnodep->user1Inc();
if (AstNode* const subnodep = nodep->virtRefDType2p()) subnodep->user1Inc();
}
void needsTask(AstNodeFTask* taskp, AstNode* containerp) {
if (!taskp) return; // Unlinked
if (taskp->isVirtual()) {
m_graph.needsVirtual(taskp, containerp);
} else {
m_graph.needs(taskp, containerp);
}
}
// VISITORS
void visit(AstNodeModule* nodep) override {
if (m_modp) m_modp->user1Inc(); // e.g. Class under Package
VL_RESTORER(m_modp);
m_modp = nodep;
VL_RESTORER(m_containingFTaskRefp);
m_containingFTaskRefp = nodep;
if (nodep->dead()) return;
if (nodep->modPublic()) m_modp->user1Inc();
iterateChildren(nodep);
@@ -140,6 +354,16 @@ class DeadVisitor final : public VNVisitor {
iterateChildren(nodep);
checkAll(nodep);
if (nodep->scopep()) nodep->scopep()->user1Inc();
// Keep formal arguments
for (AstVar* varp = nodep->argsp(); varp; varp = VN_AS(varp->nextp(), Var)) {
varp->user1Inc();
varp->user2(1);
}
}
void visit(AstPin* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
if (nodep->modVarp()) nodep->modVarp()->user1Inc();
}
void visit(AstScope* nodep) override {
iterateChildren(nodep);
@@ -148,6 +372,8 @@ class DeadVisitor final : public VNVisitor {
// Class packages might have no children, but need to remain as
// long as the class they refer to is needed
if (VN_IS(m_modp, Class) || VN_IS(m_modp, ClassPackage)) nodep->user1Inc();
// The constant pool scope must remain for entries created later
if (m_modp->isConstPool()) nodep->user1Inc();
if (!nodep->isTop() && !nodep->varsp() && !nodep->blocksp()) {
m_scopesp.push_back(nodep);
}
@@ -175,7 +401,7 @@ class DeadVisitor final : public VNVisitor {
iterateChildren(nodep);
if (!m_sideEffect && !nodep->isPure()) m_sideEffect = true;
checkAll(nodep);
if (nodep->taskp()) nodep->taskp()->user1Inc();
needsTask(nodep->taskp(), m_containingFTaskRefp);
if (nodep->classOrPackagep()) {
if (m_elimCells) {
nodep->classOrPackagep(nullptr);
@@ -187,7 +413,12 @@ class DeadVisitor final : public VNVisitor {
void visit(AstModportFTaskRef* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
if (nodep->ftaskp()) nodep->ftaskp()->user1Inc();
needsTask(nodep->ftaskp(), m_containingFTaskRefp);
}
void visit(AstModportVarRef* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
if (nodep->varp()) nodep->varp()->user1Inc();
}
void visit(AstRefDType* nodep) override {
iterateChildren(nodep);
@@ -294,6 +525,11 @@ class DeadVisitor final : public VNVisitor {
iterateChildren(nodep);
checkAll(nodep);
if (m_foreachHeaderp) nodep->user1Inc();
// Keep formal arguments
if (m_inFTask && nodep->isIO()) {
nodep->user1Inc();
nodep->user2(1);
}
if (mightElimVar(nodep)) {
m_varsp.push_back(nodep);
} else {
@@ -327,13 +563,21 @@ class DeadVisitor final : public VNVisitor {
if (assignInAssign) m_sideEffect = true; // Parent assign shouldn't optimize
}
void visit(AstNodeFTask* nodep) override {
const bool removable = !(nodep->taskPublic() || nodep->dpiExport() || nodep->dpiImport()
|| nodep->keepAlive() || nodep->isConstructor()
|| (!v3Global.opt.fDeadMethods() && nodep->classMethod()));
m_graph.findNewRemovableVertex(nodep, removable);
//
VL_RESTORER(m_containingFTaskRefp);
VL_RESTORER(m_inFTask);
m_containingFTaskRefp = nodep;
m_inFTask = true;
iterateChildren(nodep);
checkAll(nodep);
if (nodep->taskPublic() || nodep->dpiExport() || nodep->dpiImport()) {
if (nodep->isVirtual()) m_graph.funcVirtual(nodep);
if (!removable) {
if (m_modp && !m_modp->dead() && !m_modp->verilatorLib())
m_modp->user1Inc(); // Keep container
} else {
m_tasksp.push(nodep);
}
if (nodep->classOrPackagep()) {
if (m_elimCells) {
@@ -374,19 +618,41 @@ class DeadVisitor final : public VNVisitor {
}
void deadCheckTasks() {
while (!m_tasksp.empty()) {
AstNodeFTask* taskp = m_tasksp.front();
m_tasksp.pop();
if (taskp->user1() == 0 && !taskp->classMethod()) {
taskp->foreach([this](AstNodeFTaskRef* ftaskrefp) {
AstNodeFTask* task2p = ftaskrefp->taskp();
if (!task2p) return;
task2p->user1Inc(-1);
if (task2p->user1() == 0) m_tasksp.push(task2p);
});
taskp->user1(-1); // we don't want to try deleting twice
deleting(taskp);
++m_statFTasksDeadified;
while (!m_graph.funcsEmpty()) {
AstNode* const nodep = m_graph.funcsGetPopFront();
if (!nodep) continue;
UINFO(9, "Dead " << nodep);
if (AstNodeFTask* const taskp = VN_CAST(nodep, NodeFTask)) {
if (taskp->isVirtual()) {
++m_statFTasksVirtDeadified;
} else if (taskp->classMethod()) {
++m_statFTasksMDeadified;
} else {
++m_statFTasksNMDeadified;
}
}
deleting(nodep);
}
}
void deadCheckDemote() {
for (V3GraphVertex& gvtx : m_graph.vertices()) {
DeadVertex* const vtxp = gvtx.cast<DeadVertex>();
// A isVirtual vertex with single out means there's only one target virtual function
// that can be virtually called, so can make it non-virtual for faster execution
// (UVM benefits from this)
if (!vtxp->outSize1()) continue;
if (!vtxp->isVirtual()) continue;
for (V3GraphEdge& edge : vtxp->outEdges()) { // Always a single one
AstNode* const nodep = edge.top()->as<DeadVertex>()->nodep();
AstNodeFTask* const funcp = VN_AS(nodep, NodeFTask);
UASSERT_OBJ(funcp->isVirtual(), funcp,
"Only virtual ftasks should be under DeadVirtualVertex");
if (v3Global.opt.fDeadMethods()) {
funcp->isVirtual(false);
UINFO(9, "Demote to non-virtual " << funcp);
++m_statFTasksDemoted;
}
}
}
}
@@ -400,7 +666,11 @@ class DeadVisitor final : public VNVisitor {
AstNodeModule* nextmodp;
for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp; modp = nextmodp) {
nextmodp = VN_AS(modp->nextp(), NodeModule);
if (modp->dead() || (!modp->isTop() && modp->user1() == 0 && !modp->internal())) {
// Keep $unit until m_elimCells stages. Note v3Global.opt.serializeOnly()
// won't reach this stage, and will always have an empty $unit. That's ok.
// The constant pool is always kept, entries might be created later.
const bool keep = (!m_elimCells && modp->isDollarUnit()) || modp->isConstPool();
if (modp->dead() || (!modp->isTop() && modp->user1() == 0 && !keep)) {
// > 2 because L1 is the wrapper, L2 is the top user module
UINFO(4, " Dead module " << modp);
// And its children may now be killable too; correct counts
@@ -410,6 +680,7 @@ class DeadVisitor final : public VNVisitor {
cellp->modp()->user1Inc(-1);
});
}
if (modp->isDollarUnit()) v3Global.rootp()->dollarUnitPkgp(nullptr);
deleting(modp);
retry = true;
}
@@ -418,7 +689,7 @@ class DeadVisitor final : public VNVisitor {
}
bool mightElimVar(const AstVar* nodep) const {
if (nodep->isSigPublic()) return false; // Can't elim publics!
if (nodep->isIO() || nodep->isClassMember() || nodep->sensIfacep()) return false;
if (nodep->isPrimaryIO() || nodep->isClassMember() || nodep->sensIfacep()) return false;
if (nodep->isTemp() && !nodep->isTrace()) return true;
return m_elimUserVars; // Post-Trace can kill most anything
}
@@ -470,6 +741,8 @@ class DeadVisitor final : public VNVisitor {
void deadCheckVar() {
// Delete any unused varscopes
for (AstVarScope* vscp : m_vscsp) {
// Keep formal arguments
if (vscp->varp()->user2()) continue;
if (vscp->user1() == 0) {
UINFO(4, " Dead " << vscp);
const std::pair<AssignMap::iterator, AssignMap::iterator> eqrange
@@ -522,53 +795,17 @@ class DeadVisitor final : public VNVisitor {
}
}
// cppcheck-suppress constParameterPointer
void preserveTopIfaces(AstNetlist* rootp) {
// cppcheck-suppress constVariablePointer
for (AstNodeModule* modp = rootp->modulesp(); modp && modp->isTop();
modp = VN_AS(modp->nextp(), NodeModule)) {
for (AstNode* subnodep = modp->stmtsp(); subnodep; subnodep = subnodep->nextp()) {
if (AstVar* const varp = VN_CAST(subnodep, Var)) {
if (varp->isIfaceRef()) {
const AstNodeDType* const subtypep = varp->subDTypep();
const AstIfaceRefDType* ifacerefp = nullptr;
if (VN_IS(subtypep, IfaceRefDType)) {
ifacerefp = VN_AS(varp->subDTypep(), IfaceRefDType);
} else if (VN_IS(subtypep, BracketArrayDType)) {
const AstBracketArrayDType* const arrp
= VN_AS(subtypep, BracketArrayDType);
const AstNodeDType* const arrsubtypep = arrp->subDTypep();
if (VN_IS(arrsubtypep, IfaceRefDType)) {
ifacerefp = VN_AS(arrsubtypep, IfaceRefDType);
}
} else if (VN_IS(subtypep, UnpackArrayDType)) {
const AstUnpackArrayDType* const arrp
= VN_AS(subtypep, UnpackArrayDType);
const AstNodeDType* const arrsubtypep = arrp->subDTypep();
if (VN_IS(arrsubtypep, IfaceRefDType)) {
ifacerefp = VN_AS(arrsubtypep, IfaceRefDType);
}
}
if (ifacerefp && !ifacerefp->cellp()
&& (ifacerefp->ifacep()->user1() == 0)) {
ifacerefp->ifacep()->user1(1);
}
}
}
}
}
}
public:
// CONSTRUCTORS
DeadVisitor(AstNetlist* nodep, bool elimUserVars, bool elimDTypes, bool elimScopes,
bool elimCells, bool elimTopIfaces, bool elimTasks)
bool elimCells, bool elimTasks)
: m_elimUserVars{elimUserVars}
, m_elimDTypes{elimDTypes}
, m_elimCells{elimCells} {
, m_elimCells{elimCells}
, m_containingFTaskRefp{nodep} {
// Prepare to remove some datatypes
nodep->typeTablep()->clearCache();
// Operate on whole netlist
iterate(nodep);
@@ -582,6 +819,11 @@ public:
if (itr.first->user1()) itr.second->user1Inc();
}
// Simplify redundant edges (e.g. function calls another function many times)
m_graph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue);
if (dumpGraphLevel() >= 9 || debug() >= 9)
m_graph.dumpDotFilePrefixed("dead_graph", false);
if (elimTasks) deadCheckTasks();
deadCheckTypedefs();
deadCheckVar();
@@ -591,16 +833,24 @@ public:
if (elimCells) deadCheckCells();
deadCheckClasses();
// Modules after vars, because might be vars we delete inside a mod we delete
if (!elimTopIfaces) preserveTopIfaces(nodep);
deadCheckMod();
// After deleting as much as can, demote some virtual functions
if (elimTasks) deadCheckDemote();
// We may have removed some datatypes, cleanup
nodep->typeTablep()->repairCache();
VIsCached::clearCacheTree(); // Removing assignments may affect isPure
nodep->constPoolp()->rebuildVarScopesAndCache();
V3ConstPool::invalidateCache(); // Might have deleted constant pool entries
}
~DeadVisitor() override {
V3Stats::addStatSum("Optimizations, deadified FTasks", m_statFTasksDeadified);
V3Stats::addStatSum("Optimizations, FTasks, virtual-to-nonvirtual demotion",
m_statFTasksDemoted);
V3Stats::addStatSum("Optimizations, FTasks, deadified, methods", m_statFTasksMDeadified);
V3Stats::addStatSum("Optimizations, FTasks, deadified, non-methods",
m_statFTasksNMDeadified);
V3Stats::addStatSum("Optimizations, FTasks, deadified, virtual",
m_statFTasksVirtDeadified);
};
};
@@ -609,32 +859,32 @@ public:
void V3Dead::deadifyModules(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ // node, elimUserVars, elimDTypes, elimScopes, elimCells, elimTopIfaces
DeadVisitor{nodep, false, false, false, false, !v3Global.opt.topIfacesSupported(), false};
{ // node, elimUserVars, elimDTypes, elimScopes, elimCells, elimTasks
DeadVisitor{nodep, false, false, false, false, false};
} // Destruct before checking
V3Global::dumpCheckGlobalTree("deadModules", 0, dumpTreeEitherLevel() >= 6);
}
void V3Dead::deadifyDTypes(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ DeadVisitor{nodep, false, true, false, false, false, true}; } // Destruct before checking
{ DeadVisitor{nodep, false, true, false, false, true}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("deadDtypes", 0, dumpTreeEitherLevel() >= 3);
}
void V3Dead::deadifyDTypesScoped(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ DeadVisitor{nodep, false, true, true, false, false, false}; } // Destruct before checking
{ DeadVisitor{nodep, false, true, true, false, false}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("deadDtypesScoped", 0, dumpTreeEitherLevel() >= 3);
}
void V3Dead::deadifyAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ DeadVisitor{nodep, true, true, false, true, false, true}; } // Destruct before checking
{ DeadVisitor{nodep, true, true, false, true, true}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("deadAll", 0, dumpTreeEitherLevel() >= 3);
}
void V3Dead::deadifyAllScoped(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ DeadVisitor{nodep, true, true, true, true, false, true}; } // Destruct before checking
{ DeadVisitor{nodep, true, true, true, true, true}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("deadAllScoped", 0, dumpTreeEitherLevel() >= 3);
}
+141 -23
View File
@@ -118,6 +118,7 @@
#include "V3Delayed.h"
#include "V3AstUserAllocator.h"
#include "V3ClassGraph.h"
#include "V3Const.h"
#include "V3Stats.h"
@@ -250,6 +251,7 @@ class DelayedVisitor final : public VNVisitor {
// AstVar::user1() -> bool. Set true if already issued MULTIDRIVEN warning
// AstVarRef::user1() -> bool. Set true if target of NBA
// AstAssignDly::user1() -> bool. Set true if already visited
// AstCFunc::user1() -> AstUser1Allocator. See `m_cfuncsCache` below
// AstAssignDly::user2p() -> AstVarScope*: Scope this AstAssignDelay is under
// AstNodeModule::user1p() -> std::unorded_map<std::string, AstVar*> temp map via m_varMap
// AstScope::user1() -> int: Temporary counter for this scope
@@ -259,6 +261,24 @@ class DelayedVisitor final : public VNVisitor {
const VNUser1InUse m_user1InUse;
const VNUser2InUse m_user2InUse;
const VNUser3InUse m_user3InUse;
struct CFuncCache final {
VInsertionSet<AstSenTree*> m_timingDomains; // What shall be added to m_timingDomains
std::set<AstCFunc*>
m_includes; // CFuncs whose CFuncCache shall be included into this - this is used to
// break cycles: A->B->A (instead of visiting A while it is still begin
// visited B just marks that it includes A)
enum State : uint8_t {
UNINITIALIZED = 0, // Not initialized members are empty
VISITING, // Visiting - needed for breaking recursion
INITIALIZED, // Members contains correct values
} m_state // Current state of Cache
= UNINITIALIZED;
};
// Caches what should be added to m_timingDomains because of calls to the AstCFunc (with
// recursive check of other AstCFuncs called from inside)
AstUser1Allocator<AstCFunc, CFuncCache> m_cfuncsCache;
AstUser1Allocator<AstNodeModule, std::unordered_map<std::string, AstVar*>> m_varMap;
AstUser1Allocator<AstVarScope, VarScopeInfo> m_vscpInfo;
AstUser3Allocator<AstVarScope, std::vector<WriteReference>> m_writeRefs;
@@ -266,6 +286,10 @@ class DelayedVisitor final : public VNVisitor {
// STATE - across all visitors
VInsertionSet<AstSenTree*> m_timingDomains; // Timing resume domains
const std::unique_ptr<V3ClassGraph>
m_classGraphp; // class graph to get possibly called functions from a virtual call
std::vector<const AstCFunc*> m_callStack; // Current callstack of AstCFuncs
// STATE - for current visit position (use VL_RESTORER)
AstActive* m_activep = nullptr; // Current activate
const AstCFunc* m_cfuncp = nullptr; // Current public C Function
@@ -276,6 +300,7 @@ class DelayedVisitor final : public VNVisitor {
bool m_ignoreBlkAndNBlk = false; // Suppress delayed assignment BLKANDNBLK
bool m_inNonCombLogic = false; // We are in non-combinational logic
bool m_needsInitialTrigger = false; // Whether a NodeProcedure needs a initial trigger
std::vector<AstSenTree*> m_nbaEventSenTreeps; // Sensitivities of '->>' in the process
AstVarRef* m_currNbaLhsRefp = nullptr; // Current NBA LHS variable reference
// STATE - during NBA conversion (after visit)
@@ -293,6 +318,7 @@ class DelayedVisitor final : public VNVisitor {
VDouble0 m_nSchemeValueQueuesPartial; // Number of variables using Scheme::ValueQueuePartial
VDouble0 m_nSharedSetFlags; // "Set" flags actually shared by Scheme::FlagShared variables
VDouble0 m_nInitialNBA; // Number of procedural blocks with initial NBA
VDouble0 m_nonInlinedCAwaitsWithSenTree; // Count uses of not inlined co_awaits
// METHODS
@@ -563,6 +589,17 @@ class DelayedVisitor final : public VNVisitor {
return ss.str();
}
void addCFuncCachedValues(const AstCFunc* const cfuncp,
std::unordered_set<const AstCFunc*>& visited) {
if (!visited.insert(cfuncp).second) return;
CFuncCache& value = m_cfuncsCache(cfuncp);
m_timingDomains.insert(value.m_timingDomains.begin(), value.m_timingDomains.end());
m_nonInlinedCAwaitsWithSenTree += value.m_timingDomains.size();
for (const AstCFunc* const includedp : value.m_includes) {
addCFuncCachedValues(includedp, visited);
}
}
// Create a temporary variable in the given 'scopep', with the given 'name', and with 'dtypep'
// type, with the bits selected by 'sLsbp'/'sWidthp' set to 'valuep', other bits set to zero.
// Insert new statements before 'insertp'.
@@ -784,7 +821,7 @@ class DelayedVisitor final : public VNVisitor {
m_prevVscp = vscp;
// Delete original NBA
pushDeletep(nodep->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
// Scheme::FlagUnique
@@ -834,7 +871,7 @@ class DelayedVisitor final : public VNVisitor {
ifp->addThensp(new AstAssign{flp, capturedLhsp, capturedRhsp});
// Delete original NBA
pushDeletep(nodep->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
// Scheme::ValueQueuePartial/Scheme::ValueQueueWhole
@@ -865,8 +902,9 @@ class DelayedVisitor final : public VNVisitor {
activep->addStmtsp(postp);
// Add the commit
AstCMethodHard* const callp = new AstCMethodHard{
flp, new AstVarRef{flp, queueVscp, VAccess::READWRITE}, VCMethod::SCHED_COMMIT};
flp, new AstVarRef{flp, queueVscp, VAccess::READWRITE}, VCMethod::NBA_COMMIT};
callp->dtypeSetVoid();
// TODO: this is a partial update, so must be READWRITE, but that breaks scheduling
callp->addPinsp(new AstVarRef{flp, vscp, VAccess::WRITE});
postp->addStmtsp(callp->makeStmt());
}
@@ -975,7 +1013,7 @@ class DelayedVisitor final : public VNVisitor {
// Enqueue the update at the site of the original NBA
AstCMethodHard* const callp = new AstCMethodHard{
flp, new AstVarRef{flp, vscpInfo.valueQueueKit().vscp, VAccess::READWRITE},
VCMethod::SCHED_ENQUEUE};
VCMethod::NBA_ENQUEUE};
callp->dtypeSetVoid();
callp->addPinsp(valuep);
if (partial) callp->addPinsp(maskp);
@@ -983,7 +1021,7 @@ class DelayedVisitor final : public VNVisitor {
nodep->addHereThisAsNext(callp->makeStmt());
// Delete original NBA
pushDeletep(nodep->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
// Record where a variable is assigned
@@ -1007,6 +1045,43 @@ class DelayedVisitor final : public VNVisitor {
return procedurep && procedurep->sentreep();
}
// Visit AstCFunc from a AstNodeCCall - this is made into a separate quasi-visitor because
// AstCFunc that is not called from the code (e.g.: DPI exports) does not need to be visited
// this way. Also, not visiting such AstCFuncs allows to avoid caching results for them which
// this function does - which could lead to excessive memory usage
void visitCalledCFunc(AstCFunc* const nodep) {
CFuncCache& value = m_cfuncsCache(nodep);
switch (value.m_state) {
case CFuncCache::UNINITIALIZED: {
// Save current state
VL_RESTORER_CLEAR(m_timingDomains);
// Visit
value.m_state = CFuncCache::VISITING;
m_callStack.push_back(nodep);
{
VL_RESTORER(m_cfuncp);
m_cfuncp = nodep;
iterateChildren(nodep);
}
m_callStack.pop_back();
value.m_state = CFuncCache::INITIALIZED;
// Save a cache
std::swap(m_timingDomains, value.m_timingDomains);
} break;
case CFuncCache::VISITING: {
for (size_t i = m_callStack.size() - 1; m_callStack.at(i) != nodep; --i) {
m_cfuncsCache(m_callStack[i]).m_includes.insert(nodep);
}
return; // Break recursion
}
case CFuncCache::INITIALIZED: break;
}
std::unordered_set<const AstCFunc*> visited;
addCFuncCachedValues(nodep, visited);
}
// VISITORS
void visit(AstNetlist* nodep) override {
iterateChildren(nodep);
@@ -1103,11 +1178,6 @@ class DelayedVisitor final : public VNVisitor {
m_scopep = nodep;
iterateChildren(nodep);
}
void visit(AstCFunc* nodep) override {
VL_RESTORER(m_cfuncp);
m_cfuncp = nodep;
iterateChildren(nodep);
}
void visit(AstActive* nodep) override {
UASSERT_OBJ(!m_activep, nodep, "Should not nest");
VL_RESTORER(m_activep);
@@ -1121,6 +1191,7 @@ class DelayedVisitor final : public VNVisitor {
}
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_needsInitialTrigger);
VL_RESTORER_CLEAR(m_nbaEventSenTreeps);
const size_t firstNBAAddedIndex = m_nbas.size();
{
VL_RESTORER(m_inSuspendableOrFork);
@@ -1172,6 +1243,8 @@ class DelayedVisitor final : public VNVisitor {
for (size_t i = firstNBAAddedIndex; i < m_nbas.size(); ++i) {
m_vscpInfo(m_nbas[i].vscp).addSensitivity(senItemp);
}
for (AstSenTree* const senTreep : m_nbaEventSenTreeps)
senTreep->addSensesp(senItemp->cloneTree(true));
// Done with these
VL_DO_DANGLING(senItemp->deleteTree(), senItemp);
}
@@ -1182,6 +1255,7 @@ class DelayedVisitor final : public VNVisitor {
}
void visit(AstCAwait* nodep) override {
if (nodep->sentreep()) m_timingDomains.insert(nodep->sentreep());
iterateChildren(nodep);
}
void visit(AstFireEvent* nodep) override {
UASSERT_OBJ(v3Global.hasEvents(), nodep, "Inconsistent");
@@ -1196,8 +1270,15 @@ class DelayedVisitor final : public VNVisitor {
cstmtp->add(");");
AstNode* newp = cstmtp;
if (nodep->isDelayed()) {
const AstVarRef* const vrefp = VN_AS(eventp, VarRef);
const AstVarRef* const vrefp = VN_CAST(eventp, VarRef);
if (nodep->isDelayed() && (m_cfuncp || !vrefp)) {
// V3Timing converts these with --timing
nodep->v3warn(E_NOTIMING, "Nonblocking event trigger "
<< (m_cfuncp ? "in a non-inlined function/task"
: "of a class or interface member or "
"array element")
<< " requires --timing");
} else if (nodep->isDelayed()) {
const std::string newvarname = "__Vdly__" + vrefp->varp()->shortName();
AstVarScope* const dlyvscp
= createTemp(flp, vrefp->varScopep()->scopep(), newvarname, 1);
@@ -1206,20 +1287,35 @@ class DelayedVisitor final : public VNVisitor {
return new AstVarRef{flp, dlyvscp, access};
};
AstAlwaysPre* const prep = new AstAlwaysPre{flp};
prep->addStmtsp(new AstAssign{flp, dlyRef(VAccess::WRITE),
new AstConst{flp, AstConst::BitFalse{}}});
AstAlwaysPost* const postp = new AstAlwaysPost{flp};
{
AstIf* const ifp = new AstIf{flp, dlyRef(VAccess::READ)};
postp->addStmtsp(ifp);
ifp->addThensp(newp);
}
AstIf* const ifp = new AstIf{flp, dlyRef(VAccess::READ)};
postp->addStmtsp(ifp);
UASSERT_OBJ(m_activep, nodep, "No active to handle FireEvent");
AstActive* const activep = new AstActive{flp, "nba-event", m_activep->sentreep()};
AstSenTree* senTreep = m_activep->sentreep();
AstActive* activep = nullptr;
if (m_inSuspendableOrFork && (senTreep->hasInitial() || senTreep->hasClocked())) {
// Suspendable code can trigger whenever it resumes, so fire when its NBAs are
// committed (see visit(AstNodeProcedure*)), clearing the flag like
// Scheme::FlagUnique
senTreep = new AstSenTree{
flp, senTreep->hasClocked() ? senTreep->sensesp()->cloneTree(true) : nullptr};
m_nbaEventSenTreeps.push_back(senTreep);
m_needsInitialTrigger |= m_timingDomains.empty();
dlyvscp->varp()->setIgnorePostWrite();
ifp->addThensp(new AstAssign{flp, dlyRef(VAccess::WRITE),
new AstConst{flp, AstConst::BitFalse{}}});
activep = new AstActive{flp, "nba-event", senTreep};
activep->senTreeStorep(senTreep);
} else {
activep = new AstActive{flp, "nba-event", senTreep};
AstAlwaysPre* const prep = new AstAlwaysPre{flp};
prep->addStmtsp(new AstAssign{flp, dlyRef(VAccess::WRITE),
new AstConst{flp, AstConst::BitFalse{}}});
activep->addStmtsp(prep);
}
ifp->addThensp(newp);
m_activep->addNextHere(activep);
activep->addStmtsp(prep);
activep->addStmtsp(postp);
newp = new AstAssign{flp, dlyRef(VAccess::WRITE),
@@ -1337,6 +1433,24 @@ class DelayedVisitor final : public VNVisitor {
m_inLoop = true;
iterateChildren(nodep);
}
void visit(AstNodeCCall* const nodep) override {
iterateChildren(nodep);
// We need to visit bodies of non-inlined functions
const auto& cfuncps = m_classGraphp->getCallPossibleCFuncs(nodep);
if (cfuncps.empty()) {
visitCalledCFunc(nodep->funcp());
} else {
for (AstCFunc* const cfuncp : cfuncps) visitCalledCFunc(cfuncp);
}
}
void visit(AstCFunc* const nodep) override {
const auto& value = m_cfuncsCache(nodep);
// Check whether it was already visited by visitCalledCFunc()
if (value.m_state != CFuncCache::UNINITIALIZED) return;
VL_RESTORER(m_cfuncp);
m_cfuncp = nodep;
iterateChildren(nodep);
}
// Pre/Post logic are created here and their content need no further changes, so ignore.
void visit(AstAlwaysPre*) override {}
@@ -1347,7 +1461,10 @@ class DelayedVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
explicit DelayedVisitor(AstNetlist* nodep) { iterate(nodep); }
explicit DelayedVisitor(AstNetlist* nodep)
: m_classGraphp{V3ClassGraph::build(nodep)} {
iterate(nodep);
}
~DelayedVisitor() override {
V3Stats::addStat("NBA, variables using ShadowVar scheme", m_nSchemeShadowVar);
V3Stats::addStat("NBA, variables using ShadowVarMasked scheme", m_nSchemeShadowVarMasked);
@@ -1358,6 +1475,7 @@ public:
m_nSchemeValueQueuesPartial);
V3Stats::addStat("Optimizations, NBA flags shared", m_nSharedSetFlags);
V3Stats::addStat("Procedures needing initial NBA trigger", m_nInitialNBA);
V3Stats::addStat("Non-inlined co_awaits with SenTree", m_nonInlinedCAwaitsWithSenTree);
}
};
+4 -1
View File
@@ -110,7 +110,10 @@ class DepthVisitor final : public VNVisitor {
// (Here instead of new visitor after V3Descope just to avoid another visitor)
void needNonStaticFunc(AstNode* nodep) {
UASSERT_OBJ(m_cfuncp, nodep, "Non-static accessor not under a function");
if (m_cfuncp->isStatic()) {
// DPI export implementations are always invoked via the generic DPI dispatcher,
// which can only ever supply the symbol table pointer, never a scope-specific
// "this", so they must remain static regardless of what they contain.
if (m_cfuncp->isStatic() && !m_cfuncp->dpiExportImpl()) {
UINFO(5, "Mark non-public due to " << nodep);
m_cfuncp->isStatic(false);
}
+4 -3
View File
@@ -26,6 +26,7 @@
#include "V3Descope.h"
#include "V3ConstPool.h"
#include "V3EmitCBase.h"
VL_DEFINE_DEBUG_FUNCTIONS;
@@ -159,8 +160,7 @@ class DescopeVisitor final : public VNVisitor {
if (AstVar* const portp = VN_CAST(stmtp, Var)) {
if (portp->isIO() && !portp->isFuncReturn()) {
AstVarRef* const newp = new AstVarRef{
portp->fileline(), portp,
portp->isWritable() ? VAccess::WRITE : VAccess::READ};
portp->fileline(), portp, portp->direction().pinAccess()};
argsp = AstNode::addNext(argsp, newp);
}
}
@@ -236,7 +236,7 @@ class DescopeVisitor final : public VNVisitor {
if (varp->isFuncLocal()) {
// Reference to function locals need no self pointer
nodep->selfPointer(VSelfPointerText{VSelfPointerText::Empty()});
} else if (scopep->modp() == v3Global.rootp()->constPoolp()->modp()) {
} else if (scopep->modp()->isConstPool()) {
// Reference to constant pool value need no self pointer
nodep->selfPointer(VSelfPointerText{VSelfPointerText::Empty()});
} else {
@@ -295,5 +295,6 @@ public:
void V3Descope::descopeAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ DescopeVisitor{nodep}; } // Destruct before checking
V3ConstPool::setDescoped();
V3Global::dumpCheckGlobalTree("descope", 0, dumpTreeEitherLevel() >= 3);
}
+76 -25
View File
@@ -21,6 +21,7 @@
#include "V3Ast.h"
#include "V3EmitV.h"
#include "V3File.h"
#include "V3Stats.h"
VL_DEFINE_DEBUG_FUNCTIONS;
@@ -31,6 +32,8 @@ DfgGraph::DfgGraph(const string& name)
: m_name{name} {}
DfgGraph::~DfgGraph() {
V3Stats::addStatSum("Optimizations, DFG, temporary declarations reused",
static_cast<double>(m_tempDeclarationsReused));
forEachVertex([&](DfgVertex& vtx) { vtx.unlinkDelete(*this); });
}
@@ -98,26 +101,31 @@ void DfgGraph::mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) {
}
}
std::string DfgGraph::makeUniqueName(const std::string& prefix, size_t n) {
// Construct the tmpNameStub if we have not done so yet
if (m_tmpNameStub.empty()) {
// Use the hash of the graph name (avoid long names and non-identifiers)
const std::string hash = V3Hash{m_name}.toString();
// We need to keep every variable globally unique, and graph hashed
// names might not be, so keep a static table to track multiplicity
static std::unordered_map<std::string, uint32_t> s_multiplicity;
m_tmpNameStub += '_' + hash + '_' + std::to_string(s_multiplicity[hash]++) + '_';
}
// Assemble the globally unique name
return "__Vdfg" + prefix + m_tmpNameStub + std::to_string(n);
}
DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& name,
DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& prefix,
const DfgDataType& dtype, AstScope* scopep) {
// Create AstVar
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtype.astDtypep()};
// Add AstVar to the scope's module
scopep->modp()->addStmtsp(varp);
// AstVar declarations outlive all DFG graphs. Splitting or merging graphs
// does not transfer slots: each graph creates globally unique declarations.
TempDeclarations& temps = m_temporaries[scopep->modp()][{prefix, dtype.astDtypep()}];
const size_t slot = temps.m_scopeCounts[scopep]++;
AstVar* varp;
if (slot == temps.m_declps.size()) {
// Construct the name stub on the first new declaration in this graph
if (m_tmpNameStub.empty()) {
// Use the hash of the graph name (avoid long names and non-identifiers)
const std::string hash = V3Hash{m_name}.toString();
// Graph hashes may collide, so track multiplicity to keep names globally unique
static std::unordered_map<std::string, uint32_t> s_multiplicity;
m_tmpNameStub += '_' + hash + '_' + std::to_string(s_multiplicity[hash]++) + '_';
}
const std::string varName
= "__Vdfg" + prefix + m_tmpNameStub + std::to_string(m_tmpNameCount++);
varp = new AstVar{flp, VVarType::MODULETEMP, varName, dtype.astDtypep()};
scopep->modp()->addStmtsp(varp);
temps.m_declps.emplace_back(varp);
} else {
varp = temps.m_declps[slot];
++m_tempDeclarationsReused;
}
// Create AstVarScope
AstVarScope* const vscp = new AstVarScope{flp, scopep, varp};
// Add to scope
@@ -473,8 +481,11 @@ DfgGraph::neighborhood(const std::vector<const DfgVertex*>& vtxps, size_t n) con
//------------------------------------------------------------------------------
// DfgVertex
DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataType& dt)
: m_filelinep{flp}
DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataType& dt,
DfgEdge* inlineInputsp, uint32_t nInputs)
: m_inlineInputsp{inlineInputsp}
, m_nInputs{nInputs}
, m_filelinep{flp}
, m_dtype{dt}
, m_type{type} {
dfg.addVertex(*this);
@@ -791,19 +802,18 @@ AstScope* DfgVertex::scopep(ScopeCache& cache, bool tryResultVar) VL_MT_DISABLED
}
AstScope* const rootp = v3Global.rootp()->topScopep()->scopep();
AstScope* const constPoolp = v3Global.rootp()->constPoolp()->scopep();
// Note: the recursive invocation can cause a re-hash but that will not invalidate references
AstScope*& resultr = cache[this];
if (!resultr) {
// Mark to prevent infinite recursion on circular graphs - should never be called on such
resultr = reinterpret_cast<AstScope*>(1);
// Find scope based on sources, falling back on the root scope,
// also make sure it's not the constant pool scope, which is special.
// Find scope based on sources, falling back on the root scope. Never use a package
// scope, including the constant pool, so new variables are not created in packages.
AstScope* foundp = nullptr;
foreachSource([&](DfgVertex& src) {
AstScope* const scp = src.scopep(cache, true);
if (scp != rootp && scp != constPoolp) {
if (scp != rootp && !VN_IS(scp->modp(), Package)) {
foundp = scp;
return true;
}
@@ -829,6 +839,47 @@ void DfgVertex::unlinkDelete(DfgGraph& dfg) {
delete this;
}
//------------------------------------------------------------------------------
// DfgVertexVar
std::pair<DfgVertex*, uint32_t> DfgVertexVar::driverOfRange(uint32_t lo, uint32_t size) {
DfgVertex* const srcp = this->srcp();
// Not driven at all
if (!srcp) return {nullptr, 0};
// If volatile, can have other drivers
if (isVolatile()) return {nullptr, 0};
// Don't inline CReset
if (srcp->is<DfgCReset>()) return {nullptr, 0};
// If not driven via a splice, then it is driven whole, at the same offsets
DfgVertexSplice* const splicep = srcp->cast<DfgVertexSplice>();
if (!splicep) return {srcp, lo};
// Find the driver that covers the whole searched range, if there is a single one
const uint32_t hi = lo + size - 1;
DfgVertex* driverp = nullptr;
uint32_t driverLo = 0;
bool useDefault = defaultp();
splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLo) {
const uint32_t dHi = dLo + src.size() - 1;
// Note whether it overlaps the searched range, so the default cannot be used
if (dLo <= hi && lo <= dHi) useDefault = false;
// If it does not cover the whole searched range, move on
if (lo < dLo || dHi < hi) return false;
// Save the driver that covers the whole searched range
driverp = &src;
driverLo = dLo;
return true;
});
// If a single driver covers the searched range, it is the one
if (driverp) return {driverp, lo - driverLo};
// Otherwise the default driver is responsible for it, if nothing else overlaps it
if (useDefault) return {defaultp(), lo};
// Not driven by a single vertex
return {nullptr, 0};
}
//######################################################################
// Renders the canonical pattern S-expression for a single DfgVertex
+87 -31
View File
@@ -46,9 +46,11 @@
#include <algorithm>
#include <array>
#include <functional>
#include <map>
#include <new>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#ifndef VL_NOT_FINAL
@@ -119,6 +121,28 @@ public:
inline void relinkSrcp(DfgVertex* srcp);
};
//------------------------------------------------------------------------------
// Input edge storage of a fixed arity vertices, embedded in the vertex itself.
template <uint32_t N_Edges>
class DfgInlineEdgeStorage final {
static_assert(N_Edges > 0, "'DfgInlineEdgeStorage' must hold at least one edge");
VL_UNCOPYABLE(DfgInlineEdgeStorage);
VL_UNMOVABLE(DfgInlineEdgeStorage);
public:
union { // Union, for manual memory management, so DfgEdge need no implicit constructor
DfgEdge m_edges[N_Edges]; // The input edges of the owning vertex
};
explicit DfgInlineEdgeStorage(DfgVertex* vtxp) {
for (uint32_t i = 0; i < N_Edges; ++i) new (&m_edges[i]) DfgEdge{vtxp};
}
~DfgInlineEdgeStorage() {
for (uint32_t i = 0; i < N_Edges; ++i) m_edges[i].~DfgEdge();
}
DfgInlineEdgeStorage() = delete;
};
//------------------------------------------------------------------------------
// Dataflow graph vertex
class DfgVertex VL_NOT_FINAL {
@@ -127,10 +151,12 @@ class DfgVertex VL_NOT_FINAL {
friend class DfgVisitor;
template <typename, bool>
friend class DfgUserMap;
friend class DfgVertexVariadic;
// STATE
V3ListLinks<DfgVertex> m_links; // V3List links in the DfgGraph
std::vector<std::unique_ptr<DfgEdge>> m_inputps; // Input edges, as vector, for fast indexing
DfgEdge* const m_inlineInputsp; // Input edges stored inline in the vertex (iff fixed arity)
uint32_t m_nInputs; // Number of input edges
DfgEdge::List m_sinks; // List of sink edges of this vertex
FileLine* const m_filelinep; // Source location
@@ -160,25 +186,22 @@ public:
protected:
// CONSTRUCTOR
DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataType& dt) VL_MT_DISABLED;
DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataType& dt,
DfgEdge* inlineInputsp, uint32_t nInputs) VL_MT_DISABLED;
// Use unlinkDelete instead
virtual ~DfgVertex() VL_MT_DISABLED = default;
// Create a new input edge and return it
DfgEdge* newInput() {
m_inputps.emplace_back(new DfgEdge{this});
return m_inputps.back().get();
}
private:
// Get input edge 'i'
inline DfgEdge* inputEdgep(size_t i) const;
public:
// Get input 'i'
DfgVertex* inputp(size_t i) const { return m_inputps[i]->srcp(); }
DfgVertex* inputp(size_t i) const { return inputEdgep(i)->srcp(); }
// Relink input 'i'
void inputp(size_t i, DfgVertex* vtxp) { m_inputps[i]->relinkSrcp(vtxp); }
void inputp(size_t i, DfgVertex* vtxp) { inputEdgep(i)->relinkSrcp(vtxp); }
// The number of inputs this vertex has. Some might be unconnected.
size_t nInputs() const { return m_inputps.size(); }
// Unlink all inputs and reset to no inputs - use very carefully
void resetInputs() { m_inputps.clear(); }
size_t nInputs() const { return m_nInputs; }
// The type of this vertex
VDfgType type() const { return m_type; }
@@ -247,9 +270,12 @@ public:
// Calls given function 'f' for each source vertex of this vertex. If 'f'
// returns true, further sources are not iterated and this method returns
// true itself. Unconnected source edges are not iterated.
bool foreachSource(std::function<bool(DfgVertex&)> f) {
for (const std::unique_ptr<DfgEdge>& edgep : m_inputps) {
if (DfgVertex* const srcp = edgep->srcp()) {
template <typename T_Callable>
bool foreachSource(T_Callable&& f) {
static_assert(vlstd::is_invocable_r<bool, T_Callable, DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(DfgVertex&)'");
for (size_t i = 0; i < m_nInputs; ++i) {
if (DfgVertex* const srcp = inputEdgep(i)->srcp()) {
if (f(*srcp)) return true;
}
}
@@ -259,9 +285,13 @@ public:
// Calls given function 'f' for each source vertex of this vertex. If 'f'
// returns true, further sources are not iterated and this method returns
// true itself. Unconnected source edges are not iterated.
bool foreachSource(std::function<bool(const DfgVertex&)> f) const {
for (const std::unique_ptr<DfgEdge>& edgep : m_inputps) {
if (DfgVertex* const srcp = edgep->srcp()) {
template <typename T_Callable>
bool foreachSource(T_Callable&& f) const {
static_assert(
vlstd::is_invocable_r<bool, T_Callable, const DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(const DfgVertex&)'");
for (size_t i = 0; i < m_nInputs; ++i) {
if (const DfgVertex* const srcp = inputEdgep(i)->srcp()) {
if (f(*srcp)) return true;
}
}
@@ -272,7 +302,10 @@ public:
// returns true, further sinks are not iterated and this method returns
// true itself. Unlinking/deleting the given sink during iteration is safe,
// but not other sinks of this vertex.
bool foreachSink(std::function<bool(DfgVertex&)> f) {
template <typename T_Callable>
bool foreachSink(T_Callable&& f) {
static_assert(vlstd::is_invocable_r<bool, T_Callable, DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(DfgVertex&)'");
for (const DfgEdge* const edgep : m_sinks.unlinkable()) {
if (f(*edgep->dstp())) return true;
}
@@ -282,7 +315,11 @@ public:
// Calls given function 'f' for each sink vertex of this vertex. If 'f'
// returns true, further sinks are not iterated and this method returns
// true itself.
bool foreachSink(std::function<bool(const DfgVertex&)> f) const {
template <typename T_Callable>
bool foreachSink(T_Callable&& f) const {
static_assert(
vlstd::is_invocable_r<bool, T_Callable, const DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(const DfgVertex&)'");
for (const DfgEdge& edge : m_sinks) {
if (f(*edge.dstp())) return true;
}
@@ -393,6 +430,19 @@ class DfgGraph final {
size_t m_size = 0; // Number of vertices in the graph
const std::string m_name; // Name of graph - need not be unique
std::string m_tmpNameStub{""}; // Name stub for temporary variables - computed lazy
size_t m_tmpNameCount = 0; // Sequence number for newly created temporary declarations
// Slots are local to this graph and keyed by module, prefix and type.
// Different prefixes may carry different AstVar attributes, but temporaries
// with the same prefix may share an AstVar, so they must have identical ones.
// Each scope consumes each slot at most once.
struct TempDeclarations final {
std::map<AstScope*, size_t> m_scopeCounts; // Next slot for each instance
std::vector<AstVar*> m_declps; // Declarations indexed by slot
};
std::map<AstNodeModule*, std::map<std::pair<std::string, AstNodeDType*>, TempDeclarations>>
m_temporaries; // Shared slots indexed by module, purpose, and type
uint64_t m_tempDeclarationsReused = 0; // Declarations shared across instance scopes
// The only way to access thes is via DfgUserMap, so mutable is appropriate,
// the map can change while the graph is const.
@@ -491,14 +541,12 @@ public:
// DfgVertexVar instances representing the same Ast variable are unified.
void mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) VL_MT_DISABLED;
// Genarete a unique name. The provided 'prefix' and 'n' values will be part of the name, and
// must be unique (as a pair) in each invocation for this graph.
std::string makeUniqueName(const std::string& prefix, size_t n) VL_MT_DISABLED;
// Create a new variable with the given name and data type. For a Scoped
// Dfg, the AstScope where the corresponding AstVarScope will be inserted
// must be provided
DfgVertexVar* makeNewVar(FileLine*, const std::string& name, const DfgDataType&,
// Create a new scoped variable. Instances of a module share temporary
// declarations of the same prefix and type, but have independent storage.
// Each scope uses a declaration at most once; new declarations get unique names.
// As the AstVar may be shared, callers must set identical AstVar attributes
// on all temporaries created with the same prefix.
DfgVertexVar* makeNewVar(FileLine*, const std::string& prefix, const DfgDataType&,
AstScope*) VL_MT_DISABLED;
// Split this graph into individual components (unique sub-graphs with no edges between them).
@@ -558,14 +606,14 @@ namespace V3Dfg {
// Returns true if variable can be represented in the graph
inline bool isSupported(const AstVarScope* vscp) {
const AstNodeModule* const modp = vscp->scopep()->modp();
if (VN_IS(modp, Module)) {
// Regular module supported
if (VN_IS(modp, Module) || VN_IS(modp, Package)) {
// Regular modules and packages supported
} else if (const AstIface* const ifacep = VN_CAST(modp, Iface)) {
// Interfaces supported if there are no virtual interfaces for
// them, otherwise they cannot be resovled statically.
if (ifacep->hasVirtualRef()) return false;
} else {
return false; // Anything else (package, class, etc) not supported
return false; // Anything else (class, etc) not supported
}
if (DfgVertexVar::hasRWRefs(vscp)) return false; // Referenced via READWRITE references
// Check the AstVar
@@ -811,6 +859,14 @@ void DfgEdge::relinkSrcp(DfgVertex* srcp) {
// DfgVertex {{{
DfgEdge* DfgVertex::inputEdgep(size_t i) const {
UDEBUGONLY(UASSERT_OBJ(i < m_nInputs, this, "Input index out of range"););
if (VL_LIKELY(m_inlineInputsp)) return m_inlineInputsp + i;
// 'm_inlineInputsp' is null exactly for a DfgVertexVariadic
UDEBUGONLY(UASSERT_OBJ(is<DfgVertexVariadic>(), this, "Vertex without input edge storage"););
return static_cast<const DfgVertexVariadic*>(this)->m_edgeps[i].get();
}
bool DfgVertex::isCheaperThanLoad() const {
// Constants
if (is<DfgConst>()) return true;
+242 -186
View File
@@ -152,9 +152,9 @@ class TraceDriver final : public DfgVisitor {
// TYPES
// Key for caching the result of a trace
struct CacheKey final {
DfgVertex* m_vtxp;
uint32_t m_lsb;
uint32_t m_msb;
DfgVertex* m_vtxp; // Vertex being traced
uint32_t m_lsb; // LSB of the range within m_vtxp being traced
uint32_t m_msb; // MSB of the range within m_vtxp being traced
CacheKey() = delete;
CacheKey(DfgVertex* vtxp, uint32_t lsb, uint32_t msb)
@@ -187,11 +187,20 @@ class TraceDriver final : public DfgVisitor {
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!
std::vector<DfgVertex**> m_pendingps; // Pending pass-through cache entries across trace calls
// Result of tracing the currently visited Vertex. Use RETURN_RESULT below!
DfgVertex* m_resp = nullptr;
DfgVertex* m_defaultp = nullptr; // When tracing a variable, this is its 'defaultp', if any
// Set instead of 'm_resp' when the result of tracing the currently visited Vertex is
// exactly the result of tracing 'm_tailp' at the updated [m_msb:m_lsb]. 'trace'
// resolves these iteratively. Use RETURN_RESULT_TAIL below!
DfgVertex* m_tailp = nullptr;
// When tracing a splice, this is the corresponding 'defaultp' of the variable it drives
DfgVertex* m_defaultp = nullptr;
DfgVertex* m_splicep = nullptr; // The splice vertex being traced, just for assertions
// Result cache for reusing already traced vertices
std::unordered_map<CacheKey, DfgVertex*, CacheKey::Hash, CacheKey::Equal> m_cache;
// Cache for boundary vertices (where m_sccInfo.get(*vtxp) != m_component)
std::unordered_map<CacheKey, DfgVertex*, CacheKey::Hash, CacheKey::Equal> m_boundaryCache;
#ifdef VL_DEBUG
std::ofstream m_lineCoverageFile; // Line coverage file, just for testing
@@ -230,12 +239,10 @@ class TraceDriver final : public DfgVisitor {
// Create temporary capable of holding the result of 'vtxp'
DfgVertexVar* createTmp(const char* prefix, DfgVertex* vtxp) {
AstNode* nodep = v3Global.rootp();
const std::string name = m_dfg.makeUniqueName(prefix, nodep->user2Inc());
FileLine* const flp = vtxp->fileline();
DfgVertex::ScopeCache scopeCache;
AstScope* const scopep = vtxp->scopep(scopeCache);
DfgVertexVar* const varp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep);
DfgVertexVar* const varp = m_dfg.makeNewVar(flp, prefix, vtxp->dtype(), scopep);
varp->vscp()->varp()->isInternal(true);
varp->tmpForp(varp->vscp());
m_sccInfo.add(*varp, 0);
@@ -243,60 +250,108 @@ class TraceDriver final : public DfgVisitor {
}
// 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");
DfgVertex* trace(DfgVertex* vtxp, uint32_t msb, uint32_t lsb) {
// Many vertices just pass the traced bit range through to one of their sources
// (see RETURN_RESULT_TAIL). Chains of such vertices can be tens of thousands of
// vertices long in large designs (e.g.: a wide concatenation), so resolve them
// in this loop instead of by recursion, which would overflow the C++ stack.
// 'm_pendingps' holds the cache entries of the vertices on the current chain. They
// all yield the same result, which is filled in when the chain is resolved.
// Note: references into 'm_cache' are stable across the insertions below.
DfgVertex* const startVtxp = vtxp;
const uint32_t startMsb = msb;
const uint32_t startLsb = lsb;
const size_t pendingBegin = m_pendingps.size();
DfgVertex* resp = nullptr;
// Get the cache entry, which is the resulting driver that is not part of
// the same component as vtxp
DfgVertex*& respr = m_cache
.emplace(std::piecewise_construct, //
std::forward_as_tuple(vtxp, msb, lsb), //
std::forward_as_tuple(nullptr))
.first->second;
while (true) {
UASSERT_OBJ(vtxp->isPacked(), vtxp, "Can only trace packed type vertices");
UASSERT_OBJ(vtxp->size() > msb, vtxp, "Traced Vertex too narrow");
UASSERT_OBJ(!m_defaultp || vtxp == m_splicep, vtxp, "Tracing wrong vertex");
// Trace the vertex
if (respr) {
// If already traced this vtxp/msb/lsb, just use the result.
// This is important to avoid combinatorial explosion when the
// same sub-expression is needed multiple times.
} else if (m_sccInfo.get(*vtxp) != m_component) {
// If the currently traced vertex is in a different component,
// then we found what we were looking for.
respr = vtxp;
// If the result is a splice, we need to insert a temporary for it
// as a splice cannot be fed into arbitray logic
if (DfgVertexSplice* const splicep = respr->cast<DfgVertexSplice>()) {
DfgVertexVar* const tmpp = createTmp("TraceDriver", splicep);
// Note: we can't do 'splicep->replaceWith(tmpp)', as other
// variable sinks of the splice might have a defaultp driver.
tmpp->srcp(splicep);
respr = tmpp;
// then we found what we were looking for. But if it's a splice
// with a corresponding default, we need to keep going as the
// splice does not fully define the value we are seeking.
if (m_sccInfo.get(*vtxp) != m_component && !m_defaultp) {
DfgVertex*& respr
= m_boundaryCache
.emplace(std::piecewise_construct, std::forward_as_tuple(vtxp, msb, lsb),
std::forward_as_tuple(nullptr))
.first->second;
if (!respr) {
respr = vtxp;
// If the result is a splice, we need to insert a temporary for it
// as a splice cannot be fed into arbitray logic
if (DfgVertexSplice* const splicep = respr->cast<DfgVertexSplice>()) {
DfgVertexVar* const tmpp = createTmp("TraceDriver", splicep);
// Note: we can't do 'splicep->replaceWith(tmpp)', as other
// variable sinks of the splice might have a defaultp driver.
tmpp->srcp(splicep);
respr = tmpp;
}
// Apply a Sel to extract the relevant bits if only a part is needed
if (msb != respr->width() - 1 || lsb != 0) {
DfgSel* const selp = make<DfgSel>(respr, msb - lsb + 1);
selp->fromp(respr);
selp->lsb(lsb);
respr = selp;
}
}
resp = respr;
break;
}
// Apply a Sel to extract the relevant bits if only a part is needed
if (msb != respr->width() - 1 || lsb != 0) {
DfgSel* const selp = make<DfgSel>(respr, msb - lsb + 1);
selp->fromp(respr);
selp->lsb(lsb);
respr = selp;
// Normal cache for intra-component traces
DfgVertex*& respr = m_cache
.emplace(std::piecewise_construct, //
std::forward_as_tuple(vtxp, msb, lsb), //
std::forward_as_tuple(nullptr))
.first->second;
// Trace the vertex
if (respr) {
// If already traced this vtxp/msb/lsb, just use the result.
// This is important to avoid combinatorial explosion when the
// same sub-expression is needed multiple times.
} else {
// Otherwise visit the vertex to trace it
VL_RESTORER(m_msb);
VL_RESTORER(m_lsb);
VL_RESTORER_CLEAR(m_idxs);
VL_RESTORER(m_resp);
VL_RESTORER(m_tailp);
m_msb = msb;
m_lsb = lsb;
m_resp = nullptr;
m_tailp = nullptr;
iterate(vtxp);
UASSERT_OBJ((m_resp != nullptr) != (m_tailp != nullptr), vtxp,
"Expected exactly one trace result");
if (m_tailp) {
// This vertex just passes the range through, so continue with its
// source, at the updated range. Its cache entry is filled in when
// the chain is resolved.
m_pendingps.push_back(&respr);
vtxp = m_tailp;
msb = m_msb;
lsb = m_lsb;
continue;
}
respr = m_resp;
}
} else {
// 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;
m_resp = nullptr;
iterate(vtxp);
respr = m_resp;
resp = respr;
break;
}
// We only ever trace drivers of bits that are known to be independent
// of the cycles, so we should always be able to find an acyclic driver.
UASSERT_OBJ(respr, vtxp, "Tracing driver failed for " << vtxp->typeName());
UASSERT_OBJ(respr->width() == (msb - lsb + 1), vtxp, "Wrong result width");
return respr;
UASSERT_OBJ(resp, startVtxp, "Tracing driver failed for " << startVtxp->typeName());
UASSERT_OBJ(resp->width() == (startMsb - startLsb + 1), startVtxp, "Wrong result width");
// Every vertex on the pass-through chain resolves to the same driver
for (size_t i = pendingBegin; i < m_pendingps.size(); ++i) *m_pendingps[i] = resp;
m_pendingps.resize(pendingBegin);
return resp;
}
// Trace drivers of the given array vertex, at the current m_idxs, m_msb, m_lsb.
@@ -375,27 +430,59 @@ class TraceDriver final : public DfgVisitor {
return resp;
}
// Use this macro to set the result in 'visit' methods. This also emits
// Use this macro to return the result from 'visit' methods. This also emits
// a line to m_lineCoverageFile for testing.
// TODO: Use C++20 std::source_location instead of a macro
#ifdef VL_DEBUG
#define SET_RESULT(vtxp) \
#define TRACE_RESULT_COVER() \
do { \
m_resp = vtxp; \
if (VL_UNLIKELY(m_lineCoverageFile.is_open())) m_lineCoverageFile << __LINE__ << '\n'; \
} while (false)
#else
#define SET_RESULT(vtxp) m_resp = vtxp;
#define TRACE_RESULT_COVER() \
do { \
} while (false)
#endif
#define RETURN_RESULT(vtxp) \
do { \
m_resp = (vtxp); \
TRACE_RESULT_COVER(); \
return; \
} while (false)
// Use this macro in 'visit' methods when the result is exactly the result of tracing
// 'srcp' at [smsb:slsb]. 'trace' then continues iteratively instead of recursing, so
// arbitrarily long chains of such pass-through vertices do not consume C++ stack.
// Note this overwrites 'm_msb'/'m_lsb', which is fine as the visitor returns
// immediately. Only use this when no state set up by the visitor (e.g.: 'm_defaultp')
// has to stay live while 'srcp' is traced, as that happens after the visitor returned.
#define RETURN_RESULT_TAIL(srcp, smsb, slsb) \
do { \
/* Evaluate all arguments before assigning, as they usually read 'm_msb'/'m_lsb' */ \
DfgVertex* const tailp = (srcp); \
const uint32_t tailMsb = (smsb); \
const uint32_t tailLsb = (slsb); \
m_tailp = tailp; \
m_msb = tailMsb; \
m_lsb = tailLsb; \
TRACE_RESULT_COVER(); \
return; \
} while (false)
// VISITORS
void visit(DfgVertex* vtxp) override { // LCOV_EXCL_START
vtxp->v3fatalSrc("TraceDriver - Unhandled vertex type: " << vtxp->typeName());
} // LCOV_EXCL_STOP
void visit(DfgSplicePacked* vtxp) override {
UASSERT_OBJ(m_splicep == vtxp, vtxp, "Unexpected trace of DfgSplicePacked");
DfgVertex* defaultp = m_defaultp;
m_defaultp = nullptr;
m_splicep = nullptr;
struct Driver final {
DfgVertex* m_vtxp;
DfgVertex* m_vtxp; // Vertex driving this range
uint32_t m_lsb; // LSB of driven range (internal, not Verilog)
uint32_t m_msb; // MSB of driven range (internal, not Verilog)
Driver() = delete;
@@ -407,25 +494,27 @@ class TraceDriver final : public DfgVisitor {
std::vector<Driver> drivers;
// Look at all the drivers, one might cover the whole range, but also gather all drivers
bool tryWholeDefault = m_defaultp;
const bool done = vtxp->foreachDriver([&](DfgVertex& src, uint32_t lsb) {
bool tryWholeDefault = defaultp;
DfgVertex* coverp = nullptr; // Driver covering the whole searched range, if any
uint32_t coverLsb = 0; // LSB of the range driven by 'coverp'
vtxp->foreachDriver([&](DfgVertex& src, uint32_t lsb) {
const uint32_t msb = lsb + src.width() - 1;
drivers.emplace_back(&src, lsb, msb);
// Check if this driver covers any of the bits, then we can't use whole default
if (m_msb >= lsb && msb >= m_lsb) tryWholeDefault = false;
// If it does not cover the whole searched bit range, move on
if (m_lsb < lsb || msb < m_msb) return false;
// Driver covers whole search range, trace that and we are done
SET_RESULT(trace(&src, m_msb - lsb, m_lsb - lsb));
// Driver covers whole search range, we are done gathering
coverp = &src;
coverLsb = lsb;
return true;
});
if (done) return;
// Trace the driver covering the whole searched range, if there is one
if (coverp) RETURN_RESULT_TAIL(coverp, m_msb - coverLsb, m_lsb - coverLsb);
// Trace the default driver if no other drivers cover the searched range
if (tryWholeDefault) {
SET_RESULT(trace(m_defaultp, m_msb, m_lsb));
return;
}
if (tryWholeDefault) RETURN_RESULT_TAIL(defaultp, m_msb, m_lsb);
// Hard case: We need to combine multiple drivers to produce the searched bit range
@@ -444,8 +533,8 @@ class TraceDriver final : public DfgVisitor {
if (driver.m_lsb > m_msb) break;
// Gap below this driver, trace default to fill it
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, lsb));
UASSERT_OBJ(defaultp, vtxp, "Should have a default driver if needs tracing");
termps.emplace_back(trace(defaultp, driver.m_lsb - 1, lsb));
lsb = driver.m_lsb;
}
// Driver covers searched range, pick the needed/available bits
@@ -454,8 +543,8 @@ class TraceDriver final : public DfgVisitor {
lsb = lim + 1;
}
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, lsb));
UASSERT_OBJ(defaultp, vtxp, "Should have a default driver if needs tracing");
termps.emplace_back(trace(defaultp, m_msb, lsb));
}
// The earlier cheks cover the case when either a whole driver or the default covers
@@ -471,33 +560,46 @@ class TraceDriver final : public DfgVisitor {
catp->lhsp(termp);
resp = catp;
}
SET_RESULT(resp);
RETURN_RESULT(resp);
}
void visit(DfgSpliceArray* vtxp) override {
UASSERT_OBJ(m_splicep == vtxp, vtxp, "Unexpected trace of DfgSpliceArray");
DfgVertex* const defaultp = m_defaultp;
m_defaultp = nullptr;
m_splicep = nullptr;
// Explicit per-element driver (a UnitArray wrapping the element value)
if (DfgVertex* const driverp = vtxp->driverAt(m_idxs.back())) {
const uint32_t idx = m_idxs.back();
if (DfgVertex* const driverp = vtxp->driverAt(idx)) {
DfgVertex* const srcp = driverp->as<DfgUnitArray>()->srcp();
if (srcp->is<DfgVertexSplice>()) {
// Partial-element propagation is rejected during synthesis.
UASSERT_OBJ(!defaultp, vtxp, "Array default with partial element driver");
m_splicep = srcp;
}
// Consume this index, then trace the element value
SET_RESULT(tracePopIdx(driverp->as<DfgUnitArray>()->srcp()));
return;
RETURN_RESULT(tracePopIdx(srcp));
}
// 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));
// An element not driven explicitly comes from the default array at the same index.
UASSERT_OBJ(defaultp, vtxp, "Independent array element should have a driver or default");
RETURN_RESULT(traceSameIdx(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();
DfgVertex* const drvp = vtxp->srcp() ? vtxp->srcp() : m_defaultp;
DfgVertex* const srcp = vtxp->srcp();
DfgVertex* const defaultp = vtxp->defaultp();
DfgVertex* const drvp = srcp ? srcp : defaultp;
// If we are about to trace a splice, set the defaultp to the corresponding default
if (srcp && srcp->is<DfgVertexSplice>()) {
m_defaultp = defaultp;
m_splicep = srcp;
}
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));
RETURN_RESULT(m_idxs.empty() ? trace(drvp, m_msb, m_lsb) : traceSameIdx(drvp));
}
void visit(DfgArraySel* vtxp) override {
@@ -505,8 +607,7 @@ class TraceDriver final : public DfgVisitor {
// 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;
RETURN_RESULT(tracePushIdx(vtxp->fromp(), idxp->toU32()));
}
// If index is not constant, independence was proven only if the 'fromp' is
@@ -519,13 +620,13 @@ 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(selp);
RETURN_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()));
RETURN_RESULT(tracePopIdx(vtxp->srcp()));
}
void visit(DfgConcat* vtxp) override {
@@ -533,20 +634,14 @@ class TraceDriver final : public DfgVisitor {
DfgVertex* const lhsp = vtxp->lhsp();
const uint32_t rWidth = rhsp->width();
// If the traced bits are wholly in the RHS
if (rWidth > m_msb) {
SET_RESULT(trace(rhsp, m_msb, m_lsb));
return;
}
if (rWidth > m_msb) RETURN_RESULT_TAIL(rhsp, m_msb, m_lsb);
// If the traced bits are wholly in the LHS
if (m_lsb >= rWidth) {
SET_RESULT(trace(lhsp, m_msb - rWidth, m_lsb - rWidth));
return;
}
if (m_lsb >= rWidth) RETURN_RESULT_TAIL(lhsp, m_msb - rWidth, m_lsb - rWidth);
// The traced bit spans both sides, trace both
DfgConcat* const resp = make<DfgConcat>(vtxp, m_msb - m_lsb + 1);
resp->rhsp(trace(rhsp, rWidth - 1, m_lsb));
resp->lhsp(trace(lhsp, m_msb - rWidth, 0));
SET_RESULT(resp);
RETURN_RESULT(resp);
}
void visit(DfgRep* vtxp) override {
@@ -559,91 +654,75 @@ class TraceDriver final : public DfgVisitor {
DfgSel* const resp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
resp->fromp(repp);
resp->lsb(m_lsb);
SET_RESULT(resp);
return;
RETURN_RESULT(resp);
}
// If the requested bits are within the same repliacted word
if (m_msb / sWidth == m_lsb / sWidth) {
SET_RESULT(trace(srcp, m_msb % sWidth, m_lsb % sWidth));
return;
RETURN_RESULT_TAIL(srcp, m_msb % sWidth, m_lsb % sWidth);
}
// The requested bits span two replicated words
DfgConcat* const resp = make<DfgConcat>(vtxp, m_msb - m_lsb + 1);
resp->rhsp(trace(srcp, sWidth - 1, m_lsb % sWidth));
resp->lhsp(trace(srcp, m_msb % sWidth, 0));
SET_RESULT(resp);
RETURN_RESULT(resp);
}
void visit(DfgExtend* vtxp) override {
DfgVertex* const srcp = vtxp->srcp();
const uint32_t sWidth = srcp->width();
// If the traced bits are wholly in the input
if (sWidth > m_msb) {
SET_RESULT(trace(srcp, m_msb, m_lsb));
return;
}
if (sWidth > m_msb) RETURN_RESULT_TAIL(srcp, m_msb, m_lsb);
// If the traced bits are wholly in the extension
if (m_lsb >= sWidth) {
SET_RESULT(make<DfgConst>(vtxp, m_msb - m_lsb + 1));
return;
}
if (m_lsb >= sWidth) RETURN_RESULT(make<DfgConst>(vtxp, m_msb - m_lsb + 1));
// The traced bits span both sides
DfgExtend* const resp = make<DfgExtend>(vtxp, m_msb - m_lsb + 1);
resp->srcp(trace(srcp, sWidth - 1, m_lsb));
SET_RESULT(resp);
RETURN_RESULT(resp);
}
void visit(DfgExtendS* vtxp) override {
DfgVertex* const srcp = vtxp->srcp();
const uint32_t sWidth = srcp->width();
// If the traced bits are wholly in the input
if (sWidth > m_msb) {
SET_RESULT(trace(srcp, m_msb, m_lsb));
return;
}
if (sWidth > m_msb) RETURN_RESULT_TAIL(srcp, m_msb, m_lsb);
// If the traced bits are wholly in the extension
if (m_lsb >= sWidth) {
DfgVertex* const sp = trace(srcp, sWidth - 1, sWidth - 1);
if (m_msb == m_lsb) {
SET_RESULT(sp);
} else {
DfgExtendS* const resp = make<DfgExtendS>(vtxp, m_msb - m_lsb + 1);
resp->srcp(sp);
SET_RESULT(resp);
}
return;
if (m_msb == m_lsb) RETURN_RESULT_TAIL(srcp, sWidth - 1, sWidth - 1);
DfgExtendS* const resp = make<DfgExtendS>(vtxp, m_msb - m_lsb + 1);
resp->srcp(trace(srcp, sWidth - 1, sWidth - 1));
RETURN_RESULT(resp);
}
// The traced bits span both sides
DfgExtendS* const resp = make<DfgExtendS>(vtxp, m_msb - m_lsb + 1);
resp->srcp(trace(srcp, sWidth - 1, m_lsb));
SET_RESULT(resp);
RETURN_RESULT(resp);
}
void visit(DfgSel* vtxp) override {
const uint32_t lsb = vtxp->lsb();
SET_RESULT(trace(vtxp->srcp(), m_msb + lsb, m_lsb + lsb));
RETURN_RESULT_TAIL(vtxp->srcp(), m_msb + lsb, m_lsb + lsb);
}
void visit(DfgNot* vtxp) override {
DfgNot* const resp = make<DfgNot>(vtxp, m_msb - m_lsb + 1);
resp->srcp(trace(vtxp->srcp(), m_msb, m_lsb));
SET_RESULT(resp);
RETURN_RESULT(resp);
}
void visit(DfgAnd* vtxp) override { SET_RESULT(traceBitwiseBinary(vtxp)); }
void visit(DfgOr* vtxp) override { SET_RESULT(traceBitwiseBinary(vtxp)); }
void visit(DfgXor* vtxp) override { SET_RESULT(traceBitwiseBinary(vtxp)); }
void visit(DfgAdd* vtxp) override { SET_RESULT(traceAddSub(vtxp)); }
void visit(DfgSub* vtxp) override { SET_RESULT(traceAddSub(vtxp)); }
void visit(DfgRedAnd* vtxp) override { SET_RESULT(traceReduction(vtxp)); }
void visit(DfgRedOr* vtxp) override { SET_RESULT(traceReduction(vtxp)); }
void visit(DfgRedXor* vtxp) override { SET_RESULT(traceReduction(vtxp)); }
void visit(DfgEq* vtxp) override { SET_RESULT(traceCmp(vtxp)); }
void visit(DfgNeq* vtxp) override { SET_RESULT(traceCmp(vtxp)); }
void visit(DfgLt* vtxp) override { SET_RESULT(traceCmp(vtxp)); }
void visit(DfgLte* vtxp) override { SET_RESULT(traceCmp(vtxp)); }
void visit(DfgGt* vtxp) override { SET_RESULT(traceCmp(vtxp)); }
void visit(DfgGte* vtxp) override { SET_RESULT(traceCmp(vtxp)); }
void visit(DfgAnd* vtxp) override { RETURN_RESULT(traceBitwiseBinary(vtxp)); }
void visit(DfgOr* vtxp) override { RETURN_RESULT(traceBitwiseBinary(vtxp)); }
void visit(DfgXor* vtxp) override { RETURN_RESULT(traceBitwiseBinary(vtxp)); }
void visit(DfgAdd* vtxp) override { RETURN_RESULT(traceAddSub(vtxp)); }
void visit(DfgSub* vtxp) override { RETURN_RESULT(traceAddSub(vtxp)); }
void visit(DfgRedAnd* vtxp) override { RETURN_RESULT(traceReduction(vtxp)); }
void visit(DfgRedOr* vtxp) override { RETURN_RESULT(traceReduction(vtxp)); }
void visit(DfgRedXor* vtxp) override { RETURN_RESULT(traceReduction(vtxp)); }
void visit(DfgEq* vtxp) override { RETURN_RESULT(traceCmp(vtxp)); }
void visit(DfgNeq* vtxp) override { RETURN_RESULT(traceCmp(vtxp)); }
void visit(DfgLt* vtxp) override { RETURN_RESULT(traceCmp(vtxp)); }
void visit(DfgLte* vtxp) override { RETURN_RESULT(traceCmp(vtxp)); }
void visit(DfgGt* vtxp) override { RETURN_RESULT(traceCmp(vtxp)); }
void visit(DfgGte* vtxp) override { RETURN_RESULT(traceCmp(vtxp)); }
void visit(DfgShiftRS* vtxp) override {
DfgVertex* const lhsp = vtxp->lhsp();
@@ -653,34 +732,28 @@ class TraceDriver final : public DfgVisitor {
const uint32_t lowerWidth = shiftAmnt > vtxp->width() ? 0 : vtxp->width() - shiftAmnt;
// If the traced bits are wholly in the input
if (lowerWidth > m_msb) {
SET_RESULT(trace(lhsp, m_msb + shiftAmnt, m_lsb + shiftAmnt));
return;
RETURN_RESULT_TAIL(lhsp, m_msb + shiftAmnt, m_lsb + shiftAmnt);
}
// If the traced bits are wholly in the extension
if (m_lsb >= lowerWidth) {
DfgExtendS* const resp = make<DfgExtendS>(vtxp, m_msb - m_lsb + 1);
resp->srcp(trace(lhsp, lhsp->width() - 1, lhsp->width() - 1));
SET_RESULT(resp);
return;
RETURN_RESULT(resp);
}
// The traced bits span both sides
DfgExtendS* const resp = make<DfgExtendS>(vtxp, m_msb - m_lsb + 1);
resp->srcp(trace(lhsp, lowerWidth - 1 + shiftAmnt, m_lsb + shiftAmnt));
SET_RESULT(resp);
return;
RETURN_RESULT(resp);
}
DfgShiftRS* const shiftrsp = make<DfgShiftRS>(vtxp, vtxp->lhsp()->width() - m_lsb);
shiftrsp->rhsp(trace(vtxp->rhsp(), vtxp->rhsp()->width() - 1, 0));
shiftrsp->lhsp(trace(vtxp->lhsp(), vtxp->lhsp()->width() - 1, m_lsb));
if (m_msb == vtxp->lhsp()->width() - 1) {
SET_RESULT(shiftrsp);
return;
}
if (m_msb == vtxp->lhsp()->width() - 1) RETURN_RESULT(shiftrsp);
DfgSel* const selp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
selp->fromp(shiftrsp);
selp->lsb(0);
SET_RESULT(selp);
RETURN_RESULT(selp);
}
void visit(DfgShiftR* vtxp) override {
@@ -691,32 +764,24 @@ class TraceDriver final : public DfgVisitor {
const uint32_t lowerWidth = shiftAmnt > vtxp->width() ? 0 : vtxp->width() - shiftAmnt;
// If the traced bits are wholly in the input
if (lowerWidth > m_msb) {
SET_RESULT(trace(lhsp, m_msb + shiftAmnt, m_lsb + shiftAmnt));
return;
RETURN_RESULT_TAIL(lhsp, m_msb + shiftAmnt, m_lsb + shiftAmnt);
}
// If the traced bits are wholly in the extension
if (m_lsb >= lowerWidth) {
SET_RESULT(make<DfgConst>(vtxp, m_msb - m_lsb + 1));
return;
}
if (m_lsb >= lowerWidth) RETURN_RESULT(make<DfgConst>(vtxp, m_msb - m_lsb + 1));
// The traced bits span both sides
DfgExtend* const resp = make<DfgExtend>(vtxp, m_msb - m_lsb + 1);
resp->srcp(trace(lhsp, lowerWidth - 1 + shiftAmnt, m_lsb + shiftAmnt));
SET_RESULT(resp);
return;
RETURN_RESULT(resp);
}
DfgShiftR* const shiftrp = make<DfgShiftR>(vtxp, vtxp->lhsp()->width() - m_lsb);
shiftrp->rhsp(trace(vtxp->rhsp(), vtxp->rhsp()->width() - 1, 0));
shiftrp->lhsp(trace(vtxp->lhsp(), vtxp->lhsp()->width() - 1, m_lsb));
if (m_msb == vtxp->lhsp()->width() - 1) {
SET_RESULT(shiftrp);
return;
}
if (m_msb == vtxp->lhsp()->width() - 1) RETURN_RESULT(shiftrp);
DfgSel* const selp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
selp->fromp(shiftrp);
selp->lsb(0);
SET_RESULT(selp);
RETURN_RESULT(selp);
}
void visit(DfgShiftL* vtxp) override {
@@ -727,33 +792,25 @@ class TraceDriver final : public DfgVisitor {
const uint32_t lowerWidth = shiftAmnt > vtxp->width() ? vtxp->width() : shiftAmnt;
// If the traced bits are wholly in the input
if (m_lsb >= lowerWidth) {
SET_RESULT(trace(lhsp, m_msb - shiftAmnt, m_lsb - shiftAmnt));
return;
RETURN_RESULT_TAIL(lhsp, m_msb - shiftAmnt, m_lsb - shiftAmnt);
}
// If the traced bits are wholly in the extension
if (lowerWidth > m_msb) {
SET_RESULT(make<DfgConst>(vtxp, m_msb - m_lsb + 1));
return;
}
if (lowerWidth > m_msb) RETURN_RESULT(make<DfgConst>(vtxp, m_msb - m_lsb + 1));
// The traced bits span both sides
DfgConcat* const resp = make<DfgConcat>(vtxp, m_msb - m_lsb + 1);
resp->rhsp(make<DfgConst>(vtxp, resp->width() - (m_msb - lowerWidth + 1)));
resp->lhsp(trace(lhsp, m_msb - shiftAmnt, lowerWidth - shiftAmnt));
SET_RESULT(resp);
return;
RETURN_RESULT(resp);
}
DfgShiftL* const shiftlp = make<DfgShiftL>(vtxp, m_msb + 1);
shiftlp->rhsp(trace(vtxp->rhsp(), vtxp->rhsp()->width() - 1, 0));
shiftlp->lhsp(trace(vtxp->lhsp(), m_msb, 0));
if (m_lsb == 0) {
SET_RESULT(shiftlp);
return;
}
if (m_lsb == 0) RETURN_RESULT(shiftlp);
DfgSel* const selp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
selp->fromp(shiftlp);
selp->lsb(m_lsb);
SET_RESULT(selp);
RETURN_RESULT(selp);
}
void visit(DfgCond* vtxp) override {
@@ -761,7 +818,7 @@ class TraceDriver final : public DfgVisitor {
resp->condp(trace(vtxp->condp(), vtxp->condp()->width() - 1, 0));
resp->thenp(trace(vtxp->thenp(), m_msb, m_lsb));
resp->elsep(trace(vtxp->elsep(), m_msb, m_lsb));
SET_RESULT(resp);
RETURN_RESULT(resp);
}
void visit(DfgMatchMasked* vtxp) override {
@@ -771,10 +828,12 @@ 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(selp);
RETURN_RESULT(selp);
}
#undef SET_RESULT
#undef RETURN_RESULT
#undef TRACE_RESULT_COVER
#undef RETURN_RESULT_TAIL
public:
// CONSTRUCTOR
@@ -1593,9 +1652,6 @@ void breakCycles(DfgGraph& dfg, V3DfgBreakCyclesContext& ctx) {
if (dumpDfgLevel() >= level) dfg.dumpDotFilePrefixed("breakCycles-" + name);
};
// AstNetlist/AstNodeModule user2 used as sequence numbers for temporaries
const VNUser2InUse user2InUse;
// Show input for debugging
dump(7, dfg, "input");
+139 -182
View File
@@ -25,8 +25,11 @@
#ifndef VERILATOR_V3DFGCACHE_H_
#define VERILATOR_V3DFGCACHE_H_
#include "verilatedos.h"
#include "V3Dfg.h"
#include "V3DfgDataType.h"
#include "V3HashTable.h"
#include <type_traits>
@@ -52,137 +55,107 @@ struct V3DfgCacheType<Vertex, CacheBase, VertexBase, Cache, Pairs...> final {
class V3DfgCache final {
// TYPES
class KeySel final {
const DfgDataType& m_dtype;
const DfgVertex* const m_fromp;
const uint32_t m_lsb;
// Hashing and comparison of the cached vertices. Each takes either a vertex, or the
// parts a vertex would be created from, so a lookup needs no vertex and no key object.
public:
KeySel(const DfgDataType& dtype, DfgVertex* fromp, uint32_t lsb)
: m_dtype{dtype}
, m_fromp{fromp}
, m_lsb{lsb} {}
explicit KeySel(const DfgSel* vtxp)
: m_dtype{vtxp->dtype()}
, m_fromp{vtxp->fromp()}
, m_lsb{vtxp->lsb()} {}
struct Hash final {
size_t operator()(const KeySel& key) const {
// cppcheck-suppress unreadVariable // cppcheck bug
V3Hash hash = key.m_dtype.hash();
hash += vertexHash(key.m_fromp);
hash += key.m_lsb;
return hash.value();
}
};
struct Equal final {
bool operator()(const KeySel& a, const KeySel& b) const {
return a.m_lsb == b.m_lsb && a.m_dtype == b.m_dtype
&& vertexEqual(a.m_fromp, b.m_fromp);
}
};
// DfgSel
struct HashSel final {
size_t operator()(const DfgSel* vtxp) const {
return operator()(vtxp->dtype(), vtxp->fromp(), vtxp->lsb());
}
size_t operator()(const DfgDataType& dtype, const DfgVertex* fromp, uint32_t lsb) const {
// cppcheck-suppress unreadVariable // cppcheck bug
V3Hash hash = dtype.hash();
hash += vertexHash(fromp);
hash += lsb;
return hash.value();
}
};
struct EqualSel final {
bool operator()(const DfgSel* ap, const DfgSel* bp) const {
return operator()(ap, bp->dtype(), bp->fromp(), bp->lsb());
}
bool operator()(const DfgSel* vtxp, const DfgDataType& dtype, const DfgVertex* fromp,
uint32_t lsb) const {
return vtxp->lsb() == lsb && vtxp->dtype() == dtype
&& vertexEqual(vtxp->fromp(), fromp);
}
};
class KeyUnary final {
const DfgDataType& m_dtype;
const DfgVertex* const m_source0p;
public:
// cppcheck-suppress noExplicitConstructor
KeyUnary(const DfgDataType& dtype, DfgVertex* source0p)
: m_dtype{dtype}
, m_source0p{source0p} {}
explicit KeyUnary(const DfgVertexUnary* vtxp)
: m_dtype{vtxp->dtype()}
, m_source0p{vtxp->inputp(0)} {}
struct Hash final {
size_t operator()(const KeyUnary& key) const { //
V3Hash hash = key.m_dtype.hash();
hash += vertexHash(key.m_source0p);
return hash.value();
}
};
struct Equal final {
bool operator()(const KeyUnary& a, const KeyUnary& b) const {
return a.m_dtype == b.m_dtype && vertexEqual(a.m_source0p, b.m_source0p);
}
};
// DfgVertexUnary
struct HashUnary final {
size_t operator()(const DfgVertexUnary* vtxp) const {
return operator()(vtxp->dtype(), vtxp->inputp(0));
}
size_t operator()(const DfgDataType& dtype, const DfgVertex* source0p) const {
V3Hash hash = dtype.hash();
hash += vertexHash(source0p);
return hash.value();
}
};
struct EqualUnary final {
bool operator()(const DfgVertexUnary* ap, const DfgVertexUnary* bp) const {
return operator()(ap, bp->dtype(), bp->inputp(0));
}
bool operator()(const DfgVertexUnary* vtxp, const DfgDataType& dtype,
const DfgVertex* source0p) const {
return vtxp->dtype() == dtype && vertexEqual(vtxp->inputp(0), source0p);
}
};
class KeyBinary final {
const DfgDataType& m_dtype;
const DfgVertex* const m_source0p;
const DfgVertex* const m_source1p;
public:
KeyBinary(const DfgDataType& dtype, DfgVertex* source0p, DfgVertex* source1p)
: m_dtype{dtype}
, m_source0p{source0p}
, m_source1p{source1p} {}
explicit KeyBinary(const DfgVertexBinary* vtxp)
: m_dtype{vtxp->dtype()}
, m_source0p{vtxp->inputp(0)}
, m_source1p{vtxp->inputp(1)} {}
struct Hash final {
size_t operator()(const KeyBinary& key) const {
V3Hash hash = key.m_dtype.hash();
hash += vertexHash(key.m_source0p);
hash += vertexHash(key.m_source1p);
return hash.value();
}
};
struct Equal final {
bool operator()(const KeyBinary& a, const KeyBinary& b) const {
return a.m_dtype == b.m_dtype && vertexEqual(a.m_source0p, b.m_source0p)
&& vertexEqual(a.m_source1p, b.m_source1p);
}
};
// DfgVertexBinary
struct HashBinary final {
size_t operator()(const DfgVertexBinary* vtxp) const {
return operator()(vtxp->dtype(), vtxp->inputp(0), vtxp->inputp(1));
}
size_t operator()(const DfgDataType& dtype, const DfgVertex* source0p,
const DfgVertex* source1p) const {
V3Hash hash = dtype.hash();
hash += vertexHash(source0p);
hash += vertexHash(source1p);
return hash.value();
}
};
struct EqualBinary final {
bool operator()(const DfgVertexBinary* ap, const DfgVertexBinary* bp) const {
return operator()(ap, bp->dtype(), bp->inputp(0), bp->inputp(1));
}
bool operator()(const DfgVertexBinary* vtxp, const DfgDataType& dtype,
const DfgVertex* source0p, const DfgVertex* source1p) const {
return vtxp->dtype() == dtype && vertexEqual(vtxp->inputp(0), source0p)
&& vertexEqual(vtxp->inputp(1), source1p);
}
};
class KeyTernary final {
const DfgDataType& m_dtype;
const DfgVertex* const m_source0p;
const DfgVertex* const m_source1p;
const DfgVertex* const m_source2p;
public:
KeyTernary(const DfgDataType& dtype, DfgVertex* source0p, DfgVertex* source1p,
DfgVertex* source2p)
: m_dtype{dtype}
, m_source0p{source0p}
, m_source1p{source1p}
, m_source2p{source2p} {}
explicit KeyTernary(const DfgVertexTernary* vtxp)
: m_dtype{vtxp->dtype()}
, m_source0p{vtxp->inputp(0)}
, m_source1p{vtxp->inputp(1)}
, m_source2p{vtxp->inputp(2)} {}
struct Hash final {
size_t operator()(const KeyTernary& key) const {
V3Hash hash = key.m_dtype.hash();
hash += vertexHash(key.m_source0p);
hash += vertexHash(key.m_source1p);
hash += vertexHash(key.m_source2p);
return hash.value();
}
};
struct Equal final {
bool operator()(const KeyTernary& a, const KeyTernary& b) const {
return a.m_dtype == b.m_dtype && vertexEqual(a.m_source0p, b.m_source0p)
&& vertexEqual(a.m_source1p, b.m_source1p)
&& vertexEqual(a.m_source2p, b.m_source2p);
}
};
// DfgVertexTernary
struct HashTernary final {
size_t operator()(const DfgVertexTernary* vtxp) const {
return operator()(vtxp->dtype(), vtxp->inputp(0), vtxp->inputp(1), vtxp->inputp(2));
}
size_t operator()(const DfgDataType& dtype, const DfgVertex* source0p,
const DfgVertex* source1p, const DfgVertex* source2p) const {
V3Hash hash = dtype.hash();
hash += vertexHash(source0p);
hash += vertexHash(source1p);
hash += vertexHash(source2p);
return hash.value();
}
};
struct EqualTernary final {
bool operator()(const DfgVertexTernary* ap, const DfgVertexTernary* bp) const {
return operator()(ap, bp->dtype(), bp->inputp(0), bp->inputp(1), bp->inputp(2));
}
bool operator()(const DfgVertexTernary* vtxp, const DfgDataType& dtype,
const DfgVertex* source0p, const DfgVertex* source1p,
const DfgVertex* source2p) const {
return vtxp->dtype() == dtype && vertexEqual(vtxp->inputp(0), source0p)
&& vertexEqual(vtxp->inputp(1), source1p)
&& vertexEqual(vtxp->inputp(2), source2p);
}
};
// Base class of vertex caches
class CacheBase VL_NOT_FINAL {
protected:
// These set the operands of a new vertex
@@ -210,86 +183,68 @@ class V3DfgCache final {
public:
// CacheBase does not cache anything
virtual DfgVertex* cache(DfgVertex*) { return nullptr; }
virtual void invalidate(const DfgVertex*) {}
virtual void invalidate(DfgVertex*) {}
};
template <typename T_Key, typename T_Vertex>
template <typename T_Vertex, typename T_Hash, typename T_Equal>
class Cache final : public CacheBase {
static_assert(std::is_base_of<DfgVertex, T_Vertex>::value, "T_Vertex must be a DfgVertex");
// TYPES
using Hash = typename T_Key::Hash;
using Equal = typename T_Key::Equal;
using Map = std::unordered_map<T_Key, T_Vertex*, Hash, Equal>;
// STATE
Map m_map;
// METHODS
// These return a reference to the mapped entry, inserting a nullptr if not yet exists
template <typename... T_Args>
T_Vertex*& entry(T_Args&&... args) {
const T_Key key{std::forward<T_Args>(args)...};
return m_map[key];
}
template <typename... T_Args>
typename Map::iterator find(T_Args&&... args) {
const T_Key key{std::forward<T_Args>(args)...};
return m_map.find(key);
}
V3HashSet<T_Vertex*, T_Hash, T_Equal> m_set;
public:
// Add an existing vertex to the cache. If an equivalent exists,
// it is returned and the cache is not updated.
// Add an existing vertex to the cache. If an equivalent but different vertex exists,
// it is returned and the cache is not updated. Returns nullptr if the vertex is inserted.
DfgVertex* cache(DfgVertex* vtxp) override {
UASSERT_OBJ(vtxp->is<T_Vertex>(), vtxp, "Vertex is wrong type");
T_Vertex*& entrypr = entry(static_cast<const T_Vertex*>(vtxp));
if (entrypr && entrypr != vtxp) return entrypr;
entrypr = static_cast<T_Vertex*>(vtxp);
return nullptr;
UDEBUGONLY(UASSERT_OBJ(vtxp->is<T_Vertex>(), vtxp, "Vertex is wrong type"););
T_Vertex* const typedp = static_cast<T_Vertex*>(vtxp);
T_Vertex* const cachedp = *m_set.insert(typedp).first;
return cachedp != vtxp ? cachedp : nullptr;
}
// Remove an existing vertex from the cache, if it is the cached vertex, otherwise no-op
void invalidate(const DfgVertex* vtxp) override {
UASSERT_OBJ(vtxp->is<T_Vertex>(), vtxp, "Vertex is wrong type");
const auto it = find(static_cast<const T_Vertex*>(vtxp));
if (it != m_map.end() && it->second == vtxp) m_map.erase(it);
void invalidate(DfgVertex* vtxp) override {
UDEBUGONLY(UASSERT_OBJ(vtxp->is<T_Vertex>(), vtxp, "Vertex is wrong type"););
T_Vertex* const typedp = static_cast<T_Vertex*>(vtxp);
const auto it = m_set.find(typedp);
if (it != m_set.end() && *it == typedp) m_set.erase(it);
}
// Get vertex with given operands, return nullptr if not in cache
template <typename Vertex, typename... Operands>
Vertex* get(const DfgDataType& dtype, Operands... operands) {
const auto it = find(dtype, operands...);
return it != m_map.end() ? static_cast<Vertex*>(it->second) : nullptr;
const auto it = m_set.find(dtype, operands...);
return it != m_set.end() ? static_cast<Vertex*>(*it) : nullptr;
}
// Get or create (and insert) vertex with given operands
// Get vertex with given operands, if does not exist, create it
template <typename Vertex, typename... Operands>
Vertex* getOrCreate(DfgGraph& dfg, FileLine* flp, const DfgDataType& dtype,
Operands... operands) {
T_Vertex*& entryr = entry(dtype, operands...);
if (!entryr) {
T_Vertex* const newp = new Vertex{dfg, flp, dtype};
const auto pair = m_set.insertLazy(dtype, operands..., [&]() -> T_Vertex* {
Vertex* const newp = new Vertex{dfg, flp, dtype};
setOperands(newp, operands...);
entryr = newp;
}
return static_cast<Vertex*>(entryr);
return newp;
});
T_Vertex* const vtxp = *pair.first;
UDEBUGONLY(UASSERT_OBJ(vtxp->template is<Vertex>(), vtxp, "Vertex is wrong type"););
return static_cast<Vertex*>(vtxp);
}
};
// Map from Vertex type to cache type
// clang-format off
template <typename Vertex>
using CacheType =
typename V3DfgCacheType<Vertex, CacheBase, //
DfgSel, Cache<KeySel, DfgSel>, //
DfgVertexUnary, Cache<KeyUnary, DfgVertexUnary>, //
DfgVertexBinary, Cache<KeyBinary, DfgVertexBinary>, //
DfgVertexTernary, Cache<KeyTernary, DfgVertexTernary> //
>::Type;
using CacheType = typename V3DfgCacheType<Vertex, CacheBase,
DfgSel, /* -> */ Cache<DfgSel, HashSel, EqualSel>,
DfgVertexUnary, /* -> */ Cache<DfgVertexUnary, HashUnary, EqualUnary>,
DfgVertexBinary, /* -> */ Cache<DfgVertexBinary, HashBinary, EqualBinary>,
DfgVertexTernary, /* -> */ Cache<DfgVertexTernary, HashTernary, EqualTernary>
>::Type;
// clang-format on
// STATE
DfgGraph& m_dfg; // The DfgGraph we are caching the vertices of
// The per type caches
// The per type caches
#define VERTEX_CACHE_DECLARE_CACHE(t) CacheType<t> m_cache##t;
FOREACH_DFG_VERTEX_TYPE(VERTEX_CACHE_DECLARE_CACHE)
#undef VERTEX_CACHE_DECLARE_CACHE
@@ -328,21 +283,23 @@ class V3DfgCache final {
}
public:
// Note: the cache starts out empty. If the caller wants existing vertices
// to be found, it must add them itself by calling 'cache' on each.
explicit V3DfgCache(DfgGraph& dfg)
: m_dfg{dfg} {
// Initialize the type to cache lookup table
// Initialize the type to cache lookup table
#define VERTEX_CACHE_DECLARE_CACHE_PTR(t) m_vtxType2Cachep[t::dfgType()] = &m_cache##t;
FOREACH_DFG_VERTEX_TYPE(VERTEX_CACHE_DECLARE_CACHE_PTR)
FOREACH_DFG_VERTEX_TYPE(VERTEX_CACHE_DECLARE_CACHE_PTR)
#undef VERTEX_CACHE_DECLARE_CACHE_PTR
}
// Add all operation vertices to the cache
for (DfgVertex& vtx : m_dfg.opVertices()) cache(&vtx);
// Add an existing vertex to the cache. If an equivalent (but different) already exists,
// it is returned and the cache is not updated.
DfgVertex
* cache(DfgVertex * vtxp) {
return m_vtxType2Cachep[vtxp->type()]->cache(vtxp);
}
// Add an existing vertex to the cache. If an equivalent (but different) already exists,
// it is returned and the cache is not updated.
DfgVertex* cache(DfgVertex* vtxp) { return m_vtxType2Cachep[vtxp->type()]->cache(vtxp); }
// Remove an exiting vertex, it is the cached vertex.
void invalidate(DfgVertex* vtxp) { m_vtxType2Cachep[vtxp->type()]->invalidate(vtxp); }

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