mirror of
https://codeberg.org/sethm/symon.git
synced 2026-11-09 19:32:24 +00:00
Checkpoint of my Swing UI refactoring. Usable for output-only programs, but only barely.
This commit is contained in:
1 parent
5ce81ef82c
commit
ed36690e9e
13 files changed
+698
-581
No files matched your search
@@ -81,7 +81,7 @@
|
|||||||
</descriptorRefs>
|
</descriptorRefs>
|
||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
<mainClass>com.loomcom.symon.MainWindow</mainClass>
|
<mainClass>com.loomcom.symon.Simulator</mainClass>
|
||||||
</manifest>
|
</manifest>
|
||||||
</archive>
|
</archive>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
<mainClass>com.loomcom.symon.MainWindow</mainClass>
|
<mainClass>com.loomcom.symon.Simulator</mainClass>
|
||||||
<packageName>com.loomcom.symon</packageName>
|
<packageName>com.loomcom.symon</packageName>
|
||||||
</manifest>
|
</manifest>
|
||||||
<manifestEntries>
|
<manifestEntries>
|
||||||
|
|||||||
+19
-7
@@ -3,18 +3,30 @@
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
|
|
||||||
.alias cr $0d
|
.alias iobase $c000
|
||||||
.alias lf $0a
|
.alias iostatus [iobase + 1]
|
||||||
|
.alias iocmd [iobase + 2]
|
||||||
.alias out $c000
|
.alias ioctrl [iobase + 3]
|
||||||
|
|
||||||
.org $0300
|
.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
|
loop: lda string,x
|
||||||
beq start ; If A is 0, loop back and start again
|
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.
|
jmp loop ; Repeat.
|
||||||
|
|
||||||
string: .byte "Hello, 6502 world! ", 0
|
string: .byte "Hello, 6502 world! ", 0
|
||||||
@@ -111,6 +111,7 @@ public class Cpu implements InstructionTable {
|
|||||||
decimalModeFlag = false;
|
decimalModeFlag = false;
|
||||||
breakFlag = false;
|
breakFlag = false;
|
||||||
overflowFlag = false;
|
overflowFlag = false;
|
||||||
|
zeroFlag = false;
|
||||||
|
|
||||||
// Clear illegal opcode trap.
|
// Clear illegal opcode trap.
|
||||||
opTrap = false;
|
opTrap = false;
|
||||||
@@ -1375,7 +1376,7 @@ public class Cpu implements InstructionTable {
|
|||||||
return (zp + getYRegister()) & 0xff;
|
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_H, (address & 0xff00) >>> 8);
|
||||||
bus.write(RST_VECTOR_L, address & 0x00ff);
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
package com.loomcom.symon;
|
||||||
|
|
||||||
import java.util.logging.*;
|
import com.loomcom.symon.devices.Acia;
|
||||||
|
import com.loomcom.symon.devices.Memory;
|
||||||
import com.loomcom.symon.devices.*;
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
import com.loomcom.symon.exceptions.*;
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
import com.loomcom.symon.ui.UiUpdater;
|
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.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
/**
|
public class Simulator implements ActionListener, Observer {
|
||||||
* 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;
|
|
||||||
|
|
||||||
|
// 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;
|
||||||
private static final int BUS_TOP = 0xffff;
|
private static final int BUS_TOP = 0xffff;
|
||||||
|
|
||||||
@@ -40,22 +34,316 @@ public class Simulator implements Runnable {
|
|||||||
|
|
||||||
public static final int ACIA_BASE = 0xc000;
|
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 {
|
public Simulator() throws MemoryRangeException {
|
||||||
|
this.aciaBuffer = new StringBuffer(512);
|
||||||
this.acia = new Acia(ACIA_BASE);
|
this.acia = new Acia(ACIA_BASE);
|
||||||
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
||||||
this.cpu = new Cpu();
|
this.cpu = new Cpu();
|
||||||
this.ram = new Memory(MEMORY_BASE, MEMORY_SIZE, false);
|
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);
|
this.rom = new Memory(ROM_BASE, ROM_SIZE, false);
|
||||||
|
|
||||||
bus.addCpu(cpu);
|
bus.addCpu(cpu);
|
||||||
bus.addDevice(acia);
|
bus.addDevice(acia);
|
||||||
bus.addDevice(ram);
|
bus.addDevice(ram);
|
||||||
bus.addDevice(rom);
|
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);
|
cpu.setResetVector(startAddress);
|
||||||
|
|
||||||
int addr = startAddress, i;
|
int addr = startAddress, i;
|
||||||
@@ -65,12 +353,72 @@ public class Simulator implements Runnable {
|
|||||||
|
|
||||||
logger.log(Level.INFO, "Loaded " + i + " bytes at address 0x" +
|
logger.log(Level.INFO, "Loaded " + i + " bytes at address 0x" +
|
||||||
Integer.toString(startAddress, 16));
|
Integer.toString(startAddress, 16));
|
||||||
|
|
||||||
|
// After loading, be sure to reset and
|
||||||
|
// Reset (but don't clear memory, naturally)
|
||||||
|
cpu.reset();
|
||||||
|
|
||||||
|
// Immediately update the UI.
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
updateConsoleAndStatus(cpu);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUiUpdater(UiUpdater uiUpdater) {
|
/**
|
||||||
this.uiUpdater = uiUpdater;
|
* 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
public boolean isRunning() {
|
||||||
return isRunning;
|
return isRunning;
|
||||||
}
|
}
|
||||||
@@ -79,37 +427,22 @@ public class Simulator implements Runnable {
|
|||||||
isRunning = false;
|
isRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() throws MemoryAccessException {
|
|
||||||
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() {
|
public void run() {
|
||||||
logger.log(Level.INFO, "Entering 'run' on main Simulator thread");
|
logger.log(Level.INFO, "Starting main run loop.");
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
uiUpdater.simulatorDidStart();
|
// 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");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// TODO: Interrupts - both software and hardware. i.e., jump to address stored in FFFE/FFFF on BRK.
|
||||||
while (isRunning && !cpu.getBreakFlag()) {
|
while (isRunning && !cpu.getBreakFlag()) {
|
||||||
step();
|
step();
|
||||||
}
|
}
|
||||||
@@ -118,40 +451,19 @@ public class Simulator implements Runnable {
|
|||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log(Level.INFO, "Exiting 'run'. BREAK=" + cpu.getBreakBit() + "; RUN_FLAG=" + isRunning);
|
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;
|
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
uiUpdater.updateUi();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
updatesRequested = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,39 +1,45 @@
|
|||||||
package com.loomcom.symon.devices;
|
package com.loomcom.symon.devices;
|
||||||
|
|
||||||
import com.loomcom.symon.exceptions.*;
|
import com.loomcom.symon.exceptions.*;
|
||||||
import com.loomcom.symon.util.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a simulation of the MOS 6551 ACIA, with limited
|
* This is a simulation of the MOS 6551 ACIA, with limited
|
||||||
* functionality. Interrupts are not supported.
|
* functionality. Interrupts are not supported.
|
||||||
*
|
* <p/>
|
||||||
* Unlike a 16550 UART, the 6551 ACIA has only one-byte transmit and
|
* Unlike a 16550 UART, the 6551 ACIA has only one-byte transmit and
|
||||||
* receive buffers. It is the programmer's responsibility to check the
|
* receive buffers. It is the programmer's responsibility to check the
|
||||||
* status (full or empty) for transmit and receive buffers before
|
* status (full or empty) for transmit and receive buffers before
|
||||||
* writing / reading. However, in the simulation we maintain two
|
* writing / reading.
|
||||||
* small buffers of 256 characters, since we only wake up to check for
|
|
||||||
* keyboard input and do output every 500 instructions.
|
|
||||||
*/
|
*/
|
||||||
public class Acia extends Device {
|
public class Acia extends Device {
|
||||||
|
|
||||||
public static final int ACIA_SIZE = 4;
|
public static final int ACIA_SIZE = 4;
|
||||||
public static final int BUF_LEN = 256;
|
|
||||||
|
|
||||||
static final int DATA_REG = 0;
|
static final int DATA_REG = 0;
|
||||||
static final int STAT_REG = 1;
|
static final int STAT_REG = 1;
|
||||||
static final int CMND_REG = 2;
|
static final int CMND_REG = 2;
|
||||||
static final int CTRL_REG = 3;
|
static final int CTRL_REG = 3;
|
||||||
|
|
||||||
/** Register addresses */
|
/**
|
||||||
|
* Register addresses
|
||||||
|
*/
|
||||||
private int baseAddress;
|
private int baseAddress;
|
||||||
|
|
||||||
/** Registers. These are ignored in the current implementation. */
|
/**
|
||||||
|
* Registers. These are ignored in the current implementation.
|
||||||
|
*/
|
||||||
private int commandRegister;
|
private int commandRegister;
|
||||||
private int controlRegister;
|
private int controlRegister;
|
||||||
|
|
||||||
/** Read/Write buffers */
|
/**
|
||||||
private FifoRingBuffer rxBuffer = new FifoRingBuffer(BUF_LEN);
|
* Read/Write buffers
|
||||||
private FifoRingBuffer txBuffer = new FifoRingBuffer(BUF_LEN);
|
*/
|
||||||
|
private int rxChar = 0;
|
||||||
|
private int txChar = 0;
|
||||||
|
|
||||||
|
private boolean rxFull = false;
|
||||||
|
private boolean txFull = false;
|
||||||
|
|
||||||
public Acia(int address) throws MemoryRangeException {
|
public Acia(int address) throws MemoryRangeException {
|
||||||
super(address, ACIA_SIZE, "ACIA");
|
super(address, ACIA_SIZE, "ACIA");
|
||||||
@@ -44,14 +50,10 @@ public class Acia extends Device {
|
|||||||
public int read(int address) throws MemoryAccessException {
|
public int read(int address) throws MemoryAccessException {
|
||||||
switch (address) {
|
switch (address) {
|
||||||
case DATA_REG:
|
case DATA_REG:
|
||||||
try {
|
|
||||||
return rxRead();
|
return rxRead();
|
||||||
} catch (FifoUnderrunException ex) {
|
|
||||||
throw new MemoryAccessException("Buffer underrun");
|
|
||||||
}
|
|
||||||
case STAT_REG:
|
case STAT_REG:
|
||||||
return ((rxBuffer.isEmpty() ? 0x00 : 0x08) |
|
return ((rxFull ? 0x08 : 0x00) |
|
||||||
(txBuffer.isEmpty() ? 0x10 : 0x00));
|
(txFull ? 0x00 : 0x10));
|
||||||
case CMND_REG:
|
case CMND_REG:
|
||||||
return commandRegister;
|
return commandRegister;
|
||||||
case CTRL_REG:
|
case CTRL_REG:
|
||||||
@@ -86,39 +88,45 @@ public class Acia extends Device {
|
|||||||
return "ACIA@" + String.format("%04X", baseAddress);
|
return "ACIA@" + String.format("%04X", baseAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int rxRead() throws FifoUnderrunException {
|
public synchronized int rxRead() {
|
||||||
return rxBuffer.pop();
|
rxFull = false;
|
||||||
|
return rxChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rxWrite(int data) {
|
public synchronized void rxWrite(int data) {
|
||||||
rxBuffer.push(data);
|
rxFull = true;
|
||||||
|
rxChar = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int txRead() throws FifoUnderrunException {
|
public synchronized int txRead() {
|
||||||
return txBuffer.pop();
|
txFull = false;
|
||||||
|
return txChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void txWrite(int data) {
|
public synchronized void txWrite(int data) {
|
||||||
txBuffer.push(data);
|
txFull = true;
|
||||||
|
txChar = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if there is character data in the TX register.
|
* @return true if there is character data in the TX register.
|
||||||
*/
|
*/
|
||||||
public boolean hasTxChar() {
|
public boolean hasTxChar() {
|
||||||
return !txBuffer.isEmpty();
|
return txFull;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if there is character data in the RX register.
|
* @return true if there is character data in the RX register.
|
||||||
*/
|
*/
|
||||||
public boolean hasRxChar() {
|
public boolean hasRxChar() {
|
||||||
return !rxBuffer.isEmpty();
|
return rxFull;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private synchronized void reset() {
|
||||||
txBuffer.reset();
|
txChar = 0;
|
||||||
rxBuffer.reset();
|
txFull = false;
|
||||||
|
rxChar = 0;
|
||||||
|
rxFull = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,10 @@ import java.awt.event.MouseListener;
|
|||||||
|
|
||||||
import com.grahamedgecombe.jterminal.JTerminal;
|
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 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.
|
* 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 {
|
public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||||
|
|
||||||
// TODO: Pass in io threads, read and write to ACIA.
|
|
||||||
public Console() {
|
public Console() {
|
||||||
super();
|
super();
|
||||||
addKeyListener(this);
|
addKeyListener(this);
|
||||||
addMouseListener(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() {
|
public void reset() {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
+89
-51
@@ -6,12 +6,10 @@ package com.loomcom.symon.ui;
|
|||||||
import com.loomcom.symon.Cpu;
|
import com.loomcom.symon.Cpu;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.Border;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
public class StatusPane extends JPanel {
|
public class StatusPanel extends JPanel {
|
||||||
|
|
||||||
// The CPU to ask for state information.
|
|
||||||
private final Cpu cpu;
|
|
||||||
|
|
||||||
private final ImageIcon carryOn;
|
private final ImageIcon carryOn;
|
||||||
private final ImageIcon carryOff;
|
private final ImageIcon carryOff;
|
||||||
@@ -36,27 +34,36 @@ public class StatusPane extends JPanel {
|
|||||||
private final JLabel overflowFlagLabel;
|
private final JLabel overflowFlagLabel;
|
||||||
private final JLabel negativeFlagLabel;
|
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 opcodeLabel;
|
||||||
private final JLabel pcLabel;
|
private final JLabel pcLabel;
|
||||||
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 final JLabel stepCountLabel;
|
||||||
|
|
||||||
private static final int WIDTH = 134;
|
private static final int WIDTH = 140;
|
||||||
private static final int HEIGHT = 27;
|
private static final int HEIGHT = 200;
|
||||||
|
|
||||||
public StatusPane(Cpu cpu) {
|
public StatusPanel() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.cpu = cpu;
|
|
||||||
|
|
||||||
Dimension dimensions = new Dimension(WIDTH, HEIGHT);
|
Dimension dimensions = new Dimension(WIDTH, HEIGHT);
|
||||||
|
|
||||||
setMinimumSize(dimensions);
|
setMinimumSize(dimensions);
|
||||||
setPreferredSize(dimensions);
|
setPreferredSize(dimensions);
|
||||||
setMaximumSize(dimensions);
|
setMaximumSize(dimensions);
|
||||||
|
|
||||||
|
setLayout(new GridLayout(13,1));
|
||||||
|
|
||||||
|
JPanel statusFlagsPanel = new JPanel();
|
||||||
|
|
||||||
carryOn = new ImageIcon(this.getClass().getResource("images/C_on.png"));
|
carryOn = new ImageIcon(this.getClass().getResource("images/C_on.png"));
|
||||||
carryOff = new ImageIcon(this.getClass().getResource("images/C_off.png"));
|
carryOff = new ImageIcon(this.getClass().getResource("images/C_off.png"));
|
||||||
zeroOn = new ImageIcon(this.getClass().getResource("images/Z_on.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);
|
overflowFlagLabel = new JLabel(overflowOff, JLabel.CENTER);
|
||||||
negativeFlagLabel = new JLabel(negativeOff, JLabel.CENTER);
|
negativeFlagLabel = new JLabel(negativeOff, JLabel.CENTER);
|
||||||
|
|
||||||
|
Border emptyBorder = BorderFactory.createEmptyBorder(8, 6, 0, 6);
|
||||||
|
|
||||||
// Create and add register and address labels
|
// 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();
|
opcodeField = new JTextField("");
|
||||||
this.pcLabel = new JLabel();
|
pcField = new JTextField("");
|
||||||
this.aLabel = new JLabel();
|
aField = new JTextField("");
|
||||||
this.xLabel = new JLabel();
|
xField = new JTextField("");
|
||||||
this.yLabel = new JLabel();
|
yField = new JTextField("");
|
||||||
this.stepCountLabel = new JLabel();
|
// stepCountField = new JTextField("");
|
||||||
|
|
||||||
this.opcodeLabel.setMinimumSize(new Dimension(100, 20));
|
opcodeField.setEditable(false);
|
||||||
this.pcLabel.setMinimumSize(new Dimension(80, 20));
|
pcField.setEditable(false);
|
||||||
this.aLabel.setMinimumSize(new Dimension(60, 20));
|
aField.setEditable(false);
|
||||||
this.xLabel.setMinimumSize(new Dimension(60, 20));
|
xField.setEditable(false);
|
||||||
this.yLabel.setMinimumSize(new Dimension(60, 20));
|
yField.setEditable(false);
|
||||||
this.stepCountLabel.setMinimumSize(new Dimension(120, 20));
|
// stepCountField.setEditable(false);
|
||||||
|
|
||||||
this.opcodeLabel.setPreferredSize(new Dimension(100, 20));
|
opcodeLabel.setMinimumSize(new Dimension(100, 20));
|
||||||
this.pcLabel.setPreferredSize(new Dimension(80, 20));
|
pcLabel.setMinimumSize(new Dimension(80, 20));
|
||||||
this.aLabel.setPreferredSize(new Dimension(60, 20));
|
aLabel.setMinimumSize(new Dimension(60, 20));
|
||||||
this.xLabel.setPreferredSize(new Dimension(60, 20));
|
xLabel.setMinimumSize(new Dimension(60, 20));
|
||||||
this.yLabel.setPreferredSize(new Dimension(60, 20));
|
yLabel.setMinimumSize(new Dimension(60, 20));
|
||||||
this.stepCountLabel.setPreferredSize(new Dimension(120, 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);
|
statusFlagsPanel.add(negativeFlagLabel);
|
||||||
this.add(overflowFlagLabel);
|
statusFlagsPanel.add(overflowFlagLabel);
|
||||||
this.add(breakFlagLabel);
|
statusFlagsPanel.add(breakFlagLabel);
|
||||||
this.add(decimalModeFlagLabel);
|
statusFlagsPanel.add(decimalModeFlagLabel);
|
||||||
this.add(irqDisableFlagLabel);
|
statusFlagsPanel.add(irqDisableFlagLabel);
|
||||||
this.add(zeroFlagLabel);
|
statusFlagsPanel.add(zeroFlagLabel);
|
||||||
this.add(carryFlagLabel);
|
statusFlagsPanel.add(carryFlagLabel);
|
||||||
|
|
||||||
this.add(opcodeLabel);
|
add(statusFlagsPanel);
|
||||||
this.add(pcLabel);
|
add(opcodeLabel);
|
||||||
this.add(aLabel);
|
add(opcodeField);
|
||||||
this.add(xLabel);
|
add(pcLabel);
|
||||||
this.add(yLabel);
|
add(pcField);
|
||||||
this.add(stepCountLabel);
|
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
|
// Update the Processor Status Flag display
|
||||||
int state = this.cpu.getProcessorStatus();
|
int state = cpu.getProcessorStatus();
|
||||||
|
|
||||||
carryFlagLabel.setIcon(iconForFlag(state, 0));
|
carryFlagLabel.setIcon(iconForFlag(state, 0));
|
||||||
zeroFlagLabel.setIcon(iconForFlag(state, 1));
|
zeroFlagLabel.setIcon(iconForFlag(state, 1));
|
||||||
@@ -137,12 +175,12 @@ public class StatusPane extends JPanel {
|
|||||||
negativeFlagLabel.setIcon(iconForFlag(state, 7));
|
negativeFlagLabel.setIcon(iconForFlag(state, 7));
|
||||||
|
|
||||||
// Update the register and address displays
|
// Update the register and address displays
|
||||||
opcodeLabel.setText(cpu.getOpcodeStatus());
|
opcodeField.setText(cpu.getOpcodeStatus());
|
||||||
pcLabel.setText(cpu.getProgramCounterStatus());
|
pcField.setText(cpu.getProgramCounterStatus());
|
||||||
aLabel.setText(cpu.getARegisterStatus());
|
aField.setText(cpu.getARegisterStatus());
|
||||||
xLabel.setText(cpu.getXRegisterStatus());
|
xField.setText(cpu.getXRegisterStatus());
|
||||||
yLabel.setText(cpu.getYRegisterStatus());
|
yField.setText(cpu.getYRegisterStatus());
|
||||||
stepCountLabel.setText(Long.toString(cpu.getStepCounter()));
|
// stepCountField.setText(Long.toString(cpu.getStepCounter()));
|
||||||
|
|
||||||
repaint();
|
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
|
@Test
|
||||||
public void readingBuffersUntilEmptyShouldResetStatus()
|
public void readingBuffersShouldResetStatus()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Acia acia = new Acia(0x0000);
|
Acia acia = new Acia(0x0000);
|
||||||
|
|
||||||
@@ -59,64 +59,4 @@ public class AciaTest {
|
|||||||
assertEquals(0x10, acia.read(0x0001));
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user