├── .babelrc ├── .github ├── FUNDING.yml └── workflows │ ├── codeql-analysis.yml │ └── nodejs.yml ├── .gitignore ├── ChangeLog.md ├── LICENSE ├── README.md ├── babel.config.json ├── docs └── resources │ ├── digitaljs_text_right.svg │ └── digitaljs_textpath_right.svg ├── examples ├── arithconst.json ├── biggate.json ├── cycleadder.json ├── fsm.json ├── fulladder.json ├── gates.json ├── horner.json ├── io.json ├── latch.json ├── lfsr.json ├── muxsparse.json ├── ram.json ├── rom.json ├── serialadder.json ├── sextium.json ├── template.html └── warnings.json ├── package-lock.json ├── package.json ├── src ├── cells.mjs ├── cells │ ├── arith.mjs │ ├── base.mjs │ ├── bus.mjs │ ├── dff.mjs │ ├── display7.mjs │ ├── fsm.mjs │ ├── gates.mjs │ ├── io.mjs │ ├── memory.mjs │ ├── mux.mjs │ └── subcircuit.mjs ├── circuit.mjs ├── elkjs.mjs ├── engines.mjs ├── engines │ ├── base.mjs │ ├── browsersynch.mjs │ ├── synch.mjs │ ├── worker-worker.mjs │ └── worker.mjs ├── help.mjs ├── index.mjs ├── iopanel.mjs ├── monitor.mjs ├── style.css ├── tools.mjs └── transform.mjs ├── tests └── index.test.mjs └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [ 5 | ["@babel/preset-env", 6 | { 7 | "modules": "commonjs", 8 | "exclude": ["babel-plugin-transform-exponentiation-operator"] 9 | }] 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: tilk 2 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '38 3 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'javascript' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.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 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - run: npm install 21 | - run: npm run build --if-present 22 | - run: npm test 23 | env: 24 | CI: true 25 | - name: Upload artifact 26 | uses: actions/upload-artifact@v1.0.0 27 | with: 28 | name: webpack 29 | path: dist 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | /lib 4 | *.swp 5 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [0.13.0] -- 2023-03-2023 5 | 6 | ### Added 7 | - Circuit zooming buttons 8 | 9 | ### Fixed 10 | - Fixed undefined reference in memory 11 | 12 | ## [0.12.1] -- 2022-03-17 13 | 14 | ### Fixed 15 | - The cells, tools, engines and transforms namespaces are now exported for non-browser environments. 16 | 17 | ## [0.12.0] -- 2022-02-22 18 | 19 | ### Fixed 20 | - Fix the lifetime of the event listeners for the sub-windows (Contributor: Yichao Yu) 21 | - Fix binary mux transform for Yosys 0.12 and later 22 | - Elkjs layout change marked as batch (Contributor: Yichao Yu) 23 | - Save IO port orders for subcircuits (Contributor: Yichao Yu) 24 | 25 | ## [0.11.0] -- 2022-02-03 26 | 27 | ### Added 28 | - The `Dff` block can now have asynchronous loading. 29 | 30 | ### Changed 31 | - The `elkjs` layout engine now includes laying out edges via adding bend points. The SPOrE algorithm was disabled because it did not work well with the change. (Contributor: Yichao Yu) 32 | - The interface now works better with touch devices. (Contributor: Yichao Yu) 33 | 34 | ### Fixed 35 | - The `*Const` family of arithmetic operations is now correctly JSON serialized, and they are better tested. (Contributor: Yichao Yu) 36 | - Parameter changes (e.g. clock speed) are now propagated to the worker thread in the WebWorker engine. 37 | 38 | ## [0.10.0] -- 2021-10-06 39 | 40 | ### Added 41 | - The `Dff` block can now have built-in synchronous reset. 42 | - The `Dff` block can now have `set` and `clr` inputs for setting/resetting individual bits. 43 | - The `Memory` block can now have an initialization value, synchronous and asynchronous reset for synchronous read ports. 44 | - The `Memory` block can now have port transparency behavior different for each write port. 45 | 46 | ### Changed 47 | - The `Memory` block has working bit enables for write ports. 48 | - The `Memory` block can now have single bit write enables. 49 | 50 | ## [0.9.0] -- 2021-08-17 51 | 52 | ### Added 53 | - A new simulation engine, using Web Workers, has been added. It is more responsible and can be much faster than the original one (25x measured improvement). 54 | 55 | ### Changed 56 | - Architecture was changed to enable simulation engines running concurrently with the UI. 57 | - Webpack updated to version 5. 58 | 59 | ## [0.8.0] -- 2021-07-21 60 | 61 | ### Added 62 | - Circuit graph transformations, which make common circuits generated by Yosys more readable. 63 | - MuxSparse, a new multiplexer cell for situations when only a few of possible options is defined. 64 | - ElkJS layout engine, which is (in contrast to Dagre) aware of ports. 65 | 66 | ### Changed 67 | - Gates can now have more than two inputs. 68 | 69 | -------------------------------------------------------------------------------- /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 | ![][digitaljs-logo] 2 | 3 | # DigitalJS 4 | 5 | This project is a digital circuit simulator implemented in Javascript. 6 | It is designed to simulate circuits synthesized by hardware design tools 7 | like [Yosys](https://yosyshq.net/yosys/) (Github repo [here](https://github.com/YosysHQ/yosys/)), and it has a companion project 8 | [yosys2digitaljs](https://github.com/tilk/yosys2digitaljs), which converts 9 | Yosys output files to DigitalJS. It is also intended to be a teaching tool, 10 | therefore readability and ease of inspection is one of top concerns for 11 | the project. 12 | 13 | You can [try it out online](https://digitaljs.tilk.eu/). The web app is 14 | [a separate Github project](https://github.com/tilk/digitaljs_online/). 15 | 16 | # Usage 17 | 18 | You can use DigitalJS in your project by installing it from NPM: 19 | 20 | ```bash 21 | npm install digitaljs 22 | ``` 23 | 24 | Or you can use the [Webpack bundle](https://tilk.github.io/digitaljs/main.js) directly. 25 | 26 | To simulate a circuit represented using the JSON input format (described later) 27 | and display it on a `div` named `#paper`, you need to run the following 28 | JS code ([see running example](https://tilk.github.io/digitaljs/test/fulladder.html)): 29 | 30 | ```javascript 31 | // create the simulation object 32 | const circuit = new digitaljs.Circuit(input_goes_here); 33 | // display on #paper 34 | const paper = circuit.displayOn($('#paper')); 35 | // activate real-time simulation 36 | circuit.start(); 37 | ``` 38 | 39 | # Input format 40 | 41 | Circuits are represented using JSON. The top-level object has three keys, `devices`, 42 | `connectors` and `subcircuits`. Under `devices` is a list of all devices forming 43 | the circuit, represented as an object, where keys are (unique and internal) device 44 | names. Each device has a number of properties, which are represented by an object. 45 | A mandatory property is `type`, which specifies the type of the device. Example 46 | device: 47 | 48 | ```javascript 49 | "dev1": { 50 | "type": "And", 51 | "label": "AND1" 52 | } 53 | ``` 54 | 55 | Under `connectors` is a list of connections between device ports, represented as an 56 | array of objects with two keys, `from` and `to`. Both keys map to an object with two 57 | keys, `id` and `port`; the first corresponds to a device name, and the second -- to 58 | a valid port name for the device. A connection must lead from an output port to 59 | an input port, and the bitwidth of both ports must be equal. Example connection: 60 | 61 | ```javascript 62 | { 63 | "from": { 64 | "id": "dev1", 65 | "port": "out" 66 | }, 67 | "to": { 68 | "id": "dev2", 69 | "port": "in" 70 | } 71 | } 72 | ``` 73 | 74 | Under `subcircuits` is a list of subcircuit definitions, represented as an object, 75 | where keys are unique subcircuit names. A subcircuit name can be used as 76 | a `celltype` for a device of type `Subcircuit`; this instantiates the subcircuit. 77 | A subcircuit definition 78 | follows the representation of whole circuits, with the exception that subcircuits 79 | cannot (currently) define their own subcircuits. A subcircuit can include 80 | `Input` and `Output` devices, these are mapped to ports on a subcircuit 81 | instance. 82 | 83 | ## Device types 84 | 85 | * Unary gates: `Not`, `Repeater` 86 | * Attributes: `bits` (natural number) 87 | * Inputs: `in` (`bits`-bit) 88 | * Outputs: `out` (`bits`-bit) 89 | * N-ary gates: `And`, `Nand`, `Or`, `Nor`, `Xor`, `Xnor` 90 | * Attributes: `bits` (natural number), `inputs` (natural number, default 2) 91 | * Inputs: `in1`, `in2` ... `inN` (`bits`-bit, `N` = `inputs`) 92 | * Outputs: `out` (`bits`-bit) 93 | * Reducing gates: `AndReduce`, `NandReduce`, `OrReduce`, `NorReduce`, `XorReduce`, `XnorReduce` 94 | * Attributes: `bits` (natural number) 95 | * Inputs: `in` (`bits`-bit) 96 | * Outputs: `out` (1-bit) 97 | * Bit shifts: `ShiftLeft`, `ShiftRight` 98 | * Attributes: `bits.in1`, `bits.in2` and `bits.out` (natural number), `signed.in1`, `signed.in2`, `signed.out` and `fillx` (boolean) 99 | * Inputs: `in1` (`bits.in1`-bit), `in2` (`bits.in2`-bit) 100 | * Outputs: `out` (`bits.out`-bit) 101 | * Comparisons: `Eq`, `Ne`, `Lt`, `Le`, `Gt`, `Ge` 102 | * Attributes: `bits.in1` and `bits.in2` (natural number), `signed.in1` and `signed.in2` (boolean) 103 | * Inputs: `in1` (`bits.in1`-bit), `in2` (`bits.in2`-bit) 104 | * Outputs: `out` (1-bit) 105 | * Number constant: `Constant` 106 | * Attributes: `constant` (binary string) 107 | * Outputs: `out` (`constant.length`-bit) 108 | * Unary arithmetic: `Negation`, `UnaryPlus` 109 | * Attributes: `bits.in` and `bits.out` (natural number), `signed` (boolean) 110 | * Inputs: `in` (`bits.in`-bit) 111 | * Outputs: `out` (`bits.out`-bit) 112 | * Binary arithmetic: `Addition`, `Subtraction`, `Multiplication`, `Division`, `Modulo`, `Power` 113 | * Attributes: `bits.in1`, `bits.in2` and `bits.out` (natural number), `signed.in1` and `signed.in2` (boolean) 114 | * Inputs: `in1` (`bits.in1`-bit), `in2` (`bits.in2`-bit) 115 | * Outputs: `out` (`bits.out`-bit) 116 | * Multiplexer: `Mux` 117 | * Attributes: `bits.in`, `bits.sel` (natural number) 118 | * Inputs: `in0` ... `inN` (`bits.in`-bit, `N` = 2**`bits.sel`-1), `sel` (`bits.sel`-bit) 119 | * Outputs: `out` (`bits.in`-bit) 120 | * One-hot multiplexer: `Mux1Hot` 121 | * Attributes: `bits.in`, `bits.sel` (natural number) 122 | * Inputs: `in0` ... `inN` (`bits.in`-bit, `N` = `bits.sel`), `sel` (`bits.sel`-bit) 123 | * Outputs: `out` (`bits.in`-bit) 124 | * Sparse multiplexer: `MuxSparse` 125 | * Attributes: `bits.in`, `bits.sel` (natural number), `inputs` (list of natural numbers), `default_input` (optional boolean) 126 | * Inputs: `in0` ... `inN` (`bits.in`-bit, `N` = `inputs.length`, +1 if `default_input` is true) 127 | * Outputs: `out` (`bits.in`-bit) 128 | * D flip-flop: `Dff` 129 | * Attributes: `bits` (natural number), `polarity.clock`, `polarity.arst`, `polarity.srst`, `polarity.aload`, `polarity.set`, `polarity.clr`, `polarity.enable`, `enable_srst` (optional booleans), `initial` (optional binary string), `arst_value`, `srst_value` (optional binary string), `no_data` (optional boolean) 130 | * Inputs: `in` (`bits`-bit), `clk` (1-bit, if `polarity.clock` is present), `arst` (1-bit, if `polarity.arst` is present), `srst` (1-bit, if `polarity.srst` is present), `en` (1-bit, if `polarity.enable` is present), `set` (1-bit, if `polarity.set` is present), `clr` (1-bit, if `polarity.clr` is present), `ain` (`bits`-bit, if `polarity.aload` is present), `aload` (1-bit, if `polarity.aload` is present) 131 | * Outputs: `out` (`bits`-bit) 132 | * Memory: `Memory` 133 | * Attributes: `bits`, `abits`, `words`, `offset` (natural number), `rdports` (array of read port descriptors), `wrports` (array of write port descriptors), `memdata` (memory contents description) 134 | * Read port descriptor attributes: `enable_polarity`, `clock_polarity`, `arst_polarity`, `srst_polarity` (optional booleans), `init_value`, `arst_value`, `srst_value` (optional binary strings), `transparent`, `collision` (optional booleans or arrays of booleans) 135 | * Write port descriptor attributes: `enable_polarity`, `clock_polarity`, `no_bit_enable` (optional booleans) 136 | * Inputs (per read port): `rdKaddr` (`abits`-bit), `rdKen` (1-bit, if `enable_polarity` is present), `rdKclk` (1-bit, if `clock_polarity` is present), `rdKarst` (1-bit, if `arst_polarity` is present), `rdKsrst` (1-bit, if `srst_polarity` is present) 137 | * Outputs (per read port): `rdKdata` (`bits`-bit) 138 | * Inputs (per write port): `wrKaddr` (`abits`-bit), `wrKdata` (`bits`-bit), `wrKen` (1-bit (when `no_bit_enable` is true) or `bits`-bit (otherwise), if `enable_polarity` is present), `wrKclk` (1-bit, if `clock_polarity` is present) 139 | * Clock source: `Clock` 140 | * Outputs: `out` (1-bit) 141 | * Button input: `Button` 142 | * Outputs: `out` (1-bit) 143 | * Lamp output: `Lamp` 144 | * Inputs: `in` (1-bit) 145 | * Number input: `NumEntry` 146 | * Attributes: `bits` (natural number), `numbase` (string) 147 | * Outputs: `out` (`bits`-bit) 148 | * Number output: `NumDisplay` 149 | * Attributes: `bits` (natural number), `numbase` (string) 150 | * Inputs: `in` (`bits`-bit) 151 | * Subcircuit input: `Input` 152 | * Attributes: `bits` (natural number) 153 | * Outputs: `out` (`bits`-bit) 154 | * Subcircuit output: `Output` 155 | * Attributes: `bits` (natural number) 156 | * Inputs: `in` (`bits`-bit) 157 | * 7 segment display output: `Display7` 158 | * Inputs: `bits` (8-bit only - most significant bit controls decimal point LED) 159 | * Bus grouping: `BusGroup` 160 | * Attributes: `groups` (array of natural numbers) 161 | * Inputs: `in0` (`groups[0]`-bit) ... `inN` (`groups[N]`-bit) 162 | * Outputs: `out` (sum-of-`groups`-bit) 163 | * Bus ungrouping: `BusUngroup` 164 | * Attributes: `groups` (array of natural numbers) 165 | * Inputs: `in` (sum-of-`groups`-bit) 166 | * Outputs: `out0` (`groups[0]`-bit) ... `outN` (`groups[N]`-bit) 167 | * Bus slicing: `BusSlice` 168 | * Attributes: `slice.first`, `slice.count`, `slice.total` (natural number) 169 | * Inputs: `in` (`slice.total`-bit) 170 | * Outputs: `out` (`slice.count`-bit) 171 | * Zero- and sign-extension: `ZeroExtend`, `SignExtend` 172 | * Attributes: `extend.input`, `extend.output` (natural number) 173 | * Inputs: `in` (`extend.input`-bit) 174 | * Outputs: `out` (`extend.output`-bit) 175 | * Finite state machines: `FSM` 176 | * Attributes: `bits.in`, `bits.out`, `states`, `init_state`, `current_state` (natural number), `trans_table` (array of transition descriptors) 177 | * Transition descriptor attributes: `ctrl_in`, `ctrl_out` (binary strings), `state_in`, `state_out` (natural numbers) 178 | * Inputs: `clk` (1-bit), `arst` (1-bit), `in` (`bits.in`-bit) 179 | * Outputs: `out` (`bits.out`-bit) 180 | 181 | # TODO 182 | 183 | Some ideas for further developing the simulator. 184 | 185 | * Use JointJS elementTools for configuring/removing gates. 186 | * RAM/ROM import/export for Verilog format and Intel HEX. 187 | * Framebuffer element with character/bitmap display. 188 | * More editing capability: adding and removing blocks, modifying some of blocks' properties. 189 | * Undo-redo capability. 190 | * Saving and loading circuits, including layout and state. 191 | * Generic handling of negation for unary/binary gates (negation on inputs/outputs) for better clarity. 192 | * SVG export. 193 | * Verilog export. 194 | * Smartphone and tablet compatible UI. 195 | 196 | [digitaljs-logo]: docs/resources/digitaljs_textpath_right.svg 197 | 198 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": true 8 | }, 9 | "modules": "cjs", 10 | "exclude": ["babel-plugin-transform-exponentiation-operator"] 11 | } 12 | ] 13 | ], 14 | "plugins": ["babel-plugin-add-import-extension", "babel-plugin-transform-import-meta"] 15 | } 16 | -------------------------------------------------------------------------------- /docs/resources/digitaljs_text_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 23 | 25 | image/svg+xml 26 | 28 | 29 | 30 | 31 | 32 | 34 | 37 | 41 | 45 | 46 | 49 | 53 | 57 | 58 | 68 | 78 | 86 | 90 | 91 | 92 | 119 | 124 | 130 | 136 | 142 | 148 | 154 | 159 | 165 | 176 | DigitalJS 187 | 188 | -------------------------------------------------------------------------------- /examples/arithconst.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "dev0": { 4 | "type": "NumEntry", 5 | "label": "a", 6 | "net": "a", 7 | "bits": 4 8 | }, 9 | "dev1": { 10 | "type": "NumDisplay", 11 | "label": "o", 12 | "net": "o", 13 | "bits": 4 14 | }, 15 | "dev2": { 16 | "type": "AdditionConst", 17 | "label": "op", 18 | "bits": {"in": 4, "out": 4}, 19 | "constant": 1 20 | }, 21 | "dev3": { 22 | "type": "Lamp", 23 | "label": "o2", 24 | "net": "o2" 25 | }, 26 | "dev4": { 27 | "type": "LtConst", 28 | "label": "op", 29 | "bits": {"in": 4}, 30 | "constant": 1 31 | } 32 | }, 33 | "connectors": [ 34 | { 35 | "to": { 36 | "id": "dev2", 37 | "port": "in" 38 | }, 39 | "from": { 40 | "id": "dev0", 41 | "port": "out" 42 | }, 43 | "name": "a" 44 | }, 45 | { 46 | "to": { 47 | "id": "dev1", 48 | "port": "in" 49 | }, 50 | "from": { 51 | "id": "dev2", 52 | "port": "out" 53 | }, 54 | "name": "o" 55 | }, 56 | { 57 | "to": { 58 | "id": "dev4", 59 | "port": "in" 60 | }, 61 | "from": { 62 | "id": "dev0", 63 | "port": "out" 64 | }, 65 | "name": "a" 66 | }, 67 | { 68 | "to": { 69 | "id": "dev3", 70 | "port": "in" 71 | }, 72 | "from": { 73 | "id": "dev4", 74 | "port": "out" 75 | }, 76 | "name": "o2" 77 | } 78 | ], 79 | "subcircuits": [] 80 | } 81 | 82 | -------------------------------------------------------------------------------- /examples/biggate.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "in1": { 4 | "type": "Button", 5 | "label": "a", 6 | "net": "a" 7 | }, 8 | "in2": { 9 | "type": "Button", 10 | "label": "b", 11 | "net": "b" 12 | }, 13 | "in3": { 14 | "type": "Button", 15 | "label": "c", 16 | "net": "c" 17 | }, 18 | "in4": { 19 | "type": "Button", 20 | "label": "d", 21 | "net": "d" 22 | }, 23 | "in5": { 24 | "type": "Button", 25 | "label": "e", 26 | "net": "e" 27 | }, 28 | "out2": { 29 | "type": "Lamp", 30 | "label": "o2", 31 | "net": "o2" 32 | }, 33 | "out3": { 34 | "type": "Lamp", 35 | "label": "o3", 36 | "net": "o3" 37 | }, 38 | "out4": { 39 | "type": "Lamp", 40 | "label": "o4", 41 | "net": "o4" 42 | }, 43 | "out5": { 44 | "type": "Lamp", 45 | "label": "o5", 46 | "net": "o5" 47 | }, 48 | "gate2": { 49 | "type": "And", 50 | "inputs": 2 51 | }, 52 | "gate3": { 53 | "type": "And", 54 | "inputs": 3 55 | }, 56 | "gate4": { 57 | "type": "And", 58 | "inputs": 4 59 | }, 60 | "gate5": { 61 | "type": "And", 62 | "inputs": 5 63 | } 64 | }, 65 | "connectors": [ 66 | { 67 | "from": { 68 | "id": "in1", 69 | "port": "out" 70 | }, 71 | "to": { 72 | "id": "gate5", 73 | "port": "in1" 74 | } 75 | }, 76 | { 77 | "from": { 78 | "id": "in2", 79 | "port": "out" 80 | }, 81 | "to": { 82 | "id": "gate5", 83 | "port": "in2" 84 | } 85 | }, 86 | { 87 | "from": { 88 | "id": "in3", 89 | "port": "out" 90 | }, 91 | "to": { 92 | "id": "gate5", 93 | "port": "in3" 94 | } 95 | }, 96 | { 97 | "from": { 98 | "id": "in4", 99 | "port": "out" 100 | }, 101 | "to": { 102 | "id": "gate5", 103 | "port": "in4" 104 | } 105 | }, 106 | { 107 | "from": { 108 | "id": "in5", 109 | "port": "out" 110 | }, 111 | "to": { 112 | "id": "gate5", 113 | "port": "in5" 114 | } 115 | }, 116 | { 117 | "from": { 118 | "id": "gate5", 119 | "port": "out" 120 | }, 121 | "to": { 122 | "id": "out5", 123 | "port": "in" 124 | } 125 | }, 126 | { 127 | "from": { 128 | "id": "in1", 129 | "port": "out" 130 | }, 131 | "to": { 132 | "id": "gate4", 133 | "port": "in1" 134 | } 135 | }, 136 | { 137 | "from": { 138 | "id": "in2", 139 | "port": "out" 140 | }, 141 | "to": { 142 | "id": "gate4", 143 | "port": "in2" 144 | } 145 | }, 146 | { 147 | "from": { 148 | "id": "in3", 149 | "port": "out" 150 | }, 151 | "to": { 152 | "id": "gate4", 153 | "port": "in3" 154 | } 155 | }, 156 | { 157 | "from": { 158 | "id": "in4", 159 | "port": "out" 160 | }, 161 | "to": { 162 | "id": "gate4", 163 | "port": "in4" 164 | } 165 | }, 166 | { 167 | "from": { 168 | "id": "gate4", 169 | "port": "out" 170 | }, 171 | "to": { 172 | "id": "out4", 173 | "port": "in" 174 | } 175 | }, 176 | { 177 | "from": { 178 | "id": "in1", 179 | "port": "out" 180 | }, 181 | "to": { 182 | "id": "gate3", 183 | "port": "in1" 184 | } 185 | }, 186 | { 187 | "from": { 188 | "id": "in2", 189 | "port": "out" 190 | }, 191 | "to": { 192 | "id": "gate3", 193 | "port": "in2" 194 | } 195 | }, 196 | { 197 | "from": { 198 | "id": "in3", 199 | "port": "out" 200 | }, 201 | "to": { 202 | "id": "gate3", 203 | "port": "in3" 204 | } 205 | }, 206 | { 207 | "from": { 208 | "id": "gate3", 209 | "port": "out" 210 | }, 211 | "to": { 212 | "id": "out3", 213 | "port": "in" 214 | } 215 | }, 216 | { 217 | "from": { 218 | "id": "in1", 219 | "port": "out" 220 | }, 221 | "to": { 222 | "id": "gate2", 223 | "port": "in1" 224 | } 225 | }, 226 | { 227 | "from": { 228 | "id": "in2", 229 | "port": "out" 230 | }, 231 | "to": { 232 | "id": "gate2", 233 | "port": "in2" 234 | } 235 | }, 236 | { 237 | "from": { 238 | "id": "gate2", 239 | "port": "out" 240 | }, 241 | "to": { 242 | "id": "out2", 243 | "port": "in" 244 | } 245 | } 246 | ] 247 | } 248 | -------------------------------------------------------------------------------- /examples/cycleadder.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "dev0": { 4 | "type": "Clock", 5 | "label": "clk", 6 | "net": "clk", 7 | "order": 0, 8 | "bits": 1, 9 | "propagation": 100 10 | }, 11 | "dev1": { 12 | "type": "Button", 13 | "label": "rst", 14 | "net": "rst", 15 | "order": 1, 16 | "bits": 1 17 | }, 18 | "dev2": { 19 | "type": "Button", 20 | "label": "en", 21 | "net": "en", 22 | "order": 2, 23 | "bits": 1 24 | }, 25 | "dev3": { 26 | "type": "NumEntry", 27 | "label": "A", 28 | "net": "A", 29 | "order": 3, 30 | "bits": 4 31 | }, 32 | "dev4": { 33 | "type": "NumDisplay", 34 | "label": "O", 35 | "net": "O", 36 | "order": 4, 37 | "bits": 4 38 | }, 39 | "dev5": { 40 | "label": "$add$tests/cycleadder.sv:13$2", 41 | "type": "Addition", 42 | "bits": { 43 | "in1": 4, 44 | "in2": 4, 45 | "out": 4 46 | }, 47 | "signed": { 48 | "in1": false, 49 | "in2": false 50 | } 51 | }, 52 | "dev6": { 53 | "label": "$procdff$9", 54 | "type": "Dff", 55 | "bits": 4, 56 | "polarity": { 57 | "clock": true 58 | } 59 | }, 60 | "dev7": { 61 | "label": "$procmux$4", 62 | "type": "Mux", 63 | "bits": { 64 | "in": 4, 65 | "sel": 1 66 | } 67 | }, 68 | "dev8": { 69 | "label": "$procmux$7", 70 | "type": "Mux", 71 | "bits": { 72 | "in": 4, 73 | "sel": 1 74 | } 75 | }, 76 | "dev9": { 77 | "type": "Constant", 78 | "constant": "0000" 79 | } 80 | }, 81 | "connectors": [ 82 | { 83 | "to": { 84 | "id": "dev6", 85 | "port": "clk" 86 | }, 87 | "from": { 88 | "id": "dev0", 89 | "port": "out" 90 | }, 91 | "name": "clk" 92 | }, 93 | { 94 | "to": { 95 | "id": "dev8", 96 | "port": "sel" 97 | }, 98 | "from": { 99 | "id": "dev1", 100 | "port": "out" 101 | }, 102 | "name": "rst" 103 | }, 104 | { 105 | "to": { 106 | "id": "dev7", 107 | "port": "sel" 108 | }, 109 | "from": { 110 | "id": "dev2", 111 | "port": "out" 112 | }, 113 | "name": "en" 114 | }, 115 | { 116 | "to": { 117 | "id": "dev5", 118 | "port": "in2" 119 | }, 120 | "from": { 121 | "id": "dev3", 122 | "port": "out" 123 | }, 124 | "name": "A" 125 | }, 126 | { 127 | "to": { 128 | "id": "dev4", 129 | "port": "in" 130 | }, 131 | "from": { 132 | "id": "dev6", 133 | "port": "out" 134 | }, 135 | "name": "O" 136 | }, 137 | { 138 | "to": { 139 | "id": "dev5", 140 | "port": "in1" 141 | }, 142 | "from": { 143 | "id": "dev6", 144 | "port": "out" 145 | }, 146 | "name": "O" 147 | }, 148 | { 149 | "to": { 150 | "id": "dev7", 151 | "port": "in0" 152 | }, 153 | "from": { 154 | "id": "dev6", 155 | "port": "out" 156 | }, 157 | "name": "O" 158 | }, 159 | { 160 | "to": { 161 | "id": "dev7", 162 | "port": "in1" 163 | }, 164 | "from": { 165 | "id": "dev5", 166 | "port": "out" 167 | } 168 | }, 169 | { 170 | "to": { 171 | "id": "dev6", 172 | "port": "in" 173 | }, 174 | "from": { 175 | "id": "dev8", 176 | "port": "out" 177 | } 178 | }, 179 | { 180 | "to": { 181 | "id": "dev8", 182 | "port": "in0" 183 | }, 184 | "from": { 185 | "id": "dev7", 186 | "port": "out" 187 | } 188 | }, 189 | { 190 | "to": { 191 | "id": "dev8", 192 | "port": "in1" 193 | }, 194 | "from": { 195 | "id": "dev9", 196 | "port": "out" 197 | } 198 | } 199 | ], 200 | "subcircuits": {} 201 | } 202 | -------------------------------------------------------------------------------- /examples/fsm.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "dev0": { 4 | "celltype": "$clock", 5 | "label": "clk", 6 | "net": "clk", 7 | "order": 0, 8 | "bits": 1, 9 | "propagation": 100 10 | }, 11 | "dev1": { 12 | "celltype": "$button", 13 | "label": "rst", 14 | "net": "rst", 15 | "order": 1, 16 | "bits": 1 17 | }, 18 | "dev2": { 19 | "celltype": "$button", 20 | "label": "a", 21 | "net": "a", 22 | "order": 2, 23 | "bits": 1 24 | }, 25 | "dev3": { 26 | "celltype": "$lamp", 27 | "label": "b", 28 | "net": "b", 29 | "order": 3, 30 | "bits": 1 31 | }, 32 | "dev4": { 33 | "label": "$fsm$\\state$21", 34 | "celltype": "$fsm", 35 | "polarity": { 36 | "clock": true, 37 | "arst": true 38 | }, 39 | "wirename": "\\state", 40 | "bits": { 41 | "in": 1, 42 | "out": 5 43 | }, 44 | "states": 4, 45 | "init_state": 2, 46 | "trans_table": [ 47 | { 48 | "state_in": 3, 49 | "ctrl_in": "x", 50 | "state_out": 0, 51 | "ctrl_out": "10000" 52 | }, 53 | { 54 | "state_in": 2, 55 | "ctrl_in": "x", 56 | "state_out": 3, 57 | "ctrl_out": "00100" 58 | }, 59 | { 60 | "state_in": 1, 61 | "ctrl_in": "1", 62 | "state_out": 3, 63 | "ctrl_out": "00011" 64 | }, 65 | { 66 | "state_in": 1, 67 | "ctrl_in": "0", 68 | "state_out": 2, 69 | "ctrl_out": "00011" 70 | }, 71 | { 72 | "state_in": 0, 73 | "ctrl_in": "x", 74 | "state_out": 1, 75 | "ctrl_out": "01000" 76 | } 77 | ] 78 | }, 79 | "dev5": { 80 | "label": "$procmux$11_ANY", 81 | "celltype": "$reduce_or", 82 | "bits": 2 83 | }, 84 | "dev6": { 85 | "label": "$procmux$4", 86 | "celltype": "$mux", 87 | "bits": { 88 | "in": 1, 89 | "sel": 1 90 | } 91 | }, 92 | "dev7": { 93 | "label": "$procmux$8", 94 | "celltype": "$pmux", 95 | "bits": { 96 | "in": 1, 97 | "sel": 3 98 | } 99 | }, 100 | "dev8": { 101 | "celltype": "$busgroup", 102 | "groups": [ 103 | 1, 104 | 1, 105 | 1 106 | ] 107 | }, 108 | "dev9": { 109 | "celltype": "$constant", 110 | "constant": "0" 111 | }, 112 | "dev10": { 113 | "celltype": "$constant", 114 | "constant": "1" 115 | }, 116 | "dev11": { 117 | "celltype": "$constant", 118 | "constant": "x" 119 | }, 120 | "dev12": { 121 | "celltype": "$busslice", 122 | "slice": { 123 | "first": 3, 124 | "count": 2, 125 | "total": 5 126 | } 127 | }, 128 | "dev13": { 129 | "celltype": "$busslice", 130 | "slice": { 131 | "first": 2, 132 | "count": 1, 133 | "total": 5 134 | } 135 | }, 136 | "dev14": { 137 | "celltype": "$busslice", 138 | "slice": { 139 | "first": 1, 140 | "count": 1, 141 | "total": 5 142 | } 143 | } 144 | }, 145 | "connectors": [ 146 | { 147 | "to": { 148 | "id": "dev4", 149 | "port": "clk" 150 | }, 151 | "from": { 152 | "id": "dev0", 153 | "port": "out" 154 | }, 155 | "name": "clk" 156 | }, 157 | { 158 | "to": { 159 | "id": "dev4", 160 | "port": "arst" 161 | }, 162 | "from": { 163 | "id": "dev1", 164 | "port": "out" 165 | }, 166 | "name": "rst" 167 | }, 168 | { 169 | "to": { 170 | "id": "dev4", 171 | "port": "in" 172 | }, 173 | "from": { 174 | "id": "dev2", 175 | "port": "out" 176 | }, 177 | "name": "a" 178 | }, 179 | { 180 | "to": { 181 | "id": "dev6", 182 | "port": "sel" 183 | }, 184 | "from": { 185 | "id": "dev2", 186 | "port": "out" 187 | }, 188 | "name": "a" 189 | }, 190 | { 191 | "to": { 192 | "id": "dev3", 193 | "port": "in" 194 | }, 195 | "from": { 196 | "id": "dev7", 197 | "port": "out" 198 | }, 199 | "name": "b" 200 | }, 201 | { 202 | "to": { 203 | "id": "dev12", 204 | "port": "in" 205 | }, 206 | "from": { 207 | "id": "dev4", 208 | "port": "out" 209 | } 210 | }, 211 | { 212 | "to": { 213 | "id": "dev13", 214 | "port": "in" 215 | }, 216 | "from": { 217 | "id": "dev4", 218 | "port": "out" 219 | } 220 | }, 221 | { 222 | "to": { 223 | "id": "dev14", 224 | "port": "in" 225 | }, 226 | "from": { 227 | "id": "dev4", 228 | "port": "out" 229 | } 230 | }, 231 | { 232 | "to": { 233 | "id": "dev5", 234 | "port": "in" 235 | }, 236 | "from": { 237 | "id": "dev12", 238 | "port": "out" 239 | } 240 | }, 241 | { 242 | "to": { 243 | "id": "dev8", 244 | "port": "in0" 245 | }, 246 | "from": { 247 | "id": "dev5", 248 | "port": "out" 249 | } 250 | }, 251 | { 252 | "to": { 253 | "id": "dev6", 254 | "port": "in0" 255 | }, 256 | "from": { 257 | "id": "dev9", 258 | "port": "out" 259 | } 260 | }, 261 | { 262 | "to": { 263 | "id": "dev7", 264 | "port": "in1" 265 | }, 266 | "from": { 267 | "id": "dev9", 268 | "port": "out" 269 | } 270 | }, 271 | { 272 | "to": { 273 | "id": "dev6", 274 | "port": "in1" 275 | }, 276 | "from": { 277 | "id": "dev10", 278 | "port": "out" 279 | } 280 | }, 281 | { 282 | "to": { 283 | "id": "dev7", 284 | "port": "in2" 285 | }, 286 | "from": { 287 | "id": "dev10", 288 | "port": "out" 289 | } 290 | }, 291 | { 292 | "to": { 293 | "id": "dev7", 294 | "port": "in3" 295 | }, 296 | "from": { 297 | "id": "dev6", 298 | "port": "out" 299 | } 300 | }, 301 | { 302 | "to": { 303 | "id": "dev7", 304 | "port": "in0" 305 | }, 306 | "from": { 307 | "id": "dev11", 308 | "port": "out" 309 | } 310 | }, 311 | { 312 | "to": { 313 | "id": "dev7", 314 | "port": "sel" 315 | }, 316 | "from": { 317 | "id": "dev8", 318 | "port": "out" 319 | } 320 | }, 321 | { 322 | "to": { 323 | "id": "dev8", 324 | "port": "in1" 325 | }, 326 | "from": { 327 | "id": "dev13", 328 | "port": "out" 329 | } 330 | }, 331 | { 332 | "to": { 333 | "id": "dev8", 334 | "port": "in2" 335 | }, 336 | "from": { 337 | "id": "dev14", 338 | "port": "out" 339 | } 340 | } 341 | ], 342 | "subcircuits": {} 343 | } 344 | -------------------------------------------------------------------------------- /examples/fulladder.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "dev0": { 4 | "type": "Button", 5 | "label": "a", 6 | "net": "a", 7 | "order": 0, 8 | "bits": 1 9 | }, 10 | "dev1": { 11 | "type": "Button", 12 | "label": "b", 13 | "net": "b", 14 | "order": 1, 15 | "bits": 1 16 | }, 17 | "dev2": { 18 | "type": "Button", 19 | "label": "d", 20 | "net": "d", 21 | "order": 2, 22 | "bits": 1 23 | }, 24 | "dev3": { 25 | "type": "Lamp", 26 | "label": "o", 27 | "net": "o", 28 | "order": 3, 29 | "bits": 1 30 | }, 31 | "dev4": { 32 | "type": "Lamp", 33 | "label": "c", 34 | "net": "c", 35 | "order": 4, 36 | "bits": 1 37 | }, 38 | "dev5": { 39 | "type": "Or", 40 | "label": "$or$tests/fulladder.sv:28$3", 41 | "bits": 1 42 | }, 43 | "dev6": { 44 | "type": "Subcircuit", 45 | "label": "ha1", 46 | "celltype": "halfadder" 47 | }, 48 | "dev7": { 49 | "type": "Subcircuit", 50 | "label": "ha2", 51 | "celltype": "halfadder" 52 | } 53 | }, 54 | "connectors": [ 55 | { 56 | "to": { 57 | "id": "dev6", 58 | "port": "a" 59 | }, 60 | "from": { 61 | "id": "dev0", 62 | "port": "out" 63 | }, 64 | "name": "a" 65 | }, 66 | { 67 | "to": { 68 | "id": "dev6", 69 | "port": "b" 70 | }, 71 | "from": { 72 | "id": "dev1", 73 | "port": "out" 74 | }, 75 | "name": "b" 76 | }, 77 | { 78 | "to": { 79 | "id": "dev7", 80 | "port": "b" 81 | }, 82 | "from": { 83 | "id": "dev2", 84 | "port": "out" 85 | }, 86 | "name": "d" 87 | }, 88 | { 89 | "to": { 90 | "id": "dev3", 91 | "port": "in" 92 | }, 93 | "from": { 94 | "id": "dev7", 95 | "port": "o" 96 | }, 97 | "name": "o" 98 | }, 99 | { 100 | "to": { 101 | "id": "dev4", 102 | "port": "in" 103 | }, 104 | "from": { 105 | "id": "dev5", 106 | "port": "out" 107 | }, 108 | "name": "c" 109 | }, 110 | { 111 | "to": { 112 | "id": "dev5", 113 | "port": "in1" 114 | }, 115 | "from": { 116 | "id": "dev6", 117 | "port": "c" 118 | }, 119 | "name": "c1" 120 | }, 121 | { 122 | "to": { 123 | "id": "dev5", 124 | "port": "in2" 125 | }, 126 | "from": { 127 | "id": "dev7", 128 | "port": "c" 129 | }, 130 | "name": "c2" 131 | }, 132 | { 133 | "to": { 134 | "id": "dev7", 135 | "port": "a" 136 | }, 137 | "from": { 138 | "id": "dev6", 139 | "port": "o" 140 | }, 141 | "name": "t" 142 | } 143 | ], 144 | "subcircuits": { 145 | "halfadder": { 146 | "devices": { 147 | "dev0": { 148 | "type": "Input", 149 | "label": "a", 150 | "net": "a", 151 | "order": 0, 152 | "bits": 1 153 | }, 154 | "dev1": { 155 | "type": "Input", 156 | "label": "b", 157 | "net": "b", 158 | "order": 1, 159 | "bits": 1 160 | }, 161 | "dev2": { 162 | "type": "Output", 163 | "label": "o", 164 | "net": "o", 165 | "order": 2, 166 | "bits": 1 167 | }, 168 | "dev3": { 169 | "type": "Output", 170 | "label": "c", 171 | "net": "c", 172 | "order": 3, 173 | "bits": 1 174 | }, 175 | "dev4": { 176 | "label": "$and$tests/fulladder.sv:10$2", 177 | "type": "And", 178 | "bits": 1 179 | }, 180 | "dev5": { 181 | "label": "$xor$tests/fulladder.sv:9$1", 182 | "type": "Xor", 183 | "bits": 1 184 | } 185 | }, 186 | "connectors": [ 187 | { 188 | "to": { 189 | "id": "dev4", 190 | "port": "in1" 191 | }, 192 | "from": { 193 | "id": "dev0", 194 | "port": "out" 195 | }, 196 | "name": "a" 197 | }, 198 | { 199 | "to": { 200 | "id": "dev5", 201 | "port": "in1" 202 | }, 203 | "from": { 204 | "id": "dev0", 205 | "port": "out" 206 | }, 207 | "name": "a" 208 | }, 209 | { 210 | "to": { 211 | "id": "dev4", 212 | "port": "in2" 213 | }, 214 | "from": { 215 | "id": "dev1", 216 | "port": "out" 217 | }, 218 | "name": "b" 219 | }, 220 | { 221 | "to": { 222 | "id": "dev5", 223 | "port": "in2" 224 | }, 225 | "from": { 226 | "id": "dev1", 227 | "port": "out" 228 | }, 229 | "name": "b" 230 | }, 231 | { 232 | "to": { 233 | "id": "dev2", 234 | "port": "in" 235 | }, 236 | "from": { 237 | "id": "dev5", 238 | "port": "out" 239 | }, 240 | "name": "o" 241 | }, 242 | { 243 | "to": { 244 | "id": "dev3", 245 | "port": "in" 246 | }, 247 | "from": { 248 | "id": "dev4", 249 | "port": "out" 250 | }, 251 | "name": "c" 252 | } 253 | ] 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /examples/gates.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "54c9125d-c454-4f3f-9566-58687b2b23a9": { 4 | "label": "Not", 5 | "position": { 6 | "x": 0, 7 | "y": 0 8 | }, 9 | "type": "Not" 10 | }, 11 | "a0cbdc4d-ff94-45ba-ad3f-7e43f676be85": { 12 | "label": "And", 13 | "position": { 14 | "x": 0, 15 | "y": 100 16 | }, 17 | "type": "And" 18 | }, 19 | "f065d6ab-d1a0-48be-bb3f-7d6586f7371f": { 20 | "label": "Nand", 21 | "position": { 22 | "x": 0, 23 | "y": 200 24 | }, 25 | "type": "Nand" 26 | }, 27 | "61535731-f2a0-402f-8a69-ecad4cb64d72": { 28 | "label": "Or", 29 | "position": { 30 | "x": 0, 31 | "y": 300 32 | }, 33 | "type": "Or" 34 | }, 35 | "a51ea406-646a-44e1-ad5e-7089d96fcd3b": { 36 | "label": "Nor", 37 | "position": { 38 | "x": 0, 39 | "y": 400 40 | }, 41 | "type": "Nor" 42 | }, 43 | "4695ee1e-866a-45ef-b239-c75804b73340": { 44 | "label": "Xor", 45 | "position": { 46 | "x": 0, 47 | "y": 500 48 | }, 49 | "type": "Xor" 50 | }, 51 | "fccbd528-38e3-496c-b549-f954158ff5cf": { 52 | "label": "Xnor", 53 | "position": { 54 | "x": 0, 55 | "y": 600 56 | }, 57 | "type": "Xnor" 58 | }, 59 | "687b7df3-2309-4bcb-9c6a-7d1d5355ff83": { 60 | "label": "ShiftLeft", 61 | "position": { 62 | "x": 0, 63 | "y": 700 64 | }, 65 | "type": "ShiftLeft" 66 | }, 67 | "68fcf7ae-5e31-4971-a7e2-273bf7a7e9d5": { 68 | "label": "ShiftRight", 69 | "position": { 70 | "x": 0, 71 | "y": 800 72 | }, 73 | "type": "ShiftRight" 74 | }, 75 | "9d48f119-ef4d-4f9c-a21d-677fcb8f4789": { 76 | "label": "AndReduce", 77 | "position": { 78 | "x": 200, 79 | "y": 0 80 | }, 81 | "type": "AndReduce" 82 | }, 83 | "7e4da2be-dd9a-41ba-936a-313902652586": { 84 | "label": "NandReduce", 85 | "position": { 86 | "x": 200, 87 | "y": 100 88 | }, 89 | "type": "NandReduce" 90 | }, 91 | "4b442584-606c-477b-8801-10dd9fc7d7f6": { 92 | "label": "OrReduce", 93 | "position": { 94 | "x": 200, 95 | "y": 200 96 | }, 97 | "type": "OrReduce" 98 | }, 99 | "40ea5c1e-6e9d-41c7-89bf-98ece03152ca": { 100 | "label": "NorReduce", 101 | "position": { 102 | "x": 200, 103 | "y": 300 104 | }, 105 | "type": "NorReduce" 106 | }, 107 | "990efdb0-e01d-48d5-bb2e-5232f67a9e97": { 108 | "label": "XorReduce", 109 | "position": { 110 | "x": 200, 111 | "y": 400 112 | }, 113 | "type": "XorReduce" 114 | }, 115 | "9bdd77b2-6900-4d72-9c4d-ee5361826086": { 116 | "label": "Eq", 117 | "position": { 118 | "x": 400, 119 | "y": 0 120 | }, 121 | "type": "Eq" 122 | }, 123 | "50a71b3d-7710-4825-8e7b-4c65b9ee3b6a": { 124 | "label": "Ne", 125 | "position": { 126 | "x": 400, 127 | "y": 100 128 | }, 129 | "type": "Ne" 130 | }, 131 | "6d74067c-1145-4ef2-bfdb-e29212bf084c": { 132 | "label": "Lt", 133 | "position": { 134 | "x": 400, 135 | "y": 200 136 | }, 137 | "type": "Lt" 138 | }, 139 | "89d471b0-8642-4496-b5f0-392edc190d3d": { 140 | "label": "Le", 141 | "position": { 142 | "x": 400, 143 | "y": 300 144 | }, 145 | "type": "Le" 146 | }, 147 | "47490c62-6d66-48d8-ae22-a485de0569cc": { 148 | "label": "Gt", 149 | "position": { 150 | "x": 400, 151 | "y": 400 152 | }, 153 | "type": "Gt" 154 | }, 155 | "e5b28efa-1b17-4b63-821b-39eac780d72a": { 156 | "label": "Ge", 157 | "position": { 158 | "x": 400, 159 | "y": 500 160 | }, 161 | "type": "Ge" 162 | }, 163 | "36aba417-2dec-4264-b5db-5a1bade48911": { 164 | "label": "Negation", 165 | "position": { 166 | "x": 600, 167 | "y": 0 168 | }, 169 | "type": "Negation" 170 | }, 171 | "f8a99ab4-567c-474d-b9bf-efc084adf59c": { 172 | "label": "UnaryPlus", 173 | "position": { 174 | "x": 600, 175 | "y": 100 176 | }, 177 | "type": "UnaryPlus" 178 | }, 179 | "7b484788-c8b4-4f6a-9bd7-ffc52bf20410": { 180 | "label": "Addition", 181 | "position": { 182 | "x": 600, 183 | "y": 200 184 | }, 185 | "type": "Addition" 186 | }, 187 | "7c04f2e1-e71c-4b7e-bd8f-8ec4a92d7346": { 188 | "label": "Subtraction", 189 | "position": { 190 | "x": 600, 191 | "y": 300 192 | }, 193 | "type": "Subtraction" 194 | }, 195 | "a3b1073a-580e-41cf-aff3-91fc7583272b": { 196 | "label": "Multiplication", 197 | "position": { 198 | "x": 600, 199 | "y": 400 200 | }, 201 | "type": "Multiplication" 202 | }, 203 | "261d9bb1-3207-4135-96db-3d24487754fd": { 204 | "label": "Division", 205 | "position": { 206 | "x": 600, 207 | "y": 500 208 | }, 209 | "type": "Division" 210 | }, 211 | "ef4b605c-743f-4533-8d71-1236bdc3f1f3": { 212 | "label": "Modulo", 213 | "position": { 214 | "x": 600, 215 | "y": 600 216 | }, 217 | "type": "Modulo" 218 | }, 219 | "5d39da66-996c-4624-8c2b-88978e4e1395": { 220 | "label": "Power", 221 | "position": { 222 | "x": 600, 223 | "y": 700 224 | }, 225 | "type": "Power" 226 | }, 227 | "bcda2ceb-83c8-4ebc-aefe-57e2e98201aa": { 228 | "label": "Constant", 229 | "position": { 230 | "x": 800, 231 | "y": 0 232 | }, 233 | "type": "Constant" 234 | }, 235 | "4d02d820-9a6a-4ba0-9e76-212be041414c": { 236 | "label": "Clock", 237 | "position": { 238 | "x": 800, 239 | "y": 100 240 | }, 241 | "type": "Clock" 242 | }, 243 | "044f875f-1926-4911-8336-5d91ad3edf67": { 244 | "label": "1-bit input (Button)", 245 | "position": { 246 | "x": 800, 247 | "y": 200 248 | }, 249 | "type": "Input" 250 | }, 251 | "5cb2f869-4852-4447-b4cd-fdcd01442c93": { 252 | "label": "1-bit output (Lamp)", 253 | "position": { 254 | "x": 800, 255 | "y": 300 256 | }, 257 | "type": "Output" 258 | }, 259 | "d81f4f5f-16dd-4fae-954d-db6666e070ac": { 260 | "label": "4-bit input (NumEntry)", 261 | "bits": 4, 262 | "position": { 263 | "x": 800, 264 | "y": 400 265 | }, 266 | "type": "Input" 267 | }, 268 | "383468e5-fb2f-47a1-8beb-87df81cf9916": { 269 | "label": "4-bit output (NumDisplay)", 270 | "bits": 4, 271 | "position": { 272 | "x": 800, 273 | "y": 500 274 | }, 275 | "type": "Output" 276 | }, 277 | "635e6db5-8e90-4f37-864a-23ebbd21e398": { 278 | "label": "Repeater", 279 | "position": { 280 | "x": 1000, 281 | "y": 0 282 | }, 283 | "type": "Repeater" 284 | }, 285 | "434adf98-f7ea-4355-82da-60dd1d1555ec": { 286 | "label": "BusGroup", 287 | "position": { 288 | "x": 1000, 289 | "y": 100 290 | }, 291 | "type": "BusGroup" 292 | }, 293 | "10ddb9f6-850b-48e8-84f4-48c941cf1bb3": { 294 | "label": "BusUngroup", 295 | "position": { 296 | "x": 1000, 297 | "y": 200 298 | }, 299 | "type": "BusUngroup" 300 | }, 301 | "161945c4-37aa-4f94-872a-97508a6771f9": { 302 | "label": "BusSlice", 303 | "position": { 304 | "x": 1000, 305 | "y": 300 306 | }, 307 | "type": "BusSlice" 308 | }, 309 | "6228c4f1-3503-42cc-a015-4ca1257b7e20": { 310 | "label": "ZeroExtend", 311 | "position": { 312 | "x": 1000, 313 | "y": 400 314 | }, 315 | "type": "ZeroExtend" 316 | }, 317 | "5742dc12-84d9-4f0f-b0df-1491cab43ae0": { 318 | "label": "SignExtend", 319 | "position": { 320 | "x": 1000, 321 | "y": 500 322 | }, 323 | "type": "SignExtend" 324 | }, 325 | "63b6c98a-49c8-4f80-ab0b-008aa313f5e4": { 326 | "label": "Mux", 327 | "position": { 328 | "x": 1200, 329 | "y": 0 330 | }, 331 | "type": "Mux" 332 | }, 333 | "34a53445-af90-411c-9179-01845e3b1d95": { 334 | "label": "Mux1Hot", 335 | "position": { 336 | "x": 1200, 337 | "y": 100 338 | }, 339 | "type": "Mux1Hot" 340 | }, 341 | "f6f4a995-d6bb-4ef8-bc34-a568e193a8dc": { 342 | "label": "Dff", 343 | "position": { 344 | "x": 1200, 345 | "y": 200 346 | }, 347 | "type": "Dff" 348 | }, 349 | "ba9dbbd7-8dd1-43ad-bd60-9f1a63656d84": { 350 | "label": "Subcircuit", 351 | "position": { 352 | "x": 1200, 353 | "y": 300 354 | }, 355 | "type": "Subcircuit", 356 | "celltype": "pipe" 357 | }, 358 | "0662e43f-477e-4140-a79b-6c3e38f173c9": { 359 | "label": "FSM", 360 | "position": { 361 | "x": 1200, 362 | "y": 400 363 | }, 364 | "type": "FSM" 365 | }, 366 | "ecac6e41-deb2-4e34-94af-7e74f8b5047c": { 367 | "label": "Memory", 368 | "position": { 369 | "x": 1200, 370 | "y": 500 371 | }, 372 | "type": "Memory" 373 | } 374 | }, 375 | "connectors": [], 376 | "subcircuits": { 377 | "pipe": { 378 | "devices": { 379 | "in": { 380 | "type": "Input", 381 | "propagation": 0, 382 | "bits": 1, 383 | "net": "i" 384 | }, 385 | "out": { 386 | "type": "Output", 387 | "propagation": 0, 388 | "bits": 1, 389 | "net": "o" 390 | } 391 | }, 392 | "connectors": [ 393 | { 394 | "from": { 395 | "id": "in", 396 | "port": "out" 397 | }, 398 | "to": { 399 | "id": "out", 400 | "port": "in" 401 | } 402 | } 403 | ] 404 | } 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /examples/io.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "inSingle": { 4 | "label": "1-bit input", 5 | "bits": 1, 6 | "type": "Input" 7 | }, 8 | "inBus": { 9 | "label": "4-bit input", 10 | "bits": 4, 11 | "type": "Input" 12 | }, 13 | "outSingle": { 14 | "label": "1-bit output", 15 | "bits": 1, 16 | "type": "Output" 17 | }, 18 | "outBus": { 19 | "label": "4-bit output", 20 | "bits": 4, 21 | "type": "Output" 22 | }, 23 | "subcir": { 24 | "label": "Subcircuit with I/O", 25 | "type": "Subcircuit", 26 | "celltype": "pipes" 27 | } 28 | }, 29 | "connectors": [ 30 | { 31 | "from": { 32 | "id": "inSingle", 33 | "port": "out" 34 | }, 35 | "to": { 36 | "id": "subcir", 37 | "port": "i1" 38 | } 39 | }, 40 | { 41 | "from": { 42 | "id": "inBus", 43 | "port": "out" 44 | }, 45 | "to": { 46 | "id": "subcir", 47 | "port": "i4" 48 | } 49 | }, 50 | { 51 | "from": { 52 | "id": "subcir", 53 | "port": "o1" 54 | }, 55 | "to": { 56 | "id": "outSingle", 57 | "port": "in" 58 | } 59 | }, 60 | { 61 | "from": { 62 | "id": "subcir", 63 | "port": "o4" 64 | }, 65 | "to": { 66 | "id": "outBus", 67 | "port": "in" 68 | } 69 | } 70 | ], 71 | "subcircuits": { 72 | "pipes": { 73 | "devices": { 74 | "inSingle": { 75 | "type": "Input", 76 | "bits": 1, 77 | "net": "i1" 78 | }, 79 | "inBus": { 80 | "type": "Input", 81 | "bits": 4, 82 | "net": "i4" 83 | }, 84 | "outSingle": { 85 | "type": "Output", 86 | "bits": 1, 87 | "net": "o1" 88 | }, 89 | "outBus": { 90 | "type": "Output", 91 | "bits": 4, 92 | "net": "o4" 93 | } 94 | }, 95 | "connectors": [ 96 | { 97 | "from": { 98 | "id": "inSingle", 99 | "port": "out" 100 | }, 101 | "to": { 102 | "id": "outSingle", 103 | "port": "in" 104 | } 105 | }, 106 | { 107 | "from": { 108 | "id": "inBus", 109 | "port": "out" 110 | }, 111 | "to": { 112 | "id": "outBus", 113 | "port": "in" 114 | } 115 | } 116 | ] 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /examples/latch.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "dev0": { 4 | "type": "Button", 5 | "net": "d", 6 | "order": 0, 7 | "bits": 1, 8 | "label": "d" 9 | }, 10 | "dev1": { 11 | "type": "Button", 12 | "net": "c", 13 | "order": 1, 14 | "bits": 1, 15 | "label": "c" 16 | }, 17 | "dev2": { 18 | "type": "Lamp", 19 | "net": "o", 20 | "order": 2, 21 | "bits": 1, 22 | "label": "o" 23 | }, 24 | "dev3": { 25 | "label":"$auto$proc_dlatch.cc:409:proc_dlatch$15", 26 | "type":"Dff", 27 | "bits":1, 28 | "polarity": {"enable":true} 29 | } 30 | }, 31 | "connectors": [ 32 | {"to":{"id":"dev3","port":"in"},"from":{"id":"dev0","port":"out"},"name":"d"}, 33 | {"to":{"id":"dev3","port":"en"},"from":{"id":"dev1","port":"out"},"name":"c"}, 34 | {"to":{"id":"dev2","port":"in"},"from":{"id":"dev3","port":"out"},"name":"o"} 35 | ], 36 | "subcircuits": {} 37 | } 38 | -------------------------------------------------------------------------------- /examples/lfsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "dev0": { 4 | "type": "NumDisplay", 5 | "label": "out", 6 | "net": "out", 7 | "order": 0, 8 | "bits": 8 9 | }, 10 | "dev1": { 11 | "type": "Clock", 12 | "label": "clk", 13 | "net": "clk", 14 | "order": 1, 15 | "bits": 1, 16 | "propagation": 100 17 | }, 18 | "dev2": { 19 | "type": "Button", 20 | "label": "reset", 21 | "net": "reset", 22 | "order": 2, 23 | "bits": 1 24 | }, 25 | "dev4": { 26 | "label": "$procdff$4", 27 | "type": "Dff", 28 | "bits": 8, 29 | "polarity": { 30 | "clock": true, 31 | "arst": true 32 | }, 33 | "arst_value": "00000000" 34 | }, 35 | "dev5": { 36 | "label": "$xor$tests/lfsr.sv:10$1", 37 | "type": "Xnor", 38 | "bits": 1 39 | }, 40 | "dev6": { 41 | "type": "BusGroup", 42 | "groups": [ 43 | 1, 44 | 7 45 | ] 46 | }, 47 | "dev7": { 48 | "type": "BusSlice", 49 | "slice": { 50 | "first": 7, 51 | "count": 1, 52 | "total": 8 53 | } 54 | }, 55 | "dev8": { 56 | "type": "BusSlice", 57 | "slice": { 58 | "first": 3, 59 | "count": 1, 60 | "total": 8 61 | } 62 | }, 63 | "dev9": { 64 | "type": "BusSlice", 65 | "slice": { 66 | "first": 0, 67 | "count": 7, 68 | "total": 8 69 | } 70 | } 71 | }, 72 | "connectors": [ 73 | { 74 | "to": { 75 | "id": "dev0", 76 | "port": "in" 77 | }, 78 | "from": { 79 | "id": "dev4", 80 | "port": "out" 81 | }, 82 | "name": "out" 83 | }, 84 | { 85 | "to": { 86 | "id": "dev7", 87 | "port": "in" 88 | }, 89 | "from": { 90 | "id": "dev4", 91 | "port": "out" 92 | }, 93 | "name": "out" 94 | }, 95 | { 96 | "to": { 97 | "id": "dev8", 98 | "port": "in" 99 | }, 100 | "from": { 101 | "id": "dev4", 102 | "port": "out" 103 | }, 104 | "name": "out" 105 | }, 106 | { 107 | "to": { 108 | "id": "dev9", 109 | "port": "in" 110 | }, 111 | "from": { 112 | "id": "dev4", 113 | "port": "out" 114 | }, 115 | "name": "out" 116 | }, 117 | { 118 | "to": { 119 | "id": "dev4", 120 | "port": "clk" 121 | }, 122 | "from": { 123 | "id": "dev1", 124 | "port": "out" 125 | }, 126 | "name": "clk" 127 | }, 128 | { 129 | "to": { 130 | "id": "dev4", 131 | "port": "arst" 132 | }, 133 | "from": { 134 | "id": "dev2", 135 | "port": "out" 136 | }, 137 | "name": "reset" 138 | }, 139 | { 140 | "to": { 141 | "id": "dev6", 142 | "port": "in0" 143 | }, 144 | "from": { 145 | "id": "dev5", 146 | "port": "out" 147 | }, 148 | "name": "linear_feedback" 149 | }, 150 | { 151 | "to": { 152 | "id": "dev4", 153 | "port": "in" 154 | }, 155 | "from": { 156 | "id": "dev6", 157 | "port": "out" 158 | } 159 | }, 160 | { 161 | "to": { 162 | "id": "dev5", 163 | "port": "in1" 164 | }, 165 | "from": { 166 | "id": "dev7", 167 | "port": "out" 168 | } 169 | }, 170 | { 171 | "to": { 172 | "id": "dev5", 173 | "port": "in2" 174 | }, 175 | "from": { 176 | "id": "dev8", 177 | "port": "out" 178 | } 179 | }, 180 | { 181 | "to": { 182 | "id": "dev6", 183 | "port": "in1" 184 | }, 185 | "from": { 186 | "id": "dev9", 187 | "port": "out" 188 | } 189 | } 190 | ], 191 | "subcircuits": {} 192 | } 193 | -------------------------------------------------------------------------------- /examples/muxsparse.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "sel": { 4 | "type": "NumEntry", 5 | "bits": 2, 6 | "order": 2 7 | }, 8 | "btna": { 9 | "type": "Button", 10 | "label": "a", 11 | "net": "a", 12 | "order": 0, 13 | "bits": 1 14 | }, 15 | "btnb": { 16 | "type": "Button", 17 | "label": "b", 18 | "net": "b", 19 | "order": 1, 20 | "bits": 1 21 | }, 22 | "out": { 23 | "type": "Lamp", 24 | "label": "o", 25 | "net": "o", 26 | "order": 0, 27 | "bits": 1 28 | }, 29 | "mux": { 30 | "type": "MuxSparse", 31 | "inputs": [1, 3], 32 | "bits": { 33 | "sel": 2, 34 | "in": 1 35 | }, 36 | "label": "mux" 37 | } 38 | }, 39 | "connectors": [ 40 | { 41 | "from": { 42 | "id": "btna", 43 | "port": "out" 44 | }, 45 | "to": { 46 | "id": "mux", 47 | "port": "in0" 48 | } 49 | }, 50 | { 51 | "from": { 52 | "id": "btnb", 53 | "port": "out" 54 | }, 55 | "to": { 56 | "id": "mux", 57 | "port": "in1" 58 | } 59 | }, 60 | { 61 | "from": { 62 | "id": "mux", 63 | "port": "out" 64 | }, 65 | "to": { 66 | "id": "out", 67 | "port": "in" 68 | } 69 | }, 70 | { 71 | "from": { 72 | "id": "sel", 73 | "port": "out" 74 | }, 75 | "to": { 76 | "id": "mux", 77 | "port": "sel" 78 | } 79 | } 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /examples/ram.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "dev0": { 4 | "type": "Clock", 5 | "label": "clk", 6 | "net": "clk", 7 | "order": 0, 8 | "bits": 1, 9 | "propagation": 100 10 | }, 11 | "dev1": { 12 | "type": "NumEntry", 13 | "label": "addr", 14 | "net": "addr", 15 | "order": 1, 16 | "bits": 5 17 | }, 18 | "dev2": { 19 | "type": "NumDisplay", 20 | "label": "data", 21 | "net": "data", 22 | "order": 2, 23 | "bits": 4 24 | }, 25 | "dev3": { 26 | "type": "NumEntry", 27 | "label": "wraddr", 28 | "net": "wraddr", 29 | "order": 3, 30 | "bits": 5 31 | }, 32 | "dev4": { 33 | "type": "NumEntry", 34 | "label": "wrdata", 35 | "net": "wrdata", 36 | "order": 4, 37 | "bits": 4 38 | }, 39 | "dev5": { 40 | "label": "mem", 41 | "type": "Memory", 42 | "bits": 4, 43 | "abits": 5, 44 | "words": 16, 45 | "offset": 0, 46 | "rdports": [ 47 | {} 48 | ], 49 | "wrports": [ 50 | { 51 | "clock_polarity": true 52 | } 53 | ], 54 | "memdata": [ 55 | "0000", 56 | "0001", 57 | "0010", 58 | "0011", 59 | "0100", 60 | "0101", 61 | "0110", 62 | "0111", 63 | "1000", 64 | "1001", 65 | "1010", 66 | "1011", 67 | "1100", 68 | "1101", 69 | "1110", 70 | "1111" 71 | ] 72 | } 73 | }, 74 | "connectors": [ 75 | { 76 | "to": { 77 | "id": "dev5", 78 | "port": "wr0clk" 79 | }, 80 | "from": { 81 | "id": "dev0", 82 | "port": "out" 83 | }, 84 | "name": "clk" 85 | }, 86 | { 87 | "to": { 88 | "id": "dev5", 89 | "port": "rd0addr" 90 | }, 91 | "from": { 92 | "id": "dev1", 93 | "port": "out" 94 | }, 95 | "name": "addr" 96 | }, 97 | { 98 | "to": { 99 | "id": "dev2", 100 | "port": "in" 101 | }, 102 | "from": { 103 | "id": "dev5", 104 | "port": "rd0data" 105 | }, 106 | "name": "data" 107 | }, 108 | { 109 | "to": { 110 | "id": "dev5", 111 | "port": "wr0addr" 112 | }, 113 | "from": { 114 | "id": "dev3", 115 | "port": "out" 116 | }, 117 | "name": "wraddr" 118 | }, 119 | { 120 | "to": { 121 | "id": "dev5", 122 | "port": "wr0data" 123 | }, 124 | "from": { 125 | "id": "dev4", 126 | "port": "out" 127 | }, 128 | "name": "wrdata" 129 | } 130 | ], 131 | "subcircuits": {} 132 | } 133 | -------------------------------------------------------------------------------- /examples/rom.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "dev0": { 4 | "type": "NumEntry", 5 | "label": "addr", 6 | "net": "addr", 7 | "order": 0, 8 | "bits": 5 9 | }, 10 | "dev1": { 11 | "type": "NumDisplay", 12 | "label": "data", 13 | "net": "data", 14 | "order": 1, 15 | "bits": 4 16 | }, 17 | "dev2": { 18 | "label": "mem", 19 | "type": "Memory", 20 | "bits": 4, 21 | "abits": 5, 22 | "words": 16, 23 | "offset": 0, 24 | "rdports": [ 25 | {} 26 | ], 27 | "wrports": [], 28 | "memdata": [ 29 | "0000", 30 | "0001", 31 | "0010", 32 | "0011", 33 | "0100", 34 | "0101", 35 | "0110", 36 | "0111", 37 | "1000", 38 | "1001", 39 | "1010", 40 | "1011", 41 | "1100", 42 | "1101", 43 | "1110", 44 | "1111" 45 | ] 46 | } 47 | }, 48 | "connectors": [ 49 | { 50 | "to": { 51 | "id": "dev2", 52 | "port": "rd0addr" 53 | }, 54 | "from": { 55 | "id": "dev0", 56 | "port": "out" 57 | }, 58 | "name": "addr" 59 | }, 60 | { 61 | "to": { 62 | "id": "dev1", 63 | "port": "in" 64 | }, 65 | "from": { 66 | "id": "dev2", 67 | "port": "rd0data" 68 | }, 69 | "name": "data" 70 | } 71 | ], 72 | "subcircuits": {} 73 | } 74 | -------------------------------------------------------------------------------- /examples/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= htmlWebpackPlugin.options.title %> 6 | 7 | 8 |
9 | 10 | 11 |
12 |
13 |
14 |
15 | Fixed Mode 16 | 17 | Include layout information 18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 |
26 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /examples/warnings.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "const1": { 4 | "type": "Constant", 5 | "constant": "00" 6 | }, 7 | "not": { 8 | "type": "Not", 9 | "bits": 2 10 | }, 11 | "lamp": { 12 | "type": "Lamp" 13 | }, 14 | "const2": { 15 | "type": "Constant" 16 | }, 17 | "subcir": { 18 | "type": "Subcircuit", 19 | "celltype": "subcir2" 20 | }, 21 | "display": { 22 | "type": "NumDisplay", 23 | "bits": 2 24 | } 25 | }, 26 | "connectors": [ 27 | { 28 | "to": { 29 | "id": "not", 30 | "port": "in" 31 | }, 32 | "from": { 33 | "id": "const1", 34 | "port": "out" 35 | } 36 | }, 37 | { 38 | "to": { 39 | "id": "lamp", 40 | "port": "in" 41 | }, 42 | "from": { 43 | "id": "not", 44 | "port": "out" 45 | } 46 | }, 47 | { 48 | "to": { 49 | "id": "subcir", 50 | "port": "in" 51 | }, 52 | "from": { 53 | "id": "const2", 54 | "port": "out" 55 | } 56 | }, 57 | { 58 | "to": { 59 | "id": "display", 60 | "port": "in" 61 | }, 62 | "from": { 63 | "id": "subcir", 64 | "port": "out" 65 | } 66 | } 67 | ], 68 | "subcircuits": { 69 | "subcir2": { 70 | "devices": { 71 | "in": { 72 | "type": "Input", 73 | "net": "in" 74 | }, 75 | "out": { 76 | "type": "Output", 77 | "net": "out", 78 | "bits": 2 79 | }, 80 | "subcir": { 81 | "type": "Subcircuit", 82 | "celltype": "subcir" 83 | } 84 | }, 85 | "connectors": [ 86 | { 87 | "to": { 88 | "id": "out", 89 | "port": "in" 90 | }, 91 | "from": { 92 | "id": "subcir", 93 | "port": "out" 94 | } 95 | }, 96 | { 97 | "to": { 98 | "id": "subcir", 99 | "port": "in" 100 | }, 101 | "from": { 102 | "id": "in", 103 | "port": "out" 104 | } 105 | } 106 | ] 107 | }, 108 | "subcir": { 109 | "devices": { 110 | "in": { 111 | "type": "Input", 112 | "net": "in" 113 | }, 114 | "out": { 115 | "type": "Output", 116 | "net": "out", 117 | "bits": 2 118 | } 119 | }, 120 | "connectors": [ 121 | { 122 | "to": { 123 | "id": "out", 124 | "port": "in" 125 | }, 126 | "from": { 127 | "id": "in", 128 | "port": "out" 129 | } 130 | } 131 | ] 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "digitaljs", 3 | "version": "0.13.1", 4 | "description": "Digital logic simulator", 5 | "main": "./lib/circuit.js", 6 | "files": [ 7 | "lib", 8 | "dist", 9 | "src" 10 | ], 11 | "browser": "./src/index.mjs", 12 | "exports": { 13 | "node": { 14 | "import": "./src/circuit.mjs", 15 | "require": "./lib/circuit.js" 16 | }, 17 | "browser": "./src/index.mjs", 18 | "default": "./lib/circuit.js" 19 | }, 20 | "scripts": { 21 | "prepare": "npm run prod && npm run build-lib", 22 | "dev": "webpack --mode development", 23 | "prod": "webpack --mode production", 24 | "watch": "webpack --mode development --watch", 25 | "test": "jest", 26 | "build-lib": "mkdir -p lib && babel src -d lib", 27 | "gh-pages": "webpack --mode production && gh-pages -a -d dist" 28 | }, 29 | "author": "Marek Materzok", 30 | "license": "BSD-2-Clause", 31 | "devDependencies": { 32 | "@babel/cli": "^7.15.7", 33 | "@babel/core": "^7.15.5", 34 | "@babel/preset-env": "^7.15.6", 35 | "babel-jest": "^26.6.3", 36 | "babel-loader": "^8.2.2", 37 | "babel-plugin-add-import-extension": "^1.6.0", 38 | "babel-plugin-transform-import-meta": "^2.1.0", 39 | "clean-webpack-plugin": "^2.0.2", 40 | "css-loader": "^6.3.0", 41 | "expose-loader": "^3.0.0", 42 | "gh-pages": "^2.2.0", 43 | "html-webpack-plugin": "^5.3.2", 44 | "jest": "^29.5.0", 45 | "style-loader": "^3.3.0", 46 | "webpack": "^5.57.1", 47 | "webpack-cli": "^4.8.0" 48 | }, 49 | "dependencies": { 50 | "3vl": "^1.0.1", 51 | "babel-polyfill": "^6.26.0", 52 | "dagre": "^0.8.5", 53 | "elkjs": "^0.7.1", 54 | "fastpriorityqueue": "^0.6.4", 55 | "graphlib": "^2.1.8", 56 | "jointjs": "^3.4.4", 57 | "jquery": "^3.6.0", 58 | "jquery-ui": "^1.13.1", 59 | "regenerator-runtime": "^0.13.9", 60 | "resize-observer-polyfill": "^1.5.1", 61 | "wavecanvas": "^1.1.1", 62 | "web-worker": "^1.1.0" 63 | }, 64 | "homepage": "https://github.com/tilk/digitaljs", 65 | "repository": { 66 | "type": "git", 67 | "url": "https://github.com/tilk/digitaljs.git" 68 | }, 69 | "jest": { 70 | "testEnvironment": "jest-environment-node", 71 | "verbose": true, 72 | "transform": { 73 | "^.+\\.m?jsx?$": "babel-jest" 74 | }, 75 | "moduleDirectories": [ 76 | "node_modules", 77 | "src" 78 | ], 79 | "moduleFileExtensions": [ 80 | "js", 81 | "mjs" 82 | ], 83 | "testMatch": [ 84 | "**/*.test.mjs" 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/cells.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | export * from "./cells/base.mjs"; 4 | export * from "./cells/io.mjs"; 5 | export * from "./cells/gates.mjs"; 6 | export * from "./cells/arith.mjs"; 7 | export * from "./cells/bus.mjs"; 8 | export * from "./cells/subcircuit.mjs"; 9 | export * from "./cells/mux.mjs"; 10 | export * from "./cells/dff.mjs"; 11 | export * from "./cells/memory.mjs"; 12 | export * from "./cells/fsm.mjs"; 13 | export * from "./cells/display7.mjs"; 14 | -------------------------------------------------------------------------------- /src/cells/bus.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as joint from 'jointjs'; 4 | import { Box, BoxView } from './base.mjs'; 5 | import * as help from '../help.mjs'; 6 | import { Vector3vl } from '3vl'; 7 | 8 | // Bit extending 9 | export const BitExtend = Box.define('BitExtend', { 10 | /* default properties */ 11 | extend: { input: 1, output: 1 }, 12 | propagation: 0, 13 | 14 | attrs: { 15 | value: { 16 | refX: .5, refY: .5, 17 | textAnchor: 'middle', textVerticalAnchor: 'middle' 18 | } 19 | } 20 | }, { 21 | initialize() { 22 | const extend = this.get('extend'); 23 | console.assert(extend.input <= extend.output); 24 | this.get('ports').items = [ 25 | { id: 'in', group: 'in', dir: 'in', bits: extend.input }, 26 | { id: 'out', group: 'out', dir: 'out', bits: extend.output } 27 | ]; 28 | 29 | Box.prototype.initialize.apply(this, arguments); 30 | 31 | this.on('change:extend', (_, extend) => { 32 | this._setPortsBits({ in: extend.input, out: extend.output }); 33 | }); 34 | }, 35 | operation(data) { 36 | const ex = this.get('extend'); 37 | return { out: data.in.concat(Vector3vl.make(ex.output - ex.input, this._extBit(data.in))) }; 38 | }, 39 | markup: Box.prototype.markup.concat([{ 40 | tagName: 'text', 41 | className: 'value', 42 | selector: 'value' 43 | } 44 | ]), 45 | _gateParams: Box.prototype._gateParams.concat(['extend']), 46 | _operationHelpers: Box.prototype._operationHelpers.concat(['_extBit']) 47 | }); 48 | export const BitExtendView = BoxView.extend({ 49 | _autoResizeBox: true, 50 | _calculateBoxWidth() { 51 | const text = this.el.querySelector('text.value'); 52 | return text.getBBox().width + 10; 53 | } 54 | }); 55 | 56 | export const ZeroExtend = BitExtend.define('ZeroExtend', { 57 | attrs: { 58 | value: { text: 'zero-extend' } 59 | } 60 | }, { 61 | _extBit(i) { 62 | return -1; 63 | } 64 | }); 65 | export const ZeroExtendView = BitExtendView; 66 | 67 | export const SignExtend = BitExtend.define('SignExtend', { 68 | attrs: { 69 | value: { text: 'sign-extend' } 70 | } 71 | }, { 72 | _extBit(i) { 73 | return i.get(i.bits - 1); 74 | } 75 | }); 76 | export const SignExtendView = BitExtendView; 77 | 78 | // Bus slicing 79 | export const BusSlice = Box.define('BusSlice', { 80 | /* default properties */ 81 | slice: { first: 0, count: 1, total: 2 }, 82 | propagation: 0, 83 | 84 | size: { width: 40, height: 24 } 85 | }, { 86 | initialize() { 87 | const slice = this.get('slice'); 88 | 89 | const val = slice.count == 1 ? slice.first : 90 | slice.first + "-" + (slice.first + slice.count - 1); 91 | 92 | this.get('ports').items = [ 93 | { id: 'in', group: 'in', dir: 'in', bits: slice.total }, 94 | { id: 'out', group: 'out', dir: 'out', bits: slice.count, portlabel: val, labelled: true } 95 | ]; 96 | 97 | Box.prototype.initialize.apply(this, arguments); 98 | 99 | this.on('change:slice', (_, slice) => { 100 | this._setPortsBits({ in: slice.total, out: slice.count }); 101 | }); 102 | }, 103 | operation(data) { 104 | const s = this.get('slice'); 105 | return { out: data.in.slice(s.first, s.first + s.count) }; 106 | }, 107 | _gateParams: Box.prototype._gateParams.concat(['slice']) 108 | }); 109 | export const BusSliceView = BoxView.extend({ 110 | _autoResizeBox: true 111 | }); 112 | 113 | // Bus grouping 114 | export const BusRegroup = Box.define('BusRegroup', { 115 | /* default properties */ 116 | groups: [1], 117 | propagation: 0, 118 | 119 | size: { width: 40, height: undefined } 120 | }, { 121 | initialize() { 122 | var bits = 0; 123 | const ports = []; 124 | const groups = this.get('groups'); 125 | 126 | this.get('size').height = groups.length*16+8; 127 | 128 | for (const [num, gbits] of groups.entries()) { 129 | const lbl = bits + (gbits > 1 ? '-' + (bits + gbits - 1) : ''); 130 | bits += gbits; 131 | ports.push({ id: this.group_dir + num, group: this.group_dir, dir: this.group_dir, bits: gbits, portlabel: lbl, labelled: true }); 132 | } 133 | this.set('bits', bits); 134 | 135 | const contra = this.group_dir == 'out' ? 'in' : 'out'; 136 | ports.push({ id: contra, group: contra, dir: contra, bits: bits }); 137 | this.get('ports').items = ports; 138 | 139 | Box.prototype.initialize.apply(this, arguments); 140 | }, 141 | _gateParams: Box.prototype._gateParams.concat(['groups']), 142 | _unsupportedPropChanges: Box.prototype._unsupportedPropChanges.concat(['groups']) 143 | }); 144 | export const BusRegroupView = BoxView.extend({ 145 | _autoResizeBox: true 146 | }); 147 | 148 | export const BusGroup = BusRegroup.define('BusGroup', { 149 | }, { 150 | group_dir : 'in', 151 | operation(data) { 152 | const outdata = []; 153 | for (const num of this.get('groups').keys()) { 154 | outdata.push(data['in' + num]); 155 | } 156 | return { out : Vector3vl.concat(...outdata) }; 157 | } 158 | }); 159 | export const BusGroupView = BusRegroupView; 160 | 161 | export const BusUngroup = BusRegroup.define('BusUngroup', { 162 | }, { 163 | group_dir : 'out', 164 | operation(data) { 165 | const outdata = {}; 166 | let pos = 0; 167 | for (const [num, gbits] of this.get('groups').entries()) { 168 | outdata['out' + num] = data.in.slice(pos, pos + gbits); 169 | pos += gbits; 170 | } 171 | return outdata; 172 | } 173 | }); 174 | export const BusUngroupView = BusRegroupView; 175 | 176 | -------------------------------------------------------------------------------- /src/cells/dff.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as joint from 'jointjs'; 4 | import { Box, BoxView } from './base.mjs'; 5 | import * as help from '../help.mjs'; 6 | import { Vector3vl } from '3vl'; 7 | 8 | // D flip-flops 9 | export const Dff = Box.define('Dff', { 10 | /* default properties */ 11 | bits: 1, 12 | initial: 'x', 13 | 14 | size: { width: 80, height: undefined }, 15 | ports: { 16 | groups: { 17 | 'in': { 18 | position: Box.prototype._getStackedPosition({ side: 'left' }) 19 | }, 20 | 'out': { 21 | position: Box.prototype._getStackedPosition({ side: 'right' }) 22 | } 23 | } 24 | } 25 | }, { 26 | initialize() { 27 | const bits = this.get('bits'); 28 | const initial = this.get('initial'); 29 | const polarity = this.get('polarity') || {}; 30 | 31 | const ports = []; 32 | 33 | if (!this.get('no_data')) 34 | ports.push({ id: 'in', group: 'in', dir: 'in', bits: bits, portlabel: 'D', labelled: true }); 35 | ports.push({ id: 'out', group: 'out', dir: 'out', bits: bits, portlabel: 'Q', labelled: true }); 36 | 37 | if ('arst' in polarity && !this.get('arst_value')) 38 | this.set('arst_value', Array(bits).fill('0').join('')); 39 | 40 | if ('srst' in polarity && !this.get('srst_value')) 41 | this.set('srst_value', Array(bits).fill('0').join('')); 42 | 43 | let num = 1; 44 | if ('aload' in polarity) { 45 | num += 2; 46 | ports.push({ id: 'ain', group: 'in', dir: 'in', bits: bits, portlabel: 'AD', labelled: true }); 47 | ports.push({ id: 'aload', group: 'in', dir: 'in', bits: 1, polarity: polarity.aload, labelled: true }); 48 | } 49 | if ('clock' in polarity) { 50 | num++; 51 | ports.push({ id: 'clk', group: 'in', dir: 'in', bits: 1, polarity: polarity.clock, decor: Box.prototype.decorClock, labelled: true }); 52 | } 53 | if ('set' in polarity) { 54 | num++; 55 | ports.push({ id: 'set', group: 'in', dir: 'in', bits: bits, polarity: polarity.set, portlabel: 'S', labelled: true }); 56 | } 57 | if ('clr' in polarity) { 58 | num++; 59 | ports.push({ id: 'clr', group: 'in', dir: 'in', bits: bits, polarity: polarity.clr, portlabel: 'R', labelled: true }); 60 | } 61 | if ('srst' in polarity) { 62 | num++; 63 | ports.push({ id: 'srst', group: 'in', dir: 'in', bits: 1, polarity: polarity.srst, labelled: true }); 64 | } 65 | if ('arst' in polarity) { 66 | num++; 67 | ports.push({ id: 'arst', group: 'in', dir: 'in', bits: 1, polarity: polarity.arst, labelled: true }); 68 | } 69 | if ('enable' in polarity) { 70 | num++; 71 | ports.push({ id: 'en', group: 'in', dir: 'in', bits: 1, polarity: polarity.enable, labelled: true }); 72 | } 73 | 74 | this.get('size').height = num*16+8; 75 | this.get('ports').items = ports; 76 | this.last_clk = 0; 77 | 78 | Box.prototype.initialize.apply(this, arguments); 79 | }, 80 | _resetPortValue(port) { 81 | if (port.id == "out" && port.dir == "out") 82 | return Vector3vl.fromBin(this.get('initial'), port.bits); 83 | else return Box.prototype._resetPortValue.call(this, port); 84 | }, 85 | operation(data) { 86 | const polarity = this.get('polarity'); 87 | const pol = what => polarity[what] ? 1 : -1 88 | let last_clk, srbits, srbitmask; 89 | const apply_sr = (v) => ({out: srbits ? v.and(srbitmask).or(srbits) : v}); 90 | if ('clock' in polarity) { 91 | last_clk = this.last_clk; 92 | this.last_clk = data.clk.get(0); 93 | } 94 | if ('arst' in polarity && data.arst.get(0) == pol('arst')) 95 | return { out: Vector3vl.fromBin(this.get('arst_value'), this.get('bits')) }; 96 | if ('aload' in polarity && data.aload.get(0) == pol('aload')) 97 | return { out: data.ain }; 98 | if ('set' in polarity) { 99 | srbits = polarity.set ? data.set : data.set.not(); 100 | srbitmask = polarity.set ? data.set.not() : data.set; 101 | } 102 | if ('clr' in polarity) { 103 | srbits = srbits ? srbits : Vector3vl.zeros(this.get('bits')); 104 | const clrbitmask = polarity.clr ? data.clr.not() : data.clr; 105 | srbitmask = srbitmask ? clrbitmask.and(srbitmask) : clrbitmask; 106 | } 107 | if ('enable' in polarity && data.en.get(0) != pol('enable') && this.get('enable_srst')) 108 | return apply_sr(this.get('outputSignals').out); 109 | if (!('clock' in polarity) || data.clk.get(0) == pol('clock') && last_clk == -pol('clock')) { 110 | if ('srst' in polarity && data.srst.get(0) == pol('srst')) 111 | return apply_sr(Vector3vl.fromBin(this.get('srst_value'), this.get('bits'))); 112 | if ('enable' in polarity && data.en.get(0) != pol('enable') && !this.get('enable_srst')) 113 | return apply_sr(this.get('outputSignals').out); 114 | if (this.get('no_data')) 115 | return apply_sr(this.get('outputSignals').out); 116 | return apply_sr(data.in); 117 | } else return apply_sr(this.get('outputSignals').out); 118 | }, 119 | _gateParams: Box.prototype._gateParams.concat(['polarity', 'bits', 'initial', 'arst_value', 'srst_value', 'enable_srst', 'no_data']), 120 | _unsupportedPropChanges: Box.prototype._unsupportedPropChanges.concat(['polarity', 'bits', 'initial', 'arst_value', 'srst_value', 'enable_srst', 'no_data']) 121 | }); 122 | export const DffView = BoxView.extend({ 123 | _autoResizeBox: true 124 | }); 125 | 126 | -------------------------------------------------------------------------------- /src/cells/display7.mjs: -------------------------------------------------------------------------------- 1 | import { IO, IOView, NumBase } from './io.mjs'; 2 | import { BoxView } from './base.mjs'; 3 | 4 | const highColor = '#03c03c'; 5 | const lowColor = '#3c3c3c'; 6 | 7 | /* 8 | * This is a standard 7-segment display element. 9 | * It is designed to take an 8-bit number as an input. 10 | * 11 | * The most significant bit determines the decimal point state (dp). 12 | * The following bits determine states of respectively: a,b,c,d,e,f,g 13 | * segments of the display. (g is determined by the least significant bit). 14 | * 15 | * The placement of single segments of the display can be checked 16 | * on the wikipedia page here: 17 | * https://en.wikipedia.org/wiki/Seven-segment_display#/media/File:7_Segment_Display_with_Labeled_Segments.svg 18 | */ 19 | export const Display7 = IO.define('Display7', 20 | { 21 | bits: 8, 22 | size: { width: 76.5, height: 110 }, 23 | attrs: { 24 | LEDs: { 25 | fill: lowColor, 26 | transform: 'translate(1,1),scale(6)' 27 | }, 28 | a: { 29 | points: '1, 1 2, 0 8, 0 9, 1 8, 2 2, 2' 30 | }, 31 | b: { 32 | points: '9, 1 10, 2 10, 8 9, 9 8, 8 8, 2' 33 | }, 34 | c: { 35 | points: '9, 9 10,10 10,16 9,17 8,16 8,10' 36 | }, 37 | d: { 38 | points: '9,17 8,18 2,18 1,17 2,16 8,16' 39 | }, 40 | e: { 41 | points: '1,17 0,16 0,10 1, 9 2,10 2,16' 42 | }, 43 | f: { 44 | points: '1, 9 0, 8 0, 2 1, 1 2, 2 2, 8' 45 | }, 46 | g: { 47 | points: '1, 9 2, 8 8, 8 9, 9 8,10 2,10' 48 | }, 49 | dp: { 50 | cx: '11.3', 51 | cy: '16.9', 52 | r: '1.1' 53 | }, 54 | body: { 55 | height: 'calc(h)', 56 | width: 'calc(w)', 57 | stroke: '#222222', 58 | fill: '#333333', 59 | } 60 | } 61 | }, { 62 | isOutput: true, 63 | _portDirection: 'in', 64 | bits: 8, 65 | 66 | getOutput() { 67 | return this.get('inputSignals').in; 68 | }, 69 | markupBus: NumBase.prototype.markup.concat([ 70 | { 71 | tagName: 'rect', 72 | selector: 'display' 73 | }, 74 | { 75 | tagName: 'polygon', 76 | selector: 'a', 77 | groupSelector: 'LEDs' 78 | }, { 79 | tagName: 'polygon', 80 | selector: 'b', 81 | groupSelector: 'LEDs' 82 | }, { 83 | tagName: 'polygon', 84 | selector: 'c', 85 | groupSelector: 'LEDs' 86 | }, { 87 | tagName: 'polygon', 88 | selector: 'd', 89 | groupSelector: 'LEDs' 90 | }, { 91 | tagName: 'polygon', 92 | selector: 'e', 93 | groupSelector: 'LEDs' 94 | }, { 95 | tagName: 'polygon', 96 | selector: 'f', 97 | groupSelector: 'LEDs' 98 | }, { 99 | tagName: 'polygon', 100 | selector: 'g', 101 | groupSelector: 'LEDs' 102 | }, { 103 | tagName: 'circle', 104 | selector: 'dp', 105 | groupSelector: 'LEDs' 106 | }]), 107 | }); 108 | 109 | export const Display7View = IOView.extend({ 110 | _autoResizeBox: false, 111 | 112 | confirmUpdate(flags) { 113 | BoxView.prototype.confirmUpdate.apply(this, arguments); 114 | this._updateDisplay(); 115 | }, 116 | _updateDisplay() { 117 | const inputSignal = this.model.getOutput(); 118 | const newAttrs = { 119 | dp: { fill: inputSignal.get(7) === 1 ? highColor : lowColor }, 120 | a: { fill: inputSignal.get(6) === 1 ? highColor : lowColor }, 121 | b: { fill: inputSignal.get(5) === 1 ? highColor : lowColor }, 122 | c: { fill: inputSignal.get(4) === 1 ? highColor : lowColor }, 123 | d: { fill: inputSignal.get(3) === 1 ? highColor : lowColor }, 124 | e: { fill: inputSignal.get(2) === 1 ? highColor : lowColor }, 125 | f: { fill: inputSignal.get(1) === 1 ? highColor : lowColor }, 126 | g: { fill: inputSignal.get(0) === 1 ? highColor : lowColor }, 127 | }; 128 | this._applyAttrs(newAttrs); 129 | }, 130 | }); 131 | -------------------------------------------------------------------------------- /src/cells/fsm.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import $ from 'jquery'; 4 | import _ from 'lodash'; 5 | import * as joint from 'jointjs'; 6 | import { Box, BoxView } from './base.mjs'; 7 | import * as help from '../help.mjs'; 8 | import { Vector3vl, Mem3vl } from '3vl'; 9 | import dagre from 'dagre'; 10 | import graphlib from 'graphlib'; 11 | 12 | export const FSM = Box.define('FSM', { 13 | /* default properties */ 14 | bits: { in: 1, out: 1}, 15 | polarity: { clock: true }, 16 | init_state: 0, 17 | states: 1, 18 | trans_table: [], 19 | 20 | size: { width: 80, height: 3*16+8 }, 21 | ports: { 22 | groups: { 23 | 'in': { 24 | position: Box.prototype._getStackedPosition({ side: 'left' }) 25 | }, 26 | 'out': { 27 | position: Box.prototype._getStackedPosition({ side: 'right' }) 28 | } 29 | } 30 | } 31 | }, { 32 | initialize() { 33 | const bits = this.get('bits'); 34 | const polarity = this.get('polarity'); 35 | 36 | const init_state = this.get('init_state'); 37 | const states = this.get('states'); 38 | const trans_table = this.get('trans_table'); 39 | 40 | this.get('ports').items = [ 41 | { id: 'in', group: 'in', dir: 'in', bits: bits.in, labelled: true }, 42 | { id: 'clk', group: 'in', dir: 'in', bits: 1, polarity: polarity.clock, decor: Box.prototype.decorClock, labelled: true }, 43 | { id: 'arst', group: 'in', dir: 'in', bits: 1, polarity: polarity.arst, labelled: true }, 44 | { id: 'out', group: 'out', dir: 'out', bits: bits.out, labelled: true } 45 | ]; 46 | 47 | Box.prototype.initialize.apply(this, arguments); 48 | 49 | const current_state = this.get('current_state'); 50 | 51 | this.fsmgraph = new joint.dia.Graph; 52 | const statenodes = []; 53 | for (let n = 0; n < states; n++) { 54 | const node = new joint.shapes.standard.Circle({stateNo: n, id: 'state' + n, isInit: n == init_state}); 55 | node.attr('label/text', String(n)); 56 | node.resize(100,50); 57 | if (n == init_state) 58 | node.attr('body/strokeWidth', 3) 59 | if (n == current_state) 60 | node.attr('body/class', 'current_state'); 61 | node.addTo(this.fsmgraph); 62 | statenodes.push(node); 63 | } 64 | for (const [stateIn, trs] of this.transitions) { 65 | for (const tr of trs) { 66 | const trans = new joint.shapes.standard.Link({ 67 | id: tr.id, 68 | ctrlIn: tr.ctrlIn, 69 | ctrlOut: tr.ctrlOut 70 | }); 71 | trans.appendLabel({ 72 | attrs: { 73 | text: { 74 | text: trans.get('ctrlIn').toBin() + '/' + trans.get('ctrlOut').toBin() 75 | } 76 | } 77 | }); 78 | trans.source({ id: 'state' + stateIn }); 79 | trans.target({ id: 'state' + tr.stateOut }); 80 | trans.addTo(this.fsmgraph); 81 | } 82 | } 83 | 84 | this.listenTo(this, 'change:current_state', (model, state) => { 85 | const pstate = model.previous('current_state'); 86 | this.fsmgraph.getCell('state' + pstate).removeAttr('body/class'); 87 | this.fsmgraph.getCell('state' + state).attr('body/class', 'current_state'); 88 | }); 89 | this.listenTo(this, 'change:next_trans', (model, id) => { 90 | const pid = model.previous('next_trans'); 91 | if (pid) { 92 | const cell = this.fsmgraph.getCell(pid); 93 | cell.removeAttr('line/class'); 94 | cell.removeAttr('line/targetMarker/class'); 95 | } 96 | if (id) { 97 | this.fsmgraph.getCell(id).attr({ 98 | line: { 99 | class: 'next_trans', 100 | targetMarker: { 101 | class: 'next_trans' 102 | } 103 | } 104 | }); 105 | } 106 | }); 107 | }, 108 | prepare() { 109 | const bits = this.get('bits'); 110 | this.transitions = new Map(); 111 | var id = 0; 112 | for (const tr of this.get('trans_table')) { 113 | if (!this.transitions.has(tr.state_in)) 114 | this.transitions.set(tr.state_in, []); 115 | this.transitions.get(tr.state_in).push({ 116 | id: 'tr'+id, 117 | stateOut: tr.state_out, 118 | ctrlIn: Vector3vl.fromBin(tr.ctrl_in, bits.in), 119 | ctrlOut: Vector3vl.fromBin(tr.ctrl_out, bits.out) 120 | }); 121 | id++; 122 | }; 123 | var current_state = this.get('current_state'); 124 | if (current_state === undefined) { 125 | current_state = this.get('init_state'); 126 | this.set('current_state', current_state); 127 | } 128 | this.last_clk = 0; 129 | }, 130 | operation(data) { 131 | const bits = this.get('bits'); 132 | const polarity = this.get('polarity'); 133 | const next_trans = () => { 134 | const links = this.transitions.get(this.get('current_state')) || []; 135 | for (const trans of links) { 136 | const ctrlIn = trans.ctrlIn; 137 | const xmask = ctrlIn.xmask(); 138 | if (data.in.or(xmask).eq(ctrlIn.or(xmask))) 139 | return trans; 140 | } 141 | }; 142 | const next_output = () => { 143 | const links = this.transitions.get(this.get('current_state')) || []; 144 | const ixmask = data.in.xmask(); 145 | const results = []; 146 | for (const trans of links) { 147 | const ctrlIn = trans.ctrlIn; 148 | const xmask = ctrlIn.xmask().or(ixmask); 149 | if (data.in.or(xmask).eq(ctrlIn.or(xmask))) 150 | results.push(trans.ctrlOut); 151 | } 152 | const xes = Vector3vl.xes(bits.out); 153 | if (results.length == 0) return xes; 154 | while (results.length > 1) { 155 | const other = results.pop(); 156 | const eqs = results[0].xnor(other).or(xes); 157 | results[0] = results[0].and(eqs).or(xes.and(eqs.xmask())); 158 | }; 159 | return results[0]; 160 | }; 161 | const pol = what => polarity[what] ? 1 : -1; 162 | if (data.arst.get(0) == pol('arst')) { 163 | this.set('current_state', this.get('init_state')); 164 | } else { 165 | const last_clk = this.last_clk; 166 | if (data.clk.get(0) == pol('clock') && last_clk == -pol('clock')) { 167 | const trans = next_trans(); 168 | this.set('current_state', 169 | trans ? trans.stateOut : this.get('init_state')); 170 | } 171 | } 172 | this.last_clk = data.clk.get(0); 173 | const trans = next_trans(); 174 | if (!trans) { 175 | this.set('next_trans', undefined); 176 | } else { 177 | this.set('next_trans', trans.id); 178 | } 179 | return { out: next_output() }; 180 | }, 181 | markup: Box.prototype.markup.concat(Box.prototype.markupZoom), 182 | _gateParams: Box.prototype._gateParams.concat(['bits', 'polarity', 'states', 'init_state', 'trans_table']), 183 | _unsupportedPropChanges: Box.prototype._unsupportedPropChanges.concat(['bits', 'polarity', 'states', 'init_state', 'trans_table']), 184 | _presentationParams: Box.prototype._presentationParams.concat(['current_state', 'next_trans']) 185 | }); 186 | 187 | export const FSMView = BoxView.extend({ 188 | _autoResizeBox: true, 189 | events: { 190 | "click foreignObject.tooltip": "stopprop", 191 | "mousedown foreignObject.tooltip": "stopprop", 192 | "touchstart foreignObject.tooltip": "stopprop", // make sure the input receives focus 193 | "click a.zoom": "_displayEditor" 194 | }, 195 | _displayEditor(evt) { 196 | evt.stopPropagation(); 197 | const div = $('
', { 198 | title: "FSM: " + this.model.get('label') 199 | }).appendTo('html > body'); 200 | const pdiv = $('
').appendTo(div); 201 | const graph = this.model.fsmgraph; 202 | const paper = new joint.dia.Paper({ 203 | el: pdiv, 204 | model: graph 205 | }); 206 | // to visualize the cells 207 | graph.resetCells(graph.getCells()); 208 | // lazy layout 209 | if (!graph.get('laid_out')) { 210 | joint.layout.DirectedGraph.layout(graph, { 211 | dagre: dagre, 212 | graphlib: graphlib 213 | }); 214 | graph.set('laid_out', true); 215 | } 216 | // auto-resizing 217 | paper.listenTo(graph, 'change:position', (elem) => { 218 | paper.fitToContent({ padding: 30, allowNewOrigin: 'any' }); 219 | }); 220 | paper.fitToContent({ padding: 30, allowNewOrigin: 'any' }); 221 | this.paper.trigger('open:fsm', div, () => { 222 | paper.remove(); 223 | div.remove(); 224 | }); 225 | return false; 226 | } 227 | }); 228 | 229 | -------------------------------------------------------------------------------- /src/cells/gates.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as joint from 'jointjs'; 4 | import { Gate, GateView } from './base.mjs'; 5 | import * as help from '../help.mjs'; 6 | import { Vector3vl } from '3vl'; 7 | 8 | const and_path = "M19 0v32h16c9 0 16-7 16-16S44 0 35 0H20z"; 9 | const or_path = "M14.3 0l1.6 2s4.5 5.6 4.5 14s-4.5 14-4.5 14l-1.6 2H28c3.8 0 16.6-.5 25-16h0A28 28 0 0028 0H16.8z"; 10 | const buf_path = "M18-2v36l2-1 32-17h0L20-1z"; 11 | const xor_arc_path = "M6.8-1.2 10 2.7S14.2 8 14.2 16S10 29.3 10 29.3l-3.2 3.9H10l1.7-2.4s4.8-6 4.8-14.8c0-8.9-4.8-14.8-4.8-14.8l-1.7-2.4z"; 12 | 13 | const xor_arc_path_markup = { 14 | tagName: 'path', 15 | selector: 'xor_arc', 16 | attributes: { 17 | 'fill': "#000", 18 | 'd': xor_arc_path 19 | } 20 | }; 21 | 22 | const neg_markup = { 23 | tagName: 'circle', 24 | className: 'body', 25 | selector: 'neg_bubble', 26 | attributes: { 27 | 'stroke': "#000", 28 | 'stroke-width': '2px', 29 | 'cx': 56, 30 | 'cy': 16, 31 | 'r': 4 32 | } 33 | }; 34 | 35 | function gateMarkup(children = []) { 36 | return { 37 | tagName: 'g', 38 | selector: 'body', 39 | children: [ 40 | { 41 | tagName: 'path', 42 | className: 'body gate', 43 | selector: 'gate' 44 | } 45 | ].concat(children) 46 | } 47 | } 48 | 49 | // base class for gates 50 | export const GateSVG = Gate.define('GateSVG', { 51 | /* default properties */ 52 | bits: 1, 53 | 54 | size: { width: 60, height: 32 }, 55 | ports: { 56 | groups: { 57 | 'in': { position: { name: 'left', args: { dx: 30 } }, attrs: { wire: { x2: -50 }, port: { refX: -50 } }, z: -1 }, 58 | 'out': { position: { name: 'right', args: { dx: -30 } }, attrs: { wire: { x2: 50 }, port: { refX: 50 } }, z: -1 } 59 | } 60 | } 61 | }, { 62 | markup: Gate.prototype.markup.concat([gateMarkup()]), 63 | _gateParams: Gate.prototype._gateParams.concat(['bits']) 64 | }); 65 | 66 | // Single-input gate model 67 | export const Gate11 = GateSVG.define('Gate11', {}, { 68 | initialize() { 69 | const bits = this.get('bits'); 70 | this.get('ports').items = [ 71 | { id: 'in', group: 'in', dir: 'in', bits: bits }, 72 | { id: 'out', group: 'out', dir: 'out', bits: bits } 73 | ]; 74 | 75 | GateSVG.prototype.initialize.apply(this, arguments); 76 | 77 | this.on('change:bits', (_, bits) => { 78 | this._setPortsBits({ in: bits, out: bits }); 79 | }); 80 | } 81 | }); 82 | 83 | // Multi-input gate model 84 | export const GateX1 = GateSVG.define('GateX1', { 85 | /* default properties */ 86 | inputs: 2, 87 | 88 | attrs: { 89 | gate: { 90 | 'vector-effect': 'non-scaling-stroke' 91 | }, 92 | xor_arc: { 93 | 'vector-effect': 'non-scaling-stroke' 94 | }, 95 | neg_bubble: { 96 | 'vector-effect': 'non-scaling-stroke' 97 | } 98 | } 99 | }, { 100 | initialize() { 101 | const bits = this.get('bits'); 102 | const inputs = this.get('inputs'); 103 | 104 | const ports = []; 105 | for (let i = 1; i <= inputs; i++) 106 | ports.push({ id: 'in' + i, group: 'in', dir: 'in', bits: bits }); 107 | ports.push({ id: 'out', group: 'out', dir: 'out', bits: bits }); 108 | this.get('ports').items = ports; 109 | const scaling = inputs / 2; 110 | this.set('size', { width: 60 * scaling, height: 32 * scaling }); 111 | this.attr('body/transform', 'translate(-4, 0) scale('+scaling+')'); 112 | 113 | this.prop('ports/groups/in/position/args/dx', 30 * scaling); 114 | this.prop('ports/groups/in/attrs/wire/x2', -30 * scaling - 20); 115 | this.prop('ports/groups/in/attrs/port/refX', -30 * scaling - 20); 116 | this.prop('ports/groups/out/position/args/dx', -30 * scaling); 117 | this.prop('ports/groups/out/attrs/wire/x2', 30 * scaling + 20); 118 | this.prop('ports/groups/out/attrs/port/refX', 30 * scaling + 20); 119 | 120 | GateSVG.prototype.initialize.apply(this, arguments); 121 | 122 | this.on('change:bits', (_, bits) => { 123 | const inputs = this.get('inputs'); 124 | const param = { out: bits }; 125 | for (let i = 1; i <= inputs; i++) 126 | param['in' + i] = bits; 127 | this._setPortsBits(param); 128 | }); 129 | }, 130 | operation(data) { 131 | let ret = data.in1; 132 | for (let i = 2; i <= this.get('inputs'); i++) 133 | ret = this.binoperation(ret, data['in' + i]); 134 | return { out: this.finoperation(ret) }; 135 | }, 136 | finoperation(val) { 137 | return val 138 | }, 139 | _gateParams: GateSVG.prototype._gateParams.concat(['inputs']), 140 | _unsupportedPropChanges: GateSVG.prototype._unsupportedPropChanges.concat(['inputs']), 141 | _operationHelpers: GateSVG.prototype._operationHelpers.concat(['binoperation', 'finoperation']) 142 | }); 143 | 144 | // Reducing gate model 145 | export const GateReduce = GateSVG.define('GateReduce', {}, { 146 | initialize() { 147 | const bits = this.get('bits'); 148 | this.get('ports').items = [ 149 | { id: 'in', group: 'in', dir: 'in', bits: bits }, 150 | { id: 'out', group: 'out', dir: 'out', bits: 1 } 151 | ]; 152 | 153 | GateSVG.prototype.initialize.apply(this, arguments); 154 | 155 | this.on('change:bits', (_, bits) => { 156 | this._setPortsBits({ in: bits }); 157 | }); 158 | } 159 | }); 160 | 161 | // Repeater (buffer) gate model 162 | export const Repeater = Gate11.define('Repeater', { 163 | attrs: { gate: { d: buf_path }} 164 | }, { 165 | operation(data) { 166 | return { out: data.in }; 167 | } 168 | }); 169 | export const RepeaterView = GateView; 170 | 171 | // Not gate model 172 | export const Not = Gate11.define('Not', { 173 | attrs: { gate: { d: buf_path }} 174 | }, { 175 | operation(data) { 176 | return { out: data.in.not() }; 177 | }, 178 | markup: Gate.prototype.markup.concat([gateMarkup([neg_markup])]), 179 | }); 180 | export const NotView = GateView; 181 | 182 | // Or gate model 183 | export const Or = GateX1.define('Or', { 184 | attrs: { gate: { d: or_path }} 185 | }, { 186 | binoperation(in1, in2) { 187 | return in1.or(in2); 188 | } 189 | }); 190 | export const OrView = GateView; 191 | 192 | // And gate model 193 | export const And = GateX1.define('And', { 194 | attrs: { gate: { d: and_path }} 195 | }, { 196 | binoperation(in1, in2) { 197 | return in1.and(in2); 198 | } 199 | }); 200 | export const AndView = GateView; 201 | 202 | // Nor gate model 203 | export const Nor = GateX1.define('Nor', { 204 | attrs: { gate: { d: or_path }} 205 | }, { 206 | binoperation(in1, in2) { 207 | return in1.or(in2); 208 | }, 209 | finoperation(val) { 210 | return val.not(); 211 | }, 212 | markup: Gate.prototype.markup.concat([gateMarkup([neg_markup])]), 213 | }); 214 | export const NorView = GateView; 215 | 216 | // Nand gate model 217 | export const Nand = GateX1.define('Nand', { 218 | attrs: { gate: { d: and_path }} 219 | }, { 220 | binoperation(in1, in2) { 221 | return in1.and(in2); 222 | }, 223 | finoperation(val) { 224 | return val.not(); 225 | }, 226 | markup: Gate.prototype.markup.concat([gateMarkup([neg_markup])]), 227 | }); 228 | export const NandView = GateView; 229 | 230 | // Xor gate model 231 | export const Xor = GateX1.define('Xor', { 232 | attrs: { gate: { d: or_path }} 233 | }, { 234 | binoperation(in1, in2) { 235 | return in1.xor(in2); 236 | }, 237 | markup: Gate.prototype.markup.concat([gateMarkup([xor_arc_path_markup])]), 238 | }); 239 | export const XorView = GateView; 240 | 241 | // Xnor gate model 242 | export const Xnor = GateX1.define('Xnor', { 243 | attrs: { gate: { d: or_path }} 244 | }, { 245 | binoperation(in1, in2) { 246 | return in1.xor(in2); 247 | }, 248 | finoperation(val) { 249 | return val.not(); 250 | }, 251 | markup: Gate.prototype.markup.concat([gateMarkup([xor_arc_path_markup, neg_markup])]), 252 | }); 253 | export const XnorView = GateView; 254 | 255 | // Reducing Or gate model 256 | export const OrReduce = GateReduce.define('OrReduce', { 257 | attrs: { gate: { d: or_path }} 258 | }, { 259 | operation(data) { 260 | return { out: data.in.reduceOr() }; 261 | } 262 | }); 263 | export const OrReduceView = GateView; 264 | 265 | // Reducing Nor gate model 266 | export const NorReduce = GateReduce.define('NorReduce', { 267 | attrs: { gate: { d: or_path }} 268 | }, { 269 | operation(data) { 270 | return { out: data.in.reduceNor() }; 271 | }, 272 | markup: GateReduce.prototype.markup.concat([neg_markup]) 273 | }); 274 | export const NorReduceView = GateView; 275 | 276 | // Reducing And gate model 277 | export const AndReduce = GateReduce.define('AndReduce', { 278 | attrs: { gate: { d: and_path }} 279 | }, { 280 | operation(data) { 281 | return { out: data.in.reduceAnd() }; 282 | } 283 | }); 284 | export const AndReduceView = GateView; 285 | 286 | // Reducing Nand gate model 287 | export const NandReduce = GateReduce.define('NandReduce', { 288 | attrs: { gate: { d: and_path }} 289 | }, { 290 | operation(data) { 291 | return { out: data.in.reduceNand() }; 292 | }, 293 | markup: Gate.prototype.markup.concat([gateMarkup([neg_markup])]), 294 | }); 295 | export const NandReduceView = GateView; 296 | 297 | // Reducing Xor gate model 298 | export const XorReduce = GateReduce.define('XorReduce', { 299 | attrs: { gate: { d: or_path }} 300 | }, { 301 | operation(data) { 302 | return { out: data.in.reduceXor() }; 303 | }, 304 | markup: Gate.prototype.markup.concat([gateMarkup([xor_arc_path_markup])]), 305 | }); 306 | export const XorReduceView = GateView; 307 | 308 | // Reducing Xnor gate model 309 | export const XnorReduce = GateReduce.define('XnorReduce', { 310 | attrs: { gate: { d: or_path }} 311 | }, { 312 | operation(data) { 313 | return { out: data.in.reduceXnor() }; 314 | }, 315 | markup: Gate.prototype.markup.concat([gateMarkup([xor_arc_path_markup, neg_markup])]), 316 | }); 317 | export const XnorReduceView = GateView; 318 | 319 | -------------------------------------------------------------------------------- /src/cells/mux.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as joint from 'jointjs'; 4 | import _ from 'lodash'; 5 | import { Gate, GateView, portGroupAttrs } from './base.mjs'; 6 | import * as help from '../help.mjs'; 7 | import { Vector3vl } from '3vl'; 8 | 9 | // add offset of 30pt to account for the top selection port and oversized box at layout time 10 | const mux_pos_offset = 20; 11 | const mux_size_offset = 30; 12 | 13 | // Multiplexers 14 | export const GenMux = Gate.define('GenMux', { 15 | /* default properties */ 16 | bits: { in: 1, sel: 1 }, 17 | 18 | size: { width: 40, height: undefined }, 19 | ports: { 20 | groups: { 21 | 'in2': { 22 | position: { name: 'top', args: { y: 5 } }, 23 | attrs: _.merge({}, portGroupAttrs, { 24 | wire: { x2: 0, y2: -25 }, 25 | port: { magnet: 'passive', refY: -25 }, 26 | bits: { refDx: -5, refDy: 2, textAnchor: 'start' } 27 | }), 28 | z: -1 29 | } 30 | } 31 | }, 32 | attrs: { label: { refDy: 8 } } 33 | }, { 34 | initialize() { 35 | const bits = this.get('bits'); 36 | const ports = [ 37 | { id: 'sel', group: 'in2', dir: 'in', bits: bits.sel }, 38 | { id: 'out', group: 'out', dir: 'out', bits: bits.in } 39 | ]; 40 | 41 | const ins = this.muxInputs(bits.sel); 42 | this.get('size').height = ins.length*16+8; 43 | 44 | const vpath = [ 45 | [2, 0], 46 | [5, 5], 47 | [10, -5] 48 | ]; 49 | const path = 'M' + vpath.map(l => l.join(' ')).join(' L'); 50 | 51 | for (const [num, label] of ins.entries()) { 52 | const port = { id: 'in' + num, group: 'in', dir: 'in', bits: bits.in, decor: path }; 53 | if (label) { 54 | port.portlabel = String(label); 55 | port.labelled = true; 56 | } 57 | ports.push(port); 58 | } 59 | 60 | this.get('ports').items = ports; 61 | 62 | Gate.prototype.initialize.apply(this, arguments); 63 | 64 | const drawBorder = (size) => this.attr(['body', 'points'], 65 | [[0,-5],[size.width,5],[size.width,size.height-5],[0,size.height+5]] 66 | .map(x => x.join(',')).join(' ')); 67 | drawBorder(this.get('size')); 68 | 69 | this.on('change:size', (_, size) => drawBorder(size)); 70 | }, 71 | operation(data) { 72 | const i = this.muxInput(data.sel); 73 | if (i === undefined) return { out: Vector3vl.xes(this.get('bits').in) }; 74 | return { out: data['in' + i] }; 75 | }, 76 | getLayoutSize() { 77 | const size = this.size(); 78 | size.height += mux_size_offset; 79 | return size; 80 | }, 81 | setLayoutPosition(position) { 82 | this.set('position', { 83 | x: position.x, 84 | y: position.y + mux_pos_offset 85 | }); 86 | }, 87 | getPortsPositions() { 88 | const positions = Gate.prototype.getPortsPositions.apply(this, arguments); 89 | const res = {}; 90 | for (const id in positions) { 91 | res[id] = { ...positions[id] }; 92 | res[id].y = res[id].y + mux_pos_offset; 93 | } 94 | return res; 95 | }, 96 | markup: Gate.prototype.markup.concat([{ 97 | tagName: 'polygon', 98 | className: 'body', 99 | selector: 'body' 100 | } 101 | ]), 102 | _gateParams: Gate.prototype._gateParams.concat(['bits']), 103 | _unsupportedPropChanges: Gate.prototype._unsupportedPropChanges.concat(['bits']), 104 | _operationHelpers: Gate.prototype._operationHelpers.concat(['muxInput']) 105 | }); 106 | export const GenMuxView = GateView.extend({ 107 | initialize() { 108 | this.ins = this.model.muxInputs(this.model.get('bits').sel); 109 | GateView.prototype.initialize.apply(this, arguments); 110 | }, 111 | confirmUpdate(flags) { 112 | GateView.prototype.confirmUpdate.apply(this, arguments); 113 | if (this.hasFlag(flags, 'SIGNAL')) { 114 | this._updateMux(this.model.get('inputSignals')); 115 | } 116 | }, 117 | render() { 118 | GateView.prototype.render.apply(this, arguments); 119 | this._updateMux(this.model.get('inputSignals')); 120 | }, 121 | _updateMux(data) { 122 | const i = this.model.muxInput(data.sel); 123 | for (const num of this.ins.keys()) { 124 | this.$('[port=in' + num + '] path.decor').css('visibility', i == num ? 'visible' : 'hidden'); 125 | } 126 | } 127 | }); 128 | 129 | // Multiplexer with binary selection 130 | export const Mux = GenMux.define('Mux', { 131 | }, { 132 | muxInputs: n => Array(1 << n), 133 | muxInput: i => i.isFullyDefined ? i.toBigInt().toString() : undefined 134 | }); 135 | export const MuxView = GenMuxView; 136 | 137 | // Multiplexer with one-hot selection 138 | export const Mux1Hot = GenMux.define('Mux1Hot', { 139 | attrs: { 140 | info: { 141 | refX: .5, refY: .5, 142 | textAnchor: 'middle', textVerticalAnchor: 'middle', 143 | text: '1Hot', 144 | transform: 'rotate(90)' 145 | } 146 | } 147 | }, { 148 | markup: GenMux.prototype.markup.concat([{ 149 | tagName: 'text', 150 | className: 'info', 151 | selector: 'info' 152 | } 153 | ]), 154 | muxInputs: n => Array(n + 1), 155 | muxInput: s => { 156 | const i = s.toArray(); 157 | return s.isFullyDefined && i.filter(x => x == 1).length <= 1 158 | ? String(i.indexOf(1)+1) : undefined 159 | } 160 | }); 161 | export const Mux1HotView = GenMuxView; 162 | 163 | export const MuxSparse = GenMux.define('MuxSparse', { 164 | /* default properties */ 165 | inputs: undefined, 166 | default_input: false 167 | }, { 168 | initialize() { 169 | const inputs = this.get('inputs'); 170 | for (let i = 0; i < inputs.length; i++) 171 | if (typeof inputs[i] != 'bigint') 172 | inputs[i] = BigInt(inputs[i]); 173 | GenMux.prototype.initialize.apply(this, arguments); 174 | }, 175 | muxInputs(n) { 176 | if (this.get('default_input')) 177 | return ['*'].concat(this.get('inputs')) 178 | else 179 | return this.get('inputs'); 180 | }, 181 | muxInput(i) { 182 | const deflt = this.get('default_input'); 183 | if (!i.isFullyDefined) return undefined; 184 | const idx = this.get('inputs').indexOf(i.toBigInt()); 185 | return idx < 0 ? (deflt ? 0 : undefined) : (deflt ? idx + 1 : idx); 186 | }, 187 | _gateParams: GenMux.prototype._gateParams.concat(['inputs', 'default_input']), 188 | _unsupportedPropChanges: GenMux.prototype._unsupportedPropChanges.concat(['inputs', 'default_input']) 189 | }); 190 | export const MuxSparseView = GenMuxView; 191 | 192 | -------------------------------------------------------------------------------- /src/cells/subcircuit.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as joint from 'jointjs'; 4 | import _ from 'lodash'; 5 | import { Box, BoxView } from './base.mjs'; 6 | import { IO, Input, Output } from './io.mjs'; 7 | import * as help from '../help.mjs'; 8 | 9 | // add offset of 10pt to account for the top label at layout time 10 | const subcircuit_pos_offset = 10; 11 | const subcircuit_size_offset = 10; 12 | 13 | // Subcircuit model -- embeds a circuit graph in an element 14 | export const Subcircuit = Box.define('Subcircuit', { 15 | /* default properties */ 16 | propagation: 0, 17 | warning: false, 18 | 19 | attrs: { 20 | wrapper: { 21 | refWidth: 1, refHeight: 1, 22 | stroke: 'red', strokeWidth: 10 23 | }, 24 | type: { 25 | refX: .5, refY: -10, 26 | textAnchor: 'middle', textVerticalAnchor: 'middle' 27 | } 28 | } 29 | }, { 30 | initialize() { 31 | this.bindAttrToProp('text.type/text', 'celltype'); 32 | 33 | const graph = this.get('graph'); 34 | console.assert(graph instanceof joint.dia.Graph); 35 | graph.set('subcircuit', this); 36 | const IOs = graph.getCells() 37 | .filter((cell) => cell instanceof IO); 38 | const inputs = IOs.filter((cell) => cell instanceof Input); 39 | const outputs = IOs.filter((cell) => cell instanceof Output); 40 | function sortfun(x, y) { 41 | if (x.has('order') || y.has('order')) 42 | return x.get('order') - y.get('order'); 43 | return x.get('net').localeCompare(y.get('net')); 44 | } 45 | inputs.sort(sortfun); 46 | outputs.sort(sortfun); 47 | const vcount = Math.max(inputs.length, outputs.length); 48 | const size = { width: 80, height: vcount*16+8 }; 49 | const ports = [], iomap = {}; 50 | for (const [num, io] of inputs.entries()) { 51 | ports.push({ id: io.get('net'), group: 'in', dir: 'in', bits: io.get('bits'), labelled: true }); 52 | } 53 | for (const [num, io] of outputs.entries()) { 54 | ports.push({ id: io.get('net'), group: 'out', dir: 'out', bits: io.get('bits'), labelled: true }); 55 | } 56 | for (const io of IOs) { 57 | iomap[io.get('net')] = io.get('id'); 58 | } 59 | this.set('size', size); 60 | this.set('circuitIOmap', iomap); 61 | this.get('ports').items = ports; 62 | this.set('warning', graph._warnings > 0); 63 | 64 | Box.prototype.initialize.apply(this, arguments); 65 | }, 66 | _resetPortValue(port) { 67 | const iomap = this.get('circuitIOmap'); 68 | const graph = this.get('graph'); 69 | if (port.dir == 'in') 70 | return graph.getCell(iomap[port.id]).get('outputSignals').out; 71 | if (port.dir == 'out') 72 | return graph.getCell(iomap[port.id]).get('inputSignals').in; 73 | return Box.prototype._resetPortValue.call(this, port); 74 | }, 75 | getLayoutSize() { 76 | const size = this.size(); 77 | size.height += subcircuit_size_offset; 78 | return size; 79 | }, 80 | setLayoutPosition(position) { 81 | this.set('position', { 82 | x: position.x, 83 | y: position.y + subcircuit_pos_offset 84 | }); 85 | }, 86 | getPortsPositions() { 87 | const positions = Box.prototype.getPortsPositions.apply(this, arguments); 88 | const res = {}; 89 | for (const id in positions) { 90 | res[id] = { ...positions[id] }; 91 | res[id].y = res[id].y + subcircuit_pos_offset; 92 | } 93 | return res; 94 | }, 95 | markup: [{ 96 | tagName: 'rect', 97 | selector: 'wrapper' 98 | } 99 | ].concat(Box.prototype.markup, [{ 100 | tagName: 'text', 101 | className: 'type', 102 | selector: 'type' 103 | } 104 | ], Box.prototype.markupZoom), 105 | _gateParams: Box.prototype._gateParams.concat(['celltype']), 106 | _unsupportedPropChanges: Box.prototype._unsupportedPropChanges.concat(['celltype']) 107 | }); 108 | 109 | export const SubcircuitView = BoxView.extend({ 110 | attrs: _.merge({}, BoxView.prototype.attrs, { 111 | warning: { 112 | warn: { wrapper: { 'stroke-opacity': '0.5' } }, 113 | none: { wrapper: { 'stroke-opacity': '0' } } 114 | } 115 | }), 116 | _autoResizeBox: true, 117 | presentationAttributes: BoxView.addPresentationAttributes({ 118 | warning: 'WARNING' 119 | }), 120 | confirmUpdate(flags) { 121 | BoxView.prototype.confirmUpdate.apply(this, arguments); 122 | if (this.hasFlag(flags, 'WARNING')) { 123 | this._updateWarning(); 124 | } 125 | }, 126 | _updateWarning() { 127 | const warning = this.model.get('warning'); 128 | const attrs = this.attrs.warning[ 129 | warning ? 'warn' : 'none' 130 | ]; 131 | this._applyAttrs(attrs); 132 | }, 133 | update() { 134 | BoxView.prototype.update.apply(this, arguments); 135 | this._updateWarning(); 136 | }, 137 | events: { 138 | "click foreignObject.tooltip": "stopprop", 139 | "mousedown foreignObject.tooltip": "stopprop", 140 | "touchstart foreignObject.tooltip": "stopprop", // make sure the input receives focus 141 | "click a.zoom": "zoomInCircuit" 142 | }, 143 | zoomInCircuit(evt) { 144 | evt.stopPropagation(); 145 | this.paper.trigger('open:subcircuit', this.model); 146 | return false; 147 | } 148 | }); 149 | 150 | -------------------------------------------------------------------------------- /src/elkjs.mjs: -------------------------------------------------------------------------------- 1 | 2 | import elkjs from 'elkjs/lib/elk.bundled.js'; 3 | import { Clock, Input, Output } from "./cells/io.mjs"; 4 | 5 | function to_elkjs(graph) { 6 | const elkGraph = { 7 | id: "root", 8 | properties: { 9 | algorithm: 'layered', 10 | 'elk.edgeRouting': 'ORTHOGONAL', 11 | 'elk.layered.spacing.nodeNodeBetweenLayers': 40.0, 12 | 'elk.layered.nodePlacement.favorStraightEdges': true, 13 | }, 14 | children: [], 15 | edges: [] 16 | }; 17 | for (const cell of graph.getCells()) { 18 | if (cell.isLink()) { 19 | var source = cell.get('source'); 20 | var target = cell.get('target'); 21 | if (!source.id || !target.id) break; 22 | 23 | elkGraph.edges.push({ 24 | id: cell.id, 25 | sources: [ source.id + '.' + source.port ], 26 | targets: [ target.id + '.' + target.port ] 27 | }); 28 | } else { 29 | const size = cell.getLayoutSize(); 30 | 31 | const ppos = {}; 32 | 33 | const ports = cell.getPorts().map((p, i) => { 34 | if (!ppos[p.group]) 35 | ppos[p.group] = cell.getPortsPositions(p.group); 36 | return { 37 | id: cell.id + "." + p.id, 38 | x: ppos[p.group][p.id].x, 39 | y: ppos[p.group][p.id].y, 40 | properties: { 41 | 'port.side': p.group == "in" ? "WEST" : p.group == "in2" ? "NORTH" : "EAST", 42 | 'port.borderOffset': 30, 43 | 'port.index': -i 44 | } 45 | }; 46 | }); 47 | 48 | elkGraph.children.push({ 49 | id: cell.id, 50 | width: size.width, 51 | height: size.height, 52 | ports: ports, 53 | properties: { 54 | portConstraints: 'FIXED_POS', 55 | layerConstraint: (cell instanceof Input || cell instanceof Clock) ? "FIRST" 56 | : cell instanceof Output ? "LAST" : "NONE" 57 | } 58 | }); 59 | } 60 | } 61 | return elkGraph; 62 | } 63 | 64 | function from_elkjs(graph, elkGraph) { 65 | graph.startBatch('layout'); 66 | for (const child of elkGraph.children) { 67 | const cell = graph.getCell(child.id); 68 | cell.setLayoutPosition({ 69 | x: child.x, 70 | y: child.y, 71 | width: child.width, 72 | height: child.height 73 | }); 74 | } 75 | const corner_dist = 10; 76 | for (const edge of elkGraph.edges) { 77 | const bps = edge.sections[0].bendPoints; 78 | if (!bps) 79 | continue; 80 | // elkjs gives us a bunch of points to create straight edges. 81 | // This causes JointJS to overshot and create strange shape due to rounded corners. 82 | // Instead, we split each corner point to two points, each shifted towards 83 | // the center of the edge by a little to give JointJS enough space 84 | // to make its round corners. 85 | let is_first = true; 86 | let last_point = edge.sections[0].startPoint; 87 | const vertices = []; 88 | const add_point = (bp, is_last) => { 89 | if (bp.x == last_point.x) { 90 | let edgelen = Math.abs(bp.y - last_point.y); 91 | let shift = bp.y > last_point.y ? corner_dist : -corner_dist; 92 | if (edgelen > 2 * corner_dist) { 93 | is_first || vertices.push({x: last_point.x, y: last_point.y + shift}); 94 | is_last || vertices.push({x: bp.x, y: bp.y - shift}); 95 | } 96 | else if (edgelen > 2 * corner_dist && !is_first && !is_last) { 97 | vertices.push({x: bp.x, y: (bp.y + last_point.y) / 2}); 98 | } 99 | } 100 | else if (bp.y == last_point.y) { 101 | let edgelen = Math.abs(bp.x - last_point.x); 102 | let shift = bp.x > last_point.x ? corner_dist : -corner_dist; 103 | if (edgelen > 2 * corner_dist) { 104 | is_first || vertices.push({x: last_point.x + shift, y: last_point.y}); 105 | is_last || vertices.push({x: bp.x - shift, y: bp.y}); 106 | } 107 | else if (edgelen > 2 * corner_dist && !is_first && !is_last) { 108 | vertices.push({x: (bp.x + last_point.x) / 2, y: bp.y}); 109 | } 110 | } 111 | else if (!is_last) { 112 | vertices.push({x: bp.x, y: bp.y}); 113 | } 114 | last_point = bp; 115 | is_first = false; 116 | }; 117 | for (const bp of bps) 118 | add_point(bp, false); 119 | // use the end point to finish the last bend point without adding the end point itself. 120 | add_point(edge.sections[0].endPoint, true); 121 | graph.getCell(edge.id).vertices(vertices); 122 | } 123 | graph.stopBatch('layout'); 124 | } 125 | 126 | export function elk_layout(graph) { 127 | const elkGraph = to_elkjs(graph); 128 | 129 | const elk = new elkjs(); 130 | 131 | elk.layout(elkGraph).then(g => { 132 | from_elkjs(graph, g); 133 | }); 134 | } 135 | 136 | -------------------------------------------------------------------------------- /src/engines.mjs: -------------------------------------------------------------------------------- 1 | 2 | export * from "./engines/synch.mjs"; 3 | export * from "./engines/browsersynch.mjs"; 4 | export * from "./engines/worker.mjs"; 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/engines/base.mjs: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'lodash'; 3 | import Backbone from 'backbone'; 4 | 5 | export class BaseEngine { 6 | constructor(graph) { 7 | this._graph = graph; 8 | } 9 | _addGate(graph, gate) { 10 | if (gate.get('type') == 'Subcircuit') 11 | this._addGraph(gate.get('graph')); 12 | } 13 | _addLink(graph, link) { 14 | } 15 | _addGraph(graph) { 16 | this.listenTo(graph, 'add', (cell) => { 17 | if (cell.isLink()) { 18 | this._addLink(graph, cell); 19 | } else { 20 | this._addGate(graph, cell); 21 | } 22 | }); 23 | this.listenTo(graph, 'remove', (cell) => { 24 | if (cell.isLink()) { 25 | this._removeLink(graph, cell); 26 | } else { 27 | this._removeGate(graph, cell); 28 | } 29 | }); 30 | this.listenTo(graph, 'change:source', (link, src) => { 31 | this._changeLinkSource(graph, link, src, link.previous('source')); 32 | }); 33 | this.listenTo(graph, 'change:target', (link, end) => { 34 | this._changeLinkTarget(graph, link, end, link.previous('target')); 35 | }); 36 | this.listenTo(graph, 'change:warning', (link, warn) => { 37 | this._changeLinkWarning(graph, link, warn, link.previous('warning')); 38 | }); 39 | for (const gate of graph.getElements()) 40 | this._addGate(graph, gate); 41 | for (const link of graph.getLinks()) 42 | this._addLink(graph, link); 43 | } 44 | _removeGate(graph, gate) { 45 | } 46 | _removeLink(graph, link) { 47 | } 48 | _changeLinkSource(graph, link, src, prevSrc) { 49 | } 50 | _changeLinkTarget(graph, link, end, prevEnd) { 51 | } 52 | _changeLinkWarning(graph, link, warn, prevWarn) { 53 | } 54 | }; 55 | 56 | _.extend(BaseEngine.prototype, Backbone.Events); 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/engines/browsersynch.mjs: -------------------------------------------------------------------------------- 1 | 2 | import { SynchEngine } from './synch.mjs'; 3 | 4 | export class BrowserSynchEngine extends SynchEngine { 5 | constructor(graph, opts) { 6 | super(graph, opts); 7 | this._interval_ms = 10; 8 | this._interval = null; 9 | this._idle = null; 10 | } 11 | start() { 12 | this._interval = setInterval(() => { 13 | this.updateGates(); 14 | this._checkMonitors(); 15 | }, this._interval_ms); 16 | this.trigger('changeRunning'); 17 | } 18 | startFast() { 19 | const idle = () => { 20 | this._idle = requestIdleCallback((dd) => { 21 | while (dd.timeRemaining() > 0 && this.hasPendingEvents && this._idle !== null) { 22 | this.updateGatesNext(); 23 | this._checkMonitors(); 24 | } 25 | if (this._idle !== null) { 26 | idle(); 27 | } 28 | }, {timeout: 20}); 29 | } 30 | idle(); 31 | this.trigger('changeRunning'); 32 | } 33 | stop() { 34 | if (this._interval) { 35 | clearInterval(this._interval); 36 | this._interval = null; 37 | } 38 | if (this._idle) { 39 | cancelIdleCallback(this._idle); 40 | this._idle = null; 41 | } 42 | this.trigger('changeRunning'); 43 | return Promise.resolve(); 44 | } 45 | get interval() { 46 | return this._interval_ms; 47 | } 48 | set interval(ms) { 49 | console.assert(ms > 0); 50 | this._interval_ms = ms; 51 | } 52 | get running() { 53 | return this._interval !== null || this._idle !== null; 54 | } 55 | }; 56 | 57 | -------------------------------------------------------------------------------- /src/engines/synch.mjs: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'lodash'; 3 | import FastPriorityQueue from 'fastpriorityqueue'; 4 | import * as help from '../help.mjs'; 5 | import { BaseEngine } from './base.mjs'; 6 | 7 | export class SynchEngine extends BaseEngine { 8 | constructor(graph, {cells}) { 9 | super(graph); 10 | this._queue = new Map(); 11 | this._pq = new FastPriorityQueue(); 12 | this._tick = 0; 13 | this._cells = cells; 14 | this._monitorChecks = new Map(); 15 | this._alarms = new Map(); 16 | this._alarmQueue = new Map(); 17 | this._addGraph(graph); 18 | } 19 | get hasPendingEvents() { 20 | return this._queue.size > 0; 21 | } 22 | get tick() { 23 | return this._tick; 24 | } 25 | shutdown() { 26 | this.stopListening(); 27 | } 28 | _updateSubcircuit(gate, sigs, prevSigs = {}) { 29 | if (!sigs) sigs = gate.get("inputSignals"); 30 | const iomap = gate.get('circuitIOmap'); 31 | for (const [port, sig] of Object.entries(sigs)) { 32 | if (prevSigs[port] && sig.eq(prevSigs[port])) continue; 33 | const input = gate.get('graph').getCell(iomap[port]); 34 | console.assert(input.isInput); 35 | input._setInput(sig); 36 | } 37 | } 38 | _addGate(graph, gate) { 39 | super._addGate(graph, gate); 40 | this._enqueue(gate); 41 | if (gate instanceof this._cells.Subcircuit) 42 | this._updateSubcircuit(gate); 43 | } 44 | _addGraph(graph) { 45 | this.listenTo(graph, 'manualMemChange', (gate) => { 46 | this._enqueue(gate); 47 | }); 48 | this.listenTo(graph, 'change:constantCache', (gate) => { 49 | this._enqueue(gate); 50 | }); 51 | this.listenTo(graph, 'change:inputSignals', (gate, sigs) => { 52 | const prevSigs = gate.previous("inputSignals"); 53 | if (help.eqSigs(sigs, prevSigs)) return; 54 | if (gate instanceof this._cells.Subcircuit) { 55 | this._updateSubcircuit(gate, sigs, prevSigs); 56 | } 57 | if (gate instanceof this._cells.Output && gate.get('mode') == 0) { 58 | const subcir = gate.graph.get('subcircuit'); 59 | if (subcir != true) { 60 | console.assert(subcir != null); 61 | const port = gate.get('net'); 62 | const signals = _.clone(subcir.get('outputSignals')); 63 | signals[port] = gate.getOutput(); 64 | subcir.set('outputSignals', signals); 65 | } 66 | } 67 | this._enqueue(gate); 68 | }); 69 | super._addGraph(graph); 70 | } 71 | _enqueue(gate) { 72 | const k = (this._tick + gate.get('propagation')) | 0; 73 | const sq = (() => { 74 | const q = this._queue.get(k); 75 | if (q !== undefined) return q; 76 | const q1 = new Map(); 77 | this._queue.set(k, q1); 78 | this._pq.add(k); 79 | return q1; 80 | })(); 81 | sq.set(gate, gate.get('inputSignals')); 82 | } 83 | updateGatesNext() { 84 | const k = this._pq.poll() | 0; 85 | console.assert(k >= this._tick); 86 | this._tick = k; 87 | const q = this._queue.get(k); 88 | let count = 0; 89 | while (q.size) { 90 | const [gate, args] = q.entries().next().value; 91 | q.delete(gate); 92 | if (gate instanceof this._cells.Subcircuit) continue; 93 | if (gate instanceof this._cells.Input) continue; 94 | if (gate instanceof this._cells.Output) continue; 95 | const graph = gate.graph; 96 | if (!graph) continue; 97 | const newOutputSignals = gate.operation(args); 98 | if ('_clock_hack' in newOutputSignals) { 99 | delete newOutputSignals['_clock_hack']; 100 | this._enqueue(gate); 101 | } 102 | gate.set('outputSignals', newOutputSignals); 103 | count++; 104 | } 105 | this._queue.delete(k); 106 | this._tick = (k + 1) | 0; 107 | this._checkMonitors(); 108 | this.trigger('postUpdateGates', k, count); 109 | return Promise.resolve(count); 110 | } 111 | updateGates() { 112 | if (this._pq.peek() == this._tick) return this.updateGatesNext(); 113 | else { 114 | const k = this._tick | 0; 115 | this._tick = (k + 1) | 0; 116 | this._checkMonitors(); 117 | this.trigger('postUpdateGates', k, 0); 118 | return Promise.resolve(0); 119 | } 120 | } 121 | synchronize() { 122 | return Promise.resolve(); 123 | } 124 | start() { 125 | throw new Error("start() not supported"); 126 | } 127 | startFast() { 128 | throw new Error("startFast() not supported"); 129 | } 130 | stop() { 131 | return Promise.resolve() 132 | } 133 | get interval() { 134 | throw new Error("interval not supported"); 135 | } 136 | set interval(ms) { 137 | throw new Error("interval not supported"); 138 | } 139 | get running() { 140 | return false; 141 | } 142 | observeGraph(graph) { 143 | } 144 | unobserveGraph(graph) { 145 | } 146 | monitor(gate, port, callback, {triggerValues, stopOnTrigger, oneShot}) { 147 | const cb = (gate, sigs) => { 148 | const sig = sigs[port]; 149 | this._monitorChecks.set(cb, {gate, sig, cb, callback, triggerValues, stopOnTrigger, oneShot}); 150 | }; 151 | gate.on('change:outputSignals', cb); 152 | if (triggerValues == undefined) 153 | callback(this._tick, gate.get('outputSignals')[port]); 154 | return { gate: gate, callback: cb }; 155 | } 156 | unmonitor(monitorId) { 157 | monitorId.gate.off('change:outputSignals', monitorId.callback); 158 | } 159 | alarm(tick, callback, {stopOnAlarm}) { 160 | console.assert(tick > this._tick); 161 | const cb = () => { 162 | this.unalarm(cb); 163 | callback(); 164 | }; 165 | this._alarms.set(cb, { tick, stopOnAlarm }); 166 | if (!this._alarmQueue.has(tick)) 167 | this._alarmQueue.set(tick, new Set()); 168 | this._alarmQueue.get(tick).add(cb); 169 | this._pq.add(tick-1); 170 | if (!this._queue.has(tick-1)) 171 | this._queue.set(tick-1, new Map()); 172 | return cb; 173 | } 174 | unalarm(alarmId) { 175 | const { tick } = this._alarms.get(alarmId); 176 | this._alarmQueue.get(tick).delete(alarmId); 177 | if (this._alarmQueue.get(tick).size == 0) 178 | this._alarmQueue.delete(tick); 179 | this._alarms.delete(alarmId); 180 | } 181 | _checkMonitors() { 182 | for (const {gate, sig, cb, callback, triggerValues, stopOnTrigger, oneShot} of this._monitorChecks.values()) { 183 | let triggered = true; 184 | if (triggerValues != undefined) 185 | triggered = triggerValues.some((triggerValue) => sig.eq(triggerValue)); 186 | if (triggered) { 187 | if (oneShot) gate.off('change:outputSignals', cb); 188 | const ret = callback(this._tick, sig); 189 | if (ret && stopOnTrigger) this.stop(); 190 | } 191 | } 192 | this._monitorChecks = new Map(); 193 | if (this._alarmQueue.get(this._tick)) { 194 | for (const cb of this._alarmQueue.get(this._tick)) { 195 | const { stopOnAlarm } = this._alarms.get(cb); 196 | const ret = cb(); 197 | if (ret && stopOnAlarm) this.stop(); 198 | } 199 | this._alarmQueue.delete(this._tick); 200 | } 201 | } 202 | }; 203 | 204 | -------------------------------------------------------------------------------- /src/engines/worker.mjs: -------------------------------------------------------------------------------- 1 | 2 | import _ from 'lodash'; 3 | import { BaseEngine } from './base.mjs'; 4 | import { Vector3vl } from '3vl'; 5 | import * as cells from '../cells.mjs'; 6 | import Worker from 'web-worker'; 7 | 8 | export class WorkerEngine extends BaseEngine { 9 | constructor(graph, { workerURL }) { 10 | super(graph); 11 | this._running = false; 12 | this._tickCache = 0; 13 | this._pendingEventsCache = false; 14 | this._observers = Object.create(null); 15 | this._graphs = Object.create(null); 16 | this._monitors = Object.create(null); 17 | this._promises = Object.create(null); 18 | this._alarms = Object.create(null); 19 | this._uniqueCounter = 0; 20 | this._worker = workerURL ? new Worker(workerURL) : new Worker(new URL('./worker-worker.mjs', import.meta.url)); 21 | this._worker.onmessage = (e) => this._handleMessage(e.data); 22 | this.interval = 10; 23 | this._addGraph(this._graph); 24 | } 25 | _addGate(graph, gate) { 26 | const params = gate.getGateParams(); 27 | const ports = gate.getPorts().map(({id, dir, bits}) => ({id, dir, bits})); 28 | this._worker.postMessage({ type: 'addGate', args: [graph.cid, gate.id, params, ports, gate.get('inputSignals'), gate.get('outputSignals') ] }); 29 | super._addGate(graph, gate); 30 | if (gate instanceof cells.Subcircuit) { 31 | this._worker.postMessage({ 32 | type: 'addSubcircuit', 33 | args: [graph.cid, gate.id, gate.get('graph').cid, gate.get('circuitIOmap')] 34 | }); 35 | } 36 | if (gate instanceof cells.Input && gate.get('mode') != 0) { 37 | this.listenTo(gate, 'change:outputSignals', (gate, sigs) => { 38 | const prevSigs = gate.previous("outputSignals"); 39 | if (prevSigs.out.eq(sigs.out)) return; 40 | this._worker.postMessage({ 41 | type: 'changeInput', 42 | args: [graph.cid, gate.id, sigs.out] 43 | }); 44 | }); 45 | } 46 | if (gate instanceof cells.Memory) { 47 | this.listenTo(gate, 'memChange', (addr, data) => { 48 | gate.memdata.set(addr, data); 49 | }); 50 | this.listenTo(gate, 'manualMemChange', (gate, addr, data) => { 51 | this._worker.postMessage({ type: 'manualMemChange', args: [gate.graph.cid, gate.id, addr, data] }); 52 | }); 53 | } 54 | for (const paramName of gate._gateParams) { 55 | if (gate._unsupportedPropChanges.includes(paramName) || gate._presentationParams.includes(paramName)) 56 | continue; 57 | this.listenTo(gate, 'change:' + paramName, (gate, val) => { 58 | this._worker.postMessage({ 59 | type: 'changeParam', 60 | args: [graph.cid, gate.id, paramName, val] 61 | }); 62 | }); 63 | } 64 | } 65 | _addLink(graph, link) { 66 | if (!link.get('warning') && link.get('source').id && link.get('target').id) 67 | this._worker.postMessage({ type: 'addLink', args: [graph.cid, link.id, link.get('source'), link.get('target')] }); 68 | super._addLink(graph, link); 69 | } 70 | _addGraph(graph) { 71 | this._observers[graph.cid] = 0; 72 | this._graphs[graph.cid] = graph; 73 | this._worker.postMessage({ type: 'addGraph', args: [graph.cid]}); 74 | super._addGraph(graph); 75 | } 76 | _removeGate(graph, gate) { 77 | this._worker.postMessage({ type: 'removeGate', args: [graph.cid, gate.id] }); 78 | super._removeGate(graph, link); 79 | this.stopListening(gate); 80 | } 81 | _removeLink(graph, link) { 82 | if (!link.get('warning') && link.get('source').id && link.get('target').id) 83 | this._worker.postMessage({ type: 'removeLink', args: [graph.cid, link.id] }); 84 | super._removeLink(graph, link); 85 | } 86 | _changeLinkSource(graph, link, src, prevSrc) { 87 | super._changeLinkSource(graph, link, src, prevSrc); 88 | if (link.get('warning') || !link.get('target').id) return; 89 | if (src.id && !prevSrc.id) 90 | this._worker.postMessage({ type: 'addLink', args: [graph.cid, link.id, link.get('source'), link.get('target')] }); 91 | if (prevSrc.id && !src.id) 92 | this._worker.postMessage({ type: 'removeLink', args: [graph.cid, link.id] }); 93 | } 94 | _changeLinkTarget(graph, link, end, prevEnd) { 95 | super._changeLinkTarget(graph, link, end, prevEnd); 96 | if (link.get('warning') || !link.get('source').id) return; 97 | if (end.id && !prevEnd.id) 98 | this._worker.postMessage({ type: 'addLink', args: [graph.cid, link.id, link.get('source'), link.get('target')] }); 99 | if (prevEnd.id && !end.id) 100 | this._worker.postMessage({ type: 'removeLink', args: [graph.cid, link.id] }); 101 | } 102 | _changeLinkWarning(graph, link, warn, prevWarn) { 103 | super._changeLinkWarning(graph, link, warn, prevWarn); 104 | if (!link.get('source').id || !link.get('target').id) return; 105 | if (!warn && prevWarn) 106 | this._worker.postMessage({ type: 'addLink', args: [graph.cid, link.id, link.get('source'), link.get('target')] }); 107 | if (!prevWarn && warn) 108 | this._worker.postMessage({ type: 'removeLink', args: [graph.cid, link.id] }); 109 | } 110 | get hasPendingEvents() { 111 | return this._pendingEventsCache; 112 | } 113 | get tick() { 114 | return this._tickCache; 115 | } 116 | shutdown() { 117 | this._worker.terminate(); 118 | } 119 | synchronize() { 120 | const [reqid, promise] = this._generatePromise(); 121 | this._worker.postMessage({ type: 'ping', args: [reqid, true] }); 122 | return promise; 123 | } 124 | updateGatesNext({ synchronous = false } = {}) { 125 | if (this._running) 126 | throw new Error("updateGatesNext while running"); 127 | const [reqid, promise] = this._generatePromise(); 128 | this._worker.postMessage({ type: 'updateGatesNext', args: [reqid, synchronous] }); 129 | return promise; 130 | } 131 | updateGates({ synchronous = false } = {}) { 132 | if (this._running) 133 | throw new Error("updateGates while running"); 134 | const [reqid, promise] = this._generatePromise(); 135 | this._worker.postMessage({ type: 'updateGates', args: [reqid, synchronous] }); 136 | return promise; 137 | } 138 | start() { 139 | if (this.running) 140 | throw new Error("start while running"); 141 | this._worker.postMessage({ type: 'start' }); 142 | this._running = true; 143 | this.trigger('changeRunning'); 144 | } 145 | startFast() { 146 | if (this.running) 147 | throw new Error("startFast while running"); 148 | this._worker.postMessage({ type: 'startFast' }); 149 | this._running = 'fast'; 150 | this.trigger('changeRunning'); 151 | } 152 | stop({ synchronous = false } = {}) { 153 | if (!this._running) return; 154 | const [reqid, promise] = this._generatePromise(); 155 | this._worker.postMessage({ type: 'stop', args: [reqid, synchronous] }); 156 | this._running = false; 157 | this.trigger('changeRunning'); 158 | return promise; 159 | } 160 | get interval() { 161 | return this._interval; 162 | } 163 | set interval(ms) { 164 | this._interval = ms; 165 | this._worker.postMessage({ type: 'interval', arg: ms }); 166 | } 167 | get running() { 168 | return this._running != false; 169 | } 170 | observeGraph(graph) { 171 | this._observers[graph.cid] += 1; 172 | if (this._observers[graph.cid] == 1) 173 | this._worker.postMessage({ type: 'observeGraph', arg: graph.cid }); 174 | } 175 | unobserveGraph(graph) { 176 | this._observers[graph.cid] -= 1; 177 | if (this._observers[graph.cid] == 0) 178 | this._worker.postMessage({ type: 'unobserveGraph', arg: graph.cid }); 179 | } 180 | monitor(gate, port, callback, options) { 181 | const monitorId = this._generateUniqueId(); 182 | this._monitors[monitorId] = callback; 183 | this._worker.postMessage({ type: 'monitor', args: [gate.graph.cid, gate.id, port, monitorId, options] }); 184 | return monitorId; 185 | } 186 | unmonitor(monitorId) { 187 | if (!(monitorId in this._monitors)) return; 188 | this._worker.postMessage({ type: 'unmonitor', arg: monitorId }); 189 | delete this._monitors[monitorId]; 190 | } 191 | alarm(tick, callback, options) { 192 | console.assert(tick > this._tickCache); 193 | const alarmId = this._generateUniqueId(); 194 | this._alarms[alarmId] = callback; 195 | this._worker.postMessage({ type: 'alarm', args: [tick, alarmId, options] }); 196 | return alarmId; 197 | } 198 | unalarm(alarmId) { 199 | if (!(alarmId in this._alarms)) return; 200 | this._worker.postMessage({ type: 'unalarm', arg: alarmId }); 201 | delete this._alarms[alarmId]; 202 | } 203 | _handleMessage(msg) { 204 | const name = '_handle_' + msg.type; 205 | if ('arg' in msg) 206 | this[name](msg.arg); 207 | else if ('args' in msg) 208 | this[name].apply(this, msg.args); 209 | else 210 | this[name](); 211 | } 212 | _handle_update(tick, pendingEvents, updates) { 213 | let changeRunning = this._pendingEventsCache != pendingEvents; 214 | this._tickCache = tick; 215 | this._pendingEventsCache = pendingEvents; 216 | for (const [graphId, gateId, vals] of updates) { 217 | const gate = this._findGateByIds(graphId, gateId); 218 | if (gate === undefined) continue; 219 | const newOutputs = {}; 220 | for (const [port, val] of Object.entries(vals)) 221 | newOutputs[port] = Vector3vl.fromClonable(val); 222 | _.defaults(newOutputs, gate.get('outputSignals')); 223 | gate.set('outputSignals', newOutputs); 224 | } 225 | this.trigger('postUpdateGates', tick); 226 | if (changeRunning) this.trigger('changeRunning'); 227 | } 228 | _handle_stopped(tick) { 229 | this._running = false; 230 | this._pendingEventsCache = false; 231 | this._tickCache = tick; 232 | this.trigger('changeRunning'); 233 | } 234 | _handle_gateTrigger(graphId, gateId, event, args) { 235 | if (event == "memChange") 236 | args[1] = Vector3vl.fromClonable(args[1]); 237 | const gate = this._findGateByIds(graphId, gateId); 238 | if (gate === undefined) return; 239 | gate.trigger(event, ...args); 240 | } 241 | _handle_gateSet(graphId, gateId, name, value) { 242 | const gate = this._findGateByIds(graphId, gateId); 243 | if (gate === undefined) return; 244 | gate.set(name, value); 245 | } 246 | _handle_monitorValue(monitorId, tick, sig, stopped, oneShot) { 247 | const callback = this._monitors[monitorId]; 248 | if (callback == undefined) return; 249 | if (oneShot) delete this._monitors[monitorId]; 250 | const ret = callback(tick, Vector3vl.fromClonable(sig)); 251 | if (stopped) { 252 | if (ret) { 253 | this._running = false; 254 | this.trigger('changeRunning'); 255 | } else if (this._running) 256 | this._worker.postMessage({ type: this._running == 'fast' ? 'startFast' : 'start' }); 257 | } 258 | } 259 | _handle_alarmReached(alarmId, tick, stopped) { 260 | const callback = this._alarms[alarmId]; 261 | if (callback == undefined) return; 262 | delete this._alarms[alarmId]; 263 | const ret = callback(); 264 | if (stopped) { 265 | if (ret) { 266 | this._running = false; 267 | this.trigger('changeRunning'); 268 | } else if (this._running) 269 | this._worker.postMessage({ type: this._running == 'fast' ? 'startFast' : 'start' }); 270 | } 271 | } 272 | _handle_ack(reqid, response) { 273 | this._resolvePromise(reqid, response); 274 | } 275 | _findGateByIds(graphId, gateId) { 276 | const graph = this._graphs[graphId]; 277 | if (graph === undefined) return undefined; 278 | return graph.getCell(gateId); 279 | } 280 | _generateUniqueId() { 281 | return this._uniqueCounter++; 282 | } 283 | _generatePromise() { 284 | const reqid = this._generateUniqueId(); 285 | return [reqid, new Promise((resolve) => { this._promises[reqid] = resolve; })]; 286 | } 287 | _resolvePromise(reqid, value) { 288 | if (!this._promises[reqid]) { 289 | console.warn("Missing promise", reqid); 290 | return; 291 | } 292 | this._promises[reqid](value); 293 | delete this._promises[reqid]; 294 | } 295 | }; 296 | 297 | -------------------------------------------------------------------------------- /src/help.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { Vector3vl, Display3vlWithRegex, Display3vl } from '3vl'; 4 | 5 | const controlCodes20 = [ 6 | 'NUL', 'SOH', 'STX', 'ETX', 'EOT', 'ENQ', 'ACK', 'BEL', 7 | 'BS', 'HT', 'LF', 'VT', 'FF', 'CR', 'SO', 'SI', 8 | 'DLE', 'DC1', 'DC2', 'DC3', 'DC4', 'NAK', 'SYN', 'ETB', 9 | 'CAN', 'EM', 'SUB', 'ESC', 'FS', 'GS', 'RS', 'US', 10 | 'SP', 'DEL']; 11 | 12 | export class Display3vlASCII extends Display3vlWithRegex { 13 | constructor() { 14 | super('[\x20-\x7e\xa0-\xff\ufffd\u2400-\u2421]|' + controlCodes20.join('|')) 15 | } 16 | get name() { 17 | return "ascii"; 18 | } 19 | get sort() { 20 | return 1; 21 | } 22 | can(kind, bits) { 23 | return bits == 7 || bits == 8; 24 | } 25 | read(data, bits) { 26 | if (data.length == 1) { 27 | const code = data.charCodeAt(0); 28 | if (code == 0xfffd) return Vector3vl.xes(bits); 29 | if (code == 0x2421) return Vector3vl.fromHex("7f", bits); 30 | if (code >= 0x2400 && code <= 0x2420) 31 | return Vector3vl.fromHex((code - 0x2400).toString(16), bits); 32 | return Vector3vl.fromHex(code.toString(16), bits); 33 | } else { 34 | const code = controlCodes20.indexOf(data); 35 | if (code < 0) return Vector3vl.xes(bits); 36 | if (code == 0x21) return Vector3vl.fromHex("7f", bits); 37 | return Vector3vl.fromHex(code.toString(16), bits); 38 | } 39 | } 40 | show(data) { 41 | if (!data.isFullyDefined) return "\ufffd"; 42 | const code = parseInt(data.toHex(), 16); 43 | if (code <= 0x20) { 44 | return String.fromCharCode(0x2400 + code); 45 | } 46 | if (code == 0x7f) return "\u2421"; 47 | if (code > 0x7f && code < 0xa0) { 48 | return "\ufffd"; 49 | } 50 | return String.fromCharCode(code); 51 | } 52 | size(bits) { 53 | return 1; 54 | } 55 | } 56 | 57 | 58 | export function baseSelectMarkupHTML(display3vl, bits, base) { 59 | const markup = display3vl.usableDisplays('read', bits) 60 | .map(n => ''); 61 | return ''; 62 | }; 63 | 64 | export function eqSigs(sigs1, sigs2) { 65 | for (const k in sigs2) { 66 | if (!sigs1[k].eq(sigs2[k])) return false; 67 | } 68 | return true; 69 | }; 70 | 71 | -------------------------------------------------------------------------------- /src/index.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import 'babel-polyfill'; 4 | import dagre from 'dagre'; 5 | import graphlib from 'graphlib'; 6 | import * as joint from 'jointjs'; 7 | import _ from 'lodash'; 8 | import $ from 'jquery'; 9 | import Backbone from 'backbone'; 10 | import { Vector3vl } from '3vl'; 11 | import 'jquery-ui/ui/widgets/dialog.js'; 12 | import 'jquery-ui/themes/base/all.css'; 13 | import * as cells from './cells.mjs'; 14 | import * as engines from './engines.mjs'; 15 | import * as tools from './tools.mjs'; 16 | import * as transform from './transform.mjs'; 17 | import { HeadlessCircuit, getCellType } from './circuit.mjs'; 18 | import { BrowserSynchEngine } from './engines/browsersynch.mjs'; 19 | import { MonitorView, Monitor } from './monitor.mjs'; 20 | import { IOPanelView } from './iopanel.mjs'; 21 | import { elk_layout } from './elkjs.mjs'; 22 | import './style.css'; 23 | 24 | // polyfill ResizeObserver for e.g. Firefox ESR 68.8 25 | // this line and the node-module might be removed as soon as ResizeObserver is widely supported 26 | // see https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#Browser_compatibility 27 | import ResizeObserver from 'resize-observer-polyfill'; 28 | 29 | export { HeadlessCircuit, getCellType, cells, tools, engines, transform, MonitorView, Monitor, IOPanelView }; 30 | 31 | export const paperOptions = { 32 | async: true, 33 | sorting: joint.dia.Paper.sorting.APPROX, //needed for async paper, see https://github.com/clientIO/joint/issues/1320 34 | width: 100, height: 100, gridSize: 5, 35 | magnetThreshold: 'onleave', 36 | snapLinks: true, 37 | linkPinning: false, 38 | markAvailable: true, 39 | defaultLink: new cells.Wire, 40 | defaultConnectionPoint: { name: 'anchor' }, 41 | defaultRouter: { 42 | name: 'metro', 43 | args: { 44 | startDirections: ['right'], 45 | endDirections: ['left'], 46 | maximumLoops: 200, 47 | step: 2.5 48 | } 49 | }, 50 | defaultConnector: { 51 | name: 'rounded', 52 | args: { radius: 10 } 53 | }, 54 | cellViewNamespace: cells, 55 | validateConnection(vs, ms, vt, mt, e, vl) { 56 | if (e === 'target') { 57 | if (!mt) return false; 58 | const pt = vt.model.getPort(vt.findAttribute('port', mt)); 59 | if (typeof pt !== 'object' || pt.dir !== 'in' || pt.bits !== vl.model.get('bits')) 60 | return false; 61 | const link = this.model.getConnectedLinks(vt.model).find((l) => 62 | l.id !== vl.model.id && 63 | l.get('target').id === vt.model.id && 64 | l.get('target').port === vt.findAttribute('port', mt) 65 | ); 66 | return !link; 67 | } else if (e === 'source') { 68 | if (!ms) return false; 69 | const ps = vs.model.getPort(vs.findAttribute('port', ms)); 70 | if (typeof ps !== 'object' || ps.dir !== 'out' || ps.bits !== vl.model.get('bits')) 71 | return false; 72 | return true; 73 | } 74 | } 75 | }; 76 | 77 | const defaultSubcircuitButtons = [ 78 | { 79 | id: "zoomOut", 80 | hidden: false, 81 | buttonText: "–", 82 | callback: ({circuit, model, paper}) => { 83 | const newZoom = model.get('zoomLevel') - 1; 84 | circuit.scaleAndRefreshPaper(paper, newZoom); 85 | model.set("zoomLevel", newZoom); 86 | } 87 | }, 88 | { 89 | id: "zoomIn", 90 | hidden: false, 91 | buttonText: "+", 92 | callback: ({circuit, model, paper}) => { 93 | const newZoom = model.get('zoomLevel') + 1; 94 | circuit.scaleAndRefreshPaper(paper, newZoom); 95 | model.set("zoomLevel", newZoom); 96 | } 97 | } 98 | ]; 99 | 100 | export class Circuit extends HeadlessCircuit { 101 | constructor(data, { windowCallback = Circuit.prototype._defaultWindowCallback, layoutEngine = "elkjs", subcircuitButtons = [], ...options } = {}) { 102 | if (!options.engine) options.engine = BrowserSynchEngine; 103 | super(data, options); 104 | this._layoutEngine = layoutEngine 105 | this._windowCallback = windowCallback; 106 | this._subcircuitButtons = this._mergeSubcircuitButtons(subcircuitButtons); 107 | this.listenTo(this._engine, 'changeRunning', () => { 108 | this.trigger('changeRunning'); 109 | }); 110 | } 111 | _mergeSubcircuitButtons(buttons = []) { 112 | const res = new Map(); 113 | for (const button of defaultSubcircuitButtons.concat(buttons)) { 114 | if (button?.hidden) { 115 | res.delete(button.id); 116 | } else { 117 | res.set(button.id, button); 118 | } 119 | } 120 | return Array.from(res.values()); 121 | } 122 | _defaultWindowCallback(type, div, closingCallback) { 123 | const maxWidth = () => $(window).width() * 0.9; 124 | const maxHeight = () => $(window).height() * 0.9; 125 | function fixSize() { 126 | if (div.width() > maxWidth()) 127 | div.dialog("option", "width", maxWidth()); 128 | if (div.height() > maxHeight()) 129 | div.dialog("option", "height", maxHeight()); 130 | } 131 | const observer = new ResizeObserver(fixSize); 132 | observer.observe(div.get(0)); 133 | const shutdownCallback = () => { div.dialog('close'); }; 134 | this.listenToOnce(this, 'shutdown', shutdownCallback); 135 | const dialog = div.dialog({ 136 | width: 'auto', 137 | height: 'auto', 138 | maxWidth: $(window).width() * 0.9, 139 | maxHeight: $(window).height() * 0.9, 140 | resizable: type !== "Memory", 141 | close: () => { 142 | this.stopListening(this, 'shutdown', shutdownCallback); 143 | closingCallback(); 144 | observer.disconnect(); 145 | } 146 | }); 147 | } 148 | displayOn(elem) { 149 | return this._makePaper(elem, this._graph); 150 | } 151 | scaleAndRefreshPaper(paper, scale) { 152 | paper.scale(Math.pow(1.1, scale)); 153 | 154 | const graph = paper.model; 155 | paper.freeze(); 156 | graph.resetCells(graph.getCells()); 157 | paper.unfreeze(); 158 | } 159 | _makePaper(elem, graph) { 160 | this._engine.observeGraph(graph); 161 | const opts = _.merge({ el: elem, model: graph }, paperOptions); 162 | const paper = new joint.dia.Paper(opts); 163 | paper.$el.addClass('djs'); 164 | paper.freeze(); 165 | // required for the paper to visualize the graph (jointjs bug?) 166 | graph.resetCells(graph.getCells()); 167 | // lazy graph layout 168 | if (!graph.get('laid_out')) { 169 | if (this._layoutEngine == "dagre") { 170 | joint.layout.DirectedGraph.layout(graph, { 171 | nodeSep: 20, 172 | edgeSep: 0, 173 | rankSep: 110, 174 | rankDir: "LR", 175 | setPosition: (element, position) => { 176 | element.setLayoutPosition({ 177 | x: position.x - position.width/2, 178 | y: position.y - position.height/2, 179 | width: position.width, 180 | height: position.height 181 | }); 182 | }, 183 | exportElement: (element) => { 184 | return element.getLayoutSize(); 185 | }, 186 | dagre: dagre, 187 | graphlib: graphlib 188 | }); 189 | } else if (this._layoutEngine == "elkjs") { 190 | elk_layout(graph); 191 | } 192 | graph.set('laid_out', true); 193 | } 194 | paper.listenTo(this, 'display:add', () => { 195 | // a very inefficient way to refresh numbase dropdowns 196 | // TODO: a better method 197 | paper.freeze(); 198 | graph.resetCells(graph.getCells()); 199 | paper.unfreeze(); 200 | }); 201 | this.listenTo(paper, 'render:done', () => { 202 | paper.fitToContent({ padding: 30, allowNewOrigin: 'any' }); 203 | }); 204 | paper.unfreeze(); 205 | // subcircuit display 206 | const circuit = this; 207 | this.listenTo(paper, 'open:subcircuit', (model) => { 208 | const subcircuitModal = $('
', { 209 | title: model.get('celltype') + ' ' + model.get('label') 210 | }).appendTo('html > body'); 211 | 212 | // Create and set up paper 213 | const pdiv = $('
').appendTo(subcircuitModal); 214 | const graph = model.get('graph'); 215 | const paper = this._makePaper(pdiv, graph); 216 | paper.once('render:done', () => { 217 | this._windowCallback('Subcircuit', subcircuitModal, () => { 218 | this._engine.unobserveGraph(graph); 219 | paper.remove(); 220 | subcircuitModal.remove(); 221 | }); 222 | }); 223 | 224 | // Create buttons 225 | model.set("zoomLevel", 0); 226 | const buttonGroup = $('
') 227 | for (const button of this._subcircuitButtons) { 228 | $('') 229 | .append($('').text(button.buttonText)) 230 | .on('click', {circuit, model, paper}, (event) => button.callback(event.data)) 231 | .appendTo(buttonGroup); 232 | } 233 | buttonGroup.prependTo(subcircuitModal); 234 | }); 235 | this.listenTo(paper, 'open:memorycontent', (subcircuitModal, closeCallback) => { 236 | this._windowCallback('Memory', subcircuitModal, closeCallback); 237 | }); 238 | this.listenTo(paper, 'open:fsm', (subcircuitModal, closeCallback) => { 239 | this._windowCallback('FSM', subcircuitModal, closeCallback); 240 | }); 241 | paper.fixed = function(fixed) { 242 | this.setInteractivity(!fixed); 243 | this.$el.toggleClass('fixed', fixed); 244 | }; 245 | this.trigger('new:paper', paper); 246 | return paper; 247 | } 248 | }; 249 | 250 | -------------------------------------------------------------------------------- /src/iopanel.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import joint from 'jointjs'; 4 | import _ from 'lodash'; 5 | import $ from 'jquery'; 6 | import Backbone from 'backbone'; 7 | import * as help from './help.mjs'; 8 | import * as cells from './cells.mjs'; 9 | import { Vector3vl } from '3vl'; 10 | 11 | let uniq_cntr = 0; 12 | 13 | export class IOPanelView extends Backbone.View { 14 | initialize(args) { 15 | this._idnum = uniq_cntr++; 16 | this._inputPanelMarkup = args.inputPanelMarkup || '
'; 17 | this._outputPanelMarkup = args.outputPanelMarkup || '
'; 18 | this._baseSelectorMarkup = args.baseSelectorMarkup || help.baseSelectMarkupHTML; 19 | this._labelMarkup = args.labelMarkup || ''; 20 | this._buttonMarkup = args.buttonMarkup || ''; 21 | this._lampMarkup = args.lampMarkup || ''; 22 | this._rowMarkup = args.rowMarkup || '
'; 23 | this._colMarkup = args.colMarkup || '
'; 24 | this._inputMarkup = args.inputMarkup || ''; 25 | this.render(); 26 | this.listenTo(this.model._graph, 'add', this._handleAdd); 27 | this.listenTo(this.model._graph, 'remove', this._handleRemove); 28 | this.listenTo(this.model, "display:add", () => { this.render() }); 29 | } 30 | render() { 31 | // Disable the default action of submission since it will usually cause a page refresh. 32 | this.$el.html('
' + this._inputPanelMarkup + this._outputPanelMarkup + '
'); 33 | for (const element of this.model.getInputCells()) 34 | this._handleAddInput(element); 35 | for (const element of this.model.getOutputCells()) 36 | this._handleAddOutput(element); 37 | } 38 | shutdown() { 39 | this.$el.off(); 40 | this.stopListening(); 41 | } 42 | _id(text) { 43 | return 'iopanel-' + text + '-' + this._idnum; 44 | } 45 | _handleAdd(cell) { 46 | if (cell.isInput) this._handleAddInput(cell); 47 | else if (cell.isOutput) this._handleAddOutput(cell); 48 | } 49 | _addLabel(row, cell) { 50 | const label = $(this._labelMarkup) 51 | .appendTo(row); 52 | return label.find('label').addBack('label') 53 | .text(cell.get('net') || cell.get('label')); 54 | } 55 | _addLabelFor(row, cell) { 56 | return this._addLabel(row, cell) 57 | .attr('for', this._id(cell.id)); 58 | } 59 | _handleAddInput(cell) { 60 | const display3vl = this.model._display3vl; 61 | const row = $(this._rowMarkup) 62 | .appendTo(this.$('div[data-iopanel="input"]')); 63 | this._addLabelFor(row, cell); 64 | const col = $(this._colMarkup) 65 | .appendTo(row); 66 | if (cell.get('mode') == 1) { 67 | const ui = $(this._buttonMarkup) 68 | .appendTo(col); 69 | const inp = ui.find('input').addBack('input') 70 | .attr('id', this._id(cell.id)) 71 | .on('click', (evt) => { 72 | cell.setInput(Vector3vl.fromBool(evt.target.checked)); 73 | }); 74 | const updater = (cell, sigs) => { 75 | inp.prop("checked", sigs.out.isHigh); 76 | }; 77 | this.listenTo(cell, 'change:outputSignals', updater); 78 | updater(cell, cell.get('outputSignals')); 79 | } else { 80 | const ui = $(this._inputMarkup) 81 | .appendTo(col); 82 | let base = 'hex'; 83 | const base_sel = $(this._baseSelectorMarkup(display3vl, cell.get('bits'), base)) 84 | .appendTo(col); 85 | let sz = display3vl.size(base, cell.get('bits')); 86 | const bits = cell.get('bits'); 87 | const inp = ui.find('input').addBack('input') 88 | .prop('size', sz) 89 | .prop('maxlength', sz) 90 | .prop('pattern', display3vl.pattern(base)) 91 | .on('change', (e) => { 92 | if (!display3vl.validate(base, e.target.value, bits)) return; 93 | cell.setInput(display3vl.read(base, e.target.value, bits)); 94 | }); 95 | const updater = (cell, sigs) => { 96 | ui.val(display3vl.show(base, sigs.out)); 97 | }; 98 | this.listenTo(cell, 'change:outputSignals', updater); 99 | updater(cell, cell.get('outputSignals')); 100 | row.on('input', 'select[name=base]', (e) => { 101 | base = e.target.value; 102 | sz = display3vl.size(base, cell.get('bits')); 103 | inp.prop('size', sz) 104 | .prop('maxlength', sz) 105 | .prop('pattern', display3vl.pattern(base)); 106 | updater(cell, cell.get('outputSignals')); 107 | }); 108 | } 109 | } 110 | _handleAddOutput(cell) { 111 | const display3vl = this.model._display3vl; 112 | const row = $(this._rowMarkup) 113 | .appendTo(this.$('div[data-iopanel="input"]')); 114 | this._addLabel(row, cell); 115 | const col = $(this._colMarkup) 116 | .appendTo(row); 117 | if (cell.get('bits') == 1) { 118 | const ui = $(this._lampMarkup) 119 | .appendTo(col); 120 | const inp = ui.find('input').addBack('input') 121 | .prop('disabled', true); 122 | const updater = (cell, sigs) => { 123 | const val = cell.getOutput(); 124 | inp.prop("checked", val.isHigh); 125 | inp.prop("indeterminate", !val.isDefined); 126 | }; 127 | this.listenTo(cell, 'change:inputSignals', updater); 128 | updater(cell, cell.get('inputSignals')); 129 | } else { 130 | const ui = $(this._inputMarkup) 131 | .appendTo(col); 132 | let base = 'hex'; 133 | const base_sel = $(this._baseSelectorMarkup(display3vl, cell.get('bits'), base)) 134 | .appendTo(col); 135 | const sz = display3vl.size(base, cell.get('bits')); 136 | const inp = ui.find('input').addBack('input') 137 | .prop('disabled', true) 138 | .prop('size', sz); 139 | // .prop('maxlength', sz) 140 | // .prop('pattern', display3vl.pattern(base)); 141 | const updater = (cell, sigs) => { 142 | const val = cell.getOutput(); 143 | ui.val(display3vl.show(base, val)); 144 | }; 145 | this.listenTo(cell, 'change:inputSignals', updater); 146 | updater(cell, cell.get('inputSignals')); 147 | row.on('input', 'select[name=base]', (e) => { 148 | base = e.target.value; 149 | inp.prop('size', display3vl.size(base, cell.get('bits'))); 150 | updater(cell, cell.get('inputSignals')); 151 | }); 152 | } 153 | } 154 | _handleRemove(cell) { 155 | this.stopListening(cell); 156 | } 157 | }; 158 | 159 | -------------------------------------------------------------------------------- /src/monitor.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import joint from 'jointjs'; 4 | import _ from 'lodash'; 5 | import $ from 'jquery'; 6 | import Backbone from 'backbone'; 7 | import * as help from './help.mjs'; 8 | import { Vector3vl } from '3vl'; 9 | import { Waveform, drawWaveform, defaultSettings, extendSettings, calcGridStep } from 'wavecanvas'; 10 | import ResizeObserver from 'resize-observer-polyfill'; 11 | 12 | function getWireId(wire) { 13 | const hier = [wire.cid]; 14 | for (let sc = wire.graph.get('subcircuit'); sc != null; sc = sc.graph.get('subcircuit')) { 15 | hier.push(sc.cid); 16 | } 17 | hier.reverse(); 18 | return hier.join('.'); 19 | } 20 | 21 | function getWireName(wire) { 22 | const hier = []; 23 | if (wire.has('netname')) hier.push(wire.get('netname')); 24 | else { 25 | const source = wire.source(); 26 | hier.push(source.port); 27 | const cell = wire.graph.getCell(source.id); 28 | if (cell.has('label')) hier.push(cell.get('label')); 29 | else hier.push(source.id); 30 | } 31 | for (let sc = wire.graph.get('subcircuit'); sc != null; sc = sc.graph.get('subcircuit')) { 32 | if (sc.has('label')) hier.push(sc.get('label')); 33 | else hier.push(sc.id); 34 | } 35 | hier.reverse(); 36 | return hier.join('.'); 37 | } 38 | 39 | export class Monitor { 40 | constructor(circuit) { 41 | this._circuit = circuit; 42 | this._wires = new Map(); 43 | this.listenTo(this._circuit, 'new:paper', (paper) => this.attachTo(paper)); 44 | } 45 | attachTo(paper) { 46 | this.listenTo(paper, 'link:monitor', (linkView) => { 47 | this.addWire(linkView.model); 48 | }); 49 | } 50 | addWire(wire) { 51 | const wireid = getWireId(wire); 52 | if (this._wires.has(wireid)) return; 53 | const waveform = new Waveform(wire.get('bits')); 54 | const obj = {wire: wire, waveform: waveform, monitorId: undefined}; 55 | this._wires.set(wireid, obj); 56 | this.trigger('add', wire); 57 | obj.monitorId = this._circuit.monitorWire(wire, (tick, sig) => { this._handleChange(tick, wire, sig) }); 58 | } 59 | removeWire(wire) { 60 | if (typeof wire == 'string') wire = this._wires.get(wire).wire; 61 | this.trigger('remove', wire); 62 | const wireid = getWireId(wire); 63 | this._circuit.unmonitor(this._wires.get(wireid).monitorId); 64 | this._wires.delete(wireid); 65 | } 66 | getWires() { 67 | const ret = []; 68 | for (const wobj of this._wires.values()) ret.push(wobj.wire); 69 | return ret; 70 | } 71 | getWiresDesc() { 72 | return this.getWires().map(wire => { 73 | if (!wire.has('netname')) return; 74 | return { 75 | name: wire.get('netname'), 76 | path: wire.getWirePath(), 77 | bits: wire.get('bits') 78 | }; 79 | }).filter(x => x !== undefined); 80 | } 81 | loadWiresDesc(wd) { 82 | for (const w of wd) { 83 | const e = this._circuit.findWireByLabel(w.name, w.path); 84 | if (e && e.get('bits') == w.bits) this.addWire(e); 85 | } 86 | } 87 | _handleChange(tick, wire, signal) { 88 | this._wires.get(getWireId(wire)).waveform.push(tick, signal); 89 | this.trigger('change', wire, signal); 90 | } 91 | } 92 | 93 | _.extend(Monitor.prototype, Backbone.Events); 94 | 95 | export class MonitorView extends Backbone.View { 96 | initialize(args) { 97 | this._width = 800; 98 | this._settings = extendSettings(defaultSettings, {start: 0, pixelsPerTick: 5, gridStep: 1}); 99 | this._settingsFor = new Map(); 100 | this._live = true; 101 | this._autoredraw = false; 102 | this._idle = null; 103 | this._removeButtonMarkup = args.removeButtonMarkup || ''; 104 | this._baseSelectorMarkup = args.baseSelectorMarkup || help.baseSelectMarkupHTML; 105 | this._bitTriggerMarkup = args.bitTriggerMarkup || ''; 106 | this._busTriggerMarkup = args.busTriggerMarkup || ''; 107 | this.listenTo(this.model, 'add', this._handleAdd); 108 | this.listenTo(this.model, 'remove', this._handleRemove); 109 | this.listenTo(this.model._circuit, "display:add", () => { this.render() }); 110 | this.listenTo(this.model._circuit, 'postUpdateGates', (tick) => { 111 | if (this._live) this.start = tick - this._width / this._settings.pixelsPerTick; 112 | this._settings.present = tick; 113 | if (!this._idle) this._idle = requestIdleCallback(() => { 114 | this._drawAll(); 115 | this._idle = null; 116 | }, {timeout: 100}); 117 | }); 118 | this.render(); 119 | this._resizeObserver = new ResizeObserver(() => { 120 | this._canvasResize(); 121 | }); 122 | this._resizeObserver.observe(this.el); 123 | function evt_wireid(e) { 124 | return $(e.target).closest('tr').attr('wireid'); 125 | } 126 | const display3vl = this.model._circuit._display3vl; 127 | this.$el.on('click', 'button[name=remove]', (e) => { this.model.removeWire(evt_wireid(e)); }); 128 | this.$el.on('input', 'select[name=base]', (e) => { 129 | const base = e.target.value; 130 | const settings = this._settingsFor.get(evt_wireid(e)); 131 | settings.base = base; 132 | const row = $(e.target).closest('tr'); 133 | const trig = row.find('input[name=trigger]'); 134 | trig.attr('pattern', display3vl.pattern(base)); 135 | if (settings.trigger.length) 136 | trig.val(display3vl.show(base, settings.trigger[0])); 137 | this.trigger('change'); 138 | }); 139 | const handleTrigger = () => { 140 | return true; 141 | }; 142 | const setTrigger = (wireid, triggers) => { 143 | const settings = this._settingsFor.get(wireid); 144 | if (settings.triggerId) { 145 | this.model._circuit.unmonitor(settings.triggerId); 146 | settings.triggerId = null; 147 | } 148 | if (triggers.length > 0) { 149 | const wire = this.model._wires.get(wireid).wire; 150 | settings.triggerId = this.model._circuit.monitorWire(wire, handleTrigger, {triggerValues: triggers, stopOnTrigger: true}); 151 | } 152 | settings.trigger = triggers; 153 | } 154 | this.$el.on('input', 'select[name=trigger]', (e) => { 155 | const wireid = evt_wireid(e); 156 | switch (e.target.value) { 157 | case 'rising': setTrigger(wireid, [Vector3vl.one]); break; 158 | case 'falling': setTrigger(wireid, [Vector3vl.zero]); break; 159 | case 'risefall': setTrigger(wireid, [Vector3vl.one, Vector3vl.zero]); break; 160 | case 'undef': setTrigger(wireid, [Vector3vl.x]); break; 161 | default: setTrigger(wireid, []); 162 | } 163 | }); 164 | this.$el.on('change', 'input[name=trigger]', (e) => { 165 | const wireid = evt_wireid(e); 166 | const settings = this._settingsFor.get(wireid); 167 | const base = settings.base; 168 | const bits = this.model._wires.get(wireid).waveform.bits; 169 | if (e.target.value == "") { 170 | setTrigger(wireid, []); 171 | } else if (display3vl.validate(base, e.target.value, bits)) { 172 | const val = display3vl.read(base, e.target.value, bits); 173 | setTrigger(wireid, [val]); 174 | e.target.value = display3vl.show(base, val); 175 | } else { 176 | setTrigger(wireid, []); 177 | } 178 | }); 179 | this.listenTo(this, 'change', () => { if (this._autoredraw) this._drawAll() }); 180 | 181 | const dragImg = new Image(0,0); 182 | dragImg.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; 183 | let dragX, dragStart; 184 | const do_drag = (e) => { 185 | const offset = e.originalEvent.screenX - dragX; 186 | this.start = dragStart - offset / this._settings.pixelsPerTick; 187 | }; 188 | this.$el.on('dragstart', 'canvas', (e) => { 189 | const dt = e.originalEvent.dataTransfer; 190 | dt.setData('text/plain', 'dragging'); 191 | dt.setDragImage(dragImg, 0, 0); 192 | dragX = e.originalEvent.screenX; 193 | dragStart = this._settings.start; 194 | this.live = false; 195 | $(document).on('dragover', do_drag); 196 | }); 197 | this.$el.on('dragend', 'canvas', (e) => { 198 | $(document).off('dragover', do_drag); 199 | }); 200 | this.$el.on('wheel', 'canvas', (e) => { 201 | e.preventDefault(); 202 | const scaling = 2 ** Math.sign(e.originalEvent.deltaY); 203 | this.start += e.originalEvent.offsetX / this._settings.pixelsPerTick * (1 - 1 / scaling); 204 | this.pixelsPerTick *= scaling; 205 | }); 206 | } 207 | render() { 208 | this.$el.html('
'); 209 | for (const wobj of this.model._wires.values()) { 210 | this.$('table').append(this._handleAdd(wobj.wire)); 211 | } 212 | this._canvasResize(); 213 | return this; 214 | } 215 | shutdown() { 216 | this.$el.off(); 217 | if (this._resizeObserver) { 218 | this._resizeObserver.disconnect(); 219 | this._resizeObserver = undefined; 220 | } 221 | this.stopListening(); 222 | } 223 | get gridStep() { 224 | return calcGridStep(this._settings); 225 | } 226 | get autoredraw() { 227 | return this._autoredraw; 228 | } 229 | set autoredraw(val) { 230 | this._autoredraw = val; 231 | if (val) this._drawAll(); 232 | } 233 | get width() { 234 | return this._width; 235 | } 236 | get live() { 237 | return this._live; 238 | } 239 | set live(val) { 240 | if (this.live == val) return; 241 | this._live = val; 242 | this.trigger('change:live', val); 243 | this.trigger('change'); 244 | } 245 | get start() { 246 | return this._settings.start; 247 | } 248 | set start(val) { 249 | if (this._settings.start == val) return; 250 | this._settings.start = val; 251 | this.trigger('change:start', val); 252 | this.trigger('change'); 253 | } 254 | get pixelsPerTick() { 255 | return this._settings.pixelsPerTick; 256 | } 257 | set pixelsPerTick(val) { 258 | if (this._settings.pixelsPerTick == val) return; 259 | this._settings.pixelsPerTick = val; 260 | this.trigger('change:pixelsPerTick', val); 261 | this.trigger('change'); 262 | } 263 | _canvasResize() { 264 | this._width = Math.max(this.$el.width() - 300, 100); 265 | this.$('canvas').attr('width', this._width); 266 | this.trigger('change:width', this._width); 267 | this.trigger('change'); 268 | } 269 | _drawAll() { 270 | for (const wireid of this.model._wires.keys()) { 271 | this._draw(wireid); 272 | } 273 | } 274 | _draw(wireid) { 275 | const display3vl = this.model._circuit._display3vl; 276 | const canvas = this.$('tr[wireid="'+wireid+'"]').find('canvas'); 277 | const waveform = this.model._wires.get(wireid).waveform; 278 | drawWaveform(waveform, canvas[0].getContext('2d'), this._settingsFor.get(wireid), display3vl); 279 | } 280 | _handleAdd(wire) { 281 | const wireid = getWireId(wire); 282 | this._settingsFor.set(wireid, extendSettings(this._settings, {base: 'hex', trigger: [], triggerId: null})); 283 | this.$('table').append(this._createRow(wire)); 284 | } 285 | _handleRemove(wire) { 286 | const wireid = getWireId(wire); 287 | this.$('tr[wireid="'+wireid+'"]').remove(); 288 | const settings = this._settingsFor.get(wireid); 289 | if (settings.triggerId) 290 | this.model._circuit.unmonitor(settings.triggerId); 291 | this._settingsFor.delete(wireid); 292 | } 293 | _createRow(wire) { 294 | const wireid = getWireId(wire); 295 | const settings = this._settingsFor.get(wireid); 296 | const display3vl = this.model._circuit._display3vl; 297 | const base_sel = wire.get('bits') > 1 298 | ? (this._baseSelectorMarkup instanceof Function ? this._baseSelectorMarkup(display3vl, wire.get('bits'), settings.base) : this._baseSelectorMarkup) 299 | : ''; 300 | const trigger = wire.get('bits') > 1 ? this._busTriggerMarkup : this._bitTriggerMarkup; 301 | const row = $(''+base_sel+''+trigger+''+this._removeButtonMarkup+''); 302 | row.attr('wireid', wireid); 303 | row.children('td').first().text(getWireName(wire)); 304 | return row; 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | .joint-element .highlighted { 2 | outline: none; 3 | fill: #ecf0f1; 4 | stroke: #bdc3c7; 5 | cursor: crosshair; 6 | } 7 | 8 | .djs.fixed .joint-link { 9 | pointer-events: none; 10 | } 11 | .djs.fixed .joint-element { 12 | cursor: default; 13 | } 14 | 15 | 16 | 17 | /* .viewport is a node wrapping all diagram elements in the paper */ 18 | .joint-viewport { 19 | -webkit-user-select: none; 20 | -moz-user-select: none; 21 | user-select: none; 22 | } 23 | 24 | .joint-paper > svg, 25 | .joint-paper-background, 26 | .joint-paper-grid { 27 | position: absolute; 28 | top: 0; 29 | left: 0; 30 | right: 0; 31 | bottom: 0; 32 | } 33 | 34 | /* 35 | 1. IE can't handle paths without the `d` attribute for bounding box calculation 36 | 2. IE can't even handle 'd' attribute as a css selector (e.g path[d]) so the following rule will 37 | break the links rendering. 38 | 39 | path:not([d]) { 40 | display: none; 41 | } 42 | 43 | */ 44 | 45 | 46 | /* magnet is an element that can be either source or a target of a link */ 47 | .djs:not(.fixed) [magnet=true]:not(.joint-element) { 48 | cursor: crosshair; 49 | } 50 | .djs:not(.fixed) [magnet=true]:not(.joint-element):hover { 51 | opacity: .7; 52 | } 53 | 54 | /* 55 | 56 | Elements have CSS classes named by their types. E.g. type: basic.Rect has a CSS class "element basic Rect". 57 | This makes it possible to easilly style elements in CSS and have generic CSS rules applying to 58 | the whole group of elements. Each plugin can provide its own stylesheet. 59 | 60 | */ 61 | 62 | .joint-element { 63 | /* Give the user a hint that he can drag&drop the element. */ 64 | cursor: move; 65 | } 66 | 67 | .joint-element * { 68 | user-drag: none; 69 | } 70 | 71 | .joint-element .scalable * { 72 | /* The default behavior when scaling an element is not to scale the stroke in order to prevent the ugly effect of stroke with different proportions. */ 73 | vector-effect: non-scaling-stroke; 74 | } 75 | /* 76 | 77 | connection-wrap is a element of the joint.dia.Link that follows the .connection of that link. 78 | In other words, the `d` attribute of the .connection-wrap contains the same data as the `d` attribute of the 79 | .connection . The advantage of using .connection-wrap is to be able to catch pointer events 80 | in the neighborhood of the .connection . This is especially handy if the .connection is 81 | very thin. 82 | 83 | */ 84 | 85 | .marker-source, 86 | .marker-target { 87 | /* This makes the arrowheads point to the border of objects even though the transform: scale() is applied on them. */ 88 | vector-effect: non-scaling-stroke; 89 | } 90 | 91 | /* Paper */ 92 | .joint-paper { 93 | position: relative; 94 | } 95 | /* Paper */ 96 | 97 | /* Highlighting */ 98 | .joint-highlight-opacity { 99 | opacity: 0.3; 100 | } 101 | /* Highlighting */ 102 | 103 | 104 | /* foreignObject inside the elements (i.e joint.shapes.basic.TextBlock) */ 105 | .joint-element .fobj { 106 | overflow: hidden; 107 | } 108 | .joint-element .fobj body { 109 | background-color: transparent; 110 | margin: 0px; 111 | position: static; 112 | } 113 | .joint-element .fobj div { 114 | text-align: center; 115 | vertical-align: middle; 116 | display: table-cell; 117 | padding: 0px 5px 0px 5px; 118 | } 119 | 120 | /* Paper */ 121 | .joint-paper.joint-theme-dark { 122 | background-color: #18191b; 123 | } 124 | .joint-paper.joint-theme-default { 125 | background-color: #FFFFFF; 126 | } 127 | /* Paper */ 128 | 129 | body { 130 | font-family: 'verdana', sans-serif; 131 | font-weight: normal; 132 | font-style: normal; 133 | } 134 | 135 | .joint-element foreignObject { 136 | overflow: hidden; 137 | } 138 | 139 | .joint-element foreignObject body { 140 | background-color: transparent; 141 | display: block; 142 | margin: 0; 143 | position: fixed; 144 | width: 100%; 145 | height: 100%; 146 | cursor: initial; 147 | } 148 | 149 | .joint-element foreignObject body input, 150 | .joint-element foreignObject body select { 151 | pointer-events: all; 152 | } 153 | 154 | .joint-element foreignObject body input[type='text'], 155 | .joint-element foreignObject body input[type='number'], 156 | .joint-element foreignObject body select { 157 | font-size: 8pt; 158 | border: 1px solid gray; 159 | box-sizing: border-box; 160 | position: absolute; 161 | transform: translate(0, -50%); 162 | top: 50%; 163 | } 164 | 165 | .joint-element foreignObject body input[type='text'] { 166 | font-family: 'monospace'; 167 | width: 100%; 168 | } 169 | 170 | .joint-element foreignObject.tooltip body input[type='number'] { 171 | width: calc(100% - 10px); 172 | } 173 | 174 | .joint-element foreignObject body a { 175 | text-decoration: none; 176 | } 177 | 178 | .joint-element input.invalid, table.memeditor input.invalid { 179 | background: #ffaaaa; 180 | } 181 | 182 | .joint-element foreignObject.tooltip { 183 | visibility: hidden; 184 | opacity:0; 185 | transition:visibility 0s linear 0.5s,opacity 0.5s linear; 186 | } 187 | 188 | .joint-element foreignObject.tooltip:hover, 189 | g:hover > foreignObject.tooltip { 190 | visibility: visible; 191 | opacity:1; 192 | transition-delay:0s; 193 | } 194 | 195 | .joint-element foreignObject.tooltip body { 196 | border: 1px solid gray; 197 | box-sizing: border-box; 198 | background: white; 199 | padding: 5px; 200 | } 201 | 202 | div.wire_hover { 203 | position: fixed; 204 | pointer-events: none; 205 | background: white; 206 | border: 1px solid black; 207 | padding: 5px; 208 | font-size: 8pt; 209 | z-index: 200; 210 | } 211 | 212 | .joint-element .numvalue { 213 | font-family: monospace; 214 | font-size: 10px; 215 | } 216 | 217 | .joint-element text.numvalue { 218 | fill: black; 219 | text-anchor: middle; 220 | } 221 | 222 | table.monitor tr { 223 | padding: 0; margin: 0; 224 | font-size: 10px; 225 | } 226 | 227 | table.monitor td { 228 | padding: 0; margin: 0; 229 | } 230 | 231 | table.monitor { 232 | border-collapse: collapse; 233 | width: 100%; 234 | } 235 | 236 | table.monitor td.name { 237 | width: 100%; 238 | } 239 | 240 | table.monitor input[name=trigger], table.monitor select[name=trigger] { 241 | width: 5em; 242 | } 243 | 244 | canvas.wavecanvas { 245 | display: block; 246 | } 247 | 248 | table.memeditor { 249 | margin-top: 0.5em; 250 | } 251 | 252 | table.memeditor input { 253 | font-family: monospace; 254 | border: 1px solid #aaa; 255 | border-radius: 4px; 256 | padding: 0.2em 0.5em; 257 | } 258 | 259 | div[data-iopanel="input"] input, div[data-iopanel="output"] { 260 | font-family: monospace; 261 | } 262 | 263 | table.memeditor td:first-child { 264 | font-family: monospace; 265 | padding-right: 1em; 266 | } 267 | 268 | table.memeditor input.flash { 269 | animation-name: flash-animation; 270 | animation-duration: 1s; 271 | } 272 | 273 | table.memeditor input.isread { 274 | box-shadow: 0px 0px 5px green; 275 | } 276 | 277 | table.memeditor input.iswrite { 278 | box-shadow: 0px 0px 5px red; 279 | } 280 | 281 | table.memeditor input.iswrite.isread { 282 | box-shadow: 1px 1px 5px -1px red, 283 | -1px -1px 5px -1px green; 284 | } 285 | 286 | @keyframes flash-animation { 287 | from { background-color: yellow; } 288 | to { background-color: default; } 289 | } 290 | 291 | .joint-element circle.current_state { 292 | fill: yellow; 293 | } 294 | 295 | .joint-link path.next_trans, marker path.next_trans { 296 | stroke: #03c03c; 297 | fill: #03c03c; 298 | } 299 | 300 | /* jqueryui fix */ 301 | .ui-dialog, .ui-dialog-content { 302 | box-sizing: content-box; 303 | } 304 | -------------------------------------------------------------------------------- /src/tools.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as joint from 'jointjs'; 4 | import _ from 'lodash'; 5 | 6 | const circleArrowhead = { 7 | tagName: 'circle', 8 | attributes: { 9 | 'r': 7, 10 | 'fill': 'black', 11 | 'fill-opacity': 0.3, 12 | 'stroke': 'black', 13 | 'stroke-width': 2, 14 | 'cursor': 'move' 15 | } 16 | }; 17 | export const CircleSourceArrowhead = joint.linkTools.SourceArrowhead.extend(_.merge({}, circleArrowhead)); 18 | export const CircleTargetArrowhead = joint.linkTools.TargetArrowhead.extend(_.merge({}, circleArrowhead)); 19 | 20 | export const DoublyButton = joint.linkTools.Button.extend({ 21 | update() { 22 | if (this.relatedView.isShortWire()) { 23 | this.options.distance = this.options.distanceShort || this.options.distance; 24 | if (this.options.secondary) this.hide(); 25 | } else { 26 | this.options.distance = this.options.distanceLong || this.options.distance; 27 | } 28 | return joint.linkTools.Button.prototype.update.apply(this, arguments); 29 | } 30 | }); 31 | export const RemoveButton = DoublyButton.extend({ 32 | name: 'remove', 33 | children: joint.linkTools.Remove.prototype.children, 34 | options: joint.linkTools.Remove.prototype.options 35 | }); 36 | export const MonitorButton = DoublyButton.extend({ 37 | name: 'monitor', 38 | children: [{ 39 | tagName: 'circle', 40 | selector: 'button', 41 | attributes: { 42 | 'r': 7, 43 | 'fill': '#001DFF', 44 | 'cursor': 'pointer' 45 | } 46 | }, { 47 | tagName: 'path', 48 | selector: 'icon', 49 | attributes: { 50 | 'd': 'm -2.5,-0.5 a 2,2 0 1 0 4,0 2,2 0 1 0 -4,0 M 1,1 3,3', 51 | 'fill': 'none', 52 | 'stroke': '#FFFFFF', 53 | 'stroke-width': 2, 54 | 'pointer-events': 'none' 55 | } 56 | }], 57 | options: { 58 | action(evt) { 59 | this.notify('link:monitor'); 60 | } 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | const CleanWebpackPlugin = require("clean-webpack-plugin"); 4 | 5 | const outputDirectory = 'dist'; 6 | 7 | const tests = [ 8 | {name: 'fulladder', title: 'Full Adder'}, 9 | {name: 'serialadder', title: 'Serial Adder'}, 10 | {name: 'cycleadder', title: 'Accumulating Adder'}, 11 | {name: 'arithconst', title: 'Fused arithmetic with constants'}, 12 | {name: 'lfsr', title: 'Linear Feedback Shift Register'}, 13 | {name: 'sextium', title: 'Sextium III Processor'}, 14 | {name: 'rom', title: 'Async ROM'}, 15 | {name: 'ram', title: 'Simple RAM'}, 16 | {name: 'fsm', title: 'Finite State Machine'}, 17 | {name: 'gates', title: 'All available gates'}, 18 | {name: 'biggate', title: 'N-ary gates'}, 19 | {name: 'muxsparse', title: 'Sparse mux'}, 20 | {name: 'io', title: 'Input/Output types'}, 21 | {name: 'horner', title: 'Benchmark example'}, 22 | {name: 'latch', title: 'Level-triggered D-latch example'}, 23 | {name: 'warnings', title: 'Warnings example'} 24 | ]; 25 | 26 | module.exports = { 27 | output: { 28 | path: path.resolve(__dirname, outputDirectory), 29 | filename: 'main.js', 30 | library: 'digitaljs', 31 | libraryTarget: 'umd', 32 | umdNamedDefine: true, 33 | globalObject: 'this' 34 | }, 35 | entry: "./src/index.mjs", 36 | devtool: "source-map", 37 | module: { 38 | rules: [ 39 | { 40 | test: /\.css$/, 41 | use: [ 42 | 'style-loader', 43 | 'css-loader' 44 | ] 45 | }, 46 | { 47 | test: /\.svg|\.png/, 48 | type: 'asset' 49 | }, 50 | { 51 | test: require.resolve('jquery'), 52 | loader: 'expose-loader', 53 | options: { 54 | exposes: ['$'] 55 | } 56 | } 57 | ] 58 | }, 59 | plugins: [ 60 | new CleanWebpackPlugin(), 61 | ].concat(tests.map(t => new HtmlWebpackPlugin({ 62 | title: t.title, 63 | template: 'examples/template.html', 64 | test: JSON.stringify(require('./examples/' + t.name + '.json')), 65 | filename: 'test/' + t.name + '.html', 66 | inject: 'head' 67 | }))) 68 | } 69 | 70 | --------------------------------------------------------------------------------