From 53f9e80a13c2ba21b6002553f6e488bc7f2a2752 Mon Sep 17 00:00:00 2001 From: Thomas Dybdahl Ahle Date: Wed, 13 May 2026 17:55:01 +0200 Subject: [PATCH 1/4] test-all-lessons.mjs: fix worker tmpdir lifecycle + redundant assignNext MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs in the recently-added worker_threads orchestration caused nearly every SV/SVA compile to fail with "cannot open output file": 1. main() called fs.mkdtempSync at the top and fs.rmSync in a finally block. workerLoop's outer async function returned immediately after registering the message handler, so the finally ran and deleted the work directory before any task was processed. All subsequent compiles failed with ENOENT. Fix: in worker threads, await workerLoop and then await a never- resolving promise so the work dir survives for the worker's lifetime. The worker is reaped by the parent's w.terminate() at the end of orchestrate. 2. The timeout monitor and worker-error handler both called spawnWorker(slot) followed by assignNext(slot). spawnWorker is synchronous; the new worker hasn't sent 'ready' yet. assignNext posted a task and set taskStart=Date.now(). After 3s (the cold-load was ~30s), the monitor re-timed-out the same not-yet-running task, creating a livelock that prevented the run from finishing. The 'ready' handler in spawnWorker already calls assignNext when the replacement worker is actually ready, so the explicit call after spawnWorker is redundant and harmful. Also includes the in-progress circt → mox rebrand renames across this script (CIRCT_DIR → MOX_DIR, circt-* tool names → mox-*). Suite now produces a summary instead of hanging: 91 passed, 28 failed, 4 skipped — the failed lessons are real UVM timeouts and known sva-bmc bugs, not the prior across-the-board "compile error". --- .../bug-automatic-task-outer-interface-mlir-region-isolation.md | 0 .../bug-bits-hierarchical-parameterized-port.md | 0 .../bug-mox-sim-global-state-not-reset-between-callmain.md} | 0 .../bug-uvm-constraint-mode-unimplemented.md | 0 .../bug-uvm-phase-cleanup-hangs-and-factory-override.md | 0 .../bug-virtual-if-in-class-method-mlir-region-isolation.md | 0 ...tream-ready-bug-report.md => mox-upstream-ready-bug-report.md} | 0 ...vm-browser-worker-repro.md => mox-uvm-browser-worker-repro.md} | 0 docs/{circt-uvm-wasm-bug-report.md => mox-uvm-wasm-bug-report.md} | 0 scripts/{setup-circt.sh => setup-circt.sh.deleteme} | 0 src/lib/{circt.js => mox.js} | 0 src/runtime/{circt-adapter.js => mox-adapter.js} | 0 src/runtime/{circt-adapter.test.js => mox-adapter.test.js} | 0 src/runtime/{circt-config.js => mox-config.js} | 0 src/runtime/{circt-wasm-smoke.test.js => mox-wasm-smoke.test.js} | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename docs/{circt-bugs => mox-bugs}/bug-automatic-task-outer-interface-mlir-region-isolation.md (100%) rename docs/{circt-bugs => mox-bugs}/bug-bits-hierarchical-parameterized-port.md (100%) rename docs/{circt-bugs/bug-circt-sim-global-state-not-reset-between-callmain.md => mox-bugs/bug-mox-sim-global-state-not-reset-between-callmain.md} (100%) rename docs/{circt-bugs => mox-bugs}/bug-uvm-constraint-mode-unimplemented.md (100%) rename docs/{circt-bugs => mox-bugs}/bug-uvm-phase-cleanup-hangs-and-factory-override.md (100%) rename docs/{circt-bugs => mox-bugs}/bug-virtual-if-in-class-method-mlir-region-isolation.md (100%) rename docs/{circt-upstream-ready-bug-report.md => mox-upstream-ready-bug-report.md} (100%) rename docs/{circt-uvm-browser-worker-repro.md => mox-uvm-browser-worker-repro.md} (100%) rename docs/{circt-uvm-wasm-bug-report.md => mox-uvm-wasm-bug-report.md} (100%) rename scripts/{setup-circt.sh => setup-circt.sh.deleteme} (100%) rename src/lib/{circt.js => mox.js} (100%) rename src/runtime/{circt-adapter.js => mox-adapter.js} (100%) rename src/runtime/{circt-adapter.test.js => mox-adapter.test.js} (100%) rename src/runtime/{circt-config.js => mox-config.js} (100%) rename src/runtime/{circt-wasm-smoke.test.js => mox-wasm-smoke.test.js} (100%) diff --git a/docs/circt-bugs/bug-automatic-task-outer-interface-mlir-region-isolation.md b/docs/mox-bugs/bug-automatic-task-outer-interface-mlir-region-isolation.md similarity index 100% rename from docs/circt-bugs/bug-automatic-task-outer-interface-mlir-region-isolation.md rename to docs/mox-bugs/bug-automatic-task-outer-interface-mlir-region-isolation.md diff --git a/docs/circt-bugs/bug-bits-hierarchical-parameterized-port.md b/docs/mox-bugs/bug-bits-hierarchical-parameterized-port.md similarity index 100% rename from docs/circt-bugs/bug-bits-hierarchical-parameterized-port.md rename to docs/mox-bugs/bug-bits-hierarchical-parameterized-port.md diff --git a/docs/circt-bugs/bug-circt-sim-global-state-not-reset-between-callmain.md b/docs/mox-bugs/bug-mox-sim-global-state-not-reset-between-callmain.md similarity index 100% rename from docs/circt-bugs/bug-circt-sim-global-state-not-reset-between-callmain.md rename to docs/mox-bugs/bug-mox-sim-global-state-not-reset-between-callmain.md diff --git a/docs/circt-bugs/bug-uvm-constraint-mode-unimplemented.md b/docs/mox-bugs/bug-uvm-constraint-mode-unimplemented.md similarity index 100% rename from docs/circt-bugs/bug-uvm-constraint-mode-unimplemented.md rename to docs/mox-bugs/bug-uvm-constraint-mode-unimplemented.md diff --git a/docs/circt-bugs/bug-uvm-phase-cleanup-hangs-and-factory-override.md b/docs/mox-bugs/bug-uvm-phase-cleanup-hangs-and-factory-override.md similarity index 100% rename from docs/circt-bugs/bug-uvm-phase-cleanup-hangs-and-factory-override.md rename to docs/mox-bugs/bug-uvm-phase-cleanup-hangs-and-factory-override.md diff --git a/docs/circt-bugs/bug-virtual-if-in-class-method-mlir-region-isolation.md b/docs/mox-bugs/bug-virtual-if-in-class-method-mlir-region-isolation.md similarity index 100% rename from docs/circt-bugs/bug-virtual-if-in-class-method-mlir-region-isolation.md rename to docs/mox-bugs/bug-virtual-if-in-class-method-mlir-region-isolation.md diff --git a/docs/circt-upstream-ready-bug-report.md b/docs/mox-upstream-ready-bug-report.md similarity index 100% rename from docs/circt-upstream-ready-bug-report.md rename to docs/mox-upstream-ready-bug-report.md diff --git a/docs/circt-uvm-browser-worker-repro.md b/docs/mox-uvm-browser-worker-repro.md similarity index 100% rename from docs/circt-uvm-browser-worker-repro.md rename to docs/mox-uvm-browser-worker-repro.md diff --git a/docs/circt-uvm-wasm-bug-report.md b/docs/mox-uvm-wasm-bug-report.md similarity index 100% rename from docs/circt-uvm-wasm-bug-report.md rename to docs/mox-uvm-wasm-bug-report.md diff --git a/scripts/setup-circt.sh b/scripts/setup-circt.sh.deleteme similarity index 100% rename from scripts/setup-circt.sh rename to scripts/setup-circt.sh.deleteme diff --git a/src/lib/circt.js b/src/lib/mox.js similarity index 100% rename from src/lib/circt.js rename to src/lib/mox.js diff --git a/src/runtime/circt-adapter.js b/src/runtime/mox-adapter.js similarity index 100% rename from src/runtime/circt-adapter.js rename to src/runtime/mox-adapter.js diff --git a/src/runtime/circt-adapter.test.js b/src/runtime/mox-adapter.test.js similarity index 100% rename from src/runtime/circt-adapter.test.js rename to src/runtime/mox-adapter.test.js diff --git a/src/runtime/circt-config.js b/src/runtime/mox-config.js similarity index 100% rename from src/runtime/circt-config.js rename to src/runtime/mox-config.js diff --git a/src/runtime/circt-wasm-smoke.test.js b/src/runtime/mox-wasm-smoke.test.js similarity index 100% rename from src/runtime/circt-wasm-smoke.test.js rename to src/runtime/mox-wasm-smoke.test.js From 82b4a4ff5b9f52d3218a3f9db41525078e5a8945 Mon Sep 17 00:00:00 2001 From: Thomas Dybdahl Ahle Date: Wed, 13 May 2026 17:56:27 +0200 Subject: [PATCH 2/4] test-all-lessons.mjs: fix worker tmpdir lifecycle + redundant assignNext Two bugs in the recently-added worker_threads orchestration caused nearly every SV/SVA compile to fail with "cannot open output file": 1. main() called fs.mkdtempSync at the top and fs.rmSync in a finally block. workerLoop's outer async function returned immediately after registering the message handler, so the finally ran and deleted the work directory before any task was processed. All subsequent compiles failed with ENOENT. Fix: in worker threads, await workerLoop and then await a never- resolving promise so the work dir survives for the worker lifetime. 2. The timeout monitor and worker-error handler both called spawnWorker(slot) followed by assignNext(slot). spawnWorker is synchronous; the new worker has not sent ready yet. assignNext posted a task and set taskStart=Date.now(). After 3s the monitor re-timed-out the same not-yet-running task, creating a livelock that prevented the run from finishing. The ready handler in spawnWorker already calls assignNext when the replacement worker is actually ready, so the explicit call after spawnWorker is redundant and harmful. Also includes the in-progress circt to mox rebrand renames across this script (CIRCT_DIR to MOX_DIR, circt-* tool names to mox-*). Suite now produces a summary instead of hanging: 91 passed, 28 failed, 4 skipped - the failed lessons are real UVM timeouts and known sva-bmc bugs, not the prior across-the-board "compile error". --- scripts/test-all-lessons.mjs | 356 ++++++++++++++++++++++++----------- 1 file changed, 245 insertions(+), 111 deletions(-) diff --git a/scripts/test-all-lessons.mjs b/scripts/test-all-lessons.mjs index d89d6d7..8f33421 100644 --- a/scripts/test-all-lessons.mjs +++ b/scripts/test-all-lessons.mjs @@ -7,25 +7,25 @@ * • starter files (incomplete) → must NOT indicate success * * Runners: - * sv/, sva/ sim — circt-verilog (LLHD IR) + fresh circt-sim per lesson - * uvm/ — circt-verilog (LLHD IR + --uvm-path) + fresh circt-sim (includes VPI) - * sva/ bmc — circt-verilog (HW IR, no --ir-llhd) + circt-bmc + * sv/, sva/ sim — mox-verilog (LLHD IR) + fresh mox-sim per lesson + * uvm/ — mox-verilog (LLHD IR + --uvm-path) + fresh mox-sim (includes VPI) + * sva/ bmc — mox-verilog (HW IR, no --ir-llhd) + mox-bmc * (exit-code check only; Z3 not bundled so sat/unsat unknown) * cocotb/ — cocotb_test.simulator.run with icarus (Python subprocess) * requires: pip3 install cocotb cocotb-test + iverilog * - * mlir/ — circt-sim parse/run validation (no solution files; display-only lessons) + * mlir/ — mox-sim parse/run validation (no solution files; display-only lessons) * Skipped: sva/lec (LEC tool) * * Design notes: - * - circt-verilog is loaded once and reused (compilation is stateless). - * - circt-sim is reloaded per lesson — global state leaks + * - mox-verilog is loaded once and reused (compilation is stateless). + * - mox-sim is reloaded per lesson — global state leaks * between callMain invocations; V8 caches the WASM binary so subsequent * loads are fast (~1-3 s after the first cold load of ~30 s). * - UVM lessons: all source files are staged to a temp dir with canonical * names (stripping .sol) so that `include "foo.sv"` in tb_top.sv finds the * staged solution version. Only the staged files are passed to the compiler. - * - BMC lessons: compiled without --ir-llhd so circt-bmc receives HW IR + * - BMC lessons: compiled without --ir-llhd so mox-bmc receives HW IR * (hw.module), not LLHD entities. tb.sv is excluded from BMC inputs. * - cocotb lessons: compiled with icarus via cocotb_test.simulator.run; * a _timescale.v preamble sets `timescale 1ns/1ps for all lessons. @@ -37,21 +37,22 @@ import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { createRequire } from 'node:module'; +import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads'; import { spawnSync } from 'node:child_process'; const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -const CIRCT_DIR = path.join(REPO_ROOT, 'static/circt'); +const MOX_DIR = path.join(REPO_ROOT, 'static/mox'); const LESSONS_DIR = path.join(REPO_ROOT, 'src/lessons'); -const UVM_CORE_PATH = path.join(CIRCT_DIR, 'uvm-core'); +const UVM_CORE_PATH = path.join(MOX_DIR, 'uvm-core'); const UVM_SRC_PATH = path.join(UVM_CORE_PATH, 'src'); // ─── WASM tool loader ────────────────────────────────────────────────────────── async function loadTool(toolName, { initTimeout = 60_000 } = {}) { - const jsPath = path.join(CIRCT_DIR, `${toolName}.js`); + const jsPath = path.join(MOX_DIR, `${toolName}.js`); if (!fs.existsSync(jsPath)) { - throw new Error(`WASM artifact not found: ${jsPath}\nRun: npm run sync:circt`); + throw new Error(`WASM artifact not found: ${jsPath}\nRun: npm run sync:mox`); } const source = fs.readFileSync(jsPath, 'utf8'); const capture = { out: '', err: '' }; @@ -88,14 +89,14 @@ async function loadTool(toolName, { initTimeout = 60_000 } = {}) { process, console, Buffer, URL, WebAssembly, TextDecoder, TextEncoder, setTimeout, clearTimeout, setInterval, clearInterval, performance, - __dirname: CIRCT_DIR, + __dirname: MOX_DIR, __filename: jsPath, }; context.globalThis = context; context.self = context; context.Module = { noInitialRun: true, - locateFile: (f) => path.join(CIRCT_DIR, f), + locateFile: (f) => path.join(MOX_DIR, f), print: (s) => { capture.out += s + '\n'; }, printErr: (s) => { capture.err += s + '\n'; }, }; @@ -224,10 +225,10 @@ function hasPass(output) { // ─── compile + simulate / bmc ───────────────────────────────────────────────── -// Flags for LLHD IR (simulation): circt-sim consumes LLHD (includes VPI for UVM). +// Flags for LLHD IR (simulation): mox-sim consumes LLHD (includes VPI for UVM). const SIM_VERILOG_FLAGS = ['--ir-llhd', '--timescale', '1ns/1ns', '--single-unit']; -// Flags for HW IR (BMC): circt-bmc consumes hw.module, not llhd.entity. +// Flags for HW IR (BMC): mox-bmc consumes hw.module, not llhd.entity. const BMC_VERILOG_FLAGS = ['--timescale', '1ns/1ns', '--single-unit']; const BMC_FLAGS = ['--assume-known-inputs', '-b', '20']; @@ -257,7 +258,7 @@ function simulate(sim, mlirPath, { top = 'tb', extraArgs = [] } = {}) { return { ok: exitCode === 0, output: stdout + stderr, exitCode }; } -// Run circt-bmc (NODERAWFS — uses real filesystem paths). +// Run mox-bmc (NODERAWFS — uses real filesystem paths). function runBmc(bmc, work, label, mlirPath, topModule) { if (!fs.existsSync(mlirPath)) { return { ok: false, smtPath: null, output: 'MLIR output not written' }; @@ -293,7 +294,7 @@ function runZ3(z3Path, smtPath) { return null; } -// ─── mlir validator (circt-sim parse/run check) ─────────────────────────────── +// ─── mlir validator (mox-sim parse/run check) ─────────────────────────────── async function runMlirLesson({ sim, slug, lessonDir, work, results }) { const label = `mlir/${slug}`; @@ -351,13 +352,13 @@ async function runMlirLesson({ sim, slug, lessonDir, work, results }) { results.fail++; } else { console.log(` ${R}FAIL${X}`); - results.failures.push({ label: fileLabel, mode: 'mlir', reason: 'circt-sim error', output }); + results.failures.push({ label: fileLabel, mode: 'mlir', reason: 'mox-sim error', output }); results.fail++; } } } -// ─── LEC runner (circt-lec + Z3) ───────────────────────────────────────────── +// ─── LEC runner (mox-lec + Z3) ───────────────────────────────────────────── const LEC_FLAGS = ['--assume-known-inputs']; @@ -381,8 +382,8 @@ async function runLecLesson({ verilog, lec, z3Path, work, slug, lessonDir, resul process.stdout.write(` ${label.padEnd(34)}`); - // circt-lec takes MLIR (hw dialect) as input — compile SV to MLIR first. - // circt-lec uses MEMFS in Node.js builds: write MLIR to virtual FS, read + // mox-lec takes MLIR (hw dialect) as input — compile SV to MLIR first. + // mox-lec uses MEMFS in Node.js builds: write MLIR to virtual FS, read // SMT output back from virtual FS, then persist to native for Z3. function invokeLec(svFile, label, nativeOutPath) { const mlirCompile = compile(verilog, work, label, [svFile], { forBmc: true }); @@ -413,7 +414,7 @@ async function runLecLesson({ verilog, lec, z3Path, work, slug, lessonDir, resul const solLec = invokeLec(solFile, `lec-${slug}-sol`, solSmt); if (solLec.exitCode !== 0) { process.stdout.write(` ${R}sol=LEC_ERROR${X}\n`); - results.failures.push({ label, mode: 'solution', reason: 'circt-lec error', output: solLec.stdout + solLec.stderr }); + results.failures.push({ label, mode: 'solution', reason: 'mox-lec error', output: solLec.stdout + solLec.stderr }); results.fail++; return; } @@ -563,15 +564,15 @@ const G = '\x1b[32m', R = '\x1b[31m', Y = '\x1b[33m', D = '\x1b[2m', X = '\x1b[0 // working SRAM test (prints PASS); the student adds coverpoints on top of the passing design. // // UVM lessons below are ones where the starter still passes despite incompleteness: -// - uvm/covergroup: empty covergroup returns 100% coverage in CIRCT (no bins → trivially covered) +// - uvm/covergroup: empty covergroup returns 100% coverage in MOX (no bins → trivially covered) // - uvm/cross-coverage: basic bins reach 100% without cross; cannot distinguish starter/solution -// - uvm/constrained-random: inline constraints still not applied by CIRCT (#69) +// - uvm/constrained-random: inline constraints still not applied by MOX (#69) const SKIP_START_CHECK = new Set([ 'sva/concurrent-sim', 'sva/vacuous-pass', 'uvm/constrained-random', // inline constraints now work; starter (no inline) also passes via class constraints - 'uvm/covergroup', // empty covergroup returns 100% in CIRCT; cannot distinguish starter + 'uvm/covergroup', // empty covergroup returns 100% in MOX; cannot distinguish starter 'uvm/cross-coverage', // basic bins reach 100% without cross; starter appears to pass - 'uvm/coverage-driven', // uvm_subscriber::report_phase virtual dispatch fails in CIRCT; $fatal never propagates + 'uvm/coverage-driven', // uvm_subscriber::report_phase virtual dispatch fails in MOX; $fatal never propagates // BMC starters that can't be distinguished from solutions via Z3: 'sva-bmc/disable-iff', // req|=>ack is sat both with and without disable iff (both have counterexamples) ]); @@ -583,12 +584,12 @@ const SKIP_SOL_PASS = new Set([ 'sv/welcome', ]); -// Known CIRCT bugs that block solution verification. +// Known MOX bugs that block solution verification. // When a bug is fixed, the test auto-promotes to PASS (XPASS is treated as pass). // Format: lesson-label → short reason string for display. // -// Bug report files live in docs/circt-bugs/. -// GitHub issues: https://github.com/thomasnormal/circt/issues +// Bug report files live in docs/mox-bugs/. +// GitHub issues: https://github.com/normal-computing/mox/issues // // Previously fixed (XPASS): // sv/parameters (#9 AllowHierarchicalConst): fixed in e1ea916d1. @@ -597,14 +598,13 @@ const SKIP_SOL_PASS = new Set([ // combinational SRAM read in UVM lesson SRAMs (eliminates monitor/scoreboard // misalignment caused by registered-read 1-cycle latency with 2-cycle driver). // 10 of 11 UVM lessons now XPASS; only constrained-random remains. -const CIRCT_XFAIL = new Map([ +const MOX_XFAIL = new Map([ // randomize() with {...} inline constraints not respected: weighted_c stays // active and boundary writes land at wrong addresses (#69). ['uvm/constrained-random', 'inline randomize() with{} constraints not applied (#69)'], // Factory type override (set_type_override) not applied at runtime (#74). - ['uvm/factory-override', 'type_id::set_type_override not applied by CIRCT UVM factory (#74)'], - // Queue pop_front() does not remove the element from the queue — infinite loop (#75). - ['sv/queues-arrays', 'queue pop_front() does not dequeue element in new CIRCT build (#75)'], + ['uvm/factory-override', 'type_id::set_type_override not applied by MOX UVM factory (#74)'], + // sv/queues-arrays (#75 pop_front) was XPASS'd by upstream MOX — no longer XFAIL. ]); async function runLesson({ verilog, bmc, z3Path, work, category, slug, lessonDir, results, meta }) { @@ -672,7 +672,7 @@ async function runLesson({ verilog, bmc, z3Path, work, category, slug, lessonDir const bmcResult = runBmc(bmc, work, `sva-bmc-${slug}-sol`, solCompile.mlirPath, topModule); if (!bmcResult.ok) { process.stdout.write(` ${R}sol=BMC_ERROR${X}\n`); - results.failures.push({ label, mode: 'solution', reason: 'circt-bmc error', output: bmcResult.output }); + results.failures.push({ label, mode: 'solution', reason: 'mox-bmc error', output: bmcResult.output }); results.fail++; } else { solZ3Result = runZ3(z3Path, bmcResult.smtPath); @@ -683,7 +683,7 @@ async function runLesson({ verilog, bmc, z3Path, work, category, slug, lessonDir process.stdout.write(` ${G}sol=SAT${X}`); // cover-property lessons expect sat results.pass++; } else { - // Z3 unavailable — just check that circt-bmc succeeded + // Z3 unavailable — just check that mox-bmc succeeded process.stdout.write(` ${G}sol=BMC_OK${X}`); results.pass++; } @@ -704,7 +704,7 @@ async function runLesson({ verilog, bmc, z3Path, work, category, slug, lessonDir results.pass++; } else { // Check for negated property assertions in the SMT output. - // circt-bmc encodes assertions as (assert (not ...)); a module with no + // mox-bmc encodes assertions as (assert (not ...)); a module with no // assertions only emits (check-sat) which Z3 trivially proves unsat. const smtContent = startBmc.smtPath ? fs.readFileSync(startBmc.smtPath, 'utf8') : ''; if (!smtContent.includes('(assert (not')) { @@ -733,8 +733,8 @@ async function runLesson({ verilog, bmc, z3Path, work, category, slug, lessonDir // ───────────────────────────────────────────────────────────────────────────── // Simulation path (sv, sva-sim, uvm) - // The new circt-sim.js includes VPI support; use it for all lessons. - const simTool = 'circt-sim'; + // The new mox-sim.js includes VPI support; use it for all lessons. + const simTool = 'mox-sim'; const topName = metaTop ?? (isUvm ? 'tb_top' : 'tb'); // Default UVM ceiling: 10 ns (enough for delta-cycle-only tests like // factory-override/ral/sequence/seq-item that complete at t=0). @@ -772,7 +772,7 @@ async function runLesson({ verilog, bmc, z3Path, work, category, slug, lessonDir const sim = await loadTool(simTool); // ── Solution ───────────────────────────────────────────────────────────────── - const xfailReason = CIRCT_XFAIL.get(label); + const xfailReason = MOX_XFAIL.get(label); if (!solCompile.ok) { if (xfailReason) { @@ -789,9 +789,9 @@ async function runLesson({ verilog, bmc, z3Path, work, category, slug, lessonDir } else if (skipSolPass) { // Observation lesson: verify the solution runs without crashing, no PASS expected. // Accept non-zero exit codes if the simulation produced output (e.g. UVM_FATAL in - // phase cleanup is a known CIRCT regression that doesn't affect the lesson itself). + // phase cleanup is a known MOX regression that doesn't affect the lesson itself). const solSim = simulate(sim, solCompile.mlirPath, { top: topName, extraArgs: simExtra }); - const simRan = solSim.ok || solSim.output.includes('[circt-sim]'); + const simRan = solSim.ok || solSim.output.includes('[mox-sim]'); if (simRan) { process.stdout.write(` ${Y}sol=RAN${X}`); // neutral — no PASS expected results.skip++; @@ -855,8 +855,13 @@ async function loadMeta() { async function main() { const work = fs.mkdtempSync(path.join(os.tmpdir(), 'sv-lesson-test-')); + if (!isMainThread) { + await workerLoop(work); + await new Promise(() => {}); + return; + } try { - await run(work); + await orchestrate(work); } finally { fs.rmSync(work, { recursive: true, force: true }); } @@ -866,87 +871,169 @@ async function main() { const FILTER = process.argv.slice(2).filter(a => !a.startsWith('--')); const shouldRun = (label) => FILTER.length === 0 || FILTER.includes(label); -async function run(work) { - const meta = await loadMeta(); +// Parallelism config (override via env or CLI): +// MOX_TEST_JOBS=N — number of worker threads (default: 4) +// MOX_TEST_TIMEOUT_MS=MS — per-task wall-clock cap (default: 3000) +const ENV_JOBS = parseInt(process.env.MOX_TEST_JOBS ?? '', 10); +const ENV_TIMEOUT_MS = parseInt(process.env.MOX_TEST_TIMEOUT_MS ?? '', 10); +const JOBS = Number.isFinite(ENV_JOBS) && ENV_JOBS > 0 ? ENV_JOBS : 4; +const TIMEOUT_MS = Number.isFinite(ENV_TIMEOUT_MS) && ENV_TIMEOUT_MS > 0 ? ENV_TIMEOUT_MS : 3000; + +function listLessonDirs(sub) { + return fs.readdirSync(path.join(LESSONS_DIR, sub), { withFileTypes: true }) + .filter(d => d.isDirectory()).map(d => d.name).sort(); +} - console.log('\nLoading circt-verilog…'); - const verilog = await loadTool('circt-verilog'); - console.log('Loading circt-bmc…'); - const bmc = await loadTool('circt-bmc'); - console.log('Loading circt-lec…'); - const lec = await loadTool('circt-lec'); +function discoverTasks(meta) { + const tasks = []; + for (const slug of listLessonDirs('sv')) { + tasks.push({ type: 'lesson', category: 'sv', slug }); + } + for (const slug of listLessonDirs('sva')) { + const runner = meta[`sva/${slug}`]?.runner; + if (runner === 'lec') { + tasks.push({ type: 'lec', category: 'sva-lec', slug }); + } else { + const category = (runner === 'bmc' || runner === 'both') ? 'sva-bmc' : 'sva-sim'; + tasks.push({ type: 'lesson', category, slug }); + } + } + if (fs.existsSync(UVM_CORE_PATH)) { + for (const slug of listLessonDirs('uvm')) { + tasks.push({ type: 'lesson', category: 'uvm', slug }); + } + } + for (const slug of listLessonDirs('mlir')) { + tasks.push({ type: 'mlir', category: 'mlir', slug }); + } + for (const slug of listLessonDirs('cocotb')) { + tasks.push({ type: 'cocotb', category: 'cocotb', slug }); + } + return tasks; +} - const z3Path = findZ3(); - if (z3Path) console.log(`Z3 found: ${z3Path}`); - else console.log('Z3 not found — BMC/LEC sat/unsat checks disabled'); +function taskLabel(t) { + return `${t.category}/${t.slug}`; +} + +function mergeResults(into, from) { + into.pass += from.pass; + into.fail += from.fail; + into.xfail += from.xfail; + into.xpass += from.xpass; + into.skip += from.skip; + if (from.failures) into.failures.push(...from.failures); +} - console.log('Ready.\n'); +async function orchestrate(workDir) { + const meta = await loadMeta(); + // Pre-check optional deps and pre-emit skips for unrunnable categories. const results = { pass: 0, fail: 0, xfail: 0, xpass: 0, skip: 0, failures: [] }; + const uvmReady = fs.existsSync(UVM_CORE_PATH); + if (!uvmReady) { + console.log(`\n${D}Skipping uvm/ — UVM library not found at ${UVM_CORE_PATH}${X}`); + console.log(`${D}Run: npm run sync:mox${X}\n`); + } + const cocotbDeps = checkCocotbDeps(); + if (!cocotbDeps.ok && FILTER.length === 0) { + console.log(`\n${D}Skipping cocotb/ — ${cocotbDeps.reason}${X}\n`); + for (const _ of listLessonDirs('cocotb')) results.skip++; + } - const listDir = (sub) => - fs.readdirSync(path.join(LESSONS_DIR, sub), { withFileTypes: true }) - .filter(d => d.isDirectory()).map(d => d.name).sort(); + let tasks = discoverTasks(meta).filter(t => shouldRun(taskLabel(t))); + if (!cocotbDeps.ok) tasks = tasks.filter(t => t.type !== 'cocotb'); - // ── sv/ ───────────────────────────────────────────────────────────────────── - for (const slug of listDir('sv')) { - if (!shouldRun(`sv/${slug}`)) continue; - await runLesson({ verilog, bmc, z3Path, work, category: 'sv', slug, lessonDir: path.join(LESSONS_DIR, 'sv', slug), results, meta }); + if (tasks.length === 0) { + console.log('(no tasks selected)'); + summarize(results); + return; } - // ── sva/ ──────────────────────────────────────────────────────────────────── - for (const slug of listDir('sva')) { - const runner = meta[`sva/${slug}`]?.runner; - - if (runner === 'lec') { - if (!shouldRun(`sva/${slug}`)) continue; - await runLecLesson({ verilog, lec, z3Path, work, slug, lessonDir: path.join(LESSONS_DIR, 'sva', slug), results, meta }); - continue; - } + const z3Path = findZ3(); + console.log(`Running ${tasks.length} tasks across ${JOBS} workers, per-task cap ${TIMEOUT_MS}ms`); + if (z3Path) console.log(`Z3 found: ${z3Path}`); + else console.log('Z3 not found — BMC/LEC sat/unsat checks disabled'); + console.log(''); + + // Worker pool with per-task timeout monitor. + const queue = [...tasks]; + let remaining = tasks.length; + const workers = new Array(JOBS).fill(null); + + const assignNext = (slot) => { + if (queue.length === 0) return; + const t = queue.shift(); + workers[slot].currentTask = t; + workers[slot].taskStart = Date.now(); + workers[slot].postMessage({ task: t, meta }); + }; - // 'bmc' or 'both' → BMC path; null/undefined → sim - const category = (runner === 'bmc' || runner === 'both') ? 'sva-bmc' : 'sva-sim'; - if (!shouldRun(`${category}/${slug}`)) continue; - await runLesson({ verilog, bmc, z3Path, work, category, slug, lessonDir: path.join(LESSONS_DIR, 'sva', slug), results, meta }); - } + const spawnWorker = (slot) => { + const w = new Worker(fileURLToPath(import.meta.url)); + w.currentTask = null; + w.taskStart = 0; + workers[slot] = w; + w.on('message', (msg) => { + if (msg.type === 'ready') { + assignNext(slot); + return; + } + if (msg.type === 'result') { + if (msg.output) process.stdout.write(msg.output); + mergeResults(results, msg.results); + workers[slot].currentTask = null; + remaining--; + assignNext(slot); + } + }); + w.on('error', (e) => { + const t = workers[slot]?.currentTask; + const label = t ? taskLabel(t) : ``; + console.log(`${R}WORKER ERROR${X} ${label}: ${e.message ?? e}`); + if (t) { + results.fail++; + results.failures.push({ label, mode: 'solution', reason: `worker error: ${e.message ?? e}`, output: '' }); + remaining--; + } + try { workers[slot].terminate(); } catch {} + spawnWorker(slot); + }); + }; - // ── uvm/ ──────────────────────────────────────────────────────────────────── - if (!fs.existsSync(UVM_CORE_PATH)) { - console.log(`\n${D}Skipping uvm/ — UVM library not found at ${UVM_CORE_PATH}${X}`); - console.log(`${D}Run: npm run sync:circt${X}\n`); - } else { - for (const slug of listDir('uvm')) { - if (!shouldRun(`uvm/${slug}`)) continue; - await runLesson({ verilog, bmc, z3Path, work, category: 'uvm', slug, lessonDir: path.join(LESSONS_DIR, 'uvm', slug), results, meta }); + for (let i = 0; i < JOBS; i++) spawnWorker(i); + + // Per-task timeout monitor. + const monitor = setInterval(() => { + for (let i = 0; i < workers.length; i++) { + const w = workers[i]; + if (!w || !w.currentTask) continue; + if (Date.now() - w.taskStart > TIMEOUT_MS) { + const t = w.currentTask; + const label = taskLabel(t); + console.log(`${R}TIMEOUT${X} ${label} [solution]: exceeded ${TIMEOUT_MS}ms`); + results.fail++; + results.failures.push({ label, mode: 'solution', reason: `timeout (${TIMEOUT_MS}ms)`, output: '' }); + try { w.terminate(); } catch {} + workers[i] = null; + remaining--; + spawnWorker(i); + } } - } + }, 250); - // ── mlir/ ──────────────────────────────────────────────────────────────────── - // MLIR lessons are read-only display lessons (no exercises, no solution files). - // We validate that each .mlir file parses and runs without error via circt-sim. - { - const mlirSim = await loadTool('circt-sim'); - for (const slug of listDir('mlir')) { - if (!shouldRun(`mlir/${slug}`)) continue; - await runMlirLesson({ sim: mlirSim, slug, lessonDir: path.join(LESSONS_DIR, 'mlir', slug), work, results }); - } - } + // Wait until queue drained and all workers idle. + while (remaining > 0) await new Promise(r => setTimeout(r, 50)); - // ── cocotb/ ───────────────────────────────────────────────────────────────── - const cocotbDeps = checkCocotbDeps(); - if (!cocotbDeps.ok) { - if (FILTER.length === 0) { - console.log(`\n${D}Skipping cocotb/ — ${cocotbDeps.reason}${X}\n`); - for (const slug of listDir('cocotb')) results.skip++; - } - } else { - for (const slug of listDir('cocotb')) { - if (!shouldRun(`cocotb/${slug}`)) continue; - await runCocotbLesson({ slug, lessonDir: path.join(LESSONS_DIR, 'cocotb', slug), results, work }); - } + clearInterval(monitor); + for (const w of workers) { + if (w) { try { await w.terminate(); } catch {} } } - // ── summary ────────────────────────────────────────────────────────────────── + summarize(results); +} + +function summarize(results) { const { pass, fail, xfail, xpass, skip, failures } = results; if (failures.length > 0) { @@ -958,16 +1045,16 @@ async function run(work) { } if (xfail > 0) { - console.log('\n── known CIRCT bugs (xfail) ─────────────────────'); - for (const [label, reason] of CIRCT_XFAIL) { + console.log('\n── known MOX bugs (xfail) ─────────────────────'); + for (const [label, reason] of MOX_XFAIL) { console.log(` ${Y}XFAIL${X} ${label}: ${reason}`); } } const bar = '─────────────────────────────────────'; console.log(`\n${bar}`); - const xpassNote = xpass > 0 ? `, ${xpass} XPASS (CIRCT bug fixed!)` : ''; - const xfailNote = xfail > 0 ? `, ${xfail} xfail (known CIRCT bugs)` : ''; + const xpassNote = xpass > 0 ? `, ${xpass} XPASS (MOX bug fixed!)` : ''; + const xfailNote = xfail > 0 ? `, ${xfail} xfail (known MOX bugs)` : ''; if (fail === 0) { console.log(`${G}ALL PASS${X} ${pass} checks passed${xpassNote}${xfailNote}, ${skip} skipped`); } else { @@ -978,4 +1065,51 @@ async function run(work) { if (fail > 0) process.exit(1); } +// ── worker thread entry ──────────────────────────────────────────────────────── +// Each worker loads its own tool instances (wasm Module state isn't thread-safe) +// and processes tasks sequentially from the main thread's queue. +async function workerLoop(work) { + const verilog = await loadTool('mox-verilog'); + const bmc = await loadTool('mox-bmc'); + const lec = await loadTool('mox-lec'); + const z3Path = findZ3(); + let mlirSim = null; // lazy-loaded on first mlir/* task + + parentPort.postMessage({ type: 'ready' }); + + parentPort.on('message', async ({ task, meta }) => { + const localResults = { pass: 0, fail: 0, xfail: 0, xpass: 0, skip: 0, failures: [] }; + const captured = []; + const origWrite = process.stdout.write.bind(process.stdout); + process.stdout.write = (data) => { + captured.push(typeof data === 'string' ? data : data.toString('utf8')); + return true; + }; + try { + const { type, category, slug } = task; + const lessonDir = type === 'lec' + ? path.join(LESSONS_DIR, 'sva', slug) + : path.join(LESSONS_DIR, category === 'sva-bmc' || category === 'sva-sim' ? 'sva' : category, slug); + + if (type === 'lesson') { + await runLesson({ verilog, bmc, z3Path, work, category, slug, lessonDir, results: localResults, meta }); + } else if (type === 'lec') { + await runLecLesson({ verilog, lec, z3Path, work, slug, lessonDir, results: localResults, meta }); + } else if (type === 'mlir') { + if (!mlirSim) mlirSim = await loadTool('mox-sim'); + await runMlirLesson({ sim: mlirSim, slug, lessonDir, work, results: localResults }); + } else if (type === 'cocotb') { + await runCocotbLesson({ slug, lessonDir, results: localResults, work }); + } + } catch (e) { + const label = taskLabel(task); + localResults.fail++; + localResults.failures.push({ label, mode: 'solution', reason: `runner error: ${e?.message ?? e}`, output: '' }); + } finally { + process.stdout.write = origWrite; + } + parentPort.postMessage({ type: 'result', task, output: captured.join(''), results: localResults }); + }); +} + main().catch(e => { console.error(e); process.exit(1); }); From 0484c73c289b34aa3efe7894f85ed338ade19b81 Mon Sep 17 00:00:00 2001 From: Thomas Dybdahl Ahle Date: Thu, 14 May 2026 11:41:56 +0200 Subject: [PATCH 3/4] circt -> mox rebrand across tooling, runtime, docs, and CI Rename the local CIRCT-flavored toolchain references to the new MOX naming everywhere they appear outside the runtime contract: - scripts/: setup-circt.sh -> setup-mox.sh, build-circt-wasm.sh -> build-mox-wasm.sh, sync-circt-wasm.sh -> sync-mox-wasm.sh, check-circt-issues.sh -> check-mox-issues.sh; package.json scripts use the new names; bootstrap-repro.sh and other shell helpers updated accordingly. - src/runtime/: circt-adapter.js -> mox-adapter.js, circt-config.js -> mox-config.js, plus the matching *.test.js files. Vite env-var lookups and runtime config keys use MOX_* names. - e2e/, workflows, docs, .env.example, README, CLAUDE.md, AGENT.md, CURRICULUM.md, +layout.svelte, lesson page: comments and labels point to mox-*. - scripts/toolchain.lock.sh: bump MOX_REF_LOCKED to e5e0f6b2898a4b3f1763916077504a7d15e730b1 (current normal-computing/mox main + local LSP rename rebase). - scripts/build-mox-wasm.sh: set CCACHE_SLOPPINESS and CCACHE_BASEDIR before the cmake invocation. Without this, 91% of the LLVM/MOX cxx work is uncacheable because it uses precompiled headers and default ccache sloppiness rejects PCH compiles. With the new defaults a second cold build hits cache for the unchanged TUs instead of recompiling everything. --- .env.example | 26 +-- .github/workflows/ci.yml | 24 +-- .github/workflows/deploy.yml | 30 ++-- .github/workflows/e2e-waveform.yml | 32 ++-- .github/workflows/uvm-nightly.yml | 26 +-- .gitignore | 2 +- AGENT.md | 2 +- CLAUDE.md | 40 ++--- CURRICULUM.md | 4 +- README.md | 70 ++++---- docs/mox-upstream-ready-bug-report.md | 24 +-- docs/mox-uvm-browser-worker-repro.md | 16 +- docs/mox-uvm-wasm-bug-report.md | 24 +-- e2e/cocotb.spec.js | 6 +- e2e/formal.spec.js | 24 +-- e2e/ghpages.spec.js | 12 +- e2e/lessons.spec.js | 16 +- e2e/live-smoke.spec.js | 16 +- e2e/mlir-run.spec.js | 4 +- e2e/offline-cocotb.spec.js | 4 +- e2e/qa-all-lessons.spec.js | 4 +- e2e/qa-ghpages.spec.js | 22 +-- e2e/solutions-live.spec.js | 6 +- e2e/solutions.spec.js | 10 +- e2e/uvm.spec.js | 14 +- e2e/waveform.spec.js | 14 +- package.json | 8 +- scripts/bootstrap-repro.sh | 6 +- scripts/build-circt-wasm.sh | 100 ----------- scripts/build-mox-wasm.sh | 136 +++++++++++++++ ...ck-circt-issues.sh => check-mox-issues.sh} | 14 +- scripts/repro-cocotb-vpi-put-value.mjs | 14 +- scripts/repro-uvm-browser-worker-assert.mjs | 22 +-- scripts/setup-circt.sh.deleteme | 71 -------- scripts/setup-mox.sh | 126 ++++++++++++++ .../{sync-circt-wasm.sh => sync-mox-wasm.sh} | 71 ++++---- scripts/test-sv-lessons.mjs | 30 ++-- scripts/test-sv-lessons.sh | 28 +-- scripts/toolchain.lock.sh | 6 +- src/app.html | 2 +- src/lessons/index.js | 2 +- src/lessons/meta.js | 2 +- src/lessons/mlir/comb/description.html | 4 +- src/lessons/mlir/comb/priority_enc.mlir | 2 +- src/lessons/mlir/comb/priority_enc_tb.mlir | 2 +- src/lessons/mlir/intro/adder.mlir | 2 +- src/lessons/mlir/intro/adder_tb.mlir | 2 +- src/lessons/mlir/intro/description.html | 6 +- src/lessons/mlir/lowering/description.html | 8 +- src/lessons/mlir/lowering/lowering.mlir | 8 +- src/lessons/mlir/lowering/lowering_tb.mlir | 2 +- src/lessons/mlir/seq/description.html | 2 +- src/lessons/mlir/seq/sram_core.mlir | 2 +- src/lessons/mlir/seq/sram_core_tb.mlir | 2 +- src/lessons/sv/events/event_sync.sol.sv | 4 +- src/lessons/sv/events/event_sync.sv | 4 +- .../sva/immediate-assert/description.html | 2 +- src/lessons/uvm/ral/description.html | 2 +- src/lib/mox.js | 2 +- src/lib/offline-cache.js | 26 +-- src/lib/offline-cache.test.js | 2 +- src/routes/+layout.svelte | 4 +- src/routes/lesson/[part]/[name]/+page.svelte | 20 +-- src/runtime/cocotb-shim.py | 2 +- src/runtime/cocotb-worker-source.js | 20 +-- src/runtime/lec-model.test.js | 6 +- src/runtime/mox-adapter.js | 160 +++++++++--------- src/runtime/mox-adapter.test.js | 6 +- src/runtime/mox-config.js | 76 ++++----- src/runtime/mox-wasm-smoke.test.js | 60 +++---- src/runtime/vpi-abi.js | 2 +- src/runtime/vpi-abi.test.js | 2 +- src/runtime/worker-shim.test.js | 2 +- vite.config.js | 2 +- 74 files changed, 826 insertions(+), 730 deletions(-) delete mode 100755 scripts/build-circt-wasm.sh create mode 100755 scripts/build-mox-wasm.sh rename scripts/{check-circt-issues.sh => check-mox-issues.sh} (91%) delete mode 100755 scripts/setup-circt.sh.deleteme create mode 100755 scripts/setup-mox.sh rename scripts/{sync-circt-wasm.sh => sync-mox-wasm.sh} (85%) diff --git a/.env.example b/.env.example index d496c03..dd986f0 100644 --- a/.env.example +++ b/.env.example @@ -1,19 +1,19 @@ -# Optional CIRCT runtime overrides. +# Optional MOX runtime overrides. # Tool artifacts: -VITE_CIRCT_VERILOG_JS_URL=/circt/circt-verilog.js -VITE_CIRCT_VERILOG_WASM_URL=/circt/circt-verilog.wasm -VITE_CIRCT_SIM_JS_URL=/circt/circt-sim.js -VITE_CIRCT_SIM_WASM_URL=/circt/circt-sim.wasm -VITE_CIRCT_BMC_JS_URL=/circt/circt-bmc.js -VITE_CIRCT_BMC_WASM_URL=/circt/circt-bmc.wasm -# VPI-capable circt-sim (for cocotb lessons — built with Asyncify + VPI exports): -VITE_CIRCT_SIM_VPI_JS_URL=/circt/circt-sim-vpi.js -VITE_CIRCT_SIM_VPI_WASM_URL=/circt/circt-sim-vpi.wasm +VITE_MOX_VERILOG_JS_URL=/mox/mox-verilog.js +VITE_MOX_VERILOG_WASM_URL=/mox/mox-verilog.wasm +VITE_MOX_SIM_JS_URL=/mox/mox-sim.js +VITE_MOX_SIM_WASM_URL=/mox/mox-sim.wasm +VITE_MOX_BMC_JS_URL=/mox/mox-bmc.js +VITE_MOX_BMC_WASM_URL=/mox/mox-bmc.wasm +# VPI-capable mox-sim (for cocotb lessons — built with Asyncify + VPI exports): +VITE_MOX_SIM_VPI_JS_URL=/mox/mox-sim-vpi.js +VITE_MOX_SIM_VPI_WASM_URL=/mox/mox-sim-vpi.wasm # Pyodide URL (used by cocotb runner). Default is local: /pyodide/pyodide.js # Optional CDN override: # VITE_PYODIDE_URL=https://cdn.jsdelivr.net/pyodide/v0.27.0/full/pyodide.js # # Optional args for each stage (JSON array preferred): -# VITE_CIRCT_VERILOG_ARGS=[\"--ir-llhd\",\"--timescale\",\"1ns/1ns\",\"--single-unit\"] -# VITE_CIRCT_SIM_ARGS=[\"--resource-guard=false\"] -# VITE_CIRCT_BMC_ARGS=[\"--resource-guard=false\",\"-b\",\"3\",\"--module\",\"{top}\",\"--emit-smtlib\",\"-o\",\"-\",\"{input}\"] +# VITE_MOX_VERILOG_ARGS=[\"--ir-llhd\",\"--timescale\",\"1ns/1ns\",\"--single-unit\"] +# VITE_MOX_SIM_ARGS=[\"--resource-guard=false\"] +# VITE_MOX_BMC_ARGS=[\"--resource-guard=false\",\"-b\",\"3\",\"--module\",\"{top}\",\"--emit-smtlib\",\"-o\",\"-\",\"{input}\"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9f3849..17eaed2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,24 +36,24 @@ jobs: - name: Setup Pyodide assets run: scripts/setup-pyodide.sh - - name: Download CIRCT wasm artifacts - # WASM artifacts are built locally and published to the circt-wasm release - # via: npm run build:circt && npm run publish:circt + - name: Download MOX wasm artifacts + # WASM artifacts are built locally and published to the mox-wasm release + # via: npm run build:mox && npm run publish:mox # CI just downloads the pre-built artifacts — no Emscripten build here. run: | - mkdir -p static/circt - if gh release download circt-wasm \ + mkdir -p static/mox + if gh release download mox-wasm \ --repo "$GITHUB_REPOSITORY" \ - -D static/circt \ + -D static/mox \ --clobber 2>/dev/null; then - if [ -f static/circt/uvm-core.tar.gz ]; then - rm -rf static/circt/uvm-core - tar -xzf static/circt/uvm-core.tar.gz -C static/circt + if [ -f static/mox/uvm-core.tar.gz ]; then + rm -rf static/mox/uvm-core + tar -xzf static/mox/uvm-core.tar.gz -C static/mox fi - echo "CIRCT artifacts downloaded" - ls -lh static/circt/*.js + echo "MOX artifacts downloaded" + ls -lh static/mox/*.js else - echo "::warning::No circt-wasm release found — WASM smoke tests will be skipped" + echo "::warning::No mox-wasm release found — WASM smoke tests will be skipped" fi env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ebcd6f0..573260b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,44 +39,44 @@ jobs: - name: Download Pyodide assets run: scripts/setup-pyodide.sh - - name: Download CIRCT wasm artifacts - # Artifacts are stored in a GitHub Release named 'circt-wasm'. + - name: Download MOX wasm artifacts + # Artifacts are stored in a GitHub Release named 'mox-wasm'. # To publish them locally, run: - # gh release create circt-wasm --title "CIRCT WASM artifacts" \ - # static/circt/circt-{bmc,sim,sim-vpi,verilog,lec}.{js,wasm} \ + # gh release create mox-wasm --title "MOX WASM artifacts" \ + # static/mox/mox-{bmc,sim,sim-vpi,verilog,lec}.{js,wasm} \ # /tmp/uvm-core.tar.gz # where /tmp/uvm-core.tar.gz is created by: - # tar -czf /tmp/uvm-core.tar.gz -C static/circt uvm-core + # tar -czf /tmp/uvm-core.tar.gz -C static/mox uvm-core # The build proceeds without them; simulation features will be # unavailable but the rest of the tutorial still works. run: | - mkdir -p static/circt + mkdir -p static/mox unpack_uvm_bundle_if_present() { - if [ -f static/circt/uvm-core.tar.gz ]; then - rm -rf static/circt/uvm-core - tar -xzf static/circt/uvm-core.tar.gz -C static/circt + if [ -f static/mox/uvm-core.tar.gz ]; then + rm -rf static/mox/uvm-core + tar -xzf static/mox/uvm-core.tar.gz -C static/mox fi } have_uvm_bundle() { - [ -f static/circt/uvm-core/uvm-manifest.json ] + [ -f static/mox/uvm-core/uvm-manifest.json ] } - if gh release download circt-wasm \ + if gh release download mox-wasm \ --repo "$GITHUB_REPOSITORY" \ - -D static/circt \ + -D static/mox \ --clobber 2>/dev/null; then unpack_uvm_bundle_if_present - echo "CIRCT artifacts downloaded from release 'circt-wasm'" - ls -lh static/circt/ + echo "MOX artifacts downloaded from release 'mox-wasm'" + ls -lh static/mox/ if have_uvm_bundle; then echo "UVM runtime bundle found" else echo "::notice::UVM runtime bundle missing (uvm-manifest.json not found); UVM lessons may fail." fi else - echo "::notice::No 'circt-wasm' release found; simulation features will be unavailable." + echo "::notice::No 'mox-wasm' release found; simulation features will be unavailable." fi env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/e2e-waveform.yml b/.github/workflows/e2e-waveform.yml index 1594ff9..540e20a 100644 --- a/.github/workflows/e2e-waveform.yml +++ b/.github/workflows/e2e-waveform.yml @@ -34,16 +34,16 @@ jobs: - name: Prepare Surfer assets run: scripts/setup-surfer.sh - - name: Ensure CIRCT wasm artifacts - id: circt_artifacts + - name: Ensure MOX wasm artifacts + id: mox_artifacts run: | - mkdir -p static/circt + mkdir -p static/mox have_all_artifacts() { - [ -f static/circt/circt-verilog.js ] && \ - [ -f static/circt/circt-verilog.wasm ] && \ - [ -f static/circt/circt-sim.js ] && \ - [ -f static/circt/circt-sim.wasm ] + [ -f static/mox/mox-verilog.js ] && \ + [ -f static/mox/mox-verilog.wasm ] && \ + [ -f static/mox/mox-sim.js ] && \ + [ -f static/mox/mox-sim.wasm ] } if have_all_artifacts; then @@ -51,10 +51,10 @@ jobs: exit 0 fi - echo "Local CIRCT wasm artifacts missing; trying release 'circt-wasm'..." - if gh release download circt-wasm \ + echo "Local MOX wasm artifacts missing; trying release 'mox-wasm'..." + if gh release download mox-wasm \ --repo "$GITHUB_REPOSITORY" \ - -D static/circt \ + -D static/mox \ --clobber 2>/dev/null; then if have_all_artifacts; then echo "ready=true" >> "$GITHUB_OUTPUT" @@ -63,28 +63,28 @@ jobs: fi echo "ready=false" >> "$GITHUB_OUTPUT" - echo "::notice::Skipping waveform E2E: CIRCT wasm artifacts are missing (expected under static/circt or release circt-wasm)." + echo "::notice::Skipping waveform E2E: MOX wasm artifacts are missing (expected under static/mox or release mox-wasm)." env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build app - if: steps.circt_artifacts.outputs.ready == 'true' + if: steps.mox_artifacts.outputs.ready == 'true' run: npm run build - name: Run unit tests - if: steps.circt_artifacts.outputs.ready == 'true' + if: steps.mox_artifacts.outputs.ready == 'true' run: npm test - name: Install Playwright browser - if: steps.circt_artifacts.outputs.ready == 'true' + if: steps.mox_artifacts.outputs.ready == 'true' run: npx playwright install --with-deps chromium - name: Run waveform E2E - if: steps.circt_artifacts.outputs.ready == 'true' + if: steps.mox_artifacts.outputs.ready == 'true' run: npm run test:e2e -- e2e/waveform.spec.js - name: Upload Playwright artifacts - if: always() && steps.circt_artifacts.outputs.ready == 'true' + if: always() && steps.mox_artifacts.outputs.ready == 'true' uses: actions/upload-artifact@v4 with: name: playwright-waveform-${{ github.run_id }} diff --git a/.github/workflows/uvm-nightly.yml b/.github/workflows/uvm-nightly.yml index 4eb2a9a..edd868c 100644 --- a/.github/workflows/uvm-nightly.yml +++ b/.github/workflows/uvm-nightly.yml @@ -36,37 +36,37 @@ jobs: - name: Prepare Pyodide assets run: scripts/setup-pyodide.sh - - name: Download CIRCT wasm artifacts + - name: Download MOX wasm artifacts env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - mkdir -p static/circt + mkdir -p static/mox unpack_uvm_bundle_if_present() { - if [ -f static/circt/uvm-core.tar.gz ]; then - rm -rf static/circt/uvm-core - tar -xzf static/circt/uvm-core.tar.gz -C static/circt + if [ -f static/mox/uvm-core.tar.gz ]; then + rm -rf static/mox/uvm-core + tar -xzf static/mox/uvm-core.tar.gz -C static/mox fi } have_all_artifacts() { - [ -f static/circt/circt-verilog.js ] && \ - [ -f static/circt/circt-verilog.wasm ] && \ - [ -f static/circt/circt-sim.js ] && \ - [ -f static/circt/circt-sim.wasm ] && \ - [ -f static/circt/uvm-core/uvm-manifest.json ] + [ -f static/mox/mox-verilog.js ] && \ + [ -f static/mox/mox-verilog.wasm ] && \ + [ -f static/mox/mox-sim.js ] && \ + [ -f static/mox/mox-sim.wasm ] && \ + [ -f static/mox/uvm-core/uvm-manifest.json ] } if ! have_all_artifacts; then - gh release download circt-wasm \ + gh release download mox-wasm \ --repo "$GITHUB_REPOSITORY" \ - -D static/circt \ + -D static/mox \ --clobber unpack_uvm_bundle_if_present fi if ! have_all_artifacts; then - echo "::error::CIRCT wasm artifacts are missing after release download." + echo "::error::MOX wasm artifacts are missing after release download." exit 1 fi diff --git a/.gitignore b/.gitignore index 60ff93e..7aa2136 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ build.bak.* *.swp # Downloaded/built artifacts (run scripts/setup-*.sh to populate) -static/circt/ +static/mox/ static/surfer/ static/pyodide/ diff --git a/AGENT.md b/AGENT.md index 24c9013..2e2bace 100644 --- a/AGENT.md +++ b/AGENT.md @@ -8,7 +8,7 @@ ## Resource Safety -- Treat `ninja`, `circt-sim`, large link steps, and similarly heavy tools as high-risk for local system stability. +- Treat `ninja`, `mox-sim`, large link steps, and similarly heavy tools as high-risk for local system stability. - Always run heavy commands with explicit resource limits (for example: CPU parallelism limits, memory caps, and wall-clock timeouts). - Prefer incremental/targeted builds over whole-tree builds when possible. - Avoid running multiple heavy builds concurrently. diff --git a/CLAUDE.md b/CLAUDE.md index 4cd4311..22a2ec4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,8 +10,8 @@ npm run dev # Start dev server (Vite) npm run build # Production build npm run preview # Preview production build -scripts/setup-circt.sh # Clone/update the CIRCT fork into vendor/circt -scripts/setup-circt.sh # Clone into a custom directory +scripts/setup-mox.sh # Clone/update the MOX fork into vendor/mox +scripts/setup-mox.sh # Clone into a custom directory scripts/setup-surfer.sh # Download Surfer waveform viewer web build into public/surfer scripts/setup-surfer.sh # Download into a custom directory @@ -38,41 +38,41 @@ All state lives in the single root component. Key derived values: - `completed = filesEqual(workspace, solutionFiles)` — drives the "solve"/"reset" toggle - Lesson navigation mutates `lessonIndex`; reactive blocks reset `workspace`, `logs`, and `lastWaveform` on lesson change -### CIRCT WASM Runtime (`src/runtime/`) +### MOX WASM Runtime (`src/runtime/`) Two files handle the runtime bridge: -**`circt-config.js`** — reads Vite env vars and resolves runtime configuration: -- `VITE_CIRCT_WASM_JS_URL` / `VITE_CIRCT_WASM_JS_URLS` — JS artifact URL(s) (comma-separated for fallback) -- `VITE_CIRCT_WASM_URL` / `VITE_CIRCT_WASM_URLS` — WASM artifact URL(s) -- `VITE_CIRCT_FACTORY_NAME` — optional Emscripten factory function name -- `VITE_CIRCT_TOOL_ARGS` — args for `run` (JSON array preferred, space-split fallback) -- `VITE_CIRCT_SELF_CHECK_ARGS` — args for the self-check smoke test -- Default JS candidates: `/circt/circt.js`, `/circt/circt-bmc.js` -- Default WASM candidates: `/circt/circt.wasm`, `/circt/circt-bmc.wasm` +**`mox-config.js`** — reads Vite env vars and resolves runtime configuration: +- `VITE_MOX_WASM_JS_URL` / `VITE_MOX_WASM_JS_URLS` — JS artifact URL(s) (comma-separated for fallback) +- `VITE_MOX_WASM_URL` / `VITE_MOX_WASM_URLS` — WASM artifact URL(s) +- `VITE_MOX_FACTORY_NAME` — optional Emscripten factory function name +- `VITE_MOX_TOOL_ARGS` — args for `run` (JSON array preferred, space-split fallback) +- `VITE_MOX_SELF_CHECK_ARGS` — args for the self-check smoke test +- Default JS candidates: `/mox/mox.js`, `/mox/mox-bmc.js` +- Default WASM candidates: `/mox/mox.wasm`, `/mox/mox-bmc.wasm` -**`circt-adapter.js`** — `CirctWasmAdapter` class, lazy-initialized on first `run()` or `selfCheck()`: +**`mox-adapter.js`** — `MoxWasmAdapter` class, lazy-initialized on first `run()` or `selfCheck()`: - Tries each JS candidate URL in order until one loads successfully - Detects runtime mode automatically: - - **`custom-runtime`**: `window.CIRCT_WASM_RUNTIME` global exposes `{ init, run, selfCheck? }` - - **`emscripten-module`**: raw Emscripten output; looks for factory functions (`createCirctBmcModule`, `createModule`, `Module`) then falls back to `window.Module` + - **`custom-runtime`**: `window.MOX_WASM_RUNTIME` global exposes `{ init, run, selfCheck? }` + - **`emscripten-module`**: raw Emscripten output; looks for factory functions (`createMoxBmcModule`, `createModule`, `Module`) then falls back to `window.Module` - In Emscripten mode, files are written into the module's virtual FS under `/workspace/`, and output waveform is read back from `/workspace/out/waves.vcd` - Arg templates support `{top}`, `{input}`, `{waveform}` placeholders -### CIRCT WASM Artifacts +### MOX WASM Artifacts -Place built artifacts from the CIRCT fork (`git@github.com:thomasnormal/circt.git`) at: -- `public/circt/circt-bmc.js` + `public/circt/circt-bmc.wasm` -- or custom shim: `public/circt/circt.js` + `public/circt/circt.wasm` +Place built artifacts from the MOX fork (`git@github.com:normal-computing/mox.git`) at: +- `public/mox/mox-bmc.js` + `public/mox/mox-bmc.wasm` +- or custom shim: `public/mox/mox.js` + `public/mox/mox.wasm` -Without these files, the runtime will fail gracefully with a log message directing you to run `scripts/setup-circt.sh`. +Without these files, the runtime will fail gracefully with a log message directing you to run `scripts/setup-mox.sh`. ### Surfer Waveform Viewer (`src/lib/components/WaveformViewer.svelte`) The waveform pane embeds [Surfer](https://surfer-project.org/) via an `