collapseos/apps/zasm/instr.asm
Virgil Dupras b0318f4891 zasm: make instr unt write directly to ioPutC
Couldn't get rid of instrUpcode though, too complicated.
2019-05-17 15:35:49 -04:00

1104 lines
29 KiB
NASM

; *** Consts ***
; Number of rows in the argspec table
ARGSPEC_TBL_CNT .equ 31
; Number of rows in the primary instructions table
INSTR_TBL_CNT .equ 142
; size in bytes of each row in the primary instructions table
INSTR_TBL_ROWSIZE .equ 6
; Instruction IDs They correspond to the index of the table in instrNames
I_ADC .equ 0x00
I_ADD .equ 0x01
I_AND .equ 0x02
I_BIT .equ 0x03
I_CALL .equ 0x04
I_CCF .equ 0x05
I_CP .equ 0x06
I_CPD .equ 0x07
I_CPDR .equ 0x08
I_CPI .equ 0x09
I_CPIR .equ 0x0a
I_CPL .equ 0x0b
I_DAA .equ 0x0c
I_DEC .equ 0x0d
I_DI .equ 0x0e
I_DJNZ .equ 0x0f
I_EI .equ 0x10
I_EX .equ 0x11
I_EXX .equ 0x12
I_HALT .equ 0x13
I_IM .equ 0x14
I_IN .equ 0x15
I_INC .equ 0x16
I_IND .equ 0x17
I_INDR .equ 0x18
I_INI .equ 0x19
I_INIR .equ 0x1a
I_JP .equ 0x1b
I_JR .equ 0x1c
I_LD .equ 0x1d
I_LDD .equ 0x1e
I_LDDR .equ 0x1f
I_LDI .equ 0x20
I_LDIR .equ 0x21
I_NEG .equ 0x22
I_NOP .equ 0x23
I_OR .equ 0x24
I_OTDR .equ 0x25
I_OTIR .equ 0x26
I_OUT .equ 0x27
I_POP .equ 0x28
I_PUSH .equ 0x29
I_RET .equ 0x2a
I_RETI .equ 0x2b
I_RETN .equ 0x2c
I_RLA .equ 0x2d
I_RLCA .equ 0x2e
I_RRA .equ 0x2f
I_RRCA .equ 0x30
I_SBC .equ 0x31
I_SCF .equ 0x32
I_SUB .equ 0x33
I_XOR .equ 0x34
; 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)
cp 'I'
jr nz, .end ; Z already unset
inc hl
ld a, (hl)
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
cp 0
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 parseExpr
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 parseExpr
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
; 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
cp 0 ; 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 hl, de ; 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 a, (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.
cp 0
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 a, (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
ld ixh, d
ld ixl, e
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
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.
ld ixh, d
ld ixl, e
; 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 a, (ix+5) ; second upcode
cp 0 ; do we have a second upcode?
jr z, .onlyOneUpcode
; we have two upcodes
ld (de), a
inc de
.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 a, 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
ld c, 1
jr .computeBytesWritten
.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.
ld c, 2 ; this is a two bytes instruction
call zasmIsFirstPass
jr z, .computeBytesWritten
; 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 .computeBytesWritten
.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
ldi
ld c, 2
jr .computeBytesWritten
.withWord:
inc hl ; HL now points to LSB
; Clear to proceed. HL already points to our number
ldi ; LSB written, we point to MSB now
ldi ; MSB written
ld c, 3
jr .computeBytesWritten
.computeBytesWritten:
; At this point, everything that we needed to write in instrUpcode is
; written an C is 1 if we have no extra byte, 2 if we have an extra
; byte and 3 if we have an extra word. What we need to do here is check
; if ix+5 is non-zero and increase C if it is.
ld a, (ix+5)
cp 0
jr z, .end ; no second upcode? nothing to do.
; We have 2 base upcodes
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
ld a, ixl
ld (de), a
inc de
ld a, ixh
ld (de), a
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 "RLA", 0
.db "RLCA"
.db "RRA", 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, n
.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_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_RLA, 0, 0, 0, 0x17 , 0 ; RLA
.db I_RLCA,0, 0, 0, 0x07 , 0 ; RLCA
.db I_RRA, 0, 0, 0, 0x1f , 0 ; RRA
.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_SCF, 0, 0, 0, 0x37 , 0 ; SCF
.db I_SUB, 'A', 'l', 0, 0x96 , 0 ; SUB A, (HL)
.db I_SUB, 'A', 0xb, 0, 0b10010000 , 0 ; SUB A, 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