OR-1 dataflow CPU sketch
at pe-frame-redesign 347 lines 14 kB view raw
1"""Tests for asm.opcodes module — mnemonic mapping and arity classification.""" 2 3import pytest 4from cm_inst import ArithOp, LogicOp, MemOp, RoutingOp 5from asm.opcodes import ( 6 MNEMONIC_TO_OP, 7 OP_TO_MNEMONIC, 8 MONADIC_OPS, 9 is_monadic, 10 is_dyadic, 11) 12 13 14class TestMnemonicToOpMapping: 15 """Verify all ALU opcodes map to correct enum values.""" 16 17 def test_arithmetic_opcodes(self): 18 """or1-asm.AC1.1: Arithmetic opcodes map correctly.""" 19 assert MNEMONIC_TO_OP["add"] == ArithOp.ADD 20 assert MNEMONIC_TO_OP["sub"] == ArithOp.SUB 21 assert MNEMONIC_TO_OP["inc"] == ArithOp.INC 22 assert MNEMONIC_TO_OP["dec"] == ArithOp.DEC 23 assert MNEMONIC_TO_OP["shl"] == ArithOp.SHL 24 assert MNEMONIC_TO_OP["shr"] == ArithOp.SHR 25 assert MNEMONIC_TO_OP["asr"] == ArithOp.ASR 26 27 def test_logic_opcodes(self): 28 """or1-asm.AC1.1: Logic opcodes map correctly.""" 29 assert MNEMONIC_TO_OP["and"] == LogicOp.AND 30 assert MNEMONIC_TO_OP["or"] == LogicOp.OR 31 assert MNEMONIC_TO_OP["xor"] == LogicOp.XOR 32 assert MNEMONIC_TO_OP["not"] == LogicOp.NOT 33 assert MNEMONIC_TO_OP["eq"] == LogicOp.EQ 34 assert MNEMONIC_TO_OP["lt"] == LogicOp.LT 35 assert MNEMONIC_TO_OP["lte"] == LogicOp.LTE 36 assert MNEMONIC_TO_OP["gt"] == LogicOp.GT 37 assert MNEMONIC_TO_OP["gte"] == LogicOp.GTE 38 39 def test_branch_opcodes(self): 40 """or1-asm.AC1.1: Branch opcodes map correctly.""" 41 assert MNEMONIC_TO_OP["breq"] == RoutingOp.BREQ 42 assert MNEMONIC_TO_OP["brgt"] == RoutingOp.BRGT 43 assert MNEMONIC_TO_OP["brge"] == RoutingOp.BRGE 44 assert MNEMONIC_TO_OP["brof"] == RoutingOp.BROF 45 46 def test_switch_opcodes(self): 47 """or1-asm.AC1.1: Switch opcodes map correctly.""" 48 assert MNEMONIC_TO_OP["sweq"] == RoutingOp.SWEQ 49 assert MNEMONIC_TO_OP["swgt"] == RoutingOp.SWGT 50 assert MNEMONIC_TO_OP["swge"] == RoutingOp.SWGE 51 assert MNEMONIC_TO_OP["swof"] == RoutingOp.SWOF 52 53 def test_control_opcodes(self): 54 """or1-asm.AC1.1: Control/routing opcodes map correctly.""" 55 assert MNEMONIC_TO_OP["gate"] == RoutingOp.GATE 56 assert MNEMONIC_TO_OP["sel"] == RoutingOp.SEL 57 assert MNEMONIC_TO_OP["merge"] == RoutingOp.MRGE 58 assert MNEMONIC_TO_OP["pass"] == RoutingOp.PASS 59 assert MNEMONIC_TO_OP["const"] == RoutingOp.CONST 60 assert MNEMONIC_TO_OP["free_frame"] == RoutingOp.FREE_FRAME 61 62 def test_memory_opcodes(self): 63 """or1-asm.AC1.2: Memory opcodes map correctly.""" 64 assert MNEMONIC_TO_OP["read"] == MemOp.READ 65 assert MNEMONIC_TO_OP["write"] == MemOp.WRITE 66 assert MNEMONIC_TO_OP["clear"] == MemOp.CLEAR 67 assert MNEMONIC_TO_OP["alloc"] == MemOp.ALLOC 68 assert MNEMONIC_TO_OP["free"] == MemOp.FREE 69 assert MNEMONIC_TO_OP["rd_inc"] == MemOp.RD_INC 70 assert MNEMONIC_TO_OP["rd_dec"] == MemOp.RD_DEC 71 assert MNEMONIC_TO_OP["cmp_sw"] == MemOp.CMP_SW 72 73 def test_mnemonic_to_op_count(self): 74 """Verify all expected mnemonics are present.""" 75 # Total: 7 arithmetic + 9 logic + 4 branch + 4 switch + 8 control + 13 memory = 45 76 # (CfgOp removed: -2 load_inst, route_set; new MemOps added: +5 exec, raw_read, set_page, write_imm, ext) 77 # (new RoutingOps added: +2 extract_tag, alloc_remote) 78 expected_count = 45 79 assert len(MNEMONIC_TO_OP) == expected_count 80 81 82class TestOpcodeRoundTrip: 83 """Verify round-trip mapping: mnemonic -> op -> mnemonic.""" 84 85 @pytest.mark.parametrize( 86 "mnemonic", 87 [ 88 # Arithmetic 89 "add", "sub", "inc", "dec", "shl", "shr", "asr", 90 # Logic 91 "and", "or", "xor", "not", "eq", "lt", "lte", "gt", "gte", 92 # Branch 93 "breq", "brgt", "brge", "brof", 94 # Switch 95 "sweq", "swgt", "swge", "swof", 96 # Control 97 "gate", "sel", "merge", "pass", "const", "free_frame", "extract_tag", "alloc_remote", 98 # Memory 99 "read", "write", "clear", "alloc", "free", "rd_inc", "rd_dec", "cmp_sw", 100 "exec", "raw_read", "set_page", "write_imm", "ext", 101 ] 102 ) 103 def test_round_trip(self, mnemonic): 104 """Every mnemonic should round-trip through the mapping.""" 105 op = MNEMONIC_TO_OP[mnemonic] 106 recovered_mnemonic = OP_TO_MNEMONIC[op] 107 assert recovered_mnemonic == mnemonic 108 109 @pytest.mark.parametrize( 110 "mnemonic", 111 [ 112 # Arithmetic 113 "add", "sub", "inc", "dec", "shl", "shr", "asr", 114 # Logic 115 "and", "or", "xor", "not", "eq", "lt", "lte", "gt", "gte", 116 # Branch 117 "breq", "brgt", "brge", "brof", 118 # Switch 119 "sweq", "swgt", "swge", "swof", 120 # Control 121 "gate", "sel", "merge", "pass", "const", "free_frame", "extract_tag", "alloc_remote", 122 # Memory 123 "read", "write", "clear", "alloc", "free", "rd_inc", "rd_dec", "cmp_sw", 124 "exec", "raw_read", "set_page", "write_imm", "ext", 125 ] 126 ) 127 def test_round_trip_via_dict(self, mnemonic): 128 """Every mnemonic should round-trip via OP_TO_MNEMONIC dict directly. 129 130 This test verifies the dict is collision-free. For example: 131 - OP_TO_MNEMONIC[ArithOp.ADD] must return 'add' (not 'read') 132 - OP_TO_MNEMONIC[MemOp.READ] must return 'read' (not overwritten) 133 """ 134 op = MNEMONIC_TO_OP[mnemonic] 135 recovered_mnemonic = OP_TO_MNEMONIC[op] 136 assert recovered_mnemonic == mnemonic 137 138 139class TestMonadicOpsSet: 140 """Verify MONADIC_OPS contains exactly the right opcodes.""" 141 142 def test_monadic_ops_size(self): 143 """MONADIC_OPS should have exactly 22 opcodes (collision-free). 144 145 Without collision-free implementation, this would be lower due to IntEnum 146 collisions (e.g., ArithOp.INC colliding with some MemOp value). 147 Count: 5 arithmetic + 1 logic + 5 routing + 5 old memory + 5 new memory + 1 WRITE_IMM = 22 148 (Added: EXTRACT_TAG, ALLOC_REMOTE, WRITE_IMM, FREE_FRAME as monadic routing ops) 149 """ 150 assert len(MONADIC_OPS) == 22 151 152 def test_monadic_opcodes_present(self): 153 """All known monadic opcodes should be in the set.""" 154 monadic_list = [ 155 # Arithmetic (single input + const) 156 ArithOp.INC, ArithOp.DEC, 157 ArithOp.SHL, ArithOp.SHR, ArithOp.ASR, 158 # Logic 159 LogicOp.NOT, 160 # Routing 161 RoutingOp.PASS, RoutingOp.CONST, RoutingOp.FREE_FRAME, 162 RoutingOp.EXTRACT_TAG, RoutingOp.ALLOC_REMOTE, 163 # Memory 164 MemOp.READ, MemOp.ALLOC, MemOp.FREE, MemOp.CLEAR, 165 MemOp.RD_INC, MemOp.RD_DEC, MemOp.WRITE_IMM, 166 ] 167 for op in monadic_list: 168 assert op in MONADIC_OPS, f"{op} should be in MONADIC_OPS" 169 assert is_monadic(op), f"{op} should be monadic" 170 171 def test_collision_free_membership(self): 172 """MONADIC_OPS membership must be collision-free. 173 174 Due to IntEnum cross-type equality, ArithOp.ADD (0) equals MemOp.READ (0). 175 Without collision-free implementation, ArithOp.ADD in MONADIC_OPS would 176 return True because MemOp.READ is in the set. 177 """ 178 # ArithOp.ADD and MemOp.READ have the same value (0) so they're equal 179 assert ArithOp.ADD == MemOp.READ 180 # But only MemOp.READ should be in MONADIC_OPS, not ArithOp.ADD 181 assert MemOp.READ in MONADIC_OPS 182 assert ArithOp.ADD not in MONADIC_OPS 183 184 def test_context_dependent_not_in_monadic(self): 185 """WRITE should not be in MONADIC_OPS (context-dependent).""" 186 assert not is_monadic(MemOp.WRITE, const=None) 187 188 def test_always_dyadic_not_in_monadic(self): 189 """CMP_SW should not be in MONADIC_OPS (always dyadic).""" 190 assert not is_monadic(MemOp.CMP_SW) 191 192 def test_dyadic_opcodes_not_in_monadic(self): 193 """All known dyadic opcodes should be dyadic.""" 194 dyadic_list = [ 195 # Arithmetic (two inputs) 196 ArithOp.ADD, ArithOp.SUB, 197 # Logic (two inputs) 198 LogicOp.AND, LogicOp.OR, LogicOp.XOR, 199 LogicOp.EQ, LogicOp.LT, LogicOp.LTE, LogicOp.GT, LogicOp.GTE, 200 # Branch/switch (two inputs + dest) 201 RoutingOp.BREQ, RoutingOp.BRGT, RoutingOp.BRGE, RoutingOp.BROF, 202 RoutingOp.SWEQ, RoutingOp.SWGT, RoutingOp.SWGE, RoutingOp.SWOF, 203 RoutingOp.GATE, RoutingOp.SEL, RoutingOp.MRGE, 204 # Memory 205 MemOp.WRITE, # Usually dyadic 206 MemOp.CMP_SW, # Always dyadic 207 ] 208 for op in dyadic_list: 209 assert is_dyadic(op), f"{op} should be dyadic" 210 211 212class TestIsMonadicFunction: 213 """Verify is_monadic() function works correctly.""" 214 215 @pytest.mark.parametrize( 216 "op", 217 [ 218 ArithOp.INC, ArithOp.DEC, 219 ArithOp.SHL, ArithOp.SHR, ArithOp.ASR, 220 LogicOp.NOT, 221 RoutingOp.PASS, RoutingOp.CONST, RoutingOp.FREE_FRAME, 222 RoutingOp.EXTRACT_TAG, RoutingOp.ALLOC_REMOTE, 223 MemOp.READ, MemOp.ALLOC, MemOp.FREE, MemOp.CLEAR, 224 MemOp.RD_INC, MemOp.RD_DEC, 225 MemOp.EXEC, MemOp.RAW_READ, MemOp.SET_PAGE, MemOp.WRITE_IMM, MemOp.EXT, 226 ] 227 ) 228 def test_always_monadic_opcodes(self, op): 229 """Always-monadic opcodes should return True regardless of const.""" 230 assert is_monadic(op) is True 231 assert is_monadic(op, const=None) is True 232 assert is_monadic(op, const=42) is True 233 234 def test_write_monadic_with_const(self): 235 """WRITE with const should be monadic.""" 236 assert is_monadic(MemOp.WRITE, const=42) is True 237 238 def test_write_dyadic_without_const(self): 239 """WRITE without const should be dyadic.""" 240 assert is_monadic(MemOp.WRITE, const=None) is False 241 assert is_monadic(MemOp.WRITE) is False 242 243 def test_cmp_sw_always_dyadic(self): 244 """CMP_SW should always be dyadic.""" 245 assert is_monadic(MemOp.CMP_SW) is False 246 assert is_monadic(MemOp.CMP_SW, const=None) is False 247 assert is_monadic(MemOp.CMP_SW, const=42) is False 248 249 @pytest.mark.parametrize( 250 "op", 251 [ 252 ArithOp.ADD, ArithOp.SUB, 253 LogicOp.AND, LogicOp.OR, LogicOp.XOR, 254 LogicOp.EQ, LogicOp.LT, LogicOp.LTE, LogicOp.GT, LogicOp.GTE, 255 RoutingOp.BREQ, RoutingOp.BRGT, RoutingOp.BRGE, RoutingOp.BROF, 256 RoutingOp.SWEQ, RoutingOp.SWGT, RoutingOp.SWGE, RoutingOp.SWOF, 257 RoutingOp.GATE, RoutingOp.SEL, RoutingOp.MRGE, 258 ] 259 ) 260 def test_dyadic_opcodes(self, op): 261 """Dyadic opcodes should return False.""" 262 assert is_monadic(op) is False 263 264 265class TestIsDyadicFunction: 266 """Verify is_dyadic() function is the inverse of is_monadic().""" 267 268 @pytest.mark.parametrize( 269 "op", 270 [ 271 ArithOp.INC, ArithOp.DEC, 272 ArithOp.SHL, ArithOp.SHR, ArithOp.ASR, 273 LogicOp.NOT, 274 RoutingOp.PASS, RoutingOp.CONST, RoutingOp.FREE_FRAME, 275 RoutingOp.EXTRACT_TAG, RoutingOp.ALLOC_REMOTE, 276 MemOp.READ, MemOp.ALLOC, MemOp.FREE, MemOp.CLEAR, 277 MemOp.RD_INC, MemOp.RD_DEC, 278 MemOp.EXEC, MemOp.RAW_READ, MemOp.SET_PAGE, MemOp.WRITE_IMM, MemOp.EXT, 279 ] 280 ) 281 def test_always_monadic_is_not_dyadic(self, op): 282 """Always-monadic should be not dyadic.""" 283 assert is_dyadic(op) is False 284 285 def test_write_context_dependent(self): 286 """WRITE arity is context-dependent via const.""" 287 assert is_dyadic(MemOp.WRITE, const=42) is False # monadic 288 assert is_dyadic(MemOp.WRITE, const=None) is True # dyadic 289 290 def test_cmp_sw_always_dyadic(self): 291 """CMP_SW is always dyadic.""" 292 assert is_dyadic(MemOp.CMP_SW) is True 293 assert is_dyadic(MemOp.CMP_SW, const=42) is True 294 295 @pytest.mark.parametrize( 296 "op", 297 [ 298 ArithOp.ADD, ArithOp.SUB, 299 LogicOp.AND, LogicOp.OR, LogicOp.XOR, 300 LogicOp.EQ, LogicOp.LT, LogicOp.LTE, LogicOp.GT, LogicOp.GTE, 301 RoutingOp.BREQ, RoutingOp.BRGT, RoutingOp.BRGE, RoutingOp.BROF, 302 RoutingOp.SWEQ, RoutingOp.SWGT, RoutingOp.SWGE, RoutingOp.SWOF, 303 RoutingOp.GATE, RoutingOp.SEL, RoutingOp.MRGE, 304 ] 305 ) 306 def test_dyadic_opcodes_is_dyadic(self, op): 307 """Dyadic opcodes should be dyadic.""" 308 assert is_dyadic(op) is True 309 310 311class TestAC3_2TierGrouping: 312 """AC3.2: MemOp integer values preserve tier 1/tier 2 encoding boundaries.""" 313 314 def test_tier1_ops_have_values_0_through_5(self): 315 """Tier 1 memory operations must have values in range [0, 5].""" 316 tier1 = [MemOp.READ, MemOp.WRITE, MemOp.EXEC, MemOp.ALLOC, MemOp.FREE, MemOp.EXT] 317 for op in tier1: 318 assert op.value <= 5, f"{op.name} has value {op.value}, expected <= 5" 319 320 def test_tier2_ops_have_values_6_through_12(self): 321 """Tier 2 memory operations must have values in range [6, 12].""" 322 tier2 = [MemOp.CLEAR, MemOp.RD_INC, MemOp.RD_DEC, MemOp.CMP_SW, MemOp.RAW_READ, MemOp.SET_PAGE, MemOp.WRITE_IMM] 323 for op in tier2: 324 assert op.value >= 6 and op.value <= 12, f"{op.name} has value {op.value}, expected in range [6, 12]" 325 326 327class TestFreeDisambiguation: 328 """Verify free_ctx (ALU) and free (SM) are distinct and round-trip correctly.""" 329 330 def test_free_frame_is_routing_op(self): 331 """The free_frame mnemonic should map to ALU RoutingOp.FREE_FRAME.""" 332 assert MNEMONIC_TO_OP["free_frame"] == RoutingOp.FREE_FRAME 333 334 def test_free_is_memop(self): 335 """The free mnemonic should map to SM MemOp.FREE.""" 336 assert MNEMONIC_TO_OP["free"] == MemOp.FREE 337 338 def test_both_free_round_trip(self): 339 """Both free_frame and free should round-trip correctly.""" 340 # free_frame -> RoutingOp.FREE_FRAME -> free_frame 341 assert OP_TO_MNEMONIC[RoutingOp.FREE_FRAME] == "free_frame" 342 assert OP_TO_MNEMONIC[MemOp.FREE] == "free" 343 344 def test_no_collision(self): 345 """Distinct enum types should not collide in reverse mapping.""" 346 # RoutingOp.FREE_FRAME and MemOp.FREE are different enum values 347 assert RoutingOp.FREE_FRAME != MemOp.FREE