Implementing disassembler interface

This commit is contained in:
Guy Perfect 2020-08-09 10:46:29 -05:00
parent 76c95b0523
commit f8f4005e02
6 changed files with 441 additions and 37 deletions

View File

@ -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 */

View File

@ -13,13 +13,11 @@ 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 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();
dasm = new JPanel(null);
dasm.setBackground(SystemColor.window);
dasm.setFocusable(true);
// Configure instance fields
this.parent = parent;
// 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,12 +140,14 @@ class CPUWindow extends ChildWindow {
// Apply configuration settings
void configure() {
panDasm .configure();
lstProgram.configure();
lstSystem .configure();
}
// Update the display
void refresh() {
panDasm .refresh();
lstSystem .refresh();
lstProgram.refresh();
}

View File

@ -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
)
;

View File

@ -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<Row> 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<Row>();
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);
}
}
}

View File

@ -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);

View File

@ -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