├── .gitignore ├── screenshot.png ├── README.md ├── lib ├── index.js ├── to-string2_16.js ├── expr.js ├── simplify.js ├── construct.js ├── is-xor4.js ├── d.js ├── single.js ├── fpga-gates.js ├── lut.js ├── lut4cache.js └── fpga.js ├── demo ├── svg.js ├── dropzone.js ├── icedrom.js └── arizona.js ├── .travis.yml ├── LICENSE ├── package.json └── test └── lut4.js /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | node_modules 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drom/icedrom/HEAD/screenshot.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Travis](https://travis-ci.org/drom/icedrom.svg?branch=master)](https://travis-ci.org/drom/icedrom) 2 | # icedrom 3 | 4 | FPGA content visualized 5 | 6 | ![screenshot](screenshot.png "screenshot") 7 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fpga = require('./fpga'); 4 | var lut = require('./lut'); 5 | var pkg = require('../package.json'); 6 | 7 | exports.fpga = fpga; 8 | exports.lut = lut; 9 | exports.version = pkg.version; 10 | -------------------------------------------------------------------------------- /demo/svg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function svg (body) { 4 | var opt = body[1]; 5 | return ['svg', { 6 | xmlns: 'http://www.w3.org/2000/svg', 7 | width: opt.w || 512, 8 | height: opt.h || 512, 9 | viewBox: [0, 0, opt.w || 512, opt.h || 512].join(' ') 10 | }, body ]; 11 | } 12 | 13 | module.exports = svg; 14 | -------------------------------------------------------------------------------- /lib/to-string2_16.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function toString2_16 (num) { 4 | var res = '0000000000000000'; 5 | if (typeof num === 'string') { 6 | num = parseInt(num, 10); 7 | } 8 | res += num.toString(2); 9 | res = res.slice(-16); 10 | res = res.slice(0,4) + 11 | '_' + res.slice(4,8) + 12 | '_' + res.slice(8,12) + 13 | '_' + res.slice(12,16); 14 | return res; 15 | } 16 | 17 | module.exports = toString2_16; 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | sudo: false 5 | env: 6 | matrix: 7 | - TRAVIS_NODE_VERSION="13" 8 | - TRAVIS_NODE_VERSION="12" 9 | - TRAVIS_NODE_VERSION="10" 10 | - TRAVIS_NODE_VERSION="8" 11 | install: 12 | - rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION 13 | - npm --version 14 | - npm install 15 | script: npm test 16 | -------------------------------------------------------------------------------- /lib/expr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function expr (desc, first, last) { 4 | var keys = Object.keys(desc); 5 | var res = keys.map(function (key) { 6 | return '(' + 7 | desc[key].map(function (sig) { 8 | if (typeof sig === 'string') { 9 | return sig; 10 | } else { 11 | return sig.join(''); 12 | } 13 | }).join(first) + 14 | ')'; 15 | }); 16 | return res.join(last); 17 | } 18 | 19 | module.exports = expr; 20 | -------------------------------------------------------------------------------- /lib/simplify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function simplify (data, terms) { 4 | var masks = Object.keys(terms); 5 | 6 | while (masks.some(function (mask, idx) { 7 | var sum = 0; 8 | masks.forEach(function (m1, i) { 9 | if (i !== idx) { 10 | sum = sum | m1; 11 | } 12 | }); 13 | if (sum == data) { 14 | // console.log('removed: ' + mask); 15 | masks.splice(idx, 1); 16 | delete terms[mask]; 17 | return true; 18 | } 19 | })); 20 | return terms; 21 | } 22 | 23 | module.exports = simplify; 24 | -------------------------------------------------------------------------------- /lib/construct.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function construct (data, groups) { 4 | var res = {}; 5 | var tmp = 0; 6 | groups.some(function (g) { 7 | return Object.keys(g).some(function (key) { 8 | if ((data & key) == key) { 9 | if ((tmp | key) != tmp) { 10 | res[key] = g[key]; 11 | tmp = tmp | key; 12 | if (tmp == data) { 13 | return true; 14 | } 15 | } 16 | } 17 | }); 18 | }); 19 | return res; 20 | } 21 | 22 | module.exports = construct; 23 | -------------------------------------------------------------------------------- /demo/dropzone.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var tspan = require('tspan'); 4 | 5 | function dropzone (label) { 6 | var res = ['g', { w: 640, h: 128, draggable: true }, 7 | ['rect', { 8 | x: 0, y: 0, 9 | width: 640, height: 128, 10 | stroke: '#000', 11 | fill: 'none', 12 | 'stroke-width': '1px' 13 | }] 14 | ]; 15 | label.forEach(function (t, y) { 16 | var ts = tspan.parse(t); 17 | res.push( 18 | ['text', { x: 320, y: 20 * y, 'text-anchor': 'middle'}] 19 | .concat(ts) 20 | ); 21 | }); 22 | return res; 23 | 24 | } 25 | 26 | module.exports = dropzone; 27 | -------------------------------------------------------------------------------- /lib/is-xor4.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isXor4 (data) { 4 | var i0s = [0x0000, 0x5555, 0xaaaa], 5 | i1s = [0x0000, 0x3333, 0xcccc], 6 | i2s = [0x0000, 0x0f0f, 0xf0f0], 7 | i3s = [0x0000, 0x00ff, 0xff00]; 8 | 9 | var i0, i1, i2, i3; 10 | for (i0 = 0; i0 < 3; i0++) { 11 | for (i1 = 0; i1 < 3; i1++) { 12 | for (i2 = 0; i2 < 3; i2++) { 13 | for (i3 = 0; i3 < 3; i3++) { 14 | if ((i0s[i0] ^ i1s[i1] ^ i2s[i2] ^ i3s[i3]) === data) { 15 | return [i0, i1, i2, i3]; 16 | } 17 | } 18 | } 19 | } 20 | } 21 | } 22 | 23 | module.exports = isXor4; 24 | -------------------------------------------------------------------------------- /demo/icedrom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var stringify = require('onml/lib/stringify'), 4 | arizona = require('./arizona'), 5 | dropzone = require('./dropzone'), 6 | lib = require('../lib'), 7 | svg = require('./svg'); 8 | 9 | var icedrom = document.getElementById('icedrom'); 10 | 11 | icedrom.innerHTML = stringify(svg(dropzone([ 12 | '', 13 | 'yosys -q -p "synth_ice40 -blif $PRJ.blif" $PRJ.v', 14 | 'arachne-pnr $PRJ.blif -d 8k -P tq144:4k --post-place-blif $PRJ.post.blif', 15 | 'yosys -q -o $PRJ.post.json $PRJ.post.blif', 16 | '', 17 | 'Drop $PRJ.post.json here' 18 | ]))); 19 | 20 | arizona() 21 | .listen(icedrom) 22 | .onfile(function (data, name) { 23 | var mySVG = svg( 24 | lib.fpga({ 25 | fname: name, 26 | body: JSON.parse(data) 27 | }) 28 | ); 29 | icedrom.innerHTML = stringify(mySVG); 30 | }); 31 | -------------------------------------------------------------------------------- /lib/d.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | origin: 'M32.5,16.5', 5 | 6 | mux01: 'h-4 z m-4,0 v-8 z m0,-8 h-6 z m-6,4 l-6,4 v-16 l6,4 z m10,8', 7 | mux02: 'h-4 z m-4,0 v-4 z m0,-4 h-6 z m-6,8 l-6,4 v-24 l6,4 z m10,-4', 8 | 9 | and10: 'm-16,-10 h5 c6,0 11,4 11,10 0,6 -5,10 -11,10 h-5 z', 10 | and8: 'm-12,-8 h4 c4,0 8,4 8,8 0,4 -4,8 -8,8 h-5 z', 11 | and6: 'q0 6,-8 6 v-12 q8 0,8 6 z', 12 | 13 | or10: 'm-18,-10' + 14 | 'h4 c6,0 12,5 14,10 -2,5 -8,10 -14,10 h-4' + 15 | 'c2.5,-5 2.5,-15 0,-20 z', 16 | 17 | or6: 'q-2 6,-8 6 q2 -6,0 -12 q6 0,8 6 z ', 18 | 19 | xor10: 'm-21,-10 c1,3 2,6 2,10 0,4 -1,7 -2,10' + 20 | 'm3,-20' + 21 | 'h4 c6,0 12,5 14,10 -2,5 -8,10 -14,10 h-4' + 22 | 'c1,-3 2,-6 2,-10 0,-4 -1,-7 -2,-10 z', 23 | 24 | xor6: 'q-2 6,-6 6 q2 -6,0 -12 q4 0,6 6 z m-9 -6 q2 6,0 12 z m9 6', 25 | 26 | circle: 'm4,0 c0 1.1,-0.9 2,-2 2 s -2 -0.9,-2 -2 s 0.9 -2,2 -2 s 2 0.9,2 2 z', 27 | buf: 'l-12,8 v-16 z' 28 | }; 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2020 Aliaksei Chapyzhenka 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "icedrom", 3 | "version": "0.1.0", 4 | "description": "FPGA schematic rendering", 5 | "main": "lib/", 6 | "scripts": { 7 | "test": "eslint lib test && nyc mocha test", 8 | "build": "browserify demo/icedrom.js > build/app.js", 9 | "unpkg": "browserify --standalone icedrom lib/index.js > build/icedrom.js", 10 | "prepublish": "npm run test && mkdir -p build && npm run unpkg" 11 | }, 12 | "files": [ 13 | "build/icedrom.js", 14 | "lib/*" 15 | ], 16 | "unpkg": "build/icedrom.js", 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/drom/icedrom.git" 20 | }, 21 | "keywords": [ 22 | "FPGA" 23 | ], 24 | "author": "Aliaksei Chapyzhenka", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/drom/icedrom/issues" 28 | }, 29 | "homepage": "https://github.com/drom/icedrom#readme", 30 | "devDependencies": { 31 | "@drom/eslint-config": "^0.10.0", 32 | "browserify": "^16.5.0", 33 | "chai": "^4.2.0", 34 | "eslint": "^6.8.0", 35 | "nyc": "^15.0.0", 36 | "esprima": "^4.0.1", 37 | "mocha": "^7.1.0", 38 | "onml": "^1.2.0", 39 | "tspan": "^0.3.6" 40 | }, 41 | "eslintConfig": { 42 | "extends": "@drom/eslint-config/eslint4/node4" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/single.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var d = require('./d'), 4 | expr = require('./expr'); 5 | 6 | var wires = { 7 | i0: 'M8.5 4.5 v12 h24', 8 | i1: 'M8.5 12.5 v4 h24', 9 | i2: 'M8.5 20.5 v-4 h24', 10 | i3: 'M8.5 28.5 v-12 h24' 11 | }; 12 | 13 | function constant (val) { 14 | return ['text', { 15 | x: 24, y: 20, 16 | 'text-anchor': 'middle' 17 | }, val]; 18 | } 19 | 20 | function single (desc) { 21 | var term, res; 22 | var keys = Object.keys(desc); 23 | 24 | if (keys.length === 0) { 25 | return constant(0); 26 | } 27 | 28 | if (keys.length === 1) { 29 | term = desc[keys[0]]; 30 | 31 | if (typeof term === 'string') { // TODO check for value? 32 | return constant(1); 33 | } else 34 | 35 | if (term.length === 1) { 36 | res = ['g', { class: 'gate' }, 37 | ['title', {}, expr(desc, ' & ', ' | ')], 38 | ['path', { 39 | d: wires[ 40 | (typeof term[0] === 'string') ? 41 | term[0] : 42 | term[0][1] 43 | ] || '', 44 | fill: 'none' 45 | }] 46 | ]; 47 | if (typeof term[0] !== 'string') { 48 | res.push(['path', { 49 | d: 'M24.5 16.5' + d.buf + d.circle 50 | }]); 51 | } else { 52 | res.push(['rect', { 53 | x: 8, y: 0, width: 24, height: 32, 54 | 'fill-opacity': 0.1, stroke: 'none' 55 | }]); 56 | } 57 | return res; 58 | } 59 | } 60 | } 61 | 62 | module.exports = single; 63 | -------------------------------------------------------------------------------- /test/lut4.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var lut4 = require('../lib/lut'), 4 | lut4cache = require('../lib/lut4cache'), 5 | fs = require('fs'), 6 | svg = require('../demo/svg'), 7 | onml = require('onml'); 8 | 9 | function toString2_16 (num) { 10 | var res = '0000000000000000'; 11 | res += num.toString(2); 12 | res = res.slice(-16); 13 | res = res.slice(0,4) + 14 | '_' + res.slice(4,8) + 15 | '_' + res.slice(8,12) + 16 | '_' + res.slice(12,16); 17 | return res; 18 | } 19 | 20 | var inits = [ 21 | 0, // 0 22 | 0xffff, // 1 23 | 0xaaaa, // i3 24 | 0x5555, // ~i3 25 | 0xcccc, // i2 26 | 0x3333, // ~i2 27 | 0xf0f0, // i1 28 | 0x0f0f, // ~i1 29 | 0xff00, // i0 30 | 0x00ff, // ~i0 31 | 32 | 0x8000, // and4 33 | 0xfffe, // or4 34 | 35 | 0x8080, // and3 36 | 0xfefe, // or3 37 | 38 | 1, // nor4 TODO 39 | 2, 40 | 3, 41 | 4, 42 | 5, 43 | 6, 44 | 7, 45 | 8, 46 | 4096, // 47 | 112, 48 | 32423, 49 | 234, 50 | 143, // 0, 1, 2 51 | 52 | 27030, // XOR 53 | 54 | 0x4eee, // 0b0100 1110 1100 1100, 55 | 63743, 56 | 61168, 57 | 15530, 58 | 0xcaca, // mux 59 | 0x4f4f, 60 | 43808, 61 | 52426, 62 | 0xf800 63 | ]; 64 | 65 | describe('lut4', function () { 66 | it('inits', function (done) { 67 | var lut = lut4(); 68 | var res = ['g', { 69 | w: 320, 70 | h: 32 * inits.length 71 | }, 72 | ['style', { type: 'text/css' }, 73 | '' 80 | ] 81 | ]; 82 | var cache = lut4cache(); 83 | inits.forEach(function (data, i) { 84 | if (cache[data] === undefined) { 85 | cache[data] = lut(data); 86 | } 87 | res.push(['g', { 88 | transform: 'translate(32,' + (32 * i) + ')' 89 | }, 90 | cache[data], 91 | ['text', { x: 96, y: 24 }, toString2_16(data)] 92 | ]); 93 | }); 94 | fs.writeFile('test.svg', onml.s(svg(res)), done); 95 | }); 96 | }); 97 | 98 | /* eslint no-console:1 */ 99 | /* eslint-env mocha */ 100 | -------------------------------------------------------------------------------- /lib/fpga-gates.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | var d = require('./d'), 4 | expr = require('./expr'), 5 | single = require('./single'); 6 | 7 | var pinInvert = { 8 | i0: ' M8.5 4.5h4' + d.circle, 9 | i1: ' M8.5 12.5h4' + d.circle, 10 | i2: ' M8.5 20.5h4' + d.circle, 11 | i3: ' M8.5 28.5h4' + d.circle 12 | }; 13 | 14 | var pin = { 15 | i0: ' M8.5 4.5h8v2', 16 | i1: ' M8.5 12.5h8', 17 | i2: ' M8.5 20.5h8', 18 | i3: ' M8.5 28.5h8v-2' 19 | }; 20 | 21 | function inverters (term) { 22 | var res = ''; 23 | if (typeof term === 'object') { 24 | term.forEach(function (inp) { 25 | if (typeof inp === 'string') { 26 | if (pin[inp]) { 27 | res += pin[inp]; 28 | } 29 | } else { 30 | if (pinInvert[inp[1]]) { 31 | res += pinInvert[inp[1]]; 32 | } 33 | } 34 | }); 35 | } 36 | return res; 37 | } 38 | 39 | function and (desc) { 40 | var terms = Object.keys(desc); 41 | if (terms.length === 1) { 42 | return ['path', { 43 | d: 'M32.5 16.5' + d.and10 + inverters(desc[terms[0]]), 44 | class: 'gate' 45 | }, 46 | ['title', {}, expr(desc, ' & ', ' | ')] 47 | ]; 48 | } 49 | } 50 | 51 | function or (desc) { 52 | var terms = Object.keys(desc); 53 | if (terms.length === 1) { 54 | return ['path', { 55 | d: 'M32.5 16.5' + d.or10 + inverters(desc[terms[0]]), 56 | class: 'gate' 57 | }, 58 | ['title', {}, expr(desc, ' | ', ' & ')] 59 | ]; 60 | } 61 | } 62 | 63 | function xorer (desc) { 64 | // console.log(desc); 65 | var dd = 'M32.5 16.5' + d.xor10; 66 | desc.forEach(function (e, i) { 67 | if (e === 1) { 68 | dd += pin['i' + i]; 69 | } else 70 | if (e === 2) { 71 | dd += pinInvert['i' + i]; 72 | } 73 | }); 74 | return ['path', { d: dd, class: 'gate' }, 75 | ['title', {}, desc.join(',')] 76 | ]; 77 | } 78 | 79 | function blackbox (sumOfProducts, productOfSums) { 80 | return ['rect', { 81 | x: 8.5, y: 2.5, width: 20, height: 28, 82 | class: 'bbox' 83 | }, ['title', {}, 84 | expr(sumOfProducts, ' & ', ' | ') + 85 | ' \n ' + 86 | expr(productOfSums, ' | ', ' & ') 87 | ] 88 | ]; 89 | } 90 | 91 | function gates () { 92 | return { 93 | and: and, 94 | or: or, 95 | xorer: xorer, 96 | single: single, 97 | blackbox: blackbox 98 | }; 99 | } 100 | 101 | module.exports = gates; 102 | -------------------------------------------------------------------------------- /demo/arizona.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function arizona () { 4 | 5 | var onLoadCall; 6 | 7 | function onLoad (f) { 8 | var myReader = new FileReader(); 9 | 'onabort onerror onload onloadstart onloadend onloadprogress' 10 | .split(' ') 11 | .forEach(function (e) { 12 | myReader[e] = function (p) { 13 | console.log(p); 14 | if (p.type === 'loadend') { 15 | onLoadCall(p.currentTarget.result, f.name); 16 | } 17 | }; 18 | }); 19 | console.log(myReader.readAsText(f)); 20 | } 21 | 22 | var lis = { 23 | dragover: function (event) { event.preventDefault(); }, 24 | dragenter: null, 25 | dragleave: null, 26 | drop: function(event) { 27 | var i; 28 | event.preventDefault(); 29 | var dt = event.dataTransfer; 30 | if (dt.items) { 31 | // Use DataTransferItemList interface to access the file(s) 32 | for (i = 0; i < dt.items.length; i++) { 33 | if (dt.items[i].kind === 'file') { 34 | var f = dt.items[i].getAsFile(); 35 | console.log('[1] file[' + i + '].name = ' + f.name); 36 | onLoad(f); 37 | } 38 | } 39 | } else { 40 | // Use DataTransfer interface to access the file(s) 41 | for (i = 0; i < dt.files.length; i++) { 42 | console.log('[2] file[' + i + '].name=' + dt.files[i].name); 43 | console.log(dt.files[i]); 44 | onLoad(dt.files[i]); 45 | } 46 | } 47 | } 48 | }; 49 | 50 | var self = { 51 | listen: function (el) { 52 | 53 | var inp = document.createElement('input'); 54 | inp.setAttribute('type', 'file'); 55 | el.insertBefore(inp, el.childNodes[0]); 56 | // el.appendChild(inp); 57 | inp.addEventListener('change', function () { 58 | console.log(onLoad(inp.files[0])); 59 | }, false); 60 | 61 | Object.keys(lis).forEach(function (key) { 62 | var cb; 63 | if (typeof lis[key] === 'function') { 64 | cb = function (event) { 65 | lis[key](event); 66 | console.log(key); 67 | }; 68 | } else { 69 | cb = function (/*event*/) { 70 | console.log(key); 71 | }; 72 | } 73 | // document.addEventListener(key, cb, false); 74 | el.addEventListener(key, cb, false); 75 | }); 76 | 77 | return self; 78 | }, 79 | onfile: function (cb) { 80 | onLoadCall = cb; 81 | return self; 82 | } 83 | }; 84 | return self; 85 | } 86 | 87 | module.exports = arizona; 88 | /* eslint no-console:0 */ 89 | -------------------------------------------------------------------------------- /lib/lut.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gates = require('./fpga-gates')(), 4 | simplify = require('./simplify'), 5 | construct = require('./construct'), 6 | isXor4 = require('./is-xor4'); 7 | 8 | function toString2_16 (data) { 9 | return ( 10 | '0000000000000000' + 11 | parseInt(data, 10).toString(2) 12 | ).slice(-16); 13 | } 14 | 15 | function invertInputs (desc) { 16 | var res = {}; 17 | Object.keys(desc).forEach(function (key) { 18 | res[key] = desc[key].map(function (term) { 19 | if (typeof term === 'string') { 20 | return ['~', term]; 21 | } else { 22 | return term[1]; 23 | } 24 | }); 25 | }); 26 | return res; 27 | } 28 | 29 | function lut4 () { 30 | 31 | var g0 = { 32 | 0xffff: '1' 33 | }; 34 | 35 | var singles = [ 36 | [0xaaaa, 'i0'], 37 | [0xcccc, 'i1'], 38 | [0xf0f0, 'i2'], 39 | [0xff00, 'i3'] 40 | // [0xff00, 'C'], 41 | // [0xf0f0, 'D'], 42 | // [0xcccc, 'A'], 43 | // [0xaaaa, 'B'] 44 | ]; 45 | 46 | var arr = []; 47 | singles.forEach(function (e) { 48 | arr.push(e); 49 | arr.push([~e[0] & 0xffff, ['~', e[1]]]); 50 | }); 51 | 52 | var g1 = {}; 53 | arr.forEach(function (a) { 54 | var idx = a[0]; 55 | if (idx && !g1[idx]) { 56 | g1[idx] = [a[1]]; 57 | } 58 | }); 59 | 60 | var g2 = {}; 61 | arr.forEach(function (a) { 62 | arr.forEach(function (b) { 63 | var idx = a[0] & b[0]; 64 | if (idx && !g1[idx] && !g2[idx]) { 65 | g2[idx] = [a[1], b[1]]; 66 | } 67 | }); 68 | }); 69 | 70 | var g3 = {}; 71 | arr.forEach(function (a) { 72 | arr.forEach(function (b) { 73 | arr.forEach(function (c) { 74 | var idx = a[0] & b[0] & c[0]; 75 | if (idx && !g1[idx] && !g2[idx] && !g3[idx]) { 76 | g3[idx] = [a[1], b[1], c[1]]; 77 | } 78 | }); 79 | }); 80 | }); 81 | 82 | var g4 = {}; 83 | arr.forEach(function (a) { 84 | arr.forEach(function (b) { 85 | arr.forEach(function (c) { 86 | arr.forEach(function (d) { 87 | var idx = a[0] & b[0] & c[0] & d[0]; 88 | if (idx && !g1[idx] && !g2[idx] && !g3[idx] && !g4[idx]) { 89 | g4[idx] = [a[1], b[1], c[1], d[1]]; 90 | } 91 | }); 92 | }); 93 | }); 94 | }); 95 | var groups = [g0, g1, g2, g3, g4]; 96 | 97 | return function (data) { 98 | console.log(toString2_16(data)); 99 | 100 | var sumOfProducts = construct(data, groups); 101 | sumOfProducts = simplify(data, sumOfProducts); 102 | console.log(JSON.stringify(sumOfProducts, null, 4)); 103 | 104 | var resSingle = gates.single(sumOfProducts); 105 | if (resSingle) { 106 | return resSingle; 107 | } 108 | 109 | var resAnd = gates.and(sumOfProducts); 110 | if (resAnd) { 111 | return resAnd; 112 | } 113 | 114 | var productOfSums = construct(~data & 0xffff, groups); 115 | productOfSums = simplify(~data & 0xffff, productOfSums); 116 | productOfSums = invertInputs(productOfSums); 117 | console.log(JSON.stringify(productOfSums, null, 4)); 118 | 119 | var resOr = gates.or(productOfSums); 120 | if (resOr) { 121 | return resOr; 122 | } 123 | 124 | var xorer = isXor4(data); 125 | if (xorer) { 126 | return gates.xorer(xorer); 127 | } 128 | 129 | console.log('BLACKBOX!'); 130 | 131 | return gates.blackbox(sumOfProducts, productOfSums); 132 | }; 133 | } 134 | 135 | module.exports = lut4; 136 | /* eslint no-console:1 */ 137 | -------------------------------------------------------------------------------- /lib/lut4cache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var d = require('./d'); 4 | 5 | // var xor = 'm -21,-10 c1,3 2,6 2,10 m0,0 c0,4 -1,7 -2,10 m3,-20 4,0 c6,0 12,5 14,10 -2,5 -8,10 -14,10 l-4,0 c1,-3 2,-6 2,-10 0,-4 -1,-7 -2,-10 z'; 6 | // var circle = ' M 4,0 C 4,1.1 3.1,2 2,2 0.9,2 0,1.1 0,0 c 0,-1.1 0.9,-2 2,-2 1.1,0 2,0.9 2,2 z'; 7 | // var circle = 'm 4 0 c 0 1.1,-0.9 2,-2 2 s -2 -0.9,-2 -2 s 0.9 -2,2 -2 s 2 0.9,2 2 z'; 8 | // var and = 'm -16,-10 5,0 c 6,0 11,4 11,10 0,6 -5,10 -11,10 l -5,0 z'; 9 | // var buf = 'l-12,8 v-16 z'; 10 | // var or = 'm -18,-10 4,0 c 6,0 12,5 14,10 -2,5 -8,10 -14,10 l -4,0 c 2.5,-5 2.5,-15 0,-20 z'; 11 | // 12 | // var inputs = { 13 | // i0t1: 'M0 4 h12 v4 h4', 14 | // 15 | // i1t1: 'M0 12 h4 v-4 h12', 16 | // i1t2: 'M0 12 h16', 17 | // 18 | // i2t1: 'M0 20 h4 v-12 h12', 19 | // i2t3: 'M0 20 h8 v-4 h8', 20 | // 21 | // i3t3: 'M0 28 h12 v-12 h4', 22 | // i3t4: 'M0 28 h12 v-8 h4' 23 | // }; 24 | // 25 | // function group (body) { 26 | // var res = ['g', { transform: 'translate(0.5,0.5)' }]; 27 | // body.forEach(function (e) { 28 | // res.push(e); 29 | // }); 30 | // return res; 31 | // } 32 | 33 | function lut4cache () { 34 | 35 | var cache = {}; 36 | 37 | cache[0xcaca] = ['path', { 38 | d: d.origin + 39 | 'm-24 -12 h8 z m24 12' + 40 | 'm-24 -4 h8 z m24 4 ' + 41 | 'm-24 4 h10 z m10 0 v-5 z m14 -4 ' + 42 | d.mux01, 43 | class: 'gate' 44 | }, 45 | ['title', {}, 'i2 ? i1 : i0'] 46 | ]; 47 | 48 | cache[0x3caa] = ['path', { 49 | d: d.origin + 50 | 'm-24 -12 h8 z m24 12' + 51 | 'm-24 12 h10 z m10 0 v-5 z m14 -12' + 52 | d.mux02 + 53 | 'm-16,0' + d.xor6, 54 | class: 'gate' 55 | }, 56 | ['title', {}, 'i3 ? (i1 ^ i2) : i0'] 57 | ]; 58 | 59 | cache[0xccca] = ['path', { 60 | d: d.origin + 61 | 'm-24 -12 h8 z m24 12' + 62 | 'm-24 -4 h8 z m24 4 ' + 63 | 'm-16 8 h2 z m2 0 v-9 z m14 -8' + 64 | d.mux01 + 65 | 'm-16 4' + d.or6, 66 | class: 'gate' 67 | }, 68 | ['title', {}, '(i2 | i3) ? i1 : i0'] 69 | ]; 70 | 71 | cache[0xf800] = ['path', { 72 | d: d.origin + 73 | 'm-24 4 h4 z m4 0 v-4 z m0 -4 h4 z m20 0' + 74 | 'm-24 12 h12 z m12 0 v-8 z m0 -8 h4 z m12 -4' + 75 | d.and6 + ' m-8 -4' + d.or6 + 'm-8 -4' + d.and6, 76 | class: 'gate' 77 | }, 78 | ['title', {}, '((i0 & i1) | i2) & i3'] 79 | ]; 80 | 81 | 82 | // cache[0xffff] = group([ 83 | // ['text', { x: 24, y: 20, 'text-anchor': 'middle' }, '1'] 84 | // ]); 85 | // 86 | // cache[0x0ff0] = group([ 87 | // ['path', { 88 | // d: inputs.i2t1 + inputs.i3t3, 89 | // fill: 'none', stroke: '#000' 90 | // }], 91 | // ['path', { 92 | // d: 'M32 16' + xor, 93 | // fill: '#ffb', stroke: '#000' 94 | // }] 95 | // ]); 96 | // 97 | // cache[0x3c3c] = group([ 98 | // ['path', { 99 | // d: inputs.i1t1 + inputs.i2t3, 100 | // fill: 'none', stroke: '#000' 101 | // }], 102 | // ['path', { 103 | // d: 'M32 16' + xor, 104 | // fill: '#ffb', stroke: '#000' 105 | // }] 106 | // ]); 107 | // 108 | // cache[0x5555] = group([ 109 | // ['path', { 110 | // d: 'M0,4 h12 v8 h12', 111 | // fill: 'none', stroke: '#000' 112 | // }], 113 | // ['path', { 114 | // d: 'M32 16' + buf + circle, 115 | // fill: '#ffb', stroke: '#000' 116 | // }] 117 | // ]); 118 | // 119 | // cache[0xaaaa] = group([ 120 | // ['path', { 121 | // d: 'M0,4 h12 v8 h12', 122 | // fill: 'none', stroke: '#000' 123 | // }], 124 | // ['path', { 125 | // d: 'M32 16' + buf, 126 | // fill: '#ffb', stroke: '#000' 127 | // }] 128 | // ]); 129 | // 130 | // var arr16 = Array.apply(null, Array(16)); 131 | // 132 | // arr16.forEach(function (e, i) { 133 | // var tt = 1 << i; 134 | // var res = ['g', {}, 135 | // ['path', { d: 'M32 16' + and, fill: '#ffb', stroke: '#000' }, 136 | // ['title', {}, tt] 137 | // ] 138 | // ]; 139 | // [1, 2, 4, 8].forEach(function (mask, maski) { 140 | // if (!(i & mask)) { 141 | // res.push(['path', { 142 | // d: 'M12 ' + (4 + 8 * maski) + circle, 143 | // fill: '#ffb', stroke: '#000' 144 | // }]); 145 | // } 146 | // }); 147 | // cache[tt] = res; 148 | // }); 149 | // 150 | // arr16.forEach(function (e, i) { 151 | // var tt = 0xffff ^ (1 << i); 152 | // var res = ['g', {}, 153 | // ['path', { d: 'M32 16' + or, fill: '#ffb', stroke: '#000' }, 154 | // ['title', {}, tt] 155 | // ] 156 | // ]; 157 | // [1, 2, 4, 8].forEach(function (mask, maski) { 158 | // if (!(i & mask)) { 159 | // res.push(['path', { 160 | // d: 'M12 ' + (4 + 8 * maski) + circle, 161 | // fill: '#ffb', stroke: '#000' 162 | // }]); 163 | // } 164 | // }); 165 | // cache[tt] = res; 166 | // }); 167 | 168 | return cache; 169 | } 170 | 171 | module.exports = lut4cache; 172 | -------------------------------------------------------------------------------- /lib/fpga.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var lut4 = require('./lut')(); 4 | var lut4cache = require('./lut4cache')(); 5 | var toString2_16 = require('./to-string2_16'); 6 | var lut4count = {}; 7 | var blackboxes = {}; 8 | 9 | function lutSimplify (data, connections) { 10 | var mask0 = [ 11 | 0x5555, 12 | 0x3333, 13 | 0x0f0f, 14 | 0x00ff 15 | ]; 16 | var shift = [ 17 | 1, 18 | 2, 19 | 4, 20 | 8 21 | ]; 22 | 23 | 'I0 I1 I2 I3'.split(' ').forEach(function (inp, idx) { 24 | if (connections[inp][0] === '0') { 25 | // console.log(toString2_16(data), idx); 26 | data = (data & mask0[idx]) | 27 | ((data & mask0[idx]) << shift[idx]); 28 | // console.log(toString2_16(data)); 29 | } 30 | }); 31 | return data; 32 | } 33 | 34 | function runningSum (sum, e, idx, arr) { 35 | sum += e; 36 | arr[idx] = sum; 37 | return sum; 38 | } 39 | 40 | function fpga (params) { 41 | var modules = params.body.modules; 42 | var moduleNames = Object.keys(modules); 43 | var cells = modules[moduleNames[0]].cells; 44 | 45 | var res = ['g', {}, 46 | ['style', { type: 'text/css' }, 47 | '' 54 | ] 55 | ]; 56 | 57 | var colV = Array.apply(null, Array(34)).map(function () { return 1; }); 58 | var rowV = Array.apply(null, Array(34)).map(function () { return 1; }); 59 | 60 | Object.keys(cells).forEach(function (key) { 61 | var cell = cells[key]; 62 | var loc1 = cell.attributes.loc.split(','); 63 | var col = parseInt(loc1[0], 10); 64 | var loc2 = loc1[1].split('/'); 65 | var row = parseInt(loc2[0]); 66 | // var lc = parseInt(loc2[1]); 67 | colV[col] = 4; 68 | rowV[row] = 16; 69 | }); 70 | 71 | colV.reduce(runningSum, 0); 72 | rowV.reduce(runningSum, 0); 73 | 74 | colV.forEach(function (col, x) { 75 | rowV.forEach(function (row, y) { 76 | res.push(['rect', { 77 | x: 16 * col, 78 | y: 16 * row, 79 | width: 16, 80 | height: 16, 81 | stroke: 'none', 82 | opacity: 0.2, 83 | fill: ((x + y) & 1) ? '#fff' : '#000' 84 | }, ['title', {}, ((x + 1) + ',' + (y + 1))]]); 85 | }); 86 | }); 87 | 88 | var drivers = {}; 89 | 90 | Object.keys(cells).forEach(function (key) { 91 | var cell = cells[key]; 92 | var loc1 = cell.attributes.loc.split(','); 93 | var col = parseInt(loc1[0], 10); 94 | var loc2 = loc1[1].split('/'); 95 | var row = parseInt(loc2[0]); 96 | var lc = parseInt(loc2[1]); 97 | var connections; 98 | var connectionNames; 99 | 100 | if (cell.connections) { 101 | connections = cell.connections; 102 | connectionNames = Object.keys(connections); 103 | '^O-48-16 ^COUT-4-32 ^D_IN_0-48-16 ^D_IN_1-48-24 ^RDATA-48-16 ^GLOBAL_BUFFER_OUTPUT-48-16' 104 | .split(' ') 105 | .map(function (e) { 106 | var arr = e.split('-'); 107 | return { name: arr[0], x: parseInt(arr[1], 10), y: parseInt(arr[2], 10) }; 108 | }) 109 | .forEach(function (pin) { 110 | var driver; 111 | var x, y; 112 | connectionNames.forEach(function (name) { 113 | if ( 114 | name.match(pin.name) && 115 | connections[name].length 116 | ) { 117 | x = 16 * (colV[col] - 4) + pin.x; 118 | y = 16 * (rowV[row] - 16) + 32 * lc + pin.y; 119 | driver = connections[name][0]; 120 | drivers[driver] = { x: x, y: y }; 121 | } 122 | }); 123 | }); 124 | } 125 | }); 126 | 127 | // console.log(drivers); 128 | 129 | Object.keys(cells).forEach(function (key) { 130 | var cell = cells[key]; 131 | var loc1 = cell.attributes.loc.split(','); 132 | var col = parseInt(loc1[0], 10); 133 | var loc2 = loc1[1].split('/'); 134 | var row = parseInt(loc2[0]); 135 | var lc = parseInt(loc2[1]); 136 | var connections; 137 | var connectionNames; 138 | 139 | var wireGroup = ['g', { 140 | transform: 'translate(0.5,0.5)', 141 | 'stroke-linecap': 'round', 142 | fill: 'none', stroke: '#777' 143 | }]; 144 | 145 | if (cell.connections) { 146 | connections = cell.connections; 147 | connectionNames = Object.keys(connections); 148 | '^I0-0-4 ^I1-0-12 ^I2-0-20 ^I3-0-28 ^D_OUT_0-0-16 ^D_OUT_1-0-24 ^WDATA-0-16 ^WADDR-0-64 ^RADDR-0-128 ^WE-0-192 ^RE-0-208 ^WCLKE-0-224 ^RCLKE-0-240 ^USER_SIGNAL_TO_GLOBAL_BUFFER-0-16' 149 | .split(' ') 150 | .map(function (e) { 151 | var arr = e.split('-'); 152 | return { name: arr[0], x: parseInt(arr[1], 10), y: parseInt(arr[2], 10) }; 153 | }) 154 | .forEach(function (pin) { 155 | var x, y; 156 | connectionNames.forEach(function (name) { 157 | if ( 158 | name.match(pin.name) && 159 | connections[name].length && 160 | (typeof connections[name][0] === 'number') 161 | ) { 162 | var wireNumber = connections[name][0]; 163 | x = 16 * (colV[col] - 4) + pin.x; 164 | y = (16 * (rowV[row] - 16) + (lc * 32)) + pin.y; 165 | var groupO = ['path', { 166 | d: 'M' + drivers[wireNumber].x + 167 | ' ' + drivers[wireNumber].y + 168 | ' L ' + x + ' ' + y 169 | }]; 170 | wireGroup.push(groupO); 171 | } 172 | }); 173 | }); 174 | 175 | res.push(wireGroup); 176 | } 177 | }); 178 | 179 | 180 | var LUT_INIT; 181 | 182 | Object.keys(cells).forEach(function (key) { 183 | var cell = cells[key]; 184 | var loc1 = cell.attributes.loc.split(','); 185 | var col = parseInt(loc1[0], 10); 186 | var loc2 = loc1[1].split('/'); 187 | var row = parseInt(loc2[0]); 188 | var lc = parseInt(loc2[1]); 189 | var group = ['g', { 190 | transform: 'translate(' + 191 | (16 * (colV[col] - 4)) + ',' + 192 | (16 * (rowV[row] - 16) + (lc * 32)) + ')' 193 | }]; 194 | res.push(group); 195 | 196 | if (cell.type === 'SB_GB') { 197 | group.push(['rect', { 198 | x: 2, y: 2, width: 44, height: 28, 199 | stroke: 'none', fill: '#f51' 200 | }, ['title', {}, cell.attributes.loc] 201 | ]); 202 | } 203 | 204 | if (cell.type === 'SB_IO') { 205 | group.push(['g', {}, 206 | ['rect', { 207 | x: 2, y: 2, width: 44, height: 28, 208 | stroke: 'none', fill: '#1e5' 209 | }, 210 | ['title', {}, cell.attributes.loc] 211 | ], 212 | ['text', { x: 24, y: 20, 'text-anchor': 'middle'}, cell.connections.PACKAGE_PIN[0]] 213 | ]); 214 | } 215 | 216 | if (cell.type === 'SB_RAM40_4K') { 217 | group.push(['rect', { 218 | x: 2, y: 2, width: 44, height: 252, 219 | stroke: 'none', fill: '#d3d' 220 | }, ['title', {}, cell.attributes.loc] 221 | ]); 222 | } 223 | 224 | if (cell.type === 'ICESTORM_LC') { 225 | 'I0 I1 I2 I3' 226 | .split(' ') 227 | .forEach(function (pin, pindx) { 228 | if (typeof cell.connections[pin][0] === 'number') { 229 | group.push(['path', { 230 | d: 'M0.5 ' + (4.5 + 8 * pindx) + ' h8', 231 | fill: 'none', stroke: '#000' 232 | }]); 233 | } 234 | }); 235 | 236 | group.push(['path', { 237 | d: 'M28.5 16.5 h20', 238 | fill: 'none', stroke: '#000' 239 | }]); 240 | 241 | if (cell.parameters.CARRY_ENABLE) { 242 | group.push(['rect', { 243 | x: -4, y: 24, 244 | width: 8, height: 8, 245 | stroke: 'none', fill: '#333' 246 | }]); 247 | } 248 | 249 | LUT_INIT = cell.parameters.LUT_INIT; 250 | if (LUT_INIT) { 251 | LUT_INIT = lutSimplify(LUT_INIT, cell.connections); 252 | console.log(LUT_INIT); 253 | if (lut4cache[LUT_INIT] === undefined) { 254 | lut4cache[LUT_INIT] = lut4(LUT_INIT); 255 | if (lut4cache[LUT_INIT][0] === 'rect') { 256 | blackboxes[LUT_INIT] = true; 257 | } 258 | } 259 | if (lut4count[LUT_INIT] === undefined) { 260 | lut4count[LUT_INIT] = 1; 261 | } else { 262 | lut4count[LUT_INIT] += 1; 263 | } 264 | group.push(lut4cache[LUT_INIT]); 265 | } 266 | 267 | if (cell.parameters.DFF_ENABLE) { 268 | group.push(['rect', { 269 | x: 34, y: 2, width: 12, height: 28, 270 | stroke: 'none', fill: '#15f' 271 | }]); 272 | } 273 | 274 | } 275 | 276 | }); 277 | 278 | res[1] = { w: 16 * colV[colV.length - 1], h: 16 * rowV[rowV.length - 1] }; 279 | 280 | Object.keys(blackboxes).forEach(function (key) { 281 | if (lut4count[key] > 0) { 282 | console.log(key + ' = ' + toString2_16(key) + ': ' + lut4count[key]); 283 | } 284 | }); 285 | 286 | return res; 287 | } 288 | 289 | module.exports = fpga; 290 | /* eslint no-console:1 */ 291 | --------------------------------------------------------------------------------