diff --git a/pom.xml b/pom.xml
index 201e2ec..9eedcbd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,7 +81,7 @@
- com.loomcom.symon.MainWindow
+ com.loomcom.symon.Simulator
@@ -114,7 +114,7 @@
- com.loomcom.symon.MainWindow
+ com.loomcom.symon.Simulator
com.loomcom.symon
diff --git a/samples/hello.asm b/samples/hello.asm
index d704b00..a1738cc 100644
--- a/samples/hello.asm
+++ b/samples/hello.asm
@@ -3,18 +3,30 @@
;;
-.alias cr $0d
-.alias lf $0a
-
-.alias out $c000
+.alias iobase $c000
+.alias iostatus [iobase + 1]
+.alias iocmd [iobase + 2]
+.alias ioctrl [iobase + 3]
.org $0300
-start: ldx #$00
+start: cli
+ lda #$09
+ sta iocmd ; Set command status
+ lda #$16
+ sta ioctrl ; 0 stop bits, 8 bit word, 300 baud
+ ldx #$00 ; Initialize index
+
loop: lda string,x
beq start ; If A is 0, loop back and start again
- sta out ; Otherwise, store into output
- inx ; Increment X
+
+write: lda iostatus
+ and #$10 ; Load ACIA status. Is output buffer empty?
+ beq write ; If not, loop back and try again,
+ lda string,x
+ sta iobase ; Otherwise, write to output.
+
+ inx
jmp loop ; Repeat.
string: .byte "Hello, 6502 world! ", 0
diff --git a/src/main/java/com/loomcom/symon/Cpu.java b/src/main/java/com/loomcom/symon/Cpu.java
index 107b0d7..ee9ffe0 100644
--- a/src/main/java/com/loomcom/symon/Cpu.java
+++ b/src/main/java/com/loomcom/symon/Cpu.java
@@ -111,6 +111,7 @@ public class Cpu implements InstructionTable {
decimalModeFlag = false;
breakFlag = false;
overflowFlag = false;
+ zeroFlag = false;
// Clear illegal opcode trap.
opTrap = false;
@@ -1375,7 +1376,7 @@ public class Cpu implements InstructionTable {
return (zp + getYRegister()) & 0xff;
}
- void setResetVector(int address) throws MemoryAccessException {
+ public void setResetVector(int address) throws MemoryAccessException {
bus.write(RST_VECTOR_H, (address & 0xff00) >>> 8);
bus.write(RST_VECTOR_L, address & 0x00ff);
}
diff --git a/src/main/java/com/loomcom/symon/MainWindow.java b/src/main/java/com/loomcom/symon/MainWindow.java
deleted file mode 100644
index dc72578..0000000
--- a/src/main/java/com/loomcom/symon/MainWindow.java
+++ /dev/null
@@ -1,247 +0,0 @@
-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();
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/loomcom/symon/Preferences.java b/src/main/java/com/loomcom/symon/Preferences.java
new file mode 100644
index 0000000..d0d547d
--- /dev/null
+++ b/src/main/java/com/loomcom/symon/Preferences.java
@@ -0,0 +1,18 @@
+package com.loomcom.symon;
+
+import javax.swing.*;
+
+public interface Preferences {
+
+ public static final int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300;
+
+ public static final int DEFAULT_ACIA_ADDRESS = 0xc000;
+
+ public JDialog getDialog();
+
+ public int getProgramStartAddress();
+
+ public int getAciaAddress();
+
+ public void updateUi();
+}
diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java
index d145822..b3da9f1 100644
--- a/src/main/java/com/loomcom/symon/Simulator.java
+++ b/src/main/java/com/loomcom/symon/Simulator.java
@@ -1,34 +1,28 @@
package com.loomcom.symon;
-import java.util.logging.*;
-
-import com.loomcom.symon.devices.*;
-import com.loomcom.symon.exceptions.*;
-import com.loomcom.symon.ui.UiUpdater;
+import com.loomcom.symon.devices.Acia;
+import com.loomcom.symon.devices.Memory;
+import com.loomcom.symon.exceptions.MemoryAccessException;
+import com.loomcom.symon.exceptions.MemoryRangeException;
+import com.loomcom.symon.exceptions.SymonException;
+import com.loomcom.symon.ui.PreferencesDialog;
+import com.loomcom.symon.ui.StatusPanel;
+import com.loomcom.symon.ui.Console;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.io.*;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import javax.swing.*;
-/**
- * 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 implements Runnable {
-
- private final static Logger logger = Logger.getLogger(Simulator.class.getName());
-
- private Bus bus;
- private Cpu cpu;
- private Acia acia;
- private Memory ram;
- private Memory rom;
- private boolean isRunning = false;
-
- private int updatesRequested = 0;
-
- private UiUpdater uiUpdater;
+public class Simulator implements ActionListener, Observer {
+ // Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
@@ -40,22 +34,316 @@ public class Simulator implements Runnable {
public static final int ACIA_BASE = 0xc000;
- private static final int MAX_REQUESTS_BETWEEN_UPDATES = 25000;
+ // Since it is very expensive to update the UI with Swing's Event Dispatch Thread, we can't afford
+ // to refresh the view on every simualted clock cycle. Instead, we will only refresh the view after this
+ // number of steps when running normally.
+ private static final int MAX_STEPS_BETWEEN_UPDATES = 15000;
+
+ private final static Logger logger = Logger.getLogger(Simulator.class.getName());
+
+ // The simulated peripherals
+ private final Bus bus;
+ private final Cpu cpu;
+ private final Acia acia;
+ private final Memory ram;
+ private final Memory rom;
+
+ // A counter to keep track of the number of UI updates that have been
+ // requested
+ private int stepsSinceLastUpdate = 0;
+
+ private JFrame mainWindow;
+ private RunLoop runLoop;
+ private Console console;
+ private StatusPanel statusPane;
+
+ private JButton runStopButton;
+ private JButton stepButton;
+ private JButton resetButton;
+
+ // TODO: loadMenuItem seriously violates encapsulation!
+ // A far better solution would be to extend JMenu and add callback
+ // methods to enable and disable menus as required.
+
+ // Menu Items
+ private JMenuItem loadMenuItem;
+
+ private JFileChooser fileChooser;
+ private Preferences preferences;
+
+ private StringBuffer aciaBuffer;
public Simulator() throws MemoryRangeException {
+ this.aciaBuffer = new StringBuffer(512);
this.acia = new Acia(ACIA_BASE);
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
this.ram = new Memory(MEMORY_BASE, MEMORY_SIZE, false);
- // TODO: Load this ROM from a file, naturally!
+
+ // TODO: Load this ROM from a file, of course!
this.rom = new Memory(ROM_BASE, ROM_SIZE, false);
+
bus.addCpu(cpu);
bus.addDevice(acia);
bus.addDevice(ram);
bus.addDevice(rom);
}
- public void loadProgram(byte[] program, int startAddress) throws MemoryAccessException {
+ /**
+ * Display the main simulator UI.
+ */
+ public void createAndShowUi() {
+ mainWindow = new JFrame();
+ mainWindow.setTitle("Symon 6502 Simulator");
+ mainWindow.setResizable(false);
+ mainWindow.getContentPane().setLayout(new BorderLayout());
+
+ // The Menu
+ mainWindow.setJMenuBar(createMenuBar());
+
+ // UI components used for I/O.
+ this.console = new com.loomcom.symon.ui.Console();
+ this.statusPane = new StatusPanel();
+
+ // File Chooser
+ fileChooser = new JFileChooser();
+ preferences = new PreferencesDialog(mainWindow, true);
+
+ // Panel for Console and Buttons
+ JPanel controlsContainer = new JPanel();
+ JPanel buttonContainer = new JPanel();
+ Dimension buttonPanelSize = new Dimension(console.getWidth(), 36);
+
+ buttonContainer.setMinimumSize(buttonPanelSize);
+ buttonContainer.setMaximumSize(buttonPanelSize);
+ buttonContainer.setPreferredSize(buttonPanelSize);
+
+ controlsContainer.setLayout(new BorderLayout());
+ buttonContainer.setLayout(new FlowLayout());
+
+ runStopButton = new JButton("Run");
+ stepButton = new JButton("Step");
+ resetButton = new JButton("Reset");
+
+ buttonContainer.add(runStopButton);
+ buttonContainer.add(stepButton);
+ buttonContainer.add(resetButton);
+
+ // Left side - console and buttons
+ controlsContainer.add(console, BorderLayout.PAGE_START);
+ controlsContainer.add(buttonContainer, BorderLayout.PAGE_END);
+
+ mainWindow.getContentPane().add(controlsContainer, BorderLayout.LINE_START);
+ mainWindow.getContentPane().add(statusPane, BorderLayout.LINE_END);
+
+ runStopButton.addActionListener(this);
+ stepButton.addActionListener(this);
+ resetButton.addActionListener(this);
+
+ mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ console.requestFocus();
+
+ mainWindow.pack();
+ mainWindow.setVisible(true);
+ }
+
+ private JMenuBar createMenuBar() {
+ JMenuBar menuBar = new JMenuBar();
+
+ JMenu fileMenu = new JMenu("File");
+
+ menuBar.add(fileMenu);
+
+ loadMenuItem = new JMenuItem("Load Program");
+ loadMenuItem.setMnemonic(KeyEvent.VK_L);
+
+ JMenuItem prefsItem = new JMenuItem("Preferences...");
+ prefsItem.setMnemonic(KeyEvent.VK_P);
+
+ JMenuItem quitItem = new JMenuItem("Quit");
+ quitItem.setMnemonic(KeyEvent.VK_Q);
+
+ loadMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent actionEvent) {
+ handleProgramLoad();
+ }
+ });
+
+ prefsItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent actionEvent) {
+ showAndUpdatePreferences();
+ }
+ });
+
+ quitItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent actionEvent) {
+ handleQuit();
+ }
+ });
+
+ fileMenu.add(loadMenuItem);
+ fileMenu.add(prefsItem);
+ fileMenu.add(quitItem);
+
+ return menuBar;
+ }
+
+ public void showAndUpdatePreferences() {
+ preferences.getDialog().setVisible(true);
+ }
+
+ /**
+ * Receive an ActionEvent from the UI, and act on it.
+ */
+ public void actionPerformed(ActionEvent actionEvent) {
+ if (actionEvent.getSource() == resetButton) {
+ handleReset();
+ } else if (actionEvent.getSource() == stepButton) {
+ handleStep();
+ } else if (actionEvent.getSource() == runStopButton) {
+ if (runLoop != null && runLoop.isRunning()) {
+ runLoop.requestStop();
+ runLoop.interrupt();
+ runLoop = null;
+ } else {
+ // Spin up the new run loop
+ runLoop = new RunLoop();
+ runLoop.start();
+ }
+ }
+ }
+
+ /**
+ * Display a file chooser prompting the user to load a binary program.
+ * After the user selects a file, read it in starting at PROGRAM_START_ADDRESS.
+ */
+ private void handleProgramLoad() {
+ try {
+ int retVal = fileChooser.showOpenDialog(mainWindow);
+ if (retVal == JFileChooser.APPROVE_OPTION) {
+ File f = fileChooser.getSelectedFile();
+ if (f.canRead()) {
+ long fileSize = f.length();
+
+ if (fileSize > MEMORY_SIZE) {
+ 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();
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ console.reset();
+ }
+ });
+
+ // Now load the program at the starting address.
+ loadProgram(program, preferences.getProgramStartAddress());
+ }
+ }
+ }
+ } 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 (runLoop != null && runLoop.isRunning()) {
+ runLoop.requestStop();
+ runLoop.interrupt();
+ runLoop = null;
+ }
+
+ try {
+ logger.log(Level.INFO, "Reset requested. Resetting CPU and clearing memory.");
+ // Reset and clear memory
+ cpu.reset();
+ ram.fill(0x00);
+ // Clear the console.
+ console.reset();
+ // Update status.
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ updateConsoleAndStatus(cpu);
+ }
+ });
+ } catch (MemoryAccessException ex) {
+ logger.log(Level.SEVERE, "Exception during simulator reset: " + ex.getMessage());
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Step once, and immediately refresh the UI.
+ */
+ private void handleStep() {
+ try {
+ step();
+ // The simulator is lazy about updating the UI for performance reasons, so always request an
+ // immediate update after stepping manually.
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ updateConsoleAndStatus(cpu);
+ }
+ });
+ } catch (SymonException ex) {
+ logger.log(Level.SEVERE, "Exception during simulator step: " + ex.getMessage());
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Handle a request to quit.
+ */
+ private void handleQuit() {
+ if (runLoop != null && runLoop.isRunning()) {
+ runLoop.requestStop();
+ runLoop.interrupt();
+ }
+ System.exit(0);
+ }
+
+ /**
+ * Perform a single step of the simulated system.
+ */
+ private void step() throws MemoryAccessException {
+
+ cpu.step();
+
+ // Read from the ACIA
+ while (acia.hasTxChar()) {
+ aciaBuffer.append((char) acia.txRead());
+ }
+
+ // This is a very expensive update, and we're doing it without
+ // 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 (stepsSinceLastUpdate++ > MAX_STEPS_BETWEEN_UPDATES) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ updateConsoleAndStatus(cpu);
+ }
+ });
+ stepsSinceLastUpdate = 0;
+ }
+
+ }
+
+ /**
+ * Load a program into memory at the simulatorDidStart address.
+ */
+ private void loadProgram(byte[] program, int startAddress) throws MemoryAccessException {
cpu.setResetVector(startAddress);
int addr = startAddress, i;
@@ -65,93 +353,117 @@ public class Simulator implements Runnable {
logger.log(Level.INFO, "Loaded " + i + " bytes at address 0x" +
Integer.toString(startAddress, 16));
- }
- public void setUiUpdater(UiUpdater uiUpdater) {
- this.uiUpdater = uiUpdater;
- }
-
- public boolean isRunning() {
- return isRunning;
- }
-
- public void requestStop() {
- isRunning = false;
- }
-
- public void reset() throws MemoryAccessException {
+ // After loading, be sure to reset and
+ // Reset (but don't clear memory, naturally)
cpu.reset();
- }
-
- public void clearMemory() {
- ram.fill(0x00);
- }
-
- 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;
+ // Immediately update the UI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
- uiUpdater.simulatorDidStart();
+ updateConsoleAndStatus(cpu);
}
});
-
- try {
- while (isRunning && !cpu.getBreakFlag()) {
- step();
- }
- } 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);
- isRunning = false;
-
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- uiUpdater.simulatorDidStop();
- }
- });
-
}
-
- 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());
+ /**
+ * Update the UI with the latest state of the simulator.
+ */
+ private void updateConsoleAndStatus(Cpu cpu) {
+ // If we have accumulated any ACIA output in our temporary
+ // buffer since the last UI update, write it to the console,
+ // then clear the buffer.
+ if (aciaBuffer.length() > 0) {
+ console.print(aciaBuffer.toString());
+ console.repaint();
+ aciaBuffer.delete(0, aciaBuffer.length());
}
- // This is a very expensive update, and we're doing it without
- // 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) {
+ // Now update the state
+ statusPane.updateState(cpu);
+ }
+
+ public static void main(String args[]) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ // Create the main UI window
+ Simulator simulator = new Simulator();
+ simulator.createAndShowUi();
+ // Reset the simulator.
+ simulator.handleReset();
+ } catch (MemoryRangeException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ /**
+ * The configuration has changed. Re-load.
+ *
+ * @param observable
+ * @param o
+ */
+ public void update(Observable observable, Object o) {
+ // Instance equality should work here, there is only one instance.
+ if (observable == preferences) {
+ // TODO: Update system based on state. (i.e., update ACIA address, and raise a dialog if it
+ // overlaps with anything)
+ }
+ }
+
+ /**
+ * The main run thread.
+ */
+ class RunLoop extends Thread {
+ private boolean isRunning = false;
+
+ public boolean isRunning() {
+ return isRunning;
+ }
+
+ public void requestStop() {
+ isRunning = false;
+ }
+
+ public void run() {
+ logger.log(Level.INFO, "Starting main run loop.");
+ isRunning = true;
+
SwingUtilities.invokeLater(new Runnable() {
public void run() {
- uiUpdater.updateUi();
+ // Don't allow step while the simulator is running
+ stepButton.setEnabled(false);
+ loadMenuItem.setEnabled(false);
+ // Toggle the state of the run button
+ runStopButton.setText("Stop");
}
});
- updatesRequested = 0;
+
+ try {
+ // TODO: Interrupts - both software and hardware. i.e., jump to address stored in FFFE/FFFF on BRK.
+ while (isRunning && !cpu.getBreakFlag()) {
+ step();
+ }
+ } catch (SymonException ex) {
+ logger.log(Level.SEVERE, "Exception in main simulator run thread. Exiting run.");
+ ex.printStackTrace();
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ // Allow step while the simulator is stopped
+ stepButton.setEnabled(true);
+ loadMenuItem.setEnabled(true);
+ runStopButton.setText("Run");
+ // Update state.
+ updateConsoleAndStatus(cpu);
+ }
+ });
+
+ logger.log(Level.INFO, "Exiting main run loop. BREAK=" + cpu.getBreakBit() + "; RUN_FLAG=" + isRunning);
+ isRunning = false;
}
}
-}
-
+}
\ No newline at end of file
diff --git a/src/main/java/com/loomcom/symon/devices/Acia.java b/src/main/java/com/loomcom/symon/devices/Acia.java
index 8dbae36..bf3fcb1 100644
--- a/src/main/java/com/loomcom/symon/devices/Acia.java
+++ b/src/main/java/com/loomcom/symon/devices/Acia.java
@@ -1,124 +1,132 @@
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.*;
-import com.loomcom.symon.util.*;
+
/**
* This is a simulation of the MOS 6551 ACIA, with limited
* functionality. Interrupts are not supported.
- *
+ *
* Unlike a 16550 UART, the 6551 ACIA has only one-byte transmit and
* receive buffers. It is the programmer's responsibility to check the
* status (full or empty) for transmit and receive buffers before
- * writing / reading. However, in the simulation we maintain two
- * small buffers of 256 characters, since we only wake up to check for
- * keyboard input and do output every 500 instructions.
+ * writing / reading.
*/
public class Acia extends Device {
- public static final int ACIA_SIZE = 4;
- public static final int BUF_LEN = 256;
+ public static final int ACIA_SIZE = 4;
- static final int DATA_REG = 0;
- static final int STAT_REG = 1;
- static final int CMND_REG = 2;
- static final int CTRL_REG = 3;
+ static final int DATA_REG = 0;
+ static final int STAT_REG = 1;
+ static final int CMND_REG = 2;
+ static final int CTRL_REG = 3;
- /** Register addresses */
- private int baseAddress;
+ /**
+ * Register addresses
+ */
+ private int baseAddress;
- /** Registers. These are ignored in the current implementation. */
- private int commandRegister;
- private int controlRegister;
+ /**
+ * Registers. These are ignored in the current implementation.
+ */
+ private int commandRegister;
+ private int controlRegister;
- /** Read/Write buffers */
- private FifoRingBuffer rxBuffer = new FifoRingBuffer(BUF_LEN);
- private FifoRingBuffer txBuffer = new FifoRingBuffer(BUF_LEN);
+ /**
+ * Read/Write buffers
+ */
+ private int rxChar = 0;
+ private int txChar = 0;
- public Acia(int address) throws MemoryRangeException {
- super(address, ACIA_SIZE, "ACIA");
- this.baseAddress = address;
- }
+ private boolean rxFull = false;
+ private boolean txFull = false;
- @Override
- public int read(int address) throws MemoryAccessException {
- switch (address) {
- case DATA_REG:
- try {
- return rxRead();
- } catch (FifoUnderrunException ex) {
- throw new MemoryAccessException("Buffer underrun");
- }
- case STAT_REG:
- return ((rxBuffer.isEmpty() ? 0x00 : 0x08) |
- (txBuffer.isEmpty() ? 0x10 : 0x00));
- case CMND_REG:
- return commandRegister;
- case CTRL_REG:
- return controlRegister;
- default:
- throw new MemoryAccessException("No register.");
+ public Acia(int address) throws MemoryRangeException {
+ super(address, ACIA_SIZE, "ACIA");
+ this.baseAddress = address;
}
- }
- @Override
- public void write(int address, int data) throws MemoryAccessException {
- switch (address) {
- case 0:
- txWrite(data);
- break;
- case 1:
- reset();
- break;
- case 2:
- commandRegister = data;
- break;
- case 3:
- controlRegister = data;
- break;
- default:
- throw new MemoryAccessException("No register.");
+ @Override
+ public int read(int address) throws MemoryAccessException {
+ switch (address) {
+ case DATA_REG:
+ return rxRead();
+ case STAT_REG:
+ return ((rxFull ? 0x08 : 0x00) |
+ (txFull ? 0x00 : 0x10));
+ case CMND_REG:
+ return commandRegister;
+ case CTRL_REG:
+ return controlRegister;
+ default:
+ throw new MemoryAccessException("No register.");
+ }
}
- }
- @Override
- public String toString() {
- return "ACIA@" + String.format("%04X", baseAddress);
- }
+ @Override
+ public void write(int address, int data) throws MemoryAccessException {
+ switch (address) {
+ case 0:
+ txWrite(data);
+ break;
+ case 1:
+ reset();
+ break;
+ case 2:
+ commandRegister = data;
+ break;
+ case 3:
+ controlRegister = data;
+ break;
+ default:
+ throw new MemoryAccessException("No register.");
+ }
+ }
- public int rxRead() throws FifoUnderrunException {
- return rxBuffer.pop();
- }
+ @Override
+ public String toString() {
+ return "ACIA@" + String.format("%04X", baseAddress);
+ }
- public void rxWrite(int data) {
- rxBuffer.push(data);
- }
+ public synchronized int rxRead() {
+ rxFull = false;
+ return rxChar;
+ }
- public int txRead() throws FifoUnderrunException {
- return txBuffer.pop();
- }
+ public synchronized void rxWrite(int data) {
+ rxFull = true;
+ rxChar = data;
+ }
- public void txWrite(int data) {
- txBuffer.push(data);
- }
+ public synchronized int txRead() {
+ txFull = false;
+ return txChar;
+ }
- /**
- * @return true if there is character data in the TX register.
- */
- public boolean hasTxChar() {
- return !txBuffer.isEmpty();
- }
+ public synchronized void txWrite(int data) {
+ txFull = true;
+ txChar = data;
+ }
- /**
- * @return true if there is character data in the RX register.
- */
- public boolean hasRxChar() {
- return !rxBuffer.isEmpty();
- }
+ /**
+ * @return true if there is character data in the TX register.
+ */
+ public boolean hasTxChar() {
+ return txFull;
+ }
- private void reset() {
- txBuffer.reset();
- rxBuffer.reset();
- }
+ /**
+ * @return true if there is character data in the RX register.
+ */
+ public boolean hasRxChar() {
+ return rxFull;
+ }
+
+ private synchronized void reset() {
+ txChar = 0;
+ txFull = false;
+ rxChar = 0;
+ rxFull = false;
+ }
}
diff --git a/src/main/java/com/loomcom/symon/ui/Console.java b/src/main/java/com/loomcom/symon/ui/Console.java
index a1870c5..8e905e6 100644
--- a/src/main/java/com/loomcom/symon/ui/Console.java
+++ b/src/main/java/com/loomcom/symon/ui/Console.java
@@ -7,6 +7,10 @@ import java.awt.event.MouseListener;
import com.grahamedgecombe.jterminal.JTerminal;
+import javax.swing.*;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.Border;
+
/**
* 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.
@@ -14,11 +18,14 @@ import com.grahamedgecombe.jterminal.JTerminal;
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);
+ Border emptyBorder = BorderFactory.createEmptyBorder(5, 5, 5, 0);
+ Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.LOWERED);
+ Border compoundBorder = BorderFactory.createCompoundBorder(emptyBorder, bevelBorder);
+ this.setBorder(compoundBorder);
}
public void reset() {
diff --git a/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java b/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java
new file mode 100644
index 0000000..1f9be06
--- /dev/null
+++ b/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java
@@ -0,0 +1,124 @@
+package com.loomcom.symon.ui;
+
+import com.loomcom.symon.Preferences;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Observable;
+
+public class PreferencesDialog extends Observable implements Preferences {
+
+ private final JDialog dialog;
+
+ private JTextField aciaAddressField;
+ private JTextField programLoadAddressField;
+
+ private int programLoadAddress = DEFAULT_PROGRAM_LOAD_ADDRESS;
+ private int aciaAddress = DEFAULT_ACIA_ADDRESS;
+
+ public PreferencesDialog(Frame parent, boolean modal) {
+ this.dialog = new JDialog(parent, modal);
+ initComponents();
+ updateUi();
+ }
+
+ public JDialog getDialog() {
+ return dialog;
+ }
+
+ private void initComponents() {
+ dialog.setTitle("Preferences");
+ Container contents = dialog.getContentPane();
+
+ JPanel settingsContainer = new JPanel();
+ JPanel buttonsContainer = new JPanel();
+
+ GridBagLayout layout = new GridBagLayout();
+ settingsContainer.setLayout(layout);
+
+ final JLabel aciaAddressLabel = new JLabel("ACIA Address");
+ final JLabel programLoadAddressLabel = new JLabel("Program Load Address");
+
+ aciaAddressField = new JTextField(8);
+ programLoadAddressField = new JTextField(8);
+
+ aciaAddressLabel.setLabelFor(aciaAddressField);
+ programLoadAddressLabel.setLabelFor(programLoadAddressField);
+
+ GridBagConstraints constraints = new GridBagConstraints();
+
+ constraints.insets = new Insets(5, 5, 5, 5);
+ constraints.fill = GridBagConstraints.HORIZONTAL;
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+
+ settingsContainer.add(aciaAddressLabel, constraints);
+
+ constraints.gridx = 1;
+ settingsContainer.add(aciaAddressField, constraints);
+
+ constraints.gridy = 1;
+ constraints.gridx = 0;
+ settingsContainer.add(programLoadAddressLabel, constraints);
+
+ constraints.gridx = 1;
+ settingsContainer.add(programLoadAddressField, constraints);
+
+ JButton applyButton = new JButton("Apply");
+ JButton cancelButton = new JButton("Cancel");
+
+
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent actionEvent) {
+ updateUi();
+ dialog.setVisible(false);
+ }
+ });
+
+ applyButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent actionEvent) {
+ programLoadAddress = hexToInt(programLoadAddressField.getText());
+ aciaAddress = hexToInt(aciaAddressField.getText());
+ updateUi();
+ notifyObservers();
+ dialog.setVisible(false);
+ }
+ });
+
+ buttonsContainer.add(applyButton);
+ buttonsContainer.add(cancelButton);
+
+ contents.add(settingsContainer, BorderLayout.PAGE_START);
+ contents.add(buttonsContainer, BorderLayout.PAGE_END);
+
+ dialog.pack();
+ }
+
+ private String intToHex(int i) {
+ return String.format("%04x", i);
+ }
+
+ private int hexToInt(String s) {
+ try {
+ return Integer.parseInt(s, 16);
+ } catch (NumberFormatException ex) {
+ // TODO: Handle Displaying error.
+ return 0;
+ }
+ }
+
+ public int getProgramStartAddress() {
+ return programLoadAddress;
+ }
+
+ public int getAciaAddress() {
+ return aciaAddress;
+ }
+
+ public void updateUi() {
+ aciaAddressField.setText(intToHex(aciaAddress));
+ programLoadAddressField.setText(intToHex(programLoadAddress));
+ }
+}
diff --git a/src/main/java/com/loomcom/symon/ui/StatusPane.java b/src/main/java/com/loomcom/symon/ui/StatusPanel.java
similarity index 59%
rename from src/main/java/com/loomcom/symon/ui/StatusPane.java
rename to src/main/java/com/loomcom/symon/ui/StatusPanel.java
index 4616041..e28bd95 100644
--- a/src/main/java/com/loomcom/symon/ui/StatusPane.java
+++ b/src/main/java/com/loomcom/symon/ui/StatusPanel.java
@@ -6,12 +6,10 @@ package com.loomcom.symon.ui;
import com.loomcom.symon.Cpu;
import javax.swing.*;
+import javax.swing.border.Border;
import java.awt.*;
-public class StatusPane extends JPanel {
-
- // The CPU to ask for state information.
- private final Cpu cpu;
+public class StatusPanel extends JPanel {
private final ImageIcon carryOn;
private final ImageIcon carryOff;
@@ -36,27 +34,36 @@ public class StatusPane extends JPanel {
private final JLabel overflowFlagLabel;
private final JLabel negativeFlagLabel;
+ private JTextField opcodeField;
+ private JTextField pcField;
+ private JTextField aField;
+ private JTextField xField;
+ private JTextField yField;
+// private JTextField stepCountField;
+
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 final JLabel stepCountLabel;
- private static final int WIDTH = 134;
- private static final int HEIGHT = 27;
+ private static final int WIDTH = 140;
+ private static final int HEIGHT = 200;
- public StatusPane(Cpu cpu) {
+ public StatusPanel() {
super();
- this.cpu = cpu;
-
Dimension dimensions = new Dimension(WIDTH, HEIGHT);
setMinimumSize(dimensions);
setPreferredSize(dimensions);
setMaximumSize(dimensions);
+ setLayout(new GridLayout(13,1));
+
+ JPanel statusFlagsPanel = new JPanel();
+
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"));
@@ -81,52 +88,83 @@ public class StatusPane extends JPanel {
overflowFlagLabel = new JLabel(overflowOff, JLabel.CENTER);
negativeFlagLabel = new JLabel(negativeOff, JLabel.CENTER);
+ Border emptyBorder = BorderFactory.createEmptyBorder(8, 6, 0, 6);
+
// Create and add register and address labels
+ opcodeLabel = new JLabel("Instruction");
+ opcodeLabel.setBorder(emptyBorder);
+ pcLabel = new JLabel("Program Counter");
+ pcLabel.setBorder(emptyBorder);
+ aLabel = new JLabel("Accumulator");
+ aLabel.setBorder(emptyBorder);
+ xLabel = new JLabel("X Register");
+ xLabel.setBorder(emptyBorder);
+ yLabel = new JLabel("Y Register");
+ yLabel.setBorder(emptyBorder);
+// stepCountLabel = new JLabel("Steps");
+// stepCountLabel.setBorder(emptyBorder);
- this.opcodeLabel = new JLabel();
- this.pcLabel = new JLabel();
- this.aLabel = new JLabel();
- this.xLabel = new JLabel();
- this.yLabel = new JLabel();
- this.stepCountLabel = new JLabel();
+ opcodeField = new JTextField("");
+ pcField = new JTextField("");
+ aField = new JTextField("");
+ xField = new JTextField("");
+ yField = new JTextField("");
+// stepCountField = new JTextField("");
- 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));
+ opcodeField.setEditable(false);
+ pcField.setEditable(false);
+ aField.setEditable(false);
+ xField.setEditable(false);
+ yField.setEditable(false);
+// stepCountField.setEditable(false);
- 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));
+ opcodeLabel.setMinimumSize(new Dimension(100, 20));
+ pcLabel.setMinimumSize(new Dimension(80, 20));
+ aLabel.setMinimumSize(new Dimension(60, 20));
+ xLabel.setMinimumSize(new Dimension(60, 20));
+ yLabel.setMinimumSize(new Dimension(60, 20));
+// stepCountLabel.setMinimumSize(new Dimension(120, 20));
- this.setLayout(new FlowLayout());
+ opcodeLabel.setPreferredSize(new Dimension(100, 20));
+ pcLabel.setPreferredSize(new Dimension(80, 20));
+ aLabel.setPreferredSize(new Dimension(60, 20));
+ xLabel.setPreferredSize(new Dimension(60, 20));
+ yLabel.setPreferredSize(new Dimension(60, 20));
+// stepCountLabel.setPreferredSize(new Dimension(120, 20));
- this.add(negativeFlagLabel);
- this.add(overflowFlagLabel);
- this.add(breakFlagLabel);
- this.add(decimalModeFlagLabel);
- this.add(irqDisableFlagLabel);
- this.add(zeroFlagLabel);
- this.add(carryFlagLabel);
+ statusFlagsPanel.add(negativeFlagLabel);
+ statusFlagsPanel.add(overflowFlagLabel);
+ statusFlagsPanel.add(breakFlagLabel);
+ statusFlagsPanel.add(decimalModeFlagLabel);
+ statusFlagsPanel.add(irqDisableFlagLabel);
+ statusFlagsPanel.add(zeroFlagLabel);
+ statusFlagsPanel.add(carryFlagLabel);
- this.add(opcodeLabel);
- this.add(pcLabel);
- this.add(aLabel);
- this.add(xLabel);
- this.add(yLabel);
- this.add(stepCountLabel);
+ add(statusFlagsPanel);
+ add(opcodeLabel);
+ add(opcodeField);
+ add(pcLabel);
+ add(pcField);
+ add(aLabel);
+ add(aField);
+ add(xLabel);
+ add(xField);
+ add(yLabel);
+ add(yField);
+// add(stepCountLabel);
+// add(stepCountField);
- updateState();
+ setBorder(BorderFactory.createBevelBorder(3));
}
- public void updateState() {
+ /**
+ * Update the display based on the current state of the CPU.
+ *
+ * @param cpu The simulated 6502 CPU.
+ */
+ public void updateState(Cpu cpu) {
// Update the Processor Status Flag display
- int state = this.cpu.getProcessorStatus();
+ int state = cpu.getProcessorStatus();
carryFlagLabel.setIcon(iconForFlag(state, 0));
zeroFlagLabel.setIcon(iconForFlag(state, 1));
@@ -137,12 +175,12 @@ public class StatusPane extends JPanel {
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()));
+ opcodeField.setText(cpu.getOpcodeStatus());
+ pcField.setText(cpu.getProgramCounterStatus());
+ aField.setText(cpu.getARegisterStatus());
+ xField.setText(cpu.getXRegisterStatus());
+ yField.setText(cpu.getYRegisterStatus());
+// stepCountField.setText(Long.toString(cpu.getStepCounter()));
repaint();
}
diff --git a/src/main/java/com/loomcom/symon/ui/UiUpdater.java b/src/main/java/com/loomcom/symon/ui/UiUpdater.java
deleted file mode 100644
index 93f66a2..0000000
--- a/src/main/java/com/loomcom/symon/ui/UiUpdater.java
+++ /dev/null
@@ -1,63 +0,0 @@
-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();
- }
-}
diff --git a/src/test/java/com/loomcom/symon/AciaTest.java b/src/test/java/com/loomcom/symon/AciaTest.java
index 9081d9d..72b6425 100644
--- a/src/test/java/com/loomcom/symon/AciaTest.java
+++ b/src/test/java/com/loomcom/symon/AciaTest.java
@@ -44,7 +44,7 @@ public class AciaTest {
}
@Test
- public void readingBuffersUntilEmptyShouldResetStatus()
+ public void readingBuffersShouldResetStatus()
throws Exception {
Acia acia = new Acia(0x0000);
@@ -59,64 +59,4 @@ public class AciaTest {
assertEquals(0x10, acia.read(0x0001));
}
- @Test
- public void readingPastEmptyRxBufferShouldThrowException()
- throws Exception {
- Acia acia = new Acia(0x0000);
-
- acia.rxWrite('a');
- assertEquals(0x18, acia.read(0x0001)); // not empty
-
- assertEquals('a', acia.rxRead());
- assertEquals(0x10, acia.read(0x0001)); // becomes empty
-
- // Should raise (note: I prefer this style to @Test(expected=...)
- // because it allows much finer grained control over asserting
- // exactly where the exception is expected to be raised.)
- try {
- // Should cause an underrun
- acia.rxRead();
- fail("Should have raised FifoUnderrunException.");
- } catch (FifoUnderrunException ex) {}
-
- assertEquals(0x10, acia.read(0x0001)); // still again
-
- for (int i = 0; i < Acia.BUF_LEN; i++) {
- acia.rxWrite('a');
- }
-
- // Should NOT cause an overrun
- acia.rxWrite('a'); // nothing thrown
- }
-
- @Test
- public void readingPastEmptyTxBufferShouldThrowException()
- throws Exception {
- Acia acia = new Acia(0x0000);
-
- acia.txWrite('a');
- assertEquals(0x00, acia.read(0x0001)); // not empty
-
- assertEquals('a', acia.txRead());
- assertEquals(0x10, acia.read(0x0001)); // becomes empty
-
- // Should raise (note: I prefer this style to @Test(expected=...)
- // because it allows much finer grained control over asserting
- // exactly where the exception is expected to be raised.)
- try {
- // Should cause an underrun
- acia.txRead();
- fail("Should have raised FifoUnderrunException.");
- } catch (FifoUnderrunException ex) {}
-
- assertEquals(0x10, acia.read(0x0001)); // still empty
-
- for (int i = 0; i < Acia.BUF_LEN; i++) {
- acia.txWrite('a');
- }
-
- // Should NOT cause an overrun
- acia.txWrite('a'); // Nothing thrown.
- }
-
}
diff --git a/src/test/java/com/loomcom/symon/SimulatorTest.java b/src/test/java/com/loomcom/symon/SimulatorTest.java
deleted file mode 100644
index 668f5a8..0000000
--- a/src/test/java/com/loomcom/symon/SimulatorTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.loomcom.symon;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * Unit test for the j6502 Simulator class.
- */
-public class SimulatorTest extends TestCase {
- /**
- * Create the test case
- *
- * @param testName name of the test case
- */
- public SimulatorTest(String testName) {
- super(testName);
- }
-
- /**
- * @return the suite of tests being tested
- */
- public static Test suite() {
- return new TestSuite(SimulatorTest.class);
- }
-
- /**
- * Rigourous Test :-)
- */
- public void testSimulator() {
- assertTrue(true);
- }
-}