1
0
mirror of https://github.com/sethm/symon.git synced 2024-06-03 07:29:30 +00:00

Compare commits

...

140 Commits

Author SHA1 Message Date
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
0e49b197b3 Update README for 1.2.1 2016-01-08 20:52:02 -08:00
Seth Morabito
b59fa63b63 Merge branch '1.2.branch' 2016-01-08 20:41:25 -08:00
Seth Morabito
eff98118d5 Fix a few straggling Java 8-isms. 2016-01-08 20:40:23 -08:00
Seth Morabito
c599df1cfb Set version to 1.3.0-SNAPSHOT 2016-01-08 19:14:00 -08:00
Seth Morabito
66c52c8826 Revert Java 1.8 changes. Buildable with Java 1.7
There are still active users on Java 1.7, so building with Java 8
was a no-no. This change reverts the recent migration to Java 8,
allowing JDK 1.7 to compile the code.

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

- Add 1.8 features

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

- Switched from built-in Java util logger to Logback

- Updated all copyright strings to 2016
2015-12-29 14:40:42 -08:00
Matt Harlum
8335cf5421 Correct BRK behaviour
IRQ/NMI clear the BRK flag
BRK is Non-Maskable
2015-06-01 08:58:17 +10:00
Seth Morabito
ae5c5d48b2 Merge pull request #12 from maikmerten/master
mask all bits beyond bits 0 to 7 on bus reads to ensure no surplus bits ...
2014-08-23 16:59:56 -07:00
Maik Merten
72ba068beb mask all bits beyond bits 0 to 7 on bus reads to ensure no surplus bits cause unpredictable system behavior. 2014-08-23 16:50:34 +02:00
Seth Morabito
142cb470a0 Converging on a useful CRTC ROM 2014-08-18 19:52:09 -07:00
Seth Morabito
cad3673bd2 Formatting; uppercase 2014-08-16 16:54:36 -07:00
Seth Morabito
4796b65390 New ROMs. Better backspace handling. 2014-08-16 14:28:54 -07:00
Seth Morabito
e0d6017d95 Unifying and cleaning up copyrights 2014-08-16 13:50:47 -07:00
Seth Morabito
d6ea21bae4 Mostly working CRTC 2014-08-15 15:21:02 -07:00
Seth Morabito
af12039686 Merge pull request #10 from maikmerten/master
Multicomp: SD card controller emulation
2014-08-15 13:51:26 -07:00
Seth Morabito
ed1d5f999b Preparing for real implementation 2014-08-15 12:48:54 -07:00
Maik Merten
c595ff5370 README update 2014-08-15 20:15:04 +02:00
Maik Merten
e9f662cc17 Merge remote-tracking branch 'upstream/master'
Conflicts:
	src/main/java/com/loomcom/symon/machines/MulticompMachine.java
2014-08-15 20:11:33 +02:00
Seth Morabito
70ab2a6fc2 More CRTC tinkering 2014-08-14 21:03:52 -07:00
Seth Morabito
f35fbce38b Add CRTC EhBASIC, switch char rom. 2014-08-14 11:50:51 -07:00
Seth Morabito
eeb246ebc2 CRTC refactoring 2014-08-14 10:53:48 -07:00
Seth Morabito
e157217f50 Fix missing repaint on Video Window 2014-08-13 15:46:20 -07:00
Maik Merten
cc12e8f70a proper status value when SD controller is in write mode 2014-08-13 19:56:40 +02:00
Maik Merten
a9a1c1aa52 get writing to a certain position in a file somewhat more correct 2014-08-13 19:38:54 +02:00
Seth Morabito
742e3a1027 Split PIA into PIA/VIA6522 2014-08-12 15:06:08 -07:00
Seth Morabito
45f596e0d4 Rename VIA to PIA
Preparing to create a PIA interface and one or more parallel
IO device implementations.
2014-08-12 14:19:55 -07:00
Seth Morabito
4402a28e2b Update README 2014-08-12 14:05:19 -07:00
Maik Merten
693d1959ac finer grained synchronization in TraceLog. Profiling indicates that this speeds up adding log entries quite considerably. 2014-08-12 21:58:52 +02:00
Maik Merten
792366fddb some cleanup in the SD card controller 2014-08-12 21:34:00 +02:00
Maik Merten
b2670aedf1 email address update 2014-08-12 19:25:37 +02:00
Maik Merten
5d488bbcd2 Merge remote-tracking branch 'upstream/master' 2014-08-12 11:48:47 +02:00
Seth Morabito
e2a1144c7c Update copyright, prep for 1.1.0 2014-08-11 14:16:41 -07:00
Seth Morabito
ac5907691f Release version 1.0.0 2014-08-11 13:49:48 -07:00
Seth Morabito
fce0dad2a9 Removed unused constant 2014-08-11 13:40:21 -07:00
Seth Morabito
0c40cd325c Alert dialogs for ROM/Program loading 2014-08-11 13:36:31 -07:00
Seth Morabito
22a9207dca Fix for GitHub Issue #9 2014-08-11 13:36:08 -07:00
Maik Merten
010c42753f Merge remote-tracking branch 'upstream/master' 2014-08-11 09:34:44 +02:00
Seth Morabito
7c2a007928 Add Hard/Soft reset buttons 2014-08-10 20:42:39 -07:00
Seth Morabito
194a13d1ac Make register fields editable 2014-08-10 20:33:54 -07:00
Seth Morabito
66646116cb README update 2014-08-10 17:34:13 -07:00
Seth Morabito
59c6d8e23b Added Klaus Dormann's tests 2014-08-10 16:52:20 -07:00
Seth Morabito
36615fc70b Move images to resources 2014-08-10 15:19:59 -07:00
Seth Morabito
1a1d503abe Make PC editable 2014-08-10 14:24:43 -07:00
Seth Morabito
eac387e472 Make StatusPanel aware of the Machine 2014-08-10 14:08:15 -07:00
Seth Morabito
bb82db6672 Version 1.0.0-SNAPSHOT 2014-08-10 14:00:18 -07:00
Seth Morabito
f675a6e462 Merge branch 'master' of github.com:sethm/symon 2014-08-10 13:54:50 -07:00
Seth Morabito
ccae8905b3 Implement SimpleMachine 2014-08-10 13:53:04 -07:00
Maik Merten
7a215736fe rework SD controller emulation to work with Input- and OutputStreams (no longer load the image into a byte array) 2014-08-09 17:55:18 +02:00
Maik Merten
c61a63d5bb more accurate emulation of the SD controller 2014-08-09 16:47:49 +02:00
Maik Merten
7cb4e1c945 NPE fix and reset position on every command 2014-08-09 15:13:59 +02:00
Maik Merten
3a40d35fdf use SD interface in MulticompMachine and lift the speed limit on the ACIA device to better match the experience on the real machine 2014-08-09 14:58:29 +02:00
Maik Merten
d658cd0ae5 completely untested and incomplete implementation of the SD card interface of the MULTICOMP 2014-08-09 14:57:18 +02:00
Seth Morabito
6b9295e72d Merge pull request #7 from maikmerten/master
invoke setBus() on devices added to the bus
2014-07-28 10:13:09 -07:00
Maik Merten
004087a742 invoke setBus() on devices added to the bus 2014-07-28 18:19:52 +02:00
Seth Morabito
7edbb12f68 Merge branch 'reset-wip' 2014-07-27 13:42:18 -07:00
Seth Morabito
4bfc196b49 Clear memory on Control+Reset 2014-07-27 13:41:44 -07:00
Seth Morabito
b1e1e75555 Reset WIP 2014-07-27 11:33:08 -07:00
Seth Morabito
da3015defd Merge pull request #6 from maikmerten/master
minor typo fixes in README.md
2014-07-26 14:15:06 -07:00
Maik Merten
87b5b77dbf minor typo fixes in README.m 2014-07-26 23:07:06 +02:00
124 changed files with 42554 additions and 1729 deletions

2
.gitignore vendored
View File

@ -1,6 +1,8 @@
*~
*.jar
*#
target
.DS_Store
.idea
symon.iml
/dependency-reduced-pom.xml

View File

@ -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

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

View File

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2008-2013 Seth J. Morabito <web@loomcom.com>
Copyright (c) 2008-2014 Seth J. Morabito <web@loomcom.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

250
README.md
View File

@ -1,19 +1,13 @@
SYMON - A 6502 System Simulator
===============================
**NOTE: THIS SOFTWARE IS UNDER ACTIVE DEVELOPMENT. Feedback is welcome!**
**Version:** 1.4.0
**Version:** 0.9.9.0
**Last Updated:** 26 July, 2014
Copyright (c) 2014 Seth J. Morabito &lt;web@loomcom.com&gt;
Portions Copyright (c) 2014 Maik Marten &lt;maikmarten@googlemail.com&gt;
**Last Updated:** 11 November, 2023
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://github.com/sethm/symon/raw/master/screenshots/full.jpg)
## 1.0 About
@ -22,25 +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 active maintenance. Feedback and patches are always
welcome.
## 2.0 Requirements
- Java 1.7 or higher
- Java 1.8 or higher
- Maven 2.0.x or higher (for building from source)
- JUnit 4 or higher (for testing)
## 3.0 Features
Symon can simiulate multiple 6502 based architectures. At present, two
machines are implemented: Symon (the default), and MULTICOMP.
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
@ -60,28 +58,55 @@ memory.
- `$0000`--`$DFFF`: 56KB RAM
- `$E000`--`$FFFF`: 8KB ROM
- `$FFD0`--`$FFD1`: Motorola 6850 ACIA
- `$FFD8`--`$FFDF`: Controller for SD cards
### 3.1.3 Simple Memory Map
- `$0000`--`$FFFF`: 64KB RAM
#### 3.1.4 BenEater Memory Map
- `$0000`--`$3FFF`: 16KB RAM
- `$5000`--`$5003`: MOS 6551 ACIA (Serial Console)
- `$6000`--`$600F`: 6522 VIA
- `$8000`--`$FFFF`: 16KB ROM
### 3.2 Serial Console and CPU Status
![Serial Console] (https://github.com/sethm/symon/raw/master/screenshots/console.png)
![Serial Console](https://github.com/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://github.com/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://github.com/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
@ -92,27 +117,40 @@ address.
### 3.4 Memory Window
![Memory Window] (https://github.com/sethm/symon/raw/master/screenshots/memory_window.png)
![Memory Window](https://github.com/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://github.com/sethm/symon/raw/master/screenshots/trace_log.png)
The last 20,000 execution steps are disassembled and logged to the Trace Log Window.
The last 20,000 execution steps are disassembled and logged to the Trace Log
Window.
### 3.6 NEW - Experimental 6545 CRTC Video
### 3.6 Simulator Speeds
![Composite Video] (https://github.com/sethm/symon/raw/master/screenshots/video_window.png)
![Speeds](https://github.com/sethm/symon/raw/master/screenshots/simulator_menu.png)
This feature is highly experimental. It's possible to open a video window from the "View" menu.
This window simulates the output of a MOS 6545 CRT Controller located at address `$9000` and
`$9001`.
Simulated speeds may be set from 1MHz to 8MHz.
By default, the 40 x 25 character display uses video memory located at base address `$7000`.
This means that the memory from address `$7000` (28672 decimal) to `$73E8` (29672 decimal)
is directly mapped to video.
### 3.7 Breakpoints
![Breakpoints](https://github.com/sethm/symon/raw/master/screenshots/breakpoints.png)
Breakpoints can be set and removed through the Breakpoints window.
### 3.8 Experimental 6545 CRTC Video
![Composite Video](https://github.com/sethm/symon/raw/master/screenshots/video_window.png)
This feature is highly experimental. It's possible to open a video window
from the "View" menu. This window simulates the output of a MOS 6545 CRT
Controller located at address `$9000` and `$9001`.
By default, the 40 x 25 character display uses video memory located at base
address `$7000`. This means that the memory from address `$7000` (28672
decimal) to `$73E8` (29672 decimal) is directly mapped to video.
- Address Register (at address `$9000`)
- R1: Horizontal Displayed Columns
@ -125,27 +163,29 @@ is directly mapped to video.
- R14: Cursor Position (High Byte)
- R15: Cursor Position (Low Byte)
Although the simulation is pretty good, there are a few key differences between
the simulated 6545 and a real 6545:
Although the simulation is pretty good, there are a few key differences
between the simulated 6545 and a real 6545:
- The simulated 6545 supports only the straight binary addressing mode of the real 6545,
and not the Row/Column addressing mode.
- The simulated 6545 has full 16 bit addressing, where the real 6545 has only
a 14-bit address bus.
- The simulation is done at a whole-frame level, meaning that lots of
6545 programming tricks that were achieved by updating the frame address
during vertical and horizontal sync times are not achievable. There is no way
(for example) to change the Display Start Address (R12 and R13) while a
frame is being drawn.
- The simulated 6545 supports only the straight binary addressing
mode of the real 6545, and not the Row/Column addressing mode.
- The simulated 6545 has full 16 bit addressing, where the real 6545
has only a 14-bit address bus.
- The simulation is done at a whole-frame level, meaning that lots
of 6545 programming tricks that were achieved by updating the
frame address during vertical and horizontal sync times are not
achievable. There is no way (for example) to change the Display Start
Address (R12 and R13) while a frame is being drawn.
For more information on the 6545 CRTC and its programming model, please see the following resources
- [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.6.1 Example BASIC Program to test Video
#### 3.8.1 Example BASIC Program to test Video
This program will fill the video screen with all printable characters.
@ -168,13 +208,27 @@ Maven will build Symon, run unit tests, and produce a jar file in the
`target` directory containing the compiled simulator.
Symon is meant to be invoked directly from the jar file. To run with
Java 1.5 or greater, just type:
Java 1.8 or greater, just type:
$ java -jar symon-0.9.1.0.jar
$ java -jar symon-1.2.0.jar
When Symon is running, you should be presented with a simple graphical
interface.
#### 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 ROM images
The simulator requires a ROM image loaded into memory to work
@ -182,10 +236,9 @@ 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
@ -217,8 +270,47 @@ running.
## 5.0 Revision History
- **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
simulate various clock speeds. Added ability to select clock
speed at runtime. Status display now shows the next instruction
to be executed, instead of the last instruction executed.
Added support for breakpoints.
- **1.0.0:** 10 August, 2014 - Added "Simple" machine
implementation, pure RAM with no IO. Added Klaus Dormann's
6502 Functional Tests for further machine verification (these
tests must be run in the "Simple" machine).
- **0.9.9.1:** 27 July, 2014 - Pressing 'Control' while clicking
'Reset' now performs a memory clear.
- **0.9.9:** 26 July, 2014 - MULTICOMP and multi-machine support
contributed by Maik Marten &lt;maikmarten@googlemail.com&gt;
contributed by Maik Merten &lt;maikmerten@googlemail.com&gt;
- **0.9.1:** 26 January, 2014 - Support for IRQ and NMI handling.
@ -251,7 +343,15 @@ running.
- **0.1:** 20 January, 2010
## 6.0 To Do
## 6.0 Roadmap
- **1.2:** Better breakpoint support. Symbolic debugging of breakpoints.
- **2.0:** Complete rewrite of the UI in JavaFX instead of Swing. Complete
assembler and disassembler built in. Ability to attach source code for
symbolic debugging.
## 7.0 To Do
- Feedback (in the form of dialogs, status bar, etc).
@ -269,22 +369,38 @@ running.
- Allow displaying ACIA status and dumping ACIA buffers, for
debugging.
- CRTC emulation is very naive. The whole frame is drawn in one
CPU step. This should be improved by drawing scan lines during
machine steps to approximate real NTSC/PAL refresh rates.
- Symbolic debugging.
## 7.0 Acknowledgements
## 8.0 Copyright and Acknowledgements
**Copyright (c) 2014 Seth J. Morabito &lt;web@loomcom.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.
- Enhanced 6502 BASIC Copyright (c) Lee Davison
- 6502 Functional Tests Copyright (c) Klaus Dormann
- JTerminal Copyright (c) Graham Edgecombe
This project would not have been possible without the following resources:
- [Graham Edgecombe's JTerminal project] (https://github.com/grahamedgecombe/jterminal),
which forms the core of Symon's console.
- [Andrew Jacobs' 6502 Pages] (http://www.obelisk.demon.co.uk/6502/), for
- [Andrew Jacobs' 6502 Pages](http://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
## 8.0 Licensing
## 9.0 Licensing
Symon is free software. It is distributed under the MIT License.
Please see the file 'COPYING' for full details of the license.

100
pom.xml
View File

@ -4,7 +4,7 @@
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>0.9.9.0</version>
<version>1.4.0</version>
<name>symon</name>
<url>http://www.loomcom.com/symon</url>
<properties>
@ -15,88 +15,60 @@
UTF-8
</project.reporting.outputEncoding>
</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>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</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.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.5.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes/com/loomcom/symon/ui/images</outputDirectory>
<resources>
<resource>
<directory>src/main/java/com/loomcom/symon/ui/images</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<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>
@ -104,11 +76,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.6</source>
<target>1.6</target>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
@ -116,7 +88,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>

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:

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

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;
}

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

View File

@ -150,7 +150,7 @@ END_CODE
; sign on string
LAB_mess
.byte $0D,$0A,"Symon (c) 2008-2013, Seth Morabito"
.byte $0D,$0A,"Symon (c) 2008-2014, Seth Morabito"
.byte $0D,$0A,"Enhanced 6502 BASIC 2.22 (c) Lee Davison"
.byte $0D,$0A,"[C]old/[W]arm ?",$00

View File

@ -1,5 +1,5 @@
MEMORY {
RAM1: start = $0000, size = $8000;
RAM1: start = $0000, size = $8000 - __STACKSIZE__;
ROM1: start = $C000, size = $3F00, fill = yes;
MONITOR: start = $FF00, size = $FA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
@ -13,6 +13,6 @@ VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__ = $0300;
__STACKSIZE__: type = weak, value = $0300;
}

4
samples/ehbasic_crtc/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.rom
*.lst
*.map
*.o

View File

@ -0,0 +1,13 @@
CA=ca65
LD=ld65
all: ehbasic
ehbasic: ehbasic.o
$(LD) -C symon.config -vm -m ehbasic.map -o ehbasic.rom ehbasic.o
ehbasic.o:
$(CA) --listing ehbasic.lst -o ehbasic.o min_mon.asm
clean:
rm -f *.o *.rom *.map *.lst

View File

@ -0,0 +1,85 @@
This directoy contains a very slightly modified version of EhBASIC 2.22
to support the Symon simulator.
Usage
-----
The pre-assemled ROM image 'ehbasic.rom' in the "samples" directory is all you
need unless you want to rebuild this source code.
Just copy the image 'ehbasic.rom' to the directory where you run Symon, and
rename the file to 'rom.bin'. It will be loaded at memory location $D000 when
the simulator starts up. Click "Run" and you'll be presented with BASIC.
At the first prompt, type 'C' for a Cold Start
When prompted for free memory, type: $C000
Note that EhBASIC only accepts upper case input, so you'll need to use caps
lock (the cruise control for cool) to really make the most of it.
Building
--------
To build it, you'll need the CC65 tool chain from http://www.cc65.org/
and a suitable version of 'make'. Just typing:
% make
in this directory should re-build everything. You'll get a listing,
some object files, and the ROM image itself.
Changes from the EhBASIC 2.22
-----------------------------
- Minor syntax changes to allow assembly with the cc65 tool chain.
- Memory map modified for Symon.
- At reset, configure the 6551 ACIA for 8-N-1, 2400 baud.
- Monitor routines 'ACIAin' and 'ACIAout' modified for the 6551 ACIA.
Specifically, reading and writing will check the 6551 status register for
the status of rx and tx registers before receive or transmit.
EhBASIC is copyright Lee Davison <leeedavison@googlemail.com>
My changes are so slight that I hesitate to even say this, but for "CYA"
reasons:
Changes are copyright Seth Morabito <web@loomcom.com> and are distributed under
the same license as EhBASIC. I claim no commercial interest whatsoever. Any
commercial use must be negotiated directly with Lee Davison.
Original EhBASIC 2.22 README
----------------------------
Enhanced BASIC is a BASIC interpreter for the 6502 family microprocessors. It
is constructed to be quick and powerful and easily ported between 6502 systems.
It requires few resources to run and includes instructions to facilitate easy
low level handling of hardware devices. It also retains most of the powerful
high level instructions from similar BASICs.
EhBASIC is free but not copyright free. For non commercial use there is only one
restriction, any derivative work should include, in any binary image distributed,
the string "Derived from EhBASIC" and in any distribution that includes human
readable files a file that includes the above string in a human readable form
e.g. not as a comment in an HTML file.
For commercial use please contact me, Lee Davison, at leeedavison@googlemail.com
for conditions.
For more information on EhBASIC, other versions of EhBASIC and other projects
please visit my site at ..
http://mycorner.no-ip.org/index.html
P.S. c't magazin, henceforth refered to as "those thieving german bastards", are
prohibited from using this or any version of EhBASIC for any of their projects
or products. The excuse "we don't charge people for it" doesn't wash, it adds
value to your product so you owe me.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,449 @@
; minimal monitor for EhBASIC and 6502 simulator V1.05
; To run EhBASIC on the simulator load and assemble [F7] this file, start the simulator
; running [F6] then start the code with the RESET [CTRL][SHIFT]R. Just selecting RUN
; will do nothing, you'll still have to do a reset to run the code.
.feature labels_without_colons
.include "basic.asm"
; put the IRQ and MNI code in RAM so that it can be changed
IRQ_vec = VEC_SV+2 ; IRQ code vector
NMI_vec = IRQ_vec+$0A ; NMI code vector
; setup for the 6502 simulator environment
IO_AREA = $8800
ACIAdata = IO_AREA ; simulated ACIA r/w port
ACIAstatus = IO_AREA+1
ACIAcommand = IO_AREA+2
ACIAcontrol = IO_AREA+3
CRTC_A = $9000 ; CRTC register select
CRTC_C = $9001 ; CRTC register IO
CADDR_L = $E0 ; Cursor address (low)
CADDR_H = $E1 ; Cursor address (high)
C_COL = $E2 ; Cursor column
C_ROW = $E3 ; Cursor row
COUTC = $E4 ; Temp storage for char out.
R_STRT = $E5 ; E5,E6 contains starting address of
; current row
R_NXT = $E7 ; E7,E8 contains starting address of
; next row
N_ROWS = 24
N_COLS = 40
; now the code. all this does is set up the vectors and interrupt code
; and wait for the user to select [C]old or [W]arm start. nothing else
; fits in less than 128 bytes
.segment "MONITOR"
.org $FC00 ; pretend this is in a 1/8K ROM
; reset vector points here
RES_vec:
CLD ; clear decimal mode
LDX #$FF ; empty stack
TXS ; set the stack
;; Initialize the CRTC
LDA #$01
STA CRTC_A
LDA #N_COLS ; 40 columns
STA CRTC_C
LDA #$06
STA CRTC_A
LDA #N_ROWS ; 24 rows
STA CRTC_C
LDA #$70
STA CADDR_H
LDA #$00
STA CADDR_L
STA C_ROW
STA C_COL
JSR CLRVID
;; TODO: Initialize params on CRTC
; Initialize the ACIA
ACIA_init:
LDA #$00
STA ACIAstatus ; Soft reset
LDA #$0B
STA ACIAcommand ; Parity disabled, IRQ disabled
LDA #$1E
STA ACIAcontrol ; Set output for 8-N-1 9600
; set up vectors and interrupt code, copy them to page 2
LDY #END_CODE-LAB_vec ; set index/count
LAB_stlp:
LDA LAB_vec-1,Y ; get byte from interrupt code
STA VEC_IN-1,Y ; save to RAM
DEY ; decrement index/count
BNE LAB_stlp ; loop if more to do
; now do the signon message, Y = $00 here
LAB_signon:
LDA LAB_mess,Y ; get byte from sign on message
BEQ LAB_nokey ; exit loop if done
JSR V_OUTP ; output character
INY ; increment index
BNE LAB_signon ; loop, branch always
LAB_nokey:
JSR V_INPT ; call scan input device
BCC LAB_nokey ; loop if no key
AND #$DF ; mask xx0x xxxx, ensure upper case
CMP #'W' ; compare with [W]arm start
BEQ LAB_dowarm ; branch if [W]arm start
CMP #'C' ; compare with [C]old start
BNE RES_vec ; loop if not [C]old start
JMP LAB_COLD ; do EhBASIC cold start
LAB_dowarm:
JMP LAB_WARM ; do EhBASIC warm start
ACIAout:
PHA
@loop: LDA ACIAstatus
AND #$10
BEQ @loop ; Wait for buffer to empty
PLA
STA ACIAdata
RTS
;;; Byte out to the CRTC
;;;
;;; 1. Increment cursor position.
;;; 2. Scroll if necessary.
;;; 3. Store new cursor position in CRTC.
CRTCout:
JSR ACIAout ; Also echo to terminal for debugging.
;; In parallel, we're maintaining two states:
;; - The address of the cursor, and
;; - The Column/Row of the cursor.
;;
;; The latter state is used to handle scrolling and
;; for knowing how far to back up the cursor
;; for a carriage-return.
;; Backspace
CMP #$08
BEQ DO_BS
;; Line Feed
CMP #$0a
BEQ DO_LF
;; Carriage Return
CMP #$0d
BEQ DO_CR
;; Any other character...
STA COUTC ; Store the character going out
JSR COUT1 ; Output it where the cursor is.
;; We might need to scroll...
LDA C_ROW
CMP #N_ROWS-1
BNE @noscr
LDA C_COL
CMP #N_COLS-1
BNE @noscr
;; ... yup, we DO need to scroll.
JSR DO_SCROLL
RTS
@noscr: JSR INC_CADDR
INC C_COL
JSR SET_CURSOR
RTS
;; Handle a back-space character.
DO_BS: LDA C_COL
BNE @l1 ; Skip next bit
LDA #N_COLS-1
STA C_COL
BNE @l2
@l1: DEC C_COL
@l2: JSR DEC_CADDR ; Back up
JSR SET_CURSOR
@l3: LDA #$20
STA COUTC ; Echo a space ($20)
JSR COUT1
RTS
DO_LF: RTS ; Just swallow LF. CR emulates it.
DO_CR: SEC
LDA CADDR_L ; 1. Carriage return to start of row.
SBC C_COL
STA CADDR_L
LDA CADDR_H
SBC #$00 ; Will decrement H if carry was left
; set.
STA CADDR_H
;; 1. Are we on the last row? Scroll.
LDA C_ROW
CMP #N_ROWS-1
BNE @inc
JSR DO_SCROLL
RTS
@inc: JSR INCROW
@lf: CLC
LDA CADDR_L
ADC #$28 ; Now add $28
STA CADDR_L
LDA CADDR_H
ADC #$00 ; Will increment if carry was set
STA CADDR_H
LDA #$00 ; Reset cursor row to 0
STA C_COL
JSR SET_CURSOR
RTS
SET_CURSOR:
LDA #14
STA CRTC_A
LDA CADDR_H
STA CRTC_C
LDA #15
STA CRTC_A
LDA CADDR_L
STA CRTC_C
RTS
;; Increment C_ROW and put new
;; line start address in R_STRT,R_STRT+1
INCROW: TYA
PHA
INC C_ROW
BNE DECR2 ; Share code with DECROW
;; Decrement C_ROW and put new
;; line start address in R_STRT,R_STRT+1
DECROW: TYA
PHA
DEC C_ROW
DECR2: JSR SETRSTRT
PLA
TAY
RTS
;; Send the cursor home to the start of the line
HOME:
LDY C_ROW
LDA CRA_LO,Y
STA CADDR_L
LDA CRA_HI,Y
STA CADDR_H
LDA #$00
STA C_COL
RTS
;; Set the start address of the current row
SETRSTRT:
LDY C_ROW
LDA CRA_LO,Y
STA R_STRT
LDA CRA_HI,Y
STA R_STRT+1
RTS
;; Clear the current video line
CLRLN: LDA #$20
LDY #$00
@l1: STA (R_STRT),Y
INY
CPY #N_COLS
BNE @l1
RTS
;; Clear the video window and put the cursor at the home
;; position
CLRVID:
LDA #$20
LDX #N_ROWS-1
@l1: STX C_ROW
JSR SETRSTRT
JSR CLRLN
DEX
BPL @l1
RTS
;; Handle a scroll request.
;;
;; The cursor could be anywhere on the line when this scroll
;; request comes in.
DO_SCROLL:
PHA
TYA
PHA
TXA
PHA
;; The idea here is to copy each line to the line above it
LDX #$00
@l1: LDA CRA_LO,X
STA R_STRT
LDA CRA_HI,X
STA R_STRT+1
INX
LDA CRA_LO,X
STA R_NXT
LDA CRA_HI,X
STA R_NXT+1
LDY #0
@l2: LDA (R_NXT),Y
STA (R_STRT),Y
INY
CPY #N_COLS
BNE @l2
CPX #N_ROWS
BNE @l1
;; Clear the line the cursor occupies.
JSR CLRLN
;; Move the cursor to the beginning of the line
JSR HOME
JSR SET_CURSOR
PLA
TAX
PLA
TAY ; Restore Y
PLA
RTS
;; Decrement the cursor address
INC_CADDR:
INC CADDR_L
BNE @l1 ; Did we increment to 0?
INC CADDR_H ; Yes, also increment high
@l1: RTS
;; Increment the cursor address
DEC_CADDR:
CMP CADDR_L ; Is low alrady 0?
BNE @l1
DEC CADDR_H ; Yes, decrement high
@l1: DEC CADDR_L
RTS
COUT1:
TYA
PHA ; Save Y
LDY #$00
LDA COUTC
STA (CADDR_L),Y
PLA
TAY ; Restore Y
RTS
;;;
;;; byte in from ACIA. This subroutine will also force
;;; all lowercase letters to be uppercase, because EhBASIC
;;; only understands upper-case tokens
;;;
ACIAin:
LDA ACIAstatus ; Read 6551 status
AND #$08 ;
BEQ LAB_nobyw ; If rx buffer empty, no byte
LDA ACIAdata ; Read byte from 6551
CMP #'a' ; Is it < 'a'?
BCC @done ; Yes, we're done
CMP #'{' ; Is it >= '{'?
BCS @done ; Yes, we're done
AND #$5f ; Otherwise, mask to uppercase
@done:
SEC ; Flag byte received
RTS
LAB_nobyw:
CLC ; flag no byte received
no_load: ; empty load vector for EhBASIC
no_save: ; empty save vector for EhBASIC
RTS
; vector tables
LAB_vec:
.word ACIAin ; byte in from simulated ACIA
.word CRTCout ; byte out to simulated ACIA
.word no_load ; null load vector for EhBASIC
.word no_save ; null save vector for EhBASIC
; EhBASIC IRQ support
IRQ_CODE:
PHA ; save A
LDA IrqBase ; get the IRQ flag byte
LSR ; shift the set b7 to b6, and on down ...
ORA IrqBase ; OR the original back in
STA IrqBase ; save the new IRQ flag byte
PLA ; restore A
RTI
; EhBASIC NMI support
NMI_CODE:
PHA ; save A
LDA NmiBase ; get the NMI flag byte
LSR ; shift the set b7 to b6, and on down ...
ORA NmiBase ; OR the original back in
STA NmiBase ; save the new NMI flag byte
PLA ; restore A
RTI
END_CODE:
;;; CRTC row addresses: HIGH
CRA_HI:
.byte $70,$70,$70,$70,$70,$70,$70,$71
.byte $71,$71,$71,$71,$71,$72,$72,$72
.byte $72,$72,$72,$72,$73,$73,$73,$73
;;; CRTC row addresses: LOW
CRA_LO:
.byte $00,$28,$50,$78,$A0,$C8,$F0,$18
.byte $40,$68,$90,$B8,$E0,$08,$30,$58
.byte $80,$A8,$D0,$F8,$20,$48,$70,$98
; sign on string
LAB_mess:
.byte $0D,$0A,"SYMON (C) 2008-2014, SETH MORABITO"
.byte $0D,$0A,"ENHANCED 6502 BASIC 2.22 (C) LEE DAVISON"
.byte $0D,$0A,"[C]OLD/[W]ARM ?",$00
; system vectors
.segment "VECTORS"
.org $FFFA
.word NMI_vec ; NMI vector
.word RES_vec ; RESET vector
.word IRQ_vec ; IRQ vector

View File

@ -0,0 +1,18 @@
MEMORY {
RAM1: start = $0000, size = $8000 - __STACKSIZE__;
ROM1: start = $C000, size = $3C00, fill = yes;
MONITOR: start = $FC00, size = $3FA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
}
SEGMENTS {
CODE: load = ROM1, type = ro;
DATA: load = ROM1, type = ro;
MONITOR: load = MONITOR, type = ro;
VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__: type = weak, value = $0300;
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

BIN
screenshots/breakpoints.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,85 @@
package com.loomcom.symon;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.Utils;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.TreeSet;
public class Breakpoints extends AbstractTableModel {
private TreeSet<Integer> breakpoints;
private Simulator simulator;
public Breakpoints(Simulator simulator) {
this.breakpoints = new TreeSet<>();
this.simulator = simulator;
}
public boolean contains(int address) {
return this.breakpoints.contains(address);
}
public void addBreakpoint(int address) {
this.breakpoints .add(address);
fireTableDataChanged();
}
public void removeBreakpoint(int address) {
this.breakpoints.remove(address);
fireTableDataChanged();
}
public void removeBreakpointAtIndex(int index) {
if (index < 0) {
return;
}
ArrayList<Integer> values = new ArrayList<>(breakpoints);
int value = values.get(index);
this.breakpoints.remove(value);
fireTableDataChanged();
}
public void refresh() {
fireTableDataChanged();
}
@Override
public String getColumnName(int index) {
if (index == 0) {
return "Address";
} else {
return "Inst";
}
}
@Override
public int getRowCount() {
return breakpoints.size();
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
ArrayList<Integer> values = new ArrayList<>(breakpoints);
if (columnIndex == 0) {
return "$" + Utils.wordToHex(values.get(rowIndex));
} else if (columnIndex == 1) {
int address = values.get(rowIndex);
try {
return simulator.disassembleOpAtAddress(address);
} catch (MemoryAccessException ex) {
return "???";
}
} else {
return null;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -26,6 +26,7 @@ package com.loomcom.symon;
import com.loomcom.symon.devices.Device;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -42,7 +43,7 @@ public class Bus {
// The default address at which to load programs
public static int DEFAULT_LOAD_ADDRESS = 0x0200;
// By default, our bus starts at 0, and goes up to 64K
private int startAddress = 0x0000;
private int endAddress = 0xffff;
@ -52,17 +53,17 @@ public class Bus {
// Ordered sets of IO devices, associated with their priority
private Map<Integer, SortedSet<Device>> deviceMap;
// an array for quick lookup of adresses, brute-force style
private Device[] deviceAddressArray;
public Bus(int size) {
this(0, size - 1);
}
public Bus(int startAddress, int endAddress) {
this.deviceMap = new HashMap<Integer, SortedSet<Device>>();
this.deviceMap = new HashMap<>();
this.startAddress = startAddress;
this.endAddress = endAddress;
}
@ -74,67 +75,70 @@ public class Bus {
public int endAddress() {
return endAddress;
}
private void buildDeviceAddressArray() {
int size = (this.endAddress - this.startAddress) + 1;
deviceAddressArray = new Device[size];
// getDevices() provides an OrderedSet with devices ordered by priorities
for(Device device : getDevices()) {
for (Device device : getDevices()) {
MemoryRange range = device.getMemoryRange();
for(int address = range.startAddress; address <= range.endAddress; ++address) {
for (int address = range.startAddress; address <= range.endAddress; ++address) {
deviceAddressArray[address - this.startAddress] = device;
}
}
}
/**
* Add a device to the bus.
*
* @param device
* @param priority
* @param device Device to add
* @param priority Bus prioirity.
* @throws MemoryRangeException
*/
public void addDevice(Device device, int priority) throws MemoryRangeException {
MemoryRange range = device.getMemoryRange();
if(range.startAddress() < this.startAddress || range.startAddress() > this.endAddress) {
if (range.startAddress() < this.startAddress || range.startAddress() > this.endAddress) {
throw new MemoryRangeException("start address of device " + device.getName() + " does not fall within the address range of the bus");
}
if(range.endAddress() < this.startAddress || range.endAddress() > this.endAddress) {
if (range.endAddress() < this.startAddress || range.endAddress() > this.endAddress) {
throw new MemoryRangeException("end address of device " + device.getName() + " does not fall within the address range of the bus");
}
SortedSet<Device> deviceSet = deviceMap.get(priority);
if(deviceSet == null) {
deviceSet = new TreeSet<Device>();
if (deviceSet == null) {
deviceSet = new TreeSet<>();
deviceMap.put(priority, deviceSet);
}
device.setBus(this);
deviceSet.add(device);
buildDeviceAddressArray();
}
/**
* Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others.
*
* @param device
* @param device Device to add
* @throws MemoryRangeException
*/
public void addDevice(Device device) throws MemoryRangeException {
addDevice(device, 0);
}
/**
* Remove a device from the bus.
*
* @param device
* @param device Device to remove
*/
public void removeDevice(Device device) {
for(SortedSet<Device> deviceSet : deviceMap.values()) {
for (SortedSet<Device> deviceSet : deviceMap.values()) {
deviceSet.remove(device);
}
buildDeviceAddressArray();
@ -151,39 +155,39 @@ public class Bus {
* device.
*/
public boolean isComplete() {
if(deviceAddressArray == null) {
if (deviceAddressArray == null) {
buildDeviceAddressArray();
}
for(int address = startAddress; address <= endAddress; ++address) {
if(deviceAddressArray[address - startAddress] == null) {
for (int address = startAddress; address <= endAddress; ++address) {
if (deviceAddressArray[address - startAddress] == null) {
return false;
}
}
return true;
}
public int read(int address) throws MemoryAccessException {
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);
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));
}
@ -213,18 +217,18 @@ public class Bus {
public SortedSet<Device> getDevices() {
// create an ordered set of devices, ordered by device priorities
SortedSet<Device> devices = new TreeSet<Device>();
List<Integer> priorities = new ArrayList<Integer>(deviceMap.keySet());
SortedSet<Device> devices = new TreeSet<>();
List<Integer> priorities = new ArrayList<>(deviceMap.keySet());
Collections.sort(priorities);
for(int priority : priorities) {
for (int priority : priorities) {
SortedSet<Device> deviceSet = deviceMap.get(priority);
for(Device device : deviceSet) {
for (Device device : deviceSet) {
devices.add(device);
}
}
return devices;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,177 @@
package com.loomcom.symon;
import com.loomcom.symon.util.Utils;
/**
* A compact, struct-like representation of CPU state.
*/
public class CpuState {
/**
* Accumulator
*/
public int a;
/**
* X index register
*/
public int x;
/**
* Y index register
*/
public int y;
/**
* Stack Pointer
*/
public int sp;
/**
* Program Counter
*/
public int pc;
/**
* Last Loaded Instruction Register
*/
public int ir;
/**
* Peek-Ahead to next IR
*/
public int nextIr;
public int[] args = new int[2];
public int[] nextArgs = new int[2];
public int instSize;
public boolean opTrap;
public boolean irqAsserted;
public boolean nmiAsserted;
public int lastPc;
/* Status Flag Register bits */
public boolean carryFlag;
public boolean negativeFlag;
public boolean zeroFlag;
public boolean irqDisableFlag;
public boolean decimalModeFlag;
public boolean breakFlag;
public boolean overflowFlag;
public long stepCounter = 0L;
public CpuState() {}
/**
* Snapshot a copy of the CpuState. (This is a copy constructor rather than an
* implementation of <code>Cloneable</code> based on Josh Bloch's recommendation)
*
* @param s The CpuState to copy.
*/
@SuppressWarnings("CopyConstructorMissesField")
public CpuState(CpuState s) {
this.a = s.a;
this.x = s.x;
this.y = s.y;
this.sp = s.sp;
this.pc = s.pc;
this.ir = s.ir;
this.nextIr = s.nextIr;
this.lastPc = s.lastPc;
this.args[0] = s.args[0];
this.args[1] = s.args[1];
this.nextArgs[0] = s.nextArgs[0];
this.nextArgs[1] = s.nextArgs[1];
this.instSize = s.instSize;
this.opTrap = s.opTrap;
this.nmiAsserted = s.nmiAsserted;
this.irqAsserted = s.irqAsserted;
this.carryFlag = s.carryFlag;
this.negativeFlag = s.negativeFlag;
this.zeroFlag = s.zeroFlag;
this.irqDisableFlag = s.irqDisableFlag;
this.decimalModeFlag = s.decimalModeFlag;
this.breakFlag = s.breakFlag;
this.overflowFlag = s.overflowFlag;
this.stepCounter = s.stepCounter;
}
/**
* Returns a string formatted for the trace log.
*
* @return a string formatted for the trace log.
*/
public String toTraceEvent() {
String opcode = Cpu.disassembleOp(ir, args);
return getInstructionByteStatus() + " " +
String.format("%-14s", opcode) +
"A:" + Utils.byteToHex(a) + " " +
"X:" + Utils.byteToHex(x) + " " +
"Y:" + Utils.byteToHex(y) + " " +
"F:" + Utils.byteToHex(getStatusFlag()) + " " +
"S:1" + Utils.byteToHex(sp) + " " +
getProcessorStatusString() + "\n";
}
/**
* @return The value of the Process Status Register, as a byte.
*/
public int getStatusFlag() {
int status = 0x20;
if (carryFlag) {
status |= Cpu.P_CARRY;
}
if (zeroFlag) {
status |= Cpu.P_ZERO;
}
if (irqDisableFlag) {
status |= Cpu.P_IRQ_DISABLE;
}
if (decimalModeFlag) {
status |= Cpu.P_DECIMAL;
}
if (breakFlag) {
status |= Cpu.P_BREAK;
}
if (overflowFlag) {
status |= Cpu.P_OVERFLOW;
}
if (negativeFlag) {
status |= Cpu.P_NEGATIVE;
}
return status;
}
public String getInstructionByteStatus() {
switch (Cpu.instructionSizes[ir]) {
case 0:
case 1:
return Utils.wordToHex(lastPc) + " " +
Utils.byteToHex(ir) + " ";
case 2:
return Utils.wordToHex(lastPc) + " " +
Utils.byteToHex(ir) + " " +
Utils.byteToHex(args[0]) + " ";
case 3:
return Utils.wordToHex(lastPc) + " " +
Utils.byteToHex(ir) + " " +
Utils.byteToHex(args[0]) + " " +
Utils.byteToHex(args[1]);
default:
return null;
}
}
/**
* @return A string representing the current status register state.
*/
public String getProcessorStatusString() {
return "[" + (negativeFlag ? 'N' : '.') +
(overflowFlag ? 'V' : '.') +
"-" +
(breakFlag ? 'B' : '.') +
(decimalModeFlag ? 'D' : '.') +
(irqDisableFlag ? 'I' : '.') +
(zeroFlag ? 'Z' : '.') +
(carryFlag ? 'C' : '.') +
"]";
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2012 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -31,7 +31,7 @@ public interface InstructionTable {
*
* TODO: As of version 0.6, this is still not used! All CPUs are "idealized" NMOS 6502 only.
*/
public enum CpuBehavior {
enum CpuBehavior {
/**
* The earliest NMOS 6502 includes a bug that causes the ROR instruction
* to behave like an ASL that does not affect the carry bit. This version
@ -46,32 +46,33 @@ public interface InstructionTable {
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_WITH_INDIRECT_JMP_BUG,
/**
* Emulate an NMOS 6502 without the indirect JMP bug. This type of 6502
* does not actually exist in the wild.
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_WITHOUT_INDIRECT_JMP_BUG,
NMOS_6502,
/**
* Emulate a CMOS 65C02, with all CMOS instructions and addressing modes.
*/
CMOS
CMOS_6502,
/**
* Emulate a CMOS 65C816.
*/
CMOS_65816
}
/**
* Enumeration of Addressing Modes.
*/
public enum Mode {
enum Mode {
ACC {
public String toString() {
return "Accumulator";
}
},
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,185 @@ public interface InstructionTable {
// 6502 opcodes. No 65C02 opcodes implemented.
/**
* Instruction opcode names.
* Instruction opcode names. This lists all opcodes for
* NMOS 6502, CMOS 65C02, and CMOS 65C816
*/
public static final String[] opcodeNames = {
"BRK", "ORA", null, null, null, "ORA", "ASL", null,
"PHP", "ORA", "ASL", null, null, "ORA", "ASL", null,
"BPL", "ORA", null, null, null, "ORA", "ASL", null,
"CLC", "ORA", null, null, null, "ORA", "ASL", null,
"JSR", "AND", null, null, "BIT", "AND", "ROL", null,
"PLP", "AND", "ROL", null, "BIT", "AND", "ROL", null,
"BMI", "AND", null, null, null, "AND", "ROL", null,
"SEC", "AND", null, null, null, "AND", "ROL", null,
"RTI", "EOR", null, null, null, "EOR", "LSR", null,
"PHA", "EOR", "LSR", null, "JMP", "EOR", "LSR", null,
"BVC", "EOR", null, null, null, "EOR", "LSR", null,
"CLI", "EOR", null, null, null, "EOR", "LSR", null,
"RTS", "ADC", null, null, null, "ADC", "ROR", null,
"PLA", "ADC", "ROR", null, "JMP", "ADC", "ROR", null,
"BVS", "ADC", null, null, null, "ADC", "ROR", null,
"SEI", "ADC", null, null, null, "ADC", "ROR", null,
"BCS", "STA", null, null, "STY", "STA", "STX", null,
"DEY", null, "TXA", null, "STY", "STA", "STX", null,
"BCC", "STA", null, null, "STY", "STA", "STX", null,
"TYA", "STA", "TXS", null, null, "STA", null, null,
"LDY", "LDA", "LDX", null, "LDY", "LDA", "LDX", null,
"TAY", "LDA", "TAX", null, "LDY", "LDA", "LDX", null,
"BCS", "LDA", null, null, "LDY", "LDA", "LDX", null,
"CLV", "LDA", "TSX", null, "LDY", "LDA", "LDX", null,
"CPY", "CMP", null, null, "CPY", "CMP", "DEC", null,
"INY", "CMP", "DEX", null, "CPY", "CMP", "DEC", null,
"BNE", "CMP", null, null, null, "CMP", "DEC", null,
"CLD", "CMP", null, null, null, "CMP", "DEC", null,
"CPX", "SBC", null, null, "CPX", "SBC", "INC", null,
"INX", "SBC", "NOP", null, "CPX", "SBC", "INC", null,
"BEQ", "SBC", null, null, null, "SBC", "INC", null,
"SED", "SBC", null, null, null, "SBC", "INC", null
String[] opcodeNames = {
"BRK", "ORA", "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
*/
public static final Mode[] instructionModes = {
Mode[] instructionModes = {
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x00-0x03
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x04-0x07
Mode.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
*/
public static final int[] instructionSizes = {
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 0, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0,
2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0
int[] instructionSizes = {
1, 2, 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.
*/
public static final int[] instructionClocks = {
7, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 0, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 3, 3, 5, 0, 4, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 3, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 0, 3, 5, 0, 4, 2, 2, 0, 5, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
2, 6, 0, 0, 3, 3, 3, 0, 2, 0, 2, 0, 4, 4, 4, 0,
2, 6, 0, 0, 4, 4, 4, 0, 2, 5, 2, 0, 0, 5, 0, 0,
2, 6, 2, 0, 3, 3, 3, 0, 2, 2, 2, 0, 4, 4, 4, 0,
2, 5, 0, 0, 4, 4, 4, 0, 2, 4, 2, 0, 4, 4, 4, 0,
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0
int[] instructionClocksNmos = {
7, 6, 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
};
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -26,7 +26,11 @@
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.BenEaterMachine;
import org.apache.commons.cli.*;
import java.util.Locale;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
@ -38,66 +42,128 @@ public class Main {
* Main entry point to the simulator. Creates a simulator and shows the main
* window.
*
* @param args
* @param args Program arguments
*/
public static void main(String args[]) throws Exception {
public static void main(String[] args) throws Exception {
Class machineClass = SymonMachine.class;
for(int i = 0; i < args.length; ++i) {
String arg = args[i].toLowerCase(Locale.ENGLISH);
if(arg.equals("-machine") && (i+1) < args.length) {
String machine = args[i+1].trim().toLowerCase(Locale.ENGLISH);
if(machine.equals("symon")) {
machineClass = SymonMachine.class;
} else if(machine.equals("multicomp")) {
machineClass = MulticompMachine.class;
Options options = new Options();
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("machine")) {
String machine = line.getOptionValue("machine").toLowerCase(Locale.ENGLISH);
switch (machine) {
case "multicomp":
machineClass = MulticompMachine.class;
break;
case "simple":
machineClass = SimpleMachine.class;
break;
case "symon":
machineClass = SymonMachine.class;
break;
case "beneater":
machineClass = BenEaterMachine.class;
break;
default:
System.err.println("Could not start Symon. Unknown machine type " + machine);
return;
}
}
}
while(true) {
if(machineClass == null) {
Object[] possibilities = {"Symon", "Multicomp"};
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 {
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:
System.err.println("Could not start Symon. Unknown cpu type " + cpu);
return;
}
}
final Simulator simulator = new Simulator(machineClass);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi();
} catch (Exception e) {
e.printStackTrace();
if (line.hasOption("rom")) {
romFile = line.getOptionValue("rom");
}
if (line.hasOption("brk")) {
haltOnBreak = true;
}
while (true) {
if (machineClass == null) {
Object[] possibilities = {"Symon", "Multicomp", "Simple", "BenEater"};
String s = (String)JOptionPane.showInputDialog(
null,
"Please choose the machine type to be emulated:",
"Machine selection",
JOptionPane.PLAIN_MESSAGE,
null,
possibilities,
"Symon");
if (s != null && s.equals("Multicomp")) {
machineClass = MulticompMachine.class;
} else if (s != null && s.equals("Simple")) {
machineClass = SimpleMachine.class;
} else if (s != null && s.equals("BenEater")) {
machineClass = BenEaterMachine.class;
} else {
machineClass = SymonMachine.class;
}
}
});
Simulator.MAIN_CMD cmd = simulator.waitForCommand();
if(cmd.equals(Simulator.MAIN_CMD.SELECTMACHINE)) {
machineClass = null;
} else {
break;
if (cpuBehavior == null) {
cpuBehavior = InstructionTable.CpuBehavior.NMOS_6502;
}
final Simulator simulator = new Simulator(machineClass, cpuBehavior, romFile, haltOnBreak);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi();
} catch (Exception e) {
e.printStackTrace();
}
}
});
Simulator.MainCommand cmd = simulator.waitForCommand();
if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) {
machineClass = null;
} else {
break;
}
}
} catch (ParseException ex) {
System.err.println("Could not start Symon. Reason: " + ex.getMessage());
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -34,77 +34,78 @@ import com.loomcom.symon.exceptions.*;
*/
public class MemoryRange implements Comparable<MemoryRange> {
/** The starting address of the memory range. */
public int startAddress;
/** The ending address of the memory range. */
public int endAddress;
/**
* The starting address of the memory range.
*/
public int startAddress;
/**
* The ending address of the memory range.
*/
public int endAddress;
public MemoryRange(int startAddress, int endAddress)
throws MemoryRangeException {
if (startAddress < 0 || endAddress < 0) {
throw new MemoryRangeException("Addresses cannot be less than 0.");
public MemoryRange(int startAddress, int endAddress)
throws MemoryRangeException {
if (startAddress < 0 || endAddress < 0) {
throw new MemoryRangeException("Addresses cannot be less than 0.");
}
if (startAddress >= endAddress) {
throw new MemoryRangeException("End address must be greater " +
"than start address.");
}
this.startAddress = startAddress;
this.endAddress = endAddress;
}
if (startAddress >= endAddress) {
throw new MemoryRangeException("End address must be greater " +
"than start address.");
/**
* @return the starting address.
*/
public int startAddress() {
return startAddress;
}
this.startAddress = startAddress;
this.endAddress = endAddress;
}
/**
* @returns the starting address.
*/
public int startAddress() {
return startAddress;
}
/**
* @returns the ending address.
*/
public int endAddress() {
return endAddress;
}
/**
* Checks for address inclusion in the range.
*
* @returns true if the address is included within this range,
* false otherwise.
*/
public boolean includes(int address) {
return (address <= endAddress &&
address >= startAddress);
}
/**
* Checks for overlapping memory ranges.
*
* @returns true if this range overlaps in any way with the other.
*/
public boolean overlaps(MemoryRange other) {
return (this.includes(other.startAddress()) ||
other.includes(this.startAddress()));
}
// Implementation of Comparable interface
public int compareTo(MemoryRange other) {
if (other == null) {
throw new NullPointerException("Cannot compare to null.");
/**
* @return the ending address.
*/
public int endAddress() {
return endAddress;
}
if (this == other) {
return 0;
}
Integer thisStartAddr = new Integer(this.startAddress());
Integer thatStartAddr = new Integer(other.startAddress());
return thisStartAddr.compareTo(thatStartAddr);
}
public String toString() {
StringBuffer desc = new StringBuffer("@");
desc.append(String.format("0x%04x", startAddress));
desc.append("-");
desc.append(String.format("0x%04x", endAddress));
return desc.toString();
}
/**
* Checks for address inclusion in the range.
*
* @return true if the address is included within this range,
* false otherwise.
*/
public boolean includes(int address) {
return (address <= endAddress &&
address >= startAddress);
}
/**
* Checks for overlapping memory ranges.
*
* @return true if this range overlaps in any way with the other.
*/
public boolean overlaps(MemoryRange other) {
return (this.includes(other.startAddress()) ||
other.includes(this.startAddress()));
}
// Implementation of Comparable interface
public int compareTo(MemoryRange other) {
if (other == null) {
throw new NullPointerException("Cannot compare to null.");
}
if (this == other) {
return 0;
}
Integer thisStartAddr = this.startAddress();
Integer thatStartAddr = other.startAddress();
return thisStartAddr.compareTo(thatStartAddr);
}
public String toString() {
return "@" + String.format("0x%04x", startAddress) + "-" +
String.format("0x%04x", endAddress);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -27,15 +27,13 @@ import javax.swing.*;
public interface Preferences {
public static final int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300;
int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300;
public static final boolean DEFAULT_HALT_ON_BREAK = true;
JDialog getDialog();
public JDialog getDialog();
int getProgramStartAddress();
public int getProgramStartAddress();
boolean getHaltOnBreak();
public boolean getHaltOnBreak();
public void updateUi();
void updateUi();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyrighi (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -24,21 +24,21 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.FifoUnderrunException;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import com.loomcom.symon.exceptions.SymonException;
import com.loomcom.symon.exceptions.*;
import com.loomcom.symon.machines.Machine;
import com.loomcom.symon.ui.*;
import com.loomcom.symon.ui.Console;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.monitor.CounterMonitor;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Symon Simulator Interface and Control.
@ -50,10 +50,15 @@ import java.util.logging.Logger;
*/
public class Simulator {
private final static Logger logger = LoggerFactory.getLogger(Simulator.class.getName());
// UI constants
private static final int DEFAULT_FONT_SIZE = 12;
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
private static final int CONSOLE_BORDER_WIDTH = 10;
private static final int DEFAULT_FONT_SIZE = 12;
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
private static final int CONSOLE_BORDER_WIDTH = 10;
// Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz.
private static final long[] CLOCK_PERIODS = {0, 1000, 500, 333, 250, 200, 167, 143, 125};
// Since it is very expensive to update the UI with Swing's Event Dispatch Thread, we can't afford
// to refresh the status view on every simulated clock cycle. Instead, we will only refresh the status view
@ -66,14 +71,12 @@ public class Simulator {
//
private static final int MAX_STEPS_BETWEEN_UPDATES = 20000;
private final static Logger logger = Logger.getLogger(Simulator.class.getName());
// The simulated machine
private Machine machine;
// Number of CPU steps between CRT repaints.
// TODO: Dynamically refresh the value at runtime based on performance figures to reach ~ 30fps.
private long stepsBetweenCrtcRefreshes = 2500;
private static final long STEPS_BETWEEN_CRTC_REFRESHES = 2500;
// A counter to keep track of the number of UI updates that have been
// requested
@ -93,43 +96,70 @@ public class Simulator {
/**
* The Trace Window shows the most recent 50,000 CPU states.
*/
private TraceLog traceLog;
private final TraceLog traceLog;
/**
* The Memory Window shows the contents of one page of memory.
*/
private MemoryWindow memoryWindow;
private final MemoryWindow memoryWindow;
private VideoWindow videoWindow;
private final VideoWindow videoWindow;
private final BreakpointsWindow breakpointsWindow;
private SimulatorMenu menuBar;
private RunLoop runLoop;
private Console console;
private RunLoop runLoop;
private Console console;
private StatusPanel statusPane;
private JButton runStopButton;
private JButton stepButton;
private JButton resetButton;
private JComboBox<String> stepCountBox;
private JFileChooser fileChooser;
private JFileChooser fileChooser;
private PreferencesDialog preferences;
private Breakpoints breakpoints;
private final Object commandMonitorObject = new Object();
private MAIN_CMD command = MAIN_CMD.NONE;
public static enum MAIN_CMD {
private MainCommand command = MainCommand.NONE;
private boolean haltOnBreak;
public enum MainCommand {
NONE,
SELECTMACHINE
}
/**
* The list of step counts that will appear in the "Step" drop-down.
*/
private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"};
public Simulator(Class machineClass) throws Exception {
this.machine = (Machine) machineClass.getConstructors()[0].newInstance();
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(romFile);
this.machine.getCpu().setBehavior(cpuType);
// Initialize final fields in the constructor.
this.traceLog = new TraceLog();
this.memoryWindow = new MemoryWindow(machine.getBus());
this.breakpointsWindow = new BreakpointsWindow(breakpoints, mainWindow);
if (machine.getCrtc() != null) {
videoWindow = new VideoWindow(machine.getCrtc(), 2, 2);
} else {
videoWindow = null;
}
}
/**
@ -137,19 +167,19 @@ public class Simulator {
*/
public void createAndShowUi() throws IOException {
mainWindow = new JFrame();
mainWindow.setTitle("Symon 6502 Simulator");
mainWindow.setTitle("6502 Simulator - " + machine.getName());
mainWindow.setResizable(false);
mainWindow.getContentPane().setLayout(new BorderLayout());
// UI components used for I/O.
this.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT);
this.statusPane = new StatusPanel();
this.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT, false);
this.statusPane = new StatusPanel(machine);
console.setBorderWidth(CONSOLE_BORDER_WIDTH);
// 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();
@ -161,10 +191,12 @@ public class Simulator {
runStopButton = new JButton("Run");
stepButton = new JButton("Step");
resetButton = new JButton("Reset");
JButton softResetButton = new JButton("Soft Reset");
JButton hardResetButton = new JButton("Hard Reset");
stepCountBox = new JComboBox<String>(STEPS);
stepCountBox = new JComboBox<>(STEPS);
stepCountBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
try {
JComboBox cb = (JComboBox) actionEvent.getSource();
@ -179,7 +211,8 @@ public class Simulator {
buttonContainer.add(runStopButton);
buttonContainer.add(stepButton);
buttonContainer.add(stepCountBox);
buttonContainer.add(resetButton);
buttonContainer.add(softResetButton);
buttonContainer.add(hardResetButton);
// Left side - console
consoleContainer.add(console, BorderLayout.CENTER);
@ -192,39 +225,40 @@ public class Simulator {
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
runStopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (runLoop != null && runLoop.isRunning()) {
handleStop();
Simulator.this.handleStop();
} else {
handleStart();
Simulator.this.handleStart();
}
}
});
stepButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
handleStep(stepsPerClick);
Simulator.this.handleStep(stepsPerClick);
}
});
resetButton.addActionListener(new ActionListener() {
softResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
handleReset();
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(false);
}
});
// Prepare the log window
traceLog = new TraceLog();
hardResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(true);
}
});
// Prepare the memory window
memoryWindow = new MemoryWindow(machine.getBus());
// Composite Video and 6545 CRTC
if(machine.getCrtc() != null) {
videoWindow = new VideoWindow(machine.getCrtc(), 2, 2);
}
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// The Menu. This comes last, because it relies on other components having
// already been initialized.
@ -235,11 +269,11 @@ public class Simulator {
mainWindow.setVisible(true);
console.requestFocus();
handleReset();
handleReset(false);
}
public MAIN_CMD waitForCommand() {
synchronized(commandMonitorObject) {
public MainCommand waitForCommand() {
synchronized (commandMonitorObject) {
try {
commandMonitorObject.wait();
} catch (InterruptedException ex) {
@ -248,7 +282,7 @@ public class Simulator {
}
return command;
}
private void handleStart() {
// Shift focus to the console.
@ -268,7 +302,7 @@ public class Simulator {
/*
* Perform a reset.
*/
private void handleReset() {
private void handleReset(boolean isColdReset) {
if (runLoop != null && runLoop.isRunning()) {
runLoop.requestStop();
runLoop.interrupt();
@ -276,23 +310,24 @@ public class Simulator {
}
try {
logger.log(Level.INFO, "Reset requested. Resetting CPU.");
// Reset and clear memory
logger.debug("Reset requested. Resetting CPU.");
// Reset CPU
machine.getCpu().reset();
// Clear the console.
console.reset();
// Reset the trace log.
traceLog.reset();
// Update status.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(machine.getCpu());
memoryWindow.updateState();
// If we're doing a cold reset, clear the memory.
if (isColdReset) {
Memory mem = machine.getRam();
if (mem != null) {
mem.fill(0);
}
});
}
// Update status.
updateVisibleState();
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Exception during simulator reset: " + ex.getMessage());
logger.error("Exception during simulator reset", ex);
}
}
@ -304,17 +339,9 @@ public class Simulator {
for (int i = 0; i < numSteps; i++) {
step();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (traceLog.isVisible()) {
traceLog.refresh();
}
statusPane.updateState(machine.getCpu());
memoryWindow.updateState();
}
});
updateVisibleState();
} catch (SymonException ex) {
logger.log(Level.SEVERE, "Exception during simulator step: " + ex.getMessage());
logger.error("Exception during simulator step", ex);
ex.printStackTrace();
}
}
@ -329,40 +356,35 @@ public class Simulator {
// Read from the ACIA and immediately update the console if there's
// output ready.
if (machine.getAcia().hasTxChar()) {
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.
// TODO: Interrupt handling.
try {
if (console.hasInput()) {
if (machine.getAcia() != null && console.hasInput()) {
machine.getAcia().rxWrite((int) console.readInputChar());
}
} catch (FifoUnderrunException ex) {
logger.severe("Console type-ahead buffer underrun!");
logger.error("Console type-ahead buffer underrun!");
}
if (videoWindow != null && stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) {
if (videoWindow != null && stepsSinceLastCrtcRefresh++ > STEPS_BETWEEN_CRTC_REFRESHES) {
stepsSinceLastCrtcRefresh = 0;
if (videoWindow.isVisible()) {
videoWindow.repaint();
}
}
// This is a very expensive update, and we're doing it without
// a delay, so we don't want to overwhelm the Swing event processing thread
// with requests. Limit the number of ui updates that can be performed.
if (stepsSinceLastUpdate++ > MAX_STEPS_BETWEEN_UPDATES) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(machine.getCpu());
memoryWindow.updateState();
}
});
updateVisibleState();
stepsSinceLastUpdate = 0;
}
}
/**
@ -374,8 +396,7 @@ public class Simulator {
machine.getBus().write(addr++, program[i] & 0xff);
}
logger.log(Level.INFO, "Loaded " + i + " bytes at address 0x" +
Integer.toString(startAddress, 16));
logger.info("Loaded {} bytes at address 0x{}", i, Integer.toString(startAddress, 16));
// After loading, be sure to reset and
// Reset (but don't clear memory, naturally)
@ -385,16 +406,9 @@ public class Simulator {
machine.getCpu().setProgramCounter(preferences.getProgramStartAddress());
// Immediately update the UI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(machine.getCpu());
memoryWindow.updateState();
}
});
updateVisibleState();
}
/**
* The main run thread.
*/
@ -410,10 +424,11 @@ public class Simulator {
}
public void run() {
logger.log(Level.INFO, "Starting main run loop.");
logger.debug("Starting main run loop.");
isRunning = true;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
@ -429,13 +444,13 @@ public class Simulator {
step();
} while (shouldContinue());
} catch (SymonException ex) {
logger.log(Level.SEVERE, "Exception in main simulator run thread. Exiting run.");
ex.printStackTrace();
logger.error("Exception in main simulator run thread. Exiting run.", ex);
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
statusPane.updateState(machine.getCpu());
statusPane.updateState();
memoryWindow.updateState();
runStopButton.setText("Run");
stepButton.setEnabled(true);
@ -445,7 +460,6 @@ public class Simulator {
}
menuBar.simulatorDidStop();
traceLog.simulatorDidStop();
// TODO: Update memory window, if frame is visible.
}
});
@ -453,15 +467,19 @@ public class Simulator {
}
/**
* Returns true if the run loop should proceed to the next step.
*
* @return True if the run loop should proceed to the next step.
*/
private boolean shouldContinue() {
return isRunning && !(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00);
return !breakpoints.contains(machine.getCpu().getProgramCounter()) &&
isRunning &&
!(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00);
}
}
public String disassembleOpAtAddress(int address) throws MemoryAccessException {
return machine.getCpu().disassembleOpAtAddress(address);
}
class LoadProgramAction extends AbstractAction {
public LoadProgramAction() {
super("Load Program...", null);
@ -470,7 +488,6 @@ public class Simulator {
}
public void actionPerformed(ActionEvent actionEvent) {
// TODO: Error dialogs on failure.
try {
int retVal = fileChooser.showOpenDialog(mainWindow);
if (retVal == JFileChooser.APPROVE_OPTION) {
@ -479,7 +496,10 @@ public class Simulator {
long fileSize = f.length();
if (fileSize > machine.getMemorySize()) {
throw new IOException("Program will not fit in available memory.");
throw new IOException("File will not fit in " +
"available memory ($" +
Integer.toString(machine.getMemorySize(), 16) +
" bytes)");
} else {
byte[] program = new byte[(int) fileSize];
int i = 0;
@ -490,21 +510,32 @@ public class Simulator {
program[i++] = dis.readByte();
}
// Now load the program at the starting address.
loadProgram(program, preferences.getProgramStartAddress());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
console.reset();
breakpoints.refresh();
}
});
// Now load the program at the starting address.
loadProgram(program, preferences.getProgramStartAddress());
// TODO: "Don't Show Again" checkbox
JOptionPane.showMessageDialog(mainWindow,
"Loaded Successfully At " +
String.format("$%04X", preferences.getProgramStartAddress()),
"OK",
JOptionPane.PLAIN_MESSAGE);
}
}
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Unable to read program file: " + ex.getMessage());
logger.error("Unable to read program file.", ex);
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Memory access error loading program: " + ex.getMessage());
logger.error("Memory access error loading program", ex);
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
}
}
}
@ -517,7 +548,6 @@ public class Simulator {
}
public void actionPerformed(ActionEvent actionEvent) {
// TODO: Error dialogs on failure.
try {
int retVal = fileChooser.showOpenDialog(mainWindow);
if (retVal == JFileChooser.APPROVE_OPTION) {
@ -527,26 +557,40 @@ public class Simulator {
if (fileSize != machine.getRomSize()) {
throw new IOException("ROM file must be exactly " + String.valueOf(machine.getRomSize()) + " bytes.");
} else {
// Load the new ROM image
Memory rom = Memory.makeROM(machine.getRomBase(), machine.getRomBase() + machine.getRomSize() - 1, romFile);
machine.setRom(rom);
// Now, reset
machine.getCpu().reset();
logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " +
String.format("0x%04X", machine.getRomBase()));
}
// Load the new ROM image
Memory rom = Memory.makeROM(machine.getRomBase(), machine.getRomBase() + machine.getRomSize() - 1, romFile);
machine.setRom(rom);
// Now, reset
machine.getCpu().reset();
updateVisibleState();
// Refresh breakpoints to show new memory contents.
breakpoints.refresh();
logger.info("ROM File `{}' loaded at {}", romFile.getName(),
String.format("0x%04X", machine.getRomBase()));
// TODO: "Don't Show Again" checkbox
JOptionPane.showMessageDialog(mainWindow,
"Loaded Successfully At " +
String.format("$%04X", machine.getRomBase()),
"OK",
JOptionPane.PLAIN_MESSAGE);
}
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Unable to read ROM file: " + ex.getMessage());
logger.error("Unable to read ROM file: {}", ex.getMessage());
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
} catch (MemoryRangeException ex) {
logger.log(Level.SEVERE, "Memory range error while loading ROM file: " + ex.getMessage());
logger.error("Memory range error while loading ROM file: {}", ex.getMessage());
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Memory access error while loading ROM file: " + ex.getMessage());
logger.error("Memory access error while loading ROM file: {}", ex.getMessage());
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
}
}
}
@ -562,10 +606,8 @@ public class Simulator {
preferences.getDialog().setVisible(true);
}
}
class SelectMachineAction extends AbstractAction {
Simulator simulator;
public SelectMachineAction() {
super("Switch emulated machine...", null);
putValue(SHORT_DESCRIPTION, "Select the type of the machine to be emulated");
@ -573,19 +615,19 @@ public class Simulator {
}
public void actionPerformed(ActionEvent actionEvent) {
if(runLoop != null) {
if (runLoop != null) {
runLoop.requestStop();
}
memoryWindow.dispose();
traceLog.dispose();
if(videoWindow != null) {
if (videoWindow != null) {
videoWindow.dispose();
}
mainWindow.dispose();
command = MAIN_CMD.SELECTMACHINE;
synchronized(commandMonitorObject) {
command = MainCommand.SELECTMACHINE;
synchronized (commandMonitorObject) {
commandMonitorObject.notifyAll();
}
}
@ -613,11 +655,12 @@ public class Simulator {
public SetFontAction(int size) {
super(Integer.toString(size) + " pt", null);
this.size = size;
putValue(SHORT_DESCRIPTION, "Set font to " + Integer.toString(size) + "pt.");
putValue(SHORT_DESCRIPTION, "Set font to " + size + "pt.");
}
public void actionPerformed(ActionEvent actionEvent) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
console.setFont(new Font("Monospaced", Font.PLAIN, size));
mainWindow.pack();
@ -626,6 +669,40 @@ public class Simulator {
}
}
class SetSpeedAction extends AbstractAction {
private int speed;
public SetSpeedAction(int speed) {
super(Integer.toString(speed) + " MHz", null);
this.speed = speed;
putValue(SHORT_DESCRIPTION, "Set simulated speed to " + speed + " MHz.");
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (speed < 1 || speed > CLOCK_PERIODS.length - 1) {
return;
}
machine.getCpu().setClockPeriodInNs(CLOCK_PERIODS[speed]);
}
}
class SetCpuAction extends AbstractAction {
private 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);
@ -678,6 +755,23 @@ public class Simulator {
}
}
class ToggleBreakpointWindowAction extends AbstractAction {
public ToggleBreakpointWindowAction() {
super("Breakpoints...", null);
putValue(SHORT_DESCRIPTION, "Show or Hide Breakpoints");
}
public void actionPerformed(ActionEvent actionEvent) {
synchronized (breakpointsWindow) {
if (breakpointsWindow.isVisible()) {
breakpointsWindow.setVisible(false);
} else {
breakpointsWindow.setVisible(true);
}
}
}
}
class SimulatorMenu extends JMenuBar {
// Menu Items
private JMenuItem loadProgramItem;
@ -695,7 +789,9 @@ public class Simulator {
*/
public void simulatorDidStart() {
loadProgramItem.setEnabled(false);
loadRomItem.setEnabled(false);
if (loadRomItem != null) {
loadRomItem.setEnabled(false);
}
}
/**
@ -703,7 +799,9 @@ public class Simulator {
*/
public void simulatorDidStop() {
loadProgramItem.setEnabled(true);
loadRomItem.setEnabled(true);
if (loadRomItem != null) {
loadRomItem.setEnabled(true);
}
}
private void initMenu() {
@ -714,15 +812,19 @@ public class Simulator {
JMenu fileMenu = new JMenu("File");
loadProgramItem = new JMenuItem(new LoadProgramAction());
loadRomItem = new JMenuItem(new LoadRomAction());
JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction());
JMenuItem selectMachineItem = new JMenuItem(new SelectMachineAction());
JMenuItem quitItem = new JMenuItem(new QuitAction());
fileMenu.add(loadProgramItem);
fileMenu.add(loadRomItem);
// Simple Machine does not implement a ROM, so it makes no sense to
// offer a ROM load option.
if (machine.getRom() != null) {
loadRomItem = new JMenuItem(new LoadRomAction());
fileMenu.add(loadRomItem);
}
JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction());
fileMenu.add(prefsItem);
fileMenu.add(selectMachineItem);
JMenuItem quitItem = new JMenuItem(new QuitAction());
fileMenu.add(quitItem);
add(fileMenu);
@ -767,7 +869,7 @@ public class Simulator {
});
viewMenu.add(showMemoryTable);
if(videoWindow != null) {
if (videoWindow != null) {
final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction());
videoWindow.addWindowListener(new WindowAdapter() {
@Override
@ -779,16 +881,99 @@ public class Simulator {
}
add(viewMenu);
/*
* Simulator Menu
*/
JMenu simulatorMenu = new JMenu("Simulator");
// "Select Machine..." item.
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();
makeSpeedMenuItem(1, speedSubMenu, speedGroup);
makeSpeedMenuItem(2, speedSubMenu, speedGroup);
makeSpeedMenuItem(4, speedSubMenu, speedGroup);
makeSpeedMenuItem(8, speedSubMenu, speedGroup);
simulatorMenu.add(speedSubMenu);
simulatorMenu.add(cpuTypeMenu);
// "Breakpoints"
final JCheckBoxMenuItem showBreakpoints = new JCheckBoxMenuItem(new ToggleBreakpointWindowAction());
// Un-check the menu item if the user closes the window directly
breakpointsWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
showBreakpoints.setSelected(false);
}
});
simulatorMenu.add(showBreakpoints);
add(simulatorMenu);
}
private void makeFontSizeMenuItem(int size, JMenu fontSubMenu, ButtonGroup group) {
private void makeFontSizeMenuItem(int size, JMenu subMenu, ButtonGroup group) {
Action action = new SetFontAction(size);
JCheckBoxMenuItem item = new JCheckBoxMenuItem(action);
item.setSelected(size == DEFAULT_FONT_SIZE);
fontSubMenu.add(item);
subMenu.add(item);
group.add(item);
}
private void makeSpeedMenuItem(int speed, JMenu subMenu, ButtonGroup group) {
if (speed < 1 || speed > CLOCK_PERIODS.length - 1) {
return;
}
Action action = new SetSpeedAction(speed);
JCheckBoxMenuItem item = new JCheckBoxMenuItem(action);
item.setSelected(CLOCK_PERIODS[speed] == Cpu.DEFAULT_CLOCK_PERIOD_IN_NS);
subMenu.add(item);
group.add(item);
}
private void 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();
}
}
});
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -33,7 +33,7 @@ import com.loomcom.symon.exceptions.MemoryRangeException;
public abstract class Acia extends Device {
private 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;
@ -82,6 +83,7 @@ public abstract class Acia extends Device {
/**
* @return The simulated baud rate in bps.
*/
@SuppressWarnings("unused")
public int getBaudRate() {
return baudRate;
}
@ -99,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;
}
@ -153,6 +160,7 @@ public abstract class Acia extends Device {
/**
* @return true if there is character data in the RX register.
*/
@SuppressWarnings("unused")
public boolean hasRxChar() {
return rxFull;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -26,10 +26,9 @@ package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the MOS 6551 ACIA, with limited
* functionality. Interrupts are not supported.
* 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
@ -45,7 +44,6 @@ public class Acia6551 extends Acia {
static final int CMND_REG = 2;
static final int CTRL_REG = 3;
/**
* Registers. These are ignored in the current implementation.
*/
@ -55,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:
@ -76,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:
@ -106,73 +112,66 @@ public class Acia6551 extends Acia {
/**
* Set the control register and associated state.
*
* @param data
* @param data Data to write into the control register
*/
private void setControlRegister(int data) {
controlRegister = data;
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 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);
}
@ -180,8 +179,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;
@ -192,17 +191,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;
}
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -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.
*
* 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
@ -41,32 +41,24 @@ public class Acia6850 extends Acia {
public static final int ACIA_SIZE = 2;
static final int STAT_REG = 0; // read-only
static final int CTRL_REG = 0; // write-only
static final int RX_REG = 1; // read-only
static final int TX_REG = 1; // write-only
/**
* Registers. These are ignored in the current implementation.
*/
private int commandRegister;
public Acia6850(int address) throws MemoryRangeException {
super(address, ACIA_SIZE, "ACIA6850");
setBaudRate(2400);
}
@Override
public int read(int address) throws MemoryAccessException {
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.");
@ -86,19 +78,17 @@ public class Acia6850 extends Acia {
throw new MemoryAccessException("No register.");
}
}
private void setCommandRegister(int data) {
commandRegister = data;
// Bits 0 & 1 control the master reset
if((commandRegister & 0x01) != 0 && (commandRegister & 0x02) != 0) {
if((data & 0x01) != 0 && (data & 0x02) != 0) {
reset();
}
// Bit 7 controls receiver IRQ behavior
receiveIrqEnabled = (commandRegister & 0x80) != 0;
receiveIrqEnabled = (data & 0x80) != 0;
// Bits 5 & 6 controls transmit IRQ behavior
transmitIrqEnabled = (commandRegister & 0x20) != 0 && (commandRegister & 0x40) == 0;
transmitIrqEnabled = (data & 0x20) != 0 && (data & 0x40) == 0;
}
@ -107,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;
@ -119,7 +109,14 @@ public class Acia6850 extends Acia {
if (overrun) {
stat |= 0x20;
}
if (interrupt) {
stat |= 0x80;
}
if (cpuAccess) {
interrupt = false;
}
return stat;
}
@ -128,6 +125,7 @@ public class Acia6850 extends Acia {
overrun = false;
rxFull = false;
txEmpty = true;
interrupt = false;
}
}

View File

@ -1,20 +1,37 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.IOException;
import java.util.logging.Logger;
/**
* Simulation of a 6545 CRTC and virtual CRT output.
*/
public class Crtc extends Device {
private static final Logger logger = Logger.getLogger(Crtc.class.getName());
// Memory locations in the CRTC address space
public static final int REGISTER_SELECT = 0;
public static final int REGISTER_RW = 1;
@ -65,6 +82,11 @@ public class Crtc extends Device {
private int currentRegister = 0;
// Status bits
private boolean rowColumnAddressing = false;
private boolean displayEnableSkew = false;
private boolean cursorSkew = false;
private Memory memory;
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
@ -97,7 +119,7 @@ public class Crtc extends Device {
}
@Override
public int read(int address) throws MemoryAccessException {
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
switch (address) {
case REGISTER_RW:
switch (currentRegister) {
@ -118,8 +140,9 @@ public class Crtc extends Device {
return null;
}
public int[] getDmaAccess() {
return memory.getDmaAccess();
public int getCharAtAddress(int address) throws MemoryAccessException {
// TODO: Row/Column addressing
return memory.read(address, false);
}
public int getHorizontalDisplayed() {
@ -162,6 +185,18 @@ public class Crtc extends Device {
return pageSize;
}
public boolean getRowColumnAddressing() {
return rowColumnAddressing;
}
public boolean getDisplayEnableSkew() {
return displayEnableSkew;
}
public boolean getCursorSkew() {
return cursorSkew;
}
private void setCurrentRegister(int registerNumber) {
this.currentRegister = registerNumber;
}
@ -180,7 +215,9 @@ public class Crtc extends Device {
pageSize = horizontalDisplayed * verticalDisplayed;
break;
case MODE_CONTROL:
// TODO: Implement multiple addressing modes and cursor skew.
rowColumnAddressing = (data & 0x04) != 0;
displayEnableSkew = (data & 0x10) != 0;
cursorSkew = (data & 0x20) != 0;
break;
case SCAN_LINE:
scanLinesPerRow = data;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -62,22 +62,17 @@ public abstract class Device implements Comparable<Device> {
*/
private Set<DeviceChangeListener> deviceChangeListeners;
public Device(int startAddress, int endAddress, String name)
throws MemoryRangeException {
public Device(int startAddress, int endAddress, String name) throws MemoryRangeException {
this.memoryRange = new MemoryRange(startAddress, endAddress);
this.size = endAddress - startAddress + 1;
this.name = name;
this.deviceChangeListeners = new HashSet<DeviceChangeListener>();
}
public Device(int startAddress, int endAddress) throws MemoryRangeException {
this(startAddress, endAddress, null);
this.deviceChangeListeners = new HashSet<>();
}
/* Methods required to be implemented by inheriting classes. */
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();
@ -97,6 +92,7 @@ public abstract class Device implements Comparable<Device> {
return memoryRange.endAddress();
}
@SuppressWarnings("unused")
public int startAddress() {
return memoryRange.startAddress();
}
@ -105,6 +101,7 @@ public abstract class Device implements Comparable<Device> {
return name;
}
@SuppressWarnings("unused")
public void setName(String name) {
this.name = name;
}
@ -118,8 +115,8 @@ public abstract class Device implements Comparable<Device> {
}
public void notifyListeners() {
for (DeviceChangeListener l : deviceChangeListeners) {
l.deviceStateChanged();
for (DeviceChangeListener listener : deviceChangeListeners) {
listener.deviceStateChanged();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -24,5 +24,5 @@
package com.loomcom.symon.devices;
public interface DeviceChangeListener {
public void deviceStateChanged();
void deviceStateChanged();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -28,8 +28,6 @@ import java.util.*;
import com.loomcom.symon.exceptions.*;
import javax.swing.*;
public class Memory extends Device {
private boolean readOnly;
@ -57,8 +55,7 @@ public class Memory extends Device {
}
public static Memory makeRAM(int startAddress, int endAddress) throws MemoryRangeException {
Memory memory = new Memory(startAddress, endAddress, false);
return memory;
return new Memory(startAddress, endAddress, false);
}
public void write(int address, int data) throws MemoryAccessException {
@ -97,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];
}
@ -108,8 +105,4 @@ public class Memory extends Device {
public String toString() {
return "Memory: " + getMemoryRange().toString();
}
public int[] getDmaAccess() {
return mem;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -23,45 +23,19 @@
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
public class Via extends Device {
public static final int VIA_SIZE = 16;
public abstract class Pia extends Device {
private static final int ORB = 0;
private static final int ORA = 1;
private static final int DDRB = 2;
private static final int DDRA = 3;
private static final int T1C_L = 4;
private static final int T1C_H = 5;
private static final int T1L_L = 6;
private static final int T1L_H = 7;
private static final int T2C_L = 8;
private static final int T2C_H = 9;
private static final int SR = 10;
private static final int ACR = 11;
private static final int PCR = 12;
private static final int IFR = 13;
private static final int IER = 14;
private static final int ORA_H = 15;
private final String name;
public Via(int address) throws MemoryRangeException {
super(address, address + VIA_SIZE - 1, "VIA");
}
@Override
public void write(int address, int data) throws MemoryAccessException {
}
@Override
public int read(int address) throws MemoryAccessException {
return 0;
public Pia(int startAddress, int endAddress, String name) throws MemoryRangeException {
super(startAddress, endAddress, name);
this.name = name;
}
@Override
public String toString() {
return null;
return name;
}
}

View File

@ -0,0 +1,210 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Emulation for the SD-card controller of the MULTICOMP system.
* Neiter comlete nor correct.
*/
public class SdController extends Device {
private enum Status {
IDLE,
READ,
WRITE
}
public static final int CONTROLLER_SIZE = 8;
private final int SECTOR_SIZE = 512;
private final static Logger logger = Logger.getLogger(SdController.class.getName());
private File sdImageFile;
private int lba0, lba1, lba2;
private int position;
private Status status = Status.IDLE;
private final byte[] readBuffer = new byte[SECTOR_SIZE];
private final byte[] writeBuffer = new byte[SECTOR_SIZE];
private int readPosition = 0;
private int writePosition = 0;
public SdController(int address) throws MemoryRangeException {
super(address, address + CONTROLLER_SIZE - 1, "SDCONTROLLER");
sdImageFile = new File("sd.img");
if (!sdImageFile.exists()) {
sdImageFile = null;
logger.log(Level.INFO, "Could not find SD card image 'sd.img'");
}
}
@Override
public void write(int address, int data) throws MemoryAccessException {
switch (address) {
case 0:
writeData(data);
return;
case 1:
writeCommand(data);
return;
case 2:
this.lba0 = data;
return;
case 3:
this.lba1 = data;
return;
case 4:
this.lba2 = data;
}
}
@Override
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
switch (address) {
case 0:
return readData();
case 1:
return readStatus();
default:
return 0;
}
}
private void computePosition() {
this.position = lba0 + (lba1 << 8) + (lba2 << 16);
// each sector is 512 bytes, so multiply accordingly
this.position <<= 9;
}
private void prepareRead() {
this.status = Status.READ;
this.readPosition = 0;
computePosition();
if (sdImageFile != null) {
try {
FileInputStream fis = new FileInputStream(sdImageFile);
fis.skip(this.position);
int read = fis.read(readBuffer);
if (read < SECTOR_SIZE) {
logger.log(Level.WARNING, "not enough data to fill read buffer from SD image file");
}
fis.close();
} catch (IOException ex) {
logger.log(Level.WARNING, "could not fill read buffer from SD image file", ex);
}
}
}
private void prepareWrite() {
this.status = Status.WRITE;
this.writePosition = 0;
computePosition();
}
private int readData() {
if (status != Status.READ) {
return 0;
}
int data = readBuffer[readPosition++];
if (readPosition >= SECTOR_SIZE) {
this.status = Status.IDLE;
}
return data;
}
private void writeData(int data) {
if (status != Status.WRITE) {
return;
}
writeBuffer[writePosition++] = (byte) data;
if (writePosition >= SECTOR_SIZE) {
if (sdImageFile != null) {
try {
RandomAccessFile raf = new RandomAccessFile(sdImageFile, "rw");
raf.skipBytes(this.position);
raf.write(writeBuffer, 0, writeBuffer.length);
raf.close();
} catch (IOException ex) {
logger.log(Level.WARNING, "could not write data back to SD image file!", ex);
}
}
this.status = Status.IDLE;
}
}
private int readStatus() {
switch (this.status) {
case IDLE:
return 128;
case READ:
return 224;
case WRITE:
return 160;
default:
return 0;
}
}
private void writeCommand(int data) {
switch (data) {
case 0:
prepareRead();
return;
case 1:
prepareWrite();
return;
default:
this.status = Status.IDLE;
}
}
@Override
public String toString() {
return getName() + "@" + String.format("%04X", this.getMemoryRange().startAddress);
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* Very basic implementation of a MOS 6522 VIA.
*
* TODO: Implement timers as threads.
*/
public class Via6522 extends Pia {
public static final int VIA_SIZE = 16;
enum Register {
ORB, ORA, DDRB, DDRA, T1C_L, T1C_H, T1L_L, T1L_H,
T2C_L, T2C_H, SR, ACR, PCR, IFR, IER, ORA_H
}
public Via6522(int address) throws MemoryRangeException {
super(address, address + VIA_SIZE - 1, "MOS 6522 VIA");
}
@Override
public void write(int address, int data) throws MemoryAccessException {
Register[] registers = Register.values();
if (address >= registers.length) {
throw new MemoryAccessException("Unknown register: " + address);
}
Register r = registers[address];
switch (r) {
case ORA:
case ORB:
case DDRA:
case DDRB:
case T1C_L:
case T1C_H:
case T1L_L:
case T1L_H:
case T2C_L:
case T2C_H:
case SR:
case ACR:
case PCR:
case IFR:
case IER:
case ORA_H:
default:
}
}
@Override
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
Register[] registers = Register.values();
if (address >= registers.length) {
throw new MemoryAccessException("Unknown register: " + address);
}
Register r = registers[address];
switch (r) {
case ORA:
case ORB:
case DDRA:
case DDRB:
case T1C_L:
case T1C_H:
case T1L_L:
case T1L_H:
case T2C_L:
case T2C_H:
case SR:
case ACR:
case PCR:
case IFR:
case IER:
case ORA_H:
default:
}
return 0;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -31,7 +31,4 @@ public class SymonException extends Exception {
public SymonException(String msg) {
super(msg);
}
public SymonException() {
super();
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,198 @@
/*
* Copyright (c) 2016 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.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import 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 int getBorderWidth() {
return borderWidth;
}
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 println(String str) {
if (str == null) {
throw new NullPointerException("str");
}
print(str.concat("\r\n"));
}
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(1, 0, rows, 0, bufferSize + 1);
scrollBar.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent 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();
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,167 @@
/*
* 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 {
/**
* Gets the bell strategy.
* @return The bell strategy.
*/
public BellStrategy getBellStrategy();
/**
* Sets the bell strategy.
* @param strategy The bell strategy.
* @throws NullPointerException if the strategy is {@code null}.
*/
public void setBellStrategy(BellStrategy strategy);
/**
* Clears the terminal.
*/
public void clear();
/**
* Moves the cursor back n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorBack(int n);
/**
* Moves the cursor forward n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorForward(int n);
/**
* Moves the cursor down n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorDown(int n);
/**
* Moves the cursor up n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public 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.
*/
public 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.
*/
public 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}.
*/
public void print(String str);
/**
* Gets the number of columns.
* @return The number of columns.
*/
public int getColumns();
/**
* Gets the number of rows.
* @return The number of rows.
*/
public int getRows();
/**
* Gets the buffer size.
* @return The buffer size.
*/
public int getBufferSize();
/**
* Gets the cursor row.
* @return The cursor row.
*/
public int getCursorRow();
/**
* Sets the cursor row.
* @param row The cursor row.
* @throws IllegalArgumentException if the row is out of the valid range.
*/
public void setCursorRow(int row);
/**
* Gets the cursor column.
* @return The cursor column.
*/
public int getCursorColumn();
/**
* Sets the cursor column.
* @param column The cursor column.
* @throws IllegalArgumentException if the column is out of the valid range.
*/
public void setCursorColumn(int column);
/**
* Gets the default background color.
* @return The default background color.
*/
public Color getDefaultBackgroundColor();
/**
* Gets the default foreground color.
* @return The default foreground color.
*/
public Color getDefaultForegroundColor();
}

View File

@ -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();
}
}

View File

@ -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.
*/
public void soundBell();
}

View File

@ -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 */
}
}

View File

@ -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].equals("")) {
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;
}
}

View File

@ -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.
*/
public void parsedControlSequence(AnsiControlSequence seq);
/**
* Called when a string has been parsed.
* @param str The string.
*/
public void parsedString(String str);
}

View File

@ -0,0 +1,156 @@
/*
* 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 multi-byte 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 {@link #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();
}
Reader reader = new StringReader(str);
try {
try {
parse(reader);
} finally {
reader.close();
}
} 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((char) SINGLE_CSI);
buffer.append(parameters);
}
}
}

View File

@ -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() {
}
}

View File

@ -0,0 +1,494 @@
/*
* 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();
String[] parameters = seq.getParameters();
switch (command) {
case 'A':
case 'B':
case 'C':
case 'D':
int 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 if (command == 'D') {
moveCursorBack(n);
}
break;
case 'E':
case 'F':
n = 1;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
if (command == 'E') {
moveCursorDown(n);
} else if (command == 'F') {
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 BellStrategy getBellStrategy() {
return bellStrategy;
}
@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];
}
}

View File

@ -0,0 +1,169 @@
/*
* Copyright (c) 2016 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";
}
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2008-2014 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -29,32 +29,33 @@ import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.exceptions.MemoryRangeException;
public interface Machine {
public Bus getBus();
Bus getBus();
Cpu getCpu();
public Cpu getCpu();
Memory getRam();
public Memory getRam();
Acia getAcia();
public Acia getAcia();
Pia getPia();
public Via getVia();
Crtc getCrtc();
public Crtc getCrtc();
Memory getRom();
public Memory getRom();
void setRom(Memory rom) throws MemoryRangeException;
public void setRom(Memory rom) throws MemoryRangeException;
int getRomBase();
public int getRomBase();
public int getRomSize();
public int getMemorySize();
int getRomSize();
int getMemorySize();
String getName();
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2008-2014 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -30,7 +30,8 @@ import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6850;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.devices.SdController;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.File;
import java.util.logging.Logger;
@ -51,6 +52,8 @@ public class MulticompMachine implements Machine {
// ACIA at $FFD0-$FFD1
private static final int ACIA_BASE = 0xFFD0;
// SD controller at $FFD8-$FFDF
private static final int SD_BASE = 0xFFD8;
// 8KB ROM at $E000-$FFFF
private static final int ROM_BASE = 0xE000;
@ -65,29 +68,34 @@ 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);
this.acia = new Acia6850(ACIA_BASE);
this.acia.setBaudRate(0);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(acia, 1);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file " + romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
} else {
logger.info("Default ROM file " + romImage +
bus.addDevice(new SdController(SD_BASE), 1);
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
@ -111,7 +119,7 @@ public class MulticompMachine implements Machine {
}
@Override
public Via getVia() {
public Pia getPia() {
return null;
}
@ -147,5 +155,10 @@ public class MulticompMachine implements Machine {
public int getMemorySize() {
return MEMORY_SIZE;
}
@Override
public String getName() {
return "Multicomp";
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* A SimpleMachine is the simplest 6502 implementation possible - it
* consists solely of RAM and a CPU. This machine is primarily useful
* for running 6502 functional tests or debugging by hand.
*/
public class SimpleMachine implements Machine {
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
private final Bus bus;
private final Memory ram;
private final Cpu cpu;
public SimpleMachine(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();
bus.addCpu(cpu);
bus.addDevice(ram);
}
@Override
public Bus getBus() {
return bus;
}
@Override
public Cpu getCpu() {
return cpu;
}
@Override
public Memory getRam() {
return ram;
}
@Override
public Acia getAcia() {
return null;
}
@Override
public Pia getPia() {
return null;
}
@Override
public Crtc getCrtc() {
return null;
}
@Override
public Memory getRom() {
return null;
}
@Override
public void setRom(Memory rom) throws MemoryRangeException {
// No-op
}
@Override
public int getRomBase() {
return 0;
}
@Override
public int getRomSize() {
return 0;
}
@Override
public int getMemorySize() {
return BUS_TOP + 1;
}
@Override
public String getName() {
return "Simple";
}
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2008-2014 Seth J. Morabito <sethm@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -26,19 +26,16 @@ package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6551;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.devices.*;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.File;
import java.util.logging.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public class SymonMachine implements Machine {
private final static Logger logger = Logger.getLogger(SymonMachine.class.getName());
private final static Logger logger = LoggerFactory.getLogger(SymonMachine.class.getName());
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
@ -48,16 +45,15 @@ public class SymonMachine implements Machine {
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0x8000;
// VIA at $8000-$800F
// PIA at $8000-$800F
private static final int VIA_BASE = 0x8000;
private static final int PIA_BASE = 0x8000;
// ACIA at $8800-$8803
private static final int ACIA_BASE = 0x8800;
// CRTC at $9000-$9001
private static final int CRTC_BASE = 0x9000;
private static final int VIDEO_RAM_BASE = 0x7000;
// 16KB ROM at $C000-$FFFF
private static final int ROM_BASE = 0xC000;
@ -68,34 +64,37 @@ public class SymonMachine implements Machine {
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Via via;
private final Pia pia;
private final Crtc crtc;
private final Memory ram;
private Memory rom;
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);
this.via = new Via(VIA_BASE);
this.pia = new Via6522(PIA_BASE);
this.acia = new Acia6551(ACIA_BASE);
this.crtc = new Crtc(CRTC_BASE, ram);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(via);
bus.addDevice(pia);
bus.addDevice(acia);
bus.addDevice(crtc);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file " + romImage);
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 " + romImage +
" not found, loading empty R/W memory image.");
logger.info("No ROM file specified, loading empty R/W memory image.");
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
@ -124,8 +123,8 @@ public class SymonMachine implements Machine {
}
@Override
public Via getVia() {
return via;
public Pia getPia() {
return pia;
}
@Override
@ -160,8 +159,11 @@ public class SymonMachine implements Machine {
public int getMemorySize() {
return MEMORY_SIZE;
}
@Override
public String getName() {
return "Symon";
}
}

View File

@ -0,0 +1,152 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.ui;
import com.loomcom.symon.Breakpoints;
import com.loomcom.symon.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* Simple window to enter breakpoints.
*/
public class BreakpointsWindow extends JFrame {
private static final Logger logger = LoggerFactory.getLogger(BreakpointsWindow.class);
private static final Dimension FRAME_SIZE = new Dimension(240, 280);
private static final String EMPTY_STRING = "";
private JFrame mainWindow;
private Breakpoints breakpoints;
public BreakpointsWindow(Breakpoints breakpoints,
JFrame mainWindow) {
this.breakpoints = breakpoints;
this.mainWindow = mainWindow;
createUi();
}
private void createUi() {
setTitle("Breakpoints");
JPanel breakpointsPanel = new JPanel();
JPanel controlPanel = new JPanel();
breakpointsPanel.setLayout(new BorderLayout());
breakpointsPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
final JButton addButton = new JButton("Add");
final JButton removeButton = new JButton("Del");
removeButton.setEnabled(false);
final JTextField addTextField = new JTextField(4);
final JTable breakpointsTable = new JTable(breakpoints);
breakpointsTable.setShowGrid(true);
breakpointsTable.setGridColor(Color.LIGHT_GRAY);
breakpointsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
breakpointsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getFirstIndex() > -1) {
removeButton.setEnabled(true);
} else {
removeButton.setEnabled(false);
}
}
});
JScrollPane scrollPane = new JScrollPane(breakpointsTable);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
breakpointsPanel.add(scrollPane, BorderLayout.CENTER);
ActionListener addBreakpointListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int value;
String newBreakpoint = addTextField.getText();
if (newBreakpoint == null || newBreakpoint.isEmpty()) {
return;
}
try {
value = (Integer.parseInt(addTextField.getText(), 16) & 0xffff);
} catch (NumberFormatException ex) {
logger.warn("Can't parse page number {}", newBreakpoint);
return;
}
if (value < 0) {
return;
}
breakpoints.addBreakpoint(value);
logger.debug("Added breakpoint ${}", Utils.wordToHex(value));
addTextField.setText(EMPTY_STRING);
}
};
addButton.addActionListener(addBreakpointListener);
addTextField.addActionListener(addBreakpointListener);
removeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
breakpoints.removeBreakpointAtIndex(breakpointsTable.getSelectedRow());
}
});
controlPanel.add(addTextField);
controlPanel.add(addButton);
controlPanel.add(removeButton);
setLayout(new BorderLayout());
getContentPane().add(breakpointsPanel, BorderLayout.CENTER);
getContentPane().add(controlPanel, BorderLayout.SOUTH);
setMinimumSize(FRAME_SIZE);
setMaximumSize(FRAME_SIZE);
setPreferredSize(FRAME_SIZE);
setLocationRelativeTo(mainWindow);
setResizable(false);
pack();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -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,21 +44,23 @@ import java.awt.event.MouseListener;
public class Console extends JTerminal implements KeyListener, MouseListener {
private static final int DEFAULT_COLUMNS = 80;
private static final int DEFAULT_ROWS = 24;
private static final int DEFAULT_BORDER_WIDTH = 10;
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 static final boolean SEND_CR_LF_FOR_CR = false;
// If true, send CRLF (0x0d 0x0a) whenever CR is typed
private boolean sendCrForLf;
private FifoRingBuffer<Character> typeAheadBuffer;
public Console(int columns, int rows, Font font) {
public Console(int columns, int rows, Font font, boolean sendCrForLf) {
super(new Vt100TerminalModel(columns, rows), font);
//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<Character>(128);
this.typeAheadBuffer = new FifoRingBuffer<>(128);
this.sendCrForLf = sendCrForLf;
setBorderWidth(DEFAULT_BORDER_WIDTH);
addKeyListener(this);
addMouseListener(this);
@ -83,7 +85,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
/**
* Returns true if a key has been pressed since the last time input was read.
*
* @return
* @return true if the type-ahead buffer has data
*/
public boolean hasInput() {
return !typeAheadBuffer.isEmpty();
@ -105,7 +107,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
}
}
if (SEND_CR_LF_FOR_CR && keyTyped == 0x0d) {
if (sendCrForLf && (keyTyped == 0x0d)) {
typeAheadBuffer.push((char) 0x0d);
typeAheadBuffer.push((char) 0x0a);
} else {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -25,7 +25,9 @@ package com.loomcom.symon.ui;
import com.loomcom.symon.Bus;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.HexUtil;
import com.loomcom.symon.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
@ -37,8 +39,7 @@ import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.EventObject;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This Frame displays the contents of a page of memory. The page number to be displayed
@ -46,6 +47,8 @@ import java.util.logging.Logger;
*/
public class MemoryWindow extends JFrame implements ActionListener {
private static final Logger logger = LoggerFactory.getLogger(MemoryWindow.class);
private MemoryTableModel memoryTableModel;
private JTable memoryTable;
private JTextField pageNumberTextField;
@ -104,7 +107,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
previousPageButton.setEnabled(pageNumber > 0x00);
nextPageButton.setEnabled(pageNumber < 0xff);
pageNumberTextField.setText(HexUtil.byteToHex(pageNumber));
pageNumberTextField.setText(Utils.byteToHex(pageNumber));
}
/**
@ -209,9 +212,9 @@ public class MemoryWindow extends JFrame implements ActionListener {
} catch (NumberFormatException ex) {
// An invalid number was entered. Log the error, but otherwise
// take no action.
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Can't parse page number " +
pageNumberInput);
logger.warn("Can't parse page number {}", pageNumberInput);
}
updateControls();
}
}
@ -325,13 +328,13 @@ public class MemoryWindow extends JFrame implements ActionListener {
public Object getValueAt(int row, int column) {
try {
if (column == 0) {
return HexUtil.wordToHex(fullAddress(row, 1));
return Utils.wordToHex(fullAddress(row, 1));
} else if (column < 9) {
// Display hex value of the data
return HexUtil.byteToHex(bus.read(fullAddress(row, column)));
return Utils.byteToHex(bus.read(fullAddress(row, column), false));
} else {
// Display the ASCII equivalent (if printable)
return HexUtil.byteToAscii(bus.read(fullAddress(row, column - 8)));
return Utils.byteToAscii(bus.read(fullAddress(row, column - 8), false));
}
} catch (MemoryAccessException ex) {
return "??";
@ -346,12 +349,8 @@ public class MemoryWindow extends JFrame implements ActionListener {
int fullAddress = fullAddress(row, column);
int newValue = Integer.parseInt(hexValue, 16) & 0xff;
bus.write(fullAddress, newValue);
} catch (MemoryAccessException ex) {
;
} catch (NumberFormatException ex) {
;
} catch (ClassCastException ex) {
;
} catch (MemoryAccessException | NumberFormatException | ClassCastException ex) {
// Intentionally swallow exception
}
fireTableCellUpdated(row, column);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -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();
}
@ -98,19 +98,21 @@ public class PreferencesDialog extends Observable implements Preferences {
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
updateUi();
PreferencesDialog.this.updateUi();
dialog.setVisible(false);
}
});
applyButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
haltOnBreak = haltOnBreakCheckBox.isSelected();
programLoadAddress = hexToInt(programLoadAddressField.getText());
updateUi();
programLoadAddress = PreferencesDialog.this.hexToInt(programLoadAddressField.getText());
PreferencesDialog.this.updateUi();
// TODO: Actually check to see if values have changed, don't assume.
setChanged();
PreferencesDialog.this.setChanged();
PreferencesDialog.this.notifyObservers();
dialog.setVisible(false);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -24,11 +24,16 @@
package com.loomcom.symon.ui;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.CpuState;
import com.loomcom.symon.machines.Machine;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.plaf.metal.MetalTextFieldUI;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* UI component that displays the current state of the simulated CPU.
@ -50,7 +55,6 @@ public class StatusPanel extends JPanel {
private ImageIcon negativeOn;
private ImageIcon negativeOff;
private JLabel statusFlagsLabel;
private JLabel carryFlagLabel;
private JLabel zeroFlagLabel;
private JLabel irqDisableFlagLabel;
@ -66,12 +70,7 @@ public class StatusPanel extends JPanel {
private JTextField xField;
private JTextField yField;
private JLabel opcodeLabel;
private JLabel pcLabel;
private JLabel spLabel;
private JLabel aLabel;
private JLabel xLabel;
private JLabel yLabel;
private final Machine machine;
private static final int EMPTY_BORDER = 10;
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
@ -79,8 +78,9 @@ public class StatusPanel extends JPanel {
private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22);
private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
public StatusPanel() {
public StatusPanel(Machine machine) {
super();
this.machine = machine;
createUi();
}
@ -99,20 +99,20 @@ public class StatusPanel extends JPanel {
JPanel statusFlagsPanel = new JPanel();
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
carryOn = new ImageIcon(this.getClass().getResource("images/C_on.png"));
carryOff = new ImageIcon(this.getClass().getResource("images/C_off.png"));
zeroOn = new ImageIcon(this.getClass().getResource("images/Z_on.png"));
zeroOff = new ImageIcon(this.getClass().getResource("images/Z_off.png"));
irqOn = new ImageIcon(this.getClass().getResource("images/I_on.png"));
irqOff = new ImageIcon(this.getClass().getResource("images/I_off.png"));
decimalOn = new ImageIcon(this.getClass().getResource("images/D_on.png"));
decimalOff = new ImageIcon(this.getClass().getResource("images/D_off.png"));
breakOn = new ImageIcon(this.getClass().getResource("images/B_on.png"));
breakOff = new ImageIcon(this.getClass().getResource("images/B_off.png"));
overflowOn = new ImageIcon(this.getClass().getResource("images/O_on.png"));
overflowOff = new ImageIcon(this.getClass().getResource("images/O_off.png"));
negativeOn = new ImageIcon(this.getClass().getResource("images/N_on.png"));
negativeOff = new ImageIcon(this.getClass().getResource("images/N_off.png"));
carryOn = new ImageIcon(this.getClass().getResource("/C_on.png"));
carryOff = new ImageIcon(this.getClass().getResource("/C_off.png"));
zeroOn = new ImageIcon(this.getClass().getResource("/Z_on.png"));
zeroOff = new ImageIcon(this.getClass().getResource("/Z_off.png"));
irqOn = new ImageIcon(this.getClass().getResource("/I_on.png"));
irqOff = new ImageIcon(this.getClass().getResource("/I_off.png"));
decimalOn = new ImageIcon(this.getClass().getResource("/D_on.png"));
decimalOff = new ImageIcon(this.getClass().getResource("/D_off.png"));
breakOn = new ImageIcon(this.getClass().getResource("/B_on.png"));
breakOff = new ImageIcon(this.getClass().getResource("/B_off.png"));
overflowOn = new ImageIcon(this.getClass().getResource("/O_on.png"));
overflowOff = new ImageIcon(this.getClass().getResource("/O_off.png"));
negativeOn = new ImageIcon(this.getClass().getResource("/N_on.png"));
negativeOff = new ImageIcon(this.getClass().getResource("/N_off.png"));
// Initialize all to off
carryFlagLabel = new JLabel(carryOff, JLabel.CENTER);
@ -144,25 +144,96 @@ public class StatusPanel extends JPanel {
statusFlagsPanel.add(carryFlagLabel);
// Create and add register and address labels
statusFlagsLabel = makeLabel("Flags");
opcodeLabel = makeLabel("IR");
pcLabel = makeLabel("PC");
spLabel = makeLabel("SP");
aLabel = makeLabel("A");
xLabel = makeLabel("X");
yLabel = makeLabel("Y");
JLabel statusFlagsLabel = makeLabel("Flags");
JLabel opcodeLabel = makeLabel("Next IR");
JLabel pcLabel = makeLabel("PC");
JLabel spLabel = makeLabel("SP");
JLabel aLabel = makeLabel("A");
JLabel xLabel = makeLabel("X");
JLabel yLabel = makeLabel("Y");
statusFlagsLabel.setToolTipText("6502 Processor Status Flags");
opcodeLabel.setToolTipText("Instruction Register");
pcLabel.setToolTipText("Program Counter");
spLabel.setToolTipText("Stack Pointer");
opcodeField = makeTextField(LARGE_TEXT_FIELD_SIZE);
pcField = makeTextField(LARGE_TEXT_FIELD_SIZE);
spField = makeTextField(SMALL_TEXT_FIELD_SIZE);
aField = makeTextField(SMALL_TEXT_FIELD_SIZE);
xField = makeTextField(SMALL_TEXT_FIELD_SIZE);
yField = makeTextField(SMALL_TEXT_FIELD_SIZE);
opcodeField = makeTextField(LARGE_TEXT_FIELD_SIZE, false);
pcField = makeTextField(LARGE_TEXT_FIELD_SIZE, true);
spField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
aField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
xField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
yField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
// Make fields editable
pcField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = StatusPanel.this.getHexVal(pcField) & 0xffff;
machine.getCpu().setProgramCounter(newVal);
} catch (Exception ex) {
// Swallow exception
}
StatusPanel.this.updateState();
}
});
spField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = StatusPanel.this.getHexVal(spField) & 0xff;
machine.getCpu().setStackPointer(newVal);
} catch (Exception ex) {
// Swallow exception
}
StatusPanel.this.updateState();
}
});
aField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = StatusPanel.this.getHexVal(aField) & 0xff;
machine.getCpu().setAccumulator(newVal);
} catch (Exception ex) {
// Swallow exception
}
StatusPanel.this.updateState();
}
});
xField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = StatusPanel.this.getHexVal(xField) & 0xff;
machine.getCpu().setXRegister(newVal);
} catch (Exception ex) {
// Swallow exception
}
StatusPanel.this.updateState();
}
});
yField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
int newVal = StatusPanel.this.getHexVal(yField) & 0xff;
machine.getCpu().setYRegister(newVal);
} catch (Exception ex) {
// Swallow exception
}
StatusPanel.this.updateState();
}
});
constraints.anchor = GridBagConstraints.LINE_START;
constraints.gridwidth = 2;
@ -220,14 +291,13 @@ public class StatusPanel extends JPanel {
/**
* Update the display based on the current state of the CPU.
*
* @param cpu The simulated 6502 CPU.
*/
public void updateState(Cpu cpu) {
Cpu.CpuState cpuState = cpu.getCpuState();
public void updateState() {
Cpu cpu = machine.getCpu();
CpuState cpuState = cpu.getCpuState();
// Update the Processor Status Flag display
int status = cpu.getCpuState().getStatusFlag();
int status = cpuState.getStatusFlag();
carryFlagLabel.setIcon(iconForFlag(status, 0));
zeroFlagLabel.setIcon(iconForFlag(status, 1));
@ -238,7 +308,9 @@ public class StatusPanel extends JPanel {
negativeFlagLabel.setIcon(iconForFlag(status, 7));
// Update the register and address displays
opcodeField.setText(cpu.getCpuState().disassembleOp());
// We always want to show the NEXT instruction that will be executed
opcodeField.setText(cpu.disassembleNextOp());
pcField.setText(cpu.getProgramCounterStatus());
spField.setText(cpu.getStackPointerStatus());
aField.setText(cpu.getAccumulatorStatus());
@ -313,15 +385,26 @@ public class StatusPanel extends JPanel {
return label;
}
private JTextField makeTextField(Dimension size) {
private JTextField makeTextField(Dimension size, boolean editable) {
JTextField textField = new JTextField("");
textField.setAlignmentX(LEFT_ALIGNMENT);
textField.setEditable(false);
textField.setEditable(editable);
textField.setMinimumSize(size);
textField.setMaximumSize(size);
textField.setPreferredSize(size);
textField.setBackground(Color.WHITE);
// Although we usually defer to the system look-and-feel, for
// these small text fields in particular, we use a Metal
// look-and-feel because native look-and-feel breaks very small
// text fields under GTK+ (they are drawn with an inner margin
// even if the margin is set to 0)
textField.setUI(new MetalTextFieldUI());
return textField;
}
private int getHexVal(JTextField source) throws NumberFormatException {
String val = source.getText().replaceAll("[^0-9a-fA-F]", "");
return Integer.parseInt(val, 16);
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -23,7 +24,7 @@
package com.loomcom.symon.ui;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.CpuState;
import com.loomcom.symon.util.FifoRingBuffer;
import javax.swing.*;
@ -35,15 +36,15 @@ import java.awt.*;
*/
public class TraceLog extends JFrame {
private FifoRingBuffer<Cpu.CpuState> traceLog;
private JTextArea traceLogTextArea;
private final FifoRingBuffer<CpuState> traceLog;
private final JTextArea traceLogTextArea;
private static final Dimension MIN_SIZE = new Dimension(320, 200);
private static final Dimension PREFERRED_SIZE = new Dimension(640, 480);
private static final int MAX_LOG_LENGTH = 50000;
public TraceLog() {
traceLog = new FifoRingBuffer<Cpu.CpuState>(MAX_LOG_LENGTH);
traceLog = new FifoRingBuffer<>(MAX_LOG_LENGTH);
setMinimumSize(MIN_SIZE);
setPreferredSize(PREFERRED_SIZE);
setResizable(true);
@ -67,11 +68,15 @@ public class TraceLog extends JFrame {
* call.
*/
public void refresh() {
synchronized (this) {
StringBuilder logString = new StringBuilder();
for (Cpu.CpuState state : traceLog) {
StringBuilder logString = new StringBuilder();
synchronized(traceLog) {
for (CpuState state : traceLog) {
logString.append(state.toTraceEvent());
}
}
synchronized(traceLogTextArea) {
traceLogTextArea.setText(logString.toString());
}
}
@ -80,8 +85,10 @@ public class TraceLog extends JFrame {
* Reset the log area.
*/
public void reset() {
synchronized (this) {
synchronized(traceLog) {
traceLog.reset();
}
synchronized(traceLogTextArea) {
traceLogTextArea.setText("");
traceLogTextArea.setEnabled(true);
}
@ -92,9 +99,9 @@ public class TraceLog extends JFrame {
*
* @param state The CPU State to append.
*/
public void append(Cpu.CpuState state) {
synchronized(this) {
traceLog.push(new Cpu.CpuState(state));
public void append(CpuState state) {
synchronized(traceLog) {
traceLog.push(new CpuState(state));
}
}
@ -106,4 +113,8 @@ public class TraceLog extends JFrame {
traceLogTextArea.setEnabled(true);
}
public boolean shouldUpdate() {
return isVisible() && traceLogTextArea.isEnabled();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -25,6 +25,7 @@ package com.loomcom.symon.ui;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.DeviceChangeListener;
import com.loomcom.symon.exceptions.MemoryAccessException;
import javax.swing.*;
import java.awt.*;
@ -36,8 +37,11 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.lang.System.*;
/**
* VideoWindow represents a graphics framebuffer backed by a 6545 CRTC.
* Each time the window's VideoPanel is repainted, the video memory is
@ -57,6 +61,7 @@ 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;
@ -65,7 +70,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private BufferedImage image;
private int[] charRom;
private int[] videoRam;
private int horizontalDisplayed;
private int verticalDisplayed;
@ -85,17 +89,21 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private class VideoPanel extends JPanel {
@Override
public void paintComponent(Graphics g) {
for (int i = 0; i < crtc.getPageSize(); i++) {
int address = crtc.getStartAddress() + i;
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
try {
for (int i = 0; i < crtc.getPageSize(); i++) {
int address = crtc.getStartAddress() + i;
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
}
Graphics2D g2d = (Graphics2D) g;
if (shouldScale) {
g2d.scale(scaleX, scaleY);
}
g2d.drawImage(image, 0, 0, null);
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Memory Access Exception, can't paint video window! " + ex.getMessage());
}
Graphics2D g2d = (Graphics2D)g;
if (shouldScale) {
g2d.scale(scaleX, scaleY);
}
g2d.drawImage(image, 0, 0, null);
}
@Override
@ -116,10 +124,23 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private class CursorBlinker implements Runnable {
public void run() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (cursorBlinkRate > 0) {
hideCursor = !hideCursor;
repaint();
}
}
});
}
}
private class WindowPainter implements Runnable {
public void run() {
SwingUtilities.invokeLater(new Runnable () {
@Override
public void run() {
if (VideoWindow.this.isVisible()) {
VideoWindow.this.repaint();
}
}
});
@ -131,8 +152,7 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
this.scheduler = Executors.newSingleThreadScheduledExecutor();
this.crtc = crtc;
this.charRom = loadCharRom("/pet.rom");
this.videoRam = crtc.getDmaAccess();
this.charRom = loadCharRom("/ascii.rom");
this.scaleX = scaleX;
this.scaleY = scaleY;
this.shouldScale = (scaleX > 1 || scaleY > 1);
@ -145,6 +165,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.
@ -158,13 +183,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
}
public void refreshDisplay() {
// TODO: Verify whether this is necessary. Does `repaint()' do anything if the window is not visible?
if (isVisible()) {
repaint();
}
}
/**
* Called by the CRTC on state change.
*/
@ -239,15 +257,13 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
* @param address The address of the character being requested.
* @return An array of integers representing the pixel data.
*/
private int[] getGlyph(int address) {
int chr = videoRam[address];
private int[] getGlyph(int address) throws MemoryAccessException {
int chr = crtc.getCharAtAddress(address);
int romOffset = (chr & 0xff) * (CHAR_HEIGHT * CHAR_WIDTH);
int[] glyph = new int[CHAR_WIDTH * scanLinesPerRow];
// Populate the character
for (int i = 0; i < (CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow)); i++) {
glyph[i] = charRom[romOffset + i];
}
arraycopy(charRom, romOffset, glyph, 0, CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow));
// Overlay the cursor
if (!hideCursor && crtc.isCursorEnabled() && crtc.getCursorPosition() == address) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -38,7 +38,7 @@ public class FifoRingBuffer<E> implements Iterable<E> {
private int maxLength;
public FifoRingBuffer(int maxLength) {
this.fifoBuffer = new LinkedList<E>();
this.fifoBuffer = new LinkedList<>();
this.maxLength = maxLength;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -24,24 +24,9 @@
package com.loomcom.symon.util;
/**
* Hex String Utilities.
*
* <p/>
*
* But why? Java, after all, has a number of ways to convert an integer into a hex string,
* so it may look absurd to go to the trouble of writing yet another conversion! The answer is
* performance.
*
* <p/>
*
* The most convenient way to get a formatted hex value from an integer is with the <code>String.format</code>
* method, but this turns out to be extremely inefficient. Formatting a million integers
* with <code>String.format</code> takes something like 1600ms. Formatting the same number of integers
* with <code>HexUtil</code> takes only 160ms. This is on par with <code>Integer.toHexString</code>,
* but also gives the desired padding.
*
* Various Utilities
*/
public class HexUtil {
public class Utils {
static final String NON_PRINTABLE = ".";
@ -93,6 +78,7 @@ public class HexUtil {
/**
* Very fast 8-bit int to ASCII conversion.
*
* @param val The value of an ASCII character.
* @return A string representing the ASCII character.
*/
@ -121,9 +107,13 @@ public class HexUtil {
* @return Four digit, zero padded hexadecimal string.
*/
public static String wordToHex(int val) {
StringBuilder sb = new StringBuilder(4);
sb.append(HEX_CONSTANTS[(val >> 8) & 0xff]);
sb.append(HEX_CONSTANTS[val & 0xff]);
return sb.toString();
return HEX_CONSTANTS[(val >> 8) & 0xff] + HEX_CONSTANTS[val & 0xff];
}
/**
* Given two bytes, return an address.
*/
public static int address(int lowByte, int hiByte) {
return ((hiByte << 8) | lowByte) & 0xffff;
}
}

View File

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 191 B

View File

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 191 B

View File

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 196 B

View File

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 196 B

View File

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

View File

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

View File

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 183 B

View File

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 183 B

View File

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

View File

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

View File

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

View File

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

View File

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

View File

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

BIN
src/main/resources/mod.rom Normal file

Binary file not shown.

View File

@ -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);
}
}

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));
}
}

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

Some files were not shown because too many files have changed in this diff Show More