aboutsummaryrefslogtreecommitdiff
path: root/ch559/assembler.py
blob: f15f97210c02ec75d30d3c5728e1763281ba1770 (plain)
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
from dataclasses import dataclass
import struct
from typing import Any, Callable, Iterable, Literal, Optional


class Label:
    "A label in the program."

    name: Optional[str]

    def __init__(self, name=None):
        self.name = name


@dataclass
class Insn:
    "An instruction that has not yet been relocated."

    len: int
    fmt: str
    relocate: Callable[[int, dict[Label, int]], Iterable[Any]]


@dataclass
class DerefReg:
    "An indirect reference through a register."

    reg: "Reg"


@dataclass
class Reg:
    "A register."

    num: int
    derefable: bool = False

    def __str__(self):
        return f"R{self.num}"

    def deref(self) -> DerefReg:
        if not self.derefable:
            raise Exception("Cannot deref through {self}")
        return DerefReg(self)


@dataclass
class SFR:
    "A reference to one of the 256 bytes of iRAM (including the SFRs, hence this type's name)."

    num: int


class Asm:
    """
    The interface to the assembler. This class has a method for each of the
    8051's instructions.
    """

    R0 = Reg(0, derefable=True)
    R1 = Reg(1, derefable=True)
    R2 = Reg(2)
    R3 = Reg(3)
    R4 = Reg(4)
    R5 = Reg(5)
    R6 = Reg(6)
    R7 = Reg(7)

    def __init__(self):
        insns: list["Insn"] = []
        self.addr = 0x0000
        self.insns = insns
        self.labels = {}

    def assemble(self) -> bytes:
        out = b""
        addr = 0
        for insn in self.insns:
            out += struct.pack(insn.fmt, *insn.relocate(addr, self.labels))
            addr += insn.len
        return out

    def deflabel(self, label: Label) -> None:
        if label in self.labels:
            raise Exception(
                f"[{hex(self.addr)}] Attempting to redefine the label {label}"
            )
        self.labels[label] = self.addr

    def ACALL(self, label: Label) -> Insn:
        """
        Absolute Call. Pushes the address of the next instruction to the stack,
        then sets the low 11 bits of the program counter to match the label.

        The assembler checks that the top 5 bits match as well.
        """

        def relocate(addr: int, labels: dict[Label, int]) -> Iterable[int]:
            label_addr = labels[label]
            if (label_addr >> 11) != ((addr + 2) >> 1):
                raise Exception(
                    f"[{hex(self.addr)}] ACALL to out-of-range label {label}"
                )
            return (0x11 | ((label_addr >> 8) & 7), label_addr & 0xFF)

        insn = Insn(2, "cc", relocate)
        self.insns.append(insn)
        return insn

    def ADD(self, arg: int | SFR | DerefReg | Reg) -> Insn:
        """
        Add Accumulator. Adds the argument to the accumulator register (SFR 0xE0).
        """

        match arg:
            case int(lit):
                return Insn(2, "cc", lambda _addr, _labels: (0x24, lit))
            case SFR(addr):
                return Insn(2, "cc", lambda _addr, _labels: (0x25, addr))
            case DerefReg(Reg(reg)):
                return Insn(2, "c", lambda _addr, _labels: (0x26 + reg,))
            case Reg(reg):
                return Insn(2, "c", lambda _addr, _labels: (0x28 + reg,))

    def ADDC(self, arg: int | SFR | DerefReg | Reg) -> Insn:
        """
        Add Accumulator With Carry. Adds the argument plus the carry bit to the accumulator register (SFR 0xE0).
        """

        match arg:
            case int(lit):
                return Insn(2, "cc", lambda _addr, _labels: (0x34, lit))
            case SFR(addr):
                return Insn(2, "cc", lambda _addr, _labels: (0x35, addr))
            case DerefReg(Reg(reg)):
                return Insn(2, "c", lambda _addr, _labels: (0x36 + reg,))
            case Reg(reg):
                return Insn(2, "c", lambda _addr, _labels: (0x38 + reg,))

    def AJMP(self, label: Label) -> Insn:
        """
        Absolute Jump. Sets the low 11 bits of the program counter to match the
        label.

        The assembler checks that the top 5 bits match as well.
        """

        def relocate(addr: int, labels: dict[Label, int]) -> Iterable[int]:
            label_addr = labels[label]
            if (label_addr >> 11) != ((addr + 2) >> 1):
                raise Exception(
                    f"[{hex(self.addr)}] AJMP to out-of-range label {label}"
                )
            return (0x01 | ((label_addr >> 8) & 7), label_addr & 0xFF)

        insn = Insn(2, "cc", relocate)
        self.insns.append(insn)
        return insn

    def ANL(self, dst: SFR | Literal["A", "C"], src: Literal["A"] | int): ...

    # ANL - Bitwise AND
    # CJNE - Compare and Jump if Not Equal
    # CLR - Clear Register
    # CPL - Complement Register
    # DA - Decimal Adjust
    # DEC - Decrement Register
    # DIV - Divide Accumulator by B
    # DJNZ - Decrement Register and Jump if Not Zero
    # INC - Increment Register
    # JB - Jump if Bit Set
    # JBC - Jump if Bit Set and Clear Bit
    # JC - Jump if Carry Set
    # JMP - Jump to Address
    # JNB - Jump if Bit Not Set
    # JNC - Jump if Carry Not Set
    # JNZ - Jump if Accumulator Not Zero
    # JZ - Jump if Accumulator Zero
    # LCALL - Long Call
    # LJMP - Long Jump
    # MOV - Move Memory
    # MOVC - Move Code Memory
    # MOVX - Move Extended Memory
    # MUL - Multiply Accumulator by B
    # NOP - No Operation
    # ORL - Bitwise OR
    # POP - Pop Value From Stack
    # PUSH - Push Value Onto Stack
    # RET - Return From Subroutine
    # RETI - Return From Interrupt
    # RL - Rotate Accumulator Left
    # RLC - Rotate Accumulator Left Through Carry
    # RR - Rotate Accumulator Right
    # RRC - Rotate Accumulator Right Through Carry
    # SETB - Set Bit
    # SJMP - Short Jump
    # SUBB - Subtract From Accumulator With Borrow
    # SWAP - Swap Accumulator Nibbles
    # XCH - Exchange Bytes
    # XCHD - Exchange Digits
    # XRL - Bitwise Exclusive OR