13 Commits
Author SHA1 Message Date
Seth Morabito 0e49b197b3 Update README for 1.2.1 2016-01-08 20:52:02 -08:00
Seth Morabito eff98118d5 Fix a few straggling Java 8-isms. 2016-01-08 20:40:23 -08:00
Seth Morabito 66c52c8826 Revert Java 1.8 changes. Buildable with Java 1.7
There are still active users on Java 1.7, so building with Java 8
was a no-no. This change reverts the recent migration to Java 8,
allowing JDK 1.7 to compile the code.

This means, at least for the time being, no more Lambda expressions
(Boooooooooooooooo!!)
2016-01-08 19:11:42 -08:00
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
24 changed files with 693 additions and 552 deletions

No files matched your search

+20 -6
View File
@@ -1,9 +1,9 @@
SYMON - A 6502 System Simulator
===============================
**Version:** 1.1.1
**Version:** 1.2.1
**Last Updated:** 2 January, 2016
**Last Updated:** 8 January, 2016
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
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
interface.
@@ -235,6 +235,12 @@ running.
## 5.0 Revision History
- **1.2.1:** 8 January, 2016 - Remove dependency on Java 8. Now
supports compiling and running under Java 1.7.
- **1.2.0:** 3 January, 2016 - Add symbolic disassembly to breakpoints
window.
- **1.1.1:** 2 January, 2016 - Minor enhancement: Allows breakpoints
to be added with the Enter key.
@@ -286,7 +292,15 @@ running.
- **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).
@@ -310,7 +324,7 @@ running.
- Symbolic debugging.
## 7.0 Copyright and Acknowledgements
## 8.0 Copyright and Acknowledgements
**Copyright (c) 2014 Seth J. Morabito <web@loomcom.com>**
@@ -330,7 +344,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),
for information about how instructions are coded
## 8.0 Licensing
## 9.0 Licensing
Symon is free software. It is distributed under the MIT License.
Please see the file 'COPYING' for full details of the license.
+3 -3
View File
@@ -4,7 +4,7 @@
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>1.1.1</version>
<version>1.2.1</version>
<name>symon</name>
<url>http://www.loomcom.com/symon</url>
<properties>
@@ -91,8 +91,8 @@
<version>3.1</version>
<configuration>
<compilerArgument>-Xlint:unchecked</compilerArgument>
<source>1.8</source>
<target>1.8</target>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
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;
}
}
}
+4 -3
View File
@@ -34,7 +34,6 @@ import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* The Bus ties the whole thing together, man.
@@ -222,9 +221,11 @@ public class Bus {
List<Integer> priorities = new ArrayList<>(deviceMap.keySet());
Collections.sort(priorities);
for(int priority : priorities) {
for (int priority : priorities) {
SortedSet<Device> deviceSet = deviceMap.get(priority);
devices.addAll(deviceSet.stream().collect(Collectors.toList()));
for (Device device : deviceSet) {
devices.add(device);
}
}
return devices;
+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' : '.') +
"]";
}
}
+10 -7
View File
@@ -86,13 +86,16 @@ public class Main {
final Simulator simulator = new Simulator(machineClass);
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi();
} catch (Exception e) {
e.printStackTrace();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi();
} catch (Exception e) {
e.printStackTrace();
}
}
});
+93 -50
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();
@@ -185,13 +185,16 @@ public class Simulator {
JButton hardResetButton = new JButton("Hard Reset");
stepCountBox = new JComboBox<>(STEPS);
stepCountBox.addActionListener(actionEvent -> {
try {
JComboBox cb = (JComboBox) actionEvent.getSource();
stepsPerClick = Integer.parseInt((String) cb.getSelectedItem());
} catch (NumberFormatException ex) {
stepsPerClick = 1;
stepCountBox.setSelectedIndex(0);
stepCountBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
try {
JComboBox cb = (JComboBox) actionEvent.getSource();
stepsPerClick = Integer.parseInt((String) cb.getSelectedItem());
} catch (NumberFormatException ex) {
stepsPerClick = 1;
stepCountBox.setSelectedIndex(0);
}
}
});
@@ -211,24 +214,38 @@ public class Simulator {
// Bottom - buttons.
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
runStopButton.addActionListener(actionEvent -> {
if (runLoop != null && runLoop.isRunning()) {
handleStop();
} else {
handleStart();
runStopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (runLoop != null && runLoop.isRunning()) {
Simulator.this.handleStop();
} else {
Simulator.this.handleStart();
}
}
});
stepButton.addActionListener(actionEvent -> handleStep(stepsPerClick));
softResetButton.addActionListener(actionEvent -> {
// If this was a CTRL-click, do a hard reset.
handleReset(false);
stepButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
Simulator.this.handleStep(stepsPerClick);
}
});
hardResetButton.addActionListener(actionEvent -> {
// If this was a CTRL-click, do a hard reset.
handleReset(true);
softResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(false);
}
});
hardResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(true);
}
});
mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
@@ -400,13 +417,16 @@ public class Simulator {
logger.debug("Starting main run loop.");
isRunning = true;
SwingUtilities.invokeLater(() -> {
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
stepCountBox.setEnabled(false);
menuBar.simulatorDidStart();
// Toggle the state of the run button
runStopButton.setText("Stop");
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
stepCountBox.setEnabled(false);
menuBar.simulatorDidStart();
// Toggle the state of the run button
runStopButton.setText("Stop");
}
});
try {
@@ -417,17 +437,20 @@ public class Simulator {
logger.error("Exception in main simulator run thread. Exiting run.", ex);
}
SwingUtilities.invokeLater(() -> {
statusPane.updateState();
memoryWindow.updateState();
runStopButton.setText("Run");
stepButton.setEnabled(true);
stepCountBox.setEnabled(true);
if (traceLog.isVisible()) {
traceLog.refresh();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
statusPane.updateState();
memoryWindow.updateState();
runStopButton.setText("Run");
stepButton.setEnabled(true);
stepCountBox.setEnabled(true);
if (traceLog.isVisible()) {
traceLog.refresh();
}
menuBar.simulatorDidStop();
traceLog.simulatorDidStop();
}
menuBar.simulatorDidStop();
traceLog.simulatorDidStop();
});
isRunning = false;
@@ -443,6 +466,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 +500,17 @@ 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(new Runnable() {
@Override
public void run() {
console.reset();
breakpoints.refresh();
}
});
// TODO: "Don't Show Again" checkbox
JOptionPane.showMessageDialog(mainWindow,
"Loaded Successfully At " +
@@ -524,6 +558,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
@@ -612,9 +649,12 @@ public class Simulator {
}
public void actionPerformed(ActionEvent actionEvent) {
SwingUtilities.invokeLater(() -> {
console.setFont(new Font("Monospaced", Font.PLAIN, size));
mainWindow.pack();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
console.setFont(new Font("Monospaced", Font.PLAIN, size));
mainWindow.pack();
}
});
}
}
@@ -878,12 +918,15 @@ public class Simulator {
private void updateVisibleState() {
// Immediately update the UI.
SwingUtilities.invokeLater(() -> {
// Now update the state
statusPane.updateState();
memoryWindow.updateState();
if (traceLog.shouldUpdate()) {
traceLog.refresh();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Now update the state
statusPane.updateState();
memoryWindow.updateState();
if (traceLog.shouldUpdate()) {
traceLog.refresh();
}
}
});
}
@@ -115,7 +115,9 @@ public abstract class Device implements Comparable<Device> {
}
public void notifyListeners() {
deviceChangeListeners.forEach(DeviceChangeListener::deviceStateChanged);
for (DeviceChangeListener listener : deviceChangeListeners) {
listener.deviceStateChanged();
}
}
/**
@@ -27,7 +27,6 @@ package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import jdk.nashorn.internal.ir.annotations.Ignore;
import java.io.File;
import java.io.FileInputStream;
@@ -23,16 +23,18 @@
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 javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.SortedSet;
/**
* Simple window to enter breakpoints.
@@ -45,45 +47,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;
@@ -99,65 +65,72 @@ public class BreakpointsWindow extends JFrame {
breakpointsPanel.setLayout(new BorderLayout());
breakpointsPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
JButton addButton = new JButton("Add");
JButton removeButton = new JButton("Del");
final JButton addButton = new JButton("Add");
final JButton removeButton = new JButton("Del");
removeButton.setEnabled(false);
JTextField addTextField = new JTextField(5);
final 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);
final JTable breakpointsTable = new JTable(breakpoints);
breakpointsTable.setShowGrid(true);
breakpointsTable.setGridColor(Color.LIGHT_GRAY);
breakpointsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
breakpointsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent 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);
ActionListener addBreakpointListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int value;
breakpointsList.addListSelectionListener(le -> {
int idx = breakpointsList.getSelectedIndex();
String newBreakpoint = addTextField.getText();
if (idx == -1) {
removeButton.setEnabled(false);
} else {
removeButton.setEnabled(true);
if (newBreakpoint == null || newBreakpoint.isEmpty()) {
return;
}
try {
value = (Integer.parseInt(addTextField.getText(), 16) & 0xffff);
} catch (NumberFormatException ex) {
logger.warn("Can't parse page number {}", newBreakpoint);
return;
}
if (value < 0) {
return;
}
breakpoints.addBreakpoint(value);
logger.debug("Added breakpoint ${}", Utils.wordToHex(value));
addTextField.setText(EMPTY_STRING);
}
});
ActionListener addBreakpointListener = e -> {
int value = -1;
String newBreakpoint = addTextField.getText();
if (newBreakpoint == null || newBreakpoint.isEmpty()) {
return;
}
try {
value = (Integer.parseInt(addTextField.getText(), 16) & 0xffff);
} catch (NumberFormatException ex) {
logger.warn("Can't parse page number {}", newBreakpoint);
return;
}
if (value < 0) {
return;
}
listModel.addElement(value);
logger.debug("Added breakpoint ${}", HexUtil.wordToHex(value));
addTextField.setText(EMPTY_STRING);
};
addButton.addActionListener(addBreakpointListener);
addTextField.addActionListener(addBreakpointListener);
removeButton.addActionListener(e -> listModel.removeElement(breakpointsList.getSelectedIndex()));
removeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent 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 "??";
@@ -97,19 +97,25 @@ public class PreferencesDialog extends Observable implements Preferences {
JButton applyButton = new JButton("Apply");
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(actionEvent -> {
updateUi();
dialog.setVisible(false);
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
PreferencesDialog.this.updateUi();
dialog.setVisible(false);
}
});
applyButton.addActionListener(actionEvent -> {
haltOnBreak = haltOnBreakCheckBox.isSelected();
programLoadAddress = hexToInt(programLoadAddressField.getText());
updateUi();
// TODO: Actually check to see if values have changed, don't assume.
setChanged();
PreferencesDialog.this.notifyObservers();
dialog.setVisible(false);
applyButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
haltOnBreak = haltOnBreakCheckBox.isSelected();
programLoadAddress = PreferencesDialog.this.hexToInt(programLoadAddressField.getText());
PreferencesDialog.this.updateUi();
// TODO: Actually check to see if values have changed, don't assume.
PreferencesDialog.this.setChanged();
PreferencesDialog.this.notifyObservers();
dialog.setVisible(false);
}
});
buttonsContainer.add(applyButton);
@@ -24,6 +24,7 @@
package com.loomcom.symon.ui;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.CpuState;
import com.loomcom.symon.machines.Machine;
import javax.swing.*;
@@ -163,59 +164,74 @@ public class StatusPanel extends JPanel {
yField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
// Make fields editable
pcField.addActionListener(e -> {
try {
int newVal = getHexVal(pcField) & 0xffff;
machine.getCpu().setProgramCounter(newVal);
} catch (Exception ex) {
// Swallow exception
}
pcField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = StatusPanel.this.getHexVal(pcField) & 0xffff;
machine.getCpu().setProgramCounter(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
StatusPanel.this.updateState();
}
});
spField.addActionListener(e -> {
try {
int newVal = getHexVal(spField) & 0xff;
machine.getCpu().setStackPointer(newVal);
} catch (Exception ex) {
// Swallow exception
}
spField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = StatusPanel.this.getHexVal(spField) & 0xff;
machine.getCpu().setStackPointer(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
StatusPanel.this.updateState();
}
});
aField.addActionListener(e -> {
try {
int newVal = getHexVal(aField) & 0xff;
machine.getCpu().setAccumulator(newVal);
} catch (Exception ex) {
// Swallow exception
}
aField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = StatusPanel.this.getHexVal(aField) & 0xff;
machine.getCpu().setAccumulator(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
StatusPanel.this.updateState();
}
});
xField.addActionListener(e -> {
try {
int newVal = getHexVal(xField) & 0xff;
machine.getCpu().setXRegister(newVal);
} catch (Exception ex) {
// Swallow exception
}
xField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = StatusPanel.this.getHexVal(xField) & 0xff;
machine.getCpu().setXRegister(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
StatusPanel.this.updateState();
}
});
yField.addActionListener(e -> {
try {
int newVal = getHexVal(yField) & 0xff;
machine.getCpu().setYRegister(newVal);
} catch (Exception ex) {
// Swallow exception
}
yField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = StatusPanel.this.getHexVal(yField) & 0xff;
machine.getCpu().setYRegister(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
StatusPanel.this.updateState();
}
});
constraints.anchor = GridBagConstraints.LINE_START;
@@ -277,7 +293,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 +309,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));
}
}
@@ -122,10 +122,13 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
*/
private class CursorBlinker implements Runnable {
public void run() {
SwingUtilities.invokeLater(() -> {
if (cursorBlinkRate > 0) {
hideCursor = !hideCursor;
repaint();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (cursorBlinkRate > 0) {
hideCursor = !hideCursor;
VideoWindow.this.repaint();
}
}
});
}
@@ -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));
}
}
}