Web re-rewrite
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"use strict";
|
||||
|
||||
// Dedicated audio output thread
|
||||
class AudioThread extends AudioWorkletProcessor {
|
||||
|
||||
///////////////////////// Initialization Methods //////////////////////////
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Configure instance fields
|
||||
this.buffers = []; // Input sample buffer queue
|
||||
this.offset = 0; // Offset into oldest buffer
|
||||
|
||||
// Wait for initializer message from parent thread
|
||||
this.port.onmessage = m=>this.init(m.data);
|
||||
}
|
||||
|
||||
async init(main) {
|
||||
|
||||
// Configure message ports
|
||||
this.core = this.port;
|
||||
this.core.onmessage = m=>this.onCore(m.data);
|
||||
this.main = main;
|
||||
this.main.onmessage = m=>this.onMain(m.data);
|
||||
|
||||
// Notify main thread
|
||||
this.port.postMessage(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Public Methods //////////////////////////////
|
||||
|
||||
// Produce output samples (called by the user agent)
|
||||
process(inputs, outputs, parameters) {
|
||||
let output = outputs[0];
|
||||
let length = output [0].length;
|
||||
let empty = null;
|
||||
|
||||
// Process all samples
|
||||
for (let x = 0; x < length;) {
|
||||
|
||||
// No bufferfed samples are available
|
||||
if (this.buffers.length == 0) {
|
||||
for (; x < length; x++)
|
||||
output[0] = output[1] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Transfer samples from the oldest buffer
|
||||
let y, buffer = this.buffers[0];
|
||||
for (y = this.offset; x < length && y < buffer.length; x++, y+=2) {
|
||||
output[0][x] = buffer[y ];
|
||||
output[1][x] = buffer[y + 1];
|
||||
}
|
||||
|
||||
// Advance to the next buffer
|
||||
if ((this.offset = y) == buffer.length) {
|
||||
if (empty == null)
|
||||
empty = [];
|
||||
empty.push(this.buffers.shift());
|
||||
this.offset = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Return emptied sample buffers to the core thread
|
||||
if (empty != null)
|
||||
this.core.postMessage(empty, empty.map(e=>e.buffer));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Message Methods /////////////////////////////
|
||||
|
||||
// Message received from core thread
|
||||
onCore(msg) {
|
||||
}
|
||||
|
||||
// Message received from main thread
|
||||
onMain(msg) {
|
||||
}
|
||||
|
||||
}
|
||||
registerProcessor("AudioThread", AudioThread);
|
||||
@@ -0,0 +1,293 @@
|
||||
// Interface between application and WebAssembly worker thread
|
||||
class Core {
|
||||
|
||||
///////////////////////// Initialization Methods //////////////////////////
|
||||
|
||||
constructor() {
|
||||
|
||||
// Configure instance fields
|
||||
this.promises = [];
|
||||
|
||||
}
|
||||
|
||||
async init(coreUrl, wasmUrl, audioUrl) {
|
||||
|
||||
// Open audio output stream
|
||||
this.audio = new AudioContext({
|
||||
latencyHint: "interactive",
|
||||
sampleRate : 41700
|
||||
});
|
||||
await this.audio.suspend();
|
||||
|
||||
// Launch the audio thread
|
||||
await this.audio.audioWorklet.addModule(
|
||||
Core.url(audioUrl, "AudioThread.js", /***/"./AudioThread.js"));
|
||||
let node = new AudioWorkletNode(this.audio, "AudioThread", {
|
||||
numberOfInputs : 0,
|
||||
numberOfOutputs : 1,
|
||||
outputChannelCount: [2]
|
||||
});
|
||||
node.connect(this.audio.destination);
|
||||
|
||||
// Attach a second MessagePort to the audio thread
|
||||
let channel = new MessageChannel();
|
||||
this.audio.port = channel.port1;
|
||||
await new Promise(resolve=>{
|
||||
node.port.onmessage = resolve;
|
||||
node.port.postMessage(channel.port2, [channel.port2]);
|
||||
});
|
||||
this.audio.port.onmessage = m=>this.onAudio(m.data);
|
||||
|
||||
// Launch the core thread
|
||||
this.core = new Worker(
|
||||
Core.url(wasmUrl, "CoreThread.js", /***/"./CoreThread.js"));
|
||||
await new Promise(resolve=>{
|
||||
this.core.onmessage = resolve;
|
||||
this.core.postMessage({
|
||||
audio : node.port,
|
||||
wasmUrl: Core.url(wasmUrl, "core.wasm", /***/"./core.wasm")
|
||||
}, [node.port]);
|
||||
});
|
||||
this.core.onmessage = m=>this.onCore(m.data);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Static Methods //////////////////////////////
|
||||
|
||||
// Select a URL in the same path as the current script
|
||||
static url(arg, name, bundled) {
|
||||
|
||||
// The input argument was provided
|
||||
if (arg)
|
||||
return arg;
|
||||
|
||||
// Running from a bundle distribution
|
||||
if (bundled.startsWith("blob:") || bundled.startsWith("data:"))
|
||||
return bundled;
|
||||
|
||||
// Compute the URL for the given filename
|
||||
let url = new URL(import.meta.url).pathname;
|
||||
return url.substring(0, url.lastIndexOf("/") + 1) + name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Event Handlers //////////////////////////////
|
||||
|
||||
// Message received from audio thread
|
||||
onAudio(msg) {
|
||||
}
|
||||
|
||||
// Message received from core thread
|
||||
onCore(msg) {
|
||||
|
||||
// Process subscriptions
|
||||
if (msg.subscriptions && this.onsubscription instanceof Function) {
|
||||
for (let sub of msg.subscriptions) {
|
||||
let key = sub.subscription;
|
||||
delete sub.subscription;
|
||||
this.onsubscription(key, sub, this);
|
||||
}
|
||||
delete msg.subscriptions;
|
||||
}
|
||||
|
||||
// The main thread is waiting on a reply
|
||||
if (msg.isReply) {
|
||||
delete msg.isReply;
|
||||
|
||||
// For "create", produce sim objects
|
||||
if (msg.isCreate) {
|
||||
delete msg.isCreate;
|
||||
msg.sims = msg.sims.map(s=>({ pointer: s }));
|
||||
}
|
||||
|
||||
// Notify the caller
|
||||
this.promises.shift()(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Public Methods //////////////////////////////
|
||||
|
||||
// Create and initialize simulations
|
||||
create(count, options) {
|
||||
return this.message({
|
||||
command: "create",
|
||||
count : count
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Delete a simulation
|
||||
delete(sim, options) {
|
||||
return this.message({
|
||||
command: "delete",
|
||||
sim : sim.pointer
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Retrieve the value of all CPU registers
|
||||
getAllRegisters(sim, options) {
|
||||
return this.message({
|
||||
command: "getAllRegisters",
|
||||
sim : sim.pointer
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Retrieve the value of PC
|
||||
getProgramCounter(sim, options) {
|
||||
return this.message({
|
||||
command: "getProgramCounter",
|
||||
sim : sim.pointer
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Retrieve the value of a system register
|
||||
getSystemRegister(sim, id, options) {
|
||||
return this.message({
|
||||
command: "getSystemRegister",
|
||||
id : id,
|
||||
sim : sim.pointer
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Read multiple bytes from memory
|
||||
read(sim, address, length, options) {
|
||||
return this.message({
|
||||
command: "read",
|
||||
address: address,
|
||||
length : length,
|
||||
sim : sim.pointer
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Refresh subscriptions
|
||||
refresh(subscriptions = null, options) {
|
||||
return this.message({
|
||||
command : "refresh",
|
||||
subscriptions: subscriptions
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Simulate a hardware reset
|
||||
reset(sim, options) {
|
||||
return this.message({
|
||||
command: "reset",
|
||||
sim : sim.pointer
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Execute until the next current instruction
|
||||
runNext(sims, options) {
|
||||
return this.message({
|
||||
command: "runNext",
|
||||
sims : Array.isArray(sims) ?
|
||||
sims.map(s=>s.pointer) : [ sims.pointer ]
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Specify a value for the program counter
|
||||
setProgramCounter(sim, value, options) {
|
||||
return this.message({
|
||||
command: "setProgramCounter",
|
||||
sim : sim.pointer,
|
||||
value : value
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Specify a value for a program register
|
||||
setProgramRegister(sim, index, value, options) {
|
||||
return this.message({
|
||||
command: "setProgramRegister",
|
||||
index : index,
|
||||
sim : sim.pointer,
|
||||
value : value
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Specify a cartridge ROM buffer
|
||||
setROM(sim, data, options = {}) {
|
||||
data = data.slice();
|
||||
return this.message({
|
||||
command: "setROM",
|
||||
data : data,
|
||||
reset : !("reset" in options) || !!options.reset,
|
||||
sim : sim.pointer
|
||||
}, [data.buffer], options);
|
||||
}
|
||||
|
||||
// Specify a value for a system register
|
||||
setSystemRegister(sim, id, value, options) {
|
||||
return this.message({
|
||||
command: "setSystemRegister",
|
||||
id : id,
|
||||
sim : sim.pointer,
|
||||
value : value
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Execute the current instruction
|
||||
singleStep(sims, options) {
|
||||
return this.message({
|
||||
command: "singleStep",
|
||||
sims : Array.isArray(sims) ?
|
||||
sims.map(s=>s.pointer) : [ sims.pointer ]
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Cancel a subscription
|
||||
unsubscribe(subscription, options) {
|
||||
return this.message({
|
||||
command : "unsubscribe",
|
||||
subscription: subscription
|
||||
}, [], options);
|
||||
}
|
||||
|
||||
// Write multiple bytes to memory
|
||||
write(sim, address, data, options) {
|
||||
data = data.slice();
|
||||
return this.message({
|
||||
address: address,
|
||||
command: "write",
|
||||
data : data,
|
||||
sim : sim.pointer
|
||||
}, [data.buffer], options);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Private Methods /////////////////////////////
|
||||
|
||||
// Send a message to the core thread
|
||||
message(msg, transfers, options = {}) {
|
||||
|
||||
// Configure options
|
||||
if (!(options instanceof Object))
|
||||
options = { reply: options };
|
||||
if (!("reply" in options) || options.reply)
|
||||
msg.reply = true;
|
||||
if ("refresh" in options)
|
||||
msg.refresh = options.refresh;
|
||||
if ("subscription" in options)
|
||||
msg.subscription = options.subscription;
|
||||
if ("tag" in options)
|
||||
msg.tag = options.tag;
|
||||
|
||||
// Send the command to the core thread
|
||||
return msg.reply ?
|
||||
new Promise(resolve=>{
|
||||
this.promises.push(resolve);
|
||||
this.core.postMessage(msg, transfers);
|
||||
}) :
|
||||
this.core.postMessage(msg, transfers);
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Core };
|
||||
@@ -0,0 +1,330 @@
|
||||
"use strict";
|
||||
|
||||
// Dedicated emulation thread
|
||||
class CoreThread {
|
||||
|
||||
///////////////////////// Initialization Methods //////////////////////////
|
||||
|
||||
constructor() {
|
||||
|
||||
// Configure instance fields
|
||||
this.subscriptions = new Map();
|
||||
|
||||
// Wait for initializer message from parent thread
|
||||
onmessage = m=>this.init(m.data.audio, m.data.wasmUrl);
|
||||
}
|
||||
|
||||
async init(audio, wasmUrl) {
|
||||
|
||||
// Configure message ports
|
||||
this.audio = audio;
|
||||
this.audio.onmessage = m=>this.onAudio (m.data);
|
||||
this.main = globalThis;
|
||||
this.main .onmessage = m=>this.onMessage(m.data);
|
||||
|
||||
// Load and instantiate the WebAssembly module
|
||||
this.wasm = (await WebAssembly.instantiateStreaming(
|
||||
fetch(wasmUrl), {
|
||||
env: { emscripten_notify_memory_growth: ()=>this.onGrowth() }
|
||||
})).instance;
|
||||
this.onGrowth();
|
||||
this.pointerSize = this.PointerSize();
|
||||
this.pointerType = this.pointerSize == 8 ? Uint64Array : Uint32Array;
|
||||
|
||||
// Notify main thread
|
||||
this.main.postMessage(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Event Handlers //////////////////////////////
|
||||
|
||||
// Message received from audio thread
|
||||
onAudio(frames) {
|
||||
|
||||
// Audio processing was suspended
|
||||
if (frames == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for more frames
|
||||
this.audio.postMessage(0);
|
||||
}
|
||||
|
||||
// Emscripten has grown the linear memory
|
||||
onGrowth() {
|
||||
Object.assign(this, this.wasm.exports);
|
||||
}
|
||||
|
||||
// Message received from main thread
|
||||
onMessage(msg) {
|
||||
|
||||
// Subscribe to the command
|
||||
if (msg.subscription && msg.command != "refresh")
|
||||
this.subscriptions.set(CoreThread.key(msg.subscription), msg);
|
||||
|
||||
// Process the command
|
||||
let rep = this[msg.command](msg);
|
||||
|
||||
// Do not send a reply
|
||||
if (!msg.reply)
|
||||
return;
|
||||
|
||||
// Configure the reply
|
||||
if (!rep)
|
||||
rep = {};
|
||||
if (msg.reply)
|
||||
rep.isReply = true;
|
||||
if ("tag" in msg)
|
||||
rep.tag = msg.tag;
|
||||
|
||||
// Send the reply to the main thread
|
||||
let transfers = rep.transfers;
|
||||
if (transfers)
|
||||
delete rep.transfers;
|
||||
this.main.postMessage(rep, transfers || []);
|
||||
|
||||
// Refresh subscriptions
|
||||
if (msg.refresh && msg.command != "refresh") {
|
||||
let subs = {};
|
||||
if (Array.isArray(msg.refresh))
|
||||
subs.subscriptions = msg.refresh;
|
||||
this.refresh(subs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////// Commands /////////////////////////////////
|
||||
|
||||
// Create and initialize a new simulation
|
||||
create(msg) {
|
||||
let sims = new Array(msg.count);
|
||||
for (let x = 0; x < msg.count; x++)
|
||||
sims[x] = this.Create();
|
||||
return {
|
||||
isCreate: true,
|
||||
sims : sims
|
||||
};
|
||||
}
|
||||
|
||||
// Delete all memory used by a simulation
|
||||
delete(msg) {
|
||||
this.Delete(msg.sim);
|
||||
}
|
||||
|
||||
// Retrieve the values of all CPU registers
|
||||
getAllRegisters(msg) {
|
||||
let program = new Int32Array (32);
|
||||
let system = new Uint32Array(32);
|
||||
for (let x = 0; x < 32; x++) {
|
||||
program[x] = this.vbGetProgramRegister(msg.sim, x);
|
||||
system [x] = this.vbGetSystemRegister (msg.sim, x);
|
||||
}
|
||||
return {
|
||||
pc : this.vbGetProgramCounter(msg.sim) >>> 0,
|
||||
program : program,
|
||||
system : system,
|
||||
transfers: [ program.buffer, system.buffer ]
|
||||
};
|
||||
}
|
||||
|
||||
// Retrieve the value of PC
|
||||
getProgramCounter(msg) {
|
||||
return { value: this.vbGetProgramCounter(msg.sim) >>> 0 };
|
||||
}
|
||||
|
||||
// Retrieve the value of a system register
|
||||
getSystemRegister(msg) {
|
||||
return { value: this.vbGetSystemRegister(msg.sim, msg.id) >>> 0 };
|
||||
}
|
||||
|
||||
// Read multiple bytes from memory
|
||||
read(msg) {
|
||||
let buffer = this.malloc(msg.length);
|
||||
this.vbReadEx(msg.sim, msg.address, buffer.pointer, msg.length);
|
||||
let data = buffer.slice();
|
||||
this.free(buffer);
|
||||
return {
|
||||
address : msg.address,
|
||||
data : data,
|
||||
transfers: [data.buffer]
|
||||
};
|
||||
}
|
||||
|
||||
// Process subscriptions
|
||||
refresh(msg) {
|
||||
let subscriptions = [];
|
||||
let transfers = [];
|
||||
|
||||
// Select the key set to refresh
|
||||
let keys = Array.isArray(msg.subscriptions) ?
|
||||
msg.subscriptions.map(s=>CoreThread.key(s)) :
|
||||
this.subscriptions.keys()
|
||||
;
|
||||
|
||||
// Process all subscriptions
|
||||
for (let key of keys) {
|
||||
|
||||
// Process the subscription
|
||||
let sub = this.subscriptions.get(key);
|
||||
let rep = this[sub.command](sub);
|
||||
|
||||
// There is no result
|
||||
if (!rep)
|
||||
continue;
|
||||
|
||||
// Add the result to the response
|
||||
rep.subscription = sub.subscription;
|
||||
if ("tag" in sub)
|
||||
rep.tag = sub.tag;
|
||||
subscriptions.push(rep);
|
||||
|
||||
// Add the transfers to the response
|
||||
if (!rep.transfers)
|
||||
continue;
|
||||
transfers = transfers.concat(rep.transfers);
|
||||
delete rep.transfers;
|
||||
}
|
||||
|
||||
// Do not send a reply
|
||||
if (subscriptions.length == 0 && !msg.reply)
|
||||
return;
|
||||
|
||||
// Send the response to the main thread
|
||||
this.main.postMessage({
|
||||
isReply : !!msg.reply,
|
||||
subscriptions: subscriptions.sort(CoreThread.REFRESH_ORDER)
|
||||
}, transfers);
|
||||
}
|
||||
|
||||
// Simulate a hardware reset
|
||||
reset(msg) {
|
||||
this.vbReset(msg.sim);
|
||||
}
|
||||
|
||||
// Execute until the next current instruction
|
||||
runNext(msg) {
|
||||
let sims = this.malloc(msg.sims.length, true);
|
||||
for (let x = 0; x < msg.sims.length; x++)
|
||||
sims[x] = msg.sims[x];
|
||||
this.RunNext(sims.pointer, msg.sims.length);
|
||||
this.free(sims);
|
||||
|
||||
let pcs = new Array(msg.sims.length);
|
||||
for (let x = 0; x < msg.sims.length; x++)
|
||||
pcs[x] = this.vbGetProgramCounter(msg.sims[x]) >>> 0;
|
||||
|
||||
return { pcs: pcs };
|
||||
}
|
||||
|
||||
// Specify a value for the program counter
|
||||
setProgramCounter(msg) {
|
||||
return { value: this.vbSetProgramCounter(msg.sim, msg.value) >>> 0 };
|
||||
}
|
||||
|
||||
// Specify a value for a program register
|
||||
setProgramRegister(msg) {
|
||||
return {value:this.vbSetProgramRegister(msg.sim,msg.index,msg.value)};
|
||||
}
|
||||
|
||||
// Specify a cartridge ROM buffer
|
||||
setROM(msg) {
|
||||
let prev = this.vbGetROM(msg.sim, 0);
|
||||
let success = true;
|
||||
|
||||
// Specify a new ROM
|
||||
if (msg.data != null) {
|
||||
let data = this.malloc(msg.data.length);
|
||||
for (let x = 0; x < data.length; x++)
|
||||
data[x] = msg.data[x];
|
||||
success = !this.vbSetROM(msg.sim, data.pointer, data.length);
|
||||
}
|
||||
|
||||
// Operation was successful
|
||||
if (success) {
|
||||
|
||||
// Delete the previous ROM
|
||||
this.Free(prev);
|
||||
|
||||
// Reset the simulation
|
||||
if (msg.reset)
|
||||
this.vbReset(msg.sim);
|
||||
}
|
||||
|
||||
return { success: success };
|
||||
}
|
||||
|
||||
// Specify a value for a system register
|
||||
setSystemRegister(msg) {
|
||||
return {value:this.vbSetSystemRegister(msg.sim,msg.id,msg.value)>>>0};
|
||||
}
|
||||
|
||||
// Execute the current instruction
|
||||
singleStep(msg) {
|
||||
let sims = this.malloc(msg.sims.length, true);
|
||||
for (let x = 0; x < msg.sims.length; x++)
|
||||
sims[x] = msg.sims[x];
|
||||
this.SingleStep(sims.pointer, msg.sims.length);
|
||||
this.free(sims);
|
||||
|
||||
let pcs = new Array(msg.sims.length);
|
||||
for (let x = 0; x < msg.sims.length; x++)
|
||||
pcs[x] = this.vbGetProgramCounter(msg.sims[x]) >>> 0;
|
||||
|
||||
return { pcs: pcs };
|
||||
}
|
||||
|
||||
// Delete a subscription
|
||||
unsubscribe(msg) {
|
||||
this.subscriptions.delete(CoreThread.key(msg.subscription));
|
||||
}
|
||||
|
||||
// Write multiple bytes to memory
|
||||
write(msg) {
|
||||
let data = this.malloc(msg.data.length);
|
||||
for (let x = 0; x < data.length; x++)
|
||||
data[x] = msg.data[x];
|
||||
this.vbWriteEx(msg.sim, msg.address, data.pointer, data.length);
|
||||
this.free(data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Private Methods /////////////////////////////
|
||||
|
||||
// Delete a byte array in WebAssembly memory
|
||||
free(buffer) {
|
||||
this.Free(buffer.pointer);
|
||||
}
|
||||
|
||||
// Format a subscription key as a string
|
||||
static key(subscription) {
|
||||
return subscription.map(k=>k.toString()).join("\n");
|
||||
}
|
||||
|
||||
// Allocate a byte array in WebAssembly memory
|
||||
malloc(length, pointers = false) {
|
||||
let size = pointers ? length * this.pointerSize : length;
|
||||
return this.map(this.Malloc(size), length, pointers);
|
||||
}
|
||||
|
||||
// Map a typed array into WebAssembly memory
|
||||
map(address, length, pointers = false) {
|
||||
let ret = new (pointers ? this.pointerType : Uint8Array)
|
||||
(this.memory.buffer, address, length);
|
||||
ret.pointer = address;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Comparator for subscriptions within the refresh command
|
||||
static REFRESH_ORDER(a, b) {
|
||||
a = a.subscription[0];
|
||||
b = b.subscription[0];
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new CoreThread();
|
||||
@@ -0,0 +1,542 @@
|
||||
// Machine code to human readable text converter
|
||||
class Disassembler {
|
||||
|
||||
//////////////////////////////// Constants ////////////////////////////////
|
||||
|
||||
// Default settings
|
||||
static DEFAULTS = {
|
||||
condCL : "L", // Use C/NC or L/NL for conditions
|
||||
condEZ : "E", // Use E/NE or Z/NZ for conditions
|
||||
condNames : true, // Use condition names
|
||||
condUppercase: false, // Condition names uppercase
|
||||
hexPrefix : "0x", // Hexadecimal prefix
|
||||
hexSuffix : "", // Hexadecimal suffix
|
||||
hexUppercase : true, // Hexadecimal uppercase
|
||||
instUppercase: true, // Mnemonics uppercase
|
||||
jumpAddress : true, // Jump/branch shows target address
|
||||
memInside : false, // Use [reg1 + disp] notation
|
||||
opDestFirst : false, // Destination operand first
|
||||
proNames : true, // Use program register names
|
||||
proUppercase : false, // Program register names uppercase
|
||||
splitBcond : false, // BCOND condition as an operand
|
||||
splitSetf : true, // SETF condition as an operand
|
||||
sysNames : true, // Use system register names
|
||||
sysUppercase : false // System register names uppercase
|
||||
};
|
||||
|
||||
|
||||
|
||||
/////////////////////////// Disassembly Lookup ////////////////////////////
|
||||
|
||||
// Opcode descriptors
|
||||
static OPDEFS = [
|
||||
[ "MOV" , [ "opReg1" , "opReg2" ] ], // 000000
|
||||
[ "ADD" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "SUB" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "CMP" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "SHL" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "SHR" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "JMP" , [ "opReg1Ind" ] ],
|
||||
[ "SAR" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "MUL" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "DIV" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "MULU" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "DIVU" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "OR" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "AND" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "XOR" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "NOT" , [ "opReg1" , "opReg2" ] ],
|
||||
[ "MOV" , [ "opImm5S", "opReg2" ] ], // 010000
|
||||
[ "ADD" , [ "opImm5S", "opReg2" ] ],
|
||||
null, // SETF: special
|
||||
[ "CMP" , [ "opImm5S", "opReg2" ] ],
|
||||
[ "SHL" , [ "opImm5U", "opReg2" ] ],
|
||||
[ "SHR" , [ "opImm5U", "opReg2" ] ],
|
||||
[ "CLI" , [ ] ],
|
||||
[ "SAR" , [ "opImm5U", "opReg2" ] ],
|
||||
[ "TRAP" , [ "opImm5U" ] ],
|
||||
[ "RETI" , [ ] ],
|
||||
[ "HALT" , [ ] ],
|
||||
null, // Invalid
|
||||
[ "LDSR" , [ "opReg2" , "opSys" ] ],
|
||||
[ "STSR" , [ "opSys" , "opReg2" ] ],
|
||||
[ "SEI" , [ ] ],
|
||||
null, // Bit string: special
|
||||
null, // BCOND: special // 100000
|
||||
null, // BCOND: special
|
||||
null, // BCOND: special
|
||||
null, // BCOND: special
|
||||
null, // BCOND: special
|
||||
null, // BCOND: special
|
||||
null, // BCOND: special
|
||||
null, // BCOND: special
|
||||
[ "MOVEA", [ "opImm16U" , "opReg1", "opReg2" ] ],
|
||||
[ "ADDI" , [ "opImm16S" , "opReg1", "opReg2" ] ],
|
||||
[ "JR" , [ "opDisp26" ] ],
|
||||
[ "JAL" , [ "opDisp26" ] ],
|
||||
[ "ORI" , [ "opImm16U" , "opReg1", "opReg2" ] ],
|
||||
[ "ANDI" , [ "opImm16U" , "opReg1", "opReg2" ] ],
|
||||
[ "XORI" , [ "opImm16U" , "opReg1", "opReg2" ] ],
|
||||
[ "MOVHI", [ "opImm16U" , "opReg1", "opReg2" ] ],
|
||||
[ "LD.B" , [ "opReg1Disp", "opReg2" ] ], // 110000
|
||||
[ "LD.H" , [ "opReg1Disp", "opReg2" ] ],
|
||||
null, // Invalid
|
||||
[ "LD.W" , [ "opReg1Disp", "opReg2" ] ],
|
||||
[ "ST.B" , [ "opReg2" , "opReg1Disp" ] ],
|
||||
[ "ST.H" , [ "opReg2" , "opReg1Disp" ] ],
|
||||
null, // Invalid
|
||||
[ "ST.W" , [ "opReg2" , "opReg1Disp" ] ],
|
||||
[ "IN.B" , [ "opReg1Disp", "opReg2" ] ],
|
||||
[ "IN.H" , [ "opReg1Disp", "opReg2" ] ],
|
||||
[ "CAXI" , [ "opReg1Disp", "opReg2" ] ],
|
||||
[ "IN.W" , [ "opReg1Disp", "opReg2" ] ],
|
||||
[ "OUT.B", [ "opReg2" , "opReg1Disp" ] ],
|
||||
[ "OUT.H", [ "opReg2" , "opReg1Disp" ] ],
|
||||
null, // Floating-point/Nintendo: special
|
||||
[ "OUT.W", [ "opReg2" , "opReg1Disp" ] ]
|
||||
];
|
||||
|
||||
// Bit string sub-opcode descriptors
|
||||
static BITSTRING = [
|
||||
"SCH0BSU", "SCH0BSD", "SCH1BSU", "SCH1BSD",
|
||||
null , null , null , null ,
|
||||
"ORBSU" , "ANDBSU" , "XORBSU" , "MOVBSU" ,
|
||||
"ORNBSU" , "ANDNBSU", "XORNBSU", "NOTBSU" ,
|
||||
null , null , null , null ,
|
||||
null , null , null , null ,
|
||||
null , null , null , null ,
|
||||
null , null , null , null
|
||||
];
|
||||
|
||||
// Floating-point/Nintendo sub-opcode descriptors
|
||||
static FLOATENDO = [
|
||||
[ "CMPF.S" , [ "opReg1", "opReg2" ] ],
|
||||
null, // Invalid
|
||||
[ "CVT.WS" , [ "opReg1", "opReg2" ] ],
|
||||
[ "CVT.SW" , [ "opReg1", "opReg2" ] ],
|
||||
[ "ADDF.S" , [ "opReg1", "opReg2" ] ],
|
||||
[ "SUBF.S" , [ "opReg1", "opReg2" ] ],
|
||||
[ "MULF.S" , [ "opReg1", "opReg2" ] ],
|
||||
[ "DIVF.S" , [ "opReg1", "opReg2" ] ],
|
||||
[ "XB" , [ "opReg2" ] ],
|
||||
[ "XH" , [ "opReg2" ] ],
|
||||
[ "REV" , [ "opReg1", "opReg2" ] ],
|
||||
[ "TRNC.SW", [ "opReg1", "opReg2" ] ],
|
||||
[ "MPYHW" , [ "opReg1", "opReg2" ] ],
|
||||
null, null, null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null
|
||||
];
|
||||
|
||||
// Condition mnemonics
|
||||
static CONDITIONS = [
|
||||
"V" , "C" , "E" , "NH", "N", "T", "LT", "LE",
|
||||
"NV", "NC", "NE", "H" , "P", "F", "GE", "GT"
|
||||
];
|
||||
|
||||
// Program register names
|
||||
static PRONAMES = [
|
||||
"r0" , "r1" , "hp" , "sp" , "gp" , "tp" , "r6" , "r7" ,
|
||||
"r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15",
|
||||
"r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
|
||||
"r24", "r25", "r26", "r27", "r28", "r29", "r30", "lp"
|
||||
];
|
||||
|
||||
// System register names
|
||||
static SYSNAMES = [
|
||||
"EIPC", "EIPSW", "FEPC", "FEPSW", "ECR", "PSW", "PIR", "TKCW",
|
||||
"8" , "9" , "10" , "11" , "12" , "13" , "14" , "15" ,
|
||||
"16" , "17" , "18" , "19" , "20" , "21" , "22" , "23" ,
|
||||
"CHCW", "ADTRE", "26" , "27" , "28" , "29" , "30" , "31"
|
||||
];
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Static Methods //////////////////////////////
|
||||
|
||||
// Determine the bounds of a data buffer to represent all lines of output
|
||||
static dataBounds(address, line, length) {
|
||||
let before = 10; // Number of lines before the first line of output
|
||||
let max = 4; // Maximum number of bytes that can appear on a line
|
||||
|
||||
// The reference line is before the preferred earliest line
|
||||
if (line < -before) {
|
||||
length = (length - line) * max;
|
||||
}
|
||||
|
||||
// The reference line is before the first line
|
||||
else if (line < 0) {
|
||||
address -= (line + before) * max;
|
||||
length = (length + before) * max;
|
||||
}
|
||||
|
||||
// The reference line is at or after the first line
|
||||
else {
|
||||
address -= (line + before) * max;
|
||||
length = (Math.max(length, line) + before) * max;
|
||||
}
|
||||
|
||||
return {
|
||||
address: (address & ~1) >>> 0,
|
||||
length : length
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////// Initialization Methods //////////////////////////
|
||||
|
||||
constructor() {
|
||||
Object.assign(this, Disassembler.DEFAULTS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Public Methods //////////////////////////////
|
||||
|
||||
// Disassemble a region of memory
|
||||
disassemble(data, dataAddress, refAddress, refLine, length, pc = null) {
|
||||
let pcOffset = pc === null ? -1 : pc - dataAddress >>> 0;
|
||||
|
||||
// Locate the offset of the first line of output in the buffer
|
||||
let offset = 0;
|
||||
for (let
|
||||
addr = dataAddress,
|
||||
circle = refLine > 0 ? new Array(refLine) : null,
|
||||
index = 0,
|
||||
more = [],
|
||||
remain = null
|
||||
;;) {
|
||||
|
||||
// Determine the size of the current line
|
||||
if (more.length == 0)
|
||||
this.more(more, data, offset);
|
||||
let size = more.shift();
|
||||
|
||||
// The current line contains the reference address
|
||||
if (refAddress - addr >>> 0 < size) {
|
||||
|
||||
// The next item in the buffer is the first line of output
|
||||
if (refLine > 0) {
|
||||
offset = circle[index];
|
||||
break;
|
||||
}
|
||||
|
||||
// This line is the first line of output
|
||||
if (refLine == 0)
|
||||
break;
|
||||
|
||||
// Count more lines for the first line of output
|
||||
remain = refLine;
|
||||
}
|
||||
|
||||
// Record the offset of the current instruction
|
||||
if (refLine > 0) {
|
||||
circle[index] = offset;
|
||||
index = (index + 1) % circle.length;
|
||||
}
|
||||
|
||||
// Advance to the next line
|
||||
let sizeToPC = pcOffset - offset >>> 0;
|
||||
if (offset != pcOffset && sizeToPC < size) {
|
||||
size = sizeToPC;
|
||||
more.splice();
|
||||
}
|
||||
addr = addr + size >>> 0;
|
||||
offset += size;
|
||||
if (remain !== null && ++remain == 0)
|
||||
break; // The next line is the first line of output
|
||||
}
|
||||
|
||||
// Process all lines of output
|
||||
let lines = new Array(length);
|
||||
for (let
|
||||
addr = dataAddress + offset,
|
||||
more = [],
|
||||
x = 0;
|
||||
x < length; x++
|
||||
) {
|
||||
|
||||
// Determine the size of the current line
|
||||
if (more.length == 0)
|
||||
this.more(more, data, offset, pcOffset);
|
||||
let size = more.shift();
|
||||
|
||||
// Add the line to the response
|
||||
lines[x] = this.format({
|
||||
rawAddress: addr,
|
||||
rawBytes : data.slice(offset, offset + size)
|
||||
});
|
||||
|
||||
// Advance to the next line
|
||||
let sizeToPC = pcOffset - offset >>> 0;
|
||||
if (offset != pcOffset && sizeToPC < size) {
|
||||
size = sizeToPC;
|
||||
more.splice();
|
||||
}
|
||||
addr = addr + size >>> 0;
|
||||
offset += size;
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////// Formatting Methods ////////////////////////////
|
||||
|
||||
// Format a line as human-readable text
|
||||
format(line) {
|
||||
let canReverse = true;
|
||||
let opcode = line.rawBytes[1] >>> 2;
|
||||
let opdef;
|
||||
let code = [
|
||||
line.rawBytes[1] << 8 | line.rawBytes[0],
|
||||
line.rawBytes.length == 2 ? null :
|
||||
line.rawBytes[3] << 8 | line.rawBytes[2]
|
||||
];
|
||||
|
||||
// BCOND
|
||||
if ((opcode & 0b111000) == 0b100000) {
|
||||
let cond = code[0] >>> 9 & 15;
|
||||
opdef =
|
||||
cond == 13 ? [ "NOP", [ ] ] :
|
||||
this.splitBcond ? [ "BCOND", [ "opBCond", "opDisp9" ] ] :
|
||||
[
|
||||
cond == 5 ? "BR" : "B" + this.condition(cond, true),
|
||||
[ "opDisp9" ]
|
||||
]
|
||||
;
|
||||
canReverse = false;
|
||||
}
|
||||
|
||||
// Processing by opcode
|
||||
else switch (opcode) {
|
||||
|
||||
// SETF
|
||||
case 0b010010:
|
||||
opdef = !this.splitSetf ?
|
||||
[
|
||||
"SETF" + Disassembler.CONDITIONS[code[0] & 15],
|
||||
[ "opReg2" ]
|
||||
] :
|
||||
[ "SETF", [ "opCond", "opReg2" ] ]
|
||||
;
|
||||
break;
|
||||
|
||||
// Bit string
|
||||
case 0b011111:
|
||||
opdef = Disassembler.BITSTRING[code[0] & 31];
|
||||
if (opdef != null)
|
||||
opdef = [ opdef, [] ];
|
||||
break;
|
||||
|
||||
// Floating-point/Nintendo
|
||||
case 0b111110:
|
||||
opdef = Disassembler.FLOATENDO[code[1] >>> 10];
|
||||
break;
|
||||
|
||||
// All others
|
||||
default: opdef = Disassembler.OPDEFS[opcode];
|
||||
}
|
||||
|
||||
// The opcode is undefined
|
||||
if (opdef == null)
|
||||
opdef = [ "---", [] ];
|
||||
|
||||
// Format the line's display text
|
||||
line.address = this.hex(line.rawAddress, 8, false);
|
||||
line.bytes = new Array(line.rawBytes.length);
|
||||
line.mnemonic = this.instUppercase ? opdef[0] : opdef[0].toLowerCase();
|
||||
line.operands = new Array(opdef[1].length);
|
||||
for (let x = 0; x < line.bytes.length; x++)
|
||||
line.bytes[x] = this.hex(line.rawBytes[x], 2, false);
|
||||
for (let x = 0; x < line.operands.length; x++)
|
||||
line.operands[x] = this[opdef[1][x]](line, code);
|
||||
if (this.opDestFirst && canReverse)
|
||||
line.operands.reverse();
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
// Format a condition operand in a BCOND instruction
|
||||
opBCond(line, code) {
|
||||
return this.condition(code[0] >>> 9 & 15);
|
||||
}
|
||||
|
||||
// Format a condition operand in a SETF instruction
|
||||
opCond(line, code) {
|
||||
return this.condition(code[0] & 15);
|
||||
}
|
||||
|
||||
// Format a 9-bit displacement operand
|
||||
opDisp9(line, code) {
|
||||
let disp = code[0] << 23 >> 23;
|
||||
return this.jump(line.rawAddress, disp);
|
||||
}
|
||||
|
||||
// Format a 26-bit displacement operand
|
||||
opDisp26(line, code) {
|
||||
let disp = (code[0] << 16 | code[1]) << 6 >> 6;
|
||||
return this.jump(line.rawAddress, disp);
|
||||
}
|
||||
|
||||
// Format a 5-bit signed immediate operand
|
||||
opImm5S(line, code) {
|
||||
return (code[0] & 31) << 27 >> 27;
|
||||
}
|
||||
|
||||
// Format a 5-bit unsigned immediate operand
|
||||
opImm5U(line, code) {
|
||||
return code[0] & 31;
|
||||
}
|
||||
|
||||
// Format a 16-bit signed immediate operand
|
||||
opImm16S(line, code) {
|
||||
let ret = code[1] << 16 >> 16;
|
||||
return (
|
||||
ret < -256 ? "-" + this.hex(-ret) :
|
||||
ret > 256 ? this.hex( ret) :
|
||||
ret
|
||||
);
|
||||
}
|
||||
|
||||
// Format a 16-bit unsigned immediate operand
|
||||
opImm16U(line, code) {
|
||||
return this.hex(code[1], 4);
|
||||
}
|
||||
|
||||
// Format a Reg1 operand
|
||||
opReg1(line, code) {
|
||||
return this.programRegister(code[0] & 31);
|
||||
}
|
||||
|
||||
// Format a disp[reg1] operand
|
||||
opReg1Disp(line, code) {
|
||||
let disp = code[1] << 16 >> 16;
|
||||
let reg1 = this.programRegister(code[0] & 31);
|
||||
|
||||
// Do not print the displacement
|
||||
if (disp == 0)
|
||||
return "[" + reg1 + "]";
|
||||
|
||||
// Format the displacement amount
|
||||
disp =
|
||||
disp < -256 ? "-" + this.hex(-disp) :
|
||||
disp > 256 ? this.hex( disp) :
|
||||
disp.toString()
|
||||
;
|
||||
|
||||
// [reg1 + disp] notation
|
||||
if (this.memInside) {
|
||||
return "[" + reg1 + (disp.startsWith("-") ?
|
||||
" - " + disp.substring(1) :
|
||||
" + " + disp
|
||||
) + "]";
|
||||
}
|
||||
|
||||
// disp[reg1] notation
|
||||
return disp + "[" + reg1 + "]";
|
||||
}
|
||||
|
||||
// Format a [Reg1] operand
|
||||
opReg1Ind(line, code) {
|
||||
return "[" + this.programRegister(code[0] & 31) + "]";
|
||||
}
|
||||
|
||||
// Format a Reg2 operand
|
||||
opReg2(line, code) {
|
||||
return this.programRegister(code[0] >> 5 & 31);
|
||||
}
|
||||
|
||||
// Format a system register operand
|
||||
opSys(line, code) {
|
||||
return this.systemRegister(code[0] & 31);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Private Methods /////////////////////////////
|
||||
|
||||
// Select the mnemonic for a condition
|
||||
condition(index, forceUppercase = false) {
|
||||
if (!this.condNames)
|
||||
return index.toString();
|
||||
let ret =
|
||||
index == 1 ? this.condCL :
|
||||
index == 2 ? this.condEZ :
|
||||
index == 9 ? "N" + this.condCL :
|
||||
index == 10 ? "N" + this.condEZ :
|
||||
Disassembler.CONDITIONS[index]
|
||||
;
|
||||
if (!forceUppercase && !this.condUppercase)
|
||||
ret = ret.toLowerCase();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Format a number as a hexadecimal string
|
||||
hex(value, digits = null, decorated = true) {
|
||||
value = value.toString(16);
|
||||
if (this.hexUppercase)
|
||||
value = value.toUpperCase();
|
||||
if (digits != null)
|
||||
value = value.padStart(digits, "0");
|
||||
if (decorated) {
|
||||
value = this.hexPrefix + value + this.hexSuffix;
|
||||
if (this.hexPrefix == "" && "0123456789".indexOf(value[0]) == -1)
|
||||
value = "0" + value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Format a jump or branch destination
|
||||
jump(address, disp) {
|
||||
return (
|
||||
this.jumpAddress ?
|
||||
this.hex(address + disp >>> 0, 8, false) :
|
||||
disp < -256 ? "-" + this.hex(-disp) :
|
||||
disp > 256 ? "+" + this.hex( disp) :
|
||||
disp.toString()
|
||||
);
|
||||
}
|
||||
|
||||
// Determine the number of bytes in the next line(s) of disassembly
|
||||
more(more, data, offset) {
|
||||
|
||||
// Error checking
|
||||
if (offset + 1 >= data.length)
|
||||
throw new Error("Disassembly error: Unexpected EoF");
|
||||
|
||||
// Determine the instruction's size from its opcode
|
||||
let opcode = data[offset + 1] >>> 2;
|
||||
more.push(
|
||||
opcode < 0b101000 || // 16-bit instruction
|
||||
opcode == 0b110010 || // Illegal opcode
|
||||
opcode == 0b110110 // Illegal opcode
|
||||
? 2 : 4);
|
||||
}
|
||||
|
||||
// Format a program register
|
||||
programRegister(index) {
|
||||
let ret = this.proNames ? Disassembler.PRONAMES[index] : "r" + index;
|
||||
if (this.proUppercase)
|
||||
ret = ret.toUpperCase();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Format a system register
|
||||
systemRegister(index) {
|
||||
let ret = this.sysNames ?
|
||||
Disassembler.SYSNAMES[index] : index.toString();
|
||||
if (!this.sysUppercase && this.sysNames)
|
||||
ret = ret.toLowerCase();
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Disassembler };
|
||||
@@ -0,0 +1,78 @@
|
||||
#undef VBAPI
|
||||
#include <stdlib.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <vb.h>
|
||||
|
||||
|
||||
|
||||
/////////////////////////////// Module Commands ///////////////////////////////
|
||||
|
||||
// Create and initialize a new simulation
|
||||
EMSCRIPTEN_KEEPALIVE VB* Create() {
|
||||
VB *vb = malloc(sizeof (VB));
|
||||
vbInit(vb);
|
||||
return vb;
|
||||
}
|
||||
|
||||
// Delete all memory used by a simulation
|
||||
EMSCRIPTEN_KEEPALIVE void Delete(VB *vb) {
|
||||
free(vb->cart.ram);
|
||||
free(vb->cart.rom);
|
||||
free(vb);
|
||||
}
|
||||
|
||||
// Proxy for free()
|
||||
EMSCRIPTEN_KEEPALIVE void Free(void *ptr) {
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
// Proxy for malloc()
|
||||
EMSCRIPTEN_KEEPALIVE void* Malloc(int size) {
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
// Size in bytes of a pointer
|
||||
EMSCRIPTEN_KEEPALIVE int PointerSize() {
|
||||
return sizeof (void *);
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////// Debugger Commands //////////////////////////////
|
||||
|
||||
// Execute until the following instruction
|
||||
uint32_t RunNextAddress;
|
||||
static int RunNextFetch(VB *vb, int fetch, VBAccess *access) {
|
||||
return access->address == RunNextAddress;
|
||||
}
|
||||
static int RunNextExecute(VB *vb, VBInstruction *inst) {
|
||||
RunNextAddress = inst->address + inst->size;
|
||||
vbSetOnExecute(vb, NULL);
|
||||
vbSetOnFetch(vb, &RunNextFetch);
|
||||
return 0;
|
||||
}
|
||||
EMSCRIPTEN_KEEPALIVE void RunNext(VB **vbs, int count) {
|
||||
uint32_t clocks = 20000000; // 1s
|
||||
vbSetOnExecute(vbs[0], &RunNextExecute);
|
||||
vbEmulateEx (vbs, count, &clocks);
|
||||
vbSetOnExecute(vbs[0], NULL);
|
||||
vbSetOnFetch (vbs[0], NULL);
|
||||
}
|
||||
|
||||
// Execute the current instruction
|
||||
static int SingleStepBreak;
|
||||
static int SingleStepFetch(VB *vb, int fetch, VBAccess *access) {
|
||||
if (fetch != 0)
|
||||
return 0;
|
||||
if (SingleStepBreak == 1)
|
||||
return 1;
|
||||
SingleStepBreak = 1;
|
||||
return 0;
|
||||
}
|
||||
EMSCRIPTEN_KEEPALIVE void SingleStep(VB **vbs, int count) {
|
||||
uint32_t clocks = 20000000; // 1s
|
||||
SingleStepBreak = vbs[0]->cpu.stage == 0 ? 0 : 1;
|
||||
vbSetOnFetch(vbs[0], &SingleStepFetch);
|
||||
vbEmulateEx (vbs, count, &clocks);
|
||||
vbSetOnFetch(vbs[0], NULL);
|
||||
}
|
||||
Reference in New Issue
Block a user