summaryrefslogtreecommitdiff
path: root/6502/src/6502.c
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2026-07-11 00:34:23 -0400
committerLLLL Colonq <llll@colonq>2026-07-11 00:34:23 -0400
commitca8daa96caed9d53db95336bad917d2e03e7b00f (patch)
tree39c7ec19c9beba0932b3240d5033e2fbfd4e2f6c /6502/src/6502.c
parentb6f00172b4e7045a14cebba0a7d4011528a28741 (diff)
6502: All tests pass!
Diffstat (limited to '6502/src/6502.c')
-rw-r--r--6502/src/6502.c200
1 files changed, 130 insertions, 70 deletions
diff --git a/6502/src/6502.c b/6502/src/6502.c
index 54013ae..eb0f2c0 100644
--- a/6502/src/6502.c
+++ b/6502/src/6502.c
@@ -2,6 +2,7 @@
#include <lcq/6502/ins.h>
#include <stdlib.h>
+#include <stdio.h>
static u8 basic_read_handler(void *data, u16 addr) {
return ((u8 *) data)[addr];
@@ -42,79 +43,94 @@ static u16 amode_zeropage(l6502_emu *e) {
return (u16) l6502_read(e, e->pc++);
}
static u16 amode_zeropage_x(l6502_emu *e) {
- return (u16) (l6502_read(e, e->pc++) + e->regs[L6502_REG_X]);
+ return (u16) (u8) (l6502_read(e, e->pc++) + e->regs[L6502_REG_X]);
+}
+static u16 amode_zeropage_y(l6502_emu *e) {
+ return (u16) (u8) (l6502_read(e, e->pc++) + e->regs[L6502_REG_Y]);
}
static u16 amode_indirect_zeropage_x(l6502_emu *e) {
- return l6502_read_addr(e, l6502_read(e, e->pc++) + (u16) e->regs[L6502_REG_X]);
+ return l6502_read_addr_wrap_page(e, (u8) (l6502_read(e, e->pc++) + e->regs[L6502_REG_X]));
}
static u16 amode_indirect_zeropage_y(l6502_emu *e) {
- return l6502_read_addr(e, l6502_read(e, e->pc++)) + (u16) e->regs[L6502_REG_Y];
+ return l6502_read_addr_wrap_page(e, l6502_read(e, e->pc++)) + (u16) e->regs[L6502_REG_Y];
}
-void l6502_emulate(l6502_emu *e) {
+const char *l6502_emulate(l6502_emu *e) {
u8 ins = l6502_read(e, e->pc++);
u8 val = 0;
u16 res = 0;
bool set_flags = true;
+ bool set_carry = false;
bool set_result = true;
l6502_reg set_reg = L6502_REG__MARKER;
u16 addr = 0;
+ const char *nm = NULL;
+
switch (ins) { /* identify some special-case instructions */
case BRK:
- l6502_push_addr(e, e->pc);
- l6502_push(e, e->regs[L6502_REG_FLAGS]);
- e->pc = l6502_read_addr(e, 0xfffe) - 1;
- l6502_flag_set(e, L6502_FLAG_BREAK_COMMAND, true);
- break;
- case PHP: l6502_push(e, e->regs[L6502_REG_FLAGS]); break;
- case CLC: l6502_flag_set(e, L6502_FLAG_CARRY, false); break;
- case JSR:
- addr = l6502_read_addr(e, e->pc);
+ nm = "brk";
l6502_push_addr(e, e->pc + 1);
- e->pc = addr;
- break;
- case PLP: e->regs[L6502_REG_FLAGS] = l6502_pop(e); break;
- case SEC: l6502_flag_set(e, L6502_FLAG_CARRY, true); break;
+ l6502_push(e, e->regs[L6502_REG_FLAGS] | L6502_FLAG_BREAK_COMMAND);
+ e->pc = l6502_read_addr(e, 0xfffe);
+ l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, true);
+ return nm;
+ case PHP: nm = "php"; l6502_push(e, e->regs[L6502_REG_FLAGS] | L6502_FLAG_BREAK_COMMAND | L6502_FLAG_UNUSED); return nm;
+ case CLC: nm = "clc"; l6502_flag_set(e, L6502_FLAG_CARRY, false); return nm;
+ case JSR: {
+ nm = "jsr";
+ u16 alo = l6502_read(e, e->pc);
+ l6502_push_addr(e, e->pc + 1);
+ u16 ahi = l6502_read(e, e->pc + 1);
+ e->pc = ahi << 8 | alo;
+ return nm;
+ }
+ case PLP: nm = "plp"; e->regs[L6502_REG_FLAGS] = l6502_pop(e) & (u8) ~L6502_FLAG_BREAK_COMMAND; return nm;
+ case SEC: nm = "sec"; l6502_flag_set(e, L6502_FLAG_CARRY, true); return nm;
case RTI:
- e->regs[L6502_REG_FLAGS] = l6502_pop(e);
+ nm = "rti";
+ e->regs[L6502_REG_FLAGS] = l6502_pop(e) & (u8) ~L6502_FLAG_BREAK_COMMAND;
e->pc = l6502_pop_addr(e);
- break;
- case PHA: l6502_push(e, e->regs[L6502_REG_A]); break;
+ return nm;
+ case PHA: nm = "pha"; l6502_push(e, e->regs[L6502_REG_A]); return nm;
case JMP_ABSOLUTE:
+ nm = "jmp";
addr = l6502_read_addr(e, e->pc);
e->pc = addr;
- break;
- case CLI: l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, false); break;
- case RTS: e->pc = l6502_pop_addr(e) + 1; break;
- case PLA: e->regs[L6502_REG_A] = l6502_pop(e); break;
+ return nm;
+ case CLI: nm = "cli"; l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, false); return nm;
+ case RTS: nm = "rts"; e->pc = l6502_pop_addr(e) + 1; return nm;
+ case PLA: nm = "pla"; e->regs[L6502_REG_A] = l6502_pop(e); flags_check_zn(e, e->regs[L6502_REG_A]); return nm;
case JMP_INDIRECT:
- addr = l6502_read_addr(e, l6502_read_addr(e, e->pc));
+ nm = "jmp";
+ addr = l6502_read_addr_wrap_page(e, l6502_read_addr(e, e->pc));
e->pc = addr;
- break;
- case SEI: l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, true); break;
- case DEY: e->regs[L6502_REG_Y] -= 1; flags_check_zn(e, e->regs[L6502_REG_Y]); break;
- case TXA: e->regs[L6502_REG_A] = e->regs[L6502_REG_X]; flags_check_zn(e, e->regs[L6502_REG_A]); break;
- case TYA: e->regs[L6502_REG_A] = e->regs[L6502_REG_Y]; flags_check_zn(e, e->regs[L6502_REG_A]); break;
- case TXS: e->regs[L6502_REG_SP] = e->regs[L6502_REG_X]; break; /* note TXS does not set flags */
- case TAY: e->regs[L6502_REG_Y] = e->regs[L6502_REG_A]; flags_check_zn(e, e->regs[L6502_REG_Y]); break;
- case TAX: e->regs[L6502_REG_X] = e->regs[L6502_REG_A]; flags_check_zn(e, e->regs[L6502_REG_X]); break;
- case CLV: l6502_flag_set(e, L6502_FLAG_OVERFLOW, false); break;
- case TSX: e->regs[L6502_REG_X] = e->regs[L6502_REG_SP]; flags_check_zn(e, e->regs[L6502_REG_X]); break;
- case INY: e->regs[L6502_REG_Y] += 1; flags_check_zn(e, e->regs[L6502_REG_Y]); break;
- case DEX: e->regs[L6502_REG_X] -= 1; flags_check_zn(e, e->regs[L6502_REG_X]); break;
- case CLD: l6502_flag_set(e, L6502_FLAG_DECIMAL, false); break;
- case INX: e->regs[L6502_REG_X] += 1; flags_check_zn(e, e->regs[L6502_REG_X]); break;
- case NOP: break;
- case SED: l6502_flag_set(e, L6502_FLAG_DECIMAL, true); break;
+ return nm;
+ case SEI: nm = "sei"; l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, true); return nm;
+ case DEY: nm = "dey"; e->regs[L6502_REG_Y] -= 1; flags_check_zn(e, e->regs[L6502_REG_Y]); return nm;
+ case TXA: nm = "txa"; e->regs[L6502_REG_A] = e->regs[L6502_REG_X]; flags_check_zn(e, e->regs[L6502_REG_A]); return nm;
+ case TYA: nm = "tya"; e->regs[L6502_REG_A] = e->regs[L6502_REG_Y]; flags_check_zn(e, e->regs[L6502_REG_A]); return nm;
+ case TXS: nm = "txs"; e->regs[L6502_REG_SP] = e->regs[L6502_REG_X]; return nm; /* note TXS does not set flags */
+ case TAY: nm = "tay"; e->regs[L6502_REG_Y] = e->regs[L6502_REG_A]; flags_check_zn(e, e->regs[L6502_REG_Y]); return nm;
+ case TAX: nm = "tax"; e->regs[L6502_REG_X] = e->regs[L6502_REG_A]; flags_check_zn(e, e->regs[L6502_REG_X]); return nm;
+ case CLV: nm = "clv"; l6502_flag_set(e, L6502_FLAG_OVERFLOW, false); return nm;
+ case TSX: nm = "tsx"; e->regs[L6502_REG_X] = e->regs[L6502_REG_SP]; flags_check_zn(e, e->regs[L6502_REG_X]); return nm;
+ case INY: nm = "iny"; e->regs[L6502_REG_Y] += 1; flags_check_zn(e, e->regs[L6502_REG_Y]); return nm;
+ case DEX: nm = "dex"; e->regs[L6502_REG_X] -= 1; flags_check_zn(e, e->regs[L6502_REG_X]); return nm;
+ case CLD: nm = "cld"; l6502_flag_set(e, L6502_FLAG_DECIMAL, false); return nm;
+ case INX: nm = "inx"; e->regs[L6502_REG_X] += 1; flags_check_zn(e, e->regs[L6502_REG_X]); return nm;
+ case NOP: nm = "nop"; return nm;
+ case SED: nm = "sed"; l6502_flag_set(e, L6502_FLAG_DECIMAL, true); return nm;
}
+
if ((ins & 0b11111) == 0b10000) { /* identify conditional branches */
l6502_flag flags[4] = { L6502_FLAG_NEGATIVE, L6502_FLAG_OVERFLOW, L6502_FLAG_CARRY, L6502_FLAG_ZERO };
u8 off = l6502_read(e, e->pc++);
if (l6502_flag_get(e, flags[(ins >> 6) & 0b11]) == ((ins >> 5) & 0b1)) {
e->pc = (u16) ((i16) e->pc + (i16) (i8) off);
}
- return;
+ return "jcc";
}
+
switch (ins & 0b11) { /* identify instructions in common forms */
case 0b01: { /* group one */
set_reg = L6502_REG_A;
@@ -130,24 +146,38 @@ void l6502_emulate(l6502_emu *e) {
}
val = l6502_read(e, addr);
switch ((ins >> 5) & 0b111) {
- case G1_OPCODE_ORA: res = (u16) e->regs[L6502_REG_A] | (u16) val; break;
- case G1_OPCODE_AND: res = (u16) e->regs[L6502_REG_A] & (u16) val; break;
- case G1_OPCODE_EOR: res = (u16) e->regs[L6502_REG_A] ^ (u16) val; break;
- case G1_OPCODE_ADC:
- res = (u16) e->regs[L6502_REG_A] + (u16) val;
- res += (u16) l6502_flag_get(e, L6502_FLAG_CARRY);
- l6502_flag_set(e, L6502_FLAG_OVERFLOW, ((res >> 8) != 0) != ((res >> 7) & 0b1));
+ case G1_OPCODE_ORA: nm = "ora"; res = (u16) e->regs[L6502_REG_A] | (u16) val; break;
+ case G1_OPCODE_AND: nm = "and"; res = (u16) e->regs[L6502_REG_A] & (u16) val; break;
+ case G1_OPCODE_EOR: nm = "eor"; res = (u16) e->regs[L6502_REG_A] ^ (u16) val; break;
+ case G1_OPCODE_ADC: {
+ nm = "adc";
+ set_carry = true;
+ i8 a0 = (i8) e->regs[L6502_REG_A];
+ i8 a1 = (i8) val;
+ i8 a2 = (i8) l6502_flag_get(e, L6502_FLAG_CARRY);
+ res = (u16) (u8) a0 + (u16) (u8) a1; res += (u16) (u8) a2;
+ l6502_flag_set(e, L6502_FLAG_OVERFLOW, ((a0 >= 0) == (a1 >= 0)) && ((a0 >= 0) != ((i8) res >= 0)));
break;
- case G1_OPCODE_STA: l6502_write(e, addr, e->regs[L6502_REG_A]); set_flags = set_result = false; break;
- case G1_OPCODE_LDA: res = (u16) l6502_read(e, addr); break;
+ }
+ case G1_OPCODE_STA: nm = "sta"; l6502_write(e, addr, e->regs[L6502_REG_A]); set_flags = set_result = false; break;
+ case G1_OPCODE_LDA: nm = "lda"; res = (u16) l6502_read(e, addr); break;
case G1_OPCODE_CMP:
+ nm = "cmp";
set_result = false;
- res = (u16) e->regs[L6502_REG_A] - (u16) val;
+ set_carry = true;
+ res = (u16) (u8) (e->regs[L6502_REG_A] - val);
+ if (e->regs[L6502_REG_A] >= val) res |= 1 << 8;
break;
case G1_OPCODE_SBC:
- res = (u16) e->regs[L6502_REG_A] - (u16) val;
- res -= (u16) (1 - l6502_flag_get(e, L6502_FLAG_CARRY));
- l6502_flag_set(e, L6502_FLAG_OVERFLOW, ((res >> 8) != 0) != ((res >> 7) & 0b1));
+ nm = "sbc";
+ set_carry = true;
+ i8 a0 = (i8) e->regs[L6502_REG_A];
+ i8 a1 = (i8) val;
+ i8 a2 = (i8) (1 - l6502_flag_get(e, L6502_FLAG_CARRY));
+ res = (u16) (u8) a0 - (u16) (u8) a1; res -= (u16) (u8) a2;
+ res &= 0xff;
+ if ((u8) a0 >= (u8) a1 + (u8) a2) res |= 1 << 8;
+ l6502_flag_set(e, L6502_FLAG_OVERFLOW, !((a0 >= 0) == (a1 >= 0)) && ((a0 >= 0) != ((i8) res >= 0)));
break;
}
break;
@@ -158,26 +188,48 @@ void l6502_emulate(l6502_emu *e) {
case G2_ADDRESSING_MODE_ZEROPAGE: addr = amode_zeropage(e); break;
case G2_ADDRESSING_MODE_ACCUMULATOR: addr = (u16) e->regs[L6502_REG_A]; set_reg = L6502_REG_A; break;
case G2_ADDRESSING_MODE_ABSOLUTE: addr = amode_absolute(e); break;
- case G2_ADDRESSING_MODE_ZEROPAGE_X: addr = amode_zeropage_x(e); break;
- case G2_ADDRESSING_MODE_ABSOLUTE_X: addr = amode_absolute_x(e); break;
+ case G2_ADDRESSING_MODE_ZEROPAGE_X:
+ switch ((ins >> 5) & 0b111) {
+ case G2_OPCODE_LDX: case G2_OPCODE_STX: addr = amode_zeropage_y(e); break;
+ default: addr = amode_zeropage_x(e); break;
+ }
+ break;
+ case G2_ADDRESSING_MODE_ABSOLUTE_X:
+ switch ((ins >> 5) & 0b111) {
+ case G2_OPCODE_LDX: case G2_OPCODE_STX: addr = amode_absolute_y(e); break;
+ default: addr = amode_absolute_x(e); break;
+ }
+ break;
}
- val = set_reg ? (u8) addr : l6502_read(e, addr);
+ val = set_reg != L6502_REG__MARKER ? (u8) addr : l6502_read(e, addr);
switch ((ins >> 5) & 0b111) {
- case G2_OPCODE_ASL: res = ((u16) val) << 1; break;
- case G2_OPCODE_ROL: res = ((u16) val) << 1 | (u16) l6502_flag_get(e, L6502_FLAG_CARRY); break;
+ case G2_OPCODE_ASL:
+ nm = "asl";
+ set_carry = true;
+ res = ((u16) val) << 1;
+ break;
+ case G2_OPCODE_ROL:
+ nm = "rol";
+ set_carry = true;
+ res = ((u16) val) << 1 | (u16) l6502_flag_get(e, L6502_FLAG_CARRY);
+ break;
case G2_OPCODE_LSR:
+ nm = "lsr";
+ set_carry = true;
res = ((u16) val) >> 1
| (u16) (val & 0b1) << 8; /* place the previous low bit in carry */
break;
case G2_OPCODE_ROR:
+ nm = "ror";
+ set_carry = true;
res = ((u16) val) >> 1
| (u16) l6502_flag_get(e, L6502_FLAG_CARRY) << 7
| (u16) (val & 0b1) << 8; /* same as above */
break;
- case G2_OPCODE_STX: l6502_write(e, addr, e->regs[L6502_REG_X]); set_flags = set_result = false; break;
- case G2_OPCODE_LDX: res = (u16) l6502_read(e, addr); set_reg = L6502_REG_X; break;
- case G2_OPCODE_DEC: res = (u16) val - 1; break;
- case G2_OPCODE_INC: res = (u16) val + 1; break;
+ case G2_OPCODE_STX: nm = "stx"; l6502_write(e, addr, e->regs[L6502_REG_X]); set_flags = set_result = false; break;
+ case G2_OPCODE_LDX: nm = "ldx"; res = (u16) l6502_read(e, addr); set_reg = L6502_REG_X; break;
+ case G2_OPCODE_DEC: nm = "dec"; res = (u16) val - 1; break;
+ case G2_OPCODE_INC: nm = "inc"; res = (u16) val + 1; break;
}
break;
}
@@ -192,31 +244,39 @@ void l6502_emulate(l6502_emu *e) {
val = l6502_read(e, addr);
switch ((ins >> 5) & 0b111) {
case G3_OPCODE_BIT:
+ nm = "bit";
set_flags = set_result = false;
l6502_flag_set(e, L6502_FLAG_ZERO, (e->regs[L6502_REG_A] & val) == 0);
l6502_flag_set(e, L6502_FLAG_OVERFLOW, (val >> 6) & 0b1);
l6502_flag_set(e, L6502_FLAG_NEGATIVE, (val >> 7) & 0b1);
break;
- case G3_OPCODE_STY: l6502_write(e, addr, e->regs[L6502_REG_Y]); set_flags = set_result = false; break;
- case G3_OPCODE_LDY: res = (u16) l6502_read(e, addr); set_reg = L6502_REG_Y; break;
+ case G3_OPCODE_STY: nm = "sty"; l6502_write(e, addr, e->regs[L6502_REG_Y]); set_flags = set_result = false; break;
+ case G3_OPCODE_LDY: nm = "ldy"; res = (u16) l6502_read(e, addr); set_reg = L6502_REG_Y; break;
case G3_OPCODE_CPY:
+ nm = "cpy";
set_result = false;
- res = (u16) e->regs[L6502_REG_Y] - (u16) val;
+ set_carry = true;
+ res = (u16) (u8) (e->regs[L6502_REG_Y] - val);
+ if (e->regs[L6502_REG_Y] >= val) res |= 1 << 8;
break;
case G3_OPCODE_CPX:
+ nm = "cpx";
set_result = false;
- res = (u16) e->regs[L6502_REG_X] - (u16) val;
+ set_carry = true;
+ res = (u16) (u8) (e->regs[L6502_REG_X] - val);
+ if (e->regs[L6502_REG_X] >= val) res |= 1 << 8;
break;
}
}
}
if (set_flags) {
- l6502_flag_set(e, L6502_FLAG_ZERO, res == 0);
+ l6502_flag_set(e, L6502_FLAG_ZERO, (res & 0xff) == 0);
l6502_flag_set(e, L6502_FLAG_NEGATIVE, (res >> 7) & 0b1);
- l6502_flag_set(e, L6502_FLAG_CARRY, (res >> 8) != 0);
+ if (set_carry) l6502_flag_set(e, L6502_FLAG_CARRY, (res >> 8) != 0);
}
if (set_result) {
if (set_reg != L6502_REG__MARKER) e->regs[set_reg] = (u8) res;
else l6502_write(e, addr, (u8) res);
}
+ return nm;
}