1
0
mirror of https://github.com/sethm/symon.git synced 2024-06-03 07:29:30 +00:00

New UI layout, ROM loading, Font selection

- The UI layout has changed, and will likely change again in the future.

- Symon can now re-load ROM images from the File menu, under "Load ROM..."

- Font size can be changed under the "View" menu
This commit is contained in:
Seth Morabito 2012-12-05 23:19:34 -08:00
parent a7d9239ef1
commit 38a4458aff
14 changed files with 449 additions and 310 deletions

View File

@ -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 Symon is meant to be invoked directly from the jar file. To run with
Java 1.5 or greater, just type: 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 When Symon is running, you should be presented with a simple graphical
interface. 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 By default, any 16KB file named 'rom.bin' that exists in the same directory
into memory with the "Load" button. 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 The "samples" directory contains a ROM image named 'ehbasic.rom', containing
loading, the simulated CPU's reset vector is loaded with the values Lee Davison's Enhanced 6502 BASIC. This serves as a good starting point for
$00, $03, and the CPU is reset. 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, There are two very simple sample program in the "samples" directory,
for testing. for testing.
@ -69,33 +82,23 @@ for testing.
- 'hello.prg' will continuously print "Hello, 6502 World!" to the console. - '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 ### 3.4 Running
After loading a program or ROM image, clicking "Run" will start the simulator After loading a program or ROM image, clicking "Run" will start the simulator
running at address $0300. running.
## 4.0 To Do ## 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, - Better debugging tools from the UI, including memory inspection,
disassembly, breakpoints, and execution tracing. disassembly, breakpoints, and execution tracing.
- Better ROM loading (and re-loading)
- More accurate timing. - More accurate timing.
- Interrupt handling! - Smarter interrupt handling.
- UI needs a ton more polish. - UI needs a ton more polish.
@ -109,8 +112,6 @@ running at address $0300.
- Implement CMOS 65C02 instructions and NMOS / CMOS mode flag. - 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 - Allow displaying ACIA status and dumping ACIA buffers, for
debugging. debugging.

View File

@ -44,7 +44,7 @@
<dependency> <dependency>
<groupId>com.grahamedgecombe.jterminal</groupId> <groupId>com.grahamedgecombe.jterminal</groupId>
<artifactId>jterminal</artifactId> <artifactId>jterminal</artifactId>
<version>1.0.2.1-loomcom</version> <version>1.0.2.2-loomcom</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -3,7 +3,7 @@
;; ;;
.alias iobase $8000 .alias iobase $8800
.alias iostatus [iobase + 1] .alias iostatus [iobase + 1]
.alias iocmd [iobase + 2] .alias iocmd [iobase + 2]
.alias ioctrl [iobase + 3] .alias ioctrl [iobase + 3]
@ -11,7 +11,7 @@
.org $0300 .org $0300
start: cli start: cli
lda #$09 lda #$0b
sta iocmd ; Set command status sta iocmd ; Set command status
lda #$1a lda #$1a
sta ioctrl ; 0 stop bits, 8 bit word, 2400 baud sta ioctrl ; 0 stop bits, 8 bit word, 2400 baud

Binary file not shown.

Binary file not shown.

View File

@ -37,10 +37,12 @@ RES_vec
; Initialize the ACIA ; Initialize the ACIA
ACIA_init ACIA_init
LDA #$09 LDA #$00
STA ACIAcommand STA ACIAstatus ; Soft reset
LDA #$1A ; Set output for 8-N-1 2400 LDA #$0B
STA ACIAcontrol 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 ; set up vectors and interrupt code, copy them to page 2
@ -77,25 +79,33 @@ LAB_nokey
LAB_dowarm LAB_dowarm
JMP LAB_WARM ; do EhBASIC warm start JMP LAB_WARM ; do EhBASIC warm start
; byte out to simulated ACIA ; byte out to ACIA
ACIAout ACIAout
PHA ; save accumulator PHA ; save accumulator
ACIAout_w @loop
LDA ACIAstatus ; Read 6551 status LDA ACIAstatus ; Read 6551 status
AND #$10 ; Is tx buffer full? AND #$10 ; Is tx buffer full?
BEQ ACIAout_w ; if not, loop back BEQ @loop ; if not, loop back
PLA ; Otherwise, restore accumulator PLA ; Otherwise, restore accumulator
STA ACIAdata ; write byte to 6551 STA ACIAdata ; write byte to 6551
RTS RTS
; byte in from simulated ACIA ;
; byte in from ACIA. This subroutine will also force
; all lowercase letters to be uppercase.
;
ACIAin ACIAin
LDA ACIAstatus ; Read 6551 status LDA ACIAstatus ; Read 6551 status
AND #$08 ; AND #$08 ;
BEQ LAB_nobyw ; If rx buffer empty, no byte BEQ LAB_nobyw ; If rx buffer empty, no byte
LDA ACIAdata ; Read byte from 6551 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 SEC ; Flag byte received
RTS RTS
@ -137,16 +147,19 @@ NMI_CODE
END_CODE END_CODE
; sign on string
LAB_mess LAB_mess
.byte $0D,$0A,"6502 EhBASIC [C]old/[W]arm ?",$00 .byte $0D,$0A,"Symon 6502 SBC (c) 2012, Seth Morabito"
; sign on string .byte $0D,$0A,"[C]old/[W]arm ?",$00
; system vectors ; system vectors
.segment "VECTORS" .segment "VECTORS"
.org $FFFA .org $FFFA
.word 0 ; NMI vector .word NMI_vec ; NMI vector
.word RES_vec ; RESET vector .word RES_vec ; RESET vector
.word 0 ; IRQ vector .word IRQ_vec ; IRQ vector

View File

@ -3,7 +3,7 @@
;; ;;
.alias iobase $8000 .alias iobase $8800
.alias iostatus [iobase + 1] .alias iostatus [iobase + 1]
.alias iocmd [iobase + 2] .alias iocmd [iobase + 2]
.alias ioctrl [iobase + 3] .alias ioctrl [iobase + 3]
@ -11,7 +11,7 @@
.org $0300 .org $0300
start: cli start: cli
lda #$09 lda #$0b
sta iocmd ; Set command status sta iocmd ; Set command status
lda #$1a lda #$1a
sta ioctrl ; 0 stop bits, 8 bit word, 2400 baud sta ioctrl ; 0 stop bits, 8 bit word, 2400 baud

Binary file not shown.

View File

@ -6,17 +6,17 @@ public interface Preferences {
public static final int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300; 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 int DEFAULT_BORDER_WIDTH = 10;
public static final boolean DEFAULT_HALT_ON_BREAK = true;
public JDialog getDialog(); public JDialog getDialog();
public int getProgramStartAddress(); public int getProgramStartAddress();
public int getAciaAddress();
public int getBorderWidth(); public int getBorderWidth();
public boolean getHaltOnBreak();
public void updateUi(); public void updateUi();
} }

View File

@ -33,9 +33,9 @@ import com.loomcom.symon.exceptions.SymonException;
import com.loomcom.symon.ui.Console; import com.loomcom.symon.ui.Console;
import com.loomcom.symon.ui.PreferencesDialog; import com.loomcom.symon.ui.PreferencesDialog;
import com.loomcom.symon.ui.StatusPanel; import com.loomcom.symon.ui.StatusPanel;
import sun.rmi.rmic.iiop.DirectoryLoader;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
@ -55,7 +55,7 @@ import java.util.logging.Logger;
* with a basic 80x25 character display. * 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. // Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000; 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_BASE = 0xC000;
private static final int ROM_SIZE = 0x4000; 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 // 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 // to refresh the view on every simulated clock cycle. Instead, we will only refresh the view after this
// number of steps when running normally. // number of steps when running normally.
@ -100,25 +103,37 @@ public class Simulator implements ActionListener, Observer {
// requested // requested
private int stepsSinceLastUpdate = 0; private int stepsSinceLastUpdate = 0;
private JFrame mainWindow; /**
private RunLoop runLoop; * The Main Window is the primary control point for the simulator.
private Console console; * It is in charge of the menu, and sub-windows. It also shows the
private StatusPanel statusPane; * CPU status at all times.
*/
private JFrame mainWindow;
private JButton runStopButton; /**
private JButton stepButton; * The Console Window is connected to the ACIA's TX and RX lines.
private JButton resetButton; */
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 * The Zero Page Window shows the contents of page 0.
// methods to enable and disable menus as required. */
private JFrame zeroPageWindow;
// Menu Items private SimulatorMenu menuBar;
private JMenuItem loadProgramItem;
private JMenuItem loadRomItem; private RunLoop runLoop;
private Console console;
private StatusPanel statusPane;
private JButton runStopButton;
private JButton stepButton;
private JButton resetButton;
private JFileChooser fileChooser; private JFileChooser fileChooser;
private PreferencesDialog preferences; private PreferencesDialog preferences;
@ -154,10 +169,11 @@ public class Simulator implements ActionListener, Observer {
mainWindow.getContentPane().setLayout(new BorderLayout()); mainWindow.getContentPane().setLayout(new BorderLayout());
// The Menu // The Menu
mainWindow.setJMenuBar(createMenuBar()); menuBar = new SimulatorMenu();
mainWindow.setJMenuBar(menuBar);
// UI components used for I/O. // 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(); this.statusPane = new StatusPanel();
// File Chooser // File Chooser
@ -166,15 +182,11 @@ public class Simulator implements ActionListener, Observer {
preferences.addObserver(this); preferences.addObserver(this);
// Panel for Console and Buttons // Panel for Console and Buttons
JPanel controlsContainer = new JPanel(); JPanel consoleContainer = new JPanel();
JPanel buttonContainer = new JPanel(); JPanel buttonContainer = new JPanel();
Dimension buttonPanelSize = new Dimension(console.getWidth(), 36);
buttonContainer.setMinimumSize(buttonPanelSize); consoleContainer.setLayout(new BorderLayout());
buttonContainer.setMaximumSize(buttonPanelSize); consoleContainer.setBorder(new EmptyBorder(10, 10, 10, 0));
buttonContainer.setPreferredSize(buttonPanelSize);
controlsContainer.setLayout(new BorderLayout());
buttonContainer.setLayout(new FlowLayout()); buttonContainer.setLayout(new FlowLayout());
runStopButton = new JButton("Run"); runStopButton = new JButton("Run");
@ -186,8 +198,8 @@ public class Simulator implements ActionListener, Observer {
buttonContainer.add(resetButton); buttonContainer.add(resetButton);
// Left side - console // Left side - console
controlsContainer.add(console, BorderLayout.PAGE_START); consoleContainer.add(console, BorderLayout.CENTER);
mainWindow.getContentPane().add(controlsContainer, BorderLayout.LINE_START); mainWindow.getContentPane().add(consoleContainer, BorderLayout.LINE_START);
// Right side - status pane // Right side - status pane
mainWindow.getContentPane().add(statusPane, BorderLayout.LINE_END); mainWindow.getContentPane().add(statusPane, BorderLayout.LINE_END);
@ -195,9 +207,27 @@ public class Simulator implements ActionListener, Observer {
// Bottom - buttons. // Bottom - buttons.
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END); mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
runStopButton.addActionListener(this); runStopButton.addActionListener(new ActionListener() {
stepButton.addActionListener(this); public void actionPerformed(ActionEvent actionEvent) {
resetButton.addActionListener(this); 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); mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@ -207,78 +237,6 @@ public class Simulator implements ActionListener, Observer {
mainWindow.setVisible(true); 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() { private void handleStart() {
// Shift focus to the console. // Shift focus to the console.
console.requestFocus(); console.requestFocus();
@ -311,85 +269,10 @@ public class Simulator implements ActionListener, Observer {
// TODO: Update memory window, if frame is visible. // TODO: Update memory window, if frame is visible.
} }
// TODO: Alert user of errors. /*
private void handleRomLoad() { * Perform a reset.
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.
*/ */
private void handleProgramLoad() { private void handleReset() {
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() {
if (runLoop != null && runLoop.isRunning()) { if (runLoop != null && runLoop.isRunning()) {
runLoop.requestStop(); runLoop.requestStop();
runLoop.interrupt(); runLoop.interrupt();
@ -412,7 +295,6 @@ public class Simulator implements ActionListener, Observer {
}); });
} catch (MemoryAccessException ex) { } catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Exception during simulator reset: " + ex.getMessage()); 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. * Perform a single step of the simulated system.
*/ */
@ -516,7 +387,7 @@ public class Simulator implements ActionListener, Observer {
Simulator simulator = new Simulator(); Simulator simulator = new Simulator();
simulator.createAndShowUi(); simulator.createAndShowUi();
// Reset the simulator. // Reset the simulator.
simulator.coldReset(); simulator.handleReset();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -533,8 +404,6 @@ public class Simulator implements ActionListener, Observer {
public void update(Observable observable, Object o) { public void update(Observable observable, Object o) {
// Instance equality should work here, there is only one instance. // Instance equality should work here, there is only one instance.
if (observable == preferences) { if (observable == preferences) {
// TODO: Update ACIA base address if it has changed.
int oldBorderWidth = console.getBorderWidth(); int oldBorderWidth = console.getBorderWidth();
if (oldBorderWidth != preferences.getBorderWidth()) { if (oldBorderWidth != preferences.getBorderWidth()) {
// Resize the main window if the border width has changed. // Resize the main window if the border width has changed.
@ -544,7 +413,6 @@ public class Simulator implements ActionListener, Observer {
} }
} }
/** /**
* The main run thread. * The main run thread.
*/ */
@ -569,16 +437,14 @@ public class Simulator implements ActionListener, Observer {
console.startListening(); console.startListening();
// Don't allow step while the simulator is running // Don't allow step while the simulator is running
stepButton.setEnabled(false); stepButton.setEnabled(false);
loadProgramItem.setEnabled(false); menuBar.simulatorDidStart();
loadRomItem.setEnabled(false);
// Toggle the state of the run button // Toggle the state of the run button
runStopButton.setText("Stop"); runStopButton.setText("Stop");
} }
}); });
try { try {
// TODO: Interrupts - both software and hardware. i.e., jump to address stored in FFFE/FFFF on BRK. while (isRunning && !(preferences.getHaltOnBreak() && cpu.getBreakFlag())) {
while (isRunning && !cpu.getBreakFlag()) {
step(); step();
} }
} catch (SymonException ex) { } catch (SymonException ex) {
@ -591,16 +457,235 @@ public class Simulator implements ActionListener, Observer {
console.stopListening(); console.stopListening();
// Allow step while the simulator is stopped // Allow step while the simulator is stopped
stepButton.setEnabled(true); stepButton.setEnabled(true);
loadProgramItem.setEnabled(true); menuBar.simulatorDidStop();
loadRomItem.setEnabled(true);
runStopButton.setText("Run"); runStopButton.setText("Run");
// Now update the state // Now update the state
statusPane.updateState(cpu); statusPane.updateState(cpu);
} }
}); });
logger.log(Level.INFO, "Exiting main run loop. BREAK=" + cpu.getBreakBit() + "; RUN_FLAG=" + isRunning);
isRunning = false; 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);
}
}
} }

View File

@ -170,8 +170,8 @@ public class Acia extends Device {
*/ */
private long calculateBaudRateDelay() { private long calculateBaudRateDelay() {
if (baudRate > 0) { if (baudRate > 0) {
// This is a pretty rough approximation based on 8 bits per character, // TODO: This is a pretty rough approximation based on 8 bits per character,
// and 1/baudRate per bit. // and 1/baudRate per bit. It could certainly be improved
return (long)((1.0 / baudRate) * 1000000000 * 8); return (long)((1.0 / baudRate) * 1000000000 * 8);
} else { } else {
return 0; return 0;

View File

@ -1,5 +1,6 @@
package com.loomcom.symon.ui; package com.loomcom.symon.ui;
import java.awt.*;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.KeyListener; import java.awt.event.KeyListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
@ -36,12 +37,8 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
private FifoRingBuffer<Character> typeAheadBuffer; private FifoRingBuffer<Character> typeAheadBuffer;
public Console() { public Console(int columns, int rows, Font font) {
this(DEFAULT_COLUMNS, DEFAULT_ROWS); super(new Vt100TerminalModel(columns, rows), font);
}
public Console(int columns, int rows) {
super(new Vt100TerminalModel(columns, rows));
// A small type-ahead buffer, as might be found in any real // A small type-ahead buffer, as might be found in any real
// VT100-style serial terminal. // VT100-style serial terminal.
this.typeAheadBuffer = new FifoRingBuffer<Character>(128); this.typeAheadBuffer = new FifoRingBuffer<Character>(128);

View File

@ -12,13 +12,13 @@ public class PreferencesDialog extends Observable implements Preferences {
private final JDialog dialog; private final JDialog dialog;
private JTextField aciaAddressField; private JCheckBox haltOnBreakCheckBox;
private JTextField programLoadAddressField; private JTextField programLoadAddressField;
private JTextField borderWidthField; private JTextField borderWidthField;
private int programLoadAddress = DEFAULT_PROGRAM_LOAD_ADDRESS; private int programLoadAddress = DEFAULT_PROGRAM_LOAD_ADDRESS;
private int aciaAddress = DEFAULT_ACIA_ADDRESS;
private int borderWidth = DEFAULT_BORDER_WIDTH; private int borderWidth = DEFAULT_BORDER_WIDTH;
private boolean haltOnBreak = DEFAULT_HALT_ON_BREAK;
public PreferencesDialog(Frame parent, boolean modal) { public PreferencesDialog(Frame parent, boolean modal) {
this.dialog = new JDialog(parent, modal); this.dialog = new JDialog(parent, modal);
@ -43,15 +43,14 @@ public class PreferencesDialog extends Observable implements Preferences {
GridBagLayout layout = new GridBagLayout(); GridBagLayout layout = new GridBagLayout();
settingsContainer.setLayout(layout); 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 programLoadAddressLabel = new JLabel("Program Load Address");
final JLabel borderWidthLabel = new JLabel("Console Border Width"); final JLabel borderWidthLabel = new JLabel("Console Border Width");
aciaAddressField = new JTextField(8); haltOnBreakCheckBox = new JCheckBox();
programLoadAddressField = new JTextField(8); programLoadAddressField = new JTextField(8);
borderWidthField = new JTextField(8); borderWidthField = new JTextField(8);
aciaAddressLabel.setLabelFor(aciaAddressField);
programLoadAddressLabel.setLabelFor(programLoadAddressField); programLoadAddressLabel.setLabelFor(programLoadAddressField);
borderWidthLabel.setLabelFor(borderWidthField); borderWidthLabel.setLabelFor(borderWidthField);
@ -61,11 +60,10 @@ public class PreferencesDialog extends Observable implements Preferences {
constraints.fill = GridBagConstraints.HORIZONTAL; constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 0; constraints.gridx = 0;
constraints.gridy = 0; constraints.gridy = 0;
settingsContainer.add(haltOnBreakLabel, constraints);
settingsContainer.add(aciaAddressLabel, constraints);
constraints.gridx = 1; constraints.gridx = 1;
settingsContainer.add(aciaAddressField, constraints); settingsContainer.add(haltOnBreakCheckBox, constraints);
constraints.gridy = 1; constraints.gridy = 1;
constraints.gridx = 0; constraints.gridx = 0;
@ -94,8 +92,8 @@ public class PreferencesDialog extends Observable implements Preferences {
applyButton.addActionListener(new ActionListener() { applyButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) { public void actionPerformed(ActionEvent actionEvent) {
haltOnBreak = haltOnBreakCheckBox.isSelected();
programLoadAddress = hexToInt(programLoadAddressField.getText()); programLoadAddress = hexToInt(programLoadAddressField.getText());
aciaAddress = hexToInt(aciaAddressField.getText());
borderWidth = Integer.parseInt(borderWidthField.getText()); borderWidth = Integer.parseInt(borderWidthField.getText());
updateUi(); updateUi();
// TODO: Actually check to see if values have changed, don't assume. // 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; return programLoadAddress;
} }
public int getAciaAddress() {
return aciaAddress;
}
/** /**
* @return The width of the console border, in pixels. * @return The width of the console border, in pixels.
*/ */
@ -129,8 +123,15 @@ public class PreferencesDialog extends Observable implements Preferences {
return borderWidth; return borderWidth;
} }
/**
* @return True if 'halt on break' is desired, false otherwise.
*/
public boolean getHaltOnBreak() {
return haltOnBreak;
}
public void updateUi() { public void updateUi() {
aciaAddressField.setText(intToHex(aciaAddress)); haltOnBreakCheckBox.setSelected(haltOnBreak);
programLoadAddressField.setText(intToHex(programLoadAddress)); programLoadAddressField.setText(intToHex(programLoadAddress));
borderWidthField.setText(Integer.toString(borderWidth)); borderWidthField.setText(Integer.toString(borderWidth));
} }

View File

@ -26,6 +26,7 @@ public class StatusPanel extends JPanel {
private final ImageIcon negativeOn; private final ImageIcon negativeOn;
private final ImageIcon negativeOff; private final ImageIcon negativeOff;
private final JLabel statusFlagsLabel;
private final JLabel carryFlagLabel; private final JLabel carryFlagLabel;
private final JLabel zeroFlagLabel; private final JLabel zeroFlagLabel;
private final JLabel irqDisableFlagLabel; private final JLabel irqDisableFlagLabel;
@ -40,7 +41,6 @@ public class StatusPanel extends JPanel {
private JTextField aField; private JTextField aField;
private JTextField xField; private JTextField xField;
private JTextField yField; private JTextField yField;
// private JTextField stepCountField;
private final JLabel opcodeLabel; private final JLabel opcodeLabel;
private final JLabel pcLabel; private final JLabel pcLabel;
@ -48,11 +48,10 @@ public class StatusPanel extends JPanel {
private final JLabel aLabel; private final JLabel aLabel;
private final JLabel xLabel; private final JLabel xLabel;
private final JLabel yLabel; private final JLabel yLabel;
// private final JLabel stepCountLabel;
private static final int EMPTY_BORDER = 5; private static final int EMPTY_BORDER = 10;
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 4, 0, 0); private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
private static final Font LABEL_FONT = new Font("sansserif", Font.PLAIN, 11); private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 12);
public StatusPanel() { public StatusPanel() {
super(); super();
@ -63,7 +62,10 @@ public class StatusPanel extends JPanel {
setBorder(BorderFactory.createCompoundBorder(emptyBorder, etchedBorder)); 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(); JPanel statusFlagsPanel = new JPanel();
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT); statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
@ -101,37 +103,72 @@ public class StatusPanel extends JPanel {
statusFlagsPanel.add(carryFlagLabel); statusFlagsPanel.add(carryFlagLabel);
// Create and add register and address labels // Create and add register and address labels
opcodeLabel = makeLabel("Instruction"); statusFlagsLabel = makeLabel("Flags");
pcLabel = makeLabel("Program Counter"); opcodeLabel = makeLabel("IR");
spLabel = makeLabel("Stack Pointer"); pcLabel = makeLabel("PC");
aLabel = makeLabel("Accumulator"); spLabel = makeLabel("SP");
xLabel = makeLabel("X Register"); aLabel = makeLabel("A");
yLabel = makeLabel("Y Register"); xLabel = makeLabel("X");
// stepCountLabel = new JLabel("Steps"); yLabel = makeLabel("Y");
opcodeField = makeTextField(); opcodeField = makeTextField(LARGE_TEXT_FIELD_SIZE);
pcField = makeTextField(); pcField = makeTextField(LARGE_TEXT_FIELD_SIZE);
spField = makeTextField(); spField = makeTextField(SMALL_TEXT_FIELD_SIZE);
aField = makeTextField(); aField = makeTextField(SMALL_TEXT_FIELD_SIZE);
xField = makeTextField(); xField = makeTextField(SMALL_TEXT_FIELD_SIZE);
yField = makeTextField(); yField = makeTextField(SMALL_TEXT_FIELD_SIZE);
// stepCountField = new JTextField("");
add(statusFlagsPanel); constraints.anchor = GridBagConstraints.LINE_START;
add(opcodeLabel); constraints.gridwidth = 2;
add(opcodeField); constraints.gridx = 0;
add(pcLabel); constraints.gridy = 0;
add(pcField); add(statusFlagsLabel, constraints);
add(spLabel); constraints.gridy = 1;
add(spField); add(statusFlagsPanel, constraints);
add(aLabel);
add(aField); constraints.insets = new Insets(5, 0, 0, 0);
add(xLabel); constraints.gridy = 2;
add(xField); add(opcodeLabel, constraints);
add(yLabel);
add(yField); constraints.insets = new Insets(0, 0, 0, 0);
// add(stepCountLabel); constraints.gridy = 3;
// add(stepCountField); 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()); aField.setText(cpu.getAccumulatorStatus());
xField.setText(cpu.getXRegisterStatus()); xField.setText(cpu.getXRegisterStatus());
yField.setText(cpu.getYRegisterStatus()); yField.setText(cpu.getYRegisterStatus());
// stepCountField.setText(Long.toString(cpu.getStepCounter()));
repaint(); repaint();
} }
@ -228,10 +264,16 @@ public class StatusPanel extends JPanel {
return label; 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(""); JTextField textField = new JTextField("");
textField.setAlignmentX(LEFT_ALIGNMENT); textField.setAlignmentX(LEFT_ALIGNMENT);
textField.setEditable(false); textField.setEditable(false);
textField.setMinimumSize(size);
textField.setMaximumSize(size);
textField.setPreferredSize(size);
return textField; return textField;
} }