├── .github └── workflows │ ├── docs.yml │ ├── lint.yml │ ├── pypi.yaml │ └── test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docs ├── Makefile ├── advanced.rst ├── api.rst ├── basic.rst ├── conf.py ├── extending.rst ├── helpers.rst ├── images │ ├── Makefile │ ├── logo.fodg │ ├── logo.png │ ├── openflow.dot │ └── openflow.svg ├── index.rst ├── internals.rst ├── intro.rst ├── misc.rst └── tools.rst ├── examples ├── README.md ├── helpers │ ├── diamond.sh │ ├── ise.sh │ ├── libero.sh │ ├── openflow.sh │ ├── quartus.sh │ └── vivado.sh ├── hooks │ ├── diamond.py │ ├── strategies.py │ ├── vivado-elab.py │ └── vivado.py ├── projects │ ├── diamond.py │ ├── ise.py │ ├── libero.py │ ├── openflow.py │ ├── quartus.py │ └── vivado.py └── sources │ ├── cons │ ├── ZYBO │ │ ├── clk.xdc │ │ ├── led.xdc │ │ └── timing.xdc │ ├── arty_a7_35t │ │ ├── clk.xdc │ │ ├── led.xdc │ │ └── timing.xdc │ ├── brevia2 │ │ ├── clk.lpf │ │ └── led.lpf │ ├── de10nano │ │ ├── clk.tcl │ │ ├── led.tcl │ │ └── timing.sdc │ ├── ecp5evn │ │ ├── clk.lpf │ │ └── led.lpf │ ├── edu-ciaa │ │ ├── clk.pcf │ │ └── led.pcf │ ├── icestick │ │ ├── clk.pcf │ │ └── led.pcf │ ├── maker │ │ ├── clk.pdc │ │ ├── led.pdc │ │ └── timing.sdc │ ├── mpfs-disco-kit │ │ ├── clk.pdc │ │ ├── led.pdc │ │ └── timing.sdc │ ├── nexys3 │ │ ├── clk.ucf │ │ ├── led.ucf │ │ └── timing.xcf │ ├── orangecrab │ │ ├── clk.lpf │ │ └── led.lpf │ └── s6micro │ │ ├── clk.ucf │ │ ├── led.ucf │ │ └── timing.xcf │ ├── slog │ ├── blink.sv │ ├── include1 │ │ └── header1.svh │ ├── include2 │ │ └── header2.svh │ └── top.sv │ ├── vhdl │ ├── blink.vhdl │ ├── blink_pkg.vhdl │ └── top.vhdl │ └── vlog │ ├── blink.v │ ├── include1 │ └── header1.vh │ ├── include2 │ └── header2.vh │ └── top.v ├── pyfpga ├── __init__.py ├── diamond.py ├── factory.py ├── helpers │ ├── bitprog.py │ ├── hdl2bit.py │ └── prj2bit.py ├── ise.py ├── libero.py ├── openflow.py ├── project.py ├── quartus.py ├── templates │ ├── diamond-prog.jinja │ ├── diamond.jinja │ ├── ise-prog.jinja │ ├── ise.jinja │ ├── libero-prog.jinja │ ├── libero.jinja │ ├── openflow-prog.jinja │ ├── openflow.jinja │ ├── quartus-prog.jinja │ ├── quartus.jinja │ ├── vivado-prog.jinja │ └── vivado.jinja └── vivado.py ├── setup.py └── tests ├── fakedata ├── cons │ ├── all.xdc │ ├── par.xdc │ └── syn.xdc ├── dir1 │ ├── slog1.sv │ ├── slog1.svh │ ├── vhdl1.vhd │ ├── vhdl1.vhdl │ ├── vlog1.v │ └── vlog1.vh ├── dir2 │ ├── slog2.sv │ ├── slog2.svh │ ├── vhdl2.vhd │ ├── vhdl2.vhdl │ ├── vlog2.v │ └── vlog2.vh ├── dir3 │ ├── slog3.sv │ ├── slog3.svh │ ├── vhdl3.vhd │ ├── vhdl3.vhdl │ ├── vlog3.v │ └── vlog3.vh ├── slog0.sv ├── slog0.svh ├── vhdl0.vhd ├── vhdl0.vhdl ├── vlog0.v └── vlog0.vh ├── mocks ├── FPExpress ├── FPExpress.bat ├── diamondc ├── docker ├── impact ├── impact.bat ├── jtagconfig ├── libero ├── libero.bat ├── pnmainc.bat ├── quartus_pgm ├── quartus_pgm.bat ├── quartus_sh ├── quartus_sh.bat ├── source-me.sh ├── vivado ├── vivado.bat ├── xtclsh └── xtclsh.bat ├── regress.sh ├── support.py ├── test_data.py ├── test_part.py └── test_tools.py /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: 'docs' 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'docs/**' 7 | - 'pyfpga/project.py' 8 | branches: 9 | - main 10 | - dev 11 | 12 | jobs: 13 | docs: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v4 18 | - name: Install dependencies 19 | run: pip install . && pip install sphinx sphinx-rtd-theme 20 | - name: Build documentation 21 | run: make docs 22 | - name: Deploy to GitHub Pages 23 | uses: peaceiris/actions-gh-pages@v4 24 | with: 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | publish_dir: docs/build/html 27 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: 'lint' 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'examples/**' 7 | - 'pyfpga/**' 8 | - 'tests/**' 9 | 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | - name: Install dependencies 17 | run: pip install pycodestyle pylint 18 | - name: Run linting 19 | run: make lint 20 | -------------------------------------------------------------------------------- /.github/workflows/pypi.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Package to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | - name: Set up Python 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.12' 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | pip install setuptools wheel twine 22 | - name: Build package 23 | run: | 24 | python setup.py sdist bdist_wheel 25 | - name: Upload package to PyPI 26 | run: | 27 | twine upload dist/* 28 | env: 29 | TWINE_USERNAME: __token__ 30 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: 'test' 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'examples/projects/**' 7 | - 'pyfpga/**' 8 | - 'tests/**' 9 | 10 | jobs: 11 | test: 12 | strategy: 13 | matrix: 14 | os: ['ubuntu', 'windows'] 15 | pyver: ['3.8', '3.9', '3.10', '3.11', '3.12'] 16 | exclude: 17 | - os: 'windows' 18 | pyver: '3.8' 19 | - os: 'windows' 20 | pyver: '3.9' 21 | - os: 'windows' 22 | pyver: '3.10' 23 | - os: 'windows' 24 | pyver: '3.11' 25 | runs-on: ${{ matrix.os }}-latest 26 | name: ${{ matrix.os }} | ${{ matrix.pyver }} 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@v4 30 | - name: Set up Python ${{ matrix.pyver }} 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: ${{ matrix.pyver }} 34 | - name: Install dependencies 35 | run: pip install . && pip install pytest 36 | - name: Run tests 37 | run: make test 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *build 2 | ignore* 3 | results 4 | venv 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make 2 | 3 | .PHONY: docs 4 | 5 | ifeq ($(OS),Windows_NT) 6 | PATH_SEP := ; 7 | else 8 | PATH_SEP := : 9 | endif 10 | export PATH := $(CURDIR)/tests/mocks$(PATH_SEP)$(PATH) 11 | 12 | all: docs lint test 13 | 14 | docs: 15 | cd docs; make html 16 | 17 | lint: 18 | pycodestyle pyfpga examples tests 19 | pylint -s n pyfpga 20 | git diff --check --cached 21 | 22 | test: 23 | pytest -vv 24 | cd tests && bash regress.sh 25 | 26 | clean: 27 | py3clean . 28 | rm -fr docs/build 29 | rm -fr .pytest_cache 30 | rm -fr `find . -name results` 31 | rm -fr `find . -name __pycache__` 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyFPGA [![License](https://img.shields.io/badge/License-GPL--3.0-darkgreen?style=flat-square)](LICENSE) 2 | 3 | ![Diamond](https://img.shields.io/badge/Diamond-3.13-blue.svg?style=flat-square) 4 | ![ISE](https://img.shields.io/badge/ISE-14.7-blue.svg?style=flat-square) 5 | ![Libero](https://img.shields.io/badge/Libero--Soc-2024.1-blue.svg?style=flat-square) 6 | ![Quartus](https://img.shields.io/badge/Quartus--Prime-23.1-blue.svg?style=flat-square) 7 | ![Vivado](https://img.shields.io/badge/Vivado-2022.1-blue.svg?style=flat-square) 8 | 9 | ![Openflow](https://img.shields.io/badge/Openflow-GHDL%20%7C%20Yosys%20%7C%20nextpnr%20%7C%20icestorm%20%7C%20prjtrellis-darkgreen.svg?style=flat-square) 10 | 11 | PyFPGA is an abstraction layer for working with FPGA development tools in a vendor-agnostic, programmatic way. It is a Python package that provides: 12 | * One **class** per supported tool for **project creation**, **synthesis**, **place and route**, **bitstream generation**, and **programming**. 13 | * A set of **command-line helpers** for simple projects or quick evaluations. 14 | 15 | With PyFPGA, you can create your own FPGA development workflow tailored to your needs! 16 | 17 | Some of its benefits are: 18 | * It provides a unified API between tools/devices. 19 | * It's **Version Control Systems** and **Continuous Integration** friendly. 20 | * It ensures reproducibility and repeatability. 21 | * It consumes fewer system resources than GUI-based workflows. 22 | 23 | ## Basic example 24 | 25 | ```py 26 | from pyfpga import Vivado 27 | 28 | prj = Vivado('example') 29 | prj.set_part('xc7z010-1-clg400') 30 | prj.add_vlog('location1/*.v') 31 | prj.add_vlog('location2/top.v') 32 | prj.add_cons('location3/example.xdc') 33 | prj.set_top('Top') 34 | prj.make() 35 | ``` 36 | 37 | The next steps are to read the [docs](https://pyfpga.github.io/pyfpga) or take a look at [examples](examples). 38 | 39 | ## Support 40 | 41 | PyFPGA is a Python package developed having GNU/Linux platform on mind, but it should run well on any POSIX-compatible OS, and probably others! 42 | If you encounter compatibility issues, please inform us via the [issues](https://github.com/PyFPGA/pyfpga/issues) tracker. 43 | 44 | For a comprehensive list of supported tools, features and limitations, please refer to the [tools support](https://pyfpga.github.io/pyfpga/tools.html) page. 45 | 46 | > **NOTE:** 47 | > PyFPGA assumes that the underlying tools required for operation are ready to be executed from the running terminal. 48 | > This includes having the tools installed, properly configured and licensed (when needed). 49 | 50 | ## Installation 51 | 52 | > **NOTE:** PyFPGA requires Python >= 3.8. 53 | 54 | PyFPGA can be installed in several ways: 55 | 56 | 1. From PyPi using pip: 57 | 58 | ``` 59 | pip install pyfpga 60 | ``` 61 | 62 | 2. From the GitHub repository: 63 | 64 | ``` 65 | pip install 'git+https://github.com/PyFPGA/pyfpga#egg=pyfpga' 66 | ``` 67 | 68 | 3. Clone/download the repository and install it manually: 69 | 70 | ``` 71 | git clone https://github.com/PyFPGA/pyfpga.git 72 | cd pyfpga 73 | pip install -e . 74 | ``` 75 | 76 | > **NOTE:** with `-e` (`--editable`), the application is installed into site-packages via a symlink, which allows you to pull changes through git or switch branches without reinstalling the package. 77 | 78 | ## Similar projects 79 | 80 | * [edalize](https://github.com/olofk/edalize): an abstraction library for interfacing EDA tools. 81 | * Firmware Framework ([FWK](https://gitlab.desy.de/fpgafw/fwk)): set of scripts and functions/procedures that combine all the input files needed to produce build. 82 | * HDL On Git ([Hog](https://gitlab.com/hog-cern/Hog)): a set of Tcl/Shell scripts plus a suitable methodology to handle HDL designs in a GitLab repository. 83 | * [Hdlmake](https://ohwr.org/project/hdl-make): tool for generating multi-purpose makefiles for FPGA projects. 84 | * IPbus Builder ([IPBB](https://github.com/ipbus/ipbb)): a tool for streamlining the synthesis, implementation and simulation of modular firmware projects over multiple platforms. 85 | * [tsfpga](https://github.com/tsfpga/tsfpga): a flexible and scalable development platform for modern FPGA projects. 86 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | SPHINXOPTS ?= 2 | SPHINXBUILD ?= sphinx-build 3 | SOURCEDIR = . 4 | BUILDDIR = build 5 | HELPERS = $(BUILDDIR)/hdl2bit $(BUILDDIR)/prj2bit $(BUILDDIR)/bitprog 6 | 7 | help: 8 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 9 | 10 | .PHONY: help Makefile 11 | 12 | $(HELPERS): 13 | @mkdir -p $(@D) 14 | @python3 ../pyfpga/helpers/$(@F).py -h > $@ 15 | 16 | %: Makefile $(HELPERS) 17 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 18 | -------------------------------------------------------------------------------- /docs/advanced.rst: -------------------------------------------------------------------------------- 1 | Advanced usage 2 | ============== 3 | 4 | The flow implemented by PyFPGA should be sufficient for most cases, but further customizations are possible and discussed in this section. 5 | 6 | Hooks 7 | ----- 8 | 9 | Hooks allow you to insert custom code at specific stages of the project lifecycle. The available hooks are: 10 | 11 | +---------+---------------------------------------------------------------------------------------------+ 12 | | Stage | Description | 13 | +=========+=============================================================================================+ 14 | | precfg | Code inserted after project creation and before files inclusion (e.g., specify HDL version) | 15 | +---------+---------------------------------------------------------------------------------------------+ 16 | | postcfg | Code inserted after files inclusion (e.g., additional project configurations) | 17 | +---------+---------------------------------------------------------------------------------------------+ 18 | | presyn | Code inserted before synthesis (e.g., synthesis-specific options) | 19 | +---------+---------------------------------------------------------------------------------------------+ 20 | | postsyn | Code inserted after synthesis (e.g., report generation) | 21 | +---------+---------------------------------------------------------------------------------------------+ 22 | | prepar | Code inserted before place and route (e.g., place-and-route-specific options) | 23 | +---------+---------------------------------------------------------------------------------------------+ 24 | | postpar | Code inserted after place and route (e.g., report generation) | 25 | +---------+---------------------------------------------------------------------------------------------+ 26 | | prebit | Code inserted before bitstream generation (e.g., bitstream-specific options) | 27 | +---------+---------------------------------------------------------------------------------------------+ 28 | | postbit | Code inserted after bitstream generation (e.g., report generation) | 29 | +---------+---------------------------------------------------------------------------------------------+ 30 | 31 | You can specify hooks for a specific stage either line-by-line: 32 | 33 | .. code-block:: python 34 | 35 | prj.add_hook('presyn', 'COMMAND1') 36 | prj.add_hook('presyn', 'COMMAND2') 37 | prj.add_hook('presyn', 'COMMAND3') 38 | 39 | Or in a multi-line format: 40 | 41 | .. code-block:: python 42 | 43 | prj.add_hook('presyn', """ 44 | COMMAND1 45 | COMMAND2 46 | COMMAND3 47 | """) 48 | 49 | Options 50 | ------- 51 | 52 | Options allow you to specify additional settings to fine-tune certain commands. The available options are: 53 | 54 | .. ATTENTION:: 55 | 56 | This feature is WIP. 57 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | .. automodule:: pyfpga.project 5 | -------------------------------------------------------------------------------- /docs/basic.rst: -------------------------------------------------------------------------------- 1 | Basic usage 2 | =========== 3 | 4 | Project Configuration 5 | --------------------- 6 | 7 | The first steps involve importing the necessary module to support the desired tool and instantiating the corresponding ``class``: 8 | 9 | .. code-block:: python 10 | 11 | from pyfpga.vivado import Vivado 12 | 13 | prj = Vivado('PRJNAME', odir='OUTDIR') 14 | 15 | In the example, we are using Vivado, specifying the optional parameters 16 | ``project name`` (``tool name`` if omitted) and ``output directory`` 17 | (``results`` by default). 18 | 19 | Next step is to specify the target FPGA device: 20 | 21 | .. code-block:: python 22 | 23 | prj.set_part('xc7k160t-3-fbg484') 24 | 25 | .. note:: 26 | 27 | Default parts are provided for each supported tool. 28 | 29 | HDL source files are added using one of the following methods: 30 | 31 | .. code-block:: python 32 | 33 | prj.add_vhdl('PATH_TO_FILES_GLOB_COMPATIBLE', 'OPTIONAL_LIBNAME') 34 | prj.add_vlog('PATH_TO_FILES_GLOB_COMPATIBLE') 35 | prj.add_slog('PATH_TO_FILES_GLOB_COMPATIBLE') 36 | 37 | In these methods, you provide a path to the files. The path can include wildcards (like ``*.vhdl``), allowing you to match multiple files at once. 38 | In case of ``add_vhdl``, you can also optionally specify a library name where the files will be included. 39 | 40 | .. note:: 41 | 42 | Internally, the methods that specify files use `glob`_ to support wildcards and `Path`_ to obtain absolute paths. 43 | 44 | .. _glob: https://docs.python.org/3/library/glob.html 45 | .. _Path: https://docs.python.org/3/library/pathlib.html 46 | 47 | .. hint:: 48 | 49 | Files are processed in the order they are added. If a file is specified more than once, 50 | it is removed from its previous position and placed at the end of the list. 51 | This allows you to ensure that a file is processed after others when necessary 52 | (e.g., placing a top-level at the end) or to customize options 53 | (e.g., removing a VHDL library specification in case of a top-level) 54 | when multiple files are added using a wildcard. 55 | 56 | Generics/parameters can be specified with: 57 | 58 | .. code-block:: python 59 | 60 | prj.add_param('PARAMNAME', 'PARAMVALUE') 61 | 62 | For Verilog and SystemVerilog, the following methods are also available: 63 | 64 | .. code-block:: python 65 | 66 | prj.add_include('PATH_TO_A_DIRECTORY') 67 | prj.add_define('DEFNAME', 'DEFVALUE') 68 | 69 | Constraint source files are included using the following: 70 | 71 | .. code-block:: python 72 | 73 | prj.add_cons('PATH_TO_FILES_GLOB_COMPATIBLE') 74 | 75 | Finally, the top-level can be specified as follows: 76 | 77 | .. code-block:: python 78 | 79 | prj.set_top('Top') 80 | 81 | .. note:: 82 | 83 | The order of the methods described in this section is not significant. 84 | They will be arranged in the required order by the underlying template. 85 | 86 | Bitstream generation 87 | -------------------- 88 | 89 | After configuring the project, you can run the following to generate a bitstream: 90 | 91 | .. code-block:: python 92 | 93 | prj.make() 94 | 95 | By default, this method performs **project creation**, **synthesis**, **place and route**, 96 | and **bitstream generation**. 97 | However, you can optionally specify both the initial and final stages, as follows: 98 | 99 | .. code-block:: python 100 | 101 | prj.make(first='syn', last='par') 102 | 103 | .. note:: 104 | 105 | Valid values are: 106 | 107 | * ``cfg``: generates the project file 108 | * ``syn``: performs synthesis 109 | * ``par``: performs place and route 110 | * ``bit``: performs bitstream generation 111 | 112 | .. note:: 113 | 114 | After executing this method, you will find the file ``.tcl`` 115 | (``.sh`` in some cases) in the output directory. 116 | For debugging purposes, if things do not work as expected, you can review this file. 117 | 118 | Bitstream programming 119 | --------------------- 120 | 121 | The final step is programming the FPGA: 122 | 123 | .. code-block:: python 124 | 125 | prj.prog('BITSTREAM', 'POSITION') 126 | 127 | Both ``BITSTREAM`` and ``POSITION`` are optional. 128 | If ``BITSTREAM`` is not specified, PyFPGA will attempt to discover it based on project information. 129 | The ``POSITION`` parameter is not always required (depends on the tool being used). 130 | 131 | .. note:: 132 | 133 | After executing this method, you will find the file ``prog.tcl`` 134 | (``-prog.sh`` in some cases) in the output directory. 135 | For debugging purposes, if things do not work as expected, you can review this file. 136 | 137 | Debugging 138 | --------- 139 | 140 | Under the hood, `logging`_ is employed. To enable debug messages, you can use: 141 | 142 | .. code-block:: python 143 | 144 | prj.set_debug() 145 | 146 | .. _logging: https://docs.python.org/3/library/logging.html 147 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys, re 4 | from pathlib import Path 5 | 6 | sys.path.insert(0, str(Path.cwd().resolve().parent)) 7 | 8 | # -- Project information ----------------------------------------------------- 9 | 10 | project = 'PyFPGA' 11 | copyright = '2016-2024, PyFPGA Project' 12 | author = 'PyFPGA contributors' 13 | 14 | # -- General configuration --------------------------------------------------- 15 | 16 | extensions = [ 17 | 'sphinx.ext.autodoc', 18 | 'sphinx.ext.extlinks', 19 | 'sphinx.ext.intersphinx', 20 | 'sphinx.ext.todo', 21 | 'sphinx.ext.viewcode', 22 | ] 23 | 24 | autodoc_default_options = { 25 | "members": True, 26 | 'undoc-members': True, 27 | 'inherited-members': True, 28 | } 29 | 30 | extlinks = { 31 | 'repositoy': ('https://github.com/PyFPGA/pyfpga/tree/main/%s', None) 32 | } 33 | 34 | exclude_patterns = ['build'] 35 | 36 | # -- Options for HTML output ------------------------------------------------- 37 | 38 | html_theme = 'sphinx_rtd_theme' 39 | html_static_path = ['images'] 40 | -------------------------------------------------------------------------------- /docs/extending.rst: -------------------------------------------------------------------------------- 1 | Extending 2 | ========= 3 | 4 | .. note:: 5 | 6 | All classes inherit from ``Project`` (``project.py``). 7 | 8 | This is a guide on how to add support for a new TOOL. 9 | 10 | Add support for the new tool 11 | ---------------------------- 12 | 13 | .. code-block:: bash 14 | 15 | pyfpga/templates/.jinja 16 | pyfpga/templates/-prog.jinja 17 | pyfpga/.py 18 | pyfpga/factory.py # UPDATE 19 | pyfpga/helpers/prj2bit.py # UPDATE 20 | 21 | Add tests and tools mock-ups 22 | ---------------------------- 23 | 24 | .. code-block:: bash 25 | 26 | tests/test_tools.py # UPDATE 27 | tests/regress.sh # UPDATE 28 | tests/support.py # UPDATE if exceptions are needed 29 | tests/mocks/ 30 | 31 | Add examples 32 | ------------ 33 | 34 | .. code-block:: bash 35 | 36 | examples/sources/cons//timing. 37 | examples/sources/cons//clk. 38 | examples/sources/cons//led. 39 | examples/projects/.py 40 | examples/helpers/.sh 41 | examples/hooks/.py # OPTIONAL 42 | 43 | Verify the code 44 | --------------- 45 | 46 | Run it at the root of the repo. 47 | 48 | .. code-block:: bash 49 | 50 | make docs 51 | make lint 52 | make test 53 | 54 | .. tip:: 55 | 56 | You can simply run ``make`` to perform all the operations. 57 | Running ``make clean`` will remove all the generated files. 58 | 59 | Verify the functionality 60 | ------------------------ 61 | 62 | .. code-block:: bash 63 | 64 | cd tests 65 | python3 support.py --tool 66 | 67 | Updated the documentation 68 | ------------------------- 69 | 70 | .. code-block:: bash 71 | 72 | README.md 73 | docs/intro.rst 74 | docs/tools.rst 75 | -------------------------------------------------------------------------------- /docs/helpers.rst: -------------------------------------------------------------------------------- 1 | Helpers 2 | ======= 3 | 4 | hdl2bit 5 | ------- 6 | 7 | .. literalinclude:: build/hdl2bit 8 | 9 | prj2bit 10 | ------- 11 | 12 | .. literalinclude:: build/prj2bit 13 | 14 | bitprog 15 | ------- 16 | 17 | .. literalinclude:: build/bitprog 18 | -------------------------------------------------------------------------------- /docs/images/Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make 2 | 3 | FILES = $(wildcard *.dot) 4 | FILES := $(basename $(FILES)) 5 | FILES := $(addsuffix .svg,$(FILES)) 6 | 7 | ODIR = . 8 | 9 | vpath %.svg $(ODIR) 10 | 11 | %.svg: %.dot 12 | @mkdir -p $(ODIR) 13 | dot -Tsvg $< -o $(ODIR)/$@ 14 | 15 | all: $(FILES) 16 | -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/openflow.dot: -------------------------------------------------------------------------------- 1 | digraph openflow { 2 | graph [ranksep=0.25]; 3 | node [shape = doublecircle]; 4 | node [shape = rectangle]; 5 | GHDL "ghdl-yosys-plugin" Yosys "nextpnr-ice40" "nextpnr-ecp5" icetime icepack iceprog eccpack; 6 | node [shape = note ]; 7 | VHDL Verilog; 8 | node [shape = box3d ]; 9 | ice40; 10 | node [shape = oval]; 11 | "bit-ice40" [label=".bit"]; 12 | "bit-ecp5" [label=".bit"]; 13 | VHDL -> {GHDL "ghdl-yosys-plugin"}; 14 | GHDL -> "ghdl-yosys-plugin"; 15 | "ghdl-yosys-plugin" -> Yosys; 16 | Verilog -> Yosys; 17 | Yosys -> ".json"; 18 | ".json" -> {"nextpnr-ice40" "nextpnr-ecp5"}; 19 | "nextpnr-ice40" -> ".asc"; 20 | "nextpnr-ecp5" -> ".config"; 21 | ".asc" -> {icetime icepack}; 22 | icepack -> "bit-ice40"; 23 | "bit-ice40" -> iceprog; 24 | iceprog -> ice40; 25 | ".config" -> eccpack; 26 | eccpack -> "bit-ecp5"; 27 | {rank = same; GHDL ; "ghdl-yosys-plugin"; Yosys;} 28 | } 29 | -------------------------------------------------------------------------------- /docs/images/openflow.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | openflow 11 | 12 | 13 | 14 | GHDL 15 | 16 | GHDL 17 | 18 | 19 | 20 | ghdl-yosys-plugin 21 | 22 | ghdl-yosys-plugin 23 | 24 | 25 | 26 | GHDL->ghdl-yosys-plugin 27 | 28 | 29 | 30 | 31 | 32 | Yosys 33 | 34 | Yosys 35 | 36 | 37 | 38 | ghdl-yosys-plugin->Yosys 39 | 40 | 41 | 42 | 43 | 44 | .json 45 | 46 | .json 47 | 48 | 49 | 50 | Yosys->.json 51 | 52 | 53 | 54 | 55 | 56 | nextpnr-ice40 57 | 58 | nextpnr-ice40 59 | 60 | 61 | 62 | .asc 63 | 64 | .asc 65 | 66 | 67 | 68 | nextpnr-ice40->.asc 69 | 70 | 71 | 72 | 73 | 74 | nextpnr-ecp5 75 | 76 | nextpnr-ecp5 77 | 78 | 79 | 80 | .config 81 | 82 | .config 83 | 84 | 85 | 86 | nextpnr-ecp5->.config 87 | 88 | 89 | 90 | 91 | 92 | icetime 93 | 94 | icetime 95 | 96 | 97 | 98 | icepack 99 | 100 | icepack 101 | 102 | 103 | 104 | bit-ice40 105 | 106 | .bit 107 | 108 | 109 | 110 | icepack->bit-ice40 111 | 112 | 113 | 114 | 115 | 116 | iceprog 117 | 118 | iceprog 119 | 120 | 121 | 122 | ice40 123 | 124 | 125 | 126 | 127 | ice40 128 | 129 | 130 | 131 | iceprog->ice40 132 | 133 | 134 | 135 | 136 | 137 | eccpack 138 | 139 | eccpack 140 | 141 | 142 | 143 | bit-ecp5 144 | 145 | .bit 146 | 147 | 148 | 149 | eccpack->bit-ecp5 150 | 151 | 152 | 153 | 154 | 155 | VHDL 156 | 157 | 158 | 159 | VHDL 160 | 161 | 162 | 163 | VHDL->GHDL 164 | 165 | 166 | 167 | 168 | 169 | VHDL->ghdl-yosys-plugin 170 | 171 | 172 | 173 | 174 | 175 | Verilog 176 | 177 | 178 | 179 | Verilog 180 | 181 | 182 | 183 | Verilog->Yosys 184 | 185 | 186 | 187 | 188 | 189 | bit-ice40->iceprog 190 | 191 | 192 | 193 | 194 | 195 | .json->nextpnr-ice40 196 | 197 | 198 | 199 | 200 | 201 | .json->nextpnr-ecp5 202 | 203 | 204 | 205 | 206 | 207 | .asc->icetime 208 | 209 | 210 | 211 | 212 | 213 | .asc->icepack 214 | 215 | 216 | 217 | 218 | 219 | .config->eccpack 220 | 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | PyFPGA's documentation 2 | ====================== 3 | 4 | .. image:: images/logo.png 5 | :width: 200 px 6 | :align: center 7 | :target: https://github.com/PyFPGA/pyfpga 8 | 9 | .. toctree:: 10 | 11 | intro 12 | basic 13 | advanced 14 | helpers 15 | api 16 | tools 17 | internals 18 | extending 19 | misc 20 | 21 | .. |timestamp| date:: %Y-%m-%d %H:%M (%Z) 22 | 23 | .. note:: 24 | 25 | Documentation generated on |timestamp|. 26 | -------------------------------------------------------------------------------- /docs/internals.rst: -------------------------------------------------------------------------------- 1 | Internals 2 | ========= 3 | 4 | Underlying steps 5 | ---------------- 6 | 7 | .. code-block:: 8 | 9 | project creation [options] 10 | project configuration 11 | part 12 | precfg hook 13 | params 14 | defines 15 | includes 16 | files [options] 17 | top 18 | postcfg hook 19 | project close 20 | 21 | project open 22 | presyn hook 23 | synthesis [options] 24 | postsyn hook 25 | prepar hook 26 | place_and_route [options] 27 | postpar hook 28 | prebit hook 29 | bitstream [options] 30 | postbit hook 31 | project close 32 | 33 | Internal data structure 34 | ----------------------- 35 | 36 | .. code-block:: 37 | 38 | data = { 39 | 'part': 'PARTNAME', 40 | 'includes': ['DIR1', 'DIR2', 'DIR3'], 41 | 'files': { 42 | 'FILE1': {'hdl': 'vhdl', 'lib': 'LIB1', 'opt': 'OPTS'}, 43 | 'FILE2': {'hdl': 'vlog', 'opt': 'OPTS'}, 44 | 'FILE3': {'hdl': 'slog', 'opt': 'OPTS'} 45 | }, 46 | 'top': 'TOPNAME', 47 | 'constraints': { 48 | 'FILE1': {'opt': 'OPTS'}, 49 | 'FILE2': {'opt': 'OPTS'}, 50 | 'FILE3': {'opt': 'OPTS'} 51 | }, 52 | 'params': { 53 | 'PAR1': 'VAL1', 54 | 'PAR2': 'VAL2', 55 | 'PAR3': 'VAL3' 56 | }, 57 | 'defines': { 58 | 'DEF1': 'VAL1', 59 | 'DEF2': 'VAL2', 60 | 'DEF3': 'VAL3' 61 | }, 62 | 'hooks': { 63 | 'precfg': ['CMD1', 'CMD2'], 64 | 'postcfg': ['CMD1', 'CMD2'], 65 | 'presyn': ['CMD1', 'CMD2'], 66 | 'postsyn': ['CMD1', 'CMD2'], 67 | 'prepar': ['CMD1', 'CMD2'], 68 | 'postpar': ['CMD1', 'CMD2'], 69 | 'prebit': ['CMD1', 'CMD2'], 70 | 'postbit': ['CMD1', 'CMD2'] 71 | }, 72 | 'options': { 73 | 'prj': 'OPTS', 74 | 'syn': 'OPTS', 75 | 'pre': 'OPTS', 76 | 'pre': 'OPTS' 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | PyFPGA is a Python package that provides an abstraction layer for working with FPGA development tools in a vendor-agnostic, programmatic way. It includes: 5 | 6 | * A **class** for each supported tool, enabling **project creation**, **synthesis**, **place and route**, **bitstream generation**, and **programming**. 7 | * A set of **command-line** helpers for simple projects or quick evaluations. 8 | 9 | With PyFPGA, you can create your own FPGA development workflow tailored to your needs. Some of its key benefits include: 10 | 11 | * A unified API across different tools and devices. 12 | * Compatibility with *Version Control Systems* and *Continuous Integration*. 13 | * Ensured reproducibility and repeatability. 14 | * Lower resource consumption compared to GUI-based workflows. 15 | 16 | It currently supports vendor tools such as ``Diamond``, ``Ise``, ``Quartus``, ``Libero``, and ``Vivado``, as well as ``Openflow``, a solution based on *Free/Libre and Open Source Software* (**FLOSS**). 17 | 18 | .. ATTENTION:: 19 | 20 | PyFPGA assumes that the backend tool is ready to run. 21 | This implies, depending on the operating system, the following: 22 | 23 | * The tool is installed. 24 | * A valid license, if needed, is configured. 25 | * The tool is available in the system PATH. 26 | * On GNU/Linux: required packages are installed, environment variables are set, and permissions are granted for devices (to transfer the bitstream). 27 | -------------------------------------------------------------------------------- /docs/misc.rst: -------------------------------------------------------------------------------- 1 | Miscellaneous 2 | ============= 3 | 4 | History 5 | ------- 6 | 7 | The origins of this project trace back to 2015, when an in-house, never-released script called **fpga_tools**, written in *Perl*, was used to populate templates for the Xilinx ISE tool. 8 | 9 | In 2016, the project was renamed **fpga_helpers**, transitioning from *Perl* to *Python*. 10 | It was published as open-source on GitHub, adding support for Xilinx Vivado and Altera Quartus, and later for Microsemi (now Microchip) Libero-SoC. 11 | It evolved into a Makefile-based system with two Tcl scripts - one for synthesis and another for programming - both designed to support multiple vendors, along with a few automation scripts. 12 | 13 | By the end of 2019, **PyFPGA** emerged as a complete rewrite, replacing the Makefile and Tcl scripts with a Python-based workflow system. 14 | The project was launched on GitLab, with its first official release (0.1.0) on February 29, 2020 - just before the onset of the COVID-19 pandemic. 15 | 16 | Throughout 2020, support for open-source tools was gradually introduced. 17 | Initially, Yosys was integrated as the synthesizer, while ISE/Vivado handled place-and-route and bitstream generation. 18 | Later, support for VHDL via *ghdl-yosys-plugin* was added, followed by the introduction of OpenFlow - a fully solved with FLOSS tools workflow - at the end of the year. 19 | Additionally, command-line utilities were introduced to simplify working with small projects and simple and quick proof-of-concepts. 20 | 21 | In 2021, PyFPGA was migrated from GitLab to GitHub, aligning with the broader FPGA-related FLOSS ecosystem. 22 | That year, the codebase was significantly expanded and improved, but it also became more complex to maintain. 23 | This led to the release of version 0.2.0 on May 15, 2022, marking a new starting point. 24 | 25 | Between 2023 and 2024, the project underwent a major rewrite, incorporating substantial improvements. 26 | In 2024, support for a new vendor tool, Diamond, was contributed. 27 | As a result, a new release is taking place in March 2025. 28 | -------------------------------------------------------------------------------- /docs/tools.rst: -------------------------------------------------------------------------------- 1 | Tools 2 | ===== 3 | 4 | .. list-table:: Default PyFPGA's parts per tool 5 | :header-rows: 1 6 | 7 | * - Tool 8 | - Vendor 9 | - Default device 10 | - Name format 11 | * - Diamond 12 | - Lattice 13 | - LFXP2-5E-5TN144C 14 | - device-speed-package 15 | * - ISE 16 | - Xilinx 17 | - XC7K160T-3-FBG484 18 | - device-speed-package 19 | * - Libero 20 | - Microchip/Microsemi 21 | - MPF100T-1-FCG484 22 | - device-speed-package 23 | * - Openflow 24 | - FLOSS 25 | - HX8K-CT256 26 | - device-package 27 | * - Quartus 28 | - Intel/Altera 29 | - 10M50SCE144I7G 30 | - part 31 | * - Vivado 32 | - AMD/Xilinx 33 | - XC7K160T-3-FBG484 34 | - device-speed-package 35 | 36 | Diamond 37 | ------- 38 | 39 | `Diamond downloads `_ 40 | 41 | Diamond is the previous generation EDA tool from Lattice. 42 | 43 | Example: 44 | 45 | .. code:: 46 | 47 | from pyfpga.diamond import Diamond 48 | 49 | prj = Diamond() 50 | 51 | ISE 52 | --- 53 | 54 | `ISE downloads `_ 55 | 56 | ISE (*Integrated Software Environment*) is the previous Xilinx's EDA, superseded by Vivado. 57 | The last version is ISE 14.7, launched in October 2013. 58 | It supports devices starting from Spartan 3/Virtex 4 until some of the first members of the 7 series (all the 7 series and above are supported by Vivado). 59 | Previous Spartan/Virtex devices were supported until version 10. 60 | 61 | .. attention:: 62 | 63 | ISE supports Verilog 2001 and VHDL 1993, but not SystemVerilog. 64 | 65 | Example: 66 | 67 | .. code:: 68 | 69 | from pyfpga.ise import Ise 70 | 71 | prj = Ise() 72 | 73 | Valid PART formats: 74 | 75 | .. code:: 76 | 77 | -- 78 | -- 79 | 80 | Libero 81 | ------ 82 | 83 | `Libero downloads `_ 84 | 85 | Libero-SoC (Microsemi, acquired by Microchip in 2018) is the evolution of Libero-IDE (Actel, acquired by Microsemi in 2010). 86 | PyFPGA supports Libero-SoC starting from 12.0, which supports most modern families. 87 | For other devices, Libero-SoC 11.9 or Libero-IDE v9.2 are needed, but these versions are not supported by PyFPGA. 88 | 89 | Example: 90 | 91 | .. code:: 92 | 93 | from pyfpga.libero import Libero 94 | 95 | prj = Libero() 96 | 97 | Valid PART formats: 98 | 99 | .. code:: 100 | 101 | - 102 | - 103 | -- 104 | -- 105 | - 106 | - 107 | -- 108 | -- 109 | 110 | Openflow 111 | -------- 112 | 113 | `Docker downloads `_ 114 | 115 | Openflow is the combination of different Free/Libre and Open Source (FLOSS) tools: 116 | 117 | .. image:: images/openflow.svg 118 | :width: 70% 119 | :align: center 120 | 121 | * Yosys for synthesis, with ghdl-yosys-plugin for VHDL support. 122 | * nextpnr in its ice40 and ecp5 versions. 123 | * Projects icestorm and Trellis. 124 | 125 | It relies on Docker and fine-grain containers. 126 | 127 | .. attention:: 128 | 129 | It is currently the only flow not solved using Tcl (it uses docker in a bash script instead). 130 | 131 | Example: 132 | 133 | .. code:: 134 | 135 | from pyfpga.openflow import Openflow 136 | 137 | prj = Openflow() 138 | 139 | Valid PART formats: 140 | 141 | .. code:: 142 | 143 | - 144 | 145 | Quartus 146 | ------- 147 | 148 | `Quartus downloads `_ 149 | 150 | Quartus Prime (Intel) is the continuation of Quartus II (Altera) and is divided into the Pro, Standard, and Lite editions, each supporting different families. 151 | 152 | Example: 153 | 154 | .. code:: 155 | 156 | from pyfpga.quartus import Quartus 157 | 158 | prj = Quartus() 159 | 160 | Vivado 161 | ------ 162 | 163 | `Vivado downloads `_ 164 | 165 | Vivado is the current EDA tool from Xilinx, which has superseded ISE and supports the 7 series and above. 166 | It is included with Vitis, the SDK for embedded applications. 167 | 168 | Example: 169 | 170 | .. code:: 171 | 172 | from pyfpga.vivado import Vivado 173 | 174 | prj = Vivado() 175 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # PyFPGA Examples 2 | 3 | In this section, you will find: 4 | 5 | * `projects`: basic but complete examples for each supported tool. 6 | * `helpers`: examples of the PyFPGA helpers. 7 | * `hooks`: how to use this feature. 8 | 9 | For an example where all the tools are employed based on the same code, you can check 10 | [support.py](../tests/support.py) (located under the [tests](../tests) directory). 11 | -------------------------------------------------------------------------------- /examples/helpers/diamond.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | HDIR=../../pyfpga/helpers 6 | 7 | python3 $HDIR/hdl2bit.py -t diamond -o results/diamond-vlog -p lfxp2-5e-5tn144c \ 8 | -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ 9 | -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ 10 | -f ../sources/cons/brevia2/clk.lpf -f ../sources/cons/brevia2/led.lpf \ 11 | --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top 12 | 13 | python3 $HDIR/hdl2bit.py -t diamond -o results/diamond-vhdl -p lfxp2-5e-5tn144c --project example \ 14 | -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ 15 | -f ../sources/cons/brevia2/clk.lpf -f ../sources/cons/brevia2/led.lpf \ 16 | --param FREQ 125000000 --param SECS 1 --last cfg Top 17 | 18 | python3 $HDIR/prj2bit.py results/diamond-vhdl/example.ldf 19 | 20 | # Diamond programming is not yet supported 21 | -------------------------------------------------------------------------------- /examples/helpers/ise.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | HDIR=../../pyfpga/helpers 6 | 7 | python3 $HDIR/hdl2bit.py -t ise -o results/ise-vlog -p xc6slx16-3-csg32 \ 8 | -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ 9 | -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ 10 | -f ../sources/cons/nexys3/timing.xcf -f ../sources/cons/nexys3/clk.ucf -f ../sources/cons/nexys3/led.ucf \ 11 | --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top 12 | 13 | python3 $HDIR/hdl2bit.py -t ise -o results/ise-vhdl -p xc6slx16-3-csg32 --project example \ 14 | -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ 15 | -f ../sources/cons/nexys3/timing.xcf -f ../sources/cons/nexys3/clk.ucf -f ../sources/cons/nexys3/led.ucf \ 16 | --param FREQ 125000000 --param SECS 1 --last cfg Top 17 | 18 | python3 $HDIR/prj2bit.py results/ise-vhdl/example.xise 19 | 20 | python3 $HDIR/bitprog.py -t ise results/ise-vhdl/example.bit 21 | -------------------------------------------------------------------------------- /examples/helpers/libero.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | HDIR=../../pyfpga/helpers 6 | 7 | python3 $HDIR/hdl2bit.py -t libero -o results/libero-vlog -p m2s010-1-tq144 \ 8 | -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ 9 | -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ 10 | -f ../sources/cons/maker/timing.sdc -f ../sources/cons/maker/clk.pdc -f ../sources/cons/maker/led.pdc \ 11 | --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top 12 | 13 | python3 $HDIR/hdl2bit.py -t libero -o results/libero-vhdl -p m2s010-1-tq144 --project example \ 14 | -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ 15 | -f ../sources/cons/maker/timing.sdc -f ../sources/cons/maker/clk.pdc -f ../sources/cons/maker/led.pdc \ 16 | --param FREQ 125000000 --param SECS 1 --last cfg Top 17 | 18 | python3 $HDIR/prj2bit.py results/libero-vhdl/libero/example.prjx 19 | 20 | python3 $HDIR/bitprog.py -t libero results/libero-vhdl/example.ppd 21 | -------------------------------------------------------------------------------- /examples/helpers/openflow.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | HDIR=../../pyfpga/helpers 6 | 7 | python3 $HDIR/hdl2bit.py -t openflow -o results/openflow-vlog -p hx1k-tq144 \ 8 | -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ 9 | -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ 10 | -f ../sources/cons/icestick/clk.pcf -f ../sources/cons/icestick/led.pcf \ 11 | --define DEFINE1 1 --define DEFINE2 1 --param FREQ 100000000 --param SECS 1 Top 12 | 13 | python3 $HDIR/hdl2bit.py -t openflow -o results/openflow-vhdl -p hx1k-tq144 --project example \ 14 | -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ 15 | -f ../sources/cons/icestick/clk.pcf -f ../sources/cons/icestick/led.pcf \ 16 | --param FREQ 125000000 --param SECS 1 --last syn Top 17 | 18 | # OpenFlow doesn't have a project file, so it is not supported by prj2bit 19 | 20 | python3 $HDIR/bitprog.py -t openflow results/openflow-vhdl/openflow.bit 21 | -------------------------------------------------------------------------------- /examples/helpers/quartus.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | HDIR=../../pyfpga/helpers 6 | 7 | python3 $HDIR/hdl2bit.py -t quartus -o results/quartus-vlog -p 5CSEBA6U23I7 \ 8 | -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ 9 | -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ 10 | -f ../sources/cons/de10nano/timing.sdc -f ../sources/cons/de10nano/clk.tcl -f ../sources/cons/de10nano/led.tcl \ 11 | --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top 12 | 13 | python3 $HDIR/hdl2bit.py -t quartus -o results/quartus-vhdl -p 5CSEBA6U23I7 --project example \ 14 | -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ 15 | -f ../sources/cons/de10nano/timing.sdc -f ../sources/cons/de10nano/clk.tcl -f ../sources/cons/de10nano/led.tcl \ 16 | --param FREQ 125000000 --param SECS 1 --last cfg Top 17 | 18 | python3 $HDIR/prj2bit.py results/quartus-vhdl/example.qpf 19 | 20 | python3 $HDIR/bitprog.py -t quartus results/quartus-vhdl/example.sof 21 | -------------------------------------------------------------------------------- /examples/helpers/vivado.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | HDIR=../../pyfpga/helpers 6 | 7 | python3 $HDIR/hdl2bit.py -t vivado -o results/vivado-vlog -p xc7z010-1-clg400 \ 8 | -i ../sources/vlog/include1 -i ../sources/vlog/include2 \ 9 | -f ../sources/vlog/blink.v -f ../sources/vlog/top.v \ 10 | -f ../sources/cons/ZYBO/timing.xdc -f ../sources/cons/ZYBO/clk.xdc -f ../sources/cons/ZYBO/led.xdc \ 11 | --define DEFINE1 1 --define DEFINE2 1 --param FREQ 125000000 --param SECS 1 Top 12 | 13 | python3 $HDIR/hdl2bit.py -t vivado -o results/vivado-vhdl -p xc7z010-1-clg400 --project example \ 14 | -f ../sources/vhdl/blink.vhdl,blink_lib -f ../sources/vhdl/blink_pkg.vhdl,blink_lib -f ../sources/vhdl/top.vhdl \ 15 | -f ../sources/cons/ZYBO/timing.xdc -f ../sources/cons/ZYBO/clk.xdc -f ../sources/cons/ZYBO/led.xdc \ 16 | --param FREQ 125000000 --param SECS 1 --last cfg Top 17 | 18 | python3 $HDIR/prj2bit.py results/vivado-vhdl/example.xpr 19 | 20 | python3 $HDIR/bitprog.py -t vivado results/vivado-vhdl/example.bit 21 | -------------------------------------------------------------------------------- /examples/hooks/diamond.py: -------------------------------------------------------------------------------- 1 | """Diamond hooks examples.""" 2 | 3 | from pyfpga.diamond import Diamond 4 | 5 | prj = Diamond() 6 | 7 | hooks = { 8 | "reports": """ 9 | prj_run Map -task MapTrace -forceOne 10 | prj_run PAR -task PARTrace -forceOne 11 | prj_run PAR -task IOTiming -forceOne 12 | """, 13 | 14 | "netlist_simulation": """ 15 | prj_run Map -task MapVerilogSimFile 16 | prj_run Map -task MapVHDLSimFile -forceOne 17 | prj_run Export -task TimingSimFileVHD -forceOne 18 | prj_run Export -task TimingSimFileVlg -forceOne 19 | prj_run Export -task IBIS -forceOne 20 | """, 21 | 22 | "progfile_ecp5u": """ 23 | prj_run Export -task Promgen -forceOne 24 | """, 25 | 26 | "progfile_machxo2": """ 27 | prj_run Export -task Jedecgen -forceOne 28 | """ 29 | } 30 | 31 | prj.set_part('LFXP2-5E-5TN144C') 32 | 33 | prj.add_param('FREQ', '50000000') 34 | prj.add_param('SECS', '1') 35 | 36 | prj.add_cons('../sources/cons/brevia2/clk.lpf') 37 | prj.add_cons('../sources/cons/brevia2/led.lpf') 38 | 39 | prj.add_include('../sources/vlog/include1') 40 | prj.add_include('../sources/vlog/include2') 41 | prj.add_vlog('../sources/vlog/*.v') 42 | 43 | prj.add_define('DEFINE1', '1') 44 | prj.add_define('DEFINE2', '1') 45 | 46 | prj.set_top('Top') 47 | 48 | for hook_name, hook in hooks.items(): 49 | prj.add_hook('postpar', hook) 50 | 51 | prj.make() 52 | -------------------------------------------------------------------------------- /examples/hooks/strategies.py: -------------------------------------------------------------------------------- 1 | """PyFPGA Multi Vendor Strategy example. 2 | 3 | The main idea of a multi-vendor project is to implements the same HDL code 4 | with different tools, to make comparisons. The project name is not important 5 | and the default devices can be used. In this example, strategies are changed 6 | between area, power and speed. 7 | """ 8 | 9 | import logging 10 | 11 | from fpga.project import Project 12 | 13 | logging.basicConfig() 14 | 15 | commands = { 16 | 'ise': { 17 | 'area': """ 18 | project set "Optimization Goal" "Area" 19 | """, 20 | 'power': """ 21 | project set "Optimization Goal" "Area" 22 | project set "Power Reduction" "true" -process "Synthesize - XST" 23 | project set "Power Reduction" "high" -process "Map" 24 | project set "Power Reduction" "true" -process "Place & Route" 25 | """, 26 | 'speed': """ 27 | project set "Optimization Goal" "Speed" 28 | """ 29 | }, 30 | 'libero': { 31 | 'area': """ 32 | configure_tool -name {SYNTHESIZE} -params {RAM_OPTIMIZED_FOR_POWER:true} 33 | """, 34 | 'power': """ 35 | configure_tool -name {SYNTHESIZE} -params {RAM_OPTIMIZED_FOR_POWER:true} 36 | configure_tool -name {PLACEROUTE} -params {PDPR:true} 37 | """, 38 | 'speed': """ 39 | configure_tool -name {SYNTHESIZE} -params {RAM_OPTIMIZED_FOR_POWER:false} 40 | configure_tool -name {PLACEROUTE} -params {EFFORT_LEVEL:true} 41 | """ 42 | }, 43 | 'quartus': { 44 | 'area': """ 45 | set_global_assignment -name OPTIMIZATION_MODE "AGGRESSIVE AREA" 46 | set_global_assignment -name OPTIMIZATION_TECHNIQUE AREA 47 | """, 48 | 'power': """ 49 | set_global_assignment -name OPTIMIZATION_MODE "AGGRESSIVE POWER" 50 | set_global_assignment -name OPTIMIZE_POWER_DURING_SYNTHESIS "EXTRA EFFORT" 51 | set_global_assignment -name OPTIMIZE_POWER_DURING_FITTING "EXTRA EFFORT" 52 | """, 53 | 'speed': """ 54 | set_global_assignment -name OPTIMIZATION_MODE "AGGRESSIVE PERFORMANCE" 55 | set_global_assignment -name OPTIMIZATION_TECHNIQUE SPEED 56 | """ 57 | }, 58 | 'vivado': { 59 | 'area': """ 60 | set obj [get_runs synth_1] 61 | set_property strategy "Flow_AreaOptimized_high" $obj 62 | set_property "steps.synth_design.args.directive" "AreaOptimized_high" $obj 63 | set_property "steps.synth_design.args.control_set_opt_threshold" "1" $obj 64 | set obj [get_runs impl_1] 65 | set_property strategy "Area_Explore" $obj 66 | set_property "steps.opt_design.args.directive" "ExploreArea" $obj 67 | """, 68 | 'power': """ 69 | #enable power_opt_design and phys_opt_design 70 | set obj [get_runs synth_1] 71 | set_property strategy "Vivado Synthesis Defaults" $obj 72 | set obj [get_runs impl_1] 73 | set_property strategy "Power_DefaultOpt" $obj 74 | set_property "steps.power_opt_design.is_enabled" "1" $obj 75 | set_property "steps.phys_opt_design.is_enabled" "1" $obj 76 | """, 77 | 'speed': """ 78 | #enable phys_opt_design 79 | set obj [get_runs synth_1] 80 | set_property strategy "Flow_PerfOptimized_high" $obj 81 | set_property "steps.synth_design.args.fanout_limit" "400" $obj 82 | set_property "steps.synth_design.args.keep_equivalent_registers" "1" $obj 83 | set_property "steps.synth_design.args.resource_sharing" "off" $obj 84 | set_property "steps.synth_design.args.no_lc" "1" $obj 85 | set_property "steps.synth_design.args.shreg_min_size" "5" $obj 86 | set obj [get_runs impl_1] 87 | set_property strategy "Performance_Explore" $obj 88 | set_property "steps.opt_design.args.directive" "Explore" $obj 89 | set_property "steps.place_design.args.directive" "Explore" $obj 90 | set_property "steps.phys_opt_design.is_enabled" "1" $obj 91 | set_property "steps.phys_opt_design.args.directive" "Explore" $obj 92 | set_property "steps.route_design.args.directive" "Explore" $obj 93 | """ 94 | } 95 | } 96 | 97 | for tool in commands: 98 | for strategy in commands[tool]: 99 | PRJ = Project(tool) 100 | PRJ.set_outdir('../../build/hooks/%s-%s' % (tool, strategy)) 101 | PRJ.add_files('../../hdl/blinking.vhdl') 102 | PRJ.set_top('Blinking') 103 | PRJ.add_hook( 104 | 'puts "Appling {} optimizations"'.format(strategy), 105 | 'project' 106 | ) 107 | PRJ.add_hook(commands[tool][strategy], 'project') 108 | PRJ.generate(to_task='syn') 109 | -------------------------------------------------------------------------------- /examples/hooks/vivado-elab.py: -------------------------------------------------------------------------------- 1 | """Vivado elaboration example.""" 2 | 3 | from pyfpga.vivado import Vivado 4 | 5 | 6 | prj = Vivado() 7 | 8 | prj.set_part('xc7z010-1-clg400') 9 | 10 | prj.add_param('FREQ', '125000000') 11 | prj.add_param('SECS', '1') 12 | prj.add_define('DEFINE1', '1') 13 | prj.add_define('DEFINE2', '1') 14 | 15 | prj.add_include('../sources/slog/include1') 16 | prj.add_include('../sources/slog/include2') 17 | prj.add_slog('../sources/slog/*.sv') 18 | 19 | prj.set_top('Top') 20 | 21 | prj.add_hook('presyn', 'synth_design -rtl -rtl_skip_mlo; exit 0') 22 | 23 | prj.make() 24 | -------------------------------------------------------------------------------- /examples/hooks/vivado.py: -------------------------------------------------------------------------------- 1 | """Vivado hooks examples.""" 2 | 3 | from pathlib import Path 4 | from pyfpga.vivado import Vivado 5 | 6 | 7 | prj = Vivado() 8 | 9 | prj.set_part('xc7z010-1-clg400') 10 | 11 | prj.add_param('FREQ', '125000000') 12 | prj.add_param('SECS', '1') 13 | prj.add_define('DEFINE1', '1') 14 | prj.add_define('DEFINE2', '1') 15 | 16 | prj.add_include('../sources/slog/include1') 17 | prj.add_include('../sources/slog/include2') 18 | prj.add_slog('../sources/slog/*.sv') 19 | 20 | prj.add_cons('../sources/cons/ZYBO/timing.xdc') 21 | prj.add_cons('../sources/cons/ZYBO/clk.xdc') 22 | prj.add_cons('../sources/cons/ZYBO/led.xdc') 23 | 24 | prj.set_top('Top') 25 | 26 | prj.add_hook('precfg', ''' 27 | set obj [get_runs synth_1] 28 | set_property strategy "Flow_AreaOptimized_high" $obj 29 | set_property "steps.synth_design.args.directive" "AreaOptimized_high" $obj 30 | set_property "steps.synth_design.args.control_set_opt_threshold" "1" $obj 31 | set obj [get_runs impl_1] 32 | set_property strategy "Area_Explore" $obj 33 | set_property "steps.opt_design.args.directive" "ExploreArea" $obj 34 | ''') 35 | 36 | place = ['../sources/cons/ZYBO/clk.xdc', '../sources/cons/ZYBO/led.xdc'] 37 | prj.add_hook('postcfg', f''' 38 | set_property USED_IN_SYNTHESIS FALSE [get_files {Path(place[0]).resolve()}] 39 | set_property USED_IN_SYNTHESIS FALSE [get_files {Path(place[1]).resolve()}] 40 | ''') 41 | 42 | prj.make() 43 | -------------------------------------------------------------------------------- /examples/projects/diamond.py: -------------------------------------------------------------------------------- 1 | """Diamond examples.""" 2 | 3 | import argparse 4 | 5 | from pyfpga.diamond import Diamond 6 | 7 | 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument( 10 | '--board', choices=['brevia2'], default='brevia2' 11 | ) 12 | parser.add_argument( 13 | '--source', choices=['vlog', 'vhdl', 'slog'], default='vlog' 14 | ) 15 | parser.add_argument( 16 | '--action', choices=['make', 'prog', 'all'], default='make' 17 | ) 18 | args = parser.parse_args() 19 | 20 | prj = Diamond(odir=f'results/diamond/{args.source}/{args.board}') 21 | 22 | if args.board == 'brevia2': 23 | prj.set_part('LFXP2-5E-5TN144C') 24 | prj.add_param('FREQ', '50000000') 25 | prj.add_cons('../sources/cons/brevia2/clk.lpf') 26 | prj.add_cons('../sources/cons/brevia2/led.lpf') 27 | prj.add_param('SECS', '1') 28 | 29 | if args.source == 'vhdl': 30 | prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') 31 | prj.add_vhdl('../sources/vhdl/top.vhdl') 32 | if args.source == 'vlog': 33 | prj.add_include('../sources/vlog/include1') 34 | prj.add_include('../sources/vlog/include2') 35 | prj.add_vlog('../sources/vlog/*.v') 36 | if args.source == 'slog': 37 | prj.add_include('../sources/slog/include1') 38 | prj.add_include('../sources/slog/include2') 39 | prj.add_slog('../sources/slog/*.sv') 40 | if args.source in ['vlog', 'slog']: 41 | prj.add_define('DEFINE1', '1') 42 | prj.add_define('DEFINE2', '1') 43 | 44 | prj.set_top('Top') 45 | 46 | if args.action in ['make', 'all']: 47 | prj.make() 48 | if args.action in ['prog', 'all']: 49 | prj.prog() 50 | -------------------------------------------------------------------------------- /examples/projects/ise.py: -------------------------------------------------------------------------------- 1 | """ISE examples.""" 2 | 3 | import argparse 4 | 5 | from pyfpga.ise import Ise 6 | 7 | 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument( 10 | '--board', choices=['s6micro', 'nexys3'], default='s6micro' 11 | ) 12 | parser.add_argument( 13 | '--source', choices=['vlog', 'vhdl'], default='vlog' 14 | ) 15 | parser.add_argument( 16 | '--action', choices=['make', 'prog', 'all'], default='make' 17 | ) 18 | args = parser.parse_args() 19 | 20 | prj = Ise(odir=f'results/ise/{args.source}/{args.board}') 21 | 22 | if args.board == 's6micro': 23 | prj.set_part('xc6slx9-2-csg324') 24 | prj.add_param('FREQ', '125000000') 25 | prj.add_cons('../sources/cons/s6micro/timing.xcf') 26 | prj.add_cons('../sources/cons/s6micro/clk.ucf') 27 | prj.add_cons('../sources/cons/s6micro/led.ucf') 28 | if args.board == 'nexys3': 29 | prj.set_part('xc6slx16-3-csg32') 30 | prj.add_param('FREQ', '100000000') 31 | prj.add_cons('../sources/cons/nexys3/timing.xcf') 32 | prj.add_cons('../sources/cons/nexys3/clk.ucf') 33 | prj.add_cons('../sources/cons/nexys3/led.ucf') 34 | prj.add_param('SECS', '1') 35 | 36 | if args.source == 'vhdl': 37 | prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') 38 | prj.add_vhdl('../sources/vhdl/top.vhdl') 39 | if args.source == 'vlog': 40 | prj.add_include('../sources/vlog/include1') 41 | prj.add_include('../sources/vlog/include2') 42 | prj.add_vlog('../sources/vlog/*.v') 43 | prj.add_define('DEFINE1', '1') 44 | prj.add_define('DEFINE2', '1') 45 | 46 | prj.set_top('Top') 47 | 48 | if args.action in ['make', 'all']: 49 | prj.make() 50 | if args.action in ['prog', 'all']: 51 | prj.prog() 52 | -------------------------------------------------------------------------------- /examples/projects/libero.py: -------------------------------------------------------------------------------- 1 | """Libero examples.""" 2 | 3 | import argparse 4 | 5 | from pyfpga.libero import Libero 6 | 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument( 9 | '--board', choices=['mpfs-disco-kit', 'maker'], default='mpfs-disco-kit' 10 | ) 11 | parser.add_argument( 12 | '--source', choices=['vlog', 'vhdl', 'slog'], default='vlog' 13 | ) 14 | parser.add_argument( 15 | '--action', choices=['make', 'prog', 'all'], default='make' 16 | ) 17 | args = parser.parse_args() 18 | 19 | prj = Libero(odir=f'results/libero/{args.source}/{args.board}') 20 | 21 | 22 | if args.board == 'mpfs-disco-kit': 23 | prj.set_part('MPFS095T-1-FCSG325E') 24 | prj.add_param('FREQ', '50000000') 25 | prj.add_cons('../sources/cons/mpfs-disco-kit/timing.sdc') 26 | prj.add_cons('../sources/cons/mpfs-disco-kit/clk.pdc') 27 | prj.add_cons('../sources/cons/mpfs-disco-kit/led.pdc') 28 | if args.board == 'maker': 29 | prj.set_part('m2s010-1-tq144') 30 | prj.add_param('FREQ', '125000000') 31 | prj.add_cons('../sources/cons/maker/timing.sdc') 32 | prj.add_cons('../sources/cons/maker/clk.pdc') 33 | prj.add_cons('../sources/cons/maker/led.pdc') 34 | prj.add_param('SECS', '1') 35 | 36 | if args.source == 'vhdl': 37 | prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') 38 | prj.add_vhdl('../sources/vhdl/top.vhdl') 39 | if args.source == 'vlog': 40 | prj.add_include('../sources/vlog/include1') 41 | prj.add_include('../sources/vlog/include2') 42 | prj.add_vlog('../sources/vlog/*.v') 43 | if args.source == 'slog': 44 | prj.add_include('../sources/slog/include1') 45 | prj.add_include('../sources/slog/include2') 46 | prj.add_vlog('../sources/slog/*.sv') 47 | if args.source in ['vlog', 'slog']: 48 | prj.add_define('DEFINE1', '1') 49 | prj.add_define('DEFINE2', '1') 50 | 51 | prj.set_top('Top') 52 | 53 | if args.action in ['make', 'all']: 54 | prj.make() 55 | if args.action in ['prog', 'all']: 56 | prj.prog() 57 | -------------------------------------------------------------------------------- /examples/projects/openflow.py: -------------------------------------------------------------------------------- 1 | """Openflow examples.""" 2 | 3 | import argparse 4 | 5 | from pyfpga.openflow import Openflow 6 | 7 | 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument( 10 | '--board', choices=['icestick', 'edu-ciaa', 'orangecrab', 'ecp5evn'], 11 | default='icestick' 12 | ) 13 | parser.add_argument( 14 | '--source', choices=['vlog', 'vhdl', 'slog'], default='vlog' 15 | ) 16 | parser.add_argument( 17 | '--action', choices=['make', 'prog', 'all'], default='make' 18 | ) 19 | args = parser.parse_args() 20 | 21 | prj = Openflow(odir=f'results/openflow/{args.source}/{args.board}') 22 | 23 | if args.board == 'icestick': 24 | prj.set_part('hx1k-tq144') 25 | prj.add_param('FREQ', '12000000') 26 | prj.add_cons('../sources/cons/icestick/clk.pcf') 27 | prj.add_cons('../sources/cons/icestick/led.pcf') 28 | if args.board == 'edu-ciaa': 29 | prj.set_part('hx4k-tq144') 30 | prj.add_param('FREQ', '12000000') 31 | prj.add_cons('../sources/cons/edu-ciaa/clk.pcf') 32 | prj.add_cons('../sources/cons/edu-ciaa/led.pcf') 33 | if args.board == 'orangecrab': 34 | prj.set_part('25k-CSFBGA285') 35 | prj.add_param('FREQ', '100000000') 36 | prj.add_cons('../sources/cons/orangecrab/clk.lpf') 37 | prj.add_cons('../sources/cons/orangecrab/led.lpf') 38 | if args.board == 'ecp5evn': 39 | prj.set_part('um5g-85k-CABGA381') 40 | prj.add_param('FREQ', '100000000') 41 | prj.add_cons('../sources/cons/ecp5evn/clk.lpf') 42 | prj.add_cons('../sources/cons/ecp5evn/led.lpf') 43 | prj.add_param('SECS', '1') 44 | 45 | if args.source == 'vhdl': 46 | prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') 47 | prj.add_vhdl('../sources/vhdl/top.vhdl') 48 | if args.source == 'vlog': 49 | prj.add_include('../sources/vlog/include1') 50 | prj.add_include('../sources/vlog/include2') 51 | prj.add_vlog('../sources/vlog/*.v') 52 | if args.source == 'slog': 53 | prj.add_include('../sources/slog/include1') 54 | prj.add_include('../sources/slog/include2') 55 | prj.add_slog('../sources/slog/*.sv') 56 | if args.source in ['vlog', 'slog']: 57 | prj.add_define('DEFINE1', '1') 58 | prj.add_define('DEFINE2', '1') 59 | 60 | prj.set_top('Top') 61 | 62 | if args.action in ['make', 'all']: 63 | prj.make() 64 | if args.action in ['prog', 'all']: 65 | prj.prog() 66 | -------------------------------------------------------------------------------- /examples/projects/quartus.py: -------------------------------------------------------------------------------- 1 | """Quartus examples.""" 2 | 3 | import argparse 4 | 5 | from pyfpga.quartus import Quartus 6 | 7 | 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument( 10 | '--board', choices=['de10nano'], default='de10nano' 11 | ) 12 | parser.add_argument( 13 | '--source', choices=['vlog', 'vhdl', 'slog'], default='vlog' 14 | ) 15 | parser.add_argument( 16 | '--action', choices=['make', 'prog', 'all'], default='make' 17 | ) 18 | args = parser.parse_args() 19 | 20 | prj = Quartus(odir=f'results/quartus/{args.source}/{args.board}') 21 | 22 | if args.board == 'de10nano': 23 | prj.set_part('5CSEBA6U23I7') 24 | prj.add_param('FREQ', '125000000') 25 | prj.add_cons('../sources/cons/de10nano/timing.sdc') 26 | prj.add_cons('../sources/cons/de10nano/clk.tcl') 27 | prj.add_cons('../sources/cons/de10nano/led.tcl') 28 | prj.add_param('SECS', '1') 29 | 30 | if args.source == 'vhdl': 31 | prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') 32 | prj.add_vhdl('../sources/vhdl/top.vhdl') 33 | if args.source == 'vlog': 34 | prj.add_include('../sources/vlog/include1') 35 | prj.add_include('../sources/vlog/include2') 36 | prj.add_vlog('../sources/vlog/*.v') 37 | if args.source == 'slog': 38 | prj.add_include('../sources/slog/include1') 39 | prj.add_include('../sources/slog/include2') 40 | prj.add_slog('../sources/slog/*.sv') 41 | if args.source in ['vlog', 'slog']: 42 | prj.add_define('DEFINE1', '1') 43 | prj.add_define('DEFINE2', '1') 44 | 45 | prj.set_top('Top') 46 | 47 | if args.action in ['make', 'all']: 48 | prj.make() 49 | if args.action in ['prog', 'all']: 50 | prj.prog() 51 | -------------------------------------------------------------------------------- /examples/projects/vivado.py: -------------------------------------------------------------------------------- 1 | """Vivado examples.""" 2 | 3 | import argparse 4 | 5 | from pyfpga.vivado import Vivado 6 | 7 | 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument( 10 | '--board', choices=['zybo', 'arty'], default='zybo' 11 | ) 12 | parser.add_argument( 13 | '--source', choices=['vlog', 'vhdl', 'slog'], default='vlog' 14 | ) 15 | parser.add_argument( 16 | '--action', choices=['make', 'prog', 'all'], default='make' 17 | ) 18 | args = parser.parse_args() 19 | 20 | prj = Vivado(odir=f'results/vivado/{args.source}/{args.board}') 21 | 22 | if args.board == 'zybo': 23 | prj.set_part('xc7z010-1-clg400') 24 | prj.add_param('FREQ', '125000000') 25 | prj.add_cons('../sources/cons/ZYBO/timing.xdc') 26 | prj.add_cons('../sources/cons/ZYBO/clk.xdc') 27 | prj.add_cons('../sources/cons/ZYBO/led.xdc') 28 | if args.board == 'arty': 29 | prj.set_part('xc7a35ticsg324-1L') 30 | prj.add_param('FREQ', '100000000') 31 | prj.add_cons('../sources/cons/arty_a7_35t/timing.xdc') 32 | prj.add_cons('../sources/cons/arty_a7_35t/clk.xdc') 33 | prj.add_cons('../sources/cons/arty_a7_35t/led.xdc') 34 | prj.add_param('SECS', '1') 35 | 36 | if args.source == 'vhdl': 37 | prj.add_vhdl('../sources/vhdl/*.vhdl', 'blink_lib') 38 | prj.add_vhdl('../sources/vhdl/top.vhdl') 39 | if args.source == 'vlog': 40 | prj.add_include('../sources/vlog/include1') 41 | prj.add_include('../sources/vlog/include2') 42 | prj.add_vlog('../sources/vlog/*.v') 43 | if args.source == 'slog': 44 | prj.add_include('../sources/slog/include1') 45 | prj.add_include('../sources/slog/include2') 46 | prj.add_slog('../sources/slog/*.sv') 47 | if args.source in ['vlog', 'slog']: 48 | prj.add_define('DEFINE1', '1') 49 | prj.add_define('DEFINE2', '1') 50 | 51 | prj.set_top('Top') 52 | 53 | if args.action in ['make', 'all']: 54 | prj.make() 55 | if args.action in ['prog', 'all']: 56 | prj.prog() 57 | -------------------------------------------------------------------------------- /examples/sources/cons/ZYBO/clk.xdc: -------------------------------------------------------------------------------- 1 | set_property PACKAGE_PIN L16 [get_ports clk_i] 2 | set_property IOSTANDARD LVCMOS33 [get_ports clk_i] 3 | -------------------------------------------------------------------------------- /examples/sources/cons/ZYBO/led.xdc: -------------------------------------------------------------------------------- 1 | set_property PACKAGE_PIN M14 [get_ports led_o] 2 | set_property IOSTANDARD LVCMOS33 [get_ports led_o] 3 | -------------------------------------------------------------------------------- /examples/sources/cons/ZYBO/timing.xdc: -------------------------------------------------------------------------------- 1 | create_clock -name clk_i -period 8 [get_ports clk_i] 2 | -------------------------------------------------------------------------------- /examples/sources/cons/arty_a7_35t/clk.xdc: -------------------------------------------------------------------------------- 1 | set_property PACKAGE_PIN E3 [get_ports clk_i] 2 | set_property IOSTANDARD LVCMOS33 [get_ports clk_i] 3 | -------------------------------------------------------------------------------- /examples/sources/cons/arty_a7_35t/led.xdc: -------------------------------------------------------------------------------- 1 | set_property PACKAGE_PIN H5 [get_ports led_o] 2 | set_property IOSTANDARD LVCMOS33 [get_ports led_o] 3 | -------------------------------------------------------------------------------- /examples/sources/cons/arty_a7_35t/timing.xdc: -------------------------------------------------------------------------------- 1 | create_clock -name clk_i -period 10.0 [get_ports clk_i] 2 | -------------------------------------------------------------------------------- /examples/sources/cons/brevia2/clk.lpf: -------------------------------------------------------------------------------- 1 | BLOCK RESETPATHS ; 2 | BLOCK ASYNCPATHS ; 3 | FREQUENCY NET "clk_i_c" 50.000000 MHz ; 4 | -------------------------------------------------------------------------------- /examples/sources/cons/brevia2/led.lpf: -------------------------------------------------------------------------------- 1 | LOCATE COMP "clk_i" SITE "21" ; 2 | IOBUF PORT "clk_i" IO_TYPE=LVCMOS33 ; 3 | LOCATE COMP "led_o" SITE "37" ; 4 | IOBUF PORT "led_o" IO_TYPE=LVCMOS33 ; 5 | -------------------------------------------------------------------------------- /examples/sources/cons/de10nano/clk.tcl: -------------------------------------------------------------------------------- 1 | set_location_assignment PIN_V11 -to clk_i 2 | -------------------------------------------------------------------------------- /examples/sources/cons/de10nano/led.tcl: -------------------------------------------------------------------------------- 1 | set_location_assignment PIN_W15 -to led_o 2 | -------------------------------------------------------------------------------- /examples/sources/cons/de10nano/timing.sdc: -------------------------------------------------------------------------------- 1 | create_clock -period 20 [get_ports clk_i] 2 | -------------------------------------------------------------------------------- /examples/sources/cons/ecp5evn/clk.lpf: -------------------------------------------------------------------------------- 1 | LOCATE COMP "clk_i" SITE "A10"; 2 | IOBUF PORT "clk_i" IO_TYPE=LVCMOS33; 3 | -------------------------------------------------------------------------------- /examples/sources/cons/ecp5evn/led.lpf: -------------------------------------------------------------------------------- 1 | LOCATE COMP "led_o" SITE "A13"; 2 | IOBUF PORT "led_o" IO_TYPE=LVCMOS25; 3 | -------------------------------------------------------------------------------- /examples/sources/cons/edu-ciaa/clk.pcf: -------------------------------------------------------------------------------- 1 | set_io clk_i 94 2 | -------------------------------------------------------------------------------- /examples/sources/cons/edu-ciaa/led.pcf: -------------------------------------------------------------------------------- 1 | set_io led_o 4 2 | -------------------------------------------------------------------------------- /examples/sources/cons/icestick/clk.pcf: -------------------------------------------------------------------------------- 1 | set_io clk_i 21 2 | -------------------------------------------------------------------------------- /examples/sources/cons/icestick/led.pcf: -------------------------------------------------------------------------------- 1 | set_io led_o 95 2 | -------------------------------------------------------------------------------- /examples/sources/cons/maker/clk.pdc: -------------------------------------------------------------------------------- 1 | set_io clk_i -DIRECTION INPUT -pinname 23 -fixed yes 2 | -------------------------------------------------------------------------------- /examples/sources/cons/maker/led.pdc: -------------------------------------------------------------------------------- 1 | set_io led_o -DIRECTION OUTPUT -pinname 117 -fixed yes 2 | -------------------------------------------------------------------------------- /examples/sources/cons/maker/timing.sdc: -------------------------------------------------------------------------------- 1 | create_clock -period 20 [get_ports clk_i] 2 | -------------------------------------------------------------------------------- /examples/sources/cons/mpfs-disco-kit/clk.pdc: -------------------------------------------------------------------------------- 1 | set_io -port_name clk_i -DIRECTION INPUT -pin_name R18 -fixed true 2 | -------------------------------------------------------------------------------- /examples/sources/cons/mpfs-disco-kit/led.pdc: -------------------------------------------------------------------------------- 1 | set_io -port_name led_o -DIRECTION OUTPUT -pin_name T18 -fixed true 2 | -------------------------------------------------------------------------------- /examples/sources/cons/mpfs-disco-kit/timing.sdc: -------------------------------------------------------------------------------- 1 | create_clock -period 20 [get_ports clk_i] 2 | -------------------------------------------------------------------------------- /examples/sources/cons/nexys3/clk.ucf: -------------------------------------------------------------------------------- 1 | NET "clk_i" LOC = "V10"; 2 | -------------------------------------------------------------------------------- /examples/sources/cons/nexys3/led.ucf: -------------------------------------------------------------------------------- 1 | NET "led_o" LOC = "U16"; 2 | -------------------------------------------------------------------------------- /examples/sources/cons/nexys3/timing.xcf: -------------------------------------------------------------------------------- 1 | NET "clk_i" TNM_NET = "clk_i"; 2 | TIMESPEC "TS_clk_i" = PERIOD "clk_i" 10.00 ns HIGH 50%; 3 | -------------------------------------------------------------------------------- /examples/sources/cons/orangecrab/clk.lpf: -------------------------------------------------------------------------------- 1 | LOCATE COMP "clk_i" SITE "A9"; 2 | IOBUF PORT "clk_i" IO_TYPE=LVCMOS33; 3 | -------------------------------------------------------------------------------- /examples/sources/cons/orangecrab/led.lpf: -------------------------------------------------------------------------------- 1 | LOCATE COMP "led_o" SITE "K4"; 2 | IOBUF PORT "led_o" IO_TYPE=LVCMOS33; 3 | -------------------------------------------------------------------------------- /examples/sources/cons/s6micro/clk.ucf: -------------------------------------------------------------------------------- 1 | NET "clk_i" LOC = "V10"; 2 | -------------------------------------------------------------------------------- /examples/sources/cons/s6micro/led.ucf: -------------------------------------------------------------------------------- 1 | NET "led_o" LOC = "C2"; 2 | -------------------------------------------------------------------------------- /examples/sources/cons/s6micro/timing.xcf: -------------------------------------------------------------------------------- 1 | NET "clk_i" TNM_NET = "clk_i"; 2 | TIMESPEC "TS_clk_i" = PERIOD "clk_i" 20.00 ns HIGH 50%; 3 | -------------------------------------------------------------------------------- /examples/sources/slog/blink.sv: -------------------------------------------------------------------------------- 1 | module Blink #( 2 | parameter int FREQ = 25000000, 3 | parameter int SECS = 1 4 | )( 5 | input clk_i, 6 | output led_o 7 | ); 8 | 9 | localparam int DIV = FREQ*SECS; 10 | logic led = 0; 11 | logic [$clog2(DIV)-1:0] cnt = 0; 12 | 13 | always_ff @(posedge clk_i) begin 14 | if (cnt == DIV-1) begin 15 | cnt <= 0; 16 | led <= ~led; 17 | end else begin 18 | cnt <= cnt + 1; 19 | end 20 | end 21 | 22 | assign led_o = led; 23 | 24 | endmodule 25 | -------------------------------------------------------------------------------- /examples/sources/slog/include1/header1.svh: -------------------------------------------------------------------------------- 1 | `define INCLUDE1 2 | -------------------------------------------------------------------------------- /examples/sources/slog/include2/header2.svh: -------------------------------------------------------------------------------- 1 | `define INCLUDE2 2 | -------------------------------------------------------------------------------- /examples/sources/slog/top.sv: -------------------------------------------------------------------------------- 1 | `include "header1.svh" 2 | `include "header2.svh" 3 | 4 | module Top #( 5 | parameter int FREQ = 0, 6 | parameter int SECS = 0 7 | )( 8 | input clk_i, 9 | output led_o 10 | ); 11 | 12 | Blink #(.FREQ (FREQ), .SECS (SECS)) dut (.clk_i (clk_i), .led_o (led_o)); 13 | 14 | `ifndef INCLUDE1 15 | reg led; 16 | always @(posedge clk_i) led <= 1'b0; 17 | always @(posedge clk_i) led <= 1'b1; 18 | assign led_o = led; 19 | initial begin $stop; $error("intentional"); end 20 | `endif 21 | 22 | `ifndef INCLUDE2 23 | reg led; 24 | always @(posedge clk_i) led <= 1'b0; 25 | always @(posedge clk_i) led <= 1'b1; 26 | assign led_o = led; 27 | initial begin $stop; $error("intentional"); end 28 | `endif 29 | 30 | `ifndef DEFINE1 31 | reg led; 32 | always @(posedge clk_i) led <= 1'b0; 33 | always @(posedge clk_i) led <= 1'b1; 34 | assign led_o = led; 35 | initial begin $stop; $error("intentional"); end 36 | `endif 37 | 38 | `ifndef DEFINE2 39 | reg led; 40 | always @(posedge clk_i) led <= 1'b0; 41 | always @(posedge clk_i) led <= 1'b1; 42 | assign led_o = led; 43 | initial begin $stop; $error("intentional"); end 44 | `endif 45 | 46 | generate 47 | if (!FREQ || !SECS) begin 48 | reg led; 49 | always @(posedge clk_i) led <= 1'b0; 50 | always @(posedge clk_i) led <= 1'b1; 51 | assign led_o = led; 52 | initial begin $stop; $error("intentional"); end 53 | end 54 | endgenerate 55 | 56 | endmodule 57 | -------------------------------------------------------------------------------- /examples/sources/vhdl/blink.vhdl: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.std_logic_1164.all; 3 | 4 | entity Blink is 5 | generic ( 6 | FREQ : positive := 25e6; 7 | SECS : positive := 1 8 | ); 9 | port ( 10 | clk_i : in std_logic; 11 | led_o : out std_logic 12 | ); 13 | end entity Blink; 14 | 15 | architecture RTL of Blink is 16 | constant DIV : positive := FREQ*SECS; 17 | signal led : std_logic := '0'; 18 | signal cnt : natural range 0 to DIV-1 := 0; 19 | begin 20 | 21 | blink_p: process (clk_i) 22 | begin 23 | if rising_edge(clk_i) then 24 | if cnt = DIV-1 then 25 | cnt <= 0; 26 | led <= not led; 27 | else 28 | cnt <= cnt + 1; 29 | end if; 30 | end if; 31 | end process blink_p; 32 | 33 | led_o <= led; 34 | 35 | end architecture RTL; 36 | -------------------------------------------------------------------------------- /examples/sources/vhdl/blink_pkg.vhdl: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.std_logic_1164.all; 3 | 4 | package blink_pkg is 5 | 6 | component Blink is 7 | generic ( 8 | FREQ : positive := 25e6; 9 | SECS : positive := 1 10 | ); 11 | port ( 12 | clk_i : in std_logic; 13 | led_o : out std_logic 14 | ); 15 | end component Blink; 16 | 17 | end package blink_pkg; 18 | -------------------------------------------------------------------------------- /examples/sources/vhdl/top.vhdl: -------------------------------------------------------------------------------- 1 | library IEEE; 2 | use IEEE.std_logic_1164.all; 3 | library blink_lib; 4 | use blink_lib.blink_pkg.all; 5 | 6 | entity Top is 7 | generic ( 8 | FREQ : natural := 0; 9 | SECS : natural := 0 10 | ); 11 | port ( 12 | clk_i : in std_logic; 13 | led_o : out std_logic 14 | ); 15 | end entity Top; 16 | 17 | architecture ARCH of Top is 18 | signal led : std_logic; 19 | begin 20 | 21 | blink_i: Blink 22 | generic map (FREQ => FREQ, SECS => SECS) 23 | port map (clk_i => clk_i, led_o => led_o); 24 | 25 | gen_error: if (FREQ=0 or SECS=0) generate 26 | begin 27 | process(clk_i) 28 | begin 29 | if rising_edge(clk_i) then 30 | led <= '0'; 31 | end if; 32 | end process; 33 | process(clk_i) 34 | begin 35 | if rising_edge(clk_i) then 36 | led <= '1'; 37 | end if; 38 | end process; 39 | led_o <= led; 40 | end generate gen_error; 41 | 42 | end architecture ARCH; 43 | -------------------------------------------------------------------------------- /examples/sources/vlog/blink.v: -------------------------------------------------------------------------------- 1 | module Blink #( 2 | parameter FREQ = 25000000, 3 | parameter SECS = 1 4 | )( 5 | input clk_i, 6 | output led_o 7 | ); 8 | 9 | localparam DIV = FREQ*SECS; 10 | reg led = 0; 11 | reg [$clog2(DIV)-1:0] cnt = 0; 12 | 13 | always @(posedge clk_i) begin 14 | if (cnt == DIV-1) begin 15 | cnt <= 0; 16 | led <= ~led; 17 | end else begin 18 | cnt <= cnt + 1; 19 | end 20 | end 21 | 22 | assign led_o = led; 23 | 24 | endmodule 25 | -------------------------------------------------------------------------------- /examples/sources/vlog/include1/header1.vh: -------------------------------------------------------------------------------- 1 | `define INCLUDE1 2 | -------------------------------------------------------------------------------- /examples/sources/vlog/include2/header2.vh: -------------------------------------------------------------------------------- 1 | `define INCLUDE2 2 | -------------------------------------------------------------------------------- /examples/sources/vlog/top.v: -------------------------------------------------------------------------------- 1 | `include "header1.vh" 2 | `include "header2.vh" 3 | 4 | module Top #( 5 | parameter FREQ = 0, 6 | parameter SECS = 0 7 | )( 8 | input clk_i, 9 | output led_o 10 | ); 11 | 12 | Blink #(.FREQ (FREQ), .SECS (SECS)) dut (.clk_i (clk_i), .led_o (led_o)); 13 | 14 | `ifndef INCLUDE1 15 | reg led; 16 | always @(posedge clk_i) led <= 1'b0; 17 | always @(posedge clk_i) led <= 1'b1; 18 | assign led_o = led; 19 | initial begin $stop; $error("intentional"); end 20 | `endif 21 | 22 | `ifndef INCLUDE2 23 | reg led; 24 | always @(posedge clk_i) led <= 1'b0; 25 | always @(posedge clk_i) led <= 1'b1; 26 | assign led_o = led; 27 | initial begin $stop; $error("intentional"); end 28 | `endif 29 | 30 | `ifndef DEFINE1 31 | reg led; 32 | always @(posedge clk_i) led <= 1'b0; 33 | always @(posedge clk_i) led <= 1'b1; 34 | assign led_o = led; 35 | initial begin $stop; $error("intentional"); end 36 | `endif 37 | 38 | `ifndef DEFINE2 39 | reg led; 40 | always @(posedge clk_i) led <= 1'b0; 41 | always @(posedge clk_i) led <= 1'b1; 42 | assign led_o = led; 43 | initial begin $stop; $error("intentional"); end 44 | `endif 45 | 46 | generate 47 | if (!FREQ || !SECS) begin 48 | reg led; 49 | always @(posedge clk_i) led <= 1'b0; 50 | always @(posedge clk_i) led <= 1'b1; 51 | assign led_o = led; 52 | initial begin $stop; $error("intentional"); end 53 | end 54 | endgenerate 55 | 56 | endmodule 57 | -------------------------------------------------------------------------------- /pyfpga/__init__.py: -------------------------------------------------------------------------------- 1 | """PyFPGA""" 2 | 3 | __version__ = '0.4.0' 4 | -------------------------------------------------------------------------------- /pyfpga/diamond.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2024 PyFPGA Project 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | # 6 | 7 | """ 8 | Implements support for Diamond. 9 | """ 10 | 11 | import os 12 | from pyfpga.project import Project 13 | 14 | 15 | class Diamond(Project): 16 | """Class to support Diamond projects.""" 17 | 18 | def _configure(self): 19 | tool = 'diamond' 20 | executable = 'pnmainc' if os.name == 'nt' else 'diamondc' 21 | self.conf['tool'] = tool 22 | self.conf['make_cmd'] = f'{executable} {tool}.tcl' 23 | self.conf['make_ext'] = 'tcl' 24 | self.conf['prog_bit'] = ['bit'] 25 | self.conf['prog_cmd'] = f'sh {tool}-prog.sh' 26 | self.conf['prog_ext'] = 'sh' 27 | 28 | def _make_custom(self): 29 | if 'part' not in self.data: 30 | self.data['part'] = 'LFXP2-5E-5TN144C' 31 | -------------------------------------------------------------------------------- /pyfpga/factory.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2024 PyFPGA Project 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | # 6 | 7 | """ 8 | A factory class to create FPGA projects. 9 | """ 10 | 11 | # pylint: disable=too-few-public-methods 12 | 13 | from pyfpga.diamond import Diamond 14 | from pyfpga.ise import Ise 15 | from pyfpga.libero import Libero 16 | from pyfpga.openflow import Openflow 17 | from pyfpga.quartus import Quartus 18 | from pyfpga.vivado import Vivado 19 | 20 | 21 | TOOLS = { 22 | 'diamond': Diamond, 23 | 'ise': Ise, 24 | 'libero': Libero, 25 | 'openflow': Openflow, 26 | 'quartus': Quartus, 27 | 'vivado': Vivado 28 | } 29 | 30 | 31 | class Factory: 32 | """A factory class to create FPGA projects.""" 33 | 34 | def __init__(self, tool='vivado', project=None, odir='results'): 35 | """Class constructor.""" 36 | if tool not in TOOLS: 37 | raise NotImplementedError(f'{tool} is unsupported') 38 | self._instance = TOOLS[tool](project, odir) 39 | 40 | def __getattr__(self, name): 41 | """Delegate attribute access to the tool instance.""" 42 | return getattr(self._instance, name) 43 | -------------------------------------------------------------------------------- /pyfpga/helpers/bitprog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (C) 2020-2025 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | 8 | """ 9 | A CLI helper utility to transfer a bitstream to a supported device. 10 | """ 11 | 12 | import argparse 13 | import sys 14 | 15 | from pathlib import Path 16 | from pyfpga import __version__ as version 17 | from pyfpga.factory import Factory, TOOLS 18 | from pyfpga.project import STEPS 19 | 20 | tools = list(TOOLS.keys()) 21 | positions = range(1, 10) 22 | 23 | EPILOGUE = f""" 24 | Supported values of arguments with choices: 25 | * TOOL = {'|'.join(tools)} 26 | * POSITION = {'|'.join(map(str, positions))} 27 | """ 28 | 29 | 30 | def main(): 31 | """Solves the main functionality of this helper.""" 32 | 33 | # Parsing the command-line. 34 | 35 | parser = argparse.ArgumentParser( 36 | description=__doc__, 37 | epilog=EPILOGUE, 38 | formatter_class=argparse.RawDescriptionHelpFormatter 39 | ) 40 | 41 | parser.add_argument( 42 | '-v', '--version', 43 | action='version', 44 | version=f'v{version}' 45 | ) 46 | 47 | parser.add_argument( 48 | '-t', '--tool', 49 | metavar='TOOL', 50 | default='vivado', 51 | choices=tools, 52 | help='backend tool to be used [vivado]' 53 | ) 54 | 55 | parser.add_argument( 56 | '-p', '--position', 57 | metavar='POSITION', 58 | choices=positions, 59 | type=int, 60 | default=1, 61 | help='the device position into the JTAG chain [1]' 62 | ) 63 | 64 | parser.add_argument( 65 | '-o', '--odir', 66 | metavar='PATH', 67 | default='results', 68 | help='where to generate files [results]' 69 | ) 70 | 71 | parser.add_argument( 72 | 'bit', 73 | metavar='BITFILE', 74 | help='a bitstream file' 75 | ) 76 | 77 | args = parser.parse_args() 78 | 79 | # ------------------------------------------------------------------------- 80 | # Solving with PyFPGA 81 | # ------------------------------------------------------------------------- 82 | 83 | prj = Factory(args.tool, odir=args.odir) 84 | 85 | try: 86 | prj.prog(args.bit, args.position) 87 | except Exception as e: 88 | sys.exit('{} ({})'.format(type(e).__name__, e)) 89 | 90 | 91 | if __name__ == "__main__": 92 | main() 93 | -------------------------------------------------------------------------------- /pyfpga/helpers/hdl2bit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (C) 2020-2025 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | 8 | """ 9 | A CLI helper utility to transform HDL design files into a bitstream. 10 | """ 11 | 12 | import argparse 13 | import sys 14 | 15 | from pathlib import Path 16 | from pyfpga import __version__ as version 17 | from pyfpga.factory import Factory, TOOLS 18 | from pyfpga.project import STEPS 19 | 20 | tools = list(TOOLS.keys()) 21 | steps = list(STEPS.keys()) 22 | 23 | EPILOGUE = f""" 24 | Supported values of arguments with choices: 25 | * TOOL = {'|'.join(tools)} 26 | * STEP = {'|'.join(steps)} 27 | 28 | Notes: 29 | * PATH and FILE must be relative to the execution directory. 30 | * The default PART name and how to specify it depends on the selected TOOL. 31 | * More than one '--file', '--include' or '--param' arguments can be specified. 32 | """ 33 | 34 | 35 | def main(): 36 | """Solves the main functionality of this helper.""" 37 | 38 | # Parsing the command-line. 39 | 40 | parser = argparse.ArgumentParser( 41 | description=__doc__, 42 | epilog=EPILOGUE, 43 | formatter_class=argparse.RawDescriptionHelpFormatter 44 | ) 45 | 46 | parser.add_argument( 47 | '-v', '--version', 48 | action='version', 49 | version=f'v{version}' 50 | ) 51 | 52 | parser.add_argument( 53 | '-t', '--tool', 54 | metavar='TOOL', 55 | default='vivado', 56 | choices=tools, 57 | help='backend tool to be used [vivado]' 58 | ) 59 | 60 | parser.add_argument( 61 | '-p', '--part', 62 | metavar='PART', 63 | help='the target device' 64 | ) 65 | 66 | parser.add_argument( 67 | '-f', '--file', 68 | metavar='FILE[,LIBRARY]', 69 | action='append', 70 | help='add a design file (optionally specifying a VHDL library)' 71 | ) 72 | 73 | parser.add_argument( 74 | '-i', '--include', 75 | metavar='PATH', 76 | action='append', 77 | help='specify a Verilog Include directory' 78 | ) 79 | 80 | parser.add_argument( 81 | '--define', 82 | metavar=('DEFINE', 'VALUE'), 83 | action='append', 84 | nargs=2, 85 | help='define and set the value of a Verilog Define' 86 | ) 87 | 88 | parser.add_argument( 89 | '--param', 90 | metavar=('PARAMETER', 'VALUE'), 91 | action='append', 92 | nargs=2, 93 | help='set the value of a Generic/Parameter of the top-level' 94 | ) 95 | 96 | parser.add_argument( 97 | '--project', 98 | metavar='PROJECT', 99 | help='optional PROJECT name' 100 | ) 101 | 102 | parser.add_argument( 103 | '--last', 104 | metavar='STEP', 105 | choices=steps, 106 | default='bit', 107 | help=f'last step to perform [{steps[-1]}] ({"|".join(steps)})' 108 | ) 109 | 110 | parser.add_argument( 111 | '-o', '--odir', 112 | metavar='PATH', 113 | default='results', 114 | help='where to generate files [results]' 115 | ) 116 | 117 | parser.add_argument( 118 | 'toplevel', 119 | metavar='TOPLEVEL', 120 | help='the top-level name' 121 | ) 122 | 123 | args = parser.parse_args() 124 | 125 | # ------------------------------------------------------------------------- 126 | # Solving with PyFPGA 127 | # ------------------------------------------------------------------------- 128 | 129 | project = args.project or args.tool 130 | 131 | prj = Factory(args.tool, project, odir=args.odir) 132 | 133 | if args.part is not None: 134 | prj.set_part(args.part) 135 | 136 | if args.include is not None: 137 | for include in args.include: 138 | prj.add_include(include) 139 | 140 | if args.file is not None: 141 | for file in args.file: 142 | aux = file.split(',') 143 | file = aux[0] 144 | lib = aux[1] if len(aux) > 1 else None 145 | ext = Path(file).suffix.lower() 146 | if ext == '.v': 147 | print(f'* Adding Verilog file: {file}') 148 | prj.add_vlog(file) 149 | elif ext == '.sv': 150 | print(f'* Adding System Verilog file: {file}') 151 | prj.add_slog(file) 152 | elif ext in ['.vhd', '.vhdl']: 153 | if lib: 154 | print(f'* Adding VHDL file: {file} (library={lib})') 155 | else: 156 | print(f'* Adding VHDL file: {file}') 157 | prj.add_vhdl(file, lib) 158 | else: 159 | print(f'* Adding Constraint file: {file}') 160 | prj.add_cons(file) 161 | 162 | if args.define is not None: 163 | for define in args.define: 164 | prj.add_define(define[0], define[1]) 165 | 166 | if args.param is not None: 167 | for param in args.param: 168 | prj.add_param(param[0], param[1]) 169 | 170 | prj.set_top(args.toplevel) 171 | 172 | try: 173 | prj.make(last=args.last) 174 | except Exception as e: 175 | sys.exit('{} ({})'.format(type(e).__name__, e)) 176 | 177 | 178 | if __name__ == "__main__": 179 | main() 180 | -------------------------------------------------------------------------------- /pyfpga/helpers/prj2bit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (C) 2020-2025 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | 8 | """ 9 | A CLI helper utility to deal with a vendor FPGA Project file. 10 | """ 11 | 12 | import argparse 13 | import sys 14 | 15 | from pathlib import Path 16 | from pyfpga import __version__ as version 17 | from pyfpga.factory import Factory, TOOLS 18 | from pyfpga.project import STEPS 19 | 20 | steps = list(STEPS.keys())[1:len(STEPS)] 21 | 22 | 23 | def main(): 24 | """Solves the main functionality of this helper.""" 25 | 26 | # Parsing the command-line. 27 | 28 | parser = argparse.ArgumentParser( 29 | description=__doc__ 30 | ) 31 | 32 | parser.add_argument( 33 | '-v', '--version', 34 | action='version', 35 | version=f'v{version}' 36 | ) 37 | 38 | parser.add_argument( 39 | '--last', 40 | metavar='STEP', 41 | choices=steps, 42 | default='bit', 43 | help=f'last step to perform [{steps[-1]}] ({"|".join(steps)})' 44 | ) 45 | 46 | parser.add_argument( 47 | 'prjfile', 48 | metavar='PRJFILE', 49 | help='a vendor Project File' 50 | ) 51 | 52 | args = parser.parse_args() 53 | 54 | # Detecting a Project file 55 | 56 | tool_per_ext = { 57 | '.ldf': 'diamond', 58 | '.xise': 'ise', 59 | '.prjx': 'libero', 60 | '.qpf': 'quartus', 61 | '.xpr': 'vivado' 62 | } 63 | 64 | prjfile = Path(args.prjfile) 65 | 66 | if not prjfile.exists(): 67 | sys.exit(f'ERROR: {prjfile} file not found.') 68 | 69 | directory = prjfile.parent 70 | base_name = prjfile.stem 71 | extension = prjfile.suffix 72 | 73 | tool = '' 74 | if extension in tool_per_ext: 75 | tool = tool_per_ext[extension] 76 | print(f'INFO: {tool} project file found.') 77 | else: 78 | sys.exit(f'ERROR: unknown project file extension ({extension})') 79 | 80 | # ------------------------------------------------------------------------- 81 | # Solving with PyFPGA 82 | # ------------------------------------------------------------------------- 83 | 84 | prj = Factory(tool, base_name, directory) 85 | 86 | try: 87 | prj.make('syn', args.last) 88 | except Exception as e: 89 | sys.exit('{} ({})'.format(type(e).__name__, e)) 90 | 91 | 92 | if __name__ == "__main__": 93 | main() 94 | -------------------------------------------------------------------------------- /pyfpga/ise.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019-2024 PyFPGA Project 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | # 6 | 7 | """ 8 | Implements support for ISE. 9 | """ 10 | 11 | import re 12 | 13 | from pyfpga.project import Project 14 | 15 | 16 | class Ise(Project): 17 | """Class to support ISE projects.""" 18 | 19 | def _configure(self): 20 | tool = 'ise' 21 | self.conf['tool'] = tool 22 | self.conf['make_cmd'] = f'xtclsh {tool}.tcl' 23 | self.conf['make_ext'] = 'tcl' 24 | self.conf['prog_bit'] = ['bit'] 25 | self.conf['prog_cmd'] = f'impact -batch {tool}-prog.tcl' 26 | self.conf['prog_ext'] = 'tcl' 27 | 28 | def _make_custom(self): 29 | info = get_info(self.data.get('part', 'xc7k160t-3-fbg484')) 30 | self.data['family'] = info['family'] 31 | self.data['device'] = info['device'] 32 | self.data['speed'] = info['speed'] 33 | self.data['package'] = info['package'] 34 | 35 | def add_slog(self, pathname): 36 | """Add System Verilog file/s.""" 37 | raise NotImplementedError('ISE does not support SystemVerilog') 38 | 39 | 40 | # pylint: disable=duplicate-code 41 | 42 | def get_info(part): 43 | """Get info about the FPGA part. 44 | 45 | :param part: the FPGA part as specified by the tool 46 | :returns: a dict with the keys family, device, speed and package 47 | """ 48 | part = part.lower().replace(' ', '') 49 | # Looking for the family 50 | family = None 51 | families = { 52 | r'xc7a\d+l': 'artix7l', 53 | r'xc7a': 'artix7', 54 | r'xc7k\d+l': 'kintex7l', 55 | r'xc7k': 'kintex7', 56 | r'xc3sd\d+a': 'spartan3adsp', 57 | r'xc3s\d+a': 'spartan3a', 58 | r'xc3s\d+e': 'spartan3e', 59 | r'xc3s': 'spartan3', 60 | r'xc6s\d+l': 'spartan6l', 61 | r'xc6s': 'spartan6', 62 | r'xc4v': 'virtex4', 63 | r'xc5v': 'virtex5', 64 | r'xc6v\d+l': 'virtex6l', 65 | r'xc6v': 'virtex6', 66 | r'xc7v\d+l': 'virtex7l', 67 | r'xc7v': 'virtex7', 68 | r'xc7z': 'zynq' 69 | } 70 | for key, value in families.items(): 71 | if re.match(key, part): 72 | family = value 73 | break 74 | # Looking for the other values 75 | device = None 76 | speed = None 77 | package = None 78 | aux = part.split('-') 79 | if len(aux) == 3: 80 | device = aux[0] 81 | if len(aux[1]) < len(aux[2]): 82 | speed = f'-{aux[1]}' 83 | package = aux[2] 84 | else: 85 | speed = f'-{aux[2]}' 86 | package = aux[1] 87 | else: 88 | valid = 'DEVICE-SPEED-PACKAGE or DEVICE-PACKAGE-SPEED' 89 | raise ValueError(f'Invalid PART format ({valid})') 90 | # Finish 91 | return { 92 | 'family': family, 93 | 'device': device, 94 | 'speed': speed, 95 | 'package': package 96 | } 97 | -------------------------------------------------------------------------------- /pyfpga/libero.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019-2024 PyFPGA Project 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | # 6 | 7 | """ 8 | Implements support for Libero. 9 | """ 10 | 11 | import re 12 | 13 | from pyfpga.project import Project 14 | 15 | 16 | class Libero(Project): 17 | """Class to support Libero projects.""" 18 | 19 | def _configure(self): 20 | tool = 'libero' 21 | self.conf['tool'] = tool 22 | self.conf['make_cmd'] = f'{tool} SCRIPT:{tool}.tcl' 23 | self.conf['make_ext'] = 'tcl' 24 | self.conf['prog_bit'] = ['ppd', 'stp', 'bit', 'jed'] 25 | self.conf['prog_cmd'] = f'FPExpress SCRIPT:{tool}-prog.tcl' 26 | self.conf['prog_ext'] = 'tcl' 27 | 28 | def _make_custom(self): 29 | info = get_info(self.data.get('part', 'mpf100t-1-fcg484')) 30 | self.data['family'] = info['family'] 31 | self.data['device'] = info['device'] 32 | self.data['speed'] = info['speed'] 33 | self.data['package'] = info['package'] 34 | self.data['prange'] = info['prange'] 35 | 36 | 37 | # pylint: disable=duplicate-code 38 | 39 | def get_info(part): 40 | """Get info about the FPGA part. 41 | 42 | :param part: the FPGA part as specified by the tool 43 | :returns: a dict with the keys family, device, speed, package and prange 44 | """ 45 | part = part.lower().replace(' ', '') 46 | # Looking for the family 47 | family = None 48 | families = { 49 | r'm2s': 'SmartFusion2', 50 | r'm2gl': 'IGLOO2', 51 | r'rt4g': 'RTG4', 52 | r'mpfs': 'PolarFireSoC', 53 | r'mpf': 'PolarFire', 54 | r'a2f': 'SmartFusion', 55 | r'afs': 'Fusion', 56 | r'aglp': 'IGLOO+', 57 | r'agle': 'IGLOOE', 58 | r'agl': 'IGLOO', 59 | r'a3p\d+l': 'ProAsic3L', 60 | r'a3pe': 'ProAsic3E', 61 | r'a3p': 'ProAsic3' 62 | } 63 | for key, value in families.items(): 64 | if re.match(key, part): 65 | family = value 66 | break 67 | # Looking for the other values 68 | device = None 69 | speed = None 70 | package = None 71 | prange = None 72 | aux = part.split('-') 73 | if len(aux) == 2: 74 | device = aux[0] 75 | package = aux[1] 76 | if package[0].isdigit(): 77 | speed = f'-{package[0]}' 78 | package = package[1:] 79 | else: 80 | speed = 'STD' 81 | elif len(aux) == 3: 82 | device = aux[0] 83 | if len(aux[1]) < len(aux[2]): 84 | speed = f'-{aux[1]}' 85 | package = aux[2] 86 | else: 87 | speed = f'-{aux[2]}' 88 | package = aux[1] 89 | else: 90 | valid = 'DEVICE-[SPEED][-]PACKAGE[PRANGE][-SPEED]' 91 | raise ValueError(f'Invalid PART format ({valid})') 92 | pranges = { 93 | 'c': 'COM', 94 | 'e': 'EXT', 95 | 'i': 'IND', 96 | 'm': 'MIL', 97 | 't1': 'TGrade1', 98 | 't2': 'TGrade2' 99 | } 100 | prange = 'COM' 101 | for suffix, name in pranges.items(): 102 | if package.endswith(suffix): 103 | package = package[:-len(suffix)] 104 | prange = name 105 | break 106 | # Finish 107 | return { 108 | 'family': family, 109 | 'device': device, 110 | 'speed': speed, 111 | 'package': package, 112 | 'prange': prange 113 | } 114 | -------------------------------------------------------------------------------- /pyfpga/openflow.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2020-2024 PyFPGA Project 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | # 6 | 7 | """ 8 | Implements support for an Open Source development flow. 9 | """ 10 | 11 | from pathlib import Path 12 | from pyfpga.project import Project 13 | 14 | 15 | class Openflow(Project): 16 | """Class to support Open Source tools.""" 17 | 18 | def _configure(self): 19 | tool = 'openflow' 20 | self.conf['tool'] = tool 21 | self.conf['make_cmd'] = f'bash {tool}.sh' 22 | self.conf['make_ext'] = 'sh' 23 | self.conf['prog_bit'] = ['svf', 'bit'] 24 | self.conf['prog_cmd'] = f'bash {tool}-prog.sh' 25 | self.conf['prog_ext'] = 'sh' 26 | 27 | def _make_custom(self): 28 | info = get_info(self.data.get('part', 'hx8k-ct256')) 29 | self.data['family'] = info['family'] 30 | self.data['device'] = info['device'] 31 | self.data['package'] = info['package'] 32 | 33 | def _prog_custom(self): 34 | info = get_info(self.data.get('part', 'hx8k-ct256')) 35 | self.data['family'] = info['family'] 36 | 37 | @staticmethod 38 | def _get_absolute(path, ext): 39 | return Path(path).resolve().as_posix() 40 | 41 | 42 | def get_info(part): 43 | """Get info about the FPGA part. 44 | 45 | :param part: the FPGA part as specified by the tool 46 | :returns: a dict with the keys family, device and package 47 | """ 48 | part = part.lower().replace(' ', '') 49 | # Looking for the family 50 | family = None 51 | families = [ 52 | # From /techlibs/xilinx/synth_xilinx.cc 53 | 'xcup', 'xcu', 'xc7', 'xc6s', 'xc6v', 'xc5v', 'xc4v', 'xc3sda', 54 | 'xc3sa', 'xc3se', 'xc3s', 'xc2vp', 'xc2v', 'xcve', 'xcv' 55 | ] 56 | for item in families: 57 | if part.startswith(item): 58 | family = item 59 | break 60 | families = [ 61 | # From /ice40/main.cc 62 | 'lp384', 'lp1k', 'lp4k', 'lp8k', 'hx1k', 'hx4k', 'hx8k', 63 | 'up3k', 'up5k', 'u1k', 'u2k', 'u4k' 64 | ] 65 | if part.startswith(tuple(families)): 66 | family = 'ice40' 67 | families = [ 68 | # From /ecp5/main.cc 69 | '12k', '25k', '45k', '85k', 'um-25k', 'um-45k', 'um-85k', 70 | 'um5g-25k', 'um5g-45k', 'um5g-85k' 71 | ] 72 | if part.startswith(tuple(families)): 73 | family = 'ecp5' 74 | # Looking for the other values 75 | device = None 76 | package = None 77 | aux = part.split('-') 78 | if len(aux) == 2: 79 | device = aux[0] 80 | package = aux[1] 81 | elif len(aux) == 3: 82 | device = f'{aux[0]}-{aux[1]}' 83 | package = aux[2] 84 | else: 85 | valid = 'DEVICE-PACKAGE' 86 | raise ValueError(f'Invalid PART format ({valid})') 87 | if family in ['lp4k', 'hx4k']: # See http://www.clifford.at/icestorm 88 | device = device.replace('4', '8') 89 | package += ":4k" 90 | if family == 'ecp5': 91 | package = package.upper() 92 | # Finish 93 | return { 94 | 'family': family, 95 | 'device': device, 96 | 'package': package 97 | } 98 | -------------------------------------------------------------------------------- /pyfpga/project.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019-2025 PyFPGA Project 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | # 6 | 7 | """ 8 | Base class that implements agnostic methods to deal with FPGA projects. 9 | """ 10 | 11 | import glob 12 | import logging 13 | import os 14 | import subprocess 15 | 16 | from pathlib import Path 17 | from time import time 18 | from jinja2 import Environment, FileSystemLoader 19 | 20 | 21 | STEPS = { 22 | 'cfg': 'Project Creation', 23 | 'syn': 'Synthesis', 24 | 'par': 'Place and Route', 25 | 'bit': 'Bitstream generation' 26 | } 27 | 28 | 29 | class Project: 30 | """Base class to manage an FPGA project. 31 | 32 | :param project: project name (tool name when nothing specified) 33 | :type project: str, optional 34 | :param odir: output directory 35 | :type odir: str, optional 36 | """ 37 | 38 | def __init__(self, project=None, odir='results'): 39 | """Class constructor.""" 40 | self.conf = {} 41 | self.data = {} 42 | self._configure() 43 | self.data['project'] = project or self.conf['tool'] 44 | self.odir = odir 45 | # logging config 46 | self.logger = logging.getLogger(self.__class__.__name__) 47 | if not self.logger.handlers: 48 | self.logger.setLevel(logging.INFO) 49 | handler = logging.StreamHandler() 50 | formatter = logging.Formatter( 51 | '%(asctime)s - %(levelname)s - %(message)s', 52 | datefmt='%Y-%m-%d %H:%M:%S' 53 | ) 54 | handler.setFormatter(formatter) 55 | self.logger.addHandler(handler) 56 | 57 | def set_part(self, name): 58 | """Set the FPGA part name. 59 | 60 | :param name: FPGA part name 61 | :type name: str 62 | """ 63 | self.logger.debug('Executing set_part: %s', name) 64 | self.data['part'] = name 65 | 66 | def add_include(self, path): 67 | """Add an Include path. 68 | 69 | Specify where to search for Included Verilog Files, IP repos, etc. 70 | 71 | :param path: path of a directory 72 | :type name: str 73 | :raises NotADirectoryError: if path is not a directory 74 | """ 75 | self.logger.debug('Executing add_include: %s', path) 76 | path = self._get_absolute(path, self.conf['make_ext']) 77 | if not Path(path).is_dir(): 78 | raise NotADirectoryError(path) 79 | self.data.setdefault('includes', []).append(path) 80 | 81 | def _add_file(self, pathname, hdl=None, lib=None): 82 | files = glob.glob(pathname, recursive=True) 83 | if len(files) == 0: 84 | raise FileNotFoundError(pathname) 85 | for file in files: 86 | path = self._get_absolute(file, self.conf['make_ext']) 87 | attr = {} 88 | if hdl: 89 | attr['hdl'] = hdl 90 | if lib: 91 | attr['lib'] = lib 92 | if path in self.data.get('files', {}): 93 | del self.data['files'][path] 94 | self.data.setdefault('files', {})[path] = attr 95 | 96 | def add_slog(self, pathname): 97 | """Add System Verilog file/s. 98 | 99 | :param pathname: path to a SV file (glob compliant) 100 | :type pathname: str 101 | :raises FileNotFoundError: when pathname is not found 102 | """ 103 | self.logger.debug('Executing add_slog: %s', pathname) 104 | self._add_file(pathname, 'slog') 105 | 106 | def add_vhdl(self, pathname, lib=None): 107 | """Add VHDL file/s. 108 | 109 | :param pathname: path to a SV file (glob compliant) 110 | :type pathname: str 111 | :param lib: VHDL library name 112 | :type lib: str, optional 113 | :raises FileNotFoundError: when pathname is not found 114 | """ 115 | lib_str = 'default library' if lib is None else lib 116 | self.logger.debug('Executing add_vhdl: %s : %s', lib_str, pathname) 117 | self._add_file(pathname, 'vhdl', lib) 118 | 119 | def add_vlog(self, pathname): 120 | """Add Verilog file/s. 121 | 122 | :param pathname: path to a SV file (glob compliant) 123 | :type pathname: str 124 | :raises FileNotFoundError: when pathname is not found 125 | """ 126 | self.logger.debug('Executing add_vlog: %s', pathname) 127 | self._add_file(pathname, 'vlog') 128 | 129 | def add_cons(self, path): 130 | """Add a constraint file. 131 | 132 | :param pathname: path of a file 133 | :type pathname: str 134 | :raises FileNotFoundError: if path is not found 135 | """ 136 | self.logger.debug('Executing add_cons: %s', path) 137 | path = self._get_absolute(path, self.conf['make_ext']) 138 | if not Path(path).is_file(): 139 | raise FileNotFoundError(path) 140 | attr = {} 141 | self.data.setdefault('constraints', {})[path] = attr 142 | 143 | def add_param(self, name, value): 144 | """Add a Parameter/Generic Value. 145 | 146 | :param name: parameter/generic name 147 | :type name: str 148 | :param value: parameter/generic value 149 | :type name: str 150 | """ 151 | self.logger.debug('Executing add_param: %s : %s', name, value) 152 | self.data.setdefault('params', {})[name] = value 153 | 154 | def add_define(self, name, value): 155 | """Add a Verilog Defile Value. 156 | 157 | :param name: define name 158 | :type name: str 159 | :param value: define value 160 | :type name: str 161 | """ 162 | self.logger.debug('Executing add_define: %s : %s', name, value) 163 | self.data.setdefault('defines', {})[name] = value 164 | 165 | def add_fileset(self, pathname): 166 | """Add fileset file/s. 167 | 168 | :param pathname: path to a fileset file 169 | :type pathname: str 170 | :raises FileNotFoundError: when pathname is not found 171 | """ 172 | self.logger.debug('Executing add_fileset: %s', pathname) 173 | if not Path(pathname).is_file(): 174 | raise FileNotFoundError(pathname) 175 | raise NotImplementedError() 176 | 177 | def set_top(self, name): 178 | """Set the name of the top level component. 179 | 180 | :param name: top-level name 181 | :type name: str 182 | """ 183 | self.logger.debug('Executing set_top: %s', name) 184 | self.data['top'] = name 185 | 186 | def add_hook(self, stage, hook): 187 | """Add a hook in the specific stage. 188 | 189 | A hook is a place that allows you to insert customized code. 190 | 191 | :param stage: where to insert the hook 192 | :type stage: str 193 | :param hook: a tool-specific command 194 | :type hook: str 195 | :raises ValueError: when stage is invalid 196 | """ 197 | self.logger.debug('Executing add_hook: %s : %s', stage, hook) 198 | stages = [ 199 | 'precfg', 'postcfg', 'presyn', 'postsyn', 200 | 'prepar', 'postpar', 'prebit', 'postbit' 201 | ] 202 | if stage not in stages: 203 | raise ValueError('Invalid stage.') 204 | self.data.setdefault('hooks', {}).setdefault(stage, []).append(hook) 205 | 206 | def set_debug(self): 207 | """Enables debug messages.""" 208 | self.logger.setLevel(logging.DEBUG) 209 | 210 | def make(self, first='cfg', last='bit'): 211 | """Run the underlying tool. 212 | 213 | :param first: first step 214 | :type first: str, optional 215 | :param last: last step 216 | :type last: str, optional 217 | :raises ValueError: for missing or wrong values 218 | :raises RuntimeError: error running the needed underlying tool 219 | 220 | .. note:: valid steps are ``cfg``, ``syn``, ``par`` and ``bit``. 221 | """ 222 | self.logger.debug('Executing make') 223 | if last not in STEPS: 224 | raise ValueError('Invalid last step.') 225 | if first not in STEPS: 226 | raise ValueError('Invalid first step.') 227 | keys = list(STEPS.keys()) 228 | index = [keys.index(first), keys.index(last)] 229 | if index[0] > index[1]: 230 | raise ValueError('Invalid steps combination.') 231 | message = f'from {STEPS[first]} to {STEPS[last]}' 232 | if first == last: 233 | message = STEPS[first] 234 | self.logger.info('Running %s', message) 235 | self.data['steps'] = keys[index[0]:index[1]+1] 236 | self._make_custom() 237 | self._create_file(self.conf['tool'], self.conf['make_ext']) 238 | self._run(self.conf['make_cmd'], 'make.log') 239 | 240 | def _get_bitstream(self, bitstream=None): 241 | if not bitstream: 242 | for ext in self.conf['prog_bit']: 243 | candidate = Path(self.odir) / f'{self.data["project"]}.{ext}' 244 | if candidate.is_file(): 245 | bitstream = candidate 246 | break 247 | if not bitstream or not Path(bitstream).is_file(): 248 | raise FileNotFoundError(bitstream) 249 | return self._get_absolute(bitstream, self.conf['prog_ext']) 250 | 251 | def prog(self, bitstream=None, position=1): 252 | """Program the FPGA 253 | 254 | :param bitstream: bitstream to be programmed 255 | :type bitstream: str, optional 256 | :param position: position of the device in the JTAG chain 257 | :type position: str, optional 258 | :raises ValueError: for missing or wrong values 259 | :raises FileNotFoundError: when bitstream is not found 260 | :raises RuntimeError: error running the needed underlying tool 261 | """ 262 | self.logger.debug('Executing prog') 263 | if position not in range(1, 9): 264 | raise ValueError('Invalid position.') 265 | self.logger.info('Programming') 266 | self.data['bitstream'] = self._get_bitstream(bitstream) 267 | self.data['position'] = position 268 | self._prog_custom() 269 | self._create_file(f'{self.conf["tool"]}-prog', self.conf['prog_ext']) 270 | self._run(self.conf['prog_cmd'], 'prog.log') 271 | 272 | def _configure(self): 273 | raise NotImplementedError('Tool-dependent') 274 | 275 | def _make_custom(self): 276 | pass 277 | 278 | def _prog_custom(self): 279 | pass 280 | 281 | def _create_file(self, basename, extension): 282 | tempdir = Path(__file__).parent.joinpath('templates') 283 | jinja_file_loader = FileSystemLoader(str(tempdir)) 284 | jinja_env = Environment(loader=jinja_file_loader) 285 | jinja_template = jinja_env.get_template(f'{basename}.jinja') 286 | content = jinja_template.render(self.data) 287 | directory = Path(self.odir) 288 | directory.mkdir(parents=True, exist_ok=True) 289 | filename = f'{basename}.{extension}' 290 | with open(directory / filename, 'w', encoding='utf-8') as file: 291 | file.write(content) 292 | 293 | def _run(self, command, logname): 294 | num = 20 295 | error = 0 296 | old_dir = Path.cwd() 297 | new_dir = Path(self.odir) 298 | start = time() 299 | try: 300 | os.chdir(new_dir) 301 | with open(logname, 'w', encoding='utf-8') as file: 302 | subprocess.run( 303 | command, shell=True, check=True, text=True, 304 | stdout=file, stderr=subprocess.STDOUT 305 | ) 306 | except subprocess.CalledProcessError: 307 | with open(logname, 'r', encoding='utf-8') as file: 308 | lines = file.readlines() 309 | last_lines = lines[-num:] if len(lines) >= num else lines 310 | for line in last_lines: 311 | message = line.strip() 312 | if len(message): 313 | print(f'>> {message}') 314 | error = 1 315 | finally: 316 | os.chdir(old_dir) 317 | end = time() 318 | elapsed = end - start 319 | self.logger.info( 320 | 'Elapsed time %dh %dm %.2fs', 321 | int(elapsed // 3600), 322 | int((elapsed % 3600) // 60), 323 | elapsed % 60 324 | ) 325 | if error: 326 | raise RuntimeError('Problem with the underlying tool') 327 | 328 | @staticmethod 329 | def _get_absolute(path, ext): 330 | path = Path(path).resolve() 331 | if ext == 'tcl': 332 | return path.as_posix() 333 | return str(path) 334 | -------------------------------------------------------------------------------- /pyfpga/quartus.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019-2024 PyFPGA Project 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | # 6 | 7 | """ 8 | Implements support for Quartus. 9 | """ 10 | 11 | from pyfpga.project import Project 12 | 13 | 14 | class Quartus(Project): 15 | """Class to support Quartus projects.""" 16 | 17 | def _configure(self): 18 | tool = 'quartus' 19 | self.conf['tool'] = tool 20 | self.conf['make_cmd'] = f'quartus_sh --script {tool}.tcl' 21 | self.conf['make_ext'] = 'tcl' 22 | self.conf['prog_bit'] = ['sof', 'pof'] 23 | self.conf['prog_cmd'] = f'bash {tool}-prog.sh' 24 | self.conf['prog_ext'] = 'sh' 25 | 26 | def _make_custom(self): 27 | if 'part' not in self.data: 28 | self.data['part'] = '10M50SCE144I7G' 29 | -------------------------------------------------------------------------------- /pyfpga/templates/diamond-prog.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # Copyright (C) 2024-2025 PyFPGA Project 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | # 6 | #} 7 | 8 | if [ "$DIAMOND_XCF" == "" ]; then 9 | DIAMOND_XCF=impl1/impl1.xcf 10 | fi 11 | 12 | if [ -f "$DIAMOND_XCF" ]; then 13 | pgrcmd -infile $DIAMOND_XCF 14 | else 15 | echo "Automatic programming with Diamond is not yet supported." 16 | echo "Please create the `realpath $DIAMOND_XCF` file manually and rerun the prog command." 17 | echo "Hint: You can change the location of the XCF file by setting the DIAMOND_XCF environment variable." 18 | fi 19 | -------------------------------------------------------------------------------- /pyfpga/templates/diamond.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2015-2024 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | {% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- 10 | 11 | prj_project new -name {{ project }} -dev {{ part }} 12 | 13 | # For now, let's enforce Synplify as LSE (the default) has broken top level generic handling 14 | prj_syn set synplify 15 | 16 | {% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} 17 | 18 | {% if files %}# Files inclusion 19 | {% for name, attr in files.items() %} 20 | prj_src add {% if 'lib' in attr %}-work {{ attr.lib }}{% else %}{% endif %} {{ name }} 21 | {% endfor %} 22 | {% endif %} 23 | 24 | {% if constraints %} 25 | # Constraints inclusion 26 | # Diamond only supports one constraints file, so we need to combine them into the default diamond.lpf. 27 | # We can't just do `prj_src add ` multiple times. 28 | set fileId [open {{ project }}.lpf "w"] 29 | {% for name, attr in constraints.items() %} 30 | set fp [open "{{ name }}" r] 31 | set file_data [read $fp] 32 | close $fp 33 | puts -nonewline $fileId $file_data 34 | {% endfor %} 35 | close $fileId 36 | {% endif %} 37 | 38 | {% if top %}# Top-level specification 39 | prj_impl option top "{{ top }}" 40 | {% endif %} 41 | 42 | {% if includes %}# Verilog Includes 43 | {% for include in includes %} 44 | prj_impl option -append {include path} {{ "{"+include+"}" }} 45 | {% endfor %} 46 | {% endif %} 47 | 48 | {% if defines %}# Verilog Defines 49 | {% for key, value in defines.items() %} 50 | prj_impl option -append VERILOG_DIRECTIVES {{ key }}={{ value }} 51 | {% endfor %} 52 | {% endif %} 53 | 54 | {% if params %}# Verilog Parameters / VHDL Generics 55 | {% for key, value in params.items() %} 56 | prj_impl option -append HDL_PARAM {{ key }}={{ value }} 57 | {% endfor %} 58 | {% endif %} 59 | 60 | {% if hooks %}{{ hooks.postcfg | join('\n') }}{% endif %} 61 | 62 | prj_project save 63 | prj_project close 64 | 65 | {% endif %} 66 | 67 | {% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- 68 | 69 | prj_project open {{ project }}.ldf 70 | 71 | {% if 'syn' in steps %}# Synthesis 72 | 73 | {% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} 74 | 75 | prj_run Synthesis -forceOne 76 | 77 | {% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} 78 | 79 | {% endif %} 80 | 81 | {% if 'par' in steps %} # Translate, Map, and Place and Route 82 | {% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} 83 | 84 | prj_run Translate -forceOne 85 | prj_run Map -forceOne 86 | prj_run PAR -forceOne 87 | 88 | {% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} 89 | 90 | {% endif %} 91 | 92 | {% if 'bit' in steps %}# Bitstream generation 93 | 94 | {% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} 95 | 96 | prj_run Export -task Bitgen -forceOne 97 | 98 | {% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} 99 | 100 | {% endif %} 101 | 102 | prj_project save 103 | prj_project close 104 | 105 | {% endif %} 106 | -------------------------------------------------------------------------------- /pyfpga/templates/ise-prog.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2015-2025 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | cleancablelock 10 | 11 | {% if not spi and not bpi %} 12 | 13 | setMode -bs 14 | setCable -port auto 15 | Identify -inferir 16 | assignFile -p {{ position }} -file {{ bitstream }} 17 | Program -p {{ position }} 18 | 19 | {% elif spi %} 20 | 21 | setMode -pff 22 | addConfigDevice -name {{ name }} -path . 23 | setSubmode -pffspi 24 | addDesign -version 0 -name 0 25 | addDeviceChain -index 0 26 | addDevice -p 1 -file {{ bitstream }} 27 | generate -generic 28 | 29 | setMode -bs 30 | setCable -port auto 31 | Identify 32 | attachflash -position {{ position }} -spi {{ name }} 33 | assignfiletoattachedflash -position {{ position }} -file ./{{ name }}.mcs 34 | Program -p {{ position }} -dataWidth {{ width }} -spionly -e -v -loadfpga 35 | 36 | {% else %} 37 | 38 | setMode -pff 39 | addConfigDevice -name {{ name }} -path . 40 | setSubmode -pffbpi 41 | addDesign -version 0 -name 0 42 | addDeviceChain -index 0 43 | setAttribute -configdevice -attr flashDataWidth -value {{ width }} 44 | addDevice -p 1 -file {{ bitstream }} 45 | generate -generic 46 | 47 | setMode -bs 48 | setCable -port auto 49 | Identify 50 | attachflash -position {{ position }} -bpi {{ name }} 51 | assignfiletoattachedflash -position {{ position }} -file ./{{ name }}.mcs 52 | Program -p {{ position }} -dataWidth {{ width }} -rs1 NONE -rs0 NONE -bpionly -e -v -loadfpga 53 | 54 | {% endif %} 55 | 56 | quit 57 | -------------------------------------------------------------------------------- /pyfpga/templates/ise.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2015-2024 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | {% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- 10 | 11 | if { [ file exists {{ project }}.xise ] } { file delete {{ project }}.xise } 12 | project new {{ project }}.xise 13 | project set family {{ family }} 14 | project set device {{ device }} 15 | project set package {{ package }} 16 | project set speed {{ speed }} 17 | 18 | {% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} 19 | 20 | {% if files %}# Files inclusion 21 | {% for name, attr in files.items() %} 22 | {% if 'lib' in attr %}lib_vhdl new {{ attr.lib }}{% endif %} 23 | xfile add {{ name }}{% if 'lib' in attr %} -lib_vhdl {{ attr.lib }}{% endif %} 24 | {% endfor %} 25 | {% endif %} 26 | 27 | {% if constraints %}# Constraints inclusion 28 | {% for name, attr in constraints.items() %} 29 | xfile add {{ name }} 30 | {% if name.endswith('.xcf') %} 31 | project set "Synthesis Constraints File" "{{ name }}" -process "Synthesize - XST" 32 | {% endif %} 33 | {% endfor %} 34 | {% endif %} 35 | 36 | {% if top %}# Top-level specification 37 | project set top {{ top }} 38 | {% endif %} 39 | 40 | {% if includes %}# Verilog Includes 41 | project set "Verilog Include Directories" "{{ includes | join('|') }}" -process "Synthesize - XST" 42 | {% endif %} 43 | 44 | {% if defines %}# Verilog Defines 45 | project set "Verilog Macros" "{{ defines.items() | map('join', '=') | join(' | ') }}" -process "Synthesize - XST" 46 | {% endif %} 47 | 48 | {% if params %}# Verilog Parameters / VHDL Generics 49 | project set "Generics, Parameters" "{{ params.items() | map('join', '=') | join(' ') }}" -process "Synthesize - XST" 50 | {% endif %} 51 | 52 | {% if hooks %}{{ hooks.postcfg | join('\n') }}{% endif %} 53 | 54 | project close 55 | 56 | {% endif %} 57 | 58 | {% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- 59 | 60 | project open {{ project }}.xise 61 | 62 | {% if 'syn' in steps %}# Synthesis 63 | 64 | {% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} 65 | 66 | # PRESYNTH 67 | #project set top_level_module_type "EDIF" 68 | project clean 69 | process run "Synthesize" 70 | if { [process get "Synthesize" status] == "errors" } { exit 1 } 71 | 72 | {% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} 73 | 74 | {% endif %} 75 | 76 | {% if 'par' in steps %}# Place and Route 77 | 78 | {% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} 79 | 80 | process run "Translate" 81 | if { [process get "Translate" status] == "errors" } { exit 1 } 82 | process run "Map" 83 | if { [process get "Map" status] == "errors" } { exit 1 } 84 | process run "Place & Route" 85 | if { [process get "Place & Route" status] == "errors" } { exit 1 } 86 | 87 | {% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} 88 | 89 | {% endif %} 90 | 91 | {% if 'bit' in steps %}# Bitstream generation 92 | 93 | {% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} 94 | 95 | process run "Generate Programming File" 96 | if { [process get "Generate Programming File" status] == "errors" } { exit 1 } 97 | catch { file copy -force {{ top }}.bit {{ project }}.bit } 98 | 99 | {% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} 100 | 101 | {% endif %} 102 | 103 | project close 104 | 105 | {% endif %} 106 | -------------------------------------------------------------------------------- /pyfpga/templates/libero-prog.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2024 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | file delete -force -- libero-prog 10 | new_project -name libero -location libero-prog -mode single 11 | 12 | set_programming_file -file {{ bitstream }} 13 | set_programming_action -action {PROGRAM} 14 | 15 | run_selected_actions 16 | -------------------------------------------------------------------------------- /pyfpga/templates/libero.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2015-2024 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | {% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- 10 | 11 | if { [ file exists {{ project }} ] } { file delete -force -- {{ project }} } 12 | new_project -name {{ project }} -location libero -hdl VERILOG -family {{ family }} 13 | set_device -family {{ family }} -die {{ device }} -package {{ package}} -speed {{ speed }} -part_range {{ prange }} 14 | 15 | {% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} 16 | 17 | {% if includes %}# Verilog Includes 18 | set_global_include_path_order -paths "{{ includes | join(' ') }}" 19 | {% endif %} 20 | 21 | {% if files %}# Files inclusion 22 | {% for name, attr in files.items() %} 23 | create_links -hdl_source {{ name }}{% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} 24 | {% endfor %} 25 | {% endif %} 26 | 27 | {% if constraints %}# Constraints inclusion 28 | {% for name, attr in constraints.items() %} 29 | create_links {% if name.endswith('.sdc') %}-sdc{% else %}-io_pdc{% endif %} {{ name }} 30 | {% endfor %} 31 | {% endif %} 32 | 33 | build_design_hierarchy 34 | 35 | {% if top %}# Top-level specification 36 | set_root {{ top }} 37 | {% endif %} 38 | 39 | {% if constraints %}# Constraints configuration 40 | {% set sdc_files = [] %} 41 | {% set pdc_files = [] %} 42 | {% for name, attr in constraints.items() %} 43 | {% if name.endswith('.sdc') %} 44 | {% set _ = sdc_files.append(name) %} 45 | {% endif %} 46 | {% set _ = pdc_files.append(name) %} 47 | {% endfor %} 48 | {% endif %} 49 | 50 | {% if sdc_files %}organize_tool_files -tool {SYNTHESIZE} -file {{ sdc_files | join(' -file ') }} -module {{ top }} -input_type {constraint}{% endif %} 51 | {% if pdc_files %}organize_tool_files -tool {PLACEROUTE} -file {{ pdc_files | join(' -file ') }} -module {{ top }} -input_type {constraint}{% endif %} 52 | {% if sdc_files %}organize_tool_files -tool {VERIFYTIMING} -file {{ sdc_files | join(' -file ') }} -module {{ top }} -input_type {constraint}{% endif %} 53 | 54 | {% if includes or defines or params %}# Synopsys configuration 55 | configure_tool -name {SYNTHESIZE} -params {SYNPLIFY_OPTIONS: 56 | 57 | {% if includes %}# Verilog Includes (Synopsys) 58 | {% for include in includes %} 59 | set_option -include_path "{{ include }}" 60 | {% endfor %} 61 | {% endif %} 62 | 63 | {% if defines %}# Verilog Defines (Synopsys) 64 | {% for key, value in defines.items() %} 65 | set_option -hdl_define -set {{ key }}={{ value }} 66 | {% endfor %} 67 | {% endif %} 68 | 69 | {% if params %}# Verilog Parameters / VHDL Generics (Synopsys) 70 | {% for key, value in params.items() %} 71 | set_option -hdl_param -set {{ key }}={{ value }} 72 | {% endfor %} 73 | {% endif %} 74 | 75 | } 76 | {% endif %} 77 | 78 | {% if hooks %}{{ hooks.postcfg | join('\n') }}{% endif %} 79 | 80 | close_project 81 | 82 | {% endif %} 83 | 84 | {% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- 85 | 86 | if { [catch {open_project {{ project }}/{{ project }}.prjx} ] } { 87 | open_project {{ project }}.prjx 88 | } 89 | 90 | {% if 'syn' in steps %}# Synthesis 91 | 92 | {% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} 93 | 94 | run_tool -name {SYNTHESIZE} 95 | 96 | {% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} 97 | 98 | {% endif %} 99 | 100 | {% if 'par' in steps %}# Place and Route 101 | 102 | {% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} 103 | 104 | run_tool -name {PLACEROUTE} 105 | run_tool -name {VERIFYTIMING} 106 | 107 | {% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} 108 | 109 | {% endif %} 110 | 111 | {% if 'bit' in steps %}# Bitstream generation 112 | 113 | {% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} 114 | 115 | run_tool -name {GENERATEPROGRAMMINGFILE} 116 | catch { file copy -force {{ project }}/designer/{{ top }}/{{ top }}.ppd {{ project }}.ppd } 117 | 118 | {% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} 119 | 120 | {% endif %} 121 | 122 | close_project 123 | 124 | {% endif %} 125 | -------------------------------------------------------------------------------- /pyfpga/templates/openflow-prog.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2020-2025 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | set -e 10 | 11 | DOCKER="docker run --rm -v $HOME:$HOME -w $PWD" 12 | 13 | {% if family == 'ecp5' %} 14 | $DOCKER --device /dev/bus/usb hdlc/prog openocd -f /usr/share/trellis/misc/openocd/ecp5-evn.cfg -c "transport select jtag; init; svf {{ bitstream }}; exit" 15 | {% else %} 16 | $DOCKER --device /dev/bus/usb hdlc/prog iceprog {{ bitstream }} 17 | {% endif %} 18 | -------------------------------------------------------------------------------- /pyfpga/templates/openflow.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2020-2024 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | set -e 10 | 11 | DOCKER="docker run --user $(id -u):$(id -g) --rm -v $HOME:$HOME -w $PWD" 12 | 13 | {% if 'syn' in steps %}# Synthesis 14 | $DOCKER hdlc/ghdl:yosys /bin/bash -c " 15 | {% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} 16 | 17 | {% set gflags = '--std=08 -fsynopsys -fexplicit -frelaxed' %} 18 | {% if files %}# VHDL Files inclusion 19 | {% for name, attr in files.items() %} 20 | {% if attr.hdl == "vhdl" %} 21 | ghdl -a {{ gflags }}{% if 'lib' in attr %} --work={{ attr.lib }}{% endif %} {{ name }} 22 | {% endif %} 23 | {% endfor %} 24 | {% endif %} 25 | 26 | yosys -Q -m ghdl -p ' 27 | 28 | {% if includes %}# Verilog Includes 29 | verilog_defaults -add{% for path in includes %} -I{{ path }}{% endfor %} 30 | {% endif %} 31 | 32 | {% if defines %}# Verilog Defines 33 | verilog_defines{% for key, value in defines.items() %} -D{{ key }}={{ value }}{% endfor %} 34 | {% endif %} 35 | 36 | {% if files %}# VLOG Files inclusion 37 | {% for name, attr in files.items() %} 38 | {% if attr.hdl == "vlog" %} 39 | read_verilog -defer {{ name }} 40 | {% elif attr.hdl == "slog" %} 41 | read_verilog -defer -sv {{ name }} 42 | {% elif attr.hdl == "vhdl" %} 43 | {% if loop.first %} 44 | {% if params %}# VHDL Generics 45 | ghdl {{ gflags }}{% for key, value in params.items() %} -g{{ key }}={{ value }}{% endfor %} {{ top }} 46 | {% else %} 47 | ghdl {{ gflags }} {{ top }} 48 | {% endif %} 49 | {% endif %} 50 | {% endif %} 51 | {% if loop.last and attr.hdl in ["vlog", "slog"] and params %}# Verilog Parameters 52 | chparam{% for key, value in params.items() %} -set {{ key }} {{ value }}{% endfor %} 53 | {% endif %} 54 | {% endfor %} 55 | {% endif %} 56 | 57 | # Top-level specification and Syntesis 58 | {% if family in ['ice40', 'ecp5'] %} 59 | synth_{{ family }} -top {{ top }} -json {{ project }}.json 60 | {% elif family in ['xc6s', 'xc6v', 'xc5v', 'xc4v', 'xc3sda', 'xc3sa', 'xc3se', 'xc3s', 'xc2vp', 'xc2v', 'xcve', 'xcv'] %} 61 | synth_xilinx -top {{ top }} -family {{ family }} 62 | write_edif -pvector bra {{ project }}.edif -ise 63 | {% elif family %} 64 | synth_xilinx -top {{ top }} -family {{ family }} 65 | write_edif -pvector bra {{ project }}.edif 66 | {% else %} 67 | synth -top {{ top }} 68 | write_verilog {{ project }}.v 69 | {% endif %} 70 | ' 71 | {% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} 72 | " 73 | {% endif %} 74 | 75 | {% if 'par' in steps %}# Place and Route 76 | 77 | CONSTRAINTS="{{ constraints | join(' ') }}" 78 | 79 | {% if family == 'ice40' %} 80 | {% if constraints %} 81 | cat $CONSTRAINTS > {{ project }}.pcf 82 | CONSTRAINT="--pcf {{ project }}.pcf" 83 | {% endif %} 84 | $DOCKER hdlc/nextpnr:ice40 /bin/bash -c " 85 | {% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} 86 | nextpnr-ice40 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ project }}.json --asc {{ project }}.asc 87 | {% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} 88 | " 89 | $DOCKER hdlc/icestorm /bin/bash -c " 90 | icetime -d {{ device }} -mtr {{ project }}.rpt {{ project }}.asc 91 | " 92 | {% endif %} 93 | 94 | {% if family == 'ecp5' %} 95 | {% if constraints %} 96 | cat $CONSTRAINTS > {{ project }}.lpf 97 | CONSTRAINT="--lpf {{ project }}.lpf" 98 | {% endif %} 99 | $DOCKER hdlc/nextpnr:ecp5 /bin/bash -c " 100 | {% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} 101 | nextpnr-ecp5 --{{ device }} --package {{ package }} $CONSTRAINT --json {{ project }}.json --textcfg {{ project }}.config 102 | {% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} 103 | " 104 | {% endif %} 105 | 106 | {% endif %} 107 | 108 | {% if 'bit' in steps %}# Bitstream generation 109 | 110 | {% if family == 'ice40' %} 111 | $DOCKER hdlc/icestorm /bin/bash -c " 112 | {% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} 113 | icepack {{ project }}.asc {{ project }}.bit 114 | {% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} 115 | " 116 | {% endif %} 117 | 118 | {% if family == 'ecp5' %} 119 | $DOCKER hdlc/prjtrellis /bin/bash -c " 120 | {% if hooks %}{{ prebit | join('\n') }}{% endif %} 121 | ecppack --svf {{ project }}.svf {{ project }}.config {{ project }}.bit 122 | {% if hooks %}{{ postbit | join('\n') }}{% endif %} 123 | " 124 | {% endif %} 125 | 126 | {% endif %} 127 | -------------------------------------------------------------------------------- /pyfpga/templates/quartus-prog.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2015-2025 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | RESULT=$(jtagconfig) 10 | echo "$RESULT" 11 | 12 | CABLE=$(echo "$RESULT" | awk -F '1\\) | \\[' '/1\)/ {print $2}') 13 | 14 | quartus_pgm -c $CABLE --mode jtag -o "p;{{ bitstream }}@{{ position }}" 15 | -------------------------------------------------------------------------------- /pyfpga/templates/quartus.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2015-2024 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | {% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- 10 | 11 | package require ::quartus::project 12 | project_new {{ project }} -overwrite 13 | set_global_assignment -name DEVICE {{ part }} 14 | 15 | {% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} 16 | 17 | {% if files %}# Files inclusion 18 | {% for name, attr in files.items() %} 19 | {% if attr.hdl == "vhdl" %} 20 | set_global_assignment -name VHDL_FILE {{ name }} {% if 'lib' in attr %} -library {{ attr.lib }}{% endif %} 21 | {% elif attr.hdl == "vlog" %} 22 | set_global_assignment -name VERILOG_FILE {{ name }} 23 | {% elif attr.hdl == "slog" %} 24 | set_global_assignment -name SYSTEMVERILOG_FILE {{ name }} 25 | {% endif %} 26 | {% endfor %} 27 | {% endif %} 28 | 29 | {% if constraints %}# Constraints inclusion 30 | {% for name, attr in constraints.items() %} 31 | {% if name.endswith('.sdc') %} 32 | set_global_assignment -name SDC_FILE {{ name }} 33 | {% else %} 34 | source {{ name }} 35 | {% endif %} 36 | {% endfor %} 37 | {% endif %} 38 | 39 | {% if top %}# Top-level specification 40 | set_global_assignment -name TOP_LEVEL_ENTITY {{ top }} 41 | {% endif %} 42 | 43 | {% if includes %}# Verilog Includes 44 | {% for include in includes %} 45 | set_global_assignment -name SEARCH_PATH {{ include }} 46 | {% endfor %} 47 | {% endif %} 48 | 49 | {% if defines %}# Verilog Defines 50 | {% for key, value in defines.items() %} 51 | set_global_assignment -name VERILOG_MACRO {{ key }}={{ value }} 52 | {% endfor %} 53 | {% endif %} 54 | 55 | {% if params %}# Verilog Parameters / VHDL Generics 56 | {% for key, value in params.items() %} 57 | set_parameter -name {{ key }} {{ value }} 58 | {% endfor %} 59 | {% endif %} 60 | 61 | {% if hooks %}{{ hooks.postcfg | join('\n') }}{% endif %} 62 | 63 | project_close 64 | 65 | {% endif %} 66 | 67 | {% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- 68 | 69 | package require ::quartus::flow 70 | project_open -force {{ project }}.qpf 71 | # set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL 72 | 73 | {% if 'syn' in steps %}# Synthesis 74 | 75 | {% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} 76 | 77 | execute_module -tool map 78 | 79 | {% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} 80 | 81 | {% endif %} 82 | 83 | {% if 'par' in steps %}# Place and Route 84 | 85 | {% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} 86 | 87 | execute_module -tool fit 88 | execute_module -tool sta 89 | 90 | {% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} 91 | 92 | {% endif %} 93 | 94 | {% if 'bit' in steps %}# Bitstream generation 95 | 96 | {% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} 97 | 98 | execute_module -tool asm 99 | 100 | {% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} 101 | 102 | {% endif %} 103 | 104 | project_close 105 | 106 | {% endif %} 107 | -------------------------------------------------------------------------------- /pyfpga/templates/vivado-prog.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2015-2024 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | if { [ catch { open_hw_manager } ] } { open_hw } 10 | connect_hw_server 11 | open_hw_target 12 | puts [get_hw_devices] 13 | set obj [lindex [get_hw_devices [current_hw_device]] 0] 14 | set_property PROGRAM.FILE {{ bitstream }} $obj 15 | program_hw_devices $obj 16 | -------------------------------------------------------------------------------- /pyfpga/templates/vivado.jinja: -------------------------------------------------------------------------------- 1 | {# 2 | # 3 | # Copyright (C) 2015-2024 PyFPGA Project 4 | # 5 | # SPDX-License-Identifier: GPL-3.0-or-later 6 | # 7 | #} 8 | 9 | {% if 'cfg' in steps %}# Project configuration ------------------------------------------------------- 10 | 11 | create_project -force {{ project }} 12 | set_property SOURCE_MGMT_MODE None [current_project] 13 | set_property STEPS.SYNTH_DESIGN.ARGS.ASSERT true [get_runs synth_1] 14 | set_property PART {{ part }} [current_project] 15 | 16 | {% if hooks %}{{ hooks.precfg | join('\n') }}{% endif %} 17 | 18 | {% if files %}# Files inclusion 19 | {% for name, attr in files.items() %} 20 | add_file {{ name }} 21 | {% if 'lib' in attr %}set_property library {{ attr.lib }} [get_files {{ name }}]{% endif %} 22 | {% endfor %} 23 | {% endif %} 24 | 25 | {% if constraints %}# Constraints inclusion 26 | {% for name, attr in constraints.items() %} 27 | add_file -fileset constrs_1 {{ name }} 28 | {% if loop.first %}set_property TARGET_CONSTRS_FILE {{ name }} [current_fileset -constrset]{% endif %} 29 | {% endfor %} 30 | {% endif %} 31 | 32 | {% if top %}# Top-level specification 33 | set_property TOP {{ top }} [current_fileset] 34 | {% endif %} 35 | 36 | {% if includes %}# Verilog Includes 37 | set_property INCLUDE_DIRS { {{ includes | join(' ') }} } [current_fileset] 38 | {% endif %} 39 | 40 | {% if defines %}# Verilog Defines 41 | set_property VERILOG_DEFINE { {{ defines.items() | map('join', '=') | join(' ') }} } [current_fileset] 42 | {% endif %} 43 | 44 | {% if params %}# Verilog Parameters / VHDL Generics 45 | set_property GENERIC { {{ params.items() | map('join', '=') | join(' ') }} } -objects [get_filesets sources_1] 46 | {% endif %} 47 | 48 | {% if hooks %}{{ hooks.postcfg | join('\n') }}{% endif %} 49 | 50 | close_project 51 | 52 | {% endif %} 53 | 54 | {% if 'syn' in steps or 'par' in steps or 'bit' in steps %}# Design flow ----------------------------------------------------------------- 55 | 56 | open_project {{ project }} 57 | 58 | {% if 'syn' in steps %}# Synthesis 59 | 60 | {% if hooks %}{{ hooks.presyn | join('\n') }}{% endif %} 61 | 62 | # PRESYNTH 63 | # set_property DESIGN_MODE GateLvl [current_fileset] 64 | reset_run synth_1 65 | launch_runs synth_1 66 | wait_on_run synth_1 67 | #report_property [get_runs synth_1] 68 | if { [get_property STATUS [get_runs synth_1]] ne "synth_design Complete!" } { exit 1 } 69 | 70 | {% if hooks %}{{ hooks.postsyn | join('\n') }}{% endif %} 71 | 72 | {% endif %} 73 | 74 | {% if 'par' in steps %}# Place and Route 75 | 76 | {% if hooks %}{{ hooks.prepar | join('\n') }}{% endif %} 77 | 78 | reset_run impl_1 79 | launch_runs impl_1 80 | wait_on_run impl_1 81 | #report_property [get_runs impl_1] 82 | if { [get_property STATUS [get_runs impl_1]] ne "route_design Complete!" } { exit 1 } 83 | 84 | {% if hooks %}{{ hooks.postpar | join('\n') }}{% endif %} 85 | 86 | {% endif %} 87 | 88 | {% if 'bit' in steps %}# Bitstream generation 89 | 90 | {% if hooks %}{{ hooks.prebit | join('\n') }}{% endif %} 91 | 92 | open_run impl_1 93 | write_bitstream -force {{ project }} 94 | write_debug_probes -force -quiet {{ project }}.ltx 95 | 96 | {% if hooks %}{{ hooks.postbit | join('\n') }}{% endif %} 97 | 98 | {% endif %} 99 | 100 | close_project 101 | 102 | {% endif %} 103 | -------------------------------------------------------------------------------- /pyfpga/vivado.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2019-2024 PyFPGA Project 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | # 6 | 7 | """ 8 | Implements support for Vivado. 9 | """ 10 | 11 | from pyfpga.project import Project 12 | 13 | 14 | class Vivado(Project): 15 | """Class to support Vivado projects.""" 16 | 17 | def _configure(self): 18 | tool = 'vivado' 19 | command = 'vivado -mode batch -notrace -quiet -source' 20 | self.conf['tool'] = tool 21 | self.conf['make_cmd'] = f'{command} {tool}.tcl' 22 | self.conf['make_ext'] = 'tcl' 23 | self.conf['prog_bit'] = ['bit'] 24 | self.conf['prog_cmd'] = f'{command} {tool}-prog.tcl' 25 | self.conf['prog_ext'] = 'tcl' 26 | 27 | def _make_custom(self): 28 | if 'part' not in self.data: 29 | self.data['part'] = 'xc7k160t-3-fbg484' 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | import pyfpga 4 | 5 | with open('README.md', 'r') as fh: 6 | long_description = fh.read() 7 | 8 | setup( 9 | name='PyFPGA', 10 | version=pyfpga.__version__, 11 | description='A Python package to use FPGA development tools programmatically', 12 | long_description=long_description, 13 | long_description_content_type='text/markdown', 14 | author='Rodrigo A. Melo', 15 | author_email='rodrigomelo9@gmail.com', 16 | license='GPLv3', 17 | url='https://github.com/PyFPGA/pyfpga', 18 | package_data={'': ['templates/*.jinja', 'helpers/*']}, 19 | packages=find_packages(), 20 | entry_points={ 21 | 'console_scripts': [ 22 | 'hdl2bit = pyfpga.helpers.hdl2bit:main', 23 | 'prj2bit = pyfpga.helpers.prj2bit:main', 24 | 'bitprog = pyfpga.helpers.bitprog:main' 25 | ], 26 | }, 27 | classifiers=[ 28 | 'Development Status :: 4 - Beta', 29 | 'Environment :: Console', 30 | 'Intended Audience :: Developers', 31 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 32 | 'Operating System :: OS Independent', 33 | 'Programming Language :: Python :: 3.8', 34 | 'Programming Language :: Python :: 3.9', 35 | 'Programming Language :: Python :: 3.10', 36 | 'Programming Language :: Python :: 3.11', 37 | 'Programming Language :: Python :: 3.12', 38 | 'Topic :: Utilities', 39 | 'Topic :: Software Development :: Build Tools', 40 | "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)" 41 | ], 42 | install_requires=['jinja2'] 43 | ) 44 | -------------------------------------------------------------------------------- /tests/fakedata/cons/all.xdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/cons/all.xdc -------------------------------------------------------------------------------- /tests/fakedata/cons/par.xdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/cons/par.xdc -------------------------------------------------------------------------------- /tests/fakedata/cons/syn.xdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/cons/syn.xdc -------------------------------------------------------------------------------- /tests/fakedata/dir1/slog1.sv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir1/slog1.sv -------------------------------------------------------------------------------- /tests/fakedata/dir1/slog1.svh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir1/slog1.svh -------------------------------------------------------------------------------- /tests/fakedata/dir1/vhdl1.vhd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir1/vhdl1.vhd -------------------------------------------------------------------------------- /tests/fakedata/dir1/vhdl1.vhdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir1/vhdl1.vhdl -------------------------------------------------------------------------------- /tests/fakedata/dir1/vlog1.v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir1/vlog1.v -------------------------------------------------------------------------------- /tests/fakedata/dir1/vlog1.vh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir1/vlog1.vh -------------------------------------------------------------------------------- /tests/fakedata/dir2/slog2.sv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir2/slog2.sv -------------------------------------------------------------------------------- /tests/fakedata/dir2/slog2.svh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir2/slog2.svh -------------------------------------------------------------------------------- /tests/fakedata/dir2/vhdl2.vhd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir2/vhdl2.vhd -------------------------------------------------------------------------------- /tests/fakedata/dir2/vhdl2.vhdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir2/vhdl2.vhdl -------------------------------------------------------------------------------- /tests/fakedata/dir2/vlog2.v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir2/vlog2.v -------------------------------------------------------------------------------- /tests/fakedata/dir2/vlog2.vh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir2/vlog2.vh -------------------------------------------------------------------------------- /tests/fakedata/dir3/slog3.sv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir3/slog3.sv -------------------------------------------------------------------------------- /tests/fakedata/dir3/slog3.svh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir3/slog3.svh -------------------------------------------------------------------------------- /tests/fakedata/dir3/vhdl3.vhd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir3/vhdl3.vhd -------------------------------------------------------------------------------- /tests/fakedata/dir3/vhdl3.vhdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir3/vhdl3.vhdl -------------------------------------------------------------------------------- /tests/fakedata/dir3/vlog3.v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir3/vlog3.v -------------------------------------------------------------------------------- /tests/fakedata/dir3/vlog3.vh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/dir3/vlog3.vh -------------------------------------------------------------------------------- /tests/fakedata/slog0.sv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/slog0.sv -------------------------------------------------------------------------------- /tests/fakedata/slog0.svh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/slog0.svh -------------------------------------------------------------------------------- /tests/fakedata/vhdl0.vhd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/vhdl0.vhd -------------------------------------------------------------------------------- /tests/fakedata/vhdl0.vhdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/vhdl0.vhdl -------------------------------------------------------------------------------- /tests/fakedata/vlog0.v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/vlog0.v -------------------------------------------------------------------------------- /tests/fakedata/vlog0.vh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyFPGA/pyfpga/15f4d8e67aa1dfbab851f882e00b743cded5f579/tests/fakedata/vlog0.vh -------------------------------------------------------------------------------- /tests/mocks/FPExpress: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright (C) 2024-2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | import argparse 10 | import sys 11 | 12 | 13 | parser = argparse.ArgumentParser() 14 | 15 | parser.add_argument('source') 16 | 17 | args = parser.parse_args() 18 | 19 | tool = parser.prog 20 | 21 | if not args.source.startswith("SCRIPT:", 0): 22 | print('ERROR:the parameter should start width "SCRIPT:"') 23 | sys.exit(1) 24 | 25 | print(f'INFO:the {tool.upper()} mock has been executed') 26 | -------------------------------------------------------------------------------- /tests/mocks/FPExpress.bat: -------------------------------------------------------------------------------- 1 | @echo off & python "%~dp0%~n0" %* 2 | -------------------------------------------------------------------------------- /tests/mocks/diamondc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright (C) 2024-2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | import argparse 10 | import re 11 | import subprocess 12 | 13 | 14 | parser = argparse.ArgumentParser() 15 | 16 | parser.add_argument('source') 17 | 18 | args = parser.parse_args() 19 | 20 | tool = parser.prog 21 | 22 | tcl = f''' 23 | proc unknown args {{ }} 24 | 25 | source {args.source} 26 | ''' 27 | 28 | with open(f'{tool}-mock.tcl', 'w', encoding='utf-8') as file: 29 | file.write(tcl) 30 | 31 | subprocess.run( 32 | f'tclsh {tool}-mock.tcl', 33 | shell=True, 34 | check=True, 35 | universal_newlines=True 36 | ) 37 | 38 | pattern = r'prj_project\s+new\s+-name\s+(\S+)\s' 39 | with open(args.source, 'r', encoding='utf-8') as file: 40 | match = re.search(pattern, file.read()) 41 | if match: 42 | project = match.group(1) 43 | open(f'{project}.ldf', 'w', encoding='utf-8').close() 44 | open(f'{project}.bit', 'w', encoding='utf-8').close() 45 | 46 | print(f'INFO:the {tool.upper()} mock has been executed') 47 | -------------------------------------------------------------------------------- /tests/mocks/docker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright (C) 2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | open('openflow.bit', 'w', encoding='utf-8').close() 10 | open('openflow.svf', 'w', encoding='utf-8').close() 11 | 12 | print('INFO:the DOCKER mock has been executed') 13 | -------------------------------------------------------------------------------- /tests/mocks/impact: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright (C) 2022-2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | import argparse 10 | 11 | 12 | parser = argparse.ArgumentParser() 13 | 14 | parser.add_argument('-batch', action='store_true', required=True) 15 | parser.add_argument('source') 16 | 17 | args = parser.parse_args() 18 | 19 | tool = parser.prog 20 | 21 | print(f'INFO:the {tool.upper()} mock has been executed') 22 | -------------------------------------------------------------------------------- /tests/mocks/impact.bat: -------------------------------------------------------------------------------- 1 | @echo off & python "%~dp0%~n0" %* 2 | -------------------------------------------------------------------------------- /tests/mocks/jtagconfig: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright (C) 2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | print('1) USB-Blaster [USB-0]') 10 | print(' 02D120DD EP4CE22') 11 | -------------------------------------------------------------------------------- /tests/mocks/libero: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright (C) 2022-2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | import argparse 10 | import re 11 | import subprocess 12 | import sys 13 | 14 | from pathlib import Path 15 | 16 | 17 | parser = argparse.ArgumentParser() 18 | 19 | parser.add_argument('source') 20 | 21 | args = parser.parse_args() 22 | 23 | tool = parser.prog 24 | 25 | if not args.source.startswith("SCRIPT:", 0): 26 | print('ERROR:the parameter should start width "SCRIPT:"') 27 | sys.exit(1) 28 | 29 | args.source = args.source.replace('SCRIPT:', '') 30 | 31 | tcl = f''' 32 | proc unknown args {{ }} 33 | 34 | source {args.source} 35 | ''' 36 | 37 | with open(f'{tool}-mock.tcl', 'w', encoding='utf-8') as file: 38 | file.write(tcl) 39 | 40 | subprocess.run( 41 | f'tclsh {tool}-mock.tcl', 42 | shell=True, 43 | check=True, 44 | universal_newlines=True 45 | ) 46 | 47 | pattern = r'new_project\s+-name\s+(\S+)\s' 48 | with open(args.source, 'r', encoding='utf-8') as file: 49 | match = re.search(pattern, file.read()) 50 | if match: 51 | project = match.group(1) 52 | directory = Path('libero') 53 | directory.mkdir(parents=True, exist_ok=True) 54 | open(directory / f'{project}.prjx', 'w', encoding='utf-8').close() 55 | open(f'{project}.ppd', 'w', encoding='utf-8').close() 56 | 57 | print(f'INFO:the {tool.upper()} mock has been executed') 58 | -------------------------------------------------------------------------------- /tests/mocks/libero.bat: -------------------------------------------------------------------------------- 1 | @echo off & python "%~dp0%~n0" %* 2 | -------------------------------------------------------------------------------- /tests/mocks/pnmainc.bat: -------------------------------------------------------------------------------- 1 | @echo off & python "%~dp0diamondc" %* 2 | -------------------------------------------------------------------------------- /tests/mocks/quartus_pgm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright (C) 2022-2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | import argparse 10 | 11 | 12 | parser = argparse.ArgumentParser() 13 | 14 | parser.add_argument('-c', required=True) 15 | parser.add_argument('--mode', choices=['jtag'], required=True) 16 | parser.add_argument('-o', required=True) 17 | 18 | args = parser.parse_args() 19 | 20 | tool = parser.prog 21 | 22 | print(f'INFO:the {tool.upper()} mock has been executed') 23 | -------------------------------------------------------------------------------- /tests/mocks/quartus_pgm.bat: -------------------------------------------------------------------------------- 1 | @echo off & python "%~dp0%~n0" %* 2 | -------------------------------------------------------------------------------- /tests/mocks/quartus_sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright (C) 2022-2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | import argparse 10 | import os 11 | import re 12 | import subprocess 13 | 14 | 15 | parser = argparse.ArgumentParser() 16 | 17 | parser.add_argument('--script', required=True) 18 | 19 | args = parser.parse_args() 20 | 21 | tool = parser.prog 22 | 23 | tcl = f''' 24 | proc unknown args {{ }} 25 | 26 | package provide ::quartus::project 1.0 27 | namespace eval ::quartus::project {{ }} 28 | 29 | package provide ::quartus::flow 1.0 30 | namespace eval ::quartus::flow {{ }} 31 | 32 | source {args.script} 33 | ''' 34 | 35 | with open(f'{tool}-mock.tcl', 'w', encoding='utf-8') as file: 36 | file.write(tcl) 37 | 38 | subprocess.run( 39 | f'tclsh {tool}-mock.tcl', 40 | shell=True, 41 | check=True, 42 | universal_newlines=True 43 | ) 44 | 45 | pattern = r'project_new\s+(\S+)\s' 46 | with open(args.script, 'r', encoding='utf-8') as file: 47 | match = re.search(pattern, file.read()) 48 | if match: 49 | project = match.group(1) 50 | open(f'{project}.qpf', 'w', encoding='utf-8').close() 51 | open(f'{project}.sof', 'w', encoding='utf-8').close() 52 | 53 | print(f'INFO:the {tool.upper()} mock has been executed') 54 | -------------------------------------------------------------------------------- /tests/mocks/quartus_sh.bat: -------------------------------------------------------------------------------- 1 | @echo off & python "%~dp0%~n0" %* 2 | -------------------------------------------------------------------------------- /tests/mocks/source-me.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MDIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) 4 | 5 | export PATH=$MDIR:$PATH 6 | -------------------------------------------------------------------------------- /tests/mocks/vivado: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright (C) 2022-2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | import argparse 10 | import re 11 | import subprocess 12 | 13 | 14 | parser = argparse.ArgumentParser() 15 | 16 | parser.add_argument('-mode', choices=['batch'], required=True) 17 | parser.add_argument('-notrace', action='store_true', required=True) 18 | parser.add_argument('-quiet', action='store_true', required=True) 19 | parser.add_argument('-source', required=True) 20 | 21 | args = parser.parse_args() 22 | 23 | tool = parser.prog 24 | 25 | #proc create_project args {{ }} 26 | #proc open_project args {{ }} 27 | #proc current_project args {{ }} 28 | #proc current_fileset args {{ }} 29 | #proc get_filesets args {{ }} 30 | #proc set_property args {{ }} 31 | #proc add_files args {{ }} 32 | #proc get_files args {{ }} 33 | #proc reset_run args {{ }} 34 | #proc launch_runs args {{ }} 35 | #proc get_runs args {{ }} 36 | #proc wait_on_run args {{ }} 37 | #proc open_run args {{ }} 38 | #proc write_bitstream args {{ }} 39 | #proc close_project args {{ }} 40 | #proc current_bd_design args {{ }} 41 | #proc get_bd_cells args {{ }} 42 | 43 | tcl = f''' 44 | proc unknown args {{ }} 45 | 46 | proc get_runs {{run_name}} {{ 47 | return $run_name 48 | }} 49 | 50 | proc get_property {{property run}} {{ 51 | if {{ $property eq "STATUS" }} {{ 52 | if {{ $run eq "synth_1" }} {{ 53 | return "synth_design Complete!" 54 | }} elseif {{ $run eq "impl_1" }} {{ 55 | return "route_design Complete!" 56 | }} 57 | }} 58 | return "" 59 | }} 60 | 61 | source {args.source} 62 | ''' 63 | 64 | with open(f'{tool}-mock.tcl', 'w', encoding='utf-8') as file: 65 | file.write(tcl) 66 | 67 | subprocess.run( 68 | f'tclsh {tool}-mock.tcl', 69 | shell=True, 70 | check=True, 71 | universal_newlines=True 72 | ) 73 | 74 | pattern = r'create_project\s+-force\s+(\S+)' 75 | with open(args.source, 'r', encoding='utf-8') as file: 76 | match = re.search(pattern, file.read()) 77 | if match: 78 | project = match.group(1) 79 | open(f'{project}.xpr', 'w', encoding='utf-8').close() 80 | open(f'{project}.bit', 'w', encoding='utf-8').close() 81 | 82 | print(f'INFO:the {tool.upper()} mock has been executed') 83 | -------------------------------------------------------------------------------- /tests/mocks/vivado.bat: -------------------------------------------------------------------------------- 1 | @echo off & python "%~dp0%~n0" %* 2 | -------------------------------------------------------------------------------- /tests/mocks/xtclsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Copyright (C) 2022-2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | import argparse 10 | import re 11 | import subprocess 12 | 13 | 14 | parser = argparse.ArgumentParser() 15 | 16 | parser.add_argument('source') 17 | 18 | args = parser.parse_args() 19 | 20 | tool = parser.prog 21 | 22 | tcl = f''' 23 | proc unknown args {{ }} 24 | 25 | source {args.source} 26 | ''' 27 | 28 | with open(f'{tool}-mock.tcl', 'w', encoding='utf-8') as file: 29 | file.write(tcl) 30 | 31 | subprocess.run( 32 | f'tclsh {tool}-mock.tcl', 33 | shell=True, 34 | check=True, 35 | universal_newlines=True 36 | ) 37 | 38 | pattern = r'project\s+new\s+(\S+)\.xise' 39 | with open(args.source, 'r', encoding='utf-8') as file: 40 | match = re.search(pattern, file.read()) 41 | if match: 42 | project = match.group(1) 43 | open(f'{project}.xise', 'w', encoding='utf-8').close() 44 | open(f'{project}.bit', 'w', encoding='utf-8').close() 45 | 46 | print(f'INFO:the {tool.upper()} mock has been executed') 47 | -------------------------------------------------------------------------------- /tests/mocks/xtclsh.bat: -------------------------------------------------------------------------------- 1 | @echo off & python "%~dp0%~n0" %* 2 | -------------------------------------------------------------------------------- /tests/regress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright (C) 2024-2025 PyFPGA Project 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | # 8 | 9 | set -e 10 | 11 | SDIR=$PWD 12 | 13 | echo "##################################################################################" 14 | echo "# Tools #" 15 | echo "##################################################################################" 16 | 17 | declare -A TOOLS 18 | 19 | TOOLS["diamond"]="brevia2" 20 | TOOLS["ise"]="s6micro nexys3" 21 | TOOLS["libero"]="maker" 22 | TOOLS["openflow"]="icestick edu-ciaa orangecrab ecp5evn" 23 | TOOLS["quartus"]="de10nano" 24 | TOOLS["vivado"]="zybo arty" 25 | 26 | SOURCES=("vlog" "vhdl" "slog") 27 | 28 | SPECIFIED_TOOL="" 29 | NOTOOL=false 30 | while [[ "$#" -gt 0 ]]; do 31 | case $1 in 32 | --tool) 33 | SPECIFIED_TOOL="$2" 34 | shift 2 35 | ;; 36 | *) 37 | echo "Invalid option: $1" 38 | exit 1 39 | ;; 40 | esac 41 | done 42 | 43 | for TOOL in "${!TOOLS[@]}"; do 44 | if [[ -n "$SPECIFIED_TOOL" && "$TOOL" != "$SPECIFIED_TOOL" ]]; then 45 | continue 46 | fi 47 | BOARDS=${TOOLS[$TOOL]} 48 | for BOARD in $BOARDS; do 49 | for SOURCE in "${SOURCES[@]}"; do 50 | if [[ "$TOOL" == "ise" && "$SOURCE" == "slog" ]]; then 51 | continue 52 | fi 53 | echo "> $TOOL - $BOARD - $SOURCE" 54 | cd ../examples/projects; 55 | python3 $TOOL.py --board $BOARD --source $SOURCE --action all; 56 | cd $SDIR; 57 | done 58 | done 59 | done 60 | 61 | echo "##################################################################################" 62 | echo "# Helpers #" 63 | echo "##################################################################################" 64 | 65 | cd ../examples/helpers 66 | 67 | for TOOL in "${!TOOLS[@]}"; do 68 | bash "$TOOL".sh 69 | done 70 | 71 | cd $SDIR 72 | 73 | echo "##################################################################################" 74 | echo "# Test successfully finished #" 75 | echo "##################################################################################" 76 | -------------------------------------------------------------------------------- /tests/support.py: -------------------------------------------------------------------------------- 1 | """Support examples.""" 2 | 3 | import argparse 4 | import sys 5 | 6 | from pyfpga.factory import Factory, TOOLS 7 | 8 | 9 | parser = argparse.ArgumentParser() 10 | parser.add_argument( 11 | '--tool', default='openflow', 12 | choices=list(TOOLS.keys()) 13 | ) 14 | args = parser.parse_args() 15 | 16 | print(f'INFO: the Tool Under Test is {args.tool}') 17 | 18 | print('INFO: checking basic Verilog Support') 19 | prj = Factory(args.tool) 20 | prj.add_vlog('../examples/sources/vlog/blink.v') 21 | prj.set_top('Blink') 22 | prj.make(last='syn') 23 | 24 | print('INFO: checking advanced Verilog Support') 25 | prj = Factory(args.tool) 26 | prj.add_vlog('../examples/sources/vlog/*.v') 27 | prj.set_top('Top') 28 | prj.add_include('../examples/sources/vlog/include1') 29 | prj.add_include('../examples/sources/vlog/include2') 30 | prj.add_define('DEFINE1', '1') 31 | prj.add_define('DEFINE2', '1') 32 | prj.add_param('FREQ', '1') 33 | prj.add_param('SECS', '1') 34 | prj.make(last='syn') 35 | 36 | try: 37 | print('INFO: checking Verilog Includes Support') 38 | prj = Factory(args.tool) 39 | prj.add_vlog('../examples/sources/vlog/*.v') 40 | prj.set_top('Top') 41 | prj.add_define('DEFINE1', '1') 42 | prj.add_define('DEFINE2', '1') 43 | prj.add_param('FREQ', '1') 44 | prj.add_param('SECS', '1') 45 | prj.make(last='syn') 46 | sys.exit('ERROR: something does not work as expected') 47 | except SystemExit: 48 | raise 49 | except Exception: 50 | pass 51 | 52 | try: 53 | print('INFO: checking Verilog Defines Support') 54 | prj = Factory(args.tool) 55 | prj.add_vlog('../examples/sources/vlog/*.v') 56 | prj.set_top('Top') 57 | prj.add_include('../examples/sources/vlog/include1') 58 | prj.add_include('../examples/sources/vlog/include2') 59 | prj.add_param('FREQ', '1') 60 | prj.add_param('SECS', '1') 61 | prj.make(last='syn') 62 | sys.exit('ERROR: something does not work as expected') 63 | except SystemExit: 64 | raise 65 | except Exception: 66 | pass 67 | 68 | try: 69 | print('INFO: checking Verilog Parameters Support') 70 | prj = Factory(args.tool) 71 | prj.add_vlog('../examples/sources/vlog/*.v') 72 | prj.set_top('Top') 73 | prj.add_include('../examples/sources/vlog/include1') 74 | prj.add_include('../examples/sources/vlog/include2') 75 | prj.add_define('DEFINE1', '1') 76 | prj.add_define('DEFINE2', '1') 77 | prj.make(last='syn') 78 | sys.exit('ERROR: something does not work as expected') 79 | except SystemExit: 80 | raise 81 | except Exception: 82 | pass 83 | 84 | if args.tool not in ['ise']: 85 | print('INFO: checking basic System Verilog Support') 86 | prj = Factory(args.tool) 87 | prj.add_slog('../examples/sources/slog/blink.sv') 88 | prj.set_top('Blink') 89 | prj.make(last='syn') 90 | 91 | print('INFO: checking advanced System Verilog Support') 92 | prj = Factory(args.tool) 93 | prj.add_slog('../examples/sources/slog/*.sv') 94 | prj.set_top('Top') 95 | prj.add_include('../examples/sources/slog/include1') 96 | prj.add_include('../examples/sources/slog/include2') 97 | prj.add_define('DEFINE1', '1') 98 | prj.add_define('DEFINE2', '1') 99 | prj.add_param('FREQ', '1') 100 | prj.add_param('SECS', '1') 101 | prj.make(last='syn') 102 | 103 | if args.tool not in ['openflow']: 104 | print('* INFO: checking basic VHDL Support') 105 | prj = Factory(args.tool) 106 | prj.add_vhdl('../examples/sources/vhdl/blink.vhdl') 107 | prj.set_top('Blink') 108 | prj.make(last='syn') 109 | 110 | print('* INFO: checking advanced VHDL Support') 111 | prj = Factory(args.tool) 112 | prj.add_vhdl('../examples/sources/vhdl/*.vhdl', 'blink_lib') 113 | prj.add_vhdl('../examples/sources/vhdl/top.vhdl') 114 | prj.set_top('Top') 115 | prj.add_param('FREQ', '1') 116 | prj.add_param('SECS', '1') 117 | prj.make(last='syn') 118 | 119 | try: 120 | print('INFO: checking VHDL Generics') 121 | prj = Factory(args.tool) 122 | prj.add_vhdl('../examples/sources/vhdl/*.vhdl', 'blink_lib') 123 | prj.add_vhdl('../examples/sources/vhdl/top.vhdl') 124 | prj.set_top('Top') 125 | prj.make(last='syn') 126 | sys.exit('ERROR: something does not work as expected') 127 | except SystemExit: 128 | raise 129 | except Exception: 130 | pass 131 | 132 | print(f'INFO: {args.tool} support works as expected') 133 | -------------------------------------------------------------------------------- /tests/test_data.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from pyfpga.vivado import Vivado 3 | 4 | tdir = Path(__file__).parent.resolve() 5 | 6 | 7 | def prepare(path): 8 | return Path(tdir / path).as_posix() 9 | 10 | 11 | pattern = { 12 | 'project': 'EXAMPLE', 13 | 'part': 'PARTNAME', 14 | 'includes': [ 15 | prepare('fakedata/dir1'), 16 | prepare('fakedata/dir2'), 17 | prepare('fakedata/dir3') 18 | ], 19 | 'files': { 20 | prepare('fakedata/vhdl0.vhdl'): {'hdl': 'vhdl', 'lib': 'LIB'}, 21 | prepare('fakedata/dir1/vhdl1.vhdl'): {'hdl': 'vhdl', 'lib': 'LIB'}, 22 | prepare('fakedata/dir2/vhdl2.vhdl'): {'hdl': 'vhdl', 'lib': 'LIB'}, 23 | prepare('fakedata/dir3/vhdl3.vhdl'): {'hdl': 'vhdl', 'lib': 'LIB'}, 24 | prepare('fakedata/vlog0.v'): {'hdl': 'vlog'}, 25 | prepare('fakedata/dir1/vlog1.v'): {'hdl': 'vlog'}, 26 | prepare('fakedata/dir2/vlog2.v'): {'hdl': 'vlog'}, 27 | prepare('fakedata/dir3/vlog3.v'): {'hdl': 'vlog'}, 28 | prepare('fakedata/slog0.sv'): {'hdl': 'slog'}, 29 | prepare('fakedata/dir1/slog1.sv'): {'hdl': 'slog'}, 30 | prepare('fakedata/dir2/slog2.sv'): {'hdl': 'slog'}, 31 | prepare('fakedata/dir3/slog3.sv'): {'hdl': 'slog'} 32 | }, 33 | 'top': 'TOPNAME', 34 | 'constraints': { 35 | prepare('fakedata/cons/all.xdc'): {}, 36 | prepare('fakedata/cons/syn.xdc'): {}, 37 | prepare('fakedata/cons/par.xdc'): {} 38 | }, 39 | 'params': { 40 | 'PAR1': 'VAL01', 41 | 'PAR2': 'VAL02', 42 | 'PAR3': 'VAL03' 43 | }, 44 | 'defines': { 45 | 'DEF1': 'VAL01', 46 | 'DEF2': 'VAL02', 47 | 'DEF3': 'VAL03' 48 | }, 49 | 'hooks': { 50 | 'precfg': ['CMD01', 'CMD02'], 51 | 'postcfg': ['CMD03', 'CMD04'], 52 | 'presyn': ['CMD05', 'CMD06'], 53 | 'postsyn': ['CMD07', 'CMD08'], 54 | 'prepar': ['CMD09', 'CMD10'], 55 | 'postpar': ['CMD11', 'CMD12'], 56 | 'prebit': ['CMD13', 'CMD14'], 57 | 'postbit': ['CMD15', 'CMD16'] 58 | } 59 | } 60 | 61 | 62 | def test_data(): 63 | prj = Vivado('EXAMPLE') 64 | prj.set_part('PARTNAME') 65 | prj.set_top('TOPNAME') 66 | prj.add_include(str(tdir / 'fakedata/dir1')) 67 | prj.add_include(str(tdir / 'fakedata/dir2')) 68 | prj.add_include(str(tdir / 'fakedata/dir3')) 69 | prj.add_slog(str(tdir / 'fakedata/**/*.sv')) 70 | prj.add_vhdl(str(tdir / 'fakedata/**/*.vhdl'), 'LIB') 71 | prj.add_vlog(str(tdir / 'fakedata/**/*.v')) 72 | prj.add_cons(str(tdir / 'fakedata/cons/all.xdc')) 73 | prj.add_cons(str(tdir / 'fakedata/cons/syn.xdc')) 74 | prj.add_cons(str(tdir / 'fakedata/cons/par.xdc')) 75 | prj.add_param('PAR1', 'VAL01') 76 | prj.add_param('PAR2', 'VAL02') 77 | prj.add_param('PAR3', 'VAL03') 78 | prj.add_define('DEF1', 'VAL01') 79 | prj.add_define('DEF2', 'VAL02') 80 | prj.add_define('DEF3', 'VAL03') 81 | prj.add_hook('precfg', 'CMD01') 82 | prj.add_hook('precfg', 'CMD02') 83 | prj.add_hook('postcfg', 'CMD03') 84 | prj.add_hook('postcfg', 'CMD04') 85 | prj.add_hook('presyn', 'CMD05') 86 | prj.add_hook('presyn', 'CMD06') 87 | prj.add_hook('postsyn', 'CMD07') 88 | prj.add_hook('postsyn', 'CMD08') 89 | prj.add_hook('prepar', 'CMD09') 90 | prj.add_hook('prepar', 'CMD10') 91 | prj.add_hook('postpar', 'CMD11') 92 | prj.add_hook('postpar', 'CMD12') 93 | prj.add_hook('prebit', 'CMD13') 94 | prj.add_hook('prebit', 'CMD14') 95 | prj.add_hook('postbit', 'CMD15') 96 | prj.add_hook('postbit', 'CMD16') 97 | assert prj.data == pattern, 'ERROR: unexpected data' 98 | paths = prj.data['includes'] + list(prj.data['files'].keys()) 99 | for path in paths: 100 | assert '\\' not in path, ( 101 | f"'{path}' contains a '\\' character, which is not allowed." 102 | ) 103 | -------------------------------------------------------------------------------- /tests/test_part.py: -------------------------------------------------------------------------------- 1 | from pyfpga.ise import get_info as get_info_ise 2 | from pyfpga.libero import get_info as get_info_libero 3 | from pyfpga.openflow import get_info as get_info_openflow 4 | 5 | 6 | def test_ise(): 7 | info = { 8 | 'family': 'kintex7', 9 | 'device': 'xc7k160t', 10 | 'speed': '-3', 11 | 'package': 'fbg484' 12 | } 13 | assert get_info_ise('xc7k160t-3-fbg484') == info 14 | assert get_info_ise('xc7k160t-fbg484-3') == info 15 | info['speed'] = '-3l' 16 | assert get_info_ise('xc7k160t-3L-fbg484') == info 17 | assert get_info_ise('xc7k160t-fbg484-3L') == info 18 | 19 | 20 | def test_libero(): 21 | info = { 22 | 'family': 'SmartFusion2', 23 | 'device': 'm2s025t', 24 | 'speed': 'STD', 25 | 'package': 'fg484', 26 | 'prange': 'COM' 27 | } 28 | assert get_info_libero('M2S025T-FG484') == info 29 | info['prange'] = 'IND' 30 | assert get_info_libero('M2S025T-FG484I') == info 31 | info['speed'] = '-1' 32 | info['prange'] = 'COM' 33 | assert get_info_libero('M2S025T-1FG484') == info 34 | assert get_info_libero('M2S025T-1-FG484') == info 35 | assert get_info_libero('M2S025T-FG484-1') == info 36 | info['prange'] = 'IND' 37 | assert get_info_libero('M2S025T-1FG484I') == info 38 | assert get_info_libero('M2S025T-1-FG484I') == info 39 | assert get_info_libero('M2S025T-FG484I-1') == info 40 | info['prange'] = 'MIL' 41 | assert get_info_libero('M2S025T-1FG484M') == info 42 | assert get_info_libero('M2S025T-1-FG484M') == info 43 | assert get_info_libero('M2S025T-FG484M-1') == info 44 | info = { 45 | 'family': 'IGLOO2', 46 | 'device': 'm2gl025', 47 | 'speed': 'STD', 48 | 'package': 'fg484', 49 | 'prange': 'COM' 50 | } 51 | assert get_info_libero('M2GL025-FG484') == info 52 | info['prange'] = 'IND' 53 | assert get_info_libero('M2GL025-FG484I') == info 54 | info['speed'] = '-1' 55 | info['prange'] = 'COM' 56 | assert get_info_libero('M2GL025-1FG484') == info 57 | assert get_info_libero('M2GL025-1-FG484') == info 58 | assert get_info_libero('M2GL025-FG484-1') == info 59 | info['prange'] = 'IND' 60 | assert get_info_libero('M2GL025-1FG484I') == info 61 | assert get_info_libero('M2GL025-1-FG484I') == info 62 | assert get_info_libero('M2GL025-FG484I-1') == info 63 | info['prange'] = 'MIL' 64 | assert get_info_libero('M2GL025-1FG484M') == info 65 | assert get_info_libero('M2GL025-1-FG484M') == info 66 | assert get_info_libero('M2GL025-FG484M-1') == info 67 | info['prange'] = 'TGrade1' 68 | assert get_info_libero('M2GL025-1FG484T1') == info 69 | assert get_info_libero('M2GL025-1-FG484T1') == info 70 | assert get_info_libero('M2GL025-FG484T1-1') == info 71 | info = { 72 | 'family': 'PolarFire', 73 | 'device': 'mpf300ts_es', 74 | 'speed': 'STD', 75 | 'package': 'fg484', 76 | 'prange': 'EXT' 77 | } 78 | assert get_info_libero('MPF300TS_ES-FG484E') == info 79 | info['prange'] = 'IND' 80 | assert get_info_libero('MPF300TS_ES-FG484I') == info 81 | info['speed'] = '-1' 82 | info['prange'] = 'EXT' 83 | assert get_info_libero('MPF300TS_ES-1FG484E') == info 84 | assert get_info_libero('MPF300TS_ES-1-FG484E') == info 85 | assert get_info_libero('MPF300TS_ES-FG484E-1') == info 86 | info['prange'] = 'IND' 87 | assert get_info_libero('MPF300TS_ES-1FG484I') == info 88 | assert get_info_libero('MPF300TS_ES-1-FG484I') == info 89 | assert get_info_libero('MPF300TS_ES-FG484I-1') == info 90 | info = { 91 | 'family': 'PolarFireSoC', 92 | 'device': 'mpfs025t', 93 | 'speed': 'STD', 94 | 'package': 'fcvg484', 95 | 'prange': 'EXT' 96 | } 97 | assert get_info_libero('MPFS025T-FCVG484E') == info 98 | info['prange'] = 'IND' 99 | assert get_info_libero('MPFS025T-FCVG484I') == info 100 | info['speed'] = '-1' 101 | info['prange'] = 'EXT' 102 | assert get_info_libero('MPFS025T-1FCVG484E') == info 103 | assert get_info_libero('MPFS025T-1-FCVG484E') == info 104 | assert get_info_libero('MPFS025T-FCVG484E-1') == info 105 | info['prange'] = 'IND' 106 | assert get_info_libero('MPFS025T-1FCVG484I') == info 107 | assert get_info_libero('MPFS025T-1-FCVG484I') == info 108 | assert get_info_libero('MPFS025T-FCVG484I-1') == info 109 | 110 | 111 | def test_openflow(): 112 | info = {'family': 'xc7', 'device': 'xc7k160t-3', 'package': 'fbg484'} 113 | assert get_info_openflow('xc7k160t-3-fbg484') == info 114 | info = {'family': 'ice40', 'device': 'hx1k', 'package': 'tq144'} 115 | assert get_info_openflow('hx1k-tq144') == info 116 | info = {'family': 'ecp5', 'device': '25k', 'package': 'CSFBGA285'} 117 | assert get_info_openflow('25k-CSFBGA285') == info 118 | info = {'family': 'ecp5', 'device': 'um5g-85k', 'package': 'CABGA381'} 119 | assert get_info_openflow('um5g-85k-CABGA381') == info 120 | -------------------------------------------------------------------------------- /tests/test_tools.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pathlib import Path 4 | from pyfpga.factory import Factory 5 | 6 | tdir = Path(__file__).parent.resolve() 7 | 8 | 9 | def test_diamond(): 10 | tool = 'diamond' 11 | generate(tool, 'PARTNAME') 12 | base = f'results/{tool}/{tool}' 13 | assert Path(f'{base}.tcl').exists(), 'file not found' 14 | assert Path(f'{base}-prog.sh').exists(), 'file not found' 15 | 16 | 17 | def test_ise(): 18 | tool = 'ise' 19 | generate(tool, 'DEVICE-PACKAGE-SPEED') 20 | base = f'results/{tool}/{tool}' 21 | assert Path(f'{base}.tcl').exists(), 'file not found' 22 | assert Path(f'{base}-prog.tcl').exists(), 'file not found' 23 | 24 | 25 | def test_libero(): 26 | tool = 'libero' 27 | generate(tool, 'DEVICE-PACKAGE-SPEED') 28 | base = f'results/{tool}/{tool}' 29 | assert Path(f'{base}.tcl').exists(), 'file not found' 30 | assert Path(f'{base}-prog.tcl').exists(), 'file not found' 31 | 32 | 33 | def test_openflow(): 34 | tool = 'openflow' 35 | generate(tool, 'DEVICE-PACKAGE') 36 | base = f'results/{tool}/{tool}' 37 | assert Path(f'{base}.sh').exists(), 'file not found' 38 | assert Path(f'{base}-prog.sh').exists(), 'file not found' 39 | 40 | 41 | def test_quartus(): 42 | tool = 'quartus' 43 | generate(tool, 'PARTNAME') 44 | base = f'results/{tool}/{tool}' 45 | assert Path(f'{base}.tcl').exists(), 'file not found' 46 | assert Path(f'{base}-prog.sh').exists(), 'file not found' 47 | 48 | 49 | def test_vivado(): 50 | tool = 'vivado' 51 | generate(tool, 'PARTNAME') 52 | base = f'results/{tool}/{tool}' 53 | assert Path(f'{base}.tcl').exists(), 'file not found' 54 | assert Path(f'{base}-prog.tcl').exists(), 'file not found' 55 | 56 | 57 | def generate(tool, part): 58 | prj = Factory(tool, odir=f'results/{tool}') 59 | prj.set_part(part) 60 | prj.set_top('TOPNAME') 61 | prj.add_include(str(tdir / 'fakedata/dir1')) 62 | prj.add_include(str(tdir / 'fakedata/dir2')) 63 | if tool != 'ise': 64 | prj.add_slog(str(tdir / 'fakedata/**/*.sv')) 65 | prj.add_vhdl(str(tdir / 'fakedata/**/*.vhdl'), 'LIB') 66 | prj.add_vlog(str(tdir / 'fakedata/**/*.v')) 67 | prj.add_cons(str(tdir / 'fakedata/cons/all.xdc')) 68 | prj.add_cons(str(tdir / 'fakedata/cons/syn.xdc')) 69 | prj.add_cons(str(tdir / 'fakedata/cons/par.xdc')) 70 | prj.add_param('PAR1', 'VAL1') 71 | prj.add_param('PAR2', 'VAL2') 72 | prj.add_define('DEF1', 'VAL1') 73 | prj.add_define('DEF2', 'VAL2') 74 | prj.add_hook('precfg', 'HOOK01') 75 | prj.add_hook('precfg', 'HOOK02') 76 | prj.add_hook('postcfg', 'HOOK03') 77 | prj.add_hook('postcfg', 'HOOK04') 78 | prj.add_hook('presyn', 'HOOK05') 79 | prj.add_hook('presyn', 'HOOK06') 80 | prj.add_hook('postsyn', 'HOOK07') 81 | prj.add_hook('postsyn', 'HOOK08') 82 | prj.add_hook('prepar', 'HOOK09') 83 | prj.add_hook('prepar', 'HOOK10') 84 | prj.add_hook('postpar', 'HOOK11') 85 | prj.add_hook('postpar', 'HOOK12') 86 | prj.add_hook('prebit', 'HOOK13') 87 | prj.add_hook('prebit', 'HOOK14') 88 | prj.add_hook('postbit', 'HOOK15') 89 | prj.add_hook('postbit', 'HOOK16') 90 | prj.make() 91 | prj.prog() 92 | # 93 | separator = '\\' 94 | # 95 | for path in prj.data['includes']: 96 | assert separator not in path, f'invalid path {path}' 97 | for category in ['files', 'constraints']: 98 | for path in prj.data[category]: 99 | assert separator not in path, f'invalid path {path}' 100 | # 101 | if os.name == 'nt' and tool in ['diamond', 'quartus']: 102 | separator = '/' 103 | path = prj._get_bitstream() 104 | assert separator not in path, f'invalid path {path}' 105 | --------------------------------------------------------------------------------