├── .bazelignore ├── .bazelrc ├── .clang-format ├── .clang-tidy ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── MODULE.bazel ├── README.md ├── bazel ├── BUILD ├── bazel-compile-commands-extractor-fix.patch └── prjxray-add-module-bazel.patch ├── flake.lock ├── flake.nix ├── fpga ├── BUILD ├── assembler.cc ├── database-parsers.cc ├── database-parsers.h ├── database-parsers_test.cc ├── database.cc ├── database.h ├── database_test.cc ├── fasm-parser.h ├── fasm-parser_test.cc ├── memory-mapped-file.cc ├── memory-mapped-file.h └── xilinx │ ├── BUILD │ ├── README.md │ ├── arch-types.h │ ├── arch-xc7-configuration-packet.cc │ ├── arch-xc7-configuration-packet.h │ ├── arch-xc7-configuration-packet_test.cc │ ├── arch-xc7-frame.cc │ ├── arch-xc7-frame.h │ ├── arch-xc7-frame_test.cc │ ├── arch-xc7-part.cc │ ├── arch-xc7-part.h │ ├── arch-xc7-part_test.cc │ ├── big-endian-span.h │ ├── big-endian-span_test.cc │ ├── bistream-writer.h │ ├── bit-ops.h │ ├── bit-ops_test.cc │ ├── bitstream-reader-xc7_test.cc │ ├── bitstream-reader.h │ ├── bitstream-writer.cc │ ├── bitstream-writer.h │ ├── bitstream-writer_test.cc │ ├── bitstream.h │ ├── configuration-packet.h │ ├── configuration-xc7_test.cc │ ├── configuration.cc │ ├── configuration.h │ ├── frames-xc7_test.cc │ ├── frames.h │ └── testdata │ ├── xc7-configuration-test.json │ ├── xc7-configuration.bit │ ├── xc7-configuration.debug.bit │ └── xc7-configuration.perframecrc.bit ├── img └── fasm2frames.svg └── scripts ├── before-submit.sh ├── create-workspace-status.sh ├── get-bant-path.sh ├── make-compilation-db.sh ├── run-build-cleaner.sh ├── run-clang-format.sh └── run-clang-tidy-cached.cc /.bazelignore: -------------------------------------------------------------------------------- 1 | # While using flakes, bazel might pickup the sources used 2 | # as flake input stored in direnv and lead to a broken build. 3 | .direnv 4 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # Building the version number to be baked into the binary. 2 | build --workspace_status_command="scripts/create-workspace-status.sh" 3 | 4 | # C++, with warnings mostly turned to 11. 5 | build --cxxopt=-std=c++20 --host_cxxopt=-std=c++20 6 | build --cxxopt=-xc++ --host_cxxopt=-xc++ 7 | build --cxxopt=-Wall --host_cxxopt=-Wall 8 | build --cxxopt=-Wextra --host_cxxopt=-Wextra 9 | build --cxxopt=-Wno-unused-parameter --host_cxxopt=-Wno-unused-parameter 10 | 11 | # Avoid costly language features. 12 | build --cxxopt=-fno-rtti --host_cxxopt=-fno-rtti 13 | build --cxxopt=-fno-exceptions --host_cxxopt=-fno-exceptions 14 | 15 | # For 3rd party code: Disable warnings entirely. 16 | # They are not actionable and just create noise. 17 | build --per_file_copt=external/.*@-w 18 | build --host_per_file_copt=external/.*@-w 19 | 20 | # Platform specific options. 21 | build --enable_platform_specific_config 22 | build:macos --macos_minimum_os=10.15 23 | build:macos --features=-supports_dynamic_linker 24 | build:macos --cxxopt=-std=c++20 --host_cxxopt=-std=c++20 25 | 26 | # Print out test log on failure. 27 | test --test_output=errors 28 | 29 | # Address sanitizer settings. 30 | build:asan --strip=never 31 | build:asan --copt -fsanitize=address 32 | build:asan --copt -DADDRESS_SANITIZER 33 | build:asan --copt -O1 34 | build:asan --copt -g 35 | build:asan --copt -fno-omit-frame-pointer 36 | build:asan --linkopt -fsanitize=address 37 | 38 | # Load user-specific configuration, if any. 39 | try-import %workspace%/user.bazelrc 40 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Use the Google style in this project. 2 | # https://google.github.io/styleguide/cppguide.html 3 | BasedOnStyle: Google 4 | 5 | # ... adapt to our local style. 6 | ContinuationIndentWidth: 2 7 | IndentPPDirectives: None 8 | AllowShortCaseLabelsOnASingleLine: true 9 | AlignConsecutiveBitFields: true 10 | AlignConsecutiveMacros: true 11 | IndentCaseLabels: false 12 | 13 | # Pointers/references always right-hugging. 14 | DerivePointerAlignment: false 15 | PointerAlignment: Right 16 | ReferenceAlignment: Right -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | # We're not using readability-braces-around-statements as we using 2 | # the google-* version of that which is more sensible (allows one-liners 3 | # without braces). 4 | ### 5 | Checks: > 6 | clang-diagnostic-*, 7 | -clang-diagnostic-unknown-pragmas, 8 | clang-analyzer-*, 9 | -clang-analyzer-optin.core.EnumCastOutOfRange, 10 | abseil-*, 11 | readability-*, 12 | -readability-avoid-unconditional-preprocessor-if, 13 | -readability-braces-around-statements, 14 | -readability-function-cognitive-complexity, 15 | -readability-identifier-length, 16 | -readability-implicit-bool-conversion, 17 | -readability-magic-numbers, 18 | -readability-math-missing-parentheses, 19 | -readability-named-parameter, 20 | -readability-static-definition-in-anonymous-namespace, 21 | -readability-uppercase-literal-suffix, 22 | -readability-use-anyofallof, 23 | google-*, 24 | -google-readability-casting, 25 | -google-readability-todo, 26 | -google-readability-avoid-underscore-in-googletest-name, 27 | performance-*, 28 | -performance-enum-size, 29 | bugprone-*, 30 | -bugprone-easily-swappable-parameters, 31 | -bugprone-narrowing-conversions, 32 | modernize-*, 33 | -modernize-avoid-c-arrays, 34 | -modernize-concat-nested-namespaces, 35 | -modernize-make-unique, 36 | -modernize-use-auto, 37 | -modernize-use-designated-initializers, 38 | -modernize-use-nodiscard, 39 | -modernize-use-std-format, 40 | -modernize-use-std-print, 41 | -modernize-use-trailing-return-type, 42 | misc-*, 43 | -misc-no-recursion, 44 | -misc-unused-parameters, 45 | -misc-use-anonymous-namespace, 46 | CheckOptions: 47 | # Structs can not have non-public memebers, so they should be public. 48 | # but clang-tidy warns about them anyway as being visible. 49 | # clang-tidy can't distinguish between classes and structs, so approximate 50 | # with this: 51 | - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 52 | value: '1' 53 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | Test: 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Create Cache Timestamp 21 | id: cache_timestamp 22 | uses: nanzm/get-time-action@v2.0 23 | with: 24 | format: 'YYYY-MM-DD-HH-mm-ss' 25 | 26 | - name: Mount bazel cache 27 | uses: actions/cache@v4 28 | with: 29 | path: "~/.cache/bazel" 30 | key: bazelcache_test_${{ steps.cache_timestamp.outputs.time }} 31 | restore-keys: bazelcache_test_ 32 | 33 | - name: Test 34 | run: | 35 | export CC=gcc-13 36 | export CXX=g++-13 37 | bazel test --noshow_progress --keep_going ... 38 | 39 | MacOsBuild: 40 | runs-on: macos-latest 41 | steps: 42 | - name: Checkout code 43 | uses: actions/checkout@v4 44 | with: 45 | fetch-depth: 0 46 | 47 | - name: Test 48 | run: | 49 | bazel test --noshow_progress --keep_going ... 50 | 51 | - name: Build 52 | run: | 53 | bazel build --noshow_progress -c opt //fpga:fpga-as 54 | 55 | CodeFormatting: 56 | runs-on: ubuntu-24.04 57 | steps: 58 | - name: Checkout code 59 | uses: actions/checkout@v4 60 | with: 61 | fetch-depth: 0 62 | 63 | - name: Install Dependencies 64 | run: | 65 | sudo apt-get install clang-format-17 66 | 67 | - name: Run formatting style check 68 | run: | 69 | clang-format-17 --version 70 | RUNNING_IN_CI=1 CLANG_FORMAT=clang-format-17 \ 71 | scripts/run-clang-format.sh 72 | 73 | ClangTidy: 74 | runs-on: ubuntu-24.04 75 | steps: 76 | - name: Checkout code 77 | uses: actions/checkout@v4 78 | with: 79 | fetch-depth: 0 80 | 81 | - name: Install Dependencies 82 | run: | 83 | sudo apt-get install clang-tidy-18 84 | 85 | - name: Create Cache Timestamp 86 | id: cache_timestamp 87 | uses: nanzm/get-time-action@v2.0 88 | with: 89 | format: 'YYYY-MM-DD-HH-mm-ss' 90 | 91 | - name: Mount clang-tidy cache 92 | uses: actions/cache@v4 93 | with: 94 | path: | 95 | ~/.cache/clang-tidy 96 | ~/.cache/bazel 97 | key: clang-tidy-cache_${{ steps.cache_timestamp.outputs.time }} 98 | restore-keys: clang-tidy-cache_ 99 | 100 | - name: Build Compilation DB 101 | run: | 102 | scripts/make-compilation-db.sh 103 | 104 | - name: Run clang-tidy 105 | run: | 106 | clang-tidy-18 --version 107 | CLANG_TIDY=clang-tidy-18 scripts/run-clang-tidy-cached.cc \ 108 | || ( cat fpga-assembler_clang-tidy.out ; exit 1) 109 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MODULE.bazel.lock 2 | *.out 3 | *.summary 4 | 5 | # misc 6 | .DS_Store 7 | .env.local 8 | .env.development.local 9 | .env.test.local 10 | .env.production.local 11 | .direnv 12 | .envrc 13 | compile_flags.txt 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # clang-tidy runner artifacts 19 | run-clang-tidy-cached 20 | xstream_clang-tidy.* 21 | 22 | # Ignore backup files. 23 | *~ 24 | 25 | # Ignore Vim swap files. 26 | .*.swp 27 | 28 | # Ignore files generated by IDEs. 29 | /.classpath 30 | /.factorypath 31 | /.idea/ 32 | /.ijwb/ 33 | /.project 34 | /.settings 35 | /.vscode/ 36 | /bazel.iml 37 | 38 | # User-specific .bazelrc 39 | user.bazelrc 40 | 41 | # Ignore all bazel-* symlinks. There is no full list since this can change 42 | # based on the name of the directory bazel is cloned into. 43 | /bazel-* 44 | 45 | # Ignore outputs generated during Bazel bootstrapping. 46 | /output/ 47 | 48 | # User-specific .bazelrc 49 | user.bazelrc 50 | 51 | ### Added by Hedron's Bazel Compile Commands Extractor: https://github.com/hedronvision/bazel-compile-commands-extractor 52 | # The external link: Differs on Windows vs macOS/Linux, so we can't check it in. The pattern needs to not have a trailing / because it's a symlink on macOS/Linux. 53 | /external 54 | 55 | # Compiled output -> don't check in 56 | /compile_commands.json 57 | 58 | # Directory where clangd puts its indexing work 59 | /.cache/ 60 | 61 | # dependencies 62 | node_modules 63 | /.pnp 64 | .pnp.js 65 | 66 | # testing 67 | /coverage 68 | 69 | # production 70 | /build 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "fpga-assembler", 3 | version = "0.0.1", 4 | ) 5 | 6 | # Bazel C/C++ language rules and deps. 7 | bazel_dep(name = "rules_cc", version = "0.0.16") 8 | git_override( 9 | module_name = "rules_cc", 10 | commit = "d74915024017250e46d95e91a3defc34174effe0", 11 | remote = "https://github.com/bazelbuild/rules_cc", 12 | ) 13 | 14 | bazel_dep(name = "abseil-cpp", version = "20240722.0.bcr.2") 15 | bazel_dep(name = "googletest", version = "1.15.2") 16 | bazel_dep(name = "rapidjson", version = "1.1.0.bcr.20241007") 17 | bazel_dep(name = "rules_license", version = "1.0.0") 18 | 19 | # compilation DB; build_cleaner 20 | bazel_dep(name = "bant", version = "0.2.0", dev_dependency = True) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 2 | [![Continuous Integration](https://github.com/lromor/fpga-assembler/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/lromor/fpga-assembler/actions/workflows/ci.yml) 3 | 4 | # fpga-assembler 5 | 6 | This command-line tool converts [FASM][fasm-spec] files into bitstreams, simplifying the assembly of human-readable FPGA configurations into the binary formats needed to program various FPGAs. 7 | 8 | At this stage, it wraps most of [fasm2frames](https://github.com/chipsalliance/f4pga-xc-fasm/blob/25dc605c9c0896204f0c3425b52a332034cf5e5c/xc_fasm/fasm2frames.py) and [xc7frames2bit](https://github.com/f4pga/prjxray/blob/faf9c774a340e39cf6802d009996ed6016e63521/tools/xc7frames2bit.cc) logic into a single binary. 9 | It has been tested with the Artix-7 [counter example][counter-example], where it produces identical frames and a working bitstream—at approximately 10 times the speed compared to the pure Python textX based parser implementation. 10 | 11 | ## Usage 12 | 13 | First, install [Bazel][bazel] and ensure you have a basic C/C++ toolchain set up. 14 | 15 | > [!NOTE] 16 | > If you are using Nix or NixOS, ensure you have [flakes enabled][enable-flakes] and enter the development shell via `nix develop`. 17 | 18 | Then run: 19 | 20 | ``` 21 | bazel run -c opt //fpga:fpga-as -- --prjxray_db_path=/some/path/prjxray-db/artix7 --part=xc7a35tcsg324-1 < /some/path.fasm > output.bit 22 | ``` 23 | 24 | Finally, load the bitstream in your FPGA using [openFPGALoader][open-fpga-loader] 25 | 26 | ``` 27 | openFPGALoader -b arty output.bit 28 | ``` 29 | 30 | ## Installation 31 | 32 | For installing the binary in your home directory (e.g., ~/bin), run the following command: 33 | 34 | ``` 35 | bazel build -c opt //fpga:fpga-as && install -D --strip bazel-bin/fpga/fpga-as ~/bin/fpga-as 36 | ``` 37 | 38 | or install in system directory that requires root-access: 39 | 40 | ``` 41 | sudo install -D --strip bazel-bin/fpga/fpga-as /usr/local/bin/fpga-as 42 | ``` 43 | 44 | # How it works 45 | 46 | ## Frames generation 47 | 48 | 49 | In a FASM file, each line represents a sub-tile feature along with the configuration bits required to enable that feature. 50 | In other words, when a tile feature is enabled, its corresponding configuration bits must also be activated. 51 | 52 | The diagram below illustrates the basic process for the `xc7a50t` fabric: 53 | 54 | ![fasm2frames](./img/fasm2frames.svg) 55 | 56 | For example, consider the FASM line: 57 | ``` 58 | CLBLM_R_X33Y38.SLICEM_X0.ALUT.INIT[63:32]=32'b00000000000000000000000000000100 59 | ``` 60 | This line instructs the system to configure the SLICEM_X0.ALUT.INIT feature of the tile named CLBLM_R_X33Y38, enabling the feature at address 34. 61 | 62 | The next step is to locate the tile metadata in the FPGA fabric's `tilegrid.json` file. The metadata provides essential information, including: 63 | 64 | * tile_type: `"CLBLM_R"` 65 | * baseaddr: `"0x00401080` 66 | * offset: `77` 67 | 68 | Using this metadata, you can search the segbits database for the specific feature. By matching the tile_type and the FASM feature name, you can identify the correct configuration bits in the tile type segbits file (`segbits_clblm_r.db`). In this case, you would look for the entry corresponding to CLBLM_R.SLICEM_X0.ALUT.INIT and find the entry for address `[34]`. The value 34_06 then provides the coordinates for the word index and the specific bit index to be set. 69 | 70 | [fasm-spec]: https://fasm.readthedocs.io/en/stable/# 71 | [bazel]: https://bazel.build/ 72 | [counter-example]: https://github.com/chipsalliance/f4pga-examples/blob/13f11197b33dae1cde3bf146f317d63f0134eacf/xc7/counter_test/counter.v 73 | [open-fpga-loader]: https://github.com/trabucayre/openFPGALoader 74 | [enable-flakes]: https://nixos.wiki/wiki/Flakes#Enable_flakes_permanently_in_NixOS 75 | -------------------------------------------------------------------------------- /bazel/BUILD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lromor/fpga-assembler/a7181e1bd91b09f308947dd3e12ea5f07a24ba40/bazel/BUILD -------------------------------------------------------------------------------- /bazel/bazel-compile-commands-extractor-fix.patch: -------------------------------------------------------------------------------- 1 | From f5fbd4cee671d8d908f37c83abaf70fba5928fc7 Mon Sep 17 00:00:00 2001 2 | From: Mikael Persson 3 | Date: Wed, 18 Sep 2024 11:30:31 -0400 4 | Subject: [PATCH] Fix for header-only compile actions by Bazel 5 | 6 | --- 7 | refresh.template.py | 16 ++++++++++++---- 8 | 1 file changed, 12 insertions(+), 4 deletions(-) 9 | 10 | diff --git a/refresh.template.py b/refresh.template.py 11 | index 194f365..0246373 100644 12 | --- a/refresh.template.py 13 | +++ b/refresh.template.py 14 | @@ -173,7 +173,7 @@ def _parse_headers_from_makefile_deps(d_file_content: str, source_path_for_sanit 15 | # For example, `d_file_content` might be: `"foo.o : foo.cc bar.h \\\n baz.hpp"`. 16 | target, dependencies = d_file_content.split(': ', 1) # Needs to handle absolute Windows paths, like C:\ 17 | target = target.strip() # Remove the optional trailing space. 18 | - assert target.endswith(('.o', '.obj')), "Something went wrong in makefile parsing to get headers. The target should be an object file. Output:\n" + d_file_content 19 | + assert target.endswith(_get_headers.output_extensions), "Something went wrong in makefile parsing to get headers. The target should be an object file. Output:\n" + d_file_content 20 | # Undo shell-like line wrapping because the newlines aren't eaten by shlex.join. Note also that it's the line wrapping is inconsistently generated across compilers and depends on the lengths of the filenames, so you can't just split on the escaped newlines. 21 | dependencies = dependencies.replace('\\\n', '') 22 | # On Windows, swap out (single) backslash path directory separators for forward slash. Shlex otherwise eats the separators...and Windows gcc intermixes backslash separators with backslash escaped spaces. For a real example of gcc run from Windows, see https://github.com/hedronvision/bazel-compile-commands-extractor/issues/81 23 | @@ -267,13 +267,17 @@ def _get_headers_gcc(compile_action, source_path: str, action_key: str): 24 | 25 | # Strip output flags. Apple clang tries to do a full compile if you don't. 26 | header_cmd = (arg for arg in header_cmd 27 | - if arg != '-o' and not arg.endswith('.o')) 28 | + if arg != '-o' and not arg.endswith(_get_headers.output_extensions)) 29 | 30 | # Strip sanitizer ignore lists...so they don't show up in the dependency list. 31 | # See https://clang.llvm.org/docs/SanitizerSpecialCaseList.html and https://github.com/hedronvision/bazel-compile-commands-extractor/issues/34 for more context. 32 | header_cmd = (arg for arg in header_cmd 33 | if not arg.startswith('-fsanitize')) 34 | 35 | + # Strip syntax-only option since it is ignored when running pre-processor only, and will create noisy warnings. 36 | + header_cmd = (arg for arg in header_cmd 37 | + if not arg.startswith('-fsyntax-only')) 38 | + 39 | # Dump system and user headers to stdout...in makefile format. 40 | # Relies on our having made the workspace directory simulate a complete version of the execroot with //external symlink 41 | header_cmd = list(header_cmd) 42 | @@ -422,6 +426,9 @@ def _get_headers_msvc(compile_action, source_path: str): 43 | '/EP', # Preprocess (only, no compilation for speed), writing to stdout where we can easily ignore it instead of a file. https://docs.microsoft.com/en-us/cpp/build/reference/ep-preprocess-to-stdout-without-hash-line-directives 44 | ] 45 | 46 | + # Strip syntax-only (/Zs) option since it is ignored when running pre-processor only, and will create noisy warnings. 47 | + header_cmd = (arg for arg in header_cmd if arg != '/Zs') 48 | + 49 | # cl.exe needs the `INCLUDE` environment variable to find the system headers, since they aren't specified in the action command 50 | # Bazel neglects to include INCLUDE per action, so we'll do the best we can and infer them from the default (host) cc toolchain. 51 | # These are set in https://github.com/bazelbuild/bazel/bloc/master/tools/cpp/windows_cc_configure.bzl. Search INCLUDE. 52 | @@ -608,6 +615,7 @@ def _get_headers(compile_action, source_path: str): 53 | 54 | return headers 55 | _get_headers.has_logged = False 56 | +_get_headers.output_extensions = ('.o', '.obj', '.processed') 57 | 58 | 59 | def _get_files(compile_action): 60 | @@ -691,8 +699,8 @@ def _get_files(compile_action): 61 | _get_files.has_logged_missing_file_error = False 62 | # Setup extensions and flags for the whole C-language family. 63 | # Clang has a list: https://github.com/llvm/llvm-project/blob/b9f3b7f89a4cb4cf541b7116d9389c73690f78fa/clang/lib/Driver/Types.cpp#L293 64 | -_get_files.c_source_extensions = ('.c', '.i') 65 | -_get_files.cpp_source_extensions = ('.cc', '.cpp', '.cxx', '.c++', '.C', '.CC', '.cp', '.CPP', '.C++', '.CXX', '.ii') 66 | +_get_files.c_source_extensions = ('.c', '.i', '.h', '.inl') 67 | +_get_files.cpp_source_extensions = ('.cc', '.cpp', '.cxx', '.c++', '.C', '.CC', '.cp', '.CPP', '.C++', '.CXX', '.ii', '.hh', '.hpp', '.hxx') 68 | _get_files.objc_source_extensions = ('.m',) 69 | _get_files.objcpp_source_extensions = ('.mm', '.M') 70 | _get_files.cuda_source_extensions = ('.cu', '.cui') -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1743583204, 24 | "narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "repo": "nixpkgs", 33 | "rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "fpga-assembler"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/2c8d3f48d33929642c1c12cd243df4cc7d2ce434"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = 10 | { 11 | self, 12 | nixpkgs, 13 | flake-utils, 14 | }@inputs: 15 | flake-utils.lib.eachDefaultSystem ( 16 | system: 17 | let 18 | pkgs = import nixpkgs { 19 | inherit system; 20 | }; 21 | common = with pkgs; { 22 | bazel = bazel_7; 23 | jdk = jdk; 24 | }; 25 | in 26 | { 27 | devShells.default = 28 | let 29 | # There is too much volatility between even micro-versions of 30 | # newer clang-format. Use slightly older version for now. 31 | clang_for_formatting = pkgs.llvmPackages_17.clang-tools; 32 | 33 | # clang tidy: use latest. 34 | clang_for_tidy = pkgs.llvmPackages_18.clang-tools; 35 | in 36 | with pkgs; 37 | pkgs.mkShell { 38 | packages = with pkgs; [ 39 | git 40 | common.bazel 41 | common.jdk 42 | bash 43 | gdb 44 | 45 | # For clang-tidy and clang-format. 46 | clang_for_formatting 47 | clang_for_tidy 48 | 49 | # For buildifier, buildozer. 50 | bazel-buildtools 51 | bant 52 | 53 | # Profiling and sanitizers. 54 | linuxPackages_latest.perf 55 | pprof 56 | perf_data_converter 57 | valgrind 58 | 59 | # FPGA utils. 60 | openfpgaloader 61 | ]; 62 | 63 | CLANG_TIDY = "${clang_for_tidy}/bin/clang-tidy"; 64 | CLANG_FORMAT = "${clang_for_formatting}/bin/clang-format"; 65 | 66 | shellHook = '' 67 | exec bash 68 | ''; 69 | }; 70 | 71 | # Package fpga-assembler. 72 | packages.default = 73 | (pkgs.callPackage ( 74 | { 75 | buildBazelPackage, 76 | stdenv, 77 | fetchFromGitHub, 78 | lib, 79 | nix-gitignore, 80 | }: 81 | let 82 | system = stdenv.hostPlatform.system; 83 | registry = fetchFromGitHub { 84 | owner = "bazelbuild"; 85 | repo = "bazel-central-registry"; 86 | rev = "63f3af762b2fdd7acaa7987856cd3ac314eaea09"; 87 | hash = "sha256-ugNzoP0gdrhl9vH1TRdwoevuTsSqjitXnAoMSSTlCgI="; 88 | }; 89 | in 90 | buildBazelPackage { 91 | pname = "fpga-as"; 92 | 93 | version = "0.0.1"; 94 | 95 | src = nix-gitignore.gitignoreSourcePure [ ] ./.; 96 | 97 | bazelFlags = [ 98 | "--registry" 99 | "file://${registry}" 100 | ]; 101 | 102 | postPatch = '' 103 | patchShebangs scripts/create-workspace-status.sh 104 | ''; 105 | 106 | fetchAttrs = { 107 | hash = 108 | { 109 | aarch64-linux = "sha256-E4VHjDa0qkHmKUNpTBfJi7dhMLcd1z5he+p31/XvUl8="; 110 | x86_64-linux = "sha256-hVBJB0Hsd9sXuEoNcjhTkbPl89vlZT1w39JppCD+n8Y="; 111 | } 112 | .${system} or (throw "No hash for system: ${system}"); 113 | }; 114 | 115 | removeRulesCC = false; 116 | removeLocalConfigCc = false; 117 | removeLocalConfigSh = false; 118 | 119 | nativeBuildInputs = [ 120 | common.jdk 121 | pkgs.git 122 | pkgs.bash 123 | # Convenient tool to enter into the sandbox and start debugging. 124 | pkgs.breakpointHook 125 | ]; 126 | 127 | bazel = common.bazel; 128 | 129 | bazelBuildFlags = [ "-c opt" ]; 130 | bazelTestTargets = [ "//..." ]; 131 | bazelTargets = [ "//fpga:fpga-as" ]; 132 | 133 | buildAttrs = { 134 | installPhase = '' 135 | install -D --strip bazel-bin/fpga/fpga-as "$out/bin/fpga-as" 136 | ''; 137 | }; 138 | 139 | meta = { 140 | description = "Tool to convert FASM to FPGA bitstream."; 141 | homepage = "https://github.com/lromor/fpga-assembler"; 142 | license = lib.licenses.asl20; 143 | platforms = lib.platforms.linux; 144 | }; 145 | } 146 | ) { }).overrideAttrs 147 | ( 148 | final: prev: { 149 | # Fixup the deps so they always contain correrct 150 | # shebangs paths pointing to the store. 151 | deps = prev.deps.overrideAttrs ( 152 | final: prev: { 153 | installPhase = 154 | '' 155 | patchShebangs $bazelOut/external 156 | '' 157 | + prev.installPhase; 158 | } 159 | ); 160 | } 161 | ); 162 | } 163 | ); 164 | } 165 | -------------------------------------------------------------------------------- /fpga/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") 2 | load("@rules_license//rules:license.bzl", "license") 3 | 4 | package( 5 | default_applicable_licenses = [":license"], 6 | default_visibility = ["//:__subpackages__"], 7 | ) 8 | 9 | license( 10 | name = "license", 11 | package_name = "fpga", 12 | license_kinds = [ 13 | "@rules_license//licenses/spdx:Apache-2.0", 14 | ], 15 | ) 16 | 17 | cc_library( 18 | name = "fasm-parser", 19 | hdrs = [ 20 | "fasm-parser.h", 21 | ], 22 | ) 23 | 24 | cc_test( 25 | name = "fasm-parser_test", 26 | srcs = [ 27 | "fasm-parser_test.cc", 28 | ], 29 | deps = [ 30 | ":fasm-parser", 31 | ], 32 | ) 33 | 34 | cc_library( 35 | name = "memory-mapped-file", 36 | srcs = [ 37 | "memory-mapped-file.cc", 38 | "memory-mapped-file.h", 39 | ], 40 | deps = [ 41 | "@abseil-cpp//absl/status", 42 | "@abseil-cpp//absl/status:statusor", 43 | "@abseil-cpp//absl/strings:str_format", 44 | "@abseil-cpp//absl/types:span", 45 | ], 46 | ) 47 | 48 | cc_library( 49 | name = "database-parsers", 50 | srcs = [ 51 | "database-parsers.cc", 52 | ], 53 | hdrs = [ 54 | "database-parsers.h", 55 | ], 56 | deps = [ 57 | "@abseil-cpp//absl/base:core_headers", 58 | "@abseil-cpp//absl/container:flat_hash_map", 59 | "@abseil-cpp//absl/log:check", 60 | "@abseil-cpp//absl/status", 61 | "@abseil-cpp//absl/status:statusor", 62 | "@abseil-cpp//absl/strings", 63 | "@abseil-cpp//absl/strings:str_format", 64 | "@rapidjson", 65 | ], 66 | ) 67 | 68 | cc_test( 69 | name = "database-parsers_test", 70 | srcs = [ 71 | "database-parsers_test.cc", 72 | ], 73 | deps = [ 74 | ":database-parsers", 75 | ":memory-mapped-file", 76 | "@abseil-cpp//absl/container:flat_hash_map", 77 | "@abseil-cpp//absl/status:statusor", 78 | "@abseil-cpp//absl/strings:str_format", 79 | "@googletest//:gtest", 80 | "@googletest//:gtest_main", 81 | ], 82 | ) 83 | 84 | cc_library( 85 | name = "database", 86 | srcs = [ 87 | "database.cc", 88 | ], 89 | hdrs = [ 90 | "database.h", 91 | ], 92 | deps = [ 93 | ":database-parsers", 94 | ":memory-mapped-file", 95 | "@abseil-cpp//absl/container:btree", 96 | "@abseil-cpp//absl/container:flat_hash_map", 97 | "@abseil-cpp//absl/container:flat_hash_set", 98 | "@abseil-cpp//absl/log:check", 99 | "@abseil-cpp//absl/status", 100 | "@abseil-cpp//absl/status:statusor", 101 | "@abseil-cpp//absl/strings", 102 | "@abseil-cpp//absl/strings:str_format", 103 | "@rapidjson", 104 | ], 105 | ) 106 | 107 | cc_test( 108 | name = "database_test", 109 | srcs = [ 110 | "database_test.cc", 111 | ], 112 | deps = [ 113 | ":database", 114 | ":database-parsers", 115 | "@abseil-cpp//absl/container:flat_hash_map", 116 | "@abseil-cpp//absl/container:flat_hash_set", 117 | "@abseil-cpp//absl/status:statusor", 118 | "@googletest//:gtest", 119 | "@googletest//:gtest_main", 120 | ], 121 | ) 122 | 123 | cc_binary( 124 | name = "fpga-as", 125 | srcs = [ 126 | "assembler.cc", 127 | ], 128 | deps = [ 129 | ":database", 130 | ":database-parsers", 131 | ":fasm-parser", 132 | ":memory-mapped-file", 133 | "//fpga/xilinx:arch-types", 134 | "//fpga/xilinx:bitstream", 135 | "@abseil-cpp//absl/cleanup:cleanup", 136 | "@abseil-cpp//absl/container:flat_hash_map", 137 | "@abseil-cpp//absl/container:flat_hash_set", 138 | "@abseil-cpp//absl/flags:flag", 139 | "@abseil-cpp//absl/flags:parse", 140 | "@abseil-cpp//absl/flags:usage", 141 | "@abseil-cpp//absl/log:check", 142 | "@abseil-cpp//absl/status", 143 | "@abseil-cpp//absl/strings", 144 | "@abseil-cpp//absl/strings:str_format", 145 | ], 146 | ) 147 | -------------------------------------------------------------------------------- /fpga/database-parsers.h: -------------------------------------------------------------------------------- 1 | #ifndef FPGA_DATABASE_PARSERS_H 2 | #define FPGA_DATABASE_PARSERS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "absl/container/flat_hash_map.h" 11 | #include "absl/status/statusor.h" 12 | 13 | namespace fpga { 14 | enum class ConfigBusType { 15 | kCLBIOCLK, 16 | kBlockRam, 17 | kCFGCLB, 18 | }; 19 | 20 | struct Location { 21 | uint32_t x; 22 | uint32_t y; 23 | }; 24 | 25 | using bits_addr_t = uint64_t; 26 | 27 | struct BitsBlockAlias { 28 | absl::flat_hash_map sites; 29 | uint32_t start_offset; 30 | std::string type; 31 | }; 32 | 33 | struct BitsBlock { 34 | std::optional alias; 35 | bits_addr_t base_address; 36 | uint32_t frames; 37 | uint32_t offset; 38 | uint32_t words; 39 | }; 40 | 41 | using Bits = absl::flat_hash_map; 42 | 43 | struct Tile { 44 | // Tile type. 45 | std::string type; 46 | 47 | // Grid coordinates. 48 | // x: column, increasing right. 49 | // y: row, increasing down. 50 | Location coord; 51 | 52 | // Maybe repeated. 53 | std::optional clock_region; 54 | 55 | // Tile configuration bits. 56 | Bits bits; 57 | 58 | // Indicates the special functions of the Tile pins. 59 | // Usually it is related to IOB blocks and indicates 60 | // i.e. differential output pins. 61 | absl::flat_hash_map pin_functions; 62 | 63 | // Maps to . 64 | absl::flat_hash_map sites; 65 | 66 | // Which sites not to use in the tile. 67 | std::vector prohibited_sites; 68 | }; 69 | 70 | using TileGrid = absl::flat_hash_map; 71 | 72 | enum class PseudoPIPType { 73 | kAlways, 74 | kDefault, 75 | kHint, 76 | }; 77 | 78 | // Pseudo Programmable Interconnect Points. 79 | using PseudoPIPs = absl::flat_hash_map; 80 | 81 | struct SegmentBit { 82 | // To which word the bit is part of. 83 | uint32_t word_column; 84 | 85 | // Word index of the bit to enable. 86 | uint32_t word_bit; 87 | 88 | // False if the char '!' is prepended. 89 | bool is_set; 90 | }; 91 | 92 | struct TileFeature { 93 | // Expecting a tile type and feature in a single string. 94 | std::string tile_feature; 95 | 96 | // If not specified in the db, is 0 by default. 97 | uint32_t address; 98 | 99 | template 100 | friend H AbslHashValue(H h, const TileFeature &c) { 101 | return H::combine(std::move(h), c.tile_feature, c.address); 102 | } 103 | auto operator<=>(const TileFeature &o) const = default; 104 | }; 105 | 106 | using SegmentsBits = absl::flat_hash_map>; 107 | 108 | struct PackagePin { 109 | std::string pin; 110 | uint32_t bank; 111 | std::string site; 112 | std::string tile; 113 | std::string pin_function; 114 | }; 115 | 116 | using PackagePins = std::vector; 117 | 118 | using IOBanksIDsToLocation = absl::flat_hash_map; 119 | 120 | // For each column index, associate a number of frames. 121 | using ConfigColumnsFramesCount = std::vector; 122 | 123 | using ClockRegionRow = 124 | absl::flat_hash_map; 125 | 126 | using GlobalClockRegionHalf = std::vector; 127 | 128 | struct GlobalClockRegions { 129 | GlobalClockRegionHalf bottom_rows; 130 | GlobalClockRegionHalf top_rows; 131 | }; 132 | 133 | struct Part { 134 | GlobalClockRegions global_clock_regions; 135 | uint32_t idcode; 136 | IOBanksIDsToLocation iobanks; 137 | }; 138 | 139 | struct PartInfo { 140 | std::string device; 141 | std::string fabric; 142 | std::string package; 143 | std::string speedgrade; 144 | }; 145 | 146 | // Parse family part. 147 | // ///part.json. 148 | absl::StatusOr ParsePartJSON(std::string_view content); 149 | 150 | // Parses file found at: 151 | // ///package_pins.csv 152 | // Expects a csv file containing: 153 | // ``` 154 | // pin,bank,site,tile,pin_function 155 | // A2,216,OPAD_X0Y2,GTP_CHANNEL_1_X97Y121,MGTPTXN1_216 156 | // ``` 157 | absl::StatusOr ParsePackagePins(std::string_view content); 158 | 159 | // Parse pseudo pips associated to each tile that is part 160 | // of a tile sub-type. 161 | absl::StatusOr ParsePseudoPIPsDatabase(std::string_view content); 162 | 163 | // Parse the segments bits associated to each tile that is part 164 | // of a tile sub-type. 165 | absl::StatusOr ParseSegmentsBitsDatabase( 166 | std::string_view content); 167 | 168 | absl::StatusOr> ParsePartsInfos( 169 | std::string_view parts_mapper_yaml, std::string_view devices_mapper_yaml); 170 | 171 | absl::StatusOr ParseTileGridJSON(std::string_view content); 172 | } // namespace fpga 173 | #endif // FPGA_DATABASE_PARSERS_H 174 | -------------------------------------------------------------------------------- /fpga/database.h: -------------------------------------------------------------------------------- 1 | #ifndef FPGA_DATABASE_H 2 | #define FPGA_DATABASE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "absl/container/btree_map.h" 15 | #include "absl/container/flat_hash_map.h" 16 | #include "absl/status/statusor.h" 17 | #include "fpga/database-parsers.h" 18 | 19 | namespace fpga { 20 | // Many to many map between banks and tiles. 21 | class BanksTilesRegistry { 22 | using tile_to_bank_type = 23 | absl::flat_hash_map>; 24 | using banks_to_tiles_type = 25 | absl::flat_hash_map>; 26 | 27 | public: 28 | using const_iterator = banks_to_tiles_type::const_iterator; 29 | const_iterator begin() const { return banks_to_tiles_.begin(); } 30 | const_iterator end() const { return banks_to_tiles_.end(); } 31 | 32 | static absl::StatusOr Create( 33 | const Part &part, const PackagePins &package_pins); 34 | 35 | // Get tiles from an IO bank name. 36 | std::optional> Tiles(uint32_t bank) const; 37 | 38 | // Get an IO bank from a tile. 39 | std::vector TileBanks(const std::string &tile) const; 40 | 41 | private: 42 | explicit BanksTilesRegistry(tile_to_bank_type tile_to_bank, 43 | banks_to_tiles_type banks_to_tiles) 44 | : tile_to_bank_(std::move(tile_to_bank)), 45 | banks_to_tiles_(std::move(banks_to_tiles)) {} 46 | const tile_to_bank_type tile_to_bank_; 47 | const banks_to_tiles_type banks_to_tiles_; 48 | }; 49 | 50 | inline constexpr uint32_t kFrameWordCount = 101; 51 | inline constexpr uint32_t kWordSizeBits = 32; 52 | 53 | // Define frame configutration word. 54 | using word_t = uint32_t; 55 | static_assert(8 * sizeof(word_t) == 32, "expected word size of 32"); 56 | 57 | // Frame is made of 101 words of 32-bit size. 58 | // Maps an address to an array of 101 words. 59 | using Frames = 60 | absl::btree_map>; 61 | 62 | struct SegmentsBitsWithPseudoPIPs { 63 | PseudoPIPs pips; 64 | absl::flat_hash_map segment_bits; 65 | }; 66 | 67 | // Maps tile types to segbits. 68 | using TileTypesSegmentsBitsGetter = 69 | std::function(std::string)>; 70 | 71 | // Centralize access to all the required information for a specific part. 72 | class PartDatabase { 73 | public: 74 | ~PartDatabase() = default; 75 | struct Tiles { 76 | Tiles(TileGrid grid, TileTypesSegmentsBitsGetter bits, 77 | BanksTilesRegistry banks, Part part) 78 | : grid(std::move(grid)), 79 | bits(std::move(bits)), 80 | banks(std::move(banks)), 81 | part(std::move(part)) {} 82 | TileGrid grid; 83 | TileTypesSegmentsBitsGetter bits; 84 | BanksTilesRegistry banks; 85 | Part part; 86 | }; 87 | explicit PartDatabase(std::shared_ptr part_tiles) 88 | : tiles_(std::move(part_tiles)) {} 89 | 90 | static absl::StatusOr Parse(std::string_view database_path, 91 | std::string_view part_name); 92 | 93 | struct FrameBit { 94 | uint32_t word; 95 | uint32_t index; 96 | }; 97 | using BitSetter = std::function; 99 | 100 | // Set bits to configure a feature in a specific tile. 101 | void ConfigBits(const std::string &tile_name, const std::string &feature, 102 | uint32_t address, const BitSetter &bit_setter); 103 | const struct Tiles &tiles() { return *tiles_; } 104 | 105 | private: 106 | bool AddSegbitsToCache(const std::string &tile_type); 107 | 108 | std::shared_ptr tiles_; 109 | absl::flat_hash_map 110 | segment_bits_cache_; 111 | }; 112 | } // namespace fpga 113 | #endif // FPGA_DATABASE_H 114 | -------------------------------------------------------------------------------- /fpga/database_test.cc: -------------------------------------------------------------------------------- 1 | #include "fpga/database.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "absl/container/flat_hash_map.h" 8 | #include "absl/container/flat_hash_set.h" 9 | #include "absl/status/statusor.h" 10 | #include "fpga/database-parsers.h" 11 | #include "gmock/gmock.h" 12 | #include "gtest/gtest.h" 13 | 14 | namespace fpga { 15 | namespace { 16 | struct CorrectMappingAndTileNamesTestCase { 17 | Part part; 18 | PackagePins package_pins; 19 | absl::StatusOr>> 20 | expected_banks_tiles_res; 21 | }; 22 | 23 | // Tile names should be unique per bank. The IOBank locations should be 24 | // prepended by "HCLK_IOI3_" to make the final tile name. 25 | TEST(BanksTilesRegistry, CorrectMappingAndTileNames) { 26 | // clang-format off 27 | const struct CorrectMappingAndTileNamesTestCase kTestCases[] = {{ 28 | .part = { 29 | {}, {}, IOBanksIDsToLocation{{0, "X1Y78"}, {3, "X2Y43"}, {4, "X1Y78"}} 30 | }, 31 | .package_pins = { 32 | {{}, 0, {}, "LIOB33_X0Y93", {}}, 33 | {{}, 216, {}, "GTP_CHANNEL_1_X97Y121", {}}, 34 | {{}, 0, {}, "HCLK_IOI3_X1Y79", {}} 35 | }, 36 | .expected_banks_tiles_res = {{ 37 | {0, {"HCLK_IOI3_X1Y78", "LIOB33_X0Y93", "HCLK_IOI3_X1Y79"}}, 38 | {3, {"HCLK_IOI3_X2Y43"}}, 39 | {4, {"HCLK_IOI3_X1Y78"}}, 40 | {216, {"GTP_CHANNEL_1_X97Y121"}}} 41 | }, 42 | }, 43 | }; 44 | // clang-format on 45 | for (const auto &test : kTestCases) { 46 | const absl::StatusOr res = 47 | BanksTilesRegistry::Create(test.part, test.package_pins); 48 | if (test.expected_banks_tiles_res.ok()) { 49 | ASSERT_TRUE(res.ok()) << res.status().message(); 50 | } 51 | const absl::flat_hash_map> &expected = 52 | test.expected_banks_tiles_res.value(); 53 | const BanksTilesRegistry ®istry = res.value(); 54 | for (const auto &pair : expected) { 55 | const auto maybe_tiles = registry.Tiles(pair.first); 56 | ASSERT_TRUE(maybe_tiles.has_value()); 57 | // NOLINTNEXTLINE(bugprone-unchecked-optional-access) 58 | const std::vector &tiles_vector = maybe_tiles.value(); 59 | const absl::flat_hash_set actual_tiles(tiles_vector.begin(), 60 | tiles_vector.end()); 61 | // Vector must have unique elements (tile names). 62 | EXPECT_EQ(tiles_vector.size(), actual_tiles.size()); 63 | 64 | const absl::flat_hash_set expected_tiles(pair.second.begin(), 65 | pair.second.end()); 66 | EXPECT_EQ(actual_tiles, expected_tiles); 67 | 68 | // Check all the tiles can be mapped back to the bank. 69 | for (const auto &tile : tiles_vector) { 70 | const std::vector banks = registry.TileBanks(tile); 71 | ASSERT_FALSE(banks.empty()); 72 | EXPECT_THAT(banks, ::testing::Contains(pair.first)); 73 | } 74 | } 75 | } 76 | } 77 | } // namespace 78 | } // namespace fpga 79 | -------------------------------------------------------------------------------- /fpga/fasm-parser_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Henner Zeller 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "fpga/fasm-parser.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | using fasm::ParseResult; 26 | 27 | // Make ParseResult printable so that we can use it in test outputs. 28 | std::ostream &operator<<(std::ostream &o, fasm::ParseResult r) { 29 | switch (r) { 30 | case fasm::ParseResult::kSuccess: return o << "Success"; 31 | case fasm::ParseResult::kInfo: return o << "Info"; 32 | case fasm::ParseResult::kNonCritical: return o << "NonCritical"; 33 | case fasm::ParseResult::kSkipped: return o << "Skipped"; 34 | case fasm::ParseResult::kUserAbort: return o << "UserAbort"; 35 | case fasm::ParseResult::kError: return o << "Error"; 36 | } 37 | return o; 38 | } 39 | 40 | static int expect_mismatch_count = 0; 41 | #define EXPECT_EQ(a, b) \ 42 | if ((a) == (b)) { \ 43 | } else \ 44 | (++expect_mismatch_count, std::cerr) \ 45 | << __LINE__ << ": EXPECT FAIL (" << #a << " == " << #b << ") (" << (a) \ 46 | << " vs. " << (b) << ") " 47 | 48 | struct ValueTestCase { 49 | std::string_view input; 50 | // Expected outputs 51 | fasm::ParseResult result; 52 | std::string_view feature_name; 53 | int min_bit; 54 | int width; 55 | uint64_t bits; 56 | }; 57 | 58 | void ValueParseTest() { 59 | std::cout << "\n-- Value parse test -- \n"; 60 | constexpr ValueTestCase tests[] = { 61 | // Names 62 | {"DOTS.IN.FEATURE", ParseResult::kSuccess, "DOTS.IN.FEATURE", 0, 1, 1}, 63 | {"D_1_G1TS", ParseResult::kSuccess, "D_1_G1TS", 0, 1, 1}, 64 | {" \tINDENTED # foo", ParseResult::kSuccess, "INDENTED", 0, 1, 1}, 65 | 66 | // We don't validate if the start of an identifier is actually in the 67 | // allowed set that won't include digits. 68 | // The receiver will verify if the feature exists. So 'Success' it is. 69 | {"0valid", ParseResult::kSuccess, "0valid", 0, 1, 1}, 70 | {"[8:0]", ParseResult::kError, "", 0, 0, 0}, // Range without feature. 71 | 72 | // Empty lines and comments 73 | {"", ParseResult::kSuccess, "", 0, 0, 0}, // Callback never called 74 | {" # hello ", ParseResult::kSuccess, "", 0, 0, 0}, // dito 75 | {"COMMENT # more stuff", ParseResult::kSuccess, "COMMENT", 0, 1, 1}, 76 | {"COMMENT[3:0] = 12 # ok", ParseResult::kSuccess, "COMMENT", 0, 4, 12}, 77 | 78 | // Implicit set with no assign; explicit zero assign. 79 | {"IMPLICIT_ONE", ParseResult::kSuccess, "IMPLICIT_ONE", 0, 1, 1}, 80 | {"EXPLICIT_ZERO = 0", ParseResult::kSuccess, "EXPLICIT_ZERO", 0, 1, 0}, 81 | 82 | // An equal assignment without any value followed is interpreted as 83 | // zero. Maybe too lenient, so maybe should be kError ? 84 | {"IMPLICIT_ZERO[8:0] = # no value assigned", ParseResult::kSuccess, 85 | "IMPLICIT_ZERO", 0, 9, 0}, 86 | 87 | // Parsing numbers with included underscores 88 | {"UNDERSCORE_BITPOS[ _8_ ]", ParseResult::kSuccess, // 89 | "UNDERSCORE_BITPOS", 8, 1, 1}, 90 | {"UNDERSCORE_DECIMAL[15:0] = 1_234", ParseResult::kSuccess, // 91 | "UNDERSCORE_DECIMAL", 0, 16, 1234}, 92 | {"UNDERSCORE_HEXVALUE[15:0] = 'hAB_CD", ParseResult::kSuccess, // 93 | "UNDERSCORE_HEXVALUE", 0, 16, 0xabcd}, 94 | 95 | // Decimal, hex, binary and octal 96 | {"ASSIGN_DECIMAL[3:0] = 5", ParseResult::kSuccess, // 97 | "ASSIGN_DECIMAL", 0, 4, 5}, 98 | {"ASSIGN_DECIMAL[3:0] = 4'd5", ParseResult::kSuccess, // 99 | "ASSIGN_DECIMAL", 0, 4, 5}, 100 | // Invalid digit at end. 101 | {"ASSIGN_BROKEN_DEC[7:0] = 4'd5a", ParseResult::kError, // 102 | "ASSIGN_BROKEN_DEC", 0, 8, 5}, 103 | 104 | {"ASSIGN_HEX1[15:0] = 16'hCa_Fe", ParseResult::kSuccess, // 105 | "ASSIGN_HEX1", 0, 16, 0xcafe}, 106 | {"ASSIGN_HEX2[31:0] = 32'h_dead_beef", ParseResult::kSuccess, // 107 | "ASSIGN_HEX2", 0, 32, 0xdeadbeef}, 108 | {"ASSIGN_HEX3[31:0] = 32 ' h _dead_beef ", ParseResult::kSuccess, // 109 | "ASSIGN_HEX3", 0, 32, 0xdeadbeef}, 110 | 111 | {"BINARY[63:48] = 16'b1111_0000_1111_0000", ParseResult::kSuccess, "BINARY", 112 | 48, 16, 0xF0F0}, 113 | {"ASSIGN_OCT[8:0] = 9'o644", ParseResult::kSuccess, // 114 | "ASSIGN_OCT", 0, 9, 0644}, 115 | {"UNKNOWN_BASE[7:0] = 8'y123", ParseResult::kError, // 116 | "UNKNOWN_BASE", 0, 8, 1}, // fallback to default on bit. 117 | 118 | // Unannounced hex value. 119 | {"ASSIGN_INVALID[8:0] = beef # hex not expected", ParseResult::kError, 120 | "ASSIGN_INVALID", 0, 9, 0}, 121 | {"ASSIGN_INVALID[8:0] = 5beef # starts valid dec", ParseResult::kError, 122 | "ASSIGN_INVALID", 0, 9, 5}, 123 | 124 | // Error: inverted ranges or plain old parse errors. 125 | {"INVERTED_RANGE[0:8]", ParseResult::kSkipped, // 126 | "", 0, 0, 0}, // Callback never called 127 | {"BRACKET_MISSING[4:0xyz", ParseResult::kError, // 128 | "", 0, 0, 0}, // Callback never called 129 | 130 | // Numbers longer than 64 bit can not be dealt with, only best effort 131 | // parse 132 | {"VERY_LONG_NOT_SUPPORTED[255:0] = 256'h1", ParseResult::kError, 133 | "VERY_LONG_NOT_SUPPORTED", 0, 64, 1}, // Short enough to parse complete 134 | {"BEST_EFFORT[127:0] = 128'hdeadbeef_deadbeef_c0feface_1337f00d", 135 | ParseResult::kError, // 136 | "BEST_EFFORT", 0, 64, 0xc0feface1337f00d}, // Truncated 137 | 138 | // Examples from README. 139 | {"FOO[255:192] = 42", ParseResult::kSuccess, "FOO", 192, 64, 42}, 140 | {"BAR[255:0] = 42", ParseResult::kError, "BAR", 0, 64, 42}, 141 | 142 | // Attempt to assign too wide number; warn but comes back properly shaved 143 | {"ASSIGN_HEX[15:0] = 32'hcafebabe", ParseResult::kNonCritical, "ASSIGN_HEX", 144 | 0, 16, 0xbabe}, 145 | {"ASSIGN_DECIMAL[3:0] = 255", ParseResult::kSuccess, // 146 | "ASSIGN_DECIMAL", 0, 4, 0x0F}, // Shaved down 147 | 148 | // Annotations are acknowledged, but ignored. 149 | // Global annotation, no feature. Callback never called. 150 | {"{.global = \"annotation\"}", ParseResult::kSuccess, "", 0, 0, 0}, 151 | 152 | // Even though annotations are ignored, the values are still parsed. 153 | {"HELLO {.foo = \"bar\"}", ParseResult::kSuccess, "HELLO", 0, 1, 1}, 154 | {"HELLO[5:0] = 42{.foo = \"bar\"}", ParseResult::kSuccess, // 155 | "HELLO", 0, 6, 42}, 156 | {"EXPLICIT_ZERO = 0 {.foo = \"bar\"}", ParseResult::kSuccess, 157 | "EXPLICIT_ZERO", 0, 1, 0}, 158 | }; 159 | 160 | for (const ValueTestCase &expected : tests) { 161 | for (const char *line_ending : {"\n", "\r\n"}) { 162 | const std::string line = std::string(expected.input); 163 | const std::string input = line + line_ending; 164 | bool was_called = false; 165 | auto result = fasm::Parse( 166 | input, stderr, 167 | [&](uint32_t, std::string_view n, int min_bit, int width, 168 | uint64_t bits) { 169 | was_called = true; 170 | EXPECT_EQ(n, expected.feature_name) << expected.input << "\n"; 171 | EXPECT_EQ(min_bit, expected.min_bit) << expected.input << "\n"; 172 | EXPECT_EQ(width, expected.width) << expected.input << "\n"; 173 | EXPECT_EQ(bits, expected.bits) << expected.input << "\n"; 174 | return true; 175 | }); 176 | 177 | EXPECT_EQ(result, expected.result) << expected.input << "\n"; 178 | // If the expected the callback to be called, the expect data will have 179 | // a width != 0. 180 | EXPECT_EQ(was_called, (expected.width != 0)) << expected.input << "\n"; 181 | } 182 | } 183 | } 184 | 185 | struct AnnotationTestCase { 186 | std::string_view input; 187 | fasm::ParseResult result; 188 | // Expected outputs 189 | std::vector> annotations; 190 | }; 191 | void AnnotationParseTest() { 192 | std::cout << "\n-- Annotation parse test -- \n"; 193 | const AnnotationTestCase tests[] = { 194 | // Simple, multi name=value pair 195 | {"{ foo = \"bar\", baz = \"quux\" }\n", 196 | ParseResult::kSuccess, 197 | {{"foo", "bar"}, {"baz", "quux"}}}, 198 | 199 | {"SOME_FEATURE = 42 { foo = \"bar\", baz = \"quux\" }\n", 200 | ParseResult::kSuccess, 201 | {{"foo", "bar"}, {"baz", "quux"}}}, 202 | 203 | // Value with backslash-escaped quote 204 | {"{ .escaped = \"Some quote with \\\"quote\\\"\" }\n", 205 | ParseResult::kSuccess, 206 | {{".escaped", R"(Some quote with \"quote\")"}}}, 207 | 208 | // Error: String quote missing around value 209 | {"{ foo = \"bar\", baz = quux\" }\n", 210 | ParseResult::kError, 211 | {{"foo", "bar"}}}, 212 | 213 | // Error: Semicolon instead of comma. 214 | {"{ foo = \"bar\"; baz = \"quux\" }\n", 215 | ParseResult::kError, 216 | {{"foo", "bar"}}}, 217 | 218 | // Error: String does not end - failed to find " at end of line 219 | {"{ unterminated = \"string }\nNEXT_LINE\n", ParseResult::kError, {}}, 220 | 221 | {"{ line_continuation_is_error = \"string\\\nNEXT_LINE\"\n", 222 | ParseResult::kError, 223 | {}}, 224 | }; 225 | 226 | for (const AnnotationTestCase &expected : tests) { 227 | auto annotation_pos = expected.annotations.begin(); 228 | auto result = fasm::Parse( 229 | expected.input, stderr, 230 | [&](uint32_t, std::string_view feature_name, int, int, uint64_t) { 231 | // Global annotations don't have a feature associated with it. This 232 | // callback should only be called if there is a feature. 233 | EXPECT_EQ(feature_name.empty(), false) << expected.input; 234 | return true; 235 | }, 236 | [&](uint32_t, std::string_view, // 237 | std::string_view name, std::string_view value) { 238 | EXPECT_EQ(annotation_pos == expected.annotations.end(), false) 239 | << expected.input; 240 | EXPECT_EQ(annotation_pos->first, name) << expected.input; 241 | EXPECT_EQ(annotation_pos->second, value) << expected.input; 242 | ++annotation_pos; 243 | std::cout << name << " = " << value << "\n"; 244 | }); 245 | EXPECT_EQ(annotation_pos == expected.annotations.end(), true) 246 | << expected.input; 247 | 248 | EXPECT_EQ(result, expected.result) << expected.input; 249 | } 250 | } 251 | 252 | int main() { 253 | ValueParseTest(); 254 | AnnotationParseTest(); 255 | 256 | if (expect_mismatch_count == 0) { 257 | printf("\nPASS, all expectations met.\n"); 258 | } else { 259 | printf("\nFAIL, %d expectations **not** met.\n", expect_mismatch_count); 260 | } 261 | 262 | return expect_mismatch_count; 263 | } 264 | -------------------------------------------------------------------------------- /fpga/memory-mapped-file.cc: -------------------------------------------------------------------------------- 1 | #include "fpga/memory-mapped-file.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "absl/status/status.h" 14 | #include "absl/status/statusor.h" 15 | #include "absl/strings/str_format.h" 16 | 17 | namespace fpga { 18 | namespace { 19 | class MemoryMappedFile final : public MemoryBlock { 20 | public: 21 | MemoryMappedFile(char *const data, const size_t size) 22 | : data_(data), size_(size) {} 23 | 24 | std::string_view AsStringView() const override { return {data_, size_}; } 25 | 26 | ~MemoryMappedFile() override { munmap(data_, size_); } 27 | 28 | private: 29 | char *const data_; 30 | const size_t size_; 31 | }; 32 | } // namespace 33 | 34 | absl::StatusOr> MemoryMapFile( 35 | std::string_view path) { 36 | const int fd = open(std::string(path).c_str(), O_RDONLY); 37 | if (fd < 0) { 38 | return absl::Status(absl::ErrnoToStatus( 39 | errno, absl::StrFormat("could not open file: %s", path))); 40 | } 41 | struct stat s; 42 | fstat(fd, &s); 43 | const size_t file_size = s.st_size; 44 | // Memory map everything into a convenient contiguous buffer 45 | void *const buffer = mmap(nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0); 46 | close(fd); 47 | return std::make_unique((char *)buffer, file_size); 48 | } 49 | } // namespace fpga 50 | -------------------------------------------------------------------------------- /fpga/memory-mapped-file.h: -------------------------------------------------------------------------------- 1 | #ifndef FPGA_MEMORY_MAPPED_FILE_H 2 | #define FPGA_MEMORY_MAPPED_FILE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "absl/status/statusor.h" 11 | #include "absl/types/span.h" 12 | 13 | namespace fpga { 14 | // Taken from: verible/common/strings/mem-block.h 15 | class MemoryBlock { 16 | public: 17 | virtual ~MemoryBlock() = default; 18 | virtual std::string_view AsStringView() const = 0; 19 | absl::Span AsBytesView() const { 20 | std::string_view const bytes = AsStringView(); 21 | return {reinterpret_cast(bytes.data()), bytes.size()}; 22 | } 23 | 24 | protected: 25 | MemoryBlock() = default; 26 | 27 | public: 28 | MemoryBlock(const MemoryBlock &) = delete; 29 | MemoryBlock(MemoryBlock &&) = delete; 30 | MemoryBlock &operator=(const MemoryBlock &) = delete; 31 | MemoryBlock &operator=(MemoryBlock &&) = delete; 32 | }; 33 | 34 | absl::StatusOr> MemoryMapFile( 35 | std::string_view path); 36 | 37 | inline absl::StatusOr> MemoryMapFile( 38 | const std::filesystem::path &path) { 39 | return MemoryMapFile(std::string_view(std::string(path))); 40 | } 41 | } // namespace fpga 42 | #endif // FPGA_MEMORY_MAPPED_FILE_H 43 | -------------------------------------------------------------------------------- /fpga/xilinx/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") 2 | load("@rules_license//rules:license.bzl", "license") 3 | 4 | package( 5 | default_applicable_licenses = [":license"], 6 | default_visibility = ["//:__subpackages__"], 7 | ) 8 | 9 | license( 10 | name = "license", 11 | package_name = "xilinx", 12 | license_kinds = [ 13 | "@rules_license//licenses/spdx:ISC", 14 | ], 15 | ) 16 | 17 | cc_library( 18 | name = "big-endian-span", 19 | hdrs = [ 20 | "big-endian-span.h", 21 | ], 22 | deps = [ 23 | "@abseil-cpp//absl/types:span", 24 | ], 25 | ) 26 | 27 | cc_test( 28 | name = "big-endian-span_test", 29 | srcs = [ 30 | "big-endian-span_test.cc", 31 | ], 32 | deps = [ 33 | ":big-endian-span", 34 | "@googletest//:gtest", 35 | "@googletest//:gtest_main", 36 | ], 37 | ) 38 | 39 | cc_library( 40 | name = "bit-ops", 41 | hdrs = [ 42 | "bit-ops.h", 43 | ], 44 | ) 45 | 46 | cc_test( 47 | name = "bit-ops_test", 48 | srcs = [ 49 | "bit-ops_test.cc", 50 | ], 51 | deps = [ 52 | ":bit-ops", 53 | "@googletest//:gtest", 54 | "@googletest//:gtest_main", 55 | ], 56 | ) 57 | 58 | cc_library( 59 | name = "arch-xc7-frame", 60 | srcs = [ 61 | "arch-xc7-frame.cc", 62 | ], 63 | hdrs = [ 64 | "arch-xc7-frame.h", 65 | ], 66 | deps = [ 67 | ":bit-ops", 68 | ], 69 | ) 70 | 71 | cc_test( 72 | name = "arch-xc7-frame_test", 73 | srcs = [ 74 | "arch-xc7-frame_test.cc", 75 | ], 76 | deps = [ 77 | ":arch-xc7-frame", 78 | "@googletest//:gtest", 79 | "@googletest//:gtest_main", 80 | ], 81 | ) 82 | 83 | cc_library( 84 | name = "configuration-packet", 85 | hdrs = [ 86 | "configuration-packet.h", 87 | ], 88 | deps = [ 89 | "@abseil-cpp//absl/types:optional", 90 | "@abseil-cpp//absl/types:span", 91 | ], 92 | ) 93 | 94 | cc_library( 95 | name = "arch-xc7-configuration-packet", 96 | srcs = [ 97 | "arch-xc7-configuration-packet.cc", 98 | ], 99 | hdrs = [ 100 | "arch-xc7-configuration-packet.h", 101 | ], 102 | deps = [ 103 | ":arch-xc7-frame", 104 | ":bit-ops", 105 | ":configuration-packet", 106 | "@abseil-cpp//absl/types:span", 107 | ], 108 | ) 109 | 110 | cc_test( 111 | name = "arch-xc7-configuration-packet_test", 112 | srcs = [ 113 | "arch-xc7-configuration-packet_test.cc", 114 | ], 115 | deps = [ 116 | ":arch-xc7-configuration-packet", 117 | ":arch-xc7-frame", 118 | ":bit-ops", 119 | "@abseil-cpp//absl/types:span", 120 | "@googletest//:gtest", 121 | "@googletest//:gtest_main", 122 | ], 123 | ) 124 | 125 | cc_library( 126 | name = "arch-xc7-part", 127 | srcs = [ 128 | "arch-xc7-part.cc", 129 | ], 130 | hdrs = [ 131 | "arch-xc7-part.h", 132 | ], 133 | deps = [ 134 | ":arch-xc7-frame", 135 | "//fpga:database-parsers", 136 | "//fpga:memory-mapped-file", 137 | "@abseil-cpp//absl/container:btree", 138 | "@abseil-cpp//absl/log:check", 139 | "@abseil-cpp//absl/status:statusor", 140 | ], 141 | ) 142 | 143 | cc_test( 144 | name = "arch-xc7-part_test", 145 | srcs = [ 146 | "arch-xc7-part_test.cc", 147 | ], 148 | deps = [ 149 | ":arch-xc7-frame", 150 | ":arch-xc7-part", 151 | "@googletest//:gtest", 152 | "@googletest//:gtest_main", 153 | ], 154 | ) 155 | 156 | cc_library( 157 | name = "arch-types", 158 | hdrs = [ 159 | "arch-types.h", 160 | ], 161 | deps = [ 162 | ":arch-xc7-configuration-packet", 163 | ":arch-xc7-frame", 164 | ":arch-xc7-part", 165 | ], 166 | ) 167 | 168 | cc_library( 169 | name = "frames", 170 | hdrs = [ 171 | "frames.h", 172 | ], 173 | deps = [ 174 | ":arch-types", 175 | ], 176 | ) 177 | 178 | cc_test( 179 | name = "frames-xc7_test", 180 | srcs = [ 181 | "frames-xc7_test.cc", 182 | ], 183 | deps = [ 184 | ":arch-types", 185 | ":arch-xc7-frame", 186 | ":frames", 187 | "@googletest//:gtest", 188 | "@googletest//:gtest_main", 189 | ], 190 | ) 191 | 192 | cc_library( 193 | name = "configuration", 194 | srcs = [ 195 | "configuration.cc", 196 | ], 197 | hdrs = [ 198 | "configuration.h", 199 | ], 200 | deps = [ 201 | ":arch-types", 202 | ":arch-xc7-configuration-packet", 203 | ":bit-ops", 204 | ":configuration-packet", 205 | "@abseil-cpp//absl/container:btree", 206 | "@abseil-cpp//absl/log:check", 207 | "@abseil-cpp//absl/types:optional", 208 | "@abseil-cpp//absl/types:span", 209 | ], 210 | ) 211 | 212 | cc_test( 213 | name = "configuration-xc7_test", 214 | srcs = [ 215 | "configuration-xc7_test.cc", 216 | ], 217 | data = [ 218 | "testdata/xc7-configuration.bit", 219 | "testdata/xc7-configuration.debug.bit", 220 | "testdata/xc7-configuration.perframecrc.bit", 221 | "testdata/xc7-configuration-test.json", 222 | ], 223 | deps = [ 224 | ":arch-types", 225 | ":arch-xc7-frame", 226 | ":bitstream-reader", 227 | ":configuration", 228 | ":configuration-packet", 229 | ":frames", 230 | "//fpga:database-parsers", 231 | "//fpga:memory-mapped-file", 232 | "@abseil-cpp//absl/log:check", 233 | "@abseil-cpp//absl/status:statusor", 234 | "@abseil-cpp//absl/types:optional", 235 | "@abseil-cpp//absl/types:span", 236 | "@googletest//:gtest", 237 | "@googletest//:gtest_main", 238 | ], 239 | ) 240 | 241 | cc_library( 242 | name = "bitstream-reader", 243 | hdrs = [ 244 | "bitstream-reader.h", 245 | ], 246 | deps = [ 247 | ":arch-types", 248 | ":big-endian-span", 249 | "@abseil-cpp//absl/types:optional", 250 | "@abseil-cpp//absl/types:span", 251 | ], 252 | ) 253 | 254 | cc_test( 255 | name = "bitstream-reader_test", 256 | srcs = [ 257 | "bitstream-reader-xc7_test.cc", 258 | ], 259 | deps = [ 260 | ":arch-types", 261 | ":bitstream-reader", 262 | "@abseil-cpp//absl/types:span", 263 | "@googletest//:gtest_main", 264 | ], 265 | ) 266 | 267 | cc_library( 268 | name = "bitstream-writer", 269 | srcs = [ 270 | "bitstream-writer.cc", 271 | ], 272 | hdrs = [ 273 | "bitstream-writer.h", 274 | ], 275 | deps = [ 276 | ":arch-types", 277 | ":arch-xc7-configuration-packet", 278 | ":bit-ops", 279 | ":configuration-packet", 280 | "@abseil-cpp//absl/log:check", 281 | "@abseil-cpp//absl/strings", 282 | "@abseil-cpp//absl/time", 283 | "@abseil-cpp//absl/types:optional", 284 | "@abseil-cpp//absl/types:span", 285 | ], 286 | ) 287 | 288 | cc_test( 289 | name = "bitstream-writer_test", 290 | srcs = [ 291 | "bitstream-writer_test.cc", 292 | ], 293 | deps = [ 294 | ":arch-types", 295 | ":arch-xc7-configuration-packet", 296 | ":bit-ops", 297 | ":bitstream-writer", 298 | ":configuration-packet", 299 | "@abseil-cpp//absl/types:span", 300 | "@googletest//:gtest", 301 | "@googletest//:gtest_main", 302 | ], 303 | ) 304 | 305 | cc_library( 306 | name = "bitstream", 307 | hdrs = [ 308 | "bitstream.h", 309 | ], 310 | deps = [ 311 | ":arch-types", 312 | ":bitstream-writer", 313 | ":configuration", 314 | ":frames", 315 | "//fpga:database-parsers", 316 | "@abseil-cpp//absl/container:btree", 317 | "@abseil-cpp//absl/status", 318 | "@abseil-cpp//absl/status:statusor", 319 | "@abseil-cpp//absl/strings:string_view", 320 | ], 321 | ) 322 | -------------------------------------------------------------------------------- /fpga/xilinx/README.md: -------------------------------------------------------------------------------- 1 | # Xilinx Bitstream Generation 2 | 3 | Most of this code originates from [prjxray][prjxray]. It was imported into this repository to simplify type interfacing and eliminate the dependency on yaml-cpp, which requires exceptions to be enabled. 4 | 5 | [prjxray]: https://github.com/f4pga/prjxray/tree/faf9c774a340e39cf6802d009996ed6016e63521/lib 6 | -------------------------------------------------------------------------------- /fpga/xilinx/arch-types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #ifndef FPGA_XILINX_ARCH_H 11 | #define FPGA_XILINX_ARCH_H 12 | 13 | #include 14 | #include 15 | 16 | #include "fpga/xilinx/arch-xc7-configuration-packet.h" 17 | #include "fpga/xilinx/arch-xc7-frame.h" 18 | #include "fpga/xilinx/arch-xc7-part.h" 19 | 20 | namespace fpga { 21 | namespace xilinx { 22 | enum class Architecture { 23 | kXC7, 24 | kXC7UltraScale, 25 | kXC7UltraScalePlus, 26 | }; 27 | 28 | template 29 | struct ArchitectureTraits; 30 | 31 | template 32 | struct ArchitectureXC7Base { 33 | using Part = xc7::Part; 34 | using ConfigurationPacket = xc7::ConfigurationPacket; 35 | using ConfigurationPackage = 36 | std::vector>; 37 | using ConfigurationRegister = ConfigurationPacket::ConfRegType; 38 | using FrameAddress = xc7::FrameAddress; 39 | using FrameWords = xc7::FrameWords; 40 | }; 41 | 42 | template <> 43 | struct ArchitectureTraits { 44 | using type = ArchitectureXC7Base; 45 | }; 46 | 47 | template <> 48 | struct ArchitectureTraits { 49 | using type = ArchitectureXC7Base; 50 | }; 51 | 52 | template <> 53 | struct ArchitectureTraits { 54 | using type = ArchitectureXC7Base; 55 | }; 56 | 57 | template 58 | using ArchitectureType = ArchitectureTraits::type; 59 | } // namespace xilinx 60 | } // namespace fpga 61 | #endif // FPGA_XILINX_ARCH_H 62 | -------------------------------------------------------------------------------- /fpga/xilinx/arch-xc7-configuration-packet.cc: -------------------------------------------------------------------------------- 1 | #include "fpga/xilinx/arch-xc7-configuration-packet.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "absl/types/span.h" 8 | #include "fpga/xilinx/arch-xc7-frame.h" 9 | #include "fpga/xilinx/bit-ops.h" 10 | #include "fpga/xilinx/configuration-packet.h" 11 | 12 | namespace fpga { 13 | namespace xilinx { 14 | namespace xc7 { 15 | std::ostream &operator<<(std::ostream &o, const ConfigurationRegister &value) { 16 | switch (value) { 17 | case ConfigurationRegister::kCRC: return o << "CRC"; 18 | case ConfigurationRegister::kFAR: return o << "Frame Address"; 19 | case ConfigurationRegister::kFDRI: return o << "Frame Data Input"; 20 | case ConfigurationRegister::kFDRO: return o << "Frame Data Output"; 21 | case ConfigurationRegister::kCMD: return o << "Command"; 22 | case ConfigurationRegister::kCTL0: return o << "Control 0"; 23 | case ConfigurationRegister::kMASK: return o << "Mask for CTL0 and CTL1"; 24 | case ConfigurationRegister::kSTAT: return o << "Status"; 25 | case ConfigurationRegister::kLOUT: return o << "Legacy Output"; 26 | case ConfigurationRegister::kCOR0: return o << "Configuration Option 0"; 27 | case ConfigurationRegister::kMFWR: return o << "Multiple Frame Write"; 28 | case ConfigurationRegister::kCBC: return o << "Initial CBC Value"; 29 | case ConfigurationRegister::kIDCODE: return o << "Device ID"; 30 | case ConfigurationRegister::kAXSS: return o << "User Access"; 31 | case ConfigurationRegister::kCOR1: return o << "Configuration Option 1"; 32 | case ConfigurationRegister::kWBSTAR: return o << "Warm Boot Start Address"; 33 | case ConfigurationRegister::kTIMER: return o << "Watchdog Timer"; 34 | case ConfigurationRegister::kBOOTSTS: return o << "Boot History Status"; 35 | case ConfigurationRegister::kCTL1: return o << "Control 1"; 36 | case ConfigurationRegister::kBSPI: 37 | return o << "BPI/SPI Configuration Options"; 38 | default: return o << "Unknown"; 39 | } 40 | } 41 | 42 | ConfigurationPacket::ParseResult ConfigurationPacket::InitWithWordsImpl( 43 | absl::Span words, const ConfigurationPacket *previous_packet) { 44 | using ConfigurationRegister = ConfigurationRegister; 45 | // Need at least one 32-bit word to have a valid packet header. 46 | if (words.empty()) { 47 | return {words, {}}; 48 | } 49 | const ConfigurationPacketType header_type = 50 | static_cast(bit_field_get(words[0], 31, 29)); 51 | switch (header_type) { 52 | case ConfigurationPacketType::kNONE: 53 | // Type 0 is emitted at the end of a configuration row 54 | // when BITSTREAM.GENERAL.DEBUGBITSTREAM is set to YES. 55 | // These seem to be padding that are interepreted as 56 | // NOPs. Since Type 0 packets don't exist according to 57 | // UG470 and they seem to be zero-filled, just consume 58 | // the bytes without generating a packet. 59 | return {words.subspan(1), 60 | {{static_cast(header_type), 61 | Opcode::kNOP, 62 | ConfigurationRegister::kCRC, 63 | {}}}}; 64 | case ConfigurationPacketType::kTYPE1: { 65 | const Opcode opcode = static_cast(bit_field_get(words[0], 28, 27)); 66 | const ConfigurationRegister address = 67 | static_cast(bit_field_get(words[0], 26, 13)); 68 | const uint32_t data_word_count = bit_field_get(words[0], 10, 0); 69 | 70 | // If the full packet has not been received, return as 71 | // though no valid packet was found. 72 | if (data_word_count > words.size() - 1) { 73 | return {words, {}}; 74 | } 75 | 76 | return {words.subspan(data_word_count + 1), 77 | {{static_cast(header_type), opcode, address, 78 | words.subspan(1, data_word_count)}}}; 79 | } 80 | case ConfigurationPacketType::kTYPE2: { 81 | std::optional packet; 82 | const Opcode opcode = static_cast(bit_field_get(words[0], 28, 27)); 83 | const uint32_t data_word_count = bit_field_get(words[0], 26, 0); 84 | 85 | // If the full packet has not been received, return as 86 | // though no valid packet was found. 87 | if (data_word_count > words.size() - 1) { 88 | return {words, {}}; 89 | } 90 | 91 | if (previous_packet) { 92 | packet = ConfigurationPacket(static_cast(header_type), opcode, 93 | previous_packet->address(), 94 | words.subspan(1, data_word_count)); 95 | } 96 | 97 | return {words.subspan(data_word_count + 1), packet}; 98 | } 99 | default: return {{}, {}}; 100 | } 101 | } 102 | } // namespace xc7 103 | } // namespace xilinx 104 | } // namespace fpga 105 | -------------------------------------------------------------------------------- /fpga/xilinx/arch-xc7-configuration-packet.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #ifndef FPGA_XILINX_ARCH_XC7_CONFIGURATION_PACKET_H 11 | #define FPGA_XILINX_ARCH_XC7_CONFIGURATION_PACKET_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "absl/types/span.h" 19 | #include "fpga/xilinx/arch-xc7-frame.h" 20 | #include "fpga/xilinx/bit-ops.h" 21 | #include "fpga/xilinx/configuration-packet.h" 22 | 23 | namespace fpga { 24 | namespace xilinx { 25 | namespace xc7 { 26 | enum class Command : uint32_t { 27 | kNOP = 0x0, 28 | kWCFG = 0x1, 29 | kMFW = 0x2, 30 | kLFRM = 0x3, 31 | kRCFG = 0x4, 32 | kSTART = 0x5, 33 | kRCAP = 0x6, 34 | kRCRC = 0x7, 35 | kAGHIGH = 0x8, 36 | kSWITCH = 0x9, 37 | kGRESTORE = 0xA, 38 | kSHUTDOWN = 0xB, 39 | kGCAPTURE = 0xC, 40 | kDESYNC = 0xD, 41 | kIPROG = 0xF, 42 | kCRCC = 0x10, 43 | kLTIMER = 0x11, 44 | kBSPI_READ = 0x12, 45 | kFALL_EDGE = 0x13, 46 | }; 47 | 48 | // Series-7 configuration register addresses 49 | // according to UG470, pg. 109 50 | enum class ConfigurationRegister : unsigned int { 51 | kCRC = 0x00, 52 | kFAR = 0x01, 53 | kFDRI = 0x02, 54 | kFDRO = 0x03, 55 | kCMD = 0x04, 56 | kCTL0 = 0x05, 57 | kMASK = 0x06, 58 | kSTAT = 0x07, 59 | kLOUT = 0x08, 60 | kCOR0 = 0x09, 61 | kMFWR = 0x0a, 62 | kCBC = 0x0b, 63 | kIDCODE = 0x0c, 64 | kAXSS = 0x0d, 65 | kCOR1 = 0x0e, 66 | kWBSTAR = 0x10, 67 | kTIMER = 0x11, 68 | kUNKNOWN = 0x13, 69 | kBOOTSTS = 0x16, 70 | kCTL1 = 0x18, 71 | kBSPI = 0x1F, 72 | }; 73 | 74 | std::ostream &operator<<(std::ostream &o, const ConfigurationRegister &value); 75 | 76 | class ConfigurationPacket 77 | : public ConfigurationPacketBase { 79 | private: 80 | using BaseType = 81 | ConfigurationPacketBase; 82 | 83 | public: 84 | using BaseType::BaseType; 85 | using ConfRegType = ConfigurationRegister; 86 | 87 | private: 88 | friend BaseType; 89 | static ParseResult InitWithWordsImpl( 90 | absl::Span words, 91 | const ConfigurationPacket *previous_packet = nullptr); 92 | }; 93 | 94 | template 95 | using ConfigurationPackage = std::vector>; 96 | 97 | class ConfigurationOptions0Value { 98 | public: 99 | ConfigurationOptions0Value() = default; 100 | 101 | enum class StartupClockSource : uint32_t { 102 | CCLK = 0x0, 103 | User = 0x1, 104 | JTAG = 0x2, 105 | }; 106 | 107 | enum class SignalReleaseCycle : uint32_t { 108 | Phase1 = 0x0, 109 | Phase2 = 0x1, 110 | Phase3 = 0x2, 111 | Phase4 = 0x3, 112 | Phase5 = 0x4, 113 | Phase6 = 0x5, 114 | TrackDone = 0x6, 115 | Keep = 0x7, 116 | }; 117 | 118 | enum class StallCycle : uint32_t { 119 | Phase0 = 0x0, 120 | Phase1 = 0x1, 121 | Phase2 = 0x2, 122 | Phase3 = 0x3, 123 | Phase4 = 0x4, 124 | Phase5 = 0x5, 125 | Phase6 = 0x6, 126 | NoWait = 0x7, 127 | }; 128 | 129 | explicit operator uint32_t() const { return value_; } 130 | 131 | ConfigurationOptions0Value &SetUseDonePinAsPowerdownStatus(bool enabled) { 132 | value_ = bit_field_set(value_, 27, 27, enabled ? 1 : 0); 133 | return *this; 134 | } 135 | 136 | ConfigurationOptions0Value &SetAddPipelineStageForDoneIn(bool enabled) { 137 | value_ = bit_field_set(value_, 25, 25, enabled ? 1 : 0); 138 | return *this; 139 | } 140 | 141 | ConfigurationOptions0Value &SetDriveDoneHigh(bool enabled) { 142 | value_ = bit_field_set(value_, 24, 24, enabled); 143 | return *this; 144 | } 145 | 146 | ConfigurationOptions0Value &SetReadbackIsSingleShot(bool enabled) { 147 | value_ = bit_field_set(value_, 23, 23, enabled); 148 | return *this; 149 | } 150 | 151 | ConfigurationOptions0Value &SetCclkFrequency(uint32_t mhz) { 152 | value_ = bit_field_set(value_, 22, 17, mhz); 153 | return *this; 154 | } 155 | 156 | ConfigurationOptions0Value &SetStartupClockSource(StartupClockSource source) { 157 | value_ = bit_field_set(value_, 16, 15, static_cast(source)); 158 | return *this; 159 | } 160 | 161 | ConfigurationOptions0Value &SetReleaseDonePinAtStartupCycle( 162 | SignalReleaseCycle cycle) { 163 | value_ = bit_field_set(value_, 14, 12, static_cast(cycle)); 164 | return *this; 165 | } 166 | 167 | ConfigurationOptions0Value &SetStallAtStartupCycleUntilDciMatch( 168 | StallCycle cycle) { 169 | value_ = bit_field_set(value_, 11, 9, static_cast(cycle)); 170 | return *this; 171 | }; 172 | 173 | ConfigurationOptions0Value &SetStallAtStartupCycleUntilMmcmLock( 174 | StallCycle cycle) { 175 | value_ = bit_field_set(value_, 8, 6, static_cast(cycle)); 176 | return *this; 177 | }; 178 | 179 | ConfigurationOptions0Value &SetReleaseGtsSignalAtStartupCycle( 180 | SignalReleaseCycle cycle) { 181 | value_ = bit_field_set(value_, 5, 3, static_cast(cycle)); 182 | return *this; 183 | } 184 | 185 | ConfigurationOptions0Value &SetReleaseGweSignalAtStartupCycle( 186 | SignalReleaseCycle cycle) { 187 | value_ = bit_field_set(value_, 2, 0, static_cast(cycle)); 188 | return *this; 189 | } 190 | 191 | private: 192 | uint32_t value_; 193 | }; 194 | } // namespace xc7 195 | } // namespace xilinx 196 | } // namespace fpga 197 | #endif // FPGA_XILINX_ARCH_XC7_CONFIGURATION_PACKET_H 198 | -------------------------------------------------------------------------------- /fpga/xilinx/arch-xc7-configuration-packet_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #include "fpga/xilinx/arch-xc7-configuration-packet.h" 11 | 12 | #include 13 | 14 | #include "absl/types/span.h" 15 | #include "fpga/xilinx/arch-xc7-frame.h" 16 | #include "fpga/xilinx/bit-ops.h" 17 | #include "gtest/gtest.h" 18 | 19 | namespace fpga { 20 | namespace xilinx { 21 | namespace xc7 { 22 | constexpr FrameWord kType1NOP = bit_field_set(0, 31, 29, 0x1); 23 | 24 | constexpr FrameWord MakeType1(const int opcode, const int address, 25 | const int word_count) { 26 | return bit_field_set( 27 | bit_field_set( 28 | bit_field_set(bit_field_set(0x0, 31, 29, 0x1), 28, 29 | 27, opcode), 30 | 26, 13, address), 31 | 10, 0, word_count); 32 | } 33 | 34 | constexpr FrameWord MakeType2(const int opcode, const int word_count) { 35 | return bit_field_set( 36 | bit_field_set(bit_field_set(0x0, 31, 29, 0x2), 28, 27, 37 | opcode), 38 | 26, 0, word_count); 39 | } 40 | 41 | TEST(ConfigPacket, InitWithZeroBytes) { 42 | auto packet = ConfigurationPacket::InitWithWords({}); 43 | 44 | EXPECT_EQ(packet.first, absl::Span()); 45 | EXPECT_FALSE(packet.second); 46 | } 47 | 48 | TEST(ConfigPacket, InitWithType1Nop) { 49 | std::vector words{kType1NOP}; 50 | const absl::Span word_span(words); 51 | auto packet = ConfigurationPacket::InitWithWords(word_span); 52 | EXPECT_EQ(packet.first, absl::Span()); 53 | ASSERT_TRUE(packet.second); 54 | // NOLINTBEGIN(bugprone-unchecked-optional-access) 55 | EXPECT_EQ(packet.second->opcode(), ConfigurationPacket::Opcode::kNOP); 56 | EXPECT_EQ(packet.second->address(), ConfigurationRegister::kCRC); 57 | EXPECT_EQ(packet.second->data(), absl::Span()); 58 | // NOLINTEND(bugprone-unchecked-optional-access) 59 | } 60 | 61 | TEST(ConfigPacket, InitWithType1Read) { 62 | std::vector words{MakeType1(0x1, 0x2, 2), 0xAA, 0xBB}; 63 | const absl::Span word_span(words); 64 | auto packet = ConfigurationPacket::InitWithWords(word_span); 65 | EXPECT_EQ(packet.first, absl::Span()); 66 | ASSERT_TRUE(packet.second); 67 | // NOLINTBEGIN(bugprone-unchecked-optional-access) 68 | EXPECT_EQ(packet.second->opcode(), ConfigurationPacket::Opcode::kRead); 69 | EXPECT_EQ(packet.second->address(), ConfigurationRegister::kFDRI); 70 | EXPECT_EQ(packet.second->data(), word_span.subspan(1)); 71 | // NOLINTEND(bugprone-unchecked-optional-access) 72 | } 73 | 74 | TEST(ConfigPacket, InitWithType1Write) { 75 | std::vector words{MakeType1(0x2, 0x3, 2), 0xAA, 0xBB}; 76 | const absl::Span word_span(words); 77 | auto packet = ConfigurationPacket::InitWithWords(word_span); 78 | EXPECT_EQ(packet.first, absl::Span()); 79 | ASSERT_TRUE(packet.second); 80 | // NOLINTBEGIN(bugprone-unchecked-optional-access) 81 | EXPECT_EQ(packet.second->opcode(), ConfigurationPacket::Opcode::kWrite); 82 | EXPECT_EQ(packet.second->address(), ConfigurationRegister::kFDRO); 83 | EXPECT_EQ(packet.second->data(), word_span.subspan(1)); 84 | // NOLINTEND(bugprone-unchecked-optional-access) 85 | } 86 | 87 | TEST(ConfigPacket, InitWithType2WithoutPreviousPacketFails) { 88 | std::vector words{MakeType2(0x01, 12)}; 89 | const absl::Span word_span(words); 90 | auto packet = ConfigurationPacket::InitWithWords(word_span); 91 | EXPECT_EQ(packet.first, words); 92 | EXPECT_FALSE(packet.second); 93 | } 94 | 95 | TEST(ConfigPacket, InitWithType2WithPreviousPacket) { 96 | const ConfigurationPacket previous_packet( 97 | static_cast(0x1), ConfigurationPacket::Opcode::kRead, 98 | ConfigurationRegister::kMFWR, absl::Span()); 99 | std::vector words{ 100 | MakeType2(0x01, 12), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; 101 | const absl::Span word_span(words); 102 | auto packet = ConfigurationPacket::InitWithWords(word_span, &previous_packet); 103 | EXPECT_EQ(packet.first, absl::Span()); 104 | ASSERT_TRUE(packet.second); 105 | // NOLINTBEGIN(bugprone-unchecked-optional-access) 106 | EXPECT_EQ(packet.second->opcode(), ConfigurationPacket::Opcode::kRead); 107 | EXPECT_EQ(packet.second->address(), ConfigurationRegister::kMFWR); 108 | EXPECT_EQ(packet.second->data(), word_span.subspan(1)); 109 | // NOLINTEND(bugprone-unchecked-optional-access) 110 | } 111 | } // namespace xc7 112 | } // namespace xilinx 113 | } // namespace fpga 114 | -------------------------------------------------------------------------------- /fpga/xilinx/arch-xc7-frame.cc: -------------------------------------------------------------------------------- 1 | #include "fpga/xilinx/arch-xc7-frame.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace fpga { 9 | namespace xilinx { 10 | namespace xc7 { 11 | std::ostream &operator<<(std::ostream &o, BlockType value) { 12 | switch (value) { 13 | case BlockType::kCLBIOCLK: o << "CLB/IO/CLK"; break; 14 | case BlockType::kBlockRam: o << "Block RAM"; break; 15 | case BlockType::kCFGCLB: o << "Config CLB"; break; 16 | case BlockType::kReserved: o << "Reserved"; break; 17 | case BlockType::kInvalid: o << "Invalid"; break; 18 | } 19 | return o; 20 | } 21 | 22 | std::ostream &operator<<(std::ostream &o, const FrameAddress &addr) { 23 | o << "[" << std::hex << std::showbase << std::setw(10) 24 | << static_cast(addr) << "] " 25 | << (addr.is_bottom_half_rows() ? "BOTTOM" : "TOP") 26 | << " Row=" << std::setw(2) << std::dec 27 | << static_cast(addr.row()) << " Column=" << std::setw(2) 28 | << std::dec << addr.column() << " Minor=" << std::setw(2) << std::dec 29 | << static_cast(addr.minor()) << " Type=" << addr.block_type(); 30 | return o; 31 | } 32 | namespace internal { 33 | uint32_t ICAPECC(uint32_t idx, uint32_t word, uint32_t ecc) { 34 | uint32_t val = idx * 32; // bit offset 35 | if (idx > 0x25) { // avoid 0x800 36 | val += 0x1360; 37 | } else if (idx > 0x6) { // avoid 0x400 38 | val += 0x1340; 39 | } else { // avoid lower 40 | val += 0x1320; 41 | } 42 | 43 | if (idx == 0x32) { // mask ECC 44 | word &= 0xFFFFE000; 45 | } 46 | 47 | for (int i = 0; i < 32; i++) { 48 | if (word & 1) { 49 | ecc ^= val + i; 50 | } 51 | 52 | word >>= 1; 53 | } 54 | if (idx == 0x64) { // last index 55 | uint32_t v = ecc & 0xFFF; 56 | v ^= v >> 8; 57 | v ^= v >> 4; 58 | v ^= v >> 2; 59 | v ^= v >> 1; 60 | ecc ^= (v & 1) << 12; // parity 61 | } 62 | return ecc; 63 | } 64 | } // namespace internal 65 | } // namespace xc7 66 | } // namespace xilinx 67 | } // namespace fpga 68 | -------------------------------------------------------------------------------- /fpga/xilinx/arch-xc7-frame.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #ifndef FPGA_XILINX_ARCH_XC7_FRAME_H 11 | #define FPGA_XILINX_ARCH_XC7_FRAME_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "fpga/xilinx/bit-ops.h" 19 | 20 | namespace fpga { 21 | namespace xilinx { 22 | namespace xc7 { 23 | enum class Architecture { 24 | kBase, 25 | kUltraScale, 26 | kUltraScalePlus, 27 | }; 28 | 29 | template 30 | struct frame_words_count; 31 | 32 | template <> 33 | struct frame_words_count { 34 | static constexpr int value = 101; 35 | }; 36 | 37 | template <> 38 | struct frame_words_count { 39 | static constexpr int value = 123; 40 | }; 41 | 42 | template <> 43 | struct frame_words_count { 44 | static constexpr int value = 93; 45 | }; 46 | 47 | template 48 | constexpr int frame_words_count_v = frame_words_count::value; 49 | 50 | enum class BlockType : unsigned int { 51 | kCLBIOCLK = 0x0, 52 | kBlockRam = 0x1, 53 | kCFGCLB = 0x2, 54 | kReserved = 0x3, 55 | kInvalid = 0xFFFFFFFF, 56 | }; 57 | 58 | std::ostream &operator<<(std::ostream &o, BlockType value); 59 | 60 | using FrameWord = uint32_t; 61 | 62 | class FrameAddress { 63 | public: 64 | FrameAddress(BlockType block_type, bool is_bottom_half_rows, uint8_t row, 65 | uint16_t column, uint8_t minor) { 66 | value_ = bit_field_set(0, 25, 23, block_type); 67 | value_ = bit_field_set(value_, 22, 22, is_bottom_half_rows); 68 | value_ = bit_field_set(value_, 21, 17, row); 69 | value_ = bit_field_set(value_, 16, 7, column); 70 | value_ = bit_field_set(value_, 6, 0, minor); 71 | } 72 | explicit FrameAddress(uint32_t value) : value_(value){}; 73 | auto operator<=>(const FrameAddress &o) const = default; 74 | 75 | BlockType block_type() const { 76 | return static_cast(bit_field_get(value_, 25, 23)); 77 | } 78 | bool is_bottom_half_rows() const { return bit_field_get(value_, 22, 22); } 79 | uint8_t row() const { return bit_field_get(value_, 21, 17); } 80 | uint16_t column() const { return bit_field_get(value_, 16, 7); } 81 | uint8_t minor() const { return bit_field_get(value_, 6, 0); } 82 | explicit operator uint32_t() const { return value_; } 83 | 84 | private: 85 | uint32_t value_; 86 | }; 87 | static_assert(sizeof(FrameAddress) == sizeof(uint32_t)); 88 | 89 | std::ostream &operator<<(std::ostream &o, const FrameAddress &addr); 90 | 91 | template 92 | using FrameWords = std::array>; 93 | 94 | namespace internal { 95 | // Extend the current ECC code with one data word (32 bit) at a given 96 | // word index in the configuration frame and return the new ECC code. 97 | uint32_t ICAPECC(uint32_t idx, uint32_t word, uint32_t ecc); 98 | 99 | inline constexpr size_t kECCFrameNumber = 0x32; 100 | inline constexpr uint32_t kCrc32CastagnoliPolynomial = 0x82F63B78; 101 | 102 | // The CRC is calculated from each written data word and the current 103 | // register address the data is written to. 104 | 105 | // Extend the current CRC value with one register address (5bit) and 106 | // frame data (32bit) pair and return the newly computed CRC value. 107 | inline uint32_t ICAPCRC(uint32_t addr, uint32_t data, uint32_t prev) { 108 | constexpr int kAddressBitWidth = 5; 109 | constexpr int kDataBitWidth = 32; 110 | uint64_t const poly = static_cast(kCrc32CastagnoliPolynomial) << 1; 111 | uint64_t val = (static_cast(addr) << 32) | data; 112 | uint64_t crc = prev; 113 | 114 | for (int i = 0; i < kAddressBitWidth + kDataBitWidth; i++) { 115 | if ((val & 1) != (crc & 1)) { 116 | crc ^= poly; 117 | } 118 | 119 | val >>= 1; 120 | crc >>= 1; 121 | } 122 | return crc; 123 | } 124 | 125 | template 126 | uint32_t CalculateECC(const FrameWords &data) { 127 | FrameWord ecc = 0; 128 | for (size_t i = 0; i < data.size(); ++i) { 129 | ecc = ICAPECC(i, data[i], ecc); 130 | } 131 | return ecc; 132 | } 133 | } // namespace internal 134 | 135 | // Updates the ECC information in the frame. 136 | template 137 | void UpdateECC(FrameWords &words) { 138 | CHECK(words.size() >= internal::kECCFrameNumber); 139 | // Replace the old ECC with the new. 140 | words[internal::kECCFrameNumber] &= 0xFFFFE000; 141 | words[internal::kECCFrameNumber] |= (internal::CalculateECC(words) & 0x1FFF); 142 | } 143 | } // namespace xc7 144 | } // namespace xilinx 145 | } // namespace fpga 146 | #endif // FPGA_XILINX_ARCH_XC7_FRAME_H 147 | -------------------------------------------------------------------------------- /fpga/xilinx/arch-xc7-frame_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #include "fpga/xilinx/arch-xc7-frame.h" 11 | 12 | #include 13 | 14 | #include "gtest/gtest.h" 15 | 16 | namespace fpga { 17 | namespace xilinx { 18 | namespace xc7 { 19 | namespace { 20 | TEST(IcapCrcTest, SimpleTests) { 21 | // CRC for Zero Data 22 | EXPECT_EQ(internal::ICAPCRC(0, 0, 0), 0x0L); 23 | // Polynomial (single bit operation) 24 | EXPECT_EQ(internal::ICAPCRC(1 << 4, 0, 0), 0x82F63B78); 25 | // All Reg/Data bits 26 | EXPECT_EQ(internal::ICAPCRC(~0, ~0, 0), 0xBF86D4DF); 27 | // All CRC bits 28 | EXPECT_EQ(internal::ICAPCRC(0, 0, ~0), 0xC631E365); 29 | } 30 | 31 | TEST(IcapEccTest, SimpleTests) { 32 | // ECC for Zero Data 33 | EXPECT_EQ(internal::ICAPECC(0, 0, 0), (uint32_t)0x0); 34 | // 0x1320 - 0x13FF (avoid lower) 35 | EXPECT_EQ(internal::ICAPECC(0, 1, 0), (uint32_t)0x1320); 36 | // 0x1420 - 0x17FF (avoid 0x400) 37 | EXPECT_EQ(internal::ICAPECC(0x7, 1, 0), (uint32_t)0x1420); 38 | // 0x1820 - 0x1FFF (avoid 0x800) 39 | EXPECT_EQ(internal::ICAPECC(0x26, 1, 0), (uint32_t)0x1820); 40 | // Masked ECC Value 41 | EXPECT_EQ(internal::ICAPECC(0x32, ~0, 0), (uint32_t)0x000019AC); 42 | // Final ECC Parity 43 | EXPECT_EQ(internal::ICAPECC(0x64, 0, 1), (uint32_t)0x00001001); 44 | } 45 | } // namespace 46 | } // namespace xc7 47 | } // namespace xilinx 48 | } // namespace fpga 49 | -------------------------------------------------------------------------------- /fpga/xilinx/arch-xc7-part.cc: -------------------------------------------------------------------------------- 1 | #include "fpga/xilinx/arch-xc7-part.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "absl/container/btree_map.h" 9 | #include "absl/log/check.h" 10 | #include "fpga/database-parsers.h" 11 | #include "fpga/xilinx/arch-xc7-frame.h" 12 | 13 | namespace fpga { 14 | namespace xilinx { 15 | namespace xc7 { 16 | namespace { 17 | static BlockType BlockTypeFrom(const fpga::ConfigBusType type) { 18 | switch (type) { 19 | case ConfigBusType::kCLBIOCLK: return BlockType::kCLBIOCLK; 20 | case ConfigBusType::kBlockRam: return BlockType::kBlockRam; 21 | case ConfigBusType::kCFGCLB: return BlockType::kCLBIOCLK; 22 | default: break; 23 | } 24 | return BlockType::kInvalid; 25 | } 26 | 27 | absl::StatusOr RowFrom(const fpga::ClockRegionRow &clock_region_row) { 28 | absl::btree_map row; 29 | for (const auto &bus_column_pair : clock_region_row) { 30 | const std::vector &config_column = bus_column_pair.second; 31 | if (config_column.empty()) { 32 | continue; 33 | } 34 | const BlockType block_type = BlockTypeFrom(bus_column_pair.first); 35 | absl::btree_map configuration_bus; 36 | for (size_t i = 0; i < config_column.size(); ++i) { 37 | configuration_bus.emplace(i, config_column[i]); 38 | } 39 | row.emplace(block_type, ConfigurationBus(configuration_bus)); 40 | } 41 | return Row(row); 42 | } 43 | 44 | absl::StatusOr GlobalClockRegionFrom( 45 | const fpga::GlobalClockRegionHalf &half) { 46 | absl::btree_map rows; 47 | for (size_t i = 0; i < half.size(); ++i) { 48 | const absl::StatusOr row_status = RowFrom(half[i]); 49 | if (!row_status.ok()) { 50 | return row_status.status(); 51 | } 52 | rows.emplace(i, row_status.value()); 53 | } 54 | return GlobalClockRegion(rows); 55 | } 56 | } // namespace 57 | absl::StatusOr Part::FromPart(const fpga::Part &part) { 58 | absl::StatusOr bottom = 59 | GlobalClockRegionFrom(part.global_clock_regions.bottom_rows); 60 | if (!bottom.ok()) { 61 | return bottom.status(); 62 | } 63 | absl::StatusOr top = 64 | GlobalClockRegionFrom(part.global_clock_regions.top_rows); 65 | if (!top.ok()) { 66 | return top.status(); 67 | } 68 | return Part(part.idcode, top.value(), bottom.value()); 69 | } 70 | 71 | bool ConfigurationColumn::IsValidFrameAddress(FrameAddress address) const { 72 | return address.minor() < frame_count_; 73 | } 74 | 75 | std::optional ConfigurationColumn::GetNextFrameAddress( 76 | FrameAddress address) const { 77 | if (!IsValidFrameAddress(address)) { 78 | return {}; 79 | } 80 | 81 | if (static_cast(address.minor() + 1) < frame_count_) { 82 | return FrameAddress(static_cast(address) + 1); 83 | } 84 | 85 | // Next address is not in this column. 86 | return {}; 87 | } 88 | 89 | bool ConfigurationBus::IsValidFrameAddress(FrameAddress address) const { 90 | auto addr_column = configuration_columns_.find(address.column()); 91 | if (addr_column == configuration_columns_.end()) { 92 | return false; 93 | } 94 | 95 | return addr_column->second.IsValidFrameAddress(address); 96 | } 97 | 98 | std::optional ConfigurationBus::GetNextFrameAddress( 99 | FrameAddress address) const { 100 | // Find the column for the current address. 101 | auto addr_column = configuration_columns_.find(address.column()); 102 | 103 | // If the current address isn't in a known column, no way to know the 104 | // next address. 105 | if (addr_column == configuration_columns_.end()) { 106 | return {}; 107 | } 108 | 109 | // Ask the column for the next address. 110 | std::optional next_address = 111 | addr_column->second.GetNextFrameAddress(address); 112 | if (next_address) { 113 | return next_address; 114 | } 115 | 116 | // The current column doesn't know what the next address is. Assume 117 | // that the next valid address is the beginning of the next column. 118 | if (++addr_column != configuration_columns_.end()) { 119 | auto next_address = 120 | FrameAddress(address.block_type(), address.is_bottom_half_rows(), 121 | address.row(), addr_column->first, 0); 122 | if (addr_column->second.IsValidFrameAddress(next_address)) { 123 | return next_address; 124 | } 125 | } 126 | 127 | // Not in this bus. 128 | return {}; 129 | } 130 | 131 | bool Row::IsValidFrameAddress(FrameAddress address) const { 132 | auto addr_bus = configuration_buses_.find(address.block_type()); 133 | if (addr_bus == configuration_buses_.end()) { 134 | return false; 135 | } 136 | return addr_bus->second.IsValidFrameAddress(address); 137 | } 138 | 139 | std::optional Row::GetNextFrameAddress( 140 | FrameAddress address) const { 141 | // Find the bus for the current address. 142 | auto addr_bus = configuration_buses_.find(address.block_type()); 143 | 144 | // If the current address isn't in a known bus, no way to know the next. 145 | if (addr_bus == configuration_buses_.end()) { 146 | return {}; 147 | } 148 | 149 | // Ask the bus for the next address. 150 | std::optional next_address = 151 | addr_bus->second.GetNextFrameAddress(address); 152 | if (next_address) { 153 | return next_address; 154 | } 155 | 156 | // The current bus doesn't know what the next address is. Rows come next 157 | // in frame address numerical order so punt back to the caller to figure 158 | // it out. 159 | return {}; 160 | } 161 | 162 | bool GlobalClockRegion::IsValidFrameAddress(FrameAddress address) const { 163 | auto addr_row = rows_.find(address.row()); 164 | if (addr_row == rows_.end()) { 165 | return false; 166 | } 167 | return addr_row->second.IsValidFrameAddress(address); 168 | } 169 | 170 | std::optional GlobalClockRegion::GetNextFrameAddress( 171 | FrameAddress address) const { 172 | // Find the row for the current address. 173 | auto addr_row = rows_.find(address.row()); 174 | 175 | // If the current address isn't in a known row, no way to know the next. 176 | if (addr_row == rows_.end()) { 177 | return {}; 178 | } 179 | 180 | // Ask the row for the next address. 181 | std::optional next_address = 182 | addr_row->second.GetNextFrameAddress(address); 183 | if (next_address) { 184 | return next_address; 185 | } 186 | 187 | // The current row doesn't know what the next address is. Assume that 188 | // the next valid address is the beginning of the next row. 189 | if (++addr_row != rows_.end()) { 190 | auto next_address = 191 | FrameAddress(address.block_type(), address.is_bottom_half_rows(), 192 | addr_row->first, 0, 0); 193 | if (addr_row->second.IsValidFrameAddress(next_address)) { 194 | return next_address; 195 | } 196 | } 197 | 198 | // Must be in a different global clock region. 199 | return {}; 200 | } 201 | 202 | bool Part::IsValidFrameAddress(FrameAddress address) const { 203 | if (address.is_bottom_half_rows()) { 204 | return bottom_region_.IsValidFrameAddress(address); 205 | } 206 | return top_region_.IsValidFrameAddress(address); 207 | } 208 | 209 | std::optional Part::GetNextFrameAddress( 210 | FrameAddress address) const { 211 | // Ask the current global clock region first. 212 | std::optional next_address = 213 | (address.is_bottom_half_rows() ? bottom_region_.GetNextFrameAddress(address) 214 | : top_region_.GetNextFrameAddress(address)); 215 | if (next_address) { 216 | return next_address; 217 | } 218 | 219 | // If the current address is in the top region, the bottom region is 220 | // next numerically. 221 | if (!address.is_bottom_half_rows()) { 222 | next_address = FrameAddress(address.block_type(), true, 0, 0, 0); 223 | if (bottom_region_.IsValidFrameAddress(*next_address)) { 224 | return next_address; 225 | } 226 | } 227 | 228 | // Block types are next numerically. 229 | if (address.block_type() < BlockType::kBlockRam) { 230 | next_address = FrameAddress(BlockType::kBlockRam, false, 0, 0, 0); 231 | if (IsValidFrameAddress(*next_address)) { 232 | return next_address; 233 | } 234 | } 235 | 236 | if (address.block_type() < BlockType::kCFGCLB) { 237 | next_address = FrameAddress(BlockType::kCFGCLB, false, 0, 0, 0); 238 | if (IsValidFrameAddress(*next_address)) { 239 | return next_address; 240 | } 241 | } 242 | return {}; 243 | } 244 | } // namespace xc7 245 | } // namespace xilinx 246 | } // namespace fpga 247 | -------------------------------------------------------------------------------- /fpga/xilinx/arch-xc7-part.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #ifndef FPGA_XILINX_ARCH_XC7_PART_H 11 | #define FPGA_XILINX_ARCH_XC7_PART_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "absl/container/btree_map.h" 21 | #include "absl/status/statusor.h" 22 | #include "fpga/database-parsers.h" 23 | #include "fpga/xilinx/arch-xc7-frame.h" 24 | 25 | namespace fpga { 26 | namespace xilinx { 27 | namespace xc7 { 28 | // ConfigurationColumn represents an endpoint on a ConfigurationBus. 29 | class ConfigurationColumn { 30 | public: 31 | ConfigurationColumn() = default; 32 | explicit ConfigurationColumn(unsigned int frame_count) 33 | : frame_count_(frame_count) {} 34 | 35 | // Returns a ConfigurationColumn that describes a continguous range of 36 | // minor addresses that encompasses the given 37 | // frame addresses. The provided addresses must only 38 | // differ only by their minor addresses. 39 | template 40 | ConfigurationColumn(T first, T last); 41 | 42 | // Returns true if the minor field of the address is within the valid 43 | // range of this column. 44 | bool IsValidFrameAddress(FrameAddress address) const; 45 | 46 | // Returns the next address in numerical order. If the next address 47 | // would be outside this column, return no object. 48 | std::optional GetNextFrameAddress(FrameAddress address) const; 49 | 50 | private: 51 | unsigned int frame_count_; 52 | }; 53 | 54 | template 55 | ConfigurationColumn::ConfigurationColumn(T first, T last) { 56 | assert(std::all_of(first, last, [&](const typename T::value_type &addr) { 57 | return (addr.block_type() == first->block_type() && 58 | addr.is_bottom_half_rows() == first->is_bottom_half_rows() && 59 | addr.row() == first->row() && addr.column() == first->column()); 60 | })); 61 | 62 | auto max_minor = std::max_element( 63 | first, last, [](const FrameAddress &lhs, const FrameAddress &rhs) { 64 | return lhs.minor() < rhs.minor(); 65 | }); 66 | 67 | frame_count_ = max_minor->minor() + 1; 68 | } 69 | 70 | // ConfigurationBus represents a bus for sending frames to a specific BlockType 71 | // within a Row. An instance of ConfigurationBus will contain one or more 72 | // ConfigurationColumns. 73 | class ConfigurationBus { 74 | private: 75 | using container_type = absl::btree_map; 76 | 77 | public: 78 | ConfigurationBus() = default; 79 | 80 | explicit ConfigurationBus(container_type configuration_columns) 81 | : configuration_columns_(std::move(configuration_columns)) {} 82 | // Constructs a ConfigurationBus from iterators yielding 83 | // frame addresses. The frame address need not be contiguous or sorted 84 | // but they must all have the same block type, row half, and row 85 | // address components. 86 | template 87 | ConfigurationBus(T first, T last); 88 | 89 | // Returns true if the provided address falls into a valid segment of 90 | // the address range on this bus. Only the column and minor components 91 | // of the address are considered as all other components are outside 92 | // the scope of a bus. 93 | bool IsValidFrameAddress(FrameAddress address) const; 94 | 95 | // Returns the next valid address on the bus in numerically increasing 96 | // order. If the next address would fall outside this bus, no object is 97 | // returned. 98 | std::optional GetNextFrameAddress(FrameAddress address) const; 99 | 100 | private: 101 | container_type configuration_columns_; 102 | }; 103 | 104 | template 105 | ConfigurationBus::ConfigurationBus(T first, T last) { 106 | assert(std::all_of(first, last, [&](const typename T::value_type &addr) { 107 | return (addr.block_type() == first->block_type() && 108 | addr.is_bottom_half_rows() == first->is_bottom_half_rows() && 109 | addr.row() == first->row()); 110 | })); 111 | 112 | std::sort(first, last, [](const FrameAddress &lhs, const FrameAddress &rhs) { 113 | return lhs.column() < rhs.column(); 114 | }); 115 | 116 | for (auto col_first = first; col_first != last;) { 117 | auto col_last = 118 | std::upper_bound(col_first, last, col_first->column(), 119 | [](const unsigned int &lhs, const FrameAddress &rhs) { 120 | return lhs < rhs.column(); 121 | }); 122 | 123 | configuration_columns_.emplace(col_first->column(), 124 | ConfigurationColumn(col_first, col_last)); 125 | col_first = col_last; 126 | } 127 | } 128 | 129 | class Row { 130 | private: 131 | using container_type = absl::btree_map; 132 | 133 | public: 134 | Row() = default; 135 | 136 | // Construct a row from a range of iterators that yield frame addresses. 137 | // The addresses may be noncontinguous and/or unsorted but all must 138 | // share the same row half and row components. 139 | template 140 | Row(T first, T last); 141 | 142 | explicit Row(container_type configuration_buses) 143 | : configuration_buses_(std::move(configuration_buses)) {} 144 | 145 | // Returns true if the provided address falls within a valid range 146 | // attributed to this row. Only the block type, column, and minor 147 | // address components are considerd as the remaining components are 148 | // outside the scope of a row. 149 | bool IsValidFrameAddress(FrameAddress address) const; 150 | 151 | // Returns the next numerically increasing address within the Row. If 152 | // the next address would fall outside the Row, no object is returned. 153 | // If the next address would cross from one block type to another, no 154 | // object is returned as other rows of the same block type come before 155 | // other block types numerically. 156 | std::optional GetNextFrameAddress(FrameAddress address) const; 157 | 158 | private: 159 | container_type configuration_buses_; 160 | }; 161 | 162 | template 163 | Row::Row(T first, T last) { 164 | assert(std::all_of(first, last, [&](const typename T::value_type &addr) { 165 | return (addr.is_bottom_half_rows() == first->is_bottom_half_rows() && 166 | addr.row() == first->row()); 167 | })); 168 | 169 | std::sort(first, last, [](const FrameAddress &lhs, const FrameAddress &rhs) { 170 | return lhs.block_type() < rhs.block_type(); 171 | }); 172 | 173 | for (auto bus_first = first; bus_first != last;) { 174 | auto bus_last = 175 | std::upper_bound(bus_first, last, bus_first->block_type(), 176 | [](const BlockType &lhs, const FrameAddress &rhs) { 177 | return lhs < rhs.block_type(); 178 | }); 179 | 180 | configuration_buses_.emplace( 181 | bus_first->block_type(), 182 | std::move(ConfigurationBus(bus_first, bus_last))); 183 | bus_first = bus_last; 184 | } 185 | } 186 | 187 | // GlobalClockRegion represents all the resources associated with a single 188 | // global clock buffer (BUFG) tile. In 7-Series FPGAs, there are two BUFG 189 | // tiles that divide the chip into top and bottom "halves". Each half may 190 | // contains any number of rows, buses, and columns. 191 | class GlobalClockRegion { 192 | private: 193 | using container_type = absl::btree_map; 194 | 195 | public: 196 | GlobalClockRegion() = default; 197 | 198 | explicit GlobalClockRegion(container_type rows) : rows_(std::move(rows)) {} 199 | 200 | // Construct a GlobalClockRegion from iterators that yield 201 | // frame addresses which are known to be valid. The addresses may be 202 | // noncontinguous and/or unordered but they must share the same row 203 | // half address component. 204 | template 205 | GlobalClockRegion(T first, T last); 206 | 207 | GlobalClockRegion(container_type::const_iterator first, 208 | container_type::const_iterator last) 209 | : rows_(first, last) {} 210 | 211 | // Returns true if the address falls within a valid range inside the 212 | // global clock region. The row half address component is ignored as it 213 | // is outside the context of a global clock region. 214 | bool IsValidFrameAddress(FrameAddress address) const; 215 | 216 | // Returns the next numerically increasing address known within this 217 | // global clock region. If the next address would fall outside this 218 | // global clock region, no address is returned. If the next address 219 | // would jump to a different block type, no address is returned as the 220 | // same block type in other global clock regions come numerically 221 | // before other block types. 222 | std::optional GetNextFrameAddress(FrameAddress address) const; 223 | 224 | private: 225 | container_type rows_; 226 | }; 227 | 228 | template 229 | GlobalClockRegion::GlobalClockRegion(T first, T last) { 230 | assert(std::all_of(first, last, [&](const typename T::value_type &addr) { 231 | return addr.is_bottom_half_rows() == first->is_bottom_half_rows(); 232 | })); 233 | 234 | std::sort(first, last, [](const FrameAddress &lhs, const FrameAddress &rhs) { 235 | return lhs.row() < rhs.row(); 236 | }); 237 | 238 | for (auto row_first = first; row_first != last;) { 239 | auto row_last = 240 | std::upper_bound(row_first, last, row_first->row(), 241 | [](const uint8_t &lhs, const FrameAddress &rhs) { 242 | return lhs < rhs.row(); 243 | }); 244 | 245 | rows_.emplace(row_first->row(), std::move(Row(row_first, row_last))); 246 | row_first = row_last; 247 | } 248 | } 249 | 250 | class Part { 251 | public: 252 | constexpr static uint32_t kInvalidIdcode = 0; 253 | 254 | static absl::StatusOr FromPart(const fpga::Part &part); 255 | 256 | // Constructs an invalid part with a zero IDCODE. Required for YAML 257 | // conversion but shouldn't be used otherwise. 258 | Part() : idcode_(kInvalidIdcode) {} 259 | 260 | template 261 | Part(uint32_t idcode, T collection) 262 | : Part(idcode, std::begin(collection), std::end(collection)) {} 263 | 264 | template 265 | Part(uint32_t idcode, T first, T last); 266 | 267 | Part(uint32_t idcode, GlobalClockRegion top, GlobalClockRegion bottom) 268 | : idcode_(idcode), 269 | top_region_(std::move(top)), 270 | bottom_region_(std::move(bottom)) {} 271 | 272 | uint32_t idcode() const { return idcode_; } 273 | 274 | bool IsValidFrameAddress(FrameAddress address) const; 275 | 276 | std::optional GetNextFrameAddress(FrameAddress address) const; 277 | 278 | private: 279 | uint32_t idcode_; 280 | GlobalClockRegion top_region_; 281 | GlobalClockRegion bottom_region_; 282 | }; 283 | 284 | template 285 | Part::Part(uint32_t idcode, T first, T last) : idcode_(idcode) { 286 | auto first_of_top = std::partition(first, last, [](const FrameAddress &addr) { 287 | return addr.is_bottom_half_rows(); 288 | }); 289 | top_region_ = GlobalClockRegion(first_of_top, last); 290 | bottom_region_ = GlobalClockRegion(first, first_of_top); 291 | } 292 | } // namespace xc7 293 | } // namespace xilinx 294 | } // namespace fpga 295 | #endif // FPGA_XILINX_ARCH_XC7_PART_H 296 | -------------------------------------------------------------------------------- /fpga/xilinx/big-endian-span.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #ifndef FPGA_XILINX_BIG_ENDIAN_SPAN 11 | #define FPGA_XILINX_BIG_ENDIAN_SPAN 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "absl/types/span.h" 19 | 20 | namespace fpga { 21 | namespace xilinx { 22 | template 23 | class BigEndianSpan { 24 | public: 25 | constexpr static size_t kBytesPerElement = sizeof(WordType); 26 | using byte_type = ByteType; 27 | using word_type = WordType; 28 | using size_type = std::size_t; 29 | class value_type { 30 | public: 31 | explicit operator WordType() const { 32 | WordType word = 0; 33 | for (size_t ii = 0; ii < kBytesPerElement; ++ii) { 34 | word |= (static_cast(bytes_[ii]) 35 | << ((kBytesPerElement - 1 - ii) * 8)); 36 | } 37 | return word; 38 | } 39 | 40 | value_type &operator=(WordType word) { 41 | for (size_t ii = 0; ii < kBytesPerElement; ++ii) { 42 | bytes_[ii] = ((word >> ((kBytesPerElement - 1 - ii) * 8)) & 0xFF); 43 | } 44 | return *this; 45 | } 46 | 47 | protected: 48 | friend class BigEndianSpan; 49 | 50 | explicit value_type(absl::Span bytes) : bytes_(bytes){}; 51 | 52 | private: 53 | absl::Span bytes_; 54 | }; 55 | 56 | class iterator { 57 | public: 58 | using iterator_category = std::input_iterator_tag; 59 | using value_type = BigEndianSpan::value_type; 60 | using difference_type = std::ptrdiff_t; 61 | using pointer = value_type *; 62 | using reference = value_type &; 63 | value_type operator*() const { return value_type(bytes_); } 64 | 65 | bool operator==(const iterator &other) const { 66 | return bytes_ == other.bytes_; 67 | } 68 | 69 | bool operator!=(const iterator &other) const { 70 | return bytes_ != other.bytes_; 71 | } 72 | 73 | iterator &operator++() { 74 | bytes_ = bytes_.subspan(kBytesPerElement); 75 | return *this; 76 | } 77 | 78 | protected: 79 | friend class BigEndianSpan; 80 | 81 | explicit iterator(absl::Span bytes) : bytes_(bytes){}; 82 | 83 | private: 84 | absl::Span bytes_; 85 | }; 86 | 87 | using pointer = value_type *; 88 | using reference = value_type &; 89 | 90 | explicit BigEndianSpan(absl::Span bytes) : bytes_(bytes){}; 91 | 92 | constexpr size_type size() const noexcept { 93 | return bytes_.size() / kBytesPerElement; 94 | }; 95 | 96 | constexpr size_type length() const noexcept { return size(); } 97 | 98 | constexpr bool empty() const noexcept { return size() == 0; } 99 | 100 | value_type operator[](size_type pos) const { 101 | assert(pos < size()); 102 | return value_type(bytes_.subspan((pos * kBytesPerElement))); 103 | } 104 | 105 | constexpr reference at(size_type pos) const { return this->operator[](pos); } 106 | 107 | iterator begin() const { return iterator(bytes_); } 108 | iterator end() const { return iterator({}); } 109 | 110 | private: 111 | absl::Span bytes_; 112 | }; 113 | 114 | template 115 | BigEndianSpan make_big_endian_span( 116 | Container &bytes) { 117 | return BigEndianSpan( 118 | absl::Span(bytes)); 119 | } 120 | 121 | template 122 | BigEndianSpan make_big_endian_span( 123 | absl::Span &bytes) { 124 | return BigEndianSpan( 125 | absl::Span(bytes)); 126 | } 127 | } // namespace xilinx 128 | } // namespace fpga 129 | #endif // FPGA_XILINX_BIG_ENDIAN_SPAN 130 | -------------------------------------------------------------------------------- /fpga/xilinx/big-endian-span_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #include "fpga/xilinx/big-endian-span.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "gtest/gtest.h" 17 | 18 | namespace fpga { 19 | namespace xilinx { 20 | namespace { 21 | TEST(BigEndianSpanTest, Read32WithEmptySpan) { 22 | std::vector bytes; 23 | auto words = make_big_endian_span(bytes); 24 | EXPECT_EQ(words.size(), static_cast(0)); 25 | } 26 | 27 | TEST(BigEndianSpanTest, Read32WithTooFewBytes) { 28 | std::vector bytes{0x0, 0x1, 0x2}; 29 | auto words = make_big_endian_span(bytes); 30 | EXPECT_EQ(words.size(), static_cast(0)); 31 | } 32 | 33 | TEST(BigEndianSpanTest, Read32WithExactBytes) { 34 | std::vector bytes{0x0, 0x1, 0x2, 0x3}; 35 | auto words = make_big_endian_span(bytes); 36 | ASSERT_EQ(words.size(), static_cast(1)); 37 | EXPECT_EQ(static_cast(words[0]), static_cast(0x00010203)); 38 | } 39 | 40 | TEST(BigEndianSpanTest, Read32WithMultipleWords) { 41 | std::vector bytes{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}; 42 | auto words = make_big_endian_span(bytes); 43 | ASSERT_EQ(words.size(), static_cast(2)); 44 | EXPECT_EQ(static_cast(words[0]), static_cast(0x00010203)); 45 | EXPECT_EQ(static_cast(words[1]), static_cast(0x04050607)); 46 | } 47 | 48 | TEST(BigEndianSpanTest, Write32) { 49 | std::vector bytes{0x0, 0x1, 0x2, 0x3}; 50 | auto words = make_big_endian_span(bytes); 51 | words[0] = 0x04050607; 52 | 53 | const std::vector expected{0x4, 0x5, 0x6, 0x7}; 54 | EXPECT_EQ(bytes, expected); 55 | } 56 | } // namespace 57 | } // namespace xilinx 58 | } // namespace fpga 59 | -------------------------------------------------------------------------------- /fpga/xilinx/bit-ops.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #ifndef FPGA_XILINX_BIT_OPS_H 11 | #define FPGA_XILINX_BIT_OPS_H 12 | namespace fpga { 13 | namespace xilinx { 14 | template 15 | constexpr UInt bit_mask(const int bit) { 16 | return (static_cast(1) << bit); 17 | } 18 | 19 | template 20 | constexpr UInt bit_sizeof() { 21 | return sizeof(UInt) * 8; 22 | } 23 | 24 | template 25 | constexpr UInt bit_all_ones() { 26 | return ~static_cast(0); 27 | } 28 | 29 | template 30 | constexpr UInt bit_mask_range(const int top_bit, const int bottom_bit) { 31 | return ( 32 | (bit_all_ones() >> (bit_sizeof() - 1 - top_bit)) & 33 | (bit_all_ones() - bit_mask(bottom_bit) + static_cast(1))); 34 | } 35 | 36 | template 37 | constexpr UInt bit_field_get(UInt value, const int top_bit, 38 | const int bottom_bit) { 39 | return (value & bit_mask_range(top_bit, bottom_bit)) >> bottom_bit; 40 | } 41 | 42 | template 43 | constexpr UInt bit_field_set(const UInt reg_value, const int top_bit, 44 | const int bottom_bit, 45 | const ValueType field_value) { 46 | return ((reg_value & ~bit_mask_range(top_bit, bottom_bit)) | 47 | ((static_cast(field_value) << bottom_bit) & 48 | bit_mask_range(top_bit, bottom_bit))); 49 | } 50 | } // namespace xilinx 51 | } // namespace fpga 52 | #endif // FPGA_XILINX_BIT_OPS_H 53 | -------------------------------------------------------------------------------- /fpga/xilinx/bit-ops_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #include "fpga/xilinx/bit-ops.h" 11 | 12 | #include 13 | 14 | #include "gtest/gtest.h" 15 | 16 | namespace fpga { 17 | namespace xilinx { 18 | namespace { 19 | TEST(BitMaskTest, Bit0) { 20 | const uint32_t expected = bit_mask(0); 21 | EXPECT_EQ(static_cast(0x1), expected); 22 | } 23 | 24 | TEST(BitMaskTest, Bit3) { 25 | const uint32_t expected = bit_mask(3); 26 | EXPECT_EQ(static_cast(0x8), expected); 27 | } 28 | 29 | TEST(BitMaskRange, SingleBit) { 30 | const uint32_t expected = bit_mask_range(23, 23); 31 | EXPECT_EQ(static_cast(0x800000), expected); 32 | } 33 | 34 | TEST(BitMaskRange, DownToZero) { 35 | const uint32_t expected = bit_mask_range(7, 0); 36 | EXPECT_EQ(static_cast(0xFF), expected); 37 | } 38 | 39 | TEST(BitMaskRange, MiddleBits) { 40 | const uint32_t expected = bit_mask_range(18, 8); 41 | EXPECT_EQ(static_cast(0x7FF00), expected); 42 | } 43 | 44 | TEST(BitFieldGetTest, OneSelectedBit) { 45 | const uint32_t expected = bit_field_get(0xFFFFFFFF, 23, 23); 46 | EXPECT_EQ(static_cast(1), expected); 47 | } 48 | 49 | TEST(BitFieldGetTest, SelectDownToZero) { 50 | const uint32_t expected = bit_field_get(0xFFCCBBAA, 7, 0); 51 | EXPECT_EQ(static_cast(0xAA), expected); 52 | } 53 | 54 | TEST(BitFieldGetTest, SelectMidway) { 55 | const uint32_t expected = bit_field_get(0xFFCCBBAA, 18, 8); 56 | EXPECT_EQ(static_cast(0x4BB), expected); 57 | } 58 | 59 | TEST(BitFieldSetTest, WriteOneBit) { 60 | const uint32_t actual = bit_field_set(static_cast(0x0), 23, 23, 61 | static_cast(0x1)); 62 | EXPECT_EQ(actual, static_cast(0x800000)); 63 | } 64 | 65 | TEST(BitFieldSetTest, WriteOneBitWithOutOfRangeValue) { 66 | const uint32_t actual = bit_field_set(static_cast(0x0), 23, 23, 67 | static_cast(0x3)); 68 | EXPECT_EQ(actual, static_cast(0x800000)); 69 | } 70 | 71 | TEST(BitFieldSetTest, WriteMultipleBits) { 72 | const uint32_t actual = bit_field_set(static_cast(0x0), 18, 8, 73 | static_cast(0x123)); 74 | EXPECT_EQ(actual, static_cast(0x12300)); 75 | } 76 | 77 | TEST(BitFieldSetTest, WriteMultipleBitsWithOutOfRangeValue) { 78 | const uint32_t actual = bit_field_set(static_cast(0x0), 18, 8, 79 | static_cast(0x1234)); 80 | EXPECT_EQ(actual, static_cast(0x23400)); 81 | } 82 | } // namespace 83 | } // namespace xilinx 84 | } // namespace fpga 85 | -------------------------------------------------------------------------------- /fpga/xilinx/bitstream-reader-xc7_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "absl/types/span.h" 16 | #include "fpga/xilinx/arch-types.h" 17 | #include "fpga/xilinx/bitstream-reader.h" 18 | 19 | namespace fpga { 20 | namespace xilinx { 21 | namespace { 22 | constexpr fpga::xilinx::Architecture kArch = fpga::xilinx::Architecture::kXC7; 23 | using ArchType = ArchitectureType; 24 | using ConfigurationPacket = ArchType::ConfigurationPacket; 25 | using ConfigurationRegister = ArchType::ConfigurationRegister; 26 | 27 | TEST(XC7BitstreamReaderTest, InitWithEmptyBytesReturnsNull) { 28 | absl::Span const bitstream; 29 | auto reader = BitstreamReader::InitWithBytes(bitstream); 30 | EXPECT_FALSE(reader); 31 | } 32 | 33 | TEST(XC7BitstreamReaderTest, InitWithOnlySyncReturnsObject) { 34 | std::vector const bitstream{0xAA, 0x99, 0x55, 0x66}; 35 | auto reader = BitstreamReader::InitWithBytes(bitstream); 36 | EXPECT_TRUE(reader); 37 | } 38 | 39 | TEST(XC7BitstreamReaderTest, 40 | InitWithSyncAfterNonWordSizedPaddingReturnsObject) { 41 | std::vector const bitstream{0xFF, 0xFE, 0xAA, 0x99, 0x55, 0x66}; 42 | auto reader = BitstreamReader::InitWithBytes(bitstream); 43 | EXPECT_TRUE(reader); 44 | } 45 | 46 | TEST(XC7BitstreamReaderTest, InitWithSyncAfterWordSizedPaddingReturnsObject) { 47 | std::vector const bitstream{0xFF, 0xFE, 0xFD, 0xFC, 48 | 0xAA, 0x99, 0x55, 0x66}; 49 | auto reader = BitstreamReader::InitWithBytes(bitstream); 50 | EXPECT_TRUE(reader); 51 | } 52 | 53 | TEST(XC7BitstreamReaderTest, ParsesType1Packet) { 54 | std::vector const bitstream{ 55 | 0xAA, 0x99, 0x55, 0x66, // sync 56 | 0x20, 0x00, 0x00, 0x00, // NOP 57 | }; 58 | auto reader = BitstreamReader::InitWithBytes(bitstream); 59 | ASSERT_TRUE(reader); 60 | // NOLINTBEGIN(bugprone-unchecked-optional-access) 61 | ASSERT_NE(reader->begin(), reader->end()); 62 | 63 | auto first_packet = reader->begin(); 64 | EXPECT_EQ(first_packet->opcode(), ConfigurationPacket::Opcode::kNOP); 65 | 66 | EXPECT_EQ(++first_packet, reader->end()); 67 | // NOLINTEND(bugprone-unchecked-optional-access) 68 | } 69 | 70 | TEST(XC7BitstreamReaderTest, ParseType2PacketWithoutType1Fails) { 71 | std::vector const bitstream{ 72 | 0xAA, 0x99, 0x55, 0x66, // sync 73 | 0x40, 0x00, 0x00, 0x00, // Type 2 NOP 74 | }; 75 | auto reader = BitstreamReader::InitWithBytes(bitstream); 76 | ASSERT_TRUE(reader); 77 | // NOLINTNEXTLINE(bugprone-unchecked-optional-access) 78 | EXPECT_EQ(reader->begin(), reader->end()); 79 | } 80 | 81 | TEST(XC7BitstreamReaderTest, ParsesType2AfterType1Packet) { 82 | std::vector const bitstream{ 83 | 0xAA, 0x99, 0x55, 0x66, // sync 84 | 0x28, 0x00, 0x60, 0x00, // Type 1 Read zero bytes from 6 85 | 0x48, 0x00, 0x00, 0x04, // Type 2 write of 4 words 86 | 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 87 | 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x10, 88 | }; 89 | std::vector data_words{0x01020304, 0x05060708, 0x090A0B0C, 90 | 0x0D0E0F10}; 91 | 92 | auto reader = BitstreamReader::InitWithBytes(bitstream); 93 | ASSERT_TRUE(reader); 94 | // NOLINTBEGIN(bugprone-unchecked-optional-access) 95 | ASSERT_NE(reader->begin(), reader->end()); 96 | 97 | auto first_packet = reader->begin(); 98 | EXPECT_EQ(first_packet->opcode(), ConfigurationPacket::Opcode::kRead); 99 | EXPECT_EQ(first_packet->address(), ConfigurationRegister::kFDRO); 100 | EXPECT_EQ(first_packet->data(), absl::Span()); 101 | 102 | auto second_packet = ++first_packet; 103 | ASSERT_NE(second_packet, reader->end()); 104 | EXPECT_EQ(second_packet->opcode(), ConfigurationPacket::Opcode::kRead); 105 | EXPECT_EQ(second_packet->address(), ConfigurationRegister::kFDRO); 106 | EXPECT_EQ(first_packet->data(), absl::Span(data_words)); 107 | 108 | EXPECT_EQ(++first_packet, reader->end()); 109 | // NOLINTEND(bugprone-unchecked-optional-access) 110 | } 111 | } // namespace 112 | } // namespace xilinx 113 | } // namespace fpga 114 | -------------------------------------------------------------------------------- /fpga/xilinx/bitstream-reader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #ifndef FPGA_XILINX_BITSTREAM_READER_H 11 | #define FPGA_XILINX_BITSTREAM_READER_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "absl/types/optional.h" 23 | #include "absl/types/span.h" 24 | #include "fpga/xilinx/arch-types.h" 25 | #include "fpga/xilinx/big-endian-span.h" 26 | 27 | namespace fpga { 28 | namespace xilinx { 29 | // Constructs a collection of 32-bit big-endian words from a bitstream file. 30 | // Provides an iterator over the configuration packets. 31 | template 32 | class BitstreamReader { 33 | public: 34 | using ArchType = ArchitectureType; 35 | using value_type = ArchType::ConfigurationPacket; 36 | 37 | // Implements an iterator over the words grouped in configuration 38 | // packets. 39 | class iterator { 40 | public: 41 | using iterator_category = std::input_iterator_tag; 42 | using value_type = BitstreamReader::value_type; 43 | using difference_type = std::ptrdiff_t; 44 | using pointer = value_type *; 45 | using reference = value_type &; 46 | iterator &operator++(); 47 | 48 | bool operator==(const iterator &other) const; 49 | bool operator!=(const iterator &other) const; 50 | 51 | const value_type &operator*() const; 52 | const value_type *operator->() const; 53 | 54 | protected: 55 | explicit iterator(absl::Span words); 56 | 57 | private: 58 | friend BitstreamReader; 59 | 60 | typename value_type::ParseResult parse_result_; 61 | absl::Span words_; 62 | }; 63 | 64 | // Construct a reader from a collection of 32-bit, big-endian words. 65 | // Assumes that any sync word has already been removed. 66 | explicit BitstreamReader(std::vector &&words) 67 | : words_(std::move(words)) {} 68 | 69 | BitstreamReader() = default; 70 | size_t size() { return words_.size(); } 71 | 72 | // Construct a `BitstreamReader` from a Container of bytes. 73 | // Any bytes preceding an initial sync word are ignored. 74 | template 75 | static absl::optional> InitWithBytes(T bitstream); 76 | 77 | // Extract information from bitstream necessary to reconstruct RBT 78 | // header and add it to the AUX data 79 | template 80 | static void PrintHeader(T bitstream, FILE *aux_fp); 81 | 82 | // Extract configuration logic data and add to the AUX data 83 | void PrintFpgaConfigurationLogicData(FILE *aux_fp); 84 | 85 | const std::vector &words() { return words_; }; 86 | 87 | // Returns an iterator that yields `ConfigurationPackets` 88 | // as read from the bitstream. 89 | iterator begin(); 90 | iterator end(); 91 | 92 | private: 93 | static const std::array kSyncWord; 94 | static const std::array kWcfgCmd; 95 | static const std::array kNullCmd; 96 | 97 | std::vector words_; 98 | }; 99 | 100 | // Extract FPGA configuration logic information 101 | template 102 | void BitstreamReader::PrintFpgaConfigurationLogicData(FILE *aux_fp) { 103 | // Get the data before the first FDRI_WRITE command packet 104 | const auto fpga_conf_end = std::search(words_.cbegin(), words_.cend(), 105 | kWcfgCmd.cbegin(), kWcfgCmd.cend()); 106 | fprintf(aux_fp, "FPGA configuration logic prefix:"); 107 | for (auto it = words_.cbegin(); it != fpga_conf_end; ++it) { 108 | fprintf(aux_fp, " %08X", *it); 109 | } 110 | fprintf(aux_fp, "\n"); 111 | 112 | // Get the data after the last Null Command packet 113 | const auto last_null_cmd = std::find_end(words_.cbegin(), words_.cend(), 114 | kNullCmd.cbegin(), kNullCmd.cend()); 115 | fprintf(aux_fp, "FPGA configuration logic suffix:"); 116 | for (auto it = last_null_cmd; it != words_.cend(); ++it) { 117 | fprintf(aux_fp, " %08X", *it); 118 | } 119 | fprintf(aux_fp, "\n"); 120 | } 121 | 122 | template 123 | template 124 | void BitstreamReader::PrintHeader(T bitstream, FILE *aux_fp) { 125 | // If this is really a Xilinx bitstream, there will be a sync 126 | // word somewhere toward the beginning. 127 | auto sync_pos = std::search(bitstream.begin(), bitstream.end(), 128 | kSyncWord.begin(), kSyncWord.end()); 129 | if (sync_pos == bitstream.end()) { 130 | return; 131 | } 132 | sync_pos += kSyncWord.size(); 133 | // Wrap the provided container in a span that strips off the preamble. 134 | absl::Span bitstream_span(bitstream); 135 | auto header_packets = bitstream_span.subspan(0, sync_pos - bitstream.begin()); 136 | 137 | fprintf(aux_fp, "Header bytes:"); 138 | for (auto &word : header_packets) { 139 | fprintf(aux_fp, " %02X", word); 140 | } 141 | fprintf(aux_fp, "\n"); 142 | } 143 | 144 | template 145 | template 146 | absl::optional> BitstreamReader::InitWithBytes( 147 | T bitstream) { 148 | using bitstream_value_type = const T::value_type; 149 | using words_value_type = ArchType::FrameWords::value_type; 150 | 151 | // If this is really a Xilinx bitstream, there will be a sync 152 | // word somewhere toward the beginning. 153 | auto sync_pos = std::search(bitstream.begin(), bitstream.end(), 154 | kSyncWord.begin(), kSyncWord.end()); 155 | if (sync_pos == bitstream.end()) { 156 | return absl::optional>(); 157 | } 158 | sync_pos += kSyncWord.size(); 159 | 160 | // Wrap the provided container in a span that strips off the preamble. 161 | absl::Span bitstream_span(bitstream); 162 | absl::Span config_packets = 163 | bitstream_span.subspan(sync_pos - bitstream.begin()); 164 | 165 | // Convert the bytes into 32-bit or 16-bit in case of Spartan6, 166 | // big-endian words. 167 | auto big_endian_reader = 168 | make_big_endian_span(config_packets); 169 | /// std::vector words; 170 | std::vector words{big_endian_reader.begin(), 171 | big_endian_reader.end()}; 172 | 173 | return BitstreamReader(std::move(words)); 174 | } 175 | 176 | // Sync word as specified in UG470 page 81 177 | template 178 | const std::array BitstreamReader::kSyncWord{0xAA, 0x99, 0x55, 179 | 0x66}; 180 | 181 | // Writing the WCFG(0x1) command in type 1 packet with 1 word to the CMD 182 | // register (0x30008001) Refer to UG470 page 110 183 | template 184 | const std::array BitstreamReader::kWcfgCmd = {0x30008001, 185 | 0x1}; 186 | 187 | // Writing the NULL(0x0) command in type 1 packet with 1 word to the CMD 188 | // register (0x30008001) Refer to UG470 page 110 189 | template 190 | const std::array BitstreamReader::kNullCmd = {0x30008001, 191 | 0x0}; 192 | 193 | template 194 | typename BitstreamReader::iterator BitstreamReader::begin() { 195 | return iterator(absl::MakeSpan(words_)); 196 | } 197 | 198 | template 199 | typename BitstreamReader::iterator BitstreamReader::end() { 200 | return iterator({}); 201 | } 202 | 203 | template 204 | BitstreamReader::iterator::iterator(absl::Span words) { 205 | parse_result_.first = words; 206 | parse_result_.second = {}; 207 | ++(*this); 208 | } 209 | 210 | template 211 | typename BitstreamReader::iterator & 212 | BitstreamReader::iterator::operator++() { 213 | do { 214 | auto new_result = value_type::InitWithWords( 215 | parse_result_.first, parse_result_.second.has_value() 216 | ? parse_result_.second.operator->() 217 | : nullptr); 218 | // If the a valid header is being found but there are 219 | // insufficient words to yield a packet, consider it the end. 220 | if (new_result.first == parse_result_.first) { 221 | words_ = absl::Span(); 222 | break; 223 | } 224 | words_ = parse_result_.first; 225 | parse_result_ = new_result; 226 | } while (!parse_result_.first.empty() && !parse_result_.second); 227 | 228 | if (!parse_result_.second) { 229 | words_ = absl::Span(); 230 | } 231 | return *this; 232 | } 233 | 234 | template 235 | bool BitstreamReader::iterator::operator==(const iterator &other) const { 236 | return words_ == other.words_; 237 | } 238 | 239 | template 240 | bool BitstreamReader::iterator::operator!=(const iterator &other) const { 241 | return !(*this == other); 242 | } 243 | 244 | template 245 | const typename BitstreamReader::value_type & 246 | BitstreamReader::iterator::operator*() const { 247 | return *(parse_result_.second); 248 | } 249 | 250 | template 251 | const typename BitstreamReader::value_type * 252 | BitstreamReader::iterator::operator->() const { 253 | return parse_result_.second.operator->(); 254 | } 255 | } // namespace xilinx 256 | } // namespace fpga 257 | #endif // FPGA_XILINX_BITSTREAM_READER_H 258 | -------------------------------------------------------------------------------- /fpga/xilinx/bitstream-writer.cc: -------------------------------------------------------------------------------- 1 | #include "fpga/xilinx/bitstream-writer.h" 2 | 3 | #include 4 | 5 | #include "fpga/xilinx/arch-types.h" 6 | #include "fpga/xilinx/arch-xc7-configuration-packet.h" 7 | #include "fpga/xilinx/bit-ops.h" 8 | #include "fpga/xilinx/configuration-packet.h" 9 | 10 | namespace fpga { 11 | namespace xilinx { 12 | // Per UG470 pg 80: Bus Width Auto Detection 13 | template <> 14 | typename BitstreamWriter::header_t 15 | BitstreamWriter::header_{ 16 | 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 17 | 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000BB, 0x11220044, 18 | 0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566}; 19 | 20 | template <> 21 | typename BitstreamWriter::header_t 22 | BitstreamWriter::header_{ 23 | 0xFFFFFFFF, 0x000000BB, 0x11220044, 0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566}; 24 | 25 | template <> 26 | typename BitstreamWriter::header_t 27 | BitstreamWriter::header_{ 28 | 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 29 | 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 30 | 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000BB, 0x11220044, 31 | 0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566}; 32 | 33 | uint32_t PacketHeader(const xc7::ConfigurationPacket &packet) { 34 | uint32_t ret = 0; 35 | ret = bit_field_set(ret, 31, 29, packet.header_type()); 36 | switch (static_cast(packet.header_type())) { 37 | case ConfigurationPacketType::kNONE: 38 | // Bitstreams are 0 padded sometimes, essentially making 39 | // a type 0 frame Ignore the other fields for now 40 | break; 41 | case ConfigurationPacketType::kTYPE1: { 42 | // Table 5-20: Type 1 Packet Header Format 43 | ret = bit_field_set(ret, 28, 27, packet.opcode()); 44 | ret = bit_field_set(ret, 26, 13, packet.address()); 45 | ret = bit_field_set(ret, 10, 0, packet.data().length()); 46 | break; 47 | } 48 | case ConfigurationPacketType::kTYPE2: { 49 | // Table 5-22: Type 2 Packet Header 50 | // Note address is from previous type 1 header 51 | ret = bit_field_set(ret, 28, 27, packet.opcode()); 52 | ret = bit_field_set(ret, 26, 0, packet.data().length()); 53 | break; 54 | } 55 | default: break; 56 | } 57 | return ret; 58 | } 59 | } // namespace xilinx 60 | } // namespace fpga 61 | -------------------------------------------------------------------------------- /fpga/xilinx/bitstream-writer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | /* 11 | * Takes in a collection of ConfigurationPacket and writes them to specified 12 | * file This includes the following: -Bus auto detection -Sync Word -FPGA 13 | * configuration 14 | */ 15 | #ifndef FPGA_XILINX_BITSTREAM_WRITER_H 16 | #define FPGA_XILINX_BITSTREAM_WRITER_H 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "absl/log/check.h" 27 | #include "absl/strings/str_cat.h" 28 | #include "absl/time/clock.h" 29 | #include "absl/time/time.h" 30 | #include "absl/types/optional.h" 31 | #include "absl/types/span.h" 32 | #include "fpga/xilinx/arch-types.h" 33 | #include "fpga/xilinx/arch-xc7-configuration-packet.h" 34 | 35 | namespace fpga { 36 | namespace xilinx { 37 | uint32_t PacketHeader(const xc7::ConfigurationPacket &packet); 38 | 39 | // Writes out the complete Xilinx bitstream including 40 | // header, sync word and configuration sequence. 41 | template 42 | class BitstreamWriter { 43 | private: 44 | using ArchType = ArchitectureType; 45 | using FrameWords = ArchType::FrameWords; 46 | using FrameAddress = ArchType::FrameAddress; 47 | using ConfigurationPacket = ArchType::ConfigurationPacket; 48 | using ConfigurationPackage = ArchType::ConfigurationPackage; 49 | using ConfigurationRegister = ArchType::ConfigurationRegister; 50 | 51 | public: 52 | using header_t = std::vector; 53 | using packets_t = std::vector>; 54 | using BitstreamHeader = std::vector; 55 | // Only defined if a packet exists 56 | using op_data_t = absl::optional>; 57 | using data_iterator_t = absl::Span::iterator; 58 | using itr_value_type = uint32_t; 59 | 60 | class packet_iterator { 61 | public: 62 | using iterator_category = std::input_iterator_tag; 63 | using value_type = BitstreamWriter::itr_value_type; 64 | using difference_type = std::ptrdiff_t; 65 | using pointer = value_type *; 66 | using reference = value_type &; 67 | 68 | packet_iterator &operator++(); 69 | 70 | bool operator==(const packet_iterator &other) const; 71 | bool operator!=(const packet_iterator &other) const; 72 | 73 | itr_value_type operator*() const; 74 | itr_value_type operator->() const; 75 | 76 | using state_t = enum { 77 | STATE_HEADER = 1, 78 | STATE_DATA = 2, 79 | STATE_END = 3, 80 | }; 81 | 82 | protected: 83 | explicit packet_iterator(const ConfigurationPacket *packet, state_t state, 84 | data_iterator_t itr_data); 85 | 86 | private: 87 | friend class iterator; 88 | friend BitstreamWriter; 89 | 90 | // Data iterators 91 | // First over the fixed header, then the configuration data 92 | state_t state_; 93 | // Over packet.data() 94 | data_iterator_t itr_data_; 95 | 96 | const ConfigurationPacket *packet_; 97 | }; 98 | 99 | class iterator { 100 | public: 101 | using iterator_category = std::input_iterator_tag; 102 | using value_type = BitstreamWriter::itr_value_type; 103 | using difference_type = std::ptrdiff_t; 104 | using pointer = value_type *; 105 | using reference = value_type &; 106 | 107 | iterator &operator++(); 108 | 109 | bool operator==(const iterator &other) const; 110 | bool operator!=(const iterator &other) const; 111 | 112 | itr_value_type operator*() const; 113 | itr_value_type operator->() const; 114 | 115 | packet_iterator packet_begin(); 116 | packet_iterator packet_end(); 117 | 118 | protected: 119 | explicit iterator(header_t::iterator itr_header, const packets_t &packets, 120 | typename packets_t::const_iterator itr_packets, 121 | absl::optional op_itr_packet); 122 | 123 | private: 124 | friend BitstreamWriter; 125 | // Data iterators 126 | // First over the fixed header, then the configuration data 127 | header_t::iterator itr_header_; 128 | const packets_t &packets_; 129 | typename packets_t::const_iterator itr_packets_; 130 | absl::optional op_itr_packet_; 131 | }; 132 | 133 | explicit BitstreamWriter(const packets_t &packets) : packets_(packets) {} 134 | 135 | // Writes out the complete bitstream for Xilinx FPGA based on 136 | // the Configuration Package which holds the complete programming 137 | // sequence. 138 | int writeBitstream(const typename ArchType::ConfigurationPackage &packets, 139 | const std::string &part_name, 140 | const std::string &source_name, 141 | const std::string &generator_name, std::ostream &out); 142 | iterator begin(); 143 | iterator end(); 144 | 145 | private: 146 | static header_t header_; 147 | const packets_t &packets_; 148 | 149 | // Creates a Xilinx bit header which is mostly a 150 | // Tag-Length-Value(TLV) format documented here: 151 | // http://www.fpga-faq.com/FAQ_Pages/0026_Tell_me_about_bit_files.htm 152 | BitstreamHeader create_header(const std::string &part_name, 153 | const std::string &source_name, 154 | const std::string &generator_name, 155 | std::ostream &out); 156 | }; 157 | 158 | template 159 | int BitstreamWriter::writeBitstream(const ConfigurationPackage &packets, 160 | const std::string &part_name, 161 | const std::string &source_name, 162 | const std::string &generator_name, 163 | std::ostream &out) { 164 | BitstreamHeader bit_header( 165 | create_header(part_name, source_name, generator_name, out)); 166 | out.write(reinterpret_cast(bit_header.data()), 167 | bit_header.size()); 168 | 169 | auto end_of_header_pos = out.tellp(); 170 | auto header_data_length_pos = 171 | end_of_header_pos - static_cast(4); 172 | 173 | BitstreamWriter out_bitstream_writer(packets); 174 | int bytes_per_word = sizeof(typename FrameWords::value_type); 175 | for (uint32_t word : out_bitstream_writer) { 176 | for (int byte = bytes_per_word - 1; byte >= 0; byte--) { 177 | out.put((word >> (byte * 8)) & 0xFF); 178 | } 179 | } 180 | 181 | uint32_t const length_of_data = out.tellp() - end_of_header_pos; 182 | 183 | out.seekp(header_data_length_pos); 184 | for (int byte = 3; byte >= 0; byte--) { 185 | out.put((length_of_data >> (byte * 8)) & 0xFF); 186 | } 187 | return 0; 188 | } 189 | 190 | template 191 | typename BitstreamWriter::BitstreamHeader 192 | BitstreamWriter::create_header(const std::string &part_name, 193 | const std::string &source_name, 194 | const std::string &generator_name, 195 | std::ostream &out) { 196 | // Sync header 197 | BitstreamHeader bit_header{0x0, 0x9, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 198 | 0xf0, 0x0f, 0xf0, 0x00, 0x00, 0x01, 'a'}; 199 | auto build_source = absl::StrCat(source_name, ";Generator=" + generator_name); 200 | bit_header.push_back(static_cast((build_source.size() + 1) >> 8)); 201 | bit_header.push_back(static_cast(build_source.size() + 1)); 202 | bit_header.insert(bit_header.end(), build_source.begin(), build_source.end()); 203 | bit_header.push_back(0x0); 204 | 205 | // Source file. 206 | bit_header.push_back('b'); 207 | bit_header.push_back(static_cast((part_name.size() + 1) >> 8)); 208 | bit_header.push_back(static_cast(part_name.size() + 1)); 209 | bit_header.insert(bit_header.end(), part_name.begin(), part_name.end()); 210 | bit_header.push_back(0x0); 211 | 212 | // Build timestamp. 213 | auto build_time = absl::Now(); 214 | auto build_date_string = 215 | absl::FormatTime("%E4Y/%m/%d", build_time, absl::UTCTimeZone()); 216 | auto build_time_string = 217 | absl::FormatTime("%H:%M:%S", build_time, absl::UTCTimeZone()); 218 | 219 | bit_header.push_back('c'); 220 | bit_header.push_back( 221 | static_cast((build_date_string.size() + 1) >> 8)); 222 | bit_header.push_back(static_cast(build_date_string.size() + 1)); 223 | bit_header.insert(bit_header.end(), build_date_string.begin(), 224 | build_date_string.end()); 225 | bit_header.push_back(0x0); 226 | 227 | bit_header.push_back('d'); 228 | bit_header.push_back( 229 | static_cast((build_time_string.size() + 1) >> 8)); 230 | bit_header.push_back(static_cast(build_time_string.size() + 1)); 231 | bit_header.insert(bit_header.end(), build_time_string.begin(), 232 | build_time_string.end()); 233 | bit_header.push_back(0x0); 234 | bit_header.insert(bit_header.end(), {'e', 0x0, 0x0, 0x0, 0x0}); 235 | return bit_header; 236 | } 237 | 238 | template 239 | typename BitstreamWriter::packet_iterator 240 | BitstreamWriter::iterator::packet_begin() { 241 | // itr_packets = packets.begin(); 242 | const ConfigurationPacket &packet = **itr_packets_; 243 | 244 | return BitstreamWriter::packet_iterator( 245 | &packet, BitstreamWriter::packet_iterator::STATE_HEADER, 246 | packet.data().begin()); 247 | } 248 | 249 | template 250 | typename BitstreamWriter::packet_iterator 251 | BitstreamWriter::iterator::packet_end() { 252 | const ConfigurationPacket &packet = **itr_packets_; 253 | 254 | return BitstreamWriter::packet_iterator( 255 | &packet, BitstreamWriter::packet_iterator::STATE_END, 256 | // Essentially ignored 257 | packet.data().end()); 258 | } 259 | 260 | template 261 | BitstreamWriter::packet_iterator::packet_iterator( 262 | const ConfigurationPacket *packet, state_t state, data_iterator_t itr_data) 263 | : state_(state), itr_data_(itr_data), packet_(packet) {} 264 | 265 | template 266 | typename BitstreamWriter::packet_iterator & 267 | BitstreamWriter::packet_iterator::operator++() { 268 | if (state_ == STATE_HEADER) { 269 | itr_data_ = packet_->data().begin(); 270 | if (itr_data_ == packet_->data().end()) { 271 | state_ = STATE_END; 272 | } else { 273 | state_ = STATE_DATA; 274 | } 275 | } else if (state_ == STATE_DATA) { 276 | /// Advance. data must be valid while not at end 277 | itr_data_++; 278 | // Reached this end of this packet? 279 | if (itr_data_ == packet_->data().end()) { 280 | state_ = STATE_END; 281 | } 282 | } 283 | return *this; 284 | } 285 | 286 | template 287 | bool BitstreamWriter::packet_iterator::operator==( 288 | const packet_iterator &other) const { 289 | return state_ == other.state_ && itr_data_ == other.itr_data_; 290 | } 291 | 292 | template 293 | bool BitstreamWriter::packet_iterator::operator!=( 294 | const packet_iterator &other) const { 295 | return !(*this == other); 296 | } 297 | 298 | template 299 | typename BitstreamWriter::itr_value_type 300 | BitstreamWriter::packet_iterator::operator*() const { 301 | if (state_ == STATE_HEADER) { 302 | return PacketHeader(*packet_); 303 | } 304 | if (state_ == STATE_DATA) { 305 | return *itr_data_; 306 | } 307 | CHECK(false) << "unreachable state"; 308 | } 309 | 310 | template 311 | typename BitstreamWriter::itr_value_type 312 | BitstreamWriter::packet_iterator::operator->() const { 313 | return *(*this); 314 | } 315 | 316 | template 317 | typename BitstreamWriter::iterator BitstreamWriter::begin() { 318 | typename packets_t::const_iterator itr_packets = packets_.begin(); 319 | absl::optional op_packet_itr; 320 | 321 | // May have no packets 322 | if (itr_packets != packets_.end()) { 323 | // op_packet_itr = packet_begin(); 324 | // FIXME: de-duplicate this 325 | const ConfigurationPacket &packet = **itr_packets; 326 | packet_iterator packet_itr = packet_iterator( 327 | &packet, packet_iterator::STATE_HEADER, packet.data().begin()); 328 | op_packet_itr = packet_itr; 329 | } 330 | return iterator(header_.begin(), packets_, itr_packets, op_packet_itr); 331 | } 332 | 333 | template 334 | typename BitstreamWriter::iterator BitstreamWriter::end() { 335 | return iterator(header_.end(), packets_, packets_.end(), 336 | absl::optional()); 337 | } 338 | 339 | template 340 | BitstreamWriter::iterator::iterator( 341 | header_t::iterator itr_header, 342 | const typename BitstreamWriter::packets_t &packets, 343 | typename BitstreamWriter::packets_t::const_iterator itr_packets, 344 | absl::optional itr_packet) 345 | : itr_header_(itr_header), 346 | packets_(packets), 347 | itr_packets_(itr_packets), 348 | op_itr_packet_(itr_packet) {} 349 | 350 | template 351 | typename BitstreamWriter::iterator & 352 | BitstreamWriter::iterator::operator++() { 353 | // Still generating header? 354 | if (itr_header_ != header_.end()) { 355 | itr_header_++; 356 | // Finished header? 357 | // Will advance to initialized itr_packets value 358 | // XXX: maybe should just overwrite here 359 | if (itr_header_ == header_.end()) { 360 | itr_packets_ = packets_.begin(); 361 | if (itr_packets_ != packets_.end()) { 362 | op_itr_packet_ = packet_begin(); 363 | } 364 | } 365 | // Then somewhere in packets 366 | } else { 367 | // We are either at end() in which case this operation is 368 | // invalid Or there is a packet in progress packet in progress? 369 | // Advance it 370 | ++(*op_itr_packet_); 371 | // Done with this packet? 372 | if (*op_itr_packet_ == packet_end()) { 373 | itr_packets_++; 374 | if (itr_packets_ == packets_.end()) { 375 | // we are at the very end 376 | // invalidate data to be neat 377 | op_itr_packet_.reset(); 378 | } else { 379 | op_itr_packet_ = packet_begin(); 380 | } 381 | } 382 | } 383 | return *this; 384 | } 385 | 386 | template 387 | bool BitstreamWriter::iterator::operator==(const iterator &other) const { 388 | return itr_header_ == other.itr_header_ && 389 | itr_packets_ == other.itr_packets_ && 390 | op_itr_packet_ == other.op_itr_packet_; 391 | } 392 | 393 | template 394 | bool BitstreamWriter::iterator::operator!=(const iterator &other) const { 395 | return !(*this == other); 396 | } 397 | 398 | template 399 | typename BitstreamWriter::itr_value_type 400 | BitstreamWriter::iterator::operator*() const { 401 | if (itr_header_ != header_.end()) { 402 | return *itr_header_; 403 | } 404 | // Iterating over packets, get data from current packet position 405 | return *(*op_itr_packet_); 406 | } 407 | 408 | template 409 | typename BitstreamWriter::itr_value_type 410 | BitstreamWriter::iterator::operator->() const { 411 | return *(*this); 412 | } 413 | } // namespace xilinx 414 | } // namespace fpga 415 | #endif // FPGA_XILINX_XC7SERIES_BITSTREAM_WRITER_H 416 | -------------------------------------------------------------------------------- /fpga/xilinx/bitstream-writer_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #include "fpga/xilinx/bitstream-writer.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "absl/types/span.h" 17 | #include "fpga/xilinx/arch-types.h" 18 | #include "fpga/xilinx/arch-xc7-configuration-packet.h" 19 | #include "fpga/xilinx/bit-ops.h" 20 | #include "fpga/xilinx/configuration-packet.h" 21 | #include "gtest/gtest.h" 22 | 23 | namespace fpga { 24 | namespace xilinx { 25 | namespace { 26 | constexpr fpga::xilinx::Architecture kArch = fpga::xilinx::Architecture::kXC7; 27 | using ArchType = ArchitectureType; 28 | using ConfigurationPacket = ArchType::ConfigurationPacket; 29 | using ConfigurationRegister = ArchType::ConfigurationRegister; 30 | 31 | constexpr uint32_t MakeType1(const int opcode, const int address, 32 | const int word_count) { 33 | return bit_field_set( 34 | bit_field_set( 35 | bit_field_set(bit_field_set(0x0, 31, 29, 0x1), 28, 27, 36 | opcode), 37 | 26, 13, address), 38 | 10, 0, word_count); 39 | } 40 | 41 | constexpr uint32_t MakeType2(const int opcode, const int word_count) { 42 | return bit_field_set( 43 | bit_field_set(bit_field_set(0x0, 31, 29, 0x2), 28, 27, 44 | opcode), 45 | 26, 0, word_count); 46 | } 47 | 48 | #if 0 49 | void DumpPackets(BitstreamWriter writer, bool nl = true) { 50 | int i = 0; 51 | for (unsigned int itr : writer) { 52 | if (nl) { 53 | printf("% 3d: 0x0%08X\n", i, itr); 54 | } else { 55 | printf("0x0%08X, ", itr); 56 | } 57 | fflush(stdout); 58 | ++i; 59 | } 60 | if (!nl) { 61 | printf("\n"); 62 | } 63 | } 64 | #endif 65 | 66 | // Special all 0's 67 | void AddType0(std::vector> &packets) { 68 | static std::vector words{}; 69 | absl::Span const word_span(words); 70 | // CRC is config value 0 71 | packets.emplace_back( 72 | new ConfigurationPacket(0, ConfigurationPacket::Opcode::kNOP, 73 | xc7::ConfigurationRegister::kCRC, word_span)); 74 | } 75 | 76 | void AddType1(std::vector> &packets) { 77 | static std::vector words{MakeType1(0x2, 0x3, 2), 0xAA, 0xBB}; 78 | absl::Span const word_span(words); 79 | auto packet = ConfigurationPacket::InitWithWords(word_span); 80 | ASSERT_TRUE(packet.second.has_value()); 81 | // NOLINTNEXTLINE(bugprone-unchecked-optional-access) 82 | packets.emplace_back(new ConfigurationPacket(*(packet.second))); 83 | } 84 | 85 | // Empty 86 | void AddType1E(std::vector> &packets) { 87 | static std::vector words{MakeType1(0x2, 0x3, 0)}; 88 | absl::Span const word_span(words); 89 | auto packet = ConfigurationPacket::InitWithWords(word_span); 90 | ASSERT_TRUE(packet.second.has_value()); 91 | // NOLINTNEXTLINE(bugprone-unchecked-optional-access) 92 | packets.emplace_back(new ConfigurationPacket(*(packet.second))); 93 | } 94 | 95 | void AddType2(std::vector> &packets) { 96 | // Type 1 packet with address 97 | // Without this InitWithWords will fail on type 2 98 | ConfigurationPacket *packet1; 99 | { 100 | static std::vector words{MakeType1(0x2, 0x3, 0)}; 101 | absl::Span const word_span(words); 102 | auto packet1_pair = ConfigurationPacket::InitWithWords(word_span); 103 | ASSERT_TRUE(packet1_pair.second.has_value()); 104 | // NOLINTNEXTLINE(bugprone-unchecked-optional-access) 105 | packets.emplace_back(new ConfigurationPacket(*(packet1_pair.second))); 106 | packet1 = packets[0].get(); 107 | } 108 | // Type 2 packet with data 109 | { 110 | static std::vector words{ 111 | MakeType2(0x01, 12), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; 112 | absl::Span const word_span(words); 113 | auto packet = ConfigurationPacket::InitWithWords(word_span, packet1); 114 | ASSERT_TRUE(packet.second.has_value()); 115 | // NOLINTNEXTLINE(bugprone-unchecked-optional-access) 116 | packets.emplace_back(new ConfigurationPacket(*(packet.second))); 117 | } 118 | } 119 | 120 | // Empty packets should produce just the header 121 | TEST(BitstreamWriterTest, WriteHeader) { 122 | std::vector> const packets; 123 | 124 | BitstreamWriter writer(packets); 125 | std::vector const words(writer.begin(), writer.end()); 126 | 127 | // Per UG470 pg 80: Bus Width Auto Detection 128 | std::vector const ref_header{ 129 | 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 130 | 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000BB, 0x11220044, 131 | 0xFFFFFFFF, 0xFFFFFFFF, 0xAA995566}; 132 | EXPECT_EQ(words, ref_header); 133 | } 134 | 135 | TEST(BitstreamWriterTest, WriteType0) { 136 | std::vector> packets; 137 | AddType0(packets); 138 | BitstreamWriter writer(packets); 139 | std::vector const words(writer.begin(), writer.end()); 140 | std::vector const ref{ 141 | // Bus width + sync 142 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 143 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0000000BB, 0x011220044, 144 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0AA995566, 145 | // Type 0 146 | 0x00000000}; 147 | EXPECT_EQ(words, ref); 148 | } 149 | TEST(BitstreamWriterTest, WriteType1) { 150 | std::vector> packets; 151 | AddType1(packets); 152 | BitstreamWriter writer(packets); 153 | std::vector const words(writer.begin(), writer.end()); 154 | std::vector const ref{ 155 | // Bus width + sync 156 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 157 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0000000BB, 0x011220044, 158 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0AA995566, 159 | // Type 1 160 | 0x030006002, 0x0000000AA, 0x0000000BB}; 161 | EXPECT_EQ(words, ref); 162 | } 163 | 164 | TEST(BitstreamWriterTest, WriteType2) { 165 | std::vector> packets; 166 | AddType2(packets); 167 | BitstreamWriter writer(packets); 168 | std::vector const words(writer.begin(), writer.end()); 169 | std::vector const ref{ 170 | // Bus width + sync 171 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 172 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0000000BB, 0x011220044, 173 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0AA995566, 174 | // Type 1 + type 2 header 175 | 0x030006000, 0x04800000C, 0x000000001, 0x000000002, 0x000000003, 176 | 0x000000004, 0x000000005, 0x000000006, 0x000000007, 0x000000008, 177 | 0x000000009, 0x00000000A, 0x00000000B, 0x00000000C}; 178 | EXPECT_EQ(words, ref); 179 | } 180 | 181 | TEST(BitstreamWriterTest, WriteMulti) { 182 | std::vector> packets; 183 | AddType1(packets); 184 | AddType1E(packets); 185 | AddType2(packets); 186 | AddType1E(packets); 187 | BitstreamWriter writer(packets); 188 | std::vector const words(writer.begin(), writer.end()); 189 | std::vector const ref{ 190 | // Bus width + sync 191 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 192 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0000000BB, 0x011220044, 193 | 0x0FFFFFFFF, 0x0FFFFFFFF, 0x0AA995566, 194 | // Type1 195 | 0x030006002, 0x0000000AA, 0x0000000BB, 196 | // Type1 197 | 0x030006000, 198 | // Type 1 + type 2 header 199 | 0x030006000, 0x04800000C, 0x000000001, 0x000000002, 0x000000003, 200 | 0x000000004, 0x000000005, 0x000000006, 0x000000007, 0x000000008, 201 | 0x000000009, 0x00000000A, 0x00000000B, 0x00000000C, 202 | // Type 1 203 | 0x030006000}; 204 | EXPECT_EQ(words, ref); 205 | } 206 | } // namespace 207 | } // namespace xilinx 208 | } // namespace fpga 209 | -------------------------------------------------------------------------------- /fpga/xilinx/bitstream.h: -------------------------------------------------------------------------------- 1 | #ifndef FPGA_XILINX_BITSTREAM_H 2 | #define FPGA_XILINX_BITSTREAM_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "absl/container/btree_map.h" 9 | #include "absl/status/status.h" 10 | #include "absl/status/statusor.h" 11 | #include "absl/strings/string_view.h" 12 | #include "fpga/database-parsers.h" 13 | #include "fpga/xilinx/arch-types.h" 14 | #include "fpga/xilinx/bitstream-writer.h" 15 | #include "fpga/xilinx/configuration.h" 16 | #include "fpga/xilinx/frames.h" 17 | 18 | namespace fpga { 19 | namespace xilinx { 20 | template 21 | class BitStream { 22 | private: 23 | using ArchType = ArchitectureType; 24 | using FrameWords = ArchType::FrameWords; 25 | using FrameAddress = ArchType::FrameAddress; 26 | using ConfigurationPackage = ArchType::ConfigurationPackage; 27 | using Part = ArchType::Part; 28 | using FramesType = Frames; 29 | using ConfigurationType = Configuration; 30 | using BitstreamWriterType = BitstreamWriter; 31 | 32 | public: 33 | template 34 | static absl::Status Encode(const fpga::Part &part, 35 | absl::string_view part_name, 36 | absl::string_view source_name, 37 | const FramesData &frames_data, std::ostream &out) { 38 | absl::btree_map converted_frames; 39 | for (const auto &address_words_pair : frames_data) { 40 | converted_frames.emplace(address_words_pair.first, 41 | address_words_pair.second); 42 | } 43 | constexpr absl::string_view kGeneratorName = "fpga-assembler"; 44 | FramesType frames(converted_frames); 45 | absl::StatusOr xilinx_part_status = Part::FromPart(part); 46 | if (!xilinx_part_status.ok()) { 47 | return xilinx_part_status.status(); 48 | } 49 | std::optional xilinx_part = xilinx_part_status.value(); 50 | frames.AddMissingFrames(xilinx_part); 51 | // Create data for the type 2 configuration packet with 52 | // information about all frames 53 | typename ConfigurationType::PacketData configuration_packet_data( 54 | ConfigurationType::CreateType2ConfigurationPacketData(frames.GetFrames(), 55 | xilinx_part)); 56 | 57 | // Put together a configuration package 58 | ConfigurationPackage configuration_package; 59 | ConfigurationType::CreateConfigurationPackage( 60 | configuration_package, configuration_packet_data, xilinx_part); 61 | 62 | // Write bitstream 63 | auto bitstream_writer = BitstreamWriterType(configuration_package); 64 | if (bitstream_writer.writeBitstream( 65 | configuration_package, std::string(part_name), 66 | std::string(source_name), std::string(kGeneratorName), out)) { 67 | return absl::InternalError("failed generating bitstream"); 68 | } 69 | return absl::OkStatus(); 70 | } 71 | }; 72 | } // namespace xilinx 73 | } // namespace fpga 74 | #endif // FPGA_XILINX_BITSTREAM_H 75 | -------------------------------------------------------------------------------- /fpga/xilinx/configuration-packet.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #ifndef FPGA_XILINX_CONFIGURATION_PACKET_H 11 | #define FPGA_XILINX_CONFIGURATION_PACKET_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "absl/types/optional.h" 22 | #include "absl/types/span.h" 23 | 24 | namespace fpga { 25 | namespace xilinx { 26 | // As described in the configuration user guide for Series-7 27 | // (UG470, pg. 108) there are two types of configuration packets 28 | enum class ConfigurationPacketType : uint32_t { kNONE, kTYPE1, kTYPE2 }; 29 | 30 | // Creates a Type1 or Type2 configuration packet. 31 | // Specification of the packets for Series-7 can be found in UG470, pg. 108 32 | // For Spartan6 UG380, pg. 98 33 | template 34 | class ConfigurationPacketBase { 35 | public: 36 | using ParseResult = std::pair, absl::optional>; 37 | 38 | // Opcodes as specified in UG470 page 108 39 | enum class Opcode { 40 | kNOP = 0, 41 | kRead = 1, 42 | kWrite = 2, 43 | // reserved = 3 44 | }; 45 | 46 | ConfigurationPacketBase(uint32_t header_type, Opcode opcode, 47 | ConfigRegType address, 48 | const absl::Span &data) 49 | : header_type_(header_type), 50 | opcode_(opcode), 51 | address_(address), 52 | data_(data) {} 53 | 54 | unsigned int header_type() const { return header_type_; } 55 | Opcode opcode() const { return opcode_; } 56 | ConfigRegType address() const { return address_; } 57 | const absl::Span &data() const { return data_; } 58 | static ParseResult InitWithWords(absl::Span words, 59 | const Derived *previous_packet = nullptr) { 60 | return Derived::InitWithWordsImpl(words, previous_packet); 61 | } 62 | 63 | private: 64 | unsigned int header_type_; 65 | Opcode opcode_; 66 | ConfigRegType address_; 67 | absl::Span data_; 68 | }; 69 | 70 | template 71 | inline std::ostream &operator<<( 72 | std::ostream &o, 73 | const ConfigurationPacketBase &packet) { 74 | if (packet.header_type() == 0x0) { 75 | return o << "[Zero-pad]" << '\n'; 76 | } 77 | using Opcode = ConfigurationPacketBase::Opcode; 78 | switch (packet.opcode()) { 79 | case Opcode::NOP: o << "[NOP]" << '\n'; break; 80 | case Opcode::Read: 81 | o << "[Read Type="; 82 | o << packet.header_type(); 83 | o << " Address="; 84 | o << std::setw(2) << std::hex; 85 | o << static_cast(packet.address()); 86 | o << " Length="; 87 | o << std::setw(10) << std::dec << packet.data().size(); 88 | o << " Reg=\"" << packet.address() << "\""; 89 | o << "]" << '\n'; 90 | break; 91 | case Opcode::Write: 92 | o << "[Write Type="; 93 | o << packet.header_type(); 94 | o << " Address="; 95 | o << std::setw(2) << std::hex; 96 | o << static_cast(packet.address()); 97 | o << " Length="; 98 | o << std::setw(10) << std::dec << packet.data().size(); 99 | o << " Reg=\"" << packet.address() << "\""; 100 | o << "]" << '\n'; 101 | o << "Data in hex:" << '\n'; 102 | 103 | for (size_t ii = 0; ii < packet.data().size(); ++ii) { 104 | o << std::setw(8) << std::hex; 105 | o << packet.data()[ii] << " "; 106 | 107 | if ((ii + 1) % 4 == 0) { 108 | o << '\n'; 109 | } 110 | } 111 | if (packet.data().size() % 4 != 0) { 112 | o << '\n'; 113 | } 114 | break; 115 | default: o << "[Invalid Opcode]" << '\n'; 116 | } 117 | return o; 118 | } 119 | 120 | template 121 | class ConfigurationPacketWithPayload : public ConfigurationPacket { 122 | private: 123 | using ConfRegType = ConfigurationPacket::ConfRegType; 124 | 125 | public: 126 | ConfigurationPacketWithPayload(ConfigurationPacket::Opcode op, 127 | ConfRegType reg, 128 | const std::array &payload) 129 | : ConfigurationPacket( 130 | static_cast(ConfigurationPacketType::kTYPE1), op, reg, 131 | absl::Span(payload_)), 132 | payload_(std::move(payload)) {} 133 | 134 | private: 135 | std::array payload_; 136 | }; 137 | 138 | template 139 | class NopPacket : public ConfigurationPacket { 140 | public: 141 | using ConfRegType = ConfigurationPacket::ConfRegType; 142 | NopPacket() 143 | : ConfigurationPacket( 144 | static_cast(ConfigurationPacketType::kTYPE1), 145 | ConfigurationPacket::Opcode::kNOP, ConfRegType::kCRC, {}) {} 146 | }; 147 | } // namespace xilinx 148 | } // namespace fpga 149 | #endif // FPGA_XILINX_CONFIGURATION_PACKET_H 150 | -------------------------------------------------------------------------------- /fpga/xilinx/configuration-xc7_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "absl/log/check.h" 18 | #include "absl/status/statusor.h" 19 | #include "absl/types/optional.h" 20 | #include "absl/types/span.h" 21 | #include "fpga/database-parsers.h" 22 | #include "fpga/memory-mapped-file.h" 23 | #include "fpga/xilinx/arch-types.h" 24 | #include "fpga/xilinx/arch-xc7-frame.h" 25 | #include "fpga/xilinx/bitstream-reader.h" 26 | #include "fpga/xilinx/configuration-packet.h" 27 | #include "fpga/xilinx/configuration.h" 28 | #include "fpga/xilinx/frames.h" 29 | #include "gtest/gtest.h" 30 | 31 | namespace fpga { 32 | namespace xilinx { 33 | namespace { 34 | constexpr fpga::xilinx::Architecture kArch = fpga::xilinx::Architecture::kXC7; 35 | using ArchType = ArchitectureType; 36 | using ConfigurationPacket = ArchType::ConfigurationPacket; 37 | using ConfigurationRegister = ArchType::ConfigurationRegister; 38 | using FrameAddress = ArchType::FrameAddress; 39 | using Part = ArchType::Part; 40 | using FrameWords = ArchType::FrameWords; 41 | 42 | TEST(XC7ConfigurationTest, ConstructFromPacketsWithSingleFrame) { 43 | std::vector test_part_addresses; 44 | test_part_addresses.emplace_back(static_cast(0x4567)); 45 | test_part_addresses.emplace_back(static_cast(0x4568)); 46 | 47 | Part const test_part(0x1234, test_part_addresses); 48 | 49 | std::vector idcode{0x1234}; 50 | std::vector cmd{0x0001}; 51 | std::vector frame_address{0x4567}; 52 | std::vector frame(101, 0xAA); 53 | 54 | std::vector packets{ 55 | { 56 | static_cast(0x1), 57 | ConfigurationPacket::Opcode::kWrite, 58 | ConfigurationRegister::kIDCODE, 59 | absl::MakeSpan(idcode), 60 | }, 61 | { 62 | static_cast(0x1), 63 | ConfigurationPacket::Opcode::kWrite, 64 | ConfigurationRegister::kFAR, 65 | absl::MakeSpan(frame_address), 66 | }, 67 | { 68 | static_cast(0x1), 69 | ConfigurationPacket::Opcode::kWrite, 70 | ConfigurationRegister::kCMD, 71 | absl::MakeSpan(cmd), 72 | }, 73 | { 74 | static_cast(0x1), 75 | ConfigurationPacket::Opcode::kWrite, 76 | ConfigurationRegister::kFDRI, 77 | absl::MakeSpan(frame), 78 | }, 79 | }; 80 | 81 | auto test_config = 82 | Configuration::InitWithPackets(test_part, packets); 83 | ASSERT_TRUE(test_config); 84 | // NOLINTBEGIN(bugprone-unchecked-optional-access) 85 | EXPECT_EQ(test_config->part().idcode(), static_cast(0x1234)); 86 | EXPECT_EQ(test_config->frames().size(), static_cast(1)); 87 | EXPECT_EQ(test_config->frames().at(FrameAddress(0x4567)), frame); 88 | // NOLINTEND(bugprone-unchecked-optional-access) 89 | } 90 | 91 | TEST(XC7ConfigurationTest, ConstructFromPacketsWithAutoincrement) { 92 | std::vector test_part_addresses; 93 | for (int ii = 0x4560; ii < 0x4570; ++ii) { 94 | test_part_addresses.emplace_back(static_cast(ii)); 95 | } 96 | for (int ii = 0x4580; ii < 0x4590; ++ii) { 97 | test_part_addresses.emplace_back(static_cast(ii)); 98 | } 99 | 100 | Part const test_part(0x1234, test_part_addresses); 101 | 102 | std::vector idcode{0x1234}; 103 | std::vector cmd{0x0001}; 104 | std::vector frame_address{0x456F}; 105 | std::vector frame(202, 0xAA); 106 | std::fill_n(frame.begin() + 101, 101, 0xBB); 107 | 108 | std::vector packets{ 109 | { 110 | static_cast(0x1), 111 | ConfigurationPacket::Opcode::kWrite, 112 | ConfigurationRegister::kIDCODE, 113 | absl::MakeSpan(idcode), 114 | }, 115 | { 116 | static_cast(0x1), 117 | ConfigurationPacket::Opcode::kWrite, 118 | ConfigurationRegister::kFAR, 119 | absl::MakeSpan(frame_address), 120 | }, 121 | { 122 | static_cast(0x1), 123 | ConfigurationPacket::Opcode::kWrite, 124 | ConfigurationRegister::kCMD, 125 | absl::MakeSpan(cmd), 126 | }, 127 | { 128 | static_cast(0x1), 129 | ConfigurationPacket::Opcode::kWrite, 130 | ConfigurationRegister::kFDRI, 131 | absl::MakeSpan(frame), 132 | }, 133 | }; 134 | 135 | auto test_config = 136 | Configuration::InitWithPackets(test_part, packets); 137 | ASSERT_TRUE(test_config); 138 | // NOLINTBEGIN(bugprone-unchecked-optional-access) 139 | absl::Span const frame_span(frame); 140 | EXPECT_EQ(test_config->part().idcode(), static_cast(0x1234)); 141 | EXPECT_EQ(test_config->frames().size(), static_cast(2)); 142 | EXPECT_EQ(test_config->frames().at(FrameAddress(0x456F)), 143 | std::vector(101, 0xAA)); 144 | EXPECT_EQ(test_config->frames().at(FrameAddress(0x4580)), 145 | std::vector(101, 0xBB)); 146 | // NOLINTEND(bugprone-unchecked-optional-access) 147 | } 148 | 149 | // Load Part from JSON. 150 | absl::StatusOr LoadPartJSON(const std::filesystem::path &path) { 151 | const auto block_status = MemoryMapFile(path); 152 | if (!block_status.ok()) { 153 | return block_status.status(); 154 | } 155 | const auto part_status = ParsePartJSON(block_status.value()->AsStringView()); 156 | if (!part_status.ok()) { 157 | return part_status.status(); 158 | } 159 | return Part::FromPart(part_status.value()); 160 | } 161 | 162 | TEST(XC7ConfigurationTest, 163 | DebugAndPerFrameCrcBitstreamsProduceEqualConfigurations) { 164 | const std::filesystem::path kTestDataBase = "fpga/xilinx/testdata"; 165 | auto part = LoadPartJSON(kTestDataBase / "xc7-configuration-test.json"); 166 | ASSERT_TRUE(part.ok()) << part.status(); 167 | 168 | absl::StatusOr> debug_bitstream_status = 169 | MemoryMapFile(kTestDataBase / "xc7-configuration.debug.bit"); 170 | ASSERT_TRUE(debug_bitstream_status.ok()) << debug_bitstream_status.status(); 171 | auto &debug_bitstream = debug_bitstream_status.value(); 172 | CHECK(debug_bitstream); 173 | 174 | auto debug_reader = 175 | BitstreamReader::InitWithBytes(debug_bitstream->AsBytesView()); 176 | CHECK(debug_reader.has_value()); 177 | auto debug_configuration = 178 | Configuration::InitWithPackets(*part, *debug_reader); 179 | CHECK(debug_configuration.has_value()); 180 | 181 | absl::StatusOr> perframecrc_bitstream_status = 182 | MemoryMapFile(kTestDataBase / "xc7-configuration.perframecrc.bit"); 183 | ASSERT_TRUE(perframecrc_bitstream_status.ok()) 184 | << perframecrc_bitstream_status.status(); 185 | auto &perframecrc_bitstream = perframecrc_bitstream_status.value(); 186 | ASSERT_TRUE(perframecrc_bitstream); 187 | 188 | auto perframecrc_reader = 189 | BitstreamReader::InitWithBytes(perframecrc_bitstream->AsBytesView()); 190 | CHECK(perframecrc_reader.has_value()); 191 | auto perframecrc_configuration = 192 | Configuration::InitWithPackets(*part, *perframecrc_reader); 193 | CHECK(perframecrc_configuration.has_value()); 194 | 195 | for (auto debug_frame : debug_configuration->frames()) { 196 | auto perframecrc_frame = 197 | perframecrc_configuration->frames().find(debug_frame.first); 198 | if (perframecrc_frame == perframecrc_configuration->frames().end()) { 199 | ADD_FAILURE() << debug_frame.first 200 | << ": missing in perframecrc bitstream"; 201 | continue; 202 | } 203 | 204 | for (int ii = 0; ii < 101; ++ii) { 205 | EXPECT_EQ(perframecrc_frame->second[ii], debug_frame.second[ii]) 206 | << debug_frame.first << ": word " << ii; 207 | } 208 | } 209 | for (auto perframecrc_frame : perframecrc_configuration->frames()) { 210 | auto debug_frame = 211 | debug_configuration->frames().find(perframecrc_frame.first); 212 | if (debug_frame == debug_configuration->frames().end()) { 213 | ADD_FAILURE() << perframecrc_frame.first 214 | << ": unexpectedly present in " 215 | "perframecrc bitstream"; 216 | } 217 | } 218 | } 219 | 220 | TEST(XC7ConfigurationTest, DebugAndNormalBitstreamsProduceEqualConfigurations) { 221 | const std::filesystem::path kTestDataBase = "fpga/xilinx/testdata"; 222 | auto part = LoadPartJSON(kTestDataBase / "xc7-configuration-test.json"); 223 | ASSERT_TRUE(part.ok()) << part.status(); 224 | 225 | absl::StatusOr> debug_bitstream_status = 226 | MemoryMapFile(kTestDataBase / "xc7-configuration.debug.bit"); 227 | ASSERT_TRUE(debug_bitstream_status.ok()) << debug_bitstream_status.status(); 228 | auto &debug_bitstream = debug_bitstream_status.value(); 229 | CHECK(debug_bitstream); 230 | 231 | auto debug_reader = 232 | BitstreamReader::InitWithBytes(debug_bitstream->AsBytesView()); 233 | CHECK(debug_reader.has_value()); 234 | auto debug_configuration = 235 | Configuration::InitWithPackets(*part, *debug_reader); 236 | CHECK(debug_configuration.has_value()); 237 | 238 | absl::StatusOr> normal_bitstream_status = 239 | MemoryMapFile(kTestDataBase / "xc7-configuration.bit"); 240 | ASSERT_TRUE(normal_bitstream_status.ok()) << normal_bitstream_status.status(); 241 | auto &normal_bitstream = normal_bitstream_status.value(); 242 | ASSERT_TRUE(normal_bitstream); 243 | 244 | auto normal_reader = 245 | BitstreamReader::InitWithBytes(normal_bitstream->AsBytesView()); 246 | CHECK(normal_reader.has_value()); 247 | auto normal_configuration = 248 | Configuration::InitWithPackets(*part, *normal_reader); 249 | CHECK(normal_configuration.has_value()); 250 | for (const auto &debug_frame : debug_configuration->frames()) { 251 | const auto normal_frame = 252 | normal_configuration->frames().find(debug_frame.first); 253 | if (normal_frame == normal_configuration->frames().end()) { 254 | ADD_FAILURE() << debug_frame.first << ": missing in normal bitstream"; 255 | continue; 256 | } 257 | 258 | for (int ii = 0; ii < 101; ++ii) { 259 | EXPECT_EQ(normal_frame->second[ii], debug_frame.second[ii]) 260 | << debug_frame.first << ": word " << ii; 261 | } 262 | } 263 | for (const auto &normal_frame : normal_configuration->frames()) { 264 | auto debug_frame = debug_configuration->frames().find(normal_frame.first); 265 | if (debug_frame == debug_configuration->frames().end()) { 266 | ADD_FAILURE() << normal_frame.first 267 | << ": unexpectedly present in normal bitstream"; 268 | } 269 | } 270 | } 271 | 272 | template 273 | T Fill(typename T::value_type value) { 274 | T out; 275 | out.fill(value); 276 | return out; 277 | } 278 | 279 | TEST(XC7ConfigurationTest, CheckForPaddingFrames) { 280 | std::vector test_part_addresses = { 281 | FrameAddress(xc7::BlockType::kCLBIOCLK, false, 0, 0, 0), 282 | FrameAddress(xc7::BlockType::kCLBIOCLK, true, 0, 0, 0), 283 | FrameAddress(xc7::BlockType::kCLBIOCLK, true, 1, 0, 0), 284 | FrameAddress(xc7::BlockType::kBlockRam, false, 0, 0, 0), 285 | FrameAddress(xc7::BlockType::kBlockRam, false, 1, 0, 0)}; 286 | 287 | auto test_part = absl::optional(Part(0x1234, test_part_addresses)); 288 | 289 | Frames frames; 290 | frames.GetFrames().emplace(FrameAddress(test_part_addresses.at(0)), 291 | Fill(0xAA)); 292 | frames.GetFrames().emplace(FrameAddress(test_part_addresses.at(1)), 293 | Fill(0xBB)); 294 | frames.GetFrames().emplace(FrameAddress(test_part_addresses.at(2)), 295 | Fill(0xCC)); 296 | frames.GetFrames().emplace(FrameAddress(test_part_addresses.at(3)), 297 | Fill(0xDD)); 298 | frames.GetFrames().emplace(FrameAddress(test_part_addresses.at(4)), 299 | Fill(0xEE)); 300 | ASSERT_EQ(frames.GetFrames().size(), 5); 301 | 302 | Configuration::PacketData packet_data = 303 | Configuration::CreateType2ConfigurationPacketData( 304 | frames.GetFrames(), test_part); 305 | // createType2ConfigurationPacketData should detect 4 306 | // row/half/block_type switches thus add 4*2 padding frames moreover 2 307 | // extra padding frames are added at the end of the creation of the data 308 | // overall this gives us: 5(real frames) + 4*2 + 2 = 15 frames, which is 309 | // 15 * 101 = 1515 words 310 | EXPECT_EQ(packet_data.size(), 15 * 101); 311 | 312 | std::vector idcode{0x1234}; 313 | std::vector cmd{0x0001}; 314 | std::vector frame_address{0x0}; 315 | 316 | std::vector packets{ 317 | { 318 | static_cast(0x1), 319 | ConfigurationPacket::Opcode::kWrite, 320 | ConfigurationRegister::kIDCODE, 321 | absl::MakeSpan(idcode), 322 | }, 323 | { 324 | static_cast(0x1), 325 | ConfigurationPacket::Opcode::kWrite, 326 | ConfigurationRegister::kFAR, 327 | absl::MakeSpan(frame_address), 328 | }, 329 | { 330 | static_cast(0x1), 331 | ConfigurationPacket::Opcode::kWrite, 332 | ConfigurationRegister::kCMD, 333 | absl::MakeSpan(cmd), 334 | }, 335 | { 336 | static_cast(0x1), 337 | ConfigurationPacket::Opcode::kWrite, 338 | ConfigurationRegister::kFDRI, 339 | absl::MakeSpan(packet_data), 340 | }, 341 | }; 342 | 343 | auto test_config = 344 | Configuration::InitWithPackets(*test_part, packets); 345 | CHECK(test_config.has_value()); 346 | ASSERT_EQ(test_config->frames().size(), 5); 347 | for (const auto &frame : test_config->frames()) { 348 | EXPECT_EQ(frame.second, frames.GetFrames().at(frame.first)); 349 | } 350 | } 351 | } // namespace 352 | } // namespace xilinx 353 | } // namespace fpga 354 | -------------------------------------------------------------------------------- /fpga/xilinx/configuration.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | 11 | #include "fpga/xilinx/configuration.h" 12 | 13 | #include 14 | 15 | #include "absl/log/check.h" 16 | #include "absl/types/optional.h" 17 | #include "fpga/xilinx/arch-types.h" 18 | #include "fpga/xilinx/arch-xc7-configuration-packet.h" 19 | #include "fpga/xilinx/configuration-packet.h" 20 | 21 | namespace fpga { 22 | namespace xilinx { 23 | template <> 24 | void Configuration::CreateConfigurationPackage( 25 | ConfigurationPackage &out_packets, const PacketData &packet_data, 26 | absl::optional &part) { 27 | // Initialization sequence 28 | out_packets.emplace_back(new NopPacket()); 29 | out_packets.emplace_back( 30 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 31 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kTIMER, 32 | {0x0})); 33 | out_packets.emplace_back( 34 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 35 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kWBSTAR, 36 | {0x0})); 37 | out_packets.emplace_back( 38 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 39 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCMD, 40 | {static_cast(xc7::Command::kNOP)})); 41 | out_packets.emplace_back(new NopPacket()); 42 | out_packets.emplace_back( 43 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 44 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCMD, 45 | {static_cast(xc7::Command::kRCRC)})); 46 | out_packets.emplace_back(new NopPacket()); 47 | out_packets.emplace_back(new NopPacket()); 48 | out_packets.emplace_back( 49 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 50 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kUNKNOWN, 51 | {0x0})); 52 | 53 | // Configuration Options 0 54 | out_packets.emplace_back( 55 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 56 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCOR0, 57 | {static_cast( 58 | xc7::ConfigurationOptions0Value() 59 | .SetAddPipelineStageForDoneIn(true) 60 | .SetReleaseDonePinAtStartupCycle( 61 | xc7::ConfigurationOptions0Value::SignalReleaseCycle::Phase4) 62 | .SetStallAtStartupCycleUntilDciMatch( 63 | xc7::ConfigurationOptions0Value::StallCycle::NoWait) 64 | .SetStallAtStartupCycleUntilMmcmLock( 65 | xc7::ConfigurationOptions0Value::StallCycle::NoWait) 66 | .SetReleaseGtsSignalAtStartupCycle( 67 | xc7::ConfigurationOptions0Value::SignalReleaseCycle::Phase5) 68 | .SetReleaseGweSignalAtStartupCycle( 69 | xc7::ConfigurationOptions0Value::SignalReleaseCycle::Phase6))})); 70 | 71 | out_packets.emplace_back( 72 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 73 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCOR1, 74 | {0x0})); 75 | CHECK(part.has_value()); 76 | out_packets.emplace_back( 77 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 78 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kIDCODE, 79 | {part->idcode()})); 80 | out_packets.emplace_back( 81 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 82 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCMD, 83 | {static_cast(xc7::Command::kSWITCH)})); 84 | out_packets.emplace_back(new NopPacket()); 85 | out_packets.emplace_back( 86 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 87 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kMASK, 88 | {0x401})); 89 | out_packets.emplace_back( 90 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 91 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCTL0, 92 | {0x501})); 93 | out_packets.emplace_back( 94 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 95 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kMASK, 96 | {0x0})); 97 | out_packets.emplace_back( 98 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 99 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCTL1, 100 | {0x0})); 101 | out_packets.emplace_back(new NopPacket()); 102 | out_packets.emplace_back(new NopPacket()); 103 | out_packets.emplace_back(new NopPacket()); 104 | out_packets.emplace_back(new NopPacket()); 105 | out_packets.emplace_back(new NopPacket()); 106 | out_packets.emplace_back(new NopPacket()); 107 | out_packets.emplace_back(new NopPacket()); 108 | out_packets.emplace_back(new NopPacket()); 109 | out_packets.emplace_back( 110 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 111 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kFAR, {0x0})); 112 | out_packets.emplace_back( 113 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 114 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCMD, 115 | {static_cast(xc7::Command::kWCFG)})); 116 | out_packets.emplace_back(new NopPacket()); 117 | 118 | // Frame data write 119 | out_packets.emplace_back(new ConfigurationPacket( 120 | static_cast(ConfigurationPacketType::kTYPE1), 121 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kFDRI, {})); 122 | out_packets.emplace_back(new ConfigurationPacket( 123 | static_cast(ConfigurationPacketType::kTYPE2), 124 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kFDRI, 125 | packet_data)); 126 | 127 | // Finalization sequence 128 | out_packets.emplace_back( 129 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 130 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCMD, 131 | {static_cast(xc7::Command::kRCRC)})); 132 | out_packets.emplace_back(new NopPacket()); 133 | out_packets.emplace_back(new NopPacket()); 134 | out_packets.emplace_back( 135 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 136 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCMD, 137 | {static_cast(xc7::Command::kGRESTORE)})); 138 | out_packets.emplace_back(new NopPacket()); 139 | out_packets.emplace_back( 140 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 141 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCMD, 142 | {static_cast(xc7::Command::kLFRM)})); 143 | for (int ii = 0; ii < 100; ++ii) { 144 | out_packets.emplace_back(new NopPacket()); 145 | } 146 | out_packets.emplace_back( 147 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 148 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCMD, 149 | {static_cast(xc7::Command::kSTART)})); 150 | out_packets.emplace_back(new NopPacket()); 151 | out_packets.emplace_back( 152 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 153 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kFAR, 154 | {0x3be0000})); 155 | out_packets.emplace_back( 156 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 157 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kMASK, 158 | {0x501})); 159 | out_packets.emplace_back( 160 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 161 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCTL0, 162 | {0x501})); 163 | out_packets.emplace_back( 164 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 165 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCMD, 166 | {static_cast(xc7::Command::kRCRC)})); 167 | out_packets.emplace_back(new NopPacket()); 168 | out_packets.emplace_back(new NopPacket()); 169 | out_packets.emplace_back( 170 | new ConfigurationPacketWithPayload<1, ConfigurationPacket>( 171 | ConfigurationPacket::Opcode::kWrite, ConfigurationRegister::kCMD, 172 | {static_cast(xc7::Command::kDESYNC)})); 173 | for (int ii = 0; ii < 400; ++ii) { 174 | out_packets.emplace_back(new NopPacket()); 175 | } 176 | } 177 | } // namespace xilinx 178 | } // namespace fpga 179 | -------------------------------------------------------------------------------- /fpga/xilinx/configuration.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #ifndef FPGA_XILINX_CONFIGURATION_H 11 | #define FPGA_XILINX_CONFIGURATION_H 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "absl/container/btree_map.h" 21 | #include "absl/types/optional.h" 22 | #include "absl/types/span.h" 23 | #include "fpga/xilinx/arch-types.h" 24 | #include "fpga/xilinx/bit-ops.h" 25 | 26 | namespace fpga { 27 | namespace xilinx { 28 | template 29 | class Configuration { 30 | private: 31 | using ArchType = ArchitectureType; 32 | using FrameWords = ArchType::FrameWords; 33 | using FrameAddress = ArchType::FrameAddress; 34 | using ConfigurationPacket = ArchType::ConfigurationPacket; 35 | using ConfigurationPackage = ArchType::ConfigurationPackage; 36 | using ConfigurationRegister = ArchType::ConfigurationRegister; 37 | using Part = ArchType::Part; 38 | static constexpr auto kWordsPerFrame = std::tuple_size_v; 39 | 40 | public: 41 | using FrameMap = absl::btree_map>; 42 | using PacketData = std::vector; 43 | 44 | // Returns a configuration, i.e. collection of frame addresses 45 | // and corresponding data from a collection of configuration packets. 46 | template 47 | static std::optional> InitWithPackets( 48 | const Part &part, Collection &packets); 49 | 50 | // Creates the complete configuration package which is later on 51 | // used by the bitstream writer to generate the bitstream file. 52 | // The pacakge forms a sequence suitable for Xilinx devices. 53 | // The programming sequence for Series-7 is taken from 54 | // https://www.kc8apf.net/2018/05/unpacking-xilinx-7-series-bitstreams-part-2/ 55 | static void CreateConfigurationPackage(ConfigurationPackage &out_packets, 56 | const PacketData &packet_data, 57 | std::optional &part); 58 | 59 | // Returns the payload for a type 2 packet 60 | // which allows for bigger payload compared to type 1. 61 | static PacketData CreateType2ConfigurationPacketData( 62 | const absl::btree_map &frames, 63 | std::optional &part) { 64 | PacketData packet_data; 65 | // Certain configuration frames blocks are separated by Zero Frames, 66 | // i.e. frames with words with all zeroes. For Series-7, US and US+ 67 | // there zero frames separator consists of two frames. 68 | static const int kZeroFramesSeparatorWords = kWordsPerFrame * 2; 69 | for (auto &frame : frames) { 70 | std::copy(frame.second.begin(), frame.second.end(), 71 | std::back_inserter(packet_data)); 72 | 73 | auto next_address = part->GetNextFrameAddress(frame.first); 74 | if (next_address && 75 | (next_address->block_type() != frame.first.block_type() || 76 | next_address->is_bottom_half_rows() != 77 | frame.first.is_bottom_half_rows() || 78 | next_address->row() != frame.first.row())) { 79 | packet_data.insert(packet_data.end(), kZeroFramesSeparatorWords, 0); 80 | } 81 | } 82 | packet_data.insert(packet_data.end(), kZeroFramesSeparatorWords, 0); 83 | return packet_data; 84 | } 85 | 86 | Configuration(const Part &part, 87 | absl::btree_map &frames) 88 | : part_(part) { 89 | for (auto &frame : frames) { 90 | frames_[frame.first] = absl::Span(frame.second); 91 | } 92 | } 93 | 94 | Configuration(const Part &part, const FrameMap &frames) 95 | : part_(part), frames_(std::move(frames)) {} 96 | 97 | const Part &part() const { return part_; } 98 | const FrameMap &frames() const { return frames_; } 99 | void PrintFrameAddresses(FILE *fp); 100 | 101 | private: 102 | Part part_; 103 | FrameMap frames_; 104 | }; 105 | 106 | template 107 | template 108 | absl::optional> Configuration::InitWithPackets( 109 | const Part &part, Collection &packets) { 110 | // Registers that can be directly written to. 111 | uint32_t command_register = 0; 112 | uint32_t frame_address_register = 0; 113 | uint32_t mask_register = 0; 114 | uint32_t ctl1_register = 0; 115 | 116 | // Internal state machine for writes. 117 | bool start_new_write = false; 118 | FrameAddress current_frame_address = static_cast(0); 119 | 120 | Configuration::FrameMap frames; 121 | for (auto packet : packets) { 122 | if (packet.opcode() != ConfigurationPacket::Opcode::kWrite) { 123 | continue; 124 | } 125 | 126 | switch (packet.address()) { 127 | case ConfigurationRegister::kMASK: 128 | if (packet.data().size() < 1) { 129 | continue; 130 | } 131 | mask_register = packet.data()[0]; 132 | break; 133 | case ConfigurationRegister::kCTL1: 134 | if (packet.data().size() < 1) { 135 | continue; 136 | } 137 | ctl1_register = packet.data()[0] & mask_register; 138 | break; 139 | case ConfigurationRegister::kCMD: 140 | if (packet.data().size() < 1) { 141 | continue; 142 | } 143 | command_register = packet.data()[0]; 144 | // Writes to CMD trigger an immediate action. In 145 | // the case of WCFG, that is just setting a flag 146 | // for the next FDIR. 147 | if (command_register == 0x1) { 148 | start_new_write = true; 149 | } 150 | break; 151 | case ConfigurationRegister::kIDCODE: 152 | // This really should be a one-word write. 153 | if (packet.data().size() < 1) { 154 | continue; 155 | } 156 | 157 | // If the IDCODE doesn't match our expected 158 | // part, consider the bitstream invalid. 159 | if (packet.data()[0] != part.idcode()) { 160 | return {}; 161 | } 162 | break; 163 | case ConfigurationRegister::kFAR: 164 | // This really should be a one-word write. 165 | if (packet.data().size() < 1) { 166 | continue; 167 | } 168 | frame_address_register = static_cast(packet.data()[0]); 169 | 170 | // Per UG470, the command present in the CMD 171 | // register is executed each time the FAR 172 | // register is laoded with a new value. As we 173 | // only care about WCFG commands, just check 174 | // that here. CTRL1 is completely undocumented 175 | // but looking at generated bitstreams, bit 21 176 | // is used when per-frame CRC is enabled. 177 | // Setting this bit seems to inhibit the 178 | // re-execution of CMD during a FAR write. In 179 | // practice, this is used so FAR writes can be 180 | // added in the bitstream to show progress 181 | // markers without impacting the actual write 182 | // operation. 183 | if (bit_field_get(ctl1_register, 21, 21) == 0 && 184 | command_register == 0x1) { 185 | start_new_write = true; 186 | } 187 | break; 188 | case ConfigurationRegister::kFDRI: { 189 | if (start_new_write) { 190 | current_frame_address = 191 | static_cast(frame_address_register); 192 | start_new_write = false; 193 | } 194 | 195 | // Number of words in configuration frames 196 | // depend on tje architecture. Writes to this 197 | // register can be multiples of that number to 198 | // do auto-incrementing block writes. 199 | for (size_t ii = 0; ii < packet.data().size(); ii += kWordsPerFrame) { 200 | frames[current_frame_address] = 201 | packet.data().subspan(ii, kWordsPerFrame); 202 | 203 | auto next_address = part.GetNextFrameAddress(current_frame_address); 204 | if (!next_address) { 205 | break; 206 | } 207 | 208 | // Bitstreams appear to have 2 frames of 209 | // padding between rows. 210 | if (next_address && 211 | (next_address->block_type() != current_frame_address.block_type() || 212 | next_address->is_bottom_half_rows() != 213 | current_frame_address.is_bottom_half_rows() || 214 | next_address->row() != current_frame_address.row())) { 215 | ii += 2 * kWordsPerFrame; 216 | } 217 | current_frame_address = *next_address; 218 | } 219 | break; 220 | } 221 | default: break; 222 | } 223 | } 224 | return Configuration(part, frames); 225 | } 226 | 227 | template 228 | void Configuration::PrintFrameAddresses(FILE *fp) { 229 | fprintf(fp, "Frame addresses in bitstream: "); 230 | for (auto frame = frames_.begin(); frame != frames_.end(); ++frame) { 231 | fprintf(fp, "%08X", (int)frame->first); 232 | if (std::next(frame) != frames_.end()) { 233 | fprintf(fp, " "); 234 | } else { 235 | fprintf(fp, "\n"); 236 | } 237 | } 238 | } 239 | } // namespace xilinx 240 | } // namespace fpga 241 | #endif // FPGA_XILINX_CONFIGURATION_H 242 | -------------------------------------------------------------------------------- /fpga/xilinx/frames-xc7_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #include 11 | 12 | #include "fpga/xilinx/arch-types.h" 13 | #include "fpga/xilinx/arch-xc7-frame.h" 14 | #include "fpga/xilinx/frames.h" 15 | #include "gtest/gtest.h" 16 | 17 | namespace fpga { 18 | namespace xilinx { 19 | namespace xc7 { 20 | namespace { 21 | 22 | template 23 | T Fill(typename T::value_type value) { 24 | T out; 25 | out.fill(value); 26 | return out; 27 | } 28 | 29 | TEST(XC7FramesTest, FillInMissingFrames) { 30 | constexpr fpga::xilinx::Architecture kArch = fpga::xilinx::Architecture::kXC7; 31 | using ArchType = ArchitectureType; 32 | using FrameAddress = ArchType::FrameAddress; 33 | using Part = ArchType::Part; 34 | using FrameWords = ArchType::FrameWords; 35 | 36 | std::vector test_part_addresses = { 37 | FrameAddress(xc7::BlockType::kCLBIOCLK, false, 0, 0, 0), 38 | FrameAddress(xc7::BlockType::kCLBIOCLK, false, 0, 0, 1), 39 | FrameAddress(xc7::BlockType::kCLBIOCLK, false, 0, 0, 2), 40 | FrameAddress(xc7::BlockType::kCLBIOCLK, false, 0, 0, 3), 41 | FrameAddress(xc7::BlockType::kCLBIOCLK, false, 0, 0, 4)}; 42 | 43 | Part test_part(0x1234, test_part_addresses); 44 | Frames frames; 45 | frames.GetFrames().emplace(FrameAddress(2), Fill(0xCC)); 46 | frames.GetFrames().emplace(xc7::FrameAddress(3), Fill(0xDD)); 47 | frames.GetFrames().emplace(xc7::FrameAddress(4), Fill(0xEE)); 48 | 49 | ASSERT_EQ(frames.GetFrames().size(), 3); 50 | EXPECT_EQ(frames.GetFrames().at(test_part_addresses[2]), 51 | Fill(0xCC)); 52 | EXPECT_EQ(frames.GetFrames().at(test_part_addresses[3]), 53 | Fill(0xDD)); 54 | EXPECT_EQ(frames.GetFrames().at(test_part_addresses[4]), 55 | Fill(0xEE)); 56 | 57 | frames.AddMissingFrames(test_part); 58 | 59 | ASSERT_EQ(frames.GetFrames().size(), 5); 60 | EXPECT_EQ(frames.GetFrames().at(test_part_addresses[0]), Fill(0)); 61 | EXPECT_EQ(frames.GetFrames().at(test_part_addresses[1]), Fill(0)); 62 | EXPECT_EQ(frames.GetFrames().at(test_part_addresses[2]), 63 | Fill(0xCC)); 64 | EXPECT_EQ(frames.GetFrames().at(test_part_addresses[3]), 65 | Fill(0xDD)); 66 | EXPECT_EQ(frames.GetFrames().at(test_part_addresses[4]), 67 | Fill(0xEE)); 68 | } 69 | } // namespace 70 | } // namespace xc7 71 | } // namespace xilinx 72 | } // namespace fpga 73 | -------------------------------------------------------------------------------- /fpga/xilinx/frames.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2020 The Project X-Ray Authors. 3 | * 4 | * Use of this source code is governed by a ISC-style 5 | * license that can be found in the LICENSE file or at 6 | * https://opensource.org/licenses/ISC 7 | * 8 | * SPDX-License-Identifier: ISC 9 | */ 10 | #ifndef FPGA_XILINX_FRAMES_H 11 | #define FPGA_XILINX_FRAMES_H 12 | 13 | #include 14 | 15 | #include "fpga/xilinx/arch-types.h" 16 | 17 | namespace fpga { 18 | namespace xilinx { 19 | // Contains frame information which is used for the generation 20 | // of the configuration package that is used in bitstream generation. 21 | template 22 | class Frames { 23 | public: 24 | using ArchType = ArchitectureType; 25 | using FrameWords = ArchType::FrameWords; 26 | using FrameAddress = ArchType::FrameAddress; 27 | using Part = ArchType::Part; 28 | 29 | void AddFrame(FrameAddress address, FrameWords words) { 30 | UpdateECC(data_); 31 | data_.insert(address, words); 32 | } 33 | 34 | Frames() = default; 35 | explicit Frames(absl::btree_map data) 36 | : data_(std::move(data)) {} 37 | 38 | // Adds empty frames that are present in the tilegrid of a specific part 39 | // but are missing in the current frames container. 40 | void AddMissingFrames(const std::optional &part); 41 | 42 | // Returns the map with frame addresses and corresponding data 43 | absl::btree_map &GetFrames() { return data_; } 44 | 45 | private: 46 | absl::btree_map data_; 47 | 48 | // Updates the ECC information in the frame 49 | void UpdateECC(FrameWords &words); 50 | }; 51 | 52 | template 53 | void Frames::AddMissingFrames(const std::optional &part) { 54 | auto current_frame_address = std::optional(FrameAddress(0)); 55 | do { 56 | auto iter = data_.find(*current_frame_address); 57 | if (iter == data_.end()) { 58 | data_.insert({*current_frame_address, {}}); 59 | } 60 | current_frame_address = part->GetNextFrameAddress(*current_frame_address); 61 | } while (current_frame_address); 62 | } 63 | } // namespace xilinx 64 | } // namespace fpga 65 | #endif // FPGA_XILINX_FRAMES_H 66 | -------------------------------------------------------------------------------- /fpga/xilinx/testdata/xc7-configuration.bit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lromor/fpga-assembler/a7181e1bd91b09f308947dd3e12ea5f07a24ba40/fpga/xilinx/testdata/xc7-configuration.bit -------------------------------------------------------------------------------- /fpga/xilinx/testdata/xc7-configuration.debug.bit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lromor/fpga-assembler/a7181e1bd91b09f308947dd3e12ea5f07a24ba40/fpga/xilinx/testdata/xc7-configuration.debug.bit -------------------------------------------------------------------------------- /fpga/xilinx/testdata/xc7-configuration.perframecrc.bit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lromor/fpga-assembler/a7181e1bd91b09f308947dd3e12ea5f07a24ba40/fpga/xilinx/testdata/xc7-configuration.perframecrc.bit -------------------------------------------------------------------------------- /scripts/before-submit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Good to run before each submit to catch or repair issues. 4 | 5 | cd $(dirname $0)/.. 6 | 7 | set -e 8 | 9 | . <(scripts/run-build-cleaner.sh ...) # auto-repair issues 10 | scripts/run-clang-format.sh 11 | bazel build -c opt ... 12 | bazel test -c opt ... 13 | scripts/make-compilation-db.sh 14 | sh scripts/run-clang-tidy-cached.cc 15 | -------------------------------------------------------------------------------- /scripts/create-workspace-status.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | version_from_git() { 4 | set -o pipefail 5 | git describe --match=v* 2>/dev/null \ 6 | | sed 's/v\([^-]*\)-\([0-9]*\).*/\1-\2/' \ 7 | | sed 's/^v//' 8 | } 9 | 10 | version_from_module_bazel() { 11 | awk '/module/ { in_module=1; } 12 | /version/ { if (in_module) print $0; } 13 | /)/ { in_module=0; }' $(dirname $0)/../MODULE.bazel \ 14 | | sed 's/.*version[ ]*=[ ]*"\([0-9.]*\)".*/\1/p;d' 15 | } 16 | 17 | # Get version from git including everything since last tag, but if that is 18 | # not available, just fall back to the version we get from module bazel. 19 | echo "BUILD_VERSION \"$(version_from_git || version_from_module_bazel)\"" 20 | -------------------------------------------------------------------------------- /scripts/get-bant-path.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Print path to a bant binary. Can be provided by an environment variable 4 | # or built from our dependency. 5 | 6 | BAZEL=${BAZEL:-bazel} 7 | BANT=${BANT:-needs-to-be-compiled-locally} 8 | 9 | # Bant not given, compile from bzlmod dep. 10 | if [ "${BANT}" = "needs-to-be-compiled-locally" ]; then 11 | "${BAZEL}" build --noshow_progress -c opt @bant//bant:bant 2>/dev/null 12 | BANT=$(realpath bazel-bin/external/bant*/bant/bant | head -1) 13 | fi 14 | 15 | echo $BANT 16 | -------------------------------------------------------------------------------- /scripts/make-compilation-db.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -u 4 | set -e 5 | 6 | # Which bazel and bant to use. If unset, defaults are used. 7 | BAZEL=${BAZEL:-bazel} 8 | BANT=$($(dirname $0)/get-bant-path.sh) 9 | 10 | # Important to run with --remote_download_outputs=all to make sure generated 11 | # files are actually visible locally in case a remote cache (that includes 12 | # --disk_cache) is used ( https://github.com/hzeller/bazel-gen-file-issue ) 13 | BAZEL_OPTS="-c opt --remote_download_outputs=all" 14 | 15 | # Tickle some build targets to fetch all dependencies and generate files, 16 | # so that they can be seen by the users of the compilation db. 17 | "${BAZEL}" build ${BAZEL_OPTS} @googletest//:has_absl @rapidjson 18 | 19 | # Create compilation DB. Command 'compilation-db' creates a huge *.json file, 20 | # but compile_flags.txt is perfectly sufficient and easier for tools to use. 21 | "${BANT}" compile-flags > compile_flags.txt 22 | 23 | # If there are two styles of comp-dbs, tools might have issues. Warn user. 24 | if [ -r compile_commands.json ]; then 25 | echo -e "\n\033[1;31mSuggest to remove old compile_commands.json to not interfere with compile_flags.txt\033[0m\n" 26 | fi 27 | -------------------------------------------------------------------------------- /scripts/run-build-cleaner.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -u 4 | 5 | readonly BANT_EXIT_ON_DWYU_ISSUES=3 6 | 7 | # Which bazel and bant to use can be chosen by environment variables 8 | BAZEL=${BAZEL:-bazel} 9 | BANT=$($(dirname $0)/get-bant-path.sh) 10 | 11 | # Run depend-on-what-you-use build-cleaner. 12 | # Print buildifier commands to fix if needed. 13 | "${BANT}" dwyu "$@" 14 | 15 | BANT_EXIT=$? 16 | if [ ${BANT_EXIT} -eq ${BANT_EXIT_ON_DWYU_ISSUES} ]; then 17 | cat >&2 </dev/null; then 19 | echo "Run $(buildifier --version)" 20 | ${BUILDIFIER} -lint=fix MODULE.bazel $(find . -name BUILD -o -name "*.bzl") 21 | fi 22 | 23 | # If in CI, we want to report changes, as we don't expect them 24 | if [ "$RUNNING_IN_CI" -eq 1 ]; then 25 | # Check if we got any diff 26 | git diff > ${FORMAT_OUT} 27 | 28 | if [ -s ${FORMAT_OUT} ]; then 29 | echo "Style not matching" 30 | echo "Run" 31 | echo " scripts/run-clang-format.sh" 32 | echo "and amend PR." 33 | echo "-------------------------------------------------" 34 | echo 35 | cat ${FORMAT_OUT} 36 | exit 1 37 | fi 38 | fi 39 | 40 | exit 0 41 | --------------------------------------------------------------------------------