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); this.core.onsubscriptions = e=>this.onSubscriptions(e);
// Temporary config debugging // Temporary config debugging
console.log("Memory keyboard commands:");
console.log(" Ctrl+G: Goto");
console.log("Disassembler keyboard commands:"); 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(" F10: Run to next");
console.log(" F11: Single step"); console.log(" F11: Single step");
console.log("Call dasm(\"key\", value) in the console " + console.log("Call dasm(\"key\", value) in the console " +

View File

@ -100,6 +100,7 @@ class Line {
constructor(parent, first) { constructor(parent, first) {
// Configure instance fields // Configure instance fields
this.first = first;
this.parent = parent; this.parent = parent;
// Configure labels // Configure labels
@ -133,7 +134,7 @@ class Line {
} }
// Update style according to selection // Update style according to selection
let method = isPC ? "add" : "remove"; let method = row && isPC ? "add" : "remove";
this.lblAddress .classList[method]("tk-selected"); this.lblAddress .classList[method]("tk-selected");
this.lblBytes .classList[method]("tk-selected"); this.lblBytes .classList[method]("tk-selected");
this.lblMnemonic.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 // Specify whether the elements on this line are visible
setVisible(visible) { setVisible(visible) {
visible = visible ? "block" : "none";
this.lblAddress .style.display = visible; // Column elements
this.lblBytes .style.display = visible; let columns = [
this.lblMnemonic.style.display = visible; this.lblAddress,
this.lblOperands.style.display = visible; 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.columns = [ 0, 0, 0, 0 ];
this.data = []; this.data = [];
this.debug = debug; this.debug = debug;
this.hasBytes = true;
this.isSubscribed = false; this.isSubscribed = false;
this.lines = null; this.lines = null;
this.pc = this.address; this.pc = this.address;
@ -294,8 +318,30 @@ class Disassembler extends Toolkit.ScrollPane {
onKeyDown(e) { onKeyDown(e) {
let tall = this.tall(false); 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 // Navigation
case "ArrowDown" : this.fetch(+1 , true); break; case "ArrowDown" : this.fetch(+1 , true); break;
@ -309,13 +355,6 @@ class Disassembler extends Toolkit.ScrollPane {
case "ArrowRight": this.horizontal.setValue( case "ArrowRight": this.horizontal.setValue(
this.horizontal.value + this.horizontal.increment); break; this.horizontal.value + this.horizontal.increment); break;
// Goto
case "g": case "G":
if (!e.ctrlKey)
return;
this.promptGoto();
break;
// Single step // Single step
case "F10": case "F10":
this.debug.runNext(); 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 // Represent a hexadecimal value
hex(value, digits) { hex(value, digits) {
let sign = Util.s32(value) < 0 ? "-" : ""; let sign = Util.s32(value) < 0 ? "-" : "";
@ -629,12 +678,29 @@ class Disassembler extends Toolkit.ScrollPane {
return this.proregCaps ? ret.toUpperCase() : ret; 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 // Measure how many rows of output are visible
tall(partial) { tall(partial) {
let lineHeight = !this.metrics ? 0 : let lineHeight = !this.metrics ? 0 :
Math.ceil(this.metrics.getBounds().height); Math.ceil(this.metrics.getBounds().height);
return lineHeight <= 0 ? 1 : Math.max(1, Math[partial?"ceil":"floor"]( 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 // Text to hex digit conversion
const DIGITS = { const DIGITS = {
"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "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 // Update the elements' display
refresh() { refresh() {
let address = Util.u32(this.parent.address + this.index * 16); let bus = this.parent[this.parent.bus];
let data = this.parent.data; let address = this.parent.mask(bus.address + this.index * 16);
let dataAddress = this.parent.dataAddress; let data = bus.data;
let dataAddress = bus.dataAddress;
let hexCaps = this.parent.dasm.hexCaps; 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 // Format the line's address
let text = address.toString(16).padStart(8, "0"); let text = address.toString(16).padStart(8, "0");
@ -59,9 +64,12 @@ class Line {
this.lblAddress.innerText = text; this.lblAddress.innerText = text;
// The line's data is not available // The line's data is not available
if (offset < 0 || offset >= data.length) if (offset < 0 || offset >= data.length) {
for (let lbl of this.lblBytes) for (let lbl of this.lblBytes) {
lbl.innerText = "--"; lbl.innerText = "--";
lbl.classList.remove("tk-selected");
}
}
// The line's data is available // The line's data is available
else for (let x = 0; x < 16; x++, offset++) { else for (let x = 0; x < 16; x++, offset++) {
@ -69,14 +77,14 @@ class Line {
text = data[offset].toString(16).padStart(2, "0"); text = data[offset].toString(16).padStart(2, "0");
// The byte is the current selection // The byte is the current selection
if (Util.u32(address + x) == this.parent.selection) { if (Util.u32(address + x) == bus.selection) {
lbl.classList.add("selected"); lbl.classList.add("tk-selected");
if (this.parent.digit !== null) if (this.parent.digit !== null)
text = this.parent.digit.toString(16); text = this.parent.digit.toString(16);
} }
// The byte is not the current selection // The byte is not the current selection
else lbl.classList.remove("selected"); else lbl.classList.remove("tk-selected");
// Update the label's text // Update the label's text
if (hexCaps) if (hexCaps)
@ -103,41 +111,59 @@ class Line {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Memory hex editor // Memory hex editor
class Memory extends Toolkit.ScrollPane { class Memory extends Toolkit.Component {
///////////////////////// Initialization Methods ////////////////////////// ///////////////////////// Initialization Methods //////////////////////////
constructor(debug) { constructor(debug) {
super(debug.app, { 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, horizontal: Toolkit.ScrollPane.AS_NEEDED,
focusable : true, focusable : true,
tabStop : true, tabStop : true,
tagName : "div", tagName : "div",
vertical : Toolkit.ScrollPane.NEVER vertical : Toolkit.ScrollPane.NEVER
}); });
this.append(this.editor);
// 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;
// Configure view // Configure view
let view = document.createElement("div"); this.view = document.createElement("div");
view.className = "tk tk-view"; this.view.className = "tk tk-view";
Object.assign(view.style, { Object.assign(this.view.style, {
display : "grid", display : "grid",
gridTemplateColumns: "repeat(17, max-content)" gridTemplateColumns: "repeat(17, max-content)"
}); });
this.setView(view); this.editor.setView(this.view);
// Font-measuring element // Font-measuring element
this.metrics = new Toolkit.Component(this.app, { this.metrics = new Toolkit.Component(this.app, {
@ -152,7 +178,7 @@ class Memory extends Toolkit.ScrollPane {
this.append(this.metrics.element); this.append(this.metrics.element);
// Configure event handlers // 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("keydown" , e=>this.onKeyDown (e));
this.addEventListener("pointerdown", e=>this.onPointerDown(e)); this.addEventListener("pointerdown", e=>this.onPointerDown(e));
this.addEventListener("wheel" , e=>this.onMouseWheel (e)); this.addEventListener("wheel" , e=>this.onMouseWheel (e));
@ -164,23 +190,25 @@ class Memory extends Toolkit.ScrollPane {
// Typed a digit // Typed a digit
onDigit(digit) { onDigit(digit) {
let bus = this[this.bus];
// Begin an edit // Begin an edit
if (this.digit === null) { if (this.digit === null) {
this.digit = digit; this.digit = digit;
this.setSelection(this.selection, true); this.setSelection(bus.selection, true);
} }
// Complete an edit // Complete an edit
else { else {
this.digit = this.digit << 4 | digit; this.digit = this.digit << 4 | digit;
this.setSelection(this.selection + 1); this.setSelection(bus.selection + 1);
} }
} }
// Key press // Key press
onKeyDown(e) { onKeyDown(e) {
let bus = this[this.bus];
let key = e.key; let key = e.key;
// A hex digit was entered // A hex digit was entered
@ -189,39 +217,44 @@ class Memory extends Toolkit.ScrollPane {
key = "digit"; key = "digit";
} }
// Processing by key // Ctrl key is pressed
switch (key) { 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 // Arrow key navigation
case "ArrowDown" : this.setSelection(this.selection + 16); break; case "ArrowDown" : this.setSelection(bus.selection + 16); break;
case "ArrowLeft" : this.setSelection(this.selection - 1); break; case "ArrowLeft" : this.setSelection(bus.selection - 1); break;
case "ArrowRight": this.setSelection(this.selection + 1); break; case "ArrowRight": this.setSelection(bus.selection + 1); break;
case "ArrowUp" : this.setSelection(this.selection - 16); break; case "ArrowUp" : this.setSelection(bus.selection - 16); break;
// Commit current edit // Commit current edit
case "Enter": case "Enter":
case " ": case " ":
if (this.digit !== null) if (this.digit !== null)
this.setSelection(this.selection); this.setSelection(bus.selection);
break;
// Goto
case "g": case "G":
if (!e.ctrlKey)
return;
this.promptGoto();
break; break;
// Page key navigation // Page key navigation
case "PageDown": case "PageDown":
this.setSelection(this.selection + this.tall(false) * 16); this.setSelection(bus.selection + this.tall(false) * 16);
break; break;
case "PageUp": case "PageUp":
this.setSelection(this.selection - this.tall(false) * 16); this.setSelection(bus.selection - this.tall(false) * 16);
break; break;
// Hex digit: already processed // Hex digit: already processed
case "digit": break; case "digit": break;
default: return; default: return;
} }
@ -243,15 +276,14 @@ class Memory extends Toolkit.ScrollPane {
return; return;
// Update the display address // Update the display address
this.address = Util.u32(this.address + offset); this.fetch(this[this.bus].address + offset, true);
this.fetch(this.address, true);
} }
// Pointer down // Pointer down
onPointerDown(e) { onPointerDown(e) {
// Common handling // Common handling
this.focus(); this.editor.focus();
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -261,8 +293,11 @@ class Memory extends Toolkit.ScrollPane {
// Determine the row that was clicked on // Determine the row that was clicked on
let lineHeight = !this.metrics ? 0 : let lineHeight = !this.metrics ? 0 :
Math.max(1, Math.ceil(this.metrics.getBounds().height)); Math.max(0, Math.ceil(this.metrics.getBounds().height));
let y = Math.floor((e.y - this.getBounds().top) / lineHeight); if (lineHeight == 0)
return;
let y = Math.floor(
(e.y - this.view.getBoundingClientRect().top) / lineHeight);
// Determine the column that was clicked on // Determine the column that was clicked on
let columns = this.lines[0].lblBytes; let columns = this.lines[0].lblBytes;
@ -274,7 +309,7 @@ class Memory extends Toolkit.ScrollPane {
// The current column was clicked: update the selection // The current column was clicked: update the selection
if (e.x < (x == 15 ? bndCur.right : if (e.x < (x == 15 ? bndCur.right :
bndCur.right + (bndNext.left - bndCur.right) / 2)) { bndCur.right + (bndNext.left - bndCur.right) / 2)) {
this.setSelection(this.address + y * 16 + x); this.setSelection(this[this.bus].address + y * 16 + x);
return; return;
} }
@ -303,12 +338,12 @@ class Memory extends Toolkit.ScrollPane {
this.lines[x].setVisible(false); this.lines[x].setVisible(false);
// Configure horizontal scroll bar // Configure horizontal scroll bar
if (this.metrics) if (this.metrics) this.editor.horizontal
this.horizontal.setIncrement(this.metrics.getBounds().width); .setIncrement(this.metrics.getBounds().width);
// Update the display // Update the display
if (fetch) if (fetch)
this.fetch(this.address, true); this.fetch(this[this.bus].address, true);
else this.refresh(); else this.refresh();
} }
@ -318,11 +353,12 @@ class Memory extends Toolkit.ScrollPane {
// Update with memory state from the core // Update with memory state from the core
refresh(data) { refresh(data) {
let bus = this[this.bus];
// Update with data from the core thread // Update with data from the core thread
if (data) { if (data) {
this.data = data.bytes; bus.data = data.bytes;
this.dataAddress = data.address; bus.dataAddress = data.address;
} }
// Update elements // Update elements
@ -343,7 +379,7 @@ class Memory extends Toolkit.ScrollPane {
// Subscribe to core updates // Subscribe to core updates
if (subscribed) if (subscribed)
this.fetch(this.address); this.fetch(this[this.bus].address);
// Unsubscribe from core updates // Unsubscribe from core updates
else this.sim.unsubscribe("memory"); else this.sim.unsubscribe("memory");
@ -366,7 +402,7 @@ class Memory extends Toolkit.ScrollPane {
async fetch(address, prefresh) { async fetch(address, prefresh) {
// Configure instance fields // Configure instance fields
this.address = address; this[this.bus].address = address = this.mask(address);
// Update the view immediately // Update the view immediately
if (prefresh) 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 // Prompt the user to specify a new address
promptGoto() { promptGoto() {
@ -397,17 +438,23 @@ class Memory extends Toolkit.ScrollPane {
// The address is not currently visible in the output // The address is not currently visible in the output
let tall = this.tall(false); let tall = this.tall(false);
if (Util.u32(address - this.address) >= tall * 16) { if (Util.u32(address - this.address) >= tall * 16)
this.fetch(Util.u32( this.fetch((address & 0xFFFFFFF0) - Math.floor(tall / 3) * 16);
(address & 0xFFFFFFF0) - Math.floor(tall / 3) * 16));
}
// Move the selection and refresh the display // 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 // Specify which byte is selected
setSelection(address, noCommit) { setSelection(address, noCommit) {
let bus = this[this.bus];
let fetch = false; let fetch = false;
// Commit a pending data entry // Commit a pending data entry
@ -418,27 +465,27 @@ class Memory extends Toolkit.ScrollPane {
} }
// Configure instance fields // Configure instance fields
this.selection = address = Util.u32(address); bus.selection = address = this.mask(address);
// Working variables // 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 // The new address is above the top line of output
if (row < 0) { if (row < 0) {
this.fetch(Util.u32(this.address + row * 16), true); this.fetch(bus.address + row * 16 & 0xFFFFFFF0, true);
return; return;
} }
// The new address is below the bottom line of output // The new address is below the bottom line of output
let tall = this.tall(false); let tall = this.tall(false);
if (row >= tall) { if (row >= tall) {
this.fetch(Util.u32(address - tall * 16 + 16 & 0xFFFFFFF0), true); this.fetch(address - tall * 16 + 16 & 0xFFFFFFF0, true);
return; return;
} }
// Update the display // Update the display
if (fetch) if (fetch)
this.fetch(this.address, true); this.fetch(bus.address, true);
else this.refresh(); else this.refresh();
} }
@ -447,14 +494,17 @@ class Memory extends Toolkit.ScrollPane {
let lineHeight = !this.metrics ? 0 : let lineHeight = !this.metrics ? 0 :
Math.ceil(this.metrics.getBounds().height); Math.ceil(this.metrics.getBounds().height);
return lineHeight <= 0 ? 1 : Math.max(1, Math[partial?"ceil":"floor"]( 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 a value to the core thread
write(value) { 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.sim.write(
this.selection, bus.selection,
Uint8Array.from([ value ]), { Uint8Array.from([ value ]), {
refresh: true refresh: true
}); });

View File

@ -124,24 +124,20 @@ class Register {
this.app = list.app; this.app = list.app;
this.controls = []; this.controls = [];
this.dasm = list.dasm; this.dasm = list.dasm;
this.format = HEX;
this.index = index; this.index = index;
this.isExpanded = null; this.isExpanded = null;
this.list = list; this.list = list;
this.metrics = { width: 0, height: 0 };
this.orMask = orMask; this.orMask = orMask;
this.sim = list.sim; this.sim = list.sim;
this.system = list.system; this.system = list.system;
this.type = HEX;
this.value = 0x00000000; this.value = 0x00000000;
// Configure main controls // Establish elements
this.contents = new Toolkit.Component(this.app, { let row = document.createElement("tr");
className: "tk tk-contents", let cell;
tagName : "div", list.view.append(row);
style : {
display : "grid",
gridTemplateColumns: "max-content auto max-content"
}
});
// Processing by type // Processing by type
this[this.system ? "initSystem" : "initProgram"](); this[this.system ? "initSystem" : "initProgram"]();
@ -151,7 +147,10 @@ class Register {
className: "tk tk-expand tk-mono", className: "tk tk-expand tk-mono",
tagName : "div" 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 // Name label
this.lblName = document.createElement("div"); this.lblName = document.createElement("div");
@ -161,7 +160,9 @@ class Register {
innerText: this.dasm.sysregCaps?this.name:this.name.toLowerCase() innerText: this.dasm.sysregCaps?this.name:this.name.toLowerCase()
}); });
this.lblName.style.userSelect = "none"; 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 // Value text box
this.txtValue = new Toolkit.TextBox(this.app, { this.txtValue = new Toolkit.TextBox(this.app, {
@ -171,7 +172,13 @@ class Register {
this.txtValue.setAttribute("aria-labelledby", this.lblName.id); this.txtValue.setAttribute("aria-labelledby", this.lblName.id);
this.txtValue.setAttribute("digits", "8"); this.txtValue.setAttribute("digits", "8");
this.txtValue.addEventListener("action", e=>this.onValue()); 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 // Expansion area
if (this.expansion != null) if (this.expansion != null)
@ -226,17 +233,23 @@ class Register {
let two = this.index == ECR || this.index == PIR; let two = this.index == ECR || this.index == PIR;
// Establish expansion element // 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", className: "tk tk-expansion",
id : Toolkit.id(), id : Toolkit.id(),
tagName : "div", tagName : "div",
style : { style : {
display : "none", display : "grid",
gridColumnEnd : "span 3",
gridTemplateColumns: gridTemplateColumns:
this.system ? "repeat(2, max-content)" : "max-content" 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 // Produce program register controls
if (!this.system) { if (!this.system) {
@ -256,7 +269,7 @@ class Register {
// Configure event handler // Configure event handler
ctrl.addEventListener("action", ctrl.addEventListener("action",
e=>this.setType(e.component.format)); e=>this.setFormat(e.component.format));
// Add the control to the element // Add the control to the element
let box = document.createElement("div"); let box = document.createElement("div");
@ -405,7 +418,7 @@ class Register {
let value; let value;
// Processing by type // Processing by type
switch (this.type) { switch (this.format) {
// Unsigned hexadecimal // Unsigned hexadecimal
case HEX: case HEX:
@ -431,25 +444,6 @@ class Register {
this.setValue(isNaN(value) ? this.value : value); 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 ///////////////////////////// ///////////////////////////// Package Methods /////////////////////////////
@ -483,17 +477,10 @@ class Register {
let text; let text;
// Configure instance fields // Configure instance fields
this.value = value; this.value = value = Util.u32(value);
// Value text box // Value text box
if (this.type == HEX) { switch (this.format) {
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) {
// Unsigned hexadecimal // Unsigned hexadecimal
case HEX: 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 ///////////////////////////// ///////////////////////////// Private Methods /////////////////////////////
// Specify the formatting type of the register value // Specify the formatting type of the register value
setType(type) { setFormat(format) {
if (type == this.type) if (format == this.format)
return; 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); this.refresh(this.value);
} }
@ -604,6 +646,7 @@ class RegisterList extends Toolkit.ScrollPane {
this.app = debug.app; this.app = debug.app;
this.dasm = debug.disassembler; this.dasm = debug.disassembler;
this.method = system?"getSystemRegisters":"getProgramRegisters"; this.method = system?"getSystemRegisters":"getProgramRegisters";
this.registers = [];
this.sim = debug.sim; this.sim = debug.sim;
this.subscription = system ? "sysregs" : "proregs"; this.subscription = system ? "sysregs" : "proregs";
this.system = system; this.system = system;
@ -611,10 +654,9 @@ class RegisterList extends Toolkit.ScrollPane {
// Configure view element // Configure view element
this.setView(new Toolkit.Component(debug.app, { this.setView(new Toolkit.Component(debug.app, {
className: "tk tk-list", className: "tk tk-list",
tagName : "div", tagName : "table",
style : { style : {
display : "grid", width: "100%"
gridTemplateColumns: "max-content auto max-content"
} }
})); }));
@ -647,27 +689,27 @@ class RegisterList extends Toolkit.ScrollPane {
// Initialize a list of program registers // Initialize a list of program registers
initProgram() { initProgram() {
this[0] = new Register(this, 0, 0x00000000, 0x00000000); this.add(new Register(this, 0, 0x00000000, 0x00000000));
for (let x = 1; x < 32; x++) 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 // Initialie a list of system registers
initSystem() { initSystem() {
this[PC ] = new Register(this, PC , 0xFFFFFFFE, 0x00000000); this.add(new Register(this, PC , 0xFFFFFFFE, 0x00000000));
this[PSW ] = new Register(this, PSW , 0x000FF3FF, 0x00000000); this.add(new Register(this, PSW , 0x000FF3FF, 0x00000000));
this[ADTRE] = new Register(this, ADTRE, 0xFFFFFFFE, 0x00000000); this.add(new Register(this, ADTRE, 0xFFFFFFFE, 0x00000000));
this[CHCW ] = new Register(this, CHCW , 0x00000002, 0x00000000); this.add(new Register(this, CHCW , 0x00000002, 0x00000000));
this[ECR ] = new Register(this, ECR , 0xFFFFFFFF, 0x00000000); this.add(new Register(this, ECR , 0xFFFFFFFF, 0x00000000));
this[EIPC ] = new Register(this, EIPC , 0xFFFFFFFE, 0x00000000); this.add(new Register(this, EIPC , 0xFFFFFFFE, 0x00000000));
this[EIPSW] = new Register(this, EIPSW, 0x000FF3FF, 0x00000000); this.add(new Register(this, EIPSW, 0x000FF3FF, 0x00000000));
this[FEPC ] = new Register(this, FEPC , 0xFFFFFFFE, 0x00000000); this.add(new Register(this, FEPC , 0xFFFFFFFE, 0x00000000));
this[FEPSW] = new Register(this, FEPSW, 0x000FF3FF, 0x00000000); this.add(new Register(this, FEPSW, 0x000FF3FF, 0x00000000));
this[PIR ] = new Register(this, PIR , 0x00000000, 0x00005346); this.add(new Register(this, PIR , 0x00000000, 0x00005346));
this[TKCW ] = new Register(this, TKCW , 0x00000000, 0x000000E0); this.add(new Register(this, TKCW , 0x00000000, 0x000000E0));
this[29 ] = new Register(this, 29 , 0xFFFFFFFF, 0x00000000); this.add(new Register(this, 29 , 0xFFFFFFFF, 0x00000000));
this[30 ] = new Register(this, 30 , 0x00000000, 0x00000004); this.add(new Register(this, 30 , 0x00000000, 0x00000004));
this[31 ] = new Register(this, 31 , 0xFFFFFFFF, 0x00000000); this.add(new Register(this, 31 , 0xFFFFFFFF, 0x00000000));
} }
@ -725,14 +767,9 @@ class RegisterList extends Toolkit.ScrollPane {
let width = Math.ceil(bounds.width); let width = Math.ceil(bounds.width);
let height = Math.ceil(bounds.height / 32); let height = Math.ceil(bounds.height / 32);
// Resize all monospaced text boxes // Resize all text boxes
for (let box of this.element for (let reg of this.registers)
.querySelectorAll(".tk-textbox[digits]")) { reg.setMetrics(width, height);
Object.assign(box.style, {
height: height + "px",
width : (parseInt(box.getAttribute("digits")) * width) + "px"
});
}
// Update scroll bars // Update scroll bars
this.horizontal.setIncrement(height); this.horizontal.setIncrement(height);
@ -765,20 +802,8 @@ class RegisterList extends Toolkit.ScrollPane {
// System registers // System registers
if (this.system) { if (this.system) {
this[ADTRE].refresh(registers.adtre); for (let reg of Object.entries(SYSREGS))
this[CHCW ].refresh(registers.chcw ); this[reg[0]].refresh(registers[reg[1].toLowerCase()]);
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] );
} }
// Program registers // Program registers
@ -811,13 +836,8 @@ class RegisterList extends Toolkit.ScrollPane {
// Disassembler settings have been updated // Disassembler settings have been updated
dasmChanged() { dasmChanged() {
if (this.system) { for (let reg of this.registers)
for (let key of Object.keys(SYSREGS)) reg.dasmChanged();
this[key].dasmChanged();
} else {
for (let key = 0; key < 32; key++)
this[key].dasmChanged();
}
} }
// Determine the initial size of the register list // Determine the initial size of the register list
@ -836,7 +856,7 @@ class RegisterList extends Toolkit.ScrollPane {
// Locate the bottom of PSW // Locate the bottom of PSW
if (this.system && this[PSW].expansion) { if (this.system && this[PSW].expansion) {
ret.height = this[PSW].expansion.getBounds().bottom - ret.height = this[PSW].expansion.getBoundingClientRect().bottom -
this.view.getBounds().top; this.view.getBounds().top;
} }
@ -847,6 +867,12 @@ class RegisterList extends Toolkit.ScrollPane {
///////////////////////////// Private Methods ///////////////////////////// ///////////////////////////// Private Methods /////////////////////////////
// Add a register to the list
add(reg) {
this[reg.index] = reg;
this.registers.push(reg);
}
// Retrieve CPU state from the core // Retrieve CPU state from the core
async fetch() { async fetch() {
this.refresh( this.refresh(

View File

@ -321,7 +321,6 @@ new class CoreWorker {
address: address, address: address,
bytes : [ bits & 0xFF, bits >> 8 ], bytes : [ bits & 0xFF, bits >> 8 ],
size : u32(address + 2) == pc ? 2 : size size : u32(address + 2) == pc ? 2 : size
//size : Math.min(u32(pc - address), size)
}; };
// Read additional bytes // Read additional bytes

View File

@ -13,6 +13,11 @@
padding : 0; padding : 0;
} }
table.tk {
border : none;
border-spacing: 0;
}
.tk-body { .tk-body {
overflow: hidden; 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 **********************************/ /********************************* Menu Bar **********************************/
.tk-menu-bar { .tk-menu-bar {
@ -526,7 +544,7 @@
/******************************* Disassembler ********************************/ /************************************ CPU ************************************/
.tk-cpu .tk-main { .tk-cpu .tk-main {
height: 100%; height: 100%;
@ -654,18 +672,21 @@
background: transparent; background: transparent;
border : none; border : none;
padding : 0; padding : 0;
width : 1.5em;
}
.tk-reglist.tk-program .tk-textbox:not(.tk-mono) {
text-align: right;
width : 6em;
} }
.tk-reglist .tk-expansion { .tk-reglist .tk-expansion {
align-items : center; align-items : center;
column-gap : 0.8em;
margin-bottom: 2px; margin-bottom: 2px;
padding : 2px 0 0 1.5em; padding : 2px 0 0 1.5em;
} }
.tk-reglist .tk-expansion {
column-gap: 0.8em;
}
.tk-reglist .tk-expansion .tk-number .tk-label { .tk-reglist .tk-expansion .tk-number .tk-label {
align-items : center; align-items : center;
display : flex; display : flex;
@ -673,10 +694,6 @@
min-width : 12px; 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-reglist .tk-expansion .tk-checkbox[aria-disabled="true"][aria-checked="true"]
.tk-icon:before { .tk-icon:before {
background: var(--tk-control-shadow); background: var(--tk-control-shadow);
@ -694,6 +711,11 @@
/********************************** Memory ***********************************/ /********************************** Memory ***********************************/
.tk-window .tk-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); box-shadow: 0 0 0 1px var(--tk-control),0 0 0 2px var(--tk-control-shadow);
height : calc(100% - 6px); height : calc(100% - 6px);
margin : 3px; margin : 3px;
@ -715,22 +737,25 @@
.tk-memory .tk-byte { .tk-memory .tk-byte {
border : 0 solid transparent; border : 0 solid transparent;
margin-left: calc(0.6em - 1px);
padding : 0 1px; padding : 0 1px;
text-align: center; text-align: center;
} }
.tk-memory .tk-byte.tk-0, .tk-memory .tk-byte:not(.tk-15) {
.tk-memory .tk-byte.tk-8 { margin-right: calc(0.6em - 1px);
margin-left: calc(1.2em - 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); background: var(--tk-selected-blur);
color : var(--tk-selected-blur-text); 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); background: var(--tk-selected);
color : var(--tk-selected-text); 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 { Component } from /**/"./Component.js";
import { Button, CheckBox, Group, Radio } from /**/"./Button.js" ; import { Button, CheckBox, Group, Radio } from /**/"./Button.js" ;
import { DropDown } from /**/"./DropDown.js" ;
import { Menu, MenuBar, MenuItem, MenuSeparator } from /**/"./MenuBar.js" ; import { Menu, MenuBar, MenuItem, MenuSeparator } from /**/"./MenuBar.js" ;
import { ScrollBar, ScrollPane, SplitPane } from /**/"./ScrollBar.js"; import { ScrollBar, ScrollPane, SplitPane } from /**/"./ScrollBar.js";
import { TextBox } from /**/"./TextBox.js" ; import { TextBox } from /**/"./TextBox.js" ;
@ -26,6 +27,7 @@ let Toolkit = globalThis.Toolkit = (class GUI extends Component {
this.components = []; this.components = [];
Button .setToolkit(this); this.components.push(Button .Component); Button .setToolkit(this); this.components.push(Button .Component);
Component.setToolkit(this); this.components.push( Component); Component.setToolkit(this); this.components.push( Component);
DropDown .setToolkit(this); this.components.push(DropDown .Component);
MenuBar .setToolkit(this); this.components.push(MenuBar .Component); MenuBar .setToolkit(this); this.components.push(MenuBar .Component);
ScrollBar.setToolkit(this); this.components.push(ScrollBar.Component); ScrollBar.setToolkit(this); this.components.push(ScrollBar.Component);
TextBox .setToolkit(this); this.components.push(TextBox .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.CheckBox = CheckBox;
this.Component = Component; this.Component = Component;
this.Desktop = Desktop; this.Desktop = Desktop;
this.DropDown = DropDown;
this.Group = Group; this.Group = Group;
this.Menu = Menu; this.Menu = Menu;
this.MenuBar = MenuBar; this.MenuBar = MenuBar;

View File

@ -6,7 +6,7 @@
/***************************** Utility Functions *****************************/ /***************************** Utility Functions *****************************/
/* Read a data unit from a memory buffer */ /* 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 */ /* Little-endian implementation */
#ifdef VB_LITTLEENDIAN #ifdef VB_LITTLEENDIAN
@ -33,7 +33,7 @@ static int32_t busReadMemory(uint8_t *mem, int type) {
} }
/* Write a data unit to a memory buffer */ /* 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 */ /* Little-endian implementation */
#ifdef VB_LITTLEENDIAN #ifdef VB_LITTLEENDIAN
@ -72,17 +72,17 @@ static int32_t busRead(VB *sim, uint32_t address, int type, int debug) {
/* Process by address range */ /* Process by address range */
switch (address >> 24 & 7) { switch (address >> 24 & 7) {
case 0 : return 0; /* VIP */ 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 2 : return 0; /* Miscellaneous hardware */
case 3 : return 0; /* Unmapped */ case 3 : return 0; /* Unmapped */
case 4 : return 0; /* Game pak expansion */ case 4 : return 0; /* Game pak expansion */
case 5 : return /* WRAM */ 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 */ 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); [address & (sim->cart.sramSize - 1)], type);
default: return sim->cart.rom == NULL ? 0 : /* Game pak ROM */ default: return sim->cart.rom == NULL ? 0 : /* Game pak ROM */
busReadMemory(&sim->cart.rom busReadBuffer(&sim->cart.rom
[address & (sim->cart.romSize - 1)], type); [address & (sim->cart.romSize - 1)], type);
} }
@ -103,16 +103,16 @@ static void busWrite(
case 3 : return; /* Unmapped */ case 3 : return; /* Unmapped */
case 4 : return; /* Game pak expansion */ case 4 : return; /* Game pak expansion */
case 5 : /* WRAM */ case 5 : /* WRAM */
busWriteMemory(&sim->wram[address & 0xFFFF], type, value); busWriteBuffer(&sim->wram[address & 0xFFFF], type, value);
return; return;
case 6 : /* Cartridge RAM */ case 6 : /* Cartridge RAM */
if (sim->cart.sram != NULL) if (sim->cart.sram != NULL)
busWriteMemory(&sim->cart.sram busWriteBuffer(&sim->cart.sram
[address & (sim->cart.sramSize - 1)], type, value); [address & (sim->cart.sramSize - 1)], type, value);
return; return;
default: /* Cartridge ROM */ default: /* Cartridge ROM */
if (debug && sim->cart.rom != NULL) if (debug && sim->cart.rom != NULL)
busWriteMemory(&sim->cart.rom busWriteBuffer(&sim->cart.rom
[address & (sim->cart.romSize - 1)], type, value); [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 */ /* TODO: Perform dump/restore operations */
return value & 0x00000002; return value & 0x00000002;
case VB_ECR : case VB_ECR :
if (debug) { if (!debug)
return (uint32_t) sim->cpu.ecr.fecc << 16 | sim->cpu.ecr.eicc;
sim->cpu.ecr.fecc = value >> 16; sim->cpu.ecr.fecc = value >> 16;
sim->cpu.ecr.eicc = value; sim->cpu.ecr.eicc = value;
return value; return value;
}
return vbGetSystemRegister(sim, id);
case VB_PSW : case VB_PSW :
sim->cpu.psw.i = value >> 16 & 15; sim->cpu.psw.i = value >> 16 & 15;
sim->cpu.psw.np = value >> 15 & 1; 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 */ /* Initiate the write */
if (sim->cpu.busWait == 0) { if (sim->cpu.busWait == 0) {
/* Read the data unit from the bus */ /* Write the data unit to the bus */
if (cpuWrite( if (cpuWrite(
sim, sim,
sim->cpu.program[inst->bits[0] & 0x1F] + sim->cpu.program[inst->bits[0] & 0x1F] +

View File

@ -1,4 +1,8 @@
#ifdef VB_EXPORT
#define VBAPI VB_EXPORT #define VBAPI VB_EXPORT
#else
#define VBAPI
#endif
/* Header includes */ /* Header includes */
#include <float.h> #include <float.h>
@ -50,36 +54,31 @@ static uint32_t sysUntil(VB *sim, uint32_t clocks) {
/******************************* API Functions *******************************/ /******************************* API Functions *******************************/
/* Associate two simulations as peers, or remove an association */ /* Associate two simulations as peers, or remove an association */
void vbConnect(VB *a, VB *b) { void vbConnect(VB *sim1, VB *sim2) {
/* Disconnect */ /* Disconnect */
if (b == NULL) { if (sim2 == NULL) {
if (a->peer != NULL) if (sim1->peer != NULL)
a->peer->peer = NULL; sim1->peer->peer = NULL;
a->peer = NULL; sim1->peer = NULL;
return; return;
} }
/* The simulations are already linked */
if (a->peer == b && b->peer == a)
return;
/* Disconnect any existing link associations */ /* Disconnect any existing link associations */
if (a->peer != NULL && a->peer != b) if (sim1->peer != NULL && sim1->peer != sim2)
a->peer->peer = NULL; sim1->peer->peer = NULL;
if (b->peer != NULL && b->peer != a) if (sim2->peer != NULL && sim2->peer != sim1)
b->peer->peer = NULL; sim2->peer->peer = NULL;
/* Link the two simulations */ /* Link the two simulations */
a->peer = b; sim1->peer = sim2;
b->peer = a; sim2->peer = sim1;
} }
/* Process one simulation */ /* Process one simulation */
int vbEmulate(VB *sim, uint32_t *clocks) { int vbEmulate(VB *sim, uint32_t *clocks) {
int broke; /* The simulation requested an application break */ int broke; /* The simulation requested an application break */
uint32_t until; /* Maximum clocks before a break could happen */ uint32_t until; /* Maximum clocks before a break could happen */
int x; /* Iterator */
/* Process the simulation until a break condition occurs */ /* Process the simulation until a break condition occurs */
do { do {
@ -114,18 +113,20 @@ int vbEmulateMulti(VB **sims, int count, uint32_t *clocks) {
/* Retrieve a current breakpoint callback */ /* Retrieve a current breakpoint callback */
void* vbGetCallback(VB *sim, int type) { void* vbGetCallback(VB *sim, int type) {
/* -Wpedantic ignored for pointer conversion because no alternative */ void **field; /* Pointer to field within simulation */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic" /* Select the field to update */
switch (type) { switch (type) {
case VB_ONEXCEPTION: return sim->onException; case VB_ONEXCEPTION: field = (void *) &sim->onException; break;
case VB_ONEXECUTE : return sim->onExecute; case VB_ONEXECUTE : field = (void *) &sim->onExecute ; break;
case VB_ONFETCH : return sim->onFetch; case VB_ONFETCH : field = (void *) &sim->onFetch ; break;
case VB_ONREAD : return sim->onRead; case VB_ONREAD : field = (void *) &sim->onRead ; break;
case VB_ONWRITE : return sim->onWrite; 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 */ /* Retrieve the value of PC */
@ -231,18 +232,24 @@ void vbReset(VB *sim) {
} }
/* Specify a breakpoint callback */ /* Specify a breakpoint callback */
void vbSetCallback(VB *sim, int type, void *callback) { void* vbSetCallback(VB *sim, int type, void *callback) {
/* -Wpedantic ignored for pointer conversion because no alternative */ void **field; /* Pointer to field within simulation */
#pragma GCC diagnostic push void *prev; /* Previous value within field */
#pragma GCC diagnostic ignored "-Wpedantic"
/* Select the field to update */
switch (type) { switch (type) {
case VB_ONEXCEPTION: sim->onException=(VB_EXCEPTIONPROC)callback;break; case VB_ONEXCEPTION: field = (void *) &sim->onException; break;
case VB_ONEXECUTE : sim->onExecute =(VB_EXECUTEPROC )callback;break; case VB_ONEXECUTE : field = (void *) &sim->onExecute ; break;
case VB_ONFETCH : sim->onFetch =(VB_FETCHPROC )callback;break; case VB_ONFETCH : field = (void *) &sim->onFetch ; break;
case VB_ONREAD : sim->onRead =(VB_READPROC )callback;break; case VB_ONREAD : field = (void *) &sim->onRead ; break;
case VB_ONWRITE : sim->onWrite =(VB_WRITEPROC )callback;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 */ /* 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 void vbInit (VB *sim);
VBAPI int32_t vbRead (VB *sim, uint32_t address, int type, int debug); VBAPI int32_t vbRead (VB *sim, uint32_t address, int type, int debug);
VBAPI void vbReset (VB *sim); 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 uint32_t vbSetProgramCounter (VB *sim, uint32_t value);
VBAPI int32_t vbSetProgramRegister (VB *sim, int id, int32_t value); VBAPI int32_t vbSetProgramRegister (VB *sim, int id, int32_t value);
VBAPI int vbSetROM (VB *sim, void *rom, uint32_t size); VBAPI int vbSetROM (VB *sim, void *rom, uint32_t size);

View File

@ -1,7 +1,7 @@
.PHONY: help .PHONY: help
help: help:
@echo @echo
@echo "Virtual Boy Emulator - April 14, 2022" @echo "Virtual Boy Emulator - April 20, 2022"
@echo @echo
@echo "Target build environment is any Debian with the following packages:" @echo "Target build environment is any Debian with the following packages:"
@echo " emscripten" @echo " emscripten"
@ -39,6 +39,10 @@ core:
-fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90 -fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90
@gcc core/vb.c -I core -D VB_LITTLEENDIAN \ @gcc core/vb.c -I core -D VB_LITTLEENDIAN \
-fsyntax-only -Wall -Wextra -Werror -Wpedantic -std=c90 -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 .PHONY: wasm
wasm: wasm:

View File

@ -124,7 +124,3 @@ EMSCRIPTEN_KEEPALIVE void SingleStep(VB *sim0, VB *sim1) {
vbSetCallback(sim0, VB_ONFETCH, NULL); vbSetCallback(sim0, VB_ONFETCH, NULL);
} }
EMSCRIPTEN_KEEPALIVE uint32_t Clocks(VB *sim) {
return sim->cpu.clocks;
}