mirror of
https://github.com/rkujawa/rk65c02.git
synced 2024-12-13 01:29:57 +00:00
327 lines
6.6 KiB
C
327 lines
6.6 KiB
C
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#include "bus.h"
|
|
#include "rk65c02.h"
|
|
#include "65c02isa.h"
|
|
#include "instruction.h"
|
|
|
|
instruction_t
|
|
instruction_fetch(bus_t *b, uint16_t addr)
|
|
{
|
|
instruction_t i;
|
|
instrdef_t id;
|
|
|
|
i.opcode = bus_read_1(b, addr);
|
|
id = instruction_decode(i.opcode);
|
|
|
|
//assert(i.def.opcode != OP_UNIMPL);
|
|
|
|
/* handle operands */
|
|
switch (id.mode) {
|
|
case IMMEDIATE:
|
|
case ZP:
|
|
case ZPX:
|
|
case ZPY:
|
|
case IZP:
|
|
case IZPX:
|
|
case IZPY:
|
|
case RELATIVE:
|
|
i.op1 = bus_read_1(b, addr+1);
|
|
break;
|
|
case ABSOLUTE:
|
|
case ABSOLUTEX:
|
|
case ABSOLUTEY:
|
|
case IABSOLUTE:
|
|
case IABSOLUTEX:
|
|
i.op1 = bus_read_1(b, addr+1);
|
|
i.op2 = bus_read_1(b, addr+2);
|
|
break;
|
|
case IMPLIED:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/*void
|
|
instruction_execute(rk65c02emu_t *e, instruction_t *i)
|
|
{
|
|
id.emul();
|
|
e->regs.PC += id.size;
|
|
}*/
|
|
|
|
void
|
|
instruction_print(instruction_t *i)
|
|
{
|
|
instrdef_t id;
|
|
|
|
id = instruction_decode(i->opcode);
|
|
switch (id.mode) {
|
|
case IMPLIED:
|
|
printf("%s", id.mnemonic);
|
|
break;
|
|
case ACCUMULATOR:
|
|
printf("%s A", id.mnemonic);
|
|
break;
|
|
case IMMEDIATE:
|
|
printf("%s #%X", id.mnemonic, i->op1);
|
|
break;
|
|
case ZP:
|
|
printf("%s %X", id.mnemonic, i->op1);
|
|
break;
|
|
case ZPX:
|
|
printf("%s %X,X", id.mnemonic, i->op1);
|
|
break;
|
|
case ZPY:
|
|
printf("%s %X,Y", id.mnemonic, i->op1);
|
|
break;
|
|
case IZP:
|
|
printf("%s (%X)", id.mnemonic, i->op1);
|
|
break;
|
|
case IZPX:
|
|
printf("%s (%X,X)", id.mnemonic, i->op1);
|
|
break;
|
|
case IZPY:
|
|
printf("%s (%X),Y", id.mnemonic, i->op1);
|
|
break;
|
|
case ZPR:
|
|
printf("%s %X,%X", id.mnemonic, i->op1, i->op2);
|
|
break;
|
|
case ABSOLUTE:
|
|
printf("%s %02X%02X", id.mnemonic, i->op2, i->op1);
|
|
break;
|
|
case ABSOLUTEX:
|
|
printf("%s %02X%02X,X", id.mnemonic, i->op2, i->op1);
|
|
break;
|
|
case ABSOLUTEY:
|
|
printf("%s %02X%02X,Y", id.mnemonic, i->op2, i->op1);
|
|
break;
|
|
case IABSOLUTE:
|
|
printf("%s (%02X%02X)", id.mnemonic, i->op2, i->op1);
|
|
break;
|
|
case IABSOLUTEX:
|
|
printf("%s (%02X%02X,X)", id.mnemonic, i->op2, i->op1);
|
|
break;
|
|
case RELATIVE:
|
|
printf("%s %02X%02X", id.mnemonic, i->op2, i->op1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
disassemble(bus_t *b, uint16_t addr)
|
|
{
|
|
instruction_t i;
|
|
instrdef_t id;
|
|
|
|
i = instruction_fetch(b, addr);
|
|
id = instruction_decode(i.opcode);
|
|
|
|
printf("%X:\t", addr);
|
|
instruction_print(&i);
|
|
printf("\t\t// ");
|
|
|
|
if (id.size == 1)
|
|
printf("%X", id.opcode);
|
|
else if (id.size == 2)
|
|
printf("%X %X", id.opcode, i.op1);
|
|
else if (id.size == 3)
|
|
printf("%X %X %X", id.opcode, i.op1, i.op2);
|
|
printf("\n");
|
|
}
|
|
|
|
instrdef_t
|
|
instruction_decode(uint8_t opcode)
|
|
{
|
|
instrdef_t id;
|
|
|
|
id = instrs[opcode];
|
|
|
|
return id;
|
|
}
|
|
|
|
void
|
|
instruction_status_adjust_zero(rk65c02emu_t *e, uint8_t regval)
|
|
{
|
|
if (regval == 0)
|
|
e->regs.P |= P_ZERO;
|
|
else
|
|
e->regs.P &= ~P_ZERO;
|
|
}
|
|
|
|
void
|
|
instruction_status_adjust_negative(rk65c02emu_t *e, uint8_t regval)
|
|
{
|
|
if (regval & NEGATIVE)
|
|
e->regs.P |= P_NEGATIVE;
|
|
else
|
|
e->regs.P &= ~P_NEGATIVE;
|
|
}
|
|
|
|
void
|
|
instruction_data_write_1(rk65c02emu_t *e, instrdef_t *id, instruction_t *i, uint8_t val)
|
|
{
|
|
uint16_t iaddr;
|
|
|
|
switch (id->mode) {
|
|
case ZP:
|
|
bus_write_1(e->bus, i->op1, val);
|
|
break;
|
|
case ZPX:
|
|
/* XXX: wraps around zero page? */
|
|
bus_write_1(e->bus, i->op1 + e->regs.X, val);
|
|
break;
|
|
case ZPY:
|
|
bus_write_1(e->bus, i->op1 + e->regs.Y, val);
|
|
break;
|
|
case IZP:
|
|
iaddr = bus_read_1(e->bus, i->op1);
|
|
iaddr |= (bus_read_1(e->bus, i->op1 + 1) << 8);
|
|
bus_write_1(e->bus, iaddr, val);
|
|
break;
|
|
case ABSOLUTE:
|
|
bus_write_1(e->bus, i->op1 + (i->op2 << 8), val);
|
|
break;
|
|
case IZPX:
|
|
/* XXX */
|
|
iaddr = bus_read_1(e->bus, i->op1 + e->regs.X);
|
|
iaddr |= (bus_read_1(e->bus, i->op1 + e->regs.X + 1) << 8);
|
|
bus_write_1(e->bus, iaddr, val);
|
|
break;
|
|
case IZPY:
|
|
/* XXX */
|
|
iaddr = bus_read_1(e->bus, i->op1);
|
|
iaddr |= (bus_read_1(e->bus, i->op1 + 1) << 8);
|
|
bus_write_1(e->bus, iaddr, val + e->regs.Y);
|
|
break;
|
|
case ABSOLUTEX:
|
|
bus_write_1(e->bus, (i->op1 + (i->op2 << 8)) + e->regs.X, val);
|
|
break;
|
|
case ABSOLUTEY:
|
|
bus_write_1(e->bus, (i->op1 + (i->op2 << 8)) + e->regs.Y, val);
|
|
break;
|
|
case ZPR:
|
|
/* XXX */
|
|
case ACCUMULATOR:
|
|
case IMMEDIATE:
|
|
case RELATIVE:
|
|
case IABSOLUTE:
|
|
case IABSOLUTEX:
|
|
/*
|
|
* IABSOLUTE, IABSOLUTEX, RELATIVE are only for branches
|
|
* and jumps. They do not read or write anything, only modify
|
|
* PC which is handled within emulation of a given opcode.
|
|
*/
|
|
default:
|
|
printf("unhandled addressing mode for opcode %x\n",
|
|
i->opcode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint8_t
|
|
instruction_data_read_1(rk65c02emu_t *e, instrdef_t *id, instruction_t *i)
|
|
{
|
|
uint8_t rv; /* data read from the bus */
|
|
// uint8_t ziaddr; /* indirect zero page address */
|
|
uint16_t iaddr; /* indirect address */
|
|
|
|
rv = 0;
|
|
|
|
switch (id->mode) {
|
|
case ACCUMULATOR:
|
|
rv = e->regs.A;
|
|
break;
|
|
case IMMEDIATE:
|
|
rv = i->op1;
|
|
break;
|
|
case ZP:
|
|
rv = bus_read_1(e->bus, i->op1);
|
|
break;
|
|
case ZPX:
|
|
/* XXX: wraps around zero page? */
|
|
rv = bus_read_1(e->bus, i->op1 + e->regs.X);
|
|
break;
|
|
case ZPY:
|
|
rv = bus_read_1(e->bus, i->op1 + e->regs.Y);
|
|
break;
|
|
case IZP:
|
|
iaddr = bus_read_1(e->bus, i->op1);
|
|
iaddr |= (bus_read_1(e->bus, i->op1 + 1) << 8);
|
|
rv = bus_read_1(e->bus, iaddr);
|
|
break;
|
|
case IZPX:
|
|
/* XXX: what about page wraps / roll over */
|
|
iaddr = bus_read_1(e->bus, i->op1 + e->regs.X);
|
|
iaddr |= (bus_read_1(e->bus, i->op1 + e->regs.X + 1) << 8);
|
|
rv = bus_read_1(e->bus, iaddr);
|
|
break;
|
|
case IZPY:
|
|
/* XXX: what about page wraps / roll over */
|
|
iaddr = bus_read_1(e->bus, i->op1);
|
|
iaddr |= (bus_read_1(e->bus, i->op1 + 1) << 8);
|
|
rv = bus_read_1(e->bus, iaddr) + e->regs.Y;
|
|
break;
|
|
case ABSOLUTE:
|
|
rv = bus_read_1(e->bus, i->op1 + (i->op2 << 8));
|
|
break;
|
|
case ABSOLUTEX:
|
|
rv = bus_read_1(e->bus, (i->op1 + (i->op2 << 8)) + e->regs.X);
|
|
break;
|
|
case ABSOLUTEY:
|
|
rv = bus_read_1(e->bus, (i->op1 + (i->op2 << 8)) + e->regs.Y);
|
|
break;
|
|
case ZPR:
|
|
/* XXX */
|
|
case IABSOLUTE:
|
|
case IABSOLUTEX:
|
|
case RELATIVE:
|
|
/*
|
|
* IABSOLUTE, IABSOLUTEX, RELATIVE are only for branches
|
|
* and jumps. They do not read or write anything, only modify
|
|
* PC which is handled within emulation of a given opcode.
|
|
*/
|
|
default:
|
|
printf("unhandled addressing mode for opcode %x\n",
|
|
i->opcode);
|
|
break;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* put value onto the stack */
|
|
void
|
|
stack_push(rk65c02emu_t *e, uint8_t val)
|
|
{
|
|
bus_write_1(e->bus, STACK_START+e->regs.SP, val);
|
|
e->regs.SP--;
|
|
}
|
|
|
|
/* pull/pop value from the stack */
|
|
uint8_t
|
|
stack_pop(rk65c02emu_t *e)
|
|
{
|
|
uint8_t val;
|
|
|
|
e->regs.SP++;
|
|
val = bus_read_1(e->bus, STACK_START+e->regs.SP);
|
|
|
|
return val;
|
|
}
|
|
|
|
/* increment program counter based on instruction size (opcode + operands) */
|
|
void
|
|
program_counter_increment(rk65c02emu_t *e, instrdef_t *id)
|
|
{
|
|
e->regs.PC += id->size;
|
|
}
|
|
|