From 85600275699db6a6eb233a4ca5dc755d338600ff Mon Sep 17 00:00:00 2001 From: Intubun <41478036+Intubun@users.noreply.github.com> Date: Mon, 4 May 2026 13:32:51 +0200 Subject: [PATCH] Add npm package and CI workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The user-facing layer of the WASM port: a publishable npm package plus the GitHub Actions that build and ship it. * npm/package.json — publishes as `magic-vlsi-wasm`, ESM-only, HPND licensed, version tracks Magic's own VERSION file (8.3.637). Whitelists the published files and exposes index.js + index.d.ts. * npm/index.js, npm/index.d.ts — thin JS/TS wrapper around the four WASM exports. createMagic(opts) returns { init, runCommand, sourceFile, update, FS } so consumers can write into the Emscripten virtual filesystem and dispatch Magic commands from Node.js, browsers or Web Workers. * npm/build.sh — end-to-end build: locates emsdk (via PATH or EMSDK_DIR), runs distclean+configure+make in the right order (techs before mains so embed-files are present), copies magic.js / magic.wasm into npm/. Optional --release, --test, --pack flags. Preserves configure's exec bits across invocations. * npm/pack.sh — produces a reproducible npm tarball by touching every file to the build time and exporting SOURCE_DATE_EPOCH so pacote does not rewrite mtimes to its 1985 fallback. * npm/examples/ — runnable smoke tests for the four common workflows (extract, gds, drc, cif), driven by examples/all.js. Each example is self-contained and uses the bundled siliwiz technology. helpers.js encapsulates the boilerplate. * npm/LICENSE, npm/README.md — license text and consumer-facing docs (install, quick-start, API, examples, build-from-source, license, third-party content notice). * .github/workflows/main.yml — adds a `simple_build_wasm` job that installs a pinned emsdk (3.1.56), builds the WASM module, runs the example test suite and uploads the npm tarball as an artifact. Pinned for reproducibility against the post-build.sh patches; switchable to "latest" by commenting two lines. * .github/workflows/main-aarch64.yml — drops the now-redundant WASM ARM job. WASM is architecture-independent. * .github/workflows/npm-publish.yml — new workflow. Publishes to npm on `v*` tag pushes (manual `workflow_dispatch` supported as a dry-run). Uses the same pinned emsdk and pack.sh. Also sets FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 in both workflows to silence the Node.js 20 deprecation warnings until actions/upload-artifact@v6 ships a Node-24 release. --- .github/workflows/main-aarch64.yml | 43 +- .github/workflows/main.yml | 65 +- .github/workflows/npm-publish.yml | 95 ++ npm/.gitignore | 6 + npm/LICENSE | 10 + npm/README.md | 127 +++ npm/build.sh | 132 +++ npm/examples/all.js | 50 + npm/examples/cif.js | 30 + npm/examples/cif.tcl | 4 + npm/examples/drc.js | 31 + npm/examples/drc.tcl | 5 + npm/examples/example.js | 40 + npm/examples/extract.js | 41 + npm/examples/extract.tcl | 27 + npm/examples/gds.js | 30 + npm/examples/gds.tcl | 4 + npm/examples/helpers.js | 72 ++ npm/examples/siliwiz.mag | 51 + npm/examples/siliwiz.tech | 1452 ++++++++++++++++++++++++++++ npm/index.d.ts | 57 ++ npm/index.js | 15 + npm/pack.sh | 24 + npm/package.json | 36 + 24 files changed, 2392 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/npm-publish.yml create mode 100644 npm/.gitignore create mode 100644 npm/LICENSE create mode 100644 npm/README.md create mode 100755 npm/build.sh create mode 100644 npm/examples/all.js create mode 100644 npm/examples/cif.js create mode 100644 npm/examples/cif.tcl create mode 100644 npm/examples/drc.js create mode 100644 npm/examples/drc.tcl create mode 100644 npm/examples/example.js create mode 100644 npm/examples/extract.js create mode 100644 npm/examples/extract.tcl create mode 100644 npm/examples/gds.js create mode 100644 npm/examples/gds.tcl create mode 100644 npm/examples/helpers.js create mode 100644 npm/examples/siliwiz.mag create mode 100644 npm/examples/siliwiz.tech create mode 100644 npm/index.d.ts create mode 100644 npm/index.js create mode 100755 npm/pack.sh create mode 100644 npm/package.json diff --git a/.github/workflows/main-aarch64.yml b/.github/workflows/main-aarch64.yml index e4e3fc9c..cbb3d749 100644 --- a/.github/workflows/main-aarch64.yml +++ b/.github/workflows/main-aarch64.yml @@ -1,14 +1,15 @@ -# This is a basic workflow to help you get started with Actions +# CI for native ARM64 Linux build. +# WASM is not built here — WebAssembly bytecode is architecture-independent, +# so the single x86-64 WASM artifact produced by the CI workflow is identical +# to what an ARM build would produce. See .github/workflows/main.yml. name: CI-aarch64 -# Controls when the workflow will run on: push: pull_request: workflow_dispatch: -# A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: simple_build_linux_arm: runs-on: ubuntu-24.04-arm @@ -23,39 +24,3 @@ jobs: ./configure make database/database.h make -j$(nproc) - simple_build_wasm_arm: - runs-on: ubuntu-24.04-arm - steps: - - uses: actions/checkout@v4 - - name: Get Dependencies - run: | - git clone https://github.com/emscripten-core/emsdk.git - cd emsdk - ./emsdk install latest - ./emsdk activate latest - - name: Emscripten Diagnostic - run: | - source ./emsdk/emsdk_env.sh - echo "===== gcc -dM -E - =====" - echo | gcc -dM -E - | sort - echo "===== g++ -dM -E - =====" - echo | g++ -dM -E - | sort - echo "===== emcc -dM -E - =====" - echo | emcc -dM -E - | sort - echo "===== em++ -dM -E - =====" - echo | em++ -dM -E - | sort - - name: Build - run: | - source ./emsdk/emsdk_env.sh - # The --without and --disable in these build options is due to no WASM library being available for that feature - CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1 -g" emconfigure ./configure --without-cairo --without-opengl --without-x --without-tk --without-tcl --disable-readline --disable-compression --target=asmjs-unknown-emscripten - echo "===== defs.mak =====" - cat defs.mak - echo "===== defs.mak =====" - emmake make - - name: archive wasm bundle - uses: actions/upload-artifact@v4 - with: - name: magic-wasm-bundle-arm - path: | - ${{ github.workspace }}/magic/magic.wasm diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8a64284a..3c271224 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,19 +1,21 @@ -# This is a basic workflow to help you get started with Actions - name: CI -# Controls when the workflow will run on: push: pull_request: workflow_dispatch: -# A workflow run is made up of one or more jobs that can run sequentially or in parallel +# actions/upload-artifact@v5 still runs on Node.js 20. Force Node 24 to +# silence the deprecation warning until upload-artifact ships a Node-24 +# release. Drop this once upgraded. +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: simple_build_linux: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Get Dependencies run: | sudo apt-get install -y tcl-dev tk-dev libcairo-dev @@ -24,14 +26,24 @@ jobs: make -j$(nproc) simple_build_wasm: runs-on: ubuntu-22.04 + env: + # --- emsdk version switch --------------------------------------------- + # Pinned: post-build.sh patches depend on the exact text output of this + # Emscripten version. Bump intentionally and re-verify the patches. + # To test against the latest emsdk, comment the pinned line and + # uncomment the "latest" line below. + EMSDK_VERSION: '3.1.56' + # EMSDK_VERSION: 'latest' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Get Dependencies run: | git clone https://github.com/emscripten-core/emsdk.git cd emsdk - ./emsdk install latest - ./emsdk activate latest + ./emsdk install "$EMSDK_VERSION" + ./emsdk activate "$EMSDK_VERSION" + # Dump native + emscripten preprocessor defines. Useful for diagnosing + # WASM-build differences after an emsdk bump. - name: Emscripten Diagnostic run: | source ./emsdk/emsdk_env.sh @@ -46,15 +58,36 @@ jobs: - name: Build run: | source ./emsdk/emsdk_env.sh - # The --without and --disable in these build options is due to no WASM library being available for that feature - CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1 -g" emconfigure ./configure --without-cairo --without-opengl --without-x --without-tk --without-tcl --disable-readline --disable-compression --target=asmjs-unknown-emscripten + # --without/--disable flags: no WASM library available for these features + CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1 -g" emconfigure ./configure \ + --without-cairo --without-opengl --without-x --without-tk --without-tcl \ + --disable-readline --disable-compression \ + --host=asmjs-unknown-emscripten \ + --target=asmjs-unknown-emscripten + # Append WASM linker flags and activate the WASM link target + cat toolchains/emscripten/defs.mak >> defs.mak + # Echo the merged defs.mak so CI logs show the exact build config echo "===== defs.mak =====" cat defs.mak echo "===== defs.mak =====" - emmake make - - name: archive wasm bundle - uses: actions/upload-artifact@v4 + # Build in order: techs must exist before mains (--embed-file embeds them) + emmake make depend + emmake make -j$(nproc) modules libs + emmake make techs + emmake make mains + - name: Set up Node.js + uses: actions/setup-node@v5 with: - name: magic-wasm-bundle - path: | - ${{ github.workspace }}/magic/magic.wasm + node-version: '22' + - name: Run example tests + run: | + cp magic/magic.js npm/ + cp magic/magic.wasm npm/ + cd npm && npm run test + - name: Pack npm package + run: ./npm/pack.sh + - name: Upload npm package + uses: actions/upload-artifact@v5 + with: + name: magic-vlsi-wasm-npm + path: ${{ github.workspace }}/npm/*.tgz diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 00000000..3f15657e --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,95 @@ +name: Publish npm package + +# Publishes magic-vlsi-wasm to npm. +# Triggered automatically on version tags (v*); can also be run manually. +# Requires an NPM_TOKEN repository secret for publishing. +# +# WASM is architecture-independent — built once on x86-64, usable everywhere. + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run: pack only, do not publish' + type: boolean + default: true + +# actions/upload-artifact@v5 still runs on Node.js 20. Force Node 24 to +# silence the deprecation warning until upload-artifact ships a Node-24 +# release. Drop this once upgraded. +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + build-and-publish: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v5 + + - name: Set up Node.js + uses: actions/setup-node@v5 + with: + node-version: '22' + registry-url: 'https://registry.npmjs.org' + + - name: Install emsdk + env: + # --- emsdk version switch ----------------------------------------- + # Pinned: must match .github/workflows/main.yml. See post-build.sh. + # To test against the latest emsdk, comment the pinned line and + # uncomment the "latest" line below. + EMSDK_VERSION: '3.1.56' + # EMSDK_VERSION: 'latest' + run: | + git clone https://github.com/emscripten-core/emsdk.git + cd emsdk + ./emsdk install "$EMSDK_VERSION" + ./emsdk activate "$EMSDK_VERSION" + + - name: Build WASM + run: | + source ./emsdk/emsdk_env.sh + # --without/--disable flags: no WASM library available for these features + CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1" emconfigure ./configure \ + --without-cairo --without-opengl --without-x --without-tk --without-tcl \ + --disable-readline --disable-compression \ + --host=asmjs-unknown-emscripten \ + --target=asmjs-unknown-emscripten + # Append WASM linker flags and activate the WASM link target + cat toolchains/emscripten/defs.mak >> defs.mak + # Build in order: techs must exist before mains (--embed-file embeds them) + emmake make depend + emmake make -j$(nproc) modules libs + emmake make techs + emmake make mains + + - name: Copy WASM artifacts into npm/ + run: | + cp magic/magic.js npm/ + cp magic/magic.wasm npm/ + + - name: Set package version from tag + if: startsWith(github.ref, 'refs/tags/v') + run: | + VERSION="${GITHUB_REF#refs/tags/v}" + cd npm + npm version "$VERSION" --no-git-tag-version + + - name: Pack + run: ./npm/pack.sh + + - name: Upload tarball as artifact + uses: actions/upload-artifact@v5 + with: + name: magic-wasm-npm-package + path: npm/*.tgz + + - name: Publish to npm + if: startsWith(github.ref, 'refs/tags/v') && github.event.inputs.dry_run != 'true' + run: cd npm && npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/npm/.gitignore b/npm/.gitignore new file mode 100644 index 00000000..228c6d22 --- /dev/null +++ b/npm/.gitignore @@ -0,0 +1,6 @@ +magic.js +magic.wasm +*.tgz +node_modules/ +package-lock.json +examples/output/ diff --git a/npm/LICENSE b/npm/LICENSE new file mode 100644 index 00000000..015556c5 --- /dev/null +++ b/npm/LICENSE @@ -0,0 +1,10 @@ +Copyright (C) 1985, 1990 Regents of the University of California. + +Permission to use, copy, modify, and distribute this +software and its documentation for any purpose and without +fee is hereby granted, provided that the above copyright +notice appear in all copies. The University of California +makes no representations about the suitability of this +software for any purpose. It is provided "as is" without +express or implied warranty. Export of this software outside +of the United States of America may require an export license. diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 00000000..2ae19049 --- /dev/null +++ b/npm/README.md @@ -0,0 +1,127 @@ +# magic-vlsi-wasm + +[Magic VLSI](http://opencircuitdesign.com/magic/) layout tool, compiled to +WebAssembly as a headless library. Runs in Node.js, browsers, and Web Workers +— no X11, no Tk, no native dependencies. + +Use it to programmatically read and write `.mag`, `.gds`, `.cif`, `.ext`, and +SPICE netlists; run DRC; extract parasitics — anywhere JavaScript runs. + +## Install + +```bash +npm install magic-vlsi-wasm +``` + +Requires Node.js 18 or newer. + +## Quick start + +```js +import createMagic from 'magic-vlsi-wasm'; + +const { runCommand, FS } = await createMagic(); + +// Write a layout into Magic's virtual filesystem +FS.writeFile('/work/inv.mag', layoutBytes); + +// Run Magic commands +runCommand('tech load minimum'); +runCommand('load /work/inv'); +runCommand('gds write /work/inv'); + +// Read the result back out +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/.tech`. + +## API + +```ts +createMagic(options?): Promise +``` + +`options` is forwarded to the underlying Emscripten module. Useful keys: + +| Key | Default | Purpose | +|--------------|------------------|---------| +| `wasmBinary` | fetched lazily | Pre-fetched ArrayBuffer of `magic.wasm` (skips a network round-trip in browsers) | +| `print` | `console.log` | Callback for each stdout line | +| `printErr` | `console.error` | Callback for each stderr line | + +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. | +| `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). + +## Examples + +The package ships runnable examples for the most common workflows. After +installing, run one directly: + +```bash +node node_modules/magic-vlsi-wasm/examples/extract.js +node node_modules/magic-vlsi-wasm/examples/gds.js +node node_modules/magic-vlsi-wasm/examples/drc.js +node node_modules/magic-vlsi-wasm/examples/cif.js +``` + +Or, when developing inside this repo: + +```bash +npm test # full suite (extract, gds, drc, cif) +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. + +## Build from source + +If you want to rebuild the WASM module yourself, see +[`toolchains/emscripten/README.md`](../toolchains/emscripten/README.md). +The short version: + +```bash +bash npm/build.sh # debug build, copies magic.js + magic.wasm into npm/ +bash npm/build.sh --release # optimized +bash npm/build.sh --test # build + run tests +bash npm/build.sh --pack # build + produce magic-vlsi-wasm-.tgz +``` + +You will need an activated [emsdk](https://emscripten.org/docs/getting_started/downloads.html) +checkout (Magic pins emsdk `3.1.56` — see the comment in `npm/build.sh`). + +## Limitations + +- Headless only. There is no display driver, so commands that draw to a + window (`view`, `findbox`, interactive macros) are no-ops. +- WASM memory starts at 32 MB and grows as needed. Very large GDS imports + may need `INITIAL_MEMORY` bumped (rebuild required). +- Single-threaded. WASM modules are not thread-safe — create one instance + per worker. + +## License + +[HPND](LICENSE) — Copyright (C) 1985, 1990 Regents of the University of California. + +### Bundled test technology + +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. diff --git a/npm/build.sh b/npm/build.sh new file mode 100755 index 00000000..4c4928c9 --- /dev/null +++ b/npm/build.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +# Build Magic WASM and copy artifacts into this npm/ directory. +# +# Usage: +# npm/build.sh [--release] [--test] [--pack] +# +# --release Omit debug symbols (-g). +# --test Run `npm run test` after copying artifacts. +# --pack Run `npm pack` after copying artifacts (and tests, if given). +# +# Requirements (must be on PATH or set via env vars before running): +# emcc / emmake / emconfigure — Emscripten compiler tools +# make, gcc — standard build tools +# node, npm — only required for --test / --pack +# +# Environment: +# EMSDK_DIR Path to an activated emsdk checkout. +# If set, emsdk_env.sh is sourced from there. +# If unset, emcc must already be on PATH (e.g. sourced externally). + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" + +OPT_RELEASE=0 +OPT_TEST=0 +OPT_PACK=0 +for arg in "$@"; do + case "$arg" in + --release) OPT_RELEASE=1 ;; + --test) OPT_TEST=1 ;; + --pack) OPT_PACK=1 ;; + *) echo "Unknown option: $arg" >&2; exit 1 ;; + esac +done + +# --- locate emscripten ------------------------------------------------------- +if [ -n "${EMSDK_DIR:-}" ]; then + if [ ! -f "$EMSDK_DIR/emsdk_env.sh" ]; then + echo "Error: EMSDK_DIR is set to '$EMSDK_DIR' but emsdk_env.sh was not found there." >&2 + exit 1 + fi + # shellcheck source=/dev/null + source "$EMSDK_DIR/emsdk_env.sh" +else + if ! command -v emcc &>/dev/null; then + echo "Error: emcc not found on PATH and EMSDK_DIR is not set." >&2 + echo " Either source emsdk_env.sh before running this script," >&2 + echo " or set EMSDK_DIR to your emsdk checkout directory." >&2 + exit 1 + fi +fi + +echo "Using emcc: $(command -v emcc)" +emcc --version | head -1 + +# --- portability helpers ----------------------------------------------------- +# CPU count: Linux has nproc, macOS has sysctl, fall back to getconf. +ncpu() { + if command -v nproc &>/dev/null; then + nproc + elif [ "$(uname)" = "Darwin" ]; then + sysctl -n hw.ncpu + else + getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1 + fi +} + +# Portable in-place sed (BSD sed on macOS disagrees with GNU on -i). +# Uses redirect-back instead of mv so the file's mode bits are preserved +# (configure must stay executable across build.sh invocations). +sed_strip_cr() { + local file=$1 tmp + tmp=$(mktemp) + sed 's/\r//' "$file" > "$tmp" && cat "$tmp" > "$file" && rm "$tmp" +} + +# --- clean ------------------------------------------------------------------- +cd "$REPO_ROOT" + +# Only distclean if there's something to clean. A stale `|| true` here would +# hide real failures (e.g. broken toolchain) on a fresh checkout. +if [ -f defs.mak ]; then + emmake make distclean || true +fi +rm -f defs.mak database/database.h + +# --- configure --------------------------------------------------------------- + +# Strip Windows CRLF line endings (no-op on Linux-native files). +sed_strip_cr configure +find scripts/ -type f -print0 | while IFS= read -r -d '' f; do sed_strip_cr "$f"; done + +if [ $OPT_RELEASE -eq 1 ]; then + EXTRA_CFLAGS=" -O2" +else + EXTRA_CFLAGS=" -g" +fi + +CFLAGS="--std=c17 -D_DEFAULT_SOURCE=1 -DEMSCRIPTEN=1${EXTRA_CFLAGS}" \ + emconfigure ./configure \ + --without-cairo --without-opengl --without-x --without-tk --without-tcl \ + --disable-readline --disable-compression \ + --host=asmjs-unknown-emscripten \ + --target=asmjs-unknown-emscripten + +cat toolchains/emscripten/defs.mak >> defs.mak + +# --- build ------------------------------------------------------------------- +emmake make depend +emmake make -j"$(ncpu)" modules libs +emmake make techs +emmake make mains + +# --- copy artifacts ---------------------------------------------------------- +cp magic/magic.js "$SCRIPT_DIR/" +cp magic/magic.wasm "$SCRIPT_DIR/" +echo "Copied magic.js and magic.wasm to npm/" + +# --- optional test ----------------------------------------------------------- +# Runs the same smoke test that CI runs (see .github/workflows/main.yml). +if [ $OPT_TEST -eq 1 ]; then + cd "$SCRIPT_DIR" + npm run test +fi + +# --- optional pack ----------------------------------------------------------- +if [ $OPT_PACK -eq 1 ]; then + "$SCRIPT_DIR/pack.sh" + echo "npm package tarball created in npm/" +fi diff --git a/npm/examples/all.js b/npm/examples/all.js new file mode 100644 index 00000000..8bc4985d --- /dev/null +++ b/npm/examples/all.js @@ -0,0 +1,50 @@ +// all.js — Run all Magic WASM example tests and print a summary. +// +// Usage: node examples/all.js +import { run as runExtract } from './extract.js'; +import { run as runGds } from './gds.js'; +import { run as runDrc } from './drc.js'; +import { run as runCif } from './cif.js'; + +const PAD = 9; + +async function test(name, fn) { + process.stdout.write(` ${name.padEnd(PAD)} `); + try { + const result = await fn(); + console.log(`PASS ${formatResult(name, result)}`); + return true; + } catch (e) { + console.log(`FAIL ${e.message ?? e}`); + return false; + } +} + +function formatResult(name, r) { + if (!r) return ''; + switch (name) { + case 'extract': return [r.ext, r.spice].filter(Boolean).map(p => p.split(/[\\/]/).pop()).join(', '); + case 'gds': return `${r.outPath.split(/[\\/]/).pop()} (${r.bytes} B)`; + case 'cif': return `${r.outPath.split(/[\\/]/).pop()} (${r.bytes} B)`; + case 'drc': return r.violations != null ? `${r.violations} violation${r.violations !== 1 ? 's' : ''}` : ''; + default: return ''; + } +} + +console.log('\nMagic WASM — test suite\n'); + +const suite = [ + ['extract', runExtract], + ['gds', runGds], + ['drc', runDrc], + ['cif', runCif], +]; + +const passed = []; +for (const [name, fn] of suite) { + passed.push(await test(name, fn)); +} + +const ok = passed.filter(Boolean).length; +console.log(`\n${ok}/${suite.length} passed`); +process.exit(ok === suite.length ? 0 : 1); diff --git a/npm/examples/cif.js b/npm/examples/cif.js new file mode 100644 index 00000000..90ff21e7 --- /dev/null +++ b/npm/examples/cif.js @@ -0,0 +1,30 @@ +// cif.js — Export layout to CIF (Caltech Intermediate Form). +// +// Usage: node examples/cif.js [magFile [techFile [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 } = {}) { + const { magic } = await createMagic(); + const { FS } = magic; + const { tech, cell } = loadCell(FS, techFile, magFile); + + magic.runScript(loadScript('cif.tcl', tech, cell)); + + mkdirSync(outputDir, { recursive: true }); + const data = vfsRead(FS, `/work/${cell}.cif`); + if (!data) throw new Error(`CIF export failed: /work/${cell}.cif not created`); + + const outPath = resolve(outputDir, `${cell}.cif`); + writeFileSync(outPath, data); + return { outPath, bytes: data.length }; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const { outPath, bytes } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); }); + console.log(`\ncif: ${outPath} (${bytes} bytes)`); + console.log('Done.'); +} diff --git a/npm/examples/cif.tcl b/npm/examples/cif.tcl new file mode 100644 index 00000000..13cb32a6 --- /dev/null +++ b/npm/examples/cif.tcl @@ -0,0 +1,4 @@ +# CIF export +tech load __TECH__ +load /work/__CELL__ +cif write /work/__CELL__ diff --git a/npm/examples/drc.js b/npm/examples/drc.js new file mode 100644 index 00000000..a8209cf7 --- /dev/null +++ b/npm/examples/drc.js @@ -0,0 +1,31 @@ +// drc.js — Run design rule checking and report violations. +// +// Usage: node examples/drc.js [magFile [techFile]] +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 } = {}) { + 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); + + magic.runScript(loadScript('drc.tcl', tech, 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)); + const match = summary?.match(/(\d+)/); + const violations = match ? parseInt(match[1], 10) : null; + + return { violations, output }; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const { violations } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); }); + console.log(`\nDRC violations: ${violations ?? '(count not found in output)'}`); + console.log('Done.'); +} diff --git a/npm/examples/drc.tcl b/npm/examples/drc.tcl new file mode 100644 index 00000000..e3de9f77 --- /dev/null +++ b/npm/examples/drc.tcl @@ -0,0 +1,5 @@ +# DRC check — runs design rule checking and reports violation count. +tech load __TECH__ +load /work/__CELL__ +drc catchup +drc count total diff --git a/npm/examples/example.js b/npm/examples/example.js new file mode 100644 index 00000000..d9a179ad --- /dev/null +++ b/npm/examples/example.js @@ -0,0 +1,40 @@ +// example.js — GDS → CIF format conversion using the Magic WASM module. +// +// Usage: +// node examples/example.js [input.gds] [output.cif] +// +// Defaults: +// input: design.gds (in current working directory) +// output: design.cif (in current working directory) + +import { readFileSync, writeFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve, basename } from 'node:path'; +import createMagicModule from '../magic.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const wasmBinary = readFileSync(resolve(__dirname, '../magic.wasm')); + +const inputGds = process.argv[2] ?? 'design.gds'; +const outputCif = process.argv[3] ?? inputGds.replace(/\.gds$/i, '.cif'); +const cellName = basename(inputGds, '.gds'); + +const module = await createMagicModule({ + wasmBinary, + print: msg => console.log('[magic]', msg), + printErr: msg => console.error('[magic]', msg), +}); + +module.FS.mkdirTree('/work'); +module.FS.writeFile(`/work/${cellName}.gds`, readFileSync(inputGds)); + +module._magic_wasm_init(); +module.cwrap('magic_wasm_run_command', 'number', ['string'])(`gds read /work/${cellName}`); +module.cwrap('magic_wasm_run_command', 'number', ['string'])(`load ${cellName}`); +module.cwrap('magic_wasm_run_command', 'number', ['string'])(`cif write /work/${cellName}`); + +const cifBytes = module.FS.readFile(`/work/${cellName}.cif`); +writeFileSync(outputCif, cifBytes); + +console.log(`Converted ${inputGds} → ${outputCif}`); diff --git a/npm/examples/extract.js b/npm/examples/extract.js new file mode 100644 index 00000000..6e1ad456 --- /dev/null +++ b/npm/examples/extract.js @@ -0,0 +1,41 @@ +// extract.js — RC extraction example (extract → extresist → ext2spice). +// +// Usage: node examples/extract.js [magFile [techFile [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 } = {}) { + const { magic } = await createMagic(); + const { FS } = magic; + const { tech, cell } = loadCell(FS, techFile, magFile); + + magic.runScript(loadScript('extract.tcl', tech, cell)); + + mkdirSync(outputDir, { recursive: true }); + + const extData = vfsRead(FS, `/work/${cell}.ext`); + if (!extData) throw new Error(`Extraction failed: /work/${cell}.ext not created`); + writeFileSync(resolve(outputDir, `${cell}.ext`), extData); + + const resExtData = vfsRead(FS, `/work/${cell}.res.ext`); + if (resExtData) writeFileSync(resolve(outputDir, `${cell}.res.ext`), resExtData); + + const spiceData = vfsRead(FS, `/work/${cell}.spice`) ?? vfsRead(FS, `/work/${cell}.spc`); + const spiceExt = vfsRead(FS, `/work/${cell}.spice`) ? 'spice' : 'spc'; + if (spiceData) writeFileSync(resolve(outputDir, `${cell}.${spiceExt}`), spiceData); + + return { + ext: resolve(outputDir, `${cell}.ext`), + spice: spiceData ? resolve(outputDir, `${cell}.${spiceExt}`) : null, + }; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const { ext, spice } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); }); + console.log(`\next: ${ext}`); + if (spice) console.log(`spice: ${spice}`); + console.log('Done.'); +} diff --git a/npm/examples/extract.tcl b/npm/examples/extract.tcl new file mode 100644 index 00000000..ff06746e --- /dev/null +++ b/npm/examples/extract.tcl @@ -0,0 +1,27 @@ +# extract.tcl +# +# Complete Magic extraction workflow: +# 1. Load technology +# 2. Load layout +# 3. Extract parasitic capacitances (extract all → __CELL__.ext) +# 4. Extract parasitic resistances (extresist all → __CELL__.res.ext) +# 5. Write SPICE netlist (ext2spice → __CELL__.spice) +# +# __TECH__ and __CELL__ are substituted by extract.js before execution. + +tech load __TECH__ +load /work/__CELL__ + +# Write all intermediate files to /work/ so ext2spice can find __CELL__.res.ext +extract path /work +extract all + +# extresist requires a valid box cursor; span the full layout to be safe +box 0 0 100000 100000 +extresist all +ext2spice format ngspice +ext2spice extresist on +ext2spice cthresh 0 +ext2spice rthresh 0 + +ext2spice /work/__CELL__ diff --git a/npm/examples/gds.js b/npm/examples/gds.js new file mode 100644 index 00000000..b1c0e236 --- /dev/null +++ b/npm/examples/gds.js @@ -0,0 +1,30 @@ +// gds.js — Export layout to GDS II stream format. +// +// Usage: node examples/gds.js [magFile [techFile [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 } = {}) { + const { magic } = await createMagic(); + const { FS } = magic; + const { tech, cell } = loadCell(FS, techFile, magFile); + + magic.runScript(loadScript('gds.tcl', tech, cell)); + + mkdirSync(outputDir, { recursive: true }); + const data = vfsRead(FS, `/work/${cell}.gds`); + if (!data) throw new Error(`GDS export failed: /work/${cell}.gds not created`); + + const outPath = resolve(outputDir, `${cell}.gds`); + writeFileSync(outPath, data); + return { outPath, bytes: data.length }; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const { outPath, bytes } = await run().catch(e => { console.error(e.message ?? e); process.exit(1); }); + console.log(`\ngds: ${outPath} (${bytes} bytes)`); + console.log('Done.'); +} diff --git a/npm/examples/gds.tcl b/npm/examples/gds.tcl new file mode 100644 index 00000000..7f0b6532 --- /dev/null +++ b/npm/examples/gds.tcl @@ -0,0 +1,4 @@ +# GDS export +tech load __TECH__ +load /work/__CELL__ +gds write /work/__CELL__ diff --git a/npm/examples/helpers.js b/npm/examples/helpers.js new file mode 100644 index 00000000..ac7b151e --- /dev/null +++ b/npm/examples/helpers.js @@ -0,0 +1,72 @@ +// Shared utilities for Magic WASM examples. +import createMagicModule from '../magic.js'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +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_OUT = resolve(EXAMPLES_DIR, 'output'); + +export { basename }; + +export function vfsWrite(FS, vfsPath, source) { + const dir = vfsPath.substring(0, vfsPath.lastIndexOf('/')); + if (dir) FS.mkdirTree(dir); + FS.writeFile(vfsPath, typeof source === 'string' ? readFileSync(source) : source); +} + +export function vfsRead(FS, vfsPath) { + try { return Buffer.from(FS.readFile(vfsPath)); } catch { return null; } +} + +export class MagicWasm { + constructor(mod) { + this._init = mod.cwrap('magic_wasm_init', 'number', []); + this._run = mod.cwrap('magic_wasm_run_command', 'number', ['string']); + this.FS = mod.FS; + } + init() { + const rc = this._init(); + if (rc !== 0) throw new Error(`magic_wasm_init failed with code ${rc}`); + } + runScript(text) { + for (const line of text.split('\n')) { + const l = line.trim(); + if (l && !l.startsWith('#')) this._run(l); + } + } +} + +// Creates a fresh Magic WASM instance and calls init(). +// onPrint / onPrintErr default to console.log/error with a [magic] prefix. +// All output lines are also collected into the returned `lines` array. +export async function createMagic({ onPrint, onPrintErr } = {}) { + const lines = []; + const mod = await createMagicModule({ + wasmBinary, + print: onPrint ?? (msg => { lines.push(msg); console.log('[magic]', msg); }), + printErr: onPrintErr ?? (msg => { lines.push(msg); console.error('[magic]', msg); }), + }); + const magic = new MagicWasm(mod); + magic.init(); + 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); + return { tech, cell }; +} + +// Reads a TCL script from EXAMPLES_DIR, substitutes __TECH__ and __CELL__. +export function loadScript(name, tech, cell) { + return readFileSync(resolve(EXAMPLES_DIR, name), 'utf8') + .replaceAll('__TECH__', tech) + .replaceAll('__CELL__', cell); +} diff --git a/npm/examples/siliwiz.mag b/npm/examples/siliwiz.mag new file mode 100644 index 00000000..ff2dcd44 --- /dev/null +++ b/npm/examples/siliwiz.mag @@ -0,0 +1,51 @@ +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 >> diff --git a/npm/examples/siliwiz.tech b/npm/examples/siliwiz.tech new file mode 100644 index 00000000..c9bc241c --- /dev/null +++ b/npm/examples/siliwiz.tech @@ -0,0 +1,1452 @@ +#-------------------------------------------------------------------------- +# Copyright (c) R. Timothy Edwards, Open Circuit Design +# January 2023 +# +# This file contains no proprietary information +# +# This file was designed for tech format 33, magic version 7.5.56 or newer. +#-------------------------------------------------------------------------- +tech + format 35 + siliwiz +end + +version + version 2023-2 + description "Sample generic techfile, 6 metal layers, poly resistor, and MiM cap" + requires magic-8.3.0 +end + +#-------------------------------------------------------------------------- +# Version 2006-1: Original release +# Version 2022-1: Update with added high-sheet-rho resistor device +# Version 2023-1: Update with added MiM cap device +# Version 2023-2: Moved the MiM cap to be located between m1 and m2. +#-------------------------------------------------------------------------- + +#-------------------------------------------- +# Tile planes +#-------------------------------------------- + +planes + well,w + active,a + metal1,m1 + mimcap,mc + metal2,m2 + metal3,m3 + metal4,m4 + metal5,m5 + metal6,m6 + comment,c +end + +#-------------------------------------------- +# Tile types +#-------------------------------------------- + +types + well nwell,nw + well pwell,pw + + active ntransistor,nfet + active ptransistor,pfet + active ndiffusion,ndiff + active pdiffusion,pdiff + active ndcontact,ndc + active pdcontact,pdc + active psubstratepdiff,ppdiff,ppd,psd + active nsubstratendiff,nndiff,nnd,nsd + active psubstratepcontact,ppc,psc + active nsubstratencontact,nnc,nsc + active varactor,varact,var + active polysilicon,poly,p + active polycontact,pcontact,pc + active polyres,rpoly,rp + + metal1 metal1,m1 + metal1 rmetal1,rm1 + metal1 obsm1 + metal1 m1pin + metal1 m2contact,m2cut,m2c,via,v + mimcap mimcap + mimcap mimcapcontact,mimcapc,mimcc + metal2 metal2,m2 + metal2 rmetal2,rm2 + metal2 obsm2 + metal2 m2pin + metal2 m3contact,m3cut,m3c,via2,v2 + metal3 metal3,m3,cyan + metal3 rmetal3,rm3 + metal3 obsm3 + metal3 m3pin + metal3 m4contact,m4c,m4via,m4v + metal4 metal4,m4 + metal4 rmetal4,rm4 + metal4 obsm4 + metal4 m4pin + metal4 m5contact,m5c,m5via,m5v + metal5 metal5,m5 + metal5 rmetal5,rm5 + metal5 obsm5 + metal5 m5pin + metal5 m6contact,m6c,mtopcontact,mtopcut,mtopc,topvia,vtop + metal6 metal6,m6,metaltop,mtop,topmetal,topm + metal6 obsm6 + metal6 m6pin + metal6 rmetal6,rm6,rmetaltop,rmtop,rtopmetal,rtopm + metal6 pad + + comment glass + comment comment + comment boundary,bound + +end + +#-------------------------------------------- +# Magic contact types +#-------------------------------------------- + +contact + pc poly metal1 + ndc ndiff metal1 + pdc pdiff metal1 + nsc nsd metal1 + psc psd metal1 + m2c metal1 metal2 + m3c metal2 metal3 + m4c metal3 metal4 + m5c metal4 metal5 + m6c metal5 metal6 + stackable + + # MiM cap contacts are not stackable + mimcc mimcap metal2 +end + +#-------------------------------------------- +# Layer aliases +#-------------------------------------------- + +aliases + +# Layer group definitions for making life easy when +# writing/editing the techfile + + allnfets nfet + allpfets pfet + allfets allnfets,allpfets,varactor + + allnactivenonfet *ndiff,*nsd + allnactive allnactivenonfet,allnfets + + allpactivenonfet *pdiff,*psd + allpactive allpactivenonfet,allpfets + + allactivenonfet allnactivenonfet,allpactivenonfet + allactive allactivenonfet,allfets + + allpolynonfet *poly,rpoly + allpoly allpolynonfet,allfets + + allm1 *m1,rm1,m1pin + allm2 *m2,rm2,m2pin + allm3 *m3,rm3,m3pin + allm4 *m4,rm4,m4pin + allm5 *m5,rm5,m5pin + allm6 *m6,rm6,m6pin,pad +end + +#-------------------------------------------- +# Layer drawing styles +#-------------------------------------------- + +styles + styletype mos + + nwell nwell + pwell pwell + ndiff ndiffusion + pdiff pdiffusion + nsd ndiff_in_nwell + psd pdiff_in_pwell + nfet ntransistor ntransistor_stripes + pfet ptransistor ptransistor_stripes + varact polysilicon ndiff_in_nwell + ndc ndiffusion metal1 contact_X'es + pdc pdiffusion metal1 contact_X'es + nsc ndiff_in_nwell metal1 contact_X'es + psc pdiff_in_pwell metal1 contact_X'es + poly polysilicon + rpoly poly_resist poly_resist_stripes + pc poly_contact contact_X'es + metal1 metal1 + rm1 metal1 poly_resist_stripes + m1p metal1 overglass + m2contact metal1 metal2 via1arrow + mimcap mems + mimcc contact_X'es mems metal2 + metal2 metal2 + rm2 metal2 poly_resist_stripes + m2p metal2 overglass + m3contact metal2 metal3 via2arrow + metal3 metal3 + rm3 metal3 poly_resist_stripes + m3p metal3 overglass + m4contact metal3 metal4 via3alt + m4 metal4 + rm4 metal4 poly_resist_stripes + m4p metal4 overglass + m5contact metal4 metal5 via4 + m5 metal5 + rm5 metal5 poly_resist_stripes + m5p metal5 overglass + m6contact metal5 metal6 via5 + m6 metal6 + rm6 metal6 poly_resist_stripes + m6p metal6 overglass + pad metal5 metal6 via5 overglass + obsm1 obsmetal1 + obsm2 obsmetal2 + obsm3 obsmetal3 + obsm4 obsmetal4 + obsm5 obsmetal5 + obsm6 obsmetal6 + glass overglass + comment comment + boundary subcircuit + error_p error_waffle + error_s error_waffle + error_ps error_waffle + magnet substrate_field_implant + rotate via3alt + fence via6 +end + +#-------------------------------------------- +# Special paint/erase rules +#-------------------------------------------- + +compose + compose nfet poly ndiff + compose pfet poly pdiff + compose varact poly nsd + + paint ndc nwell pdc + paint nfet nwell pfet + paint ndiff nwell pdiff + paint psd nwell nsd + paint psc nwell nsc + + paint pdc pwell ndc + paint pfet pwell nfet + paint pdiff pwell ndiff + paint nsd pwell psd + paint nsc pwell psc + + paint pad m1 pad + paint pad m2c pad + paint pad m2 pad + paint pad m3c pad + paint pad m3 pad + paint pad m4c pad + paint pad m4 pad + paint pad m5c pad + paint pad m5 pad + paint pad m6c pad + paint pad m6 pad +end + +#-------------------------------------------- +# Electrical connectivity +#-------------------------------------------- + +connect + *nwell,*nsd *nwell,*nsd + *pwell,*psd *pwell,*psd + *m1,m1p *m1,m1p + *m2,m2p *m2,m2p + *m3,m3p *m3,m3p + *m4,m4p *m4,m4p + *m5,m5p *m5,m5p + *m6,m6p *m6,m6p + *mimcap *mimcap + *ndiff,*psd *ndiff,*psd + *pdiff,*nsd *pdiff,*nsd + *poly,allfets *poly,allfets + pad *m1,*m2,*m3,*m4,*m5,*m6 +end + +#-------------------------------------------- +# CIF/GDS output layer definitions +#-------------------------------------------- + +cifoutput + +style lambda=0.09(generic) + + scalefactor 90 nanometers + gridlimit 10 + options calma-permissive-labels + +# nwell + layer NWELL nwell + bloat-or allpactive/a * 430 + shrink 430 + grow 430 + bloat-or varact * 500 *nsd 860 + grow 700 + shrink 700 + calma 1 0 + +# P-select (PP) + + templayer XDP + bloat-or allpactivenonfet/a * 180 *nsd 50 + bloat-or allpfets * 320 *poly 350 *nsd 50 + bloat-or *psd * 180 allnactive/a 50 + + layer PPLUS XDP + shrink 220 + grow 220 + grow 220 + shrink 220 + calma 5 0 + + layer DIFF allactive + labels allactive + calma 3 0 + + layer POLY allpoly + labels allpoly + calma 8 0 + + layer POLYRES rpoly + labels rpoly + calma 8 0 + + layer CONT ndc,nsc,pdc,psc + squares 100 200 240 + calma 9 0 + + layer CONT pc + squares 60 200 240 + calma 9 0 + + layer VIA1 m2c,mimcc + squares 80 260 280 + calma 11 0 + + layer VIA2 m3c + squares 120 260 260 + calma 13 0 + + layer VIA3 m4c + squares 120 260 260 + calma 15 0 + + layer VIA4 m5c + squares 120 260 260 + calma 17 0 + + layer VIA5 m6c + squares 200 750 750 + calma 19 0 + + layer GLASS glass + calma 25 0 + + layer PAD pad + calma 30 0 + + layer BOUND boundary + calma 60 0 + + layer MET1 allm1 + labels allm1 + calma 10 0 + + layer MET2 allm2 + labels allm2 + calma 12 0 + + layer MET3 allm3 + labels allm3 + calma 14 0 + + layer MET4 allm4 + labels allm4 + calma 16 0 + + layer MET5 allm5 + labels allm5 + calma 18 0 + + layer MIMC *mimcap + labels *mimcap + calma 50 0 + + layer MET6 allm6 + labels allm6 + calma 20 0 + + layer GLASS pad + shrink 3000 + calma 25 0 + +# varactor marker layer. + + layer VARACT + bloat-or varact * 500 *nsd 860 + calma 40 0 + + layer PRES rpoly + calma 8 6 + + layer M1RES rm1 + calma 10 6 + + layer M2RES rm2 + calma 12 6 + + layer M3RES rm3 + calma 14 6 + + layer M4RES rm4 + calma 16 6 + + layer M5RES rm5 + calma 18 6 + + layer M6RES rm6 + calma 20 6 + + render MET6 metal6 6.0 1.3 + render VIA5 via 5.0 1.0 + render MET5 metal5 4.5 0.5 + render VIA4 via 3.9 0.6 + render MET4 metal4 3.4 0.5 + render VIA3 via 2.8 0.6 + render MET3 metal3 2.3 0.5 + render VIA2 via 1.7 0.6 + render MET2 metal2 1.2 0.5 + render VIA1 via 0.8 0.4 + render MET1 metal1 0.5 0.3 + render CONT via 0.2 0.3 + render POLY polysilicon 0.0 0.2 + render DIFF ndiffusion -0.3 0.3 + +end + +#-------------------------------------------- +# CIF/GDS input layer definitions +#-------------------------------------------- + +cifinput + +style lambda=0.09(generic) + scalefactor 90 nanometers + + layer nwell NWELL + labels NWELL + + layer pwell PWELL + labels PWELL + + layer ndiff DIFF + and-not POLY + and-not NWELL + and-not PPLUS + labels DIFF + + layer pdiff DIFF + and-not POLY + and NWELL + and PPLUS + labels DIFF + + layer nfet DIFF + and POLY + and-not NWELL + and-not PPLUS + labels DIFF + + layer varactor DIFF + and POLY + and NWELL + and-not PPLUS + labels DIFF + + layer pfet DIFF + and POLY + and NWELL + and PPLUS + labels DIFF + + layer nsd DIFF + and-not POLY + and NWELL + and-not PPLUS + labels DIFF + + layer psd DIFF + and-not NWELL + and PPLUS + labels DIFF + + layer nsc CONT + and MET1 + and DIFF + and NWELL + and-not PPLUS + grow 100 + grow 120 + shrink 120 + + layer psc CONT + and MET1 + and DIFF + and PPLUS + and-not NWELL + grow 100 + grow 120 + shrink 120 + + layer ndc CONT + and DIFF + and-not PPLUS + and-not NWELL + and MET1 + grow 100 + grow 120 + shrink 120 + + layer pdc CONT + and MET1 + and DIFF + and PPLUS + and NWELL + grow 100 + grow 120 + shrink 120 + + layer pc CONT + and POLY + and-not DIFF + and MET1 + grow 60 + grow 120 + shrink 120 + + layer poly POLY,POPIN + and-not PRES + labels POLY + + layer rpoly POLY + and PRES + labels POLY,PRES + + layer m1 MET1 + and-not M1RES + and-not PAD + labels MET1 + + layer rm1 MET1 + and M1RES + labels MET1,M1RES + + layer m2c VIA1 + and-not PAD + grow 80 + and MET2 + and MET1 + grow 140 + shrink 140 + + layer m2 MET2 + and-not M2RES + and-not PAD + labels MET2 + + layer rm2 MET2 + and M2RES + labels MET2,M2RES + + layer m3c VIA2 + and-not PAD + grow 80 + and MET3 + and MET2 + grow 140 + shrink 140 + + layer m3 MET3 + and-not M3RES + and-not PAD + labels MET3 + + layer rm3 MET3 + and M3RES + labels MET3,M3RES + + layer m4c VIA3 + and-not PAD + grow 80 + and MET4 + and MET3 + grow 140 + shrink 140 + + layer m4 MET4 + and-not M4RES + and-not PAD + labels MET4 + + layer rm4 MET4 + and M4RES + labels MET4,M4RES + + layer m5 MET5 + and-not M5RES + and-not PAD + labels MET5 + + layer rm5 MET5 + and M5RES + labels MET5,M5RES + + layer m5c VIA4 + and-not PAD + grow 80 + and MET4 + and MET5 + grow 140 + shrink 140 + + layer m6 MET6 + and-not M6RES + and-not PAD + labels MET6 + + layer rm6 MET6 + and M6RES + labels MET6,M6RES + + layer m6c VIA5 + and-not PAD + and-not MIMC + grow 100 + and MET5 + and MET6 + grow 200 + shrink 200 + + layer mimcc VIA1 + and-not PAD + and MIMC + grow 100 + and MET1 + and MET2 + grow 200 + shrink 200 + + layer mimcap MIMC + labels MIMC + + layer pad PAD + labels PAD + + layer glass GLASS + labels GLASS + + layer boundary BOUND + labels BOUND + + layer comment COMMENT + labels COMMENT + + ignore PWELL + ignore VARACT + # NOTE: Need to handle ports! + ignore POPIN + ignore M1PIN + ignore M2PIN + ignore M3PIN + ignore M4PIN + ignore M5PIN + ignore M6PIN + # NOTE: Need to handle obstruction layers! + ignore POOBS + ignore M1OBS + ignore M2OBS + ignore M3OBS + ignore M4OBS + ignore M5OBS + ignore M6OBS + + calma NWELL 1 0 + calma PWELL 2 0 + + calma DIFF 3 0 + calma PPLUS 5 0 + + calma POLY 8 0 + calma MET1 10 0 + calma MET2 12 0 + calma MET3 14 0 + calma MET4 16 0 + calma MET5 18 0 + calma MIMC 50 0 + calma MET6 20 0 + + calma POPIN 8 2 + calma M1PIN 10 2 + calma M2PIN 12 2 + calma M3PIN 14 2 + calma M4PIN 16 2 + calma M5PIN 18 2 + calma M6PIN 20 2 + + calma POOBS 8 4 + calma M1OBS 10 4 + calma M2OBS 12 4 + calma M3OBS 14 4 + calma M4OBS 16 4 + calma M5OBS 18 4 + calma M6OBS 20 4 + + calma PRES 8 6 + calma M1RES 10 6 + calma M2RES 12 6 + calma M3RES 14 6 + calma M4RES 16 6 + calma M5RES 18 6 + calma M6RES 20 6 + + calma CONT 9 0 + calma VIA1 11 0 + calma VIA2 13 0 + calma VIA3 15 0 + calma VIA4 17 0 + calma VIA5 19 0 + + calma GLASS 25 0 + calma PAD 30 0 + + calma VARACT 40 0 + calma BOUND 60 0 + +end + +#-------------------------------------------- +# Digital flow maze router cost parameters +#-------------------------------------------- + +mzrouter + style irouter +# layer hCost vCost jogCost hintCost overCost + layer metal6 4000 4000 100 100 5000 + layer metal5 2000 2000 100 100 2000 + layer metal4 1000 1000 100 100 2000 + layer metal3 600 600 100 100 1200 + layer metal2 200 200 100 100 400 + layer metal1 100 100 100 100 200 + layer poly 2000 2000 200 100 4000 + contact m6c m5 m6 16000 + contact m5c m4 m5 8000 + contact m4c metal3 m4 4000 + contact m3contact metal2 metal3 4000 + contact m2contact metal1 metal2 2000 + contact pcontact poly metal1 8000 + + notactive poly pcontact + notactive m6 m6c + notactive m5 m5c + notactive m4 m4c + +style garouter + layer metal6 4000 4000 100 100 5000 + layer metal5 2000 2000 100 100 2000 + layer metal4 1000 1000 100 100 2000 + layer metal3 600 600 100 100 1200 + layer metal2 200 200 100 100 400 + layer metal1 100 100 100 100 200 + layer poly 2000 2000 200 100 4000 + contact m6c m5 m6 16000 + contact m5c m4 m5 8000 + contact m4c metal3 m4 4000 + contact m3contact metal2 metal3 4000 + contact m2contact metal1 metal2 2000 + contact pcontact poly metal1 8000 + + notactive poly pcontact + notactive m6 m6c + notactive m5 m5c + notactive m4 m4c +end + +#-------------------------------------------- +# Vendor DRC rules +#-------------------------------------------- + +drc + + style generic variants (basic),(stress),() + + scalefactor 9 + +#------------- +# Well rules +#------------- + + width nwell 90 \ + "N-well width < 0.90um" + + width pwell 86 \ + "P-well width < 0.90um" + + spacing nwell nwell 50 touching_ok \ + "N-well(at-same-potential) spacing < 0.50um" + + spacing pwell pwell 50 touching_ok \ + "P-well(at-same-potential) spacing < 0.50um" + +# Well->Diffusion interaction + + spacing nwell *ndiff,allnfets 45 touching_illegal \ + "N-well spacing to N-Diffusion < 0.45um" + + spacing pwell *pdiff,allpfets 45 touching_illegal \ + "P-well spacing to P-Diffusion < 0.45um" + + spacing nwell *psd 18 touching_illegal \ + "N-well spacing to P-Ohmic < 0.18um" + + spacing pwell *nsd 18 touching_illegal \ + "P-well spacing to N-Ohmic < 0.18um" + + surround (*pdiff,allpfets)/a nwell 45 absence_ok \ + "N-well overlap of P-Diffusion < 0.45um" + + surround (*ndiff,allnfets)/a pwell 45 absence_ok \ + "P-well overlap of N-Diffusion < 0.45um" + + surround (*nsd)/a nwell 18 absence_ok \ + "N-well overlap of N-Ohmic < 0.18um" + + surround (*psd)/a pwell 18 absence_ok \ + "P-well overlap of P-Ohmic < 0.18um" + + +# Diffusion rules +#------------- + + width (allactive)/a 27 \ + "Diffusion width < 0.27um" + + spacing (allactive)/a (allactive)/a 36 touching_ok \ + "Diffusion spacing < 0.36um" + + spacing *nsd *pdiff 36 touching_illegal \ + "P-Diffusion width in N-Ohmic < 0.36um" + + spacing *psd *ndiff 36 touching_illegal \ + "N-Diffusion width in P-Ohmic < 0.36um" + + spacing (allnactive)/a (allpactive)/a 90 touching_illegal \ + "N-Diffusion spacing to P-Diffusion < 0.90um" + + spacing *ndiff *nsd 63 touching_illegal \ + "N-Diffusion spacing to N-Ohmic < 0.63um" + + spacing *pdiff *psd 63 touching_illegal \ + "P-Diffusion spacing to P-Ohmic < 0.63um" + + spacing *nsd *psd 36 touching_illegal \ + "N-Ohmic spacing to P-Ohmic < 0.36um" + + spacing *ndiff *psd 45 touching_ok \ + "N-Diffusion spacing to P-Ohmic < 0.45um" + + spacing *pdiff *nsd 45 touching_ok \ + "P-Diffusion spacing to N-Ohmic < 0.45um" + +variant (stress),() + + maxwidth allnactive,allpactive 10000 \ + "Diffusion maximum width > 100um" + +variant * + +# Polysilicon and transistor rules +#------------- + + width (allpoly)/a 18 \ + "Poly width < 0.18um" + + spacing (allpoly)/a (allpoly)/a 24 touching_ok \ + "Poly spacing < 0.24um" + + # Polysilicon->Diffusion interactions + + spacing (allpolynonfet)/a (allactivenonfet)/a 9 corner_ok allfets \ + "Poly spacing to Diffusion < 0.09um" + + overhang (allpolynonfet)/a allfets 24 \ + "Poly overhang of Transistor < 0.24um" + + spacing pfet *nsd 54 touching_ok \ + "P-Transistor spacing to N-Ohmic < 0.54um" + + spacing allnfets *psd 54 touching_ok \ + "N-Transistor spacing to P-Ohmic < 0.54um" + + overhang (allnactivenonfet)/a allnfets 27 \ + "N-Diffusion overhang of N-Transistor < 0.27um" + + overhang (allpactivenonfet)/a allpfets 27 \ + "P-Diffusion overhang of P-Transistor < 0.27um" + + spacing allnfets *psd 27 touching_illegal \ + "N-Transistor space to P-Ohmic < 0.27um" + + spacing allpfets *nsd 27 touching_illegal \ + "P-Transistor space to N-Ohmic < 0.27um" + + +# Poly and diffusion contact rules +#--------------- + + width pc/m1 32 \ + "Poly contact width < 0.32um" + + width (ndc,nsc,pdc,psc)/m1 40 \ + "Diffusion contact width < 0.40um" + + spacing pc/a pc/a 18 touching_ok \ + "Poly contact spacing < 0.18um" + + spacing ndc/a,pdc/a,psc/a,nsc/a ndc/a,pdc/a,psc/a,nsc/a 9 touching_ok \ + "Diffusion contact spacing < 0.09um" + + spacing (allactive)/a (ndc,pdc,nsc,psc)/a 9 touching_ok \ + "Diffusion space to Diffusion contact < 0.09um" + + rect_only pdc,psc,pc \ + "Contact not rectangular" + + exact_overlap ndc,nsc,pdc,psc,pc + + # Diffusion->Contact interactions + + spacing allfets ndc/a,pdc/a,psc/a,nsc/a 9 touching_illegal \ + "N-Transistor,P-Transistor spacing to Diffusion contact < 0.09um" + + spacing (allactive)/a pc/a 12 touching_illegal \ + "Diffusion spacing to Poly contact < 0.12um" + +# MOS Varactor rules +#----------- + + edge4way varact *poly 144 ~(*psd/a)/a *nsd 108 \ + "Varactor spacing to p-Ohmic < 1.44um in direction of gate end" + + edge4way varact *nsd 108 ~(*psd/a)/a *poly 144 \ + "Varactor spacing to p-Ohmic < 1.08um in direction of active" + +# Metal and Via rules +#------------- + +# Metal1 + + width (allm1)/m1 27 \ + "Metal1 width < 0.27um" + + spacing (allm1)/m1 (allm1)/m1 27 touching_ok \ + "Metal1 spacing < 0.27" + + widespacing (allm1)/m1 400 (allm1)/m1 60 touching_ok \ + "Wide metal1 (> 4.0um) spacing < 0.60um" + +variant (stress),() + + maxwidth allm1 3500 \ + "Metal 1 maximum width > 35.0um" + +variant * + +# Via1 + + width m2c/m1 28 \ + "Metal2 contact width < 0.28um" + + area m2c/m1 1232 44 \ + "Metal2 contact area < 0.28 x 0.44" + + spacing m2c/m1 m2c/m1 36 touching_ok \ + "Metal2 contact spacing < 0.36um" + + rect_only m2c \ + "Contact not rectangular (Magic rule)" + + exact_overlap m2c + +# Metal2 + + width allm2 28 \ + "Metal2 width < 0.28um" + + spacing allm2 allm2 28 touching_ok \ + "Metal2 spacing < 0.28" + + widespacing *m2 400 *m2 60 touching_ok \ + "Wide metal2 (> 4.0um) spacing < 0.60um" + +# Via2 + + width m3c/m2 40 \ + "Metal3 contact width < 0.40um" + + rect_only m3c \ + "Contact not rectangular" + + exact_overlap m3c + +# Metal3 + + width allm3 28 \ + "Metal3 width < 0.28um" + + spacing allm3 allm3 28 touching_ok \ + "Metal3 spacing < 0.28" + + widespacing *m3 400 *m3 60 touching_ok \ + "Wide metal3 (> 4.0um) spacing < 0.60" + +# ViaM4 + + width m4c 40 \ + "M4 metal contact width < 0.40um" + + rect_only m4c \ + "Contact not rectangular" + + exact_overlap m4c + +# Metal M4 + + width allm4 100 \ + "M4 metal width < 1.0um" + + spacing allm4 allm4 28 touching_ok \ + "Metal M4 spacing < 0.28um" + + widespacing *m4 400 *m4 60 touching_ok \ + "Wide metal4 (> 4.0um) spacing < 0.60" + +# ViaM5 + + width m5c 40 \ + "M5 metal contact width < 0.40um" + + rect_only m5c \ + "Contact not rectangular (Magic rule)" + + exact_overlap m5c + +# MiM cap + + width mimcc 40 \ + "MiM contact width < 0.40um" + + width *mimcap 100 \ + "MiM cap width < 1.00um" + + spacing *mimcap *mimcap 200 touching_ok \ + "MiM cap spacing < 2.00um" + + surround *mimcap *metal1 50 absence_illegal \ + "M1 must surround MiM cap by 0.5um" + + rect_only mimcc \ + "MiM contact not rectangular (Magic rule)" + +# Metal M5 + + width allm5 100 \ + "M5 metal width < 1.00um" + + spacing allm5 allm5 125 touching_ok \ + "Metal M5 spacing < 1.25um" + +# Via M6 + + width m6c 120 \ + "M6 metal contact width < 1.2um" + + spacing m6c m6c 150 touching_ok \ + "M6 Metal contact spacing < 1.5um" + + rect_only m6c \ + "Contact not rectangular" + + exact_overlap m6c + +# Metal M6 + + width allm6 200 \ + "M6 metal width < 2.0um" + + spacing allm6 allm6 180 touching_ok \ + "M6 metal spacing < 1.8um" + +# Pad + + rect_only pad \ + "Contact not rectangular" + +#----------- + + stepsize 2000 +end + +#-------------------------------------------- +# LEF format definitions +#-------------------------------------------- + +lef + + ignore POLY + ignore DIFF + + routing m1 M1 m1 met1 + routing m2 M2 m2 met2 + routing m3 M3 m3 met3 + routing m4 M4 m4 met4 + routing m5 M5 m5 met5 + routing m6 M6 m6 met6 + + cut m2c via1 cont2 via12 + cut m3c via2 cont3 via23 + cut m4c via3 cont4 via34 + cut m5c via4 cont5 via45 + cut m6c via5 cont6 via56 + + obs obsm1 met1 + obs obsm2 met2 + obs obsm3 met3 + obs obsm4 met4 + obs obsm5 met5 + obs obsm6 met6 + +end + +#-------------------------------------------- +# Device and Parasitic Extraction +#-------------------------------------------- + + +extract + style lambda=0.09 variants (generic),(model) + +# Declare 9 centimicrons per database "lambda" unit + lambda 9 + +# Declare that capacitor values are aF/um and aF/um^2 + units microns + + step 100 + sidehalo 8 + cscale 1 + + planeorder well 0 + planeorder active 1 + planeorder metal1 2 + planeorder metal2 3 + planeorder metal3 4 + planeorder metal4 5 + planeorder metal5 6 + planeorder metal6 7 + planeorder comment 8 + planeorder mimcap 9 + + substrate *psd,space/w,pwell well $SUB + +# All resistance and contact values are in milliohms per square + + resist (*ndiff,nsd)/active 8000 + resist (*pdiff,*psd)/active 8000 + resist (nwell)/well 900000 + resist (pwell)/well 900000 + + resist (*poly)/active 7400 + # Unsalicided poly resistor is 400 ohms/square + resist rpoly/active 400000 + resist (allm1)/metal1 80 + resist (allm2)/metal2 80 + resist (allm3)/metal3 80 + resist (allm4)/metal4 80 + resist (allm5)/metal5 80 + resist (allm6)/metal6 40 + + contact ndc,nsc 10000 + contact pdc,psc 10000 + contact pc 10000 + contact m2c,m3c,m4c,m5c 6800 + contact mimcc 6800 + contact m6c 2000 + +#------------------------------------------------------------------------- +# Parasitic capacitance values +#------------------------------------------------------------------------- +# This uses the new "default" definitions that determine the intervening +# planes from the planeorder stack, take care of the reflexive sideoverlap +# definitions, and generally clean up the section and make it more readable. +#------------------------------------------------------------------------- +# Remember that device capacitances to substrate are taken care of by the +# models. Thus, active and poly definitions ignore all "fet" types. +# However, fet types are included when computing parasitic capacitance to +# poly and active from layers above them. +#------------------------------------------------------------------------- + +# All perimeter capacitance values are in aF/um +# All area capacitance values are in aF/um^2 +# (determined by the value of "lambda" and "units" above) + +#n-well + defaultareacap nwell well 0.502 + +#n-active + defaultareacap allnactivenonfet active 15.163 + defaultperimeter allnactivenonfet active 39.600 + +#p-active + defaultareacap allpactivenonfet active 15.204 + defaultperimeter allpactivenonfet active 31.680 + +#poly + defaultsidewall allpolynonfet active 12.714 + defaultareacap allpolynonfet active well 0.786 + defaultperimeter allpolynonfet active well 2.316 + +#metal1 + defaultsidewall allm1 metal1 23.087 + defaultareacap allm1 metal1 well 0.308 + defaultperimeter allm1 metal1 well 2.070 + +#metal1->diff + defaultoverlap allm1 metal1 allactivenonfet active 0.405 + defaultsideoverlap allm1 metal1 allactivenonfet active 2.070 + +#metal1->poly + defaultoverlap allm1 metal1 allpoly active 0.510 + defaultsideoverlap allm1 metal1 allpoly active 6.300 + +#metal2 + defaultsidewall allm2 metal2 25.475 + defaultareacap allm2 metal2 well 0.154 + defaultperimeter allm2 metal2 well 5.400 + +#metal2->active + defaultoverlap allm2 metal2 allactivenonfet active 0.162 + defaultsideoverlap allm2 metal2 allactivenonfet active 5.400 + +#metal2->poly + defaultoverlap allm2 metal2 allpoly active 0.138 + defaultsideoverlap allm2 metal2 allpoly active 3.780 + +#metal2->metal1 + defaultoverlap allm2 metal2 allm1 metal1 0.300 + defaultsideoverlap allm2 metal2 allm1 metal1 0.680 + +#metal3 + defaultsidewall allm3 metal3 26.825 + defaultareacap allm3 metal3 well 0.105 + defaultperimeter allm3 metal3 well 5.040 + +#metal3->active + defaultoverlap allm3 metal3 allactive active 0.113 + defaultsideoverlap allm3 metal3 allactive active 5.040 + +#metal3->poly + defaultoverlap allm3 metal3 allpoly active 0.081 + defaultsideoverlap allm3 metal3 allpoly active 2.700 + +#metal3->metal1 + defaultoverlap allm3 metal3 allm1 metal1 0.122 + defaultsideoverlap allm3 metal3 allm1 metal1 3.240 + +#metal3->metal2 + defaultoverlap allm3 metal3 allm2 metal2 0.308 + defaultsideoverlap allm3 metal3 allm2 metal2 4.410 + +#metal4 + defaultsidewall allm4 m4 27.675 + defaultareacap allm4 m4 well 0.065 + defaultperimeter allm4 m4 well 2.160 + +#metal4->active + defaultoverlap allm4 metal4 allactive active 0.113 + defaultsideoverlap allm4 metal4 allactive active 5.040 + +#metal4->poly + defaultoverlap allm4 metal4 allpoly active 0.113 + defaultsideoverlap allm4 metal4 allpoly active 5.040 + +#metal4->metal1 + defaultoverlap allm4 metal4 allm1 metal1 0.113 + defaultsideoverlap allm4 metal4 allm1 metal1 5.040 + +#metal4->metal2 + defaultoverlap allm4 metal4 allm2 metal2 0.113 + defaultsideoverlap allm4 metal4 allm2 metal2 5.040 + +#metal4->metal3 + defaultoverlap allm4 metal4 allm3 metal3 0.113 + defaultsideoverlap allm4 metal4 allm3 metal3 5.040 + +#metal5 + defaultsidewall allm5 m5 27.675 + defaultareacap allm5 m5 well 0.065 + defaultperimeter allm5 m5 well 2.160 + +#metal5->active + defaultoverlap allm5 metal5 allactive active 0.113 + defaultsideoverlap allm5 metal5 allactive active 5.040 + +#metal5->poly + defaultoverlap allm5 metal5 allpoly active 0.113 + defaultsideoverlap allm5 metal5 allpoly active 5.040 + +#metal5->metal1 + defaultoverlap allm5 metal5 allm1 m1 0.113 + defaultsideoverlap allm5 metal5 allm1 m1 5.040 + +#metal5->metal2 + defaultoverlap allm5 metal5 allm2 m2 0.113 + defaultsideoverlap allm5 metal5 allm2 m2 5.040 + +#metal5->metal3 + defaultoverlap allm5 metal5 allm3 m3 0.113 + defaultsideoverlap allm5 metal5 allm3 m3 5.040 + +#metal5->metal4 + defaultoverlap allm5 metal5 allm4 m4 0.113 + defaultsideoverlap allm5 metal5 allm4 m4 5.040 + +#metal6 + defaultsidewall allm6 m6 27.675 + defaultareacap allm6 m6 well 0.065 + defaultperimeter allm6 m6 well 2.160 + +#m6->active + defaultoverlap allm6 m6 allactivenonfet active 0.073 + defaultsideoverlap allm6 m6 allactivenonfet active 2.160 + +#m6->poly + defaultoverlap allm6 m6 allpoly active 0.049 + defaultsideoverlap allm6 m6 allpoly active 1.890 + +#m6->m1 + defaultoverlap allm6 m6 allm1 metal1 0.073 + defaultsideoverlap allm6 m6 allm1 metal1 2.160 + +#m6->m2 + defaultoverlap allm6 m6 allm2 metal2 0.073 + defaultsideoverlap allm6 m6 allm2 metal2 2.610 + +#m6->m3 + defaultoverlap allm6 m6 allm3 metal3 0.300 + defaultsideoverlap allm6 m6 allm3 metal3 5.850 + +#m6->m4 + defaultoverlap allm6 m6 allm4 m4 0.300 + defaultsideoverlap allm6 m6 allm4 m4 5.850 + +#m6->m5 + defaultoverlap allm6 m6 allm5 m5 0.300 + defaultsideoverlap allm6 m6 allm5 m5 5.850 + +#devices + + device mosfet pmos pfet pdiff,pdc nwell $VDD 50 46 + device mosfet nmos nfet ndiff,ndc pwell,space/w $GND 50 46 + + device mosfet varact varact nsd,nsc nwell $GND 50 46 + + fetresis pfet linear 40000 + fetresis pfet saturation 40000 + fetresis nfet linear 9000 + fetresis nfet saturation 9000 + +variant (generic) + + device resistor None rpoly *poly + device resistor None rmetal1 *metal1 + device resistor None rmetal2 *metal2 + device resistor None rmetal3 *metal3 + device resistor None rmetal4 *metal4 + device resistor None rmetal5 *metal5 + device resistor None rmetal6 *metal6 + + # Value 1000 is in aF/um^2 + device capacitor None *mimcap *metal1 4000 + +variant (model) + + device rsubcircuit rp rpoly *poly w=w l=l + device rsubcircuit rm1 rmetal1 *metal1 w=w l=l + device rsubcircuit rm2 rmetal2 *metal2 w=w l=l + device rsubcircuit rm3 rmetal3 *metal3 w=w l=l + device rsubcircuit rm4 rmetal4 *metal4 w=w l=l + device rsubcircuit rm5 rmetal5 *metal5 w=w l=l + device rsubcircuit rm6 rmetal6 *metal6 w=w l=l + + device csubcircuit cmim *mimcap *metal1 w=w l=l + +variant * + +end + +#-------------------------------------------- +# Wiring tool definitions +#-------------------------------------------- + +wiring + contact pdcontact 4 pdiff 0 metal1 0 + contact ndcontact 4 ndiff 0 metal1 0 + contact pcontact 4 poly 0 metal1 0 + contact m2contact 4 metal1 0 metal2 0 + contact m3contact 5 metal2 0 metal3 0 + contact m4contact 5 metal3 0 metal4 0 + contact m5contact 5 metal4 0 metal5 0 + contact m6contact 8 metal5 0 metal6 0 +end + +#-------------------------------------------- +# Plain old router. . . +#-------------------------------------------- + +router + layer2 metal2 3 allm2 4 + layer1 metal1 3 allm1 3 + contacts m2contact 5 + gridspacing 9 +end + +#--------------------------------------------------------- +# Plowing (which doesn't work in Magic 7.5 right now. . .) +#--------------------------------------------------------- + +plowing + fixed allfets + covered allfets + drag allfets +end + +#--------------------------------------------------------------- +# No special plot layers defined (use default PNM color choices) +#--------------------------------------------------------------- + +plot +end diff --git a/npm/index.d.ts b/npm/index.d.ts new file mode 100644 index 00000000..26e838fd --- /dev/null +++ b/npm/index.d.ts @@ -0,0 +1,57 @@ +export interface MagicInstance { + /** + * Initialize Magic (idempotent — safe to call multiple times). + * Returns 0 on success, non-zero on failure. + */ + init(): number; + + /** + * Dispatch a single Magic command string. + * Calls init() automatically on the first invocation. + * Returns 0 on success, non-zero on failure. + */ + runCommand(command: string): number; + + /** + * Read and execute a command file at the given virtual filesystem path. + * Calls init() automatically on the first invocation. + * Returns 0 on success, -1 if the file could not be opened. + */ + sourceFile(path: string): number; + + /** + * Drive a display-update cycle. + * No-op in headless builds — the null display driver suspends all redraws. + */ + update(): void; + + /** + * Emscripten virtual filesystem. + * Use FS.writeFile / FS.readFile to pass layout files in and out of Magic. + * See https://emscripten.org/docs/api_reference/Filesystem-API.html + */ + FS: any; +} + +/** + * Create a Magic WASM instance. + * + * @param options Emscripten module options. Useful keys: + * - `wasmBinary` Pre-fetched ArrayBuffer of magic.wasm (avoids a second fetch) + * - `print` Callback for stdout lines (default: console.log) + * - `printErr` Callback for stderr lines (default: console.error) + * + * @example + * ```js + * import createMagic from 'magic-vlsi-wasm'; + * + * const { runCommand, FS } = await createMagic(); + * FS.writeFile('/work/inv.mag', layoutBytes); + * runCommand('tech load sky130A'); + * runCommand('load /work/inv'); + * runCommand('gds write /work/inv'); + * const gds = FS.readFile('/work/inv.gds'); + * ``` + */ +export default function createMagic(options?: Record): Promise; +export { createMagic }; diff --git a/npm/index.js b/npm/index.js new file mode 100644 index 00000000..9cb6e61b --- /dev/null +++ b/npm/index.js @@ -0,0 +1,15 @@ +import MagicModuleFactory from './magic.js'; + +async function createMagic(options = {}) { + const module = await MagicModuleFactory(options); + + const init = module.cwrap('magic_wasm_init', 'number', []); + const runCommand = module.cwrap('magic_wasm_run_command', 'number', ['string']); + const sourceFile = module.cwrap('magic_wasm_source_file', 'number', ['string']); + const update = module.cwrap('magic_wasm_update', null, []); + + return { init, runCommand, sourceFile, update, FS: module.FS }; +} + +export { createMagic }; +export default createMagic; diff --git a/npm/pack.sh b/npm/pack.sh new file mode 100755 index 00000000..10553f48 --- /dev/null +++ b/npm/pack.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Pack the magic-vlsi-wasm npm tarball with reproducible-but-current timestamps. +# +# npm/pacote normalizes file mtimes for reproducibility and, when no +# SOURCE_DATE_EPOCH is set, falls back to 1985-10-26. That makes published +# tarballs carry 1985 dates, which confuses users and tooling. +# +# This script: +# 1. touches every file in npm/ so mtimes reflect the build time +# 2. sets SOURCE_DATE_EPOCH to `now` so pacote's normalization uses it +# 3. runs `npm pack`, producing magic-vlsi-wasm-.tgz in npm/ +# +# Used by: +# - npm/build.sh --pack (local build) +# - .github/workflows/main.yml (CI artifact upload) +# - .github/workflows/npm-publish.yml (tag-triggered publish) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +find . -exec touch {} + +SOURCE_DATE_EPOCH=$(date +%s) npm pack diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 00000000..0b43ce76 --- /dev/null +++ b/npm/package.json @@ -0,0 +1,36 @@ +{ + "name": "magic-vlsi-wasm", + "version": "8.3.637", + "description": "Magic VLSI Layout Tool — headless WebAssembly build", + "type": "module", + "main": "index.js", + "types": "index.d.ts", + "exports": { + ".": { + "import": "./index.js", + "types": "./index.d.ts" + } + }, + "files": [ + "index.js", + "index.d.ts", + "magic.js", + "magic.wasm", + "examples/", + "LICENSE", + "README.md" + ], + "scripts": { + "example": "node examples/extract.js", + "test": "node examples/all.js", + "test:gds": "node examples/gds.js", + "test:drc": "node examples/drc.js", + "test:cif": "node examples/cif.js" + }, + + "keywords": ["magic", "vlsi", "eda", "wasm", "webassembly", "layout"], + "license": "HPND", + "engines": { + "node": ">=18" + } +}