From ed36690e9edd2b8240a4b0433fb7b8c0a69b8a14 Mon Sep 17 00:00:00 2001 From: Seth Morabito Date: Mon, 28 May 2012 22:18:35 -0700 Subject: [PATCH] Checkpoint of my Swing UI refactoring. Usable for output-only programs, but only barely. --- pom.xml | 4 +- samples/hello.asm | 26 +- src/main/java/com/loomcom/symon/Cpu.java | 3 +- .../java/com/loomcom/symon/MainWindow.java | 247 --------- .../java/com/loomcom/symon/Preferences.java | 18 + .../java/com/loomcom/symon/Simulator.java | 514 ++++++++++++++---- .../java/com/loomcom/symon/devices/Acia.java | 192 +++---- .../java/com/loomcom/symon/ui/Console.java | 9 +- .../loomcom/symon/ui/PreferencesDialog.java | 124 +++++ .../ui/{StatusPane.java => StatusPanel.java} | 140 +++-- .../java/com/loomcom/symon/ui/UiUpdater.java | 63 --- src/test/java/com/loomcom/symon/AciaTest.java | 62 +-- .../java/com/loomcom/symon/SimulatorTest.java | 33 -- 13 files changed, 776 insertions(+), 659 deletions(-) delete mode 100644 src/main/java/com/loomcom/symon/MainWindow.java create mode 100644 src/main/java/com/loomcom/symon/Preferences.java create mode 100644 src/main/java/com/loomcom/symon/ui/PreferencesDialog.java rename src/main/java/com/loomcom/symon/ui/{StatusPane.java => StatusPanel.java} (59%) delete mode 100644 src/main/java/com/loomcom/symon/ui/UiUpdater.java delete mode 100644 src/test/java/com/loomcom/symon/SimulatorTest.java 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); - } -}