diff --git a/src/main/java/com/loomcom/lm6502/Bus.java b/src/main/java/com/loomcom/lm6502/Bus.java index 728f489..9856a93 100644 --- a/src/main/java/com/loomcom/lm6502/Bus.java +++ b/src/main/java/com/loomcom/lm6502/Bus.java @@ -2,33 +2,35 @@ package com.loomcom.lm6502; import java.util.*; import com.loomcom.lm6502.devices.*; +import com.loomcom.lm6502.exceptions.*; public class Bus { - private int bottom = 0x0000; - private int top = 0xffff; + /* By default, our bus starts at 0, and goes up to 64K */ + private int startAddress = 0x0000; + private int endAddress = 0xffff; /** * Ordered list of IO devices. */ - private List devices; + private SortedSet devices; public Bus(int size) { this(0, size - 1); } - public Bus(int bottom, int top) { - this.devices = new ArrayList(8); - this.bottom = bottom; - this.top = top; + public Bus(int startAddress, int endAddress) { + this.devices = new TreeSet(); + this.startAddress = startAddress; + this.endAddress = endAddress; } - public int bottom() { - return bottom; + public int startAddress() { + return startAddress; } - public int top() { - return top; + public int endAddress() { + return endAddress; } public void addDevice(Device device) @@ -62,21 +64,21 @@ public class Bus { for (Device d : devices) { MemoryRange cur = d.getMemoryRange(); if (i == 0) { - // If the first entry doesn't start at 'bottom', return false. - if (cur.getStartAddress() != bottom) { return false; } + // If the first entry doesn't start at 'startAddress', return false. + if (cur.startAddress() != startAddress) { return false; } } if (prev != null && i < length - 1) { // Otherwise, compare previous map's end against this map's - // top. They must be adjacent! - if (cur.getStartAddress() - 1 != prev.getEndAddress()) { + // endAddress. They must be adjacent! + if (cur.startAddress() - 1 != prev.endAddress()) { return false; } } if (i == length - 1) { - // If the last entry doesn't end at top, return false; - if (cur.getEndAddress() != top) { return false; } + // If the last entry doesn't end at endAddress, return false; + if (cur.endAddress() != endAddress) { return false; } } i++; @@ -87,8 +89,35 @@ public class Bus { return true; } - public List getDevices() { + public int read(int address) { + for (Device d : devices) { + 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); + } + } + // TODO: Raise access error. + throw new RuntimeException("Read failed! Device not found."); + } + + public void write(int address, int value) { + for (Device d : devices) { + 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; + } + } + // TODO: Raise access error. + throw new RuntimeException("Write failed! Device not found."); + } + + public SortedSet getDevices() { // Expose a copy of the device list, not the original - return new ArrayList(devices); + return new TreeSet(devices); } } diff --git a/src/main/java/com/loomcom/lm6502/MemoryRange.java b/src/main/java/com/loomcom/lm6502/MemoryRange.java index ecf3e3e..885ba7d 100644 --- a/src/main/java/com/loomcom/lm6502/MemoryRange.java +++ b/src/main/java/com/loomcom/lm6502/MemoryRange.java @@ -1,7 +1,19 @@ package com.loomcom.lm6502; +import com.loomcom.lm6502.exceptions.*; + +/** + * MemoryRange is a simple container class representing a literal + * range of memory, with a staraddress, and an end address. It has + * guards against creating impossible memory ranges, and implements + * some useful methods for checking address inclusion and range + * overlaps. + */ public class MemoryRange implements Comparable { + + /** The starting address of the memory range. */ public int startAddress; + /** The ending address of the memory range. */ public int endAddress; public MemoryRange(int startAddress, int endAddress) @@ -17,11 +29,17 @@ public class MemoryRange implements Comparable { this.endAddress = endAddress; } - public int getStartAddress() { + /** + * @returns the starting address. + */ + public int startAddress() { return startAddress; } - public int getEndAddress() { + /** + * @returns the ending address. + */ + public int endAddress() { return endAddress; } @@ -42,8 +60,8 @@ public class MemoryRange implements Comparable { * @returns true if this range overlaps in any way with the other. */ public boolean overlaps(MemoryRange other) { - return (this.includes(other.getStartAddress()) || - other.includes(this.getStartAddress())); + return (this.includes(other.startAddress()) || + other.includes(this.startAddress())); } // Implementation of Comparable interface @@ -54,8 +72,8 @@ public class MemoryRange implements Comparable { if (this == other) { return 0; } - Integer thisStartAddr = new Integer(this.getStartAddress()); - Integer thatStartAddr = new Integer(other.getStartAddress()); + Integer thisStartAddr = new Integer(this.startAddress()); + Integer thatStartAddr = new Integer(other.startAddress()); return thisStartAddr.compareTo(thatStartAddr); } diff --git a/src/main/java/com/loomcom/lm6502/Profiler.java b/src/main/java/com/loomcom/lm6502/Profiler.java new file mode 100644 index 0000000..2c5315e --- /dev/null +++ b/src/main/java/com/loomcom/lm6502/Profiler.java @@ -0,0 +1,66 @@ +/** + * A simple profiler, for debugging the simulator. + * It is safe to ignore me. + */ + +package com.loomcom.lm6502; + +import java.util.*; +import com.loomcom.lm6502.devices.*; +import com.loomcom.lm6502.exceptions.*; + +public class Profiler { + public static void main(String[] args) { + + try { + + // Create a bus. + Bus b = new Bus(0, 65535); + + // Create eight devices, each 8KB, to fill the bus. + b.addDevice(new Memory(0x0000, 0x2000, null)); // 8KB @ $0000-$1fff + b.addDevice(new Memory(0x2000, 0x2000, null)); // 8KB @ $2000-$3fff + b.addDevice(new Memory(0x4000, 0x2000, null)); // 8KB @ $4000-$5fff + b.addDevice(new Memory(0x6000, 0x2000, null)); // 8KB @ $6000-$7fff + b.addDevice(new Memory(0x8000, 0x2000, null)); // 8KB @ $8000-$9fff + b.addDevice(new Memory(0xa000, 0x2000, null)); // 8KB @ $a000-$bfff + b.addDevice(new Memory(0xc000, 0x2000, null)); // 8KB @ $c000-$dfff + b.addDevice(new Memory(0xe000, 0x2000, null)); // 8KB @ $e000-$ffff + + // Read memory + long sum = 0; + long average = 0; + + long iters = 500; + + for (int i = 0; i < iters; i++) { + + long startTime = System.nanoTime(); + + // Read and assign to a buffer + int buf = 0; + for (int j = 0; j < 0xffff; j++) { + buf = b.read(j); + if (buf != 0xff) { + System.out.println("WARNING! MEMORY SHOULD HAVE BEEN $FF, WAS: " + buf); + System.exit(0); + } + } + + long endTime = System.nanoTime(); + long diff = endTime - startTime; + + // System.out.println("Read of 64KB took: " + diff + " ns (" + (diff / 1000) + " us)"); + + sum += diff; + average = sum / (i + 1); + } + + System.out.println("Average time to read 64KB: " + average + " ns (" + (average / 1000) + " us)"); + System.out.println("Average time to read one byte: " + sum / (64 * 1024 * iters) + " ns"); + + } catch (MemoryRangeException ex) { + System.out.println("Memory Access Exception! " + ex.getMessage()); + } + } +} diff --git a/src/main/java/com/loomcom/lm6502/Simulator.java b/src/main/java/com/loomcom/lm6502/Simulator.java index 391b641..0aeeac4 100644 --- a/src/main/java/com/loomcom/lm6502/Simulator.java +++ b/src/main/java/com/loomcom/lm6502/Simulator.java @@ -10,30 +10,26 @@ public class Simulator { /** * Command-line parser used by this simulator. */ - CommandParser m_parser; + CommandParser parser; /** * The CPU itself. */ - Cpu m_cpu; + Cpu cpu; /** * The Bus responsible for routing memory read/write requests to the * correct IO devices. */ - Bus m_bus; + Bus bus; public Simulator() { - m_cpu = new Cpu(this); - m_parser = new CommandParser(System.in, System.out, this); + cpu = new Cpu(this); + parser = new CommandParser(System.in, System.out, this); } public void run() { - m_parser.run(); - } - - public static void main(String[] args) { - new Simulator().run(); + parser.run(); } public void step() { @@ -45,4 +41,12 @@ public class Simulator { public void write(int address, int value) { } + + /** + * Main simulator routine. + */ + public static void main(String[] args) { + new Simulator().run(); + } + } diff --git a/src/main/java/com/loomcom/lm6502/devices/Device.java b/src/main/java/com/loomcom/lm6502/devices/Device.java index ce9d7c8..20cb6b2 100644 --- a/src/main/java/com/loomcom/lm6502/devices/Device.java +++ b/src/main/java/com/loomcom/lm6502/devices/Device.java @@ -1,12 +1,13 @@ package com.loomcom.lm6502.devices; import com.loomcom.lm6502.*; +import com.loomcom.lm6502.exceptions.*; /** * A memory-mapped IO Device. */ -public abstract class Device { +public abstract class Device implements Comparable { /** The memory range for this device. */ private MemoryRange memoryRange; @@ -23,20 +24,21 @@ public abstract class Device { this.cpu = cpu; } + /* Methods required to be implemented by inheriting classes. */ public abstract void write(int address, int data); - public abstract int read(int address); + public abstract String toString(); public MemoryRange getMemoryRange() { return memoryRange; } - public int getEndAddress() { - return memoryRange.getEndAddress(); + public int endAddress() { + return memoryRange.endAddress(); } - public int getStartAddress() { - return memoryRange.getStartAddress(); + public int startAddress() { + return memoryRange.startAddress(); } public String getName() { @@ -47,4 +49,17 @@ public abstract class Device { this.name = name; } + /** + * Compares two devices. The sort order is defined by the sort + * order of the device's memory ranges. + */ + public int compareTo(Device other) { + if (other == null) { + throw new NullPointerException("Cannot compare to null."); + } + if (this == other) { + return 0; + } + return getMemoryRange().compareTo(other.getMemoryRange()); + } } diff --git a/src/main/java/com/loomcom/lm6502/devices/Memory.java b/src/main/java/com/loomcom/lm6502/devices/Memory.java index a3878c8..fc308f3 100644 --- a/src/main/java/com/loomcom/lm6502/devices/Memory.java +++ b/src/main/java/com/loomcom/lm6502/devices/Memory.java @@ -1,15 +1,25 @@ package com.loomcom.lm6502.devices; +import java.util.*; + import com.loomcom.lm6502.*; +import com.loomcom.lm6502.exceptions.*; public class Memory extends Device { private boolean readOnly; + private int[] mem; + public Memory(int address, int size, Cpu cpu, boolean readOnly) throws MemoryRangeException { super(address, size, "RW Memory", cpu); + this.readOnly = readOnly; + this.mem = new int[size]; + + // Init the mem to all 0xff + Arrays.fill(this.mem, 0xff); } public Memory(int address, int size, Cpu cpu) @@ -18,10 +28,17 @@ public class Memory extends Device { } public void write(int address, int data) { + System.out.println(String.format("[write] Before write: $%04x=$%04x", address, this.mem[address])); + this.mem[address] = data; + System.out.println(String.format("[write] After write: $%04x=$%04x", address, this.mem[address])); } public int read(int address) { - return 0; + return this.mem[address]; + } + + public String toString() { + return "Memory: " + getMemoryRange().toString(); } } \ No newline at end of file diff --git a/src/main/java/com/loomcom/lm6502/exceptions/MemoryAccessException.java b/src/main/java/com/loomcom/lm6502/exceptions/MemoryAccessException.java new file mode 100644 index 0000000..951df42 --- /dev/null +++ b/src/main/java/com/loomcom/lm6502/exceptions/MemoryAccessException.java @@ -0,0 +1,11 @@ +package com.loomcom.lm6502.exceptions; + +/** + * Exception that will be thrown if access to memory or IO cannot be + * accessed. + */ +public class MemoryAccessException extends Exception { + public MemoryAccessException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/loomcom/lm6502/MemoryRangeException.java b/src/main/java/com/loomcom/lm6502/exceptions/MemoryRangeException.java similarity index 83% rename from src/main/java/com/loomcom/lm6502/MemoryRangeException.java rename to src/main/java/com/loomcom/lm6502/exceptions/MemoryRangeException.java index 0d8e42c..9551d1b 100644 --- a/src/main/java/com/loomcom/lm6502/MemoryRangeException.java +++ b/src/main/java/com/loomcom/lm6502/exceptions/MemoryRangeException.java @@ -1,4 +1,4 @@ -package com.loomcom.lm6502; +package com.loomcom.lm6502.exceptions; /** * Exception that will be thrown if devices conflict in the IO map. diff --git a/src/test/java/com/loomcom/lm6502/BusTest.java b/src/test/java/com/loomcom/lm6502/BusTest.java index c066bc2..744e8c6 100644 --- a/src/test/java/com/loomcom/lm6502/BusTest.java +++ b/src/test/java/com/loomcom/lm6502/BusTest.java @@ -3,6 +3,7 @@ package com.loomcom.lm6502; import junit.framework.*; import com.loomcom.lm6502.devices.*; +import com.loomcom.lm6502.exceptions.*; import java.util.*; @@ -19,32 +20,32 @@ public class BusTest extends TestCase { return new TestSuite(BusTest.class); } - public void testCreatingWithTopAndBottom() { + public void testCreatingWithStartAndEndAddresses() { Bus b = null; b = new Bus(0x00, 0xff); - assertEquals(0x00, b.bottom()); - assertEquals(0xff, b.top()); + assertEquals(0x00, b.startAddress()); + assertEquals(0xff, b.endAddress()); b = new Bus(0x20, 0xea); - assertEquals(0x20, b.bottom()); - assertEquals(0xea, b.top()); + assertEquals(0x20, b.startAddress()); + assertEquals(0xea, b.endAddress()); } public void testCreatingWithSize() { Bus b = null; b = new Bus(256); - assertEquals(0x00, b.bottom()); - assertEquals(0xff, b.top()); + assertEquals(0x00, b.startAddress()); + assertEquals(0xff, b.endAddress()); b = new Bus(4096); - assertEquals(0x000, b.bottom()); - assertEquals(0xfff, b.top()); + assertEquals(0x000, b.startAddress()); + assertEquals(0xfff, b.endAddress()); b = new Bus(65536); - assertEquals(0x0000, b.bottom()); - assertEquals(0xffff, b.top()); + assertEquals(0x0000, b.startAddress()); + assertEquals(0xffff, b.endAddress()); } public void testAddDevice() throws MemoryRangeException { @@ -76,7 +77,7 @@ public class BusTest extends TestCase { } } - public void testIsCompleteWithFirstDeviceNotStartingAtBottom() throws MemoryRangeException { + public void testIsCompleteWithFirstDeviceNotStartingAtStartAddress() throws MemoryRangeException { Device memory = new Memory(0x00ff, 0xff00, null, true); Bus b = new Bus(0x0000, 0xffff); diff --git a/src/test/java/com/loomcom/lm6502/MemoryRangeTest.java b/src/test/java/com/loomcom/lm6502/MemoryRangeTest.java index d115626..f4fd340 100644 --- a/src/test/java/com/loomcom/lm6502/MemoryRangeTest.java +++ b/src/test/java/com/loomcom/lm6502/MemoryRangeTest.java @@ -5,6 +5,8 @@ import junit.framework.*; import java.util.HashMap; import java.util.Map; +import com.loomcom.lm6502.exceptions.*; + /** * */ @@ -36,14 +38,14 @@ public class MemoryRangeTest extends TestCase { new MemoryRange(2, 10); } - public void testGetStartAddress() throws MemoryRangeException { + public void testStartAddress() throws MemoryRangeException { MemoryRange r = new MemoryRange(0x101, 0x202); - assertEquals(0x101, r.getStartAddress()); + assertEquals(0x101, r.startAddress()); } - public void testGetEndAddress() throws MemoryRangeException { + public void testEndAddress() throws MemoryRangeException { MemoryRange r = new MemoryRange(0x101, 0x202); - assertEquals(0x202, r.getEndAddress()); + assertEquals(0x202, r.endAddress()); } public void testOverlaps() throws MemoryRangeException {