File size: 4,933 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
"""Tests for the .hdcasm compiler."""

import os
import sys
import unittest

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

from hdc.assembler import AsmError, assemble, roundtrip_report, split_operands  # noqa: E402

D = 2000


class TestLabels(unittest.TestCase):
    def test_forward_and_backward(self):
        src = """
            jmp done
        loop:
            nop
            jmp loop
        done:
            halt
        """
        prog = assemble(src, d=D)
        self.assertEqual(prog.labels["loop"], 1)
        self.assertEqual(prog.labels["done"], 3)
        self.assertEqual(prog.listing[0].tgt, 3)
        self.assertEqual(prog.listing[2].tgt, 1)

    def test_numeric_target(self):
        prog = assemble("jmp @2\nnop\nhalt", d=D)
        self.assertEqual(prog.listing[0].tgt, 2)

    def test_duplicate_label_rejected(self):
        with self.assertRaises(AsmError):
            assemble("loop:\n nop\nloop:\n nop", d=D)

    def test_undefined_label_rejected(self):
        with self.assertRaises(AsmError):
            assemble("jmp nowhere", d=D)

    def test_label_on_directive_line(self):
        prog = assemble('here: .str greeting "hello"\n ini: nop\n halt', d=D)
        self.assertEqual(prog.labels["ini"], 0)
        self.assertEqual(len(prog.instrs), 2)


class TestDirectives(unittest.TestCase):
    SRC = """
        .name out
        .data tbl 42
        .str  hello "hi there"
            ldi   R0, 1
            store out, R0
            prints "hi there"
            halt
    """

    def test_data_directory_populates_image(self):
        prog = assemble(self.SRC, d=D)
        self.assertEqual(prog.data["tbl"], "int:42")
        self.assertEqual(prog.data["hello"], "str:hi there")
        self.assertEqual(prog.cell_names, ["hello", "out", "tbl"])
        self.assertIn("hi there", prog.strings)
        self.assertIn(42, prog.literals)
        self.assertEqual(len(prog.instrs), 4)

    def test_install_writes_cells(self):
        from hdc.memory import HDCStorage

        prog = assemble(self.SRC, d=D)
        store = HDCStorage(d=D)
        names = prog.install(store)
        self.assertEqual(names, ["code/0", "code/1", "code/2", "code/3"])
        for name in names:
            self.assertIn(name, store)
            self.assertNotEqual(store.read(name), 0)
        self.assertIn("out", store)
        self.assertIn("tbl", store)

    def test_unknown_directive_rejected(self):
        with self.assertRaises(AsmError):
            assemble(".frobnicate x", d=D)


class TestRoundTrip(unittest.TestCase):
    SRC = """
        .str hello "hdc storage"
        .data acc 0
    start:
        ldi   R0, 5
        ldi   R1, 37
        add   R2, R0, R1
        out   R2
        bind  R3, R0, R1
        sim   R4, R3, R0
        out   R4
        prints hello
        store acc, R2
        load  R5, acc
        out   R5
        halt
    """

    def test_every_cell_decodes_to_its_source_opcode(self):
        prog = assemble(self.SRC, d=D)
        report = roundtrip_report(prog)
        n = len(prog.instrs)
        self.assertIn("decode-back check: %d/%d cells round-trip" % (n, n), report)
        for i, line in enumerate(prog.source):
            opcode = line.text.split()[0].upper()
            self.assertEqual(prog.listing[i].op, opcode, msg="cell %d" % i)
            self.assertGreater(prog.listing[i].confidence, 0.15)

    def test_source_lines_are_instruction_lines_only(self):
        prog = assemble(self.SRC, d=D)
        self.assertEqual(len(prog.source), len(prog.instrs))

    def test_listing_text_contains_confidence(self):
        prog = assemble(self.SRC, d=D)
        text = prog.listing_text()
        self.assertIn("conf", text)
        self.assertIn("start:", text)


class TestErrors(unittest.TestCase):
    def test_wrong_operand_count(self):
        with self.assertRaises(AsmError):
            assemble("add R0, R1", d=D)

    def test_unknown_opcode(self):
        with self.assertRaises(AsmError):
            assemble("frob R0", d=D)

    def test_bad_register(self):
        with self.assertRaises(AsmError):
            assemble("ldi R99, 1", d=D)

    def test_bad_literal(self):
        with self.assertRaises(AsmError):
            assemble("ldi R0, banana", d=D)

    def test_unquoted_string(self):
        with self.assertRaises(AsmError):
            assemble("prints hello", d=D)


class TestTokenizer(unittest.TestCase):
    def test_commas_inside_quotes_survive(self):
        self.assertEqual(
            split_operands('"a, b", R0'), ['"a, b"', "R0"]
        )

    def test_empty(self):
        self.assertEqual(split_operands("   "), [])

    def test_comments_stripped(self):
        prog = assemble("ldi R0, 5   ; set five\n halt  # done", d=D)
        self.assertEqual(len(prog.instrs), 2)
        self.assertEqual(prog.listing[0].imm, 5)


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