1
0
mirror of https://github.com/sethm/symon.git synced 2024-06-02 14:41:33 +00:00

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:
Seth J. Morabito 2010-01-20 18:19:39 -08:00
parent 596d05e398
commit 00ab8cd9ff
10 changed files with 749 additions and 124 deletions

221
README
View File

@ -10,8 +10,8 @@ deemed ready for testing, it will be given a version number of "0.1".
====================================================================
Version: PRERELEASE
Last Updated: 10 January, 2010
Version: 0.1
Last Updated: 20 January, 2010
Copyright (c) 2008-2010 Seth J. Morabito <sethm@loomcom.com>
See the file COPYING for license.
@ -29,26 +29,223 @@ The initial goal is to simulate a system with an NMOS 6502 or CMOS
65C02 central processor; one or more 6522 VIAs; and one or more 6551
ACIAs. More functionality may be considered as time goes on.
2.0 Usage
2.0 Requirements
----------------
- Java 1.5 or higher
- Maven 2.0.x or higher (for building from source)
- JUnit 4 or higher (for testing)
3.0 Usage
---------
2.1 Requirements
To build Symon with Apache Maven, just type:
- Java 1.5 or higher
- Maven 2.0.x or higher (for building from source)
- JUnit 4 or higher (for testing)
$ mvn package
(More to come!)
Maven will build Symon, run unit tests, and produce a jar file in the
'target' directory containing the compiled simulator.
3.0 To Do
Symon is meant to be invoked directly from the jar file. To run with
Java 1.5 or greater, just type:
$ jar -jar symon-0.1.jar
When Symon is running , you should be greeted by the following message
Welcome to the Symon 6502 Simulator. Type 'h' for help.
symon>
Commands are entered at the monitor 'symon>' prompt. They can be the
full name (e.g. 'examine') or abbrevations (e.g. 'ex', or even just
'e' if there is no ambiguity). The basic commands are documented below
3.1 Help
--------
Typing 'h' or 'help' will show the help screen.
symon> h
Symon 6502 Simulator
All addresses must be in hexadecimal.
Commands may be short or long (e.g. 'e' or 'ex' or 'examine').
Note that 'go' clears the BREAK processor status flag.
h Show this help file.
e [start] [end] Examine memory at PC, start, or start-end.
d <address> <data> Deposit data into address.
f <start> <end> <data> Fill memory with data.
set {pc,a,x,y} [data] Set register to data value.
load <file> <address> Load binary file at address.
g [address] [steps] Start running at address, or at PC.
step [address] Step once, optionally starting at address.
stat Show CPU state.
reset Reset simulator.
trace Toggle trace.
q (or Control-D) Quit.
3.2 Examine
-----------
Memory locations can be examined with the 'examine' (abbreviated 'ex'
or 'e') command.
If given no arguments, it will display the contents of memory at the
program counter, as well as the address it is showing.
Example: Examine memory location at the program counter.
symon> ex
0000 35
Subsequent invocations without arguments will move forward in memory
from the program counter, showing one byte per invocation
Example: Continue examining memory from previous location, one byte at
a time.
symon> ex
0001 ff
symon> ex
0002 1d
symon> ex
0003 a9
An address to examine can be specified by a single argument.
Example: Examine memory location $200
symon> ex 0200
0200 a9
To examine a range of memory, both a start and end address can be
specified.
Example: Examine all memory from memory location $300 to $325
symon> e 0300 0325
0300 a2 00 bd 11 03 f0 07 8d 00 c0 e8 4c 02 03 4c 0e
0310 03 48 65 6c 6c 6f 2c 20 36 35 30 32 20 77 6f 72
0320 6c 64 21 0d 0a 00
3.3 Deposit
-----------
Memory contents can be updated using the 'deposit' command
(abbreviated 'dep' or 'd'). It takes a single argument, a
16-bit address.
Example: Deposit the byte value $A9 into memory location $400
symon> d 0400 a9
3.4 Fill
--------
A region of memory can be filled using the 'fill' command (abbreviated
'fi', 'fl', or 'f'). It takes three arguments. The first is the start
address, the second is the end address (inclusive), and the third is
the byte value to fill the memory with.
Example: Fill the memory range $400 to $4FF with the byte value $EA
symon> fill 0400 04ff ea
3.5 Set Registers
-----------------
Individual registers can be modified directly using the 'set' command.
The program counter (PC), accumulator (A), X index register and Y
index register can be set.
The 'set' command takes two arguments. The first is the register to be
modified, and the second is the one or two byte value to set.
Example: Set the program counter to $300
symon> set pc 0300
Example: Set the X register to 00
symon> set x 00
3.6 Load A Binary File
----------------------
Programs in the form of raw binary object files can be loaded directly
into memory with the 'load' command (abbreviated 'lo' or 'l'). It
takes two arguments: A file name (with full path if not in the current
working directory where the simulator was started), and a starting
address.
Example: Load the file 'test.bin' at address $300
symon> load test2.bin 0300
Loading file 'test2.bin' at address 0300...
Loaded 38 ($0026) bytes
3.7 Start Running
-----------------
The simulator's CPU can be started running with the 'go' command. If
invoked with no argument, the simulator will start running from the
current program counter.
Example: Start running from the current program counter.
symon> go
If invoked with one argument, the PC will first be set to the
specified address before the simulated CPU starts running.
Example: Start running from memory location $300
symon> go 0300
If invoked with two arguments, the second is interpreted as the
maximum number of steps to execute.
Example: Start running from memory location $300, but for no more than
255 steps
symon> go 0300 ff
[TODO: More to come]
4.0 To Do
---------
- Finish core functionality.
- Finish command monitor.
- Refactor address decoding (second refactor to DRY up more).
- More extensive testing.
- Command monitor improvements:
* Allow 'deposit' to take no argument, but auto-increment
deposited-to address with each invocation.
- Clean up JavaDoc.
- Busses are defined by start address and length. Devices are defined
by start address and end address. They should both use start/end
address.
- Implement CMOS 65C02 instructions and NMOS / CMOS mode flag.
- Allow a flag to disable breaking to monitor on BRK.
- Allow displaying ACIA status and dumping ACIA buffers, for
debugging.
- Allow fine-tuning of keyboard polling interval; 500 instructions is
a lot of latency.
4.0 Licensing
-------------

View File

@ -4,7 +4,7 @@
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>snapshot</version>
<version>0.1</version>
<name>symon</name>
<url>http://www.loomcom.com/symon</url>
<dependencies>

View File

@ -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;

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -0,0 +1,7 @@
package com.loomcom.symon.exceptions;
public class FifoUnderrunException extends Exception {
public FifoUnderrunException(String msg) {
super(msg);
}
}

View File

@ -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 + "]";
}
}

View File

@ -0,0 +1,122 @@
package com.loomcom.symon;
import org.junit.*;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.exceptions.FifoUnderrunException;
import static org.junit.Assert.*;
public class AciaTest {
@Test
public void newAciaShouldHaveTxEmptyStatus() throws Exception {
Acia acia = new Acia(0x000);
assertEquals(0x10, acia.read(0x0001));
}
@Test
public void aciaShouldHaveTxEmptyStatusOffIfTxHasData() throws Exception {
Acia acia = new Acia(0x000);
acia.txWrite('a');
assertEquals(0x00, acia.read(0x0001));
}
@Test
public void aciaShouldHaveRxFullStatusOffIfRxHasData() throws Exception {
Acia acia = new Acia(0x000);
acia.rxWrite('a');
assertEquals(0x18, acia.read(0x0001));
}
@Test
public void aciaShouldHaveTxEmptyAndRxFullStatusOffIfRxAndTxHaveData()
throws Exception {
Acia acia = new Acia(0x000);
acia.rxWrite('a');
acia.txWrite('b');
assertEquals(0x08, acia.read(0x0001));
}
@Test
public void readingBuffersUntilEmptyShouldResetStatus()
throws Exception {
Acia acia = new Acia(0x0000);
acia.rxWrite('a');
acia.txWrite('b');
assertEquals(0x08, acia.read(0x0001));
assertEquals('a', acia.rxRead());
assertEquals('b', acia.txRead());
assertEquals(0x10, acia.read(0x0001));
}
@Test
public void readingPastEmptyRxBufferShouldThrowException()
throws Exception {
Acia acia = new Acia(0x0000);
acia.rxWrite('a');
assertEquals(0x18, acia.read(0x0001)); // not empty
assertEquals('a', acia.rxRead());
assertEquals(0x10, acia.read(0x0001)); // becomes empty
// Should raise (note: I prefer this style to @Test(expected=...)
// because it allows much finer grained control over asserting
// exactly where the exception is expected to be raised.)
try {
// Should cause an underrun
acia.rxRead();
fail("Should have raised FifoUnderrunException.");
} catch (FifoUnderrunException ex) {}
assertEquals(0x10, acia.read(0x0001)); // still again
for (int i = 0; i < Acia.BUF_LEN; i++) {
acia.rxWrite('a');
}
// Should NOT cause an overrun
acia.rxWrite('a'); // nothing thrown
}
@Test
public void readingPastEmptyTxBufferShouldThrowException()
throws Exception {
Acia acia = new Acia(0x0000);
acia.txWrite('a');
assertEquals(0x00, acia.read(0x0001)); // not empty
assertEquals('a', acia.txRead());
assertEquals(0x10, acia.read(0x0001)); // becomes empty
// Should raise (note: I prefer this style to @Test(expected=...)
// because it allows much finer grained control over asserting
// exactly where the exception is expected to be raised.)
try {
// Should cause an underrun
acia.txRead();
fail("Should have raised FifoUnderrunException.");
} catch (FifoUnderrunException ex) {}
assertEquals(0x10, acia.read(0x0001)); // still empty
for (int i = 0; i < Acia.BUF_LEN; i++) {
acia.txWrite('a');
}
// Should NOT cause an overrun
acia.txWrite('a'); // Nothing thrown.
}
}

View File

@ -1,7 +1,6 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import org.junit.*;
import static org.junit.Assert.*;