Author SHA1 Message Date
Seth Morabito 3a59dedc29 Update README and pom.xml for release 1.5.0 2025-03-30 15:28:32 -07:00
Seth Morabito 7268766cd6 Implement Step-Over functionality
This change adds a new "Step Over" button, which is enabled
when a "JSR" (Jump to Subroutine) instruction is encountered
while stepping through execution one instruction at a time.

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

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

Implements feature request #18.
2025-03-30 15:18:00 -07:00
Seth Morabito 2284535b7f Fix #17: Correct disassembly 2025-03-15 15:46:50 -07:00
Seth Morabito 963526621d Maven build update
- Bump to version 1.5.0-dev
- Update dependencies
- Fix broken Symon homepage URL
- Fix maven xsd URL
2025-02-13 16:36:50 -08:00
Seth Morabito c317544e7b Address IntelliJ warnings
This commit addresses a large number of IntelliJ warnings
that have been left hanging around for far too long.
2025-02-13 16:05:42 -08:00
Seth Morabito ecd4bbbd9a README formatting fix 2023-11-18 10:50:07 -08:00
Seth Morabito a537328852 Minor README update 2023-11-18 10:46:55 -08:00
Seth Morabito 4975fca506 Missed a README update for v1.4.0 2023-11-11 08:20:49 -08:00
47 changed files with 534 additions and 519 deletions

No files matched your search

+29 -17
View File
@@ -1,9 +1,9 @@
SYMON - A 6502 System Simulator
===============================
**Version:** 1.3.2
**Version:** 1.5.0
**Last Updated:** 08 March, 2021
**Last Updated:** 30 March, 2025
See the file COPYING for license.
@@ -25,20 +25,20 @@ Klaus Dormann's 6502 Functional Test Suite as of version 0.8.2
(See [this thread on the 6502.org Forums](http://forum.6502.org/viewtopic.php?f=2&t=2241)
for more information about this functional test suite).
Symon is under constant, active development. Feedback and patches
are always welcome.
Symon is under active maintenance. Feedback and patches are always
welcome.
## 2.0 Requirements
- Java 1.8 or higher
- Maven 2.0.x or higher (for building from source)
- Java 10 or higher
- Maven 3.0.x or higher (for building from source)
- JUnit 4 or higher (for testing)
## 3.0 Features
Symon can simulate multiple 6502 based architectures. At present, four
machines are implemented: Symon (the default), MULTICOMP, BenEater, and a "Simple"
machine useful for debugging.
machines are implemented: Symon (the default), MULTICOMP, BenEater, and
a "Simple" machine useful for debugging.
### 3.1 Memory Maps
@@ -270,6 +270,17 @@ running.
## 5.0 Revision History
- **1.5.0:** 30 March 2025 - Fixed a bug that caused some 6502
instructions to be disassembled improperly in the trace log
(GitHub issue #17).
Added a new "Step Over" button that allows stepping over the
current subroutine. This button only becomes active when the
next instruction is a "JSR" while stepping through execution
one instruction at a time. It will continue execution until
the subroutine returns through a corresponding "RTS" instruction.
(GitHub feature request #18).
- **1.4.0:** 11 November 2023 - Adds a new machine, the Ben Eater
machine. Correct handling of 6551 interrupts, and several 6551
bug fixes. Fixes power-on status of 6502 status register. Fixes a
@@ -355,17 +366,12 @@ running.
- Feedback (in the form of dialogs, status bar, etc).
- Better debugging tools from the UI, including breakpoints
and disassembly.
- UI needs a ton more polish.
- More extensive testing.
- Clean up JavaDoc.
- Implement CMOS 65C02 instructions and NMOS / CMOS mode flag.
- Allow displaying ACIA status and dumping ACIA buffers, for
debugging.
@@ -375,13 +381,19 @@ running.
- Symbolic debugging.
- Better / correct disassembly and tracing of 65C02 and 65C816
instructions.
## 8.0 Copyright and Acknowledgements
**Copyright (c) 2014 Seth J. Morabito <web@loomcom.com>**
**Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>**
Portions Copyright (c) 2014 Maik Merten <maikmerten@googlemail.com>
Portions Copyright (c) 2022 Tim Allen <thristian@gmail.com>
Portions Copyright (c) 2023 Chelsea Wilkinson <mail@chelseawilkinson.me>
- Portions Copyright (c) 2014 Maik Merten
<maikmerten@googlemail.com>
- Portions Copyright (c) 2022 Tim Allen
<thristian@gmail.com>
- Portions Copyright (c) 2023 Chelsea Wilkinson
<mail@chelseawilkinson.me>
Additional components used in this project are copyright their respective owners.
+5 -5
View File
@@ -1,12 +1,12 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>1.4.0</version>
<version>1.5.0</version>
<name>symon</name>
<url>http://www.loomcom.com/symon</url>
<url>https://git.loomcom.com/seth/symon</url>
<properties>
<project.build.sourceEncoding>
UTF-8
@@ -19,7 +19,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
<version>1.5.16</version>
</dependency>
<dependency>
<groupId>junit</groupId>
@@ -30,7 +30,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.8.0</version>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -1,3 +1,26 @@
/*
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon;
import com.loomcom.symon.exceptions.MemoryAccessException;
@@ -9,8 +32,8 @@ import java.util.TreeSet;
public class Breakpoints extends AbstractTableModel {
private TreeSet<Integer> breakpoints;
private Simulator simulator;
private final TreeSet<Integer> breakpoints;
private final Simulator simulator;
public Breakpoints(Simulator simulator) {
this.breakpoints = new TreeSet<>();
@@ -26,11 +49,6 @@ public class Breakpoints extends AbstractTableModel {
fireTableDataChanged();
}
public void removeBreakpoint(int address) {
this.breakpoints.remove(address);
fireTableDataChanged();
}
public void removeBreakpointAtIndex(int index) {
if (index < 0) {
return;
+11 -18
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -42,19 +42,19 @@ import java.util.TreeSet;
public class Bus {
// The default address at which to load programs
public static int DEFAULT_LOAD_ADDRESS = 0x0200;
public static final int DEFAULT_LOAD_ADDRESS = 0x0200;
// By default, our bus starts at 0, and goes up to 64K
private int startAddress = 0x0000;
private int endAddress = 0xffff;
private final int startAddress;
private final int endAddress;
// The CPU
private Cpu cpu;
// Ordered sets of IO devices, associated with their priority
private Map<Integer, SortedSet<Device>> deviceMap;
private final Map<Integer, SortedSet<Device>> deviceMap;
// an array for quick lookup of adresses, brute-force style
// an array for quick lookup of addresses, brute-force style
private Device[] deviceAddressArray;
@@ -94,8 +94,8 @@ public class Bus {
* Add a device to the bus.
*
* @param device Device to add
* @param priority Bus prioirity.
* @throws MemoryRangeException
* @param priority Bus priority.
* @throws MemoryRangeException Invalid device address range.
*/
public void addDevice(Device device, int priority) throws MemoryRangeException {
@@ -109,12 +109,7 @@ public class Bus {
throw new MemoryRangeException("end address of device " + device.getName() + " does not fall within the address range of the bus");
}
SortedSet<Device> deviceSet = deviceMap.get(priority);
if (deviceSet == null) {
deviceSet = new TreeSet<>();
deviceMap.put(priority, deviceSet);
}
SortedSet<Device> deviceSet = deviceMap.computeIfAbsent(priority, k -> new TreeSet<>());
device.setBus(this);
deviceSet.add(device);
@@ -125,7 +120,7 @@ public class Bus {
* Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others.
*
* @param device Device to add
* @throws MemoryRangeException
* @throws MemoryRangeException Invalid device memory range.
*/
public void addDevice(Device device) throws MemoryRangeException {
addDevice(device, 0);
@@ -224,9 +219,7 @@ public class Bus {
for (int priority : priorities) {
SortedSet<Device> deviceSet = deviceMap.get(priority);
for (Device device : deviceSet) {
devices.add(device);
}
devices.addAll(deviceSet);
}
return devices;
+13 -3
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -158,6 +158,8 @@ public class Cpu implements InstructionTable {
* Performs an individual instruction cycle.
*/
public void step() throws MemoryAccessException {
int hi, lo; // Address calculation
opBeginTime = System.nanoTime();
// Store the address from which the IR was read, for debugging
@@ -317,8 +319,8 @@ public class Cpu implements InstructionTable {
break;
case 0x40: // RTI - Return from Interrupt - Implied
setProcessorStatus(stackPop());
int lo = stackPop();
int hi = stackPop();
lo = stackPop();
hi = stackPop();
setProgramCounter(Utils.address(lo, hi));
break;
case 0x48: // PHA - Push Accumulator - Implied
@@ -1697,6 +1699,10 @@ public class Cpu implements InstructionTable {
return state.ir;
}
public int getNextIr() {
return state.nextIr;
}
/**
* @value The value of the Process Status Register bits to be set.
*/
@@ -1923,6 +1929,7 @@ public class Cpu implements InstructionTable {
break;
case AIX:
sb.append(" ($").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(",X)");
break;
case ABX:
sb.append(" $").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(",X");
break;
@@ -1935,6 +1942,9 @@ public class Cpu implements InstructionTable {
case IND:
sb.append(" ($").append(Utils.wordToHex(Utils.address(args[0], args[1]))).append(")");
break;
case ZPI:
sb.append(" ($").append(Utils.byteToHex(args[0])).append(")");
break;
case XIN:
sb.append(" ($").append(Utils.byteToHex(args[0])).append(",X)");
break;
@@ -1,3 +1,26 @@
/*
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon;
import com.loomcom.symon.util.Utils;
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -25,26 +25,24 @@ package com.loomcom.symon;
public interface InstructionTable {
int BRK = 0x00;
int JSR = 0x20;
int RTS = 0x60;
/**
* Enumeration of valid CPU behaviors. These determine what behavior and instruction
* set will be simulated, depending on desired version of 6502.
*
* TODO: As of version 0.6, this is still not used! All CPUs are "idealized" NMOS 6502 only.
*/
enum CpuBehavior {
/**
* The earliest NMOS 6502 includes a bug that causes the ROR instruction
* to behave like an ASL that does not affect the carry bit. This version
* is very rare in the wild.
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_WITH_ROR_BUG,
/**
* All NMOS 6502's have a bug with the indirect JMP instruction. If the
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_6502,
@@ -68,11 +66,13 @@ public interface InstructionTable {
return "Accumulator";
}
},
AIX {
public String toString() {
return "Absolute, X-Indexed Indirect";
}
},
ABS {
public String toString() {
return "Absolute";
@@ -168,41 +168,44 @@ public interface InstructionTable {
/**
* Instruction opcode names. This lists all opcodes for
* NMOS 6502, CMOS 65C02, and CMOS 65C816
* NMOS 6502, CMOS 65C02, and CMOS 65C816.
* <p>
* TODO (2025-03-14): This table is clearly wrong for 65C816.
* TODO (2025-03-14): Rockwell/WDC 65C02 names conflict with 65C816 names.
*/
String[] opcodeNames = {
"BRK", "ORA", "NOP", "NOP", "TSB", "ORA", "ASL", "RMB0", // 0x00-0x07
"PHP", "ORA", "ASL", "NOP", "TSB", "ORA", "ASL", "BBR0", // 0x08-0x0f
"BPL", "ORA", "ORA", "NOP", "TRB", "ORA", "ASL", "RMB1", // 0x10-0x17
"CLC", "ORA", "INC", "NOP", "TRB", "ORA", "ASL", "BBR1", // 0x18-0x1f
"JSR", "AND", "NOP", "NOP", "BIT", "AND", "ROL", "RMB2", // 0x20-0x27
"PLP", "AND", "ROL", "NOP", "BIT", "AND", "ROL", "BBR2", // 0x28-0x2f
"BMI", "AND", "AND", "NOP", "BIT", "AND", "ROL", "RMB3", // 0x30-0x37
"SEC", "AND", "DEC", "NOP", "BIT", "AND", "ROL", "BBR3", // 0x38-0x3f
"RTI", "EOR", "NOP", "NOP", "NOP", "EOR", "LSR", "RMB4", // 0x40-0x47
"PHA", "EOR", "LSR", "NOP", "JMP", "EOR", "LSR", "BBR4", // 0x48-0x4f
"BVC", "EOR", "EOR", "NOP", "NOP", "EOR", "LSR", "RMB5", // 0x50-0x57
"CLI", "EOR", "PHY", "NOP", "NOP", "EOR", "LSR", "BBR5", // 0x58-0x5f
"RTS", "ADC", "NOP", "NOP", "STZ", "ADC", "ROR", "RMB6", // 0x60-0x67
"PLA", "ADC", "ROR", "NOP", "JMP", "ADC", "ROR", "BBR6", // 0x68-0x6f
"BVS", "ADC", "ADC", "NOP", "STZ", "ADC", "ROR", "RMB7", // 0x70-0x77
"SEI", "ADC", "PLY", "NOP", "JMP", "ADC", "ROR", "BBR7", // 0x78-0x7f
"BRA", "STA", "NOP", "NOP", "STY", "STA", "STX", "SMB0", // 0x80-0x87
"DEY", "BIT", "TXA", "NOP", "STY", "STA", "STX", "BBS0", // 0x88-0x8f
"BCC", "STA", "STA", "NOP", "STY", "STA", "STX", "SMB1", // 0x90-0x97
"TYA", "STA", "TXS", "NOP", "STZ", "STA", "STZ", "BBS1", // 0x98-0x9f
"LDY", "LDA", "LDX", "NOP", "LDY", "LDA", "LDX", "SMB2", // 0xa0-0xa7
"TAY", "LDA", "TAX", "NOP", "LDY", "LDA", "LDX", "BBS2", // 0xa8-0xaf
"BCS", "LDA", "LDA", "NOP", "LDY", "LDA", "LDX", "SMB3", // 0xb0-0xb7
"CLV", "LDA", "TSX", "NOP", "LDY", "LDA", "LDX", "BBS3", // 0xb8-0xbf
"CPY", "CMP", "NOP", "NOP", "CPY", "CMP", "DEC", "SMB4", // 0xc0-0xc7
"INY", "CMP", "DEX", "NOP", "CPY", "CMP", "DEC", "BBS4", // 0xc8-0xcf
"BNE", "CMP", "CMP", "NOP", "NOP", "CMP", "DEC", "SMB5", // 0xd0-0xd7
"CLD", "CMP", "PHX", "NOP", "NOP", "CMP", "DEC", "BBS5", // 0xd8-0xdf
"CPX", "SBC", "NOP", "NOP", "CPX", "SBC", "INC", "SMB6", // 0xe0-0xe7
"INX", "SBC", "NOP", "NOP", "CPX", "SBC", "INC", "BBS6", // 0xe8-0xef
"BEQ", "SBC", "SBC", "NOP", "NOP", "SBC", "INC", "SMB7", // 0xf0-0xf7
"SED", "SBC", "PLX", "NOP", "NOP", "SBC", "INC", "BBS7" // 0xf8-0xff
"BRK", "ORA", "NOP", "NOP", "TSB", "ORA", "ASL", "RMB0", // 0x00-0x07
"PHP", "ORA", "ASL", "NOP", "TSB", "ORA", "ASL", "BBR0", // 0x08-0x0f
"BPL", "ORA", "ORA", "NOP", "TRB", "ORA", "ASL", "RMB1", // 0x10-0x17
"CLC", "ORA", "INC", "NOP", "TRB", "ORA", "ASL", "BBR1", // 0x18-0x1f
"JSR", "AND", "NOP", "NOP", "BIT", "AND", "ROL", "RMB2", // 0x20-0x27
"PLP", "AND", "ROL", "NOP", "BIT", "AND", "ROL", "BBR2", // 0x28-0x2f
"BMI", "AND", "AND", "NOP", "BIT", "AND", "ROL", "RMB3", // 0x30-0x37
"SEC", "AND", "DEC", "NOP", "BIT", "AND", "ROL", "BBR3", // 0x38-0x3f
"RTI", "EOR", "NOP", "NOP", "NOP", "EOR", "LSR", "RMB4", // 0x40-0x47
"PHA", "EOR", "LSR", "NOP", "JMP", "EOR", "LSR", "BBR4", // 0x48-0x4f
"BVC", "EOR", "EOR", "NOP", "NOP", "EOR", "LSR", "RMB5", // 0x50-0x57
"CLI", "EOR", "PHY", "NOP", "NOP", "EOR", "LSR", "BBR5", // 0x58-0x5f
"RTS", "ADC", "NOP", "NOP", "STZ", "ADC", "ROR", "RMB6", // 0x60-0x67
"PLA", "ADC", "ROR", "NOP", "JMP", "ADC", "ROR", "BBR6", // 0x68-0x6f
"BVS", "ADC", "ADC", "NOP", "STZ", "ADC", "ROR", "RMB7", // 0x70-0x77
"SEI", "ADC", "PLY", "NOP", "JMP", "ADC", "ROR", "BBR7", // 0x78-0x7f
"BRA", "STA", "NOP", "NOP", "STY", "STA", "STX", "SMB0", // 0x80-0x87
"DEY", "BIT", "TXA", "NOP", "STY", "STA", "STX", "BBS0", // 0x88-0x8f
"BCC", "STA", "STA", "NOP", "STY", "STA", "STX", "SMB1", // 0x90-0x97
"TYA", "STA", "TXS", "NOP", "STZ", "STA", "STZ", "BBS1", // 0x98-0x9f
"LDY", "LDA", "LDX", "NOP", "LDY", "LDA", "LDX", "SMB2", // 0xa0-0xa7
"TAY", "LDA", "TAX", "NOP", "LDY", "LDA", "LDX", "BBS2", // 0xa8-0xaf
"BCS", "LDA", "LDA", "NOP", "LDY", "LDA", "LDX", "SMB3", // 0xb0-0xb7
"CLV", "LDA", "TSX", "NOP", "LDY", "LDA", "LDX", "BBS3", // 0xb8-0xbf
"CPY", "CMP", "NOP", "NOP", "CPY", "CMP", "DEC", "SMB4", // 0xc0-0xc7
"INY", "CMP", "DEX", "NOP", "CPY", "CMP", "DEC", "BBS4", // 0xc8-0xcf
"BNE", "CMP", "CMP", "NOP", "NOP", "CMP", "DEC", "SMB5", // 0xd0-0xd7
"CLD", "CMP", "PHX", "NOP", "NOP", "CMP", "DEC", "BBS5", // 0xd8-0xdf
"CPX", "SBC", "NOP", "NOP", "CPX", "SBC", "INC", "SMB6", // 0xe0-0xe7
"INX", "SBC", "NOP", "NOP", "CPX", "SBC", "INC", "BBS6", // 0xe8-0xef
"BEQ", "SBC", "SBC", "NOP", "NOP", "SBC", "INC", "SMB7", // 0xf0-0xf7
"SED", "SBC", "PLX", "NOP", "NOP", "SBC", "INC", "BBS7" // 0xf8-0xff
};
/**
+20 -23
View File
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -22,14 +22,12 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon;
import com.loomcom.symon.machines.MulticompMachine;
import com.loomcom.symon.machines.SimpleMachine;
import com.loomcom.symon.machines.SymonMachine;
import com.loomcom.symon.machines.BenEaterMachine;
import com.loomcom.symon.machines.*;
import org.apache.commons.cli.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Locale;
import javax.swing.JOptionPane;
@@ -37,7 +35,8 @@ import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class Main {
private final static Logger logger = LoggerFactory.getLogger(Main.class.getName());
/**
* Main entry point to the simulator. Creates a simulator and shows the main
* window.
@@ -45,8 +44,8 @@ public class Main {
* @param args Program arguments
*/
public static void main(String[] args) throws Exception {
Class machineClass = SymonMachine.class;
Class<?> machineClass = null;
Options options = new Options();
@@ -79,7 +78,7 @@ public class Main {
machineClass = BenEaterMachine.class;
break;
default:
System.err.println("Could not start Symon. Unknown machine type " + machine);
logger.error("Could not start Symon. Unknown machine type {}", machine);
return;
}
}
@@ -97,7 +96,7 @@ public class Main {
cpuBehavior = InstructionTable.CpuBehavior.CMOS_65816;
break;
default:
System.err.println("Could not start Symon. Unknown cpu type " + cpu);
logger.error("Could not start Symon. Unknown cpu type {}", cpu);
return;
}
}
@@ -140,16 +139,14 @@ public class Main {
final Simulator simulator = new Simulator(machineClass, cpuBehavior, romFile, haltOnBreak);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi();
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi();
} catch (Exception e) {
logger.error("Error during Symon UI initialization: {}", e.getMessage());
System.exit(-1);
}
});
@@ -163,7 +160,7 @@ public class Main {
}
}
} catch (ParseException ex) {
System.err.println("Could not start Symon. Reason: " + ex.getMessage());
logger.error("Could not start Symon. Reason: {}", ex.getMessage());
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -27,7 +27,7 @@ import com.loomcom.symon.exceptions.*;
/**
* MemoryRange is a simple container class representing a literal
* range of memory, with a staraddress, and an end address. It has
* range of memory, with a start address, and an end address. It has
* guards against creating impossible memory ranges, and implements
* some useful methods for checking address inclusion and range
* overlaps.
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
+128 -118
View File
@@ -1,5 +1,5 @@
/*
* Copyrighi (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -31,14 +31,13 @@ import com.loomcom.symon.ui.Console;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.monitor.CounterMonitor;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.SortedSet;
import java.util.TreeSet;
import static com.loomcom.symon.InstructionTable.*;
/**
* Symon Simulator Interface and Control.
@@ -72,7 +71,7 @@ public class Simulator {
private static final int MAX_STEPS_BETWEEN_UPDATES = 20000;
// The simulated machine
private Machine machine;
private final Machine machine;
// Number of CPU steps between CRT repaints.
// TODO: Dynamically refresh the value at runtime based on performance figures to reach ~ 30fps.
@@ -115,18 +114,19 @@ public class Simulator {
private JButton runStopButton;
private JButton stepButton;
private JButton stepOverButton;
private JComboBox<String> stepCountBox;
private JFileChooser fileChooser;
private PreferencesDialog preferences;
private Breakpoints breakpoints;
private final Breakpoints breakpoints;
private final Object commandMonitorObject = new Object();
private MainCommand command = MainCommand.NONE;
private boolean haltOnBreak;
private final boolean haltOnBreak;
public enum MainCommand {
NONE,
@@ -138,11 +138,11 @@ public class Simulator {
*/
private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"};
public Simulator(Class machineClass) throws Exception {
public Simulator(Class<?> machineClass) throws Exception {
this(machineClass, InstructionTable.CpuBehavior.NMOS_6502, null, false);
}
public Simulator(Class machineClass, InstructionTable.CpuBehavior cpuType,
public Simulator(Class<?> machineClass, InstructionTable.CpuBehavior cpuType,
String romFile, boolean haltOnBreak) throws Exception {
this.haltOnBreak = haltOnBreak;
this.breakpoints = new Breakpoints(this);
@@ -165,7 +165,7 @@ public class Simulator {
/**
* Display the main simulator UI.
*/
public void createAndShowUi() throws IOException {
public void createAndShowUi() {
mainWindow = new JFrame();
mainWindow.setTitle("6502 Simulator - " + machine.getName());
mainWindow.setResizable(false);
@@ -191,18 +191,22 @@ public class Simulator {
runStopButton = new JButton("Run");
stepButton = new JButton("Step");
stepOverButton = new JButton("Step Over");
stepOverButton.setEnabled(false);
JButton softResetButton = new JButton("Soft Reset");
JButton hardResetButton = new JButton("Hard Reset");
stepCountBox = new JComboBox<>(STEPS);
stepCountBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
stepCountBox.addActionListener(actionEvent -> {
JComboBox<?> cb = (JComboBox<?>) actionEvent.getSource();
String cbStepsPerClick = (String) cb.getSelectedItem();
stepsPerClick = 1; // Default in case of error
if (cbStepsPerClick != null) {
try {
JComboBox cb = (JComboBox) actionEvent.getSource();
stepsPerClick = Integer.parseInt((String) cb.getSelectedItem());
stepsPerClick = Integer.parseInt(cbStepsPerClick);
} catch (NumberFormatException ex) {
stepsPerClick = 1;
stepCountBox.setSelectedIndex(0);
}
}
@@ -210,6 +214,7 @@ public class Simulator {
buttonContainer.add(runStopButton);
buttonContainer.add(stepButton);
buttonContainer.add(stepOverButton);
buttonContainer.add(stepCountBox);
buttonContainer.add(softResetButton);
buttonContainer.add(hardResetButton);
@@ -224,38 +229,25 @@ public class Simulator {
// Bottom - buttons.
mainWindow.getContentPane().add(buttonContainer, BorderLayout.PAGE_END);
runStopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (runLoop != null && runLoop.isRunning()) {
Simulator.this.handleStop();
} else {
Simulator.this.handleStart();
}
runStopButton.addActionListener(actionEvent -> {
if (runLoop != null && runLoop.isRunning()) {
Simulator.this.handleStop();
} else {
Simulator.this.handleStart();
}
});
stepButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
Simulator.this.handleStep(stepsPerClick);
}
stepButton.addActionListener(actionEvent -> Simulator.this.handleStep(stepsPerClick));
stepOverButton.addActionListener(actionEvent -> Simulator.this.handleStepOver());
softResetButton.addActionListener(actionEvent -> {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(false);
});
softResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(false);
}
});
hardResetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(true);
}
hardResetButton.addActionListener(actionEvent -> {
// If this was a CTRL-click, do a hard reset.
Simulator.this.handleReset(true);
});
mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
@@ -277,7 +269,7 @@ public class Simulator {
try {
commandMonitorObject.wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
logger.error("Interrupted during wait on command monitor.");
}
}
return command;
@@ -340,12 +332,28 @@ public class Simulator {
step();
}
updateVisibleState();
stepOverButton.setEnabled(machine.getCpu().getNextIr() == JSR);
} catch (SymonException ex) {
logger.error("Exception during simulator step", ex);
ex.printStackTrace();
}
}
/**
* Step over a JSR / RTS function. This function is much more like a "run()"
* than a "step()", and may enter an infinite loop if no RTS is encountered.
*/
private void handleStepOver() {
if (runLoop != null && runLoop.isRunning()) {
// This should really never happen...
logger.error("Can't step over when the simulator is already running!");
return;
}
runLoop = new RunLoop();
runLoop.haltOnRts(true);
runLoop.start();
}
/**
* Perform a single step of the simulated system.
*/
@@ -365,7 +373,7 @@ public class Simulator {
// If a key has been pressed, fill the ACIA.
try {
if (machine.getAcia() != null && console.hasInput()) {
machine.getAcia().rxWrite((int) console.readInputChar());
machine.getAcia().rxWrite(console.readInputChar());
}
} catch (FifoUnderrunException ex) {
logger.error("Console type-ahead buffer underrun!");
@@ -414,53 +422,73 @@ public class Simulator {
*/
class RunLoop extends Thread {
private boolean isRunning = false;
private boolean haltOnRts = false;
// The number of JSR instructions we've seen since being asked to
// step over a subroutine. While stepping over, this is incremented
// on RTS instructions, and decremented on JSR instructions.
private int callStackDepth = 0;
public boolean isRunning() {
return isRunning;
return this.isRunning;
}
public void requestStop() {
isRunning = false;
this.isRunning = false;
}
public void haltOnRts(boolean value) {
this.haltOnRts = value;
}
public void run() {
logger.debug("Starting main run loop.");
isRunning = true;
if (this.haltOnRts) {
logger.debug("Running until next RTS instruction");
} else {
logger.debug("Running");
}
this.isRunning = true;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
stepCountBox.setEnabled(false);
menuBar.simulatorDidStart();
// Toggle the state of the run button
runStopButton.setText("Stop");
}
SwingUtilities.invokeLater(() -> {
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
stepOverButton.setEnabled(false);
stepCountBox.setEnabled(false);
menuBar.simulatorDidStart();
// Toggle the state of the run button
runStopButton.setText("Stop");
});
try {
do {
step();
if (this.haltOnRts) {
var instruction = machine.getCpu().getInstruction();
if (instruction == JSR) {
this.callStackDepth++;
logger.trace("Step-over call stack increased to {}", this.callStackDepth);
} else if (this.callStackDepth > 0 && instruction == RTS) {
this.callStackDepth--;
logger.trace("Step-over call stack decreased to {}", this.callStackDepth);
}
}
} while (shouldContinue());
} catch (SymonException ex) {
logger.error("Exception in main simulator run thread. Exiting run.", ex);
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
statusPane.updateState();
memoryWindow.updateState();
runStopButton.setText("Run");
stepButton.setEnabled(true);
stepCountBox.setEnabled(true);
if (traceLog.isVisible()) {
traceLog.refresh();
}
menuBar.simulatorDidStop();
traceLog.simulatorDidStop();
SwingUtilities.invokeLater(() -> {
statusPane.updateState();
memoryWindow.updateState();
runStopButton.setText("Run");
stepButton.setEnabled(true);
stepOverButton.setEnabled(machine.getCpu().getNextIr() == JSR);
stepCountBox.setEnabled(true);
if (traceLog.isVisible()) {
traceLog.refresh();
}
menuBar.simulatorDidStop();
traceLog.simulatorDidStop();
});
isRunning = false;
@@ -470,9 +498,13 @@ public class Simulator {
* @return True if the run loop should proceed to the next step.
*/
private boolean shouldContinue() {
return !breakpoints.contains(machine.getCpu().getProgramCounter()) &&
isRunning &&
!(preferences.getHaltOnBreak() && machine.getCpu().getInstruction() == 0x00);
var instruction = machine.getCpu().getInstruction();
var stepOverHalt = this.haltOnRts && this.callStackDepth == 0 && instruction == RTS;
var breakpointHalt = breakpoints.contains(machine.getCpu().getProgramCounter());
var brkHalt = preferences.getHaltOnBreak() && instruction == BRK;
return isRunning && !(stepOverHalt || breakpointHalt || brkHalt);
}
}
@@ -513,12 +545,9 @@ public class Simulator {
// Now load the program at the starting address.
loadProgram(program, preferences.getProgramStartAddress());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
console.reset();
breakpoints.refresh();
}
SwingUtilities.invokeLater(() -> {
console.reset();
breakpoints.refresh();
});
// TODO: "Don't Show Again" checkbox
@@ -556,7 +585,7 @@ public class Simulator {
long fileSize = romFile.length();
if (fileSize != machine.getRomSize()) {
throw new IOException("ROM file must be exactly " + String.valueOf(machine.getRomSize()) + " bytes.");
throw new IOException("ROM file must be exactly " + machine.getRomSize() + " bytes.");
}
// Load the new ROM image
@@ -650,30 +679,27 @@ public class Simulator {
}
class SetFontAction extends AbstractAction {
private int size;
private final int size;
public SetFontAction(int size) {
super(Integer.toString(size) + " pt", null);
super(size + " pt", null);
this.size = size;
putValue(SHORT_DESCRIPTION, "Set font to " + size + "pt.");
}
public void actionPerformed(ActionEvent actionEvent) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
console.setFont(new Font("Monospaced", Font.PLAIN, size));
mainWindow.pack();
}
SwingUtilities.invokeLater(() -> {
console.setFont(new Font("Monospaced", Font.PLAIN, size));
mainWindow.pack();
});
}
}
class SetSpeedAction extends AbstractAction {
private int speed;
private final int speed;
public SetSpeedAction(int speed) {
super(Integer.toString(speed) + " MHz", null);
super(speed + " MHz", null);
this.speed = speed;
putValue(SHORT_DESCRIPTION, "Set simulated speed to " + speed + " MHz.");
}
@@ -689,7 +715,7 @@ public class Simulator {
}
class SetCpuAction extends AbstractAction {
private Cpu.CpuBehavior behavior;
private final Cpu.CpuBehavior behavior;
public SetCpuAction(String cpu, Cpu.CpuBehavior behavior) {
super(cpu, null);
@@ -729,11 +755,7 @@ public class Simulator {
public void actionPerformed(ActionEvent actionEvent) {
synchronized (memoryWindow) {
if (memoryWindow.isVisible()) {
memoryWindow.setVisible(false);
} else {
memoryWindow.setVisible(true);
}
memoryWindow.setVisible(!memoryWindow.isVisible());
}
}
}
@@ -746,11 +768,7 @@ public class Simulator {
public void actionPerformed(ActionEvent actionEvent) {
synchronized (videoWindow) {
if (videoWindow.isVisible()) {
videoWindow.setVisible(false);
} else {
videoWindow.setVisible(true);
}
videoWindow.setVisible(!videoWindow.isVisible());
}
}
}
@@ -763,11 +781,7 @@ public class Simulator {
public void actionPerformed(ActionEvent actionEvent) {
synchronized (breakpointsWindow) {
if (breakpointsWindow.isVisible()) {
breakpointsWindow.setVisible(false);
} else {
breakpointsWindow.setVisible(true);
}
breakpointsWindow.setVisible(!breakpointsWindow.isVisible());
}
}
}
@@ -963,15 +977,11 @@ public class Simulator {
private void updateVisibleState() {
// Immediately update the UI.
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Now update the state
statusPane.updateState();
memoryWindow.updateState();
if (traceLog.shouldUpdate()) {
traceLog.refresh();
}
SwingUtilities.invokeLater(() -> {
statusPane.updateState();
memoryWindow.updateState();
if (traceLog.shouldUpdate()) {
traceLog.refresh();
}
});
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -32,7 +32,7 @@ import com.loomcom.symon.exceptions.MemoryRangeException;
public abstract class Acia extends Device {
private String name;
private final String name;
/**
* Register addresses
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -121,9 +121,6 @@ public class Acia6551 extends Acia {
// Mask the lower four bits to get the baud rate.
int baudSelector = data & 0x0f;
switch (baudSelector) {
case 0:
rate = 0;
break;
case 1:
rate = 50;
break;
@@ -169,6 +166,8 @@ public class Acia6551 extends Acia {
case 15:
rate = 19200;
break;
default:
break;
}
setBaudRate(rate);
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -31,7 +31,7 @@ import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* This is a simulation of the Motorola 6850 ACIA, with limited
* functionality.
*
* <p>
* Unlike a 16550 UART, the 6850 ACIA has only one-byte transmit and
* receive buffers. It is the programmer's responsibility to check the
* status (full or empty) for transmit and receive buffers before
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -26,8 +26,6 @@ package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.IOException;
/**
* Simulation of a 6545 CRTC and virtual CRT output.
*/
@@ -87,9 +85,9 @@ public class Crtc extends Device {
private boolean displayEnableSkew = false;
private boolean cursorSkew = false;
private Memory memory;
private final Memory memory;
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException {
super(deviceAddress, deviceAddress + 2, "CRTC");
this.memory = memory;
@@ -120,19 +118,17 @@ public class Crtc extends Device {
@Override
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
switch (address) {
case REGISTER_RW:
switch (currentRegister) {
case CURSOR_POSITION_LOW:
return cursorPosition & 0xff;
case CURSOR_POSITION_HIGH:
return cursorPosition >> 8;
default:
return 0;
}
default:
return 0;
if (address == REGISTER_RW) {
switch (currentRegister) {
case CURSOR_POSITION_LOW:
return cursorPosition & 0xff;
case CURSOR_POSITION_HIGH:
return cursorPosition >> 8;
default:
return 0;
}
}
return 0;
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -45,7 +45,7 @@ public abstract class Device implements Comparable<Device> {
/**
* The memory range for this device.
*/
private MemoryRange memoryRange;
private final MemoryRange memoryRange;
/**
* The name of the device.
@@ -60,7 +60,7 @@ public abstract class Device implements Comparable<Device> {
/**
* Listeners to notify on update.
*/
private Set<DeviceChangeListener> deviceChangeListeners;
private final Set<DeviceChangeListener> deviceChangeListeners;
public Device(int startAddress, int endAddress, String name) throws MemoryRangeException {
this.memoryRange = new MemoryRange(startAddress, endAddress);
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -30,8 +30,8 @@ import com.loomcom.symon.exceptions.*;
public class Memory extends Device {
private boolean readOnly;
private int[] mem;
private final boolean readOnly;
private final int[] mem;
/* Initialize all locations to 0x00 (BRK) */
private static final int DEFAULT_FILL = 0x00;
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -119,7 +119,10 @@ public class SdController extends Device {
if (sdImageFile != null) {
try {
FileInputStream fis = new FileInputStream(sdImageFile);
fis.skip(this.position);
long bytesSkipped = fis.skip(this.position);
if (bytesSkipped < this.position) {
logger.log(Level.WARNING, "Failed to seek to position " + this.position);
}
int read = fis.read(readBuffer);
if (read < SECTOR_SIZE) {
logger.log(Level.WARNING, "not enough data to fill read buffer from SD image file");
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -27,83 +27,23 @@ import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
* Very basic implementation of a MOS 6522 VIA.
*
* TODO: Implement timers as threads.
* Skeleton for future implementation of a MOS 6522 VIA.
*/
public class Via6522 extends Pia {
public static final int VIA_SIZE = 16;
enum Register {
ORB, ORA, DDRB, DDRA, T1C_L, T1C_H, T1L_L, T1L_H,
T2C_L, T2C_H, SR, ACR, PCR, IFR, IER, ORA_H
}
public Via6522(int address) throws MemoryRangeException {
super(address, address + VIA_SIZE - 1, "MOS 6522 VIA");
}
@Override
public void write(int address, int data) throws MemoryAccessException {
Register[] registers = Register.values();
if (address >= registers.length) {
throw new MemoryAccessException("Unknown register: " + address);
}
Register r = registers[address];
switch (r) {
case ORA:
case ORB:
case DDRA:
case DDRB:
case T1C_L:
case T1C_H:
case T1L_L:
case T1L_H:
case T2C_L:
case T2C_H:
case SR:
case ACR:
case PCR:
case IFR:
case IER:
case ORA_H:
default:
}
// TODO: Implement write.
}
@Override
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
Register[] registers = Register.values();
if (address >= registers.length) {
throw new MemoryAccessException("Unknown register: " + address);
}
Register r = registers[address];
switch (r) {
case ORA:
case ORB:
case DDRA:
case DDRB:
case T1C_L:
case T1C_H:
case T1L_L:
case T1L_H:
case T2C_L:
case T2C_H:
case SR:
case ACR:
case PCR:
case IFR:
case IER:
case ORA_H:
default:
}
// TODO: Implement read.
return 0;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Portions Copyright (c) 2009-2011 Graham Edgecombe
*
* Permission is hereby granted, free of charge, to any person obtaining
@@ -25,13 +25,8 @@
package com.loomcom.symon.jterminal;
import com.loomcom.symon.jterminal.vt100.Vt100TerminalModel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.*;
import javax.swing.JComponent;
import javax.swing.JScrollBar;
@@ -88,10 +83,6 @@ public class JTerminal extends JComponent {
revalidate();
}
public int getBorderWidth() {
return borderWidth;
}
public void setFont(Font font) {
this.font = font;
setCellWidthAndHeight(font);
@@ -113,13 +104,6 @@ public class JTerminal extends JComponent {
return model;
}
public void println(String str) {
if (str == null) {
throw new NullPointerException("str");
}
print(str.concat("\r\n"));
}
public void print(String str) {
model.print(str);
}
@@ -177,12 +161,8 @@ public class JTerminal extends JComponent {
int bufferSize = model.getBufferSize();
if (bufferSize > rows) {
scrollBar = new JScrollBar(1, 0, rows, 0, bufferSize + 1);
scrollBar.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent evt) {
repaint();
}
});
scrollBar = new JScrollBar(Adjustable.VERTICAL, 0, rows, 0, bufferSize + 1);
scrollBar.addAdjustmentListener(evt -> repaint());
add("After", scrollBar);
}
@@ -32,51 +32,45 @@ import com.loomcom.symon.jterminal.bell.BellStrategy;
*/
public interface TerminalModel {
/**
* Gets the bell strategy.
* @return The bell strategy.
*/
public BellStrategy getBellStrategy();
/**
* Sets the bell strategy.
* @param strategy The bell strategy.
* @throws NullPointerException if the strategy is {@code null}.
*/
public void setBellStrategy(BellStrategy strategy);
void setBellStrategy(BellStrategy strategy);
/**
* Clears the terminal.
*/
public void clear();
void clear();
/**
* Moves the cursor back n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorBack(int n);
void moveCursorBack(int n);
/**
* Moves the cursor forward n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorForward(int n);
void moveCursorForward(int n);
/**
* Moves the cursor down n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorDown(int n);
void moveCursorDown(int n);
/**
* Moves the cursor up n characters.
* @param n The number of characters.
* @throws IllegalArgumentException if n is not positive.
*/
public void moveCursorUp(int n);
void moveCursorUp(int n);
/**
* Sets a cell.
@@ -86,7 +80,7 @@ public interface TerminalModel {
* @throws IndexOutOfBoundsException if the column and/or row number(s) are
* out of bounds.
*/
public void setCell(int column, int row, TerminalCell cell);
void setCell(int column, int row, TerminalCell cell);
/**
* Gets a cell.
@@ -96,7 +90,7 @@ public interface TerminalModel {
* @throws IndexOutOfBoundsException if the column and/or row number(s) are
* out of bounds.
*/
public TerminalCell getCell(int column, int row);
TerminalCell getCell(int column, int row);
/**
* Prints the specified string to the terminal at the cursor position,
@@ -105,63 +99,63 @@ public interface TerminalModel {
* @param str The string to print.
* @throws NullPointerException if the string is {@code null}.
*/
public void print(String str);
void print(String str);
/**
* Gets the number of columns.
* @return The number of columns.
*/
public int getColumns();
int getColumns();
/**
* Gets the number of rows.
* @return The number of rows.
*/
public int getRows();
int getRows();
/**
* Gets the buffer size.
* @return The buffer size.
*/
public int getBufferSize();
int getBufferSize();
/**
* Gets the cursor row.
* @return The cursor row.
*/
public int getCursorRow();
int getCursorRow();
/**
* Sets the cursor row.
* @param row The cursor row.
* @throws IllegalArgumentException if the row is out of the valid range.
*/
public void setCursorRow(int row);
void setCursorRow(int row);
/**
* Gets the cursor column.
* @return The cursor column.
*/
public int getCursorColumn();
int getCursorColumn();
/**
* Sets the cursor column.
* @param column The cursor column.
* @throws IllegalArgumentException if the column is out of the valid range.
*/
public void setCursorColumn(int column);
void setCursorColumn(int column);
/**
* Gets the default background color.
* @return The default background color.
*/
public Color getDefaultBackgroundColor();
Color getDefaultBackgroundColor();
/**
* Gets the default foreground color.
* @return The default foreground color.
*/
public Color getDefaultForegroundColor();
Color getDefaultForegroundColor();
}
@@ -31,7 +31,7 @@ public interface BellStrategy {
/**
* Sounds the bell.
*/
public void soundBell();
void soundBell();
}
@@ -50,7 +50,7 @@ public class AnsiControlSequence {
throw new NullPointerException("parameters");
}
this.command = command;
if (parameters.length == 1 && parameters[0].equals("")) {
if (parameters.length == 1 && parameters[0].isEmpty()) {
this.parameters = new String[0];
} else {
this.parameters = parameters.clone();
@@ -32,13 +32,13 @@ public interface AnsiControlSequenceListener {
* Called when a control sequence has been parsed.
* @param seq The control sequence.
*/
public void parsedControlSequence(AnsiControlSequence seq);
void parsedControlSequence(AnsiControlSequence seq);
/**
* Called when a string has been parsed.
* @param str The string.
*/
public void parsedString(String str);
void parsedString(String str);
}
@@ -33,7 +33,7 @@ import java.io.StringReader;
public class AnsiControlSequenceParser {
/**
* The multi-byte control sequence introducer.
* The multibyte control sequence introducer.
*/
private static final char[] MULTI_CSI = new char[] { 27, '[' };
@@ -43,7 +43,7 @@ public class AnsiControlSequenceParser {
private static final char SINGLE_CSI = 155;
/**
* The buffer of data from the last call to {@link #parse()}. This is
* The buffer of data from the last call to "parse()". This is
* populated with data if an escape sequence is not complete.
*/
private StringBuilder buffer = new StringBuilder();
@@ -70,13 +70,10 @@ public class AnsiControlSequenceParser {
str = buffer.toString().concat(str);
buffer = new StringBuilder();
}
Reader reader = new StringReader(str);
try {
try {
parse(reader);
} finally {
reader.close();
}
try {
try (Reader reader = new StringReader(str)) {
parse(reader);
}
} catch (IOException ex) {
/* ignore */
}
@@ -147,7 +144,7 @@ public class AnsiControlSequenceParser {
if (!finishedSequence) {
// not an ideal solution if they used the two byte CSI, but it's
// easier and cleaner than keeping track of it
buffer.append((char) SINGLE_CSI);
buffer.append(SINGLE_CSI);
buffer.append(parameters);
}
}
@@ -56,6 +56,7 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
@Override
public void parsedControlSequence(AnsiControlSequence seq) {
char command = seq.getCommand();
int n;
String[] parameters = seq.getParameters();
switch (command) {
@@ -63,7 +64,7 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
case 'B':
case 'C':
case 'D':
int n = 1;
n = 1;
if (parameters.length == 1) {
n = Integer.parseInt(parameters[0]);
}
@@ -73,7 +74,7 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
moveCursorDown(n);
} else if (command == 'C') {
moveCursorForward(n);
} else if (command == 'D') {
} else {
moveCursorBack(n);
}
break;
@@ -85,7 +86,7 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
}
if (command == 'E') {
moveCursorDown(n);
} else if (command == 'F') {
} else {
moveCursorUp(n);
}
setCursorColumn(0);
@@ -465,12 +466,7 @@ public class Vt100TerminalModel extends AbstractTerminalModel {
return bufferSize;
}
@Override
public BellStrategy getBellStrategy() {
return bellStrategy;
}
@Override
@Override
public void setBellStrategy(BellStrategy strategy) {
if (strategy == null) {
throw new NullPointerException("strategy");
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Chelsea Wilkinson <mail@chelseawilkinson.me>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Chelsea Wilkinson <mail@chelseawilkinson.me>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -42,9 +42,9 @@ public interface Machine {
Memory getRam();
Acia getAcia();
Pia getPia();
Crtc getCrtc();
Memory getRom();
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -91,7 +91,7 @@ public class SimpleMachine implements Machine {
}
@Override
public void setRom(Memory rom) throws MemoryRangeException {
public void setRom(Memory rom) {
// No-op
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -30,10 +30,7 @@ import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
@@ -46,8 +43,8 @@ public class BreakpointsWindow extends JFrame {
private static final Dimension FRAME_SIZE = new Dimension(240, 280);
private static final String EMPTY_STRING = "";
private JFrame mainWindow;
private Breakpoints breakpoints;
private final JFrame mainWindow;
private final Breakpoints breakpoints;
public BreakpointsWindow(Breakpoints breakpoints,
JFrame mainWindow) {
@@ -75,16 +72,7 @@ public class BreakpointsWindow extends JFrame {
breakpointsTable.setShowGrid(true);
breakpointsTable.setGridColor(Color.LIGHT_GRAY);
breakpointsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
breakpointsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getFirstIndex() > -1) {
removeButton.setEnabled(true);
} else {
removeButton.setEnabled(false);
}
}
});
breakpointsTable.getSelectionModel().addListSelectionListener(e -> removeButton.setEnabled(e.getFirstIndex() > -1));
JScrollPane scrollPane = new JScrollPane(breakpointsTable);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
@@ -92,45 +80,33 @@ public class BreakpointsWindow extends JFrame {
breakpointsPanel.add(scrollPane, BorderLayout.CENTER);
ActionListener addBreakpointListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int value;
ActionListener addBreakpointListener = e -> {
int value;
String newBreakpoint = addTextField.getText();
String newBreakpoint = addTextField.getText();
if (newBreakpoint == null || newBreakpoint.isEmpty()) {
return;
}
try {
value = (Integer.parseInt(addTextField.getText(), 16) & 0xffff);
} catch (NumberFormatException ex) {
logger.warn("Can't parse page number {}", newBreakpoint);
return;
}
if (value < 0) {
return;
}
breakpoints.addBreakpoint(value);
logger.debug("Added breakpoint ${}", Utils.wordToHex(value));
addTextField.setText(EMPTY_STRING);
if (newBreakpoint == null || newBreakpoint.isEmpty()) {
return;
}
try {
value = (Integer.parseInt(addTextField.getText(), 16) & 0xffff);
} catch (NumberFormatException ex) {
logger.warn("Can't parse page number {}", newBreakpoint);
return;
}
breakpoints.addBreakpoint(value);
logger.debug("Added breakpoint ${}", Utils.wordToHex(value));
addTextField.setText(EMPTY_STRING);
};
addButton.addActionListener(addBreakpointListener);
addTextField.addActionListener(addBreakpointListener);
removeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
breakpoints.removeBreakpointAtIndex(breakpointsTable.getSelectedRow());
}
});
removeButton.addActionListener(e -> breakpoints.removeBreakpointAtIndex(breakpointsTable.getSelectedRow()));
controlPanel.add(addTextField);
controlPanel.add(addButton);
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -51,8 +51,8 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
private static final boolean SWAP_CR_AND_LF = true;
// If true, send CRLF (0x0d 0x0a) whenever CR is typed
private boolean sendCrForLf;
private FifoRingBuffer<Character> typeAheadBuffer;
private final boolean sendCrForLf;
private final FifoRingBuffer<Character> typeAheadBuffer;
public Console(int columns, int rows, Font font, boolean sendCrForLf) {
super(new Vt100TerminalModel(columns, rows), font);
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -49,7 +49,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
private static final Logger logger = LoggerFactory.getLogger(MemoryWindow.class);
private MemoryTableModel memoryTableModel;
private final MemoryTableModel memoryTableModel;
private JTable memoryTable;
private JTextField pageNumberTextField;
private JButton previousPageButton;
@@ -111,7 +111,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
}
/**
* Set-up the UI.
* Set up the UI.
*/
private void createUi() {
setTitle("Memory Contents");
@@ -230,7 +230,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
* A JTable that will automatically select all text in a cell
* being edited.
*/
private class MemoryTable extends JTable {
private static class MemoryTable extends JTable {
public MemoryTable(TableModel tableModel) {
super(tableModel);
@@ -242,7 +242,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
final Component editor = getEditorComponent();
if (editor != null && editor instanceof JTextComponent) {
if (editor instanceof JTextComponent) {
((JTextComponent) editor).selectAll();
}
@@ -250,7 +250,7 @@ public class MemoryWindow extends JFrame implements ActionListener {
}
}
private class MemoryTableCellRenderer extends DefaultTableCellRenderer {
private static class MemoryTableCellRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
@@ -272,9 +272,9 @@ public class MemoryWindow extends JFrame implements ActionListener {
/**
* The model that backs the Memory Table.
*/
private class MemoryTableModel extends AbstractTableModel {
private static class MemoryTableModel extends AbstractTableModel {
private Bus bus;
private final Bus bus;
private int pageNumber;
private static final int COLUMN_COUNT = 17;
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
* Maik Merten <maikmerten@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -64,12 +64,13 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private static final long WINDOW_REPAINT_INTERVAL = 66; // 30fps rate
private static final int CHAR_WIDTH = 8;
private static final int CHAR_HEIGHT = 8;
private static final String ASCII_ROM = "/ascii.rom";
private final int scaleX, scaleY;
private final boolean shouldScale;
private BufferedImage image;
private int[] charRom;
private final int[] charRom;
private int horizontalDisplayed;
private int verticalDisplayed;
@@ -78,9 +79,9 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private boolean hideCursor;
private Dimension dimensions;
private Crtc crtc;
private final Crtc crtc;
private ScheduledExecutorService scheduler;
private final ScheduledExecutorService scheduler;
private ScheduledFuture<?> cursorBlinker;
/**
@@ -123,12 +124,9 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
*/
private class CursorBlinker implements Runnable {
public void run() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (cursorBlinkRate > 0) {
hideCursor = !hideCursor;
}
SwingUtilities.invokeLater(() -> {
if (cursorBlinkRate > 0) {
hideCursor = !hideCursor;
}
});
}
@@ -136,12 +134,9 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private class WindowPainter implements Runnable {
public void run() {
SwingUtilities.invokeLater(new Runnable () {
@Override
public void run() {
if (VideoWindow.this.isVisible()) {
VideoWindow.this.repaint();
}
SwingUtilities.invokeLater(() -> {
if (VideoWindow.this.isVisible()) {
VideoWindow.this.repaint();
}
});
}
@@ -152,7 +147,7 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
this.scheduler = Executors.newSingleThreadScheduledExecutor();
this.crtc = crtc;
this.charRom = loadCharRom("/ascii.rom");
this.charRom = loadCharRom();
this.scaleX = scaleX;
this.scaleY = scaleY;
this.shouldScale = (scaleX > 1 || scaleY > 1);
@@ -292,14 +287,17 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
* Since the BufferedImage is a TYPE_BYTE_BINARY, the data must be converted
* into a single byte per pixel, 0 for black and 255 for white.
* @param resource The ROM file resource to load.
* @return An array of glyphs, each ready for insertion.
* @throws IOException
* @throws IOException Failure to load character ROM.
*/
private int[] loadCharRom(String resource) throws IOException {
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(this.getClass().getResourceAsStream(resource));
private int[] loadCharRom() throws IOException {
var resource = this.getClass().getResourceAsStream(ASCII_ROM);
if (resource == null) {
throw new IOException("Resource " + ASCII_ROM + " not found");
}
try (BufferedInputStream bis = new BufferedInputStream(resource)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (bis.available() > 0) {
bos.write(bis.read());
@@ -314,7 +312,7 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
int[] converted = new int[raw.length * CHAR_WIDTH];
int romIndex = 0;
for (int i = 0; i < converted.length;) {
for (int i = 0; i < converted.length; ) {
byte charRow = raw[romIndex++];
for (int j = 7; j >= 0; j--) {
@@ -322,10 +320,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
}
}
return converted;
} finally {
if (bis != null) {
bis.close();
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -34,8 +34,8 @@ import java.util.Queue;
*/
public class FifoRingBuffer<E> implements Iterable<E> {
private Queue<E> fifoBuffer;
private int maxLength;
private final Queue<E> fifoBuffer;
private final int maxLength;
public FifoRingBuffer(int maxLength) {
this.fifoBuffer = new LinkedList<>();
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016 Seth J. Morabito <web@loomcom.com>
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@@ -670,4 +670,78 @@ public class CpuTest extends TestCase {
cpu.step();
assertEquals(0x3E, cpu.getAccumulator());
}
// Have tests:
// ACC, AIX, ABS, ABX, ABY, IMP, IMM, IND, XIN, INY,
// REL, ZPG, ZPI
// Need to test:
// ZPR, ZPX, ZPY, NUL
// Question: Should 'NUL' even exist? Isn't it just 'IMP'?
public void testDisassembleOpHandlesAccumulatorCorrectly() {
var dis = Cpu.disassembleOp(0x2a, new int[]{});
assertEquals("ROL", dis);
}
public void testDisassembleOpHandlesAbsoluteXIndexedIndirectCorrectly() {
var dis = Cpu.disassembleOp(0x7c, new int[]{0x00, 0x1a});
assertEquals("JMP ($1A00,X)", dis);
}
public void testDisassembleOpHandlesAbsoluteCorrectly() {
var dis = Cpu.disassembleOp(0x0d, new int[]{0x10, 0x0c});
assertEquals("ORA $0C10", dis);
}
public void testDisassembleOpHandlesAbsoluteXIndexedCorrectly() {
var dis = Cpu.disassembleOp(0x1d, new int[]{0x00, 0x1c});
assertEquals("ORA $1C00,X", dis);
}
public void testDisassembleOpHandlesAbsoluteYIndexedCorrectly() {
var dis = Cpu.disassembleOp(0x19, new int[]{0x00, 0x1c});
assertEquals("ORA $1C00,Y", dis);
}
public void testDisassembleOpHandlesImpliedCorrectly() {
var dis = Cpu.disassembleOp(0x00, new int[]{});
assertEquals("BRK", dis);
}
public void testDisassembleOpHandlesIndirectCorrectly() {
var dis = Cpu.disassembleOp(0x6c, new int[]{0x00, 0x1c});
assertEquals("JMP ($1C00)", dis);
}
public void testDisassembleOpHandlesImmediateCorrectly() {
var dis = Cpu.disassembleOp(0xA9, new int[]{0xff});
assertEquals("LDA #$FF", dis);
}
public void testDisassembleOpHandlesXIndexedIndirectCorrectly() {
var dis = Cpu.disassembleOp(0x01, new int[]{0x10});
assertEquals("ORA ($10,X)", dis);
}
public void testDisassembleOpHandlesIndirectYIndexedCorrectly() {
var dis = Cpu.disassembleOp(0x11, new int[]{0x10});
assertEquals("ORA ($10),Y", dis);
}
public void testDisassembleOpHandlesRelativeCorrectly() {
var dis = Cpu.disassembleOp(0x10, new int[]{0xff});
assertEquals("BPL $FF", dis);
}
public void testDisassembleOpHandlesZeroPageCorrectly() {
var dis = Cpu.disassembleOp(0x24, new int[]{0x10});
assertEquals("BIT $10", dis);
}
public void testDisassembleOpHandlesZeroPageIndirectCorrectly() throws Exception {
var dis = Cpu.disassembleOp(0xB2, new int[]{0x1a});
assertEquals("LDA ($1A)", dis);
}
}