Unit testing. MemoryRange work.
This commit is contained in:
1 parent
541e7c609c
commit
5bbd72d44e
7 files changed
+289
-88
No files matched your search
@@ -1,20 +1,32 @@
|
|||||||
package com.loomcom.lm6502;
|
package com.loomcom.lm6502;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
|
|
||||||
public class AddressDecoder {
|
public class AddressDecoder {
|
||||||
/**
|
|
||||||
* Map of memory ranges to IO devices.
|
|
||||||
*/
|
|
||||||
private Map<MemoryRange, Device> 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<MemoryRange, Device> ioMap;
|
||||||
|
|
||||||
|
public AddressDecoder() {
|
||||||
|
ioMap = new TreeMap();
|
||||||
|
}
|
||||||
|
|
||||||
public void addDevice(Device d)
|
public void addDevice(Device d)
|
||||||
throws MemoryConflictException {
|
throws MemoryRangeException {
|
||||||
// Make sure there's no memory overlap.
|
// 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.
|
// 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.
|
* device.
|
||||||
*/
|
*/
|
||||||
public boolean isComplete() {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the memory map is 'sparse', i.e., there
|
* Returns true if there are any gap in the memory map.
|
||||||
* are gaps between IO devices.
|
|
||||||
*/
|
*/
|
||||||
public boolean isSparse() {
|
public boolean hasGaps() {
|
||||||
return !isComplete();
|
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 {}
|
|
||||||
@@ -4,13 +4,14 @@ import java.io.*;
|
|||||||
|
|
||||||
public class CommandParser {
|
public class CommandParser {
|
||||||
|
|
||||||
private BufferedReader m_in;
|
private BufferedReader in;
|
||||||
private BufferedWriter m_out;
|
private BufferedWriter out;
|
||||||
private Simulator m_simulator;
|
private Simulator simulator;
|
||||||
|
|
||||||
public CommandParser(InputStream in, OutputStream out, Simulator s) {
|
public CommandParser(InputStream i, OutputStream o, Simulator s) {
|
||||||
m_in = new BufferedReader(new InputStreamReader(in));
|
this.in = new BufferedReader(new InputStreamReader(i));
|
||||||
m_out = new BufferedWriter(new OutputStreamWriter(out));
|
this.out = new BufferedWriter(new OutputStreamWriter(o));
|
||||||
|
this.simulator = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -45,20 +46,20 @@ public class CommandParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void prompt() throws IOException {
|
private void prompt() throws IOException {
|
||||||
m_out.write("j6502> ");
|
out.write("j6502> ");
|
||||||
m_out.flush();
|
out.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String readLine() throws IOException {
|
private String readLine() throws IOException {
|
||||||
String line = m_in.readLine();
|
String line = in.readLine();
|
||||||
if (line == null) { return null; }
|
if (line == null) { return null; }
|
||||||
return line.trim();
|
return line.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLine(String line) throws IOException {
|
private void writeLine(String line) throws IOException {
|
||||||
m_out.write(line);
|
out.write(line);
|
||||||
m_out.newLine();
|
out.newLine();
|
||||||
m_out.flush();
|
out.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,33 +5,22 @@ package com.loomcom.lm6502;
|
|||||||
*/
|
*/
|
||||||
public class Cpu {
|
public class Cpu {
|
||||||
|
|
||||||
/**
|
private int pc;
|
||||||
* The Program Counter.
|
private int sp;
|
||||||
*/
|
private Simulator sim;
|
||||||
private int m_pc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The system stack pointer.
|
|
||||||
*/
|
|
||||||
private int m_sp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to the simulator
|
|
||||||
*/
|
|
||||||
private Simulator m_sim;
|
|
||||||
|
|
||||||
public Cpu(Simulator sim) {
|
public Cpu(Simulator sim) {
|
||||||
reset();
|
reset();
|
||||||
this.m_sim = sim;
|
this.sim = sim;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the CPU to known initial values.
|
* Reset the CPU to known initial values.
|
||||||
*/
|
*/
|
||||||
public void reset() {
|
public void reset() {
|
||||||
m_sp = 0x01ff;
|
sp = 0x01ff;
|
||||||
/* locations fffc and fffd hold the reset vector address */
|
/* locations fffc and fffd hold the reset vector address */
|
||||||
m_pc = 0xfffc;
|
pc = 0xfffc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,7 +40,7 @@ public class Cpu {
|
|||||||
* Program Counter.
|
* Program Counter.
|
||||||
*/
|
*/
|
||||||
private int readAddress() {
|
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 <tt>addr</tt>
|
* @return The address specified in the two bytes at location <tt>addr</tt>
|
||||||
*/
|
*/
|
||||||
private int readAddress(int address) {
|
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() {
|
public Simulator getSimulator() {
|
||||||
return m_sim;
|
return sim;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,34 +7,34 @@
|
|||||||
public abstract class Device {
|
public abstract class Device {
|
||||||
|
|
||||||
/** The memory range for this device. */
|
/** The memory range for this device. */
|
||||||
private MemoryRange m_memoryRange;
|
private MemoryRange memoryRange;
|
||||||
|
|
||||||
/** Reference to the CPU, for interrupts. */
|
/** Reference to the CPU, for interrupts. */
|
||||||
private Cpu m_cpu;
|
private Cpu cpu;
|
||||||
|
|
||||||
public Device(MemoryRange range, Cpu cpu) {
|
public Device(MemoryRange range, Cpu cpu) {
|
||||||
m_memoryRange = range;
|
this.memoryRange = range;
|
||||||
m_cpu = cpu;
|
this.cpu = cpu;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemoryRange getMemoryRange() {
|
public MemoryRange getMemoryRange() {
|
||||||
return m_memoryRange;
|
return memoryRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getEndAddress() {
|
public int getEndAddress() {
|
||||||
return m_memoryRange.getEndAddress();
|
return memoryRange.getEndAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getStartAddress() {
|
public int getStartAddress() {
|
||||||
return m_memoryRange.getStartAddress();
|
return memoryRange.getStartAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generateInterrupt() {
|
public void generateInterrupt() {
|
||||||
m_cpu.interrupt();
|
cpu.interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generateNonMaskableInterrupt() {
|
public void generateNonMaskableInterrupt() {
|
||||||
m_cpu.nmiInterrupt();
|
cpu.nmiInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.loomcom.lm6502;
|
||||||
|
|
||||||
|
public class MemoryRange implements Comparable<MemoryRange> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in new issue
Block a user