emu_nes/cpu.c
vin 6a5b0b728e start fixing bugs with memory access
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.
2024-06-17 15:11:45 +05:30

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(&regs.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;
}