Change npm examples to scmos examples

This commit is contained in:
Enno Schnackenberg 2026-05-06 19:16:55 +02:00 committed by R. Timothy Edwards
parent 354416f717
commit c8b50a702d
9 changed files with 98 additions and 1542 deletions

View File

@ -22,11 +22,12 @@ import createMagic from 'magic-vlsi-wasm';
const { runCommand, FS } = await createMagic(); const { runCommand, FS } = await createMagic();
// Write a layout into Magic's virtual filesystem // Drop a layout into Magic's virtual filesystem
FS.mkdirTree('/work');
FS.writeFile('/work/inv.mag', layoutBytes); FS.writeFile('/work/inv.mag', layoutBytes);
// Run Magic commands // Run Magic commands — scmos is built into the WASM binary, no tech file needed
runCommand('tech load minimum'); runCommand('tech load scmos');
runCommand('load /work/inv'); runCommand('load /work/inv');
runCommand('gds write /work/inv'); runCommand('gds write /work/inv');
@ -35,8 +36,9 @@ const gdsBytes = FS.readFile('/work/inv.gds');
``` ```
The `scmos` technology family (`scmos`, `minimum`, `nmos`, ...) is embedded in The `scmos` technology family (`scmos`, `minimum`, `nmos`, ...) is embedded in
the WASM binary and available out of the box. Custom tech files can be written the WASM binary and available out of the box — those names work without
into the VFS at `/magic/sys/current/<name>.tech`. writing any tech file. To use a custom technology, write its `.tech` file into
the VFS at `/magic/sys/current/<name>.tech` before calling `tech load <name>`.
## API ## API
@ -57,13 +59,34 @@ The returned `MagicInstance` exposes:
| Method | Description | | Method | Description |
|---------------------------|-------------| |---------------------------|-------------|
| `runCommand(cmd: string)` | Dispatch a single Magic command. Returns 0 on success. | | `runCommand(cmd: string)` | Dispatch a single Magic command. Returns 0 on success. |
| `sourceFile(path: string)` | Execute a script from the virtual filesystem. | | `sourceFile(path: string)` | Execute a script from the virtual filesystem. Returns 0 on success, -1 if the file could not be opened. |
| `init()` | Force initialization. Idempotent — `runCommand` and `sourceFile` call it for you. | | `init()` | Force initialization. Idempotent — `runCommand` and `sourceFile` call it for you. |
| `update()` | Drive a display-update cycle. No-op in this headless build. | | `update()` | Drive a display-update cycle. No-op in this headless build. |
| `FS` | Emscripten virtual filesystem. See the [Emscripten docs](https://emscripten.org/docs/api_reference/Filesystem-API.html). | | `FS` | Emscripten virtual filesystem. See the [Emscripten docs](https://emscripten.org/docs/api_reference/Filesystem-API.html). |
Full TypeScript types ship in [`index.d.ts`](index.d.ts). Full TypeScript types ship in [`index.d.ts`](index.d.ts).
### Low-level access
`createMagic()` is a thin convenience wrapper over the underlying Emscripten
module. If you need direct access — for example to call `cwrap` yourself or
to drive `magic_wasm_init` manually — import the module factory directly:
```js
import createMagicModule from 'magic-vlsi-wasm/magic.js';
const module = await createMagicModule({ wasmBinary, print, printErr });
module._magic_wasm_init();
const run = module.cwrap('magic_wasm_run_command', 'number', ['string']);
run('tech load scmos');
```
The bundled examples use this lower-level path together with a small helper
class ([`examples/helpers.js`](examples/helpers.js)) that adds a
`runScript(text)` convenience method — it splits a multi-line Tcl block,
strips comments, and dispatches each line via `runCommand`. Useful when you
have a script as a string rather than as a file in the VFS.
## Examples ## Examples
The package ships runnable examples for the most common workflows. After The package ships runnable examples for the most common workflows. After
@ -80,13 +103,17 @@ Or, when developing inside this repo:
```bash ```bash
npm test # full suite (extract, gds, drc, cif) npm test # full suite (extract, gds, drc, cif)
npm run example # extract.js — RC extraction + SPICE netlist
npm run test:gds # GDS write only npm run test:gds # GDS write only
npm run test:drc # DRC check only npm run test:drc # DRC check only
npm run test:cif # CIF write only npm run test:cif # CIF write only
``` ```
Each example is self-contained and reads `examples/siliwiz.{mag,tech}` by Each example loads the bundled [`min.mag`](examples/min.mag) (a small NPN
default. See [`examples/`](examples/) for the source. transistor cell from Magic's own scmos test suite) under the built-in `scmos`
technology — no external tech file required. See [`examples/`](examples/) for
the source; [`example.js`](examples/example.js) is the simplest entry point
(GDS → CIF conversion in ~40 lines).
## Build from source ## Build from source
@ -117,11 +144,10 @@ checkout (Magic pins emsdk `3.1.56` — see the comment in `npm/build.sh`).
[HPND](LICENSE) — Copyright (C) 1985, 1990 Regents of the University of California. [HPND](LICENSE) — Copyright (C) 1985, 1990 Regents of the University of California.
### Bundled test technology ### Bundled test layout
The example layout (`examples/siliwiz.mag`) and technology file The example layout [`examples/min.mag`](examples/min.mag) is taken from
(`examples/siliwiz.tech`) are derived from the Magic's own scmos test suite ([`scmos/examples/bipolar/min.mag`](../scmos/examples/bipolar/min.mag))
[SiliWiz](https://github.com/wokwi/siliwiz) educational silicon design and is included here as a runnable smoke test for the WASM build. The `scmos`
tool. They are bundled here only as a runnable smoke test for the WASM technology it targets is compiled into the WASM binary, so no external tech
build. The technology file is © R. Timothy Edwards, Open Circuit Design, file is shipped.
2023, marked by the author as containing no proprietary information.

View File

@ -1,18 +1,18 @@
// cif.js — Export layout to CIF (Caltech Intermediate Form). // cif.js — Export layout to CIF (Caltech Intermediate Form).
// //
// Usage: node examples/cif.js [magFile [techFile [outputDir]]] // Usage: node examples/cif.js [magFile [tech [outputDir]]]
import { createMagic, vfsRead, loadCell, loadScript, import { createMagic, vfsRead, loadCell, loadScript,
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js'; DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js';
import { writeFileSync, mkdirSync } from 'node:fs'; import { writeFileSync, mkdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { resolve } from 'node:path'; import { resolve } from 'node:path';
export async function run({ magFile = DEFAULT_MAG, techFile = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {}) { export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {}) {
const { magic } = await createMagic(); const { magic } = await createMagic();
const { FS } = magic; const { FS } = magic;
const { tech, cell } = loadCell(FS, techFile, magFile); const { tech: techName, cell } = loadCell(FS, tech, magFile);
magic.runScript(loadScript('cif.tcl', tech, cell)); magic.runScript(loadScript('cif.tcl', techName, cell));
mkdirSync(outputDir, { recursive: true }); mkdirSync(outputDir, { recursive: true });
const data = vfsRead(FS, `/work/${cell}.cif`); const data = vfsRead(FS, `/work/${cell}.cif`);

View File

@ -1,20 +1,20 @@
// drc.js — Run design rule checking and report violations. // drc.js — Run design rule checking and report violations.
// //
// Usage: node examples/drc.js [magFile [techFile]] // Usage: node examples/drc.js [magFile [tech]]
import { createMagic, loadCell, loadScript, import { createMagic, loadCell, loadScript,
DEFAULT_TECH, DEFAULT_MAG } from './helpers.js'; DEFAULT_TECH, DEFAULT_MAG } from './helpers.js';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
export async function run({ magFile = DEFAULT_MAG, techFile = DEFAULT_TECH } = {}) { export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH } = {}) {
const output = []; const output = [];
const { magic } = await createMagic({ const { magic } = await createMagic({
onPrint: msg => { output.push(msg); console.log('[magic]', msg); }, onPrint: msg => { output.push(msg); console.log('[magic]', msg); },
onPrintErr: msg => { output.push(msg); console.error('[magic]', msg); }, onPrintErr: msg => { output.push(msg); console.error('[magic]', msg); },
}); });
const { FS } = magic; const { FS } = magic;
const { tech, cell } = loadCell(FS, techFile, magFile); const { tech: techName, cell } = loadCell(FS, tech, magFile);
magic.runScript(loadScript('drc.tcl', tech, cell)); magic.runScript(loadScript('drc.tcl', techName, cell));
// Magic prints "Total DRC errors found: N" at the end of drc listall. // Magic prints "Total DRC errors found: N" at the end of drc listall.
const summary = output.find(l => /Total DRC errors/i.test(l)); const summary = output.find(l => /Total DRC errors/i.test(l));

View File

@ -1,18 +1,18 @@
// extract.js — RC extraction example (extract → extresist → ext2spice). // extract.js — RC extraction example (extract → extresist → ext2spice).
// //
// Usage: node examples/extract.js [magFile [techFile [outputDir]]] // Usage: node examples/extract.js [magFile [tech [outputDir]]]
import { createMagic, vfsWrite, vfsRead, loadCell, loadScript, import { createMagic, vfsWrite, vfsRead, loadCell, loadScript,
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js'; DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js';
import { writeFileSync, mkdirSync } from 'node:fs'; import { writeFileSync, mkdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { resolve } from 'node:path'; import { resolve } from 'node:path';
export async function run({ magFile = DEFAULT_MAG, techFile = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {}) { export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {}) {
const { magic } = await createMagic(); const { magic } = await createMagic();
const { FS } = magic; const { FS } = magic;
const { tech, cell } = loadCell(FS, techFile, magFile); const { tech: techName, cell } = loadCell(FS, tech, magFile);
magic.runScript(loadScript('extract.tcl', tech, cell)); magic.runScript(loadScript('extract.tcl', techName, cell));
mkdirSync(outputDir, { recursive: true }); mkdirSync(outputDir, { recursive: true });

View File

@ -1,18 +1,18 @@
// gds.js — Export layout to GDS II stream format. // gds.js — Export layout to GDS II stream format.
// //
// Usage: node examples/gds.js [magFile [techFile [outputDir]]] // Usage: node examples/gds.js [magFile [tech [outputDir]]]
import { createMagic, vfsRead, loadCell, loadScript, import { createMagic, vfsRead, loadCell, loadScript,
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js'; DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js';
import { writeFileSync, mkdirSync } from 'node:fs'; import { writeFileSync, mkdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { resolve } from 'node:path'; import { resolve } from 'node:path';
export async function run({ magFile = DEFAULT_MAG, techFile = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {}) { export async function run({ magFile = DEFAULT_MAG, tech = DEFAULT_TECH, outputDir = DEFAULT_OUT } = {}) {
const { magic } = await createMagic(); const { magic } = await createMagic();
const { FS } = magic; const { FS } = magic;
const { tech, cell } = loadCell(FS, techFile, magFile); const { tech: techName, cell } = loadCell(FS, tech, magFile);
magic.runScript(loadScript('gds.tcl', tech, cell)); magic.runScript(loadScript('gds.tcl', techName, cell));
mkdirSync(outputDir, { recursive: true }); mkdirSync(outputDir, { recursive: true });
const data = vfsRead(FS, `/work/${cell}.gds`); const data = vfsRead(FS, `/work/${cell}.gds`);

View File

@ -6,8 +6,8 @@ import { dirname, resolve, basename } from 'node:path';
export const EXAMPLES_DIR = dirname(fileURLToPath(import.meta.url)); export const EXAMPLES_DIR = dirname(fileURLToPath(import.meta.url));
export const wasmBinary = readFileSync(resolve(EXAMPLES_DIR, '../magic.wasm')); export const wasmBinary = readFileSync(resolve(EXAMPLES_DIR, '../magic.wasm'));
export const DEFAULT_TECH = resolve(EXAMPLES_DIR, 'siliwiz.tech'); export const DEFAULT_TECH = 'scmos';
export const DEFAULT_MAG = resolve(EXAMPLES_DIR, 'siliwiz.mag'); export const DEFAULT_MAG = resolve(EXAMPLES_DIR, 'min.mag');
export const DEFAULT_OUT = resolve(EXAMPLES_DIR, 'output'); export const DEFAULT_OUT = resolve(EXAMPLES_DIR, 'output');
export { basename }; export { basename };
@ -55,12 +55,12 @@ export async function createMagic({ onPrint, onPrintErr } = {}) {
return { magic, lines }; return { magic, lines };
} }
// Loads tech + mag into VFS using standard paths. // Loads a layout into the VFS. `tech` is the name of a technology that is
export function loadCell(FS, techFile, magFile) { // either built into the WASM binary (e.g. 'scmos', 'minimum', 'nmos') or has
const tech = basename(techFile, '.tech'); // already been written to /magic/sys/current/<tech>.tech by the caller.
const cell = basename(magFile, '.mag'); export function loadCell(FS, tech, magFile) {
vfsWrite(FS, `/magic/sys/current/${tech}.tech`, techFile); const cell = basename(magFile, '.mag');
vfsWrite(FS, `/work/${cell}.mag`, magFile); vfsWrite(FS, `/work/${cell}.mag`, magFile);
return { tech, cell }; return { tech, cell };
} }

33
npm/examples/min.mag Normal file
View File

@ -0,0 +1,33 @@
magic
tech scmos
timestamp 783737348
<< nwell >>
rect 5 3 46 27
<< metal1 >>
rect 9 0 13 13
rect 22 0 26 13
rect 33 0 37 13
<< collector >>
rect 8 17 14 18
rect 8 13 9 17
rect 13 13 14 17
rect 8 12 14 13
<< pbase >>
rect 18 17 40 21
rect 18 13 22 17
rect 26 13 33 17
rect 37 13 40 17
rect 18 9 40 13
<< collectorcontact >>
rect 9 13 13 17
<< emittercontact >>
rect 22 13 26 17
<< pbasecontact >>
rect 33 13 37 17
<< labels >>
rlabel space 0 17 0 17 3 without
rlabel space 0 13 0 13 3 guardring
rlabel metal1 35 1 35 1 5 base
rlabel metal1 24 1 24 1 5 emitter
rlabel metal1 11 1 11 1 5 collector
<< end >>

View File

@ -1,51 +0,0 @@
magic
tech siliwiz
magscale 1 1
timestamp 1776371010
<< nwell >>
rect 95 221 349 377
<< ndiffusion >>
rect 132 85 270 131
<< pdiffusion >>
rect 132 254 270 316
<< psubstratepdiff >>
rect 319 0 382 58
<< nsubstratendiff >>
rect 291 309 338 361
<< polysilicon >>
rect 178 51 217 327
rect 148 179 201 220
<< pdcontact >>
rect 233 264 266 307
rect 132 259 169 312
<< ndcontact >>
rect 236 89 268 127
rect 132 88 166 128
<< polycontact >>
rect 147 185 172 214
<< nsubstratencontact >>
rect 295 330 335 364
<< psubstratepcontact >>
rect 333 8 377 49
<< metal1 >>
rect 39 179 127 218
rect 270 180 366 218
rect 110 179 173 216
rect 229 89 270 307
rect 244 180 284 214
rect 128 264 164 366
rect 132 13 166 130
rect 40 9 374 49
rect 42 334 358 373
rect 29 328 80 377
rect 30 6 81 56
<< labels >>
flabel metal1 s 39 179 127 218 0 FreeSans 240 90 0 0 in
port 2 nsew signal output
flabel metal1 s 270 180 366 218 0 FreeSans 240 90 0 0 out
port 3 nsew signal output
flabel metal1 s 29 328 80 377 0 FreeSans 240 90 0 0 vdd
port 4 nsew signal output
flabel metal1 s 30 6 81 56 0 FreeSans 240 90 0 0 vss
port 5 nsew signal output
<< end >>

File diff suppressed because it is too large Load Diff