Simulator is just about ready for real-world testing now. Added a simulated
MOS6551 ACIA at address $C000 which does buffered input and output via the console. Updated the README with a bit more documentation, and bumped the version number to 0.1 because I'm impatient.
This commit is contained in:
1 parent
596d05e398
commit
00ab8cd9ff
10 files changed
+749
-124
No files matched your search
@@ -50,7 +50,6 @@ public class Cpu implements InstructionTable {
|
||||
private int irAddressMode; // Bits 3-5 of IR: [ | | |X|X|X| | ]
|
||||
private int irOpMode; // Bits 6-7 of IR: [ | | | | | |X|X]
|
||||
private int effectiveAddress;
|
||||
private int effectiveData;
|
||||
|
||||
/* Internal scratch space */
|
||||
private int lo = 0, hi = 0; // Used in address calculation
|
||||
@@ -149,24 +148,20 @@ public class Cpu implements InstructionTable {
|
||||
// Get the data from the effective address (if any)
|
||||
|
||||
effectiveAddress = 0;
|
||||
effectiveData = 0;
|
||||
|
||||
switch(irOpMode) {
|
||||
case 0:
|
||||
case 2:
|
||||
switch(irAddressMode) {
|
||||
case 0: // #Immediate
|
||||
effectiveData = args[0];
|
||||
break;
|
||||
case 1: // Zero Page
|
||||
effectiveAddress = args[0];
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
case 2: // Accumulator - ignored
|
||||
break;
|
||||
case 3: // Absolute
|
||||
effectiveAddress = address(args[0], args[1]);
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
case 5: // Zero Page,X / Zero Page,Y
|
||||
if (ir == 0x96 || ir == 0xb6) {
|
||||
@@ -174,7 +169,6 @@ public class Cpu implements InstructionTable {
|
||||
} else {
|
||||
effectiveAddress = zpxAddress(args[0]);
|
||||
}
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
case 7: // Absolute,X / Absolute,Y
|
||||
if (ir == 0xbe) {
|
||||
@@ -182,7 +176,6 @@ public class Cpu implements InstructionTable {
|
||||
} else {
|
||||
effectiveAddress = xAddress(args[0], args[1]);
|
||||
}
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -191,37 +184,29 @@ public class Cpu implements InstructionTable {
|
||||
case 0: // (Zero Page,X)
|
||||
tmp = args[0] + getXRegister();
|
||||
effectiveAddress = address(bus.read(tmp), bus.read(tmp + 1));
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
case 1: // Zero Page
|
||||
effectiveAddress = args[0];
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
case 2: // #Immediate
|
||||
effectiveAddress = -1;
|
||||
effectiveData = args[0];
|
||||
break;
|
||||
case 3: // Absolute
|
||||
effectiveAddress = address(args[0], args[1]);
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
case 4: // (Zero Page),Y
|
||||
tmp = address(bus.read(args[0]),
|
||||
bus.read((args[0]+1)&0xff));
|
||||
effectiveAddress = (tmp + getYRegister())&0xffff;
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
case 5: // Zero Page,X
|
||||
effectiveAddress = zpxAddress(args[0]);
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
case 6: // Absolute, Y
|
||||
effectiveAddress = yAddress(args[0], args[1]);
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
case 7: // Absolute, X
|
||||
effectiveAddress = xAddress(args[0], args[1]);
|
||||
effectiveData = bus.read(effectiveAddress);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -402,15 +387,18 @@ public class Cpu implements InstructionTable {
|
||||
|
||||
|
||||
/** ORA - Logical Inclusive Or ******************************************/
|
||||
case 0x09: // #Immediate
|
||||
a |= args[0];
|
||||
setArithmeticFlags(a);
|
||||
break;
|
||||
case 0x01: // (Zero Page,X)
|
||||
case 0x05: // Zero Page
|
||||
case 0x09: // #Immediate
|
||||
case 0x0d: // Absolute
|
||||
case 0x11: // (Zero Page),Y
|
||||
case 0x15: // Zero Page,X
|
||||
case 0x19: // Absolute,Y
|
||||
case 0x1d: // Absolute,X
|
||||
a |= effectiveData;
|
||||
a |= bus.read(effectiveAddress);
|
||||
setArithmeticFlags(a);
|
||||
break;
|
||||
|
||||
@@ -424,7 +412,7 @@ public class Cpu implements InstructionTable {
|
||||
case 0x0e: // Absolute
|
||||
case 0x16: // Zero Page,X
|
||||
case 0x1e: // Absolute,X
|
||||
tmp = asl(effectiveData);
|
||||
tmp = asl(bus.read(effectiveAddress));
|
||||
bus.write(effectiveAddress, tmp);
|
||||
setArithmeticFlags(tmp);
|
||||
break;
|
||||
@@ -433,7 +421,7 @@ public class Cpu implements InstructionTable {
|
||||
/** BIT - Bit Test ******************************************************/
|
||||
case 0x24: // Zero Page
|
||||
case 0x2c: // Absolute
|
||||
tmp = a & effectiveData;
|
||||
tmp = a & bus.read(effectiveAddress);
|
||||
setZeroFlag(tmp == 0);
|
||||
setNegativeFlag((tmp & 0x80) != 0);
|
||||
setOverflowFlag((tmp & 0x40) != 0);
|
||||
@@ -441,15 +429,18 @@ public class Cpu implements InstructionTable {
|
||||
|
||||
|
||||
/** AND - Logical AND ***************************************************/
|
||||
case 0x29: // #Immediate
|
||||
a &= args[0];
|
||||
setArithmeticFlags(a);
|
||||
break;
|
||||
case 0x21: // (Zero Page,X)
|
||||
case 0x25: // Zero Page
|
||||
case 0x29: // #Immediate
|
||||
case 0x2d: // Absolute
|
||||
case 0x31: // (Zero Page),Y
|
||||
case 0x35: // Zero Page,X
|
||||
case 0x39: // Absolute,Y
|
||||
case 0x3d: // Absolute,X
|
||||
a &= effectiveData;
|
||||
a &= bus.read(effectiveAddress);
|
||||
setArithmeticFlags(a);
|
||||
break;
|
||||
|
||||
@@ -463,22 +454,25 @@ public class Cpu implements InstructionTable {
|
||||
case 0x2e: // Absolute
|
||||
case 0x36: // Zero Page,X
|
||||
case 0x3e: // Absolute,X
|
||||
tmp = rol(effectiveData);
|
||||
tmp = rol(bus.read(effectiveAddress));
|
||||
bus.write(effectiveAddress, tmp);
|
||||
setArithmeticFlags(tmp);
|
||||
break;
|
||||
|
||||
|
||||
/** EOR - Exclusive OR **************************************************/
|
||||
case 0x49: // #Immediate
|
||||
a ^= args[0];
|
||||
setArithmeticFlags(a);
|
||||
break;
|
||||
case 0x41: // (Zero Page,X)
|
||||
case 0x45: // Zero Page
|
||||
case 0x49: // Immediate
|
||||
case 0x4d: // Absolute
|
||||
case 0x51: // (Zero Page,Y)
|
||||
case 0x55: // Zero Page,X
|
||||
case 0x59: // Absolute,Y
|
||||
case 0x5d: // Absolute,X
|
||||
a ^= effectiveData;
|
||||
a ^= bus.read(effectiveAddress);
|
||||
setArithmeticFlags(a);
|
||||
break;
|
||||
|
||||
@@ -492,25 +486,31 @@ public class Cpu implements InstructionTable {
|
||||
case 0x4e: // Absolute
|
||||
case 0x56: // Zero Page,X
|
||||
case 0x5e: // Absolute,X
|
||||
tmp = lsr(effectiveData);
|
||||
tmp = lsr(bus.read(effectiveAddress));
|
||||
bus.write(effectiveAddress, tmp);
|
||||
setArithmeticFlags(tmp);
|
||||
break;
|
||||
|
||||
|
||||
/** ADC - Add with Carry ************************************************/
|
||||
case 0x69: // #Immediate
|
||||
if (decimalModeFlag) {
|
||||
a = adcDecimal(a, args[0]);
|
||||
} else {
|
||||
a = adc(a, args[0]);
|
||||
}
|
||||
break;
|
||||
case 0x61: // (Zero Page,X)
|
||||
case 0x65: // Zero Page
|
||||
case 0x69: // Immediate
|
||||
case 0x6d: // Absolute
|
||||
case 0x71: // (Zero Page),Y
|
||||
case 0x75: // Zero Page,X
|
||||
case 0x79: // Absolute,Y
|
||||
case 0x7d: // Absolute,X
|
||||
if (decimalModeFlag) {
|
||||
a = adcDecimal(a, effectiveData);
|
||||
a = adcDecimal(a, bus.read(effectiveAddress));
|
||||
} else {
|
||||
a = adc(a, effectiveData);
|
||||
a = adc(a, bus.read(effectiveAddress));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -524,7 +524,7 @@ public class Cpu implements InstructionTable {
|
||||
case 0x6e: // Absolute
|
||||
case 0x76: // Zero Page,X
|
||||
case 0x7e: // Absolute,X
|
||||
tmp = ror(effectiveData);
|
||||
tmp = ror(bus.read(effectiveAddress));
|
||||
bus.write(effectiveAddress, tmp);
|
||||
setArithmeticFlags(tmp);
|
||||
break;
|
||||
@@ -562,59 +562,72 @@ public class Cpu implements InstructionTable {
|
||||
|
||||
|
||||
/** LDY - Load Y Register ***********************************************/
|
||||
case 0xa0: // Immediate
|
||||
case 0xa0: // #Immediate
|
||||
y = args[0];
|
||||
setArithmeticFlags(y);
|
||||
break;
|
||||
case 0xa4: // Zero Page
|
||||
case 0xac: // Absolute
|
||||
case 0xb4: // Zero Page,X
|
||||
case 0xbc: // Absolute,X
|
||||
y = effectiveData;
|
||||
y = bus.read(effectiveAddress);
|
||||
setArithmeticFlags(y);
|
||||
break;
|
||||
|
||||
|
||||
/** LDX - Load X Register ***********************************************/
|
||||
case 0xa2: // Immediate
|
||||
case 0xa2: // #Immediate
|
||||
x = args[0];
|
||||
setArithmeticFlags(x);
|
||||
break;
|
||||
case 0xa6: // Zero Page
|
||||
case 0xae: // Absolute
|
||||
case 0xb6: // Zero Page,Y
|
||||
case 0xbe: // Absolute,Y
|
||||
x = effectiveData;
|
||||
x = bus.read(effectiveAddress);
|
||||
setArithmeticFlags(x);
|
||||
break;
|
||||
|
||||
|
||||
/** LDA - Load Accumulator **********************************************/
|
||||
case 0xa9: // #Immediate
|
||||
a = args[0];
|
||||
setArithmeticFlags(a);
|
||||
break;
|
||||
case 0xa1: // (Zero Page,X)
|
||||
case 0xa5: // Zero Page
|
||||
case 0xa9: // Immediate
|
||||
case 0xad: // Absolute
|
||||
case 0xb1: // (Zero Page),Y
|
||||
case 0xb5: // Zero Page,X
|
||||
case 0xb9: // Absolute,Y
|
||||
case 0xbd: // Absolute,X
|
||||
a = effectiveData;
|
||||
a = bus.read(effectiveAddress);
|
||||
setArithmeticFlags(a);
|
||||
break;
|
||||
|
||||
|
||||
/** CPY - Compare Y Register ********************************************/
|
||||
case 0xc0: // Immediate
|
||||
case 0xc0: // #Immediate
|
||||
cmp(y, args[0]);
|
||||
break;
|
||||
case 0xc4: // Zero Page
|
||||
case 0xcc: // Absolute
|
||||
cmp(y, effectiveData);
|
||||
cmp(y, bus.read(effectiveAddress));
|
||||
break;
|
||||
|
||||
|
||||
/** CMP - Compare Accumulator *******************************************/
|
||||
case 0xc9: // #Immediate
|
||||
cmp(a, args[0]);
|
||||
break;
|
||||
case 0xc1: // (Zero Page,X)
|
||||
case 0xc5: // Zero Page
|
||||
case 0xc9: // #Immediate
|
||||
case 0xcd: // Absolute
|
||||
case 0xd1: // (Zero Page),Y
|
||||
case 0xd5: // Zero Page,X
|
||||
case 0xd9: // Absolute,Y
|
||||
case 0xdd: // Absolute,X
|
||||
cmp(a, effectiveData);
|
||||
cmp(a, bus.read(effectiveAddress));
|
||||
break;
|
||||
|
||||
|
||||
@@ -623,33 +636,41 @@ public class Cpu implements InstructionTable {
|
||||
case 0xce: // Absolute
|
||||
case 0xd6: // Zero Page,X
|
||||
case 0xde: // Absolute,X
|
||||
tmp = --effectiveData & 0xff;
|
||||
tmp = (bus.read(effectiveAddress) - 1) & 0xff;
|
||||
bus.write(effectiveAddress, tmp);
|
||||
setArithmeticFlags(tmp);
|
||||
break;
|
||||
|
||||
|
||||
/** CPX - Compare X Register ********************************************/
|
||||
case 0xe0: // Immediate
|
||||
case 0xe0: // #Immediate
|
||||
cmp(x, args[0]);
|
||||
break;
|
||||
case 0xe4: // Zero Page
|
||||
case 0xec: // Absolute
|
||||
cmp(x, effectiveData);
|
||||
cmp(x, bus.read(effectiveAddress));
|
||||
break;
|
||||
|
||||
|
||||
/** SBC - Subtract with Carry (Borrow) **********************************/
|
||||
case 0xe9: // #Immediate
|
||||
if (decimalModeFlag) {
|
||||
a = sbcDecimal(a, args[0]);
|
||||
} else {
|
||||
a = sbc(a, args[0]);
|
||||
}
|
||||
break;
|
||||
case 0xe1: // (Zero Page,X)
|
||||
case 0xe5: // Zero Page
|
||||
case 0xe9: // Immediate
|
||||
case 0xed: // Absolute
|
||||
case 0xf1: // (Zero Page),Y
|
||||
case 0xf5: // Zero Page,X
|
||||
case 0xf9: // Absolute,Y
|
||||
case 0xfd: // Absolute,X
|
||||
if (decimalModeFlag) {
|
||||
a = sbcDecimal(a, effectiveData);
|
||||
a = sbcDecimal(a, bus.read(effectiveAddress));
|
||||
} else {
|
||||
a = sbc(a, effectiveData);
|
||||
a = sbc(a, bus.read(effectiveAddress));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -659,7 +680,7 @@ public class Cpu implements InstructionTable {
|
||||
case 0xee: // Absolute
|
||||
case 0xf6: // Zero Page,X
|
||||
case 0xfe: // Absolute,X
|
||||
tmp = ++effectiveData & 0xff;
|
||||
tmp = (bus.read(effectiveAddress) + 1) & 0xff;
|
||||
bus.write(effectiveAddress, tmp);
|
||||
setArithmeticFlags(tmp);
|
||||
break;
|
||||
|
||||
@@ -21,24 +21,50 @@ public class Simulator {
|
||||
* correct IO devices.
|
||||
*/
|
||||
private Bus bus;
|
||||
|
||||
|
||||
/**
|
||||
* The ACIA, used for charater in/out.
|
||||
*
|
||||
* By default, the simulator uses base address c000 for the ACIA.
|
||||
*/
|
||||
private Acia acia;
|
||||
|
||||
private BufferedReader in;
|
||||
private BufferedWriter out;
|
||||
|
||||
|
||||
/* If true, trace execution of the CPU */
|
||||
private boolean trace = false;
|
||||
private int nextExamineAddress = 0;
|
||||
|
||||
private static final int BUS_BOTTOM = 0x0000;
|
||||
private static final int BUS_TOP = 0xffff;
|
||||
|
||||
private static final int ACIA_BASE = 0xc000;
|
||||
|
||||
private static final int MEMORY_BASE = 0x0000;
|
||||
private static final int MEMORY_SIZE = 0xc000; // 48 KB
|
||||
|
||||
private static final int ROM_BASE = 0xe000;
|
||||
private static final int ROM_SIZE = 0x2000; // 8 KB
|
||||
|
||||
public Simulator() throws MemoryRangeException {
|
||||
cpu = new Cpu();
|
||||
bus = new Bus(0x0000, 0xffff);
|
||||
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
||||
this.cpu = new Cpu();
|
||||
this.acia = new Acia(ACIA_BASE);
|
||||
|
||||
bus.addCpu(cpu);
|
||||
bus.addDevice(new Memory(0x0000, 0x10000));
|
||||
bus.addDevice(new Memory(MEMORY_BASE, MEMORY_SIZE, false));
|
||||
bus.addDevice(acia);
|
||||
// TODO: This should be read-only memory. Add a method
|
||||
// to allow one-time initialization of ROM with a loaded
|
||||
// ROM binary file.
|
||||
bus.addDevice(new Memory(ROM_BASE, ROM_SIZE, false));
|
||||
|
||||
this.in = new BufferedReader(new InputStreamReader(System.in));
|
||||
this.out = new BufferedWriter(new OutputStreamWriter(System.out));
|
||||
}
|
||||
|
||||
public void run() throws MemoryAccessException {
|
||||
public void run() throws MemoryAccessException, FifoUnderrunException {
|
||||
try {
|
||||
greeting();
|
||||
prompt();
|
||||
@@ -57,12 +83,14 @@ public class Simulator {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dispatch the command.
|
||||
*/
|
||||
public void dispatch(String commandLine)
|
||||
throws MemoryAccessException, IOException, CommandFormatException {
|
||||
public void dispatch(String commandLine) throws MemoryAccessException,
|
||||
IOException,
|
||||
CommandFormatException,
|
||||
FifoUnderrunException {
|
||||
Command c = new Command(commandLine);
|
||||
String cmd = c.getCommand();
|
||||
if (cmd != null) {
|
||||
@@ -72,7 +100,7 @@ public class Simulator {
|
||||
doGetState();
|
||||
} else if (cmd.startsWith("se")) {
|
||||
doSet(c);
|
||||
} else if (cmd.startsWith("r")) {
|
||||
} else if (cmd.startsWith("r")) {
|
||||
doReset();
|
||||
} else if (cmd.startsWith("e")) {
|
||||
doExamine(c);
|
||||
@@ -93,7 +121,7 @@ public class Simulator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void doHelp(Command c) throws IOException {
|
||||
writeLine("Symon 6502 Simulator");
|
||||
writeLine("");
|
||||
@@ -102,16 +130,16 @@ public class Simulator {
|
||||
writeLine("Note that 'go' clears the BREAK processor status flag.");
|
||||
writeLine("");
|
||||
writeLine("h Show this help file.");
|
||||
writeLine("g [address] [steps] Start running at address, or at PC.");
|
||||
writeLine("e [start] [end] Examine memory at PC, start, or start-end.");
|
||||
writeLine("d <address> <data> Deposit data into address.");
|
||||
writeLine("f <start> <end> <data> Fill memory with data.");
|
||||
writeLine("reset Reset simulator.");
|
||||
writeLine("set {pc,a,x,y} [data] Set register to data value.");
|
||||
writeLine("stat Show CPU state.");
|
||||
writeLine("step [address] Step once, optionally starting at address.");
|
||||
writeLine("trace Toggle trace.");
|
||||
writeLine("load <file> <address> Load binary file at address.");
|
||||
writeLine("g [address] [steps] Start running at address, or at PC.");
|
||||
writeLine("step [address] Step once, optionally starting at address.");
|
||||
writeLine("stat Show CPU state.");
|
||||
writeLine("reset Reset simulator.");
|
||||
writeLine("trace Toggle trace.");
|
||||
writeLine("q (or Control-D) Quit.\n");
|
||||
}
|
||||
|
||||
@@ -119,25 +147,26 @@ public class Simulator {
|
||||
writeLine(cpu.toString());
|
||||
writeLine("Trace is " + (trace ? "on" : "off"));
|
||||
}
|
||||
|
||||
public void doLoad(Command c) throws IOException, MemoryAccessException,
|
||||
CommandFormatException {
|
||||
|
||||
public void doLoad(Command c) throws IOException,
|
||||
MemoryAccessException,
|
||||
CommandFormatException {
|
||||
if (c.numArgs() != 2) {
|
||||
throw new CommandFormatException("load <file> <address>");
|
||||
}
|
||||
|
||||
|
||||
File binFile = new File(c.getArg(0));
|
||||
int address = stringToWord(c.getArg(1));
|
||||
|
||||
|
||||
if (!binFile.exists()) {
|
||||
throw new CommandFormatException("File '" + binFile +
|
||||
"' does not exist.");
|
||||
}
|
||||
writeLine("Loading file '" + binFile + "' at address " +
|
||||
String.format("%04x", address) + "...");
|
||||
|
||||
|
||||
int bytesLoaded = 0;
|
||||
|
||||
|
||||
FileInputStream fis = new FileInputStream(binFile);
|
||||
|
||||
try {
|
||||
@@ -149,13 +178,13 @@ public class Simulator {
|
||||
} finally {
|
||||
fis.close();
|
||||
}
|
||||
|
||||
|
||||
writeLine("Loaded " + bytesLoaded + " (" +
|
||||
String.format("$%04x", bytesLoaded) + ") bytes");
|
||||
}
|
||||
|
||||
public void doSet(Command c) throws MemoryAccessException,
|
||||
CommandFormatException {
|
||||
|
||||
public void doSet(Command c) throws MemoryAccessException,
|
||||
CommandFormatException {
|
||||
if (c.numArgs() != 2) {
|
||||
throw new CommandFormatException("set {a, x, y, pc} <value>");
|
||||
}
|
||||
@@ -177,9 +206,10 @@ public class Simulator {
|
||||
throw new CommandFormatException("Illegal address");
|
||||
}
|
||||
}
|
||||
|
||||
public void doExamine(Command c) throws IOException, MemoryAccessException,
|
||||
CommandFormatException {
|
||||
|
||||
public void doExamine(Command c) throws IOException,
|
||||
MemoryAccessException,
|
||||
CommandFormatException {
|
||||
try {
|
||||
if (c.numArgs() == 2) {
|
||||
int startAddress = stringToWord(c.getArgs()[0]);
|
||||
@@ -206,15 +236,15 @@ public class Simulator {
|
||||
bus.read(nextExamineAddress)));
|
||||
nextExamineAddress++;
|
||||
} else {
|
||||
throw new CommandFormatException("e [start [end]]");
|
||||
throw new CommandFormatException("e [start [end]]");
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new CommandFormatException("Illegal Address");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void doDeposit(Command c) throws MemoryAccessException,
|
||||
CommandFormatException {
|
||||
CommandFormatException {
|
||||
if (c.numArgs() != 2) {
|
||||
throw new CommandFormatException("d [address] [data]");
|
||||
}
|
||||
@@ -226,9 +256,9 @@ public class Simulator {
|
||||
throw new CommandFormatException("Illegal Address");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void doFill(Command c) throws MemoryAccessException,
|
||||
CommandFormatException {
|
||||
CommandFormatException {
|
||||
if (c.numArgs() != 3) {
|
||||
throw new CommandFormatException("f [start] [end] [data]");
|
||||
}
|
||||
@@ -244,9 +274,11 @@ public class Simulator {
|
||||
throw new CommandFormatException("Illegal Address");
|
||||
}
|
||||
}
|
||||
|
||||
public void doStep(Command c) throws IOException, MemoryAccessException,
|
||||
CommandFormatException {
|
||||
|
||||
public void doStep(Command c) throws IOException,
|
||||
MemoryAccessException,
|
||||
FifoUnderrunException,
|
||||
CommandFormatException {
|
||||
try {
|
||||
if (c.numArgs() > 0) {
|
||||
cpu.setProgramCounter(stringToWord(c.getArg(1)));
|
||||
@@ -257,16 +289,21 @@ public class Simulator {
|
||||
throw new CommandFormatException("Illegal Address");
|
||||
}
|
||||
}
|
||||
|
||||
public void doGo(Command c) throws IOException, MemoryAccessException,
|
||||
CommandFormatException {
|
||||
|
||||
public void doGo(Command c) throws IOException,
|
||||
MemoryAccessException,
|
||||
FifoUnderrunException,
|
||||
CommandFormatException {
|
||||
int readChar;
|
||||
int stepCount = 0;
|
||||
|
||||
if (c.numArgs() > 2) {
|
||||
throw new CommandFormatException("g [address] [steps]");
|
||||
}
|
||||
try {
|
||||
int start = 0;
|
||||
int steps = -1;
|
||||
|
||||
|
||||
if (c.numArgs() > 0) {
|
||||
start = stringToWord(c.getArg(0));
|
||||
} else {
|
||||
@@ -275,18 +312,56 @@ public class Simulator {
|
||||
if (c.numArgs() == 2) {
|
||||
steps = stringToWord(c.getArg(1));
|
||||
}
|
||||
|
||||
|
||||
// Make a gross assumption: Restarting the CPU clears
|
||||
// the break flag and the IRQ disable flag.
|
||||
cpu.clearBreakFlag();
|
||||
cpu.clearIrqDisableFlag();
|
||||
|
||||
|
||||
cpu.setProgramCounter(start);
|
||||
outer:
|
||||
while (!cpu.getBreakFlag() && (steps == -1 || steps-- > 0)) {
|
||||
cpu.step();
|
||||
if (trace) {
|
||||
writeLine(cpu.toString());
|
||||
}
|
||||
// Wake up and scan keyboard every 500 steps
|
||||
if (stepCount++ >= 500) {
|
||||
// Reset step count
|
||||
stepCount = 0;
|
||||
|
||||
//
|
||||
// Do output if available.
|
||||
//
|
||||
while (acia.hasTxChar()) {
|
||||
out.write(acia.txRead());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
//
|
||||
// Consume input if available.
|
||||
//
|
||||
// NOTE: On UNIX systems, System.in.available() returns 0
|
||||
// until Enter is pressed. So to interrupt we must ALWAYS
|
||||
// type "^E<enter>". Sucks hard. But such is life.
|
||||
if (System.in.available() > 0) {
|
||||
while ((readChar = in.read()) > -1) {
|
||||
// Keep consuming unless ^E is found.
|
||||
//
|
||||
// TODO: This will probably lead to a lot of spurious keyboard
|
||||
// entry. Gotta keep an eye on that.
|
||||
//
|
||||
if (readChar == 0x05) {
|
||||
break outer;
|
||||
} else {
|
||||
// Buffer keyboard input into the simulated ACIA's
|
||||
// read buffer.
|
||||
acia.rxWrite(readChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (!trace) {
|
||||
writeLine(cpu.toString());
|
||||
@@ -295,12 +370,12 @@ public class Simulator {
|
||||
throw new CommandFormatException("Illegal Address");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void doToggleTrace() throws IOException {
|
||||
this.trace = !trace;
|
||||
writeLine("Trace is now " + (trace ? "on" : "off"));
|
||||
}
|
||||
|
||||
|
||||
public void doReset() throws MemoryAccessException {
|
||||
cpu.reset();
|
||||
this.trace = false;
|
||||
@@ -309,14 +384,15 @@ public class Simulator {
|
||||
/**
|
||||
* Main simulator routine.
|
||||
*/
|
||||
public static void main(String[] args) throws MemoryAccessException {
|
||||
public static void main(String[] args) throws MemoryAccessException,
|
||||
FifoUnderrunException {
|
||||
try {
|
||||
new Simulator().run();
|
||||
} catch (MemoryRangeException ex) {
|
||||
System.err.println("Error: " + ex.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
* Private
|
||||
*******************************************************************/
|
||||
@@ -328,15 +404,15 @@ public class Simulator {
|
||||
bus.write(address + i++, d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int stringToWord(String addrString) {
|
||||
return Integer.parseInt(addrString, 16) & 0xffff;
|
||||
}
|
||||
|
||||
|
||||
private int stringToByte(String dataString) {
|
||||
return Integer.parseInt(dataString, 16) & 0xff;
|
||||
}
|
||||
|
||||
|
||||
private void greeting() throws IOException {
|
||||
writeLine("Welcome to the Symon 6502 Simulator. Type 'h' for help.");
|
||||
}
|
||||
@@ -364,7 +440,7 @@ public class Simulator {
|
||||
private boolean shouldQuit(String line) {
|
||||
return (line == null || "q".equals(line.toLowerCase()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Command line tokenizer class. Given a command line, tokenize
|
||||
* it and give easy access to the command and its arguments.
|
||||
@@ -386,15 +462,15 @@ public class Simulator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
|
||||
public String[] getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
|
||||
public String getArg(int argNum) {
|
||||
if (argNum > args.length - 1) {
|
||||
return null;
|
||||
@@ -402,13 +478,13 @@ public class Simulator {
|
||||
return args[argNum];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int numArgs() {
|
||||
return args.length;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasArgs() {
|
||||
return args.length > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.loomcom.symon.devices;
|
||||
|
||||
import com.loomcom.symon.exceptions.*;
|
||||
import com.loomcom.symon.util.*;
|
||||
|
||||
/**
|
||||
* 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. However, in the simulation we maintain two
|
||||
* small buffers of 256 characters, since we only wake up to check for
|
||||
* keyboard input and do output every 500 instructions.
|
||||
*/
|
||||
public class Acia extends Device {
|
||||
|
||||
public static final int ACIA_SIZE = 4;
|
||||
public static final int BUF_LEN = 256;
|
||||
|
||||
static final int DATA_REG = 0;
|
||||
static final int STAT_REG = 1;
|
||||
static final int CMND_REG = 2;
|
||||
static final int CTRL_REG = 3;
|
||||
|
||||
/** Register addresses */
|
||||
private int baseAddress;
|
||||
|
||||
/** Registers. These are ignored in the current implementation. */
|
||||
private int commandRegister;
|
||||
private int controlRegister;
|
||||
|
||||
/** Read/Write buffers */
|
||||
private FifoRingBuffer rxBuffer = new FifoRingBuffer(BUF_LEN);
|
||||
private FifoRingBuffer txBuffer = new FifoRingBuffer(BUF_LEN);
|
||||
|
||||
public Acia(int address) throws MemoryRangeException {
|
||||
super(address, ACIA_SIZE, "ACIA");
|
||||
this.baseAddress = address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(int address) throws MemoryAccessException {
|
||||
switch (address) {
|
||||
case DATA_REG:
|
||||
try {
|
||||
return rxRead();
|
||||
} catch (FifoUnderrunException ex) {
|
||||
throw new MemoryAccessException("Buffer underrun");
|
||||
}
|
||||
case STAT_REG:
|
||||
return ((rxBuffer.isEmpty() ? 0x00 : 0x08) |
|
||||
(txBuffer.isEmpty() ? 0x10 : 0x00));
|
||||
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:
|
||||
commandRegister = data;
|
||||
break;
|
||||
case 3:
|
||||
controlRegister = data;
|
||||
break;
|
||||
default:
|
||||
throw new MemoryAccessException("No register.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ACIA@" + String.format("%04X", baseAddress);
|
||||
}
|
||||
|
||||
public int rxRead() throws FifoUnderrunException {
|
||||
return rxBuffer.pop();
|
||||
}
|
||||
|
||||
public void rxWrite(int data) {
|
||||
rxBuffer.push(data);
|
||||
}
|
||||
|
||||
public int txRead() throws FifoUnderrunException {
|
||||
return txBuffer.pop();
|
||||
}
|
||||
|
||||
public void txWrite(int data) {
|
||||
txBuffer.push(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if there is character data in the TX register.
|
||||
*/
|
||||
public boolean hasTxChar() {
|
||||
return !txBuffer.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if there is character data in the RX register.
|
||||
*/
|
||||
public boolean hasRxChar() {
|
||||
return !rxBuffer.isEmpty();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
txBuffer.reset();
|
||||
rxBuffer.reset();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,17 +9,18 @@ public class Memory extends Device {
|
||||
private boolean readOnly;
|
||||
private int[] mem;
|
||||
|
||||
/* Initialize all locations to 0x00 (BRK) */
|
||||
private static final int DEFAULT_FILL = 0x00;
|
||||
|
||||
public Memory(int address, int size, boolean readOnly)
|
||||
throws MemoryRangeException {
|
||||
super(address, size, "RW Memory");
|
||||
throws MemoryRangeException {
|
||||
super(address, size, (readOnly ? "RO Memory" : "RW Memory"));
|
||||
this.readOnly = readOnly;
|
||||
this.mem = new int[size];
|
||||
// Initialize all locations to 0x00 (BRK)
|
||||
Arrays.fill(this.mem, 0x00);
|
||||
Arrays.fill(this.mem, DEFAULT_FILL);
|
||||
}
|
||||
|
||||
public Memory(int address, int size)
|
||||
throws MemoryRangeException {
|
||||
public Memory(int address, int size) throws MemoryRangeException {
|
||||
this(address, size, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.loomcom.symon.exceptions;
|
||||
|
||||
public class FifoUnderrunException extends Exception {
|
||||
public FifoUnderrunException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.loomcom.symon.util;
|
||||
|
||||
import com.loomcom.symon.exceptions.*;
|
||||
|
||||
/**
|
||||
* A very simple and efficient FIFO ring buffer implementation backed
|
||||
* by an array. It can only hold only integers.
|
||||
*/
|
||||
public class FifoRingBuffer {
|
||||
|
||||
private int[] fifoBuffer;
|
||||
private int readPtr = 0;
|
||||
private int writePtr = 0;
|
||||
private int size = 0;
|
||||
|
||||
public FifoRingBuffer(int size) {
|
||||
if (size <= 0) {
|
||||
throw new RuntimeException("Cannot create a FifoRingBuffer with size <= 0.");
|
||||
}
|
||||
this.size = size;
|
||||
fifoBuffer = new int[size];
|
||||
}
|
||||
|
||||
public int peek() throws FifoUnderrunException {
|
||||
if (isEmpty()) {
|
||||
throw new FifoUnderrunException("Buffer Underrun");
|
||||
}
|
||||
return fifoBuffer[readPtr];
|
||||
}
|
||||
|
||||
public int pop() throws FifoUnderrunException {
|
||||
if (isEmpty()) {
|
||||
throw new FifoUnderrunException("Buffer Underrun");
|
||||
}
|
||||
int val = fifoBuffer[readPtr];
|
||||
incrementReadPointer();
|
||||
return val;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return(readPtr == writePtr);
|
||||
}
|
||||
|
||||
public boolean isFull() {
|
||||
return((readPtr == 0 && writePtr == (size - 1)) ||
|
||||
writePtr == (readPtr - 1));
|
||||
}
|
||||
|
||||
public void push(int val) {
|
||||
fifoBuffer[writePtr] = val;
|
||||
incrementWritePointer();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
readPtr = 0;
|
||||
writePtr = 0;
|
||||
}
|
||||
|
||||
private void incrementWritePointer() {
|
||||
if (++writePtr == size) {
|
||||
writePtr = 0;
|
||||
}
|
||||
if (writePtr == readPtr) {
|
||||
incrementReadPointer();
|
||||
}
|
||||
}
|
||||
|
||||
private void incrementReadPointer() {
|
||||
if (++readPtr == size) {
|
||||
readPtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[FifoRingBuffer: size=" + size + "]";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user