pvbemu/app/windows/Register.js

497 lines
16 KiB
JavaScript

"use strict";
// List item for CPU window register lists
(CPUWindow.Register = class Register extends Toolkit.Panel {
// Static initializer
static initializer() {
let buffer = new ArrayBuffer(4);
this.F32 = new Float32Array(buffer);
this.S32 = new Int32Array (buffer);
this.U32 = new Uint32Array (buffer);
}
// Object constructor
constructor(debug, parent, system, id, name) {
super(parent.application, {
layout : "grid",
columns : "auto max-content",
overflowX: "visible",
overflowY: "visible"
});
// Configure instance fields
this.debug = debug;
this.expanded = false;
this.expansion = null;
this.fields = {};
this.format = "hex";
this.id = id;
this.name = name;
this.parent = parent;
this.system = system;
this.value = 0x00000000;
// Determine whether the register has expansion fields
this.expands = !system;
switch (id) {
case CPUWindow.CHCW:
case CPUWindow.ECR:
case CPUWindow.EIPSW:
case CPUWindow.FEPSW:
case CPUWindow.PC:
case CPUWindow.PIR:
case CPUWindow.PSW:
case CPUWindow.TKCW:
this.expands = true;
}
// Configure element
this.element.setAttribute("name", "register");
this.element.setAttribute("format", this.format);
// Name/expansion "check box"
this.chkExpand = this.add(this.newCheckBox({
enabled: this.expands,
text : name
}));
this.chkExpand.element.setAttribute("name", "expand");
this.chkExpand.element.setAttribute("aria-expanded", "false");
this.chkExpand.addChangeListener(e=>
this.setExpanded(this.chkExpand.isChecked()));
// Value text box
this.txtValue = this.add(this.newTextBox({ text: "00000000" }));
this.txtValue.element.setAttribute("name", "value");
this.txtValue.addCommitListener(e=>this.onvalue());
// Expansion controls
if (!system)
this.expansionProgram();
else switch (id) {
case CPUWindow.CHCW : this.expansionCHCW(); break;
case CPUWindow.ECR : this.expansionECR (); break;
case CPUWindow.EIPSW:
case CPUWindow.FEPSW:
case CPUWindow.PSW : this.expansionPSW (); break;
case CPUWindow.PC : this.expansionPC (); break;
case CPUWindow.PIR : this.expansionPIR (); break;
case CPUWindow.TKCW : this.expansionTKCW(); break;
}
if (this.expands) {
this.chkExpand.element.setAttribute(
"aria-controls", this.expansion.id);
}
}
///////////////////////////// Static Methods //////////////////////////////
// Force a 32-bit integer to be signed
static asSigned(value) {
this.U32[0] = value >>> 0;
return this.S32[0];
}
// Interpret a 32-bit integer as a float
static intBitsToFloat(value) {
this.U32[0] = value;
value = this.F32[0];
return Number.isFinite(value) ? value : 0;
}
// Interpret a float as a 32-bit integer
static floatToIntBits(value) {
if (!Number.isFinite(value))
return 0;
this.F32[0] = value;
return this.U32[0];
}
///////////////////////////// Package Methods /////////////////////////////
// Specify whether the expansion fields are visible
setExpanded(expanded) {
this.expanded = expanded = !!expanded;
this.expansion.setVisible(expanded);
this.chkExpand.setChecked(expanded);
this.chkExpand.element.setAttribute("aria-expanded", expanded);
}
// Specify the display mode of the register value
setFormat(format) {
this.format = format;
this.setValue(this.value);
this.element.setAttribute("format", format.replace("_", ""));
}
// Update the value of the register
setValue(value, pcFrom, pcTo) {
this.value = value;
// Value text box
let text;
switch (this.format) {
case "float":
text = CPUWindow.Register.intBitsToFloat(value).toString();
if (text.indexOf(".") == -1)
text += ".0";
break;
case "hex":
text = ("0000000" +
(value >>> 0).toString(16).toUpperCase()).slice(-8);
break;
case "signed":
text = CPUWindow.Register.asSigned(value).toString();
break;
case "unsigned":
text = (value >>> 0).toString();
}
this.txtValue.setText(text);
// Expansion fields
for (let field of Object.values(this.fields)) {
switch (field.type) {
case "bit":
field.setChecked(value >> field.bit & 1);
break;
case "decimal":
field.setText(value >> field.bit & field.mask);
break;
case "hex":
let digits = Math.max(1, Math.ceil(field.width / 4));
field.setText(("0".repeat(digits) +
(value >> field.bit & field.mask)
.toString(16).toUpperCase()
).slice(-digits));
}
}
// Special fields for PC
if (pcFrom === undefined)
return;
this.txtFrom.setText(("0000000" +
(pcFrom >>> 0).toString(16).toUpperCase()).slice(-8));
this.txtTo .setText(("0000000" +
(pcTo >>> 0).toString(16).toUpperCase()).slice(-8));
}
///////////////////////////// Private Methods /////////////////////////////
// Add a field component to the expansion area
addField(type, name, bit, width, readonly) {
let field, label, panel;
// Processing by type
switch (type) {
// Bit
case "bit":
field = this.newCheckBox({
enabled: !readonly,
text : name
});
field.addChangeListener(e=>this.onbit(field));
this.expansion.add(field);
break;
// Decimal number
case "decimal":
// Field
field = this.newTextBox({
enabled: !readonly,
name : name
});
field.addCommitListener(e=>this.onnumber(field));
label = this.newLabel({
label: true,
text : name
});
label.element.htmlFor = field.id;
if (readonly)
label.element.setAttribute("aria-disabled", "true");
// Enclose in a panel
panel = this.newPanel({
layout : "flex",
alignCross: "center",
alignMain : "start",
direction : "row"
});
panel.element.setAttribute("name", name);
panel.add(label);
panel.add(field);
this.expansion.add(panel);
break;
// Hexadecimal number
case "hex":
field = this.newTextBox({
enabled: !readonly,
name : name
});
field.addCommitListener(e=>this.onnumber(field));
label = this.newLabel({
label: true,
text : name
});
label.element.htmlFor = field.id;
if (readonly)
label.element.setAttribute("aria-disabled", "true");
this.expansion.add(label);
this.expansion.add(field);
}
// Configure field
field.bit = bit;
field.mask = (1 << width) - 1;
field.type = type;
field.width = width;
this.fields[name] = field;
}
// Expansion controls for CHCW
expansionCHCW() {
// Expansion area
this.expansion = this.newPanel({
layout : "block",
overflowX: "visible",
overflowY: "visible",
visible : false
});
this.expansion.element.setAttribute("name", "expansion");
this.expansion.element.setAttribute("register", "chcw");
// Fields
this.addField("bit", "ICE", 1, 1, false);
}
// Expansion controls for ECR
expansionECR() {
// Expansion area
this.expansion = this.newPanel({
layout : "grid",
columns : "max-content auto",
overflowX: "visible",
overflowY: "visible",
visible : false
});
this.expansion.element.setAttribute("name", "expansion");
this.expansion.element.setAttribute("register", "ecr");
this.expansion.element.style.justifyContent = "start";
// Fields
this.addField("hex", "FECC", 16, 16, false);
this.addField("hex", "EICC", 0, 16, false);
}
// Expansion controls for PC
expansionPC() {
// Expansion area
this.expansion = this.newPanel({
layout : "grid",
columns : "auto max-content",
overflowX: "visible",
overflowY: "visible",
visible : false
});
this.expansion.element.setAttribute("name", "expansion");
// From text box
let lbl = this.expansion.add(this.newLabel({
localized: true,
text : "{cpu.pcFrom}"
}));
lbl.element.setAttribute("name", "indent");
this.txtFrom = this.expansion.add(this.newTextBox());
this.txtFrom.element.setAttribute("name", "value");
// To text box
lbl = this.expansion.add(this.newLabel({
localized: true,
text : "{cpu.pcTo}"
}));
lbl.element.setAttribute("name", "indent");
this.txtTo = this.expansion.add(this.newTextBox());
this.txtTo.element.setAttribute("name", "value");
}
// Expansion controls for PIR
expansionPIR() {
// Expansion area
this.expansion = this.newPanel({
layout : "grid",
columns : "max-content auto",
overflowX: "visible",
overflowY: "visible",
visible : false
});
this.expansion.element.setAttribute("name", "expansion");
this.expansion.element.setAttribute("register", "pir");
// Fields
this.addField("hex", "PT", 0, 16, true);
}
// Expansion controls for program registers
expansionProgram() {
// Expansion area
this.expansion = this.newPanel({
layout : "grid",
columns : "auto",
overflowX: "visible",
overflowY: "visible",
visible : false
});
this.expansion.element.setAttribute("role", "radiogroup");
this.expansion.element.setAttribute("name", "expansion");
this.expansion.element.setAttribute("register", "program");
this.expansion.element.style.justifyContent = "start";
// Format selections
let group = new Toolkit.ButtonGroup();
for (let opt of [ "hex", "signed", "unsigned", "float_" ]) {
let fmt = group.add(this.expansion.add(this.newRadioButton({
checked: opt == "hex",
text : "{cpu." + opt + "}",
})));
fmt.addChangeListener(
(opt=>e=>this.setFormat(opt))
(opt.replace("_", ""))
);
}
}
// Expansion controls for EIPSW, FEPSW and PSW
expansionPSW() {
// Expansion area
this.expansion = this.newPanel({
layout : "grid",
columns : "max-content auto",
overflowX: "visible",
overflowY: "visible",
visible : false
});
this.expansion.element.setAttribute("name", "expansion");
this.expansion.element.setAttribute("register", "psw");
this.expansion.element.style.justifyContent = "start";
// Fields
this.addField("bit" , "CY" , 3, 1, false);
this.addField("bit" , "FRO", 9, 1, false);
this.addField("bit" , "OV" , 2, 1, false);
this.addField("bit" , "FIV", 8, 1, false);
this.addField("bit" , "S" , 1, 1, false);
this.addField("bit" , "FZD", 7, 1, false);
this.addField("bit" , "Z" , 0, 1, false);
this.addField("bit" , "FOV", 6, 1, false);
this.addField("bit" , "NP" , 15, 1, false);
this.addField("bit" , "FUD", 5, 1, false);
this.addField("bit" , "EP" , 14, 1, false);
this.addField("bit" , "FPR", 4, 1, false);
this.addField("bit" , "ID" , 12, 1, false);
this.addField("decimal", "I" , 16, 4, false);
this.addField("bit" , "AE" , 13, 1, false);
}
// Expansion controls for TKCW
expansionTKCW() {
// Expansion area
this.expansion = this.newPanel({
layout : "grid",
columns : "max-content auto",
overflowX: "visible",
overflowY: "visible",
visible : false
});
this.expansion.element.setAttribute("name", "expansion");
this.expansion.element.setAttribute("register", "tkcw");
this.expansion.element.style.justifyContent = "start";
// Fields
this.addField("bit" , "FIT", 7, 1, true);
this.addField("bit" , "FUT", 4, 1, true);
this.addField("bit" , "FZT", 6, 1, true);
this.addField("bit" , "FPT", 3, 1, true);
this.addField("bit" , "FVT", 5, 1, true);
this.addField("bit" , "OTM", 8, 1, true);
this.addField("bit" , "RDI", 2, 1, true);
this.addField("decimal", "RD" , 0, 2, true);
}
// Bit check box change event handler
onbit(field) {
let mask = 1 << field.bit;
let value = this.value;
if (field.isChecked())
value = (value | mask & 0xFFFFFFFF) >>> 0;
else value = (value & ~mask & 0xFFFFFFFF) >>> 0;
this.setRegister(value);
}
// Number text box commit event handler
onnumber(field) {
let value = parseInt(field.getText(),
field.type == "decimal" ? 10 : 16);
if (value == NaN)
value = this.value;
this.setRegister((
this.value & ~(field.mask << field.bit) |
(value & field.mask) << field.bit
) >>> 0);
}
// Value text box commit event handler
onvalue() {
// Process the entered value
let value = this.txtValue.getText();
switch (this.format) {
case "float":
value = parseFloat(value);
if (Number.isFinite(value))
value = CPUWindow.Register.floatToIntBits(value);
break;
case "hex":
value = parseInt(value, 16);
break;
case "signed":
case "unsigned":
value = parseInt(value);
}
// Update the value
if (!Number.isFinite(value))
this.setValue(this.value);
else this.setRegister((value & 0xFFFFFFFF) >>> 0);
}
// Update the value of the register
setRegister(value) {
this.debug.core.postMessage({
command: "SetRegister",
debug : "CPU",
id : this.id,
sim : this.debug.sim,
type : this.system ? this.id == -1 ? "pc" : "system" : "program",
value : value
});
}
}).initializer();