File size: 7,894 Bytes
ad474c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
"""End-to-end tests for the HDC machine."""

import os
import sys
import unittest

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from hdc.assembler import assemble  # noqa: E402
from hdc.interpreter import Machine  # noqa: E402
from hdc.memory import HDCStorage  # noqa: E402

D = 2000


def build(src, **kw):
    prog = assemble(src, d=D)
    machine = Machine(storage=HDCStorage(d=D), d=D, **kw)
    machine.load(prog)
    return machine


class TestArithmetic(unittest.TestCase):
    def test_add_and_out(self):
        m = build(
            """
            ldi R0, 5
            ldi R1, 37
            add R2, R0, R1
            out R2
            halt
            """
        )
        result = m.run()
        self.assertEqual(result["status"], "halted")
        self.assertEqual(result["output"], ["42"])
        self.assertEqual(m.get_int(2), 42)

    def test_bitwise_ops(self):
        m = build(
            """
            ldi R0, 12
            ldi R1, 10
            and R2, R0, R1
            or  R3, R0, R1
            xor R4, R0, R1
            not R5, R0
            shl R6, R0, 2
            shr R7, R0, 2
            halt
            """
        )
        m.run()
        self.assertEqual(m.get_int(2), 12 & 10)
        self.assertEqual(m.get_int(3), 12 | 10)
        self.assertEqual(m.get_int(4), 12 ^ 10)
        self.assertEqual(m.get_int(5), ~12)
        self.assertEqual(m.get_int(6), 48)
        self.assertEqual(m.get_int(7), 3)

    def test_sub_negatives(self):
        m = build("ldi R0, 3\n ldi R1, 10\n sub R2, R0, R1\n out R2\n halt")
        self.assertEqual(m.run()["output"], ["-7"])


class TestControlFlow(unittest.TestCase):
    def test_counting_loop_with_cmp_jnz(self):
        m = build(
            """
            ldi R0, 0
            ldi R1, 1
            ldi R2, 4
        loop:
            add R0, R0, R1
            cmp R0, R2
            jnz loop
            out R0
            halt
            """
        )
        result = m.run()
        self.assertEqual(result["output"], ["4"])
        self.assertTrue(result["flag_z"])

    def test_call_ret_and_push_pop(self):
        m = build(
            """
            ldi R0, 7
            push R0
            call sub1
            pop R2
            out R2
            out R3
            halt
        sub1:
            ldi R3, 11
            ret
            """
        )
        result = m.run()
        self.assertEqual(result["output"], ["7", "11"])
        self.assertEqual(result["stack_depth"], 0)

    def test_jmp_skips_code(self):
        m = build(
            """
            ldi R0, 1
            jmp past
            ldi R0, 99
        past:
            out R0
            halt
            """
        )
        self.assertEqual(m.run()["output"], ["1"])

    def test_step_advances_one_cell(self):
        m = build("ldi R0, 5\n ldi R1, 6\n halt")
        m.step()
        self.assertEqual(m.pc, 1)
        self.assertEqual(m.steps, 1)
        self.assertEqual(m.get_int(0), 5)
        self.assertEqual(len(m.trace), 1)
        self.assertEqual(m.trace[0].op, "LDI")


class TestHyperdimensionalOps(unittest.TestCase):
    def test_bind_then_unbind_recovers_register(self):
        m = build(
            """
            ldi  R0, 5
            ldi  R1, 37
            bind R2, R0, R1
            bind R3, R2, R1
            sim  R4, R3, R0
            out  R4
            halt
            """
        )
        m.run()
        # XOR binding is self-inverse, so the round trip is exact
        self.assertEqual(m.regs[3], m.regs[0])
        self.assertEqual(m.get_int(4), 1000)
        self.assertEqual(m.output, ["1000"])

    def test_bind_and_bundle_differ(self):
        m = build(
            """
            ldi    R0, 5
            ldi    R1, 37
            bind   R2, R0, R1
            bundle R3, R0, R1
            cmp    R2, R3
            halt
            """
        )
        m.run()
        self.assertNotEqual(m.regs[2], m.regs[3])

    def test_permute_is_deterministic_and_decorrelating(self):
        m = build(
            """
            ldi     R0, 5
            ldi     R1, 37
            bind    R2, R0, R1
            permute R3, R2, 5
            permute R4, R2, 5
            sim     R5, R3, R4
            sim     R6, R3, R2
            halt
            """
        )
        m.run()
        # same rotation of the same vector is bit-identical
        self.assertEqual(m.regs[3], m.regs[4])
        self.assertEqual(m.get_int(5), 1000)
        # a rotation by 5 is near-orthogonal to its original
        self.assertLess(abs(m.get_int(6)), 300)

    def test_popcount(self):
        m = build("ldi R0, 1\n popc R1, R0\n halt")
        m.run()
        from hdc.hypervec import popcount

        self.assertEqual(m.get_int(1), popcount(m.regs[0]))


class TestStorageOps(unittest.TestCase):
    def test_store_load_roundtrip(self):
        m = build(
            """
            .name box
            ldi   R0, 99
            store box, R0
            load  R1, box
            out   R1
            halt
            """
        )
        self.assertEqual(m.run()["output"], ["99"])
        self.assertEqual(m.storage.read("box"), m.regs[0])
        self.assertEqual(m.get_int(1), 99)

    def test_recall_finds_stored_cell(self):
        m = build(
            """
            .name probe
            ldi   R0, 7
            store probe, R0
            recall R1, R0, 1
            halt
            """
        )
        m.run()
        self.assertTrue(m.last_recall)
        self.assertEqual(m.last_recall[0][0], "probe")
        self.assertEqual(m.last_recall[0][2], 1.0)

    def test_memstat(self):
        m = build("memstat R0\n halt")
        m.run()
        self.assertEqual(m.get_int(0), len(m.storage))

    def test_program_cells_live_in_storage(self):
        m = build("ldi R0, 1\n halt")
        self.assertEqual(m.prog_len, 2)
        for pc in range(m.prog_len):
            self.assertIn("code/%d" % pc, m.storage)


class TestTraps(unittest.TestCase):
    def test_break_pauses_then_resumes(self):
        m = build(
            """
            ldi R0, 1
            break
            ldi R0, 2
            out R0
            halt
            """
        )
        result = m.run()
        self.assertEqual(result["status"], "break")
        self.assertEqual(result["output"], [])
        self.assertEqual(m.pc, 1)

        m.skip_break = True
        result = m.run()
        self.assertEqual(result["status"], "halted")
        self.assertEqual(result["output"], ["2"])

    def test_tool_without_registry_faults_not_raises(self):
        m = build(
            """
            .tool hdc.similarity
            ldi  R0, 1
            tool R1, R0, R0, hdc.similarity
            halt
            """
        )
        result = m.run()
        self.assertEqual(result["status"], "error")
        self.assertTrue(result["halted"])

    def test_ret_on_empty_stack_faults(self):
        m = build("ret\n halt")
        result = m.run()
        self.assertEqual(result["status"], "error")


class TestInspection(unittest.TestCase):
    def test_disasm_marks_current_cell(self):
        m = build("ldi R0, 1\n out R0\n halt")
        m.step()
        text = m.disasm(0, 3)
        self.assertIn("=>", text)
        self.assertIn("OUT", text)

    def test_regs_text_lists_registers(self):
        m = build("ldi R0, 5\n halt")
        m.run()
        text = m.regs_text()
        self.assertIn("R0", text)
        self.assertIn("R15", text)

    def test_reset_clears_state(self):
        m = build("ldi R0, 5\n out R0\n halt")
        m.run()
        self.assertEqual(m.output, ["5"])
        m.reset()
        self.assertEqual(m.output, [])
        self.assertEqual(m.pc, 0)
        self.assertEqual(m.steps, 0)
        self.assertEqual(m.get_int(0), 0)


if __name__ == "__main__":
    unittest.main()