Merge remote-tracking branch 'upstream/master' into nba_fix

This commit is contained in:
Ethan Sifferman 2026-02-13 12:20:44 -08:00
commit 5975f9d96d
469 changed files with 55315 additions and 53408 deletions

42
Changes
View File

@ -15,9 +15,12 @@ Verilator 5.045 devel
* Add IEEE 4-state type lint checks (#3645 partial) (#6895). [Jose Drowne]
* Add VERILATOR_NUMA_STRATEGY environment variable (#6826) (#6880). [Yangyu Chen]
* Add decoded Verilog name in JSON output (#6919) (#6995). [Oleh Maksymenko]
* Add parsing of solve-before inside foreach (#6934). [Pawel Kojma, Antmicro Ltd.]
* Add error when accessing a non-static class field from a static function (#6948). [Artur Bieniek, Antmicro Ltd.]
* Add VerilatedContext::useNumaAssign and set on threads() call (#6954). [Yangyu Chen]
* Support modport expression syntax + nested (#2601) (#5581) (#7005). [Leela Pakanati]
* Support nested interface as port connection (#5066) (#6986). [Leela Pakanati]
* Support structure initial values (#6130).
* Support vpi_put/vpi_get forcing of signals (#5933) (#6704). [Christian Hecken]
* Support detailed failure info for constraint violations (#6617) (#6883). [Yilou Wang]
@ -26,17 +29,27 @@ Verilator 5.045 devel
* Support dynamic array elements in std::randomize (#6896). [Ryszard Rozak, Antmicro Ltd.]
* Support unbounded '$' in inside range expressions (#6935) (#6938). [Wei-Lun Chiu]
* Support `extern module` as a forward-declaration that is ignored.
* Support `foreach` with nested dots (#6991). [Krzysztof Bieganski, Antmicro Ltd.]
* Support signed multiplication in constraints (#7008). [Pawel Kojma, Antmicro Ltd.]
* Support constraint_mode() on static constraints (#7027) (#7038). [Yilou Wang]
* Support some system functions in constraint blocks (#7028) (#7036). [Yilou Wang]
* Support std::randomize() for queue, dynamic array, and associative array variables (#7044). [Yilou Wang]
* Support inherited and nested pre/post_randomize callbacks (#7049) (#7053). [Yilou Wang]
* Remove deprecated `--xml-only`.
* Remove deprecated `--make cmake`.
* Change metacomment extra underscore error to BADVLTPRAGMA warning (#6968). [Geza Lore]
* Optimize string temporaries to not be localized (#6969). [Geza Lore]
* Optimize wide word shifts by multiple of word size (#6970). [Geza Lore]
* Optimize concatenations that produce unused bits in DFG (#6971). [Geza Lore]
* Optimize more wide operation temporaries with substitution (#6972). [Geza Lore]
* Optimize right shifts as clean (#6981). [Geza Lore]
* Fix parameterized virtual interface references that have no model references (#4286).
* Fix variable reference lookup for module-level variables (#6741) (#6882). [Yilou Wang]
* Change JSON dumps to not include booleans that are false (#6977).
* Change metacomment extra underscore error to BADVLTPRAGMA warning (#6968). [Geza Lore, Testorrent USA, Inc.]
* Change INITIALSTATIC to also report on processes, per IEEE (#7020).
* Optimize string temporaries to not be localized (#6969). [Geza Lore, Testorrent USA, Inc.]
* Optimize wide word shifts by multiple of word size (#6970). [Geza Lore, Testorrent USA, Inc.]
* Optimize concatenations that produce unused bits in DFG (#6971). [Geza Lore, Testorrent USA, Inc.]
* Optimize more wide operation temporaries with substitution (#6972). [Geza Lore, Testorrent USA, Inc.]
* Optimize right shifts as clean (#6981). [Geza Lore, Testorrent USA, Inc.]
* Optimize temporary insertion for concatenations in DFG (#7013). [Geza Lore, Testorrent USA, Inc.]
* Fix MULTIDRIVEN with task and default driver (#4045) (#6858). [em2machine]
* Fix parameterized virtual interface references that have no model references (#4286).
* Fix hierarchical interface/modport issues (#5941) (#6997). [Leela Pakanati]
* Fix variable reference lookup for module-level variables (#6741) (#6882). [Yilou Wang]
* Fix false CASEOVERLAP case item expression lint (#6825) (#6886). [Luca Colagrande]
* Fix virtual interface triggers (#6844). [Igor Zaworski, Antmicro Ltd.]
* Fix use-after-free error (#6846). [Matthew Ballance]
@ -56,12 +69,25 @@ Verilator 5.045 devel
* Fix memory leak in vpi_put_value and vpi_get_value (#6917). [Christian Hecken]
* Fix segfault after assignment pattern XOR error (#6928) (#6931). [emmettifelts]
* Fix delayed initial assignment (#6929). [Todd Strader]
* Fix event triggering (#6932). [Igor Zaworski, Antmicro Ltd.]
* Fix `--top-module` with underscores (#6940). [Christopher Batten]
* Fix variable randomization to better differ by seed (#6945) (#6956). [Rodrigo Batista de Moraes]
* Fix null pointer dereference in class member trigger expressions (#6946). [Cameron Waite]
* Fix type assignments for arrays of parameter types (#6955). [Todd Strader]
* Fix accessing non-rand struct member in constraints (#6960). [Pawel Kojma, Antmicro Ltd.]
* Fix associative array of events causes C++ compile error (#6962).
* Fix UNUSED / UNDRIVEN for unused functions (#6967). [Todd Strader]
* Fix non-inlined function return value clearing (#6982).
* Fix parameterized class typedef as interface type parameter (#6983) (#6984). [Leela Pakanati]
* Fix virtual interface not found internal error (#7010). [Igor Zaworski, Antmicro Ltd.]
* Fix multidimensional dynamic array elements passed to ref argument (#7023). [Ryszard Rozak, Antmicro Ltd.]
* Fix randc cyclic behavior broken with constraints (#7029) (#7035). [Yilou Wang]
* Fix inline foreach constraints on dynamic arrays of class objects (#7030) (#7037). [Yilou Wang]
* Fix rand_mode() on nested object variables causing Z3 solver error (#7031) (#7034). [Yilou Wang]
* Fix non-member identifiers used inside constraints (#7033). [Pawel Kojma, Antmicro Ltd.]
* Fix tracing without module inlining to match with inlining (#7041). [Geza Lore]
* Fix rand_mode()/constraint_mode() when used as function arguments (#7051) (#7055). [Yilou Wang]
* Fix constraint_mode()/rand_mode() in constructor being overwritten (#7054). [Yilou Wang]
Verilator 5.044 2026-01-01

304
LICENSE Normal file
View File

@ -0,0 +1,304 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License.
"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version".
The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license document.
c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for software and other kinds of works.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS
0. Definitions.
“This License” refers to version 3 of the GNU General Public License.
“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.
To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.
A “covered work” means either the unmodified Program or a work based on the Program.
To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
1. Source Code.
The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.
A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”.
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
7. Additional Terms.
“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
11. Patents.
A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”.
A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.
You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@ -578,6 +578,7 @@ MBAKE_FLAGS = format --config ./.bake.toml
format-exec:
-chmod a+x test_regress/t/*.py
-chmod a-x test_regress/t/*.v
format-make mbake:
$(MBAKE) --version

View File

@ -43,7 +43,7 @@ fi
install-vcddiff() {
TMP_DIR="$(mktemp -d)"
git clone https://github.com/veripool/vcddiff "$TMP_DIR"
git -C "${TMP_DIR}" checkout dca845020668887fd13498c772939814d9264fd5
git -C "${TMP_DIR}" checkout 4db0d84a27e8f148b127e916fc71d650837955c5
"$MAKE" -C "${TMP_DIR}"
sudo cp "${TMP_DIR}/vcddiff" /usr/local/bin
}

View File

@ -29,9 +29,9 @@ Alliance <https://chipsalliance.org>`_, and `Antmicro Ltd
Previous major corporate sponsors of Verilator, by providing significant
contributions of time or funds include: Antmicro Ltd., Atmel Corporation,
Compaq Corporation, Digital Equipment Corporation, Embecosm Ltd., Fractile
Ltd., Hicamp Systems, Intel Corporation, Marvell Inc., Mindspeed
Technologies Inc., MicroTune Inc., picoChip Designs Ltd., Sun Microsystems
Inc., Nauticus Networks Inc., SiCortex Inc, Shunyao CAD, and Western
Ltd., Hicamp Systems, Intel Corporation, Marvell Inc., Mindspeed Technologies
Inc., MicroTune Inc., picoChip Designs Ltd., Sun Microsystems Inc., Nauticus
Networks Inc., SiCortex Inc, Shunyao CAD, Tenstorrent USA, Inc. and Western
Digital Inc.
The contributors of major functionality are: Jeremy Bennett, Krzysztof

View File

@ -662,6 +662,10 @@ Summary:
Rarely needed. Do not apply the DFG optimizer before inlining.
.. option:: -fno-dfg-push-down-sels
Rarely needed. Disable DFG select/concatenation optimization.
.. option:: -fno-dfg-scoped
Rarely needed. Do not apply the DFG optimizer across module scopes.
@ -2567,3 +2571,9 @@ The grammar of control commands is as follows:
scope which the rule is to match, where 0 means all levels below, 1 the
exact level as the provided scope, and 2 means an additional level of
children below the provided scope, etc.
.. option:: verilator_lib -module "<modulename>"
Internal use only. Marks the specified module as being a stub for a library
created by :option:`--lib-creat` (including when created with
:option:`--hierarchical`). Required for special internal processing.

View File

@ -528,6 +528,12 @@ $readmemb, $readmemh
specification do not include support for readmem to multi-dimensional
arrays.
$stacktrace
The `$stacktrace` system call will show the C++ stack, not the Verilog
call stack, though the function names typically correlate. To get
symbolic names, the model must have debug symbols, e.g. compile with
`-CFLAGS -ggdb -LDFLAGS -ggdb -LDFLAGS -rdynamic`.
$test$plusargs, $value$plusargs
Supported, but the instantiating C++/SystemC wrapper must call

View File

@ -467,7 +467,7 @@ List Of Warnings
.. TODO better example
Warns that the code is comparing a value in a way that will always be
constant. For example, ``X > 1`` will always be true when X is a single
constant. For example, ``X > 1`` will always be false when X is a single
bit wide.
Ignoring this warning will only suppress the lint check; it will
@ -1022,13 +1022,20 @@ List Of Warnings
.. option:: IMPLICITSTATIC
Warns that the lifetime of a task or a function was not provided and so
was implicitly set to static. The warning is suppressed when no
variables inside the task or a function are assigned to.
an enclosed variable was implicitly set to static. The warning is
suppressed when no variables inside the task or a function are assigned
to.
Also warns that a process (e.g. "always" or "initial" statement) has
enclosed variables that were implicitly set to static.
IEEE 1800-2023 6.21 requires this error, though Verilator treats it by
default as a warning.
This is a warning because the static default differs from C++, differs
from class member function/tasks. Static is a more dangerous default
then automatic as static prevents the function from being reentrant,
which may be a source of bugs, and/or performance issues.
then automatic as static prevents the function or process from being
reentrant, which may be a source of bugs, and/or performance issues.
If the function is in a module, and does not require static behavior,
change it to "function automatic".

View File

@ -593,15 +593,17 @@ object.
This class manages processes that await events (triggers). There is one
such object per each trigger awaited by coroutines. Coroutines ``co_await``
this object's ``trigger`` function. They are stored in two stages -
`uncommitted` and `ready`. First, they land in the `uncommitted` stage, and
cannot be resumed. The ``resume`` function resumes all coroutines from the
`ready` stage and moves `uncommitted` coroutines into `ready`. The
``commit`` function only moves `uncommitted` coroutines into `ready`.
this object's ``trigger`` function. They are stored in three stages -
`awaiting`, `fired` and `toResume`. First, they land in the `awaiting` stage, and
cannot be resumed. The ``ready`` function moves all coroutines from the
`awaiting` stage into the `fired` stage. The ``moveToResumeQueue`` function moves
`fired` coroutines into `toResume`. Finally, function `resume` resumes
all coroutines from the `toResume` stage.
This split is done to avoid self-triggering and triggering coroutines
multiple times. See the `Scheduling with timing` section for details on how
this is used.
This split is done to avoid self-triggering, triggering coroutines
multiple times and triggering coroutines in the same iteration
they were suspended. See the `Scheduling with timing` section
for details on how this is used.
``VlDynamicTriggerScheduler``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -719,16 +721,35 @@ in that process. When ordering code using ``V3Order``, these triggers are
provided as external domains of these variables. This ensures that the
necessary combinational logic is triggered after a coroutine resumption.
Every call to a `VlTriggerScheduler`'s `trigger()` method is preempt by
a call to a proper `__VbeforeTrig` function which evaluates all the necessary
triggers so, the information about order of suspension/resumption is not lost.
The triggers necessary to evaluate are ones dependent on the same events
as the `trigger()` - e.g.: if `triggers()` awaits for event `a` or `b`, then
every trigger that depends on any of those shall be evaluated. If they wouldn't
be evaluated and next coroutine after resumption would fire the event `a` then
it is impossible to get to know whether await or fire on event `a` was called
first - which is necessary to know.
There are two functions for managing timing logic called by ``_eval()``:
* ``_timing_commit()``, which commits all coroutines whose triggers were
* ``_timing_ready()``, which commits all coroutines whose triggers were
not set in the current iteration,
* ``_timing_resume()``, which calls `resume()` on all trigger and delay
schedulers whose triggers were set in the current iteration.
Thanks to this separation, a coroutine awaiting a trigger cannot be
suspended and resumed in the same iteration, and it cannot be resumed
before it suspends.
Thanks to this separation a coroutine:
* awaiting a trigger cannot be suspended and resumed in the same iteration
(``test_regress/t/t_timing_eval_act.v``) - which is necessary to make
Verilator more predictable; this is the reason for introduction of 3rd stage
in `VlTriggerScheduler` and thanks to this it is guaranteed that downstream
logic will be evaluated before resumption (assuming that the coroutine wasn't
already triggered in previous iteration);
* cannot be resumed before it is suspended -
``test_regress/t/t_event_control_double_excessive.v``;
* firing cannot cannot be lost
(``test_regress/t/t_event_control_double_lost.v``) - which is possible when
triggers are not evaluated right before awaiting.
All coroutines are committed and resumed in the 'act' eval loop. With
timing features enabled, the ``_eval()`` function takes this form:
@ -1946,35 +1967,26 @@ for programmatic processing (e.g. with `astsee
<https://github.com/antmicro/astsee>`_). To enable this dump format, use
:vlopt:`--dump-tree-json` or :vlopt:`--json-only`.
Structure:
The potential fields in the JSON dump can be determined by searching for
JSON in the `src/V3AstNodes.cpp` source file. The dump will only include
booleans that are true, omitting those that are false.
Structure example:
::
{
/* Attributes that are common to all types of nodes */
/* Attributes that are common to most types of nodes */
"type": "VAR",
"name": "cyc",
"verilogName": "cyc",
/* By default addresses and filenames use short/stable ids rather than real value */
"addr": "(H)",
"loc": "a,25:12,26:15", /* "fileid,firstLine:firstCol,lastLine:endCol" (endCol is right exclusive) */
"editNum": 602,
/* Fields that are specific to AstVar nodes: */
/* Some fields that are specific to AstVar nodes: */
"origName": "cyc",
"isSc": false,
"ioDirection": "NONE",
"isConst": false,
"isPullup": false,
"isPulldown": false,
"isUsedClock": false,
"isSigPublic": false,
"isLatched": false,
"isUsedLoopIdx": false,
"noReset": false,
"attrIsolateAssign": false,
"attrFileDescr": false,
"isDpiOpenArray": false,
"isFuncReturn": false,
"isFuncLocal": false,
"attrClocker": "UNKNOWN",
"lifetime": "NONE",
"varType": "VAR",

View File

@ -273,6 +273,7 @@ Laroche
Laurens
Lavino
Leber
Leela
Leendert
Lem
Lesik
@ -361,6 +362,7 @@ Ondrej
Oron
Oyvind
PLI
Pakanati
Palaniappan
Patricio
Peltonen
@ -478,7 +480,9 @@ Tarik
Tariq
Tejada
Tengstrand
Tenstorrent
Terpstra
Testorrent
Thiede
Thierry
Thyer
@ -621,6 +625,7 @@ bitstoreal
blackbox
bokke
bool
booleans
brancoliticus
buf
bufif
@ -1102,6 +1107,7 @@ src
srcdir
srcfile
sscanf
stacktrace
stderr
stdin
stdout

View File

@ -69,6 +69,7 @@
# include <direct.h> // mkdir
#endif
#ifdef __GLIBC__
# include <cxxabi.h>
# include <execinfo.h>
# define _VL_HAVE_STACKTRACE
#endif
@ -1918,27 +1919,68 @@ IData VL_FREAD_I(int width, int array_lsb, int array_size, void* memp, IData fpi
return read_count;
}
#ifdef _VL_HAVE_STACKTRACE
static std::string _vl_stacktrace_demangle(const std::string& input) VL_MT_SAFE {
static VerilatedMutex s_demangleMutex;
const VerilatedLockGuard lock{s_demangleMutex};
std::string result;
result.reserve(input.size());
std::string word;
for (const char c : input) {
if (std::isalpha(c) || c == '_') {
word += c;
} else if (!word.empty() && std::isdigit(c)) {
word += c;
} else {
if (!word.empty()) {
// abi::__cxa_demangle mallocs demangled_name
int status = 0;
char* const demangled_name
= abi::__cxa_demangle(word.c_str(), NULL, NULL, &status);
if (status == 0) {
result += std::string{demangled_name};
std::free(demangled_name); // Free the allocated memory
} else {
result += word;
}
word.clear();
}
result += c;
}
}
// input requires final newline, so last word can't be symbol
result += word;
return result;
}
#endif
std::string VL_STACKTRACE_N() VL_MT_SAFE {
static VerilatedMutex s_stackTraceMutex;
const VerilatedLockGuard lock{s_stackTraceMutex};
#ifdef _VL_HAVE_STACKTRACE
int nptrs = 0;
char** strings = nullptr;
#ifdef _VL_HAVE_STACKTRACE
constexpr int BT_BUF_SIZE = 100;
void* buffer[BT_BUF_SIZE];
nptrs = backtrace(buffer, BT_BUF_SIZE);
strings = backtrace_symbols(buffer, nptrs);
#endif
// cppcheck-suppress knownConditionTrueFalse
if (!strings) return "Unable to backtrace\n";
if (!strings) return "Unable to backtrace, call failed\n";
std::string result = "Backtrace:\n";
for (int j = 0; j < nptrs; ++j) result += std::string{strings[j]} + "\n"s;
for (int j = 0; j < nptrs; ++j)
result += _vl_stacktrace_demangle(std::string{strings[j]} + "\n"s);
free(strings);
return result;
#else
return "Unable to backtrace; not supported\n";
#endif
}
void VL_STACKTRACE() VL_MT_SAFE {

View File

@ -134,7 +134,8 @@ void VerilatedFst::declDTypeEnum(int dtypenum, const char* name, uint32_t elemen
const char** itemValuesp) {
const fstEnumHandle enumNum
= fstWriterCreateEnumTable(m_fst, name, elements, minValbits, itemNamesp, itemValuesp);
m_local2fstdtype[dtypenum] = enumNum;
const bool newEntry = m_local2fstdtype[initUserp()].emplace(dtypenum, enumNum).second;
assert(newEntry);
}
// TODO: should return std::optional<fstScopeType>, but I can't have C++17
@ -205,7 +206,9 @@ void VerilatedFst::declare(uint32_t code, const char* name, int dtypenum,
if (bussed) name_ss << " [" << msb << ":" << lsb << "]";
const std::string name_str = name_ss.str();
if (dtypenum > 0) fstWriterEmitEnumTableRef(m_fst, m_local2fstdtype[dtypenum]);
if (dtypenum > 0) {
fstWriterEmitEnumTableRef(m_fst, m_local2fstdtype.at(initUserp()).at(dtypenum));
}
fstVarDir varDir = FST_VD_IMPLICIT;
switch (direction) {

View File

@ -53,7 +53,7 @@ private:
fstWriterContext* m_fst = nullptr;
std::map<uint32_t, vlFstHandle> m_code2symbol;
std::map<int, vlFstEnumHandle> m_local2fstdtype;
std::map<void*, std::map<int, vlFstEnumHandle>> m_local2fstdtype;
vlFstHandle* m_symbolp = nullptr; // same as m_code2symbol, but as an array
char* m_strbufp = nullptr; // String buffer long enough to hold maxBits() chars
uint64_t m_timeui = 0; // Time to emit, 0 = not needed

View File

@ -23,6 +23,7 @@
#include "verilated_random.h"
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <sstream>
@ -367,13 +368,90 @@ void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) {
os << ')';
}
size_t VlRandomizer::hashConstraints() const {
size_t h = 0;
for (const auto& c : m_constraints) {
h ^= std::hash<std::string>{}(c) + 0x9e3779b9 + (h << 6) + (h >> 2);
}
return h;
}
void VlRandomizer::enumerateRandcValues(const std::string& varName, VlRNG& rngr) {
std::vector<uint64_t> values;
const auto varIt = m_vars.find(varName);
if (varIt == m_vars.end()) return;
const int width = varIt->second->width();
std::iostream& os = getSolver();
if (!os) return;
// Set up a single incremental solver session for enumeration
os << "(set-option :produce-models true)\n";
os << "(set-logic QF_ABV)\n";
os << "(define-fun __Vbv ((b Bool)) (_ BitVec 1) (ite b #b1 #b0))\n";
os << "(define-fun __Vbool ((v (_ BitVec 1))) Bool (= #b1 v))\n";
// Declare all variables (solver needs full context for cross-var constraints)
for (const auto& var : m_vars) {
if (var.second->dimension() > 0) {
auto arrVarsp = std::make_shared<const ArrayInfoMap>(m_arr_vars);
var.second->setArrayInfo(arrVarsp);
}
os << "(declare-fun " << var.first << " () ";
var.second->emitType(os);
os << ")\n";
}
// Assert all user constraints
for (const std::string& constraint : m_constraints) {
os << "(assert (= #b1 " << constraint << "))\n";
}
// Incrementally enumerate all valid values for this randc variable
while (true) {
os << "(check-sat)\n";
std::string sat;
do { std::getline(os, sat); } while (sat.empty());
if (sat != "sat") break;
// Read just this variable's value
os << "(get-value (" << varName << "))\n";
char c;
os >> c; // '('
os >> c; // '('
std::string name, value;
os >> name; // Consume variable name token from solver output
(void)name;
std::getline(os, value, ')');
os >> c; // ')'
// Parse the SMT value to uint64_t
VlWide<VL_WQ_WORDS_E> qowp;
VL_SET_WQ(qowp, 0ULL);
if (!parseSMTNum(width, qowp, value)) break;
const uint64_t numVal = (width <= 32) ? qowp[0] : VL_SET_QW(qowp);
values.push_back(numVal);
// Exclude this value for next iteration (incremental)
os << "(assert (not (= " << varName << " (_ bv" << numVal << " " << width << "))))\n";
}
os << "(reset)\n";
// Shuffle using Fisher-Yates
for (size_t i = values.size(); i > 1; --i) {
const size_t j = VL_RANDOM_RNG_I(rngr) % i;
std::swap(values[i - 1], values[j]);
}
m_randcValueQueues[varName] = std::deque<uint64_t>(values.begin(), values.end());
}
bool VlRandomizer::next(VlRNG& rngr) {
if (m_vars.empty() && m_unique_arrays.empty()) return true;
for (const std::string& baseName : m_unique_arrays) {
const auto it = m_vars.find(baseName);
// Look up the actual size we stored earlier
// const uint32_t size = m_unique_array_sizes[baseName];
const uint32_t size = m_unique_array_sizes.at(baseName);
if (it != m_vars.end()) {
@ -388,6 +466,30 @@ bool VlRandomizer::next(VlRNG& rngr) {
}
}
// Randc queue-based cycling: enumerate valid values once, then pop per call
if (!m_randcVarNames.empty()) {
const size_t currentHash = hashConstraints();
// Invalidate queues if constraints changed (e.g., constraint_mode toggled)
if (currentHash != m_randcConstraintHash) {
m_randcValueQueues.clear();
m_randcConstraintHash = currentHash;
}
// Refill empty queues (start of new cycle)
for (const auto& name : m_randcVarNames) {
auto& queue = m_randcValueQueues[name];
if (queue.empty()) enumerateRandcValues(name, rngr);
}
}
// Pop randc values from queues (will be pinned in solver)
std::map<std::string, uint64_t> randcPinned;
for (const auto& name : m_randcVarNames) {
auto& queue = m_randcValueQueues[name];
if (queue.empty()) return false; // No valid values exist
randcPinned[name] = queue.front();
queue.pop_front();
}
std::iostream& os = getSolver();
if (!os) return false;
@ -408,6 +510,13 @@ bool VlRandomizer::next(VlRNG& rngr) {
for (const std::string& constraint : m_constraints) {
os << "(assert (= #b1 " << constraint << "))\n";
}
// Pin randc values from pre-enumerated queues
for (const auto& pair : randcPinned) {
const int w = m_vars.at(pair.first)->width();
os << "(assert (= " << pair.first << " (_ bv" << pair.second << " " << w << ")))\n";
}
os << "(check-sat)\n";
bool sat = parseSolution(os, true);
@ -620,8 +729,13 @@ void VlRandomizer::clearConstraints() {
void VlRandomizer::clearAll() {
m_constraints.clear();
m_vars.clear();
m_randcVarNames.clear();
m_randcValueQueues.clear();
m_randcConstraintHash = 0;
}
void VlRandomizer::markRandc(const char* name) { m_randcVarNames.insert(name); }
#ifdef VL_DEBUG
void VlRandomizer::dump() const {
for (const auto& var : m_vars) {

View File

@ -28,6 +28,7 @@
#include "verilated.h"
#include <deque>
#include <iomanip>
#include <iostream>
#include <ostream>
@ -209,10 +210,16 @@ class VlRandomizer VL_NOT_FINAL {
std::map<std::string, uint32_t> m_unique_array_sizes;
const VlQueue<CData>* m_randmodep = nullptr; // rand_mode state;
int m_index = 0; // Internal counter for key generation
std::set<std::string> m_randcVarNames; // Names of randc variables for cyclic tracking
std::map<std::string, std::deque<uint64_t>>
m_randcValueQueues; // Remaining values per randc var (queue-based cycling)
size_t m_randcConstraintHash = 0; // Hash of constraints when queues were built
// PRIVATE METHODS
void randomConstraint(std::ostream& os, VlRNG& rngr, int bits);
bool parseSolution(std::iostream& file, bool log = false);
void enumerateRandcValues(const std::string& varName, VlRNG& rngr);
size_t hashConstraints() const;
public:
// CONSTRUCTORS
@ -585,6 +592,7 @@ public:
const char* source = "");
void clearConstraints();
void clearAll(); // Clear both constraints and variables
void markRandc(const char* name); // Mark variable as randc for cyclic tracking
void set_randmode(const VlQueue<CData>& randmode) { m_randmodep = &randmode; }
#ifdef VL_DEBUG
void dump() const;
@ -641,6 +649,23 @@ public:
for (size_t i = 0; i < N_Depth; ++i) { basicStdRandomization(value.operator[](i), width); }
return true;
}
// Queue/dynamic array randomization
template <typename T_Value, size_t N_MaxSize>
bool basicStdRandomization(VlQueue<T_Value, N_MaxSize>& value, size_t width) {
for (int i = 0; i < value.size(); ++i) { basicStdRandomization(value.atWrite(i), width); }
return true;
}
// Associative array randomization
template <typename T_Key, typename T_Value>
bool basicStdRandomization(VlAssocArray<T_Key, T_Value>& value, size_t width) {
T_Key key;
for (int exists = value.first(key); exists; exists = value.next(key)) {
basicStdRandomization(value.at(key), width);
}
return true;
}
bool next() { return VlRandomizer::next(m_rng); }
};

View File

@ -127,44 +127,65 @@ void VlTriggerScheduler::resume(const char* eventDescription) {
VL_DEBUG_IF(dump(eventDescription);
VL_DBG_MSGF(" Resuming processes waiting for %s\n", eventDescription););
#endif
std::swap(m_ready, m_resumeQueue);
for (VlCoroutineHandle& coro : m_resumeQueue) coro.resume();
m_resumeQueue.clear();
commit(eventDescription);
for (VlCoroutineHandle& coro : m_toResume) coro.resume();
m_toResume.clear();
}
void VlTriggerScheduler::commit(const char* eventDescription) {
void VlTriggerScheduler::moveToResumeQueue(const char* eventDescription) {
#ifdef VL_DEBUG
if (!m_uncommitted.empty()) {
if (!m_fired.empty()) {
VL_DEBUG_IF(VL_DBG_MSGF(" Moving to resume queue processes waiting for %s:\n",
eventDescription);
for (const auto& susp
: m_fired) {
VL_DBG_MSGF(" - ");
susp.dump();
});
}
#endif
std::swap(m_fired, m_toResume);
}
void VlTriggerScheduler::ready(const char* eventDescription) {
#ifdef VL_DEBUG
if (!m_awaiting.empty()) {
VL_DEBUG_IF(
VL_DBG_MSGF(" Committing processes waiting for %s:\n", eventDescription);
for (const auto& susp
: m_uncommitted) {
: m_awaiting) {
VL_DBG_MSGF(" - ");
susp.dump();
});
}
#endif
m_ready.reserve(m_ready.size() + m_uncommitted.size());
m_ready.insert(m_ready.end(), std::make_move_iterator(m_uncommitted.begin()),
std::make_move_iterator(m_uncommitted.end()));
m_uncommitted.clear();
const size_t expectedSize = m_fired.size() + m_awaiting.size();
if (m_fired.capacity() < expectedSize) m_fired.reserve(expectedSize * 2);
m_fired.insert(m_fired.end(), std::make_move_iterator(m_awaiting.begin()),
std::make_move_iterator(m_awaiting.end()));
m_awaiting.clear();
}
#ifdef VL_DEBUG
void VlTriggerScheduler::dump(const char* eventDescription) const {
if (m_ready.empty()) {
VL_DBG_MSGF(" No ready processes waiting for %s\n", eventDescription);
if (m_toResume.empty()) {
VL_DBG_MSGF(" No process to resume waiting for %s\n", eventDescription);
} else {
for (const auto& susp : m_ready) {
VL_DBG_MSGF(" Ready processes waiting for %s:\n", eventDescription);
for (const auto& susp : m_toResume) {
VL_DBG_MSGF(" Processes to resume waiting for %s:\n", eventDescription);
VL_DBG_MSGF(" - ");
susp.dump();
}
}
if (!m_uncommitted.empty()) {
VL_DBG_MSGF(" Uncommitted processes waiting for %s:\n", eventDescription);
for (const auto& susp : m_uncommitted) {
if (!m_fired.empty()) {
VL_DBG_MSGF(" Triggered processes waiting for %s:\n", eventDescription);
for (const auto& susp : m_awaiting) {
VL_DBG_MSGF(" - ");
susp.dump();
}
}
if (!m_awaiting.empty()) {
VL_DBG_MSGF(" Not triggered processes waiting for %s:\n", eventDescription);
for (const auto& susp : m_awaiting) {
VL_DBG_MSGF(" - ");
susp.dump();
}

View File

@ -233,38 +233,40 @@ public:
//=============================================================================
// VlTriggerScheduler stores coroutines to be resumed by a trigger. It does not keep track of its
// trigger, relying on calling code to resume when appropriate. Coroutines are kept in two stages
// - 'uncommitted' and 'ready'. Whenever a coroutine is suspended, it lands in the 'uncommitted'
// stage. Only when commit() is called, these coroutines get moved to the 'ready' stage. That's
// when they can be resumed. This is done to avoid resuming processes before they start waiting.
// trigger, relying on calling code to resume when appropriate. Coroutines are kept in three stages
// - 'awaiting', 'fired' and 'toResume'. Whenever a coroutine is suspended, it lands in the
// 'awaiting' stage. Only when ready() is called, these coroutines get moved to the 'fired' stage.
// When moveToResumeQueue() is begin called all coroutines from 'ready' are moved to 'toResume'.
// That's when they can be resumed. This is done to avoid resuming processes before they start
// waiting.
class VlTriggerScheduler final {
// TYPES
using VlCoroutineVec = std::vector<VlCoroutineHandle>;
// MEMBERS
VlCoroutineVec m_uncommitted; // Coroutines suspended before commit() was called
// (not resumable)
VlCoroutineVec m_ready; // Coroutines that can be resumed (all coros from m_uncommitted are
// moved here in commit())
VlCoroutineVec m_resumeQueue; // Coroutines being resumed by resume(); kept as a field to
// avoid reallocation. Resumed coroutines are moved to
// m_resumeQueue to allow adding coroutines to m_ready
// during resume(). Outside of resume() should always be empty.
VlCoroutineVec m_awaiting; // Coroutines suspended before ready() was called
// (not resumable)
VlCoroutineVec m_fired; // Coroutines that were triggered (all coros from m_awaiting are moved
// here in ready())
VlCoroutineVec m_toResume; // Coroutines to resume in next resumePrep()
// - moved here in commit()
public:
// METHODS
// Resumes all coroutines from the 'ready' stage
// Resumes all coroutines from the m_toResume
void resume(const char* eventDescription = VL_UNKNOWN);
// Moves all coroutines from m_uncommitted to m_ready
void commit(const char* eventDescription = VL_UNKNOWN);
// Moves all coroutines from m_fired to m_toResume
void moveToResumeQueue(const char* eventDescription = VL_UNKNOWN);
// Moves all coroutines from m_awaiting to m_fired
void ready(const char* eventDescription = VL_UNKNOWN);
// Are there no coroutines awaiting?
bool empty() const { return m_ready.empty() && m_uncommitted.empty(); }
bool empty() const { return m_fired.empty() && m_awaiting.empty(); }
#ifdef VL_DEBUG
void dump(const char* eventDescription) const;
#endif
// Used by coroutines for co_awaiting a certain trigger
auto trigger(bool commit, VlProcessRef process, const char* eventDescription = VL_UNKNOWN,
auto trigger(bool ready, VlProcessRef process, const char* eventDescription = VL_UNKNOWN,
const char* filename = VL_UNKNOWN, int lineno = 0) {
VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n",
eventDescription, filename, lineno););
@ -279,8 +281,7 @@ public:
}
void await_resume() const {}
};
return Awaitable{commit ? m_ready : m_uncommitted, process,
VlFileLineDebug{filename, lineno}};
return Awaitable{ready ? m_fired : m_awaiting, process, VlFileLineDebug{filename, lineno}};
}
};

View File

@ -233,22 +233,41 @@ private:
};
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)
CallbackRecord(initCb_t cb, void* userp)
const bool m_isLibInstance; // Whether the callback is for a --lib-create instance
const std::string m_name; // The name of the instance callback is for
const uint32_t m_nTraceCodes; // The number of trace codes used by callback
CallbackRecord(initCb_t cb, void* userp, bool isLibInstance, const std::string& name,
uint32_t nTraceCodes)
: m_initCb{cb}
, m_fidx{0}
, m_userp{userp} {}
, m_userp{userp}
, m_isLibInstance{isLibInstance}
, m_name{name}
, m_nTraceCodes{nTraceCodes} {}
CallbackRecord(dumpCb_t cb, uint32_t fidx, void* userp)
: m_dumpCb{cb}
, m_fidx{fidx}
, m_userp{userp} {}
, m_userp{userp}
, m_isLibInstance{false} // Don't care
, m_name{} // Don't care
, m_nTraceCodes{0} // Don't care
{}
CallbackRecord(dumpOffloadCb_t cb, uint32_t fidx, void* userp)
: m_dumpOffloadCb{cb}
, m_fidx{fidx}
, m_userp{userp} {}
, m_userp{userp}
, m_isLibInstance{false} // Don't care
, m_name{} // Don't care
, m_nTraceCodes{0} // Don't care
{}
CallbackRecord(cleanupCb_t cb, void* userp)
: m_cleanupCb{cb}
, m_fidx{0}
, m_userp{userp} {}
, m_userp{userp}
, m_isLibInstance{false} // Don't care
, m_name{} // Don't care
, m_nTraceCodes{0} // Don't care
{}
};
bool m_offload = false; // Use the offload thread
@ -292,6 +311,7 @@ private:
uint32_t m_nextCode = 0; // Next code number to assign
uint32_t m_numSignals = 0; // Number of distinct signals
uint32_t m_maxBits = 0; // Number of bits in the widest signal
void* m_initUserp = nullptr; // The callback userp of the instance currently being initialized
// TODO: Should keep this as a Trie, that is how it's accessed all the time.
std::vector<std::pair<int, std::string>> m_dumpvars; // dumpvar() entries
double m_timeRes = 1e-9; // Time resolution (ns/ms etc)
@ -359,6 +379,7 @@ protected:
uint32_t nextCode() const { return m_nextCode; }
uint32_t numSignals() const { return m_numSignals; }
uint32_t maxBits() const { return m_maxBits; }
void* initUserp() const { return m_initUserp; }
void constDump(bool value) { m_constDump = value; }
void fullDump(bool value) { m_fullDump = value; }
@ -429,7 +450,8 @@ public:
// Non-hot path internal interface to Verilator generated code
void addModel(VerilatedModel*) VL_MT_SAFE_EXCLUDES(m_mutex);
void addInitCb(initCb_t cb, void* userp) VL_MT_SAFE;
void addInitCb(initCb_t cb, void* userp, const std::string& name, bool isLibInstance,
uint32_t nTraceCodes) VL_MT_SAFE;
void addConstCb(dumpCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
void addConstCb(dumpOffloadCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
void addFullCb(dumpCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
@ -437,6 +459,7 @@ public:
void addChgCb(dumpCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
void addChgCb(dumpOffloadCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
void addCleanupCb(cleanupCb_t cb, void* userp) VL_MT_SAFE;
void initLib(const std::string& name) VL_MT_UNSAFE;
};
//=============================================================================

View File

@ -312,10 +312,17 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::traceInit() VL_MT_UNSAFE {
m_maxBits = 0;
m_sigs_enabledVec.clear();
// Call all initialize callbacks, which will:
// Call all initialize callbacks for root (non-library) instances, which will:
// - Call decl* for each signal (these eventually call ::declCode)
// - Call the initialize callbacks of library instances underneath
// - Store the base code
for (const CallbackRecord& cbr : m_initCbs) cbr.m_initCb(cbr.m_userp, self(), nextCode());
for (const CallbackRecord& cbr : m_initCbs) {
if (cbr.m_isLibInstance) continue; // Will be called from parent callback
const uint32_t baseCode = nextCode();
m_nextCode += cbr.m_nTraceCodes;
m_initUserp = cbr.m_userp;
cbr.m_initCb(cbr.m_userp, self(), baseCode);
}
if (expectedCodes && nextCode() != expectedCodes) {
VL_FATAL_MT(__FILE__, __LINE__, "",
@ -352,8 +359,8 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::traceInit() VL_MT_UNSAFE {
// each signal, which is 'nextCode()' entries after the init callbacks
// above have been run, plus up to 2 more words of metadata per signal,
// plus fixed overhead of 1 for a termination flag and 3 for a time stamp
// update.
m_offloadBufferSize = nextCode() + numSignals() * 2 + 4;
// update and 2 for the buffer address.
m_offloadBufferSize = nextCode() + numSignals() * 2 + 6;
// Start the worker thread
m_workerThread.reset(
@ -393,8 +400,6 @@ bool VerilatedTrace<VL_SUB_T, VL_BUF_T>::declCode(uint32_t code, const std::stri
break;
}
int codesNeeded = VL_WORDS_I(bits);
m_nextCode = std::max(m_nextCode, code + codesNeeded);
++m_numSignals;
m_maxBits = std::max(m_maxBits, bits);
return enabled;
@ -680,8 +685,10 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addCallbackRecord(std::vector<CallbackR
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addInitCb(initCb_t cb, void* userp) VL_MT_SAFE {
addCallbackRecord(m_initCbs, CallbackRecord{cb, userp});
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addInitCb(initCb_t cb, void* userp,
const std::string& name, bool isLibInstance,
uint32_t nTraceCodes) VL_MT_SAFE {
addCallbackRecord(m_initCbs, CallbackRecord{cb, userp, isLibInstance, name, nTraceCodes});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addConstCb(dumpCb_t cb, uint32_t fidx,
@ -718,6 +725,20 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addCleanupCb(cleanupCb_t cb, void* user
addCallbackRecord(m_cleanupCbs, CallbackRecord{cb, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::initLib(const std::string& name) VL_MT_SAFE {
// Note it's possible the instance doesn't exist if the lib was compiled without tracing
void* const prevInitUserp = m_initUserp;
for (const CallbackRecord& cbr : m_initCbs) {
if (cbr.m_name != name) continue;
const uint32_t baseCode = nextCode();
m_nextCode += cbr.m_nTraceCodes;
m_initUserp = cbr.m_userp;
cbr.m_initCb(cbr.m_userp, self(), baseCode);
m_initUserp = prevInitUserp;
}
}
//=========================================================================
// Primitives converting binary values to strings...

View File

@ -173,20 +173,22 @@ class VerilatedVpioVarBase VL_NOT_FINAL : public VerilatedVpio {
protected:
const VerilatedVar* m_varp = nullptr;
const VerilatedScope* m_scopep = nullptr;
std::string m_fullname;
// Usually empty, only gets filled when fullname() is called. Has to be stored as a member so
// the char* that fullname() returns is not a temporary.
mutable std::string m_fullname;
int32_t m_indexedDim = -1;
const VerilatedRange* get_range() const { return m_varp->range(m_indexedDim + 1); }
public:
VerilatedVpioVarBase(const VerilatedVar* varp, const VerilatedScope* scopep)
: m_varp{varp}
, m_scopep{scopep}
, m_fullname{std::string{m_scopep->name()} + '.' + m_varp->name()} {}
, m_scopep{scopep} {}
explicit VerilatedVpioVarBase(const VerilatedVpioVarBase* varp) {
if (varp) {
m_varp = varp->m_varp;
m_scopep = varp->m_scopep;
m_fullname = varp->m_fullname;
m_indexedDim = varp->m_indexedDim;
}
}
@ -223,7 +225,10 @@ public:
}
const VerilatedRange* rangep() const override { return get_range(); }
const char* name() const override { return m_varp->name(); }
const char* fullname() const override { return m_fullname.c_str(); }
const char* fullname() const override {
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 {
VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_UINT8););
@ -1303,7 +1308,7 @@ auto VerilatedVpiImp::getForceControlSignals(const VerilatedVpioVarBase* const v
VerilatedVpioVar* forceValueSignalVop = VerilatedVpioVar::castp(forceValueSignalp);
if (VL_UNLIKELY(!forceEnableSignalVop)) {
VL_VPI_ERROR_(__FILE__, __LINE__,
"%s: vpi force or release requested for '%s', but vpiHandle '%p' of enable "
"%s: VPI force or release requested for '%s', but vpiHandle '%p' of enable "
"signal '%s' could not be cast to VerilatedVpioVar*. Ensure signal is "
"marked as forceable",
__func__, signalName.c_str(), forceEnableSignalp,
@ -1311,7 +1316,7 @@ auto VerilatedVpiImp::getForceControlSignals(const VerilatedVpioVarBase* const v
}
if (VL_UNLIKELY(!forceValueSignalVop)) {
VL_VPI_ERROR_(__FILE__, __LINE__,
"%s: vpi force or release requested for '%s', but vpiHandle '%p' of value "
"%s: VPI force or release requested for '%s', but vpiHandle '%p' of value "
"signal '%s' could not be cast to VerilatedVpioVar*. Ensure signal is "
"marked as forceable",
__func__, signalName.c_str(), forceValueSignalp,
@ -2095,7 +2100,7 @@ vpiHandle vpi_register_cb(p_cb_data cb_data_p) {
VL_VPI_ERROR_RESET_();
// cppcheck-suppress nullPointer
if (VL_UNLIKELY(!cb_data_p)) {
VL_VPI_WARNING_(__FILE__, __LINE__, "%s : callback data pointer is null", __func__);
VL_VPI_WARNING_(__FILE__, __LINE__, "%s: VPI callback data pointer is null", __func__);
return nullptr;
}
const PLI_INT32 reason = cb_data_p->reason;
@ -2497,8 +2502,8 @@ void vpi_get_delays(vpiHandle /*object*/, p_vpi_delay /*delay_p*/) { VL_VPI_UNIM
void vpi_put_delays(vpiHandle /*object*/, p_vpi_delay /*delay_p*/) { VL_VPI_UNIMP_(); }
// value processing
bool vl_check_format(const VerilatedVar* varp, const p_vpi_value valuep, const char* fullname,
bool isGetValue) {
bool vl_check_format(const VerilatedVpioVarBase* vop, const p_vpi_value valuep, bool isGetValue) {
const VerilatedVar* varp = vop->varp();
bool status = true;
if ((valuep->format == vpiVectorVal) || (valuep->format == vpiBinStrVal)
|| (valuep->format == vpiOctStrVal) || (valuep->format == vpiHexStrVal)) {
@ -2555,7 +2560,7 @@ bool vl_check_format(const VerilatedVar* varp, const p_vpi_value valuep, const c
status = false;
}
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported format (%s) for %s", __func__,
VerilatedVpiError::strFromVpiVal(valuep->format), fullname);
VerilatedVpiError::strFromVpiVal(valuep->format), vop->fullname());
return status;
}
@ -2705,9 +2710,8 @@ 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();
const char* fullname = vop->fullname();
if (!vl_check_format(varp, valuep, fullname, true)) return;
if (!vl_check_format(vop, valuep, true)) return;
// string data type is dynamic and may vary in size during simulation
static thread_local std::string t_outDynamicStr;
@ -2894,7 +2898,7 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) {
return;
}
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported format (%s) as requested for %s", __func__,
VerilatedVpiError::strFromVpiVal(valuep->format), fullname);
VerilatedVpiError::strFromVpiVal(valuep->format), vop->fullname());
}
void vpi_get_value(vpiHandle object, p_vpi_value valuep) {
@ -2955,8 +2959,7 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_
baseSignalVop->fullname());
return nullptr;
}
if (!vl_check_format(baseSignalVop->varp(), valuep, baseSignalVop->fullname(), false))
return nullptr;
if (!vl_check_format(baseSignalVop, valuep, false)) return nullptr;
if (delay_mode == vpiInertialDelay) {
if (!VerilatedVpiPutHolder::canInertialDelay(valuep)) {
VL_VPI_WARNING_(
@ -3410,7 +3413,7 @@ void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
const unsigned size = vop->size();
if (VL_UNCOVERABLE(num > size)) {
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: requested elements (%u) exceed array size (%u)",
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Requested elements (%u) exceed array size (%u)",
__func__, num, size);
return;
}
@ -3610,7 +3613,7 @@ void vpi_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, PLI_IN
const int lowRange = vop->rangep()->low();
const int highRange = vop->rangep()->high();
if ((index_p[0] > highRange) || (index_p[0] < lowRange)) {
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: index %u for object %s is out of bounds [%u,%u]",
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Index %u for object '%s' is out of bounds [%u,%u]",
__func__, index_p[0], vop->fullname(), lowRange, highRange);
return;
}
@ -3634,7 +3637,7 @@ void vl_put_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const P
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,
"%s: Requested elements to set (%u) exceed array size (%u)", __func__, num,
size);
return;
}
@ -3791,7 +3794,7 @@ void vpi_put_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, PLI_IN
const int lowRange = vop->rangep()->low();
const int highRange = vop->rangep()->high();
if ((index_p[0] > highRange) || (index_p[0] < lowRange)) {
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: index %u for object %s is out of bounds [%u,%u]",
VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Index %u for object '%s' is out of bounds [%u,%u]",
__func__, index_p[0], vop->fullname(), lowRange, highRange);
return;
}

View File

@ -58,6 +58,8 @@ def process() -> None:
author = am.group(1)
if re.search(r'antmicro', email):
author += ", Antmicro Ltd."
if re.search(r'tenstorrent', email):
author += ", Testorrent USA, Inc."
if re.search(r'github action', author):
author = ""
continue

View File

@ -47,7 +47,7 @@ def process() -> None:
def print_header() -> None:
print("// DESCR"
"IPTION: Verilator: Concatenated UVM header for internal testing")
print("// SP", "DX-License-Identifier: Apache-2.0")
print("// SP" + "DX-License-Identifier: Apache-2.0")
print("//----------------------------------------------------------------------")
print("// To recreate:")
print("// Using verilator_ext_tests:")

View File

@ -250,6 +250,7 @@ set(COMMON_SOURCES
V3DfgOptimizer.cpp
V3DfgPasses.cpp
V3DfgPeephole.cpp
V3DfgPushDownSels.cpp
V3DfgRegularize.cpp
V3DfgSynthesize.cpp
V3DiagSarif.cpp

View File

@ -266,6 +266,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3DfgOptimizer.o \
V3DfgPasses.o \
V3DfgPeephole.o \
V3DfgPushDownSels.o \
V3DfgRegularize.o \
V3DfgSynthesize.o \
V3DiagSarif.o \

View File

@ -610,6 +610,7 @@ public:
static string vcdName(const string& namein); // Name for printing out to vcd files
string prettyName() const { return prettyName(name()); }
string prettyNameQ() const { return prettyNameQ(name()); }
string verilogName() const { return vpiName(origName()); } // Decoded original Verilog name
// "VARREF" for error messages (NOT dtype's pretty name)
string prettyTypeName() const;
virtual string prettyOperatorName() const { return "operator " + prettyTypeName(); }

View File

@ -815,12 +815,15 @@ public:
RANDOMIZER_CLEARALL,
RANDOMIZER_HARD,
RANDOMIZER_UNIQUE,
RANDOMIZER_MARK_RANDC,
RANDOMIZER_WRITE_VAR,
RNG_GET_RANDSTATE,
RNG_SET_RANDSTATE,
SCHED_ANY_TRIGGERED,
SCHED_AWAITING_CURRENT_TIME,
SCHED_READY,
SCHED_COMMIT,
SCHED_MOVE_TO_RESUME_QUEUE,
SCHED_DELAY,
SCHED_DO_POST_UPDATES,
SCHED_ENQUEUE,
@ -945,12 +948,15 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
{RANDOMIZER_CLEARALL, "clearAll", false}, \
{RANDOMIZER_HARD, "hard", false}, \
{RANDOMIZER_UNIQUE, "rand_unique", false}, \
{RANDOMIZER_MARK_RANDC, "markRandc", false}, \
{RANDOMIZER_WRITE_VAR, "write_var", false}, \
{RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true}, \
{RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false}, \
{SCHED_ANY_TRIGGERED, "anyTriggered", false}, \
{SCHED_AWAITING_CURRENT_TIME, "awaitingCurrentTime", true}, \
{SCHED_READY, "ready", false}, \
{SCHED_COMMIT, "commit", false}, \
{SCHED_MOVE_TO_RESUME_QUEUE, "moveToResumeQueue", false}, \
{SCHED_DELAY, "delay", false}, \
{SCHED_DO_POST_UPDATES, "doPostUpdates", false}, \
{SCHED_ENQUEUE, "enqueue", false}, \
@ -1465,6 +1471,7 @@ public:
UNROLL_FULL,
FULL_CASE,
PARALLEL_CASE,
VERILATOR_LIB,
_ENUM_SIZE
};
enum en m_e;
@ -1483,6 +1490,7 @@ public:
"UNROLL_FULL", //
"FULL_CASE", //
"PARALLEL_CASE", //
"VERILATOR_LIB", //
"_ENUM_SIZE" //
};
return names[m_e];

View File

@ -291,7 +291,7 @@ public:
VNumRange declRange() const VL_MT_STABLE { return VNumRange{hi(), lo()}; }
AstNodeModule* classOrPackagep() const { return m_classOrPackagep; }
void classOrPackagep(AstNodeModule* classpackagep) { m_classOrPackagep = classpackagep; }
bool isConstrainedRand() { return m_constrainedRand; }
bool isConstrainedRand() const { return m_constrainedRand; }
void markConstrainedRand(bool flag) { m_constrainedRand = flag; }
};

View File

@ -1349,6 +1349,7 @@ class AstExprStmt final : public AstNodeExpr {
// @astgen op2 := resultp : AstNodeExpr
private:
bool m_hasResult = true;
bool m_containsTimingControl = false;
public:
AstExprStmt(FileLine* fl, AstNode* stmtsp, AstNodeExpr* resultp)
@ -1367,8 +1368,10 @@ public:
return resultp()->isPure();
}
bool sameNode(const AstNode*) const override { return true; }
bool hasResult() { return m_hasResult; }
bool hasResult() const { return m_hasResult; }
void hasResult(bool flag) { m_hasResult = flag; }
void setTimingControl() { m_containsTimingControl = true; }
bool isTimingControl() const override { return m_containsTimingControl; }
};
class AstFError final : public AstNodeExpr {
// @astgen op1 := filep : AstNode
@ -4172,6 +4175,7 @@ public:
}
string emitVerilog() override { return "%k(%l %f* %r)"; }
string emitC() override { return "VL_MULS_%nq%lq%rq(%lw, %P, %li, %ri)"; }
string emitSMT() const override { return "(bvmul %l %r)"; }
string emitSimpleOperator() override { return ""; }
bool emitCheckMaxWords() override { return true; }
bool cleanOut() const override { return false; }

View File

@ -285,6 +285,7 @@ class AstNodeModule VL_NOT_FINAL : public AstNode {
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_verilatorLib : 1; // Module is a stub for a Verilator produced --lib-create
protected:
AstNodeModule(VNType t, FileLine* fl, const string& name, const string& libname)
: AstNode{t, fl}
@ -301,7 +302,8 @@ protected:
, m_hierParams{false}
, m_internal{false}
, m_recursive{false}
, m_recursiveClone{false} {}
, m_recursiveClone{false}
, m_verilatorLib{false} {}
public:
ASTGEN_MEMBERS_AstNodeModule;
@ -343,6 +345,8 @@ public:
void recursive(bool flag) { m_recursive = flag; }
void recursiveClone(bool flag) { m_recursiveClone = flag; }
bool recursiveClone() const { return m_recursiveClone; }
void verilatorLib(bool flag) { m_verilatorLib = flag; }
bool verilatorLib() const { return m_verilatorLib; }
VLifetime lifetime() const { return m_lifetime; }
void lifetime(const VLifetime& flag) { m_lifetime = flag; }
VTimescale timeunit() const { return m_timeunit; }
@ -1254,6 +1258,7 @@ class AstNetlist final : public AstNode {
VTimescale m_timeunit; // Global time unit
VTimescale m_timeprecision; // Global time precision
bool m_timescaleSpecified = false; // Input HDL specified timescale
uint32_t m_nTraceCodes = 0; // Number of trace codes used by design
public:
AstNetlist();
ASTGEN_MEMBERS_AstNetlist;
@ -1295,6 +1300,8 @@ public:
void timeprecisionMerge(FileLine*, const VTimescale& value);
void timescaleSpecified(bool specified) { m_timescaleSpecified = specified; }
bool timescaleSpecified() const { return m_timescaleSpecified; }
uint32_t nTraceCodes() const { return m_nTraceCodes; }
void nTraceCodes(uint32_t value) { m_nTraceCodes = value; }
AstVarScope* stlFirstIterationp();
void clearStlFirstIterationp() { m_stlFirstIterationp = nullptr; }
// NBA queue pairs for pre-resume commits
@ -2116,26 +2123,26 @@ public:
if (flag) m_funcLocalSticky = true;
}
void funcReturn(bool flag) { m_funcReturn = flag; }
bool gotNansiType() const { return m_gotNansiType; }
void gotNansiType(bool flag) { m_gotNansiType = flag; }
bool gotNansiType() { return m_gotNansiType; }
bool hasStrengthAssignment() const { return m_hasStrengthAssignment; }
void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; }
bool hasStrengthAssignment() { return m_hasStrengthAssignment; }
void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; }
bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; }
void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; }
bool isHideLocal() const { return m_isHideLocal; }
void isHideLocal(bool flag) { m_isHideLocal = flag; }
bool isHideProtected() const { return m_isHideProtected; }
void isHideProtected(bool flag) { m_isHideProtected = flag; }
void noCReset(bool flag) { m_noCReset = flag; }
bool noCReset() const { return m_noCReset; }
void noReset(bool flag) { m_noReset = flag; }
void noCReset(bool flag) { m_noCReset = flag; }
bool noReset() const { return m_noReset; }
void noSubst(bool flag) { m_noSubst = flag; }
void noReset(bool flag) { m_noReset = flag; }
bool noSubst() const { return m_noSubst; }
void substConstOnly(bool flag) { m_substConstOnly = flag; }
void noSubst(bool flag) { m_noSubst = flag; }
bool substConstOnly() const { return m_substConstOnly; }
void overriddenParam(bool flag) { m_overridenParam = flag; }
void substConstOnly(bool flag) { m_substConstOnly = flag; }
bool overriddenParam() const { return m_overridenParam; }
void overriddenParam(bool flag) { m_overridenParam = flag; }
void trace(bool flag) { m_trace = flag; }
void isLatched(bool flag) { m_isLatched = flag; }
bool isForceable() const { return m_isForceable; }

View File

@ -1180,7 +1180,7 @@ class AstTraceDecl final : public AstNodeStmt {
// Parents: {statement list}
// Expression being traced - Moved to AstTraceInc by V3Trace
// @astgen op1 := valuep : Optional[AstNodeExpr]
uint32_t m_code{0}; // Trace identifier code
uint32_t m_code{std::numeric_limits<uint32_t>::max()}; // Trace identifier code
uint32_t m_fidx{0}; // Trace function index
const string m_showname; // Name of variable
const VNumRange m_bitRange; // Property of var the trace details
@ -1212,6 +1212,7 @@ public:
// Details on what we're tracing
uint32_t code() const { return m_code; }
void code(uint32_t code) { m_code = code; }
bool codeAssigned() const { return m_code != std::numeric_limits<uint32_t>::max(); }
uint32_t fidx() const { return m_fidx; }
void fidx(uint32_t fidx) { m_fidx = fidx; }
uint32_t codeInc() const {

View File

@ -56,7 +56,6 @@ void AstNode::dumpJsonPtr(std::ostream& os, const std::string& name, const AstNo
// Shorthands for dumping fields that use func name as key
#define dumpJsonNumFunc(os, func) dumpJsonNum(os, #func, func())
#define dumpJsonBoolFunc(os, func) dumpJsonBool(os, #func, func())
#define dumpJsonBoolFuncIf(os, func) dumpJsonBoolIf(os, #func, func())
#define dumpJsonStrFunc(os, func) dumpJsonStr(os, #func, func())
#define dumpJsonPtrFunc(os, func) dumpJsonPtr(os, #func, func())
@ -402,7 +401,7 @@ void AstCReset::dump(std::ostream& str) const {
if (constructing()) str << " [CONS]";
}
void AstCReset::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, constructing);
dumpJsonBoolFuncIf(str, constructing);
dumpJsonGen(str);
}
@ -444,8 +443,8 @@ void AstConsDynArray::dump(std::ostream& str) const {
if (rhsIsValue()) str << " [RVAL]";
}
void AstConsDynArray::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, lhsIsValue);
dumpJsonBoolFunc(str, rhsIsValue);
dumpJsonBoolFuncIf(str, lhsIsValue);
dumpJsonBoolFuncIf(str, rhsIsValue);
dumpJsonGen(str);
}
@ -455,8 +454,8 @@ void AstConsQueue::dump(std::ostream& str) const {
if (rhsIsValue()) str << " [RVAL]";
}
void AstConsQueue::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, lhsIsValue);
dumpJsonBoolFunc(str, rhsIsValue);
dumpJsonBoolFuncIf(str, lhsIsValue);
dumpJsonBoolFuncIf(str, rhsIsValue);
dumpJsonGen(str);
}
void AstConstraint::dump(std::ostream& str) const {
@ -470,11 +469,11 @@ void AstConstraint::dump(std::ostream& str) const {
if (isStatic()) str << " [STATIC]";
}
void AstConstraint::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isExternDef);
dumpJsonBoolFunc(str, isExternExplicit);
dumpJsonBoolFunc(str, isExternProto);
dumpJsonBoolFunc(str, isKwdPure);
dumpJsonBoolFunc(str, isStatic);
dumpJsonBoolFuncIf(str, isExternDef);
dumpJsonBoolFuncIf(str, isExternExplicit);
dumpJsonBoolFuncIf(str, isExternProto);
dumpJsonBoolFuncIf(str, isKwdPure);
dumpJsonBoolFuncIf(str, isStatic);
if (baseOverride().isAny()) dumpJsonStr(str, "baseOverride", baseOverride().ascii());
dumpJsonGen(str);
}
@ -484,8 +483,8 @@ void AstConstraintExpr::dump(std::ostream& str) const {
if (isSoft()) str << " [SOFT]";
}
void AstConstraintExpr::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isDisableSoft);
dumpJsonBoolFunc(str, isSoft);
dumpJsonBoolFuncIf(str, isDisableSoft);
dumpJsonBoolFuncIf(str, isSoft);
dumpJsonGen(str);
}
AstConst* AstConst::parseParamLiteral(FileLine* fl, const string& literal) {
@ -544,8 +543,8 @@ void AstNew::dump(std::ostream& str) const {
if (isScoped()) str << " [SCOPED]";
}
void AstNew::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isImplicit);
dumpJsonBoolFunc(str, isScoped);
dumpJsonBoolFuncIf(str, isImplicit);
dumpJsonBoolFuncIf(str, isScoped);
dumpJsonGen(str);
}
@ -1672,8 +1671,8 @@ void AstNodeProcedure::dump(std::ostream& str) const {
}
void AstNodeProcedure::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isSuspendable);
dumpJsonBoolFunc(str, needProcess);
dumpJsonBoolFuncIf(str, isSuspendable);
dumpJsonBoolFuncIf(str, needProcess);
dumpJsonGen(str);
}
@ -1777,7 +1776,7 @@ void AstCvtArrayToArray::dump(std::ostream& str) const {
str << " srcElementBits=" << srcElementBits();
}
void AstCvtArrayToArray::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, reverse);
dumpJsonBoolFuncIf(str, reverse);
dumpJsonNumFunc(str, blockSize);
dumpJsonNumFunc(str, dstElementBits);
dumpJsonNumFunc(str, srcElementBits);
@ -1795,7 +1794,8 @@ void AstCell::dump(std::ostream& str) const {
}
void AstCell::dumpJson(std::ostream& str) const {
dumpJsonStrFunc(str, origName);
dumpJsonBoolFunc(str, recursive);
dumpJsonStrFunc(str, verilogName);
dumpJsonBoolFuncIf(str, recursive);
dumpJsonGen(str);
}
void AstCellInline::dump(std::ostream& str) const {
@ -1847,9 +1847,9 @@ void AstClass::dump(std::ostream& str) const {
void AstClass::dumpJson(std::ostream& str) const {
// dumpJsonNumFunc(str, declTokenNum); // Not dumped as adding token changes whole file
dumpJsonBoolFuncIf(str, isCovergroup);
dumpJsonBoolFunc(str, isExtended);
dumpJsonBoolFunc(str, isInterfaceClass);
dumpJsonBoolFunc(str, isVirtual);
dumpJsonBoolFuncIf(str, isExtended);
dumpJsonBoolFuncIf(str, isInterfaceClass);
dumpJsonBoolFuncIf(str, isVirtual);
if (baseOverride().isAny()) dumpJsonStr(str, "baseOverride", baseOverride().ascii());
dumpJsonGen(str);
}
@ -1858,7 +1858,7 @@ void AstClassExtends::dump(std::ostream& str) const {
if (isImplements()) str << " [IMPLEMENTS]";
}
void AstClassExtends::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isImplements);
dumpJsonBoolFuncIf(str, isImplements);
dumpJsonGen(str);
}
AstClass* AstClassExtends::classOrNullp() const {
@ -1907,8 +1907,8 @@ void AstClocking::dump(std::ostream& str) const {
if (isGlobal()) str << " [GLOBAL]";
}
void AstClocking::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isDefault);
dumpJsonBoolFunc(str, isGlobal);
dumpJsonBoolFuncIf(str, isDefault);
dumpJsonBoolFuncIf(str, isGlobal);
dumpJsonGen(str);
}
void AstConfig::dump(std::ostream& str) const {
@ -1926,7 +1926,7 @@ void AstConfigRule::dump(std::ostream& str) const {
if (isCell()) str << " [CELL]";
}
void AstConfigRule::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isCell);
dumpJsonBoolFuncIf(str, isCell);
dumpJsonGen(str);
}
void AstConfigUse::dump(std::ostream& str) const {
@ -1934,7 +1934,7 @@ void AstConfigUse::dump(std::ostream& str) const {
if (isConfig()) str << " [CONFIG]";
}
void AstConfigUse::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isConfig);
dumpJsonBoolFuncIf(str, isConfig);
dumpJsonGen(str);
}
void AstDisplay::dump(std::ostream& str) const {
@ -1947,7 +1947,7 @@ void AstEnumDType::dump(std::ostream& str) const {
str << " enum";
}
void AstEnumDType::dumpJson(std::ostream& str) const {
dumpJsonBool(str, "enum", 1);
dumpJsonBoolIf(str, "enum", 1);
dumpJsonGen(str);
}
void AstEnumDType::dumpSmall(std::ostream& str) const {
@ -1998,8 +1998,8 @@ void AstGenBlock::dump(std::ostream& str) const {
if (unnamed()) str << " [UNNAMED]";
}
void AstGenBlock::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, implied);
dumpJsonBoolFunc(str, unnamed);
dumpJsonBoolFuncIf(str, implied);
dumpJsonBoolFuncIf(str, unnamed);
dumpJsonGen(str);
}
@ -2021,8 +2021,8 @@ void AstIfaceRefDType::dump(std::ostream& str) const {
}
}
void AstIfaceRefDType::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isPortDecl);
dumpJsonBoolFunc(str, isVirtual);
dumpJsonBoolFuncIf(str, isPortDecl);
dumpJsonBoolFuncIf(str, isVirtual);
dumpJsonStrFunc(str, cellName);
dumpJsonStrFunc(str, ifaceName);
dumpJsonStrFunc(str, modportName);
@ -2139,7 +2139,7 @@ void AstMemberDType::dump(std::ostream& str) const {
}
void AstMemberDType::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isConstrainedRand);
dumpJsonBoolFuncIf(str, isConstrainedRand);
dumpJsonStrFunc(str, name);
dumpJsonStrFunc(str, tag);
dumpJsonGen(str);
@ -2202,8 +2202,8 @@ void AstModportFTaskRef::dump(std::ostream& str) const {
}
}
void AstModportFTaskRef::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isExport);
dumpJsonBoolFunc(str, isImport);
dumpJsonBoolFuncIf(str, isExport);
dumpJsonBoolFuncIf(str, isImport);
dumpJsonGen(str);
}
void AstModportVarRef::dump(std::ostream& str) const {
@ -2227,9 +2227,9 @@ void AstModule::dump(std::ostream& str) const {
if (hasGenericIface()) str << " [HASGENERICIFACE]";
}
void AstModule::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isChecker);
dumpJsonBoolFunc(str, isProgram);
dumpJsonBoolFunc(str, hasGenericIface);
dumpJsonBoolFuncIf(str, isChecker);
dumpJsonBoolFuncIf(str, isProgram);
dumpJsonBoolFuncIf(str, hasGenericIface);
dumpJsonGen(str);
}
void AstPin::dump(std::ostream& str) const {
@ -2244,8 +2244,8 @@ void AstPin::dump(std::ostream& str) const {
if (svImplicit()) str << " [.SV]";
}
void AstPin::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, svDotName);
dumpJsonBoolFunc(str, svImplicit);
dumpJsonBoolFuncIf(str, svDotName);
dumpJsonBoolFuncIf(str, svImplicit);
dumpJsonGen(str);
}
string AstPin::prettyOperatorName() const {
@ -2312,8 +2312,8 @@ void AstTypedef::dump(std::ostream& str) const {
}
void AstTypedef::dumpJson(std::ostream& str) const {
// dumpJsonNumFunc(str, declTokenNum); // Not dumped as adding token changes whole file
dumpJsonBoolFunc(str, attrPublic);
dumpJsonBoolFunc(str, isUnderClass);
dumpJsonBoolFuncIf(str, attrPublic);
dumpJsonBoolFuncIf(str, isUnderClass);
dumpJsonGen(str);
}
void AstTypedefFwd::dump(std::ostream& str) const {
@ -2340,7 +2340,7 @@ void AstRange::dump(std::ostream& str) const {
if (ascending()) str << " [ASCENDING]";
}
void AstRSProdList::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, randJoin);
dumpJsonBoolFuncIf(str, randJoin);
dumpJsonGen(str);
}
void AstRSProdList::dump(std::ostream& str) const {
@ -2348,8 +2348,8 @@ void AstRSProdList::dump(std::ostream& str) const {
if (randJoin()) str << " [RANDJOIN]";
}
void AstRange::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, ascending);
dumpJsonBoolFunc(str, fromBracket);
dumpJsonBoolFuncIf(str, ascending);
dumpJsonBoolFuncIf(str, fromBracket);
dumpJsonGen(str);
}
void AstParamTypeDType::dump(std::ostream& str) const {
@ -2401,8 +2401,8 @@ void AstNodeUOrStructDType::dump(std::ostream& str) const {
if (classOrPackagep()) str << " pkg=" << nodeAddr(classOrPackagep());
}
void AstNodeUOrStructDType::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, packed);
dumpJsonBoolFunc(str, isFourstate);
dumpJsonBoolFuncIf(str, packed);
dumpJsonBoolFuncIf(str, isFourstate);
dumpJsonGen(str);
}
void AstUnionDType::dump(std::ostream& str) const {
@ -2412,8 +2412,8 @@ void AstUnionDType::dump(std::ostream& str) const {
}
void AstUnionDType::dumpJson(std::ostream& str) const {
this->AstNodeUOrStructDType::dumpJson(str);
dumpJsonBoolFunc(str, isSoft);
dumpJsonBoolFunc(str, isTagged);
dumpJsonBoolFuncIf(str, isSoft);
dumpJsonBoolFuncIf(str, isTagged);
}
bool AstUnionDType::sameNode(const AstNode* samep) const {
const AstUnionDType* const asamep = VN_DBG_AS(samep, UnionDType);
@ -2440,8 +2440,8 @@ void AstNodeDType::dump(std::ostream& str) const {
}
}
void AstNodeDType::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, generic);
if (isSigned() && !isDouble()) dumpJsonBool(str, "signed", 1);
dumpJsonBoolFuncIf(str, generic);
if (isSigned() && !isDouble()) dumpJsonBoolIf(str, "signed", 1);
dumpJsonGen(str);
}
void AstNodeDType::dumpSmall(std::ostream& str) const VL_MT_STABLE {
@ -2467,7 +2467,7 @@ void AstNodeArrayDType::dump(std::ostream& str) const {
str << " " << declRange();
}
void AstNodeArrayDType::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isCompound);
dumpJsonBoolFuncIf(str, isCompound);
dumpJsonStr(str, "declRange", cvtToStr(declRange()));
dumpJsonGen(str);
}
@ -2567,17 +2567,20 @@ void AstNodeModule::dump(std::ostream& str) const {
} else if (recursive()) {
str << " [RECURSIVE]";
}
if (verilatorLib()) str << " [VERILATOR-LIB]";
str << " [" << timeunit() << "]";
if (libname() != "work") str << " libname=" << libname();
}
void AstNodeModule::dumpJson(std::ostream& str) const {
dumpJsonStrFunc(str, origName);
dumpJsonStrFunc(str, verilogName);
dumpJsonNumFunc(str, level);
dumpJsonBoolFunc(str, modPublic);
dumpJsonBoolFunc(str, inLibrary);
dumpJsonBoolFunc(str, dead);
dumpJsonBoolFunc(str, recursiveClone);
dumpJsonBoolFunc(str, recursive);
dumpJsonBoolFuncIf(str, modPublic);
dumpJsonBoolFuncIf(str, inLibrary);
dumpJsonBoolFuncIf(str, dead);
dumpJsonBoolFuncIf(str, recursiveClone);
dumpJsonBoolFuncIf(str, recursive);
dumpJsonBoolFuncIf(str, verilatorLib);
dumpJsonStr(str, "timeunit", timeunit().ascii());
if (libname() != "work") dumpJsonStr(str, "libname=", libname());
dumpJsonGen(str);
@ -2750,7 +2753,7 @@ void AstVarScope::dump(std::ostream& str) const {
}
}
void AstVarScope::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isTrace);
dumpJsonBoolFuncIf(str, isTrace);
dumpJsonGen(str);
}
bool AstVarScope::sameNode(const AstNode* samep) const {
@ -2801,7 +2804,7 @@ void AstVarXRef::dump(std::ostream& str) const {
}
}
void AstVarXRef::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, containsGenBlock);
dumpJsonBoolFuncIf(str, containsGenBlock);
dumpJsonStrFunc(str, dotted);
dumpJsonStrFunc(str, inlinedDots);
dumpJsonGen(str);
@ -2865,35 +2868,36 @@ void AstVar::dump(std::ostream& str) const {
}
void AstVar::dumpJson(std::ostream& str) const {
dumpJsonStrFunc(str, origName);
dumpJsonBoolFunc(str, isSc);
dumpJsonBoolFunc(str, isPrimaryIO);
dumpJsonBoolFunc(str, isPrimaryClock);
dumpJsonStrFunc(str, verilogName);
dumpJsonBoolFuncIf(str, isSc);
dumpJsonBoolFuncIf(str, isPrimaryIO);
dumpJsonBoolFuncIf(str, isPrimaryClock);
dumpJsonStr(str, "direction", direction().ascii());
dumpJsonBoolFunc(str, isConst);
dumpJsonBoolFunc(str, isPullup);
dumpJsonBoolFunc(str, isPulldown);
dumpJsonBoolFunc(str, isSigPublic);
dumpJsonBoolFunc(str, isLatched);
dumpJsonBoolFunc(str, isUsedLoopIdx);
dumpJsonBoolFuncIf(str, isConst);
dumpJsonBoolFuncIf(str, isPullup);
dumpJsonBoolFuncIf(str, isPulldown);
dumpJsonBoolFuncIf(str, isSigPublic);
dumpJsonBoolFuncIf(str, isLatched);
dumpJsonBoolFuncIf(str, isUsedLoopIdx);
dumpJsonBoolFuncIf(str, noCReset);
dumpJsonBoolFunc(str, noReset);
dumpJsonBoolFunc(str, attrIsolateAssign);
dumpJsonBoolFunc(str, attrFileDescr);
dumpJsonBoolFunc(str, isDpiOpenArray);
dumpJsonBoolFunc(str, isFuncReturn);
dumpJsonBoolFunc(str, isFuncLocal);
dumpJsonBoolFunc(str, isStdRandomizeArg);
dumpJsonBoolFuncIf(str, noReset);
dumpJsonBoolFuncIf(str, attrIsolateAssign);
dumpJsonBoolFuncIf(str, attrFileDescr);
dumpJsonBoolFuncIf(str, isDpiOpenArray);
dumpJsonBoolFuncIf(str, isFuncReturn);
dumpJsonBoolFuncIf(str, isFuncLocal);
dumpJsonBoolFuncIf(str, isStdRandomizeArg);
dumpJsonStr(str, "lifetime", lifetime().ascii());
dumpJsonStr(str, "varType", varType().ascii());
if (dtypep()) dumpJsonStr(str, "dtypeName", dtypep()->name());
dumpJsonBoolFunc(str, isSigUserRdPublic);
dumpJsonBoolFunc(str, isSigUserRWPublic);
dumpJsonBoolFunc(str, isGParam);
dumpJsonBoolFunc(str, isParam);
dumpJsonBoolFunc(str, attrScBv);
dumpJsonBoolFunc(str, attrSFormat);
dumpJsonBoolFunc(str, ignorePostWrite);
dumpJsonBoolFunc(str, ignoreSchedWrite);
dumpJsonBoolFuncIf(str, isSigUserRdPublic);
dumpJsonBoolFuncIf(str, isSigUserRWPublic);
dumpJsonBoolFuncIf(str, isGParam);
dumpJsonBoolFuncIf(str, isParam);
dumpJsonBoolFuncIf(str, attrScBv);
dumpJsonBoolFuncIf(str, attrSFormat);
dumpJsonBoolFuncIf(str, ignorePostWrite);
dumpJsonBoolFuncIf(str, ignoreSchedWrite);
dumpJsonGen(str);
}
void AstScope::dump(std::ostream& str) const {
@ -2918,8 +2922,8 @@ void AstScopeName::dump(std::ostream& str) const {
str << " scopeEntr=\"" << m_scopeEntr << "\"";
}
void AstScopeName::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, dpiExport);
dumpJsonBoolFunc(str, forFormat);
dumpJsonBoolFuncIf(str, dpiExport);
dumpJsonBoolFuncIf(str, forFormat);
dumpJsonStr(str, "scopeAttr", m_scopeAttr);
dumpJsonStr(str, "scopeEntr", m_scopeEntr);
dumpJsonGen(str);
@ -2929,7 +2933,7 @@ void AstSenTree::dump(std::ostream& str) const {
if (isMulti()) str << " [MULTI]";
}
void AstSenTree::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isMulti);
dumpJsonBoolFuncIf(str, isMulti);
dumpJsonGen(str);
}
void AstSenItem::dump(std::ostream& str) const {
@ -2983,7 +2987,7 @@ void AstDot::dump(std::ostream& str) const {
if (colon()) str << " [::]";
}
void AstDot::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, colon);
dumpJsonBoolFuncIf(str, colon);
dumpJsonGen(str);
}
void AstActive::dump(std::ostream& str) const {
@ -3061,18 +3065,18 @@ bool AstNodeFTask::getPurityRecurse() const {
return true;
}
void AstNodeFTask::dumpJson(std::ostream& str) const {
dumpJsonBool(str, "method", classMethod());
dumpJsonBoolFunc(str, dpiExport);
dumpJsonBoolFunc(str, dpiImport);
dumpJsonBoolFunc(str, dpiOpenChild);
dumpJsonBoolFunc(str, dpiOpenParent);
dumpJsonBoolFunc(str, isExternDef);
dumpJsonBoolFunc(str, isExternProto);
dumpJsonBoolIf(str, "method", classMethod());
dumpJsonBoolFuncIf(str, dpiExport);
dumpJsonBoolFuncIf(str, dpiImport);
dumpJsonBoolFuncIf(str, dpiOpenChild);
dumpJsonBoolFuncIf(str, dpiOpenParent);
dumpJsonBoolFuncIf(str, isExternDef);
dumpJsonBoolFuncIf(str, isExternProto);
dumpJsonBoolFuncIf(str, isVirtual);
dumpJsonBoolFuncIf(str, needProcess);
dumpJsonBoolFunc(str, prototype);
dumpJsonBoolFunc(str, recursive);
dumpJsonBoolFunc(str, taskPublic);
dumpJsonBoolFuncIf(str, prototype);
dumpJsonBoolFuncIf(str, recursive);
dumpJsonBoolFuncIf(str, taskPublic);
if (baseOverride().isAny()) dumpJsonStr(str, "baseOverride", baseOverride().ascii());
dumpJsonStrFunc(str, cname);
dumpJsonGen(str);
@ -3082,7 +3086,7 @@ void AstNodeBlock::dump(std::ostream& str) const {
if (unnamed()) str << " [UNNAMED]";
}
void AstNodeBlock::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, unnamed);
dumpJsonBoolFuncIf(str, unnamed);
dumpJsonGen(str);
}
void AstBegin::dump(std::ostream& str) const {
@ -3091,8 +3095,8 @@ void AstBegin::dump(std::ostream& str) const {
if (needProcess()) str << " [NPRC]";
}
void AstBegin::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, implied);
dumpJsonBoolFunc(str, needProcess);
dumpJsonBoolFuncIf(str, implied);
dumpJsonBoolFuncIf(str, needProcess);
dumpJsonGen(str);
}
void AstNodeCoverDecl::dump(std::ostream& str) const {
@ -3159,7 +3163,7 @@ void AstStop::dump(std::ostream& str) const {
if (isFatal()) str << " [FATAL]";
}
void AstStop::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isFatal);
dumpJsonBoolFuncIf(str, isFatal);
dumpJsonGen(str);
}
void AstTraceDecl::dump(std::ostream& str) const {
@ -3210,8 +3214,8 @@ void AstCFile::dump(std::ostream& str) const {
}
void AstCFile::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, source);
dumpJsonBoolFunc(str, slow);
dumpJsonBoolFuncIf(str, source);
dumpJsonBoolFuncIf(str, slow);
dumpJsonGen(str);
}
void AstCFunc::dump(std::ostream& str) const {
@ -3234,18 +3238,18 @@ void AstCFunc::dump(std::ostream& str) const {
if (entryPoint()) str << " [ENTRY]";
}
void AstCFunc::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, slow);
dumpJsonBoolFunc(str, isStatic);
dumpJsonBoolFunc(str, dpiExportDispatcher);
dumpJsonBoolFunc(str, dpiExportImpl);
dumpJsonBoolFunc(str, dpiImportPrototype);
dumpJsonBoolFunc(str, dpiImportWrapper);
dumpJsonBoolFunc(str, dpiContext);
dumpJsonBoolFunc(str, isConstructor);
dumpJsonBoolFunc(str, isDestructor);
dumpJsonBoolFunc(str, isVirtual);
dumpJsonBoolFunc(str, isCoroutine);
dumpJsonBoolFunc(str, needProcess);
dumpJsonBoolFuncIf(str, slow);
dumpJsonBoolFuncIf(str, isStatic);
dumpJsonBoolFuncIf(str, dpiExportDispatcher);
dumpJsonBoolFuncIf(str, dpiExportImpl);
dumpJsonBoolFuncIf(str, dpiImportPrototype);
dumpJsonBoolFuncIf(str, dpiImportWrapper);
dumpJsonBoolFuncIf(str, dpiContext);
dumpJsonBoolFuncIf(str, isConstructor);
dumpJsonBoolFuncIf(str, isDestructor);
dumpJsonBoolFuncIf(str, isVirtual);
dumpJsonBoolFuncIf(str, isCoroutine);
dumpJsonBoolFuncIf(str, needProcess);
dumpJsonGen(str);
// TODO: maybe try to shorten these flags somehow
}
@ -3324,7 +3328,7 @@ void AstCgOptionAssign::dump(std::ostream& str) const {
this->AstNode::dump(str);
}
void AstCgOptionAssign::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, typeOption);
dumpJsonBoolFuncIf(str, typeOption);
dumpJsonGen(str);
}
void AstDelay::dump(std::ostream& str) const {
@ -3332,7 +3336,7 @@ void AstDelay::dump(std::ostream& str) const {
if (isCycleDelay()) str << " [CYCLE]";
}
void AstDelay::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, isCycleDelay);
dumpJsonBoolFuncIf(str, isCycleDelay);
dumpJsonGen(str);
}

View File

@ -68,7 +68,7 @@ class BeginVisitor final : public VNVisitor {
// STATE - for current visit position (use VL_RESTORER)
AstNodeModule* m_modp = nullptr; // Current module
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
AstNode* m_liftedp = nullptr; // Local nodes we are lifting into m_ftaskp
AstNode* m_liftedp = nullptr; // Local nodes we are lifting into m_ftaskp
string m_displayScope; // Name of %m in $display/AstScopeName
string m_namedScope; // Name of begin blocks above us
string m_unnamedScope; // Name of begin blocks, including unnamed blocks

View File

@ -292,9 +292,12 @@ class DeadVisitor final : public VNVisitor {
void visit(AstVar* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
if (nodep->isSigPublic() && m_modp && VN_IS(m_modp, Package)) m_modp->user1Inc();
if (m_selloopvarsp) nodep->user1Inc();
if (mightElimVar(nodep)) m_varsp.push_back(nodep);
if (mightElimVar(nodep)) {
m_varsp.push_back(nodep);
} else {
if (m_modp && VN_IS(m_modp, Package)) m_modp->user1Inc();
}
}
void visit(AstNodeAssign* nodep) override {
// See if simple assignments to variables may be eliminated because

View File

@ -288,6 +288,12 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
os << '\n';
varVtxp->dtype().astDtypep()->dumpSmall(os);
os << " / F" << varVtxp->fanout();
// Reference flags
os << " / ";
static const char* const rwmn[2][2] = {{"_", "W"}, {"R", "M"}};
os << rwmn[varVtxp->hasExtRdRefs()][varVtxp->hasExtWrRefs()];
os << rwmn[varVtxp->hasModRdRefs()][varVtxp->hasModWrRefs()];
os << (varVtxp->hasDfgRefs() ? "D" : "_");
// End 'label'
os << '"';
// Shape

View File

@ -171,6 +171,22 @@ private:
V3DfgPeepholeContext(V3DfgContext& ctx, const std::string& label) VL_MT_DISABLED;
~V3DfgPeepholeContext() VL_MT_DISABLED;
};
class V3DfgPushDownSelsContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
size_t m_pushedDown = 0; // Number of selects pushed down through concatenations
size_t m_wouldBeCyclic = 0; // Number of selects not pushed due to cycle
private:
V3DfgPushDownSelsContext(V3DfgContext& ctx, const std::string& label)
: V3DfgSubContext{ctx, label, "PushDownSels"} {}
~V3DfgPushDownSelsContext() {
addStat("sels pushed down", m_pushedDown);
addStat("would be cyclic", m_wouldBeCyclic);
}
};
class V3DfgRegularizeContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
@ -348,6 +364,7 @@ public:
V3DfgCseContext m_cseContext1{*this, m_label + " 2nd"};
V3DfgDfgToAstContext m_dfg2AstContext{*this, m_label};
V3DfgPeepholeContext m_peepholeContext{*this, m_label};
V3DfgPushDownSelsContext m_pushDownSelsContext{*this, m_label};
V3DfgRegularizeContext m_regularizeContext{*this, m_label};
V3DfgSynthesisContext m_synthContext{*this, m_label};

View File

@ -371,6 +371,10 @@ void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgContext& ctx) {
run("cse0 ", dumpLvl >= 4, [&]() { cse(dfg, ctx.m_cseContext0); });
run("binToOneHot ", dumpLvl >= 4, [&]() { binToOneHot(dfg, ctx.m_binToOneHotContext); });
run("peephole ", dumpLvl >= 4, [&]() { peephole(dfg, ctx.m_peepholeContext); });
// Run only on final scoped DfgGraphs, as otherwise later DfgPeephole wold just undo this work
if (!dfg.modulep()) {
run("pushDownSels", dumpLvl >= 4, [&]() { pushDownSels(dfg, ctx.m_pushDownSelsContext); });
}
run("cse1 ", dumpLvl >= 4, [&]() { cse(dfg, ctx.m_cseContext1); });
run("output ", dumpLvl >= 3, [&]() { /* debug dump only */ });

View File

@ -76,6 +76,8 @@ uint32_t colorStronglyConnectedComponents(const DfgGraph&, DfgUserMap<uint64_t>&
void cse(DfgGraph&, V3DfgCseContext&) VL_MT_DISABLED;
// Inline fully driven variables
void inlineVars(DfgGraph&) VL_MT_DISABLED;
// Push down selects through concatenations
void pushDownSels(DfgGraph& dfg, V3DfgPushDownSelsContext& ctx) VL_MT_DISABLED;
// Peephole optimizations
void peephole(DfgGraph&, V3DfgPeepholeContext&) VL_MT_DISABLED;
// Regularize graph. This must be run before converting back to Ast.

395
src/V3DfgPushDownSels.cpp Normal file
View File

@ -0,0 +1,395 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Push DfgSels through DfgConcat to avoid temporaries
//
// 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: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// If a DfgConcat drives both a DfgSel and a DfgConcat, and would othersiwe
// not need a temporary, then push the DfgSel down to the lower DfgConcat.
// This avoids having to insert a temporary for many intermediate results.
//
// We need to be careful not to create a cycle by pushing down a DfgSel
// that in turn feeds the concat it is being redirected to. To handle this,
// we use the Pierce-Kelly algorithm to check if a cycle would be created by
// adding a new edge. See: "A Dynamic Topological Sort Algorithm for
// Directed Acyclic Graphs", David J. Pearce, Paul H.J. Kelly, 2007
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Dfg.h"
#include "V3DfgPasses.h"
#include "V3Error.h"
VL_DEFINE_DEBUG_FUNCTIONS;
class V3DfgPushDownSels final {
// TYPES
// Each vertex has an associated State via DfgUserMap
struct State final {
// -- For Pearce-Kelly algorithm only
// Topological ordering index. For all pair of vertices (a, b),
// ord(a) < ord(b) iff there is no path from b to a in the graph.
uint32_t ord = 0;
bool visited = false; // Whether the vertex has been visited during DFS
// -- For the actial optimization only management
bool onWorklist = false; // Whether the vertex is in m_catps
};
// STATE
// The graph being processed - must be acyclic (DAG)
DfgGraph& m_dfg;
// Context for pass
V3DfgPushDownSelsContext& m_ctx;
// Map from DfgVertex to State
DfgUserMap<State> m_stateMap = m_dfg.makeUserMap<State>();
// STATE - Temporaries for Pearce-Kelly algorithm - as members to avoid reallocations
std::vector<DfgVertex*> m_stack; // DFS stack for various steps
std::vector<DfgVertex*> m_fwdVtxps; // Vertices found during forward DFS
std::vector<DfgVertex*> m_bwdVtxps; // Vertices found during backward DFS - also work buffer
std::vector<uint32_t> m_ords; // Ordering numbers reassigned in current ordering update
// STATE - For vertex movement
std::vector<DfgConcat*> m_catps; // DfgConcat vertices that may be optimizable
// METHODS - Pearce-Kelly algorithm
void debugCheck() {
if (VL_LIKELY(!v3Global.opt.debugCheck())) return;
m_dfg.forEachVertex([&](const DfgVertex& src) {
const State& srcState = m_stateMap[src];
UASSERT_OBJ(!srcState.visited, &src, "Visit marker not reset");
UASSERT_OBJ(srcState.ord > 0, &src, "No ordering assigned");
src.foreachSink([&](const DfgVertex& dst) {
const State& dstState = m_stateMap[dst];
UASSERT_OBJ(srcState.ord < dstState.ord, &src, "Invalid ordering");
return false;
});
});
}
// Find initial topological ordering using reverse post order numbering via DFS
void initializeOrdering() {
// Start from all vertices with no inputs
m_stack.reserve(m_dfg.size());
for (DfgVertexVar& vtx : m_dfg.varVertices()) {
if (vtx.srcp() || vtx.defaultp()) continue;
m_stack.push_back(&vtx);
}
for (DfgConst& vtx : m_dfg.constVertices()) m_stack.push_back(&vtx);
// Reverse post order number to assign to next vertex
uint32_t rpoNext = m_dfg.size();
// DFS loop
while (!m_stack.empty()) {
DfgVertex& vtx = *m_stack.back();
State& vtxState = m_stateMap[vtx];
// If the ordering already assigned, just pop. It was visited
// through another path through a different child.
if (vtxState.ord) {
UASSERT_OBJ(vtxState.visited, &vtx, "Not visited, but ordering assigned");
m_stack.pop_back();
continue;
}
// When exiting a vertex, assign the reverse post order number as ordering
if (vtxState.visited) {
vtxState.ord = rpoNext--;
m_stack.pop_back();
continue;
}
// Entering vertex. Enqueue all unvisited children.
vtxState.visited = true;
vtx.foreachSink([&](DfgVertex& dst) {
const State& dstState = m_stateMap[dst];
if (dstState.visited) return false;
m_stack.push_back(&dst);
return false;
});
}
// Should reach exact zero
UASSERT(!rpoNext, "All vertics should have been visited exactly once");
// Reset marks
m_dfg.forEachVertex([&](DfgVertex& vtx) { m_stateMap[vtx].visited = false; });
// Make sure it's valid
debugCheck();
}
// Attempt to add an edge to the graph. Returns false if this would create
// a cycle, and in that case, no state is modified, so it is safe to then
// not add the actual edge. Otherwise returns true and updates state as
// if the edge was indeed added, so caller must add the actual edge.
bool addEdge(DfgVertex& src, DfgVertex& dst) {
UASSERT_OBJ(&src != &dst, &src, "Should be different");
State& srcState = m_stateMap[src];
State& dstState = m_stateMap[dst];
// If 'dst' is after 'src' in the topological ordering,
// then ok to add edge and no need to update the ordering.
if (dstState.ord > srcState.ord) return true;
// Pearce-Kelly dicovery step
if (pkFwdDfs(src, dst)) return false;
pkBwdDfs(src, dst);
// Pearce-Kelly update step
pkReorder();
return true;
}
// Pearce-Kelly forward DFS discovery step. Record visited vertices.
// Returns true if a cycle would be created by adding the edge (src, dst).
bool pkFwdDfs(DfgVertex& src, DfgVertex& dst) {
const uint32_t srcOrd = m_stateMap[src].ord;
// DFS forward from dst
m_stack.push_back(&dst);
while (!m_stack.empty()) {
DfgVertex& vtx = *m_stack.back();
m_stack.pop_back();
State& vtxState = m_stateMap[vtx];
// Ignore if already visited through another path through different sink
if (vtxState.visited) continue;
// Save vertex, mark visited
m_fwdVtxps.push_back(&vtx);
vtxState.visited = true;
// Enqueue unvisited sinks in affeced area
const bool cyclic = vtx.foreachSink([&](DfgVertex& sink) {
State& sinkState = m_stateMap[sink];
if (sinkState.ord == srcOrd) return true; // Stop completely if cyclic
if (sinkState.visited) return false; // Stop search if already visited
if (sinkState.ord > srcOrd) return false; // Stop search if outside critical area
m_stack.push_back(&sink);
return false;
});
// If would be cyclic, reset state and return true
if (cyclic) {
for (DfgVertex* const vtxp : m_fwdVtxps) m_stateMap[vtxp].visited = false;
m_fwdVtxps.clear();
m_stack.clear();
return true;
}
}
// Won't be cyclic, return false
return false;
}
// Pearce-Kelly backward DFS discovery step. Record visited vertices.
void pkBwdDfs(DfgVertex& src, DfgVertex& dst) {
const uint32_t dstOrd = m_stateMap[dst].ord;
// DFS backward from src
m_stack.push_back(&src);
while (!m_stack.empty()) {
DfgVertex& vtx = *m_stack.back();
m_stack.pop_back();
State& vtxState = m_stateMap[vtx];
// Ignore if already visited through another path through different source
if (vtxState.visited) continue;
// Save vertex, mark visited
m_bwdVtxps.push_back(&vtx);
vtxState.visited = true;
// Enqueue unvisited sources in affeced area
vtx.foreachSource([&](DfgVertex& source) {
const State& sourceState = m_stateMap[source];
if (sourceState.visited) return false; // Stop search if already visited
if (sourceState.ord < dstOrd)
return false; // Stop search if outside critical area
m_stack.push_back(&source);
return false;
});
}
}
// Pearce-Kelly reorder step
void pkReorder() {
// Sort vertices found during forward and backward search
const auto cmp = [this](const DfgVertex* const ap, const DfgVertex* const bp) {
return m_stateMap[ap].ord < m_stateMap[bp].ord;
};
std::sort(m_bwdVtxps.begin(), m_bwdVtxps.end(), cmp);
std::sort(m_fwdVtxps.begin(), m_fwdVtxps.end(), cmp);
// Will use m_bwdVtxps for processing to avoid copying. Save the size.
const size_t bwdSize = m_bwdVtxps.size();
// Append forward vertices to the backward list for processing
m_bwdVtxps.insert(m_bwdVtxps.end(), m_fwdVtxps.begin(), m_fwdVtxps.end());
// Save the current ordering numbers, reset visitation marks
for (DfgVertex* const vtxp : m_bwdVtxps) {
State& state = m_stateMap[vtxp];
state.visited = false;
m_ords.push_back(state.ord);
}
// The current ordering numbers are sorted in the two sub lists, merge them
std::inplace_merge(m_ords.begin(), m_ords.begin() + bwdSize, m_ords.end());
// Assign new ordering
for (size_t i = 0; i < m_ords.size(); ++i) m_stateMap[m_bwdVtxps[i]].ord = m_ords[i];
// Reset sate
m_fwdVtxps.clear();
m_bwdVtxps.clear();
m_ords.clear();
// Make sure it's valid
debugCheck();
}
// METHODS - Vertex processing
static bool ignoredSink(const DfgVertex& sink) {
// Ignore non observable variable sinks. These will be eliminated.
if (const DfgVarPacked* const varp = sink.cast<DfgVarPacked>()) {
if (!varp->hasSinks() && !varp->isObserved()) return true;
}
return false;
}
// Find all concatenations that feed another concatenation and may be
// optimizable. These are the ones that feed a DfgSel, and no other
// observable sinks. (If there were other observable sinks, a temporary
// would be required anyway.)
void findCandidatess() {
for (DfgVertex& vtx : m_dfg.opVertices()) {
// Consider only concatenations ...
DfgConcat* const catp = vtx.cast<DfgConcat>();
if (!catp) continue;
// Count the various types of sinks
uint32_t nSels = 0;
uint32_t nCats = 0;
uint32_t nOther = 0;
vtx.foreachSink([&](const DfgVertex& sink) {
if (sink.is<DfgSel>()) {
++nSels;
} else if (sink.is<DfgConcat>()) {
++nCats;
} else if (!ignoredSink(sink)) {
++nOther;
}
return false;
});
// Consider if optimizable
if (nSels > 0 && nCats == 1 && nOther == 0) {
m_catps.push_back(catp);
m_stateMap[catp].onWorklist = true;
}
}
}
void pushDownSels() {
// Selects driven by the current vertex. Outside loop to avoid reallocation.
std::vector<DfgSel*> selps;
selps.reserve(m_dfg.size());
// Consider each concatenation
while (!m_catps.empty()) {
DfgConcat* const catp = m_catps.back();
m_catps.pop_back();
m_stateMap[catp].onWorklist = false;
// Iterate sinks, collect selects, check if should be optimized
selps.clear();
DfgVertex* sinkp = nullptr; // The only non DfgSel sink (ignoring some DfgVars)
const bool multipleNonSelSinks = catp->foreachSink([&](DfgVertex& sink) {
// Collect selects
if (DfgSel* const selp = sink.cast<DfgSel>()) {
selps.emplace_back(selp);
return false;
}
// Skip ignored sinks
if (ignoredSink(sink)) return false;
// If already found a non DfgSel sink, return true
if (sinkp) return true;
// Save the non DfgSel sink
sinkp = &sink;
return false;
});
// It it has multiple non DfgSel sinks, it will need a temporary, so don't bother
if (multipleNonSelSinks) continue;
// We only add DfgConcats to the work list that drive a select.
UASSERT_OBJ(!selps.empty(), catp, "Should have selects");
// If no other sink, then nothing to do
if (!sinkp) continue;
// If the only other sink is not a concatenation, then nothing to do
DfgConcat* const sinkCatp = sinkp->cast<DfgConcat>();
if (!sinkCatp) continue;
// Ok, we can try to push the selects down to the sink DfgConcat
const uint32_t offset = sinkCatp->rhsp() == catp ? 0 : sinkCatp->rhsp()->width();
const uint32_t pushedDownBefore = m_ctx.m_pushedDown;
for (DfgSel* const selp : selps) {
// Don't do it if it would create a cycle
if (!addEdge(*sinkCatp, *selp)) {
++m_ctx.m_wouldBeCyclic;
continue;
}
// Otherwise redirect the select
++m_ctx.m_pushedDown;
selp->lsb(selp->lsb() + offset);
selp->fromp(sinkCatp);
}
// If we pushed down any selects, then we need to consider the sink concatenation
// again
State& sinkCatState = m_stateMap[sinkCatp];
if (pushedDownBefore != m_ctx.m_pushedDown && !sinkCatState.onWorklist) {
m_catps.push_back(sinkCatp);
sinkCatState.onWorklist = true;
}
}
}
// CONSTRUCTOR
V3DfgPushDownSels(DfgGraph& dfg, V3DfgPushDownSelsContext& ctx)
: m_dfg{dfg}
, m_ctx{ctx} {
// Find optimization candidates
m_catps.reserve(m_dfg.size());
findCandidatess();
// Early exit if nothing to do
if (m_catps.empty()) return;
// Pre-allocate storage
m_stack.reserve(m_dfg.size());
m_fwdVtxps.reserve(m_dfg.size());
m_bwdVtxps.reserve(m_dfg.size());
m_ords.reserve(m_dfg.size());
// Initialize topologicel ordering
initializeOrdering();
// Sort candidates in topological order so we process them the least amount
std::sort(m_catps.begin(), m_catps.end(),
[this](const DfgConcat* const ap, const DfgConcat* const bp) {
return m_stateMap[ap].ord < m_stateMap[bp].ord;
});
// Push selects down to the lowest concatenation
pushDownSels();
}
public:
static void apply(DfgGraph& dfg, V3DfgPushDownSelsContext& ctx) {
V3DfgPushDownSels{dfg, ctx};
}
};
void V3DfgPasses::pushDownSels(DfgGraph& dfg, V3DfgPushDownSelsContext& ctx) {
if (!v3Global.opt.fDfgPushDownSels()) return;
V3DfgPushDownSels::apply(dfg, ctx);
}

View File

@ -176,7 +176,7 @@ void EmitCBaseVisitorConst::emitVarDecl(const AstVar* nodep, bool asRef) {
}
};
if (nodep->isIO() && nodep->isSc()) {
if (nodep->isPrimaryIO() && nodep->isSc()) {
UASSERT_OBJ(basicp, nodep, "Unimplemented: Outputting this data type");
if (nodep->isInout()) {
putns(nodep, "sc_core::sc_inout<");
@ -197,7 +197,7 @@ void EmitCBaseVisitorConst::emitVarDecl(const AstVar* nodep, bool asRef) {
if (asRef && refNeedParens) puts(")");
emitDeclArrayBrackets(nodep);
puts(";\n");
} else if (nodep->isIO() && basicp && !basicp->isOpaque()) {
} else if (nodep->isPrimaryIO() && basicp && !basicp->isOpaque()) {
if (nodep->isInout()) {
putns(nodep, "VL_INOUT");
} else if (nodep->isWritable()) {

View File

@ -144,6 +144,8 @@ class EmitCFunc VL_NOT_FINAL : public EmitCConstInit {
} m_emitDispState;
protected:
VL_DEFINE_DEBUG_FUNCTIONS;
EmitCLazyDecls m_lazyDecls{*this}; // Visitor for emitting lazy declarations
bool m_useSelfForThis = false; // Replace "this" with "vlSelf"
bool m_usevlSelfRef = false; // Use vlSelfRef reference instead of vlSelf pointer
@ -478,7 +480,7 @@ public:
if (nodep->isCoroutine()) {
// Sometimes coroutines don't have co_awaits,
// so emit a co_return at the end to avoid compile errors.
puts("co_return;");
puts("co_return;\n");
}
puts("}\n");

View File

@ -549,10 +549,14 @@ class EmitCModel final : public EmitCFunc {
"0.\");\n");
puts("}\n");
puts("vlSymsp->__Vm_baseCode = code;\n");
puts("tracep->pushPrefix(vlSymsp->name(), VerilatedTracePrefixType::SCOPE_MODULE);\n");
if (v3Global.opt.libCreate().empty()) {
puts("tracep->pushPrefix(vlSymsp->name(), VerilatedTracePrefixType::SCOPE_MODULE);\n");
}
puts(topModNameProtected + "__" + protect("trace_decl_types") + "(tracep);\n");
puts(topModNameProtected + "__" + protect("trace_init_top") + "(vlSelf, tracep);\n");
puts("tracep->popPrefix();\n");
if (v3Global.opt.libCreate().empty()) { //
puts("tracep->popPrefix();\n");
}
puts("}\n");
// Forward declaration
@ -583,8 +587,13 @@ class EmitCModel final : public EmitCFunc {
+ " and --trace-vcd with VerilatedVcd object\");\n");
puts(/**/ "}\n");
puts(/**/ "stfp->spTrace()->addModel(this);\n");
puts(/**/ "stfp->spTrace()->addInitCb(&" + protect("trace_init")
+ ", &(vlSymsp->TOP));\n");
puts(/**/ "stfp->spTrace()->addInitCb("s //
+ "&" + protect("trace_init") //
+ ", &(vlSymsp->TOP)" //
+ ", name()" //
+ ", " + (v3Global.opt.libCreate().empty() ? "false" : "true") //
+ ", " + std::to_string(v3Global.rootp()->nTraceCodes()) //
+ ");\n");
puts(/**/ topModNameProtected + "__" + protect("trace_register")
+ "(&(vlSymsp->TOP), stfp->spTrace());\n");
puts("}\n");

View File

@ -225,12 +225,6 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
iterateAndNextConstNull(nodep->rhsp());
if (!m_suppressSemi) puts(";\n");
}
void visit(AstAssignDly* nodep) override {
iterateAndNextConstNull(nodep->lhsp());
putfs(nodep, " <= ");
iterateAndNextConstNull(nodep->rhsp());
puts(";\n");
}
void visit(AstAlias* nodep) override {
putbs("alias ");
iterateConst(nodep->itemsp());
@ -271,6 +265,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
if (nodep->sensp()) puts(" ");
iterateChildrenConst(nodep);
}
void visit(AstCReset* nodep) override { puts("/*CRESET*/"); }
void visit(AstCase* nodep) override {
putfs(nodep, "");
if (nodep->priorityPragma()) puts("priority ");
@ -1223,6 +1218,12 @@ void V3EmitV::debugVerilogForTree(const AstNode* nodep, std::ostream& os) {
{ EmitVStreamVisitor{nodep, os, /* tracking: */ true, true}; }
}
std::string V3EmitV::debugVerilogForTree(const AstNode* nodep) {
std::stringstream ss;
debugVerilogForTree(nodep, ss);
return ss.str();
}
void V3EmitV::emitvFiles() {
UINFO(2, __FUNCTION__ << ":");
for (AstNodeFile* filep = v3Global.rootp()->filesp(); filep;

View File

@ -29,6 +29,7 @@ class V3EmitV final {
public:
static void verilogForTree(const AstNode* nodep, std::ostream& os = std::cout);
static void debugVerilogForTree(const AstNode* nodep, std::ostream& os);
static std::string debugVerilogForTree(const AstNode* nodep);
static void emitvFiles();
static void debugEmitV(const string& filename);
};

View File

@ -350,6 +350,7 @@ class DynScopeVisitor final : public VNVisitor {
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
if (!VN_IS(nodep, Class)) m_modp = nodep;
VL_RESTORER(m_id);
m_id = 0;
iterateChildren(nodep);
}
@ -385,10 +386,12 @@ class DynScopeVisitor final : public VNVisitor {
bindNodeToDynScope(varp, framep);
}
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
AstAssign* const asgnp = VN_CAST(stmtp, Assign);
UASSERT_OBJ(asgnp, stmtp, "Invalid node under block item initialization part of fork");
bindNodeToDynScope(asgnp->lhsp(), framep);
iterate(asgnp->rhsp());
if (AstAssign* const asgnp = VN_CAST(stmtp, Assign)) {
bindNodeToDynScope(asgnp->lhsp(), framep);
iterate(asgnp->rhsp());
} else {
stmtp->v3fatalSrc("Invalid node under block item initialization part of fork");
}
}
for (AstNode* stmtp = nodep->forksp(); stmtp; stmtp = stmtp->nextp()) {

View File

@ -122,6 +122,12 @@ class InlineMarkVisitor final : public VNVisitor {
if (m_modp->modPublic() && (m_modp->isTop() || !v3Global.opt.flatten())) {
cantInline("modPublic", false);
}
// If the instance is a --lib-create library stub instance, and need tracing,
// then don't inline as we need to know its a lib stub for sepecial handling
// in V3TraceDecl. See #7001.
if (m_modp->verilatorLib() && v3Global.opt.trace()) {
cantInline("verilatorLib with --trace", false);
}
iterateChildren(nodep);
}
@ -158,8 +164,8 @@ class InlineMarkVisitor final : public VNVisitor {
}
}
void visit(AstVarXRef* nodep) override {
// Remove link. V3LinkDot will reestablish it after inlining.
nodep->varp(nullptr);
// Keep varp - V3Const::constifyEdit is called during pinReconnectSimple
// which needs varp to be set. V3LinkDot will re-resolve after inlining.
}
void visit(AstNodeFTaskRef* nodep) override {
// Remove link. V3LinkDot will reestablish it after inlining.
@ -322,7 +328,7 @@ class InlineRelinkVisitor final : public VNVisitor {
}
void visit(AstVarRef* nodep) override {
// If the target port is being inlined, replace reference with the
// connected expression (always a Const of a VarRef).
// connected expression (a Const, VarRef, or VarXRef).
AstNode* const pinExpr = nodep->varp()->user2p();
if (!pinExpr) return;
@ -342,7 +348,8 @@ class InlineRelinkVisitor final : public VNVisitor {
// variable that will later be pruned (it will otherwise be unreferenced).
if (!nodep->access().isReadOnly()) {
AstVar* const varp = nodep->varp();
const std::string name = "__vInlPlaceholder_" + std::to_string(++m_nPlaceholders);
const std::string name
= m_cellp->name() + "__vInlPlaceholder_" + std::to_string(++m_nPlaceholders);
AstVar* const holdep = new AstVar{varp->fileline(), VVarType::VAR, name, varp};
m_modp->addStmtsp(holdep);
AstVarRef* const newp = new AstVarRef{nodep->fileline(), holdep, nodep->access()};
@ -354,10 +361,25 @@ class InlineRelinkVisitor final : public VNVisitor {
return;
}
// Otherwise it must be a variable reference, retarget this ref
const AstVarRef* const vrefp = VN_AS(pinExpr, VarRef);
nodep->varp(vrefp->varp());
nodep->classOrPackagep(vrefp->classOrPackagep());
// Handle VarRef: simple retarget
if (const AstVarRef* const vrefp = VN_CAST(pinExpr, VarRef)) {
nodep->varp(vrefp->varp());
nodep->classOrPackagep(vrefp->classOrPackagep());
return;
}
// Handle VarXRef: replace VarRef with VarXRef (e.g., nested interface port)
const AstVarXRef* const xrefp = VN_AS(pinExpr, VarXRef);
AstVarXRef* const newp
= new AstVarXRef{nodep->fileline(), xrefp->name(), xrefp->dotted(), nodep->access()};
newp->varp(xrefp->varp());
// Copy inlinedDots from pin expression - the normal visitor iteration will
// prepend the cell name when this VarXRef is visited later
newp->inlinedDots(xrefp->inlinedDots());
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
// Note: Don't call iterate(newp) here - the node will be visited during
// normal tree iteration which will apply the inlining transformations
}
void visit(AstVarXRef* nodep) override {
// Track what scope it was originally under so V3LinkDot can resolve it
@ -481,13 +503,28 @@ void connectPort(AstNodeModule* modp, AstVar* nodep, AstNodeExpr* pinExprp) {
}
// Otherwise it must be a variable reference due to having called pinReconnectSimple
const AstVarRef* const pinRefp = VN_AS(pinExprp, VarRef);
const AstNodeVarRef* const pinRefp = VN_AS(pinExprp, NodeVarRef);
// Helper to create an AstVarRef reference to the pin variable
const auto pinRef = [&](VAccess access) {
AstVarRef* const p = new AstVarRef{pinRefp->fileline(), pinRefp->varp(), access};
p->classOrPackagep(pinRefp->classOrPackagep());
return p;
const auto pinRefAsVarRef = [&](VAccess access) -> AstVarRef* {
const AstVarRef* const vrp = VN_AS(pinRefp, VarRef);
AstVarRef* const newp = new AstVarRef{vrp->fileline(), vrp->varp(), access};
newp->classOrPackagep(vrp->classOrPackagep());
return newp;
};
const auto pinRefAsExpr = [&](VAccess access) -> AstNodeExpr* {
if (const AstVarRef* const vrp = VN_CAST(pinRefp, VarRef)) {
AstVarRef* const newp = new AstVarRef{vrp->fileline(), vrp->varp(), access};
newp->classOrPackagep(vrp->classOrPackagep());
return newp;
} else {
const AstVarXRef* const xrp = VN_AS(pinRefp, VarXRef);
AstVarXRef* const newp
= new AstVarXRef{xrp->fileline(), xrp->name(), xrp->dotted(), access};
newp->varp(xrp->varp());
newp->inlinedDots(xrp->inlinedDots());
return newp;
}
};
// If it is being inlined, create the alias for it
@ -495,10 +532,10 @@ void connectPort(AstNodeModule* modp, AstVar* nodep, AstNodeExpr* pinExprp) {
UINFO(6, "Inlining port variable: " << nodep);
if (nodep->isIfaceRef()) {
modp->addStmtsp(
new AstAliasScope{flp, portRef(VAccess::WRITE), pinRef(VAccess::READ)});
new AstAliasScope{flp, portRef(VAccess::WRITE), pinRefAsExpr(VAccess::READ)});
} else {
AstVarRef* const aliasArgsp = portRef(VAccess::WRITE);
aliasArgsp->addNext(pinRef(VAccess::READ));
aliasArgsp->addNext(pinRefAsVarRef(VAccess::READ));
modp->addStmtsp(new AstAlias{flp, aliasArgsp});
}
// They will become the same variable, so propagate file-line and variable attributes
@ -512,10 +549,12 @@ void connectPort(AstNodeModule* modp, AstVar* nodep, AstNodeExpr* pinExprp) {
// Otherwise create the continuous assignment between the port var and the pin expression
UINFO(6, "Not inlining port variable: " << nodep);
if (nodep->direction() == VDirection::INPUT) {
AstAssignW* const ap = new AstAssignW{flp, portRef(VAccess::WRITE), pinRef(VAccess::READ)};
AstAssignW* const ap
= new AstAssignW{flp, portRef(VAccess::WRITE), pinRefAsExpr(VAccess::READ)};
modp->addStmtsp(new AstAlways{ap});
} else if (nodep->direction() == VDirection::OUTPUT) {
AstAssignW* const ap = new AstAssignW{flp, pinRef(VAccess::WRITE), portRef(VAccess::READ)};
AstAssignW* const ap
= new AstAssignW{flp, pinRefAsExpr(VAccess::WRITE), portRef(VAccess::READ)};
modp->addStmtsp(new AstAlways{ap});
} else {
pinExprp->v3fatalSrc("V3Tristate left INOUT port");

View File

@ -171,7 +171,8 @@ public:
entr.init(false);
} else {
if (AstConst* const constp = entr.constNodep()) {
if (!varrefp->varp()->isSigPublic() && !varrefp->varp()->sensIfacep()) {
if (!varrefp->varp()->isSigPublic() && !varrefp->varp()->isWrittenByDpi()
&& !varrefp->varp()->isVirtIface()) {
// Aha, variable is constant; substitute in.
// We'll later constant propagate
UINFO(4, " replaceconst: " << varrefp);

View File

@ -774,13 +774,51 @@ public:
}
if (!lookupSymp) return nullptr; // Not found
}
} else { // Searching for middle submodule, must be a cell name
} else { // Searching for middle path component, must be a cell or interface port
VSymEnt* findSymp = findWithAltFlat(lookupSymp, ident, altIdent);
if (!findSymp) findSymp = findForkParentAlias(lookupSymp, ident);
if (findSymp)
if (findSymp) {
lookupSymp = unwrapForkParent(findSymp, ident);
else {
return nullptr; // Not found
} else {
// Try prefixed lookup for interface ports accessed through hierarchy
// (e.g., slave_inst.bus.data where bus is an interface port)
// After inlining, interface ports have prefixed names like
// top__DOT__slave_inst__DOT__bus
UINFO(8, " middle-path: trying prefixed lookup for '" << ident << "'\n");
string baddot;
findSymp = findSymPrefixed(lookupSymp, ident, baddot, /*fallback=*/true);
UINFO(8, " middle-path: prefixed lookup result: "
<< (findSymp ? "found" : "not found") << "\n");
if (findSymp) {
// Check if this is an interface reference variable
const AstVar* varp = VN_CAST(findSymp->nodep(), Var);
if (!varp) {
if (const AstVarScope* vscp = VN_CAST(findSymp->nodep(), VarScope)) {
varp = vscp->varp();
}
}
if (varp && varp->isIfaceRef()) {
// Found an interface port - use findSymp directly
// computeScopeAliases has already imported interface members
// into this symbol entry, so we use it instead of redirecting
// to the shared modport definition (which would cause all
// instances to resolve to the same symbols - bug #2656)
lookupSymp = findSymp;
} else {
// Non-interface symbol in middle path must be a cell (module instance)
// to continue hierarchical resolution
if (VN_IS(findSymp->nodep(), Cell)) {
lookupSymp = findSymp;
} else {
// Reject non-cell, non-interface symbols found via fallback
// to prevent accidental resolution to unrelated symbols
UINFO(8, " middle-path: rejecting non-cell symbol\n");
return nullptr;
}
}
} else {
return nullptr; // Not found
}
}
}
if (lookupSymp) {
@ -795,6 +833,53 @@ public:
}
}
}
// Follow scope alias for nested interface port access
if (!leftname.empty()) {
const auto aliasIt = m_scopeAliasMap[SAMN_IFTOP].find(lookupSymp);
if (aliasIt != m_scopeAliasMap[SAMN_IFTOP].end()) {
lookupSymp = aliasIt->second;
// Alias may point to __Viftop VarScope; find corresponding Cell
if (const AstVarScope* const vscp
= VN_CAST(lookupSymp->nodep(), VarScope)) {
const string varName = vscp->varp()->name();
static constexpr const char* const VIFTOP_SUFFIX = "__Viftop";
if (VString::endsWith(varName, VIFTOP_SUFFIX)) {
const string cellName
= varName.substr(0, varName.size() - strlen(VIFTOP_SUFFIX));
VSymEnt* const parentSymp = lookupSymp->parentp();
if (parentSymp) {
VSymEnt* const cellSymp = parentSymp->findIdFlat(cellName);
if (cellSymp && VN_IS(cellSymp->nodep(), Cell)) {
lookupSymp = cellSymp; // Use Cell for member lookup
}
}
}
}
} else {
// No alias; try following IfaceRefDType to interface cell
const AstVar* varp = VN_CAST(lookupSymp->nodep(), Var);
if (!varp) {
if (const AstVarScope* const vscp
= VN_CAST(lookupSymp->nodep(), VarScope)) {
varp = vscp->varp();
}
}
if (varp && varp->isIfaceRef()) {
if (const AstIfaceRefDType* const ifaceRefp
= ifaceRefFromArray(varp->dtypep())) {
if (ifaceRefp->cellp() && existsNodeSym(ifaceRefp->cellp())) {
lookupSymp = getNodeSym(ifaceRefp->cellp());
} else if (ifaceRefp->ifaceViaCellp()
&& existsNodeSym(ifaceRefp->ifaceViaCellp())) {
lookupSymp = getNodeSym(ifaceRefp->ifaceViaCellp());
} else if (ifaceRefp->ifacep()
&& existsNodeSym(ifaceRefp->ifacep())) {
lookupSymp = getNodeSym(ifaceRefp->ifacep());
}
}
}
}
}
}
firstId = false;
}
@ -1851,11 +1936,15 @@ class LinkDotFindVisitor final : public VNVisitor {
m_curSymp->fallbackp(VL_RESTORER_PREV(m_curSymp));
// DOT(x, SELLOOPVARS(var, loops)) -> SELLOOPVARS(DOT(x, var), loops)
if (AstDot* const dotp = VN_CAST(nodep->arrayp(), Dot)) {
if (AstSelLoopVars* const loopvarsp = VN_CAST(dotp->rhsp(), SelLoopVars)) {
AstDot* dotAbovep = dotp;
while (AstDot* const dotNextp = VN_CAST(dotAbovep->rhsp(), Dot)) {
dotAbovep = dotNextp;
}
if (AstSelLoopVars* const loopvarsp = VN_CAST(dotAbovep->rhsp(), SelLoopVars)) {
AstNodeExpr* const fromp = loopvarsp->fromp()->unlinkFrBack();
loopvarsp->unlinkFrBack();
dotp->replaceWith(loopvarsp);
dotp->rhsp(fromp);
dotAbovep->rhsp(fromp);
loopvarsp->fromp(dotp);
}
}
@ -2298,6 +2387,8 @@ class LinkDotScopeVisitor final : public VNVisitor {
LinkDotState* const m_statep; // State to pass between visitors, including symbol table
const AstScope* m_scopep = nullptr; // The current scope
VSymEnt* m_modSymp = nullptr; // Symbol entry for current module
// Deferred AliasScope processing - must be done outer-to-inner for correct alias resolution
std::vector<std::pair<AstAliasScope*, VSymEnt*>> m_deferredAliasScopes;
// METHODS
public:
@ -2341,37 +2432,39 @@ private:
if (!nodep->varp()->isFuncLocal() && !nodep->varp()->isClassMember()) {
VSymEnt* const varSymp
= m_statep->insertSym(m_modSymp, nodep->varp()->name(), nodep, nullptr);
if (nodep->varp()->isIfaceRef() && nodep->varp()->isIfaceParent()) {
UINFO(9, "Iface parent ref var " << nodep->varp()->name() << " " << nodep);
// Find the interface cell the var references
if (nodep->varp()->isIfaceRef()) {
AstIfaceRefDType* const dtypep
= LinkDotState::ifaceRefFromArray(nodep->varp()->dtypep());
UASSERT_OBJ(dtypep, nodep, "Non AstIfaceRefDType on isIfaceRef() var");
UINFO(9, "Iface parent dtype " << dtypep);
const string ifcellname = dtypep->cellName();
string baddot;
VSymEnt* okSymp;
VSymEnt* cellSymp = m_statep->findDotted(nodep->fileline(), m_modSymp, ifcellname,
baddot, okSymp, false);
UASSERT_OBJ(
cellSymp, nodep,
"No symbol for interface instance: " << nodep->prettyNameQ(ifcellname));
UINFO(5, " Found interface instance: se" << cvtToHex(cellSymp) << " "
<< cellSymp->nodep());
if (dtypep->modportName() != "") {
VSymEnt* const mpSymp = m_statep->findDotted(
nodep->fileline(), m_modSymp, ifcellname, baddot, okSymp, false);
UASSERT_OBJ(mpSymp, nodep,
"No symbol for interface modport: "
<< nodep->prettyNameQ(dtypep->modportName()));
cellSymp = mpSymp;
UINFO(5, " Found modport cell: se" << cvtToHex(cellSymp) << " "
<< mpSymp->nodep());
if (nodep->varp()->isIfaceParent()) {
UINFO(9, "Iface parent ref var " << nodep->varp()->name() << " " << nodep);
// Find the interface cell the var references
UINFO(9, "Iface parent dtype " << dtypep);
const string ifcellname = dtypep->cellName();
string baddot;
VSymEnt* okSymp;
VSymEnt* cellSymp = m_statep->findDotted(nodep->fileline(), m_modSymp,
ifcellname, baddot, okSymp, false);
UASSERT_OBJ(
cellSymp, nodep,
"No symbol for interface instance: " << nodep->prettyNameQ(ifcellname));
UINFO(5, " Found interface instance: se" << cvtToHex(cellSymp) << " "
<< cellSymp->nodep());
if (dtypep->modportName() != "") {
// Look up the modport within the interface cell's symbol table
VSymEnt* const mpSymp = cellSymp->findIdFallback(dtypep->modportName());
UASSERT_OBJ(mpSymp, nodep,
"No symbol for interface modport: "
<< nodep->prettyNameQ(dtypep->modportName()));
cellSymp = mpSymp;
UINFO(5, " Found modport cell: se" << cvtToHex(cellSymp) << " "
<< mpSymp->nodep());
}
// Interface reference; need to put whole thing into
// symtable, but can't clone it now as we may have a later
// alias for it.
m_statep->insertScopeAlias(LinkDotState::SAMN_IFTOP, varSymp, cellSymp);
}
// Interface reference; need to put whole thing into
// symtable, but can't clone it now as we may have a later
// alias for it.
m_statep->insertScopeAlias(LinkDotState::SAMN_IFTOP, varSymp, cellSymp);
}
}
}
@ -2410,6 +2503,12 @@ private:
pushDeletep(nodep->unlinkFrBack());
}
void visit(AstAliasScope* nodep) override { // ScopeVisitor::
// Defer AliasScope processing - must process outer scopes before inner ones
// so that nested interface port alias resolution works correctly
UINFO(5, "ALIASSCOPE (deferred) " << nodep);
m_deferredAliasScopes.emplace_back(nodep, m_modSymp);
}
void processAliasScope(AstAliasScope* nodep, VSymEnt* modSymp) {
UINFO(5, "ALIASSCOPE " << nodep);
UINFOTREE(9, nodep, "", "avs");
VSymEnt* rhsSymp;
@ -2421,22 +2520,21 @@ private:
string inl
= ((xrefp && xrefp->inlinedDots().size()) ? (xrefp->inlinedDots() + "__DOT__")
: "");
const string dottedPath
= (xrefp && !xrefp->dotted().empty()) ? (xrefp->dotted() + ".") : "";
VSymEnt* symp = nullptr;
string scopename;
while (!symp) {
scopename
= refp ? refp->name() : (inl.size() ? (inl + xrefp->name()) : xrefp->name());
scopename = refp ? refp->name()
: (inl.size() ? (inl + dottedPath + xrefp->name())
: (dottedPath + xrefp->name()));
string baddot;
VSymEnt* okSymp;
symp = m_statep->findDotted(nodep->rhsp()->fileline(), m_modSymp, scopename,
baddot, okSymp, false);
symp = m_statep->findDotted(nodep->rhsp()->fileline(), modSymp, scopename, baddot,
okSymp, true);
if (inl == "") break;
inl = LinkDotState::removeLastInlineScope(inl);
}
if (!symp) {
UINFO(9, "No symbol for interface alias rhs ("
<< std::string{refp ? "VARREF " : "VARXREF "} << scopename << ")");
}
UASSERT_OBJ(symp, nodep, "No symbol for interface alias rhs");
UINFO(5, " Found a linked scope RHS: " << scopename << " se" << cvtToHex(symp)
<< " " << symp->nodep());
@ -2453,7 +2551,7 @@ private:
= refp ? refp->varp()->name() : xrefp->dotted() + "." + xrefp->name();
string baddot;
VSymEnt* okSymp;
VSymEnt* const symp = m_statep->findDotted(nodep->lhsp()->fileline(), m_modSymp,
VSymEnt* const symp = m_statep->findDotted(nodep->lhsp()->fileline(), modSymp,
scopename, baddot, okSymp, false);
UASSERT_OBJ(symp, nodep, "No symbol for interface alias lhs");
UINFO(5, " Found a linked scope LHS: " << scopename << " se" << cvtToHex(symp)
@ -2466,6 +2564,27 @@ private:
// We have stored the link, we don't need these any more
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
void processDeferredAliasScopes() {
// Sort by hierarchy depth (shallower first) so outer aliases are resolved before inner
// Pre-compute depth map to avoid O(N log N * D) complexity in sort comparisons
std::unordered_map<VSymEnt*, int> depthMap;
for (const auto& pair : m_deferredAliasScopes) {
VSymEnt* const symp = pair.second;
if (depthMap.find(symp) == depthMap.end()) {
int depth = 0;
for (VSymEnt* p = symp; p; p = p->parentp()) ++depth;
depthMap[symp] = depth;
}
}
std::stable_sort(m_deferredAliasScopes.begin(), m_deferredAliasScopes.end(),
[&depthMap](const std::pair<AstAliasScope*, VSymEnt*>& a,
const std::pair<AstAliasScope*, VSymEnt*>& b) {
return depthMap.at(a.second) < depthMap.at(b.second);
});
// Process in sorted order
for (auto& pair : m_deferredAliasScopes) { processAliasScope(pair.first, pair.second); }
m_deferredAliasScopes.clear();
}
void visit(AstNodeGen* nodep) override { // ScopeVisitor:: // LCOV_EXCL_LINE
nodep->v3fatalSrc("Generate constructs should have been reduced out");
}
@ -2481,6 +2600,8 @@ public:
: m_statep{statep} {
UINFO(4, __FUNCTION__ << ": ");
iterate(rootp);
// Process deferred AliasScopes in outer-to-inner order
processDeferredAliasScopes();
}
~LinkDotScopeVisitor() override = default;
};
@ -2528,13 +2649,168 @@ class LinkDotIfaceVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
// Helper to extract a dotted path string from an AstDot tree
// Returns empty string and sets hasPartSelect=true if part-select detected
string extractDottedPath(AstNode* nodep, bool& hasPartSelect) {
if (AstParseRef* const refp = VN_CAST(nodep, ParseRef)) {
return refp->name();
} else if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
return refp->name();
} else if (AstDot* const dotp = VN_CAST(nodep, Dot)) {
const string lhs = extractDottedPath(dotp->lhsp(), hasPartSelect);
const string rhs = extractDottedPath(dotp->rhsp(), hasPartSelect);
if (lhs.empty()) return rhs;
if (rhs.empty()) return lhs;
return lhs + "." + rhs;
} else if (VN_IS(nodep, SelBit) || VN_IS(nodep, SelExtract)) {
hasPartSelect = true;
return "";
}
return "";
}
// Helper to resolve remaining path through a nested interface
// When findDotted() partially matches (okSymp set, baddot non-empty),
// this follows the interface type to resolve the remaining path.
// Returns the resolved symbol, or nullptr if not found.
// On success, clears baddot; on partial match of multi-level path, updates baddot.
VSymEnt* resolveNestedInterfacePath(FileLine* fl, VSymEnt* okSymp, string& baddot) {
if (!okSymp || baddot.empty()) return nullptr;
static constexpr int MAX_NESTING_DEPTH = 64;
VSymEnt* curOkSymp = okSymp;
for (int depth = 0; depth < MAX_NESTING_DEPTH; ++depth) {
// Try to get interface from the partially-matched symbol
AstIface* ifacep = nullptr;
if (const AstCell* const cellp = VN_CAST(curOkSymp->nodep(), Cell)) {
ifacep = VN_CAST(cellp->modp(), Iface);
} else if (const AstVar* const varp = VN_CAST(curOkSymp->nodep(), Var)) {
if (varp->isIfaceRef()) {
if (const AstIfaceRefDType* const ifaceRefp
= LinkDotState::ifaceRefFromArray(varp->dtypep())) {
ifacep = ifaceRefp->ifaceViaCellp();
}
}
}
if (!ifacep || !m_statep->existsNodeSym(ifacep)) return nullptr;
VSymEnt* const ifaceSymp = m_statep->getNodeSym(ifacep);
if (baddot.find('.') == string::npos) {
// Simple identifier - direct lookup
VSymEnt* const symp = ifaceSymp->findIdFallback(baddot);
if (symp) baddot.clear();
return symp;
}
// Multi-level path - use findDotted for partial resolution
string remainingBaddot;
VSymEnt* remainingOkSymp = nullptr;
VSymEnt* const symp = m_statep->findDotted(fl, ifaceSymp, baddot, remainingBaddot,
remainingOkSymp, true);
if (symp) {
baddot = remainingBaddot;
return symp;
}
// findDotted partially matched - check progress
if (remainingBaddot == baddot || !remainingOkSymp) return nullptr;
// Continue resolving with updated state
baddot = remainingBaddot;
curOkSymp = remainingOkSymp;
}
UINFO(1, "Nested interface resolution depth limit exceeded at " << fl << endl);
return nullptr;
}
// Resolve a modport expression to find the referenced symbol
VSymEnt* resolveModportExpression(FileLine* fl, AstNodeExpr* exprp) {
UINFO(5, " resolveModportExpression: " << exprp << endl);
VSymEnt* symp = nullptr;
if (AstParseRef* const refp = VN_CAST(exprp, ParseRef)) {
// Simple variable reference: modport mp(input .a(sig_a))
symp = m_curSymp->findIdFallback(refp->name());
} else if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) {
// Already resolved VarRef (can happen if iterateChildren resolved it)
if (refp->varScopep()) {
// During scope creation, VarRef may have VarScope already linked
symp = m_statep->getNodeSym(refp->varScopep());
} else if (refp->varp()) {
symp = m_curSymp->findIdFallback(refp->varp()->name());
}
} else if (AstVarXRef* const refp = VN_CAST(exprp, VarXRef)) {
// Resolved VarXRef (dotted reference resolved by earlier pass)
if (refp->varp()) {
// For nested interfaces, the var is in a different scope
// First try to find the symbol via the var itself
if (m_statep->existsNodeSym(refp->varp())) {
symp = m_statep->getNodeSym(refp->varp());
} else {
// Fallback: look up in current scope
symp = m_curSymp->findIdFallback(refp->varp()->name());
}
} else if (!refp->dotted().empty()) {
// varp not set yet - use dotted path to find the symbol
// The dotted part (e.g., "base") is the interface cell, and name is the member
const string fullPath = refp->dotted() + "." + refp->name();
string baddot;
VSymEnt* okSymp = nullptr;
symp = m_statep->findDotted(fl, m_curSymp, fullPath, baddot, okSymp, true);
// Handle nested interface path when findDotted had partial match
if (okSymp && !baddot.empty()) {
VSymEnt* const resolved = resolveNestedInterfacePath(fl, okSymp, baddot);
if (resolved) symp = resolved;
}
}
} else if (AstDot* const dotp = VN_CAST(exprp, Dot)) {
// Dotted path: modport mp(input .a(inner.sig))
bool hasPartSelect = false;
const string dottedPath = extractDottedPath(dotp, hasPartSelect);
if (hasPartSelect) {
fl->v3warn(
E_UNSUPPORTED,
"Unsupported: Modport expression with part select (IEEE 1800-2023 25.5.4)");
} else {
string baddot;
VSymEnt* okSymp = nullptr;
symp = m_statep->findDotted(fl, m_curSymp, dottedPath, baddot, okSymp, true);
// Handle nested interface path when findDotted had partial match
if (okSymp && !baddot.empty()) {
VSymEnt* const resolved = resolveNestedInterfacePath(fl, okSymp, baddot);
if (resolved) symp = resolved;
}
if (!symp || !baddot.empty()) {
fl->v3error("Can't find modport expression target: "
<< AstNode::prettyNameQ(dottedPath));
symp = nullptr;
}
}
} else if (VN_IS(exprp, SelBit) || VN_IS(exprp, SelExtract)) {
// Part select expressions not yet supported
fl->v3warn(E_UNSUPPORTED,
"Unsupported: Modport expression with part select (IEEE 1800-2023 25.5.4)");
} else {
// Other expression types not supported
fl->v3warn(E_UNSUPPORTED,
"Unsupported: Complex modport expression (IEEE 1800-2023 25.5.4)");
}
return symp;
}
void visit(AstModportVarRef* nodep) override { // IfaceVisitor::
UINFO(5, " fiv: " << nodep);
iterateChildren(nodep);
VSymEnt* symp = nullptr;
if (nodep->exprp()) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Modport expressions (IEEE 1800-2023 25.5.4)");
// Modport expression syntax: modport mp(input .port_name(expression))
symp = resolveModportExpression(nodep->fileline(), nodep->exprp());
} else {
symp = m_curSymp->findIdFallback(nodep->name());
}
@ -2544,11 +2820,30 @@ class LinkDotIfaceVisitor final : public VNVisitor {
// Make symbol under modport that points at the _interface_'s var via the modport.
// (Need modport still to test input/output markings)
nodep->varp(varp);
m_statep->insertSym(m_curSymp, nodep->name(), nodep, nullptr /*package*/);
if (nodep->exprp()) {
// For modport expressions, insert symbol pointing to the underlying var (not the
// ModportVarRef which will be deleted during scope creation). The virtual port
// name maps to the real signal's var.
VSymEnt* const subSymp
= m_statep->insertSym(m_curSymp, nodep->name(), varp, nullptr /*package*/);
m_statep->insertScopeAlias(LinkDotState::SAMN_MODPORT, subSymp, symp);
} else {
// For regular modport items, insert symbol pointing to ModportVarRef for
// input/output marking tests.
m_statep->insertSym(m_curSymp, nodep->name(), nodep, nullptr /*package*/);
}
} else if (AstVarScope* const vscp = VN_CAST(symp->nodep(), VarScope)) {
// Make symbol under modport that points at the _interface_'s var, not the modport.
nodep->varp(vscp->varp());
m_statep->insertSym(m_curSymp, nodep->name(), vscp, nullptr /*package*/);
// Only insert symbol for modport expression virtual ports (exprp set).
// For regular modport items, don't insert into the shared modport table because
// each instance would overwrite the previous one, causing wrong VarScope lookups.
// Regular items are found via the modport's fallback to the interface.
if (nodep->exprp()) {
VSymEnt* const subSymp
= m_statep->insertSym(m_curSymp, nodep->name(), vscp, nullptr /*package*/);
m_statep->insertScopeAlias(LinkDotState::SAMN_MODPORT, subSymp, symp);
}
} else {
nodep->v3error("Modport item is not a variable: " << nodep->prettyNameQ());
}
@ -2745,6 +3040,46 @@ class LinkDotResolveVisitor final : public VNVisitor {
return nullptr;
}
}
// Look up a virtual port through modport expression mapping.
// When a dotted reference uses a modport with expression syntax, the virtual port name
// maps to the real signal. This helper resolves that mapping during scope creation.
// Returns the VarScope for the real signal, or nullptr if not found.
AstVarScope* findVirtualPortVarScope(VSymEnt* dotSymp, const string& portName) {
const AstVarScope* const dotVscp = VN_CAST(dotSymp->nodep(), VarScope);
const AstVar* const dotVarp = dotVscp ? dotVscp->varp() : nullptr;
if (!dotVarp) return nullptr;
AstNodeDType* dtypep = dotVarp->childDTypep();
if (!dtypep) dtypep = dotVarp->subDTypep();
if (!dtypep) return nullptr;
const AstIfaceRefDType* const ifaceRefp = VN_CAST(dtypep, IfaceRefDType);
if (!ifaceRefp || !ifaceRefp->modportp() || !ifaceRefp->ifaceViaCellp()) return nullptr;
// Get the interface cell's symbol table
VSymEnt* const ifaceCellSymp = m_statep->getNodeSym(ifaceRefp->ifaceViaCellp());
// Look up modport by name in interface cell
VSymEnt* const modportSymp = ifaceCellSymp->findIdFallback(ifaceRefp->modportp()->name());
if (!modportSymp) return nullptr;
// Look up virtual port name in modport symbol table
string baddot;
VSymEnt* const virtPortp = m_statep->findSymPrefixed(modportSymp, portName, baddot, true);
if (!virtPortp) return nullptr;
// Symbol may point to VarScope or Var
if (AstVarScope* vscp = VN_CAST(virtPortp->nodep(), VarScope)) return vscp;
// Symbol points to Var - look up VarScope by name in interface cell
if (const AstVar* const realVarp = VN_CAST(virtPortp->nodep(), Var)) {
VSymEnt* const realFoundp
= m_statep->findSymPrefixed(ifaceCellSymp, realVarp->name(), baddot, true);
return realFoundp ? VN_CAST(realFoundp->nodep(), VarScope) : nullptr;
}
return nullptr;
}
AstNodeStmt* addImplicitSuperNewCall(AstFunc* const nodep,
const AstClassExtends* const classExtendsp) {
// Returns the added node
@ -3742,6 +4077,24 @@ class LinkDotResolveVisitor final : public VNVisitor {
} else {
foundp = m_ds.m_dotSymp->findIdFlat(nodep->name());
}
// If not found in modport, check interface fallback for parameters.
// Parameters are always visible through a modport (IEEE 1800-2023 25.5).
// This mirrors the VarXRef modport parameter fallback in visit(AstVarXRef).
if (!foundp && VN_IS(m_ds.m_dotSymp->nodep(), Modport)
&& m_ds.m_dotSymp->fallbackp()) {
VSymEnt* const ifaceFoundp
= m_ds.m_dotSymp->fallbackp()->findIdFlat(nodep->name());
if (ifaceFoundp) {
if (const AstVar* const varp = VN_CAST(ifaceFoundp->nodep(), Var)) {
if (varp->isParam()) foundp = ifaceFoundp;
}
}
}
// When flat lookup in modport fails, provide dotSymp for error diagnostics
// so the "Known scopes under..." hint appears (restores pre-routing behavior)
if (!foundp && !okSymp && VN_IS(m_ds.m_dotSymp->nodep(), Modport)) {
okSymp = m_ds.m_dotSymp;
}
if (foundp) {
UINFO(9, indent() << "found=se" << cvtToHex(foundp) << " exp=" << expectWhat
<< " n=" << foundp->nodep());
@ -3838,7 +4191,12 @@ class LinkDotResolveVisitor final : public VNVisitor {
// Really this is a scope reference into an interface
UINFO(9, indent() << "varref-ifaceref " << m_ds.m_dotText << " " << nodep);
m_ds.m_dotText = VString::dot(m_ds.m_dotText, ".", nodep->name());
m_ds.m_dotSymp = m_statep->getNodeSym(ifacerefp->ifaceViaCellp());
// If modport specified, use modport symbol table for lookup of virtual ports
if (ifacerefp->modportp() && m_statep->existsNodeSym(ifacerefp->modportp())) {
m_ds.m_dotSymp = m_statep->getNodeSym(ifacerefp->modportp());
} else {
m_ds.m_dotSymp = m_statep->getNodeSym(ifacerefp->ifaceViaCellp());
}
m_ds.m_dotPos = DP_SCOPE;
ok = true;
AstNode* const newp = new AstVarRef{nodep->fileline(), varp, VAccess::READ};
@ -3988,6 +4346,12 @@ class LinkDotResolveVisitor final : public VNVisitor {
m_ds.m_dotSymp = foundp;
if (m_ds.m_dotText != "") m_ds.m_dotText += "." + nodep->name();
ok = m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST;
} else if (const AstModportClockingRef* const clockingRefp
= VN_CAST(foundp->nodep(), ModportClockingRef)) {
// Clocking block accessed through a modport - redirect to actual clocking
m_ds.m_dotSymp = m_statep->getNodeSym(clockingRefp->clockingp());
if (m_ds.m_dotText != "") m_ds.m_dotText += "." + nodep->name();
ok = m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST;
} else if (const AstNodeFTask* const ftaskp = VN_CAST(foundp->nodep(), NodeFTask)) {
if (!ftaskp->isFunction() || ftaskp->classMethod()) {
ok = m_ds.m_dotPos == DP_NONE;
@ -4275,7 +4639,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
bool modport = false;
if (const AstVar* varp = VN_CAST(dotSymp->nodep(), Var)) {
if (const AstVar* const varp = VN_CAST(dotSymp->nodep(), Var)) {
if (const AstIfaceRefDType* const ifaceRefp
= VN_CAST(varp->childDTypep(), IfaceRefDType)) {
if (ifaceRefp->modportp()) {
@ -4336,10 +4700,10 @@ class LinkDotResolveVisitor final : public VNVisitor {
<< okSymp->cellErrorScopes(nodep));
return;
}
// V3Inst may have expanded arrays of interfaces to
// AstVarXRef's even though they are in the same module detect
// this and convert to normal VarRefs
if (!m_statep->forPrearray() && !m_statep->forScopeCreation()) {
// V3Inst may have expanded arrays of interfaces to AstVarXRef's even though
// they are in the same module; convert to normal VarRefs (but not if dotted)
if (!m_statep->forPrearray() && !m_statep->forScopeCreation()
&& nodep->dotted().empty()) {
if (const AstIfaceRefDType* const ifaceDtp
= VN_CAST(nodep->dtypep(), IfaceRefDType)) {
if (!ifaceDtp->isVirtual()) {
@ -4353,7 +4717,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
} else {
VSymEnt* const foundp
= m_statep->findSymPrefixed(dotSymp, nodep->name(), baddot, true);
AstVarScope* vscp = foundp ? VN_AS(foundp->nodep(), VarScope) : nullptr;
AstVarScope* vscp = foundp ? VN_CAST(foundp->nodep(), VarScope) : nullptr;
// Handle modport expression virtual ports
if (!vscp) vscp = findVirtualPortVarScope(dotSymp, nodep->name());
// If found, check if it's ok to access in case it's in a hier_block
if (vscp && errorHierNonPort(nodep, vscp->varp(), dotSymp)) return;
if (!vscp) {

View File

@ -308,6 +308,7 @@ class LinkIncVisitor final : public VNVisitor {
AstVar* const varp = new AstVar{
fl, VVarType::BLOCKTEMP, name, VFlagChildDType{},
new AstRefDType{fl, AstRefDType::FlagTypeOfExpr{}, readp->cloneTree(true)}};
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
if (m_ftaskp) varp->funcLocal(true);
// Declare the variable

View File

@ -325,13 +325,12 @@ class LinkParseVisitor final : public VNVisitor {
&& !nodep->isIO()
&& !nodep->isParam()
// In task, or a procedure but not Initial/Final as executed only once
&& ((m_ftaskp && !m_ftaskp->lifetime().isStaticExplicit())
|| (m_procedurep && !VN_IS(m_procedurep, Initial)
&& !VN_IS(m_procedurep, Final)))) {
&& ((m_ftaskp && !m_ftaskp->lifetime().isStaticExplicit()) || m_procedurep)) {
if (VN_IS(m_modp, Module) && m_ftaskp) {
m_ftaskp->v3warn(
IMPLICITSTATIC,
"Function/task's lifetime implicitly set to static\n"
"Function/task's lifetime implicitly set to static;"
" variables made static (IEEE 1800-2023 6.21)\n"
<< m_ftaskp->warnMore() << "... Suggest use '" << m_ftaskp->verilogKwd()
<< " automatic' or '" << m_ftaskp->verilogKwd() << " static'\n"
<< m_ftaskp->warnContextPrimary() << '\n'
@ -339,12 +338,12 @@ class LinkParseVisitor final : public VNVisitor {
<< nodep->warnMore() << "... The initializer value will only be set once\n"
<< nodep->warnContextSecondary());
} else {
nodep->v3warn(IMPLICITSTATIC,
"Variable's lifetime implicitly set to static\n"
<< nodep->warnMore()
<< "... The initializer value will only be set once\n"
<< nodep->warnMore()
<< "... Suggest use 'static' before variable declaration'");
nodep->v3warn(
IMPLICITSTATIC,
"Variable's lifetime implicitly set to static (IEEE 1800-2023 6.21)\n"
<< nodep->warnMore() << "... The initializer value will only be set once\n"
<< nodep->warnMore()
<< "... Suggest use 'static' before variable declaration'");
}
}
if (!m_lifetimeAllowed && nodep->lifetime().isAutomatic()) {

View File

@ -73,6 +73,11 @@ class LinkResolveVisitor final : public VNVisitor {
void visit(AstClass* nodep) override {
VL_RESTORER(m_classp);
m_classp = nodep;
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* const varp = VN_CAST(stmtp, Var)) {
if (!varp->isParam()) varp->varType(VVarType::MEMBER);
}
}
iterateChildren(nodep);
}
void visit(AstConstraint* nodep) override {
@ -120,7 +125,6 @@ class LinkResolveVisitor final : public VNVisitor {
}
void visit(AstVar* nodep) override {
iterateChildren(nodep);
if (m_classp && !nodep->isParam()) nodep->varType(VVarType::MEMBER);
if (m_ftaskp) nodep->funcLocal(true);
if (nodep->isSigModPublic()) {
nodep->sigModPublic(false); // We're done with this attribute
@ -307,6 +311,11 @@ class LinkResolveVisitor final : public VNVisitor {
m_modp->modPublic(true); // Need to get to the task...
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else if (nodep->pragType() == VPragmaType::VERILATOR_LIB) {
UASSERT_OBJ(m_modp, nodep, "VERILATOR_LIB not under a module");
m_modp->verilatorLib(true);
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else {
iterateChildren(nodep);
}

View File

@ -1464,6 +1464,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
});
DECL_OPTION("-fdfg-pre-inline", FOnOff, &m_fDfgPreInline);
DECL_OPTION("-fdfg-post-inline", FOnOff, &m_fDfgPostInline);
DECL_OPTION("-fdfg-push-down-sels", FOnOff, &m_fDfgPushDownSels);
DECL_OPTION("-fdfg-scoped", FOnOff, &m_fDfgScoped);
DECL_OPTION("-fdfg-synthesize-all", FOnOff, &m_fDfgSynthesizeAll);
DECL_OPTION("-fexpand", FOnOff, &m_fExpand);

View File

@ -397,6 +397,7 @@ private:
bool m_fDfgPeephole = true; // main switch: -fno-dfg-peephole
bool m_fDfgPreInline; // main switch: -fno-dfg-pre-inline and -fno-dfg
bool m_fDfgPostInline; // main switch: -fno-dfg-post-inline and -fno-dfg
bool m_fDfgPushDownSels = true; // main switch: -fno-dfg-push-down-sels
bool m_fDfgScoped; // main switch: -fno-dfg-scoped and -fno-dfg
bool m_fDfgSynthesizeAll = false; // main switch: -fdfg-synthesize-all
bool m_fDeadAssigns; // main switch: -fno-dead-assigns: remove dead assigns
@ -711,6 +712,7 @@ public:
bool fDfgPeephole() const { return m_fDfgPeephole; }
bool fDfgPreInline() const { return m_fDfgPreInline; }
bool fDfgPostInline() const { return m_fDfgPostInline; }
bool fDfgPushDownSels() const { return m_fDfgPushDownSels; }
bool fDfgScoped() const { return m_fDfgScoped; }
bool fDfgSynthesizeAll() const { return m_fDfgSynthesizeAll; }
bool fDfgPeepholeEnabled(const std::string& name) const {

View File

@ -616,6 +616,108 @@ class ParamProcessor final {
if (nodep->op4p()) replaceRefsRecurse(nodep->op4p(), oldClassp, newClassp);
if (nodep->nextp()) replaceRefsRecurse(nodep->nextp(), oldClassp, newClassp);
}
// Helper visitor to update VarXRefs to use variables from specialized interfaces.
// When a module with interface ports is cloned and the port's interface is remapped
// to a specialized version, VarXRefs that access members of the old interface need
// to be updated to reference the corresponding members in the new interface.
class VarXRefRelinkVisitor final : public VNVisitor {
AstNodeModule* m_modp; // The cloned module we're updating
std::unordered_map<AstVar*, AstNodeModule*> m_varModuleMap; // Cache var->module lookups
public:
explicit VarXRefRelinkVisitor(AstNodeModule* newModp)
: m_modp{newModp} {
iterate(newModp);
}
private:
// Find which module a variable belongs to, using cache to avoid repeated backp() walks
AstNodeModule* findVarModule(AstVar* varp) {
const auto it = m_varModuleMap.find(varp);
if (it != m_varModuleMap.end()) return it->second;
AstNodeModule* varModp = nullptr;
for (AstNode* np = varp; np; np = np->backp()) {
if (AstNodeModule* const modp = VN_CAST(np, NodeModule)) {
varModp = modp;
break;
}
}
m_varModuleMap[varp] = varModp;
return varModp;
}
void visit(AstVarXRef* nodep) override {
AstVar* const varp = nodep->varp();
if (!varp) {
iterateChildren(nodep);
return;
}
// Get the dotted prefix (port name) from the VarXRef
// dotted() format: "portname" or "portname.subpath" or empty
const string& dotted = nodep->dotted();
if (dotted.empty()) {
iterateChildren(nodep);
return;
}
const size_t dotPos = dotted.find('.');
const string portName = (dotPos == string::npos) ? dotted : dotted.substr(0, dotPos);
if (portName.empty()) {
iterateChildren(nodep);
return;
}
// Find the interface port variable in the cloned module
AstVar* portVarp = nullptr;
for (AstNode* stmtp = m_modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* const varChkp = VN_CAST(stmtp, Var)) {
if (varChkp->name() == portName && varChkp->isIfaceRef()) {
portVarp = varChkp;
break;
}
}
}
if (!portVarp) {
iterateChildren(nodep);
return;
}
// Get the interface module from the port's dtype
AstIfaceRefDType* const irefp = VN_CAST(portVarp->subDTypep(), IfaceRefDType);
if (!irefp) {
iterateChildren(nodep);
return;
}
AstNodeModule* const newIfacep = irefp->ifaceViaCellp();
if (!newIfacep) {
iterateChildren(nodep);
return;
}
// Find which module the variable currently belongs to (cached)
AstNodeModule* const varModp = findVarModule(varp);
// If variable is in a different module than the port's interface, remap it
if (varModp && varModp != newIfacep) {
for (AstNode* stmtp = newIfacep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* const newVarp = VN_CAST(stmtp, Var)) {
if (newVarp->name() == varp->name()) {
UINFO(9, "VarXRef relink " << varp->name() << " in " << varModp->name()
<< " -> " << newIfacep->name() << endl);
nodep->varp(newVarp);
break;
}
}
}
}
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
};
// Return true on success, false on error
bool deepCloneModule(AstNodeModule* srcModp, AstNode* ifErrorp, AstPin* paramsp,
const string& newname, const IfaceRefRefs& ifaceRefRefs) {
@ -737,7 +839,25 @@ class ParamProcessor final {
// thus we need to stash this info.
collectPins(clonemapp, newModp, srcModp->user3p());
// Relink parameter vars to the new module
relinkPins(clonemapp, paramsp);
// For interface ports (e.g., l3_if #(W, L0A_W) l3), the parameter pins may
// reference variables from the enclosing module rather than from the interface
// being cloned. In such cases, use relinkPinsByName to match by variable name.
// Check if any parameter pins reference variables outside the cloned interface.
// This is O(n) but acceptable since parameter pin lists are typically small (<10 pins).
bool needRelinkByName = false;
if (paramsp) {
for (AstPin* pinp = paramsp; pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
if (pinp->modVarp() && clonemapp->find(pinp->modVarp()) == clonemapp->end()) {
needRelinkByName = true;
break;
}
}
}
if (needRelinkByName) {
relinkPinsByName(paramsp, newModp);
} else {
relinkPins(clonemapp, paramsp);
}
// Fix any interface references
for (auto it = ifaceRefRefs.cbegin(); it != ifaceRefRefs.cend(); ++it) {
@ -751,6 +871,12 @@ class ParamProcessor final {
cloneIrefp->ifacep(pinIrefp->ifaceViaCellp());
UINFO(8, " IfaceNew " << cloneIrefp);
}
// Fix VarXRefs that reference variables in old interfaces.
// Now that interface port dtypes have been updated above, we can use them
// to find the correct interface for each VarXRef.
if (!ifaceRefRefs.empty()) { VarXRefRelinkVisitor{newModp}; }
// Assign parameters to the constants specified
// DOES clone() so must be finished with module clonep() before here
for (AstPin* pinp = paramsp; pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
@ -961,8 +1087,9 @@ class ParamProcessor final {
}
AstIfaceRefDType* pinIrefp = nullptr;
const AstNode* const exprp = pinp->exprp();
const AstVar* const varp
= (exprp && VN_IS(exprp, VarRef)) ? VN_AS(exprp, VarRef)->varp() : nullptr;
const AstVar* const varp = (exprp && VN_IS(exprp, NodeVarRef))
? VN_AS(exprp, NodeVarRef)->varp()
: nullptr;
if (varp && varp->subDTypep() && VN_IS(varp->subDTypep(), IfaceRefDType)) {
pinIrefp = VN_AS(varp->subDTypep(), IfaceRefDType);
} else if (varp && varp->subDTypep() && arraySubDTypep(varp->subDTypep())
@ -978,6 +1105,13 @@ class ParamProcessor final {
pinIrefp
= VN_AS(arraySubDTypep(VN_AS(exprp->op1p(), VarRef)->varp()->subDTypep()),
IfaceRefDType);
} else if (VN_IS(exprp, CellArrayRef)) {
// Interface array element selection (e.g., l1(l2.l1[0]) for nested iface
// array) The CellArrayRef is not yet fully linked to an interface type. Skip
// interface cleanup for this pin - V3LinkDot will resolve this later. Just
// continue to the next pin without error.
UINFO(9, "Skipping interface cleanup for CellArrayRef pin: " << pinp << endl);
continue;
}
UINFO(9, " portIfaceRef " << portIrefp);
@ -1184,9 +1318,11 @@ class ParamProcessor final {
cellInterfaceCleanup(pinsp, srcModp, longname /*ref*/, any_overrides /*ref*/,
ifaceRefRefs /*ref*/);
// Default params are resolved as overrides
// Classes/modules with type parameters need specialization even when types match defaults.
// This is required for UVM parameterized classes. However, interfaces should NOT
// be specialized when type params match defaults (needed for nested interface ports).
bool defaultsResolved = false;
if (!any_overrides) {
if (!any_overrides && !VN_IS(srcModp, Iface)) {
for (AstPin* pinp = paramsp; pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
if (pinp->modPTypep()) {
any_overrides = true;
@ -1238,6 +1374,7 @@ class ParamProcessor final {
<< " cellName=" << nodep->name()
<< " cloned=" << cloned);
// Link source class to its specialized version for later relinking of method references
if (defaultsResolved) srcModp->user4p(newModp);
for (auto* stmtp = newModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
@ -1569,6 +1706,25 @@ class ParamVisitor final : public VNVisitor {
return false;
}
// Recursively specialize nested interface cells within a specialized interface.
// This handles parameter passthrough for nested interface hierarchies.
void specializeNestedIfaceCells(AstNodeModule* ifaceModp) {
for (AstNode* stmtp = ifaceModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
AstCell* const nestedCellp = VN_CAST(stmtp, Cell);
if (!nestedCellp) continue;
if (!VN_IS(nestedCellp->modp(), Iface)) continue;
if (!nestedCellp->paramsp()) continue;
if (cellParamsReferenceIfacePorts(nestedCellp)) continue;
AstNodeModule* const nestedSrcModp = nestedCellp->modp();
if (AstNodeModule* const nestedNewModp = m_processor.nodeDeparam(
nestedCellp, nestedSrcModp, ifaceModp, ifaceModp->someInstanceName())) {
// Recursively process nested interfaces within this nested interface
if (nestedNewModp != nestedSrcModp) specializeNestedIfaceCells(nestedNewModp);
}
}
}
// A generic visitor for cells and class refs
void visitCellOrClassRef(AstNode* nodep, bool isIface) {
// Must do ifaces first, so push to list and do in proper order
@ -1581,7 +1737,13 @@ class ParamVisitor final : public VNVisitor {
AstCell* const cellp = VN_CAST(nodep, Cell);
if (!cellParamsReferenceIfacePorts(cellp)) {
AstNodeModule* const srcModp = cellp->modp();
m_processor.nodeDeparam(cellp, srcModp, m_modp, m_modp->someInstanceName());
if (AstNodeModule* const newModp = m_processor.nodeDeparam(
cellp, srcModp, m_modp, m_modp->someInstanceName())) {
// For specialized interfaces, recursively process nested interface cells.
// This ensures nested interfaces are already specialized when modules
// using the interface are processed (parameter passthrough fix).
if (newModp != srcModp) specializeNestedIfaceCells(newModp);
}
}
}
@ -1808,13 +1970,25 @@ class ParamVisitor final : public VNVisitor {
V3Const::constifyParamsEdit(nodep->selp());
if (const AstConst* const constp = VN_CAST(nodep->selp(), Const)) {
const string index = AstNode::encodeNumber(constp->toSInt());
const string replacestr = nodep->name() + "__BRA__??__KET__";
// For nested interface array ports, the node name may have a __Viftop suffix
// that doesn't exist in the original unlinked text. Try without the suffix.
const string viftopSuffix = "__Viftop";
const string baseName
= VString::endsWith(nodep->name(), viftopSuffix)
? nodep->name().substr(0, nodep->name().size() - viftopSuffix.size())
: nodep->name();
const string replacestr = baseName + "__BRA__??__KET__";
const size_t pos = m_unlinkedTxt.find(replacestr);
UASSERT_OBJ(pos != string::npos, nodep,
"Could not find array index in unlinked text: '"
<< m_unlinkedTxt << "' for node: " << nodep);
// For interface port array element selections (e.g., l1(l2.l1[0])),
// the AstCellArrayRef may be visited outside of an AstUnlinkedRef context.
// In such cases, m_unlinkedTxt won't contain the expected pattern.
// Simply skip the replacement - the cell array ref will be resolved later.
if (pos == string::npos) {
UINFO(9, "Skipping unlinked text replacement for " << nodep << endl);
return;
}
m_unlinkedTxt.replace(pos, replacestr.length(),
nodep->name() + "__BRA__" + index + "__KET__");
baseName + "__BRA__" + index + "__KET__");
} else {
nodep->v3error("Could not expand constant selection inside dotted reference: "
<< nodep->selp()->prettyNameQ());

View File

@ -102,6 +102,8 @@ class ProtectVisitor final : public VNVisitor {
txtp->add("\n`ifdef VERILATOR\n");
txtp->add("`verilator_config\n");
txtp->add("verilator_lib -module \"" + m_libName + "\"\n");
txtp->add("profile_data -hier-dpi \"" + m_libName + "_protectlib_combo_update\" -cost 64'd"
+ std::to_string(v3Global.currentHierBlockCost()) + "\n");
txtp->add("profile_data -hier-dpi \"" + m_libName + "_protectlib_seq_update\" -cost 64'd"

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,13 @@
//*************************************************************************
// V3Reorder transformations:
//
// reorderAll() reorders statements within individual blocks
// to avoid delay vars when possible. It no longer splits always blocks.
// reorderAll() reorders statements within individual blocks to avoid
// shwdow variables use by non blocking assignments when possible.
// For exmaple, the left side needs a shadow variable for 'b', the
// right side does not:
// Bad: Good:
// b <= a; c <= b;
// c <= b; b <= a;
//
// The scoreboard tracks data deps as follows:
//
@ -54,11 +59,12 @@
#include "V3Reorder.h"
#include "V3EmitV.h"
#include "V3Graph.h"
#include "V3Stats.h"
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -68,431 +74,237 @@ namespace {
//######################################################################
// Support classes
class SplitNodeVertex VL_NOT_FINAL : public V3GraphVertex {
VL_RTTI_IMPL(SplitNodeVertex, V3GraphVertex)
class ReorderNodeVertex VL_NOT_FINAL : public V3GraphVertex {
VL_RTTI_IMPL(ReorderNodeVertex, V3GraphVertex)
AstNode* const m_nodep;
protected:
SplitNodeVertex(V3Graph* graphp, AstNode* nodep)
ReorderNodeVertex(V3Graph* graphp, AstNode* nodep)
: V3GraphVertex{graphp}
, m_nodep{nodep} {}
~SplitNodeVertex() override = default;
~ReorderNodeVertex() override = default;
// ACCESSORS
// Do not make accessor for nodep(), It may change due to
// reordering a lower block, but we don't repair it
string name() const override { return cvtToHex(m_nodep) + ' ' + m_nodep->prettyTypeName(); }
std::string name() const override {
std::string str = cvtToHex(m_nodep) + '\n';
if (AstVarScope* const vscp = VN_CAST(m_nodep, VarScope)) {
str += vscp->prettyName();
} else {
str += V3EmitV::debugVerilogForTree(m_nodep);
str = VString::quoteBackslash(str);
str = VString::quoteAny(str, '"', '\\');
str = VString::replaceSubstr(str, "\n", "\\l");
}
return str;
}
FileLine* fileline() const override { return nodep()->fileline(); }
std::string dotShape() const override { return VN_IS(m_nodep, VarScope) ? "ellipse" : "box"; }
public:
virtual AstNode* nodep() const { return m_nodep; }
};
class SplitPliVertex final : public SplitNodeVertex {
VL_RTTI_IMPL(SplitPliVertex, SplitNodeVertex)
class ReorderImpureVertex final : public ReorderNodeVertex {
VL_RTTI_IMPL(ReorderImpureVertex, ReorderNodeVertex)
public:
explicit SplitPliVertex(V3Graph* graphp, AstNode* nodep)
: SplitNodeVertex{graphp, nodep} {}
~SplitPliVertex() override = default;
string name() const override VL_MT_STABLE { return "*PLI*"; }
explicit ReorderImpureVertex(V3Graph* graphp, AstNode* nodep)
: ReorderNodeVertex{graphp, nodep} {}
~ReorderImpureVertex() override = default;
string name() const override VL_MT_STABLE { return "*IMPURE*"; }
string dotColor() const override { return "red"; }
};
class ReorderLogicVertex final : public ReorderNodeVertex {
VL_RTTI_IMPL(ReorderLogicVertex, ReorderNodeVertex)
public:
ReorderLogicVertex(V3Graph* graphp, AstNode* nodep)
: ReorderNodeVertex{graphp, nodep} {}
~ReorderLogicVertex() override = default;
string dotColor() const override { return "black"; }
};
class ReorderVarStdVertex final : public ReorderNodeVertex {
VL_RTTI_IMPL(ReorderVarStdVertex, ReorderNodeVertex)
public:
ReorderVarStdVertex(V3Graph* graphp, AstVarScope* nodep)
: ReorderNodeVertex{graphp, nodep} {}
~ReorderVarStdVertex() override = default;
string dotColor() const override { return "blue"; }
};
class ReorderVarPostVertex final : public ReorderNodeVertex {
VL_RTTI_IMPL(ReorderVarPostVertex, ReorderNodeVertex)
public:
ReorderVarPostVertex(V3Graph* graphp, AstVarScope* nodep)
: ReorderNodeVertex{graphp, nodep} {}
~ReorderVarPostVertex() override = default;
string name() const override { return "POST "s + ReorderNodeVertex::name(); }
string dotColor() const override { return "green"; }
};
class SplitLogicVertex final : public SplitNodeVertex {
VL_RTTI_IMPL(SplitLogicVertex, SplitNodeVertex)
public:
SplitLogicVertex(V3Graph* graphp, AstNode* nodep)
: SplitNodeVertex{graphp, nodep} {}
~SplitLogicVertex() override = default;
string dotColor() const override { return "yellow"; }
};
class SplitVarStdVertex final : public SplitNodeVertex {
VL_RTTI_IMPL(SplitVarStdVertex, SplitNodeVertex)
public:
SplitVarStdVertex(V3Graph* graphp, AstNode* nodep)
: SplitNodeVertex{graphp, nodep} {}
~SplitVarStdVertex() override = default;
string dotColor() const override { return "skyblue"; }
};
class SplitVarPostVertex final : public SplitNodeVertex {
VL_RTTI_IMPL(SplitVarPostVertex, SplitNodeVertex)
public:
SplitVarPostVertex(V3Graph* graphp, AstNode* nodep)
: SplitNodeVertex{graphp, nodep} {}
~SplitVarPostVertex() override = default;
string name() const override { return "POST "s + SplitNodeVertex::name(); }
string dotColor() const override { return "CadetBlue"; }
};
//######################################################################
// Edge types
class SplitEdge VL_NOT_FINAL : public V3GraphEdge {
VL_RTTI_IMPL(SplitEdge, V3GraphEdge)
class ReorderEdge VL_NOT_FINAL : public V3GraphEdge {
VL_RTTI_IMPL(ReorderEdge, V3GraphEdge)
uint32_t m_ignoreInStep = 0; // Step number that if set to, causes this edge to be ignored
static uint32_t s_stepNum; // Global step number
protected:
static constexpr int WEIGHT_NORMAL = 10;
SplitEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight,
bool cutable = CUTABLE)
: V3GraphEdge{graphp, fromp, top, weight, cutable} {}
~SplitEdge() override = default;
ReorderEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, bool cutable)
: V3GraphEdge{graphp, fromp, top, WEIGHT_NORMAL, cutable} {}
~ReorderEdge() override = default;
virtual bool followScoreboard() const = 0;
std::string dotStyle() const override {
return ignoreThisStep() ? "dotted" : V3GraphEdge::dotStyle();
}
public:
// Iterator for graph functions
static void incrementStep() { ++s_stepNum; }
bool ignoreThisStep() const { return m_ignoreInStep == s_stepNum; }
void setIgnoreThisStep() { m_ignoreInStep = s_stepNum; }
virtual bool followScoreboard() const = 0;
static bool followScoreboard(const V3GraphEdge* edgep) {
const SplitEdge* const oedgep = static_cast<const SplitEdge*>(edgep);
if (oedgep->ignoreThisStep()) return false;
return oedgep->followScoreboard();
const ReorderEdge& edge = *edgep->as<ReorderEdge>();
return !edge.ignoreThisStep() && edge.followScoreboard();
}
static bool followCyclic(const V3GraphEdge* edgep) {
const SplitEdge* const oedgep = static_cast<const SplitEdge*>(edgep);
return (!oedgep->ignoreThisStep());
}
string dotStyle() const override {
return ignoreThisStep() ? "dotted" : V3GraphEdge::dotStyle();
const ReorderEdge& edge = *edgep->as<ReorderEdge>();
return !edge.ignoreThisStep();
}
};
uint32_t SplitEdge::s_stepNum = 0;
uint32_t ReorderEdge::s_stepNum = 0;
class SplitPostEdge final : public SplitEdge {
VL_RTTI_IMPL(SplitPostEdge, SplitEdge)
class ReorderPostEdge final : public ReorderEdge {
VL_RTTI_IMPL(ReorderPostEdge, ReorderEdge)
public:
SplitPostEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {}
~SplitPostEdge() override = default;
ReorderPostEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: ReorderEdge{graphp, fromp, top, CUTABLE} {}
~ReorderPostEdge() override = default;
bool followScoreboard() const override { return false; }
string dotColor() const override { return "khaki"; }
};
class SplitLVEdge final : public SplitEdge {
VL_RTTI_IMPL(SplitLVEdge, SplitEdge)
class ReorderLVEdge final : public ReorderEdge {
VL_RTTI_IMPL(ReorderLVEdge, ReorderEdge)
public:
SplitLVEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {}
~SplitLVEdge() override = default;
ReorderLVEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: ReorderEdge{graphp, fromp, top, CUTABLE} {}
~ReorderLVEdge() override = default;
bool followScoreboard() const override { return true; }
string dotColor() const override { return "yellowGreen"; }
};
class SplitRVEdge final : public SplitEdge {
VL_RTTI_IMPL(SplitRVEdge, SplitEdge)
class ReorderRVEdge final : public ReorderEdge {
VL_RTTI_IMPL(ReorderRVEdge, ReorderEdge)
public:
SplitRVEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {}
~SplitRVEdge() override = default;
ReorderRVEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: ReorderEdge{graphp, fromp, top, CUTABLE} {}
~ReorderRVEdge() override = default;
bool followScoreboard() const override { return true; }
string dotColor() const override { return "green"; }
};
class SplitScorebdEdge final : public SplitEdge {
VL_RTTI_IMPL(SplitScorebdEdge, SplitEdge)
class ReorderScorebdEdge final : public ReorderEdge {
VL_RTTI_IMPL(ReorderScorebdEdge, ReorderEdge)
public:
SplitScorebdEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {}
~SplitScorebdEdge() override = default;
ReorderScorebdEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: ReorderEdge{graphp, fromp, top, CUTABLE} {}
~ReorderScorebdEdge() override = default;
bool followScoreboard() const override { return true; }
string dotColor() const override { return "blue"; }
};
class SplitStrictEdge final : public SplitEdge {
VL_RTTI_IMPL(SplitStrictEdge, SplitEdge)
class ReorderStrictEdge final : public ReorderEdge {
VL_RTTI_IMPL(ReorderStrictEdge, ReorderEdge)
// A strict order, based on the original statement order in the graph
// The only non-cutable edge type
public:
SplitStrictEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: SplitEdge{graphp, fromp, top, WEIGHT_NORMAL, NOT_CUTABLE} {}
~SplitStrictEdge() override = default;
ReorderStrictEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top)
: ReorderEdge{graphp, fromp, top, NOT_CUTABLE} {}
~ReorderStrictEdge() override = default;
bool followScoreboard() const override { return true; }
string dotColor() const override { return "blue"; }
};
//######################################################################
// Split class functions
// Reorder class functions
class SplitReorderBaseVisitor VL_NOT_FINAL : public VNVisitor {
// NODE STATE
// AstVarScope::user1p -> Var SplitNodeVertex* for usage var, 0=not set yet
// AstVarScope::user2p -> Var SplitNodeVertex* for delayed assignment var, 0=not set yet
// Ast*::user3p -> Statement SplitLogicVertex* (temporary only)
// Ast*::user4 -> Current ordering number (reorderBlock usage)
const VNUser1InUse m_inuser1;
const VNUser2InUse m_inuser2;
const VNUser3InUse m_inuser3;
const VNUser4InUse m_inuser4;
class ReorderVisitor final : public VNVisitor {
// NODE STATE - Only under AstAlways
// AstVarScope::user1p -> Var ReorderVarStdVertex* for usage var, 0=not set yet
// AstVarScope::user2p -> Var ReorderVarPostVertex* for delayed assignment var, 0=not set yet
// Ast*::user3p -> Statement ReorderLogicVertex* (temporary only)
// Ast*::user4 -> Current ordering number (reorderBlock usage)
protected:
// STATE
string m_noReorderWhy; // Reason we can't reorder
std::vector<SplitLogicVertex*> m_stmtStackps; // Current statements being tracked
SplitPliVertex* m_pliVertexp; // Element specifying PLI ordering
V3Graph m_graph; // Scoreboard of var usages/dependencies
bool m_inDly; // Inside ASSIGNDLY
// CONSTRUCTORS
public:
SplitReorderBaseVisitor() { scoreboardClear(); }
~SplitReorderBaseVisitor() override = default;
V3Graph* m_graphp = nullptr; // Scoreboard of var usages/dependencies
ReorderImpureVertex* m_impureVtxp = nullptr; // Element specifying PLI ordering
bool m_inDly = false; // Inside ASSIGNDLY
const char* m_noReorderWhy = nullptr; // Reason we can't reorder
std::vector<ReorderLogicVertex*> m_stmtStackps; // Current statements being tracked
// METHODS
protected:
void scoreboardClear() {
// VV***** We reset user1p() and user2p on each block!!!
m_inDly = false;
m_graph.clear();
m_stmtStackps.clear();
m_pliVertexp = nullptr;
m_noReorderWhy = "";
AstNode::user1ClearTree();
AstNode::user2ClearTree();
AstNode::user3ClearTree();
AstNode::user4ClearTree();
}
private:
void scoreboardPli(AstNode* nodep) {
// Order all PLI statements with other PLI statements
// This ensures $display's and such remain in proper order
// We don't prevent splitting out other non-pli statements, however.
if (!m_pliVertexp) {
m_pliVertexp = new SplitPliVertex{&m_graph, nodep}; // m_graph.clear() will delete it
}
for (const auto& vtxp : m_stmtStackps) {
// Both ways...
new SplitScorebdEdge{&m_graph, vtxp, m_pliVertexp};
new SplitScorebdEdge{&m_graph, m_pliVertexp, vtxp};
}
}
void scoreboardPushStmt(AstNode* nodep) {
// UINFO(9, " push " << nodep);
SplitLogicVertex* const vertexp = new SplitLogicVertex{&m_graph, nodep};
m_stmtStackps.push_back(vertexp);
UASSERT_OBJ(!nodep->user3p(), nodep, "user3p should not be used; cleared in processBlock");
nodep->user3p(vertexp);
}
void scoreboardPopStmt() {
// UINFO(9, " pop");
UASSERT(!m_stmtStackps.empty(), "Stack underflow");
m_stmtStackps.pop_back();
}
protected:
void scanBlock(AstNode* nodep) {
// Iterate across current block, making the scoreboard
for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) {
scoreboardPushStmt(nextp);
iterate(nextp);
scoreboardPopStmt();
}
}
void pruneDepsOnInputs() {
for (V3GraphVertex& vertex : m_graph.vertices()) {
if (vertex.outEmpty() && vertex.is<SplitVarStdVertex>()) {
if (debug() >= 9) {
const SplitVarStdVertex& sVtx = static_cast<SplitVarStdVertex&>(vertex);
UINFO(0, "Will prune deps on var " << sVtx.nodep());
sVtx.nodep()->dumpTree("- ");
}
for (V3GraphEdge& edge : vertex.inEdges()) {
SplitEdge& oedge = static_cast<SplitEdge&>(edge);
oedge.setIgnoreThisStep();
}
}
}
}
virtual void makeRvalueEdges(SplitVarStdVertex* vstdp) = 0;
// VISITORS
void visit(AstAlways* nodep) override = 0;
void visit(AstNodeIf* nodep) override = 0;
// We don't do AstLoop, due to the standard question of what is before vs. after
void visit(AstExprStmt* nodep) override {
VL_RESTORER(m_inDly);
m_inDly = false;
iterateChildren(nodep);
}
void visit(AstAssignDly* nodep) override {
UINFO(4, " ASSIGNDLY " << nodep);
iterate(nodep->rhsp());
VL_RESTORER(m_inDly);
m_inDly = true;
iterate(nodep->lhsp());
}
void visit(AstVarRef* nodep) override {
if (!m_stmtStackps.empty()) {
AstVarScope* const vscp = nodep->varScopep();
UASSERT_OBJ(vscp, nodep, "Not linked");
if (!nodep->varp()->isConst()) { // Constant lookups can be ignored
// ---
// NOTE: Formerly at this location we would avoid
// splitting or reordering if the variable is public.
//
// However, it should be perfectly safe to split an
// always block containing a public variable.
// Neither operation should perturb PLI's view of
// the variable.
//
// Former code:
//
// if (nodep->varp()->isSigPublic()) {
// // Public signals shouldn't be changed,
// // pli code might be messing with them
// scoreboardPli(nodep);
// }
// ---
// Create vertexes for variable
if (!vscp->user1p()) {
SplitVarStdVertex* const vstdp = new SplitVarStdVertex{&m_graph, vscp};
vscp->user1p(vstdp);
}
SplitVarStdVertex* const vstdp
= reinterpret_cast<SplitVarStdVertex*>(vscp->user1p());
// SPEEDUP: We add duplicate edges, that should be fixed
if (m_inDly && nodep->access().isWriteOrRW()) {
UINFO(4, " VARREFDLY: " << nodep);
// Delayed variable is different from non-delayed variable
if (!vscp->user2p()) {
SplitVarPostVertex* const vpostp = new SplitVarPostVertex{&m_graph, vscp};
vscp->user2p(vpostp);
new SplitPostEdge{&m_graph, vstdp, vpostp};
}
SplitVarPostVertex* const vpostp
= reinterpret_cast<SplitVarPostVertex*>(vscp->user2p());
// Add edges
for (SplitLogicVertex* vxp : m_stmtStackps) {
new SplitLVEdge{&m_graph, vpostp, vxp};
}
} else { // Nondelayed assignment
if (nodep->access().isWriteOrRW()) {
// Non-delay; need to maintain existing ordering
// with all consumers of the signal
UINFO(4, " VARREFLV: " << nodep);
for (SplitLogicVertex* ivxp : m_stmtStackps) {
new SplitLVEdge{&m_graph, vstdp, ivxp};
}
} else {
UINFO(4, " VARREF: " << nodep);
makeRvalueEdges(vstdp);
}
}
}
}
}
void visit(AstJumpGo* nodep) override {
// Jumps will disable reordering at all levels
// This is overly pessimistic; we could treat jumps as barriers, and
// reorder everything between jumps/labels, however jumps are rare
// in always, so the performance gain probably isn't worth the work.
UINFO(9, " NoReordering " << nodep);
m_noReorderWhy = "JumpGo";
iterateChildren(nodep);
}
//--------------------
// Default
void visit(AstNode* nodep) override {
// **** SPECIAL default type that sets PLI_ORDERING
if (!m_stmtStackps.empty() && !nodep->isPure()) {
UINFO(9, " NotSplittable " << nodep);
scoreboardPli(nodep);
}
if (nodep->isTimingControl()) {
UINFO(9, " NoReordering " << nodep);
m_noReorderWhy = "TimingControl";
}
iterateChildren(nodep);
}
private:
VL_UNCOPYABLE(SplitReorderBaseVisitor);
};
class ReorderVisitor final : public SplitReorderBaseVisitor {
// CONSTRUCTORS
public:
explicit ReorderVisitor(AstNetlist* nodep) { iterate(nodep); }
~ReorderVisitor() override = default;
// METHODS
protected:
void makeRvalueEdges(SplitVarStdVertex* vstdp) override {
for (SplitLogicVertex* vxp : m_stmtStackps) new SplitRVEdge{&m_graph, vxp, vstdp};
}
void cleanupBlockGraph(AstNode* nodep) {
// Transform the graph into what we need
UINFO(5, "ReorderBlock " << nodep);
m_graph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue);
if (dumpGraphLevel() >= 9) m_graph.dumpDotFilePrefixed("reorderg_nodup", false);
// Simplify graph by removing redundant edges
m_graphp->removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue);
if (dumpGraphLevel() >= 9) m_graphp->dumpDotFilePrefixed("reorderg_nodup", false);
// Mark all the logic for this step
// Vertex::m_user begin: true indicates logic for this step
m_graph.userClearVertices();
// Mark all the logic for this step by setting Vertex::user() to true
m_graphp->userClearVertices();
for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) {
SplitLogicVertex* const vvertexp
= reinterpret_cast<SplitLogicVertex*>(nextp->user3p());
vvertexp->user(true);
nextp->user3u().to<ReorderLogicVertex*>()->user(true);
}
// New step
ReorderEdge::incrementStep();
// If a var vertex has only inputs, it's a input-only node,
// and can be ignored for coloring **this block only**
SplitEdge::incrementStep();
pruneDepsOnInputs();
for (V3GraphVertex& vtx : m_graphp->vertices()) {
if (!vtx.outEmpty()) continue;
if (!vtx.is<ReorderVarStdVertex>()) continue;
for (V3GraphEdge& edge : vtx.inEdges()) edge.as<ReorderEdge>()->setIgnoreThisStep();
}
// For reordering this single block only, mark all logic
// vertexes not involved with this step as unimportant
for (V3GraphVertex& vertex : m_graph.vertices()) {
if (!vertex.user()) {
if (vertex.is<SplitLogicVertex>()) {
for (V3GraphEdge& edge : vertex.inEdges()) {
SplitEdge& oedge = static_cast<SplitEdge&>(edge);
oedge.setIgnoreThisStep();
}
for (V3GraphEdge& edge : vertex.outEdges()) {
SplitEdge& oedge = static_cast<SplitEdge&>(edge);
oedge.setIgnoreThisStep();
}
}
}
for (V3GraphVertex& vertex : m_graphp->vertices()) {
if (vertex.user()) continue;
if (!vertex.is<ReorderLogicVertex>()) continue;
for (V3GraphEdge& edge : vertex.inEdges()) edge.as<ReorderEdge>()->setIgnoreThisStep();
for (V3GraphEdge& edge : vertex.outEdges())
edge.as<ReorderEdge>()->setIgnoreThisStep();
}
// Weak coloring to determine what needs to remain in order
// This follows all step-relevant edges excluding PostEdges, which are done later
m_graph.weaklyConnected(&SplitEdge::followScoreboard);
m_graphp->weaklyConnected(&ReorderEdge::followScoreboard);
// Add hard orderings between all nodes of same color, in the order they appeared
std::unordered_map<uint32_t, SplitLogicVertex*> lastOfColor;
for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) {
SplitLogicVertex* const vvertexp
= reinterpret_cast<SplitLogicVertex*>(nextp->user3p());
const uint32_t color = vvertexp->color();
UASSERT_OBJ(color, nextp, "No node color assigned");
if (lastOfColor[color]) {
new SplitStrictEdge{&m_graph, lastOfColor[color], vvertexp};
}
lastOfColor[color] = vvertexp;
std::unordered_map<uint32_t, ReorderLogicVertex*> lastOfColor;
for (AstNode* currp = nodep; currp; currp = currp->nextp()) {
ReorderLogicVertex* const vtxp = currp->user3u().to<ReorderLogicVertex*>();
const uint32_t color = vtxp->color();
UASSERT_OBJ(color, currp, "No node color assigned");
if (lastOfColor[color]) new ReorderStrictEdge{m_graphp, lastOfColor[color], vtxp};
lastOfColor[color] = vtxp;
}
// And a real ordering to get the statements into something reasonable
// We don't care if there's cutable violations here...
// Non-cutable violations should be impossible; as those edges are program-order
if (dumpGraphLevel() >= 9) m_graph.dumpDotFilePrefixed("splitg_preo", false);
m_graph.acyclic(&SplitEdge::followCyclic);
m_graph.rank(&SplitEdge::followCyclic); // Or order(), but that's more expensive
if (dumpGraphLevel() >= 9) m_graph.dumpDotFilePrefixed("splitg_opt", false);
if (dumpGraphLevel() >= 9) m_graphp->dumpDotFilePrefixed("reorderg_pre", false);
m_graphp->acyclic(&ReorderEdge::followCyclic);
m_graphp->rank(&ReorderEdge::followCyclic); // Or order(), but that's more expensive
if (dumpGraphLevel() >= 9) m_graphp->dumpDotFilePrefixed("reorderg_opt", false);
}
void reorderBlock(AstNode* nodep) {
@ -501,98 +313,213 @@ protected:
// Map the rank numbers into nodes they associate with
std::multimap<uint32_t, AstNode*> rankMap;
int currOrder = 0; // Existing sequence number of assignment
for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) {
const SplitLogicVertex* const vvertexp
= reinterpret_cast<SplitLogicVertex*>(nextp->user3p());
rankMap.emplace(vvertexp->rank(), nextp);
nextp->user4(++currOrder); // Record current ordering
for (AstNode* currp = nodep; currp; currp = currp->nextp()) {
const ReorderLogicVertex* const vtxp = currp->user3u().to<ReorderLogicVertex*>();
rankMap.emplace(vtxp->rank(), currp);
currp->user4(++currOrder); // Record current ordering
}
// Is the current ordering OK?
bool leaveAlone = true;
int newOrder = 0; // New sequence number of assignment
for (auto it = rankMap.cbegin(); it != rankMap.cend(); ++it) {
const AstNode* const nextp = it->second;
for (const auto& item : rankMap) {
const AstNode* const nextp = item.second;
if (++newOrder != nextp->user4()) leaveAlone = false;
}
if (leaveAlone) {
UINFO(6, " No changes");
} else {
VNRelinker replaceHandle; // Where to add the list
AstNode* newListp = nullptr;
for (auto it = rankMap.cbegin(); it != rankMap.cend(); ++it) {
AstNode* const nextp = it->second;
UINFO(6, " New order: " << nextp);
if (nextp == nodep) {
nodep->unlinkFrBack(&replaceHandle);
} else {
nextp->unlinkFrBack();
}
if (newListp) {
newListp = newListp->addNext(nextp);
} else {
newListp = nextp;
}
}
replaceHandle.relink(newListp);
return;
}
VNRelinker replaceHandle; // Where to add the list
AstNode* newListp = nullptr;
for (const auto& item : rankMap) {
AstNode* const nextp = item.second;
UINFO(6, " New order: " << nextp);
nextp->unlinkFrBack(nextp == nodep ? &replaceHandle : nullptr);
newListp = AstNode::addNext(newListp, nextp);
}
replaceHandle.relink(newListp);
}
void processBlock(AstNode* nodep) {
if (!nodep) return; // Empty lists are ignorable
// Pass the first node in a list of block items, we'll process them
// Check there's >= 2 sub statements, else nothing to analyze
// Save recursion state
AstNode* firstp = nodep; // We may reorder, and nodep is no longer first.
void* const oldBlockUser3 = nodep->user3p(); // May be overloaded in below loop, save it
nodep->user3p(nullptr);
UASSERT_OBJ(nodep->firstAbovep(), nodep,
"Node passed is in next list; should have processed all list at once");
// Process it
if (m_noReorderWhy) return;
// Empty lists are ignorable
if (!nodep) return;
UASSERT_OBJ(nodep->firstAbovep(), nodep, "Node passed is in not head of list");
UASSERT_OBJ(!nodep->user3p(), nodep, "Should not have a logic vertex");
// It nothing to reorder with, just iterate
if (!nodep->nextp()) {
// Just one, so can't reorder. Just look for more blocks/statements.
iterate(nodep);
} else {
UINFO(9, " processBlock " << nodep);
// Process block and followers
scanBlock(nodep);
if (m_noReorderWhy != "") { // Jump or something nasty
UINFO(9, " NoReorderBlock because " << m_noReorderWhy);
} else {
// Reorder statements in this block
cleanupBlockGraph(nodep);
reorderBlock(nodep);
// Delete old vertexes and edges only applying to this block
// First, walk back to first in list
while (firstp->backp()->nextp() == firstp) firstp = firstp->backp();
for (AstNode* nextp = firstp; nextp; nextp = nextp->nextp()) {
SplitLogicVertex* const vvertexp
= reinterpret_cast<SplitLogicVertex*>(nextp->user3p());
vvertexp->unlinkDelete(&m_graph);
}
}
return;
}
// Process it
UINFO(9, " processBlock " << nodep);
// Iterate across current block, making the scoreboard
for (AstNode* currp = nodep; currp; currp = currp->nextp()) {
// Create the logic vertex for this statement
UASSERT_OBJ(!currp->user3p(), currp, "user3p should not be set");
ReorderLogicVertex* const vtxp = new ReorderLogicVertex{m_graphp, currp};
currp->user3p(vtxp);
// Visit the statement - this can recursively reorder sub statements
m_stmtStackps.push_back(vtxp);
iterate(currp);
m_stmtStackps.pop_back();
}
if (m_noReorderWhy) { // Jump or something nasty
UINFO(9, " NoReorderBlock because " << m_noReorderWhy);
return;
}
// Reorder statements in this block
cleanupBlockGraph(nodep);
reorderBlock(nodep);
// 'nodep' might no longer be the head of the list, rewind
while (nodep->backp()->nextp() == nodep) nodep = nodep->backp();
// Delete vertexes and edges only applying to this block
for (AstNode* currp = nodep; currp; currp = currp->nextp()) {
currp->user3u().to<ReorderLogicVertex*>()->unlinkDelete(m_graphp);
currp->user3p(nullptr);
}
// Again, nodep may no longer be first.
firstp->user3p(oldBlockUser3);
}
// VISITORS
void visit(AstAlways* nodep) override {
UINFO(4, " ALW " << nodep);
UASSERT_OBJ(!m_graphp, nodep, "AstAlways should not nest");
VL_RESTORER(m_graphp);
VL_RESTORER(m_impureVtxp);
VL_RESTORER(m_inDly);
VL_RESTORER(m_noReorderWhy);
V3Graph graph;
m_graphp = &graph;
m_impureVtxp = nullptr;
m_inDly = false;
m_noReorderWhy = nullptr;
const VNUser1InUse user1InUse;
const VNUser2InUse user2InUse;
const VNUser3InUse user3InUse;
const VNUser4InUse user4InUse;
UINFOTREE(9, nodep, "", "alwIn:");
scoreboardClear();
processBlock(nodep->stmtsp());
UINFOTREE(9, nodep, "", "alwOut");
m_stmtStackps.clear();
}
void visit(AstNodeIf* nodep) override {
UINFO(4, " IF " << nodep);
if (!m_graphp || m_noReorderWhy) return;
iterateAndNextNull(nodep->condp());
processBlock(nodep->thensp());
processBlock(nodep->elsesp());
}
private:
VL_UNCOPYABLE(ReorderVisitor);
void visit(AstJumpGo* nodep) override {
if (!m_graphp || m_noReorderWhy) return;
m_noReorderWhy = "JumpGo";
iterateChildren(nodep);
}
void visit(AstExprStmt* nodep) override {
if (!m_graphp || m_noReorderWhy) return;
VL_RESTORER(m_inDly);
m_inDly = false;
iterateChildren(nodep);
}
void visit(AstAssignDly* nodep) override {
if (!m_graphp || m_noReorderWhy) return;
iterate(nodep->rhsp());
VL_RESTORER(m_inDly);
m_inDly = true;
iterate(nodep->lhsp());
}
void visit(AstVarRef* nodep) override {
if (!m_graphp || m_noReorderWhy) return;
if (m_stmtStackps.empty()) return;
// Reads of constants can be ignored - TODO: This should be "constexpr", not run-time const
if (nodep->varp()->isConst()) return;
// SPEEDUP: We add duplicate edges, that should be fixed
AstVarScope* const vscp = nodep->varScopep();
// Create vertexes for variable
if (!vscp->user1p()) vscp->user1p(new ReorderVarStdVertex{m_graphp, vscp});
ReorderVarStdVertex* const vstdp = vscp->user1u().to<ReorderVarStdVertex*>();
// Variable is read
if (nodep->access().isReadOnly()) {
for (ReorderLogicVertex* const vtxp : m_stmtStackps) {
new ReorderRVEdge{m_graphp, vtxp, vstdp};
}
return;
}
// Variable is written, not NBA
if (!m_inDly) {
for (ReorderLogicVertex* const vtxp : m_stmtStackps) {
new ReorderLVEdge{m_graphp, vstdp, vtxp};
}
return;
}
// Variable is written by NBA
if (!vscp->user2p()) {
ReorderVarPostVertex* const vpostp = new ReorderVarPostVertex{m_graphp, vscp};
vscp->user2p(vpostp);
new ReorderPostEdge{m_graphp, vstdp, vpostp};
}
ReorderVarPostVertex* const vpostp = vscp->user2u().to<ReorderVarPostVertex*>();
for (ReorderLogicVertex* const vtxp : m_stmtStackps) {
new ReorderLVEdge{m_graphp, vpostp, vtxp};
}
}
void visit(AstNode* nodep) override {
// Outside AstAlways, just descend
if (!m_graphp) {
iterateChildren(nodep);
return;
}
// Early exit if decided not to reorder
if (m_noReorderWhy) return;
// Timing control prevents reordering
if (nodep->isTimingControl()) {
m_noReorderWhy = "TimingControl";
return;
}
// Order all impure statements with other impure statements
if (!nodep->isPure()) {
if (!m_impureVtxp) m_impureVtxp = new ReorderImpureVertex{m_graphp, nodep};
// This edge is only used to find weakly connected components, so one edge is enough
for (ReorderLogicVertex* const vtxp : m_stmtStackps) {
new ReorderScorebdEdge{m_graphp, m_impureVtxp, vtxp};
}
}
iterateChildren(nodep);
}
// CONSTRUCTORS
public:
explicit ReorderVisitor(AstNetlist* nodep) { iterate(nodep); }
~ReorderVisitor() override = default;
};
} // namespace

View File

@ -86,18 +86,32 @@ void invertAndMergeSenTreeMap(
}
std::vector<AstSenTree*>
findTriggeredIface(const AstVarScope* vscp, const VirtIfaceTriggers::IfaceSensMap& vifTrigged,
findTriggeredIface(const AstVarScope* vscp,
const VirtIfaceTriggers::IfaceMemberSensMap& vifMemberTriggered) {
UASSERT_OBJ(vscp->varp()->sensIfacep(), vscp, "Not an virtual interface trigger");
std::vector<AstSenTree*> result;
const auto ifaceIt = vifTrigged.find(vscp->varp()->sensIfacep());
if (ifaceIt != vifTrigged.end()) result.push_back(ifaceIt->second);
for (const auto& memberIt : vifMemberTriggered) {
if (vscp->varp()->sensIfacep() == memberIt.first.m_ifacep) {
result.push_back(memberIt.second);
}
const AstIface* ifacep;
if (vscp->varp()->isVirtIface()) {
// If `vscp->varp()->isVirtIface()` is true then the interface type that viface is pointing
// to is under `VN_AS(vscp->varp()->dtypep(), IfaceRefDType)->ifacep()`
ifacep = VN_AS(vscp->varp()->dtypep(), IfaceRefDType)->ifacep();
// Virtual interface is sensitive to a different interface type than it is a virtual type
// of - this may be a valid behaviour but this function does not expects that
UASSERT_OBJ(vscp->varp()->sensIfacep() == nullptr, vscp,
"Virtual interface has an ambiguous type - "
<< vscp->varp()->sensIfacep()->prettyTypeName()
<< " != " << ifacep->prettyTypeName());
} else {
// If `vscp->varp()` is of a non-virtual interface type it has `sensIfacep()` set to
// interface it is sensitive to
ifacep = vscp->varp()->sensIfacep();
}
if (result.empty()) vscp->v3fatalSrc("Did not find virtual interface trigger");
UASSERT_OBJ(ifacep, vscp, "Variable is not sensitive for any interface");
std::vector<AstSenTree*> result;
for (const auto& memberIt : vifMemberTriggered) {
if (memberIt.first.m_ifacep == ifacep) result.push_back(memberIt.second);
}
UASSERT_OBJ(!result.empty(), vscp, "Did not find virtual interface trigger");
return result;
}
@ -381,10 +395,6 @@ void createFinal(AstNetlist* netlistp, const LogicClasses& logicClasses) {
void addVirtIfaceTriggerAssignments(const VirtIfaceTriggers& virtIfaceTriggers,
uint32_t vifTriggerIndex, uint32_t vifMemberTriggerIndex,
const TriggerKit& trigKit) {
for (const auto& p : virtIfaceTriggers.m_ifaceTriggers) {
trigKit.addExtraTriggerAssignment(p.second, vifTriggerIndex);
++vifTriggerIndex;
}
for (const auto& p : virtIfaceTriggers.m_memberTriggers) {
trigKit.addExtraTriggerAssignment(p.second, vifMemberTriggerIndex);
++vifMemberTriggerIndex;
@ -411,7 +421,7 @@ void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilde
// Gather the relevant sensitivity expressions and create the trigger kit
const auto& senTreeps = getSenTreesUsedBy({&comb, &hybrid});
const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBulider, {},
senTreeps, "stl", extraTriggers, true);
senTreeps, "stl", extraTriggers, true, false);
// Remap sensitivities (comb has none, so only do the hybrid)
remapSensitivities(hybrid, trigKit.mapVec());
@ -436,7 +446,11 @@ void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilde
// Inner loop statements
nullptr,
// Prep statements: Compute the current 'stl' triggers
trigKit.newCompCall(),
[&trigKit] {
AstNodeStmt* const stmtp = trigKit.newCompBaseCall();
if (stmtp) stmtp->addNext(trigKit.newDumpCall(trigKit.vscp(), trigKit.name(), true));
return stmtp;
}(),
// Work statements: Invoke the 'stl' function
util::callVoidFunc(stlFuncp));
@ -479,9 +493,6 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
? extraTriggers.allocate("DPI export trigger")
: std::numeric_limits<uint32_t>::max();
const size_t firstVifTriggerIndex = extraTriggers.size();
for (const auto& p : virtIfaceTriggers.m_ifaceTriggers) {
extraTriggers.allocate("virtual interface: " + p.first->name());
}
const size_t firstVifMemberTriggerIndex = extraTriggers.size();
for (const auto& p : virtIfaceTriggers.m_memberTriggers) {
const auto& item = p.first;
@ -492,7 +503,8 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
// Gather the relevant sensitivity expressions and create the trigger kit
const auto& senTreeps = getSenTreesUsedBy({&logic});
const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, {},
senTreeps, "ico", extraTriggers, false);
senTreeps, "ico", extraTriggers, false, false);
std::ignore = senExprBuilder.getAndClearResults();
if (dpiExportTriggerVscp) {
trigKit.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
@ -516,8 +528,6 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
= dpiExportTriggerVscp
? trigKit.newExtraTriggerSenTree(trigKit.vscp(), dpiExportTriggerIndex)
: nullptr;
const auto& vifTriggeredIco
= virtIfaceTriggers.makeIfaceToSensMap(trigKit, firstVifTriggerIndex, trigKit.vscp());
const auto& vifMemberTriggeredIco = virtIfaceTriggers.makeMemberToSensMap(
trigKit, firstVifMemberTriggerIndex, trigKit.vscp());
@ -530,9 +540,9 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
out.push_back(inputChanged);
}
if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered);
if (vscp->varp()->sensIfacep()) {
if (vscp->varp()->isVirtIface()) {
std::vector<AstSenTree*> ifaceTriggered
= findTriggeredIface(vscp, vifTriggeredIco, vifMemberTriggeredIco);
= findTriggeredIface(vscp, vifMemberTriggeredIco);
out.insert(out.end(), ifaceTriggered.begin(), ifaceTriggered.end());
}
});
@ -544,7 +554,11 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
// Inner loop statements
nullptr,
// Prep statements: Compute the current 'ico' triggers
trigKit.newCompCall(),
[&trigKit] {
AstNodeStmt* const stmtp = trigKit.newCompBaseCall();
if (stmtp) stmtp->addNext(trigKit.newDumpCall(trigKit.vscp(), trigKit.name(), true));
return stmtp;
}(),
// Work statements: Invoke the 'ico' function
util::callVoidFunc(icoFuncp));
@ -581,8 +595,8 @@ void createEval(AstNetlist* netlistp, //
) {
FileLine* const flp = netlistp->fileline();
// 'createResume' consumes the contents that 'createCommit' needs, so do the right order
AstCCall* const timingCommitp = timingKit.createCommit(netlistp);
// 'createResume' consumes the contents that 'createReady' needs, so do the right order
AstCCall* const timingReadyp = timingKit.createReady(netlistp);
AstCCall* const timingResumep = timingKit.createResume(netlistp);
// Create the active eval loop
@ -593,9 +607,16 @@ void createEval(AstNetlist* netlistp, //
// Prep statements
[&]() {
// Compute the current 'act' triggers - the NBA triggers are the latched value
AstNodeStmt* stmtsp = trigKit.newCompCall(nbaKit.m_vscp);
// Commit trigger awaits from the previous iteration
if (timingCommitp) stmtsp = AstNode::addNext(stmtsp, timingCommitp->makeStmt());
AstNodeStmt* stmtsp = trigKit.newCompBaseCall();
AstNodeStmt* const dumpp
= stmtsp ? trigKit.newDumpCall(trigKit.vscp(), trigKit.name(), true) : nullptr;
// Mark as ready for triggered awaits
if (timingReadyp) stmtsp = AstNode::addNext(stmtsp, timingReadyp->makeStmt());
if (AstVarScope* const vscAccp = trigKit.vscAccp()) {
stmtsp = AstNode::addNext(stmtsp, trigKit.newOrIntoCall(actKit.m_vscp, vscAccp));
}
stmtsp = AstNode::addNext(stmtsp, trigKit.newCompExtCall(nbaKit.m_vscp));
stmtsp = AstNode::addNext(stmtsp, dumpp);
// Latch the 'act' triggers under the 'nba' triggers
stmtsp = AstNode::addNext(stmtsp, trigKit.newOrIntoCall(nbaKit.m_vscp, actKit.m_vscp));
//
@ -604,8 +625,15 @@ void createEval(AstNetlist* netlistp, //
// Work statements
[&]() {
AstNodeStmt* workp = nullptr;
if (AstVarScope* const actAccp = trigKit.vscAccp()) {
AstCMethodHard* const cCallp = new AstCMethodHard{
flp, new AstVarRef{flp, actAccp, VAccess::WRITE}, VCMethod::UNPACKED_FILL,
new AstConst{flp, AstConst::Unsized64{}, 0}};
cCallp->dtypeSetVoid();
workp = AstNode::addNext(workp, cCallp->makeStmt());
}
// Resume triggered timing schedulers
if (timingResumep) workp = timingResumep->makeStmt();
if (timingResumep) workp = AstNode::addNext(workp, timingResumep->makeStmt());
// Invoke the 'act' function
workp = AstNode::addNext(workp, util::callVoidFunc(actKit.m_funcp));
//
@ -645,7 +673,7 @@ void createEval(AstNetlist* netlistp, //
netlistp->nbaEventp(nullptr);
netlistp->nbaEventTriggerp(nullptr);
// If a dynamic NBA is pending, clear the pending flag and fire the commit event
// If a dynamic NBA is pending, clear the pending flag and fire the ready event
AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, nbaEventTriggerp, VAccess::READ}};
ifp->addThensp(util::setVar(continuep, 1));
ifp->addThensp(util::setVar(nbaEventTriggerp, 0));
@ -721,17 +749,6 @@ void createEval(AstNetlist* netlistp, //
//============================================================================
// Helper that builds virtual interface trigger sentrees
VirtIfaceTriggers::IfaceSensMap
VirtIfaceTriggers::makeIfaceToSensMap(const TriggerKit& trigKit, uint32_t vifTriggerIndex,
AstVarScope* trigVscp) const {
std::map<const AstIface*, AstSenTree*> map;
for (const auto& p : m_ifaceTriggers) {
map.emplace(p.first, trigKit.newExtraTriggerSenTree(trigVscp, vifTriggerIndex));
++vifTriggerIndex;
}
return map;
}
VirtIfaceTriggers::IfaceMemberSensMap
VirtIfaceTriggers::makeMemberToSensMap(const TriggerKit& trigKit, uint32_t vifTriggerIndex,
AstVarScope* trigVscp) const {
@ -860,9 +877,6 @@ void schedule(AstNetlist* netlistp) {
? extraTriggers.allocate("DPI export trigger")
: std::numeric_limits<uint32_t>::max();
const uint32_t firstVifTriggerIndex = extraTriggers.size();
for (const auto& p : virtIfaceTriggers.m_ifaceTriggers) {
extraTriggers.allocate("virtual interface: " + p.first->name());
}
const uint32_t firstVifMemberTriggerIndex = extraTriggers.size();
for (const auto& p : virtIfaceTriggers.m_memberTriggers) {
const auto& item = p.first;
@ -876,11 +890,12 @@ void schedule(AstNetlist* netlistp) {
&logicRegions.m_obs, //
&logicRegions.m_react, //
&timingKit.m_lbs});
const TriggerKit trigKit = TriggerKit::create(netlistp, staticp, senExprBuilder, preTreeps,
senTreeps, "act", extraTriggers, false);
const TriggerKit trigKit
= TriggerKit::create(netlistp, staticp, senExprBuilder, preTreeps, senTreeps, "act",
extraTriggers, false, v3Global.usesTiming());
// Add post updates from the timing kit
if (timingKit.m_postUpdates) trigKit.compp()->addStmtsp(timingKit.m_postUpdates);
if (timingKit.m_postUpdates) trigKit.compBasep()->addStmtsp(timingKit.m_postUpdates);
if (dpiExportTriggerVscp) {
trigKit.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
@ -915,8 +930,6 @@ void schedule(AstNetlist* netlistp) {
? trigKit.newExtraTriggerSenTree(trigKit.vscp(), dpiExportTriggerIndex)
: nullptr;
const auto& vifTriggeredAct
= virtIfaceTriggers.makeIfaceToSensMap(trigKit, firstVifTriggerIndex, trigKit.vscp());
const auto& vifMemberTriggeredAct = virtIfaceTriggers.makeMemberToSensMap(
trigKit, firstVifMemberTriggerIndex, trigKit.vscp());
@ -926,9 +939,9 @@ void schedule(AstNetlist* netlistp) {
auto it = actTimingDomains.find(vscp);
if (it != actTimingDomains.end()) out = it->second;
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredAct);
if (vscp->varp()->sensIfacep()) {
if (vscp->varp()->isVirtIface()) {
std::vector<AstSenTree*> ifaceTriggered
= findTriggeredIface(vscp, vifTriggeredAct, vifMemberTriggeredAct);
= findTriggeredIface(vscp, vifMemberTriggeredAct);
out.insert(out.end(), ifaceTriggered.begin(), ifaceTriggered.end());
}
});
@ -954,8 +967,6 @@ void schedule(AstNetlist* netlistp) {
= dpiExportTriggerVscp
? trigKit.newExtraTriggerSenTree(trigVscp, dpiExportTriggerIndex)
: nullptr;
const auto& vifTriggered
= virtIfaceTriggers.makeIfaceToSensMap(trigKit, firstVifTriggerIndex, trigVscp);
const auto& vifMemberTriggered
= virtIfaceTriggers.makeMemberToSensMap(trigKit, firstVifMemberTriggerIndex, trigVscp);
@ -966,9 +977,11 @@ void schedule(AstNetlist* netlistp) {
auto it = timingDomains.find(vscp);
if (it != timingDomains.end()) out = it->second;
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered);
if (vscp->varp()->sensIfacep()) {
// Sometimes virtual interfaces mix with non-virtual one so, here both have to be
// detected - look `t_virtual_interface_nba_assign`
if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) {
std::vector<AstSenTree*> ifaceTriggered
= findTriggeredIface(vscp, vifTriggered, vifMemberTriggered);
= findTriggeredIface(vscp, vifMemberTriggered);
out.insert(out.end(), ifaceTriggered.begin(), ifaceTriggered.end());
}
});
@ -1007,7 +1020,44 @@ void schedule(AstNetlist* netlistp) {
createEval(netlistp, icoLoopp, trigKit, actKit, nbaKit, obsKit, reactKit, postponedFuncp,
timingKit);
// Step 15: Clean up
// Step 15: Add neccessary evaluation before awaits
if (AstCCall* const readyp = timingKit.createReady(netlistp)) {
staticp->addStmtsp(readyp->makeStmt());
beforeTrigVisitor(netlistp, senExprBuilder, trigKit);
} else {
// beforeTrigVisitor clears Sentree pointers in AstCAwaits (as these sentrees will get
// deleted later) if there was no need to call it, SenTrees have to be cleaned manually
netlistp->foreach([](AstCAwait* const cAwaitp) { cAwaitp->clearSentreep(); });
}
if (AstVarScope* const trigAccp = trigKit.vscAccp()) {
// Copy trigger vector to accumulator at the end of static initialziation so,
// triggers fired during initialization persist to the first resume.
const AstUnpackArrayDType* const trigAccDTypep
= VN_AS(trigAccp->dtypep(), UnpackArrayDType);
UASSERT_OBJ(
trigAccDTypep->right() == 0, trigAccp,
"Expected that trigger vector and accumulator start elements enumeration from 0");
UASSERT_OBJ(trigAccDTypep->left() >= 0, trigAccp,
"Expected that trigger vector and accumulator has no negative indexes");
FileLine* const flp = trigAccp->fileline();
AstVarScope* const vscp = netlistp->topScopep()->scopep()->createTemp("__Vi", 32);
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(
new AstAssign{flp,
new AstArraySel{flp, new AstVarRef{flp, trigAccp, VAccess::WRITE},
new AstVarRef{flp, vscp, VAccess::READ}},
new AstArraySel{flp, new AstVarRef{flp, actKit.m_vscp, VAccess::READ},
new AstVarRef{flp, vscp, VAccess::READ}}});
loopp->addStmtsp(util::incrementVar(vscp));
loopp->addStmtsp(new AstLoopTest{
flp, loopp,
new AstLte{flp, new AstVarRef{flp, vscp, VAccess::READ},
new AstConst{flp, AstConst::WidthedValue{}, 32,
static_cast<uint32_t>(trigAccDTypep->left())}}});
staticp->addStmtsp(loopp);
}
// Step 16: Clean up
netlistp->clearStlFirstIterationp();
// Haven't split static initializer yet

View File

@ -184,10 +184,12 @@ struct LogicReplicas final {
// "Extra" triggers, with "Sense" triggers taking up the bulk of the bits.
//
class TriggerKit final {
public:
// Triggers are storead as an UnpackedArray with a fixed word size
static constexpr uint32_t WORD_SIZE_LOG2 = 6; // 64-bits / VL_QUADSIZE
static constexpr uint32_t WORD_SIZE = 1 << WORD_SIZE_LOG2;
private:
const std::string m_name; // TriggerKit name
const bool m_slow; // TriggerKit is for schedulign 'slow' code
const uint32_t m_nSenseWords; // Number of words for Sense triggers
@ -195,6 +197,8 @@ class TriggerKit final {
const uint32_t m_nPreWords; // Number of words for 'pre' part
const uint32_t m_nVecWords = m_nSenseWords + m_nExtraWords; // Number of words in 'vec' part
// SenItems to corresponding bit indexes
std::unordered_map<VNRef<const AstSenItem>, size_t> m_senItem2TrigIdx;
// Data type of a single trigger word
AstNodeDType* m_wordDTypep = nullptr;
// Data type of a trigger vector holding one copy of all triggers
@ -204,8 +208,14 @@ class TriggerKit final {
AstUnpackArrayDType* m_trigExtDTypep = nullptr;
// The AstVarScope representing the extended trigger vector
AstVarScope* m_vscp = nullptr;
// The AstCFunc that computes the current active triggers
AstCFunc* m_compp = nullptr;
// The AstVarScope representing the trigger accumulator vector
// It is used to accumulate triggers that were found fired and cleared in beforeTrigger's
// in current 'act' region iteration
AstVarScope* m_vscAccp = nullptr;
// The AstCFunc that computes the current active base triggers
AstCFunc* m_compVecp = nullptr;
// The AstCFunc that computes the current active extended triggers
AstCFunc* m_compExtp = nullptr;
// The AstCFunc that dumps a trigger vector
AstCFunc* m_dumpp = nullptr;
// The AstCFunc that dumps an exended trigger vector - create lazily
@ -214,8 +224,7 @@ class TriggerKit final {
mutable AstCFunc* m_anySetVecp = nullptr;
mutable AstCFunc* m_anySetExtp = nullptr;
// The AstCFunc setting bits in a trigger vector that are set in another - create lazily
mutable AstCFunc* m_orIntoVecp = nullptr;
mutable AstCFunc* m_orIntoExtp = nullptr;
mutable std::array<AstCFunc*, 4> m_orIntoVecps = {nullptr};
// The AstCFunc setting a trigger vector to all zeroes - create lazily
mutable AstCFunc* m_clearp = nullptr;
@ -228,13 +237,15 @@ class TriggerKit final {
AstCFunc* createDumpExtFunc() const;
AstCFunc* createAnySetFunc(AstUnpackArrayDType* const dtypep) const;
AstCFunc* createClearFunc() const;
AstCFunc* createOrIntoFunc(AstUnpackArrayDType* const iDtypep) const;
AstCFunc* createOrIntoFunc(AstUnpackArrayDType* const oDtypep,
AstUnpackArrayDType* const iDtypep) const;
// Create an AstSenTree that is sensitive to the given trigger indices
AstSenTree* newTriggerSenTree(AstVarScope* vscp, const std::vector<uint32_t>& indices) const;
TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords, uint32_t nExtraWords,
uint32_t nPreWords);
uint32_t nPreWords,
std::unordered_map<VNRef<const AstSenItem>, size_t> senItem2TrigIdx, bool useAcc);
VL_UNCOPYABLE(TriggerKit);
TriggerKit& operator=(TriggerKit&&) = delete;
@ -258,6 +269,9 @@ public:
}
uint32_t size() const { return m_descriptions.size(); }
};
// Generates list of assignments that fills
static AstAssign* createSenTrigVecAssignment(AstVarScope* const target,
std::vector<AstNodeExpr*>& trigps);
// Create a TriggerKit for the given AstSenTree vector
static TriggerKit create(AstNetlist* netlistp, //
@ -267,11 +281,17 @@ public:
const std::vector<const AstSenTree*>& senTreeps, //
const string& name, //
const ExtraTriggers& extraTriggers, //
bool slow);
bool slow, //
bool useAcc);
// ACCESSORS
AstVarScope* vscp() const { return m_vscp; }
AstCFunc* compp() const { return m_compp; }
AstVarScope* vscAccp() const { return m_vscAccp; }
size_t senItem2TrigIdx(const AstSenItem* senItemp) const {
return m_senItem2TrigIdx.at(*senItemp);
}
AstCFunc* compBasep() const { return m_compVecp; }
const std::string& name() const { return m_name; }
const std::unordered_map<const AstSenTree*, AstSenTree*>& mapPre() const { return m_mapPre; }
const std::unordered_map<const AstSenTree*, AstSenTree*>& mapVec() const { return m_mapVec; }
@ -281,7 +301,8 @@ public:
AstNodeStmt* newClearCall(AstVarScope* vscp) const;
AstNodeStmt* newOrIntoCall(AstVarScope* op, AstVarScope* ip) const;
// Helpers for code generation
AstNodeStmt* newCompCall(AstVarScope* vscp = nullptr) const;
AstNodeStmt* newCompBaseCall() const;
AstNodeStmt* newCompExtCall(AstVarScope* vscp) const;
AstNodeStmt* newDumpCall(AstVarScope* vscp, const std::string& tag, bool debugOnly) const;
// Create a new (non-extended) trigger vector - might return nullptr if there are no triggers
AstVarScope* newTrigVec(const std::string& name) const;
@ -296,7 +317,7 @@ public:
// Everything needed for combining timing with static scheduling.
class TimingKit final {
AstCFunc* m_resumeFuncp = nullptr; // Global timing resume function
AstCFunc* m_commitFuncp = nullptr; // Global timing commit function
AstCFunc* m_readyFuncp = nullptr; // Global timing ready function
// Additional var sensitivities for V3Order
std::map<const AstVarScope*, std::set<AstSenTree*>> m_externalDomains;
@ -314,8 +335,8 @@ public:
const std::unordered_map<const AstSenTree*, AstSenTree*>& trigMap) const VL_MT_DISABLED;
// Creates a timing resume call (if needed, else returns null)
AstCCall* createResume(AstNetlist* const netlistp) VL_MT_DISABLED;
// Creates a timing commit call (if needed, else returns null)
AstCCall* createCommit(AstNetlist* const netlistp) VL_MT_DISABLED;
// Creates a timing ready call (if needed, else returns null)
AstCCall* createReady(AstNetlist* const netlistp) VL_MT_DISABLED;
TimingKit() = default;
TimingKit(LogicByScope&& lbs, AstNodeStmt* postUpdates,
@ -342,15 +363,10 @@ class VirtIfaceTriggers final {
};
public:
using IfaceSensMap = std::map<const AstIface*, AstSenTree*>;
using IfaceMemberSensMap = std::map<IfaceMember, AstSenTree*>;
std::vector<std::pair<const AstIface*, AstVarScope*>> m_ifaceTriggers;
std::vector<std::pair<IfaceMember, AstVarScope*>> m_memberTriggers;
void addIfaceTrigger(const AstIface* ifacep, AstVarScope* vscp) {
m_ifaceTriggers.emplace_back(ifacep, vscp);
}
void addMemberTrigger(const AstIface* ifacep, const AstVar* memberp, AstVarScope* vscp) {
m_memberTriggers.emplace_back(IfaceMember{ifacep, memberp}, vscp);
}
@ -366,9 +382,6 @@ public:
IfaceMemberSensMap makeMemberToSensMap(const TriggerKit& trigKit, uint32_t vifTriggerIndex,
AstVarScope* trigVscp) const;
IfaceSensMap makeIfaceToSensMap(const TriggerKit& trigKit, uint32_t vifTriggerIndex,
AstVarScope* trigVscp) const;
VL_UNCOPYABLE(VirtIfaceTriggers);
VirtIfaceTriggers() = default;
VirtIfaceTriggers(VirtIfaceTriggers&&) = default;
@ -415,6 +428,9 @@ void splitCheck(AstCFunc* ofuncp);
AstIf* createIfFromSenTree(AstSenTree* senTreep);
} // namespace util
void beforeTrigVisitor(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
const TriggerKit& trigKit);
} // namespace V3Sched
#endif // Guard

View File

@ -151,7 +151,7 @@ public:
, m_vscp{vscp} {
// Top level inputs are
if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()
|| varp()->sensIfacep()) {
|| varp()->sensIfacep() || varp()->isVirtIface()) {
addDrivingRegions(INPUT);
}
// Currently we always execute suspendable processes at the beginning of

View File

@ -16,7 +16,7 @@
//
// Functions defined in this file are used by V3Sched.cpp to properly integrate
// static scheduling with timing features. They create external domains for
// variables, remap them to trigger vectors, and create timing resume/commit
// variables, remap them to trigger vectors, and create timing resume/ready
// calls for the global eval loop. There is also a function that transforms
// forks into emittable constructs.
//
@ -92,13 +92,12 @@ AstIf* TimingKit::processTimingActives() {
AstVarRef* const schedrefp = VN_AS(
VN_AS(VN_AS(activep->stmtsp(), StmtExpr)->exprp(), CMethodHard)->fromp(), VarRef);
AstIf* const ifp = V3Sched::util::createIfFromSenTree(activep->sentreep());
ifp->addThensp(activep->stmtsp()->unlinkFrBackWithNext());
AstNode* const actionp = activep->stmtsp()->unlinkFrBackWithNext();
if (schedrefp->varScopep()->dtypep()->basicp()->isDelayScheduler()) {
dlyShedIfp = ifp;
dlyShedIfp = V3Sched::util::createIfFromSenTree(activep->sentreep());
dlyShedIfp->addThensp(actionp);
} else {
m_resumeFuncp->addStmtsp(ifp);
m_resumeFuncp->addStmtsp(actionp);
}
}
return dlyShedIfp;
@ -127,6 +126,25 @@ AstCCall* TimingKit::createResume(AstNetlist* const netlistp) {
m_resumeFuncp->declPrivate(true);
scopeTopp->addBlocksp(m_resumeFuncp);
// Add move-to-resume-queue calls for trigger schedulers
for (const auto& p : m_lbs) {
AstActive* const activep = p.second;
activep->foreach([this](AstCMethodHard* const exprp) {
if (exprp->method() != VCMethod::SCHED_RESUME) return;
AstNodeExpr* const fromp = exprp->fromp();
if (VN_AS(fromp->dtypep(), BasicDType)->keyword()
!= VBasicDTypeKwd::TRIGGER_SCHEDULER) {
return;
}
AstCMethodHard* const moveToResumep = new AstCMethodHard{
fromp->fileline(), fromp->cloneTree(false),
VCMethod::SCHED_MOVE_TO_RESUME_QUEUE,
exprp->pinsp() ? exprp->pinsp()->cloneTree(true) : nullptr};
moveToResumep->dtypeSetVoid();
m_resumeFuncp->addStmtsp(moveToResumep->makeStmt());
});
}
// Process all timing actives and get delay scheduler if present
AstIf* const dlyShedIfp = processTimingActives();
@ -146,10 +164,10 @@ AstCCall* TimingKit::createResume(AstNetlist* const netlistp) {
}
//============================================================================
// Creates a timing commit call (if needed, else returns null)
// Creates a timing ready call (if needed, else returns null)
AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) {
if (!m_commitFuncp) {
AstCCall* TimingKit::createReady(AstNetlist* const netlistp) {
if (!m_readyFuncp) {
for (auto& p : m_lbs) {
AstActive* const activep = p.second;
auto* const resumep = VN_AS(VN_AS(activep->stmtsp(), StmtExpr)->exprp(), CMethodHard);
@ -160,67 +178,35 @@ AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) {
|| schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler(),
schedulerp, "Unexpected type");
if (!schedulerp->dtypep()->basicp()->isTriggerScheduler()) continue;
// Create the global commit function only if we have trigger schedulers
if (!m_commitFuncp) {
// Create the global ready function only if we have trigger schedulers
if (!m_readyFuncp) {
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
m_commitFuncp
= new AstCFunc{netlistp->fileline(), "_timing_commit", scopeTopp, ""};
m_commitFuncp->dontCombine(true);
m_commitFuncp->isLoose(true);
m_commitFuncp->isConst(false);
m_commitFuncp->declPrivate(true);
scopeTopp->addBlocksp(m_commitFuncp);
m_readyFuncp = new AstCFunc{netlistp->fileline(), "_timing_ready", scopeTopp, ""};
m_readyFuncp->dontCombine(true);
m_readyFuncp->isLoose(true);
m_readyFuncp->isConst(false);
m_readyFuncp->declPrivate(true);
scopeTopp->addBlocksp(m_readyFuncp);
}
// There is a somewhat complicate dance here. Given a suspendable
// process of the form:
// ->evntA;
// $display("Fired evntA");
// @(evntA or evntB);
// The firing of the event cannot trigger the event control
// following it, as the process is not yet sensitive to the event
// when it fires (same applies for change detects). The way the
// scheduling works, the @evnt will suspend the process before
// the firing of the event is recognized on the next iteration of
// the 'act' loop, and hence could incorrectly resume the @evnt
// statement. To make this work, whenever a process suspends, it
// goes into an "uncommitted" state, so it cannot be resumed
// immediately on the next iteration of the 'act' loop, which is
// what we want. The question then is, when should the suspended
// process be "committed" and hence possible to be resumed. This is
// done when it is know for sure the suspending expression was not
// triggered on the current iteration of the 'act' loop. With
// multiple events in the suspending expression, all events need
// to be not triggered to safely commit the suspended process.
//
// This is is consistent with IEEE scheduling semantics, and
// behaves as if the above was executed as:
// ->evntA;
// $display("Fired evnt");
// ... all other statements in the 'act' loop that might fire evntA or evntB ...
// @(evntA or evntB);
// which is a valid execution. Race conditions be fun to debug,
// but they are a responsibility of the user.
AstSenTree* const senTreep = activep->sentreep();
FileLine* const flp = senTreep->fileline();
// Create an 'AstIf' sensitive to the suspending triggers
AstIf* const ifp = V3Sched::util::createIfFromSenTree(senTreep);
m_commitFuncp->addStmtsp(ifp);
m_readyFuncp->addStmtsp(ifp);
// Commit the processes suspended on this sensitivity expression
// in the **else** branch, when the event is known to be not fired.
// Mark as ready the processes resumed on this sensitivity expression
AstVarRef* const refp = new AstVarRef{flp, schedulerp, VAccess::READWRITE};
AstCMethodHard* const callp = new AstCMethodHard{flp, refp, VCMethod::SCHED_COMMIT};
AstCMethodHard* const callp = new AstCMethodHard{flp, refp, VCMethod::SCHED_READY};
callp->dtypeSetVoid();
if (resumep->pinsp()) callp->addPinsp(resumep->pinsp()->cloneTree(false));
ifp->addElsesp(callp->makeStmt());
ifp->addThensp(callp->makeStmt());
}
// We still haven't created a commit function (no trigger schedulers), return null
if (!m_commitFuncp) return nullptr;
// We still haven't created a ready function (no trigger schedulers), return null
if (!m_readyFuncp) return nullptr;
}
AstCCall* const callp = new AstCCall{m_commitFuncp->fileline(), m_commitFuncp};
AstCCall* const callp = new AstCCall{m_readyFuncp->fileline(), m_readyFuncp};
callp->dtypeSetVoid();
return callp;
}
@ -272,7 +258,7 @@ class AwaitVisitor final : public VNVisitor {
if (schedulerp->dtypep()->basicp()->isTriggerScheduler()) {
UASSERT_OBJ(methodp->pinsp(), methodp,
"Trigger method should have pins from V3Timing");
// The first pin is the commit boolean, the rest (if any) should be debug info
// The first pin is the ready boolean, the rest (if any) should be debug info
// See V3Timing for details
if (AstNode* const dbginfop = methodp->pinsp()->nextp()) {
if (methodp->pinsp()) addResumePins(resumep, static_cast<AstNodeExpr*>(dbginfop));
@ -316,7 +302,6 @@ class AwaitVisitor final : public VNVisitor {
void visit(AstCAwait* nodep) override {
if (AstSenTree* const sentreep = nodep->sentreep()) {
if (!sentreep->user1SetOnce()) createResumeActive(nodep);
nodep->clearSentreep(); // Clear as these sentrees will get deleted later
if (m_inProcess) m_processDomains.insert(sentreep);
}
}

View File

@ -225,19 +225,21 @@ AstCFunc* TriggerKit::createClearFunc() const {
// Done
return funcp;
}
AstCFunc* TriggerKit::createOrIntoFunc(AstUnpackArrayDType* const iDtypep) const {
AstCFunc* TriggerKit::createOrIntoFunc(AstUnpackArrayDType* const oDtypep,
AstUnpackArrayDType* const iDtypep) const {
AstNetlist* const netlistp = v3Global.rootp();
FileLine* const flp = netlistp->topScopep()->fileline();
AstNodeDType* const u32DTypep = netlistp->findUInt32DType();
// Create function
std::string name = "_trigger_orInto__" + m_name;
name += iDtypep == m_trigVecDTypep ? "" : "_ext";
name += iDtypep == m_trigVecDTypep ? "_vec" : "_ext";
name += oDtypep == m_trigVecDTypep ? "_vec" : "_ext";
AstCFunc* const funcp = util::makeSubFunction(netlistp, name, m_slow);
funcp->isStatic(true);
// Add arguments
AstVarScope* const oVscp = newArgument(funcp, m_trigVecDTypep, "out", VDirection::INOUT);
AstVarScope* const oVscp = newArgument(funcp, oDtypep, "out", VDirection::INOUT);
AstVarScope* const iVscp = newArgument(funcp, iDtypep, "in", VDirection::CONSTREF);
// Add loop counter variable
@ -257,10 +259,14 @@ AstCFunc* TriggerKit::createOrIntoFunc(AstUnpackArrayDType* const iDtypep) const
AstNodeExpr* const oWordp = new AstArraySel{flp, rd(oVscp), rd(nVscp)};
AstNodeExpr* const iWordp = new AstArraySel{flp, rd(iVscp), rd(nVscp)};
AstNodeExpr* const rhsp = new AstOr{flp, oWordp, iWordp};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, m_nVecWords};
AstConst* const outputRangeLeftp = VN_AS(oDtypep->rangep()->leftp(), Const);
AstConst* const inputRangeLeftp = VN_AS(iDtypep->rangep()->leftp(), Const);
AstNodeExpr* const limp = outputRangeLeftp->num().toSInt() < inputRangeLeftp->num().toSInt()
? outputRangeLeftp->cloneTreePure(false)
: inputRangeLeftp->cloneTreePure(false);
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLte{flp, rd(nVscp), limp}});
// Done
return funcp;
@ -297,16 +303,16 @@ AstNodeStmt* TriggerKit::newClearCall(AstVarScope* const vscp) const {
}
AstNodeStmt* TriggerKit::newOrIntoCall(AstVarScope* const oVscp, AstVarScope* const iVscp) const {
if (!m_nVecWords) return nullptr;
UASSERT_OBJ(oVscp->dtypep() == m_trigVecDTypep, oVscp, "Bad trigger vector type");
AstCFunc* funcp = nullptr;
if (iVscp->dtypep() == m_trigVecDTypep) {
if (!m_orIntoVecp) m_orIntoVecp = createOrIntoFunc(m_trigVecDTypep);
funcp = m_orIntoVecp;
} else if (iVscp->dtypep() == m_trigExtDTypep) {
if (!m_orIntoExtp) m_orIntoExtp = createOrIntoFunc(m_trigExtDTypep);
funcp = m_orIntoExtp;
} else {
iVscp->v3fatalSrc("Bad trigger vector type");
UASSERT_OBJ(iVscp->dtypep() == m_trigVecDTypep || iVscp->dtypep() == m_trigExtDTypep, iVscp,
"Bad input trigger vector type");
UASSERT_OBJ(oVscp->dtypep() == m_trigVecDTypep || oVscp->dtypep() == m_trigExtDTypep, oVscp,
"Bad output trigger vector type");
const size_t mask
= ((oVscp->dtypep() == m_trigExtDTypep) << 1) | (iVscp->dtypep() == m_trigExtDTypep);
AstCFunc*& funcp = m_orIntoVecps[mask];
if (!funcp) {
funcp = createOrIntoFunc(VN_AS(oVscp->dtypep(), UnpackArrayDType),
VN_AS(iVscp->dtypep(), UnpackArrayDType));
}
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, funcp};
@ -316,13 +322,19 @@ AstNodeStmt* TriggerKit::newOrIntoCall(AstVarScope* const oVscp, AstVarScope* co
return callp->makeStmt();
}
AstNodeStmt* TriggerKit::newCompCall(AstVarScope* vscp) const {
AstNodeStmt* TriggerKit::newCompBaseCall() const {
if (!m_nVecWords) return nullptr;
// If there are pre triggers, we need the argument
UASSERT(!m_nPreWords || vscp, "Need latched values for pre trigger compute");
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, m_compp};
if (m_nPreWords) callp->addArgsp(new AstVarRef{flp, vscp, VAccess::READ});
AstCCall* const callp = new AstCCall{flp, m_compVecp};
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstNodeStmt* TriggerKit::newCompExtCall(AstVarScope* vscp) const {
if (!m_nPreWords) return nullptr;
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, m_compExtp};
callp->addArgsp(new AstVarRef{flp, vscp, VAccess::READ});
callp->dtypeSetVoid();
return callp->makeStmt();
}
@ -402,24 +414,28 @@ void TriggerKit::addExtraTriggerAssignment(AstVarScope* vscp, uint32_t index, bo
AstNodeExpr* const wordp = new AstArraySel{flp, refp, static_cast<int>(wordIndex)};
AstNodeExpr* const trigLhsp = new AstSel{flp, wordp, static_cast<int>(bitIndex), 1};
AstNodeExpr* const trigRhsp = new AstVarRef{flp, vscp, VAccess::READ};
AstNodeStmt* const setp = new AstAssign{flp, trigLhsp, trigRhsp};
AstNode* const setp = new AstAssign{flp, trigLhsp, trigRhsp};
if (clear) {
// Clear the input variable
AstNodeStmt* const clrp = new AstAssign{flp, new AstVarRef{flp, vscp, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}};
// Note these are added in reverse order, so 'setp' executes before 'clrp'
m_compp->stmtsp()->addHereThisAsNext(clrp);
setp->addNext(new AstAssign{flp, new AstVarRef{flp, vscp, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}});
}
m_compp->stmtsp()->addHereThisAsNext(setp);
if (AstNode* const nodep = m_compVecp->stmtsp()) {
setp->addNext(setp, nodep->unlinkFrBackWithNext());
}
m_compVecp->addStmtsp(setp);
}
TriggerKit::TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords,
uint32_t nExtraWords, uint32_t nPreWords)
uint32_t nExtraWords, uint32_t nPreWords,
std::unordered_map<VNRef<const AstSenItem>, size_t> senItem2TrigIdx,
bool useAcc)
: m_name{name}
, m_slow{slow}
, m_nSenseWords{nSenseWords}
, m_nExtraWords{nExtraWords}
, m_nPreWords{nPreWords} {
, m_nPreWords{nPreWords}
, m_senItem2TrigIdx{std::move(senItem2TrigIdx)} {
// If no triggers, we don't need to generate anything
if (!m_nVecWords) return;
// Othewise construc the parts of the kit
@ -437,6 +453,7 @@ TriggerKit::TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords,
AstRange* const ep = new AstRange{flp, static_cast<int>(m_nVecWords + m_nPreWords - 1), 0};
m_trigExtDTypep = new AstUnpackArrayDType{flp, m_wordDTypep, ep};
netlistp->typeTablep()->addTypesp(m_trigExtDTypep);
m_compExtp = util::makeSubFunction(netlistp, "_eval_triggers_ext__" + m_name, m_slow);
} else {
m_trigExtDTypep = m_trigVecDTypep;
}
@ -444,11 +461,40 @@ TriggerKit::TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords,
m_vscp = scopep->createTemp("__V" + m_name + "Triggered", m_trigExtDTypep);
m_vscp->varp()->isInternal(true);
// The trigger computation function
m_compp = util::makeSubFunction(netlistp, "_eval_triggers__" + m_name, m_slow);
m_compVecp = util::makeSubFunction(netlistp, "_eval_triggers_vec__" + m_name, m_slow);
// The debug dump function, always 'slow'
m_dumpp = util::makeSubFunction(netlistp, "_dump_triggers__" + m_name, true);
m_dumpp->isStatic(true);
m_dumpp->ifdef("VL_DEBUG");
if (useAcc) {
m_vscAccp = scopep->createTemp("__V" + m_name + "TriggeredAcc", m_trigVecDTypep);
m_vscAccp->varp()->isInternal(true);
}
}
AstAssign* TriggerKit::createSenTrigVecAssignment(AstVarScope* const target,
std::vector<AstNodeExpr*>& trigps) {
FileLine* const flp = target->fileline();
AstAssign* trigStmtsp = nullptr;
// Assign sense triggers vector one word at a time
for (size_t i = 0; i < trigps.size(); i += WORD_SIZE) {
// Concatenate all bits in this trigger word using a balanced
for (uint32_t level = 0; level < WORD_SIZE_LOG2; ++level) {
const uint32_t stride = 1 << level;
for (uint32_t j = 0; j < WORD_SIZE; j += 2 * stride) {
trigps[i + j] = new AstConcat{trigps[i + j]->fileline(), trigps[i + j + stride],
trigps[i + j]};
trigps[i + j + stride] = nullptr;
}
}
// Set the whole word in the trigger vector
const int wordIndex = static_cast<int>(i / WORD_SIZE);
AstArraySel* const aselp
= new AstArraySel{flp, new AstVarRef{flp, target, VAccess::WRITE}, wordIndex};
trigStmtsp = AstNode::addNext(trigStmtsp, new AstAssign{flp, aselp, trigps[i]});
}
return trigStmtsp;
}
TriggerKit TriggerKit::create(AstNetlist* netlistp, //
@ -458,7 +504,8 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
const std::vector<const AstSenTree*>& senTreeps, //
const string& name, //
const ExtraTriggers& extraTriggers, //
bool slow) {
bool slow, //
bool useAcc) {
// Need to gather all the unique SenItems under the given SenTrees
// List of unique SenItems used by all 'senTreeps'
@ -512,7 +559,7 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
const uint32_t nExtraWords = nExtraTriggers / WORD_SIZE;
// We can now construct the trigger kit - this constructs all items that will be kept
TriggerKit kit{name, slow, nSenseWords, nExtraWords, nPreWords};
TriggerKit kit{name, slow, nSenseWords, nExtraWords, nPreWords, senItem2TrigIdx, useAcc};
// If there are no triggers we are done
if (!kit.m_nVecWords) return kit;
@ -587,7 +634,11 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
AstNodeExpr* const wordp = new AstArraySel{flp, wr(kit.m_vscp), wrdIndex};
AstNodeExpr* const lhsp = new AstSel{flp, wordp, bitIndex, 1};
AstNodeExpr* const rhsp = new AstConst{flp, AstConst::BitTrue{}};
initialTrigsp = AstNode::addNext(initialTrigsp, new AstAssign{flp, lhsp, rhsp});
if (useAcc) {
initFuncp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
} else {
initialTrigsp = AstNode::addNext(initialTrigsp, new AstAssign{flp, lhsp, rhsp});
}
}
// Add a debug statement for this trigger
@ -601,25 +652,7 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
}
UASSERT(trigps.size() == nSenseTriggers, "Inconsistent number of trigger expressions");
// Assign sense triggers vector one word at a time
AstNodeStmt* trigStmtsp = nullptr;
for (size_t i = 0; i < nSenseTriggers; i += WORD_SIZE) {
// Concatenate all bits in this trigger word using a balanced
for (uint32_t level = 0; level < WORD_SIZE_LOG2; ++level) {
const uint32_t stride = 1 << level;
for (uint32_t j = 0; j < WORD_SIZE; j += 2 * stride) {
trigps[i + j] = new AstConcat{trigps[i + j]->fileline(), trigps[i + j + stride],
trigps[i + j]};
trigps[i + j + stride] = nullptr;
}
}
// Set the whole word in the trigger vector
const int wordIndex = static_cast<int>(i / WORD_SIZE);
AstArraySel* const aselp = new AstArraySel{flp, wr(kit.m_vscp), wordIndex};
trigStmtsp = AstNode::addNext(trigStmtsp, new AstAssign{flp, aselp, trigps[i]});
}
trigps.clear();
AstAssign* const trigStmtsp = createSenTrigVecAssignment(kit.m_vscp, trigps);
// Add a print for each of the extra triggers
for (unsigned i = 0; i < extraTriggers.size(); ++i) {
@ -652,18 +685,18 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
}
// Get the SenExprBuilder results
const SenExprBuilder::Results senResults = senExprBuilder.getAndClearResults();
const SenExprBuilder::Results senResults = senExprBuilder.getResultsAndClearUpdates();
// Add the SenExprBuilder init statements to the static initialization functino
for (AstNodeStmt* const nodep : senResults.m_inits) initFuncp->addStmtsp(nodep);
// Assemble the trigger computation function
// Assemble the base trigger computation function
AstScope* const scopep = netlistp->topScopep()->scopep();
{
AstCFunc* const fp = kit.m_compp;
AstScope* const scopep = netlistp->topScopep()->scopep();
AstCFunc* const fp = kit.m_compVecp;
// Profiling push
if (v3Global.opt.profExec()) {
fp->addStmtsp(AstCStmt::profExecSectionPush(flp, "trig " + name));
fp->addStmtsp(AstCStmt::profExecSectionPush(flp, "trigBase " + name));
}
// Trigger computation
for (AstNodeStmt* const nodep : senResults.m_preUpdates) fp->addStmtsp(nodep);
@ -678,39 +711,39 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
ifp->addThensp(util::setVar(initVscp, 1));
ifp->addThensp(initialTrigsp);
}
// If there are 'pre' triggers, compute them
if (kit.m_nPreWords) {
// Add an argument to the function that takes the latched values
AstVarScope* const latchedp
= newArgument(fp, kit.m_trigVecDTypep, "latched", VDirection::CONSTREF);
// Add loop counter variable - this can't be local because we call util::splitCheck
AstVarScope* const nVscp = scopep->createTemp("__V" + name + "TrigPreLoopCounter", 32);
nVscp->varp()->noReset(true);
// Add a loop to compute the pre words
AstLoop* const loopp = new AstLoop{flp};
fp->addStmtsp(util::setVar(nVscp, 0));
fp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const offsetp = new AstConst{flp, kit.m_nVecWords};
AstNodeExpr* const lIdxp = new AstAdd{flp, rd(nVscp), offsetp};
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(kit.m_vscp), lIdxp};
AstNodeExpr* const aWordp = new AstArraySel{flp, rd(kit.m_vscp), rd(nVscp)};
AstNodeExpr* const bWordp = new AstArraySel{flp, rd(latchedp), rd(nVscp)};
AstNodeExpr* const rhsp = new AstAnd{flp, aWordp, new AstNot{flp, bWordp}};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, nPreWords};
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
}
// Add a call to the dumping function if debug is enabled
fp->addStmtsp(kit.newDumpCall(kit.m_vscp, name, true));
// Profiling pop
if (v3Global.opt.profExec()) {
fp->addStmtsp(AstCStmt::profExecSectionPop(flp, "trig " + name));
fp->addStmtsp(AstCStmt::profExecSectionPop(flp, "trigBase " + name));
}
// Done with the trigger computation function, split as might be large
util::splitCheck(fp);
};
// If there are 'pre' triggers, compute them
if (kit.m_nPreWords) {
AstCFunc* const fp = kit.m_compExtp;
// Add an argument to the function that takes the latched values
AstVarScope* const latchedp
= newArgument(fp, kit.m_trigVecDTypep, "latched", VDirection::CONSTREF);
// Add loop counter variable - this can't be local because we call util::splitCheck
AstVarScope* const nVscp = scopep->createTemp("__V" + name + "TrigPreLoopCounter", 32);
nVscp->varp()->noReset(true);
// Add a loop to compute the pre words
AstLoop* const loopp = new AstLoop{flp};
fp->addStmtsp(util::setVar(nVscp, 0));
fp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const offsetp = new AstConst{flp, kit.m_nVecWords};
AstNodeExpr* const lIdxp = new AstAdd{flp, rd(nVscp), offsetp};
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(kit.m_vscp), lIdxp};
AstNodeExpr* const aWordp = new AstArraySel{flp, rd(kit.m_vscp), rd(nVscp)};
AstNodeExpr* const bWordp = new AstArraySel{flp, rd(latchedp), rd(nVscp)};
AstNodeExpr* const rhsp = new AstAnd{flp, aWordp, new AstNot{flp, bWordp}};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, nPreWords};
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
util::splitCheck(fp);
}
// Done with the trigger computation function, split as might be large
// The debug code might leak signal names, so simply delete it when using --protect-ids
if (v3Global.opt.protectIds()) kit.m_dumpp->stmtsp()->unlinkFrBackWithNext()->deleteTree();
@ -720,4 +753,281 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
return kit;
}
// Find all CAwaits, clear SenTrees inside them, generate before-trigger functions (functions that
// shall be called before awaiting for a VCMethod::SCHED_TRIGGER) and add thier calls before
// proper CAwaits
class AwaitBeforeTrigVisitor final : public VNVisitor {
const VNUser1InUse m_user1InUse;
/**
* AstCAwait::user1() -> bool. True if node has been visited
* AstSenTree::user1p() -> AstCFunc*. Function that has to be called before awaiting
* for CAwait pointing to this SenTree
* AstCFunc::user1p() -> AstVarScope* Function's local temporary extended trigger
* vector variable scope
*/
// Netlist - needed for using util::makeSubFunction()
AstNetlist* const m_netlistp;
// Trigger kit - for accessing trigger vectors and mapping senItems to thier indexes
const TriggerKit& m_trigKit;
// Expression builder - for building expressions from SenItems
SenExprBuilder& m_senExprBuilder;
// Generator of unique names for before-trigger function
V3UniqueNames m_beforeTriggerFuncUniqueName;
// Vector containing every generated CFuncs and related SenTree
std::vector<std::pair<AstCFunc*, AstSenTree*>> m_generatedFuncs;
// Map from SenTree to coresponding scheduler
std::map<AstSenTree*, AstNodeExpr*> m_senTreeToSched;
// Map containing vectors of SenItems that share the same prevValue variable
std::unordered_map<VNRef<AstNode>, std::vector<AstSenItem*>> m_senExprToSenItem;
// Returns node which is used for grouping SenItems in `m_senExprToSenItem`
static AstNode* getSenHashNode(const AstSenItem* const nodep) {
if (AstVarRef* const varRefp = VN_CAST(nodep->sensp(), VarRef)) return varRefp;
return nodep->sensp();
}
// Populates `m_senExprToSenItem` with every group of SenItems that share the same prevValue
// variable. Groups that contain only one type of an edge are omitted.
void fillSenExprToSenItem() {
for (auto senTreeSched : m_senTreeToSched) {
AstSenTree* const senTreep = senTreeSched.first;
for (AstSenItem* senItemp = senTreep->sensesp(); senItemp;
senItemp = VN_AS(senItemp->nextp(), SenItem)) {
const VEdgeType edge = senItemp->edgeType();
if (edge.anEdge() || edge == VEdgeType::ET_CHANGED
|| edge == VEdgeType::ET_HYBRID) {
m_senExprToSenItem[*getSenHashNode(senItemp)].push_back(senItemp);
}
}
}
std::vector<VNRef<AstNode>> toRemove;
for (const auto& senExprToSenTree : m_senExprToSenItem) {
std::vector<AstSenItem*> senItemps = senExprToSenTree.second;
toRemove.push_back(senExprToSenTree.first);
for (size_t i = 1; i < senItemps.size(); ++i) {
if (senItemps[i]->edgeType() != senItemps[i - 1]->edgeType()) {
toRemove.pop_back();
break;
}
}
}
for (VNRef<AstNode> it : toRemove) m_senExprToSenItem.erase(it);
}
// For set of bits indexes (of sensitivity vector) return map from those indexes to set
// of schedulers sensitive to these indexes. Indices are split into word index and bit
// masking this index within given word
std::map<size_t, std::map<size_t, std::set<AstNodeExpr*>>>
getUsedTriggersToTrees(const std::set<size_t>& usedTriggers) {
std::map<size_t, std::map<size_t, std::set<AstNodeExpr*>>> usedTrigsToUsingTrees;
for (auto senTreeSched : m_senTreeToSched) {
const AstSenTree* const senTreep = senTreeSched.first;
AstNodeExpr* const shedp = senTreeSched.second;
// Find all common SenItem indexes for `senTreep` and `usedTriggers`
std::set<size_t> usedTriggersInSenTree;
for (AstSenItem* senItemp = senTreep->sensesp(); senItemp;
senItemp = VN_AS(senItemp->nextp(), SenItem)) {
const size_t idx = m_trigKit.senItem2TrigIdx(senItemp);
if (usedTriggers.find(idx) != usedTriggers.end()) {
usedTrigsToUsingTrees[idx / TriggerKit::WORD_SIZE]
[1 << (idx % TriggerKit::WORD_SIZE)]
.insert(shedp);
}
}
}
return usedTrigsToUsingTrees;
}
// Returns a CCall to a before-trigger function for a given SenTree,
// Constructs such a function if it doesn't exist yet
AstCCall* getBeforeTriggerStmt(AstSenTree* const senTreep) {
FileLine* const flp = senTreep->fileline();
if (!senTreep->user1p()) {
AstCFunc* const funcp = util::makeSubFunction(
m_netlistp, m_beforeTriggerFuncUniqueName.get(senTreep), false);
senTreep->user1p(funcp);
// Create a local temporary extended vector
AstVarScope* const vscAccp = m_trigKit.vscAccp();
AstVarScope* const tmpp = vscAccp->scopep()->createTempLike("__VTmp", vscAccp);
AstVar* const tmpVarp = tmpp->varp()->unlinkFrBack();
funcp->user1p(tmpp);
funcp->addVarsp(tmpVarp);
tmpVarp->funcLocal(true);
tmpVarp->noReset(true);
AstVar* const argp = new AstVar{flp, VVarType::BLOCKTEMP, "__VeventDescription",
senTreep->findBasicDType(VBasicDTypeKwd::CHARPTR)};
argp->funcLocal(true);
argp->direction(VDirection::INPUT);
funcp->addArgsp(argp);
// Scope is created in the constructor after iterate finishes
m_generatedFuncs.emplace_back(funcp, senTreep);
}
AstCCall* const callp = new AstCCall{flp, VN_AS(senTreep->user1p(), CFunc)};
callp->dtypeSetVoid();
return callp;
}
void visit(AstCAwait* const nodep) override {
if (nodep->user1SetOnce()) return;
// Check whether it is a CAwait for a VCMethod::SCHED_TRIGGER
if (const AstCMethodHard* const cMethodHardp = VN_CAST(nodep->exprp(), CMethodHard)) {
if (cMethodHardp->method() == VCMethod::SCHED_TRIGGER) {
AstCCall* const beforeTrigp = getBeforeTriggerStmt(nodep->sentreep());
FileLine* const flp = nodep->fileline();
// Add eventDescription argument value to a CCall - it is used for --runtime-debug
AstNode* const pinp = cMethodHardp->pinsp()->nextp()->nextp();
UASSERT_OBJ(pinp, cMethodHardp, "No event description");
beforeTrigp->addArgsp(VN_AS(pinp, NodeExpr)->cloneTree(false));
// Change CAwait Expression into StmtExpr that calls to a before-trigger function
// first and then return CAwait
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
AstExprStmt* const exprstmtp
= new AstExprStmt{flp, beforeTrigp->makeStmt(), nodep};
exprstmtp->setTimingControl();
relinker.relink(exprstmtp);
m_senTreeToSched.emplace(nodep->sentreep(), cMethodHardp->fromp());
}
}
nodep->clearSentreep(); // Clear as these sentrees will get deleted later
iterate(nodep);
}
void visit(AstNode* const nodep) override { iterateChildren(nodep); }
public:
AwaitBeforeTrigVisitor(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
const TriggerKit& trigKit)
: m_netlistp{netlistp}
, m_trigKit{trigKit}
, m_senExprBuilder{senExprBuilder}
, m_beforeTriggerFuncUniqueName{"__VbeforeTrig"} {
iterate(netlistp);
fillSenExprToSenItem();
std::vector<AstNodeExpr*> trigps;
std::set<size_t> usedTriggers;
// In each of before-trigger functions check if anything was triggered and mark as ready
// triggered schedulers
for (const auto& funcToUsedTriggers : m_generatedFuncs) {
AstCFunc* const funcp = funcToUsedTriggers.first;
AstVarScope* const vscp = VN_AS(funcp->user1p(), VarScope);
FileLine* const flp = funcp->fileline();
// Generate trigger evaluation
{
AstSenTree* const senTreep = funcToUsedTriggers.second;
// Puts `exprp` at `pos` and makes sure that trigps.size() is multiple of
// TriggerKit::WORD_SIZE
const auto emplaceAt
= [flp, &trigps, &usedTriggers](AstNodeExpr* const exprp, const size_t pos) {
const size_t targetSize
= vlstd::roundUpToMultipleOf<TriggerKit::WORD_SIZE>(pos + 1);
if (trigps.capacity() < targetSize) trigps.reserve(targetSize * 2);
while (trigps.size() < targetSize) {
trigps.push_back(new AstConst{flp, AstConst::BitFalse{}});
}
trigps[pos]->deleteTree();
trigps[pos] = exprp;
usedTriggers.insert(pos);
};
// Find all trigger indexes of SenItems inside `senTreep`
// and add them to `trigps` and `usedTriggers`
for (const AstSenItem* itemp = senTreep->sensesp(); itemp;
itemp = VN_AS(itemp->nextp(), SenItem)) {
const size_t idx = m_trigKit.senItem2TrigIdx(itemp);
emplaceAt(m_senExprBuilder.build(itemp).first, idx);
auto iter = m_senExprToSenItem.find(*getSenHashNode(itemp));
if (iter != m_senExprToSenItem.end()) {
for (AstSenItem* const additionalItemp : iter->second) {
const size_t idx = m_trigKit.senItem2TrigIdx(additionalItemp);
emplaceAt(m_senExprBuilder.build(additionalItemp).first, idx);
}
}
}
// Fill the function with neccessary statements
SenExprBuilder::Results results = m_senExprBuilder.getResultsAndClearUpdates();
for (AstNodeStmt* const stmtsp : results.m_inits) funcp->addStmtsp(stmtsp);
for (AstNodeStmt* const stmtsp : results.m_preUpdates) funcp->addStmtsp(stmtsp);
funcp->addStmtsp(TriggerKit::createSenTrigVecAssignment(vscp, trigps));
trigps.clear();
for (AstNodeStmt* const stmtsp : results.m_postUpdates) funcp->addStmtsp(stmtsp);
}
std::map<size_t, std::map<size_t, std::set<AstNodeExpr*>>> usedTrigsToUsingTrees
= getUsedTriggersToTrees(usedTriggers);
usedTriggers.clear();
// Helper returning expression getting array index `idx` from `scocep` with access
// `access`
const auto getIdx = [flp](AstVarScope* const scocep, VAccess access, size_t idx) {
return new AstArraySel{flp, new AstVarRef{flp, scocep, access},
new AstConst{flp, AstConst::Unsized64{}, idx}};
};
// Get eventDescription argument
AstVarScope* const argpVscp = new AstVarScope{flp, funcp->scopep(), funcp->argsp()};
funcp->scopep()->addVarsp(argpVscp);
// Mark as ready triggered schedulers
for (const auto& triggersToTrees : usedTrigsToUsingTrees) {
const size_t word = triggersToTrees.first;
for (const auto& bitsToTrees : triggersToTrees.second) {
const size_t bit = bitsToTrees.first;
const auto& schedulers = bitsToTrees.second;
// Check if given bit is fired - single bits are checked since
// usually there is only a few of them (only one most of the times as we await
// only for one event)
AstConst* const maskConstp = new AstConst{flp, AstConst::Unsized64{}, bit};
AstAnd* const condp
= new AstAnd{flp, getIdx(vscp, VAccess::READ, word), maskConstp};
AstIf* const ifp = new AstIf{flp, condp};
// Call ready() on each scheduler sensitive to `condp`
for (AstNodeExpr* const schedp : schedulers) {
AstCMethodHard* const callp = new AstCMethodHard{
flp, schedp->cloneTree(false), VCMethod::SCHED_READY};
callp->dtypeSetVoid();
callp->addPinsp(new AstVarRef{flp, argpVscp, VAccess::READ});
ifp->addThensp(callp->makeStmt());
}
funcp->addStmtsp(ifp);
}
}
AstVarScope* const vscAccp = m_trigKit.vscAccp();
// Add touched values to accumulator
for (const auto& triggersToTrees : usedTrigsToUsingTrees) {
const size_t word = triggersToTrees.first;
funcp->addStmtsp(new AstAssign{flp, getIdx(vscAccp, VAccess::WRITE, word),
new AstOr{flp, getIdx(vscAccp, VAccess::READ, word),
getIdx(vscp, VAccess::READ, word)}});
}
}
}
~AwaitBeforeTrigVisitor() override = default;
};
void beforeTrigVisitor(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
const TriggerKit& trigKit) {
AwaitBeforeTrigVisitor{netlistp, senExprBuilder, trigKit};
}
} // namespace V3Sched

View File

@ -42,6 +42,7 @@ class VirtIfaceVisitor final : public VNVisitor {
private:
// NODE STATE
// AstVarRef::user1() -> bool. Whether it has been visited
// AstMemberSel::user1() -> bool. Whether it has been visited
const VNUser1InUse m_user1InUse;
// TYPES
@ -64,7 +65,7 @@ private:
AstIfaceRefDType* const dtypep = VN_CAST(refp->varp()->dtypep(), IfaceRefDType);
const bool writesToVirtIfaceMember
= (dtypep && dtypep->isVirtual() && VN_IS(refp->firstAbovep(), MemberSel));
const bool writesToIfaceSensVar = refp->varp()->sensIfacep();
const bool writesToIfaceSensVar = refp->varp()->isVirtIface();
return writesToVirtIfaceMember || writesToIfaceSensVar;
});
}
@ -86,27 +87,21 @@ private:
return new AstVarRef{flp, existingTrigger, VAccess::WRITE};
}
// VISITORS
void visit(AstNodeProcedure* nodep) override {
// Not sure if needed, but be paranoid to match previous behavior as didn't optimize
// before ..
if (VN_IS(nodep, AlwaysPost) && writesToVirtIface(nodep)) {
nodep->foreach([](AstVarRef* refp) { refp->varScopep()->optimizeLifePost(false); });
}
iterateChildren(nodep);
}
void visit(AstVarRef* const nodep) override {
template <typename T>
void handleIface(T nodep) {
static_assert(std::is_same<typename std::remove_cv<T>::type,
typename std::add_pointer<AstVarRef>::type>::value
|| std::is_same<typename std::remove_cv<T>::type,
typename std::add_pointer<AstMemberSel>::type>::value,
"Node has to be of AstVarRef* or AstMemberSel* type");
if (nodep->access().isReadOnly()) return;
if (nodep->user1SetOnce()) return;
AstIface* ifacep = nullptr;
AstVar* memberVarp = nullptr;
if (AstIfaceRefDType* const dtypep = VN_CAST(nodep->varp()->dtypep(), IfaceRefDType)) {
if (dtypep->isVirtual()) {
if (AstMemberSel* const memberSelp = VN_CAST(nodep->firstAbovep(), MemberSel)) {
// Extract the member varp from the MemberSel node
memberVarp = memberSelp->varp();
ifacep = dtypep->ifacep();
}
if (nodep->varp()->isVirtIface()) {
if (AstMemberSel* const memberSelp = VN_CAST(nodep->firstAbovep(), MemberSel)) {
ifacep = VN_AS(nodep->varp()->dtypep(), IfaceRefDType)->ifacep();
memberVarp = memberSelp->varp();
}
} else if ((ifacep = nodep->varp()->sensIfacep())) {
memberVarp = nodep->varp();
@ -123,6 +118,18 @@ private:
nodep});
}
}
// VISITORS
void visit(AstNodeProcedure* nodep) override {
// Not sure if needed, but be paranoid to match previous behavior as didn't optimize
// before ..
if (VN_IS(nodep, AlwaysPost) && writesToVirtIface(nodep)) {
nodep->foreach([](AstVarRef* refp) { refp->varScopep()->optimizeLifePost(false); });
}
iterateChildren(nodep);
}
void visit(AstMemberSel* const nodep) override { handleIface(nodep); }
void visit(AstVarRef* const nodep) override { handleIface(nodep); }
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:

View File

@ -322,6 +322,14 @@ public:
return {resultp, firedAtInitialization};
}
Results getResultsAndClearUpdates() {
m_hasPreUpdate.clear();
m_hasPostUpdate.clear();
Results ans = std::move(m_results);
m_results = {};
return ans;
}
Results getAndClearResults() {
m_curr.clear();
m_prev.clear();

View File

@ -508,6 +508,16 @@ class TaskVisitor final : public VNVisitor {
return assp;
}
void changeAtWriteRecurse(AstNodeExpr* const exprp) {
// Change nested at methods to writable variant
if (AstCMethodHard* const cMethodp = VN_CAST(exprp, CMethodHard)) {
if (cMethodp->method() == VCMethod::ARRAY_AT) {
cMethodp->method(VCMethod::ARRAY_AT_WRITE);
}
changeAtWriteRecurse(cMethodp->fromp());
}
}
void connectPort(AstVar* portp, AstArg* argp, const string& namePrefix, AstNode* beginp,
bool inlineTask) {
AstNodeExpr* const pinp = argp->exprp();
@ -538,10 +548,7 @@ class TaskVisitor final : public VNVisitor {
refArgOk = cMethodp->method() == VCMethod::DYN_AT_WRITE_APPEND
|| cMethodp->method() == VCMethod::DYN_AT_WRITE_APPEND_BACK;
} else {
if (cMethodp->method() == VCMethod::ARRAY_AT) {
// Change the method to writable variant
cMethodp->method(VCMethod::ARRAY_AT_WRITE);
}
changeAtWriteRecurse(cMethodp);
refArgOk = cMethodp->method() == VCMethod::ARRAY_AT_WRITE;
}
}

View File

@ -190,7 +190,6 @@ class TraceVisitor final : public VNVisitor {
VDouble0 m_statSetters; // Statistic tracking
VDouble0 m_statSettersSlow; // Statistic tracking
VDouble0 m_statUniqCodes; // Statistic tracking
VDouble0 m_statUniqSigs; // Statistic tracking
// All activity numbers applying to a given trace
@ -595,19 +594,18 @@ class TraceVisitor final : public VNVisitor {
// no need to create a TraceInc node.
const AstTraceDecl* const canonDeclp = canonVtxp->nodep();
UASSERT_OBJ(!canonVtxp->duplicatep(), canonDeclp, "Canonical node is a duplicate");
UASSERT_OBJ(canonDeclp->code() != 0, canonDeclp,
UASSERT_OBJ(canonDeclp->codeAssigned(), canonDeclp,
"Canonical node should have code assigned already");
declp->code(canonDeclp->code());
continue;
}
// This is a canonical trace node. Assign trace code (signal number).
UASSERT_OBJ(declp->code() == 0, declp,
UASSERT_OBJ(!declp->codeAssigned(), declp,
"Canonical node should not have code assigned yet");
declp->code(m_code);
const uint32_t codeInc = declp->codeInc();
m_code += codeInc;
m_statUniqCodes += codeInc;
++m_statUniqSigs;
// If this is a const signal, add the AstTraceInc
@ -839,9 +837,6 @@ class TraceVisitor final : public VNVisitor {
// VISITORS
void visit(AstNetlist* nodep) override {
m_code = 1; // Multiple TopScopes will require fixing how code#s
// are assigned as duplicate varscopes must result in the same tracing code#.
// Add vertexes for all TraceDecl, and edges from VARs each trace looks at
m_finding = false;
iterateChildren(nodep);
@ -852,6 +847,9 @@ class TraceVisitor final : public VNVisitor {
// Create the trace functions and insert them into the tree
createTraceFunctions();
// Save number of trace codes used
nodep->nTraceCodes(m_code);
}
void visit(AstNodeModule* nodep) override {
if (nodep->isTop()) m_topModp = nodep;
@ -945,7 +943,7 @@ public:
~TraceVisitor() override {
V3Stats::addStat("Tracing, Activity setters", m_statSetters);
V3Stats::addStat("Tracing, Activity slow blocks", m_statSettersSlow);
V3Stats::addStat("Tracing, Unique trace codes", m_statUniqCodes);
V3Stats::addStat("Tracing, Unique trace codes", m_code);
V3Stats::addStat("Tracing, Unique traced signals", m_statUniqSigs);
}
};

View File

@ -51,32 +51,37 @@ public:
, m_emit{emit} {}
// Emit Prefix adjustments until the current path is 'newPath'
void adjust(const string& newPath) {
void adjust(const string& newPath, AstCell* cellp, AstVarScope* vscp) {
// Move up to enclosing path
unsigned toPop = 0;
while (!VString::startsWith(newPath, m_stack.back())) {
++toPop;
m_emit(new AstTracePopPrefix{m_flp});
m_stack.pop_back();
}
while (toPop--) m_emit(new AstTracePopPrefix{m_flp});
if (newPath == m_stack.back()) return;
const VTracePrefixType lastScopeType = //
(cellp && VN_IS(cellp->modp(), Iface))
|| (vscp && VN_IS(vscp->dtypep(), IfaceRefDType))
? VTracePrefixType::SCOPE_INTERFACE
: VTracePrefixType::SCOPE_MODULE;
const std::string extraPrefix = newPath.substr(m_stack.back().size());
size_t begin = 0;
size_t last = extraPrefix.rfind(SEPARATOR);
// Move down, one path element at a time
if (newPath != m_stack.back()) {
const string& extraPrefix = newPath.substr(m_stack.back().size());
size_t begin = 0;
while (true) {
const size_t end = extraPrefix.find(SEPARATOR, begin);
if (end == string::npos) break;
const string& extra = extraPrefix.substr(begin, end - begin);
while (true) {
const size_t end = extraPrefix.find(SEPARATOR, begin);
if (end == string::npos) break;
const string& extra = extraPrefix.substr(begin, end - begin);
if (end == last) {
m_emit(new AstTracePushPrefix{m_flp, extra, lastScopeType});
} else {
m_emit(new AstTracePushPrefix{m_flp, extra, VTracePrefixType::SCOPE_MODULE});
m_stack.push_back(m_stack.back() + extra + SEPARATOR);
begin = end + 1;
}
const string& extra = extraPrefix.substr(begin);
if (!extra.empty()) {
m_emit(new AstTracePushPrefix{m_flp, extra, VTracePrefixType::SCOPE_MODULE});
m_stack.push_back(m_stack.back() + extra);
}
m_stack.push_back(m_stack.back() + extra + SEPARATOR);
begin = end + 1;
}
UASSERT(begin == extraPrefix.size(), "Should have consumed all of extraPrefix");
}
// Emit Prefix adjustments to unwind the path back to its original state
@ -89,8 +94,6 @@ public:
// TraceDecl state, as a visitor of each AstNode
class TraceDeclVisitor final : public VNVisitor {
// NODE STATE
// STATE
AstTopScope* const m_topScopep; // The singleton AstTopScope
const AstScope* m_currScopep = nullptr; // Current scope being visited
@ -124,29 +127,55 @@ class TraceDeclVisitor final : public VNVisitor {
AstCell* m_cellp = nullptr; // Sub scope (as AstCell) under scope being traced
std::string m_path; // Path to enclosing module in original hierarchy
std::string m_name; // Name of signal/subscope
bool m_rootio = false; // Is part of $rootio, if model at runtime uses name()=""
void init(const std::string& name) {
void init(const std::string& name, AstNode* nodep, bool inTopScope) {
// Compute path in hierarchy and item name
const std::string& vcdName = AstNode::vcdName(name);
const size_t pos = vcdName.rfind(' ');
const size_t pathLen = pos == std::string::npos ? 0 : pos + 1;
m_path = vcdName.substr(0, pathLen);
m_name = vcdName.substr(pathLen);
AstVar* const varp = VN_CAST(nodep, Var);
if (VN_IS(nodep, Cell) || VN_IS(varp->dtypep(), IfaceRefDType)) {
// Cell or interface reference
m_path = vcdName + " ";
m_name.clear();
} else if (varp->isPrimaryIO()) {
// Primary IO variable
m_path = "$rootio ";
m_name = vcdName;
} else {
// Other Variable
const size_t pos = vcdName.rfind(' ');
const size_t pathLen = pos == std::string::npos ? 0 : pos + 1;
m_path = vcdName.substr(0, pathLen);
m_name = vcdName.substr(pathLen);
}
// When creating a --lib-create library, drop the name of the top module (l2 name).
// This will be replaced by the instance name in the model that uses the library.
// This would be a bit murky when there are other top level entities ($unit,
// packages, which have an instance in all libs - a problem on its own). If
// --top-module was explicitly specified, then we will drop the prefix only for the
// actual top level module, and wrap the rest in '$libroot'. This way at least we get a
// usable dump of everything, with library instances showing in a right place, without
// pollution from other top level entities.
if (inTopScope && !v3Global.opt.libCreate().empty()) {
const size_t start = m_path.find(' ');
// Must have a prefix in the top scope with lib, as top wrapper signals not traced
UASSERT_OBJ(start != std::string::npos, nodep, "No prefix with --lib-create");
const std::string prefix = m_path.substr(0, start);
m_path = m_path.substr(start + 1);
if (v3Global.opt.topModule() != prefix) m_path = "$libroot " + m_path;
}
}
public:
explicit TraceEntry(AstVarScope* vscp)
explicit TraceEntry(const AstScope* scopep, AstVarScope* vscp)
: m_vscp{vscp} {
init(vscp->varp()->name());
init(vscp->varp()->name(), vscp->varp(), scopep->isTop());
}
explicit TraceEntry(AstCell* cellp)
explicit TraceEntry(const AstScope* scopep, AstCell* cellp)
: m_cellp{cellp} {
init(cellp->name());
init(cellp->name(), cellp, scopep->isTop());
}
int operatorCompare(const TraceEntry& b) const {
if (rootio() && !b.rootio()) return true;
if (!rootio() && b.rootio()) return false;
if (const int cmp = path().compare(b.path())) return cmp < 0;
if (const int cmp = fileline().operatorCompare(b.fileline())) return cmp < 0;
return name() < b.name();
@ -157,8 +186,6 @@ class TraceDeclVisitor final : public VNVisitor {
void path(const std::string& path) { m_path = path; }
const std::string& name() const { return m_name; }
FileLine& fileline() const { return m_vscp ? *m_vscp->fileline() : *m_cellp->fileline(); }
bool rootio() const { return m_rootio; }
void rootio(bool flag) { m_rootio = flag; }
};
std::vector<TraceEntry> m_entries; // Trace entries under current scope
AstVarScope* m_traVscp = nullptr; // Current AstVarScope we are constructing AstTraceDecls for
@ -267,43 +294,38 @@ class TraceDeclVisitor final : public VNVisitor {
const AstScope* const scopep = it->second;
FileLine* const flp = placeholderp->fileline();
// Pick up the last path element. The prefixes have already been pushed
// when building the initialization.
// We still need to find __DOT__ as cell names may have such.
const std::string dot = "__DOT__";
const size_t pos = path.rfind(dot);
const std::string name = path.substr(pos == string::npos ? 0 : pos + dot.size());
// Compute the type of the scope being fixed up
const AstCell* const cellp = scopep->aboveCellp();
const VTracePrefixType scopeType
= cellp ? (VN_IS((cellp->modp()), Iface) ? VTracePrefixType::SCOPE_INTERFACE
: VTracePrefixType::SCOPE_MODULE)
: VTracePrefixType::SCOPE_MODULE;
// Push the scope prefix
AstNodeStmt* const pushp
= new AstTracePushPrefix{flp, AstNode::prettyName(name), scopeType};
// Call the initialization functions for the scope
AstNode* stmtp = nullptr;
for (AstCFunc* const subFuncp : m_scopeInitFuncps.at(scopep)) {
AstCCall* const callp = new AstCCall{flp, subFuncp};
callp->dtypeSetVoid();
callp->argTypes("tracep");
pushp->addNext(callp->makeStmt());
stmtp = AstNode::addNext(stmtp, callp->makeStmt());
}
// Pop the scope prefix
pushp->addNext(new AstTracePopPrefix{flp});
// Add after the placeholder
placeholderp->addNextHere(pushp);
if (stmtp) placeholderp->addNextHere(stmtp);
}
// Delete the placeholder
placeholderp->unlinkFrBack();
VL_DO_DANGLING(placeholderp->deleteTree(), placeholderp);
}
void fixupLibStub(const std::string& path, AstNodeStmt* placeholderp) {
FileLine* const flp = placeholderp->fileline();
// Call the initialization function for the library instance
AstCStmt* const initp = new AstCStmt{flp};
initp->add("tracep->initLib(vlSymsp->name() + ");
initp->add(new AstConst{flp, AstConst::String{}, "." + AstNode::prettyName(path)});
initp->add(");\n");
placeholderp->addNextHere(initp);
// Delete the placeholder
VL_DO_DANGLING(placeholderp->unlinkFrBack()->deleteTree(), placeholderp);
return;
}
void fixupPlaceholders() {
// Fix up cell initialization placehodlers
UINFO(9, "fixupPlaceholders()");
@ -312,7 +334,11 @@ class TraceDeclVisitor final : public VNVisitor {
const AstCell* const cellp = std::get<1>(item);
AstNodeStmt* const placeholderp = std::get<2>(item);
const std::string path = parentp->name() + "__DOT__" + cellp->name();
fixupPlaceholder(path, placeholderp);
if (cellp->modp()->verilatorLib()) {
fixupLibStub(path, placeholderp);
} else {
fixupPlaceholder(path, placeholderp);
}
}
// Fix up interface reference initialization placeholders
@ -372,6 +398,9 @@ class TraceDeclVisitor final : public VNVisitor {
UASSERT_OBJ(!m_traValuep, nodep, "Should not nest");
UASSERT_OBJ(m_traName.empty(), nodep, "Should not nest");
// If this is a stub for a --lib-create library, skip.
if (nodep->modp()->verilatorLib()) return;
VL_RESTORER(m_currScopep);
m_currScopep = nodep;
@ -380,20 +409,12 @@ class TraceDeclVisitor final : public VNVisitor {
// Gather cells under this scope
for (AstNode* stmtp = nodep->modp()->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstCell* const cellp = VN_CAST(stmtp, Cell)) m_entries.emplace_back(cellp);
if (AstCell* const cellp = VN_CAST(stmtp, Cell)) m_entries.emplace_back(nodep, cellp);
}
if (!m_entries.empty()) {
if (nodep->name() == "TOP") {
UINFO(9, " Add $rootio " << nodep);
for (TraceEntry& entry : m_entries) {
if (entry.path() == "" && entry.vscp()) entry.rootio(true);
}
}
// Sort trace entries, first by if a $root io, then by enclosing instance
// (necessary for single traversal of hierarchy during initialization), then
// by source location, then by name.
// Sort trace entries, by enclosing instance (necessary for single traversal of
// hierarchy during initialization), then by source location, then by name.
std::stable_sort(
m_entries.begin(), m_entries.end(),
[](const TraceEntry& a, const TraceEntry& b) { return a.operatorCompare(b); });
@ -406,7 +427,7 @@ class TraceDeclVisitor final : public VNVisitor {
UINFO(9, "path='" << entry.path() << "' name='" << entry.name() << "' "
<< (entry.cellp() ? static_cast<AstNode*>(entry.cellp())
: static_cast<AstNode*>(entry.vscp())));
pathAdjustor.adjust(entry.rootio() ? "$rootio" : entry.path());
pathAdjustor.adjust(entry.path(), entry.cellp(), entry.vscp());
m_traName = entry.name();
@ -499,8 +520,16 @@ class TraceDeclVisitor final : public VNVisitor {
if (nodep->varp()->isClassMember()) return;
if (nodep->varp()->isFuncLocal()) return;
// When creating a --lib-create library ...
if (!v3Global.opt.libCreate().empty()) {
// Ignore the wrapper created primary IO ports
if (nodep->varp()->isPrimaryIO()) return;
// Ignore parameters in packages. These will be traced at the top level.
if (nodep->varp()->isParam() && VN_IS(nodep->scopep()->modp(), Package)) return;
}
// Add to traced signal list
m_entries.emplace_back(nodep);
m_entries.emplace_back(m_currScopep, nodep);
}
// VISITORS - Data types when tracing

View File

@ -1645,7 +1645,8 @@ class TristateVisitor final : public TristateBaseVisitor {
AstPin* const enpinp
= new AstPin{nodep->fileline(), nodep->pinNum(),
enModVarp->name(), // should be {var}"__en"
new AstVarRef{nodep->fileline(), enVarp, VAccess::WRITE}};
new AstVarRef{nodep->fileline(), enVarp,
inDeclProcessing ? VAccess::READ : VAccess::WRITE}};
enpinp->modVarp(enModVarp);
UINFO(9, " newpin " << enpinp);
enpinp->user2Or(U2_BOTH); // don't iterate the pin later

View File

@ -51,6 +51,7 @@ class UndrivenVarEntry final {
const AstNode* m_procWritep = nullptr; // varref if written in process
const FileLine* m_nodeFileLinep = nullptr; // File line of varref if driven, else nullptr
bool m_underGen = false; // Under a generate
bool m_ftaskDriven = false; // Last driven by function or task
const AstNodeFTaskRef* m_callNodep = nullptr; // Call node if driven via writeSummary
@ -124,7 +125,8 @@ public:
UINFO(9, "set d[*] " << m_varp->name());
m_wholeFlags[FLAG_DRIVEN] = true;
}
void drivenWhole(const AstNodeVarRef* nodep, const FileLine* fileLinep) {
void drivenWhole(const AstNodeVarRef* nodep, const FileLine* fileLinep, bool ftaskDef) {
m_ftaskDriven = ftaskDef && !isDrivenWhole();
drivenWhole();
m_nodep = nodep;
m_nodeFileLinep = fileLinep;
@ -143,6 +145,7 @@ public:
bool isUnderGen() const { return m_underGen; }
bool isDrivenWhole() const { return m_wholeFlags[FLAG_DRIVEN]; }
bool isDrivenAlwaysCombWhole() const { return m_wholeFlags[FLAG_DRIVEN_ALWCOMB]; }
bool isFtaskDriven() const { return m_ftaskDriven; }
const AstNodeVarRef* getNodep() const { return m_nodep; }
const FileLine* getNodeFileLinep() const { return m_nodeFileLinep; }
const AstAlways* getAlwCombp() const { return m_alwCombp; }
@ -207,6 +210,8 @@ public:
true); // Warn only once
}
} else { // Signal
const string varType{nodep->isFuncLocal() ? "Function variable" : "Signal"};
bool funcInout = nodep->isFuncLocal() && nodep->isInout();
bool allU = true;
bool allD = true;
bool anyU = m_wholeFlags[FLAG_USED];
@ -225,6 +230,10 @@ public:
anyDnotU |= !used && driv;
anynotDU |= !used && !driv;
}
if (funcInout) {
if (anyD) allU = true;
allD = true;
}
if (allU) m_wholeFlags[FLAG_USED] = true;
if (allD) m_wholeFlags[FLAG_DRIVEN] = true;
// Test results
@ -239,37 +248,45 @@ public:
// thus undriven+unused bits get UNUSED warnings, as they're not as buggy.
if (!unusedMatch(nodep)) {
nodep->v3warn(UNUSEDSIGNAL,
"Signal is not driven, nor used: " << nodep->prettyNameQ());
varType << " is not driven, nor used: " << nodep->prettyNameQ());
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL,
true); // Warn only once
}
} else if (allD && !anyU) {
if (!unusedMatch(nodep)) {
nodep->v3warn(UNUSEDSIGNAL, "Signal is not used: " << nodep->prettyNameQ());
nodep->v3warn(UNUSEDSIGNAL,
varType << " is not used: " << nodep->prettyNameQ());
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL,
true); // Warn only once
}
} else if (!anyD && allU) {
nodep->v3warn(UNDRIVEN, "Signal is not driven: " << nodep->prettyNameQ());
nodep->v3warn(UNDRIVEN, varType << " is not driven: " << nodep->prettyNameQ());
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNDRIVEN, true); // Warn only once
} else {
} else if (!funcInout) {
// Bits have different dispositions
const std::string varTypeLower = [&varType]() {
std::string str = varType;
str[0] = std::tolower(static_cast<unsigned char>(str[0]));
return str;
}();
bool setU = false;
bool setD = false;
if (anynotDU && !unusedMatch(nodep)) {
nodep->v3warn(UNUSEDSIGNAL, "Bits of signal are not driven, nor used: "
<< nodep->prettyNameQ() << bitNames(BN_BOTH));
nodep->v3warn(UNUSEDSIGNAL,
"Bits of " << varTypeLower << " are not driven, nor used: "
<< nodep->prettyNameQ() << bitNames(BN_BOTH));
setU = true;
}
if (anyDnotU && !unusedMatch(nodep)) {
nodep->v3warn(UNUSEDSIGNAL,
"Bits of signal are not used: " << nodep->prettyNameQ()
<< bitNames(BN_UNUSED));
nodep->v3warn(UNUSEDSIGNAL, "Bits of " << varTypeLower << " are not used: "
<< nodep->prettyNameQ()
<< bitNames(BN_UNUSED));
setU = true;
}
if (anyUnotD) {
nodep->v3warn(UNDRIVEN, "Bits of signal are not driven: "
<< nodep->prettyNameQ() << bitNames(BN_UNDRIVEN));
nodep->v3warn(UNDRIVEN, "Bits of " << varTypeLower << " are not driven: "
<< nodep->prettyNameQ()
<< bitNames(BN_UNDRIVEN));
setD = true;
}
if (setU) { // Warn only once
@ -354,18 +371,20 @@ class UndrivenVisitor final : public VNVisitorConst {
// VISITORS
void visit(AstVar* nodep) override {
const bool funcInout = nodep->isFuncLocal() && nodep->isInout();
for (int usr = 1; usr < (m_alwaysCombp ? 3 : 2); ++usr) {
// For assigns and non-combo always, do just usr==1, to look
// for module-wide undriven etc.
// For combo always, run both usr==1 for above, and also
// usr==2 for always-only checks.
UndrivenVarEntry* const entryp = getEntryp(nodep, usr);
if (nodep->isNonOutput() || nodep->isSigPublic() || nodep->isSigUserRWPublic()
if ((nodep->isNonOutput() && !funcInout) || nodep->isSigPublic()
|| nodep->isSigUserRWPublic()
|| (m_taskp && (m_taskp->dpiImport() || m_taskp->dpiExport()))) {
entryp->drivenWhole();
}
if (nodep->isWritable() || nodep->isSigPublic() || nodep->isSigUserRWPublic()
|| nodep->isSigUserRdPublic()
if ((nodep->isWritable() && !funcInout) || nodep->isSigPublic()
|| nodep->isSigUserRWPublic() || nodep->isSigUserRdPublic()
|| (m_taskp && (m_taskp->dpiImport() || m_taskp->dpiExport()))) {
entryp->usedWhole();
}
@ -433,12 +452,12 @@ class UndrivenVisitor final : public VNVisitorConst {
}
// If writeSummary is enabled, task/function definitions are treated as non-executed.
// Their effects are applied at call sites via writeSummary(), so don't let definition
// traversal create phantom "other writes" for MULTIDRIVEN.
// Remember that anything driven here doesn't count toward MULTIDRIVEN.
bool ftaskDef = false;
if (m_taskp && !m_alwaysp && !m_inContAssign && !m_inInitialStatic && !m_inBBox
&& !m_taskp->dpiExport()) {
AstVar* const retVarp = VN_CAST(m_taskp->fvarp(), Var);
if (!retVarp || nodep->varp() != retVarp) return;
if (!retVarp || nodep->varp() != retVarp) ftaskDef = true;
}
for (int usr = 1; usr < (m_alwaysCombp ? 3 : 2); ++usr) {
@ -453,7 +472,8 @@ class UndrivenVisitor final : public VNVisitorConst {
if (entryp->isDrivenWhole() && !m_inBBox && !VN_IS(nodep, VarXRef)
&& !VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)
&& nodep->fileline() != entryp->getNodeFileLinep() && !entryp->isUnderGen()
&& (entryp->getNodep() || entryp->callNodep())) {
&& (entryp->getNodep() || entryp->callNodep()) && !entryp->isFtaskDriven()
&& !ftaskDef) {
const AstNode* const otherWritep
= entryp->getNodep() ? static_cast<const AstNode*>(entryp->getNodep())
@ -483,7 +503,7 @@ class UndrivenVisitor final : public VNVisitorConst {
<< otherWritep->warnContextSecondary());
}
}
entryp->drivenWhole(nodep, nodep->fileline());
entryp->drivenWhole(nodep, nodep->fileline(), ftaskDef);
if (m_alwaysCombp && entryp->isDrivenAlwaysCombWhole()
&& m_alwaysCombp != entryp->getAlwCombp()
&& m_alwaysCombp->fileline() == entryp->getAlwCombFileLinep())

View File

@ -5495,6 +5495,16 @@ class WidthVisitor final : public VNVisitor {
}
}
bool firstNewStatementOkRecurse(AstNode* nodep) {
if (AstVar* const varp = VN_CAST(nodep, Var)) {
if (!varp->valuep() || VN_CAST(varp->valuep(), Const) || varp->isIO()) return true;
}
if (AstAssign* const aitemp = VN_CAST(nodep, Assign)) {
if (VN_IS(aitemp->rhsp(), Const) || VN_IS(aitemp->rhsp(), CReset)) return true;
}
return false;
}
//--------------------
// Top levels
@ -6510,13 +6520,7 @@ class WidthVisitor final : public VNVisitor {
}
continue;
}
if (AstVar* const varp = VN_CAST(itemp, Var)) {
if (!varp->valuep() || VN_CAST(varp->valuep(), Const) || varp->isIO())
continue;
}
if (AstAssign* const aitemp = VN_CAST(itemp, Assign)) {
if (VN_IS(aitemp->rhsp(), Const)) continue;
}
if (firstNewStatementOkRecurse(itemp)) continue;
firstp = itemp;
}
}

View File

@ -161,6 +161,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
"timing_on" { FL; return yVLT_TIMING_ON; }
"tracing_off" { FL; return yVLT_TRACING_OFF; }
"tracing_on" { FL; return yVLT_TRACING_ON; }
"verilator_lib" { FL; return yVLT_VERILATOR_LIB; }
-?"-block" { FL; return yVLT_D_BLOCK; }
-?"-contents" { FL; return yVLT_D_CONTENTS; }

View File

@ -271,6 +271,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yVLT_TIMING_ON "timing_on"
%token<fl> yVLT_TRACING_OFF "tracing_off"
%token<fl> yVLT_TRACING_ON "tracing_on"
%token<fl> yVLT_VERILATOR_LIB "verilator_lib"
%token<fl> yVLT_D_BLOCK "--block"
%token<fl> yVLT_D_CONTENTS "--contents"
@ -8199,6 +8200,8 @@ vltItem:
{ V3Control::addProfileData($<fl>1, *$2, $3->toUQuad()); }
| yVLT_PROFILE_DATA vltDModel vltDMtask vltDCost
{ V3Control::addProfileData($<fl>1, *$2, *$3, $4->toUQuad()); }
| yVLT_VERILATOR_LIB vltDModule
{ V3Control::addModulePragma(*$2, VPragmaType::VERILATOR_LIB); }
;
vltOffFront<errcodeen>:

View File

@ -2492,7 +2492,7 @@ class VlTest:
print("%Warning: HARNESS_UPDATE_GOLDEN set: cp " + fn1 + " " + fn2, file=sys.stderr)
shutil.copy(fn1, fn2)
def vcd_identical(self, fn1: str, fn2: str) -> None:
def vcd_identical(self, fn1: str, fn2: str, ignore_attr: bool = False) -> None:
"""Test if two VCD files have logically-identical contents"""
# vcddiff to check transitions, if installed
cmd = "vcddiff --help"
@ -2511,6 +2511,9 @@ class VlTest:
# Also provides backup if vcddiff not installed
h1 = self._vcd_read(fn1)
h2 = self._vcd_read(fn2)
if ignore_attr:
h1 = {k: v for k, v in h1.items() if "$attr" not in v}
h2 = {k: v for k, v in h2.items() if "$attr" not in v}
a = json.dumps(h1, sort_keys=True, indent=1)
b = json.dumps(h2, sort_keys=True, indent=1)
if a != b:
@ -2530,11 +2533,17 @@ class VlTest:
out = VtOs.run_capture(cmd, check=False)
print(out)
def fst_identical(self, fn1: str, fn2: str) -> None:
def fst_identical(self, fn1: str, fn2: str, ignore_attr: bool = False) -> None:
"""Test if two FST files have logically-identical contents"""
tmp = fn1 + ".vcd"
self.fst2vcd(fn1, tmp)
self.vcd_identical(tmp, fn2)
if fn1.endswith(".fst"):
tmp = fn1 + ".vcd"
self.fst2vcd(fn1, tmp)
fn1 = tmp
if fn2.endswith(".fst"):
tmp = fn2 + ".vcd"
self.fst2vcd(fn2, tmp)
fn2 = tmp
self.vcd_identical(fn1, fn2, ignore_attr)
def saif_identical(self, fn1: str, fn2: str) -> None:
"""Test if two SAIF files have logically-identical contents"""
@ -2545,7 +2554,7 @@ class VlTest:
if out != '':
print(out)
self.copy_if_golden(fn1, fn2)
self.error("SAIF files don't match!")
self.error("SAIF files miscompare")
def _vcd_read(self, filename: str) -> dict:
data = {}

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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
import vltest_bootstrap
test.scenarios('vlt_all')
test.top_filename = "t/t_altera_lpm.v"
module = re.sub(r'.*t_altera_', '', test.name)
module = re.sub(r'_noinl', '', module)
test.compile(verilator_flags2=["--top-module", module, "-fno-inline"])
test.passes()

View File

@ -12,8 +12,8 @@
module t;
initial begin
int res[];
int a[3] = '{100, 200, 300};
automatic int res[];
automatic int a[3] = '{100, 200, 300};
// TODO results not known to be correct
res = a.map(el) with (el == 200);

0
test_regress/t/t_assert_ctl_type_bad.v Executable file → Normal file
View File

0
test_regress/t/t_assert_ctl_unsup.v Executable file → Normal file
View File

View File

@ -9,8 +9,8 @@
import vltest_bootstrap
test.scenarios('simulator')
test.scenarios('simulator_st')
test.compile(expect_filename=test.golden_filename)
test.compile(verilator_flags2=['--no-skip-identical'], expect_filename=test.golden_filename)
test.passes()

View File

@ -51,9 +51,9 @@ module t;
endtask
initial begin
Foo foo = new(iface);
Foo foo2 = new(iface2);
Bar bar = new(foo);
automatic Foo foo = new(iface);
automatic Foo foo2 = new(iface2);
automatic Bar bar = new(foo);
clockSome();
if (iface.x != 0) $stop;
if (iface2.x != 0) $stop;

View File

@ -55,9 +55,9 @@ module t;
endtask
initial begin
Foo foo = new(iface);
Foo foo2 = new(iface2);
Bar bar = new(foo);
automatic Foo foo = new(iface);
automatic Foo foo2 = new(iface2);
automatic Bar bar = new(foo);
clockSome();
if (iface.x[0] != 0) $stop;
if (iface.x[1] != 0) $stop;

View File

@ -15,56 +15,56 @@
`endif
class nba_waiter;
// Task taken from UVM
task wait_for_nba_region;
static int nba;
int next_nba;
next_nba++;
nba <= `DELAY next_nba;
@(nba);
endtask
// Task taken from UVM
task wait_for_nba_region;
static int nba;
int next_nba;
next_nba++;
nba <= `DELAY next_nba;
@(nba);
endtask
endclass
class Foo;
task bar(logic a, logic b);
static int x;
static int y;
// bar's local vars and intravals could be overwritten by other locals
if (a) x <= `DELAY 'hDEAD;
if (b) y <= `DELAY 'hBEEF;
#2
if (x != 'hDEAD) $stop;
endtask
task bar(logic a, logic b);
static int x;
static int y;
// bar's local vars and intravals could be overwritten by other locals
if (a) x <= `DELAY 'hDEAD;
if (b) y <= `DELAY 'hBEEF;
#2;
if (x != 'hDEAD) $stop;
endtask
endclass
module t;
nba_waiter waiter = new;
Foo foo = new;
event e;
int cnt = 0;
nba_waiter waiter = new;
Foo foo = new;
event e;
int cnt = 0;
initial begin
#1 ->e;
if (cnt != 0) $stop;
cnt++;
waiter.wait_for_nba_region;
->e;
if (cnt != 2) $stop;
if ($time != `TIME_AFTER_FIRST_WAIT) $stop;
cnt++;
waiter.wait_for_nba_region;
if (cnt != 4) $stop;
if ($time != `TIME_AFTER_SECOND_WAIT) $stop;
foo.bar(1, 1);
#2
$write("*-* All Finished *-*\n");
$finish;
end
initial begin
#1 ->e;
if (cnt != 0) $stop;
cnt++;
waiter.wait_for_nba_region;
->e;
if (cnt != 2) $stop;
if ($time != `TIME_AFTER_FIRST_WAIT) $stop;
cnt++;
waiter.wait_for_nba_region;
if (cnt != 4) $stop;
if ($time != `TIME_AFTER_SECOND_WAIT) $stop;
foo.bar(1, 1);
#2;
$write("*-* All Finished *-*\n");
$finish;
end
initial begin
@e if (cnt != 1) $stop;
cnt++;
@e if (cnt != 3) $stop;
cnt++;
end
initial begin
@e if (cnt != 1) $stop;
cnt++;
@e if (cnt != 3) $stop;
cnt++;
end
endmodule

View File

@ -8,6 +8,8 @@ class Cls;
task bar;
static int qux;
qux <= '1;
// Use qux to prevent V3Dead optimizations
$display("qux = %d\n", qux);
endtask
endclass

View File

@ -39,8 +39,8 @@ module t;
begin // check that a class as key is fine
int assoc1[Cls];
int assoc2[Cls];
Cls a = new;
Cls b = new;
automatic Cls a = new;
automatic Cls b = new;
int t;
assoc1[a] = 0;
`check_ne(assoc1, assoc2)
@ -53,8 +53,8 @@ module t;
begin // check that a class as value is fine
Cls assoc1[int];
Cls assoc2[int];
Cls a = new;
Cls b = new;
automatic Cls a = new;
automatic Cls b = new;
assoc1[1] = a;
assoc2[1] = b;
`check_ne(assoc1, assoc2)

View File

@ -36,7 +36,7 @@ endclass
module t;
initial begin
X x = new;
automatic X x = new;
$finish;
end

View File

@ -10,8 +10,8 @@
module t;
initial begin
int res[];
int a[int] = '{1: 100, 2: 200, 3: 300};
automatic int res[];
automatic int a[int] = '{1: 100, 2: 200, 3: 300};
// TODO results not known to be correct
res = a.map(el) with (el == 2);

View File

@ -1,10 +1,10 @@
%Error: t/t_assoc_nokey_bad.v:12:28: Missing pattern key (need an expression then a ':')
%Error: t/t_assoc_nokey_bad.v:12:36: Missing pattern key (need an expression then a ':')
: ... note: In instance 't'
12 | int dict[string] = '{1, 2};
| ^
12 | automatic int dict[string] = '{1, 2};
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_assoc_nokey_bad.v:12:31: Missing pattern key (need an expression then a ':')
%Error: t/t_assoc_nokey_bad.v:12:39: Missing pattern key (need an expression then a ':')
: ... note: In instance 't'
12 | int dict[string] = '{1, 2};
| ^
12 | automatic int dict[string] = '{1, 2};
| ^
%Error: Exiting due to

View File

@ -8,13 +8,13 @@
module t;
initial begin
int dict[string] = '{1, 2};
int dict2[string] = '{3: 4}; // Legal due to value-to-string conversion
$display("dict=%p", dict);
$display("dict2=%p", dict2);
$write("*-* All Finished *-*\n");
$finish;
end
initial begin
automatic int dict[string] = '{1, 2};
automatic int dict2[string] = '{3: 4}; // Legal due to value-to-string conversion
$display("dict=%p", dict);
$display("dict2=%p", dict2);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -66,13 +66,13 @@ module t;
localparam string str_key = "the_key";
initial begin
Bar bar_i = new;
Baz baz_1_i = new;
Baz #(Foo2) baz_2_i = new;
Bum bum_i;
automatic Bar bar_i = new;
automatic Baz baz_1_i = new;
automatic Baz #(Foo2) baz_2_i = new;
automatic Bum bum_i;
Wrapper#(wrap_map_t) wrap_map = new();
Wrapper#(wrap_queue_t) wrap_queue = new();
automatic Wrapper#(wrap_map_t) wrap_map = new();
automatic Wrapper#(wrap_queue_t) wrap_queue = new();
bar_i.set(1);
baz_1_i.set(2);

View File

@ -12,8 +12,8 @@
module t;
initial begin
int res[];
int a [*] = '{1: 100, 2: 200, 3: 300};
automatic int res[];
automatic int a [*] = '{1: 100, 2: 200, 3: 300};
// TODO results not known to be correct
res = a.map(el) with (el == 2);

View File

@ -18,7 +18,7 @@ module t;
initial begin
int q[*];
int qe [ * ]; // Empty - Note spaces around [*] for parsing coverage
point points_q[*] = '{"a": point'{1, 2}, "b": point'{2, 4}, "c": point'{1, 4}};
automatic point points_q[*] = '{"a": point'{1, 2}, "b": point'{2, 4}, "c": point'{1, 4}};
int qv[$]; // Value returns
int qi[$]; // Index returns
int i;

View File

@ -25,7 +25,7 @@ endclass
module t;
Cls c;
initial begin
bit called = 0;
bit called;
c = new;
case (c.get())
4: $stop;

View File

@ -25,7 +25,7 @@ endclass
module t;
Cls c;
initial begin
bit called = 0;
bit called;
c = new;
case (c.get()) inside
[0:5]: $stop;

View File

@ -18,9 +18,9 @@ typedef enum {
module t;
initial begin
bit array[] = new [8];
int unsigned m_length;
uvm_tlm_command_e m_command;
automatic bit array[] = new [8];
automatic int unsigned m_length;
automatic uvm_tlm_command_e m_command;
m_length = 2;
array = '{0, 0, 0, 0, 0, 0, 1, 0};

View File

@ -33,10 +33,11 @@ module t;
typedef ExtendCls ExtendCls_t;
initial begin
Cls cls1 = null, cls2 = null;
ExtendCls_t ext_cls = null;
AnotherExtendCls an_ext_cls = null;
ExtendExtendCls ext_ext_cls = null;
automatic Cls cls1 = null;
automatic Cls cls2 = null;
automatic ExtendCls_t ext_cls = null;
automatic AnotherExtendCls an_ext_cls = null;
automatic ExtendExtendCls ext_ext_cls = null;
int r;
cls1 = (cls1 == null) ? cls2 : cls1;

View File

@ -107,7 +107,7 @@ module t;
endclass
initial begin
Cls c = new();
automatic Cls c = new();
$finish;
end
endmodule

View File

@ -22,12 +22,12 @@ endclass
module t;
initial begin
Cls a = new;
Cls b = new;
ExtendCls ext = new;
Cls::InnerCls ia = new;
Cls::InnerCls ib = new;
ExtendCls::InnerCls iext = new;
automatic Cls a = new;
automatic Cls b = new;
automatic ExtendCls ext = new;
automatic Cls::InnerCls ia = new;
automatic Cls::InnerCls ib = new;
automatic ExtendCls::InnerCls iext = new;
`check_ne(a, b)
`check_ne(a, ext)
`check_ne(ext, a)

View File

@ -11,7 +11,7 @@ endclass
module t;
initial begin
Cls c = new;
automatic Cls c = new;
if (c.aconst !== 10) $stop;
if (Cls::astatic !== 20) $stop;
$write("*-* All Finished *-*\n");

View File

@ -49,7 +49,7 @@ module t;
import p::*;
initial begin
comp_proxy cp = new;
automatic comp_proxy cp = new;
void'(cp.get_config_object("x"));
$finish;
end

View File

@ -16,9 +16,9 @@ endclass
module t;
initial begin
int dict[Cls];
Cls c1 = new(1);
Cls c2 = new(2);
automatic int dict[Cls];
automatic Cls c1 = new(1);
automatic Cls c2 = new(2);
dict[c1] = 1;
dict[c2] = 2;
`checkh(dict[c1], 1);

View File

@ -23,9 +23,9 @@ endclass
module t;
initial begin
Derived d = new("Hello");
Base b = d;
Derived c = b.cast();
automatic Derived d = new("Hello");
automatic Base b = d;
automatic Derived c = b.cast();
if (d.get() != c.get()) $stop;
$write("*-* All Finished *-*\n");
$finish;

View File

@ -87,8 +87,8 @@ endtask
module t;
initial begin
Cls c = new;
Cls::SubCls subc = new;
automatic Cls c = new;
automatic Cls::SubCls subc = new;
c.ext_t_i(2);
if (c.ext_f_np() != 1) $stop;
if (c.ext_f_p() != 2) $stop;

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