Compare commits
54
Commits
bcd80d291b
...
4ed3b72995
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ed3b72995 | ||
|
|
8598eab087 | ||
|
|
fe9c5c4781 | ||
|
|
a9a02e2d15 | ||
|
|
ab29f47004 | ||
|
|
531e4a8c3e | ||
|
|
f295e02aaf | ||
|
|
67928da1e6 | ||
|
|
4ebdad0c2d | ||
|
|
5d4a422e61 | ||
|
|
8b020ad808 | ||
|
|
483714c0d6 | ||
|
|
ecbd103917 | ||
|
|
b2412d9487 | ||
|
|
dd1045553b | ||
|
|
459a16076a | ||
|
|
185362e6cd | ||
|
|
c62dd833a3 | ||
|
|
4f0b889b74 | ||
|
|
8ae8980fc8 | ||
|
|
10505e9e98 | ||
|
|
155a3aa678 | ||
|
|
04e79c9151 | ||
|
|
57dcd8370a | ||
|
|
06849b54ba | ||
|
|
eef255b507 | ||
|
|
db966c8cb8 | ||
|
|
b4feff41f0 | ||
|
|
4449acada0 | ||
|
|
799ac9f51a | ||
|
|
b83b49221d | ||
|
|
27e926bd58 | ||
|
|
d802d39d8d | ||
|
|
18b2c589e6 | ||
|
|
f45636a491 | ||
|
|
9ce8c9c778 | ||
|
|
c90b8137de | ||
|
|
bc864644f7 | ||
|
|
e97b52e944 | ||
|
|
7e31fbd582 | ||
|
|
b4b9131f39 | ||
|
|
8b9152dde9 | ||
|
|
26a0357afa | ||
|
|
53826584b8 | ||
|
|
ae22c95dbe | ||
|
|
b134903878 | ||
|
|
52838e4a6d | ||
|
|
6cba6e5d4f | ||
|
|
650d9dfd05 | ||
|
|
f5a84fd4d0 | ||
|
|
9cc01bb6f7 | ||
|
|
e828d99da1 | ||
|
|
da001e02cd | ||
|
|
67bcb6d508 |
+13
-10
@@ -95,16 +95,16 @@ static int32_t busReadMisc(VB *sim, uint8_t address, int type) {
|
|||||||
|
|
||||||
/* Access by register */
|
/* Access by register */
|
||||||
switch (address >> 2 & 15) {
|
switch (address >> 2 & 15) {
|
||||||
case 0x00>>2: break; /* CCR */
|
case 0x00>>2: return extReadCCR(sim); /* CCR */
|
||||||
case 0x04>>2: break; /* CCSR */
|
case 0x04>>2: return extReadCCSR(sim); /* CCSR */
|
||||||
case 0x08>>2: break; /* CDTR */
|
case 0x08>>2: return sim->ext.cdtr; /* CDTR */
|
||||||
case 0x0C>>2: break; /* CDRR */
|
case 0x0C>>2: return sim->ext.cdrr; /* CDRR */
|
||||||
case 0x10>>2: return sim->pad.sdlr; /* SDLR */
|
case 0x10>>2: return sim->pad.sdlr; /* SDLR */
|
||||||
case 0x14>>2: return sim->pad.sdhr; /* SDHR */
|
case 0x14>>2: return sim->pad.sdhr; /* SDHR */
|
||||||
case 0x18>>2: return sim->tmr.counter & 0xFF; /* TLR */
|
case 0x18>>2: return sim->tmr.counter & 0xFF; /* TLR */
|
||||||
case 0x1C>>2: return sim->tmr.counter >> 8 & 0xFF; /* THR */
|
case 0x1C>>2: return sim->tmr.counter >> 8 & 0xFF; /* THR */
|
||||||
case 0x20>>2: return tmrReadControl(sim); /* TCR */
|
case 0x20>>2: return tmrReadControl(sim); /* TCR */
|
||||||
case 0x24>>2: return sim->wcr; /* WCR */
|
case 0x24>>2: return sim->wcr.exp1w << 1 | sim->wcr.rom1w; /* WCR */
|
||||||
case 0x28>>2: return padReadControl(sim); /* SCR */
|
case 0x28>>2: return padReadControl(sim); /* SCR */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,15 +123,16 @@ static void busWriteMisc(VB *sim, uint8_t address, int type, int32_t value) {
|
|||||||
|
|
||||||
/* Access by register */
|
/* Access by register */
|
||||||
switch (address >> 2 & 15) {
|
switch (address >> 2 & 15) {
|
||||||
case 0x00>>2: break; /* CCR */
|
case 0x00>>2: extWriteCCR (sim, value); break; /* CCR */
|
||||||
case 0x04>>2: break; /* CCSR */
|
case 0x04>>2: extWriteCCSR(sim, value); break; /* CCSR */
|
||||||
case 0x08>>2: break; /* CDTR */
|
case 0x08>>2: sim->ext.cdtr = value; break; /* CDTR */
|
||||||
case 0x0C>>2: break; /* CDRR */
|
|
||||||
case 0x18>>2: tmrWriteLow (sim, value); break; /* TLR */
|
case 0x18>>2: tmrWriteLow (sim, value); break; /* TLR */
|
||||||
case 0x1C>>2: tmrWriteHigh (sim, value); break; /* THR */
|
case 0x1C>>2: tmrWriteHigh (sim, value); break; /* THR */
|
||||||
case 0x20>>2: tmrWriteControl(sim, value); break; /* TCR */
|
case 0x20>>2: tmrWriteControl(sim, value); break; /* TCR */
|
||||||
case 0x24>>2: sim->wcr = value & 3; break; /* WCR */
|
|
||||||
case 0x28>>2: padWriteControl(sim, value); break; /* SCR */
|
case 0x28>>2: padWriteControl(sim, value); break; /* SCR */
|
||||||
|
case 0x24>>2: /* WCR */
|
||||||
|
sim->wcr.exp1w = value >> 1 & 1;
|
||||||
|
sim->wcr.rom1w = value & 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -158,6 +159,8 @@ static void busRead(VB *sim, uint32_t address, int type, int32_t *value) {
|
|||||||
|
|
||||||
case 2: /* Misc. I/O */
|
case 2: /* Misc. I/O */
|
||||||
*value = busReadMisc(sim, address, type);
|
*value = busReadMisc(sim, address, type);
|
||||||
|
if (type == VB_S8)
|
||||||
|
*value = (int8_t) *value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3: break; /* Unmapped */
|
case 3: break; /* Unmapped */
|
||||||
|
|||||||
+4
-8
@@ -91,11 +91,6 @@
|
|||||||
#define CPU_XORBSU 82
|
#define CPU_XORBSU 82
|
||||||
#define CPU_XORNBSU 83
|
#define CPU_XORNBSU 83
|
||||||
|
|
||||||
/* Functional operand types */
|
|
||||||
#define CPU_LITERAL 0
|
|
||||||
#define CPU_MEMORY 1
|
|
||||||
#define CPU_REGISTER 2
|
|
||||||
|
|
||||||
/* Bit string operations */
|
/* Bit string operations */
|
||||||
#define CPU_AND_BS 0
|
#define CPU_AND_BS 0
|
||||||
#define CPU_ANDN_BS 1
|
#define CPU_ANDN_BS 1
|
||||||
@@ -239,7 +234,7 @@ static int cpuOnExecute(VB *sim) {
|
|||||||
#define VB_ON_READ VB_DIRECT_READ
|
#define VB_ON_READ VB_DIRECT_READ
|
||||||
#endif
|
#endif
|
||||||
static int cpuRead(VB *sim, uint32_t address, int type, int32_t *value) {
|
static int cpuRead(VB *sim, uint32_t address, int type, int32_t *value) {
|
||||||
uint32_t cycles = 4; /* TODO: Research this */
|
uint32_t cycles = 0; /* TODO: Research this */
|
||||||
|
|
||||||
/* Retrieve the value from the simulation state directly */
|
/* Retrieve the value from the simulation state directly */
|
||||||
busRead(sim, address, type, value);
|
busRead(sim, address, type, value);
|
||||||
@@ -294,7 +289,7 @@ static int cpuReadFetch(VB *sim, int fetch, uint32_t address, int32_t *value) {
|
|||||||
#endif
|
#endif
|
||||||
static int cpuWrite(VB *sim, uint32_t address, int type, int32_t value) {
|
static int cpuWrite(VB *sim, uint32_t address, int type, int32_t value) {
|
||||||
int cancel = 0;
|
int cancel = 0;
|
||||||
uint32_t cycles = 3; /* TODO: Research this */
|
uint32_t cycles = 0; /* TODO: Research this */
|
||||||
|
|
||||||
/* Reset pseudo-halt */
|
/* Reset pseudo-halt */
|
||||||
if (sim->ph.enabled)
|
if (sim->ph.enabled)
|
||||||
@@ -500,7 +495,7 @@ static int cpuException(VB *sim) {
|
|||||||
sim->cpu.eipc = sim->cpu.pc;
|
sim->cpu.eipc = sim->cpu.pc;
|
||||||
sim->cpu.psw.ep = 1;
|
sim->cpu.psw.ep = 1;
|
||||||
sim->cpu.nextPC = 0xFFFF0000 |
|
sim->cpu.nextPC = 0xFFFF0000 |
|
||||||
((cause & 0xFFF0) == 0xFF60 ? 0xFF60 : cause & 0xFFF0);
|
((cause & 0xFFF0) == 0xFF70 ? 0xFF60 : cause & 0xFFF0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* All exceptions */
|
/* All exceptions */
|
||||||
@@ -1642,6 +1637,7 @@ static void cpuSUBF_S(VB *sim) {
|
|||||||
/* TRAP */
|
/* TRAP */
|
||||||
static void cpuTRAP(VB *sim) {
|
static void cpuTRAP(VB *sim) {
|
||||||
sim->cpu.clocks += cpuClocks(15);
|
sim->cpu.clocks += cpuClocks(15);
|
||||||
|
sim->cpu.pc += 2;
|
||||||
cpuThrow(sim, 0xFFA0 + cpuGetImm5U(sim));
|
cpuThrow(sim, 0xFFA0 + cpuGetImm5U(sim));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
/* This file is included into vb.c and cannot be compiled on its own. */
|
||||||
|
#ifdef VBAPI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Callback Handlers *****************************/
|
||||||
|
|
||||||
|
/* Prepare to handle an exception */
|
||||||
|
#ifndef VB_DIRECT_LINK
|
||||||
|
#define VB_ON_LINK sim1->onLink
|
||||||
|
#else
|
||||||
|
extern int VB_DIRECT_LINK(VB *, VB *, uint8_t *, uint8_t *);
|
||||||
|
#define VB_ON_LINK VB_DIRECT_LINK
|
||||||
|
#endif
|
||||||
|
static int extOnLink(VB *sim1, VB *sim2, uint8_t *value1, uint8_t *value2) {
|
||||||
|
return sim1->onLink != NULL && VB_ON_LINK(sim1, sim2, value1, value2);
|
||||||
|
}
|
||||||
|
#undef VB_ON_LINK
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Library Functions *****************************/
|
||||||
|
|
||||||
|
/* Process component */
|
||||||
|
static int extEmulate(VB *sim, uint32_t clocks) {
|
||||||
|
VB *peer; /* Communication peer */
|
||||||
|
VB *sims[2]; /* Communication peers */
|
||||||
|
uint8_t values[2]; /* Transmission values */
|
||||||
|
int x; /* Iterator */
|
||||||
|
|
||||||
|
/* Communication will not occur */
|
||||||
|
if (!sim->ext.c_stat || sim->ext.c_clk_sel)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Communication completes after time to process */
|
||||||
|
if (sim->ext.clocks > clocks) {
|
||||||
|
sim->ext.clocks -= clocks;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exchange transmission data */
|
||||||
|
sims [0] = sim;
|
||||||
|
sims [1] = sim->peer;
|
||||||
|
values[0] = sim->ext.cdtr;
|
||||||
|
values[1] = sim->peer == NULL ? 0 : sim->peer->ext.cdtr;
|
||||||
|
if (extOnLink(sim, sim->peer, &values[0], &values[1]))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Update state */
|
||||||
|
for (x = 0; x < 2; x++) {
|
||||||
|
sim = sims[x];
|
||||||
|
peer = sims[x ^ 1];
|
||||||
|
if (sim == NULL)
|
||||||
|
break;
|
||||||
|
if (peer == NULL)
|
||||||
|
peer = sim;
|
||||||
|
|
||||||
|
/* Registers */
|
||||||
|
sim->ext.cdrr = values[x ^ 1];
|
||||||
|
sim->ext.c_irq |= !sim->ext.c_int_inh;
|
||||||
|
sim->ext.c_stat = 0;
|
||||||
|
sim->ext.cc_rd = sim->ext.cc_wr & peer->ext.cc_wr;
|
||||||
|
sim->ext.cc_smp = sim->ext.cc_sig & peer->ext.cc_sig & sim->ext.cc_rd;
|
||||||
|
sim->ext.cc_irq |= !sim->ext.cc_int_inh &&
|
||||||
|
sim->ext.cc_smp == sim->ext.cc_int_lev;
|
||||||
|
|
||||||
|
/* Interrupt */
|
||||||
|
if (sim->ext.c_irq | sim->ext.cc_irq)
|
||||||
|
sim->cpu.irq |= 0x0008;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read a value from CCR */
|
||||||
|
static int32_t extReadCCR(VB *sim) {
|
||||||
|
return 0x6D |
|
||||||
|
sim->ext.c_int_inh << 7 |
|
||||||
|
sim->ext.c_clk_sel << 4 |
|
||||||
|
sim->ext.c_stat << 1
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read a value from CCSR */
|
||||||
|
static int32_t extReadCCSR(VB *sim) {
|
||||||
|
sim->ext.cc_rd = sim->ext.cc_wr &
|
||||||
|
(sim->peer == NULL ? 1 : sim->peer->ext.cc_wr);
|
||||||
|
return 0x60 |
|
||||||
|
sim->ext.cc_int_inh << 7 |
|
||||||
|
sim->ext.cc_int_lev << 4 |
|
||||||
|
sim->ext.cc_sig << 3 |
|
||||||
|
sim->ext.cc_smp << 2 |
|
||||||
|
sim->ext.cc_wr << 1 |
|
||||||
|
sim->ext.cc_rd
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Simulate a hardware reset */
|
||||||
|
static void extReset(VB *sim) {
|
||||||
|
|
||||||
|
/* Normal */
|
||||||
|
sim->ext.c_clk_sel = 0;
|
||||||
|
sim->ext.c_int_inh = 0;
|
||||||
|
sim->ext.c_stat = 0;
|
||||||
|
sim->ext.cc_int_inh = 1;
|
||||||
|
sim->ext.cc_int_lev = 1;
|
||||||
|
sim->ext.cc_sig = 1;
|
||||||
|
sim->ext.cc_wr = 1;
|
||||||
|
sim->ext.cdrr = 0x00;
|
||||||
|
sim->ext.cdtr = 0x00;
|
||||||
|
|
||||||
|
/* Other */
|
||||||
|
sim->ext.c_irq = 0;
|
||||||
|
sim->ext.cc_irq = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine how many clocks are guaranteed to process */
|
||||||
|
static uint32_t extUntil(VB *sim, uint32_t clocks) {
|
||||||
|
return !sim->ext.c_stat || sim->ext.c_clk_sel || clocks < sim->ext.clocks ?
|
||||||
|
clocks : sim->ext.clocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write a value to CCR */
|
||||||
|
static void extWriteCCR(VB *sim, uint8_t value) {
|
||||||
|
|
||||||
|
/* Configure state */
|
||||||
|
sim->ext.c_int_inh = value >> 7 & 1;
|
||||||
|
sim->ext.c_clk_sel = value >> 4 & 1;
|
||||||
|
|
||||||
|
/* Acknowledge interrupt */
|
||||||
|
if (sim->ext.c_int_inh) {
|
||||||
|
sim->ext.c_irq = 0;
|
||||||
|
if (!sim->ext.cc_irq)
|
||||||
|
sim->cpu.irq &= ~0x0008;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do not initiate a communication */
|
||||||
|
if (sim->ext.c_stat || (value & 0x04) == 0) /* C-Start */
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Initiate a communication */
|
||||||
|
sim->ext.c_stat = 1;
|
||||||
|
sim->ext.clocks = 3200; /* 160us */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write a value to CCSR */
|
||||||
|
static void extWriteCCSR(VB *sim, uint8_t value) {
|
||||||
|
|
||||||
|
/* Configure state */
|
||||||
|
sim->ext.cc_int_inh = value >> 7 & 1;
|
||||||
|
sim->ext.cc_int_lev = value >> 4 & 1;
|
||||||
|
sim->ext.cc_sig = value >> 3 & 1;
|
||||||
|
sim->ext.cc_wr = value >> 1 & 1;
|
||||||
|
|
||||||
|
/* Acknowledge interrupt */
|
||||||
|
if (!sim->ext.cc_int_inh)
|
||||||
|
return;
|
||||||
|
sim->ext.cc_irq = 0;
|
||||||
|
if (!sim->ext.c_irq)
|
||||||
|
sim->cpu.irq &= ~0x0008;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* VBAPI */
|
||||||
+9
-8
@@ -9,12 +9,12 @@
|
|||||||
static void padEmulate(VB *sim, uint32_t clocks) {
|
static void padEmulate(VB *sim, uint32_t clocks) {
|
||||||
|
|
||||||
/* No hardware read in progress */
|
/* No hardware read in progress */
|
||||||
if (sim->pad.si_stat == 0)
|
if (!sim->pad.si_stat)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Hardware read completes after time to process */
|
/* Hardware read completes after time to process */
|
||||||
if (sim->pad.si_stat > clocks) {
|
if (sim->pad.clocks > clocks) {
|
||||||
sim->pad.si_stat -= clocks;
|
sim->pad.clocks -= clocks;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ static int32_t padReadControl(VB *sim) {
|
|||||||
sim->pad.k_int_inh << 7 |
|
sim->pad.k_int_inh << 7 |
|
||||||
sim->pad.para_si << 5 |
|
sim->pad.para_si << 5 |
|
||||||
sim->pad.soft_ck << 4 |
|
sim->pad.soft_ck << 4 |
|
||||||
!!sim->pad.si_stat << 1 |
|
sim->pad.si_stat << 1 |
|
||||||
sim->pad.s_abt_dis
|
sim->pad.s_abt_dis
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,8 @@ static void padWriteControl(VB *sim, uint8_t value) {
|
|||||||
sim->pad.si_stat == 0 && /* No hardware read underway */
|
sim->pad.si_stat == 0 && /* No hardware read underway */
|
||||||
!sim->pad.s_abt_dis /* Hardware reads enabled */
|
!sim->pad.s_abt_dis /* Hardware reads enabled */
|
||||||
) {
|
) {
|
||||||
sim->pad.si_stat = 10240; /* 512us */
|
sim->pad.clocks = 10240; /* 512us */
|
||||||
|
sim->pad.si_stat = 1;
|
||||||
sim->pad.step = 0; /* Abort software read */
|
sim->pad.step = 0; /* Abort software read */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ static void padWriteControl(VB *sim, uint8_t value) {
|
|||||||
sim->pad.para_si = value >> 5 & 1;
|
sim->pad.para_si = value >> 5 & 1;
|
||||||
if (
|
if (
|
||||||
sim->pad.para_si &&
|
sim->pad.para_si &&
|
||||||
sim->pad.si_stat == 0 /* No hardware read underway */
|
!sim->pad.si_stat /* No hardware read underway */
|
||||||
) sim->pad.step = 32;
|
) sim->pad.step = 32;
|
||||||
|
|
||||||
/* Signal a bit for a software read */
|
/* Signal a bit for a software read */
|
||||||
@@ -107,8 +108,8 @@ static void padWriteControl(VB *sim, uint8_t value) {
|
|||||||
|
|
||||||
/* Determine how many clocks are guaranteed to process */
|
/* Determine how many clocks are guaranteed to process */
|
||||||
static uint32_t padUntil(VB *sim, uint32_t clocks) {
|
static uint32_t padUntil(VB *sim, uint32_t clocks) {
|
||||||
return sim->pad.si_stat != 0 && sim->pad.si_stat < clocks ?
|
return sim->pad.si_stat && sim->pad.clocks < clocks ?
|
||||||
sim->pad.si_stat : clocks;
|
sim->pad.clocks : clocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ static uint32_t phDPSTTS(VB *sim) {
|
|||||||
case 3: /* 8ms - L*BSY falling edge */
|
case 3: /* 8ms - L*BSY falling edge */
|
||||||
case 6: /* 13ms-18ms - Display right frame buffer */
|
case 6: /* 13ms-18ms - Display right frame buffer */
|
||||||
case 7: /* 18ms - R*BSY falling edge */
|
case 7: /* 18ms - R*BSY falling edge */
|
||||||
|
case 8: /* 20ms - Display interval complete */
|
||||||
return sim->vip.dp.until;
|
return sim->vip.dp.until;
|
||||||
}
|
}
|
||||||
/* case 1: 3ms - L*BSY rising edge */
|
/* case 1: 3ms - L*BSY rising edge */
|
||||||
|
|||||||
+39
-12
@@ -5,6 +5,15 @@
|
|||||||
|
|
||||||
/***************************** Module Functions ******************************/
|
/***************************** Module Functions ******************************/
|
||||||
|
|
||||||
|
/* Compute clocks until the next decrement to zero */
|
||||||
|
static uint32_t tmrGetUntil(VB *sim) {
|
||||||
|
uint32_t fullTick = sim->tmr.t_clk_sel ? 400 : 2000;
|
||||||
|
uint32_t thisTick = sim->tmr.clocks +
|
||||||
|
(sim->tmr.t_clk_sel ? 0 : 400 * (4 - sim->tmr.tick20));
|
||||||
|
return thisTick + fullTick *
|
||||||
|
(sim->tmr.counter == 0 ? sim->tmr.reload : sim->tmr.counter - 1);
|
||||||
|
}
|
||||||
|
|
||||||
/* Update the counter to a new value */
|
/* Update the counter to a new value */
|
||||||
static void tmrUpdate(VB *sim, uint16_t value) {
|
static void tmrUpdate(VB *sim, uint16_t value) {
|
||||||
if (value == 0 && sim->tmr.counter != 0) {
|
if (value == 0 && sim->tmr.counter != 0) {
|
||||||
@@ -23,10 +32,6 @@ static void tmrUpdate(VB *sim, uint16_t value) {
|
|||||||
/* Process component */
|
/* Process component */
|
||||||
static void tmrEmulate(VB *sim, uint32_t clocks) {
|
static void tmrEmulate(VB *sim, uint32_t clocks) {
|
||||||
|
|
||||||
/* Timer is disabled */
|
|
||||||
if (!sim->tmr.t_enb)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Process all clocks */
|
/* Process all clocks */
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
@@ -40,13 +45,20 @@ static void tmrEmulate(VB *sim, uint32_t clocks) {
|
|||||||
/* Advance forward the component's number of clocks */
|
/* Advance forward the component's number of clocks */
|
||||||
clocks -= sim->tmr.clocks;
|
clocks -= sim->tmr.clocks;
|
||||||
sim->tmr.until -= sim->tmr.clocks;
|
sim->tmr.until -= sim->tmr.clocks;
|
||||||
sim->tmr.clocks = sim->tmr.t_clk_sel ? 400 : 2000;
|
sim->tmr.clocks = 400;
|
||||||
|
sim->tmr.tick20 += sim->tmr.tick20 == 4 ? -4 : 1;
|
||||||
|
|
||||||
|
/* Do not decrement counter */
|
||||||
|
if (
|
||||||
|
!sim->tmr.t_enb ||
|
||||||
|
(!sim->tmr.t_clk_sel && sim->tmr.tick20 != 0)
|
||||||
|
) continue;
|
||||||
|
|
||||||
/* Advance to the next counter value */
|
/* Advance to the next counter value */
|
||||||
tmrUpdate(sim, sim->tmr.counter == 0 ?
|
tmrUpdate(sim, sim->tmr.counter == 0 ?
|
||||||
sim->tmr.reload : sim->tmr.counter - 1);
|
sim->tmr.reload : sim->tmr.counter - 1);
|
||||||
if (sim->tmr.counter == 0)
|
if (sim->tmr.counter == 0)
|
||||||
sim->tmr.until = sim->tmr.clocks * ((uint32_t)sim->tmr.reload + 1);
|
sim->tmr.until = tmrGetUntil(sim);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -71,8 +83,10 @@ static void tmrReset(VB *sim) {
|
|||||||
sim->tmr.z_stat = 0;
|
sim->tmr.z_stat = 0;
|
||||||
|
|
||||||
/* Other */
|
/* Other */
|
||||||
|
sim->tmr.clocks = 400;
|
||||||
sim->tmr.counter = 0xFFFF;
|
sim->tmr.counter = 0xFFFF;
|
||||||
sim->tmr.reload = 0x0000;
|
sim->tmr.reload = 0x0000;
|
||||||
|
sim->tmr.tick20 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Determine how many clocks are guaranteed to process */
|
/* Determine how many clocks are guaranteed to process */
|
||||||
@@ -88,14 +102,27 @@ static void tmrWriteControl(VB *sim, uint8_t value) {
|
|||||||
if (
|
if (
|
||||||
(value & 0x04) && /* Z-Stat-Clr */
|
(value & 0x04) && /* Z-Stat-Clr */
|
||||||
(
|
(
|
||||||
sim->tmr.counter != 0 ||
|
!sim->tmr.t_enb ||
|
||||||
!sim->tmr.t_enb
|
(
|
||||||
|
sim->tmr.counter != 0 &&
|
||||||
|
!(value & 0x01) /* T-Enb */
|
||||||
|
)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
sim->tmr.z_stat = sim->tmr.counter != 0;
|
sim->tmr.z_stat = sim->tmr.counter != 0;
|
||||||
sim->cpu.irq &= ~0x0002;
|
sim->cpu.irq &= ~0x0002;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hardware bug: decrement on switch to 20us mode */
|
||||||
|
if (
|
||||||
|
!sim->tmr.t_clk_sel &&
|
||||||
|
(value & 0x10) && /* T-Clk-Sel */
|
||||||
|
sim->tmr.tick20 != 0
|
||||||
|
) {
|
||||||
|
tmrUpdate(sim, sim->tmr.counter == 0 ?
|
||||||
|
sim->tmr.reload : sim->tmr.counter - 1);
|
||||||
|
}
|
||||||
|
|
||||||
/* Parse fields */
|
/* Parse fields */
|
||||||
sim->tmr.t_clk_sel = value >> 4 & 1;
|
sim->tmr.t_clk_sel = value >> 4 & 1;
|
||||||
sim->tmr.tim_z_int = value >> 3 & 1;
|
sim->tmr.tim_z_int = value >> 3 & 1;
|
||||||
@@ -105,10 +132,8 @@ static void tmrWriteControl(VB *sim, uint8_t value) {
|
|||||||
if (!sim->tmr.tim_z_int)
|
if (!sim->tmr.tim_z_int)
|
||||||
sim->cpu.irq &= ~0x0002;
|
sim->cpu.irq &= ~0x0002;
|
||||||
|
|
||||||
/* Configure countdowns */
|
/* Configure state */
|
||||||
sim->tmr.clocks = sim->tmr.t_clk_sel ? 400 : 2000;
|
sim->tmr.until = tmrGetUntil(sim);
|
||||||
sim->tmr.until = sim->tmr.clocks * (sim->tmr.counter == 0 ?
|
|
||||||
(uint32_t) sim->tmr.reload + 1 : sim->tmr.counter);
|
|
||||||
|
|
||||||
/* TODO: Will Z-Stat raise an interrupt when Tim-Z-Int is set? */
|
/* TODO: Will Z-Stat raise an interrupt when Tim-Z-Int is set? */
|
||||||
}
|
}
|
||||||
@@ -117,12 +142,14 @@ static void tmrWriteControl(VB *sim, uint8_t value) {
|
|||||||
static void tmrWriteHigh(VB *sim, uint8_t value) {
|
static void tmrWriteHigh(VB *sim, uint8_t value) {
|
||||||
sim->tmr.reload = (uint16_t) value << 8 | (sim->tmr.reload & 0x00FF);
|
sim->tmr.reload = (uint16_t) value << 8 | (sim->tmr.reload & 0x00FF);
|
||||||
tmrUpdate(sim, sim->tmr.reload);
|
tmrUpdate(sim, sim->tmr.reload);
|
||||||
|
sim->tmr.until = tmrGetUntil(sim);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write to the low data register */
|
/* Write to the low data register */
|
||||||
static void tmrWriteLow(VB *sim, uint8_t value) {
|
static void tmrWriteLow(VB *sim, uint8_t value) {
|
||||||
sim->tmr.reload = (sim->tmr.reload & 0xFF00) | value;
|
sim->tmr.reload = (sim->tmr.reload & 0xFF00) | value;
|
||||||
tmrUpdate(sim, sim->tmr.reload);
|
tmrUpdate(sim, sim->tmr.reload);
|
||||||
|
sim->tmr.until = tmrGetUntil(sim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,11 @@
|
|||||||
|
|
||||||
/*********************************** Types ***********************************/
|
/*********************************** Types ***********************************/
|
||||||
|
|
||||||
/* Output image */
|
/* VIP BG map cell */
|
||||||
typedef uint8_t Pixels[2][384*224];
|
typedef struct {
|
||||||
|
uint8_t *palette; /* Points to a VB.vip.gplt */
|
||||||
|
uint8_t *pixels; /* Points to a VB.vip.characters */
|
||||||
|
} Cell;
|
||||||
|
|
||||||
/* VSU channel */
|
/* VSU channel */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -26,6 +29,7 @@ typedef struct {
|
|||||||
|
|
||||||
/* Other state */
|
/* Other state */
|
||||||
uint32_t clocks; /* Clocks until modification */
|
uint32_t clocks; /* Clocks until modification */
|
||||||
|
uint8_t modmask; /* Modifications masked */
|
||||||
uint8_t reload; /* Automatic reload value */
|
uint8_t reload; /* Automatic reload value */
|
||||||
} env;
|
} env;
|
||||||
|
|
||||||
@@ -64,9 +68,58 @@ typedef struct {
|
|||||||
} wave;
|
} wave;
|
||||||
|
|
||||||
/* Other state */
|
/* Other state */
|
||||||
uint32_t clocks; /* Clocks until next sample */
|
uint32_t clocks; /* Clocks until next wave or noise sample */
|
||||||
|
uint8_t freqmod; /* Frequency modifications are active */
|
||||||
|
uint32_t until; /* Clocks until channel component update */
|
||||||
} Channel;
|
} Channel;
|
||||||
|
|
||||||
|
/* VIP character (hflp << 1 | vflp) */
|
||||||
|
typedef uint8_t Character[4][64];
|
||||||
|
|
||||||
|
/* VIP object */
|
||||||
|
typedef struct {
|
||||||
|
Cell cell; /* BG map-like attributes */
|
||||||
|
int16_t jp; /* Parallax */
|
||||||
|
int16_t jx; /* Screen base X */
|
||||||
|
int16_t jy; /* Screen Y */
|
||||||
|
uint8_t lron; /* Visible in left and right images */
|
||||||
|
} Object;
|
||||||
|
|
||||||
|
/* Output image */
|
||||||
|
typedef uint8_t Pixels[2][384*224];
|
||||||
|
|
||||||
|
/* VIP world */
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
/* Attributes */
|
||||||
|
uint8_t bgm; /* Mode */
|
||||||
|
uint8_t end; /* Control world */
|
||||||
|
int16_t gp; /* World parallax */
|
||||||
|
int16_t gx; /* World base X */
|
||||||
|
int16_t gy; /* World Y */
|
||||||
|
int16_t mx; /* Scroll base X */
|
||||||
|
int16_t mp; /* Scroll parallax */
|
||||||
|
int16_t my; /* Scroll Y */
|
||||||
|
uint8_t over; /* Use overplane */
|
||||||
|
Cell *overplane; /* Overplane cell */
|
||||||
|
|
||||||
|
/* Non-attributes */
|
||||||
|
Cell *bg[64]; /* Background arrangement */
|
||||||
|
int32_t bgHeight; /* Height of background in pixels */
|
||||||
|
uint32_t bgMaskX; /* Horizontal background mask */
|
||||||
|
uint32_t bgMaskY; /* Vertical background mask */
|
||||||
|
uint32_t bgShift; /* Bits to shift when selecting a BG map */
|
||||||
|
int32_t bgWidth; /* Width of background in pixels */
|
||||||
|
uint32_t hAffine; /* H for affine mode */
|
||||||
|
uint32_t hNaffine; /* H for not affine mode */
|
||||||
|
uint32_t height; /* Height of world in pixels */
|
||||||
|
uint8_t lron; /* Visible in left and right images */
|
||||||
|
uint32_t paramBase; /* H-bias and affine parameter address */
|
||||||
|
uint32_t wAffine; /* W for affine mode */
|
||||||
|
uint32_t wNaffine; /* W for not affine mode */
|
||||||
|
uint32_t width; /* Width of world in pixels */
|
||||||
|
} World;
|
||||||
|
|
||||||
/* Simulation state */
|
/* Simulation state */
|
||||||
struct VB {
|
struct VB {
|
||||||
|
|
||||||
@@ -146,6 +199,28 @@ struct VB {
|
|||||||
int step; /* Operation sub-task ID */
|
int step; /* Operation sub-task ID */
|
||||||
} cpu;
|
} cpu;
|
||||||
|
|
||||||
|
/* Communication port */
|
||||||
|
struct {
|
||||||
|
|
||||||
|
/* Register state */
|
||||||
|
uint8_t c_clk_sel; /* Transmission clock source */
|
||||||
|
uint8_t c_int_inh; /* Interrupt acknowledge/disable */
|
||||||
|
uint8_t c_stat; /* Communication is underway */
|
||||||
|
uint8_t cc_int_inh; /* Interrupt acknowledge/disable */
|
||||||
|
uint8_t cc_int_lev; /* Interrupt condition */
|
||||||
|
uint8_t cc_rd; /* Manual read */
|
||||||
|
uint8_t cc_sig; /* Automatic write */
|
||||||
|
uint8_t cc_smp; /* Automatic read */
|
||||||
|
uint8_t cc_wr; /* Manual write */
|
||||||
|
uint8_t cdrr; /* Data received */
|
||||||
|
uint8_t cdtr; /* Data to transmit */
|
||||||
|
|
||||||
|
/* Other state */
|
||||||
|
int8_t c_irq; /* COM interrupt request */
|
||||||
|
int8_t cc_irq; /* COMCNT interrupt request */
|
||||||
|
uint32_t clocks; /* Master clocks to wait */
|
||||||
|
} ext;
|
||||||
|
|
||||||
/* Game pad */
|
/* Game pad */
|
||||||
struct {
|
struct {
|
||||||
|
|
||||||
@@ -159,6 +234,7 @@ struct VB {
|
|||||||
uint8_t soft_ck; /* Controller communication signal */
|
uint8_t soft_ck; /* Controller communication signal */
|
||||||
|
|
||||||
/* Other state */
|
/* Other state */
|
||||||
|
uint32_t clocks; /* Master clocks to wait */
|
||||||
uint16_t keys; /* Next input bits */
|
uint16_t keys; /* Next input bits */
|
||||||
int step; /* Software read processing phase */
|
int step; /* Software read processing phase */
|
||||||
} pad;
|
} pad;
|
||||||
@@ -176,6 +252,7 @@ struct VB {
|
|||||||
uint32_t clocks; /* Master clocks to wait */
|
uint32_t clocks; /* Master clocks to wait */
|
||||||
uint16_t counter; /* Current counter value */
|
uint16_t counter; /* Current counter value */
|
||||||
uint16_t reload; /* Reload counter value */
|
uint16_t reload; /* Reload counter value */
|
||||||
|
uint8_t tick20; /* Current 20-microsecond tick */
|
||||||
uint32_t until; /* Clocks until interrupt condition */
|
uint32_t until; /* Clocks until interrupt condition */
|
||||||
} tmr;
|
} tmr;
|
||||||
|
|
||||||
@@ -236,6 +313,7 @@ struct VB {
|
|||||||
int frame; /* FRMCYC counter */
|
int frame; /* FRMCYC counter */
|
||||||
int32_t halfword; /* Current output halfword offset */
|
int32_t halfword; /* Current output halfword offset */
|
||||||
int step; /* Processing phase */
|
int step; /* Processing phase */
|
||||||
|
uint8_t toggle; /* Switch frame buffers next frame */
|
||||||
uint32_t until; /* Clocks until interrupt condition */
|
uint32_t until; /* Clocks until interrupt condition */
|
||||||
} xp;
|
} xp;
|
||||||
|
|
||||||
@@ -250,12 +328,17 @@ struct VB {
|
|||||||
uint16_t spt[4]; /* Object control */
|
uint16_t spt[4]; /* Object control */
|
||||||
|
|
||||||
/* Rendering shadow memory */
|
/* Rendering shadow memory */
|
||||||
|
uint8_t buffer; /* Output buffer */
|
||||||
uint16_t halfwords[384*28]; /* Output timing by 1x8 halfword */
|
uint16_t halfwords[384*28]; /* Output timing by 1x8 halfword */
|
||||||
Pixels output[2]; /* Output images, row-major */
|
Pixels output[2]; /* Output images, row-major */
|
||||||
Pixels shadow; /* Drawing shadow image, column-major */
|
Pixels shadow; /* Drawing shadow image, column-major */
|
||||||
|
|
||||||
/* Other state */
|
/* Other state */
|
||||||
|
Cell cells[0x10000]; /* Pre-computed BG map attributes */
|
||||||
|
Character characters[2048]; /* Pre-computed character pixels */
|
||||||
|
Object objects[1024]; /* Pre-computed object attributes */
|
||||||
uint8_t ram[0x40000]; /* Video memory */
|
uint8_t ram[0x40000]; /* Video memory */
|
||||||
|
World worlds[32]; /* Pre-computed world attributes */
|
||||||
} vip;
|
} vip;
|
||||||
|
|
||||||
/* VSU */
|
/* VSU */
|
||||||
@@ -278,6 +361,8 @@ struct VB {
|
|||||||
|
|
||||||
/* Other state */
|
/* Other state */
|
||||||
uint32_t clocks; /* Clocks until modification */
|
uint32_t clocks; /* Clocks until modification */
|
||||||
|
uint8_t freqmask; /* Frequency mask */
|
||||||
|
uint8_t modmask; /* Modifications masked */
|
||||||
uint16_t next; /* Next frequency value */
|
uint16_t next; /* Next frequency value */
|
||||||
int sample; /* Current sample index */
|
int sample; /* Current sample index */
|
||||||
} freqmod;
|
} freqmod;
|
||||||
@@ -296,9 +381,10 @@ struct VB {
|
|||||||
struct {
|
struct {
|
||||||
float i1[2]; /* Previous analog input sample */
|
float i1[2]; /* Previous analog input sample */
|
||||||
float o1[2]; /* Previous analog output sample */
|
float o1[2]; /* Previous analog output sample */
|
||||||
uint32_t capacity; /* Number of audio frames in samples */
|
unsigned capacity; /* Number of audio frames in output buffer */
|
||||||
uint32_t offset; /* Position in output buffer */
|
unsigned offset; /* Position in output buffer */
|
||||||
int16_t *samples; /* Output memory */
|
void *samples; /* Output buffer */
|
||||||
|
int type; /* Output data type */
|
||||||
} out;
|
} out;
|
||||||
|
|
||||||
/* Memory */
|
/* Memory */
|
||||||
@@ -310,6 +396,12 @@ struct VB {
|
|||||||
int sample; /* Output sample index, period 417 */
|
int sample; /* Output sample index, period 417 */
|
||||||
} vsu;
|
} vsu;
|
||||||
|
|
||||||
|
/* Wait controller */
|
||||||
|
struct {
|
||||||
|
uint8_t exp1w; /* Cartridge expansion 1-wait */
|
||||||
|
uint8_t rom1w; /* Cartridge ROM 1-wait */
|
||||||
|
} wcr;
|
||||||
|
|
||||||
/* Pseudo-halt */
|
/* Pseudo-halt */
|
||||||
struct {
|
struct {
|
||||||
uint32_t address; /* Monitor address */
|
uint32_t address; /* Monitor address */
|
||||||
@@ -335,7 +427,6 @@ struct VB {
|
|||||||
} ph;
|
} ph;
|
||||||
|
|
||||||
/* Other state */
|
/* Other state */
|
||||||
uint8_t wcr; /* Wait controller state */
|
|
||||||
uint8_t wram[0x10000]; /* System RAM */
|
uint8_t wram[0x10000]; /* System RAM */
|
||||||
|
|
||||||
/* Application data */
|
/* Application data */
|
||||||
@@ -343,9 +434,11 @@ struct VB {
|
|||||||
vbOnExecute onExecute; /* CPU instruction execute */
|
vbOnExecute onExecute; /* CPU instruction execute */
|
||||||
vbOnFetch onFetch; /* CPU instruction fetch */
|
vbOnFetch onFetch; /* CPU instruction fetch */
|
||||||
vbOnFrame onFrame; /* VIP frame ready */
|
vbOnFrame onFrame; /* VIP frame ready */
|
||||||
|
vbOnLink onLink; /* Communication transfer */
|
||||||
vbOnRead onRead; /* CPU instruction read */
|
vbOnRead onRead; /* CPU instruction read */
|
||||||
vbOnSamples onSamples; /* VSU samples full */
|
vbOnSamples onSamples; /* VSU samples full */
|
||||||
vbOnWrite onWrite; /* CPU instruction write */
|
vbOnWrite onWrite; /* CPU instruction write */
|
||||||
|
VB *peer; /* Communication peer */
|
||||||
void *tag; /* User data */
|
void *tag; /* User data */
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -368,6 +461,7 @@ static int32_t SignExtend(int32_t value, int32_t bits) {
|
|||||||
|
|
||||||
/******************************** Sub-Modules ********************************/
|
/******************************** Sub-Modules ********************************/
|
||||||
|
|
||||||
|
#include "ext.c"
|
||||||
#include "game-pad.c"
|
#include "game-pad.c"
|
||||||
#include "timer.c"
|
#include "timer.c"
|
||||||
#include "bus.c"
|
#include "bus.c"
|
||||||
@@ -391,7 +485,8 @@ static int sysEmulate(VB *sim, uint32_t clocks) {
|
|||||||
;
|
;
|
||||||
|
|
||||||
/* Process all components */
|
/* Process all components */
|
||||||
if (!never) ret = cpuEmulate(sim, clocks);
|
if (!never){ret = cpuEmulate(sim, clocks);}
|
||||||
|
ret |= extEmulate(sim, clocks);
|
||||||
padEmulate(sim, clocks);
|
padEmulate(sim, clocks);
|
||||||
tmrEmulate(sim, clocks);
|
tmrEmulate(sim, clocks);
|
||||||
vsuEmulate(sim, clocks);
|
vsuEmulate(sim, clocks);
|
||||||
@@ -403,6 +498,7 @@ static int sysEmulate(VB *sim, uint32_t clocks) {
|
|||||||
/* Determine how many clocks are guaranteed to process */
|
/* Determine how many clocks are guaranteed to process */
|
||||||
static uint32_t sysUntil(VB *sim, uint32_t clocks) {
|
static uint32_t sysUntil(VB *sim, uint32_t clocks) {
|
||||||
clocks = cpuUntil(sim, clocks);
|
clocks = cpuUntil(sim, clocks);
|
||||||
|
clocks = extUntil(sim, clocks);
|
||||||
clocks = padUntil(sim, clocks);
|
clocks = padUntil(sim, clocks);
|
||||||
clocks = tmrUntil(sim, clocks);
|
clocks = tmrUntil(sim, clocks);
|
||||||
clocks = vipUntil(sim, clocks);
|
clocks = vipUntil(sim, clocks);
|
||||||
@@ -428,19 +524,23 @@ VBAPI int vbEmulate(VB *sim, uint32_t *clocks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Process multiple simulations */
|
/* Process multiple simulations */
|
||||||
VBAPI int vbEmulateEx(VB **sims, int count, uint32_t *clocks) {
|
VBAPI int vbEmulateEx(VB **sims, unsigned count, uint32_t *clocks) {
|
||||||
int brk; /* A callback requested a break */
|
int brk; /* A callback requested a break */
|
||||||
uint32_t until; /* Clocks guaranteed to process */
|
uint32_t until; /* Clocks guaranteed to process */
|
||||||
int x; /* Iterator */
|
unsigned x; /* Iterator */
|
||||||
|
|
||||||
|
/* Process one simulation */
|
||||||
|
if (count == 1)
|
||||||
|
return vbEmulate(sims[0], clocks);
|
||||||
|
|
||||||
|
/* Process multiple simulations */
|
||||||
while (*clocks != 0) {
|
while (*clocks != 0) {
|
||||||
until = *clocks;
|
until = *clocks;
|
||||||
for (x = count - 1; x >= 0; x--)
|
for (x = count; x > 0; x--)
|
||||||
until = sysUntil(sims[x], until);
|
until = sysUntil(sims[x - 1], until);
|
||||||
|
|
||||||
brk = 0;
|
brk = 0;
|
||||||
for (x = count - 1; x >= 0; x--)
|
for (x = count; x > 0; x--)
|
||||||
brk |= sysEmulate(sims[x], until);
|
brk |= sysEmulate(sims[x - 1], until);
|
||||||
|
|
||||||
*clocks -= until;
|
*clocks -= until;
|
||||||
if (brk)
|
if (brk)
|
||||||
return brk; /* TODO: return 1 */
|
return brk; /* TODO: return 1 */
|
||||||
@@ -487,6 +587,11 @@ VBAPI uint16_t vbGetKeys(VB *sim) {
|
|||||||
return sim->pad.keys;
|
return sim->pad.keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Retrieve the current link callback handler */
|
||||||
|
VBAPI vbOnLink vbGetLinkCallback(VB *sim) {
|
||||||
|
return sim->onLink;
|
||||||
|
}
|
||||||
|
|
||||||
/* Retrieve a core option value */
|
/* Retrieve a core option value */
|
||||||
VBAPI int vbGetOption(VB *sim, int key) {
|
VBAPI int vbGetOption(VB *sim, int key) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -495,6 +600,11 @@ VBAPI int vbGetOption(VB *sim, int key) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Retrieve the communication peer */
|
||||||
|
VBAPI VB* vbGetPeer(VB *sim) {
|
||||||
|
return sim->peer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Retrieve the most recent frame image pixels */
|
/* Retrieve the most recent frame image pixels */
|
||||||
VBAPI void vbGetPixels(VB *sim, void *left, int leftStrideX, int leftStrideY,
|
VBAPI void vbGetPixels(VB *sim, void *left, int leftStrideX, int leftStrideY,
|
||||||
void *right, int rightStrideX, int rightStrideY) {
|
void *right, int rightStrideX, int rightStrideY) {
|
||||||
@@ -527,7 +637,7 @@ VBAPI void vbGetPixels(VB *sim, void *left, int leftStrideX, int leftStrideY,
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Transfer pixels to the destination */
|
/* Transfer pixels to the destination */
|
||||||
src = sim->vip.output[sim->vip.dp.buffer ^ 1][i];
|
src = sim->vip.output[sim->vip.buffer][i];
|
||||||
for (y = 0; y < 224; y++, dest += yStride)
|
for (y = 0; y < 224; y++, dest += yStride)
|
||||||
for (x = offset = 0; x < 384; x++, offset += xStride)
|
for (x = offset = 0; x < 384; x++, offset += xStride)
|
||||||
dest[offset] = *src++;
|
dest[offset] = *src++;
|
||||||
@@ -541,7 +651,7 @@ VBAPI uint32_t vbGetProgramCounter(VB *sim) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Retrieve the value in a program register */
|
/* Retrieve the value in a program register */
|
||||||
VBAPI int32_t vbGetProgramRegister(VB *sim, int index) {
|
VBAPI int32_t vbGetProgramRegister(VB *sim, unsigned index) {
|
||||||
return index < 1 || index > 31 ? 0 : sim->cpu.program[index];
|
return index < 1 || index > 31 ? 0 : sim->cpu.program[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,13 +661,18 @@ VBAPI vbOnRead vbGetReadCallback(VB *sim) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Retrieve the audio samples buffer */
|
/* Retrieve the audio samples buffer */
|
||||||
VBAPI void* vbGetSamples(VB *sim, uint32_t *capacity, uint32_t *position) {
|
VBAPI void* vbGetSamples(VB *sim, int *type,
|
||||||
|
unsigned *capacity, unsigned *position) {
|
||||||
if (sim->vsu.out.samples == NULL) {
|
if (sim->vsu.out.samples == NULL) {
|
||||||
|
if (type != NULL)
|
||||||
|
*type = 0;
|
||||||
if (capacity != NULL)
|
if (capacity != NULL)
|
||||||
*capacity = 0;
|
*capacity = 0;
|
||||||
if (position != NULL)
|
if (position != NULL)
|
||||||
*position = 0;
|
*position = 0;
|
||||||
} else {
|
} else {
|
||||||
|
if (type != NULL)
|
||||||
|
*type = sim->vsu.out.type;
|
||||||
if (capacity != NULL)
|
if (capacity != NULL)
|
||||||
*capacity = sim->vsu.out.capacity;
|
*capacity = sim->vsu.out.capacity;
|
||||||
if (position != NULL)
|
if (position != NULL)
|
||||||
@@ -572,8 +687,8 @@ VBAPI vbOnSamples vbGetSamplesCallback(VB *sim) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Retrieve the value in a system register */
|
/* Retrieve the value in a system register */
|
||||||
VBAPI uint32_t vbGetSystemRegister(VB *sim, int index) {
|
VBAPI uint32_t vbGetSystemRegister(VB *sim, unsigned index) {
|
||||||
return index < 0 || index > 31 ? 0 : cpuGetSystemRegister(sim, index);
|
return index > 31 ? 0 : cpuGetSystemRegister(sim, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Retrieve a simulation's userdata pointer */
|
/* Retrieve a simulation's userdata pointer */
|
||||||
@@ -590,14 +705,17 @@ VBAPI vbOnWrite vbGetWriteCallback(VB *sim) {
|
|||||||
VBAPI VB* vbInit(VB *sim) {
|
VBAPI VB* vbInit(VB *sim) {
|
||||||
sim->cart.ram = NULL;
|
sim->cart.ram = NULL;
|
||||||
sim->cart.rom = NULL;
|
sim->cart.rom = NULL;
|
||||||
|
sim->ph.enabled = 0;
|
||||||
sim->vsu.out.samples = NULL;
|
sim->vsu.out.samples = NULL;
|
||||||
sim->onExecute = NULL;
|
sim->onExecute = NULL;
|
||||||
sim->onFetch = NULL;
|
sim->onFetch = NULL;
|
||||||
sim->onFrame = NULL;
|
sim->onFrame = NULL;
|
||||||
|
sim->onLink = NULL;
|
||||||
sim->onRead = NULL;
|
sim->onRead = NULL;
|
||||||
sim->onSamples = NULL;
|
sim->onSamples = NULL;
|
||||||
sim->onWrite = NULL;
|
sim->onWrite = NULL;
|
||||||
sim->ph.enabled = 0;
|
sim->peer = NULL;
|
||||||
|
sim->tag = NULL;
|
||||||
vbReset(sim);
|
vbReset(sim);
|
||||||
return sim;
|
return sim;
|
||||||
}
|
}
|
||||||
@@ -616,7 +734,8 @@ VBAPI VB* vbReset(VB *sim) {
|
|||||||
int x; /* Iterator */
|
int x; /* Iterator */
|
||||||
|
|
||||||
/* Wait controller */
|
/* Wait controller */
|
||||||
sim->wcr = 0x00;
|
sim->wcr.exp1w = 0;
|
||||||
|
sim->wcr.rom1w = 0;
|
||||||
|
|
||||||
/* WRAM (the hardware does not do this) */
|
/* WRAM (the hardware does not do this) */
|
||||||
for (x = 0; x < 0x10000; x++)
|
for (x = 0; x < 0x10000; x++)
|
||||||
@@ -624,6 +743,7 @@ VBAPI VB* vbReset(VB *sim) {
|
|||||||
|
|
||||||
/* Components */
|
/* Components */
|
||||||
cpuReset(sim);
|
cpuReset(sim);
|
||||||
|
extReset(sim);
|
||||||
padReset(sim);
|
padReset(sim);
|
||||||
tmrReset(sim);
|
tmrReset(sim);
|
||||||
vipReset(sim);
|
vipReset(sim);
|
||||||
@@ -689,6 +809,13 @@ VBAPI uint16_t vbSetKeys(VB *sim, uint16_t keys) {
|
|||||||
return sim->pad.keys = keys;
|
return sim->pad.keys = keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Specify a new link callback handler */
|
||||||
|
VBAPI vbOnLink vbSetLinkCallback(VB *sim, vbOnLink callback) {
|
||||||
|
vbOnLink prev = sim->onLink;
|
||||||
|
sim->onLink = callback;
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
/* Specify a new core option value */
|
/* Specify a new core option value */
|
||||||
VBAPI int vbSetOption(VB *sim, int key, int value) {
|
VBAPI int vbSetOption(VB *sim, int key, int value) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -706,8 +833,23 @@ VBAPI int vbSetOption(VB *sim, int key, int value) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Specify a new communication peer */
|
||||||
|
VBAPI void vbSetPeer(VB *sim, VB *peer) {
|
||||||
|
if (sim->peer == peer)
|
||||||
|
return;
|
||||||
|
if (sim->peer != NULL)
|
||||||
|
sim->peer->peer = NULL;
|
||||||
|
sim->peer = peer;
|
||||||
|
if (peer == NULL)
|
||||||
|
return;
|
||||||
|
if (peer->peer != NULL)
|
||||||
|
peer->peer->peer = NULL;
|
||||||
|
peer->peer = sim;
|
||||||
|
}
|
||||||
|
|
||||||
/* Specify a new value for the program counter */
|
/* Specify a new value for the program counter */
|
||||||
VBAPI uint32_t vbSetProgramCounter(VB *sim, uint32_t value) {
|
VBAPI uint32_t vbSetProgramCounter(VB *sim, uint32_t value) {
|
||||||
|
sim->cpu.clocks = 0;
|
||||||
sim->cpu.operation = CPU_FETCH;
|
sim->cpu.operation = CPU_FETCH;
|
||||||
sim->cpu.pc = sim->cpu.nextPC = value & 0xFFFFFFFE;
|
sim->cpu.pc = sim->cpu.nextPC = value & 0xFFFFFFFE;
|
||||||
sim->cpu.step = 0;
|
sim->cpu.step = 0;
|
||||||
@@ -715,7 +857,7 @@ VBAPI uint32_t vbSetProgramCounter(VB *sim, uint32_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Specify a new value for a program register */
|
/* Specify a new value for a program register */
|
||||||
VBAPI int32_t vbSetProgramRegister(VB *sim, int index, int32_t value) {
|
VBAPI int32_t vbSetProgramRegister(VB *sim, unsigned index, int32_t value) {
|
||||||
return index < 1 || index > 31 ? 0 : (sim->cpu.program[index] = value);
|
return index < 1 || index > 31 ? 0 : (sim->cpu.program[index] = value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,12 +869,14 @@ VBAPI vbOnRead vbSetReadCallback(VB *sim, vbOnRead callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Specify a new audio samples buffer */
|
/* Specify a new audio samples buffer */
|
||||||
VBAPI int vbSetSamples(VB *sim, void *samples, uint32_t capacity) {
|
VBAPI int vbSetSamples(VB *sim, void *samples, int type, unsigned capacity) {
|
||||||
if (samples != NULL && (capacity == 0 || capacity > 0x40000000))
|
if (samples != NULL &&
|
||||||
|
(capacity == 0 || (type != VB_S16 && type != VB_F32)))
|
||||||
return 1;
|
return 1;
|
||||||
sim->vsu.out.capacity = samples == NULL ? 0 : capacity;
|
sim->vsu.out.capacity = capacity;
|
||||||
sim->vsu.out.offset = 0;
|
sim->vsu.out.offset = 0;
|
||||||
sim->vsu.out.samples = samples;
|
sim->vsu.out.samples = samples;
|
||||||
|
sim->vsu.out.type = type;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,9 +888,8 @@ VBAPI vbOnSamples vbSetSamplesCallback(VB *sim, vbOnSamples callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Specify a new value for a system register */
|
/* Specify a new value for a system register */
|
||||||
VBAPI uint32_t vbSetSystemRegister(VB *sim, int index, uint32_t value) {
|
VBAPI uint32_t vbSetSystemRegister(VB *sim, unsigned index, uint32_t value) {
|
||||||
return index < 0 || index > 31 ? 0 :
|
return index > 31 ? 0 : cpuSetSystemRegister(sim, index, value, 1);
|
||||||
cpuSetSystemRegister(sim, index, value, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Specify a new write callback handler */
|
/* Specify a new write callback handler */
|
||||||
@@ -757,7 +900,7 @@ VBAPI vbOnWrite vbSetWriteCallback(VB *sim, vbOnWrite callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Determine the size of a simulation instance */
|
/* Determine the size of a simulation instance */
|
||||||
VBAPI size_t vbSizeOf() {
|
VBAPI size_t vbSizeOf(void) {
|
||||||
return sizeof (VB);
|
return sizeof (VB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ extern "C" {
|
|||||||
VB_DIRECT_EXECUTE
|
VB_DIRECT_EXECUTE
|
||||||
VB_DIRECT_FETCH
|
VB_DIRECT_FETCH
|
||||||
VB_DIRECT_FRAME
|
VB_DIRECT_FRAME
|
||||||
|
VB_DIRECT_LINK
|
||||||
VB_DIRECT_READ
|
VB_DIRECT_READ
|
||||||
VB_DIRECT_SAMPLES
|
VB_DIRECT_SAMPLES
|
||||||
VB_DIRECT_WRITE
|
VB_DIRECT_WRITE
|
||||||
@@ -78,10 +79,29 @@ extern "C" {
|
|||||||
#define VB_S16 2
|
#define VB_S16 2
|
||||||
#define VB_U16 3
|
#define VB_U16 3
|
||||||
#define VB_S32 4
|
#define VB_S32 4
|
||||||
|
#define VB_F32 5
|
||||||
|
|
||||||
/* Option keys */
|
/* Option keys */
|
||||||
#define VB_PSEUDO_HALT 0
|
#define VB_PSEUDO_HALT 0
|
||||||
|
|
||||||
|
/* Controller buttons */
|
||||||
|
#define VB_PWR 0x0001
|
||||||
|
#define VB_SGN 0x0002
|
||||||
|
#define VB_A 0x0004
|
||||||
|
#define VB_B 0x0008
|
||||||
|
#define VB_RT 0x0010
|
||||||
|
#define VB_LT 0x0020
|
||||||
|
#define VB_RU 0x0040
|
||||||
|
#define VB_RR 0x0080
|
||||||
|
#define VB_LR 0x0100
|
||||||
|
#define VB_LL 0x0200
|
||||||
|
#define VB_LD 0x0400
|
||||||
|
#define VB_LU 0x0800
|
||||||
|
#define VB_STA 0x1000
|
||||||
|
#define VB_SEL 0x2000
|
||||||
|
#define VB_RL 0x4000
|
||||||
|
#define VB_RD 0x8000
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*********************************** Types ***********************************/
|
/*********************************** Types ***********************************/
|
||||||
@@ -94,6 +114,7 @@ typedef int (*vbOnException)(VB *sim, uint16_t *cause);
|
|||||||
typedef int (*vbOnExecute )(VB *sim, uint32_t address, const uint16_t *code, int length);
|
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 (*vbOnFetch )(VB *sim, int fetch, uint32_t address, int32_t *value, uint32_t *cycles);
|
||||||
typedef int (*vbOnFrame )(VB *sim);
|
typedef int (*vbOnFrame )(VB *sim);
|
||||||
|
typedef int (*vbOnLink )(VB *sim1, VB *sim2, uint8_t *value1, uint8_t *value2);
|
||||||
typedef int (*vbOnRead )(VB *sim, uint32_t address, int type, int32_t *value, uint32_t *cycles);
|
typedef int (*vbOnRead )(VB *sim, uint32_t address, int type, int32_t *value, uint32_t *cycles);
|
||||||
typedef void (*vbOnSamples )(VB *sim, int16_t *buffer, uint32_t capacity);
|
typedef void (*vbOnSamples )(VB *sim, int16_t *buffer, uint32_t capacity);
|
||||||
typedef int (*vbOnWrite )(VB *sim, uint32_t address, int type, int32_t *value, uint32_t *cycles, int *cancel);
|
typedef int (*vbOnWrite )(VB *sim, uint32_t address, int type, int32_t *value, uint32_t *cycles, int *cancel);
|
||||||
@@ -104,8 +125,7 @@ typedef int (*vbOnWrite )(VB *sim, uint32_t address, int type, int32_t *valu
|
|||||||
/******************************* API Commands ********************************/
|
/******************************* API Commands ********************************/
|
||||||
|
|
||||||
VBAPI int vbEmulate (VB *sim, uint32_t *clocks);
|
VBAPI int vbEmulate (VB *sim, uint32_t *clocks);
|
||||||
VBAPI int vbEmulateEx (VB **sims, int count, uint32_t *clocks);
|
VBAPI int vbEmulateEx (VB **sims, unsigned count, uint32_t *clocks);
|
||||||
VBAPI void* vbGetCallback (VB *sim, int id);
|
|
||||||
VBAPI void* vbGetCartRAM (VB *sim, uint32_t *size);
|
VBAPI void* vbGetCartRAM (VB *sim, uint32_t *size);
|
||||||
VBAPI void* vbGetCartROM (VB *sim, uint32_t *size);
|
VBAPI void* vbGetCartROM (VB *sim, uint32_t *size);
|
||||||
VBAPI vbOnException vbGetExceptionCallback(VB *sim);
|
VBAPI vbOnException vbGetExceptionCallback(VB *sim);
|
||||||
@@ -113,14 +133,16 @@ VBAPI vbOnExecute vbGetExecuteCallback (VB *sim);
|
|||||||
VBAPI vbOnFetch vbGetFetchCallback (VB *sim);
|
VBAPI vbOnFetch vbGetFetchCallback (VB *sim);
|
||||||
VBAPI vbOnFrame vbGetFrameCallback (VB *sim);
|
VBAPI vbOnFrame vbGetFrameCallback (VB *sim);
|
||||||
VBAPI uint16_t vbGetKeys (VB *sim);
|
VBAPI uint16_t vbGetKeys (VB *sim);
|
||||||
|
VBAPI vbOnLink vbGetLinkCallback (VB *sim);
|
||||||
VBAPI int vbGetOption (VB *sim, int key);
|
VBAPI int vbGetOption (VB *sim, int key);
|
||||||
|
VBAPI VB* vbGetPeer (VB *sim);
|
||||||
VBAPI void vbGetPixels (VB *sim, void *left, int leftStrideX, int leftStrideY, void *right, int rightStrideX, int rightStrideY);
|
VBAPI void vbGetPixels (VB *sim, void *left, int leftStrideX, int leftStrideY, void *right, int rightStrideX, int rightStrideY);
|
||||||
VBAPI uint32_t vbGetProgramCounter (VB *sim);
|
VBAPI uint32_t vbGetProgramCounter (VB *sim);
|
||||||
VBAPI int32_t vbGetProgramRegister (VB *sim, int index);
|
VBAPI int32_t vbGetProgramRegister (VB *sim, unsigned index);
|
||||||
VBAPI vbOnRead vbGetReadCallback (VB *sim);
|
VBAPI vbOnRead vbGetReadCallback (VB *sim);
|
||||||
VBAPI void* vbGetSamples (VB *sim, uint32_t *capacity, uint32_t *position);
|
VBAPI void* vbGetSamples (VB *sim, int *type, unsigned *capacity, unsigned *position);
|
||||||
VBAPI vbOnSamples vbGetSamplesCallback (VB *sim);
|
VBAPI vbOnSamples vbGetSamplesCallback (VB *sim);
|
||||||
VBAPI uint32_t vbGetSystemRegister (VB *sim, int index);
|
VBAPI uint32_t vbGetSystemRegister (VB *sim, unsigned index);
|
||||||
VBAPI void* vbGetUserData (VB *sim);
|
VBAPI void* vbGetUserData (VB *sim);
|
||||||
VBAPI vbOnWrite vbGetWriteCallback (VB *sim);
|
VBAPI vbOnWrite vbGetWriteCallback (VB *sim);
|
||||||
VBAPI VB* vbInit (VB *sim);
|
VBAPI VB* vbInit (VB *sim);
|
||||||
@@ -132,17 +154,19 @@ VBAPI vbOnException vbSetExceptionCallback(VB *sim, vbOnException callback);
|
|||||||
VBAPI vbOnExecute vbSetExecuteCallback (VB *sim, vbOnExecute callback);
|
VBAPI vbOnExecute vbSetExecuteCallback (VB *sim, vbOnExecute callback);
|
||||||
VBAPI vbOnFetch vbSetFetchCallback (VB *sim, vbOnFetch callback);
|
VBAPI vbOnFetch vbSetFetchCallback (VB *sim, vbOnFetch callback);
|
||||||
VBAPI vbOnFrame vbSetFrameCallback (VB *sim, vbOnFrame callback);
|
VBAPI vbOnFrame vbSetFrameCallback (VB *sim, vbOnFrame callback);
|
||||||
|
VBAPI vbOnLink vbSetLinkCallback (VB *sim, vbOnLink callback);
|
||||||
VBAPI uint16_t vbSetKeys (VB *sim, uint16_t keys);
|
VBAPI uint16_t vbSetKeys (VB *sim, uint16_t keys);
|
||||||
VBAPI int vbSetOption (VB *sim, int key, int value);
|
VBAPI int vbSetOption (VB *sim, int key, int value);
|
||||||
|
VBAPI void vbSetPeer (VB *sim, VB *peer);
|
||||||
VBAPI uint32_t vbSetProgramCounter (VB *sim, uint32_t value);
|
VBAPI uint32_t vbSetProgramCounter (VB *sim, uint32_t value);
|
||||||
VBAPI int32_t vbSetProgramRegister (VB *sim, int index, int32_t value);
|
VBAPI int32_t vbSetProgramRegister (VB *sim, unsigned index, int32_t value);
|
||||||
VBAPI vbOnRead vbSetReadCallback (VB *sim, vbOnRead callback);
|
VBAPI vbOnRead vbSetReadCallback (VB *sim, vbOnRead callback);
|
||||||
VBAPI int vbSetSamples (VB *sim, void *samples, uint32_t capacity);
|
VBAPI int vbSetSamples (VB *sim, void *samples, int type, unsigned capacity);
|
||||||
VBAPI vbOnSamples vbSetSamplesCallback (VB *sim, vbOnSamples callback);
|
VBAPI vbOnSamples vbSetSamplesCallback (VB *sim, vbOnSamples callback);
|
||||||
VBAPI uint32_t vbSetSystemRegister (VB *sim, int index, uint32_t value);
|
VBAPI uint32_t vbSetSystemRegister (VB *sim, unsigned index, uint32_t value);
|
||||||
VBAPI void* vbSetUserData (VB *sim, void *tag);
|
VBAPI void* vbSetUserData (VB *sim, void *tag);
|
||||||
VBAPI vbOnWrite vbSetWriteCallback (VB *sim, vbOnWrite callback);
|
VBAPI vbOnWrite vbSetWriteCallback (VB *sim, vbOnWrite callback);
|
||||||
VBAPI size_t vbSizeOf ();
|
VBAPI size_t vbSizeOf (void);
|
||||||
VBAPI int32_t vbWrite (VB *sim, uint32_t address, int type, int32_t value);
|
VBAPI int32_t vbWrite (VB *sim, uint32_t address, int type, int32_t value);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+622
-289
File diff suppressed because it is too large
Load Diff
+184
-85
@@ -45,9 +45,28 @@ static void vsuOnSamples(VB *sim) {
|
|||||||
|
|
||||||
/***************************** Module Functions ******************************/
|
/***************************** Module Functions ******************************/
|
||||||
|
|
||||||
|
/* Determine while frequency modifications should be processed */
|
||||||
|
static uint8_t vsuCheckFreqMod(VB *sim) {
|
||||||
|
return
|
||||||
|
sim->vsu.freqmod.enb &&
|
||||||
|
sim->vsu.freqmod.interval != 0 && (
|
||||||
|
sim->vsu.freqmod.modmask != 2 || (
|
||||||
|
sim->vsu.freqmod.func == 1 &&
|
||||||
|
sim->vsu.freqmod.rep
|
||||||
|
)
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
/* Stage the next frequency modification value */
|
/* Stage the next frequency modification value */
|
||||||
static void vsuNextFreqMod(VB *sim, Channel *chan) {
|
static void vsuNextFreqMod(VB *sim, Channel *chan) {
|
||||||
uint16_t next;
|
int32_t next;
|
||||||
|
|
||||||
|
/* Modulation mask control */
|
||||||
|
if (sim->vsu.freqmod.modmask == 1 && sim->vsu.freqmod.func == 1) {
|
||||||
|
sim->vsu.freqmod.modmask = 2;
|
||||||
|
chan->freqmod = vsuCheckFreqMod(sim);
|
||||||
|
}
|
||||||
|
|
||||||
/* Sweep */
|
/* Sweep */
|
||||||
if (sim->vsu.freqmod.func == 0) {
|
if (sim->vsu.freqmod.func == 0) {
|
||||||
@@ -57,66 +76,37 @@ static void vsuNextFreqMod(VB *sim, Channel *chan) {
|
|||||||
else next = chan->freq.current + next;
|
else next = chan->freq.current + next;
|
||||||
if (next > 2047)
|
if (next > 2047)
|
||||||
chan->int_.enb = 0;
|
chan->int_.enb = 0;
|
||||||
|
if (next < 0)
|
||||||
|
next = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modulation */
|
/* Modulation */
|
||||||
else {
|
else {
|
||||||
next = (chan->freq.written +
|
next = ((int32_t) chan->freq.written +
|
||||||
sim->vsu.modulation[sim->vsu.freqmod.sample]) & 2047;
|
sim->vsu.modulation[sim->vsu.freqmod.sample]) & 0x7FF;
|
||||||
if (sim->vsu.freqmod.sample != 31)
|
if (sim->vsu.freqmod.freqmask == 1)
|
||||||
sim->vsu.freqmod.sample++;
|
next = (next & 0x700) | (chan->freq.written & 0x0FF);
|
||||||
else if (sim->vsu.freqmod.rep)
|
else if (sim->vsu.freqmod.freqmask == 2)
|
||||||
sim->vsu.freqmod.sample = 0;
|
next = (next & 0x0FF) | (chan->freq.written & 0x700);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Configure state */
|
/* Configure state */
|
||||||
|
if (chan->freqmod)
|
||||||
sim->vsu.freqmod.next = next;
|
sim->vsu.freqmod.next = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Advance the noise channel's shift register */
|
||||||
|
static void vsuNextNoise(VB *sim) {
|
||||||
|
uint32_t bit = ((
|
||||||
|
sim->vsu.noise.register_ >> NOISE_TAPS[sim->vsu.noise.tap] ^
|
||||||
|
sim->vsu.noise.register_ >> 7
|
||||||
|
) & 1) ^ 1;
|
||||||
|
sim->vsu.noise.register_ = bit |
|
||||||
|
(sim->vsu.noise.register_ << 1 & 0x7FFE);
|
||||||
|
}
|
||||||
|
|
||||||
/* Process one channel */
|
/* Process one channel */
|
||||||
static void vsuEmulateChannel(VB *sim, int index, uint32_t clocks) {
|
static void vsuEmulateChannel(VB *sim, Channel *chan) {
|
||||||
uint32_t bit; /* Pseudorandom bit */
|
|
||||||
Channel *chan; /* Channel handle */
|
|
||||||
int freqmod; /* Frequency modifications enabled */
|
|
||||||
uint32_t until; /* Clocks to process sub-channel components */
|
|
||||||
|
|
||||||
/* Select channel */
|
|
||||||
chan = &sim->vsu.channels[index];
|
|
||||||
|
|
||||||
/* Channel is disabled */
|
|
||||||
if (!chan->int_.enb)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Process all clocks */
|
|
||||||
do {
|
|
||||||
|
|
||||||
/* Frequency modifications are active */
|
|
||||||
freqmod =
|
|
||||||
index == 4 && /* Channel 5 */
|
|
||||||
sim->vsu.freqmod.enb && /* Modifications enabled */
|
|
||||||
sim->vsu.freqmod.interval != 0 /* Modifications valid */
|
|
||||||
;
|
|
||||||
|
|
||||||
/* Clocks until next state change */
|
|
||||||
until = clocks;
|
|
||||||
if (chan->clocks < until)
|
|
||||||
until = chan->clocks;
|
|
||||||
if (chan->env.enb && chan->env.clocks < until)
|
|
||||||
until = chan->env.clocks;
|
|
||||||
if (chan->int_.auto_ && chan->int_.clocks < until)
|
|
||||||
until = chan->int_.clocks;
|
|
||||||
if (freqmod && sim->vsu.freqmod.clocks < until)
|
|
||||||
until = sim->vsu.freqmod.clocks;
|
|
||||||
|
|
||||||
/* Manage clocks */
|
|
||||||
clocks -= until;
|
|
||||||
chan->clocks -= until;
|
|
||||||
if (chan->env.enb)
|
|
||||||
chan->env.clocks -= until;
|
|
||||||
if (chan->int_.auto_)
|
|
||||||
chan->int_.clocks -= until;
|
|
||||||
if (freqmod)
|
|
||||||
sim->vsu.freqmod.clocks -= until;
|
|
||||||
|
|
||||||
/* Automatic shutoff */
|
/* Automatic shutoff */
|
||||||
if (chan->int_.auto_ && chan->int_.clocks == 0) {
|
if (chan->int_.auto_ && chan->int_.clocks == 0) {
|
||||||
@@ -124,11 +114,11 @@ static void vsuEmulateChannel(VB *sim, int index, uint32_t clocks) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Next sample */
|
/* Next input sample */
|
||||||
if (chan->clocks == 0) {
|
if (chan->clocks == 0) {
|
||||||
|
|
||||||
/* Wave */
|
/* Wave */
|
||||||
if (index != 5) {
|
if (chan != &sim->vsu.channels[5]) {
|
||||||
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
|
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
|
||||||
chan->wave.sample = (chan->wave.sample + 1) & 31;
|
chan->wave.sample = (chan->wave.sample + 1) & 31;
|
||||||
}
|
}
|
||||||
@@ -136,38 +126,61 @@ static void vsuEmulateChannel(VB *sim, int index, uint32_t clocks) {
|
|||||||
/* Noise */
|
/* Noise */
|
||||||
else {
|
else {
|
||||||
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
|
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
|
||||||
bit = ((
|
vsuNextNoise(sim);
|
||||||
sim->vsu.noise.register_ >> NOISE_TAPS[sim->vsu.noise.tap]^
|
|
||||||
sim->vsu.noise.register_ >> 7
|
|
||||||
) & 1) ^ 1;
|
|
||||||
sim->vsu.noise.register_ = bit |
|
|
||||||
(sim->vsu.noise.register_ << 1 & 0x7FFE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Envelope modification */
|
/* Envelope modification */
|
||||||
if (chan->env.enb && chan->env.clocks == 0) {
|
if (chan->env.clocks == 0) {
|
||||||
|
uint8_t new_envelope = chan->env.value;
|
||||||
if (chan->env.dir == 0 && chan->env.value != 0)
|
if (chan->env.dir == 0 && chan->env.value != 0)
|
||||||
chan->env.value--;
|
new_envelope--;
|
||||||
else if (chan->env.dir == 1 && chan->env.value != 15)
|
else if (chan->env.dir == 1 && chan->env.value != 15)
|
||||||
chan->env.value++;
|
new_envelope++;
|
||||||
else if (chan->env.rep)
|
else if (chan->env.rep && chan->env.modmask != 2) {
|
||||||
chan->env.value = chan->env.reload;
|
new_envelope = chan->env.reload;
|
||||||
|
chan->env.modmask = 0;
|
||||||
|
} else if (!chan->env.modmask)
|
||||||
|
chan->env.modmask = 1;
|
||||||
|
if (chan->env.enb && !chan->env.modmask)
|
||||||
|
chan->env.value = new_envelope;
|
||||||
chan->env.clocks = ((uint32_t) chan->env.interval + 1) * 307220;
|
chan->env.clocks = ((uint32_t) chan->env.interval + 1) * 307220;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Frequency modification */
|
/* Frequency modification */
|
||||||
if (freqmod && sim->vsu.freqmod.clocks == 0) {
|
if (chan == &sim->vsu.channels[4] && sim->vsu.freqmod.clocks == 0) {
|
||||||
|
|
||||||
|
/* Apply previously computed frequency modification */
|
||||||
|
if (chan->freqmod)
|
||||||
chan->freq.current = sim->vsu.freqmod.next;
|
chan->freq.current = sim->vsu.freqmod.next;
|
||||||
vsuNextFreqMod(sim, chan);
|
|
||||||
|
/* Channel itself is disabled */
|
||||||
if (!chan->int_.enb)
|
if (!chan->int_.enb)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/* Frequency modifications are enabled */
|
||||||
|
if (chan->freqmod) {
|
||||||
|
|
||||||
|
/* Not the final modulation sample */
|
||||||
|
if (sim->vsu.freqmod.sample != 31)
|
||||||
|
sim->vsu.freqmod.sample++;
|
||||||
|
|
||||||
|
/* Last modulation sample processed */
|
||||||
|
else {
|
||||||
|
sim->vsu.freqmod.modmask = 1;
|
||||||
|
if (sim->vsu.freqmod.rep)
|
||||||
|
sim->vsu.freqmod.sample = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Schedule next modification */
|
||||||
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
|
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
|
||||||
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
|
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (clocks != 0);
|
/* Always process and validate next frequency value */
|
||||||
|
vsuNextFreqMod(sim, chan);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +220,11 @@ static void vsuWriteEV0(VB *sim, int index, uint8_t value) {
|
|||||||
chan->env.interval = value & 7;
|
chan->env.interval = value & 7;
|
||||||
chan->env.value = value >> 4 & 15;
|
chan->env.value = value >> 4 & 15;
|
||||||
chan->env.reload = chan->env.value;
|
chan->env.reload = chan->env.value;
|
||||||
if (index == 4)
|
if (chan->env.modmask == 1) chan->env.modmask = 2;
|
||||||
|
if (index == 4) {
|
||||||
|
chan->freqmod = vsuCheckFreqMod(sim);
|
||||||
vsuNextFreqMod(sim, chan);
|
vsuNextFreqMod(sim, chan);
|
||||||
|
}
|
||||||
|
|
||||||
/* Configure state */
|
/* Configure state */
|
||||||
chan->env.clocks = 307220 * ((uint32_t) chan->env.interval + 1);
|
chan->env.clocks = 307220 * ((uint32_t) chan->env.interval + 1);
|
||||||
@@ -221,6 +237,12 @@ static void vsuWriteEV1(VB *sim, int index, uint8_t value) {
|
|||||||
/* Parse fields */
|
/* Parse fields */
|
||||||
chan->env.enb = value & 1;
|
chan->env.enb = value & 1;
|
||||||
chan->env.rep = value >> 1 & 1;
|
chan->env.rep = value >> 1 & 1;
|
||||||
|
if (
|
||||||
|
!chan->env.rep && (
|
||||||
|
(chan->env.reload == 0 && chan->env.dir == 0) ||
|
||||||
|
(chan->env.reload == 15 && chan->env.dir == 1)
|
||||||
|
)
|
||||||
|
) chan->env.modmask = 1;
|
||||||
|
|
||||||
/* Processing by channel */
|
/* Processing by channel */
|
||||||
switch (index) {
|
switch (index) {
|
||||||
@@ -229,12 +251,14 @@ static void vsuWriteEV1(VB *sim, int index, uint8_t value) {
|
|||||||
sim->vsu.freqmod.enb = value >> 6 & 1;
|
sim->vsu.freqmod.enb = value >> 6 & 1;
|
||||||
sim->vsu.freqmod.func = value >> 4 & 1;
|
sim->vsu.freqmod.func = value >> 4 & 1;
|
||||||
sim->vsu.freqmod.rep = value >> 5 & 1;
|
sim->vsu.freqmod.rep = value >> 5 & 1;
|
||||||
|
chan->freqmod = vsuCheckFreqMod(sim);
|
||||||
vsuNextFreqMod(sim, chan);
|
vsuNextFreqMod(sim, chan);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 5: /* Channel 6 */
|
case 5: /* Channel 6 */
|
||||||
sim->vsu.noise.tap = value >> 4 & 7;
|
sim->vsu.noise.tap = value >> 4 & 7;
|
||||||
sim->vsu.noise.register_ = 0x0000;
|
sim->vsu.noise.register_ = 0x0000;
|
||||||
|
vsuNextNoise(sim);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -245,7 +269,9 @@ static void vsuWriteFQH(VB *sim, int index, uint16_t value) {
|
|||||||
value = value << 8 & 0x0700;
|
value = value << 8 & 0x0700;
|
||||||
chan->freq.current = (chan->freq.current & 0x00FF) | value;
|
chan->freq.current = (chan->freq.current & 0x00FF) | value;
|
||||||
chan->freq.written = (chan->freq.written & 0x00FF) | value;
|
chan->freq.written = (chan->freq.written & 0x00FF) | value;
|
||||||
if (index == 4)
|
if (index != 4)
|
||||||
|
return;
|
||||||
|
sim->vsu.freqmod.freqmask = 2;
|
||||||
vsuNextFreqMod(sim, chan);
|
vsuNextFreqMod(sim, chan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +280,9 @@ static void vsuWriteFQL(VB *sim, int index, uint8_t value) {
|
|||||||
Channel *chan = &sim->vsu.channels[index];
|
Channel *chan = &sim->vsu.channels[index];
|
||||||
chan->freq.current = (chan->freq.current & 0x0700) | value;
|
chan->freq.current = (chan->freq.current & 0x0700) | value;
|
||||||
chan->freq.written = (chan->freq.written & 0x0700) | value;
|
chan->freq.written = (chan->freq.written & 0x0700) | value;
|
||||||
if (index == 4)
|
if (index != 4)
|
||||||
|
return;
|
||||||
|
sim->vsu.freqmod.freqmask = 1;
|
||||||
vsuNextFreqMod(sim, chan);
|
vsuNextFreqMod(sim, chan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,19 +297,29 @@ static void vsuWriteINT(VB *sim, int index, uint8_t value) {
|
|||||||
|
|
||||||
/* Update state */
|
/* Update state */
|
||||||
chan->int_.clocks = 76805 * ((uint32_t) chan->int_.interval + 1);
|
chan->int_.clocks = 76805 * ((uint32_t) chan->int_.interval + 1);
|
||||||
if (chan->env.enb)
|
chan->env.modmask = 0;
|
||||||
|
if (
|
||||||
|
!chan->env.rep && (
|
||||||
|
(chan->env.reload == 0 && chan->env.dir == 0) ||
|
||||||
|
(chan->env.reload == 15 && chan->env.dir == 1)
|
||||||
|
)
|
||||||
|
) chan->env.modmask = 1;
|
||||||
chan->env.clocks = 307220 * ((uint32_t) chan->env.interval + 1);
|
chan->env.clocks = 307220 * ((uint32_t) chan->env.interval + 1);
|
||||||
if (index != 5) {
|
if (index != 5) {
|
||||||
chan->wave.sample = 0;
|
chan->wave.sample = 0;
|
||||||
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
|
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
|
||||||
} else {
|
} else {
|
||||||
sim->vsu.noise.register_ = 0x0000;
|
sim->vsu.noise.register_ = 0x0000;
|
||||||
|
vsuNextNoise(sim);
|
||||||
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
|
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
|
||||||
}
|
}
|
||||||
if (index == 4) {
|
if (index == 4) {
|
||||||
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
|
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
|
||||||
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
|
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
|
||||||
|
sim->vsu.freqmod.modmask = 0;
|
||||||
sim->vsu.freqmod.sample = 0;
|
sim->vsu.freqmod.sample = 0;
|
||||||
|
chan->freqmod = vsuCheckFreqMod(sim);
|
||||||
|
vsuNextFreqMod(sim, chan);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -313,6 +351,8 @@ static void vsuWriteSWP(VB *sim, uint8_t value) {
|
|||||||
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
|
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
|
||||||
if (clocks < sim->vsu.freqmod.clocks)
|
if (clocks < sim->vsu.freqmod.clocks)
|
||||||
sim->vsu.freqmod.clocks = clocks;
|
sim->vsu.freqmod.clocks = clocks;
|
||||||
|
sim->vsu.channels[4].freqmod = vsuCheckFreqMod(sim);
|
||||||
|
vsuNextFreqMod(sim, &sim->vsu.channels[4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -321,6 +361,8 @@ static void vsuWriteSWP(VB *sim, uint8_t value) {
|
|||||||
|
|
||||||
/* Process component */
|
/* Process component */
|
||||||
static void vsuEmulate(VB *sim, uint32_t clocks) {
|
static void vsuEmulate(VB *sim, uint32_t clocks) {
|
||||||
|
Channel *chan; /* Input channel */
|
||||||
|
uint32_t chantil; /* Clocks until next channel state update */
|
||||||
float i0; /* Current analog input sample */
|
float i0; /* Current analog input sample */
|
||||||
float o0; /* Current analog output sample */
|
float o0; /* Current analog output sample */
|
||||||
uint16_t output[2]; /* Digital output samples */
|
uint16_t output[2]; /* Digital output samples */
|
||||||
@@ -340,12 +382,55 @@ static void vsuEmulate(VB *sim, uint32_t clocks) {
|
|||||||
sim->vsu.clocks -= until;
|
sim->vsu.clocks -= until;
|
||||||
|
|
||||||
/* Process all channels */
|
/* Process all channels */
|
||||||
for (x = 0; x < 6; x++)
|
for (x = 0; x < 6; x++) {
|
||||||
vsuEmulateChannel(sim, x, until);
|
chan = &sim->vsu.channels[x];
|
||||||
|
chantil = until;
|
||||||
|
|
||||||
|
/* Process all clocks */
|
||||||
|
while (chan->int_.enb && chantil != 0) {
|
||||||
|
|
||||||
|
/* Determine when the next state change will occur */
|
||||||
|
if (chan->until == 0) {
|
||||||
|
|
||||||
|
/* Clocks until next state change */
|
||||||
|
chan->until = chan->clocks;
|
||||||
|
if (chan->env.enb && chan->env.modmask != 2 &&
|
||||||
|
chan->env.clocks < chan->until)
|
||||||
|
chan->until = chan->env.clocks;
|
||||||
|
if (chan->int_.auto_ && chan->int_.clocks < chan->until)
|
||||||
|
chan->until = chan->int_.clocks;
|
||||||
|
if (chan->freqmod && sim->vsu.freqmod.clocks < chan->until)
|
||||||
|
chan->until = sim->vsu.freqmod.clocks;
|
||||||
|
|
||||||
|
/* Manage clocks */
|
||||||
|
chan->clocks -= chan->until;
|
||||||
|
if (chan->env.enb && chan->env.modmask != 2)
|
||||||
|
chan->env.clocks -= chan->until;
|
||||||
|
if (chan->int_.auto_)
|
||||||
|
chan->int_.clocks -= chan->until;
|
||||||
|
if (chan->freqmod)
|
||||||
|
sim->vsu.freqmod.clocks -= chan->until;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Manage clocks */
|
||||||
|
if (chan->until > chantil) {
|
||||||
|
chan->until -= chantil;
|
||||||
|
chantil = 0;
|
||||||
|
} else {
|
||||||
|
chantil -= chan->until;
|
||||||
|
chan->until = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update channel state */
|
||||||
|
if (chan->until == 0)
|
||||||
|
vsuEmulateChannel(sim, chan);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* Wait for the current sample to finish */
|
/* Wait for the current sample to finish */
|
||||||
if (sim->vsu.clocks != 0)
|
if (sim->vsu.clocks != 0)
|
||||||
continue;
|
return;
|
||||||
|
|
||||||
/* Compute the output sample */
|
/* Compute the output sample */
|
||||||
output[0] = output[1] = 0;
|
output[0] = output[1] = 0;
|
||||||
@@ -368,8 +453,21 @@ static void vsuEmulate(VB *sim, uint32_t clocks) {
|
|||||||
sim->vsu.out.samples != NULL &&
|
sim->vsu.out.samples != NULL &&
|
||||||
sim->vsu.out.offset >> 1 < sim->vsu.out.capacity
|
sim->vsu.out.offset >> 1 < sim->vsu.out.capacity
|
||||||
) {
|
) {
|
||||||
sim->vsu.out.samples[sim->vsu.out.offset++] =
|
|
||||||
(int16_t) (o0 * 32767.0f + (o0 < 0.0f ? -0.5f : +0.5f));
|
/* Processing by data type */
|
||||||
|
switch (sim->vsu.out.type) {
|
||||||
|
case VB_S16:
|
||||||
|
((int16_t *) sim->vsu.out.samples)
|
||||||
|
[sim->vsu.out.offset++] = (int16_t)
|
||||||
|
(o0 * 32767.0f + (o0 < 0.0f ? -0.5f : +0.5f));
|
||||||
|
break;
|
||||||
|
case VB_F32:
|
||||||
|
((float *) sim->vsu.out.samples)
|
||||||
|
[sim->vsu.out.offset++] = o0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Invoke the samples callback */
|
||||||
if (
|
if (
|
||||||
e == 1 &&
|
e == 1 &&
|
||||||
sim->vsu.out.offset >> 1 == sim->vsu.out.capacity
|
sim->vsu.out.offset >> 1 == sim->vsu.out.capacity
|
||||||
@@ -403,10 +501,13 @@ static void vsuReset(VB *sim) {
|
|||||||
for (x = 0; x < 6; x++) {
|
for (x = 0; x < 6; x++) {
|
||||||
chan = &sim->vsu.channels[x];
|
chan = &sim->vsu.channels[x];
|
||||||
chan->clocks = 0;
|
chan->clocks = 0;
|
||||||
|
chan->freqmod = 0;
|
||||||
|
chan->until = 0;
|
||||||
chan->env.clocks = 0;
|
chan->env.clocks = 0;
|
||||||
chan->env.enb = 0;
|
chan->env.enb = 0;
|
||||||
chan->env.dir = 0;
|
chan->env.dir = 0;
|
||||||
chan->env.interval = 0;
|
chan->env.interval = 0;
|
||||||
|
chan->env.modmask = 0;
|
||||||
chan->env.reload = 0;
|
chan->env.reload = 0;
|
||||||
chan->env.rep = 0;
|
chan->env.rep = 0;
|
||||||
chan->env.value = 0;
|
chan->env.value = 0;
|
||||||
@@ -427,8 +528,10 @@ static void vsuReset(VB *sim) {
|
|||||||
sim->vsu.freqmod.clocks = 0;
|
sim->vsu.freqmod.clocks = 0;
|
||||||
sim->vsu.freqmod.dir = 0;
|
sim->vsu.freqmod.dir = 0;
|
||||||
sim->vsu.freqmod.enb = 0;
|
sim->vsu.freqmod.enb = 0;
|
||||||
|
sim->vsu.freqmod.freqmask = 0;
|
||||||
sim->vsu.freqmod.func = 0;
|
sim->vsu.freqmod.func = 0;
|
||||||
sim->vsu.freqmod.interval = 0;
|
sim->vsu.freqmod.interval = 0;
|
||||||
|
sim->vsu.freqmod.modmask = 0;
|
||||||
sim->vsu.freqmod.next = 0;
|
sim->vsu.freqmod.next = 0;
|
||||||
sim->vsu.freqmod.rep = 0;
|
sim->vsu.freqmod.rep = 0;
|
||||||
sim->vsu.freqmod.sample = 0;
|
sim->vsu.freqmod.sample = 0;
|
||||||
@@ -445,21 +548,17 @@ static void vsuReset(VB *sim) {
|
|||||||
|
|
||||||
/* Write a typed value to the VSU bus */
|
/* Write a typed value to the VSU bus */
|
||||||
static void vsuWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){
|
static void vsuWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){
|
||||||
|
(void) type;
|
||||||
/* Only byte writes are allowed */
|
|
||||||
switch (type) {
|
|
||||||
case VB_S16:
|
|
||||||
case VB_U16:
|
|
||||||
case VB_S32:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unmapped */
|
/* Unmapped */
|
||||||
if (address & 3)
|
if (address & 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Working variables */
|
/* Working variables */
|
||||||
address &= 0x7FF;
|
address &= 0x7FC;
|
||||||
|
|
||||||
|
/* Clear frequency mask */
|
||||||
|
sim->vsu.freqmod.freqmask = 0;
|
||||||
|
|
||||||
/* Wave memory */
|
/* Wave memory */
|
||||||
if (address < 0x280) {
|
if (address < 0x280) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ help:
|
|||||||
@echo "Available make targets:"
|
@echo "Available make targets:"
|
||||||
@echo " clean Remove output files"
|
@echo " clean Remove output files"
|
||||||
@echo " core Check the C core source for compiler warnings"
|
@echo " core Check the C core source for compiler warnings"
|
||||||
|
@echo " util Check the C utility source for compiler warnings"
|
||||||
@echo " wasm Build the WebAssembly module"
|
@echo " wasm Build the WebAssembly module"
|
||||||
@echo
|
@echo
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ core:
|
|||||||
-D VB_DIRECT_EXCEPTION=textException -D VB_DIRECT_EXECUTE=textExecute \
|
-D VB_DIRECT_EXCEPTION=textException -D VB_DIRECT_EXECUTE=textExecute \
|
||||||
-D VB_DIRECT_FETCH=testFetch -D VB_DIRECT_FRAME=testFrame \
|
-D VB_DIRECT_FETCH=testFetch -D VB_DIRECT_FRAME=testFrame \
|
||||||
-D VB_DIRECT_WRITE=testWrite -D VB_DIRECT_SAMPLES=testSamples \
|
-D VB_DIRECT_WRITE=testWrite -D VB_DIRECT_SAMPLES=testSamples \
|
||||||
-D VB_DIRECT_READ=testRead
|
-D VB_DIRECT_READ=testRead -D VB_DIRECT_LINK=testLink
|
||||||
# Clang generic
|
# Clang generic
|
||||||
@emcc core/vb.c -I core -c -o /dev/null -O3 \
|
@emcc core/vb.c -I core -c -o /dev/null -O3 \
|
||||||
-Werror -std=c90 -Wall -Wextra -Wpedantic -fno-strict-aliasing
|
-Werror -std=c90 -Wall -Wextra -Wpedantic -fno-strict-aliasing
|
||||||
@@ -40,14 +41,32 @@ core:
|
|||||||
-D VB_DIRECT_EXCEPTION=textException -D VB_DIRECT_EXECUTE=textExecute \
|
-D VB_DIRECT_EXCEPTION=textException -D VB_DIRECT_EXECUTE=textExecute \
|
||||||
-D VB_DIRECT_FETCH=testFetch -D VB_DIRECT_FRAME=testFrame \
|
-D VB_DIRECT_FETCH=testFetch -D VB_DIRECT_FRAME=testFrame \
|
||||||
-D VB_DIRECT_WRITE=testWrite -D VB_DIRECT_SAMPLES=testSamples \
|
-D VB_DIRECT_WRITE=testWrite -D VB_DIRECT_SAMPLES=testSamples \
|
||||||
-D VB_DIRECT_READ=testRead
|
-D VB_DIRECT_READ=testRead -D VB_DIRECT_LINK=testLink
|
||||||
|
|
||||||
|
.PHONY: util
|
||||||
|
util:
|
||||||
|
# GCC generic
|
||||||
|
@gcc util/vbu.c -I core -I util -c -o /dev/null -O3 \
|
||||||
|
-Werror -std=c90 -Wall -Wextra -Wpedantic -fno-strict-aliasing
|
||||||
|
# GCC compilation control
|
||||||
|
@gcc util/vbu.c -I core -I util -c -o /dev/null -O3 \
|
||||||
|
-Werror -std=c90 -Wall -Wextra -Wpedantic -fno-strict-aliasing \
|
||||||
|
-D VB_SIGNED_PROPAGATE -D VBU_REALLOC=testRealloc
|
||||||
|
# Clang generic
|
||||||
|
@emcc util/vbu.c -I core -I util -c -o /dev/null -O3 \
|
||||||
|
-Werror -std=c90 -Wall -Wextra -Wpedantic -fno-strict-aliasing
|
||||||
|
# Clang compilation control
|
||||||
|
@emcc util/vbu.c -I core -I util -c -o /dev/null -O3 \
|
||||||
|
-Werror -std=c90 -Wall -Wextra -Wpedantic -fno-strict-aliasing \
|
||||||
|
-D VB_SIGNED_PROPAGATE -D VBU_REALLOC=testRealloc
|
||||||
|
|
||||||
.PHONY: wasm
|
.PHONY: wasm
|
||||||
wasm:
|
wasm:
|
||||||
@emcc -o web/core.wasm web/wasm.c core/vb.c -I core \
|
@emcc -o web/core.wasm web/wasm.c core/vb.c util/vbu.c -I core -I util \
|
||||||
-D VB_LITTLE_ENDIAN -D VB_SIGNED_PROPAGATE \
|
-D VB_LITTLE_ENDIAN -D VB_SIGNED_PROPAGATE \
|
||||||
-D "VBAPI=__attribute__((used))" \
|
-D "VBAPI=__attribute__((used))" -D "VBUAPI=__attribute__((used))" \
|
||||||
--no-entry -O2 -flto -s WASM=1 -s WARN_ON_UNDEFINED_SYMBOLS=0 \
|
-D VB_DIRECT_FRAME=wasmOnFrame \
|
||||||
|
--no-entry -O2 -flto -s WASM=1 \
|
||||||
-s EXPORTED_RUNTIME_METHODS=[] -s ALLOW_MEMORY_GROWTH \
|
-s EXPORTED_RUNTIME_METHODS=[] -s ALLOW_MEMORY_GROWTH \
|
||||||
-s MAXIMUM_MEMORY=4GB -fno-strict-aliasing
|
-s MAXIMUM_MEMORY=4GB -fno-strict-aliasing
|
||||||
@rm -f web/*.wasm.tmp*
|
@rm -f web/*.wasm.tmp*
|
||||||
|
|||||||
@@ -0,0 +1,700 @@
|
|||||||
|
/* This file is included into vbu.c and cannot be compiled on its own. */
|
||||||
|
#ifdef VBUAPI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************* Constants *********************************/
|
||||||
|
|
||||||
|
/* Operand types */
|
||||||
|
#define DASM_BCOND 0
|
||||||
|
#define DASM_DISP9 1
|
||||||
|
#define DASM_DISP26 2
|
||||||
|
#define DASM_IMM5S 3
|
||||||
|
#define DASM_IMM5U 4
|
||||||
|
#define DASM_IMM16S 5
|
||||||
|
#define DASM_IMM16U 6
|
||||||
|
#define DASM_JMP 7
|
||||||
|
#define DASM_MEMORY 8
|
||||||
|
#define DASM_REG1 9
|
||||||
|
#define DASM_REG2 10
|
||||||
|
#define DASM_SETF 11
|
||||||
|
#define DASM_SYSTEM 12
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/******************************** Lookup Data ********************************/
|
||||||
|
|
||||||
|
/* Instruction descriptor */
|
||||||
|
typedef struct {
|
||||||
|
char mnemonic[8]; /* Mnemonic, uppercase */
|
||||||
|
uint8_t operandsLength; /* Number of operands */
|
||||||
|
uint8_t operands[3]; /* Operand types */
|
||||||
|
} OpDef;
|
||||||
|
|
||||||
|
/* Instruction descriptors */
|
||||||
|
static const OpDef OPDEFS[] = {
|
||||||
|
{ "MOV" , 2, { DASM_REG1, DASM_REG2 } }, /* 000000 */
|
||||||
|
{ "ADD" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "SUB" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "CMP" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "SHL" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "SHR" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "JMP" , 1, { DASM_JMP } },
|
||||||
|
{ "SAR" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "MUL" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "DIV" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "MULU" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "DIVU" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "OR" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "AND" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "XOR" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "NOT" , 1, { DASM_REG1 } },
|
||||||
|
|
||||||
|
{ "MOV" , 2, { DASM_IMM5S, DASM_REG2 } }, /* 010000 */
|
||||||
|
{ "ADD" , 2, { DASM_IMM5S, DASM_REG2 } },
|
||||||
|
{ "" , 0, { 0 } }, /* SETF */
|
||||||
|
{ "CMP" , 2, { DASM_IMM5S, DASM_REG2 } },
|
||||||
|
{ "SHL" , 2, { DASM_IMM5U, DASM_REG2 } },
|
||||||
|
{ "SHR" , 2, { DASM_IMM5U, DASM_REG2 } },
|
||||||
|
{ "CLI" , 0, { 0 } },
|
||||||
|
{ "SAR" , 2, { DASM_IMM5U, DASM_REG2 } },
|
||||||
|
{ "TRAP" , 1, { DASM_IMM5U } },
|
||||||
|
{ "RETI" , 0, { 0 } },
|
||||||
|
{ "HALT" , 0, { 0 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "LDSR" , 2, { DASM_REG2, DASM_SYSTEM } },
|
||||||
|
{ "STSR" , 2, { DASM_SYSTEM, DASM_REG2 } },
|
||||||
|
{ "SEI" , 0, { 0 } },
|
||||||
|
{ "" , 0, { 0 } }, /* Bit string */
|
||||||
|
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */ /* 100000 */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "MOVEA", 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "ADDI" , 3, { DASM_IMM16S, DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "JR" , 1, { DASM_DISP26 } },
|
||||||
|
{ "JAL" , 1, { DASM_DISP26 } },
|
||||||
|
{ "ORI" , 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "ANDI" , 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "XORI" , 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "MOVHI", 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } },
|
||||||
|
|
||||||
|
{ "LD.B" , 2, { DASM_MEMORY, DASM_REG2 } }, /* 110000 */
|
||||||
|
{ "LD.H" , 2, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "LD.W" , 2, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "ST.B" , 2, { DASM_REG2, DASM_MEMORY } },
|
||||||
|
{ "ST.H" , 2, { DASM_REG2, DASM_MEMORY } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "ST.W" , 2, { DASM_REG2, DASM_MEMORY } },
|
||||||
|
{ "IN.B" , 2, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "IN.H" , 2, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "CAXI" , 0, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "IN.W" , 2, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "OUT.B", 2, { DASM_REG2, DASM_MEMORY } },
|
||||||
|
{ "OUT.H", 2, { DASM_REG2, DASM_MEMORY } },
|
||||||
|
{ "" , 0, { 0 } }, /* Floating-point/Nintendo */
|
||||||
|
{ "OUT.W", 2, { DASM_REG2, DASM_MEMORY } }
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Bit string instruction descriptors */
|
||||||
|
static const OpDef OPDEFS_BITSTRING[] = {
|
||||||
|
{ "SCH0BSU", 0, { 0 } }, /* 00000 */
|
||||||
|
{ "SCH0BSD", 0, { 0 } },
|
||||||
|
{ "SCH1BSU", 0, { 0 } },
|
||||||
|
{ "SCH1BSD", 0, { 0 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "ORBSU" , 0, { 0 } },
|
||||||
|
{ "ANDBSU" , 0, { 0 } },
|
||||||
|
{ "XORBSU" , 0, { 0 } },
|
||||||
|
{ "MOVBSU" , 0, { 0 } },
|
||||||
|
{ "ORNBSU" , 0, { 0 } },
|
||||||
|
{ "ANDNBSU", 0, { 0 } },
|
||||||
|
{ "XORNBSU", 0, { 0 } },
|
||||||
|
{ "NOTBSU" , 0, { 0 } }
|
||||||
|
/* +16 invalid opcodes */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Floating-point/Nintendo instruction descriptors */
|
||||||
|
static const OpDef OPDEFS_FLOATENDO[] = {
|
||||||
|
{ "CMPF.S" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "CVT.WS" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "CVT.SW" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "ADDF.S" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "SUBF.S" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "MULF.S" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "DIVF.S" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "XB" , 1, { DASM_REG2 } },
|
||||||
|
{ "XH" , 1, { DASM_REG2 } },
|
||||||
|
{ "REV" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "TRNC.SW", 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "MPYHW" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
/* +51 invalid opcodes */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Invalid instruction descriptors */
|
||||||
|
static const OpDef OPDEF_ILLEGAL = { "---", 0, { 0 } };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Module Functions ******************************/
|
||||||
|
|
||||||
|
/* Select the display text for a condition */
|
||||||
|
static char* dasmCondition(VBU_DasmConfig *config, int cond) {
|
||||||
|
switch (cond) {
|
||||||
|
case 0: return "V" ;
|
||||||
|
case 1: return config->conditionCL == VBU_C ? "C" : "L";
|
||||||
|
case 2: return config->conditionEZ == VBU_E ? "E" : "Z";
|
||||||
|
case 3: return "NH";
|
||||||
|
case 4: return "N" ;
|
||||||
|
case 5: return "T" ;
|
||||||
|
case 6: return "LT";
|
||||||
|
case 7: return "LE";
|
||||||
|
case 8: return "NV";
|
||||||
|
case 9: return config->conditionCL == VBU_C ? "NC" : "NL";
|
||||||
|
case 10: return config->conditionEZ == VBU_E ? "NE" : "NZ";
|
||||||
|
case 11: return "H" ;
|
||||||
|
case 12: return "P" ;
|
||||||
|
case 13: return "F" ;
|
||||||
|
case 14: return "GE";
|
||||||
|
}
|
||||||
|
return "GT";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a number as hexadecimal */
|
||||||
|
static void dasmToHex(char *dest, VBU_DasmConfig *config,
|
||||||
|
int minDigits, int32_t value) {
|
||||||
|
char format[9];
|
||||||
|
char *prefix = "";
|
||||||
|
char *suffix = "";
|
||||||
|
switch (config->hexNotation) {
|
||||||
|
case VBU_0X : prefix = "0x"; break;
|
||||||
|
case VBU_DOLLAR: prefix = "$" ; break;
|
||||||
|
case VBU_H : suffix = "h" ; break;
|
||||||
|
}
|
||||||
|
sprintf(format, "%%s%%0%d%s%%s", minDigits,
|
||||||
|
config->hexCase == VBU_LOWER ? "x" : "X");
|
||||||
|
sprintf(dest, format, prefix, value, suffix);
|
||||||
|
if (config->hexNotation != VBU_H || *dest <= '9')
|
||||||
|
return;
|
||||||
|
*dest++ = '0';
|
||||||
|
sprintf(dest, format, prefix, value, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert a string to lowercase */
|
||||||
|
static void dasmToLower(char *str) {
|
||||||
|
for (; *str; str++) {
|
||||||
|
if (*str >= 'A' && *str <= 'Z')
|
||||||
|
*str += 'a' - 'A';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a program register */
|
||||||
|
static void dasmProgram(char *dest, VBU_DasmConfig *config, int id) {
|
||||||
|
char *text = NULL;
|
||||||
|
if (config->programNotation != VBU_NUMBERS) {
|
||||||
|
switch (id) {
|
||||||
|
case 2: text = "HP"; break;
|
||||||
|
case 3: text = "SP"; break;
|
||||||
|
case 4: text = "GP"; break;
|
||||||
|
case 5: text = "TP"; break;
|
||||||
|
case 31: text = "LP"; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (text == NULL)
|
||||||
|
sprintf(dest, "R%d", id);
|
||||||
|
else strcpy(dest, text);
|
||||||
|
if (config->programCase == VBU_LOWER)
|
||||||
|
dasmToLower(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**************************** Operand Formatters *****************************/
|
||||||
|
|
||||||
|
/* Format the condition operand of a split BCOND instruction */
|
||||||
|
static void dasmOpBCOND(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int cond = line->code[1] >> 2 & 15;
|
||||||
|
if (config->conditionNotation == VBU_NUMBERS)
|
||||||
|
sprintf(dest, "%d", cond);
|
||||||
|
else {
|
||||||
|
strcpy(dest, dasmCondition(config, cond));
|
||||||
|
if (config->conditionCase == VBU_LOWER)
|
||||||
|
dasmToLower(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 9-bit displacement operand */
|
||||||
|
static void dasmOpDisp9(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int32_t disp = SignExtend((int32_t) line->code[1] << 8 | line->code[0], 9);
|
||||||
|
sprintf(dest, config->hexCase == VBU_LOWER ?
|
||||||
|
"%08x" : "%08X", line->address + disp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 26-bit displacement operand */
|
||||||
|
static void dasmOpDisp26(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int32_t disp = SignExtend(
|
||||||
|
(int32_t) line->code[1] << 24 |
|
||||||
|
(int32_t) line->code[0] << 16 |
|
||||||
|
(int32_t) line->code[3] << 8 |
|
||||||
|
line->code[2]
|
||||||
|
, 26);
|
||||||
|
sprintf(dest, config->hexCase == VBU_LOWER ?
|
||||||
|
"%08x" : "%08X", line->address + disp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 5-bit sign-extended immediate operand */
|
||||||
|
static void dasmOpImm5S(char *dest, VBU_DasmConfig*config, VBU_DasmLine *line){
|
||||||
|
if (config->immediateNotation == VBU_NUMBER)
|
||||||
|
*dest++ = '#';
|
||||||
|
sprintf(dest, "%d", SignExtend(line->code[0], 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 5-bit zero-filled immediate operand */
|
||||||
|
static void dasmOpImm5U(char *dest, VBU_DasmConfig*config, VBU_DasmLine *line){
|
||||||
|
if (config->immediateNotation == VBU_NUMBER)
|
||||||
|
*dest++ = '#';
|
||||||
|
sprintf(dest, "%d", line->code[0] & 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 16-bit sign-extended immediate operand */
|
||||||
|
static void dasmOpImm16S(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int32_t imm = (int16_t) ((int16_t) line->code[3] << 8 | line->code[2]);
|
||||||
|
if (config->immediateNotation == VBU_NUMBER)
|
||||||
|
*dest++ = '#';
|
||||||
|
if (imm >= -256 && imm <= 256) {
|
||||||
|
sprintf(dest, "%d", imm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (imm < 0) {
|
||||||
|
*dest++ = '-';
|
||||||
|
imm = -imm;
|
||||||
|
}
|
||||||
|
dasmToHex(dest, config, 0, imm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 16-bit zero-filled immediate operand */
|
||||||
|
static void dasmOpImm16U(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
uint16_t imm = (uint16_t) line->code[3] << 8 | line->code[2];
|
||||||
|
if (config->immediateNotation == VBU_NUMBER)
|
||||||
|
*dest++ = '#';
|
||||||
|
dasmToHex(dest, config, 4, imm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format the operand of a JMP instruction */
|
||||||
|
static void dasmOpJMP(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
*dest++ = '[';
|
||||||
|
dasmProgram(dest, config, line->code[0] & 31);
|
||||||
|
strcat(dest, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a memory operand */
|
||||||
|
static void dasmOpMemory(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int reg1 = line->code[0] & 31;
|
||||||
|
int32_t disp = (int16_t) ((int16_t) line->code[3] << 8 | line->code[2]);
|
||||||
|
|
||||||
|
/* Displacement of zero */
|
||||||
|
if (disp == 0) {
|
||||||
|
*dest++ = '[';
|
||||||
|
dasmProgram(dest, config, reg1);
|
||||||
|
strcat(dest, "]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inside */
|
||||||
|
if (config->memoryNotation == VBU_INSIDE) {
|
||||||
|
*dest++ = '[';
|
||||||
|
dasmProgram(dest, config, reg1);
|
||||||
|
dest += strlen(dest);
|
||||||
|
if (disp < 0) {
|
||||||
|
strcpy(dest, " - ");
|
||||||
|
disp = -disp;
|
||||||
|
} else strcpy(dest, " + ");
|
||||||
|
dest += 3;
|
||||||
|
if (disp <= 256)
|
||||||
|
sprintf(dest, "%d", disp);
|
||||||
|
else dasmToHex(dest, config, 0, disp);
|
||||||
|
strcat(dest, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Outside */
|
||||||
|
else {
|
||||||
|
if (disp < 0) {
|
||||||
|
*dest++ = '-';
|
||||||
|
disp = -disp;
|
||||||
|
}
|
||||||
|
if (disp <= 256)
|
||||||
|
sprintf(dest, "%d", disp);
|
||||||
|
else dasmToHex(dest, config, 0, disp);
|
||||||
|
dest += strlen(dest);
|
||||||
|
*dest++ = '[';
|
||||||
|
dasmProgram(dest, config, reg1);
|
||||||
|
strcat(dest, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a source register operand */
|
||||||
|
static void dasmOpReg1(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
dasmProgram(dest, config, line->code[0] & 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a destination register operand */
|
||||||
|
static void dasmOpReg2(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
dasmProgram(dest, config,
|
||||||
|
((uint16_t) line->code[1] << 8 | line->code[0]) >> 5 & 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format the condition operand of a split SETF instruction */
|
||||||
|
static void dasmOpSETF(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int cond = line->code[0] & 15;
|
||||||
|
if (config->conditionNotation == VBU_NUMBERS)
|
||||||
|
sprintf(dest, "%d", cond);
|
||||||
|
else {
|
||||||
|
strcpy(dest, dasmCondition(config, cond));
|
||||||
|
if (config->conditionCase == VBU_LOWER)
|
||||||
|
dasmToLower(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a system register operand */
|
||||||
|
static void dasmOpSystem(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int id = line->code[0] & 31;
|
||||||
|
char *text = NULL;
|
||||||
|
if (config->systemNotation != VBU_NUMBERS) {
|
||||||
|
switch (id) {
|
||||||
|
case 0: text = "EIPC" ; break;
|
||||||
|
case 1: text = "EIPSW"; break;
|
||||||
|
case 2: text = "FEPC" ; break;
|
||||||
|
case 3: text = "FEPSW"; break;
|
||||||
|
case 4: text = "ECR" ; break;
|
||||||
|
case 5: text = "PSW" ; break;
|
||||||
|
case 6: text = "PIR" ; break;
|
||||||
|
case 7: text = "TKCW" ; break;
|
||||||
|
case 24: text = "CHCW" ; break;
|
||||||
|
case 25: text = "ADTRE"; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (text == NULL) {
|
||||||
|
sprintf(dest, "%d", id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strcpy(dest, text);
|
||||||
|
if (config->systemCase == VBU_LOWER)
|
||||||
|
dasmToLower(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Module Functions ******************************/
|
||||||
|
|
||||||
|
/* Prepare an OpDef for a BCOND instruction */
|
||||||
|
static OpDef* dasmBCOND(VBU_DasmConfig *config,
|
||||||
|
VBU_DasmLine *line, OpDef *opdef) {
|
||||||
|
int cond; /* Condition code */
|
||||||
|
char *text; /* Mnemonic text */
|
||||||
|
|
||||||
|
/* Split notation */
|
||||||
|
if (config->bcondNotation == VBU_SPLIT) {
|
||||||
|
strcpy(opdef->mnemonic, "BCOND");
|
||||||
|
opdef->operandsLength = 2;
|
||||||
|
opdef->operands[0] = DASM_BCOND;
|
||||||
|
opdef->operands[1] = DASM_DISP9;
|
||||||
|
return opdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Joined notation */
|
||||||
|
cond = line->code[1] >> 1 & 15;
|
||||||
|
switch (cond) {
|
||||||
|
case 0: text = "BV" ; break;
|
||||||
|
case 1: text = config->conditionCL == VBU_C ? "BC" : "BL"; break;
|
||||||
|
case 2: text = config->conditionEZ == VBU_E ? "BE" : "BZ"; break;
|
||||||
|
case 3: text = "BNH"; break;
|
||||||
|
case 4: text = "BN" ; break;
|
||||||
|
case 5: text = "BR" ; break;
|
||||||
|
case 6: text = "BLT"; break;
|
||||||
|
case 7: text = "BLE"; break;
|
||||||
|
case 8: text = "BNV"; break;
|
||||||
|
case 9: text = config->conditionCL == VBU_C ? "BNC" : "BNL"; break;
|
||||||
|
case 10: text = config->conditionEZ == VBU_E ? "BNE" : "BNZ"; break;
|
||||||
|
case 11: text = "BH" ; break;
|
||||||
|
case 12: text = "BP" ; break;
|
||||||
|
case 13: text = "NOP"; break;
|
||||||
|
case 14: text = "BGE"; break;
|
||||||
|
default: text = "BGT";
|
||||||
|
}
|
||||||
|
strcpy(opdef->mnemonic, text);
|
||||||
|
opdef->operandsLength = cond != 13;
|
||||||
|
opdef->operands[0] = DASM_DISP9;
|
||||||
|
return opdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure an output buffer matches the required size */
|
||||||
|
static VBU_DasmLine* dasmGrow(
|
||||||
|
VBU_DasmLine **lines, size_t *size, size_t target, unsigned index) {
|
||||||
|
if (*size < target) {
|
||||||
|
while (*size < target)
|
||||||
|
*size *= 2;
|
||||||
|
*lines = VBU_REALLOC(*lines, *size);
|
||||||
|
}
|
||||||
|
return *lines == NULL ? NULL : &(*lines)[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format an operand */
|
||||||
|
static void dasmOperand(char *dest, VBU_DasmConfig *config,
|
||||||
|
VBU_DasmLine *line, uint8_t type) {
|
||||||
|
switch (type) {
|
||||||
|
case DASM_BCOND : dasmOpBCOND (dest, config, line); break;
|
||||||
|
case DASM_DISP9 : dasmOpDisp9 (dest, config, line); break;
|
||||||
|
case DASM_DISP26: dasmOpDisp26(dest, config, line); break;
|
||||||
|
case DASM_IMM5S : dasmOpImm5S (dest, config, line); break;
|
||||||
|
case DASM_IMM5U : dasmOpImm5U (dest, config, line); break;
|
||||||
|
case DASM_IMM16S: dasmOpImm16S(dest, config, line); break;
|
||||||
|
case DASM_IMM16U: dasmOpImm16U(dest, config, line); break;
|
||||||
|
case DASM_JMP : dasmOpJMP (dest, config, line); break;
|
||||||
|
case DASM_MEMORY: dasmOpMemory(dest, config, line); break;
|
||||||
|
case DASM_REG1 : dasmOpReg1 (dest, config, line); break;
|
||||||
|
case DASM_REG2 : dasmOpReg2 (dest, config, line); break;
|
||||||
|
case DASM_SETF : dasmOpSETF (dest, config, line); break;
|
||||||
|
case DASM_SYSTEM: dasmOpSystem(dest, config, line); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare an OpDef for a SETF instruction */
|
||||||
|
static OpDef* dasmSETF(VBU_DasmConfig *config,
|
||||||
|
VBU_DasmLine *line, OpDef *opdef) {
|
||||||
|
|
||||||
|
/* Split notation */
|
||||||
|
if (config->bcondNotation == VBU_SPLIT) {
|
||||||
|
strcpy(opdef->mnemonic, "SETF");
|
||||||
|
opdef->operandsLength = 2;
|
||||||
|
opdef->operands[0] = DASM_SETF;
|
||||||
|
opdef->operands[1] = DASM_REG2;
|
||||||
|
return opdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Joined notation */
|
||||||
|
sprintf(opdef->mnemonic, "SETF%s",
|
||||||
|
dasmCondition(config, line->code[1] >> 1 & 15));
|
||||||
|
opdef->operandsLength = 1;
|
||||||
|
opdef->operands[0] = DASM_REG2;
|
||||||
|
return opdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disassemble one instruction */
|
||||||
|
static int dasmLine(VB *sim, uint32_t *address, uint32_t pc,
|
||||||
|
VBU_DasmConfig *config, VBU_DasmLine **lines, size_t *size, size_t *offset,
|
||||||
|
unsigned index) {
|
||||||
|
char *dest; /* Text output pointer */
|
||||||
|
char *format; /* Hexadecimal format string */
|
||||||
|
VBU_DasmLine *line; /* Output line */
|
||||||
|
size_t more; /* Number of bytes to output */
|
||||||
|
OpDef *opdef; /* Instruction descriptor */
|
||||||
|
OpDef special; /* Special-case descriptor */
|
||||||
|
char tAddress[9]; /* Address display text */
|
||||||
|
char tCode[4][3]; /* Code display text */
|
||||||
|
char tMnemonic[8]; /* Mnemonic display text */
|
||||||
|
char tOperands[3][15]; /* Operands display text */
|
||||||
|
uint32_t x; /* Scratch and iterator */
|
||||||
|
|
||||||
|
/* Process non-text members */
|
||||||
|
line = &(*lines)[index];
|
||||||
|
line->address = *address;
|
||||||
|
line->codeLength = vbuCodeSize(sim, *address);
|
||||||
|
line->isPC = *address == pc;
|
||||||
|
for (x = 0; x < line->codeLength; x++)
|
||||||
|
line->code[x] = vbRead(sim, *address + x, VB_U8);
|
||||||
|
|
||||||
|
/* Advance to the next instruction or PC, whichever is sooner */
|
||||||
|
*address += pc != *address && pc - *address < line->codeLength ?
|
||||||
|
pc - *address : line->codeLength;
|
||||||
|
|
||||||
|
/* Do not process text members */
|
||||||
|
if (config == NULL) {
|
||||||
|
line->text.address = NULL;
|
||||||
|
line->text.mnemonic = NULL;
|
||||||
|
line->text.operandsLength = 0;
|
||||||
|
for (x = 0; x < line->codeLength; x++)
|
||||||
|
line->text.code[x] = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select instruction descriptor */
|
||||||
|
x = line->code[1] >> 2 & 63;
|
||||||
|
switch (x) {
|
||||||
|
case 0x12: /* SETF */
|
||||||
|
opdef = dasmSETF(config, line, &special);
|
||||||
|
break;
|
||||||
|
case 0x1F: /* Bit string */
|
||||||
|
x = line->code[0] & 31;
|
||||||
|
opdef = (OpDef *) (x < 16 ? &OPDEFS_BITSTRING[x] : &OPDEF_ILLEGAL);
|
||||||
|
break;
|
||||||
|
case 0x20: case 0x21: case 0x22: case 0x23: /* BCOND */
|
||||||
|
case 0x24: case 0x25: case 0x26: case 0x27:
|
||||||
|
opdef = dasmBCOND(config, line, &special);
|
||||||
|
break;
|
||||||
|
case 0x3E: /* Floating-point/Nintendo */
|
||||||
|
x = line->code[3] >> 2 & 63;
|
||||||
|
opdef = (OpDef *) (x < 13 ? &OPDEFS_FLOATENDO[x] : &OPDEF_ILLEGAL);
|
||||||
|
break;
|
||||||
|
default: opdef = (OpDef *) &OPDEFS[x]; /* Other */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Address */
|
||||||
|
format = config->hexCase == VBU_LOWER ? "%08x" : "%08X";
|
||||||
|
sprintf(tAddress, format, line->address);
|
||||||
|
more = 9;
|
||||||
|
|
||||||
|
/* Code */
|
||||||
|
format = config->hexCase == VBU_LOWER ? "%02x" : "%02X";
|
||||||
|
for (x = 0; x < line->codeLength; x++) {
|
||||||
|
sprintf(tCode[x], format, (int) line->code[x]);
|
||||||
|
more += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mnemonic */
|
||||||
|
strcpy(tMnemonic, opdef->mnemonic);
|
||||||
|
if (config->mnemonicCase == VBU_LOWER)
|
||||||
|
dasmToLower(tMnemonic);
|
||||||
|
more += strlen(tMnemonic) + 1;
|
||||||
|
|
||||||
|
/* Operands */
|
||||||
|
line->text.operandsLength = opdef->operandsLength;
|
||||||
|
for (x = 0; x < opdef->operandsLength; x++) {
|
||||||
|
dasmOperand(tOperands[x], config, line, opdef->operands[x]);
|
||||||
|
more += strlen(tOperands[x]) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grow the text buffer */
|
||||||
|
line = dasmGrow(lines, size, *offset + more, index);
|
||||||
|
if (line == NULL)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Working variables */
|
||||||
|
dest = &((char *) *lines)[*offset];
|
||||||
|
*offset += more;
|
||||||
|
|
||||||
|
/* Address */
|
||||||
|
strcpy(dest, tAddress);
|
||||||
|
line->text.address = dest;
|
||||||
|
dest += 9;
|
||||||
|
|
||||||
|
/* Code */
|
||||||
|
for (x = 0; x < line->codeLength; x++) {
|
||||||
|
strcpy(dest, tCode[x]);
|
||||||
|
line->text.code[x] = dest;
|
||||||
|
dest += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mnemonic */
|
||||||
|
strcpy(dest, tMnemonic);
|
||||||
|
line->text.mnemonic = dest;
|
||||||
|
dest += strlen(dest) + 1;
|
||||||
|
|
||||||
|
/* Operands */
|
||||||
|
for (x = 0; x < opdef->operandsLength; x++) {
|
||||||
|
strcpy(dest, tOperands[
|
||||||
|
config->operandOrder == VBU_DEST_FIRST ?
|
||||||
|
opdef->operandsLength - 1 - x :
|
||||||
|
x
|
||||||
|
]);
|
||||||
|
line->text.operands[x] = dest;
|
||||||
|
dest += strlen(dest) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Library Functions *****************************/
|
||||||
|
|
||||||
|
/* Disassemble from a simulation */
|
||||||
|
static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address,
|
||||||
|
VBU_DasmConfig *config, unsigned length, int line) {
|
||||||
|
uint32_t addr; /* Working address */
|
||||||
|
uint32_t *circle = NULL; /* Circular address buffer */
|
||||||
|
VBU_DasmLine *lines = NULL; /* Output line buffer */
|
||||||
|
size_t offset; /* Offset into lines buffer */
|
||||||
|
uint32_t pc; /* Program counter */
|
||||||
|
size_t size; /* Number of bytes in lines */
|
||||||
|
unsigned x; /* Iterator */
|
||||||
|
|
||||||
|
/* Nothing to disassemble */
|
||||||
|
if (length == 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Establish a circular buffer */
|
||||||
|
if (line > 0) {
|
||||||
|
circle = VBU_REALLOC(NULL, (size_t) (line + 1) * sizeof (uint32_t));
|
||||||
|
if (circle == NULL)
|
||||||
|
goto catch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Begin decoding from at least 10 lines before first/reference */
|
||||||
|
addr = (address & 0xFFFFFFFE) -
|
||||||
|
(int32_t) (line <= 0 ? 10 : line + 10) * 4;
|
||||||
|
|
||||||
|
/* Locate the address of the line containing the reference address */
|
||||||
|
pc = vbGetProgramCounter(sim);
|
||||||
|
for (x = 0;;) {
|
||||||
|
|
||||||
|
/* Track the address in the circular buffer */
|
||||||
|
if (line > 0) {
|
||||||
|
circle[x] = addr;
|
||||||
|
if (++x > (size_t) line)
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the instruction contains the reference address */
|
||||||
|
size = vbuCodeSize(sim, addr);
|
||||||
|
if (address - addr < size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Advance to the next instruction or PC, whichever is sooner */
|
||||||
|
addr += addr != pc && pc - addr < size ? pc - addr : size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Address of first line is in the circular buffer */
|
||||||
|
if (line > 0) {
|
||||||
|
addr = circle[x];
|
||||||
|
circle = VBU_REALLOC(circle, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep decoding until the first line of output */
|
||||||
|
else for (; line < 0; line++)
|
||||||
|
addr += vbuCodeSize(sim, addr);
|
||||||
|
|
||||||
|
/* Working variables */
|
||||||
|
size = length * sizeof (VBU_DasmLine);
|
||||||
|
lines = VBU_REALLOC(NULL, size);
|
||||||
|
offset = size;
|
||||||
|
if (lines == NULL)
|
||||||
|
goto catch;
|
||||||
|
|
||||||
|
/* Process output lines */
|
||||||
|
for (x = 0; x < length; x++) {
|
||||||
|
if (dasmLine(sim, &addr, pc, config, &lines, &size, &offset, x))
|
||||||
|
goto catch;
|
||||||
|
}
|
||||||
|
return VBU_REALLOC(lines, offset);
|
||||||
|
|
||||||
|
/* Exception handler */
|
||||||
|
catch:
|
||||||
|
circle = VBU_REALLOC(circle, 0);
|
||||||
|
lines = VBU_REALLOC(lines , 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* VBUAPI */
|
||||||
+297
@@ -0,0 +1,297 @@
|
|||||||
|
/* This file is included into vbu.c and cannot be compiled on its own. */
|
||||||
|
#ifdef VBUAPI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************* Constants *********************************/
|
||||||
|
|
||||||
|
/* Range types */
|
||||||
|
/*
|
||||||
|
0x41 ('A') 8-bit integer (.db), string
|
||||||
|
0x42 ('B') 8-bit integer (.db), numeric
|
||||||
|
0x43 ('C') Compiled C code
|
||||||
|
0x44 ('D') 16-bit integer (.dd, high byte first)
|
||||||
|
0x45 ('E') 65C816 code (8-bit accumulator, 8-bit index)
|
||||||
|
0x46 ('F') 65C816 code (8-bit accumulator, 16-bit index)
|
||||||
|
0x47 ('G') 65C816 code (16-bit accumulator, 8-bit index)
|
||||||
|
0x48 ('H') 65C816 code (16-bit accumulator, 16-bit index)
|
||||||
|
0x4C ('L') 32-bit integer (.dl SNES, .dw Virtual Boy, low byte first)
|
||||||
|
0x53 ('S') Size reservation, as in .bss allocation (.ds)
|
||||||
|
0x57 ('W') 16-bit integer (.dw SNES, .dh Virtual Boy, low byte first)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Module Functions ******************************/
|
||||||
|
|
||||||
|
/* Skip ISX record type 0x01 */
|
||||||
|
static size_t isx01(uint8_t *bytes, size_t length, size_t src) {
|
||||||
|
/*
|
||||||
|
u8 Type =0x01
|
||||||
|
u8 Bank Cartridge bank number
|
||||||
|
u8 BankHigh Only if Bank >= 0x80
|
||||||
|
u16 Address CPU bus address
|
||||||
|
u16 DataLength Number of bytes in Data
|
||||||
|
u8[] Data Code bytes
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Bank field is not present */
|
||||||
|
if (src + 2 > length)
|
||||||
|
return (size_t) -1;
|
||||||
|
|
||||||
|
/* Advance to DataLength field */
|
||||||
|
src += bytes[src + 1] >= 0x80 ? 5 : 4;
|
||||||
|
|
||||||
|
/* DataLength field is not present */
|
||||||
|
if (src + 2 > length)
|
||||||
|
return (size_t) -1;
|
||||||
|
|
||||||
|
/* Advance past Data field */
|
||||||
|
return src + 2 + ((uint16_t) bytes[src + 1] << 8 | bytes[src]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decode ISX record type 0x11 */
|
||||||
|
static size_t isx11(uint8_t *bytes, size_t length, size_t src,
|
||||||
|
uint32_t *address, uint32_t *size) {
|
||||||
|
/*
|
||||||
|
u8 Type =0x11
|
||||||
|
u32 Address CPU bus address
|
||||||
|
u32 DataLength Number of bytes in Data
|
||||||
|
u8[] Data Code bytes
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* DataLength field is not present */
|
||||||
|
if (src + 9 > length)
|
||||||
|
return (size_t) -1;
|
||||||
|
|
||||||
|
/* Parse Address and DataLength fields */
|
||||||
|
*address = (uint32_t) bytes[src+4] << 24 | (uint32_t) bytes[src+3] << 16 |
|
||||||
|
(uint32_t) bytes[src+2] << 8 | bytes[src+1];
|
||||||
|
*size = (uint32_t) bytes[src+8] << 24 | (uint32_t) bytes[src+7] << 16 |
|
||||||
|
(uint32_t) bytes[src+6] << 8 | bytes[src+5];
|
||||||
|
|
||||||
|
/* Advance past Data field */
|
||||||
|
return src + 9 + *size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip ISX range records */
|
||||||
|
static size_t isxRange(uint8_t *bytes,size_t length,size_t src,size_t size) {
|
||||||
|
/*
|
||||||
|
u8 Type =0x03 (SNES) or 0x13 (Virtual Boy)
|
||||||
|
u16 Count Number of ranges
|
||||||
|
Range[] Ranges
|
||||||
|
=== SNES fields ===
|
||||||
|
u8 Bank Cartridge bank number
|
||||||
|
u16 StartAddress Lowest CPU bus address
|
||||||
|
u16 EndAddress Highest CPU bus address
|
||||||
|
u8 RangeType See range types
|
||||||
|
=== Virtual Boy fields ===
|
||||||
|
u32 StartAddress Lowest CPU bus address
|
||||||
|
u32 EndAddress Highest CPU bus address
|
||||||
|
u8 RangeType See range types
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Count field is not present */
|
||||||
|
if (src + 3 > length)
|
||||||
|
return (size_t) -1;
|
||||||
|
|
||||||
|
/* Advance past Ranges field */
|
||||||
|
return src + 3 + size * ((uint16_t) bytes[src + 2] << 8 | bytes[src + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip ISX symbol record */
|
||||||
|
static size_t isxSymbol(uint8_t *bytes,size_t length,size_t src,size_t size) {
|
||||||
|
size_t count; /* Number of symbols */
|
||||||
|
size_t x; /* Iterator */
|
||||||
|
|
||||||
|
/*
|
||||||
|
u8 Type =0x04 (SNES) or 0x14 (Virtual Boy)
|
||||||
|
u16 Count Number of symbols
|
||||||
|
Symbol[] Symbols
|
||||||
|
u8 NameLength Number of bytes in Name
|
||||||
|
u8[] Name Symbol name
|
||||||
|
=== SNES fields ===
|
||||||
|
u8 Flags Undocumented
|
||||||
|
u8 Bank Cartridge bank number
|
||||||
|
u16 Address CPU bus address
|
||||||
|
=== Virtual Boy fields ===
|
||||||
|
u16 Flags Undocumented
|
||||||
|
u32 Address CPU bus address
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Count field is not present */
|
||||||
|
if (src + 3 > length)
|
||||||
|
return (size_t) -1;
|
||||||
|
|
||||||
|
/* Working variables */
|
||||||
|
count = (uint16_t) bytes[src + 2] << 8 | bytes[src + 1];
|
||||||
|
src += 3;
|
||||||
|
|
||||||
|
/* Process all symbols */
|
||||||
|
for (x = 0; x < count; x++) {
|
||||||
|
|
||||||
|
/* NameLength field is not present */
|
||||||
|
if (src > length)
|
||||||
|
return (size_t) -1;
|
||||||
|
|
||||||
|
/* Advance past remainder of symbol */
|
||||||
|
src += size + bytes[src];
|
||||||
|
|
||||||
|
/* Remainder of symbol is not present */
|
||||||
|
if (src > length)
|
||||||
|
return (size_t) -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip ISX system record */
|
||||||
|
static size_t isxSystem(uint8_t *bytes, size_t length, size_t src) {
|
||||||
|
/*
|
||||||
|
u8 Type =0x20, 0x21 or 0x22
|
||||||
|
u32 DataLength Number of bytes in Data
|
||||||
|
u8[] Data Record data
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* DataLength field is not present */
|
||||||
|
if (src + 5 > length)
|
||||||
|
return (size_t) -1;
|
||||||
|
|
||||||
|
/* Advance past Data field */
|
||||||
|
return src + 5 + (
|
||||||
|
(uint32_t) bytes[src + 4] << 24 | (uint32_t) bytes[src + 3] << 16 |
|
||||||
|
(uint32_t) bytes[src + 2] << 8 | bytes[src + 1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Library Functions *****************************/
|
||||||
|
|
||||||
|
/* Decode an ISX debugger file to a Virtual Boy ROM */
|
||||||
|
static void* isxFrom(uint8_t *bytes, size_t length, size_t *romLength) {
|
||||||
|
uint32_t addr; /* Code record base address */
|
||||||
|
uint32_t highMin; /* Lowest address in upper half of ROM */
|
||||||
|
uint32_t lowMax; /* Highest address in lower half of ROM */
|
||||||
|
uint8_t *rom; /* Output ROM */
|
||||||
|
uint32_t size; /* Code record size in bytes */
|
||||||
|
size_t src; /* Input position */
|
||||||
|
size_t start; /* Offset of first record */
|
||||||
|
|
||||||
|
/* Error checking */
|
||||||
|
if (length < 3)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Working variables */
|
||||||
|
highMin = 0xFFFFFF;
|
||||||
|
lowMax = 0x000000;
|
||||||
|
size = 0;
|
||||||
|
start = strncmp((char *) bytes, "ISX", 3) ? 0 : 32;
|
||||||
|
|
||||||
|
/* First pass: determine the size of the ROM */
|
||||||
|
for (src = start; src < length;) {
|
||||||
|
switch (bytes[src]) {
|
||||||
|
|
||||||
|
case 0x11: /* Code (Virtual Boy) */
|
||||||
|
|
||||||
|
/* Decode record fields */
|
||||||
|
src = isx11(bytes, length, src, &addr, &size);
|
||||||
|
if (src > length)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Validate address and data size */
|
||||||
|
addr &= 0x07FFFFFF;
|
||||||
|
if (
|
||||||
|
addr < 0x07000000 ||
|
||||||
|
0x08000000 - addr < size
|
||||||
|
) return NULL;
|
||||||
|
addr &= 0x00FFFFFF;
|
||||||
|
|
||||||
|
/* Requires 16 MiB */
|
||||||
|
if (addr < 0x800000 && addr + size > 0x800000) {
|
||||||
|
highMin = 0x800000;
|
||||||
|
lowMax = 0x7FFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map from top of ROM space */
|
||||||
|
else if (addr & 0x800000) {
|
||||||
|
if (addr < highMin)
|
||||||
|
highMin = addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map from bottom of ROM space */
|
||||||
|
else {
|
||||||
|
addr += size - 1;
|
||||||
|
if (addr > lowMax)
|
||||||
|
lowMax = addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01: /* Code (SNES) */
|
||||||
|
src = isx01 (bytes, length, src ); break;
|
||||||
|
case 0x03: /* Range (SNES) */
|
||||||
|
src = isxRange (bytes, length, src, 6); break;
|
||||||
|
case 0x04: /* Symbol (SNES) */
|
||||||
|
src = isxSymbol(bytes, length, src, 5); break;
|
||||||
|
case 0x13: /* Range (Virtual Boy) */
|
||||||
|
src = isxRange (bytes, length, src, 9); break;
|
||||||
|
case 0x14: /* Symbol (Virtual Boy) */
|
||||||
|
src = isxSymbol(bytes, length, src, 7); break;
|
||||||
|
case 0x20: /* System (undocumented) */
|
||||||
|
case 0x21: /* System (undocumented) */
|
||||||
|
case 0x22: /* System (undocumented) */
|
||||||
|
src = isxSystem(bytes, length, src ); break;
|
||||||
|
default: /* Invalid record type */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Record contains invalid data */
|
||||||
|
if (src > length)
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Produce a ROM buffer of minimal size to fit all the code records */
|
||||||
|
for (
|
||||||
|
src = 1;
|
||||||
|
src < lowMax + 0x1000001 - highMin;
|
||||||
|
src <<= 1
|
||||||
|
);
|
||||||
|
rom = VBU_REALLOC(NULL, src);
|
||||||
|
if (rom == NULL)
|
||||||
|
return NULL;
|
||||||
|
*romLength = src;
|
||||||
|
memset(rom, 0x00, src);
|
||||||
|
|
||||||
|
/* Second pass: fill the ROM buffer with the code records */
|
||||||
|
for (src = start; src < length;) {
|
||||||
|
switch (bytes[src]) {
|
||||||
|
|
||||||
|
case 0x11: /* Code (Virtual Boy) */
|
||||||
|
src = isx11(bytes, length, src, &addr, &size);
|
||||||
|
memcpy(&rom[addr & (*romLength-1)], &bytes[src - size], size);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01: /* Code (SNES) */
|
||||||
|
src = isx01 (bytes, length, src ); break;
|
||||||
|
case 0x03: /* Range (SNES) */
|
||||||
|
src = isxRange (bytes, length, src, 9); break;
|
||||||
|
case 0x04: /* Symbol (SNES) */
|
||||||
|
src = isxSymbol(bytes, length, src, 5); break;
|
||||||
|
case 0x13: /* Range (Virtual Boy) */
|
||||||
|
src = isxRange (bytes, length, src, 12); break;
|
||||||
|
case 0x14: /* Symbol (Virtual Boy) */
|
||||||
|
src = isxSymbol(bytes, length, src, 7); break;
|
||||||
|
case 0x20: /* System (undocumented) */
|
||||||
|
case 0x21: /* System (undocumented) */
|
||||||
|
case 0x22: /* System (undocumented) */
|
||||||
|
src = isxSystem(bytes, length, src ); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rom;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* VBUAPI */
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
#ifndef VBUAPI
|
||||||
|
#define VBUAPI
|
||||||
|
#endif
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <vbu.h>
|
||||||
|
|
||||||
|
/* Memory management */
|
||||||
|
#ifndef VBU_REALLOC
|
||||||
|
#include <stdlib.h>
|
||||||
|
#define VBU_REALLOC realloc
|
||||||
|
#else
|
||||||
|
extern void* VBU_REALLOC(void *, size_t);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Library Functions *****************************/
|
||||||
|
|
||||||
|
/* Sign-extend an integer of variable width */
|
||||||
|
static int32_t SignExtend(int32_t value, int32_t bits) {
|
||||||
|
#ifndef VB_SIGNED_PROPAGATE
|
||||||
|
value &= ~((uint32_t) 0xFFFFFFFF << bits);
|
||||||
|
bits = (int32_t) 1 << (bits - (int32_t) 1);
|
||||||
|
return (value ^ bits) - bits;
|
||||||
|
#else
|
||||||
|
return value << (32 - bits) >> (32 - bits);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/******************************** Sub-Modules ********************************/
|
||||||
|
|
||||||
|
#include "disassembler.c"
|
||||||
|
#include "isx.c"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/******************************* API Commands ********************************/
|
||||||
|
|
||||||
|
/* Determine the size in bytes of an instruction */
|
||||||
|
VBUAPI int vbuCodeSize(VB *sim, uint32_t address) {
|
||||||
|
int opcode = vbRead(sim, address, VB_U16) >> 10 & 63;
|
||||||
|
return opcode < 0x20 || opcode == 0x32 || opcode == 0x36 ? 2 : 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize disassembler options with default settings */
|
||||||
|
VBUAPI VBU_DasmConfig* vbuDasmInit(VBU_DasmConfig *config) {
|
||||||
|
config->bcondNotation = VBU_JOINED;
|
||||||
|
config->conditionCase = VBU_LOWER;
|
||||||
|
config->conditionCL = VBU_L;
|
||||||
|
config->conditionEZ = VBU_Z;
|
||||||
|
config->conditionNotation = VBU_NAMES;
|
||||||
|
config->hexCase = VBU_UPPER;
|
||||||
|
config->hexNotation = VBU_0X;
|
||||||
|
config->immediateNotation = VBU_NONE;
|
||||||
|
config->memoryNotation = VBU_OUTSIDE;
|
||||||
|
config->mnemonicCase = VBU_UPPER;
|
||||||
|
config->operandOrder = VBU_DEST_LAST;
|
||||||
|
config->programCase = VBU_LOWER;
|
||||||
|
config->programNotation = VBU_NAMES;
|
||||||
|
config->setfNotation = VBU_SPLIT;
|
||||||
|
config->systemCase = VBU_LOWER;
|
||||||
|
config->systemNotation = VBU_NAMES;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disassemble from a simulation */
|
||||||
|
VBUAPI VBU_DasmLine* vbuDisassemble(VB *sim, uint32_t address,
|
||||||
|
VBU_DasmConfig *config, unsigned length, int line) {
|
||||||
|
return dasmDisassemble(sim, address, config, length, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decode an ISX debugger file to a Virtual Boy ROM */
|
||||||
|
VBUAPI void* vbuFromISX(void *bytes, size_t length, size_t *romLength) {
|
||||||
|
return isxFrom(bytes, length, romLength);
|
||||||
|
}
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
#ifndef VBU_H_
|
||||||
|
#define VBU_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef VBUAPI
|
||||||
|
#define VBUAPI extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Header includes */
|
||||||
|
#include <vb.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************* Constants *********************************/
|
||||||
|
|
||||||
|
/* Disassembler options */
|
||||||
|
#define VBU_0X 0
|
||||||
|
#define VBU_C 1
|
||||||
|
#define VBU_DEST_FIRST 1
|
||||||
|
#define VBU_DEST_LAST 0
|
||||||
|
#define VBU_DOLLAR 1
|
||||||
|
#define VBU_E 0
|
||||||
|
#define VBU_H 2
|
||||||
|
#define VBU_INSIDE 1
|
||||||
|
#define VBU_JOINED 0
|
||||||
|
#define VBU_L 0
|
||||||
|
#define VBU_LOWER 1
|
||||||
|
#define VBU_NAMES 1
|
||||||
|
#define VBU_NONE 0
|
||||||
|
#define VBU_NUMBER 1
|
||||||
|
#define VBU_NUMBERS 0
|
||||||
|
#define VBU_OUTSIDE 0
|
||||||
|
#define VBU_SPLIT 1
|
||||||
|
#define VBU_UPPER 0
|
||||||
|
#define VBU_Z 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*********************************** Types ***********************************/
|
||||||
|
|
||||||
|
/* Disassembler text options */
|
||||||
|
typedef struct { /* Defaults listed first */
|
||||||
|
uint8_t bcondNotation; /* JOINED, SPLIT */
|
||||||
|
uint8_t conditionCase; /* LOWER, UPPER */
|
||||||
|
uint8_t conditionCL; /* L, C */
|
||||||
|
uint8_t conditionEZ; /* Z, E */
|
||||||
|
uint8_t conditionNotation; /* NAMES, NUMBERS */
|
||||||
|
uint8_t hexCase; /* UPPER, LOWER */
|
||||||
|
uint8_t hexNotation; /* 0X, H, DOLLAR */
|
||||||
|
uint8_t immediateNotation; /* NONE, NUMBER */
|
||||||
|
uint8_t memoryNotation; /* OUTSIDE, INSIDE */
|
||||||
|
uint8_t mnemonicCase; /* UPPER, LOWER */
|
||||||
|
uint8_t operandOrder; /* DEST_LAST, DEST_FIRST */
|
||||||
|
uint8_t programCase; /* LOWER, UPPER */
|
||||||
|
uint8_t programNotation; /* NAMES, NUMBERS */
|
||||||
|
uint8_t setfNotation; /* SPLIT, JOINED */
|
||||||
|
uint8_t systemCase; /* LOWER, UPPER */
|
||||||
|
uint8_t systemNotation; /* NAMES, NUMBERS */
|
||||||
|
} VBU_DasmConfig;
|
||||||
|
|
||||||
|
/* Disassembler output line */
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
/* Parsed */
|
||||||
|
uint32_t address; /* Memory address */
|
||||||
|
uint16_t code[4]; /* Instruction bytes */
|
||||||
|
uint8_t codeLength; /* Number of items in code */
|
||||||
|
uint8_t isPC; /* Set if PC is on this line */
|
||||||
|
|
||||||
|
/* Display text */
|
||||||
|
struct {
|
||||||
|
char *address; /* Memory address */
|
||||||
|
char *code[4]; /* Instruction bytes */
|
||||||
|
char *mnemonic; /* Instruction mnemonic */
|
||||||
|
char *operands[3]; /* Instruction operands */
|
||||||
|
uint8_t operandsLength; /* Number of items in operands */
|
||||||
|
} text;
|
||||||
|
|
||||||
|
} VBU_DasmLine;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/******************************* API Commands ********************************/
|
||||||
|
|
||||||
|
VBUAPI int vbuCodeSize (VB *sim, uint32_t address);
|
||||||
|
VBUAPI VBU_DasmConfig* vbuDasmInit (VBU_DasmConfig *config);
|
||||||
|
VBUAPI VBU_DasmLine* vbuDisassemble(VB *sim, uint32_t address, VBU_DasmConfig *config, unsigned length, int line);
|
||||||
|
VBUAPI void* vbuFromISX (void *bytes, size_t length, size_t *romLength);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VBU_H_ */
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Dedicated audio output processor
|
||||||
|
class Audio extends AudioWorkletProcessor {
|
||||||
|
|
||||||
|
// Instance fields
|
||||||
|
buffers; // Input sample buffer queue
|
||||||
|
core; // Communications with core thread
|
||||||
|
dom; // Communications with DOM thread
|
||||||
|
offset; // Offset into oldest buffer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////// Initialization Methods //////////////////////////
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.port.onmessage = async e=>{
|
||||||
|
await this.#construct(e.data.core);
|
||||||
|
this.port.postMessage(0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asynchronous constructor
|
||||||
|
async #construct(core) {
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.buffers = [];
|
||||||
|
this.core = core;
|
||||||
|
this.dom = this.port;
|
||||||
|
this.offset = 0;
|
||||||
|
|
||||||
|
// Configure communications
|
||||||
|
this.core.onmessage = e=>this.#onCore(e.data);
|
||||||
|
this.dom .onmessage = e=>this.#onDOM (e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// 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][x] = output[1][x] = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer samples from the oldest buffer
|
||||||
|
let buffer = this.buffers[0];
|
||||||
|
let y = this.offset;
|
||||||
|
for (; 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 (y == buffer.length) {
|
||||||
|
if (empty == null)
|
||||||
|
empty = [];
|
||||||
|
empty.push(this.buffers.shift().buffer);
|
||||||
|
this.offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer is not empty
|
||||||
|
else this.offset = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return emptied sample buffers to the core thread
|
||||||
|
if (empty != null)
|
||||||
|
this.core.postMessage(empty, empty);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Event Handlers //////////////////////////////
|
||||||
|
|
||||||
|
// Message received from core thread
|
||||||
|
#onCore(e) {
|
||||||
|
this.buffers.push(new Float32Array(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message received from DOM thread
|
||||||
|
#onDOM(e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
registerProcessor("shrooms-vb", Audio);
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
"use strict";
|
||||||
|
export default {
|
||||||
|
|
||||||
|
// Core
|
||||||
|
VB: {
|
||||||
|
|
||||||
|
// System registers
|
||||||
|
ADTRE: 25,
|
||||||
|
CHCW : 24,
|
||||||
|
ECR : 4,
|
||||||
|
EIPC : 0,
|
||||||
|
EIPSW: 1,
|
||||||
|
FEPC : 2,
|
||||||
|
FEPSW: 3,
|
||||||
|
PIR : 6,
|
||||||
|
PSW : 5,
|
||||||
|
TKCW : 7,
|
||||||
|
|
||||||
|
// Memory access data types
|
||||||
|
S8 : 0,
|
||||||
|
U8 : 1,
|
||||||
|
S16: 2,
|
||||||
|
U16: 3,
|
||||||
|
S32: 4,
|
||||||
|
F32: 5,
|
||||||
|
|
||||||
|
// Option keys
|
||||||
|
PSEUDO_HALT: 0,
|
||||||
|
|
||||||
|
// Controller buttons
|
||||||
|
PWR: 0x0001,
|
||||||
|
SGN: 0x0002,
|
||||||
|
A : 0x0004,
|
||||||
|
B : 0x0008,
|
||||||
|
RT : 0x0010,
|
||||||
|
LT : 0x0020,
|
||||||
|
RU : 0x0040,
|
||||||
|
RR : 0x0080,
|
||||||
|
LR : 0x0100,
|
||||||
|
LL : 0x0200,
|
||||||
|
LD : 0x0400,
|
||||||
|
LU : 0x0800,
|
||||||
|
STA: 0x1000,
|
||||||
|
SEL: 0x2000,
|
||||||
|
RL : 0x4000,
|
||||||
|
RD : 0x8000
|
||||||
|
},
|
||||||
|
|
||||||
|
// Utility
|
||||||
|
VBU: {
|
||||||
|
|
||||||
|
// Disassembler options
|
||||||
|
"0X" : 0,
|
||||||
|
C : 1,
|
||||||
|
DEST_FIRST: 1,
|
||||||
|
DEST_LAST : 0,
|
||||||
|
DOLLAR : 1,
|
||||||
|
E : 0,
|
||||||
|
H : 2,
|
||||||
|
INSIDE : 1,
|
||||||
|
JOINED : 0,
|
||||||
|
L : 0,
|
||||||
|
LOWER : 1,
|
||||||
|
NAMES : 1,
|
||||||
|
NONE : 0,
|
||||||
|
NUMBER : 1,
|
||||||
|
NUMBERS : 0,
|
||||||
|
OUTSIDE : 0,
|
||||||
|
SPLIT : 1,
|
||||||
|
UPPER : 0,
|
||||||
|
Z : 1
|
||||||
|
},
|
||||||
|
|
||||||
|
// Web interface
|
||||||
|
web: {
|
||||||
|
|
||||||
|
// Break types
|
||||||
|
BREAK_FRAME: 1,
|
||||||
|
BREAK_POINT: 2,
|
||||||
|
|
||||||
|
// Anaglyph colors
|
||||||
|
STEREO_CYAN : 0x00C6F0,
|
||||||
|
STEREO_GREEN : 0x00B400,
|
||||||
|
STEREO_MAGENTA: 0xC800FF,
|
||||||
|
STEREO_RED : 0xFF0000
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
+611
@@ -0,0 +1,611 @@
|
|||||||
|
"use strict";
|
||||||
|
import Constants from /***/"./Constants.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////// Core /////////////////////////////////////
|
||||||
|
|
||||||
|
// Emulation processor
|
||||||
|
new class Core {
|
||||||
|
|
||||||
|
// Instance fields
|
||||||
|
audio; // Audio communication
|
||||||
|
automatic; // Automatic emulation state
|
||||||
|
clocked; // Clocked emulation state
|
||||||
|
dom; // DOM communication
|
||||||
|
mallocs; // Memory allocations by pointer
|
||||||
|
pointerType; // TypedArray for WebAssembly pointers
|
||||||
|
sims; // Simulations by pointer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////// Initialization Methods //////////////////////////
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
onmessage = async e=>{
|
||||||
|
await this.#construct(e.data.audio, e.data.wasmUrl);
|
||||||
|
this.dom.postMessage(0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asynchronous constructor
|
||||||
|
async #construct(audio, wasmUrl) {
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.mallocs = new Map();
|
||||||
|
this.sims = new Map();
|
||||||
|
|
||||||
|
// DOM thread communication
|
||||||
|
this.dom = globalThis;
|
||||||
|
this.dom.onmessage = e=>this[e.data.command](e.data);
|
||||||
|
|
||||||
|
// Instantiate the WebAssembly module
|
||||||
|
this.wasm = (await WebAssembly.instantiateStreaming(
|
||||||
|
fetch(wasmUrl), {
|
||||||
|
env: {
|
||||||
|
emscripten_notify_memory_growth: ()=>this.#onGrowth()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
Object.assign(this, this.wasm.instance.exports);
|
||||||
|
this.pointerType = this.PointerSize() == 8 ?
|
||||||
|
BigUint64Array : Uint32Array;
|
||||||
|
|
||||||
|
// Configure audio state
|
||||||
|
this.audio = audio;
|
||||||
|
audio.buffers = [0,0,0].map(v=>new Float32Array(41700 / 50 * 2));
|
||||||
|
audio.samples =
|
||||||
|
this.#malloc(41700 / 50 * 2, audio, "samples", Float32Array);
|
||||||
|
audio.onmessage = e=>this.#onAudio(e.data);
|
||||||
|
|
||||||
|
// Configure emulation states
|
||||||
|
this.automatic = { emulating: false };
|
||||||
|
this.clocked = {};
|
||||||
|
for (let s of [ this.automatic, this.clocked ]) {
|
||||||
|
s.clocks = this.#malloc(1, s, "clocks" , Uint32Array);
|
||||||
|
s.pointers = this.#malloc(1, s, "pointers", this.pointerType);
|
||||||
|
s.sims = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////// Core Commands //////////////////////////////
|
||||||
|
|
||||||
|
// Instantiate sims
|
||||||
|
createSims(message) {
|
||||||
|
let sims = new Array(message.count);
|
||||||
|
let size = this.vbSizeOf();
|
||||||
|
|
||||||
|
// Process all sims
|
||||||
|
for (let x = 0; x < message.count; x++) {
|
||||||
|
let sim = {
|
||||||
|
canvas : null,
|
||||||
|
keys : Constants.VB.SGN,
|
||||||
|
pointer : sims[x] = this.CreateSim()
|
||||||
|
};
|
||||||
|
this.sims.set(sim.pointer, sim);
|
||||||
|
|
||||||
|
// Video
|
||||||
|
sim.pixels = this.#getPixels(sim);
|
||||||
|
sim.image = new ImageData(sim.pixels, 384, 224);
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
sim.samples = this.#noalloc(
|
||||||
|
this.GetExtSamples(sim.pointer),
|
||||||
|
41700 / 50 * 2, sim, "samples", Float32Array
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dom.postMessage({
|
||||||
|
sims : sims,
|
||||||
|
promised: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produce disassembly from a sim
|
||||||
|
disassemble(message) {
|
||||||
|
|
||||||
|
// Disassemble from the simulation
|
||||||
|
let dasm = message.config == null ?
|
||||||
|
this.vbuDisassemble(
|
||||||
|
message.sim,
|
||||||
|
message.address,
|
||||||
|
0,
|
||||||
|
message.length,
|
||||||
|
message.line
|
||||||
|
)
|
||||||
|
:
|
||||||
|
this.Disassemble(
|
||||||
|
message.config.bcondNotation,
|
||||||
|
message.config.conditionCase,
|
||||||
|
message.config.conditionCL,
|
||||||
|
message.config.conditionEZ,
|
||||||
|
message.config.conditionNotation,
|
||||||
|
message.config.hexCase,
|
||||||
|
message.config.hexNotation,
|
||||||
|
message.config.immediateNotation,
|
||||||
|
message.config.memoryNotation,
|
||||||
|
message.config.mnemonicCase,
|
||||||
|
message.config.operandOrder,
|
||||||
|
message.config.programCase,
|
||||||
|
message.config.programNotation,
|
||||||
|
message.config.setfNotation,
|
||||||
|
message.config.systemCase,
|
||||||
|
message.config.systemNotation,
|
||||||
|
message.sim,
|
||||||
|
message.address,
|
||||||
|
message.length,
|
||||||
|
message.line
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
// A memory error occurred
|
||||||
|
if (dasm == 0) {
|
||||||
|
this.dom.postMessage({
|
||||||
|
promised: message.promised,
|
||||||
|
success : false
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve all disassembly data into a working buffer
|
||||||
|
let pointer = this.Realloc(0, message.length * 17 * 4);
|
||||||
|
let buffer = new Uint32Array(
|
||||||
|
this.memory.buffer, pointer, message.length * 17);
|
||||||
|
this.GetDasm(pointer, dasm, message.length);
|
||||||
|
|
||||||
|
// Consume output lines
|
||||||
|
let lines = new Array(message.length);
|
||||||
|
for (let x = 0, z = 0; x < lines.length; x++) {
|
||||||
|
let line = lines[x] = { text: {} };
|
||||||
|
line.address = buffer[z++];
|
||||||
|
line.code = new Array(buffer[z++]);
|
||||||
|
for (let y = 0; y < line.code.length; y++)
|
||||||
|
line.code[y] = buffer[z++];
|
||||||
|
z += 4 - line.code.length;
|
||||||
|
line.isPC = buffer[z++] != 0;
|
||||||
|
line.text.address = this.#string(dasm + buffer[z++], true);
|
||||||
|
line.text.code = new Array(line.code.length);
|
||||||
|
for (let y = 0; y < line.code.length; y++)
|
||||||
|
line.text.code[y] = this.#string(dasm + buffer[z++], true);
|
||||||
|
z += 4 - line.code.length;
|
||||||
|
line.text.mnemonic = this.#string(dasm + buffer[z++], true);
|
||||||
|
line.text.operands = new Array(buffer[z++]);
|
||||||
|
for (let y = 0; y < line.text.operands.length; y++)
|
||||||
|
line.text.operands[y] = this.#string(dasm + buffer[z++], true);
|
||||||
|
z += 3 - line.text.operands.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory cleanup
|
||||||
|
this.Realloc(pointer, 0);
|
||||||
|
this.Realloc(dasm , 0);
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
this.dom.postMessage({
|
||||||
|
success : true,
|
||||||
|
lines : lines,
|
||||||
|
promised: message.promised
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emulate automatically
|
||||||
|
emulateAutomatic(message) {
|
||||||
|
|
||||||
|
// Configure sims
|
||||||
|
this.automatic.pointers = this.#realloc(
|
||||||
|
this.automatic.pointers, message.sims.length);
|
||||||
|
for (let x = 0; x < message.sims.length; x++) {
|
||||||
|
this.automatic.pointers[x] = message.sims[x];
|
||||||
|
this.automatic.sims [x] = this.sims.get(message.sims[x]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the DOM thread
|
||||||
|
this.dom.postMessage({ promised: true });
|
||||||
|
|
||||||
|
// Begin automatic emulation
|
||||||
|
this.automatic.emulating = true;
|
||||||
|
this.#autoEmulate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emulate for a given number of clocks
|
||||||
|
emulateClocked(message) {
|
||||||
|
|
||||||
|
// Configure sims
|
||||||
|
this.clocked.pointers = this.#realloc(
|
||||||
|
this.clocked.pointers, message.sims.length);
|
||||||
|
for (let x = 0; x < message.sims.length; x++) {
|
||||||
|
this.clocked.pointers[x] = message.sims[x];
|
||||||
|
this.clocked.sims [x] = this.sims.get(message.sims[x]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process simulations
|
||||||
|
let broke = false;
|
||||||
|
this.clocked.clocks[0] = message.clocks;
|
||||||
|
while (!broke && this.clocked.clocks[0] != 0) {
|
||||||
|
|
||||||
|
// Process simulations until a suspension
|
||||||
|
this.Emulate(
|
||||||
|
this.clocked.pointers.pointer,
|
||||||
|
message.sims.length,
|
||||||
|
this.clocked.clocks.pointer
|
||||||
|
);
|
||||||
|
|
||||||
|
// Monitor break conditions
|
||||||
|
for (let x = 0; x < message.sims.length; x++) {
|
||||||
|
let sim = this.clocked.sims[x];
|
||||||
|
sim.breaks = this.GetBreaks(sim.pointer);
|
||||||
|
if (breaks & Constants.web.BREAK_POINT)
|
||||||
|
broke = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update images
|
||||||
|
for (let sim of this.clocked.sims) {
|
||||||
|
if (!(sim.breaks & Constants.web.BREAK_FRAME))
|
||||||
|
continue;
|
||||||
|
this.GetPixels(sim.pointer);
|
||||||
|
sim.context.putImageData(sim.image, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify DOM thread
|
||||||
|
this.dom.postMessage({
|
||||||
|
promised: true,
|
||||||
|
broke : broke,
|
||||||
|
clocks : this.clocked.clocks[0]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to produce a ROM from an ISX debugger file
|
||||||
|
fromISX(message) {
|
||||||
|
|
||||||
|
// Transfer the input data into WebAssembly memory
|
||||||
|
let input = new Uint8Array(message.data);
|
||||||
|
let inPtr = this.Realloc(0, input.length);
|
||||||
|
let inMem = new Uint8Array(this.memory.buffer, inPtr, input.length);
|
||||||
|
for (let x = 0; x < input.length; x++)
|
||||||
|
inMem[x] = input[x];
|
||||||
|
|
||||||
|
// Attempt to decode the ISX file as a ROM
|
||||||
|
let outPtr = this.FromISX(inPtr, input.length);
|
||||||
|
this.Realloc(inPtr, 0);
|
||||||
|
|
||||||
|
// The data is not an ISX file
|
||||||
|
if (outPtr == 0) {
|
||||||
|
this.dom.postMessage({ promised: true, data: null });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer the decoded ROM from WebAssembly memory
|
||||||
|
let output = new Uint8Array(this.GetISXLength(outPtr));
|
||||||
|
let outMem = new Uint8Array(this.memory.buffer,
|
||||||
|
this.GetISXROM(outPtr), output.length);
|
||||||
|
for (let x = 0; x < output.length; x++)
|
||||||
|
output[x] = outMem[x];
|
||||||
|
this.Realloc(outPtr, 0);
|
||||||
|
|
||||||
|
// Notify DOM thread
|
||||||
|
this.dom.postMessage({
|
||||||
|
promised: true,
|
||||||
|
data : output.buffer
|
||||||
|
}, [ output.buffer ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset simulation state
|
||||||
|
reset(message) {
|
||||||
|
this.vbReset(message.sim);
|
||||||
|
this.dom.postMessage({ promised: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify anaglyph colors
|
||||||
|
setAnaglyph(message) {
|
||||||
|
this.SetAnaglyph(message.sim, message.left, message.right);
|
||||||
|
this.dom.postMessage({ promised: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the OffscreenCanvas that goes with a sim
|
||||||
|
setCanvas(message) {
|
||||||
|
let sim = this.sims.get(message.sim);
|
||||||
|
sim.canvas = message.canvas;
|
||||||
|
sim.context = sim.canvas.getContext("2d");
|
||||||
|
sim.context.putImageData(sim.image, 0, 0);
|
||||||
|
this.dom.postMessage({ promised: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify a game pak RAM buffer
|
||||||
|
setCartRAM(message) {
|
||||||
|
this.#setCartMemory(message.sim, message.data,
|
||||||
|
this.vbGetCartRAM, this.vbSetCartRAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify a game pak ROM buffer
|
||||||
|
setCartROM(message) {
|
||||||
|
this.#setCartMemory(message.sim, message.data,
|
||||||
|
this.vbGetCartROM, this.vbSetCartROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify new game pad keys
|
||||||
|
setKeys(message) {
|
||||||
|
this.vbSetKeys(message.sim, message.keys);
|
||||||
|
this.dom.postMessage({ promised: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify audio panning
|
||||||
|
setPanning(message) {
|
||||||
|
this.SetPanning(message.sim, message.panning);
|
||||||
|
this.dom.postMessage({ promised: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify a new communication peer
|
||||||
|
setPeer(message) {
|
||||||
|
let orphaned = [];
|
||||||
|
let prev = this.vbGetPeer(message.sim);
|
||||||
|
if (prev != message.peer) {
|
||||||
|
if (prev != 0) // Sim's previous peer has been orphaned
|
||||||
|
orphaned.push(prev);
|
||||||
|
if (message.peer != 0) {
|
||||||
|
prev = this.vbGetPeer(message.peer);
|
||||||
|
if (prev != null) // Peer's previous peer has been orphaned
|
||||||
|
orphaned.push(prev);
|
||||||
|
}
|
||||||
|
this.vbSetPeer(message.sim, message.peer);
|
||||||
|
}
|
||||||
|
this.dom.postMessage({
|
||||||
|
orphaned: orphaned,
|
||||||
|
promised: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify audio volume
|
||||||
|
setVolume(message) {
|
||||||
|
this.SetVolume(message.sim, message.volume);
|
||||||
|
this.dom.postMessage({ promised: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suspend automatic emulation
|
||||||
|
suspend(message) {
|
||||||
|
this.automatic.emulating = false;
|
||||||
|
this.dom.postMessage({ promised: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Event Handlers //////////////////////////////
|
||||||
|
|
||||||
|
// Message from audio thread
|
||||||
|
#onAudio(e) {
|
||||||
|
|
||||||
|
// Output staged images
|
||||||
|
if (this.automatic.emulating && this.audio.buffers.length == 0) {
|
||||||
|
for (let sim of this.automatic.sims)
|
||||||
|
sim.context.putImageData(sim.image, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire the emptied buffers and resume emulation
|
||||||
|
this.audio.buffers.push(... e.map(b=>new Float32Array(b)));
|
||||||
|
this.#autoEmulate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebAssembly memory has grown
|
||||||
|
#onGrowth() {
|
||||||
|
for (let prev of this.mallocs.values()) {
|
||||||
|
let buffer = new prev.constructor(
|
||||||
|
this.memory.buffer, prev.pointer, prev.size);
|
||||||
|
Object.assign(buffer, {
|
||||||
|
assign : prev.assign,
|
||||||
|
pointer: prev.pointer,
|
||||||
|
size : prev.size,
|
||||||
|
target : prev.target
|
||||||
|
});
|
||||||
|
this.mallocs.set(buffer.pointer, buffer);
|
||||||
|
this.#updateTarget(buffer);
|
||||||
|
}
|
||||||
|
for (let sim of this.sims.values()) {
|
||||||
|
sim.pixels = this.#getPixels(sim);
|
||||||
|
sim.image = new ImageData(sim.pixels, 384, 224);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Automatic emulation processing
|
||||||
|
#autoEmulate() {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (!this.automatic.emulating)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Process all remaining audio buffers
|
||||||
|
while (this.audio.buffers.length != 0) {
|
||||||
|
|
||||||
|
// Reset sample output
|
||||||
|
for (let sim of this.automatic.sims) {
|
||||||
|
this.vbSetSamples(sim.pointer, sim.samples.pointer,
|
||||||
|
Constants.VB.F32, 41700 / 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process all clocks
|
||||||
|
this.automatic.clocks[0] = 400000; // 0.02s
|
||||||
|
while (this.automatic.clocks[0] != 0) {
|
||||||
|
this.Emulate(
|
||||||
|
this.automatic.pointers.pointer,
|
||||||
|
this.automatic.sims.length,
|
||||||
|
this.automatic.clocks.pointer
|
||||||
|
);
|
||||||
|
|
||||||
|
// Too many buffers left to output video
|
||||||
|
if (this.audio.buffers.length > 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Stage the next video image
|
||||||
|
for (let sim of this.automatic.sims) {
|
||||||
|
let breaks = this.GetBreaks(sim.pointer);
|
||||||
|
if (breaks & Constants.web.BREAK_FRAME)
|
||||||
|
this.GetPixels(sim.pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mix and output audio samples
|
||||||
|
let buffer = this.audio.buffers.shift();
|
||||||
|
this.Mix(
|
||||||
|
this.audio.samples.pointer,
|
||||||
|
this.automatic.pointers.pointer,
|
||||||
|
this.automatic.sims.length
|
||||||
|
);
|
||||||
|
for (let x = 0; x < buffer.length; x++)
|
||||||
|
buffer[x] = this.audio.samples[x];
|
||||||
|
this.audio.postMessage(buffer.buffer, [ buffer.buffer ]);
|
||||||
|
|
||||||
|
// Output staged images if there's one audio buffer to go
|
||||||
|
if (this.audio.buffers.length != 1)
|
||||||
|
continue;
|
||||||
|
for (let sim of this.automatic.sims)
|
||||||
|
sim.context.putImageData(sim.image, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete an allocated buffer in WebAssembly memory
|
||||||
|
#free(buffer) {
|
||||||
|
this.mallocs.delete(buffer.pointer);
|
||||||
|
this.Realloc(buffer.pointer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a sim's pixel buffer
|
||||||
|
#getPixels(sim) {
|
||||||
|
return this.#noalloc(
|
||||||
|
this.GetExtPixels(sim.pointer),
|
||||||
|
384*224*4, sim, "pixels", Uint8ClampedArray
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate memory in WebAssembly and register the buffer
|
||||||
|
#malloc(count, target = null, assign = null, type = Uint8ClampedArray) {
|
||||||
|
return this.#noalloc(
|
||||||
|
this.Realloc(0, count * type.BYTES_PER_ELEMENT),
|
||||||
|
count, target, assign, type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a buffer in WebAssembly memory without allocating it
|
||||||
|
#noalloc(pointer, count, target=null, assign=null, type=Uint8ClampedArray){
|
||||||
|
let buffer = new type(this.memory.buffer, pointer, count);
|
||||||
|
Object.assign(buffer, {
|
||||||
|
assign : assign?.split("."),
|
||||||
|
count : count,
|
||||||
|
pointer: pointer,
|
||||||
|
target : target
|
||||||
|
});
|
||||||
|
this.mallocs.set(pointer, buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize a previously allocated buffer in WebAssembly memory
|
||||||
|
#realloc(prev, count) {
|
||||||
|
this.mallocs.delete(prev.pointer);
|
||||||
|
let pointer = this.Realloc(prev.pointer,
|
||||||
|
count * prev.constructor.prototype.BYTES_PER_ELEMENT);
|
||||||
|
let buffer = new prev.constructor(this.memory.buffer, pointer, count);
|
||||||
|
Object.assign(buffer, {
|
||||||
|
assign : prev.assign,
|
||||||
|
count : count,
|
||||||
|
pointer: pointer,
|
||||||
|
target : prev.target
|
||||||
|
});
|
||||||
|
this.mallocs.set(pointer, buffer);
|
||||||
|
this.#updateTarget(buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute anaglyph color values
|
||||||
|
#setAnaglyph(sim, left, right) {
|
||||||
|
|
||||||
|
// Split out the RGB channels
|
||||||
|
let color = left | right;
|
||||||
|
let stereo = [
|
||||||
|
color >> 16 & 0xFF,
|
||||||
|
color >> 8 & 0xFF,
|
||||||
|
color & 0xFF
|
||||||
|
];
|
||||||
|
|
||||||
|
// Compute scaled RGB values by output level
|
||||||
|
sim.anaglyph = new Array(256);
|
||||||
|
for (let x = 0; x < 256; x++) {
|
||||||
|
let level = sim.anaglyph[x] = new Array(3);
|
||||||
|
for (let y = 0; y < 3; y++)
|
||||||
|
level[y] = Math.round(x * stereo[y] / 255.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which channels are in each eye
|
||||||
|
sim.anaglyph.left = [];
|
||||||
|
sim.anaglyph.right = [];
|
||||||
|
for (let x = 0, y = 16; x < 3; x++, y -= 8) {
|
||||||
|
if (left >> y & 0xFF)
|
||||||
|
sim.anaglyph.left .push(x);
|
||||||
|
if (right >> y & 0xFF)
|
||||||
|
sim.anaglyph.right.push(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify a game pak memory buffer
|
||||||
|
#setCartMemory(sim, mem, getter, setter) {
|
||||||
|
|
||||||
|
// Working variables
|
||||||
|
let cart = new Uint8Array(mem);
|
||||||
|
let prev = getter(sim);
|
||||||
|
let cur = this.Realloc(0, cart.length);
|
||||||
|
mem = new Uint8Array(this.memory.buffer, cur, cart.length);
|
||||||
|
|
||||||
|
// Transfer the data into core memory
|
||||||
|
for (let x = 0; x < mem.length; x++)
|
||||||
|
mem[x] = cart[x];
|
||||||
|
|
||||||
|
// Assign the ROM to the simulation
|
||||||
|
let success = setter(sim, cur, mem.length) == 0;
|
||||||
|
if (success) {
|
||||||
|
if (prev != 0)
|
||||||
|
this.Realloc(prev, 0);
|
||||||
|
} else this.Realloc(cur, 0);
|
||||||
|
|
||||||
|
// Reply to the DOM thread
|
||||||
|
this.dom.postMessage({
|
||||||
|
success : success,
|
||||||
|
promised: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a C string from WebAssembly memory
|
||||||
|
#string(address, indirect = false) {
|
||||||
|
if (address == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (indirect) {
|
||||||
|
let next = new this.pointerType(this.memory.buffer, address, 1)[0];
|
||||||
|
address = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
let length = 0;
|
||||||
|
let memory = new Uint8Array(this.memory.buffer);
|
||||||
|
for (let addr = address; memory[addr++] != 0; length++);
|
||||||
|
return (Array.from(memory.slice(address, address + length))
|
||||||
|
.map(b=>String.fromCodePoint(b)).join(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update an allocated buffer's assignment in its monitor object
|
||||||
|
#updateTarget(buffer) {
|
||||||
|
if (buffer.target == null)
|
||||||
|
return;
|
||||||
|
let obj = buffer.target;
|
||||||
|
let assign = buffer.assign.slice();
|
||||||
|
while (assign.length > 1)
|
||||||
|
obj = obj[assign.shift()];
|
||||||
|
obj[assign[0]] = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}();
|
||||||
@@ -0,0 +1,976 @@
|
|||||||
|
"use strict";
|
||||||
|
import Constants from /**/"./Constants.js";
|
||||||
|
|
||||||
|
// Instantiation guard
|
||||||
|
const GUARD = Symbol();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////// DasmConfig //////////////////////////////////
|
||||||
|
|
||||||
|
// Disassembler option settings
|
||||||
|
class DasmConfig {
|
||||||
|
|
||||||
|
// Instance fields
|
||||||
|
#bcondNotation;
|
||||||
|
#conditionCase;
|
||||||
|
#conditionCL;
|
||||||
|
#conditionEZ;
|
||||||
|
#conditionNotation;
|
||||||
|
#hexCase;
|
||||||
|
#hexNotation;
|
||||||
|
#immediateNotation;
|
||||||
|
#memoryNotation;
|
||||||
|
#mnemonicCase;
|
||||||
|
#operandOrder;
|
||||||
|
#programCase;
|
||||||
|
#programNotation;
|
||||||
|
#setfNotation;
|
||||||
|
#systemCase;
|
||||||
|
#systemNotation;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////// Initialization Methods //////////////////////////
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#bcondNotation = Constants.VBU.JOINED;
|
||||||
|
this.#conditionCase = Constants.VBU.LOWER;
|
||||||
|
this.#conditionCL = Constants.VBU.L;
|
||||||
|
this.#conditionEZ = Constants.VBU.Z;
|
||||||
|
this.#conditionNotation = Constants.VBU.NAMES;
|
||||||
|
this.#hexCase = Constants.VBU.UPPER;
|
||||||
|
this.#hexNotation = Constants.VBU["0X"];
|
||||||
|
this.#immediateNotation = Constants.VBU.NONE;
|
||||||
|
this.#memoryNotation = Constants.VBU.OUTSIDE;
|
||||||
|
this.#mnemonicCase = Constants.VBU.UPPER;
|
||||||
|
this.#operandOrder = Constants.VBU.DEST_LAST;
|
||||||
|
this.#programCase = Constants.VBU.LOWER;
|
||||||
|
this.#programNotation = Constants.VBU.NAMES;
|
||||||
|
this.#setfNotation = Constants.VBU.SPLIT;
|
||||||
|
this.#systemCase = Constants.VBU.LOWER;
|
||||||
|
this.#systemNotation = Constants.VBU.NAMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////// Property Accessors ////////////////////////////
|
||||||
|
|
||||||
|
get bcondNotation() { return this.#bcondNotation; }
|
||||||
|
set bcondNotation(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.JOINED:
|
||||||
|
case Constants.VBU.SPLIT : break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#bcondNotation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get conditionCase() { return this.#conditionCase; }
|
||||||
|
set conditionCase(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.LOWER:
|
||||||
|
case Constants.VBU.UPPER: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#conditionCase = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get conditionCL() { return this.#conditionCL; }
|
||||||
|
set conditionCL(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.C:
|
||||||
|
case Constants.VBU.L: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#conditionCL = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get conditionEZ() { return this.#conditionEZ; }
|
||||||
|
set conditionEZ(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.E:
|
||||||
|
case Constants.VBU.Z: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#conditionEZ = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get conditionNotation() { return this.#conditionNotation; }
|
||||||
|
set conditionNotation(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.NAMES :
|
||||||
|
case Constants.VBU.NUMBERS: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#conditionNotation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hexCase() { return this.#hexCase; }
|
||||||
|
set hexCase(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.LOWER:
|
||||||
|
case Constants.VBU.UPPER: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#hexCase = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hexNotation() { return this.#hexNotation; }
|
||||||
|
set hexNotation(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU["0X"] :
|
||||||
|
case Constants.VBU.DOLLAR:
|
||||||
|
case Constants.VBU.H : break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#hexNotation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get immediateNotation() { return this.#immediateNotation; }
|
||||||
|
set immediateNotation(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.NONE :
|
||||||
|
case Constants.VBU.NUMBER: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#immediateNotation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get memoryNotation() { return this.#memoryNotation; }
|
||||||
|
set memoryNotation(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.INSIDE :
|
||||||
|
case Constants.VBU.OUTSIDE: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#memoryNotation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get mnemonicCase() { return this.#mnemonicCase; }
|
||||||
|
set mnemonicCase(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.LOWER:
|
||||||
|
case Constants.VBU.UPPER: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#mnemonicCase = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get operandOrder() { return this.#operandOrder; }
|
||||||
|
set operandOrder(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.DEST_FIRST:
|
||||||
|
case Constants.VBU.DEST_LAST : break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#operandOrder = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get programCase() { return this.#programCase; }
|
||||||
|
set programCase(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.LOWER:
|
||||||
|
case Constants.VBU.UPPER: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#programCase = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get programNotation() { return this.#programNotation; }
|
||||||
|
set programNotation(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.NAMES :
|
||||||
|
case Constants.VBU.NUMBERS: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#programNotation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get setfNotation() { return this.#setfNotation; }
|
||||||
|
set setfNotation(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.JOINED:
|
||||||
|
case Constants.VBU.SPLIT : break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#setfNotation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get systemCase() { return this.#systemCase; }
|
||||||
|
set systemCase(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.LOWER:
|
||||||
|
case Constants.VBU.UPPER: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#systemCase = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get systemNotation() { return this.#systemNotation; }
|
||||||
|
set systemNotation(value) {
|
||||||
|
switch (value) {
|
||||||
|
case Constants.VBU.NAMES :
|
||||||
|
case Constants.VBU.NUMBERS: break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
this.#systemNotation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////// DasmLine ///////////////////////////////////
|
||||||
|
|
||||||
|
// One line of disassembler output
|
||||||
|
class DasmLine {
|
||||||
|
|
||||||
|
// Instance fields
|
||||||
|
#address;
|
||||||
|
#addressText;
|
||||||
|
#code;
|
||||||
|
#codeText;
|
||||||
|
#isPC;
|
||||||
|
#mnemonicText;
|
||||||
|
#operandText;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////// Initialization Methods //////////////////////////
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (arguments[0] != GUARD)
|
||||||
|
throw new Error("Cannot be instantiated.");
|
||||||
|
let line = arguments[1];
|
||||||
|
this.#address = line.address;
|
||||||
|
this.#addressText = line.text.address;
|
||||||
|
this.#code = line.code;
|
||||||
|
this.#codeText = line.text.code;
|
||||||
|
this.#isPC = line.isPC;
|
||||||
|
this.#mnemonicText = line.text.mnemonic;
|
||||||
|
this.#operandText = line.text.operands;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////// Property Accessors ////////////////////////////
|
||||||
|
|
||||||
|
get address() { return this.#address; }
|
||||||
|
get code () { return this.#code.slice(); }
|
||||||
|
get isPC () { return this.#isPC; }
|
||||||
|
get text () {
|
||||||
|
return {
|
||||||
|
address : this.#addressText,
|
||||||
|
code : this.#codeText.slice(),
|
||||||
|
mnemonic: this.#mnemonicText,
|
||||||
|
operands: this.#operandText.slice()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Express self as a plain object
|
||||||
|
toObject() {
|
||||||
|
return {
|
||||||
|
address: this.address,
|
||||||
|
code : Array.from(this.#code),
|
||||||
|
isPC : this.isPC,
|
||||||
|
text : this.text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////// Sim /////////////////////////////////////
|
||||||
|
|
||||||
|
// Simulation instance
|
||||||
|
class Sim extends HTMLElement {
|
||||||
|
|
||||||
|
// Instance fields
|
||||||
|
#anaglyph; // Anaglyph color values
|
||||||
|
#canvas; // Canvas element
|
||||||
|
#core; // Core proxy
|
||||||
|
#emulating; // Current emulation status
|
||||||
|
#keys; // Controller state
|
||||||
|
#panning; // Audio stereo balance
|
||||||
|
#peer; // Communication peer
|
||||||
|
#pointer; // Pointer in core memory
|
||||||
|
#volume; // Audio output volume
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////// Initialization Methods //////////////////////////
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (arguments[0] != GUARD)
|
||||||
|
throw new Error("Must be created via VB.create()");
|
||||||
|
super();
|
||||||
|
this.proxy = {
|
||||||
|
construct : (core, pointer)=>this.#construct(core, pointer),
|
||||||
|
isEmulating : ()=>this.#emulating,
|
||||||
|
setEmulating: e =>this.#emulating = e,
|
||||||
|
setPeer : p =>this.#peer = p
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asynchronous constructor
|
||||||
|
async #construct(core, pointer) {
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.#anaglyph = [ VB.STEREO_RED, VB.STEREO_CYAN ];
|
||||||
|
this.#core = core;
|
||||||
|
this.#emulating = false;
|
||||||
|
this.#keys = Constants.VB.SGN;
|
||||||
|
this.#panning = 0.0;
|
||||||
|
this.#peer = null;
|
||||||
|
this.#pointer = pointer;
|
||||||
|
this.#volume = 1.0;
|
||||||
|
delete this.proxy;
|
||||||
|
|
||||||
|
// Create a <canvas> for the video image
|
||||||
|
let canvas = this.#canvas = document.createElement("canvas");
|
||||||
|
Object.assign(canvas, { width: 384, height: 224 });
|
||||||
|
canvas.style.imageRendering = "pixelated";
|
||||||
|
|
||||||
|
// Configure elements
|
||||||
|
Object.assign(this.style, {
|
||||||
|
display : "inline-block",
|
||||||
|
height : "224px",
|
||||||
|
position: "relative",
|
||||||
|
width : "384px"
|
||||||
|
});
|
||||||
|
Object.assign(canvas.style, {
|
||||||
|
height : "100%",
|
||||||
|
imageRendering: "pixelated",
|
||||||
|
left : "0",
|
||||||
|
position : "absolute",
|
||||||
|
top : "0",
|
||||||
|
width : "100%"
|
||||||
|
});
|
||||||
|
this.append(canvas);
|
||||||
|
|
||||||
|
// Send control of the canvas to the core worker
|
||||||
|
let offscreen = canvas.transferControlToOffscreen();
|
||||||
|
await core.toCore({
|
||||||
|
command : "setCanvas",
|
||||||
|
promised : true,
|
||||||
|
sim : pointer,
|
||||||
|
canvas : offscreen,
|
||||||
|
transfers: [ offscreen ]
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////// Property Accessors ////////////////////////////
|
||||||
|
|
||||||
|
get anaglyph() { return this.#anaglyph.slice(); }
|
||||||
|
get core () { return this.#core.core ; }
|
||||||
|
get keys () { return this.#keys ; }
|
||||||
|
get panning () { return this.#panning ; }
|
||||||
|
get peer () { return this.#peer ; }
|
||||||
|
get volume () { return this.#volume ; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Delete the sim
|
||||||
|
async delete() {
|
||||||
|
// Unlink peer
|
||||||
|
// Deallocate memory
|
||||||
|
// Unlink core
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disassemble from a simulation
|
||||||
|
async disassemble(address, config, length, line) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (!Number.isSafeInteger(address) ||
|
||||||
|
address < 0 || address > 0xFFFFFFFF)
|
||||||
|
throw new RangeError("Address must conform to Uint32.");
|
||||||
|
if (config != null && !(config instanceof DasmConfig))
|
||||||
|
throw new TypeError("Config must be an instance of DasmConfig.");
|
||||||
|
if (!Number.isSafeInteger(address) || length < 0)
|
||||||
|
throw new RangeError("Length must be nonnegative.");
|
||||||
|
if (!Number.isSafeInteger(line))
|
||||||
|
throw new TypeError("Line must be a safe integer.");
|
||||||
|
|
||||||
|
// Request disassembly from the core
|
||||||
|
let response = await this.#core.toCore({
|
||||||
|
command : "disassemble",
|
||||||
|
promised: true,
|
||||||
|
sim : this.#pointer,
|
||||||
|
address : address,
|
||||||
|
length : length,
|
||||||
|
line : line,
|
||||||
|
config : config == null ? null : {
|
||||||
|
bcondNotation : config.bcondNotation,
|
||||||
|
conditionCase : config.conditionCase,
|
||||||
|
conditionCL : config.conditionCL,
|
||||||
|
conditionEZ : config.conditionEZ,
|
||||||
|
conditionNotation: config.conditionNotation,
|
||||||
|
hexCase : config.hexCase,
|
||||||
|
hexNotation : config.hexNotation,
|
||||||
|
immediateNotation: config.immediateNotation,
|
||||||
|
memoryNotation : config.memoryNotation,
|
||||||
|
mnemonicCase : config.mnemonicCase,
|
||||||
|
operandOrder : config.operandOrder,
|
||||||
|
programCase : config.programCase,
|
||||||
|
programNotation : config.programNotation,
|
||||||
|
setfNotation : config.setfNotation,
|
||||||
|
systemCase : config.systemCase,
|
||||||
|
systemNotation : config.systemNotation
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process the response
|
||||||
|
return !response.success ? null :
|
||||||
|
response.lines.map(l=>new DasmLine(GUARD, l));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset simulation state
|
||||||
|
reset() {
|
||||||
|
return this.#core.toCore({
|
||||||
|
command : "reset",
|
||||||
|
promised: true,
|
||||||
|
sim : this.#pointer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify anaglyph colors
|
||||||
|
setAnaglyph(left, right) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (!Number.isSafeInteger(left ) || left < 0 || left > 0xFFFFFF)
|
||||||
|
throw new RangeError("Left must conform to Uint24.");
|
||||||
|
if (!Number.isSafeInteger(right) || right < 0 || right > 0xFFFFFF)
|
||||||
|
throw new RangeError("Right must conform to Uint24.");
|
||||||
|
if (
|
||||||
|
left & 0xFF0000 && right & 0xFF0000 ||
|
||||||
|
left & 0x00FF00 && right & 0x00FF00 ||
|
||||||
|
left & 0x0000FF && right & 0x0000FF
|
||||||
|
) throw new RangeError("Left and right overlap RGB channels.");
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.#anaglyph[0] = left;
|
||||||
|
this.#anaglyph[1] = right;
|
||||||
|
|
||||||
|
// Send the colors to the core
|
||||||
|
return this.#core.toCore({
|
||||||
|
command : "setAnaglyph",
|
||||||
|
promised: true,
|
||||||
|
sim : this.#pointer,
|
||||||
|
left : left,
|
||||||
|
right : right
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify a game pak RAM buffer
|
||||||
|
setCartRAM(wram) {
|
||||||
|
return this.#setCartMemory("setCartRAM", wram);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify a game pak ROM buffer
|
||||||
|
setCartROM(rom) {
|
||||||
|
return this.#setCartMemory("setCartROM", rom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify new game pad keys
|
||||||
|
setKeys(keys) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (!Number.isSafeInteger(keys) || keys < 0 || keys > 0xFFFF)
|
||||||
|
throw new RangeError("Keys must conform to Uint16.");
|
||||||
|
if (keys == this.#keys)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.#keys = keys;
|
||||||
|
|
||||||
|
// Send the keys to the core
|
||||||
|
return this.#core.toCore({
|
||||||
|
command : "setKeys",
|
||||||
|
promised: true,
|
||||||
|
sim : this.#pointer,
|
||||||
|
keys : keys
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify audio panning
|
||||||
|
setPanning(panning) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (!Number.isFinite(panning) ||panning < -1 || panning > +1) {
|
||||||
|
throw new RangeError(
|
||||||
|
"Panning must be a number from -1 to +1.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.#panning = panning;
|
||||||
|
|
||||||
|
// Send the panning to the core
|
||||||
|
return this.#core.toCore({
|
||||||
|
command : "setPanning",
|
||||||
|
promised: true,
|
||||||
|
sim : this.#pointer,
|
||||||
|
panning : panning
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify a new communication peer
|
||||||
|
async setPeer(peer = null) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (peer !== null && peer.#core != this.#core)
|
||||||
|
throw new RangeError("Peer sim must belong to the same core.");
|
||||||
|
|
||||||
|
// Configure peers on the core
|
||||||
|
if (peer != this.#peer)
|
||||||
|
await this.#core.setPeer(this, peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify audio volume
|
||||||
|
setVolume(volume) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (!Number.isFinite(volume) ||volume < 0 || volume > 10) {
|
||||||
|
throw new RangeError(
|
||||||
|
"Volume must be a number from 0\u00d7 to 10\u00d7.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.#volume = volume;
|
||||||
|
|
||||||
|
// Send the volume to the core
|
||||||
|
return this.#core.toCore({
|
||||||
|
command : "setVolume",
|
||||||
|
promised: true,
|
||||||
|
sim : this.#pointer,
|
||||||
|
volume : volume
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Specify a game pak memory buffer
|
||||||
|
async #setCartMemory(command, mem) {
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (mem instanceof ArrayBuffer)
|
||||||
|
mem = new Uint8Array(mem);
|
||||||
|
if (
|
||||||
|
!(mem instanceof Uint8Array) &&
|
||||||
|
!(mem instanceof Uint8ClampedArray)
|
||||||
|
) mem = Uint8Array.from(mem);
|
||||||
|
|
||||||
|
// Send the memory to the core
|
||||||
|
let response = await this.#core.toCore({
|
||||||
|
command : command,
|
||||||
|
promised : true,
|
||||||
|
sim : this.#pointer,
|
||||||
|
data : mem.buffer,
|
||||||
|
transfers: [ mem.buffer ]
|
||||||
|
});
|
||||||
|
return response.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
customElements.define("shrooms-vb", Sim);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////// VB //////////////////////////////////////
|
||||||
|
|
||||||
|
// Emulation core interface
|
||||||
|
class VB {
|
||||||
|
|
||||||
|
// Static fields
|
||||||
|
static get DasmConfig() { return DasmConfig; }
|
||||||
|
static get DasmLine () { return DasmLine; }
|
||||||
|
static get Sim () { return Sim; }
|
||||||
|
|
||||||
|
// Instance fields
|
||||||
|
#audio; // Audio worklet
|
||||||
|
#automatic; // Current automatic emulation group
|
||||||
|
#commands; // Computed method table
|
||||||
|
#core; // Core worker
|
||||||
|
#proxy; // Self proxy for sim access
|
||||||
|
#sims; // Mapping of Sim|pointer -> proxy
|
||||||
|
#state; // Operations state
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////// Constants ////////////////////////////////
|
||||||
|
|
||||||
|
// Operations states
|
||||||
|
static #SUSPENDED = Symbol();
|
||||||
|
static #RESUMING = Symbol();
|
||||||
|
static #EMULATING = Symbol();
|
||||||
|
static #SUSPENDING = Symbol();
|
||||||
|
|
||||||
|
// System registers
|
||||||
|
static get ADTRE() { return Constants.VB.ADTRE; }
|
||||||
|
static get CHCW () { return Constants.VB.CHCW ; }
|
||||||
|
static get ECR () { return Constants.VB.ECR ; }
|
||||||
|
static get EIPC () { return Constants.VB.EIPC ; }
|
||||||
|
static get EIPSW() { return Constants.VB.EIPSW; }
|
||||||
|
static get FEPC () { return Constants.VB.FEPC ; }
|
||||||
|
static get FEPSW() { return Constants.VB.FEPSW; }
|
||||||
|
static get PIR () { return Constants.VB.PIR ; }
|
||||||
|
static get PSW () { return Constants.VB.PSW ; }
|
||||||
|
static get TKCW () { return Constants.VB.TKCW ; }
|
||||||
|
|
||||||
|
// Memory access data types
|
||||||
|
static get S8 () { return Constants.VB.S8 ; }
|
||||||
|
static get U8 () { return Constants.VB.U8 ; }
|
||||||
|
static get S16() { return Constants.VB.S16; }
|
||||||
|
static get U16() { return Constants.VB.U16; }
|
||||||
|
static get S32() { return Constants.VB.S32; }
|
||||||
|
|
||||||
|
// Option keys
|
||||||
|
static get PSEUDO_HALT() { return Constants.VB.PSEUDO_HALT; }
|
||||||
|
|
||||||
|
// Controller buttons
|
||||||
|
static get PWR() { return Constants.VB.PWR; }
|
||||||
|
static get SGN() { return Constants.VB.SGN; }
|
||||||
|
static get A () { return Constants.VB.A ; }
|
||||||
|
static get B () { return Constants.VB.B ; }
|
||||||
|
static get RT () { return Constants.VB.RT ; }
|
||||||
|
static get LT () { return Constants.VB.LT ; }
|
||||||
|
static get RU () { return Constants.VB.RU ; }
|
||||||
|
static get RR () { return Constants.VB.RR ; }
|
||||||
|
static get LR () { return Constants.VB.LR ; }
|
||||||
|
static get LL () { return Constants.VB.LL ; }
|
||||||
|
static get LD () { return Constants.VB.LD ; }
|
||||||
|
static get LU () { return Constants.VB.LU ; }
|
||||||
|
static get STA() { return Constants.VB.STA; }
|
||||||
|
static get SEL() { return Constants.VB.SEL; }
|
||||||
|
static get RL () { return Constants.VB.RL ; }
|
||||||
|
static get RD () { return Constants.VB.RD ; }
|
||||||
|
|
||||||
|
// Disassembler options
|
||||||
|
static get ["0X"] () { return Constants.VBU["0X"] ; }
|
||||||
|
static get ABSOLUTE () { return Constants.VBU.ABSOLUTE ; }
|
||||||
|
static get C () { return Constants.VBU.C ; }
|
||||||
|
static get DEST_FIRST() { return Constants.VBU.DEST_FIRST; }
|
||||||
|
static get DEST_LAST () { return Constants.VBU.DEST_LAST ; }
|
||||||
|
static get DOLLAR () { return Constants.VBU.DOLLAR ; }
|
||||||
|
static get E () { return Constants.VBU.E ; }
|
||||||
|
static get H () { return Constants.VBU.H ; }
|
||||||
|
static get INSIDE () { return Constants.VBU.INSIDE ; }
|
||||||
|
static get JOINED () { return Constants.VBU.JOINED ; }
|
||||||
|
static get L () { return Constants.VBU.L ; }
|
||||||
|
static get LOWER () { return Constants.VBU.LOWER ; }
|
||||||
|
static get NAMES () { return Constants.VBU.NAMES ; }
|
||||||
|
static get NONE () { return Constants.VBU.NONE ; }
|
||||||
|
static get NUMBER () { return Constants.VBU.NUMBER ; }
|
||||||
|
static get NUMBERS () { return Constants.VBU.NUMBERS ; }
|
||||||
|
static get OUTSIDE () { return Constants.VBU.OUTSIDE ; }
|
||||||
|
static get RELATIVE () { return Constants.VBU.RELATIVE ; }
|
||||||
|
static get SPLIT () { return Constants.VBU.SPLIT ; }
|
||||||
|
static get UPPER () { return Constants.VBU.UPPER ; }
|
||||||
|
static get Z () { return Constants.VBU.Z ; }
|
||||||
|
|
||||||
|
// Anaglyph colors
|
||||||
|
static get STEREO_CYAN () { return Constants.web.STEREO_CYAN ; }
|
||||||
|
static get STEREO_GREEN () { return Constants.web.STEREO_GREEN ; }
|
||||||
|
static get STEREO_MAGENTA() { return Constants.web.STEREO_MAGENTA; }
|
||||||
|
static get STEREO_RED () { return Constants.web.STEREO_RED ; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Static Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Create a core instance
|
||||||
|
static create(options) {
|
||||||
|
return new VB(GUARD).#construct(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////// Initialization Methods //////////////////////////
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (arguments[0] != GUARD)
|
||||||
|
throw new Error("Must be created via VB.create()");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asynchronous constructor
|
||||||
|
async #construct(options) {
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.#automatic = null;
|
||||||
|
this.#sims = new Map();
|
||||||
|
this.#state = VB.#SUSPENDED;
|
||||||
|
|
||||||
|
// Ensure default options
|
||||||
|
options ??= {};
|
||||||
|
options.audioUrl ??= import.meta.resolve("./Audio.js");
|
||||||
|
options.coreUrl ??= import.meta.resolve("./Core.js");
|
||||||
|
options.wasmUrl ??= import.meta.resolve("./core.wasm");
|
||||||
|
|
||||||
|
// Core<->audio communications
|
||||||
|
let channel = new MessageChannel();
|
||||||
|
|
||||||
|
// Audio output context
|
||||||
|
let audio = new AudioContext({
|
||||||
|
latencyHint: "interactive",
|
||||||
|
sampleRate : 41700
|
||||||
|
});
|
||||||
|
await audio.suspend();
|
||||||
|
|
||||||
|
// Audio node
|
||||||
|
await audio.audioWorklet.addModule(options.audioUrl);
|
||||||
|
audio = this.#audio = new AudioWorkletNode(audio, "shrooms-vb", {
|
||||||
|
numberOfInputs : 0,
|
||||||
|
numberOfOutputs : 1,
|
||||||
|
outputChannelCount: [2]
|
||||||
|
});
|
||||||
|
audio.connect(audio.context.destination);
|
||||||
|
|
||||||
|
// Send one message channel port to the audio worklet
|
||||||
|
await new Promise(resolve=>{
|
||||||
|
audio.port.onmessage = resolve;
|
||||||
|
audio.port.postMessage({
|
||||||
|
core: channel.port1
|
||||||
|
}, [channel.port1]);
|
||||||
|
});
|
||||||
|
audio.port.onmessage = null;//e=>this.#onAudio(e.data);
|
||||||
|
|
||||||
|
// Core worker
|
||||||
|
let core = this.#core = new Worker(options.coreUrl, {type: "module"});
|
||||||
|
core.promises = [];
|
||||||
|
|
||||||
|
// Send the other message channel port to the core worker
|
||||||
|
await new Promise(resolve=>{
|
||||||
|
core.onmessage = resolve;
|
||||||
|
core.postMessage({
|
||||||
|
audio : channel.port2,
|
||||||
|
wasmUrl: options.wasmUrl
|
||||||
|
}, [ channel.port2 ]);
|
||||||
|
});
|
||||||
|
core.onmessage = e=>this.#onCore(e.data);
|
||||||
|
|
||||||
|
// Establish a concealed proxy for sim objects
|
||||||
|
this.#proxy = {
|
||||||
|
core : this,
|
||||||
|
setPeer: (a,b)=>this.#setPeer(a,b),
|
||||||
|
toCore : m=>this.#toCore(m)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configure command table
|
||||||
|
this.#commands = {
|
||||||
|
// Will be used with subscriptions
|
||||||
|
};
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////// Properties ////////////////////////////////
|
||||||
|
|
||||||
|
// Number of managed simulations
|
||||||
|
get size() { return this.#sims.size / 2; }
|
||||||
|
|
||||||
|
// Managed simulations
|
||||||
|
get sims() { return [... this.#sims.keys()].filter(s=>s instanceof Sim); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
|
// Create one or more sims
|
||||||
|
async create(count = null) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (count !== null && (!Number.isSafeInteger(count) || count < 1)) {
|
||||||
|
throw new RangeError(
|
||||||
|
"Count must be a safe integer and at least 1.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate memory in the core
|
||||||
|
let response = await this.#toCore({
|
||||||
|
command : "createSims",
|
||||||
|
promised: true,
|
||||||
|
count : count ?? 1
|
||||||
|
});
|
||||||
|
|
||||||
|
// Produce Sim elements for each instance
|
||||||
|
let sims = response.sims;
|
||||||
|
for (let x = 0; x < (count ?? 1); x++) {
|
||||||
|
let proxy = new Sim(GUARD).proxy;
|
||||||
|
proxy.pointer = sims[x];
|
||||||
|
proxy.sim = sims[x] =
|
||||||
|
await proxy.construct(this.#proxy, sims[x]);
|
||||||
|
this.#sims.set(sims[x], proxy);
|
||||||
|
this.#sims.set(proxy.pointer, proxy);
|
||||||
|
}
|
||||||
|
return count === null ? sims[0] : sims;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin emulation
|
||||||
|
async emulate(sims, clocks) {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (sims instanceof Sim)
|
||||||
|
sims = [sims];
|
||||||
|
if (
|
||||||
|
!Array.isArray(sims) ||
|
||||||
|
sims.length == 0 ||
|
||||||
|
sims.some(s=>!this.#sims.has(s))
|
||||||
|
) {
|
||||||
|
throw new TypeError("Must specify a Sim or array of Sims " +
|
||||||
|
"that belong to this core.");
|
||||||
|
}
|
||||||
|
if (sims.find(s=>this.#sims.get(s).isEmulating()))
|
||||||
|
throw new Error("Sims cannot already be part of emulation.");
|
||||||
|
if (
|
||||||
|
clocks !== true &&
|
||||||
|
!(Number.isSafeInteger(clocks) && clocks >= 0)
|
||||||
|
) {
|
||||||
|
throw new RangeError(
|
||||||
|
"Clocks must be true or a nonnegative safe integer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot resume automatic emulation
|
||||||
|
if (clocks === true && this.#state != VB.#SUSPENDED)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Manage sims
|
||||||
|
let proxies = sims .map(s=>this.#sims.get(s));
|
||||||
|
let pointers = proxies.map(p=>p.pointer);
|
||||||
|
for (let sim of proxies)
|
||||||
|
sim.setEmulating(true);
|
||||||
|
|
||||||
|
// Clocked emulation
|
||||||
|
if (clocks !== true) {
|
||||||
|
let response = await this.#toCore({
|
||||||
|
command : "emulateClocked",
|
||||||
|
promised: true,
|
||||||
|
sims : pointers,
|
||||||
|
clocks : clocks
|
||||||
|
});
|
||||||
|
for (let sim of proxies)
|
||||||
|
sim.setEmulating(false);
|
||||||
|
return {
|
||||||
|
broke : response.broke,
|
||||||
|
clocks: response.clocks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume automatic emulation
|
||||||
|
this.#automatic = proxies;
|
||||||
|
this.#state = VB.#RESUMING;
|
||||||
|
if (this.#audio.context.state == "suspended")
|
||||||
|
await this.#audio.context.resume();
|
||||||
|
await this.#toCore({
|
||||||
|
command : "emulateAutomatic",
|
||||||
|
promised: true,
|
||||||
|
sims : pointers
|
||||||
|
});
|
||||||
|
this.#state = VB.#EMULATING;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an ISX debugger file to a Virtual Boy ROM
|
||||||
|
async fromISX(data) {
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (data instanceof ArrayBuffer)
|
||||||
|
data = new Uint8Array(data);
|
||||||
|
if (
|
||||||
|
!(data instanceof Uint8Array) &&
|
||||||
|
!(data instanceof Uint8ClampedArray)
|
||||||
|
) data = Uint8Array.from(data);
|
||||||
|
|
||||||
|
// Send the memory to the core
|
||||||
|
data = data.slice();
|
||||||
|
let response = await this.#toCore({
|
||||||
|
command : "fromISX",
|
||||||
|
promised : true,
|
||||||
|
data : data.buffer,
|
||||||
|
transfers: [ data.buffer ]
|
||||||
|
});
|
||||||
|
return response.data == null ? null : new Uint8Array(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suspend automatic emulation
|
||||||
|
async suspend() {
|
||||||
|
|
||||||
|
// Error checking
|
||||||
|
if (this.#state != VB.#EMULATING)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Tell the core to stop emulating
|
||||||
|
this.#state = VB.#SUSPENDING;
|
||||||
|
await this.#toCore({
|
||||||
|
command : "suspend",
|
||||||
|
promised: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure state
|
||||||
|
this.#state = VB.#SUSPENDED;
|
||||||
|
for (let sim of this.#automatic)
|
||||||
|
sim.setEmulating(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Message received from core worker
|
||||||
|
#onCore(message) {
|
||||||
|
if (message.promised)
|
||||||
|
this.#core.promises.shift()(message);
|
||||||
|
if ("command" in message)
|
||||||
|
this.#commands[message.command](message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify a new communication peer
|
||||||
|
async #setPeer(sim, peer) {
|
||||||
|
|
||||||
|
// Associate the peers on the core
|
||||||
|
let response = await this.#toCore({
|
||||||
|
command : "setPeer",
|
||||||
|
promised: true,
|
||||||
|
sim : this.#sims.get(sim).pointer,
|
||||||
|
peer : peer == null ? 0 : this.#sims.get(peer).pointer
|
||||||
|
});
|
||||||
|
|
||||||
|
// Link sims
|
||||||
|
this.#sims.get(sim).setPeer(peer);
|
||||||
|
if (peer != null)
|
||||||
|
this.#sims.get(peer).setPeer(sim);
|
||||||
|
|
||||||
|
// Unlink orphaned sims
|
||||||
|
for (let pointer of response.orphaned)
|
||||||
|
this.#sims.get(pointer).setPeer(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a message to the core worker
|
||||||
|
async #toCore(message) {
|
||||||
|
let transfers = message.transfers;
|
||||||
|
if (transfers != null)
|
||||||
|
delete message.transfers;
|
||||||
|
return await new Promise(resolve=>{
|
||||||
|
if (message.promised)
|
||||||
|
this.#core.promises.push(resolve);
|
||||||
|
this.#core.postMessage(message, transfers ?? []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VB;
|
||||||
+273
@@ -0,0 +1,273 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <emscripten/emscripten.h>
|
||||||
|
#include <vb.h>
|
||||||
|
#include <vbu.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////// Constants //////////////////////////////////
|
||||||
|
|
||||||
|
// Break conditions
|
||||||
|
#define BREAK_FRAME 1
|
||||||
|
#define BREAK_POINT 2
|
||||||
|
|
||||||
|
// Anaglyph colors
|
||||||
|
#define STEREO_CYAN 0x00C6F0
|
||||||
|
#define STEREO_GREEN 0x00B400
|
||||||
|
#define STEREO_MAGENTA 0xC800FF
|
||||||
|
#define STEREO_RED 0xFF0000
|
||||||
|
|
||||||
|
// Element counts
|
||||||
|
#define NUM_SAMPLES (41700 / 50 * 2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////// Types ////////////////////////////////////
|
||||||
|
|
||||||
|
// Additional monitor state for simulations
|
||||||
|
typedef struct {
|
||||||
|
int32_t breaks;
|
||||||
|
uint32_t left[256];
|
||||||
|
float panning;
|
||||||
|
uint8_t pixels[384 * 224 * 4];
|
||||||
|
uint32_t right[256];
|
||||||
|
float samples[NUM_SAMPLES];
|
||||||
|
float volume;
|
||||||
|
} Ext;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////// Callbacks //////////////////////////////////
|
||||||
|
|
||||||
|
// Frame callback
|
||||||
|
int wasmOnFrame(VB *sim) {
|
||||||
|
((Ext *) vbGetUserData(sim))->breaks |= BREAK_FRAME;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////// Module Exports ////////////////////////////////
|
||||||
|
|
||||||
|
// Specify anaglyph colors
|
||||||
|
EMSCRIPTEN_KEEPALIVE void SetAnaglyph(VB *sim, uint32_t left, uint32_t right) {
|
||||||
|
Ext *ext = (Ext *) vbGetUserData(sim);
|
||||||
|
|
||||||
|
// Erase all RGB values
|
||||||
|
for (int x = 0; x < 256; x++)
|
||||||
|
ext->left[x] = ext->right[x] = 0xFF000000;
|
||||||
|
|
||||||
|
// Process all RGB channels
|
||||||
|
for (int c = 0, shift = 16; c < 3; c++, shift -= 8) {
|
||||||
|
double max; // Magnitude of channel value
|
||||||
|
uint32_t *dest; // Lookup data
|
||||||
|
|
||||||
|
// Select the magnitude and lookup channel
|
||||||
|
dest = ext->left;
|
||||||
|
max = (left >> shift & 0xFF) / 255.0;
|
||||||
|
if (max == 0) {
|
||||||
|
dest = ext->right;
|
||||||
|
max = (right >> shift & 0xFF) / 255.0;
|
||||||
|
if (max == 0)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the resulting RGB values
|
||||||
|
for (int x = 0; x < 256; x++)
|
||||||
|
*dest++ |= (uint32_t) (x * max + 0.5) << (16 - shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate a simulation
|
||||||
|
EMSCRIPTEN_KEEPALIVE void* CreateSim() {
|
||||||
|
size_t sizeOfSim = vbSizeOf();
|
||||||
|
uint8_t *pointer = malloc(sizeOfSim + sizeof (Ext));
|
||||||
|
|
||||||
|
// Configure sim
|
||||||
|
VB *sim = vbInit((VB *) pointer);
|
||||||
|
vbSetFrameCallback(sim, &wasmOnFrame);
|
||||||
|
vbSetOption(sim, VB_PSEUDO_HALT, 1);
|
||||||
|
|
||||||
|
// Configure extra
|
||||||
|
Ext *ext = (Ext *) (pointer + sizeOfSim);
|
||||||
|
ext->breaks = 0;
|
||||||
|
ext->panning = 0.0f;
|
||||||
|
ext->volume = 1.0f;
|
||||||
|
vbSetUserData(sim, ext);
|
||||||
|
SetAnaglyph(sim, STEREO_RED, STEREO_CYAN);
|
||||||
|
|
||||||
|
// Initialize pixels with opaque black
|
||||||
|
for (unsigned x = 0; x < 384 * 224; x++)
|
||||||
|
((uint32_t *) ext->pixels)[x] = 0xFF000000;
|
||||||
|
|
||||||
|
return sim;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disassemble from a simulation
|
||||||
|
EMSCRIPTEN_KEEPALIVE void* Disassemble(
|
||||||
|
int bcondNotation, int conditionCase, int conditionCL, int conditionEZ,
|
||||||
|
int conditionNotation, int hexCase, int hexNotation, int immediateNotation,
|
||||||
|
int memoryNotation, int mnemonicCase, int operandOrder, int programCase,
|
||||||
|
int programNotation, int setfNotation, int systemCase, int systemNotation,
|
||||||
|
VB *sim, uint32_t address, unsigned length, int line
|
||||||
|
) {
|
||||||
|
VBU_DasmConfig config;
|
||||||
|
config.bcondNotation = bcondNotation;
|
||||||
|
config.conditionCase = conditionCase;
|
||||||
|
config.conditionCL = conditionCL;
|
||||||
|
config.conditionEZ = conditionEZ;
|
||||||
|
config.conditionNotation = conditionNotation;
|
||||||
|
config.hexCase = hexCase;
|
||||||
|
config.hexNotation = hexNotation;
|
||||||
|
config.immediateNotation = immediateNotation;
|
||||||
|
config.memoryNotation = memoryNotation;
|
||||||
|
config.mnemonicCase = mnemonicCase;
|
||||||
|
config.operandOrder = operandOrder;
|
||||||
|
config.programCase = programCase;
|
||||||
|
config.programNotation = programNotation;
|
||||||
|
config.setfNotation = setfNotation;
|
||||||
|
config.systemCase = systemCase;
|
||||||
|
config.systemNotation = systemNotation;
|
||||||
|
return vbuDisassemble(sim, address, &config, length, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process simulations
|
||||||
|
EMSCRIPTEN_KEEPALIVE int Emulate(VB **sims, unsigned count, uint32_t *clocks) {
|
||||||
|
for (unsigned x = 0; x < count; x++)
|
||||||
|
((Ext *) vbGetUserData(sims[x]))->breaks = 0;
|
||||||
|
return vbEmulateEx(sims, count, clocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve a sim's pixel pointer
|
||||||
|
EMSCRIPTEN_KEEPALIVE void* GetExtPixels(VB *sim) {
|
||||||
|
return ((Ext *) vbGetUserData(sim))->pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve a sim's sample pointer
|
||||||
|
EMSCRIPTEN_KEEPALIVE void* GetExtSamples(VB *sim) {
|
||||||
|
return ((Ext *) vbGetUserData(sim))->samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the break condition flags for a sim
|
||||||
|
EMSCRIPTEN_KEEPALIVE int32_t GetBreaks(VB *sim) {
|
||||||
|
return ((Ext *) vbGetUserData(sim))->breaks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize disassembled lines into a linear buffer
|
||||||
|
EMSCRIPTEN_KEEPALIVE void GetDasm(uint32_t *buffer, void *dasm, int count) {
|
||||||
|
for (int x = 0; x < count; x++) {
|
||||||
|
VBU_DasmLine *line = &((VBU_DasmLine *) dasm)[x];
|
||||||
|
|
||||||
|
// Numeric data
|
||||||
|
*buffer++ = line->address;
|
||||||
|
*buffer++ = line->codeLength;
|
||||||
|
for (int y = 0; y < 4; y++)
|
||||||
|
*buffer++ = line->code[y];
|
||||||
|
*buffer++ = line->isPC;
|
||||||
|
|
||||||
|
// Text data -- Store offset of string pointer from start of dasm
|
||||||
|
*buffer++ = (uint32_t) ((void *) &line->text.address - dasm);
|
||||||
|
for (int y = 0; y < 4; y++)
|
||||||
|
*buffer++ = (uint32_t) ((void *) &line->text.code[y] - dasm);
|
||||||
|
*buffer++ = (uint32_t) ((void *) &line->text.mnemonic - dasm);
|
||||||
|
*buffer++ = line->text.operandsLength;
|
||||||
|
for (int y = 0; y < 3; y++)
|
||||||
|
*buffer++ = (uint32_t) ((void *) &line->text.operands[y] - dasm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve anaglyph-tinted pixels from a sim
|
||||||
|
EMSCRIPTEN_KEEPALIVE void GetPixels(VB *sim) {
|
||||||
|
Ext *ext = (Ext *) vbGetUserData(sim);
|
||||||
|
uint8_t *pixels = ext->pixels;
|
||||||
|
vbGetPixels(sim, pixels, 4, 384 * 4, pixels + 1, 4, 384 * 4);
|
||||||
|
for (unsigned x = 0; x < 384 * 224 * 4; x += 4, pixels += 4)
|
||||||
|
*(uint32_t *) pixels = ext->left[pixels[0]] | ext->right[pixels[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mix audio samples for output
|
||||||
|
EMSCRIPTEN_KEEPALIVE void Mix(float *buffer, VB **sims, int count) {
|
||||||
|
|
||||||
|
// Process all sims
|
||||||
|
for (int x = 0; x < count; x++) {
|
||||||
|
Ext *ext = (Ext *) vbGetUserData(sims[x]);
|
||||||
|
float *samples = ext->samples;
|
||||||
|
float v = ext->volume;
|
||||||
|
|
||||||
|
// First sim initializes buffer
|
||||||
|
if (x == 0) {
|
||||||
|
if (ext->panning < 0) {
|
||||||
|
float l = -ext->panning * v;
|
||||||
|
float r = (1.0f + ext->panning) * v;
|
||||||
|
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
|
||||||
|
buffer[y ] = samples[y] * v + samples[y + 1] * l;
|
||||||
|
buffer[y + 1] = samples[y + 1] * r;
|
||||||
|
}
|
||||||
|
} else if (ext->panning > 0) {
|
||||||
|
float r = ext->panning * v;
|
||||||
|
float l = (1.0f - ext->panning) * v;
|
||||||
|
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
|
||||||
|
buffer[y ] = samples[y] * l;
|
||||||
|
buffer[y + 1] = samples[y + 1] * v + samples[y] * r;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (unsigned y = 0; y < NUM_SAMPLES; y++)
|
||||||
|
buffer[y] = samples[y] * v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subsequent sims add to buffer
|
||||||
|
else {
|
||||||
|
if (ext->panning < 0) {
|
||||||
|
float l = -ext->panning * v;
|
||||||
|
float r = (1.0f + ext->panning) * v;
|
||||||
|
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
|
||||||
|
buffer[y ] += samples[y] * v + samples[y + 1] * l;
|
||||||
|
buffer[y + 1] += samples[y + 1] * r;
|
||||||
|
}
|
||||||
|
} else if (ext->panning > 0) {
|
||||||
|
float r = ext->panning * v;
|
||||||
|
float l = (1.0f - ext->panning) * v;
|
||||||
|
for (unsigned y = 0; y < NUM_SAMPLES; y += 2) {
|
||||||
|
buffer[y ] += samples[y] * l;
|
||||||
|
buffer[y + 1] += samples[y + 1] * v + samples[y] * r;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (unsigned y = 0; y < NUM_SAMPLES; y++)
|
||||||
|
buffer[y] += samples[y] * v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clipping
|
||||||
|
for (unsigned y = 0; y < NUM_SAMPLES; y++) {
|
||||||
|
if (buffer[y] < -1.0f)
|
||||||
|
buffer[y] = -1.0f;
|
||||||
|
else if (buffer[y] > +1.0f)
|
||||||
|
buffer[y] = +1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the size in bytes of a pointer
|
||||||
|
EMSCRIPTEN_KEEPALIVE int PointerSize() {
|
||||||
|
return sizeof (void *);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory management
|
||||||
|
EMSCRIPTEN_KEEPALIVE void* Realloc(void *data, size_t size) {
|
||||||
|
return realloc(data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify audio panning
|
||||||
|
EMSCRIPTEN_KEEPALIVE void SetPanning(VB *sim, float panning) {
|
||||||
|
((Ext *) vbGetUserData(sim))->panning = panning;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify audio volume
|
||||||
|
EMSCRIPTEN_KEEPALIVE void SetVolume(VB *sim, float volume) {
|
||||||
|
((Ext *) vbGetUserData(sim))->volume = volume;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user