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