mii_emu/src/mii_65c02.h

165 lines
4.1 KiB
C

/*
* mui_65c02.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
/*
* This is pretty heavily dependant on the way bitfields are packed in
* bytes, so it's not technically portable; if you have problems using a
* strange compiler, undefine this and use the 'discrete' version below.
*/
#define MII_PACK_P
/*
* State structure used to 'talk' to the CPU emulator.
* It works like this:
* mii_cpu_state_t s = { .reset = 1 };
* do {
* s = mii_cpu_run(cpu, s);
* if (s.w)
* write_memory(s.addr, s.data);
* else
* s.data = read_memory(s.addr);
* } while (1);
* s.sync will be 1 when we are fetching a new instruction
* s.reset will be 1 when the CPU is in reset (fetching vector)
* will turn to zero when done
* You check then the cpu->cycle to know what cycle you're in the current
* instruction.
*
* If you want to 'jump' to a new PC, you need to
* s.addr = new_pc;
* s.sync = 1;
* this will stop the current instruction and start fetching at new_pc
*
*/
typedef union mii_cpu_state_t {
struct {
uint16_t addr;
uint8_t data;
uint8_t w : 1,
sync : 1,
reset : 1,
irq : 1,
nmi : 1,
trap : 1;
} __attribute__((packed));
uint32_t raw;
} mii_cpu_state_t ;
#ifndef MII_65C02_DIRECT_ACCESS
#define MII_65C02_DIRECT_ACCESS 1
#endif
#if MII_65C02_DIRECT_ACCESS
struct mii_cpu_t;
typedef mii_cpu_state_t (*mii_cpu_direct_access_cb)(
struct mii_cpu_t *cpu,
mii_cpu_state_t access );
#endif
/* CPU state machine */
typedef struct mii_cpu_t {
uint8_t A, X, Y, S;
/* internal 16bit registers for fetch etc
* _D is the 'data' register, used for 16bit operations
* _P is the 'pointer' register, used for 16bit addressing
* This is not set hard in stone, but typically addressing modes that
* are 'indirect' and load from memory will set _P and read in _D
* so the opcode doesn't have to handle s.data at all */
uint16_t _D, _P;
#ifdef MII_PACK_P
union {
struct {
uint8_t C:1, Z:1, I:1, D:1, B:1, _R:1, V:1, N:1;
};
uint8_t P;
} P;
#else
/* My experience with simavr shows that maintaining a 8 bits bitfield
* for a status register is a lot slower than having discrete flags
* and 'constructing' the matching 8 bits register when needed */
union {
struct {
uint8_t C, Z, I, D, B, _R, V, N;
};
uint8_t P[8];
} P;
#endif
uint16_t PC;
uint8_t IR;
uint8_t IRQ; // IRQ (0) or NMI (1) or BRK (2)
uint8_t cycle; // for current instruction
uint32_t instruction_run; // how many instructions to run
/* sequence of instruction that will trigger a trap flag.
* this is used to trigger 'call backs' to the main code
* typically use a pair of NOPs sequence that is unlikely to exist in
* real code. */
uint16_t trap;
// last 4 instructions, as a shift register, used for traps or debug
uint32_t ir_log;
uint64_t total_cycle;
#if MII_65C02_DIRECT_ACCESS
mii_cpu_direct_access_cb access;
void * access_param; // typically struct mii_t*
#else
/* State of the protothread for the CPU state machine (minipt.h) */
void * state;
#endif
#ifdef MII_TEST
/* Debug only; Only used by the test units. */
uint8_t * ram; // DEBUG
#endif
} mii_cpu_t;
mii_cpu_state_t
mii_cpu_init(
mii_cpu_t *cpu );
mii_cpu_state_t
mii_cpu_run(
mii_cpu_t *cpu,
mii_cpu_state_t s);
#ifdef MII_PACK_P
#define MII_SET_P(_cpu, _byte) { \
(_cpu)->P.P = _byte | 0x30; \
}
#define MII_GET_P(_cpu, _res) \
(_res) = (_cpu)->P.P
#define MII_SET_P_BIT(_cpu, _bit, _val) { \
const int __bit = _bit; \
(_cpu)->P.P = ((_cpu)->P.P & ~(1 << __bit)) | (!!(_val) << __bit); \
}
#define MII_GET_P_BIT(_cpu, _bit) \
!!(((_cpu)->P.P & (1 << (_bit))))
#else
#define MII_SET_P(_cpu, _byte) { \
const int __byte = _byte; \
for (int _pi = 0; _pi < 8; _pi++) \
(_cpu)->P.P[_pi] = _pi == B_B || _pi == B_X || \
((__byte) & (1 << _pi)); \
}
#define MII_GET_P(_cpu, _res) { \
(_res) = 0; \
for (int _pi = 0; _pi < 8; _pi++) \
(_res) |= (_cpu)->P.P[_pi] << _pi; \
}
#define MII_SET_P_BIT(_cpu, _bit, _val) { \
(_cpu)->P.P[_bit] = _val; \
}
#define MII_GET_P_BIT(_cpu, _bit) \
((_cpu)->P.P[_bit])
#endif