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, LogicOp, MemOp, RoutingOp, is_monadic_alu
12
13
14# Build mnemonic to opcode mapping
15MNEMONIC_TO_OP: dict[str, Union[ArithOp, LogicOp, RoutingOp, MemOp]] = {
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 "exec": MemOp.EXEC,
59 "raw_read": MemOp.RAW_READ,
60 "set_page": MemOp.SET_PAGE,
61 "write_imm": MemOp.WRITE_IMM,
62 "ext": MemOp.EXT,
63}
64
65
66# Build reverse mapping with type information to avoid IntEnum collisions
67_reverse_mapping: dict[tuple[type, int], str] = {}
68for mnemonic, op in MNEMONIC_TO_OP.items():
69 _reverse_mapping[(type(op), int(op))] = mnemonic
70
71
72class TypeAwareOpToMnemonicDict:
73 """Collision-free reverse mapping from opcodes to mnemonics.
74
75 Handles IntEnum cross-type equality by using (type, value) tuples internally.
76 Supports dict-like access: OP_TO_MNEMONIC[ArithOp.ADD] returns "add",
77 OP_TO_MNEMONIC[MemOp.READ] returns "read", etc.
78 """
79
80 def __init__(self, mapping: dict[tuple[type, int], str]):
81 """Initialize with a type-indexed mapping.
82
83 Args:
84 mapping: dict from (type, value) tuples to mnemonic strings
85 """
86 self._mapping = mapping
87
88 def __getitem__(self, op: Union[ArithOp, LogicOp, RoutingOp, MemOp]) -> str:
89 """Get the mnemonic for an opcode.
90
91 Args:
92 op: The opcode enum value
93
94 Returns:
95 The mnemonic string
96
97 Raises:
98 KeyError: If the opcode is not in the mapping
99 """
100 key = (type(op), int(op))
101 if key not in self._mapping:
102 raise KeyError(f"Opcode {op} ({type(op).__name__}) not found in mapping")
103 return self._mapping[key]
104
105 def __contains__(self, op: Union[ArithOp, LogicOp, RoutingOp, MemOp]) -> bool:
106 """Check if an opcode is in the mapping."""
107 return (type(op), int(op)) in self._mapping
108
109 def __iter__(self):
110 """Iterate over mnemonic strings."""
111 return iter(self._mapping.values())
112
113 def __len__(self) -> int:
114 """Return the number of opcode-mnemonic pairs."""
115 return len(self._mapping)
116
117 def items(self):
118 """Return an iterator of (opcode_type, mnemonic) pairs for testing.
119
120 This reconstructs the original enum instances from the stored types/values.
121 """
122 result = []
123 for (op_type, op_val), mnemonic in self._mapping.items():
124 result.append((op_type(op_val), mnemonic))
125 return result
126
127
128OP_TO_MNEMONIC: TypeAwareOpToMnemonicDict = TypeAwareOpToMnemonicDict(_reverse_mapping)
129
130
131# Set of opcodes that are always monadic (single input operand).
132# We use a frozenset of (type, value) tuples to avoid IntEnum collisions.
133_MONADIC_OPS_TUPLES: frozenset[tuple[type, int]] = frozenset([
134 # ALU ops: duplicated from cm_inst.is_monadic_alu() for MONADIC_OPS membership testing.
135 # is_monadic() short-circuits to is_monadic_alu() for ALU ops, so these entries
136 # are only reached via `op in MONADIC_OPS` (TypeAwareMonadicOpsSet).
137 (ArithOp, int(ArithOp.INC)),
138 (ArithOp, int(ArithOp.DEC)),
139 (ArithOp, int(ArithOp.SHIFT_L)),
140 (ArithOp, int(ArithOp.SHIFT_R)),
141 (ArithOp, int(ArithOp.ASHFT_R)),
142 # Logic: single input
143 (LogicOp, int(LogicOp.NOT)),
144 # Routing: single input or no ALU involvement
145 (RoutingOp, int(RoutingOp.PASS)),
146 (RoutingOp, int(RoutingOp.CONST)),
147 (RoutingOp, int(RoutingOp.FREE_CTX)),
148 # Memory: single input (monadic SM operations)
149 (MemOp, int(MemOp.READ)),
150 (MemOp, int(MemOp.ALLOC)),
151 (MemOp, int(MemOp.FREE)),
152 (MemOp, int(MemOp.CLEAR)),
153 (MemOp, int(MemOp.RD_INC)),
154 (MemOp, int(MemOp.RD_DEC)),
155 (MemOp, int(MemOp.EXEC)),
156 (MemOp, int(MemOp.RAW_READ)),
157 (MemOp, int(MemOp.SET_PAGE)),
158 (MemOp, int(MemOp.WRITE_IMM)),
159 (MemOp, int(MemOp.EXT)),
160])
161
162
163class TypeAwareMonadicOpsSet:
164 """Collision-free set of monadic opcodes.
165
166 Handles IntEnum cross-type equality by using (type, value) tuples internally.
167 Supports membership testing: ArithOp.INC in MONADIC_OPS returns True,
168 but ArithOp.ADD in MONADIC_OPS returns False (collision-free).
169 """
170
171 def __init__(self, tuples: frozenset[tuple[type, int]]):
172 """Initialize with type-indexed tuples.
173
174 Args:
175 tuples: frozenset of (type, value) tuples
176 """
177 self._tuples = tuples
178
179 def __contains__(self, op: Union[ArithOp, LogicOp, RoutingOp, MemOp]) -> bool:
180 """Check if an opcode is in the set, handling IntEnum collisions.
181
182 Args:
183 op: The opcode enum value
184
185 Returns:
186 True if the opcode is monadic, False otherwise
187 """
188 return (type(op), int(op)) in self._tuples
189
190 def __iter__(self):
191 """Iterate over opcode instances in the set."""
192 for op_type, op_val in self._tuples:
193 yield op_type(op_val)
194
195 def __len__(self) -> int:
196 """Return the number of monadic opcodes."""
197 return len(self._tuples)
198
199 def __repr__(self) -> str:
200 """Return a string representation of the set."""
201 ops = list(self)
202 return f"TypeAwareMonadicOpsSet({ops})"
203
204
205MONADIC_OPS: TypeAwareMonadicOpsSet = TypeAwareMonadicOpsSet(_MONADIC_OPS_TUPLES)
206
207
208def is_monadic(op: Union[ArithOp, LogicOp, RoutingOp, MemOp], const: Optional[int] = None) -> bool:
209 """Check if an opcode is monadic (single input operand).
210
211 Args:
212 op: The ALUOp or MemOp enum value
213 const: Optional const value. Used to determine monadic form of WRITE.
214 If const is not None, WRITE is monadic (cell_addr from const).
215 If const is None, WRITE is dyadic (cell_addr from left operand).
216
217 Returns:
218 True if the opcode is always monadic, or if it's WRITE with const set.
219 False for CMP_SW (always dyadic) and WRITE with const=None (dyadic).
220 """
221 # Use canonical is_monadic_alu for ALU operations
222 if isinstance(op, (ArithOp, LogicOp, RoutingOp)):
223 return is_monadic_alu(op)
224
225 # Handle MemOp operations
226 op_tuple = (type(op), int(op))
227 if op_tuple in _MONADIC_OPS_TUPLES:
228 return True
229
230 # Special case: WRITE can be monadic (const given) or dyadic (const not given)
231 if type(op) is MemOp and op == MemOp.WRITE:
232 return const is not None
233
234 return False
235
236
237def is_dyadic(op: Union[ArithOp, LogicOp, RoutingOp, MemOp], const: Optional[int] = None) -> bool:
238 """Check if an opcode is dyadic (two input operands).
239
240 Args:
241 op: The ALUOp or MemOp enum value
242 const: Optional const value. Used for context-dependent operations like WRITE.
243
244 Returns:
245 True if the opcode is dyadic, False otherwise.
246 """
247 return not is_monadic(op, const)