OR-1 dataflow CPU sketch

feat: update monitor snapshot, graph_json, and backend for frame-based PE state and setup_tokens

Task 1: Update monitor/snapshot.py for frame-based PE state
- Replace PESnapshot fields: matching_store/gen_counters → frames/tag_store/presence/port_store/free_frames
- Update capture() to read frame data from live PE instances
- Change SMSnapshot.t0_store type from tuple[Token, ...] to tuple[int, ...]
- Update imports: add Instruction, FrameSlotValue, Port

Task 2: Update monitor/graph_json.py for frame events and act_id
- Add imports for new event types (FrameAllocated, FrameFreed, FrameSlotWritten, TokenRejected)
- Update _serialise_node to use act_id instead of ctx
- Add _serialise_slot helper function for frame slot serialization
- Update _serialise_pe_state to show frames instead of matching_store
- Update Matched event serialization to include frame_id
- Add serialization handlers for new frame event types
- Update SM node synthesis to use act_id instead of ctx

Task 3: Update monitor/backend.py for setup_tokens injection
- In _handle_load(), inject setup_tokens before seed_tokens
- Setup tokens come from AssemblyResult (IRAM writes, ALLOC, frame slot writes)

Orual ced780dd 28600d9f

+81 -43
+4
monitor/backend.py
··· 183 183 184 184 system = build_topology(env, pe_configs, sm_configs) 185 185 186 + # Inject setup tokens (IRAM writes, ALLOC, frame slot writes) 187 + for token in result.setup_tokens: 188 + system.inject(token) 189 + 186 190 # Inject seed tokens 187 191 for seed in result.seed_tokens: 188 192 system.inject(seed)
+48 -24
monitor/graph_json.py
··· 22 22 SimEvent, TokenReceived, Matched, Executed, Emitted, 23 23 CellWritten, DeferredRead as DeferredReadEvent, 24 24 DeferredSatisfied, ResultSent, 25 + FrameAllocated, FrameFreed, FrameSlotWritten, TokenRejected, 25 26 ) 26 27 from monitor.snapshot import StateSnapshot 27 28 ··· 58 59 "const": node.const, 59 60 "pe": node.pe, 60 61 "iram_offset": node.iram_offset, 61 - "ctx": node.ctx, 62 + "act_id": node.act_id, 62 63 "has_error": False, 63 64 "loc": { 64 65 "line": 0, ··· 72 73 } 73 74 74 75 76 + def _serialise_slot(slot: Any) -> Any: 77 + """Serialize a frame slot value to JSON-compatible format.""" 78 + if slot is None: 79 + return None 80 + if isinstance(slot, int): 81 + return slot 82 + # slot is a FrameDest 83 + return { 84 + "target_pe": slot.target_pe, 85 + "offset": slot.offset, 86 + "act_id": slot.act_id, 87 + "port": slot.port.name, 88 + "token_kind": slot.token_kind.name, 89 + } 90 + 91 + 75 92 def _serialise_edge(edge: IREdge) -> dict[str, Any]: 76 93 """Serialize an IR edge to JSON.""" 77 94 return { ··· 92 109 93 110 iram_json = {} 94 111 for offset, inst in pe_snap.iram.items(): 95 - opcode_str = str(inst.op.name) if hasattr(inst.op, 'name') else str(inst.op) 112 + opcode_str = str(inst.opcode.name) if hasattr(inst.opcode, 'name') else str(inst.opcode) 96 113 iram_json[str(offset)] = { 97 114 "opcode": opcode_str, 98 115 "offset": offset, 99 116 } 100 117 101 - matching_store_json = [] 102 - try: 103 - for row in pe_snap.matching_store: 104 - row_json = [] 105 - for entry in row: 106 - if isinstance(entry, dict): 107 - row_json.append({ 108 - "occupied": entry.get("occupied", False), 109 - "data": entry.get("data", 0), 110 - "port": entry.get("port", None), 111 - }) 112 - else: 113 - logger.warning(f"Non-dict entry in matching_store: {type(entry).__name__}") 114 - matching_store_json.append(row_json) 115 - except (TypeError, KeyError) as e: 116 - logger.error(f"Error serializing matching_store: {e}") 118 + frames_json = [[_serialise_slot(s) for s in frame] for frame in pe_snap.frames] 117 119 118 120 return { 121 + "pe_id": pe_snap.pe_id, 119 122 "iram": iram_json, 120 - "matching_store": matching_store_json, 121 - "gen_counters": list(pe_snap.gen_counters), 122 - "input_queue_depth": len(pe_snap.input_queue), 123 - "output_count": len(pe_snap.output_log), 123 + "frames": frames_json, 124 + "tag_store": pe_snap.tag_store, 125 + "free_frames": list(pe_snap.free_frames), 126 + "input_queue_size": len(pe_snap.input_queue), 124 127 } 125 128 126 129 ··· 167 170 base["details"] = { 168 171 "left": event.left, 169 172 "right": event.right, 170 - "ctx": event.ctx, 173 + "act_id": event.act_id, 174 + "frame_id": event.frame_id, 171 175 "offset": event.offset, 172 176 } 173 177 elif isinstance(event, Executed): ··· 178 182 } 179 183 elif isinstance(event, Emitted): 180 184 base["details"] = {"token": str(event.token)} 185 + elif isinstance(event, FrameAllocated): 186 + base["details"] = { 187 + "act_id": event.act_id, 188 + "frame_id": event.frame_id, 189 + } 190 + elif isinstance(event, FrameFreed): 191 + base["details"] = { 192 + "act_id": event.act_id, 193 + "frame_id": event.frame_id, 194 + } 195 + elif isinstance(event, FrameSlotWritten): 196 + base["details"] = { 197 + "frame_id": event.frame_id, 198 + "slot": event.slot, 199 + "value": event.value, 200 + } 201 + elif isinstance(event, TokenRejected): 202 + base["details"] = { 203 + "reason": event.reason, 204 + } 181 205 else: 182 206 base["details"] = {} 183 207 ··· 268 292 "const": None, 269 293 "pe": None, 270 294 "iram_offset": None, 271 - "ctx": None, 295 + "act_id": None, 272 296 "has_error": False, 273 297 "loc": {"line": 0, "column": 0, "end_line": None, "end_column": None}, 274 298 "sm_id": sm_id,
+29 -19
monitor/snapshot.py
··· 9 9 from dataclasses import dataclass 10 10 from typing import Optional 11 11 12 - from cm_inst import ALUInst, SMInst 12 + from cm_inst import Instruction, FrameSlotValue, Port 13 13 from emu.network import System 14 14 from sm_mod import Presence 15 15 from tokens import Token ··· 20 20 """Frozen snapshot of a Processing Element's state at a moment in time.""" 21 21 22 22 pe_id: int 23 - iram: dict[int, ALUInst | SMInst] 24 - matching_store: tuple[tuple[dict, ...], ...] 25 - gen_counters: tuple[int, ...] 23 + iram: dict[int, Instruction] 24 + frames: tuple[tuple[FrameSlotValue, ...], ...] 25 + tag_store: dict[int, int] 26 + presence: tuple[tuple[bool, ...], ...] 27 + port_store: tuple[tuple[Port | None, ...], ...] 28 + free_frames: tuple[int, ...] 26 29 input_queue: tuple[Token, ...] 27 30 output_log: tuple[Token, ...] 28 31 ··· 43 46 sm_id: int 44 47 cells: dict[int, SMCellSnapshot] 45 48 deferred_read: Optional[dict] 46 - t0_store: tuple[Token, ...] 49 + t0_store: tuple[int, ...] 47 50 input_queue: tuple[Token, ...] 48 51 49 52 ··· 60 63 def capture(system: System) -> StateSnapshot: 61 64 """Capture a frozen snapshot of the entire system state at the current moment. 62 65 63 - Reads from live PE and SM attributes (matching_store, iram, input_store.items, 64 - output_log, gen_counters, cells, deferred_read, t0_store). 66 + Reads from live PE and SM attributes (frames, tag_store, presence, port_store, 67 + free_frames, iram, input_store.items, output_log, cells, deferred_read, t0_store). 65 68 66 69 Args: 67 70 system: A System instance from emu.network.build_topology() ··· 71 74 """ 72 75 pes = {} 73 76 for pe_id, pe in system.pes.items(): 74 - matching = [] 75 - for ctx_row in pe.matching_store: 76 - row = [] 77 - for entry in ctx_row: 78 - row.append({ 79 - "occupied": entry.occupied, 80 - "data": entry.data, 81 - "port": entry.port.name if entry.occupied else None, 82 - }) 83 - matching.append(tuple(row)) 77 + frames = tuple( 78 + tuple(slot for slot in frame) 79 + for frame in pe.frames 80 + ) 81 + tag_store = dict(pe.tag_store) 82 + presence = tuple( 83 + tuple(p for p in frame_presence) 84 + for frame_presence in pe.presence 85 + ) 86 + port_store = tuple( 87 + tuple(p for p in frame_ports) 88 + for frame_ports in pe.port_store 89 + ) 90 + free_frames = tuple(pe.free_frames) 84 91 85 92 pes[pe_id] = PESnapshot( 86 93 pe_id=pe_id, 87 94 iram=dict(pe.iram), 88 - matching_store=tuple(matching), 89 - gen_counters=tuple(pe.gen_counters), 95 + frames=frames, 96 + tag_store=tag_store, 97 + presence=presence, 98 + port_store=port_store, 99 + free_frames=free_frames, 90 100 input_queue=tuple(pe.input_store.items), 91 101 output_log=tuple(pe.output_log), 92 102 )