Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b5976be8f | ||
|
|
3530e9e99a | ||
|
|
7daeb9e978 | ||
|
|
da2750f4ee | ||
|
|
df88c54f90 | ||
|
|
69e1985ec1 | ||
|
|
6267d1d777 | ||
|
|
84e5c5ad56 | ||
|
|
6e8fd40014 | ||
|
|
a4a110dcef | ||
|
|
ae5c5d48b2 | ||
|
|
72ba068beb | ||
|
|
142cb470a0 | ||
|
|
cad3673bd2 | ||
|
|
4796b65390 | ||
|
|
e0d6017d95 | ||
|
|
d6ea21bae4 | ||
|
|
af12039686 | ||
|
|
ed1d5f999b | ||
|
|
c595ff5370 | ||
|
|
e9f662cc17 | ||
|
|
70ab2a6fc2 | ||
|
|
f35fbce38b | ||
|
|
eeb246ebc2 | ||
|
|
e157217f50 | ||
|
|
cc12e8f70a | ||
|
|
a9a1c1aa52 | ||
|
|
742e3a1027 | ||
|
|
45f596e0d4 | ||
|
|
4402a28e2b | ||
|
|
693d1959ac | ||
|
|
792366fddb | ||
|
|
b2670aedf1 | ||
|
|
5d488bbcd2 | ||
|
|
e2a1144c7c | ||
|
|
ac5907691f | ||
|
|
fce0dad2a9 | ||
|
|
0c40cd325c | ||
|
|
22a9207dca | ||
|
|
010c42753f | ||
|
|
7c2a007928 | ||
|
|
194a13d1ac | ||
|
|
66646116cb | ||
|
|
59c6d8e23b | ||
|
|
36615fc70b | ||
|
|
1a1d503abe | ||
|
|
eac387e472 | ||
|
|
bb82db6672 | ||
|
|
f675a6e462 | ||
|
|
ccae8905b3 | ||
|
|
7a215736fe | ||
|
|
c61a63d5bb | ||
|
|
7cb4e1c945 | ||
|
|
3a40d35fdf | ||
|
|
d658cd0ae5 | ||
|
|
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 |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2008-2013 Seth J. Morabito <web@loomcom.com>
|
||||
Copyright (c) 2008-2014 Seth J. Morabito <web@loomcom.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
SYMON - A 6502 System Simulator
|
||||
===============================
|
||||
|
||||
**NOTE: THIS SOFTWARE IS UNDER ACTIVE DEVELOPMENT. Feedback is welcome!**
|
||||
**Version:** 1.1.1
|
||||
|
||||
**Version:** 0.9.1.0
|
||||
|
||||
**Last Updated:** 29 December, 2013
|
||||
|
||||
Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||
**Last Updated:** 2 January, 2016
|
||||
|
||||
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,52 @@ 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.8 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
|
||||
- `$FFD8`--`$FFDF`: Controller for SD cards
|
||||
|
||||
### 3.1.3 Simple Memory Map
|
||||
|
||||
- `$0000`--`$FFFF`: 64KB RAM
|
||||
|
||||
### 3.2 Serial Console and CPU Status
|
||||
|
||||
@@ -53,10 +70,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 +82,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
|
||||
|
||||
@@ -84,19 +103,32 @@ Memory contents can be viewed (and edited) one page at a time through the Memory
|
||||
|
||||
![Trace Log] (https://github.com/sethm/symon/raw/master/screenshots/trace_log.png)
|
||||
|
||||
The last 20,000 execution steps are disassembled and logged to the Trace Log Window.
|
||||
The last 20,000 execution steps are disassembled and logged to the Trace Log
|
||||
Window.
|
||||
|
||||
### 3.6 NEW - Experimental 6545 CRTC Video
|
||||
### 3.6 Simulator Speeds
|
||||
|
||||
![Speeds] (https://github.com/sethm/symon/raw/master/screenshots/simulator_menu.png)
|
||||
|
||||
Simulated speeds may be set from 1MHz to 8MHz.
|
||||
|
||||
### 3.7 Breakpoints
|
||||
|
||||
![Breakpoints] (https://github.com/sethm/symon/raw/master/screenshots/breakpoints.png)
|
||||
|
||||
Breakpoints can be set and removed through the Breakpoints window.
|
||||
|
||||
### 3.8 Experimental 6545 CRTC Video
|
||||
|
||||
![Composite Video] (https://github.com/sethm/symon/raw/master/screenshots/video_window.png)
|
||||
|
||||
This feature is highly experimental. It's possible to open a video window from the "View" menu.
|
||||
This window simulates the output of a MOS 6545 CRT Controller located at address `$9000` and
|
||||
`$9001`.
|
||||
This feature is highly experimental. It's possible to open a video window
|
||||
from the "View" menu. This window simulates the output of a MOS 6545 CRT
|
||||
Controller located at address `$9000` and `$9001`.
|
||||
|
||||
By default, the 40 x 25 character display uses video memory located at base address `$7000`.
|
||||
This means that the memory from address `$7000` (28672 decimal) to `$73E8` (29672 decimal)
|
||||
is directly mapped to video.
|
||||
By default, the 40 x 25 character display uses video memory located at base
|
||||
address `$7000`. This means that the memory from address `$7000` (28672
|
||||
decimal) to `$73E8` (29672 decimal) is directly mapped to video.
|
||||
|
||||
- Address Register (at address `$9000`)
|
||||
- R1: Horizontal Displayed Columns
|
||||
@@ -109,18 +141,20 @@ is directly mapped to video.
|
||||
- R14: Cursor Position (High Byte)
|
||||
- R15: Cursor Position (Low Byte)
|
||||
|
||||
Although the simulation is pretty good, there are a few key differences between
|
||||
the simulated 6545 and a real 6545:
|
||||
Although the simulation is pretty good, there are a few key differences
|
||||
between the simulated 6545 and a real 6545:
|
||||
|
||||
- The simulated 6545 supports only the straight binary addressing mode of the real 6545,
|
||||
and not the Row/Column addressing mode.
|
||||
- The simulated 6545 has full 16 bit addressing, where the real 6545 has only
|
||||
a 14-bit address bus.
|
||||
- The simulation is done at a whole-frame level, meaning that lots of
|
||||
6545 programming tricks that were achieved by updating the frame address
|
||||
during vertical and horizontal sync times are not achievable. There is no way
|
||||
(for example) to change the Display Start Address (R12 and R13) while a
|
||||
frame is being drawn.
|
||||
- The simulated 6545 supports only the straight binary addressing
|
||||
mode of the real 6545, and not the Row/Column addressing mode.
|
||||
|
||||
- The simulated 6545 has full 16 bit addressing, where the real 6545
|
||||
has only a 14-bit address bus.
|
||||
|
||||
- The simulation is done at a whole-frame level, meaning that lots
|
||||
of 6545 programming tricks that were achieved by updating the
|
||||
frame address during vertical and horizontal sync times are not
|
||||
achievable. There is no way (for example) to change the Display Start
|
||||
Address (R12 and R13) while a frame is being drawn.
|
||||
|
||||
For more information on the 6545 CRTC and its programming model, please see the following resources
|
||||
|
||||
@@ -129,7 +163,7 @@ For more information on the 6545 CRTC and its programming model, please see the
|
||||
- [MOS 6545 Datasheet (PDF)] (http://www.6502.org/users/andre/hwinfo/crtc/crtc.html)
|
||||
|
||||
|
||||
#### 3.6.1 Example BASIC Program to test Video
|
||||
#### 3.8.1 Example BASIC Program to test Video
|
||||
|
||||
This program will fill the video screen with all printable characters.
|
||||
|
||||
@@ -152,26 +186,31 @@ Maven will build Symon, run unit tests, and produce a jar file in the
|
||||
`target` directory containing the compiled simulator.
|
||||
|
||||
Symon is meant to be invoked directly from the jar file. To run with
|
||||
Java 1.5 or greater, just type:
|
||||
Java 1.8 or greater, just type:
|
||||
|
||||
$ java -jar symon-0.9.1.0.jar
|
||||
$ java -jar symon-1.1.1.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 +223,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 +235,26 @@ running.
|
||||
|
||||
## 5.0 Revision History
|
||||
|
||||
- **1.1.1:** 2 January, 2016 - Minor enhancement: Allows breakpoints
|
||||
to be added with the Enter key.
|
||||
|
||||
- **1.1.0:** 31 December, 2015 - Fixed delay loop to better
|
||||
simulate various clock speeds. Added ability to select clock
|
||||
speed at runtime. Status display now shows the next instruction
|
||||
to be executed, instead of the last instruction executed.
|
||||
Added support for breakpoints.
|
||||
|
||||
- **1.0.0:** 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.
|
||||
@@ -234,8 +293,6 @@ running.
|
||||
- Better debugging tools from the UI, including breakpoints
|
||||
and disassembly.
|
||||
|
||||
- More accurate timing.
|
||||
|
||||
- UI needs a ton more polish.
|
||||
|
||||
- More extensive testing.
|
||||
@@ -247,14 +304,27 @@ running.
|
||||
- Allow displaying ACIA status and dumping ACIA buffers, for
|
||||
debugging.
|
||||
|
||||
## 7.0 Acknowledgements
|
||||
- CRTC emulation is very naive. The whole frame is drawn in one
|
||||
CPU step. This should be improved by drawing scan lines during
|
||||
machine steps to approximate real NTSC/PAL refresh rates.
|
||||
|
||||
- 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.1.0</version>
|
||||
<version>1.1.1</version>
|
||||
<name>symon</name>
|
||||
<url>http://www.loomcom.com/symon</url>
|
||||
<properties>
|
||||
@@ -35,6 +35,11 @@
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
@@ -56,31 +61,10 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${basedir}/target/classes/com/loomcom/symon/ui/images</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/java/com/loomcom/symon/ui/images</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
@@ -88,7 +72,7 @@
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.loomcom.symon.Simulator</mainClass>
|
||||
<mainClass>com.loomcom.symon.Main</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
@@ -107,8 +91,8 @@
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<compilerArgument>-Xlint:unchecked</compilerArgument>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -116,7 +100,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
|
||||
@@ -150,7 +150,7 @@ END_CODE
|
||||
; sign on string
|
||||
|
||||
LAB_mess
|
||||
.byte $0D,$0A,"Symon (c) 2008-2013, Seth Morabito"
|
||||
.byte $0D,$0A,"Symon (c) 2008-2014, Seth Morabito"
|
||||
.byte $0D,$0A,"Enhanced 6502 BASIC 2.22 (c) Lee Davison"
|
||||
.byte $0D,$0A,"[C]old/[W]arm ?",$00
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
*.rom
|
||||
*.lst
|
||||
*.map
|
||||
*.o
|
||||
@@ -0,0 +1,13 @@
|
||||
CA=ca65
|
||||
LD=ld65
|
||||
|
||||
all: ehbasic
|
||||
|
||||
ehbasic: ehbasic.o
|
||||
$(LD) -C symon.config -vm -m ehbasic.map -o ehbasic.rom ehbasic.o
|
||||
|
||||
ehbasic.o:
|
||||
$(CA) --listing -o ehbasic.o min_mon.asm
|
||||
|
||||
clean:
|
||||
rm -f *.o *.rom *.map *.lst
|
||||
@@ -0,0 +1,85 @@
|
||||
This directoy contains a very slightly modified version of EhBASIC 2.22
|
||||
to support the Symon simulator.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The pre-assemled ROM image 'ehbasic.rom' in the "samples" directory is all you
|
||||
need unless you want to rebuild this source code.
|
||||
|
||||
Just copy the image 'ehbasic.rom' to the directory where you run Symon, and
|
||||
rename the file to 'rom.bin'. It will be loaded at memory location $D000 when
|
||||
the simulator starts up. Click "Run" and you'll be presented with BASIC.
|
||||
|
||||
At the first prompt, type 'C' for a Cold Start
|
||||
|
||||
When prompted for free memory, type: $C000
|
||||
|
||||
Note that EhBASIC only accepts upper case input, so you'll need to use caps
|
||||
lock (the cruise control for cool) to really make the most of it.
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
|
||||
To build it, you'll need the CC65 tool chain from http://www.cc65.org/
|
||||
and a suitable version of 'make'. Just typing:
|
||||
|
||||
% make
|
||||
|
||||
in this directory should re-build everything. You'll get a listing,
|
||||
some object files, and the ROM image itself.
|
||||
|
||||
|
||||
Changes from the EhBASIC 2.22
|
||||
-----------------------------
|
||||
|
||||
- Minor syntax changes to allow assembly with the cc65 tool chain.
|
||||
|
||||
- Memory map modified for Symon.
|
||||
|
||||
- At reset, configure the 6551 ACIA for 8-N-1, 2400 baud.
|
||||
|
||||
- Monitor routines 'ACIAin' and 'ACIAout' modified for the 6551 ACIA.
|
||||
Specifically, reading and writing will check the 6551 status register for
|
||||
the status of rx and tx registers before receive or transmit.
|
||||
|
||||
EhBASIC is copyright Lee Davison <leeedavison@googlemail.com>
|
||||
|
||||
My changes are so slight that I hesitate to even say this, but for "CYA"
|
||||
reasons:
|
||||
|
||||
Changes are copyright Seth Morabito <web@loomcom.com> and are distributed under
|
||||
the same license as EhBASIC. I claim no commercial interest whatsoever. Any
|
||||
commercial use must be negotiated directly with Lee Davison.
|
||||
|
||||
|
||||
Original EhBASIC 2.22 README
|
||||
----------------------------
|
||||
|
||||
Enhanced BASIC is a BASIC interpreter for the 6502 family microprocessors. It
|
||||
is constructed to be quick and powerful and easily ported between 6502 systems.
|
||||
It requires few resources to run and includes instructions to facilitate easy
|
||||
low level handling of hardware devices. It also retains most of the powerful
|
||||
high level instructions from similar BASICs.
|
||||
|
||||
EhBASIC is free but not copyright free. For non commercial use there is only one
|
||||
restriction, any derivative work should include, in any binary image distributed,
|
||||
the string "Derived from EhBASIC" and in any distribution that includes human
|
||||
readable files a file that includes the above string in a human readable form
|
||||
e.g. not as a comment in an HTML file.
|
||||
|
||||
For commercial use please contact me, Lee Davison, at leeedavison@googlemail.com
|
||||
for conditions.
|
||||
|
||||
For more information on EhBASIC, other versions of EhBASIC and other projects
|
||||
please visit my site at ..
|
||||
|
||||
http://mycorner.no-ip.org/index.html
|
||||
|
||||
|
||||
P.S. c't magazin, henceforth refered to as "those thieving german bastards", are
|
||||
prohibited from using this or any version of EhBASIC for any of their projects
|
||||
or products. The excuse "we don't charge people for it" doesn't wash, it adds
|
||||
value to your product so you owe me.
|
||||
@@ -0,0 +1,449 @@
|
||||
|
||||
; minimal monitor for EhBASIC and 6502 simulator V1.05
|
||||
|
||||
; To run EhBASIC on the simulator load and assemble [F7] this file, start the simulator
|
||||
; running [F6] then start the code with the RESET [CTRL][SHIFT]R. Just selecting RUN
|
||||
; will do nothing, you'll still have to do a reset to run the code.
|
||||
|
||||
.feature labels_without_colons
|
||||
.include "basic.asm"
|
||||
|
||||
; put the IRQ and MNI code in RAM so that it can be changed
|
||||
|
||||
IRQ_vec = VEC_SV+2 ; IRQ code vector
|
||||
NMI_vec = IRQ_vec+$0A ; NMI code vector
|
||||
|
||||
; setup for the 6502 simulator environment
|
||||
|
||||
IO_AREA = $8800
|
||||
ACIAdata = IO_AREA ; simulated ACIA r/w port
|
||||
ACIAstatus = IO_AREA+1
|
||||
ACIAcommand = IO_AREA+2
|
||||
ACIAcontrol = IO_AREA+3
|
||||
|
||||
CRTC_A = $9000 ; CRTC register select
|
||||
CRTC_C = $9001 ; CRTC register IO
|
||||
CADDR_L = $E0 ; Cursor address (low)
|
||||
CADDR_H = $E1 ; Cursor address (high)
|
||||
C_COL = $E2 ; Cursor column
|
||||
C_ROW = $E3 ; Cursor row
|
||||
COUTC = $E4 ; Temp storage for char out.
|
||||
R_STRT = $E5 ; E5,E6 contains starting address of
|
||||
; current row
|
||||
R_NXT = $E7 ; E7,E8 contains starting address of
|
||||
; next row
|
||||
|
||||
N_ROWS = 24
|
||||
N_COLS = 40
|
||||
|
||||
|
||||
; now the code. all this does is set up the vectors and interrupt code
|
||||
; and wait for the user to select [C]old or [W]arm start. nothing else
|
||||
; fits in less than 128 bytes
|
||||
|
||||
.segment "MONITOR"
|
||||
.org $FC00 ; pretend this is in a 1/8K ROM
|
||||
|
||||
; reset vector points here
|
||||
|
||||
RES_vec:
|
||||
CLD ; clear decimal mode
|
||||
LDX #$FF ; empty stack
|
||||
TXS ; set the stack
|
||||
|
||||
;; Initialize the CRTC
|
||||
LDA #$01
|
||||
STA CRTC_A
|
||||
LDA #N_COLS ; 40 columns
|
||||
STA CRTC_C
|
||||
LDA #$06
|
||||
STA CRTC_A
|
||||
LDA #N_ROWS ; 24 rows
|
||||
STA CRTC_C
|
||||
LDA #$70
|
||||
STA CADDR_H
|
||||
LDA #$00
|
||||
STA CADDR_L
|
||||
STA C_ROW
|
||||
STA C_COL
|
||||
JSR CLRVID
|
||||
;; TODO: Initialize params on CRTC
|
||||
|
||||
; Initialize the ACIA
|
||||
ACIA_init:
|
||||
LDA #$00
|
||||
STA ACIAstatus ; Soft reset
|
||||
LDA #$0B
|
||||
STA ACIAcommand ; Parity disabled, IRQ disabled
|
||||
LDA #$1E
|
||||
STA ACIAcontrol ; Set output for 8-N-1 9600
|
||||
|
||||
; set up vectors and interrupt code, copy them to page 2
|
||||
|
||||
LDY #END_CODE-LAB_vec ; set index/count
|
||||
LAB_stlp:
|
||||
LDA LAB_vec-1,Y ; get byte from interrupt code
|
||||
STA VEC_IN-1,Y ; save to RAM
|
||||
DEY ; decrement index/count
|
||||
BNE LAB_stlp ; loop if more to do
|
||||
|
||||
; now do the signon message, Y = $00 here
|
||||
|
||||
LAB_signon:
|
||||
LDA LAB_mess,Y ; get byte from sign on message
|
||||
BEQ LAB_nokey ; exit loop if done
|
||||
|
||||
JSR V_OUTP ; output character
|
||||
INY ; increment index
|
||||
BNE LAB_signon ; loop, branch always
|
||||
|
||||
LAB_nokey:
|
||||
JSR V_INPT ; call scan input device
|
||||
BCC LAB_nokey ; loop if no key
|
||||
|
||||
AND #$DF ; mask xx0x xxxx, ensure upper case
|
||||
CMP #'W' ; compare with [W]arm start
|
||||
BEQ LAB_dowarm ; branch if [W]arm start
|
||||
|
||||
CMP #'C' ; compare with [C]old start
|
||||
BNE RES_vec ; loop if not [C]old start
|
||||
|
||||
JMP LAB_COLD ; do EhBASIC cold start
|
||||
|
||||
LAB_dowarm:
|
||||
JMP LAB_WARM ; do EhBASIC warm start
|
||||
|
||||
|
||||
ACIAout:
|
||||
|
||||
PHA
|
||||
@loop: LDA ACIAstatus
|
||||
AND #$10
|
||||
BEQ @loop ; Wait for buffer to empty
|
||||
PLA
|
||||
STA ACIAdata
|
||||
RTS
|
||||
|
||||
;;; Byte out to the CRTC
|
||||
;;;
|
||||
;;; 1. Increment cursor position.
|
||||
;;; 2. Scroll if necessary.
|
||||
;;; 3. Store new cursor position in CRTC.
|
||||
|
||||
CRTCout:
|
||||
JSR ACIAout ; Also echo to terminal for debugging.
|
||||
|
||||
;; In parallel, we're maintaining two states:
|
||||
;; - The address of the cursor, and
|
||||
;; - The Column/Row of the cursor.
|
||||
;;
|
||||
;; The latter state is used to handle scrolling and
|
||||
;; for knowing how far to back up the cursor
|
||||
;; for a carriage-return.
|
||||
|
||||
;; Backspace
|
||||
CMP #$08
|
||||
BEQ DO_BS
|
||||
;; Line Feed
|
||||
CMP #$0a
|
||||
BEQ DO_LF
|
||||
;; Carriage Return
|
||||
CMP #$0d
|
||||
BEQ DO_CR
|
||||
;; Any other character...
|
||||
STA COUTC ; Store the character going out
|
||||
JSR COUT1 ; Output it where the cursor is.
|
||||
;; We might need to scroll...
|
||||
LDA C_ROW
|
||||
CMP #N_ROWS-1
|
||||
BNE @noscr
|
||||
LDA C_COL
|
||||
CMP #N_COLS-1
|
||||
BNE @noscr
|
||||
;; ... yup, we DO need to scroll.
|
||||
JSR DO_SCROLL
|
||||
RTS
|
||||
@noscr: JSR INC_CADDR
|
||||
INC C_COL
|
||||
JSR SET_CURSOR
|
||||
RTS
|
||||
|
||||
;; Handle a back-space character.
|
||||
DO_BS: LDA C_COL
|
||||
BNE @l1 ; Skip next bit
|
||||
LDA #N_COLS-1
|
||||
STA C_COL
|
||||
BNE @l2
|
||||
@l1: DEC C_COL
|
||||
@l2: JSR DEC_CADDR ; Back up
|
||||
JSR SET_CURSOR
|
||||
|
||||
@l3: LDA #$20
|
||||
STA COUTC ; Echo a space ($20)
|
||||
JSR COUT1
|
||||
RTS
|
||||
|
||||
DO_LF: RTS ; Just swallow LF. CR emulates it.
|
||||
|
||||
DO_CR: SEC
|
||||
LDA CADDR_L ; 1. Carriage return to start of row.
|
||||
SBC C_COL
|
||||
STA CADDR_L
|
||||
LDA CADDR_H
|
||||
SBC #$00 ; Will decrement H if carry was left
|
||||
; set.
|
||||
STA CADDR_H
|
||||
|
||||
;; 1. Are we on the last row? Scroll.
|
||||
LDA C_ROW
|
||||
CMP #N_ROWS-1
|
||||
BNE @inc
|
||||
JSR DO_SCROLL
|
||||
RTS
|
||||
|
||||
@inc: JSR INCROW
|
||||
|
||||
@lf: CLC
|
||||
LDA CADDR_L
|
||||
ADC #$28 ; Now add $28
|
||||
STA CADDR_L
|
||||
LDA CADDR_H
|
||||
ADC #$00 ; Will increment if carry was set
|
||||
STA CADDR_H
|
||||
|
||||
LDA #$00 ; Reset cursor row to 0
|
||||
STA C_COL
|
||||
JSR SET_CURSOR
|
||||
RTS
|
||||
|
||||
SET_CURSOR:
|
||||
LDA #14
|
||||
STA CRTC_A
|
||||
LDA CADDR_H
|
||||
STA CRTC_C
|
||||
LDA #15
|
||||
STA CRTC_A
|
||||
LDA CADDR_L
|
||||
STA CRTC_C
|
||||
RTS
|
||||
|
||||
;; Increment C_ROW and put new
|
||||
;; line start address in R_STRT,R_STRT+1
|
||||
INCROW: TYA
|
||||
PHA
|
||||
INC C_ROW
|
||||
BNE DECR2 ; Share code with DECROW
|
||||
|
||||
;; Decrement C_ROW and put new
|
||||
;; line start address in R_STRT,R_STRT+1
|
||||
DECROW: TYA
|
||||
PHA
|
||||
DEC C_ROW
|
||||
DECR2: JSR SETRSTRT
|
||||
PLA
|
||||
TAY
|
||||
RTS
|
||||
|
||||
;; Send the cursor home to the start of the line
|
||||
HOME:
|
||||
LDY C_ROW
|
||||
LDA CRA_LO,Y
|
||||
STA CADDR_L
|
||||
LDA CRA_HI,Y
|
||||
STA CADDR_H
|
||||
LDA #$00
|
||||
STA C_COL
|
||||
RTS
|
||||
|
||||
;; Set the start address of the current row
|
||||
SETRSTRT:
|
||||
LDY C_ROW
|
||||
LDA CRA_LO,Y
|
||||
STA R_STRT
|
||||
LDA CRA_HI,Y
|
||||
STA R_STRT+1
|
||||
RTS
|
||||
|
||||
;; Clear the current video line
|
||||
CLRLN: LDA #$20
|
||||
LDY #$00
|
||||
@l1: STA (R_STRT),Y
|
||||
INY
|
||||
CPY #N_COLS
|
||||
BNE @l1
|
||||
RTS
|
||||
|
||||
;; Clear the video window and put the cursor at the home
|
||||
;; position
|
||||
CLRVID:
|
||||
LDA #$20
|
||||
LDX #N_ROWS-1
|
||||
@l1: STX C_ROW
|
||||
JSR SETRSTRT
|
||||
JSR CLRLN
|
||||
DEX
|
||||
BPL @l1
|
||||
RTS
|
||||
|
||||
;; Handle a scroll request.
|
||||
;;
|
||||
;; The cursor could be anywhere on the line when this scroll
|
||||
;; request comes in.
|
||||
DO_SCROLL:
|
||||
PHA
|
||||
TYA
|
||||
PHA
|
||||
TXA
|
||||
PHA
|
||||
|
||||
;; The idea here is to copy each line to the line above it
|
||||
LDX #$00
|
||||
|
||||
@l1: LDA CRA_LO,X
|
||||
STA R_STRT
|
||||
LDA CRA_HI,X
|
||||
STA R_STRT+1
|
||||
|
||||
INX
|
||||
|
||||
LDA CRA_LO,X
|
||||
STA R_NXT
|
||||
LDA CRA_HI,X
|
||||
STA R_NXT+1
|
||||
|
||||
LDY #0
|
||||
@l2: LDA (R_NXT),Y
|
||||
STA (R_STRT),Y
|
||||
INY
|
||||
CPY #N_COLS
|
||||
BNE @l2
|
||||
|
||||
CPX #N_ROWS
|
||||
BNE @l1
|
||||
|
||||
;; Clear the line the cursor occupies.
|
||||
JSR CLRLN
|
||||
|
||||
;; Move the cursor to the beginning of the line
|
||||
JSR HOME
|
||||
JSR SET_CURSOR
|
||||
|
||||
PLA
|
||||
TAX
|
||||
PLA
|
||||
TAY ; Restore Y
|
||||
PLA
|
||||
RTS
|
||||
|
||||
;; Decrement the cursor address
|
||||
INC_CADDR:
|
||||
INC CADDR_L
|
||||
BNE @l1 ; Did we increment to 0?
|
||||
INC CADDR_H ; Yes, also increment high
|
||||
@l1: RTS
|
||||
|
||||
;; Increment the cursor address
|
||||
DEC_CADDR:
|
||||
CMP CADDR_L ; Is low alrady 0?
|
||||
BNE @l1
|
||||
DEC CADDR_H ; Yes, decrement high
|
||||
@l1: DEC CADDR_L
|
||||
RTS
|
||||
|
||||
COUT1:
|
||||
TYA
|
||||
PHA ; Save Y
|
||||
LDY #$00
|
||||
LDA COUTC
|
||||
STA (CADDR_L),Y
|
||||
PLA
|
||||
TAY ; Restore Y
|
||||
RTS
|
||||
|
||||
;;;
|
||||
;;; byte in from ACIA. This subroutine will also force
|
||||
;;; all lowercase letters to be uppercase, because EhBASIC
|
||||
;;; only understands upper-case tokens
|
||||
;;;
|
||||
ACIAin:
|
||||
LDA ACIAstatus ; Read 6551 status
|
||||
AND #$08 ;
|
||||
BEQ LAB_nobyw ; If rx buffer empty, no byte
|
||||
|
||||
LDA ACIAdata ; Read byte from 6551
|
||||
CMP #'a' ; Is it < 'a'?
|
||||
BCC @done ; Yes, we're done
|
||||
CMP #'{' ; Is it >= '{'?
|
||||
BCS @done ; Yes, we're done
|
||||
AND #$5f ; Otherwise, mask to uppercase
|
||||
@done:
|
||||
SEC ; Flag byte received
|
||||
RTS
|
||||
|
||||
LAB_nobyw:
|
||||
CLC ; flag no byte received
|
||||
no_load: ; empty load vector for EhBASIC
|
||||
no_save: ; empty save vector for EhBASIC
|
||||
RTS
|
||||
|
||||
; vector tables
|
||||
|
||||
LAB_vec:
|
||||
.word ACIAin ; byte in from simulated ACIA
|
||||
.word CRTCout ; byte out to simulated ACIA
|
||||
.word no_load ; null load vector for EhBASIC
|
||||
.word no_save ; null save vector for EhBASIC
|
||||
|
||||
; EhBASIC IRQ support
|
||||
|
||||
IRQ_CODE:
|
||||
PHA ; save A
|
||||
LDA IrqBase ; get the IRQ flag byte
|
||||
LSR ; shift the set b7 to b6, and on down ...
|
||||
ORA IrqBase ; OR the original back in
|
||||
STA IrqBase ; save the new IRQ flag byte
|
||||
PLA ; restore A
|
||||
RTI
|
||||
|
||||
; EhBASIC NMI support
|
||||
|
||||
NMI_CODE:
|
||||
PHA ; save A
|
||||
LDA NmiBase ; get the NMI flag byte
|
||||
LSR ; shift the set b7 to b6, and on down ...
|
||||
ORA NmiBase ; OR the original back in
|
||||
STA NmiBase ; save the new NMI flag byte
|
||||
PLA ; restore A
|
||||
RTI
|
||||
|
||||
END_CODE:
|
||||
|
||||
|
||||
;;; CRTC row addresses: HIGH
|
||||
CRA_HI:
|
||||
.byte $70,$70,$70,$70,$70,$70,$70,$71
|
||||
.byte $71,$71,$71,$71,$71,$72,$72,$72
|
||||
.byte $72,$72,$72,$72,$73,$73,$73,$73
|
||||
|
||||
|
||||
;;; CRTC row addresses: LOW
|
||||
CRA_LO:
|
||||
.byte $00,$28,$50,$78,$A0,$C8,$F0,$18
|
||||
.byte $40,$68,$90,$B8,$E0,$08,$30,$58
|
||||
.byte $80,$A8,$D0,$F8,$20,$48,$70,$98
|
||||
|
||||
; sign on string
|
||||
LAB_mess:
|
||||
.byte $0D,$0A,"SYMON (C) 2008-2014, SETH MORABITO"
|
||||
.byte $0D,$0A,"ENHANCED 6502 BASIC 2.22 (C) LEE DAVISON"
|
||||
.byte $0D,$0A,"[C]OLD/[W]ARM ?",$00
|
||||
|
||||
|
||||
; system vectors
|
||||
|
||||
.segment "VECTORS"
|
||||
.org $FFFA
|
||||
|
||||
.word NMI_vec ; NMI vector
|
||||
.word RES_vec ; RESET vector
|
||||
.word IRQ_vec ; IRQ vector
|
||||
@@ -0,0 +1,18 @@
|
||||
MEMORY {
|
||||
RAM1: start = $0000, size = $8000;
|
||||
ROM1: start = $C000, size = $3C00, fill = yes;
|
||||
MONITOR: start = $FC00, size = $3FA, fill = yes;
|
||||
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
|
||||
}
|
||||
|
||||
SEGMENTS {
|
||||
CODE: load = ROM1, type = ro;
|
||||
DATA: load = ROM1, type = ro;
|
||||
MONITOR: load = MONITOR, type = ro;
|
||||
VECTORS: load = ROMV, type = ro;
|
||||
}
|
||||
|
||||
SYMBOLS {
|
||||
__STACKSIZE__ = $0300;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 42 KiB |
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -26,9 +26,15 @@ 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;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The Bus ties the whole thing together, man.
|
||||
@@ -45,15 +51,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<>();
|
||||
this.startAddress = startAddress;
|
||||
this.endAddress = endAddress;
|
||||
}
|
||||
@@ -65,39 +75,73 @@ 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 Device to add
|
||||
* @param priority Bus prioirity.
|
||||
* @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<>();
|
||||
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.
|
||||
*
|
||||
* @param device
|
||||
* @param device Device to add
|
||||
* @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.
|
||||
*
|
||||
* @param device
|
||||
* @param device Device to remove
|
||||
*/
|
||||
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,43 +155,39 @@ 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) & 0xff;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -176,8 +216,18 @@ public class Bus {
|
||||
}
|
||||
|
||||
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<>();
|
||||
|
||||
List<Integer> priorities = new ArrayList<>(deviceMap.keySet());
|
||||
Collections.sort(priorities);
|
||||
|
||||
for(int priority : priorities) {
|
||||
SortedSet<Device> deviceSet = deviceMap.get(priority);
|
||||
devices.addAll(deviceSet.stream().collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
public Cpu getCpu() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2012 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -25,6 +25,9 @@ package com.loomcom.symon;
|
||||
|
||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||
import com.loomcom.symon.util.HexUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* This class provides a simulation of the MOS 6502 CPU's state machine.
|
||||
@@ -33,6 +36,8 @@ import com.loomcom.symon.util.HexUtil;
|
||||
*/
|
||||
public class Cpu implements InstructionTable {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(Cpu.class.getName());
|
||||
|
||||
/* Process status register mnemonics */
|
||||
public static final int P_CARRY = 0x01;
|
||||
public static final int P_ZERO = 0x02;
|
||||
@@ -53,12 +58,13 @@ 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;
|
||||
public static final long DEFAULT_CLOCK_PERIOD_IN_NS = 1000;
|
||||
|
||||
/* Simulated clock speed (default is 1MHz) */
|
||||
private long clockPeriodInNs = DEFAULT_CLOCK_PERIOD_IN_NS;
|
||||
|
||||
/* Simulated behavior */
|
||||
private static CpuBehavior behavior;
|
||||
private CpuBehavior behavior;
|
||||
|
||||
/* The Bus */
|
||||
private Bus bus;
|
||||
@@ -66,15 +72,8 @@ public class Cpu implements InstructionTable {
|
||||
/* The CPU state */
|
||||
private static final CpuState state = new CpuState();
|
||||
|
||||
/* Scratch space for addressing mode and effective address
|
||||
* calculations */
|
||||
private int irAddressMode; // Bits 3-5 of IR: [ | | |X|X|X| | ]
|
||||
private int irOpMode; // Bits 6-7 of IR: [ | | | | | |X|X]
|
||||
private int effectiveAddress;
|
||||
|
||||
/* 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.
|
||||
@@ -105,10 +104,6 @@ public class Cpu implements InstructionTable {
|
||||
this.behavior = behavior;
|
||||
}
|
||||
|
||||
public CpuBehavior getBehavior() {
|
||||
return behavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the CPU to known initial values.
|
||||
*/
|
||||
@@ -145,6 +140,8 @@ public class Cpu implements InstructionTable {
|
||||
state.a = 0;
|
||||
state.x = 0;
|
||||
state.y = 0;
|
||||
|
||||
peekAhead();
|
||||
}
|
||||
|
||||
public void step(int num) throws MemoryAccessException {
|
||||
@@ -157,6 +154,8 @@ public class Cpu implements InstructionTable {
|
||||
* 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;
|
||||
|
||||
@@ -170,8 +169,8 @@ public class Cpu implements InstructionTable {
|
||||
|
||||
// Fetch memory location for this instruction.
|
||||
state.ir = bus.read(state.pc);
|
||||
irAddressMode = (state.ir >> 2) & 0x07;
|
||||
irOpMode = state.ir & 0x03;
|
||||
int irAddressMode = (state.ir >> 2) & 0x07; // Bits 3-5 of IR: [ | | |X|X|X| | ]
|
||||
int irOpMode = state.ir & 0x03; // Bits 6-7 of IR: [ | | | | | |X|X]
|
||||
|
||||
incrementPC();
|
||||
|
||||
@@ -188,7 +187,8 @@ public class Cpu implements InstructionTable {
|
||||
state.stepCounter++;
|
||||
|
||||
// Get the data from the effective address (if any)
|
||||
effectiveAddress = 0;
|
||||
int effectiveAddress = 0;
|
||||
int tmp; // Temporary storage
|
||||
|
||||
switch (irOpMode) {
|
||||
case 0:
|
||||
@@ -292,8 +292,8 @@ public class Cpu implements InstructionTable {
|
||||
break;
|
||||
case 0x40: // RTI - Return from Interrupt - Implied
|
||||
setProcessorStatus(stackPop());
|
||||
lo = stackPop();
|
||||
hi = stackPop();
|
||||
int lo = stackPop();
|
||||
int hi = stackPop();
|
||||
setProgramCounter(address(lo, hi));
|
||||
break;
|
||||
case 0x48: // PHA - Push Accumulator - Implied
|
||||
@@ -730,6 +730,18 @@ public class Cpu implements InstructionTable {
|
||||
}
|
||||
|
||||
delayLoop(state.ir);
|
||||
|
||||
// Peek ahead to the next insturction and arguments
|
||||
peekAhead();
|
||||
}
|
||||
|
||||
private void peekAhead() throws MemoryAccessException {
|
||||
state.nextIr = bus.read(state.pc);
|
||||
int nextInstSize = Cpu.instructionSizes[state.nextIr];
|
||||
for (int i = 1; i < nextInstSize; i++) {
|
||||
int nextRead = (state.pc + i) % bus.endAddress();
|
||||
state.nextArgs[i-1] = bus.read(nextRead);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleIrq(int returnPc) throws MemoryAccessException {
|
||||
@@ -768,7 +780,7 @@ public class Cpu implements InstructionTable {
|
||||
*
|
||||
* @param acc The current value of the accumulator
|
||||
* @param operand The operand
|
||||
* @return
|
||||
* @return The sum of the accumulator and the operand
|
||||
*/
|
||||
private int adc(int acc, int operand) {
|
||||
int result = (operand & 0xff) + (acc & 0xff) + getCarryBit();
|
||||
@@ -866,7 +878,7 @@ public class Cpu implements InstructionTable {
|
||||
*/
|
||||
private int lsr(int m) {
|
||||
setCarryFlag((m & 0x01) != 0);
|
||||
return (m >>> 1) & 0xff;
|
||||
return (m & 0xff) >>> 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -891,6 +903,14 @@ public class Cpu implements InstructionTable {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clockPeriodInNs The simulated clock period, in nanoseconds
|
||||
*/
|
||||
public void setClockPeriodInNs(long clockPeriodInNs) {
|
||||
logger.debug("Setting simulated clock period to {} ns.", clockPeriodInNs);
|
||||
this.clockPeriodInNs = clockPeriodInNs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current Cpu State.
|
||||
*
|
||||
@@ -1115,6 +1135,14 @@ public class Cpu implements InstructionTable {
|
||||
|
||||
public void setProgramCounter(int addr) {
|
||||
state.pc = addr;
|
||||
|
||||
// As a side-effect of setting the program counter,
|
||||
// we want to peek ahead at the next state.
|
||||
try {
|
||||
peekAhead();
|
||||
} catch (MemoryAccessException ex) {
|
||||
logger.error("Could not peek ahead at next instruction state.");
|
||||
}
|
||||
}
|
||||
|
||||
public int getStackPointer() {
|
||||
@@ -1316,20 +1344,22 @@ public class Cpu implements InstructionTable {
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform a busy-loop for CLOCK_IN_NS nanoseconds
|
||||
* Perform a busy-loop until the instruction should complete on the wall clock
|
||||
*/
|
||||
private void delayLoop(int opcode) {
|
||||
int clockSteps = Cpu.instructionClocks[0xff & opcode];
|
||||
// Just a precaution. This could be better.
|
||||
|
||||
if (clockSteps == 0) {
|
||||
clockSteps = 1;
|
||||
}
|
||||
long startTime = System.nanoTime();
|
||||
long stopTime = startTime + (CLOCK_IN_NS * clockSteps);
|
||||
// Busy loop
|
||||
while (System.nanoTime() < stopTime) {
|
||||
;
|
||||
logger.warn("Opcode {} has clock step of 0!", opcode);
|
||||
return;
|
||||
}
|
||||
|
||||
long interval = clockSteps * clockPeriodInNs;
|
||||
long end;
|
||||
|
||||
do {
|
||||
end = System.nanoTime();
|
||||
} while (opBeginTime + interval >= end);
|
||||
}
|
||||
|
||||
|
||||
@@ -1358,15 +1388,20 @@ public class Cpu implements InstructionTable {
|
||||
*/
|
||||
public int pc;
|
||||
/**
|
||||
* Instruction Register
|
||||
* Last Loaded Instruction Register
|
||||
*/
|
||||
public int ir;
|
||||
public int lastPc;
|
||||
/**
|
||||
* Peek-Ahead to next IR
|
||||
*/
|
||||
public int nextIr;
|
||||
public int[] args = new int[2];
|
||||
public int[] nextArgs = new int[2];
|
||||
public int instSize;
|
||||
public boolean opTrap;
|
||||
public boolean irqAsserted;
|
||||
public boolean nmiAsserted;
|
||||
public int lastPc;
|
||||
|
||||
/* Status Flag Register bits */
|
||||
public boolean carryFlag;
|
||||
@@ -1386,7 +1421,7 @@ public class Cpu implements InstructionTable {
|
||||
/**
|
||||
* Snapshot a copy of the CpuState.
|
||||
*
|
||||
* (This is a copy constructor rather than an implementation of <code>Clonable</code>
|
||||
* (This is a copy constructor rather than an implementation of <code>Cloneable</code>
|
||||
* based on Josh Bloch's recommendation)
|
||||
*
|
||||
* @param s The CpuState to copy.
|
||||
@@ -1398,9 +1433,12 @@ public class Cpu implements InstructionTable {
|
||||
this.sp = s.sp;
|
||||
this.pc = s.pc;
|
||||
this.ir = s.ir;
|
||||
this.nextIr = s.nextIr;
|
||||
this.lastPc = s.lastPc;
|
||||
this.args[0] = s.args[0];
|
||||
this.args[1] = s.args[1];
|
||||
this.nextArgs[0] = s.nextArgs[0];
|
||||
this.nextArgs[1] = s.nextArgs[1];
|
||||
this.instSize = s.instSize;
|
||||
this.opTrap = s.opTrap;
|
||||
this.irqAsserted = s.irqAsserted;
|
||||
@@ -1420,21 +1458,19 @@ public class Cpu implements InstructionTable {
|
||||
* @return a string formatted for the trace log.
|
||||
*/
|
||||
public String toTraceEvent() {
|
||||
String opcode = disassembleOp();
|
||||
StringBuilder sb = new StringBuilder(getInstructionByteStatus());
|
||||
sb.append(" ");
|
||||
sb.append(String.format("%-14s", opcode));
|
||||
sb.append("A:" + HexUtil.byteToHex(a) + " ");
|
||||
sb.append("X:" + HexUtil.byteToHex(x) + " ");
|
||||
sb.append("Y:" + HexUtil.byteToHex(y) + " ");
|
||||
sb.append("F:" + HexUtil.byteToHex(getStatusFlag()) + " ");
|
||||
sb.append("S:1" + HexUtil.byteToHex(sp) + " ");
|
||||
sb.append(getProcessorStatusString() + "\n");
|
||||
return sb.toString();
|
||||
String opcode = disassembleLastOp();
|
||||
return getInstructionByteStatus() + " " +
|
||||
String.format("%-14s", opcode) +
|
||||
"A:" + HexUtil.byteToHex(a) + " " +
|
||||
"X:" + HexUtil.byteToHex(x) + " " +
|
||||
"Y:" + HexUtil.byteToHex(y) + " " +
|
||||
"F:" + HexUtil.byteToHex(getStatusFlag()) + " " +
|
||||
"S:1" + HexUtil.byteToHex(sp) + " " +
|
||||
getProcessorStatusString() + "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The value of the Process Status Register, as a byte.
|
||||
* @return The value of the Process Status Register, as a byte.
|
||||
*/
|
||||
public int getStatusFlag() {
|
||||
int status = 0x20;
|
||||
@@ -1482,13 +1518,13 @@ public class Cpu implements InstructionTable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given an opcode and its operands, return a formatted name.
|
||||
* Return a formatted string representing the last instruction and
|
||||
* operands that were executed.
|
||||
*
|
||||
* @return A string representing the mnemonic and operands of the instruction
|
||||
*/
|
||||
public String disassembleOp() {
|
||||
private String disassembleOp(int ir, int[] args) {
|
||||
String mnemonic = opcodeNames[ir];
|
||||
|
||||
if (mnemonic == null) {
|
||||
@@ -1499,41 +1535,55 @@ public class Cpu implements InstructionTable {
|
||||
|
||||
switch (instructionModes[ir]) {
|
||||
case ABS:
|
||||
sb.append(" $" + HexUtil.wordToHex(address(args[0], args[1])));
|
||||
sb.append(" $").append(HexUtil.wordToHex(address(args[0], args[1])));
|
||||
break;
|
||||
case ABX:
|
||||
sb.append(" $" + HexUtil.wordToHex(address(args[0], args[1])) + ",X");
|
||||
sb.append(" $").append(HexUtil.wordToHex(address(args[0], args[1]))).append(",X");
|
||||
break;
|
||||
case ABY:
|
||||
sb.append(" $" + HexUtil.wordToHex(address(args[0], args[1])) + ",Y");
|
||||
sb.append(" $").append(HexUtil.wordToHex(address(args[0], args[1]))).append(",Y");
|
||||
break;
|
||||
case IMM:
|
||||
sb.append(" #$" + HexUtil.byteToHex(args[0]));
|
||||
sb.append(" #$").append(HexUtil.byteToHex(args[0]));
|
||||
break;
|
||||
case IND:
|
||||
sb.append(" ($" + HexUtil.wordToHex(address(args[0], args[1])) + ")");
|
||||
sb.append(" ($").append(HexUtil.wordToHex(address(args[0], args[1]))).append(")");
|
||||
break;
|
||||
case XIN:
|
||||
sb.append(" ($" + HexUtil.byteToHex(args[0]) + ",X)");
|
||||
sb.append(" ($").append(HexUtil.byteToHex(args[0])).append(",X)");
|
||||
break;
|
||||
case INY:
|
||||
sb.append(" ($" + HexUtil.byteToHex(args[0]) + "),Y");
|
||||
sb.append(" ($").append(HexUtil.byteToHex(args[0])).append("),Y");
|
||||
break;
|
||||
case REL:
|
||||
case ZPG:
|
||||
sb.append(" $" + HexUtil.byteToHex(args[0]));
|
||||
sb.append(" $").append(HexUtil.byteToHex(args[0]));
|
||||
break;
|
||||
case ZPX:
|
||||
sb.append(" $" + HexUtil.byteToHex(args[0]) + ",X");
|
||||
sb.append(" $").append(HexUtil.byteToHex(args[0])).append(",X");
|
||||
break;
|
||||
case ZPY:
|
||||
sb.append(" $" + HexUtil.byteToHex(args[0]) + ",Y");
|
||||
sb.append(" $").append(HexUtil.byteToHex(args[0])).append(",Y");
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String disassembleLastOp() {
|
||||
return disassembleOp(ir, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a formatted string representing the next instruction and
|
||||
* operands to be executed.
|
||||
*
|
||||
* @return A string representing the mnemonic and operands of the instruction
|
||||
*/
|
||||
public String disassembleNextOp() {
|
||||
return disassembleOp(nextIr, nextArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given two bytes, return an address.
|
||||
*/
|
||||
@@ -1546,17 +1596,15 @@ public class Cpu implements InstructionTable {
|
||||
* @return A string representing the current status register state.
|
||||
*/
|
||||
public String getProcessorStatusString() {
|
||||
StringBuilder sb = new StringBuilder("[");
|
||||
sb.append(negativeFlag ? 'N' : '.'); // Bit 7
|
||||
sb.append(overflowFlag ? 'V' : '.'); // Bit 6
|
||||
sb.append("-"); // Bit 5 (always 1)
|
||||
sb.append(breakFlag ? 'B' : '.'); // Bit 4
|
||||
sb.append(decimalModeFlag ? 'D' : '.'); // Bit 3
|
||||
sb.append(irqDisableFlag ? 'I' : '.'); // Bit 2
|
||||
sb.append(zeroFlag ? 'Z' : '.'); // Bit 1
|
||||
sb.append(carryFlag ? 'C' : '.'); // Bit 0
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
return "[" + (negativeFlag ? 'N' : '.') +
|
||||
(overflowFlag ? 'V' : '.') +
|
||||
"-" +
|
||||
(breakFlag ? 'B' : '.') +
|
||||
(decimalModeFlag ? 'D' : '.') +
|
||||
(irqDisableFlag ? 'I' : '.') +
|
||||
(zeroFlag ? 'Z' : '.') +
|
||||
(carryFlag ? 'C' : '.') +
|
||||
"]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2012 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -31,7 +31,7 @@ public interface InstructionTable {
|
||||
*
|
||||
* TODO: As of version 0.6, this is still not used! All CPUs are "idealized" NMOS 6502 only.
|
||||
*/
|
||||
public enum CpuBehavior {
|
||||
enum CpuBehavior {
|
||||
/**
|
||||
* The earliest NMOS 6502 includes a bug that causes the ROR instruction
|
||||
* to behave like an ASL that does not affect the carry bit. This version
|
||||
@@ -65,7 +65,7 @@ public interface InstructionTable {
|
||||
/**
|
||||
* Enumeration of Addressing Modes.
|
||||
*/
|
||||
public enum Mode {
|
||||
enum Mode {
|
||||
ACC {
|
||||
public String toString() {
|
||||
return "Accumulator";
|
||||
@@ -156,7 +156,7 @@ public interface InstructionTable {
|
||||
/**
|
||||
* Instruction opcode names.
|
||||
*/
|
||||
public static final String[] opcodeNames = {
|
||||
String[] opcodeNames = {
|
||||
"BRK", "ORA", null, null, null, "ORA", "ASL", null,
|
||||
"PHP", "ORA", "ASL", null, null, "ORA", "ASL", null,
|
||||
"BPL", "ORA", null, null, null, "ORA", "ASL", null,
|
||||
@@ -194,7 +194,7 @@ public interface InstructionTable {
|
||||
/**
|
||||
* Instruction addressing modes.
|
||||
*/
|
||||
public static final Mode[] instructionModes = {
|
||||
Mode[] instructionModes = {
|
||||
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x00-0x03
|
||||
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x04-0x07
|
||||
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x08-0x0b
|
||||
@@ -265,7 +265,7 @@ public interface InstructionTable {
|
||||
/**
|
||||
* Size, in bytes, required for each instruction.
|
||||
*/
|
||||
public static final int[] instructionSizes = {
|
||||
int[] instructionSizes = {
|
||||
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 0, 3, 3, 0,
|
||||
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
|
||||
3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
|
||||
@@ -287,7 +287,7 @@ public interface InstructionTable {
|
||||
/**
|
||||
* Number of clock cycles required for each instruction
|
||||
*/
|
||||
public static final int[] instructionClocks = {
|
||||
int[] instructionClocks = {
|
||||
7, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 0, 4, 6, 0,
|
||||
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
|
||||
6, 6, 0, 0, 3, 3, 5, 0, 4, 2, 2, 0, 4, 4, 6, 0,
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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 Program arguments
|
||||
*/
|
||||
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);
|
||||
switch (machine) {
|
||||
case "symon":
|
||||
machineClass = SymonMachine.class;
|
||||
break;
|
||||
case "multicomp":
|
||||
machineClass = MulticompMachine.class;
|
||||
break;
|
||||
case "simple":
|
||||
machineClass = SimpleMachine.class;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(() -> {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
// Create the main UI window
|
||||
simulator.createAndShowUi();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Simulator.MainCommand cmd = simulator.waitForCommand();
|
||||
if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) {
|
||||
machineClass = null;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -34,77 +34,78 @@ import com.loomcom.symon.exceptions.*;
|
||||
*/
|
||||
public class MemoryRange implements Comparable<MemoryRange> {
|
||||
|
||||
/** The starting address of the memory range. */
|
||||
public int startAddress;
|
||||
/** The ending address of the memory range. */
|
||||
public int endAddress;
|
||||
/**
|
||||
* The starting address of the memory range.
|
||||
*/
|
||||
public int startAddress;
|
||||
/**
|
||||
* The ending address of the memory range.
|
||||
*/
|
||||
public int endAddress;
|
||||
|
||||
public MemoryRange(int startAddress, int endAddress)
|
||||
throws MemoryRangeException {
|
||||
if (startAddress < 0 || endAddress < 0) {
|
||||
throw new MemoryRangeException("Addresses cannot be less than 0.");
|
||||
public MemoryRange(int startAddress, int endAddress)
|
||||
throws MemoryRangeException {
|
||||
if (startAddress < 0 || endAddress < 0) {
|
||||
throw new MemoryRangeException("Addresses cannot be less than 0.");
|
||||
}
|
||||
if (startAddress >= endAddress) {
|
||||
throw new MemoryRangeException("End address must be greater " +
|
||||
"than start address.");
|
||||
}
|
||||
this.startAddress = startAddress;
|
||||
this.endAddress = endAddress;
|
||||
}
|
||||
if (startAddress >= endAddress) {
|
||||
throw new MemoryRangeException("End address must be greater " +
|
||||
"than start address.");
|
||||
|
||||
/**
|
||||
* @return the starting address.
|
||||
*/
|
||||
public int startAddress() {
|
||||
return startAddress;
|
||||
}
|
||||
this.startAddress = startAddress;
|
||||
this.endAddress = endAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the starting address.
|
||||
*/
|
||||
public int startAddress() {
|
||||
return startAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the ending address.
|
||||
*/
|
||||
public int endAddress() {
|
||||
return endAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for address inclusion in the range.
|
||||
*
|
||||
* @returns true if the address is included within this range,
|
||||
* false otherwise.
|
||||
*/
|
||||
public boolean includes(int address) {
|
||||
return (address <= endAddress &&
|
||||
address >= startAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for overlapping memory ranges.
|
||||
*
|
||||
* @returns true if this range overlaps in any way with the other.
|
||||
*/
|
||||
public boolean overlaps(MemoryRange other) {
|
||||
return (this.includes(other.startAddress()) ||
|
||||
other.includes(this.startAddress()));
|
||||
}
|
||||
|
||||
// Implementation of Comparable interface
|
||||
public int compareTo(MemoryRange other) {
|
||||
if (other == null) {
|
||||
throw new NullPointerException("Cannot compare to null.");
|
||||
/**
|
||||
* @return the ending address.
|
||||
*/
|
||||
public int endAddress() {
|
||||
return endAddress;
|
||||
}
|
||||
if (this == other) {
|
||||
return 0;
|
||||
}
|
||||
Integer thisStartAddr = new Integer(this.startAddress());
|
||||
Integer thatStartAddr = new Integer(other.startAddress());
|
||||
return thisStartAddr.compareTo(thatStartAddr);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer desc = new StringBuffer("@");
|
||||
desc.append(String.format("0x%04x", startAddress));
|
||||
desc.append("-");
|
||||
desc.append(String.format("0x%04x", endAddress));
|
||||
return desc.toString();
|
||||
}
|
||||
/**
|
||||
* Checks for address inclusion in the range.
|
||||
*
|
||||
* @return true if the address is included within this range,
|
||||
* false otherwise.
|
||||
*/
|
||||
public boolean includes(int address) {
|
||||
return (address <= endAddress &&
|
||||
address >= startAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for overlapping memory ranges.
|
||||
*
|
||||
* @return true if this range overlaps in any way with the other.
|
||||
*/
|
||||
public boolean overlaps(MemoryRange other) {
|
||||
return (this.includes(other.startAddress()) ||
|
||||
other.includes(this.startAddress()));
|
||||
}
|
||||
|
||||
// Implementation of Comparable interface
|
||||
public int compareTo(MemoryRange other) {
|
||||
if (other == null) {
|
||||
throw new NullPointerException("Cannot compare to null.");
|
||||
}
|
||||
if (this == other) {
|
||||
return 0;
|
||||
}
|
||||
Integer thisStartAddr = this.startAddress();
|
||||
Integer thatStartAddr = other.startAddress();
|
||||
return thisStartAddr.compareTo(thatStartAddr);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "@" + String.format("0x%04x", startAddress) + "-" +
|
||||
String.format("0x%04x", endAddress);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -27,15 +27,15 @@ import javax.swing.*;
|
||||
|
||||
public interface Preferences {
|
||||
|
||||
public static final int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300;
|
||||
int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300;
|
||||
|
||||
public static final boolean DEFAULT_HALT_ON_BREAK = true;
|
||||
boolean DEFAULT_HALT_ON_BREAK = true;
|
||||
|
||||
public JDialog getDialog();
|
||||
JDialog getDialog();
|
||||
|
||||
public int getProgramStartAddress();
|
||||
int getProgramStartAddress();
|
||||
|
||||
public boolean getHaltOnBreak();
|
||||
boolean getHaltOnBreak();
|
||||
|
||||
public void updateUi();
|
||||
void updateUi();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyrighi (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -23,24 +23,21 @@
|
||||
|
||||
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.exceptions.*;
|
||||
import com.loomcom.symon.machines.Machine;
|
||||
import com.loomcom.symon.ui.*;
|
||||
import com.loomcom.symon.ui.Console;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.io.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Symon Simulator Interface and Control.
|
||||
@@ -52,59 +49,33 @@ 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;
|
||||
private final static Logger logger = LoggerFactory.getLogger(Simulator.class.getName());
|
||||
|
||||
// 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);
|
||||
private static final int CONSOLE_BORDER_WIDTH = 10;
|
||||
private static final int DEFAULT_FONT_SIZE = 12;
|
||||
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
|
||||
private static final int CONSOLE_BORDER_WIDTH = 10;
|
||||
|
||||
// Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz.
|
||||
private static final long[] CLOCK_PERIODS = {0, 1000, 500, 333, 250, 200, 167, 143, 125};
|
||||
|
||||
// Since it is very expensive to update the UI with Swing's Event Dispatch Thread, we can't afford
|
||||
// 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.
|
||||
private long stepsBetweenCrtcRefreshes = 2500;
|
||||
private static final long STEPS_BETWEEN_CRTC_REFRESHES = 2500;
|
||||
|
||||
// A counter to keep track of the number of UI updates that have been
|
||||
// requested
|
||||
@@ -124,61 +95,61 @@ public class Simulator {
|
||||
/**
|
||||
* The Trace Window shows the most recent 50,000 CPU states.
|
||||
*/
|
||||
private TraceLog traceLog;
|
||||
private final TraceLog traceLog;
|
||||
|
||||
/**
|
||||
* The Memory Window shows the contents of one page of memory.
|
||||
*/
|
||||
private MemoryWindow memoryWindow;
|
||||
private final MemoryWindow memoryWindow;
|
||||
|
||||
private VideoWindow videoWindow;
|
||||
private final VideoWindow videoWindow;
|
||||
|
||||
private final BreakpointsWindow breakpointsWindow;
|
||||
|
||||
private SimulatorMenu menuBar;
|
||||
|
||||
private RunLoop runLoop;
|
||||
private Console console;
|
||||
private RunLoop runLoop;
|
||||
private Console console;
|
||||
private StatusPanel statusPane;
|
||||
|
||||
private JButton runStopButton;
|
||||
private JButton stepButton;
|
||||
private JButton resetButton;
|
||||
private JComboBox stepCountBox;
|
||||
private JComboBox<String> stepCountBox;
|
||||
|
||||
private JFileChooser fileChooser;
|
||||
private JFileChooser fileChooser;
|
||||
private PreferencesDialog preferences;
|
||||
|
||||
private SortedSet<Integer> breakpoints;
|
||||
|
||||
private final Object commandMonitorObject = new Object();
|
||||
|
||||
private MainCommand command = MainCommand.NONE;
|
||||
|
||||
public enum MainCommand {
|
||||
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);
|
||||
public Simulator(Class machineClass) throws Exception {
|
||||
this.breakpoints = new TreeSet<>();
|
||||
|
||||
bus.addCpu(cpu);
|
||||
this.machine = (Machine) machineClass.getConstructors()[0].newInstance();
|
||||
|
||||
bus.addDevice(ram);
|
||||
bus.addDevice(via);
|
||||
bus.addDevice(acia);
|
||||
bus.addDevice(crtc);
|
||||
// Initialize final fields in the constructor.
|
||||
this.traceLog = new TraceLog();
|
||||
this.memoryWindow = new MemoryWindow(machine.getBus());
|
||||
this.breakpointsWindow = new BreakpointsWindow(breakpoints, mainWindow);
|
||||
|
||||
// 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);
|
||||
if (machine.getCrtc() != null) {
|
||||
videoWindow = new VideoWindow(machine.getCrtc(), 2, 2);
|
||||
} 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);
|
||||
videoWindow = null;
|
||||
}
|
||||
|
||||
bus.addDevice(rom);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,13 +157,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.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT, false);
|
||||
this.statusPane = new StatusPanel(machine);
|
||||
|
||||
console.setBorderWidth(CONSOLE_BORDER_WIDTH);
|
||||
|
||||
@@ -210,25 +181,25 @@ public class Simulator {
|
||||
|
||||
runStopButton = new JButton("Run");
|
||||
stepButton = new JButton("Step");
|
||||
resetButton = new JButton("Reset");
|
||||
JButton softResetButton = new JButton("Soft Reset");
|
||||
JButton hardResetButton = new JButton("Hard Reset");
|
||||
|
||||
stepCountBox = new JComboBox(STEPS);
|
||||
stepCountBox.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
try {
|
||||
JComboBox cb = (JComboBox) actionEvent.getSource();
|
||||
stepsPerClick = Integer.parseInt((String) cb.getSelectedItem());
|
||||
} catch (NumberFormatException ex) {
|
||||
stepsPerClick = 1;
|
||||
stepCountBox.setSelectedIndex(0);
|
||||
}
|
||||
stepCountBox = new JComboBox<>(STEPS);
|
||||
stepCountBox.addActionListener(actionEvent -> {
|
||||
try {
|
||||
JComboBox cb = (JComboBox) actionEvent.getSource();
|
||||
stepsPerClick = Integer.parseInt((String) cb.getSelectedItem());
|
||||
} catch (NumberFormatException ex) {
|
||||
stepsPerClick = 1;
|
||||
stepCountBox.setSelectedIndex(0);
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
@@ -240,38 +211,27 @@ public class Simulator {
|
||||
// Bottom - buttons.
|
||||
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
|
||||
|
||||
runStopButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
if (runLoop != null && runLoop.isRunning()) {
|
||||
handleStop();
|
||||
} else {
|
||||
handleStart();
|
||||
}
|
||||
runStopButton.addActionListener(actionEvent -> {
|
||||
if (runLoop != null && runLoop.isRunning()) {
|
||||
handleStop();
|
||||
} else {
|
||||
handleStart();
|
||||
}
|
||||
});
|
||||
|
||||
stepButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
handleStep(stepsPerClick);
|
||||
}
|
||||
stepButton.addActionListener(actionEvent -> handleStep(stepsPerClick));
|
||||
|
||||
softResetButton.addActionListener(actionEvent -> {
|
||||
// If this was a CTRL-click, do a hard reset.
|
||||
handleReset(false);
|
||||
});
|
||||
|
||||
resetButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
handleReset();
|
||||
}
|
||||
hardResetButton.addActionListener(actionEvent -> {
|
||||
// If this was a CTRL-click, do a hard reset.
|
||||
handleReset(true);
|
||||
});
|
||||
|
||||
// Prepare the log window
|
||||
traceLog = new TraceLog();
|
||||
|
||||
// Prepare the memory window
|
||||
memoryWindow = new MemoryWindow(bus);
|
||||
|
||||
// Composite Video and 6545 CRTC
|
||||
videoWindow = new VideoWindow(crtc, 2, 2);
|
||||
|
||||
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
|
||||
// The Menu. This comes last, because it relies on other components having
|
||||
// already been initialized.
|
||||
@@ -282,8 +242,21 @@ public class Simulator {
|
||||
mainWindow.setVisible(true);
|
||||
|
||||
console.requestFocus();
|
||||
handleReset(false);
|
||||
}
|
||||
|
||||
public MainCommand waitForCommand() {
|
||||
synchronized (commandMonitorObject) {
|
||||
try {
|
||||
commandMonitorObject.wait();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
|
||||
private void handleStart() {
|
||||
// Shift focus to the console.
|
||||
console.requestFocus();
|
||||
@@ -302,7 +275,7 @@ public class Simulator {
|
||||
/*
|
||||
* Perform a reset.
|
||||
*/
|
||||
private void handleReset() {
|
||||
private void handleReset(boolean isColdReset) {
|
||||
if (runLoop != null && runLoop.isRunning()) {
|
||||
runLoop.requestStop();
|
||||
runLoop.interrupt();
|
||||
@@ -310,22 +283,24 @@ public class Simulator {
|
||||
}
|
||||
|
||||
try {
|
||||
logger.log(Level.INFO, "Reset requested. Resetting CPU.");
|
||||
// Reset and clear memory
|
||||
cpu.reset();
|
||||
logger.debug("Reset requested. Resetting CPU.");
|
||||
// Reset CPU
|
||||
machine.getCpu().reset();
|
||||
// Clear the console.
|
||||
console.reset();
|
||||
// Reset the trace log.
|
||||
traceLog.reset();
|
||||
// Update status.
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
// Now update the state
|
||||
statusPane.updateState(cpu);
|
||||
// If we're doing a cold reset, clear the memory.
|
||||
if (isColdReset) {
|
||||
Memory mem = machine.getRam();
|
||||
if (mem != null) {
|
||||
mem.fill(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Update status.
|
||||
updateVisibleState();
|
||||
} catch (MemoryAccessException ex) {
|
||||
logger.log(Level.SEVERE, "Exception during simulator reset: " + ex.getMessage());
|
||||
logger.error("Exception during simulator reset", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,16 +312,9 @@ public class Simulator {
|
||||
for (int i = 0; i < numSteps; i++) {
|
||||
step();
|
||||
}
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
if (traceLog.isVisible()) {
|
||||
traceLog.refresh();
|
||||
}
|
||||
statusPane.updateState(cpu);
|
||||
}
|
||||
});
|
||||
updateVisibleState();
|
||||
} catch (SymonException ex) {
|
||||
logger.log(Level.SEVERE, "Exception during simulator step: " + ex.getMessage());
|
||||
logger.error("Exception during simulator step", ex);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -355,46 +323,41 @@ 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!");
|
||||
logger.error("Console type-ahead buffer underrun!");
|
||||
}
|
||||
|
||||
if (stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) {
|
||||
videoWindow.refreshDisplay();
|
||||
if (videoWindow != null && stepsSinceLastCrtcRefresh++ > STEPS_BETWEEN_CRTC_REFRESHES) {
|
||||
stepsSinceLastCrtcRefresh = 0;
|
||||
if (videoWindow.isVisible()) {
|
||||
videoWindow.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
// This is a very expensive update, and we're doing it without
|
||||
// a delay, so we don't want to overwhelm the Swing event processing thread
|
||||
// with requests. Limit the number of ui updates that can be performed.
|
||||
if (stepsSinceLastUpdate++ > MAX_STEPS_BETWEEN_UPDATES) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
// Now update the state
|
||||
statusPane.updateState(cpu);
|
||||
}
|
||||
});
|
||||
updateVisibleState();
|
||||
stepsSinceLastUpdate = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,49 +366,20 @@ 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" +
|
||||
Integer.toString(startAddress, 16));
|
||||
logger.info("Loaded {} bytes at address 0x{}", i, Integer.toString(startAddress, 16));
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
updateVisibleState();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -463,18 +397,16 @@ public class Simulator {
|
||||
}
|
||||
|
||||
public void run() {
|
||||
logger.log(Level.INFO, "Starting main run loop.");
|
||||
logger.debug("Starting main run loop.");
|
||||
isRunning = true;
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
// Don't allow step while the simulator is running
|
||||
stepButton.setEnabled(false);
|
||||
stepCountBox.setEnabled(false);
|
||||
menuBar.simulatorDidStart();
|
||||
// Toggle the state of the run button
|
||||
runStopButton.setText("Stop");
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
// Don't allow step while the simulator is running
|
||||
stepButton.setEnabled(false);
|
||||
stepCountBox.setEnabled(false);
|
||||
menuBar.simulatorDidStart();
|
||||
// Toggle the state of the run button
|
||||
runStopButton.setText("Stop");
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -482,35 +414,32 @@ public class Simulator {
|
||||
step();
|
||||
} while (shouldContinue());
|
||||
} catch (SymonException ex) {
|
||||
logger.log(Level.SEVERE, "Exception in main simulator run thread. Exiting run.");
|
||||
ex.printStackTrace();
|
||||
logger.error("Exception in main simulator run thread. Exiting run.", ex);
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
statusPane.updateState(cpu);
|
||||
runStopButton.setText("Run");
|
||||
stepButton.setEnabled(true);
|
||||
stepCountBox.setEnabled(true);
|
||||
if (traceLog.isVisible()) {
|
||||
traceLog.refresh();
|
||||
}
|
||||
menuBar.simulatorDidStop();
|
||||
traceLog.simulatorDidStop();
|
||||
// TODO: Update memory window, if frame is visible.
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
statusPane.updateState();
|
||||
memoryWindow.updateState();
|
||||
runStopButton.setText("Run");
|
||||
stepButton.setEnabled(true);
|
||||
stepCountBox.setEnabled(true);
|
||||
if (traceLog.isVisible()) {
|
||||
traceLog.refresh();
|
||||
}
|
||||
menuBar.simulatorDidStop();
|
||||
traceLog.simulatorDidStop();
|
||||
});
|
||||
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the run loop should proceed to the next step.
|
||||
*
|
||||
* @return True if the run loop should proceed to the next step.
|
||||
*/
|
||||
private boolean shouldContinue() {
|
||||
return isRunning && !(preferences.getHaltOnBreak() && cpu.getInstruction() == 0x00);
|
||||
return !breakpoints.contains(machine.getCpu().getProgramCounter()) &&
|
||||
isRunning &&
|
||||
!(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,7 +451,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 +458,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;
|
||||
@@ -542,21 +473,25 @@ public class Simulator {
|
||||
program[i++] = dis.readByte();
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
console.reset();
|
||||
}
|
||||
});
|
||||
SwingUtilities.invokeLater(() -> console.reset());
|
||||
|
||||
// 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());
|
||||
logger.error("Unable to read program file.", ex);
|
||||
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
|
||||
} catch (MemoryAccessException ex) {
|
||||
logger.log(Level.SEVERE, "Memory access error loading program: " + ex.getMessage());
|
||||
logger.error("Memory access error loading program", ex);
|
||||
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -569,7 +504,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 +511,39 @@ 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.");
|
||||
} 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);
|
||||
|
||||
// Now, reset
|
||||
cpu.reset();
|
||||
|
||||
logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " +
|
||||
String.format("0x%04X", ROM_BASE));
|
||||
if (fileSize != machine.getRomSize()) {
|
||||
throw new IOException("ROM file must be exactly " + String.valueOf(machine.getRomSize()) + " bytes.");
|
||||
}
|
||||
|
||||
// Load the new ROM image
|
||||
Memory rom = Memory.makeROM(machine.getRomBase(), machine.getRomBase() + machine.getRomSize() - 1, romFile);
|
||||
machine.setRom(rom);
|
||||
|
||||
// Now, reset
|
||||
machine.getCpu().reset();
|
||||
|
||||
updateVisibleState();
|
||||
|
||||
logger.info("ROM File `{}' loaded at {}", romFile.getName(),
|
||||
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());
|
||||
logger.error("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());
|
||||
logger.error("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());
|
||||
logger.error("Memory access error while loading ROM file: {}", ex.getMessage());
|
||||
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -618,6 +560,32 @@ public class Simulator {
|
||||
}
|
||||
}
|
||||
|
||||
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 = MainCommand.SELECTMACHINE;
|
||||
synchronized (commandMonitorObject) {
|
||||
commandMonitorObject.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class QuitAction extends AbstractAction {
|
||||
public QuitAction() {
|
||||
super("Quit", null);
|
||||
@@ -640,19 +608,36 @@ public class Simulator {
|
||||
public SetFontAction(int size) {
|
||||
super(Integer.toString(size) + " pt", null);
|
||||
this.size = size;
|
||||
putValue(SHORT_DESCRIPTION, "Set font to " + Integer.toString(size) + "pt.");
|
||||
putValue(SHORT_DESCRIPTION, "Set font to " + size + "pt.");
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
console.setFont(new Font("Monospaced", Font.PLAIN, size));
|
||||
mainWindow.pack();
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
console.setFont(new Font("Monospaced", Font.PLAIN, size));
|
||||
mainWindow.pack();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SetSpeedAction extends AbstractAction {
|
||||
private int speed;
|
||||
|
||||
public SetSpeedAction(int speed) {
|
||||
super(Integer.toString(speed) + " MHz", null);
|
||||
this.speed = speed;
|
||||
putValue(SHORT_DESCRIPTION, "Set simulated speed to " + speed + " MHz.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
if (speed < 1 || speed > CLOCK_PERIODS.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
machine.getCpu().setClockPeriodInNs(CLOCK_PERIODS[speed]);
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleTraceWindowAction extends AbstractAction {
|
||||
public ToggleTraceWindowAction() {
|
||||
super("Trace Log", null);
|
||||
@@ -705,6 +690,23 @@ public class Simulator {
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleBreakpointWindowAction extends AbstractAction {
|
||||
public ToggleBreakpointWindowAction() {
|
||||
super("Breakpoints...", null);
|
||||
putValue(SHORT_DESCRIPTION, "Show or Hide Breakpoints");
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
synchronized (breakpointsWindow) {
|
||||
if (breakpointsWindow.isVisible()) {
|
||||
breakpointsWindow.setVisible(false);
|
||||
} else {
|
||||
breakpointsWindow.setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SimulatorMenu extends JMenuBar {
|
||||
// Menu Items
|
||||
private JMenuItem loadProgramItem;
|
||||
@@ -722,7 +724,9 @@ public class Simulator {
|
||||
*/
|
||||
public void simulatorDidStart() {
|
||||
loadProgramItem.setEnabled(false);
|
||||
loadRomItem.setEnabled(false);
|
||||
if (loadRomItem != null) {
|
||||
loadRomItem.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -730,7 +734,9 @@ public class Simulator {
|
||||
*/
|
||||
public void simulatorDidStop() {
|
||||
loadProgramItem.setEnabled(true);
|
||||
loadRomItem.setEnabled(true);
|
||||
if (loadRomItem != null) {
|
||||
loadRomItem.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void initMenu() {
|
||||
@@ -741,13 +747,19 @@ 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 quitItem = new JMenuItem(new QuitAction());
|
||||
fileMenu.add(quitItem);
|
||||
|
||||
add(fileMenu);
|
||||
@@ -792,26 +804,88 @@ 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);
|
||||
|
||||
/*
|
||||
* Simulator Menu
|
||||
*/
|
||||
|
||||
|
||||
JMenu simulatorMenu = new JMenu("Simulator");
|
||||
|
||||
// "Select Machine..." item.
|
||||
JMenuItem selectMachineItem = new JMenuItem(new SelectMachineAction());
|
||||
simulatorMenu.add(selectMachineItem);
|
||||
|
||||
// "Clock Speed" sub-menu
|
||||
JMenu speedSubMenu = new JMenu("Clock Speed");
|
||||
ButtonGroup speedGroup = new ButtonGroup();
|
||||
|
||||
makeSpeedMenuItem(1, speedSubMenu, speedGroup);
|
||||
makeSpeedMenuItem(2, speedSubMenu, speedGroup);
|
||||
makeSpeedMenuItem(4, speedSubMenu, speedGroup);
|
||||
makeSpeedMenuItem(8, speedSubMenu, speedGroup);
|
||||
|
||||
simulatorMenu.add(speedSubMenu);
|
||||
|
||||
// "Breakpoints"
|
||||
final JCheckBoxMenuItem showBreakpoints = new JCheckBoxMenuItem(new ToggleBreakpointWindowAction());
|
||||
// Un-check the menu item if the user closes the window directly
|
||||
breakpointsWindow.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
showBreakpoints.setSelected(false);
|
||||
}
|
||||
});
|
||||
simulatorMenu.add(showBreakpoints);
|
||||
|
||||
add(simulatorMenu);
|
||||
}
|
||||
|
||||
private void makeFontSizeMenuItem(int size, JMenu fontSubMenu, ButtonGroup group) {
|
||||
private void makeFontSizeMenuItem(int size, JMenu subMenu, ButtonGroup group) {
|
||||
Action action = new SetFontAction(size);
|
||||
|
||||
JCheckBoxMenuItem item = new JCheckBoxMenuItem(action);
|
||||
item.setSelected(size == DEFAULT_FONT_SIZE);
|
||||
fontSubMenu.add(item);
|
||||
subMenu.add(item);
|
||||
group.add(item);
|
||||
}
|
||||
|
||||
private void makeSpeedMenuItem(int speed, JMenu subMenu, ButtonGroup group) {
|
||||
if (speed < 1 || speed > CLOCK_PERIODS.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Action action = new SetSpeedAction(speed);
|
||||
|
||||
JCheckBoxMenuItem item = new JCheckBoxMenuItem(action);
|
||||
item.setSelected(CLOCK_PERIODS[speed] == Cpu.DEFAULT_CLOCK_PERIOD_IN_NS);
|
||||
subMenu.add(item);
|
||||
group.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVisibleState() {
|
||||
// Immediately update the UI.
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
// Now update the state
|
||||
statusPane.updateState();
|
||||
memoryWindow.updateState();
|
||||
if (traceLog.shouldUpdate()) {
|
||||
traceLog.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -23,179 +23,47 @@
|
||||
|
||||
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.
|
||||
* 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 receiveIrqEnabled = false;
|
||||
private boolean transmitIrqEnabled = false;
|
||||
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:
|
||||
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;
|
||||
|
||||
// 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
|
||||
@@ -214,6 +82,7 @@ public class Acia extends Device {
|
||||
/**
|
||||
* @return The simulated baud rate in bps.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public int getBaudRate() {
|
||||
return baudRate;
|
||||
}
|
||||
@@ -225,35 +94,31 @@ 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) {
|
||||
@@ -289,17 +154,9 @@ public class Acia extends Device {
|
||||
/**
|
||||
* @return true if there is character data in the RX register.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public boolean hasRxChar() {
|
||||
return rxFull;
|
||||
}
|
||||
|
||||
private synchronized void reset() {
|
||||
txChar = 0;
|
||||
txEmpty = true;
|
||||
rxChar = 0;
|
||||
rxFull = false;
|
||||
receiveIrqEnabled = false;
|
||||
transmitIrqEnabled = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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 Data to write into the control register
|
||||
*/
|
||||
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,123 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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
|
||||
|
||||
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) {
|
||||
// Bits 0 & 1 control the master reset
|
||||
if((data & 0x01) != 0 && (data & 0x02) != 0) {
|
||||
reset();
|
||||
}
|
||||
|
||||
// Bit 7 controls receiver IRQ behavior
|
||||
receiveIrqEnabled = (data & 0x80) != 0;
|
||||
// Bits 5 & 6 controls transmit IRQ behavior
|
||||
transmitIrqEnabled = (data & 0x20) != 0 && (data & 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* Simulation of a 6545 CRTC and virtual CRT output.
|
||||
*/
|
||||
public class Crtc extends Device {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Crtc.class.getName());
|
||||
|
||||
|
||||
// Memory locations in the CRTC address space
|
||||
public static final int REGISTER_SELECT = 0;
|
||||
public static final int REGISTER_RW = 1;
|
||||
@@ -65,6 +82,11 @@ public class Crtc extends Device {
|
||||
|
||||
private int currentRegister = 0;
|
||||
|
||||
// Status bits
|
||||
private boolean rowColumnAddressing = false;
|
||||
private boolean displayEnableSkew = false;
|
||||
private boolean cursorSkew = false;
|
||||
|
||||
private Memory memory;
|
||||
|
||||
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
|
||||
@@ -118,8 +140,9 @@ public class Crtc extends Device {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int[] getDmaAccess() {
|
||||
return memory.getDmaAccess();
|
||||
public int getCharAtAddress(int address) throws MemoryAccessException {
|
||||
// TODO: Row/Column addressing
|
||||
return memory.read(address);
|
||||
}
|
||||
|
||||
public int getHorizontalDisplayed() {
|
||||
@@ -162,6 +185,18 @@ public class Crtc extends Device {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public boolean getRowColumnAddressing() {
|
||||
return rowColumnAddressing;
|
||||
}
|
||||
|
||||
public boolean getDisplayEnableSkew() {
|
||||
return displayEnableSkew;
|
||||
}
|
||||
|
||||
public boolean getCursorSkew() {
|
||||
return cursorSkew;
|
||||
}
|
||||
|
||||
private void setCurrentRegister(int registerNumber) {
|
||||
this.currentRegister = registerNumber;
|
||||
}
|
||||
@@ -180,7 +215,9 @@ public class Crtc extends Device {
|
||||
pageSize = horizontalDisplayed * verticalDisplayed;
|
||||
break;
|
||||
case MODE_CONTROL:
|
||||
// TODO: Implement multiple addressing modes and cursor skew.
|
||||
rowColumnAddressing = (data & 0x04) != 0;
|
||||
displayEnableSkew = (data & 0x10) != 0;
|
||||
cursorSkew = (data & 0x20) != 0;
|
||||
break;
|
||||
case SCAN_LINE:
|
||||
scanLinesPerRow = data;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -62,16 +62,11 @@ public abstract class Device implements Comparable<Device> {
|
||||
*/
|
||||
private Set<DeviceChangeListener> deviceChangeListeners;
|
||||
|
||||
public Device(int startAddress, int endAddress, String name)
|
||||
throws MemoryRangeException {
|
||||
public Device(int startAddress, int endAddress, String name) throws MemoryRangeException {
|
||||
this.memoryRange = new MemoryRange(startAddress, endAddress);
|
||||
this.size = endAddress - startAddress + 1;
|
||||
this.name = name;
|
||||
this.deviceChangeListeners = new HashSet<DeviceChangeListener>();
|
||||
}
|
||||
|
||||
public Device(int startAddress, int endAddress) throws MemoryRangeException {
|
||||
this(startAddress, endAddress, null);
|
||||
this.deviceChangeListeners = new HashSet<>();
|
||||
}
|
||||
|
||||
/* Methods required to be implemented by inheriting classes. */
|
||||
@@ -97,6 +92,7 @@ public abstract class Device implements Comparable<Device> {
|
||||
return memoryRange.endAddress();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public int startAddress() {
|
||||
return memoryRange.startAddress();
|
||||
}
|
||||
@@ -105,6 +101,7 @@ public abstract class Device implements Comparable<Device> {
|
||||
return name;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@@ -118,9 +115,7 @@ public abstract class Device implements Comparable<Device> {
|
||||
}
|
||||
|
||||
public void notifyListeners() {
|
||||
for (DeviceChangeListener l : deviceChangeListeners) {
|
||||
l.deviceStateChanged();
|
||||
}
|
||||
deviceChangeListeners.forEach(DeviceChangeListener::deviceStateChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -24,5 +24,5 @@
|
||||
package com.loomcom.symon.devices;
|
||||
|
||||
public interface DeviceChangeListener {
|
||||
public void deviceStateChanged();
|
||||
void deviceStateChanged();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -28,8 +28,6 @@ import java.util.*;
|
||||
|
||||
import com.loomcom.symon.exceptions.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class Memory extends Device {
|
||||
|
||||
private boolean readOnly;
|
||||
@@ -57,8 +55,7 @@ public class Memory extends Device {
|
||||
}
|
||||
|
||||
public static Memory makeRAM(int startAddress, int endAddress) throws MemoryRangeException {
|
||||
Memory memory = new Memory(startAddress, endAddress, false);
|
||||
return memory;
|
||||
return new Memory(startAddress, endAddress, false);
|
||||
}
|
||||
|
||||
public void write(int address, int data) throws MemoryAccessException {
|
||||
@@ -108,8 +105,4 @@ public class Memory extends Device {
|
||||
public String toString() {
|
||||
return "Memory: " + getMemoryRange().toString();
|
||||
}
|
||||
|
||||
public int[] getDmaAccess() {
|
||||
return mem;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -23,45 +23,19 @@
|
||||
|
||||
package com.loomcom.symon.devices;
|
||||
|
||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||
|
||||
public class Via extends Device {
|
||||
public static final int VIA_SIZE = 16;
|
||||
public abstract class Pia extends Device {
|
||||
|
||||
private static final int ORB = 0;
|
||||
private static final int ORA = 1;
|
||||
private static final int DDRB = 2;
|
||||
private static final int DDRA = 3;
|
||||
private static final int T1C_L = 4;
|
||||
private static final int T1C_H = 5;
|
||||
private static final int T1L_L = 6;
|
||||
private static final int T1L_H = 7;
|
||||
private static final int T2C_L = 8;
|
||||
private static final int T2C_H = 9;
|
||||
private static final int SR = 10;
|
||||
private static final int ACR = 11;
|
||||
private static final int PCR = 12;
|
||||
private static final int IFR = 13;
|
||||
private static final int IER = 14;
|
||||
private static final int ORA_H = 15;
|
||||
private final String name;
|
||||
|
||||
public Via(int address) throws MemoryRangeException {
|
||||
super(address, address + VIA_SIZE - 1, "VIA");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int address, int data) throws MemoryAccessException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(int address) throws MemoryAccessException {
|
||||
return 0;
|
||||
public Pia(int startAddress, int endAddress, String name) throws MemoryRangeException {
|
||||
super(startAddress, endAddress, name);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return null;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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;
|
||||
import jdk.nashorn.internal.ir.annotations.Ignore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Emulation for the SD-card controller of the MULTICOMP system.
|
||||
* Neiter comlete nor correct.
|
||||
*/
|
||||
public class SdController extends Device {
|
||||
|
||||
private enum Status {
|
||||
IDLE,
|
||||
READ,
|
||||
WRITE
|
||||
}
|
||||
|
||||
public static final int CONTROLLER_SIZE = 8;
|
||||
private final int SECTOR_SIZE = 512;
|
||||
private final static Logger logger = Logger.getLogger(SdController.class.getName());
|
||||
|
||||
private File sdImageFile;
|
||||
private int lba0, lba1, lba2;
|
||||
private int position;
|
||||
private Status status = Status.IDLE;
|
||||
|
||||
private final byte[] readBuffer = new byte[SECTOR_SIZE];
|
||||
private final byte[] writeBuffer = new byte[SECTOR_SIZE];
|
||||
private int readPosition = 0;
|
||||
private int writePosition = 0;
|
||||
|
||||
|
||||
public SdController(int address) throws MemoryRangeException {
|
||||
super(address, address + CONTROLLER_SIZE - 1, "SDCONTROLLER");
|
||||
|
||||
sdImageFile = new File("sd.img");
|
||||
if (!sdImageFile.exists()) {
|
||||
sdImageFile = null;
|
||||
logger.log(Level.INFO, "Could not find SD card image 'sd.img'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(int address, int data) throws MemoryAccessException {
|
||||
switch (address) {
|
||||
case 0:
|
||||
writeData(data);
|
||||
return;
|
||||
case 1:
|
||||
writeCommand(data);
|
||||
return;
|
||||
case 2:
|
||||
this.lba0 = data;
|
||||
return;
|
||||
case 3:
|
||||
this.lba1 = data;
|
||||
return;
|
||||
case 4:
|
||||
this.lba2 = data;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(int address) throws MemoryAccessException {
|
||||
switch (address) {
|
||||
case 0:
|
||||
return readData();
|
||||
case 1:
|
||||
return readStatus();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void computePosition() {
|
||||
this.position = lba0 + (lba1 << 8) + (lba2 << 16);
|
||||
// each sector is 512 bytes, so multiply accordingly
|
||||
this.position <<= 9;
|
||||
}
|
||||
|
||||
private void prepareRead() {
|
||||
this.status = Status.READ;
|
||||
this.readPosition = 0;
|
||||
computePosition();
|
||||
|
||||
if (sdImageFile != null) {
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(sdImageFile);
|
||||
fis.skip(this.position);
|
||||
int read = fis.read(readBuffer);
|
||||
if (read < SECTOR_SIZE) {
|
||||
logger.log(Level.WARNING, "not enough data to fill read buffer from SD image file");
|
||||
}
|
||||
fis.close();
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "could not fill read buffer from SD image file", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareWrite() {
|
||||
this.status = Status.WRITE;
|
||||
this.writePosition = 0;
|
||||
computePosition();
|
||||
}
|
||||
|
||||
|
||||
private int readData() {
|
||||
if (status != Status.READ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int data = readBuffer[readPosition++];
|
||||
|
||||
if (readPosition >= SECTOR_SIZE) {
|
||||
this.status = Status.IDLE;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void writeData(int data) {
|
||||
if (status != Status.WRITE) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeBuffer[writePosition++] = (byte) data;
|
||||
|
||||
if (writePosition >= SECTOR_SIZE) {
|
||||
if (sdImageFile != null) {
|
||||
try {
|
||||
RandomAccessFile raf = new RandomAccessFile(sdImageFile, "rw");
|
||||
raf.skipBytes(this.position);
|
||||
raf.write(writeBuffer, 0, writeBuffer.length);
|
||||
raf.close();
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "could not write data back to SD image file!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
this.status = Status.IDLE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int readStatus() {
|
||||
switch (this.status) {
|
||||
case IDLE:
|
||||
return 128;
|
||||
case READ:
|
||||
return 224;
|
||||
case WRITE:
|
||||
return 160;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeCommand(int data) {
|
||||
switch (data) {
|
||||
case 0:
|
||||
prepareRead();
|
||||
return;
|
||||
case 1:
|
||||
prepareWrite();
|
||||
return;
|
||||
default:
|
||||
this.status = Status.IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName() + "@" + String.format("%04X", this.getMemoryRange().startAddress);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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;
|
||||
|
||||
/**
|
||||
* Very basic implementation of a MOS 6522 VIA.
|
||||
*
|
||||
* TODO: Implement timers as threads.
|
||||
*/
|
||||
public class Via6522 extends Pia {
|
||||
public static final int VIA_SIZE = 16;
|
||||
|
||||
enum Register {
|
||||
ORB, ORA, DDRB, DDRA, T1C_L, T1C_H, T1L_L, T1L_H,
|
||||
T2C_L, T2C_H, SR, ACR, PCR, IFR, IER, ORA_H
|
||||
}
|
||||
|
||||
public Via6522(int address) throws MemoryRangeException {
|
||||
super(address, address + VIA_SIZE - 1, "MOS 6522 VIA");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int address, int data) throws MemoryAccessException {
|
||||
Register[] registers = Register.values();
|
||||
|
||||
if (address >= registers.length) {
|
||||
throw new MemoryAccessException("Unknown register: " + address);
|
||||
}
|
||||
|
||||
Register r = registers[address];
|
||||
|
||||
switch (r) {
|
||||
case ORA:
|
||||
case ORB:
|
||||
case DDRA:
|
||||
case DDRB:
|
||||
case T1C_L:
|
||||
case T1C_H:
|
||||
case T1L_L:
|
||||
case T1L_H:
|
||||
case T2C_L:
|
||||
case T2C_H:
|
||||
case SR:
|
||||
case ACR:
|
||||
case PCR:
|
||||
case IFR:
|
||||
case IER:
|
||||
case ORA_H:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(int address) throws MemoryAccessException {
|
||||
Register[] registers = Register.values();
|
||||
|
||||
if (address >= registers.length) {
|
||||
throw new MemoryAccessException("Unknown register: " + address);
|
||||
}
|
||||
|
||||
Register r = registers[address];
|
||||
|
||||
switch (r) {
|
||||
case ORA:
|
||||
case ORB:
|
||||
case DDRA:
|
||||
case DDRB:
|
||||
case T1C_L:
|
||||
case T1C_H:
|
||||
case T1L_L:
|
||||
case T1L_H:
|
||||
case T2C_L:
|
||||
case T2C_H:
|
||||
case SR:
|
||||
case ACR:
|
||||
case PCR:
|
||||
case IFR:
|
||||
case IER:
|
||||
case ORA_H:
|
||||
default:
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -31,7 +31,4 @@ public class SymonException extends Exception {
|
||||
public SymonException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
public SymonException() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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.Pia;
|
||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||
|
||||
|
||||
public interface Machine {
|
||||
|
||||
Bus getBus();
|
||||
|
||||
Cpu getCpu();
|
||||
|
||||
Memory getRam();
|
||||
|
||||
Acia getAcia();
|
||||
|
||||
Pia getPia();
|
||||
|
||||
Crtc getCrtc();
|
||||
|
||||
Memory getRom();
|
||||
|
||||
void setRom(Memory rom) throws MemoryRangeException;
|
||||
|
||||
int getRomBase();
|
||||
|
||||
int getRomSize();
|
||||
|
||||
int getMemorySize();
|
||||
|
||||
String getName();
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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.Pia;
|
||||
import com.loomcom.symon.devices.SdController;
|
||||
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;
|
||||
|
||||
// SD controller at $FFD8-$FFDF
|
||||
private static final int SD_BASE = 0xFFD8;
|
||||
|
||||
// 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);
|
||||
this.acia.setBaudRate(0);
|
||||
|
||||
bus.addCpu(cpu);
|
||||
bus.addDevice(ram);
|
||||
bus.addDevice(acia, 1);
|
||||
bus.addDevice(new SdController(SD_BASE), 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 Pia getPia() {
|
||||
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,117 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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.Pia;
|
||||
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 Pia getPia() {
|
||||
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,165 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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.*;
|
||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class SymonMachine implements Machine {
|
||||
|
||||
private final static Logger logger = LoggerFactory.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;
|
||||
|
||||
// PIA at $8000-$800F
|
||||
|
||||
private static final int PIA_BASE = 0x8000;
|
||||
|
||||
// ACIA at $8800-$8803
|
||||
private static final int ACIA_BASE = 0x8800;
|
||||
|
||||
// CRTC at $9000-$9001
|
||||
private static final int CRTC_BASE = 0x9000;
|
||||
|
||||
// 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 Pia pia;
|
||||
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.pia = new Via6522(PIA_BASE);
|
||||
this.acia = new Acia6551(ACIA_BASE);
|
||||
this.crtc = new Crtc(CRTC_BASE, ram);
|
||||
|
||||
bus.addCpu(cpu);
|
||||
bus.addDevice(ram);
|
||||
bus.addDevice(pia);
|
||||
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 {} not found, loading empty R/W memory image.", romImage);
|
||||
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 Pia getPia() {
|
||||
return pia;
|
||||
}
|
||||
|
||||
@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";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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.ui;
|
||||
|
||||
import com.loomcom.symon.util.HexUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.SortedSet;
|
||||
|
||||
/**
|
||||
* Simple window to enter breakpoints.
|
||||
*/
|
||||
public class BreakpointsWindow extends JFrame {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BreakpointsWindow.class);
|
||||
|
||||
private static final Dimension FRAME_SIZE = new Dimension(240, 280);
|
||||
private static final String EMPTY_STRING = "";
|
||||
|
||||
private JFrame mainWindow;
|
||||
private SortedSet<Integer> breakpoints;
|
||||
|
||||
/**
|
||||
* Simple ListModel to back the list of breakpoints.
|
||||
*/
|
||||
private class BreakpointsListModel extends AbstractListModel<String> {
|
||||
private SortedSet<Integer> breakpoints;
|
||||
|
||||
public BreakpointsListModel(SortedSet<Integer> breakpoints) {
|
||||
this.breakpoints = breakpoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return breakpoints.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementAt(int index) {
|
||||
ArrayList<Integer> values = new ArrayList<>(breakpoints);
|
||||
return "$" + HexUtil.wordToHex(values.get(index));
|
||||
}
|
||||
|
||||
public void addElement(Integer breakpoint) {
|
||||
breakpoints.add(breakpoint);
|
||||
ArrayList<Integer> values = new ArrayList<>(breakpoints);
|
||||
int index = values.indexOf(breakpoint);
|
||||
fireIntervalAdded(this, index, index);
|
||||
}
|
||||
|
||||
public void removeElement(int index) {
|
||||
ArrayList<Integer> values = new ArrayList<>(breakpoints);
|
||||
Integer breakpoint = values.get(index);
|
||||
breakpoints.remove(breakpoint);
|
||||
fireIntervalRemoved(this, index, index);
|
||||
}
|
||||
}
|
||||
|
||||
public BreakpointsWindow(SortedSet<Integer> breakpoints,
|
||||
JFrame mainWindow) {
|
||||
this.breakpoints = breakpoints;
|
||||
this.mainWindow = mainWindow;
|
||||
createUi();
|
||||
}
|
||||
|
||||
private void createUi() {
|
||||
setTitle("Breakpoints");
|
||||
|
||||
JPanel breakpointsPanel = new JPanel();
|
||||
JPanel controlPanel = new JPanel();
|
||||
|
||||
breakpointsPanel.setLayout(new BorderLayout());
|
||||
breakpointsPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
|
||||
JButton addButton = new JButton("Add");
|
||||
JButton removeButton = new JButton("Del");
|
||||
removeButton.setEnabled(false);
|
||||
|
||||
JTextField addTextField = new JTextField(5);
|
||||
|
||||
BreakpointsListModel listModel = new BreakpointsListModel(breakpoints);
|
||||
JList<String> breakpointsList = new JList<>(listModel);
|
||||
breakpointsList.setFont(new Font("Monospace", Font.PLAIN, 14));
|
||||
breakpointsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(breakpointsList);
|
||||
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
|
||||
breakpointsPanel.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
|
||||
breakpointsList.addListSelectionListener(le -> {
|
||||
int idx = breakpointsList.getSelectedIndex();
|
||||
|
||||
if (idx == -1) {
|
||||
removeButton.setEnabled(false);
|
||||
} else {
|
||||
removeButton.setEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
ActionListener addBreakpointListener = e -> {
|
||||
int value = -1;
|
||||
|
||||
String newBreakpoint = addTextField.getText();
|
||||
|
||||
if (newBreakpoint == null || newBreakpoint.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
value = (Integer.parseInt(addTextField.getText(), 16) & 0xffff);
|
||||
} catch (NumberFormatException ex) {
|
||||
logger.warn("Can't parse page number {}", newBreakpoint);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
listModel.addElement(value);
|
||||
|
||||
logger.debug("Added breakpoint ${}", HexUtil.wordToHex(value));
|
||||
|
||||
addTextField.setText(EMPTY_STRING);
|
||||
};
|
||||
|
||||
addButton.addActionListener(addBreakpointListener);
|
||||
addTextField.addActionListener(addBreakpointListener);
|
||||
|
||||
removeButton.addActionListener(e -> listModel.removeElement(breakpointsList.getSelectedIndex()));
|
||||
|
||||
controlPanel.add(addTextField);
|
||||
controlPanel.add(addButton);
|
||||
controlPanel.add(removeButton);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
getContentPane().add(breakpointsPanel, BorderLayout.CENTER);
|
||||
getContentPane().add(controlPanel, BorderLayout.SOUTH);
|
||||
|
||||
setMinimumSize(FRAME_SIZE);
|
||||
setMaximumSize(FRAME_SIZE);
|
||||
setPreferredSize(FRAME_SIZE);
|
||||
|
||||
setLocationRelativeTo(mainWindow);
|
||||
setResizable(false);
|
||||
|
||||
pack();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -44,21 +44,20 @@ import java.awt.event.MouseListener;
|
||||
|
||||
public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||
|
||||
private static final int DEFAULT_COLUMNS = 80;
|
||||
private static final int DEFAULT_ROWS = 24;
|
||||
private static final int DEFAULT_BORDER_WIDTH = 10;
|
||||
// If true, swap CR and LF characters.
|
||||
private static final boolean SWAP_CR_AND_LF = true;
|
||||
// If true, send CRLF (0x0d 0x0a) whenever CR is typed
|
||||
private static final boolean SEND_CR_LF_FOR_CR = false;
|
||||
|
||||
// If true, send CRLF (0x0d 0x0a) whenever CR is typed
|
||||
private boolean sendCrForLf = false;
|
||||
private FifoRingBuffer<Character> typeAheadBuffer;
|
||||
|
||||
public Console(int columns, int rows, Font font) {
|
||||
public Console(int columns, int rows, Font font, boolean sendCrForLf) {
|
||||
super(new Vt100TerminalModel(columns, rows), font);
|
||||
// A small type-ahead buffer, as might be found in any real
|
||||
// VT100-style serial terminal.
|
||||
this.typeAheadBuffer = new FifoRingBuffer<Character>(128);
|
||||
this.typeAheadBuffer = new FifoRingBuffer<>(128);
|
||||
this.sendCrForLf = sendCrForLf;
|
||||
setBorderWidth(DEFAULT_BORDER_WIDTH);
|
||||
addKeyListener(this);
|
||||
addMouseListener(this);
|
||||
@@ -83,7 +82,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||
/**
|
||||
* Returns true if a key has been pressed since the last time input was read.
|
||||
*
|
||||
* @return
|
||||
* @return true if the type-ahead buffer has data
|
||||
*/
|
||||
public boolean hasInput() {
|
||||
return !typeAheadBuffer.isEmpty();
|
||||
@@ -105,7 +104,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||
}
|
||||
}
|
||||
|
||||
if (SEND_CR_LF_FOR_CR && keyTyped == 0x0d) {
|
||||
if (sendCrForLf && keyTyped == 0x0d) {
|
||||
typeAheadBuffer.push((char) 0x0d);
|
||||
typeAheadBuffer.push((char) 0x0a);
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -26,6 +26,8 @@ package com.loomcom.symon.ui;
|
||||
import com.loomcom.symon.Bus;
|
||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||
import com.loomcom.symon.util.HexUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
@@ -37,8 +39,7 @@ import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.EventObject;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* This Frame displays the contents of a page of memory. The page number to be displayed
|
||||
@@ -46,6 +47,8 @@ import java.util.logging.Logger;
|
||||
*/
|
||||
public class MemoryWindow extends JFrame implements ActionListener {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MemoryWindow.class);
|
||||
|
||||
private MemoryTableModel memoryTableModel;
|
||||
private JTable memoryTable;
|
||||
private JTextField pageNumberTextField;
|
||||
@@ -190,14 +193,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,17 +208,24 @@ 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.
|
||||
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Can't parse page number " +
|
||||
pageNumberInput);
|
||||
logger.warn("Can't parse page number {}", pageNumberInput);
|
||||
}
|
||||
|
||||
updateControls();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the view of memory
|
||||
*/
|
||||
public void updateState() {
|
||||
memoryTable.updateUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* A JTable that will automatically select all text in a cell
|
||||
* being edited.
|
||||
@@ -339,12 +349,8 @@ public class MemoryWindow extends JFrame implements ActionListener {
|
||||
int fullAddress = fullAddress(row, column);
|
||||
int newValue = Integer.parseInt(hexValue, 16) & 0xff;
|
||||
bus.write(fullAddress, newValue);
|
||||
} catch (MemoryAccessException ex) {
|
||||
;
|
||||
} catch (NumberFormatException ex) {
|
||||
;
|
||||
} catch (ClassCastException ex) {
|
||||
;
|
||||
} catch (MemoryAccessException | NumberFormatException | ClassCastException ex) {
|
||||
// Intentionally swallow exception
|
||||
}
|
||||
fireTableCellUpdated(row, column);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -97,23 +97,19 @@ public class PreferencesDialog extends Observable implements Preferences {
|
||||
JButton applyButton = new JButton("Apply");
|
||||
JButton cancelButton = new JButton("Cancel");
|
||||
|
||||
cancelButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
updateUi();
|
||||
dialog.setVisible(false);
|
||||
}
|
||||
cancelButton.addActionListener(actionEvent -> {
|
||||
updateUi();
|
||||
dialog.setVisible(false);
|
||||
});
|
||||
|
||||
applyButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
haltOnBreak = haltOnBreakCheckBox.isSelected();
|
||||
programLoadAddress = hexToInt(programLoadAddressField.getText());
|
||||
updateUi();
|
||||
// TODO: Actually check to see if values have changed, don't assume.
|
||||
setChanged();
|
||||
PreferencesDialog.this.notifyObservers();
|
||||
dialog.setVisible(false);
|
||||
}
|
||||
applyButton.addActionListener(actionEvent -> {
|
||||
haltOnBreak = haltOnBreakCheckBox.isSelected();
|
||||
programLoadAddress = hexToInt(programLoadAddressField.getText());
|
||||
updateUi();
|
||||
// TODO: Actually check to see if values have changed, don't assume.
|
||||
setChanged();
|
||||
PreferencesDialog.this.notifyObservers();
|
||||
dialog.setVisible(false);
|
||||
});
|
||||
|
||||
buttonsContainer.add(applyButton);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -24,11 +24,14 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* UI component that displays the current state of the simulated CPU.
|
||||
@@ -50,7 +53,6 @@ public class StatusPanel extends JPanel {
|
||||
private ImageIcon negativeOn;
|
||||
private ImageIcon negativeOff;
|
||||
|
||||
private JLabel statusFlagsLabel;
|
||||
private JLabel carryFlagLabel;
|
||||
private JLabel zeroFlagLabel;
|
||||
private JLabel irqDisableFlagLabel;
|
||||
@@ -66,12 +68,7 @@ public class StatusPanel extends JPanel {
|
||||
private JTextField xField;
|
||||
private JTextField yField;
|
||||
|
||||
private JLabel opcodeLabel;
|
||||
private JLabel pcLabel;
|
||||
private JLabel spLabel;
|
||||
private JLabel aLabel;
|
||||
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);
|
||||
@@ -79,8 +76,9 @@ public class StatusPanel extends JPanel {
|
||||
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 +97,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);
|
||||
@@ -144,25 +142,81 @@ public class StatusPanel extends JPanel {
|
||||
statusFlagsPanel.add(carryFlagLabel);
|
||||
|
||||
// Create and add register and address labels
|
||||
statusFlagsLabel = makeLabel("Flags");
|
||||
opcodeLabel = makeLabel("IR");
|
||||
pcLabel = makeLabel("PC");
|
||||
spLabel = makeLabel("SP");
|
||||
aLabel = makeLabel("A");
|
||||
xLabel = makeLabel("X");
|
||||
yLabel = makeLabel("Y");
|
||||
JLabel statusFlagsLabel = makeLabel("Flags");
|
||||
JLabel opcodeLabel = makeLabel("Next IR");
|
||||
JLabel pcLabel = makeLabel("PC");
|
||||
JLabel spLabel = makeLabel("SP");
|
||||
JLabel aLabel = makeLabel("A");
|
||||
JLabel xLabel = makeLabel("X");
|
||||
JLabel yLabel = makeLabel("Y");
|
||||
|
||||
statusFlagsLabel.setToolTipText("6502 Processor Status Flags");
|
||||
opcodeLabel.setToolTipText("Instruction Register");
|
||||
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(e -> {
|
||||
try {
|
||||
int newVal = getHexVal(pcField) & 0xffff;
|
||||
machine.getCpu().setProgramCounter(newVal);
|
||||
} catch (Exception ex) {
|
||||
// Swallow exception
|
||||
}
|
||||
|
||||
updateState();
|
||||
});
|
||||
|
||||
spField.addActionListener(e -> {
|
||||
try {
|
||||
int newVal = getHexVal(spField) & 0xff;
|
||||
machine.getCpu().setStackPointer(newVal);
|
||||
} catch (Exception ex) {
|
||||
// Swallow exception
|
||||
}
|
||||
|
||||
updateState();
|
||||
});
|
||||
|
||||
aField.addActionListener(e -> {
|
||||
try {
|
||||
int newVal = getHexVal(aField) & 0xff;
|
||||
machine.getCpu().setAccumulator(newVal);
|
||||
} catch (Exception ex) {
|
||||
// Swallow exception
|
||||
}
|
||||
|
||||
updateState();
|
||||
});
|
||||
|
||||
xField.addActionListener(e -> {
|
||||
try {
|
||||
int newVal = getHexVal(xField) & 0xff;
|
||||
machine.getCpu().setXRegister(newVal);
|
||||
} catch (Exception ex) {
|
||||
// Swallow exception
|
||||
}
|
||||
|
||||
updateState();
|
||||
});
|
||||
|
||||
yField.addActionListener(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 +274,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));
|
||||
@@ -238,7 +291,9 @@ public class StatusPanel extends JPanel {
|
||||
negativeFlagLabel.setIcon(iconForFlag(status, 7));
|
||||
|
||||
// Update the register and address displays
|
||||
opcodeField.setText(cpu.getCpuState().disassembleOp());
|
||||
|
||||
// We always want to show the NEXT instruction that will be executed
|
||||
opcodeField.setText(cpu.getCpuState().disassembleNextOp());
|
||||
pcField.setText(cpu.getProgramCounterStatus());
|
||||
spField.setText(cpu.getStackPointerStatus());
|
||||
aField.setText(cpu.getAccumulatorStatus());
|
||||
@@ -313,10 +368,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 +379,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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@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
|
||||
@@ -35,15 +36,15 @@ import java.awt.*;
|
||||
*/
|
||||
public class TraceLog extends JFrame {
|
||||
|
||||
private FifoRingBuffer<Cpu.CpuState> traceLog;
|
||||
private JTextArea traceLogTextArea;
|
||||
private final FifoRingBuffer<Cpu.CpuState> traceLog;
|
||||
private final JTextArea traceLogTextArea;
|
||||
|
||||
private static final Dimension MIN_SIZE = new Dimension(320, 200);
|
||||
private static final Dimension PREFERRED_SIZE = new Dimension(640, 480);
|
||||
private static final int MAX_LOG_LENGTH = 50000;
|
||||
|
||||
public TraceLog() {
|
||||
traceLog = new FifoRingBuffer<Cpu.CpuState>(MAX_LOG_LENGTH);
|
||||
traceLog = new FifoRingBuffer<>(MAX_LOG_LENGTH);
|
||||
setMinimumSize(MIN_SIZE);
|
||||
setPreferredSize(PREFERRED_SIZE);
|
||||
setResizable(true);
|
||||
@@ -67,11 +68,15 @@ public class TraceLog extends JFrame {
|
||||
* call.
|
||||
*/
|
||||
public void refresh() {
|
||||
synchronized (this) {
|
||||
StringBuilder logString = new StringBuilder();
|
||||
StringBuilder logString = new StringBuilder();
|
||||
|
||||
synchronized(traceLog) {
|
||||
for (Cpu.CpuState state : traceLog) {
|
||||
logString.append(state.toTraceEvent());
|
||||
}
|
||||
}
|
||||
|
||||
synchronized(traceLogTextArea) {
|
||||
traceLogTextArea.setText(logString.toString());
|
||||
}
|
||||
}
|
||||
@@ -80,8 +85,10 @@ public class TraceLog extends JFrame {
|
||||
* Reset the log area.
|
||||
*/
|
||||
public void reset() {
|
||||
synchronized (this) {
|
||||
synchronized(traceLog) {
|
||||
traceLog.reset();
|
||||
}
|
||||
synchronized(traceLogTextArea) {
|
||||
traceLogTextArea.setText("");
|
||||
traceLogTextArea.setEnabled(true);
|
||||
}
|
||||
@@ -93,7 +100,7 @@ public class TraceLog extends JFrame {
|
||||
* @param state The CPU State to append.
|
||||
*/
|
||||
public void append(Cpu.CpuState state) {
|
||||
synchronized(this) {
|
||||
synchronized(traceLog) {
|
||||
traceLog.push(new Cpu.CpuState(state));
|
||||
}
|
||||
}
|
||||
@@ -106,4 +113,8 @@ public class TraceLog extends JFrame {
|
||||
traceLogTextArea.setEnabled(true);
|
||||
}
|
||||
|
||||
public boolean shouldUpdate() {
|
||||
return isVisible() && traceLogTextArea.isEnabled();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -25,6 +25,7 @@ package com.loomcom.symon.ui;
|
||||
|
||||
import com.loomcom.symon.devices.Crtc;
|
||||
import com.loomcom.symon.devices.DeviceChangeListener;
|
||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -36,8 +37,11 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.lang.System.*;
|
||||
|
||||
/**
|
||||
* VideoWindow represents a graphics framebuffer backed by a 6545 CRTC.
|
||||
* Each time the window's VideoPanel is repainted, the video memory is
|
||||
@@ -65,7 +69,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
||||
|
||||
private BufferedImage image;
|
||||
private int[] charRom;
|
||||
private int[] videoRam;
|
||||
|
||||
private int horizontalDisplayed;
|
||||
private int verticalDisplayed;
|
||||
@@ -85,17 +88,21 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
||||
private class VideoPanel extends JPanel {
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
for (int i = 0; i < crtc.getPageSize(); i++) {
|
||||
int address = crtc.getStartAddress() + i;
|
||||
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
|
||||
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
|
||||
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
|
||||
try {
|
||||
for (int i = 0; i < crtc.getPageSize(); i++) {
|
||||
int address = crtc.getStartAddress() + i;
|
||||
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
|
||||
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
|
||||
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
|
||||
}
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
if (shouldScale) {
|
||||
g2d.scale(scaleX, scaleY);
|
||||
}
|
||||
g2d.drawImage(image, 0, 0, null);
|
||||
} catch (MemoryAccessException ex) {
|
||||
logger.log(Level.SEVERE, "Memory Access Exception, can't paint video window! " + ex.getMessage());
|
||||
}
|
||||
Graphics2D g2d = (Graphics2D)g;
|
||||
if (shouldScale) {
|
||||
g2d.scale(scaleX, scaleY);
|
||||
}
|
||||
g2d.drawImage(image, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,12 +122,10 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
||||
*/
|
||||
private class CursorBlinker implements Runnable {
|
||||
public void run() {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
if (cursorBlinkRate > 0) {
|
||||
hideCursor = !hideCursor;
|
||||
repaint();
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (cursorBlinkRate > 0) {
|
||||
hideCursor = !hideCursor;
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -131,8 +136,7 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
||||
|
||||
this.scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
this.crtc = crtc;
|
||||
this.charRom = loadCharRom("/pet.rom");
|
||||
this.videoRam = crtc.getDmaAccess();
|
||||
this.charRom = loadCharRom("/ascii.rom");
|
||||
this.scaleX = scaleX;
|
||||
this.scaleY = scaleY;
|
||||
this.shouldScale = (scaleX > 1 || scaleY > 1);
|
||||
@@ -158,13 +162,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
||||
|
||||
}
|
||||
|
||||
public void refreshDisplay() {
|
||||
// TODO: Verify whether this is necessary. Does `repaint()' do anything if the window is not visible?
|
||||
if (isVisible()) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the CRTC on state change.
|
||||
*/
|
||||
@@ -239,15 +236,13 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
||||
* @param address The address of the character being requested.
|
||||
* @return An array of integers representing the pixel data.
|
||||
*/
|
||||
private int[] getGlyph(int address) {
|
||||
int chr = videoRam[address];
|
||||
private int[] getGlyph(int address) throws MemoryAccessException {
|
||||
int chr = crtc.getCharAtAddress(address);
|
||||
int romOffset = (chr & 0xff) * (CHAR_HEIGHT * CHAR_WIDTH);
|
||||
int[] glyph = new int[CHAR_WIDTH * scanLinesPerRow];
|
||||
|
||||
// Populate the character
|
||||
for (int i = 0; i < (CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow)); i++) {
|
||||
glyph[i] = charRom[romOffset + i];
|
||||
}
|
||||
arraycopy(charRom, romOffset, glyph, 0, CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow));
|
||||
|
||||
// Overlay the cursor
|
||||
if (!hideCursor && crtc.isCursorEnabled() && crtc.getCursorPosition() == address) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -38,7 +38,7 @@ public class FifoRingBuffer<E> implements Iterable<E> {
|
||||
private int maxLength;
|
||||
|
||||
public FifoRingBuffer(int maxLength) {
|
||||
this.fifoBuffer = new LinkedList<E>();
|
||||
this.fifoBuffer = new LinkedList<>();
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
|
||||
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -121,9 +121,6 @@ public class HexUtil {
|
||||
* @return Four digit, zero padded hexadecimal string.
|
||||
*/
|
||||
public static String wordToHex(int val) {
|
||||
StringBuilder sb = new StringBuilder(4);
|
||||
sb.append(HEX_CONSTANTS[(val >> 8) & 0xff]);
|
||||
sb.append(HEX_CONSTANTS[val & 0xff]);
|
||||
return sb.toString();
|
||||
return HEX_CONSTANTS[(val >> 8) & 0xff] + HEX_CONSTANTS[val & 0xff];
|
||||
}
|
||||
}
|
||||
|
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,6 +1,7 @@
|
||||
package com.loomcom.symon;
|
||||
|
||||
import com.loomcom.symon.devices.Acia;
|
||||
import com.loomcom.symon.devices.Acia6551;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -12,7 +13,7 @@ public class AciaTest {
|
||||
public void shouldTriggerInterruptOnRxFullIfRxIrqEnabled() throws Exception {
|
||||
Bus mockBus = mock(Bus.class);
|
||||
|
||||
Acia acia = new Acia(0x000);
|
||||
Acia acia = new Acia6551(0x000);
|
||||
acia.setBus(mockBus);
|
||||
|
||||
// Disable TX IRQ, Enable RX IRQ
|
||||
@@ -27,7 +28,7 @@ public class AciaTest {
|
||||
public void shouldNotTriggerInterruptOnRxFullIfRxIrqNotEnabled() throws Exception {
|
||||
Bus mockBus = mock(Bus.class);
|
||||
|
||||
Acia acia = new Acia(0x000);
|
||||
Acia acia = new Acia6551(0x000);
|
||||
acia.setBus(mockBus);
|
||||
|
||||
// Disable TX IRQ, Disable RX IRQ
|
||||
@@ -42,7 +43,7 @@ public class AciaTest {
|
||||
public void shouldTriggerInterruptOnTxEmptyIfTxIrqEnabled() throws Exception {
|
||||
Bus mockBus = mock(Bus.class);
|
||||
|
||||
Acia acia = new Acia(0x000);
|
||||
Acia acia = new Acia6551(0x000);
|
||||
acia.setBus(mockBus);
|
||||
|
||||
// Enable TX IRQ, Disable RX IRQ
|
||||
@@ -63,7 +64,7 @@ public class AciaTest {
|
||||
public void shouldNotTriggerInterruptOnTxEmptyIfTxIrqNotEnabled() throws Exception {
|
||||
Bus mockBus = mock(Bus.class);
|
||||
|
||||
Acia acia = new Acia(0x000);
|
||||
Acia acia = new Acia6551(0x000);
|
||||
acia.setBus(mockBus);
|
||||
|
||||
// Disable TX IRQ, Disable RX IRQ
|
||||
@@ -80,14 +81,14 @@ public class AciaTest {
|
||||
|
||||
@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));
|
||||
@@ -95,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));
|
||||
@@ -104,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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -59,22 +59,6 @@ public class BusTest extends TestCase {
|
||||
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 void testIsCompleteWithFirstDeviceNotStartingAtStartAddress() throws MemoryRangeException {
|
||||
Device memory = new Memory(0x00ff, 0xff00, true);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ public class CpuAccumulatorModeTest 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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -583,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);
|
||||
|
||||
|
||||
@@ -19,19 +19,17 @@ import static org.mockito.Mockito.*;
|
||||
public class CrtcTest {
|
||||
|
||||
Crtc crtc;
|
||||
Memory memory;
|
||||
|
||||
@Mock
|
||||
DeviceChangeListener changeListener;
|
||||
@Mock
|
||||
Memory memory;
|
||||
|
||||
@Before
|
||||
public void createDevices() throws Exception {
|
||||
memory = new Memory(0, 0x7fff);
|
||||
|
||||
crtc = new Crtc(0x9000, memory);
|
||||
crtc.registerListener(changeListener);
|
||||
|
||||
when(memory.startAddress()).thenReturn(0);
|
||||
when(memory.endAddress()).thenReturn(0x7fff);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -340,4 +338,58 @@ public class CrtcTest {
|
||||
crtc.write(1, 0x80); // Can't position cursor
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetRowColumnAddressing() throws Exception {
|
||||
assertEquals(false, crtc.getRowColumnAddressing());
|
||||
crtc.write(0, 8); // Select mode control register
|
||||
crtc.write(1, 0x04);
|
||||
assertEquals(true, crtc.getRowColumnAddressing());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetDisplayEnableSkew() throws Exception {
|
||||
assertEquals(false, crtc.getDisplayEnableSkew());
|
||||
crtc.write(0, 8); // Select mode control register
|
||||
crtc.write(1, 0x10);
|
||||
assertEquals(true, crtc.getDisplayEnableSkew());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetCursorSkew() throws Exception {
|
||||
assertEquals(false, crtc.getCursorSkew());
|
||||
crtc.write(0, 8); // Select mode control register
|
||||
crtc.write(1, 0x20);
|
||||
assertEquals(true, crtc.getCursorSkew());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDoStraightBinaryAddressing() throws Exception {
|
||||
crtc.write(0, 8);
|
||||
crtc.write(1, 0); // Select straight binary addressing
|
||||
|
||||
// Fill the memory with a repeating pattern
|
||||
int videoMemoryBase = 0x7000;
|
||||
int j = 0;
|
||||
|
||||
for (int i = 0; i < 2048; i++) {
|
||||
memory.write(videoMemoryBase + i, j);
|
||||
if (j == 255) {
|
||||
j = 0;
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
// Now verify that straight-binary addressing of the CRTC works
|
||||
j = 0;
|
||||
|
||||
for (int i = 0; i < 2048; i++) {
|
||||
assertEquals(j, crtc.getCharAtAddress(videoMemoryBase + i));
|
||||
if (j == 255) {
|
||||
j = 0;
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||