"""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()