mirror of
https://github.com/sethm/symon.git
synced 2024-06-01 08:41:32 +00:00
Checkpoint of my Swing UI refactoring. Usable for output-only programs, but only barely.
This commit is contained in:
parent
5ce81ef82c
commit
ed36690e9e
4
pom.xml
4
pom.xml
|
@ -81,7 +81,7 @@
|
|||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.loomcom.symon.MainWindow</mainClass>
|
||||
<mainClass>com.loomcom.symon.Simulator</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
|
@ -114,7 +114,7 @@
|
|||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.loomcom.symon.MainWindow</mainClass>
|
||||
<mainClass>com.loomcom.symon.Simulator</mainClass>
|
||||
<packageName>com.loomcom.symon</packageName>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
18
src/main/java/com/loomcom/symon/Preferences.java
Normal file
18
src/main/java/com/loomcom/symon/Preferences.java
Normal file
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p/>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
124
src/main/java/com/loomcom/symon/ui/PreferencesDialog.java
Normal file
124
src/main/java/com/loomcom/symon/ui/PreferencesDialog.java
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user