First work toward moving to a Swing UI for the simulator.
No files matched your search
@@ -1,7 +1,6 @@
|
|||||||
*~
|
*~
|
||||||
*#
|
*#
|
||||||
target
|
target
|
||||||
.classpath
|
|
||||||
.project
|
|
||||||
.settings
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
symon.iml
|
||||||
@@ -7,12 +7,32 @@
|
|||||||
<version>0.1</version>
|
<version>0.1</version>
|
||||||
<name>symon</name>
|
<name>symon</name>
|
||||||
<url>http://www.loomcom.com/symon</url>
|
<url>http://www.loomcom.com/symon</url>
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>
|
||||||
|
UTF-8
|
||||||
|
</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>
|
||||||
|
UTF-8
|
||||||
|
</project.reporting.outputEncoding>
|
||||||
|
</properties>
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>jline</id>
|
<id>jline</id>
|
||||||
<name>JLine Project Repository</name>
|
<name>JLine Project Repository</name>
|
||||||
<url>http://jline.sourceforge.net/m2repo</url>
|
<url>http://jline.sourceforge.net/m2repo</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
<!-- Loomcom's Maven2 repository -->
|
||||||
|
<repository>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
<updatePolicy>always</updatePolicy>
|
||||||
|
<checksumPolicy>fail</checksumPolicy>
|
||||||
|
</releases>
|
||||||
|
<id>com.loomcom</id>
|
||||||
|
<name>Loom Communications Maven2 Repository</name>
|
||||||
|
<url>http://www.loomcom.com/maven2</url>
|
||||||
|
<layout>default</layout>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -26,6 +46,11 @@
|
|||||||
<artifactId>jline</artifactId>
|
<artifactId>jline</artifactId>
|
||||||
<version>0.9.9</version>
|
<version>0.9.9</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.grahamedgecombe.jterminal</groupId>
|
||||||
|
<artifactId>jterminal</artifactId>
|
||||||
|
<version>1.0.2-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -34,6 +59,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>2.3.2</version>
|
||||||
<!-- best lock down version of the plugin too -->
|
<!-- best lock down version of the plugin too -->
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>1.5</source>
|
<source>1.5</source>
|
||||||
@@ -45,15 +71,16 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>2.3.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
<mainClass>com.loomcom.symon.Simulator</mainClass>
|
<mainClass>com.loomcom.symon.MainWindow</mainClass>
|
||||||
<packageName>com.loomcom.symon</packageName>
|
<packageName>com.loomcom.symon</packageName>
|
||||||
</manifest>
|
</manifest>
|
||||||
<manifestEntries>
|
<manifestEntries>
|
||||||
<mode>development</mode>
|
<mode>development</mode>
|
||||||
<url>${pom.url}</url>
|
<url>${project.url}</url>
|
||||||
</manifestEntries>
|
</manifestEntries>
|
||||||
</archive>
|
</archive>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -63,6 +90,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>cobertura-maven-plugin</artifactId>
|
<artifactId>cobertura-maven-plugin</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<check>
|
<check>
|
||||||
<haltOnFailure>false</haltOnFailure>
|
<haltOnFailure>false</haltOnFailure>
|
||||||
@@ -108,6 +136,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>cobertura-maven-plugin</artifactId>
|
<artifactId>cobertura-maven-plugin</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</reporting>
|
</reporting>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.loomcom.symon;
|
package com.loomcom.symon;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import com.loomcom.symon.devices.*;
|
import com.loomcom.symon.devices.*;
|
||||||
import com.loomcom.symon.exceptions.*;
|
import com.loomcom.symon.exceptions.*;
|
||||||
|
|
||||||
@@ -64,7 +65,9 @@ public class Bus {
|
|||||||
*/
|
*/
|
||||||
public boolean isComplete() {
|
public boolean isComplete() {
|
||||||
// Empty maps cannot be complete.
|
// Empty maps cannot be complete.
|
||||||
if (devices.isEmpty()) { return false; }
|
if (devices.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Loop over devices and ensure they are contiguous.
|
// Loop over devices and ensure they are contiguous.
|
||||||
MemoryRange prev = null;
|
MemoryRange prev = null;
|
||||||
@@ -74,7 +77,9 @@ public class Bus {
|
|||||||
MemoryRange cur = d.getMemoryRange();
|
MemoryRange cur = d.getMemoryRange();
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
// If the first entry doesn't start at 'startAddress', return false.
|
// If the first entry doesn't start at 'startAddress', return false.
|
||||||
if (cur.startAddress() != startAddress) { return false; }
|
if (cur.startAddress() != startAddress) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev != null && i < length - 1) {
|
if (prev != null && i < length - 1) {
|
||||||
@@ -87,7 +92,9 @@ public class Bus {
|
|||||||
|
|
||||||
if (i == length - 1) {
|
if (i == length - 1) {
|
||||||
// If the last entry doesn't end at endAddress, return false;
|
// If the last entry doesn't end at endAddress, return false;
|
||||||
if (cur.endAddress() != endAddress) { return false; }
|
if (cur.endAddress() != endAddress) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.loomcom.symon;
|
package com.loomcom.symon;
|
||||||
|
|
||||||
import com.loomcom.symon.InstructionTable;
|
|
||||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,10 +69,14 @@ public class Cpu implements InstructionTable {
|
|||||||
private boolean breakFlag;
|
private boolean breakFlag;
|
||||||
private boolean overflowFlag;
|
private boolean overflowFlag;
|
||||||
|
|
||||||
|
/* The number of steps taken since the last reset. */
|
||||||
|
private long stepCounter = 0L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new CPU.
|
* Construct a new CPU.
|
||||||
*/
|
*/
|
||||||
public Cpu() {}
|
public Cpu() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the bus reference for this CPU.
|
* Set the bus reference for this CPU.
|
||||||
@@ -111,6 +114,9 @@ public class Cpu implements InstructionTable {
|
|||||||
|
|
||||||
// Clear illegal opcode trap.
|
// Clear illegal opcode trap.
|
||||||
opTrap = false;
|
opTrap = false;
|
||||||
|
|
||||||
|
// Reset step counter
|
||||||
|
stepCounter = 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void step(int num) throws MemoryAccessException {
|
public void step(int num) throws MemoryAccessException {
|
||||||
@@ -145,8 +151,10 @@ public class Cpu implements InstructionTable {
|
|||||||
incrementPC();
|
incrementPC();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the data from the effective address (if any)
|
// Increment step counter
|
||||||
|
stepCounter++;
|
||||||
|
|
||||||
|
// Get the data from the effective address (if any)
|
||||||
effectiveAddress = 0;
|
effectiveAddress = 0;
|
||||||
|
|
||||||
switch (irOpMode) {
|
switch (irOpMode) {
|
||||||
@@ -1136,6 +1144,22 @@ public class Cpu implements InstructionTable {
|
|||||||
this.sp = offset;
|
this.sp = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getInstructionRegister() {
|
||||||
|
return this.ir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstructionRegister(int op) {
|
||||||
|
this.ir = op;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStepCounter() {
|
||||||
|
return stepCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStepCounter(long stepCount) {
|
||||||
|
this.stepCounter = stepCount;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @value The value of the Process Status Register bits to be set.
|
* @value The value of the Process Status Register bits to be set.
|
||||||
*/
|
*/
|
||||||
@@ -1181,20 +1205,34 @@ public class Cpu implements InstructionTable {
|
|||||||
*/
|
*/
|
||||||
public int getProcessorStatus() {
|
public int getProcessorStatus() {
|
||||||
int status = 0x20;
|
int status = 0x20;
|
||||||
if (getCarryFlag()) { status |= P_CARRY; }
|
if (getCarryFlag()) {
|
||||||
if (getZeroFlag()) { status |= P_ZERO; }
|
status |= P_CARRY;
|
||||||
if (getIrqDisableFlag()) { status |= P_IRQ_DISABLE; }
|
}
|
||||||
if (getDecimalModeFlag()) { status |= P_DECIMAL; }
|
if (getZeroFlag()) {
|
||||||
if (getBreakFlag()) { status |= P_BREAK; }
|
status |= P_ZERO;
|
||||||
if (getOverflowFlag()) { status |= P_OVERFLOW; }
|
}
|
||||||
if (getNegativeFlag()) { status |= P_NEGATIVE; }
|
if (getIrqDisableFlag()) {
|
||||||
|
status |= P_IRQ_DISABLE;
|
||||||
|
}
|
||||||
|
if (getDecimalModeFlag()) {
|
||||||
|
status |= P_DECIMAL;
|
||||||
|
}
|
||||||
|
if (getBreakFlag()) {
|
||||||
|
status |= P_BREAK;
|
||||||
|
}
|
||||||
|
if (getOverflowFlag()) {
|
||||||
|
status |= P_OVERFLOW;
|
||||||
|
}
|
||||||
|
if (getNegativeFlag()) {
|
||||||
|
status |= P_NEGATIVE;
|
||||||
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return A string representing the current status register state.
|
* @return A string representing the current status register state.
|
||||||
*/
|
*/
|
||||||
public String statusRegisterString() {
|
public String getProcessorStatusString() {
|
||||||
StringBuffer sb = new StringBuffer("[");
|
StringBuffer sb = new StringBuffer("[");
|
||||||
sb.append(getNegativeFlag() ? 'N' : '.'); // Bit 7
|
sb.append(getNegativeFlag() ? 'N' : '.'); // Bit 7
|
||||||
sb.append(getOverflowFlag() ? 'V' : '.'); // Bit 6
|
sb.append(getOverflowFlag() ? 'V' : '.'); // Bit 6
|
||||||
@@ -1208,6 +1246,30 @@ public class Cpu implements InstructionTable {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getOpcodeStatus() {
|
||||||
|
return opcode(ir, args[0], args[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddressStatus() {
|
||||||
|
return String.format("$%04X", addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getARegisterStatus() {
|
||||||
|
return String.format("$%02X", a);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getXRegisterStatus() {
|
||||||
|
return String.format("$%02X", x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getYRegisterStatus() {
|
||||||
|
return String.format("$%02X", y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProgramCounterStatus() {
|
||||||
|
return String.format("$%04X", pc);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a string representing the CPU state.
|
* Returns a string representing the CPU state.
|
||||||
*/
|
*/
|
||||||
@@ -1220,7 +1282,7 @@ public class Cpu implements InstructionTable {
|
|||||||
sb.append("X=" + String.format("$%02X", x) + " ");
|
sb.append("X=" + String.format("$%02X", x) + " ");
|
||||||
sb.append("Y=" + String.format("$%02X", y) + " ");
|
sb.append("Y=" + String.format("$%02X", y) + " ");
|
||||||
sb.append("PC=" + String.format("$%04X", pc) + " ");
|
sb.append("PC=" + String.format("$%04X", pc) + " ");
|
||||||
sb.append("P=" + statusRegisterString());
|
sb.append("P=" + getProcessorStatusString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1247,9 +1309,7 @@ public class Cpu implements InstructionTable {
|
|||||||
else
|
else
|
||||||
++sp;
|
++sp;
|
||||||
|
|
||||||
int data = bus.read(0x100+sp);
|
return bus.read(0x100 + sp);
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1323,13 +1383,16 @@ public class Cpu implements InstructionTable {
|
|||||||
/**
|
/**
|
||||||
* Given an opcode and its operands, return a formatted name.
|
* Given an opcode and its operands, return a formatted name.
|
||||||
*
|
*
|
||||||
* @param opcode
|
* @param opcode The opcode
|
||||||
* @param operands
|
* @param op1 The first operand
|
||||||
|
* @param op2 The second operand
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
String opcode(int opcode, int op1, int op2) {
|
String opcode(int opcode, int op1, int op2) {
|
||||||
String opcodeName = Cpu.opcodeNames[opcode];
|
String opcodeName = Cpu.opcodeNames[opcode];
|
||||||
if (opcodeName == null) { return "???"; }
|
if (opcodeName == null) {
|
||||||
|
return "???";
|
||||||
|
}
|
||||||
|
|
||||||
StringBuffer sb = new StringBuffer(opcodeName);
|
StringBuffer sb = new StringBuffer(opcodeName);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,247 @@
|
|||||||
|
package com.loomcom.symon;
|
||||||
|
|
||||||
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
|
import com.loomcom.symon.exceptions.SymonException;
|
||||||
|
import com.loomcom.symon.ui.Console;
|
||||||
|
import com.loomcom.symon.ui.StatusPane;
|
||||||
|
import com.loomcom.symon.ui.UiUpdater;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
public class MainWindow extends JFrame implements ActionListener {
|
||||||
|
|
||||||
|
// Hard coded for now.
|
||||||
|
public static final int PROGRAM_START_ADDRESS = 0x0300;
|
||||||
|
|
||||||
|
private final static Logger logger = Logger.getLogger(MainWindow.class.getName());
|
||||||
|
|
||||||
|
private final Simulator simulator;
|
||||||
|
private Thread simulatorThread;
|
||||||
|
private final Console console;
|
||||||
|
private final StatusPane statusPane;
|
||||||
|
private final JButton loadButton;
|
||||||
|
private final JButton runButton;
|
||||||
|
private final JButton stepButton;
|
||||||
|
private final JButton resetButton;
|
||||||
|
private final JButton quitButton;
|
||||||
|
private final JFileChooser fileChooser;
|
||||||
|
|
||||||
|
private final UiUpdater uiUpdater;
|
||||||
|
|
||||||
|
public MainWindow(final Simulator simulator) {
|
||||||
|
|
||||||
|
this.setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
this.simulator = simulator;
|
||||||
|
|
||||||
|
// UI components used for I/O.
|
||||||
|
this.console = new Console();
|
||||||
|
this.statusPane = new StatusPane(simulator.getCpu());
|
||||||
|
|
||||||
|
JPanel buttonContainer = new JPanel();
|
||||||
|
JPanel controlsContainer = new JPanel();
|
||||||
|
|
||||||
|
// File Chooser
|
||||||
|
fileChooser = new JFileChooser();
|
||||||
|
|
||||||
|
buttonContainer.setLayout(new FlowLayout());
|
||||||
|
controlsContainer.setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
this.loadButton = new JButton("Load");
|
||||||
|
this.runButton = new JButton("Run");
|
||||||
|
this.stepButton = new JButton("Step");
|
||||||
|
this.resetButton = new JButton("Reset");
|
||||||
|
this.quitButton = new JButton("Quit");
|
||||||
|
|
||||||
|
buttonContainer.add(loadButton);
|
||||||
|
buttonContainer.add(runButton);
|
||||||
|
buttonContainer.add(stepButton);
|
||||||
|
buttonContainer.add(resetButton);
|
||||||
|
buttonContainer.add(quitButton);
|
||||||
|
|
||||||
|
controlsContainer.add(buttonContainer, BorderLayout.PAGE_START);
|
||||||
|
controlsContainer.add(statusPane, BorderLayout.PAGE_END);
|
||||||
|
|
||||||
|
console.setBorder(BorderFactory.createBevelBorder(1));
|
||||||
|
console.setBackground(Color.BLACK);
|
||||||
|
console.setForeground(Color.WHITE);
|
||||||
|
console.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 20));
|
||||||
|
|
||||||
|
getContentPane().add(console, BorderLayout.CENTER);
|
||||||
|
getContentPane().add(controlsContainer, BorderLayout.PAGE_END);
|
||||||
|
|
||||||
|
quitButton.addActionListener(this);
|
||||||
|
runButton.addActionListener(this);
|
||||||
|
stepButton.addActionListener(this);
|
||||||
|
resetButton.addActionListener(this);
|
||||||
|
loadButton.addActionListener(this);
|
||||||
|
|
||||||
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
|
||||||
|
console.requestFocus();
|
||||||
|
|
||||||
|
// Finally create the UI Updater and hand a reference to the Simulator.
|
||||||
|
this.uiUpdater = new UiUpdater(this);
|
||||||
|
|
||||||
|
// The simulator must always have a reference to the same UI updater.
|
||||||
|
this.simulator.setUiUpdater(uiUpdater);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent actionEvent) {
|
||||||
|
if (actionEvent.getSource() == loadButton) {
|
||||||
|
handleProgramLoad();
|
||||||
|
} else if (actionEvent.getSource() == resetButton) {
|
||||||
|
handleReset();
|
||||||
|
} else if (actionEvent.getSource() == stepButton) {
|
||||||
|
handleStep();
|
||||||
|
} else if (actionEvent.getSource() == quitButton) {
|
||||||
|
handleQuit();
|
||||||
|
} else if (actionEvent.getSource() == runButton) {
|
||||||
|
if (simulator.isRunning()) {
|
||||||
|
stop();
|
||||||
|
} else {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleProgramLoad() {
|
||||||
|
try {
|
||||||
|
int retVal = fileChooser.showOpenDialog(MainWindow.this);
|
||||||
|
if (retVal == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File f = fileChooser.getSelectedFile();
|
||||||
|
if (f.canRead()) {
|
||||||
|
long fileSize = f.length();
|
||||||
|
|
||||||
|
if (fileSize > simulator.memorySize()) {
|
||||||
|
throw new IOException("Program will not fit in available memory.");
|
||||||
|
} else {
|
||||||
|
byte[] program = new byte[(int) fileSize];
|
||||||
|
int i = 0;
|
||||||
|
FileInputStream fis = new FileInputStream(f);
|
||||||
|
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||||
|
DataInputStream dis = new DataInputStream(bis);
|
||||||
|
while (dis.available() != 0) {
|
||||||
|
program[i++] = dis.readByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now load the program at the starting address.
|
||||||
|
simulator.loadProgram(program, PROGRAM_START_ADDRESS);
|
||||||
|
// Reset (but don't clear memory, naturally)
|
||||||
|
simulator.reset();
|
||||||
|
// Update status and clear the screen
|
||||||
|
console.reset();
|
||||||
|
uiUpdater.updateUi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Unable to read file: " + ex.getMessage());
|
||||||
|
ex.printStackTrace();
|
||||||
|
} catch (MemoryAccessException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Memory access error loading program");
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleReset() {
|
||||||
|
if (simulator.isRunning()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.log(Level.INFO, "Reset requested. Resetting CPU and clearing memory.");
|
||||||
|
// Reset and clear memory
|
||||||
|
simulator.reset();
|
||||||
|
simulator.clearMemory();
|
||||||
|
// Clear the console.
|
||||||
|
console.reset();
|
||||||
|
// Update status.
|
||||||
|
uiUpdater.updateUi();
|
||||||
|
} catch (MemoryAccessException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Exception during simulator reset: " + ex.getMessage());
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStep() {
|
||||||
|
try {
|
||||||
|
simulator.step();
|
||||||
|
// The simulator is lazy about updating the UI for
|
||||||
|
// performance reasons, so always request an immediate update after stepping manually.
|
||||||
|
uiUpdater.updateUi();
|
||||||
|
} catch (SymonException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Exception during simulator step: " + ex.getMessage());
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleQuit() {
|
||||||
|
// TODO: Clean up and exit properly
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stop() {
|
||||||
|
// Allow step while the simulator is stopped
|
||||||
|
simulator.requestStop();
|
||||||
|
if (simulatorThread != null) {
|
||||||
|
simulatorThread.interrupt();
|
||||||
|
simulatorThread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start() {
|
||||||
|
simulatorThread = new Thread(simulator);
|
||||||
|
simulatorThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Console getConsole() {
|
||||||
|
return this.console;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusPane getStatusPane() {
|
||||||
|
return statusPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JButton getRunButton() {
|
||||||
|
return runButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JButton getLoadButton() {
|
||||||
|
return loadButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JButton getStepButton() {
|
||||||
|
return stepButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
try {
|
||||||
|
// Create the simulated system
|
||||||
|
Simulator simulator = new Simulator();
|
||||||
|
|
||||||
|
// Create the main UI window
|
||||||
|
MainWindow app = new MainWindow(simulator);
|
||||||
|
|
||||||
|
// Pack and display main window
|
||||||
|
app.pack();
|
||||||
|
app.setVisible(true);
|
||||||
|
|
||||||
|
// Reset the simulator.
|
||||||
|
simulator.reset();
|
||||||
|
} catch (MemoryAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (MemoryRangeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,490 +1,157 @@
|
|||||||
package com.loomcom.symon;
|
package com.loomcom.symon;
|
||||||
|
|
||||||
import java.io.*;
|
import java.util.logging.*;
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
import com.loomcom.symon.devices.*;
|
import com.loomcom.symon.devices.*;
|
||||||
import com.loomcom.symon.exceptions.*;
|
import com.loomcom.symon.exceptions.*;
|
||||||
|
import com.loomcom.symon.ui.UiUpdater;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main control class for the J6502 Simulator.
|
* Main entry point and control for the Symon Simulator.
|
||||||
|
* This class is responsible for creating the UI and starting
|
||||||
|
* the IO threads that pass data to and from the simulated
|
||||||
|
* Bus and Cpu.
|
||||||
*/
|
*/
|
||||||
public class Simulator {
|
public class Simulator implements Runnable {
|
||||||
|
|
||||||
/**
|
private final static Logger logger = Logger.getLogger(Simulator.class.getName());
|
||||||
* The CPU itself.
|
|
||||||
*/
|
|
||||||
private Cpu cpu;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Bus responsible for routing memory read/write requests to the
|
|
||||||
* correct IO devices.
|
|
||||||
*/
|
|
||||||
private Bus bus;
|
private Bus bus;
|
||||||
|
private Cpu cpu;
|
||||||
/**
|
|
||||||
* The ACIA, used for charater in/out.
|
|
||||||
*
|
|
||||||
* By default, the simulator uses base address c000 for the ACIA.
|
|
||||||
*/
|
|
||||||
private Acia acia;
|
private Acia acia;
|
||||||
|
private Memory ram;
|
||||||
|
private Memory rom;
|
||||||
|
private boolean isRunning = false;
|
||||||
|
|
||||||
private BufferedReader in;
|
private int updatesRequested = 0;
|
||||||
private BufferedWriter out;
|
|
||||||
|
|
||||||
/* If true, trace execution of the CPU */
|
private UiUpdater uiUpdater;
|
||||||
private boolean trace = false;
|
|
||||||
private int nextExamineAddress = 0;
|
|
||||||
|
|
||||||
private static final int BUS_BOTTOM = 0x0000;
|
private static final int BUS_BOTTOM = 0x0000;
|
||||||
private static final int BUS_TOP = 0xffff;
|
private static final int BUS_TOP = 0xffff;
|
||||||
|
|
||||||
private static final int ACIA_BASE = 0xc000;
|
|
||||||
|
|
||||||
private static final int MEMORY_BASE = 0x0000;
|
private static final int MEMORY_BASE = 0x0000;
|
||||||
private static final int MEMORY_SIZE = 0xc000; // 48 KB
|
private static final int MEMORY_SIZE = 0xc000; // 48 KB
|
||||||
|
|
||||||
private static final int ROM_BASE = 0xe000;
|
private static final int ROM_BASE = 0xe000;
|
||||||
private static final int ROM_SIZE = 0x2000; // 8 KB
|
private static final int ROM_SIZE = 0x2000; // 8 KB
|
||||||
|
|
||||||
|
public static final int ACIA_BASE = 0xc000;
|
||||||
|
|
||||||
|
private static final int MAX_REQUESTS_BETWEEN_UPDATES = 25000;
|
||||||
|
|
||||||
public Simulator() throws MemoryRangeException {
|
public Simulator() throws MemoryRangeException {
|
||||||
|
this.acia = new Acia(ACIA_BASE);
|
||||||
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
||||||
this.cpu = new Cpu();
|
this.cpu = new Cpu();
|
||||||
this.acia = new Acia(ACIA_BASE);
|
this.ram = new Memory(MEMORY_BASE, MEMORY_SIZE, false);
|
||||||
|
// TODO: Load this ROM from a file, naturally!
|
||||||
|
this.rom = new Memory(ROM_BASE, ROM_SIZE, false);
|
||||||
bus.addCpu(cpu);
|
bus.addCpu(cpu);
|
||||||
bus.addDevice(new Memory(MEMORY_BASE, MEMORY_SIZE, false));
|
|
||||||
bus.addDevice(acia);
|
bus.addDevice(acia);
|
||||||
// TODO: This should be read-only memory. Add a method
|
bus.addDevice(ram);
|
||||||
// to allow one-time initialization of ROM with a loaded
|
bus.addDevice(rom);
|
||||||
// ROM binary file.
|
|
||||||
bus.addDevice(new Memory(ROM_BASE, ROM_SIZE, false));
|
|
||||||
|
|
||||||
this.in = new BufferedReader(new InputStreamReader(System.in));
|
|
||||||
this.out = new BufferedWriter(new OutputStreamWriter(System.out));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() throws MemoryAccessException, FifoUnderrunException {
|
public void loadProgram(byte[] program, int startAddress) throws MemoryAccessException {
|
||||||
try {
|
cpu.setResetVector(startAddress);
|
||||||
greeting();
|
|
||||||
prompt();
|
int addr = startAddress, i;
|
||||||
String command = null;
|
for (i = 0; i < program.length; i++) {
|
||||||
while (!shouldQuit(command = readLine())) {
|
bus.write(addr++, program[i] & 0xff);
|
||||||
try {
|
|
||||||
dispatch(command);
|
|
||||||
} catch (CommandFormatException ex) {
|
|
||||||
writeLine(ex.getMessage());
|
|
||||||
}
|
|
||||||
prompt();
|
|
||||||
}
|
|
||||||
writeLine("\nGoodbye!");
|
|
||||||
} catch (IOException ex) {
|
|
||||||
System.err.println("Error: " + ex.toString());
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
logger.log(Level.INFO, "Loaded " + i + " bytes at address 0x" +
|
||||||
* Dispatch the command.
|
Integer.toString(startAddress, 16));
|
||||||
*/
|
|
||||||
public void dispatch(String commandLine) throws MemoryAccessException,
|
|
||||||
IOException,
|
|
||||||
CommandFormatException,
|
|
||||||
FifoUnderrunException {
|
|
||||||
Command c = new Command(commandLine);
|
|
||||||
String cmd = c.getCommand();
|
|
||||||
if (cmd != null) {
|
|
||||||
if (cmd.startsWith("ste")) {
|
|
||||||
doStep(c);
|
|
||||||
} else if (cmd.startsWith("sta")) {
|
|
||||||
doGetState();
|
|
||||||
} else if (cmd.startsWith("se")) {
|
|
||||||
doSet(c);
|
|
||||||
} else if (cmd.startsWith("r")) {
|
|
||||||
doReset();
|
|
||||||
} else if (cmd.startsWith("e")) {
|
|
||||||
doExamine(c);
|
|
||||||
} else if (cmd.startsWith("d")) {
|
|
||||||
doDeposit(c);
|
|
||||||
} else if (cmd.startsWith("g")) {
|
|
||||||
doGo(c);
|
|
||||||
} else if (cmd.startsWith("h")) {
|
|
||||||
doHelp(c);
|
|
||||||
} else if (cmd.startsWith("t")) {
|
|
||||||
doToggleTrace();
|
|
||||||
} else if (cmd.startsWith("f")) {
|
|
||||||
doFill(c);
|
|
||||||
} else if (cmd.startsWith("l")) {
|
|
||||||
doLoad(c);
|
|
||||||
} else {
|
|
||||||
writeLine("? Type h for help");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doHelp(Command c) throws IOException {
|
public void setUiUpdater(UiUpdater uiUpdater) {
|
||||||
writeLine("Symon 6502 Simulator");
|
this.uiUpdater = uiUpdater;
|
||||||
writeLine("");
|
|
||||||
writeLine("All addresses must be in hexadecimal.");
|
|
||||||
writeLine("Commands may be short or long (e.g. 'e' or 'ex' or 'examine').");
|
|
||||||
writeLine("Note that 'go' clears the BREAK processor status flag.");
|
|
||||||
writeLine("");
|
|
||||||
writeLine("h Show this help file.");
|
|
||||||
writeLine("e [start] [end] Examine memory at PC, start, or start-end.");
|
|
||||||
writeLine("d <address> <data> Deposit data into address.");
|
|
||||||
writeLine("f <start> <end> <data> Fill memory with data.");
|
|
||||||
writeLine("set {pc,a,x,y} [data] Set register to data value.");
|
|
||||||
writeLine("load <file> <address> Load binary file at address.");
|
|
||||||
writeLine("g [address] [steps] Start running at address, or at PC.");
|
|
||||||
writeLine("step [address] Step once, optionally starting at address.");
|
|
||||||
writeLine("stat Show CPU state.");
|
|
||||||
writeLine("reset Reset simulator.");
|
|
||||||
writeLine("trace Toggle trace.");
|
|
||||||
writeLine("q (or Control-D) Quit.\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doGetState() throws IOException, MemoryAccessException {
|
public boolean isRunning() {
|
||||||
writeLine(cpu.toString());
|
return isRunning;
|
||||||
writeLine("Trace is " + (trace ? "on" : "off"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doLoad(Command c) throws IOException,
|
public void requestStop() {
|
||||||
MemoryAccessException,
|
isRunning = false;
|
||||||
CommandFormatException {
|
|
||||||
if (c.numArgs() != 2) {
|
|
||||||
throw new CommandFormatException("load <file> <address>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
File binFile = new File(c.getArg(0));
|
public void reset() throws MemoryAccessException {
|
||||||
int address = stringToWord(c.getArg(1));
|
|
||||||
|
|
||||||
if (!binFile.exists()) {
|
|
||||||
throw new CommandFormatException("File '" + binFile +
|
|
||||||
"' does not exist.");
|
|
||||||
}
|
|
||||||
writeLine("Loading file '" + binFile + "' at address " +
|
|
||||||
String.format("%04x", address) + "...");
|
|
||||||
|
|
||||||
int bytesLoaded = 0;
|
|
||||||
|
|
||||||
FileInputStream fis = new FileInputStream(binFile);
|
|
||||||
|
|
||||||
try {
|
|
||||||
int b = 0;
|
|
||||||
while ((b = fis.read()) > -1 && address <= bus.endAddress()) {
|
|
||||||
bus.write(address++, b);
|
|
||||||
bytesLoaded++;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
fis.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLine("Loaded " + bytesLoaded + " (" +
|
|
||||||
String.format("$%04x", bytesLoaded) + ") bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doSet(Command c) throws MemoryAccessException,
|
|
||||||
CommandFormatException {
|
|
||||||
if (c.numArgs() != 2) {
|
|
||||||
throw new CommandFormatException("set {a, x, y, pc} <value>");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
String reg = c.getArg(0).toLowerCase();
|
|
||||||
String data = c.getArg(1);
|
|
||||||
if ("a".equals(reg)) {
|
|
||||||
cpu.setAccumulator(stringToByte(data));
|
|
||||||
} else if ("x".equals(reg)) {
|
|
||||||
cpu.setXRegister(stringToByte(data));
|
|
||||||
} else if ("y".equals(reg)) {
|
|
||||||
cpu.setYRegister(stringToByte(data));
|
|
||||||
} else if ("pc".equals(reg)) {
|
|
||||||
cpu.setProgramCounter(stringToWord(data));
|
|
||||||
} else {
|
|
||||||
throw new CommandFormatException("set {a, x, y, pc} <value>");
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new CommandFormatException("Illegal address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doExamine(Command c) throws IOException,
|
|
||||||
MemoryAccessException,
|
|
||||||
CommandFormatException {
|
|
||||||
try {
|
|
||||||
if (c.numArgs() == 2) {
|
|
||||||
int startAddress = stringToWord(c.getArgs()[0]);
|
|
||||||
int endAddress = stringToWord(c.getArgs()[1]);
|
|
||||||
while (startAddress < endAddress) {
|
|
||||||
StringBuffer line = new StringBuffer();
|
|
||||||
int numBytes = 0;
|
|
||||||
line.append(String.format("%04x ", startAddress));
|
|
||||||
while (numBytes++ < 16 && startAddress <= endAddress) {
|
|
||||||
line.append(String.format("%02x ", bus.read(startAddress++)));
|
|
||||||
if (numBytes % 8 == 0) {
|
|
||||||
line.append(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeLine(line.toString());
|
|
||||||
}
|
|
||||||
nextExamineAddress = endAddress + 1;
|
|
||||||
} else if (c.numArgs() == 1) {
|
|
||||||
int address = stringToWord(c.getArgs()[0]);
|
|
||||||
writeLine(String.format("%04x %02x", address, bus.read(address)));
|
|
||||||
nextExamineAddress = address + 1;
|
|
||||||
} else if (c.numArgs() == 0) {
|
|
||||||
writeLine(String.format("%04x %02x", nextExamineAddress,
|
|
||||||
bus.read(nextExamineAddress)));
|
|
||||||
nextExamineAddress++;
|
|
||||||
} else {
|
|
||||||
throw new CommandFormatException("e [start [end]]");
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new CommandFormatException("Illegal Address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doDeposit(Command c) throws MemoryAccessException,
|
|
||||||
CommandFormatException {
|
|
||||||
if (c.numArgs() != 2) {
|
|
||||||
throw new CommandFormatException("d [address] [data]");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
int address = stringToWord(c.getArg(0));
|
|
||||||
int data = stringToByte(c.getArg(1));
|
|
||||||
bus.write(address, data);
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new CommandFormatException("Illegal Address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doFill(Command c) throws MemoryAccessException,
|
|
||||||
CommandFormatException {
|
|
||||||
if (c.numArgs() != 3) {
|
|
||||||
throw new CommandFormatException("f [start] [end] [data]");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
int start = stringToWord(c.getArg(0));
|
|
||||||
int end = stringToWord(c.getArg(1));
|
|
||||||
int data = stringToByte(c.getArg(2));
|
|
||||||
while (start <= end) {
|
|
||||||
bus.write(start, data);
|
|
||||||
start++;
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new CommandFormatException("Illegal Address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doStep(Command c) throws IOException,
|
|
||||||
MemoryAccessException,
|
|
||||||
FifoUnderrunException,
|
|
||||||
CommandFormatException {
|
|
||||||
try {
|
|
||||||
if (c.numArgs() > 0) {
|
|
||||||
cpu.setProgramCounter(stringToWord(c.getArg(1)));
|
|
||||||
}
|
|
||||||
cpu.step();
|
|
||||||
writeLine(cpu.toString()); // Always show status after stepping
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new CommandFormatException("Illegal Address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doGo(Command c) throws IOException,
|
|
||||||
MemoryAccessException,
|
|
||||||
FifoUnderrunException,
|
|
||||||
CommandFormatException {
|
|
||||||
int readChar;
|
|
||||||
int stepCount = 0;
|
|
||||||
|
|
||||||
if (c.numArgs() > 2) {
|
|
||||||
throw new CommandFormatException("g [address] [steps]");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
int start = 0;
|
|
||||||
int steps = -1;
|
|
||||||
|
|
||||||
if (c.numArgs() > 0) {
|
|
||||||
start = stringToWord(c.getArg(0));
|
|
||||||
} else {
|
|
||||||
start = cpu.getProgramCounter();
|
|
||||||
}
|
|
||||||
if (c.numArgs() == 2) {
|
|
||||||
steps = stringToWord(c.getArg(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a gross assumption: Restarting the CPU clears
|
|
||||||
// the break flag and the IRQ disable flag.
|
|
||||||
cpu.clearBreakFlag();
|
|
||||||
cpu.clearIrqDisableFlag();
|
|
||||||
|
|
||||||
cpu.setProgramCounter(start);
|
|
||||||
outer:
|
|
||||||
while (!cpu.getBreakFlag() && (steps == -1 || steps-- > 0)) {
|
|
||||||
cpu.step();
|
|
||||||
if (trace) {
|
|
||||||
writeLine(cpu.toString());
|
|
||||||
}
|
|
||||||
// Wake up and scan keyboard every 500 steps
|
|
||||||
if (stepCount++ >= 500) {
|
|
||||||
// Reset step count
|
|
||||||
stepCount = 0;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Do output if available.
|
|
||||||
//
|
|
||||||
while (acia.hasTxChar()) {
|
|
||||||
out.write(acia.txRead());
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Consume input if available.
|
|
||||||
//
|
|
||||||
// NOTE: On UNIX systems, System.in.available() returns 0
|
|
||||||
// until Enter is pressed. So to interrupt we must ALWAYS
|
|
||||||
// type "^E<enter>". Sucks hard. But such is life.
|
|
||||||
if (System.in.available() > 0) {
|
|
||||||
while ((readChar = in.read()) > -1) {
|
|
||||||
// Keep consuming unless ^E is found.
|
|
||||||
//
|
|
||||||
// TODO: This will probably lead to a lot of spurious keyboard
|
|
||||||
// entry. Gotta keep an eye on that.
|
|
||||||
//
|
|
||||||
if (readChar == 0x05) {
|
|
||||||
break outer;
|
|
||||||
} else {
|
|
||||||
// Buffer keyboard input into the simulated ACIA's
|
|
||||||
// read buffer.
|
|
||||||
acia.rxWrite(readChar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!trace) {
|
|
||||||
writeLine(cpu.toString());
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new CommandFormatException("Illegal Address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doToggleTrace() throws IOException {
|
|
||||||
this.trace = !trace;
|
|
||||||
writeLine("Trace is now " + (trace ? "on" : "off"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doReset() throws MemoryAccessException {
|
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
this.trace = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void clearMemory() {
|
||||||
* Main simulator routine.
|
ram.fill(0x00);
|
||||||
*/
|
}
|
||||||
public static void main(String[] args) throws MemoryAccessException,
|
|
||||||
FifoUnderrunException {
|
public int getProcessorStatus() {
|
||||||
|
return cpu.getProcessorStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cpu getCpu() {
|
||||||
|
return this.cpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long memorySize() {
|
||||||
|
return MEMORY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
logger.log(Level.INFO, "Entering 'run' on main Simulator thread");
|
||||||
|
isRunning = true;
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
uiUpdater.simulatorDidStart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new Simulator().run();
|
while (isRunning && !cpu.getBreakFlag()) {
|
||||||
} catch (MemoryRangeException ex) {
|
step();
|
||||||
System.err.println("Error: " + ex.toString());
|
|
||||||
}
|
}
|
||||||
|
} catch (SymonException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Exception in main simulator run thread. Exiting run.");
|
||||||
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************
|
logger.log(Level.INFO, "Exiting 'run'. BREAK=" + cpu.getBreakBit() + "; RUN_FLAG=" + isRunning);
|
||||||
* Private
|
isRunning = false;
|
||||||
*******************************************************************/
|
|
||||||
|
|
||||||
public void load(int address, int[] data)
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
throws MemoryAccessException {
|
public void run() {
|
||||||
int i = 0;
|
uiUpdater.simulatorDidStop();
|
||||||
for (int d : data) {
|
|
||||||
bus.write(address + i++, d);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int stringToWord(String addrString) {
|
|
||||||
return Integer.parseInt(addrString, 16) & 0xffff;
|
public void step() throws MemoryAccessException, FifoUnderrunException {
|
||||||
|
|
||||||
|
cpu.step();
|
||||||
|
|
||||||
|
// TODO: ACIA interrupt handling. For now, poll ACIA on each step.
|
||||||
|
|
||||||
|
// Read from the ACIA and add to the UiUpdater buffer
|
||||||
|
while (acia.hasTxChar()) {
|
||||||
|
uiUpdater.consoleWrite(acia.txRead());
|
||||||
}
|
}
|
||||||
|
|
||||||
private int stringToByte(String dataString) {
|
// This is a very expensive update, and we're doing it without
|
||||||
return Integer.parseInt(dataString, 16) & 0xff;
|
// a delay, so we don't want to overwhelm the Swing event processing thread
|
||||||
|
// with requests. Limit the number of ui updates that can be performed.
|
||||||
|
if (updatesRequested++ > MAX_REQUESTS_BETWEEN_UPDATES) {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
uiUpdater.updateUi();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
private void greeting() throws IOException {
|
updatesRequested = 0;
|
||||||
writeLine("Welcome to the Symon 6502 Simulator. Type 'h' for help.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prompt() throws IOException {
|
|
||||||
out.write("symon> ");
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readLine() throws IOException {
|
|
||||||
String line = in.readLine();
|
|
||||||
if (line == null) { return null; }
|
|
||||||
return line.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeLine(String line) throws IOException {
|
|
||||||
out.write(line);
|
|
||||||
out.newLine();
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the line is a quit.
|
|
||||||
*/
|
|
||||||
private boolean shouldQuit(String line) {
|
|
||||||
return (line == null || "q".equals(line.toLowerCase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Command line tokenizer class. Given a command line, tokenize
|
|
||||||
* it and give easy access to the command and its arguments.
|
|
||||||
*/
|
|
||||||
public static final class Command {
|
|
||||||
private String command;
|
|
||||||
private String[] args;
|
|
||||||
|
|
||||||
public Command(String commandLine) {
|
|
||||||
StringTokenizer st = new StringTokenizer(commandLine);
|
|
||||||
int numTokens = st.countTokens();
|
|
||||||
int idx = 0;
|
|
||||||
args = new String[numTokens > 1 ? numTokens - 1 : 0];
|
|
||||||
while (st.hasMoreTokens()) {
|
|
||||||
if (command == null) {
|
|
||||||
command = st.nextToken();
|
|
||||||
} else {
|
|
||||||
args[idx++] = st.nextToken();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCommand() {
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getArgs() {
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getArg(int argNum) {
|
|
||||||
if (argNum > args.length - 1) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return args[argNum];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int numArgs() {
|
|
||||||
return args.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasArgs() {
|
|
||||||
return args.length > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@ public class Memory extends Device {
|
|||||||
super(address, size, (readOnly ? "RO Memory" : "RW Memory"));
|
super(address, size, (readOnly ? "RO Memory" : "RW Memory"));
|
||||||
this.readOnly = readOnly;
|
this.readOnly = readOnly;
|
||||||
this.mem = new int[size];
|
this.mem = new int[size];
|
||||||
Arrays.fill(this.mem, DEFAULT_FILL);
|
this.fill(DEFAULT_FILL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Memory(int address, int size) throws MemoryRangeException {
|
public Memory(int address, int size) throws MemoryRangeException {
|
||||||
@@ -36,6 +36,11 @@ public class Memory extends Device {
|
|||||||
return this.mem[address];
|
return this.mem[address];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void fill(int val) {
|
||||||
|
Arrays.fill(this.mem, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Memory: " + getMemoryRange().toString();
|
return "Memory: " + getMemoryRange().toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.loomcom.symon.exceptions;
|
package com.loomcom.symon.exceptions;
|
||||||
|
|
||||||
public class CommandFormatException extends Exception {
|
public class CommandFormatException extends SymonException {
|
||||||
public CommandFormatException(String msg) {
|
public CommandFormatException(String msg) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.loomcom.symon.exceptions;
|
package com.loomcom.symon.exceptions;
|
||||||
|
|
||||||
public class FifoUnderrunException extends Exception {
|
public class FifoUnderrunException extends SymonException {
|
||||||
public FifoUnderrunException(String msg) {
|
public FifoUnderrunException(String msg) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package com.loomcom.symon.exceptions;
|
|||||||
* Exception that will be thrown if access to memory or IO cannot be
|
* Exception that will be thrown if access to memory or IO cannot be
|
||||||
* accessed.
|
* accessed.
|
||||||
*/
|
*/
|
||||||
public class MemoryAccessException extends Exception {
|
public class MemoryAccessException extends SymonException {
|
||||||
public MemoryAccessException(String msg) {
|
public MemoryAccessException(String msg) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.loomcom.symon.exceptions;
|
|||||||
/**
|
/**
|
||||||
* Exception that will be thrown if devices conflict in the IO map.
|
* Exception that will be thrown if devices conflict in the IO map.
|
||||||
*/
|
*/
|
||||||
public class MemoryRangeException extends Exception {
|
public class MemoryRangeException extends SymonException {
|
||||||
public MemoryRangeException(String msg) {
|
public MemoryRangeException(String msg) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.loomcom.symon.exceptions;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Superclass for all symon Exceptions.
|
||||||
|
*/
|
||||||
|
public class SymonException extends Exception {
|
||||||
|
public SymonException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
public SymonException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.KeyListener;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseListener;
|
||||||
|
|
||||||
|
import com.grahamedgecombe.jterminal.JTerminal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Console is a simulated 80 column x 24 row VT-100 terminal attached to
|
||||||
|
* the ACIA of the system. It provides basic keyboard I/O to Symon.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||||
|
|
||||||
|
// TODO: Pass in io threads, read and write to ACIA.
|
||||||
|
public Console() {
|
||||||
|
super();
|
||||||
|
addKeyListener(this);
|
||||||
|
addMouseListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
getModel().clear();
|
||||||
|
getModel().setCursorColumn(0);
|
||||||
|
getModel().setCursorRow(0);
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void keyTyped(KeyEvent keyEvent) {
|
||||||
|
keyEvent.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void keyPressed(KeyEvent keyEvent) {
|
||||||
|
int keyCode = keyEvent.getKeyCode();
|
||||||
|
int modifiersMask = keyEvent.getModifiers();
|
||||||
|
int modifiersExMask = keyEvent.getModifiersEx();
|
||||||
|
|
||||||
|
System.out.println("Key Pressed #" + keyEvent.getKeyCode() + " : " +
|
||||||
|
KeyEvent.getKeyText(keyCode) + " MASK : " +
|
||||||
|
modifiersMask + " EXT MASK : " +
|
||||||
|
modifiersExMask
|
||||||
|
);
|
||||||
|
|
||||||
|
keyEvent.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void keyReleased(KeyEvent keyEvent) {
|
||||||
|
keyEvent.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseClicked(MouseEvent mouseEvent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mousePressed(MouseEvent mouseEvent) {
|
||||||
|
requestFocus();
|
||||||
|
mouseEvent.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseReleased(MouseEvent mouseEvent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseEntered(MouseEvent mouseEvent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseExited(MouseEvent mouseEvent) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import com.loomcom.symon.Cpu;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class StatusPane extends JPanel {
|
||||||
|
|
||||||
|
// The CPU to ask for state information.
|
||||||
|
private final Cpu cpu;
|
||||||
|
|
||||||
|
private final ImageIcon carryOn;
|
||||||
|
private final ImageIcon carryOff;
|
||||||
|
private final ImageIcon zeroOn;
|
||||||
|
private final ImageIcon zeroOff;
|
||||||
|
private final ImageIcon irqOn;
|
||||||
|
private final ImageIcon irqOff;
|
||||||
|
private final ImageIcon decimalOn;
|
||||||
|
private final ImageIcon decimalOff;
|
||||||
|
private final ImageIcon breakOn;
|
||||||
|
private final ImageIcon breakOff;
|
||||||
|
private final ImageIcon overflowOn;
|
||||||
|
private final ImageIcon overflowOff;
|
||||||
|
private final ImageIcon negativeOn;
|
||||||
|
private final ImageIcon negativeOff;
|
||||||
|
|
||||||
|
private final JLabel carryFlagLabel;
|
||||||
|
private final JLabel zeroFlagLabel;
|
||||||
|
private final JLabel irqDisableFlagLabel;
|
||||||
|
private final JLabel decimalModeFlagLabel;
|
||||||
|
private final JLabel breakFlagLabel;
|
||||||
|
private final JLabel overflowFlagLabel;
|
||||||
|
private final JLabel negativeFlagLabel;
|
||||||
|
|
||||||
|
private final JLabel opcodeLabel;
|
||||||
|
private final JLabel pcLabel;
|
||||||
|
private final JLabel aLabel;
|
||||||
|
private final JLabel xLabel;
|
||||||
|
private final JLabel yLabel;
|
||||||
|
private final JLabel stepCountLabel;
|
||||||
|
|
||||||
|
private static final int WIDTH = 134;
|
||||||
|
private static final int HEIGHT = 27;
|
||||||
|
|
||||||
|
public StatusPane(Cpu cpu) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.cpu = cpu;
|
||||||
|
|
||||||
|
Dimension dimensions = new Dimension(WIDTH, HEIGHT);
|
||||||
|
|
||||||
|
setMinimumSize(dimensions);
|
||||||
|
setPreferredSize(dimensions);
|
||||||
|
setMaximumSize(dimensions);
|
||||||
|
|
||||||
|
carryOn = new ImageIcon(this.getClass().getResource("images/C_on.png"));
|
||||||
|
carryOff = new ImageIcon(this.getClass().getResource("images/C_off.png"));
|
||||||
|
zeroOn = new ImageIcon(this.getClass().getResource("images/Z_on.png"));
|
||||||
|
zeroOff = new ImageIcon(this.getClass().getResource("images/Z_off.png"));
|
||||||
|
irqOn = new ImageIcon(this.getClass().getResource("images/I_on.png"));
|
||||||
|
irqOff = new ImageIcon(this.getClass().getResource("images/I_off.png"));
|
||||||
|
decimalOn = new ImageIcon(this.getClass().getResource("images/D_on.png"));
|
||||||
|
decimalOff = new ImageIcon(this.getClass().getResource("images/D_off.png"));
|
||||||
|
breakOn = new ImageIcon(this.getClass().getResource("images/B_on.png"));
|
||||||
|
breakOff = new ImageIcon(this.getClass().getResource("images/B_off.png"));
|
||||||
|
overflowOn = new ImageIcon(this.getClass().getResource("images/O_on.png"));
|
||||||
|
overflowOff = new ImageIcon(this.getClass().getResource("images/O_off.png"));
|
||||||
|
negativeOn = new ImageIcon(this.getClass().getResource("images/N_on.png"));
|
||||||
|
negativeOff = new ImageIcon(this.getClass().getResource("images/N_off.png"));
|
||||||
|
|
||||||
|
// Initialize all to off
|
||||||
|
carryFlagLabel = new JLabel(carryOff, JLabel.CENTER);
|
||||||
|
zeroFlagLabel = new JLabel(zeroOff, JLabel.CENTER);
|
||||||
|
irqDisableFlagLabel = new JLabel(irqOff, JLabel.CENTER);
|
||||||
|
decimalModeFlagLabel = new JLabel(decimalOff, JLabel.CENTER);
|
||||||
|
breakFlagLabel = new JLabel(breakOff, JLabel.CENTER);
|
||||||
|
overflowFlagLabel = new JLabel(overflowOff, JLabel.CENTER);
|
||||||
|
negativeFlagLabel = new JLabel(negativeOff, JLabel.CENTER);
|
||||||
|
|
||||||
|
// Create and add register and address labels
|
||||||
|
|
||||||
|
this.opcodeLabel = new JLabel();
|
||||||
|
this.pcLabel = new JLabel();
|
||||||
|
this.aLabel = new JLabel();
|
||||||
|
this.xLabel = new JLabel();
|
||||||
|
this.yLabel = new JLabel();
|
||||||
|
this.stepCountLabel = new JLabel();
|
||||||
|
|
||||||
|
this.opcodeLabel.setMinimumSize(new Dimension(100, 20));
|
||||||
|
this.pcLabel.setMinimumSize(new Dimension(80, 20));
|
||||||
|
this.aLabel.setMinimumSize(new Dimension(60, 20));
|
||||||
|
this.xLabel.setMinimumSize(new Dimension(60, 20));
|
||||||
|
this.yLabel.setMinimumSize(new Dimension(60, 20));
|
||||||
|
this.stepCountLabel.setMinimumSize(new Dimension(120, 20));
|
||||||
|
|
||||||
|
this.opcodeLabel.setPreferredSize(new Dimension(100, 20));
|
||||||
|
this.pcLabel.setPreferredSize(new Dimension(80, 20));
|
||||||
|
this.aLabel.setPreferredSize(new Dimension(60, 20));
|
||||||
|
this.xLabel.setPreferredSize(new Dimension(60, 20));
|
||||||
|
this.yLabel.setPreferredSize(new Dimension(60, 20));
|
||||||
|
this.stepCountLabel.setPreferredSize(new Dimension(120, 20));
|
||||||
|
|
||||||
|
this.setLayout(new FlowLayout());
|
||||||
|
|
||||||
|
this.add(negativeFlagLabel);
|
||||||
|
this.add(overflowFlagLabel);
|
||||||
|
this.add(breakFlagLabel);
|
||||||
|
this.add(decimalModeFlagLabel);
|
||||||
|
this.add(irqDisableFlagLabel);
|
||||||
|
this.add(zeroFlagLabel);
|
||||||
|
this.add(carryFlagLabel);
|
||||||
|
|
||||||
|
this.add(opcodeLabel);
|
||||||
|
this.add(pcLabel);
|
||||||
|
this.add(aLabel);
|
||||||
|
this.add(xLabel);
|
||||||
|
this.add(yLabel);
|
||||||
|
this.add(stepCountLabel);
|
||||||
|
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateState() {
|
||||||
|
// Update the Processor Status Flag display
|
||||||
|
int state = this.cpu.getProcessorStatus();
|
||||||
|
|
||||||
|
carryFlagLabel.setIcon(iconForFlag(state, 0));
|
||||||
|
zeroFlagLabel.setIcon(iconForFlag(state, 1));
|
||||||
|
irqDisableFlagLabel.setIcon(iconForFlag(state, 2));
|
||||||
|
decimalModeFlagLabel.setIcon(iconForFlag(state, 3));
|
||||||
|
breakFlagLabel.setIcon(iconForFlag(state, 4));
|
||||||
|
overflowFlagLabel.setIcon(iconForFlag(state, 6));
|
||||||
|
negativeFlagLabel.setIcon(iconForFlag(state, 7));
|
||||||
|
|
||||||
|
// Update the register and address displays
|
||||||
|
opcodeLabel.setText(cpu.getOpcodeStatus());
|
||||||
|
pcLabel.setText(cpu.getProgramCounterStatus());
|
||||||
|
aLabel.setText(cpu.getARegisterStatus());
|
||||||
|
xLabel.setText(cpu.getXRegisterStatus());
|
||||||
|
yLabel.setText(cpu.getYRegisterStatus());
|
||||||
|
stepCountLabel.setText(Long.toString(cpu.getStepCounter()));
|
||||||
|
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageIcon iconForFlag(int state, int flagIndex) {
|
||||||
|
ImageIcon imageIcon = null;
|
||||||
|
|
||||||
|
if ((((state & 0xff) >> flagIndex) & 0x01) == 1) {
|
||||||
|
switch (flagIndex) {
|
||||||
|
case 0:
|
||||||
|
imageIcon = carryOn;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
imageIcon = zeroOn;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
imageIcon = irqOn;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
imageIcon = decimalOn;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
imageIcon = breakOn;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
imageIcon = overflowOn;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
imageIcon = negativeOn;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (flagIndex) {
|
||||||
|
case 0:
|
||||||
|
imageIcon = carryOff;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
imageIcon = zeroOff;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
imageIcon = irqOff;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
imageIcon = decimalOff;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
imageIcon = breakOff;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
imageIcon = overflowOff;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
imageIcon = negativeOff;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import com.loomcom.symon.MainWindow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the console and status display. All direct manipulation of the
|
||||||
|
* main UI should go through this class. When not called from the Swing
|
||||||
|
* event dispatch thread, these methods should be called using
|
||||||
|
* SwingUtilities.invokeLater.
|
||||||
|
*/
|
||||||
|
public class UiUpdater {
|
||||||
|
private final Console console;
|
||||||
|
private final MainWindow mainWindow;
|
||||||
|
private final StatusPane statusPane;
|
||||||
|
|
||||||
|
private final StringBuffer data;
|
||||||
|
|
||||||
|
public UiUpdater(MainWindow mainWindow) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
this.console = mainWindow.getConsole();
|
||||||
|
this.statusPane = mainWindow.getStatusPane();
|
||||||
|
this.data = new StringBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void consoleWrite(int i) {
|
||||||
|
data.append((char) i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called by the simulator before exiting its run method.
|
||||||
|
*/
|
||||||
|
public void simulatorDidStop() {
|
||||||
|
mainWindow.getStepButton().setEnabled(true);
|
||||||
|
mainWindow.getLoadButton().setEnabled(true);
|
||||||
|
mainWindow.getRunButton().setText("Run");
|
||||||
|
updateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called by the simulator when entering its run method.
|
||||||
|
*/
|
||||||
|
public void simulatorDidStart() {
|
||||||
|
// Don't allow step while the simulator is running
|
||||||
|
mainWindow.getStepButton().setEnabled(false);
|
||||||
|
mainWindow.getLoadButton().setEnabled(false);
|
||||||
|
// Toggle the state of the run button
|
||||||
|
mainWindow.getRunButton().setText("Stop");
|
||||||
|
updateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateUi() {
|
||||||
|
// Update the console with any text
|
||||||
|
if (data.length() > 0) {
|
||||||
|
console.getModel().print(data.toString());
|
||||||
|
console.repaint();
|
||||||
|
// Clear the buffer
|
||||||
|
data.delete(0, data.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the status UI.
|
||||||
|
statusPane.updateState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 191 B |
|
After Width: | Height: | Size: 191 B |
|
After Width: | Height: | Size: 196 B |
|
After Width: | Height: | Size: 196 B |
|
After Width: | Height: | Size: 192 B |
|
After Width: | Height: | Size: 192 B |
|
After Width: | Height: | Size: 183 B |
|
After Width: | Height: | Size: 183 B |
|
After Width: | Height: | Size: 198 B |
|
After Width: | Height: | Size: 198 B |
|
After Width: | Height: | Size: 192 B |
|
After Width: | Height: | Size: 192 B |
|
After Width: | Height: | Size: 198 B |
|
After Width: | Height: | Size: 198 B |
@@ -1,64 +0,0 @@
|
|||||||
package com.loomcom.symon;
|
|
||||||
|
|
||||||
import org.junit.*;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class CommandTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommandShouldParseCorrectNumberOfArguments() {
|
|
||||||
Simulator.Command c;
|
|
||||||
|
|
||||||
c = new Simulator.Command("foo");
|
|
||||||
assertEquals("foo", c.getCommand());
|
|
||||||
assertEquals(0, c.numArgs());
|
|
||||||
|
|
||||||
c = new Simulator.Command("foo bar");
|
|
||||||
assertEquals("foo", c.getCommand());
|
|
||||||
assertEquals(1, c.numArgs());
|
|
||||||
assertEquals("bar", c.getArgs()[0]);
|
|
||||||
|
|
||||||
c = new Simulator.Command("foo bar baz quux 0 100");
|
|
||||||
assertEquals("foo", c.getCommand());
|
|
||||||
assertEquals(5, c.numArgs());
|
|
||||||
assertEquals("bar", c.getArgs()[0]);
|
|
||||||
assertEquals("baz", c.getArgs()[1]);
|
|
||||||
assertEquals("quux", c.getArgs()[2]);
|
|
||||||
assertEquals("0", c.getArgs()[3]);
|
|
||||||
assertEquals("100", c.getArgs()[4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommandShouldIgnoreWhitespaceBetweenTokens() {
|
|
||||||
Simulator.Command c;
|
|
||||||
|
|
||||||
c = new Simulator.Command("foo bar baz");
|
|
||||||
assertEquals("foo", c.getCommand());
|
|
||||||
assertEquals(2, c.numArgs());
|
|
||||||
assertEquals("bar", c.getArgs()[0]);
|
|
||||||
assertEquals("baz", c.getArgs()[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommandShouldIgnoreWhitespaceBeforeCommand() {
|
|
||||||
Simulator.Command c;
|
|
||||||
|
|
||||||
c = new Simulator.Command(" foo bar baz");
|
|
||||||
assertEquals("foo", c.getCommand());
|
|
||||||
assertEquals(2, c.numArgs());
|
|
||||||
assertEquals("bar", c.getArgs()[0]);
|
|
||||||
assertEquals("baz", c.getArgs()[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommandShouldIgnoreWhitespaceAfterCommand() {
|
|
||||||
Simulator.Command c;
|
|
||||||
|
|
||||||
c = new Simulator.Command("foo bar baz ");
|
|
||||||
assertEquals("foo", c.getCommand());
|
|
||||||
assertEquals(2, c.numArgs());
|
|
||||||
assertEquals("bar", c.getArgs()[0]);
|
|
||||||
assertEquals("baz", c.getArgs()[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -393,4 +393,26 @@ public class CpuTest extends TestCase {
|
|||||||
assertEquals(0x15, cpu.zpyAddress(0x80));
|
assertEquals(0x15, cpu.zpyAddress(0x80));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testPcStatus() {
|
||||||
|
cpu.setProgramCounter(0x03fa);
|
||||||
|
assertEquals("$03FA", cpu.getProgramCounterStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testOpcodeStatus() throws MemoryAccessException {
|
||||||
|
// LDA (immediate)
|
||||||
|
bus.write(0x0200, 0xa9);
|
||||||
|
bus.write(0x0201, 0xef);
|
||||||
|
cpu.step();
|
||||||
|
assertEquals("LDA #$EF", cpu.getOpcodeStatus());
|
||||||
|
|
||||||
|
// BRK instruction
|
||||||
|
bus.write(0x0202, 0x00);
|
||||||
|
cpu.step();
|
||||||
|
assertEquals("BRK", cpu.getOpcodeStatus());
|
||||||
|
|
||||||
|
// Illegal opcode
|
||||||
|
cpu.step();
|
||||||
|
assertEquals("BRK", cpu.getOpcodeStatus());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||