10 Commits
Author SHA1 Message Date
Seth Morabito f3a5dd93ad Release 1.2.0 2016-01-03 11:01:20 -08:00
Seth Morabito 4044458baa Fix typo 2016-01-03 10:59:11 -08:00
Seth Morabito 9ab94b3050 Prep for 1.2 2016-01-03 10:56:16 -08:00
Seth Morabito 9a504adc58 Added roadmap to README 2016-01-03 10:49:41 -08:00
Seth Morabito 57cbff42e7 Merge branch '1.1.branch' 2016-01-02 19:06:23 -08:00
Seth Morabito 634ea933f1 Add disassembled instructions to breakpoints 2016-01-02 19:05:38 -08:00
Seth Morabito 0ca30291cb Merge branch '1.1.branch' 2016-01-02 09:51:20 -08:00
Seth Morabito 95f85b71b1 Enter key can add breakpoints 2016-01-02 09:50:38 -08:00
Seth Morabito 7a9f9bfd55 Update images for 1.1.0 2016-01-01 09:49:39 -08:00
Seth Morabito 1e04a52d1b Prep for 1.2 2016-01-01 09:44:43 -08:00
18 changed files with 478 additions and 417 deletions

No files matched your search

+17 -6
View File
@@ -1,9 +1,9 @@
SYMON - A 6502 System Simulator SYMON - A 6502 System Simulator
=============================== ===============================
**Version:** 1.1.1 **Version:** 1.2.0
**Last Updated:** 2 January, 2016 **Last Updated:** 3 January, 2016
See the file COPYING for license. See the file COPYING for license.
@@ -188,7 +188,7 @@ Maven will build Symon, run unit tests, and produce a jar file in the
Symon is meant to be invoked directly from the jar file. To run with Symon is meant to be invoked directly from the jar file. To run with
Java 1.8 or greater, just type: Java 1.8 or greater, just type:
$ java -jar symon-1.1.1.jar $ java -jar symon-1.2.0.jar
When Symon is running, you should be presented with a simple graphical When Symon is running, you should be presented with a simple graphical
interface. interface.
@@ -235,6 +235,9 @@ running.
## 5.0 Revision History ## 5.0 Revision History
- **1.2.0:** 3 January, 2016 - Add symbolic disassembly to breakpoints
window.
- **1.1.1:** 2 January, 2016 - Minor enhancement: Allows breakpoints - **1.1.1:** 2 January, 2016 - Minor enhancement: Allows breakpoints
to be added with the Enter key. to be added with the Enter key.
@@ -286,7 +289,15 @@ running.
- **0.1:** 20 January, 2010 - **0.1:** 20 January, 2010
## 6.0 To Do ## 6.0 Roadmap
- **1.2:** Better breakpoint support. Symbolic debugging of breakpoints.
- **2.0:** Complete rewrite of the UI in JavaFX instead of Swing. Complete
assembler and disassembler built in. Ability to attach source code for
symbolic debugging.
## 7.0 To Do
- Feedback (in the form of dialogs, status bar, etc). - Feedback (in the form of dialogs, status bar, etc).
@@ -310,7 +321,7 @@ running.
- Symbolic debugging. - Symbolic debugging.
## 7.0 Copyright and Acknowledgements ## 8.0 Copyright and Acknowledgements
**Copyright (c) 2014 Seth J. Morabito <web@loomcom.com>** **Copyright (c) 2014 Seth J. Morabito <web@loomcom.com>**
@@ -330,7 +341,7 @@ This project would not have been possible without the following resources:
- [Neil Parker's "The 6502/65C02/65C816 Instruction Set Decoded"] (http://www.llx.com/~nparker/a2/opcodes.html), - [Neil Parker's "The 6502/65C02/65C816 Instruction Set Decoded"] (http://www.llx.com/~nparker/a2/opcodes.html),
for information about how instructions are coded for information about how instructions are coded
## 8.0 Licensing ## 9.0 Licensing
Symon is free software. It is distributed under the MIT License. Symon is free software. It is distributed under the MIT License.
Please see the file 'COPYING' for full details of the license. Please see the file 'COPYING' for full details of the license.
+1 -1
View File
@@ -4,7 +4,7 @@
<groupId>com.loomcom.symon</groupId> <groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId> <artifactId>symon</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<version>1.1.1</version> <version>1.2.0</version>
<name>symon</name> <name>symon</name>
<url>http://www.loomcom.com/symon</url> <url>http://www.loomcom.com/symon</url>
<properties> <properties>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 35 KiB

@@ -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<Integer> 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<Integer> 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<Integer> 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;
}
}
}
+92 -267
View File
@@ -24,7 +24,7 @@
package com.loomcom.symon; package com.loomcom.symon;
import com.loomcom.symon.exceptions.MemoryAccessException; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -70,7 +70,7 @@ public class Cpu implements InstructionTable {
private Bus bus; private Bus bus;
/* The CPU state */ /* 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 */ /* start time of op execution, needed for speed simulation */
private long opBeginTime; private long opBeginTime;
@@ -114,7 +114,7 @@ public class Cpu implements InstructionTable {
state.sp = 0xff; state.sp = 0xff;
// Set the PC to the address stored in the reset vector // 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. // Clear instruction register.
state.ir = 0; state.ir = 0;
@@ -202,7 +202,7 @@ public class Cpu implements InstructionTable {
case 2: // Accumulator - ignored case 2: // Accumulator - ignored
break; break;
case 3: // Absolute case 3: // Absolute
effectiveAddress = address(state.args[0], state.args[1]); effectiveAddress = Utils.address(state.args[0], state.args[1]);
break; break;
case 5: // Zero Page,X / Zero Page,Y case 5: // Zero Page,X / Zero Page,Y
if (state.ir == 0x96 || state.ir == 0xb6) { if (state.ir == 0x96 || state.ir == 0xb6) {
@@ -224,7 +224,7 @@ public class Cpu implements InstructionTable {
switch (irAddressMode) { switch (irAddressMode) {
case 0: // (Zero Page,X) case 0: // (Zero Page,X)
tmp = (state.args[0] + state.x) & 0xff; 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; break;
case 1: // Zero Page case 1: // Zero Page
effectiveAddress = state.args[0]; effectiveAddress = state.args[0];
@@ -233,10 +233,10 @@ public class Cpu implements InstructionTable {
effectiveAddress = -1; effectiveAddress = -1;
break; break;
case 3: // Absolute case 3: // Absolute
effectiveAddress = address(state.args[0], state.args[1]); effectiveAddress = Utils.address(state.args[0], state.args[1]);
break; break;
case 4: // (Zero Page),Y 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)); bus.read((state.args[0] + 1) & 0xff));
effectiveAddress = (tmp + state.y) & 0xffff; effectiveAddress = (tmp + state.y) & 0xffff;
break; break;
@@ -277,7 +277,7 @@ public class Cpu implements InstructionTable {
case 0x20: // JSR - Jump to Subroutine - Implied case 0x20: // JSR - Jump to Subroutine - Implied
stackPush((state.pc - 1 >> 8) & 0xff); // PC high byte stackPush((state.pc - 1 >> 8) & 0xff); // PC high byte
stackPush(state.pc - 1 & 0xff); // PC low 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; break;
case 0x28: // PLP - Pull Processor Status - Implied case 0x28: // PLP - Pull Processor Status - Implied
setProcessorStatus(stackPop()); setProcessorStatus(stackPop());
@@ -294,7 +294,7 @@ public class Cpu implements InstructionTable {
setProcessorStatus(stackPop()); setProcessorStatus(stackPop());
int lo = stackPop(); int lo = stackPop();
int hi = stackPop(); int hi = stackPop();
setProgramCounter(address(lo, hi)); setProgramCounter(Utils.address(lo, hi));
break; break;
case 0x48: // PHA - Push Accumulator - Implied case 0x48: // PHA - Push Accumulator - Implied
stackPush(state.a); stackPush(state.a);
@@ -310,7 +310,7 @@ public class Cpu implements InstructionTable {
case 0x60: // RTS - Return from Subroutine - Implied case 0x60: // RTS - Return from Subroutine - Implied
lo = stackPop(); lo = stackPop();
hi = stackPop(); hi = stackPop();
setProgramCounter((address(lo, hi) + 1) & 0xffff); setProgramCounter((Utils.address(lo, hi) + 1) & 0xffff);
break; break;
case 0x68: // PLA - Pull Accumulator - Implied case 0x68: // PLA - Pull Accumulator - Implied
state.a = stackPop(); state.a = stackPop();
@@ -398,20 +398,20 @@ public class Cpu implements InstructionTable {
/** JMP *****************************************************************/ /** JMP *****************************************************************/
case 0x4c: // JMP - Absolute case 0x4c: // JMP - Absolute
state.pc = address(state.args[0], state.args[1]); state.pc = Utils.address(state.args[0], state.args[1]);
break; break;
case 0x6c: // JMP - Indirect 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 && if (state.args[0] == 0xff &&
(behavior == CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG || (behavior == CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG)) { behavior == CpuBehavior.NMOS_WITH_ROR_BUG)) {
hi = address(0x00, state.args[1]); hi = Utils.address(0x00, state.args[1]);
} else { } else {
hi = lo + 1; 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: /* TODO: For accuracy, allow a flag to enable broken behavior of early 6502s:
* *
* "An original 6502 has does not correctly fetch the target * "An original 6502 has does not correctly fetch the target
@@ -770,7 +770,7 @@ public class Cpu implements InstructionTable {
setIrqDisableFlag(); setIrqDisableFlag();
// Load interrupt vector address into PC // 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() { public String getAccumulatorStatus() {
return "$" + HexUtil.byteToHex(state.a); return "$" + Utils.byteToHex(state.a);
} }
public String getXRegisterStatus() { public String getXRegisterStatus() {
return "$" + HexUtil.byteToHex(state.x); return "$" + Utils.byteToHex(state.x);
} }
public String getYRegisterStatus() { public String getYRegisterStatus() {
return "$" + HexUtil.byteToHex(state.y); return "$" + Utils.byteToHex(state.y);
} }
public String getProgramCounterStatus() { public String getProgramCounterStatus() {
return "$" + HexUtil.wordToHex(state.pc); return "$" + Utils.wordToHex(state.pc);
} }
public String getStackPointerStatus() { public String getStackPointerStatus() {
return "$" + HexUtil.byteToHex(state.sp); return "$" + Utils.byteToHex(state.sp);
} }
public int getProcessorStatus() { 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 * Given a hi byte and a low byte, return the Absolute,X
* offset address. * offset address.
*/ */
int xAddress(int lowByte, int hiByte) { 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. * offset address.
*/ */
int yAddress(int lowByte, int hiByte) { 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); } 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 { public String disassembleNextOp() {
/** return Cpu.disassembleOp(state.nextIr, state.nextArgs);
* 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; * @param address Address to disassemble
public boolean negativeFlag; * @return String containing the disassembled instruction and operands.
public boolean zeroFlag; */
public boolean irqDisableFlag; public String disassembleOpAtAddress(int address) throws MemoryAccessException {
public boolean decimalModeFlag; int opCode = bus.read(address);
public boolean breakFlag; int args[] = new int[2];
public boolean overflowFlag; int size = Cpu.instructionSizes[opCode];
public long stepCounter = 0L; for (int i = 1; i < size; i++) {
int nextRead = (address + i) % bus.endAddress();
/** args[i-1] = bus.read(nextRead);
* Create an empty CPU State.
*/
public CpuState() {}
/**
* Snapshot a copy of the CpuState.
*
* (This is a copy constructor rather than an implementation of <code>Cloneable</code>
* 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;
} }
/** return disassembleOp(opCode, args);
* 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' : '.') +
"]";
}
} }
} }
@@ -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 <code>Cloneable</code>
* 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' : '.') +
"]";
}
}
+15 -4
View File
@@ -119,7 +119,7 @@ public class Simulator {
private JFileChooser fileChooser; private JFileChooser fileChooser;
private PreferencesDialog preferences; private PreferencesDialog preferences;
private SortedSet<Integer> breakpoints; private Breakpoints breakpoints;
private final Object commandMonitorObject = new Object(); private final Object commandMonitorObject = new Object();
@@ -136,7 +136,7 @@ public class Simulator {
private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"}; private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"};
public Simulator(Class machineClass) throws Exception { public Simulator(Class machineClass) throws Exception {
this.breakpoints = new TreeSet<>(); this.breakpoints = new Breakpoints(this);
this.machine = (Machine) machineClass.getConstructors()[0].newInstance(); 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 { class LoadProgramAction extends AbstractAction {
public LoadProgramAction() { public LoadProgramAction() {
super("Load Program...", null); super("Load Program...", null);
@@ -473,10 +477,14 @@ public class Simulator {
program[i++] = dis.readByte(); program[i++] = dis.readByte();
} }
SwingUtilities.invokeLater(() -> console.reset());
// Now load the program at the starting address. // Now load the program at the starting address.
loadProgram(program, preferences.getProgramStartAddress()); loadProgram(program, preferences.getProgramStartAddress());
SwingUtilities.invokeLater(() -> {
console.reset();
breakpoints.refresh();
});
// TODO: "Don't Show Again" checkbox // TODO: "Don't Show Again" checkbox
JOptionPane.showMessageDialog(mainWindow, JOptionPane.showMessageDialog(mainWindow,
"Loaded Successfully At " + "Loaded Successfully At " +
@@ -524,6 +532,9 @@ public class Simulator {
updateVisibleState(); updateVisibleState();
// Refresh breakpoints to show new memory contents.
breakpoints.refresh();
logger.info("ROM File `{}' loaded at {}", romFile.getName(), logger.info("ROM File `{}' loaded at {}", romFile.getName(),
String.format("0x%04X", machine.getRomBase())); String.format("0x%04X", machine.getRomBase()));
// TODO: "Don't Show Again" checkbox // TODO: "Don't Show Again" checkbox
@@ -23,7 +23,8 @@
package com.loomcom.symon.ui; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -31,8 +32,6 @@ import javax.swing.*;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.SortedSet;
/** /**
* Simple window to enter breakpoints. * Simple window to enter breakpoints.
@@ -45,45 +44,9 @@ public class BreakpointsWindow extends JFrame {
private static final String EMPTY_STRING = ""; private static final String EMPTY_STRING = "";
private JFrame mainWindow; private JFrame mainWindow;
private SortedSet<Integer> breakpoints; private Breakpoints breakpoints;
/** public BreakpointsWindow(Breakpoints breakpoints,
* Simple ListModel to back the list of breakpoints.
*/
private class BreakpointsListModel extends AbstractListModel<String> {
private SortedSet<Integer> breakpoints;
public BreakpointsListModel(SortedSet<Integer> breakpoints) {
this.breakpoints = breakpoints;
}
@Override
public int getSize() {
return breakpoints.size();
}
@Override
public String getElementAt(int index) {
ArrayList<Integer> values = new ArrayList<>(breakpoints);
return "$" + HexUtil.wordToHex(values.get(index));
}
public void addElement(Integer breakpoint) {
breakpoints.add(breakpoint);
ArrayList<Integer> values = new ArrayList<>(breakpoints);
int index = values.indexOf(breakpoint);
fireIntervalAdded(this, index, index);
}
public void removeElement(int index) {
ArrayList<Integer> values = new ArrayList<>(breakpoints);
Integer breakpoint = values.get(index);
breakpoints.remove(breakpoint);
fireIntervalRemoved(this, index, index);
}
}
public BreakpointsWindow(SortedSet<Integer> breakpoints,
JFrame mainWindow) { JFrame mainWindow) {
this.breakpoints = breakpoints; this.breakpoints = breakpoints;
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
@@ -103,30 +66,26 @@ public class BreakpointsWindow extends JFrame {
JButton removeButton = new JButton("Del"); JButton removeButton = new JButton("Del");
removeButton.setEnabled(false); removeButton.setEnabled(false);
JTextField addTextField = new JTextField(5); JTextField addTextField = new JTextField(4);
BreakpointsListModel listModel = new BreakpointsListModel(breakpoints); JTable breakpointsTable = new JTable(breakpoints);
JList<String> breakpointsList = new JList<>(listModel); breakpointsTable.setShowGrid(true);
breakpointsList.setFont(new Font("Monospace", Font.PLAIN, 14)); breakpointsTable.setGridColor(Color.LIGHT_GRAY);
breakpointsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 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.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
breakpointsPanel.add(scrollPane, BorderLayout.CENTER); 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 -> { ActionListener addBreakpointListener = e -> {
int value = -1; int value = -1;
@@ -147,9 +106,9 @@ public class BreakpointsWindow extends JFrame {
return; 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); addTextField.setText(EMPTY_STRING);
}; };
@@ -157,7 +116,7 @@ public class BreakpointsWindow extends JFrame {
addButton.addActionListener(addBreakpointListener); addButton.addActionListener(addBreakpointListener);
addTextField.addActionListener(addBreakpointListener); addTextField.addActionListener(addBreakpointListener);
removeButton.addActionListener(e -> listModel.removeElement(breakpointsList.getSelectedIndex())); removeButton.addActionListener(e -> breakpoints.removeBreakpointAtIndex(breakpointsTable.getSelectedRow()));
controlPanel.add(addTextField); controlPanel.add(addTextField);
controlPanel.add(addButton); controlPanel.add(addButton);
@@ -25,7 +25,7 @@ package com.loomcom.symon.ui;
import com.loomcom.symon.Bus; import com.loomcom.symon.Bus;
import com.loomcom.symon.exceptions.MemoryAccessException; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -107,7 +107,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
previousPageButton.setEnabled(pageNumber > 0x00); previousPageButton.setEnabled(pageNumber > 0x00);
nextPageButton.setEnabled(pageNumber < 0xff); 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) { public Object getValueAt(int row, int column) {
try { try {
if (column == 0) { if (column == 0) {
return HexUtil.wordToHex(fullAddress(row, 1)); return Utils.wordToHex(fullAddress(row, 1));
} else if (column < 9) { } else if (column < 9) {
// Display hex value of the data // Display hex value of the data
return HexUtil.byteToHex(bus.read(fullAddress(row, column))); return Utils.byteToHex(bus.read(fullAddress(row, column)));
} else { } else {
// Display the ASCII equivalent (if printable) // 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) { } catch (MemoryAccessException ex) {
return "??"; return "??";
@@ -24,14 +24,13 @@
package com.loomcom.symon.ui; package com.loomcom.symon.ui;
import com.loomcom.symon.Cpu; import com.loomcom.symon.Cpu;
import com.loomcom.symon.CpuState;
import com.loomcom.symon.machines.Machine; import com.loomcom.symon.machines.Machine;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.border.EtchedBorder; import javax.swing.border.EtchedBorder;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/** /**
* UI component that displays the current state of the simulated CPU. * UI component that displays the current state of the simulated CPU.
@@ -277,7 +276,7 @@ public class StatusPanel extends JPanel {
*/ */
public void updateState() { public void updateState() {
Cpu cpu = machine.getCpu(); Cpu cpu = machine.getCpu();
Cpu.CpuState cpuState = cpu.getCpuState(); CpuState cpuState = cpu.getCpuState();
// Update the Processor Status Flag display // Update the Processor Status Flag display
int status = cpuState.getStatusFlag(); int status = cpuState.getStatusFlag();
@@ -293,7 +292,7 @@ public class StatusPanel extends JPanel {
// Update the register and address displays // Update the register and address displays
// We always want to show the NEXT instruction that will be executed // 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()); pcField.setText(cpu.getProgramCounterStatus());
spField.setText(cpu.getStackPointerStatus()); spField.setText(cpu.getStackPointerStatus());
aField.setText(cpu.getAccumulatorStatus()); aField.setText(cpu.getAccumulatorStatus());
@@ -24,7 +24,7 @@
package com.loomcom.symon.ui; package com.loomcom.symon.ui;
import com.loomcom.symon.Cpu; import com.loomcom.symon.CpuState;
import com.loomcom.symon.util.FifoRingBuffer; import com.loomcom.symon.util.FifoRingBuffer;
import javax.swing.*; import javax.swing.*;
@@ -36,7 +36,7 @@ import java.awt.*;
*/ */
public class TraceLog extends JFrame { public class TraceLog extends JFrame {
private final FifoRingBuffer<Cpu.CpuState> traceLog; private final FifoRingBuffer<CpuState> traceLog;
private final JTextArea traceLogTextArea; private final JTextArea traceLogTextArea;
private static final Dimension MIN_SIZE = new Dimension(320, 200); private static final Dimension MIN_SIZE = new Dimension(320, 200);
@@ -71,7 +71,7 @@ public class TraceLog extends JFrame {
StringBuilder logString = new StringBuilder(); StringBuilder logString = new StringBuilder();
synchronized(traceLog) { synchronized(traceLog) {
for (Cpu.CpuState state : traceLog) { for (CpuState state : traceLog) {
logString.append(state.toTraceEvent()); logString.append(state.toTraceEvent());
} }
} }
@@ -99,9 +99,9 @@ public class TraceLog extends JFrame {
* *
* @param state The CPU State to append. * @param state The CPU State to append.
*/ */
public void append(Cpu.CpuState state) { public void append(CpuState state) {
synchronized(traceLog) { synchronized(traceLog) {
traceLog.push(new Cpu.CpuState(state)); traceLog.push(new CpuState(state));
} }
} }
@@ -24,24 +24,9 @@
package com.loomcom.symon.util; package com.loomcom.symon.util;
/** /**
* Hex String Utilities. * Various Utilities
*
* <p/>
*
* 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.
*
* <p/>
*
* The most convenient way to get a formatted hex value from an integer is with the <code>String.format</code>
* method, but this turns out to be extremely inefficient. Formatting a million integers
* with <code>String.format</code> takes something like 1600ms. Formatting the same number of integers
* with <code>HexUtil</code> takes only 160ms. This is on par with <code>Integer.toHexString</code>,
* but also gives the desired padding.
*
*/ */
public class HexUtil { public class Utils {
static final String NON_PRINTABLE = "."; static final String NON_PRINTABLE = ".";
@@ -93,6 +78,7 @@ public class HexUtil {
/** /**
* Very fast 8-bit int to ASCII conversion. * Very fast 8-bit int to ASCII conversion.
*
* @param val The value of an ASCII character. * @param val The value of an ASCII character.
* @return A string representing the ASCII character. * @return A string representing the ASCII character.
*/ */
@@ -123,4 +109,11 @@ public class HexUtil {
public static String wordToHex(int val) { public static String wordToHex(int val) {
return HEX_CONSTANTS[(val >> 8) & 0xff] + HEX_CONSTANTS[val & 0xff]; 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;
}
} }
+6 -5
View File
@@ -1,5 +1,6 @@
package com.loomcom.symon; package com.loomcom.symon;
import com.loomcom.symon.util.Utils;
import junit.framework.*; import junit.framework.*;
import com.loomcom.symon.devices.*; import com.loomcom.symon.devices.*;
@@ -537,11 +538,11 @@ public class CpuTest extends TestCase {
public void testAddress() { public void testAddress() {
assertEquals(0xf1ea, cpu.address(0xea, 0xf1)); assertEquals(0xf1ea, Utils.address(0xea, 0xf1));
assertEquals(0x00ea, cpu.address(0xea, 0x00)); assertEquals(0x00ea, Utils.address(0xea, 0x00));
assertEquals(0xf100, cpu.address(0x00, 0xf1)); assertEquals(0xf100, Utils.address(0x00, 0xf1));
assertEquals(0x1234, cpu.address(0x34, 0x12)); assertEquals(0x1234, Utils.address(0x34, 0x12));
assertEquals(0xffff, cpu.address(0xff, 0xff)); assertEquals(0xffff, Utils.address(0xff, 0xff));
} }
public void testZpxAddress() { public void testZpxAddress() {
@@ -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));
}
}
}
@@ -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));
}
}
}