Adding disassembler to CPU window

This commit is contained in:
Guy Perfect 2021-09-06 00:09:15 +00:00
parent af5fc0c4fc
commit 85cc0f5754
10 changed files with 997 additions and 140 deletions

View File

@ -35,22 +35,22 @@
// Retrieve the values of all the CPU registers
getRegisters(msg) {
msg.pc = this.core.GetProgramCounter(msg.sim, 0);
msg.pcFrom = this.core.GetProgramCounter(msg.sim, 1);
msg.pcTo = this.core.GetProgramCounter(msg.sim, 2);
msg.adtre = this.core.GetSystemRegister(msg.sim, 25);
msg.chcw = this.core.GetSystemRegister(msg.sim, 24);
msg.ecr = this.core.GetSystemRegister(msg.sim, 4);
msg.eipc = this.core.GetSystemRegister(msg.sim, 0);
msg.eipsw = this.core.GetSystemRegister(msg.sim, 1);
msg.fepc = this.core.GetSystemRegister(msg.sim, 2);
msg.fepsw = this.core.GetSystemRegister(msg.sim, 3);
msg.pir = this.core.GetSystemRegister(msg.sim, 6);
msg.psw = this.core.GetSystemRegister(msg.sim, 5);
msg.tkcw = this.core.GetSystemRegister(msg.sim, 7);
msg.sr29 = this.core.GetSystemRegister(msg.sim, 29);
msg.sr30 = this.core.GetSystemRegister(msg.sim, 30);
msg.sr31 = this.core.GetSystemRegister(msg.sim, 31);
msg.pc = this.core.GetProgramCounter(msg.sim, 0) >>> 0;
msg.pcFrom = this.core.GetProgramCounter(msg.sim, 1) >>> 0;
msg.pcTo = this.core.GetProgramCounter(msg.sim, 2) >>> 0;
msg.adtre = this.core.GetSystemRegister(msg.sim, 25) >>> 0;
msg.chcw = this.core.GetSystemRegister(msg.sim, 24) >>> 0;
msg.ecr = this.core.GetSystemRegister(msg.sim, 4) >>> 0;
msg.eipc = this.core.GetSystemRegister(msg.sim, 0) >>> 0;
msg.eipsw = this.core.GetSystemRegister(msg.sim, 1) >>> 0;
msg.fepc = this.core.GetSystemRegister(msg.sim, 2) >>> 0;
msg.fepsw = this.core.GetSystemRegister(msg.sim, 3) >>> 0;
msg.pir = this.core.GetSystemRegister(msg.sim, 6) >>> 0;
msg.psw = this.core.GetSystemRegister(msg.sim, 5) >>> 0;
msg.tkcw = this.core.GetSystemRegister(msg.sim, 7) >>> 0;
msg.sr29 = this.core.GetSystemRegister(msg.sim, 29) >>> 0;
msg.sr30 = this.core.GetSystemRegister(msg.sim, 30) >>> 0;
msg.sr31 = this.core.GetSystemRegister(msg.sim, 31) >>> 0;
msg.program = new Array(32);
for (let x = 0; x <= 31; x++)
msg.program[x] = this.core.GetProgramRegister(msg.sim, x);
@ -79,6 +79,7 @@
msg.buffer = this.core.memory.buffer.slice(
buffer.pointer, buffer.pointer + msg.size);
this.free(buffer);
msg.pc = this.core.GetProgramCounter(msg.sim) >>> 0;
postMessage(msg, msg.buffer);
}

View File

@ -316,6 +316,7 @@ let run = async function() {
await Bundle.run("app/toolkit/TextBox.js");
await Bundle.run("app/toolkit/Window.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/MemoryWindow.js");
await App.create();

View File

@ -499,3 +499,11 @@ input[type="text"] {
[aria-disabled="true"] [name="check"]{
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);
}

View File

@ -5,7 +5,11 @@ Toolkit.Window = class Window extends Toolkit.Panel {
// Object constructor
constructor(application, options) {
super(application, options);
super(application, options ? {
height : options.height,
visible: true,
width : options.width
} : {});
options = options || {};
// Configure instance fields
@ -15,15 +19,17 @@ Toolkit.Window = class Window extends Toolkit.Panel {
this.dragCursor = { x: 0, y: 0 };
this.dragEdge = null;
this.dragPointer = null;
this.initialCenter = "center" in options ? !!options.center : false;
this.lastFocus = this.element;
this.shown = this.visible;
this.shown = "visible" in options ? !!options.visible : true;
this.visible = this.shown;
// Configure element
this.setLayout("grid", { columns: "auto" });
this.setRole("dialog");
this.setLocation(0, 0);
this.element.style.position = "absolute";
if (!this.shown)
this.element.style.visibility = "hidden";
this.element.setAttribute("aria-modal", "false");
this.element.setAttribute("focus" , "false");
this.element.setAttribute("tabindex" , "0" );
@ -90,6 +96,13 @@ Toolkit.Window = class Window extends Toolkit.Panel {
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
getTitle() {
return this.title.getText();
@ -107,10 +120,14 @@ Toolkit.Window = class Window extends Toolkit.Panel {
// Specify whether the component is visible
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)
return;
if (visible)
this.contain();
if (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 /////////////////////////////
// Position the window in the center of the desktop

View File

@ -36,20 +36,21 @@
// Configure instance fields
this.address = 0xFFFFFFF0;
this.columns = [ 0, 0, 0, 0 ];
this.debug = debug;
this.pendingDasm = { mode: null };
this.pendingRegs = { mode: null };
this.rows = [];
// Configure properties
this.setProperty("sim", "");
// Configure elements
this.initDisassembler();
this.initSystemRegisters();
this.initProgramRegisters();
this.initWindow();
// Layout components
// Disassembler on the left
this.mainWrap.add(this.dasmWrap);
@ -113,6 +114,11 @@
overflowY: "hidden"
}));
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
@ -137,7 +143,6 @@
this.proRegs.registers = {};
for (let x = 0; x <= 31; x++)
this.addRegister(false, x, CPUWindow.PROGRAM[x] || "r" + x);
}
// Initialize system registers pane
@ -189,7 +194,6 @@
this.client.setLayout("grid", {
columns: "auto"
});
this.client.addResizeListener(b=>this.onresize(b));
// Configure main wrapper
this.mainWrap = this.client.add(this.newPanel({
@ -203,32 +207,19 @@
///////////////////////////// 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
refresh(clientHeight, lineHeight, registers) {
if (!this.isVisible())
return;
if (registers) {
this.getRegisters({ registers });
return;
refresh() {
this.refreshDasm();
this.refreshRegs();
}
this.debug.core.postMessage({
command: "GetRegisters",
debug : "CPU",
sim : this.debug.sim
});
// Specify whether the component is visible
setVisible(visible, focus) {
let prev = this.visible
visible = !!visible;
super.setVisible(visible, focus);
if (visible && !prev)
this.refresh();
}
@ -239,6 +230,7 @@
message(msg) {
switch (msg.command) {
case "GetRegisters": this.getRegisters(msg); break;
case "ReadBuffer" : this.readBuffer (msg); break;
case "SetRegister" : this.setRegister (msg); break;
}
}
@ -262,6 +254,54 @@
this.sysRegs.registers[31 ].setValue(msg.sr31 );
for (let x = 0; x <= 31; 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
@ -284,13 +324,254 @@
list.add(reg.expansion);
}
// Resize event handler
onresize(bounds) {
if (!this.isVisible())
return;
//this.regs.setHeight(bounds.height);
// The window is being displayed for the first time
firstShow() {
super.firstShow();
this.center();
this.mainSplit.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();
// 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);
}
};

387
app/windows/Disassembler.js Normal file
View File

@ -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();

View File

@ -10,6 +10,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
// Configure instance fields
this.address = 0x05000000;
this.debug = debug;
this.pending = { mode: null };
this.rows = [];
// Configure element
@ -20,7 +21,6 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
// Configure client
this.client.setLayout("grid");
this.client.addResizeListener(b=>this.refresh(b.height));
// Wrapping element to hide overflowing scrollbar
this.hexWrap = this.client.add(this.newPanel({
@ -34,12 +34,15 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
focusable: true,
layout : "block",
hollow : false,
name : "{memory.hexEditor}",
overflowX: "auto",
overflowY: "hidden"
}));
this.hex.element.setAttribute("role", "grid");
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
this.setProperty("sim", "");
@ -49,44 +52,42 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
///////////////////////////// Package 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);
}
///////////////////////////// Public Methods //////////////////////////////
// Update the display with current emulation data
refresh(gridHeight, lineHeight) {
refresh(address) {
// Do nothing while closed
if (!this.isVisible())
// Do nothing while closed or already waiting to refresh
if (!this.isVisible() || this.pending.mode !== null)
return;
// Working variables
gridHeight = gridHeight || this.hex.getBounds().height;
lineHeight = lineHeight || this.lineHeight();
let rowCount = this.lines(false, gridHeight, lineHeight);
address = address === undefined ? this.address : address;
let lines = this.lines(false);
// Configure pending state
this.pending.mode = "first";
this.pending.address = null;
this.pending.line = null;
// Request bus data from the WebAssembly core
this.debug.core.postMessage({
command: "ReadBuffer",
debug : "Memory",
address: this.address,
sim : this.debug.sim,
size : rowCount * 16
debug : "Memory",
address: address,
lines : lines,
size : lines * 16
});
}
// Configure elements
for (let y = this.rows.length; y < rowCount; y++)
this.rows[y] = this.hex.add(new MemoryWindow.Row(this.hex));
for (let y = rowCount; y < this.rows.length; y++)
this.hex.remove(this.rows[y]);
if (this.rows.length > rowCount)
this.rows.splice(rowCount, this.rows.length - rowCount);
// Specify whether the component is visible
setVisible(visible, focus) {
let prev = this.visible
visible = !!visible;
super.setVisible(visible, focus);
if (visible && !prev)
this.refresh();
}
@ -102,12 +103,35 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
// Received bytes from the bus
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 (
let x = 0, address = this.address, offset = 0;
x < this.rows.length && offset < bytes.length;
let x = 0, address = msg.address, offset = 0;
x < lines && offset < buffer.length;
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
lines(fullyVisible, gridHeight, lineHeight) {
gridHeight = gridHeight || this.client.getBounds().height;
lineHeight = lineHeight || this.lineHeight();
lines(fullyVisible) {
let gridHeight = this.hex.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
onkeydown(e) {
onkeyhex(e) {
// Control is pressed
if (e.ctrlKey) switch (e.key) {
@ -147,32 +171,17 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
(parseInt(addr, 16) & 0xFFFFFFF0) >>> 0,
Math.floor(this.lines(true) / 3)
);
this.refresh();
break;
default: return;
}
// Processing by key
else switch (e.key) {
case "ArrowDown":
this.address = (this.address + 16 & 0xFFFFFFF0) >>> 0;
this.refresh();
break;
case "ArrowUp":
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);
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
@ -180,26 +189,69 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
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
onwheel(e) {
let lineHeight = this.lineHeight();
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 / lineHeight));
mag = Math.max(1, Math.floor(mag / this.lineHeight()));
// Configure element
e.preventDefault();
e.stopPropagation();
// Specify the new address
this.address = (this.address + sign * mag * 16 & 0xFFFFFFF0) >>> 0;
this.refresh(null, lineHeight);
// Configure display
this.scroll(sign * mag);
}
// 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
seek(address, index) {
this.address = (address - (index || 0) * 16 & 0xFFFFFFF0) >>> 0;
seek(address, line) {
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);
}
}
};

View File

@ -61,7 +61,7 @@
this.setExpanded(this.chkExpand.isChecked()));
// 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.addCommitListener(e=>this.onvalue());

121
core/cpu.c Normal file
View File

@ -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 */

View File

@ -1,7 +1,7 @@
.PHONY: help
help:
@echo
@echo "Virtual Boy Emulator - September 1, 2021"
@echo "Virtual Boy Emulator - September 5, 2021"
@echo
@echo "Target build environment is any Debian with the following packages:"
@echo " emscripten"