├── .gitignore ├── LICENSE ├── README.md ├── build.sbt ├── elk.sh ├── images ├── GCD.svg └── VizModA.svg ├── project ├── build.properties └── plugins.sbt └── src ├── main └── scala │ └── layered │ ├── Elk.scala │ ├── ToLoFirrtl.scala │ ├── elknodes │ ├── ElkNode.scala │ ├── LiteralNode.scala │ ├── MemNode.scala │ ├── ModuleNode.scala │ ├── MuxNode.scala │ ├── NodeNode.scala │ ├── PortNode.scala │ ├── PrimOpNode.scala │ └── RegisterNode.scala │ ├── stage │ ├── ElkException.scala │ ├── ElkPhase.scala │ ├── ElkStage.scala │ ├── cli │ │ ├── ElkAnnotations.scala │ │ └── ElkCli.scala │ └── phase │ │ ├── CheckPhase.scala │ │ ├── GenerateElkFilePhase.scala │ │ ├── GetFirrtlCircuitPhase.scala │ │ └── OptionallyBuildTargetDirPhase.scala │ ├── transforms │ ├── MakeGroup.scala │ └── MakeOne.scala │ └── util │ └── Scope.scala └── test └── scala └── layered ├── AttachExample.scala ├── FirExample.scala ├── GCD.scala └── HierarchicalModulesExample.scala /.gitignore: -------------------------------------------------------------------------------- 1 | ### Project Specific stuff 2 | test_run_dir/* 3 | ### XilinxISE template 4 | # intermediate build files 5 | *.bgn 6 | *.bit 7 | *.bld 8 | *.cmd_log 9 | *.drc 10 | *.ll 11 | *.lso 12 | *.msd 13 | *.msk 14 | *.ncd 15 | *.ngc 16 | *.ngd 17 | *.ngr 18 | *.pad 19 | *.par 20 | *.pcf 21 | *.prj 22 | *.ptwx 23 | *.rbb 24 | *.rbd 25 | *.stx 26 | *.syr 27 | *.twr 28 | *.twx 29 | *.unroutes 30 | *.ut 31 | *.xpi 32 | *.xst 33 | *_bitgen.xwbt 34 | *_envsettings.html 35 | *_map.map 36 | *_map.mrp 37 | *_map.ngm 38 | *_map.xrpt 39 | *_ngdbuild.xrpt 40 | *_pad.csv 41 | *_pad.txt 42 | *_par.xrpt 43 | *_summary.html 44 | *_summary.xml 45 | *_usage.xml 46 | *_xst.xrpt 47 | 48 | # project-wide generated files 49 | *.gise 50 | par_usage_statistics.html 51 | usage_statistics_webtalk.html 52 | webtalk.log 53 | webtalk_pn.xml 54 | 55 | # generated folders 56 | iseconfig/ 57 | xlnx_auto_0_xdb/ 58 | xst/ 59 | _ngo/ 60 | _xmsgs/ 61 | ### Eclipse template 62 | *.pydevproject 63 | .metadata 64 | .gradle 65 | bin/ 66 | tmp/ 67 | *.tmp 68 | *.bak 69 | *.swp 70 | *~.nib 71 | local.properties 72 | .settings/ 73 | .loadpath 74 | 75 | # Eclipse Core 76 | .project 77 | 78 | # External tool builders 79 | .externalToolBuilders/ 80 | 81 | # Locally stored "Eclipse launch configurations" 82 | *.launch 83 | 84 | # CDT-specific 85 | .cproject 86 | 87 | # JDT-specific (Eclipse Java Development Tools) 88 | .classpath 89 | 90 | # Java annotation processor (APT) 91 | .factorypath 92 | 93 | # PDT-specific 94 | .buildpath 95 | 96 | # sbteclipse plugin 97 | .target 98 | 99 | # TeXlipse plugin 100 | .texlipse 101 | ### C template 102 | # Object files 103 | *.o 104 | *.ko 105 | *.obj 106 | *.elf 107 | 108 | # Precompiled Headers 109 | *.gch 110 | *.pch 111 | 112 | # Libraries 113 | *.lib 114 | *.a 115 | *.la 116 | *.lo 117 | 118 | # Shared objects (inc. Windows DLLs) 119 | *.dll 120 | *.so 121 | *.so.* 122 | *.dylib 123 | 124 | # Executables 125 | *.exe 126 | *.out 127 | *.app 128 | *.i*86 129 | *.x86_64 130 | *.hex 131 | 132 | # Debug files 133 | *.dSYM/ 134 | ### SBT template 135 | # Simple Build Tool 136 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control 137 | 138 | target/ 139 | lib_managed/ 140 | src_managed/ 141 | project/boot/ 142 | .history 143 | .cache 144 | ### Emacs template 145 | # -*- mode: gitignore; -*- 146 | *~ 147 | \#*\# 148 | /.emacs.desktop 149 | /.emacs.desktop.lock 150 | *.elc 151 | auto-save-list 152 | tramp 153 | .\#* 154 | 155 | # Org-mode 156 | .org-id-locations 157 | *_archive 158 | 159 | # flymake-mode 160 | *_flymake.* 161 | 162 | # eshell files 163 | /eshell/history 164 | /eshell/lastdir 165 | 166 | # elpa packages 167 | /elpa/ 168 | 169 | # reftex files 170 | *.rel 171 | 172 | # AUCTeX auto folder 173 | /auto/ 174 | 175 | # cask packages 176 | .cask/ 177 | ### Vim template 178 | [._]*.s[a-w][a-z] 179 | [._]s[a-w][a-z] 180 | *.un~ 181 | Session.vim 182 | .netrwhist 183 | *~ 184 | ### JetBrains template 185 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 186 | 187 | *.iml 188 | 189 | ## Directory-based project format: 190 | .idea/ 191 | # if you remove the above rule, at least ignore the following: 192 | 193 | # User-specific stuff: 194 | # .idea/workspace.xml 195 | # .idea/tasks.xml 196 | # .idea/dictionaries 197 | 198 | # Sensitive or high-churn files: 199 | # .idea/dataSources.ids 200 | # .idea/dataSources.xml 201 | # .idea/sqlDataSources.xml 202 | # .idea/dynamic.xml 203 | # .idea/uiDesigner.xml 204 | 205 | # Gradle: 206 | # .idea/gradle.xml 207 | # .idea/libraries 208 | 209 | # Mongo Explorer plugin: 210 | # .idea/mongoSettings.xml 211 | 212 | ## File-based project format: 213 | *.ipr 214 | *.iws 215 | 216 | ## Plugin-specific files: 217 | 218 | # IntelliJ 219 | /out/ 220 | 221 | # mpeltonen/sbt-idea plugin 222 | .idea_modules/ 223 | 224 | # JIRA plugin 225 | atlassian-ide-plugin.xml 226 | 227 | # Crashlytics plugin (for Android Studio and IntelliJ) 228 | com_crashlytics_export_strings.xml 229 | crashlytics.properties 230 | crashlytics-build.properties 231 | ### C++ template 232 | # Compiled Object files 233 | *.slo 234 | *.lo 235 | *.o 236 | *.obj 237 | 238 | # Precompiled Headers 239 | *.gch 240 | *.pch 241 | 242 | # Compiled Dynamic libraries 243 | *.so 244 | *.dylib 245 | *.dll 246 | 247 | # Fortran module files 248 | *.mod 249 | 250 | # Compiled Static libraries 251 | *.lai 252 | *.la 253 | *.a 254 | *.lib 255 | 256 | # Executables 257 | *.exe 258 | *.out 259 | *.app 260 | ### OSX template 261 | .DS_Store 262 | .AppleDouble 263 | .LSOverride 264 | 265 | # Icon must end with two \r 266 | Icon 267 | 268 | # Thumbnails 269 | ._* 270 | 271 | # Files that might appear in the root of a volume 272 | .DocumentRevisions-V100 273 | .fseventsd 274 | .Spotlight-V100 275 | .TemporaryItems 276 | .Trashes 277 | .VolumeIcon.icns 278 | 279 | # Directories potentially created on remote AFP share 280 | .AppleDB 281 | .AppleDesktop 282 | Network Trash Folder 283 | Temporary Items 284 | .apdisk 285 | ### Xcode template 286 | # Xcode 287 | # 288 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 289 | 290 | ## Build generated 291 | build/ 292 | DerivedData 293 | 294 | ## Various settings 295 | *.pbxuser 296 | !default.pbxuser 297 | *.mode1v3 298 | !default.mode1v3 299 | *.mode2v3 300 | !default.mode2v3 301 | *.perspectivev3 302 | !default.perspectivev3 303 | xcuserdata 304 | 305 | ## Other 306 | *.xccheckout 307 | *.moved-aside 308 | *.xcuserstate 309 | ### Scala template 310 | *.class 311 | *.log 312 | 313 | # sbt specific 314 | .cache 315 | .history 316 | .lib/ 317 | dist/* 318 | target/ 319 | lib_managed/ 320 | src_managed/ 321 | project/boot/ 322 | project/plugins/project/ 323 | 324 | # Scala-IDE specific 325 | .scala_dependencies 326 | .worksheet 327 | ### Java template 328 | *.class 329 | 330 | # Mobile Tools for Java (J2ME) 331 | .mtj.tmp/ 332 | 333 | # Package Files # 334 | *.jar 335 | *.war 336 | *.ear 337 | 338 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 339 | hs_err_pid* 340 | .bsp 341 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | ------------------------------------------------------------------------ 180 | Note: 181 | Individual files contain the following tag instead of the full license text. 182 | 183 | // SPDX-License-Identifier: Apache-2.0 184 | 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chisel / Firrtl to ELK Graph 2 | 3 | This project can generate [ELK Graph](https://www.eclipse.org/elk/documentation/tooldevelopers/graphdatastructure.html) files, which can be layout by [ELK](https://www.eclipse.org/elk/) and rendered by [easysoc-diagrammer](https://github.com/easysoc/easysoc-diagrammer) as an interactive diagram to represent Chisel generated Firrtl circuits. 4 | 5 | > If you want to see the ELK Graph directly, you can use [this](https://rtsys.informatik.uni-kiel.de/elklive/elkgraph.html) online service, it's open source https://github.com/kieler/elk-live . But there may be some bugs, which are not suitable for circuit diagrams. 6 | > 7 | > It is highly recommended to using the [IntelliJ plugin](https://plugins.jetbrains.com/plugin/16255-easysoc-diagrammer) to view your circuit. It supports interactive viewing of the sub-modules by double-click. In addition, it supports back navigation, zoom view, export SVG format and other functions. 8 | 9 | The main idea is that the layout of graph use a [layer-based](https://www.eclipse.org/elk/reference/algorithms/org-eclipse-elk-layered.html) algorithm provided by the Eclipse Layout Kernel, and the graph rendering is implemented by [Sprotty](https://github.com/eclipse/sprotty) diagramming framework. The combination of the two gives us a very intuitive circuit schematics. 10 | 11 | The logic of circuit format conversion comes from [diagrammer](https://github.com/freechipsproject/diagrammer) project and adapted to elk data generation with some optimizations. 12 | 13 | ## Example 14 | 15 | **Purple Theme** 16 | [View this example interactively](https://www.easysoc.org/icviewer/?graph=N4IgTgpgbglgzjA9gOxALgAwBoQBcCeADhOiAOZgCGhAFiDjACakXV04DGNMANo5KjQBtUHAg8IHXBGZoAZpR5ichRAlxJBoAB7oAjADYATADpsIfOgDMVswF8cCAF4k0oAO5NcdNFYDsemY4NBAwZDS46ACseqYAHDaJSTYALA54RK4gyIiMJAyy5FS0JgDiAMIAIvQgXLz8EIIieBDakWggFdWO4pLSsgpKECpqMBoo6Dr6fqbmlmgAtEaBGOnOrh5ePkZ+QSAhYRH6BvY4BMSkPJQARuI1TCzFNGVVJle3SgDEGCAOor1SGToQbKECqdSaSYgXSLKJ7eYGWxRNYwFxQzyMbzRPYHcLtOGrM6ZUiqMCRAqPNgvSomDg8RAcADWNTqfAE6Ga0japDpDOZPQkgIGilB4LGkLc0PQCxSJzm6DlKLRkoxWLQKQJwVCeOOpwyFw67zuFI6rBKXVp9KZbxu4jg31+AF0-iAxIL+sCRcMwaNxlopbD4egAJxw5GOVEbECqnya-bao5oAnpc5ZUnkkAPU1PakmSBiDOshpNUBc9rgCAFmpuvpA+RekYQiaSmEyuU4BF69borboDU4hPtQx61OXW08e6FM3PC35iC4G0fe0-OzOrD-d11kHesV+qGtuPzKyBcOuyM9zGxgeHfEj4kddOTynm15IAD6UEUAFcIHoWdw2UaDlS1act30-Hgfz-AVa2FIZG3FZspkWOJZg7BUu3PFVezQOIVi1G9dUJfUsiNCcTSKKkLXA79f0XO0HVXF0ayFT14J9Jt-QPINfAME4EmSQS-CVKMY2xcxcUTZMiQNH0ySfbMqNfRAP1oox-3qdlhBA7kOhoyCIHUmDWPrdjdwlZCFlQnjFQjZVNkvdA8OvHU0GHYjR0NccFMol8aX0n9TDI5cnWYgEPVM0VfQsgMFkPY5gxMPjkpSlLgxEi81TjSTbw8+85IzLNfJnZS33pShGBgZAyAANVouANMAksWl0zMVPKyrqrqgyGuMiLtwQvcW2lPR+3lNBbLPezoxw0aUhcxN3JTfKyJ86dc3fDqqtq+r6K+Fc1w3WC2Kizj92sAIeKMDATFPbtsMcpMFty5bZMfCj1uolTEC-XBCF+rpGuLYCWrA77fv+3BAb6rcGw4xCuIw8aFj0XZiPuhy1T0DA4meojXtI7yPpzL63x+v6AdeYLGMO11wthszoqQgN-Hwix0CMY8TAEwSkmEuzRJw7LB3EgmSUQeTiaU-zwYp3BuoeTgAOB7TQdId9ychhXmBhuDToR86JpszDprEtyjHbeNCLcybPJAVapb8kwNYh37tb2kKmPXOnNz1ncmcR9U0fQibJoxmbHqsG6JJF9Vo7FjocjyNaSdeXQlc0oDVbLUh05947Iv9s7hrcm6ohDpYVgyh61USmPraWmTCY+FPpZMbQPcY72c46SAyGrem-cGmKYXi82TcFx6djxm271kh3MynVOaQ76m-zC32TqLg2S7injUdu6vMavevXOkkjxclxfnxKlfndQDeC4G+GhuQqP992O6sOP8SCLPue0wS0KkvNuHdyahW9ixfqcNzLMxhCkYO7M0DXXmoYVK6D0oC0yj4d+p9EwpHjk3UgSd8jX0Uk7SwGcmogx7uzXWW9h5wP0GXCuywJ7YJDDPRuF8vIt0drfEw+BO4rm7qBUgfcB6b0LowwOY9YjsJrtsRBOV8ZEN4caMhxVcxCLXr8SBg8GEvxHtKORn8j4RyyjPc+dt3qaM+q8IRVU9FHRMs-WBgdcEhwPl-U2QsrEAMvsAm+2iTDgK9i46BjNi7IT0AfcaUdbC8yEuYs2Vg0JWzPrbfKJDW5OzILgN80FajKy0pyMRpoMxQIZvrV+AZEHzErgon+blcZ4KHFk+eRM7HLxMPkwpwjnH51cTAgOhs97xLhOgqZBg0hYMUb-DJUkAkPiAbkgRfTAhVSMsUzOzVaFbMkU-EZ0SAyWwaWGFJOFp5tNUTw+2XSir2JpBs++QVxye1plUoeRimGBnGnoOESTBJGEuY9YW1trH5VsY8npLyqpFKLKUnSYFkBFK+YY9xhszkmMPnM5p1y-6LQ6c3DRMK25wtRQM8JQzIk1OMb4dJ8wjAXLxRYk+hKXpqIKms3MLywmfIMdIn5gdYn1IVLjNB0y+L8ympPNUaSrHEuIbkUhZKnZwC-NcN82zEVZzKa1DV1xDnDKiTvZCphy5IMaejb+bKObKNjtwu2C81UCMNdqql+ipFuNGbvMeVhJlSpSrM2VHCno3PDQnbl-DczutMAcqhKt9Uou2eioVmKS7YsDD4uVSiuFKvUeRbpbc42vKpQK71xyzWxTkYCoFSQQWsrNuC-+eU3qrJjRaUt8KgZIrVnpVFxraXb1qTCLNe8c1hoJYs9pyz7l8OLeqzVHr4XlsfiaulvyFXjWZbi0N8zw0ctFly6FICl1atMPy9dw6ZGG1RnXEOCQkpBulaCtU10Z4ELnTkztrxcCUF4G+Kwva9XItIP+3gQ7qkjvpdHVhVcm04SsJbFRs820kqLa63MEGeBAc9RE6Dt6-X70Ifu5pLalnocCTyi0OG8M9uvYR4VYyx67ptb4sF-iqMrKvlh2jAHcO2ATTs6h2dykgDRYKn1Jyx08QnW+nwD6Z23OdQ8s9Ai6NCeQG8pcNNGPfIzeauDSDvEKYWahyF7bePqewwJvDV6vVHNNaO-QcSQ4pHkfWxIMrw6pPSRZgt2QVU0deO6lIIG9nicNVBgzvrzW3Xg00u1yCHUN0Cy6mzXbl3zWpiIgjsWZM4omc+l9MyzOHuU5Gk9HbF1uuy68iLNDxPCbTdJ6tsnkYsrI8l6dqGnUrTU8ErLWr5pbLXY5jdMHfnjK8XWrzaTysUc5Xc09Q3Qv1Z7Ymvt+zB30PTXF05cmuu+aual1y-XOkLr4+tkb99Ai5YgfljFB2YTbpDruydB6lvHpWzV67NIwuhN+o9mlTHDMBnvTxFI08eZAp87as2H6I1fu40F5Ov6aR0aiI1sTrUcMxee4VzAcm2HsdzdYFDjr0uDfIRpuzcIHv6cJ+1orXjSMnc4xGyzgDrNrcx-Tu7gzWtVpc389758OeWK53O1btPbOAbhMJ3VkXWqScrc5+l47jsI5wkpvr1OruZb-QLsbjOK1Oc3YHJHXizGIc50eqrv3edy-4wroHkRqXC4178owKO2cnElVK+HHH5X+djlEQLP7au5gALZfm0IUnHybSBx7zl7y3hsLWJbJ1Os7RK50Zb5yYVP-TGcTZvcx4j40v1+E++RrjUbZdaItCXwIUCk9gY6G6An+2ida73ZLvNEaLsYZCzSVvJgoHjae73lnYukEfcWw36rzvm+vAn0rkpoH+2Zm0z3trov+91563n2dqPC8u-X-H-ppv3l6fL2Dl7rOTM3WZUv6XqOm9POL9fzZu2RNJqd675q4W5TaBxH7la9ZU4F405r7j6-53bT6g4FZz5vYL7a4h7sqVbc7UYY4-4J6BAOYz4H70q+7GbzAeaJSw5JLB7k4MqKrfrBZ4El46pb4q7lip774i6kEJZWqk6QGn4qYDaG5F4sH4bIHM6i4zZII17H7NrL5O5BKX7wEJ6mDt5bbb60Ld57YkG-IQF27vqCFoZRoX5wH4EepT5m5M6z5SGsYYF0HfaO42J-ZG4qErp74aHsHqweESE2Ga5HYD465TxGEj5jgiHKHmHxraZIHp5gEsYkYmBv4GFYEWYy4uGiHX5REIpsFNatSba+G6HgEBFyGnb5owHhFmFiGrpWEP4oGi5oFMr2FhqOE4E8ZKGVGZHu4g6xFEYxJuYL4YAnDzZWAhqD7WBh4QqR5MHR4WgQAACOb4BgHeO+8xXB3ugcYqiw-ByR+grSDuoRhaY+Jg8xix4hPRlelk-qgapWoxQRUuDurR0aMxrwJxJwm+uyuR5YPwOh3BehxR5Weu0B5+sB3+rxZaNRxBvxgc0h8wAKJgwxC2OxFWqRn+6RERYJ+Ryunx3hIBk2vRh2nWgRmB9qZRwJFRoJCxbxlKEJBRUJhsDRHMTRX2Chzhq+FJpxRBtJ6x9JGAjKzCAepWtBYaaBAWjB6OzxbhwGnh2JHQnBPx3JJcWefBCG3WiOIRBupKrh5htgZekJCplxUON0te7+DxaRbJPSJetg6hAB22UWGi5x4OHWFcTJ+K6p5RmpGRCeVp4UMRUmdJVe4uRJDhLJUKaJHRXpDW0puOKaaxGema-xSJUBaW7pmGWplp4Jd+B01hhR8R-yr+JR9u2BZp7R3+6ZmJOR0ZOJsZcR8ZhJBZhhpJJhIJFp1+Wm92mZ3Rfp+pLMfJyCLpyWLRxZRx6ZnJDpT+vgvJUOegVB82QpB6Ip4eUx4p-25h4WUZyesp8e1Z+JMISpDS2xqppRw+GpqZnpb4OWHZ2Z-pBp1eRp9ZKR4eQ5zB1+801pWJlZXe9pXZcZyE+hh5wRjZXKphpZL5k+PpNJY5ROMJjJQZzRIZVmJZLZCeo2Ph75G5u+qa35NZv5CZ-5DZx5KZw5oFt+umWZtRkh9K0FpciR955mj5qJ5pbcJeKF2RHxH5wB25FxBJzpsFB6SZ52J5RFyFiBEFWFO54xV0-Z8hH+jeYZIFwlo5P0YAHAEAAAkmma2V0WcJQGAGQPOOpUXh3E4jgGAOTNtAAAqIBVS4ANSqyvZWBKawkjEmCB7TKYJvwpCOX6DOWuVTLuUsyeX7yzAuiva9nyKEhvy9lxC2TITQ48TRX2COjymgh2wyB6VHG-jryQJKUqUGURFgLA7aW6X6VakuxyzQzgBmXVSWXWW2XNAwjWTjQJURUBjBhRUxWtW9mcynBvx+BZrdWrBJVckpX5RpWqpam-jqTMQ5VqValCLgJFV6W4B5VmGA75GmW-QWVWXIA2UgwNVdW+4uWlb+UwjYwHWoLHUhXMK9kpDvy+WpT8x9E3V3UvrCRDXnGpWMDpV4G-hSk2nb6QVz5nVXSHX3WpSYJ3IABWX4yAUgkIP1egVgb4kNZFogM1K18l-SC1eAOlS1GNSF9GPhG1Gg1V21u1dl9qmxiNF1L6J1HMwYVNPll171YldyY1GVo0Qu6NE1lJWlONxVy1pVssWsigislVm1pNtVe1+gUQ80O6zlwxsyvV-VCts5q4yV3on131EpxxAKXNX4yls1Rem0iAFU203UP4vU-NeNGlwl1pxNW1UtFN8+sJMwR1tNV19B-ybtYNaUntDJ6ocQuM1BvMj1AYRgcQmxKQQd3Mat3su5kdUOiIZgIdyQYdu5DNSdtgOMXmb1GtXK7NCNSx65epP5PZH8pgvtyUENds0NsNe4RdyNqNro3NRe81hV1tJVZ5WRNQDtktO1dVyEjV72oNl18d11INNNQadNpcvZVgcQN0wx6dHMbGIc89i9cd5qq9MhCqUQqdfMntnMY8t1Fq+93m6tw1mto1X141Rev4fgyxgNouwN8SC98JcdUNMNcNEwCNfgTd3RrdERpa2N-6At+NbcmmguJlVVZANVA90tbkstPEwYApr1ntqMctIcKD7tQaedl9BdN9HNcQ+tht4DFCfNoDNtRe3aRNMDcD5N9VTk51ODUqM9L9I9U9rD6Dk5eZQx82StEO4V-u79XmaQLN6uI1skhdOtv4wYJDuVc1FDuNXdERFK2yfdsDZNg9AYw9C+o9Ht49s9k9LDbl3Dc9Hmt0GAVj1jNjVjBg6DQjSCIxJ4tjrjgxF9H1192tK5hk3x-1zUT99K7DTjFjUQbjtj9jXKdd39qAP110-9nugDZh7dGYlDKjZhGJ-+Gj9D2j+1xjVdfEbDjjTK+j09HjrNWtt96Jyw8jRtERYhIDyjgtZ5KF6jdDWjCDOwUdCCsdudh9md1ePTZ9594joBBD3jE1RgU12VBtCjxt7UptnUO0PUNQaTzT9Tml9t7TTtjDLt0wldY95qid3tBzBjRzVNgVHmZz4dAzXilzBT6UozeJbNhDcTwG01szdTZhAUv4qzTTZDAiZZWT2z8DztVFzlLVCdmxELh9xzXieguMwYBTy9yCtzJmCLJgSLaDTzFelTGVvutTALseoFjTYDijxl4tJNmjOzb8DlUOQzm9AVXl6oDLfThjIxzLnlM5bLW9UOwY3L9aKLUz+ThzAYcVO6pTXDOLTGeLcT2OHzpDWpgOpLVDERWOUDlLjtoLuzAKmDMhX6S96DfgerFBBrat0r3ysrMjFshLWpBVqT-zSry6Pd0DEt1L2rQ97VPVgjXrg1+dLzEzd9OwtrhlSjZLZ5f+bTbrOTCDuj8wzVntbV8VHVMISb-yTN1zu5Aa+8GbZTFrhiVrPjEdj9rNjpIYvrUa0TDd1rcQCT5FfhvyabXiubXDUTX91bRbtbkN68nyST3+9rfz4bqjBSf+RS2THTztcbTkKb5byb3rqbc9bCyLDji7gQy7+bQqhbkzcjJd+DXFC7PEaSa7aDbb9d8N1rwYCTvbnzRLFofKHdazt7V+qhYF7ovdILDDMSSD8SXMhrhjhgJr1gv7jLp1MyUOfguwwzNgQr29FBEHvTgrh9sHfYydOdiHhjR9WdKdfTG7z8W7d9UcJbEj2FEOYHP7gQf7n9Z7P9MjUcV7Crcz+VYbqrq1G2tD0bE7uzU7uEM7aATbSCCbhj-HFBYTlj4T1jkTT1UOonYT4ndj5TxH4zVTGTx4IbarAuKr6TmNURUbVLMbztKC9LkHIHHM2bgzxnOH-r+H6JaSan3zKkEEgUg7LHmNLF77HHNLNah7BgiUf75z3nvnJnyCcLO9p9PLNzUdu9UHeDnjUjrztH7zMzirRe97DrQ74Z-SWzHnHrEO37a9wH4XoHgHvgBX6HMSZHdziUVzZTGHZnlXJg1XUrVnXjynoJIxdn3+kDmn6zGXo77nennHRmxX2MtgpWBgMHciGAo3gpSHciGLWLuDh9dX6LiL67zXcXgbNn8rzozoQAA) 17 | ![Graph](./images/GCD.svg) 18 | **Solid Theme(--flatten 2)** 19 | 20 | ![Graph](./images/VizModA.svg) 21 | 22 | ## Using 23 | 24 | ### Install 25 | 26 | **Maven** 27 | 28 | https://search.maven.org/artifact/org.easysoc/layered-firrtl_2.12 29 | 30 | for SBT 31 | 32 | ``` 33 | // "org.easysoc" %% "layered-firrtl" % "1.1-SNAPSHOT" 34 | "org.easysoc" %% "layered-firrtl" % "1.1.+" 35 | ``` 36 | 37 | **Source** 38 | 39 | ```bash 40 | git clone https://github.com/easysoc/layered-firrtl 41 | cd layered-firrtl 42 | ``` 43 | 44 | ### Creating graph from Chisel code 45 | 46 | ```scala 47 | val targetDir = "test_run_dir/gcd" 48 | (new ElkStage).execute( 49 | Array("-td", targetDir), 50 | Seq(ChiselGeneratorAnnotation(() => new GCD)) 51 | ) 52 | ``` 53 | 54 | ### Creating graph from Firrtl file 55 | 56 | To create a set of graphs of a Firrtl Circuit all you need is this project and a Firrtl file (typically a file 57 | generated by Chisel with a `.fir` extension). Let's say you have a Firrtl file `~/projects/output/circuit.fir`. 58 | From the command line you while in this directory for this project 59 | 60 | ```bash 61 | ./elk.sh -i ~/projects/output/circuit.fir 62 | ``` 63 | or run through fat jar 64 | ```bash 65 | sbt assembly 66 | ./utils/bin/elk -i ~/projects/output/circuit.fir 67 | ``` 68 | This will create a number of files in the `test_run_dir/${circuit.main}` directory that representing the firrtl circuit. Each file will be a ELK Graph for each module contained in the firrtl file. 69 | 70 | ## Options 71 | * `-i`, set the source firrtl to work on 72 | * `-td,--target-dir` work directory (default: "./test_run_dir") 73 | * `--top` the module in the hierarchy to start, default is the circuit top 74 | * `--flatten` the maxDepth of the flatten levels 75 | * It is not recommended to exceed 2, otherwise the generated graph is too complicated 76 | * `--lowFir` generate the corresponding lo.fir file 77 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | name := "layered-firrtl" 4 | 5 | resolvers ++= Seq( 6 | Resolver.sonatypeRepo("snapshots"), 7 | Resolver.sonatypeRepo("releases") 8 | ) 9 | 10 | val defaultVersions = Map( 11 | // "chisel3" -> "3.5-SNAPSHOT" 12 | "chisel3" -> "3.4.3" 13 | ) 14 | 15 | organization := "org.easysoc" 16 | organizationName := "EasySoC" 17 | organizationHomepage := Some(url("https://github.com/easysoc/")) 18 | // https://oss.sonatype.org/content/repositories/snapshots/edu/berkeley/cs/ 19 | // https://oss.sonatype.org/content/repositories/snapshots/org/easysoc/ 20 | //version := "1.1-SNAPSHOT" 21 | version := "1.1.1" 22 | autoAPIMappings := true 23 | // should match chisel's dependencies https://search.maven.org/artifact/edu.berkeley.cs/chisel3-core_2.12 24 | //scalaVersion := "2.13.6" 25 | scalaVersion := "2.12.12" 26 | crossScalaVersions := Seq("2.13.6", "2.12.13") 27 | scalacOptions := Seq("-deprecation", "-feature") ++ scalacOptionsVersion(scalaVersion.value) 28 | 29 | //crossPaths := false 30 | publishConfiguration := publishConfiguration.value.withOverwrite(true) 31 | publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true) 32 | 33 | lazy val root = (project in file(".")). 34 | enablePlugins(BuildInfoPlugin). 35 | settings( 36 | buildInfoKeys := Seq[BuildInfoKey](version), 37 | buildInfoKeys ++= Seq[BuildInfoKey]("chiselVersion" -> defaultVersions("chisel3")), 38 | buildInfoPackage := "layered", 39 | buildInfoUsePackageAsPath := true 40 | ) 41 | 42 | def scalacOptionsVersion(scalaVersion: String): Seq[String] = { 43 | Seq() ++ { 44 | // If we're building with Scala > 2.11, enable the compile option 45 | // switch to support our anonymous Bundle definitions: 46 | // https://github.com/scala/bug/issues/10047 47 | CrossVersion.partialVersion(scalaVersion) match { 48 | case Some((2, scalaMajor: Long)) if scalaMajor < 12 => Seq() 49 | case _ => Seq("-Xsource:2.11") 50 | } 51 | } 52 | } 53 | 54 | def javacOptionsVersion(scalaVersion: String): Seq[String] = { 55 | Seq() ++ { 56 | // Scala 2.12 requires Java 8. We continue to generate 57 | // Java 7 compatible code for Scala 2.11 58 | // for compatibility with old clients. 59 | CrossVersion.partialVersion(scalaVersion) match { 60 | case Some((2, scalaMajor: Long)) if scalaMajor < 12 => 61 | Seq("-source", "1.7", "-target", "1.7") 62 | case _ => 63 | Seq("-source", "1.8", "-target", "1.8") 64 | } 65 | } 66 | } 67 | 68 | publishMavenStyle := true 69 | Test / publishArtifact := false 70 | pomIncludeRepository := { _ => false } 71 | // Don't add 'scm' elements if we have a git.remoteRepo definition, 72 | // but since we don't (with the removal of ghpages), add them in below. 73 | pomExtra := https://github.com/easysoc/ 74 | 75 | 76 | Apache-2.0 77 | http://www.apache.org/licenses/LICENSE-2.0 78 | repo 79 | 80 | 81 | 82 | https://github.com/easysoc/layered-firrtl 83 | scm:git:github.com/easysoc/layered-firrtl.git 84 | 85 | 86 | 87 | itviewer 88 | XinJun Ma 89 | https://github.com/easysoc 90 | 91 | 92 | 93 | // publishSigned and sonatypeBundleRelease 94 | sonatypeBundleDirectory := baseDirectory.value / target.value.getName / "sonatype-staging" / version.value 95 | publishTo := sonatypePublishToBundle.value 96 | 97 | libraryDependencies ++= Seq("chisel3").map { dep: String => 98 | "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) 99 | } 100 | 101 | libraryDependencies ++= Seq( 102 | "org.scalatest" %% "scalatest" % "3.2.9" % "test" 103 | ) 104 | 105 | scalacOptions ++= scalacOptionsVersion(scalaVersion.value) 106 | 107 | javacOptions ++= javacOptionsVersion(scalaVersion.value) 108 | 109 | // Assembly 110 | 111 | assembly / assemblyJarName := "layered-firrtl.jar" 112 | 113 | assembly / test := {} // Should there be tests? 114 | 115 | assembly / assemblyOutputPath := file("./utils/bin/layered-firrtl.jar") 116 | 117 | import scala.xml.{Node => XmlNode, NodeSeq => XmlNodeSeq, _} 118 | import scala.xml.transform.{RewriteRule, RuleTransformer} 119 | 120 | // skip dependency elements with a scope 121 | pomPostProcess := { (node: XmlNode) => 122 | new RuleTransformer(new RewriteRule { 123 | override def transform(node: XmlNode): XmlNodeSeq = node match { 124 | case e: Elem if e.label == "dependency" 125 | && e.child.exists(child => child.label == "scope") => 126 | def txt(label: String): String = "\"" + e.child.filter(_.label == label).flatMap(_.text).mkString + "\"" 127 | Comment(s""" scoped dependency ${txt("groupId")} % ${txt("artifactId")} % ${txt("version")} % ${txt("scope")} has been omitted """) 128 | case _ => node 129 | } 130 | }).transform(node).head 131 | } -------------------------------------------------------------------------------- /elk.sh: -------------------------------------------------------------------------------- 1 | 2 | sbt "runMain layered.Elk $*" 3 | -------------------------------------------------------------------------------- /images/GCD.svg: -------------------------------------------------------------------------------- 1 | GCDclockresetio_aio_bio_eio_zio_vxregyreggtin2in1subin2in1tail1subin2in1tail1muxselin2in1muxselin2in1muxselin2in1muxselin2in1eq0in1muxselin2in10muxselin2in10 -------------------------------------------------------------------------------- /images/VizModA.svg: -------------------------------------------------------------------------------- 1 | VizModAio_inio_outmodCio_inio_outmodBio_inio_outmodCio_inio_outmodB2io_inio_outmodCio_inio_outaddtail1addtail1bits150bits150bits150 -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.5 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | // create a fat JAR of your project with all of its dependencies 4 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") 5 | // sbt-sonatype plugin used to publish artifact to maven central via sonatype nexus 6 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.5") 7 | // sbt-pgp plugin used to sign the artifcat with pgp keys 8 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") 9 | // generates Scala source from your build definitions 10 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") -------------------------------------------------------------------------------- /src/main/scala/layered/Elk.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered 4 | 5 | import firrtl._ 6 | import layered.stage.ElkStage 7 | 8 | /** 9 | * This library implements the conversion of Chisel / Firrtl to Eclipse Layout Kernel Graph. 10 | */ 11 | object Elk { 12 | def main(args: Array[String]): Unit = { 13 | (new ElkStage).execute(args, Seq.empty) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/layered/ToLoFirrtl.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered 4 | 5 | import firrtl._ 6 | import firrtl.options.{Dependency, Phase} 7 | import firrtl.stage.{FirrtlCircuitAnnotation, Forms} 8 | 9 | /** 10 | * Use these lowering transforms to prepare circuit for compiling 11 | */ 12 | class ToLoFirrtl extends Phase { 13 | private val targets = Forms.LowFormOptimized ++ Seq( 14 | Dependency(passes.RemoveEmpty) 15 | ) 16 | 17 | private def compiler = new firrtl.stage.transforms.Compiler(targets, currentState = Nil) 18 | private val transforms = compiler.flattenedTransformOrder 19 | 20 | override def transform(annotationSeq: AnnotationSeq): AnnotationSeq = { 21 | 22 | annotationSeq.flatMap { 23 | case FirrtlCircuitAnnotation(circuit) => 24 | val state = CircuitState(circuit, annotationSeq) 25 | val newState = transforms.foldLeft(state) { 26 | case (prevState, transform) => transform.runTransform(prevState) 27 | } 28 | Some(FirrtlCircuitAnnotation(newState.circuit)) 29 | case other => 30 | Some(other) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/scala/layered/elknodes/ElkNode.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.elknodes 4 | 5 | import scala.collection.mutable.ArrayBuffer 6 | 7 | trait ElkNode { 8 | def render: String 9 | def name: String 10 | def parentOpt: Option[ElkNode] 11 | def absoluteName: String = { 12 | parentOpt match { 13 | case Some(parent) => s"${parent.absoluteName}.$name" 14 | case _ => name 15 | } 16 | } 17 | 18 | def in: String = absoluteName 19 | def out: String = absoluteName 20 | def asRhs: String = absoluteName 21 | 22 | val children: ArrayBuffer[ElkNode] = new ArrayBuffer[ElkNode] 23 | 24 | def indentPrefix(rawString: String): String = { 25 | val prefix = parentOpt.get.asInstanceOf[ModuleNode].indentation 26 | rawString.split("\n").map(prefix + _).mkString("\n") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/layered/elknodes/LiteralNode.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.elknodes 4 | 5 | case class LiteralNode(name: String, value: BigInt, parentOpt: Option[ElkNode]) extends ElkNode { 6 | def render: String = { 7 | 8 | val s = 9 | s""" 10 | | node $name { 11 | | nodeLabels.placement: "H_CENTER V_CENTER INSIDE" 12 | | nodeSize.constraints: "NODE_LABELS" 13 | | label "$value" 14 | | } 15 | |""".stripMargin 16 | indentPrefix(s) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/layered/elknodes/MemNode.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.elknodes 4 | 5 | import firrtl.ir.DefMemory 6 | 7 | import scala.collection.mutable 8 | 9 | case class MemNode( 10 | name: String, 11 | parentOpt: Option[ElkNode], 12 | firrtlName: String, 13 | defMem: DefMemory, 14 | nameToNode: mutable.HashMap[String, ElkNode]) 15 | extends ElkNode { 16 | 17 | val text = new mutable.StringBuilder() 18 | 19 | text.append( 20 | s""" 21 | | node $name { 22 | | nodeLabels.placement: "H_CENTER V_TOP OUTSIDE" 23 | | nodeSize.constraints: "NODE_LABELS" 24 | | label "$name" 25 | """.stripMargin) 26 | 27 | def addPort(memPortName: String, firrtlMemPortName: String, portName: String,kind: String): Unit = { 28 | val firrtlName = s"$firrtlMemPortName.$portName" 29 | 30 | val port = MemoryPort(portName, s"$absoluteName.$memPortName.$portName", memPortName, kind) 31 | nameToNode(firrtlName) = port 32 | text.append(s" ${port.render}") 33 | } 34 | 35 | def addMemoryPort(kind: String, memPortName: String, fields: Seq[String]): Unit = { 36 | val firrtlMemPortName = s"$firrtlName.$memPortName" 37 | val s = 38 | s""" 39 | | node $memPortName { 40 | | portConstraints: FIXED_SIDE 41 | | nodeLabels.placement: "H_CENTER V_TOP OUTSIDE" 42 | | nodeSize.constraints: " PORTS PORT_LABELS NODE_LABELS" 43 | | label "$memPortName" 44 | | label "$kind" { nodeLabels.placement: "H_CENTER V_CENTER INSIDE" } 45 | |""".stripMargin 46 | text.append(s) 47 | 48 | fields.foreach { portName => addPort(memPortName, firrtlMemPortName, portName, kind) } 49 | 50 | text.append( 51 | """ 52 | | } 53 | """.stripMargin) 54 | } 55 | 56 | defMem.readers.foreach { readerName => 57 | addMemoryPort("read", readerName, Seq("en", "addr", "data", "clk")) 58 | } 59 | defMem.writers.foreach { readerName => 60 | addMemoryPort("write", readerName, Seq("en", "addr", "data", "mask", "clk")) 61 | } 62 | defMem.readwriters.foreach { readerName => 63 | addMemoryPort("write", readerName, Seq("en", "addr", "wdata", "wmask", "wmode", "clk")) 64 | } 65 | 66 | text.append( 67 | """ 68 | | } 69 | """.stripMargin) 70 | 71 | def render: String = indentPrefix(text.toString()) 72 | } 73 | 74 | case class MemoryPort(name: String, override val absoluteName: String, memPortName: String,kind: String) extends ElkNode { 75 | val parentOpt: Option[ElkNode] = None // doesn't need to know parent 76 | def render: String = { 77 | val direction = if (name.endsWith("data") && kind == "read") "EAST" else "WEST" 78 | val s = 79 | s""" 80 | | port $name { 81 | | ^port.side: $direction 82 | | label "$name" 83 | | }""".stripMargin 84 | s 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/scala/layered/elknodes/ModuleNode.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.elknodes 4 | 5 | import scala.collection.mutable 6 | import scala.collection.mutable.ArrayBuffer 7 | 8 | case class ModuleNode( 9 | name: String, 10 | parentOpt: Option[ElkNode], 11 | var urlString: Option[String] = None, 12 | subModuleDepth: Int = 0) 13 | extends ElkNode { 14 | 15 | val indentation: String = " " * subModuleDepth 16 | val fixName: String = parentOpt match { 17 | case Some(_) => s"submodule_${name.replace('_','0')}" 18 | case _ => name 19 | } 20 | 21 | var renderWithRank: Boolean = false 22 | 23 | val namedNodes: mutable.HashMap[String, ElkNode] = new mutable.HashMap() 24 | val subModuleNames: mutable.HashSet[String] = new mutable.HashSet[String]() 25 | 26 | val connections: mutable.HashMap[String, String] = new mutable.HashMap() 27 | private val analogConnections = new mutable.HashMap[String, ArrayBuffer[String]]() { 28 | override def default(key: String): ArrayBuffer[String] = { 29 | this(key) = new ArrayBuffer[String]() 30 | this(key) 31 | } 32 | } 33 | val localConnections: mutable.HashMap[String, String] = new mutable.HashMap() 34 | 35 | /** 36 | * Renders this node 37 | * @return 38 | */ 39 | def render: String = { 40 | def expandBiConnects(target: String, sources: ArrayBuffer[String]): String = { 41 | sources.map { vv => s"""${indentation}edge e${Edge.hash} : $vv -> $target""" }.mkString("\n ") 42 | } 43 | val s = s""" 44 | |${indentation}node $fixName$graphUrl { 45 | | ${indentation}portConstraints: FIXED_SIDE 46 | | ${indentation}nodeLabels.placement: "H_CENTER V_TOP OUTSIDE" 47 | | ${indentation}nodeSize.constraints: "PORTS PORT_LABELS NODE_LABELS" 48 | | ${indentation}label "$name" 49 | | $indentation${children.map(_.render).mkString("")} 50 | | ${connections.map { case (k, v) => s"${indentation}edge e${Edge.hash} : $v -> $k" }.mkString("\n ")} 51 | | ${analogConnections.map { case (k, v) => expandBiConnects(k, v) }.mkString("\n ")} 52 | |$indentation} 53 | |""".stripMargin 54 | s 55 | } 56 | 57 | override def absoluteName: String = { 58 | parentOpt match { 59 | case Some(parent) => s"${parent.absoluteName}.$fixName$graphUrl" 60 | case _ => s"$fixName" 61 | } 62 | } 63 | 64 | def graphUrl: String = { 65 | urlString match { 66 | case Some(url) => s"_$url" 67 | case None => "" 68 | } 69 | } 70 | 71 | def connect(destination: ElkNode, source: ElkNode): Unit = { 72 | connect(destination.absoluteName, source.absoluteName) 73 | } 74 | 75 | def connect(destinationName: String, source: ElkNode): Unit = { 76 | connect(destinationName, source.absoluteName) 77 | } 78 | 79 | def connect(destination: ElkNode, sourceName: String): Unit = { 80 | connect(destination.absoluteName, sourceName) 81 | } 82 | 83 | def connect(destination: String, source: String, edgeLabel: String = ""): Unit = { 84 | connections(destination) = source 85 | } 86 | 87 | def analogConnect(destination: String, source: String, edgeLabel: String = ""): Unit = { 88 | analogConnections(destination) += source 89 | } 90 | 91 | def +=(childNode: ElkNode): Unit = { 92 | namedNodes(childNode.absoluteName) = childNode 93 | children += childNode 94 | } 95 | } 96 | 97 | object Edge { 98 | var pseudoHash: Long = 10 99 | 100 | def hash: Long = { 101 | pseudoHash += 1 102 | pseudoHash 103 | } 104 | } -------------------------------------------------------------------------------- /src/main/scala/layered/elknodes/MuxNode.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.elknodes 4 | 5 | object MuxNode { 6 | var pseudoHash: Long = 0 7 | 8 | def hash: Long = { 9 | pseudoHash += 1 10 | pseudoHash 11 | } 12 | } 13 | 14 | case class MuxNode( 15 | name: String, 16 | parentOpt: Option[ElkNode], 17 | arg0ValueOpt: Option[String] = None, 18 | arg1ValueOpt: Option[String] = None) 19 | extends ElkNode { 20 | 21 | override val absoluteName: String = s"${name}_${MuxNode.hash}" 22 | val select: String = s"$absoluteName.select" 23 | def in1: String = s"$absoluteName.in1" 24 | def in2: String = s"$absoluteName.in2" 25 | override val asRhs: String = s"$absoluteName.out" 26 | 27 | def render: String = { 28 | val s = 29 | s""" 30 | | node $absoluteName { 31 | | layout [ size: 32, 56 ] 32 | | portConstraints: FIXED_ORDER 33 | | nodeLabels.placement: "H_CENTER V_TOP OUTSIDE" 34 | | portLabels.placement: "INSIDE" 35 | | label "mux" 36 | | port select { 37 | | ^port.side: "WEST" 38 | | label "sel" 39 | | } 40 | | port in2 { 41 | | ^port.side: "WEST" 42 | | label "in2" 43 | | } 44 | | port in1 { 45 | | ^port.side: "WEST" 46 | | label "in1" 47 | | } 48 | | port out { 49 | | ^port.side: "EAST" 50 | | } 51 | | } 52 | |""".stripMargin 53 | indentPrefix(s) 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/layered/elknodes/NodeNode.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.elknodes 4 | 5 | case class NodeNode(name: String, parentOpt: Option[ElkNode]) extends ElkNode { 6 | def render: String = { 7 | val s = s""" 8 | | node $name { 9 | | layout [ size: 0.01, 0.01 ] 10 | | } 11 | |""".stripMargin 12 | indentPrefix(s) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/layered/elknodes/PortNode.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.elknodes 4 | 5 | case class PortNode(name: String, parentOpt: Option[ElkNode], isInput: Boolean = false) 6 | extends ElkNode { 7 | 8 | def render: String = { 9 | val direction = if (isInput) "WEST" else "EAST" 10 | val s = s""" 11 | | port $name { 12 | | ^port.side: $direction 13 | | label "$name" 14 | | }""".stripMargin 15 | indentPrefix(s) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/layered/elknodes/PrimOpNode.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.elknodes 4 | 5 | object PrimOpNode { 6 | /* the pseudoHash is necessary because case classes with identical args have identical hashes */ 7 | var pseudoHash: Long = 0 8 | 9 | def hash: Long = { 10 | pseudoHash += 1 11 | pseudoHash 12 | } 13 | } 14 | 15 | case class BinaryOpNode( 16 | name: String, 17 | parentOpt: Option[ElkNode], 18 | arg0ValueOpt: Option[String], 19 | arg1ValueOpt: Option[String]) 20 | extends ElkNode { 21 | 22 | def in1: String = s"$absoluteName.in1" 23 | 24 | def in2: String = s"$absoluteName.in2" 25 | 26 | override val absoluteName: String = s"${name}_${PrimOpNode.hash}" 27 | override val asRhs: String = s"$absoluteName.out" 28 | 29 | def render: String = { 30 | 31 | val s = 32 | s""" 33 | | node $absoluteName { 34 | | layout [ size: 32, 56 ] 35 | | portConstraints: FIXED_ORDER 36 | | nodeLabels.placement: "H_CENTER V_TOP OUTSIDE" 37 | | portLabels.placement: "INSIDE" 38 | | label "$name" 39 | | port in2 { 40 | | ^port.side: "WEST"${ 41 | arg1ValueOpt match { 42 | case Some(value) => "\n" + s""" label "$value"""" 43 | case None => "\n" + s""" label "in2"""" 44 | } 45 | } 46 | | } 47 | | port in1 { 48 | | ^port.side: "WEST"${ 49 | arg0ValueOpt match { 50 | case Some(value) => "\n" + s""" label "$value"""" 51 | case None => "\n" + s""" label "in1"""" 52 | } 53 | } 54 | | } 55 | | port out { 56 | | ^port.side: "EAST" 57 | | } 58 | | } 59 | |""".stripMargin 60 | indentPrefix(s) 61 | } 62 | } 63 | 64 | case class UnaryOpNode(name: String, parentOpt: Option[ElkNode]) extends ElkNode { 65 | def in1: String = s"$absoluteName.in1" 66 | 67 | override val absoluteName: String = s"${name}_${PrimOpNode.hash}" 68 | override val asRhs: String = s"$absoluteName.out" 69 | 70 | def render: String = { 71 | val s = 72 | s""" 73 | | node $absoluteName { 74 | | layout [ size: 20, 40 ] 75 | | portConstraints: FIXED_SIDE 76 | | nodeLabels.placement: "H_CENTER V_TOP OUTSIDE" 77 | | label "$name" 78 | | port in1 { 79 | | ^port.side: "WEST" 80 | | } 81 | | port out { 82 | | ^port.side: "EAST" 83 | | } 84 | | } 85 | |""".stripMargin 86 | indentPrefix(s) 87 | } 88 | } 89 | 90 | case class OneArgOneParamOpNode(name: String, parentOpt: Option[ElkNode], value: BigInt) extends ElkNode { 91 | def in1: String = s"$absoluteName.in1" 92 | 93 | override val absoluteName: String = s"${name}_${PrimOpNode.hash}" 94 | override val asRhs: String = s"$absoluteName.out" 95 | 96 | def render: String = { 97 | val s = 98 | s""" 99 | | node $absoluteName { 100 | | layout [ size: 20, 40 ] 101 | | portConstraints: FIXED_SIDE 102 | | nodeLabels.placement: "H_LEFT V_TOP OUTSIDE" 103 | | portLabels.placement: "INSIDE" 104 | | label "$name" 105 | | port in1 { 106 | | ^port.side: "WEST" 107 | | } 108 | | port in2 { 109 | | ^port.side: "WEST" 110 | | label "$value" 111 | | } 112 | | port out { 113 | | ^port.side: "EAST" 114 | | } 115 | | } 116 | |""".stripMargin 117 | indentPrefix(s) 118 | } 119 | } 120 | 121 | case class OneArgTwoParamOpNode( 122 | name: String, 123 | parentOpt: Option[ElkNode], 124 | value1: BigInt, 125 | value2: BigInt) 126 | extends ElkNode { 127 | def in1: String = s"$absoluteName.in1" 128 | 129 | override val absoluteName: String = s"${name}_${PrimOpNode.hash}" 130 | override val asRhs: String = s"$absoluteName.out" 131 | 132 | def render: String = { 133 | val s = 134 | s""" 135 | | node $absoluteName { 136 | | layout [ size: 20, 40 ] 137 | | portConstraints: FIXED_ORDER 138 | | nodeLabels.placement: "H_LEFT V_TOP OUTSIDE" 139 | | portLabels.placement: "INSIDE" 140 | | label "$name" 141 | | port in1 { 142 | | ^port.side: "WEST" 143 | | ^port.index:3 144 | | } 145 | | port in2 { 146 | | ^port.side: "WEST" 147 | | ^port.index:2 148 | | label "$value1" 149 | | } 150 | | port in3 { 151 | | ^port.side: "WEST" 152 | | ^port.index:1 153 | | label "$value2" 154 | | } 155 | | port out { 156 | | ^port.side: "EAST" 157 | | } 158 | | } 159 | |""".stripMargin 160 | indentPrefix(s) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/scala/layered/elknodes/RegisterNode.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.elknodes 4 | 5 | case class RegisterNode(name: String, parentOpt: Option[ElkNode]) extends ElkNode { 6 | override val in: String = s"$absoluteName.in" 7 | override val out: String = s"$absoluteName.out" 8 | override val asRhs: String = s"$absoluteName.out" 9 | 10 | def render: String = { 11 | val s = 12 | s""" 13 | | node $name { 14 | | layout [ size: 30, 40 ] 15 | | portConstraints: FIXED_SIDE 16 | | nodeLabels.placement: "H_CENTER V_TOP OUTSIDE" 17 | | label "$name" 18 | | label "reg" { nodeLabels.placement: "H_LEFT V_CENTER INSIDE" } 19 | | port in { 20 | | ^port.side: "WEST" 21 | | } 22 | | port out { 23 | | ^port.side: "EAST" 24 | | } 25 | | } 26 | |""".stripMargin 27 | indentPrefix(s) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/layered/stage/ElkException.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.stage 4 | 5 | class ElkException(msg: String) extends Exception(msg) 6 | -------------------------------------------------------------------------------- /src/main/scala/layered/stage/ElkPhase.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.stage 4 | 5 | import layered.stage.phase.{ 6 | CheckPhase, 7 | GenerateElkFilePhase, 8 | GetFirrtlCircuitPhase, 9 | OptionallyBuildTargetDirPhase 10 | } 11 | import firrtl.options.phases.DeletedWrapper 12 | import firrtl.options.{Dependency, Phase, PhaseManager} 13 | 14 | class ElkPhase extends PhaseManager(ElkPhase.targets) { 15 | 16 | override val wrappers = Seq((a: Phase) => DeletedWrapper(a)) 17 | 18 | } 19 | 20 | object ElkPhase { 21 | 22 | val targets: Seq[PhaseManager.PhaseDependency] = Seq( 23 | Dependency[CheckPhase], 24 | Dependency[GetFirrtlCircuitPhase], 25 | Dependency[OptionallyBuildTargetDirPhase], 26 | Dependency[GenerateElkFilePhase] 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/layered/stage/ElkStage.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.stage 4 | 5 | import chisel3.stage.ChiselCli 6 | import firrtl.AnnotationSeq 7 | import firrtl.options.{Shell, Stage} 8 | import firrtl.stage.FirrtlCli 9 | import layered.stage.cli.ElkCli 10 | 11 | /** The firrtl circuit can be in any one of the following forms 12 | * ChiselGeneratorAnnotation(() => new DUT()) a chisel DUT that is used to generate firrtl 13 | * FirrtlFileAnnotation(fileName) a file name that contains firrtl source 14 | * FirrtlSourceAnnotation in-line firrtl source 15 | */ 16 | class ElkStage extends Stage { 17 | val shell: Shell = new Shell("elk") with ElkCli with ChiselCli with FirrtlCli 18 | 19 | def run(annotations: AnnotationSeq): AnnotationSeq = { 20 | (new ElkPhase).transform(annotations) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/layered/stage/cli/ElkAnnotations.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.stage.cli 4 | 5 | import firrtl.annotations.{Annotation, NoTargetAnnotation} 6 | import firrtl.options.{HasShellOptions, ShellOption, Unserializable} 7 | 8 | sealed trait ElkAnnotation { 9 | this: Annotation => 10 | } 11 | 12 | case class StartModuleNameAnnotation(name: String) 13 | extends ElkAnnotation 14 | with NoTargetAnnotation 15 | with Unserializable 16 | 17 | object StartModuleNameAnnotation extends HasShellOptions { 18 | val options = Seq( 19 | new ShellOption[String]( 20 | longOption = "top", 21 | toAnnotationSeq = (a: String) => Seq(StartModuleNameAnnotation(a)), 22 | helpText = "The module in the hierarchy to start, default is the circuit top" 23 | ) 24 | ) 25 | } 26 | 27 | case class FlattenLevelAnnotation(depth: Int) 28 | extends ElkAnnotation 29 | with NoTargetAnnotation 30 | with Unserializable 31 | 32 | object FlattenLevelAnnotation extends HasShellOptions { 33 | val options = Seq( 34 | new ShellOption[Int]( 35 | longOption = "flatten", 36 | toAnnotationSeq = (a: Int) => Seq(FlattenLevelAnnotation(a)), 37 | helpText = "The maxDepth of the flatten levels", 38 | helpValueName = Some("depth") 39 | ) 40 | ) 41 | } 42 | 43 | case object SerializeAnnotation 44 | extends NoTargetAnnotation 45 | with ElkAnnotation 46 | with HasShellOptions 47 | with Unserializable { 48 | val options = Seq( 49 | new ShellOption[Unit]( 50 | longOption = "lowFir", 51 | toAnnotationSeq = _ => Seq(SerializeAnnotation), 52 | helpText = "Serialize the low Firrtl circuit state to a lo.fir file" 53 | ) 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/main/scala/layered/stage/cli/ElkCli.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.stage.cli 4 | 5 | import firrtl.options.Shell 6 | 7 | trait ElkCli { 8 | this: Shell => 9 | 10 | parser.note("Layered Firrtl Options") 11 | 12 | Seq( 13 | StartModuleNameAnnotation, 14 | FlattenLevelAnnotation, 15 | SerializeAnnotation 16 | ).foreach(_.addOptions(parser)) 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/layered/stage/phase/CheckPhase.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.stage.phase 4 | 5 | import chisel3.stage.ChiselGeneratorAnnotation 6 | import layered.stage.ElkException 7 | import firrtl.AnnotationSeq 8 | import firrtl.options.{Phase, TargetDirAnnotation} 9 | import firrtl.stage.{FirrtlFileAnnotation, FirrtlSourceAnnotation} 10 | 11 | class CheckPhase extends Phase { 12 | 13 | override def transform(annotationSeq: AnnotationSeq): AnnotationSeq = { 14 | val sourceCount = annotationSeq.count { 15 | case _: FirrtlSourceAnnotation => true 16 | case _: FirrtlFileAnnotation => true 17 | case _: ChiselGeneratorAnnotation => true 18 | case _ => false 19 | } 20 | 21 | if (sourceCount > 1) { 22 | throw new ElkException(s"Error: Only one source of firrtl should be preset, but found $sourceCount") 23 | } else if (sourceCount < 1) { 24 | throw new ElkException(s"Error: Could not find firrtl source to diagram, perhaps you need -i ") 25 | } 26 | 27 | val targetCount = annotationSeq.count(_.isInstanceOf[TargetDirAnnotation]) 28 | if (targetCount > 1) { 29 | throw new ElkException(s"Error: There can only be 1 TargetDir, $targetCount found") 30 | } 31 | annotationSeq 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/layered/stage/phase/GenerateElkFilePhase.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.stage.phase 4 | 5 | import firrtl.options.{Dependency, Phase} 6 | import firrtl.stage.FirrtlCircuitAnnotation 7 | import firrtl.{AnnotationSeq, CircuitState} 8 | import layered.transforms.MakeGroup 9 | 10 | class GenerateElkFilePhase extends Phase { 11 | override def prerequisites = Seq(Dependency[OptionallyBuildTargetDirPhase]) 12 | 13 | override def optionalPrerequisites = Seq.empty 14 | 15 | override def optionalPrerequisiteOf = Seq.empty 16 | 17 | override def invalidates(a: Phase) = false 18 | 19 | override def transform(annotationSeq: AnnotationSeq): AnnotationSeq = { 20 | val firrtlCircuit = annotationSeq.collectFirst { case FirrtlCircuitAnnotation(circuit) => circuit }.get 21 | val circuitState = CircuitState(firrtlCircuit, annotationSeq) 22 | 23 | val transform = new MakeGroup() 24 | transform.execute(circuitState) 25 | 26 | annotationSeq 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/layered/stage/phase/GetFirrtlCircuitPhase.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.stage.phase 4 | 5 | import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} 6 | import layered.ToLoFirrtl 7 | import layered.stage.ElkException 8 | import firrtl.options.{Dependency, Phase} 9 | import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlFileAnnotation, FirrtlSourceAnnotation} 10 | import firrtl.{AnnotationSeq, FileUtils} 11 | 12 | class GetFirrtlCircuitPhase extends Phase { 13 | override def prerequisites = Seq(Dependency[CheckPhase]) 14 | override def optionalPrerequisites = Seq.empty 15 | override def optionalPrerequisiteOf = Seq.empty 16 | override def invalidates(a: Phase) = false 17 | 18 | def buildFirrtlCircuitAnnotation(firrtlText: String): FirrtlCircuitAnnotation = { 19 | val rawFirrtl = firrtl.Parser.parse(firrtlText) 20 | val processedFirrtlCircuit = (new ToLoFirrtl) 21 | .transform(Seq(FirrtlCircuitAnnotation(rawFirrtl))) 22 | .collectFirst { 23 | case circuitAnnotation: FirrtlCircuitAnnotation => circuitAnnotation 24 | } 25 | .getOrElse { 26 | throw new ElkException("Error: Could not lower firrtl circuit") 27 | } 28 | processedFirrtlCircuit 29 | } 30 | 31 | override def transform(annotations: AnnotationSeq): AnnotationSeq = { 32 | annotations.map { 33 | case FirrtlSourceAnnotation(source) => 34 | buildFirrtlCircuitAnnotation(source) 35 | case FirrtlFileAnnotation(fileName) => 36 | val source = FileUtils.getText(fileName) 37 | buildFirrtlCircuitAnnotation(source) 38 | case ChiselGeneratorAnnotation(gen) => 39 | // val filteredAnnos = annotations.filterNot(_.isInstanceOf[ChiselGeneratorAnnotation]) 40 | // val source = (new ChiselStage).emitFirrtl(gen(), annotations = filteredAnnos) 41 | val source = ChiselStage.emitFirrtl(gen()) 42 | buildFirrtlCircuitAnnotation(source) 43 | case anno => 44 | anno 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/layered/stage/phase/OptionallyBuildTargetDirPhase.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.stage.phase 4 | 5 | import java.io.File 6 | 7 | import layered.stage.ElkException 8 | import firrtl.options.{Dependency, Phase, TargetDirAnnotation} 9 | import firrtl.stage.FirrtlCircuitAnnotation 10 | import firrtl.{AnnotationSeq, FileUtils} 11 | 12 | class OptionallyBuildTargetDirPhase extends Phase { 13 | override def prerequisites = Seq(Dependency[GetFirrtlCircuitPhase]) 14 | 15 | override def optionalPrerequisites = Seq.empty 16 | 17 | override def optionalPrerequisiteOf = Seq.empty 18 | 19 | override def invalidates(a: Phase) = false 20 | 21 | override def transform(annotationSeq: AnnotationSeq): AnnotationSeq = { 22 | // TargetDirAnnotation(".") is added by default by AddDefaults Phase 23 | val dir = annotationSeq.collectFirst { case TargetDirAnnotation(targetDir) => targetDir }.get 24 | 25 | val newAnnotations: AnnotationSeq = if (dir != ".") { 26 | annotationSeq 27 | } else { 28 | val circuit = annotationSeq.collectFirst { case FirrtlCircuitAnnotation(circuit) => circuit }.get 29 | val targetDir = s"test_run_dir/${circuit.main}" 30 | val newSeq = annotationSeq.filterNot { 31 | case _: TargetDirAnnotation => true 32 | case _ => false 33 | } 34 | newSeq :+ TargetDirAnnotation(targetDir) 35 | } 36 | 37 | newAnnotations.foreach { 38 | case TargetDirAnnotation(targetDir) => 39 | val targetDirFile = new File(targetDir) 40 | if (targetDirFile.exists()) { 41 | if (!targetDirFile.isDirectory) { 42 | throw new ElkException(s"Error: Target dir ${targetDir} exists and is not a directory") 43 | } 44 | } else { 45 | FileUtils.makeDirectory(targetDir) 46 | if (!targetDirFile.exists()) { 47 | throw new ElkException(s"Error: Target dir ${targetDir} exists and is not a directory") 48 | } 49 | } 50 | case _ => 51 | } 52 | newAnnotations 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/scala/layered/transforms/MakeGroup.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.transforms 4 | 5 | import firrtl.options.TargetDirAnnotation 6 | import firrtl.{CircuitState, DependencyAPIMigration, Transform} 7 | import layered.stage.cli.{SerializeAnnotation, StartModuleNameAnnotation} 8 | 9 | import scala.collection.mutable 10 | 11 | class MakeGroup extends Transform with DependencyAPIMigration { 12 | override def prerequisites = Seq.empty 13 | 14 | override def optionalPrerequisites = Seq.empty 15 | 16 | override def optionalPrerequisiteOf = Seq.empty 17 | 18 | override def invalidates(a: Transform) = false 19 | 20 | /** 21 | * Creates a series of elk graphs starting with the startModule and continuing 22 | * through all descendant sub-modules. 23 | * @param state the state to be diagrammed 24 | * @return 25 | */ 26 | 27 | override def execute(state: CircuitState): CircuitState = { 28 | 29 | val startModuleName = state.annotations.collectFirst { 30 | case StartModuleNameAnnotation(moduleName) => moduleName 31 | }.getOrElse(state.circuit.main) 32 | 33 | val serialize = state.annotations.collectFirst { 34 | case SerializeAnnotation => true 35 | }.getOrElse(false) 36 | 37 | if (serialize) { 38 | val targetDir = state.annotations.collectFirst { case TargetDirAnnotation(dir) => dir }.get.stripSuffix("/") 39 | import java.io.BufferedWriter 40 | import java.io.FileWriter 41 | val bufferedWriter = new BufferedWriter(new FileWriter(s"$targetDir/$startModuleName.lo.fir")) 42 | try { 43 | bufferedWriter.write(state.circuit.serialize) 44 | } finally { 45 | if (bufferedWriter != null) bufferedWriter.close() 46 | } 47 | } 48 | 49 | val queue = new mutable.Queue[String]() 50 | val modulesSeen = new mutable.HashSet[String]() 51 | 52 | queue += startModuleName // set top level of diagram tree 53 | 54 | while (queue.nonEmpty) { 55 | val moduleName = queue.dequeue() 56 | if (!modulesSeen.contains(moduleName)) { 57 | 58 | val updatedAnnotations = { 59 | state.annotations.filterNot { x => 60 | x.isInstanceOf[StartModuleNameAnnotation] 61 | } :+ StartModuleNameAnnotation(moduleName) 62 | } 63 | val stateToDiagram = CircuitState(state.circuit, state.form, updatedAnnotations) 64 | 65 | val pass = new MakeOne() 66 | pass.execute(stateToDiagram) 67 | 68 | queue ++= pass.subModulesFound.map(module => module.name) 69 | } 70 | modulesSeen += moduleName 71 | } 72 | 73 | // we return the original state, all transform work is just in the interest of diagramming 74 | state 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/scala/layered/transforms/MakeOne.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.transforms 4 | 5 | import firrtl.PrimOps._ 6 | import firrtl.ir._ 7 | import firrtl.options.TargetDirAnnotation 8 | import firrtl.{CircuitState, DependencyAPIMigration, Transform, WDefInstance, WRef, WSubField, WSubIndex} 9 | import layered.elknodes._ 10 | import layered.stage.cli.{FlattenLevelAnnotation, StartModuleNameAnnotation} 11 | import layered.util.Scope 12 | 13 | import java.io.PrintWriter 14 | import scala.collection.mutable 15 | 16 | class MakeOne extends Transform with DependencyAPIMigration { 17 | override def prerequisites = Seq.empty 18 | 19 | override def optionalPrerequisites = Seq.empty 20 | 21 | override def optionalPrerequisiteOf = Seq.empty 22 | 23 | override def invalidates(a: Transform) = false 24 | 25 | val subModulesFound: mutable.HashSet[DefModule] = new mutable.HashSet[DefModule]() 26 | 27 | def execute(state: CircuitState): CircuitState = { 28 | val nameToNode: mutable.HashMap[String, ElkNode] = new mutable.HashMap() 29 | 30 | val c = state.circuit 31 | val targetDir = state.annotations.collectFirst { case TargetDirAnnotation(dir) => dir }.get.stripSuffix("/") 32 | 33 | val startModuleName = state.annotations.collectFirst { 34 | case StartModuleNameAnnotation(moduleName) => moduleName 35 | }.getOrElse(state.circuit.main) 36 | 37 | val maxDepth = state.annotations.collectFirst { 38 | case FlattenLevelAnnotation(depth) => depth 39 | }.getOrElse(1) 40 | 41 | var linesPrintedSinceFlush = 0 42 | var totalLinesPrinted = 0 43 | 44 | val printFileName = s"$targetDir/$startModuleName.graph" 45 | println(s"Creating elk graph file $printFileName") 46 | val printFile = new PrintWriter(new java.io.File(printFileName)) 47 | def pl(s: String): Unit = { 48 | printFile.println(s) 49 | val lines = s.count(_ == '\n') 50 | totalLinesPrinted += lines 51 | linesPrintedSinceFlush += lines 52 | if (linesPrintedSinceFlush > 1000) { 53 | printFile.flush() 54 | linesPrintedSinceFlush = 0 55 | } 56 | } 57 | 58 | /** 59 | * finds the specified module name in the circuit 60 | * 61 | * @param moduleName name to find 62 | * @param circuit circuit being analyzed 63 | * @return the circuit, exception occurs in not found 64 | */ 65 | def findModule(moduleName: String, circuit: Circuit): DefModule = { 66 | circuit.modules.find(module => module.name == moduleName) match { 67 | case Some(module: firrtl.ir.Module) => 68 | module 69 | case Some(externalModule: DefModule) => 70 | externalModule 71 | case _ => 72 | throw new Exception(s"Could not find top level module in $moduleName") 73 | } 74 | } 75 | 76 | /** 77 | * If rendering started, construct a graph inside moduleNode 78 | * @param modulePrefix the path to this node 79 | * @param myModule the firrtl module currently being parsed 80 | * @param moduleNode a node to elk graph notation constructed from myModule 81 | * @return 82 | */ 83 | def processModule( 84 | modulePrefix: String, 85 | myModule: DefModule, 86 | moduleNode: ModuleNode, 87 | scope: Scope = Scope(), 88 | subModuleDepth: Int = 0 89 | ): ElkNode = { 90 | 91 | /** 92 | * Half the battle here is matching references between firrtl full name for an element and 93 | * dot's reference to a connect-able module 94 | * Following functions compute the two kinds of name 95 | */ 96 | 97 | /** get firrtl's version, usually has dot's as separators 98 | * @param name components name 99 | * @return 100 | */ 101 | def getFirrtlName(name: String): String = { 102 | if (modulePrefix.isEmpty) name else modulePrefix + "." + name 103 | } 104 | 105 | def expand(name: String): String = { 106 | s"${moduleNode.absoluteName}.$name" 107 | } 108 | 109 | def reducedLongLiteral(s: String): String = { 110 | if (s.length > 32) { s.take(16) + "..." + s.takeRight(16) } 111 | else { s } 112 | } 113 | 114 | def getLiteralValue(expression: Expression): Option[String] = { 115 | expression match { 116 | case UIntLiteral(x, _) => Some(reducedLongLiteral(x.toString)) 117 | case SIntLiteral(x, _) => Some(reducedLongLiteral(x.toString)) 118 | case _ => None 119 | } 120 | } 121 | 122 | def processPrimOp(primOp: DoPrim): String = { 123 | def addBinOpNode(symbol: String): String = { 124 | val arg0ValueOpt = getLiteralValue(primOp.args.head) 125 | val arg1ValueOpt = getLiteralValue(primOp.args.tail.head) 126 | 127 | val opNode = BinaryOpNode(symbol, Some(moduleNode), arg0ValueOpt, arg1ValueOpt) 128 | moduleNode += opNode 129 | if (arg0ValueOpt.isEmpty) moduleNode.connect(opNode.in1, processExpression(primOp.args.head)) 130 | if (arg1ValueOpt.isEmpty) moduleNode.connect(opNode.in2, processExpression(primOp.args.tail.head)) 131 | opNode.asRhs 132 | } 133 | 134 | def addUnaryOpNode(symbol: String): String = { 135 | val opNode = UnaryOpNode(symbol, Some(moduleNode)) 136 | moduleNode += opNode 137 | moduleNode.connect(opNode.in1, processExpression(primOp.args.head)) 138 | opNode.asRhs 139 | } 140 | 141 | def addOneArgOneParamOpNode(symbol: String): String = { 142 | val opNode = OneArgOneParamOpNode(symbol, Some(moduleNode), primOp.consts.head) 143 | moduleNode += opNode 144 | moduleNode.connect(opNode.in1, processExpression(primOp.args.head)) 145 | opNode.asRhs 146 | } 147 | 148 | primOp.op match { 149 | case Add => addBinOpNode("add") 150 | case Sub => addBinOpNode("sub") 151 | case Mul => addBinOpNode("mul") 152 | case Div => addBinOpNode("div") 153 | case Rem => addBinOpNode("rem") 154 | 155 | case Eq => addBinOpNode("eq") 156 | case Neq => addBinOpNode("neq") 157 | case Lt => addBinOpNode("lt") 158 | case Leq => addBinOpNode("lte") 159 | case Gt => addBinOpNode("gt") 160 | case Geq => addBinOpNode("gte") 161 | 162 | case Pad => addOneArgOneParamOpNode("pad") 163 | 164 | case AsUInt => addUnaryOpNode("asUInt") 165 | case AsSInt => addUnaryOpNode("asSInt") 166 | 167 | case Shl => addOneArgOneParamOpNode("shl") 168 | case Shr => addOneArgOneParamOpNode("shr") 169 | 170 | case Dshl => addBinOpNode("dshl") 171 | case Dshr => addBinOpNode("dshr") 172 | 173 | case Cvt => addUnaryOpNode("cvt") 174 | case Neg => addUnaryOpNode("neg") 175 | case Not => addUnaryOpNode("not") 176 | 177 | case And => addBinOpNode("and") 178 | case Or => addBinOpNode("or") 179 | case Xor => addBinOpNode("xor") 180 | 181 | case Andr => addUnaryOpNode("andr") 182 | case Orr => addUnaryOpNode("orr") 183 | case Xorr => addUnaryOpNode("xorr") 184 | 185 | case Cat => addBinOpNode("cat") 186 | 187 | case Bits => 188 | val opNode = OneArgTwoParamOpNode("bits", Some(moduleNode), primOp.consts.head, primOp.consts.tail.head) 189 | moduleNode += opNode 190 | moduleNode.connect(opNode.in1, processExpression(primOp.args.head)) 191 | opNode.asRhs 192 | 193 | case Head => addOneArgOneParamOpNode("head") 194 | case Tail => addOneArgOneParamOpNode("tail") 195 | 196 | case _ => 197 | "dummy" 198 | } 199 | } 200 | 201 | def processExpression(expression: firrtl.ir.Expression): String = { 202 | def resolveRef(firrtlName: String, dotName: String): String = { 203 | nameToNode.get(firrtlName) match { 204 | case Some(node) => 205 | node.asRhs 206 | case _ => dotName 207 | } 208 | } 209 | 210 | val result = expression match { 211 | case mux: firrtl.ir.Mux => 212 | val arg0ValueOpt = getLiteralValue(mux.tval) 213 | val arg1ValueOpt = getLiteralValue(mux.fval) 214 | 215 | val muxNode = MuxNode("mux", Some(moduleNode), arg0ValueOpt, arg1ValueOpt) 216 | moduleNode += muxNode 217 | moduleNode.connect(muxNode.select, processExpression(mux.cond)) 218 | moduleNode.connect(muxNode.in1, processExpression(mux.tval)) 219 | moduleNode.connect(muxNode.in2, processExpression(mux.fval)) 220 | muxNode.asRhs 221 | case WRef(name, _, _, _) => 222 | resolveRef(getFirrtlName(name), expand(name)) 223 | case Reference(name, _, _, _) => 224 | resolveRef(getFirrtlName(name), expand(name)) 225 | case subfield: WSubField => 226 | resolveRef(getFirrtlName(subfield.serialize), expand(subfield.serialize)) 227 | case subIndex: WSubIndex => 228 | resolveRef(getFirrtlName(subIndex.serialize), expand(subIndex.serialize)) 229 | case primOp: DoPrim => 230 | processPrimOp(primOp) 231 | case c: UIntLiteral => 232 | val uInt = LiteralNode(s"lit${PrimOpNode.hash}", c.value, Some(moduleNode)) 233 | moduleNode += uInt 234 | uInt.absoluteName 235 | case c: SIntLiteral => 236 | val uInt = LiteralNode(s"lit${PrimOpNode.hash}", c.value, Some(moduleNode)) 237 | moduleNode += uInt 238 | uInt.absoluteName 239 | case other => 240 | // throw new Exception(s"renameExpression:error: unhandled expression $expression") 241 | // other.getClass.getName 242 | "" 243 | } 244 | result 245 | } 246 | 247 | def processPorts(module: DefModule): Unit = { 248 | def showPorts(dir: firrtl.ir.Direction): Unit = { 249 | module.ports.foreach { 250 | case port if port.direction == dir => 251 | val portNode = PortNode( 252 | port.name, 253 | Some(moduleNode), 254 | port.direction == firrtl.ir.Input 255 | ) 256 | nameToNode(getFirrtlName(port.name)) = portNode 257 | moduleNode += portNode 258 | case _ => None 259 | } 260 | 261 | } 262 | 263 | if (scope.doPorts()) { 264 | showPorts(firrtl.ir.Input) 265 | showPorts(firrtl.ir.Output) 266 | } 267 | } 268 | 269 | def processMemory(memory: DefMemory): Unit = { 270 | val fName = getFirrtlName(memory.name) 271 | val memNode = MemNode(memory.name, Some(moduleNode), fName, memory, nameToNode) 272 | moduleNode += memNode 273 | } 274 | 275 | def getConnectInfo(expression: Expression): String = { 276 | val (fName, dotName) = expression match { 277 | case WRef(name, _, _, _) => (getFirrtlName(name), expand(name)) 278 | case Reference(name, _, _, _) => (getFirrtlName(name), expand(name)) 279 | case subfield: WSubField => 280 | (getFirrtlName(subfield.serialize), expand(subfield.serialize)) 281 | case s: WSubIndex => (getFirrtlName(s.serialize), expand(s.serialize)) 282 | case other => 283 | println(s"Found bad connect arg $other") 284 | ("badName", "badName") 285 | } 286 | // firrtl sink => source, elk source -> sink 287 | // return sink 288 | val lhsName = nameToNode.get(fName) match { 289 | case Some(regNode: RegisterNode) => regNode.in 290 | case Some(port: PortNode) => port.absoluteName 291 | case Some(memPort: MemoryPort) => memPort.absoluteName 292 | case _ => dotName 293 | } 294 | lhsName 295 | } 296 | 297 | def processStatement(s: Statement): Unit = { 298 | s match { 299 | case block: Block => 300 | block.stmts.foreach { subStatement => 301 | processStatement(subStatement) 302 | } 303 | case con: Connect if scope.doComponents() => 304 | val lhsName = getConnectInfo(con.loc) 305 | moduleNode.connect(lhsName, processExpression(con.expr)) 306 | 307 | case Attach(_, exprs) if scope.doComponents() => 308 | exprs.toList match { 309 | case lhs :: tail => 310 | val lhsName = getConnectInfo(lhs) 311 | tail.foreach { rhs => 312 | moduleNode.analogConnect(lhsName, processExpression(rhs)) 313 | } 314 | case _ => 315 | } 316 | 317 | case WDefInstance(_, instanceName, moduleName, _) if scope.doComponents() => 318 | val subModule = findModule(moduleName, c) 319 | val newPrefix = if (modulePrefix.isEmpty) instanceName else modulePrefix + "." + instanceName 320 | val moduleNameParsed = moduleName.split("/").last 321 | val subModuleNode = 322 | ModuleNode(instanceName, Some(moduleNode), Some(moduleNameParsed), subModuleDepth + 1) 323 | moduleNode += subModuleNode 324 | 325 | subModulesFound += subModule 326 | 327 | processModule(newPrefix, subModule, subModuleNode, scope.descend, subModuleDepth + 1) 328 | 329 | moduleNode.subModuleNames += subModuleNode.absoluteName 330 | 331 | case DefNode(_, name, expression) if scope.doComponents() => 332 | val fName = getFirrtlName(name) 333 | val nodeNode = NodeNode(name, Some(moduleNode)) 334 | moduleNode += nodeNode 335 | nameToNode(fName) = nodeNode 336 | moduleNode.connect(expand(name), processExpression(expression)) 337 | case DefWire(_, name, _) if scope.doComponents() => 338 | val fName = getFirrtlName(name) 339 | val nodeNode = NodeNode(name, Some(moduleNode)) 340 | nameToNode(fName) = nodeNode 341 | case reg: DefRegister if scope.doComponents() => 342 | val regNode = RegisterNode(reg.name, Some(moduleNode)) 343 | nameToNode(getFirrtlName(reg.name)) = regNode 344 | moduleNode += regNode 345 | case memory: DefMemory if scope.doComponents() => 346 | processMemory(memory) 347 | case _ => 348 | // let everything else slide 349 | } 350 | } 351 | 352 | def removeTempWires(): Unit = { 353 | for ( key <- moduleNode.connections.keys) { 354 | // val node = moduleNode.namedNodes.getOrElse(key,null) 355 | moduleNode.namedNodes.get(key) match { 356 | case Some(_: NodeNode) => { 357 | // must be once 358 | val value = moduleNode.connections(key) 359 | for ((sink,source) <- moduleNode.connections) { 360 | if (source == key) { 361 | // must be once 362 | moduleNode.connections(sink) = value 363 | } 364 | } 365 | moduleNode.connections.remove(key) 366 | moduleNode.children -= moduleNode.namedNodes(key) 367 | } 368 | case _ => 369 | } 370 | } 371 | } 372 | 373 | myModule match { 374 | case module: firrtl.ir.Module => 375 | processPorts(myModule) 376 | processStatement(module.body) 377 | removeTempWires() 378 | case extModule: ExtModule => 379 | processPorts(extModule) 380 | case a => 381 | println(s"got a $a") 382 | } 383 | 384 | moduleNode 385 | } 386 | 387 | findModule(startModuleName, c) match { 388 | case topModule: DefModule => 389 | pl(s"// ${layered.BuildInfo.toString}, for more information, visit https://github.com/easysoc/layered-firrtl") 390 | pl("algorithm: layered") 391 | pl("hierarchyHandling: INCLUDE_CHILDREN") 392 | val topModuleNode = ModuleNode(startModuleName, parentOpt = None) 393 | processModule("", topModule, topModuleNode, Scope(0, maxDepth)) 394 | pl(topModuleNode.render) 395 | case _ => 396 | println(s"Could not find top module $startModuleName") 397 | } 398 | 399 | printFile.close() 400 | println(s"Print file closed, $totalLinesPrinted lines printed") 401 | 402 | state 403 | } 404 | } -------------------------------------------------------------------------------- /src/main/scala/layered/util/Scope.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered.util 4 | 5 | case class Scope(depth: Int = -1, maxDepth: Int = -1) { 6 | def doComponents(): Boolean = { 7 | val result = depth >= 0 && (maxDepth == -1 || depth < maxDepth) 8 | result 9 | } 10 | def doPorts(): Boolean = depth >= 0 && (maxDepth == -1 || depth <= maxDepth) 11 | 12 | def descend: Scope = { 13 | if (depth == -1) { 14 | Scope() 15 | } else { 16 | val newDepth = if (depth == maxDepth) -1 else depth + 1 17 | Scope(newDepth, maxDepth) 18 | } 19 | } 20 | 21 | override def toString: String = { 22 | val s = (depth, maxDepth) match { 23 | case (-1, -1) => "out" 24 | case (_, -1) => "in, no depth limit" 25 | case (_, _) => s"in, do ports ${doPorts()}, do components ${doComponents()}" 26 | } 27 | s"Scope($depth, $maxDepth): $s" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/scala/layered/AttachExample.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered 4 | 5 | import java.io.{ByteArrayOutputStream, PrintStream} 6 | 7 | import layered.stage.{ElkStage} 8 | import firrtl.FileUtils 9 | import firrtl.options.TargetDirAnnotation 10 | import firrtl.stage.FirrtlSourceAnnotation 11 | import org.scalatest.freespec.AnyFreeSpec 12 | import org.scalatest.matchers.should.Matchers 13 | 14 | import scala.io.Source 15 | 16 | /** 17 | * Checks that attaches are double headed arrows connected to each node 18 | */ 19 | class AttachExample extends AnyFreeSpec with Matchers { 20 | 21 | """This example contains Analog Connects""" in { 22 | val dir = "test_run_dir/attach" 23 | FileUtils.makeDirectory(dir) 24 | 25 | val firrtl = s""" 26 | |circuit AttachTest : 27 | | module AttachTest : 28 | | input clock : Clock 29 | | input reset : UInt<1> 30 | | 31 | | output io : {in : Analog<1>, out : Analog<1>} 32 | | output io2 : {in : Analog<1>, out1 : Analog<1>, out2 : Analog<1>, out3 : Analog<1>} 33 | | 34 | | attach (io.in, io.out) @[cmd8.sc 6:9] 35 | | 36 | | attach (io2.in, io2.out1, io2.out2, io2.out3) @[cmd8.sc 6:9] 37 | | 38 | """.stripMargin 39 | 40 | val outputBuf = new ByteArrayOutputStream() 41 | Console.withOut(new PrintStream(outputBuf)) { 42 | val annos = Seq(TargetDirAnnotation(dir), FirrtlSourceAnnotation(firrtl)) 43 | (new ElkStage).execute(Array.empty, annos) 44 | } 45 | val output = outputBuf.toString 46 | 47 | // confirm user gets message 48 | output should include("Creating elk graph file test_run_dir/attach/AttachTest.graph") 49 | 50 | val lines = Source.fromFile(s"$dir/AttachTest.graph").getLines() 51 | 52 | val targets = Seq( 53 | s""" edge e11 : AttachTest.io2_out1 -> AttachTest.io2_in""", 54 | s""" edge e12 : AttachTest.io2_out2 -> AttachTest.io2_in""", 55 | s""" edge e13 : AttachTest.io2_out3 -> AttachTest.io2_in""", 56 | s""" edge e14 : AttachTest.io_out -> AttachTest.io_in""" 57 | ) 58 | 59 | targets.foreach { target => 60 | lines.exists { line => line.contains(target) } should be(true) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/scala/layered/FirExample.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered 4 | 5 | import chisel3._ 6 | import chisel3.stage.ChiselStage 7 | import firrtl.options.TargetDirAnnotation 8 | import firrtl.stage.FirrtlSourceAnnotation 9 | import layered.stage.ElkStage 10 | 11 | import scala.language.reflectiveCalls 12 | 13 | class MyManyDynamicElementVecFir(length: Int) extends Module { 14 | //noinspection TypeAnnotation 15 | val io = IO(new Bundle { 16 | val in = Input(UInt(8.W)) 17 | val out = Output(UInt(8.W)) 18 | val consts = Input(Vec(length, UInt(8.W))) 19 | }) 20 | 21 | val taps: Seq[UInt] = Seq(io.in) ++ Seq.fill(io.consts.length - 1)(RegInit(0.U(8.W))) 22 | taps.zip(taps.tail).foreach { case (a, b) => b := a } 23 | 24 | io.out := taps.zip(io.consts).map { case (a, b) => a * b }.reduce(_ + _) 25 | } 26 | 27 | object FirExample extends App { 28 | 29 | val targetDir = "test_run_dir/fir_example" 30 | 31 | val firrtl = ChiselStage.emitFirrtl( 32 | new MyManyDynamicElementVecFir(length = 10) 33 | ) 34 | 35 | val annos = Seq( 36 | TargetDirAnnotation(targetDir), 37 | FirrtlSourceAnnotation(firrtl) 38 | ) 39 | (new ElkStage).transform(annos) 40 | } 41 | -------------------------------------------------------------------------------- /src/test/scala/layered/GCD.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered 4 | 5 | import chisel3._ 6 | import chisel3.stage.ChiselGeneratorAnnotation 7 | import layered.stage.ElkStage 8 | 9 | class GCD extends Module { 10 | val io = IO(new Bundle { 11 | val value1 = Input(UInt(16.W)) 12 | val value2 = Input(UInt(16.W)) 13 | val loadingValues = Input(Bool()) 14 | val outputGCD = Output(UInt(16.W)) 15 | val outputValid = Output(Bool()) 16 | }) 17 | 18 | val x = RegInit(0.U(16.W)) 19 | val y = RegInit(0.U(16.W)) 20 | 21 | when(x > y) { x := x - y } 22 | .otherwise { y := y - x } 23 | 24 | when(io.loadingValues) { 25 | x := io.value1 26 | y := io.value2 27 | } 28 | 29 | io.outputGCD := x 30 | io.outputValid := y === 0.U 31 | } 32 | 33 | object GCDTester extends App { 34 | 35 | val targetDir = "test_run_dir/gcd" 36 | 37 | (new ElkStage).execute( 38 | Array("--target-dir", targetDir, "--lowFir"), 39 | Seq(ChiselGeneratorAnnotation(() => new GCD)) 40 | ) 41 | 42 | // def getLowFirrtl(gen: => RawModule) = { 43 | // (new chisel3.stage.ChiselStage).execute(Array("-td", targetDir, "-X", "low"), 44 | // Seq(ChiselGeneratorAnnotation(() => gen))) 45 | // } 46 | // getLowFirrtl(new GCD) 47 | (new chisel3.stage.ChiselStage).emitVerilog(new GCD,Array("-td", targetDir) ) 48 | } 49 | -------------------------------------------------------------------------------- /src/test/scala/layered/HierarchicalModulesExample.scala: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | package layered 4 | 5 | import chisel3._ 6 | import chisel3.stage.ChiselGeneratorAnnotation 7 | import firrtl.options.TargetDirAnnotation 8 | import layered.stage.ElkStage 9 | 10 | /** 11 | * This class has parameterizable widths, it will generate different hardware 12 | * 13 | * @param widthC io width 14 | */ 15 | //noinspection TypeAnnotation 16 | class VizModC(widthC: Int) extends Module { 17 | val io = IO(new Bundle { 18 | val in = Input(UInt(widthC.W)) 19 | val out = Output(UInt(widthC.W)) 20 | }) 21 | io.out := io.in 22 | } 23 | 24 | /** 25 | * instantiates a C of a particular size, VizModA does not generate different hardware 26 | * based on it's parameter 27 | * @param annoParam parameter is only used in annotation not in circuit 28 | */ 29 | class VizModA(annoParam: Int) extends Module { 30 | //noinspection TypeAnnotation 31 | val io = IO(new Bundle { 32 | val in = Input(UInt()) 33 | val out = Output(UInt()) 34 | }) 35 | val modC = Module(new VizModC(16)) 36 | val modB = Module(new VizModB(16)) 37 | val modB2 = Module(new VizModB(16)) 38 | 39 | modC.io.in := io.in 40 | modB.io.in := io.in 41 | modB2.io.in := io.in 42 | io.out := modC.io.out + modB.io.out + modB2.io.out 43 | } 44 | 45 | class VizModB(widthB: Int) extends Module { 46 | //noinspection TypeAnnotation 47 | val io = IO(new Bundle { 48 | val in = Input(UInt(widthB.W)) 49 | val out = Output(UInt(widthB.W)) 50 | }) 51 | val modC = Module(new VizModC(widthB)) 52 | modC.io.in := io.in 53 | io.out := modC.io.out 54 | } 55 | 56 | class TopOfVisualizer extends Module { 57 | //noinspection TypeAnnotation 58 | val io = IO(new Bundle { 59 | val in1 = Input(UInt(32.W)) 60 | val in2 = Input(UInt(32.W)) 61 | val select = Input(Bool()) 62 | val out = Output(UInt(32.W)) 63 | val memOut = Output(UInt(32.W)) 64 | }) 65 | val x = Reg(UInt(32.W)) 66 | val y = Reg(UInt(32.W)) 67 | 68 | val myMem = Mem(16, UInt(32.W)) 69 | 70 | io.memOut := DontCare 71 | 72 | val modA = Module(new VizModA(64)) 73 | val modB = Module(new VizModB(32)) 74 | val modC = Module(new VizModC(48)) 75 | 76 | when(io.select) { 77 | x := io.in1 78 | myMem(io.in1) := io.in2 79 | }.otherwise { 80 | x := io.in2 81 | io.memOut := myMem(io.in1) 82 | } 83 | 84 | modA.io.in := x 85 | 86 | y := modA.io.out + io.in2 + modB.io.out + modC.io.out 87 | io.out := y 88 | 89 | modB.io.in := x 90 | modC.io.in := x 91 | } 92 | 93 | object HierarchicalModulesExample extends App { 94 | 95 | val annos = Seq( 96 | TargetDirAnnotation("test_run_dir/visualizer"), 97 | ChiselGeneratorAnnotation(() => new TopOfVisualizer) 98 | ) 99 | 100 | (new ElkStage).execute(Array("--lowFir"), annos) 101 | // (new ElkStage).execute(Array("--flatten", "2"), annos) 102 | // (new ElkStage).execute(Array("--top", "VizModA"), annos) 103 | } 104 | --------------------------------------------------------------------------------