diff --git a/src/core/cpu.c b/src/core/cpu.c index f76cc42..04fb393 100644 --- a/src/core/cpu.c +++ b/src/core/cpu.c @@ -27,13 +27,13 @@ static const int8_t LOOKUP_OPCODE[] = { VUE_SHL_REG, 1, VUE_SHR_REG, 1, VUE_JMP , 1, VUE_SAR_REG, 1, VUE_MUL , 1, VUE_DIV , 1, VUE_MULU , 1, VUE_DIVU , 1, VUE_OR , 1, VUE_AND , 1, VUE_XOR , 1, VUE_NOT , 1, - -VUE_MOV_IMM, 2,-VUE_ADD_IMM, 2, VUE_SETF , 2,-VUE_CMP_IMM, 2, + VUE_MOV_IMM,-2, VUE_ADD_IMM,-2, VUE_SETF , 2, VUE_CMP_IMM,-2, VUE_SHL_IMM, 2, VUE_SHR_IMM, 2, VUE_CLI , 2, VUE_SAR_IMM, 2, VUE_TRAP , 2, VUE_RETI , 2, VUE_HALT , 2, VUE_ILLEGAL, 0, VUE_LDSR , 2, VUE_STSR , 2, VUE_SEI , 2, BITSTRING , 2, VUE_BCOND , 3, VUE_BCOND , 3, VUE_BCOND , 3, VUE_BCOND , 3, VUE_BCOND , 3, VUE_BCOND , 3, VUE_BCOND , 3, VUE_BCOND , 3, - -VUE_MOVEA , 5,-VUE_ADDI , 5, VUE_JR , 4, VUE_JAL , 4, + VUE_MOVEA ,-5, VUE_ADDI ,-5, VUE_JR , 4, VUE_JAL , 4, VUE_ORI , 5, VUE_ANDI , 5, VUE_XORI , 5, VUE_MOVHI , 5, VUE_LD_B , 6, VUE_LD_H , 6, VUE_ILLEGAL, 0, VUE_LD_W , 6, VUE_ST_B , 6, VUE_ST_H , 6, VUE_ILLEGAL, 0, VUE_ST_W , 6, @@ -71,10 +71,10 @@ static void cpuDecode(VUE_INST *inst, int32_t bits) { /* Configure instance fields */ inst->bits = bits; inst->opcode = bits >> 26 & 63; - x = inst->opcode << 1 | 1; - extend = LOOKUP_OPCODE[x]; - inst->format = LOOKUP_OPCODE[x + 1]; - inst->id = extend < 0 ? -extend : extend; + x = inst->opcode << 1; + inst->id = LOOKUP_OPCODE[x ]; + extend = LOOKUP_OPCODE[x + 1]; + inst->format = extend < 0 ? -extend : extend; inst->size = inst->format < 4 ? 2 : 4; /* Decode by format */ diff --git a/src/desktop/app/CPUWindow.java b/src/desktop/app/CPUWindow.java index 7d283b1..050c9a6 100644 --- a/src/desktop/app/CPUWindow.java +++ b/src/desktop/app/CPUWindow.java @@ -13,15 +13,13 @@ import vue.*; // CPU window class CPUWindow extends ChildWindow { - // Instance fields - private int expandWidth; // Width of expand buttons - private boolean shown; // Window has been shown - private int systemHeight; // Initial height of system register list + // Package fields + MainWindow parent; // Containing window // UI components - private JPanel dasm; // Disassembler - private RegisterList lstProgram; // Program register list - private RegisterList lstSystem; // System register list + private DisassemblerPane panDasm; // Disassembler client + private RegisterList lstProgram; // Program register list + private RegisterList lstSystem; // System register list @@ -29,6 +27,8 @@ class CPUWindow extends ChildWindow { // Global Settings // /////////////////////////////////////////////////////////////////////////// + static Font dasmFont; // Disassembler font + static int dasmFontHeight; // Disassembler font line height static int regExpandWidth; // Width of register list expand button static Font regHexFont; // Register list hex font static Dimension regHexFontSize; // Max dimensions @@ -64,7 +64,19 @@ class CPUWindow extends ChildWindow { regExpandWidth = label.getPreferredSize().width; label.setText("-"); regExpandWidth = Math.max(regExpandWidth, - label.getPreferredSize().width); + label.getPreferredSize().width) + 4; + + // Disassembler font + setDasmFont(new Font(Util.fontFamily(new String[] + { "Consolas", Font.MONOSPACED } ), Font.PLAIN, 14)); + } + + // Specify a font to use as the disassembler font + static void setDasmFont(Font font) { + dasmFont = font; + var label = new JLabel("!"); + label.setFont(font); + dasmFontHeight = label.getPreferredSize().height; } // Specify a font to use as the register list hex font @@ -84,25 +96,25 @@ class CPUWindow extends ChildWindow { super(parent, "cpu.title"); instances.add(this); - var client = getContentPane(); + // Configure instance fields + this.parent = parent; - dasm = new JPanel(null); - dasm.setBackground(SystemColor.window); - dasm.setFocusable(true); - - lstSystem = new RegisterList(this, true); + // Configure child panes + panDasm = new DisassemblerPane(this); + lstSystem = new RegisterList(this, true); lstProgram = new RegisterList(this, false); + + // Configure layout var inner = Util.splitPane(JSplitPane.VERTICAL_SPLIT); inner.setTopComponent(lstSystem); inner.setBottomComponent(lstProgram); - var outer = Util.splitPane(JSplitPane.HORIZONTAL_SPLIT); - outer.setLeftComponent(new JScrollPane(dasm, - JScrollPane.VERTICAL_SCROLLBAR_NEVER, - JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); + outer.setLeftComponent(panDasm); outer.setRightComponent(inner); outer.setResizeWeight(1); + // Configure component + var client = getContentPane(); client.add(outer); client.setPreferredSize(new Dimension(480, 300)); configure(); @@ -128,13 +140,15 @@ class CPUWindow extends ChildWindow { // Apply configuration settings void configure() { + panDasm .configure(); lstProgram.configure(); lstSystem .configure(); } // Update the display void refresh() { - lstSystem.refresh(); + panDasm .refresh(); + lstSystem .refresh(); lstProgram.refresh(); } diff --git a/src/desktop/app/Disassembler.java b/src/desktop/app/Disassembler.java index 1bb60cb..c4219f7 100644 --- a/src/desktop/app/Disassembler.java +++ b/src/desktop/app/Disassembler.java @@ -172,7 +172,7 @@ class Disassembler { row.mnemonic = "SETF" + CONDITIONS[inst.imm]; // All other mnemonics - else row.mnemonic = MNEMONICS[inst.id]; + else row.mnemonic = MNEMONICS[inst.id + 1]; // Adjust mnemonic case if (!mnemonicCaps) @@ -200,7 +200,7 @@ class Disassembler { // Format the destination of a branch or jump private static String destination(Instruction inst, int address) { - return !dispDest ? toHex(inst.disp, 1, immNumber) : + return !dispDest ? toHex(inst.disp, 1, false) : String.format("%08" + (hexCaps ? "X" : "x"), address + inst.disp); } @@ -272,6 +272,13 @@ class Disassembler { break; } + // LDSR + if (inst.id == VUE.LDSR) { + String temp = imm; + imm = reg2; + reg2 = temp; + } + // Generic return String.format("%s, %s", destLast ? imm : reg2, @@ -328,10 +335,10 @@ class Disassembler { "[%s %s %s]", src, inst.disp < 0 ? "-" : "+", - toHex(Math.abs(inst.disp), 1, immNumber) + toHex(Math.abs(inst.disp), 1, false) ) : String.format( "%s[%s]", - toHex(inst.disp, 1, immNumber), + toHex(inst.disp, 1, false), src ) ; diff --git a/src/desktop/app/DisassemblerPane.java b/src/desktop/app/DisassemblerPane.java new file mode 100644 index 0000000..4669bf1 --- /dev/null +++ b/src/desktop/app/DisassemblerPane.java @@ -0,0 +1,373 @@ +package app; + +// Java imports +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; + +// Project imports +import util.*; +import vue.*; + +// Disassembler UI +class DisassemblerPane extends JScrollPane { + + // Instance fields + private int address; // Address of top row of output + private CPUWindow parent; // Containing CPU window + private boolean showBytes; // Display the bytes column + private boolean shown; // Component has been shown + private int[] widths; // Column widths + + // UI components + private JPanel client; // Client area + private ArrayList rows; // Disassembler output + + + + /////////////////////////////////////////////////////////////////////////// + // Classes // + /////////////////////////////////////////////////////////////////////////// + + // One row of disassembler output + private class Row extends Disassembler.Row { + Instruction inst; // Decoded instruction + JLabel[] labels; // Display text + } + + + + /////////////////////////////////////////////////////////////////////////// + // Constructors // + /////////////////////////////////////////////////////////////////////////// + + // Default constructor + DisassemblerPane(CPUWindow parent) { + super(VERTICAL_SCROLLBAR_NEVER, HORIZONTAL_SCROLLBAR_AS_NEEDED); + + // Configure instance fields + this.parent = parent; + rows = new ArrayList(); + showBytes = true; + widths = new int[5]; + + // Configure client area + client = new JPanel(null) { + public void paintComponent(Graphics g) { + super.paintComponent(g); + onPaint((Graphics2D) g, getWidth(), getHeight()); + } + }; + client.addFocusListener( + Util.onFocus(e->client.repaint(), e->client.repaint())); + client.addKeyListener(Util.onKey(e->onKeyDown(e), null)); + client.addMouseListener(Util.onMouse(e->client.requestFocus(), null)); + client.setBackground(SystemColor.window); + client.setFocusable(true); + + // Configure component + setViewportView(client); + configure(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Package Methods // + /////////////////////////////////////////////////////////////////////////// + + // Apply configuration settings + void configure() { + client.repaint(); + } + + // Update the display + void refresh() { + client.repaint(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Event Handlers // + /////////////////////////////////////////////////////////////////////////// + + // Key down + private void onKeyDown(KeyEvent e) { + int code = e.getKeyCode(); + int count = tall(false); + int mods = e.getModifiersEx(); + boolean alt = (mods & InputEvent.ALT_DOWN_MASK ) != 0; + boolean ctrl = (mods & InputEvent.CTRL_DOWN_MASK) != 0; + + // No Alt combinations + if (alt) return; + + // Goto + if (ctrl && code == KeyEvent.VK_G) { + String addr = JOptionPane.showInputDialog( + this, "Goto:", "Goto", JOptionPane.PLAIN_MESSAGE); + if (addr != null && addr.trim().length() != 0) { + try { seek((int) Long.parseLong(addr, 16), count / 3); } + catch (Exception x) { } + } + return; + } + + // Seek + switch (code) { + case KeyEvent.VK_UP : seek(address, 1); break; + case KeyEvent.VK_DOWN : seek(address, -1); break; + case KeyEvent.VK_PAGE_UP : seek(address, count); break; + case KeyEvent.VK_PAGE_DOWN: seek(address, -count); break; + } + + } + + // Client paint + private void onPaint(Graphics2D g, int width, int height) { + var vue = parent.parent.vue; + int pc = vue.getRegister(VUE.PC, true); + + // The view is being shown for the first time + if (!shown) { + shown = true; + seek(pc, tall(false) / 3); + return; + } + + // Configure working variables + int address = this.address; + int count = tall(true); + var data = new byte[count * 4]; + int offset = 0; + + // Disassemble from the current address + vue.read(address, data, 0, data.length); + widths[3] = widths[4] = 0; + for (int y = 0; y < count; y++) { + var row = y < rows.size() ? rows.get(y) : addRow(); + var color = SystemColor.windowText; + + // Disassemble the instruction + row.inst.decode(data, offset); + Disassembler.disassemble(address, row.inst, row); + updateRow(row); + + // Highlight PC + if (address == pc) { + Color bg = SystemColor.control; + if (client.isFocusOwner()) { + bg = SystemColor.textHighlight; + color = SystemColor.textHighlightText; + } + g.setColor(bg); + g.fillRect( + 0, y * CPUWindow.dasmFontHeight, + width, CPUWindow.dasmFontHeight + ); + } + + // Configure label text color + for (var label : row.labels) + label.setForeground(color); + + // Advance to the next instruction + int size = address + 2 == pc ? 2 : row.inst.size; + address += size; + offset += size; + } + + // Configure all rows + for (int y = 0; y < rows.size(); y++) { + var row = rows.get(y); + int top = y * CPUWindow.dasmFontHeight; + + // Configure all labels + for (int z = 0, x = 0; z < 5; z++) { + var label = row.labels[z]; + + // The label is not part of the output + if (y >= count || widths[z] == 0) { + label.setVisible(false); + continue; + } + + // Configure the label + label.setLocation(x, top); + label.setSize(label.getPreferredSize()); + x += widths[z] + CPUWindow.dasmFontHeight; + } + + } + + // Update the client's size + var size = client.getPreferredSize(); + width = -CPUWindow.dasmFontHeight + 1; + for (int x = 0; x < 5; x++) + if (widths[x] != 0 && (x != 2 || showBytes)) + width += CPUWindow.dasmFontHeight + widths[x]; + if (width == size.width) + return; + client.setPreferredSize(new Dimension(width, 0)); + revalidate(); + } + + + + /////////////////////////////////////////////////////////////////////////// + // Private Methods // + /////////////////////////////////////////////////////////////////////////// + + // Create a new row object + private Row addRow() { + var row = new Row(); + row.inst = new Instruction(); + row.labels = new JLabel[5]; + + // Initialize columns + for (int x = 0; x < 5; x++) { + var label = row.labels[x] = new JLabel(); + if (x != 4) + label.setFont(CPUWindow.dasmFont); + client.add(label); + } + + rows.add(row); + return row; + } + + // Determine whether the instruction at an address is in the client view + private boolean isVisible(int target) { + int count = Math.max(1, tall(false)); + var data = new byte[count * 4]; + int offset = 0; + var vue = parent.parent.vue; + int pc = vue.getRegister(VUE.PC, true); + target &= 0xFFFFFFFE; + + // Load enough bytes to represent every fully visible instruction + vue.read(address, data, 0, data.length); + + // Iterate through instructions + for (int x = 0, address = this.address; x < count; x++) { + + // Determine the instruction's size + int size = address + 2 == pc ? 2 : + Instruction.size(data[offset + 1] >> 2 & 0x3F); + + // The current instruction is the target + if (address == target || size == 4 && address + 2 == target) + return true; + + // Advance to the next instruction + address += size; + offset += size; + } + + // The instruction is not visible in the view + return false; + } + + // Determine the address of the top row of output + private void seek(int target, int row) { + var data = new byte[(Math.abs(row) + 9) * 4]; + int offset = 0; + var vue = parent.parent.vue; + int pc = vue.getRegister(VUE.PC, true); + target &= 0xFFFFFFFE; + + // Scrolling down + if (row <= 0) { + + // Load bytes starting up to 8 instructions prior to the target + address = target - 32; + vue.read(address, data, 0, data.length); + + // Iterate through instructions + for (boolean found = false;;) { + + // Determine the instruction's size + int size = address + 2 == pc ? 2 : + Instruction.size(data[offset + 1] >> 2 & 0x3F); + + // The current instruction is the target + if (address == target || size == 4 && address + 2 == target) + found = true; + if (found && row++ == 0) + break; + + // Advance to the next instruction + address += size; + offset += size; + } + + } + + // Scrolling up + else { + + // Load bytes starting up to 8 instructions prior to the top row + address = target - data.length + 4; + vue.read(address, data, 0, data.length); + + // Iterate through instructions + var addresses = new int[row]; // Circular buffer + for (int index = 0;;) { + + // Determine the instruction's size + int size = pc == address + 2 ? 2 : + Instruction.size(data[offset + 1] >> 2 & 63); + + // The current instruction is the target + if (address == target || size == 4 && address + 2 == target) { + address = addresses[index]; + break; + } + + // Advance to the next instruction + addresses[index] = address; + if (++index == row) + index = 0; + address += size; + offset += size; + } + + } + + // Common processing + client.repaint(); + } + + // Determine how many rows of output are visible + private int tall(boolean partial) { + return Math.max(1, (client.getHeight() + + (partial ? CPUWindow.dasmFontHeight - 1 : 0) + ) / CPUWindow.dasmFontHeight); + } + + // Update a row with its text and measure the column widths + private void updateRow(Row row) { + + for (int x = 0; x < 5; x++) { + var label = row.labels[x]; + + String text = null; + switch (x) { + case 0: text = row.address ; break; + case 1: text = row.bytes ; break; + case 2: text = row.mnemonic; break; + case 3: text = row.operands; break; + } + + if (text != null) { + label.setText(text); + label.setVisible(x == 1 ? showBytes : true); + widths[x] = Math.max(widths[x],label.getPreferredSize().width); + } else label.setVisible(false); + } + + } + +} diff --git a/src/desktop/app/RegisterList.java b/src/desktop/app/RegisterList.java index 7b1da29..d599b90 100644 --- a/src/desktop/app/RegisterList.java +++ b/src/desktop/app/RegisterList.java @@ -6,6 +6,7 @@ import java.util.*; import javax.swing.*; // Project imports +import util.*; import vue.*; // List of CPU registers @@ -48,6 +49,7 @@ class RegisterList extends JScrollPane { super.paintComponent(g); } }; + client.addMouseListener(Util.onMouse(e->client.requestFocus(), null)); client.setBackground(SystemColor.window); client.setFocusable(true); diff --git a/src/desktop/vue/Instruction.java b/src/desktop/vue/Instruction.java index e53a4bb..8672a90 100644 --- a/src/desktop/vue/Instruction.java +++ b/src/desktop/vue/Instruction.java @@ -32,13 +32,13 @@ public class Instruction { VUE.SHL_REG, 1, VUE.SHR_REG, 1, VUE.JMP , 1, VUE.SAR_REG, 1, VUE.MUL , 1, VUE.DIV , 1, VUE.MULU , 1, VUE.DIVU , 1, VUE.OR , 1, VUE.AND , 1, VUE.XOR , 1, VUE.NOT , 1, - -VUE.MOV_IMM, 2,-VUE.ADD_IMM, 2, VUE.SETF , 2,-VUE.CMP_IMM, 2, + VUE.MOV_IMM,-2, VUE.ADD_IMM,-2, VUE.SETF , 2, VUE.CMP_IMM,-2, VUE.SHL_IMM, 2, VUE.SHR_IMM, 2, VUE.CLI , 2, VUE.SAR_IMM, 2, VUE.TRAP , 2, VUE.RETI , 2, VUE.HALT , 2, VUE.ILLEGAL, 0, VUE.LDSR , 2, VUE.STSR , 2, VUE.SEI , 2, BITSTRING , 2, VUE.BCOND , 3, VUE.BCOND , 3, VUE.BCOND , 3, VUE.BCOND , 3, VUE.BCOND , 3, VUE.BCOND , 3, VUE.BCOND , 3, VUE.BCOND , 3, - -VUE.MOVEA , 5,-VUE.ADDI , 5, VUE.JR , 4, VUE.JAL , 4, + VUE.MOVEA ,-5, VUE.ADDI ,-5, VUE.JR , 4, VUE.JAL , 4, VUE.ORI , 5, VUE.ANDI , 5, VUE.XORI , 5, VUE.MOVHI , 5, VUE.LD_B , 6, VUE.LD_H , 6, VUE.ILLEGAL, 0, VUE.LD_W , 6, VUE.ST_B , 6, VUE.ST_H , 6, VUE.ILLEGAL, 0, VUE.ST_W , 6, @@ -70,7 +70,7 @@ public class Instruction { // Determine the size of an instruction given its opcode public static int size(int opcode) { - return LOOKUP_OPCODE[opcode << 1 | 1] < 4 ? 2 : 4; + return Math.abs(LOOKUP_OPCODE[opcode << 1 | 1]) < 4 ? 2 : 4; } @@ -80,7 +80,7 @@ public class Instruction { /////////////////////////////////////////////////////////////////////////// // Default constructor - Instruction() { } + public Instruction() { } @@ -88,6 +88,14 @@ public class Instruction { // Public Methods // /////////////////////////////////////////////////////////////////////////// + // Decode an instruction from a byte array + public void decode(byte[] data, int offset) { + bits = (data[offset + 1] & 0xFF) << 24 | (data[offset] & 0xFF) << 16; + if (size(bits >>> 26) == 4) + bits |= (data[offset + 3] & 0xFF) << 8 | data[offset + 2] & 0xFF; + decode(bits); + } + // Decode an instruction from its binary encoding public void decode(int bits) { byte extend; // Sign-extend the immediate operand @@ -96,10 +104,10 @@ public class Instruction { // Configure instance fields this.bits = bits; opcode = bits >> 26 & 63; - x = opcode << 1 | 1; - extend = LOOKUP_OPCODE[x]; - format = LOOKUP_OPCODE[x + 1]; - id = extend < 0 ? -extend : extend; + x = opcode << 1; + id = LOOKUP_OPCODE[x ]; + extend = LOOKUP_OPCODE[x + 1]; + format = extend < 0 ? -extend : extend; size = format < 4 ? 2 : 4; // Decode by format