Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac5907691f | ||
|
|
fce0dad2a9 | ||
|
|
0c40cd325c | ||
|
|
22a9207dca | ||
|
|
7c2a007928 | ||
|
|
194a13d1ac | ||
|
|
66646116cb | ||
|
|
59c6d8e23b | ||
|
|
36615fc70b | ||
|
|
1a1d503abe | ||
|
|
eac387e472 | ||
|
|
bb82db6672 | ||
|
|
f675a6e462 | ||
|
|
ccae8905b3 | ||
|
|
6b9295e72d | ||
|
|
004087a742 | ||
|
|
7edbb12f68 | ||
|
|
4bfc196b49 | ||
|
|
b1e1e75555 | ||
|
|
da3015defd | ||
|
|
87b5b77dbf | ||
|
|
2dc0078deb | ||
|
|
ac21b2c126 | ||
|
|
4bb6d55fa2 | ||
|
|
f90516e53c | ||
|
|
fdaeb661d3 | ||
|
|
827e9991d5 | ||
|
|
dc7dfc4b4d | ||
|
|
2217f3831d | ||
|
|
52f4e9a00f | ||
|
|
d7f8045b61 | ||
|
|
222772648d | ||
|
|
bd2337ae65 | ||
|
|
a49c0d40d8 | ||
|
|
44bfbc17ab | ||
|
|
b266a02372 | ||
|
|
72074b9715 | ||
|
|
252ebe0569 | ||
|
|
4a37dd7463 | ||
|
|
cf36f731da | ||
|
|
e01f68a19a | ||
|
|
44151f3a9c | ||
|
|
f5a25b7303 | ||
|
|
14e8ea596c | ||
|
|
8f52e1da1e | ||
|
|
0d0fddc365 | ||
|
|
31fdd87b41 | ||
|
|
9726907317 | ||
|
|
40c5397ed8 | ||
|
|
a7ea60d250 | ||
|
|
1bc8eda38d | ||
|
|
76dcbd712a | ||
|
|
b6cc480919 | ||
|
|
0c5035fc56 | ||
|
|
3d36cd5460 |
No files matched your search
@@ -1,17 +1,12 @@
|
||||
SYMON - A 6502 System Simulator
|
||||
===============================
|
||||
|
||||
**NOTE: THIS SOFTWARE IS UNDER ACTIVE DEVELOPMENT. Feedback is welcome!**
|
||||
**Version:** 1.0.0
|
||||
|
||||
**Version:** 0.9.0.1
|
||||
|
||||
**Last Updated:** 29 December, 2013
|
||||
|
||||
Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||
**Last Updated:** 11 August, 2014
|
||||
|
||||
See the file COPYING for license.
|
||||
|
||||
|
||||
![Symon Simulator in Action] (https://github.com/sethm/symon/raw/master/screenshots/full.jpg)
|
||||
|
||||
## 1.0 About
|
||||
@@ -22,30 +17,51 @@ 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
|
||||
(See [this thread on the 6502.org Forums] (http://forum.6502.org/viewtopic.php?f=2&t=2241)
|
||||
for more information about this functional test suite).
|
||||
|
||||
Symon is under constant, active development. Feedback and patches
|
||||
are always welcome.
|
||||
|
||||
## 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 simulate multiple 6502 based architectures. At present, three
|
||||
machines are implemented: Symon (the default), MULTICOMP, and a "Simple"
|
||||
machine useful for debugging.
|
||||
|
||||
### 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.1.3 Simple Memory Map
|
||||
|
||||
- `$0000`--`$FFFF`: 64KB RAM
|
||||
|
||||
### 3.2 Serial Console and CPU Status
|
||||
|
||||
@@ -53,10 +69,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 +81,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 +172,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 +207,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 +219,19 @@ running.
|
||||
|
||||
## 5.0 Revision History
|
||||
|
||||
- **1.0.0-SNAPSHOT:** 10 August, 2014 - Added "Simple" machine
|
||||
implementation, pure RAM with no IO. Added Klaus Dormann's
|
||||
6502 Functional Tests for further machine verification (these
|
||||
tests must be run in the "Simple" machine).
|
||||
|
||||
- **0.9.9.1:** 27 July, 2014 - Pressing 'Control' while clicking
|
||||
'Reset' now performs a memory clear.
|
||||
|
||||
- **0.9.9:** 26 July, 2014 - MULTICOMP and multi-machine support
|
||||
contributed by Maik Merten <maikmerten@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 +268,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,14 +279,23 @@ running.
|
||||
- Allow displaying ACIA status and dumping ACIA buffers, for
|
||||
debugging.
|
||||
|
||||
## 7.0 Acknowledgements
|
||||
- Symbolic debugging.
|
||||
|
||||
## 7.0 Copyright and Acknowledgements
|
||||
|
||||
**Copyright (c) 2014 Seth J. Morabito <web@loomcom.com>**
|
||||
|
||||
Portions Copyright (c) 2014 Maik Merten <maikmerten@googlemail.com>
|
||||
|
||||
Additional components used in this project are copyright their respective owners.
|
||||
|
||||
- Enhanced 6502 BASIC Copyright (c) Lee Davison
|
||||
- 6502 Functional Tests Copyright (c) Klaus Dormann
|
||||
- JTerminal Copyright (c) Graham Edgecombe
|
||||
|
||||
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),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<groupId>com.loomcom.symon</groupId>
|
||||
<artifactId>symon</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.9.0.1</version>
|
||||
<version>1.0.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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
*.rom
|
||||
*.lst
|
||||
*.map
|
||||
*.o
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 73 KiB |
@@ -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,50 @@ 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);
|
||||
}
|
||||
|
||||
device.setBus(this);
|
||||
deviceSet.add(device);
|
||||
buildDeviceAddressArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others.
|
||||
*
|
||||
@@ -73,21 +125,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 +135,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 +152,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() {
|
||||
|
||||
@@ -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.
|
||||
@@ -53,10 +55,6 @@ public class Cpu implements InstructionTable {
|
||||
public static final int IRQ_VECTOR_L = 0xfffe;
|
||||
public static final int IRQ_VECTOR_H = 0xffff;
|
||||
|
||||
// The delay in microseconds between steps.
|
||||
// TODO: Make configurable
|
||||
private static final int CLOCK_IN_NS = 1000;
|
||||
|
||||
/* Simulated behavior */
|
||||
private static CpuBehavior behavior;
|
||||
|
||||
@@ -75,6 +73,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 +114,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 +134,8 @@ public class Cpu implements InstructionTable {
|
||||
state.overflowFlag = false;
|
||||
state.negativeFlag = false;
|
||||
|
||||
state.irqAsserted = false;
|
||||
|
||||
// Clear illegal opcode trap.
|
||||
state.opTrap = false;
|
||||
|
||||
@@ -150,12 +155,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 +261,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 +734,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
|
||||
@@ -834,7 +868,7 @@ public class Cpu implements InstructionTable {
|
||||
*/
|
||||
private int lsr(int m) {
|
||||
setCarryFlag((m & 0x01) != 0);
|
||||
return (m >>> 1) & 0xff;
|
||||
return (m & 0xff) >>> 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1161,6 +1195,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 +1326,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 +1366,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 +1404,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;
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.SimpleMachine;
|
||||
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;
|
||||
} else if (machine.equals("simple")) {
|
||||
machineClass = SimpleMachine.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (machineClass == null) {
|
||||
Object[] possibilities = {"Symon", "Multicomp", "Simple"};
|
||||
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 if (s != null && s.equals("Simple")) {
|
||||
machineClass = SimpleMachine.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -141,44 +110,27 @@ public class Simulator {
|
||||
|
||||
private JButton runStopButton;
|
||||
private JButton stepButton;
|
||||
private JButton resetButton;
|
||||
private JComboBox stepCountBox;
|
||||
private JButton softResetButton;
|
||||
private JButton hardResetButton;
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,13 +138,13 @@ public class Simulator {
|
||||
*/
|
||||
public void createAndShowUi() throws IOException {
|
||||
mainWindow = new JFrame();
|
||||
mainWindow.setTitle("Symon 6502 Simulator");
|
||||
mainWindow.setTitle("6502 Simulator - " + machine.getName());
|
||||
mainWindow.setResizable(false);
|
||||
mainWindow.getContentPane().setLayout(new BorderLayout());
|
||||
|
||||
// UI components used for I/O.
|
||||
this.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT);
|
||||
this.statusPane = new StatusPanel();
|
||||
this.statusPane = new StatusPanel(machine);
|
||||
|
||||
console.setBorderWidth(CONSOLE_BORDER_WIDTH);
|
||||
|
||||
@@ -210,9 +162,10 @@ public class Simulator {
|
||||
|
||||
runStopButton = new JButton("Run");
|
||||
stepButton = new JButton("Step");
|
||||
resetButton = new JButton("Reset");
|
||||
softResetButton = new JButton("Soft Reset");
|
||||
hardResetButton = new JButton("Hard Reset");
|
||||
|
||||
stepCountBox = new JComboBox(STEPS);
|
||||
stepCountBox = new JComboBox<String>(STEPS);
|
||||
stepCountBox.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
try {
|
||||
@@ -228,7 +181,8 @@ public class Simulator {
|
||||
buttonContainer.add(runStopButton);
|
||||
buttonContainer.add(stepButton);
|
||||
buttonContainer.add(stepCountBox);
|
||||
buttonContainer.add(resetButton);
|
||||
buttonContainer.add(softResetButton);
|
||||
buttonContainer.add(hardResetButton);
|
||||
|
||||
// Left side - console
|
||||
consoleContainer.add(console, BorderLayout.CENTER);
|
||||
@@ -256,9 +210,17 @@ public class Simulator {
|
||||
}
|
||||
});
|
||||
|
||||
resetButton.addActionListener(new ActionListener() {
|
||||
softResetButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
handleReset();
|
||||
// If this was a CTRL-click, do a hard reset.
|
||||
handleReset(false);
|
||||
}
|
||||
});
|
||||
|
||||
hardResetButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
// If this was a CTRL-click, do a hard reset.
|
||||
handleReset(true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -266,10 +228,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 +246,20 @@ public class Simulator {
|
||||
mainWindow.setVisible(true);
|
||||
|
||||
console.requestFocus();
|
||||
handleReset(false);
|
||||
}
|
||||
|
||||
public MAIN_CMD waitForCommand() {
|
||||
synchronized(commandMonitorObject) {
|
||||
try {
|
||||
commandMonitorObject.wait();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
|
||||
private void handleStart() {
|
||||
// Shift focus to the console.
|
||||
@@ -302,7 +279,7 @@ public class Simulator {
|
||||
/*
|
||||
* Perform a reset.
|
||||
*/
|
||||
private void handleReset() {
|
||||
private void handleReset(boolean isColdReset) {
|
||||
if (runLoop != null && runLoop.isRunning()) {
|
||||
runLoop.requestStop();
|
||||
runLoop.interrupt();
|
||||
@@ -311,17 +288,25 @@ public class Simulator {
|
||||
|
||||
try {
|
||||
logger.log(Level.INFO, "Reset requested. Resetting CPU.");
|
||||
// Reset and clear memory
|
||||
cpu.reset();
|
||||
// Reset CPU
|
||||
machine.getCpu().reset();
|
||||
// Clear the console.
|
||||
console.reset();
|
||||
// Reset the trace log.
|
||||
traceLog.reset();
|
||||
// If we're doing a cold reset, clear the memory.
|
||||
if (isColdReset) {
|
||||
Memory mem = machine.getRam();
|
||||
if (mem != null) {
|
||||
mem.fill(0);
|
||||
}
|
||||
}
|
||||
// Update status.
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
// Now update the state
|
||||
statusPane.updateState(cpu);
|
||||
statusPane.updateState();
|
||||
memoryWindow.updateState();
|
||||
}
|
||||
});
|
||||
} catch (MemoryAccessException ex) {
|
||||
@@ -342,7 +327,8 @@ public class Simulator {
|
||||
if (traceLog.isVisible()) {
|
||||
traceLog.refresh();
|
||||
}
|
||||
statusPane.updateState(cpu);
|
||||
statusPane.updateState();
|
||||
memoryWindow.updateState();
|
||||
}
|
||||
});
|
||||
} catch (SymonException ex) {
|
||||
@@ -355,30 +341,28 @@ 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() != null && machine.getAcia().hasTxChar()) {
|
||||
// This is thread-safe
|
||||
console.print(Character.toString((char) acia.txRead()));
|
||||
console.print(Character.toString((char) machine.getAcia().txRead()));
|
||||
console.repaint();
|
||||
}
|
||||
|
||||
// If a key has been pressed, fill the ACIA.
|
||||
// TODO: Interrupt handling.
|
||||
try {
|
||||
if (console.hasInput()) {
|
||||
acia.rxWrite((int) console.readInputChar());
|
||||
if (machine.getAcia() != null && console.hasInput()) {
|
||||
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 +373,8 @@ public class Simulator {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
// Now update the state
|
||||
statusPane.updateState(cpu);
|
||||
statusPane.updateState();
|
||||
memoryWindow.updateState();
|
||||
}
|
||||
});
|
||||
stepsSinceLastUpdate = 0;
|
||||
@@ -403,7 +388,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 +396,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();
|
||||
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 +452,8 @@ public class Simulator {
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
statusPane.updateState(cpu);
|
||||
statusPane.updateState();
|
||||
memoryWindow.updateState();
|
||||
runStopButton.setText("Run");
|
||||
stepButton.setEnabled(true);
|
||||
stepCountBox.setEnabled(true);
|
||||
@@ -497,7 +462,6 @@ public class Simulator {
|
||||
}
|
||||
menuBar.simulatorDidStop();
|
||||
traceLog.simulatorDidStop();
|
||||
// TODO: Update memory window, if frame is visible.
|
||||
}
|
||||
});
|
||||
|
||||
@@ -510,7 +474,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,7 +486,6 @@ public class Simulator {
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
// TODO: Error dialogs on failure.
|
||||
try {
|
||||
int retVal = fileChooser.showOpenDialog(mainWindow);
|
||||
if (retVal == JFileChooser.APPROVE_OPTION) {
|
||||
@@ -530,8 +493,11 @@ public class Simulator {
|
||||
if (f.canRead()) {
|
||||
long fileSize = f.length();
|
||||
|
||||
if (fileSize > MEMORY_SIZE) {
|
||||
throw new IOException("Program will not fit in available memory.");
|
||||
if (fileSize > machine.getMemorySize()) {
|
||||
throw new IOException("File will not fit in " +
|
||||
"available memory ($" +
|
||||
Integer.toString(machine.getMemorySize(), 16) +
|
||||
" bytes)");
|
||||
} else {
|
||||
byte[] program = new byte[(int) fileSize];
|
||||
int i = 0;
|
||||
@@ -550,13 +516,21 @@ public class Simulator {
|
||||
|
||||
// Now load the program at the starting address.
|
||||
loadProgram(program, preferences.getProgramStartAddress());
|
||||
// TODO: "Don't Show Again" checkbox
|
||||
JOptionPane.showMessageDialog(mainWindow,
|
||||
"Loaded Successfully At " +
|
||||
String.format("$%04X", preferences.getProgramStartAddress()),
|
||||
"OK",
|
||||
JOptionPane.PLAIN_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to read program file: " + ex.getMessage());
|
||||
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
|
||||
} catch (MemoryAccessException ex) {
|
||||
logger.log(Level.SEVERE, "Memory access error loading program: " + ex.getMessage());
|
||||
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -569,7 +543,6 @@ public class Simulator {
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
// TODO: Error dialogs on failure.
|
||||
try {
|
||||
int retVal = fileChooser.showOpenDialog(mainWindow);
|
||||
if (retVal == JFileChooser.APPROVE_OPTION) {
|
||||
@@ -577,31 +550,37 @@ 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()));
|
||||
// TODO: "Don't Show Again" checkbox
|
||||
JOptionPane.showMessageDialog(mainWindow,
|
||||
"Loaded Successfully At " +
|
||||
String.format("$%04X", machine.getRomBase()),
|
||||
"OK",
|
||||
JOptionPane.PLAIN_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to read ROM file: " + ex.getMessage());
|
||||
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
|
||||
} catch (MemoryRangeException ex) {
|
||||
logger.log(Level.SEVERE, "Memory range error while loading ROM file: " + ex.getMessage());
|
||||
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
|
||||
} catch (MemoryAccessException ex) {
|
||||
logger.log(Level.SEVERE, "Memory access error while loading ROM file: " + ex.getMessage());
|
||||
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -617,6 +596,32 @@ public class Simulator {
|
||||
preferences.getDialog().setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
class SelectMachineAction extends AbstractAction {
|
||||
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() {
|
||||
@@ -722,7 +727,9 @@ public class Simulator {
|
||||
*/
|
||||
public void simulatorDidStart() {
|
||||
loadProgramItem.setEnabled(false);
|
||||
loadRomItem.setEnabled(false);
|
||||
if (loadRomItem != null) {
|
||||
loadRomItem.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -730,7 +737,9 @@ public class Simulator {
|
||||
*/
|
||||
public void simulatorDidStop() {
|
||||
loadProgramItem.setEnabled(true);
|
||||
loadRomItem.setEnabled(true);
|
||||
if (loadRomItem != null) {
|
||||
loadRomItem.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void initMenu() {
|
||||
@@ -741,13 +750,22 @@ public class Simulator {
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
|
||||
loadProgramItem = new JMenuItem(new LoadProgramAction());
|
||||
loadRomItem = new JMenuItem(new LoadRomAction());
|
||||
JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction());
|
||||
JMenuItem quitItem = new JMenuItem(new QuitAction());
|
||||
|
||||
fileMenu.add(loadProgramItem);
|
||||
fileMenu.add(loadRomItem);
|
||||
|
||||
// Simple Machine does not implement a ROM, so it makes no sense to
|
||||
// offer a ROM load option.
|
||||
if (machine.getRom() != null) {
|
||||
loadRomItem = new JMenuItem(new LoadRomAction());
|
||||
fileMenu.add(loadRomItem);
|
||||
}
|
||||
|
||||
JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction());
|
||||
fileMenu.add(prefsItem);
|
||||
|
||||
JMenuItem selectMachineItem = new JMenuItem(new SelectMachineAction());
|
||||
fileMenu.add(selectMachineItem);
|
||||
|
||||
JMenuItem quitItem = new JMenuItem(new QuitAction());
|
||||
fileMenu.add(quitItem);
|
||||
|
||||
add(fileMenu);
|
||||
@@ -792,14 +810,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);
|
||||
}
|
||||
|
||||
@@ -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,61 @@
|
||||
/*
|
||||
* 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();
|
||||
|
||||
String getName();
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Multicomp";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* A SimpleMachine is the simplest 6502 implementation possible - it
|
||||
* consists solely of RAM and a CPU. This machine is primarily useful
|
||||
* for running 6502 functional tests or debugging by hand.
|
||||
*/
|
||||
public class SimpleMachine implements Machine {
|
||||
|
||||
private static final int BUS_BOTTOM = 0x0000;
|
||||
private static final int BUS_TOP = 0xffff;
|
||||
|
||||
private final Bus bus;
|
||||
private final Memory ram;
|
||||
private final Cpu cpu;
|
||||
|
||||
public SimpleMachine() throws MemoryRangeException {
|
||||
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
||||
this.ram = new Memory(BUS_BOTTOM, BUS_TOP, false);
|
||||
this.cpu = new Cpu();
|
||||
|
||||
bus.addCpu(cpu);
|
||||
bus.addDevice(ram);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bus getBus() {
|
||||
return bus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cpu getCpu() {
|
||||
return cpu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Memory getRam() {
|
||||
return ram;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Acia getAcia() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Via getVia() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Crtc getCrtc() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Memory getRom() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRom(Memory rom) throws MemoryRangeException {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRomBase() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRomSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMemorySize() {
|
||||
return BUS_TOP + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Simple";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Symon";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -24,11 +24,17 @@
|
||||
package com.loomcom.symon.ui;
|
||||
|
||||
import com.loomcom.symon.Cpu;
|
||||
import com.loomcom.symon.machines.Machine;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.EtchedBorder;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* UI component that displays the current state of the simulated CPU.
|
||||
@@ -73,14 +79,17 @@ public class StatusPanel extends JPanel {
|
||||
private JLabel xLabel;
|
||||
private JLabel yLabel;
|
||||
|
||||
private Machine machine;
|
||||
|
||||
private static final int EMPTY_BORDER = 10;
|
||||
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
|
||||
private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 12);
|
||||
private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22);
|
||||
private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
|
||||
|
||||
public StatusPanel() {
|
||||
public StatusPanel(Machine machine) {
|
||||
super();
|
||||
this.machine = machine;
|
||||
createUi();
|
||||
}
|
||||
|
||||
@@ -99,20 +108,20 @@ public class StatusPanel extends JPanel {
|
||||
JPanel statusFlagsPanel = new JPanel();
|
||||
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
carryOn = new ImageIcon(this.getClass().getResource("images/C_on.png"));
|
||||
carryOff = new ImageIcon(this.getClass().getResource("images/C_off.png"));
|
||||
zeroOn = new ImageIcon(this.getClass().getResource("images/Z_on.png"));
|
||||
zeroOff = new ImageIcon(this.getClass().getResource("images/Z_off.png"));
|
||||
irqOn = new ImageIcon(this.getClass().getResource("images/I_on.png"));
|
||||
irqOff = new ImageIcon(this.getClass().getResource("images/I_off.png"));
|
||||
decimalOn = new ImageIcon(this.getClass().getResource("images/D_on.png"));
|
||||
decimalOff = new ImageIcon(this.getClass().getResource("images/D_off.png"));
|
||||
breakOn = new ImageIcon(this.getClass().getResource("images/B_on.png"));
|
||||
breakOff = new ImageIcon(this.getClass().getResource("images/B_off.png"));
|
||||
overflowOn = new ImageIcon(this.getClass().getResource("images/O_on.png"));
|
||||
overflowOff = new ImageIcon(this.getClass().getResource("images/O_off.png"));
|
||||
negativeOn = new ImageIcon(this.getClass().getResource("images/N_on.png"));
|
||||
negativeOff = new ImageIcon(this.getClass().getResource("images/N_off.png"));
|
||||
carryOn = new ImageIcon(this.getClass().getResource("/C_on.png"));
|
||||
carryOff = new ImageIcon(this.getClass().getResource("/C_off.png"));
|
||||
zeroOn = new ImageIcon(this.getClass().getResource("/Z_on.png"));
|
||||
zeroOff = new ImageIcon(this.getClass().getResource("/Z_off.png"));
|
||||
irqOn = new ImageIcon(this.getClass().getResource("/I_on.png"));
|
||||
irqOff = new ImageIcon(this.getClass().getResource("/I_off.png"));
|
||||
decimalOn = new ImageIcon(this.getClass().getResource("/D_on.png"));
|
||||
decimalOff = new ImageIcon(this.getClass().getResource("/D_off.png"));
|
||||
breakOn = new ImageIcon(this.getClass().getResource("/B_on.png"));
|
||||
breakOff = new ImageIcon(this.getClass().getResource("/B_off.png"));
|
||||
overflowOn = new ImageIcon(this.getClass().getResource("/O_on.png"));
|
||||
overflowOff = new ImageIcon(this.getClass().getResource("/O_off.png"));
|
||||
negativeOn = new ImageIcon(this.getClass().getResource("/N_on.png"));
|
||||
negativeOff = new ImageIcon(this.getClass().getResource("/N_off.png"));
|
||||
|
||||
// Initialize all to off
|
||||
carryFlagLabel = new JLabel(carryOff, JLabel.CENTER);
|
||||
@@ -157,12 +166,83 @@ public class StatusPanel extends JPanel {
|
||||
pcLabel.setToolTipText("Program Counter");
|
||||
spLabel.setToolTipText("Stack Pointer");
|
||||
|
||||
opcodeField = makeTextField(LARGE_TEXT_FIELD_SIZE);
|
||||
pcField = makeTextField(LARGE_TEXT_FIELD_SIZE);
|
||||
spField = makeTextField(SMALL_TEXT_FIELD_SIZE);
|
||||
aField = makeTextField(SMALL_TEXT_FIELD_SIZE);
|
||||
xField = makeTextField(SMALL_TEXT_FIELD_SIZE);
|
||||
yField = makeTextField(SMALL_TEXT_FIELD_SIZE);
|
||||
opcodeField = makeTextField(LARGE_TEXT_FIELD_SIZE, false);
|
||||
pcField = makeTextField(LARGE_TEXT_FIELD_SIZE, true);
|
||||
spField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
|
||||
aField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
|
||||
xField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
|
||||
yField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
|
||||
|
||||
// Make fields editable
|
||||
pcField.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
int newVal = getHexVal(pcField) & 0xffff;
|
||||
machine.getCpu().setProgramCounter(newVal);
|
||||
} catch (Exception ex) {
|
||||
// Swallow exception
|
||||
}
|
||||
|
||||
updateState();
|
||||
}
|
||||
});
|
||||
|
||||
spField.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
int newVal = getHexVal(spField) & 0xff;
|
||||
machine.getCpu().setStackPointer(newVal);
|
||||
} catch (Exception ex) {
|
||||
// Swallow exception
|
||||
}
|
||||
|
||||
updateState();
|
||||
}
|
||||
});
|
||||
|
||||
aField.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
int newVal = getHexVal(aField) & 0xff;
|
||||
machine.getCpu().setAccumulator(newVal);
|
||||
} catch (Exception ex) {
|
||||
// Swallow exception
|
||||
}
|
||||
|
||||
updateState();
|
||||
}
|
||||
});
|
||||
|
||||
xField.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
int newVal = getHexVal(xField) & 0xff;
|
||||
machine.getCpu().setXRegister(newVal);
|
||||
} catch (Exception ex) {
|
||||
// Swallow exception
|
||||
}
|
||||
|
||||
updateState();
|
||||
}
|
||||
});
|
||||
|
||||
yField.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
int newVal = getHexVal(yField) & 0xff;
|
||||
machine.getCpu().setYRegister(newVal);
|
||||
} catch (Exception ex) {
|
||||
// Swallow exception
|
||||
}
|
||||
|
||||
updateState();
|
||||
}
|
||||
});
|
||||
|
||||
constraints.anchor = GridBagConstraints.LINE_START;
|
||||
constraints.gridwidth = 2;
|
||||
@@ -220,14 +300,13 @@ public class StatusPanel extends JPanel {
|
||||
|
||||
/**
|
||||
* Update the display based on the current state of the CPU.
|
||||
*
|
||||
* @param cpu The simulated 6502 CPU.
|
||||
*/
|
||||
public void updateState(Cpu cpu) {
|
||||
public void updateState() {
|
||||
Cpu cpu = machine.getCpu();
|
||||
Cpu.CpuState cpuState = cpu.getCpuState();
|
||||
|
||||
// Update the Processor Status Flag display
|
||||
int status = cpu.getCpuState().getStatusFlag();
|
||||
int status = cpuState.getStatusFlag();
|
||||
|
||||
carryFlagLabel.setIcon(iconForFlag(status, 0));
|
||||
zeroFlagLabel.setIcon(iconForFlag(status, 1));
|
||||
@@ -313,10 +392,10 @@ public class StatusPanel extends JPanel {
|
||||
return label;
|
||||
}
|
||||
|
||||
private JTextField makeTextField(Dimension size) {
|
||||
private JTextField makeTextField(Dimension size, boolean editable) {
|
||||
JTextField textField = new JTextField("");
|
||||
textField.setAlignmentX(LEFT_ALIGNMENT);
|
||||
textField.setEditable(false);
|
||||
textField.setEditable(editable);
|
||||
textField.setMinimumSize(size);
|
||||
textField.setMaximumSize(size);
|
||||
textField.setPreferredSize(size);
|
||||
@@ -324,4 +403,8 @@ public class StatusPanel extends JPanel {
|
||||
return textField;
|
||||
}
|
||||
|
||||
private int getHexVal(JTextField source) throws NumberFormatException {
|
||||
String val = source.getText().replaceAll("[^0-9a-fA-F]", "");
|
||||
return Integer.parseInt(val, 16);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 191 B |
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 191 B |
|
Before Width: | Height: | Size: 196 B After Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 196 B After Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 192 B |
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 192 B |
|
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 183 B |
|
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 183 B |
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 192 B |
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 192 B |
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
@@ -392,4 +583,23 @@ public class CpuTest extends TestCase {
|
||||
cpu.setYRegister(0x95);
|
||||
assertEquals(0x15, cpu.zpyAddress(0x80));
|
||||
}
|
||||
|
||||
// Test for GitHub symon issue #9, "LSR can yield wrong result"
|
||||
public void testRightShiftMasksBitsCorrectly() throws Exception {
|
||||
// Illegal value, the accumulator should only care about the low 8 bytes.
|
||||
// I'm a little uncomfortable with this test because really, setAccumulator should
|
||||
// defensively mask the value, but does not. Is this relying on a bug to test another bug?
|
||||
cpu.setAccumulator(0xff8);
|
||||
// Sanity check, in case I ever change my mind on setAccumulator's behavior
|
||||
assertEquals(0xff8, cpu.getAccumulator());
|
||||
|
||||
bus.loadProgram(0x4a, // LSR
|
||||
0x4a); // LSR
|
||||
|
||||
cpu.step();
|
||||
assertEquals(0x7C, cpu.getAccumulator());
|
||||
|
||||
cpu.step();
|
||||
assertEquals(0x3E, cpu.getAccumulator());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||