collapseos/apps/zasm/instr.asm

1150 lines
30 KiB
NASM

; *** Consts ***
; Number of rows in the argspec table
.equ ARGSPEC_TBL_CNT 31
; Number of rows in the primary instructions table
.equ INSTR_TBL_CNT 151
; size in bytes of each row in the primary instructions table
.equ INSTR_TBL_ROWSIZE 6
; Instruction IDs They correspond to the index of the table in instrNames
.equ I_ADC 0x00
.equ I_ADD 0x01
.equ I_AND 0x02
.equ I_BIT 0x03
.equ I_CALL 0x04
.equ I_CCF 0x05
.equ I_CP 0x06
.equ I_CPD 0x07
.equ I_CPDR 0x08
.equ I_CPI 0x09
.equ I_CPIR 0x0a
.equ I_CPL 0x0b
.equ I_DAA 0x0c
.equ I_DEC 0x0d
.equ I_DI 0x0e
.equ I_DJNZ 0x0f
.equ I_EI 0x10
.equ I_EX 0x11
.equ I_EXX 0x12
.equ I_HALT 0x13
.equ I_IM 0x14
.equ I_IN 0x15
.equ I_INC 0x16
.equ I_IND 0x17
.equ I_INDR 0x18
.equ I_INI 0x19
.equ I_INIR 0x1a
.equ I_JP 0x1b
.equ I_JR 0x1c
.equ I_LD 0x1d
.equ I_LDD 0x1e
.equ I_LDDR 0x1f
.equ I_LDI 0x20
.equ I_LDIR 0x21
.equ I_NEG 0x22
.equ I_NOP 0x23
.equ I_OR 0x24
.equ I_OTDR 0x25
.equ I_OTIR 0x26
.equ I_OUT 0x27
.equ I_POP 0x28
.equ I_PUSH 0x29
.equ I_RET 0x2a
.equ I_RETI 0x2b
.equ I_RETN 0x2c
.equ I_RL 0x2d
.equ I_RLA 0x2e
.equ I_RLC 0x2f
.equ I_RLCA 0x30
.equ I_RR 0x31
.equ I_RRA 0x32
.equ I_RRC 0x33
.equ I_RRCA 0x34
.equ I_SBC 0x35
.equ I_SCF 0x36
.equ I_SUB 0x37
.equ I_XOR 0x38
; Checks whether A is 'N' or 'M'
checkNOrM:
cp 'N'
ret z
cp 'M'
ret
; Checks whether A is 'n', 'm', 'x' or 'y'
checknmxy:
cp 'n'
ret z
cp 'm'
ret z
cp 'x'
ret z
cp 'y'
ret
; Reads string in (HL) and returns the corresponding ID (I_*) in A. Sets Z if
; there's a match.
getInstID:
push bc
push de
ld b, I_XOR+1 ; I_XOR is the last
ld c, 4
ld de, instrNames
call findStringInList
pop de
pop bc
ret
; Parse the string at (HL) and check if it starts with IX+, IY+, IX- or IY-.
; Sets Z if yes, unset if no.
parseIXY:
push hl
ld a, (hl)
call upcase
cp 'I'
jr nz, .end ; Z already unset
inc hl
ld a, (hl)
call upcase
cp 'X'
jr z, .match1
cp 'Y'
jr z, .match1
jr .end ; Z already unset
.match1:
; Alright, we have IX or IY. Let's see if we have + or - next.
inc hl
ld a, (hl)
cp '+'
jr z, .end ; Z is already set
cp '-'
; The value of Z at this point is our final result
.end:
pop hl
ret
; find argspec for string at (HL). Returns matching argspec in A.
; Return value 0xff holds a special meaning: arg is not empty, but doesn't match
; any argspec (A == 0 means arg is empty). A return value of 0xff means an
; error.
;
; If the parsed argument is a number constant, 'N' is returned and IX contains
; the value of that constant.
parseArg:
call strlen
or a
ret z ; empty string? A already has our result: 0
push bc
push de
push hl
; We always initialize IX to zero so that non-numerical args end up with
; a clean zero.
ld ix, 0
ld de, argspecTbl
; DE now points the the "argspec char" part of the entry, but what
; we're comparing in the loop is the string next to it. Let's offset
; DE by one so that the loop goes through strings.
inc de
ld b, ARGSPEC_TBL_CNT
.loop1:
ld a, 4
call strncmpI
jr z, .found ; got it!
ld a, 5
call addDE
djnz .loop1
; We exhausted the argspecs. Let's see if we're inside parens.
call enterParens
jr z, .withParens
; (HL) has no parens
call .maybeParseExpr
jr nz, .nomatch
; We have a proper number in no parens. Number in IX.
ld a, 'N'
jr .end
.withParens:
ld c, 'M' ; C holds the argspec type until we reach
; .numberInParens
; We have parens. First, let's see if we have a (IX+d) type of arg.
call parseIXY
jr nz, .parseNumberInParens ; not I{X,Y}. just parse number.
; We have IX+/IY+/IX-/IY-.
; note: the "-" part isn't supported yet.
inc hl ; (HL) now points to X or Y
ld a, (hl)
inc hl ; advance HL to the number part
inc hl ; this is the number
cp 'Y'
jr nz, .notY
ld c, 'y'
jr .parseNumberInParens
.notY:
ld c, 'x'
.parseNumberInParens:
call .maybeParseExpr
jr nz, .nomatch
; We have a proper number in parens. Number in IX
ld a, c ; M, x, or y
jr .end
.nomatch:
; We get no match
ld a, 0xff
jr .end
.found:
; found the matching argspec row. Our result is one byte left of DE.
dec de
ld a, (de)
.end:
pop hl
pop de
pop bc
ret
.maybeParseExpr:
; Before we try to parse expr in (HL), first check if we're in first
; pass if we are, skip parseExpr. Most of the time, that parse is
; harmless, but in some cases it causes false failures. For example,
; a "-" operator can cause is to falsely overflow and generate
; truncation error.
call zasmIsFirstPass
ret z
jp parseExpr
; Returns, with Z, whether A is a groupId
isGroupId:
cp 0xc ; max group id + 1
jr nc, .notgroup ; >= 0xc? not a group
cp 0
jr z, .notgroup ; 0? not supposed to happen. something's wrong.
; A is a group. ensure Z is set
cp a
ret
.notgroup:
call unsetZ
ret
; Find argspec A in group id H.
; Set Z according to whether we found the argspec
; If found, the value in A is the argspec value in the group (its index).
findInGroup:
push bc
push hl
or a ; is our arg empty? If yes, we have nothing to do
jr z, .notfound
push af
ld a, h
cp 0xa
jr z, .specialGroupCC
cp 0xb
jr z, .specialGroupABCDEHL
jr nc, .notfound ; > 0xb? not a group
pop af
; regular group
push de
ld de, argGrpTbl
; group ids start at 1. decrease it, then multiply by 4 to have a
; proper offset in argGrpTbl
dec h
push af
ld a, h
rla
rla
call addDE ; At this point, DE points to our group
pop af
ex de, hl ; And now, HL points to the group
pop de
ld bc, 4
jr .find
.specialGroupCC:
ld hl, argGrpCC
jr .specialGroupEnd
.specialGroupABCDEHL:
ld hl, argGrpABCDEHL
.specialGroupEnd:
pop af ; from the push af just before the special group check
ld bc, 8
.find:
; This part is common to regular and special group. We expect HL to
; point to the group and BC to contain its length.
push bc ; save the start value loop index so we can sub
.loop:
cpi
jr z, .found
jp po, .notfound
jr .loop
.found:
; we found our result! Now, what we want to put in A is the index of
; the found argspec.
pop hl ; we pop from the "push bc" above. L is now 4 or 8
ld a, l
sub c
dec a ; cpi DECs BC even when there's a match, so C == the
; number of iterations we've made. But our index is
; zero-based (1 iteration == 0 index).
cp a ; ensure Z is set
jr .end
.notfound:
pop bc ; from the push bc in .find
call unsetZ
.end:
pop hl
pop bc
ret
; Compare argspec from instruction table in A with argument in (HL).
; For constant args, it's easy: if A == (HL), it's a success.
; If it's not this, then we check if it's a numerical arg.
; If A is a group ID, we do something else: we check that (HL) exists in the
; groupspec (argGrpTbl). Moreover, we go and write the group's "value" (index)
; in (HL+1). This will save us significant processing later in getUpcode.
; Set Z according to whether we match or not.
matchArg:
cp (hl)
ret z
; not an exact match. Before we continue: is A zero? Because if it is,
; we have to stop right here: no match possible.
or a
jr nz, .checkIfNumber ; not a zero, we can continue
; zero, stop here
call unsetZ
ret
.checkIfNumber:
; not an exact match, let's check for numerical constants.
call upcase
call checkNOrM
jr z, .expectsNumber
jr .notNumber
.expectsNumber:
; Our argument is a number N or M. Never a lower-case version. At this
; point in the processing, we don't care about whether N or M is upper,
; we do truncation tests later. So, let's just perform the same == test
; but in a case-insensitive way instead
cp (hl)
ret ; whether we match or not, the result of Z is
; the good one.
.notNumber:
; A bit of a delicate situation here: we want A to go in H but also
; (HL) to go in A. If not careful, we overwrite each other. EXX is
; necessary to avoid invoving other registers.
push hl
exx
ld h, a
push hl
exx
ld a, (hl)
pop hl
call findInGroup
pop hl
ret nz
; we found our group? let's write down its "value" in (HL+1). We hold
; this value in A at the moment.
inc hl
ld (hl), a
dec hl
ret
; Compare primary row at (DE) with ID in A. Sets Z flag if there's a match.
matchPrimaryRow:
push hl
push ix
push de \ pop ix
cp (ix)
jr nz, .end
; name matches, let's see the rest
ld hl, curArg1
ld a, (ix+1)
call matchArg
jr nz, .end
ld hl, curArg2
ld a, (ix+2)
call matchArg
.end:
pop ix
pop hl
ret
; *** Special opcodes ***
; The special upcode handling routines below all have the same signature.
; Instruction row is at IX and we're expected to perform the same task as
; getUpcode. The number of bytes, however, must go in C instead of A
; No need to preserve HL, DE, BC and IX: it's handled by getUpcode already.
; Handle like a regular "JP (IX+d)" except that we refuse any displacement: if
; a displacement is specified, we error out.
handleJPIX:
ld a, 0xdd
jr handleJPIXY
handleJPIY:
ld a, 0xfd
handleJPIXY:
ld (instrUpcode), a
ld a, (curArg1+1)
cp 0 ; numerical argument *must* be zero
jr nz, .error
; ok, we're good
ld a, 0xe9 ; second upcode
ld (instrUpcode+1), a
ld c, 2
ret
.error:
ld c, 0
ret
; Handle the first argument of BIT. Sets Z if first argument is valid, unset it
; if there's an error.
handleBIT:
ld a, (curArg1+1)
cp 8
jr nc, .error ; >= 8? error
; We're good
cp a ; ensure Z
ret
.error:
ld c, 0
call unsetZ
ret
handleBITHL:
call handleBIT
ret nz ; error
ld a, 0xcb ; first upcode
ld (instrUpcode), a
ld a, (curArg1+1) ; 0-7
ld b, 3 ; displacement
call rlaX
or 0b01000110 ; 2nd upcode
ld (instrUpcode+1), a
ld c, 2
ret
handleBITIX:
ld a, 0xdd
jr handleBITIXY
handleBITIY:
ld a, 0xfd
handleBITIXY:
ld (instrUpcode), a ; first upcode
call handleBIT
ret nz ; error
ld a, 0xcb ; 2nd upcode
ld (instrUpcode+1), a
ld a, (curArg2+1) ; IXY displacement
ld (instrUpcode+2), a
ld a, (curArg1+1) ; 0-7
ld b, 3 ; displacement
call rlaX
or 0b01000110 ; 4th upcode
ld (instrUpcode+3), a
ld c, 4
ret
handleBITR:
call handleBIT
ret nz ; error
; get group value
ld a, (curArg2+1) ; group value
ld c, a
; write first upcode
ld a, 0xcb ; first upcode
ld (instrUpcode), a
; get bit value
ld a, (curArg1+1) ; 0-7
ld b, 3 ; displacement
call rlaX
; Now we have group value in stack, bit value in A (properly shifted)
; and we want to OR them together
or c ; Now we have our ORed value
or 0b01000000 ; and with the constant value for that byte...
; we're good!
ld (instrUpcode+1), a
ld c, 2
ret
handleIM:
ld a, (curArg1+1)
cp 0
jr z, .im0
cp 1
jr z, .im1
cp 2
jr z, .im2
; error
ld c, 0
ret
.im0:
ld a, 0x46
jr .proceed
.im1:
ld a, 0x56
jr .proceed
.im2:
ld a, 0x5e
.proceed:
ld (instrUpcode+1), a
ld a, 0xed
ld (instrUpcode), a
ld c, 2
ret
handleLDIXn:
ld a, 0xdd
jr handleLDIXYn
handleLDIYn:
ld a, 0xfd
handleLDIXYn:
ld (instrUpcode), a
ld a, 0x36 ; second upcode
ld (instrUpcode+1), a
ld a, (curArg1+1) ; IXY displacement
ld (instrUpcode+2), a
ld a, (curArg2+1) ; N
ld (instrUpcode+3), a
ld c, 4
ret
handleLDIXr:
ld a, 0xdd
jr handleLDIXYr
handleLDIYr:
ld a, 0xfd
handleLDIXYr:
ld (instrUpcode), a
ld a, (curArg2+1) ; group value
or 0b01110000 ; second upcode
ld (instrUpcode+1), a
ld a, (curArg1+1) ; IXY displacement
ld (instrUpcode+2), a
ld c, 3
ret
handleLDrIX:
ld a, 0xdd
jr handleLDrIXY
handleLDrIY:
ld a, 0xfd
handleLDrIXY:
ld (instrUpcode), a
ld a, (curArg1+1) ; group value
rla \ rla \ rla
or 0b01000110 ; second upcode
ld (instrUpcode+1), a
ld a, (curArg2+1) ; IXY displacement
ld (instrUpcode+2), a
ld c, 3
ret
handleLDrr:
; first argument is displaced by 3 bits, second argument is not
; displaced and we or that with a leading 0b01000000
ld a, (curArg1+1) ; group value
rla
rla
rla
ld c, a ; store it
ld a, (curArg2+1) ; other group value
or c
or 0b01000000
ld (instrUpcode), a
ld c, 1
ret
; Compute the upcode for argspec row at (DE) and arguments in curArg{1,2} and
; writes the resulting upcode in instrUpcode. A is the number if bytes written
; to instrUpcode (can be zero if something went wrong).
getUpcode:
push ix
push de
push hl
push bc
; First, let's go in IX mode. It's easier to deal with offsets here.
push de \ pop ix
; Are we a "special instruction"?
bit 5, (ix+3)
jr z, .normalInstr ; not set: normal instruction
; We are a special instruction. Fetch handler (little endian, remember).
ld l, (ix+4)
ld h, (ix+5)
call callHL
; We have our result written in instrUpcode and C is set.
jp .end
.normalInstr:
; we begin by writing our "base upcode", which can be one or two bytes
ld a, (ix+4) ; first upcode
ld (instrUpcode), a
ld de, instrUpcode ; from this point, DE points to "where we are"
; in terms of upcode writing.
inc de ; make DE point to where we should write next.
ld c, 1 ; C holds our upcode count
; Now, let's determine if we have one or two upcode. As a general rule,
; we simply have to check if (ix+5) == 0, which means one upcode.
; However, some two-upcodes instructions have a 0 (ix+5) because they
; expect group OR-ing into it and all other bits are zero. See "RLC r".
; To handle those cases, we *also* check for Bit 6 in (ix+3).
ld a, (ix+5) ; second upcode
or a ; do we have a second upcode?
jr nz, .twoUpcodes
bit 6, (ix+3)
jr z, .onlyOneUpcode ; not set: single upcode
.twoUpcodes:
; we have two upcodes
ld (de), a
inc de
inc c
.onlyOneUpcode:
; now, let's see if we're dealing with a group here
ld a, (ix+1) ; first argspec
call isGroupId
jr z, .firstArgIsGroup
; First arg not a group. Maybe second is?
ld a, (ix+2) ; 2nd argspec
call isGroupId
jr nz, .writeExtraBytes ; not a group? nothing to do. go to
; next step: write extra bytes
; Second arg is group
ld hl, curArg2
jr .isGroup
.firstArgIsGroup:
ld hl, curArg1
.isGroup:
; A is a group, good, now let's get its value. HL is pointing to
; the argument. Our group value is at (HL+1).
inc hl
ld a, (hl)
; Now, we have our arg "group value" in A. Were going to need to
; displace it left by the number of steps specified in the table.
push af
ld a, (ix+3) ; displacement bit
and 0xf ; we only use the lower nibble.
ld b, a
pop af
call rlaX
; At this point, we have a properly displaced value in A. We'll want
; to OR it with the opcode.
; However, we first have to verify whether this ORing takes place on
; the second upcode or the first.
bit 6, (ix+3)
jr z, .firstUpcode ; not set: first upcode
or (ix+5) ; second upcode
ld (instrUpcode+1), a
jr .writeExtraBytes
.firstUpcode:
or (ix+4) ; first upcode
ld (instrUpcode), a
jr .writeExtraBytes
.writeExtraBytes:
; Good, we are probably finished here for many primary opcodes. However,
; some primary opcodes take 8 or 16 bit constants as an argument and
; if that's the case here, we need to write it too.
; We still have our instruction row in IX and we have DE pointing to
; where we should write next (which could be the second or the third
; byte of instrUpcode).
ld a, (ix+1) ; first argspec
ld hl, curArg1
call checkNOrM
jr z, .withWord
call checknmxy
jr z, .withByte
ld a, (ix+2) ; second argspec
ld hl, curArg2
call checkNOrM
jr z, .withWord
call checknmxy
jr z, .withByte
; nope, no number, alright, we're finished here
jr .end
.withByte:
inc hl
; HL points to our number (LSB), with (HL+1) being our MSB which should
; normally by zero. However, if our instruction is jr or djnz, that
; number is actually a 2-bytes address that has to be relative to PC,
; so it's a special case. Let's check for this special case.
bit 7, (ix+3)
jr z, .absoluteValue ; bit not set? regular byte value,
; Our argument is a relative address ("e" type in djnz and jr). We have
; to subtract (IO_PC) from it.
; First, check whether we're on first pass. If we are, skip processing
; below because not having real symbol value makes relative address
; verification falsely fail.
inc c ; one extra byte is written
call zasmIsFirstPass
jr z, .end
; We're on second pass
push de ; Don't let go of this, that's our dest
ld de, (IO_PC)
call intoHL
dec hl ; what we write is "e-2"
dec hl
call subDEFromHL
pop de ; Still have it? good
; HL contains our number and we'll check its bounds. If It's negative,
; H is going to be 0xff and L has to be >= 0x80. If it's positive,
; H is going to be 0 and L has to be < 0x80.
ld a, l
cp 0x80
jr c, .skipHInc ; a < 0x80, H is expected to be 0
; A being >= 0x80 is only valid in cases where HL is negative and
; within bounds. This only happens is H == 0xff. Let's increase it to 0.
inc h
.skipHInc:
; Let's write our value now even though we haven't checked our bounds
; yet. This way, we don't have to store A somewhere else.
ld (de), a
ld a, h
or a ; cp 0
jr nz, .numberTruncated ; if A is anything but zero, we're out
; of bounds.
jr .end
.absoluteValue:
; verify that the MSB in argument is zero
inc hl ; MSB is 2nd byte
ld a, (hl)
dec hl ; HL now points to LSB
or a ; cp 0
jr nz, .numberTruncated
push bc
ldi
pop bc
inc c
jr .end
.withWord:
inc hl ; HL now points to LSB
; Clear to proceed. HL already points to our number
push bc
ldi ; LSB written, we point to MSB now
ldi ; MSB written
pop bc
inc c ; two extra bytes are written
inc c
jr .end
.numberTruncated:
; problem: not zero, so value is truncated. error
ld c, 0
.end:
ld a, c
pop bc
pop hl
pop de
pop ix
ret
; Parse argument in (HL) and place it in (DE)
; Sets Z on success, reset on error.
processArg:
call parseArg
cp 0xff
jr z, .error
ld (de), a
; When A is a number, IX is set with the value of that number. Because
; We don't use the space allocated to store those numbers in any other
; occasion, we store IX there unconditonally, LSB first.
inc de
push hl
push ix \ pop hl
call writeHLinDE
pop hl
cp a ; ensure Z is set
ret
.error:
call unsetZ
ret
; Parse instruction specified in A (I_* const) with args in I/O and write
; resulting opcode(s) in I/O.
; Sets Z on success.
parseInstruction:
push bc
push hl
push de
; A is reused in matchPrimaryRow but that register is way too changing.
; Let's keep a copy in a more cosy register.
ld c, a
xor a
ld (curArg1), a
ld (curArg2), a
call readWord
jr nz, .nomorearg
ld de, curArg1
call processArg
jr nz, .error
call readComma
jr nz, .nomorearg
call readWord
jr nz, .error
ld de, curArg2
call processArg
jr nz, .error
.nomorearg:
; Parsing done, no error, let's move forward to instr row matching!
ld de, instrTBl
ld b, INSTR_TBL_CNT
.loop:
ld a, c ; recall A param
call matchPrimaryRow
jr z, .match
ld a, INSTR_TBL_ROWSIZE
call addDE
djnz .loop
; no match
xor a
jr .end
.match:
; We have our matching instruction row. We're getting pretty near our
; goal here!
call getUpcode
or a ; is zero?
jr z, .error
ld b, a ; save output byte count
ld hl, instrUpcode
.loopWrite:
ld a, (hl)
call ioPutC
inc hl
djnz .loopWrite
cp a ; ensure Z
jr .end
.error:
call unsetZ
.end:
pop de
pop hl
pop bc
ret
; In instruction metadata below, argument types arge indicated with a single
; char mnemonic that is called "argspec". This is the table of correspondance.
; Single letters are represented by themselves, so we don't need as much
; metadata.
; Special meaning:
; 0 : no arg
; 1-10 : group id (see Groups section)
; 0xff: error
; Format: 1 byte argspec + 4 chars string
argspecTbl:
.db 'A', "A", 0, 0, 0
.db 'B', "B", 0, 0, 0
.db 'C', "C", 0, 0, 0
.db 'k', "(C)", 0
.db 'D', "D", 0, 0, 0
.db 'E', "E", 0, 0, 0
.db 'H', "H", 0, 0, 0
.db 'L', "L", 0, 0, 0
.db 'I', "I", 0, 0, 0
.db 'R', "R", 0, 0, 0
.db 'h', "HL", 0, 0
.db 'l', "(HL)"
.db 'd', "DE", 0, 0
.db 'e', "(DE)"
.db 'b', "BC", 0, 0
.db 'c', "(BC)"
.db 'a', "AF", 0, 0
.db 'f', "AF'", 0
.db 'X', "IX", 0, 0
.db 'Y', "IY", 0, 0
.db 'x', "(IX)" ; always come with displacement
.db 'y', "(IY)" ; with JP
.db 's', "SP", 0, 0
.db 'p', "(SP)"
; we also need argspecs for the condition flags
.db 'Z', "Z", 0, 0, 0
.db 'z', "NZ", 0, 0
; C is in conflict with the C register. The situation is ambiguous, but
; doesn't cause actual problems.
.db '=', "NC", 0, 0
.db '+', "P", 0, 0, 0
.db '-', "M", 0, 0, 0
.db '1', "PO", 0, 0
.db '2', "PE", 0, 0
; argspecs not in the list:
; n -> N
; N -> NN
; m -> (N) (running out of mnemonics. 'm' for 'memory pointer')
; M -> (NN)
; Groups
; Groups are specified by strings of argspecs. To facilitate jumping to them,
; we have a fixed-sized table. Because most of them are 2 or 4 bytes long, we
; have a table that is 4 in size to minimize consumed space. We treat the two
; groups that take 8 bytes in a special way.
;
; The table below is in order, starting with group 0x01
argGrpTbl:
.db "bdha" ; 0x01
.db "ZzC=" ; 0x02
.db "bdhs" ; 0x03
.db "bdXs" ; 0x04
.db "bdYs" ; 0x05
argGrpCC:
.db "zZ=C12+-" ; 0xa
argGrpABCDEHL:
.db "BCDEHL_A" ; 0xb
; Each row is 4 bytes wide, fill with zeroes
instrNames:
.db "ADC", 0
.db "ADD", 0
.db "AND", 0
.db "BIT", 0
.db "CALL"
.db "CCF", 0
.db "CP",0,0
.db "CPD", 0
.db "CPDR"
.db "CPI", 0
.db "CPIR"
.db "CPL", 0
.db "DAA", 0
.db "DEC", 0
.db "DI",0,0
.db "DJNZ"
.db "EI",0,0
.db "EX",0,0
.db "EXX", 0
.db "HALT"
.db "IM",0,0
.db "IN",0,0
.db "INC", 0
.db "IND", 0
.db "INDR"
.db "INI", 0
.db "INIR"
.db "JP",0,0
.db "JR",0,0
.db "LD",0,0
.db "LDD", 0
.db "LDDR"
.db "LDI", 0
.db "LDIR"
.db "NEG", 0
.db "NOP", 0
.db "OR",0,0
.db "OTDR"
.db "OTIR"
.db "OUT", 0
.db "POP", 0
.db "PUSH"
.db "RET", 0
.db "RETI"
.db "RETN"
.db "RL", 0, 0
.db "RLA", 0
.db "RLC", 0
.db "RLCA"
.db "RR", 0, 0
.db "RRA", 0
.db "RRC", 0
.db "RRCA"
.db "SBC", 0
.db "SCF", 0
.db "SUB", 0
.db "XOR", 0
; This is a list of all supported instructions. Each row represent a combination
; of instr/argspecs (which means more than one row per instr). Format:
;
; 1 byte for the instruction ID
; 1 byte for arg constant
; 1 byte for 2nd arg constant
; 1 byte displacement for group arguments + flags
; 2 bytes for upcode (2nd byte is zero if instr is one byte)
;
; An "arg constant" is a char corresponding to either a row in argspecTbl or
; a group index in argGrpTbl (values < 0x10 are considered group indexes).
;
; The displacement bit is split in 2 nibbles: lower nibble is the displacement
; value, upper nibble is for flags:
;
; Bit 7: indicates that the numerical argument is of the 'e' type and has to be
; decreased by 2 (djnz, jr).
; Bit 6: it indicates that the group argument's value is to be placed on the
; second upcode rather than the first.
; Bit 5: Indicates that this row is handled very specially: the next two bytes
; aren't upcode bytes, but a routine address to call to handle this case with
; custom code.
instrTBl:
.db I_ADC, 'A', 'l', 0, 0x8e , 0 ; ADC A, (HL)
.db I_ADC, 'A', 0xb, 0, 0b10001000 , 0 ; ADC A, r
.db I_ADC, 'A', 'n', 0, 0xce , 0 ; ADC A, n
.db I_ADC, 'h', 0x3, 0x44, 0xed, 0b01001010 ; ADC HL, ss
.db I_ADD, 'A', 'l', 0, 0x86 , 0 ; ADD A, (HL)
.db I_ADD, 'A', 0xb, 0, 0b10000000 , 0 ; ADD A, r
.db I_ADD, 'A', 'n', 0, 0xc6 , 0 ; ADD A, n
.db I_ADD, 'h', 0x3, 4, 0b00001001 , 0 ; ADD HL, ss
.db I_ADD, 'X', 0x4, 0x44, 0xdd, 0b00001001 ; ADD IX, pp
.db I_ADD, 'Y', 0x5, 0x44, 0xfd, 0b00001001 ; ADD IY, rr
.db I_ADD, 'A', 'x', 0, 0xdd, 0x86 ; ADD A, (IX+d)
.db I_ADD, 'A', 'y', 0, 0xfd, 0x86 ; ADD A, (IY+d)
.db I_AND, 'l', 0, 0, 0xa6 , 0 ; AND (HL)
.db I_AND, 0xb, 0, 0, 0b10100000 , 0 ; AND r
.db I_AND, 'n', 0, 0, 0xe6 , 0 ; AND n
.db I_AND, 'x', 0, 0, 0xdd, 0xa6 ; AND (IX+d)
.db I_AND, 'y', 0, 0, 0xfd, 0xa6 ; AND (IY+d)
.db I_BIT, 'n', 'l', 0x20 \ .dw handleBITHL ; BIT b, (HL)
.db I_BIT, 'n', 'x', 0x20 \ .dw handleBITIX ; BIT b, (IX+d)
.db I_BIT, 'n', 'y', 0x20 \ .dw handleBITIY ; BIT b, (IY+d)
.db I_BIT, 'n', 0xb, 0x20 \ .dw handleBITR ; BIT b, r
.db I_CALL,0xa, 'N', 3, 0b11000100 , 0 ; CALL cc, NN
.db I_CALL,'N', 0, 0, 0xcd , 0 ; CALL NN
.db I_CCF, 0, 0, 0, 0x3f , 0 ; CCF
.db I_CP, 'l', 0, 0, 0xbe , 0 ; CP (HL)
.db I_CP, 0xb, 0, 0, 0b10111000 , 0 ; CP r
.db I_CP, 'n', 0, 0, 0xfe , 0 ; CP n
.db I_CP, 'x', 0, 0, 0xdd, 0xbe ; CP (IX+d)
.db I_CP, 'y', 0, 0, 0xfd, 0xbe ; CP (IY+d)
.db I_CPD, 0, 0, 0, 0xed, 0xa9 ; CPD
.db I_CPDR,0, 0, 0, 0xed, 0xb9 ; CPDR
.db I_CPI, 0, 0, 0, 0xed, 0xa1 ; CPI
.db I_CPIR,0, 0, 0, 0xed, 0xb1 ; CPIR
.db I_CPL, 0, 0, 0, 0x2f , 0 ; CPL
.db I_DAA, 0, 0, 0, 0x27 , 0 ; DAA
.db I_DEC, 'l', 0, 0, 0x35 , 0 ; DEC (HL)
.db I_DEC, 'X', 0, 0, 0xdd, 0x2b ; DEC IX
.db I_DEC, 'x', 0, 0, 0xdd, 0x35 ; DEC (IX+d)
.db I_DEC, 'Y', 0, 0, 0xfd, 0x2b ; DEC IY
.db I_DEC, 'y', 0, 0, 0xfd, 0x35 ; DEC (IY+d)
.db I_DEC, 0xb, 0, 3, 0b00000101 , 0 ; DEC r
.db I_DEC, 0x3, 0, 4, 0b00001011 , 0 ; DEC ss
.db I_DI, 0, 0, 0, 0xf3 , 0 ; DI
.db I_DJNZ,'n', 0, 0x80, 0x10 , 0 ; DJNZ e
.db I_EI, 0, 0, 0, 0xfb , 0 ; EI
.db I_EX, 'p', 'h', 0, 0xe3 , 0 ; EX (SP), HL
.db I_EX, 'p', 'X', 0, 0xdd, 0xe3 ; EX (SP), IX
.db I_EX, 'p', 'Y', 0, 0xfd, 0xe3 ; EX (SP), IY
.db I_EX, 'a', 'f', 0, 0x08 , 0 ; EX AF, AF'
.db I_EX, 'd', 'h', 0, 0xeb , 0 ; EX DE, HL
.db I_EXX, 0, 0, 0, 0xd9 , 0 ; EXX
.db I_HALT,0, 0, 0, 0x76 , 0 ; HALT
.db I_IM, 'n', 0, 0x20 \ .dw handleIM ; IM {0,1,2}
.db I_IN, 'A', 'm', 0, 0xdb , 0 ; IN A, (n)
.db I_IN, 0xb, 'k', 0x43, 0xed, 0b01000000 ; IN r, (C)
.db I_INC, 'l', 0, 0, 0x34 , 0 ; INC (HL)
.db I_INC, 'X', 0, 0, 0xdd , 0x23 ; INC IX
.db I_INC, 'x', 0, 0, 0xdd , 0x34 ; INC (IX+d)
.db I_INC, 'Y', 0, 0, 0xfd , 0x23 ; INC IY
.db I_INC, 'y', 0, 0, 0xfd , 0x34 ; INC (IY+d)
.db I_INC, 0xb, 0, 3, 0b00000100 , 0 ; INC r
.db I_INC, 0x3, 0, 4, 0b00000011 , 0 ; INC ss
.db I_IND, 0, 0, 0, 0xed, 0xaa ; IND
.db I_INDR,0, 0, 0, 0xed, 0xba ; INDR
.db I_INI, 0, 0, 0, 0xed, 0xa2 ; INI
.db I_INIR,0, 0, 0, 0xed, 0xb2 ; INIR
.db I_JP, 'l', 0, 0, 0xe9 , 0 ; JP (HL)
.db I_JP, 0xa, 'N', 3, 0b11000010 , 0 ; JP cc, NN
.db I_JP, 'N', 0, 0, 0xc3 , 0 ; JP NN
.db I_JP, 'x', 0, 0x20 \ .dw handleJPIX ; JP (IX)
.db I_JP, 'y', 0, 0x20 \ .dw handleJPIY ; JP (IY)
.db I_JR, 'n', 0, 0x80, 0x18 , 0 ; JR e
.db I_JR, 'C', 'n', 0x80, 0x38 , 0 ; JR C, e
.db I_JR, '=', 'n', 0x80, 0x30 , 0 ; JR NC, e
.db I_JR, 'Z', 'n', 0x80, 0x28 , 0 ; JR Z, e
.db I_JR, 'z', 'n', 0x80, 0x20 , 0 ; JR NZ, e
.db I_LD, 'c', 'A', 0, 0x02 , 0 ; LD (BC), A
.db I_LD, 'e', 'A', 0, 0x12 , 0 ; LD (DE), A
.db I_LD, 'A', 'c', 0, 0x0a , 0 ; LD A, (BC)
.db I_LD, 'A', 'e', 0, 0x1a , 0 ; LD A, (DE)
.db I_LD, 's', 'h', 0, 0xf9 , 0 ; LD SP, HL
.db I_LD, 'A', 'I', 0, 0xed, 0x57 ; LD A, I
.db I_LD, 'I', 'A', 0, 0xed, 0x47 ; LD I, A
.db I_LD, 'A', 'R', 0, 0xed, 0x5f ; LD A, R
.db I_LD, 'R', 'A', 0, 0xed, 0x4f ; LD R, A
.db I_LD, 'l', 0xb, 0, 0b01110000 , 0 ; LD (HL), r
.db I_LD, 0xb, 'l', 3, 0b01000110 , 0 ; LD r, (HL)
.db I_LD, 'l', 'n', 0, 0x36 , 0 ; LD (HL), n
.db I_LD, 0xb, 'n', 3, 0b00000110 , 0 ; LD r, n
.db I_LD, 0xb, 0xb, 0x20 \ .dw handleLDrr ; LD r, r'
.db I_LD, 0x3, 'N', 4, 0b00000001 , 0 ; LD dd, nn
.db I_LD, 'X', 'N', 0, 0xdd, 0x21 ; LD IX, NN
.db I_LD, 'Y', 'N', 0, 0xfd, 0x21 ; LD IY, NN
.db I_LD, 'M', 'A', 0, 0x32 , 0 ; LD (NN), A
.db I_LD, 'A', 'M', 0, 0x3a , 0 ; LD A, (NN)
.db I_LD, 'M', 'h', 0, 0x22 , 0 ; LD (NN), HL
.db I_LD, 'h', 'M', 0, 0x2a , 0 ; LD HL, (NN)
.db I_LD, 'M', 'X', 0, 0xdd, 0x22 ; LD (NN), IX
.db I_LD, 'X', 'M', 0, 0xdd, 0x2a ; LD IX, (NN)
.db I_LD, 'M', 'Y', 0, 0xfd, 0x22 ; LD (NN), IY
.db I_LD, 'Y', 'M', 0, 0xfd, 0x2a ; LD IY, (NN)
.db I_LD, 'M', 0x3, 0x44, 0xed, 0b01000011 ; LD (NN), dd
.db I_LD, 0x3, 'M', 0x44, 0xed, 0b01001011 ; LD dd, (NN)
.db I_LD, 'x', 'n', 0x20 \ .dw handleLDIXn ; LD (IX+d), n
.db I_LD, 'y', 'n', 0x20 \ .dw handleLDIYn ; LD (IY+d), n
.db I_LD, 'x', 0xb, 0x20 \ .dw handleLDIXr ; LD (IX+d), r
.db I_LD, 'y', 0xb, 0x20 \ .dw handleLDIYr ; LD (IY+d), r
.db I_LD, 0xb, 'x', 0x20 \ .dw handleLDrIX ; LD r, (IX+d)
.db I_LD, 0xb, 'y', 0x20 \ .dw handleLDrIY ; LD r, (IY+d)
.db I_LDD, 0, 0, 0, 0xed, 0xa8 ; LDD
.db I_LDDR,0, 0, 0, 0xed, 0xb8 ; LDDR
.db I_LDI, 0, 0, 0, 0xed, 0xa0 ; LDI
.db I_LDIR,0, 0, 0, 0xed, 0xb0 ; LDIR
.db I_NEG, 0, 0, 0, 0xed, 0x44 ; NEG
.db I_NOP, 0, 0, 0, 0x00 , 0 ; NOP
.db I_OR, 'l', 0, 0, 0xb6 , 0 ; OR (HL)
.db I_OR, 0xb, 0, 0, 0b10110000 , 0 ; OR r
.db I_OR, 'n', 0, 0, 0xf6 , 0 ; OR n
.db I_OR, 'x', 0, 0, 0xdd, 0xb6 ; OR (IX+d)
.db I_OR, 'y', 0, 0, 0xfd, 0xb6 ; OR (IY+d)
.db I_OTDR,0, 0, 0, 0xed, 0xbb ; OTDR
.db I_OTIR,0, 0, 0, 0xed, 0xb3 ; OTIR
.db I_OUT, 'm', 'A', 0, 0xd3 , 0 ; OUT (n), A
.db I_OUT, 'k', 0xb, 0x43, 0xed, 0b01000001 ; OUT (C), r
.db I_POP, 'X', 0, 0, 0xdd, 0xe1 ; POP IX
.db I_POP, 'Y', 0, 0, 0xfd, 0xe1 ; POP IY
.db I_POP, 0x1, 0, 4, 0b11000001 , 0 ; POP qq
.db I_PUSH,'X', 0, 0, 0xdd, 0xe5 ; PUSH IX
.db I_PUSH,'Y', 0, 0, 0xfd, 0xe5 ; PUSH IY
.db I_PUSH,0x1, 0, 4, 0b11000101 , 0 ; PUSH qq
.db I_RET, 0, 0, 0, 0xc9 , 0 ; RET
.db I_RET, 0xa, 0, 3, 0b11000000 , 0 ; RET cc
.db I_RETI,0, 0, 0, 0xed, 0x4d ; RETI
.db I_RETN,0, 0, 0, 0xed, 0x45 ; RETN
.db I_RL, 0xb, 0,0x40, 0xcb, 0b00010000 ; RL r
.db I_RLA, 0, 0, 0, 0x17 , 0 ; RLA
.db I_RLC, 0xb, 0,0x40, 0xcb, 0b00000000 ; RLC r
.db I_RLCA,0, 0, 0, 0x07 , 0 ; RLCA
.db I_RR, 0xb, 0,0x40, 0xcb, 0b00011000 ; RR r
.db I_RRA, 0, 0, 0, 0x1f , 0 ; RRA
.db I_RRC, 0xb, 0,0x40, 0xcb, 0b00001000 ; RRC r
.db I_RRCA,0, 0, 0, 0x0f , 0 ; RRCA
.db I_SBC, 'A', 'l', 0, 0x9e , 0 ; SBC A, (HL)
.db I_SBC, 'A', 0xb, 0, 0b10011000 , 0 ; SBC A, r
.db I_SBC,'h',0x3,0x44, 0xed, 0b01000010 ; SBC HL, ss
.db I_SCF, 0, 0, 0, 0x37 , 0 ; SCF
.db I_SUB, 'l', 0, 0, 0x96 , 0 ; SUB (HL)
.db I_SUB, 0xb, 0, 0, 0b10010000 , 0 ; SUB r
.db I_SUB, 'n', 0, 0, 0xd6 , 0 ; SUB n
.db I_XOR, 'l', 0, 0, 0xae , 0 ; XOR (HL)
.db I_XOR, 0xb, 0, 0, 0b10101000 , 0 ; XOR r
; *** Variables ***
; Args are 3 bytes: argspec, then values of numerical constants (when that's
; appropriate)
curArg1:
.db 0, 0, 0
curArg2:
.db 0, 0, 0
instrUpcode:
.db 0, 0, 0, 0