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.errors and are displayed alongside the partial graph. File changes trigger reassembly within 300ms debounce window.
  • 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.

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.py imports dfgraph/categories.py for opcode categorisation.
  • Boundary: cm_inst, asm/, emu/, and root-level modules must NEVER import from dfgraph/

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 from emu/ -- it only needs the assembler IR, not the simulator
  • categorise() covers all ALUOp/MemOp values; raises ValueError on unknown types
  • graph_to_json() always returns a dict with type: "graph_update" -- even on parse failure (with empty node/edge lists)
  • WebSocket protocol: server sends GraphUpdate JSON on connect and on every file change; client sends nothing meaningful (receive loop is just for keepalive)

Key Files#

  • pipeline.py -- PipelineStage enum, PipelineResult dataclass, run_progressive() function
  • categories.py -- OpcodeCategory enum, CATEGORY_COLOURS dict, categorise() function
  • graph_json.py -- graph_to_json(PipelineResult) -> dict serialisation
  • server.py -- create_app(source_path) -> FastAPI, ConnectionManager, DebouncedFileHandler
  • frontend/src/types.ts -- TypeScript interfaces matching graph_to_json output shape
  • frontend/src/main.ts -- Cytoscape graph rendering, logical/physical view toggle
  • frontend/src/layout.ts -- ELK layout configurations for both views

Gotchas#

  • 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
  • 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
  • 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 from frontend-common as a workspace dependency