From 97269073172c8db9cc099012ae580bc20be2e2f5 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Tue, 15 Jul 2014 18:45:13 +0200 Subject: [PATCH 01/24] initial (and untested) implementation of a Motorola 6850 ACIA --- .../com/loomcom/symon/devices/Acia6850.java | 240 ++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 src/main/java/com/loomcom/symon/devices/Acia6850.java 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..cc8bf27 --- /dev/null +++ b/src/main/java/com/loomcom/symon/devices/Acia6850.java @@ -0,0 +1,240 @@ +/* + * 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 Device { + + 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 + + + /** + * Register addresses + */ + private int baseAddress; + + /** + * Registers. These are ignored in the current implementation. + */ + private int commandRegister; + + 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; + + /** + * Read/Write buffers + */ + private int rxChar = 0; + private int txChar = 0; + + private boolean rxFull = false; + private boolean txEmpty = true; + + public Acia6850(int address) throws MemoryRangeException { + super(address, address + ACIA_SIZE - 1, "ACIA"); + this.baseAddress = address; + 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; + } + + + /* + * Calculate the delay in nanoseconds between successive read/write operations, based on the + * configured baud rate. + */ + private long calculateBaudRateDelay() { + if (baudRate > 0) { + // TODO: This is a pretty rough approximation based on 8 bits per character, + // and 1/baudRate per bit. It could certainly be improved + return (long)((1.0 / baudRate) * 1000000000 * 8); + } else { + return 0; + } + } + + /** + * @return The simulated baud rate in bps. + */ + public int getBaudRate() { + return baudRate; + } + + /** + * Set the baud rate of the simulated ACIA. + * + * @param rate The baud rate in bps. 0 means no simulated baud rate delay. + */ + public void setBaudRate(int rate) { + this.baudRate = rate; + this.baudRateDelay = calculateBaudRateDelay(); + } + + /** + * @return The contents of the status register. + */ + 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; + } + + @Override + public String toString() { + return "ACIA6850@" + String.format("%04X", baseAddress); + } + + public synchronized int rxRead() { + lastRxRead = System.nanoTime(); + rxFull = false; + overrun = false; + return rxChar; + } + + public synchronized void rxWrite(int data) { + // when receiving while full: overrun + if(rxFull) { + overrun = true; + } + + rxFull = true; + + if (receiveIrqEnabled) { + getBus().assertIrq(); + } + + rxChar = data; + } + + public synchronized int txRead() { + txEmpty = true; + + if (transmitIrqEnabled) { + getBus().assertIrq(); + } + + return txChar; + } + + public synchronized void txWrite(int data) { + lastTxWrite = System.nanoTime(); + txChar = data; + txEmpty = false; + } + + /** + * @return true if there is character data in the TX register. + */ + public boolean hasTxChar() { + return !txEmpty; + } + + /** + * @return true if there is character data in the RX register. + */ + public boolean hasRxChar() { + return rxFull; + } + + private synchronized void reset() { + overrun = false; + } + +} From 31fdd87b413aad917476914c3c6865e18080acc0 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Tue, 15 Jul 2014 21:35:19 +0200 Subject: [PATCH 02/24] turn Acia into an abstract class so that the 6551 and 6850 ACIAs can reuse code and an unified interface. --- .../java/com/loomcom/symon/Simulator.java | 3 +- .../java/com/loomcom/symon/devices/Acia.java | 204 +++-------------- .../com/loomcom/symon/devices/Acia6551.java | 205 ++++++++++++++++++ .../com/loomcom/symon/devices/Acia6850.java | 116 +--------- src/test/java/com/loomcom/symon/AciaTest.java | 19 +- 5 files changed, 248 insertions(+), 299 deletions(-) create mode 100644 src/main/java/com/loomcom/symon/devices/Acia6551.java diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index 7ec8455..94dbd9e 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -24,6 +24,7 @@ package com.loomcom.symon; 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; @@ -157,7 +158,7 @@ public class Simulator { 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.acia = new Acia6551(ACIA_BASE); this.crtc = new Crtc(CRTC_BASE, ram); bus.addCpu(cpu); diff --git a/src/main/java/com/loomcom/symon/devices/Acia.java b/src/main/java/com/loomcom/symon/devices/Acia.java index 8606c0c..e8664ef 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,26 +93,17 @@ 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() { @@ -293,13 +152,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..e56e79c --- /dev/null +++ b/src/main/java/com/loomcom/symon/devices/Acia6551.java @@ -0,0 +1,205 @@ +/* + * 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: 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; + } + + + 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 index cc8bf27..0eb7917 100644 --- a/src/main/java/com/loomcom/symon/devices/Acia6850.java +++ b/src/main/java/com/loomcom/symon/devices/Acia6850.java @@ -37,7 +37,7 @@ import com.loomcom.symon.exceptions.MemoryRangeException; * status (full or empty) for transmit and receive buffers before * writing / reading. */ -public class Acia6850 extends Device { +public class Acia6850 extends Acia { public static final int ACIA_SIZE = 2; @@ -48,38 +48,15 @@ public class Acia6850 extends Device { static final int RX_REG = 1; // read-only static final int TX_REG = 1; // write-only - - /** - * Register addresses - */ - private int baseAddress; - + /** * Registers. These are ignored in the current implementation. */ private int commandRegister; - 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; - - /** - * Read/Write buffers - */ - private int rxChar = 0; - private int txChar = 0; - - private boolean rxFull = false; - private boolean txEmpty = true; public Acia6850(int address) throws MemoryRangeException { - super(address, address + ACIA_SIZE - 1, "ACIA"); - this.baseAddress = address; + super(address, ACIA_SIZE, "ACIA6850"); setBaudRate(2400); } @@ -126,40 +103,11 @@ public class Acia6850 extends Device { } - /* - * Calculate the delay in nanoseconds between successive read/write operations, based on the - * configured baud rate. - */ - private long calculateBaudRateDelay() { - if (baudRate > 0) { - // TODO: This is a pretty rough approximation based on 8 bits per character, - // and 1/baudRate per bit. It could certainly be improved - return (long)((1.0 / baudRate) * 1000000000 * 8); - } else { - return 0; - } - } - - /** - * @return The simulated baud rate in bps. - */ - public int getBaudRate() { - return baudRate; - } - - /** - * Set the baud rate of the simulated ACIA. - * - * @param rate The baud rate in bps. 0 means no simulated baud rate delay. - */ - public void setBaudRate(int rate) { - this.baudRate = rate; - this.baudRateDelay = calculateBaudRateDelay(); - } /** * @return The contents of the status register. */ + @Override public int statusReg() { // TODO: Parity Error, Framing Error, DTR, DSR, and Interrupt flags. int stat = 0; @@ -176,62 +124,6 @@ public class Acia6850 extends Device { return stat; } - @Override - public String toString() { - return "ACIA6850@" + String.format("%04X", baseAddress); - } - - public synchronized int rxRead() { - lastRxRead = System.nanoTime(); - rxFull = false; - overrun = false; - return rxChar; - } - - public synchronized void rxWrite(int data) { - // when receiving while full: overrun - if(rxFull) { - overrun = true; - } - - rxFull = true; - - if (receiveIrqEnabled) { - getBus().assertIrq(); - } - - rxChar = data; - } - - public synchronized int txRead() { - txEmpty = true; - - if (transmitIrqEnabled) { - getBus().assertIrq(); - } - - return txChar; - } - - public synchronized void txWrite(int data) { - lastTxWrite = System.nanoTime(); - txChar = data; - txEmpty = false; - } - - /** - * @return true if there is character data in the TX register. - */ - public boolean hasTxChar() { - return !txEmpty; - } - - /** - * @return true if there is character data in the RX register. - */ - public boolean hasRxChar() { - return rxFull; - } private synchronized void reset() { overrun = false; diff --git a/src/test/java/com/loomcom/symon/AciaTest.java b/src/test/java/com/loomcom/symon/AciaTest.java index bcb4784..6fa3638 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,7 +105,7 @@ 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'); @@ -115,7 +116,7 @@ public class AciaTest { @Test public void readingBuffersShouldResetStatus() throws Exception { - Acia acia = new Acia(0x0000); + Acia acia = new Acia6551(0x0000); acia.rxWrite('a'); acia.txWrite('b'); From 0d0fddc3652a32bab5e33f385328634a48103d5d Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Wed, 16 Jul 2014 21:51:34 +0200 Subject: [PATCH 03/24] tests for the 6850 ACIA and fixes in ACIA behavior --- .../com/loomcom/symon/devices/Acia6850.java | 6 +- .../java/com/loomcom/symon/AciaTest6850.java | 145 ++++++++++++++++++ 2 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/loomcom/symon/AciaTest6850.java diff --git a/src/main/java/com/loomcom/symon/devices/Acia6850.java b/src/main/java/com/loomcom/symon/devices/Acia6850.java index 0eb7917..3621887 100644 --- a/src/main/java/com/loomcom/symon/devices/Acia6850.java +++ b/src/main/java/com/loomcom/symon/devices/Acia6850.java @@ -97,9 +97,9 @@ public class Acia6850 extends Acia { } // Bit 7 controls receiver IRQ behavior - receiveIrqEnabled = (commandRegister & 0x80) == 0; + receiveIrqEnabled = (commandRegister & 0x80) != 0; // Bits 5 & 6 controls transmit IRQ behavior - transmitIrqEnabled = (commandRegister & 0x20) == 0 && (commandRegister & 0x40) != 0; + transmitIrqEnabled = (commandRegister & 0x20) != 0 && (commandRegister & 0x40) == 0; } @@ -127,6 +127,8 @@ public class Acia6850 extends Acia { private synchronized void reset() { overrun = false; + rxFull = false; + txEmpty = true; } } 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..487bf53 --- /dev/null +++ b/src/test/java/com/loomcom/symon/AciaTest6850.java @@ -0,0 +1,145 @@ +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 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); + + } +} From 8f52e1da1e22c3eb5edf5f8ae69a185e1c362106 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Wed, 16 Jul 2014 22:26:06 +0200 Subject: [PATCH 04/24] implement overrun flag for 6850 ACIA --- .../com/loomcom/symon/devices/Acia6850.java | 14 ++++++++++++++ .../java/com/loomcom/symon/AciaTest6850.java | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/main/java/com/loomcom/symon/devices/Acia6850.java b/src/main/java/com/loomcom/symon/devices/Acia6850.java index 3621887..bae935b 100644 --- a/src/main/java/com/loomcom/symon/devices/Acia6850.java +++ b/src/main/java/com/loomcom/symon/devices/Acia6850.java @@ -86,6 +86,20 @@ public class Acia6850 extends Acia { throw new MemoryAccessException("No register."); } } + + @Override + public synchronized int rxRead() { + overrun = false; + return super.rxRead(); + } + + @Override + public synchronized void rxWrite(int data) { + if(rxFull) { + overrun = true; + } + super.rxWrite(data); + } private void setCommandRegister(int data) { diff --git a/src/test/java/com/loomcom/symon/AciaTest6850.java b/src/test/java/com/loomcom/symon/AciaTest6850.java index 487bf53..5181bc7 100644 --- a/src/test/java/com/loomcom/symon/AciaTest6850.java +++ b/src/test/java/com/loomcom/symon/AciaTest6850.java @@ -125,6 +125,24 @@ public class AciaTest6850 { 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() From 14e8ea596c068fccce558ab7f647f8bb059d2783 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Thu, 17 Jul 2014 19:42:51 +0200 Subject: [PATCH 05/24] define an interface for supported machines and have the SymonMachine as first implementation. Remove machine-specific definitions out of the Simulator class. --- .../java/com/loomcom/symon/Simulator.java | 121 ++++--------- .../com/loomcom/symon/machines/Machine.java | 60 +++++++ .../loomcom/symon/machines/SymonMachine.java | 167 ++++++++++++++++++ 3 files changed, 258 insertions(+), 90 deletions(-) create mode 100644 src/main/java/com/loomcom/symon/machines/Machine.java create mode 100644 src/main/java/com/loomcom/symon/machines/SymonMachine.java diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index 94dbd9e..415333d 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -23,15 +23,13 @@ package com.loomcom.symon; -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.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.machines.SymonMachine; import com.loomcom.symon.ui.*; import com.loomcom.symon.ui.Console; @@ -53,29 +51,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); @@ -94,14 +69,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. @@ -153,33 +122,8 @@ public class Simulator { */ 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 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); + public Simulator() throws Exception { + this.machine = new SymonMachine(); } /** @@ -267,10 +211,10 @@ 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); + videoWindow = new VideoWindow(machine.getCrtc(), 2, 2); mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -313,7 +257,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. @@ -322,7 +266,7 @@ public class Simulator { SwingUtilities.invokeLater(new Runnable() { public void run() { // Now update the state - statusPane.updateState(cpu); + statusPane.updateState(machine.getCpu()); memoryWindow.updateState(); } }); @@ -344,7 +288,7 @@ public class Simulator { if (traceLog.isVisible()) { traceLog.refresh(); } - statusPane.updateState(cpu); + statusPane.updateState(machine.getCpu()); memoryWindow.updateState(); } }); @@ -358,15 +302,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(); } @@ -374,7 +318,7 @@ 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!"); @@ -392,7 +336,7 @@ public class Simulator { SwingUtilities.invokeLater(new Runnable() { public void run() { // Now update the state - statusPane.updateState(cpu); + statusPane.updateState(machine.getCpu()); memoryWindow.updateState(); } }); @@ -407,7 +351,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" + @@ -415,16 +359,16 @@ 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(); } }); @@ -493,7 +437,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); @@ -516,7 +460,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); } } @@ -536,7 +480,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]; @@ -583,22 +527,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())); } } } 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/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; + } + + + + +} From f5a25b7303f6adc8d9d6c2b0d92312dbfdbc1c85 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Thu, 17 Jul 2014 20:41:26 +0200 Subject: [PATCH 06/24] initial definition of the MULTICOMP machine. Will not yet run, as the bus will currently not allow overlapping memory ranges for devices. --- .../symon/machines/MulticompMachine.java | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src/main/java/com/loomcom/symon/machines/MulticompMachine.java 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..0150e58 --- /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; + + // 32K of RAM from $0000 - $7FFF + private static final int MEMORY_BASE = 0x0000; + private static final int MEMORY_SIZE = 0x8000; + + // 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); + + // 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; + } + +} From 44151f3a9cc2dde439894250e2a95fb8fd47ee8e Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Thu, 17 Jul 2014 21:43:46 +0200 Subject: [PATCH 07/24] have the Bus support devices with overlapping memory regions --- src/main/java/com/loomcom/symon/Bus.java | 102 +++++++++++++++++------ 1 file changed, 75 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Bus.java b/src/main/java/com/loomcom/symon/Bus.java index 650df3c..9b9e91d 100644 --- a/src/main/java/com/loomcom/symon/Bus.java +++ b/src/main/java/com/loomcom/symon/Bus.java @@ -47,6 +47,13 @@ public class Bus { // Ordered list of IO devices. private SortedSet devices; + + // Ordered list of IO devices that have overlapping memory locations + private SortedSet overlapDevices; + + // an array for quick lookup of adresses, brute-force style + private Device[] deviceAddressArray; + public Bus(int size) { this(0, size - 1); @@ -54,6 +61,7 @@ public class Bus { public Bus(int startAddress, int endAddress) { this.devices = new TreeSet(); + this.overlapDevices = new TreeSet(); this.startAddress = startAddress; this.endAddress = endAddress; } @@ -65,7 +73,56 @@ public class Bus { public int endAddress() { return endAddress; } + + private void buildDeviceAddressArray() { + // TODO: Find out why +2 and not +1 is needed here + int size = (this.endAddress - this.startAddress) + 2; + deviceAddressArray = new Device[size]; + for(Device device : devices) { + MemoryRange range = device.getMemoryRange(); + for(int address = range.startAddress; address <= range.endAddress; ++address) { + deviceAddressArray[address] = device; + } + } + for(Device device : overlapDevices) { + MemoryRange range = device.getMemoryRange(); + for(int address = range.startAddress; address <= range.endAddress; ++address) { + deviceAddressArray[address] = device; + } + } + } + /** + * Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others, unless the overlap parameter is true. + * + * @param device + * @param overlap + * @throws MemoryRangeException + */ + public void addDevice(Device device, boolean overlap) throws MemoryRangeException { + // Make sure there's no memory overlap. + MemoryRange memRange = device.getMemoryRange(); + if(!overlap) { + 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); + if(overlap) { + overlapDevices.add(device); + } else { + devices.add(device); + } + buildDeviceAddressArray(); + } + /** * Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others. * @@ -73,21 +130,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, false); } + /** * Remove a device from the bus. @@ -97,6 +142,11 @@ public class Bus { public void removeDevice(Device device) { if (devices.contains(device)) { devices.remove(device); + buildDeviceAddressArray(); + } + if (overlapDevices.contains(device)) { + overlapDevices.remove(device); + buildDeviceAddressArray(); } } @@ -127,27 +177,25 @@ public class Bus { } public int read(int address) throws MemoryAccessException { - for (Device d : devices) { + Device d = deviceAddressArray[address]; + 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]; + 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)); } From e01f68a19a262d6bdfe0902c3411f9dbceb09876 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Thu, 17 Jul 2014 21:45:37 +0200 Subject: [PATCH 08/24] make use of overlapping memory regions in MulticompMachine --- src/main/java/com/loomcom/symon/machines/MulticompMachine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/loomcom/symon/machines/MulticompMachine.java b/src/main/java/com/loomcom/symon/machines/MulticompMachine.java index 0150e58..e7349de 100644 --- a/src/main/java/com/loomcom/symon/machines/MulticompMachine.java +++ b/src/main/java/com/loomcom/symon/machines/MulticompMachine.java @@ -73,7 +73,7 @@ public class MulticompMachine implements Machine { bus.addCpu(cpu); bus.addDevice(ram); - bus.addDevice(acia); + bus.addDevice(acia, true); // TODO: Make this configurable, of course. File romImage = new File("rom.bin"); From cf36f731da25356632b63a51a544c140da3d95b0 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Thu, 17 Jul 2014 21:59:03 +0200 Subject: [PATCH 09/24] make use of the MulticompMachine in the simulator. This should be an option, not hardcoded. --- .../java/com/loomcom/symon/Simulator.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index 415333d..c9602ac 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -29,6 +29,7 @@ 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.machines.MulticompMachine; import com.loomcom.symon.machines.SymonMachine; import com.loomcom.symon.ui.*; import com.loomcom.symon.ui.Console; @@ -123,7 +124,7 @@ public class Simulator { private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"}; public Simulator() throws Exception { - this.machine = new SymonMachine(); + this.machine = new MulticompMachine(); } /** @@ -214,7 +215,9 @@ public class Simulator { memoryWindow = new MemoryWindow(machine.getBus()); // Composite Video and 6545 CRTC - videoWindow = new VideoWindow(machine.getCrtc(), 2, 2); + if(machine.getCrtc() != null) { + videoWindow = new VideoWindow(machine.getCrtc(), 2, 2); + } mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -324,8 +327,7 @@ public class Simulator { logger.severe("Console type-ahead buffer underrun!"); } - if (stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) { - videoWindow.refreshDisplay(); + if (videoWindow != null && stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) { stepsSinceLastCrtcRefresh = 0; } @@ -739,14 +741,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); } From 4a37dd74636d51a0276613b349faf281ade6a4b0 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Sat, 19 Jul 2014 17:10:57 +0200 Subject: [PATCH 10/24] move overrun detection and reset into the abstract superclass for ACIAs --- .../java/com/loomcom/symon/devices/Acia.java | 5 +++ .../com/loomcom/symon/devices/Acia6850.java | 37 ++++++------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/loomcom/symon/devices/Acia.java b/src/main/java/com/loomcom/symon/devices/Acia.java index e8664ef..f11b14e 100644 --- a/src/main/java/com/loomcom/symon/devices/Acia.java +++ b/src/main/java/com/loomcom/symon/devices/Acia.java @@ -108,11 +108,16 @@ public abstract class Acia extends Device { 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) { diff --git a/src/main/java/com/loomcom/symon/devices/Acia6850.java b/src/main/java/com/loomcom/symon/devices/Acia6850.java index bae935b..5ae9de1 100644 --- a/src/main/java/com/loomcom/symon/devices/Acia6850.java +++ b/src/main/java/com/loomcom/symon/devices/Acia6850.java @@ -42,8 +42,8 @@ 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 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 @@ -57,7 +57,7 @@ public class Acia6850 extends Acia { public Acia6850(int address) throws MemoryRangeException { super(address, ACIA_SIZE, "ACIA6850"); - setBaudRate(2400); + setBaudRate(2400); } @Override @@ -87,28 +87,13 @@ public class Acia6850 extends Acia { } } - @Override - public synchronized int rxRead() { - overrun = false; - return super.rxRead(); - } - - @Override - public synchronized void rxWrite(int data) { - if(rxFull) { - overrun = true; - } - super.rxWrite(data); - } - - private void setCommandRegister(int data) { commandRegister = data; - // Bits 0 & 1 control the master reset - if((commandRegister & 0x01) != 0 && (commandRegister & 0x02) != 0) { - reset(); - } + // 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; @@ -131,16 +116,16 @@ public class Acia6850 extends Acia { if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) { stat |= 0x02; } - if (overrun) { - stat |= 0x20; - } + if (overrun) { + stat |= 0x20; + } return stat; } private synchronized void reset() { - overrun = false; + overrun = false; rxFull = false; txEmpty = true; } From 252ebe05691344939881493b97544040084dbdb4 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Sat, 19 Jul 2014 17:30:23 +0200 Subject: [PATCH 11/24] implement overrun status flag for the 6551 ACIA --- .../com/loomcom/symon/devices/Acia6551.java | 5 ++++- src/test/java/com/loomcom/symon/AciaTest.java | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/loomcom/symon/devices/Acia6551.java b/src/main/java/com/loomcom/symon/devices/Acia6551.java index e56e79c..bdfe67c 100644 --- a/src/main/java/com/loomcom/symon/devices/Acia6551.java +++ b/src/main/java/com/loomcom/symon/devices/Acia6551.java @@ -181,7 +181,7 @@ public class Acia6551 extends Acia { */ @Override public int statusReg() { - // TODO: Overrun, Parity Error, Framing Error, DTR, DSR, and Interrupt flags. + // TODO: Parity Error, Framing Error, DTR, DSR, and Interrupt flags. int stat = 0; if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) { stat |= 0x08; @@ -189,6 +189,9 @@ public class Acia6551 extends Acia { if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) { stat |= 0x10; } + if (overrun) { + stat |= 0x04; + } return stat; } diff --git a/src/test/java/com/loomcom/symon/AciaTest.java b/src/test/java/com/loomcom/symon/AciaTest.java index 6fa3638..db34622 100644 --- a/src/test/java/com/loomcom/symon/AciaTest.java +++ b/src/test/java/com/loomcom/symon/AciaTest.java @@ -112,6 +112,25 @@ public class AciaTest { 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() From 72074b97150c6f6485a999c333c6a0b9f530acc1 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Sat, 19 Jul 2014 17:40:19 +0200 Subject: [PATCH 12/24] Silence a compiler warning regarding generics --- src/main/java/com/loomcom/symon/Simulator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index c9602ac..891f242 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -113,7 +113,7 @@ public class Simulator { private JButton runStopButton; private JButton stepButton; private JButton resetButton; - private JComboBox stepCountBox; + private JComboBox stepCountBox; private JFileChooser fileChooser; private PreferencesDialog preferences; @@ -158,7 +158,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 { From b266a023722a4e11c32c4735fed0766a61f17be6 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Sat, 19 Jul 2014 17:51:51 +0200 Subject: [PATCH 13/24] have full 56K of RAM for the Multicomp machine --- .../java/com/loomcom/symon/machines/MulticompMachine.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/loomcom/symon/machines/MulticompMachine.java b/src/main/java/com/loomcom/symon/machines/MulticompMachine.java index e7349de..5b252b0 100644 --- a/src/main/java/com/loomcom/symon/machines/MulticompMachine.java +++ b/src/main/java/com/loomcom/symon/machines/MulticompMachine.java @@ -44,9 +44,9 @@ public class MulticompMachine implements Machine { private static final int BUS_BOTTOM = 0x0000; private static final int BUS_TOP = 0xffff; - // 32K of RAM from $0000 - $7FFF + // 56K of RAM from $0000 - $DFFF private static final int MEMORY_BASE = 0x0000; - private static final int MEMORY_SIZE = 0x8000; + private static final int MEMORY_SIZE = 0xE000; // ACIA at $FFD0-$FFD1 private static final int ACIA_BASE = 0xFFD0; From 44bfbc17abd98f328746f9f803b1ed73c78b6161 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Sat, 19 Jul 2014 20:19:26 +0200 Subject: [PATCH 14/24] replace busy waiting to emulate "proper" CPU speed with some time book keeping and thread sleeping --- src/main/java/com/loomcom/symon/Cpu.java | 26 +++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Cpu.java b/src/main/java/com/loomcom/symon/Cpu.java index de6d705..4eaca75 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,10 @@ public class Cpu implements InstructionTable { /* Internal scratch space */ private int lo = 0, hi = 0; // Used in address calculation private int tmp; // Temporary storage + + /* Accounting for non-busy CPU waiting */ + private long opBeginTime; + private long surplusTime; /** * Construct a new CPU. @@ -157,6 +163,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 +1331,20 @@ 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 opFinishTime = System.nanoTime(); + long timeSpent = opFinishTime - opBeginTime; + if(timeSpent < clockSteps) { + surplusTime += clockSteps - timeSpent; + // look if a reasonable amount of time has accumulated + if(surplusTime > 1000 * 1000 * 10) { + try { + // sleep away the surplus time + Thread.sleep(surplusTime / (1000 * 1000)); + } catch (InterruptedException ex) { + Logger.getLogger(Cpu.class.getName()).log(Level.SEVERE, "Trouble putting the CPU to sleep", ex); + } + surplusTime = 0; + } } } From a49c0d40d87401cd06ccdf375424bfe94e3b74d7 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Sun, 20 Jul 2014 21:55:01 +0200 Subject: [PATCH 15/24] revert back to busy waiting for CPU speed emulation to have a less bursty CPU behavior --- src/main/java/com/loomcom/symon/Cpu.java | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Cpu.java b/src/main/java/com/loomcom/symon/Cpu.java index 4eaca75..4349c5c 100644 --- a/src/main/java/com/loomcom/symon/Cpu.java +++ b/src/main/java/com/loomcom/symon/Cpu.java @@ -78,9 +78,8 @@ public class Cpu implements InstructionTable { private int lo = 0, hi = 0; // Used in address calculation private int tmp; // Temporary storage - /* Accounting for non-busy CPU waiting */ + /* start time of op execution, needed for speed simulation */ private long opBeginTime; - private long surplusTime; /** * Construct a new CPU. @@ -1331,20 +1330,10 @@ public class Cpu implements InstructionTable { if (clockSteps == 0) { clockSteps = 1; } - long opFinishTime = System.nanoTime(); - long timeSpent = opFinishTime - opBeginTime; - if(timeSpent < clockSteps) { - surplusTime += clockSteps - timeSpent; - // look if a reasonable amount of time has accumulated - if(surplusTime > 1000 * 1000 * 10) { - try { - // sleep away the surplus time - Thread.sleep(surplusTime / (1000 * 1000)); - } catch (InterruptedException ex) { - Logger.getLogger(Cpu.class.getName()).log(Level.SEVERE, "Trouble putting the CPU to sleep", ex); - } - surplusTime = 0; - } + long opScheduledEnd = opBeginTime + clockSteps; + long now = System.nanoTime(); + while(now < opScheduledEnd) { + now = System.nanoTime(); } } From bd2337ae656aefebb34438c180e23ebc9d5a66f5 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Mon, 21 Jul 2014 17:39:20 +0200 Subject: [PATCH 16/24] allow for selecting the emulated machien type with the "-machine" parameter. The default is the Symon machine model, the Multicomp machine can be selected with "-machine multicomp". --- .../java/com/loomcom/symon/Simulator.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index 891f242..8fb6aa7 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -39,6 +39,8 @@ import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.event.*; import java.io.*; +import java.util.ArrayList; +import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; @@ -123,8 +125,8 @@ public class Simulator { */ private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"}; - public Simulator() throws Exception { - this.machine = new MulticompMachine(); + public Simulator(Class machineClass) throws Exception { + this.machine = (Machine) machineClass.getConstructors()[0].newInstance(); } /** @@ -383,12 +385,25 @@ public class Simulator { * @param args */ public static void main(String args[]) { + + 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("multicomp")) { + machineClass = MulticompMachine.class; + } + } + } + + final Class mClass = machineClass; SwingUtilities.invokeLater(new Runnable() { public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); // Create the main UI window - Simulator simulator = new Simulator(); + Simulator simulator = new Simulator(mClass); simulator.createAndShowUi(); // Reset the simulator. simulator.handleReset(); From 222772648d93da4391b6422da002f921956b345d Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Tue, 22 Jul 2014 17:50:06 +0200 Subject: [PATCH 17/24] simple dialog for choosing machine model when no command-line option was given --- .../java/com/loomcom/symon/Simulator.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index 8fb6aa7..05ee417 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -386,17 +386,37 @@ public class Simulator { */ public static void main(String args[]) { - Class machineClass = SymonMachine.class; + Class machineClass = null; 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("multicomp")) { + if(machine.equals("symon")) { + machineClass = SymonMachine.class; + } else if(machine.equals("multicomp")) { machineClass = MulticompMachine.class; } } } + 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.equals("Symon")) { + machineClass = SymonMachine.class; + } else { + machineClass = MulticompMachine.class; + } + } + + final Class mClass = machineClass; SwingUtilities.invokeLater(new Runnable() { public void run() { From d7f8045b61ed288bbcfd2e338ad005cab8047c86 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Fri, 25 Jul 2014 19:32:00 +0200 Subject: [PATCH 18/24] rework bus. Don't throw an Exception any more with overlapping device addresses - this is normal for some machines. Introduce the concept of priorities to ensure that device overlap in the correct order. --- src/main/java/com/loomcom/symon/Bus.java | 124 +++++++++--------- .../symon/machines/MulticompMachine.java | 2 +- src/test/java/com/loomcom/symon/BusTest.java | 16 --- 3 files changed, 61 insertions(+), 81 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Bus.java b/src/main/java/com/loomcom/symon/Bus.java index 9b9e91d..5ec836e 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,11 +50,8 @@ public class Bus { // The CPU private Cpu cpu; - // Ordered list of IO devices. - private SortedSet devices; - - // Ordered list of IO devices that have overlapping memory locations - private SortedSet overlapDevices; + // 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; @@ -60,8 +62,7 @@ public class Bus { } public Bus(int startAddress, int endAddress) { - this.devices = new TreeSet(); - this.overlapDevices = new TreeSet(); + this.deviceMap = new HashMap>(); this.startAddress = startAddress; this.endAddress = endAddress; } @@ -78,48 +79,37 @@ public class Bus { // TODO: Find out why +2 and not +1 is needed here int size = (this.endAddress - this.startAddress) + 2; deviceAddressArray = new Device[size]; - for(Device device : devices) { - MemoryRange range = device.getMemoryRange(); - for(int address = range.startAddress; address <= range.endAddress; ++address) { - deviceAddressArray[address] = device; - } - } - for(Device device : overlapDevices) { - MemoryRange range = device.getMemoryRange(); - for(int address = range.startAddress; address <= range.endAddress; ++address) { - deviceAddressArray[address] = device; - } - } - } - - /** - * Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others, unless the overlap parameter is true. - * - * @param device - * @param overlap - * @throws MemoryRangeException - */ - public void addDevice(Device device, boolean overlap) throws MemoryRangeException { - // Make sure there's no memory overlap. - MemoryRange memRange = device.getMemoryRange(); - if(!overlap) { - 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 + "'"); + + List priorities = new ArrayList(deviceMap.keySet()); + Collections.sort(priorities); + + for(int priority : priorities) { + SortedSet deviceSet = deviceMap.get(priority); + for(Device device : deviceSet) { + MemoryRange range = device.getMemoryRange(); + for(int address = range.startAddress; address <= range.endAddress; ++address) { + deviceAddressArray[address] = device; } } } + + } - // Add the device - device.setBus(this); - if(overlap) { - overlapDevices.add(device); - } else { - devices.add(device); + /** + * Add a device to the bus. + * + * @param device + * @param priority + */ + public void addDevice(Device device, int priority) { + + SortedSet deviceSet = deviceMap.get(priority); + if(deviceSet == null) { + deviceSet = new TreeSet(); + deviceMap.put(priority, deviceSet); } + + deviceSet.add(device); buildDeviceAddressArray(); } @@ -130,7 +120,7 @@ public class Bus { * @throws MemoryRangeException */ public void addDevice(Device device) throws MemoryRangeException { - addDevice(device, false); + addDevice(device, 0); } @@ -140,14 +130,10 @@ public class Bus { * @param device */ public void removeDevice(Device device) { - if (devices.contains(device)) { - devices.remove(device); - buildDeviceAddressArray(); - } - if (overlapDevices.contains(device)) { - overlapDevices.remove(device); - buildDeviceAddressArray(); + for(SortedSet deviceSet : deviceMap.values()) { + deviceSet.remove(device); } + buildDeviceAddressArray(); } public void addCpu(Cpu cpu) { @@ -161,19 +147,17 @@ 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] == 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 { @@ -225,7 +209,19 @@ public class Bus { public SortedSet getDevices() { // Expose a copy of the device list, not the original - return new TreeSet(devices); + 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/machines/MulticompMachine.java b/src/main/java/com/loomcom/symon/machines/MulticompMachine.java index 5b252b0..f5ec3cc 100644 --- a/src/main/java/com/loomcom/symon/machines/MulticompMachine.java +++ b/src/main/java/com/loomcom/symon/machines/MulticompMachine.java @@ -73,7 +73,7 @@ public class MulticompMachine implements Machine { bus.addCpu(cpu); bus.addDevice(ram); - bus.addDevice(acia, true); + bus.addDevice(acia, 1); // TODO: Make this configurable, of course. File romImage = new File("rom.bin"); 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); From 52f4e9a00f352d7a6865d8be803ba87242307ba4 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Fri, 25 Jul 2014 21:24:16 +0200 Subject: [PATCH 19/24] enforce that the address range of devices falls within the address range of the bus. Turned out that the CPU tests instantiate memory with the last parameter as memory size, not end address (fixed now). Also make sure that the address lookup array takes the offset caused by non-zero starting addresses into account. --- src/main/java/com/loomcom/symon/Bus.java | 22 +++++++++++++------ .../loomcom/symon/CpuAbsoluteModeTest.java | 2 +- .../loomcom/symon/CpuAbsoluteXModeTest.java | 2 +- .../loomcom/symon/CpuAbsoluteYModeTest.java | 2 +- .../loomcom/symon/CpuAccumulatorModeTest.java | 2 +- .../loomcom/symon/CpuImmediateModeTest.java | 2 +- .../com/loomcom/symon/CpuImpliedModeTest.java | 2 +- .../symon/CpuIndexedIndirectModeTest.java | 2 +- .../symon/CpuIndirectIndexedModeTest.java | 2 +- .../loomcom/symon/CpuIndirectModeTest.java | 2 +- .../loomcom/symon/CpuIndirectXModeTest.java | 2 +- .../loomcom/symon/CpuRelativeModeTest.java | 2 +- src/test/java/com/loomcom/symon/CpuTest.java | 2 +- .../loomcom/symon/CpuZeroPageModeTest.java | 2 +- .../loomcom/symon/CpuZeroPageXModeTest.java | 2 +- .../loomcom/symon/CpuZeroPageYModeTest.java | 2 +- 16 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Bus.java b/src/main/java/com/loomcom/symon/Bus.java index 5ec836e..1b36044 100644 --- a/src/main/java/com/loomcom/symon/Bus.java +++ b/src/main/java/com/loomcom/symon/Bus.java @@ -76,8 +76,7 @@ public class Bus { } private void buildDeviceAddressArray() { - // TODO: Find out why +2 and not +1 is needed here - int size = (this.endAddress - this.startAddress) + 2; + int size = (this.endAddress - this.startAddress) + 1; deviceAddressArray = new Device[size]; List priorities = new ArrayList(deviceMap.keySet()); @@ -88,7 +87,7 @@ public class Bus { for(Device device : deviceSet) { MemoryRange range = device.getMemoryRange(); for(int address = range.startAddress; address <= range.endAddress; ++address) { - deviceAddressArray[address] = device; + deviceAddressArray[address - this.startAddress] = device; } } } @@ -101,7 +100,16 @@ public class Bus { * @param device * @param priority */ - public void addDevice(Device device, int priority) { + 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) { @@ -152,7 +160,7 @@ public class Bus { } for(int address = startAddress; address <= endAddress; ++address) { - if(deviceAddressArray[address] == null) { + if(deviceAddressArray[address - startAddress] == null) { return false; } } @@ -161,7 +169,7 @@ public class Bus { } public int read(int address) throws MemoryAccessException { - Device d = deviceAddressArray[address]; + Device d = deviceAddressArray[address - this.startAddress]; if(d != null) { MemoryRange range = d.getMemoryRange(); int devAddr = address - range.startAddress(); @@ -172,7 +180,7 @@ public class Bus { } public void write(int address, int value) throws MemoryAccessException { - Device d = deviceAddressArray[address]; + Device d = deviceAddressArray[address - this.startAddress]; if(d != null) { MemoryRange range = d.getMemoryRange(); int devAddr = address - range.startAddress(); 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); From 2217f3831dbfbee9536b24dbc624f82f5bbd0887 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Fri, 25 Jul 2014 21:27:40 +0200 Subject: [PATCH 20/24] slight JavaDoc fix regarding what Exception can be thrown --- src/main/java/com/loomcom/symon/Bus.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/loomcom/symon/Bus.java b/src/main/java/com/loomcom/symon/Bus.java index 1b36044..26dc91b 100644 --- a/src/main/java/com/loomcom/symon/Bus.java +++ b/src/main/java/com/loomcom/symon/Bus.java @@ -99,6 +99,7 @@ public class Bus { * * @param device * @param priority + * @throws MemoryRangeException */ public void addDevice(Device device, int priority) throws MemoryRangeException { From dc7dfc4b4dfeca3cbd2b904b294de0a13f25db69 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Fri, 25 Jul 2014 21:44:43 +0200 Subject: [PATCH 21/24] code simplification --- src/main/java/com/loomcom/symon/Bus.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Bus.java b/src/main/java/com/loomcom/symon/Bus.java index 26dc91b..63f8070 100644 --- a/src/main/java/com/loomcom/symon/Bus.java +++ b/src/main/java/com/loomcom/symon/Bus.java @@ -78,17 +78,12 @@ public class Bus { private void buildDeviceAddressArray() { int size = (this.endAddress - this.startAddress) + 1; deviceAddressArray = new Device[size]; - - List priorities = new ArrayList(deviceMap.keySet()); - Collections.sort(priorities); - - for(int priority : priorities) { - SortedSet deviceSet = deviceMap.get(priority); - for(Device device : deviceSet) { - MemoryRange range = device.getMemoryRange(); - for(int address = range.startAddress; address <= range.endAddress; ++address) { - deviceAddressArray[address - this.startAddress] = device; - } + + // 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; } } @@ -217,7 +212,7 @@ public class Bus { } public SortedSet getDevices() { - // Expose a copy of the device list, not the original + // create an ordered set of devices, ordered by device priorities SortedSet devices = new TreeSet(); List priorities = new ArrayList(deviceMap.keySet()); From 827e9991d50e5b347429a23fc10373fd8d38bdc9 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Sat, 26 Jul 2014 18:07:29 +0200 Subject: [PATCH 22/24] move main method into Main class in anticipation of changes that allow restarting the complete UI from within the Simulator menu system --- pom.xml | 2 +- src/main/java/com/loomcom/symon/Main.java | 93 +++++++++++++++++++ .../java/com/loomcom/symon/Simulator.java | 60 +----------- 3 files changed, 95 insertions(+), 60 deletions(-) create mode 100644 src/main/java/com/loomcom/symon/Main.java 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/Main.java b/src/main/java/com/loomcom/symon/Main.java new file mode 100644 index 0000000..78d7b59 --- /dev/null +++ b/src/main/java/com/loomcom/symon/Main.java @@ -0,0 +1,93 @@ +/* + * 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[]) { + + Class machineClass = null; + 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; + } + } + } + + 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.equals("Symon")) { + machineClass = SymonMachine.class; + } else { + machineClass = MulticompMachine.class; + } + } + + + final Class mClass = machineClass; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + // Create the main UI window + Simulator simulator = new Simulator(mClass); + simulator.createAndShowUi(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + + } + +} diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index 05ee417..b064d5b 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -29,8 +29,6 @@ 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.machines.MulticompMachine; -import com.loomcom.symon.machines.SymonMachine; import com.loomcom.symon.ui.*; import com.loomcom.symon.ui.Console; @@ -39,8 +37,6 @@ import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.event.*; import java.io.*; -import java.util.ArrayList; -import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; @@ -232,6 +228,7 @@ public class Simulator { mainWindow.setVisible(true); console.requestFocus(); + handleReset(); } private void handleStart() { @@ -378,61 +375,6 @@ public class Simulator { }); } - /** - * Main entry point to the simulator. Creates a simulator and shows the main - * window. - * - * @param args - */ - public static void main(String args[]) { - - Class machineClass = null; - 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; - } - } - } - - 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.equals("Symon")) { - machineClass = SymonMachine.class; - } else { - machineClass = MulticompMachine.class; - } - } - - - final Class mClass = machineClass; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - // Create the main UI window - Simulator simulator = new Simulator(mClass); - simulator.createAndShowUi(); - // Reset the simulator. - simulator.handleReset(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } /** * The main run thread. From fdaeb661d39621d9f02b012deaa9f530628eb509 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Sat, 26 Jul 2014 18:52:57 +0200 Subject: [PATCH 23/24] add menu entry to allow to switch the emulated machine type --- src/main/java/com/loomcom/symon/Main.java | 70 +++++++++++-------- .../java/com/loomcom/symon/Simulator.java | 49 +++++++++++++ 2 files changed, 88 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Main.java b/src/main/java/com/loomcom/symon/Main.java index 78d7b59..a1b2096 100644 --- a/src/main/java/com/loomcom/symon/Main.java +++ b/src/main/java/com/loomcom/symon/Main.java @@ -40,9 +40,9 @@ public class Main { * * @param args */ - public static void main(String args[]) { + public static void main(String args[]) throws Exception { - Class machineClass = null; + 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) { @@ -55,38 +55,46 @@ public class Main { } } - 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.equals("Symon")) { - machineClass = SymonMachine.class; - } else { - machineClass = MulticompMachine.class; - } - } - - - final Class mClass = machineClass; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - // Create the main UI window - Simulator simulator = new Simulator(mClass); - simulator.createAndShowUi(); - } catch (Exception e) { - e.printStackTrace(); + 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.equals("Symon")) { + machineClass = SymonMachine.class; + } else { + machineClass = MulticompMachine.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 b064d5b..161c9d4 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -116,6 +116,13 @@ public class Simulator { 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. */ @@ -230,6 +237,18 @@ public class Simulator { 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. @@ -543,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() { @@ -669,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); From f90516e53ca57c043e43fd86bc8ccc458f58f68e Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Sat, 26 Jul 2014 18:55:51 +0200 Subject: [PATCH 24/24] fix a possible NPE --- src/main/java/com/loomcom/symon/Main.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/loomcom/symon/Main.java b/src/main/java/com/loomcom/symon/Main.java index a1b2096..cb116be 100644 --- a/src/main/java/com/loomcom/symon/Main.java +++ b/src/main/java/com/loomcom/symon/Main.java @@ -66,10 +66,12 @@ public class Main { null, possibilities, "Symon"); - if(s.equals("Symon")) { - machineClass = SymonMachine.class; - } else { + + + if(s != null && s.equals("Multicomp")) { machineClass = MulticompMachine.class; + } else { + machineClass = SymonMachine.class; } }