Skip to content

Instantly share code, notes, and snippets.

@mrorigo
Last active October 9, 2022 14:21
Show Gist options
  • Save mrorigo/7e2dd56367b5a74be68612cc029ccb29 to your computer and use it in GitHub Desktop.
Save mrorigo/7e2dd56367b5a74be68612cc029ccb29 to your computer and use it in GitHub Desktop.
Cycle exact 6502 (2A03) emulator in <1k lines of C
/**
* Copyright (C) 2022 orIgo <[email protected]>
*
* Cycle exact 6502 (2A03) emulator.
*
* - To initialize the cpu, provide an initialized `cpu_bus_t *` with memory read/write primitives to `cpu_init()` .
* - For each tick/cycle, call `cpu_tick()` with the returned `cpu_t *`, checking the return value to detect HLT.
* - To cause an NMI request, call `cpu_nmi()`.
*/
#include "cpu.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
static void cpu_write_byte(cpu_t *cpu, const uint16_t addr, const uint8_t val)
{
cpu->bus->write(addr, val);
}
static uint8_t cpu_read_byte(cpu_t *cpu, const uint16_t addr)
{
return cpu->bus->read(addr);
}
static uint16_t cpu_read_word(cpu_t *cpu, const uint16_t addr)
{
const uint8_t l = cpu->bus->read(addr);
const uint8_t h = cpu->bus->read(addr + 1);
return (h << 8) | l;
}
static void cpu_push_stack(cpu_t *cpu, const uint8_t x)
{
cpu_write_byte(cpu, cpu->regs.sp + STACK_BASE, x);
cpu->regs.sp -= 1;
}
static uint8_t cpu_pop_stack(cpu_t *cpu)
{
cpu->regs.sp += 1;
return cpu_read_byte(cpu, cpu->regs.sp + STACK_BASE);
}
#define SET_NZ(reg) cpu->flags.n = (reg & 0x80) == 0x80; cpu->flags.z = reg == 0
#define OP_IMPL(x) static void op_impl_##x(cpu_t *cpu)
OP_IMPL(hlt)
{
cpu->halted = true;
fprintf(stderr, "CPU: HLT\n");
}
/**
* (1) The BRK instruction forces the generation of an interrupt request.
* (2) The program counter and processor status are pushed on the stack
* (3) then the IRQ interrupt vector at $FFFE/F is loaded into the PC
* (4) and the break flag in the status set to one.
*/
OP_IMPL(brk)
{
cpu->soft_interrupt = 1;
cpu->pc++;
cpu_push_stack(cpu, cpu->pc >> 8);
cpu_push_stack(cpu, cpu->pc & 0xff);
cpu_push_stack(cpu, cpu->flags.flags);
cpu->flags.b = 1;
cpu->pc = cpu_read_word(cpu, IRQ_VEC);
}
OP_IMPL(nop) { (void)cpu; }
#define DO_BRANCH(cond) \
if((cond)) {\
const uint16_t from = cpu->pc + cpu->opcode->size; \
const uint16_t to = cpu->addr + cpu->opcode->size; \
if((from & 0xff00) != (to & 0xff00)) { \
cpu->extra_cycle = 2; \
} else { \
cpu->extra_cycle = 1; \
} \
cpu->pc = (cpu->addr);\
}
OP_IMPL(beq) { DO_BRANCH(cpu->flags.z); }
OP_IMPL(bne) { DO_BRANCH(!cpu->flags.z); }
OP_IMPL(bmi) { DO_BRANCH(cpu->flags.n); }
OP_IMPL(bpl) { DO_BRANCH(!cpu->flags.n); }
OP_IMPL(bcs) { DO_BRANCH(cpu->flags.c); }
OP_IMPL(bcc) { DO_BRANCH(!cpu->flags.c); }
OP_IMPL(bvs) { DO_BRANCH(cpu->flags.v); }
OP_IMPL(bvc) { DO_BRANCH(!cpu->flags.v); }
OP_IMPL(rti)
{
cpu->soft_interrupt = 0;
cpu->flags.flags = cpu_pop_stack(cpu) | 0x20;
cpu->pc = ((cpu_pop_stack(cpu) | (cpu_pop_stack(cpu)<<8)) - 1);
}
OP_IMPL(rts)
{
cpu->pc = (cpu_pop_stack(cpu) | (cpu_pop_stack(cpu)<<8));
}
OP_IMPL(jsr)
{
cpu_push_stack(cpu, (cpu->pc + 2) >> 8);
cpu_push_stack(cpu, (cpu->pc + 2) & 0xff);
cpu->pc = (cpu->addr - 3);
}
OP_IMPL(jmp)
{
cpu->pc = (cpu->addr - cpu->opcode->size);
}
OP_IMPL(sbc)
{
const int16_t t = cpu->regs.a - cpu->arg - !cpu->flags.c;
const bool ovflw = ((cpu->regs.a >> 7) != (cpu->arg >> 7)) // Overflow can only occur if MSBs are different
&& ((cpu->regs.a >> 7) != ((t & 0xFF) >> 7));
cpu->regs.a = t & 0xff;
cpu->flags.c = (t >= 0);
cpu->flags.v = ovflw;
SET_NZ(cpu->regs.a);
}
OP_IMPL(adc)
{
const int16_t t = cpu->regs.a + cpu->arg + cpu->flags.c;
const bool ovflw = ((cpu->regs.a >> 7) == (cpu->arg >> 7)) // Overflow can only occur if MSBs are equal
&& ((cpu->regs.a >> 7) != ((t & 0xFF) >> 7));
cpu->regs.a = t & 0xff;
cpu->flags.c = (t >> 8) & 0x01;
cpu->flags.v = ovflw;
SET_NZ(cpu->regs.a);
}
// The mask pattern in A is ANDed with the value in memory to set or clear the zero flag, but the result is not kept. Bits 7 and 6 of the value from memory are copied into the N and V flags.
OP_IMPL(bit)
{
cpu->flags.n = (cpu->arg & (1<<7)) >> 7;
cpu->flags.v = (cpu->arg & (1<<6)) >> 6;
cpu->flags.z = (cpu->arg & cpu->regs.a) == 0;
}
OP_IMPL(and)
{
cpu->regs.regs[cpu->opcode->reg] &= cpu->arg;
SET_NZ(cpu->regs.regs[cpu->opcode->reg]);
}
OP_IMPL(eor)
{
cpu->regs.regs[cpu->opcode->reg] ^= cpu->arg;
SET_NZ(cpu->regs.regs[cpu->opcode->reg]);
}
OP_IMPL(ora)
{
cpu->regs.regs[cpu->opcode->reg] |= cpu->arg;
SET_NZ(cpu->regs.regs[cpu->opcode->reg]);
}
OP_IMPL(inc)
{
const uint8_t v = (cpu->arg + 1) & 0xff;
if(cpu->opcode->amode == AM_Accumulator) {
assert(cpu->opcode->reg == R_A);
cpu->regs.regs[cpu->opcode->reg] = v;
} else {
cpu_write_byte(cpu, cpu->addr, v);
}
SET_NZ(v);
}
OP_IMPL(dec)
{
const uint8_t v = (cpu->arg - 1) & 0xff;
if(cpu->opcode->amode == AM_Accumulator) {
assert(cpu->opcode->reg == R_A);
cpu->regs.regs[cpu->opcode->reg] = v;
} else {
cpu_write_byte(cpu, cpu->addr, v);
}
SET_NZ(v);
}
OP_IMPL(inx)
{
assert(cpu->opcode->reg == R_X);
cpu->regs.regs[cpu->opcode->reg]++;
SET_NZ(cpu->regs.regs[cpu->opcode->reg]);
}
OP_IMPL(dex)
{
assert(cpu->opcode->reg == R_X);
cpu->regs.regs[cpu->opcode->reg]--;
SET_NZ(cpu->regs.regs[cpu->opcode->reg]);
}
OP_IMPL(iny)
{
assert(cpu->opcode->reg == R_Y);
cpu->regs.regs[cpu->opcode->reg]++;
SET_NZ(cpu->regs.regs[cpu->opcode->reg]);
}
OP_IMPL(dey)
{
assert(cpu->opcode->reg == R_Y);
cpu->regs.regs[cpu->opcode->reg]--;
SET_NZ(cpu->regs.regs[cpu->opcode->reg]);
}
OP_IMPL(sec) { cpu->flags.c = 1; }
OP_IMPL(clc) { cpu->flags.c = 0; }
OP_IMPL(sed) { cpu->flags.d = 1; }
OP_IMPL(cld) { cpu->flags.d = 0; }
OP_IMPL(sei) { cpu->flags.i = 1; }
OP_IMPL(cli) { cpu->flags.i = 0; }
OP_IMPL(clv) { cpu->flags.v = 0; }
OP_IMPL(lax)
{
cpu->regs.x = cpu->regs.a = cpu->arg;
SET_NZ(cpu->regs.x);
}
// implements ld(a|x|y)
OP_IMPL(ld)
{
cpu->regs.regs[cpu->opcode->reg] = cpu->arg;
SET_NZ(cpu->regs.regs[cpu->opcode->reg]);
}
#define op_impl_lda op_impl_ld
#define op_impl_ldx op_impl_ld
#define op_impl_ldy op_impl_ld
// implements st(a|x|y)
OP_IMPL(st)
{
cpu_write_byte(cpu, cpu->addr, cpu->regs.regs[cpu->opcode->reg]);
}
#define op_impl_sta op_impl_st
#define op_impl_stx op_impl_st
#define op_impl_sty op_impl_st
static void _impl_trr(cpu_t *cpu, const op_reg_t from)
{
cpu->regs.regs[cpu->opcode->reg] = cpu->regs.regs[from];
SET_NZ(cpu->regs.regs[cpu->opcode->reg]);
}
OP_IMPL(tax) { _impl_trr(cpu, R_A); }
OP_IMPL(txa) { _impl_trr(cpu, R_X); }
OP_IMPL(tay) { _impl_trr(cpu, R_A); }
OP_IMPL(tya) { _impl_trr(cpu, R_Y); }
OP_IMPL(txs) { cpu->regs.sp = cpu->regs.x; }
OP_IMPL(tsx) { _impl_trr(cpu, R_SP); }
OP_IMPL(php)
{
cpu_push_stack(cpu, cpu->flags.flags | 0x30);
}
OP_IMPL(pha)
{
cpu_push_stack(cpu, cpu->regs.a);
}
OP_IMPL(pla)
{
cpu->regs.a = cpu_pop_stack(cpu);
SET_NZ(cpu->regs.a);
}
OP_IMPL(plp)
{
cpu->flags.flags = (cpu_pop_stack(cpu) & ~ 0x10) | // B flag may exist on stack but not P so it is cleared
0x20; // bit 5 always set
}
#define do_lsr(x) \
cpu->flags.c = ((x&0x01) == 0x01); \
x >>= 1; \
SET_NZ(x);
OP_IMPL(lsr) {
if(cpu->opcode->amode == AM_Accumulator) {
do_lsr(cpu->regs.a);
} else {
do_lsr(cpu->arg);
cpu_write_byte(cpu, cpu->addr, cpu->arg);
}
}
// LSRs the contents of a memory location and then EORs the result with the accumulator
OP_IMPL(sre)
{
do_lsr(cpu->arg);
cpu_write_byte(cpu, cpu->addr, cpu->arg);
cpu->regs.a ^= cpu->arg;
SET_NZ(cpu->regs.a);
}
#undef do_lsr
#define do_asl(x) \
cpu->flags.c = ((x&0x80) == 0x80); \
x <<= 1; \
SET_NZ(x);
OP_IMPL(asl)
{
if(cpu->opcode->amode == AM_Accumulator) {
do_asl(cpu->regs.a);
} else {
do_asl(cpu->arg);
cpu_write_byte(cpu, cpu->addr, cpu->arg);
}
}
OP_IMPL(aso)
{
do_asl(cpu->arg);
cpu_write_byte(cpu, cpu->addr, cpu->arg);
cpu->regs.a |= cpu->arg;
SET_NZ(cpu->regs.a);
}
#undef do_asl
#define do_rol(x) \
const uint8_t old_carry = cpu->flags.c; \
cpu->flags.c = ((x&0x80)>>7); \
x = ((x << 1) & 0xff) | old_carry; \
SET_NZ(x);
OP_IMPL(rol)
{
if(cpu->opcode->amode == AM_Accumulator) {
do_rol(cpu->regs.a);
} else {
do_rol(cpu->arg);
cpu_write_byte(cpu, cpu->addr, cpu->arg);
}
}
// ROLs the contents of a memory location and then ANDs the result with the accumulator
OP_IMPL(rla)
{
do_rol(cpu->arg);
cpu_write_byte(cpu, cpu->addr, cpu->arg);
cpu->regs.a &= cpu->arg;
SET_NZ(cpu->regs.a);
}
#undef do_rol
#define do_ror(x) \
const uint8_t old_carry = cpu->flags.c; \
cpu->flags.c = ((x&1) == 1); \
x = ((x >> 1) & 0xff) | (old_carry<<7); \
SET_NZ(x);
OP_IMPL(ror)
{
if(cpu->opcode->amode == AM_Accumulator) {
do_ror(cpu->regs.a);
} else {
do_ror(cpu->arg);
cpu_write_byte(cpu, cpu->addr, cpu->arg);
}
}
OP_IMPL(rra)
{
do_ror(cpu->arg);
cpu_write_byte(cpu, cpu->addr, cpu->arg);
op_impl_adc(cpu);
}
#undef do_ror
static void _impl_cmp(cpu_t *cpu, const register_t reg)
{
const int result = cpu->regs.regs[reg] - cpu->arg;
SET_NZ(result);
cpu->flags.c = (cpu->arg <= cpu->regs.regs[reg]) ? 1 : 0;
}
OP_IMPL(cmp)
{
assert(cpu->opcode->reg == R_A);
_impl_cmp(cpu, cpu->opcode->reg);
}
OP_IMPL(cpx)
{
assert(cpu->opcode->reg == R_X);
_impl_cmp(cpu, cpu->opcode->reg);
}
OP_IMPL(cpy)
{
assert(cpu->opcode->reg == R_Y);
_impl_cmp(cpu, cpu->opcode->reg);
}
OP_IMPL(skw) { (void)cpu; } // SKW=SkipWord=NOP
// ANDs the contents of the A and X registers (without changing the contents of either register)
// and stores the result in memory. Does not affect any flags.
OP_IMPL(sax)
{
const uint8_t v = cpu->regs.a & cpu->regs.x; // AND
cpu_write_byte(cpu, cpu->addr, v); // STX
}
// DECs the contents of a memory location and then CMPs the result with the A register
OP_IMPL(dcp)
{
cpu->arg--;
cpu_write_byte(cpu, cpu->addr, cpu->arg);
_impl_cmp(cpu, R_A);
}
// DECs the contents of a memory location and then SBCs the result from the A register
OP_IMPL(isb) {
cpu->arg++;
cpu_write_byte(cpu, cpu->addr, cpu->arg);
op_impl_sbc(cpu);
}
#undef OP_IMPL
#undef SET_NZ
#define op(name, am, reg, sz, cy, ex) {#name, AM_##am,reg,sz,cy,ex,op_impl_##name}
#define op_nop op(nop, Implied, R_N, 1, 2, false)
#define op_hlt op(hlt, Implied, R_N, 1, 0, false)
#define op_nimp {0}
static const opcode_t opcodes[256] = {
/* $00 */
op(brk, Implied, R_N, 1, 7, false),
op(ora, IndirectX, R_A, 2, 6, false),
op(hlt, Implied, R_N, 1, 0, false),
op(aso, IndirectX, R_A, 2, 8, false),
op(nop, Relative, R_N, 2, 3, false), // INVALID OP $0C (NOP $XX)
op(ora, ZeroPage, R_A, 2, 3, false),
op(asl, ZeroPage, R_N, 2, 5, false),
op(aso, ZeroPage, R_A, 2, 5, false),
op(php, Implied, R_N, 1, 3, false),
op(ora, Immediate, R_A, 2, 2, false),
op(asl, Accumulator, R_A, 1, 2, false),
op_nimp, // ANC, not used by 2A03
op(skw, Absolute, R_N, 3, 4, true),
op(ora, Absolute, R_A, 3, 4, false),
op(asl, Absolute, R_N, 3, 6, false),
op(aso, Absolute, R_A, 3, 6, false),
/* $10 */
op(bpl, Relative, R_N, 2, 2, false),
op(ora, IndirectY, R_A, 2, 5, true),
op_hlt,
op(aso, IndirectY, R_A, 2, 8, false),
op(nop, ZeroPageX, R_N, 2, 4, false), // NOP $XX,x
op(ora, ZeroPageX, R_A, 2, 4, false),
op(asl, ZeroPageX, R_N, 2, 6, false),
op(aso, ZeroPageX, R_A, 2, 6, false),
op(clc, Implied, R_N, 1, 2, false),
op(ora, AbsoluteY, R_A, 3, 4, true),
op_nop,
op(aso, AbsoluteY, R_A, 3, 7, false),
op(skw, AbsoluteX, R_N, 3, 4, true),
op(ora, AbsoluteX, R_A, 3, 4, true),
op(asl, AbsoluteX, R_N, 3, 7, true),
op(aso, AbsoluteX, R_A, 3, 7, false),
/* $20 */
op(jsr, Absolute, R_N, 3, 6, false),
op(and, IndirectX, R_A, 2, 6, false),
op_hlt,
op(rla, IndirectX, R_A, 2, 8, false),
op(bit, ZeroPage, R_A, 2, 3, false),
op(and, ZeroPage, R_A, 2, 3, false),
op(rol, ZeroPage, R_A, 2, 5, false),
op(rla, ZeroPage, R_A, 2, 5, false),
op(plp, Implied, R_N, 1, 4, false),
op(and, Immediate, R_A, 2, 2, false),
op(rol, Accumulator, R_A, 1, 2, false),
op_nimp, // ANC
op(bit, Absolute, R_N, 3, 4, false),
op(and, Absolute, R_A, 3, 4, false),
op(rol, Absolute, R_A, 3, 6, false),
op(rla, Absolute, R_A, 3, 6, false),
/* $30 */
op(bmi, Relative, R_N, 2, 2, false),
op(and, IndirectY, R_A, 2, 5, true),
op_hlt,
op(rla, IndirectY, R_A, 2, 8, false),
op(nop, ZeroPageX, R_N, 2, 4, false), // NOP $XX,X
op(and, ZeroPageX, R_A, 2, 4, false),
op(rol, ZeroPageX, R_A, 2, 6, false),
op(rla, ZeroPageX, R_A, 2, 6, false),
op(sec, Implied, R_N, 1, 2, false),
op(and, AbsoluteY, R_A, 3, 4, true),
op_nop,
op(rla, AbsoluteY, R_A, 3, 7, false),
op(skw, AbsoluteX, R_N, 3, 4, true),
op(and, AbsoluteX, R_A, 3, 4, true),
op(rol, AbsoluteX, R_A, 3, 7, false),
op(rla, AbsoluteX, R_A, 3, 7, false),
/* $40 */
op(rti, Implied, R_N, 1, 6, false),
op(eor, IndirectX, R_A, 2, 6, false),
op_hlt,
op(sre, IndirectX, R_A, 2, 8, false),
op(nop, Relative, R_N, 2, 3, false),
op(eor, ZeroPage, R_A, 2, 3, false),
op(lsr, ZeroPage, R_N, 2, 5, false),
op(sre, ZeroPage, R_A, 2, 5, false),
op(pha, Implied, R_A, 1, 3, false),
op(eor, Immediate, R_A, 2, 2, false),
op(lsr, Accumulator, R_A, 1, 2, false),
op_nimp, // ALR
op(jmp, Absolute, R_N, 3, 3, false),
op(eor, Absolute, R_A, 3, 4, false),
op(lsr, Absolute, R_N, 3, 6, false),
op(sre, Absolute, R_A, 3, 6, false),
/* $50 */
op(bvc, Relative, R_N, 2, 2, false),
op(eor, IndirectY, R_A, 2, 5, true),
op_hlt,
op(sre, IndirectY, R_A, 2, 8, false),
op(nop, ZeroPageX, R_N, 2, 4, false), // NOP $XX,X
op(eor, ZeroPageX, R_A, 2, 4, false),
op(lsr, ZeroPageX, R_A, 2, 6, false),
op(sre, ZeroPageX, R_A, 2, 6, false),
op(cli, Implied, R_N, 1, 2, false),
op(eor, AbsoluteY, R_A, 3, 4, true),
op_nop,
op(sre, AbsoluteY, R_A, 3, 7, false),
op(skw, AbsoluteX, R_N, 3, 4, true),
op(eor, AbsoluteX, R_A, 3, 4, true),
op(lsr, AbsoluteX, R_A, 3, 7, false),
op(sre, AbsoluteX, R_A, 3, 7, false),
/* $60 */
op(rts, Implied, R_N, 1, 6, false),
op(adc, IndirectX, R_A, 2, 6,false),
op_hlt,
op(rra, IndirectX, R_A, 2, 8, false),
op(nop, Relative, R_N, 2, 3, false),
op(adc, ZeroPage, R_A, 2, 3, false),
op(ror, ZeroPage, R_N, 2, 5, false),
op(rra, ZeroPage, R_A, 2, 5, false),
op(pla, Implied, R_A, 1, 4, false),
op(adc, Immediate, R_A, 2, 2, false),
op(ror, Accumulator, R_A, 1, 2, false),
op_nimp, // ARR
op(jmp, Indirect, R_N, 3, 5, false),
op(adc, Absolute, R_A, 3, 4, false),
op(ror, Absolute, R_N, 3, 6, false),
op(rra, Absolute, R_A, 3, 6, false),
/* $70 */
op(bvs, Relative, R_N, 2, 2, false),
op(adc, IndirectY, R_A, 2, 5, true),
op_hlt,
op(rra, IndirectY, R_A, 2, 8, false),
op(nop, ZeroPageX, R_N, 2, 4, false), // NOP $XX,X
op(adc, ZeroPageX, R_N, 2, 4, false),
op(ror, ZeroPageX, R_N, 2, 6, false),
op(rra, ZeroPageX, R_A, 2, 6, false),
op(sei, Implied, R_N, 1, 2, false),
op(adc, AbsoluteY, R_A, 3, 4, true),
op_nop,
op(rra, AbsoluteY, R_A, 3, 7, false),
op(skw, AbsoluteX, R_N, 3, 4, true),
op(adc, AbsoluteX, R_A, 3, 4, true),
op(ror, AbsoluteX, R_N, 3, 7, false),
op(rra, AbsoluteX, R_A, 3, 7, false),
/* $80 */
op(nop, Immediate, R_N, 2, 2, false), // NOP #$XX
op(sta, IndirectX, R_A, 2, 6, false),
op_nimp, // "may be HLT"
op(sax, IndirectX, R_X, 2, 6, false),
op(sty, ZeroPage, R_Y, 2, 3, false),
op(sta, ZeroPage, R_A, 2, 3, false),
op(stx, ZeroPage, R_X, 2, 3, false),
op(sax, ZeroPage, R_X, 2, 3, false),
op(dey, Implied, R_Y, 1, 2, false),
op_nimp,
op(txa, Implied, R_A, 1, 2, false),
op_nimp,
op(sty, Absolute, R_Y, 3, 4, false),
op(sta, Absolute, R_A, 3, 4, false),
op(stx, Absolute, R_X, 3, 4, false),
op(sax, Absolute, R_X, 3, 4, false),
/* $90 */
op(bcc, Relative, R_N, 2, 2, false),
op(sta, IndirectY, R_A, 2, 6, false),
op_hlt,
op_nimp,
op(sty, ZeroPageX, R_Y, 2, 4, false),
op(sta, ZeroPageX, R_A, 2, 4, false),
op(stx, ZeroPageY, R_X, 2, 4, false),
op(sax, ZeroPageY, R_X, 2, 4, false),
op(tya, Implied, R_A, 1, 2, false),
op(sta, AbsoluteY, R_A, 3, 5, false),
op(txs, Implied, R_SP, 1, 2, false),
op_nimp,
op_nimp,
op(sta, AbsoluteX, R_A, 3, 5, false),
op_nimp,
op_nimp,
/* $A0 */
op(ldy, Immediate, R_Y, 2, 2, false),
op(lda, IndirectX, R_A, 2, 6, false),
op(ldx, Immediate, R_X, 2, 2, false),
op(lax, IndirectX, R_X, 2, 6, false),
op(ldy, ZeroPage, R_Y, 2, 3, false),
op(lda, ZeroPage, R_A, 2, 3, false),
op(ldx, ZeroPage, R_X, 2, 3, false),
op(lax, ZeroPage, R_X, 2, 3, false),
op(tay, Implied, R_Y, 1, 2, false),
op(lda, Immediate, R_A, 2, 2, false),
op(tax, Implied, R_X, 1, 2, false),
op_nimp,
op(ldy, Absolute, R_Y, 3, 4, false),
op(lda, Absolute, R_A, 3, 4, false),
op(ldx, Absolute, R_X, 3, 4, false),
op(lax, Absolute, R_X, 3, 4, false),
/* $B0 */
op(bcs, Relative, R_N, 2, 2, false),
op(lda, IndirectY, R_A, 2, 5, true),
op_hlt,
op(lax, IndirectY, R_X, 2, 5, true),
op(ldy, ZeroPageX, R_Y, 2, 4, false),
op(lda, ZeroPageX, R_A, 2, 4, false),
op(ldx, ZeroPageY, R_X, 2, 4, false),
op(lax, ZeroPageY, R_X, 2, 4, false),
op(clv, Implied, R_N, 1, 2, false),
op(lda, AbsoluteY, R_A, 3, 4, true),
op(tsx, Implied, R_X, 1, 2, false),
op_nimp,
op(ldy, AbsoluteX, R_Y, 3, 4, true),
op(lda, AbsoluteX, R_A, 3, 4, true),
op(ldx, AbsoluteY, R_X, 3, 4, true),
op(lax, AbsoluteY, R_X, 3, 4, true),
/* $C0 */
op(cpy, Immediate, R_Y, 2, 2, false),
op(cmp, IndirectX, R_A, 2, 6, false),
op_hlt, // "may be HLT"
op(dcp, IndirectX, R_A, 2, 8, false),
op(cpy, ZeroPage, R_Y, 2, 3, false),
op(cmp, ZeroPage, R_A, 2, 3, false),
op(dec, ZeroPage, R_A, 2, 5, false),
op(dcp, ZeroPage, R_A, 2, 5, false),
op(iny, Implied, R_Y, 1, 2, false),
op(cmp, Immediate, R_A, 2, 2, false),
op(dex, Implied, R_X, 1, 2, false),
op_nimp,
op(cpy, Absolute, R_Y, 3, 4, false),
op(cmp, Absolute, R_A, 3, 4, false),
op(dec, Absolute, R_A, 3, 6, false),
op(dcp, Absolute, R_A, 3, 6, false),
/* $D0 */
op(bne, Relative, R_N, 2, 2, false),
op(cmp, IndirectY, R_A, 2, 5, true),
op_hlt,
op(dcp, IndirectY, R_A, 2, 8, false),
op(nop, ZeroPageX, R_N, 2, 4, false), // NOP $XX,X
op(cmp, ZeroPageX, R_A, 2, 4, false),
op(dec, ZeroPageX, R_A, 2, 6, false),
op(dcp, ZeroPageX, R_A, 2, 6, false),
op(cld, Implied, R_N, 1, 2, false),
op(cmp, AbsoluteY, R_A, 3, 4, true),
op_nop,
op(dcp, AbsoluteY, R_A, 3, 7, false),
op(skw, AbsoluteX, R_N, 3, 4, true),
op(cmp, AbsoluteX, R_A, 3, 4, true),
op(dec, AbsoluteX, R_A, 3, 7, false),
op(dcp, AbsoluteX, R_A, 3, 7, false),
/* $E0 */
op(cpx, Immediate, R_X, 2, 2, false),
op(sbc, IndirectX, R_A, 2, 6, false),
op_nimp,
op(isb, IndirectX, R_A, 2, 8, false),
op(cpx, ZeroPage, R_X, 2, 3, false),
op(sbc, ZeroPage, R_A, 2, 3, false),
op(inc, ZeroPage, R_A, 2, 5, false),
op(isb, ZeroPage, R_A, 2, 5, false),
op(inx, Implied, R_X, 1, 2, false),
op(sbc, Immediate, R_A, 2, 2, false),
op_nop,
op(sbc, Immediate, R_A, 2, 2, false),
op(cpx, Absolute, R_X, 3, 4, false),
op(sbc, Absolute, R_A, 3, 4, false),
op(inc, Absolute, R_A, 3, 6, false),
op(isb, Absolute, R_A, 3, 6, false),
/* $F0 */
op(beq, Relative, R_N, 2, 2, false),
op(sbc, IndirectY, R_A, 2, 5, true),
op_hlt,
op(isb, IndirectY, R_A, 2, 8, false),
op(nop, ZeroPageX, R_N, 2, 4, false), // NOP $XX,X
op(sbc, ZeroPageX, R_A, 2, 4, false),
op(inc, ZeroPageX, R_A, 2, 6, false),
op(isb, ZeroPageX, R_A, 2, 6, false),
op(sed, Implied, R_N, 1, 2, false),
op(sbc, AbsoluteY, R_Y, 3, 4, true),
op_nop,
op(isb, AbsoluteY, R_A, 3, 7, false),
op(skw, AbsoluteX, R_N, 3, 4, true),
op(sbc, AbsoluteX, R_A, 3, 4, true),
op(inc, AbsoluteX, R_A, 3, 7, false),
op(isb, AbsoluteX, R_A, 3, 7, false)
};
static void check_extra(cpu_t *cpu, const uint16_t low, const uint16_t offs)
{
const uint16_t val = (low & 0xff) + offs;
if(cpu->opcode->extra_cycle && (val > 0xff)) {
cpu->extra_cycle = 1;
}
}
static void _cpu_decode_am_implied(cpu_t *cpu) { (void)cpu; }
static void _cpu_decode_am_immediate(cpu_t *cpu)
{
assert(!cpu->opcode->extra_cycle);
assert(cpu->opcode->size == 2);
cpu->arg = cpu_read_byte(cpu, cpu->pc + 1);
}
static void _cpu_decode_am_zeropage(cpu_t *cpu)
{
assert(!cpu->opcode->extra_cycle);
assert(cpu->opcode->size == 2);
cpu->addr = cpu_read_byte(cpu, cpu->pc + 1);
cpu->arg = cpu_read_byte(cpu, cpu->addr );
}
static void _cpu_decode_am_zeropagex(cpu_t *cpu)
{
assert(!cpu->opcode->extra_cycle);
assert(cpu->opcode->size == 2);
cpu->addr = ((uint8_t)(cpu_read_byte(cpu, cpu->pc + 1) + cpu->regs.x));
cpu->arg = cpu_read_byte(cpu, cpu->addr & 0xff );
}
static void _cpu_decode_am_zeropagey(cpu_t *cpu)
{
assert(!cpu->opcode->extra_cycle);
assert(cpu->opcode->size == 2);
cpu->addr = cpu_read_byte(cpu, cpu->pc + 1);
cpu->addr = (cpu->addr + (uint16_t)cpu->regs.y) & 0xff;
cpu->arg = cpu_read_byte(cpu, cpu->addr);
}
static void _cpu_decode_am_absolute(cpu_t *cpu)
{
assert(cpu->opcode->size == 3);
cpu->addr = cpu_read_word(cpu, cpu->pc + 1);
cpu->arg = cpu_read_byte(cpu, cpu->addr );
}
static void _cpu_decode_am_absolutex(cpu_t *cpu)
{
assert(cpu->opcode->size == 3);
const uint16_t tmp = cpu_read_word(cpu, cpu->pc + 1);
check_extra(cpu, tmp, cpu->regs.x);
cpu->addr = (tmp + (uint16_t)cpu->regs.x);
cpu->arg = cpu_read_byte(cpu, cpu->addr );
}
static void _cpu_decode_am_absolutey(cpu_t *cpu)
{
assert(cpu->opcode->size == 3);
const uint16_t tmp = cpu_read_word(cpu, cpu->pc + 1);
check_extra(cpu, tmp, cpu->regs.y);
cpu->addr = (tmp + cpu->regs.y);
cpu->arg = cpu_read_byte(cpu, cpu->addr );
}
static void _cpu_decode_am_indirect(cpu_t *cpu)
{
assert(cpu->opcode->size == 3);
assert(!cpu->opcode->extra_cycle);
cpu->addr = cpu_read_word(cpu, cpu->pc + 1);
const uint8_t addr_lo = cpu_read_byte(cpu, cpu->addr);
if((cpu->addr & 0x00ff) == 0xff) {
cpu->addr &= 0xff00;
} else {
cpu->addr++;
}
const uint8_t addr_hi = cpu_read_byte(cpu, cpu->addr);
cpu->addr = (addr_hi << 8) | (addr_lo);
cpu->arg = 0;
}
static void _cpu_decode_am_indirectx(cpu_t *cpu)
{
assert(cpu->opcode->size == 2);
assert(!cpu->opcode->extra_cycle);
const uint16_t tmp = cpu_read_byte(cpu, cpu->pc + 1);
cpu->addr = tmp + (int16_t)cpu->regs.x;
cpu->addr = (cpu_read_byte(cpu, cpu->addr & 0xff)) |
(cpu_read_byte(cpu, (cpu->addr + 1) & 0xff ) << 8);
cpu->arg = cpu_read_byte(cpu, cpu->addr);
}
static void _cpu_decode_am_indirecty(cpu_t *cpu)
{
assert(cpu->opcode->size == 2);
const uint16_t tmp = cpu_read_byte(cpu, cpu->pc + 1);
const uint8_t addr_lo = cpu_read_byte(cpu, tmp);
const uint8_t addr_hi = cpu_read_byte(cpu, (tmp + 1) & 0xff);
cpu->addr = (addr_hi << 8 ) | addr_lo;
check_extra(cpu, cpu->addr, cpu->regs.y);
cpu->addr = ((((addr_hi << 8 ) | addr_lo) + (int16_t)cpu->regs.y) & 0xffff);
cpu->arg = cpu_read_byte(cpu, cpu->addr);
}
static void _cpu_decode_am_relative(cpu_t *cpu)
{
assert(cpu->opcode->size == 2);
assert(!cpu->opcode->extra_cycle);
cpu->addr = cpu->pc + (int8_t)cpu_read_byte(cpu, cpu->pc + 1);
}
static void _cpu_decode_am_accumulator(cpu_t *cpu) { (void)cpu; }
typedef void (*cpu_amode_callback)(cpu_t *cpu);
static const cpu_amode_callback cpu_address_mode_callbacks[] = {
_cpu_decode_am_implied, _cpu_decode_am_immediate, _cpu_decode_am_zeropage,
_cpu_decode_am_zeropagex, _cpu_decode_am_zeropagey, _cpu_decode_am_absolute,
_cpu_decode_am_absolutex, _cpu_decode_am_absolutey, _cpu_decode_am_indirect,
_cpu_decode_am_indirectx, _cpu_decode_am_indirecty, _cpu_decode_am_relative, _cpu_decode_am_accumulator
};
bool cpu_tick(cpu_t *cpu)
{
cpu->ticks++;
if(cpu->ticks > cpu->clock) {
const uint8_t op = cpu_read_byte(cpu, cpu->pc);
const opcode_t *opcode = cpu->opcode = &opcodes[op];
cpu->extra_cycle = 0;
cpu_address_mode_callbacks[opcode->amode](cpu);
cpu_dump_state(cpu, op);
assert(opcode->fn != NULL);
opcode->fn(cpu);
cpu->pc += opcode->size;
cpu->clock += opcode->cycles + cpu->extra_cycle;
}
return !cpu->halted;
}
void cpu_nmi(cpu_t *cpu)
{
cpu->soft_interrupt = 1;
cpu->pc-=1;
cpu_push_stack(cpu, cpu->pc >> 8);
cpu_push_stack(cpu, cpu->pc & 0xff);
cpu->flags.b = 0; // push P on stack (with B flag *clear*),
cpu_push_stack(cpu, cpu->flags.flags);
cpu->pc = cpu_read_word(cpu, NMI_VEC);
fprintf(stderr, "CPU: NMI: pc=0x%04x\n", cpu->pc);
}
cpu_t *cpu_init(cpu_bus_t * restrict bus)
{
cpu_t *cpu = malloc(sizeof(cpu_t));
if(cpu) {
cpu->bus = bus;
cpu->regs.a = 0;
cpu->regs.x = 0;
cpu->regs.y = 0;
cpu->regs.sp = 0xfd;
cpu->pc = 0xc000;
cpu->clock = cpu->ticks = 7;
cpu_reset(cpu);
}
return cpu;
}
void cpu_dump_state(const cpu_t *cpu, const uint8_t op)
{
fprintf(stderr, "%04x %02x %02x %02x %s ---- A:%02x X:%02x Y:%02x P:%02x SP:%02x CYC:%d\n",
cpu->pc, op,
(cpu->addr >> 8) & 0xff,
(cpu->addr & 0xff),
cpu->opcode->name,
cpu->regs.a, cpu->regs.x, cpu->regs.y, cpu->flags.flags, cpu->regs.sp,
cpu->clock
);
}
void cpu_reset(cpu_t *cpu)
{
cpu->pc = cpu_read_word(cpu, RESET_VEC);
cpu->halted = false;
cpu->soft_interrupt = false;
cpu->flags.flags = 0x24;
fprintf(stderr, "CPU: RESET. PC=%04x\n", cpu->pc);
}
#ifndef __CPU_H__
#define __CPU_H__
#include <stdbool.h>
#include <sys/types.h>
#define STACK_BASE (0x100)
#define NMI_VEC 0xfffa
#define RESET_VEC 0xfffc
#define IRQ_VEC 0xfffe
typedef enum {
AM_Implied=0, AM_Immediate, AM_ZeroPage, AM_ZeroPageX, AM_ZeroPageY, AM_Absolute, AM_AbsoluteX,
AM_AbsoluteY, AM_Indirect, AM_IndirectX, AM_IndirectY, AM_Relative, AM_Accumulator
} addr_mode_t;
typedef enum { R_A, R_X, R_Y, R_SP, R_N } op_reg_t;
typedef union {
struct { uint8_t a,x,y,sp; };
uint8_t regs[4];
} regs_t;
typedef union {
struct {
uint8_t c:1; // carry (0)
uint8_t z:1; // zero
uint8_t i:1; // interrupt disable
uint8_t d:1; // decimal mode
uint8_t b:1; // break
uint8_t unused:1; // unused
uint8_t v:1; // overflow
uint8_t n:1; // negative (7)
};
uint8_t flags:8;
} cpu_flags_t;
typedef uint8_t (*bus_read_byte)(const uint16_t addr);
typedef void (*bus_write_byte)(const uint16_t addr, const uint8_t val);
typedef struct _cpu_bus_t {
bus_read_byte read;
bus_write_byte write;
} cpu_bus_t;
typedef struct _cpu_t {
uint16_t pc;
regs_t regs;
cpu_flags_t flags;
uint32_t clock;
uint32_t ticks;
uint8_t extra_cycle;
bool halted;
bool soft_interrupt;
uint8_t arg;
cpu_bus_t *bus;
const struct _opcode_t *opcode;
uint16_t addr;
} cpu_t;
typedef void (*op_impl)(struct _cpu_t *);
typedef struct _opcode_t {
const char *name;
const addr_mode_t amode;
const op_reg_t reg;
const uint8_t size;
const uint8_t cycles;
const bool extra_cycle;
const op_impl fn;
} opcode_t;
void cpu_reset(cpu_t *);
cpu_t *cpu_init(cpu_bus_t *);
bool cpu_tick(cpu_t *);
void cpu_dump_state(const cpu_t *, const uint8_t);
void cpu_nmi(cpu_t *);
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment