OR-1 dataflow CPU sketch
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)