91 Commits
Author SHA1 Message Date
Seth Morabito 634ea933f1 Add disassembled instructions to breakpoints 2016-01-02 19:05: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 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
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
Seth Morabito 2dc0078deb Release 0.9.9.0 2014-07-26 13:23:42 -07:00
Seth Morabito ac21b2c126 Updates for 0.9.9 2014-07-26 13:18:07 -07:00
Seth Morabito 4bb6d55fa2 Merge pull request #5 from maikmerten/master
Infrastructure for several machine types, Bus reworkings, 6850 ACIA and more
2014-07-26 12:46:45 -07:00
Maik Merten f90516e53c fix a possible NPE 2014-07-26 18:55:51 +02:00
Maik Merten fdaeb661d3 add menu entry to allow to switch the emulated machine type 2014-07-26 18:52:57 +02:00
Maik Merten 827e9991d5 move main method into Main class in anticipation of changes that allow restarting the complete UI from within the Simulator menu system 2014-07-26 18:07:29 +02:00
Maik Merten dc7dfc4b4d code simplification 2014-07-25 21:44:43 +02:00
Maik Merten 2217f3831d slight JavaDoc fix regarding what Exception can be thrown 2014-07-25 21:27:40 +02:00
Maik Merten 52f4e9a00f enforce that the address range of devices falls within the address range of the bus. Turned out that the CPU tests instantiate memory with the last parameter as memory size, not end address (fixed now). Also make sure that the address lookup array takes the offset caused by non-zero starting addresses into account. 2014-07-25 21:24:16 +02:00
Maik Merten d7f8045b61 rework bus. Don't throw an Exception any more with overlapping device addresses - this is normal for some machines. Introduce the concept of priorities to ensure that device overlap in the correct order. 2014-07-25 19:32:00 +02:00
Maik Merten 222772648d simple dialog for choosing machine model when no command-line option was given 2014-07-22 17:50:06 +02:00
Maik Merten bd2337ae65 allow for selecting the emulated machien type with the "-machine" parameter. The default is the Symon machine model, the Multicomp machine can be selected with "-machine multicomp". 2014-07-21 17:39:20 +02:00
Maik Merten a49c0d40d8 revert back to busy waiting for CPU speed emulation to have a less bursty CPU behavior 2014-07-20 21:55:01 +02:00
Maik Merten 44bfbc17ab replace busy waiting to emulate "proper" CPU speed with some time book keeping and thread sleeping 2014-07-19 20:19:26 +02:00
Maik Merten b266a02372 have full 56K of RAM for the Multicomp machine 2014-07-19 17:51:51 +02:00
Maik Merten 72074b9715 Silence a compiler warning regarding generics 2014-07-19 17:40:19 +02:00
Maik Merten 252ebe0569 implement overrun status flag for the 6551 ACIA 2014-07-19 17:30:23 +02:00
Maik Merten 4a37dd7463 move overrun detection and reset into the abstract superclass for ACIAs 2014-07-19 17:10:57 +02:00
Maik Merten cf36f731da make use of the MulticompMachine in the simulator. This should be an option, not hardcoded. 2014-07-17 21:59:03 +02:00
Maik Merten e01f68a19a make use of overlapping memory regions in MulticompMachine 2014-07-17 21:45:37 +02:00
Maik Merten 44151f3a9c have the Bus support devices with overlapping memory regions 2014-07-17 21:43:46 +02:00
Maik Merten f5a25b7303 initial definition of the MULTICOMP machine. Will not yet run, as the bus will currently not allow overlapping memory ranges for devices. 2014-07-17 20:41:26 +02:00
Maik Merten 14e8ea596c define an interface for supported machines and have the SymonMachine as first implementation. Remove machine-specific definitions out of the Simulator class. 2014-07-17 19:42:51 +02:00
Maik Merten 8f52e1da1e implement overrun flag for 6850 ACIA 2014-07-16 22:26:06 +02:00
Maik Merten 0d0fddc365 tests for the 6850 ACIA and fixes in ACIA behavior 2014-07-16 21:51:34 +02:00
Maik Merten 31fdd87b41 turn Acia into an abstract class so that the 6551 and 6850 ACIAs can reuse code and an unified interface. 2014-07-15 21:35:19 +02:00
Maik Merten 9726907317 initial (and untested) implementation of a Motorola 6850 ACIA 2014-07-15 18:45:13 +02:00
Seth Morabito 40c5397ed8 Faster refresh of memory window 2014-07-06 12:17:39 -07:00
90 changed files with 37707 additions and 1269 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2008-2013 Seth J. Morabito <web@loomcom.com>
Copyright (c) 2008-2014 Seth J. Morabito <web@loomcom.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
+130 -60
View File
@@ -1,17 +1,12 @@
SYMON - A 6502 System Simulator
===============================
**NOTE: THIS SOFTWARE IS UNDER ACTIVE DEVELOPMENT. Feedback is welcome!**
**Version:** 1.1.1
**Version:** 0.9.1.0
**Last Updated:** 29 December, 2013
Copyright (c) 2013 Seth J. Morabito &lt;web@loomcom.com&gt;
**Last Updated:** 2 January, 2016
See the file COPYING for license.
![Symon Simulator in Action] (https://github.com/sethm/symon/raw/master/screenshots/full.jpg)
## 1.0 About
@@ -22,30 +17,52 @@ in Java. Its core goals are accuracy, ease of development, clear
documentation, and extensive test suites for validating correctness.
Symon simulates a complete system with a 1 MHz NMOS 6502, 32KB of RAM,
16KB of ROM, a 6551 ACIA, a 6522 VIA, and a 6545 CRTC.
16KB of ROM, a MOS 6551 or Motorola 6850 ACIA, a MOS 6522 VIA, and an
experimental 6545 CRTC.
Symon has extensive unit tests to verify correctness, and fully passes
Klaus Dormann's 6502 Functional Test Suite as of version 0.8.2
(See [this thread on the 6502.org Forums] (http://forum.6502.org/viewtopic.php?f=2&t=2241)
for more information about this functional test suite).
Symon is under constant, active development. Feedback and patches
are always welcome.
## 2.0 Requirements
- Java 1.5 or higher
- Java 1.8 or higher
- Maven 2.0.x or higher (for building from source)
- JUnit 4 or higher (for testing)
## 3.0 Features
### 3.1 Memory Map
Symon can simulate multiple 6502 based architectures. At present, three
machines are implemented: Symon (the default), MULTICOMP, and a "Simple"
machine useful for debugging.
### 3.1 Memory Maps
#### 3.1.1 Symon Memory Map
- `$0000`--`$7FFF`: 32KB RAM
- `$8000`--`$800F`: 6522 VIA
- `$8800`--`$8803`: 6551 ACIA (Serial Console)
- `$9000`--`$9001`: 6545 CRTC
- `$8800`--`$8803`: MOS 6551 ACIA (Serial Console)
- `$9000`--`$9001`: MOS 6545 CRTC
- `$C000`--`$FFFF`: 16KB ROM
The CRT Controller uses memory address `$7000` as the start of Video memory.
The CRT Controller uses memory address `$7000` as the start of Video
memory.
#### 3.1.2 MULTICOMP Memory Map
- `$0000`--`$DFFF`: 56KB RAM
- `$E000`--`$FFFF`: 8KB ROM
- `$FFD0`--`$FFD1`: Motorola 6850 ACIA
- `$FFD8`--`$FFDF`: Controller for SD cards
### 3.1.3 Simple Memory Map
- `$0000`--`$FFFF`: 64KB RAM
### 3.2 Serial Console and CPU Status
@@ -53,10 +70,9 @@ The CRT Controller uses memory address `$7000` as the start of Video memory.
The main window of the simulator acts as the primary Input/Output
system through a virtual serial terminal. The terminal is attached to
a simulated MOS 6551 ACIA, including a programmable baud rate
generator that tries to approximate the correct "feel" of the
programmed baud rate. (The sample Enhanced BASIC ROM image is
programmed for 9600 baud)
a simulated ACIA, including a programmable baud rate generator that
tries to approximate the correct "feel" of the programmed baud rate.
(The sample Enhanced BASIC ROM image is programmed for 9600 baud)
It also provides CPU status. Contents of the accumulator, index
registers, processor status flags, disassembly of the instruction
@@ -66,13 +82,16 @@ register, and stack pointer are all displayed.
The console supports font sizes from 10 to 20 points.
### 3.3 16 KB ROM Loading
### 3.3 ROM Loading
![ROM Loading] (https://github.com/sethm/symon/raw/master/screenshots/load_rom.png)
Symon can load any 16 KB (16384 bytes) ROM image from the "File"
menu. The selected ROM will be placed in memory from locations `$C000`
to `$FFFF`
Symon can load any appropriately sized ROM image. The Symon
architecture expects as 16KB (16384 byte) ROM image, while the
MULTICOMP architecture expects an 8KB (8192 byte) ROM image. Images
are loaded via the "Load ROM..." action in the "File" menu. The
selected ROM file will be loaded into memory at the correct ROM base
address.
### 3.4 Memory Window
@@ -84,19 +103,32 @@ Memory contents can be viewed (and edited) one page at a time through the Memory
![Trace Log] (https://github.com/sethm/symon/raw/master/screenshots/trace_log.png)
The last 20,000 execution steps are disassembled and logged to the Trace Log Window.
The last 20,000 execution steps are disassembled and logged to the Trace Log
Window.
### 3.6 NEW - Experimental 6545 CRTC Video
### 3.6 Simulator Speeds
![Speeds] (https://github.com/sethm/symon/raw/master/screenshots/simulator_menu.png)
Simulated speeds may be set from 1MHz to 8MHz.
### 3.7 Breakpoints
![Breakpoints] (https://github.com/sethm/symon/raw/master/screenshots/breakpoints.png)
Breakpoints can be set and removed through the Breakpoints window.
### 3.8 Experimental 6545 CRTC Video
![Composite Video] (https://github.com/sethm/symon/raw/master/screenshots/video_window.png)
This feature is highly experimental. It's possible to open a video window from the "View" menu.
This window simulates the output of a MOS 6545 CRT Controller located at address `$9000` and
`$9001`.
This feature is highly experimental. It's possible to open a video window
from the "View" menu. This window simulates the output of a MOS 6545 CRT
Controller located at address `$9000` and `$9001`.
By default, the 40 x 25 character display uses video memory located at base address `$7000`.
This means that the memory from address `$7000` (28672 decimal) to `$73E8` (29672 decimal)
is directly mapped to video.
By default, the 40 x 25 character display uses video memory located at base
address `$7000`. This means that the memory from address `$7000` (28672
decimal) to `$73E8` (29672 decimal) is directly mapped to video.
- Address Register (at address `$9000`)
- R1: Horizontal Displayed Columns
@@ -109,18 +141,20 @@ is directly mapped to video.
- R14: Cursor Position (High Byte)
- R15: Cursor Position (Low Byte)
Although the simulation is pretty good, there are a few key differences between
the simulated 6545 and a real 6545:
Although the simulation is pretty good, there are a few key differences
between the simulated 6545 and a real 6545:
- The simulated 6545 supports only the straight binary addressing mode of the real 6545,
and not the Row/Column addressing mode.
- The simulated 6545 has full 16 bit addressing, where the real 6545 has only
a 14-bit address bus.
- The simulation is done at a whole-frame level, meaning that lots of
6545 programming tricks that were achieved by updating the frame address
during vertical and horizontal sync times are not achievable. There is no way
(for example) to change the Display Start Address (R12 and R13) while a
frame is being drawn.
- The simulated 6545 supports only the straight binary addressing
mode of the real 6545, and not the Row/Column addressing mode.
- The simulated 6545 has full 16 bit addressing, where the real 6545
has only a 14-bit address bus.
- The simulation is done at a whole-frame level, meaning that lots
of 6545 programming tricks that were achieved by updating the
frame address during vertical and horizontal sync times are not
achievable. There is no way (for example) to change the Display Start
Address (R12 and R13) while a frame is being drawn.
For more information on the 6545 CRTC and its programming model, please see the following resources
@@ -129,7 +163,7 @@ For more information on the 6545 CRTC and its programming model, please see the
- [MOS 6545 Datasheet (PDF)] (http://www.6502.org/users/andre/hwinfo/crtc/crtc.html)
#### 3.6.1 Example BASIC Program to test Video
#### 3.8.1 Example BASIC Program to test Video
This program will fill the video screen with all printable characters.
@@ -152,26 +186,31 @@ Maven will build Symon, run unit tests, and produce a jar file in the
`target` directory containing the compiled simulator.
Symon is meant to be invoked directly from the jar file. To run with
Java 1.5 or greater, just type:
Java 1.8 or greater, just type:
$ java -jar symon-0.9.1.0.jar
$ java -jar symon-1.1.1.jar
When Symon is running, you should be presented with a simple graphical
interface.
### 4.2 ROM images
The simulator requires a 16KB ROM image loaded at address `$C000` to `$FFFF` to
work properly. Without a ROM in memory, the simulator will not be able to
reset, since the reset vector for the 6502 is located in this address space.
The simulator requires a ROM image loaded into memory to work
properly. Without a ROM in memory, the simulator will not be able to
reset, since the reset vector for the 6502 is located in the ROM
address space.
By default, any 16KB file named `rom.bin` that exists in the same directory
where Symon is launched will be loaded as a ROM image. ROM images can also
be swapped out at run-time with the "Load ROM Image..." in the File menu.
By default, any file named `rom.bin` that exists in the same directory
where Symon is launched will be loaded as a ROM image. ROM images can
also be swapped out at run-time with the "Load ROM Image..." in the
File menu.
The "samples" directory contains a ROM image named 'ehbasic.rom', containing
Lee Davison's Enhanced 6502 BASIC. This serves as a good starting point for
exploration.
The "samples" directory contains a ROM image for the Symon
architecture named 'ehbasic.rom', containing Lee Davison's Enhanced
6502 BASIC. This serves as a good starting point for exploration.
*Note*: Presently, EhBASIC only works with the Symon machine
architecture, not with MULTICOMP.
### 4.3 Loading A Program
@@ -184,7 +223,7 @@ reset.
There are two very simple sample program in the "samples" directory,
for testing.
- 'echo.prg' will echo back anything typed at the console.
- 'hello.prg' will continuously print "Hello, 6502 World!" to the console.
@@ -196,6 +235,26 @@ running.
## 5.0 Revision History
- **1.1.1:** 2 January, 2016 - Minor enhancement: Allows breakpoints
to be added with the Enter key.
- **1.1.0:** 31 December, 2015 - Fixed delay loop to better
simulate various clock speeds. Added ability to select clock
speed at runtime. Status display now shows the next instruction
to be executed, instead of the last instruction executed.
Added support for breakpoints.
- **1.0.0:** 10 August, 2014 - Added "Simple" machine
implementation, pure RAM with no IO. Added Klaus Dormann's
6502 Functional Tests for further machine verification (these
tests must be run in the "Simple" machine).
- **0.9.9.1:** 27 July, 2014 - Pressing 'Control' while clicking
'Reset' now performs a memory clear.
- **0.9.9:** 26 July, 2014 - MULTICOMP and multi-machine support
contributed by Maik Merten &lt;maikmerten@googlemail.com&gt;
- **0.9.1:** 26 January, 2014 - Support for IRQ and NMI handling.
- **0.9.0:** 29 December, 2013 - First pass at a 6545 CRTC simulation.
@@ -234,8 +293,6 @@ running.
- Better debugging tools from the UI, including breakpoints
and disassembly.
- More accurate timing.
- UI needs a ton more polish.
- More extensive testing.
@@ -247,14 +304,27 @@ running.
- Allow displaying ACIA status and dumping ACIA buffers, for
debugging.
## 7.0 Acknowledgements
- CRTC emulation is very naive. The whole frame is drawn in one
CPU step. This should be improved by drawing scan lines during
machine steps to approximate real NTSC/PAL refresh rates.
- Symbolic debugging.
## 7.0 Copyright and Acknowledgements
**Copyright (c) 2014 Seth J. Morabito &lt;web@loomcom.com&gt;**
Portions Copyright (c) 2014 Maik Merten &lt;maikmerten@googlemail.com&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://www.obelisk.demon.co.uk/6502/), for
wonderfully detailed information about the 6502
- [Neil Parker's "The 6502/65C02/65C816 Instruction Set Decoded"] (http://www.llx.com/~nparker/a2/opcodes.html),
+11 -27
View File
@@ -4,7 +4,7 @@
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>0.9.1.0</version>
<version>1.1.1</version>
<name>symon</name>
<url>http://www.loomcom.com/symon</url>
<properties>
@@ -35,6 +35,11 @@
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@@ -56,31 +61,10 @@
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes/com/loomcom/symon/ui/images</outputDirectory>
<resources>
<resource>
<directory>src/main/java/com/loomcom/symon/ui/images</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<version>2.6</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
@@ -88,7 +72,7 @@
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<mainClass>com.loomcom.symon.Simulator</mainClass>
<mainClass>com.loomcom.symon.Main</mainClass>
</manifest>
</archive>
</configuration>
@@ -107,8 +91,8 @@
<version>3.1</version>
<configuration>
<compilerArgument>-Xlint:unchecked</compilerArgument>
<source>1.6</source>
<target>1.6</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
@@ -116,7 +100,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<version>2.6</version>
<configuration>
<archive>
<manifest>
+1 -1
View File
@@ -150,7 +150,7 @@ END_CODE
; sign on string
LAB_mess
.byte $0D,$0A,"Symon (c) 2008-2013, Seth Morabito"
.byte $0D,$0A,"Symon (c) 2008-2014, Seth Morabito"
.byte $0D,$0A,"Enhanced 6502 BASIC 2.22 (c) Lee Davison"
.byte $0D,$0A,"[C]old/[W]arm ?",$00
+4
View File
@@ -0,0 +1,4 @@
*.rom
*.lst
*.map
*.o
+13
View File
@@ -0,0 +1,13 @@
CA=ca65
LD=ld65
all: ehbasic
ehbasic: ehbasic.o
$(LD) -C symon.config -vm -m ehbasic.map -o ehbasic.rom ehbasic.o
ehbasic.o:
$(CA) --listing -o ehbasic.o min_mon.asm
clean:
rm -f *.o *.rom *.map *.lst
+85
View File
@@ -0,0 +1,85 @@
This directoy contains a very slightly modified version of EhBASIC 2.22
to support the Symon simulator.
Usage
-----
The pre-assemled ROM image 'ehbasic.rom' in the "samples" directory is all you
need unless you want to rebuild this source code.
Just copy the image 'ehbasic.rom' to the directory where you run Symon, and
rename the file to 'rom.bin'. It will be loaded at memory location $D000 when
the simulator starts up. Click "Run" and you'll be presented with BASIC.
At the first prompt, type 'C' for a Cold Start
When prompted for free memory, type: $C000
Note that EhBASIC only accepts upper case input, so you'll need to use caps
lock (the cruise control for cool) to really make the most of it.
Building
--------
To build it, you'll need the CC65 tool chain from http://www.cc65.org/
and a suitable version of 'make'. Just typing:
% make
in this directory should re-build everything. You'll get a listing,
some object files, and the ROM image itself.
Changes from the EhBASIC 2.22
-----------------------------
- Minor syntax changes to allow assembly with the cc65 tool chain.
- Memory map modified for Symon.
- At reset, configure the 6551 ACIA for 8-N-1, 2400 baud.
- Monitor routines 'ACIAin' and 'ACIAout' modified for the 6551 ACIA.
Specifically, reading and writing will check the 6551 status register for
the status of rx and tx registers before receive or transmit.
EhBASIC is copyright Lee Davison <leeedavison@googlemail.com>
My changes are so slight that I hesitate to even say this, but for "CYA"
reasons:
Changes are copyright Seth Morabito <web@loomcom.com> and are distributed under
the same license as EhBASIC. I claim no commercial interest whatsoever. Any
commercial use must be negotiated directly with Lee Davison.
Original EhBASIC 2.22 README
----------------------------
Enhanced BASIC is a BASIC interpreter for the 6502 family microprocessors. It
is constructed to be quick and powerful and easily ported between 6502 systems.
It requires few resources to run and includes instructions to facilitate easy
low level handling of hardware devices. It also retains most of the powerful
high level instructions from similar BASICs.
EhBASIC is free but not copyright free. For non commercial use there is only one
restriction, any derivative work should include, in any binary image distributed,
the string "Derived from EhBASIC" and in any distribution that includes human
readable files a file that includes the above string in a human readable form
e.g. not as a comment in an HTML file.
For commercial use please contact me, Lee Davison, at leeedavison@googlemail.com
for conditions.
For more information on EhBASIC, other versions of EhBASIC and other projects
please visit my site at ..
http://mycorner.no-ip.org/index.html
P.S. c't magazin, henceforth refered to as "those thieving german bastards", are
prohibited from using this or any version of EhBASIC for any of their projects
or products. The excuse "we don't charge people for it" doesn't wash, it adds
value to your product so you owe me.
File diff suppressed because it is too large. Load diff
+449
View File
@@ -0,0 +1,449 @@
; minimal monitor for EhBASIC and 6502 simulator V1.05
; To run EhBASIC on the simulator load and assemble [F7] this file, start the simulator
; running [F6] then start the code with the RESET [CTRL][SHIFT]R. Just selecting RUN
; will do nothing, you'll still have to do a reset to run the code.
.feature labels_without_colons
.include "basic.asm"
; put the IRQ and MNI code in RAM so that it can be changed
IRQ_vec = VEC_SV+2 ; IRQ code vector
NMI_vec = IRQ_vec+$0A ; NMI code vector
; setup for the 6502 simulator environment
IO_AREA = $8800
ACIAdata = IO_AREA ; simulated ACIA r/w port
ACIAstatus = IO_AREA+1
ACIAcommand = IO_AREA+2
ACIAcontrol = IO_AREA+3
CRTC_A = $9000 ; CRTC register select
CRTC_C = $9001 ; CRTC register IO
CADDR_L = $E0 ; Cursor address (low)
CADDR_H = $E1 ; Cursor address (high)
C_COL = $E2 ; Cursor column
C_ROW = $E3 ; Cursor row
COUTC = $E4 ; Temp storage for char out.
R_STRT = $E5 ; E5,E6 contains starting address of
; current row
R_NXT = $E7 ; E7,E8 contains starting address of
; next row
N_ROWS = 24
N_COLS = 40
; now the code. all this does is set up the vectors and interrupt code
; and wait for the user to select [C]old or [W]arm start. nothing else
; fits in less than 128 bytes
.segment "MONITOR"
.org $FC00 ; pretend this is in a 1/8K ROM
; reset vector points here
RES_vec:
CLD ; clear decimal mode
LDX #$FF ; empty stack
TXS ; set the stack
;; Initialize the CRTC
LDA #$01
STA CRTC_A
LDA #N_COLS ; 40 columns
STA CRTC_C
LDA #$06
STA CRTC_A
LDA #N_ROWS ; 24 rows
STA CRTC_C
LDA #$70
STA CADDR_H
LDA #$00
STA CADDR_L
STA C_ROW
STA C_COL
JSR CLRVID
;; TODO: Initialize params on CRTC
; Initialize the ACIA
ACIA_init:
LDA #$00
STA ACIAstatus ; Soft reset
LDA #$0B
STA ACIAcommand ; Parity disabled, IRQ disabled
LDA #$1E
STA ACIAcontrol ; Set output for 8-N-1 9600
; set up vectors and interrupt code, copy them to page 2
LDY #END_CODE-LAB_vec ; set index/count
LAB_stlp:
LDA LAB_vec-1,Y ; get byte from interrupt code
STA VEC_IN-1,Y ; save to RAM
DEY ; decrement index/count
BNE LAB_stlp ; loop if more to do
; now do the signon message, Y = $00 here
LAB_signon:
LDA LAB_mess,Y ; get byte from sign on message
BEQ LAB_nokey ; exit loop if done
JSR V_OUTP ; output character
INY ; increment index
BNE LAB_signon ; loop, branch always
LAB_nokey:
JSR V_INPT ; call scan input device
BCC LAB_nokey ; loop if no key
AND #$DF ; mask xx0x xxxx, ensure upper case
CMP #'W' ; compare with [W]arm start
BEQ LAB_dowarm ; branch if [W]arm start
CMP #'C' ; compare with [C]old start
BNE RES_vec ; loop if not [C]old start
JMP LAB_COLD ; do EhBASIC cold start
LAB_dowarm:
JMP LAB_WARM ; do EhBASIC warm start
ACIAout:
PHA
@loop: LDA ACIAstatus
AND #$10
BEQ @loop ; Wait for buffer to empty
PLA
STA ACIAdata
RTS
;;; Byte out to the CRTC
;;;
;;; 1. Increment cursor position.
;;; 2. Scroll if necessary.
;;; 3. Store new cursor position in CRTC.
CRTCout:
JSR ACIAout ; Also echo to terminal for debugging.
;; In parallel, we're maintaining two states:
;; - The address of the cursor, and
;; - The Column/Row of the cursor.
;;
;; The latter state is used to handle scrolling and
;; for knowing how far to back up the cursor
;; for a carriage-return.
;; Backspace
CMP #$08
BEQ DO_BS
;; Line Feed
CMP #$0a
BEQ DO_LF
;; Carriage Return
CMP #$0d
BEQ DO_CR
;; Any other character...
STA COUTC ; Store the character going out
JSR COUT1 ; Output it where the cursor is.
;; We might need to scroll...
LDA C_ROW
CMP #N_ROWS-1
BNE @noscr
LDA C_COL
CMP #N_COLS-1
BNE @noscr
;; ... yup, we DO need to scroll.
JSR DO_SCROLL
RTS
@noscr: JSR INC_CADDR
INC C_COL
JSR SET_CURSOR
RTS
;; Handle a back-space character.
DO_BS: LDA C_COL
BNE @l1 ; Skip next bit
LDA #N_COLS-1
STA C_COL
BNE @l2
@l1: DEC C_COL
@l2: JSR DEC_CADDR ; Back up
JSR SET_CURSOR
@l3: LDA #$20
STA COUTC ; Echo a space ($20)
JSR COUT1
RTS
DO_LF: RTS ; Just swallow LF. CR emulates it.
DO_CR: SEC
LDA CADDR_L ; 1. Carriage return to start of row.
SBC C_COL
STA CADDR_L
LDA CADDR_H
SBC #$00 ; Will decrement H if carry was left
; set.
STA CADDR_H
;; 1. Are we on the last row? Scroll.
LDA C_ROW
CMP #N_ROWS-1
BNE @inc
JSR DO_SCROLL
RTS
@inc: JSR INCROW
@lf: CLC
LDA CADDR_L
ADC #$28 ; Now add $28
STA CADDR_L
LDA CADDR_H
ADC #$00 ; Will increment if carry was set
STA CADDR_H
LDA #$00 ; Reset cursor row to 0
STA C_COL
JSR SET_CURSOR
RTS
SET_CURSOR:
LDA #14
STA CRTC_A
LDA CADDR_H
STA CRTC_C
LDA #15
STA CRTC_A
LDA CADDR_L
STA CRTC_C
RTS
;; Increment C_ROW and put new
;; line start address in R_STRT,R_STRT+1
INCROW: TYA
PHA
INC C_ROW
BNE DECR2 ; Share code with DECROW
;; Decrement C_ROW and put new
;; line start address in R_STRT,R_STRT+1
DECROW: TYA
PHA
DEC C_ROW
DECR2: JSR SETRSTRT
PLA
TAY
RTS
;; Send the cursor home to the start of the line
HOME:
LDY C_ROW
LDA CRA_LO,Y
STA CADDR_L
LDA CRA_HI,Y
STA CADDR_H
LDA #$00
STA C_COL
RTS
;; Set the start address of the current row
SETRSTRT:
LDY C_ROW
LDA CRA_LO,Y
STA R_STRT
LDA CRA_HI,Y
STA R_STRT+1
RTS
;; Clear the current video line
CLRLN: LDA #$20
LDY #$00
@l1: STA (R_STRT),Y
INY
CPY #N_COLS
BNE @l1
RTS
;; Clear the video window and put the cursor at the home
;; position
CLRVID:
LDA #$20
LDX #N_ROWS-1
@l1: STX C_ROW
JSR SETRSTRT
JSR CLRLN
DEX
BPL @l1
RTS
;; Handle a scroll request.
;;
;; The cursor could be anywhere on the line when this scroll
;; request comes in.
DO_SCROLL:
PHA
TYA
PHA
TXA
PHA
;; The idea here is to copy each line to the line above it
LDX #$00
@l1: LDA CRA_LO,X
STA R_STRT
LDA CRA_HI,X
STA R_STRT+1
INX
LDA CRA_LO,X
STA R_NXT
LDA CRA_HI,X
STA R_NXT+1
LDY #0
@l2: LDA (R_NXT),Y
STA (R_STRT),Y
INY
CPY #N_COLS
BNE @l2
CPX #N_ROWS
BNE @l1
;; Clear the line the cursor occupies.
JSR CLRLN
;; Move the cursor to the beginning of the line
JSR HOME
JSR SET_CURSOR
PLA
TAX
PLA
TAY ; Restore Y
PLA
RTS
;; Decrement the cursor address
INC_CADDR:
INC CADDR_L
BNE @l1 ; Did we increment to 0?
INC CADDR_H ; Yes, also increment high
@l1: RTS
;; Increment the cursor address
DEC_CADDR:
CMP CADDR_L ; Is low alrady 0?
BNE @l1
DEC CADDR_H ; Yes, decrement high
@l1: DEC CADDR_L
RTS
COUT1:
TYA
PHA ; Save Y
LDY #$00
LDA COUTC
STA (CADDR_L),Y
PLA
TAY ; Restore Y
RTS
;;;
;;; byte in from ACIA. This subroutine will also force
;;; all lowercase letters to be uppercase, because EhBASIC
;;; only understands upper-case tokens
;;;
ACIAin:
LDA ACIAstatus ; Read 6551 status
AND #$08 ;
BEQ LAB_nobyw ; If rx buffer empty, no byte
LDA ACIAdata ; Read byte from 6551
CMP #'a' ; Is it < 'a'?
BCC @done ; Yes, we're done
CMP #'{' ; Is it >= '{'?
BCS @done ; Yes, we're done
AND #$5f ; Otherwise, mask to uppercase
@done:
SEC ; Flag byte received
RTS
LAB_nobyw:
CLC ; flag no byte received
no_load: ; empty load vector for EhBASIC
no_save: ; empty save vector for EhBASIC
RTS
; vector tables
LAB_vec:
.word ACIAin ; byte in from simulated ACIA
.word CRTCout ; byte out to simulated ACIA
.word no_load ; null load vector for EhBASIC
.word no_save ; null save vector for EhBASIC
; EhBASIC IRQ support
IRQ_CODE:
PHA ; save A
LDA IrqBase ; get the IRQ flag byte
LSR ; shift the set b7 to b6, and on down ...
ORA IrqBase ; OR the original back in
STA IrqBase ; save the new IRQ flag byte
PLA ; restore A
RTI
; EhBASIC NMI support
NMI_CODE:
PHA ; save A
LDA NmiBase ; get the NMI flag byte
LSR ; shift the set b7 to b6, and on down ...
ORA NmiBase ; OR the original back in
STA NmiBase ; save the new NMI flag byte
PLA ; restore A
RTI
END_CODE:
;;; CRTC row addresses: HIGH
CRA_HI:
.byte $70,$70,$70,$70,$70,$70,$70,$71
.byte $71,$71,$71,$71,$71,$72,$72,$72
.byte $72,$72,$72,$72,$73,$73,$73,$73
;;; CRTC row addresses: LOW
CRA_LO:
.byte $00,$28,$50,$78,$A0,$C8,$F0,$18
.byte $40,$68,$90,$B8,$E0,$08,$30,$58
.byte $80,$A8,$D0,$F8,$20,$48,$70,$98
; sign on string
LAB_mess:
.byte $0D,$0A,"SYMON (C) 2008-2014, SETH MORABITO"
.byte $0D,$0A,"ENHANCED 6502 BASIC 2.22 (C) LEE DAVISON"
.byte $0D,$0A,"[C]OLD/[W]ARM ?",$00
; system vectors
.segment "VECTORS"
.org $FFFA
.word NMI_vec ; NMI vector
.word RES_vec ; RESET vector
.word IRQ_vec ; IRQ vector
+18
View File
@@ -0,0 +1,18 @@
MEMORY {
RAM1: start = $0000, size = $8000;
ROM1: start = $C000, size = $3C00, fill = yes;
MONITOR: start = $FC00, size = $3FA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
}
SEGMENTS {
CODE: load = ROM1, type = ro;
DATA: load = ROM1, type = ro;
MONITOR: load = MONITOR, type = ro;
VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__ = $0300;
}
Binary file not shown.
+13999
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

@@ -0,0 +1,85 @@
package com.loomcom.symon;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.Utils;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.TreeSet;
public class Breakpoints extends AbstractTableModel {
private TreeSet<Integer> breakpoints;
private Simulator simulator;
public Breakpoints(Simulator simulator) {
this.breakpoints = new TreeSet<>();
this.simulator = simulator;
}
public boolean contains(int address) {
return this.breakpoints.contains(address);
}
public void addBreakpoint(int address) {
this.breakpoints .add(address);
fireTableDataChanged();
}
public void removeBreakpoint(int address) {
this.breakpoints.remove(address);
fireTableDataChanged();
}
public void removeBreakpointAtIndex(int index) {
if (index < 0) {
return;
}
ArrayList<Integer> values = new ArrayList<>(breakpoints);
int value = values.get(index);
this.breakpoints.remove(value);
fireTableDataChanged();
}
public void refresh() {
fireTableDataChanged();
}
@Override
public String getColumnName(int index) {
if (index == 0) {
return "Address";
} else {
return "Inst";
}
}
@Override
public int getRowCount() {
return breakpoints.size();
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
ArrayList<Integer> values = new ArrayList<>(breakpoints);
if (columnIndex == 0) {
return "$" + Utils.wordToHex(values.get(rowIndex));
} else if (columnIndex == 1) {
int address = values.get(rowIndex);
try {
return simulator.disassembleOpAtAddress(address);
} catch (MemoryAccessException ex) {
return "???";
}
} else {
return null;
}
}
}
+98 -48
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,9 +26,15 @@ package com.loomcom.symon;
import com.loomcom.symon.devices.Device;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* The Bus ties the whole thing together, man.
@@ -45,15 +51,19 @@ public class Bus {
// The CPU
private Cpu cpu;
// Ordered list of IO devices.
private SortedSet<Device> devices;
// Ordered sets of IO devices, associated with their priority
private Map<Integer, SortedSet<Device>> deviceMap;
// an array for quick lookup of adresses, brute-force style
private Device[] deviceAddressArray;
public Bus(int size) {
this(0, size - 1);
}
public Bus(int startAddress, int endAddress) {
this.devices = new TreeSet<Device>();
this.deviceMap = new HashMap<>();
this.startAddress = startAddress;
this.endAddress = endAddress;
}
@@ -65,39 +75,73 @@ public class Bus {
public int endAddress() {
return endAddress;
}
private void buildDeviceAddressArray() {
int size = (this.endAddress - this.startAddress) + 1;
deviceAddressArray = new Device[size];
// getDevices() provides an OrderedSet with devices ordered by priorities
for(Device device : getDevices()) {
MemoryRange range = device.getMemoryRange();
for(int address = range.startAddress; address <= range.endAddress; ++address) {
deviceAddressArray[address - this.startAddress] = device;
}
}
}
/**
* Add a device to the bus.
*
* @param device Device to add
* @param priority Bus prioirity.
* @throws MemoryRangeException
*/
public void addDevice(Device device, int priority) throws MemoryRangeException {
MemoryRange range = device.getMemoryRange();
if (range.startAddress() < this.startAddress || range.startAddress() > this.endAddress) {
throw new MemoryRangeException("start address of device " + device.getName() + " does not fall within the address range of the bus");
}
if (range.endAddress() < this.startAddress || range.endAddress() > this.endAddress) {
throw new MemoryRangeException("end address of device " + device.getName() + " does not fall within the address range of the bus");
}
SortedSet<Device> deviceSet = deviceMap.get(priority);
if (deviceSet == null) {
deviceSet = new TreeSet<>();
deviceMap.put(priority, deviceSet);
}
device.setBus(this);
deviceSet.add(device);
buildDeviceAddressArray();
}
/**
* Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others.
*
* @param device
* @param device Device to add
* @throws MemoryRangeException
*/
public void addDevice(Device device) throws MemoryRangeException {
// Make sure there's no memory overlap.
MemoryRange memRange = device.getMemoryRange();
for (Device d : devices) {
if (d.getMemoryRange().overlaps(memRange)) {
throw new MemoryRangeException("The device being added at " +
String.format("$%04X", memRange.startAddress()) +
" overlaps with an existing " +
"device, '" + d + "'");
}
}
// Add the device
device.setBus(this);
devices.add(device);
addDevice(device, 0);
}
/**
* Remove a device from the bus.
*
* @param device
* @param device Device to remove
*/
public void removeDevice(Device device) {
if (devices.contains(device)) {
devices.remove(device);
for(SortedSet<Device> deviceSet : deviceMap.values()) {
deviceSet.remove(device);
}
buildDeviceAddressArray();
}
public void addCpu(Cpu cpu) {
@@ -111,43 +155,39 @@ public class Bus {
* device.
*/
public boolean isComplete() {
// Empty maps cannot be complete.
if (devices.isEmpty()) {
return false;
if(deviceAddressArray == null) {
buildDeviceAddressArray();
}
// Loop over devices and add their size
int filledMemory = 0;
for (Device d : devices) {
filledMemory += d.getSize();
for(int address = startAddress; address <= endAddress; ++address) {
if(deviceAddressArray[address - startAddress] == null) {
return false;
}
}
// Returns if the total size of the devices fill the bus' memory space
return filledMemory == endAddress - startAddress + 1;
return true;
}
public int read(int address) throws MemoryAccessException {
for (Device d : devices) {
Device d = deviceAddressArray[address - this.startAddress];
if(d != null) {
MemoryRange range = d.getMemoryRange();
if (range.includes(address)) {
// Compute offset into this device's address space.
int devAddr = address - range.startAddress();
return d.read(devAddr);
}
int devAddr = address - range.startAddress();
return d.read(devAddr) & 0xff;
}
throw new MemoryAccessException("Bus read failed. No device at address " + String.format("$%04X", address));
}
public void write(int address, int value) throws MemoryAccessException {
for (Device d : devices) {
Device d = deviceAddressArray[address - this.startAddress];
if(d != null) {
MemoryRange range = d.getMemoryRange();
if (range.includes(address)) {
// Compute offset into this device's address space.
int devAddr = address - range.startAddress();
d.write(devAddr, value);
return;
}
int devAddr = address - range.startAddress();
d.write(devAddr, value);
return;
}
throw new MemoryAccessException("Bus write failed. No device at address " + String.format("$%04X", address));
}
@@ -176,8 +216,18 @@ public class Bus {
}
public SortedSet<Device> getDevices() {
// Expose a copy of the device list, not the original
return new TreeSet<Device>(devices);
// create an ordered set of devices, ordered by device priorities
SortedSet<Device> devices = new TreeSet<>();
List<Integer> priorities = new ArrayList<>(deviceMap.keySet());
Collections.sort(priorities);
for(int priority : priorities) {
SortedSet<Device> deviceSet = deviceMap.get(priority);
devices.addAll(deviceSet.stream().collect(Collectors.toList()));
}
return devices;
}
public Cpu getCpu() {
+155 -282
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2012 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -24,7 +24,10 @@
package com.loomcom.symon;
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;
/**
* This class provides a simulation of the MOS 6502 CPU's state machine.
@@ -33,6 +36,8 @@ import com.loomcom.symon.util.HexUtil;
*/
public class Cpu implements InstructionTable {
private final static Logger logger = LoggerFactory.getLogger(Cpu.class.getName());
/* Process status register mnemonics */
public static final int P_CARRY = 0x01;
public static final int P_ZERO = 0x02;
@@ -53,28 +58,22 @@ public class Cpu implements InstructionTable {
public static final int IRQ_VECTOR_L = 0xfffe;
public static final int IRQ_VECTOR_H = 0xffff;
// The delay in microseconds between steps.
// TODO: Make configurable
private static final int CLOCK_IN_NS = 1000;
public static final long DEFAULT_CLOCK_PERIOD_IN_NS = 1000;
/* Simulated clock speed (default is 1MHz) */
private long clockPeriodInNs = DEFAULT_CLOCK_PERIOD_IN_NS;
/* Simulated behavior */
private static CpuBehavior behavior;
private CpuBehavior behavior;
/* The Bus */
private Bus bus;
/* The CPU state */
private static final CpuState state = new CpuState();
private final CpuState state = new CpuState();
/* Scratch space for addressing mode and effective address
* calculations */
private int irAddressMode; // Bits 3-5 of IR: [ | | |X|X|X| | ]
private int irOpMode; // Bits 6-7 of IR: [ | | | | | |X|X]
private int effectiveAddress;
/* Internal scratch space */
private int lo = 0, hi = 0; // Used in address calculation
private int tmp; // Temporary storage
/* start time of op execution, needed for speed simulation */
private long opBeginTime;
/**
* Construct a new CPU.
@@ -105,10 +104,6 @@ public class Cpu implements InstructionTable {
this.behavior = behavior;
}
public CpuBehavior getBehavior() {
return behavior;
}
/**
* Reset the CPU to known initial values.
*/
@@ -119,7 +114,7 @@ public class Cpu implements InstructionTable {
state.sp = 0xff;
// Set the PC to the address stored in the reset vector
state.pc = address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H));
state.pc = Utils.address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H));
// Clear instruction register.
state.ir = 0;
@@ -145,6 +140,8 @@ public class Cpu implements InstructionTable {
state.a = 0;
state.x = 0;
state.y = 0;
peekAhead();
}
public void step(int num) throws MemoryAccessException {
@@ -157,6 +154,8 @@ public class Cpu implements InstructionTable {
* Performs an individual instruction cycle.
*/
public void step() throws MemoryAccessException {
opBeginTime = System.nanoTime();
// Store the address from which the IR was read, for debugging
state.lastPc = state.pc;
@@ -170,8 +169,8 @@ public class Cpu implements InstructionTable {
// Fetch memory location for this instruction.
state.ir = bus.read(state.pc);
irAddressMode = (state.ir >> 2) & 0x07;
irOpMode = state.ir & 0x03;
int irAddressMode = (state.ir >> 2) & 0x07; // Bits 3-5 of IR: [ | | |X|X|X| | ]
int irOpMode = state.ir & 0x03; // Bits 6-7 of IR: [ | | | | | |X|X]
incrementPC();
@@ -188,7 +187,8 @@ public class Cpu implements InstructionTable {
state.stepCounter++;
// Get the data from the effective address (if any)
effectiveAddress = 0;
int effectiveAddress = 0;
int tmp; // Temporary storage
switch (irOpMode) {
case 0:
@@ -202,7 +202,7 @@ public class Cpu implements InstructionTable {
case 2: // Accumulator - ignored
break;
case 3: // Absolute
effectiveAddress = address(state.args[0], state.args[1]);
effectiveAddress = Utils.address(state.args[0], state.args[1]);
break;
case 5: // Zero Page,X / Zero Page,Y
if (state.ir == 0x96 || state.ir == 0xb6) {
@@ -224,7 +224,7 @@ public class Cpu implements InstructionTable {
switch (irAddressMode) {
case 0: // (Zero Page,X)
tmp = (state.args[0] + state.x) & 0xff;
effectiveAddress = address(bus.read(tmp), bus.read(tmp + 1));
effectiveAddress = Utils.address(bus.read(tmp), bus.read(tmp + 1));
break;
case 1: // Zero Page
effectiveAddress = state.args[0];
@@ -233,10 +233,10 @@ public class Cpu implements InstructionTable {
effectiveAddress = -1;
break;
case 3: // Absolute
effectiveAddress = address(state.args[0], state.args[1]);
effectiveAddress = Utils.address(state.args[0], state.args[1]);
break;
case 4: // (Zero Page),Y
tmp = address(bus.read(state.args[0]),
tmp = Utils.address(bus.read(state.args[0]),
bus.read((state.args[0] + 1) & 0xff));
effectiveAddress = (tmp + state.y) & 0xffff;
break;
@@ -277,7 +277,7 @@ public class Cpu implements InstructionTable {
case 0x20: // JSR - Jump to Subroutine - Implied
stackPush((state.pc - 1 >> 8) & 0xff); // PC high byte
stackPush(state.pc - 1 & 0xff); // PC low byte
state.pc = address(state.args[0], state.args[1]);
state.pc = Utils.address(state.args[0], state.args[1]);
break;
case 0x28: // PLP - Pull Processor Status - Implied
setProcessorStatus(stackPop());
@@ -292,9 +292,9 @@ public class Cpu implements InstructionTable {
break;
case 0x40: // RTI - Return from Interrupt - Implied
setProcessorStatus(stackPop());
lo = stackPop();
hi = stackPop();
setProgramCounter(address(lo, hi));
int lo = stackPop();
int hi = stackPop();
setProgramCounter(Utils.address(lo, hi));
break;
case 0x48: // PHA - Push Accumulator - Implied
stackPush(state.a);
@@ -310,7 +310,7 @@ public class Cpu implements InstructionTable {
case 0x60: // RTS - Return from Subroutine - Implied
lo = stackPop();
hi = stackPop();
setProgramCounter((address(lo, hi) + 1) & 0xffff);
setProgramCounter((Utils.address(lo, hi) + 1) & 0xffff);
break;
case 0x68: // PLA - Pull Accumulator - Implied
state.a = stackPop();
@@ -398,20 +398,20 @@ public class Cpu implements InstructionTable {
/** JMP *****************************************************************/
case 0x4c: // JMP - Absolute
state.pc = address(state.args[0], state.args[1]);
state.pc = Utils.address(state.args[0], state.args[1]);
break;
case 0x6c: // JMP - Indirect
lo = address(state.args[0], state.args[1]); // Address of low byte
lo = Utils.address(state.args[0], state.args[1]); // Address of low byte
if (state.args[0] == 0xff &&
(behavior == CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG)) {
hi = address(0x00, state.args[1]);
hi = Utils.address(0x00, state.args[1]);
} else {
hi = lo + 1;
}
state.pc = address(bus.read(lo), bus.read(hi));
state.pc = Utils.address(bus.read(lo), bus.read(hi));
/* TODO: For accuracy, allow a flag to enable broken behavior of early 6502s:
*
* "An original 6502 has does not correctly fetch the target
@@ -730,6 +730,18 @@ public class Cpu implements InstructionTable {
}
delayLoop(state.ir);
// Peek ahead to the next insturction and arguments
peekAhead();
}
private void peekAhead() throws MemoryAccessException {
state.nextIr = bus.read(state.pc);
int nextInstSize = Cpu.instructionSizes[state.nextIr];
for (int i = 1; i < nextInstSize; i++) {
int nextRead = (state.pc + i) % bus.endAddress();
state.nextArgs[i-1] = bus.read(nextRead);
}
}
private void handleIrq(int returnPc) throws MemoryAccessException {
@@ -758,7 +770,7 @@ public class Cpu implements InstructionTable {
setIrqDisableFlag();
// Load interrupt vector address into PC
state.pc = address(bus.read(vectorLow), bus.read(vectorHigh));
state.pc = Utils.address(bus.read(vectorLow), bus.read(vectorHigh));
}
/**
@@ -768,7 +780,7 @@ public class Cpu implements InstructionTable {
*
* @param acc The current value of the accumulator
* @param operand The operand
* @return
* @return The sum of the accumulator and the operand
*/
private int adc(int acc, int operand) {
int result = (operand & 0xff) + (acc & 0xff) + getCarryBit();
@@ -866,7 +878,7 @@ public class Cpu implements InstructionTable {
*/
private int lsr(int m) {
setCarryFlag((m & 0x01) != 0);
return (m >>> 1) & 0xff;
return (m & 0xff) >>> 1;
}
/**
@@ -891,6 +903,14 @@ public class Cpu implements InstructionTable {
return result;
}
/**
* @param clockPeriodInNs The simulated clock period, in nanoseconds
*/
public void setClockPeriodInNs(long clockPeriodInNs) {
logger.debug("Setting simulated clock period to {} ns.", clockPeriodInNs);
this.clockPeriodInNs = clockPeriodInNs;
}
/**
* Return the current Cpu State.
*
@@ -1115,6 +1135,14 @@ public class Cpu implements InstructionTable {
public void setProgramCounter(int addr) {
state.pc = addr;
// As a side-effect of setting the program counter,
// we want to peek ahead at the next state.
try {
peekAhead();
} catch (MemoryAccessException ex) {
logger.error("Could not peek ahead at next instruction state.");
}
}
public int getStackPointer() {
@@ -1170,23 +1198,23 @@ public class Cpu implements InstructionTable {
}
public String getAccumulatorStatus() {
return "$" + HexUtil.byteToHex(state.a);
return "$" + Utils.byteToHex(state.a);
}
public String getXRegisterStatus() {
return "$" + HexUtil.byteToHex(state.x);
return "$" + Utils.byteToHex(state.x);
}
public String getYRegisterStatus() {
return "$" + HexUtil.byteToHex(state.y);
return "$" + Utils.byteToHex(state.y);
}
public String getProgramCounterStatus() {
return "$" + HexUtil.wordToHex(state.pc);
return "$" + Utils.wordToHex(state.pc);
}
public String getStackPointerStatus() {
return "$" + HexUtil.byteToHex(state.sp);
return "$" + Utils.byteToHex(state.sp);
}
public int getProcessorStatus() {
@@ -1270,19 +1298,12 @@ public class Cpu implements InstructionTable {
}
}
/**
* Given two bytes, return an address.
*/
int address(int lowByte, int hiByte) {
return ((hiByte << 8) | lowByte) & 0xffff;
}
/**
* Given a hi byte and a low byte, return the Absolute,X
* offset address.
*/
int xAddress(int lowByte, int hiByte) {
return (address(lowByte, hiByte) + state.x) & 0xffff;
return (Utils.address(lowByte, hiByte) + state.x) & 0xffff;
}
/**
@@ -1290,7 +1311,7 @@ public class Cpu implements InstructionTable {
* offset address.
*/
int yAddress(int lowByte, int hiByte) {
return (address(lowByte, hiByte) + state.y) & 0xffff;
return (Utils.address(lowByte, hiByte) + state.y) & 0xffff;
}
/**
@@ -1316,247 +1337,99 @@ public class Cpu implements InstructionTable {
}
/*
* Perform a busy-loop for CLOCK_IN_NS nanoseconds
* Perform a busy-loop until the instruction should complete on the wall clock
*/
private void delayLoop(int opcode) {
int clockSteps = Cpu.instructionClocks[0xff & opcode];
// Just a precaution. This could be better.
if (clockSteps == 0) {
clockSteps = 1;
}
long startTime = System.nanoTime();
long stopTime = startTime + (CLOCK_IN_NS * clockSteps);
// Busy loop
while (System.nanoTime() < stopTime) {
;
}
}
if (clockSteps == 0) {
logger.warn("Opcode {} has clock step of 0!", opcode);
return;
}
long interval = clockSteps * clockPeriodInNs;
long end;
do {
end = System.nanoTime();
} while (opBeginTime + interval >= end);
}
/**
* A compact, struct-like representation of CPU state.
* Return a formatted string representing the last instruction and
* operands that were executed.
*
* @return A string representing the mnemonic and operands of the instruction
*/
public static class CpuState {
/**
* Accumulator
*/
public int a;
/**
* X index regsiter
*/
public int x;
/**
* Y index register
*/
public int y;
/**
* Stack Pointer
*/
public int sp;
/**
* Program Counter
*/
public int pc;
/**
* Instruction Register
*/
public int ir;
public int lastPc;
public int[] args = new int[2];
public int instSize;
public boolean opTrap;
public boolean irqAsserted;
public boolean nmiAsserted;
public static String disassembleOp(int opCode, int[] args) {
String mnemonic = opcodeNames[opCode];
/* Status Flag Register bits */
public boolean carryFlag;
public boolean negativeFlag;
public boolean zeroFlag;
public boolean irqDisableFlag;
public boolean decimalModeFlag;
public boolean breakFlag;
public boolean overflowFlag;
public long stepCounter = 0L;
/**
* Create an empty CPU State.
*/
public CpuState() {}
/**
* Snapshot a copy of the CpuState.
*
* (This is a copy constructor rather than an implementation of <code>Clonable</code>
* based on Josh Bloch's recommendation)
*
* @param s The CpuState to copy.
*/
public CpuState(CpuState s) {
this.a = s.a;
this.x = s.x;
this.y = s.y;
this.sp = s.sp;
this.pc = s.pc;
this.ir = s.ir;
this.lastPc = s.lastPc;
this.args[0] = s.args[0];
this.args[1] = s.args[1];
this.instSize = s.instSize;
this.opTrap = s.opTrap;
this.irqAsserted = s.irqAsserted;
this.carryFlag = s.carryFlag;
this.negativeFlag = s.negativeFlag;
this.zeroFlag = s.zeroFlag;
this.irqDisableFlag = s.irqDisableFlag;
this.decimalModeFlag = s.decimalModeFlag;
this.breakFlag = s.breakFlag;
this.overflowFlag = s.overflowFlag;
this.stepCounter = s.stepCounter;
if (mnemonic == null) {
return "???";
}
/**
* Returns a string formatted for the trace log.
*
* @return a string formatted for the trace log.
*/
public String toTraceEvent() {
String opcode = disassembleOp();
StringBuilder sb = new StringBuilder(getInstructionByteStatus());
sb.append(" ");
sb.append(String.format("%-14s", opcode));
sb.append("A:" + HexUtil.byteToHex(a) + " ");
sb.append("X:" + HexUtil.byteToHex(x) + " ");
sb.append("Y:" + HexUtil.byteToHex(y) + " ");
sb.append("F:" + HexUtil.byteToHex(getStatusFlag()) + " ");
sb.append("S:1" + HexUtil.byteToHex(sp) + " ");
sb.append(getProcessorStatusString() + "\n");
return sb.toString();
StringBuilder sb = new StringBuilder(mnemonic);
switch (instructionModes[opCode]) {
case ABS:
sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1])));
break;
case ABX:
sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(",X");
break;
case ABY:
sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(",Y");
break;
case IMM:
sb.append(" #$").append(Utils.byteToHex(args[0]));
break;
case IND:
sb.append(" ($").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(")");
break;
case XIN:
sb.append(" ($").append(Utils.byteToHex(args[0])).append(",X)");
break;
case INY:
sb.append(" ($").append(Utils.byteToHex(args[0])).append("),Y");
break;
case REL:
case ZPG:
sb.append(" $").append(Utils.byteToHex(args[0]));
break;
case ZPX:
sb.append(" $").append(Utils.byteToHex(args[0])).append(",X");
break;
case ZPY:
sb.append(" $").append(Utils.byteToHex(args[0])).append(",Y");
break;
}
/**
* @returns The value of the Process Status Register, as a byte.
*/
public int getStatusFlag() {
int status = 0x20;
if (carryFlag) {
status |= P_CARRY;
}
if (zeroFlag) {
status |= P_ZERO;
}
if (irqDisableFlag) {
status |= P_IRQ_DISABLE;
}
if (decimalModeFlag) {
status |= P_DECIMAL;
}
if (breakFlag) {
status |= P_BREAK;
}
if (overflowFlag) {
status |= P_OVERFLOW;
}
if (negativeFlag) {
status |= P_NEGATIVE;
}
return status;
return sb.toString();
}
/**
* Return a formatted string representing the next instruction and
* operands to be executed.
*
* @return A string representing the mnemonic and operands of the instruction
*/
public String disassembleNextOp() {
return Cpu.disassembleOp(state.nextIr, state.nextArgs);
}
/**
* @param address Address to disassemble
* @return String containing the disassembled instruction and operands.
*/
public String disassembleOpAtAddress(int address) throws MemoryAccessException {
int opCode = bus.read(address);
int args[] = new int[2];
int size = Cpu.instructionSizes[opCode];
for (int i = 1; i < size; i++) {
int nextRead = (address + i) % bus.endAddress();
args[i-1] = bus.read(nextRead);
}
public String getInstructionByteStatus() {
switch (Cpu.instructionSizes[ir]) {
case 0:
case 1:
return HexUtil.wordToHex(lastPc) + " " +
HexUtil.byteToHex(ir) + " ";
case 2:
return HexUtil.wordToHex(lastPc) + " " +
HexUtil.byteToHex(ir) + " " +
HexUtil.byteToHex(args[0]) + " ";
case 3:
return HexUtil.wordToHex(lastPc) + " " +
HexUtil.byteToHex(ir) + " " +
HexUtil.byteToHex(args[0]) + " " +
HexUtil.byteToHex(args[1]);
default:
return null;
}
}
/**
* Given an opcode and its operands, return a formatted name.
*
* @return A string representing the mnemonic and operands of the instruction
*/
public String disassembleOp() {
String mnemonic = opcodeNames[ir];
if (mnemonic == null) {
return "???";
}
StringBuilder sb = new StringBuilder(mnemonic);
switch (instructionModes[ir]) {
case ABS:
sb.append(" $" + HexUtil.wordToHex(address(args[0], args[1])));
break;
case ABX:
sb.append(" $" + HexUtil.wordToHex(address(args[0], args[1])) + ",X");
break;
case ABY:
sb.append(" $" + HexUtil.wordToHex(address(args[0], args[1])) + ",Y");
break;
case IMM:
sb.append(" #$" + HexUtil.byteToHex(args[0]));
break;
case IND:
sb.append(" ($" + HexUtil.wordToHex(address(args[0], args[1])) + ")");
break;
case XIN:
sb.append(" ($" + HexUtil.byteToHex(args[0]) + ",X)");
break;
case INY:
sb.append(" ($" + HexUtil.byteToHex(args[0]) + "),Y");
break;
case REL:
case ZPG:
sb.append(" $" + HexUtil.byteToHex(args[0]));
break;
case ZPX:
sb.append(" $" + HexUtil.byteToHex(args[0]) + ",X");
break;
case ZPY:
sb.append(" $" + HexUtil.byteToHex(args[0]) + ",Y");
break;
}
return sb.toString();
}
/**
* Given two bytes, return an address.
*/
private int address(int lowByte, int hiByte) {
return ((hiByte << 8) | lowByte) & 0xffff;
}
/**
* @return A string representing the current status register state.
*/
public String getProcessorStatusString() {
StringBuilder sb = new StringBuilder("[");
sb.append(negativeFlag ? 'N' : '.'); // Bit 7
sb.append(overflowFlag ? 'V' : '.'); // Bit 6
sb.append("-"); // Bit 5 (always 1)
sb.append(breakFlag ? 'B' : '.'); // Bit 4
sb.append(decimalModeFlag ? 'D' : '.'); // Bit 3
sb.append(irqDisableFlag ? 'I' : '.'); // Bit 2
sb.append(zeroFlag ? 'Z' : '.'); // Bit 1
sb.append(carryFlag ? 'C' : '.'); // Bit 0
sb.append("]");
return sb.toString();
}
return disassembleOp(opCode, args);
}
}
@@ -0,0 +1,177 @@
package com.loomcom.symon;
import com.loomcom.symon.util.Utils;
/**
* A compact, struct-like representation of CPU state.
*/
public class CpuState {
/**
* Accumulator
*/
public int a;
/**
* X index regsiter
*/
public int x;
/**
* Y index register
*/
public int y;
/**
* Stack Pointer
*/
public int sp;
/**
* Program Counter
*/
public int pc;
/**
* Last Loaded Instruction Register
*/
public int ir;
/**
* Peek-Ahead to next IR
*/
public int nextIr;
public int[] args = new int[2];
public int[] nextArgs = new int[2];
public int instSize;
public boolean opTrap;
public boolean irqAsserted;
public boolean nmiAsserted;
public int lastPc;
/* Status Flag Register bits */
public boolean carryFlag;
public boolean negativeFlag;
public boolean zeroFlag;
public boolean irqDisableFlag;
public boolean decimalModeFlag;
public boolean breakFlag;
public boolean overflowFlag;
public long stepCounter = 0L;
public CpuState() {}
/**
* Snapshot a copy of the CpuState.
*
* (This is a copy constructor rather than an implementation of <code>Cloneable</code>
* based on Josh Bloch's recommendation)
*
* @param s The CpuState to copy.
*/
public CpuState(CpuState s) {
this.a = s.a;
this.x = s.x;
this.y = s.y;
this.sp = s.sp;
this.pc = s.pc;
this.ir = s.ir;
this.nextIr = s.nextIr;
this.lastPc = s.lastPc;
this.args[0] = s.args[0];
this.args[1] = s.args[1];
this.nextArgs[0] = s.nextArgs[0];
this.nextArgs[1] = s.nextArgs[1];
this.instSize = s.instSize;
this.opTrap = s.opTrap;
this.irqAsserted = s.irqAsserted;
this.carryFlag = s.carryFlag;
this.negativeFlag = s.negativeFlag;
this.zeroFlag = s.zeroFlag;
this.irqDisableFlag = s.irqDisableFlag;
this.decimalModeFlag = s.decimalModeFlag;
this.breakFlag = s.breakFlag;
this.overflowFlag = s.overflowFlag;
this.stepCounter = s.stepCounter;
}
/**
* Returns a string formatted for the trace log.
*
* @return a string formatted for the trace log.
*/
public String toTraceEvent() {
String opcode = Cpu.disassembleOp(ir, args);
return getInstructionByteStatus() + " " +
String.format("%-14s", opcode) +
"A:" + Utils.byteToHex(a) + " " +
"X:" + Utils.byteToHex(x) + " " +
"Y:" + Utils.byteToHex(y) + " " +
"F:" + Utils.byteToHex(getStatusFlag()) + " " +
"S:1" + Utils.byteToHex(sp) + " " +
getProcessorStatusString() + "\n";
}
/**
* @return The value of the Process Status Register, as a byte.
*/
public int getStatusFlag() {
int status = 0x20;
if (carryFlag) {
status |= Cpu.P_CARRY;
}
if (zeroFlag) {
status |= Cpu.P_ZERO;
}
if (irqDisableFlag) {
status |= Cpu.P_IRQ_DISABLE;
}
if (decimalModeFlag) {
status |= Cpu.P_DECIMAL;
}
if (breakFlag) {
status |= Cpu.P_BREAK;
}
if (overflowFlag) {
status |= Cpu.P_OVERFLOW;
}
if (negativeFlag) {
status |= Cpu.P_NEGATIVE;
}
return status;
}
public String getInstructionByteStatus() {
switch (Cpu.instructionSizes[ir]) {
case 0:
case 1:
return Utils.wordToHex(lastPc) + " " +
Utils.byteToHex(ir) + " ";
case 2:
return Utils.wordToHex(lastPc) + " " +
Utils.byteToHex(ir) + " " +
Utils.byteToHex(args[0]) + " ";
case 3:
return Utils.wordToHex(lastPc) + " " +
Utils.byteToHex(ir) + " " +
Utils.byteToHex(args[0]) + " " +
Utils.byteToHex(args[1]);
default:
return null;
}
}
/**
* @return A string representing the current status register state.
*/
public String getProcessorStatusString() {
return "[" + (negativeFlag ? 'N' : '.') +
(overflowFlag ? 'V' : '.') +
"-" +
(breakFlag ? 'B' : '.') +
(decimalModeFlag ? 'D' : '.') +
(irqDisableFlag ? 'I' : '.') +
(zeroFlag ? 'Z' : '.') +
(carryFlag ? 'C' : '.') +
"]";
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2012 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -31,7 +31,7 @@ public interface InstructionTable {
*
* TODO: As of version 0.6, this is still not used! All CPUs are "idealized" NMOS 6502 only.
*/
public enum CpuBehavior {
enum CpuBehavior {
/**
* The earliest NMOS 6502 includes a bug that causes the ROR instruction
* to behave like an ASL that does not affect the carry bit. This version
@@ -65,7 +65,7 @@ public interface InstructionTable {
/**
* Enumeration of Addressing Modes.
*/
public enum Mode {
enum Mode {
ACC {
public String toString() {
return "Accumulator";
@@ -156,7 +156,7 @@ public interface InstructionTable {
/**
* Instruction opcode names.
*/
public static final String[] opcodeNames = {
String[] opcodeNames = {
"BRK", "ORA", null, null, null, "ORA", "ASL", null,
"PHP", "ORA", "ASL", null, null, "ORA", "ASL", null,
"BPL", "ORA", null, null, null, "ORA", "ASL", null,
@@ -194,7 +194,7 @@ public interface InstructionTable {
/**
* Instruction addressing modes.
*/
public static final Mode[] instructionModes = {
Mode[] instructionModes = {
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x00-0x03
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x04-0x07
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x08-0x0b
@@ -265,7 +265,7 @@ public interface InstructionTable {
/**
* Size, in bytes, required for each instruction.
*/
public static final int[] instructionSizes = {
int[] instructionSizes = {
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 0, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
@@ -287,7 +287,7 @@ public interface InstructionTable {
/**
* Number of clock cycles required for each instruction
*/
public static final int[] instructionClocks = {
int[] instructionClocks = {
7, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 0, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 3, 3, 5, 0, 4, 2, 2, 0, 4, 4, 6, 0,
+108
View File
@@ -0,0 +1,108 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon;
import com.loomcom.symon.machines.MulticompMachine;
import com.loomcom.symon.machines.SimpleMachine;
import com.loomcom.symon.machines.SymonMachine;
import java.util.Locale;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class Main {
/**
* Main entry point to the simulator. Creates a simulator and shows the main
* window.
*
* @param args Program arguments
*/
public static void main(String args[]) throws Exception {
Class machineClass = SymonMachine.class;
for(int i = 0; i < args.length; ++i) {
String arg = args[i].toLowerCase(Locale.ENGLISH);
if(arg.equals("-machine") && (i+1) < args.length) {
String machine = args[i+1].trim().toLowerCase(Locale.ENGLISH);
switch (machine) {
case "symon":
machineClass = SymonMachine.class;
break;
case "multicomp":
machineClass = MulticompMachine.class;
break;
case "simple":
machineClass = SimpleMachine.class;
break;
}
}
}
while (true) {
if (machineClass == null) {
Object[] possibilities = {"Symon", "Multicomp", "Simple"};
String s = (String)JOptionPane.showInputDialog(
null,
"Please choose the machine type to be emulated:",
"Machine selection",
JOptionPane.PLAIN_MESSAGE,
null,
possibilities,
"Symon");
if (s != null && s.equals("Multicomp")) {
machineClass = MulticompMachine.class;
} else if (s != null && s.equals("Simple")) {
machineClass = SimpleMachine.class;
} else {
machineClass = SymonMachine.class;
}
}
final Simulator simulator = new Simulator(machineClass);
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi();
} catch (Exception e) {
e.printStackTrace();
}
});
Simulator.MainCommand cmd = simulator.waitForCommand();
if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) {
machineClass = null;
} else {
break;
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -34,77 +34,78 @@ import com.loomcom.symon.exceptions.*;
*/
public class MemoryRange implements Comparable<MemoryRange> {
/** The starting address of the memory range. */
public int startAddress;
/** The ending address of the memory range. */
public int endAddress;
/**
* The starting address of the memory range.
*/
public int startAddress;
/**
* The ending address of the memory range.
*/
public int endAddress;
public MemoryRange(int startAddress, int endAddress)
throws MemoryRangeException {
if (startAddress < 0 || endAddress < 0) {
throw new MemoryRangeException("Addresses cannot be less than 0.");
public MemoryRange(int startAddress, int endAddress)
throws MemoryRangeException {
if (startAddress < 0 || endAddress < 0) {
throw new MemoryRangeException("Addresses cannot be less than 0.");
}
if (startAddress >= endAddress) {
throw new MemoryRangeException("End address must be greater " +
"than start address.");
}
this.startAddress = startAddress;
this.endAddress = endAddress;
}
if (startAddress >= endAddress) {
throw new MemoryRangeException("End address must be greater " +
"than start address.");
/**
* @return the starting address.
*/
public int startAddress() {
return startAddress;
}
this.startAddress = startAddress;
this.endAddress = endAddress;
}
/**
* @returns the starting address.
*/
public int startAddress() {
return startAddress;
}
/**
* @returns the ending address.
*/
public int endAddress() {
return endAddress;
}
/**
* Checks for address inclusion in the range.
*
* @returns true if the address is included within this range,
* false otherwise.
*/
public boolean includes(int address) {
return (address <= endAddress &&
address >= startAddress);
}
/**
* Checks for overlapping memory ranges.
*
* @returns true if this range overlaps in any way with the other.
*/
public boolean overlaps(MemoryRange other) {
return (this.includes(other.startAddress()) ||
other.includes(this.startAddress()));
}
// Implementation of Comparable interface
public int compareTo(MemoryRange other) {
if (other == null) {
throw new NullPointerException("Cannot compare to null.");
/**
* @return the ending address.
*/
public int endAddress() {
return endAddress;
}
if (this == other) {
return 0;
}
Integer thisStartAddr = new Integer(this.startAddress());
Integer thatStartAddr = new Integer(other.startAddress());
return thisStartAddr.compareTo(thatStartAddr);
}
public String toString() {
StringBuffer desc = new StringBuffer("@");
desc.append(String.format("0x%04x", startAddress));
desc.append("-");
desc.append(String.format("0x%04x", endAddress));
return desc.toString();
}
/**
* Checks for address inclusion in the range.
*
* @return true if the address is included within this range,
* false otherwise.
*/
public boolean includes(int address) {
return (address <= endAddress &&
address >= startAddress);
}
/**
* Checks for overlapping memory ranges.
*
* @return true if this range overlaps in any way with the other.
*/
public boolean overlaps(MemoryRange other) {
return (this.includes(other.startAddress()) ||
other.includes(this.startAddress()));
}
// Implementation of Comparable interface
public int compareTo(MemoryRange other) {
if (other == null) {
throw new NullPointerException("Cannot compare to null.");
}
if (this == other) {
return 0;
}
Integer thisStartAddr = this.startAddress();
Integer thatStartAddr = other.startAddress();
return thisStartAddr.compareTo(thatStartAddr);
}
public String toString() {
return "@" + String.format("0x%04x", startAddress) + "-" +
String.format("0x%04x", endAddress);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -27,15 +27,15 @@ import javax.swing.*;
public interface Preferences {
public static final int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300;
int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300;
public static final boolean DEFAULT_HALT_ON_BREAK = true;
boolean DEFAULT_HALT_ON_BREAK = true;
public JDialog getDialog();
JDialog getDialog();
public int getProgramStartAddress();
int getProgramStartAddress();
public boolean getHaltOnBreak();
boolean getHaltOnBreak();
public void updateUi();
void updateUi();
}
+356 -271
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
@@ -23,24 +23,21 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.exceptions.FifoUnderrunException;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import com.loomcom.symon.exceptions.SymonException;
import com.loomcom.symon.exceptions.*;
import com.loomcom.symon.machines.Machine;
import com.loomcom.symon.ui.*;
import com.loomcom.symon.ui.Console;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Symon Simulator Interface and Control.
@@ -52,59 +49,33 @@ import java.util.logging.Logger;
*/
public class Simulator {
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
// 32K of RAM from $0000 - $7FFF
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0x8000;
// VIA at $8000-$800F
private static final int VIA_BASE = 0x8000;
// ACIA at $8800-$8803
private static final int ACIA_BASE = 0x8800;
// CRTC at $9000-$9001
private static final int CRTC_BASE = 0x9000;
private static final int VIDEO_RAM_BASE = 0x7000;
// 16KB ROM at $C000-$FFFF
private static final int ROM_BASE = 0xC000;
private static final int ROM_SIZE = 0x4000;
private final static Logger logger = LoggerFactory.getLogger(Simulator.class.getName());
// UI constants
private static final int DEFAULT_FONT_SIZE = 12;
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
private static final int CONSOLE_BORDER_WIDTH = 10;
private static final int DEFAULT_FONT_SIZE = 12;
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
private static final int CONSOLE_BORDER_WIDTH = 10;
// Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz.
private static final long[] CLOCK_PERIODS = {0, 1000, 500, 333, 250, 200, 167, 143, 125};
// Since it is very expensive to update the UI with Swing's Event Dispatch Thread, we can't afford
// to refresh the status view on every simulated clock cycle. Instead, we will only refresh the status view
// after this number of steps when running normally.
//
// Since we're simulating a 1MHz 6502 here, we have a 1 us delay between steps. Setting this to 10000
// should give us a status update every 10 ms.
// Since we're simulating a 1MHz 6502 here, we have a 1 us delay between steps. Setting this to 20000
// should give us a status update about every 100 ms.
//
// TODO: Work around the event dispatch thread with custom painting code instead of relying on Swing.
//
private static final int MAX_STEPS_BETWEEN_UPDATES = 10000;
private static final int MAX_STEPS_BETWEEN_UPDATES = 20000;
private final static Logger logger = Logger.getLogger(Simulator.class.getName());
// The simulated peripherals
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Via via;
private final Crtc crtc;
private final Memory ram;
private Memory rom;
// The simulated machine
private Machine machine;
// Number of CPU steps between CRT repaints.
// TODO: Dynamically refresh the value at runtime based on performance figures to reach ~ 30fps.
private long stepsBetweenCrtcRefreshes = 2500;
private static final long STEPS_BETWEEN_CRTC_REFRESHES = 2500;
// A counter to keep track of the number of UI updates that have been
// requested
@@ -124,61 +95,61 @@ public class Simulator {
/**
* The Trace Window shows the most recent 50,000 CPU states.
*/
private TraceLog traceLog;
private final TraceLog traceLog;
/**
* The Memory Window shows the contents of one page of memory.
*/
private MemoryWindow memoryWindow;
private final MemoryWindow memoryWindow;
private VideoWindow videoWindow;
private final VideoWindow videoWindow;
private final BreakpointsWindow breakpointsWindow;
private SimulatorMenu menuBar;
private RunLoop runLoop;
private Console console;
private RunLoop runLoop;
private Console console;
private StatusPanel statusPane;
private JButton runStopButton;
private JButton stepButton;
private JButton resetButton;
private JComboBox stepCountBox;
private JComboBox<String> stepCountBox;
private JFileChooser fileChooser;
private JFileChooser fileChooser;
private PreferencesDialog preferences;
private Breakpoints breakpoints;
private final Object commandMonitorObject = new Object();
private MainCommand command = MainCommand.NONE;
public enum MainCommand {
NONE,
SELECTMACHINE
}
/**
* The list of step counts that will appear in the "Step" drop-down.
*/
private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"};
public Simulator() throws MemoryRangeException, IOException {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false);
this.via = new Via(VIA_BASE);
this.acia = new Acia(ACIA_BASE);
this.crtc = new Crtc(CRTC_BASE, ram);
public Simulator(Class machineClass) throws Exception {
this.breakpoints = new Breakpoints(this);
bus.addCpu(cpu);
this.machine = (Machine) machineClass.getConstructors()[0].newInstance();
bus.addDevice(ram);
bus.addDevice(via);
bus.addDevice(acia);
bus.addDevice(crtc);
// Initialize final fields in the constructor.
this.traceLog = new TraceLog();
this.memoryWindow = new MemoryWindow(machine.getBus());
this.breakpointsWindow = new BreakpointsWindow(breakpoints, mainWindow);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file " + romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
if (machine.getCrtc() != null) {
videoWindow = new VideoWindow(machine.getCrtc(), 2, 2);
} else {
logger.info("Default ROM file " + romImage +
" not found, loading empty R/W memory image.");
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
videoWindow = null;
}
bus.addDevice(rom);
}
/**
@@ -186,13 +157,13 @@ public class Simulator {
*/
public void createAndShowUi() throws IOException {
mainWindow = new JFrame();
mainWindow.setTitle("Symon 6502 Simulator");
mainWindow.setTitle("6502 Simulator - " + machine.getName());
mainWindow.setResizable(false);
mainWindow.getContentPane().setLayout(new BorderLayout());
// UI components used for I/O.
this.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT);
this.statusPane = new StatusPanel();
this.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT, false);
this.statusPane = new StatusPanel(machine);
console.setBorderWidth(CONSOLE_BORDER_WIDTH);
@@ -210,25 +181,25 @@ public class Simulator {
runStopButton = new JButton("Run");
stepButton = new JButton("Step");
resetButton = new JButton("Reset");
JButton softResetButton = new JButton("Soft Reset");
JButton hardResetButton = new JButton("Hard Reset");
stepCountBox = new JComboBox(STEPS);
stepCountBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
try {
JComboBox cb = (JComboBox) actionEvent.getSource();
stepsPerClick = Integer.parseInt((String) cb.getSelectedItem());
} catch (NumberFormatException ex) {
stepsPerClick = 1;
stepCountBox.setSelectedIndex(0);
}
stepCountBox = new JComboBox<>(STEPS);
stepCountBox.addActionListener(actionEvent -> {
try {
JComboBox cb = (JComboBox) actionEvent.getSource();
stepsPerClick = Integer.parseInt((String) cb.getSelectedItem());
} catch (NumberFormatException ex) {
stepsPerClick = 1;
stepCountBox.setSelectedIndex(0);
}
});
buttonContainer.add(runStopButton);
buttonContainer.add(stepButton);
buttonContainer.add(stepCountBox);
buttonContainer.add(resetButton);
buttonContainer.add(softResetButton);
buttonContainer.add(hardResetButton);
// Left side - console
consoleContainer.add(console, BorderLayout.CENTER);
@@ -240,38 +211,27 @@ public class Simulator {
// Bottom - buttons.
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
runStopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
if (runLoop != null && runLoop.isRunning()) {
handleStop();
} else {
handleStart();
}
runStopButton.addActionListener(actionEvent -> {
if (runLoop != null && runLoop.isRunning()) {
handleStop();
} else {
handleStart();
}
});
stepButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
handleStep(stepsPerClick);
}
stepButton.addActionListener(actionEvent -> handleStep(stepsPerClick));
softResetButton.addActionListener(actionEvent -> {
// If this was a CTRL-click, do a hard reset.
handleReset(false);
});
resetButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
handleReset();
}
hardResetButton.addActionListener(actionEvent -> {
// If this was a CTRL-click, do a hard reset.
handleReset(true);
});
// Prepare the log window
traceLog = new TraceLog();
// Prepare the memory window
memoryWindow = new MemoryWindow(bus);
// Composite Video and 6545 CRTC
videoWindow = new VideoWindow(crtc, 2, 2);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// The Menu. This comes last, because it relies on other components having
// already been initialized.
@@ -282,8 +242,21 @@ public class Simulator {
mainWindow.setVisible(true);
console.requestFocus();
handleReset(false);
}
public MainCommand waitForCommand() {
synchronized (commandMonitorObject) {
try {
commandMonitorObject.wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
return command;
}
private void handleStart() {
// Shift focus to the console.
console.requestFocus();
@@ -302,7 +275,7 @@ public class Simulator {
/*
* Perform a reset.
*/
private void handleReset() {
private void handleReset(boolean isColdReset) {
if (runLoop != null && runLoop.isRunning()) {
runLoop.requestStop();
runLoop.interrupt();
@@ -310,22 +283,24 @@ public class Simulator {
}
try {
logger.log(Level.INFO, "Reset requested. Resetting CPU.");
// Reset and clear memory
cpu.reset();
logger.debug("Reset requested. Resetting CPU.");
// Reset CPU
machine.getCpu().reset();
// Clear the console.
console.reset();
// Reset the trace log.
traceLog.reset();
// Update status.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(cpu);
// If we're doing a cold reset, clear the memory.
if (isColdReset) {
Memory mem = machine.getRam();
if (mem != null) {
mem.fill(0);
}
});
}
// Update status.
updateVisibleState();
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Exception during simulator reset: " + ex.getMessage());
logger.error("Exception during simulator reset", ex);
}
}
@@ -337,16 +312,9 @@ public class Simulator {
for (int i = 0; i < numSteps; i++) {
step();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (traceLog.isVisible()) {
traceLog.refresh();
}
statusPane.updateState(cpu);
}
});
updateVisibleState();
} catch (SymonException ex) {
logger.log(Level.SEVERE, "Exception during simulator step: " + ex.getMessage());
logger.error("Exception during simulator step", ex);
ex.printStackTrace();
}
}
@@ -355,46 +323,41 @@ public class Simulator {
* Perform a single step of the simulated system.
*/
private void step() throws MemoryAccessException {
cpu.step();
machine.getCpu().step();
traceLog.append(cpu.getCpuState());
traceLog.append(machine.getCpu().getCpuState());
// Read from the ACIA and immediately update the console if there's
// output ready.
if (acia.hasTxChar()) {
if (machine.getAcia() != null && machine.getAcia().hasTxChar()) {
// This is thread-safe
console.print(Character.toString((char) acia.txRead()));
console.print(Character.toString((char) machine.getAcia().txRead()));
console.repaint();
}
// If a key has been pressed, fill the ACIA.
// TODO: Interrupt handling.
try {
if (console.hasInput()) {
acia.rxWrite((int) console.readInputChar());
if (machine.getAcia() != null && console.hasInput()) {
machine.getAcia().rxWrite((int) console.readInputChar());
}
} catch (FifoUnderrunException ex) {
logger.severe("Console type-ahead buffer underrun!");
logger.error("Console type-ahead buffer underrun!");
}
if (stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) {
videoWindow.refreshDisplay();
if (videoWindow != null && stepsSinceLastCrtcRefresh++ > STEPS_BETWEEN_CRTC_REFRESHES) {
stepsSinceLastCrtcRefresh = 0;
if (videoWindow.isVisible()) {
videoWindow.repaint();
}
}
// This is a very expensive update, and we're doing it without
// a delay, so we don't want to overwhelm the Swing event processing thread
// with requests. Limit the number of ui updates that can be performed.
if (stepsSinceLastUpdate++ > MAX_STEPS_BETWEEN_UPDATES) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(cpu);
}
});
updateVisibleState();
stepsSinceLastUpdate = 0;
}
}
/**
@@ -403,49 +366,20 @@ public class Simulator {
private void loadProgram(byte[] program, int startAddress) throws MemoryAccessException {
int addr = startAddress, i;
for (i = 0; i < program.length; i++) {
bus.write(addr++, program[i] & 0xff);
machine.getBus().write(addr++, program[i] & 0xff);
}
logger.log(Level.INFO, "Loaded " + i + " bytes at address 0x" +
Integer.toString(startAddress, 16));
logger.info("Loaded {} bytes at address 0x{}", i, Integer.toString(startAddress, 16));
// After loading, be sure to reset and
// Reset (but don't clear memory, naturally)
cpu.reset();
machine.getCpu().reset();
// Reset the stack program counter
cpu.setProgramCounter(preferences.getProgramStartAddress());
machine.getCpu().setProgramCounter(preferences.getProgramStartAddress());
// Immediately update the UI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(cpu);
}
});
}
/**
* Main entry point to the simulator. Creates a simulator and shows the main
* window.
*
* @param args
*/
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
Simulator simulator = new Simulator();
simulator.createAndShowUi();
// Reset the simulator.
simulator.handleReset();
} catch (Exception e) {
e.printStackTrace();
}
}
});
updateVisibleState();
}
/**
@@ -463,18 +397,16 @@ public class Simulator {
}
public void run() {
logger.log(Level.INFO, "Starting main run loop.");
logger.debug("Starting main run loop.");
isRunning = true;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
stepCountBox.setEnabled(false);
menuBar.simulatorDidStart();
// Toggle the state of the run button
runStopButton.setText("Stop");
}
SwingUtilities.invokeLater(() -> {
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
stepCountBox.setEnabled(false);
menuBar.simulatorDidStart();
// Toggle the state of the run button
runStopButton.setText("Stop");
});
try {
@@ -482,38 +414,39 @@ public class Simulator {
step();
} while (shouldContinue());
} catch (SymonException ex) {
logger.log(Level.SEVERE, "Exception in main simulator run thread. Exiting run.");
ex.printStackTrace();
logger.error("Exception in main simulator run thread. Exiting run.", ex);
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
statusPane.updateState(cpu);
runStopButton.setText("Run");
stepButton.setEnabled(true);
stepCountBox.setEnabled(true);
if (traceLog.isVisible()) {
traceLog.refresh();
}
menuBar.simulatorDidStop();
traceLog.simulatorDidStop();
// TODO: Update memory window, if frame is visible.
SwingUtilities.invokeLater(() -> {
statusPane.updateState();
memoryWindow.updateState();
runStopButton.setText("Run");
stepButton.setEnabled(true);
stepCountBox.setEnabled(true);
if (traceLog.isVisible()) {
traceLog.refresh();
}
menuBar.simulatorDidStop();
traceLog.simulatorDidStop();
});
isRunning = false;
}
/**
* Returns true if the run loop should proceed to the next step.
*
* @return True if the run loop should proceed to the next step.
*/
private boolean shouldContinue() {
return isRunning && !(preferences.getHaltOnBreak() && cpu.getInstruction() == 0x00);
return !breakpoints.contains(machine.getCpu().getProgramCounter()) &&
isRunning &&
!(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00);
}
}
public String disassembleOpAtAddress(int address) throws MemoryAccessException {
return machine.getCpu().disassembleOpAtAddress(address);
}
class LoadProgramAction extends AbstractAction {
public LoadProgramAction() {
super("Load Program...", null);
@@ -522,7 +455,6 @@ public class Simulator {
}
public void actionPerformed(ActionEvent actionEvent) {
// TODO: Error dialogs on failure.
try {
int retVal = fileChooser.showOpenDialog(mainWindow);
if (retVal == JFileChooser.APPROVE_OPTION) {
@@ -530,8 +462,11 @@ public class Simulator {
if (f.canRead()) {
long fileSize = f.length();
if (fileSize > MEMORY_SIZE) {
throw new IOException("Program will not fit in available memory.");
if (fileSize > machine.getMemorySize()) {
throw new IOException("File will not fit in " +
"available memory ($" +
Integer.toString(machine.getMemorySize(), 16) +
" bytes)");
} else {
byte[] program = new byte[(int) fileSize];
int i = 0;
@@ -542,21 +477,29 @@ public class Simulator {
program[i++] = dis.readByte();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
console.reset();
}
});
// Now load the program at the starting address.
loadProgram(program, preferences.getProgramStartAddress());
SwingUtilities.invokeLater(() -> {
console.reset();
breakpoints.refresh();
});
// TODO: "Don't Show Again" checkbox
JOptionPane.showMessageDialog(mainWindow,
"Loaded Successfully At " +
String.format("$%04X", preferences.getProgramStartAddress()),
"OK",
JOptionPane.PLAIN_MESSAGE);
}
}
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Unable to read program file: " + ex.getMessage());
logger.error("Unable to read program file.", ex);
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Memory access error loading program: " + ex.getMessage());
logger.error("Memory access error loading program", ex);
JOptionPane.showMessageDialog(mainWindow, ex.getMessage(), "Failure", JOptionPane.ERROR_MESSAGE);
}
}
}
@@ -569,7 +512,6 @@ public class Simulator {
}
public void actionPerformed(ActionEvent actionEvent) {
// TODO: Error dialogs on failure.
try {
int retVal = fileChooser.showOpenDialog(mainWindow);
if (retVal == JFileChooser.APPROVE_OPTION) {
@@ -577,31 +519,42 @@ public class Simulator {
if (romFile.canRead()) {
long fileSize = romFile.length();
if (fileSize != ROM_SIZE) {
throw new IOException("ROM file must be exactly " + String.valueOf(ROM_SIZE) + " bytes.");
} else {
if (rom != null) {
// Unload the existing ROM image.
bus.removeDevice(rom);
}
// Load the new ROM image
rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romFile);
bus.addDevice(rom);
// Now, reset
cpu.reset();
logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " +
String.format("0x%04X", ROM_BASE));
if (fileSize != machine.getRomSize()) {
throw new IOException("ROM file must be exactly " + String.valueOf(machine.getRomSize()) + " bytes.");
}
// Load the new ROM image
Memory rom = Memory.makeROM(machine.getRomBase(), machine.getRomBase() + machine.getRomSize() - 1, romFile);
machine.setRom(rom);
// Now, reset
machine.getCpu().reset();
updateVisibleState();
// 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);
}
}
}
@@ -618,6 +571,32 @@ public class Simulator {
}
}
class SelectMachineAction extends AbstractAction {
public SelectMachineAction() {
super("Switch emulated machine...", null);
putValue(SHORT_DESCRIPTION, "Select the type of the machine to be emulated");
putValue(MNEMONIC_KEY, KeyEvent.VK_M);
}
public void actionPerformed(ActionEvent actionEvent) {
if (runLoop != null) {
runLoop.requestStop();
}
memoryWindow.dispose();
traceLog.dispose();
if (videoWindow != null) {
videoWindow.dispose();
}
mainWindow.dispose();
command = MainCommand.SELECTMACHINE;
synchronized (commandMonitorObject) {
commandMonitorObject.notifyAll();
}
}
}
class QuitAction extends AbstractAction {
public QuitAction() {
super("Quit", null);
@@ -640,19 +619,36 @@ public class Simulator {
public SetFontAction(int size) {
super(Integer.toString(size) + " pt", null);
this.size = size;
putValue(SHORT_DESCRIPTION, "Set font to " + Integer.toString(size) + "pt.");
putValue(SHORT_DESCRIPTION, "Set font to " + size + "pt.");
}
public void actionPerformed(ActionEvent actionEvent) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
console.setFont(new Font("Monospaced", Font.PLAIN, size));
mainWindow.pack();
}
SwingUtilities.invokeLater(() -> {
console.setFont(new Font("Monospaced", Font.PLAIN, size));
mainWindow.pack();
});
}
}
class SetSpeedAction extends AbstractAction {
private int speed;
public SetSpeedAction(int speed) {
super(Integer.toString(speed) + " MHz", null);
this.speed = speed;
putValue(SHORT_DESCRIPTION, "Set simulated speed to " + speed + " MHz.");
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (speed < 1 || speed > CLOCK_PERIODS.length - 1) {
return;
}
machine.getCpu().setClockPeriodInNs(CLOCK_PERIODS[speed]);
}
}
class ToggleTraceWindowAction extends AbstractAction {
public ToggleTraceWindowAction() {
super("Trace Log", null);
@@ -705,6 +701,23 @@ public class Simulator {
}
}
class ToggleBreakpointWindowAction extends AbstractAction {
public ToggleBreakpointWindowAction() {
super("Breakpoints...", null);
putValue(SHORT_DESCRIPTION, "Show or Hide Breakpoints");
}
public void actionPerformed(ActionEvent actionEvent) {
synchronized (breakpointsWindow) {
if (breakpointsWindow.isVisible()) {
breakpointsWindow.setVisible(false);
} else {
breakpointsWindow.setVisible(true);
}
}
}
}
class SimulatorMenu extends JMenuBar {
// Menu Items
private JMenuItem loadProgramItem;
@@ -722,7 +735,9 @@ public class Simulator {
*/
public void simulatorDidStart() {
loadProgramItem.setEnabled(false);
loadRomItem.setEnabled(false);
if (loadRomItem != null) {
loadRomItem.setEnabled(false);
}
}
/**
@@ -730,7 +745,9 @@ public class Simulator {
*/
public void simulatorDidStop() {
loadProgramItem.setEnabled(true);
loadRomItem.setEnabled(true);
if (loadRomItem != null) {
loadRomItem.setEnabled(true);
}
}
private void initMenu() {
@@ -741,13 +758,19 @@ public class Simulator {
JMenu fileMenu = new JMenu("File");
loadProgramItem = new JMenuItem(new LoadProgramAction());
loadRomItem = new JMenuItem(new LoadRomAction());
JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction());
JMenuItem quitItem = new JMenuItem(new QuitAction());
fileMenu.add(loadProgramItem);
fileMenu.add(loadRomItem);
// Simple Machine does not implement a ROM, so it makes no sense to
// offer a ROM load option.
if (machine.getRom() != null) {
loadRomItem = new JMenuItem(new LoadRomAction());
fileMenu.add(loadRomItem);
}
JMenuItem prefsItem = new JMenuItem(new ShowPrefsAction());
fileMenu.add(prefsItem);
JMenuItem quitItem = new JMenuItem(new QuitAction());
fileMenu.add(quitItem);
add(fileMenu);
@@ -792,26 +815,88 @@ public class Simulator {
});
viewMenu.add(showMemoryTable);
final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction());
videoWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
showVideoWindow.setSelected(false);
}
});
viewMenu.add(showVideoWindow);
if (videoWindow != null) {
final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction());
videoWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
showVideoWindow.setSelected(false);
}
});
viewMenu.add(showVideoWindow);
}
add(viewMenu);
/*
* Simulator Menu
*/
JMenu simulatorMenu = new JMenu("Simulator");
// "Select Machine..." item.
JMenuItem selectMachineItem = new JMenuItem(new SelectMachineAction());
simulatorMenu.add(selectMachineItem);
// "Clock Speed" sub-menu
JMenu speedSubMenu = new JMenu("Clock Speed");
ButtonGroup speedGroup = new ButtonGroup();
makeSpeedMenuItem(1, speedSubMenu, speedGroup);
makeSpeedMenuItem(2, speedSubMenu, speedGroup);
makeSpeedMenuItem(4, speedSubMenu, speedGroup);
makeSpeedMenuItem(8, speedSubMenu, speedGroup);
simulatorMenu.add(speedSubMenu);
// "Breakpoints"
final JCheckBoxMenuItem showBreakpoints = new JCheckBoxMenuItem(new ToggleBreakpointWindowAction());
// Un-check the menu item if the user closes the window directly
breakpointsWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
showBreakpoints.setSelected(false);
}
});
simulatorMenu.add(showBreakpoints);
add(simulatorMenu);
}
private void makeFontSizeMenuItem(int size, JMenu fontSubMenu, ButtonGroup group) {
private void makeFontSizeMenuItem(int size, JMenu subMenu, ButtonGroup group) {
Action action = new SetFontAction(size);
JCheckBoxMenuItem item = new JCheckBoxMenuItem(action);
item.setSelected(size == DEFAULT_FONT_SIZE);
fontSubMenu.add(item);
subMenu.add(item);
group.add(item);
}
private void makeSpeedMenuItem(int speed, JMenu subMenu, ButtonGroup group) {
if (speed < 1 || speed > CLOCK_PERIODS.length - 1) {
return;
}
Action action = new SetSpeedAction(speed);
JCheckBoxMenuItem item = new JCheckBoxMenuItem(action);
item.setSelected(CLOCK_PERIODS[speed] == Cpu.DEFAULT_CLOCK_PERIOD_IN_NS);
subMenu.add(item);
group.add(item);
}
}
private void updateVisibleState() {
// Immediately update the UI.
SwingUtilities.invokeLater(() -> {
// Now update the state
statusPane.updateState();
memoryWindow.updateState();
if (traceLog.shouldUpdate()) {
traceLog.refresh();
}
});
}
}
+35 -178
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,179 +23,47 @@
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the MOS 6551 ACIA, with limited
* functionality. Interrupts are not supported.
* <p/>
* Unlike a 16550 UART, the 6551 ACIA has only one-byte transmit and
* receive buffers. It is the programmer's responsibility to check the
* status (full or empty) for transmit and receive buffers before
* writing / reading.
* Abstract base class for ACIAS such as the 6551 and 6580
*/
public class Acia extends Device {
public static final int ACIA_SIZE = 4;
static final int DATA_REG = 0;
static final int STAT_REG = 1;
static final int CMND_REG = 2;
static final int CTRL_REG = 3;
public abstract class Acia extends Device {
private String name;
/**
* Register addresses
*/
private int baseAddress;
int baseAddress;
/**
* Registers. These are ignored in the current implementation.
*/
private int commandRegister;
private int controlRegister;
private boolean receiveIrqEnabled = false;
private boolean transmitIrqEnabled = false;
private boolean overrun = false;
private long lastTxWrite = 0;
private long lastRxRead = 0;
private int baudRate = 0;
private long baudRateDelay = 0;
/**
boolean receiveIrqEnabled = false;
boolean transmitIrqEnabled = false;
boolean overrun = false;
long lastTxWrite = 0;
long lastRxRead = 0;
int baudRate = 0;
long baudRateDelay = 0;
/**
* Read/Write buffers
*/
private int rxChar = 0;
private int txChar = 0;
int rxChar = 0;
int txChar = 0;
private boolean rxFull = false;
private boolean txEmpty = true;
public Acia(int address) throws MemoryRangeException {
super(address, address + ACIA_SIZE - 1, "ACIA");
boolean rxFull = false;
boolean txEmpty = true;
public Acia(int address, int size, String name) throws MemoryRangeException {
super(address, address + size - 1, name);
this.name = name;
this.baseAddress = address;
}
@Override
public int read(int address) throws MemoryAccessException {
switch (address) {
case DATA_REG:
return rxRead();
case STAT_REG:
return statusReg();
case CMND_REG:
return commandRegister;
case CTRL_REG:
return controlRegister;
default:
throw new MemoryAccessException("No register.");
}
}
@Override
public void write(int address, int data) throws MemoryAccessException {
switch (address) {
case 0:
txWrite(data);
break;
case 1:
reset();
break;
case 2:
setCommandRegister(data);
break;
case 3:
setControlRegister(data);
break;
default:
throw new MemoryAccessException("No register.");
}
}
private void setCommandRegister(int data) {
commandRegister = data;
// Bit 1 controls receiver IRQ behavior
receiveIrqEnabled = (commandRegister & 0x02) == 0;
// Bits 2 & 3 controls transmit IRQ behavior
transmitIrqEnabled = (commandRegister & 0x08) == 0 && (commandRegister & 0x04) != 0;
}
/**
* Set the control register and associated state.
*
* @param data
*/
private void setControlRegister(int data) {
controlRegister = data;
// If the value of the data is 0, this is a request to reset,
// otherwise it's a control update.
if (data == 0) {
reset();
} else {
// Mask the lower three bits to get the baud rate.
int baudSelector = data & 0x0f;
switch (baudSelector) {
case 0:
baudRate = 0;
break;
case 1:
baudRate = 50;
break;
case 2:
baudRate = 75;
break;
case 3:
baudRate = 110; // Real rate is actually 109.92
break;
case 4:
baudRate = 135; // Real rate is actually 134.58
break;
case 5:
baudRate = 150;
break;
case 6:
baudRate = 300;
break;
case 7:
baudRate = 600;
break;
case 8:
baudRate = 1200;
break;
case 9:
baudRate = 1800;
break;
case 10:
baudRate = 2400;
break;
case 11:
baudRate = 3600;
break;
case 12:
baudRate = 4800;
break;
case 13:
baudRate = 7200;
break;
case 14:
baudRate = 9600;
break;
case 15:
baudRate = 19200;
break;
}
// Recalculate the baud rate delay.
baudRateDelay = calculateBaudRateDelay();
}
}
/*
* Calculate the delay in nanoseconds between successive read/write operations, based on the
@@ -214,6 +82,7 @@ public class Acia extends Device {
/**
* @return The simulated baud rate in bps.
*/
@SuppressWarnings("unused")
public int getBaudRate() {
return baudRate;
}
@@ -225,35 +94,31 @@ public class Acia extends Device {
*/
public void setBaudRate(int rate) {
this.baudRate = rate;
this.baudRateDelay = calculateBaudRateDelay();
}
/**
* @return The contents of the status register.
*/
public int statusReg() {
// TODO: Overrun, Parity Error, Framing Error, DTR, DSR, and Interrupt flags.
int stat = 0;
if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) {
stat |= 0x08;
}
if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) {
stat |= 0x10;
}
return stat;
}
public abstract int statusReg();
@Override
public String toString() {
return "ACIA@" + String.format("%04X", baseAddress);
return name + "@" + String.format("%04X", baseAddress);
}
public synchronized int rxRead() {
lastRxRead = System.nanoTime();
overrun = false;
rxFull = false;
return rxChar;
}
public synchronized void rxWrite(int data) {
if(rxFull) {
overrun = true;
}
rxFull = true;
if (receiveIrqEnabled) {
@@ -289,17 +154,9 @@ public class Acia extends Device {
/**
* @return true if there is character data in the RX register.
*/
@SuppressWarnings("unused")
public boolean hasRxChar() {
return rxFull;
}
private synchronized void reset() {
txChar = 0;
txEmpty = true;
rxChar = 0;
rxFull = false;
receiveIrqEnabled = false;
transmitIrqEnabled = false;
}
}
@@ -0,0 +1,206 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the MOS 6551 ACIA, with limited
* functionality. Interrupts are not supported.
* <p/>
* Unlike a 16550 UART, the 6551 ACIA has only one-byte transmit and
* receive buffers. It is the programmer's responsibility to check the
* status (full or empty) for transmit and receive buffers before
* writing / reading.
*/
public class Acia6551 extends Acia {
public static final int ACIA_SIZE = 4;
static final int DATA_REG = 0;
static final int STAT_REG = 1;
static final int CMND_REG = 2;
static final int CTRL_REG = 3;
/**
* Registers. These are ignored in the current implementation.
*/
private int commandRegister;
private int controlRegister;
public Acia6551(int address) throws MemoryRangeException {
super(address, ACIA_SIZE, "ACIA");
}
@Override
public int read(int address) throws MemoryAccessException {
switch (address) {
case DATA_REG:
return rxRead();
case STAT_REG:
return statusReg();
case CMND_REG:
return commandRegister;
case CTRL_REG:
return controlRegister;
default:
throw new MemoryAccessException("No register.");
}
}
@Override
public void write(int address, int data) throws MemoryAccessException {
switch (address) {
case 0:
txWrite(data);
break;
case 1:
reset();
break;
case 2:
setCommandRegister(data);
break;
case 3:
setControlRegister(data);
break;
default:
throw new MemoryAccessException("No register.");
}
}
private void setCommandRegister(int data) {
commandRegister = data;
// Bit 1 controls receiver IRQ behavior
receiveIrqEnabled = (commandRegister & 0x02) == 0;
// Bits 2 & 3 controls transmit IRQ behavior
transmitIrqEnabled = (commandRegister & 0x08) == 0 && (commandRegister & 0x04) != 0;
}
/**
* Set the control register and associated state.
*
* @param data Data to write into the control register
*/
private void setControlRegister(int data) {
controlRegister = data;
int rate = 0;
// If the value of the data is 0, this is a request to reset,
// otherwise it's a control update.
if (data == 0) {
reset();
} else {
// Mask the lower three bits to get the baud rate.
int baudSelector = data & 0x0f;
switch (baudSelector) {
case 0:
rate = 0;
break;
case 1:
rate = 50;
break;
case 2:
rate = 75;
break;
case 3:
rate = 110; // Real rate is actually 109.92
break;
case 4:
rate = 135; // Real rate is actually 134.58
break;
case 5:
rate = 150;
break;
case 6:
rate = 300;
break;
case 7:
rate = 600;
break;
case 8:
rate = 1200;
break;
case 9:
rate = 1800;
break;
case 10:
rate = 2400;
break;
case 11:
rate = 3600;
break;
case 12:
rate = 4800;
break;
case 13:
rate = 7200;
break;
case 14:
rate = 9600;
break;
case 15:
rate = 19200;
break;
}
setBaudRate(rate);
}
}
/**
* @return The contents of the status register.
*/
@Override
public int statusReg() {
// TODO: Parity Error, Framing Error, DTR, DSR, and Interrupt flags.
int stat = 0;
if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) {
stat |= 0x08;
}
if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) {
stat |= 0x10;
}
if (overrun) {
stat |= 0x04;
}
return stat;
}
private synchronized void reset() {
txChar = 0;
txEmpty = true;
rxChar = 0;
rxFull = false;
receiveIrqEnabled = false;
transmitIrqEnabled = false;
}
}
@@ -0,0 +1,123 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the Motorola 6850 ACIA, with limited
* functionality. Interrupts are not supported.
* <p/>
* Unlike a 16550 UART, the 6850 ACIA has only one-byte transmit and
* receive buffers. It is the programmer's responsibility to check the
* status (full or empty) for transmit and receive buffers before
* writing / reading.
*/
public class Acia6850 extends Acia {
public static final int ACIA_SIZE = 2;
static final int STAT_REG = 0; // read-only
static final int CTRL_REG = 0; // write-only
static final int RX_REG = 1; // read-only
static final int TX_REG = 1; // write-only
public Acia6850(int address) throws MemoryRangeException {
super(address, ACIA_SIZE, "ACIA6850");
setBaudRate(2400);
}
@Override
public int read(int address) throws MemoryAccessException {
switch (address) {
case RX_REG:
return rxRead();
case STAT_REG:
return statusReg();
default:
throw new MemoryAccessException("No register.");
}
}
@Override
public void write(int address, int data) throws MemoryAccessException {
switch (address) {
case TX_REG:
txWrite(data);
break;
case CTRL_REG:
setCommandRegister(data);
break;
default:
throw new MemoryAccessException("No register.");
}
}
private void setCommandRegister(int data) {
// Bits 0 & 1 control the master reset
if((data & 0x01) != 0 && (data & 0x02) != 0) {
reset();
}
// Bit 7 controls receiver IRQ behavior
receiveIrqEnabled = (data & 0x80) != 0;
// Bits 5 & 6 controls transmit IRQ behavior
transmitIrqEnabled = (data & 0x20) != 0 && (data & 0x40) == 0;
}
/**
* @return The contents of the status register.
*/
@Override
public int statusReg() {
// TODO: Parity Error, Framing Error, DTR, DSR, and Interrupt flags.
int stat = 0;
if (rxFull && System.nanoTime() >= (lastRxRead + baudRateDelay)) {
stat |= 0x01;
}
if (txEmpty && System.nanoTime() >= (lastTxWrite + baudRateDelay)) {
stat |= 0x02;
}
if (overrun) {
stat |= 0x20;
}
return stat;
}
private synchronized void reset() {
overrun = false;
rxFull = false;
txEmpty = true;
}
}
@@ -1,20 +1,37 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.IOException;
import java.util.logging.Logger;
/**
* Simulation of a 6545 CRTC and virtual CRT output.
*/
public class Crtc extends Device {
private static final Logger logger = Logger.getLogger(Crtc.class.getName());
// Memory locations in the CRTC address space
public static final int REGISTER_SELECT = 0;
public static final int REGISTER_RW = 1;
@@ -65,6 +82,11 @@ public class Crtc extends Device {
private int currentRegister = 0;
// Status bits
private boolean rowColumnAddressing = false;
private boolean displayEnableSkew = false;
private boolean cursorSkew = false;
private Memory memory;
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
@@ -118,8 +140,9 @@ public class Crtc extends Device {
return null;
}
public int[] getDmaAccess() {
return memory.getDmaAccess();
public int getCharAtAddress(int address) throws MemoryAccessException {
// TODO: Row/Column addressing
return memory.read(address);
}
public int getHorizontalDisplayed() {
@@ -162,6 +185,18 @@ public class Crtc extends Device {
return pageSize;
}
public boolean getRowColumnAddressing() {
return rowColumnAddressing;
}
public boolean getDisplayEnableSkew() {
return displayEnableSkew;
}
public boolean getCursorSkew() {
return cursorSkew;
}
private void setCurrentRegister(int registerNumber) {
this.currentRegister = registerNumber;
}
@@ -180,7 +215,9 @@ public class Crtc extends Device {
pageSize = horizontalDisplayed * verticalDisplayed;
break;
case MODE_CONTROL:
// TODO: Implement multiple addressing modes and cursor skew.
rowColumnAddressing = (data & 0x04) != 0;
displayEnableSkew = (data & 0x10) != 0;
cursorSkew = (data & 0x20) != 0;
break;
case SCAN_LINE:
scanLinesPerRow = data;
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -62,16 +62,11 @@ public abstract class Device implements Comparable<Device> {
*/
private Set<DeviceChangeListener> deviceChangeListeners;
public Device(int startAddress, int endAddress, String name)
throws MemoryRangeException {
public Device(int startAddress, int endAddress, String name) throws MemoryRangeException {
this.memoryRange = new MemoryRange(startAddress, endAddress);
this.size = endAddress - startAddress + 1;
this.name = name;
this.deviceChangeListeners = new HashSet<DeviceChangeListener>();
}
public Device(int startAddress, int endAddress) throws MemoryRangeException {
this(startAddress, endAddress, null);
this.deviceChangeListeners = new HashSet<>();
}
/* Methods required to be implemented by inheriting classes. */
@@ -97,6 +92,7 @@ public abstract class Device implements Comparable<Device> {
return memoryRange.endAddress();
}
@SuppressWarnings("unused")
public int startAddress() {
return memoryRange.startAddress();
}
@@ -105,6 +101,7 @@ public abstract class Device implements Comparable<Device> {
return name;
}
@SuppressWarnings("unused")
public void setName(String name) {
this.name = name;
}
@@ -118,9 +115,7 @@ public abstract class Device implements Comparable<Device> {
}
public void notifyListeners() {
for (DeviceChangeListener l : deviceChangeListeners) {
l.deviceStateChanged();
}
deviceChangeListeners.forEach(DeviceChangeListener::deviceStateChanged);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -24,5 +24,5 @@
package com.loomcom.symon.devices;
public interface DeviceChangeListener {
public void deviceStateChanged();
void deviceStateChanged();
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -28,8 +28,6 @@ import java.util.*;
import com.loomcom.symon.exceptions.*;
import javax.swing.*;
public class Memory extends Device {
private boolean readOnly;
@@ -57,8 +55,7 @@ public class Memory extends Device {
}
public static Memory makeRAM(int startAddress, int endAddress) throws MemoryRangeException {
Memory memory = new Memory(startAddress, endAddress, false);
return memory;
return new Memory(startAddress, endAddress, false);
}
public void write(int address, int data) throws MemoryAccessException {
@@ -108,8 +105,4 @@ public class Memory extends Device {
public String toString() {
return "Memory: " + getMemoryRange().toString();
}
public int[] getDmaAccess() {
return mem;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -23,45 +23,19 @@
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
public class Via extends Device {
public static final int VIA_SIZE = 16;
public abstract class Pia extends Device {
private static final int ORB = 0;
private static final int ORA = 1;
private static final int DDRB = 2;
private static final int DDRA = 3;
private static final int T1C_L = 4;
private static final int T1C_H = 5;
private static final int T1L_L = 6;
private static final int T1L_H = 7;
private static final int T2C_L = 8;
private static final int T2C_H = 9;
private static final int SR = 10;
private static final int ACR = 11;
private static final int PCR = 12;
private static final int IFR = 13;
private static final int IER = 14;
private static final int ORA_H = 15;
private final String name;
public Via(int address) throws MemoryRangeException {
super(address, address + VIA_SIZE - 1, "VIA");
}
@Override
public void write(int address, int data) throws MemoryAccessException {
}
@Override
public int read(int address) throws MemoryAccessException {
return 0;
public Pia(int startAddress, int endAddress, String name) throws MemoryRangeException {
super(startAddress, endAddress, name);
this.name = name;
}
@Override
public String toString() {
return null;
return name;
}
}
@@ -0,0 +1,211 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import jdk.nashorn.internal.ir.annotations.Ignore;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Emulation for the SD-card controller of the MULTICOMP system.
* Neiter comlete nor correct.
*/
public class SdController extends Device {
private enum Status {
IDLE,
READ,
WRITE
}
public static final int CONTROLLER_SIZE = 8;
private final int SECTOR_SIZE = 512;
private final static Logger logger = Logger.getLogger(SdController.class.getName());
private File sdImageFile;
private int lba0, lba1, lba2;
private int position;
private Status status = Status.IDLE;
private final byte[] readBuffer = new byte[SECTOR_SIZE];
private final byte[] writeBuffer = new byte[SECTOR_SIZE];
private int readPosition = 0;
private int writePosition = 0;
public SdController(int address) throws MemoryRangeException {
super(address, address + CONTROLLER_SIZE - 1, "SDCONTROLLER");
sdImageFile = new File("sd.img");
if (!sdImageFile.exists()) {
sdImageFile = null;
logger.log(Level.INFO, "Could not find SD card image 'sd.img'");
}
}
@Override
public void write(int address, int data) throws MemoryAccessException {
switch (address) {
case 0:
writeData(data);
return;
case 1:
writeCommand(data);
return;
case 2:
this.lba0 = data;
return;
case 3:
this.lba1 = data;
return;
case 4:
this.lba2 = data;
}
}
@Override
public int read(int address) throws MemoryAccessException {
switch (address) {
case 0:
return readData();
case 1:
return readStatus();
default:
return 0;
}
}
private void computePosition() {
this.position = lba0 + (lba1 << 8) + (lba2 << 16);
// each sector is 512 bytes, so multiply accordingly
this.position <<= 9;
}
private void prepareRead() {
this.status = Status.READ;
this.readPosition = 0;
computePosition();
if (sdImageFile != null) {
try {
FileInputStream fis = new FileInputStream(sdImageFile);
fis.skip(this.position);
int read = fis.read(readBuffer);
if (read < SECTOR_SIZE) {
logger.log(Level.WARNING, "not enough data to fill read buffer from SD image file");
}
fis.close();
} catch (IOException ex) {
logger.log(Level.WARNING, "could not fill read buffer from SD image file", ex);
}
}
}
private void prepareWrite() {
this.status = Status.WRITE;
this.writePosition = 0;
computePosition();
}
private int readData() {
if (status != Status.READ) {
return 0;
}
int data = readBuffer[readPosition++];
if (readPosition >= SECTOR_SIZE) {
this.status = Status.IDLE;
}
return data;
}
private void writeData(int data) {
if (status != Status.WRITE) {
return;
}
writeBuffer[writePosition++] = (byte) data;
if (writePosition >= SECTOR_SIZE) {
if (sdImageFile != null) {
try {
RandomAccessFile raf = new RandomAccessFile(sdImageFile, "rw");
raf.skipBytes(this.position);
raf.write(writeBuffer, 0, writeBuffer.length);
raf.close();
} catch (IOException ex) {
logger.log(Level.WARNING, "could not write data back to SD image file!", ex);
}
}
this.status = Status.IDLE;
}
}
private int readStatus() {
switch (this.status) {
case IDLE:
return 128;
case READ:
return 224;
case WRITE:
return 160;
default:
return 0;
}
}
private void writeCommand(int data) {
switch (data) {
case 0:
prepareRead();
return;
case 1:
prepareWrite();
return;
default:
this.status = Status.IDLE;
}
}
@Override
public String toString() {
return getName() + "@" + String.format("%04X", this.getMemoryRange().startAddress);
}
}
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* Very basic implementation of a MOS 6522 VIA.
*
* TODO: Implement timers as threads.
*/
public class Via6522 extends Pia {
public static final int VIA_SIZE = 16;
enum Register {
ORB, ORA, DDRB, DDRA, T1C_L, T1C_H, T1L_L, T1L_H,
T2C_L, T2C_H, SR, ACR, PCR, IFR, IER, ORA_H
}
public Via6522(int address) throws MemoryRangeException {
super(address, address + VIA_SIZE - 1, "MOS 6522 VIA");
}
@Override
public void write(int address, int data) throws MemoryAccessException {
Register[] registers = Register.values();
if (address >= registers.length) {
throw new MemoryAccessException("Unknown register: " + address);
}
Register r = registers[address];
switch (r) {
case ORA:
case ORB:
case DDRA:
case DDRB:
case T1C_L:
case T1C_H:
case T1L_L:
case T1L_H:
case T2C_L:
case T2C_H:
case SR:
case ACR:
case PCR:
case IFR:
case IER:
case ORA_H:
default:
}
}
@Override
public int read(int address) throws MemoryAccessException {
Register[] registers = Register.values();
if (address >= registers.length) {
throw new MemoryAccessException("Unknown register: " + address);
}
Register r = registers[address];
switch (r) {
case ORA:
case ORB:
case DDRA:
case DDRB:
case T1C_L:
case T1C_H:
case T1L_L:
case T1L_H:
case T2C_L:
case T2C_H:
case SR:
case ACR:
case PCR:
case IFR:
case IER:
case ORA_H:
default:
}
return 0;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -31,7 +31,4 @@ public class SymonException extends Exception {
public SymonException(String msg) {
super(msg);
}
public SymonException() {
super();
}
}
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.exceptions.MemoryRangeException;
public interface Machine {
Bus getBus();
Cpu getCpu();
Memory getRam();
Acia getAcia();
Pia getPia();
Crtc getCrtc();
Memory getRom();
void setRom(Memory rom) throws MemoryRangeException;
int getRomBase();
int getRomSize();
int getMemorySize();
String getName();
}
@@ -0,0 +1,161 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6850;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.devices.SdController;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.File;
import java.util.logging.Logger;
public class MulticompMachine implements Machine {
private final static Logger logger = Logger.getLogger(MulticompMachine.class.getName());
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
// 56K of RAM from $0000 - $DFFF
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0xE000;
// ACIA at $FFD0-$FFD1
private static final int ACIA_BASE = 0xFFD0;
// SD controller at $FFD8-$FFDF
private static final int SD_BASE = 0xFFD8;
// 8KB ROM at $E000-$FFFF
private static final int ROM_BASE = 0xE000;
private static final int ROM_SIZE = 0x2000;
// The simulated peripherals
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Memory ram;
private Memory rom;
public MulticompMachine() throws Exception {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false);
this.acia = new Acia6850(ACIA_BASE);
this.acia.setBaudRate(0);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(acia, 1);
bus.addDevice(new SdController(SD_BASE), 1);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file " + romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
} else {
logger.info("Default ROM file " + romImage +
" not found, loading empty R/W memory image.");
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
bus.addDevice(rom);
}
@Override
public Bus getBus() {
return bus;
}
@Override
public Cpu getCpu() {
return cpu;
}
@Override
public Memory getRam() {
return ram;
}
@Override
public Acia getAcia() {
return acia;
}
@Override
public Pia getPia() {
return null;
}
@Override
public Crtc getCrtc() {
return null;
}
@Override
public Memory getRom() {
return rom;
}
public void setRom(Memory rom) throws MemoryRangeException {
if(this.rom != null) {
bus.removeDevice(this.rom);
}
this.rom = rom;
bus.addDevice(this.rom);
}
@Override
public int getRomBase() {
return ROM_BASE;
}
@Override
public int getRomSize() {
return ROM_SIZE;
}
@Override
public int getMemorySize() {
return MEMORY_SIZE;
}
@Override
public String getName() {
return "Multicomp";
}
}
@@ -0,0 +1,117 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* A SimpleMachine is the simplest 6502 implementation possible - it
* consists solely of RAM and a CPU. This machine is primarily useful
* for running 6502 functional tests or debugging by hand.
*/
public class SimpleMachine implements Machine {
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
private final Bus bus;
private final Memory ram;
private final Cpu cpu;
public SimpleMachine() throws MemoryRangeException {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.ram = new Memory(BUS_BOTTOM, BUS_TOP, false);
this.cpu = new Cpu();
bus.addCpu(cpu);
bus.addDevice(ram);
}
@Override
public Bus getBus() {
return bus;
}
@Override
public Cpu getCpu() {
return cpu;
}
@Override
public Memory getRam() {
return ram;
}
@Override
public Acia getAcia() {
return null;
}
@Override
public Pia getPia() {
return null;
}
@Override
public Crtc getCrtc() {
return null;
}
@Override
public Memory getRom() {
return null;
}
@Override
public void setRom(Memory rom) throws MemoryRangeException {
// No-op
}
@Override
public int getRomBase() {
return 0;
}
@Override
public int getRomSize() {
return 0;
}
@Override
public int getMemorySize() {
return BUS_TOP + 1;
}
@Override
public String getName() {
return "Simple";
}
}
@@ -0,0 +1,165 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.*;
import com.loomcom.symon.exceptions.MemoryRangeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public class SymonMachine implements Machine {
private final static Logger logger = LoggerFactory.getLogger(SymonMachine.class.getName());
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
// 32K of RAM from $0000 - $7FFF
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0x8000;
// PIA at $8000-$800F
private static final int PIA_BASE = 0x8000;
// ACIA at $8800-$8803
private static final int ACIA_BASE = 0x8800;
// CRTC at $9000-$9001
private static final int CRTC_BASE = 0x9000;
// 16KB ROM at $C000-$FFFF
private static final int ROM_BASE = 0xC000;
private static final int ROM_SIZE = 0x4000;
// The simulated peripherals
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Pia pia;
private final Crtc crtc;
private final Memory ram;
private Memory rom;
public SymonMachine() throws Exception {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false);
this.pia = new Via6522(PIA_BASE);
this.acia = new Acia6551(ACIA_BASE);
this.crtc = new Crtc(CRTC_BASE, ram);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(pia);
bus.addDevice(acia);
bus.addDevice(crtc);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file {}", romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
} else {
logger.info("Default ROM file {} not found, loading empty R/W memory image.", romImage);
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
bus.addDevice(rom);
}
@Override
public Bus getBus() {
return bus;
}
@Override
public Cpu getCpu() {
return cpu;
}
@Override
public Memory getRam() {
return ram;
}
@Override
public Acia getAcia() {
return acia;
}
@Override
public Pia getPia() {
return pia;
}
@Override
public Crtc getCrtc() {
return crtc;
}
@Override
public Memory getRom() {
return rom;
}
public void setRom(Memory rom) throws MemoryRangeException {
if(this.rom != null) {
bus.removeDevice(this.rom);
}
this.rom = rom;
bus.addDevice(this.rom);
}
@Override
public int getRomBase() {
return ROM_BASE;
}
@Override
public int getRomSize() {
return ROM_SIZE;
}
@Override
public int getMemorySize() {
return MEMORY_SIZE;
}
@Override
public String getName() {
return "Symon";
}
}
@@ -0,0 +1,138 @@
/*
* 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 java.awt.*;
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));
JButton addButton = new JButton("Add");
JButton removeButton = new JButton("Del");
removeButton.setEnabled(false);
JTextField addTextField = new JTextField(4);
JTable breakpointsTable = new JTable(breakpoints);
breakpointsTable.setShowGrid(true);
breakpointsTable.setGridColor(Color.LIGHT_GRAY);
breakpointsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
breakpointsTable.getSelectionModel().addListSelectionListener(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 = e -> {
int value = -1;
String newBreakpoint = addTextField.getText();
if (newBreakpoint == null || newBreakpoint.isEmpty()) {
return;
}
try {
value = (Integer.parseInt(addTextField.getText(), 16) & 0xffff);
} catch (NumberFormatException ex) {
logger.warn("Can't parse page number {}", newBreakpoint);
return;
}
if (value < 0) {
return;
}
breakpoints.addBreakpoint(value);
logger.debug("Added breakpoint ${}", Utils.wordToHex(value));
addTextField.setText(EMPTY_STRING);
};
addButton.addActionListener(addBreakpointListener);
addTextField.addActionListener(addBreakpointListener);
removeButton.addActionListener(e -> breakpoints.removeBreakpointAtIndex(breakpointsTable.getSelectedRow()));
controlPanel.add(addTextField);
controlPanel.add(addButton);
controlPanel.add(removeButton);
setLayout(new BorderLayout());
getContentPane().add(breakpointsPanel, BorderLayout.CENTER);
getContentPane().add(controlPanel, BorderLayout.SOUTH);
setMinimumSize(FRAME_SIZE);
setMaximumSize(FRAME_SIZE);
setPreferredSize(FRAME_SIZE);
setLocationRelativeTo(mainWindow);
setResizable(false);
pack();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -44,21 +44,20 @@ import java.awt.event.MouseListener;
public class Console extends JTerminal implements KeyListener, MouseListener {
private static final int DEFAULT_COLUMNS = 80;
private static final int DEFAULT_ROWS = 24;
private static final int DEFAULT_BORDER_WIDTH = 10;
// If true, swap CR and LF characters.
private static final boolean SWAP_CR_AND_LF = true;
// If true, send CRLF (0x0d 0x0a) whenever CR is typed
private static final boolean SEND_CR_LF_FOR_CR = false;
// If true, send CRLF (0x0d 0x0a) whenever CR is typed
private boolean sendCrForLf = false;
private FifoRingBuffer<Character> typeAheadBuffer;
public Console(int columns, int rows, Font font) {
public Console(int columns, int rows, Font font, boolean sendCrForLf) {
super(new Vt100TerminalModel(columns, rows), font);
// A small type-ahead buffer, as might be found in any real
// VT100-style serial terminal.
this.typeAheadBuffer = new FifoRingBuffer<Character>(128);
this.typeAheadBuffer = new FifoRingBuffer<>(128);
this.sendCrForLf = sendCrForLf;
setBorderWidth(DEFAULT_BORDER_WIDTH);
addKeyListener(this);
addMouseListener(this);
@@ -83,7 +82,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
/**
* Returns true if a key has been pressed since the last time input was read.
*
* @return
* @return true if the type-ahead buffer has data
*/
public boolean hasInput() {
return !typeAheadBuffer.isEmpty();
@@ -105,7 +104,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
}
}
if (SEND_CR_LF_FOR_CR && keyTyped == 0x0d) {
if (sendCrForLf && keyTyped == 0x0d) {
typeAheadBuffer.push((char) 0x0d);
typeAheadBuffer.push((char) 0x0a);
} else {
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -25,7 +25,9 @@ package com.loomcom.symon.ui;
import com.loomcom.symon.Bus;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.HexUtil;
import com.loomcom.symon.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
@@ -37,8 +39,7 @@ import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.EventObject;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This Frame displays the contents of a page of memory. The page number to be displayed
@@ -46,6 +47,8 @@ import java.util.logging.Logger;
*/
public class MemoryWindow extends JFrame implements ActionListener {
private static final Logger logger = LoggerFactory.getLogger(MemoryWindow.class);
private MemoryTableModel memoryTableModel;
private JTable memoryTable;
private JTextField pageNumberTextField;
@@ -104,7 +107,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
previousPageButton.setEnabled(pageNumber > 0x00);
nextPageButton.setEnabled(pageNumber < 0xff);
pageNumberTextField.setText(HexUtil.byteToHex(pageNumber));
pageNumberTextField.setText(Utils.byteToHex(pageNumber));
}
/**
@@ -190,14 +193,14 @@ public class MemoryWindow extends JFrame implements ActionListener {
if (currentPage > 0x00) {
setPageNumber(currentPage - 1);
updateControls();
memoryTable.updateUI();
updateState();
}
} else if (e.getSource() == nextPageButton) {
int currentPage = getPageNumber();
if (currentPage < 0xff) {
setPageNumber(currentPage + 1);
updateControls();
memoryTable.updateUI();
updateState();
}
} else if (e.getSource() == pageNumberTextField) {
String pageNumberInput = pageNumberTextField.getText();
@@ -205,17 +208,24 @@ public class MemoryWindow extends JFrame implements ActionListener {
// Try to parse a hex value out of the pageNumber.
int newPageNumber = Integer.parseInt(pageNumberInput, 16);
setPageNumber(newPageNumber & 0xff);
memoryTable.updateUI();
updateState();
} catch (NumberFormatException ex) {
// An invalid number was entered. Log the error, but otherwise
// take no action.
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Can't parse page number " +
pageNumberInput);
logger.warn("Can't parse page number {}", pageNumberInput);
}
updateControls();
}
}
/**
* Refresh the view of memory
*/
public void updateState() {
memoryTable.updateUI();
}
/**
* A JTable that will automatically select all text in a cell
* being edited.
@@ -318,13 +328,13 @@ public class MemoryWindow extends JFrame implements ActionListener {
public Object getValueAt(int row, int column) {
try {
if (column == 0) {
return HexUtil.wordToHex(fullAddress(row, 1));
return Utils.wordToHex(fullAddress(row, 1));
} else if (column < 9) {
// Display hex value of the data
return HexUtil.byteToHex(bus.read(fullAddress(row, column)));
return Utils.byteToHex(bus.read(fullAddress(row, column)));
} else {
// Display the ASCII equivalent (if printable)
return HexUtil.byteToAscii(bus.read(fullAddress(row, column - 8)));
return Utils.byteToAscii(bus.read(fullAddress(row, column - 8)));
}
} catch (MemoryAccessException ex) {
return "??";
@@ -339,12 +349,8 @@ public class MemoryWindow extends JFrame implements ActionListener {
int fullAddress = fullAddress(row, column);
int newValue = Integer.parseInt(hexValue, 16) & 0xff;
bus.write(fullAddress, newValue);
} catch (MemoryAccessException ex) {
;
} catch (NumberFormatException ex) {
;
} catch (ClassCastException ex) {
;
} catch (MemoryAccessException | NumberFormatException | ClassCastException ex) {
// Intentionally swallow exception
}
fireTableCellUpdated(row, column);
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -97,23 +97,19 @@ public class PreferencesDialog extends Observable implements Preferences {
JButton applyButton = new JButton("Apply");
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
updateUi();
dialog.setVisible(false);
}
cancelButton.addActionListener(actionEvent -> {
updateUi();
dialog.setVisible(false);
});
applyButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
haltOnBreak = haltOnBreakCheckBox.isSelected();
programLoadAddress = hexToInt(programLoadAddressField.getText());
updateUi();
// TODO: Actually check to see if values have changed, don't assume.
setChanged();
PreferencesDialog.this.notifyObservers();
dialog.setVisible(false);
}
applyButton.addActionListener(actionEvent -> {
haltOnBreak = haltOnBreakCheckBox.isSelected();
programLoadAddress = hexToInt(programLoadAddressField.getText());
updateUi();
// TODO: Actually check to see if values have changed, don't assume.
setChanged();
PreferencesDialog.this.notifyObservers();
dialog.setVisible(false);
});
buttonsContainer.add(applyButton);
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -24,6 +24,8 @@
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;
@@ -50,7 +52,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 +67,7 @@ public class StatusPanel extends JPanel {
private JTextField xField;
private JTextField yField;
private JLabel opcodeLabel;
private JLabel pcLabel;
private JLabel spLabel;
private JLabel aLabel;
private JLabel xLabel;
private JLabel yLabel;
private Machine machine;
private static final int EMPTY_BORDER = 10;
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
@@ -79,8 +75,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 +96,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 +141,81 @@ public class StatusPanel extends JPanel {
statusFlagsPanel.add(carryFlagLabel);
// Create and add register and address labels
statusFlagsLabel = makeLabel("Flags");
opcodeLabel = makeLabel("IR");
pcLabel = makeLabel("PC");
spLabel = makeLabel("SP");
aLabel = makeLabel("A");
xLabel = makeLabel("X");
yLabel = makeLabel("Y");
JLabel statusFlagsLabel = makeLabel("Flags");
JLabel opcodeLabel = makeLabel("Next IR");
JLabel pcLabel = makeLabel("PC");
JLabel spLabel = makeLabel("SP");
JLabel aLabel = makeLabel("A");
JLabel xLabel = makeLabel("X");
JLabel yLabel = makeLabel("Y");
statusFlagsLabel.setToolTipText("6502 Processor Status Flags");
opcodeLabel.setToolTipText("Instruction Register");
pcLabel.setToolTipText("Program Counter");
spLabel.setToolTipText("Stack Pointer");
opcodeField = makeTextField(LARGE_TEXT_FIELD_SIZE);
pcField = makeTextField(LARGE_TEXT_FIELD_SIZE);
spField = makeTextField(SMALL_TEXT_FIELD_SIZE);
aField = makeTextField(SMALL_TEXT_FIELD_SIZE);
xField = makeTextField(SMALL_TEXT_FIELD_SIZE);
yField = makeTextField(SMALL_TEXT_FIELD_SIZE);
opcodeField = makeTextField(LARGE_TEXT_FIELD_SIZE, false);
pcField = makeTextField(LARGE_TEXT_FIELD_SIZE, true);
spField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
aField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
xField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
yField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
// Make fields editable
pcField.addActionListener(e -> {
try {
int newVal = getHexVal(pcField) & 0xffff;
machine.getCpu().setProgramCounter(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
});
spField.addActionListener(e -> {
try {
int newVal = getHexVal(spField) & 0xff;
machine.getCpu().setStackPointer(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
});
aField.addActionListener(e -> {
try {
int newVal = getHexVal(aField) & 0xff;
machine.getCpu().setAccumulator(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
});
xField.addActionListener(e -> {
try {
int newVal = getHexVal(xField) & 0xff;
machine.getCpu().setXRegister(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
});
yField.addActionListener(e -> {
try {
int newVal = getHexVal(yField) & 0xff;
machine.getCpu().setYRegister(newVal);
} catch (Exception ex) {
// Swallow exception
}
updateState();
});
constraints.anchor = GridBagConstraints.LINE_START;
constraints.gridwidth = 2;
@@ -220,14 +273,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 +290,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,10 +367,10 @@ public class StatusPanel extends JPanel {
return label;
}
private JTextField makeTextField(Dimension size) {
private JTextField makeTextField(Dimension size, boolean editable) {
JTextField textField = new JTextField("");
textField.setAlignmentX(LEFT_ALIGNMENT);
textField.setEditable(false);
textField.setEditable(editable);
textField.setMinimumSize(size);
textField.setMaximumSize(size);
textField.setPreferredSize(size);
@@ -324,4 +378,8 @@ public class StatusPanel extends JPanel {
return textField;
}
private int getHexVal(JTextField source) throws NumberFormatException {
String val = source.getText().replaceAll("[^0-9a-fA-F]", "");
return Integer.parseInt(val, 16);
}
}
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -23,7 +24,7 @@
package com.loomcom.symon.ui;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.CpuState;
import com.loomcom.symon.util.FifoRingBuffer;
import javax.swing.*;
@@ -35,15 +36,15 @@ import java.awt.*;
*/
public class TraceLog extends JFrame {
private FifoRingBuffer<Cpu.CpuState> traceLog;
private JTextArea traceLogTextArea;
private final FifoRingBuffer<CpuState> traceLog;
private final JTextArea traceLogTextArea;
private static final Dimension MIN_SIZE = new Dimension(320, 200);
private static final Dimension PREFERRED_SIZE = new Dimension(640, 480);
private static final int MAX_LOG_LENGTH = 50000;
public TraceLog() {
traceLog = new FifoRingBuffer<Cpu.CpuState>(MAX_LOG_LENGTH);
traceLog = new FifoRingBuffer<>(MAX_LOG_LENGTH);
setMinimumSize(MIN_SIZE);
setPreferredSize(PREFERRED_SIZE);
setResizable(true);
@@ -67,11 +68,15 @@ public class TraceLog extends JFrame {
* call.
*/
public void refresh() {
synchronized (this) {
StringBuilder logString = new StringBuilder();
for (Cpu.CpuState state : traceLog) {
StringBuilder logString = new StringBuilder();
synchronized(traceLog) {
for (CpuState state : traceLog) {
logString.append(state.toTraceEvent());
}
}
synchronized(traceLogTextArea) {
traceLogTextArea.setText(logString.toString());
}
}
@@ -80,8 +85,10 @@ public class TraceLog extends JFrame {
* Reset the log area.
*/
public void reset() {
synchronized (this) {
synchronized(traceLog) {
traceLog.reset();
}
synchronized(traceLogTextArea) {
traceLogTextArea.setText("");
traceLogTextArea.setEnabled(true);
}
@@ -92,9 +99,9 @@ public class TraceLog extends JFrame {
*
* @param state The CPU State to append.
*/
public void append(Cpu.CpuState state) {
synchronized(this) {
traceLog.push(new Cpu.CpuState(state));
public void append(CpuState state) {
synchronized(traceLog) {
traceLog.push(new CpuState(state));
}
}
@@ -106,4 +113,8 @@ public class TraceLog extends JFrame {
traceLogTextArea.setEnabled(true);
}
public boolean shouldUpdate() {
return isVisible() && traceLogTextArea.isEnabled();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -25,6 +25,7 @@ package com.loomcom.symon.ui;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.DeviceChangeListener;
import com.loomcom.symon.exceptions.MemoryAccessException;
import javax.swing.*;
import java.awt.*;
@@ -36,8 +37,11 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.lang.System.*;
/**
* VideoWindow represents a graphics framebuffer backed by a 6545 CRTC.
* Each time the window's VideoPanel is repainted, the video memory is
@@ -65,7 +69,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private BufferedImage image;
private int[] charRom;
private int[] videoRam;
private int horizontalDisplayed;
private int verticalDisplayed;
@@ -85,17 +88,21 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private class VideoPanel extends JPanel {
@Override
public void paintComponent(Graphics g) {
for (int i = 0; i < crtc.getPageSize(); i++) {
int address = crtc.getStartAddress() + i;
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
try {
for (int i = 0; i < crtc.getPageSize(); i++) {
int address = crtc.getStartAddress() + i;
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
}
Graphics2D g2d = (Graphics2D) g;
if (shouldScale) {
g2d.scale(scaleX, scaleY);
}
g2d.drawImage(image, 0, 0, null);
} catch (MemoryAccessException ex) {
logger.log(Level.SEVERE, "Memory Access Exception, can't paint video window! " + ex.getMessage());
}
Graphics2D g2d = (Graphics2D)g;
if (shouldScale) {
g2d.scale(scaleX, scaleY);
}
g2d.drawImage(image, 0, 0, null);
}
@Override
@@ -115,12 +122,10 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
*/
private class CursorBlinker implements Runnable {
public void run() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (cursorBlinkRate > 0) {
hideCursor = !hideCursor;
repaint();
}
SwingUtilities.invokeLater(() -> {
if (cursorBlinkRate > 0) {
hideCursor = !hideCursor;
repaint();
}
});
}
@@ -131,8 +136,7 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
this.scheduler = Executors.newSingleThreadScheduledExecutor();
this.crtc = crtc;
this.charRom = loadCharRom("/pet.rom");
this.videoRam = crtc.getDmaAccess();
this.charRom = loadCharRom("/ascii.rom");
this.scaleX = scaleX;
this.scaleY = scaleY;
this.shouldScale = (scaleX > 1 || scaleY > 1);
@@ -158,13 +162,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
}
public void refreshDisplay() {
// TODO: Verify whether this is necessary. Does `repaint()' do anything if the window is not visible?
if (isVisible()) {
repaint();
}
}
/**
* Called by the CRTC on state change.
*/
@@ -239,15 +236,13 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
* @param address The address of the character being requested.
* @return An array of integers representing the pixel data.
*/
private int[] getGlyph(int address) {
int chr = videoRam[address];
private int[] getGlyph(int address) throws MemoryAccessException {
int chr = crtc.getCharAtAddress(address);
int romOffset = (chr & 0xff) * (CHAR_HEIGHT * CHAR_WIDTH);
int[] glyph = new int[CHAR_WIDTH * scanLinesPerRow];
// Populate the character
for (int i = 0; i < (CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow)); i++) {
glyph[i] = charRom[romOffset + i];
}
arraycopy(charRom, romOffset, glyph, 0, CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow));
// Overlay the cursor
if (!hideCursor && crtc.isCursorEnabled() && crtc.getCursorPosition() == address) {
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -38,7 +38,7 @@ public class FifoRingBuffer<E> implements Iterable<E> {
private int maxLength;
public FifoRingBuffer(int maxLength) {
this.fifoBuffer = new LinkedList<E>();
this.fifoBuffer = new LinkedList<>();
this.maxLength = maxLength;
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008-2013 Seth J. Morabito <sethm@loomcom.com>
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -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;
}
}
File renamed without changes.

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 191 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 191 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 196 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 196 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 183 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 183 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

File renamed without changes.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.
Binary file not shown.
+29 -9
View File
@@ -1,6 +1,7 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6551;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -12,7 +13,7 @@ public class AciaTest {
public void shouldTriggerInterruptOnRxFullIfRxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Enable RX IRQ
@@ -27,7 +28,7 @@ public class AciaTest {
public void shouldNotTriggerInterruptOnRxFullIfRxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
@@ -42,7 +43,7 @@ public class AciaTest {
public void shouldTriggerInterruptOnTxEmptyIfTxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Enable TX IRQ, Disable RX IRQ
@@ -63,7 +64,7 @@ public class AciaTest {
public void shouldNotTriggerInterruptOnTxEmptyIfTxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
@@ -80,14 +81,14 @@ public class AciaTest {
@Test
public void newAciaShouldHaveTxEmptyStatus() throws Exception {
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
assertEquals(0x10, acia.read(0x0001));
}
@Test
public void aciaShouldHaveTxEmptyStatusOffIfTxHasData() throws Exception {
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
acia.txWrite('a');
assertEquals(0x00, acia.read(0x0001));
@@ -95,7 +96,7 @@ public class AciaTest {
@Test
public void aciaShouldHaveRxFullStatusOffIfRxHasData() throws Exception {
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
acia.rxWrite('a');
assertEquals(0x18, acia.read(0x0001));
@@ -104,18 +105,37 @@ public class AciaTest {
@Test
public void aciaShouldHaveTxEmptyAndRxFullStatusOffIfRxAndTxHaveData()
throws Exception {
Acia acia = new Acia(0x000);
Acia acia = new Acia6551(0x000);
acia.rxWrite('a');
acia.txWrite('b');
assertEquals(0x08, acia.read(0x0001));
}
@Test
public void aciaShouldOverrunAndReadShouldReset()
throws Exception {
Acia acia = new Acia6551(0x0000);
// overrun ACIA
acia.rxWrite('a');
acia.rxWrite('b');
assertEquals(0x04, acia.read(0x0001) & 0x04);
// read should reset
acia.rxRead();
assertEquals(0x00, acia.read(0x0001) & 0x04);
}
@Test
public void readingBuffersShouldResetStatus()
throws Exception {
Acia acia = new Acia(0x0000);
Acia acia = new Acia6551(0x0000);
acia.rxWrite('a');
acia.txWrite('b');
@@ -0,0 +1,163 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6850;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
public class AciaTest6850 {
private final static int CMD_STAT_REG = 0;
private final static int DATA_REG = 1;
private Acia newAcia() throws Exception {
Acia acia = new Acia6850(0x0000);
// by default rate is limited, have it unlimited for testing
acia.setBaudRate(0);
return acia;
}
@Test
public void shouldTriggerInterruptOnRxFullIfRxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Disable TX IRQ, Enable RX IRQ
acia.write(CMD_STAT_REG, 0x80);
acia.rxWrite('a');
verify(mockBus, atLeastOnce()).assertIrq();
}
@Test
public void shouldNotTriggerInterruptOnRxFullIfRxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(CMD_STAT_REG, 0x00);
acia.rxWrite('a');
verify(mockBus, never()).assertIrq();
}
@Test
public void shouldTriggerInterruptOnTxEmptyIfTxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Enable TX IRQ, Disable RX IRQ
acia.write(CMD_STAT_REG, 0x20);
// Write data
acia.write(1, 'a');
verify(mockBus, never()).assertIrq();
// Transmission should cause IRQ
acia.txRead();
verify(mockBus, atLeastOnce()).assertIrq();
}
@Test
public void shouldNotTriggerInterruptOnTxEmptyIfTxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = newAcia();
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(CMD_STAT_REG, 0x02);
// Write data
acia.write(DATA_REG, 'a');
// Transmission should cause IRQ
acia.txRead();
verify(mockBus, never()).assertIrq();
}
@Test
public void newAciaShouldHaveTxEmptyStatus() throws Exception {
Acia acia = newAcia();
assertEquals(0x02, acia.read(CMD_STAT_REG) & 0x02);
}
@Test
public void aciaShouldHaveTxEmptyStatusOffIfTxHasData() throws Exception {
Acia acia = newAcia();
acia.txWrite('a');
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x02);
}
@Test
public void aciaShouldHaveRxFullStatusOnIfRxHasData() throws Exception {
Acia acia = newAcia();
acia.rxWrite('a');
assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x01);
}
@Test
public void aciaShouldHaveTxEmptyAndRxFullStatusOffIfRxAndTxHaveData()
throws Exception {
Acia acia = newAcia();
acia.rxWrite('a');
acia.txWrite('b');
assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x03);
}
@Test
public void aciaShouldOverrunAndReadShouldReset()
throws Exception {
Acia acia = newAcia();
// overrun ACIA
acia.rxWrite('a');
acia.rxWrite('b');
assertEquals(0x20, acia.read(CMD_STAT_REG) & 0x20);
// read should reset
acia.rxRead();
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x20);
}
@Test
public void readingBuffersShouldResetStatus()
throws Exception {
Acia acia = newAcia();
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x01);
acia.rxWrite('a');
assertEquals(0x01, acia.read(CMD_STAT_REG) & 0x01);
acia.rxRead();
assertEquals(0x00, acia.read(CMD_STAT_REG) & 0x01);
}
}
@@ -59,22 +59,6 @@ public class BusTest extends TestCase {
assertEquals(2, b.getDevices().size());
}
public void testOverlappingDevicesShouldFail() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x0100, true);
Device rom = new Memory(0x00ff, 0x0200, false);
Bus b = new Bus(0x0000, 0xffff);
b.addDevice(memory);
try {
b.addDevice(rom);
fail("Should have thrown a MemoryRangeException.");
} catch (MemoryRangeException ex) {
// expected
}
}
public void testIsCompleteWithFirstDeviceNotStartingAtStartAddress() throws MemoryRangeException {
Device memory = new Memory(0x00ff, 0xff00, true);
@@ -14,7 +14,7 @@ public class CpuAbsoluteModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuAbsoluteXModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuAbsoluteYModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuAccumulatorModeTest extends TestCase {
public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuImmediateModeTest extends TestCase {
public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -17,7 +17,7 @@ public class CpuImpliedModeTest {
public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -16,7 +16,7 @@ public class CpuIndexedIndirectModeTest {
public void runBeforeEveryTest() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -16,7 +16,7 @@ public class CpuIndirectIndexedModeTest {
public void runBeforeEveryTest() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuIndirectModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -13,7 +13,7 @@ public class CpuIndirectXModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuRelativeModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
+26 -6
View File
@@ -1,5 +1,6 @@
package com.loomcom.symon;
import com.loomcom.symon.util.Utils;
import junit.framework.*;
import com.loomcom.symon.devices.*;
@@ -25,7 +26,7 @@ public class CpuTest extends TestCase {
public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -537,11 +538,11 @@ public class CpuTest extends TestCase {
public void testAddress() {
assertEquals(0xf1ea, cpu.address(0xea, 0xf1));
assertEquals(0x00ea, cpu.address(0xea, 0x00));
assertEquals(0xf100, cpu.address(0x00, 0xf1));
assertEquals(0x1234, cpu.address(0x34, 0x12));
assertEquals(0xffff, cpu.address(0xff, 0xff));
assertEquals(0xf1ea, Utils.address(0xea, 0xf1));
assertEquals(0x00ea, Utils.address(0xea, 0x00));
assertEquals(0xf100, Utils.address(0x00, 0xf1));
assertEquals(0x1234, Utils.address(0x34, 0x12));
assertEquals(0xffff, Utils.address(0xff, 0xff));
}
public void testZpxAddress() {
@@ -583,4 +584,23 @@ public class CpuTest extends TestCase {
cpu.setYRegister(0x95);
assertEquals(0x15, cpu.zpyAddress(0x80));
}
// Test for GitHub symon issue #9, "LSR can yield wrong result"
public void testRightShiftMasksBitsCorrectly() throws Exception {
// Illegal value, the accumulator should only care about the low 8 bytes.
// I'm a little uncomfortable with this test because really, setAccumulator should
// defensively mask the value, but does not. Is this relying on a bug to test another bug?
cpu.setAccumulator(0xff8);
// Sanity check, in case I ever change my mind on setAccumulator's behavior
assertEquals(0xff8, cpu.getAccumulator());
bus.loadProgram(0x4a, // LSR
0x4a); // LSR
cpu.step();
assertEquals(0x7C, cpu.getAccumulator());
cpu.step();
assertEquals(0x3E, cpu.getAccumulator());
}
}
@@ -14,7 +14,7 @@ public class CpuZeroPageModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuZeroPageXModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
@@ -14,7 +14,7 @@ public class CpuZeroPageYModeTest extends TestCase {
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
+57 -5
View File
@@ -19,19 +19,17 @@ import static org.mockito.Mockito.*;
public class CrtcTest {
Crtc crtc;
Memory memory;
@Mock
DeviceChangeListener changeListener;
@Mock
Memory memory;
@Before
public void createDevices() throws Exception {
memory = new Memory(0, 0x7fff);
crtc = new Crtc(0x9000, memory);
crtc.registerListener(changeListener);
when(memory.startAddress()).thenReturn(0);
when(memory.endAddress()).thenReturn(0x7fff);
}
@Test
@@ -340,4 +338,58 @@ public class CrtcTest {
crtc.write(1, 0x80); // Can't position cursor
}
@Test
public void shouldSetRowColumnAddressing() throws Exception {
assertEquals(false, crtc.getRowColumnAddressing());
crtc.write(0, 8); // Select mode control register
crtc.write(1, 0x04);
assertEquals(true, crtc.getRowColumnAddressing());
}
@Test
public void shouldSetDisplayEnableSkew() throws Exception {
assertEquals(false, crtc.getDisplayEnableSkew());
crtc.write(0, 8); // Select mode control register
crtc.write(1, 0x10);
assertEquals(true, crtc.getDisplayEnableSkew());
}
@Test
public void shouldSetCursorSkew() throws Exception {
assertEquals(false, crtc.getCursorSkew());
crtc.write(0, 8); // Select mode control register
crtc.write(1, 0x20);
assertEquals(true, crtc.getCursorSkew());
}
@Test
public void shouldDoStraightBinaryAddressing() throws Exception {
crtc.write(0, 8);
crtc.write(1, 0); // Select straight binary addressing
// Fill the memory with a repeating pattern
int videoMemoryBase = 0x7000;
int j = 0;
for (int i = 0; i < 2048; i++) {
memory.write(videoMemoryBase + i, j);
if (j == 255) {
j = 0;
} else {
j++;
}
}
// Now verify that straight-binary addressing of the CRTC works
j = 0;
for (int i = 0; i < 2048; i++) {
assertEquals(j, crtc.getCharAtAddress(videoMemoryBase + i));
if (j == 255) {
j = 0;
} else {
j++;
}
}
}
}
@@ -1,42 +0,0 @@
package com.loomcom.symon;
import com.loomcom.symon.util.HexUtil;
import junit.framework.TestCase;
public class HexUtilTest extends TestCase {
public void testByteToHex() {
assertEquals("FE", HexUtil.byteToHex(0xfe));
assertEquals("00", HexUtil.byteToHex(0));
assertEquals("0A", HexUtil.byteToHex(10));
}
public void testByteToHexIgnoresSign() {
assertEquals("FF", HexUtil.byteToHex(-1));
}
public void testByteToHexMasksLowByte() {
assertEquals("FE", HexUtil.byteToHex(0xfffe));
assertEquals("00", HexUtil.byteToHex(0xff00));
}
public void testWordToHex() {
assertEquals("0000", HexUtil.wordToHex(0));
assertEquals("FFFF", HexUtil.wordToHex(65535));
assertEquals("FFFE", HexUtil.wordToHex(65534));
}
public void testWordToHexIgnoresSign() {
assertEquals("FFFF", HexUtil.wordToHex(-1));
}
public void testWordToHexMasksTwoLowBytes() {
assertEquals("FFFE", HexUtil.wordToHex(0xfffffe));
assertEquals("FF00", HexUtil.wordToHex(0xffff00));
}
public void testAllBytesAreCorrect() {
for (int i = 0; i <= 0xff; i++) {
assertEquals(String.format("%02X", i), HexUtil.byteToHex(i));
}
}
}
@@ -0,0 +1,42 @@
package com.loomcom.symon;
import com.loomcom.symon.util.Utils;
import junit.framework.TestCase;
public class UtilsTest extends TestCase {
public void testByteToHex() {
assertEquals("FE", Utils.byteToHex(0xfe));
assertEquals("00", Utils.byteToHex(0));
assertEquals("0A", Utils.byteToHex(10));
}
public void testByteToHexIgnoresSign() {
assertEquals("FF", Utils.byteToHex(-1));
}
public void testByteToHexMasksLowByte() {
assertEquals("FE", Utils.byteToHex(0xfffe));
assertEquals("00", Utils.byteToHex(0xff00));
}
public void testWordToHex() {
assertEquals("0000", Utils.wordToHex(0));
assertEquals("FFFF", Utils.wordToHex(65535));
assertEquals("FFFE", Utils.wordToHex(65534));
}
public void testWordToHexIgnoresSign() {
assertEquals("FFFF", Utils.wordToHex(-1));
}
public void testWordToHexMasksTwoLowBytes() {
assertEquals("FFFE", Utils.wordToHex(0xfffffe));
assertEquals("FF00", Utils.wordToHex(0xffff00));
}
public void testAllBytesAreCorrect() {
for (int i = 0; i <= 0xff; i++) {
assertEquals(String.format("%02X", i), Utils.byteToHex(i));
}
}
}