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.format = HEX; this.index = index; this.isExpanded = null; this.list = list; this.metrics = { width: 0, height: 0 }; this.orMask = orMask; this.sim = list.sim; this.system = list.system; this.value = 0x00000000; // Establish elements let row = document.createElement("tr"); let cell; list.view.append(row); // 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" }); row .append(cell = document.createElement("td")); cell.className = "tk"; cell.style.width = "1px"; cell.append(this.btnExpand.element); // Name label this.lblName = document.createElement("div"); 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"; row .append(cell = document.createElement("td")); cell.className = "tk"; cell.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()); row .append(cell = document.createElement("td")); Object.assign(cell.style, { textAlign: "right", width : "1px" }); cell.className = "tk"; cell.append(this.txtValue.element); // Expansion area if (this.expansion != null) 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 = document.createElement("tr"); exp.contents = new Toolkit.Component(this.app, { className: "tk tk-expansion", id : Toolkit.id(), tagName : "div", style : { display : "grid", gridTemplateColumns: this.system ? "repeat(2, max-content)" : "max-content" } }); let cell = document.createElement("td"); cell.className = "tk"; cell.colSpan = "3"; cell.append(exp.contents.element); exp.append(cell); exp = exp.contents; // Produce program register controls if (!this.system) { 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.setFormat(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.format) { // 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); } ///////////////////////////// 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 = Util.u32(value); // Value text box switch (this.format) { // 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); } } // Specify whether the expansion area is visible setExpanded(expanded) { expanded = !!expanded; // Error checking if (this.expansion == null || expanded === this.isExpanded) return; // Configure instance fields this.isExpanded = expanded; // Configure elements let key = expanded ? "common.collapse" : "common.expand"; this.btnExpand.setAttribute("aria-expanded", expanded); this.btnExpand.setToolTip(key); this.expansion.style.display = expanded ? "table-row" : "none"; } // Specify the font metrics setMetrics(width, height) { // Configure instance fields this.metrics = { width: width, height: height }; // Height height += "px"; this.txtValue.element.style.height = height; for (let ctrl of this.controls.filter(c=>c.size > 1)) ctrl.element.style.height = height; // Hexadecimal formatting if (this.format == HEX) { this.txtValue.element.style.width = (width * 8) + "px"; this.txtValue.setMaxLength(8); } // Decimal formatting else { this.txtValue.element.style.removeProperty("width"); this.txtValue.setMaxLength(null); } // Expansion text boxes for (let box of this.controls.filter(c=>c.size > 1)) { box.element.style.height = height; if (box.size == 16) box.element.style.width = (width * 4) + "px"; } } ///////////////////////////// Private Methods ///////////////////////////// // Specify the formatting type of the register value setFormat(format) { if (format == this.format) return; 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); } // 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.registers = []; 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 : "table", style : { width: "100%" } })); // 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.add(new Register(this, 0, 0x00000000, 0x00000000)); for (let x = 1; x < 32; x++) this.add(new Register(this, x, 0xFFFFFFFF, 0x00000000)); } // Initialie a list of system registers initSystem() { this.add(new Register(this, PC , 0xFFFFFFFE, 0x00000000)); this.add(new Register(this, PSW , 0x000FF3FF, 0x00000000)); this.add(new Register(this, ADTRE, 0xFFFFFFFE, 0x00000000)); this.add(new Register(this, CHCW , 0x00000002, 0x00000000)); this.add(new Register(this, ECR , 0xFFFFFFFF, 0x00000000)); this.add(new Register(this, EIPC , 0xFFFFFFFE, 0x00000000)); this.add(new Register(this, EIPSW, 0x000FF3FF, 0x00000000)); this.add(new Register(this, FEPC , 0xFFFFFFFE, 0x00000000)); this.add(new Register(this, FEPSW, 0x000FF3FF, 0x00000000)); this.add(new Register(this, PIR , 0x00000000, 0x00005346)); this.add(new Register(this, TKCW , 0x00000000, 0x000000E0)); this.add(new Register(this, 29 , 0xFFFFFFFF, 0x00000000)); this.add(new Register(this, 30 , 0x00000000, 0x00000004)); this.add(new Register(this, 31 , 0xFFFFFFFF, 0x00000000)); } ///////////////////////////// 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 text boxes for (let reg of this.registers) reg.setMetrics(width, height); // 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) { for (let reg of Object.entries(SYSREGS)) this[reg[0]].refresh(registers[reg[1].toLowerCase()]); } // 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() { for (let reg of this.registers) reg.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.getBoundingClientRect().bottom - this.view.getBounds().top; } return ret; } ///////////////////////////// Private Methods ///////////////////////////// // Add a register to the list add(reg) { this[reg.index] = reg; this.registers.push(reg); } // Retrieve CPU state from the core async fetch() { this.refresh( await this.sim[this.method]({ subscribe: this.isSubscribed && this.subscription }) ); } } export { RegisterList };