···7272 - `asm/allocate.py` — IRAM offset and context slot allocation
7373 - `asm/codegen.py` — Code generation (direct mode + token stream mode)
7474 - `asm/serialize.py` — IRGraph to dfasm source serializer
7575+- `dfgraph/` — Interactive dataflow graph renderer (see `dfgraph/CLAUDE.md`)
7676+ - `dfgraph/__main__.py` — CLI: `python -m dfgraph path/to/file.dfasm [--port 8420]`
7777+ - `dfgraph/pipeline.py` — Progressive pipeline runner (parse -> lower -> resolve -> place -> allocate with error accumulation)
7878+ - `dfgraph/categories.py` — Opcode-to-category mapping via isinstance dispatch on ALUOp hierarchy
7979+ - `dfgraph/graph_json.py` — IRGraph-to-JSON conversion for frontend consumption
8080+ - `dfgraph/server.py` — FastAPI backend with WebSocket push and file watcher (watchdog, 300ms debounce)
8181+ - `dfgraph/frontend/` — TypeScript frontend: Cytoscape.js graph with ELK layout, SVG/PNG export
7582- `tests/` — pytest + hypothesis test suite
7683 - `tests/conftest.py` — Hypothesis strategies for token/op generation
7784- `docs/` — Design documents, implementation plans, test plans
···8188- Python 3.12
8289- SimPy 4.1 (discrete event simulation)
8390- Lark (Earley parser for dfasm grammar)
9191+- FastAPI + uvicorn (dfgraph web server)
9292+- watchdog (file system monitoring for dfgraph live reload)
9393+- Cytoscape.js + cytoscape-elk (frontend graph rendering and layout)
8494- pytest + hypothesis (property-based testing)
8595- Nix flake for dev environment
8696···185195186196### Module Dependency Graph
187197188188-`cm_inst.py` defines ISA enums and instruction types (no dependencies). `tokens.py` imports from `cm_inst.py` and defines the token hierarchy. `sm_mod.py` is independent. The `emu/` package imports from root-level modules but root-level modules never import from `emu/`. The `asm/` package imports from both root-level modules and `emu/types.py` (for PEConfig/SMConfig), but neither root-level modules nor `emu/` import from `asm/`.
198198+`cm_inst.py` defines ISA enums and instruction types (no dependencies). `tokens.py` imports from `cm_inst.py` and defines the token hierarchy. `sm_mod.py` is independent. The `emu/` package imports from root-level modules but root-level modules never import from `emu/`. The `asm/` package imports from both root-level modules and `emu/types.py` (for PEConfig/SMConfig), but neither root-level modules nor `emu/` import from `asm/`. The `dfgraph/` package imports from `cm_inst`, `asm/` (ir, lower, resolve, place, allocate, errors, opcodes), and internally between its own modules. Neither root-level modules, `emu/`, nor `asm/` import from `dfgraph/`.
189199190200```
191201cm_inst.py <-- tokens.py <-- emu/types.py
···200210 | | |
201211 v v v
202212asm/lower.py asm/resolve.py asm/allocate.py
203203- |
204204- asm/place.py
213213+ | |
214214+ | asm/place.py
215215+ | |
216216+ +--- dfgraph/pipeline.py ----------+
217217+ |
218218+ dfgraph/categories.py dfgraph/graph_json.py
219219+ (cm_inst) (asm/ir, asm/opcodes)
220220+ |
221221+ dfgraph/server.py
222222+ |
223223+ dfgraph/frontend/
205224```
206225207207-<!-- freshness: 2026-02-23 -->
226226+<!-- freshness: 2026-02-24 -->
+2-2
asm/CLAUDE.md
···2323## Dependencies
24242525- **Uses**: `cm_inst` (Port, MemOp, CfgOp, ALUOp, ALUInst, SMInst, Addr), `tokens` (MonadToken, SMToken, CfgToken, LoadInstToken, RouteSetToken), `sm_mod` (Presence), `emu/types` (PEConfig, SMConfig), `lark` (parser)
2626-- **Used by**: Test suite, user programs
2626+- **Used by**: Test suite, user programs, `dfgraph/` (pipeline, graph_json use ir, lower, resolve, place, allocate, errors, opcodes)
2727- **Boundary**: `emu/` and root-level modules must NEVER import from `asm/`
28282929## Key Decisions
···5252- `MemOp.WRITE` arity depends on const: monadic when const is set (cell_addr from const), dyadic when const is None (cell_addr from left operand)
5353- `RoutingOp.FREE_CTX` (ALU context deallocation) and `MemOp.FREE` (SM free) are disambiguated by mnemonic: assembler uses `free_ctx` for ALU and `free` for SM
54545555-<!-- freshness: 2026-02-23 -->
5555+<!-- freshness: 2026-02-24 -->
+52
dfgraph/CLAUDE.md
···11+# Dataflow Graph Renderer (dfgraph/)
22+33+Last verified: 2026-02-24
44+55+## Purpose
66+77+Interactive visualisation tool for dfasm dataflow programs. Renders the assembler's IR as a live-updating graph in the browser, enabling developers to see node placement, edge routing, and pipeline errors as they edit source files.
88+99+## Contracts
1010+1111+- **Exposes**: `python -m dfgraph path/to/file.dfasm [--port 8420]` launches a web server with WebSocket-driven graph updates
1212+- **Guarantees**: Progressive pipeline captures the deepest successful IRGraph even when later passes fail, so partial graphs are always shown. Parse errors are fatal (no graph); resolve/place/allocate errors accumulate in `IRGraph.errors` and are displayed alongside the partial graph. File changes trigger reassembly within 300ms debounce window.
1313+- **Expects**: Valid path to a `.dfasm` file. The `dfasm.lark` grammar file at project root. Node modules installed in `dfgraph/frontend/` for the TypeScript build.
1414+1515+## Dependencies
1616+1717+- **Uses**: `cm_inst` (ArithOp, LogicOp, RoutingOp, MemOp, CfgOp, Addr), `asm/` (ir, lower, resolve, place, allocate, errors, opcodes), `lark` (parser), `fastapi`/`uvicorn` (server), `watchdog` (file watcher), `cytoscape`/`cytoscape-elk` (frontend)
1818+- **Used by**: Developer tooling only (not imported by any other package)
1919+- **Boundary**: `cm_inst`, `asm/`, `emu/`, and root-level modules must NEVER import from `dfgraph/`
2020+2121+## Key Decisions
2222+2323+- **isinstance dispatch for categories** (`categories.py`): ALUOp subclasses (ArithOp, LogicOp, RoutingOp) share IntEnum values across types, so dict/set lookup would collide. isinstance checks on the type hierarchy avoid this.
2424+- **LogicOp split**: EQ/LT/LTE/GT/GTE map to COMPARISON; AND/OR/XOR/NOT map to LOGIC. This gives visually distinct colours for boolean-producing vs bitwise ops.
2525+- **CONST and FREE_CTX as CONFIG**: RoutingOp.CONST and FREE_CTX are categorised as CONFIG (not ROUTING) since they are infrastructure ops, not data-movement ops.
2626+- **ELK layout engine**: Uses Eclipse Layout Kernel (via cytoscape-elk) for layered DAG layout with orthogonal edge routing. Replaced dagre for better edge routing.
2727+- **Progressive pipeline**: Unlike `asm._run_pipeline()` which raises on error, `run_progressive()` runs each pass independently so the frontend can show partial graphs with error annotations.
2828+2929+## Invariants
3030+3131+- `dfgraph/` never imports from `emu/` -- it only needs the assembler IR, not the simulator
3232+- `categorise()` covers all ALUOp/MemOp/CfgOp values; raises `ValueError` on unknown types
3333+- `graph_to_json()` always returns a dict with `type: "graph_update"` -- even on parse failure (with empty node/edge lists)
3434+- WebSocket protocol: server sends `GraphUpdate` JSON on connect and on every file change; client sends nothing meaningful (receive loop is just for keepalive)
3535+3636+## Key Files
3737+3838+- `pipeline.py` -- `PipelineStage` enum, `PipelineResult` dataclass, `run_progressive()` function
3939+- `categories.py` -- `OpcodeCategory` enum, `CATEGORY_COLOURS` dict, `categorise()` function
4040+- `graph_json.py` -- `graph_to_json(PipelineResult) -> dict` serialisation
4141+- `server.py` -- `create_app(source_path) -> FastAPI`, `ConnectionManager`, `DebouncedFileHandler`
4242+- `frontend/src/types.ts` -- TypeScript interfaces matching `graph_to_json` output shape
4343+- `frontend/src/main.ts` -- Cytoscape graph rendering, logical/physical view toggle
4444+- `frontend/src/layout.ts` -- ELK layout configurations for both views
4545+4646+## Gotchas
4747+4848+- The Lark parser is lazily initialised and cached globally in `pipeline.py` (`_parser` module var) -- safe for single-process use but not for parallel test runners that fork
4949+- `DebouncedFileHandler` runs its callback on a `threading.Timer` thread, so `_on_file_change` uses `asyncio.run_coroutine_threadsafe` to bridge into the async event loop
5050+- Frontend `dist/` directory must contain the compiled TypeScript bundle; the server mounts it as static files at `/dist`
5151+5252+<!-- freshness: 2026-02-24 -->