From 573f72e46fc19e88e9633771272bb501f03c9e1a Mon Sep 17 00:00:00 2001 From: Guy Perfect Date: Sun, 19 Sep 2021 19:36:30 +0000 Subject: [PATCH] Enabling CPU trace and hex edit --- app/App.js | 3 +- app/Debugger.js | 2 +- app/Emulator.js | 24 ++++ app/theme/dark.css | 4 + app/theme/kiosk.css | 32 +++++ app/theme/light.css | 4 + app/theme/virtual.css | 4 + app/windows/CPUWindow.js | 116 ++++++++++++---- app/windows/MemoryWindow.js | 258 ++++++++++++++++++++++++++++++------ core/bus.c | 2 +- core/cpu.c | 40 +++--- core/vb.c | 10 +- makefile | 2 +- wasm/wasm.c | 39 ++++++ 14 files changed, 449 insertions(+), 91 deletions(-) diff --git a/app/App.js b/app/App.js index 0df8cfd..d4730d0 100644 --- a/app/App.js +++ b/app/App.js @@ -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); diff --git a/app/Debugger.js b/app/Debugger.js index 6beeb24..beea995 100644 --- a/app/Debugger.js +++ b/app/Debugger.js @@ -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; } diff --git a/app/Emulator.js b/app/Emulator.js index 831a3be..d1c35fb 100644 --- a/app/Emulator.js +++ b/app/Emulator.js @@ -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); } diff --git a/app/theme/dark.css b/app/theme/dark.css index a79bbd7..cc6ed2f 100644 --- a/app/theme/dark.css +++ b/app/theme/dark.css @@ -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; diff --git a/app/theme/kiosk.css b/app/theme/kiosk.css index 7246211..1b49e3c 100644 --- a/app/theme/kiosk.css +++ b/app/theme/kiosk.css @@ -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); +} diff --git a/app/theme/light.css b/app/theme/light.css index c3c548a..2fe9588 100644 --- a/app/theme/light.css +++ b/app/theme/light.css @@ -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; diff --git a/app/theme/virtual.css b/app/theme/virtual.css index c4b7013..f948854 100644 --- a/app/theme/virtual.css +++ b/app/theme/virtual.css @@ -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; diff --git a/app/windows/CPUWindow.js b/app/windows/CPUWindow.js index 4535cc7..47bc292 100644 --- a/app/windows/CPUWindow.js +++ b/app/windows/CPUWindow.js @@ -207,9 +207,11 @@ ///////////////////////////// Public Methods ////////////////////////////// // Update the display with current emulation data - refresh() { - this.refreshDasm(); - this.refreshRegs(); + refresh(seekToPC, dasm, regs) { + if (dasm || dasm === undefined) + this.refreshDasm(this.address, 0, !!seekToPC); + if (regs || regs === undefined) + this.refreshRegs(); } // Specify whether the component is visible @@ -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); - // Configure instance fields - this.address = dasm[0].address; + // 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; - // 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); + // 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, 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,14 +513,15 @@ // 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 + command : "ReadBuffer", + sim : this.debug.sim, + 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)); diff --git a/app/windows/MemoryWindow.js b/app/windows/MemoryWindow.js index 31cfaa7..322b55f 100644 --- a/app/windows/MemoryWindow.js +++ b/app/windows/MemoryWindow.js @@ -8,10 +8,12 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window { super(debug.gui, options); // Configure instance fields - this.address = 0x05000000; - this.debug = debug; - this.pending = { mode: null }; - this.rows = []; + 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 @@ -158,32 +174,81 @@ globalThis.MemoryWindow = class MemoryWindow extends Toolkit.Window { ret = fullyVisible ? Math.floor(ret) : Math.ceil(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 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(); @@ -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()); } }; diff --git a/core/bus.c b/core/bus.c index f08b6e5..f9f6eb7 100644 --- a/core/bus.c +++ b/core/bus.c @@ -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 */ diff --git a/core/cpu.c b/core/cpu.c index f8f28d9..0f3a591 100644 --- a/core/cpu.c +++ b/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 */ diff --git a/core/vb.c b/core/vb.c index abd0b90..0eea7eb 100644 --- a/core/vb.c +++ b/core/vb.c @@ -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.state = CPU_FETCH; + 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); } diff --git a/makefile b/makefile index 4e862cd..dffacd7 100644 --- a/makefile +++ b/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" diff --git a/wasm/wasm.c b/wasm/wasm.c index 4c6a39a..9ab614d 100644 --- a/wasm/wasm.c +++ b/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 ////////////////////////////////