vin
6a5b0b728e
So this is why tests should be written while writing the program and instructions instead of all at once later. If this were all to be rewritten (which it probably will), I should add tests for each opcode instead of waiting until the end for ROM loading support.
1489 lines
23 KiB
C
1489 lines
23 KiB
C
#include <libgen.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "rom.h"
|
|
|
|
#define MAX(a, b) ((a > b) ? a : b)
|
|
|
|
#define STATUS_UPDATE_ZERO(r) \
|
|
(regs.status.zero = r == 0)
|
|
#define STATUS_UPDATE_NEGATIVE(r) \
|
|
(regs.status.negative = ((r & (1 << 7)) != 0))
|
|
|
|
#define MEMORY_MIRROR(addr) \
|
|
if (addr < 0x2000) \
|
|
addr &= 0x07FF; \
|
|
else if (addr < 0x4000) \
|
|
addr &= 0x2007;
|
|
|
|
#define PUSH(b) \
|
|
(memwrite(0x0100 + regs.sp--, b))
|
|
#define PULL() \
|
|
(peek(0x0100 + regs.sp++))
|
|
|
|
struct cpu_flags {
|
|
uint8_t carry : 1;
|
|
uint8_t zero : 1;
|
|
uint8_t interrupt_disable : 1;
|
|
uint8_t decimal_mode : 1;
|
|
uint8_t brk : 1;
|
|
uint8_t unused : 1;
|
|
uint8_t overflow : 1;
|
|
uint8_t negative : 1;
|
|
};
|
|
|
|
struct registers {
|
|
uint8_t a, x, y, sp;
|
|
struct cpu_flags status;
|
|
uint16_t pc;
|
|
};
|
|
struct registers regs = {0};
|
|
|
|
struct Rom rom = {0};
|
|
|
|
enum addressing_mode {
|
|
AM_IMM,
|
|
AM_ZP,
|
|
AM_ZP_X,
|
|
AM_ZP_Y,
|
|
AM_REL,
|
|
AM_ABS,
|
|
AM_ABS_X,
|
|
AM_ABS_Y,
|
|
AM_IND,
|
|
AM_IND_X,
|
|
AM_IND_Y,
|
|
};
|
|
|
|
/* 64K address space, 16bit words */
|
|
uint8_t memory[0x16000];
|
|
|
|
uint32_t cycles = 0;
|
|
|
|
static uint8_t
|
|
peek(uint16_t addr)
|
|
{
|
|
MEMORY_MIRROR(addr);
|
|
|
|
if (addr > 0x7FFF) {
|
|
if (rom.prg_rom_size == 0x4000)
|
|
return rom.prg_rom[(addr - 0x8000) % 0x4000];
|
|
else if (rom.prg_rom_size == 0x8000)
|
|
return rom.prg_rom[addr - 0x8000];
|
|
} else {
|
|
return memory[addr];
|
|
}
|
|
}
|
|
|
|
static uint16_t
|
|
peek16(uint16_t addr)
|
|
{
|
|
/* bytes are stored in little-endian (low then high) */
|
|
return peek(addr) | (peek(addr + 1) << 8);
|
|
}
|
|
|
|
static void
|
|
memwrite(uint16_t addr, uint8_t byte)
|
|
{
|
|
MEMORY_MIRROR(addr);
|
|
|
|
memory[addr] = byte;
|
|
}
|
|
|
|
static void
|
|
memwrite16(uint16_t addr, uint16_t word)
|
|
{
|
|
MEMORY_MIRROR(addr);
|
|
|
|
/* bytes are stored in little-endian (low then high) */
|
|
memory[addr] = word & 0xFF;
|
|
memory[addr + 1] = (word & 0xFF00) >> 8;
|
|
}
|
|
|
|
static uint8_t
|
|
opcode_arg(enum addressing_mode mode)
|
|
{
|
|
uint16_t arg, val;
|
|
|
|
if (mode != AM_ABS && mode != AM_ABS_X && mode != AM_ABS_Y)
|
|
arg = peek(regs.pc++);
|
|
else
|
|
arg = peek16(regs.pc), regs.pc += 2;
|
|
|
|
switch (mode) {
|
|
case AM_IMM:
|
|
case AM_REL:
|
|
val = arg;
|
|
break;
|
|
case AM_ZP:
|
|
val = peek(arg % 256);
|
|
break;
|
|
case AM_ZP_X:
|
|
val = peek((arg + regs.x) % 256);
|
|
break;
|
|
case AM_ZP_Y:
|
|
val = peek((arg + regs.y) % 256);
|
|
break;
|
|
case AM_ABS:
|
|
val = peek16(arg);
|
|
break;
|
|
case AM_ABS_X:
|
|
val = peek16(arg + regs.x);
|
|
break;
|
|
case AM_ABS_Y:
|
|
val = peek16(arg + regs.y);
|
|
break;
|
|
case AM_IND_X:
|
|
val = peek(peek((arg + regs.x) % 256) + peek((arg + regs.x + 1) % 256) * 256);
|
|
break;
|
|
case AM_IND_Y:
|
|
val = peek(peek(arg) + peek((arg + 1) % 256) * 256 + regs.y);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "INVALID ADDRESSING MODE\n");
|
|
abort();
|
|
}
|
|
|
|
printf("arg: $%04X $%04X\n", arg, val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static uint16_t
|
|
opcode_mem(enum addressing_mode mode)
|
|
{
|
|
uint16_t arg, val;
|
|
|
|
if (mode != AM_ABS && mode != AM_ABS_X && mode != AM_ABS_Y)
|
|
arg = peek(regs.pc++);
|
|
else
|
|
arg = peek16(regs.pc), regs.pc += 2;
|
|
|
|
switch (mode) {
|
|
case AM_ZP:
|
|
val = arg % 256;
|
|
break;
|
|
case AM_ZP_X:
|
|
val = (arg + regs.x) % 256;
|
|
break;
|
|
case AM_ZP_Y:
|
|
val = (arg + regs.y) % 256;
|
|
break;
|
|
case AM_IMM:
|
|
case AM_ABS:
|
|
val = arg;
|
|
break;
|
|
case AM_ABS_X:
|
|
val = arg + regs.x;
|
|
break;
|
|
case AM_ABS_Y:
|
|
val = arg + regs.y;
|
|
break;
|
|
case AM_IND_X:
|
|
val = peek((arg + regs.x) % 256) + peek((arg + regs.x + 1) % 256) * 256;
|
|
break;
|
|
case AM_IND_Y:
|
|
val = peek(arg) + peek((arg + 1) % 256) * 256 + regs.y;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "INVALID ADDRESSING MODE\n");
|
|
abort();
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
adc(uint8_t arg)
|
|
{
|
|
uint16_t sum; // 16-bit sum makes it easier to determine carry flag
|
|
|
|
sum = regs.a + arg + regs.status.carry;
|
|
regs.a = sum & 0xFF;
|
|
|
|
regs.status.carry = sum > 0xFF;
|
|
/* overflow flag formula: https://stackoverflow.com/a/29224684 */
|
|
regs.status.overflow = (~(regs.a ^ arg) & (regs.a ^ sum) & 0x80) != 0;
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
and(uint8_t arg)
|
|
{
|
|
regs.a &= arg;
|
|
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
asl_acc(void)
|
|
{
|
|
uint16_t tmp;
|
|
|
|
tmp = regs.a << 1;
|
|
regs.a = tmp & 0xFF;
|
|
|
|
regs.status.carry = tmp > 0xFF;
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
asl(uint16_t mem)
|
|
{
|
|
uint16_t tmp;
|
|
|
|
tmp = peek(mem) << 1;
|
|
memwrite(mem, tmp & 0xFF);
|
|
|
|
regs.status.carry = tmp > 0xFF;
|
|
STATUS_UPDATE_ZERO(tmp);
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
static void
|
|
bcc(uint8_t arg)
|
|
{
|
|
if (regs.status.carry == 0)
|
|
regs.pc += arg;
|
|
}
|
|
|
|
static void
|
|
bcs(uint8_t arg)
|
|
{
|
|
if (regs.status.carry == 1)
|
|
regs.pc += arg;
|
|
}
|
|
|
|
static void
|
|
beq(uint8_t arg)
|
|
{
|
|
if (regs.status.zero == 1)
|
|
regs.pc += arg;
|
|
}
|
|
|
|
static void
|
|
bit(uint8_t arg)
|
|
{
|
|
regs.status.zero = (regs.a & arg) == 0;
|
|
regs.status.overflow = (arg & (1 << 6)) != 0;
|
|
STATUS_UPDATE_NEGATIVE(arg);
|
|
}
|
|
|
|
static void
|
|
bmi(uint8_t arg)
|
|
{
|
|
if (regs.status.negative == 1)
|
|
regs.pc += arg;
|
|
}
|
|
|
|
static void
|
|
bne(uint8_t arg)
|
|
{
|
|
if (regs.status.zero == 0)
|
|
regs.pc += arg;
|
|
}
|
|
|
|
static void
|
|
bpl(uint8_t arg)
|
|
{
|
|
if (regs.status.negative == 0)
|
|
regs.pc += arg;
|
|
}
|
|
|
|
static void
|
|
brk(void)
|
|
{
|
|
/* TODO: push regs.pc and regs.status to stack and load IRQ vector */
|
|
regs.status.brk = 1;
|
|
exit(0);
|
|
}
|
|
|
|
static void
|
|
bvc(uint8_t arg)
|
|
{
|
|
if (regs.status.overflow == 0)
|
|
regs.pc += arg;
|
|
}
|
|
|
|
static void
|
|
bvs(uint8_t arg)
|
|
{
|
|
if (regs.status.overflow == 1)
|
|
regs.pc += arg;
|
|
}
|
|
|
|
static void
|
|
clc(void)
|
|
{
|
|
regs.status.carry = 0;
|
|
}
|
|
|
|
static void
|
|
cld(void)
|
|
{
|
|
regs.status.decimal_mode = 0;
|
|
}
|
|
|
|
static void
|
|
cli(void)
|
|
{
|
|
regs.status.interrupt_disable = 0;
|
|
}
|
|
|
|
static void
|
|
clv(void)
|
|
{
|
|
regs.status.overflow = 0;
|
|
}
|
|
|
|
static void
|
|
cmp(uint8_t arg)
|
|
{
|
|
uint8_t tmp;
|
|
|
|
tmp = regs.a - arg;
|
|
|
|
regs.status.carry = regs.a >= arg;
|
|
regs.status.zero = regs.a == arg;
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
static void
|
|
cpx(uint8_t arg)
|
|
{
|
|
uint8_t tmp;
|
|
|
|
tmp = regs.x - arg;
|
|
|
|
regs.status.carry = regs.x >= arg;
|
|
regs.status.zero = regs.x == arg;
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
static void
|
|
cpy(uint8_t arg)
|
|
{
|
|
uint8_t tmp;
|
|
|
|
tmp = regs.y - arg;
|
|
|
|
regs.status.carry = regs.y >= arg;
|
|
regs.status.zero = regs.y == arg;
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
static void
|
|
dec(uint16_t mem)
|
|
{
|
|
memwrite(mem, peek(mem) - 1);
|
|
|
|
STATUS_UPDATE_ZERO(peek(mem));
|
|
STATUS_UPDATE_NEGATIVE(peek(mem));
|
|
}
|
|
|
|
static void
|
|
dex(void)
|
|
{
|
|
regs.x--;
|
|
|
|
STATUS_UPDATE_ZERO(regs.x);
|
|
STATUS_UPDATE_NEGATIVE(regs.x);
|
|
}
|
|
|
|
static void
|
|
dey(void)
|
|
{
|
|
regs.y--;
|
|
|
|
STATUS_UPDATE_ZERO(regs.y);
|
|
STATUS_UPDATE_NEGATIVE(regs.y);
|
|
}
|
|
|
|
static void
|
|
eor(uint8_t arg)
|
|
{
|
|
regs.a ^= arg;
|
|
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
inc(uint16_t mem)
|
|
{
|
|
memwrite(mem, peek(mem) + 1);
|
|
|
|
STATUS_UPDATE_ZERO(peek(mem));
|
|
STATUS_UPDATE_NEGATIVE(peek(mem));
|
|
}
|
|
|
|
static void
|
|
inx(void)
|
|
{
|
|
regs.x++;
|
|
|
|
STATUS_UPDATE_ZERO(regs.x);
|
|
STATUS_UPDATE_NEGATIVE(regs.x);
|
|
}
|
|
|
|
static void
|
|
iny(void)
|
|
{
|
|
regs.y++;
|
|
|
|
STATUS_UPDATE_ZERO(regs.y);
|
|
STATUS_UPDATE_NEGATIVE(regs.y);
|
|
}
|
|
|
|
static void
|
|
jmp(uint16_t arg)
|
|
{
|
|
regs.pc = arg;
|
|
}
|
|
|
|
static void
|
|
jsr(uint16_t arg)
|
|
{
|
|
uint16_t tmp = regs.pc + 2;
|
|
|
|
/*
|
|
* first push high-byte of return address then low-byte
|
|
* https://www.masswerk.at/6502/6502_instruction_set.html
|
|
*/
|
|
PUSH((tmp & 0xFF00) >> 8);
|
|
PUSH(tmp & 0xFF);
|
|
regs.pc = arg;
|
|
}
|
|
|
|
static void
|
|
lda(uint8_t arg)
|
|
{
|
|
regs.a = arg;
|
|
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
ldx(uint8_t arg)
|
|
{
|
|
regs.x = arg;
|
|
|
|
STATUS_UPDATE_ZERO(regs.x);
|
|
STATUS_UPDATE_NEGATIVE(regs.x);
|
|
}
|
|
|
|
static void
|
|
ldy(uint8_t arg)
|
|
{
|
|
regs.y = arg;
|
|
|
|
STATUS_UPDATE_ZERO(regs.y);
|
|
STATUS_UPDATE_NEGATIVE(regs.y);
|
|
}
|
|
|
|
static void
|
|
lsr_acc(void)
|
|
{
|
|
regs.status.carry = regs.a & 1; // bit 0 in carry
|
|
regs.a >>= 1;
|
|
regs.a &= ~(1 << 7); // bit 7 cleared
|
|
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
lsr(uint16_t mem)
|
|
{
|
|
uint8_t tmp;
|
|
|
|
tmp = peek(mem);
|
|
|
|
regs.status.carry = tmp & 1; // bit 0 in carry
|
|
tmp >>= 1;
|
|
tmp &= ~(1 << 7); // bit 7 cleared
|
|
|
|
memwrite(mem, tmp);
|
|
|
|
STATUS_UPDATE_ZERO(tmp);
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
static void
|
|
nop(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
ora(uint8_t arg)
|
|
{
|
|
regs.a |= arg;
|
|
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
pha(void)
|
|
{
|
|
PUSH(regs.a);
|
|
}
|
|
|
|
static void
|
|
php(void)
|
|
{
|
|
uint8_t status;
|
|
|
|
status = (regs.status.carry << 7) | (regs.status.zero << 6)
|
|
| (regs.status.interrupt_disable << 5) | (regs.status.decimal_mode << 4)
|
|
| (regs.status.brk << 3) | (regs.status.unused << 2)
|
|
| (regs.status.overflow << 1) | regs.status.negative;
|
|
|
|
PUSH(status);
|
|
}
|
|
|
|
static void
|
|
pla(void)
|
|
{
|
|
regs.a = PULL();
|
|
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
plp(void)
|
|
{
|
|
uint8_t status;
|
|
|
|
status = PULL();
|
|
|
|
regs.status.carry = (status & (1 << 7)) != 0;
|
|
regs.status.zero = (status & (1 << 6)) != 0;
|
|
regs.status.interrupt_disable = (status & (1 << 5)) != 0;
|
|
regs.status.decimal_mode = (status & (1 << 4)) != 0;
|
|
regs.status.brk = (status & (1 << 3)) != 0;
|
|
regs.status.unused = (status & (1 << 2)) != 0;
|
|
regs.status.overflow = (status & (1 << 1)) != 0;
|
|
regs.status.negative = (status & 1) != 0;
|
|
}
|
|
|
|
static void
|
|
rol_acc(void)
|
|
{
|
|
uint8_t carry;
|
|
carry = (regs.a & (1 << 7)) != 0;
|
|
|
|
regs.a <<= 1;
|
|
regs.a |= regs.status.carry;
|
|
|
|
regs.status.carry = carry;
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
rol(uint16_t mem)
|
|
{
|
|
uint8_t carry, tmp;
|
|
carry = (peek(mem) & (1 << 7)) != 0;
|
|
|
|
tmp = (peek(mem) << 1) | regs.status.carry;
|
|
memwrite(mem, tmp);
|
|
|
|
regs.status.carry = carry;
|
|
STATUS_UPDATE_ZERO(tmp);
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
static void
|
|
ror_acc(void)
|
|
{
|
|
uint8_t carry;
|
|
carry = regs.a & 1;
|
|
|
|
regs.a >>= 1;
|
|
regs.a |= regs.status.carry << 7;
|
|
|
|
regs.status.carry = carry;
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
ror(uint16_t mem)
|
|
{
|
|
uint8_t carry, tmp;
|
|
carry = peek(mem) & 1;
|
|
|
|
tmp = (peek(mem) >> 1) | (regs.status.carry << 7);
|
|
memwrite(mem, tmp);
|
|
|
|
regs.status.carry = carry;
|
|
STATUS_UPDATE_ZERO(tmp);
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
static void
|
|
rti(void)
|
|
{
|
|
plp();
|
|
regs.pc = PULL();
|
|
}
|
|
|
|
static void
|
|
rts(void)
|
|
{
|
|
regs.pc = PULL() + 1;
|
|
}
|
|
|
|
static void
|
|
sbc(uint8_t arg)
|
|
{
|
|
/* SBC is described online as ADC with argument as two's complement */
|
|
adc(~arg + 1);
|
|
}
|
|
|
|
static void
|
|
sec(void)
|
|
{
|
|
regs.status.carry = 1;
|
|
}
|
|
|
|
static void
|
|
sed(void)
|
|
{
|
|
regs.status.decimal_mode = 1;
|
|
}
|
|
|
|
static void
|
|
sei(void)
|
|
{
|
|
regs.status.interrupt_disable = 1;
|
|
}
|
|
|
|
static void
|
|
sta(uint16_t mem)
|
|
{
|
|
memwrite(mem, regs.a);
|
|
}
|
|
|
|
static void
|
|
stx(uint16_t mem)
|
|
{
|
|
memwrite(mem, regs.x);
|
|
}
|
|
|
|
static void
|
|
sty(uint16_t mem)
|
|
{
|
|
memwrite(mem, regs.y);
|
|
}
|
|
|
|
static void
|
|
tax(void)
|
|
{
|
|
regs.x = regs.a;
|
|
|
|
STATUS_UPDATE_ZERO(regs.x);
|
|
STATUS_UPDATE_NEGATIVE(regs.x);
|
|
}
|
|
|
|
static void
|
|
tay(void)
|
|
{
|
|
regs.y = regs.a;
|
|
|
|
STATUS_UPDATE_ZERO(regs.y);
|
|
STATUS_UPDATE_NEGATIVE(regs.y);
|
|
}
|
|
|
|
static void
|
|
tsx(void)
|
|
{
|
|
regs.x = PULL();
|
|
|
|
STATUS_UPDATE_ZERO(regs.x);
|
|
STATUS_UPDATE_NEGATIVE(regs.x);
|
|
}
|
|
|
|
static void
|
|
txa(void)
|
|
{
|
|
regs.a = regs.x;
|
|
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
txs(void)
|
|
{
|
|
PUSH(regs.x);
|
|
}
|
|
|
|
static void
|
|
tya(void)
|
|
{
|
|
regs.a = regs.y;
|
|
|
|
STATUS_UPDATE_ZERO(regs.a);
|
|
STATUS_UPDATE_NEGATIVE(regs.a);
|
|
}
|
|
|
|
static void
|
|
interpret(void)
|
|
{
|
|
uint8_t opcode;
|
|
|
|
for (;;) {
|
|
opcode = peek(regs.pc++);
|
|
|
|
printf("opcode: $%02X @ $%04X\n", opcode, regs.pc - 1);
|
|
|
|
switch (opcode) {
|
|
case 0x69:
|
|
adc(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0x65:
|
|
adc(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0x75:
|
|
adc(opcode_arg(AM_ZP_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x6d:
|
|
adc(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0x7d:
|
|
adc(opcode_arg(AM_ABS_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x79:
|
|
adc(opcode_arg(AM_ABS_Y));
|
|
cycles += 4;
|
|
break;
|
|
case 0x72:
|
|
adc(opcode_arg(AM_IND));
|
|
cycles += 5;
|
|
break;
|
|
case 0x61:
|
|
adc(opcode_arg(AM_IND_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x71:
|
|
adc(opcode_arg(AM_IND_Y));
|
|
cycles += 5;
|
|
break;
|
|
case 0x29:
|
|
and(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0x25:
|
|
and(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0x35:
|
|
and(opcode_arg(AM_ZP_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x2d:
|
|
and(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0x3d:
|
|
and(opcode_arg(AM_ABS_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x39:
|
|
and(opcode_arg(AM_ABS_Y));
|
|
cycles += 4;
|
|
break;
|
|
case 0x32:
|
|
and(opcode_arg(AM_IND));
|
|
cycles += 5;
|
|
break;
|
|
case 0x21:
|
|
and(opcode_arg(AM_IND_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x31:
|
|
and(opcode_arg(AM_IND_Y));
|
|
cycles += 5;
|
|
break;
|
|
case 0x0a:
|
|
asl_acc();
|
|
cycles += 2;
|
|
break;
|
|
case 0x06:
|
|
asl(opcode_arg(AM_ZP));
|
|
cycles += 5;
|
|
break;
|
|
case 0x16:
|
|
asl(opcode_arg(AM_ZP_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x0e:
|
|
asl(opcode_arg(AM_ABS));
|
|
cycles += 6;
|
|
break;
|
|
case 0x1e:
|
|
asl(opcode_arg(AM_ABS_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x90:
|
|
bcc(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0xb0:
|
|
bcs(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0xf0:
|
|
beq(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0x89:
|
|
bit(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0x24:
|
|
bit(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0x34:
|
|
bit(opcode_arg(AM_ZP_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x2c:
|
|
bit(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0x3c:
|
|
bit(opcode_arg(AM_ABS_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x30:
|
|
bmi(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0xd0:
|
|
bne(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0x10:
|
|
bpl(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0x00:
|
|
brk();
|
|
cycles += 7;
|
|
break;
|
|
case 0x50:
|
|
bvc(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0x70:
|
|
bvs(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0x18:
|
|
clc();
|
|
cycles += 2;
|
|
break;
|
|
case 0xd8:
|
|
cld();
|
|
cycles += 2;
|
|
break;
|
|
case 0x58:
|
|
cli();
|
|
cycles += 2;
|
|
break;
|
|
case 0xb8:
|
|
clv();
|
|
cycles += 2;
|
|
break;
|
|
case 0xc9:
|
|
cmp(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0xc5:
|
|
cmp(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0xd5:
|
|
cmp(opcode_arg(AM_ZP_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0xcd:
|
|
cmp(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0xdd:
|
|
cmp(opcode_arg(AM_ABS_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0xd9:
|
|
cmp(opcode_arg(AM_ABS_Y));
|
|
cycles += 4;
|
|
break;
|
|
case 0xd2:
|
|
cmp(opcode_arg(AM_IND));
|
|
cycles += 5;
|
|
break;
|
|
case 0xc1:
|
|
cmp(opcode_arg(AM_IND_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0xd1:
|
|
cmp(opcode_arg(AM_IND_Y));
|
|
cycles += 5;
|
|
break;
|
|
case 0xe0:
|
|
cpx(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0xe4:
|
|
cpx(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0xec:
|
|
cpx(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0xc0:
|
|
cpy(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0xc4:
|
|
cpy(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0xcc:
|
|
cpy(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0xc6:
|
|
dec(opcode_mem(AM_ZP));
|
|
cycles += 5;
|
|
break;
|
|
case 0xd6:
|
|
dec(opcode_mem(AM_ZP_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0xce:
|
|
dec(opcode_mem(AM_ABS));
|
|
cycles += 6;
|
|
break;
|
|
case 0xde:
|
|
dec(opcode_mem(AM_ABS_X));
|
|
cycles += 7;
|
|
break;
|
|
case 0xca:
|
|
dex();
|
|
cycles += 2;
|
|
break;
|
|
case 0x88:
|
|
dey();
|
|
cycles += 2;
|
|
break;
|
|
case 0x49:
|
|
eor(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0x45:
|
|
eor(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0x55:
|
|
eor(opcode_arg(AM_ZP_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x4d:
|
|
eor(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0x5d:
|
|
eor(opcode_arg(AM_ABS_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x59:
|
|
eor(opcode_arg(AM_ABS_Y));
|
|
cycles += 4;
|
|
break;
|
|
case 0x52:
|
|
eor(opcode_arg(AM_IND));
|
|
cycles += 5;
|
|
break;
|
|
case 0x41:
|
|
eor(opcode_arg(AM_IND_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x51:
|
|
eor(opcode_arg(AM_IND_Y));
|
|
cycles += 5;
|
|
break;
|
|
case 0xe6:
|
|
inc(opcode_arg(AM_ZP));
|
|
cycles += 5;
|
|
break;
|
|
case 0xf6:
|
|
inc(opcode_arg(AM_ZP_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0xee:
|
|
inc(opcode_arg(AM_ABS));
|
|
cycles += 6;
|
|
break;
|
|
case 0xfe:
|
|
inc(opcode_arg(AM_ABS_X));
|
|
cycles += 7;
|
|
break;
|
|
case 0xe8:
|
|
inx();
|
|
cycles += 2;
|
|
break;
|
|
case 0xc8:
|
|
iny();
|
|
cycles += 2;
|
|
break;
|
|
case 0x4c:
|
|
jmp(opcode_mem(AM_ABS));
|
|
cycles += 3;
|
|
break;
|
|
case 0x6c:
|
|
jmp(opcode_arg(AM_IND));
|
|
cycles += 6;
|
|
break;
|
|
case 0x7c:
|
|
jmp(opcode_arg(AM_ABS_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x20:
|
|
jsr(opcode_arg(AM_ABS));
|
|
cycles += 6;
|
|
break;
|
|
case 0xa9:
|
|
lda(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0xa5:
|
|
lda(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0xb5:
|
|
lda(opcode_arg(AM_ZP_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0xad:
|
|
lda(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0xbd:
|
|
lda(opcode_arg(AM_ABS_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0xb9:
|
|
lda(opcode_arg(AM_ABS_Y));
|
|
cycles += 4;
|
|
break;
|
|
case 0xb2:
|
|
lda(opcode_arg(AM_IND));
|
|
cycles += 5;
|
|
break;
|
|
case 0xa1:
|
|
lda(opcode_arg(AM_IND_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0xb1:
|
|
lda(opcode_arg(AM_IND_Y));
|
|
cycles += 5;
|
|
break;
|
|
case 0xa2:
|
|
ldx(opcode_mem(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0xa6:
|
|
ldx(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0xb6:
|
|
ldx(opcode_arg(AM_ZP_Y));
|
|
cycles += 4;
|
|
break;
|
|
case 0xae:
|
|
ldx(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0xbe:
|
|
ldx(opcode_arg(AM_ABS_Y));
|
|
cycles += 4;
|
|
break;
|
|
case 0xa0:
|
|
ldy(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0xa4:
|
|
ldy(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0xb4:
|
|
ldy(opcode_arg(AM_ZP_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0xac:
|
|
ldy(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0xbc:
|
|
ldy(opcode_arg(AM_ABS_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x4a:
|
|
lsr_acc();
|
|
cycles += 2;
|
|
break;
|
|
case 0x46:
|
|
lsr(opcode_arg(AM_ZP));
|
|
cycles += 5;
|
|
break;
|
|
case 0x56:
|
|
lsr(opcode_arg(AM_ZP_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x4e:
|
|
lsr(opcode_arg(AM_ABS));
|
|
cycles += 6;
|
|
break;
|
|
case 0x5e:
|
|
lsr(opcode_arg(AM_ABS_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0xea:
|
|
nop();
|
|
cycles += 2;
|
|
break;
|
|
case 0x09:
|
|
ora(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0x05:
|
|
ora(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0x15:
|
|
ora(opcode_arg(AM_ZP_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x0d:
|
|
ora(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0x1d:
|
|
ora(opcode_arg(AM_ABS_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0x19:
|
|
ora(opcode_arg(AM_ABS_Y));
|
|
cycles += 4;
|
|
break;
|
|
case 0x12:
|
|
ora(opcode_arg(AM_IND));
|
|
cycles += 5;
|
|
break;
|
|
case 0x01:
|
|
ora(opcode_arg(AM_IND_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x11:
|
|
ora(opcode_arg(AM_IND_Y));
|
|
cycles += 5;
|
|
break;
|
|
case 0x48:
|
|
pha();
|
|
cycles += 3;
|
|
break;
|
|
case 0x08:
|
|
php();
|
|
cycles += 3;
|
|
break;
|
|
case 0x68:
|
|
pla();
|
|
cycles += 4;
|
|
break;
|
|
case 0x28:
|
|
plp();
|
|
cycles += 4;
|
|
break;
|
|
case 0x2a:
|
|
rol_acc();
|
|
cycles += 2;
|
|
break;
|
|
case 0x26:
|
|
rol(opcode_arg(AM_ZP));
|
|
cycles += 5;
|
|
break;
|
|
case 0x36:
|
|
rol(opcode_arg(AM_ZP_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x2e:
|
|
rol(opcode_arg(AM_ABS));
|
|
cycles += 6;
|
|
break;
|
|
case 0x3e:
|
|
rol(opcode_arg(AM_ABS_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x6a:
|
|
ror_acc();
|
|
cycles += 2;
|
|
break;
|
|
case 0x66:
|
|
ror(opcode_arg(AM_ZP));
|
|
cycles += 5;
|
|
break;
|
|
case 0x76:
|
|
ror(opcode_arg(AM_ZP_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x6e:
|
|
ror(opcode_arg(AM_ABS));
|
|
cycles += 6;
|
|
break;
|
|
case 0x7e:
|
|
ror(opcode_arg(AM_ABS_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x40:
|
|
rti();
|
|
cycles += 6;
|
|
break;
|
|
case 0x60:
|
|
rts();
|
|
cycles += 6;
|
|
break;
|
|
case 0xe9:
|
|
sbc(opcode_arg(AM_IMM));
|
|
cycles += 2;
|
|
break;
|
|
case 0xe5:
|
|
sbc(opcode_arg(AM_ZP));
|
|
cycles += 3;
|
|
break;
|
|
case 0xf5:
|
|
sbc(opcode_arg(AM_ZP_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0xed:
|
|
sbc(opcode_arg(AM_ABS));
|
|
cycles += 4;
|
|
break;
|
|
case 0xfd:
|
|
sbc(opcode_arg(AM_ABS_X));
|
|
cycles += 4;
|
|
break;
|
|
case 0xf9:
|
|
sbc(opcode_arg(AM_ABS_Y));
|
|
cycles += 4;
|
|
break;
|
|
case 0xf2:
|
|
sbc(opcode_arg(AM_IND));
|
|
cycles += 5;
|
|
break;
|
|
case 0xe1:
|
|
sbc(opcode_arg(AM_IND_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0xf1:
|
|
sbc(opcode_arg(AM_IND_Y));
|
|
cycles += 5;
|
|
break;
|
|
case 0x38:
|
|
sec();
|
|
cycles += 2;
|
|
break;
|
|
case 0xf8:
|
|
sed();
|
|
cycles += 2;
|
|
break;
|
|
case 0x78:
|
|
sei();
|
|
cycles += 2;
|
|
break;
|
|
case 0x85:
|
|
sta(opcode_mem(AM_ZP));
|
|
cycles += 4;
|
|
break;
|
|
case 0x95:
|
|
sta(opcode_mem(AM_ZP_X));
|
|
cycles += 5;
|
|
break;
|
|
case 0x8d:
|
|
sta(opcode_mem(AM_ABS));
|
|
cycles += 5;
|
|
break;
|
|
case 0x9d:
|
|
sta(opcode_mem(AM_ABS_X));
|
|
cycles += 6;
|
|
break;
|
|
case 0x99:
|
|
sta(opcode_mem(AM_ABS_Y));
|
|
cycles += 6;
|
|
break;
|
|
case 0x92:
|
|
sta(opcode_mem(AM_IND));
|
|
cycles += 6;
|
|
break;
|
|
case 0x81:
|
|
sta(opcode_mem(AM_IND_X));
|
|
cycles += 7;
|
|
break;
|
|
case 0x91:
|
|
sta(opcode_mem(AM_IND_Y));
|
|
cycles += 7;
|
|
break;
|
|
case 0x86:
|
|
stx(opcode_mem(AM_ZP));
|
|
cycles += 4;
|
|
break;
|
|
case 0x96:
|
|
stx(opcode_mem(AM_ZP_Y));
|
|
cycles += 5;
|
|
break;
|
|
case 0x8e:
|
|
stx(opcode_mem(AM_ABS));
|
|
cycles += 5;
|
|
break;
|
|
case 0x84:
|
|
sty(opcode_mem(AM_ZP));
|
|
cycles += 4;
|
|
break;
|
|
case 0x94:
|
|
sty(opcode_mem(AM_ZP_X));
|
|
cycles += 5;
|
|
break;
|
|
case 0x8c:
|
|
sty(opcode_mem(AM_ABS));
|
|
cycles += 5;
|
|
break;
|
|
case 0xaa:
|
|
tax();
|
|
cycles += 2;
|
|
break;
|
|
case 0xa8:
|
|
tay();
|
|
cycles += 2;
|
|
break;
|
|
case 0xba:
|
|
tsx();
|
|
cycles += 2;
|
|
break;
|
|
case 0x8a:
|
|
txa();
|
|
cycles += 2;
|
|
break;
|
|
case 0x9a:
|
|
txs();
|
|
cycles += 2;
|
|
break;
|
|
case 0x98:
|
|
tya();
|
|
cycles += 2;
|
|
break;
|
|
default:
|
|
printf("opcode $%02X not implemented\n", opcode);
|
|
break;
|
|
}
|
|
|
|
printf("status: %i%i%i%i%i%i%i%i\n", regs.status.carry,
|
|
regs.status.zero, regs.status.interrupt_disable,
|
|
regs.status.decimal_mode, regs.status.brk,
|
|
regs.status.unused, regs.status.overflow,
|
|
regs.status.negative);
|
|
printf("A: $%02X, X: $%02X, Y: $%02X, SP: $%02X, PC: $%04X\n",
|
|
regs.a, regs.x, regs.y, regs.sp, regs.pc);
|
|
}
|
|
}
|
|
|
|
/* https://www.nesdev.org/wiki/CPU_power_up_state */
|
|
void
|
|
cpu_init(void)
|
|
{
|
|
regs.a = regs.x = regs.y = 0;
|
|
regs.pc = 0xFFFC;
|
|
regs.sp = 0xFD;
|
|
|
|
//memset(®s.status, 0, sizeof(regs.status));
|
|
regs.status.unused = 1;
|
|
|
|
cycles += 7;
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
FILE *fp;
|
|
uint8_t *buf;
|
|
size_t buflen;
|
|
|
|
if (argc != 2) {
|
|
fprintf(stderr, "Usage: %s rom.nes\n", basename(argv[0]));
|
|
return 1;
|
|
}
|
|
|
|
fp = fopen(argv[1], "r");
|
|
fseek(fp, 0, SEEK_END);
|
|
buflen = ftell(fp);
|
|
buf = calloc(1, buflen);
|
|
fseek(fp, 0, SEEK_SET);
|
|
if (fread(buf, 1, buflen, fp) != buflen && ferror(fp)) {
|
|
fprintf(stderr, "file %s was not read properly\n", argv[1]);
|
|
clearerr(fp);
|
|
fclose(fp);
|
|
return 1;
|
|
}
|
|
fclose(fp);
|
|
|
|
parse_rom(buf, buflen, &rom);
|
|
free(buf);
|
|
|
|
cpu_init();
|
|
|
|
/* TODO: move to separate file? */
|
|
if (rom.mapper != 0) {
|
|
fprintf(stderr, "Only iNES ROMs using Mapper 0 are supported for now.\n");
|
|
return 1;
|
|
}
|
|
|
|
memwrite16(0xFFFC, 0xC000);
|
|
regs.pc = 0xC000;
|
|
|
|
printf("status: %i%i%i%i%i%i%i%i\n", regs.status.carry,
|
|
regs.status.zero, regs.status.interrupt_disable,
|
|
regs.status.decimal_mode, regs.status.brk,
|
|
regs.status.unused, regs.status.overflow,
|
|
regs.status.negative);
|
|
printf("A: $%02X, X: $%02X, Y: $%02X, SP: $%02X, PC: $%02X\n",
|
|
regs.a, regs.x, regs.y, regs.sp, regs.pc);
|
|
|
|
putchar('\n');
|
|
|
|
interpret();
|
|
|
|
free_rom(&rom);
|
|
|
|
return 0;
|
|
}
|