OR-1 dataflow CPU sketch
1"""
2Tests for ProcessingElement event firing with frame-based redesign.
3
4Verifies:
5- PE fires TokenReceived when a token is dequeued
6- PE fires Matched when dyadic match completes
7- PE fires Executed after ALU execution
8- PE fires Emitted for each output token
9"""
10
11import simpy
12
13from cm_inst import (
14 ArithOp, FrameDest, Instruction, LogicOp, MemOp,
15 OutputStyle, Port, RoutingOp, TokenKind,
16)
17from emu.events import (
18 Emitted, Executed, Matched, TokenReceived,
19)
20from emu.pe import ProcessingElement
21from emu.types import PEConfig
22from tokens import DyadToken, MonadToken
23
24
25def inject_and_run(env, pe, token):
26 """Helper: inject token and run simulation."""
27 def _put():
28 yield pe.input_store.put(token)
29 env.process(_put())
30 env.run()
31
32
33def inject_two_and_run(env, pe, token1, token2):
34 """Helper: inject two tokens and run simulation."""
35 def _put():
36 yield pe.input_store.put(token1)
37 yield pe.input_store.put(token2)
38 env.process(_put())
39 env.run()
40
41
42class TestTokenReceived:
43 """PE fires TokenReceived event when a token is dequeued."""
44
45 def test_token_received_on_monad(self):
46 """TokenReceived event fires for MonadToken."""
47 env = simpy.Environment()
48 events = []
49
50 config = PEConfig(pe_id=0, on_event=events.append)
51 pe = ProcessingElement(env=env, pe_id=0, config=config)
52
53 token = MonadToken(target=0, offset=0, act_id=0, data=0x1234, inline=False)
54 inject_and_run(env, pe, token)
55
56 # Verify TokenReceived event was fired
57 token_received_events = [e for e in events if isinstance(e, TokenReceived)]
58 assert len(token_received_events) >= 1
59 assert token_received_events[0].token == token
60 assert token_received_events[0].component == "pe:0"
61
62 def test_token_received_on_dyad(self):
63 """TokenReceived event fires for DyadToken."""
64 env = simpy.Environment()
65 events = []
66
67 add_inst = Instruction(
68 opcode=ArithOp.ADD,
69 output=OutputStyle.INHERIT,
70 has_const=False,
71 dest_count=1,
72 wide=False,
73 fref=8,
74 )
75
76 dest = FrameDest(
77 target_pe=1, offset=0, act_id=0,
78 port=Port.L, token_kind=TokenKind.DYADIC,
79 )
80
81 config = PEConfig(
82 pe_id=0,
83 iram={0: add_inst},
84 initial_frames={0: {8: dest}},
85 initial_tag_store={0: (0, 0)},
86 on_event=events.append,
87 )
88
89 pe = ProcessingElement(env=env, pe_id=0, config=config)
90 output_store = simpy.Store(env)
91 pe.route_table[1] = output_store
92
93 token = DyadToken(target=0, offset=0, act_id=0, data=0x5678, port=Port.L)
94 inject_and_run(env, pe, token)
95
96 # Verify TokenReceived event was fired
97 token_received_events = [e for e in events if isinstance(e, TokenReceived)]
98 assert len(token_received_events) >= 1
99 assert token_received_events[0].token == token
100
101
102class TestMatched:
103 """PE fires Matched event when dyadic pair matches."""
104
105 def test_matched_left_then_right(self):
106 """Matched event fires when dyadic tokens pair (L then R)."""
107 env = simpy.Environment()
108 events = []
109
110 add_inst = Instruction(
111 opcode=ArithOp.ADD,
112 output=OutputStyle.INHERIT,
113 has_const=False,
114 dest_count=1,
115 wide=False,
116 fref=8,
117 )
118
119 dest = FrameDest(
120 target_pe=1, offset=0, act_id=0,
121 port=Port.L, token_kind=TokenKind.DYADIC,
122 )
123
124 config = PEConfig(
125 pe_id=0,
126 iram={0: add_inst},
127 initial_frames={0: {8: dest}},
128 initial_tag_store={0: (0, 0)},
129 on_event=events.append,
130 )
131
132 pe = ProcessingElement(env=env, pe_id=0, config=config)
133 output_store = simpy.Store(env)
134 pe.route_table[1] = output_store
135
136 token_l = DyadToken(target=0, offset=0, act_id=0, data=0x1111, port=Port.L)
137 token_r = DyadToken(target=0, offset=0, act_id=0, data=0x2222, port=Port.R)
138
139 inject_two_and_run(env, pe, token_l, token_r)
140
141 # Verify Matched event was fired
142 matched_events = [e for e in events if isinstance(e, Matched)]
143 assert len(matched_events) >= 1
144 matched = matched_events[0]
145 assert matched.left == 0x1111
146 assert matched.right == 0x2222
147
148 def test_matched_right_then_left(self):
149 """Matched event fires with correct operand ordering (R then L)."""
150 env = simpy.Environment()
151 events = []
152
153 add_inst = Instruction(
154 opcode=ArithOp.ADD,
155 output=OutputStyle.INHERIT,
156 has_const=False,
157 dest_count=1,
158 wide=False,
159 fref=8,
160 )
161
162 dest = FrameDest(
163 target_pe=1, offset=5, act_id=1,
164 port=Port.L, token_kind=TokenKind.DYADIC,
165 )
166
167 config = PEConfig(
168 pe_id=0,
169 iram={5: add_inst},
170 initial_frames={0: {8: dest}},
171 initial_tag_store={1: (0, 0)},
172 on_event=events.append,
173 )
174
175 pe = ProcessingElement(env=env, pe_id=0, config=config)
176 output_store = simpy.Store(env)
177 pe.route_table[1] = output_store
178
179 token_r = DyadToken(target=0, offset=5, act_id=1, data=0x4444, port=Port.R)
180 token_l = DyadToken(target=0, offset=5, act_id=1, data=0x3333, port=Port.L)
181
182 inject_two_and_run(env, pe, token_r, token_l)
183
184 # Verify Matched event with correct operand ordering
185 matched_events = [e for e in events if isinstance(e, Matched)]
186 assert len(matched_events) >= 1
187 matched = matched_events[0]
188 assert matched.left == 0x3333 # L port data
189 assert matched.right == 0x4444 # R port data
190
191
192class TestExecuted:
193 """PE fires Executed event after ALU execution."""
194
195 def test_executed_add_instruction(self):
196 """Executed event fires for ADD instruction."""
197 env = simpy.Environment()
198 events = []
199
200 add_inst = Instruction(
201 opcode=ArithOp.ADD,
202 output=OutputStyle.INHERIT,
203 has_const=False,
204 dest_count=1,
205 wide=False,
206 fref=8,
207 )
208
209 dest = FrameDest(
210 target_pe=1, offset=0, act_id=0,
211 port=Port.L, token_kind=TokenKind.DYADIC,
212 )
213
214 config = PEConfig(
215 pe_id=0,
216 iram={0: add_inst},
217 initial_frames={0: {8: dest}},
218 initial_tag_store={0: (0, 0)},
219 on_event=events.append,
220 )
221
222 pe = ProcessingElement(env=env, pe_id=0, config=config)
223 output_store = simpy.Store(env)
224 pe.route_table[1] = output_store
225
226 token_l = DyadToken(target=0, offset=0, act_id=0, data=5, port=Port.L)
227 token_r = DyadToken(target=0, offset=0, act_id=0, data=3, port=Port.R)
228
229 inject_two_and_run(env, pe, token_l, token_r)
230
231 # Verify Executed event was fired
232 executed_events = [e for e in events if isinstance(e, Executed)]
233 assert len(executed_events) >= 1
234 executed = executed_events[0]
235 assert executed.op == ArithOp.ADD
236 assert executed.result == 8 # 5 + 3
237
238 def test_executed_comparison_instruction(self):
239 """Executed event fires for comparison instruction."""
240 env = simpy.Environment()
241 events = []
242
243 eq_inst = Instruction(
244 opcode=LogicOp.EQ,
245 output=OutputStyle.INHERIT,
246 has_const=False,
247 dest_count=1,
248 wide=False,
249 fref=8,
250 )
251
252 dest = FrameDest(
253 target_pe=1, offset=0, act_id=0,
254 port=Port.L, token_kind=TokenKind.DYADIC,
255 )
256
257 config = PEConfig(
258 pe_id=0,
259 iram={0: eq_inst},
260 initial_frames={0: {8: dest}},
261 initial_tag_store={0: (0, 0)},
262 on_event=events.append,
263 )
264
265 pe = ProcessingElement(env=env, pe_id=0, config=config)
266 output_store = simpy.Store(env)
267 pe.route_table[1] = output_store
268
269 token_l = DyadToken(target=0, offset=0, act_id=0, data=5, port=Port.L)
270 token_r = DyadToken(target=0, offset=0, act_id=0, data=5, port=Port.R)
271
272 inject_two_and_run(env, pe, token_l, token_r)
273
274 # Verify Executed event was fired
275 executed_events = [e for e in events if isinstance(e, Executed)]
276 assert len(executed_events) >= 1
277 executed = executed_events[0]
278 assert executed.op == LogicOp.EQ
279 assert executed.bool_out is True # 5 == 5
280
281
282class TestEmitted:
283 """PE fires Emitted event for each output token."""
284
285 def test_emitted_single_mode(self):
286 """Emitted event fires for SINGLE mode output."""
287 env = simpy.Environment()
288 events = []
289
290 add_inst = Instruction(
291 opcode=ArithOp.ADD,
292 output=OutputStyle.INHERIT,
293 has_const=False,
294 dest_count=1,
295 wide=False,
296 fref=8,
297 )
298
299 dest = FrameDest(
300 target_pe=1, offset=0, act_id=0,
301 port=Port.L, token_kind=TokenKind.DYADIC,
302 )
303
304 config = PEConfig(
305 pe_id=0,
306 iram={0: add_inst},
307 initial_frames={0: {8: dest}},
308 initial_tag_store={0: (0, 0)},
309 on_event=events.append,
310 )
311
312 pe = ProcessingElement(env=env, pe_id=0, config=config)
313 output_store = simpy.Store(env)
314 pe.route_table[1] = output_store
315
316 token_l = DyadToken(target=0, offset=0, act_id=0, data=10, port=Port.L)
317 token_r = DyadToken(target=0, offset=0, act_id=0, data=20, port=Port.R)
318
319 inject_two_and_run(env, pe, token_l, token_r)
320
321 # Verify Emitted events were fired
322 emitted_events = [e for e in events if isinstance(e, Emitted)]
323 assert len(emitted_events) >= 1
324 assert emitted_events[0].token.data == 30
325
326 def test_emitted_dual_mode(self):
327 """Emitted event fires for DUAL mode output (two tokens)."""
328 env = simpy.Environment()
329 events = []
330
331 add_inst = Instruction(
332 opcode=ArithOp.ADD,
333 output=OutputStyle.INHERIT,
334 has_const=False,
335 dest_count=2,
336 wide=False,
337 fref=8,
338 )
339
340 dest_l = FrameDest(
341 target_pe=1, offset=0, act_id=0,
342 port=Port.L, token_kind=TokenKind.DYADIC,
343 )
344 dest_r = FrameDest(
345 target_pe=2, offset=1, act_id=0,
346 port=Port.L, token_kind=TokenKind.DYADIC,
347 )
348
349 config = PEConfig(
350 pe_id=0,
351 iram={0: add_inst},
352 initial_frames={0: {8: dest_l, 9: dest_r}},
353 initial_tag_store={0: (0, 0)},
354 on_event=events.append,
355 )
356
357 pe = ProcessingElement(env=env, pe_id=0, config=config)
358 output_l = simpy.Store(env)
359 output_r = simpy.Store(env)
360 pe.route_table[1] = output_l
361 pe.route_table[2] = output_r
362
363 token_l = DyadToken(target=0, offset=0, act_id=0, data=10, port=Port.L)
364 token_r = DyadToken(target=0, offset=0, act_id=0, data=20, port=Port.R)
365
366 inject_two_and_run(env, pe, token_l, token_r)
367
368 # Verify Emitted events were fired (two for DUAL mode)
369 emitted_events = [e for e in events if isinstance(e, Emitted)]
370 assert len(emitted_events) >= 2
371
372 def test_emitted_switch_mode(self):
373 """Emitted event fires for SWITCH mode output (data + trigger)."""
374 env = simpy.Environment()
375 events = []
376
377 sweq_inst = Instruction(
378 opcode=RoutingOp.SWEQ,
379 output=OutputStyle.INHERIT,
380 has_const=False,
381 dest_count=2,
382 wide=False,
383 fref=8,
384 )
385
386 dest_l = FrameDest(
387 target_pe=1, offset=0, act_id=0,
388 port=Port.L, token_kind=TokenKind.MONADIC,
389 )
390 dest_r = FrameDest(
391 target_pe=2, offset=1, act_id=0,
392 port=Port.L, token_kind=TokenKind.MONADIC,
393 )
394
395 config = PEConfig(
396 pe_id=0,
397 iram={0: sweq_inst},
398 initial_frames={0: {8: dest_l, 9: dest_r}},
399 initial_tag_store={0: (0, 0)},
400 on_event=events.append,
401 )
402
403 pe = ProcessingElement(env=env, pe_id=0, config=config)
404 output_l = simpy.Store(env)
405 output_r = simpy.Store(env)
406 pe.route_table[1] = output_l
407 pe.route_table[2] = output_r
408
409 token_l = DyadToken(target=0, offset=0, act_id=0, data=0x1234, port=Port.L)
410 token_r = DyadToken(target=0, offset=0, act_id=0, data=0x1234, port=Port.R)
411
412 inject_two_and_run(env, pe, token_l, token_r)
413
414 # Verify Emitted events were fired (two for SWITCH)
415 emitted_events = [e for e in events if isinstance(e, Emitted)]
416 assert len(emitted_events) >= 2
417
418 def test_emitted_suppress_mode(self):
419 """Emitted event does not fire for SUPPRESS mode."""
420 env = simpy.Environment()
421 events = []
422
423 # FREE_FRAME (SUPPRESS mode)
424 free_inst = Instruction(
425 opcode=RoutingOp.FREE_FRAME,
426 output=OutputStyle.SINK,
427 has_const=False,
428 dest_count=0,
429 wide=False,
430 fref=8,
431 )
432
433 config = PEConfig(
434 pe_id=0,
435 iram={0: free_inst},
436 initial_tag_store={0: (0, 0)},
437 on_event=events.append,
438 )
439
440 pe = ProcessingElement(env=env, pe_id=0, config=config)
441
442 token = MonadToken(target=0, offset=0, act_id=0, data=100, inline=False)
443 inject_and_run(env, pe, token)
444
445 # Verify no Emitted events (SUPPRESS mode)
446 emitted_events = [e for e in events if isinstance(e, Emitted)]
447 assert len(emitted_events) == 0