OR-1 dataflow CPU sketch
at main 382 lines 14 kB view raw
1""" 2Property-based tests for ALU execute() function. 3 4Verifies all acceptance criteria: 5- or1-emu.AC2.1: Arithmetic ADD/SUB produce correct 16-bit wrapping results 6- or1-emu.AC2.2: INC/DEC monadic ops correctly increment/decrement 7- or1-emu.AC2.3: Shift ops use const as shift amount; ASHIFTR sign-extends 8- or1-emu.AC2.4: Logic ops (AND, OR, XOR, NOT) produce correct bitwise results 9- or1-emu.AC2.5: Comparison ops interpret operands as signed 2's complement 10- or1-emu.AC2.6: Comparison ops produce 0x0001/0x0000 result and bool_out 11- or1-emu.AC2.7: Routing ops (BR*, SW*, GATE) compute boolean and pass data through 12- or1-emu.AC2.8: PASS returns left operand, CONST returns const field 13- or1-emu.AC2.9: Signed boundary edge case 14""" 15 16import pytest 17from hypothesis import given, example 18 19from emu.alu import execute, to_signed 20from cm_inst import ArithOp, LogicOp, RoutingOp 21from tests.conftest import ( 22 uint16, shift_amount, 23 arith_dyadic_ops, arith_monadic_ops, shift_ops, 24 logic_dyadic_ops, comparison_ops, 25 branch_ops, switch_ops, overflow_ops, 26 data_routing_ops 27) 28 29 30class TestToSigned: 31 """Test the to_signed helper function.""" 32 33 def test_positive_values_unchanged(self): 34 assert to_signed(0x0000) == 0 35 assert to_signed(0x7FFF) == 32767 36 37 def test_negative_values_signed(self): 38 assert to_signed(0x8000) == -32768 39 assert to_signed(0xFFFF) == -1 40 41 42class TestArithmetic: 43 """Test arithmetic operations (AC2.1, AC2.2, AC2.3).""" 44 45 @given(uint16, uint16) 46 def test_add_wrapping(self, a, b): 47 """AC2.1: ADD produces correct 16-bit wrapping results.""" 48 result, bool_out = execute(ArithOp.ADD, a, b, None) 49 expected = (a + b) & 0xFFFF 50 assert result == expected 51 assert bool_out is False 52 assert 0 <= result <= 0xFFFF 53 54 @given(uint16, uint16) 55 def test_sub_wrapping(self, a, b): 56 """AC2.1: SUB produces correct 16-bit wrapping results.""" 57 result, bool_out = execute(ArithOp.SUB, a, b, None) 58 expected = (a - b) & 0xFFFF 59 assert result == expected 60 assert bool_out is False 61 assert 0 <= result <= 0xFFFF 62 63 @given(uint16) 64 @example(0xFFFF) # Edge case: wrap around at 16-bit boundary 65 @example(0) # Edge case: wrap around at lower boundary 66 def test_inc_monadic(self, a): 67 """AC2.2: INC correctly increments.""" 68 result, bool_out = execute(ArithOp.INC, a, None, None) 69 expected = (a + 1) & 0xFFFF 70 assert result == expected 71 assert bool_out is False 72 73 @given(uint16) 74 @example(0) # Edge case: wrap around at lower boundary 75 @example(0xFFFF) # Edge case: general boundary 76 def test_dec_monadic(self, a): 77 """AC2.2: DEC correctly decrements.""" 78 result, bool_out = execute(ArithOp.DEC, a, None, None) 79 expected = (a - 1) & 0xFFFF 80 assert result == expected 81 assert bool_out is False 82 83 @given(uint16, shift_amount) 84 def test_shift_left(self, a, shift): 85 """AC2.3: SHL produces correct left shift results.""" 86 result, bool_out = execute(ArithOp.SHL, a, None, shift) 87 expected = (a << shift) & 0xFFFF 88 assert result == expected 89 assert bool_out is False 90 91 @given(uint16, shift_amount) 92 def test_shift_right(self, a, shift): 93 """AC2.3: SHR produces correct right shift results.""" 94 result, bool_out = execute(ArithOp.SHR, a, None, shift) 95 expected = a >> shift 96 assert result == expected 97 assert bool_out is False 98 99 @given(uint16, shift_amount) 100 @example(0x8000, 1) # Edge case: sign extension test 101 def test_arithmetic_shift_right(self, a, shift): 102 """AC2.3: ASR sign-extends from bit 15.""" 103 result, bool_out = execute(ArithOp.ASR, a, None, shift) 104 signed = to_signed(a) 105 expected = (signed >> shift) & 0xFFFF 106 assert result == expected 107 assert bool_out is False 108 109 110class TestLogic: 111 """Test logic operations (AC2.4, AC2.5, AC2.6).""" 112 113 @given(uint16, uint16) 114 def test_and(self, a, b): 115 """AC2.4: AND produces correct bitwise result.""" 116 result, bool_out = execute(LogicOp.AND, a, b, None) 117 expected = (a & b) & 0xFFFF 118 assert result == expected 119 assert bool_out is False 120 121 @given(uint16, uint16) 122 def test_or(self, a, b): 123 """AC2.4: OR produces correct bitwise result.""" 124 result, bool_out = execute(LogicOp.OR, a, b, None) 125 expected = (a | b) & 0xFFFF 126 assert result == expected 127 assert bool_out is False 128 129 @given(uint16, uint16) 130 def test_xor(self, a, b): 131 """AC2.4: XOR produces correct bitwise result.""" 132 result, bool_out = execute(LogicOp.XOR, a, b, None) 133 expected = (a ^ b) & 0xFFFF 134 assert result == expected 135 assert bool_out is False 136 137 @given(uint16) 138 def test_not(self, a): 139 """AC2.4: NOT produces correct bitwise result.""" 140 result, bool_out = execute(LogicOp.NOT, a, None, None) 141 expected = (~a) & 0xFFFF 142 assert result == expected 143 assert bool_out is False 144 145 146class TestComparison: 147 """Test comparison operations (AC2.5, AC2.6).""" 148 149 @given(uint16, uint16) 150 def test_eq_result_format(self, a, b): 151 """AC2.6: EQ returns exactly 0x0001 or 0x0000.""" 152 result, bool_out = execute(LogicOp.EQ, a, b, None) 153 assert result in (0x0000, 0x0001) 154 expected_bool = (to_signed(a) == to_signed(b)) 155 assert bool_out == expected_bool 156 assert result == (0x0001 if bool_out else 0x0000) 157 158 @given(uint16, uint16) 159 @example(0xFFFF, 0x0001) # AC2.5: -1 < 1 160 def test_lt_signed_semantics(self, a, b): 161 """AC2.5, AC2.6: LT interprets as signed and returns boolean.""" 162 result, bool_out = execute(LogicOp.LT, a, b, None) 163 sa, sb = to_signed(a), to_signed(b) 164 expected_bool = sa < sb 165 assert result in (0x0000, 0x0001) 166 assert bool_out == expected_bool 167 assert result == (0x0001 if bool_out else 0x0000) 168 169 @given(uint16, uint16) 170 def test_lte_signed_semantics(self, a, b): 171 """AC2.5, AC2.6: LTE interprets as signed and returns boolean.""" 172 result, bool_out = execute(LogicOp.LTE, a, b, None) 173 sa, sb = to_signed(a), to_signed(b) 174 expected_bool = sa <= sb 175 assert result in (0x0000, 0x0001) 176 assert bool_out == expected_bool 177 178 @given(uint16, uint16) 179 @example(0x7FFF, 0x8000) # AC2.9: 32767 > -32768 180 def test_gt_signed_semantics(self, a, b): 181 """AC2.5, AC2.6, AC2.9: GT interprets as signed boundary.""" 182 result, bool_out = execute(LogicOp.GT, a, b, None) 183 sa, sb = to_signed(a), to_signed(b) 184 expected_bool = sa > sb 185 assert result in (0x0000, 0x0001) 186 assert bool_out == expected_bool 187 assert result == (0x0001 if bool_out else 0x0000) 188 189 @given(uint16, uint16) 190 def test_gte_signed_semantics(self, a, b): 191 """AC2.5, AC2.6: GTE interprets as signed and returns boolean.""" 192 result, bool_out = execute(LogicOp.GTE, a, b, None) 193 sa, sb = to_signed(a), to_signed(b) 194 expected_bool = sa >= sb 195 assert result in (0x0000, 0x0001) 196 assert bool_out == expected_bool 197 198 199class TestRouting: 200 """Test routing operations (AC2.7, AC2.8).""" 201 202 @given(uint16, uint16) 203 def test_breq_boolean_and_passthrough(self, a, b): 204 """AC2.7: BREQ computes boolean and passes left through.""" 205 result, bool_out = execute(RoutingOp.BREQ, a, b, None) 206 expected_bool = (to_signed(a) == to_signed(b)) 207 assert result == a 208 assert bool_out == expected_bool 209 210 @given(uint16, uint16) 211 def test_brgt_boolean_and_passthrough(self, a, b): 212 """AC2.7: BRGT computes boolean and passes left through.""" 213 result, bool_out = execute(RoutingOp.BRGT, a, b, None) 214 expected_bool = (to_signed(a) > to_signed(b)) 215 assert result == a 216 assert bool_out == expected_bool 217 218 @given(uint16, uint16) 219 def test_brge_boolean_and_passthrough(self, a, b): 220 """AC2.7: BRGE computes boolean and passes left through.""" 221 result, bool_out = execute(RoutingOp.BRGE, a, b, None) 222 expected_bool = (to_signed(a) >= to_signed(b)) 223 assert result == a 224 assert bool_out == expected_bool 225 226 @given(uint16, uint16) 227 def test_brof_overflow_detection(self, a, b): 228 """AC2.7: BROF detects unsigned overflow.""" 229 result, bool_out = execute(RoutingOp.BROF, a, b, None) 230 raw = a + b 231 expected_bool = raw > 0xFFFF 232 assert result == a 233 assert bool_out == expected_bool 234 235 @given(uint16, uint16) 236 def test_sweq_boolean_and_passthrough(self, a, b): 237 """AC2.7: SWEQ computes boolean and passes left through.""" 238 result, bool_out = execute(RoutingOp.SWEQ, a, b, None) 239 expected_bool = (to_signed(a) == to_signed(b)) 240 assert result == a 241 assert bool_out == expected_bool 242 243 @given(uint16, uint16) 244 def test_swgt_boolean_and_passthrough(self, a, b): 245 """AC2.7: SWGT computes boolean and passes left through.""" 246 result, bool_out = execute(RoutingOp.SWGT, a, b, None) 247 expected_bool = (to_signed(a) > to_signed(b)) 248 assert result == a 249 assert bool_out == expected_bool 250 251 @given(uint16, uint16) 252 def test_swge_boolean_and_passthrough(self, a, b): 253 """AC2.7: SWGE computes boolean and passes left through.""" 254 result, bool_out = execute(RoutingOp.SWGE, a, b, None) 255 expected_bool = (to_signed(a) >= to_signed(b)) 256 assert result == a 257 assert bool_out == expected_bool 258 259 @given(uint16, uint16) 260 def test_swof_overflow_detection(self, a, b): 261 """AC2.7: SWOF detects unsigned overflow.""" 262 result, bool_out = execute(RoutingOp.SWOF, a, b, None) 263 raw = a + b 264 expected_bool = raw > 0xFFFF 265 assert result == a 266 assert bool_out == expected_bool 267 268 @given(uint16, uint16) 269 def test_gate_boolean_from_right(self, a, b): 270 """AC2.7: GATE computes bool from right operand, passes left.""" 271 result, bool_out = execute(RoutingOp.GATE, a, b, None) 272 expected_bool = b != 0 273 assert result == a 274 assert bool_out == expected_bool 275 276 @given(uint16) 277 def test_pass_returns_left(self, a): 278 """AC2.8: PASS returns left operand unchanged.""" 279 result, bool_out = execute(RoutingOp.PASS, a, None, None) 280 assert result == a 281 assert bool_out is False 282 283 @given(uint16) 284 def test_const_returns_const_field(self, c): 285 """AC2.8: CONST returns const field masked to 16-bit.""" 286 result, bool_out = execute(RoutingOp.CONST, 0, None, c) 287 expected = c & 0xFFFF 288 assert result == expected 289 assert bool_out is False 290 291 @given(uint16, uint16) 292 def test_sel_conditional_mux(self, a, b): 293 """AC2.7: SEL is conditional mux - returns right when left != 0.""" 294 result, bool_out = execute(RoutingOp.SEL, a, b, None) 295 expected_bool = a != 0 296 if expected_bool: 297 assert result == b 298 else: 299 assert result == a 300 assert bool_out == expected_bool 301 302 @given(uint16, uint16) 303 def test_mrge_merge(self, a, b): 304 """AC2.7: MRGE passes left through.""" 305 result, bool_out = execute(RoutingOp.MRGE, a, b, None) 306 assert result == a 307 assert bool_out is False 308 309 @given(uint16) 310 def test_free_returns_zero(self, a): 311 """AC2.7: FREE_FRAME returns 0.""" 312 result, bool_out = execute(RoutingOp.FREE_FRAME, a, None, None) 313 assert result == 0 314 assert bool_out is False 315 316 317class TestResultBounds: 318 """Test that all results stay within 16-bit bounds.""" 319 320 @given(arith_dyadic_ops, uint16, uint16) 321 def test_arith_dyadic_in_bounds(self, op, a, b): 322 """All arithmetic dyadic ops produce 16-bit results.""" 323 result, _ = execute(op, a, b, None) 324 assert 0 <= result <= 0xFFFF 325 326 @given(arith_monadic_ops, uint16) 327 def test_arith_monadic_in_bounds(self, op, a): 328 """All arithmetic monadic ops produce 16-bit results.""" 329 result, _ = execute(op, a, None, None) 330 assert 0 <= result <= 0xFFFF 331 332 @given(shift_ops, uint16, shift_amount) 333 def test_shift_in_bounds(self, op, a, shift): 334 """All shift ops produce 16-bit results.""" 335 result, _ = execute(op, a, None, shift) 336 assert 0 <= result <= 0xFFFF 337 338 @given(logic_dyadic_ops, uint16, uint16) 339 def test_logic_dyadic_in_bounds(self, op, a, b): 340 """All logic dyadic ops produce 16-bit results.""" 341 result, _ = execute(op, a, b, None) 342 assert 0 <= result <= 0xFFFF 343 344 @given(comparison_ops, uint16, uint16) 345 def test_comparison_in_bounds(self, op, a, b): 346 """All comparison ops produce 0x0000 or 0x0001.""" 347 result, _ = execute(op, a, b, None) 348 assert result in (0x0000, 0x0001) 349 350 @given(branch_ops, uint16, uint16) 351 def test_branch_in_bounds(self, op, a, b): 352 """All branch ops pass left through (in bounds by definition).""" 353 result, _ = execute(op, a, b, None) 354 assert result == a 355 assert 0 <= result <= 0xFFFF 356 357 358class TestBoolOutType: 359 """Test that bool_out is always Python bool.""" 360 361 @given(uint16, uint16) 362 def test_bool_out_is_bool(self, a, b): 363 """bool_out must be Python bool type.""" 364 for op in [ArithOp.ADD, LogicOp.AND, RoutingOp.PASS]: 365 _, bool_out = execute(op, a, b, None) 366 assert isinstance(bool_out, bool) 367 368 369class TestUnknownOpcode: 370 """Test handling of unknown opcodes.""" 371 372 def test_unknown_opcode_raises_value_error(self): 373 """Unknown opcode raises ValueError.""" 374 # Create an invalid opcode-like value that's not in any ALUOp enum 375 class UnknownOp: 376 def __repr__(self): 377 return "UnknownOp" 378 379 unknown_op = UnknownOp() 380 381 with pytest.raises(ValueError, match="Unknown ALU operation"): 382 execute(unknown_op, 0x1234, 0x5678, None)