diff --git a/README.md b/README.md
index ef7ad49..10a9f9e 100644
--- a/README.md
+++ b/README.md
@@ -47,20 +47,33 @@ Maven will build Symon, run unit tests, and produce a jar file in the
Symon is meant to be invoked directly from the jar file. To run with
Java 1.5 or greater, just type:
- $ java -jar symon-0.6-jar-with-dependencies.jar
+ $ java -jar symon-0.6-snapshot.jar
When Symon is running, you should be presented with a simple graphical
interface.
+### 3.2 ROM images
-### 3.2 Loading A Program
+The simulator requires a 16KB ROM image loaded at address $C000 to $FFFF to
+work properly. Without a ROM in memory, the simulator will not be able to
+reset, since the reset vector for the 6502 is located in this address space.
-Programs in the form of raw binary object files can be loaded directly
-into memory with the "Load" button.
+By default, any 16KB file named 'rom.bin' that exists in the same directory
+where Symon is launched will be loaded as a ROM image. ROM images can also
+be swapped out at run-time with the "Load ROM Image..." in the File menu.
-Right now, all programs are loaded starting at addres $0300. After
-loading, the simulated CPU's reset vector is loaded with the values
-$00, $03, and the CPU is reset.
+The "samples" directory contains a ROM image named 'ehbasic.rom', containing
+Lee Davison's Enhanced 6502 BASIC. This serves as a good starting point for
+exploration.
+
+### 3.3 Loading A Program
+
+In addition to ROM images, programs in the form of raw binary object files can
+be loaded directly into memory from "Load Program..." in the File menu.
+
+Programs are loaded starting at addres $0300. After loading the program, the
+simulated CPU's reset vector is loaded with the values $00, $03, and the CPU is
+reset.
There are two very simple sample program in the "samples" directory,
for testing.
@@ -69,33 +82,23 @@ for testing.
- 'hello.prg' will continuously print "Hello, 6502 World!" to the console.
-The sample directory also contains a ROM image of Lee Davison's
-Ehanced 6502 BASIC. For instructions on loading the rom, please see
-the README file in that directory.
-
-### 3.3 ROM files
-
-Any 12KB file named 'rom.bin' that exists in the same directory where
-Symon is launched will be loaded at address $d000. If the file is
-larger than 12KB, loading will fail. This functionality will be
-improved in a future release!
-
### 3.4 Running
After loading a program or ROM image, clicking "Run" will start the simulator
-running at address $0300.
-
+running.
## 4.0 To Do
+- Feedback (in the form of dialogs, status bar, etc).
+
+- Better ROM image handling
+
- Better debugging tools from the UI, including memory inspection,
disassembly, breakpoints, and execution tracing.
-- Better ROM loading (and re-loading)
-
- More accurate timing.
-- Interrupt handling!
+- Smarter interrupt handling.
- UI needs a ton more polish.
@@ -109,8 +112,6 @@ running at address $0300.
- Implement CMOS 65C02 instructions and NMOS / CMOS mode flag.
-- Allow a flag to disable breaking to monitor on BRK.
-
- Allow displaying ACIA status and dumping ACIA buffers, for
debugging.
diff --git a/pom.xml b/pom.xml
index 03e1c2b..7c869e5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,7 +44,7 @@
com.grahamedgecombe.jterminal
jterminal
- 1.0.2.1-loomcom
+ 1.0.2.2-loomcom
diff --git a/samples/echo.asm b/samples/echo.asm
index 3ad09a4..8081a31 100644
--- a/samples/echo.asm
+++ b/samples/echo.asm
@@ -3,7 +3,7 @@
;;
-.alias iobase $8000
+.alias iobase $8800
.alias iostatus [iobase + 1]
.alias iocmd [iobase + 2]
.alias ioctrl [iobase + 3]
@@ -11,7 +11,7 @@
.org $0300
start: cli
- lda #$09
+ lda #$0b
sta iocmd ; Set command status
lda #$1a
sta ioctrl ; 0 stop bits, 8 bit word, 2400 baud
diff --git a/samples/echo.prg b/samples/echo.prg
index ab5f1f7..a3e4ead 100644
Binary files a/samples/echo.prg and b/samples/echo.prg differ
diff --git a/samples/ehbasic.rom b/samples/ehbasic.rom
index 8e5526e..251fc84 100644
Binary files a/samples/ehbasic.rom and b/samples/ehbasic.rom differ
diff --git a/samples/ehbasic/min_mon.asm b/samples/ehbasic/min_mon.asm
index 693078a..d151a34 100644
--- a/samples/ehbasic/min_mon.asm
+++ b/samples/ehbasic/min_mon.asm
@@ -37,10 +37,12 @@ RES_vec
; Initialize the ACIA
ACIA_init
- LDA #$09
- STA ACIAcommand
- LDA #$1A ; Set output for 8-N-1 2400
- STA ACIAcontrol
+ LDA #$00
+ STA ACIAstatus ; Soft reset
+ LDA #$0B
+ STA ACIAcommand ; Parity disabled, IRQ disabled
+ LDA #$1E
+ STA ACIAcontrol ; Set output for 8-N-1 9600
; set up vectors and interrupt code, copy them to page 2
@@ -77,25 +79,33 @@ LAB_nokey
LAB_dowarm
JMP LAB_WARM ; do EhBASIC warm start
-; byte out to simulated ACIA
+; byte out to ACIA
ACIAout
PHA ; save accumulator
-ACIAout_w
+@loop
LDA ACIAstatus ; Read 6551 status
AND #$10 ; Is tx buffer full?
- BEQ ACIAout_w ; if not, loop back
+ BEQ @loop ; if not, loop back
PLA ; Otherwise, restore accumulator
STA ACIAdata ; write byte to 6551
RTS
-; byte in from simulated ACIA
-
+;
+; byte in from ACIA. This subroutine will also force
+; all lowercase letters to be uppercase.
+;
ACIAin
LDA ACIAstatus ; Read 6551 status
AND #$08 ;
BEQ LAB_nobyw ; If rx buffer empty, no byte
LDA ACIAdata ; Read byte from 6551
+ CMP #'a' ; Is it < 'a'?
+ BCC @done ; Yes, we're done
+ CMP #'{' ; Is it >= '{'?
+ BCS @done ; Yes, we're done
+ AND #$5f ; Otherwise, mask to uppercase
+@done
SEC ; Flag byte received
RTS
@@ -137,16 +147,19 @@ NMI_CODE
END_CODE
+; sign on string
+
LAB_mess
- .byte $0D,$0A,"6502 EhBASIC [C]old/[W]arm ?",$00
- ; sign on string
+ .byte $0D,$0A,"Symon 6502 SBC (c) 2012, Seth Morabito"
+ .byte $0D,$0A,"[C]old/[W]arm ?",$00
+
; system vectors
.segment "VECTORS"
.org $FFFA
- .word 0 ; NMI vector
+ .word NMI_vec ; NMI vector
.word RES_vec ; RESET vector
- .word 0 ; IRQ vector
+ .word IRQ_vec ; IRQ vector
diff --git a/samples/hello.asm b/samples/hello.asm
index 877a690..9fbd6a1 100644
--- a/samples/hello.asm
+++ b/samples/hello.asm
@@ -3,7 +3,7 @@
;;
-.alias iobase $8000
+.alias iobase $8800
.alias iostatus [iobase + 1]
.alias iocmd [iobase + 2]
.alias ioctrl [iobase + 3]
@@ -11,7 +11,7 @@
.org $0300
start: cli
- lda #$09
+ lda #$0b
sta iocmd ; Set command status
lda #$1a
sta ioctrl ; 0 stop bits, 8 bit word, 2400 baud
diff --git a/samples/hello.prg b/samples/hello.prg
index 1d24a9d..988de4c 100644
Binary files a/samples/hello.prg and b/samples/hello.prg differ
diff --git a/src/main/java/com/loomcom/symon/Preferences.java b/src/main/java/com/loomcom/symon/Preferences.java
index 7e56593..650cb5f 100644
--- a/src/main/java/com/loomcom/symon/Preferences.java
+++ b/src/main/java/com/loomcom/symon/Preferences.java
@@ -6,17 +6,17 @@ public interface Preferences {
public static final int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300;
- public static final int DEFAULT_ACIA_ADDRESS = 0xc000;
-
public static final int DEFAULT_BORDER_WIDTH = 10;
+ public static final boolean DEFAULT_HALT_ON_BREAK = true;
+
public JDialog getDialog();
public int getProgramStartAddress();
- public int getAciaAddress();
-
public int getBorderWidth();
+ public boolean getHaltOnBreak();
+
public void updateUi();
}
diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java
index 7aeebef..ddca8b0 100644
--- a/src/main/java/com/loomcom/symon/Simulator.java
+++ b/src/main/java/com/loomcom/symon/Simulator.java
@@ -33,9 +33,9 @@ import com.loomcom.symon.exceptions.SymonException;
import com.loomcom.symon.ui.Console;
import com.loomcom.symon.ui.PreferencesDialog;
import com.loomcom.symon.ui.StatusPanel;
-import sun.rmi.rmic.iiop.DirectoryLoader;
import javax.swing.*;
+import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -55,7 +55,7 @@ import java.util.logging.Logger;
* with a basic 80x25 character display.
*
*/
-public class Simulator implements ActionListener, Observer {
+public class Simulator implements Observer {
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
@@ -75,6 +75,9 @@ public class Simulator implements ActionListener, Observer {
private static final int ROM_BASE = 0xC000;
private static final int ROM_SIZE = 0x4000;
+ private static final int DEFAULT_FONT_SIZE = 12;
+ private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
+
// 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 simulated clock cycle. Instead, we will only refresh the view after this
// number of steps when running normally.
@@ -100,25 +103,37 @@ public class Simulator implements ActionListener, Observer {
// requested
private int stepsSinceLastUpdate = 0;
- private JFrame mainWindow;
- private RunLoop runLoop;
- private Console console;
- private StatusPanel statusPane;
+ /**
+ * The Main Window is the primary control point for the simulator.
+ * It is in charge of the menu, and sub-windows. It also shows the
+ * CPU status at all times.
+ */
+ private JFrame mainWindow;
- private JButton runStopButton;
- private JButton stepButton;
- private JButton resetButton;
+ /**
+ * The Console Window is connected to the ACIA's TX and RX lines.
+ */
+ private JFrame consoleWindow;
- // The most recently read key code
- private char keyBuffer;
+ /**
+ * The Trace Window shows the most recent 50,000 CPU states.
+ */
+ private JFrame traceWindow;
- // TODO: loadProgramItem seriously violates encapsulation!
- // A far better solution would be to extend JMenu and add callback
- // methods to enable and disable menus as required.
+ /**
+ * The Zero Page Window shows the contents of page 0.
+ */
+ private JFrame zeroPageWindow;
- // Menu Items
- private JMenuItem loadProgramItem;
- private JMenuItem loadRomItem;
+ private SimulatorMenu menuBar;
+
+ private RunLoop runLoop;
+ private Console console;
+ private StatusPanel statusPane;
+
+ private JButton runStopButton;
+ private JButton stepButton;
+ private JButton resetButton;
private JFileChooser fileChooser;
private PreferencesDialog preferences;
@@ -154,10 +169,11 @@ public class Simulator implements ActionListener, Observer {
mainWindow.getContentPane().setLayout(new BorderLayout());
// The Menu
- mainWindow.setJMenuBar(createMenuBar());
+ menuBar = new SimulatorMenu();
+ mainWindow.setJMenuBar(menuBar);
// UI components used for I/O.
- this.console = new com.loomcom.symon.ui.Console();
+ this.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT);
this.statusPane = new StatusPanel();
// File Chooser
@@ -166,15 +182,11 @@ public class Simulator implements ActionListener, Observer {
preferences.addObserver(this);
// Panel for Console and Buttons
- JPanel controlsContainer = new JPanel();
+ JPanel consoleContainer = 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());
+ consoleContainer.setLayout(new BorderLayout());
+ consoleContainer.setBorder(new EmptyBorder(10, 10, 10, 0));
buttonContainer.setLayout(new FlowLayout());
runStopButton = new JButton("Run");
@@ -186,8 +198,8 @@ public class Simulator implements ActionListener, Observer {
buttonContainer.add(resetButton);
// Left side - console
- controlsContainer.add(console, BorderLayout.PAGE_START);
- mainWindow.getContentPane().add(controlsContainer, BorderLayout.LINE_START);
+ consoleContainer.add(console, BorderLayout.CENTER);
+ mainWindow.getContentPane().add(consoleContainer, BorderLayout.LINE_START);
// Right side - status pane
mainWindow.getContentPane().add(statusPane, BorderLayout.LINE_END);
@@ -195,9 +207,27 @@ public class Simulator implements ActionListener, Observer {
// Bottom - buttons.
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
- runStopButton.addActionListener(this);
- stepButton.addActionListener(this);
- resetButton.addActionListener(this);
+ runStopButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent actionEvent) {
+ if (runLoop != null && runLoop.isRunning()) {
+ handleStop();
+ } else {
+ handleStart();
+ }
+ }
+ });
+
+ stepButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent actionEvent) {
+ handleStep();
+ }
+ });
+
+ resetButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent actionEvent) {
+ handleReset();
+ }
+ });
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@@ -207,78 +237,6 @@ public class Simulator implements ActionListener, Observer {
mainWindow.setVisible(true);
}
- private JMenuBar createMenuBar() {
- JMenuBar menuBar = new JMenuBar();
-
- JMenu fileMenu = new JMenu("File");
-
- menuBar.add(fileMenu);
-
- loadProgramItem = new JMenuItem("Load Program");
- loadProgramItem.setMnemonic(KeyEvent.VK_L);
-
- loadRomItem = new JMenuItem("Load ROM...");
- loadRomItem.setMnemonic(KeyEvent.VK_R);
-
- JMenuItem prefsItem = new JMenuItem("Preferences...");
- prefsItem.setMnemonic(KeyEvent.VK_P);
-
- JMenuItem quitItem = new JMenuItem("Quit");
- quitItem.setMnemonic(KeyEvent.VK_Q);
-
- loadProgramItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent actionEvent) {
- handleProgramLoad();
- }
- });
-
- loadRomItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent actionEvent) {
- handleRomLoad();
- }
- });
-
- prefsItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent actionEvent) {
- showAndUpdatePreferences();
- }
- });
-
- quitItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent actionEvent) {
- handleQuit();
- }
- });
-
- fileMenu.add(loadProgramItem);
- fileMenu.add(loadRomItem);
- 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) {
- coldReset();
- } else if (actionEvent.getSource() == stepButton) {
- handleStep();
- } else if (actionEvent.getSource() == runStopButton) {
- if (runLoop != null && runLoop.isRunning()) {
- handleStop();
- } else {
- handleStart();
- }
- }
- }
-
private void handleStart() {
// Shift focus to the console.
console.requestFocus();
@@ -311,85 +269,10 @@ public class Simulator implements ActionListener, Observer {
// TODO: Update memory window, if frame is visible.
}
- // TODO: Alert user of errors.
- private void handleRomLoad() {
- try {
- int retVal = fileChooser.showOpenDialog(mainWindow);
- if (retVal == JFileChooser.APPROVE_OPTION) {
- File romFile = fileChooser.getSelectedFile();
- if (romFile.canRead()) {
- long fileSize = romFile.length();
-
- if (fileSize != ROM_SIZE) {
- throw new IOException("ROM file must be exactly " + String.valueOf(fileSize) + " bytes.");
- } else {
- if (rom != null) {
- // Unload the existing ROM image.
- bus.removeDevice(rom);
- }
- // Load the new ROM image
- rom = Memory.makeROM(ROM_BASE, ROM_SIZE, romFile);
- bus.addDevice(rom);
-
- logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " +
- String.format("0x%04X", ROM_BASE));
- }
- }
- }
- } catch (IOException ex) {
- logger.log(Level.SEVERE, "Unable to read file: " + ex.getMessage());
- ex.printStackTrace();
- } catch (MemoryRangeException ex) {
- logger.log(Level.SEVERE, "Memory range error loading ROM");
- ex.printStackTrace();
- }
- }
-
- /**
- * 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.
+ /*
+ * Perform a reset.
*/
- 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 coldReset() {
+ private void handleReset() {
if (runLoop != null && runLoop.isRunning()) {
runLoop.requestStop();
runLoop.interrupt();
@@ -412,7 +295,6 @@ public class Simulator implements ActionListener, Observer {
});
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Exception during simulator reset: " + ex.getMessage());
- ex.printStackTrace();
}
}
@@ -429,17 +311,6 @@ public class Simulator implements ActionListener, Observer {
}
}
- /**
- * 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.
*/
@@ -516,7 +387,7 @@ public class Simulator implements ActionListener, Observer {
Simulator simulator = new Simulator();
simulator.createAndShowUi();
// Reset the simulator.
- simulator.coldReset();
+ simulator.handleReset();
} catch (Exception e) {
e.printStackTrace();
}
@@ -533,8 +404,6 @@ public class Simulator implements ActionListener, Observer {
public void update(Observable observable, Object o) {
// Instance equality should work here, there is only one instance.
if (observable == preferences) {
- // TODO: Update ACIA base address if it has changed.
-
int oldBorderWidth = console.getBorderWidth();
if (oldBorderWidth != preferences.getBorderWidth()) {
// Resize the main window if the border width has changed.
@@ -544,7 +413,6 @@ public class Simulator implements ActionListener, Observer {
}
}
-
/**
* The main run thread.
*/
@@ -569,16 +437,14 @@ public class Simulator implements ActionListener, Observer {
console.startListening();
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
- loadProgramItem.setEnabled(false);
- loadRomItem.setEnabled(false);
+ menuBar.simulatorDidStart();
// Toggle the state of the run button
runStopButton.setText("Stop");
}
});
try {
- // TODO: Interrupts - both software and hardware. i.e., jump to address stored in FFFE/FFFF on BRK.
- while (isRunning && !cpu.getBreakFlag()) {
+ while (isRunning && !(preferences.getHaltOnBreak() && cpu.getBreakFlag())) {
step();
}
} catch (SymonException ex) {
@@ -591,16 +457,235 @@ public class Simulator implements ActionListener, Observer {
console.stopListening();
// Allow step while the simulator is stopped
stepButton.setEnabled(true);
- loadProgramItem.setEnabled(true);
- loadRomItem.setEnabled(true);
+ menuBar.simulatorDidStop();
runStopButton.setText("Run");
// Now update the state
statusPane.updateState(cpu);
}
});
- logger.log(Level.INFO, "Exiting main run loop. BREAK=" + cpu.getBreakBit() + "; RUN_FLAG=" + isRunning);
isRunning = false;
}
}
+
+ class LoadProgramAction extends AbstractAction {
+ public LoadProgramAction() {
+ super("Load Program...", null);
+ putValue(SHORT_DESCRIPTION, "Load a program into memory");
+ putValue(MNEMONIC_KEY, KeyEvent.VK_L);
+ }
+
+ public void actionPerformed(ActionEvent actionEvent) {
+ // TODO: Error dialogs on failure.
+ 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 program file: " + ex.getMessage());
+ } catch (MemoryAccessException ex) {
+ logger.log(Level.SEVERE, "Memory access error loading program: " + ex.getMessage());
+ }
+ }
+ }
+
+ class LoadRomAction extends AbstractAction {
+ public LoadRomAction() {
+ super("Load ROM...", null);
+ putValue(SHORT_DESCRIPTION, "Load a ROM image");
+ putValue(MNEMONIC_KEY, KeyEvent.VK_R);
+ }
+
+ public void actionPerformed(ActionEvent actionEvent) {
+ // TODO: Error dialogs on failure.
+ try {
+ int retVal = fileChooser.showOpenDialog(mainWindow);
+ if (retVal == JFileChooser.APPROVE_OPTION) {
+ File romFile = fileChooser.getSelectedFile();
+ if (romFile.canRead()) {
+ long fileSize = romFile.length();
+
+ if (fileSize != ROM_SIZE) {
+ throw new IOException("ROM file must be exactly " + String.valueOf(ROM_SIZE) + " bytes.");
+ } else {
+ if (rom != null) {
+ // Unload the existing ROM image.
+ bus.removeDevice(rom);
+ }
+ // Load the new ROM image
+ rom = Memory.makeROM(ROM_BASE, ROM_SIZE, romFile);
+ bus.addDevice(rom);
+
+ // Now, reset
+ cpu.reset();
+
+ logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " +
+ String.format("0x%04X", ROM_BASE));
+ }
+ }
+ }
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, "Unable to read ROM file: " + ex.getMessage());
+ } catch (MemoryRangeException ex) {
+ logger.log(Level.SEVERE, "Memory range error while loading ROM file: " + ex.getMessage());
+ } catch (MemoryAccessException ex) {
+ logger.log(Level.SEVERE, "Memory access error while loading ROM file: " + ex.getMessage());
+ }
+ }
+ }
+
+ class ShowPrefsAction extends AbstractAction {
+ public ShowPrefsAction() {
+ super("Preferences...", null);
+ putValue(SHORT_DESCRIPTION, "Show Preferences Dialog");
+ putValue(MNEMONIC_KEY, KeyEvent.VK_P);
+ }
+
+ public void actionPerformed(ActionEvent actionEvent) {
+ preferences.getDialog().setVisible(true);
+ }
+ }
+
+ class QuitAction extends AbstractAction {
+ public QuitAction() {
+ super("Quit", null);
+ putValue(SHORT_DESCRIPTION, "Exit the Simulator");
+ putValue(MNEMONIC_KEY, KeyEvent.VK_Q);
+ }
+
+ public void actionPerformed(ActionEvent actionEvent) {
+ if (runLoop != null && runLoop.isRunning()) {
+ runLoop.requestStop();
+ runLoop.interrupt();
+ }
+ System.exit(0);
+ }
+ }
+
+ class SetFontAction extends AbstractAction {
+ private int size;
+
+ public SetFontAction(int size) {
+ super(Integer.toString(size) + " pt", null);
+ this.size = size;
+ putValue(SHORT_DESCRIPTION, "Set font to " + Integer.toString(size) + "pt.");
+ }
+
+ public void actionPerformed(ActionEvent actionEvent) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ console.setFont(new Font("Monospaced", Font.PLAIN, size));
+ mainWindow.pack();
+ }
+ });
+ }
+ }
+
+ class SimulatorMenu extends JMenuBar {
+ // Menu Items
+ private JMenuItem loadProgramItem;
+ private JMenuItem loadRomItem;
+
+ /**
+ * Create a new SimulatorMenu instance.
+ */
+ public SimulatorMenu() {
+ initMenu();
+ }
+
+ /**
+ * Disable menu items that should not be available during simulator execution.
+ */
+ public void simulatorDidStart() {
+ loadProgramItem.setEnabled(false);
+ loadRomItem.setEnabled(false);
+ }
+
+ /**
+ * Enable menu items that should be available while the simulator is stopped.
+ */
+ public void simulatorDidStop() {
+ loadProgramItem.setEnabled(true);
+ loadRomItem.setEnabled(true);
+ }
+
+ private void initMenu() {
+ /*
+ * File Menu
+ */
+
+ JMenu fileMenu = new JMenu("File");
+
+ loadProgramItem = new JMenuItem(new LoadProgramAction());
+ loadRomItem = new JMenuItem(new LoadRomAction());
+ JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction());
+ JMenuItem quitItem = new JMenuItem(new QuitAction());
+
+ fileMenu.add(loadProgramItem);
+ fileMenu.add(loadRomItem);
+ fileMenu.add(prefsItem);
+ fileMenu.add(quitItem);
+
+ add(fileMenu);
+
+ /*
+ * View Menu
+ */
+
+ JMenu viewMenu = new JMenu("View");
+ JMenu fontSubMenu = new JMenu("Font Size");
+ ButtonGroup group = new ButtonGroup();
+
+ makeFontSizeMenuItem(10, fontSubMenu, group);
+ makeFontSizeMenuItem(11, fontSubMenu, group);
+ makeFontSizeMenuItem(12, fontSubMenu, group);
+ makeFontSizeMenuItem(13, fontSubMenu, group);
+ makeFontSizeMenuItem(14, fontSubMenu, group);
+ makeFontSizeMenuItem(15, fontSubMenu, group);
+ makeFontSizeMenuItem(16, fontSubMenu, group);
+ makeFontSizeMenuItem(17, fontSubMenu, group);
+ makeFontSizeMenuItem(18, fontSubMenu, group);
+ makeFontSizeMenuItem(19, fontSubMenu, group);
+ makeFontSizeMenuItem(20, fontSubMenu, group);
+
+ viewMenu.add(fontSubMenu);
+ add(viewMenu);
+ }
+
+ private void makeFontSizeMenuItem(int size, JMenu fontSubMenu, ButtonGroup group) {
+ Action action = new SetFontAction(size);
+
+ JRadioButtonMenuItem item = new JRadioButtonMenuItem(action);
+ item.setSelected(size == DEFAULT_FONT_SIZE);
+ fontSubMenu.add(item);
+ group.add(item);
+ }
+ }
+
}
diff --git a/src/main/java/com/loomcom/symon/devices/Acia.java b/src/main/java/com/loomcom/symon/devices/Acia.java
index 9630cd8..25f5a8b 100644
--- a/src/main/java/com/loomcom/symon/devices/Acia.java
+++ b/src/main/java/com/loomcom/symon/devices/Acia.java
@@ -170,8 +170,8 @@ public class Acia extends Device {
*/
private long calculateBaudRateDelay() {
if (baudRate > 0) {
- // This is a pretty rough approximation based on 8 bits per character,
- // and 1/baudRate per bit.
+ // TODO: This is a pretty rough approximation based on 8 bits per character,
+ // and 1/baudRate per bit. It could certainly be improved
return (long)((1.0 / baudRate) * 1000000000 * 8);
} else {
return 0;
diff --git a/src/main/java/com/loomcom/symon/ui/Console.java b/src/main/java/com/loomcom/symon/ui/Console.java
index 370b697..e8a6623 100644
--- a/src/main/java/com/loomcom/symon/ui/Console.java
+++ b/src/main/java/com/loomcom/symon/ui/Console.java
@@ -1,5 +1,6 @@
package com.loomcom.symon.ui;
+import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
@@ -36,12 +37,8 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
private FifoRingBuffer typeAheadBuffer;
- public Console() {
- this(DEFAULT_COLUMNS, DEFAULT_ROWS);
- }
-
- public Console(int columns, int rows) {
- super(new Vt100TerminalModel(columns, rows));
+ public Console(int columns, int rows, Font font) {
+ super(new Vt100TerminalModel(columns, rows), font);
// A small type-ahead buffer, as might be found in any real
// VT100-style serial terminal.
this.typeAheadBuffer = new FifoRingBuffer(128);
diff --git a/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java b/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java
index 35093f7..69ba253 100644
--- a/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java
+++ b/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java
@@ -12,13 +12,13 @@ public class PreferencesDialog extends Observable implements Preferences {
private final JDialog dialog;
- private JTextField aciaAddressField;
+ private JCheckBox haltOnBreakCheckBox;
private JTextField programLoadAddressField;
private JTextField borderWidthField;
private int programLoadAddress = DEFAULT_PROGRAM_LOAD_ADDRESS;
- private int aciaAddress = DEFAULT_ACIA_ADDRESS;
private int borderWidth = DEFAULT_BORDER_WIDTH;
+ private boolean haltOnBreak = DEFAULT_HALT_ON_BREAK;
public PreferencesDialog(Frame parent, boolean modal) {
this.dialog = new JDialog(parent, modal);
@@ -43,15 +43,14 @@ public class PreferencesDialog extends Observable implements Preferences {
GridBagLayout layout = new GridBagLayout();
settingsContainer.setLayout(layout);
- final JLabel aciaAddressLabel = new JLabel("ACIA Address");
+ final JLabel haltOnBreakLabel = new JLabel("Halt on BRK");
final JLabel programLoadAddressLabel = new JLabel("Program Load Address");
final JLabel borderWidthLabel = new JLabel("Console Border Width");
- aciaAddressField = new JTextField(8);
+ haltOnBreakCheckBox = new JCheckBox();
programLoadAddressField = new JTextField(8);
borderWidthField = new JTextField(8);
- aciaAddressLabel.setLabelFor(aciaAddressField);
programLoadAddressLabel.setLabelFor(programLoadAddressField);
borderWidthLabel.setLabelFor(borderWidthField);
@@ -61,11 +60,10 @@ public class PreferencesDialog extends Observable implements Preferences {
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 0;
constraints.gridy = 0;
-
- settingsContainer.add(aciaAddressLabel, constraints);
+ settingsContainer.add(haltOnBreakLabel, constraints);
constraints.gridx = 1;
- settingsContainer.add(aciaAddressField, constraints);
+ settingsContainer.add(haltOnBreakCheckBox, constraints);
constraints.gridy = 1;
constraints.gridx = 0;
@@ -94,8 +92,8 @@ public class PreferencesDialog extends Observable implements Preferences {
applyButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
+ haltOnBreak = haltOnBreakCheckBox.isSelected();
programLoadAddress = hexToInt(programLoadAddressField.getText());
- aciaAddress = hexToInt(aciaAddressField.getText());
borderWidth = Integer.parseInt(borderWidthField.getText());
updateUi();
// TODO: Actually check to see if values have changed, don't assume.
@@ -118,10 +116,6 @@ public class PreferencesDialog extends Observable implements Preferences {
return programLoadAddress;
}
- public int getAciaAddress() {
- return aciaAddress;
- }
-
/**
* @return The width of the console border, in pixels.
*/
@@ -129,8 +123,15 @@ public class PreferencesDialog extends Observable implements Preferences {
return borderWidth;
}
+ /**
+ * @return True if 'halt on break' is desired, false otherwise.
+ */
+ public boolean getHaltOnBreak() {
+ return haltOnBreak;
+ }
+
public void updateUi() {
- aciaAddressField.setText(intToHex(aciaAddress));
+ haltOnBreakCheckBox.setSelected(haltOnBreak);
programLoadAddressField.setText(intToHex(programLoadAddress));
borderWidthField.setText(Integer.toString(borderWidth));
}
diff --git a/src/main/java/com/loomcom/symon/ui/StatusPanel.java b/src/main/java/com/loomcom/symon/ui/StatusPanel.java
index b0defe7..222d6e9 100644
--- a/src/main/java/com/loomcom/symon/ui/StatusPanel.java
+++ b/src/main/java/com/loomcom/symon/ui/StatusPanel.java
@@ -26,6 +26,7 @@ public class StatusPanel extends JPanel {
private final ImageIcon negativeOn;
private final ImageIcon negativeOff;
+ private final JLabel statusFlagsLabel;
private final JLabel carryFlagLabel;
private final JLabel zeroFlagLabel;
private final JLabel irqDisableFlagLabel;
@@ -40,7 +41,6 @@ public class StatusPanel extends JPanel {
private JTextField aField;
private JTextField xField;
private JTextField yField;
-// private JTextField stepCountField;
private final JLabel opcodeLabel;
private final JLabel pcLabel;
@@ -48,11 +48,10 @@ public class StatusPanel extends JPanel {
private final JLabel aLabel;
private final JLabel xLabel;
private final JLabel yLabel;
-// private final JLabel stepCountLabel;
- private static final int EMPTY_BORDER = 5;
- private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 4, 0, 0);
- private static final Font LABEL_FONT = new Font("sansserif", Font.PLAIN, 11);
+ private static final int EMPTY_BORDER = 10;
+ private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
+ private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 12);
public StatusPanel() {
super();
@@ -63,7 +62,10 @@ public class StatusPanel extends JPanel {
setBorder(BorderFactory.createCompoundBorder(emptyBorder, etchedBorder));
- setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ GridBagLayout layout = new GridBagLayout();
+ GridBagConstraints constraints = new GridBagConstraints();
+
+ setLayout(layout);
JPanel statusFlagsPanel = new JPanel();
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
@@ -101,37 +103,72 @@ public class StatusPanel extends JPanel {
statusFlagsPanel.add(carryFlagLabel);
// Create and add register and address labels
- opcodeLabel = makeLabel("Instruction");
- pcLabel = makeLabel("Program Counter");
- spLabel = makeLabel("Stack Pointer");
- aLabel = makeLabel("Accumulator");
- xLabel = makeLabel("X Register");
- yLabel = makeLabel("Y Register");
-// stepCountLabel = new JLabel("Steps");
+ statusFlagsLabel = makeLabel("Flags");
+ opcodeLabel = makeLabel("IR");
+ pcLabel = makeLabel("PC");
+ spLabel = makeLabel("SP");
+ aLabel = makeLabel("A");
+ xLabel = makeLabel("X");
+ yLabel = makeLabel("Y");
- opcodeField = makeTextField();
- pcField = makeTextField();
- spField = makeTextField();
- aField = makeTextField();
- xField = makeTextField();
- yField = makeTextField();
-// stepCountField = new JTextField("");
+ opcodeField = makeTextField(LARGE_TEXT_FIELD_SIZE);
+ pcField = makeTextField(LARGE_TEXT_FIELD_SIZE);
+ spField = makeTextField(SMALL_TEXT_FIELD_SIZE);
+ aField = makeTextField(SMALL_TEXT_FIELD_SIZE);
+ xField = makeTextField(SMALL_TEXT_FIELD_SIZE);
+ yField = makeTextField(SMALL_TEXT_FIELD_SIZE);
- add(statusFlagsPanel);
- add(opcodeLabel);
- add(opcodeField);
- add(pcLabel);
- add(pcField);
- add(spLabel);
- add(spField);
- add(aLabel);
- add(aField);
- add(xLabel);
- add(xField);
- add(yLabel);
- add(yField);
-// add(stepCountLabel);
-// add(stepCountField);
+ constraints.anchor = GridBagConstraints.LINE_START;
+ constraints.gridwidth = 2;
+ constraints.gridx = 0;
+ constraints.gridy = 0;
+ add(statusFlagsLabel, constraints);
+ constraints.gridy = 1;
+ add(statusFlagsPanel, constraints);
+
+ constraints.insets = new Insets(5, 0, 0, 0);
+ constraints.gridy = 2;
+ add(opcodeLabel, constraints);
+
+ constraints.insets = new Insets(0, 0, 0, 0);
+ constraints.gridy = 3;
+ add(opcodeField, constraints);
+
+ constraints.insets = new Insets(5, 0, 0, 0);
+ constraints.gridy = 4;
+ add(pcLabel, constraints);
+
+ constraints.insets = new Insets(0, 0, 0, 0);
+ constraints.gridy = 5;
+ add(pcField, constraints);
+
+ constraints.insets = new Insets(5, 0, 0, 0);
+ constraints.gridwidth = 1;
+ constraints.gridy = 6;
+ add(spLabel, constraints);
+ constraints.gridx = 1;
+ add(aLabel, constraints);
+
+ constraints.insets = new Insets(0, 0, 0, 0);
+ constraints.gridx = 0;
+ constraints.gridy = 7;
+ add(spField, constraints);
+ constraints.gridx = 1;
+ add(aField, constraints);
+
+ constraints.insets = new Insets(5, 0, 0, 0);
+ constraints.gridx = 0;
+ constraints.gridy = 8;
+ add(yLabel, constraints);
+ constraints.gridx = 1;
+ add(xLabel, constraints);
+
+ constraints.insets = new Insets(0, 0, 5, 0);
+ constraints.gridx = 0;
+ constraints.gridy = 9;
+ add(xField, constraints);
+ constraints.gridx = 1;
+ add(yField, constraints);
}
/**
@@ -158,7 +195,6 @@ public class StatusPanel extends JPanel {
aField.setText(cpu.getAccumulatorStatus());
xField.setText(cpu.getXRegisterStatus());
yField.setText(cpu.getYRegisterStatus());
-// stepCountField.setText(Long.toString(cpu.getStepCounter()));
repaint();
}
@@ -228,10 +264,16 @@ public class StatusPanel extends JPanel {
return label;
}
- private JTextField makeTextField() {
+ private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22);
+ private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
+
+ private JTextField makeTextField(Dimension size) {
JTextField textField = new JTextField("");
textField.setAlignmentX(LEFT_ALIGNMENT);
textField.setEditable(false);
+ textField.setMinimumSize(size);
+ textField.setMaximumSize(size);
+ textField.setPreferredSize(size);
return textField;
}