OR-1 dataflow CPU sketch
1"""Opcode mnemonic mapping and arity classification for OR1 assembly.
2
3This module provides:
4- MNEMONIC_TO_OP: Maps assembly mnemonic strings to ALUOp or MemOp enum values
5- OP_TO_MNEMONIC: Reverse mapping for serialization (handles IntEnum value collisions)
6- MONADIC_OPS: Set of opcodes that are always monadic
7- is_monadic() and is_dyadic(): Functions to check operand arity
8"""
9
10from typing import Optional, Union
11from cm_inst import ArithOp, CfgOp, LogicOp, MemOp, RoutingOp, is_monadic_alu
12
13
14# Build mnemonic to opcode mapping
15MNEMONIC_TO_OP: dict[str, Union[ArithOp, LogicOp, RoutingOp, MemOp, CfgOp]] = {
16 # Arithmetic operations
17 "add": ArithOp.ADD,
18 "sub": ArithOp.SUB,
19 "inc": ArithOp.INC,
20 "dec": ArithOp.DEC,
21 "shiftl": ArithOp.SHIFT_L,
22 "shiftr": ArithOp.SHIFT_R,
23 "ashiftr": ArithOp.ASHFT_R,
24 # Logic operations
25 "and": LogicOp.AND,
26 "or": LogicOp.OR,
27 "xor": LogicOp.XOR,
28 "not": LogicOp.NOT,
29 "eq": LogicOp.EQ,
30 "lt": LogicOp.LT,
31 "lte": LogicOp.LTE,
32 "gt": LogicOp.GT,
33 "gte": LogicOp.GTE,
34 # Routing/branch operations
35 "breq": RoutingOp.BREQ,
36 "brgt": RoutingOp.BRGT,
37 "brge": RoutingOp.BRGE,
38 "brof": RoutingOp.BROF,
39 "sweq": RoutingOp.SWEQ,
40 "swgt": RoutingOp.SWGT,
41 "swge": RoutingOp.SWGE,
42 "swof": RoutingOp.SWOF,
43 "gate": RoutingOp.GATE,
44 "sel": RoutingOp.SEL,
45 "merge": RoutingOp.MRGE,
46 "pass": RoutingOp.PASS,
47 "const": RoutingOp.CONST,
48 "free_ctx": RoutingOp.FREE_CTX, # ALU free (deallocate context slot)
49 # Memory operations
50 "read": MemOp.READ,
51 "write": MemOp.WRITE,
52 "clear": MemOp.CLEAR,
53 "alloc": MemOp.ALLOC,
54 "free": MemOp.FREE, # SM free
55 "rd_inc": MemOp.RD_INC,
56 "rd_dec": MemOp.RD_DEC,
57 "cmp_sw": MemOp.CMP_SW,
58 # Configuration operations (system-level)
59 "load_inst": CfgOp.LOAD_INST,
60 "route_set": CfgOp.ROUTE_SET,
61}
62
63
64# Build reverse mapping with type information to avoid IntEnum collisions
65_reverse_mapping: dict[tuple[type, int], str] = {}
66for mnemonic, op in MNEMONIC_TO_OP.items():
67 _reverse_mapping[(type(op), int(op))] = mnemonic
68
69
70class TypeAwareOpToMnemonicDict:
71 """Collision-free reverse mapping from opcodes to mnemonics.
72
73 Handles IntEnum cross-type equality by using (type, value) tuples internally.
74 Supports dict-like access: OP_TO_MNEMONIC[ArithOp.ADD] returns "add",
75 OP_TO_MNEMONIC[MemOp.READ] returns "read", etc.
76 """
77
78 def __init__(self, mapping: dict[tuple[type, int], str]):
79 """Initialize with a type-indexed mapping.
80
81 Args:
82 mapping: dict from (type, value) tuples to mnemonic strings
83 """
84 self._mapping = mapping
85
86 def __getitem__(self, op: Union[ArithOp, LogicOp, RoutingOp, MemOp, CfgOp]) -> str:
87 """Get the mnemonic for an opcode.
88
89 Args:
90 op: The opcode enum value
91
92 Returns:
93 The mnemonic string
94
95 Raises:
96 KeyError: If the opcode is not in the mapping
97 """
98 key = (type(op), int(op))
99 if key not in self._mapping:
100 raise KeyError(f"Opcode {op} ({type(op).__name__}) not found in mapping")
101 return self._mapping[key]
102
103 def __contains__(self, op: Union[ArithOp, LogicOp, RoutingOp, MemOp, CfgOp]) -> bool:
104 """Check if an opcode is in the mapping."""
105 return (type(op), int(op)) in self._mapping
106
107 def __iter__(self):
108 """Iterate over mnemonic strings."""
109 return iter(self._mapping.values())
110
111 def __len__(self) -> int:
112 """Return the number of opcode-mnemonic pairs."""
113 return len(self._mapping)
114
115 def items(self):
116 """Return an iterator of (opcode_type, mnemonic) pairs for testing.
117
118 This reconstructs the original enum instances from the stored types/values.
119 """
120 result = []
121 for (op_type, op_val), mnemonic in self._mapping.items():
122 result.append((op_type(op_val), mnemonic))
123 return result
124
125
126OP_TO_MNEMONIC: TypeAwareOpToMnemonicDict = TypeAwareOpToMnemonicDict(_reverse_mapping)
127
128
129# Set of opcodes that are always monadic (single input operand).
130# We use a frozenset of (type, value) tuples to avoid IntEnum collisions.
131_MONADIC_OPS_TUPLES: frozenset[tuple[type, int]] = frozenset([
132 # ALU ops: duplicated from cm_inst.is_monadic_alu() for MONADIC_OPS membership testing.
133 # is_monadic() short-circuits to is_monadic_alu() for ALU ops, so these entries
134 # are only reached via `op in MONADIC_OPS` (TypeAwareMonadicOpsSet).
135 (ArithOp, int(ArithOp.INC)),
136 (ArithOp, int(ArithOp.DEC)),
137 (ArithOp, int(ArithOp.SHIFT_L)),
138 (ArithOp, int(ArithOp.SHIFT_R)),
139 (ArithOp, int(ArithOp.ASHFT_R)),
140 # Logic: single input
141 (LogicOp, int(LogicOp.NOT)),
142 # Routing: single input or no ALU involvement
143 (RoutingOp, int(RoutingOp.PASS)),
144 (RoutingOp, int(RoutingOp.CONST)),
145 (RoutingOp, int(RoutingOp.FREE_CTX)),
146 # Memory: single input (monadic SM operations)
147 (MemOp, int(MemOp.READ)),
148 (MemOp, int(MemOp.ALLOC)),
149 (MemOp, int(MemOp.FREE)),
150 (MemOp, int(MemOp.CLEAR)),
151 (MemOp, int(MemOp.RD_INC)),
152 (MemOp, int(MemOp.RD_DEC)),
153])
154
155
156class TypeAwareMonadicOpsSet:
157 """Collision-free set of monadic opcodes.
158
159 Handles IntEnum cross-type equality by using (type, value) tuples internally.
160 Supports membership testing: ArithOp.INC in MONADIC_OPS returns True,
161 but ArithOp.ADD in MONADIC_OPS returns False (collision-free).
162 """
163
164 def __init__(self, tuples: frozenset[tuple[type, int]]):
165 """Initialize with type-indexed tuples.
166
167 Args:
168 tuples: frozenset of (type, value) tuples
169 """
170 self._tuples = tuples
171
172 def __contains__(self, op: Union[ArithOp, LogicOp, RoutingOp, MemOp, CfgOp]) -> bool:
173 """Check if an opcode is in the set, handling IntEnum collisions.
174
175 Args:
176 op: The opcode enum value
177
178 Returns:
179 True if the opcode is monadic, False otherwise
180 """
181 return (type(op), int(op)) in self._tuples
182
183 def __iter__(self):
184 """Iterate over opcode instances in the set."""
185 for op_type, op_val in self._tuples:
186 yield op_type(op_val)
187
188 def __len__(self) -> int:
189 """Return the number of monadic opcodes."""
190 return len(self._tuples)
191
192 def __repr__(self) -> str:
193 """Return a string representation of the set."""
194 ops = list(self)
195 return f"TypeAwareMonadicOpsSet({ops})"
196
197
198MONADIC_OPS: TypeAwareMonadicOpsSet = TypeAwareMonadicOpsSet(_MONADIC_OPS_TUPLES)
199
200
201def is_monadic(op: Union[ArithOp, LogicOp, RoutingOp, MemOp, CfgOp], const: Optional[int] = None) -> bool:
202 """Check if an opcode is monadic (single input operand).
203
204 Args:
205 op: The ALUOp, MemOp, or CfgOp enum value
206 const: Optional const value. Used to determine monadic form of WRITE.
207 If const is not None, WRITE is monadic (cell_addr from const).
208 If const is None, WRITE is dyadic (cell_addr from left operand).
209
210 Returns:
211 True if the opcode is always monadic, or if it's WRITE with const set.
212 False for CMP_SW (always dyadic) and WRITE with const=None (dyadic).
213 CfgOp operations are always monadic (system-level, no ALU involvement).
214 """
215 # CfgOp operations are always monadic (system-level configuration)
216 if type(op) is CfgOp:
217 return True
218
219 # Use canonical is_monadic_alu for ALU operations
220 if isinstance(op, (ArithOp, LogicOp, RoutingOp)):
221 return is_monadic_alu(op)
222
223 # Handle MemOp operations
224 op_tuple = (type(op), int(op))
225 if op_tuple in _MONADIC_OPS_TUPLES:
226 return True
227
228 # Special case: WRITE can be monadic (const given) or dyadic (const not given)
229 if type(op) is MemOp and op == MemOp.WRITE:
230 return const is not None
231
232 return False
233
234
235def is_dyadic(op: Union[ArithOp, LogicOp, RoutingOp, MemOp, CfgOp], const: Optional[int] = None) -> bool:
236 """Check if an opcode is dyadic (two input operands).
237
238 Args:
239 op: The ALUOp, MemOp, or CfgOp enum value
240 const: Optional const value. Used for context-dependent operations like WRITE.
241
242 Returns:
243 True if the opcode is dyadic, False otherwise.
244 """
245 return not is_monadic(op, const)