├── .github
└── workflows
│ └── nodejs.yml
├── .gitignore
├── ChangeLog.md
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── process.js
├── run_tests.sh
├── src
└── index.ts
├── tests
├── cycleadder.sv
├── cycleadder_arst.sv
├── dff_masterslave.sv
├── dlatch_gate.sv
├── fsm.sv
├── fulladder.sv
├── grouping_test.sv
├── lfsr.sv
├── prio_encoder.sv
├── ram.sv
├── reduce_and.sv
├── rom.sv
├── serialadder.sv
├── sr_beh.sv
├── sr_gate.sv
├── sr_neg_gate.sv
└── z.sv
└── tsconfig.json
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | matrix:
12 | node-version: [18.x]
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - run: sudo apt-get install -y yosys
17 | - name: Use Node.js ${{ matrix.node-version }}
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: ${{ matrix.node-version }}
21 | - run: npm install
22 | - run: npm run build --if-present
23 | - run: npm test
24 | env:
25 | CI: true
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /dist
3 | *.swp
4 | *.swo
5 |
--------------------------------------------------------------------------------
/ChangeLog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | ## [0.7.0] -- 2023-03-2023
5 |
6 | ### Added
7 |
8 | - Support for `$lut` cells
9 | - Support for generating 7-segment display outputs
10 |
11 | ## [0.6.0] -- 2022-02-02
12 |
13 | ### Added
14 |
15 | - Support for Yosys 0.11 cells `$aldff`, `$aldffe`
16 | - Added a function `yosys2digitaljs` for converting a Yosys JSON output obtained elsewhere, without running Yosys locally
17 |
18 | ## [0.5.0] -- 2021-10-06
19 |
20 | ### Added
21 |
22 | - Support for Yosys 0.10 cells `$adffe`, `$sdff`, `$sdffe`, `$sdffce`, `$adlatch`, `$sr`, `$dffsr`, `$dffsre`
23 | - Support for Yosys 0.10 improved memory cell `$mem_v2`
24 | - Support for source code positions
25 |
26 | ### Changed
27 |
28 | - Support for JSON output generated by Yosys 0.10
29 |
30 | ## [0.4.0] -- 2021-08-20
31 |
32 | ### Added
33 |
34 | - Linting via Verilator
35 |
36 | ### Changed
37 |
38 | - Ported to TypeScript
39 | - Removed ad-hoc transformations, as they are now performed in DigitalJS
40 |
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2018 Marek Materzok
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 |
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 |
7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 |
9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # yosys2digitaljs
2 | This program converts JSON netlist output generated by [Yosys](https://yosyshq.net/yosys/)
3 | circuit synthesis software (Github repo [here](https://github.com/YosysHQ/yosys/)) for use with the
4 | [DigitalJS](http://github.com/tilk/digitaljs) graphical circuit simulator.
5 |
6 | # Usage
7 | You need to have [Yosys](https://yosyshq.net/yosys/) installed to run
8 | yosys2digitaljs. For example, in Debian/Ubuntu, run:
9 | ```bash
10 | apt install yosys
11 | ```
12 | Clone the repository and install dependencies using npm:
13 | ```bash
14 | git clone https://github.com/tilk/yosys2digitaljs.git
15 | cd yosys2digitaljs
16 | npm install
17 | ```
18 | Now you can run yosys2digitaljs on your Verilog or SystemVerilog files:
19 | ```bash
20 | ./process.js file.v file.sv ...
21 | ```
22 | The generated JSON is printed on standard output.
23 |
24 | # API
25 | Yosys2digitaljs can be used as a library. The API is promise (or async/await) based. Available functions are:
26 |
27 | - `yosys2digitaljs(json, options)` - converts the Yosys JSON output `json` (passed as an JS object) to a DigitalJS representation of the same circuit.
28 | - `process_sv(sv_text, options)` - converts a single SystemVerilog source passed as a string.
29 | - `process_files(texts, options)` - converts multiple Verilog/SystemVerilog sources. The `texts` parameter is an object, with keys being file names, and corresponding values containing the file contents as strings. Example: `{ 'test.sv': 'module test; ...' }`.
30 | - `process(filenames, dirname, options)` - converts Verilog/SystemVerilog sources saved on the filesystem under names `filenames` in the directory `dirname`.
31 |
32 | The functions return a promise, which fulfills with an object value with following keys:
33 |
34 | - `output`: the conversion result as JS object, which can be stringified to JSON.
35 | - `yosys_stdout` and `yosys_stderr`: standard output and error output received from Yosys.
36 |
37 | For bigger example, see the [online app demo](http://github.com/tilk/digitaljs_online).
38 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yosys2digitaljs",
3 | "version": "0.8.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "yosys2digitaljs",
9 | "version": "0.8.0",
10 | "license": "BSD-2-Clause",
11 | "dependencies": {
12 | "3vl": "^1.0.1",
13 | "big-integer": "^1.6.49",
14 | "hashmap": "^2.4.0",
15 | "minimist": "^1.2.5",
16 | "sanitize-filename": "^1.6.3",
17 | "tmp-promise": "^1.1.0",
18 | "topsort": "^0.0.2"
19 | },
20 | "devDependencies": {
21 | "@types/node": "^16.10.3",
22 | "typescript": "^4.4.3"
23 | },
24 | "engines": {
25 | "node": ">=8.11.1"
26 | }
27 | },
28 | "node_modules/@types/node": {
29 | "version": "16.10.3",
30 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz",
31 | "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==",
32 | "dev": true
33 | },
34 | "node_modules/3vl": {
35 | "version": "1.0.1",
36 | "resolved": "https://registry.npmjs.org/3vl/-/3vl-1.0.1.tgz",
37 | "integrity": "sha512-Z75UMwicaLbb3p1FjL1cBxGq/rpJIv73h5LtHl3FWD7vfOSjxzU37LAYhyMYul4tZa0wZtsJHtqCYeisIsVlJw=="
38 | },
39 | "node_modules/balanced-match": {
40 | "version": "1.0.0",
41 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
42 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
43 | },
44 | "node_modules/big-integer": {
45 | "version": "1.6.49",
46 | "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.49.tgz",
47 | "integrity": "sha512-KJ7VhqH+f/BOt9a3yMwJNmcZjG53ijWMTjSAGMveQWyLwqIiwkjNP5PFgDob3Snnx86SjDj6I89fIbv0dkQeNw==",
48 | "engines": {
49 | "node": ">=0.6"
50 | }
51 | },
52 | "node_modules/bluebird": {
53 | "version": "3.5.5",
54 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
55 | "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w=="
56 | },
57 | "node_modules/brace-expansion": {
58 | "version": "1.1.11",
59 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
60 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
61 | "dependencies": {
62 | "balanced-match": "^1.0.0",
63 | "concat-map": "0.0.1"
64 | }
65 | },
66 | "node_modules/concat-map": {
67 | "version": "0.0.1",
68 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
69 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
70 | },
71 | "node_modules/fs.realpath": {
72 | "version": "1.0.0",
73 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
74 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
75 | },
76 | "node_modules/glob": {
77 | "version": "7.1.4",
78 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
79 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
80 | "dependencies": {
81 | "fs.realpath": "^1.0.0",
82 | "inflight": "^1.0.4",
83 | "inherits": "2",
84 | "minimatch": "^3.0.4",
85 | "once": "^1.3.0",
86 | "path-is-absolute": "^1.0.0"
87 | },
88 | "engines": {
89 | "node": "*"
90 | }
91 | },
92 | "node_modules/hashmap": {
93 | "version": "2.4.0",
94 | "resolved": "https://registry.npmjs.org/hashmap/-/hashmap-2.4.0.tgz",
95 | "integrity": "sha512-Ngj48lhnxJdnBAEVbubKBJuN1elfVLZJs94ZixRi98X3GCU4v6pgj9qRkHt6H8WaVJ69Wv0r1GhtS7hvF9zCgg==",
96 | "engines": {
97 | "node": "*"
98 | }
99 | },
100 | "node_modules/inflight": {
101 | "version": "1.0.6",
102 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
103 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
104 | "dependencies": {
105 | "once": "^1.3.0",
106 | "wrappy": "1"
107 | }
108 | },
109 | "node_modules/inherits": {
110 | "version": "2.0.4",
111 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
112 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
113 | },
114 | "node_modules/minimatch": {
115 | "version": "3.1.2",
116 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
117 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
118 | "dependencies": {
119 | "brace-expansion": "^1.1.7"
120 | },
121 | "engines": {
122 | "node": "*"
123 | }
124 | },
125 | "node_modules/minimist": {
126 | "version": "1.2.8",
127 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
128 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
129 | "funding": {
130 | "url": "https://github.com/sponsors/ljharb"
131 | }
132 | },
133 | "node_modules/once": {
134 | "version": "1.4.0",
135 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
136 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
137 | "dependencies": {
138 | "wrappy": "1"
139 | }
140 | },
141 | "node_modules/path-is-absolute": {
142 | "version": "1.0.1",
143 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
144 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
145 | "engines": {
146 | "node": ">=0.10.0"
147 | }
148 | },
149 | "node_modules/rimraf": {
150 | "version": "2.6.3",
151 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
152 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
153 | "dependencies": {
154 | "glob": "^7.1.3"
155 | },
156 | "bin": {
157 | "rimraf": "bin.js"
158 | }
159 | },
160 | "node_modules/sanitize-filename": {
161 | "version": "1.6.3",
162 | "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
163 | "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
164 | "dependencies": {
165 | "truncate-utf8-bytes": "^1.0.0"
166 | }
167 | },
168 | "node_modules/tmp": {
169 | "version": "0.1.0",
170 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
171 | "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
172 | "dependencies": {
173 | "rimraf": "^2.6.3"
174 | },
175 | "engines": {
176 | "node": ">=6"
177 | }
178 | },
179 | "node_modules/tmp-promise": {
180 | "version": "1.1.0",
181 | "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-1.1.0.tgz",
182 | "integrity": "sha512-8+Ah9aB1IRXCnIOxXZ0uFozV1nMU5xiu7hhFVUSxZ3bYu+psD4TzagCzVbexUCgNNGJnsmNDQlS4nG3mTyoNkw==",
183 | "dependencies": {
184 | "bluebird": "^3.5.0",
185 | "tmp": "0.1.0"
186 | }
187 | },
188 | "node_modules/topsort": {
189 | "version": "0.0.2",
190 | "resolved": "https://registry.npmjs.org/topsort/-/topsort-0.0.2.tgz",
191 | "integrity": "sha1-Ll4O6KFDlBfxAdW5stA15iAmMyE=",
192 | "engines": {
193 | "node": ">= 0.10.0"
194 | }
195 | },
196 | "node_modules/truncate-utf8-bytes": {
197 | "version": "1.0.2",
198 | "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
199 | "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=",
200 | "dependencies": {
201 | "utf8-byte-length": "^1.0.1"
202 | }
203 | },
204 | "node_modules/typescript": {
205 | "version": "4.4.3",
206 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
207 | "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
208 | "dev": true,
209 | "bin": {
210 | "tsc": "bin/tsc",
211 | "tsserver": "bin/tsserver"
212 | },
213 | "engines": {
214 | "node": ">=4.2.0"
215 | }
216 | },
217 | "node_modules/utf8-byte-length": {
218 | "version": "1.0.4",
219 | "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
220 | "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E="
221 | },
222 | "node_modules/wrappy": {
223 | "version": "1.0.2",
224 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
225 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
226 | }
227 | },
228 | "dependencies": {
229 | "@types/node": {
230 | "version": "16.10.3",
231 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz",
232 | "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==",
233 | "dev": true
234 | },
235 | "3vl": {
236 | "version": "1.0.1",
237 | "resolved": "https://registry.npmjs.org/3vl/-/3vl-1.0.1.tgz",
238 | "integrity": "sha512-Z75UMwicaLbb3p1FjL1cBxGq/rpJIv73h5LtHl3FWD7vfOSjxzU37LAYhyMYul4tZa0wZtsJHtqCYeisIsVlJw=="
239 | },
240 | "balanced-match": {
241 | "version": "1.0.0",
242 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
243 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
244 | },
245 | "big-integer": {
246 | "version": "1.6.49",
247 | "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.49.tgz",
248 | "integrity": "sha512-KJ7VhqH+f/BOt9a3yMwJNmcZjG53ijWMTjSAGMveQWyLwqIiwkjNP5PFgDob3Snnx86SjDj6I89fIbv0dkQeNw=="
249 | },
250 | "bluebird": {
251 | "version": "3.5.5",
252 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
253 | "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w=="
254 | },
255 | "brace-expansion": {
256 | "version": "1.1.11",
257 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
258 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
259 | "requires": {
260 | "balanced-match": "^1.0.0",
261 | "concat-map": "0.0.1"
262 | }
263 | },
264 | "concat-map": {
265 | "version": "0.0.1",
266 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
267 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
268 | },
269 | "fs.realpath": {
270 | "version": "1.0.0",
271 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
272 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
273 | },
274 | "glob": {
275 | "version": "7.1.4",
276 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
277 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
278 | "requires": {
279 | "fs.realpath": "^1.0.0",
280 | "inflight": "^1.0.4",
281 | "inherits": "2",
282 | "minimatch": "^3.0.4",
283 | "once": "^1.3.0",
284 | "path-is-absolute": "^1.0.0"
285 | }
286 | },
287 | "hashmap": {
288 | "version": "2.4.0",
289 | "resolved": "https://registry.npmjs.org/hashmap/-/hashmap-2.4.0.tgz",
290 | "integrity": "sha512-Ngj48lhnxJdnBAEVbubKBJuN1elfVLZJs94ZixRi98X3GCU4v6pgj9qRkHt6H8WaVJ69Wv0r1GhtS7hvF9zCgg=="
291 | },
292 | "inflight": {
293 | "version": "1.0.6",
294 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
295 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
296 | "requires": {
297 | "once": "^1.3.0",
298 | "wrappy": "1"
299 | }
300 | },
301 | "inherits": {
302 | "version": "2.0.4",
303 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
304 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
305 | },
306 | "minimatch": {
307 | "version": "3.1.2",
308 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
309 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
310 | "requires": {
311 | "brace-expansion": "^1.1.7"
312 | }
313 | },
314 | "minimist": {
315 | "version": "1.2.8",
316 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
317 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
318 | },
319 | "once": {
320 | "version": "1.4.0",
321 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
322 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
323 | "requires": {
324 | "wrappy": "1"
325 | }
326 | },
327 | "path-is-absolute": {
328 | "version": "1.0.1",
329 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
330 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
331 | },
332 | "rimraf": {
333 | "version": "2.6.3",
334 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
335 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
336 | "requires": {
337 | "glob": "^7.1.3"
338 | }
339 | },
340 | "sanitize-filename": {
341 | "version": "1.6.3",
342 | "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
343 | "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
344 | "requires": {
345 | "truncate-utf8-bytes": "^1.0.0"
346 | }
347 | },
348 | "tmp": {
349 | "version": "0.1.0",
350 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
351 | "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
352 | "requires": {
353 | "rimraf": "^2.6.3"
354 | }
355 | },
356 | "tmp-promise": {
357 | "version": "1.1.0",
358 | "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-1.1.0.tgz",
359 | "integrity": "sha512-8+Ah9aB1IRXCnIOxXZ0uFozV1nMU5xiu7hhFVUSxZ3bYu+psD4TzagCzVbexUCgNNGJnsmNDQlS4nG3mTyoNkw==",
360 | "requires": {
361 | "bluebird": "^3.5.0",
362 | "tmp": "0.1.0"
363 | }
364 | },
365 | "topsort": {
366 | "version": "0.0.2",
367 | "resolved": "https://registry.npmjs.org/topsort/-/topsort-0.0.2.tgz",
368 | "integrity": "sha1-Ll4O6KFDlBfxAdW5stA15iAmMyE="
369 | },
370 | "truncate-utf8-bytes": {
371 | "version": "1.0.2",
372 | "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
373 | "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=",
374 | "requires": {
375 | "utf8-byte-length": "^1.0.1"
376 | }
377 | },
378 | "typescript": {
379 | "version": "4.4.3",
380 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
381 | "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
382 | "dev": true
383 | },
384 | "utf8-byte-length": {
385 | "version": "1.0.4",
386 | "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
387 | "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E="
388 | },
389 | "wrappy": {
390 | "version": "1.0.2",
391 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
392 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
393 | }
394 | }
395 | }
396 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yosys2digitaljs",
3 | "version": "0.8.0",
4 | "description": "Export Yosys netlists to a logic simulator",
5 | "main": "dist/index",
6 | "types": "dist/index.d.ts",
7 | "scripts": {
8 | "prepare": "npm run build",
9 | "cjs": "tsc -m commonjs",
10 | "build": "npm run cjs",
11 | "test": "./run_tests.sh"
12 | },
13 | "author": "Marek Materzok",
14 | "license": "BSD-2-Clause",
15 | "dependencies": {
16 | "3vl": "^1.0.1",
17 | "big-integer": "^1.6.49",
18 | "hashmap": "^2.4.0",
19 | "minimist": "^1.2.5",
20 | "sanitize-filename": "^1.6.3",
21 | "tmp-promise": "^1.1.0",
22 | "topsort": "^0.0.2"
23 | },
24 | "engines": {
25 | "node": ">=8.11.1"
26 | },
27 | "homepage": "https://github.com/tilk/yosys2digitaljs",
28 | "repository": {
29 | "type": "git",
30 | "url": "https://github.com/tilk/yosys2digitaljs.git"
31 | },
32 | "devDependencies": {
33 | "@types/node": "^16.10.3",
34 | "typescript": "^4.4.3"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/process.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict";
3 |
4 | const fs = require('fs');
5 | const argv = require('minimist')(
6 | process.argv.slice(2),
7 | {boolean: ["optimize", "yosys_out", "yosys_output", "html", "no_io_ui", "tmpdir", "noindent", "fsmexpand"],
8 | string: ["fsm"],
9 | default: {fsm: true}}
10 | );
11 | const util = require('util');
12 |
13 | function read_files(l) {
14 | const ret = {};
15 | for (const name of l) {
16 | ret[name] = fs.readFileSync(name);
17 | };
18 | return ret;
19 | }
20 |
21 | const header = `
22 |
23 |
24 |
25 |
26 |
27 |
28 | `;
29 |
30 | if (argv._.length === 0) {
31 | console.error('No Verilog files passed!');
32 | process.exit(1);
33 | }
34 | const yosys2digitaljs = require('./dist/index.js');
35 | const opts = {};
36 | if (argv.optimize) opts.optimize = true;
37 | if (argv.fsm) opts.fsm = argv.fsm;
38 | if (argv.fsmexpand) opts.fsmexpand = true;
39 | if (argv.lint) opts.lint = true;
40 | if (argv.propagation !== undefined) opts.propagation = Number(argv.propagation);
41 | const result = argv.tmpdir ? yosys2digitaljs.process_files(read_files(argv._), opts) : yosys2digitaljs.process(argv._, null, opts);
42 | result.then(res => {
43 | if (argv.html) {
44 | console.log(header);
45 | console.log('');
69 | };
70 | })
71 | .catch(res => {
72 | console.error('Yosys failed!');
73 | console.error(util.inspect(res, {showHidden: false, depth: null, colors: process.stdout.isTTY && process.stdout.hasColors()}));
74 | process.exit(1);
75 | });
76 |
77 |
--------------------------------------------------------------------------------
/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for X in tests/*.sv; do
4 | echo $X
5 | ./process.js $X > /dev/null || exit 1
6 | done
7 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict";
3 |
4 | import * as tmp from 'tmp-promise';
5 | import * as child_process from 'child_process';
6 | import * as assert from 'assert';
7 | import * as fs from 'fs';
8 | import * as path from 'path';
9 | import * as HashMap from 'hashmap';
10 | import * as bigInt from 'big-integer';
11 | import {promisify} from 'util';
12 | import {Vector3vl, Mem3vl} from '3vl';
13 |
14 | const topsort: (edges:T[][], options?:{continueOnCircularDependency: boolean}) => T[] = require('topsort');
15 | const sanitize = require("sanitize-filename");
16 |
17 | const unary_gates = new Set([
18 | '$not', '$neg', '$pos', '$reduce_and', '$reduce_or', '$reduce_xor',
19 | '$reduce_xnor', '$reduce_bool', '$logic_not']);
20 | const binary_gates = new Set([
21 | '$and', '$or', '$xor', '$xnor',
22 | '$add', '$sub', '$mul', '$div', '$mod', '$pow',
23 | '$lt', '$le', '$eq', '$ne', '$ge', '$gt', '$eqx', '$nex',
24 | '$shl', '$shr', '$sshl', '$sshr', '$shift', '$shiftx',
25 | '$logic_and', '$logic_or']);
26 | const gate_subst = new Map([
27 | ['$not', 'Not'],
28 | ['$and', 'And'],
29 | ['$nand', 'Nand'],
30 | ['$or', 'Or'],
31 | ['$nor', 'Nor'],
32 | ['$xor', 'Xor'],
33 | ['$xnor', 'Xnor'],
34 | ['$reduce_and', 'AndReduce'],
35 | ['$reduce_nand', 'NandReduce'],
36 | ['$reduce_or', 'OrReduce'],
37 | ['$reduce_nor', 'NorReduce'],
38 | ['$reduce_xor', 'XorReduce'],
39 | ['$reduce_xnor', 'XnorReduce'],
40 | ['$reduce_bool', 'OrReduce'],
41 | ['$logic_not', 'NorReduce'],
42 | ['$repeater', 'Repeater'],
43 | ['$shl', 'ShiftLeft'],
44 | ['$shr', 'ShiftRight'],
45 | ['$lt', 'Lt'],
46 | ['$le', 'Le'],
47 | ['$eq', 'Eq'],
48 | ['$ne', 'Ne'],
49 | ['$gt', 'Gt'],
50 | ['$ge', 'Ge'],
51 | ['$constant', 'Constant'],
52 | ['$neg', 'Negation'],
53 | ['$pos', 'UnaryPlus'],
54 | ['$add', 'Addition'],
55 | ['$sub', 'Subtraction'],
56 | ['$mul', 'Multiplication'],
57 | ['$div', 'Division'],
58 | ['$mod', 'Modulo'],
59 | ['$pow', 'Power'],
60 | ['$mux', 'Mux'],
61 | ['$pmux', 'Mux1Hot'],
62 | ['$mem', 'Memory'],
63 | ['$mem_v2', 'Memory'],
64 | ['$lut', 'Memory'],
65 | ['$fsm', 'FSM'],
66 | ['$clock', 'Clock'],
67 | ['$button', 'Button'],
68 | ['$lamp', 'Lamp'],
69 | ['$numdisplay', 'NumDisplay'],
70 | ['$numentry', 'NumEntry'],
71 | ['$input', 'Input'],
72 | ['$output', 'Output'],
73 | ['$busgroup', 'BusGroup'],
74 | ['$busungroup', 'BusUngroup'],
75 | ['$busslice', 'BusSlice'],
76 | ['$zeroextend', 'ZeroExtend'],
77 | ['$signextend', 'SignExtend'],
78 | ['$reduce_bool', 'OrReduce'],
79 | ['$eqx', 'Eq'],
80 | ['$nex', 'Ne'],
81 | ['$sshl', 'ShiftLeft'],
82 | ['$sshr', 'ShiftRight'],
83 | ['$shift', 'ShiftRight'],
84 | ['$shiftx', 'ShiftRight'],
85 | ['$logic_and', 'And'],
86 | ['$logic_or', 'Or'],
87 | ['$dff', 'Dff'],
88 | ['$dffe', 'Dff'],
89 | ['$adff', 'Dff'],
90 | ['$adffe', 'Dff'],
91 | ['$sdff', 'Dff'],
92 | ['$sdffe', 'Dff'],
93 | ['$sdffce', 'Dff'],
94 | ['$dlatch', 'Dff'],
95 | ['$adlatch', 'Dff'],
96 | ['$sr', 'Dff'],
97 | ['$dffsr', 'Dff'],
98 | ['$dffsre', 'Dff'],
99 | ['$aldff', 'Dff'],
100 | ['$aldffe', 'Dff']]);
101 | const gate_negations = new Map([
102 | ['And', 'Nand'],
103 | ['Nand', 'And'],
104 | ['Nor', 'Or'],
105 | ['Or', 'Nor'],
106 | ['Xor', 'Xnor'],
107 | ['Xnor', 'Xor'],
108 | ['AndReduce', 'NandReduce'],
109 | ['NandReduce', 'AndReduce'],
110 | ['NorReduce', 'OrReduce'],
111 | ['OrReduce', 'NorReduce'],
112 | ['XorReduce', 'XnorReduce'],
113 | ['XnorReduce', 'XorReduce']]);
114 |
115 | namespace Digitaljs {
116 |
117 | export type FilePosition = {
118 | line: number,
119 | column: number
120 | };
121 |
122 | export type SourcePosition = {
123 | name: string,
124 | from: FilePosition,
125 | to: FilePosition
126 | };
127 |
128 | export type MemReadPort = {
129 | clock_polarity?: boolean,
130 | enable_polarity?: boolean,
131 | arst_polarity?: boolean,
132 | srst_polarity?: boolean,
133 | enable_srst?: boolean,
134 | transparent?: boolean | boolean[],
135 | collision?: boolean | boolean[],
136 | init_value?: string,
137 | arst_value?: string,
138 | srst_value?: string
139 | };
140 |
141 | export type MemWritePort = {
142 | clock_polarity?: boolean,
143 | enable_polarity?: boolean,
144 | no_bit_enable?: boolean
145 | };
146 |
147 | export type Device = {
148 | type: string,
149 | source_positions?: SourcePosition[],
150 | [key: string]: any
151 | };
152 |
153 | export type Port = {
154 | id: string,
155 | port: string
156 | };
157 |
158 | export type Connector = {
159 | from: Port,
160 | to: Port,
161 | name?: string,
162 | source_positions?: SourcePosition[]
163 | };
164 |
165 | export type Module = {
166 | devices: { [key: string]: Device },
167 | connectors: Connector[]
168 | };
169 |
170 | export type TopModule = Module & {
171 | subcircuits: { [key: string]: Module }
172 | };
173 |
174 | };
175 |
176 | namespace Yosys {
177 |
178 | export const ConstChars = ["0", "1", "x", "z"] as const;
179 |
180 | export type BitChar = (typeof ConstChars)[number];
181 |
182 | export type Bit = number | BitChar;
183 |
184 | export type BitVector = Bit[];
185 |
186 | export type Port = {
187 | direction: 'input' | 'output' | 'inout',
188 | bits: any
189 | };
190 |
191 | export type Parameters = {
192 | WIDTH?: JsonConstant,
193 | A_WIDTH?: JsonConstant,
194 | B_WIDTH?: JsonConstant,
195 | S_WIDTH?: JsonConstant,
196 | Y_WIDTH?: JsonConstant,
197 | A_SIGNED?: JsonConstant,
198 | B_SIGNED?: JsonConstant,
199 | CLK_POLARITY?: JsonConstant,
200 | EN_POLARITY?: JsonConstant,
201 | ARST_POLARITY?: JsonConstant,
202 | ARST_VALUE: JsonConstant,
203 | CTRL_IN_WIDTH?: JsonConstant,
204 | CTRL_OUT_WIDTH?: JsonConstant,
205 | TRANS_NUM?: JsonConstant,
206 | STATE_NUM?: JsonConstant,
207 | STATE_NUM_LOG2?: JsonConstant,
208 | STATE_RST?: JsonConstant,
209 | RD_PORTS?: JsonConstant,
210 | WR_PORTS?: JsonConstant,
211 | RD_CLK_POLARITY?: JsonConstant,
212 | RD_CLK_ENABLE?: JsonConstant,
213 | RD_CLK_TRANSPARENT?: JsonConstant,
214 | WR_CLK_POLARITY?: JsonConstant,
215 | WR_CLK_ENABLE?: JsonConstant,
216 | [key: string]: any
217 | };
218 |
219 | export type JsonConstant = number | string;
220 |
221 | export type Attributes = {
222 | init: JsonConstant,
223 | [key: string]: any
224 | };
225 |
226 | export type Cell = {
227 | hide_name: 0 | 1,
228 | type: string,
229 | parameters: Parameters,
230 | attributes: Attributes,
231 | port_directions: { [key: string]: 'input' | 'output' },
232 | connections: { [key: string]: BitVector }
233 | };
234 |
235 | export type Net = {
236 | hide_name: 0 | 1,
237 | bits: BitVector,
238 | attributes: { [key: string]: string }
239 | };
240 |
241 | export type Module = {
242 | ports: { [key: string]: Port },
243 | cells: { [key: string]: Cell },
244 | netnames: { [key: string]: Net }
245 | };
246 |
247 | export type Output = {
248 | modules: { [key: string]: Module }
249 | };
250 |
251 | };
252 |
253 | type ConvertOptions = {
254 | propagation?: number,
255 | };
256 |
257 | type Options = ConvertOptions & {
258 | optimize?: boolean,
259 | fsmexpand?: boolean,
260 | fsm?: boolean | "nomap",
261 | timeout?: number,
262 | lint?: boolean
263 | };
264 |
265 | type Output = {
266 | output?: Digitaljs.TopModule,
267 | yosys_output?: any,
268 | yosys_stdout: string,
269 | yosys_stderr: string,
270 | lint?: LintMessage[]
271 | };
272 |
273 | type Portmap = { [key: string]: string };
274 | type Portmaps = { [key: string]: Portmap };
275 |
276 | type Bit = Yosys.Bit | `bit${number}`;
277 |
278 | type Net = Bit[];
279 |
280 | type NetInfo = {
281 | source: undefined | Digitaljs.Port,
282 | targets: Digitaljs.Port[],
283 | name: undefined | string,
284 | source_positions: Digitaljs.SourcePosition[]
285 | };
286 |
287 | type BitInfo = {
288 | id: string,
289 | port: string,
290 | num: number
291 | };
292 |
293 | type LintMessage = {
294 | type: string,
295 | file: string,
296 | line: number,
297 | column: number,
298 | message: string
299 | };
300 |
301 | function chunkArray(a, chunk_size){
302 | let results = [];
303 | let ca = a.splice();
304 |
305 | while (ca.length) {
306 | results.push(ca.splice(0, chunk_size));
307 | }
308 |
309 | return results;
310 | }
311 |
312 | function module_deps(data: Yosys.Output): [string, string | number][] {
313 | const out: [string, string | number][] = [];
314 | for (const [name, mod] of Object.entries(data.modules)) {
315 | out.push([name, 1/0]);
316 | for (const cname in mod.cells) {
317 | const cell = mod.cells[cname];
318 | if (cell.type in data.modules)
319 | out.push([cell.type, name]);
320 | }
321 | }
322 | return out;
323 | }
324 |
325 | function order_ports(data: Yosys.Output): Portmaps {
326 | const unmap = {A: 'in', Y: 'out'};
327 | const binmap = {A: 'in1', B: 'in2', Y: 'out'};
328 | const out = {
329 | '$mux': {A: 'in0', B: 'in1', S: 'sel', Y: 'out'},
330 | '$dff': {CLK: 'clk', D: 'in', Q: 'out'},
331 | '$dffe': {CLK: 'clk', EN: 'en', D: 'in', Q: 'out'},
332 | '$adff': {CLK: 'clk', ARST: 'arst', D: 'in', Q: 'out'},
333 | '$adffe': {CLK: 'clk', EN: 'en', ARST: 'arst', D: 'in', Q: 'out'},
334 | '$sdff': {CLK: 'clk', SRST: 'srst', D: 'in', Q: 'out'},
335 | '$sdffe': {CLK: 'clk', EN: 'en', SRST: 'srst', D: 'in', Q: 'out'},
336 | '$sdffce': {CLK: 'clk', EN: 'en', SRST: 'srst', D: 'in', Q: 'out'},
337 | '$dlatch': {EN: 'en', D: 'in', Q: 'out'},
338 | '$adlatch': {EN: 'en', ARST: 'arst', D: 'in', Q: 'out'},
339 | '$dffsr': {CLK: 'clk', SET: 'set', CLR: 'clr', D: 'in', Q: 'out'},
340 | '$dffsre': {CLK: 'clk', EN: 'en', SET: 'set', CLR: 'clr', D: 'in', Q: 'out'},
341 | '$aldff': {CLK: 'clk', ALOAD: 'aload', AD: 'ain', D: 'in', Q: 'out'},
342 | '$aldffe': {CLK: 'clk', EN: 'en', ALOAD: 'aload', AD: 'ain', D: 'in', Q: 'out'},
343 | '$sr': {SET: 'set', CLR: 'clr', Q: 'out'},
344 | '$fsm': {ARST: 'arst', CLK: 'clk', CTRL_IN: 'in', CTRL_OUT: 'out'}
345 | };
346 | binary_gates.forEach((nm) => out[nm] = binmap);
347 | unary_gates.forEach((nm) => out[nm] = unmap);
348 | for (const [name, mod] of Object.entries(data.modules)) {
349 | const portmap: Portmap = {};
350 | const ins = [], outs = [];
351 | for (const pname in mod.ports) {
352 | portmap[pname] = pname;
353 | }
354 | out[name] = portmap;
355 | }
356 | return out;
357 | }
358 |
359 | function decode_json_bigint(param: string | number): bigInt.BigInteger {
360 | if (typeof param == 'string')
361 | return bigInt(param, 2)
362 | else if (typeof param == 'number')
363 | return bigInt(param)
364 | else assert(false);
365 | }
366 |
367 | function decode_json_number(param: Yosys.JsonConstant): number {
368 | if (typeof param == 'string')
369 | return Number.parseInt(param, 2);
370 | else if (typeof param == 'number')
371 | return param
372 | else assert(false);
373 | }
374 |
375 | function decode_json_bigint_as_array(param: string | number): number[] {
376 | return decode_json_bigint(param).toArray(2).value;
377 | }
378 |
379 | function decode_json_constant(param: Yosys.JsonConstant, bits: number, fill : Yosys.BitChar = '0'): string {
380 | if (typeof param == 'number')
381 | return bigInt(param).toArray(2).value.map(String).reverse()
382 | .concat(Array(bits).fill(fill)).slice(0, bits).reverse().join('');
383 | else
384 | return param;
385 | }
386 |
387 | function parse_source_positions(str: string): Digitaljs.SourcePosition[] {
388 | const ret = [];
389 | for (const entry of str.split('|')) {
390 | const colonIdx = entry.lastIndexOf(':');
391 | const name = entry.slice(0, colonIdx);
392 | const pos = entry.slice(colonIdx+1);
393 | const [from, to] = pos.split('-').map(s => s.split('.').map(v => Number(v))).map(([line, column]) => ({line, column}));
394 | ret.push({name, from, to});
395 | }
396 | return ret;
397 | }
398 |
399 | function yosys_to_digitaljs(data: Yosys.Output, portmaps: Portmaps, options: ConvertOptions = {}): {[key: string]: Digitaljs.Module} {
400 | const out = {};
401 | for (const [name, mod] of Object.entries(data.modules)) {
402 | out[name] = yosys_to_digitaljs_mod(name, mod, portmaps, options);
403 | }
404 | return out
405 | }
406 |
407 | function yosys_to_digitaljs_mod(name: string, mod: Yosys.Module, portmaps: Portmaps, options: ConvertOptions = {}): Digitaljs.Module {
408 | function constbit(bit: Bit) {
409 | return (Yosys.ConstChars as readonly string[]).includes(bit.toString());
410 | }
411 | const nets = new HashMap();
412 | const netnames = new HashMap();
413 | const netsrc = new HashMap();
414 | const bits = new Map();
415 | const devnets = new Map>();
416 | let n = 0, pn = 0;
417 | function gen_name(): string {
418 | const nm = `dev${n++}`;
419 | devnets.set(nm, new Map());
420 | return nm;
421 | }
422 | function gen_bitname(): Bit {
423 | return `bit${pn++}`;
424 | }
425 | function get_net(k: Net): NetInfo {
426 | // create net if does not exist yet
427 | if (!nets.has(k)) {
428 | const nms = netnames.get(k);
429 | const src = netsrc.get(k);
430 | nets.set(k, {source: undefined, targets: [], name: nms ? nms[0] : undefined, source_positions: src || []});
431 | }
432 | return nets.get(k);
433 | }
434 | function add_net_source(k: Net, d: string, p: string, primary: boolean = false) {
435 | if (k.length == 0) return; // for unconnected ports
436 | const net = get_net(k);
437 | if(net.source !== undefined) {
438 | // multiple sources driving one net, disallowed in digitaljs
439 | throw Error('Multiple sources driving net: ' + net.name);
440 | }
441 | net.source = { id: d, port: p };
442 | if (primary) for (const [nbit, bit] of k.entries()) {
443 | bits.set(bit, { id: d, port: p, num: nbit });
444 | }
445 | devnets.get(d).set(p, k);
446 | }
447 | function add_net_target(k: Net, d: string, p: string) {
448 | if (k.length == 0) return; // for unconnected ports
449 | const net = get_net(k);
450 | net.targets.push({ id: d, port: p });
451 | devnets.get(d).set(p, k);
452 | }
453 | const mout = {
454 | devices: {},
455 | connectors: []
456 | }
457 | function add_device(dev : Digitaljs.Device): string {
458 | const dname = gen_name();
459 | if (options.propagation !== undefined)
460 | dev.propagation = options.propagation;
461 | mout.devices[dname] = dev;
462 | return dname;
463 | }
464 | function add_busgroup(nbits: Net, groups: Net[]) {
465 | if (get_net(nbits).source !== undefined)
466 | return; // the bits were already grouped
467 | const dname = add_device({
468 | type: 'BusGroup',
469 | groups: groups.map(g => g.length)
470 | });
471 | add_net_source(nbits, dname, 'out');
472 | for (const [gn, group] of groups.entries()) {
473 | add_net_target(group, dname, 'in' + gn);
474 | }
475 | }
476 | function connect_device(dname: string, cell: Yosys.Cell, portmap: Portmap) {
477 | for (const [pname, pdir] of Object.entries(cell.port_directions)) {
478 | const pconn = cell.connections[pname];
479 | switch (pdir) {
480 | case 'input':
481 | add_net_target(pconn, dname, portmap[pname]);
482 | break;
483 | case 'output':
484 | add_net_source(pconn, dname, portmap[pname], true);
485 | break;
486 | default:
487 | throw Error('Invalid port direction: ' + pdir);
488 | }
489 | }
490 | }
491 | function connect_pmux(dname: string, cell: Yosys.Cell) {
492 | add_net_target(cell.connections.A, dname, 'in0');
493 | add_net_target(cell.connections.S.slice().reverse(), dname, 'sel');
494 | add_net_source(cell.connections.Y, dname, 'out', true);
495 | for (const i of Array(decode_json_number(cell.parameters.S_WIDTH)).keys()) {
496 | const p = (decode_json_number(cell.parameters.S_WIDTH)-i-1) * decode_json_number(cell.parameters.WIDTH);
497 | add_net_target(cell.connections.B.slice(p, p + decode_json_number(cell.parameters.WIDTH)),
498 | dname, 'in' + (i+1));
499 | }
500 | }
501 | function connect_mem(dname: string, cell: Yosys.Cell, dev: Digitaljs.Device) {
502 | for (const [k, port] of dev.rdports.entries()) {
503 | const portname = "rd" + k;
504 | add_net_target(cell.connections.RD_ADDR.slice(dev.abits * k, dev.abits * (k+1)),
505 | dname, portname + "addr");
506 | add_net_source(cell.connections.RD_DATA.slice(dev.bits * k, dev.bits * (k+1)),
507 | dname, portname + "data", true);
508 | if ('clock_polarity' in port)
509 | add_net_target([cell.connections.RD_CLK[k]], dname, portname + "clk");
510 | if ('enable_polarity' in port)
511 | add_net_target([cell.connections.RD_EN[k]], dname, portname + "en");
512 | if ('arst_polarity' in port)
513 | add_net_target([cell.connections.RD_ARST[k]], dname, portname + "arst");
514 | if ('srst_polarity' in port)
515 | add_net_target([cell.connections.RD_SRST[k]], dname, portname + "srst");
516 | }
517 | for (const [k, port] of dev.wrports.entries()) {
518 | const portname = "wr" + k;
519 | add_net_target(cell.connections.WR_ADDR.slice(dev.abits * k, dev.abits * (k+1)),
520 | dname, portname + "addr");
521 | add_net_target(cell.connections.WR_DATA.slice(dev.bits * k, dev.bits * (k+1)),
522 | dname, portname + "data");
523 | if ('clock_polarity' in port)
524 | add_net_target([cell.connections.WR_CLK[k]], dname, portname + "clk");
525 | if ('enable_polarity' in port) {
526 | if (port.no_bit_enable)
527 | add_net_target([cell.connections.WR_EN[dev.bits * k]], dname, portname + "en");
528 | else
529 | add_net_target(cell.connections.WR_EN.slice(dev.bits * k, dev.bits * (k+1)),
530 | dname, portname + "en");
531 | }
532 | }
533 | }
534 | // Find net names
535 | for (const [nname, data] of Object.entries(mod.netnames)) {
536 | if (data.hide_name) continue;
537 | let l = netnames.get(data.bits);
538 | if (l === undefined) {
539 | l = [];
540 | netnames.set(data.bits, l);
541 | }
542 | l.push(nname);
543 | if (typeof data.attributes == 'object' && data.attributes.src) {
544 | let l = netsrc.get(data.bits);
545 | if (l === undefined) {
546 | l = [];
547 | netsrc.set(data.bits, l);
548 | }
549 | const positions = parse_source_positions(data.attributes.src);
550 | l.push(...positions);
551 | }
552 | }
553 | // Add inputs/outputs
554 | for (const [pname, port] of Object.entries(mod.ports)) {
555 | const dir = port.direction == "input" ? "Input" :
556 | port.direction == "output" ? "Output" :
557 | undefined;
558 | const dname = add_device({
559 | type: dir,
560 | net: pname,
561 | order: n,
562 | bits: port.bits.length
563 | });
564 | switch (port.direction) {
565 | case 'input':
566 | add_net_source(port.bits, dname, 'out', true);
567 | break;
568 | case 'output':
569 | add_net_target(port.bits, dname, 'in');
570 | break;
571 | default: throw Error('Invalid port direction: ' + port.direction);
572 | }
573 | }
574 | // Add gates
575 | for (const [cname, cell] of Object.entries(mod.cells)) {
576 | const dev : Digitaljs.Device = {
577 | label: cname,
578 | type: gate_subst.get(cell.type)
579 | };
580 | if (dev.type == undefined) {
581 | dev.type = 'Subcircuit';
582 | dev.celltype = cell.type;
583 | }
584 | if (typeof cell.attributes == 'object' && cell.attributes.src) {
585 | dev.source_positions = parse_source_positions(cell.attributes.src);
586 | }
587 | const dname = add_device(dev);
588 | function match_port(con: Net, nsig: Yosys.JsonConstant, sz: number) {
589 | const sig = decode_json_number(nsig);
590 | if (con.length > sz)
591 | con.splice(sz);
592 | else if (con.length < sz) {
593 | const ccon = con.slice();
594 | const pad = sig ? con.slice(-1)[0] : '0';
595 | con.splice(con.length, 0, ...Array(sz - con.length).fill(pad));
596 | if (!con.every(constbit) && get_net(con).source === undefined) {
597 | // WARNING: potentially troublesome hack for readability
598 | // handled generally in the grouping phase,
599 | // but it's hard to add sign extensions there
600 | const extname = add_device({
601 | type: sig ? 'SignExtend' : 'ZeroExtend',
602 | extend: { input: ccon.length, output: con.length }
603 | });
604 | add_net_target(ccon, extname, 'in');
605 | add_net_source(con, extname, 'out');
606 | }
607 | }
608 | }
609 | function zero_extend_output(con: Net) {
610 | if (con.length > 1) {
611 | const ccon = con.slice();
612 | con.splice(1);
613 | const extname = add_device({
614 | type: 'ZeroExtend',
615 | extend: { input: con.length, output: ccon.length }
616 | });
617 | add_net_source(ccon, extname, 'out');
618 | add_net_target(con, extname, 'in');
619 | }
620 | }
621 | if (unary_gates.has(cell.type)) {
622 | assert(cell.connections.A.length == decode_json_number(cell.parameters.A_WIDTH));
623 | assert(cell.connections.Y.length == decode_json_number(cell.parameters.Y_WIDTH));
624 | assert(cell.port_directions.A == 'input');
625 | assert(cell.port_directions.Y == 'output');
626 | }
627 | if (binary_gates.has(cell.type)) {
628 | assert(cell.connections.A.length == decode_json_number(cell.parameters.A_WIDTH));
629 | assert(cell.connections.B.length == decode_json_number(cell.parameters.B_WIDTH));
630 | assert(cell.connections.Y.length == decode_json_number(cell.parameters.Y_WIDTH));
631 | assert(cell.port_directions.A == 'input');
632 | assert(cell.port_directions.B == 'input');
633 | assert(cell.port_directions.Y == 'output');
634 | }
635 | if (['$dff', '$dffe', '$adff', '$adffe', '$sdff', '$sdffe', '$sdffce', '$dlatch', '$adlatch', '$dffsr', '$dffsre', '$aldff', '$aldffe'].includes(cell.type)) {
636 | assert(cell.connections.D.length == decode_json_number(cell.parameters.WIDTH));
637 | assert(cell.connections.Q.length == decode_json_number(cell.parameters.WIDTH));
638 | assert(cell.port_directions.D == 'input');
639 | assert(cell.port_directions.Q == 'output');
640 | if (cell.type != '$dlatch' && cell.type != '$adlatch') {
641 | assert(cell.connections.CLK.length == 1);
642 | assert(cell.port_directions.CLK == 'input');
643 | }
644 | }
645 | if (['$dffe', '$adffe', '$sdffe', '$sdffce', '$dffsre', '$aldffe', '$dlatch', '$adlatch'].includes(cell.type)) {
646 | assert(cell.connections.EN.length == 1);
647 | assert(cell.port_directions.EN == 'input');
648 | }
649 | if (['$adff', '$adffe', '$adlatch'].includes(cell.type)) {
650 | assert(cell.connections.ARST.length == 1);
651 | assert(cell.port_directions.ARST == 'input');
652 | }
653 | if (['$sdff', '$sdffe', '$sdffce'].includes(cell.type)) {
654 | assert(cell.connections.SRST.length == 1);
655 | assert(cell.port_directions.SRST == 'input');
656 | }
657 | if (['$dffsr', '$dffsre'].includes(cell.type)) {
658 | assert(cell.connections.SET.length == decode_json_number(cell.parameters.WIDTH));
659 | assert(cell.connections.CLR.length == decode_json_number(cell.parameters.WIDTH));
660 | assert(cell.port_directions.SET == 'input');
661 | assert(cell.port_directions.CLR == 'input');
662 | }
663 | switch (cell.type) {
664 | case '$neg': case '$pos':
665 | dev.bits = {
666 | in: cell.connections.A.length,
667 | out: cell.connections.Y.length
668 | };
669 | dev.signed = Boolean(decode_json_number(cell.parameters.A_SIGNED));
670 | break;
671 | case '$not':
672 | match_port(cell.connections.A, cell.parameters.A_SIGNED, cell.connections.Y.length);
673 | dev.bits = cell.connections.Y.length;
674 | break;
675 | case '$add': case '$sub': case '$mul': case '$div': case '$mod': case '$pow':
676 | dev.bits = {
677 | in1: cell.connections.A.length,
678 | in2: cell.connections.B.length,
679 | out: cell.connections.Y.length
680 | };
681 | dev.signed = {
682 | in1: Boolean(decode_json_number(cell.parameters.A_SIGNED)),
683 | in2: Boolean(decode_json_number(cell.parameters.B_SIGNED))
684 | }
685 | break;
686 | case '$and': case '$or': case '$xor': case '$xnor':
687 | match_port(cell.connections.A, cell.parameters.A_SIGNED, cell.connections.Y.length);
688 | match_port(cell.connections.B, cell.parameters.B_SIGNED, cell.connections.Y.length);
689 | dev.bits = cell.connections.Y.length;
690 | break;
691 | case '$reduce_and': case '$reduce_or': case '$reduce_xor': case '$reduce_xnor':
692 | case '$reduce_bool': case '$logic_not':
693 | dev.bits = cell.connections.A.length;
694 | zero_extend_output(cell.connections.Y);
695 | if (dev.bits == 1) {
696 | if (['$reduce_xnor', '$logic_not'].includes(cell.type))
697 | dev.type = 'Not';
698 | else
699 | dev.type = 'Repeater';
700 | }
701 | break;
702 | case '$eq': case '$ne': case '$lt': case '$le': case '$gt': case '$ge':
703 | case '$eqx': case '$nex':
704 | dev.bits = {
705 | in1: cell.connections.A.length,
706 | in2: cell.connections.B.length
707 | };
708 | dev.signed = {
709 | in1: Boolean(decode_json_number(cell.parameters.A_SIGNED)),
710 | in2: Boolean(decode_json_number(cell.parameters.B_SIGNED))
711 | };
712 | zero_extend_output(cell.connections.Y);
713 | break;
714 | case '$shl': case '$shr': case '$sshl': case '$sshr':
715 | case '$shift': case '$shiftx':
716 | dev.bits = {
717 | in1: cell.connections.A.length,
718 | in2: cell.connections.B.length,
719 | out: cell.connections.Y.length
720 | };
721 | dev.signed = {
722 | in1: Boolean(decode_json_number(cell.parameters.A_SIGNED)),
723 | in2: Boolean(decode_json_number(cell.parameters.B_SIGNED) && ['$shift', '$shiftx'].includes(cell.type)),
724 | out: Boolean(decode_json_number(cell.parameters.A_SIGNED) && ['$sshl', '$sshr'].includes(cell.type))
725 | };
726 | dev.fillx = cell.type == '$shiftx';
727 | break;
728 | case '$logic_and': case '$logic_or': {
729 | function reduce_input(con: Net) {
730 | const ccon = con.slice();
731 | con.splice(0, con.length, gen_bitname());
732 | const extname = add_device({
733 | type: 'OrReduce',
734 | bits: ccon.length
735 | });
736 | add_net_source(con, extname, 'out');
737 | add_net_target(ccon, extname, 'in');
738 | }
739 | if (cell.connections.A.length > 1)
740 | reduce_input(cell.connections.A);
741 | if (cell.connections.B.length > 1)
742 | reduce_input(cell.connections.B);
743 | zero_extend_output(cell.connections.Y);
744 | break;
745 | }
746 | case '$mux':
747 | assert(cell.connections.A.length == decode_json_number(cell.parameters.WIDTH));
748 | assert(cell.connections.B.length == decode_json_number(cell.parameters.WIDTH));
749 | assert(cell.connections.Y.length == decode_json_number(cell.parameters.WIDTH));
750 | assert(cell.port_directions.A == 'input');
751 | assert(cell.port_directions.B == 'input');
752 | assert(cell.port_directions.Y == 'output');
753 | dev.bits = {
754 | in: decode_json_number(cell.parameters.WIDTH),
755 | sel: 1
756 | };
757 | break;
758 | case '$pmux':
759 | assert(cell.connections.B.length == decode_json_number(cell.parameters.WIDTH) * decode_json_number(cell.parameters.S_WIDTH));
760 | assert(cell.connections.A.length == decode_json_number(cell.parameters.WIDTH));
761 | assert(cell.connections.S.length == decode_json_number(cell.parameters.S_WIDTH));
762 | assert(cell.connections.Y.length == decode_json_number(cell.parameters.WIDTH));
763 | assert(cell.port_directions.A == 'input');
764 | assert(cell.port_directions.B == 'input');
765 | assert(cell.port_directions.S == 'input');
766 | assert(cell.port_directions.Y == 'output');
767 | dev.bits = {
768 | in: decode_json_number(cell.parameters.WIDTH),
769 | sel: decode_json_number(cell.parameters.S_WIDTH)
770 | };
771 | break;
772 | case '$dff':
773 | dev.bits = decode_json_number(cell.parameters.WIDTH);
774 | dev.polarity = {
775 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY))
776 | };
777 | break;
778 | case '$dffe':
779 | dev.bits = decode_json_number(cell.parameters.WIDTH);
780 | dev.polarity = {
781 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
782 | enable: Boolean(decode_json_number(cell.parameters.EN_POLARITY))
783 | };
784 | break;
785 | case '$aldff':
786 | dev.bits = decode_json_number(cell.parameters.WIDTH);
787 | dev.polarity = {
788 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
789 | aload: Boolean(decode_json_number(cell.parameters.ALOAD_POLARITY))
790 | };
791 | break;
792 | case '$aldffe':
793 | dev.bits = decode_json_number(cell.parameters.WIDTH);
794 | dev.polarity = {
795 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
796 | enable: Boolean(decode_json_number(cell.parameters.EN_POLARITY)),
797 | aload: Boolean(decode_json_number(cell.parameters.ALOAD_POLARITY))
798 | };
799 | break;
800 | case '$adff':
801 | dev.bits = decode_json_number(cell.parameters.WIDTH);
802 | dev.polarity = {
803 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
804 | arst: Boolean(decode_json_number(cell.parameters.ARST_POLARITY))
805 | };
806 | dev.arst_value = decode_json_constant(cell.parameters.ARST_VALUE, dev.bits);
807 | break;
808 | case '$sdff':
809 | dev.bits = decode_json_number(cell.parameters.WIDTH);
810 | dev.polarity = {
811 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
812 | srst: Boolean(decode_json_number(cell.parameters.SRST_POLARITY))
813 | };
814 | dev.srst_value = decode_json_constant(cell.parameters.SRST_VALUE, dev.bits);
815 | break;
816 | case '$adffe':
817 | dev.bits = decode_json_number(cell.parameters.WIDTH);
818 | dev.polarity = {
819 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
820 | arst: Boolean(decode_json_number(cell.parameters.ARST_POLARITY)),
821 | enable: Boolean(decode_json_number(cell.parameters.EN_POLARITY))
822 | };
823 | dev.arst_value = decode_json_constant(cell.parameters.ARST_VALUE, dev.bits);
824 | break;
825 | case '$sdffe':
826 | dev.bits = decode_json_number(cell.parameters.WIDTH);
827 | dev.polarity = {
828 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
829 | srst: Boolean(decode_json_number(cell.parameters.SRST_POLARITY)),
830 | enable: Boolean(decode_json_number(cell.parameters.EN_POLARITY))
831 | };
832 | dev.srst_value = decode_json_constant(cell.parameters.SRST_VALUE, dev.bits);
833 | break;
834 | case '$sdffce':
835 | dev.bits = decode_json_number(cell.parameters.WIDTH);
836 | dev.polarity = {
837 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
838 | srst: Boolean(decode_json_number(cell.parameters.SRST_POLARITY)),
839 | enable: Boolean(decode_json_number(cell.parameters.EN_POLARITY))
840 | };
841 | dev.enable_srst = true;
842 | dev.srst_value = decode_json_constant(cell.parameters.SRST_VALUE, dev.bits);
843 | break;
844 | case '$dlatch':
845 | dev.bits = decode_json_number(cell.parameters.WIDTH);
846 | dev.polarity = {
847 | enable: Boolean(decode_json_number(cell.parameters.EN_POLARITY))
848 | };
849 | break;
850 | case '$adlatch':
851 | dev.bits = decode_json_number(cell.parameters.WIDTH);
852 | dev.polarity = {
853 | enable: Boolean(decode_json_number(cell.parameters.EN_POLARITY)),
854 | arst: Boolean(decode_json_number(cell.parameters.ARST_POLARITY))
855 | };
856 | dev.arst_value = decode_json_constant(cell.parameters.ARST_VALUE, dev.bits);
857 | break;
858 | case '$dffsr':
859 | dev.bits = decode_json_number(cell.parameters.WIDTH);
860 | dev.polarity = {
861 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
862 | set: Boolean(decode_json_number(cell.parameters.SET_POLARITY)),
863 | clr: Boolean(decode_json_number(cell.parameters.CLR_POLARITY))
864 | };
865 | break;
866 | case '$dffsre':
867 | dev.bits = decode_json_number(cell.parameters.WIDTH);
868 | dev.polarity = {
869 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
870 | enable: Boolean(decode_json_number(cell.parameters.EN_POLARITY)),
871 | set: Boolean(decode_json_number(cell.parameters.SET_POLARITY)),
872 | clr: Boolean(decode_json_number(cell.parameters.CLR_POLARITY))
873 | };
874 | break;
875 | case '$sr':
876 | assert(cell.connections.Q.length == decode_json_number(cell.parameters.WIDTH));
877 | assert(cell.port_directions.Q == 'output');
878 | dev.no_data = true;
879 | dev.bits = decode_json_number(cell.parameters.WIDTH);
880 | dev.polarity = {
881 | set: Boolean(decode_json_number(cell.parameters.SET_POLARITY)),
882 | clr: Boolean(decode_json_number(cell.parameters.CLR_POLARITY))
883 | };
884 | break;
885 | case '$fsm': {
886 | assert(cell.connections.ARST.length == 1);
887 | assert(cell.connections.CLK.length == 1);
888 | assert(cell.connections.CTRL_IN.length == decode_json_number(cell.parameters.CTRL_IN_WIDTH));
889 | assert(cell.connections.CTRL_OUT.length == decode_json_number(cell.parameters.CTRL_OUT_WIDTH));
890 | const TRANS_NUM = decode_json_number(cell.parameters.TRANS_NUM);
891 | const STATE_NUM_LOG2 = decode_json_number(cell.parameters.STATE_NUM_LOG2);
892 | const step = 2*STATE_NUM_LOG2
893 | + decode_json_number(cell.parameters.CTRL_IN_WIDTH)
894 | + decode_json_number(cell.parameters.CTRL_OUT_WIDTH);
895 | const tt = typeof(cell.parameters.TRANS_TABLE) == "number"
896 | ? Vector3vl.fromBin(bigInt(cell.parameters.TRANS_TABLE).toString(2), TRANS_NUM * step).toBin() // workaround for yosys silliness
897 | : cell.parameters.TRANS_TABLE;
898 | assert(tt.length == TRANS_NUM * step);
899 | dev.polarity = {
900 | clock: Boolean(decode_json_number(cell.parameters.CLK_POLARITY)),
901 | arst: Boolean(decode_json_number(cell.parameters.ARST_POLARITY))
902 | };
903 | dev.wirename = cell.parameters.NAME;
904 | dev.bits = {
905 | in: decode_json_number(cell.parameters.CTRL_IN_WIDTH),
906 | out: decode_json_number(cell.parameters.CTRL_OUT_WIDTH)
907 | };
908 | dev.states = decode_json_number(cell.parameters.STATE_NUM);
909 | dev.init_state = decode_json_number(cell.parameters.STATE_RST);
910 | dev.trans_table = [];
911 | for (let i = 0; i < TRANS_NUM; i++) {
912 | let base = i * step;
913 | const f = (sz) => {
914 | const ret = tt.slice(base, base + sz);
915 | base += sz;
916 | return ret;
917 | };
918 | const o = {
919 | state_in: parseInt(f(STATE_NUM_LOG2), 2),
920 | ctrl_in: f(decode_json_number(cell.parameters.CTRL_IN_WIDTH)).replace(/-/g, 'x'),
921 | state_out: parseInt(f(STATE_NUM_LOG2), 2),
922 | ctrl_out: f(decode_json_number(cell.parameters.CTRL_OUT_WIDTH))
923 | };
924 | dev.trans_table.push(o);
925 | }
926 | break;
927 | }
928 | case '$mem':
929 | case '$mem_v2': {
930 | const RD_PORTS = decode_json_number(cell.parameters.RD_PORTS);
931 | const WR_PORTS = decode_json_number(cell.parameters.WR_PORTS);
932 | assert(cell.connections.RD_EN.length == RD_PORTS);
933 | assert(cell.connections.RD_CLK.length == RD_PORTS);
934 | assert(cell.connections.RD_DATA.length == RD_PORTS * decode_json_number(cell.parameters.WIDTH));
935 | assert(cell.connections.RD_ADDR.length == RD_PORTS * decode_json_number(cell.parameters.ABITS));
936 | assert(cell.connections.WR_EN.length == WR_PORTS * decode_json_number(cell.parameters.WIDTH));
937 | assert(cell.connections.WR_CLK.length == WR_PORTS);
938 | assert(cell.connections.WR_DATA.length == WR_PORTS * decode_json_number(cell.parameters.WIDTH));
939 | assert(cell.connections.WR_ADDR.length == WR_PORTS * decode_json_number(cell.parameters.ABITS));
940 | if (cell.type == "$mem_v2") {
941 | assert(cell.connections.RD_ARST.length == RD_PORTS);
942 | assert(cell.connections.RD_SRST.length == RD_PORTS);
943 | }
944 | dev.bits = decode_json_number(cell.parameters.WIDTH);
945 | dev.abits = decode_json_number(cell.parameters.ABITS);
946 | dev.words = decode_json_number(cell.parameters.SIZE);
947 | dev.offset = decode_json_number(cell.parameters.OFFSET);
948 | dev.rdports = [];
949 | dev.wrports = [];
950 | const rdpol = decode_json_bigint_as_array(cell.parameters.RD_CLK_POLARITY).reverse();
951 | const rden = decode_json_bigint_as_array(cell.parameters.RD_CLK_ENABLE).reverse();
952 | const rdtr = cell.type == "$mem_v2"
953 | ? []
954 | : decode_json_bigint_as_array(cell.parameters.RD_TRANSPARENT).reverse();
955 | const wrpol = decode_json_bigint_as_array(cell.parameters.WR_CLK_POLARITY).reverse();
956 | const wren = decode_json_bigint_as_array(cell.parameters.WR_CLK_ENABLE).reverse();
957 | const init = typeof(cell.parameters.INIT) == 'number'
958 | ? bigInt(cell.parameters.INIT).toArray(2).value.map(String).reverse()
959 | : cell.parameters.INIT.split('').reverse();
960 | const v2_feature = (param) => cell.type == "$mem_v2" ? decode_json_bigint_as_array(param).reverse() : [];
961 | const v2_feature_const = (param, size) => cell.type == "$mem_v2" ? decode_json_constant(param, size) : "";
962 | const rdtrmask = v2_feature(cell.parameters.RD_TRANSPARENCY_MASK);
963 | const rdcolmask = v2_feature(cell.parameters.RD_COLLISION_X_MASK);
964 | const rdensrst = v2_feature(cell.parameters.RD_CE_OVER_SRST);
965 | const rdinit = v2_feature_const(cell.parameters.RD_INIT_VALUE, dev.bits * RD_PORTS);
966 | const rdarst = v2_feature_const(cell.parameters.RD_ARST_VALUE, dev.bits * RD_PORTS);
967 | const rdsrst = v2_feature_const(cell.parameters.RD_SRST_VALUE, dev.bits * RD_PORTS);
968 | if (cell.parameters.INIT) {
969 | const l = init.slice(-1)[0] == 'x' ? 'x' : '0';
970 | const memdata = new Mem3vl(dev.bits, dev.words);
971 | for (const k of Array(dev.words).keys()) {
972 | const wrd = init.slice(dev.bits * k, dev.bits * (k+1));
973 | while (wrd.length < dev.bits) wrd.push(l);
974 | memdata.set(k, Vector3vl.fromBin(wrd.reverse().join('')));
975 | }
976 | dev.memdata = memdata.toJSON();
977 | }
978 | for (const k of Array(RD_PORTS).keys()) {
979 | const port: Digitaljs.MemReadPort = {
980 | };
981 | if (rden[k]) {
982 | port.clock_polarity = Boolean(rdpol[k]);
983 | if (cell.connections.RD_EN[k] != '1')
984 | port.enable_polarity = true;
985 | };
986 | if (rdtr[k])
987 | port.transparent = true;
988 | if (cell.type == "$mem_v2") {
989 | if (rdensrst[k])
990 | port.enable_srst = true;
991 | function mk_init(s: string, f: (v: string) => void) {
992 | const v = s.slice(dev.bits * k, dev.bits * (k+1));
993 | if (!v.split('').every(c => c == 'x'))
994 | f(v);
995 | };
996 | mk_init(rdinit, v => port.init_value = v);
997 | if (cell.connections.RD_ARST[k] != '0') {
998 | port.arst_polarity = true;
999 | mk_init(rdarst, v => port.arst_value = v);
1000 | }
1001 | if (cell.connections.RD_SRST[k] != '0') {
1002 | port.srst_polarity = true;
1003 | mk_init(rdsrst, v => port.srst_value = v);
1004 | }
1005 | function mk_mask(s: number[], f: (v: boolean | boolean[]) => void) {
1006 | const v = Array(WR_PORTS).fill(0);
1007 | s.slice(WR_PORTS * k, WR_PORTS * (k+1)).map((c, i) => { v[i] = c });
1008 | if (v.every(c => c))
1009 | f(true);
1010 | else if (v.some(c => c))
1011 | f(v.map(c => Boolean(c)));
1012 | }
1013 | mk_mask(rdtrmask, v => port.transparent = v);
1014 | mk_mask(rdcolmask, v => port.collision = v);
1015 | }
1016 | dev.rdports.push(port);
1017 | }
1018 | for (const k of Array(WR_PORTS).keys()) {
1019 | const port: Digitaljs.MemWritePort = {
1020 | };
1021 | if (wren[k]) {
1022 | port.clock_polarity = Boolean(wrpol[k]);
1023 | const wr_en_connections = cell.connections.WR_EN.slice(dev.bits * k, dev.bits * (k+1));
1024 | if (wr_en_connections.some(z => z != '1')) {
1025 | port.enable_polarity = true;
1026 | if (wr_en_connections.every(z => z == wr_en_connections[0]))
1027 | port.no_bit_enable = true;
1028 | }
1029 | };
1030 | dev.wrports.push(port);
1031 | }
1032 | break;
1033 | }
1034 | case '$lut':
1035 | assert(cell.connections.A.length == decode_json_number(cell.parameters.WIDTH));
1036 | assert(cell.connections.Y.length == 1);
1037 | assert(cell.port_directions.A == 'input');
1038 | assert(cell.port_directions.Y == 'output');
1039 | dev.abits = cell.connections.A.length;
1040 | dev.bits = cell.connections.Y.length;
1041 | dev.rdports = [{}];
1042 | dev.wrports = [];
1043 | dev.memdata = cell.parameters.LUT.split('').reverse();
1044 | assert(dev.memdata.length == Math.pow(2, dev.abits));
1045 |
1046 | // Rewrite cell connections to be $mem compatible for port mapping
1047 | cell.connections.RD_ADDR = cell.connections.A;
1048 | cell.connections.RD_DATA = cell.connections.Y;
1049 | delete cell.connections.A;
1050 | delete cell.connections.Y;
1051 | break;
1052 | default:
1053 | }
1054 | if (dev.type == 'Dff') {
1055 | // find register initial value, if exists
1056 | // Yosys puts initial values in net attributes; there can be many for single actual net!
1057 | const nms = netnames.get(cell.connections.Q);
1058 | if (nms !== undefined) {
1059 | for (const nm of nms) {
1060 | if (mod.netnames[nm].attributes.init !== undefined)
1061 | dev.initial = decode_json_constant(mod.netnames[nm].attributes.init, dev.bits);
1062 | }
1063 | }
1064 | }
1065 | const portmap = portmaps[cell.type];
1066 | if (portmap) connect_device(dname, cell, portmap);
1067 | else if (cell.type == '$pmux') connect_pmux(dname, cell);
1068 | else if (cell.type == '$mem') connect_mem(dname, cell, dev);
1069 | else if (cell.type == '$mem_v2') connect_mem(dname, cell, dev);
1070 | else if (cell.type == '$lut') connect_mem(dname, cell, dev);
1071 | else throw Error('Invalid cell type: ' + cell.type);
1072 | }
1073 | // Group bits into nets for complex sources
1074 | for (const [nbits, net] of nets.entries()) {
1075 | if (net.source !== undefined) continue;
1076 | const groups: Net[] = [[]];
1077 | let pbitinfo: BitInfo | 'const' | undefined = undefined;
1078 | for (const bit of nbits) {
1079 | let bitinfo: BitInfo | 'const' = bits.get(bit);
1080 | if (bitinfo == undefined && constbit(bit))
1081 | bitinfo = 'const';
1082 | if (groups.slice(-1)[0].length > 0 &&
1083 | (typeof bitinfo != typeof pbitinfo ||
1084 | typeof bitinfo == 'object' &&
1085 | typeof pbitinfo == 'object' &&
1086 | (bitinfo.id != pbitinfo.id ||
1087 | bitinfo.port != pbitinfo.port ||
1088 | bitinfo.num != pbitinfo.num + 1))) {
1089 | groups.push([]);
1090 | }
1091 | groups.slice(-1)[0].push(bit);
1092 | pbitinfo = bitinfo;
1093 | }
1094 | if (groups.length == 1) continue;
1095 | if (groups.slice(-1)[0].every(x => x == '0')) {
1096 | // infer zero-extend
1097 | const ilen = nbits.length - groups.slice(-1)[0].length;
1098 | const dname = add_device({
1099 | type: 'ZeroExtend',
1100 | extend: { output: nbits.length, input: ilen }
1101 | });
1102 | const zbits = nbits.slice(0, ilen);
1103 | add_net_source(nbits, dname, 'out');
1104 | add_net_target(zbits, dname, 'in');
1105 | if (groups.length > 2)
1106 | add_busgroup(zbits, groups.slice(0, groups.length - 1));
1107 | } else add_busgroup(nbits, groups);
1108 | }
1109 | // Add constants
1110 | for (const [nbits, net] of nets.entries()) {
1111 | if (net.source !== undefined) continue;
1112 | if (!nbits.every(constbit))
1113 | continue;
1114 | const dname = add_device({
1115 | // label: String(val), // TODO
1116 | type: 'Constant',
1117 | constant: nbits.slice().reverse().join('')
1118 | });
1119 | add_net_source(nbits, dname, 'out');
1120 | }
1121 | // Select bits from complex targets
1122 | for (const [nbits, net] of nets.entries()) {
1123 | if (net.source !== undefined) continue;
1124 | // constants should be already handled!
1125 | assert(nbits.every(x => x > 1));
1126 | const bitinfos = nbits.map(x => bits.get(x));
1127 | if (!bitinfos.every(x => typeof x == 'object'))
1128 | continue; // ignore not fully driven ports
1129 | // complex sources should be already handled!
1130 | assert(bitinfos.every(info => info.id == bitinfos[0].id &&
1131 | info.port == bitinfos[0].port));
1132 | const cconn = devnets.get(bitinfos[0].id).get(bitinfos[0].port);
1133 | const dname = add_device({
1134 | type: 'BusSlice',
1135 | slice: {
1136 | first: bitinfos[0].num,
1137 | count: bitinfos.length,
1138 | total: cconn.length
1139 | }
1140 | });
1141 | add_net_source(nbits, dname, 'out');
1142 | add_net_target(cconn, dname, 'in');
1143 | }
1144 | // Generate connections between devices
1145 | for (const [nbits, net] of nets.entries()) {
1146 | if (net.source === undefined) {
1147 | console.warn('Undriven net in ' + name + ': ' + nbits);
1148 | continue;
1149 | }
1150 | let first = true;
1151 | for (const target in net.targets) {
1152 | const conn: Digitaljs.Connector = {
1153 | to: net.targets[target],
1154 | from: net.source
1155 | };
1156 | if (net.name) conn.name = net.name;
1157 | if (net.source_positions) conn.source_positions = net.source_positions;
1158 | if (!first && mout.devices[conn.from.id].type == "Constant") {
1159 | // replicate constants for better clarity
1160 | const dname = add_device({
1161 | type: 'Constant',
1162 | constant: mout.devices[conn.from.id].constant
1163 | });
1164 | conn.from = {id: dname, port: 'out'};
1165 | }
1166 | mout.connectors.push(conn);
1167 | first = false;
1168 | }
1169 | }
1170 | return mout;
1171 | }
1172 |
1173 | function ansi_c_escape_contents(cmd: string): string {
1174 | function func(ch: string) {
1175 | if (ch == '\t') return '\\t';
1176 | if (ch == '\r') return '\\r';
1177 | if (ch == '\n') return '\\n';
1178 | return '\\x' + ch.charCodeAt(0).toString(16).padStart(2, '0');
1179 | }
1180 | return cmd.replace(/(["'\\])/g,'\\$1')
1181 | .replace(/[\x00-\x1F\x7F-\x9F]/g, func);
1182 | }
1183 |
1184 | function ansi_c_escape(cmd: string): string {
1185 | return '"' + ansi_c_escape_contents(cmd) + '"';
1186 | }
1187 |
1188 | function shell_escape_contents(cmd: string): string {
1189 | return cmd.replace(/(["\r\n$`\\])/g,'\\$1');
1190 | }
1191 |
1192 | function shell_escape(cmd: string): string {
1193 | return '"' + shell_escape_contents(cmd) + '"';
1194 | }
1195 |
1196 | function process_filename(filename: string): string {
1197 | const flags = /\.sv$/.test(filename) ? " -sv" : "";
1198 | return "read_verilog" + flags + " " + ansi_c_escape(filename);
1199 | }
1200 |
1201 | const verilator_re = /^%(Warning|Error)[^:]*: ([^:]*):([0-9]+):([0-9]+): (.*)$/;
1202 |
1203 | export async function verilator_lint(filenames: string[], dirname?: string, options: Options = {}): Promise {
1204 | try {
1205 | const output: LintMessage[] = [];
1206 | const verilator_result: {stdout: string, stderr: string} = await promisify(child_process.exec)(
1207 | 'timeout -k10s 40s verilator -lint-only -Wall -Wno-DECLFILENAME -Wno-UNOPT -Wno-UNOPTFLAT ' + filenames.map(shell_escape).join(' '),
1208 | {maxBuffer: 1000000, cwd: dirname || null, timeout: options.timeout || 60000})
1209 | .catch(exc => exc);
1210 | for (const line of verilator_result.stderr.split('\n')) {
1211 | const result = line.match(verilator_re);
1212 | if (result == null) continue;
1213 | output.push({
1214 | type: result[1],
1215 | file: path.basename(result[2]),
1216 | line: Number(result[3]),
1217 | column: Number(result[4]),
1218 | message: result[5]
1219 | });
1220 | }
1221 | return output;
1222 | } catch (exc) {
1223 | return null;
1224 | }
1225 | }
1226 |
1227 | export function yosys2digitaljs(obj: Yosys.Output, options: ConvertOptions = {}): Digitaljs.TopModule {
1228 | const portmaps = order_ports(obj);
1229 | const out = yosys_to_digitaljs(obj, portmaps, options);
1230 | const toporder = topsort(module_deps(obj));
1231 | toporder.pop();
1232 | const toplevel = toporder.pop();
1233 | const output: Digitaljs.TopModule = { subcircuits: {}, ... out[toplevel] };
1234 | for (const x of toporder)
1235 | output.subcircuits[x] = out[x];
1236 | return output;
1237 | }
1238 |
1239 | export async function process(filenames: string[], dirname?: string, options: Options = {}): Promise