diff --git a/core/bus.c b/core/bus.c index 59fe586..6ee9655 100644 --- a/core/bus.c +++ b/core/bus.c @@ -3,104 +3,73 @@ -/***************************** Utility Functions *****************************/ +/********************************* 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 *buffer, uint32_t size, uint32_t offset, int type) { +static int32_t busReadBuffer(uint8_t *data, int type) { - /* There is no data */ - if (buffer == NULL) - return 0; - - /* Mirror the buffer across its address range */ - offset &= size - 1; - - /* Processing by type */ + /* Processing by data type */ switch (type) { /* Generic implementation */ #ifndef VB_LITTLE_ENDIAN - case VB_S8 : - return (int8_t) buffer[offset]; - case VB_U8: - return buffer[offset]; - case VB_S16: - return (int32_t) (int8_t) - buffer[offset + 1] << 8 | buffer[offset]; - case VB_U16: - offset &= 0xFFFFFFFE; - return (int32_t) (int8_t) - buffer[offset + 1] << 8 | buffer[offset]; - case VB_S32: - offset &= 0xFFFFFFFC; - return - (int32_t) buffer[offset + 3] << 24 | - (int32_t) buffer[offset + 2] << 16 | - (int32_t) buffer[offset + 1] << 8 | - buffer[offset ] - ; + 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 implementation */ + /* Little-endian host */ #else - case VB_S8 : return *(int8_t *)&buffer[offset ]; - case VB_U8 : return buffer[offset ]; - case VB_S16: return *(int16_t *)&buffer[offset & 0xFFFFFFFE]; - case VB_U16: return *(uint16_t *)&buffer[offset & 0xFFFFFFFE]; - case VB_S32: return *(int32_t *)&buffer[offset & 0xFFFFFFFC]; + 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 } - /* Invalid type */ - return 0; + return 0; /* Unreachable */ } /* Write a typed value to a buffer in host memory */ -static void busWriteBuffer( - uint8_t *buffer, uint32_t size, uint32_t offset, int type, int32_t value) { +static void busWriteBuffer(uint8_t *data, int type, int32_t value) { - /* There is no data */ - if (buffer == NULL) - return; - - /* Mirror the buffer across its address range */ - offset &= size - 1; - - /* Processing by type */ + /* Processing by data type */ switch (type) { /* Generic implementation */ #ifndef VB_LITTLE_ENDIAN - case VB_S32: - offset &= 0xFFFFFFFC; - buffer[offset ] = value; - buffer[offset + 1] = value >> 8; - buffer[offset + 2] = value >> 16; - buffer[offset + 3] = value >> 24; - break; - case VB_S16: - case VB_U16: - offset &= 0xFFFFFFFE; - buffer[offset ] = value; - buffer[offset + 1] = value >> 8; - break; - case VB_S8 : - case VB_U8 : - buffer[offset ] = value; + 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 implementation */ + /* Little-endian host */ #else - case VB_S8 : - case VB_U8 : - buffer[offset ] = value; - break; - case VB_S16: - case VB_U16: - *(int16_t *)&buffer[offset & 0xFFFFFFFE] = value; - break; - case VB_S32: - *(int32_t *)&buffer[offset & 0xFFFFFFFC] = value; + 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 } @@ -109,54 +78,79 @@ static void busWriteBuffer( -/***************************** Module Functions ******************************/ +/***************************** Library Functions *****************************/ /* Read a typed value from the simulation bus */ -static int32_t busRead(VB *sim, uint32_t address, int type) { +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; - /* Processing by address region */ - switch (address >> 24 & 7) { - case 0: return 0; /* VIP */ - case 1: return 0; /* VSU */ - case 2: return 0; /* Misc. hardware */ - case 3: return 0; /* Unmapped */ - case 4: return 0; /* Game pak expansion */ - case 5: return /* WRAM */ - busReadBuffer(sim->wram , 0x10000 , address, type); - case 6: return /* Game pak RAM */ - busReadBuffer(sim->cart.ram, sim->cart.ramSize, address, type); - case 7: return /* Game pak ROM */ - busReadBuffer(sim->cart.rom, sim->cart.romSize, address, type); } - /* Unreachable */ - return 0; } /* Write a typed value to the simulation bus */ static void busWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){ - (void) debug; - /* Processing by address region */ - switch (address >> 24 & 7) { + /* 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. hardware */ + case 2: break; /* Misc. I/O */ case 3: break; /* Unmapped */ - case 4: break; /* Game pak expansion */ - case 5: /* WRAM */ - busWriteBuffer(sim->wram ,0x10000 ,address,type,value); + case 4: break; /* Game Pak expansion */ + + case 5: /* WRAM */ + busWriteBuffer(&sim->wram[address & 0x0000FFFF], type, value); break; - case 6: /* Game pak RAM */ - busWriteBuffer(sim->cart.ram,sim->cart.ramSize,address,type,value); + + 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 */ - busWriteBuffer(sim->cart.rom,sim->cart.romSize,address,type,value); + + 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 index 48135c9..375dbb5 100644 --- a/core/cpu.c +++ b/core/cpu.c @@ -5,98 +5,527 @@ /********************************* Constants *********************************/ -/* Pipeline stages */ -#define CPU_FETCH 0 -#define CPU_EXECUTE_A 1 -#define CPU_EXECUTE_B 2 -#define CPU_HALT 3 -#define CPU_FATAL 4 +/* Operation IDs */ +#define CPU_HALTING 0 +#define CPU_FATAL 1 +#define CPU_FETCH 2 +#define CPU_ILLEGAL 3 +#define CPU_BITSTRING 4 +#define CPU_FLOATENDO 5 +#define CPU_DOWN 6 +#define CPU_BITWISE 7 +#define CPU_UP 8 +#define CPU_ADD 9 +#define CPU_ADDF_S 10 +#define CPU_ADDI 11 +#define CPU_AND 12 +#define CPU_ANDI 13 +#define CPU_BCOND 14 +#define CPU_CAXI 15 +#define CPU_CLI 16 +#define CPU_CMP 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 35 +#define CPU_MOVEA 36 +#define CPU_MOVHI 37 +#define CPU_MPYHW 38 +#define CPU_MUL 39 +#define CPU_MULF_S 40 +#define CPU_MULU 41 +#define CPU_NOT 42 +#define CPU_OR 43 +#define CPU_ORI 44 +#define CPU_OUT_B 45 +#define CPU_OUT_H 46 +#define CPU_OUT_W 47 +#define CPU_RETI 48 +#define CPU_REV 49 +#define CPU_SAR 50 +#define CPU_SEI 51 +#define CPU_SETF 52 +#define CPU_SHL 53 +#define CPU_SHR 54 +#define CPU_ST_B 55 +#define CPU_ST_H 56 +#define CPU_ST_W 57 +#define CPU_STSR 58 +#define CPU_SUB 59 +#define CPU_SUBF_S 60 +#define CPU_TRAP 61 +#define CPU_TRNC_SW 62 +#define CPU_XB 63 +#define CPU_XH 64 +#define CPU_XOR 65 +#define CPU_XORI 66 + +/* 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 + +/* 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 +}; -/*********************************** Types ***********************************/ +/********************************** Macros ***********************************/ -/* Handler types */ -typedef int (*ExecuteAProc )(VB *); -typedef void (*ExecuteBProc )(VB *); -typedef void (*OperationProc)(VB *, int32_t *, int32_t); -typedef int32_t (*OperandProc )(VB *); +/* Master clocks per CPU cycles */ +#define cpuClocks(x) x -/* Opcode descriptor */ -typedef struct { - ExecuteAProc executeA; /* Execute handler (no state change) */ - ExecuteBProc executeB; /* Execute handler (update state) */ - OperationProc operation; /* Operation handler */ - OperandProc operand; /* Operand handler */ - uint8_t size; /* Total size in halfwords */ - uint8_t aux; /* Number of clocks or access data type */ -} OpDef; - -/* Floating-point auxiliary memory */ -typedef struct { - float f32; /* 32-bit result */ - double f64; /* 64-bit result */ -} FloatAux; +/* Shorthand */ +#define auxData sim->cpu.aux.data -/***************************** Utility Functions *****************************/ +/******************************** Lookup Data ********************************/ -/* Check for an interrupt exception condition */ -static int cpuCheckIRQs(VB *sim) { - int x; /* Iterator */ +/* Opdefs by opcode */ +static const uint8_t OPDEFS[] = { + CPU_MOV , CPU_ADD , CPU_SUB , CPU_CMP , /* 000000 */ + CPU_SHL , CPU_SHR , CPU_JMP , CPU_SAR , + CPU_MUL , CPU_DIV , CPU_MULU , CPU_DIVU , + CPU_OR , CPU_AND , CPU_XOR , CPU_NOT , + CPU_MOV , CPU_ADD , CPU_SETF , CPU_CMP , /* 010000 */ + CPU_SHL , CPU_SHR , CPU_CLI , CPU_SAR , + 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 +}; - /* Interrupts are masked */ - if (sim->cpu.psw.id || sim->cpu.psw.ep || sim->cpu.psw.np) - return 0; +/* Opdefs by bit string sub-opcode */ +static const uint8_t OPDEFS_BITSTRING[] = { + CPU_UP , CPU_DOWN , CPU_UP , CPU_DOWN , /* 00000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_BITWISE, CPU_BITWISE, CPU_BITWISE, CPU_BITWISE, /* 01000 */ + CPU_BITWISE, CPU_BITWISE, CPU_BITWISE, CPU_BITWISE, + 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 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 cpuExecute(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 + + + +/****************************** Pipeline Stages ******************************/ + +/* 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; + /* 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 (cpuExecute(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; - /* Check for interrupt requests */ - for (x = 4; x >= sim->cpu.psw.i; x--) { - if (!sim->cpu.irq[x]) - continue; - sim->cpu.exception = 0xFE00 | x << 4; - return 1; } - - /* No interrupt */ return 0; } -/* Test a condition */ -static int cpuCondition(VB *sim, int index) { - switch (index) { - case 0: return sim->cpu.psw.ov; /*V */ - case 1: return sim->cpu.psw.cy; /*C,L*/ - case 2: return sim->cpu.psw.z; /*E,Z*/ - case 3: return sim->cpu.psw.cy | sim->cpu.psw.z; /*NH */ - case 4: return sim->cpu.psw.s; /*N */ - case 5: return 1; /*T */ - case 6: return sim->cpu.psw.ov ^ sim->cpu.psw.s; /*LT */ - case 7: return (sim->cpu.psw.ov^sim->cpu.psw.s)|sim->cpu.psw.z;/*LE */ + + +/**************************** Instruction Helpers ****************************/ + +/* 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]; +} + +/* 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; +} + +/* Memory access 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); + sim->cpu.operation = CPU_FETCH; + sim->cpu.nextPC += sim->cpu.pc + 4; + sim->cpu.step = 0; } - return !cpuCondition(sim, index - 8); + return 0; +} + + + +/************************** Instruction Operations ***************************/ + +/* 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); +} + +/* JMP */ +static int cpuJMP(VB *sim) { + sim->cpu.nextPC = cpuGetReg1(sim) & 0xFFFFFFFE; + sim->cpu.clocks += cpuClocks(3); + sim->cpu.operation = CPU_FETCH; + /* TODO: Clear prefetch buffer */ + return 0; +} + +/* 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); +} + +/* MOVEA */ +static int cpuMOVEA(VB *sim) { + cpuSetReg2(sim, cpuGetReg1(sim) + cpuGetImm16S(sim)); + sim->cpu.clocks += cpuClocks(1); + sim->cpu.operation = CPU_FETCH; + sim->cpu.nextPC = sim->cpu.pc + 4; + return 0; +} + +/* MOVHI */ +static int cpuMOVHI(VB *sim) { + cpuSetReg2(sim, cpuGetReg1(sim) + (cpuGetImm16U(sim) << 16)); + sim->cpu.clocks += cpuClocks(1); + sim->cpu.operation = CPU_FETCH; + sim->cpu.nextPC = sim->cpu.pc + 4; + return 0; +} + + + +/***************************** Library Functions *****************************/ + +/* Process component */ +static int cpuEmulate(VB *sim, uint32_t clocks) { + int brk; + + /* 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_FETCH: brk = cpuFetch(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_JMP : brk = cpuJMP (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_MOVEA: brk = cpuMOVEA(sim); break; + case CPU_MOVHI: brk = cpuMOVHI(sim); break; + + default: return -1; /* TODO: Temporary for debugging */ + } + + /* A callback requested a break */ + if (brk) + return 1; + } + + return 0; } /* Retrieve the value of a system register */ -static uint32_t cpuGetSystemRegister(VB *sim, int id) { - switch (id) { +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_TKCW : return 0x000000E0; - case 29 : return sim->cpu.sr29; - case 30 : return 0x00000004; - case 31 : return sim->cpu.sr31; - case VB_CHCW : return - (uint32_t) sim->cpu.chcw.ice << 1 - ; - case VB_ECR : return - (uint32_t) sim->cpu.ecr.fecc << 16 | - (uint32_t) sim->cpu.ecr.eicc - ; case VB_PSW : return (uint32_t) sim->cpu.psw.i << 16 | (uint32_t) sim->cpu.psw.np << 15 | @@ -112,1538 +541,65 @@ static uint32_t cpuGetSystemRegister(VB *sim, int id) { (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 - ; + (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 0; /* Invalid ID */ + return 0x00000000; /* All others */ } -/* Read a memory value from the bus */ -static int cpuRead(VB *sim, uint32_t address, int type, int32_t *value) { - VBAccess access; /* Bus access descriptor */ - - /* Retrieve the value from the simulation state */ - access.clocks = 0; /* TODO: Needs research */ - access.value = busRead(sim, address, type); - - /* Call the breakpoint handler */ - if (sim->onRead != NULL) { - access.address = address; - access.type = type; - if (sim->onRead(sim, &access)) - return 1; - } - - /* Post-processing */ - sim->cpu.clocks += access.clocks; - *value = access.value; - return 0; -} - -/* Fetch an instruction code unit from the bus */ -static int cpuReadFetch(VB *sim) { - VBAccess access; /* Bus access descriptor */ - - /* Retrieve the value from the simulation state */ - access.address = sim->cpu.pc + (sim->cpu.step << 1); - access.clocks = 0; /* TODO: Prefetch makes this tricky */ - access.value = busRead(sim, access.address, VB_U16); - - /* Call the breakpoint handler */ - if (sim->onFetch != NULL) { - access.type = VB_U16; - if (sim->onFetch(sim, sim->cpu.step, &access)) - return 1; - } - - /* Post-processing */ - sim->cpu.clocks += access.clocks; - sim->cpu.inst.code[sim->cpu.step++] = access.value; - return 0; -} - -/* Detect a floating-point reserved operand */ -#define cpuFRO(x) ( \ - ( \ - ((x) & 0x007FFFFF) != 0x00000000 && /* Is not zero */ \ - ((x) & 0x7F800000) == 0x00000000 /* Is denormal */ \ - ) || ((x) & 0x7F800000) == 0x7F800000 /* Is indefinite/NaN */ \ -) - -/* Specify a value for a system register */ -static uint32_t cpuSetSystemRegister(VB *sim,int id,uint32_t value,int debug) { - switch (id) { +/* 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_TKCW : return 0x000000E0; - case 29 : return sim->cpu.sr29 = value; - case 30 : return 0x00000004; - case 31 : return - sim->cpu.sr31 = debug || value < (uint32_t) 0x80000000 ? - value : (uint32_t) -(int32_t)value; - case VB_CHCW: - /* TODO: Manage cache functions */ - sim->cpu.chcw.ice = value >> 1 & 1; - return value & 0x00000002; - case VB_ECR: - if (debug) { - sim->cpu.ecr.fecc = value >> 16 & 0xFFFF; - sim->cpu.ecr.eicc = value & 0xFFFF; - } - return (uint32_t) sim->cpu.ecr.fecc << 16 | sim->cpu.ecr.eicc; - case VB_PSW: - sim->cpu.psw.i = value >> 16 & 15; - 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; + 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 0; /* Invalid ID */ + return 0x00000000; /* All others */ } -/* Prepare to write a memory value to the bus */ -static int cpuWritePre(VB *sim,uint32_t *address,int32_t *type,int32_t *value){ - VBAccess access; /* Bus access descriptor */ - - /* Determine how many clocks the access will take */ - access.clocks = 0; /* TODO: Needs research */ - - /* Call the breakpoint handler */ - if (sim->onWrite != NULL) { - - /* Query the application */ - access.address = *address; - access.value = *value; - access.type = *type; - if (sim->onWrite(sim, &access)) - return 1; - - /* Apply changes */ - *address = access.address; - *value = access.value; - if (access.type <= VB_S32) - *type = access.type; - } - - /* Post-processing */ - sim->cpu.clocks += access.clocks; - return 0; -} - - - -/**************************** Execute A Handlers *****************************/ - -/* Standard two-operand instruction */ -static int exaStdTwo(VB *sim) { - OpDef *def = (OpDef *) sim->cpu.inst.def; - sim->cpu.inst.aux[0] = def->operand(sim); - sim->cpu.clocks += def->aux; - return 0; -} - -/* Standard three-operand instruction */ -static int exaStdThree(VB *sim) { - OpDef *def = (OpDef *) sim->cpu.inst.def; - sim->cpu.inst.aux[0] = def->operand(sim); - sim->cpu.inst.aux[1] = sim->cpu.program[sim->cpu.inst.code[0] & 31]; - sim->cpu.clocks += def->aux; - return 0; -} - -/* Bit string bitwise */ -static int exaBitBitwise(VB *sim) { - int32_t bits; /* Working shift amount and bit mask */ - uint32_t length; /* Number of bits remaining in bit string */ - int32_t offDest; /* Bit offset of destination bit string */ - int32_t offSrc; /* Bit offset of source bit string */ - uint32_t result; /* Output wrdvalue */ - uint32_t valDest; /* Destination word value */ - - /* Initial invocation */ - if (sim->cpu.step == 0) - sim->cpu.step = sim->cpu.bitstring + 1; - - /* Read the low-order 32 source bits */ - if (sim->cpu.step == 1) { - if (cpuRead(sim, sim->cpu.program[30], VB_S32, &sim->cpu.inst.aux[0])) - return 1; - sim->cpu.clocks += 4; /* TODO: Needs research */ - sim->cpu.step = 2; - } - - /* Read the high-order 32 source bits */ - if (sim->cpu.step == 2) { - if (cpuRead(sim,sim->cpu.program[30]+4,VB_S32,&sim->cpu.inst.aux[1])) - return 1; - sim->cpu.clocks += 4; /* TODO: Needs research */ - sim->cpu.step = 3; - } - - /* Read the destination bits */ - if (sim->cpu.step == 3) { - if (cpuRead(sim, sim->cpu.program[29], VB_S32, &sim->cpu.inst.aux[2])) - return 1; - sim->cpu.clocks += 4; /* TODO: Needs research */ - sim->cpu.step = 4; - } - - /* Compute the result */ - if (sim->cpu.step == 4) { - length = sim->cpu.program[28]; - offDest = sim->cpu.program[26] & 31; - offSrc = sim->cpu.program[27] & 31; - result = sim->cpu.inst.aux[0]; - valDest = sim->cpu.inst.aux[2]; - bits = offDest - offSrc; - - /* Compose the source value */ - if (bits > 0) - result <<= bits; - else if (bits < 0) { - result >>= -bits; - #ifndef VB_SIGNED_PROPAGATE - result &= ((uint32_t) 1 << (32 + bits)) - 1; - #endif - result |= sim->cpu.inst.aux[1] << (32 + bits); - } - - /* Compose the destination value */ - ((OpDef *) sim->cpu.inst.def) - ->operation(sim, (int32_t *) &result, valDest); - bits = (1 << offDest) - 1; - if (length < 32 && offDest + length < 32) - bits |= (uint32_t) 0xFFFFFFFF << (offDest + length); - sim->cpu.inst.aux[2] = (result & ~bits) | (valDest & bits); - - /* Prepare to write the result */ - sim->cpu.inst.aux[3] = sim->cpu.program[29] & 0xFFFFFFFC; - sim->cpu.inst.aux[4] = VB_S32; - if (cpuWritePre(sim, (uint32_t *) &sim->cpu.inst.aux[3], - &sim->cpu.inst.aux[4], &sim->cpu.inst.aux[2])) - return 1; - sim->cpu.clocks += 4; /* TODO: Needs research */ - } - - return 0; -} - -/* Bit string search */ -static int exaBitSearch(VB *sim) { - if (cpuRead(sim, sim->cpu.program[30], VB_S32, &sim->cpu.inst.aux[0])) - return 1; - sim->cpu.clocks += 4; /* TODO: Needs research */ - return 0; -} - -/* Branch on condition */ -static int exaBCOND(VB *sim) { - if (cpuCondition(sim, sim->cpu.inst.code[0] >> 9 & 15)) { - sim->cpu.inst.aux[0] = - (sim->cpu.pc + SignExtend(sim->cpu.inst.code[0], 9)) & 0xFFFFFFFE; - sim->cpu.clocks += 3; - } else { - sim->cpu.inst.aux[0] = sim->cpu.pc + sim->cpu.inst.size; - sim->cpu.clocks += 1; - } - return 0; -} - -/* Compare and exchange interlocked */ -static int exaCAXI(VB *sim) { - - /* First invocation */ - if (sim->cpu.step == 0) { - exaStdThree(sim); - sim->cpu.inst.aux[0] += sim->cpu.inst.aux[1]; /* Address */ - sim->cpu.inst.aux[1] = VB_S32; /* Write type */ - sim->cpu.inst.aux[3] = sim->cpu.program[sim->cpu.inst.code[0]>>5&31]; - sim->cpu.step = 1; - } - - /* Read the lock word and determine the exchange value */ - if (sim->cpu.step == 1) { - if (cpuRead(sim, sim->cpu.inst.aux[0], VB_S32, &sim->cpu.inst.aux[4])) - return 1; - sim->cpu.inst.aux[2] = sim->cpu.inst.aux[3] == sim->cpu.inst.aux[4] ? - sim->cpu.program[30] : sim->cpu.inst.aux[4]; - sim->cpu.step = 2; - } - - /* Prepare to write the exchange value */ - if (sim->cpu.step == 2) { - if (cpuWritePre(sim, (uint32_t *) &sim->cpu.inst.aux[0], - &sim->cpu.inst.aux[1], &sim->cpu.inst.aux[2])) - return 1; - } - - return 0; -} - -/* No special action */ -static int exaDefault(VB *sim) { - sim->cpu.clocks += ((OpDef *) sim->cpu.inst.def)->aux; - return 0; -} - -/* Division */ -static int exaDivision(VB *sim) { - exaStdTwo(sim); - if (sim->cpu.inst.aux[0] == 0) { - sim->cpu.clocks = 0; /* exaStdTwo adds clocks */ - sim->cpu.exception = 0xFF80; /* Zero division */ - } - return 0; -} - -/* Exception */ -static int exaException(VB *sim) { - VBException exception; /* Exception descriptor */ - - /* Initial invocation */ - if (sim->cpu.step == 0) { - - /* Call the breakpoint handler */ - if (sim->onException != NULL) { - - /* Query the application */ - exception.address = sim->cpu.inst.aux[0]; - exception.code = sim->cpu.exception; - exception.cancel = 0; - if (sim->onException(sim, &exception)) - return 1; - - /* The application canceled the exception */ - if (exception.cancel) { - sim->cpu.exception = 0; - sim->cpu.stage = CPU_FETCH; - return 0; - } - - /* Apply changes */ - sim->cpu.inst.aux[0] = exception.address; - sim->cpu.exception = exception.code; - } - - /* Fatal exception: stage values for writing */ - if (sim->cpu.psw.np) { - sim->cpu.inst.aux[0] = 0x00000000; - sim->cpu.inst.aux[1] = VB_S32; - sim->cpu.inst.aux[2] = 0xFFFF0000 | sim->cpu.exception; - sim->cpu.inst.aux[3] = 0x00000004; - sim->cpu.inst.aux[4] = VB_S32; - sim->cpu.inst.aux[5] = cpuGetSystemRegister(sim, VB_PSW); - sim->cpu.inst.aux[6] = 0x00000008; - sim->cpu.inst.aux[7] = VB_S32; - sim->cpu.inst.aux[8] = sim->cpu.pc; - sim->cpu.step = 1; - } - - /* Other exception */ - else sim->cpu.step = 10; - } - - /* Prepare to dump fatal exception diagnostic values to memory */ - for (; sim->cpu.step < 10; sim->cpu.step += 3) { - if (cpuWritePre(sim, (uint32_t *) - &sim->cpu.inst.aux[sim->cpu.step - 1], - &sim->cpu.inst.aux[sim->cpu.step ], - &sim->cpu.inst.aux[sim->cpu.step + 1] - )) return 1; - } - - /* Common processing */ - sim->cpu.bitstring = 0; - sim->cpu.clocks += 1; /* TODO: Needs research */ - return 0; -} - -/* One-operand floating-point instruction */ -static int exaFloating1(VB *sim) { - int bits; /* Number of bits to shift */ - int32_t reg1; /* Left operand */ - int32_t result; /* Operation result */ - int32_t subop; /* Sub-opcode */ - - /* Reserved operand */ - reg1 = sim->cpu.program[sim->cpu.inst.code[0] & 31]; - if (cpuFRO(reg1)) { - sim->cpu.fpFlags = 0x00000200; /* FRO */ - sim->cpu.exception = 0xFF60; - return 0; - } - - /* Working variables */ - bits = (reg1 >> 23 & 0xFF) - 150; - result = (reg1 & 0x007FFFFF) | 0x00800000; - subop = sim->cpu.inst.code[1] >> 10 & 63; - - /* Zero */ - if ((reg1 & 0x7FFFFFFF) == 0x00000000) - result = 0; - - /* Minimum negative value */ - else if (bits == 8 && result == 0x00800000 && reg1 < 0) - result = INT32_MIN; - - /* Shifting left */ - else if (bits > 0) { - - /* Invalid operation */ - if (bits > 7) { - sim->cpu.fpFlags = 0x00000100; /* FIV */ - sim->cpu.exception = 0xFF70; - return 0; - } - - /* Compute result */ - result <<= bits; - } - - /* Shifting right */ - else if (bits < 0) { - result = - bits < -24 ? 0 : /* All bits shifted out */ - subop == 0x0B ? result >> -bits : /* Truncate */ - ((result >> (-bits - 1)) + 1) >> 1 /* Round */ - ; - } - - /* Result is negative */ - if (reg1 < 0 && result != INT32_MIN) - result = -result; - - /* Stage updates */ - sim->cpu.fpFlags = result==*(float *)®1 ? 0 : 0x00000010; /* FPR */ - sim->cpu.inst.aux[0] = result; - sim->cpu.inst.aux[1] = subop; - sim->cpu.clocks += ((OpDef *) sim->cpu.inst.def)->aux; - return 0; -} - -/* Two-operand floating-point instruction */ -static int exaFloating2(VB *sim) { - FloatAux *aux; /* Floating-point auxiliary memory */ - int32_t bits; /* Bits of testing value */ - int32_t reg1; /* Right operand */ - int32_t reg2; /* Left operand */ - float test; /* Floating-point testing value */ - OpDef *def = (OpDef *) sim->cpu.inst.def; - - /* Reserved operand */ - reg1 = sim->cpu.program[sim->cpu.inst.code[0] & 31]; - reg2 = sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31]; - if (cpuFRO(reg1) || cpuFRO(reg2)) { - sim->cpu.fpFlags = 0x00000200; /* FRO */ - sim->cpu.exception = 0xFF60; - return 0; - } - - /* Perform the operation */ - def->operation(sim, ®2, reg1); - if (sim->cpu.exception != 0) - return 0; /* Handled in opDIVF_S() */ - aux = (FloatAux *) &sim->cpu.inst.aux; - - /* Overflow */ - bits = 0x7F7FFFFF; /* Maximum value */ - test = *(float *)&bits; - if (aux->f64 > test || aux->f64 < -test) { - sim->cpu.fpFlags = 0x00000040; /* FOV */ - sim->cpu.exception = 0xFF64; - return 0; - } - - /* Process result */ - bits = *(int32_t *)&aux->f32; - sim->cpu.fpFlags = 0; - - /* Zero */ - if ((bits & 0x7FFFFFFF) == 0x00000000) - aux->f32 = bits = 0; - - /* Underflow */ - else if ((bits & 0x7F800000) == 0x00000000) { - sim->cpu.fpFlags = 0x00000020; /* FUD */ - aux->f32 = bits = 0; - } - - /* Precision degradation */ - if (aux->f32 != aux->f64) - sim->cpu.fpFlags |= 0x00000010; /* FPR */ - - /* Other state */ - sim->cpu.inst.aux[0] = bits; - sim->cpu.inst.aux[1] = sim->cpu.inst.code[1] >> 10 & 63; - sim->cpu.clocks += def->aux; - return 0; -} - -/* Illegal opcode */ -static int exaIllegal(VB *sim) { - sim->cpu.exception = 0xFF90; /* Illegal opcode */ - return 0; -} - -/* Jump relative, jump and link */ -static int exaJR(VB *sim) { - sim->cpu.inst.aux[0] = 0xFFFFFFFE & (sim->cpu.pc + SignExtend( - (int32_t) sim->cpu.inst.code[0] << 16 | sim->cpu.inst.code[1], 26)); - sim->cpu.clocks += 3; - return 0; -} - -/* Memory read */ -static int exaRead(VB *sim) { - - /* First invocation */ - if (sim->cpu.step == 0) { - exaStdThree(sim); - sim->cpu.inst.aux[1] += sim->cpu.inst.aux[0]; /* Address */ - sim->cpu.clocks += 5; /* TODO: Needs research */ - sim->cpu.step = 1; - } - - /* Read the value */ - return cpuRead(sim, sim->cpu.inst.aux[1], - ((OpDef *) sim->cpu.inst.def)->aux, &sim->cpu.inst.aux[0]); -} - -/* Trap */ -static int exaTRAP(VB *sim) { - /* TODO: Clocks is less 1 here because exaException adds 1 */ - sim->cpu.clocks += ((OpDef *) sim->cpu.inst.def)->aux - 1; - sim->cpu.exception = 0xFFA0 | (sim->cpu.inst.code[0] & 31); - return 0; -} - -/* Memory write */ -static int exaWrite(VB *sim) { - - /* First invocation */ - if (sim->cpu.step == 0) { - exaStdThree(sim); - sim->cpu.inst.aux[0] += sim->cpu.inst.aux[1]; - sim->cpu.inst.aux[1] = ((OpDef *) sim->cpu.inst.def)->aux; /* Type */ - sim->cpu.inst.aux[2] = sim->cpu.program[sim->cpu.inst.code[0]>>5&31]; - sim->cpu.clocks += 4; /* TODO: Needs research */ - sim->cpu.step = 1; - } - - /* Write the value */ - return cpuWritePre(sim, (uint32_t *) &sim->cpu.inst.aux[0], - &sim->cpu.inst.aux[1], &sim->cpu.inst.aux[2]); -} - - - -/**************************** Execute B Handlers *****************************/ - -/* Bit string bitwise */ -static void exbBitBitwise(VB *sim) { - int32_t bits; - - /* Write the output value */ - if (sim->cpu.program[28] != 0) { - busWrite(sim, sim->cpu.inst.aux[3], - sim->cpu.inst.aux[4], sim->cpu.inst.aux[2], 0); - } - - /* Prepare registers */ - sim->cpu.program[26] &= 0x0000001F; - sim->cpu.program[27] &= 0x0000001F; - sim->cpu.program[29] &= 0xFFFFFFFC; - sim->cpu.program[30] &= 0xFFFFFFFC; - - /* Determine how many bits of output have been processed */ - bits = 32 - sim->cpu.program[26]; - if ((uint32_t) sim->cpu.program[28] <= (uint32_t) bits) - bits = sim->cpu.program[28]; - - /* Update source */ - sim->cpu.program[27] += bits; - if (sim->cpu.program[27] >= 32) { - sim->cpu.program[27] &= 31; - sim->cpu.program[30] += 4; - sim->cpu.inst.aux[0] = sim->cpu.inst.aux[1]; - sim->cpu.bitstring = 1; /* Read next source word */ - } else sim->cpu.bitstring = 2; /* Skip source reads */ - - /* Update destination */ - sim->cpu.program[26] += bits; - if (sim->cpu.program[26] >= 32) { - sim->cpu.program[26] &= 31; - sim->cpu.program[29] += 4; - } - - /* Update length */ - sim->cpu.program[28] -= bits; - - /* Advance to the next instruction */ - if (sim->cpu.program[28] == 0) { - sim->cpu.bitstring = 0; - sim->cpu.pc += sim->cpu.inst.size; - } - -} - -/* Bit string search */ -static void exbBitSearch(VB *sim) { - int32_t dir = ((~sim->cpu.inst.code[0] & 1) << 1) - 1; - int32_t test = sim->cpu.inst.code[0] >> 1 & 1; - - /* Prepare registers */ - sim->cpu.program[27] &= 0x0000001F; - sim->cpu.program[30] &= 0xFFFFFFFC; - sim->cpu.psw.z = 1; - sim->cpu.bitstring = 1; - - /* Process all remaining bits in the current word */ - while (sim->cpu.psw.z && sim->cpu.program[28] != 0) { - - /* The bit does not match */ - if ( - (sim->cpu.inst.aux[0] & 1 << sim->cpu.program[27]) != - test << sim->cpu.program[27] - ) sim->cpu.program[29]++; - - /* A match was found */ - else sim->cpu.psw.z = 0; - - /* Advance to the next bit */ - sim->cpu.program[28]--; - sim->cpu.program[27] += dir; - if (sim->cpu.program[27] & 0x00000020) { - sim->cpu.program[27] &= 0x0000001F; - sim->cpu.program[30] += dir << 2; - break; - } - - } - - /* Advance to the next instruction */ - if (!sim->cpu.psw.z || sim->cpu.program[28] == 0) { - sim->cpu.bitstring = 0; - sim->cpu.pc += sim->cpu.inst.size; - } - -} - -/* Compare and exchange interlocked */ -static void exbCAXI(VB *sim) { - int32_t left = sim->cpu.inst.aux[3]; - int32_t right = sim->cpu.inst.aux[4]; - int32_t result = left - right; - sim->cpu.psw.cy = (uint32_t) left < (uint32_t) right; - sim->cpu.psw.ov = (int32_t) ((left ^ right) & (left ^ result)) < 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - sim->cpu.pc += sim->cpu.inst.size; - sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31] = right; - busWrite(sim, sim->cpu.inst.aux[0], - sim->cpu.inst.aux[1], sim->cpu.inst.aux[2], 0); -} - -/* Clear interrupt disable flag */ -static void exbCLI(VB *sim) { - sim->cpu.pc += sim->cpu.inst.size; - sim->cpu.psw.id = 0; -} - -/* Exception */ -static void exbException(VB *sim) { - int x; /* Iterator */ - - /* Apply staged floating-point flags */ - if (sim->cpu.fpFlags != 0) { - cpuSetSystemRegister(sim, VB_PSW, sim->cpu.fpFlags | - cpuGetSystemRegister(sim, VB_PSW), 0); - sim->cpu.fpFlags = 0; - } - - /* Fatal exception */ - if (sim->cpu.psw.np) { - for (x = 0; x < 9; x += 3) { - busWrite(sim, sim->cpu.inst.aux[x], - sim->cpu.inst.aux[x + 1], sim->cpu.inst.aux[x + 2], 0); - } - sim->cpu.stage = CPU_FATAL; - return; - } - - /* Duplexed exception */ - if (sim->cpu.psw.ep) { - sim->cpu.ecr.fecc = sim->cpu.exception; - sim->cpu.fepc = sim->cpu.pc; - sim->cpu.fepsw = cpuGetSystemRegister(sim, VB_PSW); - sim->cpu.pc = 0xFFFFFFD0; - sim->cpu.psw.np = 1; - } - - /* Regular exception */ - else { - sim->cpu.ecr.eicc = sim->cpu.exception; - sim->cpu.eipc = sim->cpu.pc; - sim->cpu.eipsw = cpuGetSystemRegister(sim, VB_PSW); - sim->cpu.pc = sim->cpu.inst.aux[0]; - sim->cpu.psw.ep = 1; - - /* Interrupt */ - if (sim->cpu.exception < 0xFF00) { - if ((sim->cpu.inst.code[0] & 0xFC00) == 0x6800) /* HALT */ - sim->cpu.eipc += sim->cpu.inst.size; - sim->cpu.psw.i = Min(15, (sim->cpu.exception >> 4 & 15) + 1); - } - - /* TRAP */ - if ((sim->cpu.exception & 0xFFE0) == 0xFFA0) - sim->cpu.eipc += sim->cpu.inst.size; - } - - /* Common processing */ - sim->cpu.psw.ae = 0; - sim->cpu.psw.id = 1; -} - -/* Floating-point instruction */ -static void exbFloating(VB *sim) { - int32_t result = sim->cpu.inst.aux[0]; /* Operation result */ - int32_t subop = sim->cpu.inst.aux[1]; /* Sub-opcode */ - - /* Apply staged floating-point flags */ - if (sim->cpu.fpFlags != 0) { - cpuSetSystemRegister(sim, VB_PSW, sim->cpu.fpFlags | - cpuGetSystemRegister(sim, VB_PSW), 0); - sim->cpu.fpFlags = 0; - } - - /* Update state */ - sim->cpu.pc += sim->cpu.inst.size; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - if (subop != 0x03 && subop != 0x0B) /* CVT.SW, TRNC.SW */ - sim->cpu.psw.cy = result < 0; - if (subop != 0x00) /* CMPF.S */ - sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31] = result; -} - -/* Halt */ -static void exbHALT(VB *sim) { - sim->cpu.stage = CPU_HALT; -} - -/* Jump and link */ -static void exbJAL(VB *sim) { - sim->cpu.program[31] = sim->cpu.pc + sim->cpu.inst.size; - sim->cpu.pc = sim->cpu.inst.aux[0]; -} - -/* Jump */ -static void exbJMP(VB *sim) { - sim->cpu.pc = sim->cpu.inst.aux[0]; -} - -/* Return from trap or interrupt */ -static void exbRETI(VB *sim) { - if (sim->cpu.psw.np) { - sim->cpu.pc = sim->cpu.fepc; - cpuSetSystemRegister(sim, VB_PSW, sim->cpu.fepsw, 0); - } else { - sim->cpu.pc = sim->cpu.eipc; - cpuSetSystemRegister(sim, VB_PSW, sim->cpu.eipsw, 0); - } -} - -/* Set interrupt disable flag */ -static void exbSEI(VB *sim) { - sim->cpu.pc += sim->cpu.inst.size; - sim->cpu.psw.id = 1; -} - -/* Standard two-operand instruction */ -static void exbStdTwo(VB *sim) { - ((OpDef *) sim->cpu.inst.def)->operation(sim, - &sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31], - sim->cpu.inst.aux[0] - ); - sim->cpu.pc += sim->cpu.inst.size; -} - -/* Standard three-operand instruction */ -static void exbStdThree(VB *sim) { - int32_t *reg2 = &sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31]; - *reg2 = sim->cpu.inst.aux[1]; - ((OpDef *) sim->cpu.inst.def)->operation(sim, reg2, sim->cpu.inst.aux[0]); - sim->cpu.pc += sim->cpu.inst.size; -} - - - -/**************************** Operation Handlers *****************************/ - -/* Add */ -static void opADD(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest + src; - sim->cpu.psw.cy = (uint32_t) result < (uint32_t) *dest; - sim->cpu.psw.ov = (int32_t) (~(*dest ^ src) & (*dest ^ result)) < 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Add Floating Short */ -static void opADDF_S(VB *sim, int32_t *dest, int32_t src) { - FloatAux *aux = (FloatAux *) sim->cpu.inst.aux; - double left = *(float *)dest; - double right = *(float *)&src; - double result = left + right; - aux->f32 = result; - aux->f64 = result; -} - -/* And */ -static void opAND(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest & src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; +/* Determine how many clocks are guaranteed to process */ +static uint32_t cpuUntil(VB *sim, uint32_t clocks) { + return sim->cpu.clocks < clocks ? sim->cpu.clocks : clocks; } -/* And Bit String Upward */ -static void opANDBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest &= src; -} - -/* And Not Bit String Upward */ -static void opANDNBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = ~*dest & src; -} - -/* Compare */ -static void opCMP(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest - src; - sim->cpu.psw.cy = (uint32_t) *dest < (uint32_t) src; - sim->cpu.psw.ov = (int32_t) ((*dest ^ src) & (*dest ^ result)) < 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; -} - -/* Convert Word Integer to Short Floating */ -static void opCVT_WS(VB *sim, int32_t *dest, int32_t src) { - float value = (float) src; - *dest = *(int32_t *)&value; - if ((double) value != (double) src) - sim->cpu.psw.fpr = 1; -} - -/* Divide signed */ -static void opDIV(VB *sim, int32_t *dest, int32_t src) { - int32_t result; - if (*dest == INT32_MIN && src == -1) { - sim->cpu.psw.ov = 1; - sim->cpu.psw.s = 1; - sim->cpu.psw.z = 0; - sim->cpu.program[30] = 0; - } else { - result = *dest / src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - sim->cpu.program[30] = *dest % src; - *dest = result; - } -} - -/* Divide Floating Short */ -static void opDIVF_S(VB *sim, int32_t *dest, int32_t src) { - FloatAux *aux = (FloatAux *) sim->cpu.inst.aux; - double left; /* Left operand */ - double right; /* Right operand */ - double result; /* Operation result */ - - /* Divisor is zero */ - if (*dest == 0) { - - /* Invalid operation */ - if (src == 0) { - sim->cpu.fpFlags = 0x00000100; /* FIV */ - sim->cpu.exception = 0xFF70; - } - - /* Zero division */ - else { - sim->cpu.fpFlags = 0x00000080; /* FZD */ - sim->cpu.exception = 0xFF68; - } - - return; - } - - /* Perform the operation */ - left = *(float *)dest; - right = *(float *)&src; - result = left / right; - aux->f32 = result; - aux->f64 = result; -} - -/* Divide unsigned */ -static void opDIVU(VB *sim, int32_t *dest, int32_t src) { - uint32_t result = (uint32_t) *dest / (uint32_t) src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = (int32_t) result < 0; - sim->cpu.psw.z = result == 0; - sim->cpu.program[30] = (int32_t) ((uint32_t) *dest % (uint32_t) src); - *dest = (int32_t) result; -} - -/* Load to system register */ -static void opLDSR(VB *sim, int32_t *dest, int32_t src) { - cpuSetSystemRegister(sim, src, *dest, 0); -} - -/* Move */ -static void opMOV(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = src; -} - -/* Move Bit String Upward */ -static void opMOVBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - (void) dest; - (void) src; -} - -/* Add Immediate */ -static void opMOVEA(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest += src; -} - -/* Multiply Halfword */ -static void opMPYHW(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest *= SignExtend(src, 17); -} - -/* Multiply signed */ -static void opMUL(VB *sim, int32_t *dest, int32_t src) { - int64_t result = (int64_t) *dest * (int64_t) src; - int32_t resultLow = (int32_t) result; - sim->cpu.psw.ov = result != resultLow; - sim->cpu.psw.s = resultLow < 0; - sim->cpu.psw.z = resultLow == 0; - sim->cpu.program[30] = (int32_t) (result >> 32); - *dest = resultLow; -} - -/* Multiply Floating Short */ -static void opMULF_S(VB *sim, int32_t *dest, int32_t src) { - FloatAux *aux = (FloatAux *) sim->cpu.inst.aux; - double left = *(float *)dest; - double right = *(float *)&src; - double result = left * right; - aux->f32 = result; - aux->f64 = result; -} - -/* Multiply unsigned */ -static void opMULU(VB *sim, int32_t *dest, int32_t src) { - uint64_t result = (uint64_t)(uint32_t)*dest * (uint64_t)(uint32_t)src; - uint32_t resultLow = (uint32_t) result; - sim->cpu.psw.ov = result != resultLow; - sim->cpu.psw.s = (int32_t) resultLow < 0; - sim->cpu.psw.z = resultLow == 0; - sim->cpu.program[30] = (int32_t) (result >> 32); - *dest = (int32_t) resultLow; -} - -/* Not */ -static void opNOT(VB *sim, int32_t *dest, int32_t src) { - int32_t result = ~src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Not Bit String Upward */ -static void opNOTBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - (void) src; - *dest = ~*dest; -} - -/* Or */ -static void opOR(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest | src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Or Bit String Upward */ -static void opORBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest |= src; -} - -/* Or Not Bit String Upward */ -static void opORNBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = ~*dest | src; -} - -/* Reverse Bits in Word */ -static void opREV(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - src = (src << 16 & 0xFFFF0000) | (src >> 16 & 0x0000FFFF); - src = (src << 8 & 0xFF00FF00) | (src >> 8 & 0x00FF00FF); - src = (src << 4 & 0xF0F0F0F0) | (src >> 4 & 0x0F0F0F0F); - src = (src << 2 & 0xCCCCCCCC) | (src >> 2 & 0x33333333); - *dest = (src << 1 & 0xAAAAAAAA) | (src >> 1 & 0x55555555); -} - -/* Set flag condition */ -static void opSETF(VB *sim, int32_t *dest, int32_t src) { - *dest = cpuCondition(sim, src & 15); -} - -/* Shift right arithmetic */ -static void opSAR(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest >> (src &= 31); - #ifndef VB_SIGNED_PROPAGATE - if (src != 0) - result = SignExtend(result, 32 - src); - #endif - sim->cpu.psw.cy = src != 0 && *dest & 1 << (src - 1); - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Shift left */ -static void opSHL(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest << (src &= 31); - sim->cpu.psw.cy = src != 0 && *dest & 1 << (32 - src); - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Shift right logical */ -static void opSHR(VB *sim, int32_t *dest, int32_t src) { - int32_t result = (uint32_t) *dest >> (src &= 31); - #ifndef VB_SIGNED_PROPAGATE - if (src != 0) - result &= ((uint32_t) 1 << (32 - src)) - 1; - #endif - sim->cpu.psw.cy = src != 0 && *dest & 1 << (src - 1); - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Store */ -static void opST(VB *sim, int32_t *dest, int32_t src) { - (void) dest, (void) src; - busWrite(sim, sim->cpu.inst.aux[0], - sim->cpu.inst.aux[1], sim->cpu.inst.aux[2], 0); -} - -/* Store to system register */ -static void opSTSR(VB *sim, int32_t *dest, int32_t src) { - *dest = cpuGetSystemRegister(sim, src); -} - -/* Subtract */ -static void opSUB(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest - src; - sim->cpu.psw.cy = (uint32_t) *dest < (uint32_t) src; - sim->cpu.psw.ov = (int32_t) ((*dest ^ src) & (*dest ^ result)) < 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Subtract Floating Short */ -static void opSUBF_S(VB *sim, int32_t *dest, int32_t src) { - FloatAux *aux = (FloatAux *) sim->cpu.inst.aux; - double left = *(float *)dest; - double right = *(float *)&src; - double result = left - right; - aux->f32 = result; - aux->f64 = result; -} - -/* Exchange Byte */ -static void opXB(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = (src & 0xFFFF0000) | (src << 8 & 0xFF00) | (src >> 8 & 0x00FF); -} - -/* Exchange Halfword */ -static void opXH(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = (src << 16 & 0xFFFF0000) | (src >> 16 & 0x0000FFFF); -} - -/* Exclusive Or */ -static void opXOR(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest ^ src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Exclusive Or Bit String Upward */ -static void opXORBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest ^= src; -} - -/* Exclusive Or Not Bit String Upward */ -static void opXORNBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = ~*dest ^ src; -} - - - -/***************************** Operand Handlers ******************************/ - -/* imm5 (sign-extended) */ -static int32_t opImm5S(VB *sim) { - return SignExtend(sim->cpu.inst.code[0] & 31, 5); -} - -/* imm5 */ -static int32_t opImm5U(VB *sim) { - return sim->cpu.inst.code[0] & 31; -} - -/* imm16 (shifted left by 16) */ -static int32_t opImm16H(VB *sim) { - return (uint32_t) sim->cpu.inst.code[1] << 16; -} - -/* imm16 (sign-extended) */ -static int32_t opImm16S(VB *sim) { - return SignExtend(sim->cpu.inst.code[1], 16); -} - -/* imm16 */ -static int32_t opImm16U(VB *sim) { - return sim->cpu.inst.code[1]; -} - -/* reg1 */ -static int32_t opReg1(VB *sim) { - return sim->cpu.program[sim->cpu.inst.code[0] & 31]; -} - -/* reg2 */ -static int32_t opReg2(VB *sim) { - return sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31]; -} - - - -/**************************** Opcode Definitions *****************************/ - -/* Forward references */ -static int exaBitString(VB *); -static int exaFloatendo(VB *); - -/* Exception processing */ -static const OpDef OPDEF_EXCEPTION = - { &exaException, &exbException, NULL, NULL, 0, 0 }; - -/* Top-level opcode definitions */ -static const OpDef OPDEFS[] = { - { &exaStdTwo , &exbStdTwo , &opMOV , &opReg1 , 1, 1 }, /* 0x00 */ - { &exaStdTwo , &exbStdTwo , &opADD , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSUB , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opCMP , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSHL , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSHR , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbJMP , NULL , &opReg1 , 1, 3 }, - { &exaStdTwo , &exbStdTwo , &opSAR , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opMUL , &opReg1 , 1, 13 }, - { &exaDivision , &exbStdTwo , &opDIV , &opReg1 , 1, 38 }, - { &exaStdTwo , &exbStdTwo , &opMULU , &opReg1 , 1, 13 }, - { &exaDivision , &exbStdTwo , &opDIVU , &opReg1 , 1, 36 }, - { &exaStdTwo , &exbStdTwo , &opOR , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opAND , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opXOR , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opNOT , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opMOV , &opImm5S , 1, 1 }, /* 0x10 */ - { &exaStdTwo , &exbStdTwo , &opADD , &opImm5S , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSETF , &opImm5U , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opCMP , &opImm5S , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSHL , &opImm5U , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSHR , &opImm5U , 1, 1 }, - { &exaDefault , &exbCLI , NULL , NULL , 1, 12 }, - { &exaStdTwo , &exbStdTwo , &opSAR , &opImm5U , 1, 1 }, - { &exaTRAP , NULL , NULL , NULL , 1, 15 }, - { &exaDefault , &exbRETI , NULL , NULL , 1, 10 }, - { &exaDefault , &exbHALT , NULL , NULL , 1, 0 }, - { &exaIllegal , NULL , NULL , NULL , 1, 0 }, - { &exaStdTwo , &exbStdTwo , &opLDSR , &opImm5U , 1, 8 }, - { &exaStdTwo , &exbStdTwo , &opSTSR , &opImm5U , 1, 8 }, - { &exaDefault , &exbSEI , NULL , NULL , 1, 12 }, - { &exaBitString, NULL , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, /* 0x20 */ - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaStdThree , &exbStdThree, &opMOVEA, &opImm16S, 2, 1 }, - { &exaStdThree , &exbStdThree, &opADD , &opImm16S, 2, 1 }, - { &exaJR , &exbJMP , NULL , NULL , 2, 0 }, - { &exaJR , &exbJAL , NULL , NULL , 2, 0 }, - { &exaStdThree , &exbStdThree, &opOR , &opImm16U, 2, 1 }, - { &exaStdThree , &exbStdThree, &opAND , &opImm16U, 2, 1 }, - { &exaStdThree , &exbStdThree, &opXOR , &opImm16U, 2, 1 }, - { &exaStdThree , &exbStdThree, &opMOVEA, &opImm16H, 2, 1 }, - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_S8 }, /* 0x30 */ - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_S16 }, - { &exaIllegal , NULL , NULL , NULL , 1, 0 }, - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_S32 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_S8 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_S16 }, - { &exaIllegal , NULL , NULL , NULL , 1, 0 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_S32 }, - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_U8 }, - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_U16 }, - { &exaCAXI , &exbCAXI , NULL , &opImm16S, 2, 26 }, - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_S32 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_U8 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_U16 }, - { &exaFloatendo, NULL , NULL , NULL , 2, 0 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_S32 } -}; - -/* Bit string opcode definitions */ -static const OpDef OPDEFS_BITSTRING[] = { - { &exaBitSearch , &exbBitSearch , NULL , NULL, 1, 0 }, /* 0x00 */ - { &exaBitSearch , &exbBitSearch , NULL , NULL, 1, 0 }, - { &exaBitSearch , &exbBitSearch , NULL , NULL, 1, 0 }, - { &exaBitSearch , &exbBitSearch , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opORBSU , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opANDBSU , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opXORBSU , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opMOVBSU , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opORNBSU , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opANDNBSU, NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opXORNBSU, NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opNOTBSU , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, /* 0x10 */ - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 } -}; - -/* Floating-point/Nintendo opcode definitions */ -static const OpDef OPDEFS_FLOATENDO[] = { - { &exaFloating2, &exbFloating, &opSUBF_S, NULL , 2, 10 }, /* 0x00 */ - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaStdTwo , &exbStdTwo , &opCVT_WS, &opReg1, 2, 16 }, - { &exaFloating1, &exbFloating, NULL , NULL , 2, 14 }, - { &exaFloating2, &exbFloating, &opADDF_S, NULL , 2, 28 }, - { &exaFloating2, &exbFloating, &opSUBF_S, NULL , 2, 30 }, - { &exaFloating2, &exbFloating, &opMULF_S, NULL , 2, 28 }, - { &exaFloating2, &exbFloating, &opDIVF_S, NULL , 2, 44 }, - { &exaStdTwo , &exbStdTwo , &opXB , &opReg2, 2, 6 }, - { &exaStdTwo , &exbStdTwo , &opXH , &opReg2, 2, 1 }, - { &exaStdTwo , &exbStdTwo , &opREV , &opReg1, 2, 22 }, - { &exaFloating1, &exbFloating, NULL , NULL , 2, 14 }, - { &exaStdTwo , &exbStdTwo , &opMPYHW , &opReg1, 2, 22 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, /* 0x10 */ - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, /* 0x20 */ - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, /* 0x30 */ - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, -}; - -/* Bit string instructions */ -static int exaBitString(VB *sim) { - OpDef *def; /* Opcode definition */ - sim->cpu.inst.def = def = - (OpDef *) &OPDEFS_BITSTRING[sim->cpu.inst.code[0] & 31]; - return def->executeA(sim); -} - -/* Floating-point and Nintendo instruction parser */ -static int exaFloatendo(VB *sim) { - OpDef *def; /* Opcode definition */ - sim->cpu.inst.def = def = - (OpDef *) &OPDEFS_FLOATENDO[sim->cpu.inst.code[1] >> 10 & 63]; - return def->executeA(sim); -} - - - -/****************************** Pipeline Stages ******************************/ - -/* Raise an exception */ -static void cpuException(VB *sim) { - sim->cpu.inst.def = (OpDef *) &OPDEF_EXCEPTION; - sim->cpu.stage = CPU_EXECUTE_A; - sim->cpu.inst.aux[0] = 0xFFFF0000 | (sim->cpu.exception & 0xFFF0); - if (sim->cpu.inst.aux[0] == (int32_t) 0xFFFFFF70) - sim->cpu.inst.aux[0] = 0xFFFFFF60; -} - -/* Execute: Pre-processing, does not update state */ -static int cpuExecuteA(VB *sim) { - OpDef *def; /* Opcode descriptor */ - VBInstruction inst; /* Instruction descriptor */ - - /* First invocation */ - if (sim->cpu.step == 0 && sim->cpu.exception == 0) { - - /* Call the breakpoint handler */ - if (sim->onExecute != NULL) { - - /* Query the application */ - inst.address = sim->cpu.pc; - inst.code[0] = sim->cpu.inst.code[0]; - inst.code[1] = sim->cpu.inst.code[1]; - inst.size = sim->cpu.inst.size; - if (sim->onExecute(sim, &inst)) - return 1; - - /* Apply changes */ - sim->cpu.inst.code[0] = inst.code[0]; - sim->cpu.inst.code[1] = inst.code[1]; - sim->cpu.inst.size = inst.size; - sim->cpu.inst.def = - (OpDef *) &OPDEFS[sim->cpu.inst.code[0] >> 10 & 63]; - } - - /* Detect non-bit string instruction */ - if ((sim->cpu.inst.code[0] & 0xFC00) != 0x7C00) - sim->cpu.bitstring = 0; - } - - /* Processing before updating simulation state */ - def = sim->cpu.inst.def; - for (;;) { - if (def->executeA(sim)) - return 1; - sim->cpu.step = 0; - - /* Advance to exception processing */ - if (sim->cpu.exception == 0 || def == &OPDEF_EXCEPTION) - break; - def = (OpDef *) &OPDEF_EXCEPTION; - cpuException(sim); - } - - /* Advance to execute B */ - sim->cpu.stage = CPU_EXECUTE_B; - return 0; -} - -/* Execute: Post-processing, updates state */ -static void cpuExecuteB(VB *sim) { - - /* Perform the operation and update state */ - ((OpDef *) sim->cpu.inst.def)->executeB(sim); - sim->cpu.program[0] = 0; - - /* Advance to next pipeline stage */ - if (sim->cpu.stage == CPU_EXECUTE_B) { - if (cpuCheckIRQs(sim)) - cpuException(sim); - else if (sim->cpu.bitstring != 0) - sim->cpu.stage = CPU_EXECUTE_A; - else sim->cpu.stage = CPU_FETCH; - } - -} - -/* Retrieve instruction data from the bus */ -static int cpuFetch(VB *sim) { - OpDef *def; /* Opcode definition */ - - /* First fetch */ - if (sim->cpu.step == 0) { - if (cpuReadFetch(sim)) - return 1; - sim->cpu.inst.def = def = - (OpDef *) &OPDEFS[sim->cpu.inst.code[0] >> 10 & 0x003F]; - sim->cpu.inst.size = def->size << 1; - sim->cpu.step = 1; - } else def = (OpDef *) sim->cpu.inst.def; - - /* Second fetch */ - for (; sim->cpu.step < def->size; sim->cpu.step++) { - if (cpuReadFetch(sim)) - return 1; - } - - /* Advance to execute A */ - sim->cpu.stage = CPU_EXECUTE_A; - sim->cpu.step = 0; - return 0; -} - - - -/***************************** Module Functions ******************************/ - -/* Process a simulation for a given number of clocks */ -static int cpuEmulate(VB *sim, uint32_t clocks) { - - /* Process all clocks */ - for (;;) { - - /* Processing by pipeline stage */ - switch (sim->cpu.stage) { - - /* Fetch: Retrive instruction code from memory */ - case CPU_FETCH: - if (cpuFetch(sim)) - return 1; - break; - - /* Execute A: Check for exceptions, configure CPU clocks */ - case CPU_EXECUTE_A: - if (cpuExecuteA(sim)) - return 1; - break; - - /* Execute B: Wait clocks and update state */ - case CPU_EXECUTE_B: - - /* Clocks remaining exceeds emulation clocks */ - if (clocks < sim->cpu.clocks) { - sim->cpu.clocks -= clocks; - return 0; - } - - /* Update simulation state */ - clocks -= sim->cpu.clocks; - sim->cpu.clocks = 0; - cpuExecuteB(sim); - break; - - /* Halt: Wait for an interrupt */ - case CPU_HALT: - if (!cpuCheckIRQs(sim)) - return 0; - cpuException(sim); - break; - - /* Fatal exception: Cannot recover */ - case CPU_FATAL: - return 0; - } - - }; - - /* Unreachable */ - return 0; -} - -/* Compute clocks without breakpoint or state change */ -static int cpuUntil(VB *sim, uint32_t clocks) { - return sim->cpu.stage == CPU_HALT || sim->cpu.stage == CPU_FATAL ? - clocks : Min(sim->cpu.clocks, clocks); -} - - - #endif /* VBAPI */ diff --git a/core/vb.c b/core/vb.c index f2996a6..9bb249c 100644 --- a/core/vb.c +++ b/core/vb.c @@ -5,37 +5,117 @@ -/***************************** Utility Functions *****************************/ +/*********************************** Types ***********************************/ -/* Select the lesser of two unsigned numbers */ -static uint32_t Min(uint32_t a, uint32_t b) { +/* 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 address; + int32_t value; + } data; + } aux; + + /* Other state */ + uint32_t clocks; /* Master clocks to wait */ + uint16_t code[2]; /* Instruction code units */ + 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 callbacks */ + vbOnExecute onExecute; /* CPU instruction execute */ + vbOnFetch onFetch; /* CPU instruction fetch */ + vbOnRead onRead; /* CPU instruction read */ + vbOnWrite onWrite; /* CPU instruction write */ +}; + + + +/***************************** Library Functions *****************************/ + +/* Determine the lesser of two clocks figures */ +static uint32_t MinClocks(uint32_t a, uint32_t b) { return a < b ? a : b; } -/* Sign-extend an integer, masking upper bits if positive */ -#ifndef VB_SIGNED_PROPAGATE - /* Generic implementation */ - static int32_t SignExtend(int32_t value, int bits) { - return value & 1 << (bits - 1) ? - value | ~0 << bits : - value & ((1 << bits) - 1) - ; - } -#else - /* Sign-propagating implementation */ - #define SignExtend(v, b) ((int32_t) (v) << (32 - (b)) >> (32 - (b))) -#endif +/* Sign-extend an integer of variable width */ +static int32_t SignExtend(int32_t value, int32_t bits) { + value &= ~((uint32_t) 0xFFFFFFFF << bits); + bits = (int32_t) 1 << (bits - (int32_t) 1); + return (value ^ bits) - bits; +} -/**************************** Sub-Module Imports *****************************/ +/******************************** Sub-Modules ********************************/ #include "bus.c" #include "cpu.c" -/***************************** Module Functions ******************************/ +/***************************** Library Functions *****************************/ /* Process a simulation for a given number of clocks */ static int sysEmulate(VB *sim, uint32_t clocks) { @@ -44,7 +124,7 @@ static int sysEmulate(VB *sim, uint32_t clocks) { ; } -/* Determine how many clocks can be simulated without a breakpoint */ +/* Determine how many clocks are guaranteed to process */ static uint32_t sysUntil(VB *sim, uint32_t clocks) { clocks = cpuUntil(sim, clocks); return clocks; @@ -52,127 +132,121 @@ static uint32_t sysUntil(VB *sim, uint32_t clocks) { -/************************************ API ************************************/ +/******************************* API Commands ********************************/ -/* Process a simulation */ -int vbEmulate(VB *sim, uint32_t *clocks) { - int brk; /* A break was requested */ - uint32_t until; /* Number of clocks during which no break will occur */ - - /* Process all clocks */ - for (brk = 0; *clocks != 0 && !brk; *clocks -= until) { - until = sysUntil (sim, *clocks); - brk = sysEmulate(sim, until ); +/* 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 brk; + return 0; } /* Process multiple simulations */ -int vbEmulateEx(VB **sims, int count, uint32_t *clocks) { - int brk; /* A break was requested */ - uint32_t until; /* Number of clocks during which no break will occur */ +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 */ - - /* Process all clocks */ - for (brk = 0; *clocks != 0 && !brk; *clocks -= until) { + while (*clocks != 0) { until = *clocks; - for (x = 0; x < count; x++) - until = sysUntil (sims[x], until); - for (x = 0; x < count; x++) - brk |= sysEmulate(sims[x], until); - } + for (x = count - 1; x >= 0; x--) + until = sysUntil(sims[x], until); - return brk; + 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 a current breakpoint handler */ -void* vbGetCallback(VB *sim, int id) { +/* Retrieve a callback handler */ +VBAPI void* vbGetCallback(VB *sim, int id) { switch (id) { - case VB_ONEXCEPTION: return *(void **)&sim->onException; - case VB_ONEXECUTE : return *(void **)&sim->onExecute; - case VB_ONFETCH : return *(void **)&sim->onFetch; - case VB_ONREAD : return *(void **)&sim->onRead; - case VB_ONWRITE : return *(void **)&sim->onWrite; + /*case VB_EXCEPTION: return *(void **) &sim->onException;*/ + case VB_EXECUTE : return *(void **) &sim->onExecute; + case VB_FETCH : return *(void **) &sim->onFetch; + /*case VB_FRAME : return *(void **) &sim->onFrame;*/ + case VB_READ : return *(void **) &sim->onRead; + case VB_WRITE : return *(void **) &sim->onWrite; } return NULL; } -/* Retrieve the value of a register */ -int32_t vbGetRegister(VB *sim, int type, int id) { - switch (type) { - case VB_PROGRAM: - return id < 0 || id > 31 ? 0 : sim->cpu.program[id]; - case VB_SYSTEM: - return cpuGetSystemRegister(sim, id); - case VB_OTHER: - switch (id) { - case VB_PC: return sim->cpu.pc; - } - } - return 0; /* Invalid type */ -} - -/* Retrieve a handle to the current cartridge ROM data */ -uint8_t* vbGetROM(VB *sim, uint32_t *size) { +/* Retrieve the game pack RAM buffer */ +VBAPI void* vbGetCartRAM(VB *sim, uint32_t *size) { if (size != NULL) - *size = sim->cart.romSize; - return sim->cart.rom; -} - -/* Retrieve a handle to the current cartridge RAM data */ -uint8_t* vbGetSRAM(VB *sim, uint32_t *size) { - if (size != NULL) - *size = sim->cart.ramSize; + *size = sim->cart.ram == NULL ? 0 : sim->cart.ramMask + 1; return sim->cart.ram; } -/* Prepare a simulation instance for use */ -void vbInit(VB *sim) { +/* 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; +} - /* Breakpoint handlers */ - sim->onException = NULL; - sim->onExecute = NULL; - sim->onFetch = NULL; - sim->onRead = NULL; - sim->onWrite = NULL; +/* Retrieve the value of the program counter */ +VBAPI uint32_t vbGetProgramCounter(VB *sim) { + return sim->cpu.pc; +} - /* Game pak */ - sim->cart.ram = NULL; - sim->cart.ramSize = 0; - sim->cart.rom = NULL; - sim->cart.romSize = 0; +/* 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]; +} - /* Hardware reset */ +/* Retrieve the value in a system register */ +VBAPI uint32_t vbGetSystemRegister(VB *sim, int index) { + return index < 0 || index > 31 ? 0 : cpuGetSystemRegister(sim, index); +} + +/* 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 memory */ -int32_t vbRead(VB *sim, uint32_t address, int type) { - return busRead(sim, address, type); -} - -/* Read multiple bytes from memory */ -void vbReadEx(VB *sim, uint32_t address, uint8_t *buffer, uint32_t length) { - while (length--) *buffer++ = busRead(sim, address++, VB_U8); +/* 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 */ -void vbReset(VB *sim) { - int x; /* Iterator */ +VBAPI VB* vbReset(VB *sim) { + uint32_t x; /* Iterator */ - /* Reset WRAM (the hardware does not do this) */ + /* WRAM (the hardware does not do this) */ for (x = 0; x < 0x10000; x++) sim->wram[x] = 0x00; /* CPU (normal) */ - sim->cpu.pc = 0xFFFFFFF0; + sim->cpu.irq = 0; + sim->cpu.pc = 0xFFFFFFF0; cpuSetSystemRegister(sim, VB_ECR, 0x0000FFF0, 1); cpuSetSystemRegister(sim, VB_PSW, 0x00008000, 1); - for (x = 0; x < 5; x++) - sim->cpu.irq[x] = 0; - /* CPU (extra, hardware doesn't do this) */ + /* CPU (extra, hardware does not do this) */ sim->cpu.adtre = 0x00000000; sim->cpu.eipc = 0x00000000; sim->cpu.eipsw = 0x00000000; @@ -184,99 +258,88 @@ void vbReset(VB *sim) { for (x = 0; x < 32; x++) sim->cpu.program[x] = 0x00000000; - /* CPU (internal) */ - sim->cpu.bitstring = 0; + /* CPU (other) */ sim->cpu.clocks = 0; - sim->cpu.exception = 0; - sim->cpu.stage = CPU_FETCH; + sim->cpu.nextPC = 0xFFFFFFF0; + sim->cpu.operation = CPU_FETCH; sim->cpu.step = 0; + + return sim; } -/* Specify a breakpoint handler */ -void* vbSetCallback(VB *sim, int id, void *proc) { - void *prev = vbGetCallback(sim, id); +/* Specify a new callback handler */ +VBAPI void* vbSetCallback(VB *sim, int id, void *callback) { + void *prev = NULL; + void **target = NULL; + + /* Select callback by ID */ switch (id) { - case VB_ONEXCEPTION: *(void **)&sim->onException = proc; break; - case VB_ONEXECUTE : *(void **)&sim->onExecute = proc; break; - case VB_ONFETCH : *(void **)&sim->onFetch = proc; break; - case VB_ONREAD : *(void **)&sim->onRead = proc; break; - case VB_ONWRITE : *(void **)&sim->onWrite = proc; break; + /*case VB_EXCEPTION: target = (void **) &sim->onException; break;*/ + case VB_EXECUTE : target = (void **) &sim->onExecute ; break; + case VB_FETCH : target = (void **) &sim->onFetch ; break; + /*case VB_FRAME : target = (void **) &sim->onFrame ; break;*/ + case VB_READ : target = (void **) &sim->onRead ; break; + case VB_WRITE : target = (void **) &sim->onWrite ; break; + } + + /* Retrieve current state and update new state */ + if (target != NULL) { + prev = *target; + *target = callback; } return prev; } -/* Specify a value for a register */ -int32_t vbSetRegister(VB *sim, int type, int id, int32_t value) { - switch (type) { - case VB_PROGRAM: - return id < 1 || id > 31 ? 0 : (sim->cpu.program[id] = value); - case VB_SYSTEM: - return cpuSetSystemRegister(sim, id, value, 1); - case VB_OTHER: - switch (id) { - case VB_PC: - sim->cpu.bitstring = 0; - sim->cpu.clocks = 0; - sim->cpu.exception = 0; - sim->cpu.stage = CPU_FETCH; - return sim->cpu.pc = value & 0xFFFFFFFE; - } +/* 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; } - return 0; /* Invalid type or ID */ -} - -/* Specify a cartridge ROM buffer */ -int vbSetROM(VB *sim, uint8_t *data, uint32_t size) { - - /* Specifying no ROM */ - if (data == NULL) { - sim->cart.rom = NULL; - sim->cart.romSize = 0; - return 0; - } - - /* Error checking */ - if ( - size < 4 || - size > 0x1000000 || - (size & (size - 1)) /* Power of 2 */ - ) return 1; - - /* Register the ROM data */ - sim->cart.rom = data; - sim->cart.romSize = size; + sim->cart.ram = sram; return 0; } -/* Specify a cartridge RAM buffer */ -int vbSetSRAM(VB *sim, uint8_t *data, uint32_t size) { - - /* Specifying no SRAM */ - if (data == NULL) { - sim->cart.ram = NULL; - sim->cart.ramSize = 0; - 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; } - - /* Error checking */ - if ( - size < 4 || - size > 0x1000000 || - (size & (size - 1)) /* Power of 2 */ - ) return 1; - - /* Register the SRAM data */ - sim->cart.ram = data; - sim->cart.ramSize = size; + sim->cart.rom = rom; return 0; } -/* Write a value to memory */ -void vbWrite(VB *sim, uint32_t address, int type, int32_t value) { +/* 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 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); +} + +/* Determine the size of a simulation instance */ +VBAPI size_t vbSizeOf() { + return sizeof (VB); +} + +/* 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); -} - -/* Write multiple values to memory */ -void vbWriteEx(VB *sim, uint32_t address, uint8_t *buffer, uint32_t length) { - while (length--) busWrite(sim, address++, VB_U8, *buffer++, 1); + return vbRead(sim, address, type); } diff --git a/core/vb.h b/core/vb.h index aeedea6..6edc937 100644 --- a/core/vb.h +++ b/core/vb.h @@ -15,38 +15,15 @@ extern "C" { -/**************************** Compilation Control ****************************/ - -/* - 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 *********************************/ -/* Data types */ -#define VB_S8 0 -#define VB_U8 1 -#define VB_S16 2 -#define VB_U16 3 -#define VB_S32 4 - -/* Register types */ -#define VB_PROGRAM 0 -#define VB_SYSTEM 1 -#define VB_OTHER 2 +/* 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 @@ -60,157 +37,55 @@ extern "C" { #define VB_PSW 5 #define VB_TKCW 7 -/* Other registers */ -#define VB_PC 0 +/* Memory access data types */ +#define VB_S8 0 +#define VB_U8 1 +#define VB_S16 2 +#define VB_U16 3 +#define VB_S32 4 -/* Callbacks */ -#define VB_ONEXCEPTION 0 -#define VB_ONEXECUTE 1 -#define VB_ONFETCH 2 -#define VB_ONREAD 3 -#define VB_ONWRITE 4 +/* CPU modes */ +#define VB_ACCURACY 0 +#define VB_DEBUG 1 +#define VB_WHOLE 2 /*********************************** Types ***********************************/ -/* Forward references */ +/* Simulation state */ typedef struct VB VB; -/* Memory access descriptor */ -typedef struct { - uint32_t address; /* Memory address */ - uint32_t clocks; /* Number of clocks taken */ - int32_t value; /* Data loaded/to store */ - uint8_t type; /* Data type */ -} VBAccess; - -/* Exception descriptor */ -typedef struct { - uint32_t address; /* Memory address of handler routine */ - uint16_t code; /* Cause code */ - uint8_t cancel; /* Set to cancel the exception */ -} VBException; - -/* Instruction descriptor */ -typedef struct { - uint32_t address; /* Memory address */ - uint16_t code[2]; /* Fetched code units */ - uint8_t size; /* Size in halfwords */ -} VBInstruction; - -/* Breakpoint handlers */ -typedef int (*VBExceptionProc)(VB *, VBException *); -typedef int (*VBExecuteProc )(VB *, VBInstruction *); -typedef int (*VBFetchProc )(VB *, int, VBAccess *); -typedef int (*VBMemoryProc )(VB *, VBAccess *); - -/* Simulation state */ -struct VB { - - /* Game pak */ - struct { - uint8_t *ram; /* (S)RAM data */ - uint8_t *rom; /* ROM data */ - uint32_t ramSize; /* Size of RAM in bytes */ - uint32_t romSize; /* Size of ROM in bytes */ - } cart; - - /* CPU */ - struct { - - /* Main registers */ - int32_t program[32]; /* Program registers */ - uint32_t pc; /* Program counter */ - - /* System registers */ - uint32_t adtre; /* Address trap target address */ - uint32_t eipc; /* Exception return PC */ - uint32_t eipsw; /* Exception return PSW */ - uint32_t fepc; /* Duplexed exception return PC */ - uint32_t fepsw; /* Duplexed exception return PSW */ - uint32_t sr29; /* System register 29 */ - uint32_t sr31; /* System register 31 */ - - /* Exception cause register */ - struct { - uint16_t eicc; /* Exception/interrupt cause code */ - uint16_t fecc; /* Duplexed exception cause code */ - } ecr; - - /* Cache control word */ - struct { - uint8_t ice; /* Instruction cache enable */ - } chcw; - - /* Program status word */ - struct { - uint8_t ae; /* Address trap enable */ - uint8_t cy; /* Carry */ - uint8_t ep; /* Exception pending */ - uint8_t fiv; /* Floating-point invalid operation */ - uint8_t fov; /* Floating-point overflow */ - uint8_t fpr; /* Floating point precision degradation */ - uint8_t fro; /* Floating-point reserved operand */ - uint8_t fud; /* Floating-point underflow */ - uint8_t fzd; /* Floating-point zero division */ - uint8_t i; /* Interrupt level */ - uint8_t id; /* Interrupt disable */ - uint8_t np; /* Duplexed exception pending */ - uint8_t ov; /* Overflow */ - uint8_t s; /* Sign */ - uint8_t z; /* Zero */ - } psw; - - /* Instruction */ - struct { - int32_t aux[9]; /* Auxiliary storage */ - void *def; /* Operation descriptor */ - uint16_t code[2]; /* Fetched code units */ - uint8_t size; /* Size in bytes */ - } inst; - - /* Other state */ - uint32_t clocks; /* Clocks until next activity */ - uint32_t fpFlags; /* Floating-point exception flags */ - uint16_t exception; /* Current exception cause code */ - uint8_t irq[5]; /* Interrupt requests */ - uint8_t bitstring; /* Processing a bit string instruction */ - uint8_t stage; /* Pipeline stage handler */ - uint8_t step; /* General processing step index */ - } cpu; - - /* Breakpoint hooks */ - VBExceptionProc onException; /* Exception raised */ - VBExecuteProc onExecute; /* Instruction execute */ - VBFetchProc onFetch; /* Bus read (fetch) */ - VBMemoryProc onRead; /* Bus read (execute) */ - VBMemoryProc onWrite; /* Bus write */ - - /* Other state */ - uint8_t wram[0x10000]; /* System memory */ -}; +/* Callbacks */ +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 ************************************/ -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 int32_t vbGetRegister(VB *sim, int type, int id); -VBAPI uint8_t* vbGetROM (VB *sim, uint32_t *size); -VBAPI uint8_t* vbGetSRAM (VB *sim, uint32_t *size); -VBAPI void vbInit (VB *sim); -VBAPI int32_t vbRead (VB *sim, uint32_t address, int type); -VBAPI void vbReadEx (VB *sim, uint32_t address, uint8_t *buffer, uint32_t length); -VBAPI void vbReset (VB *sim); -VBAPI void* vbSetCallback(VB *sim, int id, void *proc); -VBAPI int32_t vbSetRegister(VB *sim, int type, int id, int32_t value); -VBAPI int vbSetROM (VB *sim, uint8_t *data, uint32_t size); -VBAPI int vbSetSRAM (VB *sim, uint8_t *data, uint32_t size); -VBAPI void vbWrite (VB *sim, uint32_t address, int type, int32_t value); -VBAPI void vbWriteEx (VB *sim, uint32_t address, uint8_t *buffer, uint32_t length); +/******************************* 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 uint32_t vbGetProgramCounter (VB *sim); +VBAPI int32_t vbGetProgramRegister(VB *sim, int index); +VBAPI uint32_t vbGetSystemRegister (VB *sim, int index); +VBAPI VB* vbInit (VB *sim); +VBAPI int32_t vbRead (VB *sim, uint32_t address, int type); +VBAPI VB* vbReset (VB *sim); +VBAPI void* vbSetCallback (VB *sim, int index, void *callback); +VBAPI int vbSetCartRAM (VB *sim, void *sram, uint32_t size); +VBAPI int vbSetCartROM (VB *sim, void *rom, uint32_t size); +VBAPI uint32_t vbSetProgramCounter (VB *sim, uint32_t value); +VBAPI int32_t vbSetProgramRegister(VB *sim, int index, int32_t value); +VBAPI uint32_t vbSetSystemRegister (VB *sim, int index, uint32_t value); +VBAPI size_t vbSizeOf (); +VBAPI int32_t vbWrite (VB *sim, uint32_t address, int type, int32_t value); diff --git a/license.txt b/license.txt index 1b40ee3..48747ee 100644 --- a/license.txt +++ b/license.txt @@ -1,4 +1,4 @@ -Copyright (C) 2023 Guy Perfect +Copyright (C) 2024 Guy Perfect This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/makefile b/makefile index 2f8c750..4b8d91c 100644 --- a/makefile +++ b/makefile @@ -1,7 +1,7 @@ .PHONY: help help: @echo - @echo "Virtual Boy Emulator - March 12, 2023" + @echo "Virtual Boy Emulator - October 10, 2024" @echo @echo "Target build environment is any Debian with the following packages:" @echo " emscripten" diff --git a/web/App.js b/web/App.js deleted file mode 100644 index 5199c16..0000000 --- a/web/App.js +++ /dev/null @@ -1,691 +0,0 @@ -import { Core } from /**/"./core/Core.js"; -import { Debugger } from /**/"./debugger/Debugger.js"; -import { Disassembler } from /**/"./core/Disassembler.js"; -import { Toolkit } from /**/"./toolkit/Toolkit.js"; - -// Front-end emulator application -class App extends Toolkit.App { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(bundle) { - super({ - style: { - display : "grid", - gridTemplateRows: "max-content auto" - }, - visibility: true, - visible : false - }); - - // Configure instance fields - this.bundle = bundle; - this.debugMode = true; - this.dualMode = false; - this.text = null; - } - - async init() { - - // Theme - Object.assign(document.body.style, { margin:"0", overflow:"hidden" }); - this.stylesheet(/**/"web/theme/kiosk.css", false); - this.stylesheet(/**/"web/theme/vbemu.css", false); - this._theme = "auto"; - this.themes = { - dark : this.stylesheet(/**/"web/theme/dark.css" ), - light : this.stylesheet(/**/"web/theme/light.css" ), - virtual: this.stylesheet(/**/"web/theme/virtual.css") - }; - - // Watch for dark mode preference changes - this.isDark = window.matchMedia("(prefers-color-scheme: dark)"); - this.isDark.addEventListener("change", e=>this.onDark()); - this.onDark(); - - // Locales - await this.addLocale(/**/"web/locale/en-US.json"); - for (let id of [].concat(navigator.languages, ["en-US"])) { - if (this.setLocale(id)) - break; - } - this.setTitle("{app.title}", true); - - // Element - document.body.append(this.element); - window.addEventListener("resize", e=>{ - this.element.style.height = window.innerHeight + "px"; - this.element.style.width = window.innerWidth + "px"; - }); - window.dispatchEvent(new Event("resize")); - this.addEventListener("keydown", e=>this.onKeyDown(e)); - - // Menus - this.menuBar = new Toolkit.MenuBar(this); - this.menuBar.setLabel("{menu._}", true); - this.add(this.menuBar); - this.initFileMenu (); - this.initEmulationMenu(); - this.initDebugMenu (0, this.debugMode); - this.initDebugMenu (1, this.debugMode && this.dualMode); - this.initThemeMenu (); - - // Fallback for bubbled key events - document.body.addEventListener("focusout", e=>this.onBlur(e)); - window .addEventListener("keydown" , e=>this.onKey (e)); - window .addEventListener("keyup" , e=>this.onKey (e)); - - // Temporary: Faux game mode display - this.display = new Toolkit.Component(this, { - class : "tk display", - style : { position: "relative" }, - visible: !this.debugMode - }); - this.image1 = new Toolkit.Component(this, { style: { - background: "#000000", - position : "absolute" - }}); - this.display.add(this.image1); - this.image2 = new Toolkit.Component(this, { style: { - background: "#000000", - position : "absolute" - }}); - this.display.add(this.image2); - this.display.addEventListener("resize", e=>this.onDisplay()); - this.add(this.display); - - // Temporary: Faux debug mode display - this.desktop = new Toolkit.Desktop(this, { - visible: this.debugMode - }); - this.add(this.desktop); - - // Emulation core - this.core = await new Core().init(); - let sims = (await this.core.create(2)).sims; - this.core.onsubscription = (k,m)=>this.onSubscription(k, m); - - // Debugging managers - this.dasm = new Disassembler(); - this.debug = new Array(sims.length); - for (let x = 0; x < sims.length; x++) { - let dbg = this.debug[x] = new Debugger(this, sims[x], x); - if (x == 0 && !this.dualMode) { - dbg.cpu .substitute("#", ""); - dbg.memory.substitute("#", ""); - } - this.desktop.add(dbg.cpu); - this.desktop.add(dbg.memory); - } - - // Reveal the application - this.visible = true; - this.restoreFocus(); - - console.log( - "CPU window shortcuts:\n" + - " F11 Single step\n" + - " F10 Run to next\n" + - " Ctrl+B Toggle bytes column\n" + - " Ctrl+F Fit columns\n" + - " Ctrl+G Goto" - ); - } - - // Initialize File menu - initFileMenu() { - let bar = this.menuBar; - let item = bar.file = new Toolkit.MenuItem(this); - item.setText("{menu.file._}"); - bar.add(item); - - let menu = item.menu = new Toolkit.Menu(this); - - item = bar.file.loadROM0 = new Toolkit.MenuItem(this); - item.setText("{menu.file.loadROM}"); - item.substitute("#", this.dualMode ? " 1" : "", false); - item.addEventListener("action", e=>this.onLoadROM(0)); - menu.add(item); - - item = bar.file.loadROM1 = new Toolkit.MenuItem(this, - { visible: this.dualMode }); - item.setText("{menu.file.loadROM}"); - item.substitute("#", " 2", false); - item.addEventListener("action", e=>this.onLoadROM(1)); - menu.add(item); - - item = bar.file.dualMode = new Toolkit.MenuItem(this, - { checked: this.dualMode, type: "checkbox" }); - item.setText("{menu.file.dualMode}"); - item.addEventListener("action", e=>this.onDualMode()); - menu.add(item); - - item = bar.file.debugMode = new Toolkit.MenuItem(this, - { checked: this.debugMode, disabled: true, type: "checkbox" }); - item.setText("{menu.file.debugMode}"); - item.addEventListener("action", e=>this.onDebugMode()); - menu.add(item); - - menu.addSeparator(); - - item = new Toolkit.MenuItem(this); - item.setText("Export source...", false); - item.addEventListener("action", ()=>this.bundle.save()); - menu.add(item); - } - - // Initialize Emulation menu - initEmulationMenu() { - let bar = this.menuBar; - let item = bar.emulation = new Toolkit.MenuItem(this); - item.setText("{menu.emulation._}"); - bar.add(item); - - let menu = item.menu = new Toolkit.Menu(this); - - item = bar.emulation.run = - new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.emulation.run}"); - menu.add(item); - - item = bar.emulation.reset0 = new Toolkit.MenuItem(this); - item.setText("{menu.emulation.reset}"); - item.substitute("#", this.dualMode ? " 1" : "", false); - item.addEventListener("action", e=>this.onReset(0)); - menu.add(item); - - item = bar.emulation.reset1 = new Toolkit.MenuItem(this, - { visible: this.dualMode }); - item.setText("{menu.emulation.reset}"); - item.substitute("#", " 2", false); - item.addEventListener("action", e=>this.onReset(1)); - menu.add(item); - - item = bar.emulation.linkSims = new Toolkit.MenuItem(this, - { disabled: true, type: "checkbox", visible: this.dualMode }); - item.setText("{menu.emulation.linkSims}"); - menu.add(item); - } - - // Initialize Debug menu - initDebugMenu(index, visible) { - let bar = this.menuBar; - let item = bar["debug" + index] = new Toolkit.MenuItem(this, - { visible: visible }), top = item; - item.setText("{menu.debug._}"); - item.substitute("#", - index == 0 ? this.dualMode ? "1" : "" : " 2", false); - bar.add(item); - - let menu = item.menu = new Toolkit.Menu(this); - - item = top.console = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.console}"); - menu.add(item); - - item = top.memory = new Toolkit.MenuItem(this); - item.setText("{menu.debug.memory}"); - item.addEventListener("action", - e=>this.showWindow(this.debug[index].memory)); - menu.add(item); - - item = top.cpu = new Toolkit.MenuItem(this); - item.setText("{menu.debug.cpu}"); - item.addEventListener("action", - e=>this.showWindow(this.debug[index].cpu)); - menu.add(item); - - item=top.breakpoints = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.breakpoints}"); - menu.add(item); - - menu.addSeparator(); - - item = top.palettes = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.palettes}"); - menu.add(item); - - item = top.characters = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.characters}"); - menu.add(item); - - item = top.bgMaps = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.bgMaps}"); - menu.add(item); - - item = top.backgrounds = new Toolkit.MenuItem(this, { disabled:true }); - item.setText("{menu.debug.backgrounds}"); - menu.add(item); - - item = top.objects = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.objects}"); - menu.add(item); - - item = top.frameBuffers = new Toolkit.MenuItem(this, {disabled: true}); - item.setText("{menu.debug.frameBuffers}"); - menu.add(item); - } - - // Initialize Theme menu - initThemeMenu() { - let bar = this.menuBar; - let item = bar.theme = new Toolkit.MenuItem(this); - item.setText("{menu.theme._}"); - bar.add(item); - - let menu = item.menu = new Toolkit.Menu(this); - - item = bar.theme.auto = new Toolkit.MenuItem(this, - { checked: true, type: "checkbox" }); - item.setText("{menu.theme.auto}"); - item.theme = "auto"; - item.addEventListener("action", e=>this.theme = "auto"); - menu.add(item); - - item = bar.theme.light = new Toolkit.MenuItem(this, - { checked: false, type: "checkbox" }); - item.setText("{menu.theme.light}"); - item.theme = "light"; - item.addEventListener("action", e=>this.theme = "light"); - menu.add(item); - - item = bar.theme.dark = new Toolkit.MenuItem(this, - { checked: false, type: "checkbox" }); - item.setText("{menu.theme.dark}"); - item.theme = "dark"; - item.addEventListener("action", e=>this.theme = "dark"); - menu.add(item); - - item = bar.theme.light = new Toolkit.MenuItem(this, - { checked: false, type: "checkbox" }); - item.setText("{menu.theme.virtual}"); - item.theme = "virtual"; - item.addEventListener("action", e=>this.theme = "virtual"); - menu.add(item); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // All elements have lost focus - onBlur(e) { - if ( - e.relatedTarget == null || - e.relatedTarget == document.body - ) this.restoreFocus(); - } - - // Dark mode preference changed - onDark() { - if (this._theme != "auto") - return; - let isDark = this.isDark.matches; - this.themes.light.disabled = isDark; - this.themes.dark .disabled = !isDark; - } - - // Game mode display resized - onDisplay() { - let bounds = this.display.element.getBoundingClientRect(); - let width = Math.max(1, bounds.width); - let height = Math.max(1, bounds.height); - let scale, x1, y1, x2, y2; - - // Single mode - if (!this.dualMode) { - this.image2.visible = false; - scale = Math.max(1, Math.min( - Math.floor(width / 384), - Math.floor(height / 224) - )); - x1 = Math.max(0, Math.floor((width - 384 * scale) / 2)); - y1 = Math.max(0, Math.floor((height - 224 * scale) / 2)); - x2 = y2 = 0; - } - - // Dual mode - else { - this.image2.visible = true; - - // Horizontal orientation - if (true) { - scale = Math.max(1, Math.min( - Math.floor(width / 768), - Math.floor(height / 224) - )); - let gap = Math.max(0, width - 768 * scale); - gap = gap < 0 ? 0 : Math.floor(gap / 3) + (gap%3==2 ? 1 : 0); - x1 = gap; - x2 = Math.max(384 * scale, width - 384 * scale - gap); - y1 = y2 = Math.max(0, Math.floor((height - 224 * scale) / 2)); - } - - // Vertical orientation - else { - scale = Math.max(1, Math.min( - Math.floor(width / 384), - Math.floor(height / 448) - )); - let gap = Math.max(0, height - 448 * scale); - gap = gap < 0 ? 0 : Math.floor(gap / 3) + (gap%3==2 ? 1 : 0); - x1 = x2 = Math.max(0, Math.floor((width - 384 * scale) / 2)); - y1 = gap; - y2 = Math.max(224 * scale, height - 224 * scale - gap); - } - - } - - width = 384 * scale + "px"; - height = 224 * scale + "px"; - Object.assign(this.image1.element.style, - { left: x1+"px", top: y1+"px", width: width, height: height }); - Object.assign(this.image2.element.style, - { left: x2+"px", top: y2+"px", width: width, height: height }); - } - - // File -> Debug mode - onDebugMode() { - this.debugMode =!this.debugMode; - this.display.visible =!this.debugMode; - this.desktop.visible = this.debugMode; - this.configMenus(); - this.onDisplay(); - } - - // Emulation -> Dual mode - onDualMode() { - this.setDualMode(!this.dualMode); - this.configMenus(); - this.onDisplay(); - } - - // Key press - onKeyDown(e) { - - // Take no action - if (!e.altKey || e.key != "F10" || - this.menuBar.contains(document.activeElement)) - return; - - // Move focus into the menu bar - this.menuBar.focus(); - Toolkit.handle(e); - } - - // File -> Load ROM - async onLoadROM(index) { - - // Add a file picker to the document - let file = document.createElement("input"); - file.type = "file"; - file.style.position = "absolute"; - file.style.visibility = "hidden"; - document.body.appendChild(file); - - // Prompt the user to select a file - await new Promise(resolve=>{ - file.addEventListener("input", resolve); - file.click(); - }); - file.remove(); - - // No file was selected - file = file.files[0]; - if (!file) - return; - - // Load the file - let data = null; - try { data = new Uint8Array(await file.arrayBuffer()); } - catch { - alert(this.localize("{menu.file.loadROMError}")); - return; - } - - // Attempt to process the file as an ISX binary - try { data = Debugger.isx(data).toROM(); } catch { } - - // Error checking - if ( - data.length < 1024 || - data.length > 0x1000000 || - (data.length & data.length - 1) - ) { - alert(this.localize("{menu.file.loadROMInvalid}")); - return; - } - - // Load the ROM into simulation memory - let rep = await this.core.setROM(this.debug[index].sim, data, - { refresh: true }); - if (!rep.success) { - alert(this.localize("{menu.file.loadROMError}")); - return; - } - this.debug[index].followPC(0xFFFFFFF0); - } - - // Emulation -> Reset - async onReset(index) { - await this.core.reset(this.debug[index].sim, { refresh: true }); - this.debug[index].followPC(0xFFFFFFF0); - } - - // Core subscription - onSubscription(key, msg) { - let target = this.debug; // Handler object - for (let x = 1; x < key.length - 1; x++) - target = target[key[x]]; - target[key[key.length - 1]].call(target, msg); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Specify document title - setTitle(title, localize) { - this.setString("text", title, localize); - } - - // Specify the color theme - get theme() { return this._theme; } - set theme(theme) { - switch (theme) { - case "light": case "dark": case "virtual": - this._theme = theme; - for (let entry of Object.entries(this.themes)) - entry[1].disabled = entry[0] != theme; - break; - default: - this._theme = "auto"; - this.themes["virtual"].disabled = true; - this.onDark(); - } - for (let item of this.menuBar.theme.menu.children) - item.checked = item.theme == theme; - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Configure components for automatic localization, or localize a message - localize(a, b) { - - // Default behavior - if (a && a != this) - return super.localize(a, b); - - // Update localization strings - if (this.text != null) { - let text = this.text; - document.title = !text[1] ? text[0] : - this.localize(text[0], this.substitutions); - } - - } - - // Return focus to the most recent focused element - restoreFocus() { - - // Focus was successfully restored - if (super.restoreFocus()) - return true; - - // Select the foremost visible window - let wnd = this.desktop.getActiveWindow(); - if (wnd) { - wnd.focus(); - return true; - } - - // Select the menu bar - this.menuBar.focus(); - return true; - } - - // Perform a Run Next command on one of the simulations - runToNext(index, options) { - let debugs = [ this.debug[index] ]; - if (this.dualMode) - debugs.push(this.debug[index ^ 1]); - - let ret = this.core.runToNext(debugs.map(d=>d.sim), options); - - if (ret instanceof Promise) ret.then(msg=>{ - for (let x = 0; x < debugs.length; x++) - debugs[x].followPC(msg.pcs[x]); - }); - } - - // Perform a Single Step command on one of the simulations - singleStep(index, options) { - let debugs = [ this.debug[index] ]; - if (this.dualMode) - debugs.push(this.debug[index ^ 1]); - - let ret = this.core.singleStep(debugs.map(d=>d.sim), options); - - if (ret instanceof Promise) ret.then(msg=>{ - for (let x = 0; x < debugs.length; x++) - debugs[x].followPC(msg.pcs[x]); - }); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Configure menu item visibility - configMenus() { - let bar = this.menuBar; - bar.file.debugMode .checked = this.debugMode; - bar.file.loadROM1 .visible = this.dualMode; - bar.emulation.reset1 .visible = this.dualMode; - bar.emulation.linkSims.visible = this.dualMode; - bar.debug0 .visible = this.debugMode; - bar.debug1 .visible = this.debugMode && this.dualMode; - } - - // Specify whether dual mode is active - setDualMode(dualMode) { - - // Update state - if (dualMode == this.dualMode) - return; - this.dualMode = dualMode; - - // Working variables - let index = dualMode ? " 1" : ""; - - // Update menus - let bar = this.menuBar; - bar.file.loadROM0 .substitute("#", index, false); - bar.debug0 .substitute("#", index, false); - bar.emulation.reset0.substitute("#", index, false); - bar.file.dualMode .checked = this.dualMode; - - // Update sim 1 debug windows - let dbg = this.debug[0]; - dbg.cpu .substitute("#", index); - dbg.memory.substitute("#", index); - - // Re-show any sim 2 debug windows that were previously visible - if (dualMode) { - for (let wnd of this.desktop.children) { - if (wnd.index == 1 && wnd.wasVisible) - wnd.visible = true; - } - } - - // Hide any visible sim 2 debug windows - else for (let wnd of this.desktop.children) { - if (wnd.index == 0) - continue; - wnd.wasVisible = wnd.visible; - wnd.visible = false; - } - - } - - // Ensure a debugger window is visible - showWindow(wnd) { - let adjust = false; - - // The window is already visible - if (wnd.visible) { - - // The window is already in the foreground - if (wnd == this.desktop.getActiveWindow()) - ;//adjust = true; - - // Bring the window to the foreground - else this.desktop.bringToFront(wnd); - - } - - // The window is not visible - else { - adjust = !wnd.shown; - if (adjust && wnd.firstShow) - wnd.firstShow(); - wnd.visible = true; - this.desktop.bringToFront(wnd); - } - - // Adjust the window position - if (adjust) { - let bounds = this.desktop.element.getBoundingClientRect(); - wnd.left = Math.max(0, (bounds.width - wnd.outerWidth ) / 2); - wnd.top = Math.max(0, (bounds.height - wnd.outerHeight) / 2); - } - - // Transfer focus into the window - wnd.focus(); - } - - // Install a stylesheet. Returns the resulting element - stylesheet(filename, disabled = true) { - let ret = document.createElement("link"); - ret.href = filename; - ret.rel = "stylesheet"; - ret.type = "text/css"; - ret.disabled = disabled; - document.head.append(ret); - return ret; - } - - - - ///////////////////////////// Program Methods ///////////////////////////// - - // Program entry point - static main(bundle) { - new App(bundle).init(); - } - -} - -export { App }; diff --git a/web/Bundle.java b/web/Bundle.java deleted file mode 100644 index 7f72e46..0000000 --- a/web/Bundle.java +++ /dev/null @@ -1,203 +0,0 @@ -import java.awt.image.*; -import java.io.*; -import java.nio.charset.*; -import java.util.*; -import javax.imageio.*; - -public class Bundle { - - /////////////////////////////// BundledFile /////////////////////////////// - - // Individual packaged resource file - static class BundledFile implements Comparable { - - // Instance fields - byte[] data; // File data loaded from disk - File file; // Source file - String filename; // Logical filename - - // Constructor - BundledFile(BundledFile parent, File file) { - - // Configure instance fields - this.file = file; - filename = parent == null ? "" : parent.filename + file.getName(); - - // Load file data if file - if (file.isFile()) { - try (var stream = new FileInputStream(file)) { - data = stream.readAllBytes(); - } catch (Exception e) { } - } - - // Update filename if directory - else if (parent != null) filename += "/"; - } - - // Comparator - public int compareTo(BundledFile o) { - return - filename.equals("web/_boot.js") ? -1 : - o.filename.equals("web/_boot.js") ? +1 : - filename.compareTo(o.filename) - ; - } - - // Produce a list of child files or directories - BundledFile[] listFiles(String name, boolean isDirectory) { - - // Produce a filtered list of files - var files = this.file.listFiles(f->{ - - // Does not satisfy the directory requirement - if (f.isDirectory() != isDirectory) - return false; - - // Current directory is not root - if (!filename.equals("")) - return true; - - // Filter specific files from being bundled - String filename = f.getName(); - return !( - filename.startsWith(".git" ) || - filename.startsWith(name + "_") && - filename.endsWith (".html" ) - ); - }); - - // Process all files for bundling - var ret = new BundledFile[files.length]; - for (int x = 0; x < files.length; x++) - ret[x] = new BundledFile(this, files[x]); - return ret; - } - - } - - - - ///////////////////////////// Program Methods ///////////////////////////// - - // Program entry point - public static void main(String[] args) { - String name = name(args[0]); - var files = listFiles(args[0]); - var prepend = prepend(name, files); - var bundle = bundle(prepend, files); - var image = image(bundle); - var url = url(image); - patch(name, url); - } - - // Produce a buffer of the bundled files - static byte[] bundle(byte[] prepend, BundledFile[] files) { - try (var stream = new ByteArrayOutputStream()) { - stream.write(prepend); - stream.write(files[0].data); // web/_boot.js - stream.write(0); - for (int x = 1; x < files.length; x++) - stream.write(files[x].data); - return stream.toByteArray(); - } catch (Exception e) { return null; } - } - - // Convert a bundle buffer into a PNG-encoded image buffer - static byte[] image(byte[] bundle) { - int width = (int) Math.ceil(Math.sqrt(bundle.length)); - int height = (bundle.length + width - 1) / width; - var pixels = new int[width * height]; - - // Encode the buffer as a pixel array - for (int x = 0; x < bundle.length; x++) { - int b = bundle[x] & 0xFF; - pixels[x] = 0xFF000000 | b << 16 | b << 8 | b; - } - - // Produce an image using the pixels - var image = new BufferedImage( - width, height, BufferedImage.TYPE_INT_RGB); - image.setRGB(0, 0, width, height, pixels, 0, width); - - // Encode the image as a PNG buffer - try (var stream = new ByteArrayOutputStream()) { - ImageIO.write(image, "png", stream); - return stream.toByteArray(); - } catch (Exception e) { return null; } - } - - // List all files - static BundledFile[] listFiles(String name) { - var dirs = new ArrayList(); - var files = new ArrayList(); - - // Propagate down the file system tree - dirs.add(new BundledFile(null, new File("."))); - while (!dirs.isEmpty()) { - var dir = dirs.remove(0); - for (var sub : dir.listFiles(name, true )) - dirs.add(sub ); - for (var file : dir.listFiles(name, false)) - files.add(file); - } - - // Return the set of files as a sorted array - Collections.sort(files); - return files.toArray(new BundledFile[files.size()]); - } - - // Generate a filename for the bundle - static String name(String name) { - var calendar = Calendar.getInstance(); - return String.format("%s_%04d%02d%02d", - name, - calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH) + 1, - calendar.get(Calendar.DAY_OF_MONTH) - ); - } - - // Produce the output HTML from the template - static void patch(String name, String url) { - String markup = null; - try (var stream = new FileInputStream("web/template.html")) { - markup = new String(stream.readAllBytes(), StandardCharsets.UTF_8); - } catch (Exception e) { } - markup = markup.replace("src=\"\"", "src=\"" + url + "\""); - try (var stream = new FileOutputStream(name + ".html")) { - stream.write(markup.getBytes(StandardCharsets.UTF_8)); - } catch (Exception e) { } - } - - // Produce source data to prepend to web/_boot.js - static byte[] prepend(String name, BundledFile[] files) { - var ret = new StringBuilder(); - - // Arguments - ret.append("let " + - "buffer=arguments[0]," + - "image=arguments[1]," + - "name=\"" + name + "\"" + - ";"); - - // Bundle manifest - ret.append("let manifest=["); - for (var file : files) { - if (file != files[0]) - ret.append(","); - ret.append("[\"" + - file.filename + "\", " + file.data.length + "]"); - } - ret.append("];"); - - // Convert to byte array - return ret.toString().getBytes(StandardCharsets.UTF_8); - } - - // Convert an image buffer to a data URL - static String url(byte[] image) { - return "data:image/png;base64," + - Base64.getMimeEncoder(0, new byte[0]).encodeToString(image); - } - -} diff --git a/web/_boot.js b/web/_boot.js deleted file mode 100644 index 591d8ec..0000000 --- a/web/_boot.js +++ /dev/null @@ -1,310 +0,0 @@ -// Running as an async function -// Prepended by Bundle.java: buffer, image, manifest, name - - - -/////////////////////////////////////////////////////////////////////////////// -// Bundle // -/////////////////////////////////////////////////////////////////////////////// - -// Resource manager for bundled files -class Bundle extends Array { - - //////////////////////////////// Constants //////////////////////////////// - - // .ZIP support - static CRC_LOOKUP = new Uint32Array(256); - - // Text processing - static DECODER = new TextDecoder(); - static ENCODER = new TextEncoder(); - - static initializer() { - - // Generate the CRC32 lookup table - for (let x = 0; x <= 255; x++) { - let l = x; - for (let j = 7; j >= 0; j--) - l = ((l >>> 1) ^ (0xEDB88320 & -(l & 1))); - this.CRC_LOOKUP[x] = l; - } - - } - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(name, url, settings, isDebug) { - super(); - this.isDebug = isDebug; - this.name = name; - this.settings = settings; - this.url = url; - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Export all bundled resources to a .ZIP file - save() { - let centrals = new Array(this.length); - let locals = new Array(this.length); - let offset = 0; - let size = 0; - - // Encode file and directory entries - for (let x = 0; x < this.length; x++) { - let file = this[x]; - let sum = Bundle.crc32(file.data); - locals [x] = file.toZipHeader(sum); - centrals[x] = file.toZipHeader(sum, offset); - offset += locals [x].length; - size += centrals[x].length; - } - - // Encode end of central directory - let end = []; - Bundle.writeInt(end, 4, 0x06054B50); // Signature - Bundle.writeInt(end, 2, 0); // Disk number - Bundle.writeInt(end, 2, 0); // Central dir start disk - Bundle.writeInt(end, 2, this.length); // # central dir this disk - Bundle.writeInt(end, 2, this.length); // # central dir total - Bundle.writeInt(end, 4, size); // Size of central dir - Bundle.writeInt(end, 4, offset); // Offset of central dir - Bundle.writeInt(end, 2, 0); // .ZIP comment length - - // Prompt the user to save the resulting file - let a = document.createElement("a"); - a.download = this.name + ".zip"; - a.href = URL.createObjectURL(new Blob( - locals.concat(centrals).concat([Uint8Array.from(end)]), - { type: "application/zip" } - )); - a.style.visibility = "hidden"; - document.body.appendChild(a); - a.click(); - a.remove(); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Add a BundledFile to the collection - add(file) { - file.bundle = this; - this.push(this[file.name] = file); - } - - // Write a byte array into an output buffer - static writeBytes(data, bytes) { - for (let b of bytes) - data.push(b); - } - - // Write an integer into an output buffer - static writeInt(data, size, value) { - for (; size > 0; size--, value >>= 8) - data.push(value & 0xFF); - } - - // Write a string of text as bytes into an output buffer - static writeString(data, text) { - this.writeBytes(data, this.ENCODER.encode(text)); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Calculate the CRC32 checksum for a byte array - static crc32(data) { - let c = 0xFFFFFFFF; - for (let x = 0; x < data.length; x++) - c = ((c >>> 8) ^ this.CRC_LOOKUP[(c ^ data[x]) & 0xFF]); - return ~c & 0xFFFFFFFF; - } - -} -Bundle.initializer(); - - - -/////////////////////////////////////////////////////////////////////////////// -// BundledFile // -/////////////////////////////////////////////////////////////////////////////// - -// Individual file in the bundled data -class BundledFile { - - //////////////////////////////// Constants //////////////////////////////// - - // MIME types - static MIMES = { - ".css" : "text/css;charset=UTF-8", - ".frag" : "text/plain;charset=UTF-8", - ".js" : "text/javascript;charset=UTF-8", - ".json" : "application/json;charset=UTF-8", - ".png" : "image/png", - ".svg" : "image/svg+xml;charset=UTF-8", - ".txt" : "text/plain;charset=UTF-8", - ".vert" : "text/plain;charset=UTF-8", - ".wasm" : "application/wasm", - ".woff2": "font/woff2" - }; - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(name, buffer, offset, length) { - - // Configure instance fields - this.data = buffer.slice(offset, offset + length); - this.name = name; - - // Resolve the MIME type - let index = name.lastIndexOf("."); - this.mime = index != -1 && BundledFile.MIMES[name.substring(index)] || - "application/octet-stream"; - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Represent the file with a blob URL - toBlobURL() { - return this.blobURL || (this.blobURL = URL.createObjectURL( - new Blob([ this.data ], { type: this.mime }))); - } - - // Encode the file data as a data URL - toDataURL() { - return "data:" + this.mime + ";base64," + btoa( - Array.from(this.data).map(b=>String.fromCharCode(b)).join("")); - } - - // Pre-process URLs in a bundled file's contents - toProcURL(asDataURL = false) { - - // The URL has already been computed - if (this.url) - return this.url; - - // Working variables - let content = this.toString(); - let pattern = /\/\*\*?\*\//g; - let parts = content.split(pattern); - let ret = [ parts.shift() ]; - - // Process all URLs prefixed with /**/ or /***/ - for (let part of parts) { - let start = part.indexOf("\""); - let end = part.indexOf("\"", start + 1); - let filename = part.substring(start + 1, end); - let asData = pattern.exec(content)[0] == "/***/"; - - // Relative to current file - if (filename.startsWith(".")) { - let path = this.name.split("/"); - path.pop(); // Current filename - - // Navigate to the path of the target file - for (let dir of filename.split("/")) { - switch (dir) { - case "..": path.pop(); // Fallthrough - case "." : break; - default : path.push(dir); - } - } - - // Produce the fully-qualified filename - filename = path.join("/"); - } - - // Append the file as a data URL - let file = this.bundle[filename]; - ret.push( - part.substring(0, start + 1), - file[ - file.mime.startsWith("text/javascript") || - file.mime.startsWith("text/css") ? - "toProcURL" : asData ? "toDataURL" : "toBlobURL" - ](asData), - part.substring(end) - ); - } - - // Represent the transformed source as a URL - return this.url = asDataURL ? - "data:" + this.mime + ";base64," + btoa(ret.join("")) : - URL.createObjectURL(new Blob(ret, { type: this.mime })) - ; - } - - // Decode the file data as a UTF-8 string - toString() { - return Bundle.DECODER.decode(this.data); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Produce a .ZIP header for export - toZipHeader(crc32, offset) { - let central = offset !== undefined; - let ret = []; - if (central) { - Bundle.writeInt (ret, 4, 0x02014B50); // Signature - Bundle.writeInt (ret, 2, 20); // Version created by - } else - Bundle.writeInt (ret, 4, 0x04034B50); // Signature - Bundle.writeInt (ret, 2, 20); // Version required - Bundle.writeInt (ret, 2, 0); // Bit flags - Bundle.writeInt (ret, 2, 0); // Compression method - Bundle.writeInt (ret, 2, 0); // Modified time - Bundle.writeInt (ret, 2, 0); // Modified date - Bundle.writeInt (ret, 4, crc32); // Checksum - Bundle.writeInt (ret, 4, this.data.length); // Compressed size - Bundle.writeInt (ret, 4, this.data.length); // Uncompressed size - Bundle.writeInt (ret, 2, this.name.length); // Filename length - Bundle.writeInt (ret, 2, 0); // Extra field length - if (central) { - Bundle.writeInt (ret, 2, 0); // File comment length - Bundle.writeInt (ret, 2, 0); // Disk number start - Bundle.writeInt (ret, 2, 0); // Internal attributes - Bundle.writeInt (ret, 4, 0); // External attributes - Bundle.writeInt (ret, 4, offset); // Relative offset - } - Bundle.writeString (ret, this.name); // Filename - if (!central) - Bundle.writeBytes(ret, this.data); // File data - return Uint8Array.from(ret); - } - -} - - - -/////////////////////////////////////////////////////////////////////////////// -// Boot Program // -/////////////////////////////////////////////////////////////////////////////// - -// Produce the application bundle -let bundle = new Bundle(name, image.src, image.getAttribute("settings") || "", - location.protocol != "file:" && location.hash == "#debug"); -for (let x=0,offset=buffer.indexOf(0)-manifest[0][1]; xthis.init(m.data); - } - - async init(main) { - - // Configure message ports - this.core = this.port; - this.core.onmessage = m=>this.onCore(m.data); - this.main = main; - this.main.onmessage = m=>this.onMain(m.data); - - // Notify main thread - this.port.postMessage(0); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Produce output samples (called by the user agent) - process(inputs, outputs, parameters) { - let output = outputs[0]; - let length = output [0].length; - let empty = null; - - // Process all samples - for (let x = 0; x < length;) { - - // No bufferfed samples are available - if (this.buffers.length == 0) { - for (; x < length; x++) - output[0] = output[1] = 0; - break; - } - - // Transfer samples from the oldest buffer - let y, buffer = this.buffers[0]; - for (y = this.offset; x < length && y < buffer.length; x++, y+=2) { - output[0][x] = buffer[y ]; - output[1][x] = buffer[y + 1]; - } - - // Advance to the next buffer - if ((this.offset = y) == buffer.length) { - if (empty == null) - empty = []; - empty.push(this.buffers.shift()); - this.offset = 0; - } - - } - - // Return emptied sample buffers to the core thread - if (empty != null) - this.core.postMessage(empty, empty.map(e=>e.buffer)); - - return true; - } - - - - ///////////////////////////// Message Methods ///////////////////////////// - - // Message received from core thread - onCore(msg) { - } - - // Message received from main thread - onMain(msg) { - } - -} -registerProcessor("AudioThread", AudioThread); diff --git a/web/core/Core.js b/web/core/Core.js deleted file mode 100644 index 98350cd..0000000 --- a/web/core/Core.js +++ /dev/null @@ -1,292 +0,0 @@ -// Interface between application and WebAssembly worker thread -class Core { - - //////////////////////////////// Constants //////////////////////////////// - - /* Register types */ - static VB_PROGRAM = 0; - static VB_SYSTEM = 1; - static VB_OTHER = 2; - - /* System registers */ - static VB_ADTRE = 25; - static VB_CHCW = 24; - static VB_ECR = 4; - static VB_EIPC = 0; - static VB_EIPSW = 1; - static VB_FEPC = 2; - static VB_FEPSW = 3; - static VB_PIR = 6; - static VB_PSW = 5; - static VB_TKCW = 7; - - /* Other registers */ - static VB_PC = 0; - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor() { - - // Configure instance fields - this.promises = []; - - } - - async init(coreUrl, wasmUrl, audioUrl) { - - // Open audio output stream - this.audio = new AudioContext({ - latencyHint: "interactive", - sampleRate : 41700 - }); - await this.audio.suspend(); - - // Launch the audio thread - await this.audio.audioWorklet.addModule( - Core.url(audioUrl, "AudioThread.js", /***/"./AudioThread.js")); - let node = new AudioWorkletNode(this.audio, "AudioThread", { - numberOfInputs : 0, - numberOfOutputs : 1, - outputChannelCount: [2] - }); - node.connect(this.audio.destination); - - // Attach a second MessagePort to the audio thread - let channel = new MessageChannel(); - this.audio.port = channel.port1; - await new Promise(resolve=>{ - node.port.onmessage = resolve; - node.port.postMessage(channel.port2, [channel.port2]); - }); - this.audio.port.onmessage = m=>this.onAudio(m.data); - - // Launch the core thread - this.core = new Worker( - Core.url(wasmUrl, "CoreThread.js", /***/"./CoreThread.js")); - await new Promise(resolve=>{ - this.core.onmessage = resolve; - this.core.postMessage({ - audio : node.port, - wasmUrl: Core.url(wasmUrl, "core.wasm", /***/"./core.wasm") - }, [node.port]); - }); - this.core.onmessage = m=>this.onCore(m.data); - - return this; - } - - - - ///////////////////////////// Static Methods ////////////////////////////// - - // Select a URL in the same path as the current script - static url(arg, name, bundled) { - - // The input argument was provided - if (arg) - return arg; - - // Running from a bundle distribution - if (bundled.startsWith("blob:") || bundled.startsWith("data:")) - return bundled; - - // Compute the URL for the given filename - let url = new URL(import.meta.url).pathname; - return url.substring(0, url.lastIndexOf("/") + 1) + name; - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Message received from audio thread - onAudio(msg) { - } - - // Message received from core thread - onCore(msg) { - - // Process subscriptions - if (msg.subscriptions && this.onsubscription instanceof Function) { - for (let sub of msg.subscriptions) { - let key = sub.subscription; - delete sub.subscription; - this.onsubscription(key, sub, this); - } - delete msg.subscriptions; - } - - // The main thread is waiting on a reply - if (msg.isReply) { - delete msg.isReply; - - // For "create", produce sim objects - if (msg.isCreate) { - delete msg.isCreate; - msg.sims = msg.sims.map(s=>({ pointer: s })); - } - - // Notify the caller - this.promises.shift()(msg); - } - - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Create and initialize simulations - create(count, options) { - return this.message({ - command: "create", - count : count - }, [], options); - } - - // Delete a simulation - delete(sim, options) { - return this.message({ - command: "delete", - sim : sim.pointer - }, [], options); - } - - // Retrieve the value of all CPU registers - getAllRegisters(sim, options) { - return this.message({ - command: "getAllRegisters", - sim : sim.pointer - }, [], options); - } - - // Retrieve the value of a register - getRegister(sim, type, id, options) { - return this.message({ - command: "getRegister", - id : id, - sim : sim.pointer, - type : type - }, [], options); - } - - // Read multiple bytes from memory - read(sim, address, length, options) { - return this.message({ - command: "read", - address: address, - length : length, - sim : sim.pointer - }, [], options); - } - - // Refresh subscriptions - refresh(subscriptions = null, options) { - return this.message({ - command : "refresh", - subscriptions: subscriptions - }, [], options); - } - - // Simulate a hardware reset - reset(sim, options) { - return this.message({ - command: "reset", - sim : sim.pointer - }, [], options); - } - - // Execute until the next current instruction - runToNext(sims, options) { - return this.message({ - command: "runToNext", - sims : Array.isArray(sims) ? - sims.map(s=>s.pointer) : [ sims.pointer ] - }, [], options); - } - - // Specify a value for a register - setRegister(sim, type, id, value, options) { - return this.message({ - command: "setRegister", - id : id, - sim : sim.pointer, - type : type, - value : value - }, [], options); - } - - // Specify a cartridge ROM buffer - setROM(sim, data, options = {}) { - data = data.slice(); - return this.message({ - command: "setROM", - data : data, - reset : !("reset" in options) || !!options.reset, - sim : sim.pointer - }, [data.buffer], options); - } - - // Execute the current instruction - singleStep(sims, options) { - return this.message({ - command: "singleStep", - sims : Array.isArray(sims) ? - sims.map(s=>s.pointer) : [ sims.pointer ] - }, [], options); - } - - // Cancel a subscription - unsubscribe(subscription, options) { - return this.message({ - command : "unsubscribe", - subscription: subscription - }, [], options); - } - - // Write multiple bytes to memory - write(sim, address, data, options) { - data = data.slice(); - return this.message({ - address: address, - command: "write", - data : data, - sim : sim.pointer - }, [data.buffer], options); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Send a message to the core thread - message(msg, transfers, options = {}) { - - // Configure options - if (!(options instanceof Object)) - options = { reply: options }; - if (!("reply" in options) || options.reply) - msg.reply = true; - if ("refresh" in options) - msg.refresh = options.refresh; - if ("subscription" in options) - msg.subscription = options.subscription; - if ("tag" in options) - msg.tag = options.tag; - - // Send the command to the core thread - return msg.reply ? - new Promise(resolve=>{ - this.promises.push(resolve); - this.core.postMessage(msg, transfers); - }) : - this.core.postMessage(msg, transfers); - ; - - } - -} - -export { Core }; diff --git a/web/core/CoreThread.js b/web/core/CoreThread.js deleted file mode 100644 index 7b029b1..0000000 --- a/web/core/CoreThread.js +++ /dev/null @@ -1,338 +0,0 @@ -"use strict"; - -/* Register types */ -const VB_PROGRAM = 0; -const VB_SYSTEM = 1; -const VB_OTHER = 2; - -/* System registers */ -const VB_ADTRE = 25; -const VB_CHCW = 24; -const VB_ECR = 4; -const VB_EIPC = 0; -const VB_EIPSW = 1; -const VB_FEPC = 2; -const VB_FEPSW = 3; -const VB_PIR = 6; -const VB_PSW = 5; -const VB_TKCW = 7; - -/* Other registers */ -const VB_PC = 0; - -// Dedicated emulation thread -class CoreThread { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor() { - - // Configure instance fields - this.subscriptions = new Map(); - - // Wait for initializer message from parent thread - onmessage = m=>this.init(m.data.audio, m.data.wasmUrl); - } - - async init(audio, wasmUrl) { - - // Configure message ports - this.audio = audio; - this.audio.onmessage = m=>this.onAudio (m.data); - this.main = globalThis; - this.main .onmessage = m=>this.onMessage(m.data); - - // Load and instantiate the WebAssembly module - this.wasm = (await WebAssembly.instantiateStreaming( - fetch(wasmUrl), { - env: { emscripten_notify_memory_growth: ()=>this.onGrowth() } - })).instance; - this.onGrowth(); - this.pointerSize = this.PointerSize(); - this.pointerType = this.pointerSize == 8 ? Uint64Array : Uint32Array; - - // Notify main thread - this.main.postMessage(0); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Message received from audio thread - onAudio(frames) { - - // Audio processing was suspended - if (frames == 0) { - return; - } - - // Wait for more frames - this.audio.postMessage(0); - } - - // Emscripten has grown the linear memory - onGrowth() { - Object.assign(this, this.wasm.exports); - } - - // Message received from main thread - onMessage(msg) { - - // Subscribe to the command - if (msg.subscription && msg.command != "refresh") - this.subscriptions.set(CoreThread.key(msg.subscription), msg); - - // Process the command - let rep = this[msg.command](msg); - - // Do not send a reply - if (!msg.reply) - return; - - // Configure the reply - if (!rep) - rep = {}; - if (msg.reply) - rep.isReply = true; - if ("tag" in msg) - rep.tag = msg.tag; - - // Send the reply to the main thread - let transfers = rep.transfers; - if (transfers) - delete rep.transfers; - this.main.postMessage(rep, transfers || []); - - // Refresh subscriptions - if (msg.refresh && msg.command != "refresh") { - let subs = {}; - if (Array.isArray(msg.refresh)) - subs.subscriptions = msg.refresh; - this.refresh(subs); - } - - } - - - - //////////////////////////////// Commands ///////////////////////////////// - - // Create and initialize a new simulation - create(msg) { - let sims = new Array(msg.count); - for (let x = 0; x < msg.count; x++) - sims[x] = this.Create(); - return { - isCreate: true, - sims : sims - }; - } - - // Delete all memory used by a simulation - delete(msg) { - this.Delete(msg.sim); - } - - // Retrieve the values of all CPU registers - getAllRegisters(msg) { - let program = new Int32Array (32); - let system = new Uint32Array(32); - for (let x = 0; x < 32; x++) { - program[x] = this.vbGetRegister(msg.sim, 0, x); - system [x] = this.vbGetRegister(msg.sim, 1, x); - } - return { - pc : this.vbGetRegister(msg.sim, 2, 0) >>> 0, - program : program, - system : system, - transfers: [ program.buffer, system.buffer ] - }; - } - - // Retrieve the value of a register - getRegister(msg) { - let value = this.vbGetRegister(msg.sim, msg.type, msg.id); - if (msg.type != VB_PROGRAM) - value >>>= 0; - return { value: value }; - } - - // Read multiple bytes from memory - read(msg) { - let buffer = this.malloc(msg.length); - this.vbReadEx(msg.sim, msg.address, buffer.pointer, msg.length); - let data = buffer.slice(); - this.free(buffer); - return { - address : msg.address, - data : data, - transfers: [data.buffer] - }; - } - - // Process subscriptions - refresh(msg) { - let subscriptions = []; - let transfers = []; - - // Select the key set to refresh - let keys = Array.isArray(msg.subscriptions) ? - msg.subscriptions.map(s=>CoreThread.key(s)) : - this.subscriptions.keys() - ; - - // Process all subscriptions - for (let key of keys) { - - // Process the subscription - let sub = this.subscriptions.get(key); - let rep = this[sub.command](sub); - - // There is no result - if (!rep) - continue; - - // Add the result to the response - rep.subscription = sub.subscription; - if ("tag" in sub) - rep.tag = sub.tag; - subscriptions.push(rep); - - // Add the transfers to the response - if (!rep.transfers) - continue; - transfers = transfers.concat(rep.transfers); - delete rep.transfers; - } - - // Send the response to the main thread - if (subscriptions.length == 0) - return; - this.main.postMessage({ - subscriptions: subscriptions.sort(CoreThread.REFRESH_ORDER) - }, transfers); - } - - // Simulate a hardware reset - reset(msg) { - this.vbReset(msg.sim); - } - - // Execute until the next current instruction - runToNext(msg) { - let sims = this.malloc(msg.sims.length, true); - for (let x = 0; x < msg.sims.length; x++) - sims[x] = msg.sims[x]; - this.RunToNext(sims.pointer, msg.sims.length); - this.free(sims); - - let pcs = new Array(msg.sims.length); - for (let x = 0; x < msg.sims.length; x++) - pcs[x] = this.vbGetRegister(msg.sims[x], 2, 0) >>> 0; - - return { pcs: pcs }; - } - - // Specify a value for a register - setRegister(msg) { - let value = this.vbSetRegister(msg.sim, msg.type, msg.id, msg.value); - if (msg.type != VB_PROGRAM) - value >>>= 0; - return { value: value }; - } - - // Specify a cartridge ROM buffer - setROM(msg) { - let prev = this.vbGetROM(msg.sim, 0); - let success = true; - - // Specify a new ROM - if (msg.data != null) { - let data = this.malloc(msg.data.length); - for (let x = 0; x < data.length; x++) - data[x] = msg.data[x]; - success = !this.vbSetROM(msg.sim, data.pointer, data.length); - } - - // Operation was successful - if (success) { - - // Delete the previous ROM - this.Free(prev); - - // Reset the simulation - if (msg.reset) - this.vbReset(msg.sim); - } - - return { success: success }; - } - - // Execute the current instruction - singleStep(msg) { - let sims = this.malloc(msg.sims.length, true); - for (let x = 0; x < msg.sims.length; x++) - sims[x] = msg.sims[x]; - this.SingleStep(sims.pointer, msg.sims.length); - this.free(sims); - - let pcs = new Array(msg.sims.length); - for (let x = 0; x < msg.sims.length; x++) - pcs[x] = this.vbGetRegister(msg.sims[x], 2, 0) >>> 0; - - return { pcs: pcs }; - } - - // Delete a subscription - unsubscribe(msg) { - this.subscriptions.delete(CoreThread.key(msg.subscription)); - } - - // Write multiple bytes to memory - write(msg) { - let data = this.malloc(msg.data.length); - for (let x = 0; x < data.length; x++) - data[x] = msg.data[x]; - this.vbWriteEx(msg.sim, msg.address, data.pointer, data.length); - this.free(data); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Delete a byte array in WebAssembly memory - free(buffer) { - this.Free(buffer.pointer); - } - - // Format a subscription key as a string - static key(subscription) { - return subscription.map(k=>k.toString()).join("\n"); - } - - // Allocate a byte array in WebAssembly memory - malloc(length, pointers = false) { - let size = pointers ? length * this.pointerSize : length; - return this.map(this.Malloc(size), length, pointers); - } - - // Map a typed array into WebAssembly memory - map(address, length, pointers = false) { - let ret = new (pointers ? this.pointerType : Uint8Array) - (this.memory.buffer, address, length); - ret.pointer = address; - return ret; - } - - // Comparator for subscriptions within the refresh command - static REFRESH_ORDER(a, b) { - a = a.subscription[0]; - b = b.subscription[0]; - return a < b ? -1 : a > b ? 1 : 0; - } - -} - -new CoreThread(); diff --git a/web/core/Disassembler.js b/web/core/Disassembler.js deleted file mode 100644 index 8149762..0000000 --- a/web/core/Disassembler.js +++ /dev/null @@ -1,546 +0,0 @@ -// Machine code to human readable text converter -class Disassembler { - - //////////////////////////////// Constants //////////////////////////////// - - // Default settings - static DEFAULTS = { - condCL : "L", // Use C/NC or L/NL for conditions - condEZ : "E", // Use E/NE or Z/NZ for conditions - condNames : true, // Use condition names - condUppercase: false, // Condition names uppercase - hexPrefix : "0x", // Hexadecimal prefix - hexSuffix : "", // Hexadecimal suffix - hexUppercase : true, // Hexadecimal uppercase - instUppercase: true, // Mnemonics uppercase - jumpAddress : true, // Jump/branch shows target address - memInside : false, // Use [reg1 + disp] notation - opDestFirst : false, // Destination operand first - proNames : true, // Use program register names - proUppercase : false, // Program register names uppercase - splitBcond : false, // BCOND condition as an operand - splitSetf : true, // SETF condition as an operand - sysNames : true, // Use system register names - sysUppercase : false // System register names uppercase - }; - - - - /////////////////////////// Disassembly Lookup //////////////////////////// - - // Opcode descriptors - static OPDEFS = [ - [ "MOV" , [ "opReg1" , "opReg2" ] ], // 000000 - [ "ADD" , [ "opReg1" , "opReg2" ] ], - [ "SUB" , [ "opReg1" , "opReg2" ] ], - [ "CMP" , [ "opReg1" , "opReg2" ] ], - [ "SHL" , [ "opReg1" , "opReg2" ] ], - [ "SHR" , [ "opReg1" , "opReg2" ] ], - [ "JMP" , [ "opReg1Ind" ] ], - [ "SAR" , [ "opReg1" , "opReg2" ] ], - [ "MUL" , [ "opReg1" , "opReg2" ] ], - [ "DIV" , [ "opReg1" , "opReg2" ] ], - [ "MULU" , [ "opReg1" , "opReg2" ] ], - [ "DIVU" , [ "opReg1" , "opReg2" ] ], - [ "OR" , [ "opReg1" , "opReg2" ] ], - [ "AND" , [ "opReg1" , "opReg2" ] ], - [ "XOR" , [ "opReg1" , "opReg2" ] ], - [ "NOT" , [ "opReg1" , "opReg2" ] ], - [ "MOV" , [ "opImm5S", "opReg2" ] ], // 010000 - [ "ADD" , [ "opImm5S", "opReg2" ] ], - null, // SETF: special - [ "CMP" , [ "opImm5S", "opReg2" ] ], - [ "SHL" , [ "opImm5U", "opReg2" ] ], - [ "SHR" , [ "opImm5U", "opReg2" ] ], - [ "CLI" , [ ] ], - [ "SAR" , [ "opImm5U", "opReg2" ] ], - [ "TRAP" , [ "opImm5U" ] ], - [ "RETI" , [ ] ], - [ "HALT" , [ ] ], - null, // Invalid - [ "LDSR" , [ "opReg2" , "opSys" ] ], - [ "STSR" , [ "opSys" , "opReg2" ] ], - [ "SEI" , [ ] ], - null, // Bit string: special - null, // BCOND: special // 100000 - null, // BCOND: special - null, // BCOND: special - null, // BCOND: special - null, // BCOND: special - null, // BCOND: special - null, // BCOND: special - null, // BCOND: special - [ "MOVEA", [ "opImm16U" , "opReg1", "opReg2" ] ], - [ "ADDI" , [ "opImm16S" , "opReg1", "opReg2" ] ], - [ "JR" , [ "opDisp26" ] ], - [ "JAL" , [ "opDisp26" ] ], - [ "ORI" , [ "opImm16U" , "opReg1", "opReg2" ] ], - [ "ANDI" , [ "opImm16U" , "opReg1", "opReg2" ] ], - [ "XORI" , [ "opImm16U" , "opReg1", "opReg2" ] ], - [ "MOVHI", [ "opImm16U" , "opReg1", "opReg2" ] ], - [ "LD.B" , [ "opReg1Disp", "opReg2" ] ], // 110000 - [ "LD.H" , [ "opReg1Disp", "opReg2" ] ], - null, // Invalid - [ "LD.W" , [ "opReg1Disp", "opReg2" ] ], - [ "ST.B" , [ "opReg2" , "opReg1Disp" ] ], - [ "ST.H" , [ "opReg2" , "opReg1Disp" ] ], - null, // Invalid - [ "ST.W" , [ "opReg2" , "opReg1Disp" ] ], - [ "IN.B" , [ "opReg1Disp", "opReg2" ] ], - [ "IN.H" , [ "opReg1Disp", "opReg2" ] ], - [ "CAXI" , [ "opReg1Disp", "opReg2" ] ], - [ "IN.W" , [ "opReg1Disp", "opReg2" ] ], - [ "OUT.B", [ "opReg2" , "opReg1Disp" ] ], - [ "OUT.H", [ "opReg2" , "opReg1Disp" ] ], - null, // Floating-point/Nintendo: special - [ "OUT.W", [ "opReg2" , "opReg1Disp" ] ] - ]; - - // Bit string sub-opcode descriptors - static BITSTRING = [ - "SCH0BSU", "SCH0BSD", "SCH1BSU", "SCH1BSD", - null , null , null , null , - "ORBSU" , "ANDBSU" , "XORBSU" , "MOVBSU" , - "ORNBSU" , "ANDNBSU", "XORNBSU", "NOTBSU" , - null , null , null , null , - null , null , null , null , - null , null , null , null , - null , null , null , null - ]; - - // Floating-point/Nintendo sub-opcode descriptors - static FLOATENDO = [ - [ "CMPF.S" , [ "opReg1", "opReg2" ] ], - null, // Invalid - [ "CVT.WS" , [ "opReg1", "opReg2" ] ], - [ "CVT.SW" , [ "opReg1", "opReg2" ] ], - [ "ADDF.S" , [ "opReg1", "opReg2" ] ], - [ "SUBF.S" , [ "opReg1", "opReg2" ] ], - [ "MULF.S" , [ "opReg1", "opReg2" ] ], - [ "DIVF.S" , [ "opReg1", "opReg2" ] ], - [ "XB" , [ "opReg2" ] ], - [ "XH" , [ "opReg2" ] ], - [ "REV" , [ "opReg1", "opReg2" ] ], - [ "TRNC.SW", [ "opReg1", "opReg2" ] ], - [ "MPYHW" , [ "opReg1", "opReg2" ] ], - null, null, null, - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null - ]; - - // Condition mnemonics - static CONDITIONS = [ - "V" , "C" , "E" , "NH", "N", "T", "LT", "LE", - "NV", "NC", "NE", "H" , "P", "F", "GE", "GT" - ]; - - // Program register names - static REG_PROGRAM = [ - "r0" , "r1" , "hp" , "sp" , "gp" , "tp" , "r6" , "r7" , - "r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15", - "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", - "r24", "r25", "r26", "r27", "r28", "r29", "r30", "lp" - ]; - - // System register names - static REG_SYSTEM = [ - "EIPC", "EIPSW", "FEPC", "FEPSW", "ECR", "PSW", "PIR", "TKCW", - "8" , "9" , "10" , "11" , "12" , "13" , "14" , "15" , - "16" , "17" , "18" , "19" , "20" , "21" , "22" , "23" , - "CHCW", "ADTRE", "26" , "27" , "28" , "29" , "30" , "31" - ]; - - // Other register names - static REG_OTHER = [ "PC", "PSW" ]; - - - - ///////////////////////////// Static Methods ////////////////////////////// - - // Determine the bounds of a data buffer to represent all lines of output - static dataBounds(address, line, length) { - let before = 10; // Number of lines before the first line of output - let max = 4; // Maximum number of bytes that can appear on a line - - // The reference line is before the preferred earliest line - if (line < -before) { - length = (length - line) * max; - } - - // The reference line is before the first line - else if (line < 0) { - address -= (line + before) * max; - length = (length + before) * max; - } - - // The reference line is at or after the first line - else { - address -= (line + before) * max; - length = (Math.max(length, line) + before) * max; - } - - return { - address: (address & ~1) >>> 0, - length : length - }; - } - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor() { - Object.assign(this, Disassembler.DEFAULTS); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Disassemble a region of memory - disassemble(data, dataAddress, refAddress, refLine, length, pc = null) { - let pcOffset = pc === null ? -1 : pc - dataAddress >>> 0; - - // Locate the offset of the first line of output in the buffer - let offset = 0; - for (let - addr = dataAddress, - circle = refLine > 0 ? new Array(refLine) : null, - index = 0, - more = [], - remain = null - ;;) { - - // Determine the size of the current line - if (more.length == 0) - this.more(more, data, offset); - let size = more.shift(); - - // The current line contains the reference address - if (refAddress - addr >>> 0 < size) { - - // The next item in the buffer is the first line of output - if (refLine > 0) { - offset = circle[index]; - break; - } - - // This line is the first line of output - if (refLine == 0) - break; - - // Count more lines for the first line of output - remain = refLine; - } - - // Record the offset of the current instruction - if (refLine > 0) { - circle[index] = offset; - index = (index + 1) % circle.length; - } - - // Advance to the next line - let sizeToPC = pcOffset - offset >>> 0; - if (offset != pcOffset && sizeToPC < size) { - size = sizeToPC; - more.splice(); - } - addr = addr + size >>> 0; - offset += size; - if (remain !== null && ++remain == 0) - break; // The next line is the first line of output - } - - // Process all lines of output - let lines = new Array(length); - for (let - addr = dataAddress + offset, - more = [], - x = 0; - x < length; x++ - ) { - - // Determine the size of the current line - if (more.length == 0) - this.more(more, data, offset, pcOffset); - let size = more.shift(); - - // Add the line to the response - lines[x] = this.format({ - rawAddress: addr, - rawBytes : data.slice(offset, offset + size) - }); - - // Advance to the next line - let sizeToPC = pcOffset - offset >>> 0; - if (offset != pcOffset && sizeToPC < size) { - size = sizeToPC; - more.splice(); - } - addr = addr + size >>> 0; - offset += size; - } - - return lines; - } - - - - /////////////////////////// Formatting Methods //////////////////////////// - - // Format a line as human-readable text - format(line) { - let canReverse = true; - let opcode = line.rawBytes[1] >>> 2; - let opdef; - let code = [ - line.rawBytes[1] << 8 | line.rawBytes[0], - line.rawBytes.length == 2 ? null : - line.rawBytes[3] << 8 | line.rawBytes[2] - ]; - - // BCOND - if ((opcode & 0b111000) == 0b100000) { - let cond = code[0] >>> 9 & 15; - opdef = - cond == 13 ? [ "NOP", [ ] ] : - this.splitBcond ? [ "BCOND", [ "opBCond", "opDisp9" ] ] : - [ - cond == 5 ? "BR" : "B" + this.condition(cond, true), - [ "opDisp9" ] - ] - ; - canReverse = false; - } - - // Processing by opcode - else switch (opcode) { - - // SETF - case 0b010010: - opdef = !this.splitSetf ? - [ - "SETF" + Disassembler.CONDITIONS[code[0] & 15], - [ "opReg2" ] - ] : - [ "SETF", [ "opCond", "opReg2" ] ] - ; - break; - - // Bit string - case 0b011111: - opdef = Disassembler.BITSTRING[code[0] & 31]; - if (opdef != null) - opdef = [ opdef, [] ]; - break; - - // Floating-point/Nintendo - case 0b111110: - opdef = Disassembler.FLOATENDO[code[1] >>> 10]; - break; - - // All others - default: opdef = Disassembler.OPDEFS[opcode]; - } - - // The opcode is undefined - if (opdef == null) - opdef = [ "---", [] ]; - - // Format the line's display text - line.address = this.hex(line.rawAddress, 8, false); - line.bytes = new Array(line.rawBytes.length); - line.mnemonic = this.instUppercase ? opdef[0] : opdef[0].toLowerCase(); - line.operands = new Array(opdef[1].length); - for (let x = 0; x < line.bytes.length; x++) - line.bytes[x] = this.hex(line.rawBytes[x], 2, false); - for (let x = 0; x < line.operands.length; x++) - line.operands[x] = this[opdef[1][x]](line, code); - if (this.opDestFirst && canReverse) - line.operands.reverse(); - - return line; - } - - // Format a condition operand in a BCOND instruction - opBCond(line, code) { - return this.condition(code[0] >>> 9 & 15); - } - - // Format a condition operand in a SETF instruction - opCond(line, code) { - return this.condition(code[0] & 15); - } - - // Format a 9-bit displacement operand - opDisp9(line, code) { - let disp = code[0] << 23 >> 23; - return this.jump(line.rawAddress, disp); - } - - // Format a 26-bit displacement operand - opDisp26(line, code) { - let disp = (code[0] << 16 | code[1]) << 6 >> 6; - return this.jump(line.rawAddress, disp); - } - - // Format a 5-bit signed immediate operand - opImm5S(line, code) { - return (code[0] & 31) << 27 >> 27; - } - - // Format a 5-bit unsigned immediate operand - opImm5U(line, code) { - return code[0] & 31; - } - - // Format a 16-bit signed immediate operand - opImm16S(line, code) { - let ret = code[1] << 16 >> 16; - return ( - ret < -256 ? "-" + this.hex(-ret) : - ret > 256 ? this.hex( ret) : - ret - ); - } - - // Format a 16-bit unsigned immediate operand - opImm16U(line, code) { - return this.hex(code[1], 4); - } - - // Format a Reg1 operand - opReg1(line, code) { - return this.programRegister(code[0] & 31); - } - - // Format a disp[reg1] operand - opReg1Disp(line, code) { - let disp = code[1] << 16 >> 16; - let reg1 = this.programRegister(code[0] & 31); - - // Do not print the displacement - if (disp == 0) - return "[" + reg1 + "]"; - - // Format the displacement amount - disp = - disp < -256 ? "-" + this.hex(-disp) : - disp > 256 ? this.hex( disp) : - disp.toString() - ; - - // [reg1 + disp] notation - if (this.memInside) { - return "[" + reg1 + (disp.startsWith("-") ? - " - " + disp.substring(1) : - " + " + disp - ) + "]"; - } - - // disp[reg1] notation - return disp + "[" + reg1 + "]"; - } - - // Format a [Reg1] operand - opReg1Ind(line, code) { - return "[" + this.programRegister(code[0] & 31) + "]"; - } - - // Format a Reg2 operand - opReg2(line, code) { - return this.programRegister(code[0] >> 5 & 31); - } - - // Format a system register operand - opSys(line, code) { - return this.systemRegister(code[0] & 31); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Select the mnemonic for a condition - condition(index, forceUppercase = false) { - if (!this.condNames) - return index.toString(); - let ret = - index == 1 ? this.condCL : - index == 2 ? this.condEZ : - index == 9 ? "N" + this.condCL : - index == 10 ? "N" + this.condEZ : - Disassembler.CONDITIONS[index] - ; - if (!forceUppercase && !this.condUppercase) - ret = ret.toLowerCase(); - return ret; - } - - // Format a number as a hexadecimal string - hex(value, digits = null, decorated = true) { - value = value.toString(16); - if (this.hexUppercase) - value = value.toUpperCase(); - if (digits != null) - value = value.padStart(digits, "0"); - if (decorated) { - value = this.hexPrefix + value + this.hexSuffix; - if (this.hexPrefix == "" && "0123456789".indexOf(value[0]) == -1) - value = "0" + value; - } - return value; - } - - // Format a jump or branch destination - jump(address, disp) { - return ( - this.jumpAddress ? - this.hex(address + disp >>> 0, 8, false) : - disp < -256 ? "-" + this.hex(-disp) : - disp > 256 ? "+" + this.hex( disp) : - disp.toString() - ); - } - - // Determine the number of bytes in the next line(s) of disassembly - more(more, data, offset) { - - // Error checking - if (offset + 1 >= data.length) - throw new Error("Disassembly error: Unexpected EoF"); - - // Determine the instruction's size from its opcode - let opcode = data[offset + 1] >>> 2; - more.push( - opcode < 0b101000 || // 16-bit instruction - opcode == 0b110010 || // Illegal opcode - opcode == 0b110110 // Illegal opcode - ? 2 : 4); - } - - // Format a program register - programRegister(index) { - let ret = this.proNames ? - Disassembler.REG_PROGRAM[index] : "r" + index; - if (this.proUppercase) - ret = ret.toUpperCase(); - return ret; - } - - // Format a system register - systemRegister(index) { - let ret = this.sysNames ? - Disassembler.REG_SYSTEM[index] : index.toString(); - if (!this.sysUppercase && this.sysNames) - ret = ret.toLowerCase(); - return ret; - } - -} - -export { Disassembler }; diff --git a/web/core/wasm.c b/web/core/wasm.c deleted file mode 100644 index 01dc80c..0000000 --- a/web/core/wasm.c +++ /dev/null @@ -1,79 +0,0 @@ -#undef VBAPI -#include -#include -#include - - - -/////////////////////////////// Module Commands /////////////////////////////// - -// Create and initialize a new simulation -EMSCRIPTEN_KEEPALIVE VB* Create() { - VB *sim = malloc(sizeof (VB)); - vbInit(sim); - return sim; -} - -// Delete all memory used by a simulation -EMSCRIPTEN_KEEPALIVE void Delete(VB *sim) { - free(sim->cart.ram); - free(sim->cart.rom); - free(sim); -} - -// Proxy for free() -EMSCRIPTEN_KEEPALIVE void Free(void *ptr) { - free(ptr); -} - -// Proxy for malloc() -EMSCRIPTEN_KEEPALIVE void* Malloc(int size) { - return malloc(size); -} - -// Size in bytes of a pointer -EMSCRIPTEN_KEEPALIVE int PointerSize() { - return sizeof (void *); -} - - - -////////////////////////////// Debugger Commands ////////////////////////////// - -// Execute until the following instruction -static uint32_t RunToNextAddress; -static int RunToNextFetch(VB *sim, int fetch, VBAccess *access) { - return access->address == RunToNextAddress; -} -static int RunToNextExecute(VB *sim, VBInstruction *inst) { - RunToNextAddress = inst->address + inst->size; - vbSetCallback(sim, VB_ONEXECUTE, NULL); - vbSetCallback(sim, VB_ONFETCH, &RunToNextFetch); - return 0; -} -EMSCRIPTEN_KEEPALIVE void RunToNext(VB **sims, int count) { - uint32_t clocks = 20000000; // 1s - vbSetCallback(sims[0], VB_ONEXECUTE, &RunToNextExecute); - vbEmulateEx (sims, count, &clocks); - vbSetCallback(sims[0], VB_ONEXECUTE, NULL); - vbSetCallback(sims[0], VB_ONFETCH , NULL); -} - -// Execute the current instruction -static int SingleStepBreak; -static int SingleStepFetch(VB *sim, int fetch, VBAccess *access) { - if (fetch != 0) - return 0; - if (SingleStepBreak == 1) - return 1; - SingleStepBreak = 1; - return 0; -} -EMSCRIPTEN_KEEPALIVE void SingleStep(VB **sims, int count) { - uint32_t clocks = 20000000; // 1s - SingleStepBreak = sims[0]->cpu.stage == 0 ? 0 : 1; - vbSetCallback(sims[0], VB_ONFETCH, &SingleStepFetch); - vbEmulateEx (sims, count, &clocks); - vbSetCallback(sims[0], VB_ONEXECUTE, NULL); - vbSetCallback(sims[0], VB_ONFETCH , NULL); -} diff --git a/web/debugger/CPU.js b/web/debugger/CPU.js deleted file mode 100644 index a51ea0e..0000000 --- a/web/debugger/CPU.js +++ /dev/null @@ -1,1439 +0,0 @@ -import { Core } from /**/"../core/Core.js"; -import { Disassembler } from /**/"../core/Disassembler.js"; -import { Toolkit } from /**/"../toolkit/Toolkit.js"; -let register = Debugger => { - -// CPU disassembler and register state window -class CPU extends Toolkit.Window { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(debug, index) { - super(debug.app, { - class: "tk window cpu" - }); - - // Configure instance fields - this.debug = debug; - this.height = 300; - this.index = index; - this.shown = false; - this.width = 400; - - // Window - this.setTitle("{debug.cpu._}", true); - this.substitute("#", " " + (index + 1)); - if (index == 1) - this.element.classList.add("two"); - this.addEventListener("close" , e=>this.visible = false); - this.addEventListener("visibility", e=>this.onVisibility(e)); - - // Client area - Object.assign(this.client.style, { - display : "grid", - gridAutoRows : "100%", - gridTemplateColumns: "auto" - }); - - // Disassembler - this.disassembler = new DisassemblerPane(this); - this.lastFocus = this.disassembler.view; - - // Register lists - this.registers = new RegisterPane(this); - - // Main content area - this.add(new Toolkit.SplitPane(debug.app, { - orientation: "right", - primary : this.registers, - secondary : this.disassembler - })); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Window key press - onKeyDown(e) { - super.onKeyDown(e); - - // Error checking - if (e.altKey || e.shiftKey) - return; - - // Processing by key: CTRL down - if (e.ctrlKey) switch (e.key) { - case "b": case "B": - this.disassembler.bytesColumn = !this.disassembler.bytesColumn; - break; - case "f": case "F": - this.disassembler.fitColumns(); - break; - case "g": case "G": - this.disassembler.goto(); - break; - default: return; - } - - // Processing by key: CTRL up - else switch (e.key) { - case "F10": - this.debug.app.runToNext(this.index, { refresh: true }); - break; - case "F11": - this.debug.app.singleStep(this.index, { refresh: true }); - break; - default: return; - } - - Toolkit.handle(e); - } - - // Window visibility - onVisibility(e) { - - // Configure instance fields - this.shown = this.shown || e.visible; - - // Configure subscriptions - if (!e.visible) { - if (this.registers) - this.registers .unsubscribe(); - if (this.disassembler) - this.disassembler.unsubscribe(); - } else { - this.registers .fetch(); - this.disassembler.fetch(); - } - - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Disassembler configuration has changed - dasmConfigured() { - - // Disassembler - this.disassembler.fetch(); - - // Registers - for (let reg of this.registers.list) { - reg.chkExpand.element.style.removeProperty("min-width"); - reg.dasmConfigured(); - } - this.registers.regResize(); - } - - // Window is being shown for the first time - firstShow() { - this.disassembler.firstShow(); - this.registers .firstShow(); - } - -} - - - -/////////////////////////////////////////////////////////////////////////////// - - - -class DisassemblerPane extends Toolkit.ScrollPane { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(cpu) { - super(cpu.debug.app, { - class : "tk scroll-pane scr-dasm", - overflowX: "auto", - overflowY: "hidden" - }); - - // Configure instance fields - this._bytesColumn = true; - this.columnWidths = new Array(6); - this.cpu = cpu; - this.delta = 0; - this.dasm = null; - this.lines = []; - this.pending = false; - this.subscription = [ 1, cpu.index, "cpu", "disassembler", "refresh" ], - this.viewAddress = 0xFFFFFFF0; - this.viewLine = 10; - - // Initialize column widths - for (let x = 0; x < this.columnWidths.length; x++) - this.columnWidths[x] = 0; - - // Client area - let view = this.view = new Toolkit.Component(cpu.debug.app, { - class : "tk mono disassembler", - role : "application", - tabIndex: "0", - style : { - display : "grid", - height : "100%", - minWidth: "100%", - overflow: "hidden", - position: "relative", - width : "max-content" - } - }); - view.setLabel("{debug.cpu.disassembler}", true); - view.setRoleDescription("{debug.cpu.disassembler}", true); - view.addEventListener("keydown", e=>this.viewKeyDown(e)); - view.addEventListener("resize" , e=>this.viewResize ( )); - view.addEventListener("wheel" , e=>this.viewWheel (e)); - - // Label for measuring text dimensions in the disassembler - this.pc = new Toolkit.Label(cpu.debug.app, { - class : "tk label mono pc", - visible : false, - visibility: true, - style : { - position: "absolute" - } - }); - this.pc.setText("\u00a0", false); //   - view.append(this.pc); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Column resized - colResize() { - if (this.lines.length == 0) - return; - for (let x = 0; x < this.columnWidths.length; x++) { - let elm = this.lines[0].all[x]; - let width = elm.getBoundingClientRect().width; - if (width <= this.columnWidths[x]) - continue; - this.columnWidths[x] = width; - elm.style.minWidth = width + "px"; - } - } - - // Key press - viewKeyDown(e) { - - // Error checking - if (e.altKey || e.ctrlKey || e.shiftKey) - return; - - // Processing by key - switch (e.key) { - case "ArrowDown": - this.fetch(-1); - break; - case "ArrowLeft": - this.scrollLeft -= this.hscroll.unitIncrement; - break; - case "ArrowRight": - this.scrollLeft += this.hscroll.unitIncrement; - break; - case "ArrowUp": - this.fetch(+1); - break; - case "PageDown": - this.fetch(-this.tall(true)); - break; - case "PageUp": - this.fetch(+this.tall(true)); - break; - default: return; - } - - Toolkit.handle(e); - } - - // Resize - viewResize() { - - // Error checking - if (!this.pc) - return; - - // Working variables - let tall = this.tall(false); - let grew = this.lines.length < tall; - - // Process all new lines - for (let y = this.lines.length; y < tall; y++) { - let resizer = y != 0 ? null : - new ResizeObserver(()=>this.colResize()); - - let line = { - lblAddress : document.createElement("div"), - lblBytes : new Array(4), - lblMnemonic: document.createElement("div"), - lblOperands: document.createElement("div") - }; - - // Address label - line.lblAddress.className = "address"; - if (y == 0) - resizer.observe(line.lblAddress); - this.view.append(line.lblAddress); - - // Byte labels - for (let x = 0; x < line.lblBytes.length; x++) { - let lbl = line.lblBytes[x] = document.createElement("div"); - lbl.className = "byte" + (x == 0 ? " b0" : ""); - if (y == 0) { - lbl.style.minWidth = "0px"; - resizer.observe(lbl); - } - this.view.append(lbl); - } - - // Mnemonic label - line.lblMnemonic.className = "mnemonic"; - if (y == 0) - resizer.observe(line.lblMnemonic); - this.view.append(line.lblMnemonic); - - // Operand label - line.lblOperands.className = "operands"; - this.view.append(line.lblOperands); - - // All elements - line.all = line.lblBytes.concat([ - line.lblAddress, - line.lblMnemonic, - line.lblOperands - ]); - - this.lines.push(line); - } - - // Remove lines that are no longer visible - while (tall < this.lines.length) { - let line = this.lines[tall]; - line.lblAddress .remove(); - line.lblMnemonic.remove(); - line.lblOperands.remove(); - for (let lbl of line.lblBytes) - lbl.remove(); - this.lines.splice(tall, 1); - } - - // Configure elements - let lineHeight = this.pc.element.getBoundingClientRect().height; - this.hscroll.unitIncrement = lineHeight; - this.view.element.style.gridAutoRows = lineHeight + "px"; - - // Update components - if (grew) - this.fetch(); - else this.refresh(); - } - - // Mouse wheel - viewWheel(e) { - - // Error checking - if (e.altKey || e.ctrlKey || e.shiftKey) - return; - - // Always handle the event - Toolkit.handle(e); - - // Determine how many full lines were scrolled - let scr = Debugger.linesScrolled(e, - this.pc.element.getBoundingClientRect().height, - this.tall(true), - this.delta - ); - this.delta = scr.delta; - scr.lines = Math.max(-3, Math.min(3, scr.lines)); - - // No lines were scrolled - if (scr.lines == 0) - return; - - // Scroll the view - this.fetch(-scr.lines); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Display the bytes column in the disassembler - get bytesColumn() { return this._bytesColumn; } - set bytesColumn(show) { - show = !!show; - if (show == this._bytesColumn) - return; - this._bytesColumn = show; - this.refresh(); - } - - // Retrieve a disassembly from the simulation state - async fetch(viewScroll = 0) { - - // Select the parameters for the simulation fetch - let params = { - viewAddress: this.viewAddress, - viewLine : this.viewLine + viewScroll, - viewLength : this.tall(false) + 20, - viewScroll : viewScroll - }; - if (this.pending instanceof Object) { - params.viewLine += this.pending.viewScroll; - params.viewScroll += this.pending.viewScroll; - } - let bounds = Disassembler.dataBounds( - params.viewAddress, params.viewLine, params.viewLength); - params.dataAddress = bounds.address; - params.dataLength = bounds.length; - - // A communication with the core thread is already underway - if (this.pending) { - this.pending = params; - this.refresh(); - return; - } - - // Retrieve data from the simulation state - this.pending = params; - for (let data=null, promise=null; this.pending instanceof Object;) { - - // Wait for a transaction to complete - if (promise != null) { - this.pending = true; - data = await promise; - promise = null; - } - - // Initiate a new transaction - if (this.pending instanceof Object) { - params = this.pending; - let options = { tag: params }; - if (this.cpu.isVisible()) - options.subscription = this.subscription; - promise = this.cpu.debug.core.read(this.cpu.debug.sim, - params.dataAddress, params.dataLength, options); - } - - // Process the result of a transaction - if (data != null) { - this.refresh(data); - data = null; - } - - }; - this.pending = false; - } - - // Component is being displayed for the first time - firstShow() { - this.viewLine = Math.floor(this.tall(true) / 3) + 10; - this.viewResize(); - } - - // Shrink all columns to fit the current view - fitColumns() { - if (this.lines.length == 0) - return; - for (let elm of this.lines[0].all) - elm.style.removeProperty("min-width"); - for (let x = 0; x < this.columnWidths.length; x++) - this.columnWidths[x] = 0; - this.refresh(); - this.colResize(); - } - - // Ensure PC is visible in the view - followPC(pc) { - let tall = this.tall(true); - let count = !this.dasm ? 0 : Math.min(this.dasm.length - 10, tall); - - // Determine whether PC already is visible - for (let x = 0; x < count; x++) { - let line = this.dasm[x + 10]; - if (pc - line.rawAddress >>> 0 < line.bytes.length) - return; // PC is already visible - } - - // Request a new view containing PC - this.viewAddress = pc; - this.viewLine = Math.floor(tall / 3) + 10; - if (this.cpu.isVisible()) - this.fetch(); - } - - // Prompt the user to navigate to a new editing address - goto() { - - // Retrieve the value from the user - let addr = prompt(this.app.localize("{debug.cpu.goto}")); - if (addr === null) - return; - addr = parseInt(addr.trim(), 16); - if ( - !Number.isInteger(addr) || - addr < 0 || - addr > 4294967295 - ) return; - - // Navigate to the given address - this.viewAddress = addr; - this.viewLine = Math.floor(this.tall(true) / 3) + 10; - this.fetch(); - } - - // Update localization strings - localize() { - this.localizeRoleDescription(); - this.localizeLabel(); - } - - // Update disassembler - refresh(msg = null) { - let tall = this.tall(false); - - // Receiving data from the simulation state - if (msg != null) { - - // Disassemble the retrieved data - this.dasm = this.cpu.debug.dasm.disassemble( - msg.data, msg.address, - msg.tag.viewAddress, msg.tag.viewLine, msg.tag.viewLength, - this.cpu.registers.pc - ); - - // Configure the view - this.viewAddress = this.dasm[10].rawAddress; - this.viewLine = 10; - if (this.pending instanceof Object) - this.viewLine += this.pending.viewScroll; - } - - // Determine an initial number of visible byte columns - let showBytes = 0; - if (this.bytesColumn) { - for (let x = 0; x < 4 && - this.columnWidths[x] != 0; x++, showBytes++); - } - - // Working variables - let foundPC = false; - let lineHeight = this.pc.element.getBoundingClientRect().height; - - // Process all lines - let index = 20 - this.viewLine; - for (let y = 0; y < this.lines.length; y++, index++) { - let line = this.lines[y]; - let isPC = false; - - // There is no data for this line - if (index < 0 || this.dasm == null || index >= this.dasm.length) { - line.lblAddress .innerText = "--------"; - line.lblBytes[0].innerText = "--"; - line.lblMnemonic.innerText = "---"; - line.lblOperands.innerText = ""; - for (let x = 1; x < line.lblBytes.length; x++) - line.lblBytes[x].innerText = ""; - if (this.bytesColumn) - showBytes = Math.max(showBytes, 1); - } - - // Present the disassembled line - else { - let dasm = this.dasm[index]; - line.lblAddress .innerText = dasm.address; - line.lblMnemonic.innerText = dasm.mnemonic; - line.lblOperands.innerText = dasm.operands.join(", "); - for (let x = 0; x < line.lblBytes.length; x++) - line.lblBytes[x].innerText = dasm.bytes[x] || ""; - isPC = this.cpu.registers.pc == dasm.rawAddress; - if (this.bytesColumn) - showBytes = Math.max(showBytes, dasm.bytes.length); - } - - // Configure whether PC is on this line - if (isPC) { - foundPC = true; - this.pc.element.style.top = lineHeight * y + "px"; - for (let elm of line.all) - elm.classList.add("is-pc"); - } else { - for (let elm of line.all) - elm.classList.remove("is-pc"); - } - - } - - // Show or hide the PC background - this.pc.visible = foundPC; - - // Configure which byte columns are visible - for (let line of this.lines) { - for (let x = 0; x < line.lblBytes.length; x++) { - line.lblBytes[x].style - [x < showBytes ? "removeProperty" : "setProperty"] - ("display", "none") - ; - } - } - - // Configure layout - this.view.element.style.gridTemplateColumns = - "repeat(" + (showBytes + 3) + ", max-content)"; - } - - // Stop receiving updates from the simulation - unsubscribe() { - this.cpu.debug.core.unsubscribe(this.subscription, false); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Measure the number of lines visible in the view - tall(fully) { - return Math.max(1, Math[fully ? "floor" : "ceil"]( - (fully ? this.view : this).element - .getBoundingClientRect().height / - this.pc.element.getBoundingClientRect().height - )); - } - -} - - - -/////////////////////////////////////////////////////////////////////////////// - - - -// Entry in the register lists -class Register { - - //////////////////////////////// Constants //////////////////////////////// - - // Register types - static PLAIN = 0; - static PROGRAM = 1; - static CHCW = 2; - static ECR = 3; - static PSW = 4; - static PIR = 5; - static TKCW = 6; - - // Program register formats - static HEX = 0; - static SIGNED = 1; - static UNSIGNED = 2; - static FLOAT = 3; - - // Expansion controls by register type - static FIELDS = { - [this.CHCW]: [ - [ "check", "ICE", 1 ] - ], - [this.ECR]: [ - [ "texth", "FECC", 16, 16 ], - [ "texth", "EICC", 0, 16 ] - ], - [this.PIR]: [ - [ "texth", "PT", 0, 16 ] - ], - [this.PSW]: [ - [ "check", "CY" , 3 ], [ "check", "FRO", 9 ], - [ "check", "OV" , 2 ], [ "check", "FIV", 8 ], - [ "check", "S" , 1 ], [ "check", "FZD", 7 ], - [ "check", "Z" , 0 ], [ "check", "FOV", 6 ], - [ "check", "NP" , 15 ], [ "check", "FUD", 5 ], - [ "check", "EP" , 14 ], [ "check", "FPR", 4 ], - [ "check", "ID" , 12 ], [ "textd", "I" , 16, 4 ], - [ "check", "AE" , 13 ] - ], - [this.TKCW]: [ - [ "check", "FIT", 7 ], [ "check", "FUT", 4 ], - [ "check", "FZT", 6 ], [ "check", "FPT", 3 ], - [ "check", "FVT", 5 ], [ "check", "OTM", 8 ], - [ "check", "RDI", 2 ], [ "textd", "RD" , 0, 2 ] - ] - }; - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(registers, key, type, apiType, apiId) { - let app = registers.cpu.debug.app; - - // Configure instance fields - this.apiId = apiId; - this.apiType = apiType; - this.controls = []; - this.dasm = registers.cpu.debug.app.dasm; - this.debug = registers.cpu.debug; - this.expansion = null; - this.format = Register.HEX; - this.key = key; - this.registers = registers; - this.type = type; - - // Resolve the target object - switch (apiType) { - case Core.VB_PROGRAM: this.target = registers.program; break; - case Core.VB_SYSTEM : this.target = registers.system ; break; - case Core.VB_OTHER : this.target = registers ; break; - } - - // Main controls - this.main = new Toolkit.Component(app, { - class : "main", - visibility: true, - style : { - alignItems : "center", - display : "grid", - gridTemplateColumns: "max-content auto" - } - }); - - // Expand/collapse check box - this.chkExpand = new Toolkit.Checkbox(app, { - class : "tk expand", - disabled: true, - instant : true, - role : "" - }); - this.main.add(this.chkExpand); - - // Value text box - this.txtValue = new Toolkit.TextBox(app, { - class : "tk text-box mono", - spellcheck: "false", - size : "1", - value : "00000000" - }); - this.txtValue.setLabel(this.label); - this.txtValue.addEventListener("action" , e=>this.valAction (e)); - this.txtValue.addEventListener("keydown", e=>this.valKeyDown(e)); - this.main.add(this.txtValue); - - // Expansion area - if (type == Register.PROGRAM) - this.initProgram(app); - else this.initSystem(app, Register.FIELDS[type]); - if (this.expansion != null) { - - // Expand/collapse check box - this.chkExpand.addEventListener("input", e=>this.chkInput(e)); - this.chkExpand.disabled = false; - this.chkExpand.element.setAttribute("role", "checkbox"); - this.chkExpand.element.setAttribute("tabindex", "0"); - this.chkExpand.element - .setAttribute("aria-controls", this.expansion.element.id); - - // Expansion area - this.expansion.visible = false; - } - - // Update controls - this.dasmConfigured(); - this.refresh(); - - // PSW is initially expanded - if (apiType == Core.VB_SYSTEM && apiId == Core.VB_PSW) - this.expanded = true; - - // System registers after PSW are initially hidden - else if (apiType == Core.VB_SYSTEM) - this.visible = false; - } - - // Expansion controls for program registers - initProgram(app) { - - // Expansion area - let exp = this.expansion = new Toolkit.Component(app, { - class: "expansion", - id : Toolkit.id(), - style: { - display : "inline-grid", - gridTemplateColumns: "max-content", - } - }); - exp.localize = ()=>exp.localizeLabel(); - exp.setLabel("{debug.cpu.format}", true); - - // Radio group - let group = new Toolkit.RadioGroup(app); - - // Hex radio button - let opt = new Toolkit.Radio(app, { - checked: true, - group : group - }); - opt.setText("{debug.cpu.hex}", true); - opt.addEventListener("input", e=>this.onProgram(Register.HEX)); - exp.add(opt); - - // Signed radio button - opt = new Toolkit.Radio(app, { group: group }); - opt.setText("{debug.cpu.signed}", true); - opt.addEventListener("input", e=>this.onProgram(Register.SIGNED)); - exp.add(opt); - - // Unsigned radio button - opt = new Toolkit.Radio(app, { group: group }); - opt.setText("{debug.cpu.unsigned}", true); - opt.addEventListener("input", e=>this.onProgram(Register.UNSIGNED)); - exp.add(opt); - - // Float radio button - opt = new Toolkit.Radio(app, { group: group }); - opt.setText("{debug.cpu.float}", true); - opt.addEventListener("input", e=>this.onProgram(Register.FLOAT)); - exp.add(opt); - } - - // Expansion controls for system registers - initSystem(app, fields) { - - // No expansion area - if (!fields) - return; - - // Expansion area - let exp = this.expansion = new Toolkit.Component(app, { - class: "expansion", - id : Toolkit.id(), - style: { - display : "inline-grid", - gridTemplateColumns: "max-content max-content", - } - }); - - // Process all controls - for (let field of fields) { - - // Bit check box - if (field[0] == "check") { - let box = new Toolkit.Checkbox(app); - box.setText(field[1], false); - box.bit = field[2]; - box.addEventListener("input", e=>this.onBit(e)); - exp.append(box.element); - if (this.type == Register.PIR || this.type == Register.TKCW) - box.disabled = true; - this.controls.push(box); - } - - // Decimal text box - else if (field[0] == "textd") { - - // Containing element for outer layout purposes - let div = document.createElement("div"); - div.className = "text-dec"; - Object.assign(div.style, { - alignItems : "center", - display : "inline-grid", - gridTemplateColumns: "max-content auto" - }); - - // Text box - let txt = new Toolkit.TextBox(app, { - id : Toolkit.id(), - spellcheck: false, - value : "0", - style : { - maxWidth: "2em" - } - }); - txt.bit = field[2]; - txt.bits = field[3]; - txt.isHex = false; - txt.addEventListener("action", e=>this.onText(e)); - - // Label - let lbl = new Toolkit.Label(app, { - htmlFor: txt.element.id, - id : Toolkit.id(), - tag : "label" - }); - lbl.setText(field[1], false); - - txt.setLabel(lbl); - - // Disable all fields - if (this.type == Register.PIR || this.type == Register.TKCW) { - lbl.disabled = true; - txt.disabled = true; - } - - // Output control - this.controls.push(txt); - div.append(lbl.element); - div.append(txt.element); - exp.append(div); - } - - // Hexadecimal text box - else if (field[0] == "texth") { - - // Text box - let txt = new Toolkit.TextBox(app, { - class : "tk text-box mono", - id : Toolkit.id(), - spellcheck: false, - value : "0", - style : { - maxWidth: "3em" - } - }); - txt.bit = field[2]; - txt.bits = field[3]; - txt.isHex = true; - txt.addEventListener("action", e=>this.onText(e)); - - // Label - let lbl = new Toolkit.Label(app, { - htmlFor: txt.element.id, - id : Toolkit.id(), - tag : "label" - }); - lbl.setText(field[1], false); - - txt.setLabel(lbl); - - // Disable all fields - if (this.type == Register.PIR || this.type == Register.TKCW) { - lbl.disabled = true; - txt.disabled = true; - } - - // Output control - this.controls.push(txt); - exp.append(lbl.element); - exp.append(txt.element); - } - - } - - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Expand/collapse check box input - chkInput(e) { - this.expanded = this.chkExpand.checked; - } - - // Program register format changed - onProgram(format) { - this.format = format; - this.txtValue.element.classList - [format == Register.HEX ? "add" : "remove"]("mono"); - this.refresh(); - } - - // Bit check box input - onBit(e) { - let oldValue = this.target[this.key]; - let target = e.target.component; - let mask = 1 << target.bit; - - // Cannot change the value - if (e.disabled) - return; - - // Update the value - this.setValue(target.checked ? oldValue | mask : oldValue & ~mask); - } - - // Text box commit - onText(e) { - - // Cannot change the value - if (e.disabled) - return; - - // Working variables - let oldValue = this.target[this.key]; - let target = e.target.component; - let newValue = parseInt(target.value, target.isHex ? 16 : 10); - - // The provided value is invalid - if (!Number.isInteger(newValue)) { - this.refresh(); - return; - } - - // Update the value - let mask = (1 << target.bits) - 1 << target.bit; - this.setValue(oldValue & ~mask | newValue << target.bit & mask); - } - - // Value text box commit - valAction(e) { - Toolkit.handle(e); - let value = this.parseValue(this.txtValue.value); - if (value === null) - this.refresh(); - else this.setValue(value); - } - - // Value text box key press - valKeyDown(e) { - if (e.altKey || e.ctrlKey || e.shiftKey || e.key != "Escape") - return; - Toolkit.handle(e); - this.refresh(); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // The expansion area is visible - get expanded() { return this.chkExpand.checked; } - set expanded(expanded) { - this.chkExpand.checked = expanded; - this.setVisible(this.main.visible); - } - - // Specify whether the element is visible - get visible() { return this.main.visible; } - set visible(visible) { - visible = !!visible; - if (visible == this.main.visible) - return; - this.main.element.style[visible ? "removeProperty" : "setProperty"] - ("position", "absolute"); - this.main.visible = visible; - this.setVisible(visible); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Disassembler configuration has changed - dasmConfigured() { - let names; - switch (this.apiType) { - case Core.VB_SYSTEM: names = Disassembler.REG_SYSTEM; break; - case Core.VB_OTHER : names = Disassembler.REG_OTHER ; break; - } - this.chkExpand.uiLabel.setText(this.apiType != Core.VB_PROGRAM ? - names[this.apiId] : this.dasm.programRegister(this.key)); - } - - // Update controls from simulation state - refresh() { - - // Value text box - let value = this.target[this.key]; - this.txtValue.value = this.formatValue(value); - - // Expansion controls - for (let ctrl of this.controls) { - - // Bit check box - if (ctrl instanceof Toolkit.Checkbox) - ctrl.checked = !!(value >> ctrl.bit & 1); - - // Decimal text box - else if (ctrl instanceof Toolkit.TextBox && !ctrl.isHex) - ctrl.value = value >> ctrl.bit & (1 << ctrl.bits) - 1; - - // Hexadecimal text box - else if (ctrl instanceof Toolkit.TextBox && ctrl.isHex) { - ctrl.value = this.dasm.hex( - value >> ctrl.bit & (1 << ctrl.bits) - 1, - Math.ceil(ctrl.bits / 4), - false); - } - - } - - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Format a register value as text - formatValue(value) { - switch (this.format) { - case Register.HEX: - return this.debug.hex(value >>> 0, 8, false); - case Register.SIGNED: - return (value >> 0).toString(); - case Register.UNSIGNED: - return (value >>> 0).toString(); - case Register.FLOAT: - value = Debugger.ixf(value); - if (Number.isFinite(value)) { - let text = value.toFixed(100); - if (/[^0-9\-\.]/.test(text)) - text = value.toFixed(6); - if (text.indexOf(".") != -1) { - text = text.replace(/0+$/, "") - .replace(/\.$/, ".0"); - } else text += ".0"; - return text; - } - if (!Number.isNaN(value)) { - return ( - (value == Number.NEGATIVE_INFINITY ? "-" : "") + - this.debug.app.localize("{debug.cpu.infinity}") - ); - } - return "NaN"; - } - return null; - } - - // Parse text as a register value - parseValue(value) { - switch (this.format) { - case Register.HEX: - value = parseInt(value, 16); - return ( - !Number.isInteger(value) || - value < 0 || - value > 0xFFFFFFFF ? - null : value - ); - case Register.SIGNED: - value = parseInt(value); - return ( - !Number.isInteger(value) || - value < -0x80000000 || - value > 0x7FFFFFFF ? - null : value - ); - case Register.UNSIGNED: - value = parseInt(value); - return ( - !Number.isInteger(value) || - value < 0 || - value > 0xFFFFFFFF ? - null : value - ); - case Register.FLOAT: - value = parseFloat(value); - return ( - !Number.isFinite(value) || - value < Debugger.ixf(0xFF7FFFFF) || - value > Debugger.ixf(0x7F7FFFFF) - ? null : Debugger.fxi(value) >>> 0); - } - return null; - } - - // Specify a new register value - async setValue(value) { - - // Update the value in the simulation state - let options = {}; - if (this.key == "pc") - options.refresh = [ this.registers.cpu.disassembler.subscription ]; - let result = await this.debug.core.setRegister( - this.debug.sim, this.apiType, this.apiId, value, options); - - // Update the value in the debugger window - this.target[this.key] = result.value; - - // Update the register controls - this.refresh(); - } - - // Update visibility for expansion controls - setVisible(visible) { - if (this.expansion) - this.expansion.visible = visible && !!this.expanded; - } - -} - - - -/////////////////////////////////////////////////////////////////////////////// - - - -// Register list manager -class RegisterPane extends Toolkit.SplitPane { - - //////////////////////////////// Constants //////////////////////////////// - - // System register templates - static SYSTEMS = [ - [ "pc" , Register.PLAIN, Core.VB_OTHER , Core.VB_PC ], - [ Core.VB_PSW , Register.PSW , Core.VB_SYSTEM, Core.VB_PSW ], - [ Core.VB_ADTRE, Register.PLAIN, Core.VB_SYSTEM, Core.VB_ADTRE ], - [ Core.VB_CHCW , Register.CHCW , Core.VB_SYSTEM, Core.VB_CHCW ], - [ Core.VB_ECR , Register.ECR , Core.VB_SYSTEM, Core.VB_ECR ], - [ Core.VB_EIPC , Register.PLAIN, Core.VB_SYSTEM, Core.VB_EIPC ], - [ Core.VB_EIPSW, Register.PSW , Core.VB_SYSTEM, Core.VB_EIPSW ], - [ Core.VB_FEPC , Register.PLAIN, Core.VB_SYSTEM, Core.VB_FEPC ], - [ Core.VB_FEPSW, Register.PSW , Core.VB_SYSTEM, Core.VB_FEPSW ], - [ Core.VB_PIR , Register.PIR , Core.VB_SYSTEM, Core.VB_PIR ], - [ Core.VB_TKCW , Register.TKCW , Core.VB_SYSTEM, Core.VB_TKCW ], - [ 29 , Register.PLAIN, Core.VB_SYSTEM, 29 ], - [ 30 , Register.PLAIN, Core.VB_SYSTEM, 30 ], - [ 31 , Register.PLAIN, Core.VB_SYSTEM, 31 ] - ]; - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(cpu) { - super(cpu.debug.app, { - orientation: "top", - style: { - overflow: "visible" - } - }); - - // Configure instance fields - this.cpu = cpu; - this.list = []; - this.pending = false; - this.subscription = [ 0, cpu.index, "cpu", "registers", "refresh" ], - - // Initialize regsiters - this.pc = 0xFFFFFFF0; - this.program = new Array(32); - this.system = new Array(32); - for (let x = 0; x < 32; x++) - this.program[x] = this.system[x] = 0; - - // System registers list - this.lstSystem = new Toolkit.Component(cpu.debug.app, { - class: "tk registers", - style: { - minHeight: "100%", - minWidth : "100%", - width : "max-content" - } - }); - - // System registers scroll pane - this.scrSystem = new Toolkit.ScrollPane(cpu.debug.app, { - class : "tk scroll-pane scr-system", - overflowX: "hidden", - overflowY: "scroll", - view : this.lstSystem, - style : { - position: "relative" - } - }); - this.primary = this.scrSystem; - - // Program registers list - this.lstProgram = new Toolkit.Component(cpu.debug.app, { - class: "tk registers", - style: { - minHeight: "100%", - minWidth : "100%", - width : "max-content" - } - }); - - // Program registers scroll pane - this.scrProgram = new Toolkit.ScrollPane(cpu.debug.app, { - class : "tk scroll-pane scr-program", - overflowX: "hidden", - overflowY: "scroll", - view : this.lstProgram - }); - this.secondary = this.scrProgram; - - // Configure register lists - for (let sys of RegisterPane.SYSTEMS) - this.addRegister(new Register(this, ... sys)); - for (let x = 0; x < 32; x++) { - this.addRegister(new Register(this, - x, Register.PROGRAM, Core.VB_PROGRAM, x)); - } - - // Value text box measurer - let text = []; - for (let c of "0123456789abcdefABCDEF") - text.push(c.repeat(8)); - this.sizer = new Toolkit.Label(cpu.app, { - class: "tk text-box mono", - style: { - position : "absolute", - visibility: "hidden" - } - }); - this.sizer.setText(text.join("\n"), false); - this.list[0].main.element.after(this.sizer.element); - - // Monitor the bounds of the register names column - let resizer = new ResizeObserver(()=>this.regResize()); - for (let reg of this.list) - resizer.observe(reg.chkExpand.element); - - // Monitor the bounds of the value text boxes - this.list[0].txtValue.addEventListener("resize", e=>this.valResize(e)); - this.sizer .addEventListener("resize", e=>this.sizResize(e)); - } - - // Add a Register object to a list - addRegister(reg) { - this.list.push(reg); - - // Add the main element to the appropriate list container - let list = this[reg.type == Register.PROGRAM ? - "lstProgram" : "lstSystem"]; - list.add(reg.main); - - // Add the expansion element - if (reg.expansion != null) - list.add(reg.expansion); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Register label resized - regResize() { - let max = 0; - let widths = new Array(this.list.length); - - // Measure the widths of all labels - for (let x = 0; x < this.list.length; x++) { - widths[x] = Math.ceil(this.list[x].chkExpand.element - .getBoundingClientRect().width); - max = Math.max(max, widths[x]); - } - - // Ensure all labels share the same maximum width - for (let x = 0; x < this.list.length; x++) { - if (widths[x] < max) - this.list[x].chkExpand.element.style.minWidth = max + "px"; - } - - } - - // Sizer resized - sizResize(e) { - let width = Math.ceil(e.target.getBoundingClientRect().width) + "px"; - for (let reg of this.list) - reg.txtValue.style.minWidth = width; - } - - // Value text box resized - valResize(e) { - let height = Math.ceil(e.target.getBoundingClientRect().height); - this.scrSystem .vscroll.unitIncrement = height; - this.scrProgram.vscroll.unitIncrement = height; - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Retrieve registers from the simulation state - async fetch() { - - // Select the parameters for the simulation fetch - let params = {}; - - // A communication with the core thread is already underway - if (this.pending) { - this.pending = params; - this.refresh(); - return; - } - - // Retrieve data from the simulation state - this.pending = params; - for (let data = null, promise = null; this.pending instanceof Object;){ - - // Wait for a transaction to complete - if (promise != null) { - this.pending = true; - data = await promise; - promise = null; - } - - // Initiate a new transaction - if (this.pending instanceof Object) { - params = this.pending; - let options = {}; - if (this.isVisible()) - options.subscription = this.subscription; - promise = this.cpu.debug.core.getAllRegisters( - this.cpu.debug.sim, options); - } - - // Process the result of a transaction - if (data != null) { - this.refresh(data); - data = null; - } - - }; - this.pending = false; - } - - // Component is being displayed for the first time - firstShow() { - - // Retrieve the desired dimensions of the system registers list - let bounds = this.scrSystem.element.getBoundingClientRect(); - - // Show all hidden system registers - for (let reg of this.list) - reg.visible = true; - - // Prepare the initial dimensions of the register lists - this .element.style.width = Math.ceil(bounds.width ) + "px"; - this.scrSystem .element.style.height = Math.ceil(bounds.height) + "px"; - this.scrSystem .overflowX = "auto"; - this.scrSystem .overflowY = "auto"; - this.scrProgram.overflowX = "auto"; - this.scrProgram.overflowY = "auto"; - - this.fetch(); - } - - // Update register lists - refresh(msg = null) { - - // Receiving data from the simulation state - if (msg != null) { - this.pc = msg.pc; - this.program.splice(0, this.program.length, ... msg.program); - this.system .splice(0, this.system .length, ... msg.system ); - } - - // Update register controls - for (let reg of this.list) - reg.refresh(); - } - - // Stop receiving updates from the simulation - unsubscribe() { - this.cpu.debug.core.unsubscribe(this.subscription, false); - } - -} - -Debugger.CPU = CPU; -} - -export { register }; diff --git a/web/debugger/Debugger.js b/web/debugger/Debugger.js deleted file mode 100644 index be029a8..0000000 --- a/web/debugger/Debugger.js +++ /dev/null @@ -1,104 +0,0 @@ -import { ISX } from /**/"./ISX.js"; - -// Debug mode UI manager -class Debugger { - - ///////////////////////////// Static Methods ////////////////////////////// - - // Data type conversions - static F32 = new Float32Array(1); - static U32 = new Uint32Array(this.F32.buffer); - - // Reinterpret a float32 as a u32 - static fxi(x) { - this.F32[0] = x; - return this.U32[0]; - } - - // Process file data as ISM - static isx(data) { - return new ISX(data); - } - - // Reinterpret a u32 as a float32 - static ixf(x) { - this.U32[0] = x; - return this.F32[0]; - } - - // Compute the number of lines scrolled by a WheelEvent - static linesScrolled(e, lineHeight, pageLines, delta) { - let ret = { - delta: delta, - lines: 0 - }; - - // No scrolling occurred - if (e.deltaY == 0); - - // Scrolling by pixel - else if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL) { - ret.delta += e.deltaY; - ret.lines = Math.sign(ret.delta) * - Math.floor(Math.abs(ret.delta) / lineHeight); - ret.delta -= ret.lines * lineHeight; - } - - // Scrolling by line - else if (e.deltaMode == WheelEvent.DOM_DELTA_LINE) - ret.lines = Math.trunc(e.deltaY); - - // Scrolling by page - else if (e.deltaMode == WheelEvent.DOM_DELTA_PAGE) - ret.lines = Math.trunc(e.deltaY) * pageLines; - - // Unknown scrolling mode - else ret.lines = 3 * Math.sign(e.deltaY); - - return ret; - } - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, sim, index) { - - // Configure instance fields - this.app = app; - this.core = app.core; - this.dasm = app.dasm; - this.sim = sim; - - // Configure debugger windows - this.cpu = new Debugger.CPU (this, index); - this.memory = new Debugger.Memory(this, index); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Disassembler configuration has changed - dasmConfigured() { - this.cpu .dasmConfigured(); - this.memory.dasmConfigured(); - } - - // Ensure PC is visible in the disassembler - followPC(pc = null) { - this.cpu.disassembler.followPC(pc); - } - - // Format a number as hexadecimal - hex(value, digits = null, decorated = true) { - return this.dasm.hex(value, digits, decorated); - } - -} - -// Register component classes -(await import(/**/"./CPU.js" )).register(Debugger); -(await import(/**/"./Memory.js")).register(Debugger); - -export { Debugger }; diff --git a/web/debugger/ISX.js b/web/debugger/ISX.js deleted file mode 100644 index 44ee8da..0000000 --- a/web/debugger/ISX.js +++ /dev/null @@ -1,177 +0,0 @@ -// Debug manager for Intelligent Systems binaries -class ISX { - - ///////////////////////// Initialization Methods ////////////////////////// - - // Throws on decoding error - constructor(data) { - - // Configure instance fields - this.data = data; - this.offset = 0; - this.ranges = []; - this.symbols = []; - this.codes = []; - - // Skip any header that may be present - if (data.length >= 32 && this.readInt(3) == 0x585349) - this.offset = 32; - else this.offset = 0; - - // Process all records - while (this.offset < this.data.length) { - switch (this.readInt(1)) { - - // Virtual Boy records - case 0x11: this.code (); break; - case 0x13: this.range (); break; - case 0x14: this.symbol(); break; - - // System records - case 0x20: - case 0x21: - case 0x22: - let length = this.readInt(4); - this.offset += length; - break; - - // Other records - default: throw "ISX decode error"; - } - } - - // Cleanup instance fields - delete this.data; - delete this.decoder; - delete this.offset; - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Produce a .vb format ROM file from the ISX code segments - toROM() { - let head = 0x00000000; - let tail = 0x01000000; - - // Inspect all code segments - for (let code of this.codes) { - let start = code.address & 0x00FFFFFF; - let end = start + code.data.length; - - // Segment begins in the first half of ROM - if (start < 0x00800000) { - - // Segment ends in the second half of ROM - if (end > 0x00800000) { - head = tail = 0; - break; - } - - // Segment ends in the first half of ROM - else if (end > head) - head = end; - } - - // Segment begins in the second half of ROM - else if (start < tail) - tail = start; - } - - // Prepare the output buffer - let min = head + 0x01000000 - tail; - let size = 1; - for (; size < min; size <<= 1); - let rom = new Uint8Array(size); - - // Output all code segments - for (let code of this.codes) { - let dest = code.address & rom.length - 1; - for (let src = 0; src < code.data.length; src++, dest++) - rom[dest] = code.data[src]; - } - - return rom; - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Process a code record - code() { - let address = this.readInt(4); - let length = this.readInt(4); - let data = this.readBytes(length); - if ( - length == 0 || - length > 0x01000000 || - (address & 0x07000000) != 0x07000000 || - (address & 0x07000000) + length > 0x08000000 - ) throw "ISX decode error"; - this.codes.push({ - address: address, - data : data - }); - } - - // Process a range record - range() { - let count = this.readInt(2); - while (count--) { - let start = this.readInt(4); - let end = this.readInt(4); - let type = this.readInt(1); - this.ranges.push({ - end : end, - start: start, - type : type - }); - } - } - - // Process a symbol record - symbol() { - let count = this.readInt(2); - while (count--) { - let length = this.readInt(1); - let name = this.readString(length); - let flags = this.readInt(2); - let address = this.readInt(4); - this.symbols.push({ - address: address, - flags : flags, - name : name - }); - } - } - - // Read a byte buffer - readBytes(size) { - if (this.offset + size > this.data.length) - throw "ISX decode error"; - let ret = this.data.slice(this.offset, this.offset + size); - this.offset += size; - return ret; - } - - // Read an integer - readInt(size) { - if (this.offset + size > this.data.length) - throw "ISX decode error"; - let ret = new Uint32Array(1); - for (let shift = 0; size > 0; size--, shift += 8) - ret[0] |= this.data[this.offset++] << shift; - return ret[0]; - } - - // Read a text string - readString(size) { - return (this.decoder = this.decoder || new TextDecoder() - ).decode(this.readBytes(size)); - } - -} - -export { ISX }; diff --git a/web/debugger/Memory.js b/web/debugger/Memory.js deleted file mode 100644 index 5012a72..0000000 --- a/web/debugger/Memory.js +++ /dev/null @@ -1,574 +0,0 @@ -import { Toolkit } from /**/"../toolkit/Toolkit.js"; -let register = Debugger => Debugger.Memory = - -// Debugger memory window -class Memory extends Toolkit.Window { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(debug, index) { - super(debug.app, { - class: "tk window memory" - }); - - // Configure instance fields - this.data = null, - this.dataAddress = null, - this.debug = debug; - this.delta = 0; - this.editDigit = null; - this.height = 300; - this.index = index; - this.lines = []; - this.pending = false; - this.shown = false; - this.subscription = [ 0, index, "memory", "refresh" ]; - this.width = 400; - - // Available buses - this.buses = [ - { - editAddress: 0x05000000, - viewAddress: 0x05000000 - } - ]; - this.bus = this.buses[0]; - - // Window - this.setTitle("{debug.memory._}", true); - this.substitute("#", " " + (index + 1)); - if (index == 1) - this.element.classList.add("two"); - this.addEventListener("close" , e=>this.visible = false); - this.addEventListener("visibility", e=>this.onVisibility(e)); - - // Client area - Object.assign(this.client.style, { - display : "grid", - gridTemplateRows: "max-content auto" - }); - - // Bus drop-down - this.drpBus = new Toolkit.DropDown(debug.app); - this.drpBus.setLabel("{debug.memory.bus}", true); - this.drpBus.setTitle("{debug.memory.bus}", true); - this.drpBus.add("{debug.memory.busMemory}", true, this.buses[0]); - this.drpBus.addEventListener("input", e=>this.busInput()); - this.add(this.drpBus); - - // Hex editor - this.hexEditor = new Toolkit.Component(debug.app, { - class : "tk mono hex-editor", - role : "application", - tabIndex: "0", - style : { - display : "grid", - gridTemplateColumns: "repeat(17, max-content)", - height : "100%", - minWidth : "100%", - overflow : "hidden", - position : "relative", - width : "max-content" - } - }); - this.hexEditor.localize = ()=>{ - this.hexEditor.localizeRoleDescription(); - this.hexEditor.localizeLabel(); - }; - this.hexEditor.setLabel("{debug.memory.hexEditor}", true); - this.hexEditor.setRoleDescription("{debug.memory.hexEditor}", true); - this.hexEditor.addEventListener("focusout", e=>this.commit ( )); - this.hexEditor.addEventListener("keydown" , e=>this.hexKeyDown(e)); - this.hexEditor.addEventListener("resize" , e=>this.hexResize ( )); - this.hexEditor.addEventListener("wheel" , e=>this.hexWheel (e)); - this.hexEditor.addEventListener( - "pointerdown", e=>this.hexPointerDown(e)); - this.lastFocus = this.hexEditor; - - // Label for measuring text dimensions - this.sizer = new Toolkit.Label(debug.app, { - class : "tk label mono", - visible : false, - visibility: true, - style: { - position: "absolute" - } - }); - this.sizer.setText("\u00a0", false); //   - this.hexEditor.append(this.sizer); - - // Hex editor scroll pane - this.scrHex = new Toolkit.ScrollPane(debug.app, { - overflowX: "auto", - overflowY: "hidden", - view : this.hexEditor - }); - this.add(this.scrHex); - - // Hide the bus drop-down: Virtual Boy only has one bus - this.drpBus.visible = false; - this.client.style.gridTemplateRows = "auto"; - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Bus drop-down selection - busInput() { - - // An edit is in progress - if (this.editDigit !== null) - this.commit(false); - - // Switch to the new bus - this.bus = this.drpBus.value; - this.fetch(); - } - - // Hex editor key press - hexKeyDown(e) { - - // Error checking - if (e.altKey || e.ctrlKey) - - // Processing by key, scroll lock off - if (!e.getModifierState("ScrollLock")) switch (e.key) { - case "ArrowDown": - this.commit(); - this.setEditAddress(this.bus.editAddress + 16); - Toolkit.handle(e); - return; - case "ArrowLeft": - this.commit(); - this.setEditAddress(this.bus.editAddress - 1); - Toolkit.handle(e); - return; - case "ArrowRight": - this.commit(); - this.setEditAddress(this.bus.editAddress + 1); - Toolkit.handle(e); - return; - case "ArrowUp": - this.commit(); - this.setEditAddress(this.bus.editAddress - 16); - Toolkit.handle(e); - return; - case "PageDown": - this.commit(); - this.setEditAddress(this.bus.editAddress + this.tall(true)*16); - Toolkit.handle(e); - return; - case "PageUp": - this.commit(); - this.setEditAddress(this.bus.editAddress - this.tall(true)*16); - Toolkit.handle(e); - return; - } - - // Processing by key, scroll lock on - else switch (e.key) { - case "ArrowDown": - this.setViewAddress(this.bus.viewAddress + 16); - this.fetch(); - Toolkit.handle(e); - return; - case "ArrowLeft": - this.scrHex.scrollLeft -= this.scrHex.hscroll.unitIncrement; - Toolkit.handle(e); - return; - case "ArrowRight": - this.scrHex.scrollLeft += this.scrHex.hscroll.unitIncrement; - Toolkit.handle(e); - return; - case "ArrowUp": - this.setViewAddress(this.bus.viewAddress - 16); - this.fetch(); - Toolkit.handle(e); - return; - case "PageDown": - this.setViewAddress(this.bus.viewAddress + this.tall(true)*16); - this.fetch(); - Toolkit.handle(e); - return; - case "PageUp": - this.setViewAddress(this.bus.viewAddress - this.tall(true)*16); - this.fetch(); - Toolkit.handle(e); - return; - } - - // Processing by key, editing - switch (e.key) { - - case "0": case "1": case "2": case "3": case "4": - case "5": case "6": case "7": case "8": case "9": - case "a": case "A": case "b": case "B": case "c": - case "C": case "d": case "D": case "e": case "E": - case "f": case "F": - let digit = parseInt(e.key, 16); - if (this.editDigit === null) { - this.editDigit = digit; - this.setEditAddress(this.bus.editAddress); - } else { - this.editDigit = this.editDigit << 4 | digit; - this.commit(); - this.setEditAddress(this.bus.editAddress + 1); - } - break; - - // Commit the current edit - case "Enter": - if (this.editDigit === null) - break; - this.commit(); - this.setEditAddress(this.bus.editAddress + 1); - break; - - // Cancel the current edit - case "Escape": - if (this.editDigit === null) - return; - this.editDigit = null; - this.setEditAddress(this.bus.editAddress); - break; - - default: return; - } - - Toolkit.handle(e); - } - - // Hex editor pointer down - hexPointerDown(e) { - - // Error checking - if (e.button != 0) - return; - - // Working variables - let cols = this.lines[0].lblBytes.map(l=>l.getBoundingClientRect()); - let y = Math.max(0, Math.floor((e.clientY-cols[0].y)/cols[0].height)); - let x = 15; - - // Determine which column is closest to the touch point - if (e.clientX < cols[15].right) { - for (let l = 0; l < 15; l++) { - if (e.clientX > (cols[l].right + cols[l + 1].x) / 2) - continue; - x = l; - break; - } - } - - // Update the selection address - let address = this.toAddress(this.bus.viewAddress + y * 16 + x); - if (this.editDigit !== null && address != this.bus.editAddress) - this.commit(); - this.setEditAddress(address); - } - - // Hex editor resized - hexResize() { - let tall = this.tall(false); - let grew = this.lines.length < tall; - - // Process all visible lines - for (let y = this.lines.length; y < tall; y++) { - let line = { - lblAddress: document.createElement("div"), - lblBytes : [] - }; - - // Address label - line.lblAddress.className = "addr" + (y == 0 ? " first" : ""); - this.hexEditor.append(line.lblAddress); - - // Byte labels - for (let x = 0; x < 16; x++) { - let lbl = line.lblBytes[x] = document.createElement("div"); - lbl.className = "byte b" + x + (y == 0 ? " first" : ""); - this.hexEditor.append(lbl); - } - - this.lines.push(line); - } - - // Remove lines that are no longer visible - while (tall < this.lines.length) { - let line = this.lines[tall]; - line.lblAddress.remove(); - for (let lbl of line.lblBytes) - lbl.remove(); - this.lines.splice(tall, 1); - } - - // Configure components - let lineHeight = this.sizer.element.getBoundingClientRect().height; - this.scrHex.hscroll.unitIncrement = lineHeight; - this.hexEditor.element.style.gridAutoRows = lineHeight + "px"; - - // Update components - if (grew) - this.fetch(); - else this.refresh(); - } - - // Hex editor mouse wheel - hexWheel(e) { - - // Error checking - if (e.altKey || e.ctrlKey || e.shiftKey) - return; - - // Always handle the event - Toolkit.handle(e); - - // Determine how many full lines were scrolled - let scr = Debugger.linesScrolled(e, - this.sizer.element.getBoundingClientRect().height, - this.tall(true), - this.delta - ); - this.delta = scr.delta; - scr.lines = Math.max(-3, Math.min(3, scr.lines)); - - // No lines were scrolled - if (scr.lines == 0) - return; - - // Scroll the view - this.setViewAddress(this.bus.viewAddress + scr.lines * 16); - this.fetch(); - } - - // Window key press - onKeyDown(e) { - super.onKeyDown(e); - - // Error checking - if (e.altKey || !e.ctrlKey || e.shiftKey) - return; - - // Processing by key - switch (e.key) { - case "g": case "G": this.goto(); break; - default: return; - } - - Toolkit.handle(e); - } - - // Window visibility - onVisibility(e) { - this.shown = this.shown || e.visible; - if (!e.visible) - this.debug.core.unsubscribe(this.subscription, false); - else this.fetch(); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Disassembler configuration has changed - dasmConfigured() { - this.refresh(); - } - - // Prompt the user to navigate to a new editing address - goto() { - - // Retrieve the value from the user - let addr = prompt(this.app.localize("{debug.memory.goto}")); - if (addr === null) - return; - addr = parseInt(addr.trim(), 16); - if ( - !Number.isInteger(addr) || - addr < 0 || - addr > 4294967295 - ) return; - - // Commit an outstanding edit - if (this.editDigit !== null && this.bus.editAddress != addr) - this.commit(); - - // Navigate to the given address - this.hexEditor.focus(); - this.setEditAddress(addr, 1/3); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Write the edited value to the simulation state - commit(refresh = true) { - - // Error checking - if (this.editDigit === null) - return; - - // The edited value is in the bus's data buffer - if (this.data != null) { - let offset = this.toAddress(this.bus.editAddress-this.dataAddress); - if (offset < this.data.length) - this.data[offset] = this.editDigit; - } - - // Write one byte to the simulation state - let data = new Uint8Array(1); - data[0] = this.editDigit; - this.editDigit = null; - this.debug.core.write(this.debug.sim, this.bus.editAddress, - data, { refresh: refresh }); - } - - // Retrieve data from the simulation state - async fetch() { - - // Select the parameters for the simulation fetch - let params = { - address: this.toAddress(this.bus.viewAddress - 10 * 16), - length : (this.tall(false) + 20) * 16 - }; - - // A communication with the core thread is already underway - if (this.pending) { - this.pending = params; - this.refresh(); - return; - } - - // Retrieve data from the simulation state - this.pending = params; - for (let data=null, promise=null; this.pending instanceof Object;) { - - // Wait for a transaction to complete - if (promise != null) { - this.pending = true; - data = await promise; - promise = null; - } - - // Initiate a new transaction - if (this.pending instanceof Object) { - params = this.pending; - let options = {}; - if (this.isVisible()) - options.subscription = this.subscription; - promise = this.debug.core.read(this.debug.sim, - params.address, params.length, options); - } - - // Process the result of a transaction - if (data != null) { - this.refresh(data); - data = null; - } - - }; - this.pending = false; - } - - // Update hex editor - refresh(msg = null) { - - // Receiving data from the simulation state - if (msg != null) { - this.data = msg.data; - this.dataAddress = msg.address; - } - - // Process all lines - for (let y = 0; y < this.lines.length; y++) { - let address = this.toAddress(this.bus.viewAddress + y * 16); - let line = this.lines[y]; - - // Address label - line.lblAddress.innerText = this.debug.hex(address, 8, false); - - // Process all bytes - for (let x = 0; x < 16; x++) { - let label = line.lblBytes[x]; - let text = "--"; - - // Currently editing this byte - if (address+x==this.bus.editAddress && this.editDigit!==null) { - text = this.debug.hex(this.editDigit, 1, false); - } - - // Bus data exists - else if (this.data != null) { - let offset = this.toAddress(address-this.dataAddress+x); - - // The byte is contained in the bus data buffer - if (offset >= 0 && offset < this.data.length) - text = this.debug.hex(this.data[offset], 2, false); - } - - label.innerText = text; - label.classList[address + x == this.bus.editAddress ? - "add" : "remove"]("edit"); - } - - } - } - - // Specify the address of the hex editor's selection - setEditAddress(address, auto = false) { - let col = this.lines[0].lblBytes[address&15].getBoundingClientRect(); - let port = this.scrHex.viewport.element.getBoundingClientRect(); - let row = this.toAddress(address & ~15); - let scr = this.scrHex.scrollLeft; - let tall = this.tall(true, 0); - - // Ensure the data row is fully visible - if (this.toAddress(row - this.bus.viewAddress) >= tall * 16) { - if (!auto) { - this.setViewAddress( - this.toAddress(this.bus.viewAddress - row) <= - this.toAddress(row - (this.bus.viewAddress + tall * 16)) - ? row : this.toAddress(row - (tall - 1) * 16)); - } else this.setViewAddress(row - Math.floor(tall * auto) * 16); - this.fetch(); - } - - // Ensure the column is fully visible - this.scrHex.scrollLeft = - Math.min( - Math.max( - scr, - scr + col.right - port.right - ), - scr - port.x + col.x - ) - ; - - // Refresh the display; - this.bus.editAddress = this.toAddress(address); - this.refresh(); - } - - // Specify the address of the hex editor's view - setViewAddress(address) { - this.bus.viewAddress = this.toAddress(address); - } - - // Measure the number of lines visible in the view - tall(fully = null, plus = 1) { - return Math.max(1, Math[fully===null ? "abs" : fully?"floor":"ceil"]( - this.scrHex.viewport.element.getBoundingClientRect().height / - this.sizer .element.getBoundingClientRect().height - )) + plus; - } - - // Ensure an address is in the proper range - toAddress(address) { - return address >>> 0; - } - -} - -export { register }; diff --git a/web/locale/en-US.json b/web/locale/en-US.json deleted file mode 100644 index 9a855f0..0000000 --- a/web/locale/en-US.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "id" : "en-US", - "name": "English (US)", - - "app": { - "title": "Virtual Boy Emulator" - }, - - "menu._": "Main menu", - - "menu.file": { - "_" : "File", - "loadROM" : "Load ROM{#}...", - "loadROMError" : "Error loading ROM file", - "loadROMInvalid": "The selected file is not a Virtual Boy ROM.", - "dualMode" : "Dual mode", - "debugMode" : "Debug mode" - }, - - "menu.emulation": { - "_" : "Emulation", - "run" : "Run", - "pause" : "Pause", - "reset" : "Reset{#}", - "linkSims": "Link sims" - }, - - "menu.debug": { - "_" : "Debug{#}", - "backgrounds" : "Backgrounds", - "bgMaps" : "BG maps", - "breakpoints" : "Breakpoints", - "characters" : "Characters", - "console" : "Console", - "cpu" : "CPU", - "frameBuffers": "Frame buffers", - "memory" : "Memory", - "objects" : "Objects", - "palettes" : "Palettes" - }, - - "menu.theme": { - "_" : "Theme", - "auto" : "Auto", - "dark" : "Dark", - "light" : "Light", - "virtual": "Virtual" - }, - - "window": { - "close": "Close" - }, - - "debug.cpu": { - "_" : "CPU{#}", - "disassembler" : "Disassembler", - "float" : "Float", - "format" : "Format", - "goto" : "Enter the address to seek to:", - "hex" : "Hex", - "infinity" : "Infinity", - "programRegisters": "Program registers", - "signed" : "Signed", - "systemRegisters" : "System registers", - "unsigned" : "Unsigned", - "value" : "Value" - }, - - "debug.memory": { - "_" : "Memory{#}", - "bus" : "Bus", - "busMemory": "Memory", - "goto" : "Enter the address to seek to:", - "hexEditor": "Hex editor" - } - -} diff --git a/web/template.html b/web/template.html deleted file mode 100644 index 69f9ab8..0000000 --- a/web/template.html +++ /dev/null @@ -1 +0,0 @@ -Virtual Boy Emulator \ No newline at end of file diff --git a/web/theme/check.svg b/web/theme/check.svg deleted file mode 100644 index ac12455..0000000 --- a/web/theme/check.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/check2.svg b/web/theme/check2.svg deleted file mode 100644 index f0c4196..0000000 --- a/web/theme/check2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/theme/close.svg b/web/theme/close.svg deleted file mode 100644 index d70dcf1..0000000 --- a/web/theme/close.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/collapse.svg b/web/theme/collapse.svg deleted file mode 100644 index 7d7ab60..0000000 --- a/web/theme/collapse.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/dark.css b/web/theme/dark.css deleted file mode 100644 index b1785fb..0000000 --- a/web/theme/dark.css +++ /dev/null @@ -1,28 +0,0 @@ -:root { - --tk-control : #333333; - --tk-control-active : #555555; - --tk-control-border : #cccccc; - --tk-control-highlight : #444444; - --tk-control-shadow : #9b9b9b; - --tk-control-text : #cccccc; - --tk-desktop : #111111; - --tk-selected : #008542; - --tk-selected-blur : #325342; - --tk-selected-blur-text : #ffffff; - --tk-selected-text : #ffffff; - --tk-splitter-focus : #008542c0; - --tk-window : #222222; - --tk-window-blur-close : #d9aeae; - --tk-window-blur-close-text : #eeeeee; - --tk-window-blur-title : #9fafb9; - --tk-window-blur-title2 : #c0b0a0; - --tk-window-blur-title-text : #444444; - --tk-window-close : #ee9999; - --tk-window-close-focus : #99ee99; - --tk-window-close-focus-text: #333333; - --tk-window-close-text : #ffffff; - --tk-window-text : #cccccc; - --tk-window-title : #80ccff; - --tk-window-title2 : #ffb894; - --tk-window-title-text : #000000; -} diff --git a/web/theme/expand.svg b/web/theme/expand.svg deleted file mode 100644 index f8cdfe5..0000000 --- a/web/theme/expand.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/web/theme/expand2.svg b/web/theme/expand2.svg deleted file mode 100644 index 9673e56..0000000 --- a/web/theme/expand2.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/inconsolata.woff2 b/web/theme/inconsolata.woff2 deleted file mode 100644 index db38b3a..0000000 Binary files a/web/theme/inconsolata.woff2 and /dev/null differ diff --git a/web/theme/kiosk.css b/web/theme/kiosk.css deleted file mode 100644 index c166be4..0000000 --- a/web/theme/kiosk.css +++ /dev/null @@ -1,554 +0,0 @@ -:root { - --tk-font-dialog : "Roboto", sans-serif; - --tk-font-mono : "Inconsolata SemiExpanded Medium", monospace; - --tk-font-size : 12px; -} - -@font-face { - font-family: "Roboto"; - src : /**/url("./roboto.woff2") format("woff2"); -} - -@font-face { - font-family: "Inconsolata SemiExpanded Medium"; - src : /**/url("./inconsolata.woff2") format("woff2"); -} - -body { - background: var(--tk-control); -} - -.tk { - box-sizing : border-box; - font-family: var(--tk-font-dialog); - font-size : var(--tk-font-size); - line-height: 1em; - margin : 0; - outline : none; /* User agent focus indicator */ - padding : 0; -} - -table.tk { - border : none; - border-spacing: 0; -} - -.tk.mono { - font-family: var(--tk-font-mono); -} - -.tk::selection, -.tk *::selection { - background: var(--tk-selected); - color : var(--tk-selected-text); -} - -.tk:not(:focus-within)::selection, -.tk *:not(:focus-within)::selection { - background: var(--tk-selected-blur); - color : var(--tk-selected-blur-text); -} - -.tk.display { - background: var(--tk-desktop); -} - -.tk.desktop { - background: var(--tk-desktop); -} - - - -/********************************** Button ***********************************/ - -.tk.button { - align-items : stretch; - display : inline-grid; - grid-template-columns: auto; - justify-content : stretch; - padding : 0 1px 1px 0; -} - -.tk.button .label { - align-items : center; - background : var(--tk-control); - border : 1px solid var(--tk-control-border); - box-shadow : 1px 1px 0 var(--tk-control-border); - color : var(--tk-control-text); - display : grid; - grid-template-columns: auto; - justify-content : center; - padding : 2px; -} - -.tk.button:focus .label { - background: var(--tk-control-active); -} - -.tk.button.pushed { - padding: 1px 0 0 1px; -} - -.tk.button.pushed .label { - box-shadow: none; -} - -.tk.button[aria-disabled="true"] .label { - color : var(--tk-control-shadow); - border : 1px solid var(--tk-control-shadow); - box-shadow: 1px 1px 0 var(--tk-control-shadow); -} - - - -/********************************* Checkbox **********************************/ - -.tk.checkbox { - column-gap: 2px; -} - -.tk.checkbox .box { - border: 1px solid var(--tk-control-shadow); - color : var(--tk-control-text); -} - -.tk.checkbox:focus .box { - background: var(--tk-control-active); -} - -.tk.checkbox .box:before { - background : transparent; - content : ""; - display : block; - height : 10px; - mask : /**/url("./check.svg") center no-repeat; - -webkit-mask: /**/url("./check.svg") center no-repeat; - width : 10px; -} - -.tk.checkbox[aria-checked="true"] .box:before { - background: currentcolor; -} - -.tk.checkbox[aria-checked="mixed"] .box:before { - background : currentcolor; - mask : /**/url("./check2.svg") center no-repeat; - -webkit-mask: /**/url("./check2.svg") center no-repeat; -} - -.tk.checkbox.pushed .box:before { - background: var(--tk-control-shadow); -} - -.tk.checkbox[aria-disabled="true"] .box { - background: var(--tk-control); - color : var(--tk-control-shadow); -} -.tk.checkbox[aria-disabled="true"] .label { - color: var(--tk-control-shadow); -} - - - -/********************************* DropDown **********************************/ - -.tk.drop-down { - background: var(--tk-window); - border : 1px solid var(--tk-control-shadow); - color : var(--tk-window-text); - padding : 2px; -} - -.tk.drop-down:focus { - background: var(--tk-control-active); -} - -.tk.drop-down[aria-disabled="true"] { - color: var(--tk-control-shadow); -} - - - -/*********************************** Menus ***********************************/ - -.tk.menu-bar { - background : var(--tk-control); - border-bottom: 1px solid var(--tk-control-border); - color : var(--tk-control-text); - cursor : default; - padding : 2px; - position : relative; -} - -.tk.menu { - background: var(--tk-control); - border : 1px solid var(--tk-control-border); - box-shadow: 1px 1px 0 var(--tk-control-border); - color : var(--tk-control-text); - margin : -1px 0 0 1px; - padding : 2px; -} - -.tk.menu-item[aria-disabled="true"] { - color: var(--tk-control-shadow); -} - -.tk.menu-item > * { - align-items: center; - border : 1px solid transparent; - column-gap : 4px; - display : flex; - margin : 0 1px 1px 0; - padding : 2px; - user-select: none; -} - -.tk.menu-item .icon { - box-sizing: border-box; - height : 1em; - width : 1em; -} - -.tk.menu-item .icon:before { - content: ""; - display: block; - height : 100%; - width : 100%; -} - -.tk.menu-bar > .menu-item .icon, -.tk.menu:not(.icons) > .menu-item .icon { - display: none; -} - -.tk.menu-item.checkbox .icon { - border: 1px solid currentcolor; -} - -.tk.menu-item.checkbox[aria-checked="true"] .icon:before { - background : currentcolor; - mask : /**/url("./check.svg") center no-repeat; - -webkit-mask: /**/url("./check.svg") center no-repeat; -} - -.tk.menu-item .label { - flex-grow: 1; -} - -.tk.menu-item:not([aria-expanded="true"], - [aria-disabled="true"], .pushed):hover > *, -.tk.menu-item:not([aria-expanded="true"], .pushed):focus > * { - border : 1px solid var(--tk-control-shadow); - box-shadow: 1px 1px 0 var(--tk-control-shadow); -} - -.tk.menu-item:focus > * { - background: var(--tk-control-active); -} - -.tk.menu-item.pushed > *, -.tk.menu-item[aria-expanded="true"] > * { - background: var(--tk-control-active); - border : 1px solid var(--tk-control-shadow); - box-shadow: none; - margin : 1px 0 0 1px; -} - -.tk.menu > [role="separator"] { - border : solid var(--tk-control-shadow); - border-width: 1px 0 0 0; - margin : 4px 2px; -} - - - -/*********************************** Radio ***********************************/ - -.tk.radio { - column-gap: 2px; -} - -.tk.radio .box { - border : 1px solid var(--tk-control-shadow); - border-radius: 50%; - color : var(--tk-control-text); - margin : 1px; -} - -.tk.radio:focus .box { - background: var(--tk-control-active); -} - -.tk.radio .box:before { - background : transparent; - border-radius: 50%; - content : ""; - display : block; - height : 4px; - margin : 2px; - width : 4px; -} - -.tk.radio[aria-checked="true"] .box:before { - background: currentcolor; -} - -.tk.radio.pushed .box:before { - background: var(--tk-control-shadow); -} - -.tk.radio[aria-disabled="true"] .box { - background: var(--tk-control); - color : var(--tk-control-shadow); -} -.tk.radio[aria-disabled="true"] .label { - color: var(--tk-control-shadow); -} - - - -/********************************* ScrollBar *********************************/ - -.tk.scroll-bar { - border : 1px solid var(--tk-control-shadow); - box-sizing: border-box; -} - -.tk.scroll-bar .unit-less, -.tk.scroll-bar .unit-more { - background: var(--tk-control); - border : 0 solid var(--tk-control-shadow); - color : var(--tk-control-text); - height : 11px; - width : 11px; -} -.tk.scroll-bar[aria-orientation="horizontal"] .unit-less { - border-right-width: 1px; -} -.tk.scroll-bar[aria-orientation="horizontal"] .unit-more { - border-left-width: 1px; -} -.tk.scroll-bar[aria-orientation="vertical"] .unit-less { - border-bottom-width: 1px; -} -.tk.scroll-bar[aria-orientation="vertical"] .unit-more { - border-top-width: 1px; -} - -.tk.scroll-bar .unit-less:before, -.tk.scroll-bar .unit-more:before { - background : currentColor; - content : ""; - display : block; - height : 100%; - mask : /**/url("./scroll.svg") center no-repeat; - -webkit-mask: /**/url("./scroll.svg") center no-repeat; - width : 100%; -} - -.tk.scroll-bar .unit-less.pushed:before, -.tk.scroll-bar .unit-more.pushed:before { - mask-size : 9px; - -webkit-mask-size: 9px; -} - -.tk.scroll-bar[aria-orientation="horizontal"] .unit-less:before { - transform: rotate(-90deg); -} -.tk.scroll-bar[aria-orientation="horizontal"] .unit-more:before { - transform: rotate(90deg); -} -.tk.scroll-bar[aria-orientation="vertical"] .unit-more:before { - transform: rotate(180deg); -} - -.tk.scroll-bar .track { - background: var(--tk-control-highlight); -} - -.tk.scroll-bar .thumb { - background: var(--tk-control); - box-shadow: 0 0 0 1px var(--tk-control-shadow); -} - -.tk.scroll-bar .block-less.pushed, -.tk.scroll-bar .block-more.pushed { - background: var(--tk-control-shadow); - opacity : 0.5; -} - -.tk.scroll-bar:focus .unit-less, -.tk.scroll-bar:focus .unit-more, -.tk.scroll-bar:focus .thumb { - background: var(--tk-control-active); -} - -.tk.scroll-bar[aria-disabled="true"] .unit-less, -.tk.scroll-bar[aria-disabled="true"] .unit-more, -.tk.scroll-bar.unneeded .unit-less, -.tk.scroll-bar.unneeded .unit-more, -.tk.scroll-bar[aria-disabled="true"] .thumb { - color: var(--tk-control-shadow); -} - -.tk.scroll-bar.unneeded .thumb { - visibility: hidden; -} - - - -/******************************** ScrollPane *********************************/ - -.tk.scroll-pane { - border: 1px solid var(--tk-control-shadow); -} - -.tk.scroll-pane > .scroll-bar[aria-orientation="horizontal"] { - border-width: 1px 1px 0 0; -} -.tk.scroll-pane:not(.vertical) > .scroll-bar[aria-orientation="horizontal"] { - border-width: 1px 0 0 0; -} -.tk.scroll-pane > .scroll-bar[aria-orientation="vertical"] { - border-width: 0 0 1px 1px; -} -.tk.scroll-pane:not(.horizontal) > .scroll-bar[aria-orientation="vertical"] { - border-width: 0 0 0 1px; -} - -.tk.scroll-pane > .viewport, -.tk.scroll-pane > .corner { - background: var(--tk-control); -} - - - -/********************************* SplitPane *********************************/ - -.tk.split-pane > [role="separator"]:focus { - background: var(--tk-splitter-focus); -} - -.tk.split-pane > .horizontal[role="separator"] { - width: 3px; -} -.tk.split-pane > .vertical[role="separator"] { - height: 3px; -} - - - -/********************************** TextBox **********************************/ - -.tk.text-box { - background : var(--tk-window); - border : 1px solid var(--tk-control-border); - color : var(--tk-window-text); - line-height: 1em; - height : calc(1em + 2px); - padding : 0; - margin : 0; - min-width : 0; -} - -.tk.text-box.[aria-disabled="true"] { - background: var(--tk-control-shadow); - color : var(--tk-window-text); -} - - - -/********************************** Windows **********************************/ - -.tk.window { - background: var(--tk-control); - border : 1px solid var(--tk-control-shadow); - box-shadow: 1px 1px 0 var(--tk-control-shadow); -} - -.tk.window:focus-within { - border : 1px solid var(--tk-control-border); - box-shadow: 1px 1px 0 var(--tk-control-border); -} - -.tk.window > .nw1 { left : -2px; top : -2px; width : 8px; height: 3px; } -.tk.window > .nw2 { left : -2px; top : 1px; width : 3px; height: 5px; } -.tk.window > .n { left : 6px; top : -2px; right : 6px; height: 3px; } -.tk.window > .ne1 { right: -2px; top : -2px; width : 8px; height: 3px; } -.tk.window > .ne2 { right: -2px; top : 1px; width : 3px; height: 5px; } -.tk.window > .w { left : -2px; top : 6px; bottom: 6px; width : 3px; } -.tk.window > .e { right: -2px; top : 6px; bottom: 6px; width : 3px; } -.tk.window > .sw1 { left : -2px; bottom: -2px; width : 8px; height: 3px; } -.tk.window > .sw2 { left : -2px; bottom: 1px; width : 3px; height: 5px; } -.tk.window > .s { left : 6px; bottom: -2px; right : 6px; height: 3px; } -.tk.window > .se1 { right: -2px; bottom: -2px; width : 8px; height: 3px; } -.tk.window > .se2 { right: -2px; bottom: 1px; width : 3px; height: 5px; } - -.tk.window > .title { - align-items : center; - background : var(--tk-window-blur-title); - border-bottom: 1px solid var(--tk-control-shadow); - color : var(--tk-window-blur-title-text); - padding : 1px; - user-select : none; -} - -.tk.window:focus-within > .title { - background: var(--tk-window-title); - color : var(--tk-window-title-text); -} - -.tk.window.two > .title { - background: var(--tk-window-blur-title2); -} - -.tk.window.two:focus-within > .title { - background: var(--tk-window-title2); -} - -.tk.window > .title .text { - cursor : default; - font-weight : bold; - overflow : hidden; - text-align : center; - text-overflow: ellipsis; - white-space : nowrap; -} - -.tk.window > .title .close-button { - background: var(--tk-window-blur-close); - border : 1px solid var(--tk-control-shadow); - box-sizing: border-box; - color : var(--tk-window-close-text); - height : 13px; - width : 13px; -} - -.tk.window:focus-within > .title .close-button { - background: var(--tk-window-close); -} - -.tk.window > .title .close-button:focus { - background: var(--tk-window-close-focus); - color : var(--tk-window-close-focus-text); - outline : 1px solid var(--tk-control); -} - -.tk.window > .title .close-button:before { - background : currentcolor; - content : ""; - display : block; - height : 11px; - mask : /**/url("./close.svg") center no-repeat; - -webkit-mask: /**/url("./close.svg") center no-repeat; - width : 11px; -} - -.tk.window > .title .close-button.pushed:before { - mask-size : 9px; - -webkit-mask-size: 9px; -} - -.tk.window > .client { - overflow: hidden; -} diff --git a/web/theme/light.css b/web/theme/light.css deleted file mode 100644 index 9239e4f..0000000 --- a/web/theme/light.css +++ /dev/null @@ -1,28 +0,0 @@ -:root { - --tk-control : #eeeeee; - --tk-control-active : #cccccc; - --tk-control-border : #000000; - --tk-control-highlight : #f8f8f8; - --tk-control-shadow : #6c6c6c; - --tk-control-text : #000000; - --tk-desktop : #cccccc; - --tk-selected : #008542; - --tk-selected-blur : #5e7d70; - --tk-selected-blur-text : #ffffff; - --tk-selected-text : #ffffff; - --tk-splitter-focus : #008542c0; - --tk-window : #ffffff; - --tk-window-blur-close : #d9aeae; - --tk-window-blur-close-text : #eeeeee; - --tk-window-blur-title : #aac4d5; - --tk-window-blur-title2 : #dbc4b8; - --tk-window-blur-title-text : #444444; - --tk-window-close : #ee9999; - --tk-window-close-focus : #99ee99; - --tk-window-close-focus-text: #333333; - --tk-window-close-text : #ffffff; - --tk-window-text : #000000; - --tk-window-title : #80ccff; - --tk-window-title2 : #ffb894; - --tk-window-title-text : #000000; -} diff --git a/web/theme/radio.svg b/web/theme/radio.svg deleted file mode 100644 index 3d6392a..0000000 --- a/web/theme/radio.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/roboto.woff2 b/web/theme/roboto.woff2 deleted file mode 100644 index 0167f19..0000000 Binary files a/web/theme/roboto.woff2 and /dev/null differ diff --git a/web/theme/scroll.svg b/web/theme/scroll.svg deleted file mode 100644 index dc7e5b5..0000000 --- a/web/theme/scroll.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/vbemu.css b/web/theme/vbemu.css deleted file mode 100644 index 69410ea..0000000 --- a/web/theme/vbemu.css +++ /dev/null @@ -1,194 +0,0 @@ -/******************************** CPU Window *********************************/ - -.tk.window.cpu .client { - padding: 1px; -} - -.tk.window.cpu .scr-dasm { - border-right-width: 0; - box-shadow : 1px 0 0 var(--tk-control-shadow); -} - -.tk.window.cpu .scr-system { - border-width: 1px 1px 0 0; - box-shadow : -0.5px 0.5px 0 0.5px var(--tk-control-shadow); -} - -.tk.window.cpu .scr-program { - border-width: 0 1px 1px 0; - box-shadow : -0.5px -0.5px 0 0.5px var(--tk-control-shadow); -} - -.tk.window.cpu .disassembler { - background : var(--tk-window); - color : var(--tk-window-text); -} - -.tk.window.cpu .disassembler div { - cursor : default; - line-height: calc(1em + 2px); - isolation : isolate; - user-select: none; -} - -.tk.window.cpu .disassembler .address { - margin-left: 2px; -} - -.tk.window.cpu .disassembler .byte { - margin-left: 0.5em; - text-align : center; -} - -.tk.window.cpu .disassembler .operands { - margin-right: 2px; -} - -.tk.window.cpu .disassembler .byte.b0, -.tk.window.cpu .disassembler .mnemonic, -.tk.window.cpu .disassembler .operands { - margin-left: 1em; -} - -.tk.window.cpu .disassembler .pc { - background: var(--tk-selected-blur); - left : 1px; - right : 1px; -} -.tk.window.cpu .disassembler:focus-within .pc { - background: var(--tk-selected); -} - -.tk.window.cpu .disassembler .is-pc { - color: var(--tk-selected-blur-text); -} -.tk.window.cpu .disassembler:focus-within .is-pc { - color: var(--tk-selected-text); -} - -.tk.window.cpu .registers { - background: var(--tk-window); - color : var(--tk-window-text); -} - -.tk.window.cpu .registers > * { - padding: 0 1px; -} -.tk.window.cpu .registers > *:first-child { - padding-top: 1px; -} -.tk.window.cpu .registers > *:last-child { - padding-bottom: 1px; -} - -.tk.window.cpu .registers .expand .box { - border : none; - border-radius: 2px; - margin : 0 1px 0 0; -} -.tk.window.cpu .registers .expand .box:before { - background: transparent; - content : ""; - display : block; - height : 11px; - width : 11px; -} - -.tk.window.cpu .registers .expand[role="checkbox"] .box:before { - background : currentcolor; - mask : /**/url("./expand.svg") center no-repeat; - -webkit-mask: /**/url("./expand.svg") center no-repeat; -} -.tk.window.cpu .registers .expand[aria-checked="true"] .box:before { - background : currentcolor; - mask : /**/url("./collapse.svg") center no-repeat; - -webkit-mask: /**/url("./collapse.svg") center no-repeat; -} -.tk.window.cpu .registers .expand:focus .box { - background: var(--tk-control-active); -} -.tk.window.cpu .registers .main { - column-gap: 0.5em; -} - -.tk.window.cpu .registers .expansion { - gap : 1px 1em; - padding: 2px 2px 2px 1.4em; -} - -.tk.window.cpu .registers .main { - column-gap: 0.5em; -} - -.tk.window.cpu .registers .text-box { - background: transparent; - border : none; - padding : 0 1px; -} - -.tk.window.cpu .registers .text-box:focus { - outline: 1px solid var(--tk-selected); -} - -.tk.window.cpu .registers .text-dec { - column-gap: 2px; -} - -.tk.window.cpu .registers .text-dec .label { - text-align: center; - min-width : 13px; -} - -.tk.window.cpu .registers *[aria-disabled="true"]:is(.label, .text-box) { - color: var(--tk-control-shadow); -} - - - -/******************************* Memory Window *******************************/ - -.tk.window.memory .client { - gap : 1px; - padding: 1px; -} - -.tk.window.memory .hex-editor { - align-items: center; - background : var(--tk-window); - color : var(--tk-window-text); -} - -.tk.window.memory .hex-editor div { - cursor : default; - line-height: calc(1em + 2px); - user-select: none; -} - -.tk.window.memory .hex-editor .addr { - margin-left: 2px; -} - -.tk.window.memory .hex-editor .byte { - margin-left: 0.5em; - text-align : center; -} - -.tk.window.memory .hex-editor .b0, -.tk.window.memory .hex-editor .b8 { - margin-left: 1em; -} - -.tk.window.memory .hex-editor .b15 { - margin-right: 2px; -} - -.tk.window.memory .hex-editor .edit { - background: var(--tk-selected-blur); - color : var(--tk-selected-blur-text); - outline : 1px solid var(--tk-selected-blur); -} -.tk.window.memory .hex-editor:focus-within .edit { - background: var(--tk-selected); - color : var(--tk-selected-text); - outline : 1px solid var(--tk-selected); -} diff --git a/web/theme/virtual.css b/web/theme/virtual.css deleted file mode 100644 index a697a8f..0000000 --- a/web/theme/virtual.css +++ /dev/null @@ -1,63 +0,0 @@ -:root { - --tk-control : #000000; - --tk-control-active : #550000; - --tk-control-border : #ff0000; - --tk-control-highlight : #550000; - --tk-control-shadow : #aa0000; - --tk-control-text : #ff0000; - --tk-desktop : #000000; - --tk-selected : #aa0000; - --tk-selected-blur : #550000; - --tk-selected-blur-text : #ff0000; - --tk-selected-text : #000000; - --tk-splitter-focus : #ff0000aa; - --tk-window : #000000; - --tk-window-blur-close : #000000; - --tk-window-blur-close-text : #aa0000; - --tk-window-blur-title : #000000; - --tk-window-blur-title2 : #000000; - --tk-window-blur-title-text : #aa0000; - --tk-window-close : #550000; - --tk-window-close-focus : #ff0000; - --tk-window-close-focus-text: #550000; - --tk-window-close-text : #ff0000; - --tk-window-text : #ff0000; - --tk-window-title : #550000; - --tk-window-title2 : #550000; - --tk-window-title-text : #ff0000; -} - -input, select { - filter: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxmaWx0ZXIgaWQ9InYiPjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VHcmFwaGljIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMSAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMSAwIiAvPjwvZmlsdGVyPjwvc3ZnPg==#v"); -} - -.tk.scroll-bar .unit-less, -.tk.scroll-bar .unit-more, -.tk.scroll-bar .thumb { - background: #550000; -} -.tk.scroll-bar .track { - background: #000000; -} - -.tk.scroll-bar:focus, -.tk.scroll-bar:focus .unit-less, -.tk.scroll-bar:focus .unit-more, -.tk.scroll-bar:focus .thumb { - background : #aa0000; - border-color: #ff0000; - color : #000000; -} -.tk.scroll-bar:focus .track { - background: #550000; -} -.tk.scroll-bar:focus .thumb { - box-shadow: 0 0 0 1px #ff0000; -} - -.tk.window { - box-shadow: 1px 1px 0 #550000; -} -.tk.window:focus-within { - box-shadow: 1px 1px 0 #aa0000; -} diff --git a/web/toolkit/App.js b/web/toolkit/App.js deleted file mode 100644 index 8eafa70..0000000 --- a/web/toolkit/App.js +++ /dev/null @@ -1,249 +0,0 @@ -let register = Toolkit => Toolkit.App = - -// Root application container and localization manager -class App extends Toolkit.Component { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(options) { - super(null, Object.assign({ - tabIndex: -1 - }, options)); - - // Configure instance fields - this.components = new Set(); - this.dragElement = null; - this.lastFocus = null; - this.locale = null; - this.locales = new Map(); - - // Configure event handlers - this.addEventListener("focusin", e=>this.onFocus(e)); - this.addEventListener("keydown", e=>this.onKey (e)); - this.addEventListener("keyup" , e=>this.onKey (e)); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Child element focus gained - onFocus(e) { - - // Error checking - if (e.target != document.activeElement) - return; - - // Target is self - if (e.target == this.element) - return this.restoreFocus(); - - // Ensure the child is not contained in a MenuBar - for (let elm = e.target; elm != this.element; elm = elm.parentNode) { - if (elm.getAttribute("role") == "menubar") - return; - } - - // Track the (non-menu) element as the most recent focused component - this.lastFocus = e.target; - } - - // Key press, key release - onKey(e) { - if (this.dragElement == null || e.rerouted) - return; - this.dragElement.dispatchEvent(Object.assign(new Event(e.type), { - altKey : e.altKey, - ctrlKey : e.ctrlKey, - key : e.key, - rerouted: true, - shiftKey: e.shiftKey - })); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Install a locale from URL - async addLocale(url) { - let data; - - // Load the file as JSON, using UTF-8 with or without a BOM - try { data = JSON.parse(new TextDecoder().decode( - await (await fetch(url)).arrayBuffer() )); } - catch { return null; } - - // Error checking - if (!data.id || !data.name) - return null; - - // Flatten the object to keys - let locale = new Map(); - let entries = Object.entries(data); - let stack = []; - while (entries.length != 0) { - let entry = entries.shift(); - - // The value is a non-array object - if (entry[1] instanceof Object && !Array.isArray(entry[1])) { - entries = entries.concat(Object.entries(entry[1]) - .map(e=>[ entry[0] + "." + e[0], e[1] ])); - } - - // The value is a primitive or array - else locale.set(entry[0].toLowerCase(), entry[1]); - } - - this.locales.set(data.id, locale); - return data.id; - } - - // Specify a localization dictionary - setLocale(id) { - if (!this.locales.has(id)) - return false; - this.locale = this.locales.get(id); - for (let comp of this.components) - comp.localize(); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Begin dragging on an element - get drag() { return this.dragElement; } - set drag(event) { - - // Begin dragging - if (event) { - this.dragElement = event.currentTarget; - this.dragPointer = event.pointerId; - this.dragElement.setPointerCapture(event.pointerId); - } - - // End dragging - else { - if (this.dragElement) - this.dragElement.releasePointerCapture(this.dragPointer); - this.dragElement = null; - this.dragPointer = null; - } - - } - - // Configure components for automatic localization, or localize a message - localize(a, b) { - return a instanceof Object ? this.localizeComponents(a, b) : - this.localizeMessage(a, b); - } - - // Return focus to the most recent focused element - restoreFocus() { - - // Error checking - if (!this.lastFocus) - return false; - - // Unable to restore focus - if (!this.isVisible(this.lastFocus)) - return false; - - // Transfer focus to the most recent element - this.lastFocus.focus({ preventScroll: true }); - return true; - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Configure components for automatic localization - localizeComponents(comps, add) { - - // Process all components - for (let comp of (Array.isArray(comps) ? comps : [comps])) { - - // Error checking - if ( - !(comp instanceof Toolkit.Component) || - !(comp.localize instanceof Function) - ) continue; - - // Update the collection and component text - this.components[add ? "add" : "delete"](comp); - comp.localize(); - } - - } - - // Localize a message - localizeMessage(message, substs, circle = new Set()) { - let parts = []; - - // Separate the substitution keys from the literal text - for (let x = 0;;) { - - // Locate the start of the next substitution key - let y = message.indexOf("{", x); - let z = y == -1 ? -1 : message.indexOf("}", y + 1); - - // No substitution key or malformed substitution expression - if (z == -1) { - parts.push(message.substring(z == -1 ? x : y)); - break; - } - - // Append the literal text and the substitution key - parts.push(message.substring(x, y), message.substring(y + 1, z)); - x = z + 1; - } - - // Process all substitutions - for (let x = 1; x < parts.length; x += 2) { - let key = parts[x].toLowerCase(); - let value; - - // The substitution key is already in the recursion chain - if (circle.has(key)) { - parts[x] = "{\u21ba" + key.toUpperCase() + "}"; - continue; - } - - // Resolve the substitution key from the argument - if (substs && substs.has(key)) { - value = substs.get(key); - - // Do not recurse for this substitution - if (!value[1]) { - parts[x] = value[0]; - continue; - } - - // Substitution text - value = value[0]; - } - - // Resolve the substitution from the current locale - else if (this.locale && this.locale.has(key)) - value = this.locale.get(key); - - // A matching substitution key was not found - else { - parts[x] = "{\u00d7" + key.toUpperCase() + "}"; - continue; - } - - // Perform recursive substitution - circle.add(key); - parts[x] = this.localizeMessage(value, substs, circle); - circle.delete(key); - } - - return parts.join(""); - } - -}; - -export { register }; diff --git a/web/toolkit/Button.js b/web/toolkit/Button.js deleted file mode 100644 index 932e8f0..0000000 --- a/web/toolkit/Button.js +++ /dev/null @@ -1,119 +0,0 @@ -let register = Toolkit => Toolkit.Button = - -// Push button -class Button extends Toolkit.Component { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, options = {}) { - super(app, options = Object.assign({ - class : "tk button", - role : "button", - tabIndex: "0" - }, options)); - - // Configure options - if ("disabled" in options) - this.disabled = options.disabled; - this.doNotFocus = !("doNotFocus" in options) || options.doNotFocus; - - // Display text - this.content = new Toolkit.Label(app); - this.add(this.content); - - // Event handlers - this.addEventListener("keydown" , e=>this.onKeyDown (e)); - this.addEventListener("pointerdown", e=>this.onPointerDown(e)); - this.addEventListener("pointermove", e=>this.onPointerMove(e)); - this.addEventListener("pointerup" , e=>this.onPointerUp (e)); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Key press - onKeyDown(e) { - if ( - !(e.altKey || e.ctrlKey || e.shiftKey || this.disabled) && - (e.key == " " || e.key == "Enter") - ) this.activate(); - } - - // Pointer down - onPointerDown(e) { - - // Gain focus - if ( - !this.doNotFocus && - this.isFocusable() && - this.element != document.activeElement - ) this.element.focus(); - else e.preventDefault(); - - // Do not drag - if ( - e.button != 0 || - this.disabled || - this.element.hasPointerCapture(e.pointerId) - ) return; - - // Begin dragging - this.element.setPointerCapture(e.pointerId); - this.element.classList.add("pushed"); - Toolkit.handle(e); - } - - // Pointer move - onPointerMove(e) { - - // Do not drag - if (!this.element.hasPointerCapture(e.pointerId)) - return; - - // Process dragging - this.element.classList[this.isWithin(e) ? "add" : "remove"]("pushed"); - Toolkit.handle(e); - } - - // Pointer up - onPointerUp(e) { - - // Do not activate - if (e.button != 0 || !this.element.hasPointerCapture(e.pointerId)) - return; - - // End dragging - this.element.releasePointerCapture(e.pointerId); - this.element.classList.remove("pushed"); - Toolkit.handle(e); - - // Activate the button if applicable - if (this.isWithin(e)) - this.activate(); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Simulate a click on the button - activate() { - if (!this.disabled) - this.element.dispatchEvent(new Event("action")); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Update localization strings - localize() { - this.localizeText(this.content); - this.localizeLabel(); - this.localizeTitle(); - } - -} - -export { register }; diff --git a/web/toolkit/Checkbox.js b/web/toolkit/Checkbox.js deleted file mode 100644 index d718447..0000000 --- a/web/toolkit/Checkbox.js +++ /dev/null @@ -1,157 +0,0 @@ -let register = Toolkit => Toolkit.Checkbox = - -// Check box -class Checkbox extends Toolkit.Component { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, options = {}) { - super(app, options = Object.assign({ - class : "tk checkbox", - role : "checkbox", - tabIndex: "0" - }, options, { style: Object.assign({ - alignItems : "center", - display : "inline-grid", - gridTemplateColumns: "max-content auto" - }, options.style || {}) })); - - // Configure element - this.element.setAttribute("aria-checked", "false"); - this.addEventListener("keydown" , e=>this.onKeyDown (e)); - this.addEventListener("pointerdown", e=>this.onPointerDown(e)); - this.addEventListener("pointermove", e=>this.onPointerMove(e)); - this.addEventListener("pointerup" , e=>this.onPointerUp (e)); - - // Icon area - this.box = document.createElement("div"); - this.box.className = "tk box"; - this.append(this.box); - - // Display text - this.uiLabel = new Toolkit.Label(app); - this.add(this.uiLabel); - - // Configure options - this.checked = options.checked; - this.disabled = !!options.disabled; - this.instant = !!options.instant; - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Key press - onKeyDown(e) { - if ( - !(e.altKey || e.ctrlKey || e.shiftKey || this.disabled) && - (e.key == " " || e.key == "Enter") - ) this.setChecked(!this.checked); - } - - // Pointer down - onPointerDown(e) { - - // Gain focus - if (!this.disabled) - this.element.focus(); - else e.preventDefault(); - - // Do not drag - if ( - e.button != 0 || - this.disabled || - this.element.hasPointerCapture(e.pointerId) - ) return; - - // Begin dragging - this.element.setPointerCapture(e.pointerId); - - // Use instant activation - if (this.instant) - return this.onPointerUp(e); - - // Do not use instant activation - this.element.classList.add("pushed"); - Toolkit.handle(e); - } - - // Pointer move - onPointerMove(e) { - - // Do not drag - if (!this.element.hasPointerCapture(e.pointerId)) - return; - - // Process dragging - this.element.classList[this.isWithin(e) ? "add" : "remove"]("pushed"); - Toolkit.handle(e); - } - - // Pointer up - onPointerUp(e) { - - // Do not activate - if (e.button != 0 || !this.element.hasPointerCapture(e.pointerId)) - return; - - // End dragging - this.element.releasePointerCapture(e.pointerId); - this.element.classList.remove("pushed"); - Toolkit.handle(e); - - // Activate the check box if applicable - if (this.isWithin(e)) - this.setChecked(this.checked !== true); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // The check box is checked - get checked() { - let ret = this.element.getAttribute("aria-checked"); - return ret == "mixed" ? ret : ret == "true"; - } - set checked(checked) { - checked = checked == "mixed" ? checked : !!checked; - if (checked == this.checked) - return; - this.element.setAttribute("aria-checked", checked); - } - - // Specify the display text - setText(text, localize) { - this.uiLabel.setText(text, localize); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Update localization strings - localize() { - this.uiLabel.localize(); - this.localizeTitle(); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Specify the checked state - setChecked(checked) { - checked = !!checked; - if (checked == this.checked) - return; - let previous = this.checked - this.checked = checked; - this.element.dispatchEvent( - Object.assign(new Event("input"), { previous: previous })); - } - -} - -export { register }; diff --git a/web/toolkit/Component.js b/web/toolkit/Component.js deleted file mode 100644 index 1c62414..0000000 --- a/web/toolkit/Component.js +++ /dev/null @@ -1,473 +0,0 @@ -let register = Toolkit => Toolkit.Component = - -// Base class from which all toolkit components are derived -class Component { - - //////////////////////////////// Constants //////////////////////////////// - - // Non-attributes - static NON_ATTRIBUTES = new Set([ - "checked", "disabled", "doNotFocus", "group", "hover", "max", "min", - "name", "orientation", "overflowX", "overflowY", "tag", "text", - "value", "view", "visibility", "visible" - ]); - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, options = {}) { - - // Configure element - this.element = document.createElement(options.tag || "div"); - this.element.component = this; - for (let entry of Object.entries(options)) { - if ( - Toolkit.Component.NON_ATTRIBUTES.has(entry[0]) || - entry[0] == "type" && options.tag != "input" - ) continue; - if (entry[0] == "style" && entry[1] instanceof Object) - Object.assign(this.element.style, entry[1]); - else this.element.setAttribute(entry[0], entry[1]); - } - - // Configure instance fields - this._isLocalized = false; - this.app = app; - this.display = options.style && options.style.display; - this.style = this.element.style; - this.text = null; - this.visibility = !!options.visibility; - this.visible = !("visible" in options) || options.visible; - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Add a child component - add(comp) { - - // Error checking - if ( - !(comp instanceof Toolkit.Component) || - comp instanceof Toolkit.App || - comp.app != (this.app || this) - ) return false; - - // No components have been added yet - if (!this.children) - this.children = []; - - // The child already has a parent: remove it - if (comp.parent) { - comp.parent.children.splice( - comp.parent.children.indexOf(comp), 1); - } - - // Add the component to self - this.children.push(comp); - this.append(comp.element); - comp.parent = this; - return true; - } - - // Register an event listener on the element - addEventListener(type, listener) { - - // No event listeners have been registered yet - if (!this.listeners) - this.listeners = new Map(); - if (!this.listeners.has(type)) - this.listeners.set(type, []); - - // The listener has already been registered for this event - let listeners = this.listeners.get(type); - if (listeners.indexOf(listener) != -1) - return listener; - - // Resize events are implemented by a ResizeObserver - if (type == "resize") { - if (!this.resizeObserver) { - this.resizeObserver = new ResizeObserver(()=> - this.element.dispatchEvent(new Event("resize"))); - this.resizeObserver.observe(this.element); - } - } - - // Visibility events are implemented by an IntersectionObserver - else if (type == "visibility") { - if (!this.visibilityObserver) { - this.visibilityObserver = new IntersectionObserver( - ()=>this.element.dispatchEvent(Object.assign( - new Event("visibility"), - { visible: this.isVisible() } - )), - { root: document.body } - ); - this.visibilityObserver.observe(this.element); - } - } - - // Register the listener with the element - listeners.push(listener); - this.element.addEventListener(type, listener); - return listener; - } - - // Component cannot be interacted with - get disabled() { return this.element.hasAttribute("disabled"); } - set disabled(disabled) { this.setDisabled(disabled); } - - // Move focus into the component - focus() { - this.element.focus({ preventScroll: true }); - } - - // Specify whether the component is localized - get isLocalized() { return this._isLocalized; } - set isLocalized(isLocalized) { - if (isLocalized == this._isLocalized) - return; - this._isLocalized = isLocalized; - (this instanceof Toolkit.App ? this : this.app) - .localize(this, isLocalized); - } - - // Determine whether an element is actually visible - isVisible(element = this.element) { - if (!document.body.contains(element)) - return false; - for (; element instanceof Element; element = element.parentNode) { - let style = getComputedStyle(element); - if (style.display == "none" || style.visibility == "hidden") - return false; - } - return true; - } - - // Produce an ordered list of registered event listeners for an event type - listEventListeners(type) { - return this.listeners && this.listeners.has(type) && - this.listeners.get(type).list.slice() || []; - } - - // Remove a child component - remove(comp) { - if (comp.parent != this || !this.children) - return false; - let index = this.children.indexOf(comp); - if (index == -1) - return false; - this.children.splice(index, 1); - comp.element.remove(); - comp.parent = null; - return true; - } - - // Unregister an event listener from the element - removeEventListener(type, listener) { - - // Not listening to events of the specified type - if (!this.listeners || !this.listeners.has(type)) - return listener; - - // Listener is not registered - let listeners = this.listeners.get(type); - let index = listeners.indexOf(listener); - if (index == -1) - return listener; - - // Unregister the listener - this.element.removeEventListener(listener); - listeners.splice(index, 1); - - // Delete the ResizeObserver - if ( - type == "resize" && - listeners.list.length == 0 && - this.resizeObserver - ) { - this.resizeObserver.disconnect(); - delete this.resizeObserver; - } - - // Delete the IntersectionObserver - else if ( - type == "visibility" && - listeners.list.length == 0 && - this.visibilityObserver - ) { - this.visibilityObserver.disconnect(); - delete this.visibilityObserver; - } - - return listener; - } - - // Specify accessible name - setLabel(text, localize) { - - // Label is another component - if ( - text instanceof Toolkit.Component || - text instanceof HTMLElement - ) { - this.element.setAttribute("aria-labelledby", - (text.element || text).id); - this.setString("label", null, false); - } - - // Label is the given text - else { - this.element.removeAttribute("aria-labelledby"); - this.setString("label", text, localize); - } - - } - - // Specify role description text - setRoleDescription(text, localize) { - this.setString("roleDescription", text, localize); - } - - // Specify inner text - setText(text, localize) { - this.setString("text", text, localize); - } - - // Specify tooltip text - setTitle(text, localize) { - this.setString("title", text, localize); - } - - // Specify substitution text - substitute(key, text = null, recurse = false) { - if (text === null) { - if (this.substitutions.has(key)) - this.substitutions.delete(key); - } else this.substitutions.set(key, [ text, recurse ]); - if (this.localize instanceof Function) - this.localize(); - } - - // Determine whether the element wants to be visible - get visible() { - let style = this.element.style; - return style.display != "none" && style.visibility != "hidden"; - } - - // Specify whether the element is visible - set visible(visible) { - visible = !!visible; - - // Visibility is not changing - if (visible == this.visible) - return; - - let comps = [ this ].concat( - Array.from(this.element.querySelectorAll("*")) - .map(c=>c.component) - ).filter(c=> - c instanceof Toolkit.Component && - c.listeners && - c.listeners.has("visibility") - ) - ; - let prevs = comps.map(c=>c.isVisible()); - - // Allow the component to be shown - if (visible) { - if (!this.visibility) { - if (this.display) - this.element.style.display = this.display; - else this.element.style.removeProperty("display"); - } else this.element.style.removeProperty("visibility"); - } - - // Prevent the component from being shown - else { - this.element.style.setProperty( - this.visibility ? "visibility" : "display", - this.visibility ? "hidden" : "none" - ); - } - - for (let x = 0; x < comps.length; x++) { - let comp = comps[x]; - visible = comp.isVisible(); - if (visible == prevs[x]) - continue; - comp.element.dispatchEvent(Object.assign( - new Event("visibility"), - { visible: visible } - )); - } - - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Add a child component to the primary client region of this component - append(element) { - this.element.append(element instanceof Toolkit.Component ? - element.element : element); - } - - // Determine whether a component or element is a child of this component - contains(child) { - return this.element.contains(child instanceof Toolkit.Component ? - child.element : child); - } - - // Generate a list of focusable descendant elements - getFocusable(element = this.element) { - let cache; - return Array.from(element.querySelectorAll( - "*:is(a[href],area,button,details,input,textarea,select," + - "[tabindex='0']):not([disabled])" - )).filter(e=>{ - for (; e instanceof Element; e = e.parentNode) { - let style = - (cache || (cache = new Map())).get(e) || - cache.set(e, getComputedStyle(e)).get(e) - ; - if (style.display == "none" || style.visibility == "hidden") - return false; - } - return true; - }); - } - - // Specify the inner text of the primary client region of this component - get innerText() { return this.element.textContent; } - set innerText(text) { this.element.innerText = text; } - - // Determine whether an element is focusable - isFocusable(element = this.element) { - return element.matches( - ":is(a[href],area,button,details,input,textarea,select," + - "[tabindex='0'],[tabindex='-1']):not([disabled])" - ); - } - - // Determine whether a pointer event is within the element - isWithin(e, element = this.element) { - let bounds = element.getBoundingClientRect(); - return ( - e.clientX >= bounds.left && e.clientX < bounds.right && - e.clientY >= bounds.top && e.clientY < bounds.bottom - ); - } - - // Common processing for localizing the accessible name - localizeLabel(element = this.element) { - - // There is no label or the label is another element - if (!this.label || element.hasAttribute("aria-labelledby")) { - element.removeAttribute("aria-label"); - return; - } - - // Localize the label - let text = this.label; - text = !text[1] ? text[0] : - this.app.localize(text[0], this.substitutions); - element.setAttribute("aria-label", text); - } - - // Common processing for localizing the accessible role description - localizeRoleDescription(element = this.element) { - - // There is no role description - if (!this.roleDescription) { - element.removeAttribute("aria-roledescription"); - return; - } - - // Localize the role description - let text = this.roleDescription; - text = !text[1] ? text[0] : - this.app.localize(text[0], this.substitutions); - element.setAttribute("aria-roledescription", text); - } - - // Common processing for localizing inner text - localizeText(element = this.element) { - - // There is no title - if (!this.text) { - element.innerText = ""; - return; - } - - // Localize the text - let text = this.text; - text = !text[1] ? text[0] : - this.app.localize(text[0], this.substitutions); - element.innerText = text; - } - - // Common processing for localizing the tooltip text - localizeTitle(element = this.element) { - - // There is no title - if (!this.title) { - element.removeAttribute("title"); - return; - } - - // Localize the title - let text = this.title; - text = !text[1] ? text[0] : - this.app.localize(text[0], this.substitutions); - element.setAttribute("title", text); - } - - // Common handler for configuring whether the component is disabled - setDisabled(disabled, element = this.element) { - element[disabled ? "setAttribute" : "removeAttribute"] - ("disabled", ""); - element.setAttribute("aria-disabled", disabled ? "true" : "false"); - } - - // Specify display text - setString(key, value, localize = true) { - - // There is no method to update the display text - if (!(this.localize instanceof Function)) - return; - - // Working variables - let app = this instanceof Toolkit.App ? this : this.app; - - // Remove the string - if (value === null) { - if (app && this[key] != null && this[key][1]) - app.localize(this, false); - this[key] = null; - } - - // Set or replace the string - else { - if (app && localize && (this[key] == null || !this[key][1])) - app.localize(this, true); - this[key] = [ value, localize ]; - } - - // Update the display text - this.localize(); - } - - // Retrieve the substitutions map - get substitutions() { - if (!this._substitutions) - this._substitutions = new Map(); - return this._substitutions; - } - -}; - -export { register }; diff --git a/web/toolkit/Desktop.js b/web/toolkit/Desktop.js deleted file mode 100644 index 16815bb..0000000 --- a/web/toolkit/Desktop.js +++ /dev/null @@ -1,86 +0,0 @@ -let register = Toolkit => Toolkit.Desktop = - -// Layered window manager -class Desktop extends Toolkit.Component { - - //////////////////////////////// Constants //////////////////////////////// - - // Comparator for ordering child windows - static CHILD_ORDER(a, b) { - return b.element.style.zIndex - a.element.style.zIndex; - } - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, options = {}) { - super(app, Object.assign({ - class: "tk desktop" - }, options, { style: Object.assign({ - position: "relative", - zIndex : "0" - }, options.style || {})} )); - - // Configure event listeners - this.addEventListener("resize", e=>this.onResize()); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Element resized - onResize() { - - // The element is hidden: its size is indeterminate - if (!this.isVisible()) - return; - - // Don't allow children to be out-of-frame - if (this.children != null) { - for (let child of this.children) { - child.left = child.left; - child.top = child.top; - } - } - - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Add a child component - add(comp) { - super.add(comp); - this.bringToFront(comp); - } - - // Retrieve the foremost visible window - getActiveWindow() { - if (this.children != null) { - for (let child of this.children) { - if (child.isVisible()) - return child; - } - } - return null; - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Reorder children so that a particular child is in front - bringToFront(child) { - this.children.splice(this.children.indexOf(child), 1); - this.children.push(child); - let z = 1 - this.children.length; - for (let child of this.children) - child.element.style.zIndex = z++; - } - -} - -export { register }; diff --git a/web/toolkit/DropDown.js b/web/toolkit/DropDown.js deleted file mode 100644 index e95bdb2..0000000 --- a/web/toolkit/DropDown.js +++ /dev/null @@ -1,154 +0,0 @@ -let register = Toolkit => Toolkit.DropDown = - -class DropDown extends Toolkit.Component { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, options = {}) { - super(app, Object.assign({ - class: "tk drop-down", - tag : "select" - }, options)); - - // Configure instance fields - this.items = []; - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Add an item - add(text, localize, value) { - - // Record the item data - this.items.push([ text, localize, value ]); - - // Add an