Compare commits

...
88 Commits
Author SHA1 Message Date
RuiFPB 3ee3a7e300 Add release action
Build and Test / build (push) Successful in 1m5s
Release / release (push) Successful in 1m6s
2026-09-29 13:44:53 +01:00
RuiFPB b2810fa062 Add missing mike42 machine flag to run options
Build and Test / build (push) Successful in 1m3s
2026-09-29 12:15:37 +01:00
RuiFPB c9f9121477 Add install maven step (gitea actions
Build and Test / build (push) Successful in 1m2s
2026-09-29 01:05:01 +01:00
RuiFPB 91ef70fe4e Disable maven cache on jdk setup (gitea actions)
Build and Test / build (push) Failing after 22s
2026-09-29 01:02:24 +01:00
RuiFPB 69bf418f43 Set container image to use to run the pipeline
Build and Test / build (push) Canceled after 10m40s
2026-09-29 00:51:56 +01:00
RuiFPB 540f0dff58 Add gitea action build pipeline 2026-09-29 00:46:42 +01:00
RuiFPB 17668e2c3c Only run X11 resource reader if on Linux 2026-09-28 10:24:02 +01:00
RuiFPB 3353b8436c Implement UI for the VIA/PIA 2026-09-26 19:47:15 +01:00
RuiFPB e635d0a608 Add binary converter to Utils 2026-09-26 19:46:54 +01:00
RuiFPB 558af36cd5 Start implementation of the VIA6522
Only PA and PB implemented, not tested
2026-09-26 19:14:20 +01:00
RuiFPB 5fa6fa0fc0 Clean up imports on the Mike42 machine 2026-09-26 19:13:24 +01:00
RuiFPB f152ed9df5 Fix scaling issues on Linux 2026-09-25 00:38:28 +01:00
RuiFPB 87217afa6e Add Mike42 Machine Type 2026-09-25 00:37:52 +01:00
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
Seth Morabito 5f8f743f7d Update for 1.3.2 2022-03-08 14:36:59 -08:00
Seth Morabito 4ce8bc86de Update pom.xml
- Switched from maven-assembly-plugin to maven-shade-plugin
  for final packaging
- Updated maven-jar-plugin to 3.2.0
2021-09-25 10:46:36 -07:00
Seth Morabito 4644fe9b55 Update junit and mockito
- JUnit has been bumped to 4.13.2.
- Mockito has gone from 1.10.19 to 3.12.4, and now compiles correctly
  on OpenJDK 16 using Maven 3.8.
2021-09-24 09:39:00 -07:00
Seth Morabito 655bbb3ab0 Merge pull request #26 from sethm/dependabot/maven/junit-junit-4.13.1
Bump junit from 4.12 to 4.13.1
2020-10-13 14:45:47 -07:00
dependabot[bot] e2d31fd39c Bump junit from 4.12 to 4.13.1
Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 09:09:28 +00:00
Seth Morabito 087ba28b82 Force metal look-and-feel
* On Linux GTK+, the small text fields in the Status Panel displayed a
  large inner margin, cutting off text. This change forces Java to use the
  Metal look-and-feel for these fields, which forces no default inner
  margin.
2020-09-01 10:48:17 -07:00
Seth Morabito 8edca5d595 Update link in README 2020-05-21 15:01:34 -07:00
Seth Morabito 105a09067f Fix README for 1.3.1 2019-10-12 14:58:16 -07:00
Seth Morabito 08efd81be3 Merge branch 'master' into 1.3.branch 2019-10-12 14:55:43 -07:00
Seth Morabito 0aeb97bb56 Add new options -cpu and -rom 2019-10-12 14:54:05 -07:00
Seth Morabito d466a21b3e Fix README screenshots (again) 2018-07-09 14:00:32 -07:00
Seth Morabito 31d1f7c5e1 Update screenshots 2018-03-01 19:21:56 -08:00
Seth Morabito 247f2ba2fc Update screenshots 2018-03-01 19:21:21 -08:00
Seth Morabito 922810b34b Update POM for 1.3.0 2018-02-25 11:46:02 -08:00
Seth Morabito 9d6791330d Update README 2018-02-25 11:45:16 -08:00
Seth Morabito 73c474d606 POM cleanup 2018-02-04 11:49:26 -08:00
Chris Cureau 71905fdb19 Merge pull request #23 from ccureau/master
Integrate jterminal
2017-09-12 18:23:55 -05:00
ccureau cb918c9906 Remove unnecessary package-info files 2017-08-30 10:40:45 -05:00
ccureau be72c2ff09 implement jterminal in source 2017-08-30 10:34:50 -05:00
ccureau 18ce120984 Add jterminal to source 2017-08-30 09:23:59 -05:00
Seth Morabito 8ffb130300 Merge pull request #21 from LIV2/master
Fix PHY/PLY Addressing mode
2017-08-23 10:47:12 -07:00
Matt Harlum 441f7349a0 Fix PHY/PLY Addressing mode 2017-06-19 11:49:46 +10:00
Seth Morabito 4f27e78940 Merge pull request #20 from LIV2/master
Add cycle counts for illegal NMOS opcodes
2017-06-06 19:47:24 -07:00
Matt Harlum 11f61f50a9 Add cycle counts for illegal NMOS opcodes 2017-06-07 12:20:21 +10:00
Seth Morabito 59f8ff1bc4 Merge pull request #19 from LIV2/master
Adds support for all 65C02 Opcodes
2017-06-06 07:10:02 -07:00
Matt Harlum a9c6d5964f * Add Support for All 65C02 Opcodes and all Rockwell/WDC opcodes except WAI/STP
* Add 65C02 Opcode tests
* All tests pass, Klaus' 6502_functional_tests pass & Klaus' 65C02_extended_opcodes_test also all pass
2017-06-06 19:59:01 +10:00
Matt Harlum faf5d22660 * Add ACIA Interrupt tests.
* Fix ACIA6850 Interrupt behavior, Interrupt should be cleared on status register read.
* Remove unneeded cpuAccess if statement from Acia6850 write that was preventing build completion
* Fix ACIA6850 Tests so they run.
2017-06-06 12:55:35 +10:00
Seth Morabito 3c3aee30a7 Merge pull request #18 from izuannazrin/master
Interrupt bit support for ACIA
2017-06-04 18:53:44 -07:00
Izuan Nazrin 92f8fe3dd9 Fixed 6850 behaviour
+ Fixed interrupt bit reset behaviour
2017-05-30 01:33:12 +08:00
Izuan Nazrin 356822df71 Fixed typo
+ Fixed typo in Acia.java statusReg declaration
2017-05-30 01:14:50 +08:00
Izuan Nazrin f2ced29979 Added interrupt support for ACIA
+ Added interrupt support for ACIA
+ Auto-formatting (in case anyone cares)
2017-05-29 02:03:18 +08:00
Seth Morabito 2e33756d49 Merge pull request #17 from LIV2/master
Memory window should not reset device registers
2017-05-26 09:38:28 -07:00
Matt Harlum 079d3dbeae Fix README links 2017-05-26 17:01:48 +10:00
Matt Harlum 96819f1bf7 Issue #16: Memory window should not reset device registers 2017-05-26 17:01:41 +10:00
Seth Morabito 9cdd718e8d Fix Screenshots part two 2017-03-20 07:49:32 -07:00
Seth Morabito 5554acd29c Fix screenshots 2017-03-20 07:39:49 -07:00
Seth Morabito 5a2e057e69 Minor whitespace cleanup 2016-06-11 11:46:31 -07:00
Seth Morabito da88aadda2 Merge pull request #15 from LIV2/master
Correct BRK/IRQ behavior
2016-03-20 11:43:42 -07:00
Matt Harlum 657b69da6c Cleanup my comments 2016-03-20 17:05:48 +11:00
Chris Cureau f251e54174 Merge pull request #13 from sethm/cc65_update_patches
update Makefiles and configuration for latest cc65
2016-03-14 20:01:12 -05:00
Chris Cureau fcc57d9be4 update Makefiles and configuration for latest cc65 2016-01-12 08:14:38 -06:00
Seth Morabito 88eba2cdcb Merge branch '1.2.branch' 2016-01-08 20:53:54 -08:00
Seth Morabito b59fa63b63 Merge branch '1.2.branch' 2016-01-08 20:41:25 -08:00
Seth Morabito c599df1cfb Set version to 1.3.0-SNAPSHOT 2016-01-08 19:14:00 -08:00
Matt Harlum 8335cf5421 Correct BRK behaviour
IRQ/NMI clear the BRK flag
BRK is Non-Maskable
2015-06-01 08:58:17 +10:00
105 changed files with 7049 additions and 1258 deletions

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
name: Build and Test
on:
push:
branches:
- master
pull_request:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup JDK
uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
#cache: maven
- name: Install Maven
run: apt-get update && apt-get install -y maven
- name: Test and package
run: mvn -B verify
- name: Upload jar
uses: actions/upload-artifact@v3
with:
name: package
path: target/*.jar
+33
View File
@@ -0,0 +1,33 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup JDK
uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
- name: Install Maven
run: apt-get update && apt-get install -y maven
- name: Package
run: mvn -B verify
- name: Create release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
files: |-
target/symon*.jar
+2
View File
@@ -1,6 +1,8 @@
*~
*.jar
*#
target
.DS_Store
.idea
symon.iml
/dependency-reduced-pom.xml
@@ -0,0 +1,5 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding//src/test/java=UTF-8
encoding/<project>=UTF-8
+5
View File
@@ -0,0 +1,5 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.7
+4
View File
@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
+135 -52
View File
@@ -1,13 +1,13 @@
SYMON - A 6502 System Simulator
kSYMON - A 6502 System Simulator
===============================
**Version:** 1.2.1
**Version:** 1.5.1k
**Last Updated:** 8 January, 2016
**Last Updated:** 24 September, 2026
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
@@ -16,29 +16,29 @@ Technologies 6502 microprocessor and compatibles. Symon is implemented
in Java. Its core goals are accuracy, ease of development, clear
documentation, and extensive test suites for validating correctness.
Symon simulates a complete system with a 1 MHz NMOS 6502, 32KB of RAM,
16KB of ROM, a MOS 6551 or Motorola 6850 ACIA, a MOS 6522 VIA, and an
experimental 6545 CRTC.
Symon simulates a complete system with a 1 MHz NMOS 6502 or CMOS
65C02, 32KB of RAM, 16KB of ROM, a MOS 6551 or Motorola 6850 ACIA, a
MOS 6522 VIA, and an experimental 6545 CRTC.
Symon has extensive unit tests to verify correctness, and fully passes
Klaus Dormann's 6502 Functional Test Suite as of version 0.8.2
(See [this thread on the 6502.org Forums] (http://forum.6502.org/viewtopic.php?f=2&t=2241)
(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,56 @@ 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.1.5 Mike42 Memory Map
- `$0000`--`$7FFF`: 32KB RAM
- `$8000`--`$800F`: 6522 VIA
- `$8400`--`$8403`: MOS 6551 ACIA (Serial Console)
- `$C000`--`$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 +124,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
@@ -154,13 +183,13 @@ between the simulated 6545 and a real 6545:
of 6545 programming tricks that were achieved by updating the
frame address during vertical and horizontal sync times are not
achievable. There is no way (for example) to change the Display Start
Address (R12 and R13) while a frame is being drawn.
Address (R12 and R13) while a frame is being drawn.
For more information on the 6545 CRTC and its programming model, please see the following resources
- [CRTC 6545/6845 Information (André Fachat)] (http://6502.org/users/andre/hwinfo/crtc/index.html)
- [CRTC Operation (André Fachat)] (http://www.6502.org/users/andre/hwinfo/crtc/crtc.html)
- [MOS 6545 Datasheet (PDF)] (http://www.6502.org/users/andre/hwinfo/crtc/crtc.html)
- [CRTC 6545/6845 Information (André Fachat)](http://6502.org/users/andre/hwinfo/crtc/index.html)
- [CRTC Operation (André Fachat)](http://www.6502.org/users/andre/hwinfo/crtc/crtc.html)
- [MOS 6545 Datasheet (PDF)](http://www.6502.org/users/andre/hwinfo/crtc/crtc.html)
#### 3.8.1 Example BASIC Program to test Video
@@ -193,17 +222,41 @@ Java 1.8 or greater, just type:
When Symon is running, you should be presented with a simple graphical
interface.
### 4.2 ROM images
#### 4.1.1 Command Line Options
Two command line options may be passed to the JAR file on startup,
to specify machine type and CPU type. The options are:
- `-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 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
@@ -212,7 +265,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.
@@ -228,23 +281,50 @@ 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.
- **1.3.0:** 24 February, 2018 - Adds support for 65C02 opcodes.
- **1.2.1:** 8 January, 2016 - Remove dependency on Java 8. Now
supports compiling and running under Java 1.7.
- **1.2.0:** 3 January, 2016 - Add symbolic disassembly to breakpoints
window.
- **1.1.1:** 2 January, 2016 - Minor enhancement: Allows breakpoints
to be added with the Enter key.
- **1.1.0:** 31 December, 2015 - Fixed delay loop to better
- **1.1.0:** 31 December, 2015 - Fixed delay loop to better
simulate various clock speeds. Added ability to select clock
speed at runtime. Status display now shows the next instruction
to be executed, instead of the last instruction executed.
@@ -304,17 +384,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.
@@ -324,11 +399,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.
@@ -338,10 +421,10 @@ Additional components used in this project are copyright their respective owners
This project would not have been possible without the following resources:
- [Andrew Jacobs' 6502 Pages] (http://www.obelisk.demon.co.uk/6502/), for
- [Andrew Jacobs' 6502 Pages](http://obelisk.me.uk/6502/), for
wonderfully detailed information about the 6502
- [Neil Parker's "The 6502/65C02/65C816 Instruction Set Decoded"] (http://www.llx.com/~nparker/a2/opcodes.html),
- [Neil Parker's "The 6502/65C02/65C816 Instruction Set Decoded"](http://www.llx.com/~nparker/a2/opcodes.html),
for information about how instructions are coded
## 9.0 Licensing
+55 -44
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.2.1</version>
<version>1.5.1k</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
@@ -17,70 +18,80 @@
</properties>
<repositories>
<repository>
<id>jline</id>
<name>JLine Project Repository</name>
<url>http://jline.sourceforge.net/m2repo</url>
</repository>
<!-- Loomcom's Maven2 repository for JTerminal -->
<repository>
<releases>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</releases>
<id>com.loomcom</id>
<name>Loom Communications Maven2 Repository</name>
<url>http://www.loomcom.com/maven2</url>
<layout>default</layout>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.3</version>
<version>1.5.34</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>
<artifactId>junit</artifactId>
<version>4.11</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.grahamedgecombe.jterminal</groupId>
<artifactId>jterminal</artifactId>
<version>1.0.2.3-loomcom</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.14.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.loomcom.symon.Main</mainClass>
</manifest>
</archive>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
<goal>shade</goal>
</goals>
</execution>
</executions>
@@ -88,11 +99,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<version>3.7.0</version>
<configuration>
<compilerArgument>-Xlint:unchecked</compilerArgument>
<source>1.7</source>
<target>1.7</target>
<source>16</source>
<target>16</target>
</configuration>
</plugin>
@@ -100,7 +111,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
@@ -132,4 +143,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 -1
View File
@@ -7,7 +7,7 @@ echo_irq: echo_irq.o
$(LD) -C symon.config -vm -m echo_irq.map -o echo_irq.rom echo_irq.o
echo_irq.o:
$(CA) --listing -o echo_irq.o echo_irq.asm
$(CA) --listing echo_irq.lst -o echo_irq.o echo_irq.asm
clean:
rm -f *.o *.rom *.map *.lst
+2 -2
View File
@@ -1,5 +1,5 @@
MEMORY {
RAM1: start = $0000, size = $8000;
RAM1: start = $0000, size = $8000 - __STACKSIZE__;
ROM1: start = $C000, size = $3FFA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
}
@@ -11,6 +11,6 @@ VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__ = $0300;
__STACKSIZE__: type = weak, value = $0300;
}
+1 -1
View File
@@ -7,7 +7,7 @@ ehbasic: ehbasic.o
$(LD) -C symon.config -vm -m ehbasic.map -o ehbasic.rom ehbasic.o
ehbasic.o:
$(CA) --listing -o ehbasic.o min_mon.asm
$(CA) --listing ehbasic.lst -o ehbasic.o min_mon.asm
clean:
rm -f *.o *.rom *.map *.lst
+2 -2
View File
@@ -1,5 +1,5 @@
MEMORY {
RAM1: start = $0000, size = $8000;
RAM1: start = $0000, size = $8000 - __STACKSIZE__;
ROM1: start = $C000, size = $3F00, fill = yes;
MONITOR: start = $FF00, size = $FA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
@@ -13,6 +13,6 @@ VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__ = $0300;
__STACKSIZE__: type = weak, value = $0300;
}
+1 -1
View File
@@ -7,7 +7,7 @@ ehbasic: ehbasic.o
$(LD) -C symon.config -vm -m ehbasic.map -o ehbasic.rom ehbasic.o
ehbasic.o:
$(CA) --listing -o ehbasic.o min_mon.asm
$(CA) --listing ehbasic.lst -o ehbasic.o min_mon.asm
clean:
rm -f *.o *.rom *.map *.lst
+2 -2
View File
@@ -1,5 +1,5 @@
MEMORY {
RAM1: start = $0000, size = $8000;
RAM1: start = $0000, size = $8000 - __STACKSIZE__;
ROM1: start = $C000, size = $3C00, fill = yes;
MONITOR: start = $FC00, size = $3FA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
@@ -13,6 +13,6 @@ VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__ = $0300;
__STACKSIZE__: type = weak, value = $0300;
}
@@ -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;
+39 -45
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
@@ -26,6 +26,7 @@ package com.loomcom.symon;
import com.loomcom.symon.devices.Device;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -41,21 +42,21 @@ 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;
// an array for quick lookup of adresses, brute-force style
private final Map<Integer, SortedSet<Device>> deviceMap;
// an array for quick lookup of addresses, brute-force style
private Device[] deviceAddressArray;
public Bus(int size) {
this(0, size - 1);
@@ -74,30 +75,30 @@ public class Bus {
public int endAddress() {
return endAddress;
}
private void buildDeviceAddressArray() {
int size = (this.endAddress - this.startAddress) + 1;
deviceAddressArray = new Device[size];
// getDevices() provides an OrderedSet with devices ordered by priorities
for(Device device : getDevices()) {
for (Device device : getDevices()) {
MemoryRange range = device.getMemoryRange();
for(int address = range.startAddress; address <= range.endAddress; ++address) {
for (int address = range.startAddress; address <= range.endAddress; ++address) {
deviceAddressArray[address - this.startAddress] = device;
}
}
}
/**
* Add a device to the bus.
*
* @param device Device to add
* @param priority Bus prioirity.
* @throws MemoryRangeException
* @param device Device to add
* @param priority Bus priority.
* @throws MemoryRangeException Invalid device address range.
*/
public void addDevice(Device device, int priority) throws MemoryRangeException {
MemoryRange range = device.getMemoryRange();
if (range.startAddress() < this.startAddress || range.startAddress() > this.endAddress) {
@@ -108,28 +109,23 @@ 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);
SortedSet<Device> deviceSet = deviceMap.computeIfAbsent(priority, k -> new TreeSet<>());
if (deviceSet == null) {
deviceSet = new TreeSet<>();
deviceMap.put(priority, deviceSet);
}
device.setBus(this);
deviceSet.add(device);
buildDeviceAddressArray();
}
/**
* Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others.
*
* @param device Device to add
* @throws MemoryRangeException
* @throws MemoryRangeException Invalid device memory range.
*/
public void addDevice(Device device) throws MemoryRangeException {
addDevice(device, 0);
}
/**
* Remove a device from the bus.
@@ -137,7 +133,7 @@ public class Bus {
* @param device Device to remove
*/
public void removeDevice(Device device) {
for(SortedSet<Device> deviceSet : deviceMap.values()) {
for (SortedSet<Device> deviceSet : deviceMap.values()) {
deviceSet.remove(device);
}
buildDeviceAddressArray();
@@ -154,39 +150,39 @@ public class Bus {
* device.
*/
public boolean isComplete() {
if(deviceAddressArray == null) {
if (deviceAddressArray == null) {
buildDeviceAddressArray();
}
for(int address = startAddress; address <= endAddress; ++address) {
if(deviceAddressArray[address - startAddress] == null) {
for (int address = startAddress; address <= endAddress; ++address) {
if (deviceAddressArray[address - startAddress] == null) {
return false;
}
}
return true;
}
public int read(int address) throws MemoryAccessException {
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
Device d = deviceAddressArray[address - this.startAddress];
if(d != null) {
if (d != null) {
MemoryRange range = d.getMemoryRange();
int devAddr = address - range.startAddress();
return d.read(devAddr) & 0xff;
return d.read(devAddr, cpuAccess) & 0xff;
}
throw new MemoryAccessException("Bus read failed. No device at address " + String.format("$%04X", address));
}
public void write(int address, int value) throws MemoryAccessException {
Device d = deviceAddressArray[address - this.startAddress];
if(d != null) {
if (d != null) {
MemoryRange range = d.getMemoryRange();
int devAddr = address - range.startAddress();
d.write(devAddr, value);
return;
}
throw new MemoryAccessException("Bus write failed. No device at address " + String.format("$%04X", address));
}
@@ -217,17 +213,15 @@ public class Bus {
public SortedSet<Device> getDevices() {
// create an ordered set of devices, ordered by device priorities
SortedSet<Device> devices = new TreeSet<>();
List<Integer> priorities = new ArrayList<>(deviceMap.keySet());
Collections.sort(priorities);
for (int priority : priorities) {
SortedSet<Device> deviceSet = deviceMap.get(priority);
for (Device device : deviceSet) {
devices.add(device);
}
devices.addAll(deviceSet);
}
return devices;
}
File diff suppressed because it is too large. Load diff
+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,41 +25,36 @@ 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_WITH_INDIRECT_JMP_BUG,
/**
* Emulate an NMOS 6502 without the indirect JMP bug. This type of 6502
* does not actually exist in the wild.
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_WITHOUT_INDIRECT_JMP_BUG,
NMOS_6502,
/**
* Emulate a CMOS 65C02, with all CMOS instructions and addressing modes.
*/
CMOS
CMOS_6502,
/**
* Emulate a CMOS 65C816.
*/
CMOS_65816
}
/**
@@ -72,6 +67,12 @@ public interface InstructionTable {
}
},
AIX {
public String toString() {
return "Absolute, X-Indexed Indirect";
}
},
ABS {
public String toString() {
return "Absolute";
@@ -128,19 +129,31 @@ public interface InstructionTable {
ZPG {
public String toString() {
return "Zeropage";
return "Zero Page";
}
},
ZPR {
public String toString() {
return "Zero Page, Relative";
}
},
ZPX {
public String toString() {
return "Zeropage, X-indexed";
return "Zero Page, X-indexed";
}
},
ZPY {
public String toString() {
return "Zeropage, Y-indexed";
return "Zero Page, Y-indexed";
}
},
ZPI {
public String toString() {
return "Zero Page Indirect";
}
},
@@ -154,156 +167,188 @@ public interface InstructionTable {
// 6502 opcodes. No 65C02 opcodes implemented.
/**
* Instruction opcode names.
* Instruction opcode names. This lists all opcodes for
* NMOS 6502, CMOS 65C02, and CMOS 65C816.
* <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", null, null, null, "ORA", "ASL", null,
"PHP", "ORA", "ASL", null, null, "ORA", "ASL", null,
"BPL", "ORA", null, null, null, "ORA", "ASL", null,
"CLC", "ORA", null, null, null, "ORA", "ASL", null,
"JSR", "AND", null, null, "BIT", "AND", "ROL", null,
"PLP", "AND", "ROL", null, "BIT", "AND", "ROL", null,
"BMI", "AND", null, null, null, "AND", "ROL", null,
"SEC", "AND", null, null, null, "AND", "ROL", null,
"RTI", "EOR", null, null, null, "EOR", "LSR", null,
"PHA", "EOR", "LSR", null, "JMP", "EOR", "LSR", null,
"BVC", "EOR", null, null, null, "EOR", "LSR", null,
"CLI", "EOR", null, null, null, "EOR", "LSR", null,
"RTS", "ADC", null, null, null, "ADC", "ROR", null,
"PLA", "ADC", "ROR", null, "JMP", "ADC", "ROR", null,
"BVS", "ADC", null, null, null, "ADC", "ROR", null,
"SEI", "ADC", null, null, null, "ADC", "ROR", null,
"BCS", "STA", null, null, "STY", "STA", "STX", null,
"DEY", null, "TXA", null, "STY", "STA", "STX", null,
"BCC", "STA", null, null, "STY", "STA", "STX", null,
"TYA", "STA", "TXS", null, null, "STA", null, null,
"LDY", "LDA", "LDX", null, "LDY", "LDA", "LDX", null,
"TAY", "LDA", "TAX", null, "LDY", "LDA", "LDX", null,
"BCS", "LDA", null, null, "LDY", "LDA", "LDX", null,
"CLV", "LDA", "TSX", null, "LDY", "LDA", "LDX", null,
"CPY", "CMP", null, null, "CPY", "CMP", "DEC", null,
"INY", "CMP", "DEX", null, "CPY", "CMP", "DEC", null,
"BNE", "CMP", null, null, null, "CMP", "DEC", null,
"CLD", "CMP", null, null, null, "CMP", "DEC", null,
"CPX", "SBC", null, null, "CPX", "SBC", "INC", null,
"INX", "SBC", "NOP", null, "CPX", "SBC", "INC", null,
"BEQ", "SBC", null, null, null, "SBC", "INC", null,
"SED", "SBC", null, null, null, "SBC", "INC", null
"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
};
/**
* Instruction addressing modes.
* Instruction addressing modes. This table includes sizes
* for all instructions for NMOS 6502, CMOS 65C02,
* and CMOS 65C816
*/
Mode[] instructionModes = {
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x00-0x03
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x04-0x07
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.ZPG, // 0x04-0x07
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x08-0x0b
Mode.NUL, Mode.ABS, Mode.ABS, Mode.NUL, // 0x0c-0x0f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x10-0x13
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x14-0x17
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x18-0x1b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x1c-0x1f
Mode.ABS, Mode.ABS, Mode.ABS, Mode.ZPR, // 0x0c-0x0f
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0x10-0x13
Mode.ZPG, Mode.ZPX, Mode.ZPX, Mode.ZPG, // 0x14-0x17
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0x18-0x1b
Mode.ABS, Mode.ABX, Mode.ABX, Mode.ZPR, // 0x1c-0x1f
Mode.ABS, Mode.XIN, Mode.NUL, Mode.NUL, // 0x20-0x23
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x24-0x27
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.ZPG, // 0x24-0x27
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x28-0x2b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x2c-0x2f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x30-0x33
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x34-0x37
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x38-0x3b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x3c-0x3f
Mode.ABS, Mode.ABS, Mode.ABS, Mode.ZPR, // 0x2c-0x2f
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0x30-0x33
Mode.ZPX, Mode.ZPX, Mode.ZPX, Mode.ZPG, // 0x34-0x37
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0x38-0x3b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.ZPR, // 0x3c-0x3f
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x40-0x43
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x44-0x47
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.ZPG, // 0x44-0x47
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x48-0x4b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x4c-0x4f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x50-0x53
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x54-0x57
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x58-0x5b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x5c-0x5f
Mode.ABS, Mode.ABS, Mode.ABS, Mode.ZPR, // 0x4c-0x4f
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0x50-0x53
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.ZPG, // 0x54-0x57
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0x58-0x5b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.ZPR, // 0x5c-0x5f
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x60-0x63
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x64-0x67
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.ZPG, // 0x64-0x67
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x68-0x6b
Mode.IND, Mode.ABS, Mode.ABS, Mode.NUL, // 0x6c-0x6f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x70-0x73
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x74-0x77
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x78-0x7b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x7c-0x7f
Mode.IND, Mode.ABS, Mode.ABS, Mode.ZPR, // 0x6c-0x6f
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0x70-0x73
Mode.ZPX, Mode.ZPX, Mode.ZPX, Mode.ZPG, // 0x74-0x77
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0x78-0x7b
Mode.AIX, Mode.ABX, Mode.ABX, Mode.ZPR, // 0x7c-0x7f
Mode.REL, Mode.XIN, Mode.NUL, Mode.NUL, // 0x80-0x83
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x84-0x87
Mode.IMP, Mode.NUL, Mode.IMP, Mode.NUL, // 0x88-0x8b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x8c-0x8f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x90-0x93
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0x94-0x97
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.ZPG, // 0x84-0x87
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0x88-0x8b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.ZPR, // 0x8c-0x8f
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0x90-0x93
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.ZPG, // 0x94-0x97
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0x98-0x9b
Mode.NUL, Mode.ABX, Mode.NUL, Mode.NUL, // 0x9c-0x9f
Mode.ABS, Mode.ABX, Mode.ABX, Mode.ZPR, // 0x9c-0x9f
Mode.IMM, Mode.XIN, Mode.IMM, Mode.NUL, // 0xa0-0xa3
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xa4-0xa7
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.ZPG, // 0xa4-0xa7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xa8-0xab
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xac-0xaf
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xb0-0xb3
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0xb4-0xb7
Mode.ABS, Mode.ABS, Mode.ABS, Mode.ZPR, // 0xac-0xaf
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0xb0-0xb3
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.ZPG, // 0xb4-0xb7
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0xb8-0xbb
Mode.ABX, Mode.ABX, Mode.ABY, Mode.NUL, // 0xbc-0xbf
Mode.ABX, Mode.ABX, Mode.ABY, Mode.ZPR, // 0xbc-0xbf
Mode.IMM, Mode.XIN, Mode.NUL, Mode.NUL, // 0xc0-0xc3
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xc4-0xc7
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.ZPG, // 0xc4-0xc7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xc8-0xcb
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xcc-0xcf
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xd0-0xd3
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0xd4-0xd7
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0xd8-0xdb
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0xdc-0xdf
Mode.ABS, Mode.ABS, Mode.ABS, Mode.ZPR, // 0xcc-0xcf
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0xd0-0xd3
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.ZPG, // 0xd4-0xd7
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0xd8-0xdb
Mode.NUL, Mode.ABX, Mode.ABX, Mode.ZPR, // 0xdc-0xdf
Mode.IMM, Mode.XIN, Mode.NUL, Mode.NUL, // 0xe0-0xe3
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xe4-0xe7
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.ZPG, // 0xe4-0xe7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xe8-0xeb
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xec-0xef
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xf0-0xf3
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0xf4-0xf7
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0xf8-0xfb
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL // 0xfc-0xff
Mode.ABS, Mode.ABS, Mode.ABS, Mode.ZPR, // 0xec-0xef
Mode.REL, Mode.INY, Mode.ZPI, Mode.NUL, // 0xf0-0xf3
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.ZPG, // 0xf4-0xf7
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0xf8-0xfb
Mode.NUL, Mode.ABX, Mode.ABX, Mode.ZPR // 0xfc-0xff
};
/**
* Size, in bytes, required for each instruction.
* Size, in bytes, required for each instruction. This table
* includes sizes for all instructions for NMOS 6502, CMOS 65C02,
* and CMOS 65C816
*/
int[] instructionSizes = {
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 0, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0,
2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0
1, 2, 2, 1, 2, 2, 2, 2, 1, 2, 1, 1, 3, 3, 3, 3, // 0x00-0x0f
2, 2, 2, 1, 2, 2, 2, 2, 1, 3, 1, 1, 3, 3, 3, 3, // 0x10-0x1f
3, 2, 2, 1, 2, 2, 2, 2, 1, 2, 1, 1, 3, 3, 3, 3, // 0x20-0x2f
2, 2, 2, 1, 2, 2, 2, 2, 1, 3, 1, 1, 3, 3, 3, 3, // 0x30-0x3f
1, 2, 2, 1, 2, 2, 2, 2, 1, 2, 1, 1, 3, 3, 3, 3, // 0x40-0x4f
2, 2, 2, 1, 2, 2, 2, 2, 1, 3, 1, 1, 3, 3, 3, 3, // 0x50-0x5f
1, 2, 2, 1, 2, 2, 2, 2, 1, 2, 1, 1, 3, 3, 3, 3, // 0x60-0x6f
2, 2, 2, 1, 2, 2, 2, 2, 1, 3, 1, 1, 3, 3, 3, 3, // 0x70-0x7f
2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 1, 1, 3, 3, 3, 3, // 0x80-0x8f
2, 2, 2, 1, 2, 2, 2, 2, 1, 3, 1, 1, 3, 3, 3, 3, // 0x90-0x9f
2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 1, 1, 3, 3, 3, 3, // 0xa0-0xaf
2, 2, 2, 1, 2, 2, 2, 2, 1, 3, 1, 1, 3, 3, 3, 3, // 0xb0-0xbf
2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 1, 1, 3, 3, 3, 3, // 0xc0-0xcf
2, 2, 2, 1, 2, 2, 2, 2, 1, 3, 1, 1, 3, 3, 3, 3, // 0xd0-0xdf
2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 1, 1, 3, 3, 3, 3, // 0xe0-0xef
2, 2, 2, 1, 2, 2, 2, 2, 1, 3, 1, 1, 3, 3, 3, 3 // 0xf0-0xff
};
/**
* Number of clock cycles required for each instruction
* Number of clock cycles required for each instruction when
* in NMOS mode.
*/
int[] instructionClocks = {
7, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 0, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 3, 3, 5, 0, 4, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 3, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 0, 3, 5, 0, 4, 2, 2, 0, 5, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
2, 6, 0, 0, 3, 3, 3, 0, 2, 0, 2, 0, 4, 4, 4, 0,
2, 6, 0, 0, 4, 4, 4, 0, 2, 5, 2, 0, 0, 5, 0, 0,
2, 6, 2, 0, 3, 3, 3, 0, 2, 2, 2, 0, 4, 4, 4, 0,
2, 5, 0, 0, 4, 4, 4, 0, 2, 4, 2, 0, 4, 4, 4, 0,
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0
int[] instructionClocksNmos = {
7, 6, 1, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, // 0x00-0x0f
2, 5, 1, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, // 0x10-0x1f
6, 6, 1, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6, // 0x20-0x2f
2, 5, 1, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, // 0x30-0x3f
6, 6, 1, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6, // 0x40-0x4f
2, 5, 1, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, // 0x50-0x5f
6, 6, 1, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6, // 0x60-0x6f
2, 5, 1, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, // 0x70-0x7f
2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, // 0x80-0x8f
2, 6, 1, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5, // 0x90-0x9f
2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, // 0xa0-0xaf
2, 5, 1, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4, // 0xb0-0xbf
2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, // 0xc0-0xcf
2, 5, 1, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, // 0xd0-0xdf
2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, // 0xe0-0xef
2, 5, 1, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7 // 0xf0-0xff
};
/**
* Number of clock cycles required for each instruction when
* in CMOS mode
*/
int[] instructionClocksCmos = {
7, 6, 2, 1, 5, 3, 5, 5, 3, 2, 2, 1, 6, 4, 6, 5, // 0x00-0x0f
2, 5, 5, 1, 5, 4, 6, 5, 2, 4, 2, 1, 6, 4, 6, 5, // 0x10-0x1f
6, 6, 2, 1, 3, 3, 5, 5, 4, 2, 2, 1, 4, 4, 6, 5, // 0x20-0x2f
2, 5, 5, 1, 4, 4, 6, 5, 2, 4, 2, 1, 4, 4, 6, 5, // 0x30-0x3f
6, 6, 2, 1, 2, 3, 5, 3, 3, 2, 2, 1, 3, 4, 6, 5, // 0x40-0x4f
2, 5, 5, 1, 4, 4, 6, 5, 2, 4, 3, 1, 8, 4, 6, 5, // 0x50-0x5f
6, 6, 2, 1, 3, 3, 5, 5, 4, 2, 2, 1, 6, 4, 6, 5, // 0x60-0x6f
2, 5, 5, 1, 4, 4, 6, 5, 2, 4, 4, 3, 6, 4, 6, 5, // 0x70-0x7f
3, 6, 2, 1, 3, 3, 3, 5, 2, 2, 2, 1, 4, 4, 4, 5, // 0x80-0x8f
2, 6, 5, 1, 4, 4, 4, 5, 2, 5, 2, 1, 4, 5, 5, 5, // 0x90-0x9f
2, 6, 2, 1, 3, 3, 3, 5, 2, 2, 2, 1, 4, 4, 4, 5, // 0xa0-0xaf
2, 5, 5, 1, 4, 4, 4, 5, 2, 4, 2, 1, 4, 4, 4, 5, // 0xb0-0xbf
2, 6, 2, 1, 3, 3, 5, 5, 2, 2, 2, 3, 4, 4, 6, 5, // 0xc0-0xcf
2, 5, 5, 1, 4, 4, 6, 5, 2, 4, 3, 3, 4, 4, 7, 5, // 0xd0-0xdf
2, 6, 2, 1, 3, 3, 5, 5, 2, 2, 2, 1, 4, 4, 6, 5, // 0xe0-0xef
2, 5, 5, 1, 4, 4, 6, 5, 2, 4, 4, 1, 4, 4, 7, 5 // 0xf0-0xff
};
}
+106 -57
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,90 +22,139 @@
* 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;
for(int i = 0; i < args.length; ++i) {
String arg = args[i].toLowerCase(Locale.ENGLISH);
if(arg.equals("-machine") && (i+1) < args.length) {
String machine = args[i+1].trim().toLowerCase(Locale.ENGLISH);
public static void main(String[] args) throws Exception {
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);
switch (machine) {
case "symon":
machineClass = SymonMachine.class;
break;
case "multicomp":
machineClass = MulticompMachine.class;
break;
case "simple":
machineClass = SimpleMachine.class;
break;
case "symon":
machineClass = SymonMachine.class;
break;
case "beneater":
machineClass = BenEaterMachine.class;
break;
case "mike42":
machineClass = MichaelBillingtonMachine.class;
break;
default:
logger.error("Could not start Symon. Unknown machine type {}", machine);
return;
}
}
}
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;
if (line.hasOption("cpu")) {
String cpu = line.getOptionValue("cpu").toLowerCase(Locale.ENGLISH);
switch (cpu) {
case "6502":
cpuBehavior = InstructionTable.CpuBehavior.NMOS_6502;
break;
case "65c02":
cpuBehavior = InstructionTable.CpuBehavior.CMOS_6502;
break;
case "65c816":
cpuBehavior = InstructionTable.CpuBehavior.CMOS_65816;
break;
default:
logger.error("Could not start Symon. Unknown cpu type {}", cpu);
return;
}
}
final Simulator simulator = new Simulator(machineClass);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (line.hasOption("rom")) {
romFile = line.getOptionValue("rom");
}
if (line.hasOption("brk")) {
haltOnBreak = true;
}
Ui.initUi();
while (true) {
if (machineClass == null) {
machineClass = Ui.showMachineSelectionDialog();
}
if (cpuBehavior == null) {
cpuBehavior = InstructionTable.CpuBehavior.NMOS_6502;
}
final Simulator simulator = new Simulator(machineClass, cpuBehavior, romFile, haltOnBreak);
SwingUtilities.invokeLater(() -> {
// Create the main UI window:
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi();
} catch (Exception e) {
e.printStackTrace();
logger.error("Error during Symon UI initialization: {}", e.getMessage());
System.exit(-1);
}
});
Simulator.MainCommand cmd = simulator.waitForCommand();
if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) {
machineClass = null;
} else {
break;
}
});
Simulator.MainCommand cmd = simulator.waitForCommand();
if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) {
machineClass = null;
} else {
break;
}
} catch (ParseException ex) {
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();
+174 -118
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
@@ -23,6 +23,7 @@
package com.loomcom.symon;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.*;
import com.loomcom.symon.machines.Machine;
@@ -36,8 +37,8 @@ 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.
@@ -53,7 +54,7 @@ public class Simulator {
// UI constants
private static final int DEFAULT_FONT_SIZE = 12;
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, UIScale.scale(DEFAULT_FONT_SIZE));
private static final int CONSOLE_BORDER_WIDTH = 10;
// Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz.
@@ -71,7 +72,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.
@@ -114,17 +115,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
@@ -135,10 +139,17 @@ public class Simulator {
*/
private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"};
public Simulator(Class machineClass) throws Exception {
public Simulator(Class<?> machineClass) throws Exception {
this(machineClass, InstructionTable.CpuBehavior.NMOS_6502, null, false);
}
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();
this.machine = (Machine) machineClass.getConstructors()[0].newInstance(romFile);
this.machine.getCpu().setBehavior(cpuType);
// Initialize final fields in the constructor.
this.traceLog = new TraceLog();
@@ -155,7 +166,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);
@@ -169,7 +180,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();
@@ -181,18 +192,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);
}
}
@@ -200,6 +215,7 @@ public class Simulator {
buttonContainer.add(runStopButton);
buttonContainer.add(stepButton);
buttonContainer.add(stepOverButton);
buttonContainer.add(stepCountBox);
buttonContainer.add(softResetButton);
buttonContainer.add(hardResetButton);
@@ -214,38 +230,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);
@@ -267,7 +270,7 @@ public class Simulator {
try {
commandMonitorObject.wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
logger.error("Interrupted during wait on command monitor.");
}
}
return command;
@@ -330,12 +333,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.
*/
@@ -348,14 +367,14 @@ public class Simulator {
// output ready.
if (machine.getAcia() != null && machine.getAcia().hasTxChar()) {
// This is thread-safe
console.print(Character.toString((char) machine.getAcia().txRead()));
console.print(Character.toString((char) machine.getAcia().txRead(true)));
console.repaint();
}
// 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!");
@@ -404,53 +423,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;
@@ -460,9 +499,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);
}
}
@@ -503,12 +546,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
@@ -546,7 +586,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
@@ -640,30 +680,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.");
}
@@ -678,6 +715,21 @@ public class Simulator {
}
}
class SetCpuAction extends AbstractAction {
private final Cpu.CpuBehavior behavior;
public SetCpuAction(String cpu, Cpu.CpuBehavior behavior) {
super(cpu, null);
this.behavior = behavior;
putValue(SHORT_DESCRIPTION, "Set CPU to " + cpu);
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
machine.getCpu().setBehavior(behavior);
}
}
class ToggleTraceWindowAction extends AbstractAction {
public ToggleTraceWindowAction() {
super("Trace Log", null);
@@ -704,11 +756,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());
}
}
}
@@ -721,11 +769,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());
}
}
}
@@ -738,11 +782,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());
}
}
}
@@ -868,6 +908,14 @@ public class Simulator {
JMenuItem selectMachineItem = new JMenuItem(new SelectMachineAction());
simulatorMenu.add(selectMachineItem);
// "CPU" sub-menu
JMenu cpuTypeMenu = new JMenu("CPU");
ButtonGroup cpuGroup = new ButtonGroup();
makeCpuMenuItem("6502", Cpu.CpuBehavior.NMOS_6502, cpuTypeMenu, cpuGroup);
makeCpuMenuItem("65C02", Cpu.CpuBehavior.CMOS_6502, cpuTypeMenu, cpuGroup);
makeCpuMenuItem("65C816", Cpu.CpuBehavior.CMOS_65816, cpuTypeMenu, cpuGroup);
// "Clock Speed" sub-menu
JMenu speedSubMenu = new JMenu("Clock Speed");
ButtonGroup speedGroup = new ButtonGroup();
@@ -878,6 +926,7 @@ public class Simulator {
makeSpeedMenuItem(8, speedSubMenu, speedGroup);
simulatorMenu.add(speedSubMenu);
simulatorMenu.add(cpuTypeMenu);
// "Breakpoints"
final JCheckBoxMenuItem showBreakpoints = new JCheckBoxMenuItem(new ToggleBreakpointWindowAction());
@@ -914,19 +963,26 @@ public class Simulator {
subMenu.add(item);
group.add(item);
}
private void makeCpuMenuItem(String cpu, Cpu.CpuBehavior behavior, JMenu subMenu, ButtonGroup group) {
Action action = new SetCpuAction(cpu, behavior);
JCheckBoxMenuItem item = new JCheckBoxMenuItem(action);
item.setSelected(machine.getCpu().getBehavior() == behavior);
subMenu.add(item);
group.add(item);
}
}
private void updateVisibleState() {
// Immediately update the UI.
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Now update the state
statusPane.updateState();
memoryWindow.updateState();
if (traceLog.shouldUpdate()) {
traceLog.refresh();
}
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,8 +32,8 @@ import com.loomcom.symon.exceptions.MemoryRangeException;
public abstract class Acia extends Device {
private String name;
private final String name;
/**
* Register addresses
*/
@@ -42,13 +42,14 @@ public abstract class Acia extends Device {
boolean receiveIrqEnabled = false;
boolean transmitIrqEnabled = false;
boolean overrun = false;
long lastTxWrite = 0;
boolean interrupt = false;
long lastTxWrite = 0;
long lastRxRead = 0;
int baudRate = 0;
long baudRateDelay = 0;
/**
/**
* Read/Write buffers
*/
int rxChar = 0;
@@ -56,8 +57,8 @@ public abstract class Acia extends Device {
boolean rxFull = false;
boolean txEmpty = true;
public Acia(int address, int size, String name) throws MemoryRangeException {
super(address, address + size - 1, name);
this.name = name;
@@ -100,41 +101,46 @@ public abstract class Acia extends Device {
/**
* @return The contents of the status register.
*/
public abstract int statusReg();
public abstract int statusReg(boolean cpuAccess);
@Override
public String toString() {
return name + "@" + String.format("%04X", baseAddress);
}
public synchronized int rxRead() {
lastRxRead = System.nanoTime();
overrun = false;
rxFull = false;
public synchronized int rxRead(boolean cpuAccess) {
if (cpuAccess) {
lastRxRead = System.nanoTime();
overrun = false;
rxFull = false;
}
return rxChar;
}
public synchronized void rxWrite(int data) {
if(rxFull) {
if (rxFull) {
overrun = true;
}
rxFull = true;
if (receiveIrqEnabled) {
interrupt = true;
getBus().assertIrq();
}
rxChar = data;
}
public synchronized int txRead() {
txEmpty = true;
public synchronized int txRead(boolean cpuAccess) {
if (cpuAccess) {
txEmpty = true;
if (transmitIrqEnabled) {
getBus().assertIrq();
if (transmitIrqEnabled) {
interrupt = true;
getBus().assertIrq();
}
}
return txChar;
}
@@ -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,15 +53,23 @@ 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
public int read(int address) throws MemoryAccessException {
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
switch (address) {
case DATA_REG:
return rxRead();
return rxRead(cpuAccess);
case STAT_REG:
return statusReg();
return statusReg(cpuAccess);
case CMND_REG:
return commandRegister;
case CTRL_REG:
@@ -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);
}
@@ -178,8 +178,8 @@ public class Acia6551 extends Acia {
* @return The contents of the status register.
*/
@Override
public int statusReg() {
// TODO: Parity Error, Framing Error, DTR, DSR, and Interrupt flags.
public int statusReg(boolean cpuAccess) {
// TODO: Parity Error, Framing Error, DTR, and DSR flags.
int stat = 0;
if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) {
stat |= 0x08;
@@ -190,17 +190,29 @@ public class Acia6551 extends Acia {
if (overrun) {
stat |= 0x04;
}
if (interrupt) {
stat |= 0x80;
}
if (cpuAccess) {
interrupt = false;
}
return stat;
}
private synchronized void reset() {
txChar = 0;
txEmpty = true;
rxChar = 0;
rxFull = false;
receiveIrqEnabled = false;
transmitIrqEnabled = 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
@@ -30,8 +30,8 @@ import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the Motorola 6850 ACIA, with limited
* functionality. Interrupts are not supported.
* <p/>
* 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
@@ -43,7 +43,7 @@ public class Acia6850 extends Acia {
static final int STAT_REG = 0; // read-only
static final int CTRL_REG = 0; // write-only
static final int RX_REG = 1; // read-only
static final int TX_REG = 1; // write-only
@@ -53,12 +53,12 @@ public class Acia6850 extends Acia {
}
@Override
public int read(int address) throws MemoryAccessException {
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
switch (address) {
case RX_REG:
return rxRead();
return rxRead(cpuAccess);
case STAT_REG:
return statusReg();
return statusReg(cpuAccess);
default:
throw new MemoryAccessException("No register.");
@@ -78,7 +78,7 @@ public class Acia6850 extends Acia {
throw new MemoryAccessException("No register.");
}
}
private void setCommandRegister(int data) {
// Bits 0 & 1 control the master reset
if((data & 0x01) != 0 && (data & 0x02) != 0) {
@@ -97,8 +97,8 @@ public class Acia6850 extends Acia {
* @return The contents of the status register.
*/
@Override
public int statusReg() {
// TODO: Parity Error, Framing Error, DTR, DSR, and Interrupt flags.
public int statusReg(boolean cpuAccess) {
// TODO: Parity Error, Framing Error, DTR, and DSR flags.
int stat = 0;
if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) {
stat |= 0x01;
@@ -109,7 +109,14 @@ public class Acia6850 extends Acia {
if (overrun) {
stat |= 0x20;
}
if (interrupt) {
stat |= 0x80;
}
if (cpuAccess) {
interrupt = false;
}
return stat;
}
@@ -118,6 +125,7 @@ public class Acia6850 extends Acia {
overrun = false;
rxFull = false;
txEmpty = true;
interrupt = false;
}
}
@@ -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;
@@ -119,20 +117,18 @@ public class Crtc extends Device {
}
@Override
public int read(int address) 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;
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
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
@@ -142,7 +138,7 @@ public class Crtc extends Device {
public int getCharAtAddress(int address) throws MemoryAccessException {
// TODO: Row/Column addressing
return memory.read(address);
return memory.read(address, false);
}
public int getHorizontalDisplayed() {
@@ -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);
@@ -72,7 +72,7 @@ public abstract class Device implements Comparable<Device> {
/* Methods required to be implemented by inheriting classes. */
public abstract void write(int address, int data) throws MemoryAccessException;
public abstract int read(int address) throws MemoryAccessException;
public abstract int read(int address, boolean cpuAccess) throws MemoryAccessException;
public abstract String toString();
@@ -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;
@@ -94,7 +94,7 @@ public class Memory extends Device {
}
public int read(int address) throws MemoryAccessException {
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
return this.mem[address];
}
@@ -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
@@ -94,7 +94,7 @@ public class SdController extends Device {
}
@Override
public int read(int address) throws MemoryAccessException {
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
switch (address) {
case 0:
return readData();
@@ -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
@@ -25,85 +25,90 @@ package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import com.loomcom.symon.util.Utils;
/**
* 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
}
static final int IOB_REG = 0; // Output Register B / Input Register B
static final int IOA_REG = 1; // Output Register A / Input Register A
static final int DDRB_REG = 2; // Data Direction Register B
static final int DDRA_REG = 3; // Data Direction Register A
static final int T1CL_REG = 4; // T1 Low-Order Counter / Latches
static final int T1CH_REG = 5; // T1 High-Order Counter
static final int T1LL_REG = 6; // T1 Low-Order Latches
static final int T1LH_REG = 7; // T1 High-Order Latches
static final int T2CL_REG = 8; // T2 Low-Order Counter / Latches
static final int T2CH_REG = 9; // T2 High-Order Counter
static final int SR_REG = 10; // Shift Register
static final int AuxC_REG = 11; // Auxiliary Control Register
static final int PerC_REG = 12; // Peripheral Control Register
static final int IntF_REG = 13; // Interrupt Flag Register
static final int IntE_REG = 14; // Interrupt Enable Register
static final int IOANH_REG = 15; // Same as Register 1 (IOA), but no Handshake
private int outputRegisterB, inputRegisterB;
private int outputRegisterA, inputRegisterA;
private int dataDirectionB, dataDirectionA;
public Via6522(int address) throws MemoryRangeException {
super(address, address + VIA_SIZE - 1, "MOS 6522 VIA");
outputRegisterA = outputRegisterB = 0;
dataDirectionA = dataDirectionB = 0;
}
@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:
switch (address) {
case IOB_REG -> outputRegisterB = data & 0xFF;
case IOA_REG -> outputRegisterA = data & 0xFF;
case DDRB_REG -> dataDirectionB = data & 0xFF;
case DDRA_REG -> dataDirectionA = data & 0xFF;
case IOANH_REG -> outputRegisterA = data & 0xFF;
default -> throw new MemoryAccessException("No Register or Not Implemented!");
}
}
@Override
public int read(int address) throws MemoryAccessException {
Register[] registers = Register.values();
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
return switch (address) {
case IOB_REG -> pbRead();
case IOA_REG -> paRead();
case DDRB_REG -> dataDirectionB;
case DDRA_REG -> dataDirectionA;
case IOANH_REG -> paRead();
default -> throw new MemoryAccessException("No Register or Not Implemented!");
};
}
if (address >= registers.length) {
throw new MemoryAccessException("Unknown register: " + address);
}
public void setInputRegisterB(int data) {
inputRegisterB = data & 0xFF;
}
Register r = registers[address];
public void setInputRegisterA(int data) {
inputRegisterA = data & 0xFF;
}
switch (r) {
case ORA:
case ORB:
case DDRA:
case DDRB:
case T1C_L:
case T1C_H:
case T1L_L:
case T1L_H:
case T2C_L:
case T2C_H:
case SR:
case ACR:
case PCR:
case IFR:
case IER:
case ORA_H:
default:
}
return 0;
public String getPBStatus() {
return "%" + Utils.byteToBin(pbRead());
}
public String getPAStatus() {
return "%" + Utils.byteToBin(paRead());
}
private int pbRead() {
// Return bits of output register where DDRB is 1 and input register where DDRB is 0
return (outputRegisterB & dataDirectionB) | (inputRegisterB & ~dataDirectionB);
}
private int paRead() {
// Return bits of output register where DDRB is 1 and input register where DDRB is 0
return (outputRegisterA & dataDirectionA) | (inputRegisterA & ~dataDirectionA);
}
}
@@ -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
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal;
/**
* A {@link TerminalModel} which implements some common behaviour.
* @author Graham Edgecombe
*/
public abstract class AbstractTerminalModel implements TerminalModel {
@Override
public void clear() {
int rows = getRows(), columns = getColumns();
for (int column = 0; column < columns; column++) {
for (int row = 0; row < rows; row++) {
setCell(column, row, null);
}
}
}
@Override
public void moveCursorBack(int n) {
if (n < 0) {
throw new IllegalArgumentException("n must be positive");
}
int cursorColumn = getCursorColumn() - n;
if (cursorColumn < 0) {
cursorColumn = 0;
}
setCursorColumn(cursorColumn);
}
@Override
public void moveCursorForward(int n) {
if (n < 0) {
throw new IllegalArgumentException("n must be positive");
}
int columns = getColumns();
int cursorColumn = getCursorColumn() + n;
if (cursorColumn >= columns) {
cursorColumn = columns - 1;
}
setCursorColumn(cursorColumn);
}
@Override
public void moveCursorDown(int n) {
if (n < 0) {
throw new IllegalArgumentException("n must be positive");
}
int bufferSize = getBufferSize();
int cursorRow = getCursorRow() + n;
if (cursorRow >= bufferSize) {
cursorRow = bufferSize - 1;
}
setCursorRow(cursorRow);
}
@Override
public void moveCursorUp(int n) {
if (n < 0) {
throw new IllegalArgumentException("n must be positive");
}
int cursorRow = getCursorRow() - n;
if (cursorRow < 0) {
cursorRow = 0;
}
setCursorRow(cursorRow);
}
}
@@ -0,0 +1,178 @@
/*
* 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
* 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.jterminal;
import com.loomcom.symon.jterminal.vt100.Vt100TerminalModel;
import java.awt.*;
import javax.swing.JComponent;
import javax.swing.JScrollBar;
/**
* Swing terminal emulation component
* @author Seth J. Morabito
*
*/
public class JTerminal extends JComponent {
private static final long serialVersionUID = 2871625194146986567L;
private int borderWidth = 0;
private JScrollBar scrollBar;
/**
* The terminal emulation model
*/
private TerminalModel model;
/**
* The font this terminal uses
*/
private Font font;
/**
* The cell width in pixels
*/
private int cellWidth;
/**
* The cell height in pixels
*/
private int cellHeight;
/**
* Max descender for font
*/
private int maxDescender;
public JTerminal(Font font) {
this(new Vt100TerminalModel(), font);
}
public JTerminal(TerminalModel model, Font font) {
setModel(model);
setFont(font);
init();
}
public void setBorderWidth(int borderWidth) {
this.borderWidth = borderWidth;
revalidate();
}
public void setFont(Font font) {
this.font = font;
setCellWidthAndHeight(font);
revalidate();
}
public Font getFont() {
return font;
}
public void setModel(TerminalModel model) {
if (model == null) {
throw new NullPointerException("model");
}
this.model = model;
}
public TerminalModel getModel() {
return model;
}
public void print(String str) {
model.print(str);
}
public Dimension getMinimumSize() {
return new Dimension(model.getColumns() * cellWidth + borderWidth * 2,
model.getRows() * cellHeight + borderWidth * 2);
}
public Dimension getMaximumSize() {
return getMinimumSize();
}
public Dimension getPreferredSize() {
return getMinimumSize();
}
public void paint(Graphics g) {
g.setFont(font);
int width = model.getColumns();
int height = model.getBufferSize();
g.setColor(model.getDefaultBackgroundColor());
g.fillRect(0, 0, width * cellWidth + borderWidth * 2, height * cellHeight + borderWidth * 2);
int start = scrollBar == null ? 0 : scrollBar.getValue();
for (int y = start; y < height; y++) {
for (int x = 0; x < width; x++) {
TerminalCell cell = model.getCell(x, y);
boolean cursorHere = (model.getCursorRow() == y) && (model.getCursorColumn() == x);
if ((cursorHere) && (cell == null)) {
cell = new TerminalCell(' ', model.getDefaultBackgroundColor(), model.getDefaultForegroundColor());
}
if (cell != null) {
int px = x * cellWidth + borderWidth;
int py = (y - start) * cellHeight + borderWidth;
g.setColor(cursorHere ? cell.getForegroundColor() : cell.getBackgroundColor());
g.fillRect(px, py, cellWidth, cellHeight);
g.setColor(cursorHere ? cell.getBackgroundColor() : cell.getForegroundColor());
g.drawChars(new char[] { cell.getCharacter() }, 0, 1, px, py + cellHeight - maxDescender);
}
}
}
}
private void init() {
setLayout(new BorderLayout(0, 0));
int rows = model.getRows();
int bufferSize = model.getBufferSize();
if (bufferSize > rows) {
scrollBar = new JScrollBar(Adjustable.VERTICAL, 0, rows, 0, bufferSize + 1);
scrollBar.addAdjustmentListener(evt -> repaint());
add("After", scrollBar);
}
repaint();
}
private void setCellWidthAndHeight(Font font) {
FontMetrics metrics = getFontMetrics(font);
cellWidth = metrics.charWidth('W');
cellHeight = metrics.getHeight();
maxDescender = metrics.getMaxDescent();
}
}
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal;
import java.awt.Color;
/**
* Represents a single terminal cell which contains a character, background
* color and foreground color.
* @author Graham Edgecombe
*/
public class TerminalCell {
/**
* The character.
*/
private final char character;
/**
* The background color.
*/
private final Color backgroundColor;
/**
* The foreground color.
*/
private final Color foregroundColor;
/**
* Creates a terminal cell with the specified character, background color
* and foreground color.
* @param character The character.
* @param backgroundColor The background color.
* @param foregroundColor The foreground color.
* @throws NullPointerException if the background or foreground color(s)
* are {@code null}.
*/
public TerminalCell(char character, Color backgroundColor, Color foregroundColor) {
if (backgroundColor == null) {
throw new NullPointerException("backgroundColor");
}
if (foregroundColor == null) {
throw new NullPointerException("foregroundColor");
}
this.character = character;
this.backgroundColor = backgroundColor;
this.foregroundColor = foregroundColor;
}
/**
* Gets the character.
* @return The character.
*/
public char getCharacter() {
return character;
}
/**
* Gets the background color.
* @return The background color.
*/
public Color getBackgroundColor() {
return backgroundColor;
}
/**
* Gets the foreground color.
* @return The foreground color.
*/
public Color getForegroundColor() {
return foregroundColor;
}
}
@@ -0,0 +1,161 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal;
import java.awt.Color;
import com.loomcom.symon.jterminal.bell.BellStrategy;
/**
* Model for terminals - defines methods for getting/setting cells, printing
* text to a terminal and getting the size of the terminal and buffer.
*/
public interface TerminalModel {
/**
* Sets the bell strategy.
* @param strategy The bell strategy.
* @throws NullPointerException if the strategy is {@code null}.
*/
void setBellStrategy(BellStrategy strategy);
/**
* Clears the terminal.
*/
void clear();
/**
* Moves the cursor back n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
void moveCursorBack(int n);
/**
* Moves the cursor forward n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
void moveCursorForward(int n);
/**
* Moves the cursor down n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
void moveCursorDown(int n);
/**
* Moves the cursor up n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
void moveCursorUp(int n);
/**
* Sets a cell.
* @param column The column.
* @param row The row.
* @param cell The cell.
* @throws IndexOutOfBoundsException if the column and/or row number(s) are
* out of bounds.
*/
void setCell(int column, int row, TerminalCell cell);
/**
* Gets a cell.
* @param column The column.
* @param row The row.
* @return The cell.
* @throws IndexOutOfBoundsException if the column and/or row number(s) are
* out of bounds.
*/
TerminalCell getCell(int column, int row);
/**
* Prints the specified string to the terminal at the cursor position,
* interpreting any escape sequences/special ASCII codes the model may
* support. Lines will be wrapped if necessary.
* @param str The string to print.
* @throws NullPointerException if the string is {@code null}.
*/
void print(String str);
/**
* Gets the number of columns.
* @return The number of columns.
*/
int getColumns();
/**
* Gets the number of rows.
* @return The number of rows.
*/
int getRows();
/**
* Gets the buffer size.
* @return The buffer size.
*/
int getBufferSize();
/**
* Gets the cursor row.
* @return The cursor row.
*/
int getCursorRow();
/**
* Sets the cursor row.
* @param row The cursor row.
* @throws IllegalArgumentException if the row is out of the valid range.
*/
void setCursorRow(int row);
/**
* Gets the cursor column.
* @return The cursor column.
*/
int getCursorColumn();
/**
* Sets the cursor column.
* @param column The cursor column.
* @throws IllegalArgumentException if the column is out of the valid range.
*/
void setCursorColumn(int column);
/**
* Gets the default background color.
* @return The default background color.
*/
Color getDefaultBackgroundColor();
/**
* Gets the default foreground color.
* @return The default foreground color.
*/
Color getDefaultForegroundColor();
}
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal.bell;
import java.awt.Toolkit;
/**
* A {@link BellStrategy} which calls {@link Toolkit#beep()}.
* @author Graham Edgecombe
*/
public class BeepBellStrategy implements BellStrategy {
@Override
public void soundBell() {
Toolkit.getDefaultToolkit().beep();
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal.bell;
/**
* A 'strategy' used to sound the bell (US-ASCII character {@code 7}).
* @author Graham Edgecombe
*/
public interface BellStrategy {
/**
* Sounds the bell.
*/
void soundBell();
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal.bell;
/**
* A {@link BellStrategy} which does nothing.
* @author Graham Edgecombe
*/
public class NopBellStrategy implements BellStrategy {
@Override
public void soundBell() {
/* ignore */
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal.vt100;
/**
* Represents an ANSI control sequence.
* @author Graham Edgecombe
*/
public class AnsiControlSequence {
/**
* The command character.
*/
private final char command;
/**
* The parameters.
*/
private final String[] parameters;
/**
* Creates an ANSI control sequence with the specified command and
* parameters.
* @param command The command character.
* @param parameters The parameters array.
* @throws NullPointerException if the parameters array is {@code null}.
*/
public AnsiControlSequence(char command, String[] parameters) {
if (parameters == null) {
throw new NullPointerException("parameters");
}
this.command = command;
if (parameters.length == 1 && parameters[0].isEmpty()) {
this.parameters = new String[0];
} else {
this.parameters = parameters.clone();
}
}
/**
* Gets the command character.
* @return The command character.
*/
public char getCommand() {
return command;
}
/**
* Gets the parameters array.
* @return The parameters array.
*/
public String[] getParameters() {
return parameters;
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal.vt100;
/**
* An interface which classes may use to listen to events from a
* {@link AnsiControlSequenceParser}.
*/
public interface AnsiControlSequenceListener {
/**
* Called when a control sequence has been parsed.
* @param seq The control sequence.
*/
void parsedControlSequence(AnsiControlSequence seq);
/**
* Called when a string has been parsed.
* @param str The string.
*/
void parsedString(String str);
}
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal.vt100;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
/**
* A class which parses {@link AnsiControlSequence}s from {@link String}(s).
* @author Graham Edgecombe
*/
public class AnsiControlSequenceParser {
/**
* The multibyte control sequence introducer.
*/
private static final char[] MULTI_CSI = new char[] { 27, '[' };
/**
* The single-byte control sequence introducer.
*/
private static final char SINGLE_CSI = 155;
/**
* 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();
/**
* The ANSI control sequence listener.
*/
private final AnsiControlSequenceListener listener;
/**
* Creates the ANSI control sequence parser.
* @param listener The listener.
*/
public AnsiControlSequenceParser(AnsiControlSequenceListener listener) {
this.listener = listener;
}
/**
* Parses the specified string.
* @param str The string to parse.
*/
public void parse(String str) {
if (buffer.length() > 0) {
str = buffer.toString().concat(str);
buffer = new StringBuilder();
}
try {
try (Reader reader = new StringReader(str)) {
parse(reader);
}
} catch (IOException ex) {
/* ignore */
}
}
/**
* Parses characters from the specified character reader.
* @param reader The character reader.
* @throws IOException if an I/O error occurs.
*/
private void parse(Reader reader) throws IOException {
StringBuilder text = new StringBuilder();
int character;
while ((character = reader.read()) != -1) {
boolean introducedControlSequence = false;
if (character == SINGLE_CSI) {
introducedControlSequence = true;
} else if (character == MULTI_CSI[0]) {
int nextCharacter = reader.read();
if (nextCharacter == -1) {
buffer.append((char) character);
break;
} else if (nextCharacter == MULTI_CSI[1]) {
introducedControlSequence = true;
} else {
text.append((char) character);
text.append((char) nextCharacter);
}
} else {
text.append((char) character);
}
if (introducedControlSequence) {
if (text.length() > 0) {
listener.parsedString(text.toString());
text = new StringBuilder();
}
parseControlSequence(reader);
}
}
if (text.length() > 0) {
listener.parsedString(text.toString());
}
}
/**
* Parses a control sequence.
* @param reader The character reader.
* @throws IOException if an I/O error occurs.
*/
private void parseControlSequence(Reader reader) throws IOException {
boolean finishedSequence = false;
StringBuilder parameters = new StringBuilder();
int character;
while ((character = reader.read()) != -1) {
if ((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z')) {
String[] array = parameters.toString().split(";");
AnsiControlSequence seq = new AnsiControlSequence((char) character, array);
listener.parsedControlSequence(seq);
finishedSequence = true;
break;
} else {
parameters.append((char) character);
}
}
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(SINGLE_CSI);
buffer.append(parameters);
}
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal.vt100;
import java.awt.Color;
/**
* Contains colors used by the SGR ANSI escape sequence.
* @author Graham Edgecombe
*/
final class SgrColor {
/**
* An array of normal intensity colors.
*/
public static final Color[] COLOR_NORMAL = new Color[] {
new Color(0, 0, 0),
new Color(128, 0, 0),
new Color(0, 128, 0),
new Color(128, 128, 0),
new Color(0, 0, 128),
new Color(128, 0, 128),
new Color(0, 128, 128),
new Color(192, 192, 192)
};
/**
* An array of bright intensity colors.
*/
public static final Color[] COLOR_BRIGHT = new Color[] {
new Color(128, 128, 128),
new Color(255, 0, 0),
new Color(0, 255, 0),
new Color(255, 255, 0),
new Color(0, 0, 255),
new Color(0, 0, 255),
new Color(255, 0, 255),
new Color(0, 255, 255),
new Color(255, 255, 255)
};
/**
* Default private constructor to prevent instantiation.
*/
private SgrColor() {
}
}
@@ -0,0 +1,490 @@
/*
* Copyright (c) 2009-2011 Graham Edgecombe.
*
* 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.jterminal.vt100;
import java.awt.Color;
import com.loomcom.symon.jterminal.AbstractTerminalModel;
import com.loomcom.symon.jterminal.TerminalCell;
import com.loomcom.symon.jterminal.TerminalModel;
import com.loomcom.symon.jterminal.bell.BellStrategy;
import com.loomcom.symon.jterminal.bell.NopBellStrategy;
/**
* A VT100/ANSI-compatible terminal model.
* @author Graham Edgecombe
*/
public class Vt100TerminalModel extends AbstractTerminalModel {
/**
* A {@link AnsiControlSequenceListener} which modifies the
* {@link TerminalModel} appropriately when an event happens.
* @author Graham Edgecombe
*/
private class Vt100Listener implements AnsiControlSequenceListener {
/**
* The saved cursor row.
*/
private int savedCursorRow = -1;
/**
* The saved cursor column.
*/
private int savedCursorColumn = -1;
@Override
public void parsedControlSequence(AnsiControlSequence seq) {
char command = seq.getCommand();
int n;
String[] parameters = seq.getParameters();
switch (command) {
case 'A':
case 'B':
case 'C':
case 'D':
n = 1;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
if (command == 'A') {
moveCursorUp(n);
} else if (command == 'B') {
moveCursorDown(n);
} else if (command == 'C') {
moveCursorForward(n);
} else {
moveCursorBack(n);
}
break;
case 'E':
case 'F':
n = 1;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
if (command == 'E') {
moveCursorDown(n);
} else {
moveCursorUp(n);
}
setCursorColumn(0);
break;
case 'G':
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
setCursorColumn(n - 1);
}
break;
case 'H':
case 'f':
if (parameters.length == 2) {
n = 1;
int m = 1;
if (parameters[0].length() > 0) {
n = Integer.parseInt(parameters[0]);
}
if (parameters[1].length() > 0) {
m = Integer.parseInt(parameters[1]);
}
setCursorRow(n - 1);
setCursorColumn(m - 1);
}
break;
case 'J':
n = 0;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
if (n == 0) {
int row = cursorRow;
int column = cursorColumn;
while(row < rows) {
while(column < columns) {
cells[row][column] = null;
column++;
}
column = 0;
row++;
}
} else if (n == 1) {
int row = cursorRow;
int column = cursorColumn;
while(row >= 0) {
while(column >= 0) {
cells[row][column] = null;
column--;
}
column = columns - 1;
row--;
}
} else if (n == 2) {
clear();
}
break;
case 'K':
n = 0;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
if (n == 0) {
for (int row = cursorRow; row < rows; row++) {
cells[row][cursorColumn] = null;
}
} else if (n == 1) {
for (int row = cursorRow; row >= 0; row--) {
cells[row][cursorColumn] = null;
}
} else if (n == 2) {
for (int column = 0; column < columns; column++) {
cells[cursorRow][column] = null;
}
}
break;
case 'm':
if (parameters.length == 0) {
parameters = new String[] { "0" };
}
for (String parameter : parameters) {
if (parameter.equals("0")) {
foregroundColor = DEFAULT_FOREGROUND_COLOR;
backgroundColor = DEFAULT_BACKGROUND_COLOR;
backgroundBold = DEFAULT_BACKGROUND_BOLD;
foregroundBold = DEFAULT_FOREGROUND_BOLD;
} else if (parameter.equals("2")) {
backgroundBold = true;
foregroundBold = true;
} else if (parameter.equals("22")) {
backgroundBold = false;
foregroundBold = false;
} else if ((parameter.startsWith("3") || parameter.startsWith("4")) && parameter.length() == 2) {
int color = Integer.parseInt(parameter.substring(1));
if (parameter.startsWith("3")) {
foregroundColor = color;
} else if (parameter.startsWith("4")) {
backgroundColor = color;
}
}
}
break;
case 'u':
if (savedCursorColumn != -1 && savedCursorRow != -1) {
cursorColumn = savedCursorColumn;
cursorRow = savedCursorRow;
}
break;
case 's':
savedCursorColumn = cursorColumn;
savedCursorRow = cursorRow;
break;
}
}
@Override
public void parsedString(String str) {
for (char ch : str.toCharArray()) {
switch (ch) {
case '\0':
continue;
case '\r':
cursorColumn = 0;
continue;
case '\n':
cursorRow++;
break;
case '\t':
while ((++cursorColumn % TAB_WIDTH) != 0);
continue;
case 8: // ASCII Backspace
case 127: // ASCII Delete
if (cursorColumn > 0) {
cells[cursorRow][--cursorColumn] = null;
}
continue;
case 7:
bellStrategy.soundBell();
continue;
}
if (cursorColumn >= columns) {
cursorColumn = 0;
cursorRow++;
}
if (cursorRow >= bufferSize) {
for (int i = 1; i < bufferSize; i++) {
System.arraycopy(cells[i], 0, cells[i - 1], 0, columns);
}
for (int i = 0; i < columns; i++) {
cells[bufferSize - 1][i] = null;
}
cursorRow--;
}
Color back = backgroundBold ? SgrColor.COLOR_BRIGHT[backgroundColor] : SgrColor.COLOR_NORMAL[backgroundColor];
Color fore = foregroundBold ? SgrColor.COLOR_BRIGHT[foregroundColor] : SgrColor.COLOR_NORMAL[foregroundColor];
if (ch != '\n') {
cells[cursorRow][cursorColumn++] = new TerminalCell(ch, back, fore);
}
}
}
}
/**
* The default number of columns.
*/
private static final int DEFAULT_COLUMNS = 80;
/**
* The default number of rows.
*/
private static final int DEFAULT_ROWS = 25;
/**
* The tab width in characters.
*/
private static final int TAB_WIDTH = 8;
/**
* The default foreground bold flag.
*/
private static final boolean DEFAULT_FOREGROUND_BOLD = false;
/**
* The default background bold flag.
*/
private static final boolean DEFAULT_BACKGROUND_BOLD = false;
/**
* The default foreground color.
*/
private static final int DEFAULT_FOREGROUND_COLOR = 7;
/**
* The default background color.
*/
private static final int DEFAULT_BACKGROUND_COLOR = 0;
/**
* The ANSI control sequence listener.
*/
private final AnsiControlSequenceListener listener = this.new Vt100Listener();
/**
* The ANSI control sequence parser.
*/
private final AnsiControlSequenceParser parser = new AnsiControlSequenceParser(listener);
/**
* The current bell strategy.
*/
private BellStrategy bellStrategy = new NopBellStrategy();
/**
* The array of cells.
*/
private TerminalCell[][] cells;
/**
* The number of columns.
*/
private int columns;
/**
* The number of rows.
*/
private int rows;
/**
* The buffer size.
*/
private int bufferSize;
/**
* The cursor row.
*/
private int cursorRow = 0;
/**
* The cursor column.
*/
private int cursorColumn = 0;
/**
* The current foreground bold flag.
*/
private boolean foregroundBold = DEFAULT_FOREGROUND_BOLD;
/**
* The current background bold flag.
*/
private boolean backgroundBold = DEFAULT_BACKGROUND_BOLD;
/**
* The current foreground color.
*/
private int foregroundColor = DEFAULT_FOREGROUND_COLOR;
/**
* The current background color.
*/
private int backgroundColor = DEFAULT_BACKGROUND_COLOR;
/**
* Creates the terminal model with the default number of columns and rows,
* and the default buffer size.
*/
public Vt100TerminalModel() {
this(DEFAULT_COLUMNS, DEFAULT_ROWS);
}
/**
* Creates the terminal model with the specified number of columns and
* rows. The buffer size is set to the number of rows.
* @param columns The number of columns.
* @param rows The number of rows.
* @throws IllegalArgumentException if the number of rows or columns is
* negative.
*/
public Vt100TerminalModel(int columns, int rows) {
this(columns, rows, rows);
}
/**
* Creates the terminal model with the specified number of columns and rows
* and the specified buffer size.
* @param columns The number of columns.
* @param rows The number of rows.
* @param bufferSize The buffer size.
* @throws IllegalArgumentException if the number of rows or columns is
* negative, or if the buffer size is less than the number of rows.
*/
public Vt100TerminalModel(int columns, int rows, int bufferSize) {
if (columns < 0 || rows < 0 || bufferSize < 0) {
throw new IllegalArgumentException("Zero or positive values only allowed for columns, rows and buffer size.");
}
if (bufferSize < rows) {
throw new IllegalArgumentException("The buffer is too small");
}
this.columns = columns;
this.rows = rows;
this.bufferSize = bufferSize;
init();
}
/**
* Initializes the terminal model.
*/
private void init() {
cells = new TerminalCell[bufferSize][columns];
}
@Override
public int getCursorRow() {
return cursorRow;
}
@Override
public void setCursorRow(int row) {
if (row < 0 || row >= bufferSize) {
throw new IllegalArgumentException("row out of range");
}
cursorRow = row;
}
@Override
public int getCursorColumn() {
return cursorColumn;
}
@Override
public void setCursorColumn(int column) {
if (column < 0 || column >= columns) {
throw new IllegalArgumentException("column out of range");
}
cursorColumn = column;
}
@Override
public TerminalCell getCell(int column, int row) {
if (column < 0 || row < 0 || column >= columns || row >= bufferSize) {
throw new IndexOutOfBoundsException();
}
return cells[row][column];
}
@Override
public void setCell(int column, int row, TerminalCell cell) {
if (column < 0 || row < 0 || column >= columns || row >= bufferSize) {
throw new IndexOutOfBoundsException();
}
cells[row][column] = cell;
}
@Override
public void print(String str) {
if (str == null) {
throw new NullPointerException("str");
}
parser.parse(str);
}
@Override
public int getColumns() {
return columns;
}
@Override
public int getRows() {
return rows;
}
@Override
public int getBufferSize() {
return bufferSize;
}
@Override
public void setBellStrategy(BellStrategy strategy) {
if (strategy == null) {
throw new NullPointerException("strategy");
}
this.bellStrategy = strategy;
}
@Override
public Color getDefaultBackgroundColor() {
final int bg = DEFAULT_BACKGROUND_COLOR;
return DEFAULT_BACKGROUND_BOLD ? SgrColor.COLOR_BRIGHT[bg] : SgrColor.COLOR_NORMAL[bg];
}
@Override
public Color getDefaultForegroundColor() {
final int fg = DEFAULT_FOREGROUND_COLOR;
return DEFAULT_FOREGROUND_BOLD ? SgrColor.COLOR_BRIGHT[fg] : SgrColor.COLOR_NORMAL[fg];
}
}
@@ -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();
@@ -0,0 +1,133 @@
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 MichaelBillingtonMachine implements Machine{
private static final Logger logger = LoggerFactory.getLogger(MichaelBillingtonMachine.class.getName());
// Constants used by the simulated system
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
// 32k of ram, from $0000 to $7fff
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0x8000;
// VIA at $8000-$800f
private static final int PIA_BASE = 0x8000;
// ACIA at $8400-$8403
private static final int ACIA_BASE = 0x8400;
// 16k ROM from $c000 to $ffff
private static final int ROM_BASE = 0xc000;
private static final int ROM_SIZE = 0x4000;
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Pia pia;
private final Memory ram;
private Memory rom;
public MichaelBillingtonMachine(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);
this.pia = new Via6522(PIA_BASE);
this.acia = new Acia6551(ACIA_BASE);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(pia);
bus.addDevice(acia);
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 null;
}
@Override
public Memory getRom() {
return rom;
}
@Override
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 "mike42";
}
}
@@ -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
@@ -68,7 +68,7 @@ public class MulticompMachine implements Machine {
private Memory rom;
public MulticompMachine() throws Exception {
public MulticompMachine(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);
@@ -79,20 +79,23 @@ public class MulticompMachine implements Machine {
bus.addDevice(ram);
bus.addDevice(acia, 1);
bus.addDevice(new SdController(SD_BASE), 1);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file " + romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
} else {
logger.info("Default ROM file " + romImage +
if (romFile != null) {
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file " + romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
} else {
logger.info("Default ROM file " + romImage +
" not found, loading empty R/W memory image.");
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
} 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
@@ -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
@@ -46,7 +46,7 @@ public class SimpleMachine implements Machine {
private final Memory ram;
private final Cpu cpu;
public SimpleMachine() throws MemoryRangeException {
public SimpleMachine(String romFile) throws MemoryRangeException {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.ram = new Memory(BUS_BOTTOM, BUS_TOP, false);
this.cpu = new Cpu();
@@ -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
@@ -70,7 +70,7 @@ public class SymonMachine implements Machine {
private Memory rom;
public SymonMachine() throws Exception {
public SymonMachine(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);
@@ -83,14 +83,18 @@ public class SymonMachine implements Machine {
bus.addDevice(pia);
bus.addDevice(acia);
bus.addDevice(crtc);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file {}", romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
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("Default ROM file {} not found, loading empty R/W memory image.", romImage);
logger.info("No ROM file specified, loading empty R/W memory image.");
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
@@ -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
@@ -23,8 +23,8 @@
package com.loomcom.symon.ui;
import com.grahamedgecombe.jterminal.JTerminal;
import com.grahamedgecombe.jterminal.vt100.Vt100TerminalModel;
import com.loomcom.symon.jterminal.JTerminal;
import com.loomcom.symon.jterminal.vt100.Vt100TerminalModel;
import com.loomcom.symon.exceptions.FifoUnderrunException;
import com.loomcom.symon.util.FifoRingBuffer;
@@ -44,16 +44,19 @@ import java.awt.event.MouseListener;
public class Console extends JTerminal implements KeyListener, MouseListener {
private static final int DEFAULT_BORDER_WIDTH = 10;
private static final long serialVersionUID = 6633818486963338126L;
private static final int DEFAULT_BORDER_WIDTH = 10;
// If true, swap CR and LF characters.
private static final boolean SWAP_CR_AND_LF = true;
// If true, send CRLF (0x0d 0x0a) whenever CR is typed
private boolean sendCrForLf = false;
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);
//super(new Vt100TerminalModel(columns, rows));
// A small type-ahead buffer, as might be found in any real
// VT100-style serial terminal.
this.typeAheadBuffer = new FifoRingBuffer<>(128);
@@ -104,7 +107,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
}
}
if (sendCrForLf && keyTyped == 0x0d) {
if (sendCrForLf && (keyTyped == 0x0d)) {
typeAheadBuffer.push((char) 0x0d);
typeAheadBuffer.push((char) 0x0a);
} else {
@@ -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;
@@ -331,10 +331,10 @@ public class MemoryWindow extends JFrame implements ActionListener {
return Utils.wordToHex(fullAddress(row, 1));
} else if (column < 9) {
// Display hex value of the data
return Utils.byteToHex(bus.read(fullAddress(row, column)));
return Utils.byteToHex(bus.read(fullAddress(row, column), false));
} else {
// Display the ASCII equivalent (if printable)
return Utils.byteToAscii(bus.read(fullAddress(row, column - 8)));
return Utils.byteToAscii(bus.read(fullAddress(row, column - 8), false));
}
} catch (MemoryAccessException ex) {
return "??";
@@ -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
@@ -23,8 +23,11 @@
package com.loomcom.symon.ui;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.CpuState;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.devices.Via6522;
import com.loomcom.symon.machines.Machine;
import javax.swing.*;
@@ -33,6 +36,7 @@ import javax.swing.border.EtchedBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Objects;
/**
* UI component that displays the current state of the simulated CPU.
@@ -68,12 +72,14 @@ public class StatusPanel extends JPanel {
private JTextField aField;
private JTextField xField;
private JTextField yField;
private JTextField piaBField;
private JTextField piaAField;
private Machine machine;
private final Machine machine;
private static final int EMPTY_BORDER = 10;
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 12);
private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, UIScale.scale(12));
private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22);
private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
@@ -85,7 +91,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));
@@ -98,20 +104,20 @@ public class StatusPanel extends JPanel {
JPanel statusFlagsPanel = new JPanel();
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
carryOn = new ImageIcon(this.getClass().getResource("/C_on.png"));
carryOff = new ImageIcon(this.getClass().getResource("/C_off.png"));
zeroOn = new ImageIcon(this.getClass().getResource("/Z_on.png"));
zeroOff = new ImageIcon(this.getClass().getResource("/Z_off.png"));
irqOn = new ImageIcon(this.getClass().getResource("/I_on.png"));
irqOff = new ImageIcon(this.getClass().getResource("/I_off.png"));
decimalOn = new ImageIcon(this.getClass().getResource("/D_on.png"));
decimalOff = new ImageIcon(this.getClass().getResource("/D_off.png"));
breakOn = new ImageIcon(this.getClass().getResource("/B_on.png"));
breakOff = new ImageIcon(this.getClass().getResource("/B_off.png"));
overflowOn = new ImageIcon(this.getClass().getResource("/O_on.png"));
overflowOff = new ImageIcon(this.getClass().getResource("/O_off.png"));
negativeOn = new ImageIcon(this.getClass().getResource("/N_on.png"));
negativeOff = new ImageIcon(this.getClass().getResource("/N_off.png"));
carryOn = loadScaledIcon("/C_on.png");
carryOff = loadScaledIcon("/C_off.png");
zeroOn = loadScaledIcon("/Z_on.png");
zeroOff = loadScaledIcon("/Z_off.png");
irqOn = loadScaledIcon("/I_on.png");
irqOff = loadScaledIcon("/I_off.png");
decimalOn = loadScaledIcon("/D_on.png");
decimalOff = loadScaledIcon("/D_off.png");
breakOn = loadScaledIcon("/B_on.png");
breakOff = loadScaledIcon("/B_off.png");
overflowOn = loadScaledIcon("/O_on.png");
overflowOff = loadScaledIcon("/O_off.png");
negativeOn = loadScaledIcon("/N_on.png");
negativeOff = loadScaledIcon("/N_off.png");
// Initialize all to off
carryFlagLabel = new JLabel(carryOff, JLabel.CENTER);
@@ -124,16 +130,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);
@@ -151,6 +156,10 @@ public class StatusPanel extends JPanel {
JLabel xLabel = makeLabel("X");
JLabel yLabel = makeLabel("Y");
// PIA/VIA labels
JLabel piaBLabel = makeLabel("PB");
JLabel piaALabel = makeLabel("PA");
statusFlagsLabel.setToolTipText("6502 Processor Status Flags");
opcodeLabel.setToolTipText("Instruction Register");
pcLabel.setToolTipText("Program Counter");
@@ -163,6 +172,9 @@ public class StatusPanel extends JPanel {
xField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
yField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
piaBField = makeTextField(LARGE_TEXT_FIELD_SIZE, false);
piaAField = makeTextField(LARGE_TEXT_FIELD_SIZE, false);
// Make fields editable
pcField.addActionListener(new ActionListener() {
@Override
@@ -242,7 +254,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);
@@ -286,6 +297,25 @@ public class StatusPanel extends JPanel {
add(xField, constraints);
constraints.gridx = 1;
add(yField, constraints);
constraints.insets = new Insets(5, 0, 0, 0);
constraints.gridx = 0;
constraints.gridwidth = 2;
constraints.gridy = 10;
add(piaBLabel, constraints);
constraints.insets = new Insets(2, 2, 2, 2);
constraints.gridy = 11;
add(piaBField, constraints);
constraints.insets = new Insets(5, 0, 0, 0);
constraints.gridy = 12;
add(piaALabel, constraints);
constraints.insets = new Insets(2, 2, 2, 2);
constraints.gridy = 13;
add(piaAField, constraints);
}
/**
@@ -295,6 +325,8 @@ public class StatusPanel extends JPanel {
Cpu cpu = machine.getCpu();
CpuState cpuState = cpu.getCpuState();
Pia pia = machine.getPia();
// Update the Processor Status Flag display
int status = cpuState.getStatusFlag();
@@ -316,6 +348,12 @@ public class StatusPanel extends JPanel {
xField.setText(cpu.getXRegisterStatus());
yField.setText(cpu.getYRegisterStatus());
// Update the Pia Variables
if (pia instanceof Via6522) {
piaBField.setText(((Via6522) pia).getPBStatus());
piaAField.setText(((Via6522) pia).getPAStatus());
}
repaint();
}
@@ -388,10 +426,12 @@ public class StatusPanel extends JPanel {
JTextField textField = new JTextField("");
textField.setAlignmentX(LEFT_ALIGNMENT);
textField.setEditable(editable);
textField.setMinimumSize(size);
textField.setMaximumSize(size);
textField.setPreferredSize(size);
textField.setBackground(Color.WHITE);
Dimension scaleSize = UIScale.scale(size);
textField.setMinimumSize(scaleSize);
textField.setMaximumSize(scaleSize);
textField.setPreferredSize(scaleSize);
return textField;
}
@@ -399,4 +439,14 @@ public class StatusPanel extends JPanel {
String val = source.getText().replaceAll("[^0-9a-fA-F]", "");
return Integer.parseInt(val, 16);
}
private ImageIcon loadScaledIcon(String resourcePath) {
ImageIcon icon = new ImageIcon(Objects.requireNonNull(this.getClass().getResource(resourcePath)));
int scaledWidth = UIScale.scale(icon.getIconWidth());
int scaledHeight = UIScale.scale(icon.getIconHeight());
Image scaleImage = icon.getImage().getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
return new ImageIcon(scaleImage);
}
}
@@ -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
+153
View File
@@ -0,0 +1,153 @@
/*
* 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 com.loomcom.symon.machines.*;
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 java.awt.*;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
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");
logger.debug("AWT Toolkit class: {}", Toolkit.getDefaultToolkit().getClass().getName());
// 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");
applyDetectedUiScale();
// 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", "Mike42" };
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 if (s != null && s.equals("Mike42")) {
return MichaelBillingtonMachine.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;
}
private static void applyDetectedUiScale() {
Double rawScale = null;
if (SystemInfo.isLinux) rawScale = X11ResourceReader.readXftDpiScale(); // ex: 1.5, 2.0, 2.5...
if (rawScale != null && rawScale > 1.0) {
// flatlaf.uiScale aceita frações — mantém a precisão original
System.setProperty("flatlaf.uiScale", rawScale + "x");
// sun.java2d.uiScale só aceita inteiros — arredonda para cima
int javaScale = (int) Math.ceil(rawScale);
//System.setProperty("sun.java2d.uiScale", String.valueOf(javaScale));
logger.debug("Detected scale {}x, applying flatlaf.uiScale={}x, sun.java2d.uiScale={}x",
rawScale, rawScale, javaScale);
} else {
logger.debug("No X11 HiDPI scale detected, using default.");
}
}
}
@@ -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();
}
}
}
}
@@ -0,0 +1,54 @@
package com.loomcom.symon.ui;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class X11ResourceReader {
private final static Logger logger = LoggerFactory.getLogger(X11ResourceReader.class.getName());
public interface X11 extends Library {
X11 INSTANCE = Native.load("X11", X11.class);
Pointer XOpenDisplay(String display);
Pointer XResourceManagerString(Pointer display);
void XCloseDisplay(Pointer display);
}
public static Double readXftDpiScale() {
Pointer display = null;
try {
display = X11.INSTANCE.XOpenDisplay(null);
if (display == null) {
return null; // não conseguimos ligar ao X server (ex: Wayland puro sem XWayland)
}
Pointer resourceString = X11.INSTANCE.XResourceManagerString(display);
if (resourceString == null) {
return null;
}
String resources = resourceString.getString(0);
for (String line : resources.split("\n")) {
if (line.startsWith("Xft.dpi:")) {
int dpi = Integer.parseInt(line.split(":")[1].trim());
return Math.round((dpi / 96.0) * 4) / 4.0;
}
}
return null;
} catch (UnsatisfiedLinkError | Exception e) {
logger.debug("Could not read X11 resource manager (libX11 unavailable?)", e);
return null;
} finally {
if (display != null) {
X11.INSTANCE.XCloseDisplay(display);
}
}
}
}
@@ -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
@@ -76,6 +76,16 @@ public class Utils {
"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7",
"F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF"};
private static final String[] BIN_CONSTANTS = new String[256];
static {
for (int i = 0; i < 256; i++) {
String bin = Integer.toBinaryString(i);
// Left-pad to 8 digits with zeros
BIN_CONSTANTS[i] = "00000000".substring(bin.length()) + bin;
}
}
/**
* Very fast 8-bit int to ASCII conversion.
*
@@ -116,4 +126,14 @@ public class Utils {
public static int address(int lowByte, int hiByte) {
return ((hiByte << 8) | lowByte) & 0xffff;
}
/**
* Very fast 8-bit int to binary conversion, with zero-padded output.
*
* @param val The unsigned 8-bit value to convert to a zero padded binary string.
* @return Eight digit, zero padded binary string.
*/
public static String byteToBin(int val) {
return BIN_CONSTANTS[val & 0xff];
}
}
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
@@ -7,7 +7,7 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
public class AciaTest6850 {
public class Acia6850Test {
private final static int CMD_STAT_REG = 0;
private final static int DATA_REG = 1;
@@ -67,7 +67,7 @@ public class AciaTest6850 {
verify(mockBus, never()).assertIrq();
// Transmission should cause IRQ
acia.txRead();
acia.txRead(true);
verify(mockBus, atLeastOnce()).assertIrq();
}
@@ -86,16 +86,88 @@ public class AciaTest6850 {
acia.write(DATA_REG, 'a');
// Transmission should cause IRQ
acia.txRead();
acia.txRead(true);
verify(mockBus, never()).assertIrq();
}
@Test
public void shouldTriggerInterruptFlagOnRxFullIfRxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Disable TX IRQ, Enable RX IRQ
acia.write(CMD_STAT_REG, 0x80);
acia.rxWrite('a');
// Receive should cause IRQ flag to be set
assertEquals(0x80, acia.read(0x0000, true) & 0x80);
}
@Test
public void shouldNotTriggerInterruptFlagOnRxFullIfRxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(CMD_STAT_REG, 0x00);
acia.rxWrite('a');
// Receive should not cause IRQ flag to be set
assertEquals(0x00, acia.read(0x0000, true) & 0x80);
}
@Test
public void shouldTriggerInterruptFlagOnTxEmptyIfTxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Enable TX IRQ, Disable RX IRQ
acia.write(CMD_STAT_REG, 0x20);
// Write data
acia.write(1, 'a');
verify(mockBus, never()).assertIrq();
// Transmission should cause IRQ flag to be set
acia.txRead(true);
assertEquals(0x80, acia.read(0x0000, true) & 0x80);
}
@Test
public void shouldNotTriggerInterruptFlagOnTxEmptyIfTxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(CMD_STAT_REG, 0x02);
// Write data
acia.write(DATA_REG, 'a');
// Transmission should not cause IRQ flag to be set
acia.txRead(true);
assertEquals(0x00, acia.read(0x0000, true) & 0x80);
}
@Test
public void newAciaShouldHaveTxEmptyStatus() throws Exception {
Acia acia = newAcia();
assertEquals(0x02, acia.read(CMD_STAT_REG) & 0x02);
assertEquals(0x02, acia.read(CMD_STAT_REG, true) & 0x02);
}
@Test
@@ -103,7 +175,7 @@ public class AciaTest6850 {
Acia acia = newAcia();
acia.txWrite('a');
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x02);
assertEquals(0x00, acia.read(CMD_STAT_REG, true) & 0x02);
}
@Test
@@ -112,7 +184,7 @@ public class AciaTest6850 {
acia.rxWrite('a');
assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x01);
assertEquals(0x01, acia.read(CMD_STAT_REG, true) & 0x01);
}
@Test
@@ -123,12 +195,30 @@ public class AciaTest6850 {
acia.rxWrite('a');
acia.txWrite('b');
assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x03);
assertEquals(0x01, acia.read(CMD_STAT_REG, true) & 0x03);
}
@Test
public void aciaShouldOverrunAndReadShouldReset()
throws Exception {
Acia acia = newAcia();
// overrun ACIA
acia.rxWrite('a');
acia.rxWrite('b');
assertEquals(0x20, acia.read(CMD_STAT_REG, true) & 0x20);
// read should reset
acia.rxRead(true);
assertEquals(0x00, acia.read(CMD_STAT_REG, true) & 0x20);
}
@Test
public void aciaShouldOverrunAndMemoryWindowReadShouldNotReset()
throws Exception {
Acia acia = newAcia();
@@ -136,11 +226,11 @@ public class AciaTest6850 {
acia.rxWrite('a');
acia.rxWrite('b');
assertEquals(0x20, acia.read(CMD_STAT_REG) & 0x20);
assertEquals(0x20, acia.read(CMD_STAT_REG, true) & 0x20);
// read should reset
acia.rxRead();
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x20);
// memory window read should not reset
acia.rxRead(false);
assertEquals(0x20, acia.read(CMD_STAT_REG, true) & 0x20);
}
@@ -149,15 +239,15 @@ public class AciaTest6850 {
throws Exception {
Acia acia = newAcia();
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x01);
assertEquals(0x00, acia.read(CMD_STAT_REG, true) & 0x01);
acia.rxWrite('a');
assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x01);
assertEquals(0x01, acia.read(CMD_STAT_REG, true) & 0x01);
acia.rxRead();
acia.rxRead(true);
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x01);
assertEquals(0x00, acia.read(CMD_STAT_REG, true) & 0x01);
}
}
+192 -14
View File
@@ -55,7 +55,7 @@ public class AciaTest {
verify(mockBus, never()).assertIrq();
// Transmission should cause IRQ
acia.txRead();
acia.txRead(true);
verify(mockBus, atLeastOnce()).assertIrq();
}
@@ -73,17 +73,89 @@ public class AciaTest {
// Write data
acia.write(0, 'a');
// Transmission should cause IRQ
acia.txRead();
// Transmission should not cause IRQ
acia.txRead(true);
verify(mockBus, never()).assertIrq();
}
@Test
public void shouldTriggerInterruptFlagOnRxFullIfRxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Enable RX IRQ
acia.write(2, 0x00);
acia.rxWrite('a');
// Receive should cause IRQ flag to be set
assertEquals(0x80, acia.read(0x0001, true) & 0x80);
}
@Test
public void shouldNotTriggerInterruptFlagOnRxFullIfRxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(2, 0x02);
acia.rxWrite('a');
// Receive should not cause IRQ flag to be set
assertEquals(0x00, acia.read(0x0001, true) & 0x80);
}
@Test
public void shouldTriggerInterruptFlagOnTxEmptyIfTxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Enable TX IRQ, Disable RX IRQ
acia.write(2, 0x06);
// Write data
acia.write(0, 'a');
verify(mockBus, never()).assertIrq();
// Transmission should cause IRQ flag to be set
acia.txRead(true);
assertEquals(0x80, acia.read(0x0001, true) & 0x80);
}
@Test
public void shouldNotTriggerInterruptFlagOnTxEmptyIfTxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(2, 0x02);
// Write data
acia.write(0, 'a');
// Transmission should not cause IRQ flag to be set
acia.txRead(true);
assertEquals(0x00, acia.read(0x0001, true) & 0x80);
}
@Test
public void newAciaShouldHaveTxEmptyStatus() throws Exception {
Acia acia = new Acia6551(0x000);
assertEquals(0x10, acia.read(0x0001));
assertEquals(0x10, acia.read(0x0001, true));
}
@Test
@@ -91,7 +163,7 @@ public class AciaTest {
Acia acia = new Acia6551(0x000);
acia.txWrite('a');
assertEquals(0x00, acia.read(0x0001));
assertEquals(0x00, acia.read(0x0001, true));
}
@Test
@@ -99,7 +171,7 @@ public class AciaTest {
Acia acia = new Acia6551(0x000);
acia.rxWrite('a');
assertEquals(0x18, acia.read(0x0001));
assertEquals(0x18, acia.read(0x0001, true));
}
@Test
@@ -110,7 +182,7 @@ public class AciaTest {
acia.rxWrite('a');
acia.txWrite('b');
assertEquals(0x08, acia.read(0x0001));
assertEquals(0x08, acia.read(0x0001, true));
}
@Test
@@ -123,14 +195,31 @@ public class AciaTest {
acia.rxWrite('a');
acia.rxWrite('b');
assertEquals(0x04, acia.read(0x0001) & 0x04);
assertEquals(0x04, acia.read(0x0001, true) & 0x04);
// read should reset
acia.rxRead();
assertEquals(0x00, acia.read(0x0001) & 0x04);
acia.rxRead(true);
assertEquals(0x00, acia.read(0x0001, true) & 0x04);
}
@Test
public void aciaShouldOverrunAndMemoryWindowReadShouldNotReset()
throws Exception {
Acia acia = new Acia6551(0x0000);
// overrun ACIA
acia.rxWrite('a');
acia.rxWrite('b');
assertEquals(0x04, acia.read(0x0001, true) & 0x04);
// memory window read should not reset
acia.rxRead(false);
assertEquals(0x04, acia.read(0x0001, true) & 0x04);
}
@Test
public void readingBuffersShouldResetStatus()
@@ -141,11 +230,100 @@ public class AciaTest {
acia.txWrite('b');
assertEquals(0x08, acia.read(0x0001));
assertEquals(0x08, acia.read(0x0001, true));
assertEquals('a', acia.rxRead());
assertEquals('b', acia.txRead());
assertEquals('a', acia.rxRead(true));
assertEquals('b', acia.txRead(true));
assertEquals(0x10, acia.read(0x0001));
assertEquals(0x10, acia.read(0x0001, true));
}
@Test
public void A()
throws Exception {
Acia acia = new Acia6551(0x0000);
acia.rxWrite('a');
acia.txWrite('b');
assertEquals(0x08, acia.read(0x0001, true));
assertEquals('a', acia.rxRead(false));
assertEquals('b', acia.txRead(false));
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));
}
}
@@ -0,0 +1,125 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import junit.framework.*;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test for Zero Page Indirect addressing mode, found on some instructions
* in the 65C02 and 65816
*/
public class Cpu65C02AbsoluteModeTest extends TestCase {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
private void makeCmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.CMOS_6502);
}
private void makeNmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.NMOS_6502);
}
private void makeCpu(InstructionTable.CpuBehavior behavior) throws Exception {
this.cpu = new Cpu(behavior);
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
}
public void test_STZ() throws Exception {
makeCmosCpu();
bus.write(0x0010,0xff);
bus.loadProgram(0x9c, 0x10, 0x00); // STZ Absolute
// Test STZ Absolute ($0010)
assertEquals(0xff, bus.read(0x0010, true));
cpu.step();
assertEquals(0x00, bus.read(0x0010, true));
}
public void test_STZRequiresCmosCpu() throws Exception {
makeNmosCpu();
bus.write(0x0010,0xff);
bus.loadProgram(0x9c, 0x10, 0x00); // STZ Absolute
// Test STZ Absolute ($0010)
assertEquals(0xff, bus.read(0x0010, true));
cpu.step();
assertEquals(0xff, bus.read(0x0010, true));
}
public void test_TSB() throws Exception {
makeCmosCpu();
bus.loadProgram(0x0c, 0x10, 0x00); // 65C02 TSB Absolute $0010
bus.write(0x10, 0x01);
cpu.setAccumulator(0x01);
cpu.step();
assertEquals(0x01,bus.read(0x10,true)); // 0x01 & 0x01 = 0x01
assertFalse(cpu.getZeroFlag());
cpu.reset();
cpu.setAccumulator(0x02);
cpu.step();
assertEquals(0x03,bus.read(0x0010,true));
assertTrue(cpu.getZeroFlag());
}
public void test_TSBRequiresCmosCpu() throws Exception {
makeNmosCpu();
bus.loadProgram(0x0c, 0x10, 0x00); // 65C02 TSB Absolute $0010
bus.write(0x10, 0x00);
cpu.setAccumulator(0x01);
cpu.step();
assertEquals(0x00,bus.read(0x0010,true));
}
public void test_TRB() throws Exception {
makeCmosCpu();
bus.loadProgram(0x1c, 0x00, 0x01); // 65C02 TRB Absolute $0010
bus.write(0x0100, 0x03);
cpu.setAccumulator(0x01);
cpu.step();
assertEquals(0x02,bus.read(0x0100,true)); // $03 &= ~($01) = $02
assertFalse(cpu.getZeroFlag()); // Z = !(A & M)
cpu.reset();
cpu.setAccumulator(0x01);
cpu.step();
assertEquals(0x02,bus.read(0x0100,true)); // $02 &= ~($01) = $02
assertTrue(cpu.getZeroFlag()); // Z = !(A & M)
}
public void test_TRBRequiresCmosCpu() throws Exception {
makeNmosCpu();
bus.loadProgram(0x1c, 0x00, 0x01); // 65C02 TRB Absolute $0010
bus.write(0x0100, 0xff);
cpu.setAccumulator(0x01);
cpu.step();
assertEquals(0xff,bus.read(0x0100,true));
}
}
@@ -0,0 +1,87 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import junit.framework.*;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test for Zero Page Indirect addressing mode, found on some instructions
* in the 65C02 and 65816
*/
public class Cpu65C02AbsoluteXModeTest extends TestCase {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
private void makeCmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.CMOS_6502);
}
private void makeNmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.NMOS_6502);
}
private void makeCpu(InstructionTable.CpuBehavior behavior) throws Exception {
this.cpu = new Cpu(behavior);
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
}
public void test_STZ() throws Exception {
makeCmosCpu();
bus.write(0x0011,0xff);
bus.loadProgram(0x9e, 0x10, 0x00); // STZ Absolute,X
// Test STZ Absolute,X ($0011)
cpu.setXRegister(0x01);
assertEquals(0xff, bus.read(0x0011, true));
cpu.step();
assertEquals(0x00, bus.read(0x0011, true));
}
public void test_STZRequiresCmosCpu() throws Exception {
makeNmosCpu();
bus.write(0x0011,0xff);
bus.loadProgram(0x9e, 0x10, 0x00); // STZ Absolute,X
// Test STZ Absolute,X ($0011)
cpu.setXRegister(0x01);
assertEquals(0xff, bus.read(0x0011, true));
cpu.step();
assertEquals(0xff, bus.read(0x0011, true));
}
public void test_JMP_Indirect_Absolute_X () throws Exception {
makeCmosCpu();
bus.write(0x304,00);
bus.write(0x0305,04);
bus.loadProgram(0x7c, 0x00, 0x03);
cpu.setXRegister(0x04);
cpu.step();
assertEquals(0x0400,cpu.getProgramCounter());
}
public void test_JMP_Indirect_Absolute_XRequiresCmosCpu () throws Exception {
makeNmosCpu();
bus.write(0x304,00);
bus.write(0x0305,04);
bus.loadProgram(0x7c, 0x00, 0x03);
cpu.setXRegister(0x04);
cpu.step();
assertEquals(0x0203,cpu.getProgramCounter());
}
}
@@ -0,0 +1,72 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import junit.framework.*;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test for Zero Page Indirect addressing mode, found on some instructions
* in the 65C02 and 65816
*/
public class Cpu65C02ImmediateModeTest extends TestCase {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
private void makeCmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.CMOS_6502);
}
private void makeNmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.NMOS_6502);
}
private void makeCpu(InstructionTable.CpuBehavior behavior) throws Exception {
this.cpu = new Cpu(behavior);
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
}
public void test_BIT_Immediate() throws Exception {
makeCmosCpu();
bus.loadProgram(0x89, 0xF1); // 65C02 BIT #$01
cpu.setAccumulator(0x02);
cpu.step();
assertTrue(cpu.getZeroFlag()); // #$02 & #$F1 = 0
assertEquals(0x02,cpu.getAccumulator()); // Accumulator should not be modified
assertFalse(cpu.getNegativeFlag()); // BIT #Immediate should not set N or V Flags
assertFalse(cpu.getOverflowFlag());
cpu.reset();
cpu.setAccumulator(0x01);
cpu.step();
assertFalse(cpu.getZeroFlag()); // #$F1 & #$01 = 1
assertEquals(0x01,cpu.getAccumulator());
}
public void test_BIT_ImmediateRequiresCmosCpu() throws Exception {
makeNmosCpu();
bus.loadProgram(0x89, 0xF1); // 65C02 BIT #$01
cpu.step();
cpu.setAccumulator(0x01);
assertTrue(cpu.getZeroFlag());
assertEquals(0x01,cpu.getAccumulator());
}
}
@@ -0,0 +1,252 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import junit.framework.*;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test for Zero Page Indirect addressing mode, found on some instructions
* in the 65C02 and 65816
*/
public class Cpu65C02ImpliedModeTest extends TestCase {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
private void makeCmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.CMOS_6502);
}
private void makeNmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.NMOS_6502);
}
private void makeCpu(InstructionTable.CpuBehavior behavior) throws Exception {
this.cpu = new Cpu(behavior);
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
}
public void test_PHX() throws Exception {
makeCmosCpu();
cpu.stackPush(0x00);
cpu.setXRegister(0xff);
bus.loadProgram(0xda);
assertEquals(cpu.stackPeek(), 0x00);
cpu.step();
assertEquals(cpu.stackPeek(), 0xff);
}
public void test_PHXRequiresCmosCpu() throws Exception {
makeNmosCpu();
cpu.stackPush(0x00);
cpu.setXRegister(0xff);
bus.loadProgram(0xda);
assertEquals(cpu.stackPeek(), 0x00);
cpu.step();
assertEquals(cpu.stackPeek(), 0x00);
}
public void test_PLX() throws Exception {
makeCmosCpu();
cpu.stackPush(0xff);
cpu.setXRegister(0x00);
bus.loadProgram(0xfa);
assertEquals(0x00, cpu.getXRegister());
cpu.step();
assertEquals(0xff, cpu.getXRegister());
}
public void test_PLXRequiresCmosCpu() throws Exception {
makeNmosCpu();
cpu.stackPush(0xff);
cpu.setXRegister(0x00);
bus.loadProgram(0xfa);
assertEquals(0x00, cpu.getXRegister());
cpu.step();
assertEquals(0x00, cpu.getXRegister());
}
public void test_PHY() throws Exception {
makeCmosCpu();
cpu.stackPush(0x00);
cpu.setYRegister(0xff);
bus.loadProgram(0x5a);
assertEquals(0x00, cpu.stackPeek());
cpu.step();
assertEquals(0xff, cpu.stackPeek());
}
public void test_PHYRequiresCmosCpu() throws Exception {
makeNmosCpu();
cpu.stackPush(0x00);
cpu.setYRegister(0xff);
bus.loadProgram(0x5a);
assertEquals(0x00, cpu.stackPeek());
cpu.step();
assertEquals(0x00, cpu.stackPeek());
}
public void test_PLY() throws Exception {
makeCmosCpu();
cpu.stackPush(0xff);
cpu.setYRegister(0x00);
bus.loadProgram(0x7a);
assertEquals(0x00, cpu.getYRegister());
cpu.step();
assertEquals(0xff, cpu.getYRegister());
}
public void test_PLYRequiresCmosCpu() throws Exception {
makeNmosCpu();
cpu.stackPush(0xff);
cpu.setYRegister(0x00);
bus.loadProgram(0x7a);
assertEquals(0x00, cpu.getYRegister());
cpu.step();
assertEquals(0x00, cpu.getYRegister());
}
public void test_INC_A() throws Exception {
makeCmosCpu();
cpu.setAccumulator(0x10);
bus.loadProgram(0x1a);
cpu.step();
assertEquals(0x11, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// Incrementing to 0 should set Zero Flag
cpu.reset();
cpu.setAccumulator(0xff);
cpu.step();
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// Should set Negative Flag
cpu.reset();
cpu.setAccumulator(0x7F);
cpu.step();
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getZeroFlag());
}
public void test_INC_ARequiresCmosCpu() throws Exception {
makeNmosCpu();
cpu.setAccumulator(0x10);
bus.loadProgram(0x1a);
cpu.step();
assertEquals(0x10, cpu.getAccumulator());
}
public void test_DEC_A() throws Exception {
makeCmosCpu();
cpu.setAccumulator(0x10);
bus.loadProgram(0x3a);
cpu.step();
assertEquals(0x0F, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// Decrementing to 0 should set Zero Flag
cpu.reset();
cpu.setAccumulator(0x01);
cpu.step();
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// Should set Negative Flag
cpu.reset();
cpu.setAccumulator(0x00);
cpu.step();
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getZeroFlag());
}
public void test_DEC_ARequiresCmosCpu() throws Exception {
makeNmosCpu();
cpu.setAccumulator(0x10);
bus.loadProgram(0x3a);
cpu.step();
assertEquals(0x10, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
public void test_BRK_clearsDecimalModeFlag() throws Exception {
makeCmosCpu();
cpu.setDecimalModeFlag();
assertEquals(0x00, cpu.stackPeek());
assertFalse(cpu.getBreakFlag());
assertTrue(cpu.getDecimalModeFlag());
assertEquals(0x0200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
// Set the IRQ vector
bus.write(0xffff, 0x12);
bus.write(0xfffe, 0x34);
bus.loadProgram(0xea, // NOP
0xea, // NOP
0xea, // NOP
0x00, // BRK
0xea, // NOP
0xea); // NOP
cpu.step(3); // Three NOP instructions
assertEquals(0x203, cpu.getProgramCounter());
assertTrue(cpu.getDecimalModeFlag());
cpu.step(); // Triggers the BRK
// Was at PC = 0x204. PC+1 should now be on the stack
assertEquals(0x02, bus.read(0x1ff, true)); // PC high byte
assertEquals(0x05, bus.read(0x1fe, true)); // PC low byte
// Interrupt vector held 0x1234, so we should be there.
assertEquals(0x1234, cpu.getProgramCounter());
assertEquals(0xfc, cpu.getStackPointer());
// B and I flags should have been set on P
assertTrue(cpu.getBreakFlag());
assertFalse(cpu.getDecimalModeFlag());
}
}
@@ -0,0 +1,372 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import junit.framework.*;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test for Zero Page Indirect addressing mode, found on some instructions
* in the 65C02 and 65816
*/
public class Cpu65C02ZeroPageModeTest extends TestCase {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
private void makeCmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.CMOS_6502);
}
private void makeNmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.NMOS_6502);
}
private void makeCpu(InstructionTable.CpuBehavior behavior) throws Exception {
this.cpu = new Cpu(behavior);
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
}
public void test_STZ() throws Exception {
makeCmosCpu();
bus.write(0x0000,0xff);
bus.loadProgram(0x64,0x00); // STZ Zero Page $00
// Test STZ Zero Page
assertEquals(0xff,bus.read(0x00, true));
cpu.step();
assertEquals(0x00,bus.read(0x00, true));
}
public void test_STZRequiresCmosCpu() throws Exception {
makeNmosCpu();
bus.write(0x0000,0xff);
bus.loadProgram(0x64,0x00); // STZ Zero Page $00
// Test STZ Zero Page
assertEquals(0xff,bus.read(0x00, true));
cpu.step();
assertEquals(0xff,bus.read(0x00, true));
}
public void test_SMB() throws Exception {
makeCmosCpu();
bus.loadProgram(0x87,0x01, // SMB0 $01
0x97,0x01, // SMB1 $01
0xa7,0x01, // SMB2 $01
0xb7,0x01, // SMB3 $01
0xc7,0x01, // SMB4 $01
0xd7,0x01, // SMB5 $01
0xe7,0x01, // SMB6 $01
0xf7,0x01); // SMB7 $01
// SMB0
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(1 << 0,bus.read(0x0001, true));
// SMB1
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(1 << 1,bus.read(0x0001, true));
// SMB2
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(1 << 2,bus.read(0x0001, true));
// SMB3
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(1 << 3,bus.read(0x0001, true));
// SMB4
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(1 << 4,bus.read(0x0001, true));
// SMB5
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(1 << 5,bus.read(0x0001, true));
// SMB6
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(1 << 6,bus.read(0x0001, true));
// SMB7
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(1 << 7,bus.read(0x0001, true));
}
public void test_SMBRequiresCmosCpu() throws Exception {
makeNmosCpu();
bus.loadProgram(0x87,0x01, // SMB0 $01
0x97,0x01, // SMB1 $01
0xa7,0x01, // SMB2 $01
0xb7,0x01, // SMB3 $01
0xc7,0x01, // SMB4 $01
0xd7,0x01, // SMB5 $01
0xe7,0x01, // SMB6 $01
0xf7,0x01); // SMB7 $01
// SMB0
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(0x00,bus.read(0x0001, true));
// SMB1
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(0x00,bus.read(0x0001, true));
// SMB2
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(0x00,bus.read(0x0001, true));
// SMB3
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(0x00,bus.read(0x0001, true));
// SMB4
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(0x00,bus.read(0x0001, true));
// SMB5
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(0x00,bus.read(0x0001, true));
// SMB6
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(0x00,bus.read(0x0001, true));
// SMB7
bus.write(0x01,0x00);
assertEquals(0x00,bus.read(0x0001, true));
cpu.step();
assertEquals(0x00,bus.read(0x0001, true));
}
public void test_RMB() throws Exception {
makeCmosCpu();
bus.loadProgram(0x07,0x01, // SMB0 $01
0x17,0x01, // SMB1 $01
0x27,0x01, // SMB2 $01
0x37,0x01, // SMB3 $01
0x47,0x01, // SMB4 $01
0x57,0x01, // SMB5 $01
0x67,0x01, // SMB6 $01
0x77,0x01); // SMB7 $01
// RMB0
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xfe,bus.read(0x0001, true));
// RMB1
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xfd,bus.read(0x0001, true));
// RMB2
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xfb,bus.read(0x0001, true));
// RMB3
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xf7,bus.read(0x0001, true));
// RMB4
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xef,bus.read(0x0001, true));
// RMB5
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xdf,bus.read(0x0001, true));
// RMB6
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xbf,bus.read(0x0001, true));
// RMB7
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0x7f,bus.read(0x0001, true));
}
public void test_RMBRequiresCmosCpu() throws Exception {
makeNmosCpu();
bus.loadProgram(0x07,0x01, // SMB0 $01
0x17,0x01, // SMB1 $01
0x27,0x01, // SMB2 $01
0x37,0x01, // SMB3 $01
0x47,0x01, // SMB4 $01
0x57,0x01, // SMB5 $01
0x67,0x01, // SMB6 $01
0x77,0x01); // SMB7 $01
// RMB0
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xff,bus.read(0x0001, true));
// RMB1
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xff,bus.read(0x0001, true));
// RMB2
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xff,bus.read(0x0001, true));
// RMB3
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xff,bus.read(0x0001, true));
// RMB4
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xff,bus.read(0x0001, true));
// RMB5
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xff,bus.read(0x0001, true));
// RMB6
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xff,bus.read(0x0001, true));
// RMB7
bus.write(0x01,0xff);
assertEquals(0xff,bus.read(0x0001, true));
cpu.step();
assertEquals(0xff,bus.read(0x0001, true));
}
public void test_TSB() throws Exception {
makeCmosCpu();
bus.loadProgram(0x04, 0x10); // 65C02 TSB Zero Page $10
bus.write(0x10, 0x01);
cpu.setAccumulator(0x03);
cpu.step();
assertEquals(0x03,bus.read(0x10,true));
assertFalse(cpu.getZeroFlag());
cpu.reset();
bus.write(0x10, 0x01);
cpu.setAccumulator(0x02);
cpu.step();
assertEquals(0x03,bus.read(0x10,true));
assertTrue(cpu.getZeroFlag());
}
public void test_TSBRequiresCmosCpu() throws Exception {
makeNmosCpu();
bus.loadProgram(0x04, 0x10); // 65C02 TSB Zero Page $10
bus.write(0x10, 0x01);
cpu.setAccumulator(0x03);
cpu.step();
assertEquals(0x01,bus.read(0x10,true));
}
public void test_TRB() throws Exception {
makeCmosCpu();
cpu.reset();
bus.loadProgram(0x14, 0x40); // 65C02 TRB Zero Page $40
bus.write(0x0040, 0xff);
cpu.setAccumulator(0x01);
cpu.step();
assertEquals(0xfe,bus.read(0x0040,true)); // $03 &= ~($01) = $02
assertFalse(cpu.getZeroFlag()); // Z = !(A & M)
cpu.reset();
cpu.setAccumulator(0x01);
cpu.step();
assertEquals(0xfe,bus.read(0x0040,true)); // $02 &= ~($01) = $02
assertTrue(cpu.getZeroFlag()); // Z = !(A & M)
}
public void test_TRBRequiresCmosCpu() throws Exception {
makeNmosCpu();
cpu.reset();
bus.loadProgram(0x14, 0x40); // 65C02 TRB Zero Page $40
bus.write(0x0040, 0xff);
cpu.setAccumulator(0x01);
cpu.step();
assertEquals(0xff,bus.read(0x0040,true)); // $03 &= ~($01) = $02
}
}
@@ -0,0 +1,585 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import junit.framework.*;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test for Zero Page Indirect addressing mode, found on some instructions
* in the 65C02 and 65816
*/
public class Cpu65C02ZeroPageRelativeTest extends TestCase {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
private void makeCmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.CMOS_6502);
}
private void makeNmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.NMOS_6502);
}
private void makeCpu(InstructionTable.CpuBehavior behavior) throws Exception {
this.cpu = new Cpu(behavior);
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
}
public void test_BRA() throws Exception {
makeCmosCpu();
// Positive Offset
bus.loadProgram(0x80, 0x05); // 65C02 BRA $05 ; *=$0202+$05 ($0207)
cpu.step();
assertEquals(0x207, cpu.getProgramCounter());
// Negative Offset
cpu.reset();
bus.loadProgram(0x80, 0xfb); // 65C02 BRA $fb ; *=$0202-$05 ($01fd)
cpu.step();
assertEquals(0x1fd, cpu.getProgramCounter());
}
public void test_BRArequiresCmosCpu() throws Exception {
makeNmosCpu();
// Should be a NOOP on NMOS and end up at $0202
// Positive Offset
bus.loadProgram(0x80, 0x05); // BRA $05 ; *=$0202+$05 ($0207)
cpu.step();
assertEquals(0x202, cpu.getProgramCounter());
// Negative Offset
cpu.reset();
bus.loadProgram(0x80, 0xfb); // BRA $fb ; *=$0202-$05 ($01fd)
cpu.step();
assertEquals(0x202, cpu.getProgramCounter());
}
public void test_BBR() throws Exception {
makeCmosCpu();
/* BBR0 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0xff);
bus.loadProgram(0x0f, 0x0a, 0x05); // 65C02 BBR0 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0xfe);
bus.loadProgram(0x0f, 0x0a, 0x05); // 65C02 BBR0 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0xfe);
bus.loadProgram(0x0f, 0x0a, 0xfb); // 65C02 BBR0 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBR1 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0xff);
bus.loadProgram(0x1f, 0x0a, 0x05); // 65C02 BBR1 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0xfd);
bus.loadProgram(0x1f, 0x0a, 0x05); // 65C02 BBR1 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0xfd);
bus.loadProgram(0x1f, 0x0a, 0xfb); // 65C02 BBR1 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBR2 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0xff);
bus.loadProgram(0x2f, 0x0a, 0x05); // 65C02 BBR2 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0xfb);
bus.loadProgram(0x2f, 0x0a, 0x05); // 65C02 BBR2 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0xfb);
bus.loadProgram(0x2f, 0x0a, 0xfb); // 65C02 BBR2 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBR3 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0xff);
bus.loadProgram(0x3f, 0x0a, 0x05); // 65C02 BBR3 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0xf7);
bus.loadProgram(0x3f, 0x0a, 0x05); // 65C02 BBR3 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0xf7);
bus.loadProgram(0x3f, 0x0a, 0xfb); // 65C02 BBR3 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBR4 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0xff);
bus.loadProgram(0x4f, 0x0a, 0x05); // 65C02 BBR4 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0xef);
bus.loadProgram(0x4f, 0x0a, 0x05); // 65C02 BBR4 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0xef);
bus.loadProgram(0x4f, 0x0a, 0xfb); // 65C02 BBR4 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBR5 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0xff);
bus.loadProgram(0x5f, 0x0a, 0x05); // 65C02 BBR5 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0xdf);
bus.loadProgram(0x5f, 0x0a, 0x05); // 65C02 BBR5 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0xdf);
bus.loadProgram(0x5f, 0x0a, 0xfb); // 65C02 BBR5 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBR6 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0xff);
bus.loadProgram(0x6f, 0x0a, 0x05); // 65C02 BBR6 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0xbf);
bus.loadProgram(0x6f, 0x0a, 0x05); // 65C02 BBR6 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0xbf);
bus.loadProgram(0x6f, 0x0a, 0xfb); // 65C02 BBR6 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBR7 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0xff);
bus.loadProgram(0x7f, 0x0a, 0x05); // 65C02 BBR7 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0x7f);
bus.loadProgram(0x7f, 0x0a, 0x05); // 65C02 BBR7 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0x7f);
bus.loadProgram(0x7f, 0x0a, 0xfb); // 65C02 BBR7 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
}
public void test_BBRNeedsCmosCpu() throws Exception {
makeNmosCpu();
/* BBR0 */
bus.write(0x0a,0xfe);
bus.loadProgram(0x0f, 0x0a, 0x05); // 65C02 BBR0 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBR1 */
cpu.reset();
bus.write(0x0a,0xfd);
bus.loadProgram(0x1f, 0x0a, 0x05); // 65C02 BBR1 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBR2 */
cpu.reset();
bus.write(0x0a,0xfb);
bus.loadProgram(0x2f, 0x0a, 0x05); // 65C02 BBR2 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBR3 */
cpu.reset();
bus.write(0x0a,0xf7);
bus.loadProgram(0x3f, 0x0a, 0x05); // 65C02 BBR3 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBR4 */
cpu.reset();
bus.write(0x0a,0xef);
bus.loadProgram(0x4f, 0x0a, 0x05); // 65C02 BBR4 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBR5 */
cpu.reset();
bus.write(0x0a,0xdf);
bus.loadProgram(0x5f, 0x0a, 0x05); // 65C02 BBR5 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBR6 */
cpu.reset();
bus.write(0x0a,0xbf);
bus.loadProgram(0x6f, 0x0a, 0x05); // 65C02 BBR6 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBR7 */
cpu.reset();
bus.write(0x0a,0x7f);
bus.loadProgram(0x7f, 0x0a, 0x05); // 65C02 BBR7 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
}
public void test_BBS() throws Exception {
makeCmosCpu();
/* BBS0 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0x00);
bus.loadProgram(0x8f, 0x0a, 0x05); // 65C02 BBS0 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0x01);
bus.loadProgram(0x8f, 0x0a, 0x05); // 65C02 BBS0 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0x01);
bus.loadProgram(0x8f, 0x0a, 0xfb); // 65C02 BBS0 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBS1 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0x00);
bus.loadProgram(0x9f, 0x00, 0x05); // 65C02 BBS1 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0x02);
bus.loadProgram(0x9f, 0x0a, 0x05); // 65C02 BBS1 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0x02);
bus.loadProgram(0x9f, 0x0a, 0xfb); // 65C02 BBS1 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBS2 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0x00);
bus.loadProgram(0xaf, 0x0a, 0x05); // 65C02 BBS2 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0x04);
bus.loadProgram(0xaf, 0x0a, 0x05); // 65C02 BBS2 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0x04);
bus.loadProgram(0xaf, 0x0a, 0xfb); // 65C02 BBS2 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBS3 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0x00);
bus.loadProgram(0xbf, 0x0a, 0x05); // 65C02 BBS3 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0x08);
bus.loadProgram(0xbf, 0x0a, 0x05); // 65C02 BBS3 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0x08);
bus.loadProgram(0xbf, 0x0a, 0xfb); // 65C02 BBS3 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBS4 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0x00);
bus.loadProgram(0xcf, 0x0a, 0x05); // 65C02 BBS4 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0x10);
bus.loadProgram(0xcf, 0x0a, 0x05); // 65C02 BBS4 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0x10);
bus.loadProgram(0xcf, 0x0a, 0xfb); // 65C02 BBS4 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBS5 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0x00);
bus.loadProgram(0xdf, 0x0a, 0x05); // 65C02 BBS5 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0x20);
bus.loadProgram(0xdf, 0x0a, 0x05); // 65C02 BBS5 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0x20);
bus.loadProgram(0xdf, 0x0a, 0xfb); // 65C02 BBS5 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBS6 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0x00);
bus.loadProgram(0xef, 0x0a, 0x05); // 65C02 BBS6 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0x40);
bus.loadProgram(0xef, 0x0a, 0x05); // 65C02 BBS6 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0x40);
bus.loadProgram(0xef, 0x0a, 0xfb); // 65C02 BBS6 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
/* BBS7 */
// Positive Offset
cpu.reset();
bus.write(0x0a,0x00);
bus.loadProgram(0xff, 0x0a, 0x05); // 65C02 BBS7 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter()); // Shouldn't have branched
cpu.reset();
bus.write(0x0a,0x80);
bus.loadProgram(0xff, 0x0a, 0x05); // 65C02 BBS7 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x208, cpu.getProgramCounter()); // Should have branched
// Negative Offset
cpu.reset();
bus.write(0x0a,0x80);
bus.loadProgram(0xff, 0x0a, 0xfb); // 65C02 BBS7 $00 $fb ; *=$0203-$05 ($01fe)
cpu.step();
assertEquals(0x1fe, cpu.getProgramCounter());
}
public void test_BBSNeedsCmosCpu() throws Exception {
makeNmosCpu();
/* BBS0 */
cpu.reset();
bus.write(0x0a,0x01);
bus.loadProgram(0x8f, 0x0a, 0x05); // 65C02 BBS0 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBS1 */
cpu.reset();
bus.write(0x0a,0x02);
bus.loadProgram(0x9f, 0x0a, 0x05); // 65C02 BBS1 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBS2 */
cpu.reset();
bus.write(0x0a,0x04);
bus.loadProgram(0xaf, 0x0a, 0x05); // 65C02 BBS2 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBS3 */
cpu.reset();
bus.write(0x0a,0x08);
bus.loadProgram(0xbf, 0x0a, 0x05); // 65C02 BBS3 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBS4 */
cpu.reset();
bus.write(0x0a,0x10);
bus.loadProgram(0xcf, 0x0a, 0x05); // 65C02 BBS4 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBS5 */
cpu.reset();
bus.write(0x0a,0x20);
bus.loadProgram(0xdf, 0x0a, 0x05); // 65C02 BBS5 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBS6 */
cpu.reset();
bus.write(0x0a,0x40);
bus.loadProgram(0xef, 0x0a, 0x05); // 65C02 BBS6 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
/* BBS7 */
cpu.reset();
bus.write(0x0a,0x80);
bus.loadProgram(0xff, 0x0a, 0x05); // 65C02 BBS7 $00 $05 ; *=$0202+$05 ($0208)
cpu.step();
assertEquals(0x203, cpu.getProgramCounter());
}
}
@@ -0,0 +1,69 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import junit.framework.*;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test for Zero Page Indirect addressing mode, found on some instructions
* in the 65C02 and 65816
*/
public class Cpu65C02ZeroPageXModeTest extends TestCase {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
private void makeCmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.CMOS_6502);
}
private void makeNmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.NMOS_6502);
}
private void makeCpu(InstructionTable.CpuBehavior behavior) throws Exception {
this.cpu = new Cpu(behavior);
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
}
public void test_STZ() throws Exception {
makeCmosCpu();
bus.write(0x0002,0xff);
bus.loadProgram(0x74,0x01); // STZ Zero Page,X $01
// Test STZ Zero Page,X ($01+1)
assertEquals(0xff,bus.read(0x02, true));
cpu.setXRegister(0x01);
cpu.step();
assertEquals(0x00,bus.read(0x02, true));
}
public void test_STZRequiresCmosCpu() throws Exception {
makeNmosCpu();
bus.write(0x0002,0xff);
bus.loadProgram(0x74,0x01); // STZ Zero Page,X $01
// Test STZ Zero Page,X ($01+1)
assertEquals(0xff,bus.read(0x02, true));
cpu.setXRegister(0x01);
cpu.step();
assertEquals(0xff,bus.read(0x02, true));
}
}
@@ -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());
}
/*
@@ -121,31 +121,31 @@ public class CpuAbsoluteModeTest extends TestCase {
0x0e, 0x04, 0x12); // ASL $1204
cpu.step();
assertEquals(0x00, bus.read(0x7f00));
assertEquals(0x00, bus.read(0x7f00, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, bus.read(0x7f01));
assertEquals(0x02, bus.read(0x7f01, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x04, bus.read(0x3502));
assertEquals(0x04, bus.read(0x3502, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x88, bus.read(0x3503));
assertEquals(0x88, bus.read(0x3503, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, bus.read(0x1204));
assertEquals(0x00, bus.read(0x1204, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
@@ -165,11 +165,11 @@ public class CpuAbsoluteModeTest extends TestCase {
// Old PC-1 should be on stack (i.e.: address of third byte of the
// JSR instruction, 0x0204)
assertEquals(0x02, bus.read(0x1ff));
assertEquals(0x04, bus.read(0x1fe));
assertEquals(0x02, bus.read(0x1ff, true));
assertEquals(0x04, bus.read(0x1fe, true));
// No flags should have changed.
assertEquals(0x20, cpu.getProcessorStatus());
assertEquals(0x24, cpu.getProcessorStatus());
}
/* BIT - Bit Test - $2c */
@@ -295,61 +295,61 @@ public class CpuAbsoluteModeTest extends TestCase {
0x2e, 0x01, 0x12); // ROL $1201 (m=%10000001, c=0)
cpu.step();
assertEquals(0x00, bus.read(0x1200));
assertEquals(0x00, bus.read(0x1200, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, bus.read(0x1201));
assertEquals(0x02, bus.read(0x1201, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x05, bus.read(0x1201));
assertEquals(0x05, bus.read(0x1201, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x0a, bus.read(0x1201));
assertEquals(0x0a, bus.read(0x1201, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x14, bus.read(0x1201));
assertEquals(0x14, bus.read(0x1201, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x28, bus.read(0x1201));
assertEquals(0x28, bus.read(0x1201, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x50, bus.read(0x1201));
assertEquals(0x50, bus.read(0x1201, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0xa0, bus.read(0x1201));
assertEquals(0xa0, bus.read(0x1201, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, bus.read(0x1201));
assertEquals(0x40, bus.read(0x1201, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x81, bus.read(0x1201));
assertEquals(0x81, bus.read(0x1201, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
@@ -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 */
@@ -418,38 +418,38 @@ public class CpuAbsoluteModeTest extends TestCase {
0x4e, 0x05, 0x12); // LSR $1205
cpu.step();
assertEquals(0x00, bus.read(0x1200));
assertEquals(0x00, bus.read(0x1200, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, bus.read(0x1201));
assertEquals(0x00, bus.read(0x1201, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x01, bus.read(0x1202));
assertEquals(0x01, bus.read(0x1202, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x22, bus.read(0x1203));
assertEquals(0x22, bus.read(0x1203, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, bus.read(0x1204));
assertEquals(0x40, bus.read(0x1204, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
// Setting Carry should not affect the result.
cpu.step(2);
assertEquals(0x01, bus.read(0x1205));
assertEquals(0x01, bus.read(0x1205, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
@@ -654,61 +654,61 @@ public class CpuAbsoluteModeTest extends TestCase {
0x6e, 0x11, 0x12); // ROR $1201 (m=%00010000, c=0)
cpu.step();
assertEquals(0x00, bus.read(0x1210));
assertEquals(0x00, bus.read(0x1210, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x08, bus.read(0x1211));
assertEquals(0x08, bus.read(0x1211, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x04, bus.read(0x1211));
assertEquals(0x04, bus.read(0x1211, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, bus.read(0x1211));
assertEquals(0x02, bus.read(0x1211, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x01, bus.read(0x1211));
assertEquals(0x01, bus.read(0x1211, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, bus.read(0x1211));
assertEquals(0x00, bus.read(0x1211, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x80, bus.read(0x1211));
assertEquals(0x80, bus.read(0x1211, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, bus.read(0x1211));
assertEquals(0x40, bus.read(0x1211, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x20, bus.read(0x1211));
assertEquals(0x20, bus.read(0x1211, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x10, bus.read(0x1211));
assertEquals(0x10, bus.read(0x1211, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
@@ -720,7 +720,7 @@ public class CpuAbsoluteModeTest extends TestCase {
cpu.setYRegister(0x00);
bus.loadProgram(0x8c, 0x10, 0x12);
cpu.step();
assertEquals(0x00, bus.read(0x1210));
assertEquals(0x00, bus.read(0x1210, true));
// STY should have NO effect on flags.
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -730,7 +730,7 @@ public class CpuAbsoluteModeTest extends TestCase {
cpu.setYRegister(0x0f);
bus.loadProgram(0x8c, 0x10, 0x12);
cpu.step();
assertEquals(0x0f, bus.read(0x1210));
assertEquals(0x0f, bus.read(0x1210, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -739,7 +739,7 @@ public class CpuAbsoluteModeTest extends TestCase {
cpu.setYRegister(0x80);
bus.loadProgram(0x8c, 0x10, 0x12);
cpu.step();
assertEquals(0x80, bus.read(0x1210));
assertEquals(0x80, bus.read(0x1210, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@@ -750,7 +750,7 @@ public class CpuAbsoluteModeTest extends TestCase {
cpu.setAccumulator(0x00);
bus.loadProgram(0x8d, 0x10, 0x12);
cpu.step();
assertEquals(0x00, bus.read(0x1210));
assertEquals(0x00, bus.read(0x1210, true));
// STA should have NO effect on flags.
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -760,7 +760,7 @@ public class CpuAbsoluteModeTest extends TestCase {
cpu.setAccumulator(0x0f);
bus.loadProgram(0x8d, 0x10, 0x12);
cpu.step();
assertEquals(0x0f, bus.read(0x1210));
assertEquals(0x0f, bus.read(0x1210, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -769,7 +769,7 @@ public class CpuAbsoluteModeTest extends TestCase {
cpu.setAccumulator(0x80);
bus.loadProgram(0x8d, 0x10, 0x12);
cpu.step();
assertEquals(0x80, bus.read(0x1210));
assertEquals(0x80, bus.read(0x1210, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@@ -780,7 +780,7 @@ public class CpuAbsoluteModeTest extends TestCase {
cpu.setXRegister(0x00);
bus.loadProgram(0x8e, 0x10, 0x12);
cpu.step();
assertEquals(0x00, bus.read(0x1210));
assertEquals(0x00, bus.read(0x1210, true));
// STX should have NO effect on flags.
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -790,7 +790,7 @@ public class CpuAbsoluteModeTest extends TestCase {
cpu.setXRegister(0x0f);
bus.loadProgram(0x8e, 0x10, 0x12);
cpu.step();
assertEquals(0x0f, bus.read(0x1210));
assertEquals(0x0f, bus.read(0x1210, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -799,7 +799,7 @@ public class CpuAbsoluteModeTest extends TestCase {
cpu.setXRegister(0x80);
bus.loadProgram(0x8e, 0x10, 0x12);
cpu.step();
assertEquals(0x80, bus.read(0x1210));
assertEquals(0x80, bus.read(0x1210, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@@ -957,22 +957,22 @@ public class CpuAbsoluteModeTest extends TestCase {
0xce, 0x13, 0x12); // DEC $1213
cpu.step();
assertEquals(0xff, bus.read(0x1210));
assertEquals(0xff, bus.read(0x1210, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x00, bus.read(0x1211));
assertEquals(0x00, bus.read(0x1211, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x7f, bus.read(0x1212));
assertEquals(0x7f, bus.read(0x1212, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
cpu.step();
assertEquals(0xfe, bus.read(0x1213));
assertEquals(0xfe, bus.read(0x1213, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
}
@@ -1213,17 +1213,17 @@ public class CpuAbsoluteModeTest extends TestCase {
0xee, 0x12, 0x12); // INC $1212
cpu.step();
assertEquals(0x01, bus.read(0x1210));
assertEquals(0x01, bus.read(0x1210, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x80, bus.read(0x1211));
assertEquals(0x80, bus.read(0x1211, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x00, bus.read(0x1212));
assertEquals(0x00, bus.read(0x1212, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -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());
}
/*
@@ -117,31 +117,31 @@ public class CpuAbsoluteXModeTest extends TestCase {
0x1e, 0x04, 0x2c); // ASL $2c04,X
cpu.step();
assertEquals(0x00, bus.read(0x2c30));
assertEquals(0x00, bus.read(0x2c30, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, bus.read(0x2c31));
assertEquals(0x02, bus.read(0x2c31, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x04, bus.read(0x2c32));
assertEquals(0x04, bus.read(0x2c32, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x88, bus.read(0x2c33));
assertEquals(0x88, bus.read(0x2c33, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, bus.read(0x2c34));
assertEquals(0x00, bus.read(0x2c34, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
@@ -229,61 +229,61 @@ public class CpuAbsoluteXModeTest extends TestCase {
0x3e, 0x01, 0x10); // ROL $1001,X (m=%10000001, c=0)
cpu.step();
assertEquals(0x00, bus.read(0x1070));
assertEquals(0x00, bus.read(0x1070, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, bus.read(0x1071));
assertEquals(0x02, bus.read(0x1071, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x05, bus.read(0x1071));
assertEquals(0x05, bus.read(0x1071, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x0a, bus.read(0x1071));
assertEquals(0x0a, bus.read(0x1071, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x14, bus.read(0x1071));
assertEquals(0x14, bus.read(0x1071, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x28, bus.read(0x1071));
assertEquals(0x28, bus.read(0x1071, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x50, bus.read(0x1071));
assertEquals(0x50, bus.read(0x1071, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0xa0, bus.read(0x1071));
assertEquals(0xa0, bus.read(0x1071, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, bus.read(0x1071));
assertEquals(0x40, bus.read(0x1071, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x81, bus.read(0x1071));
assertEquals(0x81, bus.read(0x1071, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
@@ -346,38 +346,38 @@ public class CpuAbsoluteXModeTest extends TestCase {
0x5e, 0x05, 0xab); // LSR $ab05,X
cpu.step();
assertEquals(0x00, bus.read(0xab30));
assertEquals(0x00, bus.read(0xab30, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, bus.read(0xab31));
assertEquals(0x00, bus.read(0xab31, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x01, bus.read(0xab32));
assertEquals(0x01, bus.read(0xab32, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x22, bus.read(0xab33));
assertEquals(0x22, bus.read(0xab33, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, bus.read(0xab34));
assertEquals(0x40, bus.read(0xab34, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
// Setting Carry should not affect the result.
cpu.step(2);
assertEquals(0x01, bus.read(0xab35));
assertEquals(0x01, bus.read(0xab35, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
@@ -609,61 +609,61 @@ public class CpuAbsoluteXModeTest extends TestCase {
cpu.setXRegister(0x30);
cpu.step();
assertEquals(0x00, bus.read(0xab40));
assertEquals(0x00, bus.read(0xab40, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x08, bus.read(0xab41));
assertEquals(0x08, bus.read(0xab41, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x04, bus.read(0xab41));
assertEquals(0x04, bus.read(0xab41, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, bus.read(0xab41));
assertEquals(0x02, bus.read(0xab41, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x01, bus.read(0xab41));
assertEquals(0x01, bus.read(0xab41, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, bus.read(0xab41));
assertEquals(0x00, bus.read(0xab41, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x80, bus.read(0xab41));
assertEquals(0x80, bus.read(0xab41, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, bus.read(0xab41));
assertEquals(0x40, bus.read(0xab41, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x20, bus.read(0xab41));
assertEquals(0x20, bus.read(0xab41, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x10, bus.read(0xab41));
assertEquals(0x10, bus.read(0xab41, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
@@ -677,7 +677,7 @@ public class CpuAbsoluteXModeTest extends TestCase {
cpu.setAccumulator(0x00);
bus.loadProgram(0x9d, 0x10, 0xab); // STA $ab10,X
cpu.step();
assertEquals(0x00, bus.read(0xab40));
assertEquals(0x00, bus.read(0xab40, true));
// STA should have NO affect on flags.
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -688,7 +688,7 @@ public class CpuAbsoluteXModeTest extends TestCase {
cpu.setAccumulator(0x0f);
bus.loadProgram(0x9d, 0x10, 0xab); // STA $ab10,X
cpu.step();
assertEquals(0x0f, bus.read(0xab40));
assertEquals(0x0f, bus.read(0xab40, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -698,7 +698,7 @@ public class CpuAbsoluteXModeTest extends TestCase {
cpu.setAccumulator(0x80);
bus.loadProgram(0x9d, 0x10, 0xab); // STA $ab10,X
cpu.step();
assertEquals(0x80, bus.read(0xab40));
assertEquals(0x80, bus.read(0xab40, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@@ -808,22 +808,22 @@ public class CpuAbsoluteXModeTest extends TestCase {
cpu.setXRegister(0x30);
cpu.step();
assertEquals(0xff, bus.read(0xab40));
assertEquals(0xff, bus.read(0xab40, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x00, bus.read(0xab41));
assertEquals(0x00, bus.read(0xab41, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x7f, bus.read(0xab42));
assertEquals(0x7f, bus.read(0xab42, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
cpu.step();
assertEquals(0xfe, bus.read(0xab43));
assertEquals(0xfe, bus.read(0xab43, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
}
@@ -1051,17 +1051,17 @@ public class CpuAbsoluteXModeTest extends TestCase {
0xfe, 0x12, 0xab); // INC $ab12,X
cpu.step();
assertEquals(0x01, bus.read(0xab30));
assertEquals(0x01, bus.read(0xab30, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x80, bus.read(0xab31));
assertEquals(0x80, bus.read(0xab31, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x00, bus.read(0xab32));
assertEquals(0x00, bus.read(0xab32, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@@ -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());
}
/*
@@ -411,7 +411,7 @@ public class CpuAbsoluteYModeTest extends TestCase {
cpu.setAccumulator(0x00);
bus.loadProgram(0x99, 0x10, 0xab); // STA $ab10,Y
cpu.step();
assertEquals(0x00, bus.read(0xab40));
assertEquals(0x00, bus.read(0xab40, true));
// STA should have NO effect on flags
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -423,7 +423,7 @@ public class CpuAbsoluteYModeTest extends TestCase {
cpu.setAccumulator(0x0f);
bus.loadProgram(0x99, 0x10, 0xab); // STA $ab10,Y
cpu.step();
assertEquals(0x0f, bus.read(0xab40));
assertEquals(0x0f, bus.read(0xab40, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -433,7 +433,7 @@ public class CpuAbsoluteYModeTest extends TestCase {
cpu.setAccumulator(0x80);
bus.loadProgram(0x99, 0x10, 0xab); // STA $ab10,Y
cpu.step();
assertEquals(0x80, bus.read(0xab40));
assertEquals(0x80, bus.read(0xab40, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@@ -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());
@@ -101,10 +102,10 @@ public class CpuImpliedModeTest {
cpu.step(); // Triggers the BRK
// Was at PC = 0x204. PC+1 should now be on the stack
assertEquals(0x02, bus.read(0x1ff)); // PC high byte
assertEquals(0x05, bus.read(0x1fe)); // PC low byte
assertEquals(0x02, bus.read(0x1ff, true)); // PC high byte
assertEquals(0x05, bus.read(0x1fe, true)); // PC low byte
assertEquals(0x20 | Cpu.P_CARRY | Cpu.P_OVERFLOW | Cpu.P_BREAK,
bus.read(0x1fd)); // Processor Status, with B set
bus.read(0x1fd, true)); // Processor Status, with B set
// Interrupt vector held 0x1234, so we should be there.
assertEquals(0x1234, cpu.getProgramCounter());
@@ -117,9 +118,22 @@ public class CpuImpliedModeTest {
}
@Test
public void test_BRK_HonorsIrqDisableFlag() throws MemoryAccessException {
public void test_BRK_IgnoresIrqDisableFlag() throws MemoryAccessException {
cpu.setIrqDisableFlag();
cpu.setCarryFlag();
cpu.setOverflowFlag();
assertEquals(0x20 | Cpu.P_CARRY | Cpu.P_OVERFLOW | Cpu.P_IRQ_DISABLE,
cpu.getProcessorStatus());
assertEquals(0x00, cpu.stackPeek());
assertFalse(cpu.getBreakFlag());
assertEquals(0x0200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
// Set the IRQ vector
bus.write(0xffff, 0x12);
bus.write(0xfffe, 0x34);
bus.loadProgram(0xea,
0xea,
0xea,
@@ -131,23 +145,26 @@ public class CpuImpliedModeTest {
assertEquals(0x203, cpu.getProgramCounter());
// Triggers the BRK, which should do nothing because
// of the Interrupt Disable flag
// Triggers the BRK
cpu.step();
// Reset to original contents of PC
assertEquals(0x0204, cpu.getProgramCounter());
// Empty stack
assertEquals(0xff, cpu.getStackPointer());
cpu.step(2); // Two more NOPs
// Was at PC = 0x204. PC+1 should now be on the stack
assertEquals(0x02, bus.read(0x1ff, true)); // PC high byte
assertEquals(0x05, bus.read(0x1fe, true)); // PC low byte
assertEquals(0x20 | Cpu.P_CARRY | Cpu.P_OVERFLOW | Cpu.P_BREAK | Cpu.P_IRQ_DISABLE,
bus.read(0x1fd, true)); // Processor Status, with B set
// Reset to original contents of PC
assertEquals(0x0206, cpu.getProgramCounter());
// Empty stack
assertEquals(0xff, cpu.getStackPointer());
// Interrupt vector held 0x1234, so we should be there.
assertEquals(0x1234, cpu.getProgramCounter());
assertEquals(0xfc, cpu.getStackPointer());
// B and I flags should have been set on P
assertEquals(0x20 | Cpu.P_CARRY | Cpu.P_OVERFLOW | Cpu.P_BREAK | Cpu.P_IRQ_DISABLE,
cpu.getProcessorStatus());
}
/* CLC - Clear Carry Flag - $18 */
@Test
public void test_CLC() throws MemoryAccessException {
@@ -327,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 */
@@ -416,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 */
@@ -625,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
@@ -87,7 +87,7 @@ public class CpuIndexedIndirectModeTest {
cpu.step(1);
assertEquals(0x35, cpu.getAccumulator());
assertEquals(0x31, bus.read(0xc51f));
assertEquals(0x31, bus.read(0xc51f, true));
}
@Test
@@ -103,6 +103,6 @@ public class CpuIndexedIndirectModeTest {
cpu.step(1);
assertEquals(0x11, cpu.getAccumulator());
assertEquals(0x31, bus.read(0xc51f));
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
@@ -62,7 +62,7 @@ public class CpuIndirectIndexedModeTest {
cpu.step(1);
assertEquals(0xf3, cpu.getAccumulator());
assertEquals(0xe3, bus.read(0xd828));
assertEquals(0xe3, bus.read(0xd828, true));
}
@Test
@@ -78,7 +78,7 @@ public class CpuIndirectIndexedModeTest {
cpu.step(1);
assertEquals(0x22, cpu.getAccumulator());
assertEquals(0xe3, bus.read(0xd828));
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,11 +60,11 @@ 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 {
cpu.setBehavior(Cpu.CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG);
cpu.setBehavior(Cpu.CpuBehavior.NMOS_6502);
bus.write(0x3400, 0x22);
bus.write(0x34ff, 0x00);
bus.write(0x3500, 0x54);
@@ -72,11 +72,11 @@ 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 {
cpu.setBehavior(Cpu.CpuBehavior.NMOS_WITHOUT_INDIRECT_JMP_BUG);
cpu.setBehavior(Cpu.CpuBehavior.CMOS_6502);
bus.write(0x3400, 0x22);
bus.write(0x34ff, 0x00);
bus.write(0x3500, 0x54);
@@ -84,11 +84,11 @@ 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 {
cpu.setBehavior(Cpu.CpuBehavior.CMOS);
cpu.setBehavior(Cpu.CpuBehavior.CMOS_6502);
bus.write(0x3400, 0x22);
bus.write(0x34ff, 0x00);
bus.write(0x3500, 0x54);
@@ -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());
}
/*
@@ -162,7 +162,7 @@ public class CpuIndirectXModeTest extends TestCase {
cpu.setXRegister(0x30);
bus.loadProgram(0xa9, 0x88, // LDA #$88
bus.loadProgram(0xa9, 0x88, // LDA #$88
0x5d, 0x10, 0xab, // EOR $ab10,X
0x5d, 0x11, 0xab, // EOR $ab11,X
0x5d, 0x12, 0xab, // EOR $ab12,X
@@ -411,7 +411,7 @@ public class CpuIndirectXModeTest extends TestCase {
cpu.setAccumulator(0x00);
bus.loadProgram(0x9d, 0x10, 0xab); // STA $ab10,X
cpu.step();
assertEquals(0x00, bus.read(0xab40));
assertEquals(0x00, bus.read(0xab40, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -422,7 +422,7 @@ public class CpuIndirectXModeTest extends TestCase {
cpu.setAccumulator(0x0f);
bus.loadProgram(0x9d, 0x10, 0xab); // STA $ab10,X
cpu.step();
assertEquals(0x0f, bus.read(0xab40));
assertEquals(0x0f, bus.read(0xab40, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -432,7 +432,7 @@ public class CpuIndirectXModeTest extends TestCase {
cpu.setAccumulator(0x80);
bus.loadProgram(0x9d, 0x10, 0xab); // STA $ab10,X
cpu.step();
assertEquals(0x80, bus.read(0xab40));
assertEquals(0x80, bus.read(0xab40, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@@ -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());
}
}
}
+174 -33
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());
@@ -71,61 +71,61 @@ public class CpuTest extends TestCase {
public void testStackPush() throws MemoryAccessException {
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x00, bus.read(0x1ff));
assertEquals(0x00, bus.read(0x1ff, true));
cpu.stackPush(0x06);
assertEquals(0xfe, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff));
assertEquals(0x06, bus.read(0x1ff, true));
cpu.stackPush(0x05);
assertEquals(0xfd, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff));
assertEquals(0x05, bus.read(0x1fe));
assertEquals(0x06, bus.read(0x1ff, true));
assertEquals(0x05, bus.read(0x1fe, true));
cpu.stackPush(0x04);
assertEquals(0xfc, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff));
assertEquals(0x05, bus.read(0x1fe));
assertEquals(0x04, bus.read(0x1fd));
assertEquals(0x06, bus.read(0x1ff, true));
assertEquals(0x05, bus.read(0x1fe, true));
assertEquals(0x04, bus.read(0x1fd, true));
cpu.stackPush(0x03);
assertEquals(0xfb, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff));
assertEquals(0x05, bus.read(0x1fe));
assertEquals(0x04, bus.read(0x1fd));
assertEquals(0x03, bus.read(0x1fc));
assertEquals(0x06, bus.read(0x1ff, true));
assertEquals(0x05, bus.read(0x1fe, true));
assertEquals(0x04, bus.read(0x1fd, true));
assertEquals(0x03, bus.read(0x1fc, true));
cpu.stackPush(0x02);
assertEquals(0xfa, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff));
assertEquals(0x05, bus.read(0x1fe));
assertEquals(0x04, bus.read(0x1fd));
assertEquals(0x03, bus.read(0x1fc));
assertEquals(0x02, bus.read(0x1fb));
assertEquals(0x06, bus.read(0x1ff, true));
assertEquals(0x05, bus.read(0x1fe, true));
assertEquals(0x04, bus.read(0x1fd, true));
assertEquals(0x03, bus.read(0x1fc, true));
assertEquals(0x02, bus.read(0x1fb, true));
cpu.stackPush(0x01);
assertEquals(0xf9, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff));
assertEquals(0x05, bus.read(0x1fe));
assertEquals(0x04, bus.read(0x1fd));
assertEquals(0x03, bus.read(0x1fc));
assertEquals(0x02, bus.read(0x1fb));
assertEquals(0x01, bus.read(0x1fa));
assertEquals(0x06, bus.read(0x1ff, true));
assertEquals(0x05, bus.read(0x1fe, true));
assertEquals(0x04, bus.read(0x1fd, true));
assertEquals(0x03, bus.read(0x1fc, true));
assertEquals(0x02, bus.read(0x1fb, true));
assertEquals(0x01, bus.read(0x1fa, true));
}
public void testStackPushWrapsAroundToStackTop() throws MemoryAccessException {
cpu.setStackPointer(0x01);
cpu.stackPush(0x01);
assertEquals(0x01, bus.read(0x101));
assertEquals(0x01, bus.read(0x101, true));
assertEquals(0x00, cpu.getStackPointer());
cpu.stackPush(0x02);
assertEquals(0x02, bus.read(0x100));
assertEquals(0x02, bus.read(0x100, true));
assertEquals(0xff, cpu.getStackPointer());
cpu.stackPush(0x03);
assertEquals(0x03, bus.read(0x1ff));
assertEquals(0x03, bus.read(0x1ff, true));
assertEquals(0xfe, cpu.getStackPointer());
}
@@ -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());
@@ -384,6 +385,41 @@ public class CpuTest extends TestCase {
assertFalse(cpu.getCpuState().irqAsserted);
}
public void testIrqDoesNotSetBRK() throws Exception {
// Ensure the IRQ disable flag is cleared
cpu.clearIrqDisableFlag();
// Set the IRQ vector
bus.write(0xffff, 0x12);
bus.write(0xfffe, 0x34);
// Create an IRQ handler at 0x1234
cpu.setProgramCounter(0x1234);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
cpu.setProgramCounter(0x0200);
// Create a little program at 0x0200
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertIrq();
cpu.step();
assertTrue(cpu.getIrqDisableFlag()); // Should have been set by the IRQ
assertFalse(cpu.getBreakFlag());
}
public void testIrqHonorsIrqDisabledFlag() throws Exception {
// Ensure the IRQ disable flag is set
cpu.setIrqDisableFlag();
@@ -498,6 +534,37 @@ public class CpuTest extends TestCase {
assertFalse(cpu.getCpuState().nmiAsserted);
}
public void testNmiDoesNotSetBRK() throws Exception {
// Set the NMI vector to 0x1000
bus.write(0xfffb, 0x10);
bus.write(0xfffa, 0x00);
// Create an NMI handler at 0x1000
cpu.setProgramCounter(0x1000);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
// Create a little program at 0x0200
cpu.setProgramCounter(0x0200);
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertNmi();
cpu.step();
assertFalse(cpu.getBreakFlag());
}
public void testNmiIgnoresIrqDisableFlag() throws Exception {
// Set the IRQ disable flag, which should be ignored by the NMI
cpu.setIrqDisableFlag();
@@ -603,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);
}
}
@@ -0,0 +1,329 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import org.junit.Test;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test for Zero Page Indirect addressing mode, found on some instructions
* in the 65C02 and 65816
*/
public class CpuZeroPageIndirectTest {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
private void makeCmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.CMOS_6502);
}
private void makeNmosCpu() throws Exception {
makeCpu(InstructionTable.CpuBehavior.NMOS_6502);
}
private void makeCpu(InstructionTable.CpuBehavior behavior) throws Exception {
this.cpu = new Cpu(behavior);
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
}
@Test
public void test_ora() throws Exception {
makeCmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x60, 0x03);
bus.write(0x61, 0x10);
bus.write(0x1000, 0x11);
bus.write(0x1001, 0x22);
bus.write(0x1002, 0x44);
bus.write(0x1003, 0x88);
bus.loadProgram(0x12, 0x30, // ORA ($30)
0x12, 0x40, // ORA ($40)
0x12, 0x50, // ORA ($50)
0x12, 0x60); // ORA ($60)
assertEquals(0x00, cpu.getAccumulator());
// 0x00 | 0x11 = 0x11
cpu.step();
assertEquals(0x11, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// 0x11 | 0x22 = 0x33
cpu.step();
assertEquals(0x33, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// 0x33 | 0x44 = 0x77
cpu.step();
assertEquals(0x77, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// 0x77 | 0x88 = 0xff
cpu.step();
assertEquals(0xff, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
}
@Test
public void test_ora_requiresCmosCpu() throws Exception {
makeNmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x60, 0x03);
bus.write(0x61, 0x10);
bus.write(0x1000, 0x11);
bus.write(0x1001, 0x22);
bus.write(0x1002, 0x44);
bus.write(0x1003, 0x88);
bus.loadProgram(0x12, 0x30, // ORA ($30)
0x12, 0x40, // ORA ($40)
0x12, 0x50, // ORA ($50)
0x12, 0x60); // ORA ($60)
assertEquals(0x00, cpu.getAccumulator());
boolean zState = cpu.getZeroFlag();
boolean nState = cpu.getNegativeFlag();
// 0x00 | 0x11 = 0x11, but not implemented in NMOS cpu
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag()); // unchanged
assertEquals(nState, cpu.getNegativeFlag()); // unchanged
// 0x11 | 0x22 = 0x33, but not implemented in NMOS cpu
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag()); // unchanged
assertEquals(nState, cpu.getNegativeFlag()); // unchanged
// 0x33 | 0x44 = 0x77, but not implemented in NMOS cpu
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag()); // unchanged
assertEquals(nState, cpu.getNegativeFlag()); // unchanged
// 0x77 | 0x88 = 0xff, but not implemented in NMOS cpu
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag()); // unchanged
assertEquals(nState, cpu.getNegativeFlag()); // unchanged
}
@Test
public void test_and() throws Exception {
makeCmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x1000, 0x33);
bus.write(0x1001, 0x11);
bus.write(0x1002, 0x88);
bus.loadProgram(0x32, 0x30, // AND ($30)
0x32, 0x40, // AND ($40)
0x32, 0x50); // AND ($50)
cpu.setAccumulator(0xff);
// 0xFF & 0x33 == 0x33
cpu.step();
assertEquals(0x33, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// 0x33 & 0x11 == 0x11
cpu.step();
assertEquals(0x11, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
// 0x11 & 0x80 == 0
cpu.step();
assertEquals(0, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@Test
public void test_and_requiresCmosCpu() throws Exception {
makeNmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x1000, 0x33);
bus.write(0x1001, 0x11);
bus.write(0x1002, 0x88);
bus.loadProgram(0x32, 0x30, // AND ($30)
0x32, 0x40, // AND ($40)
0x32, 0x50); // AND ($50)
cpu.setAccumulator(0xff);
boolean zState = cpu.getZeroFlag();
boolean nState = cpu.getNegativeFlag();
// 0xFF & 0x33 == 0x33, but not implemented in NMOS cpu
cpu.step();
assertEquals(0xff, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag());
assertEquals(nState, cpu.getNegativeFlag());
// 0x33 & 0x11 == 0x11, but not implemented in NMOS cpu
cpu.step();
assertEquals(0xff, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag());
assertEquals(nState, cpu.getNegativeFlag());
// 0x11 & 0x80 == 0, but not implemented in NMOS cpu
cpu.step();
assertEquals(0xff, cpu.getAccumulator());
assertEquals(zState, cpu.getZeroFlag());
assertEquals(nState, cpu.getNegativeFlag());
}
@Test
public void test_eor() throws Exception {
makeCmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x60, 0x03);
bus.write(0x61, 0x10);
bus.write(0x1000, 0x00);
bus.write(0x1001, 0xff);
bus.write(0x1002, 0x33);
bus.write(0x1003, 0x44);
bus.loadProgram(0x52, 0x30, // AND ($30)
0x52, 0x40, // AND ($40)
0x52, 0x50, // EOR ($50)
0x52, 0x60); // AND ($60)
cpu.setAccumulator(0x88);
cpu.step();
assertEquals(0x88, cpu.getAccumulator());
cpu.step();
assertEquals(0x77, cpu.getAccumulator());
cpu.step();
assertEquals(0x44, cpu.getAccumulator());
cpu.step();
assertEquals(0x00, cpu.getAccumulator());
}
@Test
public void test_eor_requiresCmosCpu() throws Exception {
makeNmosCpu();
// Set some initial values in zero page.
bus.write(0x30, 0x00);
bus.write(0x31, 0x10);
bus.write(0x40, 0x01);
bus.write(0x41, 0x10);
bus.write(0x50, 0x02);
bus.write(0x51, 0x10);
bus.write(0x60, 0x03);
bus.write(0x61, 0x10);
bus.write(0x1000, 0x00);
bus.write(0x1001, 0xff);
bus.write(0x1002, 0x33);
bus.write(0x1003, 0x44);
bus.loadProgram(0x52, 0x30, // AND ($30)
0x52, 0x40, // AND ($40)
0x52, 0x50, // EOR ($50)
0x52, 0x60); // AND ($60)
cpu.setAccumulator(0x88);
cpu.step();
assertEquals(0x88, cpu.getAccumulator());
cpu.step();
assertEquals(0x88, cpu.getAccumulator());
cpu.step();
assertEquals(0x88, cpu.getAccumulator());
cpu.step();
assertEquals(0x88, cpu.getAccumulator());
}
}
@@ -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());
}
/*
@@ -119,31 +119,31 @@ public class CpuZeroPageModeTest extends TestCase {
0x06, 0x04);
cpu.step();
assertEquals(0x00, bus.read(0x0000));
assertEquals(0x00, bus.read(0x0000, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, bus.read(0x0001));
assertEquals(0x02, bus.read(0x0001, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x04, bus.read(0x0002));
assertEquals(0x04, bus.read(0x0002, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x88, bus.read(0x0003));
assertEquals(0x88, bus.read(0x0003, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, bus.read(0x0004));
assertEquals(0x00, bus.read(0x0004, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
@@ -274,61 +274,61 @@ public class CpuZeroPageModeTest extends TestCase {
0x26, 0x01); // ROL $01 (m=%10000001, c=0)
cpu.step();
assertEquals(0x00, bus.read(0x0000));
assertEquals(0x00, bus.read(0x0000, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, bus.read(0x0001));
assertEquals(0x02, bus.read(0x0001, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step(2);
assertEquals(0x05, bus.read(0x0001));
assertEquals(0x05, bus.read(0x0001, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x0a, bus.read(0x0001));
assertEquals(0x0a, bus.read(0x0001, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x14, bus.read(0x0001));
assertEquals(0x14, bus.read(0x0001, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x28, bus.read(0x0001));
assertEquals(0x28, bus.read(0x0001, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x50, bus.read(0x0001));
assertEquals(0x50, bus.read(0x0001, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0xa0, bus.read(0x0001));
assertEquals(0xa0, bus.read(0x0001, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, bus.read(0x0001));
assertEquals(0x40, bus.read(0x0001, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x81, bus.read(0x0001));
assertEquals(0x81, bus.read(0x0001, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
@@ -387,38 +387,38 @@ public class CpuZeroPageModeTest extends TestCase {
0x46, 0x05); // LSR $05
cpu.step();
assertEquals(0x00, bus.read(0x0000));
assertEquals(0x00, bus.read(0x0000, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, bus.read(0x0001));
assertEquals(0x00, bus.read(0x0001, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x01, bus.read(0x0002));
assertEquals(0x01, bus.read(0x0002, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x22, bus.read(0x0003));
assertEquals(0x22, bus.read(0x0003, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, bus.read(0x0004));
assertEquals(0x40, bus.read(0x0004, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
// Setting Carry should not affect the result.
cpu.step(2);
assertEquals(0x01, bus.read(0x0005));
assertEquals(0x01, bus.read(0x0005, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
@@ -623,61 +623,61 @@ public class CpuZeroPageModeTest extends TestCase {
0x66, 0x11); // ROR $01 (m=%00010000, c=0)
cpu.step();
assertEquals(0x00, bus.read(0x10));
assertEquals(0x00, bus.read(0x10, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x08, bus.read(0x11));
assertEquals(0x08, bus.read(0x11, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x04, bus.read(0x11));
assertEquals(0x04, bus.read(0x11, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x02, bus.read(0x11));
assertEquals(0x02, bus.read(0x11, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x01, bus.read(0x11));
assertEquals(0x01, bus.read(0x11, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x00, bus.read(0x11));
assertEquals(0x00, bus.read(0x11, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag());
cpu.step();
assertEquals(0x80, bus.read(0x11));
assertEquals(0x80, bus.read(0x11, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x40, bus.read(0x11));
assertEquals(0x40, bus.read(0x11, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x20, bus.read(0x11));
assertEquals(0x20, bus.read(0x11, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
cpu.step();
assertEquals(0x10, bus.read(0x11));
assertEquals(0x10, bus.read(0x11, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag());
@@ -689,7 +689,7 @@ public class CpuZeroPageModeTest extends TestCase {
cpu.setYRegister(0x00);
bus.loadProgram(0x84, 0x10);
cpu.step();
assertEquals(0x00, bus.read(0x10));
assertEquals(0x00, bus.read(0x10, true));
// Should have no effect on flags.
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -699,7 +699,7 @@ public class CpuZeroPageModeTest extends TestCase {
cpu.setYRegister(0x0f);
bus.loadProgram(0x84, 0x10);
cpu.step();
assertEquals(0x0f, bus.read(0x10));
assertEquals(0x0f, bus.read(0x10, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -708,7 +708,7 @@ public class CpuZeroPageModeTest extends TestCase {
cpu.setYRegister(0x80);
bus.loadProgram(0x84, 0x10);
cpu.step();
assertEquals(0x80, bus.read(0x10));
assertEquals(0x80, bus.read(0x10, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@@ -719,7 +719,7 @@ public class CpuZeroPageModeTest extends TestCase {
cpu.setAccumulator(0x00);
bus.loadProgram(0x85, 0x10);
cpu.step();
assertEquals(0x00, bus.read(0x10));
assertEquals(0x00, bus.read(0x10, true));
// Should have no effect on flags.
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -729,7 +729,7 @@ public class CpuZeroPageModeTest extends TestCase {
cpu.setAccumulator(0x0f);
bus.loadProgram(0x85, 0x10);
cpu.step();
assertEquals(0x0f, bus.read(0x10));
assertEquals(0x0f, bus.read(0x10, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -738,7 +738,7 @@ public class CpuZeroPageModeTest extends TestCase {
cpu.setAccumulator(0x80);
bus.loadProgram(0x85, 0x10);
cpu.step();
assertEquals(0x80, bus.read(0x10));
assertEquals(0x80, bus.read(0x10, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@@ -749,7 +749,7 @@ public class CpuZeroPageModeTest extends TestCase {
cpu.setXRegister(0x00);
bus.loadProgram(0x86, 0x10);
cpu.step();
assertEquals(0x00, bus.read(0x10));
assertEquals(0x00, bus.read(0x10, true));
// Should have no effect on flags.
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -759,7 +759,7 @@ public class CpuZeroPageModeTest extends TestCase {
cpu.setXRegister(0x0f);
bus.loadProgram(0x86, 0x10);
cpu.step();
assertEquals(0x0f, bus.read(0x10));
assertEquals(0x0f, bus.read(0x10, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
@@ -768,7 +768,7 @@ public class CpuZeroPageModeTest extends TestCase {
cpu.setXRegister(0x80);
bus.loadProgram(0x86, 0x10);
cpu.step();
assertEquals(0x80, bus.read(0x10));
assertEquals(0x80, bus.read(0x10, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
@@ -926,22 +926,22 @@ public class CpuZeroPageModeTest extends TestCase {
0xc6, 0x13); // DEC $13
cpu.step();
assertEquals(0xff, bus.read(0x10));
assertEquals(0xff, bus.read(0x10, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x00, bus.read(0x11));
assertEquals(0x00, bus.read(0x11, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x7f, bus.read(0x12));
assertEquals(0x7f, bus.read(0x12, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
cpu.step();
assertEquals(0xfe, bus.read(0x13));
assertEquals(0xfe, bus.read(0x13, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
}
@@ -1182,17 +1182,17 @@ public class CpuZeroPageModeTest extends TestCase {
0xe6, 0x12); // INC $12
cpu.step();
assertEquals(0x01, bus.read(0x10));
assertEquals(0x01, bus.read(0x10, true));
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x80, bus.read(0x11));
assertEquals(0x80, bus.read(0x11, true));
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag());
cpu.step();
assertEquals(0x00, bus.read(0x12));
assertEquals(0x00, bus.read(0x12, true));
assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag());
}
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff. Show more