; *** Consts *** ; Number of rows in the argspec table .equ ARGSPEC_TBL_CNT 33 ; 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_RES 0x2a .equ I_RET 0x2b .equ I_RETI 0x2c .equ I_RETN 0x2d .equ I_RL 0x2e .equ I_RLA 0x2f .equ I_RLC 0x30 .equ I_RLCA 0x31 .equ I_RR 0x32 .equ I_RRA 0x33 .equ I_RRC 0x34 .equ I_RRCA 0x35 .equ I_RST 0x36 .equ I_SBC 0x37 .equ I_SCF 0x38 .equ I_SET 0x39 .equ I_SLA 0x3a .equ I_SRL 0x3b .equ I_SUB 0x3c .equ I_XOR 0x3d ; *** Variables *** ; Args are 3 bytes: argspec, then values of numerical constants (when that's ; appropriate) .equ INS_CURARG1 INS_RAMSTART .equ INS_CURARG2 INS_CURARG1+3 .equ INS_UPCODE INS_CURARG2+3 .equ INS_RAMEND INS_UPCODE+4 ; *** Code *** ; Checks whether A is 'N' or 'M' checkNOrM: cp 'N' ret z cp 'M' ret ; Checks whether A is 'n', 'm' checknm: cp 'n' ret z cp 'm' ret checklxy: cp 'l' ret z ; Checks whether A is 'x', 'y' checkxy: 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. On success, A contains either '+' or '-'. 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 DE contains ; the value of that constant. parseArg: call strlen or a ret z ; empty string? A already has our result: 0 push bc push hl 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 DE. ld a, 'N' jr .end .withParens: ld b, 0 ; make sure it doesn't hold '-' 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-. ; A contains either '+' or '-'. Save it for later, in B. ld b, a inc hl ; (HL) now points to X or Y ld a, (hl) call upcase 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 DE ; is '-' in B? if yes, we need to negate the low part of DE ld a, b cp '-' jr nz, .dontNegateDE ; we need to negate the low part of DE ; TODO: when parsing routines properly support unary negative numbers, ; We could replace this complicated scheme below with a nice hack where ; we start parsing our displacement number at the '+' and '-' char. ld a, e neg ld e, a .dontNegateDE: 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) ; When we have non-numerical args, we set DE to zero to have a clean ; result. ld de, 0 .end: pop hl 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. ld de, 0 ; in first pass, return a clean zero call zasmIsFirstPass ret z jp parseExpr ; Returns, with Z, whether A is a groupId isGroupId: or a jp z, unsetZ ; not a group cp 0xd ; max group id + 1 jp nc, unsetZ ; >= 0xd? not a group ; A is a group. ensure Z is set cp a 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). ; IX must point to argspec row. ; 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 spitUpcode. ; 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, .skip1 ; not a zero, we can continue ; zero, stop here cp 1 ; unset Z ret .skip1: ; If our argspec is 'l', then we also match 'x' and 'y' cp 'l' jr nz, .skip2 ; Does it accept IX and IY? bit 4, (ix+3) ld a, (hl) jp nz, checkxy ; bit set: our result is checkxy ; doesn't accept? then we don't match jp unsetZ .skip2: ; Alright, let's start with a special case. Is it part of the special ; "BIT" group, 0xc? If yes, we actually expect a number, which will ; then be ORed like a regular group index. cp 0xc jr z, .expectsBIT ; 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. .expectsBIT: ld a, (hl) cp 'N' inc hl ld a, (hl) dec hl cp 8 jr c, .isBit ; A < 8 ; not a bit or a ; unset Z ret .isBit: cp a ; set Z ret .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 ; *** 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 ; spitUpcode. 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 spitUpcode already. ; Handle like a regular "JP (IX+d)" except that we refuse any displacement: if ; a displacement is specified, we error out. handleJPIXY: ld a, (INS_CURARG1+1) or a ; numerical argument *must* be zero jr nz, .error ; ok, we're good ld a, 0xe9 ; second upcode ld (INS_UPCODE), a ld c, 1 ret .error: ld c, 0 ret handleBITR: ld b, 0b01000000 jr _handleBITR handleSETR: ld b, 0b11000000 jr _handleBITR handleRESR: ld b, 0b10000000 _handleBITR: ; get group value ld a, (INS_CURARG2+1) ; group value ld c, a ; write first upcode ld a, 0xcb ; first upcode ld (INS_UPCODE), a ; get bit value ld a, (INS_CURARG1+1) ; 0-7 rlca ; clears cary if any rla rla ; 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 b ; and with our "base" value and we're good! ld (INS_UPCODE+1), a ld c, 2 ret handleIM: ld a, (INS_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 (INS_UPCODE+1), a ld a, 0xed ld (INS_UPCODE), a ld c, 2 ret handleLDIXYn: ld a, 0x36 ; second upcode ld (INS_UPCODE), a ld a, (INS_CURARG1+1) ; IXY displacement ld (INS_UPCODE+1), a ld a, (INS_CURARG2+1) ; N ld (INS_UPCODE+2), a ld c, 3 ret handleLDIXYr: ld a, (INS_CURARG2+1) ; group value or 0b01110000 ; second upcode ld (INS_UPCODE), a ld a, (INS_CURARG1+1) ; IXY displacement ld (INS_UPCODE+1), a ld c, 2 ret handleLDrIXY: ld a, (INS_CURARG1+1) ; group value rlca \ rla \ rla or 0b01000110 ; second upcode ld (INS_UPCODE), a ld a, (INS_CURARG2+1) ; IXY displacement ld (INS_UPCODE+1), a ld c, 2 ret handleLDrr: ; first argument is displaced by 3 bits, second argument is not ; displaced and we or that with a leading 0b01000000 ld a, (INS_CURARG1+1) ; group value rlca rla rla ld c, a ; store it ld a, (INS_CURARG2+1) ; other group value or c or 0b01000000 ld (INS_UPCODE), a ld c, 1 ret handleRST: ld a, (INS_CURARG1+1) ; verify that A is either 0x08, 0x10, 0x18, 0x20, 0x28, 0x30 or 0x38. ; Good news: the relevant bits (bits 5:3) are already in place. We only ; have to verify that they're surrounded by zeroes. ld c, 0b11000111 and c jr nz, .error ; We're in range. good. ld a, (INS_CURARG1+1) or c ld (INS_UPCODE), a ld c, 1 ret .error: ld c, 0 ret ; Compute the upcode for argspec row at (IX) and arguments in curArg{1,2} and ; writes the resulting upcode to IO. ; A is zero, with Z set, on success. A is non-zero, with Z unset, on error. spitUpcode: push de push hl push bc ; before we begin, are we in a 'l' argspec? Is it flagged for IX/IY ; acceptance? If yes, a 'x' or 'y' instruction? Check this on both ; args and if we detect a 'x' or 'y', things are *always* the same: ; the upcode is exactly the same as its (HL) counterpart except that ; it is preceeded by 0xdd or 0xfd. If we're 'x' or 'y', then it means ; that we've already been matched to a 'l' argspec, so after spitting ; 0xdd or 0xfd, we can continue as normal. ld a, (ix+1) call checklxy jr z, .isl ld a, (ix+2) call checklxy jr nz, .begin ; no point in checking further. .isl: ld a, (INS_CURARG1) cp 'x' jr z, .isx cp 'y' jr z, .isy ld a, (INS_CURARG2) cp 'x' jr z, .isx cp 'y' jr z, .isy jr .begin .isx: ld a, 0xdd call ioPutB jr .begin .isy: ld a, 0xfd call ioPutB .begin: ; 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 INS_UPCODE and C is set. jp .writeIO .normalInstr: ; we begin by writing our "base upcode", which can be one or two bytes ld a, (ix+4) ; first upcode ld (INS_UPCODE), a ; from this point, DE points to "where we are" in terms of upcode ; writing. ld de, INS_UPCODE+1 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, INS_CURARG2 jr .isGroup .firstArgIsGroup: ld hl, INS_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 (INS_UPCODE+1), a jr .writeExtraBytes .firstUpcode: or (ix+4) ; first upcode ld (INS_UPCODE), 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 INS_UPCODE). ld a, (ix+1) ; first argspec ld hl, INS_CURARG1 call checkNOrM jr z, .withWord call checknm jr z, .withByte ld a, (INS_CURARG1) call checkxy jr z, .withByte ld a, (ix+2) ; second argspec ld hl, INS_CURARG2 call checkNOrM jr z, .withWord call checknm jr z, .withByte ld a, (INS_CURARG2) call checkxy jr z, .withByte ; nope, no number, alright, we're finished here jr .writeIO .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 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, .writeIO ; We're on second pass push de ; Don't let go of this, that's our dest push hl call zasmGetPC ; --> HL ex de, hl pop hl 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 .writeIO .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 .writeIO .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 ; to writeIO .writeIO: ; Before we write IO, let's check a very specific case: is our first ; upcode 0xcb and our byte count == 3? If yes, then swap the two last ; bytes. In all instructions except 0xcb ones, IX/IY displacement comes ; last, but in all 0xcb instructions, they come 2nd last. call .checkCB ; Let's write INS_UPCODE to IO dec c \ inc c ; is C zero? jr z, .numberTruncated ld b, c ; save output byte count ld hl, INS_UPCODE .loopWrite: ld a, (hl) call ioPutB jr nz, .ioError inc hl djnz .loopWrite cp a ; ensure Z jr .end .numberTruncated: ; Z already unset ld a, ERR_OVFL jr .end .ioError: ; Z already unset ld a, SHELL_ERR_IO_ERROR ; continue to .end .end: pop bc pop hl pop de ret .checkCB: ld a, (INS_UPCODE) cp 0xcb ret nz ld a, c cp 3 ret nz ; We are in 0xcb + displacement situation. Swap bytes 2 and 3. ld a, (INS_UPCODE+1) ex af, af' ld a, (INS_UPCODE+2) ld (INS_UPCODE+1), a ex af, af' ld (INS_UPCODE+2), a ret ; Parse argument in (HL) and place it in (IX) ; Sets Z on success, reset on error. processArg: call parseArg cp 0xff jr z, .error ld (ix), a ; When A is a number, DE 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 DE there unconditonally, LSB first. ld (ix+1), e ld (ix+2), d cp a ; ensure Z ret .error: ld a, ERR_BAD_ARG or a ; unset Z 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. On error, A contains an error code (ERR_*) 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 (INS_CURARG1), a ld (INS_CURARG2), a call readWord jr nz, .nomorearg ld ix, INS_CURARG1 call processArg jr nz, .end ; A is set to error, Z is unset call readComma jr nz, .nomorearg call readWord jr nz, .badfmt ld ix, INS_CURARG2 call processArg jr nz, .end ; A is set to error, Z is unset .nomorearg: ; Parsing done, no error, let's move forward to instr row matching! ; To speed up things a little, we use a poor man's indexing. Full ; bisecting would involve too much complexity. ld a, c ; recall A param ld ix, instrTBl cp I_EX jr c, .loop ld ix, instrTBlEX cp I_LD jr c, .loop ld ix, instrTBlLD cp I_RET jr c, .loop ld ix, instrTBlRET .loop: ld a, c ; recall A param call .matchPrimaryRow jr z, .match ld de, INSTR_TBL_ROWSIZE add ix, de ld a, 0xff cp (ix) jr nz, .loop ; No signature match ld a, ERR_BAD_ARG or a ; unset Z jr .end .match: ; We have our matching instruction row. We're getting pretty near our ; goal here! call spitUpcode jr .end ; Z and A set properly, even on error .badfmt: ; Z already unset ld a, ERR_BAD_FMT .end: pop de pop hl pop bc ret ; Compare primary row at (IX) with ID in A. Sets Z flag if there's a match. .matchPrimaryRow: cp (ix) ret nz ; name matches, let's see the rest ld hl, INS_CURARG1 ld a, (ix+1) call matchArg ret nz ld hl, INS_CURARG2 ld a, (ix+2) jp matchArg ; In instruction metadata below, argument types arge indicated with a single ; char mnemonic that is called "argspec". This is the table of correspondence. ; 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 ; SPECIAL GROUP "BIT": 0xc ; When special group "0xc" shows up in argspec, it means: accept a number ; between 0 and 7. The value is then treated like a regular group value. ; 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 "RES", 0 .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 "RST", 0 .db "SBC", 0 .db "SCF", 0 .db "SET", 0 .db "SLA", 0 .db "SRL", 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. ; Bit 4: When in an 'l' argspec, this means "I accept IX and IY variants". ; This table needs to be kept in ascending order of I_* value. 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', 0x10, 0x86 , 0 ; ADD A, (HL) + (IX/Y) .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_AND, 'l', 0, 0x10, 0xa6 , 0 ; AND (HL) + (IX/Y) .db I_AND, 0xb, 0, 0, 0b10100000 , 0 ; AND r .db I_AND, 'n', 0, 0, 0xe6 , 0 ; AND n .db I_BIT, 0xc, 'l', 0x53, 0xcb, 0b01000110 ; BIT b, (HL) + (IX/Y) .db I_BIT, 0xc, 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, 0x10, 0xbe , 0 ; CP (HL) + (IX/Y) .db I_CP, 0xb, 0, 0, 0b10111000 , 0 ; CP r .db I_CP, 'n', 0, 0, 0xfe , 0 ; CP n .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, 0x10, 0x35 , 0 ; DEC (HL) + (IX/Y) .db I_DEC, 'X', 0, 0, 0xdd, 0x2b ; DEC IX .db I_DEC, 'Y', 0, 0, 0xfd, 0x2b ; DEC IY .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 instrTBlEX: .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, 0x10, 0x34 , 0 ; INC (HL) + (IX/Y) .db I_INC, 'X', 0, 0, 0xdd , 0x23 ; INC IX .db I_INC, 'Y', 0, 0, 0xfd , 0x23 ; INC IY .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, 'x', 0, 0x20 \ .dw handleJPIXY ; JP (IX) .db I_JP, 'y', 0, 0x20 \ .dw handleJPIXY ; JP (IY) .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_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 instrTBlLD: .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 handleLDIXYn ; LD (IX+d), n .db I_LD, 'y', 'n', 0x20 \ .dw handleLDIXYn ; LD (IY+d), n .db I_LD, 'x', 0xb, 0x20 \ .dw handleLDIXYr ; LD (IX+d), r .db I_LD, 'y', 0xb, 0x20 \ .dw handleLDIXYr ; LD (IY+d), r .db I_LD, 0xb, 'x', 0x20 \ .dw handleLDrIXY ; LD r, (IX+d) .db I_LD, 0xb, 'y', 0x20 \ .dw handleLDrIXY ; 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, 0x10, 0xb6 , 0 ; OR (HL) + (IX/Y) .db I_OR, 0xb, 0, 0, 0b10110000 , 0 ; OR r .db I_OR, 'n', 0, 0, 0xf6 , 0 ; OR n .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_RES, 0xc, 'l', 0x53, 0xcb, 0b10000110 ; RES b, (HL) + (IX/Y) .db I_RES, 0xc, 0xb, 0x20 \ .dw handleRESR ; RES b, r instrTBlRET: .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_RL, 'l', 0,0x10, 0xcb, 0b00010110 ; RL (HL) + (IX/Y) .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_RR, 'l', 0,0x10, 0xcb, 0b00011110 ; RR (HL) + (IX/Y) .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_RST, 'n', 0, 0x20 \ .dw handleRST ; RST p .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_SET, 0xc, 'l', 0x53, 0xcb, 0b11000110 ; SET b, (HL) + (IX/Y) .db I_SET, 0xc, 0xb, 0x20 \ .dw handleSETR ; SET b, r .db I_SLA, 0xb, 0,0x40, 0xcb, 0b00100000 ; SLA r .db I_SRL, 0xb, 0,0x40, 0xcb, 0b00111000 ; SRL r .db I_SRL, 'l', 0,0x10, 0xcb, 0b00111110 ; SRL (HL) + (IX/Y) .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 .db I_XOR, 'n', 0, 0, 0xee , 0 ; XOR n .db 0xff