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
|
## Registers
There are 16 general-purpose registers, `R0` to `R15`.
`R13` is the flag/condition register.
`R14` is the program counter.
`R15` is the stack pointer.
Additionally, `R12` mey be clobbered by the assembler when expanding macros.
## Instructions
XXXX denotes ignored bits.
**HLT** is a halt operation. It stops the processor.
```python
HLT
0000 XXXX XXXX XXXX
```
**ALU** is a 16-bit ALU operation.
- SSS selects the operation to perform
- LLLL is the left operand and destination register.
- In register mode (M=0), RRRR is the right operand register.
- In immediate mode (M=1), the next word is interpreted as a 16-bit immediate value.
```python
ALU
# register
0001 SSSM LLLL RRRR
# immediate
0001 SSSM LLLL XXXX
IIII IIII IIII IIII
```
**LD** is a load operation.
- LLLL is the destination register.
- In register mode (M=0), RRRR is the register holding the address to load from.
- In immediate mode (M=1), the next word is interpreted as a 16-bit address to load from.
- In direct mode (D=0), the address is used as-is.
- In indirect mode (D=1), the address is used as a pointer to another address. An indirect load can be thought of as a pointer dereference.
- In pop mode (P = 1), the RRRR register is incremented before loading. This in conjunction with an indirect load can be used as a stack pop operation. POP is only valid in register mode.
```python
LD
# register
0010 DPXM LLLL RRRR
# immediate
0010 DPXM LLLL XXXX
IIII IIII IIII IIII
```
**MOV** is a move operation. It copies the value from one register to another.
- LLLL is the destination register.
- RRRR is the source register.
```python
MOV
0011 XXXX LLLL RRRR
```
**ST** is a store operation.
- LLLL is the source register.
- In register mode (M=0), RRRR is the register holding the address to store to.
- In immediate mode (M=1), the next word is interpreted as a 16-bit address to store to.
- In direct mode (D=0), the address is used as-is.
- In indirect mode (D=1), the address is used as a pointer to another address. An indirect store can be thought of as a pointer assignment.
- In push mode (P = 1), the RRRR register is decremented after storing. This in conjunction with an indirect store can be used as a stack push operation. PUSH is only valid in register mode.
```python
ST
# register
0100 DPXM LLLL RRRR
# immediate
0100 DPXM LLLL XXXX
IIII IIII IIII IIII
```
**BR** is a branch operation.
- FF selects the condition to branch on (carry, overflow, zero, sign).
- S determines if the selected flag should be set (1) or not set (0).
- The I octet is an 8-bit signed offset.
```python
BR
0101 FFXS IIII IIII
```
## Calling convention
Arguments are passed in registers R0..R10. The return value is stored in R0.
The return address is stored on the stack to support nested calls.
Registers `R0..R3` are caller-saved (called function can clobber these registers, calling code must save them on the stack or higher registers if needed).
Registers `R4..R10` are callee-saved (called function must save these on the stack or lower registers).
## Example assembly
```java
@at 0x0
LD R0 0x1
LD R1 0x2
ADD R0 R1 ; ADD = macro that expands to ALU 000
BR SIGN UNSET $br_true
br_false:
LD R0 0xEE
HLT
br_true:
LD R0 0xFF
HLT
```
## Example macro assembly
```java
@macro ADD lhs rhs
ALU 0 $lhs $rhs
@endmacro
@let threshold 0x80
@if R1 < $threshold ; expands into a SUB and a BR
LD R0 0x1
LD R1 0x2
ADD R0 R1
HLT
@else
; something
@endif
@let PC R14
@let SP R15
@macro SPUSH reg
ST INDIRECT PUSH $reg $SP
@endmacro
@macro SPOP reg
LD INDIRECT POP $reg $SP
@endmacro
@macro CALL1 fn_lbl arg
@let ret_addr $gen_uniq ; generate a unique label
LD R0 $arg ; load argument to R0
LD R1 $ret_addr ; load return address to R1
SPUSH R1 ; push return address to stack
LD $PC $fn_lbl ; jump to function address
$ret_addr:
HLT
@endmacro
@at 0x0
main:
CALL1 :fn 0x1
HLT
; example of an absolute jump
LD $SP
$abs_jump_addr
; example of a relative jump
ADD $PC
$rel_jump_offset
; idea: syntax for passing the immediate on the same line
ADD $PC % $rel_jump_offset
; probably should just require the core instructions to be defined "correctly" and have convenience macros for the rest, such as
@macro LOAD reg from
@c_if immediate $from
LD $reg
$from
$c_else
LD $reg $from
$c_endif
@endmacro
```
### Assembler concepts
- `R0` to `R15` are register literals.
- Rows with no indentation are either directives (`@` prefix) or labels (`:` suffix).
- Rows with indentation are instructions.
- Comments are prefixed with `;`.
- Labels can be literals (`my_label:`) or variables (`$my_label_var:`).
- Compile time variables are defined with `@let` and used with `$`.
|