├── .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 | 
18 | **Solid Theme(--flatten 2)**
19 |
20 | 
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 |
--------------------------------------------------------------------------------
/images/VizModA.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------