OR-1 dataflow CPU sketch
1import simpy
2
3from emu.pe import ProcessingElement
4from emu.sm import StructureMemory
5from emu.types import PEConfig, SMConfig
6from sm_mod import SMCell
7from tokens import PEToken, SMToken, Token
8
9
10class System:
11 def __init__(
12 self,
13 env: simpy.Environment,
14 pes: dict[int, ProcessingElement],
15 sms: dict[int, StructureMemory],
16 ):
17 self.env = env
18 self.pes = pes
19 self.sms = sms
20
21 def inject(self, token: Token) -> None:
22 """Inject a token into the appropriate element's input store (direct append).
23
24 Routes by type: SMToken → target SM, PEToken → target PE.
25 Uses direct list append (bypasses SimPy put), suitable for pre-simulation setup.
26 """
27 store = self._target_store(token)
28 store.items.append(token)
29
30 def send(self, token: Token):
31 """Inject a token via SimPy store.put() with 1-cycle delivery delay (generator, yields).
32
33 Same routing as inject() but adds network latency and respects FIFO backpressure.
34 Must be called from within a SimPy process or env.process().
35 """
36 store = self._target_store(token)
37 yield self.env.timeout(1) # 1-cycle network delivery latency
38 yield store.put(token)
39
40 def load(self, tokens: list[Token]) -> None:
41 """Spawn a SimPy process that sends each token via store.put().
42
43 Tokens are injected in order, respecting FIFO capacity.
44 """
45 def _loader():
46 for token in tokens:
47 yield from self.send(token)
48 self.env.process(_loader())
49
50 def _target_store(self, token: Token) -> simpy.Store:
51 """Resolve the destination store for a token."""
52 if isinstance(token, SMToken):
53 return self.sms[token.target].input_store
54 if isinstance(token, PEToken):
55 return self.pes[token.target].input_store
56 raise TypeError(f"Unknown token type: {type(token).__name__}")
57
58
59def build_topology(
60 env: simpy.Environment,
61 pe_configs: list[PEConfig],
62 sm_configs: list[SMConfig],
63 fifo_capacity: int = 8,
64) -> System:
65 pes: dict[int, ProcessingElement] = {}
66 sms: dict[int, StructureMemory] = {}
67
68 for cfg in pe_configs:
69 pe = ProcessingElement(
70 env=env,
71 pe_id=cfg.pe_id,
72 config=cfg,
73 )
74 pes[cfg.pe_id] = pe
75
76 t0_store: list[int] = []
77
78 for cfg in sm_configs:
79 sm = StructureMemory(
80 env=env,
81 sm_id=cfg.sm_id,
82 cell_count=cfg.cell_count,
83 fifo_capacity=fifo_capacity,
84 tier_boundary=cfg.tier_boundary,
85 on_event=cfg.on_event,
86 )
87 sm.t0_store = t0_store
88 if cfg.initial_cells is not None:
89 for addr, (pres, data) in cfg.initial_cells.items():
90 sm.cells[addr] = SMCell(pres, data, None)
91 sms[cfg.sm_id] = sm
92
93 pe_stores: dict[int, simpy.Store] = {pe_id: pe.input_store for pe_id, pe in pes.items()}
94 sm_stores: dict[int, simpy.Store] = {sm_id: sm.input_store for sm_id, sm in sms.items()}
95
96 for pe in pes.values():
97 pe.route_table.update(pe_stores)
98 pe.sm_routes.update(sm_stores)
99
100 for sm in sms.values():
101 sm.route_table.update(pe_stores)
102
103 # Apply route restrictions based on PEConfig allowed_pe_routes and allowed_sm_routes
104 for cfg in pe_configs:
105 pe = pes[cfg.pe_id]
106 if cfg.allowed_pe_routes is not None:
107 pe.route_table = {
108 pid: store for pid, store in pe.route_table.items()
109 if pid in cfg.allowed_pe_routes
110 }
111 if cfg.allowed_sm_routes is not None:
112 pe.sm_routes = {
113 sid: store for sid, store in pe.sm_routes.items()
114 if sid in cfg.allowed_sm_routes
115 }
116
117 system = System(env, pes, sms)
118 for sm in sms.values():
119 sm.system = system
120 return system