OR-1 dataflow CPU sketch
1"""Tests for dfgraph/categories.py — opcode-to-category mapping.
2
3Tests verify:
4- Every opcode in MNEMONIC_TO_OP has a valid category via categorise()
5- ArithOp members map to ARITHMETIC
6- LogicOp logic ops map to LOGIC
7- LogicOp comparison ops map to COMPARISON
8- RoutingOp members (except CONST/FREE_CTX) map to ROUTING
9- RoutingOp.CONST and FREE_CTX map to CONFIG
10- MemOp members map to MEMORY
11- Every OpcodeCategory has a colour in CATEGORY_COLOURS
12"""
13
14import pytest
15
16from cm_inst import ArithOp, LogicOp, MemOp, RoutingOp
17from asm.opcodes import MNEMONIC_TO_OP
18from dfgraph.categories import categorise, OpcodeCategory, CATEGORY_COLOURS
19
20
21class TestCategoriseArithOp:
22 """Tests for ArithOp -> ARITHMETIC category mapping."""
23
24 @pytest.mark.parametrize("op", [
25 ArithOp.ADD,
26 ArithOp.SUB,
27 ArithOp.INC,
28 ArithOp.DEC,
29 ArithOp.SHIFT_L,
30 ArithOp.SHIFT_R,
31 ArithOp.ASHFT_R,
32 ])
33 def test_arith_ops_map_to_arithmetic(self, op):
34 """All ArithOp members map to ARITHMETIC category."""
35 assert categorise(op) == OpcodeCategory.ARITHMETIC
36
37
38class TestCategoriseLogicOp:
39 """Tests for LogicOp -> LOGIC or COMPARISON category mapping."""
40
41 @pytest.mark.parametrize("op", [
42 LogicOp.AND,
43 LogicOp.OR,
44 LogicOp.XOR,
45 LogicOp.NOT,
46 ])
47 def test_logic_ops_map_to_logic(self, op):
48 """Pure logic opcodes (AND, OR, XOR, NOT) map to LOGIC category."""
49 assert categorise(op) == OpcodeCategory.LOGIC
50
51 @pytest.mark.parametrize("op", [
52 LogicOp.EQ,
53 LogicOp.LT,
54 LogicOp.LTE,
55 LogicOp.GT,
56 LogicOp.GTE,
57 ])
58 def test_comparison_ops_map_to_comparison(self, op):
59 """Comparison opcodes (EQ, LT, LTE, GT, GTE) map to COMPARISON category."""
60 assert categorise(op) == OpcodeCategory.COMPARISON
61
62
63class TestCategoriseRoutingOp:
64 """Tests for RoutingOp -> ROUTING or CONFIG category mapping."""
65
66 @pytest.mark.parametrize("op", [
67 RoutingOp.BREQ,
68 RoutingOp.BRGT,
69 RoutingOp.BRGE,
70 RoutingOp.BROF,
71 RoutingOp.SWEQ,
72 RoutingOp.SWGT,
73 RoutingOp.SWGE,
74 RoutingOp.SWOF,
75 RoutingOp.GATE,
76 RoutingOp.PASS,
77 RoutingOp.SEL,
78 RoutingOp.MRGE,
79 ])
80 def test_routing_ops_map_to_routing(self, op):
81 """Routing/branch/switch/control opcodes map to ROUTING category."""
82 assert categorise(op) == OpcodeCategory.ROUTING
83
84 @pytest.mark.parametrize("op", [
85 RoutingOp.CONST,
86 RoutingOp.FREE_CTX,
87 ])
88 def test_config_routing_ops_map_to_config(self, op):
89 """RoutingOp.CONST and RoutingOp.FREE_CTX map to CONFIG category."""
90 assert categorise(op) == OpcodeCategory.CONFIG
91
92
93class TestCategoriseMemOp:
94 """Tests for MemOp -> MEMORY category mapping."""
95
96 @pytest.mark.parametrize("op", [
97 MemOp.READ,
98 MemOp.WRITE,
99 MemOp.CLEAR,
100 MemOp.ALLOC,
101 MemOp.FREE,
102 MemOp.RD_INC,
103 MemOp.RD_DEC,
104 MemOp.CMP_SW,
105 MemOp.EXEC,
106 MemOp.RAW_READ,
107 MemOp.SET_PAGE,
108 MemOp.WRITE_IMM,
109 MemOp.EXT,
110 ])
111 def test_memory_ops_map_to_memory(self, op):
112 """All MemOp members map to MEMORY category."""
113 assert categorise(op) == OpcodeCategory.MEMORY
114
115
116class TestCategoriseMnemonicToOp:
117 """Tests for all opcodes in MNEMONIC_TO_OP."""
118
119 @pytest.mark.parametrize("mnemonic, op", MNEMONIC_TO_OP.items())
120 def test_all_mnemonics_have_category(self, mnemonic, op):
121 """Every opcode in MNEMONIC_TO_OP has a valid category via categorise()."""
122 # Should not raise ValueError
123 category = categorise(op)
124 assert isinstance(category, OpcodeCategory)
125
126
127class TestCategoryColours:
128 """Tests for CATEGORY_COLOURS mapping."""
129
130 def test_all_categories_have_colour(self):
131 """Every OpcodeCategory has a colour in CATEGORY_COLOURS."""
132 for category in OpcodeCategory:
133 assert category in CATEGORY_COLOURS
134 colour = CATEGORY_COLOURS[category]
135 assert isinstance(colour, str)
136 assert colour.startswith("#")
137
138 def test_colours_are_valid_hex(self):
139 """All colours are valid 6-digit hex codes."""
140 for category, colour in CATEGORY_COLOURS.items():
141 assert len(colour) == 7, f"Colour for {category} is not 7 chars: {colour}"
142 assert colour[0] == "#", f"Colour for {category} doesn't start with #: {colour}"
143 try:
144 int(colour[1:], 16)
145 except ValueError:
146 pytest.fail(f"Colour for {category} is not valid hex: {colour}")
147
148 def test_expected_colour_values(self):
149 """Verify the design-specified colours are present."""
150 assert CATEGORY_COLOURS[OpcodeCategory.ARITHMETIC] == "#4a90d9"
151 assert CATEGORY_COLOURS[OpcodeCategory.LOGIC] == "#4caf50"
152 assert CATEGORY_COLOURS[OpcodeCategory.COMPARISON] == "#ff9800"
153 assert CATEGORY_COLOURS[OpcodeCategory.ROUTING] == "#9c27b0"
154 assert CATEGORY_COLOURS[OpcodeCategory.MEMORY] == "#ff5722"
155 assert CATEGORY_COLOURS[OpcodeCategory.IO] == "#009688"
156 assert CATEGORY_COLOURS[OpcodeCategory.CONFIG] == "#9e9e9e"
157
158
159class TestCategoriseErrors:
160 """Tests for error handling in categorise()."""
161
162 def test_unknown_type_raises_valueerror(self):
163 """Passing an unknown type to categorise() raises ValueError."""
164 class UnknownOp:
165 pass
166
167 with pytest.raises(ValueError, match="Unknown opcode type"):
168 categorise(UnknownOp()) # type: ignore