diff --git a/dcc6502.c b/dcc6502.c index c06cc31..ffde3b7 100644 --- a/dcc6502.c +++ b/dcc6502.c @@ -34,6 +34,10 @@ #define VERSION_INFO "v2.0" #define NUMBER_OPCODES 151 +/* Exceptions for cycle counting */ +#define CYCLES_CROSS_PAGE_ADDS_ONE (1 << 0) +#define CYCLES_BRANCH_TAKEN_ADDS_ONE (1 << 1) + /* The 6502's 13 addressing modes */ typedef enum { IMMED = 0, /* Immediate */ @@ -61,7 +65,7 @@ typedef struct opcode_s { const char *mnemonic; /* Index in the name table */ addressing_mode_e addressing; /* Addressing mode */ unsigned int cycles; /* Number of cycles */ - unsigned int cross_page; /* 1 if cross-page boundaries affect cycles */ + unsigned int cycles_exceptions; /* Mask of cycle-counting exceptions */ } opcode_t; typedef struct options_s { @@ -73,53 +77,52 @@ typedef struct options_s { uint16_t org; /* Origin of addresses */ } options_t; - /* Opcode table */ -opcode_t opcode_table[NUMBER_OPCODES] = { - {0x69, "ADC", IMMED, 2, 1}, /* ADC */ - {0x65, "ADC", ZEROP, 3, 1}, - {0x75, "ADC", ZEPIX, 4, 1}, - {0x6D, "ADC", ABSOL, 4, 1}, - {0x7D, "ADC", ABSIX, 4, 1}, - {0x79, "ADC", ABSIY, 4, 1}, - {0x61, "ADC", INDIN, 6, 1}, - {0x71, "ADC", ININD, 5, 1}, +static opcode_t g_opcode_table[NUMBER_OPCODES] = { + {0x69, "ADC", IMMED, 2, 0}, /* ADC */ + {0x65, "ADC", ZEROP, 3, 0}, + {0x75, "ADC", ZEPIX, 4, 0}, + {0x6D, "ADC", ABSOL, 4, 0}, + {0x7D, "ADC", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0x79, "ADC", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0x61, "ADC", INDIN, 6, 0}, + {0x71, "ADC", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE}, - {0x29, "AND", IMMED, 2, 1}, /* AND */ - {0x25, "AND", ZEROP, 3, 1}, - {0x35, "AND", ZEPIX, 4, 1}, - {0x2D, "AND", ABSOL, 4, 1}, - {0x3D, "AND", ABSIX, 4, 1}, - {0x39, "AND", ABSIY, 4, 1}, - {0x21, "AND", INDIN, 6, 1}, - {0x31, "AND", ININD, 5, 1}, + {0x29, "AND", IMMED, 2, 0}, /* AND */ + {0x25, "AND", ZEROP, 3, 0}, + {0x35, "AND", ZEPIX, 4, 0}, + {0x2D, "AND", ABSOL, 4, 0}, + {0x3D, "AND", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0x39, "AND", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0x21, "AND", INDIN, 6, 0}, + {0x31, "AND", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE}, {0x0A, "ASL", ACCUM, 2, 0}, /* ASL */ {0x06, "ASL", ZEROP, 5, 0}, {0x16, "ASL", ZEPIX, 6, 0}, {0x0E, "ASL", ABSOL, 6, 0}, - {0x1E, "ASL", ABSIX, 6, 0}, + {0x1E, "ASL", ABSIX, 7, 0}, - {0x90, "BCC", RELAT, 4, 1}, /* BCC */ + {0x90, "BCC", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BCC */ - {0xB0, "BCS", RELAT, 4, 1}, /* BCS */ + {0xB0, "BCS", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BCS */ - {0xF0, "BEQ", RELAT, 4, 1}, /* BEQ */ + {0xF0, "BEQ", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BEQ */ {0x24, "BIT", ZEROP, 3, 0}, /* BIT */ {0x2C, "BIT", ABSOL, 4, 0}, - {0x30, "BMI", RELAT, 4, 1}, /* BMI */ + {0x30, "BMI", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BMI */ - {0xD0, "BNE", RELAT, 4, 1}, /* BNE */ + {0xD0, "BNE", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BNE */ - {0x10, "BPL", RELAT, 4, 1}, /* BPL */ + {0x10, "BPL", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BPL */ {0x00, "BRK", IMPLI, 7, 0}, /* BRK */ - {0x50, "BVC", RELAT, 4, 1}, /* BVC */ + {0x50, "BVC", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BVC */ - {0x70, "BVS", RELAT, 4, 1}, /* BVS */ + {0x70, "BVS", RELAT, 2, CYCLES_CROSS_PAGE_ADDS_ONE | CYCLES_BRANCH_TAKEN_ADDS_ONE}, /* BVS */ {0x18, "CLC", IMPLI, 2, 0}, /* CLC */ @@ -133,10 +136,10 @@ opcode_t opcode_table[NUMBER_OPCODES] = { {0xC5, "CMP", ZEROP, 3, 0}, {0xD5, "CMP", ZEPIX, 4, 0}, {0xCD, "CMP", ABSOL, 4, 0}, - {0xDD, "CMP", ABSIX, 4, 0}, - {0xD9, "CMP", ABSIY, 4, 0}, + {0xDD, "CMP", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0xD9, "CMP", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, {0xC1, "CMP", INDIN, 6, 0}, - {0xD1, "CMP", ININD, 5, 0}, + {0xD1, "CMP", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE}, {0xE0, "CPX", IMMED, 2, 0}, /* CPX */ {0xE4, "CPX", ZEROP, 3, 0}, @@ -149,25 +152,25 @@ opcode_t opcode_table[NUMBER_OPCODES] = { {0xC6, "DEC", ZEROP, 5, 0}, /* DEC */ {0xD6, "DEC", ZEPIX, 6, 0}, {0xCE, "DEC", ABSOL, 6, 0}, - {0xDE, "DEC", ABSIX, 6, 0}, + {0xDE, "DEC", ABSIX, 7, 0}, {0xCA, "DEX", IMPLI, 2, 0}, /* DEX */ {0x88, "DEY", IMPLI, 2, 0}, /* DEY */ - {0x49, "EOR", IMMED, 2, 1}, /* EOR */ - {0x45, "EOR", ZEROP, 3, 1}, - {0x55, "EOR", ZEPIX, 4, 1}, - {0x4D, "EOR", ABSOL, 4, 1}, - {0x5D, "EOR", ABSIX, 4, 1}, - {0x59, "EOR", ABSIY, 4, 1}, + {0x49, "EOR", IMMED, 2, 0}, /* EOR */ + {0x45, "EOR", ZEROP, 3, 0}, + {0x55, "EOR", ZEPIX, 4, 0}, + {0x4D, "EOR", ABSOL, 4, 0}, + {0x5D, "EOR", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0x59, "EOR", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, {0x41, "EOR", INDIN, 6, 1}, - {0x51, "EOR", ININD, 5, 1}, + {0x51, "EOR", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE}, {0xE6, "INC", ZEROP, 5, 0}, /* INC */ {0xF6, "INC", ZEPIX, 6, 0}, {0xEE, "INC", ABSOL, 6, 0}, - {0xFE, "INC", ABSIX, 6, 0}, + {0xFE, "INC", ABSIX, 7, 0}, {0xE8, "INX", IMPLI, 2, 0}, /* INX */ @@ -178,32 +181,32 @@ opcode_t opcode_table[NUMBER_OPCODES] = { {0x20, "JSR", ABSOL, 6, 0}, /* JSR */ - {0xA9, "LDA", IMMED, 2, 1}, /* LDA */ - {0xA5, "LDA", ZEROP, 3, 1}, - {0xB5, "LDA", ZEPIX, 4, 1}, - {0xAD, "LDA", ABSOL, 4, 1}, - {0xBD, "LDA", ABSIX, 4, 1}, - {0xB9, "LDA", ABSIY, 4, 1}, - {0xA1, "LDA", INDIN, 6, 1}, - {0xB1, "LDA", ININD, 5, 1}, + {0xA9, "LDA", IMMED, 2, 0}, /* LDA */ + {0xA5, "LDA", ZEROP, 3, 0}, + {0xB5, "LDA", ZEPIX, 4, 0}, + {0xAD, "LDA", ABSOL, 4, 0}, + {0xBD, "LDA", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0xB9, "LDA", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0xA1, "LDA", INDIN, 6, 0}, + {0xB1, "LDA", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE}, - {0xA2, "LDX", IMMED, 2, 1}, /* LDX */ - {0xA6, "LDX", ZEROP, 3, 1}, - {0xB6, "LDX", ZEPIY, 4, 1}, - {0xAE, "LDX", ABSOL, 4, 1}, - {0xBE, "LDX", ABSIY, 4, 1}, + {0xA2, "LDX", IMMED, 2, 0}, /* LDX */ + {0xA6, "LDX", ZEROP, 3, 0}, + {0xB6, "LDX", ZEPIY, 4, 0}, + {0xAE, "LDX", ABSOL, 4, 0}, + {0xBE, "LDX", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, - {0xA0, "LDY", IMMED, 2, 1}, /* LDY */ - {0xA4, "LDY", ZEROP, 3, 1}, - {0xB4, "LDY", ZEPIX, 4, 1}, - {0xAC, "LDY", ABSOL, 4, 1}, - {0xBC, "LDY", ABSIX, 4, 1}, + {0xA0, "LDY", IMMED, 2, 0}, /* LDY */ + {0xA4, "LDY", ZEROP, 3, 0}, + {0xB4, "LDY", ZEPIX, 4, 0}, + {0xAC, "LDY", ABSOL, 4, 0}, + {0xBC, "LDY", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, {0x4A, "LSR", ACCUM, 2, 0}, /* LSR */ {0x46, "LSR", ZEROP, 5, 0}, {0x56, "LSR", ZEPIX, 6, 0}, {0x4E, "LSR", ABSOL, 6, 0}, - {0x5E, "LSR", ABSIX, 6, 0}, + {0x5E, "LSR", ABSIX, 7, 0}, {0xEA, "NOP", IMPLI, 2, 0}, /* NOP */ @@ -211,10 +214,10 @@ opcode_t opcode_table[NUMBER_OPCODES] = { {0x05, "ORA", ZEROP, 3, 0}, {0x15, "ORA", ZEPIX, 4, 0}, {0x0D, "ORA", ABSOL, 4, 0}, - {0x1D, "ORA", ABSIX, 4, 0}, - {0x19, "ORA", ABSIY, 4, 0}, + {0x1D, "ORA", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0x19, "ORA", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, {0x01, "ORA", INDIN, 6, 0}, - {0x11, "ORA", ININD, 5, 0}, + {0x11, "ORA", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE}, {0x48, "PHA", IMPLI, 3, 0}, /* PHA */ @@ -228,26 +231,26 @@ opcode_t opcode_table[NUMBER_OPCODES] = { {0x26, "ROL", ZEROP, 5, 0}, {0x36, "ROL", ZEPIX, 6, 0}, {0x2E, "ROL", ABSOL, 6, 0}, - {0x3E, "ROL", ABSIX, 6, 0}, + {0x3E, "ROL", ABSIX, 7, 0}, {0x6A, "ROR", ACCUM, 2, 0}, /* ROR */ {0x66, "ROR", ZEROP, 5, 0}, {0x76, "ROR", ZEPIX, 6, 0}, {0x6E, "ROR", ABSOL, 6, 0}, - {0x7E, "ROR", ABSIX, 6, 0}, + {0x7E, "ROR", ABSIX, 7, 0}, {0x40, "RTI", IMPLI, 6, 0}, /* RTI */ {0x60, "RTS", IMPLI, 6, 0}, /* RTS */ - {0xE9, "SBC", IMMED, 2, 1}, /* SBC */ - {0xE5, "SBC", ZEROP, 3, 1}, - {0xF5, "SBC", ZEPIX, 4, 1}, - {0xED, "SBC", ABSOL, 4, 1}, - {0xFD, "SBC", ABSIX, 4, 1}, - {0xF9, "SBC", ABSIY, 4, 1}, - {0xE1, "SBC", INDIN, 6, 1}, - {0xF1, "SBC", ININD, 5, 1}, + {0xE9, "SBC", IMMED, 2, 0}, /* SBC */ + {0xE5, "SBC", ZEROP, 3, 0}, + {0xF5, "SBC", ZEPIX, 4, 0}, + {0xED, "SBC", ABSOL, 4, 0}, + {0xFD, "SBC", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0xF9, "SBC", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0xE1, "SBC", INDIN, 6, 0}, + {0xF1, "SBC", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE}, {0x38, "SEC", IMPLI, 2, 0}, /* SEC */ @@ -258,10 +261,10 @@ opcode_t opcode_table[NUMBER_OPCODES] = { {0x85, "STA", ZEROP, 3, 0}, /* STA */ {0x95, "STA", ZEPIX, 4, 0}, {0x8D, "STA", ABSOL, 4, 0}, - {0x9D, "STA", ABSIX, 4, 0}, - {0x99, "STA", ABSIY, 4, 0}, + {0x9D, "STA", ABSIX, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, + {0x99, "STA", ABSIY, 4, CYCLES_CROSS_PAGE_ADDS_ONE}, {0x81, "STA", INDIN, 6, 0}, - {0x91, "STA", ININD, 5, 0}, + {0x91, "STA", ININD, 5, CYCLES_CROSS_PAGE_ADDS_ONE}, {0x86, "STX", ZEROP, 3, 0}, /* STX */ {0x96, "STX", ZEPIY, 4, 0}, @@ -286,7 +289,7 @@ opcode_t opcode_table[NUMBER_OPCODES] = { /* This function emits a comment header with information about the file being disassembled */ -void emit_header(options_t *options, int fsize) { +static void emit_header(options_t *options, int fsize) { fprintf(stdout, "; Source generated by DCC6502 version %s\n", VERSION_INFO); fprintf(stdout, "; For more info about DCC6502, see https://github.com/tcarmelveilleux/dcc6502\n"); fprintf(stdout, "; FILENAME: %s, File Size: %d, ORG: $%04X\n", options->filename, fsize, options->org); @@ -296,15 +299,36 @@ void emit_header(options_t *options, int fsize) { fprintf(stdout, ";---------------------------------------------------------------------------\n"); } -/* This function appends cycle counting to the comment block */ -char *append_cycle(char *input, uint8_t entry) { +/* This function appends cycle counting to the comment block. See following + * for methods used: + * "Nick Bensema's Guide to Cycle Counting on the Atari 2600" + * http://www.alienbill.com/2600/cookbook/cycles/nickb.txt + */ +static char *append_cycle(char *input, uint8_t entry, uint16_t pc, uint16_t new_pc) { char tmpstr[256]; - int cycles = opcode_table[entry].cycles; + int cycles = g_opcode_table[entry].cycles; + int exceptions = g_opcode_table[entry].cycles_exceptions; + int crosses_page = ((pc & 0xff00u) != (new_pc & 0xff00u)) ? 1 : 0; - // On page boundary crossing, instruction will take an extra cycle - if (opcode_table[entry].cross_page) { - sprintf(tmpstr, " Cycles: %d/%d", cycles, cycles + 1); + // On some exceptional conditions, instruction will take an extra cycle, or even two + if (exceptions != 0) { + if ((exceptions & CYCLES_BRANCH_TAKEN_ADDS_ONE) && (exceptions & CYCLES_CROSS_PAGE_ADDS_ONE)) { + /* Branch case: check for page crossing, since it can be determined + * statically from the relative offset and current PC. + */ + if (crosses_page) { + /* Crosses page, always at least 1 extra cycle, two times */ + sprintf(tmpstr, " Cycles: %d/%d", cycles + 1, cycles + 2); + } else { + /* Does not cross page, maybe one extra cycle if branch taken */ + sprintf(tmpstr, " Cycles: %d/%d", cycles, cycles + 1); + } + } else { + /* One exception: two times, can't tell in advance whether page crossing occurs */ + sprintf(tmpstr, " Cycles: %d/%d", cycles, cycles + 1); + } } else { + /* No exceptions, no extra time */ sprintf(tmpstr, " Cycles: %d", cycles); } @@ -312,13 +336,13 @@ char *append_cycle(char *input, uint8_t entry) { return (input + strlen(input)); } -void add_nes_str(char *instr, char *instr2) { +static void add_nes_str(char *instr, char *instr2) { strcat(instr, " [NES] "); strcat(instr, instr2); } /* This function put NES-specific info in the comment block */ -void append_nes(char *input, uint16_t arg) { +static void append_nes(char *input, uint16_t arg) { switch(arg) { case 0x2000: add_nes_str(input, "PPU setup #1"); break; case 0x2001: add_nes_str(input, "PPU setup #2"); break; @@ -354,13 +378,14 @@ void append_nes(char *input, uint16_t arg) { } } +/* Helper macros for disassemble() function */ #define DUMP_FORMAT (options->hex_output ? "%-16s%-16s;" : "%-8s%-16s;") #define HIGH_PART(val) (((val) >> 8) & 0xFFu) #define LOW_PART(val) ((val) & 0xFFu) #define LOAD_WORD(buffer, current_pc) ((uint16_t)buffer[(current_pc) + 1] | (((uint16_t)buffer[(current_pc) + 2]) << 8)) /* This function disassembles the opcode at the PC and outputs it in *output */ -void disassemble(char *output, uint8_t *buffer, options_t *options, uint16_t *pc) { +static void disassemble(char *output, uint8_t *buffer, options_t *options, uint16_t *pc) { char opcode_repr[256], hex_dump[256]; int opcode_idx; int len = 0; @@ -377,17 +402,16 @@ void disassemble(char *output, uint8_t *buffer, options_t *options, uint16_t *pc // Linear search for opcode for (opcode_idx = 0; opcode_idx < NUMBER_OPCODES; opcode_idx++) { - if (opcode == opcode_table[opcode_idx].number) { + if (opcode == g_opcode_table[opcode_idx].number) { /* Found the opcode, record its table index */ found = 1; entry = opcode_idx; } } - // TODO: Normalize %02x versus %02X // For opcode not found, terminate early if (!found) { - sprintf(opcode_repr, ".byte $%02x", opcode); + sprintf(opcode_repr, ".byte $%02X", opcode); if (options->hex_output) { sprintf(hex_dump, "$%04X> %02X:", current_addr, opcode); sprintf(output, "%-16s%-16s; INVALID OPCODE !!!\n", hex_dump, opcode_repr); @@ -399,19 +423,19 @@ void disassemble(char *output, uint8_t *buffer, options_t *options, uint16_t *pc } // Opcode found in table: disassemble properly according to addressing mode - mnemonic = opcode_table[entry].mnemonic; + mnemonic = g_opcode_table[entry].mnemonic; // Set hex dump to default single address format. Will be overwritten // by more complex output in case of hex dump mode enabled sprintf(hex_dump, "$%04X", current_addr); - switch (opcode_table[entry].addressing) { + switch (g_opcode_table[entry].addressing) { case IMMED: /* Get immediate value operand */ byte_operand = buffer[*pc + 1]; *pc += 1; - sprintf(opcode_repr, "%s #$%02x", mnemonic, byte_operand); + sprintf(opcode_repr, "%s #$%02X", mnemonic, byte_operand); if (options->hex_output) { sprintf(hex_dump, "$%04X> %02X %02X:", current_addr, opcode, byte_operand); } @@ -554,16 +578,17 @@ void disassemble(char *output, uint8_t *buffer, options_t *options, uint16_t *pc break; } + // Emit disassembly line content, prior to annotation comments len = sprintf(output, DUMP_FORMAT, hex_dump, opcode_repr); output += len; /* Add cycle count if necessary */ if (options->cycle_counting) { - output = append_cycle(output, entry); + output = append_cycle(output, entry, *pc + 1, word_operand); } /* Add NES port info if necessary */ - switch (opcode_table[entry].addressing) { + switch (g_opcode_table[entry].addressing) { case ABSOL: case ABSIX: case ABSIY: @@ -577,13 +602,13 @@ void disassemble(char *output, uint8_t *buffer, options_t *options, uint16_t *pc } } -void version(void) { +static void version(void) { fprintf(stderr, "DCC6502 %s (C)1998-2014 Tennessee Carmel-Veilleux \n", VERSION_INFO); fprintf(stderr, "This software is licensed under the MIT license. See the LICENSE file.\n"); fprintf(stderr, "See source on github: https://github.com/tcarmelveilleux/dcc6502.\n"); } -void usage(void) { +static void usage(void) { fprintf(stderr, "Usage: dcc6502 [options] FILENAME"); fprintf(stderr, " -?/-h : Show this help message\n"); fprintf(stderr, " -o ORIGIN : Set the origin (base address of disassembly) [default: 0x8000]\n"); @@ -595,7 +620,7 @@ void usage(void) { fprintf(stderr, "\n"); } -unsigned long str_arg_to_ulong(char *str, unsigned long default_val) { +static unsigned long str_arg_to_ulong(char *str, unsigned long default_val) { uint32_t tmp = 0; errno = EOK; @@ -609,7 +634,7 @@ unsigned long str_arg_to_ulong(char *str, unsigned long default_val) { } } -void usage_and_exit(int exit_code, const char *message) { +static void usage_and_exit(int exit_code, const char *message) { version(); usage(); if (NULL != message) { @@ -618,7 +643,7 @@ void usage_and_exit(int exit_code, const char *message) { exit(exit_code); } -void parse_args(int argc, char *argv[], options_t *options) { +static void parse_args(int argc, char *argv[], options_t *options) { int arg_idx = 1; options->cycle_counting = 0;