Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb097a0c49 | ||
|
|
3ee3a7e300 | ||
|
|
b2810fa062 | ||
|
|
c9f9121477 | ||
|
|
91ef70fe4e | ||
|
|
69bf418f43 | ||
|
|
540f0dff58 | ||
|
|
17668e2c3c | ||
|
|
3353b8436c
|
||
|
|
e635d0a608
|
||
|
|
558af36cd5
|
||
|
|
5fa6fa0fc0
|
||
|
|
f152ed9df5
|
||
|
|
87217afa6e
|
||
|
|
b9997e3edc | ||
|
|
45c8a69d7b | ||
|
|
2e2163fefb | ||
|
|
995b372f55 | ||
|
|
47c87aac56 |
No files matched your search
@@ -0,0 +1,36 @@
|
|||||||
|
name: Build and Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup JDK
|
||||||
|
uses: actions/setup-java@v6
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: '21'
|
||||||
|
#cache: maven
|
||||||
|
|
||||||
|
- name: Install Maven
|
||||||
|
run: apt-get update && apt-get install -y maven
|
||||||
|
|
||||||
|
- name: Test and package
|
||||||
|
run: mvn -B verify
|
||||||
|
|
||||||
|
- name: Upload jar
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: package
|
||||||
|
path: target/*.jar
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup JDK
|
||||||
|
uses: actions/setup-java@v6
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: '21'
|
||||||
|
|
||||||
|
- name: Install Maven
|
||||||
|
run: apt-get update && apt-get install -y maven
|
||||||
|
|
||||||
|
- name: Package
|
||||||
|
run: mvn -B verify
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
uses: https://gitea.com/actions/gitea-release-action@v1
|
||||||
|
with:
|
||||||
|
files: |-
|
||||||
|
target/symon*.jar
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
SYMON - A 6502 System Simulator
|
kSYMON - A 6502 System Simulator
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
**Version:** 1.5.0
|
**Version:** 1.5.1.1k
|
||||||
|
|
||||||
**Last Updated:** 30 March, 2025
|
**Last Updated:** 24 September, 2026
|
||||||
|
|
||||||
See the file COPYING for license.
|
See the file COPYING for license.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 1.0 About
|
## 1.0 About
|
||||||
|
|
||||||
@@ -71,9 +71,16 @@ memory.
|
|||||||
- `$6000`--`$600F`: 6522 VIA
|
- `$6000`--`$600F`: 6522 VIA
|
||||||
- `$8000`--`$FFFF`: 16KB ROM
|
- `$8000`--`$FFFF`: 16KB ROM
|
||||||
|
|
||||||
|
#### 3.1.5 Mike42 Memory Map
|
||||||
|
|
||||||
|
- `$0000`--`$7FFF`: 32KB RAM
|
||||||
|
- `$8000`--`$800F`: 6522 VIA
|
||||||
|
- `$8400`--`$8403`: MOS 6551 ACIA (Serial Console)
|
||||||
|
- `$C000`--`$FFFF`: 16KB ROM
|
||||||
|
|
||||||
### 3.2 Serial Console and CPU Status
|
### 3.2 Serial Console and CPU Status
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The main window of the simulator acts as the primary Input/Output
|
The main window of the simulator acts as the primary Input/Output
|
||||||
system through a virtual serial terminal. It also provides CPU status.
|
system through a virtual serial terminal. It also provides CPU status.
|
||||||
@@ -100,13 +107,13 @@ see the official datasheet:
|
|||||||
|
|
||||||
- [MOS 6551 ACIA](http://archive.6502.org/datasheets/mos_6551_acia.pdf)
|
- [MOS 6551 ACIA](http://archive.6502.org/datasheets/mos_6551_acia.pdf)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The console supports font sizes from 10 to 20 points.
|
The console supports font sizes from 10 to 20 points.
|
||||||
|
|
||||||
### 3.3 ROM Loading
|
### 3.3 ROM Loading
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Symon can load any appropriately sized ROM image. The Symon
|
Symon can load any appropriately sized ROM image. The Symon
|
||||||
architecture expects as 16KB (16384 byte) ROM image, while the
|
architecture expects as 16KB (16384 byte) ROM image, while the
|
||||||
@@ -117,32 +124,32 @@ address.
|
|||||||
|
|
||||||
### 3.4 Memory Window
|
### 3.4 Memory Window
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Memory contents can be viewed (and edited) one page at a time through the Memory Window.
|
Memory contents can be viewed (and edited) one page at a time through the Memory Window.
|
||||||
|
|
||||||
### 3.5 Trace Log
|
### 3.5 Trace Log
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The last 20,000 execution steps are disassembled and logged to the Trace Log
|
The last 20,000 execution steps are disassembled and logged to the Trace Log
|
||||||
Window.
|
Window.
|
||||||
|
|
||||||
### 3.6 Simulator Speeds
|
### 3.6 Simulator Speeds
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Simulated speeds may be set from 1MHz to 8MHz.
|
Simulated speeds may be set from 1MHz to 8MHz.
|
||||||
|
|
||||||
### 3.7 Breakpoints
|
### 3.7 Breakpoints
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Breakpoints can be set and removed through the Breakpoints window.
|
Breakpoints can be set and removed through the Breakpoints window.
|
||||||
|
|
||||||
### 3.8 Experimental 6545 CRTC Video
|
### 3.8 Experimental 6545 CRTC Video
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
This feature is highly experimental. It's possible to open a video window
|
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
|
from the "View" menu. This window simulates the output of a MOS 6545 CRT
|
||||||
@@ -229,7 +236,18 @@ to specify machine type and CPU type. The options are:
|
|||||||
- `-r`,`-rom <file>`: Use the specified file as the ROM image.
|
- `-r`,`-rom <file>`: Use the specified file as the ROM image.
|
||||||
- `-b`,`-brk`: Halt the simulator on a BRK instruction (default is to continue)
|
- `-b`,`-brk`: Halt the simulator on a BRK instruction (default is to continue)
|
||||||
|
|
||||||
### 4.2 ROM images
|
### 4.2 High-DPI UI scaling
|
||||||
|
|
||||||
|
In certain environments with high-DPI (e.g. 4K) displays, the UI may
|
||||||
|
not automatically scale and appear quite tiny. This can be fixed
|
||||||
|
with the command line argument `-Dsun.java2d.uiScale=<factor>`,
|
||||||
|
for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
java -Dsun.java2d.uiScale=2.0 -jar symon-1.5.0.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 ROM images
|
||||||
|
|
||||||
The simulator requires a ROM image loaded into memory to work
|
The simulator requires a ROM image loaded into memory to work
|
||||||
properly. Without a ROM in memory, the simulator will not be able to
|
properly. Without a ROM in memory, the simulator will not be able to
|
||||||
@@ -247,7 +265,7 @@ architecture named 'ehbasic.rom', containing Lee Davison's Enhanced
|
|||||||
*Note*: Presently, EhBASIC only works with the Symon machine
|
*Note*: Presently, EhBASIC only works with the Symon machine
|
||||||
architecture, not with MULTICOMP.
|
architecture, not with MULTICOMP.
|
||||||
|
|
||||||
### 4.3 Loading A Program
|
### 4.4 Loading A Program
|
||||||
|
|
||||||
In addition to ROM images, programs in the form of raw binary object files can
|
In addition to ROM images, programs in the form of raw binary object files can
|
||||||
be loaded directly into memory from "Load Program..." in the File menu.
|
be loaded directly into memory from "Load Program..." in the File menu.
|
||||||
@@ -263,7 +281,7 @@ for testing.
|
|||||||
|
|
||||||
- 'hello.prg' will continuously print "Hello, 6502 World!" to the console.
|
- 'hello.prg' will continuously print "Hello, 6502 World!" to the console.
|
||||||
|
|
||||||
### 4.4 Running
|
### 4.5 Running
|
||||||
|
|
||||||
After loading a program or ROM image, clicking "Run" will start the simulator
|
After loading a program or ROM image, clicking "Run" will start the simulator
|
||||||
running.
|
running.
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="https://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4.0.0.xsd">
|
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.loomcom.symon</groupId>
|
<groupId>com.loomcom.symon</groupId>
|
||||||
<artifactId>symon</artifactId>
|
<artifactId>symon</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<version>1.5.0</version>
|
<version>1.5.1.1k</version>
|
||||||
<name>symon</name>
|
<name>symon</name>
|
||||||
<url>https://git.loomcom.com/seth/symon</url>
|
<url>https://git.loomcom.com/seth/symon</url>
|
||||||
<properties>
|
<properties>
|
||||||
@@ -15,11 +16,32 @@
|
|||||||
UTF-8
|
UTF-8
|
||||||
</project.reporting.outputEncoding>
|
</project.reporting.outputEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>jitpack.io</id>
|
||||||
|
<url>https://jitpack.io</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
<version>1.5.16</version>
|
<version>1.5.34</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-cli</groupId>
|
||||||
|
<artifactId>commons-cli</artifactId>
|
||||||
|
<version>1.5.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.formdev</groupId>
|
||||||
|
<artifactId>flatlaf</artifactId>
|
||||||
|
<version>3.7.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.Dansoftowner</groupId>
|
||||||
|
<artifactId>jSystemThemeDetector</artifactId>
|
||||||
|
<version>3.9.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
@@ -34,9 +56,9 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-cli</groupId>
|
<groupId>net.java.dev.jna</groupId>
|
||||||
<artifactId>commons-cli</artifactId>
|
<artifactId>jna</artifactId>
|
||||||
<version>1.5.0</version>
|
<version>5.14.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
@@ -48,7 +70,8 @@
|
|||||||
<version>3.2.0</version>
|
<version>3.2.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<transformers>
|
<transformers>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
<mainClass>com.loomcom.symon.Main</mainClass>
|
<mainClass>com.loomcom.symon.Main</mainClass>
|
||||||
</transformer>
|
</transformer>
|
||||||
</transformers>
|
</transformers>
|
||||||
@@ -79,8 +102,8 @@
|
|||||||
<version>3.7.0</version>
|
<version>3.7.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<compilerArgument>-Xlint:unchecked</compilerArgument>
|
<compilerArgument>-Xlint:unchecked</compilerArgument>
|
||||||
<source>11</source>
|
<source>16</source>
|
||||||
<target>11</target>
|
<target>16</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
@@ -120,4 +143,4 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
@@ -25,19 +25,20 @@
|
|||||||
package com.loomcom.symon;
|
package com.loomcom.symon;
|
||||||
|
|
||||||
import com.loomcom.symon.machines.*;
|
import com.loomcom.symon.machines.*;
|
||||||
|
import com.loomcom.symon.ui.Ui;
|
||||||
|
|
||||||
import org.apache.commons.cli.*;
|
import org.apache.commons.cli.*;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.UIManager;
|
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
private final static Logger logger = LoggerFactory.getLogger(Main.class.getName());
|
private final static Logger logger = LoggerFactory.getLogger(Main.class.getName());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main entry point to the simulator. Creates a simulator and shows the main
|
* Main entry point to the simulator. Creates a simulator and shows the main
|
||||||
* window.
|
* window.
|
||||||
*
|
*
|
||||||
@@ -49,6 +50,7 @@ public class Main {
|
|||||||
|
|
||||||
Options options = new Options();
|
Options options = new Options();
|
||||||
|
|
||||||
|
options.addOption(new Option("h", "help", false, "Print this message."));
|
||||||
options.addOption(new Option("m", "machine", true, "Specify machine type."));
|
options.addOption(new Option("m", "machine", true, "Specify machine type."));
|
||||||
options.addOption(new Option("c", "cpu", true, "Specify CPU type."));
|
options.addOption(new Option("c", "cpu", true, "Specify CPU type."));
|
||||||
options.addOption(new Option("r", "rom", true, "Specify ROM file."));
|
options.addOption(new Option("r", "rom", true, "Specify ROM file."));
|
||||||
@@ -57,11 +59,18 @@ public class Main {
|
|||||||
CommandLineParser parser = new DefaultParser();
|
CommandLineParser parser = new DefaultParser();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
CommandLine line = parser.parse(options, args);
|
CommandLine line = parser.parse(options, args);
|
||||||
InstructionTable.CpuBehavior cpuBehavior = null;
|
InstructionTable.CpuBehavior cpuBehavior = null;
|
||||||
String romFile = null;
|
String romFile = null;
|
||||||
boolean haltOnBreak = false;
|
boolean haltOnBreak = false;
|
||||||
|
|
||||||
|
if (line.hasOption("help")) {
|
||||||
|
HelpFormatter formatter = new HelpFormatter();
|
||||||
|
formatter.printHelp("symon", options, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (line.hasOption("machine")) {
|
if (line.hasOption("machine")) {
|
||||||
String machine = line.getOptionValue("machine").toLowerCase(Locale.ENGLISH);
|
String machine = line.getOptionValue("machine").toLowerCase(Locale.ENGLISH);
|
||||||
switch (machine) {
|
switch (machine) {
|
||||||
@@ -77,6 +86,9 @@ public class Main {
|
|||||||
case "beneater":
|
case "beneater":
|
||||||
machineClass = BenEaterMachine.class;
|
machineClass = BenEaterMachine.class;
|
||||||
break;
|
break;
|
||||||
|
case "mike42":
|
||||||
|
machineClass = MichaelBillingtonMachine.class;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
logger.error("Could not start Symon. Unknown machine type {}", machine);
|
logger.error("Could not start Symon. Unknown machine type {}", machine);
|
||||||
return;
|
return;
|
||||||
@@ -109,28 +121,11 @@ public class Main {
|
|||||||
haltOnBreak = true;
|
haltOnBreak = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ui.initUi();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (machineClass == null) {
|
if (machineClass == null) {
|
||||||
Object[] possibilities = {"Symon", "Multicomp", "Simple", "BenEater"};
|
machineClass = Ui.showMachineSelectionDialog();
|
||||||
String s = (String)JOptionPane.showInputDialog(
|
|
||||||
null,
|
|
||||||
"Please choose the machine type to be emulated:",
|
|
||||||
"Machine selection",
|
|
||||||
JOptionPane.PLAIN_MESSAGE,
|
|
||||||
null,
|
|
||||||
possibilities,
|
|
||||||
"Symon");
|
|
||||||
|
|
||||||
|
|
||||||
if (s != null && s.equals("Multicomp")) {
|
|
||||||
machineClass = MulticompMachine.class;
|
|
||||||
} else if (s != null && s.equals("Simple")) {
|
|
||||||
machineClass = SimpleMachine.class;
|
|
||||||
} else if (s != null && s.equals("BenEater")) {
|
|
||||||
machineClass = BenEaterMachine.class;
|
|
||||||
} else {
|
|
||||||
machineClass = SymonMachine.class;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cpuBehavior == null) {
|
if (cpuBehavior == null) {
|
||||||
@@ -140,9 +135,8 @@ public class Main {
|
|||||||
final Simulator simulator = new Simulator(machineClass, cpuBehavior, romFile, haltOnBreak);
|
final Simulator simulator = new Simulator(machineClass, cpuBehavior, romFile, haltOnBreak);
|
||||||
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
// Create the main UI window:
|
||||||
try {
|
try {
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
|
||||||
// Create the main UI window
|
|
||||||
simulator.createAndShowUi();
|
simulator.createAndShowUi();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error during Symon UI initialization: {}", e.getMessage());
|
logger.error("Error during Symon UI initialization: {}", e.getMessage());
|
||||||
@@ -150,7 +144,6 @@ public class Main {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Simulator.MainCommand cmd = simulator.waitForCommand();
|
Simulator.MainCommand cmd = simulator.waitForCommand();
|
||||||
|
|
||||||
if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) {
|
if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) {
|
||||||
@@ -163,4 +156,5 @@ public class Main {
|
|||||||
logger.error("Could not start Symon. Reason: {}", ex.getMessage());
|
logger.error("Could not start Symon. Reason: {}", ex.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
package com.loomcom.symon;
|
package com.loomcom.symon;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.util.UIScale;
|
||||||
import com.loomcom.symon.devices.Memory;
|
import com.loomcom.symon.devices.Memory;
|
||||||
import com.loomcom.symon.exceptions.*;
|
import com.loomcom.symon.exceptions.*;
|
||||||
import com.loomcom.symon.machines.Machine;
|
import com.loomcom.symon.machines.Machine;
|
||||||
@@ -53,7 +54,7 @@ public class Simulator {
|
|||||||
|
|
||||||
// UI constants
|
// UI constants
|
||||||
private static final int DEFAULT_FONT_SIZE = 12;
|
private static final int DEFAULT_FONT_SIZE = 12;
|
||||||
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
|
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, UIScale.scale(DEFAULT_FONT_SIZE));
|
||||||
private static final int CONSOLE_BORDER_WIDTH = 10;
|
private static final int CONSOLE_BORDER_WIDTH = 10;
|
||||||
|
|
||||||
// Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz.
|
// Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ package com.loomcom.symon.devices;
|
|||||||
|
|
||||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
|
import com.loomcom.symon.util.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skeleton for future implementation of a MOS 6522 VIA.
|
* Skeleton for future implementation of a MOS 6522 VIA.
|
||||||
@@ -32,18 +33,82 @@ import com.loomcom.symon.exceptions.MemoryRangeException;
|
|||||||
public class Via6522 extends Pia {
|
public class Via6522 extends Pia {
|
||||||
public static final int VIA_SIZE = 16;
|
public static final int VIA_SIZE = 16;
|
||||||
|
|
||||||
|
|
||||||
|
static final int IOB_REG = 0; // Output Register B / Input Register B
|
||||||
|
static final int IOA_REG = 1; // Output Register A / Input Register A
|
||||||
|
static final int DDRB_REG = 2; // Data Direction Register B
|
||||||
|
static final int DDRA_REG = 3; // Data Direction Register A
|
||||||
|
static final int T1CL_REG = 4; // T1 Low-Order Counter / Latches
|
||||||
|
static final int T1CH_REG = 5; // T1 High-Order Counter
|
||||||
|
static final int T1LL_REG = 6; // T1 Low-Order Latches
|
||||||
|
static final int T1LH_REG = 7; // T1 High-Order Latches
|
||||||
|
static final int T2CL_REG = 8; // T2 Low-Order Counter / Latches
|
||||||
|
static final int T2CH_REG = 9; // T2 High-Order Counter
|
||||||
|
static final int SR_REG = 10; // Shift Register
|
||||||
|
static final int AuxC_REG = 11; // Auxiliary Control Register
|
||||||
|
static final int PerC_REG = 12; // Peripheral Control Register
|
||||||
|
static final int IntF_REG = 13; // Interrupt Flag Register
|
||||||
|
static final int IntE_REG = 14; // Interrupt Enable Register
|
||||||
|
static final int IOANH_REG = 15; // Same as Register 1 (IOA), but no Handshake
|
||||||
|
|
||||||
|
private int outputRegisterB, inputRegisterB;
|
||||||
|
private int outputRegisterA, inputRegisterA;
|
||||||
|
private int dataDirectionB, dataDirectionA;
|
||||||
|
|
||||||
public Via6522(int address) throws MemoryRangeException {
|
public Via6522(int address) throws MemoryRangeException {
|
||||||
super(address, address + VIA_SIZE - 1, "MOS 6522 VIA");
|
super(address, address + VIA_SIZE - 1, "MOS 6522 VIA");
|
||||||
|
outputRegisterA = outputRegisterB = 0;
|
||||||
|
dataDirectionA = dataDirectionB = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(int address, int data) throws MemoryAccessException {
|
public void write(int address, int data) throws MemoryAccessException {
|
||||||
// TODO: Implement write.
|
switch (address) {
|
||||||
|
case IOB_REG -> outputRegisterB = data & 0xFF;
|
||||||
|
case IOA_REG -> outputRegisterA = data & 0xFF;
|
||||||
|
case DDRB_REG -> dataDirectionB = data & 0xFF;
|
||||||
|
case DDRA_REG -> dataDirectionA = data & 0xFF;
|
||||||
|
case IOANH_REG -> outputRegisterA = data & 0xFF;
|
||||||
|
default -> throw new MemoryAccessException("No Register or Not Implemented!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
|
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
|
||||||
// TODO: Implement read.
|
return switch (address) {
|
||||||
return 0;
|
case IOB_REG -> pbRead();
|
||||||
|
case IOA_REG -> paRead();
|
||||||
|
case DDRB_REG -> dataDirectionB;
|
||||||
|
case DDRA_REG -> dataDirectionA;
|
||||||
|
case IOANH_REG -> paRead();
|
||||||
|
default -> throw new MemoryAccessException("No Register or Not Implemented!");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputRegisterB(int data) {
|
||||||
|
inputRegisterB = data & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputRegisterA(int data) {
|
||||||
|
inputRegisterA = data & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getPBStatus() {
|
||||||
|
return "%" + Utils.byteToBin(pbRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPAStatus() {
|
||||||
|
return "%" + Utils.byteToBin(paRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int pbRead() {
|
||||||
|
// Return bits of output register where DDRB is 1 and input register where DDRB is 0
|
||||||
|
return (outputRegisterB & dataDirectionB) | (inputRegisterB & ~dataDirectionB);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int paRead() {
|
||||||
|
// Return bits of output register where DDRB is 1 and input register where DDRB is 0
|
||||||
|
return (outputRegisterA & dataDirectionA) | (inputRegisterA & ~dataDirectionA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package com.loomcom.symon.machines;
|
||||||
|
|
||||||
|
import com.loomcom.symon.Bus;
|
||||||
|
import com.loomcom.symon.Cpu;
|
||||||
|
import com.loomcom.symon.devices.*;
|
||||||
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class MichaelBillingtonMachine implements Machine{
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MichaelBillingtonMachine.class.getName());
|
||||||
|
|
||||||
|
// Constants used by the simulated system
|
||||||
|
private static final int BUS_BOTTOM = 0x0000;
|
||||||
|
private static final int BUS_TOP = 0xffff;
|
||||||
|
|
||||||
|
// 32k of ram, from $0000 to $7fff
|
||||||
|
private static final int MEMORY_BASE = 0x0000;
|
||||||
|
private static final int MEMORY_SIZE = 0x8000;
|
||||||
|
|
||||||
|
// VIA at $8000-$800f
|
||||||
|
private static final int PIA_BASE = 0x8000;
|
||||||
|
|
||||||
|
// ACIA at $8400-$8403
|
||||||
|
private static final int ACIA_BASE = 0x8400;
|
||||||
|
|
||||||
|
// 16k ROM from $c000 to $ffff
|
||||||
|
private static final int ROM_BASE = 0xc000;
|
||||||
|
private static final int ROM_SIZE = 0x4000;
|
||||||
|
|
||||||
|
private final Bus bus;
|
||||||
|
private final Cpu cpu;
|
||||||
|
private final Acia acia;
|
||||||
|
private final Pia pia;
|
||||||
|
private final Memory ram;
|
||||||
|
private Memory rom;
|
||||||
|
|
||||||
|
public MichaelBillingtonMachine(String romFile) throws Exception {
|
||||||
|
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
||||||
|
this.cpu = new Cpu();
|
||||||
|
this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1);
|
||||||
|
this.pia = new Via6522(PIA_BASE);
|
||||||
|
this.acia = new Acia6551(ACIA_BASE);
|
||||||
|
|
||||||
|
bus.addCpu(cpu);
|
||||||
|
bus.addDevice(ram);
|
||||||
|
bus.addDevice(pia);
|
||||||
|
bus.addDevice(acia);
|
||||||
|
|
||||||
|
if (romFile != null) {
|
||||||
|
File romImage = new File(romFile);
|
||||||
|
if (romImage.canRead()) {
|
||||||
|
logger.info("Loading ROM image from file {}", romImage);
|
||||||
|
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
|
||||||
|
} else {
|
||||||
|
logger.info("Default ROM file {} not found, loading empty R/W memory image.", romImage);
|
||||||
|
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("No ROM file specified, loading empty R/W memory image.");
|
||||||
|
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.addDevice(rom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bus getBus() {
|
||||||
|
return bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cpu getCpu() {
|
||||||
|
return cpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Memory getRam() {
|
||||||
|
return ram;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Acia getAcia() {
|
||||||
|
return acia;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pia getPia() {
|
||||||
|
return pia;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Crtc getCrtc() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Memory getRom() {
|
||||||
|
return rom;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRom(Memory rom) throws MemoryRangeException {
|
||||||
|
if (this.rom != null) {
|
||||||
|
bus.removeDevice(this.rom);
|
||||||
|
}
|
||||||
|
this.rom = rom;
|
||||||
|
bus.addDevice(this.rom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRomBase() {
|
||||||
|
return ROM_BASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRomSize() {
|
||||||
|
return ROM_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMemorySize() {
|
||||||
|
return MEMORY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "mike42";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
package com.loomcom.symon.ui;
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.util.UIScale;
|
||||||
import com.loomcom.symon.Breakpoints;
|
import com.loomcom.symon.Breakpoints;
|
||||||
import com.loomcom.symon.util.Utils;
|
import com.loomcom.symon.util.Utils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -40,7 +41,7 @@ public class BreakpointsWindow extends JFrame {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(BreakpointsWindow.class);
|
private static final Logger logger = LoggerFactory.getLogger(BreakpointsWindow.class);
|
||||||
|
|
||||||
private static final Dimension FRAME_SIZE = new Dimension(240, 280);
|
private static final Dimension FRAME_SIZE = UIScale.scale(new Dimension(240, 280));
|
||||||
private static final String EMPTY_STRING = "";
|
private static final String EMPTY_STRING = "";
|
||||||
|
|
||||||
private final JFrame mainWindow;
|
private final JFrame mainWindow;
|
||||||
@@ -70,7 +71,6 @@ public class BreakpointsWindow extends JFrame {
|
|||||||
|
|
||||||
final JTable breakpointsTable = new JTable(breakpoints);
|
final JTable breakpointsTable = new JTable(breakpoints);
|
||||||
breakpointsTable.setShowGrid(true);
|
breakpointsTable.setShowGrid(true);
|
||||||
breakpointsTable.setGridColor(Color.LIGHT_GRAY);
|
|
||||||
breakpointsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
breakpointsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
breakpointsTable.getSelectionModel().addListSelectionListener(e -> removeButton.setEnabled(e.getFirstIndex() > -1));
|
breakpointsTable.getSelectionModel().addListSelectionListener(e -> removeButton.setEnabled(e.getFirstIndex() > -1));
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
package com.loomcom.symon.ui;
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.util.UIScale;
|
||||||
import com.loomcom.symon.Bus;
|
import com.loomcom.symon.Bus;
|
||||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
import com.loomcom.symon.util.Utils;
|
import com.loomcom.symon.util.Utils;
|
||||||
@@ -55,15 +56,15 @@ public class MemoryWindow extends JFrame implements ActionListener {
|
|||||||
private JButton previousPageButton;
|
private JButton previousPageButton;
|
||||||
private JButton nextPageButton;
|
private JButton nextPageButton;
|
||||||
|
|
||||||
private static final Dimension MINIMUM_SIZE = new Dimension(320, 600);
|
private static final Dimension MINIMUM_SIZE = UIScale.scale(new Dimension(400, 600));
|
||||||
|
|
||||||
// The width of column 0 (address), in pixels
|
// The width of column 0 (address), in pixels
|
||||||
private static final int ADDR_COL_WIDTH = 48;
|
private static final int ADDR_COL_WIDTH = UIScale.scale(64);
|
||||||
|
|
||||||
private static final int HEX_COL_WIDTH = 32;
|
private static final int HEX_COL_WIDTH = UIScale.scale(32);
|
||||||
|
|
||||||
// The width of the ASCII cells, in pixels
|
// The width of the ASCII cells, in pixels
|
||||||
private static final int ASCII_COL_WIDTH = 8;
|
private static final int ASCII_COL_WIDTH = UIScale.scale(15);
|
||||||
|
|
||||||
// The start/end columns of the ASCII view
|
// The start/end columns of the ASCII view
|
||||||
private static final int ASCII_COL_START = 9;
|
private static final int ASCII_COL_START = 9;
|
||||||
|
|||||||
@@ -23,17 +23,20 @@
|
|||||||
|
|
||||||
package com.loomcom.symon.ui;
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.util.UIScale;
|
||||||
import com.loomcom.symon.Cpu;
|
import com.loomcom.symon.Cpu;
|
||||||
import com.loomcom.symon.CpuState;
|
import com.loomcom.symon.CpuState;
|
||||||
|
import com.loomcom.symon.devices.Pia;
|
||||||
|
import com.loomcom.symon.devices.Via6522;
|
||||||
import com.loomcom.symon.machines.Machine;
|
import com.loomcom.symon.machines.Machine;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.Border;
|
import javax.swing.border.Border;
|
||||||
import javax.swing.border.EtchedBorder;
|
import javax.swing.border.EtchedBorder;
|
||||||
import javax.swing.plaf.metal.MetalTextFieldUI;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI component that displays the current state of the simulated CPU.
|
* UI component that displays the current state of the simulated CPU.
|
||||||
@@ -69,12 +72,14 @@ public class StatusPanel extends JPanel {
|
|||||||
private JTextField aField;
|
private JTextField aField;
|
||||||
private JTextField xField;
|
private JTextField xField;
|
||||||
private JTextField yField;
|
private JTextField yField;
|
||||||
|
private JTextField piaBField;
|
||||||
|
private JTextField piaAField;
|
||||||
|
|
||||||
private final Machine machine;
|
private final Machine machine;
|
||||||
|
|
||||||
private static final int EMPTY_BORDER = 10;
|
private static final int EMPTY_BORDER = 10;
|
||||||
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
|
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
|
||||||
private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 12);
|
private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, UIScale.scale(12));
|
||||||
private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22);
|
private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22);
|
||||||
private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
|
private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
|
||||||
|
|
||||||
@@ -86,7 +91,7 @@ public class StatusPanel extends JPanel {
|
|||||||
|
|
||||||
private void createUi() {
|
private void createUi() {
|
||||||
Border emptyBorder = BorderFactory.createEmptyBorder(EMPTY_BORDER, EMPTY_BORDER,
|
Border emptyBorder = BorderFactory.createEmptyBorder(EMPTY_BORDER, EMPTY_BORDER,
|
||||||
EMPTY_BORDER, EMPTY_BORDER);
|
EMPTY_BORDER, EMPTY_BORDER);
|
||||||
Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
|
Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
|
||||||
|
|
||||||
setBorder(BorderFactory.createCompoundBorder(emptyBorder, etchedBorder));
|
setBorder(BorderFactory.createCompoundBorder(emptyBorder, etchedBorder));
|
||||||
@@ -99,20 +104,20 @@ public class StatusPanel extends JPanel {
|
|||||||
JPanel statusFlagsPanel = new JPanel();
|
JPanel statusFlagsPanel = new JPanel();
|
||||||
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
|
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||||
|
|
||||||
carryOn = new ImageIcon(this.getClass().getResource("/C_on.png"));
|
carryOn = loadScaledIcon("/C_on.png");
|
||||||
carryOff = new ImageIcon(this.getClass().getResource("/C_off.png"));
|
carryOff = loadScaledIcon("/C_off.png");
|
||||||
zeroOn = new ImageIcon(this.getClass().getResource("/Z_on.png"));
|
zeroOn = loadScaledIcon("/Z_on.png");
|
||||||
zeroOff = new ImageIcon(this.getClass().getResource("/Z_off.png"));
|
zeroOff = loadScaledIcon("/Z_off.png");
|
||||||
irqOn = new ImageIcon(this.getClass().getResource("/I_on.png"));
|
irqOn = loadScaledIcon("/I_on.png");
|
||||||
irqOff = new ImageIcon(this.getClass().getResource("/I_off.png"));
|
irqOff = loadScaledIcon("/I_off.png");
|
||||||
decimalOn = new ImageIcon(this.getClass().getResource("/D_on.png"));
|
decimalOn = loadScaledIcon("/D_on.png");
|
||||||
decimalOff = new ImageIcon(this.getClass().getResource("/D_off.png"));
|
decimalOff = loadScaledIcon("/D_off.png");
|
||||||
breakOn = new ImageIcon(this.getClass().getResource("/B_on.png"));
|
breakOn = loadScaledIcon("/B_on.png");
|
||||||
breakOff = new ImageIcon(this.getClass().getResource("/B_off.png"));
|
breakOff = loadScaledIcon("/B_off.png");
|
||||||
overflowOn = new ImageIcon(this.getClass().getResource("/O_on.png"));
|
overflowOn = loadScaledIcon("/O_on.png");
|
||||||
overflowOff = new ImageIcon(this.getClass().getResource("/O_off.png"));
|
overflowOff = loadScaledIcon("/O_off.png");
|
||||||
negativeOn = new ImageIcon(this.getClass().getResource("/N_on.png"));
|
negativeOn = loadScaledIcon("/N_on.png");
|
||||||
negativeOff = new ImageIcon(this.getClass().getResource("/N_off.png"));
|
negativeOff = loadScaledIcon("/N_off.png");
|
||||||
|
|
||||||
// Initialize all to off
|
// Initialize all to off
|
||||||
carryFlagLabel = new JLabel(carryOff, JLabel.CENTER);
|
carryFlagLabel = new JLabel(carryOff, JLabel.CENTER);
|
||||||
@@ -125,16 +130,15 @@ public class StatusPanel extends JPanel {
|
|||||||
|
|
||||||
// Add tool-tip text
|
// Add tool-tip text
|
||||||
carryFlagLabel.setToolTipText("Carry: The last operation caused an overflow " +
|
carryFlagLabel.setToolTipText("Carry: The last operation caused an overflow " +
|
||||||
"from bit 7 of the result or an underflow from bit 0");
|
"from bit 7 of the result or an underflow from bit 0");
|
||||||
zeroFlagLabel.setToolTipText("Zero: The result of the last operation was 0");
|
zeroFlagLabel.setToolTipText("Zero: The result of the last operation was 0");
|
||||||
irqDisableFlagLabel.setToolTipText("Interrupt Disable: Processor will not respond to IRQ");
|
irqDisableFlagLabel.setToolTipText("Interrupt Disable: Processor will not respond to IRQ");
|
||||||
decimalModeFlagLabel.setToolTipText("Decimal Mode");
|
decimalModeFlagLabel.setToolTipText("Decimal Mode");
|
||||||
breakFlagLabel.setToolTipText("Break: BRK instruction occurred");
|
breakFlagLabel.setToolTipText("Break: BRK instruction occurred");
|
||||||
overflowFlagLabel.setToolTipText("Overflow: The result of the last operation was " +
|
overflowFlagLabel.setToolTipText("Overflow: The result of the last operation was " +
|
||||||
"an invalid 2's complement result");
|
"an invalid 2's complement result");
|
||||||
negativeFlagLabel.setToolTipText("Negative: The result of the last operation set bit 7");
|
negativeFlagLabel.setToolTipText("Negative: The result of the last operation set bit 7");
|
||||||
|
|
||||||
|
|
||||||
statusFlagsPanel.add(negativeFlagLabel);
|
statusFlagsPanel.add(negativeFlagLabel);
|
||||||
statusFlagsPanel.add(overflowFlagLabel);
|
statusFlagsPanel.add(overflowFlagLabel);
|
||||||
statusFlagsPanel.add(breakFlagLabel);
|
statusFlagsPanel.add(breakFlagLabel);
|
||||||
@@ -152,6 +156,10 @@ public class StatusPanel extends JPanel {
|
|||||||
JLabel xLabel = makeLabel("X");
|
JLabel xLabel = makeLabel("X");
|
||||||
JLabel yLabel = makeLabel("Y");
|
JLabel yLabel = makeLabel("Y");
|
||||||
|
|
||||||
|
// PIA/VIA labels
|
||||||
|
JLabel piaBLabel = makeLabel("PB");
|
||||||
|
JLabel piaALabel = makeLabel("PA");
|
||||||
|
|
||||||
statusFlagsLabel.setToolTipText("6502 Processor Status Flags");
|
statusFlagsLabel.setToolTipText("6502 Processor Status Flags");
|
||||||
opcodeLabel.setToolTipText("Instruction Register");
|
opcodeLabel.setToolTipText("Instruction Register");
|
||||||
pcLabel.setToolTipText("Program Counter");
|
pcLabel.setToolTipText("Program Counter");
|
||||||
@@ -164,6 +172,9 @@ public class StatusPanel extends JPanel {
|
|||||||
xField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
|
xField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
|
||||||
yField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
|
yField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
|
||||||
|
|
||||||
|
piaBField = makeTextField(LARGE_TEXT_FIELD_SIZE, false);
|
||||||
|
piaAField = makeTextField(LARGE_TEXT_FIELD_SIZE, false);
|
||||||
|
|
||||||
// Make fields editable
|
// Make fields editable
|
||||||
pcField.addActionListener(new ActionListener() {
|
pcField.addActionListener(new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -243,7 +254,6 @@ public class StatusPanel extends JPanel {
|
|||||||
constraints.gridy = 1;
|
constraints.gridy = 1;
|
||||||
add(statusFlagsPanel, constraints);
|
add(statusFlagsPanel, constraints);
|
||||||
|
|
||||||
|
|
||||||
constraints.insets = new Insets(5, 0, 0, 0);
|
constraints.insets = new Insets(5, 0, 0, 0);
|
||||||
constraints.gridy = 2;
|
constraints.gridy = 2;
|
||||||
add(opcodeLabel, constraints);
|
add(opcodeLabel, constraints);
|
||||||
@@ -287,6 +297,25 @@ public class StatusPanel extends JPanel {
|
|||||||
add(xField, constraints);
|
add(xField, constraints);
|
||||||
constraints.gridx = 1;
|
constraints.gridx = 1;
|
||||||
add(yField, constraints);
|
add(yField, constraints);
|
||||||
|
|
||||||
|
constraints.insets = new Insets(5, 0, 0, 0);
|
||||||
|
constraints.gridx = 0;
|
||||||
|
constraints.gridwidth = 2;
|
||||||
|
constraints.gridy = 10;
|
||||||
|
add(piaBLabel, constraints);
|
||||||
|
|
||||||
|
constraints.insets = new Insets(2, 2, 2, 2);
|
||||||
|
constraints.gridy = 11;
|
||||||
|
add(piaBField, constraints);
|
||||||
|
|
||||||
|
constraints.insets = new Insets(5, 0, 0, 0);
|
||||||
|
constraints.gridy = 12;
|
||||||
|
add(piaALabel, constraints);
|
||||||
|
|
||||||
|
constraints.insets = new Insets(2, 2, 2, 2);
|
||||||
|
constraints.gridy = 13;
|
||||||
|
add(piaAField, constraints);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -296,6 +325,8 @@ public class StatusPanel extends JPanel {
|
|||||||
Cpu cpu = machine.getCpu();
|
Cpu cpu = machine.getCpu();
|
||||||
CpuState cpuState = cpu.getCpuState();
|
CpuState cpuState = cpu.getCpuState();
|
||||||
|
|
||||||
|
Pia pia = machine.getPia();
|
||||||
|
|
||||||
// Update the Processor Status Flag display
|
// Update the Processor Status Flag display
|
||||||
int status = cpuState.getStatusFlag();
|
int status = cpuState.getStatusFlag();
|
||||||
|
|
||||||
@@ -317,6 +348,12 @@ public class StatusPanel extends JPanel {
|
|||||||
xField.setText(cpu.getXRegisterStatus());
|
xField.setText(cpu.getXRegisterStatus());
|
||||||
yField.setText(cpu.getYRegisterStatus());
|
yField.setText(cpu.getYRegisterStatus());
|
||||||
|
|
||||||
|
// Update the Pia Variables
|
||||||
|
if (pia instanceof Via6522) {
|
||||||
|
piaBField.setText(((Via6522) pia).getPBStatus());
|
||||||
|
piaAField.setText(((Via6522) pia).getPAStatus());
|
||||||
|
}
|
||||||
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,16 +426,11 @@ public class StatusPanel extends JPanel {
|
|||||||
JTextField textField = new JTextField("");
|
JTextField textField = new JTextField("");
|
||||||
textField.setAlignmentX(LEFT_ALIGNMENT);
|
textField.setAlignmentX(LEFT_ALIGNMENT);
|
||||||
textField.setEditable(editable);
|
textField.setEditable(editable);
|
||||||
textField.setMinimumSize(size);
|
|
||||||
textField.setMaximumSize(size);
|
Dimension scaleSize = UIScale.scale(size);
|
||||||
textField.setPreferredSize(size);
|
textField.setMinimumSize(scaleSize);
|
||||||
textField.setBackground(Color.WHITE);
|
textField.setMaximumSize(scaleSize);
|
||||||
// Although we usually defer to the system look-and-feel, for
|
textField.setPreferredSize(scaleSize);
|
||||||
// these small text fields in particular, we use a Metal
|
|
||||||
// look-and-feel because native look-and-feel breaks very small
|
|
||||||
// text fields under GTK+ (they are drawn with an inner margin
|
|
||||||
// even if the margin is set to 0)
|
|
||||||
textField.setUI(new MetalTextFieldUI());
|
|
||||||
|
|
||||||
return textField;
|
return textField;
|
||||||
}
|
}
|
||||||
@@ -407,4 +439,14 @@ public class StatusPanel extends JPanel {
|
|||||||
String val = source.getText().replaceAll("[^0-9a-fA-F]", "");
|
String val = source.getText().replaceAll("[^0-9a-fA-F]", "");
|
||||||
return Integer.parseInt(val, 16);
|
return Integer.parseInt(val, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImageIcon loadScaledIcon(String resourcePath) {
|
||||||
|
ImageIcon icon = new ImageIcon(Objects.requireNonNull(this.getClass().getResource(resourcePath)));
|
||||||
|
|
||||||
|
int scaledWidth = UIScale.scale(icon.getIconWidth());
|
||||||
|
int scaledHeight = UIScale.scale(icon.getIconHeight());
|
||||||
|
|
||||||
|
Image scaleImage = icon.getImage().getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
|
||||||
|
return new ImageIcon(scaleImage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package com.loomcom.symon.ui;
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.util.UIScale;
|
||||||
import com.loomcom.symon.CpuState;
|
import com.loomcom.symon.CpuState;
|
||||||
import com.loomcom.symon.util.FifoRingBuffer;
|
import com.loomcom.symon.util.FifoRingBuffer;
|
||||||
|
|
||||||
@@ -39,8 +40,8 @@ public class TraceLog extends JFrame {
|
|||||||
private final FifoRingBuffer<CpuState> traceLog;
|
private final FifoRingBuffer<CpuState> traceLog;
|
||||||
private final JTextArea traceLogTextArea;
|
private final JTextArea traceLogTextArea;
|
||||||
|
|
||||||
private static final Dimension MIN_SIZE = new Dimension(320, 200);
|
private static final Dimension MIN_SIZE = UIScale.scale(new Dimension(320, 200));
|
||||||
private static final Dimension PREFERRED_SIZE = new Dimension(640, 480);
|
private static final Dimension PREFERRED_SIZE = UIScale.scale(new Dimension(640, 480));
|
||||||
private static final int MAX_LOG_LENGTH = 50000;
|
private static final int MAX_LOG_LENGTH = 50000;
|
||||||
|
|
||||||
public TraceLog() {
|
public TraceLog() {
|
||||||
@@ -53,7 +54,7 @@ public class TraceLog extends JFrame {
|
|||||||
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
||||||
|
|
||||||
traceLogTextArea = new JTextArea();
|
traceLogTextArea = new JTextArea();
|
||||||
traceLogTextArea.setFont(new Font(Font.MONOSPACED, Font.BOLD, 12));
|
traceLogTextArea.setFont(new Font(Font.MONOSPACED, Font.BOLD, UIScale.scale(12)));
|
||||||
traceLogTextArea.setEditable(false);
|
traceLogTextArea.setEditable(false);
|
||||||
|
|
||||||
JScrollPane scrollableView = new JScrollPane(traceLogTextArea);
|
JScrollPane scrollableView = new JScrollPane(traceLogTextArea);
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.LookAndFeel;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.UnsupportedLookAndFeelException;
|
||||||
|
|
||||||
|
import com.loomcom.symon.machines.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.FlatDarkLaf;
|
||||||
|
import com.formdev.flatlaf.FlatLaf;
|
||||||
|
import com.formdev.flatlaf.FlatLightLaf;
|
||||||
|
import com.formdev.flatlaf.util.SystemInfo;
|
||||||
|
import com.jthemedetecor.OsThemeDetector;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public final class Ui {
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(Ui.class.getName());
|
||||||
|
|
||||||
|
public static void initUi() throws UnsupportedLookAndFeelException {
|
||||||
|
// Location of properties resources for app-specific UI defaults:
|
||||||
|
FlatLaf.registerCustomDefaultsSource("com.loomcom.symon.themes");
|
||||||
|
|
||||||
|
logger.debug("AWT Toolkit class: {}", Toolkit.getDefaultToolkit().getClass().getName());
|
||||||
|
|
||||||
|
// Enable automatic scaling, based on system DPI settings:
|
||||||
|
//System.setProperty("sun.java2d.uiScale.enabled", "true");
|
||||||
|
//System.setProperty("sun.java2d.uiScale", "1x");
|
||||||
|
//System.setProperty("flatlaf.uiScale", "1x");
|
||||||
|
|
||||||
|
applyDetectedUiScale();
|
||||||
|
|
||||||
|
// OS-specific tweaking:
|
||||||
|
|
||||||
|
if (SystemInfo.isLinux) {
|
||||||
|
logger.debug("Adapting UI for Linux.");
|
||||||
|
|
||||||
|
// Enable custom window decorations:
|
||||||
|
JFrame.setDefaultLookAndFeelDecorated(true);
|
||||||
|
JDialog.setDefaultLookAndFeelDecorated(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (SystemInfo.isMacOS) {
|
||||||
|
logger.debug("Adapting UI for MacOS.");
|
||||||
|
|
||||||
|
// Move menu bar from JFrame window to top of screen:
|
||||||
|
System.setProperty("apple.laf.useScreenMenuBar", "true");
|
||||||
|
// Set name used in screen menu bar (first menu after Apple menu):
|
||||||
|
System.setProperty("apple.awt.application.name", "Symon");
|
||||||
|
// Use current macOS appearance (light/dark) for window title bars:
|
||||||
|
System.setProperty("apple.awt.application.appearance", "system");
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (SystemInfo.isWindows) {
|
||||||
|
logger.debug("Adapting UI for Windows.");
|
||||||
|
}
|
||||||
|
|
||||||
|
UIManager.setLookAndFeel(createLookAndFeel());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<?> showMachineSelectionDialog() {
|
||||||
|
Object[] possibilities = { "Symon", "Multicomp", "Simple", "BenEater", "Mike42" };
|
||||||
|
String s = (String) JOptionPane.showInputDialog(
|
||||||
|
null,
|
||||||
|
"Please choose the machine type to be emulated:",
|
||||||
|
"Machine selection",
|
||||||
|
JOptionPane.PLAIN_MESSAGE,
|
||||||
|
null,
|
||||||
|
possibilities,
|
||||||
|
"Symon");
|
||||||
|
|
||||||
|
if (s != null && s.equals("Multicomp")) {
|
||||||
|
return MulticompMachine.class;
|
||||||
|
} else if (s != null && s.equals("Simple")) {
|
||||||
|
return SimpleMachine.class;
|
||||||
|
} else if (s != null && s.equals("BenEater")) {
|
||||||
|
return BenEaterMachine.class;
|
||||||
|
} else if (s != null && s.equals("Mike42")) {
|
||||||
|
return MichaelBillingtonMachine.class;
|
||||||
|
} else {
|
||||||
|
return SymonMachine.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LookAndFeel createLookAndFeel() {
|
||||||
|
final OsThemeDetector themeDetector = OsThemeDetector.getDetector();
|
||||||
|
final boolean systemThemeIsDark = themeDetector.isDark();
|
||||||
|
|
||||||
|
// Set light/dark mode depending on what can be detected from the system theme:
|
||||||
|
FlatLaf theme;
|
||||||
|
if (systemThemeIsDark) {
|
||||||
|
logger.debug("Using DARK theme.");
|
||||||
|
theme = new FlatDarkLaf();
|
||||||
|
} else {
|
||||||
|
logger.debug("Using LIGHT theme.");
|
||||||
|
theme = new FlatLightLaf();
|
||||||
|
}
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyDetectedUiScale() {
|
||||||
|
Double rawScale = null;
|
||||||
|
|
||||||
|
if (SystemInfo.isLinux) rawScale = X11ResourceReader.readXftDpiScale(); // ex: 1.5, 2.0, 2.5...
|
||||||
|
|
||||||
|
if (rawScale != null && rawScale > 1.0) {
|
||||||
|
// flatlaf.uiScale aceita frações — mantém a precisão original
|
||||||
|
System.setProperty("flatlaf.uiScale", rawScale + "x");
|
||||||
|
|
||||||
|
// sun.java2d.uiScale só aceita inteiros — arredonda para cima
|
||||||
|
int javaScale = (int) Math.ceil(rawScale);
|
||||||
|
//System.setProperty("sun.java2d.uiScale", String.valueOf(javaScale));
|
||||||
|
|
||||||
|
logger.debug("Detected scale {}x, applying flatlaf.uiScale={}x, sun.java2d.uiScale={}x",
|
||||||
|
rawScale, rawScale, javaScale);
|
||||||
|
} else {
|
||||||
|
logger.debug("No X11 HiDPI scale detected, using default.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
import com.sun.jna.ptr.PointerByReference;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class X11ResourceReader {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(X11ResourceReader.class.getName());
|
||||||
|
|
||||||
|
|
||||||
|
public interface X11 extends Library {
|
||||||
|
X11 INSTANCE = Native.load("X11", X11.class);
|
||||||
|
|
||||||
|
Pointer XOpenDisplay(String display);
|
||||||
|
Pointer XResourceManagerString(Pointer display);
|
||||||
|
void XCloseDisplay(Pointer display);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Double readXftDpiScale() {
|
||||||
|
Pointer display = null;
|
||||||
|
try {
|
||||||
|
display = X11.INSTANCE.XOpenDisplay(null);
|
||||||
|
if (display == null) {
|
||||||
|
return null; // não conseguimos ligar ao X server (ex: Wayland puro sem XWayland)
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer resourceString = X11.INSTANCE.XResourceManagerString(display);
|
||||||
|
if (resourceString == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String resources = resourceString.getString(0);
|
||||||
|
for (String line : resources.split("\n")) {
|
||||||
|
if (line.startsWith("Xft.dpi:")) {
|
||||||
|
int dpi = Integer.parseInt(line.split(":")[1].trim());
|
||||||
|
return Math.round((dpi / 96.0) * 4) / 4.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (UnsatisfiedLinkError | Exception e) {
|
||||||
|
logger.debug("Could not read X11 resource manager (libX11 unavailable?)", e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (display != null) {
|
||||||
|
X11.INSTANCE.XCloseDisplay(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,6 +76,16 @@ public class Utils {
|
|||||||
"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7",
|
"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7",
|
||||||
"F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF"};
|
"F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF"};
|
||||||
|
|
||||||
|
private static final String[] BIN_CONSTANTS = new String[256];
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
String bin = Integer.toBinaryString(i);
|
||||||
|
// Left-pad to 8 digits with zeros
|
||||||
|
BIN_CONSTANTS[i] = "00000000".substring(bin.length()) + bin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Very fast 8-bit int to ASCII conversion.
|
* Very fast 8-bit int to ASCII conversion.
|
||||||
*
|
*
|
||||||
@@ -116,4 +126,14 @@ public class Utils {
|
|||||||
public static int address(int lowByte, int hiByte) {
|
public static int address(int lowByte, int hiByte) {
|
||||||
return ((hiByte << 8) | lowByte) & 0xffff;
|
return ((hiByte << 8) | lowByte) & 0xffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very fast 8-bit int to binary conversion, with zero-padded output.
|
||||||
|
*
|
||||||
|
* @param val The unsigned 8-bit value to convert to a zero padded binary string.
|
||||||
|
* @return Eight digit, zero padded binary string.
|
||||||
|
*/
|
||||||
|
public static String byteToBin(int val) {
|
||||||
|
return BIN_CONSTANTS[val & 0xff];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 220 B |
|
Before Width: | Height: | Size: 196 B After Width: | Height: | Size: 232 B |
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 224 B |
|
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 202 B |
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 238 B |
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 221 B |
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 238 B |
@@ -0,0 +1,3 @@
|
|||||||
|
# Loaded for FlatDarkLaf theme only.
|
||||||
|
|
||||||
|
Colors.accent.default = #4B6EAF
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Loaded for all FlatLaf themes.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Loaded for FlatLightLaf theme only.
|
||||||
|
|
||||||
|
Colors.accent.default = #2675BF
|
||||||