Enabling CPU trace and hex edit

This commit is contained in:
Guy Perfect 2021-09-19 19:36:30 +00:00
parent 94486ecf02
commit 573f72e46f
14 changed files with 449 additions and 91 deletions

View File

@ -109,7 +109,7 @@ globalThis.App = class App {
// Message received // Message received
onmessage(msg) { onmessage(msg) {
if ("debug" in msg) { if ("dbgwnd" in msg) {
this.debuggers[msg.sim].message(msg); this.debuggers[msg.sim].message(msg);
return; return;
} }
@ -164,6 +164,7 @@ globalThis.App = class App {
// Send the ROM to the WebAssembly core module // Send the ROM to the WebAssembly core module
this.core.postMessage({ this.core.postMessage({
command: "SetROM", command: "SetROM",
reset : true,
rom : file, rom : file,
sim : 0 sim : 0
}, file); }, file);

View File

@ -39,7 +39,7 @@ globalThis.Debugger = class Debugger {
// Message received from emulation thread // Message received from emulation thread
message(msg) { message(msg) {
switch (msg.debug) { switch (msg.dbgwnd) {
case "CPU" : this.cpu .message(msg); break; case "CPU" : this.cpu .message(msg); break;
case "Memory": this.memory.message(msg); break; case "Memory": this.memory.message(msg); break;
} }

View File

@ -28,8 +28,11 @@
case "GetRegisters": this.getRegisters(msg); break; case "GetRegisters": this.getRegisters(msg); break;
case "Init" : this.init (msg); break; case "Init" : this.init (msg); break;
case "ReadBuffer" : this.readBuffer (msg); break; case "ReadBuffer" : this.readBuffer (msg); break;
case "RunNext" : this.runNext (msg); break;
case "SetRegister" : this.setRegister (msg); break; case "SetRegister" : this.setRegister (msg); break;
case "SetROM" : this.setROM (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); 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 // Specify a new value for a register
setRegister(msg) { setRegister(msg) {
switch (msg.type) { switch (msg.type) {
@ -106,6 +115,21 @@
buffer.data[x] = rom[x]; buffer.data[x] = rom[x];
msg.success = !!this.core.SetROM(msg.sim, buffer.pointer, rom.length); msg.success = !!this.core.SetROM(msg.sim, buffer.pointer, rom.length);
delete msg.rom; 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); postMessage(msg);
} }

View File

@ -11,6 +11,10 @@
--control-shadow : #999999; --control-shadow : #999999;
--control-text : #cccccc; --control-text : #cccccc;
--desktop : #111111; --desktop : #111111;
--selected : #008542;
--selectedBlur : #57665d;
--selectedText : #ffffff;
--selectedTextBlur : #ffffff;
--splitter-focus : #0099ff99; --splitter-focus : #0099ff99;
--title : #007ACC; --title : #007ACC;
--title-blur : #555555; --title-blur : #555555;

View File

@ -363,6 +363,25 @@ input[type="text"] {
margin-left: calc(var(--font-size) / 2); 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"] { [role="dialog"][window="cpu"] [name="disassembler"] [name="row"] {
column-gap: calc(var(--font-size) * 1.5); 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);
}

View File

@ -11,6 +11,10 @@
--control-shadow : #999999; --control-shadow : #999999;
--control-text : #000000; --control-text : #000000;
--desktop : #cccccc; --desktop : #cccccc;
--selected : #008542;
--selectedBlur : #57665d;
--selectedText : #ffffff;
--selectedTextBlur : #ffffff;
--splitter-focus : #0099ff99; --splitter-focus : #0099ff99;
--title : #80ccff; --title : #80ccff;
--title-blur : #cccccc; --title-blur : #cccccc;

View File

@ -11,6 +11,10 @@
--control-shadow : #aa0000; --control-shadow : #aa0000;
--control-text : #ff0000; --control-text : #ff0000;
--desktop : #000000; --desktop : #000000;
--selected : #ff0000;
--selectedBlur : #aa0000;
--selectedText : #550000;
--selectedTextBlur : #000000;
--splitter-focus : #ff000099; --splitter-focus : #ff000099;
--title : #550000; --title : #550000;
--title-blur : #000000; --title-blur : #000000;

View File

@ -207,9 +207,11 @@
///////////////////////////// Public Methods ////////////////////////////// ///////////////////////////// Public Methods //////////////////////////////
// Update the display with current emulation data // Update the display with current emulation data
refresh() { refresh(seekToPC, dasm, regs) {
this.refreshDasm(); if (dasm || dasm === undefined)
this.refreshRegs(); this.refreshDasm(this.address, 0, !!seekToPC);
if (regs || regs === undefined)
this.refreshRegs();
} }
// Specify whether the component is visible // Specify whether the component is visible
@ -231,6 +233,8 @@
case "GetRegisters": this.getRegisters(msg); break; case "GetRegisters": this.getRegisters(msg); break;
case "ReadBuffer" : this.readBuffer (msg); break; case "ReadBuffer" : this.readBuffer (msg); break;
case "SetRegister" : this.setRegister (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, new Uint8Array(msg.buffer), 0, msg.address, msg.target,
msg.pc, msg.line, lines); msg.pc, msg.line, lines);
// Configure instance fields // Ensure PC is visible if requested
this.address = dasm[0].address; let reseeking = false;
if (msg.seekToPC) {
let visible = this.lines(true);
let count = Math.min(msg.lines, visible);
let x;
// Configure elements // Ensure PC is visible in the disassembly
for (let x = 0; x < lines; x++) for (x = 0; x < count; x++)
this.rows[x].update(dasm[x], this.columns); if (dasm[x].address == msg.pc)
for (let x = 0; x < lines; x++) break;
this.rows[x].setWidths(this.columns);
// 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, msg.pc);
for (let x = 0; x < lines; x++)
this.rows[x].setWidths(this.columns);
}
// Check for pending display updates // Check for pending display updates
let address = this.pendingDasm.address === null ? let address = this.pendingDasm.address === null ?
@ -310,6 +338,7 @@
setRegister(msg) { setRegister(msg) {
(msg.type == "program" ? this.proRegs : this.sysRegs) (msg.type == "program" ? this.proRegs : this.sysRegs)
.registers[msg.id].setValue(msg.value); .registers[msg.id].setValue(msg.value);
this.refreshDasm(this.address, 0);
} }
@ -383,11 +412,45 @@
// Processing by key // Processing by key
else switch (e.key) { else switch (e.key) {
case "ArrowDown": this.scroll( 1 ); break; case "ArrowDown": this.scroll( 1); break;
case "ArrowUp" : this.scroll(-1 ); break; case "ArrowUp" : this.scroll(-1); break;
case "PageDown" : this.scroll( this.lines(true)); break; case "PageDown" : this.scroll( this.lines(true)); break;
case "PageUp" : 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 // Configure event
@ -429,7 +492,7 @@
} }
// Update the disassembler with current emulation data // Update the disassembler with current emulation data
refreshDasm(address, line) { refreshDasm(address, line, seekToPC) {
// Do nothing while closed or already waiting to refresh // Do nothing while closed or already waiting to refresh
if (!this.isVisible() || this.pendingDasm.mode != null) if (!this.isVisible() || this.pendingDasm.mode != null)
@ -450,14 +513,15 @@
// 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",
sim : this.debug.sim, sim : this.debug.sim,
debug : "CPU", dbgwnd : "CPU",
address: (address + start * 4 & 0xFFFFFFFE) >>> 0, address : (address + start * 4 & 0xFFFFFFFE) >>> 0,
line : line, line : line,
lines : lines, lines : lines,
target : address, target : address,
size : (end - start + 1) * 4 seekToPC: seekToPC,
size : (end - start + 1) * 4
}); });
} }
@ -480,7 +544,7 @@
// Request bus data from the WebAssembly core // Request bus data from the WebAssembly core
this.debug.core.postMessage({ this.debug.core.postMessage({
command: "GetRegisters", command: "GetRegisters",
debug : "CPU", dbgwnd : "CPU",
sim : this.debug.sim sim : this.debug.sim
}); });
} }
@ -580,7 +644,11 @@ CPUWindow.Row = class Row extends Toolkit.Panel {
} }
// Update the output labels with emulation state content // 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( this.address.setText(
("0000000" + line.address.toString(16).toUpperCase()).slice(-8)); ("0000000" + line.address.toString(16).toUpperCase()).slice(-8));

View File

@ -8,10 +8,12 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
super(debug.gui, options); super(debug.gui, options);
// Configure instance fields // Configure instance fields
this.address = 0x05000000; this.address = 0x05000000;
this.debug = debug; this.debug = debug;
this.pending = { mode: null }; this.editDigit = null;
this.rows = []; this.pending = { mode: null };
this.rows = [];
this.selected = this.address;
// Configure element // Configure element
this.element.setAttribute("window", "memory"); this.element.setAttribute("window", "memory");
@ -46,7 +48,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
// Configure properties // Configure properties
this.setProperty("sim", ""); 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); this.application.addComponent(this);
} }
@ -74,7 +76,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
this.debug.core.postMessage({ this.debug.core.postMessage({
command: "ReadBuffer", command: "ReadBuffer",
sim : this.debug.sim, sim : this.debug.sim,
debug : "Memory", dbgwnd : "Memory",
address: address, address: address,
lines : lines, lines : lines,
size : lines * 16 size : lines * 16
@ -98,6 +100,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
message(msg) { message(msg) {
switch (msg.command) { switch (msg.command) {
case "ReadBuffer": this.readBuffer(msg); break; 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; let x = 0, address = msg.address, offset = 0;
x < lines && offset < buffer.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, buffer, offset); ) this.rows[x].update(buffer, offset);
// Check for pending display updates // Check for pending display updates
let address = this.pending.address === null ? let address = this.pending.address === null ?
@ -139,6 +142,19 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
///////////////////////////// Private Methods ///////////////////////////// ///////////////////////////// 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 // The window is being displayed for the first time
firstShow() { firstShow() {
super.firstShow(); super.firstShow();
@ -147,7 +163,7 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
// Determine the height in pixels of one row of output // Determine the height in pixels of one row of output
lineHeight() { 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 // Determine the number of rows of output
@ -158,32 +174,81 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
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);
} }
// 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 // Key down event handler
onkeyhex(e) { onkeyhex(e) {
// Control is pressed // Control is not pressed
if (e.ctrlKey) switch (e.key) { if (!e.ctrlKey)
return;
// Processing by key
switch (e.key) {
case "g": case "G": case "g": case "G":
let addr = prompt(this.application.translate("{app.goto_}")); let addr = prompt(this.application.translate("{app.goto_}"));
if (addr === null) if (addr === null)
break; break;
this.seek( this.setSelected((parseInt(addr, 16) & 0xFFFFFFFF) >>> 0);
(parseInt(addr, 16) & 0xFFFFFFF0) >>> 0,
Math.floor(this.lines(true) / 3)
);
break; break;
default: return; 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 // Configure event
e.preventDefault(); e.preventDefault();
@ -194,7 +259,8 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window {
onresize() { onresize() {
let lines = this.lines(false); let lines = this.lines(false);
for (let y = this.rows.length; y < lines; y++) 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++) for (let y = lines; y < this.rows.length; y++)
this.hex.remove(this.rows[y]); this.hex.remove(this.rows[y]);
if (this.rows.length > lines) 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 // One row of output
MemoryWindow.Row = class Row extends Toolkit.Panel { MemoryWindow.Row = class Row extends Toolkit.Panel {
// Object constructor // Object constructor
constructor(parent) { constructor(wnd, parent, offset) {
super(parent.application, { super(parent.application, {
layout : "grid", layout : "grid",
columns : "repeat(17, max-content)", columns : "repeat(17, max-content)",
@ -271,37 +373,109 @@ MemoryWindow.Row = class Row extends Toolkit.Panel {
}); });
// Configure instance fields // Configure instance fields
this.bytes = new Array(16); this.cells = new Array(16);
this.offset = offset;
this.wnd = wnd;
// Configure element // Configure element
this.element.setAttribute("role", "row"); this.element.setAttribute("role", "row");
// Address label // Address label
this.address = this.add(parent.newLabel({ text: "\u00a0" })); this.addr = this.add(parent.newLabel({ text: "\u00a0" }));
this.address.element.setAttribute("role", "gridcell"); this.addr.element.setAttribute("role", "gridcell");
this.address.element.setAttribute("name", "address"); this.addr.element.setAttribute("name", "address");
// Byte labels // Byte labels
for (let x = 0; x < 16; x++) { for (let x = 0; x < 16; x++)
let lbl = this.bytes[x] = this.cells[x] = new MemoryWindow.Cell(wnd, this, offset + x);
this.add(parent.newLabel({ text: "\u00a0" }));
lbl.element.setAttribute("role", "gridcell");
lbl.element.setAttribute("name", "byte");
}
} }
///////////////////////////// Package Methods ///////////////////////////// ///////////////////////////// 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 the output labels with emulation state content
update(address, bytes, offset) { update(bytes, offset) {
this.address.setText( this.addr.setText(
("0000000" + address.toString(16).toUpperCase()).slice(-8)); ("0000000" + this.address().toString(16).toUpperCase()).slice(-8));
for (let x = 0; x < 16; x++, offset++) for (let cell of this.cells)
this.bytes[x].setText( cell.update(bytes ? bytes[offset++] : cell.value);
("0" + bytes[offset].toString(16).toUpperCase()).slice(-2)); }
///////////////////////////// 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());
} }
}; };

View File

@ -96,7 +96,7 @@ static void busWrite(
address &= ~((uint32_t) TYPE_SIZES[type] - 1); address &= ~((uint32_t) TYPE_SIZES[type] - 1);
/* Process by address range */ /* Process by address range */
switch (address >> 24 ^ 7) { switch (address >> 24 & 7) {
case 0 : return; /* VIP */ case 0 : return; /* VIP */
case 1 : return; /* VSU */ case 1 : return; /* VSU */
case 2 : return; /* Miscellaneous hardware */ case 2 : return; /* Miscellaneous hardware */

View File

@ -756,15 +756,15 @@ static void cpuAND(VB *emu, VB_INSTRUCTION *inst) {
/* And Bit String Upward */ /* And Bit String Upward */
#define cpuANDBSU(emu, inst) cpuBitString(emu, inst) #define cpuANDBSU(emu, inst) cpuBitString(emu, inst)
/* And Not Bit String Upward */
#define cpuANDNBSU(emu, inst) cpuBitString(emu, inst)
/* And Immediate */ /* And Immediate */
static void cpuANDI(VB *emu, VB_INSTRUCTION *inst) { static void cpuANDI(VB *emu, VB_INSTRUCTION *inst) {
emu->cpu.program[inst->bits[0] >> 5 & 0x1F] = cpuBitwise(emu, emu->cpu.program[inst->bits[0] >> 5 & 0x1F] = cpuBitwise(emu,
emu->cpu.program[inst->bits[0] & 0x1F] & inst->bits[1]); emu->cpu.program[inst->bits[0] & 0x1F] & inst->bits[1]);
} }
/* And Not Bit String Upward */
#define cpuANDNBSU(emu, inst) cpuBitString(emu, inst)
/* Conditional Branch */ /* Conditional Branch */
static void cpuBCOND(VB *emu, VB_INSTRUCTION *inst) { static void cpuBCOND(VB *emu, VB_INSTRUCTION *inst) {
int32_t disp; /* Target address displacement */ int32_t disp; /* Target address displacement */
@ -1124,15 +1124,15 @@ static void cpuOR(VB *emu, VB_INSTRUCTION *inst) {
/* Or Bit String Upward */ /* Or Bit String Upward */
#define cpuORBSU(emu, inst) cpuBitString(emu, inst) #define cpuORBSU(emu, inst) cpuBitString(emu, inst)
/* Or Not Bit String Upward */
#define cpuORNBSU(emu, inst) cpuBitString(emu, inst)
/* Or Immediate */ /* Or Immediate */
static void cpuORI(VB *emu, VB_INSTRUCTION *inst) { static void cpuORI(VB *emu, VB_INSTRUCTION *inst) {
emu->cpu.program[inst->bits[0] >> 5 & 0x1F] = cpuBitwise(emu, emu->cpu.program[inst->bits[0] >> 5 & 0x1F] = cpuBitwise(emu,
emu->cpu.program[inst->bits[0] & 0x1F] | inst->bits[1]); emu->cpu.program[inst->bits[0] & 0x1F] | inst->bits[1]);
} }
/* Or Not Bit String Upward */
#define cpuORNBSU(emu, inst) cpuBitString(emu, inst)
/* Output Byte */ /* Output Byte */
#define cpuOUT_B(emu, inst) cpuStore(emu, inst, VB_U8, 4) #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 */ /* Exclusive Or Bit String Upward */
#define cpuXORBSU(emu, inst) cpuBitString(emu, inst) #define cpuXORBSU(emu, inst) cpuBitString(emu, inst)
/* Exclusive Or Not Bit String Upward */
#define cpuXORNBSU(emu, inst) cpuBitString(emu, inst)
/* Exclusive Or Immediate */ /* Exclusive Or Immediate */
static void cpuXORI(VB *emu, VB_INSTRUCTION *inst) { static void cpuXORI(VB *emu, VB_INSTRUCTION *inst) {
emu->cpu.program[inst->bits[0] >> 5 & 0x1F] = cpuBitwise(emu, emu->cpu.program[inst->bits[0] >> 5 & 0x1F] = cpuBitwise(emu,
emu->cpu.program[inst->bits[0] & 0x1F] ^ inst->bits[1]); 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 ******************************/ /***************************** Module Functions ******************************/
@ -1505,14 +1505,14 @@ static int cpuExecute(VB *emu) {
/* Enter an exception state */ /* Enter an exception state */
static int cpuException(VB *emu) { static int cpuException(VB *emu) {
uint16_t causeCode = emu->cpu.causeCode;
/* Fatal exception */ /* Fatal exception */
if (emu->cpu.psw.np) { if (emu->cpu.psw.np) {
/* Write the cause code for debugging */ /* Write the cause code for debugging */
if (emu->cpu.busWait == 0) { if (emu->cpu.busWait == 0) {
if (cpuWrite(emu, 0x00000000, VB_S32, if (cpuWrite(emu, 0x00000000, VB_S32, 0xFFFF0000 | causeCode))
0xFFFF0000 | emu->cpu.causeCode))
return 1; return 1;
/* Update state */ /* Update state */
@ -1562,7 +1562,7 @@ static int cpuException(VB *emu) {
/* Duplexed exception */ /* Duplexed exception */
if (emu->cpu.psw.ep) { 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.fepsw = vbGetSystemRegister(emu, VB_PSW);
emu->cpu.fepc = emu->cpu.pc; emu->cpu.fepc = emu->cpu.pc;
emu->cpu.fepcFrom = emu->cpu.pcFrom; emu->cpu.fepcFrom = emu->cpu.pcFrom;
@ -1573,17 +1573,19 @@ static int cpuException(VB *emu) {
/* Exception or interrupt */ /* Exception or interrupt */
else { else {
emu->cpu.ecr.eicc = emu->cpu.causeCode; emu->cpu.ecr.eicc = causeCode;
emu->cpu.eipsw = vbGetSystemRegister(emu, VB_PSW); emu->cpu.eipsw = vbGetSystemRegister(emu, VB_PSW);
emu->cpu.eipc = emu->cpu.pc; 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.psw.ep = 1;
emu->cpu.pc = (emu->cpu.causeCode & 0x0040) != 0 ? emu->cpu.pc = (causeCode & 0x0040) != 0 ?
0xFFFFFF60 : ((uint32_t) 0xFFFF0000 | emu->cpu.causeCode); 0xFFFFFF60 : ((uint32_t) 0xFFFF0000 | causeCode);
} }
/* Interrupt */ /* Interrupt */
if (emu->cpu.causeCode < 0xFF00) if (causeCode < 0xFF00)
emu->cpu.psw.i = emu->cpu.psw.i == 15 ? 15 : emu->cpu.psw.i + 1; emu->cpu.psw.i += emu->cpu.psw.i == 15 ? 0 : 1;
/* Update state */ /* Update state */
emu->cpu.causeCode = 0; emu->cpu.causeCode = 0;
@ -1593,7 +1595,9 @@ static int cpuException(VB *emu) {
emu->cpu.pcFrom = emu->cpu.pc; emu->cpu.pcFrom = emu->cpu.pc;
emu->cpu.pcTo = emu->cpu.pc; emu->cpu.pcTo = emu->cpu.pc;
/* emu->cpu.clocks = ? */ /* emu->cpu.clocks = ? */
return 0;
/* Call the breakpoint handler if available */
return emu->onException != NULL && emu->onException(emu, causeCode);
} }
/* Perform instruction fetch operations */ /* Perform instruction fetch operations */

View File

@ -16,7 +16,9 @@ static const uint8_t TYPE_SIZES[] = { 1, 1, 2, 2, 4 };
/********************************** Macros ***********************************/ /********************************** Macros ***********************************/
/* Sign-extend a value of some number of bits to 32 bits */ /* 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; emu->cpu.pcTo = 0xFFFFFFF0;
/* Other CPU state */ /* Other CPU state */
emu->cpu.state = CPU_FETCH; emu->cpu.busWait = 0;
emu->cpu.state = CPU_FETCH;
emu->cpu.substring = 0;
for (x = 0; x < 5; x++) for (x = 0; x < 5; x++)
emu->cpu.irq[x] = 0; 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 */ /* Write a data unit to the bus */
void vbWrite(VB *emu, uint32_t address, int type, int32_t value, int debug) { 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); busWrite(emu, address, type, value, debug);
} }

View File

@ -1,7 +1,7 @@
.PHONY: help .PHONY: help
help: help:
@echo @echo
@echo "Virtual Boy Emulator - September 18, 2021" @echo "Virtual Boy Emulator - September 19, 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"

View File

@ -31,6 +31,45 @@ EMSCRIPTEN_KEEPALIVE void ReadBuffer(
*dest = vbRead(&sims[sim], address, VB_U8, debug); *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 //////////////////////////////// //////////////////////////////// Core Commands ////////////////////////////////