diff --git a/pom.xml b/pom.xml index 9724629..f07b74f 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ false - com.loomcom.symon.Simulator + com.loomcom.symon.Main diff --git a/src/main/java/com/loomcom/symon/Bus.java b/src/main/java/com/loomcom/symon/Bus.java index 650df3c..63f8070 100644 --- a/src/main/java/com/loomcom/symon/Bus.java +++ b/src/main/java/com/loomcom/symon/Bus.java @@ -26,6 +26,11 @@ package com.loomcom.symon; import com.loomcom.symon.devices.Device; import com.loomcom.symon.exceptions.MemoryAccessException; import com.loomcom.symon.exceptions.MemoryRangeException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.HashMap; +import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; @@ -45,15 +50,19 @@ public class Bus { // The CPU private Cpu cpu; - // Ordered list of IO devices. - private SortedSet devices; + // Ordered sets of IO devices, associated with their priority + private Map> deviceMap; + + // an array for quick lookup of adresses, brute-force style + private Device[] deviceAddressArray; + public Bus(int size) { this(0, size - 1); } public Bus(int startAddress, int endAddress) { - this.devices = new TreeSet(); + this.deviceMap = new HashMap>(); this.startAddress = startAddress; this.endAddress = endAddress; } @@ -65,7 +74,49 @@ public class Bus { public int endAddress() { return endAddress; } + + private void buildDeviceAddressArray() { + int size = (this.endAddress - this.startAddress) + 1; + deviceAddressArray = new Device[size]; + + // getDevices() provides an OrderedSet with devices ordered by priorities + for(Device device : getDevices()) { + MemoryRange range = device.getMemoryRange(); + for(int address = range.startAddress; address <= range.endAddress; ++address) { + deviceAddressArray[address - this.startAddress] = device; + } + } + + } + /** + * Add a device to the bus. + * + * @param device + * @param priority + * @throws MemoryRangeException + */ + public void addDevice(Device device, int priority) throws MemoryRangeException { + + MemoryRange range = device.getMemoryRange(); + if(range.startAddress() < this.startAddress || range.startAddress() > this.endAddress) { + throw new MemoryRangeException("start address of device " + device.getName() + " does not fall within the address range of the bus"); + } + if(range.endAddress() < this.startAddress || range.endAddress() > this.endAddress) { + throw new MemoryRangeException("end address of device " + device.getName() + " does not fall within the address range of the bus"); + } + + + SortedSet deviceSet = deviceMap.get(priority); + if(deviceSet == null) { + deviceSet = new TreeSet(); + deviceMap.put(priority, deviceSet); + } + + deviceSet.add(device); + buildDeviceAddressArray(); + } + /** * Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others. * @@ -73,21 +124,9 @@ public class Bus { * @throws MemoryRangeException */ public void addDevice(Device device) throws MemoryRangeException { - // Make sure there's no memory overlap. - MemoryRange memRange = device.getMemoryRange(); - for (Device d : devices) { - if (d.getMemoryRange().overlaps(memRange)) { - throw new MemoryRangeException("The device being added at " + - String.format("$%04X", memRange.startAddress()) + - " overlaps with an existing " + - "device, '" + d + "'"); - } - } - - // Add the device - device.setBus(this); - devices.add(device); + addDevice(device, 0); } + /** * Remove a device from the bus. @@ -95,9 +134,10 @@ public class Bus { * @param device */ public void removeDevice(Device device) { - if (devices.contains(device)) { - devices.remove(device); + for(SortedSet deviceSet : deviceMap.values()) { + deviceSet.remove(device); } + buildDeviceAddressArray(); } public void addCpu(Cpu cpu) { @@ -111,43 +151,39 @@ public class Bus { * device. */ public boolean isComplete() { - // Empty maps cannot be complete. - if (devices.isEmpty()) { - return false; + if(deviceAddressArray == null) { + buildDeviceAddressArray(); } - - // Loop over devices and add their size - int filledMemory = 0; - for (Device d : devices) { - filledMemory += d.getSize(); + + for(int address = startAddress; address <= endAddress; ++address) { + if(deviceAddressArray[address - startAddress] == null) { + return false; + } } - - // Returns if the total size of the devices fill the bus' memory space - return filledMemory == endAddress - startAddress + 1; + + return true; } public int read(int address) throws MemoryAccessException { - for (Device d : devices) { + Device d = deviceAddressArray[address - this.startAddress]; + if(d != null) { MemoryRange range = d.getMemoryRange(); - if (range.includes(address)) { - // Compute offset into this device's address space. - int devAddr = address - range.startAddress(); - return d.read(devAddr); - } + int devAddr = address - range.startAddress(); + return d.read(devAddr); } + throw new MemoryAccessException("Bus read failed. No device at address " + String.format("$%04X", address)); } public void write(int address, int value) throws MemoryAccessException { - for (Device d : devices) { + Device d = deviceAddressArray[address - this.startAddress]; + if(d != null) { MemoryRange range = d.getMemoryRange(); - if (range.includes(address)) { - // Compute offset into this device's address space. - int devAddr = address - range.startAddress(); - d.write(devAddr, value); - return; - } + int devAddr = address - range.startAddress(); + d.write(devAddr, value); + return; } + throw new MemoryAccessException("Bus write failed. No device at address " + String.format("$%04X", address)); } @@ -176,8 +212,20 @@ public class Bus { } public SortedSet getDevices() { - // Expose a copy of the device list, not the original - return new TreeSet(devices); + // create an ordered set of devices, ordered by device priorities + SortedSet devices = new TreeSet(); + + List priorities = new ArrayList(deviceMap.keySet()); + Collections.sort(priorities); + + for(int priority : priorities) { + SortedSet deviceSet = deviceMap.get(priority); + for(Device device : deviceSet) { + devices.add(device); + } + } + + return devices; } public Cpu getCpu() { diff --git a/src/main/java/com/loomcom/symon/Cpu.java b/src/main/java/com/loomcom/symon/Cpu.java index de6d705..4349c5c 100644 --- a/src/main/java/com/loomcom/symon/Cpu.java +++ b/src/main/java/com/loomcom/symon/Cpu.java @@ -25,6 +25,8 @@ package com.loomcom.symon; import com.loomcom.symon.exceptions.MemoryAccessException; import com.loomcom.symon.util.HexUtil; +import java.util.logging.Level; +import java.util.logging.Logger; /** * This class provides a simulation of the MOS 6502 CPU's state machine. @@ -75,6 +77,9 @@ public class Cpu implements InstructionTable { /* Internal scratch space */ private int lo = 0, hi = 0; // Used in address calculation private int tmp; // Temporary storage + + /* start time of op execution, needed for speed simulation */ + private long opBeginTime; /** * Construct a new CPU. @@ -157,6 +162,7 @@ public class Cpu implements InstructionTable { * Performs an individual instruction cycle. */ public void step() throws MemoryAccessException { + opBeginTime = System.nanoTime(); // Store the address from which the IR was read, for debugging state.lastPc = state.pc; @@ -1324,11 +1330,10 @@ public class Cpu implements InstructionTable { if (clockSteps == 0) { clockSteps = 1; } - long startTime = System.nanoTime(); - long stopTime = startTime + (CLOCK_IN_NS * clockSteps); - // Busy loop - while (System.nanoTime() < stopTime) { - ; + long opScheduledEnd = opBeginTime + clockSteps; + long now = System.nanoTime(); + while(now < opScheduledEnd) { + now = System.nanoTime(); } } diff --git a/src/main/java/com/loomcom/symon/Main.java b/src/main/java/com/loomcom/symon/Main.java new file mode 100644 index 0000000..cb116be --- /dev/null +++ b/src/main/java/com/loomcom/symon/Main.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2008-2013 Seth J. Morabito + * Maik Merten + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +package com.loomcom.symon; + +import com.loomcom.symon.machines.MulticompMachine; +import com.loomcom.symon.machines.SymonMachine; +import java.util.Locale; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +public class Main { + + /** + * Main entry point to the simulator. Creates a simulator and shows the main + * window. + * + * @param args + */ + public static void main(String args[]) throws Exception { + + Class machineClass = SymonMachine.class; + for(int i = 0; i < args.length; ++i) { + String arg = args[i].toLowerCase(Locale.ENGLISH); + if(arg.equals("-machine") && (i+1) < args.length) { + String machine = args[i+1].trim().toLowerCase(Locale.ENGLISH); + if(machine.equals("symon")) { + machineClass = SymonMachine.class; + } else if(machine.equals("multicomp")) { + machineClass = MulticompMachine.class; + } + } + } + + while(true) { + if(machineClass == null) { + Object[] possibilities = {"Symon", "Multicomp"}; + String s = (String)JOptionPane.showInputDialog( + null, + "Please choose the machine type to be emulated:", + "Machine selection", + JOptionPane.PLAIN_MESSAGE, + null, + possibilities, + "Symon"); + + + if(s != null && s.equals("Multicomp")) { + machineClass = MulticompMachine.class; + } else { + machineClass = SymonMachine.class; + } + } + + final Simulator simulator = new Simulator(machineClass); + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + // Create the main UI window + simulator.createAndShowUi(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + + Simulator.MAIN_CMD cmd = simulator.waitForCommand(); + if(cmd.equals(Simulator.MAIN_CMD.SELECTMACHINE)) { + machineClass = null; + } else { + break; + } + } + + } + +} diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index 7ec8455..161c9d4 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -23,14 +23,12 @@ package com.loomcom.symon; -import com.loomcom.symon.devices.Acia; -import com.loomcom.symon.devices.Crtc; import com.loomcom.symon.devices.Memory; -import com.loomcom.symon.devices.Via; import com.loomcom.symon.exceptions.FifoUnderrunException; import com.loomcom.symon.exceptions.MemoryAccessException; import com.loomcom.symon.exceptions.MemoryRangeException; import com.loomcom.symon.exceptions.SymonException; +import com.loomcom.symon.machines.Machine; import com.loomcom.symon.ui.*; import com.loomcom.symon.ui.Console; @@ -52,29 +50,6 @@ import java.util.logging.Logger; */ public class Simulator { - // 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; - - // 32K of RAM from $0000 - $7FFF - private static final int MEMORY_BASE = 0x0000; - private static final int MEMORY_SIZE = 0x8000; - - // VIA at $8000-$800F - - private static final int VIA_BASE = 0x8000; - - // ACIA at $8800-$8803 - private static final int ACIA_BASE = 0x8800; - - // CRTC at $9000-$9001 - private static final int CRTC_BASE = 0x9000; - private static final int VIDEO_RAM_BASE = 0x7000; - - // 16KB ROM at $C000-$FFFF - private static final int ROM_BASE = 0xC000; - private static final int ROM_SIZE = 0x4000; - // UI constants private static final int DEFAULT_FONT_SIZE = 12; private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE); @@ -93,14 +68,8 @@ public class Simulator { 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 Via via; - private final Crtc crtc; - private final Memory ram; - private Memory rom; + // The simulated machine + private Machine machine; // Number of CPU steps between CRT repaints. // TODO: Dynamically refresh the value at runtime based on performance figures to reach ~ 30fps. @@ -142,43 +111,25 @@ public class Simulator { private JButton runStopButton; private JButton stepButton; private JButton resetButton; - private JComboBox stepCountBox; + private JComboBox stepCountBox; private JFileChooser fileChooser; private PreferencesDialog preferences; + private final Object commandMonitorObject = new Object(); + private MAIN_CMD command = MAIN_CMD.NONE; + public static enum MAIN_CMD { + NONE, + SELECTMACHINE + } + /** * The list of step counts that will appear in the "Step" drop-down. */ private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"}; - public Simulator() throws MemoryRangeException, IOException { - this.bus = new Bus(BUS_BOTTOM, BUS_TOP); - this.cpu = new Cpu(); - this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false); - this.via = new Via(VIA_BASE); - this.acia = new Acia(ACIA_BASE); - this.crtc = new Crtc(CRTC_BASE, ram); - - bus.addCpu(cpu); - - bus.addDevice(ram); - bus.addDevice(via); - bus.addDevice(acia); - bus.addDevice(crtc); - - // TODO: Make this configurable, of course. - File romImage = new File("rom.bin"); - if (romImage.canRead()) { - logger.info("Loading ROM image from file " + romImage); - this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage); - } else { - logger.info("Default ROM file " + romImage + - " not found, loading empty R/W memory image."); - this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1); - } - - bus.addDevice(rom); + public Simulator(Class machineClass) throws Exception { + this.machine = (Machine) machineClass.getConstructors()[0].newInstance(); } /** @@ -212,7 +163,7 @@ public class Simulator { stepButton = new JButton("Step"); resetButton = new JButton("Reset"); - stepCountBox = new JComboBox(STEPS); + stepCountBox = new JComboBox(STEPS); stepCountBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { try { @@ -266,10 +217,12 @@ public class Simulator { traceLog = new TraceLog(); // Prepare the memory window - memoryWindow = new MemoryWindow(bus); + memoryWindow = new MemoryWindow(machine.getBus()); // Composite Video and 6545 CRTC - videoWindow = new VideoWindow(crtc, 2, 2); + if(machine.getCrtc() != null) { + videoWindow = new VideoWindow(machine.getCrtc(), 2, 2); + } mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -282,7 +235,20 @@ public class Simulator { mainWindow.setVisible(true); console.requestFocus(); + handleReset(); } + + public MAIN_CMD waitForCommand() { + synchronized(commandMonitorObject) { + try { + commandMonitorObject.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + return command; + } + private void handleStart() { // Shift focus to the console. @@ -312,7 +278,7 @@ public class Simulator { try { logger.log(Level.INFO, "Reset requested. Resetting CPU."); // Reset and clear memory - cpu.reset(); + machine.getCpu().reset(); // Clear the console. console.reset(); // Reset the trace log. @@ -321,7 +287,7 @@ public class Simulator { SwingUtilities.invokeLater(new Runnable() { public void run() { // Now update the state - statusPane.updateState(cpu); + statusPane.updateState(machine.getCpu()); memoryWindow.updateState(); } }); @@ -343,7 +309,7 @@ public class Simulator { if (traceLog.isVisible()) { traceLog.refresh(); } - statusPane.updateState(cpu); + statusPane.updateState(machine.getCpu()); memoryWindow.updateState(); } }); @@ -357,15 +323,15 @@ public class Simulator { * Perform a single step of the simulated system. */ private void step() throws MemoryAccessException { - cpu.step(); + machine.getCpu().step(); - traceLog.append(cpu.getCpuState()); + traceLog.append(machine.getCpu().getCpuState()); // Read from the ACIA and immediately update the console if there's // output ready. - if (acia.hasTxChar()) { + if (machine.getAcia().hasTxChar()) { // This is thread-safe - console.print(Character.toString((char) acia.txRead())); + console.print(Character.toString((char) machine.getAcia().txRead())); console.repaint(); } @@ -373,14 +339,13 @@ public class Simulator { // TODO: Interrupt handling. try { if (console.hasInput()) { - acia.rxWrite((int) console.readInputChar()); + machine.getAcia().rxWrite((int) console.readInputChar()); } } catch (FifoUnderrunException ex) { logger.severe("Console type-ahead buffer underrun!"); } - if (stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) { - videoWindow.refreshDisplay(); + if (videoWindow != null && stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) { stepsSinceLastCrtcRefresh = 0; } @@ -391,7 +356,7 @@ public class Simulator { SwingUtilities.invokeLater(new Runnable() { public void run() { // Now update the state - statusPane.updateState(cpu); + statusPane.updateState(machine.getCpu()); memoryWindow.updateState(); } }); @@ -406,7 +371,7 @@ public class Simulator { private void loadProgram(byte[] program, int startAddress) throws MemoryAccessException { int addr = startAddress, i; for (i = 0; i < program.length; i++) { - bus.write(addr++, program[i] & 0xff); + machine.getBus().write(addr++, program[i] & 0xff); } logger.log(Level.INFO, "Loaded " + i + " bytes at address 0x" + @@ -414,43 +379,21 @@ public class Simulator { // After loading, be sure to reset and // Reset (but don't clear memory, naturally) - cpu.reset(); + machine.getCpu().reset(); // Reset the stack program counter - cpu.setProgramCounter(preferences.getProgramStartAddress()); + machine.getCpu().setProgramCounter(preferences.getProgramStartAddress()); // Immediately update the UI. SwingUtilities.invokeLater(new Runnable() { public void run() { // Now update the state - statusPane.updateState(cpu); + statusPane.updateState(machine.getCpu()); memoryWindow.updateState(); } }); } - /** - * Main entry point to the simulator. Creates a simulator and shows the main - * window. - * - * @param args - */ - public static void main(String args[]) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - // Create the main UI window - Simulator simulator = new Simulator(); - simulator.createAndShowUi(); - // Reset the simulator. - simulator.handleReset(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } /** * The main run thread. @@ -492,7 +435,7 @@ public class Simulator { SwingUtilities.invokeLater(new Runnable() { public void run() { - statusPane.updateState(cpu); + statusPane.updateState(machine.getCpu()); memoryWindow.updateState(); runStopButton.setText("Run"); stepButton.setEnabled(true); @@ -515,7 +458,7 @@ public class Simulator { * @return True if the run loop should proceed to the next step. */ private boolean shouldContinue() { - return isRunning && !(preferences.getHaltOnBreak() && cpu.getInstruction() == 0x00); + return isRunning && !(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00); } } @@ -535,7 +478,7 @@ public class Simulator { if (f.canRead()) { long fileSize = f.length(); - if (fileSize > MEMORY_SIZE) { + if (fileSize > machine.getMemorySize()) { throw new IOException("Program will not fit in available memory."); } else { byte[] program = new byte[(int) fileSize]; @@ -582,22 +525,19 @@ public class Simulator { if (romFile.canRead()) { long fileSize = romFile.length(); - if (fileSize != ROM_SIZE) { - throw new IOException("ROM file must be exactly " + String.valueOf(ROM_SIZE) + " bytes."); + if (fileSize != machine.getRomSize()) { + throw new IOException("ROM file must be exactly " + String.valueOf(machine.getRomSize()) + " bytes."); } else { - if (rom != null) { - // Unload the existing ROM image. - bus.removeDevice(rom); - } + // Load the new ROM image - rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romFile); - bus.addDevice(rom); + Memory rom = Memory.makeROM(machine.getRomBase(), machine.getRomBase() + machine.getRomSize() - 1, romFile); + machine.setRom(rom); // Now, reset - cpu.reset(); + machine.getCpu().reset(); logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " + - String.format("0x%04X", ROM_BASE)); + String.format("0x%04X", machine.getRomBase())); } } } @@ -622,6 +562,34 @@ public class Simulator { preferences.getDialog().setVisible(true); } } + + class SelectMachineAction extends AbstractAction { + Simulator simulator; + + public SelectMachineAction() { + super("Switch emulated machine...", null); + putValue(SHORT_DESCRIPTION, "Select the type of the machine to be emulated"); + putValue(MNEMONIC_KEY, KeyEvent.VK_M); + } + + public void actionPerformed(ActionEvent actionEvent) { + if(runLoop != null) { + runLoop.requestStop(); + } + + memoryWindow.dispose(); + traceLog.dispose(); + if(videoWindow != null) { + videoWindow.dispose(); + } + mainWindow.dispose(); + + command = MAIN_CMD.SELECTMACHINE; + synchronized(commandMonitorObject) { + commandMonitorObject.notifyAll(); + } + } + } class QuitAction extends AbstractAction { public QuitAction() { @@ -748,11 +716,13 @@ public class Simulator { loadProgramItem = new JMenuItem(new LoadProgramAction()); loadRomItem = new JMenuItem(new LoadRomAction()); JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction()); + JMenuItem selectMachineItem = new JMenuItem(new SelectMachineAction()); JMenuItem quitItem = new JMenuItem(new QuitAction()); fileMenu.add(loadProgramItem); fileMenu.add(loadRomItem); fileMenu.add(prefsItem); + fileMenu.add(selectMachineItem); fileMenu.add(quitItem); add(fileMenu); @@ -797,14 +767,16 @@ public class Simulator { }); viewMenu.add(showMemoryTable); - final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction()); - videoWindow.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - showVideoWindow.setSelected(false); - } - }); - viewMenu.add(showVideoWindow); + if(videoWindow != null) { + final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction()); + videoWindow.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + showVideoWindow.setSelected(false); + } + }); + viewMenu.add(showVideoWindow); + } add(viewMenu); } diff --git a/src/main/java/com/loomcom/symon/devices/Acia.java b/src/main/java/com/loomcom/symon/devices/Acia.java index 8606c0c..f11b14e 100644 --- a/src/main/java/com/loomcom/symon/devices/Acia.java +++ b/src/main/java/com/loomcom/symon/devices/Acia.java @@ -23,179 +23,47 @@ package com.loomcom.symon.devices; -import com.loomcom.symon.exceptions.MemoryAccessException; import com.loomcom.symon.exceptions.MemoryRangeException; /** - * This is a simulation of the MOS 6551 ACIA, with limited - * functionality. Interrupts are not supported. - *

- * Unlike a 16550 UART, the 6551 ACIA has only one-byte transmit and - * receive buffers. It is the programmer's responsibility to check the - * status (full or empty) for transmit and receive buffers before - * writing / reading. + * Abstract base class for ACIAS such as the 6551 and 6580 */ -public class Acia extends Device { - 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; +public abstract class Acia extends Device { + private String name; + /** * Register addresses */ - private int baseAddress; + int baseAddress; - /** - * Registers. These are ignored in the current implementation. - */ - private int commandRegister; - private int controlRegister; - - private boolean receiveIrqEnabled = false; - private boolean transmitIrqEnabled = false; - private boolean overrun = false; - - private long lastTxWrite = 0; - private long lastRxRead = 0; - private int baudRate = 0; - private long baudRateDelay = 0; - - /** + boolean receiveIrqEnabled = false; + boolean transmitIrqEnabled = false; + boolean overrun = false; + + long lastTxWrite = 0; + long lastRxRead = 0; + int baudRate = 0; + long baudRateDelay = 0; + + /** * Read/Write buffers */ - private int rxChar = 0; - private int txChar = 0; + int rxChar = 0; + int txChar = 0; - private boolean rxFull = false; - private boolean txEmpty = true; - - public Acia(int address) throws MemoryRangeException { - super(address, address + ACIA_SIZE - 1, "ACIA"); + boolean rxFull = false; + boolean txEmpty = true; + + + public Acia(int address, int size, String name) throws MemoryRangeException { + super(address, address + size - 1, name); + this.name = name; this.baseAddress = address; } - @Override - public int read(int address) throws MemoryAccessException { - switch (address) { - case DATA_REG: - return rxRead(); - case STAT_REG: - return statusReg(); - case CMND_REG: - return commandRegister; - case CTRL_REG: - return controlRegister; - default: - throw new MemoryAccessException("No register."); - } - } - - @Override - public void write(int address, int data) throws MemoryAccessException { - switch (address) { - case 0: - txWrite(data); - break; - case 1: - reset(); - break; - case 2: - setCommandRegister(data); - break; - case 3: - setControlRegister(data); - break; - default: - throw new MemoryAccessException("No register."); - } - } - - - private void setCommandRegister(int data) { - commandRegister = data; - - // Bit 1 controls receiver IRQ behavior - receiveIrqEnabled = (commandRegister & 0x02) == 0; - // Bits 2 & 3 controls transmit IRQ behavior - transmitIrqEnabled = (commandRegister & 0x08) == 0 && (commandRegister & 0x04) != 0; - } - - /** - * Set the control register and associated state. - * - * @param data - */ - private void setControlRegister(int data) { - controlRegister = data; - - // If the value of the data is 0, this is a request to reset, - // otherwise it's a control update. - - if (data == 0) { - reset(); - } else { - // Mask the lower three bits to get the baud rate. - int baudSelector = data & 0x0f; - switch (baudSelector) { - case 0: - baudRate = 0; - break; - case 1: - baudRate = 50; - break; - case 2: - baudRate = 75; - break; - case 3: - baudRate = 110; // Real rate is actually 109.92 - break; - case 4: - baudRate = 135; // Real rate is actually 134.58 - break; - case 5: - baudRate = 150; - break; - case 6: - baudRate = 300; - break; - case 7: - baudRate = 600; - break; - case 8: - baudRate = 1200; - break; - case 9: - baudRate = 1800; - break; - case 10: - baudRate = 2400; - break; - case 11: - baudRate = 3600; - break; - case 12: - baudRate = 4800; - break; - case 13: - baudRate = 7200; - break; - case 14: - baudRate = 9600; - break; - case 15: - baudRate = 19200; - break; - } - - // Recalculate the baud rate delay. - baudRateDelay = calculateBaudRateDelay(); - } - } /* * Calculate the delay in nanoseconds between successive read/write operations, based on the @@ -225,35 +93,31 @@ public class Acia extends Device { */ public void setBaudRate(int rate) { this.baudRate = rate; + this.baudRateDelay = calculateBaudRateDelay(); } /** * @return The contents of the status register. */ - public int statusReg() { - // TODO: Overrun, Parity Error, Framing Error, DTR, DSR, and Interrupt flags. - int stat = 0; - if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) { - stat |= 0x08; - } - if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) { - stat |= 0x10; - } - return stat; - } + public abstract int statusReg(); @Override public String toString() { - return "ACIA@" + String.format("%04X", baseAddress); + return name + "@" + String.format("%04X", baseAddress); } public synchronized int rxRead() { lastRxRead = System.nanoTime(); + overrun = false; rxFull = false; return rxChar; } public synchronized void rxWrite(int data) { + if(rxFull) { + overrun = true; + } + rxFull = true; if (receiveIrqEnabled) { @@ -293,13 +157,4 @@ public class Acia extends Device { return rxFull; } - private synchronized void reset() { - txChar = 0; - txEmpty = true; - rxChar = 0; - rxFull = false; - receiveIrqEnabled = false; - transmitIrqEnabled = false; - } - } diff --git a/src/main/java/com/loomcom/symon/devices/Acia6551.java b/src/main/java/com/loomcom/symon/devices/Acia6551.java new file mode 100644 index 0000000..bdfe67c --- /dev/null +++ b/src/main/java/com/loomcom/symon/devices/Acia6551.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2008-2013 Seth J. Morabito + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.loomcom.symon.devices; + +import com.loomcom.symon.exceptions.MemoryAccessException; +import com.loomcom.symon.exceptions.MemoryRangeException; + + +/** + * This is a simulation of the MOS 6551 ACIA, with limited + * functionality. Interrupts are not supported. + *

+ * Unlike a 16550 UART, the 6551 ACIA has only one-byte transmit and + * receive buffers. It is the programmer's responsibility to check the + * status (full or empty) for transmit and receive buffers before + * writing / reading. + */ +public class Acia6551 extends Acia { + + 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; + + + /** + * Registers. These are ignored in the current implementation. + */ + private int commandRegister; + private int controlRegister; + + + public Acia6551(int address) throws MemoryRangeException { + super(address, ACIA_SIZE, "ACIA"); + } + + @Override + public int read(int address) throws MemoryAccessException { + switch (address) { + case DATA_REG: + return rxRead(); + case STAT_REG: + return statusReg(); + case CMND_REG: + return commandRegister; + case CTRL_REG: + return controlRegister; + default: + throw new MemoryAccessException("No register."); + } + } + + @Override + public void write(int address, int data) throws MemoryAccessException { + switch (address) { + case 0: + txWrite(data); + break; + case 1: + reset(); + break; + case 2: + setCommandRegister(data); + break; + case 3: + setControlRegister(data); + break; + default: + throw new MemoryAccessException("No register."); + } + } + + + private void setCommandRegister(int data) { + commandRegister = data; + + // Bit 1 controls receiver IRQ behavior + receiveIrqEnabled = (commandRegister & 0x02) == 0; + // Bits 2 & 3 controls transmit IRQ behavior + transmitIrqEnabled = (commandRegister & 0x08) == 0 && (commandRegister & 0x04) != 0; + } + + /** + * Set the control register and associated state. + * + * @param data + */ + private void setControlRegister(int data) { + controlRegister = data; + int rate = 0; + + // If the value of the data is 0, this is a request to reset, + // otherwise it's a control update. + + if (data == 0) { + reset(); + } else { + // Mask the lower three bits to get the baud rate. + int baudSelector = data & 0x0f; + switch (baudSelector) { + case 0: + rate = 0; + break; + case 1: + rate = 50; + break; + case 2: + rate = 75; + break; + case 3: + rate = 110; // Real rate is actually 109.92 + break; + case 4: + rate = 135; // Real rate is actually 134.58 + break; + case 5: + rate = 150; + break; + case 6: + rate = 300; + break; + case 7: + rate = 600; + break; + case 8: + rate = 1200; + break; + case 9: + rate = 1800; + break; + case 10: + rate = 2400; + break; + case 11: + rate = 3600; + break; + case 12: + rate = 4800; + break; + case 13: + rate = 7200; + break; + case 14: + rate = 9600; + break; + case 15: + rate = 19200; + break; + } + + setBaudRate(rate); + } + } + + + /** + * @return The contents of the status register. + */ + @Override + public int statusReg() { + // TODO: Parity Error, Framing Error, DTR, DSR, and Interrupt flags. + int stat = 0; + if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) { + stat |= 0x08; + } + if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) { + stat |= 0x10; + } + if (overrun) { + stat |= 0x04; + } + return stat; + } + + + private synchronized void reset() { + txChar = 0; + txEmpty = true; + rxChar = 0; + rxFull = false; + receiveIrqEnabled = false; + transmitIrqEnabled = false; + } + +} diff --git a/src/main/java/com/loomcom/symon/devices/Acia6850.java b/src/main/java/com/loomcom/symon/devices/Acia6850.java new file mode 100644 index 0000000..5ae9de1 --- /dev/null +++ b/src/main/java/com/loomcom/symon/devices/Acia6850.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2008-2013 Seth J. Morabito + * Maik Merten + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.loomcom.symon.devices; + +import com.loomcom.symon.exceptions.MemoryAccessException; +import com.loomcom.symon.exceptions.MemoryRangeException; + + +/** + * This is a simulation of the Motorola 6850 ACIA, with limited + * functionality. Interrupts are not supported. + *

+ * Unlike a 16550 UART, the 6850 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. + */ +public class Acia6850 extends Acia { + + public static final int ACIA_SIZE = 2; + + + static final int STAT_REG = 0; // read-only + static final int CTRL_REG = 0; // write-only + + static final int RX_REG = 1; // read-only + static final int TX_REG = 1; // write-only + + + /** + * Registers. These are ignored in the current implementation. + */ + private int commandRegister; + + + public Acia6850(int address) throws MemoryRangeException { + super(address, ACIA_SIZE, "ACIA6850"); + setBaudRate(2400); + } + + @Override + public int read(int address) throws MemoryAccessException { + switch (address) { + case RX_REG: + return rxRead(); + case STAT_REG: + return statusReg(); + + default: + throw new MemoryAccessException("No register."); + } + } + + @Override + public void write(int address, int data) throws MemoryAccessException { + switch (address) { + case TX_REG: + txWrite(data); + break; + case CTRL_REG: + setCommandRegister(data); + break; + default: + throw new MemoryAccessException("No register."); + } + } + + private void setCommandRegister(int data) { + commandRegister = data; + + // Bits 0 & 1 control the master reset + if((commandRegister & 0x01) != 0 && (commandRegister & 0x02) != 0) { + reset(); + } + + // Bit 7 controls receiver IRQ behavior + receiveIrqEnabled = (commandRegister & 0x80) != 0; + // Bits 5 & 6 controls transmit IRQ behavior + transmitIrqEnabled = (commandRegister & 0x20) != 0 && (commandRegister & 0x40) == 0; + } + + + + /** + * @return The contents of the status register. + */ + @Override + public int statusReg() { + // TODO: Parity Error, Framing Error, DTR, DSR, and Interrupt flags. + int stat = 0; + if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) { + stat |= 0x01; + } + if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) { + stat |= 0x02; + } + if (overrun) { + stat |= 0x20; + } + + return stat; + } + + + private synchronized void reset() { + overrun = false; + rxFull = false; + txEmpty = true; + } + +} diff --git a/src/main/java/com/loomcom/symon/machines/Machine.java b/src/main/java/com/loomcom/symon/machines/Machine.java new file mode 100644 index 0000000..a168b89 --- /dev/null +++ b/src/main/java/com/loomcom/symon/machines/Machine.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2008-2014 Seth J. Morabito + * Maik Merten + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.loomcom.symon.machines; + +import com.loomcom.symon.Bus; +import com.loomcom.symon.Cpu; +import com.loomcom.symon.devices.Acia; +import com.loomcom.symon.devices.Crtc; +import com.loomcom.symon.devices.Memory; +import com.loomcom.symon.devices.Via; +import com.loomcom.symon.exceptions.MemoryRangeException; + + +public interface Machine { + + public Bus getBus(); + + public Cpu getCpu(); + + public Memory getRam(); + + public Acia getAcia(); + + public Via getVia(); + + public Crtc getCrtc(); + + public Memory getRom(); + + public void setRom(Memory rom) throws MemoryRangeException; + + public int getRomBase(); + + public int getRomSize(); + + public int getMemorySize(); + +} diff --git a/src/main/java/com/loomcom/symon/machines/MulticompMachine.java b/src/main/java/com/loomcom/symon/machines/MulticompMachine.java new file mode 100644 index 0000000..f5ec3cc --- /dev/null +++ b/src/main/java/com/loomcom/symon/machines/MulticompMachine.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2008-2014 Seth J. Morabito + * Maik Merten + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.loomcom.symon.machines; + +import com.loomcom.symon.Bus; +import com.loomcom.symon.Cpu; +import com.loomcom.symon.devices.Acia; +import com.loomcom.symon.devices.Acia6850; +import com.loomcom.symon.devices.Crtc; +import com.loomcom.symon.devices.Memory; +import com.loomcom.symon.devices.Via; +import com.loomcom.symon.exceptions.MemoryRangeException; +import java.io.File; +import java.util.logging.Logger; + + +public class MulticompMachine implements Machine { + + private final static Logger logger = Logger.getLogger(MulticompMachine.class.getName()); + + // 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; + + // 56K of RAM from $0000 - $DFFF + private static final int MEMORY_BASE = 0x0000; + private static final int MEMORY_SIZE = 0xE000; + + // ACIA at $FFD0-$FFD1 + private static final int ACIA_BASE = 0xFFD0; + + + // 8KB ROM at $E000-$FFFF + private static final int ROM_BASE = 0xE000; + private static final int ROM_SIZE = 0x2000; + + + // The simulated peripherals + private final Bus bus; + private final Cpu cpu; + private final Acia acia; + private final Memory ram; + private Memory rom; + + + public MulticompMachine() throws Exception { + this.bus = new Bus(BUS_BOTTOM, BUS_TOP); + this.cpu = new Cpu(); + this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false); + this.acia = new Acia6850(ACIA_BASE); + + bus.addCpu(cpu); + bus.addDevice(ram); + bus.addDevice(acia, 1); + + // TODO: Make this configurable, of course. + File romImage = new File("rom.bin"); + if (romImage.canRead()) { + logger.info("Loading ROM image from file " + romImage); + this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage); + } else { + logger.info("Default ROM file " + romImage + + " not found, loading empty R/W memory image."); + this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1); + } + + bus.addDevice(rom); + + } + + @Override + public Bus getBus() { + return bus; + } + + @Override + public Cpu getCpu() { + return cpu; + } + + @Override + public Memory getRam() { + return ram; + } + + @Override + public Acia getAcia() { + return acia; + } + + @Override + public Via getVia() { + return null; + } + + @Override + public Crtc getCrtc() { + return null; + } + + @Override + public Memory getRom() { + return rom; + } + + public void setRom(Memory rom) throws MemoryRangeException { + if(this.rom != null) { + bus.removeDevice(this.rom); + } + this.rom = rom; + bus.addDevice(this.rom); + } + + @Override + public int getRomBase() { + return ROM_BASE; + } + + @Override + public int getRomSize() { + return ROM_SIZE; + } + + @Override + public int getMemorySize() { + return MEMORY_SIZE; + } + +} diff --git a/src/main/java/com/loomcom/symon/machines/SymonMachine.java b/src/main/java/com/loomcom/symon/machines/SymonMachine.java new file mode 100644 index 0000000..3f9efbb --- /dev/null +++ b/src/main/java/com/loomcom/symon/machines/SymonMachine.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2008-2014 Seth J. Morabito + * Maik Merten + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.loomcom.symon.machines; + +import com.loomcom.symon.Bus; +import com.loomcom.symon.Cpu; +import com.loomcom.symon.devices.Acia; +import com.loomcom.symon.devices.Acia6551; +import com.loomcom.symon.devices.Crtc; +import com.loomcom.symon.devices.Memory; +import com.loomcom.symon.devices.Via; +import com.loomcom.symon.exceptions.MemoryRangeException; +import java.io.File; +import java.util.logging.Logger; + + +public class SymonMachine implements Machine { + + private final static Logger logger = Logger.getLogger(SymonMachine.class.getName()); + + // 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; + + // 32K of RAM from $0000 - $7FFF + private static final int MEMORY_BASE = 0x0000; + private static final int MEMORY_SIZE = 0x8000; + + // VIA at $8000-$800F + + private static final int VIA_BASE = 0x8000; + + // ACIA at $8800-$8803 + private static final int ACIA_BASE = 0x8800; + + // CRTC at $9000-$9001 + private static final int CRTC_BASE = 0x9000; + private static final int VIDEO_RAM_BASE = 0x7000; + + // 16KB ROM at $C000-$FFFF + private static final int ROM_BASE = 0xC000; + private static final int ROM_SIZE = 0x4000; + + + // The simulated peripherals + private final Bus bus; + private final Cpu cpu; + private final Acia acia; + private final Via via; + private final Crtc crtc; + private final Memory ram; + private Memory rom; + + + public SymonMachine() throws Exception { + this.bus = new Bus(BUS_BOTTOM, BUS_TOP); + this.cpu = new Cpu(); + this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false); + this.via = new Via(VIA_BASE); + this.acia = new Acia6551(ACIA_BASE); + this.crtc = new Crtc(CRTC_BASE, ram); + + bus.addCpu(cpu); + bus.addDevice(ram); + bus.addDevice(via); + bus.addDevice(acia); + bus.addDevice(crtc); + + // TODO: Make this configurable, of course. + File romImage = new File("rom.bin"); + if (romImage.canRead()) { + logger.info("Loading ROM image from file " + romImage); + this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage); + } else { + logger.info("Default ROM file " + romImage + + " not found, loading empty R/W memory image."); + this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1); + } + + bus.addDevice(rom); + + } + + @Override + public Bus getBus() { + return bus; + } + + @Override + public Cpu getCpu() { + return cpu; + } + + @Override + public Memory getRam() { + return ram; + } + + @Override + public Acia getAcia() { + return acia; + } + + @Override + public Via getVia() { + return via; + } + + @Override + public Crtc getCrtc() { + return crtc; + } + + @Override + public Memory getRom() { + return rom; + } + + public void setRom(Memory rom) throws MemoryRangeException { + if(this.rom != null) { + bus.removeDevice(this.rom); + } + this.rom = rom; + bus.addDevice(this.rom); + } + + @Override + public int getRomBase() { + return ROM_BASE; + } + + @Override + public int getRomSize() { + return ROM_SIZE; + } + + @Override + public int getMemorySize() { + return MEMORY_SIZE; + } + + + + +} diff --git a/src/test/java/com/loomcom/symon/AciaTest.java b/src/test/java/com/loomcom/symon/AciaTest.java index bcb4784..db34622 100644 --- a/src/test/java/com/loomcom/symon/AciaTest.java +++ b/src/test/java/com/loomcom/symon/AciaTest.java @@ -1,6 +1,7 @@ package com.loomcom.symon; import com.loomcom.symon.devices.Acia; +import com.loomcom.symon.devices.Acia6551; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -12,7 +13,7 @@ public class AciaTest { public void shouldTriggerInterruptOnRxFullIfRxIrqEnabled() throws Exception { Bus mockBus = mock(Bus.class); - Acia acia = new Acia(0x000); + Acia acia = new Acia6551(0x000); acia.setBus(mockBus); // Disable TX IRQ, Enable RX IRQ @@ -27,7 +28,7 @@ public class AciaTest { public void shouldNotTriggerInterruptOnRxFullIfRxIrqNotEnabled() throws Exception { Bus mockBus = mock(Bus.class); - Acia acia = new Acia(0x000); + Acia acia = new Acia6551(0x000); acia.setBus(mockBus); // Disable TX IRQ, Disable RX IRQ @@ -42,7 +43,7 @@ public class AciaTest { public void shouldTriggerInterruptOnTxEmptyIfTxIrqEnabled() throws Exception { Bus mockBus = mock(Bus.class); - Acia acia = new Acia(0x000); + Acia acia = new Acia6551(0x000); acia.setBus(mockBus); // Enable TX IRQ, Disable RX IRQ @@ -63,7 +64,7 @@ public class AciaTest { public void shouldNotTriggerInterruptOnTxEmptyIfTxIrqNotEnabled() throws Exception { Bus mockBus = mock(Bus.class); - Acia acia = new Acia(0x000); + Acia acia = new Acia6551(0x000); acia.setBus(mockBus); // Disable TX IRQ, Disable RX IRQ @@ -80,14 +81,14 @@ public class AciaTest { @Test public void newAciaShouldHaveTxEmptyStatus() throws Exception { - Acia acia = new Acia(0x000); + Acia acia = new Acia6551(0x000); assertEquals(0x10, acia.read(0x0001)); } @Test public void aciaShouldHaveTxEmptyStatusOffIfTxHasData() throws Exception { - Acia acia = new Acia(0x000); + Acia acia = new Acia6551(0x000); acia.txWrite('a'); assertEquals(0x00, acia.read(0x0001)); @@ -95,7 +96,7 @@ public class AciaTest { @Test public void aciaShouldHaveRxFullStatusOffIfRxHasData() throws Exception { - Acia acia = new Acia(0x000); + Acia acia = new Acia6551(0x000); acia.rxWrite('a'); assertEquals(0x18, acia.read(0x0001)); @@ -104,18 +105,37 @@ public class AciaTest { @Test public void aciaShouldHaveTxEmptyAndRxFullStatusOffIfRxAndTxHaveData() throws Exception { - Acia acia = new Acia(0x000); + Acia acia = new Acia6551(0x000); acia.rxWrite('a'); acia.txWrite('b'); assertEquals(0x08, acia.read(0x0001)); } + + @Test + public void aciaShouldOverrunAndReadShouldReset() + throws Exception { + + Acia acia = new Acia6551(0x0000); + + // overrun ACIA + acia.rxWrite('a'); + acia.rxWrite('b'); + + assertEquals(0x04, acia.read(0x0001) & 0x04); + + // read should reset + acia.rxRead(); + assertEquals(0x00, acia.read(0x0001) & 0x04); + + } + @Test public void readingBuffersShouldResetStatus() throws Exception { - Acia acia = new Acia(0x0000); + Acia acia = new Acia6551(0x0000); acia.rxWrite('a'); acia.txWrite('b'); diff --git a/src/test/java/com/loomcom/symon/AciaTest6850.java b/src/test/java/com/loomcom/symon/AciaTest6850.java new file mode 100644 index 0000000..5181bc7 --- /dev/null +++ b/src/test/java/com/loomcom/symon/AciaTest6850.java @@ -0,0 +1,163 @@ +package com.loomcom.symon; + +import com.loomcom.symon.devices.Acia; +import com.loomcom.symon.devices.Acia6850; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +public class AciaTest6850 { + + private final static int CMD_STAT_REG = 0; + private final static int DATA_REG = 1; + + + private Acia newAcia() throws Exception { + Acia acia = new Acia6850(0x0000); + // by default rate is limited, have it unlimited for testing + acia.setBaudRate(0); + return acia; + } + + + @Test + public void shouldTriggerInterruptOnRxFullIfRxIrqEnabled() throws Exception { + Bus mockBus = mock(Bus.class); + + Acia acia = newAcia(); + acia.setBus(mockBus); + + // Disable TX IRQ, Enable RX IRQ + acia.write(CMD_STAT_REG, 0x80); + + acia.rxWrite('a'); + + verify(mockBus, atLeastOnce()).assertIrq(); + } + + @Test + public void shouldNotTriggerInterruptOnRxFullIfRxIrqNotEnabled() throws Exception { + Bus mockBus = mock(Bus.class); + + Acia acia = newAcia(); + acia.setBus(mockBus); + + // Disable TX IRQ, Disable RX IRQ + acia.write(CMD_STAT_REG, 0x00); + + acia.rxWrite('a'); + + verify(mockBus, never()).assertIrq(); + } + + @Test + public void shouldTriggerInterruptOnTxEmptyIfTxIrqEnabled() throws Exception { + Bus mockBus = mock(Bus.class); + + Acia acia = newAcia(); + acia.setBus(mockBus); + + // Enable TX IRQ, Disable RX IRQ + acia.write(CMD_STAT_REG, 0x20); + + // Write data + acia.write(1, 'a'); + + verify(mockBus, never()).assertIrq(); + + // Transmission should cause IRQ + acia.txRead(); + + verify(mockBus, atLeastOnce()).assertIrq(); + } + + @Test + public void shouldNotTriggerInterruptOnTxEmptyIfTxIrqNotEnabled() throws Exception { + Bus mockBus = mock(Bus.class); + + Acia acia = newAcia(); + acia.setBus(mockBus); + + // Disable TX IRQ, Disable RX IRQ + acia.write(CMD_STAT_REG, 0x02); + + // Write data + acia.write(DATA_REG, 'a'); + + // Transmission should cause IRQ + acia.txRead(); + + verify(mockBus, never()).assertIrq(); + } + + @Test + public void newAciaShouldHaveTxEmptyStatus() throws Exception { + Acia acia = newAcia(); + + assertEquals(0x02, acia.read(CMD_STAT_REG) & 0x02); + } + + @Test + public void aciaShouldHaveTxEmptyStatusOffIfTxHasData() throws Exception { + Acia acia = newAcia(); + + acia.txWrite('a'); + assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x02); + } + + @Test + public void aciaShouldHaveRxFullStatusOnIfRxHasData() throws Exception { + Acia acia = newAcia(); + + acia.rxWrite('a'); + + assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x01); + } + + @Test + public void aciaShouldHaveTxEmptyAndRxFullStatusOffIfRxAndTxHaveData() + throws Exception { + Acia acia = newAcia(); + + acia.rxWrite('a'); + acia.txWrite('b'); + + assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x03); + } + + @Test + public void aciaShouldOverrunAndReadShouldReset() + throws Exception { + + Acia acia = newAcia(); + + // overrun ACIA + acia.rxWrite('a'); + acia.rxWrite('b'); + + assertEquals(0x20, acia.read(CMD_STAT_REG) & 0x20); + + // read should reset + acia.rxRead(); + assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x20); + + } + + @Test + public void readingBuffersShouldResetStatus() + throws Exception { + Acia acia = newAcia(); + + assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x01); + + acia.rxWrite('a'); + + assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x01); + + acia.rxRead(); + + assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x01); + + } +} diff --git a/src/test/java/com/loomcom/symon/BusTest.java b/src/test/java/com/loomcom/symon/BusTest.java index b4a8a9a..26a4eb4 100644 --- a/src/test/java/com/loomcom/symon/BusTest.java +++ b/src/test/java/com/loomcom/symon/BusTest.java @@ -59,22 +59,6 @@ public class BusTest extends TestCase { assertEquals(2, b.getDevices().size()); } - public void testOverlappingDevicesShouldFail() throws MemoryRangeException { - Device memory = new Memory(0x0000, 0x0100, true); - Device rom = new Memory(0x00ff, 0x0200, false); - - Bus b = new Bus(0x0000, 0xffff); - - b.addDevice(memory); - - try { - b.addDevice(rom); - fail("Should have thrown a MemoryRangeException."); - } catch (MemoryRangeException ex) { - // expected - } - } - public void testIsCompleteWithFirstDeviceNotStartingAtStartAddress() throws MemoryRangeException { Device memory = new Memory(0x00ff, 0xff00, true); diff --git a/src/test/java/com/loomcom/symon/CpuAbsoluteModeTest.java b/src/test/java/com/loomcom/symon/CpuAbsoluteModeTest.java index ea63c58..6921492 100644 --- a/src/test/java/com/loomcom/symon/CpuAbsoluteModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuAbsoluteModeTest.java @@ -14,7 +14,7 @@ public class CpuAbsoluteModeTest extends TestCase { protected void setUp() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuAbsoluteXModeTest.java b/src/test/java/com/loomcom/symon/CpuAbsoluteXModeTest.java index 842f4b2..958e959 100644 --- a/src/test/java/com/loomcom/symon/CpuAbsoluteXModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuAbsoluteXModeTest.java @@ -14,7 +14,7 @@ public class CpuAbsoluteXModeTest extends TestCase { protected void setUp() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuAbsoluteYModeTest.java b/src/test/java/com/loomcom/symon/CpuAbsoluteYModeTest.java index 8521ab0..284bf29 100644 --- a/src/test/java/com/loomcom/symon/CpuAbsoluteYModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuAbsoluteYModeTest.java @@ -14,7 +14,7 @@ public class CpuAbsoluteYModeTest extends TestCase { protected void setUp() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuAccumulatorModeTest.java b/src/test/java/com/loomcom/symon/CpuAccumulatorModeTest.java index f036b9c..7b51770 100644 --- a/src/test/java/com/loomcom/symon/CpuAccumulatorModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuAccumulatorModeTest.java @@ -14,7 +14,7 @@ public class CpuAccumulatorModeTest extends TestCase { public void setUp() throws MemoryRangeException, MemoryAccessException { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java b/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java index 628a6ec..148106d 100644 --- a/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java @@ -14,7 +14,7 @@ public class CpuImmediateModeTest extends TestCase { public void setUp() throws MemoryRangeException, MemoryAccessException { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java b/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java index ae3d141..8363646 100644 --- a/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java @@ -17,7 +17,7 @@ public class CpuImpliedModeTest { public void setUp() throws MemoryRangeException, MemoryAccessException { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuIndexedIndirectModeTest.java b/src/test/java/com/loomcom/symon/CpuIndexedIndirectModeTest.java index 346dd0c..5bf39b5 100644 --- a/src/test/java/com/loomcom/symon/CpuIndexedIndirectModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuIndexedIndirectModeTest.java @@ -16,7 +16,7 @@ public class CpuIndexedIndirectModeTest { public void runBeforeEveryTest() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuIndirectIndexedModeTest.java b/src/test/java/com/loomcom/symon/CpuIndirectIndexedModeTest.java index 1e2c543..191e930 100644 --- a/src/test/java/com/loomcom/symon/CpuIndirectIndexedModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuIndirectIndexedModeTest.java @@ -16,7 +16,7 @@ public class CpuIndirectIndexedModeTest { public void runBeforeEveryTest() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuIndirectModeTest.java b/src/test/java/com/loomcom/symon/CpuIndirectModeTest.java index 910cb45..87f7442 100644 --- a/src/test/java/com/loomcom/symon/CpuIndirectModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuIndirectModeTest.java @@ -14,7 +14,7 @@ public class CpuIndirectModeTest extends TestCase { protected void setUp() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuIndirectXModeTest.java b/src/test/java/com/loomcom/symon/CpuIndirectXModeTest.java index 92fcdaa..15ad0ce 100644 --- a/src/test/java/com/loomcom/symon/CpuIndirectXModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuIndirectXModeTest.java @@ -13,7 +13,7 @@ public class CpuIndirectXModeTest extends TestCase { protected void setUp() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuRelativeModeTest.java b/src/test/java/com/loomcom/symon/CpuRelativeModeTest.java index 9a5f0fe..325bcf3 100644 --- a/src/test/java/com/loomcom/symon/CpuRelativeModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuRelativeModeTest.java @@ -14,7 +14,7 @@ public class CpuRelativeModeTest extends TestCase { protected void setUp() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuTest.java b/src/test/java/com/loomcom/symon/CpuTest.java index a44e92d..d7e7cb4 100644 --- a/src/test/java/com/loomcom/symon/CpuTest.java +++ b/src/test/java/com/loomcom/symon/CpuTest.java @@ -25,7 +25,7 @@ public class CpuTest extends TestCase { public void setUp() throws MemoryRangeException, MemoryAccessException { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuZeroPageModeTest.java b/src/test/java/com/loomcom/symon/CpuZeroPageModeTest.java index 9ba2691..93ca971 100644 --- a/src/test/java/com/loomcom/symon/CpuZeroPageModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuZeroPageModeTest.java @@ -14,7 +14,7 @@ public class CpuZeroPageModeTest extends TestCase { protected void setUp() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuZeroPageXModeTest.java b/src/test/java/com/loomcom/symon/CpuZeroPageXModeTest.java index b612d93..67d5f17 100644 --- a/src/test/java/com/loomcom/symon/CpuZeroPageXModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuZeroPageXModeTest.java @@ -14,7 +14,7 @@ public class CpuZeroPageXModeTest extends TestCase { protected void setUp() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem); diff --git a/src/test/java/com/loomcom/symon/CpuZeroPageYModeTest.java b/src/test/java/com/loomcom/symon/CpuZeroPageYModeTest.java index ed23b98..e1ef721 100644 --- a/src/test/java/com/loomcom/symon/CpuZeroPageYModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuZeroPageYModeTest.java @@ -14,7 +14,7 @@ public class CpuZeroPageYModeTest extends TestCase { protected void setUp() throws Exception { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); + this.mem = new Memory(0x0000, 0xffff); bus.addCpu(cpu); bus.addDevice(mem);