mirror of
https://github.com/sethm/symon.git
synced 2024-06-27 01:29:32 +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
217
README
217
README
|
@ -10,8 +10,8 @@ deemed ready for testing, it will be given a version number of "0.1".
|
||||||
|
|
||||||
====================================================================
|
====================================================================
|
||||||
|
|
||||||
Version: PRERELEASE
|
Version: 0.1
|
||||||
Last Updated: 10 January, 2010
|
Last Updated: 20 January, 2010
|
||||||
Copyright (c) 2008-2010 Seth J. Morabito <sethm@loomcom.com>
|
Copyright (c) 2008-2010 Seth J. Morabito <sethm@loomcom.com>
|
||||||
|
|
||||||
See the file COPYING for license.
|
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
|
65C02 central processor; one or more 6522 VIAs; and one or more 6551
|
||||||
ACIAs. More functionality may be considered as time goes on.
|
ACIAs. More functionality may be considered as time goes on.
|
||||||
|
|
||||||
2.0 Usage
|
|
||||||
---------
|
|
||||||
|
|
||||||
2.1 Requirements
|
2.0 Requirements
|
||||||
|
----------------
|
||||||
|
|
||||||
- Java 1.5 or higher
|
- Java 1.5 or higher
|
||||||
- Maven 2.0.x or higher (for building from source)
|
- Maven 2.0.x or higher (for building from source)
|
||||||
- JUnit 4 or higher (for testing)
|
- JUnit 4 or higher (for testing)
|
||||||
|
|
||||||
(More to come!)
|
|
||||||
|
|
||||||
3.0 To Do
|
3.0 Usage
|
||||||
---------
|
---------
|
||||||
|
|
||||||
- Finish core functionality.
|
To build Symon with Apache Maven, just type:
|
||||||
- Finish command monitor.
|
|
||||||
- Refactor address decoding (second refactor to DRY up more).
|
$ mvn package
|
||||||
|
|
||||||
|
Maven will build Symon, run unit tests, and produce a jar file in the
|
||||||
|
'target' directory containing the compiled simulator.
|
||||||
|
|
||||||
|
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
|
||||||
|
---------
|
||||||
|
|
||||||
|
- More extensive testing.
|
||||||
|
|
||||||
|
- Command monitor improvements:
|
||||||
|
* Allow 'deposit' to take no argument, but auto-increment
|
||||||
|
deposited-to address with each invocation.
|
||||||
|
|
||||||
- Clean up JavaDoc.
|
- 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.
|
- 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
|
4.0 Licensing
|
||||||
-------------
|
-------------
|
||||||
|
|
2
pom.xml
2
pom.xml
|
@ -4,7 +4,7 @@
|
||||||
<groupId>com.loomcom.symon</groupId>
|
<groupId>com.loomcom.symon</groupId>
|
||||||
<artifactId>symon</artifactId>
|
<artifactId>symon</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<version>snapshot</version>
|
<version>0.1</version>
|
||||||
<name>symon</name>
|
<name>symon</name>
|
||||||
<url>http://www.loomcom.com/symon</url>
|
<url>http://www.loomcom.com/symon</url>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
@ -50,7 +50,6 @@ public class Cpu implements InstructionTable {
|
||||||
private int irAddressMode; // Bits 3-5 of IR: [ | | |X|X|X| | ]
|
private int irAddressMode; // Bits 3-5 of IR: [ | | |X|X|X| | ]
|
||||||
private int irOpMode; // Bits 6-7 of IR: [ | | | | | |X|X]
|
private int irOpMode; // Bits 6-7 of IR: [ | | | | | |X|X]
|
||||||
private int effectiveAddress;
|
private int effectiveAddress;
|
||||||
private int effectiveData;
|
|
||||||
|
|
||||||
/* Internal scratch space */
|
/* Internal scratch space */
|
||||||
private int lo = 0, hi = 0; // Used in address calculation
|
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)
|
// Get the data from the effective address (if any)
|
||||||
|
|
||||||
effectiveAddress = 0;
|
effectiveAddress = 0;
|
||||||
effectiveData = 0;
|
|
||||||
|
|
||||||
switch(irOpMode) {
|
switch(irOpMode) {
|
||||||
case 0:
|
case 0:
|
||||||
case 2:
|
case 2:
|
||||||
switch(irAddressMode) {
|
switch(irAddressMode) {
|
||||||
case 0: // #Immediate
|
case 0: // #Immediate
|
||||||
effectiveData = args[0];
|
|
||||||
break;
|
break;
|
||||||
case 1: // Zero Page
|
case 1: // Zero Page
|
||||||
effectiveAddress = args[0];
|
effectiveAddress = args[0];
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
case 2: // Accumulator - ignored
|
case 2: // Accumulator - ignored
|
||||||
break;
|
break;
|
||||||
case 3: // Absolute
|
case 3: // Absolute
|
||||||
effectiveAddress = address(args[0], args[1]);
|
effectiveAddress = address(args[0], args[1]);
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
case 5: // Zero Page,X / Zero Page,Y
|
case 5: // Zero Page,X / Zero Page,Y
|
||||||
if (ir == 0x96 || ir == 0xb6) {
|
if (ir == 0x96 || ir == 0xb6) {
|
||||||
|
@ -174,7 +169,6 @@ public class Cpu implements InstructionTable {
|
||||||
} else {
|
} else {
|
||||||
effectiveAddress = zpxAddress(args[0]);
|
effectiveAddress = zpxAddress(args[0]);
|
||||||
}
|
}
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
case 7: // Absolute,X / Absolute,Y
|
case 7: // Absolute,X / Absolute,Y
|
||||||
if (ir == 0xbe) {
|
if (ir == 0xbe) {
|
||||||
|
@ -182,7 +176,6 @@ public class Cpu implements InstructionTable {
|
||||||
} else {
|
} else {
|
||||||
effectiveAddress = xAddress(args[0], args[1]);
|
effectiveAddress = xAddress(args[0], args[1]);
|
||||||
}
|
}
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -191,37 +184,29 @@ public class Cpu implements InstructionTable {
|
||||||
case 0: // (Zero Page,X)
|
case 0: // (Zero Page,X)
|
||||||
tmp = args[0] + getXRegister();
|
tmp = args[0] + getXRegister();
|
||||||
effectiveAddress = address(bus.read(tmp), bus.read(tmp + 1));
|
effectiveAddress = address(bus.read(tmp), bus.read(tmp + 1));
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
case 1: // Zero Page
|
case 1: // Zero Page
|
||||||
effectiveAddress = args[0];
|
effectiveAddress = args[0];
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
case 2: // #Immediate
|
case 2: // #Immediate
|
||||||
effectiveAddress = -1;
|
effectiveAddress = -1;
|
||||||
effectiveData = args[0];
|
|
||||||
break;
|
break;
|
||||||
case 3: // Absolute
|
case 3: // Absolute
|
||||||
effectiveAddress = address(args[0], args[1]);
|
effectiveAddress = address(args[0], args[1]);
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
case 4: // (Zero Page),Y
|
case 4: // (Zero Page),Y
|
||||||
tmp = address(bus.read(args[0]),
|
tmp = address(bus.read(args[0]),
|
||||||
bus.read((args[0]+1)&0xff));
|
bus.read((args[0]+1)&0xff));
|
||||||
effectiveAddress = (tmp + getYRegister())&0xffff;
|
effectiveAddress = (tmp + getYRegister())&0xffff;
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
case 5: // Zero Page,X
|
case 5: // Zero Page,X
|
||||||
effectiveAddress = zpxAddress(args[0]);
|
effectiveAddress = zpxAddress(args[0]);
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
case 6: // Absolute, Y
|
case 6: // Absolute, Y
|
||||||
effectiveAddress = yAddress(args[0], args[1]);
|
effectiveAddress = yAddress(args[0], args[1]);
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
case 7: // Absolute, X
|
case 7: // Absolute, X
|
||||||
effectiveAddress = xAddress(args[0], args[1]);
|
effectiveAddress = xAddress(args[0], args[1]);
|
||||||
effectiveData = bus.read(effectiveAddress);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -402,15 +387,18 @@ public class Cpu implements InstructionTable {
|
||||||
|
|
||||||
|
|
||||||
/** ORA - Logical Inclusive Or ******************************************/
|
/** ORA - Logical Inclusive Or ******************************************/
|
||||||
|
case 0x09: // #Immediate
|
||||||
|
a |= args[0];
|
||||||
|
setArithmeticFlags(a);
|
||||||
|
break;
|
||||||
case 0x01: // (Zero Page,X)
|
case 0x01: // (Zero Page,X)
|
||||||
case 0x05: // Zero Page
|
case 0x05: // Zero Page
|
||||||
case 0x09: // #Immediate
|
|
||||||
case 0x0d: // Absolute
|
case 0x0d: // Absolute
|
||||||
case 0x11: // (Zero Page),Y
|
case 0x11: // (Zero Page),Y
|
||||||
case 0x15: // Zero Page,X
|
case 0x15: // Zero Page,X
|
||||||
case 0x19: // Absolute,Y
|
case 0x19: // Absolute,Y
|
||||||
case 0x1d: // Absolute,X
|
case 0x1d: // Absolute,X
|
||||||
a |= effectiveData;
|
a |= bus.read(effectiveAddress);
|
||||||
setArithmeticFlags(a);
|
setArithmeticFlags(a);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -424,7 +412,7 @@ public class Cpu implements InstructionTable {
|
||||||
case 0x0e: // Absolute
|
case 0x0e: // Absolute
|
||||||
case 0x16: // Zero Page,X
|
case 0x16: // Zero Page,X
|
||||||
case 0x1e: // Absolute,X
|
case 0x1e: // Absolute,X
|
||||||
tmp = asl(effectiveData);
|
tmp = asl(bus.read(effectiveAddress));
|
||||||
bus.write(effectiveAddress, tmp);
|
bus.write(effectiveAddress, tmp);
|
||||||
setArithmeticFlags(tmp);
|
setArithmeticFlags(tmp);
|
||||||
break;
|
break;
|
||||||
|
@ -433,7 +421,7 @@ public class Cpu implements InstructionTable {
|
||||||
/** BIT - Bit Test ******************************************************/
|
/** BIT - Bit Test ******************************************************/
|
||||||
case 0x24: // Zero Page
|
case 0x24: // Zero Page
|
||||||
case 0x2c: // Absolute
|
case 0x2c: // Absolute
|
||||||
tmp = a & effectiveData;
|
tmp = a & bus.read(effectiveAddress);
|
||||||
setZeroFlag(tmp == 0);
|
setZeroFlag(tmp == 0);
|
||||||
setNegativeFlag((tmp & 0x80) != 0);
|
setNegativeFlag((tmp & 0x80) != 0);
|
||||||
setOverflowFlag((tmp & 0x40) != 0);
|
setOverflowFlag((tmp & 0x40) != 0);
|
||||||
|
@ -441,15 +429,18 @@ public class Cpu implements InstructionTable {
|
||||||
|
|
||||||
|
|
||||||
/** AND - Logical AND ***************************************************/
|
/** AND - Logical AND ***************************************************/
|
||||||
|
case 0x29: // #Immediate
|
||||||
|
a &= args[0];
|
||||||
|
setArithmeticFlags(a);
|
||||||
|
break;
|
||||||
case 0x21: // (Zero Page,X)
|
case 0x21: // (Zero Page,X)
|
||||||
case 0x25: // Zero Page
|
case 0x25: // Zero Page
|
||||||
case 0x29: // #Immediate
|
|
||||||
case 0x2d: // Absolute
|
case 0x2d: // Absolute
|
||||||
case 0x31: // (Zero Page),Y
|
case 0x31: // (Zero Page),Y
|
||||||
case 0x35: // Zero Page,X
|
case 0x35: // Zero Page,X
|
||||||
case 0x39: // Absolute,Y
|
case 0x39: // Absolute,Y
|
||||||
case 0x3d: // Absolute,X
|
case 0x3d: // Absolute,X
|
||||||
a &= effectiveData;
|
a &= bus.read(effectiveAddress);
|
||||||
setArithmeticFlags(a);
|
setArithmeticFlags(a);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -463,22 +454,25 @@ public class Cpu implements InstructionTable {
|
||||||
case 0x2e: // Absolute
|
case 0x2e: // Absolute
|
||||||
case 0x36: // Zero Page,X
|
case 0x36: // Zero Page,X
|
||||||
case 0x3e: // Absolute,X
|
case 0x3e: // Absolute,X
|
||||||
tmp = rol(effectiveData);
|
tmp = rol(bus.read(effectiveAddress));
|
||||||
bus.write(effectiveAddress, tmp);
|
bus.write(effectiveAddress, tmp);
|
||||||
setArithmeticFlags(tmp);
|
setArithmeticFlags(tmp);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
/** EOR - Exclusive OR **************************************************/
|
/** EOR - Exclusive OR **************************************************/
|
||||||
|
case 0x49: // #Immediate
|
||||||
|
a ^= args[0];
|
||||||
|
setArithmeticFlags(a);
|
||||||
|
break;
|
||||||
case 0x41: // (Zero Page,X)
|
case 0x41: // (Zero Page,X)
|
||||||
case 0x45: // Zero Page
|
case 0x45: // Zero Page
|
||||||
case 0x49: // Immediate
|
|
||||||
case 0x4d: // Absolute
|
case 0x4d: // Absolute
|
||||||
case 0x51: // (Zero Page,Y)
|
case 0x51: // (Zero Page,Y)
|
||||||
case 0x55: // Zero Page,X
|
case 0x55: // Zero Page,X
|
||||||
case 0x59: // Absolute,Y
|
case 0x59: // Absolute,Y
|
||||||
case 0x5d: // Absolute,X
|
case 0x5d: // Absolute,X
|
||||||
a ^= effectiveData;
|
a ^= bus.read(effectiveAddress);
|
||||||
setArithmeticFlags(a);
|
setArithmeticFlags(a);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -492,25 +486,31 @@ public class Cpu implements InstructionTable {
|
||||||
case 0x4e: // Absolute
|
case 0x4e: // Absolute
|
||||||
case 0x56: // Zero Page,X
|
case 0x56: // Zero Page,X
|
||||||
case 0x5e: // Absolute,X
|
case 0x5e: // Absolute,X
|
||||||
tmp = lsr(effectiveData);
|
tmp = lsr(bus.read(effectiveAddress));
|
||||||
bus.write(effectiveAddress, tmp);
|
bus.write(effectiveAddress, tmp);
|
||||||
setArithmeticFlags(tmp);
|
setArithmeticFlags(tmp);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
/** ADC - Add with Carry ************************************************/
|
/** 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 0x61: // (Zero Page,X)
|
||||||
case 0x65: // Zero Page
|
case 0x65: // Zero Page
|
||||||
case 0x69: // Immediate
|
|
||||||
case 0x6d: // Absolute
|
case 0x6d: // Absolute
|
||||||
case 0x71: // (Zero Page),Y
|
case 0x71: // (Zero Page),Y
|
||||||
case 0x75: // Zero Page,X
|
case 0x75: // Zero Page,X
|
||||||
case 0x79: // Absolute,Y
|
case 0x79: // Absolute,Y
|
||||||
case 0x7d: // Absolute,X
|
case 0x7d: // Absolute,X
|
||||||
if (decimalModeFlag) {
|
if (decimalModeFlag) {
|
||||||
a = adcDecimal(a, effectiveData);
|
a = adcDecimal(a, bus.read(effectiveAddress));
|
||||||
} else {
|
} else {
|
||||||
a = adc(a, effectiveData);
|
a = adc(a, bus.read(effectiveAddress));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -524,7 +524,7 @@ public class Cpu implements InstructionTable {
|
||||||
case 0x6e: // Absolute
|
case 0x6e: // Absolute
|
||||||
case 0x76: // Zero Page,X
|
case 0x76: // Zero Page,X
|
||||||
case 0x7e: // Absolute,X
|
case 0x7e: // Absolute,X
|
||||||
tmp = ror(effectiveData);
|
tmp = ror(bus.read(effectiveAddress));
|
||||||
bus.write(effectiveAddress, tmp);
|
bus.write(effectiveAddress, tmp);
|
||||||
setArithmeticFlags(tmp);
|
setArithmeticFlags(tmp);
|
||||||
break;
|
break;
|
||||||
|
@ -562,59 +562,72 @@ public class Cpu implements InstructionTable {
|
||||||
|
|
||||||
|
|
||||||
/** LDY - Load Y Register ***********************************************/
|
/** LDY - Load Y Register ***********************************************/
|
||||||
case 0xa0: // Immediate
|
case 0xa0: // #Immediate
|
||||||
|
y = args[0];
|
||||||
|
setArithmeticFlags(y);
|
||||||
|
break;
|
||||||
case 0xa4: // Zero Page
|
case 0xa4: // Zero Page
|
||||||
case 0xac: // Absolute
|
case 0xac: // Absolute
|
||||||
case 0xb4: // Zero Page,X
|
case 0xb4: // Zero Page,X
|
||||||
case 0xbc: // Absolute,X
|
case 0xbc: // Absolute,X
|
||||||
y = effectiveData;
|
y = bus.read(effectiveAddress);
|
||||||
setArithmeticFlags(y);
|
setArithmeticFlags(y);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
/** LDX - Load X Register ***********************************************/
|
/** LDX - Load X Register ***********************************************/
|
||||||
case 0xa2: // Immediate
|
case 0xa2: // #Immediate
|
||||||
|
x = args[0];
|
||||||
|
setArithmeticFlags(x);
|
||||||
|
break;
|
||||||
case 0xa6: // Zero Page
|
case 0xa6: // Zero Page
|
||||||
case 0xae: // Absolute
|
case 0xae: // Absolute
|
||||||
case 0xb6: // Zero Page,Y
|
case 0xb6: // Zero Page,Y
|
||||||
case 0xbe: // Absolute,Y
|
case 0xbe: // Absolute,Y
|
||||||
x = effectiveData;
|
x = bus.read(effectiveAddress);
|
||||||
setArithmeticFlags(x);
|
setArithmeticFlags(x);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
/** LDA - Load Accumulator **********************************************/
|
/** LDA - Load Accumulator **********************************************/
|
||||||
|
case 0xa9: // #Immediate
|
||||||
|
a = args[0];
|
||||||
|
setArithmeticFlags(a);
|
||||||
|
break;
|
||||||
case 0xa1: // (Zero Page,X)
|
case 0xa1: // (Zero Page,X)
|
||||||
case 0xa5: // Zero Page
|
case 0xa5: // Zero Page
|
||||||
case 0xa9: // Immediate
|
|
||||||
case 0xad: // Absolute
|
case 0xad: // Absolute
|
||||||
case 0xb1: // (Zero Page),Y
|
case 0xb1: // (Zero Page),Y
|
||||||
case 0xb5: // Zero Page,X
|
case 0xb5: // Zero Page,X
|
||||||
case 0xb9: // Absolute,Y
|
case 0xb9: // Absolute,Y
|
||||||
case 0xbd: // Absolute,X
|
case 0xbd: // Absolute,X
|
||||||
a = effectiveData;
|
a = bus.read(effectiveAddress);
|
||||||
setArithmeticFlags(a);
|
setArithmeticFlags(a);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
/** CPY - Compare Y Register ********************************************/
|
/** CPY - Compare Y Register ********************************************/
|
||||||
case 0xc0: // Immediate
|
case 0xc0: // #Immediate
|
||||||
|
cmp(y, args[0]);
|
||||||
|
break;
|
||||||
case 0xc4: // Zero Page
|
case 0xc4: // Zero Page
|
||||||
case 0xcc: // Absolute
|
case 0xcc: // Absolute
|
||||||
cmp(y, effectiveData);
|
cmp(y, bus.read(effectiveAddress));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
/** CMP - Compare Accumulator *******************************************/
|
/** CMP - Compare Accumulator *******************************************/
|
||||||
|
case 0xc9: // #Immediate
|
||||||
|
cmp(a, args[0]);
|
||||||
|
break;
|
||||||
case 0xc1: // (Zero Page,X)
|
case 0xc1: // (Zero Page,X)
|
||||||
case 0xc5: // Zero Page
|
case 0xc5: // Zero Page
|
||||||
case 0xc9: // #Immediate
|
|
||||||
case 0xcd: // Absolute
|
case 0xcd: // Absolute
|
||||||
case 0xd1: // (Zero Page),Y
|
case 0xd1: // (Zero Page),Y
|
||||||
case 0xd5: // Zero Page,X
|
case 0xd5: // Zero Page,X
|
||||||
case 0xd9: // Absolute,Y
|
case 0xd9: // Absolute,Y
|
||||||
case 0xdd: // Absolute,X
|
case 0xdd: // Absolute,X
|
||||||
cmp(a, effectiveData);
|
cmp(a, bus.read(effectiveAddress));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
@ -623,33 +636,41 @@ public class Cpu implements InstructionTable {
|
||||||
case 0xce: // Absolute
|
case 0xce: // Absolute
|
||||||
case 0xd6: // Zero Page,X
|
case 0xd6: // Zero Page,X
|
||||||
case 0xde: // Absolute,X
|
case 0xde: // Absolute,X
|
||||||
tmp = --effectiveData & 0xff;
|
tmp = (bus.read(effectiveAddress) - 1) & 0xff;
|
||||||
bus.write(effectiveAddress, tmp);
|
bus.write(effectiveAddress, tmp);
|
||||||
setArithmeticFlags(tmp);
|
setArithmeticFlags(tmp);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
/** CPX - Compare X Register ********************************************/
|
/** CPX - Compare X Register ********************************************/
|
||||||
case 0xe0: // Immediate
|
case 0xe0: // #Immediate
|
||||||
|
cmp(x, args[0]);
|
||||||
|
break;
|
||||||
case 0xe4: // Zero Page
|
case 0xe4: // Zero Page
|
||||||
case 0xec: // Absolute
|
case 0xec: // Absolute
|
||||||
cmp(x, effectiveData);
|
cmp(x, bus.read(effectiveAddress));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
/** SBC - Subtract with Carry (Borrow) **********************************/
|
/** 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 0xe1: // (Zero Page,X)
|
||||||
case 0xe5: // Zero Page
|
case 0xe5: // Zero Page
|
||||||
case 0xe9: // Immediate
|
|
||||||
case 0xed: // Absolute
|
case 0xed: // Absolute
|
||||||
case 0xf1: // (Zero Page),Y
|
case 0xf1: // (Zero Page),Y
|
||||||
case 0xf5: // Zero Page,X
|
case 0xf5: // Zero Page,X
|
||||||
case 0xf9: // Absolute,Y
|
case 0xf9: // Absolute,Y
|
||||||
case 0xfd: // Absolute,X
|
case 0xfd: // Absolute,X
|
||||||
if (decimalModeFlag) {
|
if (decimalModeFlag) {
|
||||||
a = sbcDecimal(a, effectiveData);
|
a = sbcDecimal(a, bus.read(effectiveAddress));
|
||||||
} else {
|
} else {
|
||||||
a = sbc(a, effectiveData);
|
a = sbc(a, bus.read(effectiveAddress));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -659,7 +680,7 @@ public class Cpu implements InstructionTable {
|
||||||
case 0xee: // Absolute
|
case 0xee: // Absolute
|
||||||
case 0xf6: // Zero Page,X
|
case 0xf6: // Zero Page,X
|
||||||
case 0xfe: // Absolute,X
|
case 0xfe: // Absolute,X
|
||||||
tmp = ++effectiveData & 0xff;
|
tmp = (bus.read(effectiveAddress) + 1) & 0xff;
|
||||||
bus.write(effectiveAddress, tmp);
|
bus.write(effectiveAddress, tmp);
|
||||||
setArithmeticFlags(tmp);
|
setArithmeticFlags(tmp);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -22,6 +22,13 @@ public class Simulator {
|
||||||
*/
|
*/
|
||||||
private Bus bus;
|
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 BufferedReader in;
|
||||||
private BufferedWriter out;
|
private BufferedWriter out;
|
||||||
|
|
||||||
|
@ -29,16 +36,35 @@ public class Simulator {
|
||||||
private boolean trace = false;
|
private boolean trace = false;
|
||||||
private int nextExamineAddress = 0;
|
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 {
|
public Simulator() throws MemoryRangeException {
|
||||||
cpu = new Cpu();
|
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
||||||
bus = new Bus(0x0000, 0xffff);
|
this.cpu = new Cpu();
|
||||||
|
this.acia = new Acia(ACIA_BASE);
|
||||||
|
|
||||||
bus.addCpu(cpu);
|
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.in = new BufferedReader(new InputStreamReader(System.in));
|
||||||
this.out = new BufferedWriter(new OutputStreamWriter(System.out));
|
this.out = new BufferedWriter(new OutputStreamWriter(System.out));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() throws MemoryAccessException {
|
public void run() throws MemoryAccessException, FifoUnderrunException {
|
||||||
try {
|
try {
|
||||||
greeting();
|
greeting();
|
||||||
prompt();
|
prompt();
|
||||||
|
@ -61,8 +87,10 @@ public class Simulator {
|
||||||
/**
|
/**
|
||||||
* Dispatch the command.
|
* Dispatch the command.
|
||||||
*/
|
*/
|
||||||
public void dispatch(String commandLine)
|
public void dispatch(String commandLine) throws MemoryAccessException,
|
||||||
throws MemoryAccessException, IOException, CommandFormatException {
|
IOException,
|
||||||
|
CommandFormatException,
|
||||||
|
FifoUnderrunException {
|
||||||
Command c = new Command(commandLine);
|
Command c = new Command(commandLine);
|
||||||
String cmd = c.getCommand();
|
String cmd = c.getCommand();
|
||||||
if (cmd != null) {
|
if (cmd != null) {
|
||||||
|
@ -102,16 +130,16 @@ public class Simulator {
|
||||||
writeLine("Note that 'go' clears the BREAK processor status flag.");
|
writeLine("Note that 'go' clears the BREAK processor status flag.");
|
||||||
writeLine("");
|
writeLine("");
|
||||||
writeLine("h Show this help file.");
|
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("e [start] [end] Examine memory at PC, start, or start-end.");
|
||||||
writeLine("d <address> <data> Deposit data into address.");
|
writeLine("d <address> <data> Deposit data into address.");
|
||||||
writeLine("f <start> <end> <data> Fill memory with data.");
|
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("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("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");
|
writeLine("q (or Control-D) Quit.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +148,8 @@ public class Simulator {
|
||||||
writeLine("Trace is " + (trace ? "on" : "off"));
|
writeLine("Trace is " + (trace ? "on" : "off"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doLoad(Command c) throws IOException, MemoryAccessException,
|
public void doLoad(Command c) throws IOException,
|
||||||
|
MemoryAccessException,
|
||||||
CommandFormatException {
|
CommandFormatException {
|
||||||
if (c.numArgs() != 2) {
|
if (c.numArgs() != 2) {
|
||||||
throw new CommandFormatException("load <file> <address>");
|
throw new CommandFormatException("load <file> <address>");
|
||||||
|
@ -178,7 +207,8 @@ public class Simulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doExamine(Command c) throws IOException, MemoryAccessException,
|
public void doExamine(Command c) throws IOException,
|
||||||
|
MemoryAccessException,
|
||||||
CommandFormatException {
|
CommandFormatException {
|
||||||
try {
|
try {
|
||||||
if (c.numArgs() == 2) {
|
if (c.numArgs() == 2) {
|
||||||
|
@ -245,7 +275,9 @@ public class Simulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doStep(Command c) throws IOException, MemoryAccessException,
|
public void doStep(Command c) throws IOException,
|
||||||
|
MemoryAccessException,
|
||||||
|
FifoUnderrunException,
|
||||||
CommandFormatException {
|
CommandFormatException {
|
||||||
try {
|
try {
|
||||||
if (c.numArgs() > 0) {
|
if (c.numArgs() > 0) {
|
||||||
|
@ -258,8 +290,13 @@ public class Simulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doGo(Command c) throws IOException, MemoryAccessException,
|
public void doGo(Command c) throws IOException,
|
||||||
|
MemoryAccessException,
|
||||||
|
FifoUnderrunException,
|
||||||
CommandFormatException {
|
CommandFormatException {
|
||||||
|
int readChar;
|
||||||
|
int stepCount = 0;
|
||||||
|
|
||||||
if (c.numArgs() > 2) {
|
if (c.numArgs() > 2) {
|
||||||
throw new CommandFormatException("g [address] [steps]");
|
throw new CommandFormatException("g [address] [steps]");
|
||||||
}
|
}
|
||||||
|
@ -282,11 +319,49 @@ public class Simulator {
|
||||||
cpu.clearIrqDisableFlag();
|
cpu.clearIrqDisableFlag();
|
||||||
|
|
||||||
cpu.setProgramCounter(start);
|
cpu.setProgramCounter(start);
|
||||||
|
outer:
|
||||||
while (!cpu.getBreakFlag() && (steps == -1 || steps-- > 0)) {
|
while (!cpu.getBreakFlag() && (steps == -1 || steps-- > 0)) {
|
||||||
cpu.step();
|
cpu.step();
|
||||||
if (trace) {
|
if (trace) {
|
||||||
writeLine(cpu.toString());
|
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) {
|
if (!trace) {
|
||||||
writeLine(cpu.toString());
|
writeLine(cpu.toString());
|
||||||
|
@ -309,7 +384,8 @@ public class Simulator {
|
||||||
/**
|
/**
|
||||||
* Main simulator routine.
|
* Main simulator routine.
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) throws MemoryAccessException {
|
public static void main(String[] args) throws MemoryAccessException,
|
||||||
|
FifoUnderrunException {
|
||||||
try {
|
try {
|
||||||
new Simulator().run();
|
new Simulator().run();
|
||||||
} catch (MemoryRangeException ex) {
|
} catch (MemoryRangeException ex) {
|
||||||
|
|
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 boolean readOnly;
|
||||||
private int[] mem;
|
private int[] mem;
|
||||||
|
|
||||||
|
/* Initialize all locations to 0x00 (BRK) */
|
||||||
|
private static final int DEFAULT_FILL = 0x00;
|
||||||
|
|
||||||
public Memory(int address, int size, boolean readOnly)
|
public Memory(int address, int size, boolean readOnly)
|
||||||
throws MemoryRangeException {
|
throws MemoryRangeException {
|
||||||
super(address, size, "RW Memory");
|
super(address, size, (readOnly ? "RO Memory" : "RW Memory"));
|
||||||
this.readOnly = readOnly;
|
this.readOnly = readOnly;
|
||||||
this.mem = new int[size];
|
this.mem = new int[size];
|
||||||
// Initialize all locations to 0x00 (BRK)
|
Arrays.fill(this.mem, DEFAULT_FILL);
|
||||||
Arrays.fill(this.mem, 0x00);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Memory(int address, int size)
|
public Memory(int address, int size) throws MemoryRangeException {
|
||||||
throws MemoryRangeException {
|
|
||||||
this(address, size, false);
|
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;
|
package com.loomcom.symon;
|
||||||
|
|
||||||
import com.loomcom.symon.devices.Memory;
|
import com.loomcom.symon.devices.Memory;
|
||||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user