├── .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 { 1240 | const optimize_simp = options.optimize ? "; opt" : "; opt_clean"; 1241 | const optimize = options.optimize ? "; opt -full" : "; opt_clean"; 1242 | const fsmexpand = options.fsmexpand ? " -expand" : ""; 1243 | const fsmpass = options.fsm == "nomap" ? "; fsm -nomap" + fsmexpand 1244 | : options.fsm ? "; fsm" + fsmexpand 1245 | : ""; 1246 | const tmpjson = await tmp.tmpName({ postfix: '.json' }); 1247 | let obj = undefined; 1248 | const yosys_result: {stdout: string, stderr: string, killed?: boolean, code?: number} = await promisify(child_process.exec)( 1249 | 'timeout -k10s 40s yosys -p "' + shell_escape_contents(filenames.map(process_filename).join('; ')) + 1250 | '; hierarchy -auto-top; proc' + optimize_simp + fsmpass + '; memory -nomap; wreduce -memx' + 1251 | optimize + '" -o "' + tmpjson + '"', 1252 | {maxBuffer: 1000000, cwd: dirname || null, timeout: options.timeout || 60000}) 1253 | .catch(exc => exc); 1254 | try { 1255 | if (yosys_result instanceof Error) { 1256 | if (yosys_result.killed) 1257 | yosys_result.message = "Yosys killed" 1258 | else if (yosys_result.code) 1259 | yosys_result.message = "Yosys failed with code " + yosys_result.code; 1260 | else 1261 | yosys_result.message = "Yosys failed"; 1262 | throw yosys_result; 1263 | } 1264 | obj = JSON.parse(fs.readFileSync(tmpjson, 'utf8')); 1265 | await promisify(fs.unlink)(tmpjson); 1266 | const output = yosys2digitaljs(obj, options); 1267 | const ret: Output = { 1268 | output: output, 1269 | yosys_output: obj, 1270 | yosys_stdout: yosys_result.stdout, 1271 | yosys_stderr: yosys_result.stderr 1272 | }; 1273 | if (options.lint) 1274 | ret.lint = await verilator_lint(filenames, dirname, options); 1275 | return ret; 1276 | } catch (exc) { 1277 | if (obj !== undefined) exc.yosys_output = obj; 1278 | exc.yosys_stdout = yosys_result.stdout; 1279 | exc.yosys_stderr = yosys_result.stderr; 1280 | throw exc; 1281 | } 1282 | } 1283 | 1284 | export function io_ui(output: Digitaljs.Module) { 1285 | for (const [name, dev] of Object.entries(output.devices)) { 1286 | if (dev.type == 'Input' || dev.type == 'Output') { 1287 | dev.label = dev.net; 1288 | } 1289 | // use clock for clocky named inputs 1290 | if (dev.type == 'Input' && dev.bits == 1 && (dev.label == 'clk' || dev.label == 'clock')) { 1291 | dev.type = 'Clock'; 1292 | dev.propagation = 100; 1293 | } 1294 | if (dev.type == 'Input') 1295 | dev.type = dev.bits == 1 ? 'Button' : 'NumEntry'; 1296 | if (dev.type == 'Output') { 1297 | if (dev.bits == 1) 1298 | dev.type = 'Lamp'; 1299 | else if (dev.bits == 8 && (dev.label == 'display7' || dev.label.startsWith('display7_'))) 1300 | dev.type = 'Display7'; 1301 | else 1302 | dev.type = 'NumDisplay'; 1303 | } 1304 | } 1305 | } 1306 | 1307 | export async function process_files(data: {[key: string]: string}, options: Options = {}): Promise { 1308 | const dir = await tmp.dir(); 1309 | const names = []; 1310 | try { 1311 | for (const [name, content] of Object.entries(data)) { 1312 | const sname = sanitize(name); 1313 | await promisify(fs.writeFile)(path.resolve(dir.path, sname), content); 1314 | if (/\.(v|sv)$/.test(sname)) names.push(sname); 1315 | } 1316 | return await process(names, dir.path, options); 1317 | } finally { 1318 | for (const name of Object.keys(data)) { 1319 | await promisify(fs.unlink)(path.resolve(dir.path, name)).catch(exc => exc); 1320 | } 1321 | dir.cleanup(); 1322 | } 1323 | } 1324 | 1325 | export async function process_sv(text: string, options: Options = {}): Promise { 1326 | const tmpsv = await tmp.file({ postfix: '.sv' }); 1327 | try { 1328 | await promisify(fs.write)(tmpsv.fd, text); 1329 | await promisify(fs.close)(tmpsv.fd); 1330 | return await process([tmpsv.path], undefined, options); 1331 | } finally { 1332 | tmpsv.cleanup(); 1333 | } 1334 | } 1335 | 1336 | -------------------------------------------------------------------------------- /tests/cycleadder.sv: -------------------------------------------------------------------------------- 1 | // Adds input to accumulator on successive cycles 2 | module cycleadder 3 | #(parameter WIDTH = 4)( 4 | input clk, 5 | input rst, 6 | input en, 7 | input [WIDTH-1:0] A, 8 | output logic [WIDTH-1:0] O 9 | ); 10 | 11 | always_ff @(posedge clk) 12 | if (rst) O <= 0; 13 | else if (en) O <= O + A; 14 | 15 | endmodule 16 | -------------------------------------------------------------------------------- /tests/cycleadder_arst.sv: -------------------------------------------------------------------------------- 1 | // Adds input to accumulator on successive cycles 2 | module cycleadder 3 | #(parameter WIDTH = 4)( 4 | input clk, 5 | input rst, 6 | input en, 7 | input [WIDTH-1:0] A, 8 | output logic [WIDTH-1:0] O 9 | ); 10 | 11 | always_ff @(posedge clk or posedge rst) 12 | if (rst) O <= 0; 13 | else if (en) O <= O + A; 14 | 15 | endmodule 16 | -------------------------------------------------------------------------------- /tests/dff_masterslave.sv: -------------------------------------------------------------------------------- 1 | // D latch (gate model) 2 | module d_latch( 3 | input d, e, 4 | output q, nq 5 | ); 6 | 7 | logic s, r, nd; 8 | 9 | nor g1(q, r, nq); 10 | nor g2(nq, s, q); 11 | and g3(r, e, nd); 12 | and g4(s, e, d); 13 | not g5(nd, d); 14 | 15 | endmodule 16 | 17 | // master-slave D flip-flop 18 | module dff_master_slave( 19 | input clk, d, 20 | output o 21 | ); 22 | 23 | logic q, nq1, nq2, nclk; 24 | 25 | d_latch dl1(d, clk, q, nq1); 26 | d_latch dl2(q, nclk, o, nq2); 27 | not g(nclk, clk); 28 | 29 | endmodule 30 | -------------------------------------------------------------------------------- /tests/dlatch_gate.sv: -------------------------------------------------------------------------------- 1 | // D latch (gate model) 2 | module d_latch( 3 | input d, e, 4 | output q, nq 5 | ); 6 | 7 | logic s, r, nd; 8 | 9 | nor g1(q, r, nq); 10 | nor g2(nq, s, q); 11 | and g3(r, e, nd); 12 | and g4(s, e, d); 13 | not g5(nd, d); 14 | 15 | endmodule 16 | -------------------------------------------------------------------------------- /tests/fsm.sv: -------------------------------------------------------------------------------- 1 | // Write your modules here! 2 | module fsm(input clk, rst, a, output logic b); 3 | 4 | (* fsm_encoding = "auto" *) 5 | logic [1:0] state; 6 | 7 | localparam A = 2'b00; 8 | localparam B = 2'b01; 9 | localparam C = 2'b10; 10 | localparam D = 2'b11; 11 | 12 | always_ff @(posedge clk or posedge rst) 13 | if (rst) state <= B; 14 | else unique case(state) 15 | A: state <= C; 16 | B: state <= D; 17 | C: if (a) state <= D; else state <= B; 18 | D: state <= A; 19 | endcase 20 | 21 | always_comb begin 22 | b = 1'bx; 23 | unique case(state) 24 | A, D: b = 0; 25 | B: b = 1; 26 | C: if (a) b = 1; else b = 0; 27 | endcase 28 | end 29 | 30 | endmodule 31 | -------------------------------------------------------------------------------- /tests/fulladder.sv: -------------------------------------------------------------------------------- 1 | // Half adder 2 | module halfadder( 3 | input a, 4 | input b, 5 | output o, 6 | output c 7 | ); 8 | 9 | assign o = a ^ b; 10 | assign c = a & b; 11 | 12 | endmodule 13 | 14 | // Full adder 15 | module fulladder( 16 | input a, 17 | input b, 18 | input d, 19 | output o, 20 | output c 21 | ); 22 | 23 | logic t, c1, c2; 24 | 25 | halfadder ha1(a, b, t, c1); 26 | halfadder ha2(t, d, o, c2); 27 | 28 | assign c = c1 | c2; 29 | 30 | endmodule 31 | 32 | -------------------------------------------------------------------------------- /tests/grouping_test.sv: -------------------------------------------------------------------------------- 1 | module grouping_test(input a, b, c, input [2:0] d1, input [3:0] d2, output [2:0] o1, output [3:0] o2); 2 | assign o1 = b ? {1'b0, a, a} : d1; 3 | assign o2 = c ? {2'b0, a, a} : d2; 4 | endmodule 5 | -------------------------------------------------------------------------------- /tests/lfsr.sv: -------------------------------------------------------------------------------- 1 | // Linear feedback shift register 2 | module lfsr( 3 | output logic [7:0] out, 4 | input clk, 5 | input reset 6 | ); 7 | 8 | logic linear_feedback; 9 | 10 | assign linear_feedback = ! (out[7] ^ out[3]); 11 | 12 | always_ff @(posedge clk or posedge reset) 13 | if (reset) begin 14 | out <= 8'b0 ; 15 | end else begin 16 | out <= {out[6],out[5], 17 | out[4],out[3], 18 | out[2],out[1], 19 | out[0], linear_feedback}; 20 | end 21 | 22 | endmodule 23 | -------------------------------------------------------------------------------- /tests/prio_encoder.sv: -------------------------------------------------------------------------------- 1 | // Four-bit priority encoder 2 | module prio_encoder( 3 | output logic [1:0] y, 4 | output logic valid, 5 | input [3:0] a 6 | ); 7 | 8 | always_comb 9 | casez (a) 10 | 4'b1??? : {y,valid} = 3'b111; 11 | 4'b01?? : {y,valid} = 3'b101; 12 | 4'b001? : {y,valid} = 3'b011; 13 | 4'b0001 : {y,valid} = 3'b001; 14 | default : {y,valid} = 3'b000; 15 | endcase 16 | 17 | endmodule 18 | -------------------------------------------------------------------------------- /tests/ram.sv: -------------------------------------------------------------------------------- 1 | // Simple RAM 2 | module ram 3 | #(parameter AWIDTH = 4, DWIDTH = 4)( 4 | input clk, 5 | input [AWIDTH-1:0] addr, 6 | output [DWIDTH-1:0] data, 7 | input [AWIDTH-1:0] wraddr, 8 | input [DWIDTH-1:0] wrdata 9 | ); 10 | 11 | integer i; 12 | 13 | logic [DWIDTH-1:0] mem[2**AWIDTH-1:0]; 14 | 15 | initial begin 16 | for (i = 0; i < 2**AWIDTH; i = i+1) mem[i] = i; 17 | end 18 | 19 | assign data = mem[addr]; 20 | 21 | always_ff @(posedge clk) mem[wraddr] <= wrdata; 22 | 23 | endmodule 24 | -------------------------------------------------------------------------------- /tests/reduce_and.sv: -------------------------------------------------------------------------------- 1 | // Multi-input AND gate 2 | module red(input [3:0] i, output o); 3 | assign o = &i; 4 | endmodule 5 | 6 | -------------------------------------------------------------------------------- /tests/rom.sv: -------------------------------------------------------------------------------- 1 | // Simple asynchronous ROM 2 | module rom 3 | #(parameter AWIDTH = 4, DWIDTH = 4)( 4 | input [AWIDTH-1:0] addr, 5 | output [DWIDTH-1:0] data 6 | ); 7 | 8 | integer i; 9 | 10 | logic [DWIDTH-1:0] mem[2**AWIDTH-1:0]; 11 | 12 | initial begin 13 | for (i = 0; i < 2**AWIDTH; i = i+1) mem[i] = i; 14 | end 15 | 16 | assign data = mem[addr]; 17 | 18 | endmodule 19 | -------------------------------------------------------------------------------- /tests/serialadder.sv: -------------------------------------------------------------------------------- 1 | // Half adder 2 | module halfadder( 3 | input a, 4 | input b, 5 | output o, 6 | output c 7 | ); 8 | 9 | assign o = a ^ b; 10 | assign c = a & b; 11 | 12 | endmodule 13 | 14 | // Full adder 15 | module fulladder( 16 | input a, 17 | input b, 18 | input d, 19 | output o, 20 | output c 21 | ); 22 | 23 | logic t, c1, c2; 24 | 25 | halfadder ha1(a, b, t, c1); 26 | halfadder ha2(t, d, o, c2); 27 | 28 | assign c = c1 | c2; 29 | 30 | endmodule 31 | 32 | // Multibit serial adder 33 | module serialadder 34 | #(parameter WIDTH = 4)( 35 | input [WIDTH-1:0] a, 36 | input [WIDTH-1:0] b, 37 | output [WIDTH:0] o 38 | ); 39 | 40 | logic [WIDTH:0] c; 41 | logic [WIDTH-1:0] s; 42 | 43 | assign c[0] = 1'b0; 44 | 45 | genvar ii; 46 | generate 47 | for (ii=0; ii