commit 767675eb42254613170e937b5b67bd9c109a9cf8 Author: Guy Perfect Date: Mon Oct 14 15:07:00 2024 -0500 Split core into its own repository diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..68aa440 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +*.c text eol=lf diff=c +*.css text eol=lf diff=css +*.h text eol=lf diff=c +*.html text eol=lf diff=html +*.java text eol=lf diff=java +*.js text eol=lf diff=js +*.txt text eol=lf + +*.class binary +*.dll binary +*.wasm binary +*.woff2 binary diff --git a/core/bus.c b/core/bus.c new file mode 100644 index 0000000..6ee9655 --- /dev/null +++ b/core/bus.c @@ -0,0 +1,156 @@ +/* This file is included into vb.c and cannot be compiled on its own. */ +#ifdef VBAPI + + + +/********************************* Constants *********************************/ + +/* Memory access address masks by data type */ +static const uint32_t TYPE_MASKS[] = { + 0x07FFFFFF, /* S8 */ + 0x07FFFFFF, /* U8 */ + 0x07FFFFFE, /* S16 */ + 0x07FFFFFE, /* U16 */ + 0x07FFFFFC /* S32 */ +}; + + + +/*************************** Sub-Module Functions ****************************/ + +/* Read a typed value from a buffer in host memory */ +static int32_t busReadBuffer(uint8_t *data, int type) { + + /* Processing by data type */ + switch (type) { + + /* Generic implementation */ + #ifndef VB_LITTLE_ENDIAN + case VB_S8 : return ((int8_t *)data)[0]; + case VB_U8 : return data [0]; + case VB_S16: return (int32_t) ((int8_t *)data)[1] << 8 | data[0]; + case VB_U16: return (int32_t) data [1] << 8 | data[0]; + case VB_S32: return + (int32_t) data[3] << 24 | (int32_t) data[2] << 16 | + (int32_t) data[1] << 8 | data[0]; + + /* Little-endian host */ + #else + case VB_S8 : return *(int8_t *) data; + case VB_U8 : return * data; + case VB_S16: return *(int16_t *) data; + case VB_U16: return *(uint16_t *) data; + case VB_S32: return *(int32_t *) data; + #endif + + } + + return 0; /* Unreachable */ +} + +/* Write a typed value to a buffer in host memory */ +static void busWriteBuffer(uint8_t *data, int type, int32_t value) { + + /* Processing by data type */ + switch (type) { + + /* Generic implementation */ + #ifndef VB_LITTLE_ENDIAN + case VB_S32: data[3] = value >> 24; + data[2] = value >> 16; /* Fallthrough */ + case VB_S16: /* Fallthrough */ + case VB_U16: data[1] = value >> 8; /* Fallthrough */ + case VB_S8 : /* Fallthrough */ + case VB_U8 : data[0] = value; + + /* Little-endian host */ + #else + case VB_S8 : /* Fallthrough */ + case VB_U8 : * data = value; return; + case VB_S16: /* Fallthrough */ + case VB_U16: *(uint16_t *) data = value; return; + case VB_S32: *(int32_t *) data = value; return; + #endif + + } + +} + + + +/***************************** Library Functions *****************************/ + +/* Read a typed value from the simulation bus */ +static void busRead(VB *sim, uint32_t address, int type, int32_t *value) { + + /* Working variables */ + address &= TYPE_MASKS[type]; + *value = 0; + + /* Process by address range */ + switch (address >> 24) { + case 0: break; /* VIP */ + case 1: break; /* VSU */ + case 2: break; /* Misc. I/O */ + case 3: break; /* Unmapped */ + case 4: break; /* Game Pak expansion */ + + case 5: /* WRAM */ + *value = busReadBuffer(&sim->wram[address & 0x0000FFFF], type); + break; + + case 6: /* Game Pak RAM */ + if (sim->cart.ram != NULL) { + *value = busReadBuffer( + &sim->cart.ram[address & sim->cart.ramMask], type); + } + break; + + case 7: /* Game Pak ROM */ + if (sim->cart.rom != NULL) { + *value = busReadBuffer( + &sim->cart.rom[address & sim->cart.romMask], type); + } + break; + + } + +} + +/* Write a typed value to the simulation bus */ +static void busWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){ + + /* Working variables */ + address &= TYPE_MASKS[type]; + + /* Process by address range */ + switch (address >> 24) { + case 0: break; /* VIP */ + case 1: break; /* VSU */ + case 2: break; /* Misc. I/O */ + case 3: break; /* Unmapped */ + case 4: break; /* Game Pak expansion */ + + case 5: /* WRAM */ + busWriteBuffer(&sim->wram[address & 0x0000FFFF], type, value); + break; + + case 6: /* Game Pak RAM */ + if (sim->cart.ram != NULL) { + busWriteBuffer( + &sim->cart.ram[address & sim->cart.ramMask], type, value); + } + break; + + case 7: /* Game Pak ROM */ + if (debug && sim->cart.rom != NULL) { + busWriteBuffer( + &sim->cart.rom[address & sim->cart.romMask], type, value); + } + break; + + } + +} + +#endif /* VBAPI */ diff --git a/core/cpu.c b/core/cpu.c new file mode 100644 index 0000000..261ac59 --- /dev/null +++ b/core/cpu.c @@ -0,0 +1,1801 @@ +/* This file is included into vb.c and cannot be compiled on its own. */ +#ifdef VBAPI + + + +/********************************* Constants *********************************/ + +/* Operation IDs */ +#define CPU_EXCEPTION 0 +#define CPU_HALTING 1 +#define CPU_FATAL 2 +#define CPU_FETCH 3 +#define CPU_ILLEGAL 4 +#define CPU_BITSTRING 5 +#define CPU_FLOATENDO 6 +#define CPU_ADD_IMM 7 +#define CPU_ADD_REG 8 +#define CPU_ADDF_S 9 +#define CPU_ADDI 10 +#define CPU_AND 11 +#define CPU_ANDI 12 +#define CPU_BCOND 13 +#define CPU_CAXI 14 +#define CPU_CLI 15 +#define CPU_CMP_IMM 16 +#define CPU_CMP_REG 17 +#define CPU_CMPF_S 18 +#define CPU_CVT_SW 19 +#define CPU_CVT_WS 20 +#define CPU_DIV 21 +#define CPU_DIVF_S 22 +#define CPU_DIVU 23 +#define CPU_HALT 24 +#define CPU_IN_B 25 +#define CPU_IN_H 26 +#define CPU_IN_W 27 +#define CPU_JAL 28 +#define CPU_JMP 29 +#define CPU_JR 30 +#define CPU_LD_B 31 +#define CPU_LD_H 32 +#define CPU_LD_W 33 +#define CPU_LDSR 34 +#define CPU_MOV_IMM 35 +#define CPU_MOV_REG 36 +#define CPU_MOVEA 37 +#define CPU_MOVHI 38 +#define CPU_MPYHW 39 +#define CPU_MUL 40 +#define CPU_MULF_S 41 +#define CPU_MULU 42 +#define CPU_NOT 43 +#define CPU_OR 44 +#define CPU_ORI 45 +#define CPU_OUT_B 46 +#define CPU_OUT_H 47 +#define CPU_OUT_W 48 +#define CPU_RETI 49 +#define CPU_REV 50 +#define CPU_SAR_IMM 51 +#define CPU_SAR_REG 52 +#define CPU_SCH0BSD 53 +#define CPU_SCH0BSU 54 +#define CPU_SCH1BSD 55 +#define CPU_SCH1BSU 56 +#define CPU_SEI 57 +#define CPU_SETF 58 +#define CPU_SHL_IMM 59 +#define CPU_SHL_REG 60 +#define CPU_SHR_IMM 61 +#define CPU_SHR_REG 62 +#define CPU_ST_B 63 +#define CPU_ST_H 64 +#define CPU_ST_W 65 +#define CPU_STSR 66 +#define CPU_SUB 67 +#define CPU_SUBF_S 68 +#define CPU_TRAP 69 +#define CPU_TRNC_SW 70 +#define CPU_XB 71 +#define CPU_XH 72 +#define CPU_XOR 73 +#define CPU_XORI 74 +#define CPU_ANDBSU 75 /* Keep bit string ALU commands sequential */ +#define CPU_ANDNBSU 76 +#define CPU_MOVBSU 77 +#define CPU_NOTBSU 78 +#define CPU_ORBSU 79 +#define CPU_ORNBSU 80 +#define CPU_XORBSU 81 +#define CPU_XORNBSU 82 + +/* Abstract operand types */ +#define CPU_IMP(x) -x-1 +#define CPU_DISP9 1 +#define CPU_DISP26 2 +#define CPU_IMM16S 3 +#define CPU_IMM16U 4 +#define CPU_IMM5S 5 +#define CPU_IMM5U 6 +#define CPU_MEM 7 +#define CPU_REG1 8 +#define CPU_REG2 9 + +/* Functional operand types */ +#define CPU_LITERAL 0 +#define CPU_MEMORY 1 +#define CPU_REGISTER 2 + +/* Bit string operations */ +#define CPU_AND_BS 0 +#define CPU_ANDN_BS 1 +#define CPU_MOV_BS 2 +#define CPU_NOT_BS 3 +#define CPU_OR_BS 4 +#define CPU_ORN_BS 5 +#define CPU_XOR_BS 6 +#define CPU_XORN_BS 7 + + + +/********************************** Macros ***********************************/ + +/* Master clocks per CPU cycles */ +#define cpuClocks(x) x + +/* Shorthands */ +#define auxBS sim->cpu.aux.bs +#define auxData sim->cpu.aux.data +#define bsDestAddr sim->cpu.program[29] +#define bsDestBit sim->cpu.program[26] +#define bsLength sim->cpu.program[28] +#define bsSkipped sim->cpu.program[29] +#define bsSrcAddr sim->cpu.program[30] +#define bsSrcBit sim->cpu.program[27] + + + +/******************************** Lookup Data ********************************/ + +/* Instruction code lengths by opcode */ +static const uint8_t INST_LENGTHS[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2 +}; + +/* Highest interrupt level by IRQ bit mask value */ +static const int8_t IRQ_LEVELS[] = { + -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 +}; + +/* Opdefs by opcode */ +static const uint8_t OPDEFS[] = { + CPU_MOV_REG, CPU_ADD_REG, CPU_SUB , CPU_CMP_REG , /* 000000 */ + CPU_SHL_REG, CPU_SHR_REG, CPU_JMP , CPU_SAR_REG , + CPU_MUL , CPU_DIV , CPU_MULU , CPU_DIVU , + CPU_OR , CPU_AND , CPU_XOR , CPU_NOT , + CPU_MOV_IMM, CPU_ADD_IMM, CPU_SETF , CPU_CMP_IMM , /* 010000 */ + CPU_SHL_IMM, CPU_SHR_IMM, CPU_CLI , CPU_SAR_IMM , + CPU_TRAP , CPU_RETI , CPU_HALT , CPU_ILLEGAL , + CPU_LDSR , CPU_STSR , CPU_SEI , CPU_BITSTRING, + CPU_BCOND , CPU_BCOND , CPU_BCOND , CPU_BCOND , /* 100000 */ + CPU_BCOND , CPU_BCOND , CPU_BCOND , CPU_BCOND , + CPU_MOVEA , CPU_ADDI , CPU_JR , CPU_JAL , + CPU_ORI , CPU_ANDI , CPU_XORI , CPU_MOVHI , + CPU_LD_B , CPU_LD_H , CPU_ILLEGAL , CPU_LD_W , /* 110000 */ + CPU_ST_B , CPU_ST_H , CPU_ILLEGAL , CPU_ST_W , + CPU_IN_B , CPU_IN_H , CPU_CAXI , CPU_IN_W , + CPU_OUT_B , CPU_OUT_H , CPU_FLOATENDO, CPU_OUT_W +}; + +/* Opdefs by bit string sub-opcode */ +static const uint8_t OPDEFS_BITSTRING[] = { + CPU_SCH0BSU, CPU_SCH0BSD, CPU_SCH1BSU, CPU_SCH1BSD, /* 00000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ORBSU , CPU_ANDBSU , CPU_XORBSU , CPU_MOVBSU , /* 01000 */ + CPU_ORNBSU , CPU_ANDNBSU, CPU_XORNBSU, CPU_NOTBSU , + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, /* 10000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, /* 11000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL +}; + +/* Opdefs by floating-point/Nintendo sub-opcodes */ +static const uint8_t OPDEFS_FLOATENDO[] = { + CPU_CMPF_S , CPU_ILLEGAL, CPU_CVT_WS , CPU_CVT_SW , /* 000000 */ + CPU_ADDF_S , CPU_SUBF_S , CPU_MULF_S , CPU_DIVF_S , + CPU_XB , CPU_XH , CPU_REV , CPU_TRNC_SW, + CPU_MPYHW , CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, /* 010000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, /* 100000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, /* 110000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL +}; + + + +/***************************** Callback Handlers *****************************/ + +/* Prepare to handle an exception */ +#ifndef VB_DIRECT_EXCEPTION + #define VB_ON_EXCEPTION sim->onException +#else + extern int vbxOnException(VB *, uint16_t *); + #define VB_ON_EXCEPTION sim->onException +#endif +static int cpuOnException(VB *sim, uint16_t *cause) { + return sim->onException != NULL && VB_ON_EXCEPTION(sim, cause); +} +#undef VB_ON_EXCEPTION + +/* Prepare to execute an instruction */ +#ifndef VB_DIRECT_EXECUTE + #define VB_ON_EXECUTE sim->onExecute +#else + extern int vbxOnExecute(VB *, uint32_t, const uint16_t *, int); + #define VB_ON_EXECUTE vbxOnExecute +#endif +static int cpuOnExecute(VB *sim) { + return + sim->onExecute != NULL && + VB_ON_EXECUTE(sim, sim->cpu.pc, sim->cpu.code, sim->cpu.length) + ; +} +#undef VB_ON_EXECUTE + +/* Read a value from the memory bus */ +#ifndef VB_DIRECT_READ + #define VB_ON_READ sim->onRead +#else + extern int vbxOnRead(VB *, uint32_t, int, int32_t *, uint32_t *); + #define VB_ON_READ vbxOnRead +#endif +static int cpuRead(VB *sim, uint32_t address, int type, int32_t *value) { + uint32_t cycles = 4; /* TODO: Research this */ + + /* Retrieve the value from the simulation state directly */ + busRead(sim, address, type, value); + + /* Invoke the callback if available */ + if ( + sim->onRead != NULL && + VB_ON_READ(sim, address, type, value, &cycles) + ) return 1; + + /* Update state */ + sim->cpu.clocks += cpuClocks(cycles); + return 0; +} +#undef VB_ON_READ + +/* Fetch a code unit from the memory bus */ +#ifndef VB_DIRECT_FETCH + #define VB_ON_FETCH sim->onFetch +#else + extern int vbxOnFetch(VB *, int, uint32_t, int32_t *, uint32_t *); + #define VB_ON_FETCH vbxOnFetch +#endif +static int cpuReadFetch(VB *sim, int fetch, uint32_t address, int32_t *value) { + uint32_t cycles = 0; + + /* Retrieve the value from the simulation state directly */ + busRead(sim, address, VB_U16, value); + + /* Invoke the callback if available */ + if ( + sim->onFetch != NULL && + VB_ON_FETCH(sim, fetch, address, value, &cycles) + ) return 1; + + /* Update state */ + sim->cpu.clocks += cpuClocks(cycles); + return 0; +} +#undef VB_ON_FETCH + +/* Write a value to the memory bus */ +#ifndef VB_DIRECT_WRITE + #define VB_ON_WRITE sim->onWrite +#else + extern int vbxOnWrite(VB *, uint32_t, int, int32_t *, uint32_t *, int *); + #define VB_ON_WRITE vbxOnWrite +#endif +static int cpuWrite(VB *sim, uint32_t address, int type, int32_t value) { + int cancel = 0; + uint32_t cycles = 3; /* TODO: Research this */ + + /* Invoke the callback if available */ + if ( + sim->onWrite != NULL && + VB_ON_WRITE(sim, address, type, &value, &cycles, &cancel) + ) return 1; + + /* Write the value to the simulation state directly */ + if (!cancel) + busWrite(sim, address, type, value, 0); + + /* Update state */ + sim->cpu.clocks += cpuClocks(cycles); + return 0; +} +#undef VB_ON_WRITE + + + +/**************************** Instruction Helpers ****************************/ + +/* Retrieve the value of a system register */ +static uint32_t cpuGetSystemRegister(VB *sim, int index) { + switch (index) { + case VB_ADTRE: return sim->cpu.adtre; + case VB_CHCW : return sim->cpu.chcw.ice << 1; + case VB_ECR : return + (uint32_t) sim->cpu.ecr.fecc << 16 | + (uint32_t) sim->cpu.ecr.eicc; + case VB_EIPC : return sim->cpu.eipc; + case VB_EIPSW: return sim->cpu.eipsw; + case VB_FEPC : return sim->cpu.fepc; + case VB_FEPSW: return sim->cpu.fepsw; + case VB_PIR : return 0x00005346; + case VB_PSW : return + (uint32_t) sim->cpu.psw.i << 16 | + (uint32_t) sim->cpu.psw.np << 15 | + (uint32_t) sim->cpu.psw.ep << 14 | + (uint32_t) sim->cpu.psw.ae << 13 | + (uint32_t) sim->cpu.psw.id << 12 | + (uint32_t) sim->cpu.psw.fro << 9 | + (uint32_t) sim->cpu.psw.fiv << 8 | + (uint32_t) sim->cpu.psw.fzd << 7 | + (uint32_t) sim->cpu.psw.fov << 6 | + (uint32_t) sim->cpu.psw.fud << 5 | + (uint32_t) sim->cpu.psw.fpr << 4 | + (uint32_t) sim->cpu.psw.cy << 3 | + (uint32_t) sim->cpu.psw.ov << 2 | + (uint32_t) sim->cpu.psw.s << 1 | + (uint32_t) sim->cpu.psw.z; + case VB_TKCW : return 0x000000E0; + case 29 : return sim->cpu.sr29; + case 30 : return 0x00000004; + case 31 : return sim->cpu.sr31; + } + return 0x00000000; /* All others */ +} + +/* Specify a new value for a system register */ +static uint32_t cpuSetSystemRegister(VB*sim, + int index, uint32_t value, int debug) { + switch (index) { + case VB_ADTRE: return sim->cpu.adtre = value & 0xFFFFFFFE; + case VB_CHCW : + /* TODO: Configure instruction cache */ + sim->cpu.chcw.ice = value >> 1 & 1; + return value & 0x00000002; + case VB_ECR : + if (debug) { + sim->cpu.ecr.fecc = value >> 16; + sim->cpu.ecr.eicc = value; + } + return + (uint32_t) sim->cpu.ecr.fecc << 16 | + (uint32_t) sim->cpu.ecr.eicc; + case VB_EIPC : return sim->cpu.eipc = value & 0xFFFFFFFE; + case VB_EIPSW: return sim->cpu.eipsw = value & 0x000FF3FF; + case VB_FEPC : return sim->cpu.fepc = value & 0xFFFFFFFE; + case VB_FEPSW: return sim->cpu.fepsw = value & 0x000FF3FF; + case VB_PIR : return 0x00005346; + case VB_PSW : + sim->cpu.psw.i = value >> 16 & 0xF; + sim->cpu.psw.np = value >> 15 & 1; + sim->cpu.psw.ep = value >> 14 & 1; + sim->cpu.psw.ae = value >> 13 & 1; + sim->cpu.psw.id = value >> 12 & 1; + sim->cpu.psw.fro = value >> 9 & 1; + sim->cpu.psw.fiv = value >> 8 & 1; + sim->cpu.psw.fzd = value >> 7 & 1; + sim->cpu.psw.fov = value >> 6 & 1; + sim->cpu.psw.fud = value >> 5 & 1; + sim->cpu.psw.fpr = value >> 4 & 1; + sim->cpu.psw.cy = value >> 3 & 1; + sim->cpu.psw.ov = value >> 2 & 1; + sim->cpu.psw.s = value >> 1 & 1; + sim->cpu.psw.z = value & 1; + return value & 0x000FF3FF; + case VB_TKCW : return 0x000000E0; + case 29 : return sim->cpu.sr29 = value; + case 30 : return 0x00000004; + case 31 : return sim->cpu.sr31 = debug ? value : + *(int32_t *) &value < 0 ? (uint32_t) -*(int32_t *) &value : value; + } + return 0x00000000; /* All others */ +} + +/* Raise an exception */ +static void cpuThrow(VB *sim, uint16_t cause) { + sim->cpu.exception = cause; + sim->cpu.operation = CPU_EXCEPTION; + sim->cpu.step = 0; +} + +/* Check for an interrupt request */ +static int cpuIRQ(VB *sim) { + int level; + if (sim->cpu.psw.id | sim->cpu.psw.ep | sim->cpu.psw.np) + return 0; + level = IRQ_LEVELS[sim->cpu.irq]; + if (level == -1 || level > sim->cpu.psw.i) + return 0; + cpuThrow(sim, 0xFE00 | level << 4); + return 1; +} + + + +/****************************** Pipeline Stages ******************************/ + +/* Handle an exception */ +static int cpuException(VB *sim) { + uint16_t cause = sim->cpu.exception; + + /* Invoke the exception callback */ + if (sim->cpu.step == 0 && cpuOnException(sim, &cause)) + return 1; + + /* Fatal exception */ + if (sim->cpu.psw.np) { + switch (sim->cpu.step) { + case 0: + auxData.value = 0xFFFF0000 | cause; + /* Fallthrough */ + case 1: + if (cpuWrite(sim, 0x00000000, VB_S32, auxData.value)) { + sim->cpu.step = 1; + return 1; + } + auxData.value = cpuGetSystemRegister(sim, VB_PSW); + /* Fallthrough */ + case 2: + if (cpuWrite(sim, 0x00000004, VB_S32, auxData.value)) { + sim->cpu.step = 2; + return 1; + } + /* Fallthrough */ + case 3: + if (cpuWrite(sim, 0x00000008, VB_S32, sim->cpu.pc)) { + sim->cpu.step = 3; + return 1; + } + } + + /* Enter fatal halting state */ + sim->cpu.halt = 1; + sim->cpu.operation = CPU_FATAL; + return 0; + } + + /* Duplexed exception */ + if (sim->cpu.psw.ep) { + sim->cpu.ecr.fecc = cause; + sim->cpu.fepsw = cpuGetSystemRegister(sim, VB_PSW); + sim->cpu.fepc = sim->cpu.pc; + sim->cpu.psw.np = 1; + sim->cpu.nextPC = 0xFFFFFFD0; + } + + /* Regular exception */ + else { + + /* Interrupts only */ + if ((cause & 0xFF00) == 0xFE00) { + sim->cpu.psw.i = (cause >> 4 & 7) + 1; + if (sim->cpu.halt) { + sim->cpu.halt = 0; + sim->cpu.pc += 2; + } + } + + /* All exceptions */ + sim->cpu.ecr.eicc = cause; + sim->cpu.eipsw = cpuGetSystemRegister(sim, VB_PSW); + sim->cpu.eipc = sim->cpu.pc; + sim->cpu.psw.ep = 1; + sim->cpu.nextPC = 0xFFFF0000 | + ((cause & 0xFFF0) == 0xFF60 ? 0xFF60 : cause & 0xFFF0); + } + + /* All exceptions */ + sim->cpu.psw.ae = 0; + sim->cpu.psw.id = 1; + sim->cpu.operation = CPU_FETCH; + /* TODO: Research clocks */ + return 0; +} + +/* Fetch the code bits for the next instruction */ +static int cpuFetch(VB *sim) { + int32_t value; + switch (sim->cpu.step) { + + case 0: + sim->cpu.pc = sim->cpu.nextPC; + if (cpuIRQ(sim)) + return 0; + + /* Fallthrough */ + case 1: + + /* Retrieve the first code unit */ + if (cpuReadFetch(sim, 0, sim->cpu.pc, &value)) { + sim->cpu.step = 1; + return 1; + } + + /* Update state */ + sim->cpu.code[0] = value; + sim->cpu.length = INST_LENGTHS[value >> 10 & 63]; + sim->cpu.step = 3 - sim->cpu.length; + + /* Wait any clocks taken */ + if (sim->cpu.clocks != 0) + return 0; + + /* Skip fetching a second code unit */ + if (sim->cpu.length == 1) + goto Step3; + + /* Fallthrough */ + case 2: + + /* Retrieve the second code unit */ + if (cpuReadFetch(sim, 1, sim->cpu.pc + 2, &value)) + return 1; + + /* Update state */ + sim->cpu.code[1] = value; + sim->cpu.step = 3; + + /* Wait any clocks taken */ + if (sim->cpu.clocks != 0) + return 0; + + /* Fallthrough */ + case 3: Step3: + + /* Prepare to execute the instruction */ + if (cpuOnExecute(sim)) + return 1; + + /* Select operation definition */ + sim->cpu.operation = OPDEFS[sim->cpu.code[0] >> 10]; + switch (sim->cpu.operation) { + case CPU_BITSTRING: + sim->cpu.operation = + OPDEFS_BITSTRING[sim->cpu.code[0] & 31]; + break; + case CPU_FLOATENDO: + sim->cpu.operation = + OPDEFS_FLOATENDO[sim->cpu.code[1] >> 10]; + } + + /* Update state */ + sim->cpu.step = 0; + + } + return 0; +} + +/* HALT instruction is pending */ +static int cpuHalt(VB *sim) { + /* TODO: Research clocks */ + return !cpuIRQ(sim); +} + + + +/**************************** Instruction Helpers ****************************/ + +/* Parse the immediate 4-bit condition value */ +static int32_t cpuGetCond(VB *sim) { + return sim->cpu.code[0] >> 9 & 15; +} + +/* Parse the 9-bit displacement value */ +static int32_t cpuGetDisp9(VB *sim) { + return SignExtend(sim->cpu.code[0], 9); +} + +/* Parse the 26-bit displacement value */ +static int32_t cpuGetDisp26(VB *sim) { + return SignExtend((int32_t) sim->cpu.code[0] << 16 | sim->cpu.code[1], 26); +} + +/* Parse the immediate 5-bit sign-extended value */ +static int32_t cpuGetImm5S(VB *sim) { + return SignExtend(sim->cpu.code[0], 5); +} + +/* Parse the immediate 5-bit zero-filled value */ +static int32_t cpuGetImm5U(VB *sim) { + return sim->cpu.code[0] & 31; +} + +/* Parse the immediate 16-bit sign-extended value */ +static int32_t cpuGetImm16S(VB *sim) { + return SignExtend(sim->cpu.code[1], 16); +} + +/* Parse the immediate 16-bit zero-filled value */ +static int32_t cpuGetImm16U(VB *sim) { + return sim->cpu.code[1]; +} + +/* Resolve the operand value for reg1 */ +static int32_t cpuGetReg1(VB *sim) { + return sim->cpu.program[sim->cpu.code[0] & 31]; +} + +/* Resolve the operand value for reg2 */ +static int32_t cpuGetReg2(VB *sim) { + return sim->cpu.program[sim->cpu.code[0] >> 5 & 31]; +} + +/* Supply an operand value for reg2 */ +static void cpuSetReg2(VB *sim, int32_t value) { + int reg2 = sim->cpu.code[0] >> 5 & 31; + if (reg2 != 0) + sim->cpu.program[reg2] = value; +} + +/* Advance to the next instruction */ +static void cpuAdvance(VB *sim, uint32_t clocks) { + sim->cpu.clocks += clocks; + sim->cpu.operation = CPU_FETCH; + sim->cpu.nextPC = sim->cpu.pc + (sim->cpu.length << 1); + sim->cpu.step = 0; +} + +/* Interpret floating short bits as word integer bits */ +static int32_t cpuFloatToWord(float x) { + return *(int32_t *) &x; +} + +/* Interpret word integer bits as floating short bits */ +static float cpuWordToFloat(int32_t x) { + return *(float *) &x; +} + +/* Addition common processing */ +static int32_t cpuAdd(VB *sim, int32_t a, int32_t b) { + int32_t c = a + b; + sim->cpu.psw.cy = (uint32_t) c < (uint32_t) a; + sim->cpu.psw.ov = (~(a ^ b) & (a ^ c)) < 0; + sim->cpu.psw.s = c < 0; + sim->cpu.psw.z = c == 0; + return c; +} + +/* Bitwise common processing */ +static int32_t cpuBitwise(VB *sim, int32_t c) { + sim->cpu.psw.ov = 0; + sim->cpu.psw.s = c < 0; + sim->cpu.psw.z = c == 0; + return c; +} + +/* Bit string arithmetic common processing */ +static int cpuBSArithmetic(VB *sim) { + uint32_t dest; + uint32_t src; + switch (sim->cpu.step) { + case 0: + bsDestAddr &= 0xFFFFFFFC; + bsDestBit &= 0x0000001F; + bsSrcAddr &= 0xFFFFFFFC; + bsSrcBit &= 0x0000001F; + sim->cpu.clocks += cpuClocks(35); /* TODO: Research */ + + /* Nothing to do */ + if (bsLength == 0) + break; + + /* Fallthrough */ + case 1: + + /* Read the low source word */ + if (cpuRead(sim, bsSrcAddr, VB_S32, (int32_t *) &src)) { + sim->cpu.step = 1; + return 1; + } + auxBS.src = src; + + /* Fallthrough */ + case 2: + + /* Read the high source word */ + if (cpuRead(sim, bsSrcAddr + 4, VB_S32, (int32_t *) &src)) { + sim->cpu.step = 2; + return 1; + } + auxBS.src = (uint32_t) auxBS.src | (uint64_t) src << 32; + + /* Fallthrough */ + case 3: + + /* Read the destination word */ + if (cpuRead(sim, bsDestAddr, VB_S32, (int32_t *) &auxBS.dest)) { + sim->cpu.step = 3; + return 1; + } + + /* Perform the ALU operation */ + dest = auxBS.dest; + src = bsSrcBit < bsDestBit ? + auxBS.src << (bsDestBit - bsSrcBit ) : + auxBS.src >> (bsSrcBit - bsDestBit) + ; + switch (sim->cpu.operation) { + case CPU_ANDBSU : dest &= src; break; + case CPU_ANDNBSU: dest &= ~src; break; + case CPU_MOVBSU : dest = src; break; + case CPU_NOTBSU : dest = ~src; break; + case CPU_ORBSU : dest |= src; break; + case CPU_ORNBSU : dest |= ~src; break; + case CPU_XORBSU : dest ^= src; break; + case CPU_XORNBSU: dest ^= ~src; break; + } + + /* Change only the bits that are part of the string */ + src = 0xFFFFFFFF << bsDestBit; + if ((uint32_t) 32 - bsDestBit > (uint32_t) bsLength) + src &= ~(0xFFFFFFFF << (bsDestBit + bsLength)); + auxBS.dest = (auxBS.dest & ~src) | (dest & src); + + /* Fallthrough */ + case 4: + + /* Write the destination word */ + if (cpuWrite(sim, bsDestAddr, VB_S32, auxBS.dest)) { + sim->cpu.step = 4; + return 1; + } + + /* Select number of bits to advance */ + src = 32 - bsDestBit; + if (src > (uint32_t) bsLength) + src = bsLength; + + /* Advance to next output word */ + if (bsSrcBit + src >= 32) { + bsSrcAddr += 4; + auxBS.src >>= 32; + sim->cpu.step = 2; /* Read high source word */ + } else sim->cpu.step = 3; /* Read destination word */ + if (bsDestBit + src >= 32) + bsDestAddr += 4; + bsSrcBit = (bsSrcBit + src) & 31; + bsDestBit = (bsDestBit + src) & 31; + bsLength -= src; + + /* Update state */ + sim->cpu.clocks += cpuClocks(12); /* TODO: Research */ + } + + /* Exit condition */ + if (bsLength == 0) + cpuAdvance(sim, 0); + return 0; +} + +/* Bit string search common processing */ +static int cpuBSSearch(VB *sim, int bit, int dir) { + switch (sim->cpu.step) { + case 0: + bsSrcAddr &= 0xFFFFFFFC; + bsSrcBit &= 0x0000001F; + sim->cpu.clocks += cpuClocks(50); /* TODO: Research */ + sim->cpu.psw.z = 1; + sim->cpu.step = 1; + + /* Nothing to do */ + if (bsLength == 0) + break; + + /* Fallthrough */ + case 1: + + /* Read the source word */ + if (cpuRead(sim, bsSrcAddr, VB_S32, &auxData.value)) + return 1; + sim->cpu.clocks += cpuClocks(5); /* TODO: Research */ + + /* Process all remaining bits in source word */ + do { + + /* Match was found */ + if ((auxData.value >> bsSrcBit & 1) == bit) + sim->cpu.psw.z = 0; + + /* Match was not found */ + else bsSkipped++; + + /* Advance to next bit */ + bsSrcBit = (bsSrcBit + dir) & 31; + bsLength--; + if (bsSrcBit != (dir == 1 ? 0 : 31)) + continue; + + /* Advance to next word */ + bsSrcAddr += dir << 2; + break; + } while (sim->cpu.psw.z && bsLength != 0); + } + + /* Exit condition */ + if (!sim->cpu.psw.z || bsLength == 0) + cpuAdvance(sim, 0); + return 0; +} + +/* Test a condition */ +static int cpuCondition(VB *sim, int id) { + switch (id) { + case 0: return sim->cpu.psw.ov; + case 1: return sim->cpu.psw.cy; + case 2: return sim->cpu.psw.z; + case 3: return sim->cpu.psw.cy | sim->cpu.psw.z; + case 4: return sim->cpu.psw.s; + case 5: return 1; + case 6: return sim->cpu.psw.ov ^ sim->cpu.psw.s; + case 7: return (sim->cpu.psw.ov ^ sim->cpu.psw.s) | sim->cpu.psw.z; + } + return !cpuCondition(sim, id & 7); +} + +/* Floating-point common processing */ +static int cpuFloatCommon(VB *sim, double value, int32_t *bits) { + float floatVal; + + /* Overflow */ + if (value < -FLT_MAX || value > FLT_MAX) { + /* TODO: CPU does give reg2 some value */ + sim->cpu.psw.fov = 1; + cpuThrow(sim, 0xFF64); + return 1; + } + + /* Underflow */ + if (value < 0 ? value > -FLT_MIN : value < FLT_MIN) { + sim->cpu.psw.fud = 1; + floatVal = 0; + /* TODO: Can this produce negative zero? */ + } + + /* Regular conversion */ + else floatVal = value; + + /* Precision degradation */ + if (floatVal != value) + sim->cpu.psw.fpr = 1; + + /* Update state */ + *bits = cpuFloatToWord(floatVal); + sim->cpu.psw.cy = floatVal < 0; + sim->cpu.psw.ov = 0; + sim->cpu.psw.s = floatVal < 0; + sim->cpu.psw.z = floatVal == 0; + return 0; +} + +/* Test whether a floating-point register value is a reserved operand */ +static int cpuFloatReservedOne(int32_t bits) { + int32_t e = bits >> 23 & 0xFF; + return e == 0 ? (bits & 0x7FFFFFFF) != 0 : e == 0xFF; +} + +/* Test whether floating-point register values are reserved operands */ +static int cpuFloatReserved(VB *sim, int32_t a, int32_t b) { + if (!(cpuFloatReservedOne(a) || cpuFloatReservedOne(b))) + return 0; + sim->cpu.psw.fro = 1; + cpuThrow(sim, 0xFF60); + return 1; +} + +/* Jump and branch common processing */ +static void cpuJump(VB *sim, uint32_t address) { + sim->cpu.clocks += cpuClocks(3); + sim->cpu.operation = CPU_FETCH; + sim->cpu.nextPC = address & 0xFFFFFFFE; + sim->cpu.step = 0; + /* TODO: Clear prefetch buffer */ +} + +/* Arithmetic right shift common processing */ +static int32_t cpuShiftArithmetic(VB *sim, int32_t b) { + int32_t a = cpuGetReg2(sim); + #ifndef VB_SIGNED_PROPAGATE + int32_t c = b == 0 ? a : SignExtend(a >> b, 32 - b); + #else + int32_t c = a >> b; + #endif + sim->cpu.psw.cy = b == 0 ? 0 : a >> (b - 1) & 1; + sim->cpu.psw.ov = 0; + sim->cpu.psw.s = c < 0; + sim->cpu.psw.z = c == 0; + return c; +} + +/* Left shift common processing */ +static int32_t cpuShiftLeft(VB *sim, int32_t b) { + int32_t a = cpuGetReg2(sim); + int32_t c = (uint32_t) a << b; + sim->cpu.psw.cy = b == 0 ? 0 : a >> (32 - b) & 1; + sim->cpu.psw.ov = 0; + sim->cpu.psw.s = c < 0; + sim->cpu.psw.z = c == 0; + return c; +} + +/* Logical right shift common processing */ +static int32_t cpuShiftRight(VB *sim, int32_t b) { + int32_t a = cpuGetReg2(sim); + int32_t c = b == 0 ? (uint32_t) a : + a >> b & ~((uint32_t) 0xFFFFFFFF << (32 - b)); + sim->cpu.psw.cy = b == 0 ? 0 : a >> (b - 1) & 1; + sim->cpu.psw.ov = 0; + sim->cpu.psw.s = c < 0; + sim->cpu.psw.z = c == 0; + return c; +} + +/* Subtraction common processing */ +static int32_t cpuSubtract(VB *sim, int32_t a, int32_t b) { + int32_t c = a - b; + sim->cpu.psw.cy = (uint32_t) a < (uint32_t) b; + sim->cpu.psw.ov = ((a ^ b) & (a ^ c)) < 0; + sim->cpu.psw.s = c < 0; + sim->cpu.psw.z = c == 0; + return c; +} + +/* Memory load/input instruction */ +static int cpuLD_IN(VB *sim, int type) { + switch (sim->cpu.step) { + + case 0: + auxData.address = cpuGetReg1(sim) + cpuGetImm16S(sim); + /* Fallthrough */ + + case 1: + + /* Read the value from memory */ + if (cpuRead(sim, auxData.address, type, &auxData.value)) { + sim->cpu.step = 1; + return 1; + } + + /* Update state */ + sim->cpu.clocks += cpuClocks(1); + + /* Wait for clocks taken */ + sim->cpu.step = 2; + return 0; + + case 2: + cpuSetReg2(sim, auxData.value); + cpuAdvance(sim, cpuClocks(4)); /* TODO: Research */ + } + return 0; +} + +/* Memory store/output instruction */ +static int cpuST_OUT(VB *sim, int type) { + switch (sim->cpu.step) { + + case 0: + auxData.address = cpuGetReg1(sim) + cpuGetImm16S(sim); + /* Fallthrough */ + + case 1: + + /* Write the value to memory */ + if (cpuWrite(sim, auxData.address, type, cpuGetReg2(sim))) { + sim->cpu.step = 1; + return 1; + } + + /* Update state */ + cpuAdvance(sim, cpuClocks(4)); /* TODO: Research */ + } + return 0; +} + + + +/************************** Instruction Operations ***************************/ + +/* ADD immediate */ +static void cpuADDImm(VB *sim) { + cpuSetReg2(sim, cpuAdd(sim, cpuGetReg2(sim), cpuGetImm5S(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* ADD register */ +static void cpuADDReg(VB *sim) { + cpuSetReg2(sim, cpuAdd(sim, cpuGetReg2(sim), cpuGetReg1(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* ADDF.S */ +static void cpuADDF_S(VB *sim) { + int32_t a = cpuGetReg2(sim); + int32_t b = cpuGetReg1(sim); + double c; /* Result */ + int32_t d; /* Float bits for result */ + if (cpuFloatReserved(sim, a, b)) + return; + c = (double) cpuWordToFloat(a) + cpuWordToFloat(b); + if (cpuFloatCommon(sim, c, &d)) + return; + cpuSetReg2(sim, d); + cpuAdvance(sim, cpuClocks(28)); /* TODO: Research */ +} + +/* ADDI */ +static void cpuADDI(VB *sim) { + cpuSetReg2(sim, cpuAdd(sim, cpuGetReg1(sim), cpuGetImm16S(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* AND */ +static void cpuAND(VB *sim) { + cpuSetReg2(sim, cpuBitwise(sim, cpuGetReg2(sim) & cpuGetReg1(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* ANDBSU */ +static int cpuANDBSU(VB *sim) { + return cpuBSArithmetic(sim); +} + +/* ANDI */ +static void cpuANDI(VB *sim) { + cpuSetReg2(sim, cpuBitwise(sim, cpuGetReg1(sim) & cpuGetImm16U(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* ANDNBSU */ +static int cpuANDNBSU(VB *sim) { + return cpuBSArithmetic(sim); +} + +/* BCOND */ +static void cpuBCOND(VB *sim) { + if (cpuCondition(sim, cpuGetCond(sim))) + cpuJump(sim, sim->cpu.pc + cpuGetDisp9(sim)); + else cpuAdvance(sim, cpuClocks(1)); +} + +/* CAXI */ +static int cpuCAXI(VB *sim) { + switch (sim->cpu.step) { + + case 0: + auxData.address = cpuGetReg1(sim) + cpuGetImm16S(sim); + /* Fallthrough */ + + case 1: + + /* Read the compare value */ + if (cpuRead(sim, auxData.address, VB_S32, &auxData.value)) { + sim->cpu.step = 1; + return 1; + } + + /* Update state */ + sim->cpu.clocks += cpuClocks(1); /* TODO: Research */ + cpuSubtract(sim, cpuGetReg2(sim), auxData.value); + if (sim->cpu.psw.z) + auxData.value = sim->cpu.program[30]; + + /* Wait for clocks taken */ + sim->cpu.step = 2; + return 0; + + case 2: + + /* Write the exchange value */ + if (cpuWrite(sim, auxData.address, VB_S32, auxData.value)) + return 1; + + /* Update state */ + cpuSetReg2(sim, auxData.value); + cpuAdvance(sim, cpuClocks(25)); /* TODO: Research */ + } + return 0; +} + +/* CLI */ +static void cpuCLI(VB *sim) { + sim->cpu.psw.id = 0; + cpuAdvance(sim, cpuClocks(12)); +} + +/* CMP immediate */ +static void cpuCMPImm(VB *sim) { + cpuSubtract(sim, cpuGetReg2(sim), cpuGetImm5S(sim)); + cpuAdvance(sim, cpuClocks(1)); +} + +/* CMP register */ +static void cpuCMPReg(VB *sim) { + cpuSubtract(sim, cpuGetReg2(sim), cpuGetReg1(sim)); + cpuAdvance(sim, cpuClocks(1)); +} + +/* CMPF.S */ +static void cpuCMPF_S(VB *sim) { + int32_t a = cpuGetReg2(sim); + int32_t b = cpuGetReg1(sim); + double c; /* Result */ + int32_t d; /* Float bits for result */ + if (cpuFloatReserved(sim, a, b)) + return; + c = (double) cpuWordToFloat(a) - cpuWordToFloat(b); + if (cpuFloatCommon(sim, c, &d)) + return; + cpuAdvance(sim, cpuClocks(10)); /* TODO: Research */ +} + +/* CVT.SW */ +static void cpuCVT_SW(VB *sim) { + double f; + int32_t w = cpuGetReg1(sim); + if (cpuFloatReservedOne(w)) { + cpuThrow(sim, 0xFF60); + return; + } + f = cpuWordToFloat(w); + if (f <= -2147483648.5 || f >= 2147483647.5) { + sim->cpu.psw.fiv = 1; + cpuThrow(sim, 0xFF70); + return; + } + w = f < 0 ? f - 0.5 : f + 0.5; + cpuSetReg2(sim, w); + if (w != f) + sim->cpu.psw.fpr = 1; + sim->cpu.psw.ov = 0; + sim->cpu.psw.s = w < 0; + sim->cpu.psw.z = w == 0; + cpuAdvance(sim, cpuClocks(14)); /* TODO: Research */ +} + +/* CVT.WS */ +static void cpuCVT_WS(VB *sim) { + int32_t w = cpuGetReg1(sim); + float f = w; + if (f != w) + sim->cpu.psw.fpr = 1; + w = cpuFloatToWord(f); + cpuSetReg2(sim, w); + sim->cpu.psw.cy = w < 0; + sim->cpu.psw.ov = 0; + sim->cpu.psw.s = w < 0; + sim->cpu.psw.z = w == 0; + cpuAdvance(sim, cpuClocks(16)); /* TODO: Research */ +} + +/* DIV */ +static void cpuDIV(VB *sim) { + int32_t a; /* Dividend */ + int32_t b; /* Divisor */ + int32_t c; /* Remainder */ + int32_t d; /* Quotient */ + #ifdef VB_DIV_GENERIC + int sa; /* Sign of a */ + int sb; /* Sign of b */ + #endif + + /* Zero division */ + b = cpuGetReg1(sim); + if (b == 0) { + cpuThrow(sim, 0xFF80); + return; + } + + /* Compute results */ + a = cpuGetReg2(sim); + sim->cpu.psw.ov = (uint32_t) a == (uint32_t) 0x80000000 && b == -1; + #ifndef VB_DIV_GENERIC + c = a % b; + d = a / b; + #else + + /* Do not divide */ + if (sim->cpu.psw.ov) { + c = 0; + d = a; + } + + /* Perform division */ + else { + + /* Take signs and absolute values of operands */ + sa = a < 0; + if (sa) + a = -a; + sb = b < 0; + if (sb) + b = -b; + + /* Compute results */ + c = (uint32_t) a % (uint32_t) b; + if (sa) + c = -c; + d = (uint32_t) a / (uint32_t) b; + if (sa ^ sb) + d = -d; + } + + #endif + sim->cpu.program[30] = c; + cpuSetReg2(sim, d); + + /* Flags */ + sim->cpu.psw.s = d < 0; + sim->cpu.psw.z = d == 0; + cpuAdvance(sim, cpuClocks(38)); +} + +/* DIVF.S */ +static void cpuDIVF_S(VB *sim) { + int32_t a = cpuGetReg2(sim); + int32_t b = cpuGetReg1(sim); + double c; /* Result */ + int32_t d; /* Float bits for result */ + if (cpuFloatReserved(sim, a, b)) + return; + if ((b & 0x7FFFFFFF) == 0) { + cpuThrow(sim, (a & 0x7FFFFFFF) == 0 ? 0xFF70 : 0xFF68); + return; + } + c = (double) cpuWordToFloat(a) / cpuWordToFloat(b); + if (cpuFloatCommon(sim, c, &d)) + return; + cpuSetReg2(sim, d); + cpuAdvance(sim, cpuClocks(44)); +} + +/* DIVU */ +static void cpuDIVU(VB *sim) { + uint32_t a; /* Dividend */ + uint32_t b; /* Divisor */ + int32_t c; /* Remainder */ + int32_t d; /* Quotient */ + + /* Zero division */ + b = cpuGetReg1(sim); + if (b == 0) { + cpuThrow(sim, 0xFF80); + return; + } + + /* Compute results */ + a = cpuGetReg2(sim); + c = a % b; + d = a / b; + sim->cpu.program[30] = c; + cpuSetReg2(sim, d); + + /* Flags */ + sim->cpu.psw.ov = 0; + sim->cpu.psw.s = d < 0; + sim->cpu.psw.z = d == 0; + cpuAdvance(sim, cpuClocks(36)); +} + +/* HALT */ +static void cpuHALT(VB *sim) { + sim->cpu.halt = 1; + sim->cpu.operation = CPU_HALTING; + /* TODO: Research clocks */ +} + +/* IN.B */ +static int cpuIN_B(VB *sim) { + return cpuLD_IN(sim, VB_U8); +} + +/* IN.H */ +static int cpuIN_H(VB *sim) { + return cpuLD_IN(sim, VB_U16); +} + +/* IN.W */ +static int cpuIN_W(VB *sim) { + return cpuLD_IN(sim, VB_S32); +} + +/* JAL */ +static void cpuJAL(VB *sim) { + sim->cpu.program[31] = sim->cpu.pc + 4; + cpuJump(sim, sim->cpu.pc + cpuGetDisp26(sim)); +} + +/* JMP */ +static void cpuJMP(VB *sim) { + cpuJump(sim, cpuGetReg1(sim)); +} + +/* JR */ +static void cpuJR(VB *sim) { + cpuJump(sim, sim->cpu.pc + cpuGetDisp26(sim)); +} + +/* LD.B */ +static int cpuLD_B(VB *sim) { + return cpuLD_IN(sim, VB_S8); +} + +/* LD.H */ +static int cpuLD_H(VB *sim) { + return cpuLD_IN(sim, VB_S16); +} + +/* LD.W */ +static int cpuLD_W(VB *sim) { + return cpuLD_IN(sim, VB_S32); +} + +/* LDSR */ +static void cpuLDSR(VB *sim) { + cpuSetSystemRegister(sim, cpuGetImm5U(sim), cpuGetReg2(sim), 0); + cpuAdvance(sim, cpuClocks(8)); +} + +/* MOV immediate */ +static void cpuMOVImm(VB *sim) { + cpuSetReg2(sim, cpuGetImm5S(sim)); + cpuAdvance(sim, cpuClocks(1)); +} + +/* MOV register */ +static void cpuMOVReg(VB *sim) { + cpuSetReg2(sim, cpuGetReg1(sim)); + cpuAdvance(sim, cpuClocks(1)); +} + +/* MOVBSU */ +static int cpuMOVBSU(VB *sim) { + return cpuBSArithmetic(sim); +} + +/* MOVEA */ +static void cpuMOVEA(VB *sim) { + cpuSetReg2(sim, cpuGetReg1(sim) + cpuGetImm16S(sim)); + cpuAdvance(sim, cpuClocks(1)); +} + +/* MOVHI */ +static void cpuMOVHI(VB *sim) { + cpuSetReg2(sim, cpuGetReg1(sim) + (cpuGetImm16U(sim) << 16)); + cpuAdvance(sim, cpuClocks(1)); +} + +/* MPYHW */ +static void cpuMPYHW(VB *sim) { + cpuSetReg2(sim, cpuGetReg2(sim) * SignExtend(cpuGetReg1(sim), 17)); + cpuAdvance(sim, cpuClocks(9)); +} + +/* MUL */ +static void cpuMUL(VB *sim) { + int64_t a = cpuGetReg2(sim); + int64_t b = cpuGetReg1(sim); + int64_t c = a * b; + int32_t d = c; + sim->cpu.program[30] = c >> 32; + cpuSetReg2(sim, d); + sim->cpu.psw.ov = d != c; + sim->cpu.psw.s = d < 0; + sim->cpu.psw.z = d == 0; + cpuAdvance(sim, 13); +} + +/* MULF.S */ +static void cpuMULF_S(VB *sim) { + int32_t a = cpuGetReg2(sim); + int32_t b = cpuGetReg1(sim); + double c; /* Result */ + int32_t d; /* Float bits for result */ + if (cpuFloatReserved(sim, a, b)) + return; + c = (double) cpuWordToFloat(a) * cpuWordToFloat(b); + if (cpuFloatCommon(sim, c, &d)) + return; + cpuSetReg2(sim, d); + cpuAdvance(sim, cpuClocks(30)); /* TODO: Research */ +} + +/* MULU */ +static void cpuMULU(VB *sim) { + uint64_t a = (uint32_t) cpuGetReg2(sim); + uint64_t b = (uint32_t) cpuGetReg1(sim); + uint64_t c = a * b; + uint32_t d = c; + sim->cpu.program[30] = c >> 32; + cpuSetReg2(sim, d); + sim->cpu.psw.ov = d != c; + sim->cpu.psw.s = (int32_t) d < 0; + sim->cpu.psw.z = d == 0; + cpuAdvance(sim, 13); +} + +/* NOT */ +static void cpuNOT(VB *sim) { + cpuSetReg2(sim, cpuBitwise(sim, ~cpuGetReg1(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* NOTBSU */ +static int cpuNOTBSU(VB *sim) { + return cpuBSArithmetic(sim); +} + +/* OR */ +static void cpuOR(VB *sim) { + cpuSetReg2(sim, cpuBitwise(sim, cpuGetReg2(sim) | cpuGetReg1(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* ORBSU */ +static int cpuORBSU(VB *sim) { + return cpuBSArithmetic(sim); +} + +/* ORI */ +static void cpuORI(VB *sim) { + cpuSetReg2(sim, cpuBitwise(sim, cpuGetReg1(sim) | cpuGetImm16U(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* ORNBSU */ +static int cpuORNBSU(VB *sim) { + return cpuBSArithmetic(sim); +} + +/* OUT.B */ +static int cpuOUT_B(VB *sim) { + return cpuST_OUT(sim, VB_U8); +} + +/* OUT.H */ +static int cpuOUT_H(VB *sim) { + return cpuST_OUT(sim, VB_U16); +} + +/* OUT.W */ +static int cpuOUT_W(VB *sim) { + return cpuST_OUT(sim, VB_S32); +} + +/* RETI */ +static void cpuRETI(VB *sim) { + if (sim->cpu.psw.np) { + sim->cpu.nextPC = sim->cpu.fepc; + cpuSetSystemRegister(sim, VB_PSW, sim->cpu.fepsw, 0); + } else { + sim->cpu.nextPC = sim->cpu.eipc; + cpuSetSystemRegister(sim, VB_PSW, sim->cpu.eipsw, 0); + } + sim->cpu.clocks += cpuClocks(10); + sim->cpu.operation = CPU_FETCH; +} + +/* REV */ +static void cpuREV(VB *sim) { + uint32_t x = cpuGetReg1(sim); + x = (x << 16 & 0xFFFF0000) | (x >> 16 & 0x0000FFFF); + x = (x << 8 & 0xFF00FF00) | (x >> 8 & 0x00FF00FF); + x = (x << 4 & 0xF0F0F0F0) | (x >> 4 & 0x0F0F0F0F); + x = (x << 2 & 0xCCCCCCCC) | (x >> 2 & 0x33333333); + x = (x << 1 & 0xAAAAAAAA) | (x >> 1 & 0x55555555); + cpuSetReg2(sim, x); + cpuAdvance(sim, cpuClocks(22)); +} + +/* SAR immediate */ +static void cpuSARImm(VB *sim) { + cpuSetReg2(sim, cpuShiftArithmetic(sim, cpuGetImm5U(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* SAR register */ +static void cpuSARReg(VB *sim) { + cpuSetReg2(sim, cpuShiftArithmetic(sim, cpuGetReg1(sim) & 31)); + cpuAdvance(sim, cpuClocks(1)); +} + +/* SCH0BSD */ +static int cpuSCH0BSD(VB *sim) { + return cpuBSSearch(sim, 0, -1); +} + +/* SCH0BSU */ +static int cpuSCH0BSU(VB *sim) { + return cpuBSSearch(sim, 0, +1); +} + +/* SCH1BSD */ +static int cpuSCH1BSD(VB *sim) { + return cpuBSSearch(sim, 1, -1); +} + +/* SCH1BSU */ +static int cpuSCH1BSU(VB *sim) { + return cpuBSSearch(sim, 1, +1); +} + +/* SEI */ +static void cpuSEI(VB *sim) { + sim->cpu.psw.id = 1; + cpuAdvance(sim, cpuClocks(12)); +} + +/* SETF */ +static void cpuSETF(VB *sim) { + cpuSetReg2(sim, cpuCondition(sim, cpuGetImm5U(sim) & 15)); + cpuAdvance(sim, cpuClocks(1)); +} + +/* SHL immediate */ +static void cpuSHLImm(VB *sim) { + cpuSetReg2(sim, cpuShiftLeft(sim, cpuGetImm5U(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* SHL register */ +static void cpuSHLReg(VB *sim) { + cpuSetReg2(sim, cpuShiftLeft(sim, cpuGetReg1(sim) & 31)); + cpuAdvance(sim, cpuClocks(1)); +} + +/* SHR immediate */ +static void cpuSHRImm(VB *sim) { + cpuSetReg2(sim, cpuShiftRight(sim, cpuGetImm5U(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* SHR register */ +static void cpuSHRReg(VB *sim) { + cpuSetReg2(sim, cpuShiftRight(sim, cpuGetReg1(sim) & 31)); + cpuAdvance(sim, cpuClocks(1)); +} + +/* ST.B */ +static int cpuST_B(VB *sim) { + return cpuST_OUT(sim, VB_S8); +} + +/* ST.H */ +static int cpuST_H(VB *sim) { + return cpuST_OUT(sim, VB_S16); +} + +/* ST.W */ +static int cpuST_W(VB *sim) { + return cpuST_OUT(sim, VB_S32); +} + +/* STSR */ +static void cpuSTSR(VB *sim) { + cpuSetReg2(sim, cpuGetSystemRegister(sim, cpuGetImm5U(sim))); + cpuAdvance(sim, cpuClocks(8)); +} + +/* SUB */ +static void cpuSUB(VB *sim) { + cpuSetReg2(sim, cpuSubtract(sim, cpuGetReg2(sim), cpuGetReg1(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* SUBF.S */ +static void cpuSUBF_S(VB *sim) { + int32_t a = cpuGetReg2(sim); + int32_t b = cpuGetReg1(sim); + double c; /* Result */ + int32_t d; /* Float bits for result */ + if (cpuFloatReserved(sim, a, b)) + return; + c = (double) cpuWordToFloat(a) - cpuWordToFloat(b); + if (cpuFloatCommon(sim, c, &d)) + return; + cpuSetReg2(sim, d); + cpuAdvance(sim, cpuClocks(28)); /* TODO: Research */ +} + +/* TRAP */ +static void cpuTRAP(VB *sim) { + sim->cpu.clocks += cpuClocks(15); + cpuThrow(sim, 0xFFA0 + cpuGetImm5U(sim)); +} + +/* TRNC.SW */ +static void cpuTRNC_SW(VB *sim) { + double f; + int32_t w = cpuGetReg1(sim); + if (cpuFloatReservedOne(w)) { + cpuThrow(sim, 0xFF60); + return; + } + f = cpuWordToFloat(w); + if (f <= -2147483649.0 || f >= 2147483648.0) { + sim->cpu.psw.fiv = 1; + cpuThrow(sim, 0xFF70); + return; + } + w = f; + cpuSetReg2(sim, w); + if (w != f) + sim->cpu.psw.fpr = 1; + sim->cpu.psw.ov = 0; + sim->cpu.psw.s = w < 0; + sim->cpu.psw.z = w == 0; + cpuAdvance(sim, cpuClocks(14)); /* TODO: Research */ +} + +/* XB */ +static void cpuXB(VB *sim) { + uint32_t x = cpuGetReg2(sim); + x = (x & 0xFFFF0000) | (x << 8 & 0x0000FF00) | (x >> 8 & 0x000000FF); + cpuSetReg2(sim, x); + cpuAdvance(sim, cpuClocks(6)); +} + +/* XH */ +static void cpuXH(VB *sim) { + uint32_t x = cpuGetReg2(sim); + x = (x << 16 & 0xFFFF0000) | (x >> 16 & 0x0000FFFF); + cpuSetReg2(sim, x); + cpuAdvance(sim, cpuClocks(1)); +} + +/* XOR */ +static void cpuXOR(VB *sim) { + cpuSetReg2(sim, cpuBitwise(sim, cpuGetReg2(sim) ^ cpuGetReg1(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* XORBSU */ +static int cpuXORBSU(VB *sim) { + return cpuBSArithmetic(sim); +} + +/* XORI */ +static void cpuXORI(VB *sim) { + cpuSetReg2(sim, cpuBitwise(sim, cpuGetReg1(sim) ^ cpuGetImm16U(sim))); + cpuAdvance(sim, cpuClocks(1)); +} + +/* XORNBSU */ +static int cpuXORNBSU(VB *sim) { + return cpuBSArithmetic(sim); +} + + + +/***************************** Library Functions *****************************/ + +/* Process component */ +static int cpuEmulate(VB *sim, uint32_t clocks) { + int brk = 0; + + /* Process until there are clocks to wait */ + for (;;) { + + /* The next event is after the time remaining */ + if (sim->cpu.clocks > clocks) { + sim->cpu.clocks -= clocks; + return 0; + } + + /* Advance forward the CPU's number of clocks */ + if (sim->cpu.clocks != 0) { + clocks -= sim->cpu.clocks; + sim->cpu.clocks = 0; + } + + /* Processing by operation ID */ + switch (sim->cpu.operation) { + case CPU_EXCEPTION: brk = cpuException(sim); break; + case CPU_FATAL : return 0; + case CPU_FETCH : brk = cpuFetch (sim); break; + case CPU_HALTING : if (cpuHalt(sim)) return 0; break; + + case CPU_ADD_IMM: cpuADDImm (sim); break; + case CPU_ADD_REG: cpuADDReg (sim); break; + case CPU_ADDF_S : cpuADDF_S (sim); break; + case CPU_ADDI : cpuADDI (sim); break; + case CPU_AND : cpuAND (sim); break; + case CPU_ANDBSU : cpuANDBSU (sim); break; + case CPU_ANDI : cpuANDI (sim); break; + case CPU_ANDNBSU: cpuANDNBSU(sim); break; + case CPU_BCOND : cpuBCOND (sim); break; + case CPU_CAXI : brk = cpuCAXI (sim); break; + case CPU_CLI : cpuCLI (sim); break; + case CPU_CMP_IMM: cpuCMPImm (sim); break; + case CPU_CMP_REG: cpuCMPReg (sim); break; + case CPU_CMPF_S : cpuCMPF_S (sim); break; + case CPU_CVT_SW : cpuCVT_SW (sim); break; + case CPU_CVT_WS : cpuCVT_WS (sim); break; + case CPU_DIV : cpuDIV (sim); break; + case CPU_DIVF_S : cpuDIVF_S (sim); break; + case CPU_DIVU : cpuDIVU (sim); break; + case CPU_HALT : cpuHALT (sim); break; + case CPU_IN_B : brk = cpuIN_B (sim); break; + case CPU_IN_H : brk = cpuIN_H (sim); break; + case CPU_IN_W : brk = cpuIN_W (sim); break; + case CPU_JAL : cpuJAL (sim); break; + case CPU_JMP : cpuJMP (sim); break; + case CPU_JR : cpuJR (sim); break; + case CPU_LD_B : brk = cpuLD_B (sim); break; + case CPU_LD_H : brk = cpuLD_H (sim); break; + case CPU_LD_W : brk = cpuLD_W (sim); break; + case CPU_LDSR : cpuLDSR (sim); break; + case CPU_MOV_IMM: cpuMOVImm (sim); break; + case CPU_MOV_REG: cpuMOVReg (sim); break; + case CPU_MOVBSU : cpuMOVBSU (sim); break; + case CPU_MOVEA : cpuMOVEA (sim); break; + case CPU_MOVHI : cpuMOVHI (sim); break; + case CPU_MPYHW : cpuMPYHW (sim); break; + case CPU_MUL : cpuMUL (sim); break; + case CPU_MULF_S : cpuMULF_S (sim); break; + case CPU_MULU : cpuMULU (sim); break; + case CPU_NOT : cpuNOT (sim); break; + case CPU_NOTBSU : cpuNOTBSU (sim); break; + case CPU_OR : cpuOR (sim); break; + case CPU_ORBSU : cpuORBSU (sim); break; + case CPU_ORI : cpuORI (sim); break; + case CPU_ORNBSU : cpuORNBSU (sim); break; + case CPU_OUT_B : brk = cpuOUT_B (sim); break; + case CPU_OUT_H : brk = cpuOUT_H (sim); break; + case CPU_OUT_W : brk = cpuOUT_W (sim); break; + case CPU_RETI : cpuRETI (sim); break; + case CPU_REV : cpuREV (sim); break; + case CPU_SAR_IMM: cpuSARImm (sim); break; + case CPU_SAR_REG: cpuSARReg (sim); break; + case CPU_SCH0BSD: cpuSCH0BSD(sim); break; + case CPU_SCH0BSU: cpuSCH0BSU(sim); break; + case CPU_SCH1BSD: cpuSCH1BSD(sim); break; + case CPU_SCH1BSU: cpuSCH1BSU(sim); break; + case CPU_SEI : cpuSEI (sim); break; + case CPU_SETF : cpuSETF (sim); break; + case CPU_SHL_IMM: cpuSHLImm (sim); break; + case CPU_SHL_REG: cpuSHLReg (sim); break; + case CPU_SHR_IMM: cpuSHRImm (sim); break; + case CPU_SHR_REG: cpuSHRReg (sim); break; + case CPU_ST_B : brk = cpuST_B (sim); break; + case CPU_ST_H : brk = cpuST_H (sim); break; + case CPU_ST_W : brk = cpuST_W (sim); break; + case CPU_STSR : cpuSTSR (sim); break; + case CPU_SUB : cpuSUB (sim); break; + case CPU_SUBF_S : cpuSUBF_S (sim); break; + case CPU_TRAP : cpuTRAP (sim); break; + case CPU_TRNC_SW: cpuTRNC_SW(sim); break; + case CPU_XB : cpuXB (sim); break; + case CPU_XH : cpuXH (sim); break; + case CPU_XOR : cpuXOR (sim); break; + case CPU_XORBSU : cpuXORBSU (sim); break; + case CPU_XORI : cpuXORI (sim); break; + case CPU_XORNBSU: cpuXORNBSU(sim); break; + + default: return -1; /* TODO: Temporary for debugging */ + } + + /* A callback requested a break */ + if (brk) + return 1; + } + + return 0; +} + +/* Determine how many clocks are guaranteed to process */ +static uint32_t cpuUntil(VB *sim, uint32_t clocks) { + return sim->cpu.halt || sim->cpu.clocks > clocks ? + clocks : sim->cpu.clocks; +} + +#endif /* VBAPI */ diff --git a/core/vb.c b/core/vb.c new file mode 100644 index 0000000..6934313 --- /dev/null +++ b/core/vb.c @@ -0,0 +1,391 @@ +#ifndef VBAPI +#define VBAPI +#endif +#include +#include + + + +/*********************************** Types ***********************************/ + +/* Simulation state */ +struct VB { + + /* Game Pak */ + struct { + uint8_t *ram; /* Save RAM */ + uint8_t *rom; /* Program ROM */ + uint32_t ramMask; /* Size of SRAM - 1 */ + uint32_t romMask; /* Size of ROM - 1 */ + } cart; + + /* CPU */ + struct { + + /* Cache Control Word */ + struct { + uint8_t ice; /* Instruction Cache Enable */ + } chcw; + + /* Exception Cause Register */ + struct { + uint16_t eicc; /* Exception/Interrupt Cause Code */ + uint16_t fecc; /* Fatal Error Cause Code */ + } ecr; + + /* Program Status Word */ + struct { + uint8_t ae; /* Address Trap Enable */ + uint8_t cy; /* Carry */ + uint8_t ep; /* Exception Pending */ + uint8_t fiv; /* Floating Invalid */ + uint8_t fov; /* Floating Overflow */ + uint8_t fpr; /* Floading Precision */ + uint8_t fro; /* Floating Reserved Operand */ + uint8_t fud; /* Floading Underflow */ + uint8_t fzd; /* Floating Zero Divide */ + uint8_t i; /* Interrupt Level */ + uint8_t id; /* Interrupt Disable */ + uint8_t np; /* NMI Pending */ + uint8_t ov; /* Overflow */ + uint8_t s; /* Sign */ + uint8_t z; /* Zero */ + } psw; + + /* Other registers */ + uint32_t adtre; /* Address Trap Register for Execution */ + uint32_t eipc; /* Exception/Interrupt PC */ + uint32_t eipsw; /* Exception/Interrupt PSW */ + uint32_t fepc; /* Fatal Error PC */ + uint32_t fepsw; /* Fatal Error PSW */ + uint32_t pc; /* Program Counter */ + int32_t program[32]; /* Program registers */ + uint32_t sr29; /* System register 29 */ + uint32_t sr31; /* System register 31 */ + + /* Working data */ + union { + struct { + uint32_t dest; + uint64_t src; + } bs; /* Arithmetic bit strings */ + struct { + uint32_t address; + int32_t value; + } data; /* Data accesses */ + } aux; + + /* Other state */ + uint32_t clocks; /* Master clocks to wait */ + uint16_t code[2]; /* Instruction code units */ + uint16_t exception; /* Exception cause code */ + int halt; /* CPU is halting */ + uint16_t irq; /* Interrupt request lines */ + int length; /* Instruction code length */ + uint32_t nextPC; /* Address of next instruction */ + int operation; /* Current operation ID */ + int step; /* Operation sub-task ID */ + } cpu; + + /* Other system state */ + uint8_t wram[0x10000]; /* System RAM */ + + /* Application data */ + vbOnException onException; /* CPU exception */ + vbOnExecute onExecute; /* CPU instruction execute */ + vbOnFetch onFetch; /* CPU instruction fetch */ + vbOnRead onRead; /* CPU instruction read */ + vbOnWrite onWrite; /* CPU instruction write */ + void *tag; /* User data */ +}; + + + +/***************************** Library Functions *****************************/ + +/* Sign-extend an integer of variable width */ +static int32_t SignExtend(int32_t value, int32_t bits) { + #ifndef VB_SIGNED_PROPAGATE + value &= ~((uint32_t) 0xFFFFFFFF << bits); + bits = (int32_t) 1 << (bits - (int32_t) 1); + return (value ^ bits) - bits; + #else + return value << (32 - bits) >> (32 - bits); + #endif +} + + + +/******************************** Sub-Modules ********************************/ + +#include "bus.c" +#include "cpu.c" + + + +/***************************** Library Functions *****************************/ + +/* Process a simulation for a given number of clocks */ +static int sysEmulate(VB *sim, uint32_t clocks) { + return + cpuEmulate(sim, clocks) + ; +} + +/* Determine how many clocks are guaranteed to process */ +static uint32_t sysUntil(VB *sim, uint32_t clocks) { + clocks = cpuUntil(sim, clocks); + return clocks; +} + + + +/******************************* API Commands ********************************/ + +/* Process one simulation */ +VBAPI int vbEmulate(VB *sim, uint32_t *clocks) { + int brk; /* A callback requested a break */ + uint32_t until; /* Clocks guaranteed to process */ + while (*clocks != 0) { + until = sysUntil(sim, *clocks); + brk = sysEmulate(sim, until); + *clocks -= until; + if (brk) + return brk; /* TODO: return 1 */ + } + return 0; +} + +/* Process multiple simulations */ +VBAPI int vbEmulateEx(VB **sims, int count, uint32_t *clocks) { + int brk; /* A callback requested a break */ + uint32_t until; /* Clocks guaranteed to process */ + int x; /* Iterator */ + while (*clocks != 0) { + until = *clocks; + for (x = count - 1; x >= 0; x--) + until = sysUntil(sims[x], until); + + brk = 0; + for (x = count - 1; x >= 0; x--) + brk |= sysEmulate(sims[x], until); + + *clocks -= until; + if (brk) + return brk; /* TODO: return 1 */ + } + return 0; +} + +/* Retrieve the game pack RAM buffer */ +VBAPI void* vbGetCartRAM(VB *sim, uint32_t *size) { + if (size != NULL) + *size = sim->cart.ram == NULL ? 0 : sim->cart.ramMask + 1; + return sim->cart.ram; +} + +/* Retrieve the game pack ROM buffer */ +VBAPI void* vbGetCartROM(VB *sim, uint32_t *size) { + if (size != NULL) + *size = sim->cart.rom == NULL ? 0 : sim->cart.romMask + 1; + return sim->cart.rom; +} + +/* Retrieve the exception callback handle */ +VBAPI vbOnException vbGetExceptionCallback(VB *sim) { + return sim->onException; +} + +/* Retrieve the execute callback handle */ +VBAPI vbOnExecute vbGetExecuteCallback(VB *sim) { + return sim->onExecute; +} + +/* Retrieve the fetch callback handle */ +VBAPI vbOnFetch vbGetFetchCallback(VB *sim) { + return sim->onFetch; +} + +/* Retrieve the value of the program counter */ +VBAPI uint32_t vbGetProgramCounter(VB *sim) { + return sim->cpu.pc; +} + +/* Retrieve the value in a program register */ +VBAPI int32_t vbGetProgramRegister(VB *sim, int index) { + return index < 1 || index > 31 ? 0 : sim->cpu.program[index]; +} + +/* Retrieve the read callback handle */ +VBAPI vbOnRead vbGetReadCallback(VB *sim) { + return sim->onRead; +} + +/* Retrieve the value in a system register */ +VBAPI uint32_t vbGetSystemRegister(VB *sim, int index) { + return index < 0 || index > 31 ? 0 : cpuGetSystemRegister(sim, index); +} + +/* Retrieve a simulation's userdata pointer */ +VBAPI void* vbGetUserData(VB *sim) { + return sim->tag; +} + +/* Retrieve the write callback handle */ +VBAPI vbOnWrite vbGetWriteCallback(VB *sim) { + return sim->onWrite; +} + +/* Initialize a simulation instance */ +VBAPI VB* vbInit(VB *sim) { + sim->cart.ram = NULL; + sim->cart.rom = NULL; + sim->onExecute = NULL; + sim->onFetch = NULL; + sim->onRead = NULL; + sim->onWrite = NULL; + vbReset(sim); + return sim; +} + +/* Read a value from the memory bus */ +VBAPI int32_t vbRead(VB *sim, uint32_t address, int type) { + int32_t value; + if (type < 0 || type > 4) + return 0; + busRead(sim, address, type, &value); + return value; +} + +/* Simulate a hardware reset */ +VBAPI VB* vbReset(VB *sim) { + uint32_t x; /* Iterator */ + + /* WRAM (the hardware does not do this) */ + for (x = 0; x < 0x10000; x++) + sim->wram[x] = 0x00; + + /* CPU (normal) */ + sim->cpu.exception = 0; + sim->cpu.halt = 0; + sim->cpu.irq = 0; + sim->cpu.pc = 0xFFFFFFF0; + cpuSetSystemRegister(sim, VB_ECR, 0x0000FFF0, 1); + cpuSetSystemRegister(sim, VB_PSW, 0x00008000, 1); + + /* CPU (extra, hardware does not do this) */ + sim->cpu.adtre = 0x00000000; + sim->cpu.eipc = 0x00000000; + sim->cpu.eipsw = 0x00000000; + sim->cpu.fepc = 0x00000000; + sim->cpu.fepsw = 0x00000000; + sim->cpu.sr29 = 0x00000000; + sim->cpu.sr31 = 0x00000000; + cpuSetSystemRegister(sim, VB_CHCW, 0x00000000, 1); + for (x = 0; x < 32; x++) + sim->cpu.program[x] = 0x00000000; + + /* CPU (other) */ + sim->cpu.clocks = 0; + sim->cpu.nextPC = 0xFFFFFFF0; + sim->cpu.operation = CPU_FETCH; + sim->cpu.step = 0; + + return sim; +} + +/* Specify a game pak RAM buffer */ +VBAPI int vbSetCartRAM(VB *sim, void *sram, uint32_t size) { + if (sram != NULL) { + if (size < 16 || size > 0x1000000 || (size & (size - 1)) != 0) + return 1; + sim->cart.ramMask = size - 1; + } + sim->cart.ram = sram; + return 0; +} + +/* Specify a game pak ROM buffer */ +VBAPI int vbSetCartROM(VB *sim, void *rom, uint32_t size) { + if (rom != NULL) { + if (size < 16 || size > 0x1000000 || (size & (size - 1)) != 0) + return 1; + sim->cart.romMask = size - 1; + } + sim->cart.rom = rom; + return 0; +} + +/* Specify a new exception callback handle */ +VBAPI vbOnException vbSetExceptionCallback(VB *sim, vbOnException callback) { + vbOnException prev = sim->onException; + sim->onException = callback; + return prev; +} + +/* Specify a new execute callback handle */ +VBAPI vbOnExecute vbSetExecuteCallback(VB *sim, vbOnExecute callback) { + vbOnExecute prev = sim->onExecute; + sim->onExecute = callback; + return prev; +} + +/* Specify a new fetch callback handle */ +VBAPI vbOnFetch vbSetFetchCallback(VB *sim, vbOnFetch callback) { + vbOnFetch prev = sim->onFetch; + sim->onFetch = callback; + return prev; +} + +/* Specify a new value for the program counter */ +VBAPI uint32_t vbSetProgramCounter(VB *sim, uint32_t value) { + sim->cpu.operation = CPU_FETCH; + sim->cpu.pc = sim->cpu.nextPC = value & 0xFFFFFFFE; + sim->cpu.step = 0; + return sim->cpu.pc; +} + +/* Specify a new value for a program register */ +VBAPI int32_t vbSetProgramRegister(VB *sim, int index, int32_t value) { + return index < 1 || index > 31 ? 0 : (sim->cpu.program[index] = value); +} + +/* Specify a new read callback handle */ +VBAPI vbOnRead vbSetReadCallback(VB *sim, vbOnRead callback) { + vbOnRead prev = sim->onRead; + sim->onRead = callback; + return prev; +} + +/* Specify a new value for a system register */ +VBAPI uint32_t vbSetSystemRegister(VB *sim, int index, uint32_t value) { + return index < 0 || index > 31 ? 0 : + cpuSetSystemRegister(sim, index, value, 1); +} + +/* Specify a new write callback handle */ +VBAPI vbOnWrite vbSetWriteCallback(VB *sim, vbOnWrite callback) { + vbOnWrite prev = sim->onWrite; + sim->onWrite = callback; + return prev; +} + +/* Determine the size of a simulation instance */ +VBAPI size_t vbSizeOf() { + return sizeof (VB); +} + +/* Specify a simulation's userdata pointer */ +VBAPI void* vbSetUserData(VB *sim, void *tag) { + void *prev = sim->tag; + sim->tag = tag; + return prev; +} + +/* Write a value to the memory bus */ +VBAPI int32_t vbWrite(VB *sim, uint32_t address, int type, int32_t value) { + if (type < 0 || type > 4) + return 0; + busWrite(sim, address, type, value, 1); + return vbRead(sim, address, type); +} diff --git a/core/vb.h b/core/vb.h new file mode 100644 index 0000000..39caf2b --- /dev/null +++ b/core/vb.h @@ -0,0 +1,143 @@ +#ifndef VB_H_ +#define VB_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef VBAPI +#define VBAPI extern +#endif + +/* Header includes */ +#include +#include + + + +/**************************** Compilation Control ****************************/ + +/* + VB_DIRECT_EXCEPTION + VB_DIRECT_EXECUTE + VB_DIRECT_FETCH + VB_DIRECT_READ + VB_DIRECT_WRITE + + Implements callbacks as direct function calls with names in the form of + vbxOn() with the same prototypes as the regular function pointer + callbacks. If left undefined, the function pointers are used. +*/ + +/* + VB_DIV_GENERIC + + Provides a generic implementation for the DIV instruction that guarantees the + correct results for quotient and remainder. If left undefined, an accelerated + implementation is used that depends on the compiler's behavior. +*/ + +/* + VB_LITTLE_ENDIAN + + Accelerates simulated memory reads and writes on hosts with little-endian + byte ordering. If left undefined, a generic implementation is used instead. +*/ + +/* + VB_SIGNED_PROPAGATE + + Accelerates sign extension by assuming the >> operator propagates the sign + bit for signed types and does not for unsigned types. If left undefined, a + generic implementation is used instead. +*/ + + + + +/********************************* Constants *********************************/ + +/* Callback IDs */ +#define VB_EXCEPTION 0 +#define VB_EXECUTE 1 +#define VB_FETCH 2 +#define VB_FRAME 3 +#define VB_READ 4 +#define VB_WRITE 5 + +/* System registers */ +#define VB_ADTRE 25 +#define VB_CHCW 24 +#define VB_ECR 4 +#define VB_EIPC 0 +#define VB_EIPSW 1 +#define VB_FEPC 2 +#define VB_FEPSW 3 +#define VB_PIR 6 +#define VB_PSW 5 +#define VB_TKCW 7 + +/* Memory access data types */ +#define VB_S8 0 +#define VB_U8 1 +#define VB_S16 2 +#define VB_U16 3 +#define VB_S32 4 + + + +/*********************************** Types ***********************************/ + +/* Simulation state */ +typedef struct VB VB; + +/* Callbacks */ +typedef int (*vbOnException)(VB *sim, uint16_t *cause); +typedef int (*vbOnExecute )(VB *sim, uint32_t address, const uint16_t *code, int length); +typedef int (*vbOnFetch )(VB *sim, int fetch, uint32_t address, int32_t *value, uint32_t *cycles); +typedef int (*vbOnRead )(VB *sim, uint32_t address, int type, int32_t *value, uint32_t *cycles); +typedef int (*vbOnWrite )(VB *sim, uint32_t address, int type, int32_t *value, uint32_t *cycles, int *cancel); + + + + +/******************************* API Commands ********************************/ + +VBAPI int vbEmulate (VB *sim, uint32_t *clocks); +VBAPI int vbEmulateEx (VB **sims, int count, uint32_t *clocks); +VBAPI void* vbGetCallback (VB *sim, int id); +VBAPI void* vbGetCartRAM (VB *sim, uint32_t *size); +VBAPI void* vbGetCartROM (VB *sim, uint32_t *size); +VBAPI vbOnException vbGetExceptionCallback(VB *sim); +VBAPI vbOnExecute vbGetExecuteCallback (VB *sim); +VBAPI vbOnFetch vbGetFetchCallback (VB *sim); +VBAPI uint32_t vbGetProgramCounter (VB *sim); +VBAPI int32_t vbGetProgramRegister (VB *sim, int index); +VBAPI vbOnRead vbGetReadCallback (VB *sim); +VBAPI uint32_t vbGetSystemRegister (VB *sim, int index); +VBAPI void* vbGetUserData (VB *sim); +VBAPI vbOnWrite vbGetWriteCallback (VB *sim); +VBAPI VB* vbInit (VB *sim); +VBAPI int32_t vbRead (VB *sim, uint32_t address, int type); +VBAPI VB* vbReset (VB *sim); +VBAPI int vbSetCartRAM (VB *sim, void *sram, uint32_t size); +VBAPI int vbSetCartROM (VB *sim, void *rom, uint32_t size); +VBAPI vbOnException vbSetExceptionCallback(VB *sim, vbOnException callback); +VBAPI vbOnExecute vbSetExecuteCallback (VB *sim, vbOnExecute callback); +VBAPI vbOnFetch vbSetFetchCallback (VB *sim, vbOnFetch callback); +VBAPI uint32_t vbSetProgramCounter (VB *sim, uint32_t value); +VBAPI int32_t vbSetProgramRegister (VB *sim, int index, int32_t value); +VBAPI vbOnRead vbSetReadCallback (VB *sim, vbOnRead callback); +VBAPI uint32_t vbSetSystemRegister (VB *sim, int index, uint32_t value); +VBAPI void* vbSetUserData (VB *sim, void *tag); +VBAPI vbOnWrite vbSetWriteCallback (VB *sim, vbOnWrite callback); +VBAPI size_t vbSizeOf (); +VBAPI int32_t vbWrite (VB *sim, uint32_t address, int type, int32_t value); + + + +#ifdef __cplusplus +} +#endif + +#endif /* VB_H_ */ diff --git a/makefile b/makefile new file mode 100644 index 0000000..3be4908 --- /dev/null +++ b/makefile @@ -0,0 +1,49 @@ +.PHONY: help +help: + @echo + @echo "Shrooms Virtual Boy core module - October 14, 2024" + @echo + @echo "Target build environment is any Debian with the following packages:" + @echo " emscripten" + @echo " gcc" + @echo + @echo "Available make targets:" + @echo " clean Remove output files" + @echo " core Check the C core source for compiler warnings" + @echo " wasm Build the WebAssembly module" + @echo + +.PHONY: clean +clean: + @rm -f web/core.wasm + +.PHONY: core +core: +# GCC generic + @gcc core/vb.c -I core -c -o /dev/null \ + -Werror -std=c90 -Wall -Wextra -Wpedantic +# GCC compilation control + @gcc core/vb.c -I core -c -o /dev/null \ + -Werror -std=c90 -Wall -Wextra -Wpedantic \ + -D VB_LITTLE_ENDIAN -D VB_SIGNED_PROPAGATE -D VB_DIV_GENERIC \ + -D VB_DIRECT_EXCEPTION -D VB_DIRECT_EXECUTE -D VB_DIRECT_FETCH \ + -D VB_DIRECT_READ -D VB_DIRECT_WRITE -D VB_DIV_GENERIC +# Clang generic + @emcc core/vb.c -I core -c -o /dev/null \ + -Werror -std=c90 -Wall -Wextra -Wpedantic +# Clang compilation control + @emcc core/vb.c -I core -c -o /dev/null \ + -Werror -std=c90 -Wall -Wextra -Wpedantic \ + -D VB_LITTLE_ENDIAN -D VB_SIGNED_PROPAGATE -D VB_DIV_GENERIC \ + -D VB_DIRECT_EXCEPTION -D VB_DIRECT_EXECUTE -D VB_DIRECT_FETCH \ + -D VB_DIRECT_READ -D VB_DIRECT_WRITE -D VB_DIV_GENERIC + +.PHONY: wasm +wasm: + @emcc -o web/core.wasm web/wasm.c core/vb.c -I core \ + -D VB_LITTLE_ENDIAN -D VB_SIGNED_PROPAGATE \ + -D "VBAPI=__attribute__((used))" \ + --no-entry -O2 -flto -s WASM=1 \ + -s EXPORTED_RUNTIME_METHODS=[] -s ALLOW_MEMORY_GROWTH \ + -s MAXIMUM_MEMORY=4GB -fno-strict-aliasing + @rm -f web/*.wasm.tmp*