├── test.cir ├── example.cir ├── .gitignore ├── make.sh ├── gen.sh ├── configure.diff ├── partsim.cir ├── notes.txt ├── spinit ├── LICENSE ├── pre.js └── README.md /test.cir: -------------------------------------------------------------------------------- 1 | test 2 | v1 1 0 1 3 | r1 1 0 2k 4 | .options filetype=ascii 5 | .dc v1 1 1 1 6 | .end 7 | -------------------------------------------------------------------------------- /example.cir: -------------------------------------------------------------------------------- 1 | Example netlist 2 | v1 1 0 dc 15 3 | r1 1 0 2.2k 4 | r2 1 2 3.3k 5 | r3 2 0 150 6 | .end 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/ 2 | tests/ 3 | man/ 4 | *.log 5 | Makefile 6 | config.status 7 | libtool 8 | ngspice.js 9 | ngspice.html 10 | ngspice.html.mem 11 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export EMCC_DEBUG=1 4 | export EMCC_CFLAGS="-g" 5 | emmake make 2>&1 | tee emmake.log 6 | # (ignore ngmakeidx issue) 7 | mv src/ngspice src/ngspice.bc 8 | source gen.sh 9 | -------------------------------------------------------------------------------- /gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export EMCC_CFLAGS= 4 | emcc -O2 -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=67108864 -s MAX_SETJMPS=50 --embed-file spinit@/usr/local/share/ngspice/scripts/spinit --embed-file test.cir --pre-js pre.js src/ngspice.bc -o ngspice.html 5 | -------------------------------------------------------------------------------- /configure.diff: -------------------------------------------------------------------------------- 1 | --- configure 2015-01-28 15:14:52.000000000 -0500 2 | +++ configure.patched 2015-01-28 15:14:37.000000000 -0500 3 | @@ -16646,7 +16646,7 @@ 4 | return 0; 5 | } 6 | _ACEOF 7 | -if ac_fn_c_try_link "$LINENO"; then : 8 | +if false; then : 9 | { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 10 | $as_echo "yes" >&6; } 11 | 12 | -------------------------------------------------------------------------------- /partsim.cir: -------------------------------------------------------------------------------- 1 | UnNamed Project Simulation 2 | V1 Net1000 0 15V 3 | R2 Net1002 Net1000 3.3K 4 | R1 0 Net1000 2.2K 5 | R3 0 Net1002 150 6 | .options rshunt = 1.0e12 KEEPOPINFO 7 | .control 8 | OP 9 | * OP Let expressions, if any: 10 | 11 | write Net1000 Net1002 I(V1) 12 | set appendwrite true 13 | dc V1 0 1 1 14 | *Let expressions, if any: 15 | 16 | write v(Net1000) v(Net1002) 17 | rusage everything 18 | .endc 19 | .end 20 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | test 2 | v1 1 0 1 3 | r1 1 0 2k 4 | .options filetype=ascii 5 | .dc v1 1 1 1 6 | .end 7 | 8 | 9 | UnNamed Project Simulation 10 | V1 Net1000 0 15V 11 | R2 Net1002 Net1000 3.3K 12 | R1 0 Net1000 2.2K 13 | R3 0 Net1002 150 14 | 15 | 16 | * ---- 17 | 18 | destroy 19 | devhelp 20 | listing 21 | remcirc 22 | reset 23 | save -- remember this is for saving on memory; must occur before run 24 | 25 | error handling is inconsistent, so `set strict_errorhandling` 26 | -------------------------------------------------------------------------------- /spinit: -------------------------------------------------------------------------------- 1 | * Standard ngspice init file 2 | alias exit quit 3 | alias acct rusage all 4 | set x11lineararcs 5 | *set rndseed=12 6 | ** ascii rawfile ** 7 | *set filetype=ascii 8 | ** frontend debug output ** 9 | *set ngdebug 10 | ** no asking after quit ** 11 | set noaskquit 12 | ** set the number of threads in openmp 13 | ** default (if compiled with --enable-openmp) is: 2 14 | *set num_threads=4 15 | 16 | strcmp __flag $program "ngspice" 17 | if $__flag = 0 18 | 19 | * For SPICE2 POLYs, edit the below line to point to the location 20 | * of your codemodel. 21 | 22 | * codemodel /usr/local/lib/ngspice/spice2poly.cm 23 | 24 | * The other codemodels 25 | * codemodel /usr/local/lib/ngspice/analog.cm 26 | * codemodel /usr/local/lib/ngspice/digital.cm 27 | * codemodel /usr/local/lib/ngspice/xtradev.cm 28 | * codemodel /usr/local/lib/ngspice/xtraevt.cm 29 | 30 | end 31 | unset __flag 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Concord Consortium 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /pre.js: -------------------------------------------------------------------------------- 1 | // TODO: Module.preRun should be an *array*; we clobber MEMFS initialization below! 2 | Module.preRun = Module.preRun || []; 3 | 4 | function OutStream() { 5 | this.buf = ""; 6 | } 7 | 8 | OutStream.prototype.accept = function(charCode) { 9 | if (charCode === null || charCode === 10) { 10 | console.log(this.buf); 11 | this.buf = ""; 12 | return; 13 | } 14 | this.buf = this.buf + String.fromCharCode(charCode); 15 | }; 16 | 17 | Module.preRun.push(function() { 18 | var inputs = ['help\n'].join('\n').split(''); 19 | function input() { 20 | var chr = inputs.shift(); 21 | if (!chr) { 22 | // If there's no more input, just pretend we pressed enter. 23 | // 24 | // Currently emscripten doesn't support nonblocking streams. If we return null 25 | // then emscripten's "stdin" stream is permanently at EOF. If we return undefined 26 | // then emscripten sets EAGAIN and permanently marks the stream as having errored 27 | // (All subsequent calls to getc() will return -1 once we return undefined from 28 | // input().) 29 | // However, with a slight tweak to the ngspice source, reading '\n' from stdin 30 | // simply quits the mainloop until the emscripten interval calls it back again. 31 | return 10; 32 | } 33 | return chr.charCodeAt(); 34 | } 35 | var stdout = new OutStream(); 36 | var stderr = new OutStream(); 37 | 38 | // Demonstration of how we can asynchronously control ngspice-js 39 | window.setTimeout(function() { 40 | console.log("(asynchronously appending to stdin)"); 41 | inputs = inputs.concat(['source test.cir', 'run', 'print V(1)'].join('\n').split('')); 42 | }, 1000); 43 | 44 | // Initialize stdin, stdout, and stderr 45 | FS.init(input, stdout.accept.bind(stdout), stderr.accept.bind(stderr)); 46 | }); 47 | 48 | Module.logReadFiles = true; 49 | 50 | // Need to enable 'pipe' (-p) mode for ngspice to accept our input from stdin 51 | Module.arguments = ['-p']; 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # build-ngspice-js 2 | 3 | Quick & dirty demonstration of building [ngspice](http://sourceforge.net/projects/ngspice/) to Javascript/[asm.js](http://asmjs.org/spec/latest/) with [Emscripten](http://emscripten.org/). This will eventually enable educational and other webapps to use SPICE circuit simulation lag-free and without requiring developers to provision a server that runs a SPICE binary. 4 | 5 | In order for the build product to be a manageable size (~2.5MB), most device types were removed from the ngspice source code before building. The current demo includes voltage and current sources, resistors, capacitors, inductors, diodes, bipolar junction transistors, and ideal voltage-controlled switches. Devices can be added back, but note they must be statically linked, so the device set should be chosen wisely based on project needs. 6 | 7 | Note this is intended to become a backend component to be used by a separate UI; it does not provide a user-facing interface (though a command-line based demo would be trivially easy at this point). 8 | 9 | The build runs on my computer (running OS X 10.9.5); that is all I can promise. 10 | 11 | ## Setup 12 | 13 | 1. Install [Emscripten](http://emscripten.org/) -- either the official Emscripten SDK, or from Homebrew 14 | 2. Clone *the `emscripten` branch* of ngspice from the Concord Consortium version on Github: https://github.com/concord-consortium/ngspice: 15 | 16 | git clone --branch emscripten https://github.com/concord-consortium/ngspice.git 17 | 18 | 3. Clone *this* repository into `/release`: 19 | 20 | cd ngspice 21 | git clone https://github.com/concord-consortium/build-ngspice-js.git release 22 | 23 | 4. `cd release` and run `./build.sh`: 24 | 25 | cd release 26 | # this must be run from the `/release`: 27 | ./build.sh 28 | 29 | This should build `ngspice.html`, `ngspice.js`, and `ngspice.html.mem`. You can open `ngspice.html`. The page will appear to be blank, but you can open it in your browser's developer console to see the output from SPICE. 30 | 31 | ## Rebuilding: 32 | 33 | * Initially, and after making structural changes to the C source code that require makefile/autoconf changes: `./build.sh` (always rebuilds the entire project) 34 | * After modifying C source: `./make.sh` (reruns Make, recompiling only changed sources, then runs linker & JS generation) 35 | * After modifying Javascript sources: `./gen.sh` (skips the C compilation, and just runs the linker and JS generation) 36 | 37 | ## Key learnings: 38 | 39 | * The autoconf-generated `configure` must be (and is) patched by `build.sh` to fail the sigsetjmp test: see https://groups.google.com/d/msg/emscripten-discuss/nvq18u4lj6E/sCBVl1uAZvkJ 40 | * Emscripten doesn't compile sources with debug information if you run the `configure` step with `--enable-debug`. To override this, we need to ensure `emcc`'s `-g` flag is set during the compilation steps; however, `EMMAKEN_CFLAGS` and `CFLAGS` env vars are overwritten somehow during the make process and are ignored. Setting `EMCC_CFLAGS` seems to work, however. 41 | * Sourcemaps aren't currently working (Sending the `-g4` flag to `emcc` in `gen.sh` doesn't work; output is similar to that described in https://github.com/kripken/emscripten/issues/2970) 42 | * We can execute ngspice commands from Javascript by providing a JS-defined stdin and stdout to ngspice. In order to do this we call `FS.init` with an appropriate callbacks (see `pre.js`) and switch `ngspice` to "pipe mode" (see `Module.arguments` in `pre.js`). 43 | * However, ngspice wants to use an infinite loop and blocking I/O to read from stdin, which would hang the browser until ngspice exits, preventing us from supplying asynchronous input to ngspice from Javascript. 44 | * Emscripten doesn't support nonblocking streams (if the `input` function we supply to `FS.init` returns `null` or `undefined`, emscripten marks the stream as having reached EOF or having errored, and refuses to read further from it). 45 | * Therefore, we modify ngspice's `main` to call [emscripten_set_main_loop] (http://kripken.github.io/emscripten-site/docs/api_reference/emscripten.h.html#c.emscripten_set_main_loop) so that `app_rl_readlines` is called from a `setInterval` loop. This almost fixes the problem, but as noted above emscripten requires that our JS stdin always return a value if it is to be readable at a later time. Therefore we also had to modify ngspice to return from the main loop whenever it receives a single newline (without preceding input). This allows our `input` function to return `\n` (and terminate this interval's main loop call) if there is no data, without affecting ngspice's behavior. 46 | 47 | ## TODO: 48 | 49 | * Instead of using `emscripten_set_main_loop` in `main.c` (therefore running the ngspice parser every 1/60s), modify `main.c` so that it publishes an endpoint callable from Javascript. (See http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code) That way ngspice can be quiescent until we have input for it, at which point we populate the `stdin` buffer, add or modify netlists and other files in the virtual `MEMFS` filesystem seen by ngspice, and call `app_rl_readlines` from Javascript. 50 | * Make a simple demo page that (asynchronously) accepts user input to spice 51 | * Maintain the build product at a canonical URL: the current HTML/JS build products are available at https://github.com/concord-consortium/build-ngspice-js/tree/gh-pages 52 | * Setup a Travis build 53 | * Adapt the supported subset of tests from the ngspice repository. 54 | 55 | --------------------------------------------------------------------------------