Change npm examples to scmos examples

This commit is contained in:
Enno Schnackenberg 2026-05-06 19:16:55 +02:00
parent ba6289478e
commit 17b30b517a
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();
// 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);
// Run Magic commands
runCommand('tech load minimum');
// Run Magic commands — scmos is built into the WASM binary, no tech file needed
runCommand('tech load scmos');
runCommand('load /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 WASM binary and available out of the box. Custom tech files can be written
into the VFS at `/magic/sys/current/<name>.tech`.
the WASM binary and available out of the box — those names work without
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
@ -57,13 +59,34 @@ The returned `MagicInstance` exposes:
| Method | Description |
|---------------------------|-------------|
| `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. |
| `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). |
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
The package ships runnable examples for the most common workflows. After
@ -80,13 +103,17 @@ Or, when developing inside this repo:
```bash
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:drc # DRC check only
npm run test:cif # CIF write only
```
Each example is self-contained and reads `examples/siliwiz.{mag,tech}` by
default. See [`examples/`](examples/) for the source.
Each example loads the bundled [`min.mag`](examples/min.mag) (a small NPN
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
@ -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.
### Bundled test technology
### Bundled test layout
The example layout (`examples/siliwiz.mag`) and technology file
(`examples/siliwiz.tech`) are derived from the
[SiliWiz](https://github.com/wokwi/siliwiz) educational silicon design
tool. They are bundled here only as a runnable smoke test for the WASM
build. The technology file is © R. Timothy Edwards, Open Circuit Design,
2023, marked by the author as containing no proprietary information.
The example layout [`examples/min.mag`](examples/min.mag) is taken from
Magic's own scmos test suite ([`scmos/examples/bipolar/min.mag`](../scmos/examples/bipolar/min.mag))
and is included here as a runnable smoke test for the WASM build. The `scmos`
technology it targets is compiled into the WASM binary, so no external tech
file is shipped.

View File

@ -1,18 +1,18 @@
// 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,
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js';
import { writeFileSync, mkdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
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 { 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 });
const data = vfsRead(FS, `/work/${cell}.cif`);

View File

@ -1,20 +1,20 @@
// 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,
DEFAULT_TECH, DEFAULT_MAG } from './helpers.js';
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 { magic } = await createMagic({
onPrint: msg => { output.push(msg); console.log('[magic]', msg); },
onPrintErr: msg => { output.push(msg); console.error('[magic]', msg); },
});
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.
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).
//
// Usage: node examples/extract.js [magFile [techFile [outputDir]]]
// Usage: node examples/extract.js [magFile [tech [outputDir]]]
import { createMagic, vfsWrite, vfsRead, loadCell, loadScript,
DEFAULT_TECH, DEFAULT_MAG, DEFAULT_OUT } from './helpers.js';
import { writeFileSync, mkdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
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 { 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 });

View File

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