Compare commits

..
60 Commits
Author SHA1 Message Date
GuyPerfect 6e9b727b31 Fix botched disassembler backport 2026-10-19 19:01:25 -05:00
GuyPerfect 7ab5bd28bf Fix affine world width assignment logic 2026-10-19 18:20:00 -05:00
GuyPerfect 41cd7bc089 Backport Shrooms2 disassembler 2026-10-17 22:01:08 -05:00
GuyPerfect 31b7403252 Correction for negative affine parallax 2026-05-21 16:23:23 -05:00
GuyPerfect 29ade46a0a Fix for affine parameter address resolution 2026-03-28 16:46:32 -05:00
GuyPerfect 534ce852f4 Adjust brightness values 2025-11-23 12:08:36 -06:00
GuyPerfect 4ed3b72995 Fix nullary function declarations 2025-08-27 09:53:01 -05:00
GuyPerfect 8598eab087 Fix double CPU clocks in reads/writes, assuage unused argument in vsuWrite() 2025-08-24 11:03:51 -05:00
GuyPerfect fe9c5c4781 Adjustment for VSU hardware behavior on erroneous address writes 2025-08-18 20:16:41 -05:00
GuyPerfect a9a02e2d15 Merging pull request 2025-08-17 13:32:01 -05:00
GuyPerfect ab29f47004 Merge pull request 'Fix issues with H-Bias rendering' (#1) from hbias-fix into main
Reviewed-on: #1
2025-08-17 18:22:53 +00:00
GuyPerfect 531e4a8c3e Fix handler address for FIV exception 2025-08-17 13:21:53 -05:00
GuyPerfect f295e02aaf Style adjustments for previous 2025-08-17 13:11:29 -05:00
GuyPerfect 67928da1e6 Merge pull request 'Add Floogle's latest audio fixes' (#3) from floogle-audio-fixes into main
Reviewed-on: #3
2025-08-17 18:02:04 +00:00
SonicSwordcane 4ebdad0c2d Add Floogle's latest audio fixes 2025-08-15 00:24:07 -04:00
SonicSwordcane 5d4a422e61 Fix handler address for FIV exception 2025-08-15 00:07:04 -04:00
SonicSwordcane 8b020ad808 Fix issues with HBias rendering 2025-08-14 23:46:39 -04:00
GuyPerfect 483714c0d6 Pushing current web edits 2025-03-23 07:26:43 -05:00
GuyPerfect ecbd103917 Manage IRQ on writes to INTENB 2025-03-04 10:19:12 -06:00
GuyPerfect b2412d9487 Fix shameful syntax error 2025-03-01 15:51:49 -06:00
GuyPerfect dd1045553b Even more VSU edge cases 2025-03-01 15:42:16 -06:00
GuyPerfect 459a16076a Expand BRTx to 0..132, fix affine parallax 2025-02-25 09:02:22 -06:00
GuyPerfect 185362e6cd Fix BKCOL debug writes, more VSU edge cases 2025-02-24 13:44:26 -06:00
GuyPerfect c62dd833a3 Include missing ISX decover 2025-02-18 15:13:18 -06:00
GuyPerfect 4f0b889b74 Adjustments for use in the web frontend 2025-02-18 15:10:46 -06:00
GuyPerfect 8ae8980fc8 Implement ISX decoding 2025-02-18 15:09:38 -06:00
GuyPerfect 10505e9e98 Supporting additional VSU edge cases 2025-02-18 15:09:15 -06:00
GuyPerfect 155a3aa678 Adjust GAMESTART logic, consolidate VIP sign-extensions 2025-01-17 09:43:58 -06:00
GuyPerfect 04e79c9151 Fix sign extensions from vipRead() and busReadMisc() 2025-01-15 19:47:44 -06:00
GuyPerfect 57dcd8370a Frame buffer management fixes 2025-01-05 20:54:37 -06:00
GuyPerfect 06849b54ba Fix TRAP restore PC, but better 2024-12-29 09:52:07 -06:00
GuyPerfect eef255b507 Fix TRAP restore PC 2024-12-29 09:22:14 -06:00
GuyPerfect db966c8cb8 Fix memory growth handling 2024-12-26 14:13:13 -06:00
GuyPerfect b4feff41f0 Fix broken timer fix bug fixes 2024-12-26 13:23:56 -06:00
GuyPerfect 4449acada0 Fix timer clock computation 2024-12-26 12:57:44 -06:00
GuyPerfect 799ac9f51a Fix style check error 2024-12-26 12:28:10 -06:00
GuyPerfect b83b49221d Timer accuracy adjustments 2024-12-26 12:23:00 -06:00
GuyPerfect 27e926bd58 Fix byte writes to VIP cells, objects and worlds 2024-12-25 13:48:39 -06:00
GuyPerfect d802d39d8d Prevent non-debug writes to CTA 2024-12-23 09:56:54 -06:00
GuyPerfect 18b2c589e6 Add vbuCodeSize() 2024-11-21 18:50:31 -06:00
GuyPerfect f45636a491 VIP bugfix 2024-11-19 22:22:34 -06:00
GuyPerfect 9ce8c9c778 VIP bugfix 2024-11-19 22:21:06 -06:00
GuyPerfect c90b8137de VIP optimizations 2024-11-19 21:56:44 -06:00
GuyPerfect bc864644f7 Aid VSU performance 2024-11-17 14:57:56 -06:00
GuyPerfect e97b52e944 Cleanup adjustments 2024-11-17 12:31:37 -06:00
GuyPerfect 7e31fbd582 VSU performance tweak, add immediate notation setting 2024-11-11 20:05:14 -06:00
GuyPerfect b4b9131f39 Web: add panning, move mixing to wasm 2024-11-02 11:14:24 -05:00
GuyPerfect 8b9152dde9 Fix S*RAM, Sim.setVolume 2024-11-01 18:29:13 -05:00
GuyPerfect 26a0357afa Introduce web core 2024-11-01 15:03:49 -05:00
GuyPerfect 53826584b8 Fix disassembler not advancing past PC 2024-11-01 15:01:09 -05:00
GuyPerfect ae22c95dbe Tweaks in preparation for web module 2024-10-31 19:14:18 -05:00
GuyPerfect b134903878 Fix first line of disassembly 2024-10-30 15:12:38 -05:00
GuyPerfect 52838e4a6d Disassembler adjustments 2024-10-30 14:54:13 -05:00
GuyPerfect 6cba6e5d4f Prevent disassembling over PC 2024-10-30 10:22:49 -05:00
GuyPerfect 650d9dfd05 Add util to makefile 2024-10-30 09:52:33 -05:00
GuyPerfect f5a84fd4d0 Implement disassembler 2024-10-30 09:48:56 -05:00
GuyPerfect 9cc01bb6f7 CCSR.CC_Rd does not require a communication 2024-10-28 19:20:08 -05:00
GuyPerfect e828d99da1 Implement communication port 2024-10-28 19:11:33 -05:00
GuyPerfect da001e02cd Fix overplane 2024-10-26 15:04:27 -05:00
GuyPerfect 67bcb6d508 Prepare for new BG processors 2024-10-24 10:59:11 -05:00
20 changed files with 4713 additions and 516 deletions
+19 -16
View File
@@ -95,17 +95,17 @@ static int32_t busReadMisc(VB *sim, uint8_t address, int type) {
/* Access by register */
switch (address >> 2 & 15) {
case 0x00>>2: break; /* CCR */
case 0x04>>2: break; /* CCSR */
case 0x08>>2: break; /* CDTR */
case 0x0C>>2: break; /* CDRR */
case 0x10>>2: return sim->pad.sdlr; /* SDLR */
case 0x14>>2: return sim->pad.sdhr; /* SDHR */
case 0x18>>2: return sim->tmr.counter & 0xFF; /* TLR */
case 0x1C>>2: return sim->tmr.counter >> 8 & 0xFF; /* THR */
case 0x20>>2: return tmrReadControl(sim); /* TCR */
case 0x24>>2: return sim->wcr; /* WCR */
case 0x28>>2: return padReadControl(sim); /* SCR */
case 0x00>>2: return extReadCCR(sim); /* CCR */
case 0x04>>2: return extReadCCSR(sim); /* CCSR */
case 0x08>>2: return sim->ext.cdtr; /* CDTR */
case 0x0C>>2: return sim->ext.cdrr; /* CDRR */
case 0x10>>2: return sim->pad.sdlr; /* SDLR */
case 0x14>>2: return sim->pad.sdhr; /* SDHR */
case 0x18>>2: return sim->tmr.counter & 0xFF; /* TLR */
case 0x1C>>2: return sim->tmr.counter >> 8 & 0xFF; /* THR */
case 0x20>>2: return tmrReadControl(sim); /* TCR */
case 0x24>>2: return sim->wcr.exp1w << 1 | sim->wcr.rom1w; /* WCR */
case 0x28>>2: return padReadControl(sim); /* SCR */
}
/* Unmapped */
@@ -123,15 +123,16 @@ static void busWriteMisc(VB *sim, uint8_t address, int type, int32_t value) {
/* Access by register */
switch (address >> 2 & 15) {
case 0x00>>2: break; /* CCR */
case 0x04>>2: break; /* CCSR */
case 0x08>>2: break; /* CDTR */
case 0x0C>>2: break; /* CDRR */
case 0x00>>2: extWriteCCR (sim, value); break; /* CCR */
case 0x04>>2: extWriteCCSR(sim, value); break; /* CCSR */
case 0x08>>2: sim->ext.cdtr = value; break; /* CDTR */
case 0x18>>2: tmrWriteLow (sim, value); break; /* TLR */
case 0x1C>>2: tmrWriteHigh (sim, value); break; /* THR */
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 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 */
*value = busReadMisc(sim, address, type);
if (type == VB_S8)
*value = (int8_t) *value;
break;
case 3: break; /* Unmapped */
+4 -8
View File
@@ -91,11 +91,6 @@
#define CPU_XORBSU 82
#define CPU_XORNBSU 83
/* Functional operand types */
#define CPU_LITERAL 0
#define CPU_MEMORY 1
#define CPU_REGISTER 2
/* Bit string operations */
#define CPU_AND_BS 0
#define CPU_ANDN_BS 1
@@ -239,7 +234,7 @@ static int cpuOnExecute(VB *sim) {
#define VB_ON_READ VB_DIRECT_READ
#endif
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 */
busRead(sim, address, type, value);
@@ -294,7 +289,7 @@ static int cpuReadFetch(VB *sim, int fetch, uint32_t address, int32_t *value) {
#endif
static int cpuWrite(VB *sim, uint32_t address, int type, int32_t value) {
int cancel = 0;
uint32_t cycles = 3; /* TODO: Research this */
uint32_t cycles = 0; /* TODO: Research this */
/* Reset pseudo-halt */
if (sim->ph.enabled)
@@ -500,7 +495,7 @@ static int cpuException(VB *sim) {
sim->cpu.eipc = sim->cpu.pc;
sim->cpu.psw.ep = 1;
sim->cpu.nextPC = 0xFFFF0000 |
((cause & 0xFFF0) == 0xFF60 ? 0xFF60 : cause & 0xFFF0);
((cause & 0xFFF0) == 0xFF70 ? 0xFF60 : cause & 0xFFF0);
}
/* All exceptions */
@@ -1642,6 +1637,7 @@ static void cpuSUBF_S(VB *sim) {
/* TRAP */
static void cpuTRAP(VB *sim) {
sim->cpu.clocks += cpuClocks(15);
sim->cpu.pc += 2;
cpuThrow(sim, 0xFFA0 + cpuGetImm5U(sim));
}
+164
View File
@@ -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
View File
@@ -9,12 +9,12 @@
static void padEmulate(VB *sim, uint32_t clocks) {
/* No hardware read in progress */
if (sim->pad.si_stat == 0)
if (!sim->pad.si_stat)
return;
/* Hardware read completes after time to process */
if (sim->pad.si_stat > clocks) {
sim->pad.si_stat -= clocks;
if (sim->pad.clocks > clocks) {
sim->pad.clocks -= clocks;
return;
}
@@ -38,7 +38,7 @@ static int32_t padReadControl(VB *sim) {
sim->pad.k_int_inh << 7 |
sim->pad.para_si << 5 |
sim->pad.soft_ck << 4 |
!!sim->pad.si_stat << 1 |
sim->pad.si_stat << 1 |
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.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 */
}
@@ -86,7 +87,7 @@ static void padWriteControl(VB *sim, uint8_t value) {
sim->pad.para_si = value >> 5 & 1;
if (
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;
/* 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 */
static uint32_t padUntil(VB *sim, uint32_t clocks) {
return sim->pad.si_stat != 0 && sim->pad.si_stat < clocks ?
sim->pad.si_stat : clocks;
return sim->pad.si_stat && sim->pad.clocks < clocks ?
sim->pad.clocks : clocks;
}
+1
View File
@@ -142,6 +142,7 @@ static uint32_t phDPSTTS(VB *sim) {
case 3: /* 8ms - L*BSY falling edge */
case 6: /* 13ms-18ms - Display right frame buffer */
case 7: /* 18ms - R*BSY falling edge */
case 8: /* 20ms - Display interval complete */
return sim->vip.dp.until;
}
/* case 1: 3ms - L*BSY rising edge */
+41 -14
View File
@@ -5,6 +5,15 @@
/***************************** 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 */
static void tmrUpdate(VB *sim, uint16_t value) {
if (value == 0 && sim->tmr.counter != 0) {
@@ -23,10 +32,6 @@ static void tmrUpdate(VB *sim, uint16_t value) {
/* Process component */
static void tmrEmulate(VB *sim, uint32_t clocks) {
/* Timer is disabled */
if (!sim->tmr.t_enb)
return;
/* Process all clocks */
for (;;) {
@@ -38,15 +43,22 @@ static void tmrEmulate(VB *sim, uint32_t clocks) {
}
/* Advance forward the component's number of clocks */
clocks -= sim->tmr.clocks;
sim->tmr.until -= sim->tmr.clocks;
sim->tmr.clocks = sim->tmr.t_clk_sel ? 400 : 2000;
clocks -= sim->tmr.clocks;
sim->tmr.until -= sim->tmr.clocks;
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 */
tmrUpdate(sim, sim->tmr.counter == 0 ?
sim->tmr.reload : sim->tmr.counter - 1);
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;
/* Other */
sim->tmr.clocks = 400;
sim->tmr.counter = 0xFFFF;
sim->tmr.reload = 0x0000;
sim->tmr.tick20 = 0;
}
/* Determine how many clocks are guaranteed to process */
@@ -88,14 +102,27 @@ static void tmrWriteControl(VB *sim, uint8_t value) {
if (
(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->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 */
sim->tmr.t_clk_sel = value >> 4 & 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)
sim->cpu.irq &= ~0x0002;
/* Configure countdowns */
sim->tmr.clocks = sim->tmr.t_clk_sel ? 400 : 2000;
sim->tmr.until = sim->tmr.clocks * (sim->tmr.counter == 0 ?
(uint32_t) sim->tmr.reload + 1 : sim->tmr.counter);
/* Configure state */
sim->tmr.until = tmrGetUntil(sim);
/* 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) {
sim->tmr.reload = (uint16_t) value << 8 | (sim->tmr.reload & 0x00FF);
tmrUpdate(sim, sim->tmr.reload);
sim->tmr.until = tmrGetUntil(sim);
}
/* Write to the low data register */
static void tmrWriteLow(VB *sim, uint8_t value) {
sim->tmr.reload = (sim->tmr.reload & 0xFF00) | value;
tmrUpdate(sim, sim->tmr.reload);
sim->tmr.until = tmrGetUntil(sim);
}
+189 -46
View File
@@ -8,8 +8,11 @@
/*********************************** Types ***********************************/
/* Output image */
typedef uint8_t Pixels[2][384*224];
/* VIP BG map cell */
typedef struct {
uint8_t *palette; /* Points to a VB.vip.gplt */
uint8_t *pixels; /* Points to a VB.vip.characters */
} Cell;
/* VSU channel */
typedef struct {
@@ -25,8 +28,9 @@ typedef struct {
uint8_t value; /* Master output level */
/* Other state */
uint32_t clocks; /* Clocks until modification */
uint8_t reload; /* Automatic reload value */
uint32_t clocks; /* Clocks until modification */
uint8_t modmask; /* Modifications masked */
uint8_t reload; /* Automatic reload value */
} env;
/* Frequency */
@@ -64,9 +68,58 @@ typedef struct {
} wave;
/* 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;
/* 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 */
struct VB {
@@ -146,6 +199,28 @@ struct VB {
int step; /* Operation sub-task ID */
} 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 */
struct {
@@ -159,8 +234,9 @@ struct VB {
uint8_t soft_ck; /* Controller communication signal */
/* Other state */
uint16_t keys; /* Next input bits */
int step; /* Software read processing phase */
uint32_t clocks; /* Master clocks to wait */
uint16_t keys; /* Next input bits */
int step; /* Software read processing phase */
} pad;
/* Timer */
@@ -176,6 +252,7 @@ struct VB {
uint32_t clocks; /* Master clocks to wait */
uint16_t counter; /* Current counter value */
uint16_t reload; /* Reload counter value */
uint8_t tick20; /* Current 20-microsecond tick */
uint32_t until; /* Clocks until interrupt condition */
} tmr;
@@ -236,6 +313,7 @@ struct VB {
int frame; /* FRMCYC counter */
int32_t halfword; /* Current output halfword offset */
int step; /* Processing phase */
uint8_t toggle; /* Switch frame buffers next frame */
uint32_t until; /* Clocks until interrupt condition */
} xp;
@@ -250,12 +328,17 @@ struct VB {
uint16_t spt[4]; /* Object control */
/* Rendering shadow memory */
uint8_t buffer; /* Output buffer */
uint16_t halfwords[384*28]; /* Output timing by 1x8 halfword */
Pixels output[2]; /* Output images, row-major */
Pixels shadow; /* Drawing shadow image, column-major */
/* Other state */
uint8_t ram[0x40000]; /* Video memory */
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 */
World worlds[32]; /* Pre-computed world attributes */
} vip;
/* VSU */
@@ -277,9 +360,11 @@ struct VB {
uint8_t shift; /* Sweep shift amount */
/* Other state */
uint32_t clocks; /* Clocks until modification */
uint16_t next; /* Next frequency value */
int sample; /* Current sample index */
uint32_t clocks; /* Clocks until modification */
uint8_t freqmask; /* Frequency mask */
uint8_t modmask; /* Modifications masked */
uint16_t next; /* Next frequency value */
int sample; /* Current sample index */
} freqmod;
/* Channel 6 noise generator */
@@ -296,9 +381,10 @@ struct VB {
struct {
float i1[2]; /* Previous analog input sample */
float o1[2]; /* Previous analog output sample */
uint32_t capacity; /* Number of audio frames in samples */
uint32_t offset; /* Position in output buffer */
int16_t *samples; /* Output memory */
unsigned capacity; /* Number of audio frames in output buffer */
unsigned offset; /* Position in output buffer */
void *samples; /* Output buffer */
int type; /* Output data type */
} out;
/* Memory */
@@ -310,6 +396,12 @@ struct VB {
int sample; /* Output sample index, period 417 */
} vsu;
/* Wait controller */
struct {
uint8_t exp1w; /* Cartridge expansion 1-wait */
uint8_t rom1w; /* Cartridge ROM 1-wait */
} wcr;
/* Pseudo-halt */
struct {
uint32_t address; /* Monitor address */
@@ -335,7 +427,6 @@ struct VB {
} ph;
/* Other state */
uint8_t wcr; /* Wait controller state */
uint8_t wram[0x10000]; /* System RAM */
/* Application data */
@@ -343,9 +434,11 @@ struct VB {
vbOnExecute onExecute; /* CPU instruction execute */
vbOnFetch onFetch; /* CPU instruction fetch */
vbOnFrame onFrame; /* VIP frame ready */
vbOnLink onLink; /* Communication transfer */
vbOnRead onRead; /* CPU instruction read */
vbOnSamples onSamples; /* VSU samples full */
vbOnWrite onWrite; /* CPU instruction write */
VB *peer; /* Communication peer */
void *tag; /* User data */
};
@@ -368,6 +461,7 @@ static int32_t SignExtend(int32_t value, int32_t bits) {
/******************************** Sub-Modules ********************************/
#include "ext.c"
#include "game-pad.c"
#include "timer.c"
#include "bus.c"
@@ -391,7 +485,8 @@ static int sysEmulate(VB *sim, uint32_t clocks) {
;
/* Process all components */
if (!never) ret = cpuEmulate(sim, clocks);
if (!never){ret = cpuEmulate(sim, clocks);}
ret |= extEmulate(sim, clocks);
padEmulate(sim, clocks);
tmrEmulate(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 */
static uint32_t sysUntil(VB *sim, uint32_t clocks) {
clocks = cpuUntil(sim, clocks);
clocks = extUntil(sim, clocks);
clocks = padUntil(sim, clocks);
clocks = tmrUntil(sim, clocks);
clocks = vipUntil(sim, clocks);
@@ -428,19 +524,23 @@ VBAPI int vbEmulate(VB *sim, uint32_t *clocks) {
}
/* 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 */
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) {
until = *clocks;
for (x = count - 1; x >= 0; x--)
until = sysUntil(sims[x], until);
for (x = count; x > 0; x--)
until = sysUntil(sims[x - 1], until);
brk = 0;
for (x = count - 1; x >= 0; x--)
brk |= sysEmulate(sims[x], until);
for (x = count; x > 0; x--)
brk |= sysEmulate(sims[x - 1], until);
*clocks -= until;
if (brk)
return brk; /* TODO: return 1 */
@@ -487,6 +587,11 @@ VBAPI uint16_t vbGetKeys(VB *sim) {
return sim->pad.keys;
}
/* Retrieve the current link callback handler */
VBAPI vbOnLink vbGetLinkCallback(VB *sim) {
return sim->onLink;
}
/* Retrieve a core option value */
VBAPI int vbGetOption(VB *sim, int key) {
switch (key) {
@@ -495,6 +600,11 @@ VBAPI int vbGetOption(VB *sim, int key) {
return 0;
}
/* Retrieve the communication peer */
VBAPI VB* vbGetPeer(VB *sim) {
return sim->peer;
}
/* Retrieve the most recent frame image pixels */
VBAPI void vbGetPixels(VB *sim, void *left, int leftStrideX, int leftStrideY,
void *right, int rightStrideX, int rightStrideY) {
@@ -527,7 +637,7 @@ VBAPI void vbGetPixels(VB *sim, void *left, int leftStrideX, int leftStrideY,
continue;
/* 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 (x = offset = 0; x < 384; x++, offset += xStride)
dest[offset] = *src++;
@@ -541,7 +651,7 @@ VBAPI uint32_t vbGetProgramCounter(VB *sim) {
}
/* 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];
}
@@ -551,13 +661,18 @@ VBAPI vbOnRead vbGetReadCallback(VB *sim) {
}
/* 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 (type != NULL)
*type = 0;
if (capacity != NULL)
*capacity = 0;
if (position != NULL)
*position = 0;
} else {
if (type != NULL)
*type = sim->vsu.out.type;
if (capacity != NULL)
*capacity = sim->vsu.out.capacity;
if (position != NULL)
@@ -572,8 +687,8 @@ VBAPI vbOnSamples vbGetSamplesCallback(VB *sim) {
}
/* Retrieve the value in a system register */
VBAPI uint32_t vbGetSystemRegister(VB *sim, int index) {
return index < 0 || index > 31 ? 0 : cpuGetSystemRegister(sim, index);
VBAPI uint32_t vbGetSystemRegister(VB *sim, unsigned index) {
return index > 31 ? 0 : cpuGetSystemRegister(sim, index);
}
/* Retrieve a simulation's userdata pointer */
@@ -590,14 +705,17 @@ VBAPI vbOnWrite vbGetWriteCallback(VB *sim) {
VBAPI VB* vbInit(VB *sim) {
sim->cart.ram = NULL;
sim->cart.rom = NULL;
sim->ph.enabled = 0;
sim->vsu.out.samples = NULL;
sim->onExecute = NULL;
sim->onFetch = NULL;
sim->onFrame = NULL;
sim->onLink = NULL;
sim->onRead = NULL;
sim->onSamples = NULL;
sim->onWrite = NULL;
sim->ph.enabled = 0;
sim->peer = NULL;
sim->tag = NULL;
vbReset(sim);
return sim;
}
@@ -616,7 +734,8 @@ VBAPI VB* vbReset(VB *sim) {
int x; /* Iterator */
/* Wait controller */
sim->wcr = 0x00;
sim->wcr.exp1w = 0;
sim->wcr.rom1w = 0;
/* WRAM (the hardware does not do this) */
for (x = 0; x < 0x10000; x++)
@@ -624,6 +743,7 @@ VBAPI VB* vbReset(VB *sim) {
/* Components */
cpuReset(sim);
extReset(sim);
padReset(sim);
tmrReset(sim);
vipReset(sim);
@@ -659,28 +779,28 @@ VBAPI int vbSetCartROM(VB *sim, void *rom, uint32_t size) {
/* Specify a new exception callback handler */
VBAPI vbOnException vbSetExceptionCallback(VB *sim, vbOnException callback) {
vbOnException prev = sim->onException;
sim->onException = callback;
sim->onException = callback;
return prev;
}
/* Specify a new execute callback handler */
VBAPI vbOnExecute vbSetExecuteCallback(VB *sim, vbOnExecute callback) {
vbOnExecute prev = sim->onExecute;
sim->onExecute = callback;
sim->onExecute = callback;
return prev;
}
/* Specify a new fetch callback handler */
VBAPI vbOnFetch vbSetFetchCallback(VB *sim, vbOnFetch callback) {
vbOnFetch prev = sim->onFetch;
sim->onFetch = callback;
sim->onFetch = callback;
return prev;
}
/* Specify a new frame callback handler */
VBAPI vbOnFrame vbSetFrameCallback(VB *sim, vbOnFrame callback) {
vbOnFrame prev = sim->onFrame;
sim->onFrame = callback;
sim->onFrame = callback;
return prev;
}
@@ -689,6 +809,13 @@ VBAPI uint16_t vbSetKeys(VB *sim, uint16_t 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 */
VBAPI int vbSetOption(VB *sim, int key, int value) {
switch (key) {
@@ -706,8 +833,23 @@ VBAPI int vbSetOption(VB *sim, int key, int 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 */
VBAPI uint32_t vbSetProgramCounter(VB *sim, uint32_t value) {
sim->cpu.clocks = 0;
sim->cpu.operation = CPU_FETCH;
sim->cpu.pc = sim->cpu.nextPC = value & 0xFFFFFFFE;
sim->cpu.step = 0;
@@ -715,24 +857,26 @@ VBAPI uint32_t vbSetProgramCounter(VB *sim, uint32_t value) {
}
/* 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);
}
/* Specify a new read callback handler */
VBAPI vbOnRead vbSetReadCallback(VB *sim, vbOnRead callback) {
vbOnRead prev = sim->onRead;
sim->onRead = callback;
sim->onRead = callback;
return prev;
}
/* Specify a new audio samples buffer */
VBAPI int vbSetSamples(VB *sim, void *samples, uint32_t capacity) {
if (samples != NULL && (capacity == 0 || capacity > 0x40000000))
VBAPI int vbSetSamples(VB *sim, void *samples, int type, unsigned capacity) {
if (samples != NULL &&
(capacity == 0 || (type != VB_S16 && type != VB_F32)))
return 1;
sim->vsu.out.capacity = samples == NULL ? 0 : capacity;
sim->vsu.out.capacity = capacity;
sim->vsu.out.offset = 0;
sim->vsu.out.samples = samples;
sim->vsu.out.type = type;
return 0;
}
@@ -744,27 +888,26 @@ VBAPI vbOnSamples vbSetSamplesCallback(VB *sim, vbOnSamples callback) {
}
/* Specify a new value for a system register */
VBAPI uint32_t vbSetSystemRegister(VB *sim, int index, uint32_t value) {
return index < 0 || index > 31 ? 0 :
cpuSetSystemRegister(sim, index, value, 1);
VBAPI uint32_t vbSetSystemRegister(VB *sim, unsigned index, uint32_t value) {
return index > 31 ? 0 : cpuSetSystemRegister(sim, index, value, 1);
}
/* Specify a new write callback handler */
VBAPI vbOnWrite vbSetWriteCallback(VB *sim, vbOnWrite callback) {
vbOnWrite prev = sim->onWrite;
sim->onWrite = callback;
sim->onWrite = callback;
return prev;
}
/* Determine the size of a simulation instance */
VBAPI size_t vbSizeOf() {
VBAPI size_t vbSizeOf(void) {
return sizeof (VB);
}
/* Specify a simulation's userdata pointer */
VBAPI void* vbSetUserData(VB *sim, void *tag) {
void *prev = sim->tag;
sim->tag = tag;
sim->tag = tag;
return prev;
}
+33 -9
View File
@@ -22,6 +22,7 @@ extern "C" {
VB_DIRECT_EXECUTE
VB_DIRECT_FETCH
VB_DIRECT_FRAME
VB_DIRECT_LINK
VB_DIRECT_READ
VB_DIRECT_SAMPLES
VB_DIRECT_WRITE
@@ -78,10 +79,29 @@ extern "C" {
#define VB_S16 2
#define VB_U16 3
#define VB_S32 4
#define VB_F32 5
/* Option keys */
#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 ***********************************/
@@ -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 (*vbOnFetch )(VB *sim, int fetch, uint32_t address, int32_t *value, uint32_t *cycles);
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 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);
@@ -104,8 +125,7 @@ typedef int (*vbOnWrite )(VB *sim, uint32_t address, int type, int32_t *valu
/******************************* API Commands ********************************/
VBAPI int vbEmulate (VB *sim, uint32_t *clocks);
VBAPI int vbEmulateEx (VB **sims, int count, uint32_t *clocks);
VBAPI void* vbGetCallback (VB *sim, int id);
VBAPI int vbEmulateEx (VB **sims, unsigned count, uint32_t *clocks);
VBAPI void* vbGetCartRAM (VB *sim, uint32_t *size);
VBAPI void* vbGetCartROM (VB *sim, uint32_t *size);
VBAPI vbOnException vbGetExceptionCallback(VB *sim);
@@ -113,14 +133,16 @@ VBAPI vbOnExecute vbGetExecuteCallback (VB *sim);
VBAPI vbOnFetch vbGetFetchCallback (VB *sim);
VBAPI vbOnFrame vbGetFrameCallback (VB *sim);
VBAPI uint16_t vbGetKeys (VB *sim);
VBAPI vbOnLink vbGetLinkCallback (VB *sim);
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 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 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 uint32_t vbGetSystemRegister (VB *sim, int index);
VBAPI uint32_t vbGetSystemRegister (VB *sim, unsigned index);
VBAPI void* vbGetUserData (VB *sim);
VBAPI vbOnWrite vbGetWriteCallback (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 vbOnFetch vbSetFetchCallback (VB *sim, vbOnFetch 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 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 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 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 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 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);
+639 -299
View File
File diff suppressed because it is too large Load Diff
+210 -111
View File
@@ -45,9 +45,28 @@ static void vsuOnSamples(VB *sim) {
/***************************** 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 */
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 */
if (sim->vsu.freqmod.func == 0) {
@@ -57,117 +76,111 @@ static void vsuNextFreqMod(VB *sim, Channel *chan) {
else next = chan->freq.current + next;
if (next > 2047)
chan->int_.enb = 0;
if (next < 0)
next = 0;
}
/* Modulation */
else {
next = (chan->freq.written +
sim->vsu.modulation[sim->vsu.freqmod.sample]) & 2047;
if (sim->vsu.freqmod.sample != 31)
sim->vsu.freqmod.sample++;
else if (sim->vsu.freqmod.rep)
sim->vsu.freqmod.sample = 0;
next = ((int32_t) chan->freq.written +
sim->vsu.modulation[sim->vsu.freqmod.sample]) & 0x7FF;
if (sim->vsu.freqmod.freqmask == 1)
next = (next & 0x700) | (chan->freq.written & 0x0FF);
else if (sim->vsu.freqmod.freqmask == 2)
next = (next & 0x0FF) | (chan->freq.written & 0x700);
}
/* Configure state */
sim->vsu.freqmod.next = next;
if (chan->freqmod)
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 */
static void vsuEmulateChannel(VB *sim, int index, uint32_t clocks) {
uint32_t bit; /* Pseudorandom bit */
Channel *chan; /* Channel handle */
int freqmod; /* Frequency modifications enabled */
uint32_t until; /* Clocks to process sub-channel components */
static void vsuEmulateChannel(VB *sim, Channel *chan) {
/* Select channel */
chan = &sim->vsu.channels[index];
/* Channel is disabled */
if (!chan->int_.enb)
/* Automatic shutoff */
if (chan->int_.auto_ && chan->int_.clocks == 0) {
chan->int_.enb = 0;
return;
}
/* Process all clocks */
do {
/* Next input sample */
if (chan->clocks == 0) {
/* 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 */
if (chan->int_.auto_ && chan->int_.clocks == 0) {
chan->int_.enb = 0;
return;
/* Wave */
if (chan != &sim->vsu.channels[5]) {
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
chan->wave.sample = (chan->wave.sample + 1) & 31;
}
/* Next sample */
if (chan->clocks == 0) {
/* Wave */
if (index != 5) {
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
chan->wave.sample = (chan->wave.sample + 1) & 31;
}
/* Noise */
else {
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
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);
}
/* Noise */
else {
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
vsuNextNoise(sim);
}
/* Envelope modification */
if (chan->env.enb && chan->env.clocks == 0) {
if (chan->env.dir == 0 && chan->env.value != 0)
chan->env.value--;
else if (chan->env.dir == 1 && chan->env.value != 15)
chan->env.value++;
else if (chan->env.rep)
chan->env.value = chan->env.reload;
chan->env.clocks = ((uint32_t) chan->env.interval + 1) * 307220;
}
}
/* Frequency modification */
if (freqmod && sim->vsu.freqmod.clocks == 0) {
/* Envelope modification */
if (chan->env.clocks == 0) {
uint8_t new_envelope = chan->env.value;
if (chan->env.dir == 0 && chan->env.value != 0)
new_envelope--;
else if (chan->env.dir == 1 && chan->env.value != 15)
new_envelope++;
else if (chan->env.rep && chan->env.modmask != 2) {
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;
}
/* Frequency modification */
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;
vsuNextFreqMod(sim, chan);
if (!chan->int_.enb)
return;
/* Channel itself is disabled */
if (!chan->int_.enb)
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.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.value = value >> 4 & 15;
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);
}
/* Configure state */
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 */
chan->env.enb = value & 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 */
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.func = value >> 4 & 1;
sim->vsu.freqmod.rep = value >> 5 & 1;
chan->freqmod = vsuCheckFreqMod(sim);
vsuNextFreqMod(sim, chan);
break;
case 5: /* Channel 6 */
sim->vsu.noise.tap = value >> 4 & 7;
sim->vsu.noise.register_ = 0x0000;
vsuNextNoise(sim);
}
}
@@ -245,8 +269,10 @@ static void vsuWriteFQH(VB *sim, int index, uint16_t value) {
value = value << 8 & 0x0700;
chan->freq.current = (chan->freq.current & 0x00FF) | value;
chan->freq.written = (chan->freq.written & 0x00FF) | value;
if (index == 4)
vsuNextFreqMod(sim, chan);
if (index != 4)
return;
sim->vsu.freqmod.freqmask = 2;
vsuNextFreqMod(sim, chan);
}
/* Write a value to S*FQL */
@@ -254,8 +280,10 @@ static void vsuWriteFQL(VB *sim, int index, uint8_t value) {
Channel *chan = &sim->vsu.channels[index];
chan->freq.current = (chan->freq.current & 0x0700) | value;
chan->freq.written = (chan->freq.written & 0x0700) | value;
if (index == 4)
vsuNextFreqMod(sim, chan);
if (index != 4)
return;
sim->vsu.freqmod.freqmask = 1;
vsuNextFreqMod(sim, chan);
}
/* Write a value to S*INT */
@@ -269,19 +297,29 @@ static void vsuWriteINT(VB *sim, int index, uint8_t value) {
/* Update state */
chan->int_.clocks = 76805 * ((uint32_t) chan->int_.interval + 1);
if (chan->env.enb)
chan->env.clocks = 307220 * ((uint32_t) chan->env.interval + 1);
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);
if (index != 5) {
chan->wave.sample = 0;
chan->clocks = 4 * (2048 - (uint32_t) chan->freq.current);
} else {
sim->vsu.noise.register_ = 0x0000;
vsuNextNoise(sim);
chan->clocks = 40 * (2048 - (uint32_t) chan->freq.current);
}
if (index == 4) {
sim->vsu.freqmod.clocks = (uint32_t) sim->vsu.freqmod.interval *
(sim->vsu.freqmod.clk == 0 ? 19200 : 153600);
sim->vsu.freqmod.sample = 0;
sim->vsu.freqmod.modmask = 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);
if (clocks < sim->vsu.freqmod.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 */
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 o0; /* Current analog output sample */
uint16_t output[2]; /* Digital output samples */
@@ -340,12 +382,55 @@ static void vsuEmulate(VB *sim, uint32_t clocks) {
sim->vsu.clocks -= until;
/* Process all channels */
for (x = 0; x < 6; x++)
vsuEmulateChannel(sim, x, until);
for (x = 0; x < 6; x++) {
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 */
if (sim->vsu.clocks != 0)
continue;
return;
/* Compute the output sample */
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.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 (
e == 1 &&
sim->vsu.out.offset >> 1 == sim->vsu.out.capacity
@@ -403,10 +501,13 @@ static void vsuReset(VB *sim) {
for (x = 0; x < 6; x++) {
chan = &sim->vsu.channels[x];
chan->clocks = 0;
chan->freqmod = 0;
chan->until = 0;
chan->env.clocks = 0;
chan->env.enb = 0;
chan->env.dir = 0;
chan->env.interval = 0;
chan->env.modmask = 0;
chan->env.reload = 0;
chan->env.rep = 0;
chan->env.value = 0;
@@ -427,8 +528,10 @@ static void vsuReset(VB *sim) {
sim->vsu.freqmod.clocks = 0;
sim->vsu.freqmod.dir = 0;
sim->vsu.freqmod.enb = 0;
sim->vsu.freqmod.freqmask = 0;
sim->vsu.freqmod.func = 0;
sim->vsu.freqmod.interval = 0;
sim->vsu.freqmod.modmask = 0;
sim->vsu.freqmod.next = 0;
sim->vsu.freqmod.rep = 0;
sim->vsu.freqmod.sample = 0;
@@ -445,21 +548,17 @@ static void vsuReset(VB *sim) {
/* Write a typed value to the VSU bus */
static void vsuWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){
/* Only byte writes are allowed */
switch (type) {
case VB_S16:
case VB_U16:
case VB_S32:
return;
}
(void) type;
/* Unmapped */
if (address & 3)
if (address & 1)
return;
/* Working variables */
address &= 0x7FF;
address &= 0x7FC;
/* Clear frequency mask */
sim->vsu.freqmod.freqmask = 0;
/* Wave memory */
if (address < 0x280) {
+24 -5
View File
@@ -10,6 +10,7 @@ help:
@echo "Available make targets:"
@echo " clean Remove output files"
@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
@@ -29,7 +30,7 @@ core:
-D VB_DIRECT_EXCEPTION=textException -D VB_DIRECT_EXECUTE=textExecute \
-D VB_DIRECT_FETCH=testFetch -D VB_DIRECT_FRAME=testFrame \
-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
@emcc core/vb.c -I core -c -o /dev/null -O3 \
-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_FETCH=testFetch -D VB_DIRECT_FRAME=testFrame \
-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
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 "VBAPI=__attribute__((used))" \
--no-entry -O2 -flto -s WASM=1 -s WARN_ON_UNDEFINED_SYMBOLS=0 \
-D "VBAPI=__attribute__((used))" -D "VBUAPI=__attribute__((used))" \
-D VB_DIRECT_FRAME=wasmOnFrame \
--no-entry -O2 -flto -s WASM=1 \
-s EXPORTED_RUNTIME_METHODS=[] -s ALLOW_MEMORY_GROWTH \
-s MAXIMUM_MEMORY=4GB -fno-strict-aliasing
@rm -f web/*.wasm.tmp*
+838
View File
@@ -0,0 +1,838 @@
/* This file is included into vbu.c and cannot be compiled on its own. */
#ifdef VBUAPI
/********************************* Constants *********************************/
/* Assembly text formats */
#define FMT_BITSTRING 0
#define FMT_BCOND 1
#define FMT_FLOATENDO 2
#define FMT_SETF 3
#define FMT_ILLEGAL 4
#define FMT_NORMAL 5
/* Operand types */
#define OP_COND 0
#define OP_DISP9 1
#define OP_DISP26 2
#define OP_IMM5S 3
#define OP_IMM5U 4
#define OP_IMM16S 5
#define OP_IMM16U 6
#define OP_JMP 7
#define OP_MEM 8
#define OP_REG1 9
#define OP_REG2 10
#define OP_SYS 11
/*********************************** Types ***********************************/
/* Disassembler output */
typedef struct {
char *buffer; /* Managed memory buffer */
size_t capacity; /* Size of allocated buffer */
size_t length; /* Bytes currently used in buffer */
} DasmOutput;
/* Opcode definition */
typedef struct {
char *mnemonic; /* Display text of instruction's mnemonic */
uint8_t codeLength; /* Number of bytes in instruction code */
uint8_t format; /* Display text format */
uint8_t operandsLength; /* Number of operands in display format */
uint8_t operands[3]; /* Types of display text operands */
} OpDef;
/******************************** Lookup Data ********************************/
/* Top-level opcodes */
static const OpDef OPDEFS[] = {
{ "MOV" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "ADD" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "SUB" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "CMP" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "SHL" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "SHR" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "JMP" , 2, FMT_NORMAL , 1, { OP_JMP , 0 , 0 } },
{ "SAR" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "MUL" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "DIV" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "MULU" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "DIVU" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "OR" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "AND" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "XOR" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "NOT" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
{ "MOV" , 2, FMT_NORMAL , 2, { OP_IMM5S , OP_REG2, 0 } },
{ "ADD" , 2, FMT_NORMAL , 2, { OP_IMM5S , OP_REG2, 0 } },
{ "SETF" , 2, FMT_SETF , 0, { 0 , 0 , 0 } },
{ "CMP" , 2, FMT_NORMAL , 2, { OP_IMM5S , OP_REG2, 0 } },
{ "SHL" , 2, FMT_NORMAL , 2, { OP_IMM5U , OP_REG2, 0 } },
{ "SHR" , 2, FMT_NORMAL , 2, { OP_IMM5U , OP_REG2, 0 } },
{ "CLI" , 2, FMT_NORMAL , 0, { 0 , 0 , 0 } },
{ "SAR" , 2, FMT_NORMAL , 2, { OP_IMM5U , OP_REG2, 0 } },
{ "TRAP" , 2, FMT_NORMAL , 1, { OP_IMM5U , 0 , 0 } },
{ "RETI" , 2, FMT_NORMAL , 0, { 0 , 0 , 0 } },
{ "HALT" , 2, FMT_NORMAL , 0, { 0 , 0 , 0 } },
{ NULL , 2, FMT_ILLEGAL , 0, { 0 , 0 , 0 } },
{ "LDSR" , 2, FMT_NORMAL , 2, { OP_REG2 , OP_SYS , 0 } },
{ "STSR" , 2, FMT_NORMAL , 2, { OP_SYS , OP_REG2, 0 } },
{ "SEI" , 2, FMT_NORMAL , 0, { 0 , 0 , 0 } },
{ NULL , 2, FMT_BITSTRING, 0, { 0 , 0 , 0 } },
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
{ "MOVEA", 4, FMT_NORMAL , 3, { OP_IMM16U, OP_REG1, OP_REG2 } },
{ "ADDI" , 4, FMT_NORMAL , 3, { OP_IMM16S, OP_REG1, OP_REG2 } },
{ "JR" , 4, FMT_NORMAL , 1, { OP_DISP26, 0 , 0 } },
{ "JAL" , 4, FMT_NORMAL , 1, { OP_DISP26, 0 , 0 } },
{ "ORI" , 4, FMT_NORMAL , 3, { OP_IMM16U, OP_REG1, OP_REG2 } },
{ "AND" , 4, FMT_NORMAL , 3, { OP_IMM16U, OP_REG1, OP_REG2 } },
{ "XOR" , 4, FMT_NORMAL , 3, { OP_IMM16U, OP_REG1, OP_REG2 } },
{ "MOVHI", 4, FMT_NORMAL , 3, { OP_IMM16U, OP_REG1, OP_REG2 } },
{ "LD.B" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
{ "LD.H" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
{ NULL , 2, FMT_ILLEGAL , 0, { 0 , 0 , 0 } },
{ "LD.W" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
{ "ST.B" , 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } },
{ "ST.H" , 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } },
{ NULL , 2, FMT_ILLEGAL , 0, { 0 , 0 , 0 } },
{ "ST.W" , 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } },
{ "IN.B" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
{ "IN.H" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
{ "CAXI" , 2, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
{ "IN.W" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
{ "OUT.B", 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } },
{ "OUT.H", 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } },
{ NULL , 4, FMT_FLOATENDO, 0, { 0 , 0 , 0 } },
{ "OUT.W", 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } }
};
/* Format VII opcodes */
static const OpDef FLOATENDOS[] = {
{ "CMPF.S" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
{ NULL , 4, FMT_ILLEGAL, 0, { 0 , 0 , 0 } },
{ "CVT.WS" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
{ "CVT.SW" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
{ "ADDF.S" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
{ "SUBF.S" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
{ "MULF.S" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
{ "DIVF.S" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
{ "XB" , 4, FMT_NORMAL , 1, { OP_REG2, 0 , 0 } },
{ "XH" , 4, FMT_NORMAL , 1, { OP_REG2, 0 , 0 } },
{ "REV" , 4, FMT_NORMAL , 1, { OP_REG1, OP_REG2, 0 } },
{ "TRNC.SW", 4, FMT_NORMAL , 1, { OP_REG1, OP_REG2, 0 } },
{ "MPYHW" , 4, FMT_NORMAL , 1, { OP_REG1, OP_REG2, 0 } }
};
/* Bit string mnemonics */
static const OpDef BITSTRINGS[] = {
{ "SCH0BSU", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ "SCH0BSD", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ "SCH1BSU", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ "SCH1BSD", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ NULL , 2, FMT_ILLEGAL, 0, { 0, 0, 0 } },
{ NULL , 2, FMT_ILLEGAL, 0, { 0, 0, 0 } },
{ NULL , 2, FMT_ILLEGAL, 0, { 0, 0, 0 } },
{ NULL , 2, FMT_ILLEGAL, 0, { 0, 0, 0 } },
{ "ORBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ "ANDBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ "XORBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ "MOVBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ "ORNBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ "ANDNBSU", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ "XORNBSU", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
{ "NOTBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } }
};
/* Special instructions */
static const OpDef
BCOND_J = { NULL , 2, FMT_NORMAL, 1, { OP_DISP9, 0 , 0 } },
BCOND_S = { "Bcond", 2, FMT_NORMAL, 2, { OP_COND , OP_DISP9, 0 } },
ILLEGAL = { "---" , 2, FMT_NORMAL, 0, { 0 , 0 , 0 } },
SETF_J = { NULL , 2, FMT_NORMAL, 1, { OP_REG2 , 0 , 0 } },
SETF_S = { "SETF" , 2, FMT_NORMAL, 2, { OP_COND , OP_REG2 , 0 } }
;
/***************************** Module Functions ******************************/
/* Format a number as hexadecimal */
static int dasmDec(char *dest, int32_t value) {
int digits; /* Number of digits in value */
int32_t divisor; /* Current power of 10 */
int len = 0; /* Length of resulting string */
int32_t x; /* Scratch */
/* Value is negative */
if (value < 0) {
dest[len++] = '-';
value = -value;
}
/* Count how many digits are needed to represent the number */
digits = 1;
divisor = 1;
for (x = value / 10; x > 0; x /= 10) {
digits++;
divisor *= 10;
}
/* Output each digit from highest to lowest */
for (x = 0; x < digits; x++) {
int digit = value / divisor;
dest[len++] = '0' + digit;
value -= digit * divisor;
divisor /= 10;
}
/* Return length of string */
dest[len] = 0;
return len;
}
/* Retrieve a joined condition-based mnemonic */
static char* dasmGetCondition(
VBU_DasmConfig *config, int id, int bcond, int setf) {
/* Joined Bcond mnemonics */
static const char *BCONDS[] = {
"BV" , "BL" , "BZ" , "BNH", "BN", "BR" , "BLT", "BLE",
"BNV", "BNL", "BNZ", "BH" , "BP", "NOP", "BGE", "BGT",
NULL , "BC" , "BE" , NULL , NULL, NULL , NULL , NULL ,
NULL , "BNC", "BNE", NULL , NULL, NULL , NULL , NULL
};
/* Split condition operand mnemonics */
static const char *CONDITIONS[] = {
"V" , "L" , "Z" , "NH", "N" , "T" , "LT", "LE",
"NV", "NL", "NZ", "H" , "P" , "F" , "GE", "GT",
NULL, "C" , "E" , NULL, NULL, NULL, NULL, NULL,
NULL, "NC", "NE", NULL, NULL, NULL, NULL, NULL
};
/* Joined SETF mnemonics */
static const char *SETFS[] = {
"SETFV" , "SETFL" , "SETFZ" , "SETFNH",
"SETFN" , "SETFT" , "SETFLT", "SETFLE",
"SETFNV", "SETFNL", "SETFNZ", "SETFH" ,
"SETFP" , "SETFF" , "SETFGE", "SETFGT",
NULL , "SETFC" , "SETFE" , NULL ,
NULL , NULL , NULL , NULL ,
NULL , "SETFNC", "SETFNE", NULL ,
NULL , NULL , NULL , NULL
};
/* Alternate mnemonic for C/L and E/Z */
if (
(config->conditionCL == VBU_C && (id & 7) == 1) ||
(config->conditionEZ == VBU_E && (id & 7) == 2)
) id += 16;
/* Select the mnemonic from the appropriate list */
return (char *) (bcond ? BCONDS : setf ? SETFS : CONDITIONS)[id];
}
/* Format a number as hexadecimal */
static int dasmHex(char *dest,int format,int caps,int32_t value,int digits) {
int len = 0; /* Length of resulting string */
int x; /* Iterator */
/* Hex digits */
static const char DIGITS[] = "0123456789ABCDEF";
/* Prefix */
switch (format) {
case VBU_PREFIX_0X:
dest[len++] = '0';
dest[len++] = 'x';
break;
case VBU_PREFIX_DOLLAR:
dest[len++] = '$';
break;
case VBU_SUFFIX_H:
if ((value >> ((digits - 1) << 2) & 15) >= 10)
dest[len++] = '0';
break;
}
/* Value */
for (x = 0; x < digits; x++) {
int digit = value >> ((digits - 1 - x) << 2) & 15;
char c = DIGITS[digit];
if (caps == VBU_LOWER && digit >= 10)
c += 'a' - 'A';
dest[len++] = c;
}
/* Suffix */
if (format == VBU_SUFFIX_H)
dest[len++] = 'h';
/* Return length of string */
dest[len] = 0;
return len;
}
/* Format a branch operand */
static int dasmOpBranch(char *dest, VBU_DasmConfig *config,
uint16_t *code, int type, uint32_t address) {
int32_t disp; /* Displacement amount */
int len = 0; /* Length of resulting string */
/* Parse the displacement operand */
disp = type == OP_DISP9 ?
(int32_t) (int8_t ) (code[0] >> 1) << 1 | (code[0] & 1) :
(int32_t) (int16_t) (code[0] << 6) << 10 | code[1]
;
/* Absolute target address formatting */
if (config->branchNotation == VBU_ABSOLUTE)
return dasmHex(dest, -1, config->hexCase, address + disp, 8);
/* Negative displacement */
if (disp < 0) {
disp = -disp;
dest[len++] = '-';
}
/* Positive displacement */
else dest[len++] = '+';
/* Format the displacement as decimal */
return len + dasmDec(&dest[len], disp);
}
/* Format a condition operand */
static int dasmOpCondition(char *dest, VBU_DasmConfig *config, uint16_t *code){
char *src; /* Source mnemonic */
int x; /* Iterator */
/* Retrieve the standalone condition mnemonic */
src = dasmGetCondition(config,
code[0] >> ((code[0] >> 15 & 1) ? 9 : 0) & 15, 0, 0);
/* Copy the characters to the output */
for (x = 0; src[x]; x++) {
char c = src[x];
if (config->conditionCase == VBU_LOWER && c >= 'A' && c <= 'Z')
c += 'a' - 'A';
dest[x] = c;
}
/* Return length of string */
dest[x] = 0;
return x;
}
/* Format an immediate operand */
static int dasmOpImmediate(
char *dest, VBU_DasmConfig *config, uint16_t *code, int type) {
int len = 0; /* Length of resulting string */
int32_t value; /* Immediate value */
/* Prefix */
if (config->immediateNotation == VBU_PREFIX_NUMBER)
dest[len++] = '#';
/* Select format type */
switch (type) {
case OP_IMM5S:
value = code[0] & 31;
return len + dasmDec(&dest[len], value > 15 ? value - 32 : value);
case OP_IMM5U:
return len + dasmDec(&dest[len], code[0] & 31);
case OP_IMM16U:
return len + dasmHex(&dest[len],
config->hexNotation, config->hexCase, code[1], 4);
}
/* Parse the value */
value = (int16_t) code[1];
/* Magnitude is small: format as decimal */
if (value >= -256 && value <= 256)
return len + dasmDec(&dest[len], value);
/* Value is negative */
if (value < 0) {
dest[len++] = '-';
value = -value;
}
/* Magnitude is large: format as hexadecimal */
return len + dasmHex(&dest[len],
config->hexNotation, config->hexCase, value, 4);
}
/* Format a program register operand */
static int dasmOpProgram(
char *dest, VBU_DasmConfig *config, uint16_t *code, int type) {
int len = 0; /* Length of resulting string */
char *src; /* Program register mnemonic */
int x; /* Scratch */
/* Program registers by name */
static const char *NAMES[] = {
NULL, NULL, "HP", "SP", "GP", "TP", NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, "LP"
};
/* Program registers by number */
static const char *NUMBERS[] = {
"R0" , "R1" , "R2" , "R3" , "R4" , "R5" , "R6" , "R7" ,
"R8" , "R9" , "R10", "R11", "R12", "R13", "R14", "R15",
"R16", "R17", "R18", "R19", "R20", "R21", "R22", "R23",
"R24", "R25", "R26", "R27", "R28", "R29", "R30", "R31"
};
/* Select the program register mnemonic */
x = code[0] >> (type == OP_REG1 ? 0 : 5) & 31;
src = (char *) NUMBERS[x];
if (config->programNotation == VBU_NAMES && NAMES[x] != NULL)
src = (char *) NAMES[x];
/* Format the operand characters */
for (x = 0; src[x]; x++) {
char c = src[x];
if (config->programCase == VBU_LOWER && c >= 'A' && c <= 'Z')
c += 'a' - 'A';
dest[len++] = c;
}
/* Return length of string */
dest[len] = 0;
return len;
}
/* Format a JMP operand */
static int dasmOpJMP(char *dest, VBU_DasmConfig *config, uint16_t *code) {
int len = 0; /* Length of resulting string */
/* Format the operand characters */
dest[len++] = '[';
len += dasmOpProgram(&dest[len], config, code, OP_REG1);
dest[len++] = ']';
/* Return length of string */
dest[len] = 0;
return len;
}
/* Format a memory operand */
static int dasmOpMemory(char *dest, VBU_DasmConfig *config, uint16_t *code) {
int32_t disp; /* Memory displacement */
int len = 0; /* Length of resulting string */
/* Parse operand */
disp = (int16_t) code[1];
/* No-displacement notation */
if (disp == 0) {
dest[len++] = '[';
len += dasmOpProgram(&dest[len], config, code, OP_REG1);
dest[len++] = ']';
}
/* Outside notation */
else if (config->memoryNotation == VBU_OUTSIDE) {
/* Magnitude is large: format as hexadecimal */
if (disp < -256 || disp > 256) {
/* Displacement is negative */
if (disp < 0) {
dest[len++] = '-';
disp = -disp;
}
/* Format displacement */
len += dasmHex(&dest[len], config->hexNotation,
config->hexCase, disp, 4);
}
/* Magnitude is small: format as decimal */
else len += dasmDec(&dest[len], disp);
/* Format operand characters */
dest[len++] = '[';
len += dasmOpProgram(&dest[len], config, code, OP_REG1);
dest[len++] = ']';
}
/* Inside notation */
else {
/* Format operand characters */
dest[len++] = '[';
len += dasmOpProgram(&dest[len], config, code, OP_REG1);
dest[len++] = ' ';
/* Displacement is negative */
if (disp < 0) {
dest[len++] = '-';
disp = -disp;
}
/* Displacement is positive */
else dest[len++] = '+';
/* Format operand characters */
dest[len++] = ' ';
/* Magnitude is large: format as hexadecimal */
if (disp < -256 || disp > 256) {
len += dasmHex(&dest[len], config->hexNotation,
config->hexCase, disp, 4);
}
/* Magnitude is small: format as decimal */
else len += dasmDec(&dest[len], disp);
/* Format operand characters */
dest[len++] = ']';
}
/* Return length of string */
dest[len] = 0;
return len;
}
/* Format a system register operand */
static int dasmOpSystem(char *dest, VBU_DasmConfig *config, uint16_t *code) {
int len = 0; /* Length of resulting string */
char *src; /* System register mnemonic */
int x; /* Scratch */
/* System registers by name */
static const char *NAMES[] = {
"EIPC", "EIPSW", "FEPC", "FEPSW", "ECR", "PSW", "PIR", "TKCW",
NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL ,
NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL ,
"CHCW", "ADTRE", NULL , NULL , NULL , NULL , NULL , NULL
};
/* System registers by number */
static const char *NUMBERS[] = {
"0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" ,
"8" , "9" , "10", "11", "12", "13", "14", "15",
"16", "17", "18", "19", "20", "21", "22", "23",
"24", "25", "26", "27", "28", "29", "30", "31"
};
/* Select the system register mnemonic */
x = code[0] & 31;
src = (char *) NUMBERS[x];
if (config->systemNotation == VBU_NAMES && NAMES[x] != NULL)
src = (char *) NAMES[x];
/* Format the operand characters */
for (x = 0; src[x]; x++) {
char c = src[x];
if (config->systemCase == VBU_LOWER && c >= 'A' && c <= 'Z')
c += 'a' - 'A';
dest[len++] = c;
}
/* Return length of string */
dest[len] = 0;
return len;
}
/* Process one line of disassembly */
static uint32_t dasmLine(DasmOutput *ret, unsigned index,
VB *sim, uint32_t address, int isPC, VBU_DasmConfig *config) {
uint16_t code[2]; /* Instruction bits */
OpDef *def; /* Opcode definition */
VBU_DasmLine *line; /* Current output line */
char *mnemonic; /* Mnemonic display text */
int mnemonicLen; /* Length of mnemonic text */
char operands[3][16]; /* Operand display text */
int opLens[3]; /* Operand string lengths */
size_t textLen; /* Number of text bytes needed */
int x; /* Scratch */
/* Select the current line in the buffer */
line = &((VBU_DasmLine *) ret->buffer)[index];
line->address = address;
line->isPC = isPC;
/* Fetch first code halfword */
code[0] = vbRead(sim, address, VB_U16);
line->code[0] = code[0];
line->code[1] = code[0] >> 8;
/* Select opcode definition */
def = (OpDef *) &OPDEFS[code[0] >> 10 & 63];
line->codeLength = def->codeLength;
mnemonic = def->mnemonic;
/* Fetch second code halfword if necessary */
if (line->codeLength == 4) {
code[1] = vbRead(sim, address + 2, VB_U16);
line->code[2] = code[1];
line->code[3] = code[1] >> 8;
} else {
code[1] = 0;
line->code[2] = 0;
line->code[3] = 0;
}
/* Two-step processing */
switch (def->format) {
/* Bcond */
case FMT_BCOND:
if (config->bcondNotation == VBU_SPLIT) {
def = (OpDef *) &BCOND_S;
mnemonic = def->mnemonic;
} else {
x = code[0] >> 9 & 15;
def = (OpDef *) &BCOND_J;
mnemonic = dasmGetCondition(config, x, 1, 0);
}
break;
/* Bit string instructions */
case FMT_BITSTRING:
x = code[0] & 31;
def = (OpDef *) &BITSTRINGS[x > 15 ? 4 : x];
mnemonic = def->mnemonic;
break;
/* Floating-point and Nintendo instructions */
case FMT_FLOATENDO:
x = code[1] >> 10 & 63;
def = (OpDef *) &FLOATENDOS[x > 12 ? 1 : x];
mnemonic = def->mnemonic;
break;
/* SETF */
case FMT_SETF:
if (config->setfNotation == VBU_SPLIT) {
def = (OpDef *) &SETF_S;
mnemonic = def->mnemonic;
} else {
x = code[0] & 15;
def = (OpDef *) &SETF_J;
mnemonic = dasmGetCondition(config, x, 0, 1);
}
break;
}
/* Single template for illegal opcodes */
if (def->format == FMT_ILLEGAL) {
def = (OpDef *) &ILLEGAL;
mnemonic = def->mnemonic;
}
/* Working variables */
line->text.operandsLength = def->operandsLength;
for (mnemonicLen = 0; mnemonic[mnemonicLen]; mnemonicLen++);
textLen = 9 /* Address */ + line->codeLength * 3 + mnemonicLen + 1;
/* Process all operands */
for (x = 0; x < def->operandsLength; x++) {
int len = 0; /* Length of operand string */
int type = def->operands[x];
char *op = operands[x];
/* Select operand type */
switch (def->operands[x]) {
case OP_COND:
len = dasmOpCondition(op, config, code);
break;
case OP_DISP9: /* Fallthrough */
case OP_DISP26:
len = dasmOpBranch (op, config, code, type, address);
break;
case OP_IMM5U: /* Fallthrough */
case OP_IMM5S: /* Fallthrough */
case OP_IMM16U: /* Fallthrough */
case OP_IMM16S:
len = dasmOpImmediate(op, config, code, type);
break;
case OP_JMP:
len = dasmOpJMP (op, config, code);
break;
case OP_MEM:
len = dasmOpMemory (op, config, code);
break;
case OP_REG1: /* Fallthrough */
case OP_REG2:
len = dasmOpProgram (op, config, code, type);
break;
case OP_SYS:
len = dasmOpSystem (op, config, code);
break;
}
/* Incorporate text length */
opLens[x] = len;
textLen += len + 1;
}
/* Output buffer needs to grow */
if (ret->length + textLen > ret->capacity) {
ret->capacity *= 2;
ret->buffer = VBU_MEMMGR(ret->buffer, ret->capacity);
}
/* Output address */
line->text.address = (char *) ret->length;
ret->length += dasmHex(&ret->buffer[ret->length], -1,
config->hexCase, line->address, 8);
ret->buffer[ret->length++] = 0;
/* Output code */
for (x = 0; x < line->codeLength; x++) {
line->text.code[x] = (char *) ret->length;
ret->length += dasmHex(&ret->buffer[ret->length], -1,
config->hexCase, line->code[x], 2);
ret->buffer[ret->length++] = 0;
}
for (; x < 4; x++)
line->text.code[x] = NULL;
/* Output mnemonic */
line->text.mnemonic = (char *) ret->length;
for (x = 0; x < mnemonicLen; x++) {
char c = mnemonic[x];
if (config->mnemonicCase == VBU_LOWER && c >= 'A' && c <= 'Z')
c += 'a' - 'A';
ret->buffer[ret->length++] = c;
}
ret->buffer[ret->length++] = 0;
/* Output operands */
for (x = 0; x < line->text.operandsLength; x++) {
char *op = operands[x];
int len = opLens[x];
int y;
line->text.operands[x] = (char *) ret->length;
for (y = 0; y < len; y++)
ret->buffer[ret->length++] = op[y];
ret->buffer[ret->length++] = 0;
}
for (; x < 3; x++)
line->text.operands[x] = NULL;
/* Adjust operand order */
if (config->operandOrder==VBU_DEST_FIRST && line->text.operandsLength>=2) {
int l = line->text.operandsLength - 1;
char *t = line->text.operands[0];
line->text.operands[0] = line->text.operands[l];
line->text.operands[l] = t;
}
return line->codeLength;
}
/***************************** Library Functions *****************************/
/* Disassemble instructions from a simulation */
static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address,
VBU_DasmConfig *config, unsigned length, int line) {
uint32_t cur; /* Current disassembly address */
uint32_t pc; /* Current simulation program counter */
uint32_t rel; /* Line relative to start to the first output line */
DasmOutput ret; /* Output buffer state */
uint32_t start; /* At least 10 lines before the first output line */
unsigned x, y; /* Iterators */
/* Working variables */
pc = vbGetProgramCounter(sim);
start = (address - (line + 10) * 4) & 0xFFFFFFFE;
/* Locate the instruction containing the first line of output */
for (cur = start, rel = 0;; rel++) {
uint32_t size = vbuCodeSize(sim, cur);
/* Cannot skip over PC or target address */
if (cur != pc && pc - cur < size)
size = pc - cur;
if (cur != address && address - cur < size)
size = address - cur;
/* This instruction contains the target address */
if (address - cur < size)
break;
/* Advance to the next instruction */
cur += size;
}
/* Seek to the first line of output */
for (cur = start; rel > 0; rel--) {
uint32_t size = vbuCodeSize(sim, cur);
/* Cannot skip over PC or target address */
if (cur != pc && pc - cur < size)
size = pc - cur;
if (cur != address && address - cur < size)
size = address - cur;
/* Advance to the next instruction */
cur += size;
}
/* Process all lines */
ret.length = length * sizeof (VBU_DasmLine);
ret.capacity = ret.length * 2;
ret.buffer = VBU_MEMMGR(NULL, ret.capacity);
for (x = 0; x < length; x++) {
uint32_t size;
/* Disassemble the instruction */
size = dasmLine(&ret, x, sim, cur, cur == pc, config);
/* Cannot skip over PC or target address */
if (cur != pc && pc - cur < size)
size = pc - cur;
if (cur != address && address - cur < size)
size = address - cur;
/* Advance to the next instruction */
cur += size;
}
/* Finalize the output buffer */
if (ret.capacity > ret.length)
ret.buffer = VBU_MEMMGR(ret.buffer, ret.length);
for (x = 0; x < length; x++) {
VBU_DasmLine *line = &((VBU_DasmLine *) ret.buffer)[x];
/* Convert text offsets into proper pointers */
line->text.address = &ret.buffer[(size_t) line->text.address ];
line->text.mnemonic = &ret.buffer[(size_t) line->text.mnemonic];
for (y = 0; y < line->codeLength; y++)
line->text.code[y] = &ret.buffer[(size_t) line->text.code[y]];
for (y = 0; y < line->text.operandsLength; y++) {
line->text.operands[y] =
&ret.buffer[(size_t) line->text.operands[y]];
}
}
return (VBU_DasmLine *) ret.buffer;
}
#endif /* VBUAPI */
+297
View File
@@ -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_MEMMGR(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 */
+96
View File
@@ -0,0 +1,96 @@
#ifndef VBUAPI
#define VBUAPI
#endif
#include <stdio.h>
#include <string.h>
#include <vbu.h>
/***************************** Library Functions *****************************/
/* Memory manager */
#ifdef VBU_MEMMGR
extern void* VBU_MEMMGR(void *ptr, size_t size);
#else
#include <stdlib.h>
static void* VBU_MEMMGR(void *ptr, size_t size) {
/* Free a buffer */
if (size == 0) {
if (ptr != NULL)
free(ptr);
return NULL;
}
/* Create or resize a buffer */
return realloc(ptr, size);
}
#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* vbuDasmDefaults(VBU_DasmConfig *config) {
/* Error checking */
if (config == NULL)
return NULL;
/* Supply default configuration */
config->bcondNotation = VBU_JOINED;
config->branchNotation = VBU_ABSOLUTE;
config->conditionCase = VBU_LOWER;
config->conditionCL = VBU_L;
config->conditionEZ = VBU_Z;
config->conditionNotation = VBU_NAMES;
config->hexCase = VBU_UPPER;
config->hexNotation = VBU_PREFIX_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) {
VBU_DasmConfig defs; /* Default settings */
/* Error checking */
if (sim == NULL || length == 0)
return NULL;
/* Supply default configuration */
if (config == NULL)
config = vbuDasmDefaults(&defs);
/* Disassemble instructions */
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);
}
+102
View File
@@ -0,0 +1,102 @@
#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_ABSOLUTE 0
#define VBU_C 1
#define VBU_DEST_FIRST 1
#define VBU_DEST_LAST 0
#define VBU_E 0
#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_NUMBERS 0
#define VBU_OUTSIDE 0
#define VBU_PREFIX_0X 0
#define VBU_PREFIX_DOLLAR 1
#define VBU_PREFIX_NUMBER 1
#define VBU_RELATIVE 1
#define VBU_SPLIT 1
#define VBU_SUFFIX_H 2
#define VBU_UPPER 0
#define VBU_Z 1
/*********************************** Types ***********************************/
/* Disassembler text options */
typedef struct { /* Defaults listed first */
uint8_t bcondNotation; /* JOINED, SPLIT */
uint8_t branchNotation; /* ABSOLUTE, RELATIVE */
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; /* PREFIX_0X, SUFFIX_H, PREFIX_DOLLAR */
uint8_t immediateNotation; /* NONE, SUFFIX_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 */
uint8_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* vbuDasmDefaults(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_ */
+99
View File
@@ -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);
+88
View File
@@ -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
View File
@@ -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;
}
}();
+976
View File
@@ -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
View File
@@ -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;
}