diff --git a/.gitignore b/.gitignore
index 0e6c415b31..17ab4db28c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -121,6 +121,7 @@ __pycache__
/tools/bmp2rb
/tools/codepages
/tools/rdf2binary
+/tools/reggen
/tools/mkboot
/tools/mk500boot
/tools/uclpack
diff --git a/tools/reggen_src/LICENSE-GPLv3.txt b/tools/reggen_src/LICENSE-GPLv3.txt
new file mode 100644
index 0000000000..f288702d2f
--- /dev/null
+++ b/tools/reggen_src/LICENSE-GPLv3.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ 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.
+
+
+ Copyright (C)
+
+ 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 .
+
+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:
+
+ Copyright (C)
+ 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
+.
+
+ 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
+.
diff --git a/tools/reggen_src/README.md b/tools/reggen_src/README.md
new file mode 100644
index 0000000000..b66b3e6eae
--- /dev/null
+++ b/tools/reggen_src/README.md
@@ -0,0 +1,556 @@
+# RegGen: register definition generator
+
+RegGen is a utility that generates C headers files for accessing
+memory-mapped hardware registers in SoCs and microcontrollers.
+
+RegGen uses a custom description format designed to be easy to
+read & maintain by hand. It includes various features aimed at
+reducing unnecessary repetition in register descriptions.
+
+The output of RegGen is comparable to ARM's CMSIS headers, but
+unlike CMSIS it does not include any C code, only preprocessor
+definitions. This allows RegGen output to be used in assembly,
+or any other format amenable to the C preprocessor. RegGen is
+also not limited to a single processor architecture.
+
+While RegGen's output can be used directly in C or C++ code,
+it's intended to be used with a set of helper macros which make
+register manipulation less verbose. These helper macros are not
+part of the generated output, and are instead written by hand.
+
+## Examples
+
+Examples can be found for the STM32H743 and Ingenic X1000
+processors in the `regs` directory. Run `make example` to
+generate sample C header outputs in the `out` directory.
+
+## Input format
+
+RegGen input files should have the extension `.regs`. They may
+contain both _type definitions_ and _instance definitions_ at
+the top level.
+
+### Whitespace and comments
+
+Whitespace is not significant, and is only needed to separate
+tokens. Both C-style line comments `//` and multi-line comments
+`/* ... */` are supported and can occur anywhere in the input.
+
+### Type definitions
+
+There are three kinds of types: enums, registers, and blocks.
+Enums are similar to C enums and are used to describe special
+values of register fields. Registers are used to describe the
+layout of fields in a machine register.
+
+Blocks act as containers for registers, which are defined with
+instances in the body of the block. These instances specify the
+offset of a register relative to the block's base address. It's
+also possible to define sub-blocks of registers inside a block,
+by giving an instance a block type instead of a register type.
+
+#### General syntax
+
+The general form of a type definition is:
+
+```
+type NAME {
+ // body of type goes here
+}
+```
+
+where `type` is either `enum`, `reg`, or `block` depending on
+the kind of type being declared. `NAME` is an identifier which
+specifies the type's name.
+
+#### Enum definitions
+
+Enums are defined with the following syntax:
+
+```
+enum SWCLK {
+ 0 = HSI
+ 1 = CSI
+ 2 = HSE
+ 3 = PLL1P
+}
+```
+
+Each enumerator has the value on the left hand side, which
+must be a non-negative integer, and the name on the right
+hand side of the `=`, unlike C.
+
+#### Register definitions
+
+Registers are defined with the following syntax:
+
+```
+reg REGISTER {
+ // Untyped field occupying bits 15 through 20, inclusive.
+ 20 15 FIELD1
+
+ // Field with an enum type 'TYPE', which must be previously defined.
+ 14 10 FIELD2 : TYPE
+
+ // Field with inline enum; the 'enum' keyword is optional.
+ 09 08 FIELD3 : { 0 = E1; 1 = E2 }
+ 07 06 FIELD4 : enum { ... }
+
+ // Single bit fields, occupying bits 5 and 4 only.
+ // The '--' is only used to align the field names nicely.
+ -- 05 FIELD5
+ 04 -- FIELD6
+
+ // Single bit fields can be declared with a single number
+ 03 FIELD7
+
+ // Leading zeros are only needed for alignment
+ 2 0 FIELD8
+}
+```
+
+Each register field is defined by specifying its most significant
+bit (MSB), least significant bit (LSB), name, and optionally an
+enum type following the colon (`:`). Assigning a type which is not
+an enum is an error.
+
+Because many enums are only used for a single field it is allowed
+to define an enum inline, shown in the definitions of `FIELD3` and
+`FIELD4` above. An inline enum is equivalent to declaring an enum
+separately with the same name as the field, like this:
+
+```
+reg REGISTER {
+ enum FIELD3 { 0 = E1; 1 = E2 }
+
+ 09 08 FIELD3 : FIELD3
+}
+```
+
+Field names only need to be unique within their register and may
+have the same name as a type.
+
+The examples in `FIELD5` to `FIELD7` show how to declare a single
+bit register field. It is also possible to manually set the MSB
+and LSB equal as in `04 04 FIELD6`, but this is error-prone so
+should be avoided.
+
+Fields must occupy a contiguous range of bits and not overlap any
+other fields in the same register. They must also stay within the
+bounds of the register word type. A register declared with the
+`reg` keyword uses the machine word type specified on the RegGen
+command line, but it is possible to use the alternative keywords
+`reg8`, `reg16`, `reg32`, or `reg64` to explicitly set the width
+of the register between 8- and 64-bits.
+
+Unused bits in the register word are allowed, and remain anonymous.
+There is no need to create dummy fields for padding bits.
+
+#### Block definitions
+
+Blocks are defined with the following syntax:
+
+```
+block BLOCK {
+ // Single instance
+ INST1 @ 0x00 : TYPE
+
+ // Instance with inline register definition
+ REG @ 0x04 : reg {
+ 15 00 FIELD
+ // ...
+ }
+
+ // Instance with anonymous register
+ ANON_REG @ 0x08 : reg
+
+ // Instance with inline block definition
+ INNER_BLOCK @ 0x10 : block {
+ // Instances within the sub-block are relative
+ // to the sub-block, not the outer block, so this
+ // register will be at offset 0x10 + 0x04 = 0x14
+ // relative to the outer block
+ SUBREG @ 0x04 : reg
+ }
+
+ // Arrayed instance
+ ARRAYINST @ 0x80 [4; 0x100] : TYPE
+}
+```
+
+The name of an instance is given on the left hand side of the
+at-sign (`@`) and the address offset is given on the right hand
+side. The type assignment after the colon (`:`) is required, and
+the type must be a previously defined register or block type.
+
+##### Singular instances
+
+_Singular instances_ are the most common, and have the form:
+
+```
+NAME @ OFFSET : TYPE
+```
+
+All the examples above except for `ARRAYINST` are singular
+instances. Declaring a singular instance means that if you
+have an pointer to `BLOCK`, then you can get a pointer to
+whatever is referred to by `TYPE` by adding `OFFSET` to the
+block pointer. This may be a pointer to a register, in which
+case it can be dereferenced as a compatible integer type
+(eg. `uint32_t`), or it may be another block, in which case
+the pointer is only useful for further address calculations.
+
+The type can be defined inline, similarly to inline definitions
+of enums in register fields. This is again identical to creating
+a separate type definition with the same name as the instance.
+The type keyword is also required because the parser otherwise
+can't determine whether the inline type is a register or block.
+
+The `ANON_REG` example shows an anonymous register definition.
+These behave a bit differently than a normal inline register
+definition like `REG`: an anonymous register doesn't generate
+any type name. It is also possible to use an explicit width
+suffix on anonymous registers (`reg32`, etc.) to set the word
+type of the register. Anonymous registers have no fields and
+can only be accessed as bare machine words.
+
+##### Arrayed instances
+
+The `ARRAYINST` example shows an _arrayed instance_, which is
+used to create instances of the same register or block type at
+multiple related addresses. In the generated code the instance
+macros will accept an index parameter to select which instance
+is used. The general form for an arrayed instance is:
+
+```
+NAME @ OFFSET [COUNT; STRIDE] : TYPE
+```
+
+which declares an array of `COUNT` instances, the first one
+at offset `OFFSET` and the n'th one at `OFFSET + (n) * STRIDE`.
+There is no requirement that all of the instances actually be
+implemented in hardware.
+
+It is not possible to declare arrays with a non-uniform stride.
+If you have several instances at unrelated addresses, then you
+must declare multiple singular instances.
+
+##### Block instance rules
+
+Blocks don't perform any checking of instances, except that
+the name of instances must be unique within the block. It is
+allowed to have multiple instances at overlapping addresses.
+
+This is sometimes useful when the hardware being described
+maps multiple incompatible registers at the same address
+(usually which one is mapped is controlled by a separate
+register).
+
+However, this lack of checking can be a source of errors, and
+it is up to the user to avoid mistakes.
+
+#### Nested type definitions
+
+It is possible to define types within the body of another type.
+This is only for convenience; all types exist in single global
+namespace. All types are assigned a _fully qualified name_ based
+on the following rules:
+
+1. A type named `N` defined at top level has the fully qualified
+ name `N`.
+2. A type named `N` defined in the body of another type has
+ the name `U_N`, where `U` is the fully qualified name of
+ the enclosing type.
+
+For example, in the snippet below:
+
+```
+block BLK {
+ reg REG {
+ enum ENUM {
+ ...
+ }
+ }
+}
+```
+
+the fully qualified name of each type is:
+
+- `block BLK` is `BLK`
+- `reg REG` is `BLK_REG`
+- `enum ENUM` is `BLK_REG_ENUM`
+
+Note that the same thing can be achieved by writing every type
+at top level:
+
+```
+enum BLK_REG_ENUM { ... }
+reg BLK_REG { ... }
+block BLK { ... }
+```
+
+The fully qualified name of every type must be globally unique
+in the input file. If this is not the case, RegGen will exit
+and print an error message.
+
+#### Type name resolution
+
+Types can be referred to by name in various parts of the input
+file, examples of which will be given below. However, it would
+be cumbersome to write the fully qualified name of each type
+every time you refer to it, so when a type is referred to within
+the body of another type the parser will try several fully
+qualified names automatically, ranging from most-specific to
+least-specific.
+
+The intuitive idea is that when you refer to a type with the
+name `N`, the parser looks in the body of each syntactically
+enclosing type starting from the innermost type, and searches
+for the type named `N` until it finds a match.
+
+The exact algorithm is as follows: if `N` is the identifier
+used to refer to the type, let `X1`, `X2`, ..., `Xn` be the
+syntactically enclosing types, as in the example below:
+
+```
+type X1 {
+ type X2 {
+ /* ... */
+ ... {
+ type Xn {
+ // 'N' is used here
+ }
+ }
+ }
+}
+```
+
+The parser will construct the fully qualified name `X1_X2_..._Xn_N`
+and look for a matching type. If no match is found, it will
+strip off the innermost enclosing type and try again. For n=3
+this search would look like:
+
+1. `X1_X2_X3_N`
+2. `X1_X2_N`
+3. `X1_N`
+
+The search stops once the parser finds a match. If no match is
+found then `N` is tried directly.
+
+#### Inclusion
+
+To reduce repetition it is possible to include the contents of
+one type in another. For example, sometimes several registers
+will share most of their fields but have one or two unique ones
+not present in the other registers -- copying and pasting the
+same fields in multiple locations is tiresome and can be avoided
+by using includes.
+
+The syntax for an include is:
+
+```
+type TYPE {
+ include OTHERTYPE
+}
+```
+
+The identifier `OTHERTYPE` names the type whose contents will
+be copied to `TYPE`. `OTHERTYPE` must be the same kind of type
+as `TYPE`. Attempting to include a different kind of type, for
+example trying to include an enum in a register, is an error.
+
+The inclusion of other members is semantic, not syntactic or
+textual: the resolved types of included members are unchanged,
+and won't be re-resolved in the context of the including type.
+
+### Root instance definitions
+
+Instances that are defined at the top level, outside of any
+block, are called _root instances_. They specify an absolute
+memory address where a block or register exists.
+
+Usually, a register description for a particular machine
+will define a block type for each peripheral and use root
+instances to define the address of each peripheral block.
+
+The general form of a root instance definition is:
+
+```
+// Single instance
+NAME @ ADDRESS : TYPE
+
+// Arrayed instance
+NAME @ ADDRESS [COUNT; STRIDE] : TYPE
+```
+
+The meaning is that if you cast the absolute address `ADDRESS`
+to a pointer, then it points to whatever is defined by `TYPE`
+(either a block or register). The address calculation for the
+n'th member of an arrayed instance is `ADDRESS + (n) * STRIDE`.
+
+## Output format
+
+RegGen generates multiple C headers as outputs. One header
+is generated for each type referenced by a root instance.
+The header filename is the name of the type converted to
+lowercase.
+
+Types are then generated by recursing into each block type
+accessible through an instance. Instances that point to a
+register type will cause macros for that register type to
+be generated.
+
+Only types referenced by an instance will be generated.
+"Free-floating" types are ignored, as no valid address
+can be derived for them and thus they are treated as not
+accessible.
+
+Instances also generate several macros to define their
+addresses/offsets, and their register type if any.
+
+### Register macros
+
+Each generated register will emit the following macro:
+
+```
+// Register word type
+#define RTYPE_{REGNAME} uintN_t
+```
+
+The string `{REGNAME}` is replaced by the fully qualified
+name of the register type. The `uintN_t` type is selected
+to match the register width.
+
+### Field macros
+
+Each field in a generated register will emit several macros.
+
+```
+// Field mask
+#define BM_{REGNAME}_{FIELDNAME} field_mask
+
+// Field position
+#define BP_{REGNAME}_{FIELDNAME} field_lsb
+
+// Field value
+#define BF_{REGNAME}_{FIELDNAME}(x) ((x << field_lsb) & field_mask)
+
+// Field value mask; argument 'x' is ignored
+#define BFM_{REGNAME}_{FIELDNAME}(x) field_mask
+```
+
+where `field_mask` is an integer constant the same width
+as the register type, with all bits in the field set to 1,
+and all other bits set to 0. For example, a field occupying
+bits 11 to 8 will have a mask of `0xf00`.
+
+The `field_lsb` is the least significant bit number in
+the field. A field occupying bits 11 to 8 would have the
+value 8.
+
+The string `{FIELDNAME}` is replaced by the name of the
+field being generated.
+
+If the field is assigned an enum type, then each member of
+of the enum is emitted according to the following pattern:
+
+```
+// Field enum member
+#define BV_{REGNAME}_{FIELDNAME}_{MEMBERNAME} member_value
+```
+
+`{MEMBERNAME}` is replaced by the name of the enum member
+and `member_value` is its corresponding integer value. The
+type name of the enum is ignored.
+
+Fields assigned an enum type will also have a couple of
+extra macros emitted:
+
+```
+// Field enum value
+#define BF_{REGNAME}_{FIELDNAME}_V(e) \
+ (BF_{REGNAME}_{FIELDNAME}(BV_{REGNAME}_{FIELDNAME}_##e))
+
+// Field enum value mask; argument 'e' is ignored
+#define BFM_{REGNAME}_{FIELDNAME}_V(e) \
+ BM_{REGNAME}_{FIELDNAME}
+```
+
+thus the expression `BF_{REGNAME}_{FIELDNAME}_V(memb)` expands
+to the value of the enum member `memb` shifted into the field's
+position.
+
+### Instance macros
+
+Each instance reachable from the root instance will generate
+a set of macros. Root instances will generate address macros,
+while instances in blocks generate offset macros.
+```
+// Singular instance address
+#define ITA_{INSTNAME} address
+
+// Singular instance offset
+#define ITO_{INSTNAME} offset
+
+// Arrayed instance address
+#define ITA_{INSTNAME}(i) (address + (i) * stride)
+
+// Arrayed instance offset
+#define ITO_{INSTNAME}(i) (offset + (i) * stride)
+
+// Instance type name; only defined if instance has register type.
+// Has same arguments (or lack thereof) as the 'ITA_' / 'ITO_' macros.
+// Not defined if the instance register is anonymous.
+#define ITNA_{INSTNAME}(...) {TYPENAME}
+#define ITNO_{INSTNAME}(...) {TYPENAME}
+
+// Instance access type; only defined if instance has register type.
+// Has same arguments (or lack thereof) as the 'ITA_' / 'ITO_' macros.
+// If the instance register is anonymous, then expands directly to
+// a uintN_t type matching the width of the anonymous register.
+#define ITTA_{INSTNAME}(...) RTYPE_{TYPENAME}
+#define ITTO_{INSTNAME}(...) RTYPE_{TYPENAME}
+```
+
+The string `{INSTNAME}` is replaced by the instance name.
+For root instances, this is the name used when the instance
+is defined while for block instances this is prefixed by the
+string `{BLOCKNAME}_`, where `{BLOCKNAME}` is the fully
+qualified name of the block type containing the instance.
+(For example instance `I` in block `B` has `{INSTNAME} = B_I`).
+
+The string `{TYPENAME}` is replaced by the fully qualified
+name of the instance type.
+
+An instance defined in a block will also generate an address
+macro if it is reachable through a unique path from a root
+instance. That is, it must have an address that is calculated
+by applying successive offsets from a single root instance.
+For the purposes of this check, an arrayed instance counts as
+a single node in the path, even though it generates multiple
+addresses. An instance which is accessible in this way has an
+absolute address that is calculable using only integer
+multiplication and integer constants, without further input.
+
+This means that the `ITA_{INSTNAME}` macro may have more
+than one index argument for instances defined in sub-blocks.
+One index argument is required for each arrayed instance in
+the unique path. The order of arguments goes from the root
+down, ie. the first index argument is used to address the
+first arrayed instance in the path, nearest to the root.
+
+## License
+
+Copyright (C) 2025 Aidan MacDonald.
+
+The RegGen utility source code is distributed under the
+terms of the GNU GPLv3, or at your option, any later version;
+see `LICENSE-GPLv3.txt`.
+
+Register definition files in the `regs` directory are
+distributed under the terms of the Creative Commons CC0
+V1.0 license; see `LICENSE-CC0.txt`.
+
+For avoidance of doubt, the source code license does *not*
+apply to any C source code emitted by RegGen, only to the
+source code of the RegGen utility itself.
diff --git a/tools/reggen_src/array.c b/tools/reggen_src/array.c
new file mode 100644
index 0000000000..43ad7c9d71
--- /dev/null
+++ b/tools/reggen_src/array.c
@@ -0,0 +1,53 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#include "array.h"
+#include
+#include
+
+void array_free(struct array *array)
+{
+ free(array->data);
+ memset(array, 0, sizeof(*array));
+}
+
+void array_push(struct array *array, void *data)
+{
+ if (array->size == array->capacity)
+ {
+ array->capacity += 128;
+ array->data = realloc(array->data, array->capacity * sizeof(*array->data));
+ }
+
+ array->data[array->size++] = data;
+}
+
+void *array_pop(struct array *array)
+{
+ array->size--;
+ return array->data[array->size];
+}
+
+size_t array_size(struct array *array)
+{
+ return array->size;
+}
+
+void *array_get(struct array *array, size_t index)
+{
+ return array->data[index];
+}
diff --git a/tools/reggen_src/array.h b/tools/reggen_src/array.h
new file mode 100644
index 0000000000..a1a772a8f4
--- /dev/null
+++ b/tools/reggen_src/array.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#ifndef REGGEN_ARRAY_H
+#define REGGEN_ARRAY_H
+
+#include
+
+/*
+ * Dynamically sized array
+ */
+struct array
+{
+ void **data;
+ size_t size;
+ size_t capacity;
+};
+
+#define ARRAY_FOREACH(arr, value) \
+ for (size_t index##value = 0; index##value < (arr)->size; ++index##value) \
+ if (value = (arr)->data[index##value], 1)
+
+void array_free(struct array *array);
+void array_push(struct array *array, void *data);
+void *array_pop(struct array *array);
+size_t array_size(struct array *array);
+void *array_get(struct array *array, size_t index);
+
+#endif /* REGGEN_ARRAY_H */
diff --git a/tools/reggen_src/common.c b/tools/reggen_src/common.c
new file mode 100644
index 0000000000..8b87be5c64
--- /dev/null
+++ b/tools/reggen_src/common.c
@@ -0,0 +1,228 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#include "common.h"
+#include
+#include
+#include
+
+const char *type_to_str(enum type type)
+{
+ switch (type)
+ {
+ case TYPE_REG: return "reg";
+ case TYPE_ENUM: return "enum";
+ case TYPE_BLOCK: return "block";
+ default: return NULL;
+ }
+}
+
+char *vformat_string(const char *fmt, va_list ap_in)
+{
+ char *buf = NULL;
+ size_t buflen = 0;
+ for (;;)
+ {
+ int ret;
+ va_list ap;
+ va_copy(ap, ap_in);
+ ret = vsnprintf(buf, buflen, fmt, ap);
+ va_end(ap);
+
+ if (ret < 0)
+ return NULL;
+
+ if ((size_t)ret >= buflen)
+ {
+ buflen = ret + 1;
+ buf = realloc(buf, buflen);
+ continue;
+ }
+
+ return buf;
+ }
+}
+
+char *format_string(const char *fmt, ...)
+{
+ char *buf;
+ va_list ap;
+ va_start(ap, fmt);
+ buf = vformat_string(fmt, ap);
+ va_end(ap);
+
+ return buf;
+}
+
+char *format_source_loc(const struct source_loc *loc)
+{
+ if (loc == NULL)
+ return NULL;
+
+ if (loc->filename == NULL)
+ return NULL;
+
+ return format_string("%s:%d:%d", loc->filename, loc->line, loc->column);
+}
+
+void context_verror(struct context *ctx,
+ const struct source_loc *loc,
+ const char *msg, va_list ap_in)
+{
+ char *loc_str = format_source_loc(loc);
+ if (loc_str)
+ {
+ fprintf(stderr, "%s: ", loc_str);
+ free(loc_str);
+ }
+
+ va_list ap;
+ va_copy(ap, ap_in);
+ vfprintf(stderr, msg, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+
+ ctx->num_errors++;
+}
+
+void context_error(struct context *ctx,
+ const struct source_loc *loc,
+ const char *msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ context_verror(ctx, loc, msg, ap);
+ va_end(ap);
+}
+
+void context_free(struct context *ctx)
+{
+ struct type_member *member;
+ ARRAY_FOREACH(&ctx->memblist, member)
+ free(member);
+
+ struct type_def *tdef;
+ ARRAY_FOREACH(&ctx->typelist, tdef)
+ {
+ hashmap_free(&tdef->members);
+ array_free(&tdef->members_sorted);
+ free(tdef);
+ }
+
+ char *str;
+ HASHMAP_FOREACH(&ctx->strtab, str)
+ free(str);
+
+ hashmap_free(&ctx->types);
+ hashmap_free(&ctx->strtab);
+ array_free(&ctx->memblist);
+ array_free(&ctx->typelist);
+ array_free(&ctx->root_instances);
+}
+
+/* Intern a static string; creates a duplicate of the input string */
+const char *intern_static_string(struct context *ctx, const char *str)
+{
+ char *istr = hashmap_lookup(&ctx->strtab, str);
+ if (istr)
+ return istr;
+
+ istr = strdup(str);
+ hashmap_insert(&ctx->strtab, istr, istr);
+ return istr;
+}
+
+/*
+ * Intern a dynamically allocated string; frees the input string
+ * or transfers ownership to the context
+ */
+const char *intern_string(struct context *ctx, char *str)
+{
+ char *istr = hashmap_lookup(&ctx->strtab, str);
+ if (istr)
+ {
+ free(str);
+ return istr;
+ }
+
+ hashmap_insert(&ctx->strtab, str, str);
+ return str;
+}
+
+/* Intern a string generated from a format string */
+const char *intern_stringf(struct context *ctx, const char *fmt, ...)
+{
+ char *buf;
+ va_list ap;
+ va_start(ap, fmt);
+ buf = vformat_string(fmt, ap);
+ va_end(ap);
+
+ return intern_string(ctx, buf);
+}
+
+/* Construct a new type definition */
+struct type_def *type_def_init(struct context *ctx, const char *name,
+ enum type type, enum word_width width)
+{
+ struct type_def *tdef = calloc(1, sizeof(*tdef));
+
+ tdef->name = name;
+ tdef->type = type;
+ tdef->width = width;
+
+ array_push(&ctx->typelist, tdef);
+ return tdef;
+}
+
+/* Map symbolic word types to real types for the machine */
+enum word_width map_word_type(struct context *ctx, enum word_width width)
+{
+ if (width == WIDTH_UNSPECIFIED)
+ return ctx->config.machine_word_width;
+ if (width == WIDTH_ADDRESS)
+ return map_word_type(ctx, ctx->config.address_word_width);
+
+ return width;
+}
+
+/* Get number of bits in word type */
+int get_word_bits(struct context *ctx, enum word_width width)
+{
+ switch (map_word_type(ctx, width))
+ {
+ case WIDTH_8: return 8;
+ case WIDTH_16: return 16;
+ case WIDTH_32: return 32;
+ case WIDTH_64: return 64;
+ default: return 0;
+ }
+}
+
+/* Return suffix for an unsigned integer literal */
+const char *get_word_literal_suffix(struct context *ctx, enum word_width width)
+{
+ switch (map_word_type(ctx, width))
+ {
+ case WIDTH_8: return "u";
+ case WIDTH_16: return "u";
+ case WIDTH_32: return "ul";
+ case WIDTH_64: return "ull";
+ default: return "";
+ }
+}
diff --git a/tools/reggen_src/common.h b/tools/reggen_src/common.h
new file mode 100644
index 0000000000..903681abf1
--- /dev/null
+++ b/tools/reggen_src/common.h
@@ -0,0 +1,203 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#ifndef REGGEN_COMMON_H
+#define REGGEN_COMMON_H
+
+#include "array.h"
+#include "hashmap.h"
+#include
+#include
+#include
+
+/*
+ * Common types
+ */
+enum word_width
+{
+ WIDTH_UNSPECIFIED,
+ WIDTH_ADDRESS,
+ WIDTH_8,
+ WIDTH_16,
+ WIDTH_32,
+ WIDTH_64,
+};
+
+enum type
+{
+ TYPE_ENUM,
+ TYPE_REG,
+ TYPE_BLOCK,
+};
+
+struct type_def;
+
+/* Reference to a source file location */
+struct source_loc
+{
+ const char *filename;
+ int line;
+ int column;
+};
+
+/* Common data for type members */
+struct type_member
+{
+ /* Name of member */
+ const char *name;
+
+ /* Type assigned to this member */
+ struct type_def *type;
+
+ /* Source location where member was defined */
+ struct source_loc loc;
+};
+
+/* Data for enumerators */
+struct enum_value
+{
+ struct type_member comm;
+ uint64_t value;
+};
+
+/* Data for register fields */
+struct reg_field
+{
+ struct type_member comm;
+ int msb;
+ int lsb;
+};
+
+/* Data for instances */
+struct instance
+{
+ struct type_member comm;
+ uint64_t offset;
+ uint64_t stride;
+ size_t count;
+
+ /*
+ * Number of unique paths that reach this instance.
+ * (Arrayed instances only count as 1 path node.)
+ */
+ size_t path_count;
+};
+
+/* Type definition */
+struct type_def
+{
+ /* Kind of type (enum/register/block) */
+ enum type type;
+
+ /* Type's full name */
+ const char *name;
+
+ /* Width of the type (for registers) */
+ enum word_width width;
+
+ /* Members */
+ struct hashmap members;
+
+ /* Sorted list of members (cached for ease of use) */
+ struct array members_sorted;
+
+ /* Flag used to track if the type was visited during code generation */
+ bool visited;
+
+ /* File where the type was written during code generation */
+ struct output_file *output_file;
+
+ /* Source location where type was defined */
+ struct source_loc loc;
+};
+
+struct config
+{
+ enum word_width machine_word_width;
+ enum word_width address_word_width;
+
+ /* Prefix used for include guards on generated headers */
+ const char *include_guard_prefix;
+
+ /* Header included at the start of every generated header */
+ const char *reggen_header_name;
+
+ /* Prefixes used for each possible macro */
+ const char *implicit_type_prefix;
+ const char *register_iotype_macro_prefix;
+ const char *field_pos_macro_prefix;
+ const char *field_mask_macro_prefix;
+ const char *field_enum_macro_prefix;
+ const char *field_value_macro_prefix;
+ const char *field_valmask_macro_prefix;
+ const char *instance_address_macro_prefix;
+ const char *instance_offset_macro_prefix;
+ const char *instance_name_address_macro_prefix;
+ const char *instance_name_offset_macro_prefix;
+ const char *instance_type_address_macro_prefix;
+ const char *instance_type_offset_macro_prefix;
+
+ /* Output directory name */
+ const char *output_directory;
+};
+
+struct context
+{
+ struct config config;
+
+ /* name -> struct type_def mapping (non-owning) */
+ struct hashmap types;
+
+ /* String table for interning (owns strings) */
+ struct hashmap strtab;
+
+ /* List of all top-level instances */
+ struct array root_instances;
+
+ /* Owns all type_def / type_member objects */
+ struct array typelist;
+ struct array memblist;
+
+ /* Error counter */
+ size_t num_errors;
+};
+
+const char *type_to_str(enum type type);
+char *vformat_string(const char *fmt, va_list ap_in);
+char *format_string(const char *fmt, ...);
+char *format_source_loc(const struct source_loc *loc);
+
+void context_verror(struct context *ctx,
+ const struct source_loc *loc,
+ const char *msg, va_list ap_in);
+void context_error(struct context *ctx,
+ const struct source_loc *loc,
+ const char *msg, ...);
+void context_free(struct context *ctx);
+
+const char *intern_static_string(struct context *ctx, const char *str);
+const char *intern_string(struct context *ctx, char *str);
+const char *intern_stringf(struct context *ctx, const char *fmt, ...);
+
+struct type_def *type_def_init(struct context *ctx, const char *name,
+ enum type type, enum word_width width);
+
+enum word_width map_word_type(struct context *ctx, enum word_width width);
+int get_word_bits(struct context *ctx, enum word_width width);
+const char *get_word_literal_suffix(struct context *ctx, enum word_width width);
+
+#endif /* REGGEN_COMMON_H */
diff --git a/tools/reggen_src/generate.c b/tools/reggen_src/generate.c
new file mode 100644
index 0000000000..dcbbc75a81
--- /dev/null
+++ b/tools/reggen_src/generate.c
@@ -0,0 +1,437 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#include "generate.h"
+#include "array.h"
+#include "common.h"
+#include "output.h"
+#include
+#include
+#include
+
+struct output_writer
+{
+ struct context *ctx;
+ struct output_file *file;
+ struct array instance_path;
+};
+
+static void write_register_macros(struct output_writer *wr, struct type_def *tdef)
+{
+ output_addcol(wr->file, 0, "/* Register: %s */", tdef->name);
+ output_newline(wr->file);
+ output_flush(wr->file);
+
+ /* Register I/O type */
+ output_addcol(wr->file, 0, "#define %s%s%s",
+ wr->ctx->config.register_iotype_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ tdef->name);
+ output_addcol(wr->file, 1, " uint%d_t", get_word_bits(wr->ctx, tdef->width));
+ output_newline(wr->file);
+
+ /* Generate macros for each register field */
+ struct reg_field *field;
+ ARRAY_FOREACH(&tdef->members_sorted, field)
+ {
+ int width = field->msb - field->lsb + 1;
+ uint64_t mask = ~(~0ull << width);
+ const char *mask_suffix = get_word_literal_suffix(wr->ctx, tdef->width);
+
+ /* Field mask: constant with all bits in field set to 1 */
+ output_addcol(wr->file, 0, "#define %s%s%s_%s",
+ wr->ctx->config.field_mask_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ tdef->name, field->comm.name);
+ output_addcol(wr->file, 1, " (0x%" PRIX64 "%s << %d)",
+ mask, mask_suffix, field->lsb);
+ output_newline(wr->file);
+
+ /* Field position: index of least significant bit of field */
+ output_addcol(wr->file, 0, "#define %s%s%s_%s",
+ wr->ctx->config.field_pos_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ tdef->name, field->comm.name);
+ output_addcol(wr->file, 1, " %d", field->lsb);
+ output_newline(wr->file);
+
+ /* Field value: function-like macro m(x) which puts value x in field */
+ output_addcol(wr->file, 0, "#define %s%s%s_%s(x)",
+ wr->ctx->config.field_value_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ tdef->name, field->comm.name);
+ output_addcol(wr->file, 1, " (((x) & 0x%" PRIX64 "%s) << %d)", mask, mask_suffix, field->lsb);
+ output_newline(wr->file);
+
+ /* Field mask value: same as field mask but takes an argument which is ignored */
+ output_addcol(wr->file, 0, "#define %s%s%s_%s(x)",
+ wr->ctx->config.field_valmask_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ tdef->name, field->comm.name);
+ output_addcol(wr->file, 1, " (0x%" PRIX64 "%s << %d)",
+ mask, mask_suffix, field->lsb);
+ output_newline(wr->file);
+
+ if (field->comm.type && hashmap_size(&field->comm.type->members) > 0)
+ {
+ /* Print enum members */
+ struct enum_value *value;
+ ARRAY_FOREACH(&field->comm.type->members_sorted, value)
+ {
+ output_addcol(wr->file, 0, "#define %s%s%s_%s_%s",
+ wr->ctx->config.field_enum_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ tdef->name, field->comm.name, value->comm.name);
+ output_addcol(wr->file, 1, " 0x%" PRIX64 "%s", value->value, mask_suffix);
+ output_newline(wr->file);
+ }
+
+ /*
+ * Field enum value: function-like macro m(e) which puts the
+ * value of the enum member e into the field
+ */
+ output_addcol(wr->file, 0, "#define %s%s%s_%s_V(e)",
+ wr->ctx->config.field_value_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ tdef->name, field->comm.name);
+ output_addcol(wr->file, 1, " (%s%s%s_%s_##e << %d)",
+ wr->ctx->config.field_enum_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ tdef->name, field->comm.name, field->lsb);
+ output_newline(wr->file);
+
+ /* Field enum mask value: same as field mask value, ignores its argument */
+ output_addcol(wr->file, 0, "#define %s%s%s_%s_V(e)",
+ wr->ctx->config.field_valmask_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ tdef->name, field->comm.name);
+ output_addcol(wr->file, 1, " (0x%" PRIX64 "%s << %d)",
+ mask, mask_suffix, field->lsb);
+ output_newline(wr->file);
+ }
+ }
+
+ output_newline(wr->file);
+ output_flush(wr->file);
+}
+
+static void write_instance_macros(struct output_writer *wr,
+ struct type_def *enclosing_type,
+ struct instance *instance)
+{
+ const char *addr_literal_suffix = get_word_literal_suffix(wr->ctx, WIDTH_ADDRESS);
+ const char *addr_macro_prefix = enclosing_type ? wr->ctx->config.instance_offset_macro_prefix
+ : wr->ctx->config.instance_address_macro_prefix;
+ const char *offset_macro_args = "";
+ const char *address_macro_args = "";
+ const char *instname = instance->comm.name;
+ if (enclosing_type)
+ instname = intern_stringf(wr->ctx, "%s_%s", enclosing_type->name, instname);
+
+ /* Generate offset macro if not at top level */
+ if (enclosing_type)
+ {
+ if (instance->count == 1)
+ {
+ offset_macro_args = "";
+ output_addcol(wr->file, 1, " 0x%" PRIX64 "%s",
+ instance->offset, addr_literal_suffix);
+ }
+ else
+ {
+ offset_macro_args = "(i)";
+ output_addcol(wr->file, 1, " (0x%" PRIX64 "%s + (i) * 0x%" PRIX64 "%s)",
+ instance->offset, addr_literal_suffix,
+ instance->stride, addr_literal_suffix);
+ }
+
+ output_addcol(wr->file, 0, "#define %s%s%s%s",
+ addr_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ instname, offset_macro_args);
+ output_newline(wr->file);
+ }
+
+ /*
+ * Generate address macro if the instance can only be accessed
+ * through a unique path (which may contain arrayed instances).
+ */
+ bool has_address_macro = (instance->path_count == 1);
+ if (has_address_macro)
+ {
+ output_addcol(wr->file, 0, "#define %s%s%s",
+ wr->ctx->config.instance_address_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ instname);
+ output_addcol(wr->file, 1, " ");
+
+ struct instance *path_inst;
+ size_t instindex = 0;
+ size_t argindex = 0;
+ array_push(&wr->instance_path, instance);
+ ARRAY_FOREACH(&wr->instance_path, path_inst)
+ {
+ if (path_inst->count == 1)
+ {
+ output_addcol(wr->file, 1, "%s0x%" PRIX64 "%s",
+ instindex == 0 ? "(" : " + ",
+ path_inst->offset, addr_literal_suffix);
+ }
+ else
+ {
+ output_addcol(wr->file, 0, "%si%zu",
+ argindex == 0 ? "(" : ", ",
+ argindex + 1);
+ output_addcol(wr->file, 1, "%s(0x%" PRIX64 "%s + (i%zu) * 0x%" PRIX64 "%s)",
+ instindex == 0 ? "(" : " + ",
+ path_inst->offset, addr_literal_suffix,
+ argindex + 1,
+ path_inst->stride, addr_literal_suffix);
+ argindex++;
+ }
+
+ instindex++;
+ }
+
+ array_pop(&wr->instance_path);
+
+ if (argindex > 0)
+ {
+ /*
+ * cheat a little bit; the ITTA and ITNA macros don't
+ * use their macro arguments, they just need to have a
+ * compatible signature with the address macro, which
+ * varargs will always be.
+ */
+ address_macro_args = "(...)";
+ output_addcol(wr->file, 0, ")");
+ }
+
+ output_addcol(wr->file, 1, ")");
+ output_newline(wr->file);
+ }
+
+ /* Type name macro: expands to the name of the target register type */
+ if (instance->comm.type->type == TYPE_REG &&
+ hashmap_size(&instance->comm.type->members) > 0)
+ {
+ output_addcol(wr->file, 0, "#define %s%s%s%s",
+ wr->ctx->config.instance_name_offset_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ instname, offset_macro_args);
+ output_addcol(wr->file, 1, " %s%s",
+ wr->ctx->config.implicit_type_prefix,
+ instance->comm.type->name);
+ output_newline(wr->file);
+
+ if (has_address_macro)
+ {
+ output_addcol(wr->file, 0, "#define %s%s%s%s",
+ wr->ctx->config.instance_name_address_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ instname, address_macro_args);
+ output_addcol(wr->file, 1, " %s%s",
+ wr->ctx->config.implicit_type_prefix,
+ instance->comm.type->name);
+ output_newline(wr->file);
+ }
+ }
+
+ /* Access type macro: expands to the uintN_t of the target register type */
+ if (instance->comm.type->type == TYPE_REG)
+ {
+ int num_bits = get_word_bits(wr->ctx, instance->comm.type->width);
+
+ output_addcol(wr->file, 0, "#define %s%s%s%s",
+ wr->ctx->config.instance_type_offset_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ instname, offset_macro_args);
+ output_addcol(wr->file, 1, " uint%d_t", num_bits);
+ output_newline(wr->file);
+
+ if (has_address_macro)
+ {
+ output_addcol(wr->file, 0, "#define %s%s%s%s",
+ wr->ctx->config.instance_type_address_macro_prefix,
+ wr->ctx->config.implicit_type_prefix,
+ instname, address_macro_args);
+ output_addcol(wr->file, 1, " uint%d_t", num_bits);
+ output_newline(wr->file);
+ }
+ }
+}
+
+static void write_block_macros(struct output_writer *wr, struct type_def *tdef)
+{
+ output_addcol(wr->file, 0, "/* Block: %s */", tdef->name);
+ output_newline(wr->file);
+ output_flush(wr->file);
+
+ struct instance *instance;
+ ARRAY_FOREACH(&tdef->members_sorted, instance)
+ write_instance_macros(wr, tdef, instance);
+
+ output_newline(wr->file);
+ output_flush(wr->file);
+}
+
+static void write_type_def(struct output_writer *wr, struct instance *instance)
+{
+ struct type_def *tdef = instance->comm.type;
+ if (tdef->visited)
+ {
+ if (wr->file == tdef->output_file)
+ return;
+ if (hashmap_size(&tdef->members) == 0)
+ return;
+
+ context_error(wr->ctx, &tdef->loc,
+ "referencing the same type in multiple output files is not supported");
+ context_error(wr->ctx, &tdef->loc,
+ "type was first written to %s",
+ tdef->output_file->filename);
+ context_error(wr->ctx, &instance->comm.loc,
+ "type also needed by instance %s in file %s",
+ instance->comm.name, wr->file->filename);
+ return;
+ }
+
+ switch (tdef->type)
+ {
+ case TYPE_REG:
+ if (hashmap_size(&tdef->members) > 0)
+ write_register_macros(wr, tdef);
+ break;
+
+ case TYPE_BLOCK:
+ write_block_macros(wr, tdef);
+ break;
+
+ default:
+ context_error(wr->ctx, &tdef->loc, "internal error: unknown type");
+ break;
+ }
+
+ tdef->visited = true;
+ tdef->output_file = wr->file;
+}
+
+static void write_instance_types(struct output_writer *wr, struct instance *instance)
+{
+ array_push(&wr->instance_path, instance);
+
+ /* Generate block member types first */
+ struct type_def *tdef = instance->comm.type;
+ if (tdef->type == TYPE_BLOCK)
+ {
+ struct instance *child_instance;
+ ARRAY_FOREACH(&tdef->members_sorted, child_instance)
+ write_instance_types(wr, child_instance);
+ }
+
+ /* Generate the type */
+ write_type_def(wr, instance);
+
+ array_pop(&wr->instance_path);
+}
+
+static const char *get_filename_for_type(struct context *ctx, struct type_def *tdef)
+{
+ char *fname = format_string("%s.h", tdef->name);
+ for (char *s = fname; *s != 0; ++s)
+ *s = tolower(*s);
+
+ return intern_string(ctx, fname);
+}
+
+void generate_output(struct context *ctx)
+{
+ struct output_writer wr = {
+ .ctx = ctx,
+ };
+
+ struct hashmap files = { 0 };
+ struct instance *instance;
+ ARRAY_FOREACH(&ctx->root_instances, instance)
+ {
+ const char *filename = get_filename_for_type(ctx, instance->comm.type);
+
+ wr.file = hashmap_lookup(&files, filename);
+ if (!wr.file)
+ {
+ const char *fpath = intern_stringf(ctx, "%s/%s",
+ ctx->config.output_directory, filename);
+ const char *include_guard_token = intern_stringf(ctx, "REGGEN_%s_%s_H",
+ ctx->config.include_guard_prefix,
+ instance->comm.type->name);
+
+ wr.file = calloc(1, sizeof(*wr.file));
+ wr.file->filename = filename;
+ wr.file->include_guard_token = include_guard_token;
+ wr.file->fstream = fopen(fpath, "wb");
+ if (!wr.file->fstream)
+ context_error(ctx, NULL, "failed to open output file for writing: %s", fpath);
+
+ /* Write type information for new file */
+ output_addcol(wr.file, 0, "#ifndef %s", include_guard_token);
+ output_newline(wr.file);
+
+ output_addcol(wr.file, 0, "#define %s", include_guard_token);
+ output_newline(wr.file);
+ output_newline(wr.file);
+
+ if (ctx->config.reggen_header_name)
+ {
+ output_addcol(wr.file, 0, "#include \"%s\"",
+ ctx->config.reggen_header_name);
+ output_newline(wr.file);
+ output_newline(wr.file);
+ }
+
+ output_flush(wr.file);
+
+ write_instance_types(&wr, instance);
+
+ output_addcol(wr.file, 0, "/* Instances */");
+ output_newline(wr.file);
+
+ hashmap_insert(&files, filename, wr.file);
+ }
+
+ /* All root instances with the same type go into the same file */
+ write_instance_macros(&wr, NULL, instance);
+ }
+
+ struct output_file *file;
+ HASHMAP_FOREACH(&files, file)
+ {
+ /* Write file trailer */
+ output_newline(file);
+ output_flush(file);
+
+ output_addcol(file, 0, "#endif /* %s */", file->include_guard_token);
+ output_newline(file);
+ output_flush(file);
+
+ /* Cleanup */
+ output_free(file);
+ free(file);
+ }
+
+ array_free(&wr.instance_path);
+ hashmap_free(&files);
+}
diff --git a/tools/reggen_src/generate.h b/tools/reggen_src/generate.h
new file mode 100644
index 0000000000..b20ede83d3
--- /dev/null
+++ b/tools/reggen_src/generate.h
@@ -0,0 +1,25 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#ifndef REGGEN_GENERATE_H
+#define REGGEN_GENERATE_H
+
+struct context;
+
+void generate_output(struct context *ctx);
+
+#endif /* REGGEN_GENERATE_H */
diff --git a/tools/reggen_src/hashmap.c b/tools/reggen_src/hashmap.c
new file mode 100644
index 0000000000..9af334b54f
--- /dev/null
+++ b/tools/reggen_src/hashmap.c
@@ -0,0 +1,123 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#include "hashmap.h"
+#include
+#include
+
+static size_t hash_string(const char *str)
+{
+ /* DJB hash */
+ size_t hash = 5381;
+ while (*str)
+ hash = (33 * hash) + *str++;
+
+ return hash;
+}
+
+static void hashmap_grow(struct hashmap *map)
+{
+ struct hash_bucket *oldbuckets = map->buckets;
+ size_t oldcap = map->capacity;
+
+ map->capacity = oldcap ? oldcap*2 : 32;
+ map->buckets = calloc(map->capacity, sizeof(*map->buckets));
+ map->size = 0;
+
+ for (size_t i = 0; i < oldcap; ++i)
+ {
+ struct hash_bucket *bucket = &oldbuckets[i];
+ if (bucket->key == NULL)
+ continue;
+
+ hashmap_insert(map, bucket->key, bucket->value);
+ }
+
+ free(oldbuckets);
+}
+
+void hashmap_free(struct hashmap *map)
+{
+ free(map->buckets);
+ memset(map, 0, sizeof(*map));
+}
+
+int hashmap_insert(struct hashmap *map, const char *key, void *value)
+{
+ if (map->capacity == 0)
+ hashmap_grow(map);
+
+ size_t hash = hash_string(key);
+ size_t startpos = hash % map->capacity;
+ size_t pos = startpos;
+ for (;;)
+ {
+ struct hash_bucket *bucket = &map->buckets[pos];
+ if (bucket->key == NULL)
+ {
+ bucket->key = key;
+ bucket->value = value;
+ map->size++;
+ return 0;
+ }
+ else if (!strcmp(key, bucket->key))
+ {
+ return HASHERR_EXISTS;
+ }
+
+ if (++pos == map->capacity)
+ pos = 0;
+
+ /* no free space; grow and try again */
+ if (pos == startpos)
+ {
+ hashmap_grow(map);
+ startpos = hash % map->capacity;
+ pos = startpos;
+ }
+ }
+}
+
+void *hashmap_lookup(struct hashmap *map, const char *key)
+{
+ if (map->capacity == 0)
+ return NULL;
+
+ size_t hash = hash_string(key);
+ size_t startpos = hash % map->capacity;
+ size_t pos = startpos;
+ for (;;)
+ {
+ struct hash_bucket *bucket = &map->buckets[pos];
+ if (bucket->key == NULL)
+ return NULL;
+
+ if (!strcmp(key, bucket->key))
+ return bucket->value;
+
+ if (++pos == map->capacity)
+ pos = 0;
+
+ if (pos == startpos)
+ return NULL;
+ }
+}
+
+size_t hashmap_size(struct hashmap *map)
+{
+ return map->size;
+}
diff --git a/tools/reggen_src/hashmap.h b/tools/reggen_src/hashmap.h
new file mode 100644
index 0000000000..2f0f6150a9
--- /dev/null
+++ b/tools/reggen_src/hashmap.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#ifndef REGGEN_HASHMAP_H
+#define REGGEN_HASHMAP_H
+
+#include
+
+/*
+ * Open-addressed hash table with string keys
+ */
+struct hash_bucket
+{
+ const char *key;
+ void *value;
+};
+
+struct hashmap
+{
+ struct hash_bucket *buckets;
+ size_t capacity;
+ size_t size;
+};
+
+#define HASHMAP_FOREACH(map, _value) \
+ for (size_t index##_value = 0; index##_value < (map)->capacity; ++index##_value) \
+ if ((_value = (map)->buckets[index##_value].value))
+
+#define HASHERR_EXISTS (-1)
+
+void hashmap_free(struct hashmap *map);
+int hashmap_insert(struct hashmap *map, const char *key, void *value);
+void *hashmap_lookup(struct hashmap *map, const char *key);
+size_t hashmap_size(struct hashmap *map);
+
+#endif /* REGGEN_HASHMAP_H */
diff --git a/tools/reggen_src/main.c b/tools/reggen_src/main.c
new file mode 100644
index 0000000000..a8f10c12eb
--- /dev/null
+++ b/tools/reggen_src/main.c
@@ -0,0 +1,305 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#include "parse.h"
+#include "validate.h"
+#include "generate.h"
+#include "common.h"
+#include
+#include
+
+enum opt_type
+{
+ OPTTYPE_FLAG,
+ OPTTYPE_STRING,
+ OPTTYPE_INT,
+ OPTTYPE_POSARG,
+};
+
+#define OPT_FOUND 1
+#define OPT_NOT_FOUND 0
+#define OPT_ERROR (-1)
+
+int opt_get(int *argc_p, char ***argv_p,
+ struct context *ctx,
+ enum opt_type opttype,
+ const char *shortopt,
+ const char *longopt,
+ void *output)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ bool force_posarg = false;
+
+ for (int i = 1; i < argc; ++i)
+ {
+ char *opt_val = NULL;
+ int num_args = 1;
+
+ /*
+ * Accept arguments of the form "--opt arg", "--opt=arg", or "-o arg".
+ * Positional arguments can be interleaved with options. A positional
+ * argument starting with a dash is only allowed after "--" indicating
+ * the end of options.
+ */
+ if (!strcmp(argv[i], "--"))
+ {
+ force_posarg = true;
+ continue;
+ }
+
+ if (opttype == OPTTYPE_POSARG)
+ {
+ if (!force_posarg && argv[i][0] == '-')
+ continue;
+
+ opt_val = argv[i];
+ }
+ else if (!force_posarg && longopt && strstr(argv[i], longopt) == argv[i])
+ {
+ size_t n = strlen(longopt);
+ if (opttype != OPTTYPE_FLAG && argv[i][n] == '=')
+ opt_val = &argv[i][n+1];
+ else if (argv[i][n] != '\0')
+ continue;
+ }
+ else if (!force_posarg && shortopt && strcmp(argv[i], shortopt))
+ {
+ continue;
+ }
+
+ if (opttype != OPTTYPE_FLAG && opt_val == NULL)
+ {
+ if (i + 1 >= argc)
+ {
+ context_error(ctx, NULL, "missing argument for option %s", argv[i]);
+ return OPT_ERROR;
+ }
+
+ opt_val = argv[i + 1];
+ num_args++;
+ }
+
+ /* Return output value for option */
+ char *endptr = NULL;
+ switch (opttype)
+ {
+ case OPTTYPE_FLAG:
+ *(bool *)output = true;
+ break;
+
+ case OPTTYPE_INT:
+ *(long *)output = strtol(opt_val, &endptr, 0);
+ if (*opt_val == '\0' || *endptr != '\0')
+ {
+ context_error(ctx, NULL, "invalid argument for option %s: %s", argv[i], opt_val);
+ return OPT_ERROR;
+ }
+
+ break;
+
+ case OPTTYPE_STRING:
+ case OPTTYPE_POSARG:
+ *(char **)output = opt_val;
+ break;
+
+ default:
+ return OPT_NOT_FOUND;
+ }
+
+ /* Remove the parsed option from the argv array */
+ memmove(&argv[i], &argv[i + num_args], (argc - i - num_args) * sizeof(*argv));
+ *argc_p -= num_args;
+ return OPT_FOUND;
+ }
+
+ return OPT_NOT_FOUND;
+}
+
+/* Initial context with default configuration */
+struct context g_ctx = {
+ .config = {
+ .machine_word_width = WIDTH_32,
+ .address_word_width = WIDTH_UNSPECIFIED,
+ .include_guard_prefix = "INCLUDE",
+ .reggen_header_name = NULL,
+ .implicit_type_prefix = "",
+ .register_iotype_macro_prefix = "RTYPE_",
+ .field_pos_macro_prefix = "BP_",
+ .field_mask_macro_prefix = "BM_",
+ .field_enum_macro_prefix = "BV_",
+ .field_value_macro_prefix = "BF_",
+ .field_valmask_macro_prefix = "BFM_",
+ .instance_address_macro_prefix = "ITA_",
+ .instance_offset_macro_prefix = "ITO_",
+ .instance_name_address_macro_prefix = "ITNA_",
+ .instance_name_offset_macro_prefix = "ITNO_",
+ .instance_type_address_macro_prefix = "ITTA_",
+ .instance_type_offset_macro_prefix = "ITTO_",
+ .output_directory = NULL,
+ },
+};
+
+int handle_word_width_option(int width, enum word_width *dest)
+{
+ switch (width)
+ {
+ case 8: *dest = WIDTH_8; break;
+ case 16: *dest = WIDTH_16; break;
+ case 32: *dest = WIDTH_32; break;
+ case 64: *dest = WIDTH_64; break;
+ default: return -1;
+ }
+
+ return 0;
+}
+
+int get_context_options(struct context *ctx, int *argcp, char ***argvp)
+{
+ int width;
+ int rc = opt_get(argcp, argvp, ctx, OPTTYPE_INT, "-mw", "--machine-word-width", &width);
+ if (rc == OPT_ERROR)
+ return -1;
+ if (rc == OPT_FOUND)
+ {
+ if (handle_word_width_option(width, &ctx->config.machine_word_width))
+ {
+ context_error(ctx, NULL, "invalid machine word width: %d", width);
+ return -1;
+ }
+ }
+
+ rc = opt_get(argcp, argvp, ctx, OPTTYPE_INT, "-aw", "--address-word-width", &width);
+ if (rc == OPT_ERROR)
+ return -1;
+ if (rc == OPT_FOUND)
+ {
+ if (handle_word_width_option(width, &ctx->config.address_word_width))
+ {
+ context_error(ctx, NULL, "invalid address word width: %d", width);
+ return -1;
+ }
+ }
+
+ struct {
+ const char *shortopt;
+ const char *longopt;
+ const char **dest;
+ } str_opt_map[] = {
+ {"-ig", "--include-guard-prefix", &ctx->config.include_guard_prefix},
+ {"-rh", "--reggen-header-name", &ctx->config.reggen_header_name},
+ {"-itp", "--implicit-type-prefix", &ctx->config.implicit_type_prefix},
+ {"-rim", "--register-iotype-macro-prefix", &ctx->config.register_iotype_macro_prefix},
+ {"-fpm", "--field-pos-macro-prefix", &ctx->config.field_pos_macro_prefix},
+ {"-fmm", "--field-mask-macro-prefix", &ctx->config.field_mask_macro_prefix},
+ {"-fem", "--field-enum-macro-prefix", &ctx->config.field_enum_macro_prefix},
+ {"-fvm", "--field-value-macro-prefix", &ctx->config.field_value_macro_prefix},
+ {"-fvmm", "--field-valmask-macro-prefix", &ctx->config.field_valmask_macro_prefix},
+ {"-iam", "--instance-address-macro-prefix", &ctx->config.instance_address_macro_prefix},
+ {"-iom", "--instance-offset-macro-prefix", &ctx->config.instance_offset_macro_prefix},
+ {"-inam", "--instance-name-address-macro-prefix", &ctx->config.instance_name_address_macro_prefix},
+ {"-inom", "--instance-name-offset-macro-prefix", &ctx->config.instance_name_offset_macro_prefix},
+ {"-itam", "--instance-type-address-macro-prefix", &ctx->config.instance_type_address_macro_prefix},
+ {"-itom", "--instance-type-offset-macro-prefix", &ctx->config.instance_type_offset_macro_prefix},
+ {"-o", "--output-directory", &ctx->config.output_directory},
+ {NULL, NULL, NULL},
+ };
+
+ for (size_t i = 0; str_opt_map[i].dest != NULL; ++i)
+ {
+ rc = opt_get(argcp, argvp, ctx, OPTTYPE_STRING,
+ str_opt_map[i].shortopt,
+ str_opt_map[i].longopt,
+ str_opt_map[i].dest);
+ if (rc == OPT_ERROR)
+ return -1;
+ }
+
+ if (!ctx->config.output_directory)
+ {
+ context_error(ctx, NULL, "output directory not specified");
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct context *ctx = &g_ctx;
+
+ /* Option parsing */
+ int rc;
+ char *input_filename = NULL;
+
+ rc = opt_get(&argc, &argv, ctx, OPTTYPE_POSARG, NULL, NULL, &input_filename);
+ if (rc != OPT_FOUND)
+ {
+ context_error(ctx, NULL, "missing input filename");
+ return 1;
+ }
+
+ /* Load options for context */
+ if (get_context_options(ctx, &argc, &argv))
+ return 1;
+
+ /* Check for unknown options, ignoring "--" which may be left over */
+ for (int i = 1; i < argc; ++i)
+ {
+ if (strcmp(argv[i], "--"))
+ {
+ context_error(ctx, NULL, "unknown option: %s", argv[i]);
+ return 1;
+ }
+ }
+
+ FILE *input_file = NULL;
+
+ /* Open input file */
+ input_file = fopen(input_filename, "rb");
+ if (!input_file)
+ {
+ context_error(ctx, NULL, "unable to open input file: %s", input_filename);
+ goto exit;
+ }
+
+ /* Parse the input */
+ rc = parse(ctx, input_filename, input_file);
+ if (rc != PARSE_OK)
+ {
+ context_error(ctx, NULL, "failed to parse input file: %s", input_filename);
+ goto exit;
+ }
+
+ /* Validate input to check for errors */
+ validate(ctx);
+
+exit:
+ if (ctx->num_errors == 0)
+ generate_output(ctx);
+
+ if (input_file)
+ fclose(input_file);
+
+ if (ctx->num_errors > 0)
+ rc = 1;
+ else
+ rc = 0;
+
+ context_free(ctx);
+ return rc;
+}
diff --git a/tools/reggen_src/output.c b/tools/reggen_src/output.c
new file mode 100644
index 0000000000..0cf0042846
--- /dev/null
+++ b/tools/reggen_src/output.c
@@ -0,0 +1,207 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#include "output.h"
+#include
+#include
+
+#define MAX_COLUMNS 16
+
+struct output_column
+{
+ char *buffer;
+ size_t length;
+ size_t capacity;
+};
+
+struct output_line
+{
+ struct array columns;
+};
+
+static struct output_line *output_getline(struct output_file *file, size_t line_nr)
+{
+ struct output_line *line;
+
+ /* Allocate line buffer if needed */
+ while (line_nr >= array_size(&file->lines))
+ {
+ line = calloc(1, sizeof(*line));
+ array_push(&file->lines, line);
+ }
+
+ /* Get line buffer */
+ return array_get(&file->lines, line_nr);
+}
+
+static struct output_column *output_getcol(struct output_file *file, size_t column_nr)
+{
+ struct output_line *line = output_getline(file, file->num_lines);
+ struct output_column *column;
+
+ /* Allocate column buffer if needed */
+ while (column_nr >= array_size(&line->columns))
+ {
+ column = calloc(1, sizeof(*column));
+ array_push(&line->columns, column);
+ }
+
+ /* Get column buffer */
+ return array_get(&line->columns, column_nr);
+}
+
+void output_vaddcol(struct output_file *file, size_t column_nr,
+ const char *fmt, va_list ap_in)
+{
+ struct output_column *column = output_getcol(file, column_nr);
+
+ /* Append text to column buffer */
+ for (;;)
+ {
+ size_t avail_len = column->capacity - column->length;
+ va_list ap;
+ int rc;
+
+ va_copy(ap, ap_in);
+ rc = vsnprintf(column->buffer + column->length, avail_len, fmt, ap);
+ va_end(ap);
+
+ /* TODO: report error? */
+ if (rc < 0)
+ break;
+
+ if ((size_t)rc >= avail_len)
+ {
+ column->capacity += (size_t)rc - avail_len + 1;
+ column->buffer = realloc(column->buffer, column->capacity);
+ continue;
+ }
+
+ column->length += rc;
+ break;
+ }
+}
+
+void output_addcol(struct output_file *file, size_t column_nr,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ output_vaddcol(file, column_nr, fmt, ap);
+ va_end(ap);
+}
+
+void output_newline(struct output_file *file)
+{
+ file->num_lines++;
+}
+
+void output_flush(struct output_file *file)
+{
+ /* Iterate over columns and compute max width of each column */
+ size_t column_width[MAX_COLUMNS] = { 0 };
+ for (size_t i = 0; i < file->num_lines; ++i)
+ {
+ if (i >= array_size(&file->lines))
+ break;
+
+ struct output_line *line = array_get(&file->lines, i);
+ for (size_t j = 0; j < array_size(&line->columns); ++j)
+ {
+ struct output_column *column = array_get(&line->columns, j);
+ if (column->length > column_width[j])
+ column_width[j] = column->length;
+ }
+ }
+
+ /* Calculate worst-case output buffer length for columnated data */
+ size_t need_linesize = 0;
+ for (size_t j = 0; j < MAX_COLUMNS; ++j)
+ {
+ /* Add 1 byte for space/newline */
+ need_linesize += column_width[j] + 1;
+ }
+
+ /* Add 1 byte for null terminator */
+ need_linesize += 1;
+
+ /* Allocate output buffer */
+ if (file->linesize < need_linesize)
+ {
+ file->linesize = need_linesize;
+ file->linebuf = realloc(file->linebuf, file->linesize);
+ }
+
+ /* Write columns to the output with required amount of padding */
+ for (size_t i = 0; i < file->num_lines; ++i)
+ {
+ struct output_line *line = NULL;
+ if (i < array_size(&file->lines))
+ line = array_get(&file->lines, i);
+
+ int pos = 0;
+ for (size_t j = 0; line && j < array_size(&line->columns); ++j)
+ {
+ struct output_column *column = array_get(&line->columns, j);
+ size_t num_spaces = column_width[j] - column->length;
+
+ /* Fill column data */
+ memcpy(file->linebuf + pos, column->buffer, column->length);
+ memset(file->linebuf + pos + column->length, ' ', num_spaces);
+
+ /* Update position & clear column data for next run */
+ pos += column_width[j];
+ column->length = 0;
+ }
+
+ /* Strip trailing spaces & add a newline */
+ while (pos > 0 && file->linebuf[pos-1] == ' ')
+ pos--;
+
+ file->linebuf[pos++] = '\n';
+
+ /* Append to the output */
+ if (file->fstream)
+ fprintf(file->fstream, "%.*s", pos, file->linebuf);
+ }
+
+ /* Clear all used lines */
+ file->num_lines = 0;
+}
+
+void output_free(struct output_file *file)
+{
+ struct output_line *line;
+ ARRAY_FOREACH(&file->lines, line)
+ {
+ struct output_column *column;
+ ARRAY_FOREACH(&line->columns, column)
+ {
+ free(column->buffer);
+ free(column);
+ }
+
+ array_free(&line->columns);
+ free(line);
+ }
+
+ array_free(&file->lines);
+ free(file->linebuf);
+
+ if (file->fstream)
+ fclose(file->fstream);
+}
diff --git a/tools/reggen_src/output.h b/tools/reggen_src/output.h
new file mode 100644
index 0000000000..f42482eef3
--- /dev/null
+++ b/tools/reggen_src/output.h
@@ -0,0 +1,48 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#ifndef REGGEN_OUTPUT_H
+#define REGGEN_OUTPUT_H
+
+#include "array.h"
+#include
+#include
+#include
+
+struct output_file
+{
+ FILE *fstream;
+
+ const char *filename;
+ const char *include_guard_token;
+
+ char *linebuf;
+ size_t linesize;
+
+ struct array lines;
+ size_t num_lines;
+};
+
+void output_vaddcol(struct output_file *file, size_t column_nr,
+ const char *fmt, va_list ap_in);
+void output_addcol(struct output_file *file, size_t column_nr,
+ const char *fmt, ...);
+void output_newline(struct output_file *file);
+void output_flush(struct output_file *file);
+void output_free(struct output_file *file);
+
+#endif /* REGGEN_OUTPUT_H */
diff --git a/tools/reggen_src/parse.c b/tools/reggen_src/parse.c
new file mode 100644
index 0000000000..e95afbe628
--- /dev/null
+++ b/tools/reggen_src/parse.c
@@ -0,0 +1,932 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#include "parse.h"
+#include "common.h"
+#include
+#include
+
+#define IDENT_MAXLEN 4096
+#define NUMBER_MAXLEN 128
+
+struct parser
+{
+ struct context *ctx;
+ struct array type_scope;
+
+ FILE *input;
+ struct source_loc loc;
+};
+
+enum inline_type_style
+{
+ INLINE_KEYWORD,
+ INLINE_BLOCK,
+};
+
+#define TYPEBIT_ENUM (1 << TYPE_ENUM)
+#define TYPEBIT_REG (1 << TYPE_REG)
+#define TYPEBIT_BLOCK (1 << TYPE_BLOCK)
+
+#define IS_PARSE_OK(s) ((s) > 0)
+#define IS_PARSE_EMPTY(s) ((s) == 0)
+#define IS_PARSE_ERR(s) ((s) < 0)
+
+static const char KEYWORD_INCLUDE[] = "include";
+
+static int parse_type_def(struct parser *p, const char *keyword);
+static int parse_type_def_ex(struct parser *p,
+ enum type type,
+ enum word_width width,
+ const char *inline_name,
+ struct type_def **tdefp);
+static int parse_inline_type_def(struct parser *p,
+ const char *inline_name,
+ enum inline_type_style style,
+ enum type type,
+ enum word_width width,
+ struct type_def **tdefp);
+
+static bool parse_lookup_type_keyword(const char *keyword,
+ enum type *typep,
+ enum word_width *rwidthp)
+{
+ struct keyword_entry {
+ const char *keyword;
+ enum type type;
+ enum word_width width;
+ };
+
+ static const struct keyword_entry keyword_mapping[] = {
+ {"enum", TYPE_ENUM, WIDTH_UNSPECIFIED},
+ {"block", TYPE_BLOCK, WIDTH_UNSPECIFIED},
+ {"reg", TYPE_REG, WIDTH_UNSPECIFIED},
+ {"reg8", TYPE_REG, WIDTH_8},
+ {"reg16", TYPE_REG, WIDTH_16},
+ {"reg32", TYPE_REG, WIDTH_32},
+ {"reg64", TYPE_REG, WIDTH_64},
+ };
+
+ size_t len = sizeof(keyword_mapping) / sizeof(*keyword_mapping);
+ for (size_t i = 0; i < len; ++i)
+ {
+ if (!strcmp(keyword, keyword_mapping[i].keyword))
+ {
+ if (rwidthp)
+ *rwidthp = keyword_mapping[i].width;
+ if (typep)
+ *typep = keyword_mapping[i].type;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int parse_error(struct parser *p, const char *msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ context_verror(p->ctx, &p->loc, msg, ap);
+ va_end(ap);
+
+ return PARSE_ERR;
+}
+
+const char *parse_get_scoped_type_name(struct parser *p, const char *last, size_t nskip)
+{
+ char fqn[IDENT_MAXLEN];
+ size_t len = 0;
+
+ if (last)
+ {
+ array_push(&p->type_scope, (void *)last);
+ ++nskip;
+ }
+
+ for (size_t index = 0; index < array_size(&p->type_scope); ++index)
+ {
+ if (index >= array_size(&p->type_scope) - nskip &&
+ index < array_size(&p->type_scope) - 1)
+ {
+ continue;
+ }
+
+ const char *sn = array_get(&p->type_scope, index);
+ size_t slen = strlen(sn);
+ if (len + slen + 1 >= IDENT_MAXLEN)
+ {
+ parse_error(p, "qualified type name too long");
+ return NULL;
+ }
+
+ if (len != 0)
+ fqn[len++] = '_';
+
+ memcpy(&fqn[len], sn, slen);
+ len += slen;
+ }
+
+ if (last)
+ array_pop(&p->type_scope);
+
+ fqn[len] = 0;
+ return intern_static_string(p->ctx, fqn);
+}
+
+struct type_def *parse_lookup_type(struct parser *p, const char *name, size_t init_skip)
+{
+ size_t max_skip = array_size(&p->type_scope);
+ for (size_t skip = init_skip; skip <= max_skip; ++skip)
+ {
+ const char *qname = parse_get_scoped_type_name(p, name, skip);
+ if (!qname)
+ return NULL;
+
+ struct type_def *type = hashmap_lookup(&p->ctx->types, qname);
+ if (type)
+ return type;
+ }
+
+ return NULL;
+}
+
+int parse_whitespace(struct parser *p)
+{
+ bool in_comment = false;
+ bool in_multiline_comment = false;
+
+ for (;;)
+ {
+ int c = fgetc(p->input);
+ p->loc.column++;
+
+ if (c == '\n')
+ {
+ if (!in_multiline_comment)
+ in_comment = false;
+
+ p->loc.line++;
+ p->loc.column = 0;
+ }
+
+ if (c == '/' && !in_comment)
+ {
+ c = fgetc(p->input);
+ p->loc.column++;
+
+ if (c == '*')
+ in_multiline_comment = true;
+ else if (c != '/')
+ return parse_error(p, "invalid comment");
+
+ in_comment = true;
+ }
+
+ if (c == '*' && in_multiline_comment)
+ {
+ c = fgetc(p->input);
+ p->loc.column++;
+
+ if (c == '/')
+ {
+ c = fgetc(p->input);
+ p->loc.column++;
+
+ in_multiline_comment = false;
+ in_comment = false;
+ }
+ }
+
+ if (c == EOF)
+ {
+ if (in_multiline_comment)
+ return parse_error(p, "unterminated comment");
+
+ in_comment = false;
+ }
+
+ if (!in_comment &&
+ c != ' ' && c != '\t' &&
+ c != '\n' && c != '\r')
+ {
+ ungetc(c, p->input);
+ p->loc.column--;
+
+ return PARSE_OK;
+ }
+ }
+}
+
+const char *parse_identifier_ex(struct parser *p, bool allow_first_digit)
+{
+ char ident[IDENT_MAXLEN];
+ size_t idlen = 0;
+ int c;
+
+ if (parse_whitespace(p) != PARSE_OK)
+ return NULL;
+
+ for (;;)
+ {
+ c = fgetc(p->input);
+ p->loc.column++;
+
+ if ((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c == '_'))
+ {
+ ident[idlen++] = c;
+ }
+ else if (c >= '0' && c <= '9' && (idlen > 0 || allow_first_digit))
+ {
+ ident[idlen++] = c;
+ }
+ else
+ {
+ break;
+ }
+
+ if (idlen == IDENT_MAXLEN)
+ {
+ parse_error(p, "identifier too long");
+ return NULL;
+ }
+ }
+
+ ungetc(c, p->input);
+ p->loc.column--;
+
+ if (idlen == 0)
+ return NULL;
+
+ ident[idlen] = 0;
+ return intern_static_string(p->ctx, ident);
+}
+
+const char *parse_identifier(struct parser *p)
+{
+ return parse_identifier_ex(p, false);
+}
+
+int parse_literal_optional(struct parser *p, const char *str)
+{
+ if (parse_whitespace(p) != PARSE_OK)
+ return PARSE_ERR;
+
+ for (size_t index = 0; str[index] != 0; ++index)
+ {
+ int c = fgetc(p->input);
+ p->loc.column++;
+
+ if (c != str[index])
+ {
+ if (index > 0)
+ return parse_error(p, "expected '%s'", str);
+
+ ungetc(c, p->input);
+ p->loc.column--;
+
+ return PARSE_EMPTY;
+ }
+ }
+
+ return PARSE_OK;
+
+}
+
+int parse_literal(struct parser *p, const char *str)
+{
+ int status = parse_literal_optional(p, str);
+ if (status == PARSE_EMPTY)
+ return parse_error(p, "missing '%s'", str);
+
+ return status;
+}
+
+int parse_number(struct parser *p, uint64_t *val)
+{
+ enum {
+ DEC_NUM,
+ HEX_NUM,
+ BIN_NUM,
+ };
+
+ char buf[NUMBER_MAXLEN];
+ size_t len = 0;
+ int mode = DEC_NUM;
+
+ if (parse_whitespace(p) != PARSE_OK)
+ return PARSE_ERR;
+
+ for (;;)
+ {
+ bool valid = false;
+ int c = fgetc(p->input);
+ p->loc.column++;
+
+ if (c >= '0' && c <= '9')
+ {
+ if (c <= '1' || mode != BIN_NUM)
+ valid = true;
+ }
+ else if (mode == HEX_NUM)
+ {
+ if (c >= 'a' && c <= 'f')
+ valid = true;
+ if (c >= 'A' && c <= 'F')
+ valid = true;
+ }
+
+ if (c == '_' && len > 0)
+ valid = true;
+
+ if (!valid)
+ {
+ if (len == 1 && buf[0] == '0')
+ {
+ if (c == 'x' || c == 'X')
+ {
+ mode = HEX_NUM;
+ len = 0;
+ continue;
+ }
+ else if (c == 'b' || c == 'B')
+ {
+ mode = BIN_NUM;
+ len = 0;
+ continue;
+ }
+ }
+
+ if (len > 0)
+ {
+ /* detect silly errors like 0b0123, etc. */
+ if ((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z'))
+ return parse_error(p, "invalid number");
+ }
+
+ /* parsed last valid character */
+ ungetc(c, p->input);
+ p->loc.column--;
+ break;
+ }
+
+ buf[len++] = c;
+ if (len == NUMBER_MAXLEN)
+ return parse_error(p, "number too long");
+ }
+
+ if (len == 0)
+ {
+ if (mode != DEC_NUM)
+ return parse_error(p, "invalid number");
+
+ return PARSE_EMPTY;
+ }
+
+ uint64_t rval = 0;
+ for (size_t i = 0; i < len; ++i)
+ {
+ if (buf[i] == '_')
+ continue;
+
+ switch (mode)
+ {
+ case BIN_NUM:
+ rval *= 2;
+ break;
+ case DEC_NUM:
+ rval *= 10;
+ break;
+ case HEX_NUM:
+ rval *= 16;
+ break;
+ }
+
+ if (buf[i] >= '0' && buf[i] <= '9')
+ rval += buf[i] - '0';
+ else if (buf[i] >= 'a' && buf[i] <= 'f')
+ rval += 10 + buf[i] - 'a';
+ else if (buf[i] >= 'A' && buf[i] <= 'F')
+ rval += 10 + buf[i] - 'A';
+ }
+
+ if (val)
+ *val = rval;
+
+ return PARSE_OK;
+}
+
+int parse_member_type(struct parser *p,
+ struct type_def **tdef_out,
+ const char *inline_name,
+ int allowed_types)
+{
+ if (parse_literal_optional(p, ":") == PARSE_EMPTY)
+ {
+ *tdef_out = NULL;
+
+ parse_literal_optional(p, ";");
+ return PARSE_EMPTY;
+ }
+
+ struct type_def *tdef = NULL;
+ bool is_inline_type = false;
+ enum type inline_type;
+ enum word_width inline_width;
+ enum inline_type_style inline_style;
+ int status = 0;
+
+ const char *ident = parse_identifier(p);
+ if (!ident)
+ {
+ if (parse_literal(p, "{") != PARSE_OK)
+ return PARSE_ERR;
+ if (!inline_name)
+ return parse_error(p, "internal error: missing default inline type name");
+
+ if (allowed_types == TYPEBIT_ENUM)
+ inline_type = TYPE_ENUM;
+ else if (allowed_types == TYPEBIT_REG)
+ inline_type = TYPE_REG;
+ else if (allowed_types == TYPEBIT_BLOCK)
+ inline_type = TYPE_BLOCK;
+ else
+ return parse_error(p, "type keyword required for inline defintion here");
+
+ is_inline_type = true;
+ inline_style = INLINE_BLOCK;
+ inline_width = WIDTH_UNSPECIFIED;
+ }
+ else if (parse_lookup_type_keyword(ident, &inline_type, &inline_width))
+ {
+ if ((allowed_types & (1 << inline_type)) == 0)
+ {
+ return parse_error(p, "inline %s not allowed here",
+ type_to_str(inline_type));
+ }
+
+ is_inline_type = true;
+ inline_style = INLINE_KEYWORD;
+ }
+
+ if (is_inline_type)
+ {
+ status = parse_inline_type_def(p, inline_name, inline_style,
+ inline_type, inline_width, &tdef);
+ if (IS_PARSE_ERR(status))
+ return status;
+ }
+ else
+ {
+ tdef = parse_lookup_type(p, ident, 0);
+ if (tdef == NULL)
+ return parse_error(p, "unknown type '%s'", ident);
+ else if ((allowed_types & (1 << tdef->type)) == 0)
+ return parse_error(p, "%s type not allowed here", type_to_str(tdef->type));
+ }
+
+ *tdef_out = tdef;
+
+ parse_literal_optional(p, ";");
+ return PARSE_OK;
+}
+
+int parse_include(struct parser *p, struct type_def *tdef)
+{
+ const char *type = parse_identifier(p);
+ if (!type)
+ {
+ return parse_error(p, "missing %s type name for include",
+ type_to_str(tdef->type));
+ }
+
+ struct type_def *itdef = parse_lookup_type(p, type, 1);
+ if (!itdef)
+ return parse_error(p, "unknown type '%s'", type);
+ if (itdef->type != tdef->type)
+ {
+ return parse_error(p, "cannot include %s type '%s' from %s",
+ type_to_str(itdef->type), itdef->name,
+ type_to_str(tdef->type));
+ }
+
+ struct type_member *memb;
+ HASHMAP_FOREACH(&itdef->members, memb)
+ {
+ if (hashmap_insert(&tdef->members, memb->name, memb))
+ {
+ return parse_error(p, "duplicate member '%s' from included %s %s",
+ memb->name, type_to_str(tdef->type), itdef->name);
+ }
+ }
+
+ parse_literal_optional(p, ";");
+ return PARSE_OK;
+}
+
+int parse_enum(struct parser *p, struct type_def *tdef)
+{
+ for (;;)
+ {
+ int status;
+ const char *keyword = parse_identifier(p);
+ if (keyword)
+ {
+ if (strcmp(keyword, KEYWORD_INCLUDE))
+ return parse_error(p, "invalid syntax");
+
+ status = parse_include(p, tdef);
+ if (status != PARSE_OK)
+ return status;
+
+ continue;
+ }
+
+ uint64_t value;
+ status = parse_number(p, &value);
+ if (IS_PARSE_ERR(status))
+ return status;
+ if (IS_PARSE_EMPTY(status))
+ return PARSE_OK;
+
+ if (parse_literal(p, "=") != PARSE_OK)
+ return PARSE_ERR;
+
+ const char *valname = parse_identifier_ex(p, true);
+ if (!valname)
+ return parse_error(p, "missing name for enum value");
+
+ parse_literal_optional(p, ";");
+
+ struct enum_value *eval = calloc(1, sizeof(*eval));
+ array_push(&p->ctx->memblist, eval);
+
+ eval->comm.name = valname;
+ eval->comm.loc = p->loc;
+ eval->value = value;
+
+ if (hashmap_insert(&tdef->members, valname, eval))
+ return parse_error(p, "duplicate enum value '%s'", valname);
+ }
+}
+
+int parse_reg_field(struct parser *p, struct type_def *tdef)
+{
+ uint64_t num;
+ int msb = -1, lsb = -1;
+ int status = parse_number(p, &num);
+ if (IS_PARSE_OK(status))
+ msb = num;
+ else if (IS_PARSE_ERR(status))
+ return status;
+ else if (IS_PARSE_EMPTY(status))
+ {
+ if (parse_literal_optional(p, "-") == PARSE_EMPTY)
+ return PARSE_EMPTY;
+
+ if (parse_literal_optional(p, "-") == PARSE_ERR)
+ return PARSE_ERR;
+ }
+
+ status = parse_number(p, &num);
+ if (IS_PARSE_OK(status))
+ lsb = num;
+ else if (IS_PARSE_ERR(status))
+ return status;
+ else if (IS_PARSE_EMPTY(status))
+ {
+ if (parse_literal_optional(p, "-") == PARSE_ERR)
+ return PARSE_ERR;
+
+ if (parse_literal_optional(p, "-") == PARSE_ERR)
+ return PARSE_ERR;
+ }
+
+ if (msb < 0 && lsb < 0)
+ return parse_error(p, "no bits specified for register field");
+ else if (msb < 0)
+ msb = lsb;
+ else if (lsb < 0)
+ lsb = msb;
+
+ const char *fieldname = parse_identifier_ex(p, true);
+ if (!fieldname)
+ return parse_error(p, "expected field name");
+
+ struct reg_field *field = calloc(1, sizeof(*field));
+ array_push(&p->ctx->memblist, field);
+
+ field->comm.name = fieldname;
+ field->comm.loc = p->loc;
+ field->msb = msb;
+ field->lsb = lsb;
+
+ status = parse_member_type(p, &field->comm.type, fieldname, TYPEBIT_ENUM);
+ if (status < 0)
+ return status;
+
+ if (hashmap_insert(&tdef->members, fieldname, field))
+ return parse_error(p, "duplicate register field '%s'", fieldname);
+
+ return 1;
+}
+
+int parse_reg(struct parser *p, struct type_def *tdef)
+{
+ for (;;)
+ {
+ int status = parse_reg_field(p, tdef);
+ if (IS_PARSE_ERR(status))
+ return status;
+ if (IS_PARSE_OK(status))
+ continue;
+
+ const char *keyword = parse_identifier(p);
+ if (!keyword)
+ return PARSE_OK;
+
+ if (!strcmp(keyword, KEYWORD_INCLUDE))
+ {
+ status = parse_include(p, tdef);
+ if (status != PARSE_OK)
+ return status;
+
+ continue;
+ }
+
+ /* otherwise try to parse nested types */
+ status = parse_type_def(p, keyword);
+ if (IS_PARSE_OK(status))
+ continue;
+
+ return status;
+ }
+}
+
+int parse_instance_data(struct parser *p, const char *instname, struct instance **instancep)
+{
+ uint64_t offset;
+ int status = parse_number(p, &offset);
+ if (IS_PARSE_ERR(status))
+ return status;
+ if (IS_PARSE_EMPTY(status))
+ return parse_error(p, "missing offset for '%s'", instname);
+
+ uint64_t count = 1;
+ uint64_t stride = 0;
+ if (parse_literal_optional(p, "[") == PARSE_OK)
+ {
+ status = parse_number(p, &count);
+ if (IS_PARSE_ERR(status))
+ return status;
+ if (IS_PARSE_EMPTY(status))
+ return parse_error(p, "missing instance count");
+ if (count == 0)
+ return parse_error(p, "instance count cannot be 0");
+
+ if (parse_literal(p, ";") != PARSE_OK)
+ return PARSE_ERR;
+
+ status = parse_number(p, &stride);
+ if (IS_PARSE_ERR(status))
+ return status;
+ if (IS_PARSE_EMPTY(status))
+ return parse_error(p, "missing stride");
+ if (stride == 0)
+ return parse_error(p, "stride cannot be 0");
+
+ if (parse_literal(p, "]") != PARSE_OK)
+ return PARSE_ERR;
+ }
+
+ struct instance *instance = calloc(1, sizeof(*instance));
+ array_push(&p->ctx->memblist, instance);
+
+ instance->comm.name = instname;
+ instance->comm.loc = p->loc;
+ instance->offset = offset;
+ instance->stride = stride;
+ instance->count = count;
+
+ status = parse_member_type(p, &instance->comm.type, instname,
+ TYPEBIT_REG | TYPEBIT_BLOCK);
+ if (IS_PARSE_ERR(status))
+ return status;
+ if (IS_PARSE_EMPTY(status))
+ return parse_error(p, "missing type for '%s'", instname);
+
+ *instancep = instance;
+ return PARSE_OK;
+}
+
+static int parse_block(struct parser *p, struct type_def *tdef)
+{
+ for (;;)
+ {
+ int status;
+ const char *instname = parse_identifier_ex(p, true);
+ if (!instname)
+ return PARSE_OK;
+
+ if (!strcmp(instname, KEYWORD_INCLUDE))
+ {
+ status = parse_include(p, tdef);
+ if (status != PARSE_OK)
+ return status;
+
+ continue;
+ }
+
+ if (parse_literal_optional(p, "@") == PARSE_EMPTY)
+ {
+ if (parse_type_def(p, instname) != PARSE_OK)
+ return PARSE_ERR;
+ }
+ else
+ {
+ struct instance *instance;
+ if (parse_instance_data(p, instname, &instance) != PARSE_OK)
+ return PARSE_ERR;
+
+ if (hashmap_insert(&tdef->members, instname, instance))
+ return parse_error(p, "duplicate instance definition");
+ }
+ }
+}
+
+int parse_type_def_ex(struct parser *p,
+ enum type type,
+ enum word_width width,
+ const char *inline_name,
+ struct type_def **tdefp)
+{
+ int status;
+ const char *localname = inline_name;
+ if (!localname)
+ localname = parse_identifier(p);
+ if (!localname)
+ return parse_error(p, "%s definition missing name", type_to_str(type));
+
+ array_push(&p->type_scope, (void *)localname);
+
+ const char *tname = parse_get_scoped_type_name(p, NULL, 0);
+ if (!tname)
+ return PARSE_ERR;
+
+ struct type_def *tdef = type_def_init(p->ctx, tname, type, width);
+ tdef->loc = p->loc;
+
+ if (!inline_name && parse_literal(p, "{") != PARSE_OK)
+ return PARSE_ERR;
+
+ if (type == TYPE_ENUM)
+ status = parse_enum(p, tdef);
+ else if (type == TYPE_REG)
+ status = parse_reg(p, tdef);
+ else if (type == TYPE_BLOCK)
+ status = parse_block(p, tdef);
+ else
+ status = parse_error(p, "internal error: bad type");
+
+ if (status != PARSE_OK)
+ return PARSE_ERR;
+
+ if (parse_literal(p, "}") != PARSE_OK)
+ return PARSE_ERR;
+
+ if (hashmap_insert(&p->ctx->types, tdef->name, tdef))
+ return parse_error(p, "redefinition of type '%s'", tdef->name);
+
+ if (tdefp)
+ *tdefp = tdef;
+
+ array_pop(&p->type_scope);
+ return PARSE_OK;
+}
+
+struct type_def *make_anonymous_reg(struct parser *p, enum word_width width)
+{
+ width = map_word_type(p->ctx, width);
+
+ const char *regname = intern_stringf(p->ctx, "$anon%d", (int)width);
+ struct type_def *tdef = hashmap_lookup(&p->ctx->types, regname);
+ if (!tdef)
+ {
+ tdef = type_def_init(p->ctx, regname, TYPE_REG, width);
+ hashmap_insert(&p->ctx->types, tdef->name, tdef);
+ }
+
+ return tdef;
+}
+
+int parse_inline_type_def(struct parser *p,
+ const char *inline_name,
+ enum inline_type_style style,
+ enum type type,
+ enum word_width width,
+ struct type_def **tdefp)
+{
+ if (style == INLINE_KEYWORD)
+ {
+ if (parse_literal_optional(p, "{") == PARSE_EMPTY)
+ {
+ /* reject bare keyword unless this is a register */
+ if (type != TYPE_REG)
+ return parse_error(p, "missing '{'");
+
+ /* anonymous registers with no fields are allowed */
+ *tdefp = make_anonymous_reg(p, width);
+ return 0;
+ }
+ }
+
+ return parse_type_def_ex(p, type, width, inline_name, tdefp);
+}
+
+int parse_type_def(struct parser *p, const char *keyword)
+{
+ enum type type;
+ enum word_width width;
+ if (parse_lookup_type_keyword(keyword, &type, &width))
+ return parse_type_def_ex(p, type, width, NULL, NULL);
+
+ return parse_error(p, "invalid type '%s'", keyword);
+}
+
+int parse_root_instance(struct parser *p, const char *instname)
+{
+ if (parse_literal(p, "@") != PARSE_OK)
+ return PARSE_ERR;
+
+ struct instance *instance;
+ if (parse_instance_data(p, instname, &instance) != PARSE_OK)
+ return PARSE_ERR;
+
+ array_push(&p->ctx->root_instances, instance);
+ return PARSE_OK;
+}
+
+int parse(struct context *ctx, const char *filename, FILE *input)
+{
+ int rc = PARSE_EMPTY;
+ struct parser p = {
+ .ctx = ctx,
+ .input = input,
+ .loc = {
+ .filename = filename,
+ .line = 1,
+ .column = 0,
+ },
+ };
+
+ for (;;)
+ {
+ const char *ident = parse_identifier(&p);
+ if (!ident)
+ {
+ if (!feof(input))
+ {
+ rc = parse_error(&p, "unexpected character '%c'", fgetc(input));
+ break;
+ }
+
+ rc = PARSE_OK;
+ break;
+ }
+
+ if (parse_lookup_type_keyword(ident, NULL, NULL))
+ {
+ rc = parse_type_def(&p, ident);
+ if (IS_PARSE_ERR(rc))
+ break;
+ }
+ else
+ {
+ rc = parse_root_instance(&p, ident);
+ if (IS_PARSE_ERR(rc))
+ break;
+ }
+ }
+
+ array_free(&p.type_scope);
+ return rc;
+}
diff --git a/tools/reggen_src/parse.h b/tools/reggen_src/parse.h
new file mode 100644
index 0000000000..8802302055
--- /dev/null
+++ b/tools/reggen_src/parse.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#ifndef REGGEN_PARSE_H
+#define REGGEN_PARSE_H
+
+#include
+
+#define PARSE_OK 1
+#define PARSE_EMPTY 0
+#define PARSE_ERR (-1)
+
+struct context;
+
+int parse(struct context *ctx, const char *filename, FILE *input);
+
+#endif /* REGGEN_PARSE_H */
diff --git a/tools/reggen_src/upstream-commit b/tools/reggen_src/upstream-commit
new file mode 100644
index 0000000000..649346dca9
--- /dev/null
+++ b/tools/reggen_src/upstream-commit
@@ -0,0 +1 @@
+0f09935faab28a7e322ae2c4c1b73e9cd1a8f1a7
diff --git a/tools/reggen_src/upstream-sync.sh b/tools/reggen_src/upstream-sync.sh
new file mode 100755
index 0000000000..4f9a091747
--- /dev/null
+++ b/tools/reggen_src/upstream-sync.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+set -eu
+
+UPSTREAM="https://github.com/amachronic/reggen"
+temp_dir="$(mktemp -d)"
+repo_dir="${temp_dir}/reggen"
+dest_dir=$(dirname "$0")
+
+trap 'echo rm -rf "$temp_dir"' EXIT
+
+git clone "$UPSTREAM" "$repo_dir"
+
+rm -f "$dest_dir"/*.[ch] "$dest_dir"/*.md "$dest_dir"/*.txt
+cp "$repo_dir"/src/*.[ch] -t "$dest_dir"
+cp "$repo_dir"/README.md -t "$dest_dir"
+cp "$repo_dir"/LICENSE-GPLv3.txt -t "$dest_dir"
+
+git -C "$repo_dir" rev-parse HEAD > "$dest_dir"/upstream-commit
diff --git a/tools/reggen_src/validate.c b/tools/reggen_src/validate.c
new file mode 100644
index 0000000000..f89ab9d91b
--- /dev/null
+++ b/tools/reggen_src/validate.c
@@ -0,0 +1,129 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#include "validate.h"
+#include "common.h"
+#include
+
+static int sort_enum_members_by_value(const void *e1, const void *e2)
+{
+ const struct enum_value *eval1 = *(const void **)e1;
+ const struct enum_value *eval2 = *(const void **)e2;
+
+ if (eval1->value < eval2->value)
+ return -1;
+ if (eval1->value > eval2->value)
+ return 1;
+
+ return 0;
+}
+
+static int sort_register_fields_by_lsb(const void *f1, const void *f2)
+{
+ const struct reg_field *field1 = *(const void **)f1;
+ const struct reg_field *field2 = *(const void **)f2;
+
+ return field1->lsb - field2->lsb;
+}
+
+static int sort_instances_by_offset(const void *i1, const void *i2)
+{
+ const struct instance *inst1 = *(const void **)i1;
+ const struct instance *inst2 = *(const void **)i2;
+
+ if (inst1->offset < inst2->offset)
+ return -1;
+ if (inst1->offset > inst2->offset)
+ return 1;
+
+ return 0;
+}
+
+static void sort_type_members(struct type_def *tdef)
+{
+ struct type_member *member;
+ HASHMAP_FOREACH(&tdef->members, member)
+ array_push(&tdef->members_sorted, member);
+
+ int (*sort_fn) (const void *, const void *);
+ switch (tdef->type)
+ {
+ case TYPE_ENUM: sort_fn = sort_enum_members_by_value; break;
+ case TYPE_REG: sort_fn = sort_register_fields_by_lsb; break;
+ case TYPE_BLOCK: sort_fn = sort_instances_by_offset; break;
+ default: return;
+ }
+
+ qsort(tdef->members_sorted.data, tdef->members_sorted.size,
+ sizeof(*tdef->members_sorted.data), sort_fn);
+}
+
+static void check_register_fields(struct context *ctx, struct type_def *tdef)
+{
+ /* Check overlap between fields */
+ struct reg_field *field;
+ const char *last_field_name = NULL;
+ int cur_bit = 0;
+ int register_bits = get_word_bits(ctx, tdef->width);
+ ARRAY_FOREACH(&tdef->members_sorted, field)
+ {
+ if (field->lsb < cur_bit)
+ {
+ context_error(ctx, &field->comm.loc, "in %s: field %s overlaps %s",
+ tdef->name, field->comm.name, last_field_name);
+ }
+
+ if (field->msb >= register_bits ||
+ field->lsb >= register_bits)
+ {
+ context_error(ctx, &field->comm.loc, "in %s: field %s doesn't fit in %d-bit register",
+ tdef->name, field->comm.name, register_bits);
+ }
+
+ last_field_name = field->comm.name;
+ cur_bit = field->msb;
+ }
+}
+
+static void count_instance_paths(struct instance *instance)
+{
+ instance->path_count += 1;
+
+ struct type_def *tdef = instance->comm.type;
+ if (tdef->type == TYPE_BLOCK)
+ {
+ struct instance *child_instance;
+ ARRAY_FOREACH(&tdef->members_sorted, child_instance)
+ count_instance_paths(child_instance);
+ }
+}
+
+void validate(struct context *ctx)
+{
+ struct type_def *tdef;
+ HASHMAP_FOREACH(&ctx->types, tdef)
+ {
+ sort_type_members(tdef);
+
+ if (tdef->type == TYPE_REG)
+ check_register_fields(ctx, tdef);
+ }
+
+ struct instance *instance;
+ ARRAY_FOREACH(&ctx->root_instances, instance)
+ count_instance_paths(instance);
+}
diff --git a/tools/reggen_src/validate.h b/tools/reggen_src/validate.h
new file mode 100644
index 0000000000..7a1ad3d7fa
--- /dev/null
+++ b/tools/reggen_src/validate.h
@@ -0,0 +1,25 @@
+/*
+ * This file is part of RegGen -- register definition generator
+ * Copyright (C) 2025 Aidan MacDonald
+ *
+ * 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 .
+ */
+#ifndef REGGEN_VALIDATE_H
+#define REGGEN_VALIDATE_H
+
+struct context;
+
+void validate(struct context *ctx);
+
+#endif /* REGGEN_VALIDATE_H */