pvbemu/app/app/RegisterList.js

864 lines
25 KiB
JavaScript

import { Util } from /**/"./Util.js";
// Value types
const HEX = 0;
const SIGNED = 1;
const UNSIGNED = 2;
const FLOAT = 3;
// System register indexes
const ADTRE = 25;
const CHCW = 24;
const ECR = 4;
const EIPC = 0;
const EIPSW = 1;
const FEPC = 2;
const FEPSW = 3;
const PC = -1;
const PIR = 6;
const PSW = 5;
const TKCW = 7;
// Program register names
const PROREGS = {
[ 2]: "hp",
[ 3]: "sp",
[ 4]: "gp",
[ 5]: "tp",
[31]: "lp"
};
// System register names
const SYSREGS = {
[ADTRE]: "ADTRE",
[CHCW ]: "CHCW",
[ECR ]: "ECR",
[EIPC ]: "EIPC",
[EIPSW]: "EIPSW",
[FEPC ]: "FEPC",
[FEPSW]: "FEPSW",
[PC ]: "PC",
[PIR ]: "PIR",
[PSW ]: "PSW",
[TKCW ]: "TKCW",
[29 ]: "29",
[30 ]: "30",
[31 ]: "31"
};
// Expansion control types
const BIT = 0;
const INT = 1;
// Produce a template object for register expansion controls
function ctrl(name, shift, size, disabled) {
return {
disabled: !!disabled,
name : name,
shift : shift,
size : size
};
}
// Program register epansion controls
const EXP_PROGRAM = [
ctrl("cpu.hex" , true , HEX ),
ctrl("cpu.signed" , false, SIGNED ),
ctrl("cpu.unsigned", false, UNSIGNED),
ctrl("cpu.float" , false, FLOAT )
];
// CHCW expansion controls
const EXP_CHCW = [
ctrl("ICE", 1, 1)
];
// ECR expansion controls
const EXP_ECR = [
ctrl("FECC", 16, 16),
ctrl("EICC", 0, 16)
];
// PIR expansion controls
const EXP_PIR = [
ctrl("PT", 0, 16, true)
];
// PSW expansion controls
const EXP_PSW = [
ctrl("CY", 3, 1), ctrl("FRO", 9, 1),
ctrl("OV", 2, 1), ctrl("FIV", 8, 1),
ctrl("S" , 1, 1), ctrl("FZD", 7, 1),
ctrl("Z" , 0, 1), ctrl("FOV", 6, 1),
ctrl("NP", 15, 1), ctrl("FUD", 5, 1),
ctrl("EP", 14, 1), ctrl("FPR", 4, 1),
ctrl("ID", 12, 1), ctrl("I" , 16, 4),
ctrl("AE", 13, 1)
];
// TKCW expansion controls
const EXP_TKCW = [
ctrl("FIT", 7, 1, true), ctrl("FUT", 4, 1, true),
ctrl("FZT", 6, 1, true), ctrl("FPT", 3, 1, true),
ctrl("FVT", 5, 1, true), ctrl("OTM", 8, 1, true),
ctrl("RDI", 2, 1, true), ctrl("RD" , 0, 2, true)
];
///////////////////////////////////////////////////////////////////////////////
// Register //
///////////////////////////////////////////////////////////////////////////////
// One register within a register list
class Register {
///////////////////////// Initialization Methods //////////////////////////
constructor(list, index, andMask, orMask) {
// Configure instance fields
this.andMask = andMask;
this.app = list.app;
this.controls = [];
this.dasm = list.dasm;
this.index = index;
this.isExpanded = null;
this.list = list;
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"
}
});
// Processing by type
this[this.system ? "initSystem" : "initProgram"]();
// Expansion button
this.btnExpand = new Toolkit.Component(this.app, {
className: "tk tk-expand tk-mono",
tagName : "div"
});
this.list.view.append(this.btnExpand);
// Name label
this.lblName = document.createElement("div");
Object.assign(this.lblName, {
className: "tk tk-name",
id : Toolkit.id(),
innerText: this.dasm.sysregCaps?this.name:this.name.toLowerCase()
});
this.lblName.style.userSelect = "none";
this.list.view.append(this.lblName);
// Value text box
this.txtValue = new Toolkit.TextBox(this.app, {
className: "tk tk-textbox tk-mono",
maxLength: 8
});
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);
// Expansion area
if (this.expansion != null)
this.list.view.append(this.expansion);
// Enable expansion function
if (this.expansion != null) {
let key = e=>this.expandKeyDown (e);
let pointer = e=>this.expandPointerDown(e);
this.btnExpand.setAttribute("aria-controls", this.expansion.id);
this.btnExpand.setAttribute("aria-labelledby", this.lblName.id);
this.btnExpand.setAttribute("role", "button");
this.btnExpand.setAttribute("tabindex", "0");
this.btnExpand.addEventListener("keydown" , key );
this.btnExpand.addEventListener("pointerdown", pointer);
this.lblName .addEventListener("pointerdown", pointer);
this.setExpanded(this.system && this.index == PSW);
}
// Expansion function is unavailable
else this.btnExpand.setAttribute("aria-hidden", "true");
}
// Set up a program register
initProgram() {
this.name = PROREGS[this.index] || "r" + this.index.toString();
this.initExpansion(EXP_PROGRAM);
}
// Set up a system register
initSystem() {
this.name = SYSREGS[this.index] || this.index.toString();
switch (this.index) {
case CHCW :
this.initExpansion(EXP_CHCW); break;
case ECR :
this.initExpansion(EXP_ECR ); break;
case EIPSW: case FEPSW: case PSW:
this.initExpansion(EXP_PSW ); break;
case PIR :
this.initExpansion(EXP_PIR ); break;
case TKCW :
this.initExpansion(EXP_TKCW); break;
}
}
// Initialize expansion controls
initExpansion(controls) {
let two = this.index == ECR || this.index == PIR;
// Establish expansion element
let exp = this.expansion = new Toolkit.Component(this.app, {
className: "tk tk-expansion",
id : Toolkit.id(),
tagName : "div",
style : {
display : "none",
gridColumnEnd : "span 3",
gridTemplateColumns:
this.system ? "repeat(2, max-content)" : "max-content"
}
});
// Produce program register controls
if (!this.system) {
let group = new Toolkit.Group();
exp.append(group);
// Process all controls
for (let template of controls) {
// Create control
let ctrl = new Toolkit.Radio(this.app, {
group : group,
selected: template.shift,
text : template.name
});
ctrl.format = template.size;
// Configure event handler
ctrl.addEventListener("action",
e=>this.setType(e.component.format));
// Add the control to the element
let box = document.createElement("div");
box.append(ctrl.element);
exp.append(box);
}
return;
}
// Process all control templates
for (let template of controls) {
let box, ctrl;
// Not using an inner two-column layout
if (!two)
exp.append(box = document.createElement("div"));
// Bit check box
if (template.size == 1) {
box.classList.add("tk-bit");
// Create control
ctrl = new Toolkit.CheckBox(this.app, {
text : "name",
substitutions: { name: template.name }
});
ctrl.mask = 1 << template.shift;
box.append(ctrl.element);
// Disable control
if (template.disabled)
ctrl.setEnabled(false);
// Configure event handler
ctrl.addEventListener("action", e=>this.onBit(e.component));
}
// Number text box
else {
if (!two)
box.classList.add("tk-number");
// Create label
let label = document.createElement("label");
Object.assign(label, {
className: "tk tk-label",
innerText: template.name,
});
if (!two) Object.assign(box.style, {
columnGap : "2px",
display : "grid",
gridTemplateColumns: "max-content auto"
});
(two ? exp : box).append(label);
// Create control
ctrl = new Toolkit.TextBox(this.app, {
id : Toolkit.id(),
style: { height: "1em" }
});
label.htmlFor = ctrl.id;
(two ? exp : box).append(ctrl.element);
// Control is a hex field
if (template.size == 16) {
ctrl.element.classList.add("tk-mono");
ctrl.setAttribute("digits", 4);
ctrl.setMaxLength(4);
}
// Disable control
if (template.disabled) {
ctrl.setEnabled(false);
(two ? label : box).setAttribute("disabled", "true");
}
// Configure event handler
ctrl.addEventListener("action", e=>this.onNumber(e.component));
}
Object.assign(ctrl, template);
this.controls.push(ctrl);
}
}
///////////////////////////// Event Handlers //////////////////////////////
// Expand button key press
expandKeyDown(e) {
// Processing by key
switch (e.key) {
case "Enter":
case " ":
this.setExpanded(!this.isExpanded);
break;
default: return;
}
// Configure event
e.stopPropagation();
e.preventDefault();
}
// Expand button pointer down
expandPointerDown(e) {
// Focus management
this.btnExpand.focus();
// Error checking
if (e.button != 0)
return;
// Configure event
e.stopPropagation();
e.preventDefault();
// Configure expansion area
this.setExpanded(!this.isExpanded);
}
// Expansion bit check box
onBit(ctrl) {
this.setValue(ctrl.isSelected ?
this.value | ctrl.mask :
this.value & Util.u32(~ctrl.mask)
);
}
// Expansion number text box
onNumber(ctrl) {
let mask = (1 << ctrl.size) - 1 << ctrl.shift;
let value = parseInt(ctrl.getText(), ctrl.size == 16 ? 16 : 10);
this.setValue(isNaN(value) ? this.value :
this.value & Util.u32(~mask) | value << ctrl.shift & mask);
}
// Register value
onValue() {
let text = this.txtValue.getText();
let value;
// Processing by type
switch (this.type) {
// Unsigned hexadecimal
case HEX:
value = parseInt(text, 16);
break;
// Decimal
case SIGNED:
case UNSIGNED:
value = parseInt(text);
break;
// Float
case FLOAT:
value = parseFloat(text);
if (isNaN(value))
break;
value = Util.fromF32(value);
break;
}
// Assign the new 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 /////////////////////////////
// Disassembler settings have been updated
dasmChanged() {
let dasm = this.list.dasm;
let name = this.name;
// Program register name
if (!this.system) {
if (!dasm.proregNames)
name = "r" + this.index.toString();
if (dasm.proregCaps)
name = name.toUpperCase();
}
// System register name
else {
if (!dasm.sysregCaps)
name = name.toLowerCase();
}
// Common processing
this.lblName.innerText = name;
this.refresh(this.value);
}
// Update the value returned from the core
refresh(value) {
let text;
// Configure instance fields
this.value = 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) {
// Unsigned hexadecimal
case HEX:
text = value.toString(16).padStart(8, "0");
if (this.dasm.hexCaps)
text = text.toUpperCase();
break;
// Signed decimal
case SIGNED:
text = Util.s32(value).toString();
break;
// Unsigned decial
case UNSIGNED:
text = Util.u32(value).toString();
break;
// Float
case FLOAT:
if ((value & 0x7F800000) != 0x7F800000) {
text = Util.toF32(value).toFixed(5).replace(/0+$/, "");
if (text.endsWith("."))
text += "0";
} else text = "NaN";
break;
}
this.txtValue.setText(text);
// No further processing for program registers
if (!this.system)
return;
// Process all expansion controls
for (let ctrl of this.controls) {
// Bit check box
if (ctrl.size == 1) {
ctrl.setSelected(value & ctrl.mask);
continue;
}
// Integer text box
text = value >> ctrl.shift & (1 << ctrl.size) - 1;
text = ctrl.size != 16 ? text.toString() :
text.toString(16).padStart(4, "0");
if (this.dasm.hexCaps)
text = text.toUpperCase();
ctrl.setText(text);
}
}
///////////////////////////// Private Methods /////////////////////////////
// Specify the formatting type of the register value
setType(type) {
if (type == this.type)
return;
this.type = type;
this.refresh(this.value);
}
// Specify a new value for the register
async setValue(value) {
// Update the display with the new value immediately
value = Util.u32(value & this.andMask | this.orMask);
let matched = value == this.value;
this.refresh(value);
if (matched)
return;
// Update the new value in the core
let options = { refresh: true };
this.refresh(await (
!this.system ?
this.sim.setProgramRegister(this.index, value, options) :
this.index == PC ?
this.sim.setProgramCounter ( value, options) :
this.sim.setSystemRegister (this.index, value, options)
));
}
}
///////////////////////////////////////////////////////////////////////////////
// RegisterList //
///////////////////////////////////////////////////////////////////////////////
// Scrolling list of registers
class RegisterList extends Toolkit.ScrollPane {
///////////////////////// Initialization Methods //////////////////////////
constructor(debug, system) {
super(debug.app, {
className: "tk tk-scrollpane tk-reglist " +
(system ? "tk-system" : "tk-program"),
vertical : Toolkit.ScrollPane.ALWAYS
});
// Configure instance fields
this.app = debug.app;
this.dasm = debug.disassembler;
this.method = system?"getSystemRegisters":"getProgramRegisters";
this.sim = debug.sim;
this.subscription = system ? "sysregs" : "proregs";
this.system = system;
// Configure view element
this.setView(new Toolkit.Component(debug.app, {
className: "tk tk-list",
tagName : "div",
style : {
display : "grid",
gridTemplateColumns: "max-content auto max-content"
}
}));
// Font-measuring element
let text = "";
for (let x = 0; x < 16; x++) {
if (x != 0) text += "\n";
let digit = x.toString(16);
text += digit + "\n" + digit.toUpperCase();
}
this.metrics = new Toolkit.Component(this.app, {
className: "tk tk-mono",
tagName : "div",
style : {
position : "absolute",
visibility: "hidden"
}
});
this.metrics.element.innerText = text;
this.metrics.addEventListener("resize", e=>this.onMetrics());
this.viewport.append(this.metrics.element);
// Processing by type
this[system ? "initSystem" : "initProgram"]();
// Configure component
this.addEventListener("keydown", e=>this.onKeyDown (e));
this.addEventListener("wheel" , e=>this.onMouseWheel(e));
}
// Initialize a list of program registers
initProgram() {
this[0] = new Register(this, 0, 0x00000000, 0x00000000);
for (let x = 1; x < 32; x++)
this[x] = 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);
}
///////////////////////////// Event Handlers //////////////////////////////
// Key press
onKeyDown(e) {
// Processing by key
switch (e.key) {
case "ArrowDown":
this.vertical.setValue(this.vertical.value +
this.vertical.increment);
break;
case "ArrowLeft":
this.horizontal.setValue(this.horizontal.value -
this.horizontal.increment);
break;
case "ArrowRight":
this.horizontal.setValue(this.horizontal.value +
this.horizontal.increment);
break;
case "ArrowUp":
this.vertical.setValue(this.vertical.value -
this.vertical.increment);
break;
case "PageDown":
this.vertical.setValue(this.vertical.value +
this.vertical.extent);
break;
case "PageUp":
this.vertical.setValue(this.vertical.value -
this.vertical.extent);
break;
default: return;
}
// Configure event
e.stopPropagation();
e.preventDefault();
}
// Metrics element resized
onMetrics() {
// Error checking
if (!this.metrics)
return;
// Measure the dimensions of one hex character
let bounds = this.metrics.getBounds();
if (bounds.height <= 0)
return;
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"
});
}
// Update scroll bars
this.horizontal.setIncrement(height);
this.vertical .setIncrement(height);
}
// Mouse wheel
onMouseWheel(e) {
// User agent scaling action
if (e.ctrlKey)
return;
// No rotation has occurred
let offset = Math.sign(e.deltaY) * 3;
if (offset == 0)
return;
// Update the display address
this.vertical.setValue(this.vertical.value +
this.vertical.increment * offset);
}
///////////////////////////// Public Methods //////////////////////////////
// Update with CPU state from the core
refresh(registers) {
// 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] );
}
// Program registers
else for (let x = 0; x < 32; x++)
this[x].refresh(registers[x]);
}
// Subscribe to or unsubscribe from core updates
setSubscribed(subscribed) {
subscribed = !!subscribed;
// Nothing to change
if (subscribed == this.isSubscribed)
return;
// Configure instance fields
this.isSubscribed = subscribed;
// Subscribe to core updates
if (subscribed)
this.fetch();
// Unsubscribe from core updates
else this.sim.unsubscribe(this.subscription);
}
///////////////////////////// Package Methods /////////////////////////////
// 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();
}
}
// Determine the initial size of the register list
getPreferredSize() {
let ret = {
height: 0,
width : 0
};
// Error checking
if (!this.view)
return ret;
// Measure the view element
ret.width = this.view.element.scrollWidth;
// Locate the bottom of PSW
if (this.system && this[PSW].expansion) {
ret.height = this[PSW].expansion.getBounds().bottom -
this.view.getBounds().top;
}
return ret;
}
///////////////////////////// Private Methods /////////////////////////////
// Retrieve CPU state from the core
async fetch() {
this.refresh(
await this.sim[this.method]({
subscribe: this.isSubscribed && this.subscription
})
);
}
}
export { RegisterList };