/* casm - Simple, portable assembler Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.co.uk) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ------------------------------------------------------------------------- GBCPU Assembler */ #include #include #include #include "global.h" #include "expr.h" #include "label.h" #include "parse.h" #include "cmd.h" #include "codepage.h" #include "varchar.h" #include "gbcpu.h" /* ---------------------------------------- TYPES AND GLOBALS */ typedef enum { A8, B8, C8, D8, E8, H8, L8, F8, AF16, BC16, DE16, HL16, SP16, BC_ADDRESS, DE_ADDRESS, HL_ADDRESS, SP_ADDRESS, HL_INCREMENT, HL_DECREMENT, FF00_C_INDEX, ADDRESS, VALUE, INVALID_REG } RegisterMode; typedef enum { NZ_FLAG, Z_FLAG, NC_FLAG, C_FLAG, } ProcessorFlag; typedef enum { IS_NORMAL_8_BIT = 0x01, IS_SPECIAL_8_BIT = 0x02, IS_16_BIT = 0x04, IS_MEMORY = 0x08, IS_FF00_C = 0x10, IS_SP = 0x20, IS_VALUE = 0x40, IS_SPECIAL_16_BIT = 0x80, } RegisterType; #define IsNormal8Bit(t) ((t) & IS_NORMAL_8_BIT) #define IsSpecial8Bit(t) ((t) & IS_SPECIAL_8_BIT) #define Is16Bit(t) ((t) & IS_16_BIT) #define IsMemory(t) ((t) & IS_MEMORY) #define IsSP(t) ((t) & IS_SP) #define IsAddress(t) (((t) & IS_VALUE) && ((t) & IS_MEMORY)) #define IsValue(t) ((t) & IS_VALUE) #define IsSimpleValue(t) ((t) == IS_VALUE) #define CheckRange(arg,num,min,max) \ do \ { \ if (IsFinalPass() && (num < min || num > max)) \ { \ snprintf(err, errsize, "%s: outside valid " \ "range of %d - %d", arg, min, max); \ return CMD_FAILED; \ } \ } while(0) #define CheckOffset(a,o) CheckRange(a,0,-128,127) static const int flag_bitmask[] = { 0x00, /* NZ_FLAG */ 0x01, /* Z_FLAG */ 0x02, /* NC_FLAG */ 0x03 /* C_FLAG */ }; static const char *flag_text[] = { "NZ", /* NZ_FLAG */ "Z", /* Z_FLAG */ "NC", /* NC_FLAG */ "C", /* C_FLAG */ NULL }; static const char *register_mode_name[] = { "A", "B", "C", "D", "E", "H", "L", "F", "AF", "BC", "DE", "HL", "SP", "(BC)", "(DE)", "(HL)", "(SP)", "(HL+)", "(HL-)", "(C)", "(address)", "value", "INVALID" }; static const int register_bitmask[] = { 0x7, /* A8 */ 0x0, /* B8 */ 0x1, /* C8 */ 0x2, /* D8 */ 0x3, /* E8 */ 0x4, /* H8 */ 0x5, /* L8 */ 0x6, /* F8 */ 0x3, /* AF16 */ 0x0, /* BC16 */ 0x1, /* DE16 */ 0x2, /* HL16 */ 0x3, /* SP16 */ 0x0, /* BC_ADDRESS */ 0x0, /* DE_ADDRESS */ 0x0, /* HL_ADDRESS */ 0x0, /* SP_ADDRESS */ 0x0, /* HL_INCREMENT */ 0x0, /* HL_DECREMENT */ 0x0, /* FF00_C_INDEX */ 0x0, /* ADDRESS */ 0x0, /* VALUE */ }; typedef struct { RegisterMode mode; int quote; int starts_with; int take_value; const char *ident; RegisterType type; } RegisterModeTable; static RegisterModeTable register_mode_table[] = { { A8, 0, FALSE, FALSE, "A", IS_NORMAL_8_BIT }, { B8, 0, FALSE, FALSE, "B", IS_NORMAL_8_BIT }, { C8, 0, FALSE, FALSE, "C", IS_NORMAL_8_BIT }, { D8, 0, FALSE, FALSE, "D", IS_NORMAL_8_BIT }, { E8, 0, FALSE, FALSE, "E", IS_NORMAL_8_BIT }, { H8, 0, FALSE, FALSE, "H", IS_NORMAL_8_BIT }, { L8, 0, FALSE, FALSE, "L", IS_NORMAL_8_BIT }, { F8, 0, FALSE, FALSE, "F", IS_SPECIAL_8_BIT }, { AF16, 0, FALSE, FALSE, "AF", IS_SPECIAL_16_BIT }, { BC16, 0, FALSE, FALSE, "BC", IS_16_BIT }, { DE16, 0, FALSE, FALSE, "DE", IS_16_BIT }, { HL16, 0, FALSE, FALSE, "HL", IS_16_BIT }, { SP16, 0, FALSE, FALSE, "SP", IS_16_BIT|IS_SP }, { BC_ADDRESS, '(', FALSE, FALSE, "BC", IS_16_BIT|IS_MEMORY }, { DE_ADDRESS, '(', FALSE, FALSE, "DE", IS_16_BIT|IS_MEMORY }, { HL_ADDRESS, '(', FALSE, FALSE, "HL", IS_16_BIT|IS_MEMORY }, { SP_ADDRESS, '(', FALSE, FALSE, "SP", IS_SPECIAL_16_BIT|IS_MEMORY|IS_SP }, { HL_INCREMENT, '(', FALSE, FALSE, "HLI", IS_SPECIAL_16_BIT|IS_MEMORY }, { HL_INCREMENT, '(', FALSE, FALSE, "HL+", IS_SPECIAL_16_BIT|IS_MEMORY }, { HL_DECREMENT, '(', FALSE, FALSE, "HLD", IS_SPECIAL_16_BIT|IS_MEMORY }, { HL_DECREMENT, '(', FALSE, FALSE, "HL-", IS_SPECIAL_16_BIT|IS_MEMORY }, { FF00_C_INDEX, '(', FALSE, FALSE, "C", IS_FF00_C }, /* These cheat -- basically anything that doesn't match until here is either an address or a value. */ { ADDRESS, '(', TRUE, TRUE, "", IS_VALUE|IS_MEMORY }, { VALUE, 0, TRUE, TRUE, "", IS_VALUE }, {0} }; typedef enum { WRITE_BYTE_LHS = -1, WRITE_WORD_LHS = -2, WRITE_BYTE_RHS = -3, WRITE_WORD_RHS = -4 } StreamCodes; typedef struct { RegisterMode lhs; RegisterMode rhs; int code[10]; } RegisterPairCodes; #define NUM_REGISTER_CODES(a) ((sizeof a)/(sizeof a[1])) /* ---------------------------------------- PRIVATE FUNCTIONS */ static int CalcRegisterMode(const char *arg, int quote, RegisterMode *mode, RegisterType *type, int *offset, char *err, size_t errsize) { int f; if (IsNullOrEmpty(arg)) { snprintf(err, errsize, "empty argument supplied"); return FALSE; } for(f = 0; register_mode_table[f].ident; f++) { RegisterModeTable *t = register_mode_table + f; if (quote == t->quote) { int match; if (t->starts_with) { match = CompareStart(arg, t->ident); } else { match = CompareString(arg, t->ident); } if (match) { *mode = t->mode; *type = t->type; *offset = 0; if (t->take_value) { size_t l = strlen(t->ident); if (!ExprEval(arg + l, offset)) { snprintf(err, errsize, "%s: expression error: %s", arg, ExprError()); return FALSE; } } return TRUE; } } } snprintf(err, errsize, "%s: couldn't calculate register/addressing mode", arg); return FALSE; } static int CalcFlagMode(const char *arg, ProcessorFlag *flag, int *mask, char *err, size_t errsize) { int f; for(f = 0; flag_text[f]; f++) { if (CompareString(arg, flag_text[f])) { *flag = f; *mask = flag_bitmask[f]; return TRUE; } } snprintf(err, errsize, "%s: unknown flag", arg); return FALSE; } static int WriteRegisterPairModes(const RegisterPairCodes *codes, size_t count, RegisterMode lhs, RegisterMode rhs, int val_lhs, int val_rhs, char *err, size_t errsize) { size_t f; for(f = 0; f < count; f++) { if (codes[f].lhs == lhs && codes[f].rhs == rhs) { int r; for(r = 0; codes[f].code[r]; r++) { switch(codes[f].code[r]) { case WRITE_BYTE_LHS: PCWrite(val_lhs); break; case WRITE_WORD_LHS: PCWriteWord(val_lhs); break; case WRITE_BYTE_RHS: PCWrite(val_rhs); break; case WRITE_WORD_RHS: PCWriteWord(val_rhs); break; default: PCWrite(codes[f].code[r]); break; } } return TRUE; } } snprintf(err, errsize, "LD: no code generation for register pair %s,%s", register_mode_name[lhs], register_mode_name[rhs]); return FALSE; } /* Assume accumulator if only one argument */ static CommandStatus AssumeAccumulator(int argc, char *argv[], int quoted[], char *err, size_t errsize, RegisterMode *r1, RegisterMode *r2, RegisterType *t1, RegisterType *t2, int *off1, int *off2) { CMD_ARGC_CHECK(2); if (argc == 2) { CalcRegisterMode("A", 0, r1, t1, off1, err, errsize); if (!CalcRegisterMode(argv[1], quoted[1], r2, t2, off2, err, errsize)) { return CMD_FAILED; } } else { if (!CalcRegisterMode(argv[1], quoted[1], r1, t1, off1, err, errsize)) { return CMD_FAILED; } if (!CalcRegisterMode(argv[2], quoted[2], r2, t2, off2, err, errsize)) { return CMD_FAILED; } } return CMD_OK; } /* Returns true if the passed register is any of the passed ones. The list of registers is terminated with INVALID_REG */ static int IsAnyOf(RegisterMode reg, ...) { va_list ap; RegisterMode m; va_start(ap, reg); m = va_arg(ap, RegisterMode); while(m != INVALID_REG) { if (reg == m) { return TRUE; } m = va_arg(ap, RegisterMode); } return FALSE; } static CommandStatus IllegalArgs(int argc, char *argv[], int quoted[], char *err, size_t errsize) { Varchar *str; int f; str = VarcharCreate(NULL); switch(argc) { case 0: VarcharPrintf(str, "no command/arguments"); break; case 1: VarcharPrintf(str, "%s: no arguments", argv[0]); break; case 2: VarcharPrintf(str, "%s: illegal argument", argv[0]); break; default: VarcharPrintf(str, "%s: illegal arguments", argv[0]); break; } for(f = 1; f < argc; f++) { if (f == 1) { VarcharAdd(str, " "); } else { VarcharAdd(str, ", "); } if (quoted[f] && quoted[f] == '(') { VarcharPrintf(str, "(%s)", argv[f]); } else if (quoted[f]) { VarcharPrintf(str, "%c%s%c", quoted[f], argv[f], quoted[f]); } else { VarcharAdd(str, argv[f]); } } snprintf(err, errsize, "%s", VarcharContents(str)); VarcharFree(str); return CMD_FAILED; } /* ---------------------------------------- COMMAND HANDLERS */ static CommandStatus LD(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { A8, BC_ADDRESS, {0x0a}, A8, DE_ADDRESS, {0x1a}, A8, ADDRESS, {0xfa, WRITE_WORD_RHS}, BC_ADDRESS, A8, {0x02}, DE_ADDRESS, A8, {0x12}, ADDRESS, A8, {0xea, WRITE_WORD_LHS}, HL_DECREMENT, A8, {0x32}, A8, HL_DECREMENT, {0x3a}, HL_INCREMENT, A8, {0x22}, A8, HL_INCREMENT, {0x2a}, FF00_C_INDEX, A8, {0xe2} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; CMD_ARGC_CHECK(3); if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) { return CMD_FAILED; } if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, &off2, err, errsize)) { return CMD_FAILED; } /* LD r,r' */ if (IsNormal8Bit(t1) && IsNormal8Bit(t2)) { CommandStatus s = CMD_OK; PCWrite(0x40 | register_bitmask[r1] << 3 | register_bitmask[r2]); return s; } /* LD r,n */ if (IsNormal8Bit(t1) && IsSimpleValue(t2)) { PCWrite(register_bitmask[r1] << 3 | 0x6); PCWrite(off2); return CMD_OK; } /* LD r,(HL) */ if (IsNormal8Bit(t1) && r2 == HL_ADDRESS) { PCWrite(0x46 | register_bitmask[r1] << 3); return CMD_OK; } /* LD (HL),r */ if (IsNormal8Bit(t2) && r1 == HL_ADDRESS) { PCWrite(0x70 | register_bitmask[r2]); return CMD_OK; } /* LD (HL),n */ if (r1 == HL_ADDRESS && r2 == VALUE) { PCWrite(0x36); PCWrite(off2); return CMD_OK; } /* LD rr,nn */ if (Is16Bit(t1) && !IsMemory(t1) && r2 == VALUE) { PCWrite(register_bitmask[r1] << 4 | 0x01); PCWriteWord(off2); return CMD_OK; } /* LD HL,(nn) */ if (r1 == HL16 && r2 == ADDRESS) { PCWrite(0x2a); PCWriteWord(off2); return CMD_OK; } /* LD rr,(nn) */ if (Is16Bit(t1) && !IsMemory(t1) && r2 == ADDRESS) { PCWrite(0xed); PCWrite(0x4b | register_bitmask[r1] << 4); PCWriteWord(off2); return CMD_OK; } /* LD (nn),HL */ if (r2 == HL16 && r1 == ADDRESS) { PCWrite(0x22); PCWriteWord(off1); return CMD_OK; } /* LD (nn),rr */ if (Is16Bit(t2) && !IsMemory(t2) && r1 == ADDRESS) { PCWrite(0xed); PCWrite(0x43 | register_bitmask[r2] << 4); PCWriteWord(off1); return CMD_OK; } /* LD SP,HL */ if (r2 == HL16 && r1 == SP16) { PCWrite(0xf9); return CMD_OK; } /* LD ($ff00 + n), A */ if (r1 == A8 && r2 == ADDRESS && off2 >= 0xff00) { PCWrite(0xf0); PCWrite(off2 - 0xff00); return CMD_OK; } /* LD ($ff00 + n), A */ if (r1 == ADDRESS && r2 == A8 && off1 >= 0xff00) { PCWrite(0xe0); PCWrite(off1 - 0xff00); return CMD_OK; } /* Custom opcode generation using the codes table */ if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus LDH(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { A8, ADDRESS, {0xf0, WRITE_BYTE_RHS}, ADDRESS, A8, {0xe0, WRITE_BYTE_LHS} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; CMD_ARGC_CHECK(3); if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) { return CMD_FAILED; } if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, &off2, err, errsize)) { return CMD_FAILED; } if (r1 == ADDRESS) { if (off1 >= 0xff00 && off1 <= 0xffff) { off1 -= 0xff00; } CheckRange(argv[1], off1, 0, 255); } if (r2 == ADDRESS) { if (off2 >= 0xff00 && off2 <= 0xffff) { off2 -= 0xff00; } CheckRange(argv[2], off2, 0, 255); } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus LDD(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { HL_ADDRESS, A8, {0x32}, A8, HL_ADDRESS, {0x3a} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; CMD_ARGC_CHECK(3); if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) { return CMD_FAILED; } if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, &off2, err, errsize)) { return CMD_FAILED; } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus LDI(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { HL_ADDRESS, A8, {0x22}, A8, HL_ADDRESS, {0x2a} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; CMD_ARGC_CHECK(3); if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) { return CMD_FAILED; } if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, &off2, err, errsize)) { return CMD_FAILED; } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus PUSH(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { RegisterMode r1; RegisterType t1; int off1; CMD_ARGC_CHECK(2); if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) { return CMD_FAILED; } /* PUSH rr */ if (r1 == AF16 || r1 == BC16 || r1 == DE16 || r1 == HL16) { PCWrite(0xc5 | register_bitmask[r1] << 4); return CMD_OK; } snprintf(err, errsize, "%s: invalid argument %s", argv[0], argv[1]); return CMD_OK; } static CommandStatus POP(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { RegisterMode r1; RegisterType t1; int off1; CMD_ARGC_CHECK(2); if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) { return CMD_FAILED; } /* POP rr */ if (r1 == AF16 || r1 == BC16 || r1 == DE16 || r1 == HL16) { PCWrite(0xc1 | register_bitmask[r1] << 4); return CMD_OK; } snprintf(err, errsize, "%s: invalid argument %s", argv[0], argv[1]); return CMD_OK; } static CommandStatus ADD(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { A8, VALUE, {0xc6, WRITE_BYTE_RHS}, A8, HL_ADDRESS, {0x86}, SP16, VALUE, {0xe8, WRITE_WORD_RHS} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; if (AssumeAccumulator(argc, argv, quoted, err, errsize, &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) { return CMD_FAILED; } /* ADD A,r */ if (r1 == A8 && IsNormal8Bit(t2)) { PCWrite(0x80 | register_bitmask[r2]); return CMD_OK; } /* ADD HL,rr */ if (r1 == HL16 && IsAnyOf(r2, BC16, DE16, HL16, SP16, INVALID_REG)) { PCWrite(0x09 | register_bitmask[r2] << 4); return CMD_OK; } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus ADC(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { A8, VALUE, {0xce, WRITE_BYTE_RHS}, A8, HL_ADDRESS, {0x8e} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; if (AssumeAccumulator(argc, argv, quoted, err, errsize, &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) { return CMD_FAILED; } /* ADC A,r */ if (r1 == A8 && IsNormal8Bit(t2)) { PCWrite(0x88 | register_bitmask[r2]); return CMD_OK; } /* ADC HL,rr */ if (r1 == HL16 && IsAnyOf(r2, BC16, DE16, HL16, SP16, INVALID_REG)) { PCWrite(0xed); PCWrite(0x4a | register_bitmask[r2] << 4); return CMD_OK; } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus SUB(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { A8, VALUE, {0xd6, WRITE_BYTE_RHS}, A8, HL_ADDRESS, {0x96} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; if (AssumeAccumulator(argc, argv, quoted, err, errsize, &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) { return CMD_FAILED; } /* SUB A,r */ if (r1 == A8 && IsNormal8Bit(t2)) { PCWrite(0x90 | register_bitmask[r2]); return CMD_OK; } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus SBC(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { A8, VALUE, {0xde, WRITE_BYTE_RHS}, A8, HL_ADDRESS, {0x9e} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; if (AssumeAccumulator(argc, argv, quoted, err, errsize, &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) { return CMD_FAILED; } /* SBC A,r */ if (r1 == A8 && IsNormal8Bit(t2)) { PCWrite(0x98 | register_bitmask[r2]); return CMD_OK; } /* SBC HL,rr */ if (r1 == HL16 && IsAnyOf(r2, BC16, DE16, HL16, SP16, INVALID_REG)) { PCWrite(0xed); PCWrite(0x42 | register_bitmask[r2] << 4); return CMD_OK; } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus AND(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { A8, VALUE, {0xe6, WRITE_BYTE_RHS}, A8, HL_ADDRESS, {0xa6} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; if (AssumeAccumulator(argc, argv, quoted, err, errsize, &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) { return CMD_FAILED; } /* AND A,r */ if (r1 == A8 && IsNormal8Bit(t2)) { PCWrite(0xa0 | register_bitmask[r2]); return CMD_OK; } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus OR(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { A8, VALUE, {0xf6, WRITE_BYTE_RHS}, A8, HL_ADDRESS, {0xb6} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; if (AssumeAccumulator(argc, argv, quoted, err, errsize, &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) { return CMD_FAILED; } /* OR A,r */ if (r1 == A8 && IsNormal8Bit(t2)) { PCWrite(0xb0 | register_bitmask[r2]); return CMD_OK; } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus XOR(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { A8, VALUE, {0xee, WRITE_BYTE_RHS}, A8, HL_ADDRESS, {0xae} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; if (AssumeAccumulator(argc, argv, quoted, err, errsize, &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) { return CMD_FAILED; } /* XOR A,r */ if (r1 == A8 && IsNormal8Bit(t2)) { PCWrite(0xa8 | register_bitmask[r2]); return CMD_OK; } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus CP(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static RegisterPairCodes codes[] = { A8, VALUE, {0xfe, WRITE_BYTE_RHS}, A8, HL_ADDRESS, {0xbe} }; RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; if (AssumeAccumulator(argc, argv, quoted, err, errsize, &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) { return CMD_FAILED; } /* CP A,r */ if (r1 == A8 && IsNormal8Bit(t2)) { PCWrite(0xb8 | register_bitmask[r2]); return CMD_OK; } if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), r1, r2, off1, off2, err, errsize)) { return CMD_FAILED; } return CMD_OK; } static CommandStatus INC(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { RegisterMode r1; RegisterType t1; int off1; CMD_ARGC_CHECK(2); if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) { return CMD_FAILED; } /* INC r */ if (IsNormal8Bit(t1)) { PCWrite(0x04 | register_bitmask[r1] << 3); return CMD_OK; } /* INC (HL) */ if (r1 == HL_ADDRESS) { PCWrite(0x34); return CMD_OK; } /* INC rr */ if (Is16Bit(t1) && !IsMemory(t1)) { PCWrite(0x03 | register_bitmask[r1] << 4); return CMD_OK; } return IllegalArgs(argc, argv, quoted, err, errsize); } static CommandStatus DEC(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { RegisterMode r1; RegisterType t1; int off1; CMD_ARGC_CHECK(2); if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) { return CMD_FAILED; } /* DEC r */ if (IsNormal8Bit(t1)) { PCWrite(0x05 | register_bitmask[r1] << 3); return CMD_OK; } /* DEC (HL) */ if (r1 == HL_ADDRESS) { PCWrite(0x35); return CMD_OK; } /* DEC rr */ if (Is16Bit(t1) && !IsMemory(t1)) { PCWrite(0x0b | register_bitmask[r1] << 4); return CMD_OK; } return IllegalArgs(argc, argv, quoted, err, errsize); } static CommandStatus RLC_RL_SLA_SWAP(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { RegisterMode r1; RegisterType t1; int off1; int opcode_mask; CMD_ARGC_CHECK(2); if (CompareString(argv[0], "RLC")) { opcode_mask = 0x00; } else if (CompareString(argv[0], "RL")) { opcode_mask = 0x10; } else if (CompareString(argv[0], "SLA")) { opcode_mask = 0x20; } else if (CompareString(argv[0], "SWAP")) { opcode_mask = 0x30; } if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) { return CMD_FAILED; } /* Normal opcodes */ if (argc == 2) { /* OP r */ if (IsNormal8Bit(t1)) { PCWrite(0xcb); PCWrite(opcode_mask | register_bitmask[r1]); return CMD_OK; } /* OP (HL) */ if (r1 == HL_ADDRESS) { PCWrite(0xcb); PCWrite(opcode_mask | 0x06); return CMD_OK; } } return IllegalArgs(argc, argv, quoted, err, errsize); } static CommandStatus BIT_SET_RES(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { RegisterMode r1, r2; RegisterType t1, t2; int off1, off2; int opcode_mask; CMD_ARGC_CHECK(3); if (CompareString(argv[0], "BIT")) { opcode_mask = 0x40; } else if (CompareString(argv[0], "SET")) { opcode_mask = 0xc0; } else if (CompareString(argv[0], "RES")) { opcode_mask = 0x80; } if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) { return CMD_FAILED; } if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, &off2, err, errsize)) { return CMD_FAILED; } if (r1 != VALUE || (off1 < 0 || off1 > 7)) { snprintf(err, errsize, "%s: illegal value %s for bit number", argv[0], argv[1]); return CMD_FAILED; } /* Normal opcodes */ if (argc == 3) { /* OP b,r */ if (IsNormal8Bit(t2)) { PCWrite(0xcb); PCWrite(opcode_mask | off1 << 3 | register_bitmask[r2]); return CMD_OK; } /* OP b,(HL) */ if (r2 == HL_ADDRESS) { PCWrite(0xcb); PCWrite(opcode_mask | off1 << 3 | 0x06); return CMD_OK; } } return IllegalArgs(argc, argv, quoted, err, errsize); } static CommandStatus JP(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { if (argc == 2) { RegisterMode mode; RegisterType type; int val; if (!CalcRegisterMode(argv[1], quoted[1], &mode, &type, &val, err, errsize)) { return CMD_FAILED; } if (mode == VALUE) { PCWrite(0xc3); PCWriteWord(val); return CMD_OK; } if (mode == HL_ADDRESS) { PCWrite(0xe9); return CMD_OK; } } else if (argc == 3) { RegisterMode mode; RegisterType type; int val; ProcessorFlag flag; int mask; if (!CalcFlagMode(argv[1], &flag, &mask, err, errsize) || !CalcRegisterMode(argv[2], quoted[2], &mode, &type, &val, err, errsize)) { return CMD_FAILED; } if (mode == VALUE) { PCWrite(0xc2 | mask << 3); PCWriteWord(val); return CMD_OK; } } return IllegalArgs(argc, argv, quoted, err, errsize); } static CommandStatus JR(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { if (argc == 2) { RegisterMode mode; RegisterType type; int val; if (!CalcRegisterMode(argv[1], quoted[1], &mode, &type, &val, err, errsize)) { return CMD_FAILED; } if (mode == VALUE) { int rel; rel = val - ((PC() + 2) % 0x10000); CheckOffset(argv[1], rel); PCWrite(0x18); PCWrite(rel); return CMD_OK; } } else if (argc == 3) { RegisterMode mode; RegisterType type; int val; ProcessorFlag flag; int mask; if (!CalcFlagMode(argv[1], &flag, &mask, err, errsize) || !CalcRegisterMode(argv[2], quoted[2], &mode, &type, &val, err, errsize)) { return CMD_FAILED; } if (mode == VALUE && (flag >= NZ_FLAG && flag <= C_FLAG)) { int rel; rel = val - ((PC() + 2) % 0x10000); CheckOffset(argv[2], rel); PCWrite(0x20 | mask << 3); PCWrite(rel); return CMD_OK; } } return IllegalArgs(argc, argv, quoted, err, errsize); } static CommandStatus CALL(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { if (argc == 2) { RegisterMode mode; RegisterType type; int val; if (!CalcRegisterMode(argv[1], quoted[1], &mode, &type, &val, err, errsize)) { return CMD_FAILED; } if (mode == VALUE) { PCWrite(0xcd); PCWriteWord(val); return CMD_OK; } } else if (argc == 3) { RegisterMode mode; RegisterType type; int val; ProcessorFlag flag; int mask; if (!CalcFlagMode(argv[1], &flag, &mask, err, errsize) || !CalcRegisterMode(argv[2], quoted[2], &mode, &type, &val, err, errsize)) { return CMD_FAILED; } if (mode == VALUE) { PCWrite(0xc4 | mask << 3); PCWriteWord(val); return CMD_OK; } } return IllegalArgs(argc, argv, quoted, err, errsize); } static CommandStatus RET(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { if (argc == 1) { PCWrite(0xc9); return CMD_OK; } else if (argc == 2) { ProcessorFlag flag; int mask; if (!CalcFlagMode(argv[1], &flag, &mask, err, errsize)) { return CMD_FAILED; } PCWrite(0xc0 | mask << 3); return CMD_OK; } return IllegalArgs(argc, argv, quoted, err, errsize); } static CommandStatus RST(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { static struct { int dec; int hex; } rst_arg[] = { {0, 0x00}, {8, 0x08}, {10, 0x10}, {18, 0x18}, {20, 0x20}, {28, 0x28}, {30, 0x30}, {38, 0x38}, {-1} }; RegisterMode mode; RegisterType type; int val; CMD_ARGC_CHECK(2); if (!CalcRegisterMode(argv[1], quoted[1], &mode, &type, &val, err, errsize)) { return CMD_FAILED; } if (mode == VALUE) { int f; int mask = -1; for(f = 0; rst_arg[f].dec != -1; f++) { if (rst_arg[f].dec == val || rst_arg[f].hex == val) { mask = f << 3; } } if (mask != -1) { PCWrite(0xc7 | mask); return CMD_OK; } } return IllegalArgs(argc, argv, quoted, err, errsize); } static CommandStatus STOP(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { if (argc == 1) { PCWrite(0x10); PCWrite(0x00); return CMD_OK; } return IllegalArgs(argc, argv, quoted, err, errsize); } /* ---------------------------------------- OPCODE TABLES */ typedef struct { const char *op; int code[3]; /* Zero ends, but code[0] always used. */ } OpcodeTable; typedef struct { const char *op; Command cmd; } HandlerTable; static const HandlerTable handler_table[] = { {"LD", LD}, {"LDH", LDH}, {"LDD", LDD}, {"LDI", LDI}, {"PUSH", PUSH}, {"POP", POP}, {"ADD", ADD}, {"ADC", ADC}, {"SUB", SUB}, {"SBC", SBC}, {"AND", AND}, {"OR", OR}, {"XOR", XOR}, {"EOR", XOR}, {"CP", CP}, {"INC", INC}, {"DEC", DEC}, {"RLC", RLC_RL_SLA_SWAP}, {"RL", RLC_RL_SLA_SWAP}, {"SLA", RLC_RL_SLA_SWAP}, {"SWAP", RLC_RL_SLA_SWAP}, {"BIT", BIT_SET_RES}, {"RES", BIT_SET_RES}, {"SET", BIT_SET_RES}, {"JP", JP}, {"JR", JR}, {"CALL", CALL}, {"RET", RET}, {"RST", RST}, {"STOP", STOP}, {NULL} }; static const OpcodeTable implied_opcodes[] = { {"NOP", {0x00}}, {"DI", {0xf3}}, {"EI", {0xfb}}, {"HALT", {0x76}}, {"HLT", {0x76}}, {"DAA", {0x27}}, {"CPL", {0x2f}}, {"SCF", {0x37}}, {"CCF", {0x3f}}, {"RLCA", {0x07}}, {"RRCA", {0x0f}}, {"RLA", {0x17}}, {"RRA", {0x1f}}, {"RETI", {0xd9}}, {NULL} }; /* ---------------------------------------- PUBLIC INTERFACES */ void Init_GBCPU(void) { } const ValueTable *Options_GBCPU(void) { return NULL; } CommandStatus SetOption_GBCPU(int opt, int argc, char *argv[], int quoted[], char *err, size_t errsize) { return CMD_OK; } CommandStatus Handler_GBCPU(const char *label, int argc, char *argv[], int quoted[], char *err, size_t errsize) { int f; /* Check for simple (implied addressing) opcodes */ for(f = 0; implied_opcodes[f].op; f++) { if (CompareString(argv[0], implied_opcodes[f].op)) { int r; PCWrite(implied_opcodes[f].code[0]); for(r = 1; implied_opcodes[f].code[r]; r++) { PCWrite(implied_opcodes[f].code[r]); } return CMD_OK; } } /* Check for other opcodes */ for(f = 0; handler_table[f].op; f++) { if (CompareString(argv[0], handler_table[f].op)) { return handler_table[f].cmd(label, argc, argv, quoted, err, errsize);; } } return CMD_NOT_KNOWN; } /* vim: ai sw=4 ts=8 expandtab */