Compare commits

..
34 Commits
Author SHA1 Message Date
Seth Morabito 2dc0078deb Release 0.9.9.0 2014-07-26 13:23:42 -07:00
Seth Morabito ac21b2c126 Updates for 0.9.9 2014-07-26 13:18:07 -07:00
Seth Morabito 4bb6d55fa2 Merge pull request #5 from maikmerten/master
Infrastructure for several machine types, Bus reworkings, 6850 ACIA and more
2014-07-26 12:46:45 -07:00
Maik Merten f90516e53c fix a possible NPE 2014-07-26 18:55:51 +02:00
Maik Merten fdaeb661d3 add menu entry to allow to switch the emulated machine type 2014-07-26 18:52:57 +02:00
Maik Merten 827e9991d5 move main method into Main class in anticipation of changes that allow restarting the complete UI from within the Simulator menu system 2014-07-26 18:07:29 +02:00
Maik Merten dc7dfc4b4d code simplification 2014-07-25 21:44:43 +02:00
Maik Merten 2217f3831d slight JavaDoc fix regarding what Exception can be thrown 2014-07-25 21:27:40 +02:00
Maik Merten 52f4e9a00f enforce that the address range of devices falls within the address range of the bus. Turned out that the CPU tests instantiate memory with the last parameter as memory size, not end address (fixed now). Also make sure that the address lookup array takes the offset caused by non-zero starting addresses into account. 2014-07-25 21:24:16 +02:00
Maik Merten d7f8045b61 rework bus. Don't throw an Exception any more with overlapping device addresses - this is normal for some machines. Introduce the concept of priorities to ensure that device overlap in the correct order. 2014-07-25 19:32:00 +02:00
Maik Merten 222772648d simple dialog for choosing machine model when no command-line option was given 2014-07-22 17:50:06 +02:00
Maik Merten bd2337ae65 allow for selecting the emulated machien type with the "-machine" parameter. The default is the Symon machine model, the Multicomp machine can be selected with "-machine multicomp". 2014-07-21 17:39:20 +02:00
Maik Merten a49c0d40d8 revert back to busy waiting for CPU speed emulation to have a less bursty CPU behavior 2014-07-20 21:55:01 +02:00
Maik Merten 44bfbc17ab replace busy waiting to emulate "proper" CPU speed with some time book keeping and thread sleeping 2014-07-19 20:19:26 +02:00
Maik Merten b266a02372 have full 56K of RAM for the Multicomp machine 2014-07-19 17:51:51 +02:00
Maik Merten 72074b9715 Silence a compiler warning regarding generics 2014-07-19 17:40:19 +02:00
Maik Merten 252ebe0569 implement overrun status flag for the 6551 ACIA 2014-07-19 17:30:23 +02:00
Maik Merten 4a37dd7463 move overrun detection and reset into the abstract superclass for ACIAs 2014-07-19 17:10:57 +02:00
Maik Merten cf36f731da make use of the MulticompMachine in the simulator. This should be an option, not hardcoded. 2014-07-17 21:59:03 +02:00
Maik Merten e01f68a19a make use of overlapping memory regions in MulticompMachine 2014-07-17 21:45:37 +02:00
Maik Merten 44151f3a9c have the Bus support devices with overlapping memory regions 2014-07-17 21:43:46 +02:00
Maik Merten f5a25b7303 initial definition of the MULTICOMP machine. Will not yet run, as the bus will currently not allow overlapping memory ranges for devices. 2014-07-17 20:41:26 +02:00
Maik Merten 14e8ea596c define an interface for supported machines and have the SymonMachine as first implementation. Remove machine-specific definitions out of the Simulator class. 2014-07-17 19:42:51 +02:00
Maik Merten 8f52e1da1e implement overrun flag for 6850 ACIA 2014-07-16 22:26:06 +02:00
Maik Merten 0d0fddc365 tests for the 6850 ACIA and fixes in ACIA behavior 2014-07-16 21:51:34 +02:00
Maik Merten 31fdd87b41 turn Acia into an abstract class so that the 6551 and 6850 ACIAs can reuse code and an unified interface. 2014-07-15 21:35:19 +02:00
Maik Merten 9726907317 initial (and untested) implementation of a Motorola 6850 ACIA 2014-07-15 18:45:13 +02:00
Seth Morabito 40c5397ed8 Faster refresh of memory window 2014-07-06 12:17:39 -07:00
Seth Morabito a7ea60d250 Bump version to 0.9.1.0 2014-01-26 01:29:09 -08:00
Seth Morabito 1bc8eda38d ACIA interrupt handling 2014-01-26 01:25:12 -08:00
Seth Morabito 76dcbd712a IRQ and NMI handling at CPU level. 2014-01-25 20:45:39 -08:00
Seth Morabito b6cc480919 Add NMI flag to CPU 2014-01-25 20:02:17 -08:00
Seth Morabito 0c5035fc56 Add interrupt flag to CPU 2014-01-25 19:53:53 -08:00
Seth Morabito 3d36cd5460 Improved screenshots 2013-12-29 18:43:29 -08:00
42 changed files with 2153 additions and 761 deletions

No files matched your search

+58 -34
View File
@@ -3,15 +3,16 @@ SYMON - A 6502 System Simulator
**NOTE: THIS SOFTWARE IS UNDER ACTIVE DEVELOPMENT. Feedback is welcome!**
**Version:** 0.9.0.1
**Version:** 0.9.9.0
**Last Updated:** 29 December, 2013
**Last Updated:** 26 July, 2014
Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
Copyright (c) 2014 Seth J. Morabito <web@loomcom.com>
Portions Copyright (c) 2014 Maik Marten <maikmarten@googlemail.com>
See the file COPYING for license.
![Symon Simulator in Action] (https://github.com/sethm/symon/raw/master/screenshots/full.jpg)
## 1.0 About
@@ -22,7 +23,8 @@ in Java. Its core goals are accuracy, ease of development, clear
documentation, and extensive test suites for validating correctness.
Symon simulates a complete system with a 1 MHz NMOS 6502, 32KB of RAM,
16KB of ROM, a 6551 ACIA, a 6522 VIA, and a 6545 CRTC.
16KB of ROM, a MOS 6551 or Motorola 6850 ACIA, a MOS 6522 VIA, and an
experimental 6545 CRTC.
Symon has extensive unit tests to verify correctness, and fully passes
Klaus Dormann's 6502 Functional Test Suite as of version 0.8.2
@@ -31,21 +33,33 @@ for more information about this functional test suite).
## 2.0 Requirements
- Java 1.5 or higher
- Java 1.7 or higher
- Maven 2.0.x or higher (for building from source)
- JUnit 4 or higher (for testing)
## 3.0 Features
### 3.1 Memory Map
Symon can simiulate multiple 6502 based architectures. At present, two
machines are implemented: Symon (the default), and MULTICOMP.
### 3.1 Memory Maps
#### 3.1.1 Symon Memory Map
- `$0000`--`$7FFF`: 32KB RAM
- `$8000`--`$800F`: 6522 VIA
- `$8800`--`$8803`: 6551 ACIA (Serial Console)
- `$9000`--`$9001`: 6545 CRTC
- `$8800`--`$8803`: MOS 6551 ACIA (Serial Console)
- `$9000`--`$9001`: MOS 6545 CRTC
- `$C000`--`$FFFF`: 16KB ROM
The CRT Controller uses memory address `$7000` as the start of Video memory.
The CRT Controller uses memory address `$7000` as the start of Video
memory.
#### 3.1.2 MULTICOMP Memory Map
- `$0000`--`$DFFF`: 56KB RAM
- `$E000`--`$FFFF`: 8KB ROM
- `$FFD0`--`$FFD1`: Motorola 6850 ACIA
### 3.2 Serial Console and CPU Status
@@ -53,10 +67,9 @@ The CRT Controller uses memory address `$7000` as the start of Video memory.
The main window of the simulator acts as the primary Input/Output
system through a virtual serial terminal. The terminal is attached to
a simulated MOS 6551 ACIA, including a programmable baud rate
generator that tries to approximate the correct "feel" of the
programmed baud rate. (The sample Enhanced BASIC ROM image is
programmed for 9600 baud)
a simulated ACIA, including a programmable baud rate generator that
tries to approximate the correct "feel" of the programmed baud rate.
(The sample Enhanced BASIC ROM image is programmed for 9600 baud)
It also provides CPU status. Contents of the accumulator, index
registers, processor status flags, disassembly of the instruction
@@ -66,13 +79,16 @@ register, and stack pointer are all displayed.
The console supports font sizes from 10 to 20 points.
### 3.3 16 KB ROM Loading
### 3.3 ROM Loading
![ROM Loading] (https://github.com/sethm/symon/raw/master/screenshots/load_rom.png)
Symon can load any 16 KB (16384 bytes) ROM image from the "File"
menu. The selected ROM will be placed in memory from locations `$C000`
to `$FFFF`
Symon can load any appropriately sized ROM image. The Symon
architecture expects as 16KB (16384 byte) ROM image, while the
MULTICOMP architecture expects an 8KB (8192 byte) ROM image. Images
are loaded via the "Load ROM..." action in the "File" menu. The
selected ROM file will be loaded into memory at the correct ROM base
address.
### 3.4 Memory Window
@@ -154,24 +170,29 @@ Maven will build Symon, run unit tests, and produce a jar file in the
Symon is meant to be invoked directly from the jar file. To run with
Java 1.5 or greater, just type:
$ java -jar symon-0.9.0.jar
$ java -jar symon-0.9.1.0.jar
When Symon is running, you should be presented with a simple graphical
interface.
### 4.2 ROM images
The simulator requires a 16KB ROM image loaded at address `$C000` to `$FFFF` to
work properly. Without a ROM in memory, the simulator will not be able to
reset, since the reset vector for the 6502 is located in this address space.
The simulator requires a ROM image loaded into memory to work
properly. Without a ROM in memory, the simulator will not be able to
reset, since the reset vector for the 6502 is located in the ROM
address space.
By default, any 16KB file named `rom.bin` that exists in the same directory
where Symon is launched will be loaded as a ROM image. ROM images can also
be swapped out at run-time with the "Load ROM Image..." in the File menu.
By default, any file named `rom.bin` that exists in the same directory
where Symon is launched will be loaded as a ROM image. ROM images can
also be swapped out at run-time with the "Load ROM Image..." in the
File menu.
The "samples" directory contains a ROM image named 'ehbasic.rom', containing
Lee Davison's Enhanced 6502 BASIC. This serves as a good starting point for
exploration.
The "samples" directory contains a ROM image for the Symon
architecture named 'ehbasic.rom', containing Lee Davison's Enhanced
6502 BASIC. This serves as a good starting point for exploration.
*Note*: Presently, EhBASIC only works with the Symon machine
architecture, not with MULTICOMP.
### 4.3 Loading A Program
@@ -184,7 +205,7 @@ reset.
There are two very simple sample program in the "samples" directory,
for testing.
- 'echo.prg' will echo back anything typed at the console.
- 'hello.prg' will continuously print "Hello, 6502 World!" to the console.
@@ -196,6 +217,11 @@ running.
## 5.0 Revision History
- **0.9.9:** 26 July, 2014 - MULTICOMP and multi-machine support
contributed by Maik Marten <maikmarten@googlemail.com>
- **0.9.1:** 26 January, 2014 - Support for IRQ and NMI handling.
- **0.9.0:** 29 December, 2013 - First pass at a 6545 CRTC simulation.
- **0.8.5:** 30 March, 2013 - ASCII display for memory window.
@@ -232,10 +258,6 @@ running.
- Better debugging tools from the UI, including breakpoints
and disassembly.
- More accurate timing.
- Smarter interrupt handling.
- UI needs a ton more polish.
- More extensive testing.
@@ -247,6 +269,8 @@ running.
- Allow displaying ACIA status and dumping ACIA buffers, for
debugging.
- Symbolic debugging.
## 7.0 Acknowledgements
This project would not have been possible without the following resources:
@@ -254,7 +278,7 @@ This project would not have been possible without the following resources:
- [Graham Edgecombe's JTerminal project] (https://github.com/grahamedgecombe/jterminal),
which forms the core of Symon's console.
- [Andrew Jacobs' 6502 Pages] (http://www.obelisk.demon.co.uk/6502/), for
- [Andrew Jacobs' 6502 Pages] (http://www.obelisk.demon.co.uk/6502/), for
wonderfully detailed information about the 6502
- [Neil Parker's "The 6502/65C02/65C816 Instruction Set Decoded"] (http://www.llx.com/~nparker/a2/opcodes.html),
+2 -2
View File
@@ -4,7 +4,7 @@
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>0.9.0.1</version>
<version>0.9.9.0</version>
<name>symon</name>
<url>http://www.loomcom.com/symon</url>
<properties>
@@ -88,7 +88,7 @@
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<mainClass>com.loomcom.symon.Simulator</mainClass>
<mainClass>com.loomcom.symon.Main</mainClass>
</manifest>
</archive>
</configuration>
+7 -3
View File
@@ -6,18 +6,22 @@ Sample Programs
When loaded at address $0300, this program will print "Hello, 6502 World!" in
infinite loop.
2. echo.prg
2. echo_poll.prg
When loaded at address $0300, this program will echo back to the console
anything typed.
Both hello.prg and echo.prg were assembled with the Ophis assembler:
Both hello.prg and echo_poll.prg were assembled with the Ophis assembler:
https://hkn.eecs.berkeley.edu/~mcmartin/ophis/
3. echo_irq.rom
3. ehbasic.rom
This is another echo program, and behaves identically to echo_poll.prg,
except it is interrupt-driven.
4. ehbasic.rom
This is Lee Davison's Enhanced 6502 BASIC.
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
*.rom
*.lst
*.map
*.o
+13
View File
@@ -0,0 +1,13 @@
CA=ca65
LD=ld65
all: echo_irq
echo_irq: echo_irq.o
$(LD) -C symon.config -vm -m echo_irq.map -o echo_irq.rom echo_irq.o
echo_irq.o:
$(CA) --listing -o echo_irq.o echo_irq.asm
clean:
rm -f *.o *.rom *.map *.lst
+48
View File
@@ -0,0 +1,48 @@
;;
;; Read input from the keyboard, and echo to console.
;;
IOBASE = $8800
IOSTATUS = IOBASE + 1
IOCMD = IOBASE + 2
IOCTRL = IOBASE + 3
.segment "CODE"
.org $C000
START: CLI
LDA #$09
STA IOCMD ; Set command status
LDA #$1A
STA IOCTRL ; 0 stop bits, 8 bit word, 2400 baud
;;
;; Infinite idle loop, waiting for interrupt.
;;
IDLE: LDA #$FF
TAX
IDLE1: DEX
BNE IDLE1
JMP IDLE
;;
;; IRQ handler
;;
IRQ: LDA IOBASE ; Get the character in the ACIA.
PHA ; Save accumulator
ECHO1: LDA IOSTATUS ; Read the ACIA status
AND #$10 ; Is the tx register empty?
BEQ ECHO1 ; No, wait for it to empty
PLA ; Otherwise, load saved accumulator,
STA IOBASE ; write to output,
RTI ; and return
; system vectors
.segment "VECTORS"
.org $FFFA
.word IRQ ; NMI vector
.word START ; RESET vector
.word IRQ ; IRQ vector
+16
View File
@@ -0,0 +1,16 @@
MEMORY {
RAM1: start = $0000, size = $8000;
ROM1: start = $C000, size = $3FFA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
}
SEGMENTS {
CODE: load = ROM1, type = ro;
DATA: load = ROM1, type = ro;
VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__ = $0300;
}
File renamed without changes.
File renamed without changes.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 73 KiB

+117 -45
View File
@@ -26,6 +26,11 @@ package com.loomcom.symon;
import com.loomcom.symon.devices.Device;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -45,15 +50,19 @@ public class Bus {
// The CPU
private Cpu cpu;
// Ordered list of IO devices.
private SortedSet<Device> devices;
// Ordered sets of IO devices, associated with their priority
private Map<Integer, SortedSet<Device>> deviceMap;
// an array for quick lookup of adresses, brute-force style
private Device[] deviceAddressArray;
public Bus(int size) {
this(0, size - 1);
}
public Bus(int startAddress, int endAddress) {
this.devices = new TreeSet<Device>();
this.deviceMap = new HashMap<Integer, SortedSet<Device>>();
this.startAddress = startAddress;
this.endAddress = endAddress;
}
@@ -65,7 +74,49 @@ public class Bus {
public int endAddress() {
return endAddress;
}
private void buildDeviceAddressArray() {
int size = (this.endAddress - this.startAddress) + 1;
deviceAddressArray = new Device[size];
// getDevices() provides an OrderedSet with devices ordered by priorities
for(Device device : getDevices()) {
MemoryRange range = device.getMemoryRange();
for(int address = range.startAddress; address <= range.endAddress; ++address) {
deviceAddressArray[address - this.startAddress] = device;
}
}
}
/**
* Add a device to the bus.
*
* @param device
* @param priority
* @throws MemoryRangeException
*/
public void addDevice(Device device, int priority) throws MemoryRangeException {
MemoryRange range = device.getMemoryRange();
if(range.startAddress() < this.startAddress || range.startAddress() > this.endAddress) {
throw new MemoryRangeException("start address of device " + device.getName() + " does not fall within the address range of the bus");
}
if(range.endAddress() < this.startAddress || range.endAddress() > this.endAddress) {
throw new MemoryRangeException("end address of device " + device.getName() + " does not fall within the address range of the bus");
}
SortedSet<Device> deviceSet = deviceMap.get(priority);
if(deviceSet == null) {
deviceSet = new TreeSet<Device>();
deviceMap.put(priority, deviceSet);
}
deviceSet.add(device);
buildDeviceAddressArray();
}
/**
* Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others.
*
@@ -73,21 +124,9 @@ public class Bus {
* @throws MemoryRangeException
*/
public void addDevice(Device device) throws MemoryRangeException {
// Make sure there's no memory overlap.
MemoryRange memRange = device.getMemoryRange();
for (Device d : devices) {
if (d.getMemoryRange().overlaps(memRange)) {
throw new MemoryRangeException("The device being added at " +
String.format("$%04X", memRange.startAddress()) +
" overlaps with an existing " +
"device, '" + d + "'");
}
}
// Add the device
device.setBus(this);
devices.add(device);
addDevice(device, 0);
}
/**
* Remove a device from the bus.
@@ -95,9 +134,10 @@ public class Bus {
* @param device
*/
public void removeDevice(Device device) {
if (devices.contains(device)) {
devices.remove(device);
for(SortedSet<Device> deviceSet : deviceMap.values()) {
deviceSet.remove(device);
}
buildDeviceAddressArray();
}
public void addCpu(Cpu cpu) {
@@ -111,49 +151,81 @@ public class Bus {
* device.
*/
public boolean isComplete() {
// Empty maps cannot be complete.
if (devices.isEmpty()) {
return false;
if(deviceAddressArray == null) {
buildDeviceAddressArray();
}
// Loop over devices and add their size
int filledMemory = 0;
for (Device d : devices) {
filledMemory += d.getSize();
for(int address = startAddress; address <= endAddress; ++address) {
if(deviceAddressArray[address - startAddress] == null) {
return false;
}
}
// Returns if the total size of the devices fill the bus' memory space
return filledMemory == endAddress - startAddress + 1;
return true;
}
public int read(int address) throws MemoryAccessException {
for (Device d : devices) {
Device d = deviceAddressArray[address - this.startAddress];
if(d != null) {
MemoryRange range = d.getMemoryRange();
if (range.includes(address)) {
// Compute offset into this device's address space.
int devAddr = address - range.startAddress();
return d.read(devAddr);
}
int devAddr = address - range.startAddress();
return d.read(devAddr);
}
throw new MemoryAccessException("Bus read failed. No device at address " + String.format("$%04X", address));
}
public void write(int address, int value) throws MemoryAccessException {
for (Device d : devices) {
Device d = deviceAddressArray[address - this.startAddress];
if(d != null) {
MemoryRange range = d.getMemoryRange();
if (range.includes(address)) {
// Compute offset into this device's address space.
int devAddr = address - range.startAddress();
d.write(devAddr, value);
return;
}
int devAddr = address - range.startAddress();
d.write(devAddr, value);
return;
}
throw new MemoryAccessException("Bus write failed. No device at address " + String.format("$%04X", address));
}
public void assertIrq() {
if (cpu != null) {
cpu.assertIrq();
}
}
public void clearIrq() {
if (cpu != null) {
cpu.clearIrq();
}
}
public void assertNmi() {
if (cpu != null) {
cpu.assertNmi();
}
}
public void clearNmi() {
if (cpu != null) {
cpu.clearNmi();
}
}
public SortedSet<Device> getDevices() {
// Expose a copy of the device list, not the original
return new TreeSet<Device>(devices);
// create an ordered set of devices, ordered by device priorities
SortedSet<Device> devices = new TreeSet<Device>();
List<Integer> priorities = new ArrayList<Integer>(deviceMap.keySet());
Collections.sort(priorities);
for(int priority : priorities) {
SortedSet<Device> deviceSet = deviceMap.get(priority);
for(Device device : deviceSet) {
devices.add(device);
}
}
return devices;
}
public Cpu getCpu() {
+85 -17
View File
@@ -25,6 +25,8 @@ package com.loomcom.symon;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.HexUtil;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class provides a simulation of the MOS 6502 CPU's state machine.
@@ -75,6 +77,9 @@ public class Cpu implements InstructionTable {
/* Internal scratch space */
private int lo = 0, hi = 0; // Used in address calculation
private int tmp; // Temporary storage
/* start time of op execution, needed for speed simulation */
private long opBeginTime;
/**
* Construct a new CPU.
@@ -113,7 +118,9 @@ public class Cpu implements InstructionTable {
* Reset the CPU to known initial values.
*/
public void reset() throws MemoryAccessException {
// Registers
/* TODO: In reality, the stack pointer could be anywhere
on the stack after reset. This non-deterministic behavior might be
worth while to simulate. */
state.sp = 0xff;
// Set the PC to the address stored in the reset vector
@@ -131,6 +138,8 @@ public class Cpu implements InstructionTable {
state.overflowFlag = false;
state.negativeFlag = false;
state.irqAsserted = false;
// Clear illegal opcode trap.
state.opTrap = false;
@@ -150,12 +159,21 @@ public class Cpu implements InstructionTable {
}
/**
* Performs an individual machine cycle.
* Performs an individual instruction cycle.
*/
public void step() throws MemoryAccessException {
opBeginTime = System.nanoTime();
// Store the address from which the IR was read, for debugging
state.lastPc = state.pc;
// Check for Interrupts before doing anything else.
// This will set the PC and jump to the interrupt vector.
if (state.nmiAsserted) {
handleNmi();
} else if (state.irqAsserted && !getIrqDisableFlag()) {
handleIrq(state.pc);
}
// Fetch memory location for this instruction.
state.ir = bus.read(state.pc);
irAddressMode = (state.ir >> 2) & 0x07;
@@ -247,16 +265,7 @@ public class Cpu implements InstructionTable {
/** Single Byte Instructions; Implied and Relative **/
case 0x00: // BRK - Force Interrupt - Implied
if (!getIrqDisableFlag()) {
// Set the break flag before pushing.
setBreakFlag();
// Push program counter + 1 onto the stack
stackPush((state.pc + 1 >> 8) & 0xff); // PC high byte
stackPush(state.pc + 1 & 0xff); // PC low byte
stackPush(state.getStatusFlag());
// Set the Interrupt Disabled flag. RTI will clear it.
setIrqDisableFlag();
// Load interrupt vector address into PC
state.pc = address(bus.read(IRQ_VECTOR_L), bus.read(IRQ_VECTOR_H));
handleIrq(state.pc + 1);
}
break;
case 0x08: // PHP - Push Processor Status - Implied
@@ -729,6 +738,35 @@ public class Cpu implements InstructionTable {
delayLoop(state.ir);
}
private void handleIrq(int returnPc) throws MemoryAccessException {
handleInterrupt(returnPc, IRQ_VECTOR_L, IRQ_VECTOR_H);
clearIrq();
}
private void handleNmi() throws MemoryAccessException {
handleInterrupt(state.pc, NMI_VECTOR_L, NMI_VECTOR_H);
clearNmi();
}
/**
* Handle the common behavior of BRK, /IRQ, and /NMI
*
* @throws MemoryAccessException
*/
private void handleInterrupt(int returnPc, int vectorLow, int vectorHigh) throws MemoryAccessException {
// Set the break flag before pushing.
setBreakFlag();
// Push program counter + 1 onto the stack
stackPush((returnPc >> 8) & 0xff); // PC high byte
stackPush(returnPc & 0xff); // PC low byte
stackPush(state.getStatusFlag());
// Set the Interrupt Disabled flag. RTI will clear it.
setIrqDisableFlag();
// Load interrupt vector address into PC
state.pc = address(bus.read(vectorLow), bus.read(vectorHigh));
}
/**
* Add with Carry, used by all addressing mode implementations of ADC.
* As a side effect, this will set the overflow and carry flags as
@@ -1161,6 +1199,34 @@ public class Cpu implements InstructionTable {
return state.getStatusFlag();
}
/**
* Simulate transition from logic-high to logic-low on the INT line.
*/
public void assertIrq() {
state.irqAsserted = true;
}
/**
* Simulate transition from logic-low to logic-high of the INT line.
*/
public void clearIrq() {
state.irqAsserted = false;
}
/**
* Simulate transition from logic-high to logic-low on the NMI line.
*/
public void assertNmi() {
state.nmiAsserted = true;
}
/**
* Simulate transition from logic-low to logic-high of the NMI line.
*/
public void clearNmi() {
state.nmiAsserted = false;
}
/**
* Push an item onto the stack, and decrement the stack counter.
* Will wrap-around if already at the bottom of the stack (This
@@ -1264,11 +1330,10 @@ public class Cpu implements InstructionTable {
if (clockSteps == 0) {
clockSteps = 1;
}
long startTime = System.nanoTime();
long stopTime = startTime + (CLOCK_IN_NS * clockSteps);
// Busy loop
while (System.nanoTime() < stopTime) {
;
long opScheduledEnd = opBeginTime + clockSteps;
long now = System.nanoTime();
while(now < opScheduledEnd) {
now = System.nanoTime();
}
}
@@ -1305,6 +1370,8 @@ public class Cpu implements InstructionTable {
public int[] args = new int[2];
public int instSize;
public boolean opTrap;
public boolean irqAsserted;
public boolean nmiAsserted;
/* Status Flag Register bits */
public boolean carryFlag;
@@ -1341,6 +1408,7 @@ public class Cpu implements InstructionTable {
this.args[1] = s.args[1];
this.instSize = s.instSize;
this.opTrap = s.opTrap;
this.irqAsserted = s.irqAsserted;
this.carryFlag = s.carryFlag;
this.negativeFlag = s.negativeFlag;
this.zeroFlag = s.zeroFlag;
+103
View File
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon;
import com.loomcom.symon.machines.MulticompMachine;
import com.loomcom.symon.machines.SymonMachine;
import java.util.Locale;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class Main {
/**
* Main entry point to the simulator. Creates a simulator and shows the main
* window.
*
* @param args
*/
public static void main(String args[]) throws Exception {
Class machineClass = SymonMachine.class;
for(int i = 0; i < args.length; ++i) {
String arg = args[i].toLowerCase(Locale.ENGLISH);
if(arg.equals("-machine") && (i+1) < args.length) {
String machine = args[i+1].trim().toLowerCase(Locale.ENGLISH);
if(machine.equals("symon")) {
machineClass = SymonMachine.class;
} else if(machine.equals("multicomp")) {
machineClass = MulticompMachine.class;
}
}
}
while(true) {
if(machineClass == null) {
Object[] possibilities = {"Symon", "Multicomp"};
String s = (String)JOptionPane.showInputDialog(
null,
"Please choose the machine type to be emulated:",
"Machine selection",
JOptionPane.PLAIN_MESSAGE,
null,
possibilities,
"Symon");
if(s != null && s.equals("Multicomp")) {
machineClass = MulticompMachine.class;
} else {
machineClass = SymonMachine.class;
}
}
final Simulator simulator = new Simulator(machineClass);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi();
} catch (Exception e) {
e.printStackTrace();
}
}
});
Simulator.MAIN_CMD cmd = simulator.waitForCommand();
if(cmd.equals(Simulator.MAIN_CMD.SELECTMACHINE)) {
machineClass = null;
} else {
break;
}
}
}
}
+103 -126
View File
@@ -23,14 +23,12 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.exceptions.FifoUnderrunException;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import com.loomcom.symon.exceptions.SymonException;
import com.loomcom.symon.machines.Machine;
import com.loomcom.symon.ui.*;
import com.loomcom.symon.ui.Console;
@@ -52,29 +50,6 @@ import java.util.logging.Logger;
*/
public class Simulator {
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
// 32K of RAM from $0000 - $7FFF
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0x8000;
// VIA at $8000-$800F
private static final int VIA_BASE = 0x8000;
// ACIA at $8800-$8803
private static final int ACIA_BASE = 0x8800;
// CRTC at $9000-$9001
private static final int CRTC_BASE = 0x9000;
private static final int VIDEO_RAM_BASE = 0x7000;
// 16KB ROM at $C000-$FFFF
private static final int ROM_BASE = 0xC000;
private static final int ROM_SIZE = 0x4000;
// UI constants
private static final int DEFAULT_FONT_SIZE = 12;
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
@@ -84,23 +59,17 @@ public class Simulator {
// to refresh the status view on every simulated clock cycle. Instead, we will only refresh the status view
// after this number of steps when running normally.
//
// Since we're simulating a 1MHz 6502 here, we have a 1 us delay between steps. Setting this to 10000
// should give us a status update every 10 ms.
// Since we're simulating a 1MHz 6502 here, we have a 1 us delay between steps. Setting this to 20000
// should give us a status update about every 100 ms.
//
// TODO: Work around the event dispatch thread with custom painting code instead of relying on Swing.
//
private static final int MAX_STEPS_BETWEEN_UPDATES = 10000;
private static final int MAX_STEPS_BETWEEN_UPDATES = 20000;
private final static Logger logger = Logger.getLogger(Simulator.class.getName());
// The simulated peripherals
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Via via;
private final Crtc crtc;
private final Memory ram;
private Memory rom;
// The simulated machine
private Machine machine;
// Number of CPU steps between CRT repaints.
// TODO: Dynamically refresh the value at runtime based on performance figures to reach ~ 30fps.
@@ -142,43 +111,25 @@ public class Simulator {
private JButton runStopButton;
private JButton stepButton;
private JButton resetButton;
private JComboBox stepCountBox;
private JComboBox<String> stepCountBox;
private JFileChooser fileChooser;
private PreferencesDialog preferences;
private final Object commandMonitorObject = new Object();
private MAIN_CMD command = MAIN_CMD.NONE;
public static enum MAIN_CMD {
NONE,
SELECTMACHINE
}
/**
* The list of step counts that will appear in the "Step" drop-down.
*/
private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"};
public Simulator() throws MemoryRangeException, IOException {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false);
this.via = new Via(VIA_BASE);
this.acia = new Acia(ACIA_BASE);
this.crtc = new Crtc(CRTC_BASE, ram);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(via);
bus.addDevice(acia);
bus.addDevice(crtc);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file " + romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
} else {
logger.info("Default ROM file " + romImage +
" not found, loading empty R/W memory image.");
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
bus.addDevice(rom);
public Simulator(Class machineClass) throws Exception {
this.machine = (Machine) machineClass.getConstructors()[0].newInstance();
}
/**
@@ -212,7 +163,7 @@ public class Simulator {
stepButton = new JButton("Step");
resetButton = new JButton("Reset");
stepCountBox = new JComboBox(STEPS);
stepCountBox = new JComboBox<String>(STEPS);
stepCountBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
try {
@@ -266,10 +217,12 @@ public class Simulator {
traceLog = new TraceLog();
// Prepare the memory window
memoryWindow = new MemoryWindow(bus);
memoryWindow = new MemoryWindow(machine.getBus());
// Composite Video and 6545 CRTC
videoWindow = new VideoWindow(crtc, 2, 2);
if(machine.getCrtc() != null) {
videoWindow = new VideoWindow(machine.getCrtc(), 2, 2);
}
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@@ -282,7 +235,20 @@ public class Simulator {
mainWindow.setVisible(true);
console.requestFocus();
handleReset();
}
public MAIN_CMD waitForCommand() {
synchronized(commandMonitorObject) {
try {
commandMonitorObject.wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
return command;
}
private void handleStart() {
// Shift focus to the console.
@@ -312,7 +278,7 @@ public class Simulator {
try {
logger.log(Level.INFO, "Reset requested. Resetting CPU.");
// Reset and clear memory
cpu.reset();
machine.getCpu().reset();
// Clear the console.
console.reset();
// Reset the trace log.
@@ -321,7 +287,8 @@ public class Simulator {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(cpu);
statusPane.updateState(machine.getCpu());
memoryWindow.updateState();
}
});
} catch (MemoryAccessException ex) {
@@ -342,7 +309,8 @@ public class Simulator {
if (traceLog.isVisible()) {
traceLog.refresh();
}
statusPane.updateState(cpu);
statusPane.updateState(machine.getCpu());
memoryWindow.updateState();
}
});
} catch (SymonException ex) {
@@ -355,15 +323,15 @@ public class Simulator {
* Perform a single step of the simulated system.
*/
private void step() throws MemoryAccessException {
cpu.step();
machine.getCpu().step();
traceLog.append(cpu.getCpuState());
traceLog.append(machine.getCpu().getCpuState());
// Read from the ACIA and immediately update the console if there's
// output ready.
if (acia.hasTxChar()) {
if (machine.getAcia().hasTxChar()) {
// This is thread-safe
console.print(Character.toString((char) acia.txRead()));
console.print(Character.toString((char) machine.getAcia().txRead()));
console.repaint();
}
@@ -371,14 +339,13 @@ public class Simulator {
// TODO: Interrupt handling.
try {
if (console.hasInput()) {
acia.rxWrite((int) console.readInputChar());
machine.getAcia().rxWrite((int) console.readInputChar());
}
} catch (FifoUnderrunException ex) {
logger.severe("Console type-ahead buffer underrun!");
}
if (stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) {
videoWindow.refreshDisplay();
if (videoWindow != null && stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) {
stepsSinceLastCrtcRefresh = 0;
}
@@ -389,7 +356,8 @@ public class Simulator {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(cpu);
statusPane.updateState(machine.getCpu());
memoryWindow.updateState();
}
});
stepsSinceLastUpdate = 0;
@@ -403,7 +371,7 @@ public class Simulator {
private void loadProgram(byte[] program, int startAddress) throws MemoryAccessException {
int addr = startAddress, i;
for (i = 0; i < program.length; i++) {
bus.write(addr++, program[i] & 0xff);
machine.getBus().write(addr++, program[i] & 0xff);
}
logger.log(Level.INFO, "Loaded " + i + " bytes at address 0x" +
@@ -411,42 +379,21 @@ public class Simulator {
// After loading, be sure to reset and
// Reset (but don't clear memory, naturally)
cpu.reset();
machine.getCpu().reset();
// Reset the stack program counter
cpu.setProgramCounter(preferences.getProgramStartAddress());
machine.getCpu().setProgramCounter(preferences.getProgramStartAddress());
// Immediately update the UI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(cpu);
statusPane.updateState(machine.getCpu());
memoryWindow.updateState();
}
});
}
/**
* Main entry point to the simulator. Creates a simulator and shows the main
* window.
*
* @param args
*/
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
Simulator simulator = new Simulator();
simulator.createAndShowUi();
// Reset the simulator.
simulator.handleReset();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* The main run thread.
@@ -488,7 +435,8 @@ public class Simulator {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
statusPane.updateState(cpu);
statusPane.updateState(machine.getCpu());
memoryWindow.updateState();
runStopButton.setText("Run");
stepButton.setEnabled(true);
stepCountBox.setEnabled(true);
@@ -510,7 +458,7 @@ public class Simulator {
* @return True if the run loop should proceed to the next step.
*/
private boolean shouldContinue() {
return isRunning && !(preferences.getHaltOnBreak() && cpu.getInstruction() == 0x00);
return isRunning && !(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00);
}
}
@@ -530,7 +478,7 @@ public class Simulator {
if (f.canRead()) {
long fileSize = f.length();
if (fileSize > MEMORY_SIZE) {
if (fileSize > machine.getMemorySize()) {
throw new IOException("Program will not fit in available memory.");
} else {
byte[] program = new byte[(int) fileSize];
@@ -577,22 +525,19 @@ public class Simulator {
if (romFile.canRead()) {
long fileSize = romFile.length();
if (fileSize != ROM_SIZE) {
throw new IOException("ROM file must be exactly " + String.valueOf(ROM_SIZE) + " bytes.");
if (fileSize != machine.getRomSize()) {
throw new IOException("ROM file must be exactly " + String.valueOf(machine.getRomSize()) + " bytes.");
} else {
if (rom != null) {
// Unload the existing ROM image.
bus.removeDevice(rom);
}
// Load the new ROM image
rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romFile);
bus.addDevice(rom);
Memory rom = Memory.makeROM(machine.getRomBase(), machine.getRomBase() + machine.getRomSize() - 1, romFile);
machine.setRom(rom);
// Now, reset
cpu.reset();
machine.getCpu().reset();
logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " +
String.format("0x%04X", ROM_BASE));
String.format("0x%04X", machine.getRomBase()));
}
}
}
@@ -617,6 +562,34 @@ public class Simulator {
preferences.getDialog().setVisible(true);
}
}
class SelectMachineAction extends AbstractAction {
Simulator simulator;
public SelectMachineAction() {
super("Switch emulated machine...", null);
putValue(SHORT_DESCRIPTION, "Select the type of the machine to be emulated");
putValue(MNEMONIC_KEY, KeyEvent.VK_M);
}
public void actionPerformed(ActionEvent actionEvent) {
if(runLoop != null) {
runLoop.requestStop();
}
memoryWindow.dispose();
traceLog.dispose();
if(videoWindow != null) {
videoWindow.dispose();
}
mainWindow.dispose();
command = MAIN_CMD.SELECTMACHINE;
synchronized(commandMonitorObject) {
commandMonitorObject.notifyAll();
}
}
}
class QuitAction extends AbstractAction {
public QuitAction() {
@@ -743,11 +716,13 @@ public class Simulator {
loadProgramItem = new JMenuItem(new LoadProgramAction());
loadRomItem = new JMenuItem(new LoadRomAction());
JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction());
JMenuItem selectMachineItem = new JMenuItem(new SelectMachineAction());
JMenuItem quitItem = new JMenuItem(new QuitAction());
fileMenu.add(loadProgramItem);
fileMenu.add(loadRomItem);
fileMenu.add(prefsItem);
fileMenu.add(selectMachineItem);
fileMenu.add(quitItem);
add(fileMenu);
@@ -792,14 +767,16 @@ public class Simulator {
});
viewMenu.add(showMemoryTable);
final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction());
videoWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
showVideoWindow.setSelected(false);
}
});
viewMenu.add(showVideoWindow);
if(videoWindow != null) {
final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction());
videoWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
showVideoWindow.setSelected(false);
}
});
viewMenu.add(showVideoWindow);
}
add(viewMenu);
}
+44 -167
View File
@@ -23,169 +23,47 @@
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the MOS 6551 ACIA, with limited
* functionality. Interrupts are not supported.
* <p/>
* 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.
* Abstract base class for ACIAS such as the 6551 and 6580
*/
public class Acia extends Device {
public static final int ACIA_SIZE = 4;
static final int DATA_REG = 0;
static final int STAT_REG = 1;
static final int CMND_REG = 2;
static final int CTRL_REG = 3;
public abstract class Acia extends Device {
private String name;
/**
* Register addresses
*/
private int baseAddress;
int baseAddress;
/**
* Registers. These are ignored in the current implementation.
*/
private int commandRegister;
private int controlRegister;
private boolean overrun = false;
private long lastTxWrite = 0;
private long lastRxRead = 0;
private int baudRate = 0;
private long baudRateDelay = 0;
/**
boolean receiveIrqEnabled = false;
boolean transmitIrqEnabled = false;
boolean overrun = false;
long lastTxWrite = 0;
long lastRxRead = 0;
int baudRate = 0;
long baudRateDelay = 0;
/**
* Read/Write buffers
*/
private int rxChar = 0;
private int txChar = 0;
int rxChar = 0;
int txChar = 0;
private boolean rxFull = false;
private boolean txEmpty = true;
public Acia(int address) throws MemoryRangeException {
super(address, address + ACIA_SIZE - 1, "ACIA");
boolean rxFull = false;
boolean txEmpty = true;
public Acia(int address, int size, String name) throws MemoryRangeException {
super(address, address + size - 1, name);
this.name = name;
this.baseAddress = address;
}
@Override
public int read(int address) throws MemoryAccessException {
switch (address) {
case DATA_REG:
return rxRead();
case STAT_REG:
return statusReg();
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:
setControlRegister(data);
break;
default:
throw new MemoryAccessException("No register.");
}
}
/**
* Set the control register and associated state.
*
* @param data
*/
public void setControlRegister(int data) {
this.controlRegister = data;
// If the value of the data is 0, this is a request to reset,
// otherwise it's a control update.
if (data == 0) {
reset();
} else {
// Mask the lower three bits to get the baud rate.
int baudSelector = data & 0x0f;
switch (baudSelector) {
case 0:
baudRate = 0;
break;
case 1:
baudRate = 50;
break;
case 2:
baudRate = 75;
break;
case 3:
baudRate = 110; // Real rate is actually 109.92
break;
case 4:
baudRate = 135; // Real rate is actually 134.58
break;
case 5:
baudRate = 150;
break;
case 6:
baudRate = 300;
break;
case 7:
baudRate = 600;
break;
case 8:
baudRate = 1200;
break;
case 9:
baudRate = 1800;
break;
case 10:
baudRate = 2400;
break;
case 11:
baudRate = 3600;
break;
case 12:
baudRate = 4800;
break;
case 13:
baudRate = 7200;
break;
case 14:
baudRate = 9600;
break;
case 15:
baudRate = 19200;
break;
}
// Recalculate the baud rate delay.
baudRateDelay = calculateBaudRateDelay();
}
}
/*
* Calculate the delay in nanoseconds between successive read/write operations, based on the
@@ -215,48 +93,54 @@ public class Acia extends Device {
*/
public void setBaudRate(int rate) {
this.baudRate = rate;
this.baudRateDelay = calculateBaudRateDelay();
}
/**
* @return The contents of the status register.
*/
public int statusReg() {
// TODO: Overrun, Parity Error, Framing Error, DTR, DSR, and Interrupt flags.
int stat = 0;
if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) {
stat |= 0x08;
}
if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) {
stat |= 0x10;
}
return stat;
}
public abstract int statusReg();
@Override
public String toString() {
return "ACIA@" + String.format("%04X", baseAddress);
return name + "@" + String.format("%04X", baseAddress);
}
public synchronized int rxRead() {
lastRxRead = System.nanoTime();
overrun = false;
rxFull = false;
return rxChar;
}
public synchronized void rxWrite(int data) {
if(rxFull) {
overrun = true;
}
rxFull = true;
if (receiveIrqEnabled) {
getBus().assertIrq();
}
rxChar = data;
}
public synchronized int txRead() {
txEmpty = true;
if (transmitIrqEnabled) {
getBus().assertIrq();
}
return txChar;
}
public synchronized void txWrite(int data) {
lastTxWrite = System.nanoTime();
txEmpty = false;
txChar = data;
txEmpty = false;
}
/**
@@ -273,11 +157,4 @@ public class Acia extends Device {
return rxFull;
}
private synchronized void reset() {
txChar = 0;
txEmpty = true;
rxChar = 0;
rxFull = false;
}
}
@@ -0,0 +1,208 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the MOS 6551 ACIA, with limited
* functionality. Interrupts are not supported.
* <p/>
* 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.
*/
public class Acia6551 extends Acia {
public static final int ACIA_SIZE = 4;
static final int DATA_REG = 0;
static final int STAT_REG = 1;
static final int CMND_REG = 2;
static final int CTRL_REG = 3;
/**
* Registers. These are ignored in the current implementation.
*/
private int commandRegister;
private int controlRegister;
public Acia6551(int address) throws MemoryRangeException {
super(address, ACIA_SIZE, "ACIA");
}
@Override
public int read(int address) throws MemoryAccessException {
switch (address) {
case DATA_REG:
return rxRead();
case STAT_REG:
return statusReg();
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:
setCommandRegister(data);
break;
case 3:
setControlRegister(data);
break;
default:
throw new MemoryAccessException("No register.");
}
}
private void setCommandRegister(int data) {
commandRegister = data;
// Bit 1 controls receiver IRQ behavior
receiveIrqEnabled = (commandRegister & 0x02) == 0;
// Bits 2 & 3 controls transmit IRQ behavior
transmitIrqEnabled = (commandRegister & 0x08) == 0 && (commandRegister & 0x04) != 0;
}
/**
* Set the control register and associated state.
*
* @param data
*/
private void setControlRegister(int data) {
controlRegister = data;
int rate = 0;
// If the value of the data is 0, this is a request to reset,
// otherwise it's a control update.
if (data == 0) {
reset();
} else {
// Mask the lower three bits to get the baud rate.
int baudSelector = data & 0x0f;
switch (baudSelector) {
case 0:
rate = 0;
break;
case 1:
rate = 50;
break;
case 2:
rate = 75;
break;
case 3:
rate = 110; // Real rate is actually 109.92
break;
case 4:
rate = 135; // Real rate is actually 134.58
break;
case 5:
rate = 150;
break;
case 6:
rate = 300;
break;
case 7:
rate = 600;
break;
case 8:
rate = 1200;
break;
case 9:
rate = 1800;
break;
case 10:
rate = 2400;
break;
case 11:
rate = 3600;
break;
case 12:
rate = 4800;
break;
case 13:
rate = 7200;
break;
case 14:
rate = 9600;
break;
case 15:
rate = 19200;
break;
}
setBaudRate(rate);
}
}
/**
* @return The contents of the status register.
*/
@Override
public int statusReg() {
// TODO: Parity Error, Framing Error, DTR, DSR, and Interrupt flags.
int stat = 0;
if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) {
stat |= 0x08;
}
if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) {
stat |= 0x10;
}
if (overrun) {
stat |= 0x04;
}
return stat;
}
private synchronized void reset() {
txChar = 0;
txEmpty = true;
rxChar = 0;
rxFull = false;
receiveIrqEnabled = false;
transmitIrqEnabled = false;
}
}
@@ -0,0 +1,133 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the Motorola 6850 ACIA, with limited
* functionality. Interrupts are not supported.
* <p/>
* Unlike a 16550 UART, the 6850 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.
*/
public class Acia6850 extends Acia {
public static final int ACIA_SIZE = 2;
static final int STAT_REG = 0; // read-only
static final int CTRL_REG = 0; // write-only
static final int RX_REG = 1; // read-only
static final int TX_REG = 1; // write-only
/**
* Registers. These are ignored in the current implementation.
*/
private int commandRegister;
public Acia6850(int address) throws MemoryRangeException {
super(address, ACIA_SIZE, "ACIA6850");
setBaudRate(2400);
}
@Override
public int read(int address) throws MemoryAccessException {
switch (address) {
case RX_REG:
return rxRead();
case STAT_REG:
return statusReg();
default:
throw new MemoryAccessException("No register.");
}
}
@Override
public void write(int address, int data) throws MemoryAccessException {
switch (address) {
case TX_REG:
txWrite(data);
break;
case CTRL_REG:
setCommandRegister(data);
break;
default:
throw new MemoryAccessException("No register.");
}
}
private void setCommandRegister(int data) {
commandRegister = data;
// Bits 0 & 1 control the master reset
if((commandRegister & 0x01) != 0 && (commandRegister & 0x02) != 0) {
reset();
}
// Bit 7 controls receiver IRQ behavior
receiveIrqEnabled = (commandRegister & 0x80) != 0;
// Bits 5 & 6 controls transmit IRQ behavior
transmitIrqEnabled = (commandRegister & 0x20) != 0 && (commandRegister & 0x40) == 0;
}
/**
* @return The contents of the status register.
*/
@Override
public int statusReg() {
// TODO: Parity Error, Framing Error, DTR, DSR, and Interrupt flags.
int stat = 0;
if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) {
stat |= 0x01;
}
if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) {
stat |= 0x02;
}
if (overrun) {
stat |= 0x20;
}
return stat;
}
private synchronized void reset() {
overrun = false;
rxFull = false;
txEmpty = true;
}
}
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2008-2014 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.exceptions.MemoryRangeException;
public interface Machine {
public Bus getBus();
public Cpu getCpu();
public Memory getRam();
public Acia getAcia();
public Via getVia();
public Crtc getCrtc();
public Memory getRom();
public void setRom(Memory rom) throws MemoryRangeException;
public int getRomBase();
public int getRomSize();
public int getMemorySize();
}
@@ -0,0 +1,151 @@
/*
* Copyright (c) 2008-2014 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6850;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.File;
import java.util.logging.Logger;
public class MulticompMachine implements Machine {
private final static Logger logger = Logger.getLogger(MulticompMachine.class.getName());
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
// 56K of RAM from $0000 - $DFFF
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0xE000;
// ACIA at $FFD0-$FFD1
private static final int ACIA_BASE = 0xFFD0;
// 8KB ROM at $E000-$FFFF
private static final int ROM_BASE = 0xE000;
private static final int ROM_SIZE = 0x2000;
// The simulated peripherals
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Memory ram;
private Memory rom;
public MulticompMachine() throws Exception {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false);
this.acia = new Acia6850(ACIA_BASE);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(acia, 1);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file " + romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
} else {
logger.info("Default ROM file " + romImage +
" not found, loading empty R/W memory image.");
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
bus.addDevice(rom);
}
@Override
public Bus getBus() {
return bus;
}
@Override
public Cpu getCpu() {
return cpu;
}
@Override
public Memory getRam() {
return ram;
}
@Override
public Acia getAcia() {
return acia;
}
@Override
public Via getVia() {
return null;
}
@Override
public Crtc getCrtc() {
return null;
}
@Override
public Memory getRom() {
return rom;
}
public void setRom(Memory rom) throws MemoryRangeException {
if(this.rom != null) {
bus.removeDevice(this.rom);
}
this.rom = rom;
bus.addDevice(this.rom);
}
@Override
public int getRomBase() {
return ROM_BASE;
}
@Override
public int getRomSize() {
return ROM_SIZE;
}
@Override
public int getMemorySize() {
return MEMORY_SIZE;
}
}
@@ -0,0 +1,167 @@
/*
* Copyright (c) 2008-2014 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6551;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.File;
import java.util.logging.Logger;
public class SymonMachine implements Machine {
private final static Logger logger = Logger.getLogger(SymonMachine.class.getName());
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
// 32K of RAM from $0000 - $7FFF
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0x8000;
// VIA at $8000-$800F
private static final int VIA_BASE = 0x8000;
// ACIA at $8800-$8803
private static final int ACIA_BASE = 0x8800;
// CRTC at $9000-$9001
private static final int CRTC_BASE = 0x9000;
private static final int VIDEO_RAM_BASE = 0x7000;
// 16KB ROM at $C000-$FFFF
private static final int ROM_BASE = 0xC000;
private static final int ROM_SIZE = 0x4000;
// The simulated peripherals
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Via via;
private final Crtc crtc;
private final Memory ram;
private Memory rom;
public SymonMachine() throws Exception {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false);
this.via = new Via(VIA_BASE);
this.acia = new Acia6551(ACIA_BASE);
this.crtc = new Crtc(CRTC_BASE, ram);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(via);
bus.addDevice(acia);
bus.addDevice(crtc);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file " + romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
} else {
logger.info("Default ROM file " + romImage +
" not found, loading empty R/W memory image.");
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
bus.addDevice(rom);
}
@Override
public Bus getBus() {
return bus;
}
@Override
public Cpu getCpu() {
return cpu;
}
@Override
public Memory getRam() {
return ram;
}
@Override
public Acia getAcia() {
return acia;
}
@Override
public Via getVia() {
return via;
}
@Override
public Crtc getCrtc() {
return crtc;
}
@Override
public Memory getRom() {
return rom;
}
public void setRom(Memory rom) throws MemoryRangeException {
if(this.rom != null) {
bus.removeDevice(this.rom);
}
this.rom = rom;
bus.addDevice(this.rom);
}
@Override
public int getRomBase() {
return ROM_BASE;
}
@Override
public int getRomSize() {
return ROM_SIZE;
}
@Override
public int getMemorySize() {
return MEMORY_SIZE;
}
}
@@ -190,14 +190,14 @@ public class MemoryWindow extends JFrame implements ActionListener {
if (currentPage > 0x00) {
setPageNumber(currentPage - 1);
updateControls();
memoryTable.updateUI();
updateState();
}
} else if (e.getSource() == nextPageButton) {
int currentPage = getPageNumber();
if (currentPage < 0xff) {
setPageNumber(currentPage + 1);
updateControls();
memoryTable.updateUI();
updateState();
}
} else if (e.getSource() == pageNumberTextField) {
String pageNumberInput = pageNumberTextField.getText();
@@ -205,7 +205,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
// Try to parse a hex value out of the pageNumber.
int newPageNumber = Integer.parseInt(pageNumberInput, 16);
setPageNumber(newPageNumber & 0xff);
memoryTable.updateUI();
updateState();
} catch (NumberFormatException ex) {
// An invalid number was entered. Log the error, but otherwise
// take no action.
@@ -216,6 +216,13 @@ public class MemoryWindow extends JFrame implements ActionListener {
}
}
/**
* Refresh the view of memory
*/
public void updateState() {
memoryTable.updateUI();
}
/**
* A JTable that will automatically select all text in a cell
* being edited.
+99 -9
View File
@@ -1,23 +1,94 @@
package com.loomcom.symon;
import org.junit.*;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.exceptions.FifoUnderrunException;
import com.loomcom.symon.devices.Acia6551;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
public class AciaTest {
@Test
public void shouldTriggerInterruptOnRxFullIfRxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Enable RX IRQ
acia.write(2, 0x00);
acia.rxWrite('a');
verify(mockBus, atLeastOnce()).assertIrq();
}
@Test
public void shouldNotTriggerInterruptOnRxFullIfRxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(2, 0x02);
acia.rxWrite('a');
verify(mockBus, never()).assertIrq();
}
@Test
public void shouldTriggerInterruptOnTxEmptyIfTxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Enable TX IRQ, Disable RX IRQ
acia.write(2, 0x06);
// Write data
acia.write(0, 'a');
verify(mockBus, never()).assertIrq();
// Transmission should cause IRQ
acia.txRead();
verify(mockBus, atLeastOnce()).assertIrq();
}
@Test
public void shouldNotTriggerInterruptOnTxEmptyIfTxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(2, 0x02);
// Write data
acia.write(0, 'a');
// Transmission should cause IRQ
acia.txRead();
verify(mockBus, never()).assertIrq();
}
@Test
public void newAciaShouldHaveTxEmptyStatus() throws Exception {
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
assertEquals(0x10, acia.read(0x0001));
}
@Test
public void aciaShouldHaveTxEmptyStatusOffIfTxHasData() throws Exception {
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
acia.txWrite('a');
assertEquals(0x00, acia.read(0x0001));
@@ -25,7 +96,7 @@ public class AciaTest {
@Test
public void aciaShouldHaveRxFullStatusOffIfRxHasData() throws Exception {
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
acia.rxWrite('a');
assertEquals(0x18, acia.read(0x0001));
@@ -34,18 +105,37 @@ public class AciaTest {
@Test
public void aciaShouldHaveTxEmptyAndRxFullStatusOffIfRxAndTxHaveData()
throws Exception {
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
acia.rxWrite('a');
acia.txWrite('b');
assertEquals(0x08, acia.read(0x0001));
}
@Test
public void aciaShouldOverrunAndReadShouldReset()
throws Exception {
Acia acia = new Acia6551(0x0000);
// overrun ACIA
acia.rxWrite('a');
acia.rxWrite('b');
assertEquals(0x04, acia.read(0x0001) & 0x04);
// read should reset
acia.rxRead();
assertEquals(0x00, acia.read(0x0001) & 0x04);
}
@Test
public void readingBuffersShouldResetStatus()
throws Exception {
Acia acia = new Acia(0x0000);
Acia acia = new Acia6551(0x0000);
acia.rxWrite('a');
acia.txWrite('b');
@@ -0,0 +1,163 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6850;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
public class AciaTest6850 {
private final static int CMD_STAT_REG = 0;
private final static int DATA_REG = 1;
private Acia newAcia() throws Exception {
Acia acia = new Acia6850(0x0000);
// by default rate is limited, have it unlimited for testing
acia.setBaudRate(0);
return acia;
}
@Test
public void shouldTriggerInterruptOnRxFullIfRxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Disable TX IRQ, Enable RX IRQ
acia.write(CMD_STAT_REG, 0x80);
acia.rxWrite('a');
verify(mockBus, atLeastOnce()).assertIrq();
}
@Test
public void shouldNotTriggerInterruptOnRxFullIfRxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(CMD_STAT_REG, 0x00);
acia.rxWrite('a');
verify(mockBus, never()).assertIrq();
}
@Test
public void shouldTriggerInterruptOnTxEmptyIfTxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Enable TX IRQ, Disable RX IRQ
acia.write(CMD_STAT_REG, 0x20);
// Write data
acia.write(1, 'a');
verify(mockBus, never()).assertIrq();
// Transmission should cause IRQ
acia.txRead();
verify(mockBus, atLeastOnce()).assertIrq();
}
@Test
public void shouldNotTriggerInterruptOnTxEmptyIfTxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(CMD_STAT_REG, 0x02);
// Write data
acia.write(DATA_REG, 'a');
// Transmission should cause IRQ
acia.txRead();
verify(mockBus, never()).assertIrq();
}
@Test
public void newAciaShouldHaveTxEmptyStatus() throws Exception {
Acia acia = newAcia();
assertEquals(0x02, acia.read(CMD_STAT_REG) & 0x02);
}
@Test
public void aciaShouldHaveTxEmptyStatusOffIfTxHasData() throws Exception {
Acia acia = newAcia();
acia.txWrite('a');
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x02);
}
@Test
public void aciaShouldHaveRxFullStatusOnIfRxHasData() throws Exception {
Acia acia = newAcia();
acia.rxWrite('a');
assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x01);
}
@Test
public void aciaShouldHaveTxEmptyAndRxFullStatusOffIfRxAndTxHaveData()
throws Exception {
Acia acia = newAcia();
acia.rxWrite('a');
acia.txWrite('b');
assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x03);
}
@Test
public void aciaShouldOverrunAndReadShouldReset()
throws Exception {
Acia acia = newAcia();
// overrun ACIA
acia.rxWrite('a');
acia.rxWrite('b');
assertEquals(0x20, acia.read(CMD_STAT_REG) & 0x20);
// read should reset
acia.rxRead();
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x20);
}
@Test
public void readingBuffersShouldResetStatus()
throws Exception {
Acia acia = newAcia();
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x01);
acia.rxWrite('a');
assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x01);
acia.rxRead();
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x01);
}
}
+116 -100
View File
@@ -10,113 +10,129 @@ import com.loomcom.symon.exceptions.*;
*/
public class BusTest extends TestCase {
public BusTest(String testName) {
super(testName);
}
public static Test suite() {
return new TestSuite(BusTest.class);
}
public void testCreatingWithStartAndEndAddresses() {
Bus b = null;
b = new Bus(0x00, 0xff);
assertEquals(0x00, b.startAddress());
assertEquals(0xff, b.endAddress());
b = new Bus(0x20, 0xea);
assertEquals(0x20, b.startAddress());
assertEquals(0xea, b.endAddress());
}
public void testCreatingWithSize() {
Bus b = null;
b = new Bus(256);
assertEquals(0x00, b.startAddress());
assertEquals(0xff, b.endAddress());
b = new Bus(4096);
assertEquals(0x000, b.startAddress());
assertEquals(0xfff, b.endAddress());
b = new Bus(65536);
assertEquals(0x0000, b.startAddress());
assertEquals(0xffff, b.endAddress());
}
public void testAddDevice() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x00ff, true);
Device rom = new Memory(0x0100, 0x02ff, false);
Bus b = new Bus(0x0000, 0xffff);
assertEquals(0, b.getDevices().size());
b.addDevice(memory);
assertEquals(1, b.getDevices().size());
b.addDevice(rom);
assertEquals(2, b.getDevices().size());
}
public void testOverlappingDevicesShouldFail() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x0100, true);
Device rom = new Memory(0x00ff, 0x0200, false);
Bus b = new Bus(0x0000, 0xffff);
b.addDevice(memory);
try {
b.addDevice(rom);
fail("Should have thrown a MemoryRangeException.");
} catch (MemoryRangeException ex) {
// expected
public BusTest(String testName) {
super(testName);
}
}
public void testIsCompleteWithFirstDeviceNotStartingAtStartAddress() throws MemoryRangeException {
Device memory = new Memory(0x00ff, 0xff00, true);
public static Test suite() {
return new TestSuite(BusTest.class);
}
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
}
public void testCreatingWithStartAndEndAddresses() {
Bus b = null;
public void testIsCompleteWithOneDevice() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0xffff, true);
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertTrue("Address space should have been complete!", b.isComplete());
}
b = new Bus(0x00, 0xff);
assertEquals(0x00, b.startAddress());
assertEquals(0xff, b.endAddress());
public void testIsCompleteWithTwoDevices() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x7fff, true);
Device rom = new Memory(0x8000, 0xffff, false);
b = new Bus(0x20, 0xea);
assertEquals(0x20, b.startAddress());
assertEquals(0xea, b.endAddress());
}
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom);
assertTrue("Address space should have been complete!", b.isComplete());
}
public void testCreatingWithSize() {
Bus b = null;
public void testIsCompleteWithThreeDevices() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x7fff, true);
Device rom1 = new Memory(0x8000, 0xBfff, false);
Device rom2 = new Memory(0xC000, 0xffff, false);
b = new Bus(256);
assertEquals(0x00, b.startAddress());
assertEquals(0xff, b.endAddress());
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom1);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom2);
assertTrue("Address space should have been complete!", b.isComplete());
}
b = new Bus(4096);
assertEquals(0x000, b.startAddress());
assertEquals(0xfff, b.endAddress());
b = new Bus(65536);
assertEquals(0x0000, b.startAddress());
assertEquals(0xffff, b.endAddress());
}
public void testAddDevice() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x00ff, true);
Device rom = new Memory(0x0100, 0x02ff, false);
Bus b = new Bus(0x0000, 0xffff);
assertEquals(0, b.getDevices().size());
b.addDevice(memory);
assertEquals(1, b.getDevices().size());
b.addDevice(rom);
assertEquals(2, b.getDevices().size());
}
public void testIsCompleteWithFirstDeviceNotStartingAtStartAddress() throws MemoryRangeException {
Device memory = new Memory(0x00ff, 0xff00, true);
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
}
public void testIsCompleteWithOneDevice() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0xffff, true);
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertTrue("Address space should have been complete!", b.isComplete());
}
public void testIsCompleteWithTwoDevices() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x7fff, true);
Device rom = new Memory(0x8000, 0xffff, false);
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom);
assertTrue("Address space should have been complete!", b.isComplete());
}
public void testIsCompleteWithThreeDevices() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x7fff, true);
Device rom1 = new Memory(0x8000, 0xBfff, false);
Device rom2 = new Memory(0xC000, 0xffff, false);
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom1);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom2);
assertTrue("Address space should have been complete!", b.isComplete());
}
public void testSetAndClearIrq() throws Exception {
Bus b = new Bus(0x0000, 0xffff);
Cpu c = new Cpu();
b.addCpu(c);
assertFalse(c.getCpuState().irqAsserted);
b.assertIrq();
assertTrue(c.getCpuState().irqAsserted);
b.clearIrq();
assertFalse(c.getCpuState().irqAsserted);
}
public void testSetAndClearNmi() throws Exception {
Bus b = new Bus(0x0000, 0xffff);
Cpu c = new Cpu();
b.addCpu(c);
assertFalse(c.getCpuState().nmiAsserted);
b.assertNmi();
assertTrue(c.getCpuState().nmiAsserted);
b.clearNmi();
assertFalse(c.getCpuState().nmiAsserted);
}
}
@@ -14,7 +14,7 @@ public class CpuAbsoluteModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuAbsoluteXModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuAbsoluteYModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -7,303 +7,303 @@ import junit.framework.*;
public class CpuAccumulatorModeTest extends TestCase {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
bus.addCpu(cpu);
bus.addDevice(mem);
public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00)>>>8);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
cpu.reset();
// Assert initial state
assertEquals(0, cpu.getAccumulator());
assertEquals(0, cpu.getXRegister());
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
}
// Assert initial state
assertEquals(0, cpu.getAccumulator());
assertEquals(0, cpu.getXRegister());
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
}
/*
* The following opcodes are tested for correctness in this file:
*
* ASL - $0a
* ROL - $2a
* LSR - $4a
* ROR - $6a
*/
/*
* The following opcodes are tested for correctness in this file:
*
* ASL - $0a
* ROL - $2a
* LSR - $4a
* ROR - $6a
*/
/* ASL - Arithmetic Shift Left - $0a */
public void test_ASL() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00
0x0a, // ASL A
public void test_ASL() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00
0x0a, // ASL A
0xa9, 0x01, // LDA #$01
0x0a, // ASL A
0xa9, 0x01, // LDA #$01
0x0a, // ASL A
0xa9, 0x02, // LDA #$02
0x0a, // ASL A
0xa9, 0x02, // LDA #$02
0x0a, // ASL A
0xa9, 0x44, // LDA #$44
0x0a, // ASL A
0xa9, 0x44, // LDA #$44
0x0a, // ASL A
0xa9, 0x80, // LDA #$80
0x0a); // ASL A
0xa9, 0x80, // LDA #$80
0x0a); // ASL A
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x02, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x02, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x04, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x04, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x88, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x88, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
}
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
}
/* ROL - Rotate Left - $2a */
public void test_ROL() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00
0x2a, // ROL A (m=%00000000, c=0)
0xa9, 0x01, // LDA #$01
0x2a, // ROL A (m=%00000010, c=0)
0x38, // SEC (m=%00000010, c=1)
0x2a, // ROL A (m=%00000101, c=0)
0x2a, // ROL A (m=%00001010, c=0)
0x2a, // ROL A (m=%00010100, c=0)
0x2a, // ROL A (m=%00101000, c=0)
0x2a, // ROL A (m=%01010000, c=0)
0x2a, // ROL A (m=%10100000, c=0)
0x2a, // ROL A (m=%01000000, c=1)
0x2a); // ROL A (m=%10000001, c=0)
public void test_ROL() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00
0x2a, // ROL A (m=%00000000, c=0)
0xa9, 0x01, // LDA #$01
0x2a, // ROL A (m=%00000010, c=0)
0x38, // SEC (m=%00000010, c=1)
0x2a, // ROL A (m=%00000101, c=0)
0x2a, // ROL A (m=%00001010, c=0)
0x2a, // ROL A (m=%00010100, c=0)
0x2a, // ROL A (m=%00101000, c=0)
0x2a, // ROL A (m=%01010000, c=0)
0x2a, // ROL A (m=%10100000, c=0)
0x2a, // ROL A (m=%01000000, c=1)
0x2a); // ROL A (m=%10000001, c=0)
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x02, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x02, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x05, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x05, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x0a, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x0a, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x14, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x14, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x28, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x28, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x50, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x50, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0xa0, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0xa0, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x81, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
}
cpu.step();
assertEquals(0x81, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
}
/* LSR - Logical Shift Right - $4a */
public void test_LSR() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00
0x4a, // LSR A
public void test_LSR() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00
0x4a, // LSR A
0xa9, 0x01, // LDA #$01
0x4a, // LSR A
0xa9, 0x01, // LDA #$01
0x4a, // LSR A
0xa9, 0x02, // LDA #$02
0x4a, // LSR A
0xa9, 0x02, // LDA #$02
0x4a, // LSR A
0xa9, 0x44, // LDA #$44
0x4a, // LSR A
0xa9, 0x44, // LDA #$44
0x4a, // LSR A
0xa9, 0x80, // LDA #$80
0x4a, // LSR A
0xa9, 0x80, // LDA #$80
0x4a, // LSR A
0x38, // SEC
0xa9, 0x02, // LDA #$02
0x4a); // LSR $05
0x38, // SEC
0xa9, 0x02, // LDA #$02
0x4a); // LSR $05
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x01, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x01, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x22, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x22, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x40, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x40, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
// Setting Carry should not affect the result.
cpu.step(3);
assertEquals(0x01, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
}
// Setting Carry should not affect the result.
cpu.step(3);
assertEquals(0x01, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
}
/* ROR - Rotate Right - $6a */
public void test_ROR() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00
0x6a, // ROR A (m=%00000000, c=0)
0xa9, 0x10, // LDA #$10
0x6a, // ROR A (m=%00001000, c=0)
0x6a, // ROR A (m=%00000100, c=0)
0x6a, // ROR A (m=%00000010, c=0)
0x6a, // ROR A (m=%00000001, c=0)
0x6a, // ROR A (m=%00000000, c=1)
0x6a, // ROR A (m=%10000000, c=0)
0x6a, // ROR A (m=%01000000, c=0)
0x6a, // ROR A (m=%00100000, c=0)
0x6a); // ROR A (m=%00010000, c=0)
public void test_ROR() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00
0x6a, // ROR A (m=%00000000, c=0)
0xa9, 0x10, // LDA #$10
0x6a, // ROR A (m=%00001000, c=0)
0x6a, // ROR A (m=%00000100, c=0)
0x6a, // ROR A (m=%00000010, c=0)
0x6a, // ROR A (m=%00000001, c=0)
0x6a, // ROR A (m=%00000000, c=1)
0x6a, // ROR A (m=%10000000, c=0)
0x6a, // ROR A (m=%01000000, c=0)
0x6a, // ROR A (m=%00100000, c=0)
0x6a); // ROR A (m=%00010000, c=0)
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x08, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x08, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x04, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x04, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x01, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x01, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x80, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x80, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x20, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x20, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x10, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
}
cpu.step();
assertEquals(0x10, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
}
}
@@ -14,7 +14,7 @@ public class CpuImmediateModeTest extends TestCase {
public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -17,7 +17,7 @@ public class CpuImpliedModeTest {
public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -16,7 +16,7 @@ public class CpuIndexedIndirectModeTest {
public void runBeforeEveryTest() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -16,7 +16,7 @@ public class CpuIndirectIndexedModeTest {
public void runBeforeEveryTest() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuIndirectModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -13,7 +13,7 @@ public class CpuIndirectXModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuRelativeModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
+192 -1
View File
@@ -25,7 +25,7 @@ public class CpuTest extends TestCase {
public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -345,6 +345,197 @@ public class CpuTest extends TestCase {
assertFalse(cpu.getNegativeFlag());
}
public void testIrq() throws Exception {
// Ensure the IRQ disable flag is cleared
cpu.clearIrqDisableFlag();
// Set the IRQ vector
bus.write(0xffff, 0x12);
bus.write(0xfffe, 0x34);
// Create an IRQ handler at 0x1234
cpu.setProgramCounter(0x1234);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
cpu.setProgramCounter(0x0200);
// Create a little program at 0x0200
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertIrq();
cpu.step();
assertTrue(cpu.getIrqDisableFlag()); // Should have been set by the IRQ
assertEquals(0x1236, cpu.getProgramCounter());
assertEquals(0x33, cpu.getAccumulator());
// Be sure that the IRQ line is no longer held low
assertFalse(cpu.getCpuState().irqAsserted);
}
public void testIrqHonorsIrqDisabledFlag() throws Exception {
// Ensure the IRQ disable flag is set
cpu.setIrqDisableFlag();
// Set the IRQ vector
bus.write(0xffff, 0x12);
bus.write(0xfffe, 0x34);
// Create an IRQ handler at 0x1234
cpu.setProgramCounter(0x1234);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
cpu.setProgramCounter(0x0200);
// Create a little program at 0x0200
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertIrq(); // Should be ignored, because the disable flag is set.
cpu.step();
assertTrue(cpu.getIrqDisableFlag()); // Should have been left alone.
assertEquals(0x0205, cpu.getProgramCounter());
assertEquals(0x02, cpu.getAccumulator());
}
public void testIrqPushesCorrectReturnAddressOntoStack() throws Exception {
// Ensure the IRQs are enabled
cpu.clearIrqDisableFlag();
// Set the IRQ vector
bus.write(0xffff, 0x10);
bus.write(0xfffe, 0x00);
// Create an IRQ handler at 0x1000 that just RTIs.
cpu.setProgramCounter(0x1000);
bus.loadProgram(0xea, 0xea, 0x40); // NOP, NOP, RTI
cpu.setProgramCounter(0x0200);
// Create a little program at 0x0200 with three instruction sizes.
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$01
0x6d, 0x06, 0x02, // ADC $0207
0x00, // BRK
0x03); // $03 (data @ $0206)
cpu.step(); // CLC
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.assertIrq();
cpu.step(); // NOP
assertEquals(0x1001, cpu.getProgramCounter());
cpu.step(); // NOP
assertEquals(0x1002, cpu.getProgramCounter());
cpu.step(); // RTI: PC -> 0x0201
assertEquals(0x0201, cpu.getProgramCounter());
cpu.step(); // LDA $#01
assertEquals(0x0203, cpu.getProgramCounter());
cpu.assertIrq();
cpu.step(); // NOP
assertEquals(0x1001, cpu.getProgramCounter());
cpu.step(); // NOP
assertEquals(0x1002, cpu.getProgramCounter());
cpu.step(); // RTI: PC -> 0x0203
assertEquals(0x0203, cpu.getProgramCounter());
}
public void testNmi() throws Exception {
// Set the NMI vector to 0x1000
bus.write(0xfffb, 0x10);
bus.write(0xfffa, 0x00);
// Create an NMI handler at 0x1000
cpu.setProgramCounter(0x1000);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
// Create a little program at 0x0200
cpu.setProgramCounter(0x0200);
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertNmi();
cpu.step();
assertTrue(cpu.getIrqDisableFlag()); // Should have been set by the NMI
assertEquals(0x1002, cpu.getProgramCounter());
assertEquals(0x33, cpu.getAccumulator());
// Be sure that the NMI line is no longer held low
assertFalse(cpu.getCpuState().nmiAsserted);
}
public void testNmiIgnoresIrqDisableFlag() throws Exception {
// Set the IRQ disable flag, which should be ignored by the NMI
cpu.setIrqDisableFlag();
// Set the NMI vector to 0x1000
bus.write(0xfffb, 0x10);
bus.write(0xfffa, 0x00);
// Create an NMI handler at 0x1000
cpu.setProgramCounter(0x1000);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
// Create a little program at 0x0200
cpu.setProgramCounter(0x0200);
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertNmi();
cpu.step();
assertTrue(cpu.getIrqDisableFlag()); // Should have been set by the NMI
assertEquals(0x1002, cpu.getProgramCounter());
assertEquals(0x33, cpu.getAccumulator());
// Be sure that the NMI line is no longer held low
assertFalse(cpu.getCpuState().nmiAsserted);
}
public void testAddress() {
assertEquals(0xf1ea, cpu.address(0xea, 0xf1));
assertEquals(0x00ea, cpu.address(0xea, 0x00));
@@ -14,7 +14,7 @@ public class CpuZeroPageModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuZeroPageXModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuZeroPageYModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);