From 5bbd72d44e8b5d928c9c847c5391fa86199d78fb Mon Sep 17 00:00:00 2001 From: "Seth J. Morabito" Date: Mon, 8 Dec 2008 20:40:27 -0800 Subject: [PATCH] Unit testing. MemoryRange work. --- .../com/loomcom/lm6502/AddressDecoder.java | 97 ++++++------- .../com/loomcom/lm6502/CommandParser.java | 25 ++-- src/main/java/com/loomcom/lm6502/Cpu.java | 29 ++-- src/main/java/com/loomcom/lm6502/Device.java | 18 +-- .../java/com/loomcom/lm6502/MemoryRange.java | 66 +++++++++ .../loomcom/lm6502/MemoryRangeException.java | 10 ++ .../com/loomcom/lm6502/MemoryRangeTest.java | 132 ++++++++++++++++++ 7 files changed, 289 insertions(+), 88 deletions(-) create mode 100644 src/main/java/com/loomcom/lm6502/MemoryRange.java create mode 100644 src/main/java/com/loomcom/lm6502/MemoryRangeException.java create mode 100644 src/test/java/com/loomcom/lm6502/MemoryRangeTest.java diff --git a/src/main/java/com/loomcom/lm6502/AddressDecoder.java b/src/main/java/com/loomcom/lm6502/AddressDecoder.java index 67634b9..5472dc3 100644 --- a/src/main/java/com/loomcom/lm6502/AddressDecoder.java +++ b/src/main/java/com/loomcom/lm6502/AddressDecoder.java @@ -1,20 +1,32 @@ package com.loomcom.lm6502; -import java.util.Map; +import java.util.*; public class AddressDecoder { - /** - * Map of memory ranges to IO devices. - */ - private Map m_ioMap; - public AddressDecoder() {} + public static final int MEMORY_BOTTOM = 0x0000; + public static final int MEMORY_TOP = 0xFFFF; + + /** + * Ordered map of memory ranges to IO devices. + */ + private SortedMap ioMap; + + public AddressDecoder() { + ioMap = new TreeMap(); + } public void addDevice(Device d) - throws MemoryConflictException { + throws MemoryRangeException { // Make sure there's no memory overlap. + for (MemoryRange memRange : ioMap.keySet()) { + if (d.getMemoryRange().overlapsWith(memRange)) { + throw new MemoryRangeException("The device being added overlaps with an existing device."); + } + } + // Add the device to the map. - m_ioMap.put(d.getMemoryRange(), d); + ioMap.put(d.getMemoryRange(), d); } /** @@ -23,49 +35,40 @@ public class AddressDecoder { * device. */ public boolean isComplete() { + // Emtpy maps cannot be complete. + if (ioMap.isEmpty()) { return false; } + + // Loop over devices and ensure they are contiguous. + MemoryRange prev = null; + int i = 0; + int size = ioMap.size(); + for (Map.Entry e : ioMap.entrySet()) { + MemoryRange cur = (MemoryRange)e.getKey(); + if (i == 0) { + // If the first entry doesn't start at MEMORY_BOTTOM, return false. + if (cur.getStartAddress() != MEMORY_BOTTOM) { return false; } + } else if (i < size - 1) { + // Otherwise, compare previous map's end against this map's + // top. They must be adjacent! + if (cur.getStartAddress() - 1 != prev.getEndAddress()) { + return false; + } + } else { + // If the last entry doesn't end at MEMORY_TOP, return false; + if (cur.getEndAddress() != MEMORY_TOP) { return false; } + } + i++; + prev = cur; + } + + // Must be complete. return true; } /** - * Returns true if the memory map is 'sparse', i.e., there - * are gaps between IO devices. + * Returns true if there are any gap in the memory map. */ - public boolean isSparse() { + public boolean hasGaps() { return !isComplete(); } -} - -class MemoryRange { - public int m_startAddress; - public int m_endAddress; - - /** - * @returns true if the address is included within this range, - * false otherwise. - */ - public boolean includes(int address) { - return (address <= m_endAddress && - address >= m_startAddress); - } - - public void setStartAddress(int startAddress) { - m_startAddress = startAddress; - } - - public void setEndAddress(int endAddress) { - m_endAddress = endAddress; - } - - public int getStartAddress() { - return m_startAddress; - } - - public int getEndAddress() { - return m_endAddress; - } -} - -/** - * Excption that will be thrown if devices conflict in the IO map. - */ -class MemoryConflictException extends Exception {} \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/java/com/loomcom/lm6502/CommandParser.java b/src/main/java/com/loomcom/lm6502/CommandParser.java index e960055..e0d1f9f 100644 --- a/src/main/java/com/loomcom/lm6502/CommandParser.java +++ b/src/main/java/com/loomcom/lm6502/CommandParser.java @@ -4,13 +4,14 @@ import java.io.*; public class CommandParser { - private BufferedReader m_in; - private BufferedWriter m_out; - private Simulator m_simulator; + private BufferedReader in; + private BufferedWriter out; + private Simulator simulator; - public CommandParser(InputStream in, OutputStream out, Simulator s) { - m_in = new BufferedReader(new InputStreamReader(in)); - m_out = new BufferedWriter(new OutputStreamWriter(out)); + public CommandParser(InputStream i, OutputStream o, Simulator s) { + this.in = new BufferedReader(new InputStreamReader(i)); + this.out = new BufferedWriter(new OutputStreamWriter(o)); + this.simulator = s; } public void run() { @@ -45,20 +46,20 @@ public class CommandParser { } private void prompt() throws IOException { - m_out.write("j6502> "); - m_out.flush(); + out.write("j6502> "); + out.flush(); } private String readLine() throws IOException { - String line = m_in.readLine(); + String line = in.readLine(); if (line == null) { return null; } return line.trim(); } private void writeLine(String line) throws IOException { - m_out.write(line); - m_out.newLine(); - m_out.flush(); + out.write(line); + out.newLine(); + out.flush(); } /** diff --git a/src/main/java/com/loomcom/lm6502/Cpu.java b/src/main/java/com/loomcom/lm6502/Cpu.java index b051f2d..4c8d1bc 100644 --- a/src/main/java/com/loomcom/lm6502/Cpu.java +++ b/src/main/java/com/loomcom/lm6502/Cpu.java @@ -5,33 +5,22 @@ package com.loomcom.lm6502; */ public class Cpu { - /** - * The Program Counter. - */ - private int m_pc; - - /** - * The system stack pointer. - */ - private int m_sp; - - /** - * Reference to the simulator - */ - private Simulator m_sim; + private int pc; + private int sp; + private Simulator sim; public Cpu(Simulator sim) { reset(); - this.m_sim = sim; + this.sim = sim; } /** * Reset the CPU to known initial values. */ public void reset() { - m_sp = 0x01ff; + sp = 0x01ff; /* locations fffc and fffd hold the reset vector address */ - m_pc = 0xfffc; + pc = 0xfffc; } /** @@ -51,7 +40,7 @@ public class Cpu { * Program Counter. */ private int readAddress() { - return readAddress(m_pc); + return readAddress(pc); } /** @@ -62,11 +51,11 @@ public class Cpu { * @return The address specified in the two bytes at location addr */ private int readAddress(int address) { - return (m_sim.read(address)<<8 & m_sim.read(address+1)); + return (sim.read(address)<<8 & sim.read(address+1)); } public Simulator getSimulator() { - return m_sim; + return sim; } } diff --git a/src/main/java/com/loomcom/lm6502/Device.java b/src/main/java/com/loomcom/lm6502/Device.java index 4fc7cca..0a3e2f5 100644 --- a/src/main/java/com/loomcom/lm6502/Device.java +++ b/src/main/java/com/loomcom/lm6502/Device.java @@ -7,34 +7,34 @@ public abstract class Device { /** The memory range for this device. */ - private MemoryRange m_memoryRange; + private MemoryRange memoryRange; /** Reference to the CPU, for interrupts. */ - private Cpu m_cpu; + private Cpu cpu; public Device(MemoryRange range, Cpu cpu) { - m_memoryRange = range; - m_cpu = cpu; + this.memoryRange = range; + this.cpu = cpu; } public MemoryRange getMemoryRange() { - return m_memoryRange; + return memoryRange; } public int getEndAddress() { - return m_memoryRange.getEndAddress(); + return memoryRange.getEndAddress(); } public int getStartAddress() { - return m_memoryRange.getStartAddress(); + return memoryRange.getStartAddress(); } public void generateInterrupt() { - m_cpu.interrupt(); + cpu.interrupt(); } public void generateNonMaskableInterrupt() { - m_cpu.nmiInterrupt(); + cpu.nmiInterrupt(); } } \ No newline at end of file diff --git a/src/main/java/com/loomcom/lm6502/MemoryRange.java b/src/main/java/com/loomcom/lm6502/MemoryRange.java new file mode 100644 index 0000000..0e0455a --- /dev/null +++ b/src/main/java/com/loomcom/lm6502/MemoryRange.java @@ -0,0 +1,66 @@ +package com.loomcom.lm6502; + +public class MemoryRange implements Comparable { + public int startAddress; + public int endAddress; + + public MemoryRange(int startAddress, int endAddress) + throws MemoryRangeException { + if (startAddress < 0 || endAddress < 0) { + throw new MemoryRangeException("Addresses cannot be less than 0."); + } + if (startAddress >= endAddress) { + throw new MemoryRangeException("End address must be greater than start address."); + } + this.startAddress = startAddress; + this.endAddress = endAddress; + } + + public int getStartAddress() { + return startAddress; + } + + public int getEndAddress() { + return endAddress; + } + + /** + * Checks for address inclusion in the range. + * + * @returns true if the address is included within this range, + * false otherwise. + */ + public boolean includes(int address) { + return (address <= endAddress && + address >= startAddress); + } + + /** + * Checks for overlapping memory ranges. + * + * @returns true if this range overlaps in any way with the other. + */ + public boolean overlapsWith(MemoryRange other) { + return ((this.getEndAddress() >= other.getStartAddress() && + this.getEndAddress() <= other.getEndAddress()) || + (other.getEndAddress() >= this.getStartAddress() && + other.getEndAddress() <= this.getEndAddress()) || + (this.getStartAddress() <= other.getStartAddress() && + this.getEndAddress() >= other.getEndAddress()) || + (other.getStartAddress() <= this.getStartAddress() && + other.getEndAddress() >= this.getEndAddress())); + } + + // Implementation of Comparable interface + public int compareTo(MemoryRange other) { + if (other == null) { + throw new NullPointerException("Cannot compare to null."); + } + if (this == other) { + return 0; + } + Integer thisStartAddr = new Integer(this.getStartAddress()); + Integer thatStartAddr = new Integer(other.getStartAddress()); + return thisStartAddr.compareTo(thatStartAddr); + } +} diff --git a/src/main/java/com/loomcom/lm6502/MemoryRangeException.java b/src/main/java/com/loomcom/lm6502/MemoryRangeException.java new file mode 100644 index 0000000..e309c36 --- /dev/null +++ b/src/main/java/com/loomcom/lm6502/MemoryRangeException.java @@ -0,0 +1,10 @@ +package com.loomcom.lm6502; + +/** + * Exception that will be thrown if devices conflict in the IO map. + */ +class MemoryRangeException extends Exception { + public MemoryRangeException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/src/test/java/com/loomcom/lm6502/MemoryRangeTest.java b/src/test/java/com/loomcom/lm6502/MemoryRangeTest.java new file mode 100644 index 0000000..a331a6c --- /dev/null +++ b/src/test/java/com/loomcom/lm6502/MemoryRangeTest.java @@ -0,0 +1,132 @@ +package com.loomcom.lm6502; + +import junit.framework.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +public class MemoryRangeTest extends TestCase { + + public MemoryRangeTest(String testName) { + super(testName); + } + + public static Test suite() { + return new TestSuite(MemoryRangeTest.class); + } + + public void testConstructorThrowsWhenInvalid() { + // All of these should raise MemoryRangeException + doInvalidMemoryRangeTest(0, -1); + doInvalidMemoryRangeTest(-1, 0); + doInvalidMemoryRangeTest(0, 0); + doInvalidMemoryRangeTest(1, 1); + doInvalidMemoryRangeTest(1, 0); + doInvalidMemoryRangeTest(2, 1); + } + + public void testConstructorShouldNotThrow() throws MemoryRangeException { + // None of these should throw. + new MemoryRange(0, 1); + new MemoryRange(0, 2); + new MemoryRange(1, 2); + new MemoryRange(2, 10); + } + + public void testGetStartAddress() throws MemoryRangeException { + MemoryRange r = new MemoryRange(0x101, 0x202); + assertEquals(0x101, r.getStartAddress()); + } + + public void testGetEndAddress() throws MemoryRangeException { + MemoryRange r = new MemoryRange(0x101, 0x202); + assertEquals(0x202, r.getEndAddress()); + } + + public void testOverlapsWith() throws MemoryRangeException { + MemoryRange a = new MemoryRange(0x0000, 0x0fff); + MemoryRange b = new MemoryRange(0x2000, 0x2fff); + MemoryRange c = new MemoryRange(0x1000, 0x1fff); // fits between a and b + MemoryRange d = new MemoryRange(0x0f00, 0x10ff); // overlaps with a + MemoryRange e = new MemoryRange(0x1fff, 0x20ff); // overlaps with b + MemoryRange f = new MemoryRange(0x0fff, 0x2000); // overlaps a and b + MemoryRange g = new MemoryRange(0x00ff, 0x0100); // Overlaps (inside) a, below b + MemoryRange h = new MemoryRange(0x20ff, 0x2100); // Overlaps (inside) b, above a + + assertFalse(c.overlapsWith(a)); + assertFalse(c.overlapsWith(b)); + assertFalse(a.overlapsWith(c)); + assertFalse(b.overlapsWith(c)); + assertFalse(a.overlapsWith(b)); + assertFalse(b.overlapsWith(a)); + + assertTrue(d.overlapsWith(a)); + assertTrue(a.overlapsWith(d)); + + assertTrue(e.overlapsWith(b)); + assertTrue(b.overlapsWith(e)); + + assertTrue(f.overlapsWith(a)); + assertTrue(a.overlapsWith(f)); + assertTrue(f.overlapsWith(b)); + assertTrue(b.overlapsWith(f)); + + assertTrue(a.overlapsWith(g)); + assertTrue(g.overlapsWith(a)); + assertFalse(b.overlapsWith(g)); + assertFalse(g.overlapsWith(b)); + + assertFalse(a.overlapsWith(h)); + assertFalse(h.overlapsWith(a)); + assertTrue(b.overlapsWith(h)); + assertTrue(h.overlapsWith(b)); + } + + public void testIncluded() throws MemoryRangeException { + MemoryRange a = new MemoryRange(0x0100, 0x0fff); + + assertFalse(a.includes(0x0000)); + assertFalse(a.includes(0x00ff)); + assertTrue(a.includes(0x0100)); + assertTrue(a.includes(0x0fff)); + assertFalse(a.includes(0x1000)); + assertFalse(a.includes(0xffff)); + } + + public void testCompareTo() throws MemoryRangeException { + MemoryRange a = new MemoryRange(0x0000, 0x0100); + MemoryRange b = new MemoryRange(0x0200, 0x0300); + MemoryRange c = new MemoryRange(0x0200, 0x0300); + // a < b + assertTrue(a.compareTo(b) == -1); + // b > a + assertTrue(b.compareTo(a) == 1); + // Identity + assertTrue(a.compareTo(a) == 0); + assertTrue(b.compareTo(b) == 0); + // Equality + assertTrue(b.compareTo(c) == 0); + assertTrue(c.compareTo(b) == 0); + // Null + try { + a.compareTo(null); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException ex) { + // expected + } + } + + // Helper method. + public void doInvalidMemoryRangeTest(int start, int end) { + try { + new MemoryRange(start, end); + fail("MemoryRangeException should have been thrown."); + } catch (MemoryRangeException e) { + // Expected. + } + } + +} \ No newline at end of file