Fixed cycle counting and finished 2.0

* Added rules for branch taken/not taken
* Fixed incorrect adding of cycles for many ALU instructions
* Fixed timing for ASL, DEC, INC, LSR, ROL, and ROR
* Renamed global opcode_table to g_opcode_table
* Normalized all hex output to be capitals
This commit is contained in:
Tennessee Carmel-Veilleux 2014-07-24 23:00:31 -04:00
parent d8869a8d28
commit aa6c897c1a
1 changed files with 123 additions and 98 deletions

221
dcc6502.c
View File

@ -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 <veilleux@tentech.ca>\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;