# SM Nodes in Graph Visualization ## Goal Make Structure Memory instances visible as first-class nodes in the dataflow graph, so token flow through memory is visible rather than hidden in the state inspector. ## Current State - IR nodes with `MemOp` opcodes (read, write, etc.) are rendered with MEMORY category - `IRNode.sm_id` is set during allocation (defaults to sm0 for single-SM systems) - SM interactions are implicit: the PE instruction *targets* an SM, but there's no SM node in the graph and no edge showing the request/return flow - SM state is only visible in the monitor's state inspector panel ## Design ### Synthesized SM Nodes Add SM nodes in the **graph JSON layer** (not the IR). One node per SM instance. **Node shape:** - `id`: `"__sm_0"`, `"__sm_1"`, etc. (double-underscore prefix = synthetic) - `opcode`: `"sm"` (not a real opcode — display-only label) - `category`: `"structure_memory"` (new category) - `colour`: `"#795548"` (brown — distinct from all existing categories) - `pe`: `null` (SMs aren't on any PE) - `iram_offset`, `ctx`: `null` - `sm_id`: the SM instance ID (new field for SM nodes) ### Synthesized Edges For each IR node where `isinstance(node.opcode, MemOp)` and `node.sm_id is not None`: 1. **Request edge**: from the MemOp node → SM node - `source`: node.name, `target`: `"__sm_{sm_id}"` - `port`: `"REQ"` (new synthetic port label) - Visual: uses MEMORY colour, dashed line to distinguish from data edges 2. **Return edge** (if `node.dest_l` exists — i.e., there's a return route): - `source`: `"__sm_{sm_id}"`, `target`: dest_l node name - `port`: the port from the resolved dest - Visual: same dashed style, opposite direction ### Where to Implement Both `dfgraph/graph_json.py` and `monitor/graph_json.py` need the same SM node synthesis. Extract a shared helper. **New file: `graph_common.py` in one of the packages?** No — simpler to just add the synthesis functions to each graph_json module directly. They're small (< 30 lines each) and the two modules have different serialization shapes anyway (dfgraph includes error info, monitor includes execution overlay). Actually, even simpler: add a shared helper in `dfgraph/categories.py` since both modules already import it. But categories.py is about opcode mapping, not graph synthesis. Bad fit. **Decision:** Add synthesis functions directly to each `graph_json.py`. ~20 lines each. The duplication is minimal and the serialization format differs between them. ### Categories Update In `dfgraph/categories.py`: - Add `STRUCTURE_MEMORY = "structure_memory"` to `OpcodeCategory` - Add colour `"#795548"` to `CATEGORY_COLOURS` - No change to `categorise()` — SM nodes don't have real opcodes ### Frontend Styling In `frontend-common/src/style.ts`: - Add selector for SM nodes: `node[category = "structure_memory"]` - Shape: **rounded rectangle** (visually distinct from ellipse instruction nodes) - Larger size (60×40) to accommodate "SM 0" label - Brown border, slightly different background In `frontend-common/src/types.ts`: - Add optional `sm_id: number | null` to `GraphNode` - Add optional `synthetic: boolean` to `GraphNode` (so frontends can identify synthesized nodes vs IR nodes) ### Edge Styling In `frontend-common/src/style.ts`: - Add selector for SM edges: `edge[synthetic = "true"]` or `edge.sm-edge` - Dashed line style - Same MEMORY colour (`#ff5722`) or SM brown ### Monitor Execution Overlay In `monitor/graph_json.py`: - SM nodes get `active: true` when any `TokenReceived` event has `component: "sm:N"` - SM nodes get a new `cell_written: true` flag when `CellWritten` events occur - SM edges get `token_flow: true` when `ResultSent` events occur (return direction) - SM node carries `sm_state` dict in JSON: cell contents (addr → {presence, data_l, data_r}), deferred_read info, so the frontend can render a rich label showing what's inside - The frontend label for SM nodes shows a compact text summary: - Cell lines: `[0] FULL 42` or `[0] EMPTY` (only non-empty cells shown) - Deferred read: `DR: [3] → pe1:0:L` (if occupied) ### Physical Layout Grouping SMs don't belong to any PE. In physical layout mode (grouped by PE): - SM nodes go in their own group/container: `"SM 0"`, `"SM 1"` - Alternatively: SM nodes float as ungrouped nodes between PE groups Either works. The container approach is more consistent with how PEs are grouped. ## Implementation Order ### 1. Categories + Types (~5 min) - Add `STRUCTURE_MEMORY` to `OpcodeCategory` enum - Add colour to `CATEGORY_COLOURS` - Add `sm_id` and `synthetic` fields to `GraphNode` TypeScript type ### 2. dfgraph/graph_json.py (~15 min) - Add `_synthesize_sm_nodes(graph)` → list of SM node dicts - Add `_synthesize_sm_edges(all_nodes, sm_count)` → list of SM edge dicts - Wire into `graph_to_json()` — append to nodes_json and edges_json - Mark synthetic edges with a `synthetic: true` field ### 3. monitor/graph_json.py (~15 min) - Same synthesis functions adapted for monitor JSON shape (with overlay fields) - Wire into both `graph_to_monitor_json()` and `graph_loaded_json()` - Add event-driven overlay: SM node activity from SM events - Add token_flow on return edges from ResultSent events ### 4. Frontend styling (~10 min) - Add SM node style (rounded rectangle, brown, larger) - Add SM edge style (dashed) - Both dfgraph and monitor frontends inherit from frontend-common ### 5. Tests (~15 min) - Test `_synthesize_sm_nodes` produces correct nodes for N SMs - Test `_synthesize_sm_edges` produces request + return edges - Test monitor overlay marks SM nodes active on SM events - Test that programs with no SMs produce no synthetic nodes - Test existing graph JSON tests still pass (no regression) ## Not In Scope - Cell-level grid visualization within SM nodes (future enhancement) - Changes to IR, grammar, or assembler pipeline - Changes to emulator - T0 store visualization (could be a separate node in future) ## Resolved Questions - **SM node labels:** Show cell contents and deferred read state. In the monitor, the SM node label includes a compact summary: cell states with data, and the deferred read slot if occupied. In dfgraph (static), just "SM 0" since there's no runtime state. - **Pre-allocation visibility:** Only show SM nodes when sm_id has been assigned on MemOp nodes OR when datadefs reference an SM. Don't show SM nodes for pre-allocation graphs unless a datadef explicitly targets an SM. - **State via colours:** SM node border/background colour reflects aggregate state: - Default (brown): idle - Monitor overlay: active (received token), cell_written (state changed)