pvbemu/web/debugger/CPU.js

1440 lines
44 KiB
JavaScript

import { Core } from /**/"../core/Core.js";
import { Disassembler } from /**/"../core/Disassembler.js";
import { Toolkit } from /**/"../toolkit/Toolkit.js";
let register = Debugger => {
// CPU disassembler and register state window
class CPU extends Toolkit.Window {
///////////////////////// Initialization Methods //////////////////////////
constructor(debug, index) {
super(debug.app, {
class: "tk window cpu"
});
// Configure instance fields
this.debug = debug;
this.height = 300;
this.index = index;
this.shown = false;
this.width = 400;
// Window
this.setTitle("{debug.cpu._}", true);
this.substitute("#", " " + (index + 1));
if (index == 1)
this.element.classList.add("two");
this.addEventListener("close" , e=>this.visible = false);
this.addEventListener("visibility", e=>this.onVisibility(e));
// Client area
Object.assign(this.client.style, {
display : "grid",
gridAutoRows : "100%",
gridTemplateColumns: "auto"
});
// Disassembler
this.disassembler = new DisassemblerPane(this);
this.lastFocus = this.disassembler.view;
// Register lists
this.registers = new RegisterPane(this);
// Main content area
this.add(new Toolkit.SplitPane(debug.app, {
orientation: "right",
primary : this.registers,
secondary : this.disassembler
}));
}
///////////////////////////// Event Handlers //////////////////////////////
// Window key press
onKeyDown(e) {
super.onKeyDown(e);
// Error checking
if (e.altKey || e.shiftKey)
return;
// Processing by key: CTRL down
if (e.ctrlKey) switch (e.key) {
case "b": case "B":
this.disassembler.bytesColumn = !this.disassembler.bytesColumn;
break;
case "f": case "F":
this.disassembler.fitColumns();
break;
case "g": case "G":
this.disassembler.goto();
break;
default: return;
}
// Processing by key: CTRL up
else switch (e.key) {
case "F10":
this.debug.app.runToNext(this.index, { refresh: true });
break;
case "F11":
this.debug.app.singleStep(this.index, { refresh: true });
break;
default: return;
}
Toolkit.handle(e);
}
// Window visibility
onVisibility(e) {
// Configure instance fields
this.shown = this.shown || e.visible;
// Configure subscriptions
if (!e.visible) {
if (this.registers)
this.registers .unsubscribe();
if (this.disassembler)
this.disassembler.unsubscribe();
} else {
this.registers .fetch();
this.disassembler.fetch();
}
}
///////////////////////////// Package Methods /////////////////////////////
// Disassembler configuration has changed
dasmConfigured() {
// Disassembler
this.disassembler.fetch();
// Registers
for (let reg of this.registers.list) {
reg.chkExpand.element.style.removeProperty("min-width");
reg.dasmConfigured();
}
this.registers.regResize();
}
// Window is being shown for the first time
firstShow() {
this.disassembler.firstShow();
this.registers .firstShow();
}
}
///////////////////////////////////////////////////////////////////////////////
class DisassemblerPane extends Toolkit.ScrollPane {
///////////////////////// Initialization Methods //////////////////////////
constructor(cpu) {
super(cpu.debug.app, {
class : "tk scroll-pane scr-dasm",
overflowX: "auto",
overflowY: "hidden"
});
// Configure instance fields
this._bytesColumn = true;
this.columnWidths = new Array(6);
this.cpu = cpu;
this.delta = 0;
this.dasm = null;
this.lines = [];
this.pending = false;
this.subscription = [ 1, cpu.index, "cpu", "disassembler", "refresh" ],
this.viewAddress = 0xFFFFFFF0;
this.viewLine = 10;
// Initialize column widths
for (let x = 0; x < this.columnWidths.length; x++)
this.columnWidths[x] = 0;
// Client area
let view = this.view = new Toolkit.Component(cpu.debug.app, {
class : "tk mono disassembler",
role : "application",
tabIndex: "0",
style : {
display : "grid",
height : "100%",
minWidth: "100%",
overflow: "hidden",
position: "relative",
width : "max-content"
}
});
view.setLabel("{debug.cpu.disassembler}", true);
view.setRoleDescription("{debug.cpu.disassembler}", true);
view.addEventListener("keydown", e=>this.viewKeyDown(e));
view.addEventListener("resize" , e=>this.viewResize ( ));
view.addEventListener("wheel" , e=>this.viewWheel (e));
// Label for measuring text dimensions in the disassembler
this.pc = new Toolkit.Label(cpu.debug.app, {
class : "tk label mono pc",
visible : false,
visibility: true,
style : {
position: "absolute"
}
});
this.pc.setText("\u00a0", false); // &nbsp;
view.append(this.pc);
}
///////////////////////////// Event Handlers //////////////////////////////
// Column resized
colResize() {
if (this.lines.length == 0)
return;
for (let x = 0; x < this.columnWidths.length; x++) {
let elm = this.lines[0].all[x];
let width = elm.getBoundingClientRect().width;
if (width <= this.columnWidths[x])
continue;
this.columnWidths[x] = width;
elm.style.minWidth = width + "px";
}
}
// Key press
viewKeyDown(e) {
// Error checking
if (e.altKey || e.ctrlKey || e.shiftKey)
return;
// Processing by key
switch (e.key) {
case "ArrowDown":
this.fetch(-1);
break;
case "ArrowLeft":
this.scrollLeft -= this.hscroll.unitIncrement;
break;
case "ArrowRight":
this.scrollLeft += this.hscroll.unitIncrement;
break;
case "ArrowUp":
this.fetch(+1);
break;
case "PageDown":
this.fetch(-this.tall(true));
break;
case "PageUp":
this.fetch(+this.tall(true));
break;
default: return;
}
Toolkit.handle(e);
}
// Resize
viewResize() {
// Error checking
if (!this.pc)
return;
// Working variables
let tall = this.tall(false);
let grew = this.lines.length < tall;
// Process all new lines
for (let y = this.lines.length; y < tall; y++) {
let resizer = y != 0 ? null :
new ResizeObserver(()=>this.colResize());
let line = {
lblAddress : document.createElement("div"),
lblBytes : new Array(4),
lblMnemonic: document.createElement("div"),
lblOperands: document.createElement("div")
};
// Address label
line.lblAddress.className = "address";
if (y == 0)
resizer.observe(line.lblAddress);
this.view.append(line.lblAddress);
// Byte labels
for (let x = 0; x < line.lblBytes.length; x++) {
let lbl = line.lblBytes[x] = document.createElement("div");
lbl.className = "byte" + (x == 0 ? " b0" : "");
if (y == 0) {
lbl.style.minWidth = "0px";
resizer.observe(lbl);
}
this.view.append(lbl);
}
// Mnemonic label
line.lblMnemonic.className = "mnemonic";
if (y == 0)
resizer.observe(line.lblMnemonic);
this.view.append(line.lblMnemonic);
// Operand label
line.lblOperands.className = "operands";
this.view.append(line.lblOperands);
// All elements
line.all = line.lblBytes.concat([
line.lblAddress,
line.lblMnemonic,
line.lblOperands
]);
this.lines.push(line);
}
// Remove lines that are no longer visible
while (tall < this.lines.length) {
let line = this.lines[tall];
line.lblAddress .remove();
line.lblMnemonic.remove();
line.lblOperands.remove();
for (let lbl of line.lblBytes)
lbl.remove();
this.lines.splice(tall, 1);
}
// Configure elements
let lineHeight = this.pc.element.getBoundingClientRect().height;
this.hscroll.unitIncrement = lineHeight;
this.view.element.style.gridAutoRows = lineHeight + "px";
// Update components
if (grew)
this.fetch();
else this.refresh();
}
// Mouse wheel
viewWheel(e) {
// Error checking
if (e.altKey || e.ctrlKey || e.shiftKey)
return;
// Always handle the event
Toolkit.handle(e);
// Determine how many full lines were scrolled
let scr = Debugger.linesScrolled(e,
this.pc.element.getBoundingClientRect().height,
this.tall(true),
this.delta
);
this.delta = scr.delta;
scr.lines = Math.max(-3, Math.min(3, scr.lines));
// No lines were scrolled
if (scr.lines == 0)
return;
// Scroll the view
this.fetch(-scr.lines);
}
///////////////////////////// Package Methods /////////////////////////////
// Display the bytes column in the disassembler
get bytesColumn() { return this._bytesColumn; }
set bytesColumn(show) {
show = !!show;
if (show == this._bytesColumn)
return;
this._bytesColumn = show;
this.refresh();
}
// Retrieve a disassembly from the simulation state
async fetch(viewScroll = 0) {
// Select the parameters for the simulation fetch
let params = {
viewAddress: this.viewAddress,
viewLine : this.viewLine + viewScroll,
viewLength : this.tall(false) + 20,
viewScroll : viewScroll
};
if (this.pending instanceof Object) {
params.viewLine += this.pending.viewScroll;
params.viewScroll += this.pending.viewScroll;
}
let bounds = Disassembler.dataBounds(
params.viewAddress, params.viewLine, params.viewLength);
params.dataAddress = bounds.address;
params.dataLength = bounds.length;
// A communication with the core thread is already underway
if (this.pending) {
this.pending = params;
this.refresh();
return;
}
// Retrieve data from the simulation state
this.pending = params;
for (let data=null, promise=null; this.pending instanceof Object;) {
// Wait for a transaction to complete
if (promise != null) {
this.pending = true;
data = await promise;
promise = null;
}
// Initiate a new transaction
if (this.pending instanceof Object) {
params = this.pending;
let options = { tag: params };
if (this.cpu.isVisible())
options.subscription = this.subscription;
promise = this.cpu.debug.core.read(this.cpu.debug.sim,
params.dataAddress, params.dataLength, options);
}
// Process the result of a transaction
if (data != null) {
this.refresh(data);
data = null;
}
};
this.pending = false;
}
// Component is being displayed for the first time
firstShow() {
this.viewLine = Math.floor(this.tall(true) / 3) + 10;
this.viewResize();
}
// Shrink all columns to fit the current view
fitColumns() {
if (this.lines.length == 0)
return;
for (let elm of this.lines[0].all)
elm.style.removeProperty("min-width");
for (let x = 0; x < this.columnWidths.length; x++)
this.columnWidths[x] = 0;
this.refresh();
this.colResize();
}
// Ensure PC is visible in the view
followPC(pc) {
let tall = this.tall(true);
let count = !this.dasm ? 0 : Math.min(this.dasm.length - 10, tall);
// Determine whether PC already is visible
for (let x = 0; x < count; x++) {
let line = this.dasm[x + 10];
if (pc - line.rawAddress >>> 0 < line.bytes.length)
return; // PC is already visible
}
// Request a new view containing PC
this.viewAddress = pc;
this.viewLine = Math.floor(tall / 3) + 10;
if (this.cpu.isVisible())
this.fetch();
}
// Prompt the user to navigate to a new editing address
goto() {
// Retrieve the value from the user
let addr = prompt(this.app.localize("{debug.cpu.goto}"));
if (addr === null)
return;
addr = parseInt(addr.trim(), 16);
if (
!Number.isInteger(addr) ||
addr < 0 ||
addr > 4294967295
) return;
// Navigate to the given address
this.viewAddress = addr;
this.viewLine = Math.floor(this.tall(true) / 3) + 10;
this.fetch();
}
// Update localization strings
localize() {
this.localizeRoleDescription();
this.localizeLabel();
}
// Update disassembler
refresh(msg = null) {
let tall = this.tall(false);
// Receiving data from the simulation state
if (msg != null) {
// Disassemble the retrieved data
this.dasm = this.cpu.debug.dasm.disassemble(
msg.data, msg.address,
msg.tag.viewAddress, msg.tag.viewLine, msg.tag.viewLength,
this.cpu.registers.pc
);
// Configure the view
this.viewAddress = this.dasm[10].rawAddress;
this.viewLine = 10;
if (this.pending instanceof Object)
this.viewLine += this.pending.viewScroll;
}
// Determine an initial number of visible byte columns
let showBytes = 0;
if (this.bytesColumn) {
for (let x = 0; x < 4 &&
this.columnWidths[x] != 0; x++, showBytes++);
}
// Working variables
let foundPC = false;
let lineHeight = this.pc.element.getBoundingClientRect().height;
// Process all lines
let index = 20 - this.viewLine;
for (let y = 0; y < this.lines.length; y++, index++) {
let line = this.lines[y];
let isPC = false;
// There is no data for this line
if (index < 0 || this.dasm == null || index >= this.dasm.length) {
line.lblAddress .innerText = "--------";
line.lblBytes[0].innerText = "--";
line.lblMnemonic.innerText = "---";
line.lblOperands.innerText = "";
for (let x = 1; x < line.lblBytes.length; x++)
line.lblBytes[x].innerText = "";
if (this.bytesColumn)
showBytes = Math.max(showBytes, 1);
}
// Present the disassembled line
else {
let dasm = this.dasm[index];
line.lblAddress .innerText = dasm.address;
line.lblMnemonic.innerText = dasm.mnemonic;
line.lblOperands.innerText = dasm.operands.join(", ");
for (let x = 0; x < line.lblBytes.length; x++)
line.lblBytes[x].innerText = dasm.bytes[x] || "";
isPC = this.cpu.registers.pc == dasm.rawAddress;
if (this.bytesColumn)
showBytes = Math.max(showBytes, dasm.bytes.length);
}
// Configure whether PC is on this line
if (isPC) {
foundPC = true;
this.pc.element.style.top = lineHeight * y + "px";
for (let elm of line.all)
elm.classList.add("is-pc");
} else {
for (let elm of line.all)
elm.classList.remove("is-pc");
}
}
// Show or hide the PC background
this.pc.visible = foundPC;
// Configure which byte columns are visible
for (let line of this.lines) {
for (let x = 0; x < line.lblBytes.length; x++) {
line.lblBytes[x].style
[x < showBytes ? "removeProperty" : "setProperty"]
("display", "none")
;
}
}
// Configure layout
this.view.element.style.gridTemplateColumns =
"repeat(" + (showBytes + 3) + ", max-content)";
}
// Stop receiving updates from the simulation
unsubscribe() {
this.cpu.debug.core.unsubscribe(this.subscription, false);
}
///////////////////////////// Private Methods /////////////////////////////
// Measure the number of lines visible in the view
tall(fully) {
return Math.max(1, Math[fully ? "floor" : "ceil"](
(fully ? this.view : this).element
.getBoundingClientRect().height /
this.pc.element.getBoundingClientRect().height
));
}
}
///////////////////////////////////////////////////////////////////////////////
// Entry in the register lists
class Register {
//////////////////////////////// Constants ////////////////////////////////
// Register types
static PLAIN = 0;
static PROGRAM = 1;
static CHCW = 2;
static ECR = 3;
static PSW = 4;
static PIR = 5;
static TKCW = 6;
// Program register formats
static HEX = 0;
static SIGNED = 1;
static UNSIGNED = 2;
static FLOAT = 3;
// Expansion controls by register type
static FIELDS = {
[this.CHCW]: [
[ "check", "ICE", 1 ]
],
[this.ECR]: [
[ "texth", "FECC", 16, 16 ],
[ "texth", "EICC", 0, 16 ]
],
[this.PIR]: [
[ "texth", "PT", 0, 16 ]
],
[this.PSW]: [
[ "check", "CY" , 3 ], [ "check", "FRO", 9 ],
[ "check", "OV" , 2 ], [ "check", "FIV", 8 ],
[ "check", "S" , 1 ], [ "check", "FZD", 7 ],
[ "check", "Z" , 0 ], [ "check", "FOV", 6 ],
[ "check", "NP" , 15 ], [ "check", "FUD", 5 ],
[ "check", "EP" , 14 ], [ "check", "FPR", 4 ],
[ "check", "ID" , 12 ], [ "textd", "I" , 16, 4 ],
[ "check", "AE" , 13 ]
],
[this.TKCW]: [
[ "check", "FIT", 7 ], [ "check", "FUT", 4 ],
[ "check", "FZT", 6 ], [ "check", "FPT", 3 ],
[ "check", "FVT", 5 ], [ "check", "OTM", 8 ],
[ "check", "RDI", 2 ], [ "textd", "RD" , 0, 2 ]
]
};
///////////////////////// Initialization Methods //////////////////////////
constructor(registers, key, type, apiType, apiId) {
let app = registers.cpu.debug.app;
// Configure instance fields
this.apiId = apiId;
this.apiType = apiType;
this.controls = [];
this.dasm = registers.cpu.debug.app.dasm;
this.debug = registers.cpu.debug;
this.expansion = null;
this.format = Register.HEX;
this.key = key;
this.registers = registers;
this.type = type;
// Resolve the target object
switch (apiType) {
case Core.VB_PROGRAM: this.target = registers.program; break;
case Core.VB_SYSTEM : this.target = registers.system ; break;
case Core.VB_OTHER : this.target = registers ; break;
}
// Main controls
this.main = new Toolkit.Component(app, {
class : "main",
visibility: true,
style : {
alignItems : "center",
display : "grid",
gridTemplateColumns: "max-content auto"
}
});
// Expand/collapse check box
this.chkExpand = new Toolkit.Checkbox(app, {
class : "tk expand",
disabled: true,
instant : true,
role : ""
});
this.main.add(this.chkExpand);
// Value text box
this.txtValue = new Toolkit.TextBox(app, {
class : "tk text-box mono",
spellcheck: "false",
size : "1",
value : "00000000"
});
this.txtValue.setLabel(this.label);
this.txtValue.addEventListener("action" , e=>this.valAction (e));
this.txtValue.addEventListener("keydown", e=>this.valKeyDown(e));
this.main.add(this.txtValue);
// Expansion area
if (type == Register.PROGRAM)
this.initProgram(app);
else this.initSystem(app, Register.FIELDS[type]);
if (this.expansion != null) {
// Expand/collapse check box
this.chkExpand.addEventListener("input", e=>this.chkInput(e));
this.chkExpand.disabled = false;
this.chkExpand.element.setAttribute("role", "checkbox");
this.chkExpand.element.setAttribute("tabindex", "0");
this.chkExpand.element
.setAttribute("aria-controls", this.expansion.element.id);
// Expansion area
this.expansion.visible = false;
}
// Update controls
this.dasmConfigured();
this.refresh();
// PSW is initially expanded
if (apiType == Core.VB_SYSTEM && apiId == Core.VB_PSW)
this.expanded = true;
// System registers after PSW are initially hidden
else if (apiType == Core.VB_SYSTEM)
this.visible = false;
}
// Expansion controls for program registers
initProgram(app) {
// Expansion area
let exp = this.expansion = new Toolkit.Component(app, {
class: "expansion",
id : Toolkit.id(),
style: {
display : "inline-grid",
gridTemplateColumns: "max-content",
}
});
exp.localize = ()=>exp.localizeLabel();
exp.setLabel("{debug.cpu.format}", true);
// Radio group
let group = new Toolkit.RadioGroup(app);
// Hex radio button
let opt = new Toolkit.Radio(app, {
checked: true,
group : group
});
opt.setText("{debug.cpu.hex}", true);
opt.addEventListener("input", e=>this.onProgram(Register.HEX));
exp.add(opt);
// Signed radio button
opt = new Toolkit.Radio(app, { group: group });
opt.setText("{debug.cpu.signed}", true);
opt.addEventListener("input", e=>this.onProgram(Register.SIGNED));
exp.add(opt);
// Unsigned radio button
opt = new Toolkit.Radio(app, { group: group });
opt.setText("{debug.cpu.unsigned}", true);
opt.addEventListener("input", e=>this.onProgram(Register.UNSIGNED));
exp.add(opt);
// Float radio button
opt = new Toolkit.Radio(app, { group: group });
opt.setText("{debug.cpu.float}", true);
opt.addEventListener("input", e=>this.onProgram(Register.FLOAT));
exp.add(opt);
}
// Expansion controls for system registers
initSystem(app, fields) {
// No expansion area
if (!fields)
return;
// Expansion area
let exp = this.expansion = new Toolkit.Component(app, {
class: "expansion",
id : Toolkit.id(),
style: {
display : "inline-grid",
gridTemplateColumns: "max-content max-content",
}
});
// Process all controls
for (let field of fields) {
// Bit check box
if (field[0] == "check") {
let box = new Toolkit.Checkbox(app);
box.setText(field[1], false);
box.bit = field[2];
box.addEventListener("input", e=>this.onBit(e));
exp.append(box.element);
if (this.type == Register.PIR || this.type == Register.TKCW)
box.disabled = true;
this.controls.push(box);
}
// Decimal text box
else if (field[0] == "textd") {
// Containing element for outer layout purposes
let div = document.createElement("div");
div.className = "text-dec";
Object.assign(div.style, {
alignItems : "center",
display : "inline-grid",
gridTemplateColumns: "max-content auto"
});
// Text box
let txt = new Toolkit.TextBox(app, {
id : Toolkit.id(),
spellcheck: false,
value : "0",
style : {
maxWidth: "2em"
}
});
txt.bit = field[2];
txt.bits = field[3];
txt.isHex = false;
txt.addEventListener("action", e=>this.onText(e));
// Label
let lbl = new Toolkit.Label(app, {
htmlFor: txt.element.id,
id : Toolkit.id(),
tag : "label"
});
lbl.setText(field[1], false);
txt.setLabel(lbl);
// Disable all fields
if (this.type == Register.PIR || this.type == Register.TKCW) {
lbl.disabled = true;
txt.disabled = true;
}
// Output control
this.controls.push(txt);
div.append(lbl.element);
div.append(txt.element);
exp.append(div);
}
// Hexadecimal text box
else if (field[0] == "texth") {
// Text box
let txt = new Toolkit.TextBox(app, {
class : "tk text-box mono",
id : Toolkit.id(),
spellcheck: false,
value : "0",
style : {
maxWidth: "3em"
}
});
txt.bit = field[2];
txt.bits = field[3];
txt.isHex = true;
txt.addEventListener("action", e=>this.onText(e));
// Label
let lbl = new Toolkit.Label(app, {
htmlFor: txt.element.id,
id : Toolkit.id(),
tag : "label"
});
lbl.setText(field[1], false);
txt.setLabel(lbl);
// Disable all fields
if (this.type == Register.PIR || this.type == Register.TKCW) {
lbl.disabled = true;
txt.disabled = true;
}
// Output control
this.controls.push(txt);
exp.append(lbl.element);
exp.append(txt.element);
}
}
}
///////////////////////////// Event Handlers //////////////////////////////
// Expand/collapse check box input
chkInput(e) {
this.expanded = this.chkExpand.checked;
}
// Program register format changed
onProgram(format) {
this.format = format;
this.txtValue.element.classList
[format == Register.HEX ? "add" : "remove"]("mono");
this.refresh();
}
// Bit check box input
onBit(e) {
let oldValue = this.target[this.key];
let target = e.target.component;
let mask = 1 << target.bit;
// Cannot change the value
if (e.disabled)
return;
// Update the value
this.setValue(target.checked ? oldValue | mask : oldValue & ~mask);
}
// Text box commit
onText(e) {
// Cannot change the value
if (e.disabled)
return;
// Working variables
let oldValue = this.target[this.key];
let target = e.target.component;
let newValue = parseInt(target.value, target.isHex ? 16 : 10);
// The provided value is invalid
if (!Number.isInteger(newValue)) {
this.refresh();
return;
}
// Update the value
let mask = (1 << target.bits) - 1 << target.bit;
this.setValue(oldValue & ~mask | newValue << target.bit & mask);
}
// Value text box commit
valAction(e) {
Toolkit.handle(e);
let value = this.parseValue(this.txtValue.value);
if (value === null)
this.refresh();
else this.setValue(value);
}
// Value text box key press
valKeyDown(e) {
if (e.altKey || e.ctrlKey || e.shiftKey || e.key != "Escape")
return;
Toolkit.handle(e);
this.refresh();
}
///////////////////////////// Public Methods //////////////////////////////
// The expansion area is visible
get expanded() { return this.chkExpand.checked; }
set expanded(expanded) {
this.chkExpand.checked = expanded;
this.setVisible(this.main.visible);
}
// Specify whether the element is visible
get visible() { return this.main.visible; }
set visible(visible) {
visible = !!visible;
if (visible == this.main.visible)
return;
this.main.element.style[visible ? "removeProperty" : "setProperty"]
("position", "absolute");
this.main.visible = visible;
this.setVisible(visible);
}
///////////////////////////// Package Methods /////////////////////////////
// Disassembler configuration has changed
dasmConfigured() {
let names;
switch (this.apiType) {
case Core.VB_SYSTEM: names = Disassembler.REG_SYSTEM; break;
case Core.VB_OTHER : names = Disassembler.REG_OTHER ; break;
}
this.chkExpand.uiLabel.setText(this.apiType != Core.VB_PROGRAM ?
names[this.apiId] : this.dasm.programRegister(this.key));
}
// Update controls from simulation state
refresh() {
// Value text box
let value = this.target[this.key];
this.txtValue.value = this.formatValue(value);
// Expansion controls
for (let ctrl of this.controls) {
// Bit check box
if (ctrl instanceof Toolkit.Checkbox)
ctrl.checked = !!(value >> ctrl.bit & 1);
// Decimal text box
else if (ctrl instanceof Toolkit.TextBox && !ctrl.isHex)
ctrl.value = value >> ctrl.bit & (1 << ctrl.bits) - 1;
// Hexadecimal text box
else if (ctrl instanceof Toolkit.TextBox && ctrl.isHex) {
ctrl.value = this.dasm.hex(
value >> ctrl.bit & (1 << ctrl.bits) - 1,
Math.ceil(ctrl.bits / 4),
false);
}
}
}
///////////////////////////// Private Methods /////////////////////////////
// Format a register value as text
formatValue(value) {
switch (this.format) {
case Register.HEX:
return this.debug.hex(value >>> 0, 8, false);
case Register.SIGNED:
return (value >> 0).toString();
case Register.UNSIGNED:
return (value >>> 0).toString();
case Register.FLOAT:
value = Debugger.ixf(value);
if (Number.isFinite(value)) {
let text = value.toFixed(100);
if (/[^0-9\-\.]/.test(text))
text = value.toFixed(6);
if (text.indexOf(".") != -1) {
text = text.replace(/0+$/, "")
.replace(/\.$/, ".0");
} else text += ".0";
return text;
}
if (!Number.isNaN(value)) {
return (
(value == Number.NEGATIVE_INFINITY ? "-" : "") +
this.debug.app.localize("{debug.cpu.infinity}")
);
}
return "NaN";
}
return null;
}
// Parse text as a register value
parseValue(value) {
switch (this.format) {
case Register.HEX:
value = parseInt(value, 16);
return (
!Number.isInteger(value) ||
value < 0 ||
value > 0xFFFFFFFF ?
null : value
);
case Register.SIGNED:
value = parseInt(value);
return (
!Number.isInteger(value) ||
value < -0x80000000 ||
value > 0x7FFFFFFF ?
null : value
);
case Register.UNSIGNED:
value = parseInt(value);
return (
!Number.isInteger(value) ||
value < 0 ||
value > 0xFFFFFFFF ?
null : value
);
case Register.FLOAT:
value = parseFloat(value);
return (
!Number.isFinite(value) ||
value < Debugger.ixf(0xFF7FFFFF) ||
value > Debugger.ixf(0x7F7FFFFF)
? null : Debugger.fxi(value) >>> 0);
}
return null;
}
// Specify a new register value
async setValue(value) {
// Update the value in the simulation state
let options = {};
if (this.key == "pc")
options.refresh = [ this.registers.cpu.disassembler.subscription ];
let result = await this.debug.core.setRegister(
this.debug.sim, this.apiType, this.apiId, value, options);
// Update the value in the debugger window
this.target[this.key] = result.value;
// Update the register controls
this.refresh();
}
// Update visibility for expansion controls
setVisible(visible) {
if (this.expansion)
this.expansion.visible = visible && !!this.expanded;
}
}
///////////////////////////////////////////////////////////////////////////////
// Register list manager
class RegisterPane extends Toolkit.SplitPane {
//////////////////////////////// Constants ////////////////////////////////
// System register templates
static SYSTEMS = [
[ "pc" , Register.PLAIN, Core.VB_OTHER , Core.VB_PC ],
[ Core.VB_PSW , Register.PSW , Core.VB_SYSTEM, Core.VB_PSW ],
[ Core.VB_ADTRE, Register.PLAIN, Core.VB_SYSTEM, Core.VB_ADTRE ],
[ Core.VB_CHCW , Register.CHCW , Core.VB_SYSTEM, Core.VB_CHCW ],
[ Core.VB_ECR , Register.ECR , Core.VB_SYSTEM, Core.VB_ECR ],
[ Core.VB_EIPC , Register.PLAIN, Core.VB_SYSTEM, Core.VB_EIPC ],
[ Core.VB_EIPSW, Register.PSW , Core.VB_SYSTEM, Core.VB_EIPSW ],
[ Core.VB_FEPC , Register.PLAIN, Core.VB_SYSTEM, Core.VB_FEPC ],
[ Core.VB_FEPSW, Register.PSW , Core.VB_SYSTEM, Core.VB_FEPSW ],
[ Core.VB_PIR , Register.PIR , Core.VB_SYSTEM, Core.VB_PIR ],
[ Core.VB_TKCW , Register.TKCW , Core.VB_SYSTEM, Core.VB_TKCW ],
[ 29 , Register.PLAIN, Core.VB_SYSTEM, 29 ],
[ 30 , Register.PLAIN, Core.VB_SYSTEM, 30 ],
[ 31 , Register.PLAIN, Core.VB_SYSTEM, 31 ]
];
///////////////////////// Initialization Methods //////////////////////////
constructor(cpu) {
super(cpu.debug.app, {
orientation: "top",
style: {
overflow: "visible"
}
});
// Configure instance fields
this.cpu = cpu;
this.list = [];
this.pending = false;
this.subscription = [ 0, cpu.index, "cpu", "registers", "refresh" ],
// Initialize regsiters
this.pc = 0xFFFFFFF0;
this.program = new Array(32);
this.system = new Array(32);
for (let x = 0; x < 32; x++)
this.program[x] = this.system[x] = 0;
// System registers list
this.lstSystem = new Toolkit.Component(cpu.debug.app, {
class: "tk registers",
style: {
minHeight: "100%",
minWidth : "100%",
width : "max-content"
}
});
// System registers scroll pane
this.scrSystem = new Toolkit.ScrollPane(cpu.debug.app, {
class : "tk scroll-pane scr-system",
overflowX: "hidden",
overflowY: "scroll",
view : this.lstSystem,
style : {
position: "relative"
}
});
this.primary = this.scrSystem;
// Program registers list
this.lstProgram = new Toolkit.Component(cpu.debug.app, {
class: "tk registers",
style: {
minHeight: "100%",
minWidth : "100%",
width : "max-content"
}
});
// Program registers scroll pane
this.scrProgram = new Toolkit.ScrollPane(cpu.debug.app, {
class : "tk scroll-pane scr-program",
overflowX: "hidden",
overflowY: "scroll",
view : this.lstProgram
});
this.secondary = this.scrProgram;
// Configure register lists
for (let sys of RegisterPane.SYSTEMS)
this.addRegister(new Register(this, ... sys));
for (let x = 0; x < 32; x++) {
this.addRegister(new Register(this,
x, Register.PROGRAM, Core.VB_PROGRAM, x));
}
// Value text box measurer
let text = [];
for (let c of "0123456789abcdefABCDEF")
text.push(c.repeat(8));
this.sizer = new Toolkit.Label(cpu.app, {
class: "tk text-box mono",
style: {
position : "absolute",
visibility: "hidden"
}
});
this.sizer.setText(text.join("\n"), false);
this.list[0].main.element.after(this.sizer.element);
// Monitor the bounds of the register names column
let resizer = new ResizeObserver(()=>this.regResize());
for (let reg of this.list)
resizer.observe(reg.chkExpand.element);
// Monitor the bounds of the value text boxes
this.list[0].txtValue.addEventListener("resize", e=>this.valResize(e));
this.sizer .addEventListener("resize", e=>this.sizResize(e));
}
// Add a Register object to a list
addRegister(reg) {
this.list.push(reg);
// Add the main element to the appropriate list container
let list = this[reg.type == Register.PROGRAM ?
"lstProgram" : "lstSystem"];
list.add(reg.main);
// Add the expansion element
if (reg.expansion != null)
list.add(reg.expansion);
}
///////////////////////////// Event Handlers //////////////////////////////
// Register label resized
regResize() {
let max = 0;
let widths = new Array(this.list.length);
// Measure the widths of all labels
for (let x = 0; x < this.list.length; x++) {
widths[x] = Math.ceil(this.list[x].chkExpand.element
.getBoundingClientRect().width);
max = Math.max(max, widths[x]);
}
// Ensure all labels share the same maximum width
for (let x = 0; x < this.list.length; x++) {
if (widths[x] < max)
this.list[x].chkExpand.element.style.minWidth = max + "px";
}
}
// Sizer resized
sizResize(e) {
let width = Math.ceil(e.target.getBoundingClientRect().width) + "px";
for (let reg of this.list)
reg.txtValue.style.minWidth = width;
}
// Value text box resized
valResize(e) {
let height = Math.ceil(e.target.getBoundingClientRect().height);
this.scrSystem .vscroll.unitIncrement = height;
this.scrProgram.vscroll.unitIncrement = height;
}
///////////////////////////// Package Methods /////////////////////////////
// Retrieve registers from the simulation state
async fetch() {
// Select the parameters for the simulation fetch
let params = {};
// A communication with the core thread is already underway
if (this.pending) {
this.pending = params;
this.refresh();
return;
}
// Retrieve data from the simulation state
this.pending = params;
for (let data = null, promise = null; this.pending instanceof Object;){
// Wait for a transaction to complete
if (promise != null) {
this.pending = true;
data = await promise;
promise = null;
}
// Initiate a new transaction
if (this.pending instanceof Object) {
params = this.pending;
let options = {};
if (this.isVisible())
options.subscription = this.subscription;
promise = this.cpu.debug.core.getAllRegisters(
this.cpu.debug.sim, options);
}
// Process the result of a transaction
if (data != null) {
this.refresh(data);
data = null;
}
};
this.pending = false;
}
// Component is being displayed for the first time
firstShow() {
// Retrieve the desired dimensions of the system registers list
let bounds = this.scrSystem.element.getBoundingClientRect();
// Show all hidden system registers
for (let reg of this.list)
reg.visible = true;
// Prepare the initial dimensions of the register lists
this .element.style.width = Math.ceil(bounds.width ) + "px";
this.scrSystem .element.style.height = Math.ceil(bounds.height) + "px";
this.scrSystem .overflowX = "auto";
this.scrSystem .overflowY = "auto";
this.scrProgram.overflowX = "auto";
this.scrProgram.overflowY = "auto";
this.fetch();
}
// Update register lists
refresh(msg = null) {
// Receiving data from the simulation state
if (msg != null) {
this.pc = msg.pc;
this.program.splice(0, this.program.length, ... msg.program);
this.system .splice(0, this.system .length, ... msg.system );
}
// Update register controls
for (let reg of this.list)
reg.refresh();
}
// Stop receiving updates from the simulation
unsubscribe() {
this.cpu.debug.core.unsubscribe(this.subscription, false);
}
}
Debugger.CPU = CPU;
}
export { register };