Enabling CPU trace and hex edit
This commit is contained in:
parent
94486ecf02
commit
573f72e46f
|
@ -109,7 +109,7 @@ globalThis.App = class App {
|
|||
|
||||
// Message received
|
||||
onmessage(msg) {
|
||||
if ("debug" in msg) {
|
||||
if ("dbgwnd" in msg) {
|
||||
this.debuggers[msg.sim].message(msg);
|
||||
return;
|
||||
}
|
||||
|
@ -164,6 +164,7 @@ globalThis.App = class App {
|
|||
// Send the ROM to the WebAssembly core module
|
||||
this.core.postMessage({
|
||||
command: "SetROM",
|
||||
reset : true,
|
||||
rom : file,
|
||||
sim : 0
|
||||
}, file);
|
||||
|
|
|
@ -39,7 +39,7 @@ globalThis.Debugger = class Debugger {
|
|||
|
||||
// Message received from emulation thread
|
||||
message(msg) {
|
||||
switch (msg.debug) {
|
||||
switch (msg.dbgwnd) {
|
||||
case "CPU" : this.cpu .message(msg); break;
|
||||
case "Memory": this.memory.message(msg); break;
|
||||
}
|
||||
|
|
|
@ -28,8 +28,11 @@
|
|||
case "GetRegisters": this.getRegisters(msg); break;
|
||||
case "Init" : this.init (msg); break;
|
||||
case "ReadBuffer" : this.readBuffer (msg); break;
|
||||
case "RunNext" : this.runNext (msg); break;
|
||||
case "SetRegister" : this.setRegister (msg); break;
|
||||
case "SetROM" : this.setROM (msg); break;
|
||||
case "SingleStep" : this.singleStep (msg); break;
|
||||
case "Write" : this.write (msg); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,6 +86,12 @@
|
|||
postMessage(msg, msg.buffer);
|
||||
}
|
||||
|
||||
// Attempt to advance to the next instruction
|
||||
runNext(msg) {
|
||||
this.core.RunNext(msg.sim);
|
||||
postMessage(msg);
|
||||
}
|
||||
|
||||
// Specify a new value for a register
|
||||
setRegister(msg) {
|
||||
switch (msg.type) {
|
||||
|
@ -106,6 +115,21 @@
|
|||
buffer.data[x] = rom[x];
|
||||
msg.success = !!this.core.SetROM(msg.sim, buffer.pointer, rom.length);
|
||||
delete msg.rom;
|
||||
if (msg.reset)
|
||||
this.core.Reset(msg.sim);
|
||||
postMessage(msg);
|
||||
}
|
||||
|
||||
// Execute the current instruction
|
||||
singleStep(msg) {
|
||||
this.core.SingleStep(msg.sim);
|
||||
postMessage(msg);
|
||||
}
|
||||
|
||||
// Write a single value into the bus
|
||||
write(msg) {
|
||||
this.core.Write(msg.sim, msg.address, msg.type, msg.value,
|
||||
msg.debug ? 1 : 0);
|
||||
postMessage(msg);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
--control-shadow : #999999;
|
||||
--control-text : #cccccc;
|
||||
--desktop : #111111;
|
||||
--selected : #008542;
|
||||
--selectedBlur : #57665d;
|
||||
--selectedText : #ffffff;
|
||||
--selectedTextBlur : #ffffff;
|
||||
--splitter-focus : #0099ff99;
|
||||
--title : #007ACC;
|
||||
--title-blur : #555555;
|
||||
|
|
|
@ -363,6 +363,25 @@ input[type="text"] {
|
|||
margin-left: calc(var(--font-size) / 2);
|
||||
}
|
||||
|
||||
[role="dialog"][window="memory"] [name="hex"] [name="byte"][selected] {
|
||||
background: var(--selected);
|
||||
box-shadow:
|
||||
-0.5px 0.5px 0 0.5px var(--selected),
|
||||
0.5px 0.5px 0 0.5px var(--selected)
|
||||
;
|
||||
color : var(--selectedText);
|
||||
}
|
||||
|
||||
[role="dialog"][window="memory"][focus="false"]
|
||||
[name="hex"] [name="byte"][selected] {
|
||||
background: var(--selectedBlur);
|
||||
box-shadow:
|
||||
-0.5px 0.5px 0 0.5px var(--selectedBlur),
|
||||
0.5px 0.5px 0 0.5px var(--selectedBlur)
|
||||
;
|
||||
color : var(--selectedTextBlur);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -507,3 +526,16 @@ input[type="text"] {
|
|||
[role="dialog"][window="cpu"] [name="disassembler"] [name="row"] {
|
||||
column-gap: calc(var(--font-size) * 1.5);
|
||||
}
|
||||
|
||||
[role="dialog"][window="cpu"] [name="disassembler"] [name="row"][pc] {
|
||||
background: var(--selected);
|
||||
box-shadow: 0 1px 0 var(--selected);
|
||||
color : var(--selectedText);
|
||||
}
|
||||
|
||||
[role="dialog"][window="cpu"][focus="false"]
|
||||
[name="disassembler"] [name="row"][pc] {
|
||||
background: var(--selectedBlur);
|
||||
box-shadow: 0 1px 0 var(--selectedBlur);
|
||||
color : var(--selectedTextBlur);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
--control-shadow : #999999;
|
||||
--control-text : #000000;
|
||||
--desktop : #cccccc;
|
||||
--selected : #008542;
|
||||
--selectedBlur : #57665d;
|
||||
--selectedText : #ffffff;
|
||||
--selectedTextBlur : #ffffff;
|
||||
--splitter-focus : #0099ff99;
|
||||
--title : #80ccff;
|
||||
--title-blur : #cccccc;
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
--control-shadow : #aa0000;
|
||||
--control-text : #ff0000;
|
||||
--desktop : #000000;
|
||||
--selected : #ff0000;
|
||||
--selectedBlur : #aa0000;
|
||||
--selectedText : #550000;
|
||||
--selectedTextBlur : #000000;
|
||||
--splitter-focus : #ff000099;
|
||||
--title : #550000;
|
||||
--title-blur : #000000;
|
||||
|
|
|
@ -207,8 +207,10 @@
|
|||
///////////////////////////// Public Methods //////////////////////////////
|
||||
|
||||
// Update the display with current emulation data
|
||||
refresh() {
|
||||
this.refreshDasm();
|
||||
refresh(seekToPC, dasm, regs) {
|
||||
if (dasm || dasm === undefined)
|
||||
this.refreshDasm(this.address, 0, !!seekToPC);
|
||||
if (regs || regs === undefined)
|
||||
this.refreshRegs();
|
||||
}
|
||||
|
||||
|
@ -231,6 +233,8 @@
|
|||
case "GetRegisters": this.getRegisters(msg); break;
|
||||
case "ReadBuffer" : this.readBuffer (msg); break;
|
||||
case "SetRegister" : this.setRegister (msg); break;
|
||||
case "RunNext": case "SingleStep":
|
||||
this.refresh(true); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,14 +282,38 @@
|
|||
new Uint8Array(msg.buffer), 0, msg.address, msg.target,
|
||||
msg.pc, msg.line, lines);
|
||||
|
||||
// Ensure PC is visible if requested
|
||||
let reseeking = false;
|
||||
if (msg.seekToPC) {
|
||||
let visible = this.lines(true);
|
||||
let count = Math.min(msg.lines, visible);
|
||||
let x;
|
||||
|
||||
// Ensure PC is visible in the disassembly
|
||||
for (x = 0; x < count; x++)
|
||||
if (dasm[x].address == msg.pc)
|
||||
break;
|
||||
|
||||
// Seek to display PC in the view
|
||||
if (x == count) {
|
||||
reseeking = true;
|
||||
this.seek(msg.pc, Math.floor(visible / 3));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Not seeking to PC
|
||||
if (!reseeking) {
|
||||
|
||||
// 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);
|
||||
this.rows[x].update(dasm[x], this.columns, msg.pc);
|
||||
for (let x = 0; x < lines; x++)
|
||||
this.rows[x].setWidths(this.columns);
|
||||
}
|
||||
|
||||
// Check for pending display updates
|
||||
let address = this.pendingDasm.address === null ?
|
||||
|
@ -310,6 +338,7 @@
|
|||
setRegister(msg) {
|
||||
(msg.type == "program" ? this.proRegs : this.sysRegs)
|
||||
.registers[msg.id].setValue(msg.value);
|
||||
this.refreshDasm(this.address, 0);
|
||||
}
|
||||
|
||||
|
||||
|
@ -383,11 +412,45 @@
|
|||
|
||||
// Processing by key
|
||||
else switch (e.key) {
|
||||
case "ArrowDown": this.scroll( 1 ); break;
|
||||
case "ArrowUp" : this.scroll(-1 ); break;
|
||||
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;
|
||||
default: return;
|
||||
}
|
||||
|
||||
// Configure event
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
// Key down event handler
|
||||
onkeydown(e) {
|
||||
|
||||
// Processing by key
|
||||
switch (e.key) {
|
||||
|
||||
// Run next
|
||||
case "F10":
|
||||
this.debug.core.postMessage({
|
||||
command : "RunNext",
|
||||
dbgwnd : "CPU",
|
||||
sim : this.debug.sim,
|
||||
seekToPC: true
|
||||
});
|
||||
break;
|
||||
|
||||
// Single step
|
||||
case "F11":
|
||||
this.debug.core.postMessage({
|
||||
command : "SingleStep",
|
||||
dbgwnd : "CPU",
|
||||
sim : this.debug.sim,
|
||||
seekToPC: true
|
||||
});
|
||||
break;
|
||||
|
||||
default: return super.onkeydown(e);
|
||||
}
|
||||
|
||||
// Configure event
|
||||
|
@ -429,7 +492,7 @@
|
|||
}
|
||||
|
||||
// Update the disassembler with current emulation data
|
||||
refreshDasm(address, line) {
|
||||
refreshDasm(address, line, seekToPC) {
|
||||
|
||||
// Do nothing while closed or already waiting to refresh
|
||||
if (!this.isVisible() || this.pendingDasm.mode != null)
|
||||
|
@ -450,13 +513,14 @@
|
|||
|
||||
// Request bus data from the WebAssembly core
|
||||
this.debug.core.postMessage({
|
||||
command: "ReadBuffer",
|
||||
command : "ReadBuffer",
|
||||
sim : this.debug.sim,
|
||||
debug : "CPU",
|
||||
address: (address + start * 4 & 0xFFFFFFFE) >>> 0,
|
||||
dbgwnd : "CPU",
|
||||
address : (address + start * 4 & 0xFFFFFFFE) >>> 0,
|
||||
line : line,
|
||||
lines : lines,
|
||||
target : address,
|
||||
seekToPC: seekToPC,
|
||||
size : (end - start + 1) * 4
|
||||
});
|
||||
}
|
||||
|
@ -480,7 +544,7 @@
|
|||
// Request bus data from the WebAssembly core
|
||||
this.debug.core.postMessage({
|
||||
command: "GetRegisters",
|
||||
debug : "CPU",
|
||||
dbgwnd : "CPU",
|
||||
sim : this.debug.sim
|
||||
});
|
||||
}
|
||||
|
@ -580,7 +644,11 @@ CPUWindow.Row = class Row extends Toolkit.Panel {
|
|||
}
|
||||
|
||||
// Update the output labels with emulation state content
|
||||
update(line, columns) {
|
||||
update(line, columns, pc) {
|
||||
if (pc == line.address)
|
||||
this.element.setAttribute("pc", "");
|
||||
else this.element.removeAttribute("pc");
|
||||
|
||||
this.address.setText(
|
||||
("0000000" + line.address.toString(16).toUpperCase()).slice(-8));
|
||||
|
||||
|
|
|
@ -10,8 +10,10 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
// Configure instance fields
|
||||
this.address = 0x05000000;
|
||||
this.debug = debug;
|
||||
this.editDigit = null;
|
||||
this.pending = { mode: null };
|
||||
this.rows = [];
|
||||
this.selected = this.address;
|
||||
|
||||
// Configure element
|
||||
this.element.setAttribute("window", "memory");
|
||||
|
@ -46,7 +48,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
|
||||
// Configure properties
|
||||
this.setProperty("sim", "");
|
||||
this.rows.push(this.hex.add(new MemoryWindow.Row(this.hex)));
|
||||
this.rows.push(this.hex.add(new MemoryWindow.Row(this, this.hex, 0)));
|
||||
this.application.addComponent(this);
|
||||
}
|
||||
|
||||
|
@ -74,7 +76,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
this.debug.core.postMessage({
|
||||
command: "ReadBuffer",
|
||||
sim : this.debug.sim,
|
||||
debug : "Memory",
|
||||
dbgwnd : "Memory",
|
||||
address: address,
|
||||
lines : lines,
|
||||
size : lines * 16
|
||||
|
@ -98,6 +100,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
message(msg) {
|
||||
switch (msg.command) {
|
||||
case "ReadBuffer": this.readBuffer(msg); break;
|
||||
case "Write" : this.debug.refresh(); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +117,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
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, buffer, offset);
|
||||
) this.rows[x].update(buffer, offset);
|
||||
|
||||
// Check for pending display updates
|
||||
let address = this.pending.address === null ?
|
||||
|
@ -139,6 +142,19 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
|
||||
///////////////////////////// Private Methods /////////////////////////////
|
||||
|
||||
// Write the current edited value to the bus
|
||||
commit(value) {
|
||||
this.editDigit = null;
|
||||
this.debug.core.postMessage({
|
||||
command: "Write",
|
||||
sim : this.debug.sim,
|
||||
dbgwnd : "Memory",
|
||||
address: this.selected,
|
||||
type : 0,
|
||||
value : value
|
||||
});
|
||||
}
|
||||
|
||||
// The window is being displayed for the first time
|
||||
firstShow() {
|
||||
super.firstShow();
|
||||
|
@ -147,7 +163,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
|
||||
// Determine the height in pixels of one row of output
|
||||
lineHeight() {
|
||||
return Math.max(10, this.rows[0].address.getBounds().height);
|
||||
return Math.max(10, this.rows[0].addr.getBounds().height);
|
||||
}
|
||||
|
||||
// Determine the number of rows of output
|
||||
|
@ -159,32 +175,81 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
return Math.max(1, ret);
|
||||
}
|
||||
|
||||
// Focus lost event capture
|
||||
onblur(e) {
|
||||
super.onblur(e);
|
||||
if (this.editDigit !== null && !this.contains(e.relatedTarget))
|
||||
this.commit(this.editDigit);
|
||||
}
|
||||
|
||||
// Key down event handler
|
||||
onkeydown(e) {
|
||||
let change = null;
|
||||
let digit = null;
|
||||
|
||||
// Processing by key
|
||||
switch (e.key) {
|
||||
case "ArrowDown" : change = 16 ; break;
|
||||
case "ArrowLeft" : change = - 1 ; break;
|
||||
case "ArrowRight": change = 1 ; break;
|
||||
case "ArrowUp" : change = -16 ; break;
|
||||
case "PageDown" : change = visible; break;
|
||||
case "PageUp" : change = -visible; break;
|
||||
case "0": case "1": case "2": case "3": case "4":
|
||||
case "5": case "6": case "7": case "8": case "9":
|
||||
digit = e.key.codePointAt(0) - "0".codePointAt(0);
|
||||
break;
|
||||
case "a": case "b": case "c": case "d": case "e": case "f":
|
||||
digit = e.key.codePointAt(0) - "a".codePointAt(0) + 10;
|
||||
break;
|
||||
case "A": case "B": case "C": case "D": case "E": case "F":
|
||||
digit = e.key.codePointAt(0) - "A".codePointAt(0) + 10;
|
||||
break;
|
||||
default: return super.onkeydown(e);
|
||||
}
|
||||
|
||||
// Moving the selection
|
||||
if (change !== null) {
|
||||
if (this.editDigit !== null)
|
||||
this.commit(this.editDigit);
|
||||
this.setSelected((this.selected + change & 0xFFFFFFFF) >>> 0);
|
||||
}
|
||||
|
||||
// Entering a digit
|
||||
if (digit !== null) {
|
||||
let selected = this.selected;
|
||||
if (this.editDigit !== null) {
|
||||
this.commit(this.editDigit << 4 | digit);
|
||||
selected++;
|
||||
} else this.editDigit = digit;
|
||||
if (!this.setSelected(selected))
|
||||
for (let row of this.rows)
|
||||
row.update();
|
||||
}
|
||||
|
||||
// Configure event
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
// Key down event handler
|
||||
onkeyhex(e) {
|
||||
|
||||
// Control is pressed
|
||||
if (e.ctrlKey) switch (e.key) {
|
||||
// Control is not pressed
|
||||
if (!e.ctrlKey)
|
||||
return;
|
||||
|
||||
// Processing by key
|
||||
switch (e.key) {
|
||||
case "g": case "G":
|
||||
let addr = prompt(this.application.translate("{app.goto_}"));
|
||||
if (addr === null)
|
||||
break;
|
||||
this.seek(
|
||||
(parseInt(addr, 16) & 0xFFFFFFF0) >>> 0,
|
||||
Math.floor(this.lines(true) / 3)
|
||||
);
|
||||
this.setSelected((parseInt(addr, 16) & 0xFFFFFFFF) >>> 0);
|
||||
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();
|
||||
|
@ -194,7 +259,8 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
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));
|
||||
this.rows[y] =
|
||||
this.hex.add(new MemoryWindow.Row(this, this.hex, y * 16));
|
||||
for (let y = lines; y < this.rows.length; y++)
|
||||
this.hex.remove(this.rows[y]);
|
||||
if (this.rows.length > lines)
|
||||
|
@ -255,13 +321,49 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
|
|||
}
|
||||
}
|
||||
|
||||
// Specify which byte value is selected
|
||||
setSelected(selected) {
|
||||
|
||||
// The selected cell is not changing
|
||||
if (selected == this.selected)
|
||||
return false;
|
||||
|
||||
// An edit was in progress
|
||||
if (this.editDigit !== null)
|
||||
this.commit(this.editDigit);
|
||||
|
||||
// Working variables
|
||||
let pos = (selected - this.address & 0xFFFFFFFF) >>> 0;
|
||||
let visible = this.lines(true) * 16;
|
||||
|
||||
// The selected cell is visible
|
||||
if (pos >= 0 && pos < visible) {
|
||||
this.selected = selected;
|
||||
for (let y = 0; y < this.rows.length; y++)
|
||||
this.rows[y].checkSelected();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Working variables
|
||||
let down = (selected - this.address & 0xFFFFFFF0) >>> 0;
|
||||
let up = (this.address - selected + 15 & 0xFFFFFFF0) >>> 0;
|
||||
|
||||
// Seek to show the new selection in the view
|
||||
this.selected = selected;
|
||||
if (down <= up) {
|
||||
this.seek((this.address + down & 0xFFFFFFFF) >>> 0,
|
||||
visible / 16 - 1);
|
||||
} else this.seek((this.address - up & 0xFFFFFFFF) >>> 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// One row of output
|
||||
MemoryWindow.Row = class Row extends Toolkit.Panel {
|
||||
|
||||
// Object constructor
|
||||
constructor(parent) {
|
||||
constructor(wnd, parent, offset) {
|
||||
super(parent.application, {
|
||||
layout : "grid",
|
||||
columns : "repeat(17, max-content)",
|
||||
|
@ -271,37 +373,109 @@ MemoryWindow.Row = class Row extends Toolkit.Panel {
|
|||
});
|
||||
|
||||
// Configure instance fields
|
||||
this.bytes = new Array(16);
|
||||
this.cells = new Array(16);
|
||||
this.offset = offset;
|
||||
this.wnd = wnd;
|
||||
|
||||
// Configure element
|
||||
this.element.setAttribute("role", "row");
|
||||
|
||||
// Address label
|
||||
this.address = this.add(parent.newLabel({ text: "\u00a0" }));
|
||||
this.address.element.setAttribute("role", "gridcell");
|
||||
this.address.element.setAttribute("name", "address");
|
||||
this.addr = this.add(parent.newLabel({ text: "\u00a0" }));
|
||||
this.addr.element.setAttribute("role", "gridcell");
|
||||
this.addr.element.setAttribute("name", "address");
|
||||
|
||||
// Byte labels
|
||||
for (let x = 0; x < 16; x++) {
|
||||
let lbl = this.bytes[x] =
|
||||
this.add(parent.newLabel({ text: "\u00a0" }));
|
||||
lbl.element.setAttribute("role", "gridcell");
|
||||
lbl.element.setAttribute("name", "byte");
|
||||
}
|
||||
|
||||
for (let x = 0; x < 16; x++)
|
||||
this.cells[x] = new MemoryWindow.Cell(wnd, this, offset + x);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Package Methods /////////////////////////////
|
||||
|
||||
// Check whether any byte label is the selected byte
|
||||
checkSelected() {
|
||||
for (let cell of this.cells)
|
||||
cell.checkSelected();
|
||||
}
|
||||
|
||||
// Update the output labels with emulation state content
|
||||
update(address, bytes, offset) {
|
||||
this.address.setText(
|
||||
("0000000" + address.toString(16).toUpperCase()).slice(-8));
|
||||
for (let x = 0; x < 16; x++, offset++)
|
||||
this.bytes[x].setText(
|
||||
("0" + bytes[offset].toString(16).toUpperCase()).slice(-2));
|
||||
update(bytes, offset) {
|
||||
this.addr.setText(
|
||||
("0000000" + this.address().toString(16).toUpperCase()).slice(-8));
|
||||
for (let cell of this.cells)
|
||||
cell.update(bytes ? bytes[offset++] : cell.value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Private Methods /////////////////////////////
|
||||
|
||||
// Compute the current address of the row
|
||||
address() {
|
||||
return (this.wnd.address + this.offset & 0xFFFFFFFF) >>> 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// One cell of output
|
||||
MemoryWindow.Cell = class Cell extends Toolkit.Label {
|
||||
|
||||
// Object constructor
|
||||
constructor(wnd, parent, offset) {
|
||||
super(wnd.application, { text: "\u00a0" });
|
||||
|
||||
// Configure instance fields
|
||||
this.offset = offset;
|
||||
this.wnd = wnd;
|
||||
this.value = 0x00;
|
||||
|
||||
// Configure element
|
||||
this.element.setAttribute("role", "gridcell");
|
||||
this.element.setAttribute("name", "byte");
|
||||
this.element.addEventListener("pointerdown", e=>this.onpointerdown(e));
|
||||
|
||||
parent.add(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Package Methods /////////////////////////////
|
||||
|
||||
// Check whether this cell is the selected cell
|
||||
checkSelected() {
|
||||
let selected = this.address() == this.wnd.selected;
|
||||
if (selected)
|
||||
this.element.setAttribute("selected", "");
|
||||
else this.element.removeAttribute("selected");
|
||||
return selected;
|
||||
}
|
||||
|
||||
// Update the output with emulation state content
|
||||
update(value) {
|
||||
if (value === undefined)
|
||||
value = this.value;
|
||||
else this.value = value;
|
||||
if (this.checkSelected() && this.wnd.editDigit !== null) {
|
||||
this.setText("\u00a0" +
|
||||
this.wnd.editDigit.toString(16).toUpperCase());
|
||||
} else this.setText(("0"+value.toString(16).toUpperCase()).slice(-2));
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////// Private Methods /////////////////////////////
|
||||
|
||||
// Compute the current address of the cell
|
||||
address() {
|
||||
return (this.wnd.address + this.offset & 0xFFFFFFFF) >>> 0;
|
||||
}
|
||||
|
||||
// Pointer down event handler
|
||||
onpointerdown(e) {
|
||||
if (e.button == 0)
|
||||
this.wnd.setSelected(this.address());
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -96,7 +96,7 @@ static void busWrite(
|
|||
address &= ~((uint32_t) TYPE_SIZES[type] - 1);
|
||||
|
||||
/* Process by address range */
|
||||
switch (address >> 24 ^ 7) {
|
||||
switch (address >> 24 & 7) {
|
||||
case 0 : return; /* VIP */
|
||||
case 1 : return; /* VSU */
|
||||
case 2 : return; /* Miscellaneous hardware */
|
||||
|
|
40
core/cpu.c
40
core/cpu.c
|
@ -756,15 +756,15 @@ static void cpuAND(VB *emu, VB_INSTRUCTION *inst) {
|
|||
/* And Bit String Upward */
|
||||
#define cpuANDBSU(emu, inst) cpuBitString(emu, inst)
|
||||
|
||||
/* And Not Bit String Upward */
|
||||
#define cpuANDNBSU(emu, inst) cpuBitString(emu, inst)
|
||||
|
||||
/* And Immediate */
|
||||
static void cpuANDI(VB *emu, VB_INSTRUCTION *inst) {
|
||||
emu->cpu.program[inst->bits[0] >> 5 & 0x1F] = cpuBitwise(emu,
|
||||
emu->cpu.program[inst->bits[0] & 0x1F] & inst->bits[1]);
|
||||
}
|
||||
|
||||
/* And Not Bit String Upward */
|
||||
#define cpuANDNBSU(emu, inst) cpuBitString(emu, inst)
|
||||
|
||||
/* Conditional Branch */
|
||||
static void cpuBCOND(VB *emu, VB_INSTRUCTION *inst) {
|
||||
int32_t disp; /* Target address displacement */
|
||||
|
@ -1124,15 +1124,15 @@ static void cpuOR(VB *emu, VB_INSTRUCTION *inst) {
|
|||
/* Or Bit String Upward */
|
||||
#define cpuORBSU(emu, inst) cpuBitString(emu, inst)
|
||||
|
||||
/* Or Not Bit String Upward */
|
||||
#define cpuORNBSU(emu, inst) cpuBitString(emu, inst)
|
||||
|
||||
/* Or Immediate */
|
||||
static void cpuORI(VB *emu, VB_INSTRUCTION *inst) {
|
||||
emu->cpu.program[inst->bits[0] >> 5 & 0x1F] = cpuBitwise(emu,
|
||||
emu->cpu.program[inst->bits[0] & 0x1F] | inst->bits[1]);
|
||||
}
|
||||
|
||||
/* Or Not Bit String Upward */
|
||||
#define cpuORNBSU(emu, inst) cpuBitString(emu, inst)
|
||||
|
||||
/* Output Byte */
|
||||
#define cpuOUT_B(emu, inst) cpuStore(emu, inst, VB_U8, 4)
|
||||
|
||||
|
@ -1334,15 +1334,15 @@ static void cpuXOR(VB *emu, VB_INSTRUCTION *inst) {
|
|||
/* Exclusive Or Bit String Upward */
|
||||
#define cpuXORBSU(emu, inst) cpuBitString(emu, inst)
|
||||
|
||||
/* Exclusive Or Not Bit String Upward */
|
||||
#define cpuXORNBSU(emu, inst) cpuBitString(emu, inst)
|
||||
|
||||
/* Exclusive Or Immediate */
|
||||
static void cpuXORI(VB *emu, VB_INSTRUCTION *inst) {
|
||||
emu->cpu.program[inst->bits[0] >> 5 & 0x1F] = cpuBitwise(emu,
|
||||
emu->cpu.program[inst->bits[0] & 0x1F] ^ inst->bits[1]);
|
||||
}
|
||||
|
||||
/* Exclusive Or Not Bit String Upward */
|
||||
#define cpuXORNBSU(emu, inst) cpuBitString(emu, inst)
|
||||
|
||||
|
||||
|
||||
/***************************** Module Functions ******************************/
|
||||
|
@ -1505,14 +1505,14 @@ static int cpuExecute(VB *emu) {
|
|||
|
||||
/* Enter an exception state */
|
||||
static int cpuException(VB *emu) {
|
||||
uint16_t causeCode = emu->cpu.causeCode;
|
||||
|
||||
/* Fatal exception */
|
||||
if (emu->cpu.psw.np) {
|
||||
|
||||
/* Write the cause code for debugging */
|
||||
if (emu->cpu.busWait == 0) {
|
||||
if (cpuWrite(emu, 0x00000000, VB_S32,
|
||||
0xFFFF0000 | emu->cpu.causeCode))
|
||||
if (cpuWrite(emu, 0x00000000, VB_S32, 0xFFFF0000 | causeCode))
|
||||
return 1;
|
||||
|
||||
/* Update state */
|
||||
|
@ -1562,7 +1562,7 @@ static int cpuException(VB *emu) {
|
|||
|
||||
/* Duplexed exception */
|
||||
if (emu->cpu.psw.ep) {
|
||||
emu->cpu.ecr.fecc = emu->cpu.causeCode;
|
||||
emu->cpu.ecr.fecc = causeCode;
|
||||
emu->cpu.fepsw = vbGetSystemRegister(emu, VB_PSW);
|
||||
emu->cpu.fepc = emu->cpu.pc;
|
||||
emu->cpu.fepcFrom = emu->cpu.pcFrom;
|
||||
|
@ -1573,17 +1573,19 @@ static int cpuException(VB *emu) {
|
|||
|
||||
/* Exception or interrupt */
|
||||
else {
|
||||
emu->cpu.ecr.eicc = emu->cpu.causeCode;
|
||||
emu->cpu.ecr.eicc = causeCode;
|
||||
emu->cpu.eipsw = vbGetSystemRegister(emu, VB_PSW);
|
||||
emu->cpu.eipc = emu->cpu.pc;
|
||||
emu->cpu.eipcFrom = emu->cpu.pcFrom;
|
||||
emu->cpu.eipcTo = emu->cpu.pcTo;
|
||||
emu->cpu.psw.ep = 1;
|
||||
emu->cpu.pc = (emu->cpu.causeCode & 0x0040) != 0 ?
|
||||
0xFFFFFF60 : ((uint32_t) 0xFFFF0000 | emu->cpu.causeCode);
|
||||
emu->cpu.pc = (causeCode & 0x0040) != 0 ?
|
||||
0xFFFFFF60 : ((uint32_t) 0xFFFF0000 | causeCode);
|
||||
}
|
||||
|
||||
/* Interrupt */
|
||||
if (emu->cpu.causeCode < 0xFF00)
|
||||
emu->cpu.psw.i = emu->cpu.psw.i == 15 ? 15 : emu->cpu.psw.i + 1;
|
||||
if (causeCode < 0xFF00)
|
||||
emu->cpu.psw.i += emu->cpu.psw.i == 15 ? 0 : 1;
|
||||
|
||||
/* Update state */
|
||||
emu->cpu.causeCode = 0;
|
||||
|
@ -1593,7 +1595,9 @@ static int cpuException(VB *emu) {
|
|||
emu->cpu.pcFrom = emu->cpu.pc;
|
||||
emu->cpu.pcTo = emu->cpu.pc;
|
||||
/* emu->cpu.clocks = ? */
|
||||
return 0;
|
||||
|
||||
/* Call the breakpoint handler if available */
|
||||
return emu->onException != NULL && emu->onException(emu, causeCode);
|
||||
}
|
||||
|
||||
/* Perform instruction fetch operations */
|
||||
|
|
|
@ -16,7 +16,9 @@ static const uint8_t TYPE_SIZES[] = { 1, 1, 2, 2, 4 };
|
|||
/********************************** Macros ***********************************/
|
||||
|
||||
/* Sign-extend a value of some number of bits to 32 bits */
|
||||
#define SignExtend(v,b) ((v) | (((v) & ((1<<(b)) - 1)) ? ~(int32_t)0<<(b) : 0))
|
||||
#define SignExtend(v,b) \
|
||||
((v) | (((v) & (1 << ((b) - 1))) ? (uint32_t) 0xFFFFFFFF << (b) : 0))
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -220,7 +222,9 @@ void vbReset(VB *emu) {
|
|||
emu->cpu.pcTo = 0xFFFFFFF0;
|
||||
|
||||
/* Other CPU state */
|
||||
emu->cpu.busWait = 0;
|
||||
emu->cpu.state = CPU_FETCH;
|
||||
emu->cpu.substring = 0;
|
||||
for (x = 0; x < 5; x++)
|
||||
emu->cpu.irq[x] = 0;
|
||||
|
||||
|
@ -277,6 +281,6 @@ uint32_t vbSetSystemRegister(VB *emu, int id, uint32_t value) {
|
|||
|
||||
/* Write a data unit to the bus */
|
||||
void vbWrite(VB *emu, uint32_t address, int type, int32_t value, int debug) {
|
||||
if (type < 0 || type >= (int32_t) sizeof TYPE_SIZES)
|
||||
if (type >= 0 && type < (int32_t) sizeof TYPE_SIZES)
|
||||
busWrite(emu, address, type, value, debug);
|
||||
}
|
||||
|
|
2
makefile
2
makefile
|
@ -1,7 +1,7 @@
|
|||
.PHONY: help
|
||||
help:
|
||||
@echo
|
||||
@echo "Virtual Boy Emulator - September 18, 2021"
|
||||
@echo "Virtual Boy Emulator - September 19, 2021"
|
||||
@echo
|
||||
@echo "Target build environment is any Debian with the following packages:"
|
||||
@echo " emscripten"
|
||||
|
|
39
wasm/wasm.c
39
wasm/wasm.c
|
@ -31,6 +31,45 @@ EMSCRIPTEN_KEEPALIVE void ReadBuffer(
|
|||
*dest = vbRead(&sims[sim], address, VB_U8, debug);
|
||||
}
|
||||
|
||||
// Execute one instruction
|
||||
static uint32_t SingleStepPC;
|
||||
static int SingleStepProc(VB *emu, int fetch, VB_ACCESS *acc) {
|
||||
if (fetch == 0 && vbGetProgramCounter(emu, VB_PC) != SingleStepPC)
|
||||
return 1;
|
||||
acc->value = vbRead(emu, acc->address, acc->type, 0);
|
||||
return 0;
|
||||
}
|
||||
EMSCRIPTEN_KEEPALIVE void SingleStep(int sim) {
|
||||
uint32_t clocks = 400000; // 1/50s
|
||||
VB *emu = &sims[sim];
|
||||
SingleStepPC = vbGetProgramCounter(emu, VB_PC);
|
||||
emu->onFetch = &SingleStepProc;
|
||||
vbEmulate(emu, NULL, &clocks);
|
||||
emu->onFetch = NULL;
|
||||
}
|
||||
|
||||
// Attempt to execute until the following instruction
|
||||
static uint32_t RunNextPC;
|
||||
static int RunNextProcB(VB *emu, int fetch, VB_ACCESS *acc) {
|
||||
if (fetch == 0 && vbGetProgramCounter(emu, VB_PC) == RunNextPC)
|
||||
return 1;
|
||||
acc->value = vbRead(emu, acc->address, acc->type, 0);
|
||||
return 0;
|
||||
}
|
||||
static int RunNextProcA(VB *emu, VB_INSTRUCTION *inst) {
|
||||
RunNextPC = vbGetProgramCounter(emu, VB_PC) + inst->size;
|
||||
emu->onExecute = NULL;
|
||||
emu->onFetch = &RunNextProcB;
|
||||
return 0;
|
||||
}
|
||||
EMSCRIPTEN_KEEPALIVE void RunNext(int sim) {
|
||||
uint32_t clocks = 400000; // 1/50s
|
||||
VB *emu = &sims[sim];
|
||||
emu->onExecute = &RunNextProcA;
|
||||
vbEmulate(emu, NULL, &clocks);
|
||||
emu->onFetch = NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////// Core Commands ////////////////////////////////
|
||||
|
|
Loading…
Reference in New Issue