Tweaks from concurrent project

This commit is contained in:
Guy Perfect 2022-04-20 22:37:05 -05:00
parent 3cf006ba13
commit 7d31c55391
14 changed files with 570 additions and 266 deletions

View File

@ -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 " +

View File

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

View File

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

View File

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

View File

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

View File

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

125
app/toolkit/DropDown.js Normal file
View File

@ -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 };

View File

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

View File

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

View File

@ -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] +

View File

@ -1,4 +1,8 @@
#define VBAPI VB_EXPORT
#ifdef VB_EXPORT
#define VBAPI VB_EXPORT
#else
#define VBAPI
#endif
/* Header includes */
#include <float.h>
@ -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 */

View File

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

View File

@ -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:

View File

@ -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;
}