Compare commits

..
4 Commits
Author SHA1 Message Date
Seth Morabito 634ea933f1 Add disassembled instructions to breakpoints 2016-01-02 19:05:38 -08:00
Seth Morabito 6b5976be8f Update for 1.1.1 release 2016-01-02 09:46:12 -08:00
Seth Morabito 3530e9e99a Enter key can add breakpoints 2016-01-02 09:40:34 -08:00
Seth Morabito 7daeb9e978 Update README for 1.1.0 2015-12-31 16:38:27 -08:00
17 changed files with 509 additions and 437 deletions

No files matched your search

+41 -23
View File
@@ -1,9 +1,9 @@
SYMON - A 6502 System Simulator
===============================
**Version:** 1.1.0
**Version:** 1.1.1
**Last Updated:** 31 December, 2015
**Last Updated:** 2 January, 2016
See the file COPYING for license.
@@ -103,19 +103,32 @@ Memory contents can be viewed (and edited) one page at a time through the Memory
![Trace Log] (https://github.com/sethm/symon/raw/master/screenshots/trace_log.png)
The last 20,000 execution steps are disassembled and logged to the Trace Log Window.
The last 20,000 execution steps are disassembled and logged to the Trace Log
Window.
### 3.6 NEW - Experimental 6545 CRTC Video
### 3.6 Simulator Speeds
![Speeds] (https://github.com/sethm/symon/raw/master/screenshots/simulator_menu.png)
Simulated speeds may be set from 1MHz to 8MHz.
### 3.7 Breakpoints
![Breakpoints] (https://github.com/sethm/symon/raw/master/screenshots/breakpoints.png)
Breakpoints can be set and removed through the Breakpoints window.
### 3.8 Experimental 6545 CRTC Video
![Composite Video] (https://github.com/sethm/symon/raw/master/screenshots/video_window.png)
This feature is highly experimental. It's possible to open a video window from the "View" menu.
This window simulates the output of a MOS 6545 CRT Controller located at address `$9000` and
`$9001`.
This feature is highly experimental. It's possible to open a video window
from the "View" menu. This window simulates the output of a MOS 6545 CRT
Controller located at address `$9000` and `$9001`.
By default, the 40 x 25 character display uses video memory located at base address `$7000`.
This means that the memory from address `$7000` (28672 decimal) to `$73E8` (29672 decimal)
is directly mapped to video.
By default, the 40 x 25 character display uses video memory located at base
address `$7000`. This means that the memory from address `$7000` (28672
decimal) to `$73E8` (29672 decimal) is directly mapped to video.
- Address Register (at address `$9000`)
- R1: Horizontal Displayed Columns
@@ -128,18 +141,20 @@ is directly mapped to video.
- R14: Cursor Position (High Byte)
- R15: Cursor Position (Low Byte)
Although the simulation is pretty good, there are a few key differences between
the simulated 6545 and a real 6545:
Although the simulation is pretty good, there are a few key differences
between the simulated 6545 and a real 6545:
- The simulated 6545 supports only the straight binary addressing mode of the real 6545,
and not the Row/Column addressing mode.
- The simulated 6545 has full 16 bit addressing, where the real 6545 has only
a 14-bit address bus.
- The simulation is done at a whole-frame level, meaning that lots of
6545 programming tricks that were achieved by updating the frame address
during vertical and horizontal sync times are not achievable. There is no way
(for example) to change the Display Start Address (R12 and R13) while a
frame is being drawn.
- The simulated 6545 supports only the straight binary addressing
mode of the real 6545, and not the Row/Column addressing mode.
- The simulated 6545 has full 16 bit addressing, where the real 6545
has only a 14-bit address bus.
- The simulation is done at a whole-frame level, meaning that lots
of 6545 programming tricks that were achieved by updating the
frame address during vertical and horizontal sync times are not
achievable. There is no way (for example) to change the Display Start
Address (R12 and R13) while a frame is being drawn.
For more information on the 6545 CRTC and its programming model, please see the following resources
@@ -148,7 +163,7 @@ For more information on the 6545 CRTC and its programming model, please see the
- [MOS 6545 Datasheet (PDF)] (http://www.6502.org/users/andre/hwinfo/crtc/crtc.html)
#### 3.6.1 Example BASIC Program to test Video
#### 3.8.1 Example BASIC Program to test Video
This program will fill the video screen with all printable characters.
@@ -173,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
Java 1.8 or greater, just type:
$ java -jar symon-1.1.0.0-SNAPSHOT.jar
$ java -jar symon-1.1.1.jar
When Symon is running, you should be presented with a simple graphical
interface.
@@ -220,6 +235,9 @@ running.
## 5.0 Revision History
- **1.1.1:** 2 January, 2016 - Minor enhancement: Allows breakpoints
to be added with the Enter key.
- **1.1.0:** 31 December, 2015 - Fixed delay loop to better
simulate various clock speeds. Added ability to select clock
speed at runtime. Status display now shows the next instruction
+1 -1
View File
@@ -4,7 +4,7 @@
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>1.1.0</version>
<version>1.1.1</version>
<name>symon</name>
<url>http://www.loomcom.com/symon</url>
<properties>
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 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;
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 <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;
/**
* @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);
}
}
@@ -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 PreferencesDialog preferences;
private SortedSet<Integer> 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
@@ -23,15 +23,15 @@
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;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.util.ArrayList;
import java.util.SortedSet;
import java.awt.event.ActionListener;
/**
* Simple window to enter breakpoints.
@@ -44,45 +44,9 @@ public class BreakpointsWindow extends JFrame {
private static final String EMPTY_STRING = "";
private JFrame mainWindow;
private SortedSet<Integer> breakpoints;
private 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,
public BreakpointsWindow(Breakpoints breakpoints,
JFrame mainWindow) {
this.breakpoints = breakpoints;
this.mainWindow = mainWindow;
@@ -102,31 +66,27 @@ 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<String> 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);
}
});
addButton.addActionListener(e -> {
ActionListener addBreakpointListener = e -> {
int value = -1;
String newBreakpoint = addTextField.getText();
@@ -136,7 +96,7 @@ public class BreakpointsWindow extends JFrame {
}
try {
value = Integer.parseInt(addTextField.getText(), 16);
value = (Integer.parseInt(addTextField.getText(), 16) & 0xffff);
} catch (NumberFormatException ex) {
logger.warn("Can't parse page number {}", newBreakpoint);
return;
@@ -146,14 +106,17 @@ 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);
});
};
removeButton.addActionListener(e -> listModel.removeElement(breakpointsList.getSelectedIndex()));
addButton.addActionListener(addBreakpointListener);
addTextField.addActionListener(addBreakpointListener);
removeButton.addActionListener(e -> breakpoints.removeBreakpointAtIndex(breakpointsTable.getSelectedRow()));
controlPanel.add(addTextField);
controlPanel.add(addButton);
@@ -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 "??";
@@ -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());
@@ -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<Cpu.CpuState> traceLog;
private final FifoRingBuffer<CpuState> 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));
}
}
@@ -24,24 +24,9 @@
package com.loomcom.symon.util;
/**
* Hex String 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.
*
* 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;
}
}
+6 -5
View File
@@ -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() {
@@ -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));
}
}
}