OR-1 dataflow CPU sketch
Dataflow Graph Renderer (dfgraph/)#
Last verified: 2026-02-26
Purpose#
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.
Contracts#
- Exposes:
python -m dfgraph path/to/file.dfasm [--port 8420]launches a web server with WebSocket-driven graph updates - 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.errorsand are displayed alongside the partial graph. File changes trigger reassembly within 300ms debounce window. - Expects: Valid path to a
.dfasmfile. Thedfasm.larkgrammar file at project root. Node modules installed indfgraph/frontend/for the TypeScript build.
Dependencies#
- Uses:
cm_inst(ArithOp, LogicOp, RoutingOp, MemOp, Addr),asm/(ir, lower, resolve, place, allocate, errors, opcodes),lark(parser),fastapi/uvicorn(server),watchdog(file watcher),cytoscape/cytoscape-elk(frontend) - Used by: Developer tooling only.
monitor/graph_json.pyimportsdfgraph/categories.pyfor opcode categorisation. - Boundary:
cm_inst,asm/,emu/, and root-level modules must NEVER import fromdfgraph/
Key Decisions#
- 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. - 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.
- 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.
- ELK layout engine: Uses Eclipse Layout Kernel (via cytoscape-elk) for layered DAG layout with orthogonal edge routing. Replaced dagre for better edge routing.
- 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.
Invariants#
dfgraph/never imports fromemu/-- it only needs the assembler IR, not the simulatorcategorise()covers all ALUOp/MemOp values; raisesValueErroron unknown typesgraph_to_json()always returns a dict withtype: "graph_update"-- even on parse failure (with empty node/edge lists)- WebSocket protocol: server sends
GraphUpdateJSON on connect and on every file change; client sends nothing meaningful (receive loop is just for keepalive)
Key Files#
pipeline.py--PipelineStageenum,PipelineResultdataclass,run_progressive()functioncategories.py--OpcodeCategoryenum,CATEGORY_COLOURSdict,categorise()functiongraph_json.py--graph_to_json(PipelineResult) -> dictserialisationserver.py--create_app(source_path) -> FastAPI,ConnectionManager,DebouncedFileHandlerfrontend/src/types.ts-- TypeScript interfaces matchinggraph_to_jsonoutput shapefrontend/src/main.ts-- Cytoscape graph rendering, logical/physical view togglefrontend/src/layout.ts-- ELK layout configurations for both views
Gotchas#
- The Lark parser is lazily initialised and cached globally in
pipeline.py(_parsermodule var) -- safe for single-process use but not for parallel test runners that fork DebouncedFileHandlerruns its callback on athreading.Timerthread, so_on_file_changeusesasyncio.run_coroutine_threadsafeto bridge into the async event loop- Frontend
dist/directory must contain the compiled TypeScript bundle; the server mounts it as static files at/dist - Shared TypeScript modules (layout, style, export, types) have been extracted to
frontend-common/;dfgraph/frontend/now imports fromfrontend-commonas a workspace dependency