import { Util } from /**/"../app/Util.js"; // Opcode definition class Opdef { constructor(format, mnemonic, signExtend) { this.format = format; this.mnemonic = mnemonic; this.signExtend = !!signExtend; } } // Top-level opcode definition lookup table by opcode let OPDEFS = [ new Opdef(1, "MOV" ), new Opdef(1, "ADD" ), new Opdef(1, "SUB" ), new Opdef(1, "CMP" ), new Opdef(1, "SHL" ), new Opdef(1, "SHR" ), new Opdef(1, "JMP" ), new Opdef(1, "SAR" ), new Opdef(1, "MUL" ), new Opdef(1, "DIV" ), new Opdef(1, "MULU" ), new Opdef(1, "DIVU" ), new Opdef(1, "OR" ), new Opdef(1, "AND" ), new Opdef(1, "XOR" ), new Opdef(1, "NOT" ), new Opdef(2, "MOV" ,1), new Opdef(2, "ADD",1), new Opdef(2, "SETF" ), new Opdef(2, "CMP" ,1), new Opdef(2, "SHL" ), new Opdef(2, "SHR" ), new Opdef(2, "CLI" ), new Opdef(2, "SAR" ), new Opdef(2, "TRAP" ), new Opdef(2, "RETI" ), new Opdef(2, "HALT" ), new Opdef(0, null ), new Opdef(2, "LDSR" ), new Opdef(2, "STSR" ), new Opdef(2, "SEI" ), new Opdef(2, null ), new Opdef(3, "Bcond"), new Opdef(3, "Bcond"), new Opdef(3, "Bcond" ), new Opdef(3, "Bcond"), new Opdef(3, "Bcond"), new Opdef(3, "Bcond" ), new Opdef(3, "Bcond"), new Opdef(3, "Bcond"), new Opdef(5,"MOVEA",1), new Opdef(5,"ADDI",1), new Opdef(4, "JR" ), new Opdef(4, "JAL" ), new Opdef(5, "ORI" ), new Opdef(5, "ANDI" ), new Opdef(5, "XORI" ), new Opdef(5, "MOVHI"), new Opdef(6, "LD.B" ), new Opdef(6, "LD.H" ), new Opdef(0, null ), new Opdef(6, "LD.W" ), new Opdef(6, "ST.B" ), new Opdef(6, "ST.H" ), new Opdef(0, null ), new Opdef(6, "ST.W" ), new Opdef(6, "IN.B" ), new Opdef(6, "IN.H" ), new Opdef(6, "CAXI" ), new Opdef(6, "IN.W" ), new Opdef(6, "OUT.B"), new Opdef(6, "OUT.H" ), new Opdef(7, null ), new Opdef(6, "OUT.W") ]; // Bit string mnemonic lookup table by sub-opcode let BITSTRINGS = [ "SCH0BSU", "SCH0BSD", "SCH1BSU", "SCH1BSD", null , null , null , null , "ORBSU" , "ANDBSU" , "XORBSU" , "MOVBSU" , "ORNBSU" , "ANDNBSU", "XORNBSU", "NOTBSU" ]; // Floating-point/Nintendo mnemonic lookup table by sub-opcode let FLOATENDOS = [ "CMPF.S", null , "CVT.WS", "CVT.SW" , "ADDF.S", "SUBF.S", "MULF.S", "DIVF.S" , "XB" , "XH" , "REV" , "TRNC.SW", "MPYHW" ]; // Program register names let PROREGS = { 2: "hp", 3: "sp", 4: "gp", 5: "tp", 31: "lp" }; // System register names let SYSREGS = [ "EIPC", "EIPSW", "FEPC", "FEPSW", "ECR" , "PSW" , "PIR" , "TKCW" , null , null , null , null , null , null , null , null , null , null , null , null , null , null , null , null , "CHCW", "ADTRE", null , null , null , null , null , null ]; // Condition mnemonics let CONDS = [ "V" , ["C" , "L" ], ["E" , "Z" ], "NH", "N" , "T" , "LT" , "LE", "NV", ["NC", "NL"], ["NE", "NZ"], "H" , "P" , "F" , "GE" , "GT" ]; // Output setting keys const SETTINGS = [ "bcondMerged", "branchAddress", "condCase", "condCL", "condEZ", "condNames", "hexCaps", "hexDollar", "hexSuffix", "imm5OtherHex", "imm5ShiftHex", "imm5TrapHex", "imm16AddiLargeHex", "imm16AddiSmallHex", "imm16MoveHex", "imm16OtherHex", "jmpBrackets", "memoryLargeHex", "memorySmallHex", "memoryInside", "mnemonicCaps", "operandReverse", "proregCaps", "proregNames", "setfMerged", "sysregCaps", "sysregNames" ]; /////////////////////////////////////////////////////////////////////////////// // Line // /////////////////////////////////////////////////////////////////////////////// // One line of output class Line { ///////////////////////// Initialization Methods ////////////////////////// constructor(parent, first) { // Configure instance fields this.parent = parent; // Configure labels this.lblAddress = this.label("tk-address" , first); this.lblBytes = this.label("tk-bytes" , first); this.lblMnemonic = this.label("tk-mnemonic", first); this.lblOperands = this.label("tk-operands", false); } ///////////////////////////// Package Methods ///////////////////////////// // Update the elements' display refresh(row, isPC) { // The row is not available if (!row) { this.lblAddress .innerText = "--------"; this.lblBytes .innerText = ""; this.lblMnemonic.innerText = "---"; this.lblOperands.innerText = ""; } // Update labels with the disassembled row's contents else { this.lblAddress .innerText = row.address; this.lblBytes .innerText = row.bytes; this.lblMnemonic.innerText = row.mnemonic; this.lblOperands.innerText = row.operands; } // Update style according to selection let method = isPC ? "add" : "remove"; this.lblAddress .classList[method]("tk-selected"); this.lblBytes .classList[method]("tk-selected"); this.lblMnemonic.classList[method]("tk-selected"); this.lblOperands.classList[method]("tk-selected"); } // Specify whether the elements on this line are visible setVisible(visible) { visible = visible ? "block" : "none"; this.lblAddress .style.display = visible; this.lblBytes .style.display = visible; this.lblMnemonic.style.display = visible; this.lblOperands.style.display = visible; } ///////////////////////////// Private Methods ///////////////////////////// // Create a display label label(className, first) { // Create the label element let label = document.createElement("div"); label.className = "tk " + className; // The label is part of the first row of output let element = label; if (first) { // Create a container element element = document.createElement("div"); element.append(label); element.max = 0; // Ensure the container can always fit the column contents Toolkit.addResizeListener(element, ()=>{ let width = Math.ceil(label.getBoundingClientRect().width); if (width <= element.max) return; element.max = width; element.style.minWidth = width + "px"; }); } // Configure elements this.parent.view.append(element); return label; } } /////////////////////////////////////////////////////////////////////////////// // Disassembler // /////////////////////////////////////////////////////////////////////////////// // Text disassembler for NVC class Disassembler extends Toolkit.ScrollPane { ///////////////////////// Initialization Methods ////////////////////////// constructor(debug) { super(debug.app, { className : "tk tk-scrollpane tk-disassembler", horizontal: Toolkit.ScrollPane.AS_NEEDED, focusable : true, tabStop : true, tagName : "div", vertical : Toolkit.ScrollPane.NEVER }); // Configure instance fields this.address = Util.u32(0xFFFFFFF0); this.app = debug.app; this.columns = [ 0, 0, 0, 0 ]; this.data = []; this.debug = debug; this.isSubscribed = false; this.lines = null; this.pc = this.address; this.pending = []; this.rows = []; this.scroll = 0; this.sim = debug.sim; // Default output settings this.setConfig({ bcondMerged : true, branchAddress : true, condCase : false, condCL : 1, condEZ : 1, condNames : true, hexCaps : true, hexDollar : false, hexSuffix : false, imm5OtherHex : false, imm5ShiftHex : false, imm5TrapHex : false, imm16AddiLargeHex: true, imm16AddiSmallHex: false, imm16MoveHex : true, imm16OtherHex : true, jmpBrackets : true, memoryLargeHex : true, memorySmallHex : false, memoryInside : false, mnemonicCaps : true, operandReverse : false, proregCaps : false, proregNames : true, setfMerged : false, sysregCaps : true, sysregNames : true }); // Configure viewport this.viewport.classList.add("tk-mono"); // Configure view let view = document.createElement("div"); view.className = "tk tk-view"; Object.assign(view.style, { display : "grid", gridTemplateColumns: "repeat(3, max-content) auto" }); this.setView(view); // Font-measuring element this.metrics = new Toolkit.Component(this.app, { className: "tk tk-metrics tk-mono", tagName : "div", style : { position : "absolute", visibility: "hidden" } }); this.metrics.element.innerText = "X"; this.append(this.metrics.element); // First row always exists this.lines = [ new Line(this, true) ]; // Configure event handlers Toolkit.addResizeListener(this.viewport, e=>this.onResize(e)); this.addEventListener("keydown", e=>this.onKeyDown (e)); this.addEventListener("wheel" , e=>this.onMouseWheel(e)); } ///////////////////////////// Event Handlers ////////////////////////////// // Key press onKeyDown(e) { let tall = this.tall(false); // Processing by key switch (e.key) { // Navigation case "ArrowDown" : this.fetch(+1 , true); break; case "ArrowUp" : this.fetch(-1 , true); break; case "PageDown" : this.fetch(+tall, true); break; case "PageUp" : this.fetch(-tall, true); break; // View control case "ArrowLeft" : this.horizontal.setValue( this.horizontal.value - this.horizontal.increment); break; case "ArrowRight": this.horizontal.setValue( this.horizontal.value + this.horizontal.increment); break; // Goto case "g": case "G": if (!e.ctrlKey) return; this.promptGoto(); break; // Single step case "F10": this.debug.runNext(); break; // Single step case "F11": this.debug.singleStep(); break; default: return; } // Configure event e.stopPropagation(); e.preventDefault(); } // 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.fetch(offset, true); } // Viewport resized onResize(e) { let fetch = false; let tall = this.tall(true); // Add additional lines to the output for (let x = 0; x < tall; x++) { if (x >= this.lines.length) { fetch = true; this.lines.push(new Line(this)); } this.lines[x].setVisible(true); } // Remove extra lines from the output for (let x = tall; x < this.lines.length; x++) this.lines[x].setVisible(false); // Configure horizontal scroll bar if (this.metrics) this.horizontal.setIncrement(this.metrics.getBounds().width); // Update the display if (fetch) this.fetch(0, true); else this.refresh(); } ///////////////////////////// Public Methods ////////////////////////////// // Produce disassembly text disassemble(rows) { // Produce a deep copy of the input list let copy = new Array(rows.length); for (let x = 0; x < rows.length; x++) { copy[x] = {}; Object.assign(copy[x], rows[x]); } rows = copy; // Process all rows for (let row of rows) { row.operands = []; // Read instruction bits from the bus let bits0 = row.bytes[1] << 8 | row.bytes[0]; let bits1; if (row.bytes.length == 4) bits1 = row.bytes[3] << 8 | row.bytes[2]; // Working variables let opcode = bits0 >> 10; let opdef = OPDEFS[opcode]; // Sub-opcode mnemonics if (row.opcode == 0b011111) row.mnemonic = BITSTRINGS[bits0 & 31] || "---"; else if (row.opcode == 0b111110) row.mnemonic = FLOATENDOS[bits1 >> 10 & 63] || "---"; else row.mnemonic = opdef.mnemonic; // Processing by format switch (opdef.format) { case 1: this.formatI (row, bits0 ); break; case 3: this.formatIII(row, bits0 ); break; case 4: this.formatIV (row, bits0, bits1); break; case 6: this.formatVI (row, bits0, bits1); break; case 7: this.formatVII(row, bits0 ); break; case 2: this.formatII(row, bits0, opdef.signExtend); break; case 5: this.formatV (row, bits0, bits1, opdef.signExtend); } // Format bytes let text = []; for (let x = 0; x < row.bytes.length; x++) text.push(row.bytes[x].toString(16).padStart(2, "0")); row.bytes = text.join(" "); // Post-processing row.address = row.address.toString(16).padStart(8, "0"); if (this.hexCaps) { row.address = row.address.toUpperCase(); row.bytes = row.bytes .toUpperCase(); } if (!this.mnemonicCaps) row.mnemonic = row.mnemonic.toLowerCase(); if (this.operandReverse) row.operands.reverse(); row.operands = row.operands.join(", "); } return rows; } // Retrieve all output settings in an object getConfig() { let ret = {}; for (let key of SETTINGS) ret[key] = this[key]; return ret; } // Update with disassembly state from the core refresh(data = 0) { let bias; // Scrolling prefresh if (typeof data == "number") bias = 16 + data; // Received data from the core thread else { this.data = data.rows; this.pc = data.pc; if (this.data.length == 0) return; this.address = this.data[0].address; this.rows = this.disassemble(this.data); bias = 16 + (data.scroll === null ? 0 : this.scroll - data.scroll); } // Update elements let count = Math.min(this.tall(true), this.data.length); for (let y = 0; y < count; y++) { let index = bias + y; let line = this.data[index]; let row = this.rows[index]; this.lines[y].refresh(row, line && line.address == this.pc); } // Refesh scroll pane this.update(); } // Bring an address into view seek(address, force) { // Check if the address is already in the view if (!force) { let bias = 16; let tall = this.tall(false); let count = Math.min(tall, this.data.length); // The address is currently visible in the output for (let y = 0; y < count; y++) { let row = this.data[bias + y]; if (!row || Util.u32(address - row.address) >= row.size) continue; // The address is on this row this.refresh(); return; } } // Place the address at a particular position in the view this.address = address; this.fetch(null); } // Update output settings setConfig(config) { // Update settings for (let key of SETTINGS) if (key in config) this[key] = config[key]; // Regenerate output this.refresh({ pc : this.pc, rows : this.data, scroll: null }); } // 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(0); // Unsubscribe from core updates else this.sim.unsubscribe("dasm"); } ///////////////////////////// Private Methods ///////////////////////////// // Select a condition's name cond(cond) { let ret = CONDS[cond]; switch (cond) { case 1: case 9: return CONDS[cond][this.condCL]; case 2: case 10: return CONDS[cond][this.condEZ]; } return CONDS[cond]; } // Retrieve disassembly data from the core async fetch(scroll, prefresh) { let row; // Scrolling relative to the current view if (scroll) { if (prefresh) this.refresh(scroll); this.scroll = Util.s32(this.scroll + scroll); row = -scroll; } // Jumping to an address directly else row = scroll === null ? Math.floor(this.tall(false) / 3) + 16 : 0; // Retrieve data from the core this.refresh( await this.sim.disassemble( this.address, row, this.tall(true) + 32, scroll === null ? null : this.scroll, { subscribe: this.isSubscribed && "dasm" }) ); } // Represent a hexadecimal value hex(value, digits) { let sign = Util.s32(value) < 0 ? "-" : ""; let ret = Math.abs(Util.u32(value)).toString(16).padStart(digits,"0"); if (this.hexCaps) ret = ret.toUpperCase(); if (this.hexSuffix) ret = ("abcdefABCDEF".indexOf(ret[0]) == -1 ? "" : "0") + ret + "h"; else ret = (this.hexDollar ? "$" : "0x") + ret; return sign + ret; } // Prompt the user to specify a new address promptGoto() { // Receive input from the user let address = prompt(this.app.translate("common.gotoPrompt")); if (address == null) return; // Process the input as an address in hexadecimal address = parseInt(address, 16); if (isNaN(address)) return; // Move the selection and refresh the display this.seek(Util.u32(address)); } // Select a program register name proreg(index) { let ret = this.proregNames && PROREGS[index] || "r" + index; return this.proregCaps ? ret.toUpperCase() : ret; } // Measure how many rows of output are visible tall(partial) { let lineHeight = !this.metrics ? 0 : Math.ceil(this.metrics.getBounds().height); return lineHeight <= 0 ? 1 : Math.max(1, Math[partial?"ceil":"floor"]( this.viewport.getBoundingClientRect().height / lineHeight)); } //////////////////////////// Decoding Methods ///////////////////////////// // Disassemble a Format I instruction formatI(row, bits0) { let reg1 = this.proreg(bits0 & 31); // JMP if (row.mnemonic == "JMP") { if (this.jmpBrackets) reg1 = "[" + reg1 + "]"; row.operands.push(reg1); } // Other instructions else { let reg2 = this.proreg(bits0 >> 5 & 31); row.operands.push(reg1, reg2); } } // Disassemble a Format II instruction formatII(row, bits0, signExtend) { // Bit-string instructions are zero-operand if (bits0 >> 10 == 0b011111) return; // Processing by mnemonic switch (row.mnemonic) { // Zero-operand case "---" : // Fallthrough case "CLI" : // Fallthrough case "HALT": // Fallthrough case "RETI": // Fallthrough case "SEI" : return; // Distinct notation case "LDSR": return this.ldstsr(row, bits0, true ); case "SETF": return this.setf (row, bits0 ); case "STSR": return this.ldstsr(row, bits0, false); } // Retrieve immediate operand let imm = bits0 & 31; if (signExtend) imm = Util.signExtend(bits0, 5); // TRAP instruction is one-operand if (row.mnemonic == "TRAP") { row.operands.push(this.trapHex ? this.hex(imm, 1) : imm.toString()); return; } // Processing by mnemonic let hex = this.imm5OtherHex; switch (row.mnemonic) { case "SAR": // Fallthrough case "SHL": // Fallthrough case "SHR": hex = this.imm5ShiftHex; } imm = hex ? this.hex(imm, 1) : imm.toString(); // Two-operand instruction let reg2 = this.proreg(bits0 >> 5 & 31); row.operands.push(imm, reg2); } // Disassemble a Format III instruction formatIII(row, bits0) { let cond = this.cond(bits0 >> 9 & 15); let disp = Util.signExtend(bits0 & 0x1FF, 9); // Condition merged with mnemonic if (this.bcondMerged) { switch (cond) { case "F": row.mnemonic = "NOP"; return; case "T": row.mnemonic = "BR" ; break; default : row.mnemonic = "B" + cond; } } // Condition as operand else { if (!this.condCaps) cond = cond.toLowerCase(); row.operands.push(cond); } // Operand as destination address if (this.branchAddress) { disp = Util.u32(row.address + disp & 0xFFFFFFFE) .toString(16).padStart(8, "0"); if (this.hexCaps) disp = disp.toUpperCase(); row.operands.push(disp); } // Operand as displacement else { let sign = disp < 0 ? "-" : disp > 0 ? "+" : ""; let rel = this.hex(Math.abs(disp), 1); row.operands.push(sign + rel); } } // Disassemble a Format IV instruction formatIV(row, bits0, bits1) { let disp = Util.signExtend(bits0 << 16 | bits1, 26); // Operand as destination address if (this.branchAddress) { disp = Util.u32(row.address + disp & 0xFFFFFFFE) .toString(16).padStart(8, "0"); if (this.hexCaps) disp = disp.toUpperCase(); row.operands.push(disp); } // Operand as displacement else { let sign = disp < 0 ? "-" : disp > 0 ? "+" : ""; let rel = this.hex(Math.abs(disp), 1); row.operands.push(sign + rel); } } // Disassemble a Format V instruction formatV(row, bits0, bits1, signExtend) { let imm = signExtend ? Util.signExtend(bits1) : bits1; let reg1 = this.proreg(bits0 & 31); let reg2 = this.proreg(bits0 >> 5 & 31); if ( row.mnemonic == "ADDI" ? Math.abs(imm) <= 256 ? this.imm16AddiSmallHex : this.imm16AddiLargeHex : row.mnemonic == "MOVEA" || row.mnemonic == "MOVHI" ? this.imm16MoveHex : this.imm16OtherHex ) imm = this.hex(imm, 4); row.operands.push(imm, reg1, reg2); } // Disassemble a Format VI instruction formatVI(row, bits0, bits1) { let disp = Util.signExtend(bits1); let reg1 = this.proreg(bits0 & 31); let reg2 = this.proreg(bits0 >> 5 & 31); let sign = disp < 0 ? "-" : disp == 0 || !this.memoryInside ? "" : "+"; // Displacement is hexadecimal disp = Math.abs(disp); if (disp == 0) disp = "" else if (disp <= 256 ? this.memorySmallHex : this.memoryLargeHex) disp = this.hex(disp, 1); // Format the displacement figure according to its presentation disp = this.memoryInside ? sign == "" ? "" : " " + sign + " " + disp : sign + disp ; // Apply operands row.operands.push(this.memoryInside ? "[" + reg1 + disp + "]" : disp + "[" + reg1 + "]", reg2); // Swap operands for output and store instructions switch (row.mnemonic) { case "OUT.B": case "OUT.H": case "OUT.W": case "ST.B" : case "ST.H" : case "ST.W" : row.operands.reverse(); } } // Disassemble a Format VII instruction formatVII(row, bits0) { let reg1 = this.proreg(bits0 & 31); let reg2 = this.proreg(bits0 >> 5 & 31); // Invalid sub-opcode is zero-operand if (row.mnemonic == "---") return; // Processing by mnemonic switch (row.mnemonic) { case "XB": // Fallthrough case "XH": break; default : row.operands.push(reg1); } row.operands.push(reg2); } // Format an LDSR or STSR instruction ldstsr(row, bits0, reverse) { // System register let sysreg = bits0 & 31; sysreg = this.sysregNames && SYSREGS[sysreg] || sysreg.toString(); if (!this.sysregCaps) sysreg = sysreg.toLowerCase(); // Program register let reg2 = this.proreg(bits0 >> 5 & 31); // Operands row.operands.push(sysreg, reg2); if (reverse) row.operands.reverse(); } // Format a SETF instruction setf(row, bits0) { let cond = this.cond (bits0 & 15); let reg2 = this.proreg(bits0 >> 5 & 31); // Condition merged with mnemonic if (!this.bcondMerged) { row.mnemonic += cond; } // Condition as operand else { if (!this.condCaps) cond = cond.toLowerCase(); row.operands.push(cond); } row.operands.push(reg2); } } export { Disassembler };