"""Tests for instruction encoding and compute-in-memory decoding.""" import os import sys import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from hdc.isa import ( # noqa: E402 OPCODES, DecodeContext, DecodeError, decode_instr, encode_instr, format_operands, opcode_table_text, ) D = 2000 def ctx(**kw): base = dict(d=D) base.update(kw) return DecodeContext(**base) class TestRoundTrip(unittest.TestCase): """Every field kind must survive encode -> bundle -> unbind -> cleanup.""" CASES = [ ("NOP", dict(), dict()), ("HALT", dict(), dict()), ("LDI", dict(regs=[3], imm=7), dict(imms=[7])), ("MOV", dict(regs=[1, 2]), dict()), ("ADD", dict(regs=[2, 0, 1]), dict()), ("PERMUTE", dict(regs=[4, 5], imm=3), dict(imms=[3])), ("POPC", dict(regs=[6, 7]), dict()), ("JMP", dict(tgt=9), dict(tgts=[9])), ("JNZ", dict(tgt=12), dict(tgts=[12])), ("STORE", dict(name="out", regs=[6]), dict(names=["out"])), ("LOAD", dict(regs=[7], name="out"), dict(names=["out"])), ("RECALL", dict(regs=[9, 10], imm=3), dict(imms=[3])), ("PRINTS", dict(text="hi there"), dict(strs=["hi there"])), ( "TOOL", dict(regs=[0, 1, 2], tool="hdc.similarity"), dict(tools=["hdc.similarity"]), ), ] def test_roundtrip_fields(self): for op, kw, alphabets in self.CASES: with self.subTest(op=op): hv = encode_instr(op, d=D, **kw) dec = decode_instr(hv, ctx(**alphabets)) self.assertEqual(dec.op, op) self.assertEqual(dec.regs, list(kw.get("regs", []))) self.assertEqual(dec.imm, kw.get("imm")) self.assertEqual(dec.tgt, kw.get("tgt")) self.assertEqual(dec.name, kw.get("name")) self.assertEqual(dec.text, kw.get("text")) self.assertEqual(dec.tool, kw.get("tool")) self.assertGreater(dec.confidence, 0.2, msg="op %s" % op) def test_margins_are_recorded(self): dec = decode_instr(encode_instr("ADD", regs=[2, 0, 1], d=D), ctx()) self.assertIn("op", dec.margins) self.assertIn("ra", dec.margins) self.assertGreater(dec.margins["op"], 0.0) def test_whole_opcode_table_encodes(self): for name in OPCODES: with self.subTest(op=name): # a body-free vector must still identify its opcode dec = decode_instr(encode_instr(name, d=D), ctx()) self.assertEqual(dec.op, name) def test_format_operands(self): dec = decode_instr(encode_instr("STORE", name="out", regs=[6], d=D), ctx(names=["out"])) self.assertEqual(format_operands(dec), "out, R6") dec = decode_instr(encode_instr("JMP", tgt=9, d=D), ctx(tgts=[9])) self.assertEqual(format_operands(dec), "@9") class TestDegradedContext(unittest.TestCase): def test_missing_literal_alphabet_still_decodes_opcode(self): hv = encode_instr("LDI", regs=[3], imm=7, d=D) dec = decode_instr(hv, ctx()) # no imms supplied at all self.assertEqual(dec.op, "LDI") self.assertEqual(dec.regs, [3]) self.assertIsNone(dec.imm) def test_default_context_has_no_alphabets(self): self.assertEqual(DecodeContext().imms, ()) self.assertEqual(DecodeContext().names, ()) # The context must carry the same dimensionality as the encoding; # an empty alphabet is the point, a mismatched d would be a user error. dec = decode_instr(encode_instr("HALT", d=D), DecodeContext(d=D)) self.assertEqual(dec.op, "HALT") class TestErrors(unittest.TestCase): def test_unknown_opcode(self): with self.assertRaises(DecodeError): encode_instr("FROBNICATE", d=D) def test_register_out_of_range(self): with self.assertRaises(DecodeError): encode_instr("MOV", regs=[16], d=D) def test_too_many_registers(self): with self.assertRaises(DecodeError): encode_instr("ADD", regs=[0, 1, 2, 3], d=D) def test_negative_register(self): with self.assertRaises(DecodeError): encode_instr("MOV", regs=[-1], d=D) class TestTable(unittest.TestCase): def test_table_text_mentions_every_opcode(self): text = opcode_table_text() for name in OPCODES: self.assertIn(name, text) def test_effects_are_known_classes(self): allowed = {"ctrl", "alu", "hd", "mem", "io", "tool", "debug"} for op in OPCODES.values(): self.assertIn(op.effect, allowed) if __name__ == "__main__": unittest.main()