Adding disassembler to CPU window
This commit is contained in:
parent
af5fc0c4fc
commit
85cc0f5754
|
@ -35,22 +35,22 @@
|
||||||
|
|
||||||
// Retrieve the values of all the CPU registers
|
// Retrieve the values of all the CPU registers
|
||||||
getRegisters(msg) {
|
getRegisters(msg) {
|
||||||
msg.pc = this.core.GetProgramCounter(msg.sim, 0);
|
msg.pc = this.core.GetProgramCounter(msg.sim, 0) >>> 0;
|
||||||
msg.pcFrom = this.core.GetProgramCounter(msg.sim, 1);
|
msg.pcFrom = this.core.GetProgramCounter(msg.sim, 1) >>> 0;
|
||||||
msg.pcTo = this.core.GetProgramCounter(msg.sim, 2);
|
msg.pcTo = this.core.GetProgramCounter(msg.sim, 2) >>> 0;
|
||||||
msg.adtre = this.core.GetSystemRegister(msg.sim, 25);
|
msg.adtre = this.core.GetSystemRegister(msg.sim, 25) >>> 0;
|
||||||
msg.chcw = this.core.GetSystemRegister(msg.sim, 24);
|
msg.chcw = this.core.GetSystemRegister(msg.sim, 24) >>> 0;
|
||||||
msg.ecr = this.core.GetSystemRegister(msg.sim, 4);
|
msg.ecr = this.core.GetSystemRegister(msg.sim, 4) >>> 0;
|
||||||
msg.eipc = this.core.GetSystemRegister(msg.sim, 0);
|
msg.eipc = this.core.GetSystemRegister(msg.sim, 0) >>> 0;
|
||||||
msg.eipsw = this.core.GetSystemRegister(msg.sim, 1);
|
msg.eipsw = this.core.GetSystemRegister(msg.sim, 1) >>> 0;
|
||||||
msg.fepc = this.core.GetSystemRegister(msg.sim, 2);
|
msg.fepc = this.core.GetSystemRegister(msg.sim, 2) >>> 0;
|
||||||
msg.fepsw = this.core.GetSystemRegister(msg.sim, 3);
|
msg.fepsw = this.core.GetSystemRegister(msg.sim, 3) >>> 0;
|
||||||
msg.pir = this.core.GetSystemRegister(msg.sim, 6);
|
msg.pir = this.core.GetSystemRegister(msg.sim, 6) >>> 0;
|
||||||
msg.psw = this.core.GetSystemRegister(msg.sim, 5);
|
msg.psw = this.core.GetSystemRegister(msg.sim, 5) >>> 0;
|
||||||
msg.tkcw = this.core.GetSystemRegister(msg.sim, 7);
|
msg.tkcw = this.core.GetSystemRegister(msg.sim, 7) >>> 0;
|
||||||
msg.sr29 = this.core.GetSystemRegister(msg.sim, 29);
|
msg.sr29 = this.core.GetSystemRegister(msg.sim, 29) >>> 0;
|
||||||
msg.sr30 = this.core.GetSystemRegister(msg.sim, 30);
|
msg.sr30 = this.core.GetSystemRegister(msg.sim, 30) >>> 0;
|
||||||
msg.sr31 = this.core.GetSystemRegister(msg.sim, 31);
|
msg.sr31 = this.core.GetSystemRegister(msg.sim, 31) >>> 0;
|
||||||
msg.program = new Array(32);
|
msg.program = new Array(32);
|
||||||
for (let x = 0; x <= 31; x++)
|
for (let x = 0; x <= 31; x++)
|
||||||
msg.program[x] = this.core.GetProgramRegister(msg.sim, x);
|
msg.program[x] = this.core.GetProgramRegister(msg.sim, x);
|
||||||
|
@ -79,6 +79,7 @@
|
||||||
msg.buffer = this.core.memory.buffer.slice(
|
msg.buffer = this.core.memory.buffer.slice(
|
||||||
buffer.pointer, buffer.pointer + msg.size);
|
buffer.pointer, buffer.pointer + msg.size);
|
||||||
this.free(buffer);
|
this.free(buffer);
|
||||||
|
msg.pc = this.core.GetProgramCounter(msg.sim) >>> 0;
|
||||||
postMessage(msg, msg.buffer);
|
postMessage(msg, msg.buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -316,6 +316,7 @@ let run = async function() {
|
||||||
await Bundle.run("app/toolkit/TextBox.js");
|
await Bundle.run("app/toolkit/TextBox.js");
|
||||||
await Bundle.run("app/toolkit/Window.js");
|
await Bundle.run("app/toolkit/Window.js");
|
||||||
await Bundle.run("app/windows/CPUWindow.js");
|
await Bundle.run("app/windows/CPUWindow.js");
|
||||||
|
await Bundle.run("app/windows/Disassembler.js");
|
||||||
await Bundle.run("app/windows/Register.js");
|
await Bundle.run("app/windows/Register.js");
|
||||||
await Bundle.run("app/windows/MemoryWindow.js");
|
await Bundle.run("app/windows/MemoryWindow.js");
|
||||||
await App.create();
|
await App.create();
|
||||||
|
|
|
@ -499,3 +499,11 @@ input[type="text"] {
|
||||||
[aria-disabled="true"] [name="check"]{
|
[aria-disabled="true"] [name="check"]{
|
||||||
cursor: not-allowed !important;
|
cursor: not-allowed !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="disassembler"] {
|
||||||
|
font-family: var(--font-hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="dialog"][window="cpu"] [name="disassembler"] [name="row"] {
|
||||||
|
column-gap: calc(var(--font-size) * 1.5);
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,11 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
|
|
||||||
// Object constructor
|
// Object constructor
|
||||||
constructor(application, options) {
|
constructor(application, options) {
|
||||||
super(application, options);
|
super(application, options ? {
|
||||||
|
height : options.height,
|
||||||
|
visible: true,
|
||||||
|
width : options.width
|
||||||
|
} : {});
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
// Configure instance fields
|
// Configure instance fields
|
||||||
|
@ -15,15 +19,17 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
this.dragCursor = { x: 0, y: 0 };
|
this.dragCursor = { x: 0, y: 0 };
|
||||||
this.dragEdge = null;
|
this.dragEdge = null;
|
||||||
this.dragPointer = null;
|
this.dragPointer = null;
|
||||||
this.initialCenter = "center" in options ? !!options.center : false;
|
|
||||||
this.lastFocus = this.element;
|
this.lastFocus = this.element;
|
||||||
this.shown = this.visible;
|
this.shown = "visible" in options ? !!options.visible : true;
|
||||||
|
this.visible = this.shown;
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
this.setLayout("grid", { columns: "auto" });
|
this.setLayout("grid", { columns: "auto" });
|
||||||
this.setRole("dialog");
|
this.setRole("dialog");
|
||||||
this.setLocation(0, 0);
|
this.setLocation(0, 0);
|
||||||
this.element.style.position = "absolute";
|
this.element.style.position = "absolute";
|
||||||
|
if (!this.shown)
|
||||||
|
this.element.style.visibility = "hidden";
|
||||||
this.element.setAttribute("aria-modal", "false");
|
this.element.setAttribute("aria-modal", "false");
|
||||||
this.element.setAttribute("focus" , "false");
|
this.element.setAttribute("focus" , "false");
|
||||||
this.element.setAttribute("tabindex" , "0" );
|
this.element.setAttribute("tabindex" , "0" );
|
||||||
|
@ -90,6 +96,13 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
this.closeListeners.push(listener);
|
this.closeListeners.push(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request focus on the appropriate element
|
||||||
|
focus() {
|
||||||
|
if (this.lastFocus != this)
|
||||||
|
this.lastFocus.focus();
|
||||||
|
else this.element.focus();
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve the window's title text
|
// Retrieve the window's title text
|
||||||
getTitle() {
|
getTitle() {
|
||||||
return this.title.getText();
|
return this.title.getText();
|
||||||
|
@ -107,10 +120,14 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
|
|
||||||
// Specify whether the component is visible
|
// Specify whether the component is visible
|
||||||
setVisible(visible, focus) {
|
setVisible(visible, focus) {
|
||||||
super.setVisible(visible);
|
this.visible = visible = !!visible;
|
||||||
|
if (!visible) {
|
||||||
|
this.element.style.visibility = "hidden";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.element.style.removeProperty("visibility");
|
||||||
if (this.client === undefined)
|
if (this.client === undefined)
|
||||||
return;
|
return;
|
||||||
if (visible)
|
|
||||||
this.contain();
|
this.contain();
|
||||||
if (focus)
|
if (focus)
|
||||||
this.focus();
|
this.focus();
|
||||||
|
@ -126,17 +143,6 @@ Toolkit.Window = class Window extends Toolkit.Panel {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////// Package Methods /////////////////////////////
|
|
||||||
|
|
||||||
// Request focus on the appropriate element
|
|
||||||
focus() {
|
|
||||||
if (this.lastFocus != this)
|
|
||||||
this.lastFocus.focus();
|
|
||||||
else this.element.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////// Private Methods /////////////////////////////
|
///////////////////////////// Private Methods /////////////////////////////
|
||||||
|
|
||||||
// Position the window in the center of the desktop
|
// Position the window in the center of the desktop
|
||||||
|
|
|
@ -36,20 +36,21 @@
|
||||||
|
|
||||||
// Configure instance fields
|
// Configure instance fields
|
||||||
this.address = 0xFFFFFFF0;
|
this.address = 0xFFFFFFF0;
|
||||||
|
this.columns = [ 0, 0, 0, 0 ];
|
||||||
this.debug = debug;
|
this.debug = debug;
|
||||||
|
this.pendingDasm = { mode: null };
|
||||||
|
this.pendingRegs = { mode: null };
|
||||||
|
this.rows = [];
|
||||||
|
|
||||||
// Configure properties
|
// Configure properties
|
||||||
this.setProperty("sim", "");
|
this.setProperty("sim", "");
|
||||||
|
|
||||||
// Configure elements
|
// Configure elements
|
||||||
|
|
||||||
this.initDisassembler();
|
this.initDisassembler();
|
||||||
this.initSystemRegisters();
|
this.initSystemRegisters();
|
||||||
this.initProgramRegisters();
|
this.initProgramRegisters();
|
||||||
this.initWindow();
|
this.initWindow();
|
||||||
|
|
||||||
// Layout components
|
|
||||||
|
|
||||||
// Disassembler on the left
|
// Disassembler on the left
|
||||||
this.mainWrap.add(this.dasmWrap);
|
this.mainWrap.add(this.dasmWrap);
|
||||||
|
|
||||||
|
@ -113,6 +114,11 @@
|
||||||
overflowY: "hidden"
|
overflowY: "hidden"
|
||||||
}));
|
}));
|
||||||
this.dasm.element.setAttribute("name", "disassembler");
|
this.dasm.element.setAttribute("name", "disassembler");
|
||||||
|
this.dasm.addResizeListener(b=>this.onresize(b));
|
||||||
|
this.dasm.element.addEventListener("keydown", e=>this.onkeydasm(e));
|
||||||
|
this.dasm.element.addEventListener("wheel" , e=>this.onwheel (e));
|
||||||
|
|
||||||
|
this.rows.push(this.dasm.add(new CPUWindow.Row(this.dasm)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize program registers pane
|
// Initialize program registers pane
|
||||||
|
@ -137,7 +143,6 @@
|
||||||
this.proRegs.registers = {};
|
this.proRegs.registers = {};
|
||||||
for (let x = 0; x <= 31; x++)
|
for (let x = 0; x <= 31; x++)
|
||||||
this.addRegister(false, x, CPUWindow.PROGRAM[x] || "r" + x);
|
this.addRegister(false, x, CPUWindow.PROGRAM[x] || "r" + x);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize system registers pane
|
// Initialize system registers pane
|
||||||
|
@ -189,7 +194,6 @@
|
||||||
this.client.setLayout("grid", {
|
this.client.setLayout("grid", {
|
||||||
columns: "auto"
|
columns: "auto"
|
||||||
});
|
});
|
||||||
this.client.addResizeListener(b=>this.onresize(b));
|
|
||||||
|
|
||||||
// Configure main wrapper
|
// Configure main wrapper
|
||||||
this.mainWrap = this.client.add(this.newPanel({
|
this.mainWrap = this.client.add(this.newPanel({
|
||||||
|
@ -203,32 +207,19 @@
|
||||||
|
|
||||||
///////////////////////////// Public Methods //////////////////////////////
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
// The window is being displayed for the first time
|
|
||||||
firstShow() {
|
|
||||||
super.firstShow();
|
|
||||||
this.center();
|
|
||||||
this.mainSplit.measure();
|
|
||||||
this.regsSplit.measure();
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////// Package Methods /////////////////////////////
|
|
||||||
|
|
||||||
// Update the display with current emulation data
|
// Update the display with current emulation data
|
||||||
refresh(clientHeight, lineHeight, registers) {
|
refresh() {
|
||||||
if (!this.isVisible())
|
this.refreshDasm();
|
||||||
return;
|
this.refreshRegs();
|
||||||
if (registers) {
|
|
||||||
this.getRegisters({ registers });
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
this.debug.core.postMessage({
|
|
||||||
command: "GetRegisters",
|
// Specify whether the component is visible
|
||||||
debug : "CPU",
|
setVisible(visible, focus) {
|
||||||
sim : this.debug.sim
|
let prev = this.visible
|
||||||
});
|
visible = !!visible;
|
||||||
|
super.setVisible(visible, focus);
|
||||||
|
if (visible && !prev)
|
||||||
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -239,6 +230,7 @@
|
||||||
message(msg) {
|
message(msg) {
|
||||||
switch (msg.command) {
|
switch (msg.command) {
|
||||||
case "GetRegisters": this.getRegisters(msg); break;
|
case "GetRegisters": this.getRegisters(msg); break;
|
||||||
|
case "ReadBuffer" : this.readBuffer (msg); break;
|
||||||
case "SetRegister" : this.setRegister (msg); break;
|
case "SetRegister" : this.setRegister (msg); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,6 +254,54 @@
|
||||||
this.sysRegs.registers[31 ].setValue(msg.sr31 );
|
this.sysRegs.registers[31 ].setValue(msg.sr31 );
|
||||||
for (let x = 0; x <= 31; x++)
|
for (let x = 0; x <= 31; x++)
|
||||||
this.proRegs.registers[x].setValue(msg.program[x]);
|
this.proRegs.registers[x].setValue(msg.program[x]);
|
||||||
|
|
||||||
|
// Check for pending display updates
|
||||||
|
let mode = this.pendingDasm.mode;
|
||||||
|
this.pendingRegs.mode = null;
|
||||||
|
switch (mode) {
|
||||||
|
case "first":
|
||||||
|
case null :
|
||||||
|
return;
|
||||||
|
case "refresh":
|
||||||
|
this.refreshRegs();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieved data for disassembly
|
||||||
|
readBuffer(msg) {
|
||||||
|
let lines = Math.min(msg.lines, this.rows.length);
|
||||||
|
|
||||||
|
// Disassemble the visible instructions
|
||||||
|
let dasm = Disassembler.disassemble(
|
||||||
|
new Uint8Array(msg.buffer), 0, msg.address, msg.target,
|
||||||
|
msg.pc, msg.line, lines);
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.address = dasm[0].address;
|
||||||
|
|
||||||
|
// Configure elements
|
||||||
|
for (let x = 0; x < lines; x++)
|
||||||
|
this.rows[x].update(dasm[x], this.columns);
|
||||||
|
for (let x = 0; x < lines; x++)
|
||||||
|
this.rows[x].setWidths(this.columns);
|
||||||
|
|
||||||
|
// Check for pending display updates
|
||||||
|
let address = this.pendingDasm.address === null ?
|
||||||
|
this.address : this.pendingDasm.address;
|
||||||
|
let line = this.pendingDasm.line === null ?
|
||||||
|
0 : this.pendingDasm.line ;
|
||||||
|
let mode = this.pendingDasm.mode;
|
||||||
|
this.pendingDasm.mode = null;
|
||||||
|
switch (mode) {
|
||||||
|
case "first":
|
||||||
|
case null :
|
||||||
|
return;
|
||||||
|
case "refresh":
|
||||||
|
case "scroll" :
|
||||||
|
case "seek" :
|
||||||
|
this.refreshDasm(address, line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modified a register value
|
// Modified a register value
|
||||||
|
@ -284,13 +324,254 @@
|
||||||
list.add(reg.expansion);
|
list.add(reg.expansion);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize event handler
|
// The window is being displayed for the first time
|
||||||
onresize(bounds) {
|
firstShow() {
|
||||||
if (!this.isVisible())
|
super.firstShow();
|
||||||
return;
|
this.center();
|
||||||
//this.regs.setHeight(bounds.height);
|
|
||||||
this.mainSplit.measure();
|
this.mainSplit.measure();
|
||||||
this.regsSplit.measure();
|
this.regsSplit.measure();
|
||||||
|
this.seek(this.address, Math.floor(this.lines(true) / 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the height in pixels of one row of output
|
||||||
|
lineHeight() {
|
||||||
|
return Math.max(10, this.rows[0].address.getBounds().height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the number of rows of output
|
||||||
|
lines(fullyVisible) {
|
||||||
|
let gridHeight = this.dasm.getBounds().height;
|
||||||
|
let lineHeight = this.lineHeight();
|
||||||
|
let ret = gridHeight / lineHeight;
|
||||||
|
ret = fullyVisible ? Math.floor(ret) : Math.ceil(ret);
|
||||||
|
return Math.max(1, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key down event handler
|
||||||
|
onkeydasm(e) {
|
||||||
|
|
||||||
|
// Control is pressed
|
||||||
|
if (e.ctrlKey) switch (e.key) {
|
||||||
|
case "g": case "G":
|
||||||
|
let addr = prompt(this.application.translate("{app.goto_}"));
|
||||||
|
if (addr === null)
|
||||||
|
break;
|
||||||
|
this.seek(
|
||||||
|
(parseInt(addr, 16) & 0xFFFFFFFE) >>> 0,
|
||||||
|
Math.floor(this.lines(true) / 3)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processing by key
|
||||||
|
else switch (e.key) {
|
||||||
|
case "ArrowDown": this.scroll( 1 ); break;
|
||||||
|
case "ArrowUp" : this.scroll(-1 ); break;
|
||||||
|
case "PageDown" : this.scroll( this.lines(true)); break;
|
||||||
|
case "PageUp" : this.scroll(-this.lines(true)); break;
|
||||||
|
default : return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure event
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize event handler
|
||||||
|
onresize(bounds) {
|
||||||
|
|
||||||
|
// Update Splitters
|
||||||
|
this.mainSplit.measure();
|
||||||
|
this.regsSplit.measure();
|
||||||
|
|
||||||
|
// Configure disassembler elements
|
||||||
|
let lines = this.lines(false);
|
||||||
|
for (let y = this.rows.length; y < lines; y++)
|
||||||
|
this.rows[y] = this.dasm.add(new CPUWindow.Row(this.dasm));
|
||||||
|
for (let y = lines; y < this.rows.length; y++)
|
||||||
|
this.dasm.remove(this.rows[y]);
|
||||||
|
if (this.rows.length > lines)
|
||||||
|
this.rows.splice(lines, this.rows.length - lines);
|
||||||
|
this.refreshDasm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse wheel event handler
|
||||||
|
onwheel(e) {
|
||||||
|
let sign = Math.sign(e.deltaY);
|
||||||
|
let mag = Math.abs (e.deltaY);
|
||||||
|
if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL)
|
||||||
|
mag = Math.max(1, Math.floor(mag / this.lineHeight()));
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Configure display
|
||||||
|
this.scroll(sign * mag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the disassembler with current emulation data
|
||||||
|
refreshDasm(address, line) {
|
||||||
|
|
||||||
|
// Do nothing while closed or already waiting to refresh
|
||||||
|
if (!this.isVisible() || this.pendingDasm.mode != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Working variables
|
||||||
|
address = address !== undefined ?
|
||||||
|
(address & 0xFFFFFFFE) >>> 0 : this.address;
|
||||||
|
line = line || 0;
|
||||||
|
let lines = this.lines(false);
|
||||||
|
let start = -10 - Math.max(0, line);
|
||||||
|
let end = lines - Math.min(0, line);
|
||||||
|
|
||||||
|
// Configure pending state
|
||||||
|
this.pendingDasm.mode = "first";
|
||||||
|
this.pendingDasm.address = null;
|
||||||
|
this.pendingDasm.line = null;
|
||||||
|
|
||||||
|
// Request bus data from the WebAssembly core
|
||||||
|
this.debug.core.postMessage({
|
||||||
|
command: "ReadBuffer",
|
||||||
|
sim : this.debug.sim,
|
||||||
|
debug : "CPU",
|
||||||
|
address: (address + start * 4 & 0xFFFFFFFE) >>> 0,
|
||||||
|
line : line,
|
||||||
|
lines : lines,
|
||||||
|
target : address,
|
||||||
|
size : (end - start + 1) * 4
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the register list with current emulation data
|
||||||
|
refreshRegs() {
|
||||||
|
|
||||||
|
// Schedule another refresh
|
||||||
|
if (this.pendingRegs.mode != null)
|
||||||
|
this.pendingRegs.mode = "refresh";
|
||||||
|
|
||||||
|
// Do nothing while closed or already waiting to refresh
|
||||||
|
if (!this.isVisible() || this.pendingRegs.mode != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Configure pending state
|
||||||
|
this.pendingRegs.mode = "first";
|
||||||
|
this.pendingRegs.address = null;
|
||||||
|
this.pendingRegs.line = null;
|
||||||
|
|
||||||
|
// Request bus data from the WebAssembly core
|
||||||
|
this.debug.core.postMessage({
|
||||||
|
command: "GetRegisters",
|
||||||
|
debug : "CPU",
|
||||||
|
sim : this.debug.sim
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to a new address relative to the current address
|
||||||
|
scroll(lines) {
|
||||||
|
switch (this.pendingDasm.mode) {
|
||||||
|
case "first" :
|
||||||
|
case "refresh":
|
||||||
|
this.pendingDasm.mode = "scroll";
|
||||||
|
this.pendingDasm.line = -lines;
|
||||||
|
break;
|
||||||
|
case "seek" :
|
||||||
|
case "scroll":
|
||||||
|
this.pendingDasm.mode = "scroll";
|
||||||
|
this.pendingDasm.line -= lines;
|
||||||
|
break;
|
||||||
|
case null:
|
||||||
|
this.refreshDasm(this.address, -lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to a new address positioned at a particular row of output
|
||||||
|
seek(address, line) {
|
||||||
|
switch (this.pendingDasm.mode) {
|
||||||
|
case "first" :
|
||||||
|
case "refresh":
|
||||||
|
this.pendingDasm.mode = "seek";
|
||||||
|
this.pendingDasm.address = address;
|
||||||
|
this.pendingDasm.line = line;
|
||||||
|
break;
|
||||||
|
case "seek" :
|
||||||
|
case "scroll":
|
||||||
|
this.pendingDasm.mode = "seek";
|
||||||
|
this.pendingDasm.address = address;
|
||||||
|
this.pendingDasm.line += line;
|
||||||
|
break;
|
||||||
|
case null:
|
||||||
|
this.refreshDasm(address, line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}).initializer();
|
}).initializer();
|
||||||
|
|
||||||
|
// One row of disassembly
|
||||||
|
CPUWindow.Row = class Row extends Toolkit.Panel {
|
||||||
|
|
||||||
|
// Object constructor
|
||||||
|
constructor(parent) {
|
||||||
|
super(parent.application, {
|
||||||
|
layout : "grid",
|
||||||
|
columns : "repeat(4, max-content)",
|
||||||
|
hollow : false,
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "visible"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure element
|
||||||
|
this.element.style.justifyContent = "start";
|
||||||
|
this.element.setAttribute("name", "row");
|
||||||
|
|
||||||
|
// Address column
|
||||||
|
this.address = this.add(parent.newLabel({ text: "\u00a0" }));
|
||||||
|
this.address.element.setAttribute("name", "address");
|
||||||
|
|
||||||
|
// Bytes column
|
||||||
|
this.bytes = this.add(parent.newLabel({ text: "\u00a0" }));
|
||||||
|
this.bytes.element.setAttribute("name", "bytes");
|
||||||
|
|
||||||
|
// Mnemonic column
|
||||||
|
this.mnemonic = this.add(parent.newLabel({ text: "\u00a0" }));
|
||||||
|
this.mnemonic.element.setAttribute("name", "mnemonic");
|
||||||
|
|
||||||
|
// Operands column
|
||||||
|
this.operands = this.add(parent.newLabel({ text: "\u00a0" }));
|
||||||
|
this.operands.element.setAttribute("name", "operands");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////// Package Methods /////////////////////////////
|
||||||
|
|
||||||
|
// Specify the column widths
|
||||||
|
setWidths(columns) {
|
||||||
|
this.address .element.style.minWidth = columns[0] + "px";
|
||||||
|
this.bytes .element.style.minWidth = columns[1] + "px";
|
||||||
|
this.mnemonic.element.style.minWidth = columns[2] + "px";
|
||||||
|
this.operands.element.style.minWidth = columns[3] + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the output labels with emulation state content
|
||||||
|
update(line, columns) {
|
||||||
|
this.address.setText(
|
||||||
|
("0000000" + line.address.toString(16).toUpperCase()).slice(-8));
|
||||||
|
|
||||||
|
let bytes = new Array(line.bytes.length);
|
||||||
|
for (let x = 0; x < bytes.length; x++)
|
||||||
|
bytes[x] =
|
||||||
|
("0"+line.bytes[x].toString(16).toUpperCase()).slice(-2);
|
||||||
|
this.bytes.setText(bytes.join(" "));
|
||||||
|
|
||||||
|
this.mnemonic.setText(line.mnemonic);
|
||||||
|
this.operands.setText(line.operands);
|
||||||
|
|
||||||
|
columns[0] = Math.max(columns[0], this.address .getBounds().width);
|
||||||
|
columns[1] = Math.max(columns[1], this.bytes .getBounds().width);
|
||||||
|
columns[2] = Math.max(columns[2], this.mnemonic.getBounds().width);
|
||||||
|
columns[3] = Math.max(columns[3], this.operands.getBounds().width);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,387 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Decode and format instruction code
|
||||||
|
(globalThis.Disassembler = class Disassembler {
|
||||||
|
|
||||||
|
// Static initializer
|
||||||
|
static initializer() {
|
||||||
|
|
||||||
|
// Bcond conditions
|
||||||
|
this.BCONDS = [
|
||||||
|
"BV" , "BL" , "BZ" , "BNH", "BN", "BR" , "BLT", "BLE",
|
||||||
|
"BNV", "BNL", "BNZ", "BH" , "BP", "NOP", "BGE", "BGT"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mapping for bit string instruction IDs
|
||||||
|
this.BITSTRING = [
|
||||||
|
"SCH0BSU", "SCH0BSD", "SCH1BSU", "SCH1BSD",
|
||||||
|
null , null , null , null ,
|
||||||
|
"ORBSU" , "ANDBSU" , "XORBSU" , "MOVBSU" ,
|
||||||
|
"ORNBSU" , "ANDNBSU", "XORNBSU", "NOTBSU"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mapping for floating-point/Nintendo instruction IDs
|
||||||
|
this.FLOATENDO = [
|
||||||
|
"CMPF.S" , null , "CVT.WS" , "CVT.SW" ,
|
||||||
|
"ADDF.S" , "SUBF.S" , "MULF.S" , "DIVF.S" ,
|
||||||
|
"XB" , "XH" , "REV" , "TRNC.SW",
|
||||||
|
"MPYHW"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Opcode definitions
|
||||||
|
this.OPDEFS = [
|
||||||
|
{ format: 1, mnemonic: "MOV" },
|
||||||
|
{ format: 1, mnemonic: "ADD" },
|
||||||
|
{ format: 1, mnemonic: "SUB" },
|
||||||
|
{ format: 1, mnemonic: "CMP" },
|
||||||
|
{ format: 1, mnemonic: "SHL" },
|
||||||
|
{ format: 1, mnemonic: "SHR" },
|
||||||
|
{ format: 1, mnemonic: "JMP" },
|
||||||
|
{ format: 1, mnemonic: "SAR" },
|
||||||
|
{ format: 1, mnemonic: "MUL" },
|
||||||
|
{ format: 1, mnemonic: "DIV" },
|
||||||
|
{ format: 1, mnemonic: "MULU" },
|
||||||
|
{ format: 1, mnemonic: "DIVU" },
|
||||||
|
{ format: 1, mnemonic: "OR" },
|
||||||
|
{ format: 1, mnemonic: "AND" },
|
||||||
|
{ format: 1, mnemonic: "XOR" },
|
||||||
|
{ format: 1, mnemonic: "NOT" },
|
||||||
|
{ format: 2, mnemonic: "MOV" },
|
||||||
|
{ format: 2, mnemonic: "ADD" },
|
||||||
|
{ format: 2, mnemonic: "SETF" },
|
||||||
|
{ format: 2, mnemonic: "CMP" },
|
||||||
|
{ format: 2, mnemonic: "SHL" },
|
||||||
|
{ format: 2, mnemonic: "SHR" },
|
||||||
|
{ format: 2, mnemonic: "CLI" },
|
||||||
|
{ format: 2, mnemonic: "SAR" },
|
||||||
|
{ format: 2, mnemonic: "TRAP" },
|
||||||
|
{ format: 2, mnemonic: "RETI" },
|
||||||
|
{ format: 2, mnemonic: "HALT" },
|
||||||
|
{ format: 0, mnemonic: null },
|
||||||
|
{ format: 2, mnemonic: "LDSR" },
|
||||||
|
{ format: 2, mnemonic: "STSR" },
|
||||||
|
{ format: 2, mnemonic: "SEI" },
|
||||||
|
{ format: 2, mnemonic: null },
|
||||||
|
{ format: 3, mnemonic: null },
|
||||||
|
{ format: 3, mnemonic: null },
|
||||||
|
{ format: 3, mnemonic: null },
|
||||||
|
{ format: 3, mnemonic: null },
|
||||||
|
{ format: 3, mnemonic: null },
|
||||||
|
{ format: 3, mnemonic: null },
|
||||||
|
{ format: 3, mnemonic: null },
|
||||||
|
{ format: 3, mnemonic: null },
|
||||||
|
{ format: 5, mnemonic: "MOVEA" },
|
||||||
|
{ format: 5, mnemonic: "ADDI" },
|
||||||
|
{ format: 4, mnemonic: "JR" },
|
||||||
|
{ format: 4, mnemonic: "JAL" },
|
||||||
|
{ format: 5, mnemonic: "ORI" },
|
||||||
|
{ format: 5, mnemonic: "ANDI" },
|
||||||
|
{ format: 5, mnemonic: "XORI" },
|
||||||
|
{ format: 5, mnemonic: "MOVHI" },
|
||||||
|
{ format: 6, mnemonic: "LD.B" },
|
||||||
|
{ format: 6, mnemonic: "LD.H" },
|
||||||
|
{ format: 0, mnemonic: null },
|
||||||
|
{ format: 6, mnemonic: "LD.W" },
|
||||||
|
{ format: 6, mnemonic: "ST.B" },
|
||||||
|
{ format: 6, mnemonic: "ST.H" },
|
||||||
|
{ format: 0, mnemonic: null },
|
||||||
|
{ format: 6, mnemonic: "ST.W" },
|
||||||
|
{ format: 6, mnemonic: "IN.B" },
|
||||||
|
{ format: 6, mnemonic: "IN.H" },
|
||||||
|
{ format: 6, mnemonic: "IN.W" },
|
||||||
|
{ format: 6, mnemonic: "CAXI" },
|
||||||
|
{ format: 6, mnemonic: "OUT.B" },
|
||||||
|
{ format: 6, mnemonic: "OUT.H" },
|
||||||
|
{ format: 7, mnemonic: null },
|
||||||
|
{ format: 6, mnemonic: "OUT.W" }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Program register names
|
||||||
|
this.PROREGNAMES = [
|
||||||
|
"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"
|
||||||
|
];
|
||||||
|
|
||||||
|
// SETF conditions
|
||||||
|
this.SETFS = [
|
||||||
|
"V" , "L" , "Z" , "NH", "N", "T", "LT", "LE",
|
||||||
|
"NV", "NL", "NZ", "H" , "P", "F", "GE", "GT"
|
||||||
|
];
|
||||||
|
|
||||||
|
// System register names
|
||||||
|
this.SYSREGNAMES = [
|
||||||
|
"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 *****************************/
|
||||||
|
|
||||||
|
// Disassemble instructions as lines of text
|
||||||
|
static disassemble(buffer, offset, address, target, pc, line, lines) {
|
||||||
|
let history, hIndex;
|
||||||
|
|
||||||
|
// Two bytes before PC to ensure PC isn't skipped
|
||||||
|
let prePC = (pc - 2 & 0xFFFFFFFF) >>> 0;
|
||||||
|
|
||||||
|
// Prepare history buffer
|
||||||
|
if (line > 0) {
|
||||||
|
history = new Array(line);
|
||||||
|
hIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locate the instruction containing the target address
|
||||||
|
for (;;) {
|
||||||
|
|
||||||
|
// Emergency error checking
|
||||||
|
if (offset >= buffer.length)
|
||||||
|
throw "Error: Target address not in disassembly buffer";
|
||||||
|
|
||||||
|
// Determine the size of the current instruction
|
||||||
|
let size = address == prePC ||
|
||||||
|
this.OPDEFS[buffer[offset + 1] >>> 2].format < 4 ? 2 : 4;
|
||||||
|
|
||||||
|
// The instruction contianis the target address
|
||||||
|
if ((target - address & 0xFFFFFFFF) >>> 0 < size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Record the current instruction in the history
|
||||||
|
if (line > 0) {
|
||||||
|
let item = history[hIndex] = history[hIndex] || {};
|
||||||
|
hIndex = hIndex < history.length - 1 ? hIndex + 1 : 0;
|
||||||
|
item.address = address;
|
||||||
|
item.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance to the next instruction
|
||||||
|
offset += size;
|
||||||
|
address = (address + size & 0xFFFFFFFF) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The target address is before the first line of output
|
||||||
|
for (; line < 0; line++) {
|
||||||
|
let size = address == prePC ||
|
||||||
|
this.OPDEFS[buffer[offset + 1] >>> 2].format < 4 ? 2 : 4;
|
||||||
|
offset += size;
|
||||||
|
address = (address + size & 0xFFFFFFFF) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The target address is after the first line of output
|
||||||
|
if (line > 0) {
|
||||||
|
let item = history[hIndex];
|
||||||
|
|
||||||
|
// Emergency error checking
|
||||||
|
if (!item)
|
||||||
|
throw "Error: First output not in disassembly history";
|
||||||
|
|
||||||
|
// Inherit the address of the first history item
|
||||||
|
address = item.address;
|
||||||
|
offset = item.offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the instructions of the output
|
||||||
|
let ret = new Array(lines);
|
||||||
|
for (let x = 0; x < lines; x++) {
|
||||||
|
let inst = ret[x] = this.decode(buffer, offset, address);
|
||||||
|
let size = address == prePC ? 2 : inst.size;
|
||||||
|
offset += size;
|
||||||
|
address = (address + size & 0xFFFFFFFF) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**************************** Private Methods ****************************/
|
||||||
|
|
||||||
|
// Retrieve bits for a 16-bit instruction
|
||||||
|
static bits(inst) {
|
||||||
|
return inst.size == 2 ?
|
||||||
|
inst.bytes[1] << 8 | inst.bytes[0] : (
|
||||||
|
inst.bytes[1] << 24 | inst.bytes[0] << 16 |
|
||||||
|
inst.bytes[3] << 8 | inst.bytes[2]
|
||||||
|
) >>> 0
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode one instruction
|
||||||
|
static decode(buffer, offset, address) {
|
||||||
|
let opcode = buffer[offset + 1] >>> 2;
|
||||||
|
let opdef = this.OPDEFS[opcode];
|
||||||
|
let size = opdef.format < 4 ? 2 : 4;
|
||||||
|
|
||||||
|
// Emergency error checking
|
||||||
|
if (offset + size > buffer.length)
|
||||||
|
throw "Error: Insufficient disassembly data";
|
||||||
|
|
||||||
|
// Produce output line object
|
||||||
|
let inst = {
|
||||||
|
address : address,
|
||||||
|
bytes : new Uint8Array(buffer.buffer.slice(offset, offset+size)),
|
||||||
|
mnemonic: opdef.mnemonic,
|
||||||
|
opcode : opcode,
|
||||||
|
operands: null,
|
||||||
|
size : size
|
||||||
|
};
|
||||||
|
|
||||||
|
// Processing by instruction format
|
||||||
|
switch (opdef.format) {
|
||||||
|
case 1: this.decodeFormat1(inst ); break;
|
||||||
|
case 2: this.decodeFormat2(inst ); break;
|
||||||
|
case 3: this.decodeFormat3(inst, address); break;
|
||||||
|
case 4: this.decodeFormat4(inst, address); break;
|
||||||
|
case 5: this.decodeFormat5(inst ); break;
|
||||||
|
case 6: this.decodeFormat6(inst ); break;
|
||||||
|
case 7: this.decodeFormat7(inst ); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Illegal opcode
|
||||||
|
if (inst.mnemonic == null)
|
||||||
|
inst.mnemonic = "---";
|
||||||
|
|
||||||
|
return inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format I
|
||||||
|
static decodeFormat1(inst) {
|
||||||
|
let bits = this.bits(inst);
|
||||||
|
let reg1 = this.PROREGNAMES[bits & 31];
|
||||||
|
switch (inst.opcode) {
|
||||||
|
case 0b000110: // JMP
|
||||||
|
inst.operands = "[" + reg1 + "]";
|
||||||
|
break;
|
||||||
|
case 0b001111: // NOT
|
||||||
|
inst.operands = reg1;
|
||||||
|
break;
|
||||||
|
default: // All others
|
||||||
|
inst.operands = reg1 + ", " + this.PROREGNAMES[bits >> 5 & 31];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format II
|
||||||
|
static decodeFormat2(inst) {
|
||||||
|
let bits = this.bits(inst);
|
||||||
|
let reg2 = this.PROREGNAMES[bits >> 5 & 31];
|
||||||
|
let other = bits & 31;
|
||||||
|
switch (inst.opcode) {
|
||||||
|
case 0b010010: // SETF
|
||||||
|
inst.operands = this.SETFS[other & 15] + ", " + reg2;
|
||||||
|
break;
|
||||||
|
case 0b011001: // TRAP
|
||||||
|
inst.operands = other;
|
||||||
|
break;
|
||||||
|
case 0b011100: // LDSR
|
||||||
|
inst.operands = reg2 + ", " + this.SYSREGNAMES[other];
|
||||||
|
break;
|
||||||
|
case 0b011101: // STSR
|
||||||
|
inst.operands = this.SYSREGNAMES[other] + ", " + reg2;
|
||||||
|
break;
|
||||||
|
case 0b011111: // Bit string
|
||||||
|
inst.mnemonic = this.BITSTRING[other];
|
||||||
|
break;
|
||||||
|
case 0b010110: // CLI
|
||||||
|
case 0b011001: // RETI
|
||||||
|
case 0b011010: // HALT
|
||||||
|
case 0b011110: // SEI
|
||||||
|
break;
|
||||||
|
case 0b010000: // MOV
|
||||||
|
case 0b010001: // ADD
|
||||||
|
case 0b010011: // CMP
|
||||||
|
inst.operands = this.signExtend(other, 5) + ", " + reg2;
|
||||||
|
break;
|
||||||
|
default: // SHL, SHR, SAR
|
||||||
|
inst.operands = other + ", " + reg2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format III
|
||||||
|
static decodeFormat3(inst, address) {
|
||||||
|
let bits = this.bits(inst);
|
||||||
|
let disp = this.signExtend(bits & 0x1FF, 9);
|
||||||
|
let cond = bits >> 9 & 15;
|
||||||
|
inst.mnemonic = this.BCONDS[cond];
|
||||||
|
if (cond == 13)
|
||||||
|
return; // NOP
|
||||||
|
inst.operands = ("0000000" + ((address + disp & 0xFFFFFFFF) >>> 0)
|
||||||
|
.toString(16).toUpperCase()).slice(-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format IV
|
||||||
|
static decodeFormat4(inst, address) {
|
||||||
|
let bits = this.bits(inst);
|
||||||
|
let disp = this.signExtend(bits & 0x3FFFFFF, 26);
|
||||||
|
inst.operands = ("0000000" + ((address + disp & 0xFFFFFFFF) >>> 0)
|
||||||
|
.toString(16).toUpperCase()).slice(-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format V
|
||||||
|
static decodeFormat5(inst) {
|
||||||
|
let bits = this.bits(inst);
|
||||||
|
let reg2 = this.PROREGNAMES[bits >> 21 & 31];
|
||||||
|
let reg1 = this.PROREGNAMES[bits >> 16 & 31];
|
||||||
|
let imm = bits & 0xFFFF;
|
||||||
|
switch (inst.opcode) {
|
||||||
|
case 0b101001: // ADDI
|
||||||
|
inst.operands =
|
||||||
|
this.signExtend(imm, 16) + ", " + reg1 + ", " + reg2;
|
||||||
|
break;
|
||||||
|
default: // All others
|
||||||
|
inst.operands = "0x" + ("000" + imm.toString(16).toUpperCase())
|
||||||
|
.slice(-4) + ", " + reg1 + ", " + reg2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format VI
|
||||||
|
static decodeFormat6(inst) {
|
||||||
|
let bits = this.bits(inst);
|
||||||
|
let reg2 = this.PROREGNAMES[bits >> 21 & 31];
|
||||||
|
let reg1 = this.PROREGNAMES[bits >> 16 & 31];
|
||||||
|
let disp = this.signExtend(bits & 0xFFFF, 16);
|
||||||
|
disp = disp == 0 ? "" : (disp < 0 ? "-" : "") + "0x" +
|
||||||
|
Math.abs(disp).toString(16).toUpperCase();
|
||||||
|
switch (inst.opcode) {
|
||||||
|
case 0b110000: // LD.B
|
||||||
|
case 0b110001: // LD.H
|
||||||
|
case 0b110011: // LD.W
|
||||||
|
case 0b111000: // IN.B
|
||||||
|
case 0b111001: // IN.H
|
||||||
|
case 0b111011: // IN.W
|
||||||
|
inst.operands = disp + "[" + reg1 + "], " + reg2;
|
||||||
|
break;
|
||||||
|
default: // Output and store
|
||||||
|
inst.operands = reg2 + ", " + disp + "[" + reg1 + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format VII
|
||||||
|
static decodeFormat7(inst) {
|
||||||
|
let bits = this.bits(inst);
|
||||||
|
let reg2 = this.PROREGNAMES[bits >> 21 & 31];
|
||||||
|
let reg1 = this.PROREGNAMES[bits >> 16 & 31];
|
||||||
|
let subop = this.signExtend(bits & 0xFFFF, 16);
|
||||||
|
inst.mnemonic = this.FLOATENDO[subop];
|
||||||
|
if (inst.mnemonic = null)
|
||||||
|
return;
|
||||||
|
switch (subop) {
|
||||||
|
case 0b001000: // XB
|
||||||
|
case 0b001001: // XH
|
||||||
|
inst.operands = reg2;
|
||||||
|
break;
|
||||||
|
default: // All others
|
||||||
|
inst.operands = reg1 + ", " + reg2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign extend a value
|
||||||
|
static signExtend(value, bits) {
|
||||||
|
return value & 1 << bits - 1 ? value | -1 << bits : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}).initializer();
|
|
@ -10,6 +10,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
// Configure instance fields
|
// Configure instance fields
|
||||||
this.address = 0x05000000;
|
this.address = 0x05000000;
|
||||||
this.debug = debug;
|
this.debug = debug;
|
||||||
|
this.pending = { mode: null };
|
||||||
this.rows = [];
|
this.rows = [];
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
|
@ -20,7 +21,6 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
|
|
||||||
// Configure client
|
// Configure client
|
||||||
this.client.setLayout("grid");
|
this.client.setLayout("grid");
|
||||||
this.client.addResizeListener(b=>this.refresh(b.height));
|
|
||||||
|
|
||||||
// Wrapping element to hide overflowing scrollbar
|
// Wrapping element to hide overflowing scrollbar
|
||||||
this.hexWrap = this.client.add(this.newPanel({
|
this.hexWrap = this.client.add(this.newPanel({
|
||||||
|
@ -34,12 +34,15 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
focusable: true,
|
focusable: true,
|
||||||
layout : "block",
|
layout : "block",
|
||||||
hollow : false,
|
hollow : false,
|
||||||
|
name : "{memory.hexEditor}",
|
||||||
overflowX: "auto",
|
overflowX: "auto",
|
||||||
overflowY: "hidden"
|
overflowY: "hidden"
|
||||||
}));
|
}));
|
||||||
this.hex.element.setAttribute("role", "grid");
|
this.hex.element.setAttribute("role", "grid");
|
||||||
this.hex.element.setAttribute("name", "hex");
|
this.hex.element.setAttribute("name", "hex");
|
||||||
this.hex.element.addEventListener("wheel", e=>this.onwheel(e));
|
this.hex.element.addEventListener("keydown", e=>this.onkeyhex(e));
|
||||||
|
this.hex.element.addEventListener("wheel" , e=>this.onwheel (e));
|
||||||
|
this.hex.addResizeListener(b=>this.onresize());
|
||||||
|
|
||||||
// Configure properties
|
// Configure properties
|
||||||
this.setProperty("sim", "");
|
this.setProperty("sim", "");
|
||||||
|
@ -49,44 +52,42 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////// Package Methods /////////////////////////////
|
///////////////////////////// Public Methods //////////////////////////////
|
||||||
|
|
||||||
// Update display text with localized strings
|
|
||||||
localize() {
|
|
||||||
let hex = "";
|
|
||||||
if (this.application)
|
|
||||||
hex = this.application.translate("{memory.hexEditor}");
|
|
||||||
this.hex.element.setAttribute("aria-label", hex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the display with current emulation data
|
// Update the display with current emulation data
|
||||||
refresh(gridHeight, lineHeight) {
|
refresh(address) {
|
||||||
|
|
||||||
// Do nothing while closed
|
// Do nothing while closed or already waiting to refresh
|
||||||
if (!this.isVisible())
|
if (!this.isVisible() || this.pending.mode !== null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Working variables
|
// Working variables
|
||||||
gridHeight = gridHeight || this.hex.getBounds().height;
|
address = address === undefined ? this.address : address;
|
||||||
lineHeight = lineHeight || this.lineHeight();
|
let lines = this.lines(false);
|
||||||
let rowCount = this.lines(false, gridHeight, lineHeight);
|
|
||||||
|
// Configure pending state
|
||||||
|
this.pending.mode = "first";
|
||||||
|
this.pending.address = null;
|
||||||
|
this.pending.line = null;
|
||||||
|
|
||||||
// Request bus data from the WebAssembly core
|
// Request bus data from the WebAssembly core
|
||||||
this.debug.core.postMessage({
|
this.debug.core.postMessage({
|
||||||
command: "ReadBuffer",
|
command: "ReadBuffer",
|
||||||
debug : "Memory",
|
|
||||||
address: this.address,
|
|
||||||
sim : this.debug.sim,
|
sim : this.debug.sim,
|
||||||
size : rowCount * 16
|
debug : "Memory",
|
||||||
|
address: address,
|
||||||
|
lines : lines,
|
||||||
|
size : lines * 16
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Configure elements
|
// Specify whether the component is visible
|
||||||
for (let y = this.rows.length; y < rowCount; y++)
|
setVisible(visible, focus) {
|
||||||
this.rows[y] = this.hex.add(new MemoryWindow.Row(this.hex));
|
let prev = this.visible
|
||||||
for (let y = rowCount; y < this.rows.length; y++)
|
visible = !!visible;
|
||||||
this.hex.remove(this.rows[y]);
|
super.setVisible(visible, focus);
|
||||||
if (this.rows.length > rowCount)
|
if (visible && !prev)
|
||||||
this.rows.splice(rowCount, this.rows.length - rowCount);
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,12 +103,35 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
|
|
||||||
// Received bytes from the bus
|
// Received bytes from the bus
|
||||||
readBuffer(msg) {
|
readBuffer(msg) {
|
||||||
let bytes = new Uint8Array(msg.buffer);
|
let buffer = new Uint8Array(msg.buffer);
|
||||||
|
let lines = Math.min(msg.lines, this.rows.length);
|
||||||
|
|
||||||
|
// Configure instance fields
|
||||||
|
this.address = msg.address;
|
||||||
|
|
||||||
|
// Update display
|
||||||
for (
|
for (
|
||||||
let x = 0, address = this.address, offset = 0;
|
let x = 0, address = msg.address, offset = 0;
|
||||||
x < this.rows.length && offset < bytes.length;
|
x < lines && offset < buffer.length;
|
||||||
x++, address = (address + 16 & 0xFFFFFFF0) >>> 0, offset += 16
|
x++, address = (address + 16 & 0xFFFFFFF0) >>> 0, offset += 16
|
||||||
) this.rows[x].update(address, bytes, offset);
|
) this.rows[x].update(address, buffer, offset);
|
||||||
|
|
||||||
|
// Check for pending display updates
|
||||||
|
let address = this.pending.address === null ?
|
||||||
|
this.address : this.pending.address;
|
||||||
|
let line = this.pending.line === null ?
|
||||||
|
0 : this.pending.line ;
|
||||||
|
let mode = this.pending.mode;
|
||||||
|
this.pending.mode = null;
|
||||||
|
switch (mode) {
|
||||||
|
case "first":
|
||||||
|
case null :
|
||||||
|
return;
|
||||||
|
case "refresh":
|
||||||
|
case "scroll" :
|
||||||
|
case "seek" :
|
||||||
|
this.refresh((address + line * 16 & 0xFFFFFFF0) >>> 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,16 +150,16 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the number of rows of output
|
// Determine the number of rows of output
|
||||||
lines(fullyVisible, gridHeight, lineHeight) {
|
lines(fullyVisible) {
|
||||||
gridHeight = gridHeight || this.client.getBounds().height;
|
let gridHeight = this.hex.getBounds().height;
|
||||||
lineHeight = lineHeight || this.lineHeight();
|
let lineHeight = this.lineHeight();
|
||||||
let ret = gridHeight / lineHeight;
|
let ret = gridHeight / lineHeight;
|
||||||
ret = fullyVisible ? Math.floor(ret) : Math.ceil(ret);
|
ret = fullyVisible ? Math.floor(ret) : Math.ceil(ret);
|
||||||
return Math.max(1, ret);
|
return Math.max(1, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key down event handler
|
// Key down event handler
|
||||||
onkeydown(e) {
|
onkeyhex(e) {
|
||||||
|
|
||||||
// Control is pressed
|
// Control is pressed
|
||||||
if (e.ctrlKey) switch (e.key) {
|
if (e.ctrlKey) switch (e.key) {
|
||||||
|
@ -147,32 +171,17 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
(parseInt(addr, 16) & 0xFFFFFFF0) >>> 0,
|
(parseInt(addr, 16) & 0xFFFFFFF0) >>> 0,
|
||||||
Math.floor(this.lines(true) / 3)
|
Math.floor(this.lines(true) / 3)
|
||||||
);
|
);
|
||||||
this.refresh();
|
|
||||||
break;
|
break;
|
||||||
default: return;
|
default: return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processing by key
|
// Processing by key
|
||||||
else switch (e.key) {
|
else switch (e.key) {
|
||||||
case "ArrowDown":
|
case "ArrowDown": this.scroll( 1 ); break;
|
||||||
this.address = (this.address + 16 & 0xFFFFFFF0) >>> 0;
|
case "ArrowUp" : this.scroll(-1 ); break;
|
||||||
this.refresh();
|
case "PageDown" : this.scroll( this.lines(true)); break;
|
||||||
break;
|
case "PageUp" : this.scroll(-this.lines(true)); break;
|
||||||
case "ArrowUp":
|
default : return;
|
||||||
this.address = (this.address - 16 & 0xFFFFFFF0) >>> 0;
|
|
||||||
this.refresh();
|
|
||||||
break;
|
|
||||||
case "PageUp":
|
|
||||||
this.address = (this.address - 16 * this.lines(true) &
|
|
||||||
0xFFFFFFF0) >>> 0;
|
|
||||||
this.refresh();
|
|
||||||
break;
|
|
||||||
case "PageDown":
|
|
||||||
this.address = (this.address + 16 * this.lines(true) &
|
|
||||||
0xFFFFFFF0) >>> 0;
|
|
||||||
this.refresh();
|
|
||||||
break;
|
|
||||||
default: return super.onkeydown(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure event
|
// Configure event
|
||||||
|
@ -180,26 +189,69 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resize event handler
|
||||||
|
onresize() {
|
||||||
|
let lines = this.lines(false);
|
||||||
|
for (let y = this.rows.length; y < lines; y++)
|
||||||
|
this.rows[y] = this.hex.add(new MemoryWindow.Row(this.hex));
|
||||||
|
for (let y = lines; y < this.rows.length; y++)
|
||||||
|
this.hex.remove(this.rows[y]);
|
||||||
|
if (this.rows.length > lines)
|
||||||
|
this.rows.splice(lines, this.rows.length - lines);
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
// Mouse wheel event handler
|
// Mouse wheel event handler
|
||||||
onwheel(e) {
|
onwheel(e) {
|
||||||
let lineHeight = this.lineHeight();
|
|
||||||
let sign = Math.sign(e.deltaY);
|
let sign = Math.sign(e.deltaY);
|
||||||
let mag = Math.abs (e.deltaY);
|
let mag = Math.abs (e.deltaY);
|
||||||
if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL)
|
if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL)
|
||||||
mag = Math.max(1, Math.floor(mag / lineHeight));
|
mag = Math.max(1, Math.floor(mag / this.lineHeight()));
|
||||||
|
|
||||||
// Configure element
|
// Configure element
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// Specify the new address
|
// Configure display
|
||||||
this.address = (this.address + sign * mag * 16 & 0xFFFFFFF0) >>> 0;
|
this.scroll(sign * mag);
|
||||||
this.refresh(null, lineHeight);
|
}
|
||||||
|
|
||||||
|
// Move to a new address relative to the current address
|
||||||
|
scroll(lines) {
|
||||||
|
switch (this.pending.mode) {
|
||||||
|
case "first" :
|
||||||
|
case "refresh":
|
||||||
|
this.pending.mode = "scroll";
|
||||||
|
this.pending.line = lines;
|
||||||
|
break;
|
||||||
|
case "scroll":
|
||||||
|
case "seek" :
|
||||||
|
this.pending.mode = "scroll";
|
||||||
|
this.pending.line += lines;
|
||||||
|
break;
|
||||||
|
case null:
|
||||||
|
this.refresh((this.address + lines * 16 & 0xFFFFFFF0) >>> 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to a new address positioned at a particular row of output
|
// Move to a new address positioned at a particular row of output
|
||||||
seek(address, index) {
|
seek(address, line) {
|
||||||
this.address = (address - (index || 0) * 16 & 0xFFFFFFF0) >>> 0;
|
switch (this.pending.mode) {
|
||||||
|
case "first" :
|
||||||
|
case "refresh":
|
||||||
|
this.pending.mode = "seek";
|
||||||
|
this.pending.address = address;
|
||||||
|
this.pending.line = line;
|
||||||
|
break;
|
||||||
|
case "scroll":
|
||||||
|
case "seek" :
|
||||||
|
this.pending.mode = "seek";
|
||||||
|
this.pending.address = address;
|
||||||
|
this.pending.line += line;
|
||||||
|
break;
|
||||||
|
case null:
|
||||||
|
this.refresh((address - line * 16 & 0xFFFFFFF0) >>> 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
this.setExpanded(this.chkExpand.isChecked()));
|
this.setExpanded(this.chkExpand.isChecked()));
|
||||||
|
|
||||||
// Value text box
|
// Value text box
|
||||||
this.txtValue = this.add(this.newTextBox({ text: "00000000" }));
|
this.txtValue = this.add(this.newTextBox({ text: "\u00a0" }));
|
||||||
this.txtValue.element.setAttribute("name", "value");
|
this.txtValue.element.setAttribute("name", "value");
|
||||||
this.txtValue.addCommitListener(e=>this.onvalue());
|
this.txtValue.addCommitListener(e=>this.onvalue());
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
/* This file is included into vb.c and cannot be compiled on its own. */
|
||||||
|
#ifdef VBAPI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************* Constants *********************************/
|
||||||
|
|
||||||
|
/* Instruction IDs */
|
||||||
|
#define CPU_ILLEGAL -1
|
||||||
|
#define CPU_ADD_IMM 0
|
||||||
|
#define CPU_ADD_REG 1
|
||||||
|
#define CPU_ADDF_S 2
|
||||||
|
#define CPU_ADDI 3
|
||||||
|
#define CPU_AND 4
|
||||||
|
#define CPU_ANDBSU 5
|
||||||
|
#define CPU_ANDI 6
|
||||||
|
#define CPU_ANDNBSU 7
|
||||||
|
#define CPU_BCOND 8
|
||||||
|
#define CPU_CAXI 9
|
||||||
|
#define CPU_CLI 10
|
||||||
|
#define CPU_CMP_IMM 11
|
||||||
|
#define CPU_CMP_REG 12
|
||||||
|
#define CPU_CMPF_S 13
|
||||||
|
#define CPU_CVT_SW 14
|
||||||
|
#define CPU_CVT_WS 15
|
||||||
|
#define CPU_DIV 16
|
||||||
|
#define CPU_DIVF_S 17
|
||||||
|
#define CPU_DIVU 18
|
||||||
|
#define CPU_HALT 19
|
||||||
|
#define CPU_IN_B 20
|
||||||
|
#define CPU_IN_H 21
|
||||||
|
#define CPU_IN_W 22
|
||||||
|
#define CPU_JAL 23
|
||||||
|
#define CPU_JMP 24
|
||||||
|
#define CPU_JR 25
|
||||||
|
#define CPU_LD_B 26
|
||||||
|
#define CPU_LD_H 27
|
||||||
|
#define CPU_LD_W 28
|
||||||
|
#define CPU_LDSR 29
|
||||||
|
#define CPU_MOV_IMM 30
|
||||||
|
#define CPU_MOV_REG 31
|
||||||
|
#define CPU_MOVBSU 32
|
||||||
|
#define CPU_MOVEA 33
|
||||||
|
#define CPU_MOVHI 34
|
||||||
|
#define CPU_MPYHW 35
|
||||||
|
#define CPU_MUL 36
|
||||||
|
#define CPU_MULF_S 37
|
||||||
|
#define CPU_MULU 38
|
||||||
|
#define CPU_NOT 39
|
||||||
|
#define CPU_NOTBSU 40
|
||||||
|
#define CPU_OR 41
|
||||||
|
#define CPU_ORBSU 42
|
||||||
|
#define CPU_ORI 43
|
||||||
|
#define CPU_ORNBSU 44
|
||||||
|
#define CPU_OUT_B 45
|
||||||
|
#define CPU_OUT_H 46
|
||||||
|
#define CPU_OUT_W 47
|
||||||
|
#define CPU_RETI 48
|
||||||
|
#define CPU_REV 49
|
||||||
|
#define CPU_SAR_IMM 50
|
||||||
|
#define CPU_SAR_REG 51
|
||||||
|
#define CPU_SCH0BSD 52
|
||||||
|
#define CPU_SCH0BSU 53
|
||||||
|
#define CPU_SCH1BSD 54
|
||||||
|
#define CPU_SCH1BSU 55
|
||||||
|
#define CPU_SEI 56
|
||||||
|
#define CPU_SETF 57
|
||||||
|
#define CPU_SHL_IMM 58
|
||||||
|
#define CPU_SHL_REG 59
|
||||||
|
#define CPU_SHR_IMM 60
|
||||||
|
#define CPU_SHR_REG 61
|
||||||
|
#define CPU_ST_B 62
|
||||||
|
#define CPU_ST_H 63
|
||||||
|
#define CPU_ST_W 64
|
||||||
|
#define CPU_STSR 65
|
||||||
|
#define CPU_SUB 66
|
||||||
|
#define CPU_SUBF_S 67
|
||||||
|
#define CPU_TRAP 68
|
||||||
|
#define CPU_TRNC_SW 69
|
||||||
|
#define CPU_XB 70
|
||||||
|
#define CPU_XH 71
|
||||||
|
#define CPU_XOR 72
|
||||||
|
#define CPU_XORBSU 73
|
||||||
|
#define CPU_XORI 74
|
||||||
|
#define CPU_XORNBSU 75
|
||||||
|
#define CPU_BITSTRING 76
|
||||||
|
#define CPU_FLOATENDO 77
|
||||||
|
|
||||||
|
/* Mapping for bit string sub-opcodes */
|
||||||
|
static const int8_t CPU_BITSTRINGS[] = {
|
||||||
|
CPU_SCH0BSU, CPU_SCH0BSD, CPU_SCH1BSU, CPU_SCH1BSD,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ORBSU , CPU_ANDBSU , CPU_XORBSU , CPU_MOVBSU ,
|
||||||
|
CPU_ORNBSU , CPU_ANDNBSU, CPU_XORNBSU, CPU_NOTBSU ,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Mapping for floating-point/Nintendo sub-opcodes */
|
||||||
|
static const int8_t CPU_FLOATENDO[] = {
|
||||||
|
CPU_CMPF_S , CPU_ILLEGAL, CPU_CVT_WS , CPU_CVT_SW ,
|
||||||
|
CPU_ADDF_S , CPU_SUBF_S , CPU_MULF_S , CPU_DIVF_S ,
|
||||||
|
CPU_XB , CPU_XH , CPU_REV , CPU_TRNC_SW,
|
||||||
|
CPU_MPYHW , CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL,
|
||||||
|
CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* VBAPI */
|
2
makefile
2
makefile
|
@ -1,7 +1,7 @@
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help:
|
help:
|
||||||
@echo
|
@echo
|
||||||
@echo "Virtual Boy Emulator - September 1, 2021"
|
@echo "Virtual Boy Emulator - September 5, 2021"
|
||||||
@echo
|
@echo
|
||||||
@echo "Target build environment is any Debian with the following packages:"
|
@echo "Target build environment is any Debian with the following packages:"
|
||||||
@echo " emscripten"
|
@echo " emscripten"
|
||||||
|
|
Loading…
Reference in New Issue