OR-1 dataflow CPU sketch

fix: address Phase 3 code review issues

- Remove dead except block around flit_count() in _handle_exec
flit_count() handles all 16-bit inputs without raising exceptions,
so the try/except (ValueError, KeyError) block was unreachable code

- Update stale _handle_t0_write docstring to reflect current architecture
T0 is now list[int] with pack_token()/unpack_token() serialization
(future work from original docstring was already implemented in Phase 3)

- Change t0_store padding from None to 0 to match list[int] annotation
Simplifies _handle_t0_read by removing None check
Makes padding consistent with type annotation

- Update test comment in test_malformed_flit_invalid_prefix
Accurately describes what happens: flit_count(0xFFFF) returns 2,
but only 1 flit available, so truncation check catches it

- Rename test_exec_stops_at_none_sentinel to test_exec_processes_multiple_packets_until_end
Old test relied on None sentinel that no longer exists
New test verifies EXEC processes multiple consecutive valid packets

Orual bf3fbc52 d692ace0

+26 -27
+7 -16
emu/sm.py
··· 246 246 yield self.env.timeout(1) # process cycle 247 247 if t0_idx < len(self.t0_store): 248 248 data = self.t0_store[t0_idx] 249 - yield from self._send_result(token.ret, data if data is not None else 0) 249 + yield from self._send_result(token.ret, data) 250 250 else: 251 251 yield from self._send_result(token.ret, 0) 252 252 253 253 def _handle_t0_write(self, addr: int, token: SMToken): 254 254 """T0 WRITE: store raw integer data without presence checking. 255 255 256 - Note: This stores token.data (an int) into t0_store. For Token object 257 - storage (needed by EXEC), populate t0_store directly before simulation 258 - starts (e.g., via build_topology or test setup). This asymmetry is 259 - intentional — SM WRITE provides data-level access (lookup tables, 260 - shared counters), while EXEC consumes pre-loaded Token objects for 261 - bootstrap. Future work will unify T0 as list[int] with token 262 - serialisation/deserialisation. 256 + Stores token.data (an int) into t0_store at the T0-relative index. 257 + For EXEC bootstrap, pre-load t0_store with packed flit sequences 258 + (via pack_token()) before simulation starts. T0 is now list[int] with 259 + token serialisation/deserialisation via pack_token()/unpack_token(). 263 260 """ 264 261 t0_idx = addr - self.tier_boundary 265 262 while len(self.t0_store) <= t0_idx: 266 - self.t0_store.append(None) 263 + self.t0_store.append(0) 267 264 self.t0_store[t0_idx] = token.data 268 265 269 266 def _handle_exec(self, addr: int): ··· 284 281 pos = t0_idx 285 282 while pos < len(self.t0_store): 286 283 header = self.t0_store[pos] 287 - if header is None: 288 - break 289 - try: 290 - count = flit_count(header) 291 - except (ValueError, KeyError): 292 - logger.warning("SM%d: malformed flit at T0[%d], stopping EXEC", self.sm_id, pos) 293 - break 284 + count = flit_count(header) 294 285 if pos + count > len(self.t0_store): 295 286 logger.warning("SM%d: truncated packet at T0[%d], stopping EXEC", self.sm_id, pos) 296 287 break
+19 -11
tests/test_sm_t0_raw.py
··· 158 158 assert isinstance(received2, MonadToken) 159 159 assert received2.data == 0x2222 160 160 161 - def test_exec_stops_at_none_sentinel(self): 162 - """EXEC stops reading T0 when it encounters None.""" 161 + def test_exec_processes_multiple_packets_until_end(self): 162 + """EXEC processes multiple consecutive packets until end of T0 store.""" 163 163 env = simpy.Environment() 164 164 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256) 165 165 ··· 175 175 176 176 mock_system.send = mock_send 177 177 178 - # Create one token 178 + # Create three valid tokens 179 179 token1 = DyadToken(target=0, offset=5, act_id=1, data=0x1111, port=Port.L) 180 + token2 = MonadToken(target=1, offset=10, act_id=2, data=0x2222, inline=False) 181 + token3 = DyadToken(target=2, offset=15, act_id=3, data=0x3333, port=Port.R) 182 + 180 183 flits1 = pack_token(token1) 184 + flits2 = pack_token(token2) 185 + flits3 = pack_token(token3) 181 186 182 - # Pre-load with token, then None sentinel, then junk 187 + # Pre-load all three tokens 183 188 sm.t0_store.extend(flits1) 184 - sm.t0_store.append(None) 185 - sm.t0_store.extend([0xFFFF, 0xFFFF]) # junk after None 189 + sm.t0_store.extend(flits2) 190 + sm.t0_store.extend(flits3) 186 191 187 192 # EXEC from start 188 193 exec_token = SMToken( ··· 196 201 env.process(do_exec()) 197 202 env.run() 198 203 199 - # Should have received exactly 1 token (stopped at None) 200 - assert len(mock_pe_store.items) == 1 204 + # Should have received all three tokens 205 + assert len(mock_pe_store.items) == 3 201 206 assert mock_pe_store.items[0].data == 0x1111 207 + assert mock_pe_store.items[1].data == 0x2222 208 + assert mock_pe_store.items[2].data == 0x3333 202 209 203 210 204 211 class TestAC4_3RoundTripPackUnpack: ··· 269 276 """AC4.4: Malformed flit sequences are handled gracefully without crash.""" 270 277 271 278 def test_malformed_flit_invalid_prefix(self): 272 - """EXEC stops gracefully on invalid flit prefix bits.""" 279 + """EXEC stops gracefully when flit count exceeds available data (truncation detection).""" 273 280 env = simpy.Environment() 274 281 sm = StructureMemory(env, 0, cell_count=512, tier_boundary=256) 275 282 ··· 285 292 286 293 mock_system.send = mock_send 287 294 288 - # Pre-load T0 with invalid header (impossible bit pattern) 289 - # flit_count() should raise ValueError for bad prefix 295 + # Pre-load T0 with 0xFFFF (SM token prefix). 296 + # flit_count(0xFFFF) returns 2 (SM token header indicates 2 flits), 297 + # but only 1 flit is in store, so truncation check catches it. 290 298 sm.t0_store.extend([0xFFFF]) 291 299 292 300 # EXEC from start