2019-09-23 13:28:05 +00:00
|
|
|
/*
|
|
|
|
* SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
*
|
|
|
|
* rk65c02
|
2021-01-25 00:09:01 +00:00
|
|
|
* Copyright (C) 2017-2021 Radoslaw Kujawa
|
2019-09-23 13:28:05 +00:00
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, version 3 of the License.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2017-01-17 13:29:20 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdbool.h>
|
2018-03-23 12:37:07 +00:00
|
|
|
#include <errno.h>
|
2017-01-17 13:29:20 +00:00
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2018-03-26 10:36:47 +00:00
|
|
|
#include <gc/gc.h>
|
|
|
|
|
2017-01-17 13:29:20 +00:00
|
|
|
#include "bus.h"
|
2017-01-18 16:18:19 +00:00
|
|
|
#include "rk65c02.h"
|
2017-01-17 13:29:20 +00:00
|
|
|
#include "65c02isa.h"
|
2018-03-22 14:08:51 +00:00
|
|
|
#include "log.h"
|
2017-01-17 13:29:20 +00:00
|
|
|
#include "instruction.h"
|
|
|
|
|
|
|
|
instruction_t
|
|
|
|
instruction_fetch(bus_t *b, uint16_t addr)
|
|
|
|
{
|
|
|
|
instruction_t i;
|
2017-01-18 16:18:19 +00:00
|
|
|
instrdef_t id;
|
2017-01-17 13:29:20 +00:00
|
|
|
|
2017-01-18 16:18:19 +00:00
|
|
|
i.opcode = bus_read_1(b, addr);
|
|
|
|
id = instruction_decode(i.opcode);
|
2017-01-17 13:29:20 +00:00
|
|
|
|
2017-01-18 13:37:24 +00:00
|
|
|
//assert(i.def.opcode != OP_UNIMPL);
|
2017-01-17 13:29:20 +00:00
|
|
|
|
|
|
|
/* handle operands */
|
2017-01-18 16:18:19 +00:00
|
|
|
switch (id.mode) {
|
2017-01-17 13:29:20 +00:00
|
|
|
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:
|
2017-02-13 08:37:11 +00:00
|
|
|
case ZPR:
|
2017-01-17 13:29:20 +00:00
|
|
|
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_print(instruction_t *i)
|
|
|
|
{
|
2018-03-23 12:37:07 +00:00
|
|
|
char *str;
|
|
|
|
|
|
|
|
str = instruction_string_get(i);
|
|
|
|
|
|
|
|
printf("%s", str);
|
|
|
|
}
|
|
|
|
|
|
|
|
char *
|
|
|
|
instruction_string_get(instruction_t *i)
|
|
|
|
{
|
|
|
|
#define INSTR_STR_LEN 16
|
2017-01-18 16:18:19 +00:00
|
|
|
instrdef_t id;
|
2018-03-23 12:37:07 +00:00
|
|
|
char *str;
|
|
|
|
|
2018-03-26 10:36:47 +00:00
|
|
|
str = GC_MALLOC(INSTR_STR_LEN);
|
2018-08-05 21:45:44 +00:00
|
|
|
assert(str != NULL);
|
2018-03-23 12:37:07 +00:00
|
|
|
memset(str, 0, INSTR_STR_LEN);
|
2017-01-18 16:18:19 +00:00
|
|
|
|
|
|
|
id = instruction_decode(i->opcode);
|
|
|
|
switch (id.mode) {
|
2017-01-17 13:29:20 +00:00
|
|
|
case IMPLIED:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s", id.mnemonic);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
2017-01-18 10:12:37 +00:00
|
|
|
case ACCUMULATOR:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s A", id.mnemonic);
|
2017-01-18 10:12:37 +00:00
|
|
|
break;
|
2017-01-17 13:29:20 +00:00
|
|
|
case IMMEDIATE:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s #%#02x", id.mnemonic, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case ZP:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s %#02x", id.mnemonic, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case ZPX:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s %#02x,X", id.mnemonic, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case ZPY:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s %#02x,Y", id.mnemonic, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case IZP:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s (%#02x)", id.mnemonic, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case IZPX:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s (%#02x,X)", id.mnemonic, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case IZPY:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s (%#02x),Y", id.mnemonic, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
2017-01-23 11:17:06 +00:00
|
|
|
case ZPR:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s %#02x,%#02x", id.mnemonic, i->op1, i->op2);
|
2017-01-23 11:17:06 +00:00
|
|
|
break;
|
2017-01-17 13:29:20 +00:00
|
|
|
case ABSOLUTE:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s %#02x%02x", id.mnemonic, i->op2, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case ABSOLUTEX:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s %#02x%02x,X", id.mnemonic, i->op2, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case ABSOLUTEY:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s %#02x%02x,Y", id.mnemonic, i->op2, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case IABSOLUTE:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s (%#02x%02x)", id.mnemonic, i->op2, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case IABSOLUTEX:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s (%#02x%02x,X)", id.mnemonic, i->op2, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
case RELATIVE:
|
2018-03-23 12:37:07 +00:00
|
|
|
snprintf(str, INSTR_STR_LEN, "%s %#02x", id.mnemonic, i->op1);
|
2017-01-17 13:29:20 +00:00
|
|
|
break;
|
|
|
|
}
|
2018-03-23 12:37:07 +00:00
|
|
|
|
|
|
|
return str;
|
2017-01-17 13:29:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
disassemble(bus_t *b, uint16_t addr)
|
|
|
|
{
|
|
|
|
instruction_t i;
|
2017-01-18 16:18:19 +00:00
|
|
|
instrdef_t id;
|
2017-01-17 13:29:20 +00:00
|
|
|
|
|
|
|
i = instruction_fetch(b, addr);
|
2017-01-18 16:18:19 +00:00
|
|
|
id = instruction_decode(i.opcode);
|
2017-01-17 13:29:20 +00:00
|
|
|
|
|
|
|
printf("%X:\t", addr);
|
|
|
|
instruction_print(&i);
|
2017-01-20 08:46:33 +00:00
|
|
|
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);
|
2017-01-17 13:29:20 +00:00
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
2017-01-18 11:09:14 +00:00
|
|
|
instrdef_t
|
2017-01-18 16:18:19 +00:00
|
|
|
instruction_decode(uint8_t opcode)
|
2017-01-18 11:09:14 +00:00
|
|
|
{
|
|
|
|
instrdef_t id;
|
|
|
|
|
|
|
|
id = instrs[opcode];
|
|
|
|
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2017-01-20 09:25:19 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-01-21 20:43:31 +00:00
|
|
|
void
|
|
|
|
instruction_data_write_1(rk65c02emu_t *e, instrdef_t *id, instruction_t *i, uint8_t val)
|
|
|
|
{
|
2018-04-27 08:34:48 +00:00
|
|
|
if (id->mode == ACCUMULATOR) {
|
2017-01-27 10:03:50 +00:00
|
|
|
e->regs.A = val;
|
2018-04-27 08:34:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (id->mode == IMMEDIATE) {
|
|
|
|
rk65c02_panic(e,
|
|
|
|
"invalid IMMEDIATE addressing mode for opcode %x\n",
|
2018-04-01 19:40:49 +00:00
|
|
|
i->opcode);
|
2018-04-27 08:34:48 +00:00
|
|
|
return;
|
2017-01-21 20:43:31 +00:00
|
|
|
}
|
2018-04-27 08:34:48 +00:00
|
|
|
|
|
|
|
bus_write_1(e->bus, instruction_data_address(e, id, i), val);
|
2017-01-21 20:43:31 +00:00
|
|
|
}
|
|
|
|
|
2017-01-19 09:59:35 +00:00
|
|
|
uint8_t
|
|
|
|
instruction_data_read_1(rk65c02emu_t *e, instrdef_t *id, instruction_t *i)
|
|
|
|
{
|
2018-04-27 08:34:48 +00:00
|
|
|
if (id->mode == ACCUMULATOR)
|
|
|
|
return e->regs.A;
|
|
|
|
else if (id->mode == IMMEDIATE)
|
|
|
|
return i->op1;
|
|
|
|
|
|
|
|
return bus_read_1(e->bus, instruction_data_address(e, id, i));
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t
|
|
|
|
instruction_data_address(rk65c02emu_t *e, instrdef_t *id, instruction_t *i)
|
|
|
|
{
|
|
|
|
uint16_t addr;
|
2017-01-19 09:59:35 +00:00
|
|
|
|
2018-04-27 08:34:48 +00:00
|
|
|
addr = 0;
|
2017-01-19 09:59:35 +00:00
|
|
|
|
|
|
|
switch (id->mode) {
|
|
|
|
case ZP:
|
2017-02-13 17:37:14 +00:00
|
|
|
case ZPR:
|
2018-04-27 08:34:48 +00:00
|
|
|
addr = i->op1;
|
2017-01-19 09:59:35 +00:00
|
|
|
break;
|
|
|
|
case ZPX:
|
2018-04-27 08:34:48 +00:00
|
|
|
addr = ((uint8_t) (i->op1 + e->regs.X));
|
2017-01-19 09:59:35 +00:00
|
|
|
break;
|
|
|
|
case ZPY:
|
2018-04-27 08:34:48 +00:00
|
|
|
addr = i->op1 + e->regs.Y;
|
2017-01-19 09:59:35 +00:00
|
|
|
break;
|
|
|
|
case IZP:
|
2018-04-27 08:34:48 +00:00
|
|
|
addr = bus_read_1(e->bus, i->op1);
|
|
|
|
addr |= (bus_read_1(e->bus, i->op1 + 1) << 8);
|
2017-01-19 09:59:35 +00:00
|
|
|
break;
|
2018-03-31 13:03:22 +00:00
|
|
|
case IZPX: /* Zero Page Indexed Indirect with X */
|
2018-04-27 08:34:48 +00:00
|
|
|
addr = bus_read_1(e->bus, (uint8_t) (i->op1 + e->regs.X));
|
|
|
|
addr |= (bus_read_1(e->bus, (uint8_t) (i->op1 + e->regs.X + 1)) << 8);
|
2017-01-22 23:00:45 +00:00
|
|
|
break;
|
2018-03-31 10:38:45 +00:00
|
|
|
case IZPY: /* Zero Page Indirect Indexed with Y */
|
2018-04-27 08:34:48 +00:00
|
|
|
addr = bus_read_1(e->bus, i->op1);
|
|
|
|
addr |= (bus_read_1(e->bus, i->op1 + 1) << 8);
|
|
|
|
addr += e->regs.Y;
|
2017-01-22 23:00:45 +00:00
|
|
|
break;
|
2017-01-19 09:59:35 +00:00
|
|
|
case ABSOLUTE:
|
2018-04-27 08:34:48 +00:00
|
|
|
addr = i->op1 + (i->op2 << 8);
|
2017-01-22 23:00:45 +00:00
|
|
|
break;
|
2017-01-19 09:59:35 +00:00
|
|
|
case ABSOLUTEX:
|
2018-04-27 08:34:48 +00:00
|
|
|
addr = i->op1 + (i->op2 << 8) + e->regs.X;
|
2017-01-23 09:48:37 +00:00
|
|
|
break;
|
2017-01-19 09:59:35 +00:00
|
|
|
case ABSOLUTEY:
|
2018-04-27 08:34:48 +00:00
|
|
|
addr = i->op1 + (i->op2 << 8) + e->regs.Y;
|
2017-01-23 09:48:37 +00:00
|
|
|
break;
|
2017-01-19 09:59:35 +00:00
|
|
|
case IABSOLUTE:
|
|
|
|
case IABSOLUTEX:
|
2017-01-22 23:00:45 +00:00
|
|
|
case RELATIVE:
|
2017-01-23 09:48:37 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2017-01-19 09:59:35 +00:00
|
|
|
default:
|
2018-04-01 19:40:49 +00:00
|
|
|
rk65c02_panic(e, "unhandled addressing mode for opcode %x\n",
|
|
|
|
i->opcode);
|
2017-01-19 09:59:35 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-04-27 08:34:48 +00:00
|
|
|
return addr;
|
2017-01-19 09:59:35 +00:00
|
|
|
}
|
|
|
|
|
2017-01-20 21:03:03 +00:00
|
|
|
/* 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++;
|
2017-01-20 22:16:02 +00:00
|
|
|
val = bus_read_1(e->bus, STACK_START+e->regs.SP);
|
2017-01-20 21:03:03 +00:00
|
|
|
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2017-01-22 10:07:19 +00:00
|
|
|
/* increment program counter based on instruction size (opcode + operands) */
|
|
|
|
void
|
|
|
|
program_counter_increment(rk65c02emu_t *e, instrdef_t *id)
|
|
|
|
{
|
|
|
|
e->regs.PC += id->size;
|
|
|
|
}
|
|
|
|
|
2017-01-29 12:29:17 +00:00
|
|
|
void
|
|
|
|
program_counter_branch(rk65c02emu_t *e, int8_t boffset)
|
|
|
|
{
|
|
|
|
e->regs.PC += boffset + 2;
|
|
|
|
}
|
|
|
|
|
2017-01-27 19:43:08 +00:00
|
|
|
/* check whether given instruction modify program counter */
|
|
|
|
bool
|
|
|
|
instruction_modify_pc(instrdef_t *id)
|
|
|
|
{
|
|
|
|
return id->modify_pc;
|
|
|
|
}
|
|
|
|
|
2018-06-25 12:05:47 +00:00
|
|
|
/* find instr definition (and opcode) searching by mnemonic and addr mode */
|
|
|
|
bool
|
|
|
|
instruction_opcode_by_mnemonic(char *mnemonic, addressing_t mode, uint8_t *opcode, instrdef_t *id)
|
|
|
|
{
|
|
|
|
bool found;
|
|
|
|
|
|
|
|
found = false;
|
|
|
|
|
|
|
|
while ((*opcode) <= 0xFF) { /* this is stupid */
|
|
|
|
*id = instruction_decode(*opcode);
|
|
|
|
if ((strcmp(mnemonic, id->mnemonic) == 0) && (id->mode == mode)) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
(*opcode)++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|