"use strict"; // CPU register and disassembler display (globalThis.CPUWindow = class CPUWindow extends Toolkit.Window { // Static initializer static initializer() { // System register IDs this.ADTRE = 25; this.CHCW = 24; this.ECR = 4; this.EIPC = 0; this.EIPSW = 1; this.FEPC = 2; this.FEPSW = 3; this.PC = -1; this.PIR = 6; this.PSW = 5; this.TKCW = 7; // Program register names this.PROGRAM = { [ 2]: "hp", [ 3]: "sp", [ 4]: "gp", [ 5]: "tp", [31]: "lp" }; } // Object constructor constructor(debug, options) { super(debug.gui, options); // Configure instance fields this.address = 0xFFFFFFF0; this.columns = [ 0, 0, 0, 0 ]; this.debug = debug; this.pendingDasm = { mode: null }; this.pendingRegs = { mode: null }; this.rows = []; // Configure properties this.setProperty("sim", ""); // Configure elements this.initDisassembler(); this.initSystemRegisters(); this.initProgramRegisters(); this.initWindow(); // Disassembler on the left this.mainWrap.add(this.dasmWrap); // Registers on the right this.regs = this.newPanel({ layout: "grid", rows : "max-content max-content auto" }); this.regs.element.setAttribute("name", "wrap-registers"); // Splitter between disassembler and registers this.mainSplit = this.newSplitter({ component : this.regs, orientation: "vertical", edge : "right", name : "{cpu.mainSplit}" }); this.mainSplit.element.setAttribute("name", "split-main"); this.mainSplit.element.style.width = "3px"; this.mainSplit.element.style.minWidth = "3px"; this.mainSplit.element.style.cursor = "ew-resize"; this.mainWrap.add(this.mainSplit); // Registers on the right this.mainWrap.add(this.regs); // System registers on top this.regs.add(this.sysWrap); // Splitter between system registers and program registers this.regsSplit = this.regs.add(this.newSplitter({ component : this.sysWrap, orientation: "horizontal", edge : "top", name : "{cpu.regsSplit}" })); this.regsSplit.element.style.height = "3px"; this.regsSplit.element.style.minHeight = "3px"; this.regsSplit.element.style.cursor = "ns-resize"; // Program registers on the bottom this.regs.add(this.proWrap); } // Initialize disassembler pane initDisassembler() { // Wrapping element to hide overflowing scrollbar this.dasmWrap = this.newPanel({ layout : "grid", overflowX: "hidden", overflowY: "hidden" }); this.dasmWrap.element.setAttribute("name", "wrap-disassembler"); // Main element this.dasm = this.dasmWrap.add(this.newPanel({ focusable: true, name : "{cpu.disassembler}", overflowX: "auto", overflowY: "hidden" })); this.dasm.element.setAttribute("name", "disassembler"); this.dasm.addResizeListener(b=>this.onresize(b)); this.dasm.element.addEventListener("keydown", e=>this.onkeydasm(e)); this.dasm.element.addEventListener("wheel" , e=>this.onwheel (e)); this.rows.push(this.dasm.add(new CPUWindow.Row(this.dasm))); } // Initialize program registers pane initProgramRegisters() { // Wrapping element to hide overflowing scrollbar this.proWrap = this.newPanel({ layout : "grid", overflow: "hidden" }); this.proWrap.element.setAttribute("name", "wrap-program-registers"); // Main element this.proRegs = this.proWrap.add(this.newPanel({ overflowX: "auto", overflowY: "scroll" })); this.proRegs.element.setAttribute("name", "program-registers"); // List of registers this.proRegs.registers = {}; for (let x = 0; x <= 31; x++) this.addRegister(false, x, CPUWindow.PROGRAM[x] || "r" + x); } // Initialize system registers pane initSystemRegisters() { // Wrapping element to hide overflowing scrollbar this.sysWrap = this.newPanel({ layout : "grid", overflow: "hidden" }); this.sysWrap.element.setAttribute("name", "wrap-system-registers"); // Main element this.sysRegs = this.sysWrap.add(this.newPanel({ overflowX: "auto", overflowY: "scroll" })); this.sysRegs.element.setAttribute("name", "system-registers"); // List of registers this.sysRegs.registers = {}; this.addRegister(true, CPUWindow.PC , "PC" ); this.addRegister(true, CPUWindow.PSW , "PSW" ); this.addRegister(true, CPUWindow.ADTRE, "ADTRE"); this.addRegister(true, CPUWindow.CHCW , "CHCW" ); this.addRegister(true, CPUWindow.ECR , "ECR" ); this.addRegister(true, CPUWindow.EIPC , "EIPC" ); this.addRegister(true, CPUWindow.EIPSW, "EIPSW"); this.addRegister(true, CPUWindow.FEPC , "FEPC" ); this.addRegister(true, CPUWindow.FEPSW, "FEPSW"); this.addRegister(true, CPUWindow.PIR , "PIR" ); this.addRegister(true, CPUWindow.TKCW , "TKCW" ); this.addRegister(true, 29 , "29" ); this.addRegister(true, 30 , "30" ); this.addRegister(true, 31 , "31" ); this.sysRegs.registers[CPUWindow.PSW].setExpanded(true); } // Initialize window and client initWindow() { // Configure element this.element.setAttribute("window", "cpu"); // Configure body this.body.element.setAttribute("filter", ""); // Configure client this.client.setLayout("grid", { columns: "auto" }); // Configure main wrapper this.mainWrap = this.client.add(this.newPanel({ layout : "grid", columns: "auto max-content max-content" })); this.mainWrap.element.setAttribute("name", "wrap-main"); } ///////////////////////////// Public Methods ////////////////////////////// // Update the display with current emulation data refresh(seekToPC, dasm, regs) { if (dasm || dasm === undefined) this.refreshDasm(this.address, 0, !!seekToPC); if (regs || regs === undefined) this.refreshRegs(); } // Specify whether the component is visible setVisible(visible, focus) { let prev = this.visible visible = !!visible; super.setVisible(visible, focus); if (visible && !prev) this.refresh(); } ///////////////////////////// Message Methods ///////////////////////////// // Message received message(msg) { switch (msg.command) { case "GetRegisters": this.getRegisters(msg); break; case "ReadBuffer" : this.readBuffer (msg); break; case "SetRegister" : this.setRegister (msg); break; case "RunNext": case "SingleStep": this.refresh(true); break; } } // Retrieved all register values getRegisters(msg) { // Update controls this.sysRegs.registers[CPUWindow.PC ] .setValue(msg.pc, msg.pcFrom, msg.pcTo); this.sysRegs.registers[CPUWindow.PSW ].setValue(msg.psw ); this.sysRegs.registers[CPUWindow.ADTRE].setValue(msg.adtre); this.sysRegs.registers[CPUWindow.CHCW ].setValue(msg.chcw ); this.sysRegs.registers[CPUWindow.ECR ].setValue(msg.ecr ); this.sysRegs.registers[CPUWindow.EIPC ].setValue(msg.eipc ); this.sysRegs.registers[CPUWindow.EIPSW].setValue(msg.eipsw); this.sysRegs.registers[CPUWindow.FEPC ].setValue(msg.fepc ); this.sysRegs.registers[CPUWindow.FEPSW].setValue(msg.fepsw); this.sysRegs.registers[CPUWindow.PIR ].setValue(msg.pir ); this.sysRegs.registers[CPUWindow.TKCW ].setValue(msg.tkcw ); this.sysRegs.registers[29 ].setValue(msg.sr29 ); this.sysRegs.registers[30 ].setValue(msg.sr30 ); this.sysRegs.registers[31 ].setValue(msg.sr31 ); for (let x = 0; x <= 31; x++) this.proRegs.registers[x].setValue(msg.program[x]); // Check for pending display updates let mode = this.pendingDasm.mode; this.pendingRegs.mode = null; switch (mode) { case "first": case null : return; case "refresh": this.refreshRegs(); } } // Retrieved data for disassembly readBuffer(msg) { let lines = Math.min(msg.lines, this.rows.length); // Disassemble the visible instructions let dasm = Disassembler.disassemble( new Uint8Array(msg.buffer), 0, msg.address, msg.target, msg.pc, msg.line, lines); // Ensure PC is visible if requested let reseeking = false; if (msg.seekToPC) { let visible = this.lines(true); let count = Math.min(msg.lines, visible); let x; // Ensure PC is visible in the disassembly for (x = 0; x < count; x++) if (dasm[x].address == msg.pc) break; // Seek to display PC in the view if (x == count) { reseeking = true; this.seek(msg.pc, Math.floor(visible / 3)); } } // Not seeking to PC if (!reseeking) { // Configure instance fields this.address = dasm[0].address; // Configure elements for (let x = 0; x < lines; x++) this.rows[x].update(dasm[x], this.columns, msg.pc); for (let x = 0; x < lines; x++) this.rows[x].setWidths(this.columns); } // Check for pending display updates let address = this.pendingDasm.address === null ? this.address : this.pendingDasm.address; let line = this.pendingDasm.line === null ? 0 : this.pendingDasm.line ; let mode = this.pendingDasm.mode; this.pendingDasm.mode = null; switch (mode) { case "first": case null : return; case "refresh": case "scroll" : case "seek" : this.refreshDasm(address, line); } } // Modified a register value setRegister(msg) { (msg.type == "program" ? this.proRegs : this.sysRegs) .registers[msg.id].setValue(msg.value); this.refreshDasm(this.address, 0); } ///////////////////////////// Private Methods ///////////////////////////// // Insert a register control to a register list addRegister(system, id, name) { let list = system ? this.sysRegs : this.proRegs; let reg = new CPUWindow.Register(this.debug, list, system, id, name); list.registers[id] = reg; list.add(reg); if (reg.expands) list.add(reg.expansion); } // The window is being displayed for the first time firstShow() { super.firstShow(); this.center(); this.mainSplit.measure(); this.regsSplit.measure(); this.seek(this.address, Math.floor(this.lines(true) / 3)); } // Determine the height in pixels of one row of output lineHeight() { return Math.max(10, this.rows[0].address.getBounds().height); } // Determine the number of rows of output lines(fullyVisible) { let gridHeight = this.dasm.getBounds().height; let lineHeight = this.lineHeight(); let ret = gridHeight / lineHeight; ret = fullyVisible ? Math.floor(ret) : Math.ceil(ret); return Math.max(1, ret); } // Key down event handler onkeydasm(e) { // Control is pressed if (e.ctrlKey) switch (e.key) { // Auto-fit case "f": case "F": for (let x = 0; x < this.columns.length; x++) this.columns[x] = 0; for (let row of this.rows) row.setWidths(this.columns); for (let row of this.rows) row.measure(this.columns); for (let row of this.rows) row.setWidths(this.columns); break; // Goto case "g": case "G": let addr = prompt(this.application.translate("{app.goto_}")); if (addr === null) break; this.seek( (parseInt(addr, 16) & 0xFFFFFFFE) >>> 0, Math.floor(this.lines(true) / 3) ); break; default: return; } // Processing by key else switch (e.key) { case "ArrowDown": this.scroll( 1); break; case "ArrowUp" : this.scroll(-1); break; case "PageDown" : this.scroll( this.lines(true)); break; case "PageUp" : this.scroll(-this.lines(true)); break; default: return; } // Configure event e.preventDefault(); e.stopPropagation(); } // Key down event handler onkeydown(e) { // Processing by key switch (e.key) { // Run next case "F10": this.debug.core.postMessage({ command : "RunNext", dbgwnd : "CPU", sim : this.debug.sim, seekToPC: true }); break; // Single step case "F11": this.debug.core.postMessage({ command : "SingleStep", dbgwnd : "CPU", sim : this.debug.sim, seekToPC: true }); break; default: return super.onkeydown(e); } // Configure event e.preventDefault(); e.stopPropagation(); } // Resize event handler onresize(bounds) { // Update Splitters this.mainSplit.measure(); this.regsSplit.measure(); // Configure disassembler elements let lines = this.lines(false); for (let y = this.rows.length; y < lines; y++) this.rows[y] = this.dasm.add(new CPUWindow.Row(this.dasm)); for (let y = lines; y < this.rows.length; y++) this.dasm.remove(this.rows[y]); if (this.rows.length > lines) this.rows.splice(lines, this.rows.length - lines); this.refreshDasm(); } // Mouse wheel event handler onwheel(e) { let sign = Math.sign(e.deltaY); let mag = Math.abs (e.deltaY); if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL) mag = Math.max(1, Math.floor(mag / this.lineHeight())); // Configure element e.preventDefault(); e.stopPropagation(); // Configure display this.scroll(sign * mag); } // Update the disassembler with current emulation data refreshDasm(address, line, seekToPC) { // Do nothing while closed or already waiting to refresh if (!this.isVisible() || this.pendingDasm.mode != null) return; // Working variables address = address !== undefined ? (address & 0xFFFFFFFE) >>> 0 : this.address; line = line || 0; let lines = this.lines(false); let start = -10 - Math.max(0, line); let end = lines - Math.min(0, line); // Configure pending state this.pendingDasm.mode = "first"; this.pendingDasm.address = null; this.pendingDasm.line = null; // Request bus data from the WebAssembly core this.debug.core.postMessage({ command : "ReadBuffer", sim : this.debug.sim, dbgwnd : "CPU", address : (address + start * 4 & 0xFFFFFFFE) >>> 0, line : line, lines : lines, target : address, seekToPC: seekToPC, size : (end - start + 1) * 4 }); } // Update the register list with current emulation data refreshRegs() { // Schedule another refresh if (this.pendingRegs.mode != null) this.pendingRegs.mode = "refresh"; // Do nothing while closed or already waiting to refresh if (!this.isVisible() || this.pendingRegs.mode != null) return; // Configure pending state this.pendingRegs.mode = "first"; this.pendingRegs.address = null; this.pendingRegs.line = null; // Request bus data from the WebAssembly core this.debug.core.postMessage({ command: "GetRegisters", dbgwnd : "CPU", sim : this.debug.sim }); } // Move to a new address relative to the current address scroll(lines) { switch (this.pendingDasm.mode) { case "first" : case "refresh": this.pendingDasm.mode = "scroll"; this.pendingDasm.line = -lines; break; case "seek" : case "scroll": this.pendingDasm.mode = "scroll"; this.pendingDasm.line -= lines; break; case null: this.refreshDasm(this.address, -lines); } } // Move to a new address positioned at a particular row of output seek(address, line) { switch (this.pendingDasm.mode) { case "first" : case "refresh": this.pendingDasm.mode = "seek"; this.pendingDasm.address = address; this.pendingDasm.line = line; break; case "seek" : case "scroll": this.pendingDasm.mode = "seek"; this.pendingDasm.address = address; this.pendingDasm.line += line; break; case null: this.refreshDasm(address, line); } } }).initializer(); // One row of disassembly CPUWindow.Row = class Row extends Toolkit.Panel { // Object constructor constructor(parent) { super(parent.application, { layout : "grid", columns : "repeat(4, max-content)", hollow : false, overflowX: "visible", overflowY: "visible" }); // Configure element this.element.style.justifyContent = "start"; this.element.setAttribute("name", "row"); // Address column this.address = this.add(parent.newLabel({ text: "\u00a0" })); this.address.element.setAttribute("name", "address"); // Bytes column this.bytes = this.add(parent.newLabel({ text: "\u00a0" })); this.bytes.element.setAttribute("name", "bytes"); // Mnemonic column this.mnemonic = this.add(parent.newLabel({ text: "\u00a0" })); this.mnemonic.element.setAttribute("name", "mnemonic"); // Operands column this.operands = this.add(parent.newLabel({ text: "\u00a0" })); this.operands.element.setAttribute("name", "operands"); } ///////////////////////////// Package Methods ///////////////////////////// // Measure the content widths of each column of output measure(columns) { columns[0] = Math.max(columns[0], this.address .getBounds().width); columns[1] = Math.max(columns[1], this.bytes .getBounds().width); columns[2] = Math.max(columns[2], this.mnemonic.getBounds().width); columns[3] = Math.max(columns[3], this.operands.getBounds().width); } // Specify the column widths setWidths(columns) { this.address .element.style.minWidth = columns[0] + "px"; this.bytes .element.style.minWidth = columns[1] + "px"; this.mnemonic.element.style.minWidth = columns[2] + "px"; this.operands.element.style.minWidth = columns[3] + "px"; } // Update the output labels with emulation state content update(line, columns, pc) { if (pc == line.address) this.element.setAttribute("pc", ""); else this.element.removeAttribute("pc"); this.address.setText( ("0000000" + line.address.toString(16).toUpperCase()).slice(-8)); let bytes = new Array(line.bytes.length); for (let x = 0; x < bytes.length; x++) bytes[x] = ("0" + line.bytes[x].toString(16).toUpperCase()).slice(-2); this.bytes.setText(bytes.join(" ")); this.mnemonic.setText(line.mnemonic); this.operands.setText(line.operands); this.measure(columns); } };