Compare commits

..
30 Commits
Author SHA1 Message Date
Retrograde b9997e3edc Use HTTPS instead of HTTP in Schema referencs in the Maven POM file. (#25)
Use HTTPS instead of HTTP in Schema referencs in the Maven POM file.

This fixes cvc-elt.1.a: Cannot find the declaration of element 'project'. errors when building fx from VS.code.

Reviewed-on: https://codeberg.org/sethm/symon/pulls/25
Reviewed-by: Seth Morabito <sethm@noreply.codeberg.org>
2026-06-25 00:08:22 +02:00
Retrograde 45c8a69d7b FlatLaf look-and-feel with better Dark mode support. (#23)
This PR configures Symon to use the FlatLaf Swing look-and-feel, giving both a slightly more modern look but also much better support for Dark/Light UI modes. Additional small tweaks to make the UI more readable in both Dark/Light modes.

Detection of system theme also added, selecting the Dark/Light FlatLaf variant accordingly.

Gets rid of need for the hardcoded Metal UI for text fields in StatusPanel workaround as well, as FlatLaf renders these small fields correctly.

Reviewed-on: https://codeberg.org/sethm/symon/pulls/23
Reviewed-by: Seth Morabito <sethm@noreply.codeberg.org>
2026-06-24 23:13:23 +02:00
Retrograde 2e2163fefb Added handling of -h/--help CLI option, to print out available options. (#22)
Small CLI improvement: adding a -h / --help switch, to be able to get a printout of the available options.

Reviewed-on: https://codeberg.org/sethm/symon/pulls/22
Reviewed-by: Seth Morabito <sethm@noreply.codeberg.org>
2026-06-24 20:45:51 +02:00
Seth Morabito 995b372f55 Update README with Codeberg links 2026-03-25 13:16:52 -07:00
Seth Morabito 47c87aac56 Update README for high-dpi 2025-04-13 11:38:33 -07:00
Seth Morabito 3a59dedc29 Update README and pom.xml for release 1.5.0 2025-03-30 15:28:32 -07:00
Seth Morabito 7268766cd6 Implement Step-Over functionality
This change adds a new "Step Over" button, which is enabled
when a "JSR" (Jump to Subroutine) instruction is encountered
while stepping through execution one instruction at a time.

Pressing "Step Over" will continue running instructions until a
corresponding "RTS" (Return from Subroutine) is encountered.
While stepping over a subroutine, the call stack depth is tracked
in an attempt to halt only when the *correct* RTS is encountered.
In other words, it should skip over other subroutines nested
inside the call you want to step over:

```
     JSR A # Step over here...
     ...
     JSR B
     ...
     RTS   # Returns from 'B', doesn't halt
     ...
     RTS   # Returns from 'A', halts after return
```

Implements feature request #18.
2025-03-30 15:18:00 -07:00
Seth Morabito 2284535b7f Fix #17: Correct disassembly 2025-03-15 15:46:50 -07:00
Seth Morabito 963526621d Maven build update
- Bump to version 1.5.0-dev
- Update dependencies
- Fix broken Symon homepage URL
- Fix maven xsd URL
2025-02-13 16:36:50 -08:00
Seth Morabito c317544e7b Address IntelliJ warnings
This commit addresses a large number of IntelliJ warnings
that have been left hanging around for far too long.
2025-02-13 16:05:42 -08:00
Seth Morabito ecd4bbbd9a README formatting fix 2023-11-18 10:50:07 -08:00
Seth Morabito a537328852 Minor README update 2023-11-18 10:46:55 -08:00
Seth Morabito 4975fca506 Missed a README update for v1.4.0 2023-11-11 08:20:49 -08:00
Seth Morabito 199d96c025 Prepare release 1.4.0 2023-11-11 08:15:35 -08:00
Seth Morabito 0c026e38dd By default, do not halt on BRK (#14)
This change introduces a new command line flag, '-b', that cause the
simulator to halt on the `BRK` instruction. By default, however, the
simulator will no longer halt on `BRK`.

As before, this behavior can be toggled in the preferences at run-time.
2023-11-10 09:11:24 -08:00
Seth Morabito 5e56627f32 #13: Move cursor backward on ASCII Backspace 2023-10-31 08:28:26 -07:00
Chelsea Wilkinson 67f5e17f78 Added BenEaterMachine 2023-10-31 07:44:55 -07:00
Seth Morabito 5a25750f46 Clarify how to load ROMs in README files 2023-07-23 10:16:36 -07:00
Tim Allen 4a8a803472 Document the quirks of Symon's ACIA emulation.
Fixes #7.
2023-06-11 08:21:37 -07:00
Tim Allen 4423623816 Writing 0 to the 6551 ACIA's control register is not a program reset.
So far as I can tell, ever since the first version of the ACIA emulation in
Symon, writing 0x00 to the control register has been interpreted as a request
to reset, rather than to actually set the control register to 0x00. This is
strange for a number of reasons:

- All-zeros is actually a very sensible value for the control register, and
  is in fact the hardware-reset default.
- I can't find any description of such behaviour in the 6551, W65C51S, or
  W65C51N data sheets.
- The 6551 already has a way to trigger "program reset" by writing to the
  status register.

So I've removed that quirk, and writing to the control register now just
writes to the control register and nothing else.
2023-06-11 08:21:37 -07:00
Tim Allen 5df775bbb0 Make the emulated 6551's soft ("program") reset state match the MOS datasheet.
The only description of the effects of "program reset" in the original MOS
datasheet is in the section for each register. The W65C51S and W65C51N
datasheets have a heading "PROGRAM RESET OPERATION", but it amounts to:

- internal registers are modified as described in the section for each register
- changes to the DTR, DCD, and DSR pins which Symon does not emulate
- the overrun flag is cleared

...which is what this new implementation does.

It would make *sense* for the reset to do things like "cancel transmission or
reception in progress" and stop asserting an interrupt, as the old code did,
but I can't find any evidence of such behaviour in the datasheets.
2023-06-11 08:21:37 -07:00
Tim Allen d076046f57 Make the emulated 6551's hardware reset state match the MOS datasheet. 2023-06-11 08:21:37 -07:00
Tim Allen b725fb5fdd Fix comment: The 6551 baud rate is in the lower *four* bits.
The code is correct, the comment is wrong.
2023-06-10 10:19:24 -07:00
Tim Allen 9351d785ae Fix comment: The 6551 ACIA *does* support interrupts. 2023-06-10 10:19:24 -07:00
Tim Allen 66a92f4196 Use ACIA register names where we can. 2023-06-10 10:19:24 -07:00
Seth Morabito cda9a218af Merge pull request #6 from Screwtapello/irq-disabled-at-reset
Make the Processor Status register match a real 6502 at power-on.
2023-02-03 16:38:06 -08:00
Tim Allen b5a470d3ba Make the Processor Status register match a real 6502 at power-on.
When describing the CPU's reset pin, the W65C02S data sheet says:

> All Registers are initialized by software except the Decimal and Interrupt
> disable mode select bits of the Processor Status Register (P) are initialized
> by hardware.

It then has a diagram of the power-on state of the processor status register:

>     7 6 5 4 3 2 1 0
>     * * 1 1 0 1 * *
>     N V - B D I Z C
>
> * = software initialized

Confusingly the text indicates that only the D and I flags are initialised by
hardware, while the diagram indicates that the B flag is initialised too.

Meanwhile, https://www.nesdev.org/wiki/CPU_power_up_state says that
the power-on state of the NES CPU is $34 (exactly matching the diagram above)
but https://www.nesdev.org/wiki/Status_flags#The_B_flag says that the B flag
does not physically exist within P register, it's only relevant in the copy
of P that gets pushed to the stack by BRK (set), PHP (set), or an interrupt
signal (cleared).

As a result, the most sensible power-on state for the processor status register
is with the "interrupt disable" flag set and everything else cleared.
2023-02-03 18:16:57 +11:00
Seth Morabito e210a40639 Use correct Mockito package 2023-01-12 16:56:28 -08:00
Seth Morabito 0bad9912ce Repaint Video window at 30fps
A bug caused the video window to repaint only when the cursor was
blinking. This meant that if the cursor was disabled, the window would
never update!

This change removes the repaint logic from the cursor blinking timer,
and instead puts it into its own periodic timer callback that runs
independently of cursor blink.
2023-01-11 13:31:22 -08:00
Seth Morabito b36b442b14 Cleanup some IntelliJ IDEA warnings
Cleaning up some warnings for the first time in many eons.
2022-12-15 09:44:53 -08:00
76 changed files with 1245 additions and 771 deletions

No files matched your search

+1
View File
@@ -1,4 +1,5 @@
*~
*.jar
*#
target
.DS_Store
+103 -45
View File
@@ -1,13 +1,13 @@
SYMON - A 6502 System Simulator
===============================
**Version:** 1.3.2
**Version:** 1.5.0
**Last Updated:** 08 March, 2021
**Last Updated:** 30 March, 2025
See the file COPYING for license.
![Symon Simulator in Action](https://github.com/sethm/symon/raw/master/screenshots/full.jpg)
![Symon Simulator in Action](https://codeberg.org/sethm/symon/raw/master/screenshots/full.jpg)
## 1.0 About
@@ -25,20 +25,20 @@ Klaus Dormann's 6502 Functional Test Suite as of version 0.8.2
(See [this thread on the 6502.org Forums](http://forum.6502.org/viewtopic.php?f=2&t=2241)
for more information about this functional test suite).
Symon is under constant, active development. Feedback and patches
are always welcome.
Symon is under active maintenance. Feedback and patches are always
welcome.
## 2.0 Requirements
- Java 1.8 or higher
- Maven 2.0.x or higher (for building from source)
- Java 10 or higher
- Maven 3.0.x or higher (for building from source)
- JUnit 4 or higher (for testing)
## 3.0 Features
Symon can simulate multiple 6502 based architectures. At present, three
machines are implemented: Symon (the default), MULTICOMP, and a "Simple"
machine useful for debugging.
Symon can simulate multiple 6502 based architectures. At present, four
machines are implemented: Symon (the default), MULTICOMP, BenEater, and
a "Simple" machine useful for debugging.
### 3.1 Memory Maps
@@ -64,27 +64,49 @@ memory.
- `$0000`--`$FFFF`: 64KB RAM
#### 3.1.4 BenEater Memory Map
- `$0000`--`$3FFF`: 16KB RAM
- `$5000`--`$5003`: MOS 6551 ACIA (Serial Console)
- `$6000`--`$600F`: 6522 VIA
- `$8000`--`$FFFF`: 16KB ROM
### 3.2 Serial Console and CPU Status
![Serial Console](https://github.com/sethm/symon/raw/master/screenshots/console.png)
![Serial Console](https://codeberg.org/sethm/symon/raw/master/screenshots/console.png)
The main window of the simulator acts as the primary Input/Output
system through a virtual serial terminal. The terminal is attached to
a simulated ACIA, including a programmable baud rate generator that
tries to approximate the correct "feel" of the programmed baud rate.
(The sample Enhanced BASIC ROM image is programmed for 9600 baud)
system through a virtual serial terminal. It also provides CPU status.
Contents of the accumulator, index registers, processor status flags,
disassembly of the instruction register, and stack pointer are all displayed.
It also provides CPU status. Contents of the accumulator, index
registers, processor status flags, disassembly of the instruction
register, and stack pointer are all displayed.
The terminal is attached to a simulated MOS 6551 ACIA. It behaves very much
as described in the datasheet, with some exceptions:
![Font Selection](https://github.com/sethm/symon/raw/master/screenshots/font_selection.png)
- The simulated ACIA is permanently connected to the virtual terminal,
the Data Carrier Detect and Data Set Ready status bits always indicate
a connection is ready.
- The parity, stop-bits and bits-per-character settings are ignored. The
ACIA always sends and receives 8-bit characters, and parity errors
do not occur.
- The ACIA tries to honour the configured baud rate, but as a special case
the default "16x External Clock" rate is interpreted to mean "as fast as
possible" (The sample Enhanced BASIC ROM image is programmed for 9600 baud).
- The ACIA ignores the configured state of the Data Terminal Ready pin;
it is always ready to receive and transmit.
For more information on the MOS 6551 ACIA and its programming model,
see the official datasheet:
- [MOS 6551 ACIA](http://archive.6502.org/datasheets/mos_6551_acia.pdf)
![Font Selection](https://codeberg.org/sethm/symon/raw/master/screenshots/font_selection.png)
The console supports font sizes from 10 to 20 points.
### 3.3 ROM Loading
![ROM Loading](https://github.com/sethm/symon/raw/master/screenshots/load_rom.png)
![ROM Loading](https://codeberg.org/sethm/symon/raw/master/screenshots/load_rom.png)
Symon can load any appropriately sized ROM image. The Symon
architecture expects as 16KB (16384 byte) ROM image, while the
@@ -95,32 +117,32 @@ address.
### 3.4 Memory Window
![Memory Window](https://github.com/sethm/symon/raw/master/screenshots/memory_window.png)
![Memory Window](https://codeberg.org/sethm/symon/raw/master/screenshots/memory_window.png)
Memory contents can be viewed (and edited) one page at a time through the Memory Window.
### 3.5 Trace Log
![Trace Log](https://github.com/sethm/symon/raw/master/screenshots/trace_log.png)
![Trace Log](https://codeberg.org/sethm/symon/raw/master/screenshots/trace_log.png)
The last 20,000 execution steps are disassembled and logged to the Trace Log
Window.
### 3.6 Simulator Speeds
![Speeds](https://github.com/sethm/symon/raw/master/screenshots/simulator_menu.png)
![Speeds](https://codeberg.org/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](https://codeberg.org/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)
![Composite Video](https://codeberg.org/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
@@ -198,24 +220,36 @@ interface.
Two command line options may be passed to the JAR file on startup,
to specify machine type and CPU type. The options are:
- `-cpu 6502`: Use the NMOS 6502 CPU type by default.
- `-cpu 65c02`: Use the CMOS 65C02 CPU type by default.
- `-machine symon`: Use the **Symon** machine type by default.
- `-machine multicomp`: Use the **Multicomp** machine type by default.
- `-machine simple`: Use the **Simple** machine type by default.
- `-rom <file>`: Use the specified file as the ROM image.
- `-c`,`-cpu 6502`: Use the NMOS 6502 CPU type by default.
- `-c`,`-cpu 65c02`: Use the CMOS 65C02 CPU type by default.
- `-c`,`-machine symon`: Use the **Symon** machine type by default.
- `-c`,`-machine multicomp`: Use the **Multicomp** machine type by default.
- `-m`,`-machine simple`: Use the **Simple** machine type by default.
- `-m`,`-machine beneater`: Use the **BenEater** machine type by default.
- `-r`,`-rom <file>`: Use the specified file as the ROM image.
- `-b`,`-brk`: Halt the simulator on a BRK instruction (default is to continue)
### 4.2 ROM images
### 4.2 High-DPI UI scaling
In certain environments with high-DPI (e.g. 4K) displays, the UI may
not automatically scale and appear quite tiny. This can be fixed
with the command line argument `-Dsun.java2d.uiScale=<factor>`,
for example:
```
java -Dsun.java2d.uiScale=2.0 -jar symon-1.5.0.jar
```
### 4.3 ROM images
The simulator requires a ROM image loaded into memory to work
properly. Without a ROM in memory, the simulator will not be able to
reset, since the reset vector for the 6502 is located in the ROM
address space.
By default, any file named `rom.bin` that exists in the same directory
where Symon is launched will be loaded as a ROM image. ROM images can
also be swapped out at run-time with the "Load ROM Image..." in the
File menu.
ROM images can be loaded with the `-rom` argument when running
Symon from the command line. ROM images can also be swapped out at
run-time with the "Load ROM..." item in the File menu.
The "samples" directory contains a ROM image for the Symon
architecture named 'ehbasic.rom', containing Lee Davison's Enhanced
@@ -224,7 +258,7 @@ architecture named 'ehbasic.rom', containing Lee Davison's Enhanced
*Note*: Presently, EhBASIC only works with the Symon machine
architecture, not with MULTICOMP.
### 4.3 Loading A Program
### 4.4 Loading A Program
In addition to ROM images, programs in the form of raw binary object files can
be loaded directly into memory from "Load Program..." in the File menu.
@@ -240,13 +274,34 @@ for testing.
- 'hello.prg' will continuously print "Hello, 6502 World!" to the console.
### 4.4 Running
### 4.5 Running
After loading a program or ROM image, clicking "Run" will start the simulator
running.
## 5.0 Revision History
- **1.5.0:** 30 March 2025 - Fixed a bug that caused some 6502
instructions to be disassembled improperly in the trace log
(GitHub issue #17).
Added a new "Step Over" button that allows stepping over the
current subroutine. This button only becomes active when the
next instruction is a "JSR" while stepping through execution
one instruction at a time. It will continue execution until
the subroutine returns through a corresponding "RTS" instruction.
(GitHub feature request #18).
- **1.4.0:** 11 November 2023 - Adds a new machine, the Ben Eater
machine. Correct handling of 6551 interrupts, and several 6551
bug fixes. Fixes power-on status of 6502 status register. Fixes a
bug with ASCII backspace character not moving the cursor
backwards. Finally, "halt on BRK" is no longer enabled by default,
but can be set at runtime or by a command line flag. Thank you to
Tim Allen and Chelsea Wilkinson for contributions!
- **1.3.2:** 8 March 2022 - Minor bug fixes.
- **1.3.1:** 12 October, 2019 - Add support for new command line
option `-cpu <type>` to specify one of `6502` or `65c02` on startup,
and new option `-rom <file>` to specify a ROM file to load.
@@ -322,17 +377,12 @@ running.
- Feedback (in the form of dialogs, status bar, etc).
- Better debugging tools from the UI, including breakpoints
and disassembly.
- UI needs a ton more polish.
- More extensive testing.
- Clean up JavaDoc.
- Implement CMOS 65C02 instructions and NMOS / CMOS mode flag.
- Allow displaying ACIA status and dumping ACIA buffers, for
debugging.
@@ -342,11 +392,19 @@ running.
- Symbolic debugging.
- Better / correct disassembly and tracing of 65C02 and 65C816
instructions.
## 8.0 Copyright and Acknowledgements
**Copyright (c) 2014 Seth J. Morabito &lt;web@loomcom.com&gt;**
**Copyright (c) 2008-2025 Seth J. Morabito &lt;web@loomcom.com&gt;**
Portions Copyright (c) 2014 Maik Merten &lt;maikmerten@googlemail.com&gt;
- Portions Copyright (c) 2014 Maik Merten
&lt;maikmerten@googlemail.com&gt;
- Portions Copyright (c) 2022 Tim Allen
&lt;thristian@gmail.com&gt;
- Portions Copyright (c) 2023 Chelsea Wilkinson
&lt;mail@chelseawilkinson.me&gt;
Additional components used in this project are copyright their respective owners.
+33 -15
View File
@@ -1,12 +1,13 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="https://maven.apache.org/POM/4.0.0"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>1.3.2</version>
<version>1.5.0</version>
<name>symon</name>
<url>http://www.loomcom.com/symon</url>
<url>https://git.loomcom.com/seth/symon</url>
<properties>
<project.build.sourceEncoding>
UTF-8
@@ -15,11 +16,32 @@
UTF-8
</project.reporting.outputEncoding>
</properties>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<version>1.5.16</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.7.1</version>
</dependency>
<dependency>
<groupId>com.github.Dansoftowner</groupId>
<artifactId>jSystemThemeDetector</artifactId>
<version>3.9.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
@@ -30,14 +52,9 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.12.4</version>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
<build>
@@ -48,7 +65,8 @@
<version>3.2.0</version>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.loomcom.symon.Main</mainClass>
</transformer>
</transformers>
@@ -79,8 +97,8 @@
<version>3.7.0</version>
<configuration>
<compilerArgument>-Xlint:unchecked</compilerArgument>
<source>1.7</source>
<target>1.7</target>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
@@ -120,4 +138,4 @@
</plugin>
</plugins>
</build>
</project>
</project>
+9 -8
View File
@@ -11,7 +11,6 @@ Sample Programs
When loaded at address $0300, this program will echo back to the console
anything typed.
Both hello.prg and echo_poll.prg were assembled with the Ophis assembler:
https://hkn.eecs.berkeley.edu/~mcmartin/ophis/
@@ -19,23 +18,25 @@ Both hello.prg and echo_poll.prg were assembled with the Ophis assembler:
3. echo_irq.rom
This is another echo program, and behaves identically to echo_poll.prg,
except it is interrupt-driven.
except it is interrupt-driven. This ROM can be loaded either through the
"File->Load ROM..." menu in Symon, or by specifying the path to
"echo_irq.rom" with the "-jar" command line option when running Symon,
for example: `java -jar symon-1.3.2.jar -rom samples/echo_irq.rom`
4. ehbasic.rom
This is Lee Davison's Enhanced 6502 BASIC.
To use this ROM image, just copy the file 'ehbasic.rom' into the directory
where you run Symon. Rename the file to 'rom.bin'. When you start Symon,
the ROM file will be automatically loaded at address $d000.
The EhBASIC ROM can be loaded either through the "File -> Load ROM..."
menu in Symon, or by specifying the path to "ehbasic.rom" with
the "-jar" command line option when running Symon, for example:
`java -jar symon-1.3.2.jar -rom samples/ehbasic.rom`
Click the "Run" button and EhBASIC should automatically start running.
Type 'C' to do a cold start.
Then, type $C000 when prompted for the memory size.
NOTE: EhBASIC only wants upper-case input. This confused me at first!
Then, type '49152' (without the quotes) when prompted for the memory size.
More information can be found in the 'ehbasic' directory, and by visiting
the EhBASIC web page:
@@ -1,3 +1,26 @@
/*
* Copyright (c) 2008-2025 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;
import com.loomcom.symon.exceptions.MemoryAccessException;
@@ -9,8 +32,8 @@ import java.util.TreeSet;
public class Breakpoints extends AbstractTableModel {
private TreeSet<Integer> breakpoints;
private Simulator simulator;
private final TreeSet<Integer> breakpoints;
private final Simulator simulator;
public Breakpoints(Simulator simulator) {
this.breakpoints = new TreeSet<>();
@@ -26,11 +49,6 @@ public class Breakpoints extends AbstractTableModel {
fireTableDataChanged();
}
public void removeBreakpoint(int address) {
this.breakpoints.remove(address);
fireTableDataChanged();
}
public void removeBreakpointAtIndex(int index) {
if (index < 0) {
return;
+11 -18
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -42,19 +42,19 @@ import java.util.TreeSet;
public class Bus {
// The default address at which to load programs
public static int DEFAULT_LOAD_ADDRESS = 0x0200;
public static final 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;
private final int startAddress;
private final int endAddress;
// The CPU
private Cpu cpu;
// Ordered sets of IO devices, associated with their priority
private Map<Integer, SortedSet<Device>> deviceMap;
private final Map<Integer, SortedSet<Device>> deviceMap;
// an array for quick lookup of adresses, brute-force style
// an array for quick lookup of addresses, brute-force style
private Device[] deviceAddressArray;
@@ -94,8 +94,8 @@ public class Bus {
* Add a device to the bus.
*
* @param device Device to add
* @param priority Bus prioirity.
* @throws MemoryRangeException
* @param priority Bus priority.
* @throws MemoryRangeException Invalid device address range.
*/
public void addDevice(Device device, int priority) throws MemoryRangeException {
@@ -109,12 +109,7 @@ public class Bus {
throw new MemoryRangeException("end address of device " + device.getName() + " does not fall within the address range of the bus");
}
SortedSet<Device> deviceSet = deviceMap.get(priority);
if (deviceSet == null) {
deviceSet = new TreeSet<>();
deviceMap.put(priority, deviceSet);
}
SortedSet<Device> deviceSet = deviceMap.computeIfAbsent(priority, k -> new TreeSet<>());
device.setBus(this);
deviceSet.add(device);
@@ -125,7 +120,7 @@ public class Bus {
* Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others.
*
* @param device Device to add
* @throws MemoryRangeException
* @throws MemoryRangeException Invalid device memory range.
*/
public void addDevice(Device device) throws MemoryRangeException {
addDevice(device, 0);
@@ -224,9 +219,7 @@ public class Bus {
for (int priority : priorities) {
SortedSet<Device> deviceSet = deviceMap.get(priority);
for (Device device : deviceSet) {
devices.add(device);
}
devices.addAll(deviceSet);
}
return devices;
+73 -61
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -15,7 +15,7 @@
* 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
* NON-INFRINGEMENT. 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.
@@ -32,7 +32,7 @@ import org.slf4j.LoggerFactory;
/**
* This class provides a simulation of the MOS 6502 CPU's state machine.
* A simple interface allows this 6502 to read and write to a simulated bus,
* and exposes some of the internal state for inspection and debugging.
* and exposes some internal state for inspection and debugging.
*/
public class Cpu implements InstructionTable {
@@ -126,7 +126,7 @@ public class Cpu implements InstructionTable {
// Clear status register bits.
state.carryFlag = false;
state.zeroFlag = false;
state.irqDisableFlag = false;
state.irqDisableFlag = true;
state.decimalModeFlag = false;
state.breakFlag = false;
state.overflowFlag = false;
@@ -158,6 +158,8 @@ public class Cpu implements InstructionTable {
* Performs an individual instruction cycle.
*/
public void step() throws MemoryAccessException {
int hi, lo; // Address calculation
opBeginTime = System.nanoTime();
// Store the address from which the IR was read, for debugging
@@ -283,7 +285,7 @@ public class Cpu implements InstructionTable {
// Execute
switch (state.ir) {
/** Single Byte Instructions; Implied and Relative **/
// Single Byte Instructions; Implied and Relative
case 0x00: // BRK - Force Interrupt - Implied
handleBrk(state.pc + 1);
break;
@@ -317,8 +319,8 @@ public class Cpu implements InstructionTable {
break;
case 0x40: // RTI - Return from Interrupt - Implied
setProcessorStatus(stackPop());
int lo = stackPop();
int hi = stackPop();
lo = stackPop();
hi = stackPop();
setProgramCounter(Utils.address(lo, hi));
break;
case 0x48: // PHA - Push Accumulator - Implied
@@ -457,7 +459,7 @@ public class Cpu implements InstructionTable {
setArithmeticFlags(state.x);
break;
/** JMP *****************************************************************/
// JMP
case 0x4c: // JMP - Absolute
state.pc = Utils.address(state.args[0], state.args[1]);
break;
@@ -495,7 +497,7 @@ public class Cpu implements InstructionTable {
state.pc = Utils.address(bus.read(lo, true), bus.read(hi, true));
break;
/** ORA - Logical Inclusive Or ******************************************/
// ORA - Logical Inclusive Or
case 0x09: // #Immediate
state.a |= state.args[0];
setArithmeticFlags(state.a);
@@ -517,7 +519,7 @@ public class Cpu implements InstructionTable {
break;
/** ASL - Arithmetic Shift Left *****************************************/
// ASL - Arithmetic Shift Left
case 0x0a: // Accumulator
state.a = asl(state.a);
setArithmeticFlags(state.a);
@@ -532,7 +534,7 @@ public class Cpu implements InstructionTable {
break;
/** BIT - Bit Test ******************************************************/
// BIT - Bit Test
case 0x89: // 65C02 #Immediate
setZeroFlag((state.a & state.args[0]) == 0);
break;
@@ -551,7 +553,7 @@ public class Cpu implements InstructionTable {
break;
/** AND - Logical AND ***************************************************/
// AND - Logical AND
case 0x29: // #Immediate
state.a &= state.args[0];
setArithmeticFlags(state.a);
@@ -573,7 +575,7 @@ public class Cpu implements InstructionTable {
break;
/** ROL - Rotate Left ***************************************************/
// ROL - Rotate Left
case 0x2a: // Accumulator
state.a = rol(state.a);
setArithmeticFlags(state.a);
@@ -588,7 +590,7 @@ public class Cpu implements InstructionTable {
break;
/** EOR - Exclusive OR **************************************************/
// EOR - Exclusive OR
case 0x49: // #Immediate
state.a ^= state.args[0];
setArithmeticFlags(state.a);
@@ -610,7 +612,7 @@ public class Cpu implements InstructionTable {
break;
/** LSR - Logical Shift Right *******************************************/
// LSR - Logical Shift Right
case 0x4a: // Accumulator
state.a = lsr(state.a);
setArithmeticFlags(state.a);
@@ -625,7 +627,7 @@ public class Cpu implements InstructionTable {
break;
/** ADC - Add with Carry ************************************************/
// ADC - Add with Carry
case 0x69: // #Immediate
if (state.decimalModeFlag) {
state.a = adcDecimal(state.a, state.args[0]);
@@ -653,7 +655,7 @@ public class Cpu implements InstructionTable {
break;
/** ROR - Rotate Right **************************************************/
// ROR - Rotate Right
case 0x6a: // Accumulator
state.a = ror(state.a);
setArithmeticFlags(state.a);
@@ -668,7 +670,7 @@ public class Cpu implements InstructionTable {
break;
/** STA - Store Accumulator *********************************************/
// STA - Store Accumulator
case 0x92: // 65C02 STA (ZP)
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
@@ -685,7 +687,7 @@ public class Cpu implements InstructionTable {
break;
/** STY - Store Y Register **********************************************/
// STY - Store Y Register
case 0x84: // Zero Page
case 0x8c: // Absolute
case 0x94: // Zero Page,X
@@ -693,14 +695,14 @@ public class Cpu implements InstructionTable {
break;
/** STX - Store X Register **********************************************/
// STX - Store X Register
case 0x86: // Zero Page
case 0x8e: // Absolute
case 0x96: // Zero Page,Y
bus.write(effectiveAddress, state.x);
break;
/** STZ - 65C02 Store Zero ****************************************************/
// STZ - 65C02 Store Zero
case 0x64: // Zero Page
case 0x74: // Zero Page,X
case 0x9c: // Absolute
@@ -712,7 +714,7 @@ public class Cpu implements InstructionTable {
bus.write(effectiveAddress, 0);
break;
/** LDY - Load Y Register ***********************************************/
// LDY - Load Y Register
case 0xa0: // #Immediate
state.y = state.args[0];
setArithmeticFlags(state.y);
@@ -726,7 +728,7 @@ public class Cpu implements InstructionTable {
break;
/** LDX - Load X Register ***********************************************/
// LDX - Load X Register
case 0xa2: // #Immediate
state.x = state.args[0];
setArithmeticFlags(state.x);
@@ -740,7 +742,7 @@ public class Cpu implements InstructionTable {
break;
/** LDA - Load Accumulator **********************************************/
// LDA - Load Accumulator
case 0xa9: // #Immediate
state.a = state.args[0];
setArithmeticFlags(state.a);
@@ -762,7 +764,7 @@ public class Cpu implements InstructionTable {
break;
/** CPY - Compare Y Register ********************************************/
// CPY - Compare Y Register
case 0xc0: // #Immediate
cmp(state.y, state.args[0]);
break;
@@ -772,7 +774,7 @@ public class Cpu implements InstructionTable {
break;
/** CMP - Compare Accumulator *******************************************/
// CMP - Compare Accumulator
case 0xc9: // #Immediate
cmp(state.a, state.args[0]);
break;
@@ -792,7 +794,7 @@ public class Cpu implements InstructionTable {
break;
/** DEC - Decrement Memory **********************************************/
// DEC - Decrement Memory
case 0x3a: // 65C02 Immediate
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
@@ -811,7 +813,7 @@ public class Cpu implements InstructionTable {
break;
/** CPX - Compare X Register ********************************************/
// CPX - Compare X Register
case 0xe0: // #Immediate
cmp(state.x, state.args[0]);
break;
@@ -821,7 +823,7 @@ public class Cpu implements InstructionTable {
break;
/** SBC - Subtract with Carry (Borrow) **********************************/
// SBC - Subtract with Carry (Borrow)
case 0xe9: // #Immediate
if (state.decimalModeFlag) {
state.a = sbcDecimal(state.a, state.args[0]);
@@ -849,7 +851,7 @@ public class Cpu implements InstructionTable {
break;
/** INC - Increment Memory **********************************************/
// INC - Increment Memory
case 0x1a: // 65C02 Increment Immediate
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
@@ -868,14 +870,14 @@ public class Cpu implements InstructionTable {
break;
/** 65C02 RMB - Reset Memory Bit **************************************/
// 65C02 RMB - Reset Memory Bit
case 0x07: // 65C02 RMB0 - Zero Page
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
tmp = bus.read(effectiveAddress, true) & 0xff;
tmp &= ~(1 << 0);
tmp &= ~1;
bus.write(effectiveAddress, tmp);
break;
case 0x17: // 65C02 RMB1 - Zero Page
@@ -943,14 +945,14 @@ public class Cpu implements InstructionTable {
break;
/** 65C02 SMB - Set Memory Bit **************************************/
// 65C02 SMB - Set Memory Bit
case 0x87: // 65C02 SMB0 - Zero Page
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
tmp = bus.read(effectiveAddress, true) & 0xff;
tmp |= (1);
tmp |= 1;
bus.write(effectiveAddress, tmp);
break;
case 0x97: // 65C02 SMB1 - Zero Page
@@ -1017,7 +1019,7 @@ public class Cpu implements InstructionTable {
bus.write(effectiveAddress, tmp);
break;
/** 65C02 TRB/TSB - Test and Reset Bit/Test and Set Bit ***************/
// 65C02 TRB/TSB - Test and Reset Bit/Test and Set Bit
case 0x14: // 65C02 TRB - Test and Reset bit - Zero Page
case 0x1c: // 65C02 TRB - Test and Reset bit - Absolute
if (behavior == CpuBehavior.NMOS_6502 ||
@@ -1026,8 +1028,9 @@ public class Cpu implements InstructionTable {
}
tmp = bus.read(effectiveAddress, true);
setZeroFlag((state.a & tmp) == 0);
tmp = (tmp &= ~(state.a)) & 0xff;
bus.write(effectiveAddress,tmp);
tmp &= ~(state.a);
tmp &= 0xff;
bus.write(effectiveAddress, tmp);
break;
case 0x04: // 65C02 TSB - Test and Set bit - Zero Page
@@ -1038,18 +1041,19 @@ public class Cpu implements InstructionTable {
}
tmp = bus.read(effectiveAddress, true);
setZeroFlag((state.a & tmp) == 0);
tmp = (tmp |= (state.a)) & 0xff;
tmp |= state.a;
tmp = tmp & 0xff;
bus.write(effectiveAddress,tmp);
break;
/** 65C02 BBR - Branch if Bit Reset *************************************/
// 65C02 BBR - Branch if Bit Reset
case 0x0f: // 65C02 BBR - Branch if bit 0 reset - Zero Page
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 0) == 0) {
if ((tmp & 1) == 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1060,7 +1064,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 1) == 0) {
if ((tmp & (1 << 1)) == 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1071,7 +1075,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 2) == 0) {
if ((tmp & (1 << 2)) == 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1082,7 +1086,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 3) == 0) {
if ((tmp & (1 << 3)) == 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1093,7 +1097,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 4) == 0) {
if ((tmp & (1 << 4)) == 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1105,7 +1109,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 5) == 0) {
if ((tmp & (1 << 5)) == 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1116,7 +1120,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 6) == 0) {
if ((tmp & (1 << 6)) == 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1127,20 +1131,20 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 7) == 0) {
if ((tmp & (1 << 7)) == 0) {
state.pc = relAddress(state.args[1]);
}
break;
/** 65C02 BBS - Branch if Bit Set ************************************/
// 65C02 BBS - Branch if Bit Set
case 0x8f: // 65C02 BBS - Branch if bit 0 set - Zero Page
if (behavior == CpuBehavior.NMOS_6502 ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG) {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 0) > 0) {
if ((tmp & 1) != 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1151,7 +1155,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 1) > 0) {
if ((tmp & (1 << 1)) != 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1162,7 +1166,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 2) > 0) {
if ((tmp & (1 << 2)) > 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1173,7 +1177,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 3) > 0) {
if ((tmp & (1 << 3)) > 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1185,7 +1189,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 4) > 0) {
if ((tmp & (1 << 4)) > 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1197,7 +1201,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 5) > 0) {
if ((tmp & (1 << 5)) > 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1208,7 +1212,7 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 6) > 0) {
if ((tmp & (1 << 6)) > 0) {
state.pc = relAddress(state.args[1]);
}
break;
@@ -1219,13 +1223,13 @@ public class Cpu implements InstructionTable {
break;
}
tmp = bus.read(effectiveAddress, true);
if ((tmp & 1 << 7) > 0) {
if ((tmp & (1 << 7)) > 0) {
state.pc = relAddress(state.args[1]);
}
break;
/** Unimplemented Instructions ****************************************/
// Unimplemented Instructions
// TODO: Create a flag to enable highly-accurate emulation of unimplemented instructions.
default:
setOpTrap();
@@ -1234,7 +1238,7 @@ public class Cpu implements InstructionTable {
delayLoop(state.ir);
// Peek ahead to the next insturction and arguments
// Peek ahead to the next instruction and arguments
peekAhead();
}
@@ -1265,7 +1269,7 @@ public class Cpu implements InstructionTable {
/**
* Handle the common behavior of BRK, /IRQ, and /NMI
*
* @throws MemoryAccessException
* @throws MemoryAccessException on memory access failure
*/
private void handleInterrupt(int returnPc, int vectorLow, int vectorHigh, boolean isBreak) throws MemoryAccessException {
@@ -1674,7 +1678,7 @@ public class Cpu implements InstructionTable {
public void setProgramCounter(int addr) {
state.pc = addr;
// As a side-effect of setting the program counter,
// As a side effect of setting the program counter,
// we want to peek ahead at the next state.
try {
peekAhead();
@@ -1695,6 +1699,10 @@ public class Cpu implements InstructionTable {
return state.ir;
}
public int getNextIr() {
return state.nextIr;
}
/**
* @value The value of the Process Status Register bits to be set.
*/
@@ -1921,6 +1929,7 @@ public class Cpu implements InstructionTable {
break;
case AIX:
sb.append(" ($").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(",X)");
break;
case ABX:
sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(",X");
break;
@@ -1933,6 +1942,9 @@ public class Cpu implements InstructionTable {
case IND:
sb.append(" ($").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(")");
break;
case ZPI:
sb.append(" ($").append(Utils.byteToHex(args[0])).append(")");
break;
case XIN:
sb.append(" ($").append(Utils.byteToHex(args[0])).append(",X)");
break;
@@ -1971,7 +1983,7 @@ public class Cpu implements InstructionTable {
*/
public String disassembleOpAtAddress(int address) throws MemoryAccessException {
int opCode = bus.read(address, true);
int args[] = new int[2];
int[] args = new int[2];
int size = Cpu.instructionSizes[opCode];
for (int i = 1; i < size; i++) {
int nextRead = (address + i) % bus.endAddress();
+28 -5
View File
@@ -1,3 +1,26 @@
/*
* Copyright (c) 2008-2025 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;
import com.loomcom.symon.util.Utils;
@@ -12,7 +35,7 @@ public class CpuState {
public int a;
/**
* X index regsiter
* X index register
*/
public int x;
@@ -61,13 +84,12 @@ public class CpuState {
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)
* 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.
*/
@SuppressWarnings("CopyConstructorMissesField")
public CpuState(CpuState s) {
this.a = s.a;
this.x = s.x;
@@ -83,6 +105,7 @@ public class CpuState {
this.nextArgs[1] = s.nextArgs[1];
this.instSize = s.instSize;
this.opTrap = s.opTrap;
this.nmiAsserted = s.nmiAsserted;
this.irqAsserted = s.irqAsserted;
this.carryFlag = s.carryFlag;
this.negativeFlag = s.negativeFlag;
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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,26 +25,24 @@ package com.loomcom.symon;
public interface InstructionTable {
int BRK = 0x00;
int JSR = 0x20;
int RTS = 0x60;
/**
* Enumeration of valid CPU behaviors. These determine what behavior and instruction
* set will be simulated, depending on desired version of 6502.
*
* TODO: As of version 0.6, this is still not used! All CPUs are "idealized" NMOS 6502 only.
*/
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
* is very rare in the wild.
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_WITH_ROR_BUG,
/**
* All NMOS 6502's have a bug with the indirect JMP instruction. If the
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_6502,
@@ -68,11 +66,13 @@ public interface InstructionTable {
return "Accumulator";
}
},
AIX {
public String toString() {
return "Absolute, X-Indexed Indirect";
}
},
ABS {
public String toString() {
return "Absolute";
@@ -168,41 +168,44 @@ public interface InstructionTable {
/**
* Instruction opcode names. This lists all opcodes for
* NMOS 6502, CMOS 65C02, and CMOS 65C816
* NMOS 6502, CMOS 65C02, and CMOS 65C816.
* <p>
* TODO (2025-03-14): This table is clearly wrong for 65C816.
* TODO (2025-03-14): Rockwell/WDC 65C02 names conflict with 65C816 names.
*/
String[] opcodeNames = {
"BRK", "ORA", "NOP", "NOP", "TSB", "ORA", "ASL", "RMB0", // 0x00-0x07
"PHP", "ORA", "ASL", "NOP", "TSB", "ORA", "ASL", "BBR0", // 0x08-0x0f
"BPL", "ORA", "ORA", "NOP", "TRB", "ORA", "ASL", "RMB1", // 0x10-0x17
"CLC", "ORA", "INC", "NOP", "TRB", "ORA", "ASL", "BBR1", // 0x18-0x1f
"JSR", "AND", "NOP", "NOP", "BIT", "AND", "ROL", "RMB2", // 0x20-0x27
"PLP", "AND", "ROL", "NOP", "BIT", "AND", "ROL", "BBR2", // 0x28-0x2f
"BMI", "AND", "AND", "NOP", "BIT", "AND", "ROL", "RMB3", // 0x30-0x37
"SEC", "AND", "DEC", "NOP", "BIT", "AND", "ROL", "BBR3", // 0x38-0x3f
"RTI", "EOR", "NOP", "NOP", "NOP", "EOR", "LSR", "RMB4", // 0x40-0x47
"PHA", "EOR", "LSR", "NOP", "JMP", "EOR", "LSR", "BBR4", // 0x48-0x4f
"BVC", "EOR", "EOR", "NOP", "NOP", "EOR", "LSR", "RMB5", // 0x50-0x57
"CLI", "EOR", "PHY", "NOP", "NOP", "EOR", "LSR", "BBR5", // 0x58-0x5f
"RTS", "ADC", "NOP", "NOP", "STZ", "ADC", "ROR", "RMB6", // 0x60-0x67
"PLA", "ADC", "ROR", "NOP", "JMP", "ADC", "ROR", "BBR6", // 0x68-0x6f
"BVS", "ADC", "ADC", "NOP", "STZ", "ADC", "ROR", "RMB7", // 0x70-0x77
"SEI", "ADC", "PLY", "NOP", "JMP", "ADC", "ROR", "BBR7", // 0x78-0x7f
"BRA", "STA", "NOP", "NOP", "STY", "STA", "STX", "SMB0", // 0x80-0x87
"DEY", "BIT", "TXA", "NOP", "STY", "STA", "STX", "BBS0", // 0x88-0x8f
"BCC", "STA", "STA", "NOP", "STY", "STA", "STX", "SMB1", // 0x90-0x97
"TYA", "STA", "TXS", "NOP", "STZ", "STA", "STZ", "BBS1", // 0x98-0x9f
"LDY", "LDA", "LDX", "NOP", "LDY", "LDA", "LDX", "SMB2", // 0xa0-0xa7
"TAY", "LDA", "TAX", "NOP", "LDY", "LDA", "LDX", "BBS2", // 0xa8-0xaf
"BCS", "LDA", "LDA", "NOP", "LDY", "LDA", "LDX", "SMB3", // 0xb0-0xb7
"CLV", "LDA", "TSX", "NOP", "LDY", "LDA", "LDX", "BBS3", // 0xb8-0xbf
"CPY", "CMP", "NOP", "NOP", "CPY", "CMP", "DEC", "SMB4", // 0xc0-0xc7
"INY", "CMP", "DEX", "NOP", "CPY", "CMP", "DEC", "BBS4", // 0xc8-0xcf
"BNE", "CMP", "CMP", "NOP", "NOP", "CMP", "DEC", "SMB5", // 0xd0-0xd7
"CLD", "CMP", "PHX", "NOP", "NOP", "CMP", "DEC", "BBS5", // 0xd8-0xdf
"CPX", "SBC", "NOP", "NOP", "CPX", "SBC", "INC", "SMB6", // 0xe0-0xe7
"INX", "SBC", "NOP", "NOP", "CPX", "SBC", "INC", "BBS6", // 0xe8-0xef
"BEQ", "SBC", "SBC", "NOP", "NOP", "SBC", "INC", "SMB7", // 0xf0-0xf7
"SED", "SBC", "PLX", "NOP", "NOP", "SBC", "INC", "BBS7" // 0xf8-0xff
"BRK", "ORA", "NOP", "NOP", "TSB", "ORA", "ASL", "RMB0", // 0x00-0x07
"PHP", "ORA", "ASL", "NOP", "TSB", "ORA", "ASL", "BBR0", // 0x08-0x0f
"BPL", "ORA", "ORA", "NOP", "TRB", "ORA", "ASL", "RMB1", // 0x10-0x17
"CLC", "ORA", "INC", "NOP", "TRB", "ORA", "ASL", "BBR1", // 0x18-0x1f
"JSR", "AND", "NOP", "NOP", "BIT", "AND", "ROL", "RMB2", // 0x20-0x27
"PLP", "AND", "ROL", "NOP", "BIT", "AND", "ROL", "BBR2", // 0x28-0x2f
"BMI", "AND", "AND", "NOP", "BIT", "AND", "ROL", "RMB3", // 0x30-0x37
"SEC", "AND", "DEC", "NOP", "BIT", "AND", "ROL", "BBR3", // 0x38-0x3f
"RTI", "EOR", "NOP", "NOP", "NOP", "EOR", "LSR", "RMB4", // 0x40-0x47
"PHA", "EOR", "LSR", "NOP", "JMP", "EOR", "LSR", "BBR4", // 0x48-0x4f
"BVC", "EOR", "EOR", "NOP", "NOP", "EOR", "LSR", "RMB5", // 0x50-0x57
"CLI", "EOR", "PHY", "NOP", "NOP", "EOR", "LSR", "BBR5", // 0x58-0x5f
"RTS", "ADC", "NOP", "NOP", "STZ", "ADC", "ROR", "RMB6", // 0x60-0x67
"PLA", "ADC", "ROR", "NOP", "JMP", "ADC", "ROR", "BBR6", // 0x68-0x6f
"BVS", "ADC", "ADC", "NOP", "STZ", "ADC", "ROR", "RMB7", // 0x70-0x77
"SEI", "ADC", "PLY", "NOP", "JMP", "ADC", "ROR", "BBR7", // 0x78-0x7f
"BRA", "STA", "NOP", "NOP", "STY", "STA", "STX", "SMB0", // 0x80-0x87
"DEY", "BIT", "TXA", "NOP", "STY", "STA", "STX", "BBS0", // 0x88-0x8f
"BCC", "STA", "STA", "NOP", "STY", "STA", "STX", "SMB1", // 0x90-0x97
"TYA", "STA", "TXS", "NOP", "STZ", "STA", "STZ", "BBS1", // 0x98-0x9f
"LDY", "LDA", "LDX", "NOP", "LDY", "LDA", "LDX", "SMB2", // 0xa0-0xa7
"TAY", "LDA", "TAX", "NOP", "LDY", "LDA", "LDX", "BBS2", // 0xa8-0xaf
"BCS", "LDA", "LDA", "NOP", "LDY", "LDA", "LDX", "SMB3", // 0xb0-0xb7
"CLV", "LDA", "TSX", "NOP", "LDY", "LDA", "LDX", "BBS3", // 0xb8-0xbf
"CPY", "CMP", "NOP", "NOP", "CPY", "CMP", "DEC", "SMB4", // 0xc0-0xc7
"INY", "CMP", "DEX", "NOP", "CPY", "CMP", "DEC", "BBS4", // 0xc8-0xcf
"BNE", "CMP", "CMP", "NOP", "NOP", "CMP", "DEC", "SMB5", // 0xd0-0xd7
"CLD", "CMP", "PHX", "NOP", "NOP", "CMP", "DEC", "BBS5", // 0xd8-0xdf
"CPX", "SBC", "NOP", "NOP", "CPX", "SBC", "INC", "SMB6", // 0xe0-0xe7
"INX", "SBC", "NOP", "NOP", "CPX", "SBC", "INC", "BBS6", // 0xe8-0xef
"BEQ", "SBC", "SBC", "NOP", "NOP", "SBC", "INC", "SMB7", // 0xf0-0xf7
"SED", "SBC", "PLX", "NOP", "NOP", "SBC", "INC", "BBS7" // 0xf8-0xff
};
/**
+45 -45
View File
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 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
@@ -22,43 +22,54 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon;
import com.loomcom.symon.machines.MulticompMachine;
import com.loomcom.symon.machines.SimpleMachine;
import com.loomcom.symon.machines.SymonMachine;
import com.loomcom.symon.machines.*;
import com.loomcom.symon.ui.Ui;
import org.apache.commons.cli.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Locale;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class Main {
/**
private final static Logger logger = LoggerFactory.getLogger(Main.class.getName());
/**
* Main entry point to the simulator. Creates a simulator and shows the main
* window.
*
* @param args Program arguments
*/
public static void main(String[] args) throws Exception {
Class machineClass = SymonMachine.class;
Class<?> machineClass = null;
Options options = new Options();
options.addOption(new Option("h", "help", false, "Print this message."));
options.addOption(new Option("m", "machine", true, "Specify machine type."));
options.addOption(new Option("c", "cpu", true, "Specify CPU type."));
options.addOption(new Option("r", "rom", true, "Specify ROM file."));
options.addOption(new Option("b", "brk", false, "Halt on BRK"));
CommandLineParser parser = new DefaultParser();
try {
CommandLine line = parser.parse(options, args);
InstructionTable.CpuBehavior cpuBehavior = null;
String romFile = null;
boolean haltOnBreak = false;
if (line.hasOption("help")) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("symon", options, false);
return;
}
if (line.hasOption("machine")) {
String machine = line.getOptionValue("machine").toLowerCase(Locale.ENGLISH);
@@ -72,8 +83,11 @@ public class Main {
case "symon":
machineClass = SymonMachine.class;
break;
case "beneater":
machineClass = BenEaterMachine.class;
break;
default:
System.err.println("Could not start Symon. Unknown machine type " + machine);
logger.error("Could not start Symon. Unknown machine type {}", machine);
return;
}
}
@@ -91,7 +105,7 @@ public class Main {
cpuBehavior = InstructionTable.CpuBehavior.CMOS_65816;
break;
default:
System.err.println("Could not start Symon. Unknown cpu type " + cpu);
logger.error("Could not start Symon. Unknown cpu type {}", cpu);
return;
}
}
@@ -100,48 +114,33 @@ public class Main {
romFile = line.getOptionValue("rom");
}
if (line.hasOption("brk")) {
haltOnBreak = true;
}
Ui.initUi();
while (true) {
if (machineClass == null) {
Object[] possibilities = {"Symon", "Multicomp", "Simple"};
String s = (String)JOptionPane.showInputDialog(
null,
"Please choose the machine type to be emulated:",
"Machine selection",
JOptionPane.PLAIN_MESSAGE,
null,
possibilities,
"Symon");
if (s != null && s.equals("Multicomp")) {
machineClass = MulticompMachine.class;
} else if (s != null && s.equals("Simple")) {
machineClass = SimpleMachine.class;
} else {
machineClass = SymonMachine.class;
}
machineClass = Ui.showMachineSelectionDialog();
}
if (cpuBehavior == null) {
cpuBehavior = InstructionTable.CpuBehavior.NMOS_6502;
}
final Simulator simulator = new Simulator(machineClass, cpuBehavior, romFile);
final Simulator simulator = new Simulator(machineClass, cpuBehavior, romFile, haltOnBreak);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi();
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(() -> {
// Create the main UI window:
try {
simulator.createAndShowUi();
} catch (Exception e) {
logger.error("Error during Symon UI initialization: {}", e.getMessage());
System.exit(-1);
}
});
Simulator.MainCommand cmd = simulator.waitForCommand();
if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) {
@@ -151,7 +150,8 @@ public class Main {
}
}
} catch (ParseException ex) {
System.err.println("Could not start Symon. Reason: " + ex.getMessage());
logger.error("Could not start Symon. Reason: {}", ex.getMessage());
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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,7 +27,7 @@ import com.loomcom.symon.exceptions.*;
/**
* MemoryRange is a simple container class representing a literal
* range of memory, with a staraddress, and an end address. It has
* range of memory, with a start address, and an end address. It has
* guards against creating impossible memory ranges, and implements
* some useful methods for checking address inclusion and range
* overlaps.
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -29,8 +29,6 @@ public interface Preferences {
int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300;
boolean DEFAULT_HALT_ON_BREAK = true;
JDialog getDialog();
int getProgramStartAddress();
+133 -120
View File
@@ -1,5 +1,5 @@
/*
* Copyrighi (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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,14 +31,13 @@ import com.loomcom.symon.ui.Console;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.monitor.CounterMonitor;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.SortedSet;
import java.util.TreeSet;
import static com.loomcom.symon.InstructionTable.*;
/**
* Symon Simulator Interface and Control.
@@ -72,7 +71,7 @@ public class Simulator {
private static final int MAX_STEPS_BETWEEN_UPDATES = 20000;
// The simulated machine
private Machine machine;
private final Machine machine;
// Number of CPU steps between CRT repaints.
// TODO: Dynamically refresh the value at runtime based on performance figures to reach ~ 30fps.
@@ -115,17 +114,20 @@ public class Simulator {
private JButton runStopButton;
private JButton stepButton;
private JButton stepOverButton;
private JComboBox<String> stepCountBox;
private JFileChooser fileChooser;
private PreferencesDialog preferences;
private Breakpoints breakpoints;
private final Breakpoints breakpoints;
private final Object commandMonitorObject = new Object();
private MainCommand command = MainCommand.NONE;
private final boolean haltOnBreak;
public enum MainCommand {
NONE,
SELECTMACHINE
@@ -136,17 +138,18 @@ public class Simulator {
*/
private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"};
public Simulator(Class machineClass) throws Exception {
this(machineClass, InstructionTable.CpuBehavior.NMOS_6502, null);
public Simulator(Class<?> machineClass) throws Exception {
this(machineClass, InstructionTable.CpuBehavior.NMOS_6502, null, false);
}
public Simulator(Class machineClass, InstructionTable.CpuBehavior cpuType, String romFile) throws Exception {
public Simulator(Class<?> machineClass, InstructionTable.CpuBehavior cpuType,
String romFile, boolean haltOnBreak) throws Exception {
this.haltOnBreak = haltOnBreak;
this.breakpoints = new Breakpoints(this);
this.machine = (Machine) machineClass.getConstructors()[0].newInstance(romFile);
this.machine.getCpu().setBehavior(cpuType);
// Initialize final fields in the constructor.
this.traceLog = new TraceLog();
this.memoryWindow = new MemoryWindow(machine.getBus());
@@ -162,7 +165,7 @@ public class Simulator {
/**
* Display the main simulator UI.
*/
public void createAndShowUi() throws IOException {
public void createAndShowUi() {
mainWindow = new JFrame();
mainWindow.setTitle("6502 Simulator - " + machine.getName());
mainWindow.setResizable(false);
@@ -176,7 +179,7 @@ public class Simulator {
// File Chooser
fileChooser = new JFileChooser(System.getProperty("user.dir"));
preferences = new PreferencesDialog(mainWindow, true);
preferences = new PreferencesDialog(mainWindow, true, haltOnBreak);
// Panel for Console and Buttons
JPanel consoleContainer = new JPanel();
@@ -188,18 +191,22 @@ public class Simulator {
runStopButton = new JButton("Run");
stepButton = new JButton("Step");
stepOverButton = new JButton("Step Over");
stepOverButton.setEnabled(false);
JButton softResetButton = new JButton("Soft Reset");
JButton hardResetButton = new JButton("Hard Reset");
stepCountBox = new JComboBox<>(STEPS);
stepCountBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
stepCountBox.addActionListener(actionEvent -> {
JComboBox<?> cb = (JComboBox<?>) actionEvent.getSource();
String cbStepsPerClick = (String) cb.getSelectedItem();
stepsPerClick = 1; // Default in case of error
if (cbStepsPerClick != null) {
try {
JComboBox cb = (JComboBox) actionEvent.getSource();
stepsPerClick = Integer.parseInt((String) cb.getSelectedItem());
stepsPerClick = Integer.parseInt(cbStepsPerClick);
} catch (NumberFormatException ex) {
stepsPerClick = 1;
stepCountBox.setSelectedIndex(0);
}
}
@@ -207,6 +214,7 @@ public class Simulator {
buttonContainer.add(runStopButton);
buttonContainer.add(stepButton);
buttonContainer.add(stepOverButton);
buttonContainer.add(stepCountBox);
buttonContainer.add(softResetButton);
buttonContainer.add(hardResetButton);
@@ -221,38 +229,25 @@ public class Simulator {
// Bottom - buttons.
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
runStopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (runLoop != null && runLoop.isRunning()) {
Simulator.this.handleStop();
} else {
Simulator.this.handleStart();
}
runStopButton.addActionListener(actionEvent -> {
if (runLoop != null && runLoop.isRunning()) {
Simulator.this.handleStop();
} else {
Simulator.this.handleStart();
}
});
stepButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
Simulator.this.handleStep(stepsPerClick);
}
stepButton.addActionListener(actionEvent -> Simulator.this.handleStep(stepsPerClick));
stepOverButton.addActionListener(actionEvent -> Simulator.this.handleStepOver());
softResetButton.addActionListener(actionEvent -> {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(false);
});
softResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(false);
}
});
hardResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(true);
}
hardResetButton.addActionListener(actionEvent -> {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(true);
});
mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
@@ -274,7 +269,7 @@ public class Simulator {
try {
commandMonitorObject.wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
logger.error("Interrupted during wait on command monitor.");
}
}
return command;
@@ -337,12 +332,28 @@ public class Simulator {
step();
}
updateVisibleState();
stepOverButton.setEnabled(machine.getCpu().getNextIr() == JSR);
} catch (SymonException ex) {
logger.error("Exception during simulator step", ex);
ex.printStackTrace();
}
}
/**
* Step over a JSR / RTS function. This function is much more like a "run()"
* than a "step()", and may enter an infinite loop if no RTS is encountered.
*/
private void handleStepOver() {
if (runLoop != null && runLoop.isRunning()) {
// This should really never happen...
logger.error("Can't step over when the simulator is already running!");
return;
}
runLoop = new RunLoop();
runLoop.haltOnRts(true);
runLoop.start();
}
/**
* Perform a single step of the simulated system.
*/
@@ -362,7 +373,7 @@ public class Simulator {
// If a key has been pressed, fill the ACIA.
try {
if (machine.getAcia() != null && console.hasInput()) {
machine.getAcia().rxWrite((int) console.readInputChar());
machine.getAcia().rxWrite(console.readInputChar());
}
} catch (FifoUnderrunException ex) {
logger.error("Console type-ahead buffer underrun!");
@@ -411,53 +422,73 @@ public class Simulator {
*/
class RunLoop extends Thread {
private boolean isRunning = false;
private boolean haltOnRts = false;
// The number of JSR instructions we've seen since being asked to
// step over a subroutine. While stepping over, this is incremented
// on RTS instructions, and decremented on JSR instructions.
private int callStackDepth = 0;
public boolean isRunning() {
return isRunning;
return this.isRunning;
}
public void requestStop() {
isRunning = false;
this.isRunning = false;
}
public void haltOnRts(boolean value) {
this.haltOnRts = value;
}
public void run() {
logger.debug("Starting main run loop.");
isRunning = true;
if (this.haltOnRts) {
logger.debug("Running until next RTS instruction");
} else {
logger.debug("Running");
}
this.isRunning = true;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
stepCountBox.setEnabled(false);
menuBar.simulatorDidStart();
// Toggle the state of the run button
runStopButton.setText("Stop");
}
SwingUtilities.invokeLater(() -> {
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
stepOverButton.setEnabled(false);
stepCountBox.setEnabled(false);
menuBar.simulatorDidStart();
// Toggle the state of the run button
runStopButton.setText("Stop");
});
try {
do {
step();
if (this.haltOnRts) {
var instruction = machine.getCpu().getInstruction();
if (instruction == JSR) {
this.callStackDepth++;
logger.trace("Step-over call stack increased to {}", this.callStackDepth);
} else if (this.callStackDepth > 0 && instruction == RTS) {
this.callStackDepth--;
logger.trace("Step-over call stack decreased to {}", this.callStackDepth);
}
}
} while (shouldContinue());
} catch (SymonException ex) {
logger.error("Exception in main simulator run thread. Exiting run.", ex);
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
statusPane.updateState();
memoryWindow.updateState();
runStopButton.setText("Run");
stepButton.setEnabled(true);
stepCountBox.setEnabled(true);
if (traceLog.isVisible()) {
traceLog.refresh();
}
menuBar.simulatorDidStop();
traceLog.simulatorDidStop();
SwingUtilities.invokeLater(() -> {
statusPane.updateState();
memoryWindow.updateState();
runStopButton.setText("Run");
stepButton.setEnabled(true);
stepOverButton.setEnabled(machine.getCpu().getNextIr() == JSR);
stepCountBox.setEnabled(true);
if (traceLog.isVisible()) {
traceLog.refresh();
}
menuBar.simulatorDidStop();
traceLog.simulatorDidStop();
});
isRunning = false;
@@ -467,9 +498,13 @@ public class Simulator {
* @return True if the run loop should proceed to the next step.
*/
private boolean shouldContinue() {
return !breakpoints.contains(machine.getCpu().getProgramCounter()) &&
isRunning &&
!(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00);
var instruction = machine.getCpu().getInstruction();
var stepOverHalt = this.haltOnRts && this.callStackDepth == 0 && instruction == RTS;
var breakpointHalt = breakpoints.contains(machine.getCpu().getProgramCounter());
var brkHalt = preferences.getHaltOnBreak() && instruction == BRK;
return isRunning && !(stepOverHalt || breakpointHalt || brkHalt);
}
}
@@ -510,12 +545,9 @@ public class Simulator {
// Now load the program at the starting address.
loadProgram(program, preferences.getProgramStartAddress());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
console.reset();
breakpoints.refresh();
}
SwingUtilities.invokeLater(() -> {
console.reset();
breakpoints.refresh();
});
// TODO: "Don't Show Again" checkbox
@@ -553,7 +585,7 @@ public class Simulator {
long fileSize = romFile.length();
if (fileSize != machine.getRomSize()) {
throw new IOException("ROM file must be exactly " + String.valueOf(machine.getRomSize()) + " bytes.");
throw new IOException("ROM file must be exactly " + machine.getRomSize() + " bytes.");
}
// Load the new ROM image
@@ -647,30 +679,27 @@ public class Simulator {
}
class SetFontAction extends AbstractAction {
private int size;
private final int size;
public SetFontAction(int size) {
super(Integer.toString(size) + " pt", null);
super(size + " pt", null);
this.size = size;
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();
}
SwingUtilities.invokeLater(() -> {
console.setFont(new Font("Monospaced", Font.PLAIN, size));
mainWindow.pack();
});
}
}
class SetSpeedAction extends AbstractAction {
private int speed;
private final int speed;
public SetSpeedAction(int speed) {
super(Integer.toString(speed) + " MHz", null);
super(speed + " MHz", null);
this.speed = speed;
putValue(SHORT_DESCRIPTION, "Set simulated speed to " + speed + " MHz.");
}
@@ -686,7 +715,7 @@ public class Simulator {
}
class SetCpuAction extends AbstractAction {
private Cpu.CpuBehavior behavior;
private final Cpu.CpuBehavior behavior;
public SetCpuAction(String cpu, Cpu.CpuBehavior behavior) {
super(cpu, null);
@@ -726,11 +755,7 @@ public class Simulator {
public void actionPerformed(ActionEvent actionEvent) {
synchronized (memoryWindow) {
if (memoryWindow.isVisible()) {
memoryWindow.setVisible(false);
} else {
memoryWindow.setVisible(true);
}
memoryWindow.setVisible(!memoryWindow.isVisible());
}
}
}
@@ -743,11 +768,7 @@ public class Simulator {
public void actionPerformed(ActionEvent actionEvent) {
synchronized (videoWindow) {
if (videoWindow.isVisible()) {
videoWindow.setVisible(false);
} else {
videoWindow.setVisible(true);
}
videoWindow.setVisible(!videoWindow.isVisible());
}
}
}
@@ -760,11 +781,7 @@ public class Simulator {
public void actionPerformed(ActionEvent actionEvent) {
synchronized (breakpointsWindow) {
if (breakpointsWindow.isVisible()) {
breakpointsWindow.setVisible(false);
} else {
breakpointsWindow.setVisible(true);
}
breakpointsWindow.setVisible(!breakpointsWindow.isVisible());
}
}
}
@@ -960,15 +977,11 @@ public class Simulator {
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();
}
SwingUtilities.invokeLater(() -> {
statusPane.updateState();
memoryWindow.updateState();
if (traceLog.shouldUpdate()) {
traceLog.refresh();
}
});
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -32,7 +32,7 @@ import com.loomcom.symon.exceptions.MemoryRangeException;
public abstract class Acia extends Device {
private String name;
private final String name;
/**
* Register addresses
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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,7 +28,7 @@ import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the MOS 6551 ACIA, with limited
* functionality. Interrupts are not supported.
* functionality.
* <p/>
* Unlike a 16550 UART, the 6551 ACIA has only one-byte transmit and
* receive buffers. It is the programmer's responsibility to check the
@@ -53,6 +53,14 @@ public class Acia6551 extends Acia {
public Acia6551(int address) throws MemoryRangeException {
super(address, ACIA_SIZE, "ACIA");
// Figure 6 in the 6551 ACIA data sheet says the "hardware reset"
// state of the Control Register is all zeros.
setControlRegister(0b00000000);
// Figure 7 of the 6551 ACIA data sheet says the "hardware reset"
// state of the Command Register is zeros, but Transmitter Control
// is set to "interrupt disabled, ready to send".
setCommandRegister(0b00000010);
}
@Override
@@ -74,16 +82,16 @@ public class Acia6551 extends Acia {
@Override
public void write(int address, int data) throws MemoryAccessException {
switch (address) {
case 0:
case DATA_REG:
txWrite(data);
break;
case 1:
case STAT_REG:
reset();
break;
case 2:
case CMND_REG:
setCommandRegister(data);
break;
case 3:
case CTRL_REG:
setControlRegister(data);
break;
default:
@@ -110,67 +118,59 @@ public class Acia6551 extends Acia {
controlRegister = data;
int rate = 0;
// If the value of the data is 0, this is a request to reset,
// otherwise it's a control update.
if (data == 0) {
reset();
} else {
// Mask the lower three bits to get the baud rate.
int baudSelector = data & 0x0f;
switch (baudSelector) {
case 0:
rate = 0;
break;
case 1:
rate = 50;
break;
case 2:
rate = 75;
break;
case 3:
rate = 110; // Real rate is actually 109.92
break;
case 4:
rate = 135; // Real rate is actually 134.58
break;
case 5:
rate = 150;
break;
case 6:
rate = 300;
break;
case 7:
rate = 600;
break;
case 8:
rate = 1200;
break;
case 9:
rate = 1800;
break;
case 10:
rate = 2400;
break;
case 11:
rate = 3600;
break;
case 12:
rate = 4800;
break;
case 13:
rate = 7200;
break;
case 14:
rate = 9600;
break;
case 15:
rate = 19200;
break;
}
setBaudRate(rate);
// Mask the lower four bits to get the baud rate.
int baudSelector = data & 0x0f;
switch (baudSelector) {
case 1:
rate = 50;
break;
case 2:
rate = 75;
break;
case 3:
rate = 110; // Real rate is actually 109.92
break;
case 4:
rate = 135; // Real rate is actually 134.58
break;
case 5:
rate = 150;
break;
case 6:
rate = 300;
break;
case 7:
rate = 600;
break;
case 8:
rate = 1200;
break;
case 9:
rate = 1800;
break;
case 10:
rate = 2400;
break;
case 11:
rate = 3600;
break;
case 12:
rate = 4800;
break;
case 13:
rate = 7200;
break;
case 14:
rate = 9600;
break;
case 15:
rate = 19200;
break;
default:
break;
}
setBaudRate(rate);
}
@@ -203,13 +203,16 @@ public class Acia6551 extends Acia {
private synchronized void reset() {
txChar = 0;
txEmpty = true;
rxChar = 0;
rxFull = false;
receiveIrqEnabled = false;
transmitIrqEnabled = false;
interrupt = false;
}
// Figure 6 in the 6551 ACIA data sheet says the "program reset"
// event does not modify the control register.
// Figure 7 in the 6551 ACIA data sheet says the "program reset"
// event keeps the "parity check" configuration in the command
// register, but resets the other bits to defaults.
setCommandRegister((commandRegister & 0xe0) | 0x02);
// Figure 8 in the 6551 ACIA data sheet says the "program reset"
// event clears the "overrun" flag but otherwise has no effect.
overrun = false;
}
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 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
@@ -31,7 +31,7 @@ import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the Motorola 6850 ACIA, with limited
* functionality.
*
* <p>
* Unlike a 16550 UART, the 6850 ACIA has only one-byte transmit and
* receive buffers. It is the programmer's responsibility to check the
* status (full or empty) for transmit and receive buffers before
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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,8 +26,6 @@ package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.IOException;
/**
* Simulation of a 6545 CRTC and virtual CRT output.
*/
@@ -87,9 +85,9 @@ public class Crtc extends Device {
private boolean displayEnableSkew = false;
private boolean cursorSkew = false;
private Memory memory;
private final Memory memory;
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException {
super(deviceAddress, deviceAddress + 2, "CRTC");
this.memory = memory;
@@ -120,19 +118,17 @@ public class Crtc extends Device {
@Override
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
switch (address) {
case REGISTER_RW:
switch (currentRegister) {
case CURSOR_POSITION_LOW:
return cursorPosition & 0xff;
case CURSOR_POSITION_HIGH:
return cursorPosition >> 8;
default:
return 0;
}
default:
return 0;
if (address == REGISTER_RW) {
switch (currentRegister) {
case CURSOR_POSITION_LOW:
return cursorPosition & 0xff;
case CURSOR_POSITION_HIGH:
return cursorPosition >> 8;
default:
return 0;
}
}
return 0;
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -45,7 +45,7 @@ public abstract class Device implements Comparable<Device> {
/**
* The memory range for this device.
*/
private MemoryRange memoryRange;
private final MemoryRange memoryRange;
/**
* The name of the device.
@@ -60,7 +60,7 @@ public abstract class Device implements Comparable<Device> {
/**
* Listeners to notify on update.
*/
private Set<DeviceChangeListener> deviceChangeListeners;
private final Set<DeviceChangeListener> deviceChangeListeners;
public Device(int startAddress, int endAddress, String name) throws MemoryRangeException {
this.memoryRange = new MemoryRange(startAddress, endAddress);
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -30,8 +30,8 @@ import com.loomcom.symon.exceptions.*;
public class Memory extends Device {
private boolean readOnly;
private int[] mem;
private final boolean readOnly;
private final int[] mem;
/* Initialize all locations to 0x00 (BRK) */
private static final int DEFAULT_FILL = 0x00;
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 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
@@ -119,7 +119,10 @@ public class SdController extends Device {
if (sdImageFile != null) {
try {
FileInputStream fis = new FileInputStream(sdImageFile);
fis.skip(this.position);
long bytesSkipped = fis.skip(this.position);
if (bytesSkipped < this.position) {
logger.log(Level.WARNING, "Failed to seek to position " + 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");
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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,83 +27,23 @@ 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.
* Skeleton for future implementation of a MOS 6522 VIA.
*/
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:
}
// TODO: Implement write.
}
@Override
public int read(int address, boolean cpuAccess) 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:
}
// TODO: Implement read.
return 0;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Portions Copyright (c) 2009-2011 Graham Edgecombe
*
* Permission is hereby granted, free of charge, to any person obtaining
@@ -25,13 +25,8 @@
package com.loomcom.symon.jterminal;
import com.loomcom.symon.jterminal.vt100.Vt100TerminalModel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.*;
import javax.swing.JComponent;
import javax.swing.JScrollBar;
@@ -88,10 +83,6 @@ public class JTerminal extends JComponent {
revalidate();
}
public int getBorderWidth() {
return borderWidth;
}
public void setFont(Font font) {
this.font = font;
setCellWidthAndHeight(font);
@@ -113,13 +104,6 @@ public class JTerminal extends JComponent {
return model;
}
public void println(String str) {
if (str == null) {
throw new NullPointerException("str");
}
print(str.concat("\r\n"));
}
public void print(String str) {
model.print(str);
}
@@ -177,12 +161,8 @@ public class JTerminal extends JComponent {
int bufferSize = model.getBufferSize();
if (bufferSize > rows) {
scrollBar = new JScrollBar(1, 0, rows, 0, bufferSize + 1);
scrollBar.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent evt) {
repaint();
}
});
scrollBar = new JScrollBar(Adjustable.VERTICAL, 0, rows, 0, bufferSize + 1);
scrollBar.addAdjustmentListener(evt -> repaint());
add("After", scrollBar);
}
@@ -32,51 +32,45 @@ import com.loomcom.symon.jterminal.bell.BellStrategy;
*/
public interface TerminalModel {
/**
* Gets the bell strategy.
* @return The bell strategy.
*/
public BellStrategy getBellStrategy();
/**
* Sets the bell strategy.
* @param strategy The bell strategy.
* @throws NullPointerException if the strategy is {@code null}.
*/
public void setBellStrategy(BellStrategy strategy);
void setBellStrategy(BellStrategy strategy);
/**
* Clears the terminal.
*/
public void clear();
void clear();
/**
* Moves the cursor back n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorBack(int n);
void moveCursorBack(int n);
/**
* Moves the cursor forward n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorForward(int n);
void moveCursorForward(int n);
/**
* Moves the cursor down n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorDown(int n);
void moveCursorDown(int n);
/**
* Moves the cursor up n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorUp(int n);
void moveCursorUp(int n);
/**
* Sets a cell.
@@ -86,7 +80,7 @@ public interface TerminalModel {
* @throws IndexOutOfBoundsException if the column and/or row number(s) are
* out of bounds.
*/
public void setCell(int column, int row, TerminalCell cell);
void setCell(int column, int row, TerminalCell cell);
/**
* Gets a cell.
@@ -96,7 +90,7 @@ public interface TerminalModel {
* @throws IndexOutOfBoundsException if the column and/or row number(s) are
* out of bounds.
*/
public TerminalCell getCell(int column, int row);
TerminalCell getCell(int column, int row);
/**
* Prints the specified string to the terminal at the cursor position,
@@ -105,63 +99,63 @@ public interface TerminalModel {
* @param str The string to print.
* @throws NullPointerException if the string is {@code null}.
*/
public void print(String str);
void print(String str);
/**
* Gets the number of columns.
* @return The number of columns.
*/
public int getColumns();
int getColumns();
/**
* Gets the number of rows.
* @return The number of rows.
*/
public int getRows();
int getRows();
/**
* Gets the buffer size.
* @return The buffer size.
*/
public int getBufferSize();
int getBufferSize();
/**
* Gets the cursor row.
* @return The cursor row.
*/
public int getCursorRow();
int getCursorRow();
/**
* Sets the cursor row.
* @param row The cursor row.
* @throws IllegalArgumentException if the row is out of the valid range.
*/
public void setCursorRow(int row);
void setCursorRow(int row);
/**
* Gets the cursor column.
* @return The cursor column.
*/
public int getCursorColumn();
int getCursorColumn();
/**
* Sets the cursor column.
* @param column The cursor column.
* @throws IllegalArgumentException if the column is out of the valid range.
*/
public void setCursorColumn(int column);
void setCursorColumn(int column);
/**
* Gets the default background color.
* @return The default background color.
*/
public Color getDefaultBackgroundColor();
Color getDefaultBackgroundColor();
/**
* Gets the default foreground color.
* @return The default foreground color.
*/
public Color getDefaultForegroundColor();
Color getDefaultForegroundColor();
}
@@ -31,7 +31,7 @@ public interface BellStrategy {
/**
* Sounds the bell.
*/
public void soundBell();
void soundBell();
}
@@ -50,7 +50,7 @@ public class AnsiControlSequence {
throw new NullPointerException("parameters");
}
this.command = command;
if (parameters.length == 1 && parameters[0].equals("")) {
if (parameters.length == 1 && parameters[0].isEmpty()) {
this.parameters = new String[0];
} else {
this.parameters = parameters.clone();
@@ -32,13 +32,13 @@ public interface AnsiControlSequenceListener {
* Called when a control sequence has been parsed.
* @param seq The control sequence.
*/
public void parsedControlSequence(AnsiControlSequence seq);
void parsedControlSequence(AnsiControlSequence seq);
/**
* Called when a string has been parsed.
* @param str The string.
*/
public void parsedString(String str);
void parsedString(String str);
}
@@ -33,7 +33,7 @@ import java.io.StringReader;
public class AnsiControlSequenceParser {
/**
* The multi-byte control sequence introducer.
* The multibyte control sequence introducer.
*/
private static final char[] MULTI_CSI = new char[] { 27, '[' };
@@ -43,7 +43,7 @@ public class AnsiControlSequenceParser {
private static final char SINGLE_CSI = 155;
/**
* The buffer of data from the last call to {@link #parse()}. This is
* The buffer of data from the last call to "parse()". This is
* populated with data if an escape sequence is not complete.
*/
private StringBuilder buffer = new StringBuilder();
@@ -70,13 +70,10 @@ public class AnsiControlSequenceParser {
str = buffer.toString().concat(str);
buffer = new StringBuilder();
}
Reader reader = new StringReader(str);
try {
try {
parse(reader);
} finally {
reader.close();
}
try {
try (Reader reader = new StringReader(str)) {
parse(reader);
}
} catch (IOException ex) {
/* ignore */
}
@@ -147,7 +144,7 @@ public class AnsiControlSequenceParser {
if (!finishedSequence) {
// not an ideal solution if they used the two byte CSI, but it's
// easier and cleaner than keeping track of it
buffer.append((char) SINGLE_CSI);
buffer.append(SINGLE_CSI);
buffer.append(parameters);
}
}
@@ -56,6 +56,7 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
@Override
public void parsedControlSequence(AnsiControlSequence seq) {
char command = seq.getCommand();
int n;
String[] parameters = seq.getParameters();
switch (command) {
@@ -63,7 +64,7 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
case 'B':
case 'C':
case 'D':
int n = 1;
n = 1;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
@@ -73,7 +74,7 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
moveCursorDown(n);
} else if (command == 'C') {
moveCursorForward(n);
} else if (command == 'D') {
} else {
moveCursorBack(n);
}
break;
@@ -85,7 +86,7 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
}
if (command == 'E') {
moveCursorDown(n);
} else if (command == 'F') {
} else {
moveCursorUp(n);
}
setCursorColumn(0);
@@ -215,7 +216,8 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
case '\t':
while ((++cursorColumn % TAB_WIDTH) != 0);
continue;
case 127:
case 8: // ASCII Backspace
case 127: // ASCII Delete
if (cursorColumn > 0) {
cells[cursorRow][--cursorColumn] = null;
}
@@ -464,12 +466,7 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
return bufferSize;
}
@Override
public BellStrategy getBellStrategy() {
return bellStrategy;
}
@Override
@Override
public void setBellStrategy(BellStrategy strategy) {
if (strategy == null) {
throw new NullPointerException("strategy");
@@ -0,0 +1,169 @@
/*
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Chelsea Wilkinson <mail@chelseawilkinson.me>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.*;
import com.loomcom.symon.exceptions.MemoryRangeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public class BenEaterMachine implements Machine {
private final static Logger logger = LoggerFactory.getLogger(BenEaterMachine.class.getName());
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
// 16K of RAM from $0000 - $3FFF
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0x4000;
// VIA at $6000-$600F
private static final int PIA_BASE = 0x6000;
// ACIA at $5000-$5003
private static final int ACIA_BASE = 0x5000;
// CRTC at $4000-$4001
private static final int CRTC_BASE = 0x4000;
// 32KB ROM at $8000-$FFFF
private static final int ROM_BASE = 0x8000;
private static final int ROM_SIZE = 0x8000;
// The simulated peripherals
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Pia pia;
private final Crtc crtc;
private final Memory ram;
private Memory rom;
public BenEaterMachine(String romFile) throws Exception {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false);
this.pia = new Via6522(PIA_BASE);
this.acia = new Acia6551(ACIA_BASE);
this.crtc = new Crtc(CRTC_BASE, ram);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(pia);
bus.addDevice(acia);
bus.addDevice(crtc);
if (romFile != null) {
File romImage = new File(romFile);
if (romImage.canRead()) {
logger.info("Loading ROM image from file {}", romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
} else {
logger.info("Default ROM file {} not found, loading empty R/W memory image.", romImage);
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
} else {
logger.info("No ROM file specified, loading empty R/W memory image.");
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
bus.addDevice(rom);
}
@Override
public Bus getBus() {
return bus;
}
@Override
public Cpu getCpu() {
return cpu;
}
@Override
public Memory getRam() {
return ram;
}
@Override
public Acia getAcia() {
return acia;
}
@Override
public Pia getPia() {
return pia;
}
@Override
public Crtc getCrtc() {
return crtc;
}
@Override
public Memory getRom() {
return rom;
}
public void setRom(Memory rom) throws MemoryRangeException {
if(this.rom != null) {
bus.removeDevice(this.rom);
}
this.rom = rom;
bus.addDevice(this.rom);
}
@Override
public int getRomBase() {
return ROM_BASE;
}
@Override
public int getRomSize() {
return ROM_SIZE;
}
@Override
public int getMemorySize() {
return MEMORY_SIZE;
}
@Override
public String getName() {
return "benEater";
}
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 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
@@ -42,9 +42,9 @@ public interface Machine {
Memory getRam();
Acia getAcia();
Pia getPia();
Crtc getCrtc();
Memory getRom();
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 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
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 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
@@ -91,7 +91,7 @@ public class SimpleMachine implements Machine {
}
@Override
public void setRom(Memory rom) throws MemoryRangeException {
public void setRom(Memory rom) {
// No-op
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 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
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -30,10 +30,7 @@ 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;
/**
@@ -46,8 +43,8 @@ public class BreakpointsWindow extends JFrame {
private static final Dimension FRAME_SIZE = new Dimension(240, 280);
private static final String EMPTY_STRING = "";
private JFrame mainWindow;
private Breakpoints breakpoints;
private final JFrame mainWindow;
private final Breakpoints breakpoints;
public BreakpointsWindow(Breakpoints breakpoints,
JFrame mainWindow) {
@@ -73,18 +70,8 @@ public class BreakpointsWindow extends JFrame {
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);
}
}
});
breakpointsTable.getSelectionModel().addListSelectionListener(e -> removeButton.setEnabled(e.getFirstIndex() > -1));
JScrollPane scrollPane = new JScrollPane(breakpointsTable);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
@@ -92,45 +79,33 @@ public class BreakpointsWindow extends JFrame {
breakpointsPanel.add(scrollPane, BorderLayout.CENTER);
ActionListener addBreakpointListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int value;
ActionListener addBreakpointListener = e -> {
int value;
String newBreakpoint = addTextField.getText();
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);
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;
}
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());
}
});
removeButton.addActionListener(e -> breakpoints.removeBreakpointAtIndex(breakpointsTable.getSelectedRow()));
controlPanel.add(addTextField);
controlPanel.add(addButton);
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -51,8 +51,8 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
private static final boolean SWAP_CR_AND_LF = true;
// If true, send CRLF (0x0d 0x0a) whenever CR is typed
private boolean sendCrForLf;
private FifoRingBuffer<Character> typeAheadBuffer;
private final boolean sendCrForLf;
private final FifoRingBuffer<Character> typeAheadBuffer;
public Console(int columns, int rows, Font font, boolean sendCrForLf) {
super(new Vt100TerminalModel(columns, rows), font);
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -49,7 +49,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
private static final Logger logger = LoggerFactory.getLogger(MemoryWindow.class);
private MemoryTableModel memoryTableModel;
private final MemoryTableModel memoryTableModel;
private JTable memoryTable;
private JTextField pageNumberTextField;
private JButton previousPageButton;
@@ -111,7 +111,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
}
/**
* Set-up the UI.
* Set up the UI.
*/
private void createUi() {
setTitle("Memory Contents");
@@ -230,7 +230,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
* A JTable that will automatically select all text in a cell
* being edited.
*/
private class MemoryTable extends JTable {
private static class MemoryTable extends JTable {
public MemoryTable(TableModel tableModel) {
super(tableModel);
@@ -242,7 +242,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
final Component editor = getEditorComponent();
if (editor != null && editor instanceof JTextComponent) {
if (editor instanceof JTextComponent) {
((JTextComponent) editor).selectAll();
}
@@ -250,7 +250,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
}
}
private class MemoryTableCellRenderer extends DefaultTableCellRenderer {
private static class MemoryTableCellRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
@@ -272,9 +272,9 @@ public class MemoryWindow extends JFrame implements ActionListener {
/**
* The model that backs the Memory Table.
*/
private class MemoryTableModel extends AbstractTableModel {
private static class MemoryTableModel extends AbstractTableModel {
private Bus bus;
private final Bus bus;
private int pageNumber;
private static final int COLUMN_COUNT = 17;
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -42,11 +42,11 @@ public class PreferencesDialog extends Observable implements Preferences {
private JTextField programLoadAddressField;
private int programLoadAddress = DEFAULT_PROGRAM_LOAD_ADDRESS;
private boolean haltOnBreak = DEFAULT_HALT_ON_BREAK;
private boolean haltOnBreak;
public PreferencesDialog(Frame parent, boolean modal) {
public PreferencesDialog(Frame parent, boolean modal, boolean haltOnBreak) {
this.dialog = new JDialog(parent, modal);
this.haltOnBreak = haltOnBreak;
createUi();
updateUi();
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -86,7 +86,7 @@ public class StatusPanel extends JPanel {
private void createUi() {
Border emptyBorder = BorderFactory.createEmptyBorder(EMPTY_BORDER, EMPTY_BORDER,
EMPTY_BORDER, EMPTY_BORDER);
EMPTY_BORDER, EMPTY_BORDER);
Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
setBorder(BorderFactory.createCompoundBorder(emptyBorder, etchedBorder));
@@ -125,16 +125,15 @@ public class StatusPanel extends JPanel {
// Add tool-tip text
carryFlagLabel.setToolTipText("Carry: The last operation caused an overflow " +
"from bit 7 of the result or an underflow from bit 0");
"from bit 7 of the result or an underflow from bit 0");
zeroFlagLabel.setToolTipText("Zero: The result of the last operation was 0");
irqDisableFlagLabel.setToolTipText("Interrupt Disable: Processor will not respond to IRQ");
decimalModeFlagLabel.setToolTipText("Decimal Mode");
breakFlagLabel.setToolTipText("Break: BRK instruction occurred");
overflowFlagLabel.setToolTipText("Overflow: The result of the last operation was " +
"an invalid 2's complement result");
"an invalid 2's complement result");
negativeFlagLabel.setToolTipText("Negative: The result of the last operation set bit 7");
statusFlagsPanel.add(negativeFlagLabel);
statusFlagsPanel.add(overflowFlagLabel);
statusFlagsPanel.add(breakFlagLabel);
@@ -243,7 +242,6 @@ public class StatusPanel extends JPanel {
constraints.gridy = 1;
add(statusFlagsPanel, constraints);
constraints.insets = new Insets(5, 0, 0, 0);
constraints.gridy = 2;
add(opcodeLabel, constraints);
@@ -392,13 +390,6 @@ public class StatusPanel extends JPanel {
textField.setMinimumSize(size);
textField.setMaximumSize(size);
textField.setPreferredSize(size);
textField.setBackground(Color.WHITE);
// Although we usually defer to the system look-and-feel, for
// these small text fields in particular, we use a Metal
// look-and-feel because native look-and-feel breaks very small
// text fields under GTK+ (they are drawn with an inner margin
// even if the margin is set to 0)
textField.setUI(new MetalTextFieldUI());
return textField;
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 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
+124
View File
@@ -0,0 +1,124 @@
/*
* Copyright (c) 2008-2025 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 javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.formdev.flatlaf.FlatDarkLaf;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.util.SystemInfo;
import com.jthemedetecor.OsThemeDetector;
import com.loomcom.symon.machines.BenEaterMachine;
import com.loomcom.symon.machines.MulticompMachine;
import com.loomcom.symon.machines.SimpleMachine;
import com.loomcom.symon.machines.SymonMachine;
public final class Ui {
private final static Logger logger = LoggerFactory.getLogger(Ui.class.getName());
public static void initUi() throws UnsupportedLookAndFeelException {
// Location of properties resources for app-specific UI defaults:
FlatLaf.registerCustomDefaultsSource("com.loomcom.symon.themes");
// Enable automatic scaling, based on system DPI settings:
System.setProperty("sun.java2d.uiScale.enabled", "true");
System.setProperty("sun.java2d.uiScale", "1x");
System.setProperty("flatlaf.uiScale", "1x");
// OS-specific tweaking:
if (SystemInfo.isLinux) {
logger.debug("Adapting UI for Linux.");
// Enable custom window decorations:
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
}
else if (SystemInfo.isMacOS) {
logger.debug("Adapting UI for MacOS.");
// Move menu bar from JFrame window to top of screen:
System.setProperty("apple.laf.useScreenMenuBar", "true");
// Set name used in screen menu bar (first menu after Apple menu):
System.setProperty("apple.awt.application.name", "Symon");
// Use current macOS appearance (light/dark) for window title bars:
System.setProperty("apple.awt.application.appearance", "system");
}
else if (SystemInfo.isWindows) {
logger.debug("Adapting UI for Windows.");
}
UIManager.setLookAndFeel(createLookAndFeel());
}
public static Class<?> showMachineSelectionDialog() {
Object[] possibilities = { "Symon", "Multicomp", "Simple", "BenEater" };
String s = (String) JOptionPane.showInputDialog(
null,
"Please choose the machine type to be emulated:",
"Machine selection",
JOptionPane.PLAIN_MESSAGE,
null,
possibilities,
"Symon");
if (s != null && s.equals("Multicomp")) {
return MulticompMachine.class;
} else if (s != null && s.equals("Simple")) {
return SimpleMachine.class;
} else if (s != null && s.equals("BenEater")) {
return BenEaterMachine.class;
} else {
return SymonMachine.class;
}
}
private static LookAndFeel createLookAndFeel() {
final OsThemeDetector themeDetector = OsThemeDetector.getDetector();
final boolean systemThemeIsDark = themeDetector.isDark();
// Set light/dark mode depending on what can be detected from the system theme:
FlatLaf theme;
if (systemThemeIsDark) {
logger.debug("Using DARK theme.");
theme = new FlatDarkLaf();
} else {
logger.debug("Using LIGHT theme.");
theme = new FlatLightLaf();
}
return theme;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
@@ -61,14 +61,16 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private static final Logger logger = Logger.getLogger(VideoWindow.class.getName());
private static final long WINDOW_REPAINT_INTERVAL = 66; // 30fps rate
private static final int CHAR_WIDTH = 8;
private static final int CHAR_HEIGHT = 8;
private static final String ASCII_ROM = "/ascii.rom";
private final int scaleX, scaleY;
private final boolean shouldScale;
private BufferedImage image;
private int[] charRom;
private final int[] charRom;
private int horizontalDisplayed;
private int verticalDisplayed;
@@ -77,9 +79,9 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private boolean hideCursor;
private Dimension dimensions;
private Crtc crtc;
private final Crtc crtc;
private ScheduledExecutorService scheduler;
private final ScheduledExecutorService scheduler;
private ScheduledFuture<?> cursorBlinker;
/**
@@ -122,13 +124,19 @@ 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;
VideoWindow.this.repaint();
}
SwingUtilities.invokeLater(() -> {
if (cursorBlinkRate > 0) {
hideCursor = !hideCursor;
}
});
}
}
private class WindowPainter implements Runnable {
public void run() {
SwingUtilities.invokeLater(() -> {
if (VideoWindow.this.isVisible()) {
VideoWindow.this.repaint();
}
});
}
@@ -139,7 +147,7 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
this.scheduler = Executors.newSingleThreadScheduledExecutor();
this.crtc = crtc;
this.charRom = loadCharRom("/ascii.rom");
this.charRom = loadCharRom();
this.scaleX = scaleX;
this.scaleY = scaleY;
this.shouldScale = (scaleX > 1 || scaleY > 1);
@@ -152,6 +160,11 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
TimeUnit.MILLISECONDS);
}
scheduler.scheduleAtFixedRate(new WindowPainter(),
WINDOW_REPAINT_INTERVAL,
WINDOW_REPAINT_INTERVAL,
TimeUnit.MILLISECONDS);
// Capture some state from the CRTC that will define the
// window size. When these values change, the window will
// need to re-pack and redraw.
@@ -274,14 +287,17 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
* Since the BufferedImage is a TYPE_BYTE_BINARY, the data must be converted
* into a single byte per pixel, 0 for black and 255 for white.
* @param resource The ROM file resource to load.
* @return An array of glyphs, each ready for insertion.
* @throws IOException
* @throws IOException Failure to load character ROM.
*/
private int[] loadCharRom(String resource) throws IOException {
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(this.getClass().getResourceAsStream(resource));
private int[] loadCharRom() throws IOException {
var resource = this.getClass().getResourceAsStream(ASCII_ROM);
if (resource == null) {
throw new IOException("Resource " + ASCII_ROM + " not found");
}
try (BufferedInputStream bis = new BufferedInputStream(resource)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (bis.available() > 0) {
bos.write(bis.read());
@@ -296,7 +312,7 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
int[] converted = new int[raw.length * CHAR_WIDTH];
int romIndex = 0;
for (int i = 0; i < converted.length;) {
for (int i = 0; i < converted.length; ) {
byte charRow = raw[romIndex++];
for (int j = 7; j >= 0; j--) {
@@ -304,10 +320,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
}
}
return converted;
} finally {
if (bis != null) {
bis.close();
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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,8 +34,8 @@ import java.util.Queue;
*/
public class FifoRingBuffer<E> implements Iterable<E> {
private Queue<E> fifoBuffer;
private int maxLength;
private final Queue<E> fifoBuffer;
private final int maxLength;
public FifoRingBuffer(int maxLength) {
this.fifoBuffer = new LinkedList<>();
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 238 B

@@ -0,0 +1,3 @@
# Loaded for FlatDarkLaf theme only.
Colors.accent.default = #4B6EAF
@@ -0,0 +1 @@
# Loaded for all FlatLaf themes.
@@ -0,0 +1,3 @@
# Loaded for FlatLightLaf theme only.
Colors.accent.default = #2675BF
@@ -254,4 +254,76 @@ public class AciaTest {
assertEquals(0x08, acia.read(0x0001, true));
}
@Test
public void statusRegisterInitializedAtHardwareReset() throws Exception {
Acia6551 acia = new Acia6551(0x0000);
assertEquals(0x10, acia.read(0x0001, false));
}
@Test
public void commandRegisterInitializedAtHardwareReset() throws Exception {
Acia6551 acia = new Acia6551(0x0000);
assertEquals(0x02, acia.read(0x0002, false));
}
@Test
public void controlRegisterInitializedAtHardwareReset() throws Exception {
Acia6551 acia = new Acia6551(0x0000);
assertEquals(0x00, acia.read(0x0003, false));
}
@Test
public void programResetClearsOverrunStatus() throws Exception {
Acia6551 acia = new Acia6551(0x0000);
Bus bus = new Bus(acia.ACIA_SIZE);
acia.setBus(bus);
// Change as many status bits as we can.
acia.write(0x0002, 0x00); // enable receive interrupt
acia.rxWrite('a');
acia.rxWrite('b'); // overrun, receive full, interrupt signalled
acia.write(0x0000, 'c'); // Transmitter Data Register not empty
// Check that all the bits we expected to be set actually are
assertEquals(0x8C, acia.read(0x0001, false));
// Do a "program reset". The value is ignored.
acia.write(0x0001, 0xFF);
// Check that only bit 2 was cleared.
assertEquals(0x88, acia.read(0x0001, false));
}
@Test
public void programResetKeepsParitySettings() throws Exception {
Acia6551 acia = new Acia6551(0x0000);
// Set all the command register bits
acia.write(0x0002, 0xFF);
// Do a "program reset". The value is ignored.
acia.write(0x0001, 0xFF);
// The top 3 bits should be kept as-is,
// the bottom 5 bits should be reset to defaults.
assertEquals(0xE2, acia.read(0x0002, false));
}
@Test
public void programResetLeavesControlRegisterUnchanged() throws Exception {
Acia6551 acia = new Acia6551(0x0000);
// Set all the control register bits
acia.write(0x0003, 0xFF);
// Do a "program reset". The value is ignored.
acia.write(0x0001, 0xFF);
// No bits should have changed.
assertEquals(0xFF, acia.read(0x0003, false));
}
}
@@ -29,7 +29,7 @@ public class CpuAbsoluteModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -169,7 +169,7 @@ public class CpuAbsoluteModeTest extends TestCase {
assertEquals(0x04, bus.read(0x1fe, true));
// No flags should have changed.
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/* BIT - Bit Test - $2c */
@@ -362,7 +362,7 @@ public class CpuAbsoluteModeTest extends TestCase {
cpu.step();
assertEquals(0x3400, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/* EOR - Exclusive OR - $4d */
@@ -29,7 +29,7 @@ public class CpuAbsoluteXModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -29,7 +29,7 @@ public class CpuAbsoluteYModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -30,7 +30,7 @@ public class CpuAccumulatorModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -306,4 +306,4 @@ public class CpuAccumulatorModeTest extends TestCase {
assertFalse(cpu.getCarryFlag());
}
}
}
@@ -29,7 +29,7 @@ public class CpuImmediateModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -33,7 +33,7 @@ public class CpuImpliedModeTest {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -76,6 +76,7 @@ public class CpuImpliedModeTest {
public void test_BRK() throws MemoryAccessException {
cpu.setCarryFlag();
cpu.setOverflowFlag();
cpu.clearIrqDisableFlag();
assertEquals(0x20 | Cpu.P_CARRY | Cpu.P_OVERFLOW,
cpu.getProcessorStatus());
assertEquals(0x00, cpu.stackPeek());
@@ -343,7 +344,7 @@ public class CpuImpliedModeTest {
assertEquals(0, cpu.getYRegister());
assertEquals(0x201, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/* PHA - Push Accumulator - $48 */
@@ -432,7 +433,7 @@ public class CpuImpliedModeTest {
cpu.step();
assertEquals(0x0f12, cpu.getProgramCounter());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/* SEC - Set Carry Flag - $38 */
@@ -641,4 +642,4 @@ public class CpuImpliedModeTest {
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
}
}
}
@@ -31,7 +31,7 @@ public class CpuIndexedIndirectModeTest {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
@Test
@@ -105,4 +105,4 @@ public class CpuIndexedIndirectModeTest {
assertEquals(0x11, cpu.getAccumulator());
assertEquals(0x31, bus.read(0xc51f, true));
}
}
}
@@ -31,7 +31,7 @@ public class CpuIndirectIndexedModeTest {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
@Test
@@ -81,4 +81,4 @@ public class CpuIndirectIndexedModeTest {
assertEquals(0xe3, bus.read(0xd828, true));
}
}
}
@@ -29,7 +29,7 @@ public class CpuIndirectModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -48,7 +48,7 @@ public class CpuIndirectModeTest extends TestCase {
cpu.step();
assertEquals(0x5400, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
public void test_JMP_with_ROR_Bug() throws MemoryAccessException {
@@ -60,7 +60,7 @@ public class CpuIndirectModeTest extends TestCase {
cpu.step();
assertEquals(0x2200, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
public void test_JMP_withIndirectBug() throws MemoryAccessException {
@@ -72,7 +72,7 @@ public class CpuIndirectModeTest extends TestCase {
cpu.step();
assertEquals(0x2200, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
public void test_JMP_withOutIndirectBug() throws MemoryAccessException {
@@ -84,7 +84,7 @@ public class CpuIndirectModeTest extends TestCase {
cpu.step();
assertEquals(0x5400, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
public void test_JMP_cmos() throws MemoryAccessException {
@@ -96,7 +96,7 @@ public class CpuIndirectModeTest extends TestCase {
cpu.step();
assertEquals(0x5400, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
}
}
@@ -28,7 +28,7 @@ public class CpuIndirectXModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -29,7 +29,7 @@ public class CpuRelativeModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -278,4 +278,4 @@ public class CpuRelativeModeTest extends TestCase {
assertEquals(0x202, cpu.getProgramCounter());
}
}
}
+83 -8
View File
@@ -44,7 +44,7 @@ public class CpuTest extends TestCase {
assertEquals(0x0200, cpu.getProgramCounter());
assertFalse(cpu.getCarryFlag());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getIrqDisableFlag());
assertTrue(cpu.getIrqDisableFlag());
assertFalse(cpu.getDecimalModeFlag());
assertFalse(cpu.getBreakFlag());
assertFalse(cpu.getOverflowFlag());
@@ -205,14 +205,12 @@ public class CpuTest extends TestCase {
}
public void testGetProcessorStatus() {
// By default, no flags are set. Remember, bit 5
// By default, only "interrupt disable" is set. Remember, bit 5
// is always '1'.
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
cpu.setCarryFlag();
assertEquals(0x21, cpu.getProcessorStatus());
assertEquals(0x25, cpu.getProcessorStatus());
cpu.setZeroFlag();
assertEquals(0x23, cpu.getProcessorStatus());
cpu.setIrqDisableFlag();
assertEquals(0x27, cpu.getProcessorStatus());
cpu.setDecimalModeFlag();
assertEquals(0x2f, cpu.getProcessorStatus());
@@ -237,13 +235,16 @@ public class CpuTest extends TestCase {
assertEquals(0xa0, cpu.getProcessorStatus());
cpu.clearNegativeFlag();
assertEquals(0x20, cpu.getProcessorStatus());
cpu.setIrqDisableFlag();
assertEquals(0x24, cpu.getProcessorStatus());
}
public void testSetProcessorStatus() {
// Default
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getIrqDisableFlag());
assertTrue(cpu.getIrqDisableFlag());
assertFalse(cpu.getDecimalModeFlag());
assertFalse(cpu.getBreakFlag());
assertFalse(cpu.getOverflowFlag());
@@ -669,4 +670,78 @@ public class CpuTest extends TestCase {
cpu.step();
assertEquals(0x3E, cpu.getAccumulator());
}
}
// Have tests:
// ACC, AIX, ABS, ABX, ABY, IMP, IMM, IND, XIN, INY,
// REL, ZPG, ZPI
// Need to test:
// ZPR, ZPX, ZPY, NUL
// Question: Should 'NUL' even exist? Isn't it just 'IMP'?
public void testDisassembleOpHandlesAccumulatorCorrectly() {
var dis = Cpu.disassembleOp(0x2a, new int[]{});
assertEquals("ROL", dis);
}
public void testDisassembleOpHandlesAbsoluteXIndexedIndirectCorrectly() {
var dis = Cpu.disassembleOp(0x7c, new int[]{0x00, 0x1a});
assertEquals("JMP ($1A00,X)", dis);
}
public void testDisassembleOpHandlesAbsoluteCorrectly() {
var dis = Cpu.disassembleOp(0x0d, new int[]{0x10, 0x0c});
assertEquals("ORA $0C10", dis);
}
public void testDisassembleOpHandlesAbsoluteXIndexedCorrectly() {
var dis = Cpu.disassembleOp(0x1d, new int[]{0x00, 0x1c});
assertEquals("ORA $1C00,X", dis);
}
public void testDisassembleOpHandlesAbsoluteYIndexedCorrectly() {
var dis = Cpu.disassembleOp(0x19, new int[]{0x00, 0x1c});
assertEquals("ORA $1C00,Y", dis);
}
public void testDisassembleOpHandlesImpliedCorrectly() {
var dis = Cpu.disassembleOp(0x00, new int[]{});
assertEquals("BRK", dis);
}
public void testDisassembleOpHandlesIndirectCorrectly() {
var dis = Cpu.disassembleOp(0x6c, new int[]{0x00, 0x1c});
assertEquals("JMP ($1C00)", dis);
}
public void testDisassembleOpHandlesImmediateCorrectly() {
var dis = Cpu.disassembleOp(0xA9, new int[]{0xff});
assertEquals("LDA #$FF", dis);
}
public void testDisassembleOpHandlesXIndexedIndirectCorrectly() {
var dis = Cpu.disassembleOp(0x01, new int[]{0x10});
assertEquals("ORA ($10,X)", dis);
}
public void testDisassembleOpHandlesIndirectYIndexedCorrectly() {
var dis = Cpu.disassembleOp(0x11, new int[]{0x10});
assertEquals("ORA ($10),Y", dis);
}
public void testDisassembleOpHandlesRelativeCorrectly() {
var dis = Cpu.disassembleOp(0x10, new int[]{0xff});
assertEquals("BPL $FF", dis);
}
public void testDisassembleOpHandlesZeroPageCorrectly() {
var dis = Cpu.disassembleOp(0x24, new int[]{0x10});
assertEquals("BIT $10", dis);
}
public void testDisassembleOpHandlesZeroPageIndirectCorrectly() throws Exception {
var dis = Cpu.disassembleOp(0xB2, new int[]{0x1a});
assertEquals("LDA ($1A)", dis);
}
}
@@ -29,7 +29,7 @@ public class CpuZeroPageModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -29,7 +29,7 @@ public class CpuZeroPageXModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -29,7 +29,7 @@ public class CpuZeroPageYModeTest extends TestCase {
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/*
@@ -8,7 +8,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;