├── .gitattributes ├── examples ├── xorcy.v ├── muxcy.v ├── dff.v ├── carry4whole.v ├── carry4bits.v └── Makefile ├── jest.config.js ├── .gitignore ├── appveyor.yml ├── tsconfig.json ├── test ├── analog │ ├── makefile │ ├── vcc_and_gnd.json │ ├── resistor_divider.json │ ├── mcu.json │ ├── and.json │ └── common_emitter_full.json ├── digital │ ├── ports_splitjoin.json │ ├── hyperedges.json │ ├── generics.json │ ├── pc.json │ ├── mux4.json │ └── up3down5.json ├── test-all.js ├── Cell.test.ts ├── FlatModule.test.ts └── drawModule.test.ts ├── .eslintrc.yml ├── tslint.json ├── lib ├── @types │ ├── onml │ │ └── index.d.ts │ └── elkjs │ │ └── index.d.ts ├── YosysModel.ts ├── index.ts ├── yosys.schema.json5 ├── Skin.ts ├── Port.ts ├── elkGraph.ts ├── FlatModule.ts └── drawModule.ts ├── LICENSE ├── jsmodule └── index.js ├── demo ├── demo.js └── index.html ├── doc ├── vcc_and_gnd.svg ├── resistor_divider.svg ├── hyperedges.svg ├── mcu.svg ├── ports_splitjoin.svg ├── mux4.svg ├── generics.svg ├── common_emitter_full.svg └── and.svg ├── built ├── YosysModel.js ├── index.js ├── Skin.js ├── Port.js ├── elkGraph.js ├── draw.js ├── FlatModule.js └── drawModule.js ├── .travis.yml ├── bin ├── exportLayout.js └── netlistsvg.js └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf -------------------------------------------------------------------------------- /examples/xorcy.v: -------------------------------------------------------------------------------- 1 | module XORCY(output O, input CI, LI); 2 | assign O = CI ^ LI; 3 | endmodule 4 | -------------------------------------------------------------------------------- /examples/muxcy.v: -------------------------------------------------------------------------------- 1 | module MUXCY(output O, input CI, DI, S); 2 | assign O = S ? CI : DI; 3 | endmodule 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/node_modules/', '/built/'] 5 | }; 6 | -------------------------------------------------------------------------------- /examples/dff.v: -------------------------------------------------------------------------------- 1 | module DFF (output reg Q, input C, D, R); 2 | always @(posedge C) 3 | if (~R) begin 4 | Q <= 1'b0; 5 | end else begin 6 | Q <= D; 7 | end 8 | endmodule 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out.svg 3 | test/*.json 4 | test/**/*.svg 5 | examples/*.json 6 | examples/**/*.svg 7 | examples/**/*.png 8 | .yosys_extra 9 | package-lock.json 10 | built/*.test.js 11 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | nodejs_version: "14" 3 | 4 | install: 5 | - ps: Install-Product node $env:nodejs_version 6 | - npm install 7 | 8 | test_script: 9 | - node --version 10 | - npm --version 11 | - npm test 12 | 13 | build: off -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./built", 4 | "allowJs": true, 5 | "lib": [ "es6", "dom" ], 6 | "target": "es5" 7 | }, 8 | "include": [ 9 | "./lib/*", 10 | "./lib/@types" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/analog/makefile: -------------------------------------------------------------------------------- 1 | JSON=$(wildcard *.json) 2 | SVGS=$(JSON:.json=.svg) 3 | SKIN=../../lib/analog.svg 4 | 5 | all: $(SVGS) 6 | 7 | %.svg: %.json $(SKIN) 8 | node ../../bin/netlistsvg.js --skin $(SKIN) $< -o $@ 9 | 10 | clean: 11 | rm -f *.svg 12 | 13 | .PHONY: all clean 14 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | es6: true 4 | extends: 'eslint:recommended' 5 | rules: 6 | indent: 7 | - error 8 | - 4 9 | linebreak-style: 10 | - error 11 | - unix 12 | quotes: 13 | - error 14 | - single 15 | semi: 16 | - error 17 | - always 18 | no-redeclare: 19 | - off -------------------------------------------------------------------------------- /examples/carry4whole.v: -------------------------------------------------------------------------------- 1 | module CARRY4WHOLE(output [3:0] CO, O, input CI, CYINIT, input [3:0] DI, S); 2 | assign O = S ^ {CO[2:0], CI | CYINIT}; 3 | assign CO[0] = S[0] ? CI | CYINIT : DI[0]; 4 | assign CO[1] = S[1] ? CO[0] : DI[1]; 5 | assign CO[2] = S[2] ? CO[1] : DI[2]; 6 | assign CO[3] = S[3] ? CO[2] : DI[3]; 7 | endmodule 8 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "object-literal-sort-keys":[false], 9 | "interface-name":[false], 10 | "ordered-imports":[false], 11 | "no-namespace":false, 12 | "quotemark": [ 13 | true, 14 | "single" 15 | ]}, 16 | "rulesDirectory": [] 17 | } 18 | -------------------------------------------------------------------------------- /lib/@types/onml/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'onml' { 2 | // avoid the headache of recursive type 3 | export type Element = [ 4 | string, 5 | Attributes?, 6 | string?, 7 | ...Array[], 8 | ]; 9 | 10 | export interface Attributes { 11 | [attrName: string]: string; 12 | } 13 | 14 | export function parse(dir: string): Element; 15 | export function p(dir: string): Element; 16 | export function stringify(o: Element): string; 17 | export function s(o: Element): string; 18 | export function traverse(o: Element, callbacks: object); 19 | export function t(o: Element, callbacks: object); 20 | } 21 | -------------------------------------------------------------------------------- /test/analog/vcc_and_gnd.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": { 3 | "test": { 4 | "ports": {}, 5 | "cells": { 6 | "vcc": { 7 | "type": "vcc", 8 | "port_directions": { 9 | "A": "output" 10 | }, 11 | "connections": { 12 | "A": [ 2 ] 13 | }, 14 | "attributes": { 15 | "value": "+12V", 16 | }, 17 | }, 18 | "gnd": { 19 | "type": "gnd", 20 | "port_directions": { 21 | "A": "input" 22 | }, 23 | "connections": { 24 | "A": [ 2 ] 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/carry4bits.v: -------------------------------------------------------------------------------- 1 | `include "muxcy.v" 2 | `include "xorcy.v" 3 | 4 | module CARRY4BITS(output [3:0] CO, O, input CI, CYINIT, input [3:0] DI, S); 5 | wire CIN = CI | CYINIT; 6 | 7 | MUXCY muxcy0 (.O(CO[0]), .CI(CIN), .DI(DI[0]), .S(S[0])); 8 | MUXCY muxcy1 (.O(CO[1]), .CI(CO[0]), .DI(DI[1]), .S(S[1])); 9 | MUXCY muxcy2 (.O(CO[2]), .CI(CO[1]), .DI(DI[2]), .S(S[2])); 10 | MUXCY muxcy3 (.O(CO[3]), .CI(CO[2]), .DI(DI[3]), .S(S[3])); 11 | 12 | XORCY xorcy0 (.O(O[0]), .CI(CIN), .LI(S[0])); 13 | XORCY xorcy1 (.O(O[1]), .CI(CO[0]), .LI(S[1])); 14 | XORCY xorcy2 (.O(O[2]), .CI(CO[1]), .LI(S[2])); 15 | XORCY xorcy3 (.O(O[3]), .CI(CO[2]), .LI(S[3])); 16 | endmodule 17 | -------------------------------------------------------------------------------- /test/digital/ports_splitjoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": { 3 | "ports_splitjoin": { 4 | "ports": { 5 | "inthing": { 6 | "direction": "input", 7 | "bits": [ 2, 3, 4, 5 ] 8 | }, 9 | "outthing": { 10 | "direction": "output", 11 | "bits": [ 2, 3, 2, 3 ] 12 | }, 13 | "outthing2": { 14 | "direction": "output", 15 | "bits": [ 2, 3, 5 ] 16 | }, 17 | "outthing3": { 18 | "direction": "output", 19 | "bits": [ 2, 3, 5 ] 20 | }, 21 | "outthing4": { 22 | "direction": "output", 23 | "bits": [ 2 ] 24 | } 25 | }, 26 | "cells": {} 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /test/test-all.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | bin = require('../bin/netlistsvg'); 3 | 4 | var digital_tests = ['generics', 'ports_splitjoin', 'up3down5', 'mux4', 'hyperedges', 'pc']; 5 | var analog_tests = ['and', 'common_emitter_full', 'mcu', 'resistor_divider', 'vcc_and_gnd'] 6 | 7 | for (var test of digital_tests) { 8 | bin.main( 9 | path.join('test', 'digital', test + '.json'), 10 | path.join('test', 'digital', test + '.svg'), 11 | path.join('lib', 'default.svg') 12 | ); 13 | } 14 | for (var test of analog_tests) { 15 | bin.main( 16 | path.join('test', 'analog', test + '.json'), 17 | path.join('test', 'analog', test + '.svg'), 18 | path.join('lib', 'analog.svg') 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /test/digital/hyperedges.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": { 3 | "hyperedges": { 4 | "ports": { 5 | "i0": { 6 | "direction": "input", 7 | "bits": [ 2 ] 8 | }, 9 | "i1": { 10 | "direction": "input", 11 | "bits": [ 2 ] 12 | }, 13 | "i2": { 14 | "direction": "input", 15 | "bits": [ 3 ] 16 | }, 17 | "i3": { 18 | "direction": "input", 19 | "bits": [ 3 ] 20 | }, 21 | "i4": { 22 | "direction": "input", 23 | "bits": [ 2 ] 24 | }, 25 | "o0": { 26 | "direction": "output", 27 | "bits": [ 3 ] 28 | }, 29 | "o1": { 30 | "direction": "output", 31 | "bits": [ 4 ] 32 | }, 33 | "o2": { 34 | "direction": "output", 35 | "bits": [ 4 ] 36 | }, 37 | "o3": { 38 | "direction": "output", 39 | "bits": [ 4 ] 40 | } 41 | }, 42 | "cells": {} 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Neil Turley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /jsmodule/index.js: -------------------------------------------------------------------------------- 1 | const lib = require('../built'); 2 | const fs = require('fs'); 3 | const json5 = require('json5'); 4 | const Ajv = require('ajv'); 5 | var ajv = new Ajv({allErrors: true}); 6 | require('ajv-errors')(ajv); 7 | 8 | const digital = fs.readFileSync(__dirname + '/../lib/default.svg', 'utf8'); 9 | const analog = fs.readFileSync(__dirname + '/../lib/analog.svg', 'utf8'); 10 | const exampleDigital = fs.readFileSync(__dirname + '/../test/digital/up3down5.json'); 11 | const exampleAnalog = fs.readFileSync(__dirname + '/../test/analog/and.json'); 12 | const schema = fs.readFileSync(__dirname + '/../lib/yosys.schema.json5'); 13 | const exampleDigitalJson = json5.parse(exampleDigital); 14 | const exampleAnalogJson = json5.parse(exampleAnalog); 15 | 16 | function render(skinData, netlistData, cb) { 17 | var valid = ajv.validate(json5.parse(schema), netlistData); 18 | if (!valid) { 19 | throw Error(JSON.stringify(ajv.errors, null, 2)); 20 | } 21 | return lib.render(skinData, netlistData, cb); 22 | } 23 | 24 | module.exports = { 25 | render: render, 26 | digitalSkin: digital, 27 | analogSkin: analog, 28 | exampleDigital: exampleDigitalJson, 29 | exampleAnalog: exampleAnalogJson 30 | }; 31 | -------------------------------------------------------------------------------- /test/analog/resistor_divider.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": { 3 | "resistor_divider": { 4 | "ports": { 5 | "vout": { 6 | "direction": "output", 7 | "bits": [ 3 ] 8 | } 9 | }, 10 | "cells": { 11 | "r1": { 12 | "type": "r_v", 13 | "port_directions": { 14 | "A": "input", 15 | "B": "output" 16 | }, 17 | "connections": { 18 | "A": [ 2 ], 19 | "B": [ 3 ] 20 | } 21 | }, 22 | "r2": { 23 | "type": "r_v", 24 | "port_directions": { 25 | "A": "input", 26 | "B": "output" 27 | }, 28 | "connections": { 29 | "A": [ 3 ], 30 | "B": [ 4 ] 31 | } 32 | }, 33 | "vcc": { 34 | "type": "vcc", 35 | "port_directions": { 36 | "A": "output" 37 | }, 38 | "connections": { 39 | "A": [ 2 ] 40 | } 41 | }, 42 | "gnd": { 43 | "type": "gnd", 44 | "port_directions": { 45 | "A": "input" 46 | }, 47 | "connections": { 48 | "A": [ 4 ] 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var superagent = require('superagent'); 3 | var json5 = require('json5'); 4 | var netlistSvg = require('../built'); 5 | var up3down5 = require('../test/digital/up3down5.json'); 6 | 7 | var skins = ['lib/default.svg', 'lib/analog.svg']; 8 | 9 | var textarea = document.querySelector('textarea'); 10 | var skinSelect = document.querySelector('#skinSelect'); 11 | var renderButton = document.querySelector('#renderButton'); 12 | var formatButton = document.querySelector('#formatButton'); 13 | var svgArea = document.querySelector('#svgArea'); 14 | 15 | textarea.value = json5.stringify(up3down5, null, 4); 16 | 17 | skins.forEach(function(skinPath, i) { 18 | superagent.get(skinPath).end(function(err, r) { 19 | var option = document.createElement('option'); 20 | option.selected = i === 0; 21 | option.value = r.text; 22 | option.text = skinPath; 23 | skinSelect.append(option); 24 | }); 25 | }); 26 | 27 | function render() { 28 | var netlist = json5.parse(textarea.value); 29 | netlistSvg.render(skinSelect.value, netlist, function(e, svg) { 30 | svgArea.src = 'data:image/svg+xml,' + encodeURIComponent(svg); 31 | }); 32 | } 33 | 34 | function format() { 35 | var netlist = json5.parse(textarea.value); 36 | textarea.value = json5.stringify(netlist, null, 4); 37 | } 38 | 39 | renderButton.onclick = render; 40 | formatButton.onclick = format; 41 | -------------------------------------------------------------------------------- /doc/vcc_and_gnd.svg: -------------------------------------------------------------------------------- 1 | 2 | 32 | 33 | 34 | +12V 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | netlistsvg demo 7 | 16 | 17 | 18 |

19 | This is a demo of netlistsvg. 20 |

21 |
22 |
23 |
24 | 25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /built/YosysModel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Yosys; 4 | (function (Yosys) { 5 | var ConstantVal; 6 | (function (ConstantVal) { 7 | ConstantVal["Zero"] = "0"; 8 | ConstantVal["One"] = "1"; 9 | ConstantVal["X"] = "x"; 10 | })(ConstantVal || (ConstantVal = {})); 11 | var Direction; 12 | (function (Direction) { 13 | Direction["Input"] = "input"; 14 | Direction["Output"] = "output"; 15 | })(Direction = Yosys.Direction || (Yosys.Direction = {})); 16 | function getInputPortPids(cell) { 17 | if (cell.port_directions) { 18 | return Object.keys(cell.port_directions).filter(function (k) { 19 | return cell.port_directions[k] === Direction.Input; 20 | }); 21 | } 22 | return []; 23 | } 24 | Yosys.getInputPortPids = getInputPortPids; 25 | function getOutputPortPids(cell) { 26 | if (cell.port_directions) { 27 | return Object.keys(cell.port_directions).filter(function (k) { 28 | return cell.port_directions[k] === Direction.Output; 29 | }); 30 | } 31 | return []; 32 | } 33 | Yosys.getOutputPortPids = getOutputPortPids; 34 | var HideName; 35 | (function (HideName) { 36 | HideName[HideName["Hide"] = 0] = "Hide"; 37 | HideName[HideName["NoHide"] = 1] = "NoHide"; 38 | })(HideName || (HideName = {})); 39 | })(Yosys || (Yosys = {})); 40 | exports.default = Yosys; 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "8" 5 | - "6" 6 | 7 | # Install yosys and inkscape 8 | sudo: true 9 | dist: trusty 10 | before_install: 11 | - sudo add-apt-repository -y ppa:saltmakrell/ppa 12 | - sudo apt-get update 13 | - sudo apt-get install -y yosys 14 | - sudo apt-get install -y inkscape 15 | 16 | # Once https://github.com/travis-ci/apt-source-whitelist/pull/376 is merged, 17 | # the above lines can be replaced with the following; 18 | # 19 | # addons: 20 | # apt: 21 | # sources: 22 | # - yosys 23 | # packages: 24 | # - yosys 25 | # - inkscape 26 | 27 | 28 | script: 29 | - npm test 30 | - npm run build-demo 31 | - cd examples; make build.all; cd .. 32 | 33 | deploy: 34 | provider: pages 35 | skip_cleanup: true 36 | github_token: 37 | secure: "qhyBDZ7DIA1ai2VPCGUjPHbeYx6FZNNC68n/u/i2qJR0/Bo3UMTYqEYOL622KANPnPwWAxSNsrrkB4J8C/BQuaN8/yUKZU6ReD5L5hMzQsEUtLfy+V3TsogT4TwZQLVOBExemA/hQOadC1qTeoeHqhp5wQ1PZfR9Vm6BI8KdJfK83j5mMHCbJyPPz7a1TFeNOnylLh+i/NbTl9HXMAkAj2VdsQRM5qKoqa+qpImnh9gPKSSvhSw4VSh8uw19Nxi3Y63tt/z1ZxznZd9OX9hmJmCkkQPuBoCMwf6Ql+NafmB/mHmquW/3eURC2lmaBcLHG2iTLIbQrOEyD0nnsfOL2IMiKRYUFq0mdTG43+98QEfLf50uaZNIjjzDT0qJPBDZfSXeEtIxZB4uUHZ79EIQlup/Q8LmHpNtYPsM19KG8gSHHiG7MwKahn1GUfiE2/BEdVOeJQEmcCYDKUHdWRicN9RDH1qHlFPG5Spc7rIjEgmZNq5ELQ8VJCG3eXkNVRgrK8lQJ6A2bUvR6n+On1kgV3bUcWiFzA+gVcDovDVhjC2KBty05XwnOgE+CmmQvQ4F8eSsssekd9gFCql6w9H/ZvaTxenrjRMMJ/4ymT/l+En8bFBXzPsTSd/QnE1BS8wVNkGsC9LppUkJWsOrfD6dkj86hbuEEOMWutLj03E7mBQ=" 38 | on: 39 | branch: master 40 | node: "6" 41 | -------------------------------------------------------------------------------- /test/Cell.test.ts: -------------------------------------------------------------------------------- 1 | import Cell from '../lib/Cell'; 2 | import Yosys from '../lib/YosysModel'; 3 | 4 | test('Create Cell from Yosys Input Port', () => { 5 | const inputPort: Yosys.ExtPort = {direction: Yosys.Direction.Input, bits: [47, 12, 16]}; 6 | const cell: Cell = Cell.fromPort(inputPort, 'testInput'); 7 | expect(cell.Type).toEqual('$_inputExt_'); 8 | expect(cell.outputPortVals()).toEqual([',47,12,16,']); 9 | expect(cell.inputPortVals()).toEqual([]); 10 | }); 11 | 12 | test('Create Cell from Yosys Output Port', () => { 13 | const inputPort: Yosys.ExtPort = {direction: Yosys.Direction.Output, bits: [47, 12, 16]}; 14 | const cell: Cell = Cell.fromPort(inputPort, 'testOutput'); 15 | expect(cell.Type).toEqual('$_outputExt_'); 16 | expect(cell.outputPortVals()).toEqual([]); 17 | expect(cell.inputPortVals()).toEqual([',47,12,16,']); 18 | }); 19 | 20 | test('Create Cell from Constant', () => { 21 | const cell: Cell = Cell.fromConstantInfo('bob', [0, 1, 0, 1, 1]); 22 | expect(cell.Type).toEqual('$_constant_'); 23 | expect(cell.outputPortVals()).toEqual([',0,1,0,1,1,']); 24 | expect(cell.inputPortVals()).toEqual([]); 25 | }); 26 | 27 | test('Create Cell from Join', () => { 28 | const cell: Cell = Cell.fromJoinInfo(',3,4,5,', ['0', '1:2']); 29 | expect(cell.Type).toEqual('$_join_'); 30 | expect(cell.inputPortVals()).toEqual([',3,', ',4,5,']); 31 | expect(cell.outputPortVals()).toEqual([',3,4,5,']); 32 | }); 33 | 34 | test('Create Cell from Split', () => { 35 | const cell: Cell = Cell.fromSplitInfo(',3,4,5,', ['0:1', '2']); 36 | expect(cell.Type).toEqual('$_split_'); 37 | expect(cell.inputPortVals()).toEqual([',3,4,5,']); 38 | expect(cell.outputPortVals()).toEqual([',3,4,', ',5,']); 39 | }); 40 | -------------------------------------------------------------------------------- /lib/@types/elkjs/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'elkjs' { 2 | interface Graph { 3 | id: string; 4 | children: Cell[]; 5 | edges: Edge[]; 6 | width?: number; 7 | height?: number; 8 | } 9 | 10 | interface Port { 11 | id: string; 12 | width: number; 13 | height: number; 14 | x?: number; 15 | y?: number; 16 | labels?: Label[]; 17 | } 18 | 19 | interface Segment { 20 | startPoint: WirePoint; 21 | endPoint: WirePoint; 22 | bendPoints: WirePoint[]; 23 | } 24 | 25 | interface Edge { 26 | id: string; 27 | source: string; 28 | sourcePort: string; 29 | target: string; 30 | targetPort: string; 31 | layoutOptions?: ElkLayoutOptions; 32 | junctionPoints?: WirePoint[]; 33 | bendPoints?: WirePoint[]; 34 | sections?: Segment[]; 35 | } 36 | 37 | interface ElkLayoutOptions { 38 | [option: string]: any; 39 | } 40 | 41 | interface Cell { 42 | id: string; 43 | width: number; 44 | height: number; 45 | ports: Port[]; 46 | layoutOptions?: ElkLayoutOptions; 47 | labels?: Label[]; 48 | x?: number; 49 | y?: number; 50 | } 51 | 52 | interface Label { 53 | id: string; 54 | text: string; 55 | x: number; 56 | y: number; 57 | height: number; 58 | width: number; 59 | } 60 | 61 | interface WirePoint { 62 | x: number; 63 | y: number; 64 | } 65 | 66 | interface ElkOptions { 67 | layoutOptions: ElkLayoutOptions; 68 | } 69 | 70 | class ELK { 71 | public layout(Graph, ElkOptions): Promise; 72 | } 73 | export = ELK; 74 | } 75 | -------------------------------------------------------------------------------- /bin/exportLayout.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var lib = require('../built'), 5 | fs = require('fs'), 6 | path = require('path'), 7 | json5 = require('json5'), 8 | yargs = require('yargs'), 9 | Ajv = require('ajv'); 10 | 11 | var ajv = new Ajv({allErrors: true}); 12 | require('ajv-errors')(ajv); 13 | 14 | if (require.main === module) { 15 | var argv = yargs 16 | .demand(1) 17 | .usage('usage: $0 input_json_file [-o output_json_file] [--skin skin_file] [--pre]') 18 | .argv; 19 | main(argv._[0], argv.o, argv.skin, argv.pre); 20 | } 21 | 22 | function dump(skinData, netlist, outputPath, preLayout) { 23 | lib.dumpLayout(skinData, netlist, preLayout, (err, jsonData) => { 24 | if (err) throw err; 25 | fs.writeFile(outputPath, jsonData, 'utf-8', (err) => { 26 | if (err) throw err; 27 | }); 28 | }); 29 | } 30 | 31 | function parseFiles(skinPath, netlistPath, callback) { 32 | fs.readFile(skinPath, 'utf-8', (err, skinData) => { 33 | if (err) throw err; 34 | fs.readFile(netlistPath, (err, netlistData) => { 35 | if (err) throw err; 36 | callback(skinData, netlistData); 37 | }); 38 | }); 39 | } 40 | 41 | function main(netlistPath, outputPath, skinPath, preLayout) { 42 | skinPath = skinPath || path.join(__dirname, '../lib/default.svg'); 43 | outputPath = outputPath || 'out.json'; 44 | var schemaPath = path.join(__dirname, '../lib/yosys.schema.json5'); 45 | parseFiles(skinPath, netlistPath, (skinData, netlistString) => { 46 | var netlistJson = json5.parse(netlistString); 47 | var valid = ajv.validate(json5.parse(fs.readFileSync(schemaPath)), netlistJson); 48 | if (!valid) { 49 | throw Error(JSON.stringify(ajv.errors, null, 2)); 50 | } 51 | dump(skinData, netlistJson, outputPath, preLayout); 52 | }); 53 | } 54 | 55 | module.exports.main = main; 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netlistsvg", 3 | "version": "1.0.2", 4 | "description": "rendering a schematic from a netlist", 5 | "main": "built/index.js", 6 | "author": "Neil Turley", 7 | "license": "MIT", 8 | "dependencies": { 9 | "@types/clone": "^2.1.0", 10 | "@types/lodash": "^4.14.170", 11 | "ajv": "^8.6.1", 12 | "ajv-errors": "^3.0.0", 13 | "clone": "^2.1.2", 14 | "elkjs": "^0.7.1", 15 | "fs-extra": "^10.0.0", 16 | "json5": "^2.2.0", 17 | "lodash": "^4.17.21", 18 | "onml": "^2.1.0", 19 | "yargs": "^17.0.1" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/nturley/netlistsvg.git" 24 | }, 25 | "bin": { 26 | "netlistsvg": "./bin/netlistsvg.js", 27 | "netlistsvg-dumplayout": "./bin/exportLayout.js" 28 | }, 29 | "scripts": { 30 | "test-examples": "tsc && node --trace-warnings test/test-all.js", 31 | "lint": "tslint --project . && eslint bin/**/*.js && eslint --global=document demo/**/*.js", 32 | "test": "npm run -s test-examples && npm run -s lint && jest", 33 | "build-demo": "cp demo/index.html . && cp node_modules/elkjs/lib/elk.bundled.js . && browserify demo/demo.js > bundle.js", 34 | "build-module": "browserify -t brfs -s netlistsvg jsmodule/index.js > built/netlistsvg.bundle.js" 35 | }, 36 | "keywords": [ 37 | "svg", 38 | "netlist" 39 | ], 40 | "bugs": { 41 | "url": "https://github.com/nturley/netlistsvg/issues" 42 | }, 43 | "homepage": "https://github.com/nturley/netlistsvg#readme", 44 | "devDependencies": { 45 | "@types/jest": "^26.0.23", 46 | "@types/node": "^16.0.0", 47 | "browserify": "^17.0.0", 48 | "browserify-shim": "^3.8.14", 49 | "eslint": "^7.30.0", 50 | "jest": "^27.0.6", 51 | "superagent": "^6.1.0", 52 | "ts-jest": "^27.0.3", 53 | "tslint": "^6.1.3", 54 | "typescript": "^3.3.4000", 55 | "brfs": "^2.0.2" 56 | }, 57 | "browserify-shim": { 58 | "elkjs": "global:ELK" 59 | }, 60 | "browserify": { 61 | "transform": [ 62 | "browserify-shim" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /bin/netlistsvg.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var lib = require('../built'), 5 | fs = require('fs'), 6 | path = require('path'), 7 | json5 = require('json5'), 8 | yargs = require('yargs'), 9 | Ajv = require('ajv'); 10 | 11 | var ajv = new Ajv({allErrors: true}); 12 | require('ajv-errors')(ajv); 13 | 14 | if (require.main === module) { 15 | var argv = yargs 16 | .demand(1) 17 | .usage('usage: $0 input_json_file [-o output_svg_file] [--skin skin_file] [--layout elk_json_file]') 18 | .argv; 19 | main(argv._[0], argv.o, argv.skin, argv.layout); 20 | } 21 | 22 | function render(skinData, netlist, outputPath, elkData) { 23 | lib.render(skinData, netlist, (err, svgData) => { 24 | if (err) throw err; 25 | fs.writeFile(outputPath, svgData, 'utf-8', (err) => { 26 | if (err) throw err; 27 | }); 28 | }, elkData); 29 | } 30 | 31 | function parseFiles(skinPath, netlistPath, elkJsonPath, callback) { 32 | fs.readFile(skinPath, 'utf-8', (err, skinData) => { 33 | if (err) throw err; 34 | fs.readFile(netlistPath, (err, netlistData) => { 35 | if (err) throw err; 36 | if (elkJsonPath) { 37 | fs.readFile(elkJsonPath, (err, elkString) => { 38 | callback(skinData, netlistData, json5.parse(elkString)); 39 | }); 40 | } else { 41 | callback(skinData, netlistData); 42 | } 43 | }); 44 | }); 45 | } 46 | 47 | function main(netlistPath, outputPath, skinPath, elkJsonPath) { 48 | skinPath = skinPath || path.join(__dirname, '../lib/default.svg'); 49 | outputPath = outputPath || 'out.svg'; 50 | var schemaPath = path.join(__dirname, '../lib/yosys.schema.json5'); 51 | parseFiles(skinPath, netlistPath, elkJsonPath, (skinData, netlistString, elkData) => { 52 | var netlistJson = json5.parse(netlistString); 53 | var valid = ajv.validate(json5.parse(fs.readFileSync(schemaPath)), netlistJson); 54 | if (!valid) { 55 | throw Error(JSON.stringify(ajv.errors, null, 2)); 56 | } 57 | render(skinData, netlistJson, outputPath, elkData); 58 | }); 59 | } 60 | 61 | module.exports.main = main; 62 | -------------------------------------------------------------------------------- /test/digital/generics.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": { 3 | "generics": { 4 | "ports": { 5 | "clk100": { 6 | "direction": "input", 7 | "bits": [ 2 ] 8 | }, 9 | "clk40": { 10 | "direction": "output", 11 | "bits": [ 3 ] 12 | }, 13 | "clk125": { 14 | "direction": "output", 15 | "bits": [ 5 ] 16 | } 17 | }, 18 | "cells" : { 19 | "PLL": { 20 | "type": "PLL", 21 | "port_directions": { 22 | "clkin": "input", 23 | "clk40": "output", 24 | "clk200": "output", 25 | "clk125": "output", 26 | "locked": "output" 27 | }, 28 | "connections": { 29 | "clkin": [ 2 ], 30 | "clk40": [3], 31 | "clk200": [6], 32 | "clk125": [5], 33 | "locked": [8] 34 | } 35 | }, 36 | "MIG": { 37 | "type": "MIG", 38 | "port_directions": { 39 | "clk_ref": "input", 40 | "clk_sys": "input", 41 | "reset": "input" 42 | }, 43 | "connections": { 44 | "clk_ref": [6], 45 | "clk_sys": [2], 46 | "reset": [4] 47 | } 48 | }, 49 | "counter": { 50 | "type": "counter", 51 | "port_directions": { 52 | "clk": "input", 53 | "start": "input", 54 | "elapsed": "output" 55 | }, 56 | "connections": { 57 | "clk": [2], 58 | "start": [8], 59 | "elapsed": [4] 60 | } 61 | }, 62 | "sync": { 63 | "type": "sync", 64 | "port_directions": { 65 | "clk": "input", 66 | "in": "input", 67 | "out": "output" 68 | }, 69 | "connections": { 70 | "clk": [3], 71 | "in": [4], 72 | "out": [7] 73 | } 74 | }, 75 | "businterface": { 76 | "type": "businterface", 77 | "port_directions": { 78 | "clk": "input", 79 | "reset": "input" 80 | }, 81 | "connections": { 82 | "clk": [3], 83 | "reset": [7] 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /test/FlatModule.test.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import fs = require('fs'); 3 | import json5 = require('json5'); 4 | import onml = require('onml'); 5 | 6 | import Yosys from '../lib/YosysModel'; 7 | import Cell from '../lib/Cell'; 8 | import { FlatModule } from '../lib/FlatModule'; 9 | import Skin from '../lib/Skin'; 10 | 11 | /** 12 | * Helper function for tests that use test files 13 | * @param testFile the name of the test case, don't include path or extension 14 | */ 15 | function createFlatModule(testFile: string): FlatModule { 16 | const testPath = path.join(__dirname,'digital', testFile + '.json'); 17 | const defaultSkin = path.join(__dirname, '../lib/default.svg'); 18 | const testStr = fs.readFileSync(testPath).toString(); 19 | const netlist: Yosys.Netlist = json5.parse(testStr); 20 | const skin = onml.parse(fs.readFileSync(defaultSkin).toString()); 21 | Skin.skin = skin; 22 | return new FlatModule(netlist); 23 | } 24 | 25 | /** 26 | * make sure the correct number of splits and joins is calculated. 27 | */ 28 | test('split join', () => { 29 | const flatModule = createFlatModule('ports_splitjoin'); 30 | // there are 5 external ports 31 | const numStartNodes = flatModule.nodes.length; 32 | flatModule.addSplitsJoins(); 33 | const nodes = flatModule.nodes; 34 | // should have 3 more nodes, one split, two joins 35 | expect(nodes.length - numStartNodes).toEqual(3); 36 | const splits = nodes.filter( (node: Cell) => node.Type === '$_split_'); 37 | expect(splits.length).toEqual(1); 38 | const split = splits[0]; 39 | // split should have 3 outputs 40 | expect(split.OutputPorts.length).toEqual(3); 41 | const joins = nodes.filter( (node: Cell) => node.Type === '$_join_'); 42 | expect(joins.length).toEqual(2); 43 | // both joins should have two inputs 44 | joins.forEach( (join: Cell) => expect(join.InputPorts.length).toEqual(2)); 45 | }); 46 | 47 | /** 48 | * Make sure create wires handles hyper edges correctly 49 | */ 50 | test('create wires', () => { 51 | const flatModule = createFlatModule('hyperedges'); 52 | flatModule.createWires(); 53 | const wires = flatModule.wires; 54 | expect(wires.length).toEqual(3); 55 | expect(wires.find((wire) => wire.drivers.length === 3)).toBeDefined(); 56 | expect(wires.find((wire) => wire.riders.length === 3)).toBeDefined(); 57 | expect(wires.find((wire) => wire.drivers.length === 2 && wire.riders.length === 1)).toBeDefined(); 58 | }); 59 | -------------------------------------------------------------------------------- /test/analog/mcu.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": { 3 | "resistor_divider": { 4 | "ports": {}, 5 | "cells": { 6 | "r1": { 7 | "type": "r_v", 8 | "port_directions": { 9 | "A": "output", 10 | "B": "input" 11 | }, 12 | "connections": { 13 | "A": [ 2 ], 14 | "B": [ 3 ] 15 | } 16 | }, 17 | "c1": { 18 | "type": "c_v", 19 | "port_directions": { 20 | "A": "input", 21 | "B": "output" 22 | }, 23 | "connections": { 24 | "A": [ 6 ], 25 | "B": [ 5 ] 26 | } 27 | }, 28 | "r3": { 29 | "type": "r_v", 30 | "port_directions": { 31 | "A": "input", 32 | "B": "output" 33 | }, 34 | "connections": { 35 | "A": [ 3 ], 36 | "B": [ 4 ] 37 | } 38 | }, 39 | "u1" : { 40 | "type":"MCU", 41 | "port_directions": { 42 | "VCC":"input", 43 | "GPIO1":"output", 44 | "GND":"input" 45 | }, 46 | "connections":{ 47 | "VCC":[6], 48 | "GPIO1":[3], 49 | "GND":[7] 50 | } 51 | }, 52 | "vcc": { 53 | "type": "vcc", 54 | "port_directions": { 55 | "A": "output" 56 | }, 57 | "connections": { 58 | "A": [ 2 ] 59 | } 60 | }, 61 | "vcc_2": { 62 | "type": "vcc", 63 | "port_directions": { 64 | "A": "output" 65 | }, 66 | "connections": { 67 | "A": [ 6 ] 68 | } 69 | }, 70 | "gnd": { 71 | "type": "gnd", 72 | "port_directions": { 73 | "A": "input" 74 | }, 75 | "connections": { 76 | "A": [ 4 ] 77 | } 78 | }, 79 | "gnd3": { 80 | "type": "gnd", 81 | "port_directions": { 82 | "A": "output" 83 | }, 84 | "connections": { 85 | "A": [ 7 ] 86 | } 87 | }, 88 | "gnd2": { 89 | "type": "gnd", 90 | "port_directions": { 91 | "A": "input" 92 | }, 93 | "connections": { 94 | "A": [ 5 ] 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/YosysModel.ts: -------------------------------------------------------------------------------- 1 | 2 | namespace Yosys { 3 | enum ConstantVal { 4 | Zero = '0', 5 | One = '1', 6 | X = 'x', 7 | } 8 | 9 | export type Signals = (number | ConstantVal)[]; 10 | 11 | interface ModuleMap { 12 | [moduleName: string]: Module; 13 | } 14 | 15 | export interface Netlist { 16 | modules: ModuleMap; 17 | } 18 | 19 | interface ModuleAttributes { 20 | top?: number|string; 21 | [attrName: string]: any; 22 | } 23 | 24 | interface NetAttributes { 25 | [attrName: string]: any; 26 | } 27 | 28 | export interface CellAttributes { 29 | value?: string; 30 | [attrName: string]: any; 31 | } 32 | 33 | export enum Direction { 34 | Input = 'input', 35 | Output = 'output', 36 | } 37 | 38 | export interface ExtPort { 39 | direction: Direction; 40 | bits: Signals; 41 | } 42 | 43 | interface ExtPortMap { 44 | [portName: string]: ExtPort; 45 | } 46 | 47 | export interface PortDirMap { 48 | [portName: string]: Direction; 49 | } 50 | 51 | export interface PortConnectionMap { 52 | [portName: string]: Signals; 53 | } 54 | 55 | export interface Cell { 56 | type: string; 57 | port_directions: PortDirMap; 58 | connections: PortConnectionMap; 59 | attributes?: CellAttributes; 60 | hide_name?: HideName; 61 | parameters?: { [key: string]: any }; 62 | } 63 | 64 | export function getInputPortPids(cell: Cell): string[] { 65 | if (cell.port_directions) { 66 | return Object.keys(cell.port_directions).filter((k) => { 67 | return cell.port_directions[k] === Direction.Input; 68 | }); 69 | } 70 | return []; 71 | } 72 | 73 | export function getOutputPortPids(cell: Cell): string[] { 74 | if (cell.port_directions) { 75 | return Object.keys(cell.port_directions).filter((k) => { 76 | return cell.port_directions[k] === Direction.Output; 77 | }); 78 | } 79 | return []; 80 | } 81 | 82 | interface CellMap { 83 | [cellName: string]: Cell; 84 | } 85 | 86 | enum HideName { 87 | Hide, 88 | NoHide, 89 | } 90 | 91 | interface Net { 92 | bits: Signals; 93 | hide_name: HideName; 94 | attributes: NetAttributes; 95 | } 96 | 97 | interface NetNameMap { 98 | [netName: string]: Net; 99 | } 100 | 101 | export interface Module { 102 | ports: ExtPortMap; 103 | cells: CellMap; 104 | netNames: NetNameMap; 105 | attributes?: ModuleAttributes; 106 | } 107 | } 108 | export default Yosys; 109 | -------------------------------------------------------------------------------- /built/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.render = exports.dumpLayout = void 0; 4 | var ELK = require("elkjs"); 5 | var onml = require("onml"); 6 | var FlatModule_1 = require("./FlatModule"); 7 | var Skin_1 = require("./Skin"); 8 | var elkGraph_1 = require("./elkGraph"); 9 | var drawModule_1 = require("./drawModule"); 10 | var elk = new ELK(); 11 | function createFlatModule(skinData, yosysNetlist) { 12 | Skin_1.default.skin = onml.p(skinData); 13 | var layoutProps = Skin_1.default.getProperties(); 14 | var flatModule = new FlatModule_1.FlatModule(yosysNetlist); 15 | // this can be skipped if there are no 0's or 1's 16 | if (layoutProps.constants !== false) { 17 | flatModule.addConstants(); 18 | } 19 | // this can be skipped if there are no splits or joins 20 | if (layoutProps.splitsAndJoins !== false) { 21 | flatModule.addSplitsJoins(); 22 | } 23 | flatModule.createWires(); 24 | return flatModule; 25 | } 26 | function dumpLayout(skinData, yosysNetlist, prelayout, done) { 27 | var flatModule = createFlatModule(skinData, yosysNetlist); 28 | var kgraph = elkGraph_1.buildElkGraph(flatModule); 29 | if (prelayout) { 30 | done(null, JSON.stringify(kgraph, null, 2)); 31 | return; 32 | } 33 | var layoutProps = Skin_1.default.getProperties(); 34 | var promise = elk.layout(kgraph, { layoutOptions: layoutProps.layoutEngine }); 35 | promise.then(function (graph) { 36 | done(null, JSON.stringify(graph, null, 2)); 37 | }).catch(function (reason) { 38 | throw Error(reason); 39 | }); 40 | } 41 | exports.dumpLayout = dumpLayout; 42 | function render(skinData, yosysNetlist, done, elkData) { 43 | var flatModule = createFlatModule(skinData, yosysNetlist); 44 | var kgraph = elkGraph_1.buildElkGraph(flatModule); 45 | var layoutProps = Skin_1.default.getProperties(); 46 | var promise; 47 | // if we already have a layout then use it 48 | if (elkData) { 49 | promise = new Promise(function (resolve) { 50 | drawModule_1.default(elkData, flatModule); 51 | resolve(); 52 | }); 53 | } 54 | else { 55 | // otherwise use ELK to generate the layout 56 | promise = elk.layout(kgraph, { layoutOptions: layoutProps.layoutEngine }) 57 | .then(function (g) { return drawModule_1.default(g, flatModule); }) 58 | // tslint:disable-next-line:no-console 59 | .catch(function (e) { console.error(e); }); 60 | } 61 | // support legacy callback style 62 | if (typeof done === 'function') { 63 | promise.then(function (output) { 64 | done(null, output); 65 | return output; 66 | }).catch(function (reason) { 67 | throw Error(reason); 68 | }); 69 | } 70 | return promise; 71 | } 72 | exports.render = render; 73 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import ELK = require('elkjs'); 4 | import onml = require('onml'); 5 | 6 | import { FlatModule } from './FlatModule'; 7 | import Yosys from './YosysModel'; 8 | import Skin from './Skin'; 9 | import { ElkModel, buildElkGraph } from './elkGraph'; 10 | import drawModule from './drawModule'; 11 | 12 | const elk = new ELK(); 13 | 14 | type ICallback = (error: Error, result?: string) => void; 15 | 16 | function createFlatModule(skinData: string, yosysNetlist: Yosys.Netlist): FlatModule { 17 | Skin.skin = onml.p(skinData); 18 | const layoutProps = Skin.getProperties(); 19 | const flatModule = new FlatModule(yosysNetlist); 20 | // this can be skipped if there are no 0's or 1's 21 | if (layoutProps.constants !== false) { 22 | flatModule.addConstants(); 23 | } 24 | // this can be skipped if there are no splits or joins 25 | if (layoutProps.splitsAndJoins !== false) { 26 | flatModule.addSplitsJoins(); 27 | } 28 | flatModule.createWires(); 29 | return flatModule; 30 | } 31 | 32 | export function dumpLayout(skinData: string, yosysNetlist: Yosys.Netlist, prelayout: boolean, done: ICallback) { 33 | const flatModule = createFlatModule(skinData, yosysNetlist); 34 | const kgraph: ElkModel.Graph = buildElkGraph(flatModule); 35 | if (prelayout) { 36 | done(null, JSON.stringify(kgraph, null, 2)); 37 | return; 38 | } 39 | const layoutProps = Skin.getProperties(); 40 | const promise = elk.layout(kgraph, { layoutOptions: layoutProps.layoutEngine }); 41 | promise.then((graph: ElkModel.Graph) => { 42 | done(null, JSON.stringify(graph, null, 2)); 43 | }).catch((reason) => { 44 | throw Error(reason); 45 | }); 46 | } 47 | 48 | export function render(skinData: string, yosysNetlist: Yosys.Netlist, done?: ICallback, elkData?: ElkModel.Graph) { 49 | const flatModule = createFlatModule(skinData, yosysNetlist); 50 | const kgraph: ElkModel.Graph = buildElkGraph(flatModule); 51 | const layoutProps = Skin.getProperties(); 52 | 53 | let promise; 54 | // if we already have a layout then use it 55 | if (elkData) { 56 | promise = new Promise((resolve) => { 57 | drawModule(elkData, flatModule); 58 | resolve(); 59 | }); 60 | } else { 61 | // otherwise use ELK to generate the layout 62 | promise = elk.layout(kgraph, { layoutOptions: layoutProps.layoutEngine }) 63 | .then((g) => drawModule(g, flatModule)) 64 | // tslint:disable-next-line:no-console 65 | .catch((e) => { console.error(e); }); 66 | } 67 | 68 | // support legacy callback style 69 | if (typeof done === 'function') { 70 | promise.then((output: string) => { 71 | done(null, output); 72 | return output; 73 | }).catch((reason) => { 74 | throw Error(reason); 75 | }); 76 | } 77 | return promise; 78 | } 79 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | YOSYS ?= yosys 2 | INKSCAPE ?= inkscape 3 | VIEWER ?= eog 4 | 5 | YOSYS_FLAGS ?= -q 6 | # YOSYS_FLAGS ?= -Q -T 7 | 8 | # Node JS is sometimes installed as node and sometimes as nodejs 9 | ifneq ($(shell which node),) 10 | NODE ?= node 11 | else 12 | ifneq ($(shell which nodejs),) 13 | NODE ?= nodejs 14 | else 15 | $(error "Can not find node(js), please set $$NODE to the node binary") 16 | endif 17 | endif 18 | 19 | NETLISTSVG = ../bin/netlistsvg 20 | NETLISTSVG_SKIN ?= ../lib/default.svg 21 | NETLISTSVG_DPI ?= 300 22 | 23 | # Simple files are the same flattened as not 24 | SIMPLE_FILES=dff.v muxcy.v xorcy.v 25 | # Complex files are different when flattened 26 | COMPLEX_FILES=carry4bits.v carry4whole.v 27 | 28 | ALL_TARGETS= \ 29 | $(foreach v,$(SIMPLE_FILES) ,$(basename $(v)).simple.all) \ 30 | $(foreach v,$(COMPLEX_FILES),$(basename $(v)).complex.all) 31 | 32 | GET_TOP ?= export TOP=$$(echo $(basename $<) | tr a-z A-Z); 33 | 34 | # Top level diagram 35 | %.json: %.v Makefile 36 | $(GET_TOP) $(YOSYS) $(YOSYS_FLAGS) -p "prep -top $$TOP; write_json $@" $< 37 | 38 | # Split wires, can make it easier for the diagram if nets are split up 39 | %.split.json: %.v Makefile 40 | $(GET_TOP) $(YOSYS) $(YOSYS_FLAGS) -p "prep -top $$TOP; splitnets; write_json $@" $< 41 | 42 | # Flatten the diagram into logic + black boxes 43 | %.flat.json: %.v Makefile 44 | $(GET_TOP) $(YOSYS) $(YOSYS_FLAGS) -p "prep -top $$TOP -flatten; write_json $@" $< 45 | 46 | # Convert logic into AND and NOT logic 47 | %.aig.json: %.v Makefile 48 | $(GET_TOP) $(YOSYS) $(YOSYS_FLAGS) -p "prep -top $$TOP -flatten; cd $$TOP; aigmap; write_json $@" $< 49 | 50 | # Convert logic into NAND, AND and NOT logic 51 | %.naig.json: %.v Makefile 52 | $(GET_TOP) $(YOSYS) $(YOSYS_FLAGS) -p "prep -top $$TOP -flatten; cd $$TOP; aigmap -nand; write_json $@" $< 53 | 54 | # Convert logic into "simple logic" - NOT, AND, XOR, etc 55 | %.simplemap.json: %.v Makefile 56 | $(GET_TOP) $(YOSYS) $(YOSYS_FLAGS) -p "prep -top $$TOP -flatten; cd $$TOP; simplemap; write_json $@" $< 57 | 58 | # Use netlistsvg to generate SVG files 59 | %.svg: %.json $(NETLISTSVG_SKIN) 60 | $(NODE) $(NETLISTSVG) $< -o $@ --skin $(NETLISTSVG_SKIN) 61 | 62 | # Use inkscape to render the SVG files into PNG files. 63 | %.png: %.svg 64 | $(INKSCAPE) --export-png $@ --export-dpi $(NETLISTSVG_DPI) $< 2>&1 | grep -v "WARNING: unknown type: s:alias" 65 | 66 | # Open the rendered PNG in a file viewer 67 | %.view: %.png 68 | eog $< & 69 | 70 | # Generate all PNGs for simple files 71 | %.simple.all: %.png %.aig.png 72 | @true 73 | 74 | # Generate all PNGS for complex files 75 | %.complex.all: %.png %.split.png %.flat.png %.aig.png %.simplemap.png 76 | @true 77 | 78 | # Build everything! 79 | build.all: $(ALL_TARGETS) 80 | @true 81 | 82 | # View everything! 83 | view.all: build.all 84 | eog *.png & 85 | 86 | clean: 87 | rm -f *.json *.svg *.png 88 | 89 | all: 90 | make clean 91 | make view.all 92 | 93 | .DEFAULT_GOAL := all 94 | .PRECIOUS: %.png 95 | .PHONY: view clean all 96 | -------------------------------------------------------------------------------- /doc/resistor_divider.svg: -------------------------------------------------------------------------------- 1 | 2 | 32 | 33 | 34 | r1 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | r2 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | VCC 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | vout 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /test/analog/and.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": { 3 | "resistor_divider": { 4 | "ports": { 5 | "A": { 6 | "direction": "input", 7 | "bits": [2] 8 | }, 9 | "B": { 10 | "direction": "input", 11 | "bits": [3] 12 | }, 13 | "A AND B": { 14 | "direction": "output", 15 | "bits": [4] 16 | } 17 | }, 18 | "cells": { 19 | "R1": { 20 | "type": "r_v", 21 | "connections": { 22 | "A": [2], 23 | "B": [5] 24 | }, 25 | "attributes": { 26 | "value":"10k" 27 | } 28 | }, 29 | "R2": { 30 | "type": "r_v", 31 | "connections": { 32 | "A": [3], 33 | "B": [5] 34 | }, 35 | "attributes": { 36 | "value":"10k" 37 | } 38 | }, 39 | "Q1": { 40 | "type": "q_pnp", 41 | "port_directions": { 42 | "C": "input", 43 | "B": "input", 44 | "E": "output" 45 | }, 46 | "connections": { 47 | "C": [6], 48 | "B": [5], 49 | "E": [7] 50 | } 51 | }, 52 | "R3": { 53 | "type": "r_v", 54 | "connections": { 55 | "A": [7], 56 | "B": [8] 57 | }, 58 | "attributes": { 59 | "value":"10k" 60 | } 61 | }, 62 | "R4": { 63 | "type": "r_v", 64 | "connections": { 65 | "A": [7], 66 | "B": [9] 67 | }, 68 | "attributes": { 69 | "value":"10k" 70 | } 71 | }, 72 | "R5": { 73 | "type": "r_v", 74 | "connections": { 75 | "A": [4], 76 | "B": [12] 77 | }, 78 | "attributes": { 79 | "value":"10k" 80 | } 81 | }, 82 | "Q2": { 83 | "type": "q_pnp", 84 | "port_directions": { 85 | "C": "input", 86 | "B": "input", 87 | "E": "output" 88 | }, 89 | "connections": { 90 | "C": [10], 91 | "B": [9], 92 | "E": [4] 93 | } 94 | }, 95 | "vcc": { 96 | "type": "vcc", 97 | "connections": { 98 | "A": [6] 99 | }, 100 | "attributes": { 101 | "name":"VCC" 102 | } 103 | }, 104 | "vcc2": { 105 | "type": "vcc", 106 | "connections": { 107 | "A": [10] 108 | }, 109 | "attributes": { 110 | "name":"VCC" 111 | } 112 | }, 113 | "gnd": { 114 | "type": "gnd", 115 | "port_directions": { 116 | "A": "input" 117 | }, 118 | "connections": { 119 | "A": [8] 120 | }, 121 | "attributes": { 122 | "name":"DGND" 123 | } 124 | }, 125 | "gnd2": { 126 | "type": "gnd", 127 | "port_directions": { 128 | "A": "input" 129 | }, 130 | "connections": { 131 | "A": [12] 132 | }, 133 | "attributes": { 134 | "name":"DGND" 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/yosys.schema.json5: -------------------------------------------------------------------------------- 1 | { 2 | "description": "JSON Schema Yosys netlists JSON format", 3 | "type": "object", 4 | // an empty object is invalid 5 | "required": ["modules"], 6 | "errorMessage": { 7 | "type": "netlist must be a JSON object", 8 | "required": "netlist must have a modules property", 9 | }, 10 | "properties": { 11 | "modules": { 12 | "type": "object", 13 | // there must be at least one module 14 | "minProperties": 1, 15 | "errorMessage": { 16 | "type": "netlist modules must be objects", 17 | "minProperties": "netlist must have at least one module", 18 | }, 19 | "additionalProperties": { 20 | "type": "object", 21 | "properties": { 22 | "ports": { 23 | "type": "object", 24 | "additionalProperties": { 25 | "type": "object", 26 | // all ports must have bits and a direction 27 | "required": ["direction", "bits"], 28 | "properties": { 29 | "direction": { 30 | "enum": ["input", "output", "inout"] 31 | }, 32 | "bits": { 33 | "type": "array", 34 | // bits can be the string "0", "1", "x", "z", or a number. 35 | "items": { 36 | "oneOf":[{"type":"number"}, {"enum":["0","1","x","z"]}] 37 | } 38 | } 39 | } 40 | } 41 | }, 42 | "cells": { 43 | "type": "object", 44 | "additionalProperties": { 45 | "type": "object", 46 | // all cells must have a type and connections 47 | "required": [ 48 | "type", 49 | "connections" 50 | ], 51 | "properties": { 52 | "type":{"type":"string"}, 53 | "connections": { 54 | "type": "object", 55 | "additionalProperties": { 56 | "type":"array", 57 | "items": { 58 | "oneOf":[{"type":"number"}, {"enum":["0","1","x","z"]}] 59 | } 60 | } 61 | }, 62 | // port directions are optional 63 | "port_directions":{ 64 | "type": "object", 65 | "additionalProperties": { 66 | "enum": ["input", "output", "inout"] 67 | } 68 | }, 69 | // netlistsvg doesn't use these yet 70 | "hide_name": {"enum":[0, 1]}, 71 | "parameters": {"type": "object"}, 72 | "attributes": {"type": "object"} 73 | } 74 | } 75 | }, 76 | // not yet used by netlistsvg 77 | "netnames": { 78 | "type": "object", 79 | "additionalProperties": { 80 | "type": "object", 81 | "properties": { 82 | "bits": { 83 | "type": "array", 84 | // bits can be the string "0", "1", "x", "z", or a number. 85 | "items": { 86 | "oneOf": [{"type": "number"}, {"enum": ["0", "1", "x", "z"]}] 87 | } 88 | }, 89 | "hide_name": {"enum": [0, 1]}, 90 | "attributes": {"type": "object"} 91 | } 92 | } 93 | }, 94 | "attributes": { 95 | "type": "object", 96 | "properties": { 97 | "top": {"enum": [0, 1, "00000000000000000000000000000000", "00000000000000000000000000000001"]} 98 | } 99 | } 100 | }, 101 | // there must either be ports or cells attribute 102 | "anyOf": [{"required": ["ports"]},{"required": ["cells"]}] 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/analog/common_emitter_full.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": { 3 | "Common Emitter": { 4 | "cells": { 5 | "C1": { 6 | "connections": { 7 | "A": [ 8 | 12 9 | ], 10 | "B": [ 11 | 85 12 | ] 13 | }, 14 | "port_directions": { 15 | "A": "input", 16 | "B": "output" 17 | }, 18 | "type": "c_v" 19 | }, 20 | "C2": { 21 | "connections": { 22 | "A": [ 23 | 70 24 | ], 25 | "B": [ 26 | 10 27 | ] 28 | }, 29 | "port_directions": { 30 | "A": "input", 31 | "B": "output" 32 | }, 33 | "type": "c_h" 34 | }, 35 | "Q": { 36 | "connections": { 37 | "B": [ 38 | 10 39 | ], 40 | "C": [ 41 | 18 42 | ], 43 | "E": [ 44 | 12 45 | ] 46 | }, 47 | "port_directions": { 48 | "B": "input", 49 | "C": "input", 50 | "E": "output" 51 | }, 52 | "type": "q_npn" 53 | }, 54 | "R1": { 55 | "connections": { 56 | "A": [ 57 | 80 58 | ], 59 | "B": [ 60 | 18 61 | ] 62 | }, 63 | "port_directions": { 64 | "A": "input", 65 | "B": "output" 66 | }, 67 | "type": "r_v" 68 | }, 69 | "R2": { 70 | "connections": { 71 | "A": [ 72 | 12 73 | ], 74 | "B": [ 75 | 83 76 | ] 77 | }, 78 | "port_directions": { 79 | "A": "input", 80 | "B": "output" 81 | }, 82 | "type": "r_v" 83 | }, 84 | "R3": { 85 | "connections": { 86 | "A": [ 87 | 86 88 | ], 89 | "B": [ 90 | 10 91 | ] 92 | }, 93 | "port_directions": { 94 | "A": "input", 95 | "B": "output" 96 | }, 97 | "type": "r_v" 98 | }, 99 | "R4": { 100 | "connections": { 101 | "A": [ 102 | 10 103 | ], 104 | "B": [ 105 | 89 106 | ] 107 | }, 108 | "port_directions": { 109 | "A": "input", 110 | "B": "output" 111 | }, 112 | "type": "r_v" 113 | }, 114 | "gnd83": { 115 | "connections": { 116 | "A": [ 117 | 83 118 | ] 119 | }, 120 | "port_directions": { 121 | "A": "input" 122 | }, 123 | "type": "gnd" 124 | }, 125 | "gnd85": { 126 | "connections": { 127 | "A": [ 128 | 85 129 | ] 130 | }, 131 | "port_directions": { 132 | "A": "input" 133 | }, 134 | "type": "gnd" 135 | }, 136 | "gnd89": { 137 | "connections": { 138 | "A": [ 139 | 89 140 | ] 141 | }, 142 | "port_directions": { 143 | "A": "input" 144 | }, 145 | "type": "gnd" 146 | }, 147 | "vcc80": { 148 | "connections": { 149 | "A": [ 150 | 80 151 | ] 152 | }, 153 | "port_directions": { 154 | "A": "output" 155 | }, 156 | "type": "vcc" 157 | }, 158 | "vcc86": { 159 | "connections": { 160 | "A": [ 161 | 86 162 | ] 163 | }, 164 | "port_directions": { 165 | "A": "output" 166 | }, 167 | "type": "vcc" 168 | } 169 | }, 170 | "ports": { 171 | "vin": { 172 | "bits": [ 173 | 70 174 | ], 175 | "direction": "input" 176 | }, 177 | "vout": { 178 | "bits": [ 179 | 18 180 | ], 181 | "direction": "output" 182 | } 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /lib/Skin.ts: -------------------------------------------------------------------------------- 1 | 2 | import onml = require('onml'); 3 | import _ = require('lodash'); 4 | import { ElkModel } from './elkGraph'; 5 | 6 | export namespace Skin { 7 | 8 | export let skin: onml.Element = null; 9 | 10 | export function getPortsWithPrefix(template: any[], prefix: string) { 11 | const ports = _.filter(template, (e) => { 12 | try { 13 | if (e instanceof Array && e[0] === 'g') { 14 | return e[1]['s:pid'].startsWith(prefix); 15 | } 16 | } catch (exception) { 17 | // Do nothing if the SVG group doesn't have a pin id. 18 | } 19 | }); 20 | return ports; 21 | } 22 | 23 | function filterPortPids(template, filter): string[] { 24 | const ports = _.filter(template, (element: any[]) => { 25 | const tag: string = element[0]; 26 | if (element instanceof Array && tag === 'g') { 27 | const attrs: any = element[1]; 28 | return filter(attrs); 29 | } 30 | return false; 31 | }); 32 | return ports.map((port) => { 33 | return port[1]['s:pid']; 34 | }); 35 | } 36 | 37 | export function getInputPids(template): string[] { 38 | return filterPortPids(template, (attrs) => { 39 | if (attrs['s:position']) { 40 | return attrs['s:position'] === 'top'; 41 | } 42 | return false; 43 | }); 44 | } 45 | 46 | export function getOutputPids(template): string[] { 47 | return filterPortPids(template, (attrs) => { 48 | if (attrs['s:position']) { 49 | return attrs['s:position'] === 'bottom'; 50 | } 51 | return false; 52 | }); 53 | } 54 | 55 | export function getLateralPortPids(template): string[] { 56 | return filterPortPids(template, (attrs) => { 57 | if (attrs['s:dir']) { 58 | return attrs['s:dir'] === 'lateral'; 59 | } 60 | if (attrs['s:position']) { 61 | return attrs['s:position'] === 'left' || 62 | attrs['s:position'] === 'right'; 63 | } 64 | return false; 65 | }); 66 | } 67 | 68 | export function findSkinType(type: string) { 69 | let ret = null; 70 | onml.traverse(skin, { 71 | enter: (node, parent) => { 72 | if (node.name === 's:alias' && node.attr.val === type) { 73 | ret = parent; 74 | } 75 | }, 76 | }); 77 | if (ret == null) { 78 | onml.traverse(skin, { 79 | enter: (node) => { 80 | if (node.attr['s:type'] === 'generic') { 81 | ret = node; 82 | } 83 | }, 84 | }); 85 | } 86 | return ret.full; 87 | } 88 | 89 | export function getLowPriorityAliases(): string[] { 90 | const ret = []; 91 | onml.t(skin, { 92 | enter: (node) => { 93 | if (node.name === 's:low_priority_alias') { 94 | ret.push(node.attr.value); 95 | } 96 | }, 97 | }); 98 | return ret; 99 | } 100 | interface SkinProperties { 101 | [attr: string]: boolean | string | number | ElkModel.LayoutOptions; 102 | } 103 | 104 | export function getProperties(): SkinProperties { 105 | let vals; 106 | onml.t(skin, { 107 | enter: (node) => { 108 | if (node.name === 's:properties') { 109 | vals = _.mapValues(node.attr, (val: string) => { 110 | if (!isNaN(Number(val))) { 111 | return Number(val); 112 | } 113 | if (val === 'true') { 114 | return true; 115 | } 116 | if (val === 'false') { 117 | return false; 118 | } 119 | return val; 120 | }); 121 | } else if (node.name === 's:layoutEngine') { 122 | vals.layoutEngine = node.attr; 123 | } 124 | }, 125 | }); 126 | 127 | if (!vals.layoutEngine) { 128 | vals.layoutEngine = {}; 129 | } 130 | 131 | return vals; 132 | } 133 | } 134 | export default Skin; 135 | -------------------------------------------------------------------------------- /built/Skin.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Skin = void 0; 4 | var onml = require("onml"); 5 | var _ = require("lodash"); 6 | var Skin; 7 | (function (Skin) { 8 | Skin.skin = null; 9 | function getPortsWithPrefix(template, prefix) { 10 | var ports = _.filter(template, function (e) { 11 | try { 12 | if (e instanceof Array && e[0] === 'g') { 13 | return e[1]['s:pid'].startsWith(prefix); 14 | } 15 | } 16 | catch (exception) { 17 | // Do nothing if the SVG group doesn't have a pin id. 18 | } 19 | }); 20 | return ports; 21 | } 22 | Skin.getPortsWithPrefix = getPortsWithPrefix; 23 | function filterPortPids(template, filter) { 24 | var ports = _.filter(template, function (element) { 25 | var tag = element[0]; 26 | if (element instanceof Array && tag === 'g') { 27 | var attrs = element[1]; 28 | return filter(attrs); 29 | } 30 | return false; 31 | }); 32 | return ports.map(function (port) { 33 | return port[1]['s:pid']; 34 | }); 35 | } 36 | function getInputPids(template) { 37 | return filterPortPids(template, function (attrs) { 38 | if (attrs['s:position']) { 39 | return attrs['s:position'] === 'top'; 40 | } 41 | return false; 42 | }); 43 | } 44 | Skin.getInputPids = getInputPids; 45 | function getOutputPids(template) { 46 | return filterPortPids(template, function (attrs) { 47 | if (attrs['s:position']) { 48 | return attrs['s:position'] === 'bottom'; 49 | } 50 | return false; 51 | }); 52 | } 53 | Skin.getOutputPids = getOutputPids; 54 | function getLateralPortPids(template) { 55 | return filterPortPids(template, function (attrs) { 56 | if (attrs['s:dir']) { 57 | return attrs['s:dir'] === 'lateral'; 58 | } 59 | if (attrs['s:position']) { 60 | return attrs['s:position'] === 'left' || 61 | attrs['s:position'] === 'right'; 62 | } 63 | return false; 64 | }); 65 | } 66 | Skin.getLateralPortPids = getLateralPortPids; 67 | function findSkinType(type) { 68 | var ret = null; 69 | onml.traverse(Skin.skin, { 70 | enter: function (node, parent) { 71 | if (node.name === 's:alias' && node.attr.val === type) { 72 | ret = parent; 73 | } 74 | }, 75 | }); 76 | if (ret == null) { 77 | onml.traverse(Skin.skin, { 78 | enter: function (node) { 79 | if (node.attr['s:type'] === 'generic') { 80 | ret = node; 81 | } 82 | }, 83 | }); 84 | } 85 | return ret.full; 86 | } 87 | Skin.findSkinType = findSkinType; 88 | function getLowPriorityAliases() { 89 | var ret = []; 90 | onml.t(Skin.skin, { 91 | enter: function (node) { 92 | if (node.name === 's:low_priority_alias') { 93 | ret.push(node.attr.value); 94 | } 95 | }, 96 | }); 97 | return ret; 98 | } 99 | Skin.getLowPriorityAliases = getLowPriorityAliases; 100 | function getProperties() { 101 | var vals; 102 | onml.t(Skin.skin, { 103 | enter: function (node) { 104 | if (node.name === 's:properties') { 105 | vals = _.mapValues(node.attr, function (val) { 106 | if (!isNaN(Number(val))) { 107 | return Number(val); 108 | } 109 | if (val === 'true') { 110 | return true; 111 | } 112 | if (val === 'false') { 113 | return false; 114 | } 115 | return val; 116 | }); 117 | } 118 | else if (node.name === 's:layoutEngine') { 119 | vals.layoutEngine = node.attr; 120 | } 121 | }, 122 | }); 123 | if (!vals.layoutEngine) { 124 | vals.layoutEngine = {}; 125 | } 126 | return vals; 127 | } 128 | Skin.getProperties = getProperties; 129 | })(Skin = exports.Skin || (exports.Skin = {})); 130 | exports.default = Skin; 131 | -------------------------------------------------------------------------------- /doc/hyperedges.svg: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | i0 24 | 25 | 26 | 27 | 28 | 29 | i1 30 | 31 | 32 | 33 | 34 | 35 | i2 36 | 37 | 38 | 39 | 40 | 41 | i3 42 | 43 | 44 | 45 | 46 | 47 | i4 48 | 49 | 50 | 51 | 52 | 53 | o0 54 | 55 | 56 | 57 | 58 | 59 | o1 60 | 61 | 62 | 63 | 64 | 65 | o2 66 | 67 | 68 | 69 | 70 | 71 | o3 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /doc/mcu.svg: -------------------------------------------------------------------------------- 1 | 2 | 32 | 33 | 34 | r1 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | c1 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | r3 51 | 52 | 53 | 54 | 55 | 56 | 57 | MCU 58 | 59 | 60 | VCC 61 | 62 | 63 | GND 64 | 65 | 66 | GPIO1 67 | 68 | 69 | 70 | 71 | VCC 72 | 73 | 74 | 75 | 76 | 77 | VCC 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /doc/ports_splitjoin.svg: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | inthing 24 | 25 | 26 | 27 | 28 | 29 | outthing 30 | 31 | 32 | 33 | 34 | 35 | outthing2 36 | 37 | 38 | 39 | 40 | 41 | outthing3 42 | 43 | 44 | 45 | 46 | 47 | outthing4 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 0:1 58 | 59 | 60 | 2:3 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 0:1 69 | 70 | 71 | 2 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 0:1 80 | 81 | 82 | 3 83 | 84 | 85 | 0 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /built/Port.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Port = void 0; 4 | var Cell_1 = require("./Cell"); 5 | var _ = require("lodash"); 6 | var Port = /** @class */ (function () { 7 | function Port(key, value) { 8 | this.key = key; 9 | this.value = value; 10 | } 11 | Object.defineProperty(Port.prototype, "Key", { 12 | get: function () { 13 | return this.key; 14 | }, 15 | enumerable: false, 16 | configurable: true 17 | }); 18 | Port.prototype.keyIn = function (pids) { 19 | return _.includes(pids, this.key); 20 | }; 21 | Port.prototype.maxVal = function () { 22 | return _.max(_.map(this.value, function (v) { return Number(v); })); 23 | }; 24 | Port.prototype.valString = function () { 25 | return ',' + this.value.join() + ','; 26 | }; 27 | Port.prototype.findConstants = function (sigsByConstantName, maxNum, constantCollector) { 28 | var _this = this; 29 | var constNameCollector = ''; 30 | var constNumCollector = []; 31 | var portSigs = this.value; 32 | portSigs.forEach(function (portSig, portSigIndex) { 33 | // is constant? 34 | if (portSig === '0' || portSig === '1') { 35 | maxNum += 1; 36 | constNameCollector += portSig; 37 | // replace the constant with new signal num 38 | portSigs[portSigIndex] = maxNum; 39 | constNumCollector.push(maxNum); 40 | // string of constants ended before end of p.value 41 | } 42 | else if (constNumCollector.length > 0) { 43 | _this.assignConstant(constNameCollector, constNumCollector, portSigIndex, sigsByConstantName, constantCollector); 44 | // reset name and num collectors 45 | constNameCollector = ''; 46 | constNumCollector = []; 47 | } 48 | }); 49 | if (constNumCollector.length > 0) { 50 | this.assignConstant(constNameCollector, constNumCollector, portSigs.length, sigsByConstantName, constantCollector); 51 | } 52 | return maxNum; 53 | }; 54 | Port.prototype.getGenericElkPort = function (index, templatePorts, dir) { 55 | var nkey = this.parentNode.Key; 56 | var type = this.parentNode.getTemplate()[1]['s:type']; 57 | if (index === 0) { 58 | var ret = { 59 | id: nkey + '.' + this.key, 60 | width: 1, 61 | height: 1, 62 | x: Number(templatePorts[0][1]['s:x']), 63 | y: Number(templatePorts[0][1]['s:y']), 64 | }; 65 | if ((type === 'generic' || type === 'join') && dir === 'in') { 66 | ret.labels = [{ 67 | id: nkey + '.' + this.key + '.label', 68 | text: this.key, 69 | x: Number(templatePorts[0][2][1].x) - 10, 70 | y: Number(templatePorts[0][2][1].y) - 6, 71 | width: (6 * this.key.length), 72 | height: 11, 73 | }]; 74 | } 75 | if ((type === 'generic' || type === 'split') && dir === 'out') { 76 | ret.labels = [{ 77 | id: nkey + '.' + this.key + '.label', 78 | text: this.key, 79 | x: Number(templatePorts[0][2][1].x) - 10, 80 | y: Number(templatePorts[0][2][1].y) - 6, 81 | width: (6 * this.key.length), 82 | height: 11, 83 | }]; 84 | } 85 | return ret; 86 | } 87 | else { 88 | var gap = Number(templatePorts[1][1]['s:y']) - Number(templatePorts[0][1]['s:y']); 89 | var ret = { 90 | id: nkey + '.' + this.key, 91 | width: 1, 92 | height: 1, 93 | x: Number(templatePorts[0][1]['s:x']), 94 | y: (index) * gap + Number(templatePorts[0][1]['s:y']), 95 | }; 96 | if (type === 'generic') { 97 | ret.labels = [{ 98 | id: nkey + '.' + this.key + '.label', 99 | text: this.key, 100 | x: Number(templatePorts[0][2][1].x) - 10, 101 | y: Number(templatePorts[0][2][1].y) - 6, 102 | width: (6 * this.key.length), 103 | height: 11, 104 | }]; 105 | } 106 | return ret; 107 | } 108 | }; 109 | Port.prototype.assignConstant = function (nameCollector, constants, currIndex, signalsByConstantName, constantCollector) { 110 | var _this = this; 111 | // we've been appending to nameCollector, so reverse to get const name 112 | var constName = nameCollector.split('').reverse().join(''); 113 | // if the constant has already been used 114 | if (signalsByConstantName.hasOwnProperty(constName)) { 115 | var constSigs = signalsByConstantName[constName]; 116 | // go back and fix signal values 117 | var constLength_1 = constSigs.length; 118 | constSigs.forEach(function (constSig, constIndex) { 119 | // i is where in port_signals we need to update 120 | var i = currIndex - constLength_1 + constIndex; 121 | _this.value[i] = constSig; 122 | }); 123 | } 124 | else { 125 | constantCollector.push(Cell_1.default.fromConstantInfo(constName, constants)); 126 | signalsByConstantName[constName] = constants; 127 | } 128 | }; 129 | return Port; 130 | }()); 131 | exports.Port = Port; 132 | -------------------------------------------------------------------------------- /test/digital/pc.json: -------------------------------------------------------------------------------- 1 | { 2 | "creator": "Yosys 0.9+4303 (open-tool-forge build) (git sha1 c88eaea6, gcc 9.3.0-17ubuntu1~20.04 -Os)", 3 | "modules": { 4 | "PC": { 5 | "attributes": { 6 | }, 7 | "ports": { 8 | "clk": { 9 | "direction": "input", 10 | "bits": [ 2 ] 11 | }, 12 | "write_enable": { 13 | "direction": "input", 14 | "bits": [ 3 ] 15 | }, 16 | "rst": { 17 | "direction": "input", 18 | "bits": [ 4 ] 19 | }, 20 | "AddressIn": { 21 | "direction": "input", 22 | "bits": [ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68 ] 23 | }, 24 | "AddressOut": { 25 | "direction": "output", 26 | "bits": [ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132 ] 27 | } 28 | }, 29 | "cells": { 30 | "\\10": { 31 | "hide_name": 0, 32 | "type": "$adff", 33 | "parameters": { 34 | "ARST_POLARITY": "00000000000000000000000000000001", 35 | "ARST_VALUE": "0000000000000000000000000000000000000000000000000000000000000000", 36 | "CLK_POLARITY": "00000000000000000000000000000001", 37 | "WIDTH": "00000000000000000000000001000000" 38 | }, 39 | "attributes": { 40 | }, 41 | "port_directions": { 42 | "ARST": "input", 43 | "CLK": "input", 44 | "D": "input", 45 | "Q": "output" 46 | }, 47 | "connections": { 48 | "ARST": [ 4 ], 49 | "CLK": [ 2 ], 50 | "D": [ 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196 ], 51 | "Q": [ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132 ] 52 | } 53 | }, 54 | "\\9": { 55 | "hide_name": 0, 56 | "type": "$mux", 57 | "parameters": { 58 | "WIDTH": "00000000000000000000000001000000" 59 | }, 60 | "attributes": { 61 | }, 62 | "port_directions": { 63 | "A": "input", 64 | "B": "input", 65 | "S": "input", 66 | "Y": "output" 67 | }, 68 | "connections": { 69 | "A": [ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132 ], 70 | "B": [ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68 ], 71 | "S": [ 3 ], 72 | "Y": [ 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196 ] 73 | } 74 | } 75 | }, 76 | "netnames": { 77 | "$auto$ghdl.cc:762:import_module$1": { 78 | "hide_name": 1, 79 | "bits": [ 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196 ], 80 | "attributes": { 81 | } 82 | }, 83 | "AddressIn": { 84 | "hide_name": 0, 85 | "bits": [ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68 ], 86 | "attributes": { 87 | } 88 | }, 89 | "AddressOut": { 90 | "hide_name": 0, 91 | "bits": [ 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132 ], 92 | "attributes": { 93 | } 94 | }, 95 | "clk": { 96 | "hide_name": 0, 97 | "bits": [ 2 ], 98 | "attributes": { 99 | } 100 | }, 101 | "rst": { 102 | "hide_name": 0, 103 | "bits": [ 4 ], 104 | "attributes": { 105 | } 106 | }, 107 | "write_enable": { 108 | "hide_name": 0, 109 | "bits": [ 3 ], 110 | "attributes": { 111 | } 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/Port.ts: -------------------------------------------------------------------------------- 1 | import Cell from './Cell'; 2 | import {SigsByConstName} from './FlatModule'; 3 | import Yosys from './YosysModel'; 4 | import _ = require('lodash'); 5 | import { ElkModel } from './elkGraph'; 6 | 7 | export class Port { 8 | public parentNode?: Cell; 9 | private key: string; 10 | private value: number[] | Yosys.Signals; 11 | 12 | constructor(key: string, value: number[] | Yosys.Signals) { 13 | this.key = key; 14 | this.value = value; 15 | } 16 | 17 | public get Key() { 18 | return this.key; 19 | } 20 | 21 | public keyIn(pids: string[]): boolean { 22 | return _.includes(pids, this.key); 23 | } 24 | 25 | public maxVal() { 26 | return _.max(_.map(this.value, (v) => Number(v))); 27 | } 28 | 29 | public valString() { 30 | return ',' + this.value.join() + ','; 31 | } 32 | 33 | public findConstants(sigsByConstantName: SigsByConstName, 34 | maxNum: number, 35 | constantCollector: Cell[]): number { 36 | let constNameCollector = ''; 37 | let constNumCollector: number[] = []; 38 | const portSigs: Yosys.Signals = this.value; 39 | portSigs.forEach((portSig, portSigIndex) => { 40 | // is constant? 41 | if (portSig === '0' || portSig === '1') { 42 | maxNum += 1; 43 | constNameCollector += portSig; 44 | // replace the constant with new signal num 45 | portSigs[portSigIndex] = maxNum; 46 | constNumCollector.push(maxNum); 47 | // string of constants ended before end of p.value 48 | } else if (constNumCollector.length > 0) { 49 | this.assignConstant( 50 | constNameCollector, 51 | constNumCollector, 52 | portSigIndex, 53 | sigsByConstantName, 54 | constantCollector); 55 | // reset name and num collectors 56 | constNameCollector = ''; 57 | constNumCollector = []; 58 | } 59 | }); 60 | if (constNumCollector.length > 0) { 61 | this.assignConstant( 62 | constNameCollector, 63 | constNumCollector, 64 | portSigs.length, 65 | sigsByConstantName, 66 | constantCollector); 67 | } 68 | return maxNum; 69 | } 70 | 71 | public getGenericElkPort( 72 | index: number, 73 | templatePorts: any[], 74 | dir: string, 75 | ): ElkModel.Port { 76 | const nkey = this.parentNode.Key; 77 | const type = this.parentNode.getTemplate()[1]['s:type']; 78 | if (index === 0) { 79 | const ret: ElkModel.Port = { 80 | id: nkey + '.' + this.key, 81 | width: 1, 82 | height: 1, 83 | x: Number(templatePorts[0][1]['s:x']), 84 | y: Number(templatePorts[0][1]['s:y']), 85 | }; 86 | 87 | if ((type === 'generic' || type === 'join') && dir === 'in') { 88 | ret.labels = [{ 89 | id: nkey + '.' + this.key + '.label', 90 | text: this.key, 91 | x: Number(templatePorts[0][2][1].x) - 10, 92 | y: Number(templatePorts[0][2][1].y) - 6, 93 | width: (6 * this.key.length), 94 | height: 11, 95 | }]; 96 | } 97 | 98 | if ((type === 'generic' || type === 'split') && dir === 'out') { 99 | ret.labels = [{ 100 | id: nkey + '.' + this.key + '.label', 101 | text: this.key, 102 | x: Number(templatePorts[0][2][1].x) - 10, 103 | y: Number(templatePorts[0][2][1].y) - 6, 104 | width: (6 * this.key.length), 105 | height: 11, 106 | }]; 107 | } 108 | return ret; 109 | } else { 110 | const gap: number = Number(templatePorts[1][1]['s:y']) - Number(templatePorts[0][1]['s:y']); 111 | const ret: ElkModel.Port = { 112 | id: nkey + '.' + this.key, 113 | width: 1, 114 | height: 1, 115 | x: Number(templatePorts[0][1]['s:x']), 116 | y: (index) * gap + Number(templatePorts[0][1]['s:y']), 117 | }; 118 | if (type === 'generic') { 119 | ret.labels = [{ 120 | id: nkey + '.' + this.key + '.label', 121 | text: this.key, 122 | x: Number(templatePorts[0][2][1].x) - 10, 123 | y: Number(templatePorts[0][2][1].y) - 6, 124 | width: (6 * this.key.length), 125 | height: 11, 126 | }]; 127 | } 128 | return ret; 129 | } 130 | } 131 | 132 | private assignConstant(nameCollector: string, 133 | constants: number[], 134 | currIndex: number, 135 | signalsByConstantName: SigsByConstName, 136 | constantCollector: Cell[]) { 137 | // we've been appending to nameCollector, so reverse to get const name 138 | const constName = nameCollector.split('').reverse().join(''); 139 | // if the constant has already been used 140 | if (signalsByConstantName.hasOwnProperty(constName)) { 141 | const constSigs: number[] = signalsByConstantName[constName]; 142 | // go back and fix signal values 143 | const constLength = constSigs.length; 144 | constSigs.forEach((constSig, constIndex) => { 145 | // i is where in port_signals we need to update 146 | const i: number = currIndex - constLength + constIndex; 147 | this.value[i] = constSig; 148 | }); 149 | } else { 150 | constantCollector.push(Cell.fromConstantInfo(constName, constants)); 151 | signalsByConstantName[constName] = constants; 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /test/digital/mux4.json: -------------------------------------------------------------------------------- 1 | { 2 | "creator": "Yosys 0.7+381 (git sha1 8f2638ae, gcc 6.3.0-18 -fPIC -Os)", 3 | "modules": { 4 | "MUX2": { 5 | "attributes": { 6 | "src": "../mux2/sim.v:1" 7 | }, 8 | "ports": { 9 | "I0": { 10 | "direction": "input", 11 | "bits": [ 2 ] 12 | }, 13 | "I1": { 14 | "direction": "input", 15 | "bits": [ 3 ] 16 | }, 17 | "S0": { 18 | "direction": "input", 19 | "bits": [ 4 ] 20 | }, 21 | "O": { 22 | "direction": "output", 23 | "bits": [ 5 ] 24 | } 25 | }, 26 | "cells": { 27 | "$ternary$../mux2/sim.v:7$1": { 28 | "hide_name": 1, 29 | "type": "$mux", 30 | "parameters": { 31 | "WIDTH": 1 32 | }, 33 | "attributes": { 34 | "src": "../mux2/sim.v:7" 35 | }, 36 | "port_directions": { 37 | "A": "input", 38 | "B": "input", 39 | "S": "input", 40 | "Y": "output" 41 | }, 42 | "connections": { 43 | "A": [ 3 ], 44 | "B": [ 2 ], 45 | "S": [ 4 ], 46 | "Y": [ 5 ] 47 | } 48 | } 49 | }, 50 | "netnames": { 51 | "$ternary$../mux2/sim.v:7$1_Y": { 52 | "hide_name": 1, 53 | "bits": [ 5 ], 54 | "attributes": { 55 | "src": "../mux2/sim.v:7" 56 | } 57 | }, 58 | "I0": { 59 | "hide_name": 0, 60 | "bits": [ 2 ], 61 | "attributes": { 62 | "src": "../mux2/sim.v:2" 63 | } 64 | }, 65 | "I1": { 66 | "hide_name": 0, 67 | "bits": [ 3 ], 68 | "attributes": { 69 | "src": "../mux2/sim.v:3" 70 | } 71 | }, 72 | "O": { 73 | "hide_name": 0, 74 | "bits": [ 5 ], 75 | "attributes": { 76 | "src": "../mux2/sim.v:5" 77 | } 78 | }, 79 | "S0": { 80 | "hide_name": 0, 81 | "bits": [ 4 ], 82 | "attributes": { 83 | "src": "../mux2/sim.v:4" 84 | } 85 | } 86 | } 87 | }, 88 | "MUX4": { 89 | "attributes": { 90 | "top": 1, 91 | "src": "sim.v:3" 92 | }, 93 | "ports": { 94 | "I0": { 95 | "direction": "input", 96 | "bits": [ 2 ] 97 | }, 98 | "I1": { 99 | "direction": "input", 100 | "bits": [ 3 ] 101 | }, 102 | "I2": { 103 | "direction": "input", 104 | "bits": [ 4 ] 105 | }, 106 | "I3": { 107 | "direction": "input", 108 | "bits": [ 5 ] 109 | }, 110 | "S0": { 111 | "direction": "input", 112 | "bits": [ 6 ] 113 | }, 114 | "S1": { 115 | "direction": "input", 116 | "bits": [ 7 ] 117 | }, 118 | "O": { 119 | "direction": "output", 120 | "bits": [ 8 ] 121 | } 122 | }, 123 | "cells": { 124 | "mux0": { 125 | "hide_name": 0, 126 | "type": "MUX2", 127 | "parameters": { 128 | }, 129 | "attributes": { 130 | "src": "sim.v:15" 131 | }, 132 | "port_directions": { 133 | "I0": "input", 134 | "I1": "input", 135 | "O": "output", 136 | "S0": "input" 137 | }, 138 | "connections": { 139 | "I0": [ 2 ], 140 | "I1": [ 3 ], 141 | "O": [ 9 ], 142 | "S0": [ 6 ] 143 | } 144 | }, 145 | "mux1": { 146 | "hide_name": 0, 147 | "type": "MUX2", 148 | "parameters": { 149 | }, 150 | "attributes": { 151 | "src": "sim.v:16" 152 | }, 153 | "port_directions": { 154 | "I0": "input", 155 | "I1": "input", 156 | "O": "output", 157 | "S0": "input" 158 | }, 159 | "connections": { 160 | "I0": [ 4 ], 161 | "I1": [ 5 ], 162 | "O": [ 10 ], 163 | "S0": [ 6 ] 164 | } 165 | }, 166 | "mux_out": { 167 | "hide_name": 0, 168 | "type": "MUX2", 169 | "parameters": { 170 | }, 171 | "attributes": { 172 | "src": "sim.v:17" 173 | }, 174 | "port_directions": { 175 | "I0": "input", 176 | "I1": "input", 177 | "O": "output", 178 | "S0": "input" 179 | }, 180 | "connections": { 181 | "I0": [ 9 ], 182 | "I1": [ 10 ], 183 | "O": [ 8 ], 184 | "S0": [ 7 ] 185 | } 186 | } 187 | }, 188 | "netnames": { 189 | "I0": { 190 | "hide_name": 0, 191 | "bits": [ 2 ], 192 | "attributes": { 193 | "src": "sim.v:4" 194 | } 195 | }, 196 | "I1": { 197 | "hide_name": 0, 198 | "bits": [ 3 ], 199 | "attributes": { 200 | "src": "sim.v:5" 201 | } 202 | }, 203 | "I2": { 204 | "hide_name": 0, 205 | "bits": [ 4 ], 206 | "attributes": { 207 | "src": "sim.v:6" 208 | } 209 | }, 210 | "I3": { 211 | "hide_name": 0, 212 | "bits": [ 5 ], 213 | "attributes": { 214 | "src": "sim.v:7" 215 | } 216 | }, 217 | "O": { 218 | "hide_name": 0, 219 | "bits": [ 8 ], 220 | "attributes": { 221 | "src": "sim.v:10" 222 | } 223 | }, 224 | "S0": { 225 | "hide_name": 0, 226 | "bits": [ 6 ], 227 | "attributes": { 228 | "src": "sim.v:8" 229 | } 230 | }, 231 | "S1": { 232 | "hide_name": 0, 233 | "bits": [ 7 ], 234 | "attributes": { 235 | "src": "sim.v:9" 236 | } 237 | }, 238 | "m0": { 239 | "hide_name": 0, 240 | "bits": [ 9 ], 241 | "attributes": { 242 | "src": "sim.v:12" 243 | } 244 | }, 245 | "m1": { 246 | "hide_name": 0, 247 | "bits": [ 10 ], 248 | "attributes": { 249 | "src": "sim.v:13" 250 | } 251 | } 252 | } 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /test/drawModule.test.ts: -------------------------------------------------------------------------------- 1 | import { ElkModel } from '../lib/elkGraph'; 2 | import { removeDummyEdges } from '../lib/drawModule'; 3 | import _ = require('lodash'); 4 | 5 | test('remove dummy edges outputs', () => { 6 | const e2end = {x: 249, y: 162}; 7 | const e3end = {x: 249, y: 97}; 8 | const e4end = {x: 249, y: 32}; 9 | const dummyPos = {x: 214, y: 32}; 10 | const highJunct = {x: 224, y: 32}; 11 | const lowJunct = {x: 224, y: 97}; 12 | 13 | const testGraph: ElkModel.Graph = { 14 | id: 'fake id', 15 | children: [], 16 | edges: 17 | [{ 18 | id: 'e2', 19 | source: '$d_0', 20 | sourcePort: '$d_0.p', 21 | target: 'o1', 22 | targetPort: 'o1.A', 23 | sections: [ 24 | { 25 | id: 'e2_s0', 26 | startPoint: dummyPos, 27 | endPoint: e2end, 28 | bendPoints: [ 29 | {x: 224, y: 32}, 30 | {x: 224, y: 162}, 31 | ], 32 | }, 33 | ], 34 | junctionPoints: [highJunct], 35 | }, 36 | { 37 | id: 'e3', 38 | source: '$d_0', 39 | sourcePort: '$d_0.p', 40 | target: 'o2', 41 | targetPort: 'o2.A', 42 | sections: [ 43 | { 44 | id: 'e3_s0', 45 | startPoint: dummyPos, 46 | endPoint: e3end, 47 | bendPoints: [ 48 | {x: 224, y: 32}, 49 | {x: 224, y: 97}, 50 | ], 51 | }, 52 | ], 53 | junctionPoints: [lowJunct], 54 | }, 55 | { 56 | id: 'e4', 57 | source: '$d_0', 58 | sourcePort: '$d_0.p', 59 | target: 'o3', 60 | targetPort: 'o3.A', 61 | sections: [ 62 | { 63 | id: 'e4_s0', 64 | startPoint: dummyPos, 65 | endPoint: e4end, 66 | }, 67 | ], 68 | }, 69 | ], 70 | }; 71 | removeDummyEdges(testGraph); 72 | const e2 = testGraph.edges.find((edge) => edge.id === 'e2') as ElkModel.Edge; 73 | const e3 = testGraph.edges.find((edge) => edge.id === 'e3') as ElkModel.Edge; 74 | const e4 = testGraph.edges.find((edge) => edge.id === 'e4') as ElkModel.Edge; 75 | // edge end points should stay the same 76 | expect(e2.sections[0].endPoint).toEqual(e2end); 77 | expect(e3.sections[0].endPoint).toEqual(e3end); 78 | expect(e4.sections[0].endPoint).toEqual(e4end); 79 | // edge start points should be lowjunct 80 | expect(e2.sections[0].startPoint).toEqual(highJunct); 81 | expect(e3.sections[0].startPoint).toEqual(highJunct); 82 | expect(e4.sections[0].startPoint).toEqual(highJunct); 83 | const junctionPoints = _.flatMap(testGraph.edges, (edge) => (edge as ElkModel.Edge).junctionPoints || []); 84 | expect(junctionPoints.length).toEqual(1); 85 | }); 86 | 87 | test('remove dummy edges inputs', () => { 88 | // this test case came from hyperedges.json 89 | const e5Start = {x: 159, y: 162}; 90 | const e6Start = {x: 159, y: 97}; 91 | const e7Start = {x: 159, y: 32}; 92 | const initEnd = {x: 202, y: 97}; 93 | const junctPoint = {x: 177, y: 97}; 94 | const testGraph: ElkModel.Graph = { 95 | id: 'fake id', 96 | children: [], 97 | edges: 98 | [ 99 | { 100 | id: 'e5', 101 | source: 'i0', 102 | sourcePort: 'i0.Y', 103 | target: '$d_0', 104 | targetPort: '$d_0.p', 105 | sections: 106 | [ 107 | { 108 | id: 'e5_s0', 109 | startPoint: e5Start, 110 | endPoint: initEnd, 111 | bendPoints: [ 112 | {x: 177, y: 162}, 113 | {x: 177, y: 97}, 114 | ], 115 | }, 116 | ], 117 | }, 118 | { 119 | id: 'e6', 120 | source: 'i1', 121 | sourcePort: 'i1.Y', 122 | target: '$d_0', 123 | targetPort: '$d_0.p', 124 | sections: 125 | [ 126 | { 127 | id: 'e6_s0', 128 | startPoint: e6Start, 129 | endPoint: initEnd, 130 | }, 131 | ], 132 | }, 133 | { 134 | id: 'e7', 135 | source: 'i4', 136 | sourcePort: 'i4.Y', 137 | target: '$d_0', 138 | targetPort: '$d_0.p', 139 | sections: 140 | [ 141 | { 142 | id: 'e7_s0', 143 | startPoint: e7Start, 144 | endPoint: initEnd, 145 | bendPoints: 146 | [ 147 | {x: 177, y: 32}, 148 | {x: 177, y: 97}, 149 | ], 150 | }, 151 | ], 152 | junctionPoints: [junctPoint], 153 | }, 154 | ], 155 | }; 156 | removeDummyEdges(testGraph); 157 | const e5 = testGraph.edges.find((edge) => edge.id === 'e5') as ElkModel.Edge; 158 | const e6 = testGraph.edges.find((edge) => edge.id === 'e6') as ElkModel.Edge; 159 | const e7 = testGraph.edges.find((edge) => edge.id === 'e7') as ElkModel.Edge; 160 | // edge start points should stay the same 161 | expect(e5.sections[0].startPoint).toEqual(e5Start); 162 | expect(e6.sections[0].startPoint).toEqual(e6Start); 163 | expect(e7.sections[0].startPoint).toEqual(e7Start); 164 | // edge end points should now be the junction 165 | expect(e5.sections[0].endPoint).toEqual(junctPoint); 166 | expect(e6.sections[0].endPoint).toEqual(junctPoint); 167 | expect(e7.sections[0].endPoint).toEqual(junctPoint); 168 | // there should still be one junction 169 | const junctionPoints = _.flatMap(testGraph.edges, (edge) => (edge as ElkModel.Edge).junctionPoints || []); 170 | expect(junctionPoints.length).toEqual(1); 171 | }); 172 | -------------------------------------------------------------------------------- /built/elkGraph.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.buildElkGraph = exports.ElkModel = void 0; 4 | var _ = require("lodash"); 5 | var ElkModel; 6 | (function (ElkModel) { 7 | ElkModel.wireNameLookup = {}; 8 | ElkModel.dummyNum = 0; 9 | ElkModel.edgeIndex = 0; 10 | })(ElkModel = exports.ElkModel || (exports.ElkModel = {})); 11 | function buildElkGraph(module) { 12 | var children = module.nodes.map(function (n) { 13 | return n.buildElkChild(); 14 | }); 15 | ElkModel.edgeIndex = 0; 16 | ElkModel.dummyNum = 0; 17 | var edges = _.flatMap(module.wires, function (w) { 18 | var numWires = w.netName.split(',').length - 2; 19 | // at least one driver and at least one rider and no laterals 20 | if (w.drivers.length > 0 && w.riders.length > 0 && w.laterals.length === 0) { 21 | var ret = []; 22 | route(w.drivers, w.riders, ret, numWires); 23 | return ret; 24 | // at least one driver or rider and at least one lateral 25 | } 26 | else if (w.drivers.concat(w.riders).length > 0 && w.laterals.length > 0) { 27 | var ret = []; 28 | route(w.drivers, w.laterals, ret, numWires); 29 | route(w.laterals, w.riders, ret, numWires); 30 | return ret; 31 | // at least two drivers and no riders 32 | } 33 | else if (w.riders.length === 0 && w.drivers.length > 1) { 34 | // create a dummy node and add it to children 35 | var dummyId_1 = addDummy(children); 36 | ElkModel.dummyNum += 1; 37 | var dummyEdges = w.drivers.map(function (driver) { 38 | var sourceParentKey = driver.parentNode.Key; 39 | var id = 'e' + String(ElkModel.edgeIndex); 40 | ElkModel.edgeIndex += 1; 41 | var d = { 42 | id: id, 43 | source: sourceParentKey, 44 | sourcePort: sourceParentKey + '.' + driver.key, 45 | target: dummyId_1, 46 | targetPort: dummyId_1 + '.p', 47 | }; 48 | ElkModel.wireNameLookup[id] = driver.wire.netName; 49 | return d; 50 | }); 51 | return dummyEdges; 52 | // at least one rider and no drivers 53 | } 54 | else if (w.riders.length > 1 && w.drivers.length === 0) { 55 | // create a dummy node and add it to children 56 | var dummyId_2 = addDummy(children); 57 | ElkModel.dummyNum += 1; 58 | var dummyEdges = w.riders.map(function (rider) { 59 | var sourceParentKey = rider.parentNode.Key; 60 | var id = 'e' + String(ElkModel.edgeIndex); 61 | ElkModel.edgeIndex += 1; 62 | var edge = { 63 | id: id, 64 | source: dummyId_2, 65 | sourcePort: dummyId_2 + '.p', 66 | target: sourceParentKey, 67 | targetPort: sourceParentKey + '.' + rider.key, 68 | }; 69 | ElkModel.wireNameLookup[id] = rider.wire.netName; 70 | return edge; 71 | }); 72 | return dummyEdges; 73 | } 74 | else if (w.laterals.length > 1) { 75 | var source_1 = w.laterals[0]; 76 | var sourceParentKey_1 = source_1.parentNode.Key; 77 | var lateralEdges = w.laterals.slice(1).map(function (lateral) { 78 | var lateralParentKey = lateral.parentNode.Key; 79 | var id = 'e' + String(ElkModel.edgeIndex); 80 | ElkModel.edgeIndex += 1; 81 | var edge = { 82 | id: id, 83 | source: sourceParentKey_1, 84 | sourcePort: sourceParentKey_1 + '.' + source_1.key, 85 | target: lateralParentKey, 86 | targetPort: lateralParentKey + '.' + lateral.key, 87 | }; 88 | ElkModel.wireNameLookup[id] = lateral.wire.netName; 89 | return edge; 90 | }); 91 | return lateralEdges; 92 | } 93 | // for only one driver or only one rider, don't create any edges 94 | return []; 95 | }); 96 | return { 97 | id: module.moduleName, 98 | children: children, 99 | edges: edges, 100 | }; 101 | } 102 | exports.buildElkGraph = buildElkGraph; 103 | function addDummy(children) { 104 | var dummyId = '$d_' + String(ElkModel.dummyNum); 105 | var child = { 106 | id: dummyId, 107 | width: 0, 108 | height: 0, 109 | ports: [{ 110 | id: dummyId + '.p', 111 | width: 0, 112 | height: 0, 113 | }], 114 | layoutOptions: { 'org.eclipse.elk.portConstraints': 'FIXED_SIDE' }, 115 | }; 116 | children.push(child); 117 | return dummyId; 118 | } 119 | function route(sourcePorts, targetPorts, edges, numWires) { 120 | var newEdges = (_.flatMap(sourcePorts, function (sourcePort) { 121 | var sourceParentKey = sourcePort.parentNode.key; 122 | var sourceKey = sourceParentKey + '.' + sourcePort.key; 123 | var edgeLabel; 124 | if (numWires > 1) { 125 | edgeLabel = [{ 126 | id: '', 127 | text: String(numWires), 128 | width: 4, 129 | height: 6, 130 | x: 0, 131 | y: 0, 132 | layoutOptions: { 133 | 'org.eclipse.elk.edgeLabels.inline': true, 134 | }, 135 | }]; 136 | } 137 | return targetPorts.map(function (targetPort) { 138 | var targetParentKey = targetPort.parentNode.key; 139 | var targetKey = targetParentKey + '.' + targetPort.key; 140 | var id = 'e' + ElkModel.edgeIndex; 141 | var edge = { 142 | id: id, 143 | labels: edgeLabel, 144 | sources: [sourceKey], 145 | targets: [targetKey], 146 | }; 147 | ElkModel.wireNameLookup[id] = targetPort.wire.netName; 148 | if (sourcePort.parentNode.type !== '$dff') { 149 | edge.layoutOptions = { 'org.eclipse.elk.layered.priority.direction': 10, 150 | 'org.eclipse.elk.edge.thickness': (numWires > 1 ? 2 : 1) }; 151 | } 152 | else { 153 | edge.layoutOptions = { 'org.eclipse.elk.edge.thickness': (numWires > 1 ? 2 : 1) }; 154 | } 155 | ElkModel.edgeIndex += 1; 156 | return edge; 157 | }); 158 | })); 159 | edges.push.apply(edges, newEdges); 160 | } 161 | -------------------------------------------------------------------------------- /built/draw.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var FlatModule_1 = require("./FlatModule"); 4 | var _ = require("lodash"); 5 | var onml = require("onml"); 6 | var WireDirection; 7 | (function (WireDirection) { 8 | WireDirection[WireDirection["Up"] = 0] = "Up"; 9 | WireDirection[WireDirection["Down"] = 1] = "Down"; 10 | WireDirection[WireDirection["Left"] = 2] = "Left"; 11 | WireDirection[WireDirection["Right"] = 3] = "Right"; 12 | })(WireDirection || (WireDirection = {})); 13 | function drawModule(g, module) { 14 | var nodes = module.getNodes().map(function (n) { 15 | var kchild = _.find(g.children, function (c) { return c.id === n.Key; }); 16 | return n.render(kchild); 17 | }); 18 | removeDummyEdges(g); 19 | var lines = _.flatMap(g.edges, function (e) { 20 | return _.flatMap(e.sections, function (s) { 21 | var startPoint = s.startPoint; 22 | s.bendPoints = s.bendPoints || []; 23 | var bends = s.bendPoints.map(function (b) { 24 | var l = ['line', { 25 | x1: startPoint.x, 26 | x2: b.x, 27 | y1: startPoint.y, 28 | y2: b.y, 29 | }]; 30 | startPoint = b; 31 | return l; 32 | }); 33 | if (e.junctionPoints) { 34 | var circles = e.junctionPoints.map(function (j) { 35 | return ['circle', { 36 | cx: j.x, 37 | cy: j.y, 38 | r: 2, 39 | style: 'fill:#000', 40 | }]; 41 | }); 42 | bends = bends.concat(circles); 43 | } 44 | var line = [['line', { 45 | x1: startPoint.x, 46 | x2: s.endPoint.x, 47 | y1: startPoint.y, 48 | y2: s.endPoint.y, 49 | }]]; 50 | return bends.concat(line); 51 | }); 52 | }); 53 | var svg = module.getSkin().slice(0, 2); 54 | svg[1].width = g.width; 55 | svg[1].height = g.height; 56 | var styles = _.filter(module.getSkin(), function (el) { 57 | return el[0] === 'style'; 58 | }); 59 | var ret = svg.concat(styles).concat(nodes).concat(lines); 60 | return onml.s(ret); 61 | } 62 | exports.drawModule = drawModule; 63 | function setGenericSize(tempclone, height) { 64 | onml.traverse(tempclone, { 65 | enter: function (node) { 66 | if (node.name === 'rect' && node.attr['s:generic'] === 'body') { 67 | node.attr.height = height; 68 | } 69 | }, 70 | }); 71 | } 72 | exports.setGenericSize = setGenericSize; 73 | function setTextAttribute(tempclone, attribute, value) { 74 | onml.traverse(tempclone, { 75 | enter: function (node) { 76 | if (node.name === 'text' && node.attr['s:attribute'] === attribute) { 77 | node.full[2] = value; 78 | } 79 | }, 80 | }); 81 | } 82 | exports.setTextAttribute = setTextAttribute; 83 | function which_dir(start, end) { 84 | if (end.x === start.x && end.y === start.y) { 85 | throw new Error('start and end are the same'); 86 | } 87 | if (end.x !== start.x && end.y !== start.y) { 88 | throw new Error('start and end arent orthogonal'); 89 | } 90 | if (end.x > start.x) { 91 | return WireDirection.Right; 92 | } 93 | if (end.x < start.x) { 94 | return WireDirection.Left; 95 | } 96 | if (end.y > start.y) { 97 | return WireDirection.Down; 98 | } 99 | if (end.y < start.y) { 100 | return WireDirection.Up; 101 | } 102 | throw new Error('unexpected direction'); 103 | } 104 | function removeDummyEdges(g) { 105 | // go through each edge group for each dummy 106 | var dummyNum = 0; 107 | var _loop_1 = function () { 108 | var dummyId = '$d_' + String(dummyNum); 109 | // find all edges connected to this dummy 110 | var edgeGroup = _.filter(g.edges, function (e) { 111 | return e.source === dummyId || e.target === dummyId; 112 | }); 113 | if (edgeGroup.length === 0) { 114 | return "break"; 115 | } 116 | var junctEdge = _.minBy(edgeGroup, function (e) { 117 | if (e.source === dummyId) { 118 | if (e.junctionPoints) { 119 | var firstJunction_1 = e.junctionPoints[0]; 120 | return _.findIndex(e.bendPoints, function (bend) { 121 | return bend.x === firstJunction_1.x && bend.y === firstJunction_1.y; 122 | }); 123 | } 124 | // a number bigger than any bendpoint index 125 | return 10000; 126 | } 127 | else { 128 | if (e.junctionPoints) { 129 | var lastJunction_1 = e.junctionPoints[e.junctionPoints.length - 1]; 130 | // flip the sign of the index so we find the max instead of the min 131 | return 0 - _.findIndex(e.bendPoints, function (bend) { 132 | return bend.x === lastJunction_1.x && bend.y === lastJunction_1.y; 133 | }); 134 | } 135 | // a number bigger than any bendpoint index 136 | return 1000; 137 | } 138 | }); 139 | var junct = junctEdge.junctionPoints[0]; 140 | var dirs = edgeGroup.map(function (e) { 141 | var s = e.sections[0]; 142 | if (e.source === dummyId) { 143 | s.startPoint = junct; 144 | if (s.bendPoints) { 145 | if (s.bendPoints[0].x === junct.x && s.bendPoints[0].y === junct.y) { 146 | s.bendPoints = s.bendPoints.slice(1); 147 | } 148 | if (s.bendPoints.length > 0) { 149 | return which_dir(junct, s.bendPoints[0]); 150 | } 151 | } 152 | return which_dir(junct, s.endPoint); 153 | } 154 | else { 155 | s.endPoint = junct; 156 | if (s.bendPoints) { 157 | var lastBend = s.bendPoints[s.bendPoints.length - 1]; 158 | if (lastBend.x === junct.x && lastBend.y === junct.y) { 159 | s.bendPoints.pop(); 160 | } 161 | if (s.bendPoints.length > 0) { 162 | return which_dir(junct, s.bendPoints[s.bendPoints.length - 1]); 163 | } 164 | } 165 | return which_dir(junct, s.startPoint); 166 | } 167 | }); 168 | var dirSet = FlatModule_1.removeDups(dirs.map(function (wd) { return WireDirection[wd]; })); 169 | if (dirSet.length === 2) { 170 | junctEdge.junctionPoints = junctEdge.junctionPoints.slice(1); 171 | } 172 | dummyNum += 1; 173 | }; 174 | // loop until we can't find an edge group or we hit 10,000 175 | while (dummyNum < 10000) { 176 | var state_1 = _loop_1(); 177 | if (state_1 === "break") 178 | break; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /doc/mux4.svg: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | MUX2 24 | 25 | 26 | I0 27 | 28 | 29 | I1 30 | 31 | 32 | S0 33 | 34 | 35 | O 36 | 37 | 38 | 39 | MUX2 40 | 41 | 42 | I0 43 | 44 | 45 | I1 46 | 47 | 48 | S0 49 | 50 | 51 | O 52 | 53 | 54 | 55 | MUX2 56 | 57 | 58 | I0 59 | 60 | 61 | I1 62 | 63 | 64 | S0 65 | 66 | 67 | O 68 | 69 | 70 | 71 | I0 72 | 73 | 74 | 75 | 76 | 77 | I1 78 | 79 | 80 | 81 | 82 | 83 | I2 84 | 85 | 86 | 87 | 88 | 89 | I3 90 | 91 | 92 | 93 | 94 | 95 | S0 96 | 97 | 98 | 99 | 100 | 101 | S1 102 | 103 | 104 | 105 | 106 | 107 | O 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /built/FlatModule.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.removeDups = exports.addToDefaultDict = exports.arrayToBitstring = exports.FlatModule = void 0; 4 | var Skin_1 = require("./Skin"); 5 | var Cell_1 = require("./Cell"); 6 | var _ = require("lodash"); 7 | var FlatModule = /** @class */ (function () { 8 | function FlatModule(netlist) { 9 | var _this = this; 10 | this.moduleName = null; 11 | _.forEach(netlist.modules, function (mod, name) { 12 | if (mod.attributes && Number(mod.attributes.top) === 1) { 13 | _this.moduleName = name; 14 | } 15 | }); 16 | // Otherwise default the first one in the file... 17 | if (this.moduleName == null) { 18 | this.moduleName = Object.keys(netlist.modules)[0]; 19 | } 20 | var top = netlist.modules[this.moduleName]; 21 | var ports = _.map(top.ports, Cell_1.default.fromPort); 22 | var cells = _.map(top.cells, function (c, key) { return Cell_1.default.fromYosysCell(c, key); }); 23 | this.nodes = cells.concat(ports); 24 | // populated by createWires 25 | this.wires = []; 26 | } 27 | // converts input ports with constant assignments to constant nodes 28 | FlatModule.prototype.addConstants = function () { 29 | // find the maximum signal number 30 | var maxNum = this.nodes.reduce((function (acc, v) { return v.maxOutVal(acc); }), -1); 31 | // add constants to nodes 32 | var signalsByConstantName = {}; 33 | var cells = []; 34 | this.nodes.forEach(function (n) { 35 | maxNum = n.findConstants(signalsByConstantName, maxNum, cells); 36 | }); 37 | this.nodes = this.nodes.concat(cells); 38 | }; 39 | // solves for minimal bus splits and joins and adds them to module 40 | FlatModule.prototype.addSplitsJoins = function () { 41 | var allInputs = _.flatMap(this.nodes, function (n) { return n.inputPortVals(); }); 42 | var allOutputs = _.flatMap(this.nodes, function (n) { return n.outputPortVals(); }); 43 | var allInputsCopy = allInputs.slice(); 44 | var splits = {}; 45 | var joins = {}; 46 | allInputs.forEach(function (input) { 47 | gather(allOutputs, allInputsCopy, input, 0, input.length, splits, joins); 48 | }); 49 | this.nodes = this.nodes.concat(_.map(joins, function (joinOutput, joinInputs) { 50 | return Cell_1.default.fromJoinInfo(joinInputs, joinOutput); 51 | })).concat(_.map(splits, function (splitOutputs, splitInput) { 52 | return Cell_1.default.fromSplitInfo(splitInput, splitOutputs); 53 | })); 54 | }; 55 | // search through all the ports to find all of the wires 56 | FlatModule.prototype.createWires = function () { 57 | var layoutProps = Skin_1.default.getProperties(); 58 | var ridersByNet = {}; 59 | var driversByNet = {}; 60 | var lateralsByNet = {}; 61 | this.nodes.forEach(function (n) { 62 | n.collectPortsByDirection(ridersByNet, driversByNet, lateralsByNet, layoutProps.genericsLaterals); 63 | }); 64 | // list of unique nets 65 | var nets = removeDups(_.keys(ridersByNet).concat(_.keys(driversByNet)).concat(_.keys(lateralsByNet))); 66 | var wires = nets.map(function (net) { 67 | var drivers = driversByNet[net] || []; 68 | var riders = ridersByNet[net] || []; 69 | var laterals = lateralsByNet[net] || []; 70 | var wire = { netName: net, drivers: drivers, riders: riders, laterals: laterals }; 71 | drivers.concat(riders).concat(laterals).forEach(function (port) { 72 | port.wire = wire; 73 | }); 74 | return wire; 75 | }); 76 | this.wires = wires; 77 | }; 78 | return FlatModule; 79 | }()); 80 | exports.FlatModule = FlatModule; 81 | // returns a string that represents the values of the array of integers 82 | // [1, 2, 3] -> ',1,2,3,' 83 | function arrayToBitstring(bitArray) { 84 | var ret = ''; 85 | bitArray.forEach(function (bit) { 86 | var sbit = String(bit); 87 | if (ret === '') { 88 | ret = sbit; 89 | } 90 | else { 91 | ret += ',' + sbit; 92 | } 93 | }); 94 | return ',' + ret + ','; 95 | } 96 | exports.arrayToBitstring = arrayToBitstring; 97 | // returns whether needle is a substring of haystack 98 | function arrayContains(needle, haystack) { 99 | return (haystack.indexOf(needle) > -1); 100 | } 101 | // returns the index of the string that contains a substring 102 | // given arrhaystack, an array of strings 103 | function indexOfContains(needle, arrhaystack) { 104 | return _.findIndex(arrhaystack, function (haystack) { 105 | return arrayContains(needle, haystack); 106 | }); 107 | } 108 | function addToDefaultDict(dict, key, value) { 109 | if (dict[key] === undefined) { 110 | dict[key] = [value]; 111 | } 112 | else { 113 | dict[key].push(value); 114 | } 115 | } 116 | exports.addToDefaultDict = addToDefaultDict; 117 | // string (for labels), that represents an index 118 | // or range of indices. 119 | function getIndicesString(bitstring, query, start) { 120 | var splitStart = _.max([bitstring.indexOf(query), start]); 121 | var startIndex = bitstring.substring(0, splitStart).split(',').length - 1; 122 | var endIndex = startIndex + query.split(',').length - 3; 123 | if (startIndex === endIndex) { 124 | return String(startIndex); 125 | } 126 | else { 127 | return String(startIndex) + ':' + String(endIndex); 128 | } 129 | } 130 | // gather splits and joins 131 | function gather(inputs, // all inputs 132 | outputs, // all outputs 133 | toSolve, // an input array we are trying to solve 134 | start, // index of toSolve to start from 135 | end, // index of toSolve to end at 136 | splits, // container collecting the splits 137 | joins) { 138 | // remove myself from outputs list if present 139 | var outputIndex = outputs.indexOf(toSolve); 140 | if (outputIndex !== -1) { 141 | outputs.splice(outputIndex, 1); 142 | } 143 | // This toSolve is compconste 144 | if (start >= toSolve.length || end - start < 2) { 145 | return; 146 | } 147 | var query = toSolve.slice(start, end); 148 | // are there are perfect matches? 149 | if (arrayContains(query, inputs)) { 150 | if (query !== toSolve) { 151 | addToDefaultDict(joins, toSolve, getIndicesString(toSolve, query, start)); 152 | } 153 | gather(inputs, outputs, toSolve, end - 1, toSolve.length, splits, joins); 154 | return; 155 | } 156 | var index = indexOfContains(query, inputs); 157 | // are there any partial matches? 158 | if (index !== -1) { 159 | if (query !== toSolve) { 160 | addToDefaultDict(joins, toSolve, getIndicesString(toSolve, query, start)); 161 | } 162 | // found a split 163 | addToDefaultDict(splits, inputs[index], getIndicesString(inputs[index], query, 0)); 164 | // we can match to this now 165 | inputs.push(query); 166 | gather(inputs, outputs, toSolve, end - 1, toSolve.length, splits, joins); 167 | return; 168 | } 169 | // are there any output matches? 170 | if (indexOfContains(query, outputs) !== -1) { 171 | if (query !== toSolve) { 172 | // add to join 173 | addToDefaultDict(joins, toSolve, getIndicesString(toSolve, query, start)); 174 | } 175 | // gather without outputs 176 | gather(inputs, [], query, 0, query.length, splits, joins); 177 | inputs.push(query); 178 | return; 179 | } 180 | gather(inputs, outputs, toSolve, start, start + query.slice(0, -1).lastIndexOf(',') + 1, splits, joins); 181 | } 182 | function removeDups(inStrs) { 183 | var map = {}; 184 | inStrs.forEach(function (str) { 185 | map[str] = true; 186 | }); 187 | return _.keys(map); 188 | } 189 | exports.removeDups = removeDups; 190 | -------------------------------------------------------------------------------- /doc/generics.svg: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | PLL 24 | 25 | 26 | clkin 27 | 28 | 29 | clk40 30 | 31 | 32 | clk200 33 | 34 | 35 | clk125 36 | 37 | 38 | locked 39 | 40 | 41 | 42 | MIG 43 | 44 | 45 | clk_ref 46 | 47 | 48 | clk_sys 49 | 50 | 51 | reset 52 | 53 | 54 | 55 | counter 56 | 57 | 58 | clk 59 | 60 | 61 | start 62 | 63 | 64 | elapsed 65 | 66 | 67 | 68 | sync 69 | 70 | 71 | clk 72 | 73 | 74 | in 75 | 76 | 77 | out 78 | 79 | 80 | 81 | businterface 82 | 83 | 84 | clk 85 | 86 | 87 | reset 88 | 89 | 90 | 91 | clk100 92 | 93 | 94 | 95 | 96 | 97 | clk40 98 | 99 | 100 | 101 | 102 | 103 | clk125 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /doc/common_emitter_full.svg: -------------------------------------------------------------------------------- 1 | 2 | 32 | 33 | 34 | C1 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | C2 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Q 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | R1 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | R2 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | R3 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | R4 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | VCC 110 | 111 | 112 | 113 | 114 | 115 | VCC 116 | 117 | 118 | 119 | 120 | vin 121 | 122 | 123 | 124 | 125 | 126 | vout 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /lib/elkGraph.ts: -------------------------------------------------------------------------------- 1 | import { FlatModule } from './FlatModule'; 2 | import _ = require('lodash'); 3 | 4 | export namespace ElkModel { 5 | interface WireNameLookup { 6 | [edgeId: string]: string; 7 | } 8 | export let wireNameLookup: WireNameLookup = {}; 9 | export let dummyNum: number = 0; 10 | export let edgeIndex: number = 0; 11 | 12 | export interface WirePoint { 13 | x: number; 14 | y: number; 15 | } 16 | 17 | export interface Cell { 18 | id: string; 19 | width: number; 20 | height: number; 21 | ports: Port[]; 22 | layoutOptions?: LayoutOptions; 23 | labels?: Label[]; 24 | x?: number; 25 | y?: number; 26 | } 27 | 28 | export interface Graph { 29 | id: string; 30 | children: Cell[]; 31 | edges: (Edge|ExtendedEdge)[]; 32 | width?: number; 33 | height?: number; 34 | } 35 | 36 | export interface Port { 37 | id: string; 38 | width: number; 39 | height: number; 40 | x?: number; 41 | y?: number; 42 | labels?: Label[]; 43 | } 44 | 45 | export interface Section { 46 | id?: string; 47 | startPoint: WirePoint; 48 | endPoint: WirePoint; 49 | bendPoints?: WirePoint[]; 50 | } 51 | 52 | export interface Edge { 53 | id: string; 54 | labels?: Label[]; 55 | source: string; 56 | sourcePort: string; 57 | target: string; 58 | targetPort: string; 59 | layoutOptions?: LayoutOptions; 60 | junctionPoints?: WirePoint[]; 61 | bendPoints?: WirePoint[]; 62 | sections?: Section[]; 63 | } 64 | 65 | export interface ExtendedEdge { 66 | id: string; 67 | labels?: Label[]; 68 | sources: [ string ]; 69 | targets: [ string ]; 70 | layoutOptions?: LayoutOptions; 71 | } 72 | 73 | export interface LayoutOptions { 74 | [option: string]: any; 75 | } 76 | 77 | export interface Label { 78 | id: string; 79 | text: string; 80 | x: number; 81 | y: number; 82 | height: number; 83 | width: number; 84 | layoutOptions?: LayoutOptions; 85 | } 86 | } 87 | export function buildElkGraph(module: FlatModule): ElkModel.Graph { 88 | const children: ElkModel.Cell[] = module.nodes.map((n) => { 89 | return n.buildElkChild(); 90 | }); 91 | ElkModel.edgeIndex = 0; 92 | ElkModel.dummyNum = 0; 93 | const edges: ElkModel.Edge[] = _.flatMap(module.wires, (w) => { 94 | const numWires = w.netName.split(',').length - 2; 95 | // at least one driver and at least one rider and no laterals 96 | if (w.drivers.length > 0 && w.riders.length > 0 && w.laterals.length === 0) { 97 | const ret: ElkModel.Edge[] = []; 98 | route(w.drivers, w.riders, ret, numWires); 99 | return ret; 100 | // at least one driver or rider and at least one lateral 101 | } else if (w.drivers.concat(w.riders).length > 0 && w.laterals.length > 0) { 102 | const ret: ElkModel.Edge[] = []; 103 | route(w.drivers, w.laterals, ret, numWires); 104 | route(w.laterals, w.riders, ret, numWires); 105 | return ret; 106 | // at least two drivers and no riders 107 | } else if (w.riders.length === 0 && w.drivers.length > 1) { 108 | // create a dummy node and add it to children 109 | const dummyId: string = addDummy(children); 110 | ElkModel.dummyNum += 1; 111 | const dummyEdges: ElkModel.Edge[] = w.drivers.map((driver) => { 112 | const sourceParentKey: string = driver.parentNode.Key; 113 | const id: string = 'e' + String(ElkModel.edgeIndex); 114 | ElkModel.edgeIndex += 1; 115 | const d: ElkModel.Edge = { 116 | id, 117 | source: sourceParentKey, 118 | sourcePort: sourceParentKey + '.' + driver.key, 119 | target: dummyId, 120 | targetPort: dummyId + '.p', 121 | }; 122 | ElkModel.wireNameLookup[id] = driver.wire.netName; 123 | return d; 124 | }); 125 | 126 | return dummyEdges; 127 | // at least one rider and no drivers 128 | } else if (w.riders.length > 1 && w.drivers.length === 0) { 129 | // create a dummy node and add it to children 130 | const dummyId: string = addDummy(children); 131 | ElkModel.dummyNum += 1; 132 | const dummyEdges: ElkModel.Edge[] = w.riders.map((rider) => { 133 | const sourceParentKey: string = rider.parentNode.Key; 134 | const id: string = 'e' + String(ElkModel.edgeIndex); 135 | ElkModel.edgeIndex += 1; 136 | const edge: ElkModel.Edge = { 137 | id, 138 | source: dummyId, 139 | sourcePort: dummyId + '.p', 140 | target: sourceParentKey, 141 | targetPort: sourceParentKey + '.' + rider.key, 142 | }; 143 | ElkModel.wireNameLookup[id] = rider.wire.netName; 144 | return edge; 145 | }); 146 | return dummyEdges; 147 | } else if (w.laterals.length > 1) { 148 | const source = w.laterals[0]; 149 | const sourceParentKey: string = source.parentNode.Key; 150 | const lateralEdges: ElkModel.Edge[] = w.laterals.slice(1).map((lateral) => { 151 | const lateralParentKey: string = lateral.parentNode.Key; 152 | const id: string = 'e' + String(ElkModel.edgeIndex); 153 | ElkModel.edgeIndex += 1; 154 | const edge: ElkModel.Edge = { 155 | id, 156 | source: sourceParentKey, 157 | sourcePort: sourceParentKey + '.' + source.key, 158 | target: lateralParentKey, 159 | targetPort: lateralParentKey + '.' + lateral.key, 160 | }; 161 | ElkModel.wireNameLookup[id] = lateral.wire.netName; 162 | return edge; 163 | }); 164 | return lateralEdges; 165 | } 166 | // for only one driver or only one rider, don't create any edges 167 | return []; 168 | }); 169 | return { 170 | id: module.moduleName, 171 | children, 172 | edges, 173 | }; 174 | } 175 | 176 | function addDummy(children: ElkModel.Cell[]) { 177 | const dummyId: string = '$d_' + String(ElkModel.dummyNum); 178 | const child: ElkModel.Cell = { 179 | id: dummyId, 180 | width: 0, 181 | height: 0, 182 | ports: [{ 183 | id: dummyId + '.p', 184 | width: 0, 185 | height: 0, 186 | }], 187 | layoutOptions: { 'org.eclipse.elk.portConstraints': 'FIXED_SIDE' }, 188 | }; 189 | children.push(child); 190 | return dummyId; 191 | } 192 | 193 | function route(sourcePorts, targetPorts, edges: ElkModel.Edge[], numWires) { 194 | const newEdges: ElkModel.Edge[] = (_.flatMap(sourcePorts, (sourcePort) => { 195 | const sourceParentKey: string = sourcePort.parentNode.key; 196 | const sourceKey: string = sourceParentKey + '.' + sourcePort.key; 197 | let edgeLabel: ElkModel.Label[]; 198 | if (numWires > 1) { 199 | edgeLabel = [{ 200 | id: '', 201 | text: String(numWires), 202 | width: 4, 203 | height: 6, 204 | x: 0, 205 | y: 0, 206 | layoutOptions: { 207 | 'org.eclipse.elk.edgeLabels.inline': true, 208 | }, 209 | }]; 210 | } 211 | return targetPorts.map((targetPort) => { 212 | const targetParentKey: string = targetPort.parentNode.key; 213 | const targetKey: string = targetParentKey + '.' + targetPort.key; 214 | const id: string = 'e' + ElkModel.edgeIndex; 215 | const edge: ElkModel.ExtendedEdge = { 216 | id, 217 | labels: edgeLabel, 218 | sources: [sourceKey], 219 | targets: [targetKey], 220 | }; 221 | ElkModel.wireNameLookup[id] = targetPort.wire.netName; 222 | if (sourcePort.parentNode.type !== '$dff') { 223 | edge.layoutOptions = { 'org.eclipse.elk.layered.priority.direction': 10, 224 | 'org.eclipse.elk.edge.thickness': (numWires > 1 ? 2 : 1) }; 225 | } else { 226 | edge.layoutOptions = { 'org.eclipse.elk.edge.thickness': (numWires > 1 ? 2 : 1) }; 227 | } 228 | ElkModel.edgeIndex += 1; 229 | return edge; 230 | }); 231 | })); 232 | edges.push.apply(edges, newEdges); 233 | } 234 | -------------------------------------------------------------------------------- /doc/and.svg: -------------------------------------------------------------------------------- 1 | 2 | 32 | 33 | 34 | R1 35 | 10k 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | R2 44 | 10k 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | Q1 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | R3 65 | 10k 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | R4 74 | 10k 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | R5 83 | 10k 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | Q2 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | VCC 105 | 106 | 107 | 108 | 109 | 110 | 111 | VCC 112 | 113 | 114 | 115 | 116 | 117 | DGND 118 | 119 | 120 | 121 | 122 | 123 | DGND 124 | 125 | 126 | 127 | 128 | A 129 | 130 | 131 | 132 | 133 | 134 | B 135 | 136 | 137 | 138 | 139 | 140 | A AND B 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /lib/FlatModule.ts: -------------------------------------------------------------------------------- 1 | import Yosys from './YosysModel'; 2 | import Skin from './Skin'; 3 | import Cell from './Cell'; 4 | import _ = require('lodash'); 5 | 6 | export interface FlatPort { 7 | key: string; 8 | value?: number[] | Yosys.Signals; 9 | parentNode?: Cell; 10 | wire?: Wire; 11 | } 12 | 13 | export interface Wire { 14 | netName: string; 15 | drivers: FlatPort[]; 16 | riders: FlatPort[]; 17 | laterals: FlatPort[]; 18 | } 19 | 20 | export class FlatModule { 21 | public moduleName: string; 22 | public nodes: Cell[]; 23 | public wires: Wire[]; 24 | 25 | constructor(netlist: Yosys.Netlist) { 26 | this.moduleName = null; 27 | _.forEach(netlist.modules, (mod: Yosys.Module, name: string) => { 28 | if (mod.attributes && Number(mod.attributes.top) === 1) { 29 | this.moduleName = name; 30 | } 31 | }); 32 | // Otherwise default the first one in the file... 33 | if (this.moduleName == null) { 34 | this.moduleName = Object.keys(netlist.modules)[0]; 35 | } 36 | const top = netlist.modules[this.moduleName]; 37 | const ports = _.map(top.ports, Cell.fromPort); 38 | const cells = _.map(top.cells, (c, key) => Cell.fromYosysCell(c, key)); 39 | this.nodes = cells.concat(ports); 40 | // populated by createWires 41 | this.wires = []; 42 | } 43 | 44 | // converts input ports with constant assignments to constant nodes 45 | public addConstants(): void { 46 | // find the maximum signal number 47 | let maxNum: number = this.nodes.reduce(((acc, v) => v.maxOutVal(acc)), -1); 48 | 49 | // add constants to nodes 50 | const signalsByConstantName: SigsByConstName = {}; 51 | const cells: Cell[] = []; 52 | this.nodes.forEach((n) => { 53 | maxNum = n.findConstants(signalsByConstantName, maxNum, cells); 54 | }); 55 | this.nodes = this.nodes.concat(cells); 56 | } 57 | 58 | // solves for minimal bus splits and joins and adds them to module 59 | public addSplitsJoins(): void { 60 | const allInputs = _.flatMap(this.nodes, (n) => n.inputPortVals()); 61 | const allOutputs = _.flatMap(this.nodes, (n) => n.outputPortVals()); 62 | 63 | const allInputsCopy = allInputs.slice(); 64 | const splits: SplitJoin = {}; 65 | const joins: SplitJoin = {}; 66 | allInputs.forEach((input) => { 67 | gather( 68 | allOutputs, 69 | allInputsCopy, 70 | input, 71 | 0, 72 | input.length, 73 | splits, 74 | joins); 75 | }); 76 | 77 | this.nodes = this.nodes.concat(_.map(joins, (joinOutput, joinInputs) => { 78 | return Cell.fromJoinInfo(joinInputs, joinOutput); 79 | })).concat(_.map(splits, (splitOutputs, splitInput) => { 80 | return Cell.fromSplitInfo(splitInput, splitOutputs); 81 | })); 82 | } 83 | 84 | // search through all the ports to find all of the wires 85 | public createWires() { 86 | const layoutProps = Skin.getProperties(); 87 | const ridersByNet: NameToPorts = {}; 88 | const driversByNet: NameToPorts = {}; 89 | const lateralsByNet: NameToPorts = {}; 90 | this.nodes.forEach((n) => { 91 | n.collectPortsByDirection( 92 | ridersByNet, 93 | driversByNet, 94 | lateralsByNet, 95 | layoutProps.genericsLaterals as boolean); 96 | }); 97 | // list of unique nets 98 | const nets = removeDups(_.keys(ridersByNet).concat(_.keys(driversByNet)).concat(_.keys(lateralsByNet))); 99 | const wires: Wire[] = nets.map((net) => { 100 | const drivers: FlatPort[] = driversByNet[net] || []; 101 | const riders: FlatPort[] = ridersByNet[net] || []; 102 | const laterals: FlatPort[] = lateralsByNet[net] || []; 103 | const wire: Wire = { netName: net, drivers, riders, laterals}; 104 | drivers.concat(riders).concat(laterals).forEach((port) => { 105 | port.wire = wire; 106 | }); 107 | return wire; 108 | }); 109 | this.wires = wires; 110 | } 111 | } 112 | 113 | export interface SigsByConstName { 114 | [constantName: string]: number[]; 115 | } 116 | 117 | // returns a string that represents the values of the array of integers 118 | // [1, 2, 3] -> ',1,2,3,' 119 | export function arrayToBitstring(bitArray: number[]): string { 120 | let ret: string = ''; 121 | bitArray.forEach((bit: number) => { 122 | const sbit = String(bit); 123 | if (ret === '') { 124 | ret = sbit; 125 | } else { 126 | ret += ',' + sbit; 127 | } 128 | }); 129 | return ',' + ret + ','; 130 | } 131 | 132 | // returns whether needle is a substring of haystack 133 | function arrayContains(needle: string, haystack: string | string[]): boolean { 134 | return (haystack.indexOf(needle) > -1); 135 | } 136 | 137 | // returns the index of the string that contains a substring 138 | // given arrhaystack, an array of strings 139 | function indexOfContains(needle: string, arrhaystack: string[]): number { 140 | return _.findIndex(arrhaystack, (haystack: string) => { 141 | return arrayContains(needle, haystack); 142 | }); 143 | } 144 | 145 | interface SplitJoin { 146 | [portName: string]: string[]; 147 | } 148 | 149 | export function addToDefaultDict(dict: any, key: string, value: any): void { 150 | if (dict[key] === undefined) { 151 | dict[key] = [value]; 152 | } else { 153 | dict[key].push(value); 154 | } 155 | } 156 | 157 | // string (for labels), that represents an index 158 | // or range of indices. 159 | function getIndicesString(bitstring: string, 160 | query: string, 161 | start: number): string { 162 | const splitStart: number = _.max([bitstring.indexOf(query), start]); 163 | const startIndex: number = bitstring.substring(0, splitStart).split(',').length - 1; 164 | const endIndex: number = startIndex + query.split(',').length - 3; 165 | 166 | if (startIndex === endIndex) { 167 | return String(startIndex); 168 | } else { 169 | return String(startIndex) + ':' + String(endIndex); 170 | } 171 | } 172 | 173 | // gather splits and joins 174 | function gather(inputs: string[], // all inputs 175 | outputs: string[], // all outputs 176 | toSolve: string, // an input array we are trying to solve 177 | start: number, // index of toSolve to start from 178 | end: number, // index of toSolve to end at 179 | splits: SplitJoin, // container collecting the splits 180 | joins: SplitJoin): void { // container collecting the joins 181 | // remove myself from outputs list if present 182 | const outputIndex: number = outputs.indexOf(toSolve); 183 | if (outputIndex !== -1) { 184 | outputs.splice(outputIndex, 1); 185 | } 186 | 187 | // This toSolve is compconste 188 | if (start >= toSolve.length || end - start < 2) { 189 | return; 190 | } 191 | 192 | const query: string = toSolve.slice(start, end); 193 | 194 | // are there are perfect matches? 195 | if (arrayContains(query, inputs)) { 196 | if (query !== toSolve) { 197 | addToDefaultDict(joins, toSolve, getIndicesString(toSolve, query, start)); 198 | } 199 | gather(inputs, outputs, toSolve, end - 1, toSolve.length, splits, joins); 200 | return; 201 | } 202 | const index: number = indexOfContains(query, inputs); 203 | // are there any partial matches? 204 | if (index !== -1) { 205 | if (query !== toSolve) { 206 | addToDefaultDict(joins, toSolve, getIndicesString(toSolve, query, start)); 207 | } 208 | // found a split 209 | addToDefaultDict(splits, inputs[index], getIndicesString(inputs[index], query, 0)); 210 | // we can match to this now 211 | inputs.push(query); 212 | gather(inputs, outputs, toSolve, end - 1, toSolve.length, splits, joins); 213 | return; 214 | } 215 | // are there any output matches? 216 | if (indexOfContains(query, outputs) !== -1) { 217 | if (query !== toSolve) { 218 | // add to join 219 | addToDefaultDict(joins, toSolve, getIndicesString(toSolve, query, start)); 220 | } 221 | // gather without outputs 222 | gather(inputs, [], query, 0, query.length, splits, joins); 223 | inputs.push(query); 224 | return; 225 | } 226 | gather(inputs, outputs, toSolve, start, start + query.slice(0, -1).lastIndexOf(',') + 1, splits, joins); 227 | } 228 | 229 | export interface NameToPorts { 230 | [netName: string]: FlatPort[]; 231 | } 232 | 233 | interface StringToBool { 234 | [s: string]: boolean; 235 | } 236 | 237 | export function removeDups(inStrs: string[]): string[] { 238 | const map: StringToBool = {}; 239 | inStrs.forEach((str) => { 240 | map[str] = true; 241 | }); 242 | return _.keys(map); 243 | } 244 | -------------------------------------------------------------------------------- /lib/drawModule.ts: -------------------------------------------------------------------------------- 1 | import { ElkModel } from './elkGraph'; 2 | import { FlatModule, removeDups } from './FlatModule'; 3 | import Cell from './Cell'; 4 | import Skin from './Skin'; 5 | 6 | import _ = require('lodash'); 7 | import onml = require('onml'); 8 | import assert = require('assert'); 9 | 10 | enum WireDirection { 11 | Up, Down, Left, Right, 12 | } 13 | 14 | export default function drawModule(g: ElkModel.Graph, module: FlatModule) { 15 | const nodes: onml.Element[] = module.nodes.map((n: Cell) => { 16 | const kchild: ElkModel.Cell = _.find(g.children, (c) => c.id === n.Key); 17 | return n.render(kchild); 18 | }); 19 | removeDummyEdges(g); 20 | let lines: onml.Element[] = _.flatMap(g.edges, (e: ElkModel.Edge) => { 21 | const netId = ElkModel.wireNameLookup[e.id]; 22 | const numWires = netId.split(',').length - 2; 23 | const lineStyle = 'stroke-width: ' + (numWires > 1 ? 2 : 1); 24 | const netName = 'net_' + netId.slice(1, netId.length - 1) + ' width_' + numWires; 25 | return _.flatMap(e.sections, (s: ElkModel.Section) => { 26 | let startPoint = s.startPoint; 27 | s.bendPoints = s.bendPoints || []; 28 | let bends: any[] = s.bendPoints.map((b) => { 29 | const l = ['line', { 30 | x1: startPoint.x, 31 | x2: b.x, 32 | y1: startPoint.y, 33 | y2: b.y, 34 | class: netName, 35 | style: lineStyle, 36 | }]; 37 | startPoint = b; 38 | return l; 39 | }); 40 | if (e.junctionPoints) { 41 | const circles: any[] = e.junctionPoints.map((j: ElkModel.WirePoint) => 42 | ['circle', { 43 | cx: j.x, 44 | cy: j.y, 45 | r: (numWires > 1 ? 3 : 2), 46 | style: 'fill:#000', 47 | class: netName, 48 | }]); 49 | bends = bends.concat(circles); 50 | } 51 | const line = [['line', { 52 | x1: startPoint.x, 53 | x2: s.endPoint.x, 54 | y1: startPoint.y, 55 | y2: s.endPoint.y, 56 | class: netName, 57 | style: lineStyle, 58 | }]]; 59 | return bends.concat(line); 60 | }); 61 | }); 62 | let labels: any[]; 63 | for (const index in g.edges) { 64 | if (g.edges.hasOwnProperty(index)) { 65 | const e = g.edges[index]; 66 | const netId = ElkModel.wireNameLookup[e.id]; 67 | const numWires = netId.split(',').length - 2; 68 | const netName = 'net_' + netId.slice(1, netId.length - 1) + 69 | ' width_' + numWires + 70 | ' busLabel_' + numWires; 71 | if ((e as ElkModel.ExtendedEdge).labels !== undefined && 72 | (e as ElkModel.ExtendedEdge).labels[0] !== undefined && 73 | (e as ElkModel.ExtendedEdge).labels[0].text !== undefined) { 74 | const label = [ 75 | ['rect', 76 | { 77 | x: e.labels[0].x + 1, 78 | y: e.labels[0].y - 1, 79 | width: (e.labels[0].text.length + 2) * 6 - 2, 80 | height: 9, 81 | class: netName, 82 | style: 'fill: white; stroke: none', 83 | }, 84 | ], ['text', 85 | { 86 | x: e.labels[0].x, 87 | y: e.labels[0].y + 7, 88 | class: netName, 89 | }, 90 | '/' + e.labels[0].text + '/', 91 | ], 92 | ]; 93 | if (labels !== undefined) { 94 | labels = labels.concat(label); 95 | } else { 96 | labels = label; 97 | } 98 | } 99 | } 100 | } 101 | if (labels !== undefined && labels.length > 0) { 102 | lines = lines.concat(labels); 103 | } 104 | const svgAttrs: onml.Attributes = Skin.skin[1]; 105 | svgAttrs.width = g.width.toString(); 106 | svgAttrs.height = g.height.toString(); 107 | 108 | const styles: onml.Element = ['style', {}, '']; 109 | onml.t(Skin.skin, { 110 | enter: (node) => { 111 | if (node.name === 'style') { 112 | styles[2] += node.full[2]; 113 | } 114 | }, 115 | }); 116 | const elements: onml.Element[] = [styles, ...nodes, ...lines]; 117 | const ret: onml.Element = ['svg', svgAttrs, ...elements]; 118 | return onml.s(ret); 119 | } 120 | 121 | function which_dir(start: ElkModel.WirePoint, end: ElkModel.WirePoint): WireDirection { 122 | if (end.x === start.x && end.y === start.y) { 123 | throw new Error('start and end are the same'); 124 | } 125 | if (end.x !== start.x && end.y !== start.y) { 126 | throw new Error('start and end arent orthogonal'); 127 | } 128 | if (end.x > start.x) { 129 | return WireDirection.Right; 130 | } 131 | if (end.x < start.x) { 132 | return WireDirection.Left; 133 | } 134 | if (end.y > start.y) { 135 | return WireDirection.Down; 136 | } 137 | if (end.y < start.y) { 138 | return WireDirection.Up; 139 | } 140 | throw new Error('unexpected direction'); 141 | } 142 | 143 | function findBendNearDummy( 144 | net: ElkModel.Edge[], 145 | dummyIsSource: boolean, 146 | dummyLoc: ElkModel.WirePoint): ElkModel.WirePoint { 147 | const candidates = net.map( (edge) => { 148 | const bends = edge.sections[0].bendPoints || [null]; 149 | if (dummyIsSource) { 150 | return _.first(bends); 151 | } else { 152 | return _.last(bends); 153 | } 154 | }).filter((p) => p !== null); 155 | return _.minBy(candidates, (pt: ElkModel.WirePoint) => { 156 | return Math.abs(dummyLoc.x - pt.x) + Math.abs(dummyLoc.y - pt.y); 157 | }); 158 | } 159 | 160 | export function removeDummyEdges(g: ElkModel.Graph) { 161 | // go through each edge group for each dummy 162 | let dummyNum: number = 0; 163 | // loop until we can't find an edge group or we hit 10,000 164 | while (dummyNum < 10000) { 165 | const dummyId: string = '$d_' + String(dummyNum); 166 | // find all edges connected to this dummy 167 | const edgeGroup = _.filter(g.edges, (e: ElkModel.Edge) => { 168 | return e.source === dummyId || e.target === dummyId; 169 | }); 170 | if (edgeGroup.length === 0) { 171 | break; 172 | } 173 | let dummyIsSource: boolean; 174 | let dummyLoc: ElkModel.WirePoint; 175 | const firstEdge: ElkModel.Edge = edgeGroup[0] as ElkModel.Edge; 176 | if (firstEdge.source === dummyId) { 177 | dummyIsSource = true; 178 | dummyLoc = firstEdge.sections[0].startPoint; 179 | } else { 180 | dummyIsSource = false; 181 | dummyLoc = firstEdge.sections[0].endPoint; 182 | } 183 | const newEnd: ElkModel.WirePoint = findBendNearDummy(edgeGroup as ElkModel.Edge[], dummyIsSource, dummyLoc); 184 | for (const edge of edgeGroup) { 185 | const e: ElkModel.Edge = edge as ElkModel.Edge; 186 | const section = e.sections[0]; 187 | if (dummyIsSource) { 188 | section.startPoint = newEnd; 189 | if (section.bendPoints) { 190 | section.bendPoints.shift(); 191 | } 192 | } else { 193 | section.endPoint = newEnd; 194 | if (section.bendPoints) { 195 | section.bendPoints.pop(); 196 | } 197 | } 198 | } 199 | // delete junction point if necessary 200 | const directions = new Set(_.flatMap(edgeGroup, (edge: ElkModel.Edge) => { 201 | const section = edge.sections[0]; 202 | if (dummyIsSource) { 203 | // get first bend or endPoint 204 | if (section.bendPoints && section.bendPoints.length > 0) { 205 | return [section.bendPoints[0]]; 206 | } 207 | return section.endPoint; 208 | } else { 209 | if (section.bendPoints && section.bendPoints.length > 0) { 210 | return [_.last(section.bendPoints)]; 211 | } 212 | return section.startPoint; 213 | } 214 | }).map( (pt) => { 215 | if (pt.x > newEnd.x) { 216 | return WireDirection.Right; 217 | } 218 | if (pt.x < newEnd.x) { 219 | return WireDirection.Left; 220 | } 221 | if (pt.y > newEnd.y) { 222 | return WireDirection.Down; 223 | } 224 | return WireDirection.Up; 225 | })); 226 | if (directions.size < 3) { 227 | // remove junctions at newEnd 228 | edgeGroup.forEach((edge: ElkModel.Edge) => { 229 | if (edge.junctionPoints) { 230 | edge.junctionPoints = edge.junctionPoints.filter((junct) => { 231 | return !_.isEqual(junct, newEnd); 232 | }); 233 | } 234 | }); 235 | } 236 | dummyNum += 1; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /built/drawModule.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __spreadArrays = (this && this.__spreadArrays) || function () { 3 | for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; 4 | for (var r = Array(s), k = 0, i = 0; i < il; i++) 5 | for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) 6 | r[k] = a[j]; 7 | return r; 8 | }; 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.removeDummyEdges = void 0; 11 | var elkGraph_1 = require("./elkGraph"); 12 | var Skin_1 = require("./Skin"); 13 | var _ = require("lodash"); 14 | var onml = require("onml"); 15 | var WireDirection; 16 | (function (WireDirection) { 17 | WireDirection[WireDirection["Up"] = 0] = "Up"; 18 | WireDirection[WireDirection["Down"] = 1] = "Down"; 19 | WireDirection[WireDirection["Left"] = 2] = "Left"; 20 | WireDirection[WireDirection["Right"] = 3] = "Right"; 21 | })(WireDirection || (WireDirection = {})); 22 | function drawModule(g, module) { 23 | var nodes = module.nodes.map(function (n) { 24 | var kchild = _.find(g.children, function (c) { return c.id === n.Key; }); 25 | return n.render(kchild); 26 | }); 27 | removeDummyEdges(g); 28 | var lines = _.flatMap(g.edges, function (e) { 29 | var netId = elkGraph_1.ElkModel.wireNameLookup[e.id]; 30 | var numWires = netId.split(',').length - 2; 31 | var lineStyle = 'stroke-width: ' + (numWires > 1 ? 2 : 1); 32 | var netName = 'net_' + netId.slice(1, netId.length - 1) + ' width_' + numWires; 33 | return _.flatMap(e.sections, function (s) { 34 | var startPoint = s.startPoint; 35 | s.bendPoints = s.bendPoints || []; 36 | var bends = s.bendPoints.map(function (b) { 37 | var l = ['line', { 38 | x1: startPoint.x, 39 | x2: b.x, 40 | y1: startPoint.y, 41 | y2: b.y, 42 | class: netName, 43 | style: lineStyle, 44 | }]; 45 | startPoint = b; 46 | return l; 47 | }); 48 | if (e.junctionPoints) { 49 | var circles = e.junctionPoints.map(function (j) { 50 | return ['circle', { 51 | cx: j.x, 52 | cy: j.y, 53 | r: (numWires > 1 ? 3 : 2), 54 | style: 'fill:#000', 55 | class: netName, 56 | }]; 57 | }); 58 | bends = bends.concat(circles); 59 | } 60 | var line = [['line', { 61 | x1: startPoint.x, 62 | x2: s.endPoint.x, 63 | y1: startPoint.y, 64 | y2: s.endPoint.y, 65 | class: netName, 66 | style: lineStyle, 67 | }]]; 68 | return bends.concat(line); 69 | }); 70 | }); 71 | var labels; 72 | for (var index in g.edges) { 73 | if (g.edges.hasOwnProperty(index)) { 74 | var e = g.edges[index]; 75 | var netId = elkGraph_1.ElkModel.wireNameLookup[e.id]; 76 | var numWires = netId.split(',').length - 2; 77 | var netName = 'net_' + netId.slice(1, netId.length - 1) + 78 | ' width_' + numWires + 79 | ' busLabel_' + numWires; 80 | if (e.labels !== undefined && 81 | e.labels[0] !== undefined && 82 | e.labels[0].text !== undefined) { 83 | var label = [ 84 | ['rect', 85 | { 86 | x: e.labels[0].x + 1, 87 | y: e.labels[0].y - 1, 88 | width: (e.labels[0].text.length + 2) * 6 - 2, 89 | height: 9, 90 | class: netName, 91 | style: 'fill: white; stroke: none', 92 | }, 93 | ], ['text', 94 | { 95 | x: e.labels[0].x, 96 | y: e.labels[0].y + 7, 97 | class: netName, 98 | }, 99 | '/' + e.labels[0].text + '/', 100 | ], 101 | ]; 102 | if (labels !== undefined) { 103 | labels = labels.concat(label); 104 | } 105 | else { 106 | labels = label; 107 | } 108 | } 109 | } 110 | } 111 | if (labels !== undefined && labels.length > 0) { 112 | lines = lines.concat(labels); 113 | } 114 | var svgAttrs = Skin_1.default.skin[1]; 115 | svgAttrs.width = g.width.toString(); 116 | svgAttrs.height = g.height.toString(); 117 | var styles = ['style', {}, '']; 118 | onml.t(Skin_1.default.skin, { 119 | enter: function (node) { 120 | if (node.name === 'style') { 121 | styles[2] += node.full[2]; 122 | } 123 | }, 124 | }); 125 | var elements = __spreadArrays([styles], nodes, lines); 126 | var ret = __spreadArrays(['svg', svgAttrs], elements); 127 | return onml.s(ret); 128 | } 129 | exports.default = drawModule; 130 | function which_dir(start, end) { 131 | if (end.x === start.x && end.y === start.y) { 132 | throw new Error('start and end are the same'); 133 | } 134 | if (end.x !== start.x && end.y !== start.y) { 135 | throw new Error('start and end arent orthogonal'); 136 | } 137 | if (end.x > start.x) { 138 | return WireDirection.Right; 139 | } 140 | if (end.x < start.x) { 141 | return WireDirection.Left; 142 | } 143 | if (end.y > start.y) { 144 | return WireDirection.Down; 145 | } 146 | if (end.y < start.y) { 147 | return WireDirection.Up; 148 | } 149 | throw new Error('unexpected direction'); 150 | } 151 | function findBendNearDummy(net, dummyIsSource, dummyLoc) { 152 | var candidates = net.map(function (edge) { 153 | var bends = edge.sections[0].bendPoints || [null]; 154 | if (dummyIsSource) { 155 | return _.first(bends); 156 | } 157 | else { 158 | return _.last(bends); 159 | } 160 | }).filter(function (p) { return p !== null; }); 161 | return _.minBy(candidates, function (pt) { 162 | return Math.abs(dummyLoc.x - pt.x) + Math.abs(dummyLoc.y - pt.y); 163 | }); 164 | } 165 | function removeDummyEdges(g) { 166 | // go through each edge group for each dummy 167 | var dummyNum = 0; 168 | var _loop_1 = function () { 169 | var dummyId = '$d_' + String(dummyNum); 170 | // find all edges connected to this dummy 171 | var edgeGroup = _.filter(g.edges, function (e) { 172 | return e.source === dummyId || e.target === dummyId; 173 | }); 174 | if (edgeGroup.length === 0) { 175 | return "break"; 176 | } 177 | var dummyIsSource; 178 | var dummyLoc = void 0; 179 | var firstEdge = edgeGroup[0]; 180 | if (firstEdge.source === dummyId) { 181 | dummyIsSource = true; 182 | dummyLoc = firstEdge.sections[0].startPoint; 183 | } 184 | else { 185 | dummyIsSource = false; 186 | dummyLoc = firstEdge.sections[0].endPoint; 187 | } 188 | var newEnd = findBendNearDummy(edgeGroup, dummyIsSource, dummyLoc); 189 | for (var _i = 0, edgeGroup_1 = edgeGroup; _i < edgeGroup_1.length; _i++) { 190 | var edge = edgeGroup_1[_i]; 191 | var e = edge; 192 | var section = e.sections[0]; 193 | if (dummyIsSource) { 194 | section.startPoint = newEnd; 195 | if (section.bendPoints) { 196 | section.bendPoints.shift(); 197 | } 198 | } 199 | else { 200 | section.endPoint = newEnd; 201 | if (section.bendPoints) { 202 | section.bendPoints.pop(); 203 | } 204 | } 205 | } 206 | // delete junction point if necessary 207 | var directions = new Set(_.flatMap(edgeGroup, function (edge) { 208 | var section = edge.sections[0]; 209 | if (dummyIsSource) { 210 | // get first bend or endPoint 211 | if (section.bendPoints && section.bendPoints.length > 0) { 212 | return [section.bendPoints[0]]; 213 | } 214 | return section.endPoint; 215 | } 216 | else { 217 | if (section.bendPoints && section.bendPoints.length > 0) { 218 | return [_.last(section.bendPoints)]; 219 | } 220 | return section.startPoint; 221 | } 222 | }).map(function (pt) { 223 | if (pt.x > newEnd.x) { 224 | return WireDirection.Right; 225 | } 226 | if (pt.x < newEnd.x) { 227 | return WireDirection.Left; 228 | } 229 | if (pt.y > newEnd.y) { 230 | return WireDirection.Down; 231 | } 232 | return WireDirection.Up; 233 | })); 234 | if (directions.size < 3) { 235 | // remove junctions at newEnd 236 | edgeGroup.forEach(function (edge) { 237 | if (edge.junctionPoints) { 238 | edge.junctionPoints = edge.junctionPoints.filter(function (junct) { 239 | return !_.isEqual(junct, newEnd); 240 | }); 241 | } 242 | }); 243 | } 244 | dummyNum += 1; 245 | }; 246 | // loop until we can't find an edge group or we hit 10,000 247 | while (dummyNum < 10000) { 248 | var state_1 = _loop_1(); 249 | if (state_1 === "break") 250 | break; 251 | } 252 | } 253 | exports.removeDummyEdges = removeDummyEdges; 254 | -------------------------------------------------------------------------------- /test/digital/up3down5.json: -------------------------------------------------------------------------------- 1 | { 2 | "creator": "Yosys 0.5+220 (git sha1 94fbaff, emcc -Os)", 3 | "modules": { 4 | "up3down5": { 5 | "ports": { 6 | "clock": { 7 | "direction": "input", 8 | "bits": [ 2 ] 9 | }, 10 | "data_in": { 11 | "direction": "input", 12 | "bits": [ 3, 4, 5, 6, 7, 8, 9, 10, 11 ] 13 | }, 14 | "up": { 15 | "direction": "input", 16 | "bits": [ 12 ] 17 | }, 18 | "down": { 19 | "direction": "input", 20 | "bits": [ 13 ] 21 | }, 22 | "carry_out": { 23 | "direction": "output", 24 | "bits": [ 14 ] 25 | }, 26 | "borrow_out": { 27 | "direction": "output", 28 | "bits": [ 15 ] 29 | }, 30 | "count_out": { 31 | "direction": "output", 32 | "bits": [ 16, 17, 18, 19, 20, 21, 22, 23, 24 ] 33 | }, 34 | "parity_out": { 35 | "direction": "output", 36 | "bits": [ 25 ] 37 | } 38 | }, 39 | "cells": { 40 | "$add$input.v:17$3": { 41 | "hide_name": 1, 42 | "type": "$add", 43 | "parameters": { 44 | "A_SIGNED": 0, 45 | "A_WIDTH": 9, 46 | "B_SIGNED": 0, 47 | "B_WIDTH": 2, 48 | "Y_WIDTH": 10 49 | }, 50 | "attributes": { 51 | "src": "input.v:17" 52 | }, 53 | "port_directions": { 54 | "A": "input", 55 | "B": "input", 56 | "Y": "output" 57 | }, 58 | "connections": { 59 | "A": [ 16, 17, 18, 19, 20, 21, 22, 23, 24 ], 60 | "B": [ "1", "1" ], 61 | "Y": [ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 ] 62 | } 63 | }, 64 | "$and$input.v:28$5": { 65 | "hide_name": 1, 66 | "type": "$and", 67 | "parameters": { 68 | "A_SIGNED": 0, 69 | "A_WIDTH": 1, 70 | "B_SIGNED": 0, 71 | "B_WIDTH": 1, 72 | "Y_WIDTH": 1 73 | }, 74 | "attributes": { 75 | "src": "input.v:28" 76 | }, 77 | "port_directions": { 78 | "A": "input", 79 | "B": "input", 80 | "Y": "output" 81 | }, 82 | "connections": { 83 | "A": [ 12 ], 84 | "B": [ 35 ], 85 | "Y": [ 36 ] 86 | } 87 | }, 88 | "$and$input.v:29$6": { 89 | "hide_name": 1, 90 | "type": "$and", 91 | "parameters": { 92 | "A_SIGNED": 0, 93 | "A_WIDTH": 1, 94 | "B_SIGNED": 0, 95 | "B_WIDTH": 1, 96 | "Y_WIDTH": 1 97 | }, 98 | "attributes": { 99 | "src": "input.v:29" 100 | }, 101 | "port_directions": { 102 | "A": "input", 103 | "B": "input", 104 | "Y": "output" 105 | }, 106 | "connections": { 107 | "A": [ 13 ], 108 | "B": [ 37 ], 109 | "Y": [ 38 ] 110 | } 111 | }, 112 | "$procdff$40": { 113 | "hide_name": 1, 114 | "type": "$dff", 115 | "parameters": { 116 | "CLK_POLARITY": 1, 117 | "WIDTH": 9 118 | }, 119 | "attributes": { 120 | "src": "input.v:14" 121 | }, 122 | "port_directions": { 123 | "CLK": "input", 124 | "D": "input", 125 | "Q": "output" 126 | }, 127 | "connections": { 128 | "CLK": [ 2 ], 129 | "D": [ 39, 40, 41, 42, 43, 44, 45, 46, 47 ], 130 | "Q": [ 16, 17, 18, 19, 20, 21, 22, 23, 24 ] 131 | } 132 | }, 133 | "$procdff$41": { 134 | "hide_name": 1, 135 | "type": "$dff", 136 | "parameters": { 137 | "CLK_POLARITY": 1, 138 | "WIDTH": 1 139 | }, 140 | "attributes": { 141 | "src": "input.v:14" 142 | }, 143 | "port_directions": { 144 | "CLK": "input", 145 | "D": "input", 146 | "Q": "output" 147 | }, 148 | "connections": { 149 | "CLK": [ 2 ], 150 | "D": [ 36 ], 151 | "Q": [ 14 ] 152 | } 153 | }, 154 | "$procdff$42": { 155 | "hide_name": 1, 156 | "type": "$dff", 157 | "parameters": { 158 | "CLK_POLARITY": 1, 159 | "WIDTH": 1 160 | }, 161 | "attributes": { 162 | "src": "input.v:14" 163 | }, 164 | "port_directions": { 165 | "CLK": "input", 166 | "D": "input", 167 | "Q": "output" 168 | }, 169 | "connections": { 170 | "CLK": [ 2 ], 171 | "D": [ 38 ], 172 | "Q": [ 15 ] 173 | } 174 | }, 175 | "$procdff$43": { 176 | "hide_name": 1, 177 | "type": "$dff", 178 | "parameters": { 179 | "CLK_POLARITY": 1, 180 | "WIDTH": 1 181 | }, 182 | "attributes": { 183 | "src": "input.v:14" 184 | }, 185 | "port_directions": { 186 | "CLK": "input", 187 | "D": "input", 188 | "Q": "output" 189 | }, 190 | "connections": { 191 | "CLK": [ 2 ], 192 | "D": [ 48 ], 193 | "Q": [ 25 ] 194 | } 195 | }, 196 | "$procmux$36": { 197 | "hide_name": 1, 198 | "type": "$pmux", 199 | "parameters": { 200 | "S_WIDTH": 3, 201 | "WIDTH": 9 202 | }, 203 | "attributes": { 204 | }, 205 | "port_directions": { 206 | "A": "input", 207 | "B": "input", 208 | "S": "input", 209 | "Y": "output" 210 | }, 211 | "connections": { 212 | "A": [ 16, 17, 18, 19, 20, 21, 22, 23, 24 ], 213 | "B": [ 26, 27, 28, 29, 30, 31, 32, 33, 34, 49, 50, 51, 52, 53, 54, 55, 56, 57, 3, 4, 5, 6, 7, 8, 9, 10, 11 ], 214 | "S": [ 58, 59, 60 ], 215 | "Y": [ 39, 40, 41, 42, 43, 44, 45, 46, 47 ] 216 | } 217 | }, 218 | "$procmux$37_CMP0": { 219 | "hide_name": 1, 220 | "type": "$eq", 221 | "parameters": { 222 | "A_SIGNED": 0, 223 | "A_WIDTH": 2, 224 | "B_SIGNED": 0, 225 | "B_WIDTH": 2, 226 | "Y_WIDTH": 1 227 | }, 228 | "attributes": { 229 | }, 230 | "port_directions": { 231 | "A": "input", 232 | "B": "input", 233 | "Y": "output" 234 | }, 235 | "connections": { 236 | "A": [ 13, 12 ], 237 | "B": [ "0", "1" ], 238 | "Y": [ 58 ] 239 | } 240 | }, 241 | "$procmux$38_CMP0": { 242 | "hide_name": 1, 243 | "type": "$eq", 244 | "parameters": { 245 | "A_SIGNED": 0, 246 | "A_WIDTH": 2, 247 | "B_SIGNED": 0, 248 | "B_WIDTH": 2, 249 | "Y_WIDTH": 1 250 | }, 251 | "attributes": { 252 | }, 253 | "port_directions": { 254 | "A": "input", 255 | "B": "input", 256 | "Y": "output" 257 | }, 258 | "connections": { 259 | "A": [ 13, 12 ], 260 | "B": [ "1", "0" ], 261 | "Y": [ 59 ] 262 | } 263 | }, 264 | "$procmux$39_CMP0": { 265 | "hide_name": 1, 266 | "type": "$eq", 267 | "parameters": { 268 | "A_SIGNED": 0, 269 | "A_WIDTH": 2, 270 | "B_SIGNED": 0, 271 | "B_WIDTH": 2, 272 | "Y_WIDTH": 1 273 | }, 274 | "attributes": { 275 | }, 276 | "port_directions": { 277 | "A": "input", 278 | "B": "input", 279 | "Y": "output" 280 | }, 281 | "connections": { 282 | "A": [ 13, 12 ], 283 | "B": [ "0", "0" ], 284 | "Y": [ 60 ] 285 | } 286 | }, 287 | "$reduce_xor$input.v:27$4": { 288 | "hide_name": 1, 289 | "type": "$reduce_xor", 290 | "parameters": { 291 | "A_SIGNED": 0, 292 | "A_WIDTH": 9, 293 | "Y_WIDTH": 1 294 | }, 295 | "attributes": { 296 | "src": "input.v:27" 297 | }, 298 | "port_directions": { 299 | "A": "input", 300 | "Y": "output" 301 | }, 302 | "connections": { 303 | "A": [ 39, 40, 41, 42, 43, 44, 45, 46, 47 ], 304 | "Y": [ 48 ] 305 | } 306 | }, 307 | "$sub$input.v:16$2": { 308 | "hide_name": 1, 309 | "type": "$sub", 310 | "parameters": { 311 | "A_SIGNED": 0, 312 | "A_WIDTH": 9, 313 | "B_SIGNED": 0, 314 | "B_WIDTH": 3, 315 | "Y_WIDTH": 10 316 | }, 317 | "attributes": { 318 | "src": "input.v:16" 319 | }, 320 | "port_directions": { 321 | "A": "input", 322 | "B": "input", 323 | "Y": "output" 324 | }, 325 | "connections": { 326 | "A": [ 16, 17, 18, 19, 20, 21, 22, 23, 24 ], 327 | "B": [ "1", "0", "1" ], 328 | "Y": [ 49, 50, 51, 52, 53, 54, 55, 56, 57, 37 ] 329 | } 330 | } 331 | }, 332 | "netnames": { 333 | "$0\\borrow_out[0:0]": { 334 | "hide_name": 1, 335 | "bits": [ 38 ], 336 | "attributes": { 337 | "src": "input.v:14" 338 | } 339 | }, 340 | "$0\\carry_out[0:0]": { 341 | "hide_name": 1, 342 | "bits": [ 36 ], 343 | "attributes": { 344 | "src": "input.v:14" 345 | } 346 | }, 347 | "$0\\cnt_dn[9:0]": { 348 | "hide_name": 1, 349 | "bits": [ 49, 50, 51, 52, 53, 54, 55, 56, 57, 37 ], 350 | "attributes": { 351 | "src": "input.v:14" 352 | } 353 | }, 354 | "$0\\cnt_up[9:0]": { 355 | "hide_name": 1, 356 | "bits": [ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 ], 357 | "attributes": { 358 | "src": "input.v:14" 359 | } 360 | }, 361 | "$0\\count_out[8:0]": { 362 | "hide_name": 1, 363 | "bits": [ 39, 40, 41, 42, 43, 44, 45, 46, 47 ], 364 | "attributes": { 365 | "src": "input.v:14" 366 | } 367 | }, 368 | "$0\\parity_out[0:0]": { 369 | "hide_name": 1, 370 | "bits": [ 48 ], 371 | "attributes": { 372 | "src": "input.v:14" 373 | } 374 | }, 375 | "$procmux$37_CMP": { 376 | "hide_name": 1, 377 | "bits": [ 58 ], 378 | "attributes": { 379 | } 380 | }, 381 | "$procmux$38_CMP": { 382 | "hide_name": 1, 383 | "bits": [ 59 ], 384 | "attributes": { 385 | } 386 | }, 387 | "$procmux$39_CMP": { 388 | "hide_name": 1, 389 | "bits": [ 60 ], 390 | "attributes": { 391 | } 392 | }, 393 | "borrow_out": { 394 | "hide_name": 0, 395 | "bits": [ 15 ], 396 | "attributes": { 397 | "src": "input.v:9" 398 | } 399 | }, 400 | "carry_out": { 401 | "hide_name": 0, 402 | "bits": [ 14 ], 403 | "attributes": { 404 | "src": "input.v:9" 405 | } 406 | }, 407 | "clock": { 408 | "hide_name": 0, 409 | "bits": [ 2 ], 410 | "attributes": { 411 | "src": "input.v:6" 412 | } 413 | }, 414 | "count_out": { 415 | "hide_name": 0, 416 | "bits": [ 16, 17, 18, 19, 20, 21, 22, 23, 24 ], 417 | "attributes": { 418 | "src": "input.v:8" 419 | } 420 | }, 421 | "data_in": { 422 | "hide_name": 0, 423 | "bits": [ 3, 4, 5, 6, 7, 8, 9, 10, 11 ], 424 | "attributes": { 425 | "src": "input.v:5" 426 | } 427 | }, 428 | "down": { 429 | "hide_name": 0, 430 | "bits": [ 13 ], 431 | "attributes": { 432 | "src": "input.v:6" 433 | } 434 | }, 435 | "parity_out": { 436 | "hide_name": 0, 437 | "bits": [ 25 ], 438 | "attributes": { 439 | "src": "input.v:9" 440 | } 441 | }, 442 | "up": { 443 | "hide_name": 0, 444 | "bits": [ 12 ], 445 | "attributes": { 446 | "src": "input.v:6" 447 | } 448 | } 449 | } 450 | } 451 | } 452 | } --------------------------------------------------------------------------------