From 634ea933f1c96caeb142807817aab53a6d098fa4 Mon Sep 17 00:00:00 2001 From: Seth Morabito Date: Sat, 2 Jan 2016 19:05:38 -0800 Subject: [PATCH] Add disassembled instructions to breakpoints --- .../java/com/loomcom/symon/Breakpoints.java | 85 +++++ src/main/java/com/loomcom/symon/Cpu.java | 359 +++++------------- src/main/java/com/loomcom/symon/CpuState.java | 177 +++++++++ .../java/com/loomcom/symon/Simulator.java | 19 +- .../loomcom/symon/ui/BreakpointsWindow.java | 81 +--- .../com/loomcom/symon/ui/MemoryWindow.java | 10 +- .../com/loomcom/symon/ui/StatusPanel.java | 7 +- .../java/com/loomcom/symon/ui/TraceLog.java | 10 +- .../symon/util/{HexUtil.java => Utils.java} | 27 +- src/test/java/com/loomcom/symon/CpuTest.java | 11 +- .../java/com/loomcom/symon/HexUtilTest.java | 42 -- .../java/com/loomcom/symon/UtilsTest.java | 42 ++ 12 files changed, 460 insertions(+), 410 deletions(-) create mode 100644 src/main/java/com/loomcom/symon/Breakpoints.java create mode 100644 src/main/java/com/loomcom/symon/CpuState.java rename src/main/java/com/loomcom/symon/util/{HexUtil.java => Utils.java} (89%) delete mode 100644 src/test/java/com/loomcom/symon/HexUtilTest.java create mode 100644 src/test/java/com/loomcom/symon/UtilsTest.java diff --git a/src/main/java/com/loomcom/symon/Breakpoints.java b/src/main/java/com/loomcom/symon/Breakpoints.java new file mode 100644 index 0000000..65616f0 --- /dev/null +++ b/src/main/java/com/loomcom/symon/Breakpoints.java @@ -0,0 +1,85 @@ +package com.loomcom.symon; + +import com.loomcom.symon.exceptions.MemoryAccessException; +import com.loomcom.symon.util.Utils; + +import javax.swing.table.AbstractTableModel; +import java.util.ArrayList; +import java.util.TreeSet; + +public class Breakpoints extends AbstractTableModel { + + private TreeSet breakpoints; + private Simulator simulator; + + public Breakpoints(Simulator simulator) { + this.breakpoints = new TreeSet<>(); + this.simulator = simulator; + } + + public boolean contains(int address) { + return this.breakpoints.contains(address); + } + + public void addBreakpoint(int address) { + this.breakpoints .add(address); + fireTableDataChanged(); + } + + public void removeBreakpoint(int address) { + this.breakpoints.remove(address); + fireTableDataChanged(); + } + + public void removeBreakpointAtIndex(int index) { + if (index < 0) { + return; + } + + ArrayList values = new ArrayList<>(breakpoints); + int value = values.get(index); + this.breakpoints.remove(value); + fireTableDataChanged(); + } + + public void refresh() { + fireTableDataChanged(); + } + + @Override + public String getColumnName(int index) { + if (index == 0) { + return "Address"; + } else { + return "Inst"; + } + } + + @Override + public int getRowCount() { + return breakpoints.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + ArrayList values = new ArrayList<>(breakpoints); + + if (columnIndex == 0) { + return "$" + Utils.wordToHex(values.get(rowIndex)); + } else if (columnIndex == 1) { + int address = values.get(rowIndex); + try { + return simulator.disassembleOpAtAddress(address); + } catch (MemoryAccessException ex) { + return "???"; + } + } else { + return null; + } + } +} diff --git a/src/main/java/com/loomcom/symon/Cpu.java b/src/main/java/com/loomcom/symon/Cpu.java index 68f4814..4f76f73 100644 --- a/src/main/java/com/loomcom/symon/Cpu.java +++ b/src/main/java/com/loomcom/symon/Cpu.java @@ -24,7 +24,7 @@ package com.loomcom.symon; import com.loomcom.symon.exceptions.MemoryAccessException; -import com.loomcom.symon.util.HexUtil; +import com.loomcom.symon.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,7 +70,7 @@ public class Cpu implements InstructionTable { private Bus bus; /* The CPU state */ - private static final CpuState state = new CpuState(); + private final CpuState state = new CpuState(); /* start time of op execution, needed for speed simulation */ private long opBeginTime; @@ -114,7 +114,7 @@ public class Cpu implements InstructionTable { state.sp = 0xff; // Set the PC to the address stored in the reset vector - state.pc = address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H)); + state.pc = Utils.address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H)); // Clear instruction register. state.ir = 0; @@ -202,7 +202,7 @@ public class Cpu implements InstructionTable { case 2: // Accumulator - ignored break; case 3: // Absolute - effectiveAddress = address(state.args[0], state.args[1]); + effectiveAddress = Utils.address(state.args[0], state.args[1]); break; case 5: // Zero Page,X / Zero Page,Y if (state.ir == 0x96 || state.ir == 0xb6) { @@ -224,7 +224,7 @@ public class Cpu implements InstructionTable { switch (irAddressMode) { case 0: // (Zero Page,X) tmp = (state.args[0] + state.x) & 0xff; - effectiveAddress = address(bus.read(tmp), bus.read(tmp + 1)); + effectiveAddress = Utils.address(bus.read(tmp), bus.read(tmp + 1)); break; case 1: // Zero Page effectiveAddress = state.args[0]; @@ -233,10 +233,10 @@ public class Cpu implements InstructionTable { effectiveAddress = -1; break; case 3: // Absolute - effectiveAddress = address(state.args[0], state.args[1]); + effectiveAddress = Utils.address(state.args[0], state.args[1]); break; case 4: // (Zero Page),Y - tmp = address(bus.read(state.args[0]), + tmp = Utils.address(bus.read(state.args[0]), bus.read((state.args[0] + 1) & 0xff)); effectiveAddress = (tmp + state.y) & 0xffff; break; @@ -277,7 +277,7 @@ public class Cpu implements InstructionTable { case 0x20: // JSR - Jump to Subroutine - Implied stackPush((state.pc - 1 >> 8) & 0xff); // PC high byte stackPush(state.pc - 1 & 0xff); // PC low byte - state.pc = address(state.args[0], state.args[1]); + state.pc = Utils.address(state.args[0], state.args[1]); break; case 0x28: // PLP - Pull Processor Status - Implied setProcessorStatus(stackPop()); @@ -294,7 +294,7 @@ public class Cpu implements InstructionTable { setProcessorStatus(stackPop()); int lo = stackPop(); int hi = stackPop(); - setProgramCounter(address(lo, hi)); + setProgramCounter(Utils.address(lo, hi)); break; case 0x48: // PHA - Push Accumulator - Implied stackPush(state.a); @@ -310,7 +310,7 @@ public class Cpu implements InstructionTable { case 0x60: // RTS - Return from Subroutine - Implied lo = stackPop(); hi = stackPop(); - setProgramCounter((address(lo, hi) + 1) & 0xffff); + setProgramCounter((Utils.address(lo, hi) + 1) & 0xffff); break; case 0x68: // PLA - Pull Accumulator - Implied state.a = stackPop(); @@ -398,20 +398,20 @@ public class Cpu implements InstructionTable { /** JMP *****************************************************************/ case 0x4c: // JMP - Absolute - state.pc = address(state.args[0], state.args[1]); + state.pc = Utils.address(state.args[0], state.args[1]); break; case 0x6c: // JMP - Indirect - lo = address(state.args[0], state.args[1]); // Address of low byte + lo = Utils.address(state.args[0], state.args[1]); // Address of low byte if (state.args[0] == 0xff && (behavior == CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG || behavior == CpuBehavior.NMOS_WITH_ROR_BUG)) { - hi = address(0x00, state.args[1]); + hi = Utils.address(0x00, state.args[1]); } else { hi = lo + 1; } - state.pc = address(bus.read(lo), bus.read(hi)); + state.pc = Utils.address(bus.read(lo), bus.read(hi)); /* TODO: For accuracy, allow a flag to enable broken behavior of early 6502s: * * "An original 6502 has does not correctly fetch the target @@ -770,7 +770,7 @@ public class Cpu implements InstructionTable { setIrqDisableFlag(); // Load interrupt vector address into PC - state.pc = address(bus.read(vectorLow), bus.read(vectorHigh)); + state.pc = Utils.address(bus.read(vectorLow), bus.read(vectorHigh)); } /** @@ -1198,23 +1198,23 @@ public class Cpu implements InstructionTable { } public String getAccumulatorStatus() { - return "$" + HexUtil.byteToHex(state.a); + return "$" + Utils.byteToHex(state.a); } public String getXRegisterStatus() { - return "$" + HexUtil.byteToHex(state.x); + return "$" + Utils.byteToHex(state.x); } public String getYRegisterStatus() { - return "$" + HexUtil.byteToHex(state.y); + return "$" + Utils.byteToHex(state.y); } public String getProgramCounterStatus() { - return "$" + HexUtil.wordToHex(state.pc); + return "$" + Utils.wordToHex(state.pc); } public String getStackPointerStatus() { - return "$" + HexUtil.byteToHex(state.sp); + return "$" + Utils.byteToHex(state.sp); } public int getProcessorStatus() { @@ -1298,19 +1298,12 @@ public class Cpu implements InstructionTable { } } - /** - * Given two bytes, return an address. - */ - int address(int lowByte, int hiByte) { - return ((hiByte << 8) | lowByte) & 0xffff; - } - /** * Given a hi byte and a low byte, return the Absolute,X * offset address. */ int xAddress(int lowByte, int hiByte) { - return (address(lowByte, hiByte) + state.x) & 0xffff; + return (Utils.address(lowByte, hiByte) + state.x) & 0xffff; } /** @@ -1318,7 +1311,7 @@ public class Cpu implements InstructionTable { * offset address. */ int yAddress(int lowByte, int hiByte) { - return (address(lowByte, hiByte) + state.y) & 0xffff; + return (Utils.address(lowByte, hiByte) + state.y) & 0xffff; } /** @@ -1362,249 +1355,81 @@ public class Cpu implements InstructionTable { } while (opBeginTime + interval >= end); } + /** + * Return a formatted string representing the last instruction and + * operands that were executed. + * + * @return A string representing the mnemonic and operands of the instruction + */ + public static String disassembleOp(int opCode, int[] args) { + String mnemonic = opcodeNames[opCode]; + + if (mnemonic == null) { + return "???"; + } + + StringBuilder sb = new StringBuilder(mnemonic); + + switch (instructionModes[opCode]) { + case ABS: + sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1]))); + break; + case ABX: + sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(",X"); + break; + case ABY: + sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(",Y"); + break; + case IMM: + sb.append(" #$").append(Utils.byteToHex(args[0])); + break; + case IND: + sb.append(" ($").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(")"); + break; + case XIN: + sb.append(" ($").append(Utils.byteToHex(args[0])).append(",X)"); + break; + case INY: + sb.append(" ($").append(Utils.byteToHex(args[0])).append("),Y"); + break; + case REL: + case ZPG: + sb.append(" $").append(Utils.byteToHex(args[0])); + break; + case ZPX: + sb.append(" $").append(Utils.byteToHex(args[0])).append(",X"); + break; + case ZPY: + sb.append(" $").append(Utils.byteToHex(args[0])).append(",Y"); + break; + } + + return sb.toString(); + } /** - * A compact, struct-like representation of CPU state. + * Return a formatted string representing the next instruction and + * operands to be executed. + * + * @return A string representing the mnemonic and operands of the instruction */ - public static class CpuState { - /** - * Accumulator - */ - public int a; - /** - * X index regsiter - */ - public int x; - /** - * Y index register - */ - public int y; - /** - * Stack Pointer - */ - public int sp; - /** - * Program Counter - */ - public int pc; - /** - * Last Loaded Instruction Register - */ - public int ir; - /** - * Peek-Ahead to next IR - */ - public int nextIr; - public int[] args = new int[2]; - public int[] nextArgs = new int[2]; - public int instSize; - public boolean opTrap; - public boolean irqAsserted; - public boolean nmiAsserted; - public int lastPc; + public String disassembleNextOp() { + return Cpu.disassembleOp(state.nextIr, state.nextArgs); + } - /* Status Flag Register bits */ - public boolean carryFlag; - public boolean negativeFlag; - public boolean zeroFlag; - public boolean irqDisableFlag; - public boolean decimalModeFlag; - public boolean breakFlag; - public boolean overflowFlag; - public long stepCounter = 0L; - - /** - * Create an empty CPU State. - */ - public CpuState() {} - - /** - * Snapshot a copy of the CpuState. - * - * (This is a copy constructor rather than an implementation of Cloneable - * based on Josh Bloch's recommendation) - * - * @param s The CpuState to copy. - */ - public CpuState(CpuState s) { - this.a = s.a; - this.x = s.x; - this.y = s.y; - this.sp = s.sp; - this.pc = s.pc; - this.ir = s.ir; - this.nextIr = s.nextIr; - this.lastPc = s.lastPc; - this.args[0] = s.args[0]; - this.args[1] = s.args[1]; - this.nextArgs[0] = s.nextArgs[0]; - this.nextArgs[1] = s.nextArgs[1]; - this.instSize = s.instSize; - this.opTrap = s.opTrap; - this.irqAsserted = s.irqAsserted; - this.carryFlag = s.carryFlag; - this.negativeFlag = s.negativeFlag; - this.zeroFlag = s.zeroFlag; - this.irqDisableFlag = s.irqDisableFlag; - this.decimalModeFlag = s.decimalModeFlag; - this.breakFlag = s.breakFlag; - this.overflowFlag = s.overflowFlag; - this.stepCounter = s.stepCounter; + /** + * @param address Address to disassemble + * @return String containing the disassembled instruction and operands. + */ + public String disassembleOpAtAddress(int address) throws MemoryAccessException { + int opCode = bus.read(address); + int args[] = new int[2]; + int size = Cpu.instructionSizes[opCode]; + for (int i = 1; i < size; i++) { + int nextRead = (address + i) % bus.endAddress(); + args[i-1] = bus.read(nextRead); } - /** - * Returns a string formatted for the trace log. - * - * @return a string formatted for the trace log. - */ - public String toTraceEvent() { - String opcode = disassembleLastOp(); - return getInstructionByteStatus() + " " + - String.format("%-14s", opcode) + - "A:" + HexUtil.byteToHex(a) + " " + - "X:" + HexUtil.byteToHex(x) + " " + - "Y:" + HexUtil.byteToHex(y) + " " + - "F:" + HexUtil.byteToHex(getStatusFlag()) + " " + - "S:1" + HexUtil.byteToHex(sp) + " " + - getProcessorStatusString() + "\n"; - } - - /** - * @return The value of the Process Status Register, as a byte. - */ - public int getStatusFlag() { - int status = 0x20; - if (carryFlag) { - status |= P_CARRY; - } - if (zeroFlag) { - status |= P_ZERO; - } - if (irqDisableFlag) { - status |= P_IRQ_DISABLE; - } - if (decimalModeFlag) { - status |= P_DECIMAL; - } - if (breakFlag) { - status |= P_BREAK; - } - if (overflowFlag) { - status |= P_OVERFLOW; - } - if (negativeFlag) { - status |= P_NEGATIVE; - } - return status; - } - - public String getInstructionByteStatus() { - switch (Cpu.instructionSizes[ir]) { - case 0: - case 1: - return HexUtil.wordToHex(lastPc) + " " + - HexUtil.byteToHex(ir) + " "; - case 2: - return HexUtil.wordToHex(lastPc) + " " + - HexUtil.byteToHex(ir) + " " + - HexUtil.byteToHex(args[0]) + " "; - case 3: - return HexUtil.wordToHex(lastPc) + " " + - HexUtil.byteToHex(ir) + " " + - HexUtil.byteToHex(args[0]) + " " + - HexUtil.byteToHex(args[1]); - default: - return null; - } - } - - /** - * Return a formatted string representing the last instruction and - * operands that were executed. - * - * @return A string representing the mnemonic and operands of the instruction - */ - private String disassembleOp(int ir, int[] args) { - String mnemonic = opcodeNames[ir]; - - if (mnemonic == null) { - return "???"; - } - - StringBuilder sb = new StringBuilder(mnemonic); - - switch (instructionModes[ir]) { - case ABS: - sb.append(" $").append(HexUtil.wordToHex(address(args[0], args[1]))); - break; - case ABX: - sb.append(" $").append(HexUtil.wordToHex(address(args[0], args[1]))).append(",X"); - break; - case ABY: - sb.append(" $").append(HexUtil.wordToHex(address(args[0], args[1]))).append(",Y"); - break; - case IMM: - sb.append(" #$").append(HexUtil.byteToHex(args[0])); - break; - case IND: - sb.append(" ($").append(HexUtil.wordToHex(address(args[0], args[1]))).append(")"); - break; - case XIN: - sb.append(" ($").append(HexUtil.byteToHex(args[0])).append(",X)"); - break; - case INY: - sb.append(" ($").append(HexUtil.byteToHex(args[0])).append("),Y"); - break; - case REL: - case ZPG: - sb.append(" $").append(HexUtil.byteToHex(args[0])); - break; - case ZPX: - sb.append(" $").append(HexUtil.byteToHex(args[0])).append(",X"); - break; - case ZPY: - sb.append(" $").append(HexUtil.byteToHex(args[0])).append(",Y"); - break; - } - - return sb.toString(); - } - - public String disassembleLastOp() { - return disassembleOp(ir, args); - } - - /** - * Return a formatted string representing the next instruction and - * operands to be executed. - * - * @return A string representing the mnemonic and operands of the instruction - */ - public String disassembleNextOp() { - return disassembleOp(nextIr, nextArgs); - } - - /** - * Given two bytes, return an address. - */ - private int address(int lowByte, int hiByte) { - return ((hiByte << 8) | lowByte) & 0xffff; - } - - - /** - * @return A string representing the current status register state. - */ - public String getProcessorStatusString() { - return "[" + (negativeFlag ? 'N' : '.') + - (overflowFlag ? 'V' : '.') + - "-" + - (breakFlag ? 'B' : '.') + - (decimalModeFlag ? 'D' : '.') + - (irqDisableFlag ? 'I' : '.') + - (zeroFlag ? 'Z' : '.') + - (carryFlag ? 'C' : '.') + - "]"; - } + return disassembleOp(opCode, args); } } diff --git a/src/main/java/com/loomcom/symon/CpuState.java b/src/main/java/com/loomcom/symon/CpuState.java new file mode 100644 index 0000000..ae81aca --- /dev/null +++ b/src/main/java/com/loomcom/symon/CpuState.java @@ -0,0 +1,177 @@ +package com.loomcom.symon; + +import com.loomcom.symon.util.Utils; + +/** + * A compact, struct-like representation of CPU state. + */ +public class CpuState { + /** + * Accumulator + */ + public int a; + + /** + * X index regsiter + */ + public int x; + + /** + * Y index register + */ + public int y; + + /** + * Stack Pointer + */ + public int sp; + + /** + * Program Counter + */ + public int pc; + + /** + * Last Loaded Instruction Register + */ + public int ir; + + /** + * Peek-Ahead to next IR + */ + public int nextIr; + public int[] args = new int[2]; + public int[] nextArgs = new int[2]; + public int instSize; + public boolean opTrap; + public boolean irqAsserted; + public boolean nmiAsserted; + public int lastPc; + + /* Status Flag Register bits */ + public boolean carryFlag; + public boolean negativeFlag; + public boolean zeroFlag; + public boolean irqDisableFlag; + public boolean decimalModeFlag; + public boolean breakFlag; + public boolean overflowFlag; + public long stepCounter = 0L; + + public CpuState() {} + + /** + * Snapshot a copy of the CpuState. + * + * (This is a copy constructor rather than an implementation of Cloneable + * based on Josh Bloch's recommendation) + * + * @param s The CpuState to copy. + */ + public CpuState(CpuState s) { + this.a = s.a; + this.x = s.x; + this.y = s.y; + this.sp = s.sp; + this.pc = s.pc; + this.ir = s.ir; + this.nextIr = s.nextIr; + this.lastPc = s.lastPc; + this.args[0] = s.args[0]; + this.args[1] = s.args[1]; + this.nextArgs[0] = s.nextArgs[0]; + this.nextArgs[1] = s.nextArgs[1]; + this.instSize = s.instSize; + this.opTrap = s.opTrap; + this.irqAsserted = s.irqAsserted; + this.carryFlag = s.carryFlag; + this.negativeFlag = s.negativeFlag; + this.zeroFlag = s.zeroFlag; + this.irqDisableFlag = s.irqDisableFlag; + this.decimalModeFlag = s.decimalModeFlag; + this.breakFlag = s.breakFlag; + this.overflowFlag = s.overflowFlag; + this.stepCounter = s.stepCounter; + } + + /** + * Returns a string formatted for the trace log. + * + * @return a string formatted for the trace log. + */ + public String toTraceEvent() { + String opcode = Cpu.disassembleOp(ir, args); + return getInstructionByteStatus() + " " + + String.format("%-14s", opcode) + + "A:" + Utils.byteToHex(a) + " " + + "X:" + Utils.byteToHex(x) + " " + + "Y:" + Utils.byteToHex(y) + " " + + "F:" + Utils.byteToHex(getStatusFlag()) + " " + + "S:1" + Utils.byteToHex(sp) + " " + + getProcessorStatusString() + "\n"; + } + + /** + * @return The value of the Process Status Register, as a byte. + */ + public int getStatusFlag() { + int status = 0x20; + if (carryFlag) { + status |= Cpu.P_CARRY; + } + if (zeroFlag) { + status |= Cpu.P_ZERO; + } + if (irqDisableFlag) { + status |= Cpu.P_IRQ_DISABLE; + } + if (decimalModeFlag) { + status |= Cpu.P_DECIMAL; + } + if (breakFlag) { + status |= Cpu.P_BREAK; + } + if (overflowFlag) { + status |= Cpu.P_OVERFLOW; + } + if (negativeFlag) { + status |= Cpu.P_NEGATIVE; + } + return status; + } + + public String getInstructionByteStatus() { + switch (Cpu.instructionSizes[ir]) { + case 0: + case 1: + return Utils.wordToHex(lastPc) + " " + + Utils.byteToHex(ir) + " "; + case 2: + return Utils.wordToHex(lastPc) + " " + + Utils.byteToHex(ir) + " " + + Utils.byteToHex(args[0]) + " "; + case 3: + return Utils.wordToHex(lastPc) + " " + + Utils.byteToHex(ir) + " " + + Utils.byteToHex(args[0]) + " " + + Utils.byteToHex(args[1]); + default: + return null; + } + } + + /** + * @return A string representing the current status register state. + */ + public String getProcessorStatusString() { + return "[" + (negativeFlag ? 'N' : '.') + + (overflowFlag ? 'V' : '.') + + "-" + + (breakFlag ? 'B' : '.') + + (decimalModeFlag ? 'D' : '.') + + (irqDisableFlag ? 'I' : '.') + + (zeroFlag ? 'Z' : '.') + + (carryFlag ? 'C' : '.') + + "]"; + } +} diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index 9783419..5366577 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -119,7 +119,7 @@ public class Simulator { private JFileChooser fileChooser; private PreferencesDialog preferences; - private SortedSet breakpoints; + private Breakpoints breakpoints; private final Object commandMonitorObject = new Object(); @@ -136,7 +136,7 @@ public class Simulator { private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"}; public Simulator(Class machineClass) throws Exception { - this.breakpoints = new TreeSet<>(); + this.breakpoints = new Breakpoints(this); this.machine = (Machine) machineClass.getConstructors()[0].newInstance(); @@ -443,6 +443,10 @@ public class Simulator { } } + public String disassembleOpAtAddress(int address) throws MemoryAccessException { + return machine.getCpu().disassembleOpAtAddress(address); + } + class LoadProgramAction extends AbstractAction { public LoadProgramAction() { super("Load Program...", null); @@ -473,10 +477,14 @@ public class Simulator { program[i++] = dis.readByte(); } - SwingUtilities.invokeLater(() -> console.reset()); - // Now load the program at the starting address. loadProgram(program, preferences.getProgramStartAddress()); + + SwingUtilities.invokeLater(() -> { + console.reset(); + breakpoints.refresh(); + }); + // TODO: "Don't Show Again" checkbox JOptionPane.showMessageDialog(mainWindow, "Loaded Successfully At " + @@ -524,6 +532,9 @@ public class Simulator { updateVisibleState(); + // Refresh breakpoints to show new memory contents. + breakpoints.refresh(); + logger.info("ROM File `{}' loaded at {}", romFile.getName(), String.format("0x%04X", machine.getRomBase())); // TODO: "Don't Show Again" checkbox diff --git a/src/main/java/com/loomcom/symon/ui/BreakpointsWindow.java b/src/main/java/com/loomcom/symon/ui/BreakpointsWindow.java index e8ed2e5..7bbab76 100644 --- a/src/main/java/com/loomcom/symon/ui/BreakpointsWindow.java +++ b/src/main/java/com/loomcom/symon/ui/BreakpointsWindow.java @@ -23,7 +23,8 @@ package com.loomcom.symon.ui; -import com.loomcom.symon.util.HexUtil; +import com.loomcom.symon.Breakpoints; +import com.loomcom.symon.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,8 +32,6 @@ import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.SortedSet; /** * Simple window to enter breakpoints. @@ -45,45 +44,9 @@ public class BreakpointsWindow extends JFrame { private static final String EMPTY_STRING = ""; private JFrame mainWindow; - private SortedSet breakpoints; + private Breakpoints breakpoints; - /** - * Simple ListModel to back the list of breakpoints. - */ - private class BreakpointsListModel extends AbstractListModel { - private SortedSet breakpoints; - - public BreakpointsListModel(SortedSet breakpoints) { - this.breakpoints = breakpoints; - } - - @Override - public int getSize() { - return breakpoints.size(); - } - - @Override - public String getElementAt(int index) { - ArrayList values = new ArrayList<>(breakpoints); - return "$" + HexUtil.wordToHex(values.get(index)); - } - - public void addElement(Integer breakpoint) { - breakpoints.add(breakpoint); - ArrayList values = new ArrayList<>(breakpoints); - int index = values.indexOf(breakpoint); - fireIntervalAdded(this, index, index); - } - - public void removeElement(int index) { - ArrayList values = new ArrayList<>(breakpoints); - Integer breakpoint = values.get(index); - breakpoints.remove(breakpoint); - fireIntervalRemoved(this, index, index); - } - } - - public BreakpointsWindow(SortedSet breakpoints, + public BreakpointsWindow(Breakpoints breakpoints, JFrame mainWindow) { this.breakpoints = breakpoints; this.mainWindow = mainWindow; @@ -103,30 +66,26 @@ public class BreakpointsWindow extends JFrame { JButton removeButton = new JButton("Del"); removeButton.setEnabled(false); - JTextField addTextField = new JTextField(5); + JTextField addTextField = new JTextField(4); - BreakpointsListModel listModel = new BreakpointsListModel(breakpoints); - JList breakpointsList = new JList<>(listModel); - breakpointsList.setFont(new Font("Monospace", Font.PLAIN, 14)); - breakpointsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + JTable breakpointsTable = new JTable(breakpoints); + breakpointsTable.setShowGrid(true); + breakpointsTable.setGridColor(Color.LIGHT_GRAY); + breakpointsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + breakpointsTable.getSelectionModel().addListSelectionListener(e -> { + if (e.getFirstIndex() > -1) { + removeButton.setEnabled(true); + } else { + removeButton.setEnabled(false); + } + }); - JScrollPane scrollPane = new JScrollPane(breakpointsList); + JScrollPane scrollPane = new JScrollPane(breakpointsTable); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); breakpointsPanel.add(scrollPane, BorderLayout.CENTER); - - breakpointsList.addListSelectionListener(le -> { - int idx = breakpointsList.getSelectedIndex(); - - if (idx == -1) { - removeButton.setEnabled(false); - } else { - removeButton.setEnabled(true); - } - }); - ActionListener addBreakpointListener = e -> { int value = -1; @@ -147,9 +106,9 @@ public class BreakpointsWindow extends JFrame { return; } - listModel.addElement(value); + breakpoints.addBreakpoint(value); - logger.debug("Added breakpoint ${}", HexUtil.wordToHex(value)); + logger.debug("Added breakpoint ${}", Utils.wordToHex(value)); addTextField.setText(EMPTY_STRING); }; @@ -157,7 +116,7 @@ public class BreakpointsWindow extends JFrame { addButton.addActionListener(addBreakpointListener); addTextField.addActionListener(addBreakpointListener); - removeButton.addActionListener(e -> listModel.removeElement(breakpointsList.getSelectedIndex())); + removeButton.addActionListener(e -> breakpoints.removeBreakpointAtIndex(breakpointsTable.getSelectedRow())); controlPanel.add(addTextField); controlPanel.add(addButton); diff --git a/src/main/java/com/loomcom/symon/ui/MemoryWindow.java b/src/main/java/com/loomcom/symon/ui/MemoryWindow.java index db3e6fd..ebb21ce 100644 --- a/src/main/java/com/loomcom/symon/ui/MemoryWindow.java +++ b/src/main/java/com/loomcom/symon/ui/MemoryWindow.java @@ -25,7 +25,7 @@ package com.loomcom.symon.ui; import com.loomcom.symon.Bus; import com.loomcom.symon.exceptions.MemoryAccessException; -import com.loomcom.symon.util.HexUtil; +import com.loomcom.symon.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -107,7 +107,7 @@ public class MemoryWindow extends JFrame implements ActionListener { previousPageButton.setEnabled(pageNumber > 0x00); nextPageButton.setEnabled(pageNumber < 0xff); - pageNumberTextField.setText(HexUtil.byteToHex(pageNumber)); + pageNumberTextField.setText(Utils.byteToHex(pageNumber)); } /** @@ -328,13 +328,13 @@ public class MemoryWindow extends JFrame implements ActionListener { public Object getValueAt(int row, int column) { try { if (column == 0) { - return HexUtil.wordToHex(fullAddress(row, 1)); + return Utils.wordToHex(fullAddress(row, 1)); } else if (column < 9) { // Display hex value of the data - return HexUtil.byteToHex(bus.read(fullAddress(row, column))); + return Utils.byteToHex(bus.read(fullAddress(row, column))); } else { // Display the ASCII equivalent (if printable) - return HexUtil.byteToAscii(bus.read(fullAddress(row, column - 8))); + return Utils.byteToAscii(bus.read(fullAddress(row, column - 8))); } } catch (MemoryAccessException ex) { return "??"; diff --git a/src/main/java/com/loomcom/symon/ui/StatusPanel.java b/src/main/java/com/loomcom/symon/ui/StatusPanel.java index f3fcd78..a3cd793 100644 --- a/src/main/java/com/loomcom/symon/ui/StatusPanel.java +++ b/src/main/java/com/loomcom/symon/ui/StatusPanel.java @@ -24,14 +24,13 @@ package com.loomcom.symon.ui; import com.loomcom.symon.Cpu; +import com.loomcom.symon.CpuState; import com.loomcom.symon.machines.Machine; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.EtchedBorder; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; /** * UI component that displays the current state of the simulated CPU. @@ -277,7 +276,7 @@ public class StatusPanel extends JPanel { */ public void updateState() { Cpu cpu = machine.getCpu(); - Cpu.CpuState cpuState = cpu.getCpuState(); + CpuState cpuState = cpu.getCpuState(); // Update the Processor Status Flag display int status = cpuState.getStatusFlag(); @@ -293,7 +292,7 @@ public class StatusPanel extends JPanel { // Update the register and address displays // We always want to show the NEXT instruction that will be executed - opcodeField.setText(cpu.getCpuState().disassembleNextOp()); + opcodeField.setText(cpu.disassembleNextOp()); pcField.setText(cpu.getProgramCounterStatus()); spField.setText(cpu.getStackPointerStatus()); aField.setText(cpu.getAccumulatorStatus()); diff --git a/src/main/java/com/loomcom/symon/ui/TraceLog.java b/src/main/java/com/loomcom/symon/ui/TraceLog.java index e2ca29e..0601326 100644 --- a/src/main/java/com/loomcom/symon/ui/TraceLog.java +++ b/src/main/java/com/loomcom/symon/ui/TraceLog.java @@ -24,7 +24,7 @@ package com.loomcom.symon.ui; -import com.loomcom.symon.Cpu; +import com.loomcom.symon.CpuState; import com.loomcom.symon.util.FifoRingBuffer; import javax.swing.*; @@ -36,7 +36,7 @@ import java.awt.*; */ public class TraceLog extends JFrame { - private final FifoRingBuffer traceLog; + private final FifoRingBuffer traceLog; private final JTextArea traceLogTextArea; private static final Dimension MIN_SIZE = new Dimension(320, 200); @@ -71,7 +71,7 @@ public class TraceLog extends JFrame { StringBuilder logString = new StringBuilder(); synchronized(traceLog) { - for (Cpu.CpuState state : traceLog) { + for (CpuState state : traceLog) { logString.append(state.toTraceEvent()); } } @@ -99,9 +99,9 @@ public class TraceLog extends JFrame { * * @param state The CPU State to append. */ - public void append(Cpu.CpuState state) { + public void append(CpuState state) { synchronized(traceLog) { - traceLog.push(new Cpu.CpuState(state)); + traceLog.push(new CpuState(state)); } } diff --git a/src/main/java/com/loomcom/symon/util/HexUtil.java b/src/main/java/com/loomcom/symon/util/Utils.java similarity index 89% rename from src/main/java/com/loomcom/symon/util/HexUtil.java rename to src/main/java/com/loomcom/symon/util/Utils.java index fd51057..ff83107 100644 --- a/src/main/java/com/loomcom/symon/util/HexUtil.java +++ b/src/main/java/com/loomcom/symon/util/Utils.java @@ -24,24 +24,9 @@ package com.loomcom.symon.util; /** - * Hex String Utilities. - * - *

- * - * But why? Java, after all, has a number of ways to convert an integer into a hex string, - * so it may look absurd to go to the trouble of writing yet another conversion! The answer is - * performance. - * - *

- * - * The most convenient way to get a formatted hex value from an integer is with the String.format - * method, but this turns out to be extremely inefficient. Formatting a million integers - * with String.format takes something like 1600ms. Formatting the same number of integers - * with HexUtil takes only 160ms. This is on par with Integer.toHexString, - * but also gives the desired padding. - * + * Various Utilities */ -public class HexUtil { +public class Utils { static final String NON_PRINTABLE = "."; @@ -93,6 +78,7 @@ public class HexUtil { /** * Very fast 8-bit int to ASCII conversion. + * * @param val The value of an ASCII character. * @return A string representing the ASCII character. */ @@ -123,4 +109,11 @@ public class HexUtil { public static String wordToHex(int val) { return HEX_CONSTANTS[(val >> 8) & 0xff] + HEX_CONSTANTS[val & 0xff]; } + + /** + * Given two bytes, return an address. + */ + public static int address(int lowByte, int hiByte) { + return ((hiByte << 8) | lowByte) & 0xffff; + } } \ No newline at end of file diff --git a/src/test/java/com/loomcom/symon/CpuTest.java b/src/test/java/com/loomcom/symon/CpuTest.java index 7b59ff5..0f66155 100644 --- a/src/test/java/com/loomcom/symon/CpuTest.java +++ b/src/test/java/com/loomcom/symon/CpuTest.java @@ -1,5 +1,6 @@ package com.loomcom.symon; +import com.loomcom.symon.util.Utils; import junit.framework.*; import com.loomcom.symon.devices.*; @@ -537,11 +538,11 @@ public class CpuTest extends TestCase { public void testAddress() { - assertEquals(0xf1ea, cpu.address(0xea, 0xf1)); - assertEquals(0x00ea, cpu.address(0xea, 0x00)); - assertEquals(0xf100, cpu.address(0x00, 0xf1)); - assertEquals(0x1234, cpu.address(0x34, 0x12)); - assertEquals(0xffff, cpu.address(0xff, 0xff)); + assertEquals(0xf1ea, Utils.address(0xea, 0xf1)); + assertEquals(0x00ea, Utils.address(0xea, 0x00)); + assertEquals(0xf100, Utils.address(0x00, 0xf1)); + assertEquals(0x1234, Utils.address(0x34, 0x12)); + assertEquals(0xffff, Utils.address(0xff, 0xff)); } public void testZpxAddress() { diff --git a/src/test/java/com/loomcom/symon/HexUtilTest.java b/src/test/java/com/loomcom/symon/HexUtilTest.java deleted file mode 100644 index c88cf6b..0000000 --- a/src/test/java/com/loomcom/symon/HexUtilTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.loomcom.symon; - -import com.loomcom.symon.util.HexUtil; -import junit.framework.TestCase; - -public class HexUtilTest extends TestCase { - public void testByteToHex() { - assertEquals("FE", HexUtil.byteToHex(0xfe)); - assertEquals("00", HexUtil.byteToHex(0)); - assertEquals("0A", HexUtil.byteToHex(10)); - } - - public void testByteToHexIgnoresSign() { - assertEquals("FF", HexUtil.byteToHex(-1)); - } - - public void testByteToHexMasksLowByte() { - assertEquals("FE", HexUtil.byteToHex(0xfffe)); - assertEquals("00", HexUtil.byteToHex(0xff00)); - } - - public void testWordToHex() { - assertEquals("0000", HexUtil.wordToHex(0)); - assertEquals("FFFF", HexUtil.wordToHex(65535)); - assertEquals("FFFE", HexUtil.wordToHex(65534)); - } - - public void testWordToHexIgnoresSign() { - assertEquals("FFFF", HexUtil.wordToHex(-1)); - } - - public void testWordToHexMasksTwoLowBytes() { - assertEquals("FFFE", HexUtil.wordToHex(0xfffffe)); - assertEquals("FF00", HexUtil.wordToHex(0xffff00)); - } - - public void testAllBytesAreCorrect() { - for (int i = 0; i <= 0xff; i++) { - assertEquals(String.format("%02X", i), HexUtil.byteToHex(i)); - } - } -} diff --git a/src/test/java/com/loomcom/symon/UtilsTest.java b/src/test/java/com/loomcom/symon/UtilsTest.java new file mode 100644 index 0000000..2507ea3 --- /dev/null +++ b/src/test/java/com/loomcom/symon/UtilsTest.java @@ -0,0 +1,42 @@ +package com.loomcom.symon; + +import com.loomcom.symon.util.Utils; +import junit.framework.TestCase; + +public class UtilsTest extends TestCase { + public void testByteToHex() { + assertEquals("FE", Utils.byteToHex(0xfe)); + assertEquals("00", Utils.byteToHex(0)); + assertEquals("0A", Utils.byteToHex(10)); + } + + public void testByteToHexIgnoresSign() { + assertEquals("FF", Utils.byteToHex(-1)); + } + + public void testByteToHexMasksLowByte() { + assertEquals("FE", Utils.byteToHex(0xfffe)); + assertEquals("00", Utils.byteToHex(0xff00)); + } + + public void testWordToHex() { + assertEquals("0000", Utils.wordToHex(0)); + assertEquals("FFFF", Utils.wordToHex(65535)); + assertEquals("FFFE", Utils.wordToHex(65534)); + } + + public void testWordToHexIgnoresSign() { + assertEquals("FFFF", Utils.wordToHex(-1)); + } + + public void testWordToHexMasksTwoLowBytes() { + assertEquals("FFFE", Utils.wordToHex(0xfffffe)); + assertEquals("FF00", Utils.wordToHex(0xffff00)); + } + + public void testAllBytesAreCorrect() { + for (int i = 0; i <= 0xff; i++) { + assertEquals(String.format("%02X", i), Utils.byteToHex(i)); + } + } +}