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(); return; } // Processing by key: CTRL up else switch (e.key) { case "F10": this.debug.app.runNext(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) { let firstShow = !this.shown && e.visible; // Configure instance fields this.shown = this.shown || e.visible; // Window visible for the first time if (firstShow) { this.disassembler.firstShow(); this.registers .firstShow(); } // Configure subscriptions if (!e.visible) { if (this.registers) this.registers .unsubscribe(); if (this.disassembler) this.disassembler.unsubscribe(); } else { this.registers .fetch(); this.disassembler.fetch(); } } } /////////////////////////////////////////////////////////////////////////////// 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 = [ 0, 0, 0, 0, 0, 0 ]; 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; // 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.sizer = new Toolkit.Label(cpu.debug.app, { class : "tk label mono", visible : false, visibility: true, style: { position: "absolute" } }); this.sizer.setText("\u00a0", false); //   view.append(this.sizer); } ///////////////////////////// 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.sizer) return; // Working variables let tall = this.tall(false) + 1; let grew = this.lines.length < tall; // Process all new lines for (let y = this.lines.length; y < tall; y++) { let first = y == 0 ? " first" : ""; let resizer = y != 0 ? null : new ResizeObserver(()=>this.colResize()); let line = { lblAddress : document.createElement("div"), lblBytes : [], lblMnemonic: document.createElement("div"), lblOperands: document.createElement("div"), spacer : document.createElement("div") }; // Address label line.lblAddress.className = "addr" + first; if (y == 0) resizer.observe(line.lblAddress); this.view.append(line.lblAddress); // Byte labels for (let x = 0; x < 4; x++) { let lbl = line.lblBytes[x] = document.createElement("div"); lbl.className = "byte" + first + (x == 0 ? " b0" : ""); if (y == 0) { lbl.style.minWidth = "0px"; resizer.observe(lbl); } this.view.append(lbl); } // Mnemonic label line.lblMnemonic.className = "inst" + first; if (y == 0) resizer.observe(line.lblMnemonic); this.view.append(line.lblMnemonic); // Operand label line.lblOperands.className = "ops" + first; this.view.append(line.lblOperands); // Growing spacer line.spacer.className = "spacer" + first; this.view.append(line.spacer); // All elements line.all = line.lblBytes.concat([ line.lblAddress, line.lblMnemonic, line.lblOperands, line.spacer ]); 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(); line.spacer .remove(); for (let lbl of line.lblBytes) lbl.remove(); this.lines.splice(tall, 1); } // Configure scroll bar this.hscroll.unitIncrement = this.sizer.element.getBoundingClientRect().height; // 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.sizer.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; } // 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(); } // Ensure PC is visible in the view followPC(pc = null) { let tall = this.tall(false); 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++); // Process all lines let index = 20 - this.viewLine; for (let y = 0; y < this.lines.length; y++, index++) { let line = this.lines[y]; let method = "remove"; // 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 < 4; 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 < 4; x++) line.lblBytes[x].innerText = dasm.bytes[x] || ""; if (this.cpu.registers.pc == dasm.rawAddress) method = "add"; if (this.bytesColumn) showBytes = Math.max(showBytes, dasm.bytes.length); } // Configure whether PC is on this line for (let elm of line.all) elm.classList[method]("pc"); } // Configure which byte columns are visible for (let line of this.lines) { for (let x = 0; x < 4; x++) { line.lblBytes[x].style [x < showBytes ? "removeProperty" : "setProperty"] ("display", "none") ; } } // Configure layout this.view.element.style.gridTemplateColumns = "repeat(" + (showBytes + 3) + ", max-content) auto"; } // 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 = null) { return Math.max(1, Math[fully===null ? "abs" : fully?"floor":"ceil"]( this.view .element.getBoundingClientRect().height / this.sizer.element.getBoundingClientRect().height )); } } /////////////////////////////////////////////////////////////////////////////// // Entry in the register lists class Register { //////////////////////////////// Constants //////////////////////////////// // Register types static PROGRAM = 0; static PLAIN = 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) { let app = registers.cpu.debug.app; // Configure instance fields this.controls = []; this.dasm = registers.cpu.debug.app.dasm; this.debug = registers.cpu.debug; this.expansion = null; this.registers = registers; this.target = registers; this.type = type; // Resolve the register reference key = key.slice(); while (key.length != 1) this.target = this.target[key.shift()]; this.key = key[0]; // Main controls this.main = new Toolkit.Component(app, { class: "main", style: { alignItems : "center", display : "grid", gridTemplateColumns: "max-content auto" } }); // Expand/collapse button this.btnExpand = new Toolkit.Component(app, { class: "tk expand", style: { alignItems : "center", display : "grid", gridTemplateColumns: "max-content auto" } }); this.main.add(this.btnExpand); // Expand/collapse icon this.icon = new Toolkit.Component(app, { class: "icon" }); this.btnExpand.add(this.icon); // Register name this.label = new Toolkit.Label(app, { class: "label", id : Toolkit.id() }); this.btnExpand.add(this.label); // 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 button this.btnExpand.element.setAttribute("aria-expanded", "false" ); this.btnExpand.element.setAttribute("tabindex" , "0" ); this.btnExpand.element.setAttribute("role" , "button"); this.btnExpand.element .setAttribute("aria-controls", this.expansion.element.id); // Expansion area this.btnExpand.addEventListener("keydown", e=>this.expKeyDown (e)); this.btnExpand.addEventListener("pointerdown", e=>this.expPointerDown(e)); this.expansion.visible = false; } // Update controls this.refresh(); // PSW is initially expanded if (type == Register.PSW && key == 5) this.expanded = true; // System registers after PSW are initially hidden else if (key != "pc" && key != 5 && type != Register.PROGRAM) { this.main.style.position = "absolute"; this.main.style.visibility = "hidden"; } } // 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); this.format = Register.HEX; // 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 button key press expKeyDown(e) { if ( !(e.altKey || e.ctrlKey || e.shiftKey) && (e.key == " " || e.key == "Enter") ) this.expanded = !this.expanded; } // Expand/collapse button pointer down expPointerDown(e) { this.btnExpand.element.focus(); if (e.button != 0) return; Toolkit.handle(e); this.expanded = !this.expanded; } // Program register format changed onProgram(format) { this.format = format; this.txtValue.element.classList [format == Register.HEX ? "add" : "remove"]("mono"); this.formatValue(); } // 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) { let oldValue = this.target[this.key]; let target = e.target.component; let newValue = parseInt(target.value, target.isHex ? 16 : 10); // Cannot change the value if (e.disabled) return; // 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) { let text = this.txtValue.value; let value = null; Toolkit.handle(e); // Program register with non-default format if (this.type == Register.PROGRAM && this.format != Register.HEX) { switch (this.format) { case Register.SIGNED: value = parseInt(text); if ( !Number.isInteger(value) || value < -2147483648 || value > +2147483647 ) value = null; break; case Register.UNSIGNED: value = parseInt(text); if ( !Number.isInteger(value) || value < 0 || value > 4294967295 ) value = null; break; case Register.FLOAT: value = parseFloat(text); value = !Number.isFinite(value) || value < Debugger.ixf(0xFF7FFFFF) || value > Debugger.ixf(0x7F7FFFFF) ? null : Debugger.fxi(value) >>> 0; break; } } // Default hexadecimal format else { value = parseInt(text, 16); if ( !Number.isInteger(value) || value < 0 || value > 4294967295 ) value = null; } // Apply the new value if (value === null) this.formatValue(); 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.txtValue.value = this.debug.dasm.hex(this.target[this.key], 8, false); } ///////////////////////////// Public Methods ////////////////////////////// // The expansion area is visible get expanded() { return this.btnExpand.element.getAttribute("aria-expanded") == "true"; } set expanded(expanded) { expanded = !!expanded; if (this.expansion == null || expanded == this.expanded) return; this.btnExpand.element.setAttribute("aria-expanded", expanded); this.expansion.visible = expanded; } ///////////////////////////// Package Methods ///////////////////////////// // Update controls from simulation state refresh() { // Name label this.label.setText( this.key == "pc" ? "PC" : this.type != Register.PROGRAM ? Disassembler.SYSNAMES[this.key] : this.dasm.programRegister(this.key) ); // Value text box let value = this.target[this.key]; this.formatValue(); // 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 the value as a string in the text box formatValue() { let text = ""; let value = this.target[this.key]; // Program register with non-default format if (this.type == Register.PROGRAM && this.format != Register.HEX) { switch (this.format) { case Register.SIGNED: text = (value >> 0).toString(); break; case Register.UNSIGNED: text = (value >>> 0).toString(); break; case Register.FLOAT: value = Debugger.ixf(value); if (Number.isFinite(value)) { 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"; } else if (!Number.isNaN(value)) { text = (value == Number.NEGATIVE_INFINITY ? "-" : "") + this.debug.app.localize("{debug.cpu.infinity}") ; } else text = "NaN"; break; } } // Default hexadecimal format else text = this.dasm.hex(value >>> 0, 8, false); // Update the text this.txtValue.value = text; } // Specify a new register value async setValue(value) { // Update the value in the simulation state let sim = this.debug.sim; let result = await ( this.key == "pc" ? this.debug.core .setProgramCounter(sim, value, { refresh: [ this.registers.cpu.disassembler.subscription ] }) : this.type != Register.PROGRAM ? this.debug.core .setSystemRegister(sim, this.key, value) : this.debug.core .setProgramRegister(sim, this.key, value) ); // Update the value in the debugger window this.target[this.key] = result.value; // Update the register controls this.refresh(); } } /////////////////////////////////////////////////////////////////////////////// // Register list manager class RegisterPane extends Toolkit.SplitPane { //////////////////////////////// Constants //////////////////////////////// // System register templates static SYSTEMS = [ [ [ "pc" ], Register.PLAIN ], [ [ "system", 5 ], Register.PSW ], [ [ "system", 25 ], Register.PLAIN ], [ [ "system", 24 ], Register.CHCW ], [ [ "system", 4 ], Register.ECR ], [ [ "system", 0 ], Register.PLAIN ], [ [ "system", 1 ], Register.PSW ], [ [ "system", 2 ], Register.PLAIN ], [ [ "system", 3 ], Register.PSW ], [ [ "system", 6 ], Register.PIR ], [ [ "system", 7 ], Register.TKCW ], [ [ "system", 29 ], Register.PLAIN ], [ [ "system", 30 ], Register.PLAIN ], [ [ "system", 31 ], Register.PLAIN ] ]; ///////////////////////// Initialization Methods ////////////////////////// constructor(cpu) { super(cpu.debug.app, { orientation: "top", style: { overflow: "visible" } }); // Configure instance fields this.cpu = cpu; this.list = []; this.pc = 0xFFFFFFF0; this.program = new Array(32); this.pending = false; this.subscription = [ 0, cpu.index, "cpu", "registers", "refresh" ], this.system = new Array(32); // Initialize regsiters 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: "auto", 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: "auto", overflowY: "scroll", view : this.lstProgram }); this.secondary = this.scrProgram; // Configure register lists for (let sys of RegisterPane.SYSTEMS) this.addRegister(new Register(this, sys[0], sys[1])); for (let x = 0; x < 32; x++) { this.addRegister(new Register(this, [ "program", x ], Register.PROGRAM)); } // 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.label.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].label.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].label.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(); for (let reg of this.list) { let style = reg.main.style; if (style.visibility) { style.removeProperty("position"); style.removeProperty("visibility"); } } // 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"; } // 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 };