Compare commits

..
64 Commits
Author SHA1 Message Date
Seth Morabito 4cca0b4663 Initial 65C02 Support
First pass at adding CMOS 65C02 support for the following instructions
and addressing modes:

   * ORA (ZP Indirect)
   * AND (ZP Indirect)
   * EOR (ZP Indirect)
   * ADC (ZP Indirect)
   * STA (ZP Indirect)
   * LDA (ZP Indirect)
   * CMP (ZP Indirect)
   * SBC (ZP Indirect)
2016-06-12 18:55:09 -07:00
Seth Morabito 5a2e057e69 Minor whitespace cleanup 2016-06-11 11:46:31 -07:00
Seth Morabito da88aadda2 Merge pull request #15 from LIV2/master
Correct BRK/IRQ behavior
2016-03-20 11:43:42 -07:00
Matt Harlum 657b69da6c Cleanup my comments 2016-03-20 17:05:48 +11:00
Chris Cureau f251e54174 Merge pull request #13 from sethm/cc65_update_patches
update Makefiles and configuration for latest cc65
2016-03-14 20:01:12 -05:00
Chris Cureau fcc57d9be4 update Makefiles and configuration for latest cc65 2016-01-12 08:14:38 -06:00
Seth Morabito 88eba2cdcb Merge branch '1.2.branch' 2016-01-08 20:53:54 -08:00
Seth Morabito 0e49b197b3 Update README for 1.2.1 2016-01-08 20:52:02 -08:00
Seth Morabito b59fa63b63 Merge branch '1.2.branch' 2016-01-08 20:41:25 -08:00
Seth Morabito eff98118d5 Fix a few straggling Java 8-isms. 2016-01-08 20:40:23 -08:00
Seth Morabito c599df1cfb Set version to 1.3.0-SNAPSHOT 2016-01-08 19:14:00 -08:00
Seth Morabito 66c52c8826 Revert Java 1.8 changes. Buildable with Java 1.7
There are still active users on Java 1.7, so building with Java 8
was a no-no. This change reverts the recent migration to Java 8,
allowing JDK 1.7 to compile the code.

This means, at least for the time being, no more Lambda expressions
(Boooooooooooooooo!!)
2016-01-08 19:11:42 -08:00
Seth Morabito f3a5dd93ad Release 1.2.0 2016-01-03 11:01:20 -08:00
Seth Morabito 4044458baa Fix typo 2016-01-03 10:59:11 -08:00
Seth Morabito 9ab94b3050 Prep for 1.2 2016-01-03 10:56:16 -08:00
Seth Morabito 9a504adc58 Added roadmap to README 2016-01-03 10:49:41 -08:00
Seth Morabito 57cbff42e7 Merge branch '1.1.branch' 2016-01-02 19:06:23 -08:00
Seth Morabito 634ea933f1 Add disassembled instructions to breakpoints 2016-01-02 19:05:38 -08:00
Seth Morabito 0ca30291cb Merge branch '1.1.branch' 2016-01-02 09:51:20 -08:00
Seth Morabito 95f85b71b1 Enter key can add breakpoints 2016-01-02 09:50:38 -08:00
Seth Morabito 6b5976be8f Update for 1.1.1 release 2016-01-02 09:46:12 -08:00
Seth Morabito 3530e9e99a Enter key can add breakpoints 2016-01-02 09:40:34 -08:00
Seth Morabito 7a9f9bfd55 Update images for 1.1.0 2016-01-01 09:49:39 -08:00
Seth Morabito 1e04a52d1b Prep for 1.2 2016-01-01 09:44:43 -08:00
Seth Morabito 7daeb9e978 Update README for 1.1.0 2015-12-31 16:38:27 -08:00
Seth Morabito da2750f4ee Release 1.1.0 2015-12-31 16:19:06 -08:00
Seth Morabito df88c54f90 Support for breakpoints
- Adds a new window that allows adding and deleting breakpoints.
  Will halt the simulator when a breakpoint is reached.
2015-12-31 16:09:50 -08:00
Seth Morabito 69e1985ec1 Update README 2015-12-30 11:03:23 -08:00
Seth Morabito 6267d1d777 Allow runtime selection of CPU speed 2015-12-30 10:56:03 -08:00
Seth Morabito 84e5c5ad56 Refactor delay loop 2015-12-30 10:08:44 -08:00
Seth Morabito 6e8fd40014 Refactor for Java 1.8
- Clean up and refactor code

- Add 1.8 features

- Clean up IntelliJ inspector warnings
2015-12-29 17:55:41 -08:00
Seth Morabito a4a110dcef Bugfixes, change logger, update copyright
- IR field in status panel now correctly displays the next instruction
  to be executed, instead of the instruction that was just executed.

- Switched from built-in Java util logger to Logback

- Updated all copyright strings to 2016
2015-12-29 14:40:42 -08:00
Matt Harlum 8335cf5421 Correct BRK behaviour
IRQ/NMI clear the BRK flag
BRK is Non-Maskable
2015-06-01 08:58:17 +10:00
Seth Morabito ae5c5d48b2 Merge pull request #12 from maikmerten/master
mask all bits beyond bits 0 to 7 on bus reads to ensure no surplus bits ...
2014-08-23 16:59:56 -07:00
Maik Merten 72ba068beb mask all bits beyond bits 0 to 7 on bus reads to ensure no surplus bits cause unpredictable system behavior. 2014-08-23 16:50:34 +02:00
Seth Morabito 142cb470a0 Converging on a useful CRTC ROM 2014-08-18 19:52:09 -07:00
Seth Morabito cad3673bd2 Formatting; uppercase 2014-08-16 16:54:36 -07:00
Seth Morabito 4796b65390 New ROMs. Better backspace handling. 2014-08-16 14:28:54 -07:00
Seth Morabito e0d6017d95 Unifying and cleaning up copyrights 2014-08-16 13:50:47 -07:00
Seth Morabito d6ea21bae4 Mostly working CRTC 2014-08-15 15:21:02 -07:00
Seth Morabito af12039686 Merge pull request #10 from maikmerten/master
Multicomp: SD card controller emulation
2014-08-15 13:51:26 -07:00
Seth Morabito ed1d5f999b Preparing for real implementation 2014-08-15 12:48:54 -07:00
Maik Merten c595ff5370 README update 2014-08-15 20:15:04 +02:00
Maik Merten e9f662cc17 Merge remote-tracking branch 'upstream/master'
Conflicts:
	src/main/java/com/loomcom/symon/machines/MulticompMachine.java
2014-08-15 20:11:33 +02:00
Seth Morabito 70ab2a6fc2 More CRTC tinkering 2014-08-14 21:03:52 -07:00
Seth Morabito f35fbce38b Add CRTC EhBASIC, switch char rom. 2014-08-14 11:50:51 -07:00
Seth Morabito eeb246ebc2 CRTC refactoring 2014-08-14 10:53:48 -07:00
Seth Morabito e157217f50 Fix missing repaint on Video Window 2014-08-13 15:46:20 -07:00
Maik Merten cc12e8f70a proper status value when SD controller is in write mode 2014-08-13 19:56:40 +02:00
Maik Merten a9a1c1aa52 get writing to a certain position in a file somewhat more correct 2014-08-13 19:38:54 +02:00
Seth Morabito 742e3a1027 Split PIA into PIA/VIA6522 2014-08-12 15:06:08 -07:00
Seth Morabito 45f596e0d4 Rename VIA to PIA
Preparing to create a PIA interface and one or more parallel
IO device implementations.
2014-08-12 14:19:55 -07:00
Seth Morabito 4402a28e2b Update README 2014-08-12 14:05:19 -07:00
Maik Merten 693d1959ac finer grained synchronization in TraceLog. Profiling indicates that this speeds up adding log entries quite considerably. 2014-08-12 21:58:52 +02:00
Maik Merten 792366fddb some cleanup in the SD card controller 2014-08-12 21:34:00 +02:00
Maik Merten b2670aedf1 email address update 2014-08-12 19:25:37 +02:00
Maik Merten 5d488bbcd2 Merge remote-tracking branch 'upstream/master' 2014-08-12 11:48:47 +02:00
Seth Morabito e2a1144c7c Update copyright, prep for 1.1.0 2014-08-11 14:16:41 -07:00
Maik Merten 010c42753f Merge remote-tracking branch 'upstream/master' 2014-08-11 09:34:44 +02:00
Maik Merten 7a215736fe rework SD controller emulation to work with Input- and OutputStreams (no longer load the image into a byte array) 2014-08-09 17:55:18 +02:00
Maik Merten c61a63d5bb more accurate emulation of the SD controller 2014-08-09 16:47:49 +02:00
Maik Merten 7cb4e1c945 NPE fix and reset position on every command 2014-08-09 15:13:59 +02:00
Maik Merten 3a40d35fdf use SD interface in MulticompMachine and lift the speed limit on the ACIA device to better match the experience on the real machine 2014-08-09 14:58:29 +02:00
Maik Merten d658cd0ae5 completely untested and incomplete implementation of the SD card interface of the MULTICOMP 2014-08-09 14:57:18 +02:00
67 changed files with 11643 additions and 1063 deletions

No files matched your search

+1 -1
View File
@@ -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
+72 -29
View File
@@ -1,9 +1,9 @@
SYMON - A 6502 System Simulator
===============================
**Version:** 1.0.0
**Version:** 1.2.1
**Last Updated:** 11 August, 2014
**Last Updated:** 8 January, 2016
See the file COPYING for license.
@@ -30,7 +30,7 @@ are always welcome.
## 2.0 Requirements
- Java 1.7 or higher
- Java 1.8 or higher
- Maven 2.0.x or higher (for building from source)
- JUnit 4 or higher (for testing)
@@ -58,6 +58,7 @@ memory.
- `$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
@@ -102,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
@@ -127,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
@@ -147,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.
@@ -170,9 +186,9 @@ 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.2.0.jar
When Symon is running, you should be presented with a simple graphical
interface.
@@ -219,7 +235,22 @@ running.
## 5.0 Revision History
- **1.0.0-SNAPSHOT:** 10 August, 2014 - Added "Simple" machine
- **1.2.1:** 8 January, 2016 - Remove dependency on Java 8. Now
supports compiling and running under Java 1.7.
- **1.2.0:** 3 January, 2016 - Add symbolic disassembly to breakpoints
window.
- **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).
@@ -261,7 +292,15 @@ running.
- **0.1:** 20 January, 2010
## 6.0 To Do
## 6.0 Roadmap
- **1.2:** Better breakpoint support. Symbolic debugging of breakpoints.
- **2.0:** Complete rewrite of the UI in JavaFX instead of Swing. Complete
assembler and disassembler built in. Ability to attach source code for
symbolic debugging.
## 7.0 To Do
- Feedback (in the form of dialogs, status bar, etc).
@@ -279,9 +318,13 @@ running.
- Allow displaying ACIA status and dumping ACIA buffers, for
debugging.
- 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
## 8.0 Copyright and Acknowledgements
**Copyright (c) 2014 Seth J. Morabito &lt;web@loomcom.com&gt;**
@@ -301,7 +344,7 @@ This project would not have been possible without the following resources:
- [Neil Parker's "The 6502/65C02/65C816 Instruction Set Decoded"] (http://www.llx.com/~nparker/a2/opcodes.html),
for information about how instructions are coded
## 8.0 Licensing
## 9.0 Licensing
Symon is free software. It is distributed under the MIT License.
Please see the file 'COPYING' for full details of the license.
+10 -26
View File
@@ -4,7 +4,7 @@
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<version>1.3.0-SNAPSHOT</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>
@@ -107,8 +91,8 @@
<version>3.1</version>
<configuration>
<compilerArgument>-Xlint:unchecked</compilerArgument>
<source>1.6</source>
<target>1.6</target>
<source>1.7</source>
<target>1.7</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>
+1 -1
View File
@@ -7,7 +7,7 @@ echo_irq: echo_irq.o
$(LD) -C symon.config -vm -m echo_irq.map -o echo_irq.rom echo_irq.o
echo_irq.o:
$(CA) --listing -o echo_irq.o echo_irq.asm
$(CA) --listing echo_irq.lst -o echo_irq.o echo_irq.asm
clean:
rm -f *.o *.rom *.map *.lst
+2 -2
View File
@@ -1,5 +1,5 @@
MEMORY {
RAM1: start = $0000, size = $8000;
RAM1: start = $0000, size = $8000 - __STACKSIZE__;
ROM1: start = $C000, size = $3FFA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
}
@@ -11,6 +11,6 @@ VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__ = $0300;
__STACKSIZE__: type = weak, value = $0300;
}
+1 -1
View File
@@ -7,7 +7,7 @@ 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
$(CA) --listing ehbasic.lst -o ehbasic.o min_mon.asm
clean:
rm -f *.o *.rom *.map *.lst
+1 -1
View File
@@ -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
+2 -2
View File
@@ -1,5 +1,5 @@
MEMORY {
RAM1: start = $0000, size = $8000;
RAM1: start = $0000, size = $8000 - __STACKSIZE__;
ROM1: start = $C000, size = $3F00, fill = yes;
MONITOR: start = $FF00, size = $FA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
@@ -13,6 +13,6 @@ VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__ = $0300;
__STACKSIZE__: type = weak, value = $0300;
}
+4
View File
@@ -0,0 +1,4 @@
*.rom
*.lst
*.map
*.o
+13
View File
@@ -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 ehbasic.lst -o ehbasic.o min_mon.asm
clean:
rm -f *.o *.rom *.map *.lst
+85
View File
@@ -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.
File diff suppressed because it is too large. Load diff
+449
View File
@@ -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
+18
View File
@@ -0,0 +1,18 @@
MEMORY {
RAM1: start = $0000, size = $8000 - __STACKSIZE__;
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__: type = weak, value = $0300;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

@@ -0,0 +1,85 @@
package com.loomcom.symon;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.Utils;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.TreeSet;
public class Breakpoints extends AbstractTableModel {
private TreeSet<Integer> breakpoints;
private Simulator simulator;
public Breakpoints(Simulator simulator) {
this.breakpoints = new TreeSet<>();
this.simulator = simulator;
}
public boolean contains(int address) {
return this.breakpoints.contains(address);
}
public void addBreakpoint(int address) {
this.breakpoints .add(address);
fireTableDataChanged();
}
public void removeBreakpoint(int address) {
this.breakpoints.remove(address);
fireTableDataChanged();
}
public void removeBreakpointAtIndex(int index) {
if (index < 0) {
return;
}
ArrayList<Integer> values = new ArrayList<>(breakpoints);
int value = values.get(index);
this.breakpoints.remove(value);
fireTableDataChanged();
}
public void refresh() {
fireTableDataChanged();
}
@Override
public String getColumnName(int index) {
if (index == 0) {
return "Address";
} else {
return "Inst";
}
}
@Override
public int getRowCount() {
return breakpoints.size();
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
ArrayList<Integer> values = new ArrayList<>(breakpoints);
if (columnIndex == 0) {
return "$" + Utils.wordToHex(values.get(rowIndex));
} else if (columnIndex == 1) {
int address = values.get(rowIndex);
try {
return simulator.disassembleOpAtAddress(address);
} catch (MemoryAccessException ex) {
return "???";
}
} else {
return null;
}
}
}
+45 -42
View File
@@ -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,6 +26,7 @@ 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;
@@ -42,7 +43,7 @@ public class Bus {
// The default address at which to load programs
public static int DEFAULT_LOAD_ADDRESS = 0x0200;
// By default, our bus starts at 0, and goes up to 64K
private int startAddress = 0x0000;
private int endAddress = 0xffff;
@@ -52,17 +53,17 @@ public class Bus {
// 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.deviceMap = new HashMap<Integer, SortedSet<Device>>();
this.deviceMap = new HashMap<>();
this.startAddress = startAddress;
this.endAddress = endAddress;
}
@@ -74,68 +75,70 @@ 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()) {
for (Device device : getDevices()) {
MemoryRange range = device.getMemoryRange();
for(int address = range.startAddress; address <= range.endAddress; ++address) {
for (int address = range.startAddress; address <= range.endAddress; ++address) {
deviceAddressArray[address - this.startAddress] = device;
}
}
}
/**
* Add a device to the bus.
*
* @param device
* @param priority
* @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) {
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) {
if (range.endAddress() < this.startAddress || range.endAddress() > this.endAddress) {
throw new MemoryRangeException("end address of device " + device.getName() + " does not fall within the address range of the bus");
}
SortedSet<Device> deviceSet = deviceMap.get(priority);
if(deviceSet == null) {
deviceSet = new TreeSet<Device>();
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 {
addDevice(device, 0);
}
/**
* Remove a device from the bus.
*
* @param device
* @param device Device to remove
*/
public void removeDevice(Device device) {
for(SortedSet<Device> deviceSet : deviceMap.values()) {
for (SortedSet<Device> deviceSet : deviceMap.values()) {
deviceSet.remove(device);
}
buildDeviceAddressArray();
@@ -152,39 +155,39 @@ public class Bus {
* device.
*/
public boolean isComplete() {
if(deviceAddressArray == null) {
if (deviceAddressArray == null) {
buildDeviceAddressArray();
}
for(int address = startAddress; address <= endAddress; ++address) {
if(deviceAddressArray[address - startAddress] == null) {
for (int address = startAddress; address <= endAddress; ++address) {
if (deviceAddressArray[address - startAddress] == null) {
return false;
}
}
return true;
}
public int read(int address) throws MemoryAccessException {
Device d = deviceAddressArray[address - this.startAddress];
if(d != null) {
if (d != null) {
MemoryRange range = d.getMemoryRange();
int devAddr = address - range.startAddress();
return d.read(devAddr);
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 {
Device d = deviceAddressArray[address - this.startAddress];
if(d != null) {
if (d != null) {
MemoryRange range = d.getMemoryRange();
int devAddr = address - range.startAddress();
d.write(devAddr, value);
return;
}
throw new MemoryAccessException("Bus write failed. No device at address " + String.format("$%04X", address));
}
@@ -214,18 +217,18 @@ public class Bus {
public SortedSet<Device> getDevices() {
// create an ordered set of devices, ordered by device priorities
SortedSet<Device> devices = new TreeSet<Device>();
List<Integer> priorities = new ArrayList<Integer>(deviceMap.keySet());
SortedSet<Device> devices = new TreeSet<>();
List<Integer> priorities = new ArrayList<>(deviceMap.keySet());
Collections.sort(priorities);
for(int priority : priorities) {
for (int priority : priorities) {
SortedSet<Device> deviceSet = deviceMap.get(priority);
for(Device device : deviceSet) {
for (Device device : deviceSet) {
devices.add(device);
}
}
return devices;
}
+242 -300
View File
@@ -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
@@ -24,9 +24,10 @@
package com.loomcom.symon;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.HexUtil;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.loomcom.symon.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides a simulation of the MOS 6502 CPU's state machine.
@@ -35,6 +36,8 @@ import java.util.logging.Logger;
*/
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;
@@ -55,25 +58,20 @@ public class Cpu implements InstructionTable {
public static final int IRQ_VECTOR_L = 0xfffe;
public static final int IRQ_VECTOR_H = 0xffff;
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;
/* The CPU state */
private static final CpuState state = new CpuState();
private 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;
@@ -81,7 +79,7 @@ public class Cpu implements InstructionTable {
* Construct a new CPU.
*/
public Cpu() {
this(CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG);
this(CpuBehavior.NMOS_6502);
}
public Cpu(CpuBehavior behavior) {
@@ -106,10 +104,6 @@ public class Cpu implements InstructionTable {
this.behavior = behavior;
}
public CpuBehavior getBehavior() {
return behavior;
}
/**
* Reset the CPU to known initial values.
*/
@@ -120,7 +114,7 @@ public class Cpu implements InstructionTable {
state.sp = 0xff;
// Set the PC to the address stored in the reset vector
state.pc = address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H));
state.pc = Utils.address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H));
// Clear instruction register.
state.ir = 0;
@@ -146,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 {
@@ -159,6 +155,7 @@ public class Cpu implements InstructionTable {
*/
public void step() throws MemoryAccessException {
opBeginTime = System.nanoTime();
// Store the address from which the IR was read, for debugging
state.lastPc = state.pc;
@@ -172,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();
@@ -190,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:
@@ -204,7 +202,14 @@ public class Cpu implements InstructionTable {
case 2: // Accumulator - ignored
break;
case 3: // Absolute
effectiveAddress = address(state.args[0], state.args[1]);
effectiveAddress = Utils.address(state.args[0], state.args[1]);
break;
case 4: // 65C02 (Zero Page)
if (behavior == CpuBehavior.CMOS_6502 ||
behavior == CpuBehavior.CMOS_65816) {
effectiveAddress = Utils.address(bus.read(state.args[0]),
bus.read((state.args[0] + 1) & 0xff));
}
break;
case 5: // Zero Page,X / Zero Page,Y
if (state.ir == 0x96 || state.ir == 0xb6) {
@@ -226,7 +231,7 @@ public class Cpu implements InstructionTable {
switch (irAddressMode) {
case 0: // (Zero Page,X)
tmp = (state.args[0] + state.x) & 0xff;
effectiveAddress = address(bus.read(tmp), bus.read(tmp + 1));
effectiveAddress = Utils.address(bus.read(tmp), bus.read(tmp + 1));
break;
case 1: // Zero Page
effectiveAddress = state.args[0];
@@ -235,10 +240,10 @@ public class Cpu implements InstructionTable {
effectiveAddress = -1;
break;
case 3: // Absolute
effectiveAddress = address(state.args[0], state.args[1]);
effectiveAddress = Utils.address(state.args[0], state.args[1]);
break;
case 4: // (Zero Page),Y
tmp = address(bus.read(state.args[0]),
tmp = Utils.address(bus.read(state.args[0]),
bus.read((state.args[0] + 1) & 0xff));
effectiveAddress = (tmp + state.y) & 0xffff;
break;
@@ -260,13 +265,11 @@ public class Cpu implements InstructionTable {
/** Single Byte Instructions; Implied and Relative **/
case 0x00: // BRK - Force Interrupt - Implied
if (!getIrqDisableFlag()) {
handleIrq(state.pc + 1);
}
handleBrk(state.pc + 1);
break;
case 0x08: // PHP - Push Processor Status - Implied
// Break flag is always set in the stack value.
stackPush(state.getStatusFlag() | 0x10);
stackPush(state.getStatusFlag() | 0x10);
break;
case 0x10: // BPL - Branch if Positive - Relative
if (!getNegativeFlag()) {
@@ -279,7 +282,7 @@ public class Cpu implements InstructionTable {
case 0x20: // JSR - Jump to Subroutine - Implied
stackPush((state.pc - 1 >> 8) & 0xff); // PC high byte
stackPush(state.pc - 1 & 0xff); // PC low byte
state.pc = address(state.args[0], state.args[1]);
state.pc = Utils.address(state.args[0], state.args[1]);
break;
case 0x28: // PLP - Pull Processor Status - Implied
setProcessorStatus(stackPop());
@@ -294,9 +297,9 @@ public class Cpu implements InstructionTable {
break;
case 0x40: // RTI - Return from Interrupt - Implied
setProcessorStatus(stackPop());
lo = stackPop();
hi = stackPop();
setProgramCounter(address(lo, hi));
int lo = stackPop();
int hi = stackPop();
setProgramCounter(Utils.address(lo, hi));
break;
case 0x48: // PHA - Push Accumulator - Implied
stackPush(state.a);
@@ -312,7 +315,7 @@ public class Cpu implements InstructionTable {
case 0x60: // RTS - Return from Subroutine - Implied
lo = stackPop();
hi = stackPop();
setProgramCounter((address(lo, hi) + 1) & 0xffff);
setProgramCounter((Utils.address(lo, hi) + 1) & 0xffff);
break;
case 0x68: // PLA - Pull Accumulator - Implied
state.a = stackPop();
@@ -400,20 +403,21 @@ public class Cpu implements InstructionTable {
/** JMP *****************************************************************/
case 0x4c: // JMP - Absolute
state.pc = address(state.args[0], state.args[1]);
state.pc = Utils.address(state.args[0], state.args[1]);
break;
// TODO: 65C02 instruction 0x7C, JMP (Indirect,X)
case 0x6c: // JMP - Indirect
lo = address(state.args[0], state.args[1]); // Address of low byte
lo = Utils.address(state.args[0], state.args[1]); // Address of low byte
if (state.args[0] == 0xff &&
(behavior == CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG ||
(behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG)) {
hi = address(0x00, state.args[1]);
hi = Utils.address(0x00, state.args[1]);
} else {
hi = lo + 1;
}
state.pc = address(bus.read(lo), bus.read(hi));
state.pc = Utils.address(bus.read(lo), bus.read(hi));
/* TODO: For accuracy, allow a flag to enable broken behavior of early 6502s:
*
* "An original 6502 has does not correctly fetch the target
@@ -426,13 +430,20 @@ public class Cpu implements InstructionTable {
* (http://www.obelisk.demon.co.uk/6502/reference.html#JMP)
*/
break;
case 0x7C: // 65C02 JMP - Absolute,X
state.pc = (Utils.address(state.args[0], state.args[1]) + state.x) & 0xffff;
break;
/** ORA - Logical Inclusive Or ******************************************/
case 0x09: // #Immediate
state.a |= state.args[0];
setArithmeticFlags(state.a);
break;
case 0x12: // 65C02 ORA (ZP)
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
case 0x01: // (Zero Page,X)
case 0x05: // Zero Page
case 0x0d: // Absolute
@@ -475,6 +486,11 @@ public class Cpu implements InstructionTable {
state.a &= state.args[0];
setArithmeticFlags(state.a);
break;
case 0x32: // 65C02 AND (ZP)
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
case 0x21: // (Zero Page,X)
case 0x25: // Zero Page
case 0x2d: // Absolute
@@ -507,6 +523,11 @@ public class Cpu implements InstructionTable {
state.a ^= state.args[0];
setArithmeticFlags(state.a);
break;
case 0x52: // 65C02 EOR (ZP)
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
case 0x41: // (Zero Page,X)
case 0x45: // Zero Page
case 0x4d: // Absolute
@@ -542,6 +563,11 @@ public class Cpu implements InstructionTable {
state.a = adc(state.a, state.args[0]);
}
break;
case 0x72: // 65C02 ADC (ZP)
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
case 0x61: // (Zero Page,X)
case 0x65: // Zero Page
case 0x6d: // Absolute
@@ -573,6 +599,11 @@ public class Cpu implements InstructionTable {
/** STA - Store Accumulator *********************************************/
case 0x92: // 65C02 STA (ZP)
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
case 0x81: // (Zero Page,X)
case 0x85: // Zero Page
case 0x8d: // Absolute
@@ -633,6 +664,11 @@ public class Cpu implements InstructionTable {
state.a = state.args[0];
setArithmeticFlags(state.a);
break;
case 0xb2: // 65C02 LDA (ZP)
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
case 0xa1: // (Zero Page,X)
case 0xa5: // Zero Page
case 0xad: // Absolute
@@ -659,6 +695,11 @@ public class Cpu implements InstructionTable {
case 0xc9: // #Immediate
cmp(state.a, state.args[0]);
break;
case 0xd2: // 65C02 CMP (ZP)
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
case 0xc1: // (Zero Page,X)
case 0xc5: // Zero Page
case 0xcd: // Absolute
@@ -699,6 +740,11 @@ public class Cpu implements InstructionTable {
state.a = sbc(state.a, state.args[0]);
}
break;
case 0xf2: // 65C02 SBC (ZP)
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
case 0xe1: // (Zero Page,X)
case 0xe5: // Zero Page
case 0xed: // Absolute
@@ -732,15 +778,32 @@ 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 handleBrk(int returnPc) throws MemoryAccessException {
handleInterrupt(returnPc, IRQ_VECTOR_L, IRQ_VECTOR_H, true);
clearIrq();
}
private void handleIrq(int returnPc) throws MemoryAccessException {
handleInterrupt(returnPc, IRQ_VECTOR_L, IRQ_VECTOR_H);
handleInterrupt(returnPc, IRQ_VECTOR_L, IRQ_VECTOR_H, false);
clearIrq();
}
private void handleNmi() throws MemoryAccessException {
handleInterrupt(state.pc, NMI_VECTOR_L, NMI_VECTOR_H);
handleInterrupt(state.pc, NMI_VECTOR_L, NMI_VECTOR_H, false);
clearNmi();
}
@@ -749,9 +812,19 @@ public class Cpu implements InstructionTable {
*
* @throws MemoryAccessException
*/
private void handleInterrupt(int returnPc, int vectorLow, int vectorHigh) throws MemoryAccessException {
// Set the break flag before pushing.
setBreakFlag();
private void handleInterrupt(int returnPc, int vectorLow, int vectorHigh, boolean isBreak) throws MemoryAccessException {
if (isBreak)
{
// Set the break flag before pushing.
setBreakFlag();
}
else
{
// IRQ & NMI clear break flag
clearBreakFlag();
}
// Push program counter + 1 onto the stack
stackPush((returnPc >> 8) & 0xff); // PC high byte
stackPush(returnPc & 0xff); // PC low byte
@@ -760,7 +833,7 @@ public class Cpu implements InstructionTable {
setIrqDisableFlag();
// Load interrupt vector address into PC
state.pc = address(bus.read(vectorLow), bus.read(vectorHigh));
state.pc = Utils.address(bus.read(vectorLow), bus.read(vectorHigh));
}
/**
@@ -770,7 +843,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();
@@ -893,6 +966,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.
*
@@ -1117,6 +1198,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() {
@@ -1172,23 +1261,23 @@ public class Cpu implements InstructionTable {
}
public String getAccumulatorStatus() {
return "$" + HexUtil.byteToHex(state.a);
return "$" + Utils.byteToHex(state.a);
}
public String getXRegisterStatus() {
return "$" + HexUtil.byteToHex(state.x);
return "$" + Utils.byteToHex(state.x);
}
public String getYRegisterStatus() {
return "$" + HexUtil.byteToHex(state.y);
return "$" + Utils.byteToHex(state.y);
}
public String getProgramCounterStatus() {
return "$" + HexUtil.wordToHex(state.pc);
return "$" + Utils.wordToHex(state.pc);
}
public String getStackPointerStatus() {
return "$" + HexUtil.byteToHex(state.sp);
return "$" + Utils.byteToHex(state.sp);
}
public int getProcessorStatus() {
@@ -1272,19 +1361,12 @@ public class Cpu implements InstructionTable {
}
}
/**
* Given two bytes, return an address.
*/
int address(int lowByte, int hiByte) {
return ((hiByte << 8) | lowByte) & 0xffff;
}
/**
* Given a hi byte and a low byte, return the Absolute,X
* offset address.
*/
int xAddress(int lowByte, int hiByte) {
return (address(lowByte, hiByte) + state.x) & 0xffff;
return (Utils.address(lowByte, hiByte) + state.x) & 0xffff;
}
/**
@@ -1292,7 +1374,7 @@ public class Cpu implements InstructionTable {
* offset address.
*/
int yAddress(int lowByte, int hiByte) {
return (address(lowByte, hiByte) + state.y) & 0xffff;
return (Utils.address(lowByte, hiByte) + state.y) & 0xffff;
}
/**
@@ -1302,6 +1384,13 @@ public class Cpu implements InstructionTable {
return (zp + state.x) & 0xff;
}
/**
* Given a single byte, compute the Zero Page,Y offset address.
*/
int zpyAddress(int zp) {
return (zp + state.y) & 0xff;
}
/**
* Given a single byte, compute the offset address.
*/
@@ -1310,254 +1399,107 @@ public class Cpu implements InstructionTable {
return (state.pc + (byte) offset) & 0xffff;
}
/**
* Given a single byte, compute the Zero Page,Y offset address.
*/
int zpyAddress(int zp) {
return (zp + state.y) & 0xff;
}
/*
* 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 opScheduledEnd = opBeginTime + clockSteps;
long now = System.nanoTime();
while(now < opScheduledEnd) {
now = System.nanoTime();
}
}
final int clockSteps;
if (behavior == CpuBehavior.NMOS_WITH_ROR_BUG ||
behavior == CpuBehavior.NMOS_6502) {
clockSteps = Cpu.instructionClocksNmos[0xff & opcode];
} else {
clockSteps = Cpu.instructionClocksCmos[0xff & opcode];
}
if (clockSteps == 0) {
logger.warn("Opcode {} has clock step of 0!", String.format("0x%02x", opcode));
return;
}
long interval = clockSteps * clockPeriodInNs;
long end;
do {
end = System.nanoTime();
} while (opBeginTime + interval >= end);
}
/**
* A compact, struct-like representation of CPU state.
* 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 static class CpuState {
/**
* Accumulator
*/
public int a;
/**
* X index regsiter
*/
public int x;
/**
* Y index register
*/
public int y;
/**
* Stack Pointer
*/
public int sp;
/**
* Program Counter
*/
public int pc;
/**
* Instruction Register
*/
public int ir;
public int lastPc;
public int[] args = new int[2];
public int instSize;
public boolean opTrap;
public boolean irqAsserted;
public boolean nmiAsserted;
public static String disassembleOp(int opCode, int[] args) {
String mnemonic = opcodeNames[opCode];
/* Status Flag Register bits */
public boolean carryFlag;
public boolean negativeFlag;
public boolean zeroFlag;
public boolean irqDisableFlag;
public boolean decimalModeFlag;
public boolean breakFlag;
public boolean overflowFlag;
public long stepCounter = 0L;
/**
* Create an empty CPU State.
*/
public CpuState() {}
/**
* Snapshot a copy of the CpuState.
*
* (This is a copy constructor rather than an implementation of <code>Clonable</code>
* based on Josh Bloch's recommendation)
*
* @param s The CpuState to copy.
*/
public CpuState(CpuState s) {
this.a = s.a;
this.x = s.x;
this.y = s.y;
this.sp = s.sp;
this.pc = s.pc;
this.ir = s.ir;
this.lastPc = s.lastPc;
this.args[0] = s.args[0];
this.args[1] = s.args[1];
this.instSize = s.instSize;
this.opTrap = s.opTrap;
this.irqAsserted = s.irqAsserted;
this.carryFlag = s.carryFlag;
this.negativeFlag = s.negativeFlag;
this.zeroFlag = s.zeroFlag;
this.irqDisableFlag = s.irqDisableFlag;
this.decimalModeFlag = s.decimalModeFlag;
this.breakFlag = s.breakFlag;
this.overflowFlag = s.overflowFlag;
this.stepCounter = s.stepCounter;
if (mnemonic == null) {
return "???";
}
/**
* Returns a string formatted for the trace log.
*
* @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();
StringBuilder sb = new StringBuilder(mnemonic);
switch (instructionModes[opCode]) {
case ABS:
sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1])));
break;
case ABX:
sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(",X");
break;
case ABY:
sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(",Y");
break;
case IMM:
sb.append(" #$").append(Utils.byteToHex(args[0]));
break;
case IND:
sb.append(" ($").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(")");
break;
case XIN:
sb.append(" ($").append(Utils.byteToHex(args[0])).append(",X)");
break;
case INY:
sb.append(" ($").append(Utils.byteToHex(args[0])).append("),Y");
break;
case REL:
case ZPG:
sb.append(" $").append(Utils.byteToHex(args[0]));
break;
case ZPX:
sb.append(" $").append(Utils.byteToHex(args[0])).append(",X");
break;
case ZPY:
sb.append(" $").append(Utils.byteToHex(args[0])).append(",Y");
break;
}
/**
* @returns The value of the Process Status Register, as a byte.
*/
public int getStatusFlag() {
int status = 0x20;
if (carryFlag) {
status |= P_CARRY;
}
if (zeroFlag) {
status |= P_ZERO;
}
if (irqDisableFlag) {
status |= P_IRQ_DISABLE;
}
if (decimalModeFlag) {
status |= P_DECIMAL;
}
if (breakFlag) {
status |= P_BREAK;
}
if (overflowFlag) {
status |= P_OVERFLOW;
}
if (negativeFlag) {
status |= P_NEGATIVE;
}
return status;
return sb.toString();
}
/**
* 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 Cpu.disassembleOp(state.nextIr, state.nextArgs);
}
/**
* @param address Address to disassemble
* @return String containing the disassembled instruction and operands.
*/
public String disassembleOpAtAddress(int address) throws MemoryAccessException {
int opCode = bus.read(address);
int args[] = new int[2];
int size = Cpu.instructionSizes[opCode];
for (int i = 1; i < size; i++) {
int nextRead = (address + i) % bus.endAddress();
args[i-1] = bus.read(nextRead);
}
public String getInstructionByteStatus() {
switch (Cpu.instructionSizes[ir]) {
case 0:
case 1:
return HexUtil.wordToHex(lastPc) + " " +
HexUtil.byteToHex(ir) + " ";
case 2:
return HexUtil.wordToHex(lastPc) + " " +
HexUtil.byteToHex(ir) + " " +
HexUtil.byteToHex(args[0]) + " ";
case 3:
return HexUtil.wordToHex(lastPc) + " " +
HexUtil.byteToHex(ir) + " " +
HexUtil.byteToHex(args[0]) + " " +
HexUtil.byteToHex(args[1]);
default:
return null;
}
}
/**
* Given an opcode and its operands, return a formatted name.
*
* @return A string representing the mnemonic and operands of the instruction
*/
public String disassembleOp() {
String mnemonic = opcodeNames[ir];
if (mnemonic == null) {
return "???";
}
StringBuilder sb = new StringBuilder(mnemonic);
switch (instructionModes[ir]) {
case ABS:
sb.append(" $" + HexUtil.wordToHex(address(args[0], args[1])));
break;
case ABX:
sb.append(" $" + HexUtil.wordToHex(address(args[0], args[1])) + ",X");
break;
case ABY:
sb.append(" $" + HexUtil.wordToHex(address(args[0], args[1])) + ",Y");
break;
case IMM:
sb.append(" #$" + HexUtil.byteToHex(args[0]));
break;
case IND:
sb.append(" ($" + HexUtil.wordToHex(address(args[0], args[1])) + ")");
break;
case XIN:
sb.append(" ($" + HexUtil.byteToHex(args[0]) + ",X)");
break;
case INY:
sb.append(" ($" + HexUtil.byteToHex(args[0]) + "),Y");
break;
case REL:
case ZPG:
sb.append(" $" + HexUtil.byteToHex(args[0]));
break;
case ZPX:
sb.append(" $" + HexUtil.byteToHex(args[0]) + ",X");
break;
case ZPY:
sb.append(" $" + HexUtil.byteToHex(args[0]) + ",Y");
break;
}
return sb.toString();
}
/**
* Given two bytes, return an address.
*/
private int address(int lowByte, int hiByte) {
return ((hiByte << 8) | lowByte) & 0xffff;
}
/**
* @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 disassembleOp(opCode, args);
}
}
@@ -0,0 +1,177 @@
package com.loomcom.symon;
import com.loomcom.symon.util.Utils;
/**
* A compact, struct-like representation of CPU state.
*/
public class CpuState {
/**
* Accumulator
*/
public int a;
/**
* X index regsiter
*/
public int x;
/**
* Y index register
*/
public int y;
/**
* Stack Pointer
*/
public int sp;
/**
* Program Counter
*/
public int pc;
/**
* Last Loaded Instruction Register
*/
public int ir;
/**
* 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;
public boolean negativeFlag;
public boolean zeroFlag;
public boolean irqDisableFlag;
public boolean decimalModeFlag;
public boolean breakFlag;
public boolean overflowFlag;
public long stepCounter = 0L;
public CpuState() {}
/**
* Snapshot a copy of the CpuState.
*
* (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.
*/
public CpuState(CpuState s) {
this.a = s.a;
this.x = s.x;
this.y = s.y;
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;
this.carryFlag = s.carryFlag;
this.negativeFlag = s.negativeFlag;
this.zeroFlag = s.zeroFlag;
this.irqDisableFlag = s.irqDisableFlag;
this.decimalModeFlag = s.decimalModeFlag;
this.breakFlag = s.breakFlag;
this.overflowFlag = s.overflowFlag;
this.stepCounter = s.stepCounter;
}
/**
* Returns a string formatted for the trace log.
*
* @return a string formatted for the trace log.
*/
public String toTraceEvent() {
String opcode = Cpu.disassembleOp(ir, args);
return getInstructionByteStatus() + " " +
String.format("%-14s", opcode) +
"A:" + Utils.byteToHex(a) + " " +
"X:" + Utils.byteToHex(x) + " " +
"Y:" + Utils.byteToHex(y) + " " +
"F:" + Utils.byteToHex(getStatusFlag()) + " " +
"S:1" + Utils.byteToHex(sp) + " " +
getProcessorStatusString() + "\n";
}
/**
* @return The value of the Process Status Register, as a byte.
*/
public int getStatusFlag() {
int status = 0x20;
if (carryFlag) {
status |= Cpu.P_CARRY;
}
if (zeroFlag) {
status |= Cpu.P_ZERO;
}
if (irqDisableFlag) {
status |= Cpu.P_IRQ_DISABLE;
}
if (decimalModeFlag) {
status |= Cpu.P_DECIMAL;
}
if (breakFlag) {
status |= Cpu.P_BREAK;
}
if (overflowFlag) {
status |= Cpu.P_OVERFLOW;
}
if (negativeFlag) {
status |= Cpu.P_NEGATIVE;
}
return status;
}
public String getInstructionByteStatus() {
switch (Cpu.instructionSizes[ir]) {
case 0:
case 1:
return Utils.wordToHex(lastPc) + " " +
Utils.byteToHex(ir) + " ";
case 2:
return Utils.wordToHex(lastPc) + " " +
Utils.byteToHex(ir) + " " +
Utils.byteToHex(args[0]) + " ";
case 3:
return Utils.wordToHex(lastPc) + " " +
Utils.byteToHex(ir) + " " +
Utils.byteToHex(args[0]) + " " +
Utils.byteToHex(args[1]);
default:
return null;
}
}
/**
* @return A string representing the current status register state.
*/
public String getProcessorStatusString() {
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
@@ -46,26 +46,23 @@ public interface InstructionTable {
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_WITH_INDIRECT_JMP_BUG,
/**
* Emulate an NMOS 6502 without the indirect JMP bug. This type of 6502
* does not actually exist in the wild.
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_WITHOUT_INDIRECT_JMP_BUG,
NMOS_6502,
/**
* Emulate a CMOS 65C02, with all CMOS instructions and addressing modes.
*/
CMOS
CMOS_6502,
/**
* Emulate a CMOS 65C816.
*/
CMOS_65816
}
/**
* Enumeration of Addressing Modes.
*/
public enum Mode {
enum Mode {
ACC {
public String toString() {
return "Accumulator";
@@ -128,19 +125,25 @@ public interface InstructionTable {
ZPG {
public String toString() {
return "Zeropage";
return "Zero Page";
}
},
ZPX {
public String toString() {
return "Zeropage, X-indexed";
return "Zero Page, X-indexed";
}
},
ZPY {
public String toString() {
return "Zeropage, Y-indexed";
return "Zero Page, Y-indexed";
}
},
ZPI {
public String toString() {
return "Zero Page Indirect";
}
},
@@ -154,52 +157,55 @@ public interface InstructionTable {
// 6502 opcodes. No 65C02 opcodes implemented.
/**
* Instruction opcode names.
* Instruction opcode names. This lists all opcodes for
* NMOS 6502, CMOS 65C02, and CMOS 65C816
*/
public static final 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,
"CLC", "ORA", null, null, null, "ORA", "ASL", null,
"JSR", "AND", null, null, "BIT", "AND", "ROL", null,
"PLP", "AND", "ROL", null, "BIT", "AND", "ROL", null,
"BMI", "AND", null, null, null, "AND", "ROL", null,
"SEC", "AND", null, null, null, "AND", "ROL", null,
"RTI", "EOR", null, null, null, "EOR", "LSR", null,
"PHA", "EOR", "LSR", null, "JMP", "EOR", "LSR", null,
"BVC", "EOR", null, null, null, "EOR", "LSR", null,
"CLI", "EOR", null, null, null, "EOR", "LSR", null,
"RTS", "ADC", null, null, null, "ADC", "ROR", null,
"PLA", "ADC", "ROR", null, "JMP", "ADC", "ROR", null,
"BVS", "ADC", null, null, null, "ADC", "ROR", null,
"SEI", "ADC", null, null, null, "ADC", "ROR", null,
"BCS", "STA", null, null, "STY", "STA", "STX", null,
"DEY", null, "TXA", null, "STY", "STA", "STX", null,
"BCC", "STA", null, null, "STY", "STA", "STX", null,
"TYA", "STA", "TXS", null, null, "STA", null, null,
"LDY", "LDA", "LDX", null, "LDY", "LDA", "LDX", null,
"TAY", "LDA", "TAX", null, "LDY", "LDA", "LDX", null,
"BCS", "LDA", null, null, "LDY", "LDA", "LDX", null,
"CLV", "LDA", "TSX", null, "LDY", "LDA", "LDX", null,
"CPY", "CMP", null, null, "CPY", "CMP", "DEC", null,
"INY", "CMP", "DEX", null, "CPY", "CMP", "DEC", null,
"BNE", "CMP", null, null, null, "CMP", "DEC", null,
"CLD", "CMP", null, null, null, "CMP", "DEC", null,
"CPX", "SBC", null, null, "CPX", "SBC", "INC", null,
"INX", "SBC", "NOP", null, "CPX", "SBC", "INC", null,
"BEQ", "SBC", null, null, null, "SBC", "INC", null,
"SED", "SBC", null, null, null, "SBC", "INC", null
String[] opcodeNames = {
"BRK", "ORA", null, null, null, "ORA", "ASL", null, // 0x00-0x07
"PHP", "ORA", "ASL", null, null, "ORA", "ASL", null, // 0x08-0x0f
"BPL", "ORA", "ORA", null, null, "ORA", "ASL", null, // 0x10-0x17
"CLC", "ORA", null, null, null, "ORA", "ASL", null, // 0x18-0x1f
"JSR", "AND", null, null, "BIT", "AND", "ROL", null, // 0x20-0x27
"PLP", "AND", "ROL", null, "BIT", "AND", "ROL", null, // 0x28-0x2f
"BMI", "AND", "AND", null, null, "AND", "ROL", null, // 0x30-0x37
"SEC", "AND", null, null, null, "AND", "ROL", null, // 0x38-0x3f
"RTI", "EOR", null, null, null, "EOR", "LSR", null, // 0x40-0x47
"PHA", "EOR", "LSR", null, "JMP", "EOR", "LSR", null, // 0x48-0x4f
"BVC", "EOR", "EOR", null, null, "EOR", "LSR", null, // 0x50-0x57
"CLI", "EOR", null, null, null, "EOR", "LSR", null, // 0x58-0x5f
"RTS", "ADC", null, null, null, "ADC", "ROR", null, // 0x60-0x67
"PLA", "ADC", "ROR", null, "JMP", "ADC", "ROR", null, // 0x68-0x6f
"BVS", "ADC", "ADC", null, null, "ADC", "ROR", null, // 0x70-0x77
"SEI", "ADC", null, null, "JMP", "ADC", "ROR", null, // 0x78-0x7f
"BCS", "STA", null, null, "STY", "STA", "STX", null, // 0x80-0x87
"DEY", null, "TXA", null, "STY", "STA", "STX", null, // 0x88-0x8f
"BCC", "STA", "STA", null, "STY", "STA", "STX", null, // 0x90-0x97
"TYA", "STA", "TXS", null, null, "STA", null, null, // 0x98-0x9f
"LDY", "LDA", "LDX", null, "LDY", "LDA", "LDX", null, // 0xa0-0xa7
"TAY", "LDA", "TAX", null, "LDY", "LDA", "LDX", null, // 0xa8-0xaf
"BCS", "LDA", "LDA", null, "LDY", "LDA", "LDX", null, // 0xb0-0xb7
"CLV", "LDA", "TSX", null, "LDY", "LDA", "LDX", null, // 0xb8-0xbf
"CPY", "CMP", null, null, "CPY", "CMP", "DEC", null, // 0xc0-0xc7
"INY", "CMP", "DEX", null, "CPY", "CMP", "DEC", null, // 0xc8-0xcf
"BNE", "CMP", "CMP", null, null, "CMP", "DEC", null, // 0xd0-0xd7
"CLD", "CMP", null, null, null, "CMP", "DEC", null, // 0xd8-0xdf
"CPX", "SBC", null, null, "CPX", "SBC", "INC", null, // 0xe0-0xe7
"INX", "SBC", "NOP", null, "CPX", "SBC", "INC", null, // 0xe8-0xef
"BEQ", "SBC", "SBC", null, null, "SBC", "INC", null, // 0xf0-0xf7
"SED", "SBC", null, null, null, "SBC", "INC", null // 0xf8-0xff
};
/**
* Instruction addressing modes.
* Instruction addressing modes. This table includes sizes
* for all instructions for NMOS 6502, CMOS 65C02,
* and CMOS 65C816
*/
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
Mode.NUL, Mode.ABS, Mode.ABS, Mode.NUL, // 0x0c-0x0f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x10-0x13
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0x10-0x13
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x14-0x17
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x18-0x1b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x1c-0x1f
@@ -207,7 +213,7 @@ public interface InstructionTable {
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x24-0x27
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x28-0x2b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x2c-0x2f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x30-0x33
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0x30-0x33
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x34-0x37
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x38-0x3b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x3c-0x3f
@@ -215,7 +221,7 @@ public interface InstructionTable {
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x44-0x47
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x48-0x4b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x4c-0x4f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x50-0x53
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0x50-0x53
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x54-0x57
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x58-0x5b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x5c-0x5f
@@ -223,15 +229,15 @@ public interface InstructionTable {
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x64-0x67
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x68-0x6b
Mode.IND, Mode.ABS, Mode.ABS, Mode.NUL, // 0x6c-0x6f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x70-0x73
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0x70-0x73
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x74-0x77
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x78-0x7b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x7c-0x7f
Mode.ABX, Mode.ABX, Mode.ABX, Mode.NUL, // 0x7c-0x7f
Mode.REL, Mode.XIN, Mode.NUL, Mode.NUL, // 0x80-0x83
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x84-0x87
Mode.IMP, Mode.NUL, Mode.IMP, Mode.NUL, // 0x88-0x8b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x8c-0x8f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x90-0x93
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0x90-0x93
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0x94-0x97
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0x98-0x9b
Mode.NUL, Mode.ABX, Mode.NUL, Mode.NUL, // 0x9c-0x9f
@@ -239,7 +245,7 @@ public interface InstructionTable {
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xa4-0xa7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xa8-0xab
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xac-0xaf
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xb0-0xb3
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0xb0-0xb3
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0xb4-0xb7
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0xb8-0xbb
Mode.ABX, Mode.ABX, Mode.ABY, Mode.NUL, // 0xbc-0xbf
@@ -247,7 +253,7 @@ public interface InstructionTable {
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xc4-0xc7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xc8-0xcb
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xcc-0xcf
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xd0-0xd3
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0xd0-0xd3
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0xd4-0xd7
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0xd8-0xdb
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0xdc-0xdf
@@ -255,7 +261,7 @@ public interface InstructionTable {
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xe4-0xe7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xe8-0xeb
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xec-0xef
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xf0-0xf3
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0xf0-0xf3
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0xf4-0xf7
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0xf8-0xfb
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL // 0xfc-0xff
@@ -263,47 +269,73 @@ public interface InstructionTable {
/**
* Size, in bytes, required for each instruction.
* Size, in bytes, required for each instruction. This table
* includes sizes for all instructions for NMOS 6502, CMOS 65C02,
* and CMOS 65C816
*/
public static final 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,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0,
2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0
int[] instructionSizes = {
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 0, 3, 3, 0, // 0x00-0x0f
2, 2, 2, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, // 0x10-0x1f
3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 0x20-0x2f
2, 2, 2, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, // 0x30-0x3f
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 0x40-0x4f
2, 2, 2, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, // 0x50-0x5f
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 0x60-0x6f
2, 2, 2, 0, 0, 2, 2, 0, 1, 3, 0, 0, 3, 3, 3, 0, // 0x70-0x7f
2, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0, // 0x80-0x8f
2, 2, 2, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0, // 0x90-0x9f
2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 0xa0-0xaf
2, 2, 2, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, // 0xb0-0xbf
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 0xc0-0xcf
2, 2, 2, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, // 0xd0-0xdf
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 0xe0-0xef
2, 2, 2, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0 // 0xf0-0xff
};
/**
* Number of clock cycles required for each instruction
* Number of clock cycles required for each instruction when
* in NMOS mode.
*/
public static final 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,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 3, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 0, 3, 5, 0, 4, 2, 2, 0, 5, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
2, 6, 0, 0, 3, 3, 3, 0, 2, 0, 2, 0, 4, 4, 4, 0,
2, 6, 0, 0, 4, 4, 4, 0, 2, 5, 2, 0, 0, 5, 0, 0,
2, 6, 2, 0, 3, 3, 3, 0, 2, 2, 2, 0, 4, 4, 4, 0,
2, 5, 0, 0, 4, 4, 4, 0, 2, 4, 2, 0, 4, 4, 4, 0,
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0
int[] instructionClocksNmos = {
7, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 0, 4, 6, 0, // 0x00-0x0f
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0, // 0x10-0x1f
6, 6, 0, 0, 3, 3, 5, 0, 4, 2, 2, 0, 4, 4, 6, 0, // 0x20-0x2f
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0, // 0x30-0x3f
6, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 3, 4, 6, 0, // 0x40-0x4f
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0, // 0x50-0x5f
6, 6, 0, 0, 0, 3, 5, 0, 4, 2, 2, 0, 5, 4, 6, 0, // 0x60-0x6f
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0, // 0x70-0x7f
2, 6, 0, 0, 3, 3, 3, 0, 2, 0, 2, 0, 4, 4, 4, 0, // 0x80-0x8f
2, 6, 0, 0, 4, 4, 4, 0, 2, 5, 2, 0, 0, 5, 0, 0, // 0x90-0x9f
2, 6, 2, 0, 3, 3, 3, 0, 2, 2, 2, 0, 4, 4, 4, 0, // 0xa0-0xaf
2, 5, 0, 0, 4, 4, 4, 0, 2, 4, 2, 0, 4, 4, 4, 0, // 0xb0-0xbf
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0, // 0xc0-0xcf
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0, // 0xd0-0xdf
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0, // 0xe0-0xef
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0 // 0xf0-0xff
};
/**
* Number of clock cycles required for each instruction when
* in CMOS mode
*/
int[] instructionClocksCmos = {
7, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 0, 4, 6, 0, // 0x00-0x0f
2, 5, 5, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0, // 0x10-0x1f
6, 6, 0, 0, 3, 3, 5, 0, 4, 2, 2, 0, 4, 4, 6, 0, // 0x20-0x2f
2, 5, 5, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0, // 0x30-0x3f
6, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 3, 4, 6, 0, // 0x40-0x4f
2, 5, 5, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0, // 0x50-0x5f
6, 6, 0, 0, 0, 3, 5, 0, 4, 2, 2, 0, 5, 4, 6, 0, // 0x60-0x6f
2, 5, 5, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0, // 0x70-0x7f
2, 6, 0, 0, 3, 3, 3, 0, 2, 0, 2, 0, 4, 4, 4, 0, // 0x80-0x8f
2, 6, 5, 0, 4, 4, 4, 0, 2, 5, 2, 0, 0, 5, 0, 0, // 0x90-0x9f
2, 6, 2, 0, 3, 3, 3, 0, 2, 2, 2, 0, 4, 4, 4, 0, // 0xa0-0xaf
2, 5, 5, 0, 4, 4, 4, 0, 2, 4, 2, 0, 4, 4, 4, 0, // 0xb0-0xbf
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0, // 0xc0-0xcf
2, 5, 5, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0, // 0xd0-0xdf
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0, // 0xe0-0xef
2, 5, 5, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0 // 0xf0-0xff
};
}
+16 -11
View File
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.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
@@ -39,7 +39,7 @@ public class Main {
* Main entry point to the simulator. Creates a simulator and shows the main
* window.
*
* @param args
* @param args Program arguments
*/
public static void main(String args[]) throws Exception {
@@ -48,12 +48,16 @@ public class Main {
String arg = args[i].toLowerCase(Locale.ENGLISH);
if(arg.equals("-machine") && (i+1) < args.length) {
String machine = args[i+1].trim().toLowerCase(Locale.ENGLISH);
if(machine.equals("symon")) {
machineClass = SymonMachine.class;
} else if(machine.equals("multicomp")) {
machineClass = MulticompMachine.class;
} else if (machine.equals("simple")) {
machineClass = SimpleMachine.class;
switch (machine) {
case "symon":
machineClass = SymonMachine.class;
break;
case "multicomp":
machineClass = MulticompMachine.class;
break;
case "simple":
machineClass = SimpleMachine.class;
break;
}
}
}
@@ -83,6 +87,7 @@ public class Main {
final Simulator simulator = new Simulator(machineClass);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
@@ -95,8 +100,8 @@ public class Main {
});
Simulator.MAIN_CMD cmd = simulator.waitForCommand();
if (cmd.equals(Simulator.MAIN_CMD.SELECTMACHINE)) {
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();
}
+227 -130
View File
@@ -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
@@ -24,21 +24,20 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
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.
@@ -50,10 +49,15 @@ import java.util.logging.Logger;
*/
public class Simulator {
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
@@ -66,14 +70,12 @@ public class Simulator {
//
private static final int MAX_STEPS_BETWEEN_UPDATES = 20000;
private final static Logger logger = Logger.getLogger(Simulator.class.getName());
// 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
@@ -93,44 +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 softResetButton;
private JButton hardResetButton;
private JComboBox<String> stepCountBox;
private JFileChooser fileChooser;
private JFileChooser fileChooser;
private PreferencesDialog preferences;
private Breakpoints breakpoints;
private final Object commandMonitorObject = new Object();
private MAIN_CMD command = MAIN_CMD.NONE;
public static enum MAIN_CMD {
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(Class machineClass) throws Exception {
this.breakpoints = new Breakpoints(this);
this.machine = (Machine) machineClass.getConstructors()[0].newInstance();
// Initialize final fields in the constructor.
this.traceLog = new TraceLog();
this.memoryWindow = new MemoryWindow(machine.getBus());
this.breakpointsWindow = new BreakpointsWindow(breakpoints, mainWindow);
if (machine.getCrtc() != null) {
videoWindow = new VideoWindow(machine.getCrtc(), 2, 2);
} else {
videoWindow = null;
}
}
/**
@@ -143,7 +162,7 @@ public class Simulator {
mainWindow.getContentPane().setLayout(new BorderLayout());
// UI components used for I/O.
this.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT);
this.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT, false);
this.statusPane = new StatusPanel(machine);
console.setBorderWidth(CONSOLE_BORDER_WIDTH);
@@ -162,11 +181,12 @@ public class Simulator {
runStopButton = new JButton("Run");
stepButton = new JButton("Step");
softResetButton = new JButton("Soft Reset");
hardResetButton = new JButton("Hard Reset");
JButton softResetButton = new JButton("Soft Reset");
JButton hardResetButton = new JButton("Hard Reset");
stepCountBox = new JComboBox<String>(STEPS);
stepCountBox = new JComboBox<>(STEPS);
stepCountBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
try {
JComboBox cb = (JComboBox) actionEvent.getSource();
@@ -195,47 +215,40 @@ public class Simulator {
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
runStopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (runLoop != null && runLoop.isRunning()) {
handleStop();
Simulator.this.handleStop();
} else {
handleStart();
Simulator.this.handleStart();
}
}
});
stepButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
handleStep(stepsPerClick);
Simulator.this.handleStep(stepsPerClick);
}
});
softResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// If this was a CTRL-click, do a hard reset.
handleReset(false);
Simulator.this.handleReset(false);
}
});
hardResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// If this was a CTRL-click, do a hard reset.
handleReset(true);
Simulator.this.handleReset(true);
}
});
// Prepare the log window
traceLog = new TraceLog();
// Prepare the memory window
memoryWindow = new MemoryWindow(machine.getBus());
// Composite Video and 6545 CRTC
if(machine.getCrtc() != null) {
videoWindow = new VideoWindow(machine.getCrtc(), 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.
@@ -248,9 +261,9 @@ public class Simulator {
console.requestFocus();
handleReset(false);
}
public MAIN_CMD waitForCommand() {
synchronized(commandMonitorObject) {
public MainCommand waitForCommand() {
synchronized (commandMonitorObject) {
try {
commandMonitorObject.wait();
} catch (InterruptedException ex) {
@@ -259,7 +272,7 @@ public class Simulator {
}
return command;
}
private void handleStart() {
// Shift focus to the console.
@@ -287,7 +300,7 @@ public class Simulator {
}
try {
logger.log(Level.INFO, "Reset requested. Resetting CPU.");
logger.debug("Reset requested. Resetting CPU.");
// Reset CPU
machine.getCpu().reset();
// Clear the console.
@@ -302,15 +315,9 @@ public class Simulator {
}
}
// Update status.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState();
memoryWindow.updateState();
}
});
updateVisibleState();
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Exception during simulator reset: " + ex.getMessage());
logger.error("Exception during simulator reset", ex);
}
}
@@ -322,17 +329,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();
memoryWindow.updateState();
}
});
updateVisibleState();
} catch (SymonException ex) {
logger.log(Level.SEVERE, "Exception during simulator step: " + ex.getMessage());
logger.error("Exception during simulator step", ex);
ex.printStackTrace();
}
}
@@ -359,27 +358,23 @@ public class Simulator {
machine.getAcia().rxWrite((int) console.readInputChar());
}
} catch (FifoUnderrunException ex) {
logger.severe("Console type-ahead buffer underrun!");
logger.error("Console type-ahead buffer underrun!");
}
if (videoWindow != null && stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) {
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();
memoryWindow.updateState();
}
});
updateVisibleState();
stepsSinceLastUpdate = 0;
}
}
/**
@@ -391,8 +386,7 @@ public class Simulator {
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)
@@ -402,16 +396,9 @@ public class Simulator {
machine.getCpu().setProgramCounter(preferences.getProgramStartAddress());
// Immediately update the UI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState();
memoryWindow.updateState();
}
});
updateVisibleState();
}
/**
* The main run thread.
*/
@@ -427,10 +414,11 @@ 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() {
@Override
public void run() {
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
@@ -446,11 +434,11 @@ 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() {
@Override
public void run() {
statusPane.updateState();
memoryWindow.updateState();
@@ -469,15 +457,19 @@ public class Simulator {
}
/**
* 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() && machine.getCpu().getInstruction() == 0x00);
return !breakpoints.contains(machine.getCpu().getProgramCounter()) &&
isRunning &&
!(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00);
}
}
public String disassembleOpAtAddress(int address) throws MemoryAccessException {
return machine.getCpu().disassembleOpAtAddress(address);
}
class LoadProgramAction extends AbstractAction {
public LoadProgramAction() {
super("Load Program...", null);
@@ -508,14 +500,17 @@ public class Simulator {
program[i++] = dis.readByte();
}
// Now load the program at the starting address.
loadProgram(program, preferences.getProgramStartAddress());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
console.reset();
breakpoints.refresh();
}
});
// Now load the program at the starting address.
loadProgram(program, preferences.getProgramStartAddress());
// TODO: "Don't Show Again" checkbox
JOptionPane.showMessageDialog(mainWindow,
"Loaded Successfully At " +
@@ -526,10 +521,10 @@ public class Simulator {
}
}
} 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);
}
}
@@ -552,34 +547,39 @@ public class Simulator {
if (fileSize != machine.getRomSize()) {
throw new IOException("ROM file must be exactly " + String.valueOf(machine.getRomSize()) + " bytes.");
} else {
// 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();
logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " +
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);
}
// 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();
// Refresh breakpoints to show new memory contents.
breakpoints.refresh();
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);
}
}
@@ -596,7 +596,7 @@ public class Simulator {
preferences.getDialog().setVisible(true);
}
}
class SelectMachineAction extends AbstractAction {
public SelectMachineAction() {
super("Switch emulated machine...", null);
@@ -605,19 +605,19 @@ public class Simulator {
}
public void actionPerformed(ActionEvent actionEvent) {
if(runLoop != null) {
if (runLoop != null) {
runLoop.requestStop();
}
memoryWindow.dispose();
traceLog.dispose();
if(videoWindow != null) {
if (videoWindow != null) {
videoWindow.dispose();
}
mainWindow.dispose();
command = MAIN_CMD.SELECTMACHINE;
synchronized(commandMonitorObject) {
command = MainCommand.SELECTMACHINE;
synchronized (commandMonitorObject) {
commandMonitorObject.notifyAll();
}
}
@@ -645,11 +645,12 @@ 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() {
@Override
public void run() {
console.setFont(new Font("Monospaced", Font.PLAIN, size));
mainWindow.pack();
@@ -658,6 +659,25 @@ public class Simulator {
}
}
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);
@@ -710,6 +730,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;
@@ -762,9 +799,6 @@ public class Simulator {
JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction());
fileMenu.add(prefsItem);
JMenuItem selectMachineItem = new JMenuItem(new SelectMachineAction());
fileMenu.add(selectMachineItem);
JMenuItem quitItem = new JMenuItem(new QuitAction());
fileMenu.add(quitItem);
@@ -810,7 +844,7 @@ public class Simulator {
});
viewMenu.add(showMemoryTable);
if(videoWindow != null) {
if (videoWindow != null) {
final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction());
videoWindow.addWindowListener(new WindowAdapter() {
@Override
@@ -822,16 +856,79 @@ public class Simulator {
}
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(new Runnable() {
@Override
public void run() {
// 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
@@ -82,6 +82,7 @@ public abstract class Acia extends Device {
/**
* @return The simulated baud rate in bps.
*/
@SuppressWarnings("unused")
public int getBaudRate() {
return baudRate;
}
@@ -153,6 +154,7 @@ public abstract class Acia extends Device {
/**
* @return true if there is character data in the RX register.
*/
@SuppressWarnings("unused")
public boolean hasRxChar() {
return rxFull;
}
@@ -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,7 +26,6 @@ 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.
@@ -45,7 +44,6 @@ public class Acia6551 extends Acia {
static final int CMND_REG = 2;
static final int CTRL_REG = 3;
/**
* Registers. These are ignored in the current implementation.
*/
@@ -106,7 +104,7 @@ public class Acia6551 extends Acia {
/**
* Set the control register and associated state.
*
* @param data
* @param data Data to write into the control register
*/
private void setControlRegister(int data) {
controlRegister = data;
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.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
@@ -41,20 +41,12 @@ public class Acia6850 extends Acia {
public static final int ACIA_SIZE = 2;
static final int STAT_REG = 0; // read-only
static final int CTRL_REG = 0; // write-only
static final int RX_REG = 1; // read-only
static final int TX_REG = 1; // write-only
/**
* Registers. These are ignored in the current implementation.
*/
private int commandRegister;
public Acia6850(int address) throws MemoryRangeException {
super(address, ACIA_SIZE, "ACIA6850");
setBaudRate(2400);
@@ -88,17 +80,15 @@ public class Acia6850 extends Acia {
}
private void setCommandRegister(int data) {
commandRegister = data;
// Bits 0 & 1 control the master reset
if((commandRegister & 0x01) != 0 && (commandRegister & 0x02) != 0) {
if((data & 0x01) != 0 && (data & 0x02) != 0) {
reset();
}
// Bit 7 controls receiver IRQ behavior
receiveIrqEnabled = (commandRegister & 0x80) != 0;
receiveIrqEnabled = (data & 0x80) != 0;
// Bits 5 & 6 controls transmit IRQ behavior
transmitIrqEnabled = (commandRegister & 0x20) != 0 && (commandRegister & 0x40) == 0;
transmitIrqEnabled = (data & 0x20) != 0 && (data & 0x40) == 0;
}
@@ -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,8 +115,8 @@ public abstract class Device implements Comparable<Device> {
}
public void notifyListeners() {
for (DeviceChangeListener l : deviceChangeListeners) {
l.deviceStateChanged();
for (DeviceChangeListener listener : deviceChangeListeners) {
listener.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,210 @@
/*
* 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 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();
}
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2008-2014 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.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
@@ -29,33 +29,33 @@ import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.exceptions.MemoryRangeException;
public interface Machine {
public Bus getBus();
Bus getBus();
Cpu getCpu();
public Cpu getCpu();
Memory getRam();
public Memory getRam();
Acia getAcia();
public Acia getAcia();
Pia getPia();
public Via getVia();
Crtc getCrtc();
public Crtc getCrtc();
Memory getRom();
public Memory getRom();
void setRom(Memory rom) throws MemoryRangeException;
public void setRom(Memory rom) throws MemoryRangeException;
int getRomBase();
public int getRomBase();
int getRomSize();
public int getRomSize();
public int getMemorySize();
int getMemorySize();
String getName();
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2008-2014 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.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
@@ -30,7 +30,8 @@ import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6850;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.devices.SdController;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.File;
import java.util.logging.Logger;
@@ -51,6 +52,8 @@ public class MulticompMachine implements Machine {
// 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;
@@ -70,10 +73,12 @@ public class MulticompMachine implements Machine {
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");
@@ -111,7 +116,7 @@ public class MulticompMachine implements Machine {
}
@Override
public Via getVia() {
public Pia getPia() {
return null;
}
@@ -1,3 +1,27 @@
/*
* 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;
@@ -5,7 +29,7 @@ import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
@@ -52,7 +76,7 @@ public class SimpleMachine implements Machine {
}
@Override
public Via getVia() {
public Pia getPia() {
return null;
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2008-2014 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.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
@@ -26,19 +26,16 @@ package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6551;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.devices.*;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.File;
import java.util.logging.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public class SymonMachine implements Machine {
private final static Logger logger = Logger.getLogger(SymonMachine.class.getName());
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;
@@ -48,16 +45,15 @@ public class SymonMachine implements Machine {
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0x8000;
// VIA at $8000-$800F
// PIA at $8000-$800F
private static final int VIA_BASE = 0x8000;
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;
private static final int VIDEO_RAM_BASE = 0x7000;
// 16KB ROM at $C000-$FFFF
private static final int ROM_BASE = 0xC000;
@@ -68,7 +64,7 @@ public class SymonMachine implements Machine {
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Via via;
private final Pia pia;
private final Crtc crtc;
private final Memory ram;
private Memory rom;
@@ -78,24 +74,23 @@ public class SymonMachine implements Machine {
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.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(via);
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);
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.");
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);
}
@@ -124,8 +119,8 @@ public class SymonMachine implements Machine {
}
@Override
public Via getVia() {
return via;
public Pia getPia() {
return pia;
}
@Override
@@ -0,0 +1,152 @@
/*
* 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.Breakpoints;
import com.loomcom.symon.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* 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 Breakpoints breakpoints;
public BreakpointsWindow(Breakpoints 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));
final JButton addButton = new JButton("Add");
final JButton removeButton = new JButton("Del");
removeButton.setEnabled(false);
final JTextField addTextField = new JTextField(4);
final JTable breakpointsTable = new JTable(breakpoints);
breakpointsTable.setShowGrid(true);
breakpointsTable.setGridColor(Color.LIGHT_GRAY);
breakpointsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
breakpointsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getFirstIndex() > -1) {
removeButton.setEnabled(true);
} else {
removeButton.setEnabled(false);
}
}
});
JScrollPane scrollPane = new JScrollPane(breakpointsTable);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
breakpointsPanel.add(scrollPane, BorderLayout.CENTER);
ActionListener addBreakpointListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int value;
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;
}
breakpoints.addBreakpoint(value);
logger.debug("Added breakpoint ${}", Utils.wordToHex(value));
addTextField.setText(EMPTY_STRING);
}
};
addButton.addActionListener(addBreakpointListener);
addTextField.addActionListener(addBreakpointListener);
removeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
breakpoints.removeBreakpointAtIndex(breakpointsTable.getSelectedRow());
}
});
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
@@ -25,7 +25,9 @@ package com.loomcom.symon.ui;
import com.loomcom.symon.Bus;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.HexUtil;
import com.loomcom.symon.util.Utils;
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;
@@ -104,7 +107,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
previousPageButton.setEnabled(pageNumber > 0x00);
nextPageButton.setEnabled(pageNumber < 0xff);
pageNumberTextField.setText(HexUtil.byteToHex(pageNumber));
pageNumberTextField.setText(Utils.byteToHex(pageNumber));
}
/**
@@ -209,9 +212,9 @@ public class MemoryWindow extends JFrame implements ActionListener {
} 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();
}
}
@@ -325,13 +328,13 @@ public class MemoryWindow extends JFrame implements ActionListener {
public Object getValueAt(int row, int column) {
try {
if (column == 0) {
return HexUtil.wordToHex(fullAddress(row, 1));
return Utils.wordToHex(fullAddress(row, 1));
} else if (column < 9) {
// Display hex value of the data
return HexUtil.byteToHex(bus.read(fullAddress(row, column)));
return Utils.byteToHex(bus.read(fullAddress(row, column)));
} else {
// Display the ASCII equivalent (if printable)
return HexUtil.byteToAscii(bus.read(fullAddress(row, column - 8)));
return Utils.byteToAscii(bus.read(fullAddress(row, column - 8)));
}
} catch (MemoryAccessException ex) {
return "??";
@@ -346,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
@@ -98,19 +98,21 @@ public class PreferencesDialog extends Observable implements Preferences {
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
updateUi();
PreferencesDialog.this.updateUi();
dialog.setVisible(false);
}
});
applyButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
haltOnBreak = haltOnBreakCheckBox.isSelected();
programLoadAddress = hexToInt(programLoadAddressField.getText());
updateUi();
programLoadAddress = PreferencesDialog.this.hexToInt(programLoadAddressField.getText());
PreferencesDialog.this.updateUi();
// TODO: Actually check to see if values have changed, don't assume.
setChanged();
PreferencesDialog.this.setChanged();
PreferencesDialog.this.notifyObservers();
dialog.setVisible(false);
}
@@ -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,6 +24,7 @@
package com.loomcom.symon.ui;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.CpuState;
import com.loomcom.symon.machines.Machine;
import javax.swing.*;
@@ -32,9 +33,6 @@ import javax.swing.border.EtchedBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
/**
* UI component that displays the current state of the simulated CPU.
@@ -56,7 +54,6 @@ public class StatusPanel extends JPanel {
private ImageIcon negativeOn;
private ImageIcon negativeOff;
private JLabel statusFlagsLabel;
private JLabel carryFlagLabel;
private JLabel zeroFlagLabel;
private JLabel irqDisableFlagLabel;
@@ -72,13 +69,6 @@ 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;
@@ -153,13 +143,13 @@ 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");
@@ -178,13 +168,13 @@ public class StatusPanel extends JPanel {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = getHexVal(pcField) & 0xffff;
int newVal = StatusPanel.this.getHexVal(pcField) & 0xffff;
machine.getCpu().setProgramCounter(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
StatusPanel.this.updateState();
}
});
@@ -192,13 +182,13 @@ public class StatusPanel extends JPanel {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = getHexVal(spField) & 0xff;
int newVal = StatusPanel.this.getHexVal(spField) & 0xff;
machine.getCpu().setStackPointer(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
StatusPanel.this.updateState();
}
});
@@ -206,13 +196,13 @@ public class StatusPanel extends JPanel {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = getHexVal(aField) & 0xff;
int newVal = StatusPanel.this.getHexVal(aField) & 0xff;
machine.getCpu().setAccumulator(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
StatusPanel.this.updateState();
}
});
@@ -220,13 +210,13 @@ public class StatusPanel extends JPanel {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = getHexVal(xField) & 0xff;
int newVal = StatusPanel.this.getHexVal(xField) & 0xff;
machine.getCpu().setXRegister(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
StatusPanel.this.updateState();
}
});
@@ -234,13 +224,13 @@ public class StatusPanel extends JPanel {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = getHexVal(yField) & 0xff;
int newVal = StatusPanel.this.getHexVal(yField) & 0xff;
machine.getCpu().setYRegister(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
StatusPanel.this.updateState();
}
});
@@ -303,7 +293,7 @@ public class StatusPanel extends JPanel {
*/
public void updateState() {
Cpu cpu = machine.getCpu();
Cpu.CpuState cpuState = cpu.getCpuState();
CpuState cpuState = cpu.getCpuState();
// Update the Processor Status Flag display
int status = cpuState.getStatusFlag();
@@ -317,7 +307,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.disassembleNextOp());
pcField.setText(cpu.getProgramCounterStatus());
spField.setText(cpu.getStackPointerStatus());
aField.setText(cpu.getAccumulatorStatus());
@@ -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
@@ -23,7 +24,7 @@
package com.loomcom.symon.ui;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.CpuState;
import com.loomcom.symon.util.FifoRingBuffer;
import javax.swing.*;
@@ -35,15 +36,15 @@ import java.awt.*;
*/
public class TraceLog extends JFrame {
private FifoRingBuffer<Cpu.CpuState> traceLog;
private JTextArea traceLogTextArea;
private final FifoRingBuffer<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();
for (Cpu.CpuState state : traceLog) {
StringBuilder logString = new StringBuilder();
synchronized(traceLog) {
for (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);
}
@@ -92,9 +99,9 @@ public class TraceLog extends JFrame {
*
* @param state The CPU State to append.
*/
public void append(Cpu.CpuState state) {
synchronized(this) {
traceLog.push(new Cpu.CpuState(state));
public void append(CpuState state) {
synchronized(traceLog) {
traceLog.push(new 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
@@ -116,10 +123,11 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private class CursorBlinker implements Runnable {
public void run() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (cursorBlinkRate > 0) {
hideCursor = !hideCursor;
repaint();
VideoWindow.this.repaint();
}
}
});
@@ -131,8 +139,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 +165,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 +239,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
@@ -24,24 +24,9 @@
package com.loomcom.symon.util;
/**
* Hex String Utilities.
*
* <p/>
*
* But why? Java, after all, has a number of ways to convert an integer into a hex string,
* so it may look absurd to go to the trouble of writing yet another conversion! The answer is
* performance.
*
* <p/>
*
* The most convenient way to get a formatted hex value from an integer is with the <code>String.format</code>
* method, but this turns out to be extremely inefficient. Formatting a million integers
* with <code>String.format</code> takes something like 1600ms. Formatting the same number of integers
* with <code>HexUtil</code> takes only 160ms. This is on par with <code>Integer.toHexString</code>,
* but also gives the desired padding.
*
* Various Utilities
*/
public class HexUtil {
public class Utils {
static final String NON_PRINTABLE = ".";
@@ -93,6 +78,7 @@ public class HexUtil {
/**
* Very fast 8-bit int to ASCII conversion.
*
* @param val The value of an ASCII character.
* @return A string representing the ASCII character.
*/
@@ -121,9 +107,13 @@ 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];
}
/**
* Given two bytes, return an address.
*/
public static int address(int lowByte, int hiByte) {
return ((hiByte << 8) | lowByte) & 0xffff;
}
}
Binary file not shown.
Binary file not shown.
@@ -117,9 +117,22 @@ public class CpuImpliedModeTest {
}
@Test
public void test_BRK_HonorsIrqDisableFlag() throws MemoryAccessException {
public void test_BRK_IgnoresIrqDisableFlag() throws MemoryAccessException {
cpu.setIrqDisableFlag();
cpu.setCarryFlag();
cpu.setOverflowFlag();
assertEquals(0x20 | Cpu.P_CARRY | Cpu.P_OVERFLOW | Cpu.P_IRQ_DISABLE,
cpu.getProcessorStatus());
assertEquals(0x00, cpu.stackPeek());
assertFalse(cpu.getBreakFlag());
assertEquals(0x0200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
// Set the IRQ vector
bus.write(0xffff, 0x12);
bus.write(0xfffe, 0x34);
bus.loadProgram(0xea,
0xea,
0xea,
@@ -131,23 +144,26 @@ public class CpuImpliedModeTest {
assertEquals(0x203, cpu.getProgramCounter());
// Triggers the BRK, which should do nothing because
// of the Interrupt Disable flag
// Triggers the BRK
cpu.step();
// Reset to original contents of PC
assertEquals(0x0204, cpu.getProgramCounter());
// Empty stack
assertEquals(0xff, cpu.getStackPointer());
cpu.step(2); // Two more NOPs
// Was at PC = 0x204. PC+1 should now be on the stack
assertEquals(0x02, bus.read(0x1ff)); // PC high byte
assertEquals(0x05, bus.read(0x1fe)); // PC low byte
assertEquals(0x20 | Cpu.P_CARRY | Cpu.P_OVERFLOW | Cpu.P_BREAK | Cpu.P_IRQ_DISABLE,
bus.read(0x1fd)); // Processor Status, with B set
// Reset to original contents of PC
assertEquals(0x0206, cpu.getProgramCounter());
// Empty stack
assertEquals(0xff, cpu.getStackPointer());
// Interrupt vector held 0x1234, so we should be there.
assertEquals(0x1234, cpu.getProgramCounter());
assertEquals(0xfc, cpu.getStackPointer());
// B and I flags should have been set on P
assertEquals(0x20 | Cpu.P_CARRY | Cpu.P_OVERFLOW | Cpu.P_BREAK | Cpu.P_IRQ_DISABLE,
cpu.getProcessorStatus());
}
/* CLC - Clear Carry Flag - $18 */
@Test
public void test_CLC() throws MemoryAccessException {
@@ -25,18 +25,10 @@ public class CpuIndirectIndexedModeTest {
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
// Assert initial state
assertEquals(0, cpu.getAccumulator());
assertEquals(0, cpu.getXRegister());
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
}
@Test
public void test_LDA() throws Exception {
assertEquals(cpu.toString(), 0x00, cpu.getAccumulator());
bus.write(0x0014, 0x00);
bus.write(0x0015, 0xd8);
bus.write(0xd828, 0x03);
@@ -3,15 +3,19 @@ package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Test;
public class CpuIndirectModeTest extends TestCase {
import static org.junit.Assert.assertEquals;
public class CpuIndirectModeTest {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
protected void setUp() throws Exception {
@Before
public void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
@@ -41,6 +45,7 @@ public class CpuIndirectModeTest extends TestCase {
/* JMP - Jump - $6c */
@Test
public void test_JMP_notOnPageBoundary() throws MemoryAccessException {
bus.write(0x3400, 0x00);
bus.write(0x3401, 0x54);
@@ -51,6 +56,7 @@ public class CpuIndirectModeTest extends TestCase {
assertEquals(0x20, cpu.getProcessorStatus());
}
@Test
public void test_JMP_with_ROR_Bug() throws MemoryAccessException {
cpu.setBehavior(Cpu.CpuBehavior.NMOS_WITH_ROR_BUG);
bus.write(0x3400, 0x22);
@@ -63,8 +69,9 @@ public class CpuIndirectModeTest extends TestCase {
assertEquals(0x20, cpu.getProcessorStatus());
}
@Test
public void test_JMP_withIndirectBug() throws MemoryAccessException {
cpu.setBehavior(Cpu.CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG);
cpu.setBehavior(Cpu.CpuBehavior.NMOS_6502);
bus.write(0x3400, 0x22);
bus.write(0x34ff, 0x00);
bus.write(0x3500, 0x54);
@@ -75,8 +82,9 @@ public class CpuIndirectModeTest extends TestCase {
assertEquals(0x20, cpu.getProcessorStatus());
}
@Test
public void test_JMP_withOutIndirectBug() throws MemoryAccessException {
cpu.setBehavior(Cpu.CpuBehavior.NMOS_WITHOUT_INDIRECT_JMP_BUG);
cpu.setBehavior(Cpu.CpuBehavior.CMOS_6502);
bus.write(0x3400, 0x22);
bus.write(0x34ff, 0x00);
bus.write(0x3500, 0x54);
@@ -87,8 +95,9 @@ public class CpuIndirectModeTest extends TestCase {
assertEquals(0x20, cpu.getProcessorStatus());
}
@Test
public void test_JMP_cmos() throws MemoryAccessException {
cpu.setBehavior(Cpu.CpuBehavior.CMOS);
cpu.setBehavior(Cpu.CpuBehavior.CMOS_6502);
bus.write(0x3400, 0x22);
bus.write(0x34ff, 0x00);
bus.write(0x3500, 0x54);
@@ -3,14 +3,21 @@ package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Test;
public class CpuIndirectXModeTest extends TestCase {
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class CpuIndirectXModeTest {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
protected void setUp() throws Exception {
@Before
public void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
@@ -47,7 +54,7 @@ public class CpuIndirectXModeTest extends TestCase {
*/
/* ORA - Logical Inclusive OR - $1d */
@Test
public void test_ORA() throws MemoryAccessException {
// Set some initial values in memory
bus.write(0x2c30, 0x00);
@@ -93,7 +100,7 @@ public class CpuIndirectXModeTest extends TestCase {
}
/* AND - Logical AND - $3d */
@Test
public void test_AND() throws MemoryAccessException {
bus.write(0x1a30, 0x00);
bus.write(0x1a31, 0x11);
@@ -153,7 +160,7 @@ public class CpuIndirectXModeTest extends TestCase {
}
/* EOR - Exclusive OR - $5d */
@Test
public void test_EOR() throws MemoryAccessException {
bus.write(0xab40, 0x00);
bus.write(0xab41, 0xff);
@@ -162,7 +169,7 @@ public class CpuIndirectXModeTest extends TestCase {
cpu.setXRegister(0x30);
bus.loadProgram(0xa9, 0x88, // LDA #$88
bus.loadProgram(0xa9, 0x88, // LDA #$88
0x5d, 0x10, 0xab, // EOR $ab10,X
0x5d, 0x11, 0xab, // EOR $ab11,X
0x5d, 0x12, 0xab, // EOR $ab12,X
@@ -189,7 +196,7 @@ public class CpuIndirectXModeTest extends TestCase {
}
/* ADC - Add with Carry - $7d */
@Test
public void test_ADC() throws MemoryAccessException {
bus.write(0xab40, 0x01);
bus.write(0xab41, 0xff);
@@ -290,6 +297,7 @@ public class CpuIndirectXModeTest extends TestCase {
assertTrue(cpu.getCarryFlag());
}
@Test
public void test_ADC_IncludesCarry() throws MemoryAccessException {
bus.write(0xab40, 0x01);
@@ -307,6 +315,7 @@ public class CpuIndirectXModeTest extends TestCase {
assertFalse(cpu.getCarryFlag());
}
@Test
public void test_ADC_DecimalMode() throws MemoryAccessException {
bus.write(0xab40, 0x01);
bus.write(0xab41, 0x99);
@@ -404,7 +413,7 @@ public class CpuIndirectXModeTest extends TestCase {
}
/* STA - Store Accumulator - $9d */
@Test
public void test_STA() throws MemoryAccessException {
cpu.setXRegister(0x30);
@@ -438,7 +447,7 @@ public class CpuIndirectXModeTest extends TestCase {
}
/* LDA - Load Accumulator - $bd */
@Test
public void test_LDA() throws MemoryAccessException {
bus.write(0xab42, 0x00);
bus.write(0xab43, 0x0f);
@@ -467,7 +476,7 @@ public class CpuIndirectXModeTest extends TestCase {
}
/* CMP - Compare Accumulator - $dd */
@Test
public void test_CMP() throws MemoryAccessException {
bus.write(0xab40, 0x00);
bus.write(0xab41, 0x80);
@@ -498,7 +507,7 @@ public class CpuIndirectXModeTest extends TestCase {
}
/* SBC - Subtract with Carry - $fd */
@Test
public void test_SBC() throws MemoryAccessException {
bus.write(0xab40, 0x01);
@@ -557,6 +566,7 @@ public class CpuIndirectXModeTest extends TestCase {
assertTrue(cpu.getCarryFlag());
}
@Test
public void test_SBC_IncludesNotOfCarry() throws MemoryAccessException {
bus.write(0xab40, 0x01);
@@ -602,6 +612,7 @@ public class CpuIndirectXModeTest extends TestCase {
}
@Test
public void test_SBC_DecimalMode() throws MemoryAccessException {
bus.write(0xab40, 0x01);
bus.write(0xab50, 0x11);
+73 -5
View File
@@ -1,5 +1,6 @@
package com.loomcom.symon;
import com.loomcom.symon.util.Utils;
import junit.framework.*;
import com.loomcom.symon.devices.*;
@@ -240,6 +241,7 @@ public class CpuTest extends TestCase {
public void testSetProcessorStatus() {
// Default
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getIrqDisableFlag());
@@ -383,6 +385,41 @@ public class CpuTest extends TestCase {
assertFalse(cpu.getCpuState().irqAsserted);
}
public void testIrqDoesNotSetBRK() throws Exception {
// Ensure the IRQ disable flag is cleared
cpu.clearIrqDisableFlag();
// Set the IRQ vector
bus.write(0xffff, 0x12);
bus.write(0xfffe, 0x34);
// Create an IRQ handler at 0x1234
cpu.setProgramCounter(0x1234);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
cpu.setProgramCounter(0x0200);
// Create a little program at 0x0200
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertIrq();
cpu.step();
assertTrue(cpu.getIrqDisableFlag()); // Should have been set by the IRQ
assertFalse(cpu.getBreakFlag());
}
public void testIrqHonorsIrqDisabledFlag() throws Exception {
// Ensure the IRQ disable flag is set
cpu.setIrqDisableFlag();
@@ -497,6 +534,37 @@ public class CpuTest extends TestCase {
assertFalse(cpu.getCpuState().nmiAsserted);
}
public void testNmiDoesNotSetBRK() throws Exception {
// Set the NMI vector to 0x1000
bus.write(0xfffb, 0x10);
bus.write(0xfffa, 0x00);
// Create an NMI handler at 0x1000
cpu.setProgramCounter(0x1000);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
// Create a little program at 0x0200
cpu.setProgramCounter(0x0200);
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertNmi();
cpu.step();
assertFalse(cpu.getBreakFlag());
}
public void testNmiIgnoresIrqDisableFlag() throws Exception {
// Set the IRQ disable flag, which should be ignored by the NMI
cpu.setIrqDisableFlag();
@@ -537,11 +605,11 @@ public class CpuTest extends TestCase {
public void testAddress() {
assertEquals(0xf1ea, cpu.address(0xea, 0xf1));
assertEquals(0x00ea, cpu.address(0xea, 0x00));
assertEquals(0xf100, cpu.address(0x00, 0xf1));
assertEquals(0x1234, cpu.address(0x34, 0x12));
assertEquals(0xffff, cpu.address(0xff, 0xff));
assertEquals(0xf1ea, Utils.address(0xea, 0xf1));
assertEquals(0x00ea, Utils.address(0xea, 0x00));
assertEquals(0xf100, Utils.address(0x00, 0xf1));
assertEquals(0x1234, Utils.address(0x34, 0x12));
assertEquals(0xffff, Utils.address(0xff, 0xff));
}
public void testZpxAddress() {
@@ -0,0 +1,329 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import org.junit.Test;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test for Zero Page Indirect addressing mode, found on some instructions
* in the 65C02 and 65816
*/
public class CpuZeroPageIndirectTest {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
private void makeCmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.CMOS_6502);
}
private void makeNmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.NMOS_6502);
}
private void makeCpu(InstructionTable.CpuBehavior behavior) throws Exception {
this.cpu = new Cpu(behavior);
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
}
@Test
public void test_ora() throws Exception {
makeCmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x60, 0x03);
bus.write(0x61, 0x10);
bus.write(0x1000, 0x11);
bus.write(0x1001, 0x22);
bus.write(0x1002, 0x44);
bus.write(0x1003, 0x88);
bus.loadProgram(0x12, 0x30, // ORA ($30)
0x12, 0x40, // ORA ($40)
0x12, 0x50, // ORA ($50)
0x12, 0x60); // ORA ($60)
assertEquals(0x00, cpu.getAccumulator());
// 0x00 | 0x11 = 0x11
cpu.step();
assertEquals(0x11, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// 0x11 | 0x22 = 0x33
cpu.step();
assertEquals(0x33, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// 0x33 | 0x44 = 0x77
cpu.step();
assertEquals(0x77, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// 0x77 | 0x88 = 0xff
cpu.step();
assertEquals(0xff, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
}
@Test
public void test_ora_requiresCmosCpu() throws Exception {
makeNmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x60, 0x03);
bus.write(0x61, 0x10);
bus.write(0x1000, 0x11);
bus.write(0x1001, 0x22);
bus.write(0x1002, 0x44);
bus.write(0x1003, 0x88);
bus.loadProgram(0x12, 0x30, // ORA ($30)
0x12, 0x40, // ORA ($40)
0x12, 0x50, // ORA ($50)
0x12, 0x60); // ORA ($60)
assertEquals(0x00, cpu.getAccumulator());
boolean zState = cpu.getZeroFlag();
boolean nState = cpu.getNegativeFlag();
// 0x00 | 0x11 = 0x11, but not implemented in NMOS cpu
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag()); // unchanged
assertEquals(nState, cpu.getNegativeFlag()); // unchanged
// 0x11 | 0x22 = 0x33, but not implemented in NMOS cpu
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag()); // unchanged
assertEquals(nState, cpu.getNegativeFlag()); // unchanged
// 0x33 | 0x44 = 0x77, but not implemented in NMOS cpu
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag()); // unchanged
assertEquals(nState, cpu.getNegativeFlag()); // unchanged
// 0x77 | 0x88 = 0xff, but not implemented in NMOS cpu
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag()); // unchanged
assertEquals(nState, cpu.getNegativeFlag()); // unchanged
}
@Test
public void test_and() throws Exception {
makeCmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x1000, 0x33);
bus.write(0x1001, 0x11);
bus.write(0x1002, 0x88);
bus.loadProgram(0x32, 0x30, // AND ($30)
0x32, 0x40, // AND ($40)
0x32, 0x50); // AND ($50)
cpu.setAccumulator(0xff);
// 0xFF & 0x33 == 0x33
cpu.step();
assertEquals(0x33, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// 0x33 & 0x11 == 0x11
cpu.step();
assertEquals(0x11, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// 0x11 & 0x80 == 0
cpu.step();
assertEquals(0, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@Test
public void test_and_requiresCmosCpu() throws Exception {
makeNmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x1000, 0x33);
bus.write(0x1001, 0x11);
bus.write(0x1002, 0x88);
bus.loadProgram(0x32, 0x30, // AND ($30)
0x32, 0x40, // AND ($40)
0x32, 0x50); // AND ($50)
cpu.setAccumulator(0xff);
boolean zState = cpu.getZeroFlag();
boolean nState = cpu.getNegativeFlag();
// 0xFF & 0x33 == 0x33, but not implemented in NMOS cpu
cpu.step();
assertEquals(0xff, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag());
assertEquals(nState, cpu.getNegativeFlag());
// 0x33 & 0x11 == 0x11, but not implemented in NMOS cpu
cpu.step();
assertEquals(0xff, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag());
assertEquals(nState, cpu.getNegativeFlag());
// 0x11 & 0x80 == 0, but not implemented in NMOS cpu
cpu.step();
assertEquals(0xff, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag());
assertEquals(nState, cpu.getNegativeFlag());
}
@Test
public void test_eor() throws Exception {
makeCmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x60, 0x03);
bus.write(0x61, 0x10);
bus.write(0x1000, 0x00);
bus.write(0x1001, 0xff);
bus.write(0x1002, 0x33);
bus.write(0x1003, 0x44);
bus.loadProgram(0x52, 0x30, // AND ($30)
0x52, 0x40, // AND ($40)
0x52, 0x50, // EOR ($50)
0x52, 0x60); // AND ($60)
cpu.setAccumulator(0x88);
cpu.step();
assertEquals(0x88, cpu.getAccumulator());
cpu.step();
assertEquals(0x77, cpu.getAccumulator());
cpu.step();
assertEquals(0x44, cpu.getAccumulator());
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
}
@Test
public void test_eor_requiresCmosCpu() throws Exception {
makeNmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x60, 0x03);
bus.write(0x61, 0x10);
bus.write(0x1000, 0x00);
bus.write(0x1001, 0xff);
bus.write(0x1002, 0x33);
bus.write(0x1003, 0x44);
bus.loadProgram(0x52, 0x30, // AND ($30)
0x52, 0x40, // AND ($40)
0x52, 0x50, // EOR ($50)
0x52, 0x60); // AND ($60)
cpu.setAccumulator(0x88);
cpu.step();
assertEquals(0x88, cpu.getAccumulator());
cpu.step();
assertEquals(0x88, cpu.getAccumulator());
cpu.step();
assertEquals(0x88, cpu.getAccumulator());
cpu.step();
assertEquals(0x88, cpu.getAccumulator());
}
}
+57 -5
View File
@@ -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++;
}
}
}
}
@@ -1,42 +0,0 @@
package com.loomcom.symon;
import com.loomcom.symon.util.HexUtil;
import junit.framework.TestCase;
public class HexUtilTest extends TestCase {
public void testByteToHex() {
assertEquals("FE", HexUtil.byteToHex(0xfe));
assertEquals("00", HexUtil.byteToHex(0));
assertEquals("0A", HexUtil.byteToHex(10));
}
public void testByteToHexIgnoresSign() {
assertEquals("FF", HexUtil.byteToHex(-1));
}
public void testByteToHexMasksLowByte() {
assertEquals("FE", HexUtil.byteToHex(0xfffe));
assertEquals("00", HexUtil.byteToHex(0xff00));
}
public void testWordToHex() {
assertEquals("0000", HexUtil.wordToHex(0));
assertEquals("FFFF", HexUtil.wordToHex(65535));
assertEquals("FFFE", HexUtil.wordToHex(65534));
}
public void testWordToHexIgnoresSign() {
assertEquals("FFFF", HexUtil.wordToHex(-1));
}
public void testWordToHexMasksTwoLowBytes() {
assertEquals("FFFE", HexUtil.wordToHex(0xfffffe));
assertEquals("FF00", HexUtil.wordToHex(0xffff00));
}
public void testAllBytesAreCorrect() {
for (int i = 0; i <= 0xff; i++) {
assertEquals(String.format("%02X", i), HexUtil.byteToHex(i));
}
}
}
@@ -0,0 +1,42 @@
package com.loomcom.symon;
import com.loomcom.symon.util.Utils;
import junit.framework.TestCase;
public class UtilsTest extends TestCase {
public void testByteToHex() {
assertEquals("FE", Utils.byteToHex(0xfe));
assertEquals("00", Utils.byteToHex(0));
assertEquals("0A", Utils.byteToHex(10));
}
public void testByteToHexIgnoresSign() {
assertEquals("FF", Utils.byteToHex(-1));
}
public void testByteToHexMasksLowByte() {
assertEquals("FE", Utils.byteToHex(0xfffe));
assertEquals("00", Utils.byteToHex(0xff00));
}
public void testWordToHex() {
assertEquals("0000", Utils.wordToHex(0));
assertEquals("FFFF", Utils.wordToHex(65535));
assertEquals("FFFE", Utils.wordToHex(65534));
}
public void testWordToHexIgnoresSign() {
assertEquals("FFFF", Utils.wordToHex(-1));
}
public void testWordToHexMasksTwoLowBytes() {
assertEquals("FFFE", Utils.wordToHex(0xfffffe));
assertEquals("FF00", Utils.wordToHex(0xffff00));
}
public void testAllBytesAreCorrect() {
for (int i = 0; i <= 0xff; i++) {
assertEquals(String.format("%02X", i), Utils.byteToHex(i));
}
}
}