mirror of
https://github.com/sethm/symon.git
synced 2025-04-12 23:37:07 +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:
parent
596d05e398
commit
00ab8cd9ff
221
README
221
README
@ -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
|
||||
-------------
|
||||
|
2
pom.xml
2
pom.xml
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
124
src/main/java/com/loomcom/symon/devices/Acia.java
Normal file
124
src/main/java/com/loomcom/symon/devices/Acia.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
78
src/main/java/com/loomcom/symon/util/FifoRingBuffer.java
Normal file
78
src/main/java/com/loomcom/symon/util/FifoRingBuffer.java
Normal 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 + "]";
|
||||
}
|
||||
}
|
||||
|
122
src/test/java/com/loomcom/symon/AciaTest.java
Normal file
122
src/test/java/com/loomcom/symon/AciaTest.java
Normal 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.
|
||||
}
|
||||
|
||||
}
|
@ -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.*;
|
||||
|
Loading…
x
Reference in New Issue
Block a user