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 */