~vdupras/collapseos

ref: ac3629b817115b1af75096856ee9499cfbbd3392 collapseos/doc/asm.txt -rw-r--r-- 8.4 KiB
ac3629b8Virgil Dupras Make BLK@* and BLK!* into ialiases 1 year, 11 months ago
                                                                                
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# Assembling Z80 binaries

(All assembers in Collapse OS follow the same basic principles.
There are sections, below, for each supported architectures, but
you should read this first section first to be familiar with
those common, basic principles)

Words in the Z80 assembler, loaded with "5 LOAD" allow you to
assemble z80 binaries. Being Forth words, opcode assembly is a
bit different than with a typical assembler. For example, what
would traditionally be "ld a, b" would become "A B LDrr,".

Those opcode words, of which there is a complete list below, end
with "," to indicate that their effect is to write (,) the cor-
responding opcode.

The "argtype" suffix after each mnemonic is needed because the
assembler doesn't auto-detect the op's form based on arguments.
It has to be explicitly specified. "r" is for 8-bit registers,
"d" for 16-bit ones, "i" for immediate, "c" is for conditions.
Be aware that "SP" and "AF" refer to the same value: some 16-
bit ops can affect SP, others, AF. If you use the wrong argu-
ment on the wrong op, you will affect the wrong register.

Mnemonics having only a single form, such as PUSH and POP,
don't have argtype suffixes.

In addition to opcode words, some variables are also defined by
this program:

BIN( is the addr at which the compiled binary will live. It is
often 0.

ORG is H@ offset at which we begin spitting binary. Used to
compute PC. To have a proper PC, call  "H@ ORG !" at the
beginning of your assembly process. PC is H@ - ORG + BIN(.

Labels are a convenient way of managing relative jump
calculations. Backward labels are easy. It is only a matter or
recording "HERE" and do subtractions. Forward labels record the
place where we should write the offset, and then when we get to
that point later on, the label records the offset there.

To avoid using dict memory in compilation targets, we
pre-declare label variables here, which means we have a limited
number of it. We have 4: L1, L2, L3, L4.

# Flow

There are 2 label types: backward and forward. For each type,
there are two actions: set and write. Setting a label is
declaring where it is. Words for this are BSET and FSET. It has
to be performed at the label's destination. Writing a label is
writing its offset difference to the binary result. It has to be
done right after a relative jump operation. Word for this are
BWR and FWR. Yes, those words are only for relative jumps.

For backward labels, set happens before write. For forward
labels, write happen before set. The write operation writes a
dummy placeholder, and then the set operation writes the offset
at that placeholder's address.

Important limitation: Flow words are broken when PC reaches
0x8000. The BREAK, word relies on that 15th bit as a flag.

Variable actions are expected to be called with labels in
front of them. Examples:

L1 BSET NOP, JR, L1 BWR ( backward jump )
JR, L1 FWR NOP, L1 FSET ( forward jump )

If you look at the code for those words, you'll notice a mys-
terious "1-". z80 relative jumps receives "e-2", that is, the
offset that *counts the 2 bytes of the jump itself*. Because we
set the label *after* the jump OP1 itself, that's 1 byte that is
taken care of. We still need to adjust by another byte before
writing the offset.

Can you use labels with JP, and CALL,? Yes, but only backwards
jumps, and in that case, you use the label's value directly.
Example: L2 @ CALL,

# Structured flow

z80a also has words that behave similarly to IF..THEN and
BEGIN..UNTIL.

On the IF side, we have IFZ, IFNZ, IFC, IFNC, and THEN,. When
the opposite condition is met, a relative jump is made to
THEN,'s PC. For example, if you have IFZ, a jump is made when
Z is unset.

There can be an ELSE, in the middle of an IF, and THEN,. When
present, IF, jumps to it when the condition is unmet. When the
condition is met, upon reaching the ELSE, we unconditionally
jump to the THEN,.

On the BEGIN,..AGAIN, side, it's a bit different. You start
with your BEGIN, instruction, and then later you issue a
JRxx, instr followed by AGAIN,. Exactly like you would do
with a label.

On top of that, you have the very nice BREAK, instruction,
which must also be preceded by a JRxx, and will jump to the
PC following the next AGAIN,. Examples:

IFZ, NOP, ELSE, NOP, THEN,
BEGIN, NOP, JR, AGAIN, ( unconditional )
BEGIN, NOP, JRZ, AGAIN, ( conditional )
BEGIN, NOP, JRZ, BREAK, JR, AGAIN, ( break off the loop )

# Z80 Instructions list

Letters in [] brackets indicate "argtype" variants. When the
bracket starts with ",", it means that a "plain" mnemonic is
available. For example, "RET," and "RETc," exist.

Note that assemblers in Collapse OS are incomplete and opcode
words were implemented in a "just-in-time" fashion, when needed.

r => A B C D E H L (HL)
d => BC DE HL AF/SP
c => CNZ CZ CNC CC CPO CPE CP CM

LD  [rr, ri, di, (i)HL, HL(i), d(i), (i)d, rIXY, IXYr,
    (DE)A, A(DE), (i)A, A(i)]
ADD [r, i, HLd, IXd, IXIX, IYd, IYIY]
ADC [r, HLd]
CP  [r, i, (IXY+)]
SBC [r, HLd]
SUB [r, i]
INC [r, d, (IXY+)]
DEC [r, d, (IXY+)]
AND [r, i]
OR  [r, i]
XOR [r, i]
OUT [iA, (C)r]
IN  [Ai, r(C)]
JP  [i, (HL), (IX), (IY)]
JR  [, Z, NZ, C, NC]

PUSH       POP
SET        RES         BIT
RL         RLC         SLA         RLA         RLCA
RR         RRC         SRL         RRA         RRCA
CALL       RST         DJNZ
DI         EI          EXDEHL      EXX         HALT
NOP        RET [,c]    RETI        RETN        SCF

Macros:

SUBHLd     PUSH [0,1,Z,A]          HLZ         DEZ
LDDE(HL)   OUT [HL,DE]

# 8086 assembler

Load with "30 LOAD". As with the Z80 assembler, it is incom-
plete.

Mnemonics are followed by argument types. For example, MOVri,
moves 8-bit immediate to 8-bit register.

'r' = 8-bit register           'x' = 16-bit register
'i' = 8-bit immediate          'I' = 16-bit immediate
's' = SREG register

Mnemonics that only have one signature (for example INT,) don't
have operands letters.

For jumps, it's special. 's' is SHORT, 'n' is NEAR, 'f' is FAR.

# 8086 Instructions list

r -> AL BL CL DL AH BH CH DX
x -> AX BX CX DX SP BP SI DI
s -> ES CS SS DS
[] -> [SI] [DI] [BP] [BX] [BX+SI] [BX+DI] [BP+SI] [BP+DI]

RET   CLI   STI   HLT   CLD   STD   NOP   CBW   REPZ  REPNZ
LODSB LODSW CMPSB SMPSW MOVSB MOVSW SCASB SCASW STOSB STOSW

CALL  J[Z,NZ,C,NC] JMP[s,n,r,f]

INC[r,x,[w],[b],[w]+,[b]+]
DEC[r,x,[w],[b],[w]+,[b]+]
POP[x,[w],[w]+]
PUSH[x,[w],[w]+,s]
MUL[r,x]
DIV[r,x]
XOR[rr,xx]
OR[rr,xx]
AND[rr,xx,ALi,AXI]
ADD[rr,xx,ALi,AXI,xi]
SUB[rr,xx,ALi,AXI,xi]
INT

CMP[rr,xx,r[],x[],r[]+,x[]+]
MOV[rr,xx,r[],x[],[]r,[]x,r[]+,x[]+,[]+r,[]+x,ri,xI,sx,rm,xm
    mr,mx]

("1" means "shift by 1", "CL" means "shift by CL")
ROL[r1,x1,rCL,xCL]
ROR[r1,x1,rCL,xCL]
SHL[r1,x1,rCL,xCL]
SHR[r1,x1,rCL,xCL]

# AVR assembler

Load with "50 LOAD". As with the Z80 assembler, it is incom-
plete.

All mnemonics in AVR have a single signature. Therefore, we
don't need any "argtype" suffixes.

Registers are referred to with consts R0-R31. There is
X, Y, Z, X+, Y+, Z+, X-, Y-, Z- for appropriate ops (LD, ST).
XL, XH, YL, YH, ZL, ZH are simple aliases to R26-R31.

Branching works differently. Instead of expecting a byte to be
written after the naked op, branching words expect a displace-
ment argument.

This is because there's bitwise ORing involved in the creation  
of the final opcode, which makes z80a's approach impractical.

This makes labelling a bit different too. Instead of expecting  
label words after the naked branching op, we rather have label
words expecting branching wordref as an argument. Examples:

L2 ' BRTS FLBL! ( branch forward to L2 )
L1 ' RJMP LBL, ( branch backward to L1 )

# Model-specific constants

Model-specific constants must be loaded separately. Here is a
list of units:

- ATMega328P: B65-B66

Those units contain register constants such as PORTB, DDRB, etc.
Unlike many moder assemblers, they do not include bit constants.
Here's an example use:

DDRB 5 SBI,
PORTB 5 CBI,
R16 TIFR0 IN,
R16 0 ( TOV0 ) SBRS,

# AVR instructions list

OPRd (B53)
ASR  COM   DEC   INC   LAC   LAS   LAT   LSR   NEG   POP   PUSH
ROR  SWAP  XCH

OPRdRr (B54)
ADC  ADD   AND   CP    CPC   CPSE  EOR   MOV   MUL   OR   SBC
SUB

OPRdA (B54)
IN   OUT

OPRdK (B55)
ANDI CPI   LDI   ORI   SBCI   SBR   SUBI

OPAb (B55)
CBI   SBI   SBIC  SBIS

OPNA (B56)
BREAK  CL[C,H,I,N,S,T,V,Z] SE[C,H,I,N,S,T,V,Z] EIJMP ICALL
EICALL IJMP  NOP   RET   RETI  SLEEP WDR

OPb (B57)
BCLR  BSET

OPRdb (B57)
BLD   BST   SBRC  SBRS

Special (B57,B60)
CLR   TST   LSL   LD    ST

Flow (B58)
RJMP  RCALL
BR[BC,BS,CC,CS,EQ,NE,GE,HC,HS,ID,IE,LO,LT,MI,PL,SH,TC,TS,VC,VS]

Flow macros (B61)
LBL! LBL, SKIP, TO, FLBL, FLBL! BEGIN, AGAIN? AGAIN, IF, THEN,