mirror of https://github.com/buserror/mii_emu.git
137 lines
3.5 KiB
C
137 lines
3.5 KiB
C
#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;
|
|
};
|
|
uint32_t raw;
|
|
} mii_cpu_state_t;
|
|
|
|
/* 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
|
|
/* State of the protothread for the CPU state machine (minipt.h) */
|
|
void * state;
|
|
|
|
/* 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;
|
|
|
|
/* Debug only; Only used by the test units. */
|
|
uint8_t * ram; // DEBUG
|
|
} 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
|