From 7d31c5539190d350f0da5038630a68c0814a89ca Mon Sep 17 00:00:00 2001 From: Guy Perfect Date: Wed, 20 Apr 2022 22:37:05 -0500 Subject: [PATCH] Tweaks from concurrent project --- app/app/App.js | 6 +- app/app/Disassembler.js | 98 +++++++++++++++--- app/app/Memory.js | 196 ++++++++++++++++++++++------------- app/app/RegisterList.js | 222 ++++++++++++++++++++++------------------ app/core/CoreWorker.js | 1 - app/theme/kiosk.css | 61 +++++++---- app/toolkit/DropDown.js | 125 ++++++++++++++++++++++ app/toolkit/Toolkit.js | 3 + core/bus.c | 18 ++-- core/cpu.c | 13 ++- core/vb.c | 81 ++++++++------- core/vb.h | 2 +- makefile | 6 +- wasm/wasm.c | 4 - 14 files changed, 570 insertions(+), 266 deletions(-) create mode 100644 app/toolkit/DropDown.js diff --git a/app/app/App.js b/app/app/App.js index 8d2f8b0..0997582 100644 --- a/app/app/App.js +++ b/app/app/App.js @@ -79,8 +79,12 @@ class App extends Toolkit { this.core.onsubscriptions = e=>this.onSubscriptions(e); // Temporary config debugging + console.log("Memory keyboard commands:"); + console.log(" Ctrl+G: Goto"); console.log("Disassembler keyboard commands:"); - console.log(" Ctrl+G: Goto (also works in Memory window)"); + console.log(" Ctrl+B: Toggle bytes column"); + console.log(" Ctrl+F: Fit columns"); + console.log(" Ctrl+G: Goto"); console.log(" F10: Run to next"); console.log(" F11: Single step"); console.log("Call dasm(\"key\", value) in the console " + diff --git a/app/app/Disassembler.js b/app/app/Disassembler.js index 140b9b5..249989d 100644 --- a/app/app/Disassembler.js +++ b/app/app/Disassembler.js @@ -100,6 +100,7 @@ class Line { constructor(parent, first) { // Configure instance fields + this.first = first; this.parent = parent; // Configure labels @@ -133,7 +134,7 @@ class Line { } // Update style according to selection - let method = isPC ? "add" : "remove"; + let method = row && isPC ? "add" : "remove"; this.lblAddress .classList[method]("tk-selected"); this.lblBytes .classList[method]("tk-selected"); this.lblMnemonic.classList[method]("tk-selected"); @@ -142,11 +143,33 @@ class Line { // Specify whether the elements on this line are visible setVisible(visible) { - visible = visible ? "block" : "none"; - this.lblAddress .style.display = visible; - this.lblBytes .style.display = visible; - this.lblMnemonic.style.display = visible; - this.lblOperands.style.display = visible; + + // Column elements + let columns = [ + this.lblAddress, + this.lblBytes, + this.lblMnemonic, + this.lblOperands + ]; + + // Column elements on the first row + if (this.first) { + columns[0] = columns[0].parentNode; // Address + columns[1] = columns[1].parentNode; // Bytes + columns[2] = columns[2].parentNode; // Mnemonic + } + + // Column visibility + visible = [ + visible, // Address + visible && this.parent.hasBytes, // Bytes + visible, // Mnemonic + visible // Operands + ]; + + // Configure elements + for (let x = 0; x < 4; x++) + columns[x].style.display = visible[x] ? "block" : "none"; } @@ -214,6 +237,7 @@ class Disassembler extends Toolkit.ScrollPane { this.columns = [ 0, 0, 0, 0 ]; this.data = []; this.debug = debug; + this.hasBytes = true; this.isSubscribed = false; this.lines = null; this.pc = this.address; @@ -294,8 +318,30 @@ class Disassembler extends Toolkit.ScrollPane { onKeyDown(e) { let tall = this.tall(false); - // Processing by key - switch (e.key) { + + // Ctrl key is pressed + if (e.ctrlKey) switch (e.key) { + + // Toggle bytes column + case "b": case "B": + this.showBytes(!this.hasBytes); + break; + + // Fit columns + case "f": case "F": + this.fitColumns(); + break; + + // Goto + case "g": case "G": + this.promptGoto(); + break; + + default: return; + } + + // Ctrl key is not pressed + else switch (e.key) { // Navigation case "ArrowDown" : this.fetch(+1 , true); break; @@ -309,13 +355,6 @@ class Disassembler extends Toolkit.ScrollPane { case "ArrowRight": this.horizontal.setValue( this.horizontal.value + this.horizontal.increment); break; - // Goto - case "g": case "G": - if (!e.ctrlKey) - return; - this.promptGoto(); - break; - // Single step case "F10": this.debug.runNext(); @@ -593,6 +632,16 @@ class Disassembler extends Toolkit.ScrollPane { ); } + // Shrink all columns to their minimum size + fitColumns() { + let line = this.lines[0]; + for (let column of [ "lblAddress", "lblBytes", "lblMnemonic" ] ) { + let element = line[column].parentNode; + element.max = 0; + element.style.removeProperty("min-width"); + } + } + // Represent a hexadecimal value hex(value, digits) { let sign = Util.s32(value) < 0 ? "-" : ""; @@ -629,12 +678,29 @@ class Disassembler extends Toolkit.ScrollPane { return this.proregCaps ? ret.toUpperCase() : ret; } + // Specify whether or not to show the bytes column + showBytes(show) { + let tall = this.tall(true); + + // Configure instance fields + this.hasBytes = show; + + // Configure elements + this.view.style.gridTemplateColumns = + "repeat(" + (show ? 3 : 2) + ", max-content) auto"; + for (let x = 0; x < tall; x++) + this.lines[x].setVisible(true); + + // Measure scroll pane + this.update(); + } + // Measure how many rows of output are visible tall(partial) { let lineHeight = !this.metrics ? 0 : Math.ceil(this.metrics.getBounds().height); return lineHeight <= 0 ? 1 : Math.max(1, Math[partial?"ceil":"floor"]( - this.viewport.getBoundingClientRect().height / lineHeight)); + this.getBounds().height / lineHeight)); } diff --git a/app/app/Memory.js b/app/app/Memory.js index be6603c..c40b724 100644 --- a/app/app/Memory.js +++ b/app/app/Memory.js @@ -2,6 +2,9 @@ import { Util } from /**/"./Util.js"; +// Bus indexes +const MEMORY = 0; + // Text to hex digit conversion const DIGITS = { "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, @@ -46,11 +49,13 @@ class Line { // Update the elements' display refresh() { - let address = Util.u32(this.parent.address + this.index * 16); - let data = this.parent.data; - let dataAddress = this.parent.dataAddress; + let bus = this.parent[this.parent.bus]; + let address = this.parent.mask(bus.address + this.index * 16); + let data = bus.data; + let dataAddress = bus.dataAddress; let hexCaps = this.parent.dasm.hexCaps; - let offset = Util.s32(address - dataAddress); + let offset = + (this.parent.row(address) - this.parent.row(dataAddress)) * 16; // Format the line's address let text = address.toString(16).padStart(8, "0"); @@ -59,9 +64,12 @@ class Line { this.lblAddress.innerText = text; // The line's data is not available - if (offset < 0 || offset >= data.length) - for (let lbl of this.lblBytes) + if (offset < 0 || offset >= data.length) { + for (let lbl of this.lblBytes) { lbl.innerText = "--"; + lbl.classList.remove("tk-selected"); + } + } // The line's data is available else for (let x = 0; x < 16; x++, offset++) { @@ -69,14 +77,14 @@ class Line { text = data[offset].toString(16).padStart(2, "0"); // The byte is the current selection - if (Util.u32(address + x) == this.parent.selection) { - lbl.classList.add("selected"); + if (Util.u32(address + x) == bus.selection) { + lbl.classList.add("tk-selected"); if (this.parent.digit !== null) text = this.parent.digit.toString(16); } // The byte is not the current selection - else lbl.classList.remove("selected"); + else lbl.classList.remove("tk-selected"); // Update the label's text if (hexCaps) @@ -103,41 +111,59 @@ class Line { /////////////////////////////////////////////////////////////////////////////// // Memory hex editor -class Memory extends Toolkit.ScrollPane { +class Memory extends Toolkit.Component { ///////////////////////// Initialization Methods ////////////////////////// constructor(debug) { super(debug.app, { - className : "tk tk-scrollpane tk-memory", + className : "tk tk-memory", + tagName : "div", + style : { + alignItems : "stretch", + display : "grid", + gridTemplateRows: "auto", + position : "relative" + } + }); + + // Configure instance fields + this.app = debug.app; + this.bus = MEMORY; + this.dasm = debug.disassembler; + this.debug = debug; + this.digit = null; + this.isSubscribed = false; + this.lines = []; + this.sim = debug.sim; + + // Initialize bus + this[MEMORY] = { + address : 0x05000000, + data : [], + dataAddress: 0x05000000, + selection : 0x05000000 + }; + + // Configure editor pane + this.editor = new Toolkit.ScrollPane(this.app, { + className : "tk tk-scrollpane tk-editor", horizontal: Toolkit.ScrollPane.AS_NEEDED, focusable : true, tabStop : true, tagName : "div", vertical : Toolkit.ScrollPane.NEVER }); - - // Configure instance fields - this.address = 0x05000000; - this.app = debug.app; - this.dasm = debug.disassembler; - this.data = []; - this.dataAddress = this.address; - this.debug = debug; - this.digit = null; - this.isSubscribed = false; - this.lines = []; - this.selection = this.address; - this.sim = debug.sim; + this.append(this.editor); // Configure view - let view = document.createElement("div"); - view.className = "tk tk-view"; - Object.assign(view.style, { + this.view = document.createElement("div"); + this.view.className = "tk tk-view"; + Object.assign(this.view.style, { display : "grid", gridTemplateColumns: "repeat(17, max-content)" }); - this.setView(view); + this.editor.setView(this.view); // Font-measuring element this.metrics = new Toolkit.Component(this.app, { @@ -152,7 +178,7 @@ class Memory extends Toolkit.ScrollPane { this.append(this.metrics.element); // Configure event handlers - Toolkit.addResizeListener(this.viewport, e=>this.onResize(e)); + Toolkit.addResizeListener(this.editor.viewport, e=>this.onResize(e)); this.addEventListener("keydown" , e=>this.onKeyDown (e)); this.addEventListener("pointerdown", e=>this.onPointerDown(e)); this.addEventListener("wheel" , e=>this.onMouseWheel (e)); @@ -164,23 +190,25 @@ class Memory extends Toolkit.ScrollPane { // Typed a digit onDigit(digit) { + let bus = this[this.bus]; // Begin an edit if (this.digit === null) { this.digit = digit; - this.setSelection(this.selection, true); + this.setSelection(bus.selection, true); } // Complete an edit else { this.digit = this.digit << 4 | digit; - this.setSelection(this.selection + 1); + this.setSelection(bus.selection + 1); } } // Key press onKeyDown(e) { + let bus = this[this.bus]; let key = e.key; // A hex digit was entered @@ -189,39 +217,44 @@ class Memory extends Toolkit.ScrollPane { key = "digit"; } - // Processing by key - switch (key) { + // Ctrl key is pressed + if (e.ctrlKey) switch (key) { + + // Goto + case "g": case "G": + this.promptGoto(); + break; + + default: return; + } + + // Ctrl key is not pressed + else switch (key) { // Arrow key navigation - case "ArrowDown" : this.setSelection(this.selection + 16); break; - case "ArrowLeft" : this.setSelection(this.selection - 1); break; - case "ArrowRight": this.setSelection(this.selection + 1); break; - case "ArrowUp" : this.setSelection(this.selection - 16); break; + case "ArrowDown" : this.setSelection(bus.selection + 16); break; + case "ArrowLeft" : this.setSelection(bus.selection - 1); break; + case "ArrowRight": this.setSelection(bus.selection + 1); break; + case "ArrowUp" : this.setSelection(bus.selection - 16); break; // Commit current edit case "Enter": case " ": if (this.digit !== null) - this.setSelection(this.selection); - break; - - // Goto - case "g": case "G": - if (!e.ctrlKey) - return; - this.promptGoto(); + this.setSelection(bus.selection); break; // Page key navigation case "PageDown": - this.setSelection(this.selection + this.tall(false) * 16); + this.setSelection(bus.selection + this.tall(false) * 16); break; case "PageUp": - this.setSelection(this.selection - this.tall(false) * 16); + this.setSelection(bus.selection - this.tall(false) * 16); break; // Hex digit: already processed case "digit": break; + default: return; } @@ -243,15 +276,14 @@ class Memory extends Toolkit.ScrollPane { return; // Update the display address - this.address = Util.u32(this.address + offset); - this.fetch(this.address, true); + this.fetch(this[this.bus].address + offset, true); } // Pointer down onPointerDown(e) { // Common handling - this.focus(); + this.editor.focus(); e.stopPropagation(); e.preventDefault(); @@ -261,8 +293,11 @@ class Memory extends Toolkit.ScrollPane { // Determine the row that was clicked on let lineHeight = !this.metrics ? 0 : - Math.max(1, Math.ceil(this.metrics.getBounds().height)); - let y = Math.floor((e.y - this.getBounds().top) / lineHeight); + Math.max(0, Math.ceil(this.metrics.getBounds().height)); + if (lineHeight == 0) + return; + let y = Math.floor( + (e.y - this.view.getBoundingClientRect().top) / lineHeight); // Determine the column that was clicked on let columns = this.lines[0].lblBytes; @@ -274,7 +309,7 @@ class Memory extends Toolkit.ScrollPane { // The current column was clicked: update the selection if (e.x < (x == 15 ? bndCur.right : bndCur.right + (bndNext.left - bndCur.right) / 2)) { - this.setSelection(this.address + y * 16 + x); + this.setSelection(this[this.bus].address + y * 16 + x); return; } @@ -303,12 +338,12 @@ class Memory extends Toolkit.ScrollPane { this.lines[x].setVisible(false); // Configure horizontal scroll bar - if (this.metrics) - this.horizontal.setIncrement(this.metrics.getBounds().width); + if (this.metrics) this.editor.horizontal + .setIncrement(this.metrics.getBounds().width); // Update the display if (fetch) - this.fetch(this.address, true); + this.fetch(this[this.bus].address, true); else this.refresh(); } @@ -318,11 +353,12 @@ class Memory extends Toolkit.ScrollPane { // Update with memory state from the core refresh(data) { + let bus = this[this.bus]; // Update with data from the core thread if (data) { - this.data = data.bytes; - this.dataAddress = data.address; + bus.data = data.bytes; + bus.dataAddress = data.address; } // Update elements @@ -343,7 +379,7 @@ class Memory extends Toolkit.ScrollPane { // Subscribe to core updates if (subscribed) - this.fetch(this.address); + this.fetch(this[this.bus].address); // Unsubscribe from core updates else this.sim.unsubscribe("memory"); @@ -366,7 +402,7 @@ class Memory extends Toolkit.ScrollPane { async fetch(address, prefresh) { // Configure instance fields - this.address = address; + this[this.bus].address = address = this.mask(address); // Update the view immediately if (prefresh) @@ -382,6 +418,11 @@ class Memory extends Toolkit.ScrollPane { ); } + // Mask an address according to the current bus + mask(address) { + return Util.u32(address); + } + // Prompt the user to specify a new address promptGoto() { @@ -397,17 +438,23 @@ class Memory extends Toolkit.ScrollPane { // The address is not currently visible in the output let tall = this.tall(false); - if (Util.u32(address - this.address) >= tall * 16) { - this.fetch(Util.u32( - (address & 0xFFFFFFF0) - Math.floor(tall / 3) * 16)); - } + if (Util.u32(address - this.address) >= tall * 16) + this.fetch((address & 0xFFFFFFF0) - Math.floor(tall / 3) * 16); // Move the selection and refresh the display - this.setSelection(Util.u32(address)); + this.setSelection(address); + } + + // Determine which row relative to top the selection is on + row(address) { + let row = address - this[this.bus].address & 0xFFFFFFF0; + row = Util.s32(row); + return row / 16; } // Specify which byte is selected setSelection(address, noCommit) { + let bus = this[this.bus]; let fetch = false; // Commit a pending data entry @@ -418,27 +465,27 @@ class Memory extends Toolkit.ScrollPane { } // Configure instance fields - this.selection = address = Util.u32(address); + bus.selection = address = this.mask(address); // Working variables - let row = Util.s32(address - this.address & 0xFFFFFFF0) / 16; + let row = this.row(address); // The new address is above the top line of output if (row < 0) { - this.fetch(Util.u32(this.address + row * 16), true); + this.fetch(bus.address + row * 16 & 0xFFFFFFF0, true); return; } // The new address is below the bottom line of output let tall = this.tall(false); if (row >= tall) { - this.fetch(Util.u32(address - tall * 16 + 16 & 0xFFFFFFF0), true); + this.fetch(address - tall * 16 + 16 & 0xFFFFFFF0, true); return; } // Update the display if (fetch) - this.fetch(this.address, true); + this.fetch(bus.address, true); else this.refresh(); } @@ -447,14 +494,17 @@ class Memory extends Toolkit.ScrollPane { let lineHeight = !this.metrics ? 0 : Math.ceil(this.metrics.getBounds().height); return lineHeight <= 0 ? 1 : Math.max(1, Math[partial?"ceil":"floor"]( - this.viewport.getBoundingClientRect().height / lineHeight)); + this.editor.getBounds().height / lineHeight)); } // Write a value to the core thread write(value) { - this.data[Util.s32(this.selection - this.dataAddress)] = value; + let bus = this[this.bus]; + let offset = (this.row(bus.selection) + 16) * 16; + if (offset < bus.data.length) + bus.data[offset | bus.selection & 15] = value; this.sim.write( - this.selection, + bus.selection, Uint8Array.from([ value ]), { refresh: true }); diff --git a/app/app/RegisterList.js b/app/app/RegisterList.js index 2dfee16..15b0e70 100644 --- a/app/app/RegisterList.js +++ b/app/app/RegisterList.js @@ -124,24 +124,20 @@ class Register { this.app = list.app; this.controls = []; this.dasm = list.dasm; + this.format = HEX; this.index = index; this.isExpanded = null; this.list = list; + this.metrics = { width: 0, height: 0 }; this.orMask = orMask; this.sim = list.sim; this.system = list.system; - this.type = HEX; this.value = 0x00000000; - // Configure main controls - this.contents = new Toolkit.Component(this.app, { - className: "tk tk-contents", - tagName : "div", - style : { - display : "grid", - gridTemplateColumns: "max-content auto max-content" - } - }); + // Establish elements + let row = document.createElement("tr"); + let cell; + list.view.append(row); // Processing by type this[this.system ? "initSystem" : "initProgram"](); @@ -151,7 +147,10 @@ class Register { className: "tk tk-expand tk-mono", tagName : "div" }); - this.list.view.append(this.btnExpand); + row .append(cell = document.createElement("td")); + cell.className = "tk"; + cell.style.width = "1px"; + cell.append(this.btnExpand.element); // Name label this.lblName = document.createElement("div"); @@ -161,7 +160,9 @@ class Register { innerText: this.dasm.sysregCaps?this.name:this.name.toLowerCase() }); this.lblName.style.userSelect = "none"; - this.list.view.append(this.lblName); + row .append(cell = document.createElement("td")); + cell.className = "tk"; + cell.append(this.lblName); // Value text box this.txtValue = new Toolkit.TextBox(this.app, { @@ -171,7 +172,13 @@ class Register { this.txtValue.setAttribute("aria-labelledby", this.lblName.id); this.txtValue.setAttribute("digits", "8"); this.txtValue.addEventListener("action", e=>this.onValue()); - this.list.view.append(this.txtValue); + row .append(cell = document.createElement("td")); + Object.assign(cell.style, { + textAlign: "right", + width : "1px" + }); + cell.className = "tk"; + cell.append(this.txtValue.element); // Expansion area if (this.expansion != null) @@ -226,17 +233,23 @@ class Register { let two = this.index == ECR || this.index == PIR; // Establish expansion element - let exp = this.expansion = new Toolkit.Component(this.app, { + let exp = this.expansion = document.createElement("tr"); + exp.contents = new Toolkit.Component(this.app, { className: "tk tk-expansion", id : Toolkit.id(), tagName : "div", style : { - display : "none", - gridColumnEnd : "span 3", + display : "grid", gridTemplateColumns: this.system ? "repeat(2, max-content)" : "max-content" } }); + let cell = document.createElement("td"); + cell.className = "tk"; + cell.colSpan = "3"; + cell.append(exp.contents.element); + exp.append(cell); + exp = exp.contents; // Produce program register controls if (!this.system) { @@ -256,7 +269,7 @@ class Register { // Configure event handler ctrl.addEventListener("action", - e=>this.setType(e.component.format)); + e=>this.setFormat(e.component.format)); // Add the control to the element let box = document.createElement("div"); @@ -405,7 +418,7 @@ class Register { let value; // Processing by type - switch (this.type) { + switch (this.format) { // Unsigned hexadecimal case HEX: @@ -431,25 +444,6 @@ class Register { this.setValue(isNaN(value) ? this.value : value); } - // Specify whether the expansion area is visible - setExpanded(expanded) { - expanded = !!expanded; - - // Error checking - if (this.expansion == null || expanded === this.isExpanded) - return; - - // Configure instance fields - this.isExpanded = expanded; - - // Configure elements - let key = expanded ? "common.collapse" : "common.expand"; - this.btnExpand.setAttribute("aria-expanded", expanded); - this.btnExpand.setToolTip(key); - this.expansion.element.style.display = - expanded ? "inline-grid" : "none"; - } - ///////////////////////////// Package Methods ///////////////////////////// @@ -483,17 +477,10 @@ class Register { let text; // Configure instance fields - this.value = value; + this.value = value = Util.u32(value); // Value text box - if (this.type == HEX) { - this.txtValue.element.classList.add("tk-mono"); - this.txtValue.setMaxLength(8); - } else { - this.txtValue.element.classList.remove("tk-mono"); - this.txtValue.setMaxLength(null); - } - switch (this.type) { + switch (this.format) { // Unsigned hexadecimal case HEX: @@ -547,15 +534,70 @@ class Register { } + // Specify whether the expansion area is visible + setExpanded(expanded) { + expanded = !!expanded; + + // Error checking + if (this.expansion == null || expanded === this.isExpanded) + return; + + // Configure instance fields + this.isExpanded = expanded; + + // Configure elements + let key = expanded ? "common.collapse" : "common.expand"; + this.btnExpand.setAttribute("aria-expanded", expanded); + this.btnExpand.setToolTip(key); + this.expansion.style.display = + expanded ? "table-row" : "none"; + } + + // Specify the font metrics + setMetrics(width, height) { + + // Configure instance fields + this.metrics = { width: width, height: height }; + + // Height + height += "px"; + this.txtValue.element.style.height = height; + for (let ctrl of this.controls.filter(c=>c.size > 1)) + ctrl.element.style.height = height; + + // Hexadecimal formatting + if (this.format == HEX) { + this.txtValue.element.style.width = (width * 8) + "px"; + this.txtValue.setMaxLength(8); + } + + // Decimal formatting + else { + this.txtValue.element.style.removeProperty("width"); + this.txtValue.setMaxLength(null); + } + + // Expansion text boxes + for (let box of this.controls.filter(c=>c.size > 1)) { + box.element.style.height = height; + if (box.size == 16) + box.element.style.width = (width * 4) + "px"; + } + + } + ///////////////////////////// Private Methods ///////////////////////////// // Specify the formatting type of the register value - setType(type) { - if (type == this.type) + setFormat(format) { + if (format == this.format) return; - this.type = type; + this.format = format; + this.txtValue.element + .classList[format == HEX ? "add" : "remove"]("tk-mono"); + this.setMetrics(this.metrics.width, this.metrics.height); this.refresh(this.value); } @@ -604,6 +646,7 @@ class RegisterList extends Toolkit.ScrollPane { this.app = debug.app; this.dasm = debug.disassembler; this.method = system?"getSystemRegisters":"getProgramRegisters"; + this.registers = []; this.sim = debug.sim; this.subscription = system ? "sysregs" : "proregs"; this.system = system; @@ -611,10 +654,9 @@ class RegisterList extends Toolkit.ScrollPane { // Configure view element this.setView(new Toolkit.Component(debug.app, { className: "tk tk-list", - tagName : "div", + tagName : "table", style : { - display : "grid", - gridTemplateColumns: "max-content auto max-content" + width: "100%" } })); @@ -647,27 +689,27 @@ class RegisterList extends Toolkit.ScrollPane { // Initialize a list of program registers initProgram() { - this[0] = new Register(this, 0, 0x00000000, 0x00000000); + this.add(new Register(this, 0, 0x00000000, 0x00000000)); for (let x = 1; x < 32; x++) - this[x] = new Register(this, x, 0xFFFFFFFF, 0x00000000); + this.add(new Register(this, x, 0xFFFFFFFF, 0x00000000)); } // Initialie a list of system registers initSystem() { - this[PC ] = new Register(this, PC , 0xFFFFFFFE, 0x00000000); - this[PSW ] = new Register(this, PSW , 0x000FF3FF, 0x00000000); - this[ADTRE] = new Register(this, ADTRE, 0xFFFFFFFE, 0x00000000); - this[CHCW ] = new Register(this, CHCW , 0x00000002, 0x00000000); - this[ECR ] = new Register(this, ECR , 0xFFFFFFFF, 0x00000000); - this[EIPC ] = new Register(this, EIPC , 0xFFFFFFFE, 0x00000000); - this[EIPSW] = new Register(this, EIPSW, 0x000FF3FF, 0x00000000); - this[FEPC ] = new Register(this, FEPC , 0xFFFFFFFE, 0x00000000); - this[FEPSW] = new Register(this, FEPSW, 0x000FF3FF, 0x00000000); - this[PIR ] = new Register(this, PIR , 0x00000000, 0x00005346); - this[TKCW ] = new Register(this, TKCW , 0x00000000, 0x000000E0); - this[29 ] = new Register(this, 29 , 0xFFFFFFFF, 0x00000000); - this[30 ] = new Register(this, 30 , 0x00000000, 0x00000004); - this[31 ] = new Register(this, 31 , 0xFFFFFFFF, 0x00000000); + this.add(new Register(this, PC , 0xFFFFFFFE, 0x00000000)); + this.add(new Register(this, PSW , 0x000FF3FF, 0x00000000)); + this.add(new Register(this, ADTRE, 0xFFFFFFFE, 0x00000000)); + this.add(new Register(this, CHCW , 0x00000002, 0x00000000)); + this.add(new Register(this, ECR , 0xFFFFFFFF, 0x00000000)); + this.add(new Register(this, EIPC , 0xFFFFFFFE, 0x00000000)); + this.add(new Register(this, EIPSW, 0x000FF3FF, 0x00000000)); + this.add(new Register(this, FEPC , 0xFFFFFFFE, 0x00000000)); + this.add(new Register(this, FEPSW, 0x000FF3FF, 0x00000000)); + this.add(new Register(this, PIR , 0x00000000, 0x00005346)); + this.add(new Register(this, TKCW , 0x00000000, 0x000000E0)); + this.add(new Register(this, 29 , 0xFFFFFFFF, 0x00000000)); + this.add(new Register(this, 30 , 0x00000000, 0x00000004)); + this.add(new Register(this, 31 , 0xFFFFFFFF, 0x00000000)); } @@ -725,14 +767,9 @@ class RegisterList extends Toolkit.ScrollPane { let width = Math.ceil(bounds.width); let height = Math.ceil(bounds.height / 32); - // Resize all monospaced text boxes - for (let box of this.element - .querySelectorAll(".tk-textbox[digits]")) { - Object.assign(box.style, { - height: height + "px", - width : (parseInt(box.getAttribute("digits")) * width) + "px" - }); - } + // Resize all text boxes + for (let reg of this.registers) + reg.setMetrics(width, height); // Update scroll bars this.horizontal.setIncrement(height); @@ -765,20 +802,8 @@ class RegisterList extends Toolkit.ScrollPane { // System registers if (this.system) { - this[ADTRE].refresh(registers.adtre); - this[CHCW ].refresh(registers.chcw ); - this[ECR ].refresh(registers.ecr ); - this[EIPC ].refresh(registers.eipc ); - this[EIPSW].refresh(registers.eipsw); - this[FEPC ].refresh(registers.fepc ); - this[FEPSW].refresh(registers.fepsw); - this[PC ].refresh(registers.pc ); - this[PIR ].refresh(registers.pir ); - this[PSW ].refresh(registers.psw ); - this[TKCW ].refresh(registers.tkcw ); - this[29 ].refresh(registers[29] ); - this[30 ].refresh(registers[30] ); - this[31 ].refresh(registers[31] ); + for (let reg of Object.entries(SYSREGS)) + this[reg[0]].refresh(registers[reg[1].toLowerCase()]); } // Program registers @@ -811,13 +836,8 @@ class RegisterList extends Toolkit.ScrollPane { // Disassembler settings have been updated dasmChanged() { - if (this.system) { - for (let key of Object.keys(SYSREGS)) - this[key].dasmChanged(); - } else { - for (let key = 0; key < 32; key++) - this[key].dasmChanged(); - } + for (let reg of this.registers) + reg.dasmChanged(); } // Determine the initial size of the register list @@ -836,7 +856,7 @@ class RegisterList extends Toolkit.ScrollPane { // Locate the bottom of PSW if (this.system && this[PSW].expansion) { - ret.height = this[PSW].expansion.getBounds().bottom - + ret.height = this[PSW].expansion.getBoundingClientRect().bottom - this.view.getBounds().top; } @@ -847,6 +867,12 @@ class RegisterList extends Toolkit.ScrollPane { ///////////////////////////// Private Methods ///////////////////////////// + // Add a register to the list + add(reg) { + this[reg.index] = reg; + this.registers.push(reg); + } + // Retrieve CPU state from the core async fetch() { this.refresh( diff --git a/app/core/CoreWorker.js b/app/core/CoreWorker.js index b8989e4..4bc8f9a 100644 --- a/app/core/CoreWorker.js +++ b/app/core/CoreWorker.js @@ -321,7 +321,6 @@ new class CoreWorker { address: address, bytes : [ bits & 0xFF, bits >> 8 ], size : u32(address + 2) == pc ? 2 : size - //size : Math.min(u32(pc - address), size) }; // Read additional bytes diff --git a/app/theme/kiosk.css b/app/theme/kiosk.css index 1560cad..e4de36e 100644 --- a/app/theme/kiosk.css +++ b/app/theme/kiosk.css @@ -13,6 +13,11 @@ padding : 0; } +table.tk { + border : none; + border-spacing: 0; +} + .tk-body { overflow: hidden; } @@ -106,6 +111,19 @@ +/****************************** Drop-Down List *******************************/ + +.tk-dropdown { + background : var(--tk-window); + border : 1px solid var(--tk-control-shadow); + border-radius: 0; + color : var(--tk-window-text); + margin : 0; + padding : 1px; +} + + + /********************************* Menu Bar **********************************/ .tk-menu-bar { @@ -526,7 +544,7 @@ -/******************************* Disassembler ********************************/ +/************************************ CPU ************************************/ .tk-cpu .tk-main { height: 100%; @@ -654,18 +672,21 @@ background: transparent; border : none; padding : 0; + width : 1.5em; +} + +.tk-reglist.tk-program .tk-textbox:not(.tk-mono) { + text-align: right; + width : 6em; } .tk-reglist .tk-expansion { align-items : center; + column-gap : 0.8em; margin-bottom: 2px; padding : 2px 0 0 1.5em; } -.tk-reglist .tk-expansion { - column-gap: 0.8em; -} - .tk-reglist .tk-expansion .tk-number .tk-label { align-items : center; display : flex; @@ -673,10 +694,6 @@ min-width : 12px; } -.tk-reglist .tk-expansion .tk-textbox { - width: 1.5em; -} - .tk-reglist .tk-expansion .tk-checkbox[aria-disabled="true"][aria-checked="true"] .tk-icon:before { background: var(--tk-control-shadow); @@ -694,6 +711,11 @@ /********************************** Memory ***********************************/ .tk-window .tk-memory { + height: 100%; + width : 100%; +} + +.tk-memory .tk-editor { box-shadow: 0 0 0 1px var(--tk-control),0 0 0 2px var(--tk-control-shadow); height : calc(100% - 6px); margin : 3px; @@ -714,23 +736,26 @@ } .tk-memory .tk-byte { - border : 0 solid transparent; - margin-left: calc(0.6em - 1px); - padding : 0 1px; - text-align : center; + border : 0 solid transparent; + padding : 0 1px; + text-align: center; } -.tk-memory .tk-byte.tk-0, -.tk-memory .tk-byte.tk-8 { - margin-left: calc(1.2em - 1px); +.tk-memory .tk-byte:not(.tk-15) { + margin-right: calc(0.6em - 1px); } -.tk-memory .tk-byte.selected { +.tk-memory .tk-address, +.tk-memory .tk-byte.tk-7 { + margin-right: calc(1.2em - 1px); +} + +.tk-memory .tk-byte.tk-selected { background: var(--tk-selected-blur); color : var(--tk-selected-blur-text); } -.tk-memory:focus-within .tk-byte.selected { +.tk-memory .tk-editor:focus-within .tk-byte.tk-selected { background: var(--tk-selected); color : var(--tk-selected-text); } diff --git a/app/toolkit/DropDown.js b/app/toolkit/DropDown.js new file mode 100644 index 0000000..73ef37b --- /dev/null +++ b/app/toolkit/DropDown.js @@ -0,0 +1,125 @@ +import { Component } from /**/"./Component.js"; +let Toolkit; + + + +/////////////////////////////////////////////////////////////////////////////// +// DropDown // +/////////////////////////////////////////////////////////////////////////////// + +// Text entry field +class DropDown extends Component { + static Component = Component; + + ///////////////////////// Initialization Methods ////////////////////////// + + constructor(gui, options) { + super(gui, options, { + className: "tk tk-dropdown", + tagName : "select" + }); + + // Configure instance fields + this.isEnabled = null; + this.options = []; + + // Configure component + options = options || {}; + this.setEnabled(!("enabled" in options) || options.enabled); + if ("options" in options) + this.setOptions(options.options); + this.setSelectedIndex( + ("selectedIndex" in options ? options : this).selectedIndex); + + // Configure event handlers + this.addEventListener("keydown" , e=>e.stopPropagation()); + this.addEventListener("pointerdown", e=>e.stopPropagation()); + } + + + + ///////////////////////////// Public Methods ////////////////////////////// + + // Programmatically change the selection + change() { + this.element.dispatchEvent(this.event("input")); + } + + // Retrieve the current selection index + getSelectedIndex() { + return this.element.selectedIndex; + } + + // Specify whether the button can be activated + setEnabled(enabled) { + this.isEnabled = enabled = !!enabled; + this.setAttribute("disabled", enabled ? null : "true"); + } + + // Specify the list contents + setOptions(options) { + + // Error checking + if (!Array.isArray(options)) + return; + + // Erase the list of options + this.options.splice(0); + this.element.replaceChildren(); + + // Add options from the input + for (let option of options) { + if (typeof option != "string") + continue; + this.options.push(option); + this.element.add(document.createElement("option")); + } + + // Update the display text + this.translate(); + } + + // Specify the current selection + setSelectedIndex(index) { + + // Error checking + if (typeof index != "number" || isNaN(index)) + return this.element.selectedIndex; + index = Math.round(index); + if (index < -1 || index >= this.options.length) + return this.element.selectedIndex; + + // Configure element and instance fields + return this.element.selectedIndex = index; + } + + + + ///////////////////////////// Package Methods ///////////////////////////// + + // Update the global Toolkit object + static setToolkit(toolkit) { + Toolkit = toolkit; + } + + // Regenerate localized display text + translate() { + super.translate(); + + // Error checking + if (!this.options) + return; + + // Update the list items + for (let x = 0; x < this.options.length; x++) { + this.element.item(x).innerText = + this.gui.translate(this.options[x], this); + } + + } + +} + + + +export { DropDown }; diff --git a/app/toolkit/Toolkit.js b/app/toolkit/Toolkit.js index fb314de..2235ff9 100644 --- a/app/toolkit/Toolkit.js +++ b/app/toolkit/Toolkit.js @@ -1,5 +1,6 @@ import { Component } from /**/"./Component.js"; import { Button, CheckBox, Group, Radio } from /**/"./Button.js" ; +import { DropDown } from /**/"./DropDown.js" ; import { Menu, MenuBar, MenuItem, MenuSeparator } from /**/"./MenuBar.js" ; import { ScrollBar, ScrollPane, SplitPane } from /**/"./ScrollBar.js"; import { TextBox } from /**/"./TextBox.js" ; @@ -26,6 +27,7 @@ let Toolkit = globalThis.Toolkit = (class GUI extends Component { this.components = []; Button .setToolkit(this); this.components.push(Button .Component); Component.setToolkit(this); this.components.push( Component); + DropDown .setToolkit(this); this.components.push(DropDown .Component); MenuBar .setToolkit(this); this.components.push(MenuBar .Component); ScrollBar.setToolkit(this); this.components.push(ScrollBar.Component); TextBox .setToolkit(this); this.components.push(TextBox .Component); @@ -34,6 +36,7 @@ let Toolkit = globalThis.Toolkit = (class GUI extends Component { this.CheckBox = CheckBox; this.Component = Component; this.Desktop = Desktop; + this.DropDown = DropDown; this.Group = Group; this.Menu = Menu; this.MenuBar = MenuBar; diff --git a/core/bus.c b/core/bus.c index d2c1353..71bfeef 100644 --- a/core/bus.c +++ b/core/bus.c @@ -6,7 +6,7 @@ /***************************** Utility Functions *****************************/ /* Read a data unit from a memory buffer */ -static int32_t busReadMemory(uint8_t *mem, int type) { +static int32_t busReadBuffer(uint8_t *mem, int type) { /* Little-endian implementation */ #ifdef VB_LITTLEENDIAN @@ -33,7 +33,7 @@ static int32_t busReadMemory(uint8_t *mem, int type) { } /* Write a data unit to a memory buffer */ -static void busWriteMemory(uint8_t *mem, int type, int32_t value) { +static void busWriteBuffer(uint8_t *mem, int type, int32_t value) { /* Little-endian implementation */ #ifdef VB_LITTLEENDIAN @@ -72,17 +72,17 @@ static int32_t busRead(VB *sim, uint32_t address, int type, int debug) { /* Process by address range */ switch (address >> 24 & 7) { case 0 : return 0; /* VIP */ - case 1 : debug = debug; return 0; /* VSU */ + case 1 : return 0 * debug; /* VSU */ case 2 : return 0; /* Miscellaneous hardware */ case 3 : return 0; /* Unmapped */ case 4 : return 0; /* Game pak expansion */ case 5 : return /* WRAM */ - busReadMemory(&sim->wram[address & 0xFFFF], type); + busReadBuffer(&sim->wram[address & 0xFFFF], type); case 6 : return sim->cart.sram == NULL ? 0 : /* Game pak RAM */ - busReadMemory(&sim->cart.sram + busReadBuffer(&sim->cart.sram [address & (sim->cart.sramSize - 1)], type); default: return sim->cart.rom == NULL ? 0 : /* Game pak ROM */ - busReadMemory(&sim->cart.rom + busReadBuffer(&sim->cart.rom [address & (sim->cart.romSize - 1)], type); } @@ -103,16 +103,16 @@ static void busWrite( case 3 : return; /* Unmapped */ case 4 : return; /* Game pak expansion */ case 5 : /* WRAM */ - busWriteMemory(&sim->wram[address & 0xFFFF], type, value); + busWriteBuffer(&sim->wram[address & 0xFFFF], type, value); return; case 6 : /* Cartridge RAM */ if (sim->cart.sram != NULL) - busWriteMemory(&sim->cart.sram + busWriteBuffer(&sim->cart.sram [address & (sim->cart.sramSize - 1)], type, value); return; default: /* Cartridge ROM */ if (debug && sim->cart.rom != NULL) - busWriteMemory(&sim->cart.rom + busWriteBuffer(&sim->cart.rom [address & (sim->cart.romSize - 1)], type, value); } diff --git a/core/cpu.c b/core/cpu.c index 21a0562..bc8df36 100644 --- a/core/cpu.c +++ b/core/cpu.c @@ -646,12 +646,11 @@ static uint32_t cpuSetSystemRegister(VB *sim,int id,uint32_t value,int debug) { /* TODO: Perform dump/restore operations */ return value & 0x00000002; case VB_ECR : - if (debug) { - sim->cpu.ecr.fecc = value >> 16; - sim->cpu.ecr.eicc = value; - return value; - } - return vbGetSystemRegister(sim, id); + if (!debug) + return (uint32_t) sim->cpu.ecr.fecc << 16 | sim->cpu.ecr.eicc; + sim->cpu.ecr.fecc = value >> 16; + sim->cpu.ecr.eicc = value; + return value; case VB_PSW : sim->cpu.psw.i = value >> 16 & 15; sim->cpu.psw.np = value >> 15 & 1; @@ -690,7 +689,7 @@ static int cpuStore(VB *sim,VB_INSTRUCTION *inst,uint8_t type,uint32_t clocks){ /* Initiate the write */ if (sim->cpu.busWait == 0) { - /* Read the data unit from the bus */ + /* Write the data unit to the bus */ if (cpuWrite( sim, sim->cpu.program[inst->bits[0] & 0x1F] + diff --git a/core/vb.c b/core/vb.c index 7aad9be..6547a24 100644 --- a/core/vb.c +++ b/core/vb.c @@ -1,4 +1,8 @@ -#define VBAPI VB_EXPORT +#ifdef VB_EXPORT + #define VBAPI VB_EXPORT +#else + #define VBAPI +#endif /* Header includes */ #include @@ -50,36 +54,31 @@ static uint32_t sysUntil(VB *sim, uint32_t clocks) { /******************************* API Functions *******************************/ /* Associate two simulations as peers, or remove an association */ -void vbConnect(VB *a, VB *b) { +void vbConnect(VB *sim1, VB *sim2) { /* Disconnect */ - if (b == NULL) { - if (a->peer != NULL) - a->peer->peer = NULL; - a->peer = NULL; + if (sim2 == NULL) { + if (sim1->peer != NULL) + sim1->peer->peer = NULL; + sim1->peer = NULL; return; } - /* The simulations are already linked */ - if (a->peer == b && b->peer == a) - return; - /* Disconnect any existing link associations */ - if (a->peer != NULL && a->peer != b) - a->peer->peer = NULL; - if (b->peer != NULL && b->peer != a) - b->peer->peer = NULL; + if (sim1->peer != NULL && sim1->peer != sim2) + sim1->peer->peer = NULL; + if (sim2->peer != NULL && sim2->peer != sim1) + sim2->peer->peer = NULL; /* Link the two simulations */ - a->peer = b; - b->peer = a; + sim1->peer = sim2; + sim2->peer = sim1; } /* Process one simulation */ int vbEmulate(VB *sim, uint32_t *clocks) { int broke; /* The simulation requested an application break */ uint32_t until; /* Maximum clocks before a break could happen */ - int x; /* Iterator */ /* Process the simulation until a break condition occurs */ do { @@ -114,18 +113,20 @@ int vbEmulateMulti(VB **sims, int count, uint32_t *clocks) { /* Retrieve a current breakpoint callback */ void* vbGetCallback(VB *sim, int type) { - /* -Wpedantic ignored for pointer conversion because no alternative */ - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wpedantic" + void **field; /* Pointer to field within simulation */ + + /* Select the field to update */ switch (type) { - case VB_ONEXCEPTION: return sim->onException; - case VB_ONEXECUTE : return sim->onExecute; - case VB_ONFETCH : return sim->onFetch; - case VB_ONREAD : return sim->onRead; - case VB_ONWRITE : return sim->onWrite; + case VB_ONEXCEPTION: field = (void *) &sim->onException; break; + case VB_ONEXECUTE : field = (void *) &sim->onExecute ; break; + case VB_ONFETCH : field = (void *) &sim->onFetch ; break; + case VB_ONREAD : field = (void *) &sim->onRead ; break; + case VB_ONWRITE : field = (void *) &sim->onWrite ; break; + default: return NULL; } - #pragma GCC diagnostic pop - return NULL; + + /* Retrieve the simulation field */ + return *field; } /* Retrieve the value of PC */ @@ -231,18 +232,24 @@ void vbReset(VB *sim) { } /* Specify a breakpoint callback */ -void vbSetCallback(VB *sim, int type, void *callback) { - /* -Wpedantic ignored for pointer conversion because no alternative */ - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wpedantic" +void* vbSetCallback(VB *sim, int type, void *callback) { + void **field; /* Pointer to field within simulation */ + void *prev; /* Previous value within field */ + + /* Select the field to update */ switch (type) { - case VB_ONEXCEPTION: sim->onException=(VB_EXCEPTIONPROC)callback;break; - case VB_ONEXECUTE : sim->onExecute =(VB_EXECUTEPROC )callback;break; - case VB_ONFETCH : sim->onFetch =(VB_FETCHPROC )callback;break; - case VB_ONREAD : sim->onRead =(VB_READPROC )callback;break; - case VB_ONWRITE : sim->onWrite =(VB_WRITEPROC )callback;break; + case VB_ONEXCEPTION: field = (void *) &sim->onException; break; + case VB_ONEXECUTE : field = (void *) &sim->onExecute ; break; + case VB_ONFETCH : field = (void *) &sim->onFetch ; break; + case VB_ONREAD : field = (void *) &sim->onRead ; break; + case VB_ONWRITE : field = (void *) &sim->onWrite ; break; + return NULL; } - #pragma GCC diagnostic pop + + /* Update the simulation field */ + prev = *field; + *field = callback; + return prev; } /* Specify a new value for PC */ diff --git a/core/vb.h b/core/vb.h index fbca175..f4d40be 100644 --- a/core/vb.h +++ b/core/vb.h @@ -181,7 +181,7 @@ VBAPI uint32_t vbGetSystemRegister (VB *sim, int id); VBAPI void vbInit (VB *sim); VBAPI int32_t vbRead (VB *sim, uint32_t address, int type, int debug); VBAPI void vbReset (VB *sim); -VBAPI void vbSetCallback (VB *sim, int type, void *callback); +VBAPI void* vbSetCallback (VB *sim, int type, void *callback); VBAPI uint32_t vbSetProgramCounter (VB *sim, uint32_t value); VBAPI int32_t vbSetProgramRegister (VB *sim, int id, int32_t value); VBAPI int vbSetROM (VB *sim, void *rom, uint32_t size); diff --git a/makefile b/makefile index f923d31..5006fc9 100644 --- a/makefile +++ b/makefile @@ -1,7 +1,7 @@ .PHONY: help help: @echo - @echo "Virtual Boy Emulator - April 14, 2022" + @echo "Virtual Boy Emulator - April 20, 2022" @echo @echo "Target build environment is any Debian with the following packages:" @echo " emscripten" @@ -39,6 +39,10 @@ core: -fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90 @gcc core/vb.c -I core -D VB_LITTLEENDIAN \ -fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90 + @emcc core/vb.c -I core \ + -fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90 + @emcc core/vb.c -I core -D VB_LITTLEENDIAN \ + -fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90 .PHONY: wasm wasm: diff --git a/wasm/wasm.c b/wasm/wasm.c index 44a734f..bfddec2 100644 --- a/wasm/wasm.c +++ b/wasm/wasm.c @@ -124,7 +124,3 @@ EMSCRIPTEN_KEEPALIVE void SingleStep(VB *sim0, VB *sim1) { vbSetCallback(sim0, VB_ONFETCH, NULL); } - -EMSCRIPTEN_KEEPALIVE uint32_t Clocks(VB *sim) { - return sim->cpu.clocks; -}