mirror of
https://github.com/badvision/jace.git
synced 2026-03-12 00:41:43 +00:00
Compare commits
1 Commits
main
...
65c02_func
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ae5cf18bd |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,4 +21,3 @@ hs_err_pid*
|
||||
!/lib/nestedvm.jar
|
||||
_acme_tmp*
|
||||
.vscode/settings.json
|
||||
*.log
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "src/test/resources/65x02_unit_tests"]
|
||||
path = src/test/resources/65x02_unit_tests
|
||||
url = https://github.com/SingleStepTests/65x02
|
||||
@@ -1 +1 @@
|
||||
23
|
||||
graalvm64-17.0.3
|
||||
|
||||
11
README.md
11
README.md
@@ -48,17 +48,6 @@ The Gluon documentation provides a compatibility matrix for each OS platform and
|
||||
|
||||
All other native dependencies are automatically downloaded as needed by Maven for the various LWJGL libraries.
|
||||
|
||||
### Windows-specific build notes:
|
||||
|
||||
On Windows, the Visual Studio linker must be in your PATH before Git's linker. The easiest way to build is to use Git Bash with the Visual Studio tools directory prepended to PATH:
|
||||
|
||||
```bash
|
||||
export PATH="/c/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.41.34120/bin/Hostx64/x64:$PATH"
|
||||
mvn clean gluonfx:build
|
||||
```
|
||||
|
||||
The native executable will be created at `target\gluonfx\x86_64-windows\Jace.exe`. Note: You may need to adjust the MSVC version number (14.41.34120) to match your installed Visual Studio version.
|
||||
|
||||
### First time build note:
|
||||
Because Jace provides an annotation processor for compilation, there is a chicken-and-egg problem when building the first time. Currently, this means the first time you compile, run `mvn install` twice. You don't have to do this step again as long as Maven is able to find a previously build version of Jace to provide this annotation processor. I tried to set up the profiles in the pom.xml so that it disables the annotation processor the first time you compile to avoid any issues. If running in a CICD environment, keep in mind you will likely always need to run the "mvn install" step twice, but only if your goal is to build the entire application including the annotations (should not be needed for just running unit tests.)
|
||||
|
||||
|
||||
14
pom.xml
14
pom.xml
@@ -31,20 +31,14 @@
|
||||
<artifactId>gluonfx-maven-plugin</artifactId>
|
||||
<version>1.0.23</version>
|
||||
<configuration>
|
||||
<target>host</target>
|
||||
<mainClass>jace.JaceApplication</mainClass>
|
||||
<executable>Jace</executable>
|
||||
<appIdentifier>jace</appIdentifier>
|
||||
<resourcesList>
|
||||
<resourcesList>ceAppl
|
||||
<resource>.*</resource>
|
||||
</resourcesList>
|
||||
<releaseConfiguration>
|
||||
<vendor>org.badvision</vendor>
|
||||
<skipSigning>true</skipSigning>
|
||||
</releaseConfiguration>
|
||||
<nativeImageArgs>
|
||||
<arg>--no-fallback</arg>
|
||||
</nativeImageArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -195,12 +189,6 @@
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<scope>test</scope>
|
||||
<version>2.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial.thirdparty</groupId>
|
||||
<artifactId>nestedvm</artifactId>
|
||||
|
||||
@@ -413,8 +413,8 @@ public class Apple2e extends Computer {
|
||||
animCycleNumber++;
|
||||
} else {
|
||||
getMemory().write(animAddr, animOldValue, true, true);
|
||||
animationSchedule.cancel(false);
|
||||
animAddr = 0;
|
||||
animationSchedule.cancel(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -422,9 +422,6 @@ public class Apple2e extends Computer {
|
||||
if (hints.isEmpty()) {
|
||||
hints.add(getMemory().observe("Helpful hints", RAMEvent.TYPE.EXECUTE, 0x0FB63, (e)->{
|
||||
animationTimer.schedule(drawHints, 1, TimeUnit.SECONDS);
|
||||
if (animationSchedule != null) {
|
||||
animationSchedule.cancel(true);
|
||||
}
|
||||
animationSchedule =
|
||||
animationTimer.scheduleAtFixedRate(doAnimation, 1250, 100, TimeUnit.MILLISECONDS);
|
||||
}));
|
||||
|
||||
@@ -136,7 +136,7 @@ public class MOS65C02 extends CPU {
|
||||
BBS6(0x0ef, COMMAND.BBS6, MODE.ZP_REL, 5, true),
|
||||
BBS7(0x0ff, COMMAND.BBS7, MODE.ZP_REL, 5, true),
|
||||
BEQ_REL0(0x00F0, COMMAND.BEQ, MODE.RELATIVE, 2),
|
||||
BIT_IMM(0x0089, COMMAND.BIT, MODE.IMMEDIATE, 3, true),
|
||||
BIT_IMM(0x0089, COMMAND.BIT, MODE.IMMEDIATE, 2, true),
|
||||
BIT_ZP(0x0024, COMMAND.BIT, MODE.ZEROPAGE, 3),
|
||||
BIT_ZP_X(0x0034, COMMAND.BIT, MODE.ZEROPAGE_X, 4, true),
|
||||
BIT_AB(0x002C, COMMAND.BIT, MODE.ABSOLUTE, 4),
|
||||
@@ -193,8 +193,7 @@ public class MOS65C02 extends CPU {
|
||||
INX(0x00E8, COMMAND.INX, MODE.IMPLIED, 2),
|
||||
INY(0x00C8, COMMAND.INY, MODE.IMPLIED, 2),
|
||||
JMP_AB(0x004C, COMMAND.JMP, MODE.ABSOLUTE, 3, false, false),
|
||||
// JMP_IND(0x006C, COMMAND.JMP, MODE.INDIRECT_BUGGY, 6),
|
||||
JMP_IND(0x006C, COMMAND.JMP, MODE.INDIRECT, 6),
|
||||
JMP_IND(0x006C, COMMAND.JMP, MODE.INDIRECT, 5),
|
||||
JMP_IND_X(0x007C, COMMAND.JMP, MODE.INDIRECT_X, 6, true),
|
||||
JSR_AB(0x0020, COMMAND.JSR, MODE.ABSOLUTE, 6, false, false),
|
||||
LDA_IMM(0x00A9, COMMAND.LDA, MODE.IMMEDIATE, 2),
|
||||
@@ -387,10 +386,6 @@ public class MOS65C02 extends CPU {
|
||||
ZEROPAGE(2, "$~1", (cpu) -> cpu.getMemory().read(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false) & 0x00FF),
|
||||
ZEROPAGE_X(2, "$~1,X", (cpu) -> 0x0FF & (cpu.getMemory().read(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false) + cpu.X)),
|
||||
ZEROPAGE_Y(2, "$~1,Y", (cpu) -> 0x0FF & (cpu.getMemory().read(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false) + cpu.Y)),
|
||||
INDIRECT_BUGGY(3, "$(~2~1)", (cpu) -> {
|
||||
int address = cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
|
||||
return cpu.getMemory().readWordPageWraparound(address, TYPE.READ_DATA, true, false);
|
||||
}),
|
||||
INDIRECT(3, "$(~2~1)", (cpu) -> {
|
||||
int address = cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
|
||||
return cpu.getMemory().readWord(address, TYPE.READ_DATA, true, false);
|
||||
@@ -401,15 +396,15 @@ public class MOS65C02 extends CPU {
|
||||
}),
|
||||
INDIRECT_ZP(2, "$(~1)", (cpu) -> {
|
||||
int address = cpu.getMemory().read(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
|
||||
return cpu.getMemory().readWordPageWraparound(address & 0x0FF, TYPE.READ_DATA, true, false);
|
||||
return cpu.getMemory().readWord(address & 0x0FF, TYPE.READ_DATA, true, false);
|
||||
}),
|
||||
INDIRECT_ZP_X(2, "$(~1,X)", (cpu) -> {
|
||||
int address = cpu.getMemory().read(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false) + cpu.X;
|
||||
return cpu.getMemory().readWordPageWraparound(address & 0x0FF, TYPE.READ_DATA, true, false);
|
||||
return cpu.getMemory().readWord(address & 0x0FF, TYPE.READ_DATA, true, false);
|
||||
}),
|
||||
INDIRECT_ZP_Y(2, "$(~1),Y", (cpu) -> {
|
||||
int address = 0x00FF & cpu.getMemory().read(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
|
||||
address = cpu.getMemory().readWordPageWraparound(address, TYPE.READ_DATA, true, false);
|
||||
address = cpu.getMemory().readWord(address, TYPE.READ_DATA, true, false);
|
||||
int address2 = address + cpu.Y;
|
||||
cpu.setPageBoundaryPenalty((address & 0x00ff00) != (address2 & 0x00ff00));
|
||||
cpu.setPageBoundaryApplied(true);
|
||||
@@ -420,7 +415,7 @@ public class MOS65C02 extends CPU {
|
||||
int address2 = cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
|
||||
int address = 0x0FFFF & (address2 + cpu.X);
|
||||
// False read
|
||||
cpu.getMemory().read(address, TYPE.READ_FAKE, true, false);
|
||||
cpu.getMemory().read(address, TYPE.READ_DATA, true, false);
|
||||
cpu.setPageBoundaryPenalty((address & 0x00ff00) != (address2 & 0x00ff00));
|
||||
cpu.setPageBoundaryApplied(true);
|
||||
return address;
|
||||
@@ -429,7 +424,7 @@ public class MOS65C02 extends CPU {
|
||||
int address2 = cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
|
||||
int address = 0x0FFFF & (address2 + cpu.Y);
|
||||
// False read
|
||||
cpu.getMemory().read(address, TYPE.READ_FAKE, true, false);
|
||||
cpu.getMemory().read(address, TYPE.READ_DATA, true, false);
|
||||
cpu.setPageBoundaryPenalty((address & 0x00ff00) != (address2 & 0x00ff00));
|
||||
cpu.setPageBoundaryApplied(true);
|
||||
return address;
|
||||
@@ -531,8 +526,7 @@ public class MOS65C02 extends CPU {
|
||||
public void processCommand(int address, int value, MODE addressMode, MOS65C02 cpu) {
|
||||
if ((value & (1 << bit)) == 0) {
|
||||
cpu.setProgramCounter(address);
|
||||
cpu.setPageBoundaryApplied(true);
|
||||
cpu.addWaitCycles(1);
|
||||
cpu.addWaitCycles(cpu.pageBoundaryPenalty ? 2 : 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -672,9 +666,9 @@ public class MOS65C02 extends CPU {
|
||||
BIT((address, value, addressMode, cpu) -> {
|
||||
int result = (cpu.A & value);
|
||||
cpu.Z = result == 0;
|
||||
cpu.N = (value & 0x080) != 0;
|
||||
// As per http://www.6502.org/tutorials/vflag.html
|
||||
if (addressMode != MODE.IMMEDIATE) {
|
||||
cpu.N = (value & 0x080) != 0;
|
||||
cpu.V = (value & 0x040) != 0;
|
||||
}
|
||||
}),
|
||||
@@ -837,7 +831,7 @@ public class MOS65C02 extends CPU {
|
||||
cpu.push((byte) cpu.A);
|
||||
}),
|
||||
PHP((address, value, addressMode, cpu) -> {
|
||||
cpu.push((byte) (cpu.getStatus() | 0x10));
|
||||
cpu.push((cpu.getStatus()));
|
||||
}),
|
||||
PHX((address, value, addressMode, cpu) -> {
|
||||
cpu.push((byte) cpu.X);
|
||||
@@ -975,11 +969,11 @@ public class MOS65C02 extends CPU {
|
||||
cpu.setNZ(cpu.Y);
|
||||
}),
|
||||
TRB((address, value, addressMode, cpu) -> {
|
||||
cpu.Z = (value & cpu.A) == 0;
|
||||
cpu.C = (value & cpu.A) != 0 ? 1 : 0;
|
||||
cpu.getMemory().write(address, (byte) (value & ~cpu.A), true, false);
|
||||
}),
|
||||
TSB((address, value, addressMode, cpu) -> {
|
||||
cpu.Z = (value & cpu.A) == 0;
|
||||
cpu.C = (value & cpu.A) != 0 ? 1 : 0;
|
||||
cpu.getMemory().write(address, (byte) (value | cpu.A), true, false);
|
||||
}),
|
||||
TSX((address, value, addressMode, cpu) -> {
|
||||
@@ -1067,6 +1061,10 @@ public class MOS65C02 extends CPU {
|
||||
bytes = 2;
|
||||
wait = 2;
|
||||
}
|
||||
case 3, 7, 0x0b, 0x0f -> {
|
||||
wait = 1;
|
||||
bytes = 1;
|
||||
}
|
||||
case 4 -> {
|
||||
bytes = 2;
|
||||
if ((op & 0x0f0) == 0x040) {
|
||||
@@ -1083,12 +1081,8 @@ public class MOS65C02 extends CPU {
|
||||
wait = 4;
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
wait = 1;
|
||||
bytes = 1;
|
||||
}
|
||||
default -> bytes = 2;
|
||||
}
|
||||
wait--;
|
||||
incrementProgramCounter(bytes);
|
||||
addWaitCycles(wait);
|
||||
|
||||
@@ -1135,7 +1129,7 @@ public class MOS65C02 extends CPU {
|
||||
return getMemory().read(0x0100 + STACK, TYPE.READ_DATA, true, false);
|
||||
}
|
||||
|
||||
public byte getStatus() {
|
||||
private byte getStatus() {
|
||||
return (byte) ((N ? 0x080 : 0)
|
||||
| (V ? 0x040 : 0)
|
||||
| 0x020
|
||||
@@ -1146,17 +1140,10 @@ public class MOS65C02 extends CPU {
|
||||
| ((C > 0) ? 0x01 : 0));
|
||||
}
|
||||
|
||||
public void setStatus(byte b) {
|
||||
setStatus(b, false);
|
||||
}
|
||||
|
||||
public void setStatus(byte b, boolean setBreakFlag) {
|
||||
private void setStatus(byte b) {
|
||||
N = (b & 0x080) != 0;
|
||||
V = (b & 0x040) != 0;
|
||||
// B flag is normally unaffected in this way, can be bypassed for unit testing
|
||||
if (setBreakFlag) {
|
||||
B = (b & 0x010) != 0;
|
||||
}
|
||||
// B flag is unaffected in this way.
|
||||
D = (b & 0x08) != 0;
|
||||
I = (b & 0x04) != 0;
|
||||
Z = (b & 0x02) != 0;
|
||||
@@ -1184,13 +1171,10 @@ public class MOS65C02 extends CPU {
|
||||
LOG.log(Level.WARNING, "BRK at ${0}", Integer.toString(getProgramCounter(), 16));
|
||||
dumpTrace();
|
||||
}
|
||||
programCounter++;
|
||||
pushPC();
|
||||
push((byte) (getStatus() | 0x010));
|
||||
B = true;
|
||||
// 65c02 clears D flag on BRK
|
||||
I = true;
|
||||
D = false;
|
||||
setProgramCounter(getMemory().readWord(INT_VECTOR, TYPE.READ_DATA, true, false));
|
||||
interruptSignalled = true;
|
||||
}
|
||||
|
||||
// Hardware IRQ generated
|
||||
@@ -1222,7 +1206,6 @@ public class MOS65C02 extends CPU {
|
||||
public void reset() {
|
||||
pushWord(getProgramCounter());
|
||||
push(getStatus());
|
||||
setWaitCycles(0);
|
||||
// STACK = 0x0ff;
|
||||
// B = false;
|
||||
B = true;
|
||||
@@ -1234,7 +1217,7 @@ public class MOS65C02 extends CPU {
|
||||
// Z = true;
|
||||
int resetVector = getMemory().readWord(RESET_VECTOR, TYPE.READ_DATA, true, false);
|
||||
int newPC = resetVector;
|
||||
// LOG.log(Level.WARNING, "Reset called, setting PC to ({0}) = {1}", new Object[]{Integer.toString(RESET_VECTOR, 16), Integer.toString(newPC, 16)});
|
||||
LOG.log(Level.WARNING, "Reset called, setting PC to ({0}) = {1}", new Object[]{Integer.toString(RESET_VECTOR, 16), Integer.toString(newPC, 16)});
|
||||
setProgramCounter(newPC);
|
||||
}
|
||||
|
||||
|
||||
@@ -194,9 +194,9 @@ abstract public class RAM128k extends RAM {
|
||||
public String getReadConfiguration() {
|
||||
String rstate = "";
|
||||
if (SoftSwitches.RAMRD.getState()) {
|
||||
rstate += "Ra_";
|
||||
rstate += "Ra";
|
||||
} else {
|
||||
rstate += "R0_";
|
||||
rstate += "R0";
|
||||
}
|
||||
String LCR = "L0R";
|
||||
if (SoftSwitches.LCRAM.isOn()) {
|
||||
@@ -214,16 +214,16 @@ abstract public class RAM128k extends RAM {
|
||||
}
|
||||
rstate += LCR;
|
||||
if (SoftSwitches.CXROM.getState()) {
|
||||
rstate += "_CXROM";
|
||||
rstate += "CXROM";
|
||||
} else {
|
||||
rstate += "_!CX";
|
||||
rstate += "!CX";
|
||||
if (SoftSwitches.SLOTC3ROM.isOff()) {
|
||||
rstate += "_C3";
|
||||
rstate += "C3";
|
||||
}
|
||||
if (SoftSwitches.INTC8ROM.isOn()) {
|
||||
rstate += "_C8";
|
||||
rstate += "C8";
|
||||
} else {
|
||||
rstate += "_C8"+getActiveSlot();
|
||||
rstate += "C8"+getActiveSlot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,9 +233,9 @@ abstract public class RAM128k extends RAM {
|
||||
public String getWriteConfiguration() {
|
||||
String wstate = "";
|
||||
if (SoftSwitches.RAMWRT.getState()) {
|
||||
wstate += "Wa_";
|
||||
wstate += "Wa";
|
||||
} else {
|
||||
wstate += "W0_";
|
||||
wstate += "W0";
|
||||
}
|
||||
String LCW = "L0W";
|
||||
if (SoftSwitches.LCWRITE.isOn()) {
|
||||
@@ -256,26 +256,22 @@ abstract public class RAM128k extends RAM {
|
||||
}
|
||||
|
||||
public String getAuxZPConfiguration() {
|
||||
String astate = "__";
|
||||
String astate = "";
|
||||
if (SoftSwitches._80STORE.isOn()) {
|
||||
astate += "80S_";
|
||||
astate += "80S";
|
||||
if (SoftSwitches.PAGE2.isOn()) {
|
||||
astate += "P2_";
|
||||
} else {
|
||||
astate += "P1_";
|
||||
astate += "2";
|
||||
}
|
||||
if (SoftSwitches.HIRES.isOn()) {
|
||||
astate += "H1_";
|
||||
} else {
|
||||
astate += "H0_";
|
||||
astate += "H";
|
||||
}
|
||||
}
|
||||
|
||||
// Handle zero-page bankswitching
|
||||
if (SoftSwitches.AUXZP.getState()) {
|
||||
astate += "Za_";
|
||||
astate += "Za";
|
||||
} else {
|
||||
astate += "Z0_";
|
||||
astate += "Z0";
|
||||
}
|
||||
return astate;
|
||||
}
|
||||
@@ -421,8 +417,7 @@ abstract public class RAM128k extends RAM {
|
||||
state = newState;
|
||||
|
||||
log("MMU Switches");
|
||||
// System.out.println("read: " + readConfiguration);
|
||||
// System.out.println("write: " + writeConfiguration);
|
||||
|
||||
if (memoryConfigurations.containsKey(readConfiguration)) {
|
||||
activeRead = memoryConfigurations.get(readConfiguration);
|
||||
} else {
|
||||
|
||||
@@ -74,20 +74,11 @@ public enum SoftSwitches {
|
||||
if (_80STORE.isOn()) {
|
||||
Emulator.withMemory(m->m.configureActiveMemory());
|
||||
} else {
|
||||
super.stateChanged();
|
||||
Emulator.withVideo(v->v.configureVideoMode());
|
||||
}
|
||||
}
|
||||
}),
|
||||
HIRES(new VideoSoftSwitch("Hires", 0x0c056, 0x0c057, 0x0c01d, RAMEvent.TYPE.ANY, false) {
|
||||
@Override
|
||||
public void stateChanged() {
|
||||
// PAGE2 is a hybrid switch; 80STORE ? memory : video
|
||||
if (_80STORE.isOn()) {
|
||||
Emulator.withMemory(m->m.configureActiveMemory());
|
||||
}
|
||||
super.stateChanged();
|
||||
}
|
||||
}),
|
||||
HIRES(new VideoSoftSwitch("Hires", 0x0c056, 0x0c057, 0x0c01d, RAMEvent.TYPE.ANY, false)),
|
||||
DHIRES(new VideoSoftSwitch("Double-hires", 0x0c05f, 0x0c05e, 0x0c07f, RAMEvent.TYPE.ANY, false)),
|
||||
PB0(new MemorySoftSwitch("Pushbutton0", -1, -1, 0x0c061, RAMEvent.TYPE.ANY, null)),
|
||||
PB1(new MemorySoftSwitch("Pushbutton1", -1, -1, 0x0c062, RAMEvent.TYPE.ANY, null)),
|
||||
|
||||
@@ -38,7 +38,7 @@ public class MemorySoftSwitch extends SoftSwitch {
|
||||
|
||||
@Override
|
||||
public void stateChanged() {
|
||||
// System.out.println(getName()+ " was switched to "+getState());
|
||||
// System.out.println(getName()+ " was switched to "+getState());
|
||||
Emulator.withMemory(m->m.configureActiveMemory());
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package jace.core;
|
||||
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
|
||||
/**
|
||||
* Card is an abstraction of an Apple ][ hardware module which can carry its own
|
||||
@@ -114,9 +113,6 @@ public abstract class Card extends TimedDevice {
|
||||
int baseIO = 0x0c080 + slot * 16;
|
||||
int baseRom = 0x0c000 + slot * 256;
|
||||
ioListener = getMemory().observe("Slot " + getSlot() + " " + getDeviceName() + " IO access", RAMEvent.TYPE.ANY, baseIO, baseIO + 15, (e) -> {
|
||||
if (e.getType() == TYPE.READ_FAKE) {
|
||||
return;
|
||||
}
|
||||
int address = e.getAddress() & 0x0f;
|
||||
handleIOAccess(address, e.getType(), e.getNewValue(), e);
|
||||
});
|
||||
@@ -125,10 +121,7 @@ public abstract class Card extends TimedDevice {
|
||||
getMemory().setActiveCard(slot);
|
||||
// Sather 6-4: Writes will still go through even when CXROM inhibits slot ROM
|
||||
if (SoftSwitches.CXROM.isOff() || !e.getType().isRead()) {
|
||||
if (e.getType() == TYPE.READ_FAKE) {
|
||||
return;
|
||||
}
|
||||
handleFirmwareAccess(e.getAddress() & 0x0ff, e.getType(), e.getNewValue(), e);
|
||||
handleFirmwareAccess(e.getAddress() & 0x0ff, e.getType(), e.getNewValue(), e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -65,11 +65,6 @@ public abstract class Device implements Reconfigurable {
|
||||
return _ram;
|
||||
}
|
||||
|
||||
// NOTE: This is for unit testing only, don't actually use this for anything else or expect things to be weird.
|
||||
public void setMemory(RAM ram) {
|
||||
_ram = ram;
|
||||
}
|
||||
|
||||
Device parentDevice = null;
|
||||
public Device getParent() {
|
||||
return parentDevice;
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.util.function.Consumer;
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.config.Reconfigurable;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
|
||||
/**
|
||||
* RAM is a 64K address space of paged memory. It also manages sets of memory
|
||||
@@ -165,14 +164,6 @@ public abstract class RAM implements Reconfigurable {
|
||||
return msb + lsb;
|
||||
}
|
||||
|
||||
// This is used by opcodes that wrap around page boundaries
|
||||
public int readWordPageWraparound(int address, TYPE eventType, boolean triggerEvent, boolean requireSynchronization) {
|
||||
int lsb = 0x00ff & read(address, eventType, triggerEvent, requireSynchronization);
|
||||
int addr1 = ((address + 1) & 0x0ff) | (address & 0x0ff00);
|
||||
int msb = (0x00ff & read(addr1, eventType, triggerEvent, requireSynchronization)) << 8;
|
||||
return msb + lsb;
|
||||
}
|
||||
|
||||
private synchronized void mapListener(RAMListener l, int address) {
|
||||
if ((address & 0x0FF00) == 0x0C000) {
|
||||
int index = address & 0x0FF;
|
||||
|
||||
@@ -40,7 +40,6 @@ public class RAMEvent {
|
||||
READ(true),
|
||||
READ_DATA(true),
|
||||
READ_OPERAND(true),
|
||||
READ_FAKE(true),
|
||||
EXECUTE(true),
|
||||
WRITE(false),
|
||||
ANY(false);
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
package jace.hardware;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -57,19 +55,6 @@ public class CardHayesMicromodem extends CardSSC {
|
||||
TRANS_IRQ_ENABLED = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRom() throws IOException {
|
||||
String path = "/jace/data/hayes-micromodem-8308a271.rom";
|
||||
// Load rom file, first 0x0FF bytes are CX rom, next 0x0800 bytes are C8 rom
|
||||
try (InputStream romFile = CardSSC.class.getResourceAsStream(path)) {
|
||||
final int cxRomLength = 0x0100;
|
||||
final int c8RomLength = 0x0800;
|
||||
byte[] rom8Data = new byte[c8RomLength];
|
||||
getC8Rom().loadData(rom8Data);
|
||||
getCxRom().loadData(Arrays.copyOf(rom8Data, cxRomLength));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientConnected() {
|
||||
setRingIndicator(true);
|
||||
@@ -120,6 +105,11 @@ public class CardHayesMicromodem extends CardSSC {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRom(String path) throws IOException {
|
||||
// Do nothing -- there is no rom for this card right now.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ringIndicator
|
||||
*/
|
||||
|
||||
@@ -162,10 +162,10 @@ public class CardMockingboard extends Card {
|
||||
if (e.getType().isRead()) {
|
||||
int val = controller.readRegister(register & 0x0f);
|
||||
e.setNewValue(val);
|
||||
// if (DEBUG) System.out.println("Chip " + chip + " Read "+Integer.toHexString(register & 0x0f)+" == "+val);
|
||||
if (DEBUG) System.out.println("Chip " + chip + " Read "+Integer.toHexString(register & 0x0f)+" == "+val);
|
||||
} else {
|
||||
controller.writeRegister(register & 0x0f, e.getNewValue());
|
||||
// if (DEBUG) System.out.println("Chip " + chip + " Write "+Integer.toHexString(register & 0x0f)+" == "+e.getNewValue());
|
||||
if (DEBUG) System.out.println("Chip " + chip + " Write "+Integer.toHexString(register & 0x0f)+" == "+e.getNewValue());
|
||||
}
|
||||
// Any firmware access will reset the idle counter and wake up the card, this allows the timers to start running again
|
||||
// Games such as "Skyfox" use the timer to detect if the card is present.
|
||||
|
||||
@@ -110,7 +110,7 @@ public class CardSSC extends Card {
|
||||
@Override
|
||||
public void setSlot(int slot) {
|
||||
try {
|
||||
loadRom();
|
||||
loadRom("/jace/data/SSC.rom");
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardSSC.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
@@ -173,25 +173,22 @@ public class CardSSC extends Card {
|
||||
System.out.println("Client disconnected");
|
||||
}
|
||||
|
||||
public void loadRom() throws IOException {
|
||||
System.out.println("Loading SSC rom");
|
||||
String path = "/jace/data/SSC.rom";
|
||||
public void loadRom(String path) throws IOException {
|
||||
// Load rom file, first 0x0700 bytes are C8 rom, last 0x0100 bytes are CX rom
|
||||
// CF00-CFFF are unused by the SSC
|
||||
try (InputStream romFile = CardSSC.class.getResourceAsStream(path)) {
|
||||
final int cxRomLength = 0x0100;
|
||||
final int c8RomLength = 0x0700;
|
||||
byte[] romxData = new byte[cxRomLength];
|
||||
byte[] rom8Data = new byte[c8RomLength];
|
||||
if (romFile.read(rom8Data) != c8RomLength) {
|
||||
throw new IOException("Bad SSC rom size");
|
||||
}
|
||||
getC8Rom().loadData(rom8Data);
|
||||
if (romFile.read(romxData) != cxRomLength) {
|
||||
throw new IOException("Bad SSC rom size");
|
||||
}
|
||||
getCxRom().loadData(romxData);
|
||||
InputStream romFile = CardSSC.class.getResourceAsStream(path);
|
||||
final int cxRomLength = 0x0100;
|
||||
final int c8RomLength = 0x0700;
|
||||
byte[] romxData = new byte[cxRomLength];
|
||||
byte[] rom8Data = new byte[c8RomLength];
|
||||
if (romFile.read(rom8Data) != c8RomLength) {
|
||||
throw new IOException("Bad SSC rom size");
|
||||
}
|
||||
getC8Rom().loadData(rom8Data);
|
||||
if (romFile.read(romxData) != cxRomLength) {
|
||||
throw new IOException("Bad SSC rom size");
|
||||
}
|
||||
getCxRom().loadData(romxData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -336,8 +333,6 @@ public class CardSSC extends Card {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case READ_FAKE:
|
||||
return;
|
||||
}
|
||||
if (newValue > -1) {
|
||||
e.setNewValue(newValue);
|
||||
|
||||
@@ -75,9 +75,7 @@ public class EnvelopeGenerator extends TimedGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
int shape;
|
||||
public void setShape(int shape) {
|
||||
this.shape = shape & 15;
|
||||
oddEven = false;
|
||||
counter = 0;
|
||||
cont = (shape & 8) != 0;
|
||||
|
||||
@@ -23,8 +23,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import jace.hardware.CardMockingboard;
|
||||
|
||||
/**
|
||||
* Implementation of the AY sound PSG chip. This class manages register values
|
||||
* and mixes the channels together (in the update method) The work of
|
||||
@@ -227,32 +225,6 @@ public class PSG {
|
||||
case PortA, PortB -> {
|
||||
}
|
||||
}
|
||||
if (CardMockingboard.DEBUG) {
|
||||
debugStatus();
|
||||
}
|
||||
}
|
||||
|
||||
String lastStatus = "";
|
||||
public void debugStatus() {
|
||||
String status = String.format("b%02X: A %03X %s %01X | B %03X %s %01X | C %03X %s %01X | N %03X | E %01X %04X",
|
||||
baseReg,
|
||||
channels.get(0).period,
|
||||
(channels.get(0).active ? "T" : "_") + (channels.get(0).noiseActive ? "N" : "_"),
|
||||
channels.get(0).amplitude,
|
||||
channels.get(1).period,
|
||||
(channels.get(1).active ? "T" : "_") + (channels.get(1).noiseActive ? "N" : "_"),
|
||||
channels.get(1).amplitude,
|
||||
channels.get(2).period,
|
||||
(channels.get(2).active ? "T" : "_") + (channels.get(2).noiseActive ? "N" : "_"),
|
||||
channels.get(2).amplitude,
|
||||
noiseGenerator.period,
|
||||
envelopeGenerator.shape,
|
||||
envelopeGenerator.period
|
||||
);
|
||||
if (!lastStatus.equals(status)) {
|
||||
System.out.println(status);
|
||||
lastStatus = status;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(AtomicInteger bufA, boolean clearA, AtomicInteger bufB, boolean clearB, AtomicInteger bufC, boolean clearC) {
|
||||
|
||||
@@ -51,7 +51,7 @@ public class TimedGenerator {
|
||||
}
|
||||
|
||||
public void setPeriod(int _period) {
|
||||
period = _period;
|
||||
period = _period > 0 ? _period : 1;
|
||||
clocksPerPeriod = (period * stepsPerCycle());
|
||||
// set counter back... necessary?
|
||||
// while (clocksPerPeriod > period) {
|
||||
@@ -60,10 +60,6 @@ public class TimedGenerator {
|
||||
}
|
||||
|
||||
protected int updateCounter() {
|
||||
// Period == 0 means the generator is off
|
||||
if (period <= 1 || clocksPerPeriod <= 1) {
|
||||
return 0;
|
||||
}
|
||||
counter += cyclesPerSample;
|
||||
int numStateChanges = 0;
|
||||
while (counter >= clocksPerPeriod) {
|
||||
@@ -75,6 +71,6 @@ public class TimedGenerator {
|
||||
|
||||
public void reset() {
|
||||
counter = 0;
|
||||
period = 0;
|
||||
period = 1;
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,12 @@ import static org.lwjgl.openal.AL10.AL_NO_ERROR;
|
||||
import static org.lwjgl.openal.AL10.alGetError;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.sound.sampled.Mixer;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import static org.lwjgl.openal.AL11.*;
|
||||
import static org.lwjgl.openal.EXTEfx.*;
|
||||
import org.lwjgl.openal.EXTEfx;
|
||||
|
||||
import jace.core.SoundMixer;
|
||||
import jace.core.SoundMixer.SoundBuffer;
|
||||
@@ -69,7 +64,7 @@ public class Votrax extends TimedDevice {
|
||||
// 1b: UH3 (nUt)
|
||||
// 1c: ER (bIRd)
|
||||
// 1d: R (Roof)
|
||||
// 1e: R01 (Rug)
|
||||
// 1e: R1 (Rug)
|
||||
// 1f: R2 (mutteR -- German)
|
||||
// 20: L (Lift)
|
||||
// 21: L1 (pLay)
|
||||
@@ -136,17 +131,43 @@ public class Votrax extends TimedDevice {
|
||||
this.sampleCounter = 0.0;
|
||||
this.samplesPerClock = clockFrequency / sampleRate;
|
||||
}
|
||||
public abstract int getBufferDuration();
|
||||
public Optional<Double> tick() {
|
||||
sampleCounter += samplesPerClock;
|
||||
if (sampleCounter >= 1.0) {
|
||||
sampleCounter -= 1.0;
|
||||
|
||||
public void fillBuffer(ShortBuffer buffer) {
|
||||
for (int i = 0; i < getBufferDuration(); i++) {
|
||||
buffer.put((short) (doGenerate() * 32767));
|
||||
return Optional.of(doGenerate());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract double doGenerate();
|
||||
}
|
||||
|
||||
public static class Mixer extends Generator {
|
||||
public List<Generator> inputs = new ArrayList<>();
|
||||
public List<Double> gains = new ArrayList<>();
|
||||
double volume=0.0;
|
||||
|
||||
public void addInput(Generator input) {
|
||||
inputs.add(input);
|
||||
gains.add(1.0);
|
||||
}
|
||||
|
||||
public void setGain(int index, double gain) {
|
||||
gains.set(index, gain);
|
||||
}
|
||||
|
||||
public double doGenerate() {
|
||||
double sample = 0.0;
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
sample += inputs.get(i).doGenerate() * gains.get(i);
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SawGenerator extends Generator {
|
||||
double pitch=440.0;
|
||||
double sample = 0.0;
|
||||
@@ -165,12 +186,6 @@ public class Votrax extends TimedDevice {
|
||||
changePerSample = direction * 2.0 * pitch / sampleRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
// Let's generate 10 loops of the sawtooth wave
|
||||
public int getBufferDuration() {
|
||||
return (int) (10.0 * sampleRate / pitch);
|
||||
}
|
||||
|
||||
public double doGenerate() {
|
||||
sample += changePerSample;
|
||||
if (sample < -1.0) {
|
||||
@@ -187,51 +202,29 @@ public class Votrax extends TimedDevice {
|
||||
public double doGenerate() {
|
||||
return Math.random() * 2.0 - 1.0;
|
||||
}
|
||||
@Override
|
||||
// Let's generate 10 seconds of noise
|
||||
public int getBufferDuration() {
|
||||
return sampleRate * 10;
|
||||
}
|
||||
}
|
||||
|
||||
public float mixerGain = 32767.0f;
|
||||
public int[] filters = new int[5];
|
||||
public SawGenerator formantGenerator = new SawGenerator();
|
||||
public NoiseGenerator noiseGenerator = new NoiseGenerator();
|
||||
public Mixer mixer = new Mixer();
|
||||
public static int FORMANT = 0;
|
||||
public static int NOISE = 1;
|
||||
// private Thread playbackThread = null;
|
||||
private Thread playbackThread = null;
|
||||
|
||||
public Votrax() throws Exception {
|
||||
// loadPhonemeData();
|
||||
formantGenerator.setSampleRate(44100);
|
||||
formantGenerator.setPitch(100);
|
||||
noiseGenerator.setSampleRate(44100);
|
||||
mixer.addInput(formantGenerator);
|
||||
mixer.addInput(noiseGenerator);
|
||||
mixer.setGain(FORMANT, 0.5);
|
||||
mixer.setGain(NOISE, 0.1);
|
||||
}
|
||||
|
||||
int formantSource = -1;
|
||||
int noiseSource = -1;
|
||||
|
||||
public void resume() {
|
||||
// Create a buffer for the sawtooth wave
|
||||
ShortBuffer formantLoop = BufferUtils.createShortBuffer(formantGenerator.getBufferDuration());
|
||||
// Create a buffer for the noise
|
||||
ShortBuffer noiseLoop = BufferUtils.createShortBuffer(noiseGenerator.getBufferDuration());
|
||||
// Fill the buffers
|
||||
formantGenerator.fillBuffer(formantLoop);
|
||||
noiseGenerator.fillBuffer(noiseLoop);
|
||||
|
||||
// Create a source for the formant generator
|
||||
formantSource = alGenSources();
|
||||
alSourcei(formantSource, AL_BUFFER, formantLoop.get(0));
|
||||
alSourcei(formantSource, AL_LOOPING, AL_TRUE);
|
||||
alSourcePlay(formantSource);
|
||||
// Create a source for the noise generator
|
||||
noiseSource = alGenSources();
|
||||
alSourcei(noiseSource, AL_BUFFER, noiseLoop.get(0));
|
||||
alSourcei(noiseSource, AL_LOOPING, AL_TRUE);
|
||||
alSourcePlay(noiseSource);
|
||||
|
||||
public void resume() {
|
||||
try {
|
||||
createFilters();
|
||||
} catch (Exception e) {
|
||||
@@ -239,33 +232,33 @@ public class Votrax extends TimedDevice {
|
||||
suspend();
|
||||
}
|
||||
super.resume();
|
||||
// if (playbackThread != null && !playbackThread.isAlive()) {
|
||||
// return;
|
||||
// }
|
||||
// playbackThread = new Thread(() -> {
|
||||
// SoundBuffer soundBuffer = null;
|
||||
// try {
|
||||
// soundBuffer = SoundMixer.createBuffer(false);
|
||||
// while (isRunning()) {
|
||||
// try {
|
||||
// soundBuffer.playSample((short) (mixer.doGenerate() * mixerGain));
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// suspend();
|
||||
// }
|
||||
// }
|
||||
// } catch (InterruptedException | ExecutionException | SoundError e) {
|
||||
// e.printStackTrace();
|
||||
// suspend();
|
||||
// } finally {
|
||||
// try {
|
||||
// soundBuffer.shutdown();
|
||||
// } catch (InterruptedException | ExecutionException | SoundError e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// playbackThread.start();
|
||||
if (playbackThread != null && !playbackThread.isAlive()) {
|
||||
return;
|
||||
}
|
||||
playbackThread = new Thread(() -> {
|
||||
SoundBuffer soundBuffer = null;
|
||||
try {
|
||||
soundBuffer = SoundMixer.createBuffer(false);
|
||||
while (isRunning()) {
|
||||
try {
|
||||
soundBuffer.playSample((short) (mixer.doGenerate() * mixerGain));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
suspend();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException | SoundError e) {
|
||||
e.printStackTrace();
|
||||
suspend();
|
||||
} finally {
|
||||
try {
|
||||
soundBuffer.shutdown();
|
||||
} catch (InterruptedException | ExecutionException | SoundError e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
playbackThread.start();
|
||||
}
|
||||
|
||||
private void createFilters() throws Exception {
|
||||
@@ -276,22 +269,19 @@ public class Votrax extends TimedDevice {
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
alGetError();
|
||||
filters[i] = alGenFilters();
|
||||
filters[i] = EXTEfx.alGenFilters();
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
throw new Exception("Failed to create filter " + i);
|
||||
}
|
||||
if (alIsFilter(filters[i])) {
|
||||
if (EXTEfx.alIsFilter(filters[i])) {
|
||||
// Set Filter type to Band-Pass and set parameters
|
||||
alFilteri(filters[i], AL_FILTER_TYPE, AL_FILTER_BANDPASS);
|
||||
EXTEfx.alFilteri(filters[i], EXTEfx.AL_FILTER_TYPE, EXTEfx.AL_FILTER_BANDPASS);
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
System.out.println("Band pass filter not supported.");
|
||||
} else {
|
||||
alFilterf(filters[i], AL_BANDPASS_GAIN, 0.5f);
|
||||
alFilterf(filters[i], AL_BANDPASS_GAINHF, 0.5f);
|
||||
EXTEfx.alFilterf(filters[i], EXTEfx.AL_BANDPASS_GAIN, 0.5f);
|
||||
EXTEfx.alFilterf(filters[i], EXTEfx.AL_BANDPASS_GAINHF, 0.5f);
|
||||
System.out.println("Band pass filter "+i+" created.");
|
||||
// Now add an aux send for the noise and formant sources to go to this filter
|
||||
// Inspiration: https://github.com/LWJGL/lwjgl3/blob/master/modules/samples/src/test/java/org/lwjgl/demo/openal/EFXTest.java
|
||||
alSource3i(formantSource, AL_AUXILIARY_SEND_FILTER, filters[i], 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,14 +290,14 @@ public class Votrax extends TimedDevice {
|
||||
public boolean suspend() {
|
||||
destroyFilters();
|
||||
|
||||
// playbackThread = null;
|
||||
playbackThread = null;
|
||||
return super.suspend();
|
||||
}
|
||||
|
||||
private void destroyFilters() {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (alIsFilter(filters[i])) {
|
||||
alDeleteFilters(filters[i]);
|
||||
if (EXTEfx.alIsFilter(filters[i])) {
|
||||
EXTEfx.alDeleteFilters(filters[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ module jace {
|
||||
requires org.lwjgl.openal;
|
||||
requires org.lwjgl.stb;
|
||||
requires org.lwjgl.glfw;
|
||||
requires org.lwjgl;
|
||||
|
||||
// requires org.reflections;
|
||||
|
||||
|
||||
@@ -571,18 +571,10 @@
|
||||
"name":"javafx.geometry.NodeOrientation",
|
||||
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"javafx.geometry.HPos",
|
||||
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"javafx.geometry.Pos",
|
||||
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"javafx.geometry.VPos",
|
||||
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
|
||||
},
|
||||
{
|
||||
"name":"javafx.scene.Camera"
|
||||
},
|
||||
@@ -860,65 +852,15 @@
|
||||
],
|
||||
"queriedMethods":[{"name":"getAlignment","parameterTypes":["javafx.scene.Node"] }]
|
||||
},
|
||||
{
|
||||
"name":"javafx.scene.layout.ColumnConstraints",
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicConstructors":true,
|
||||
"methods":[
|
||||
{"name":"<init>","parameterTypes":[] },
|
||||
{"name":"setHalignment","parameterTypes":["javafx.geometry.HPos"] },
|
||||
{"name":"setHgrow","parameterTypes":["javafx.scene.layout.Priority"] },
|
||||
{"name":"setMinWidth","parameterTypes":["double"] },
|
||||
{"name":"setMaxWidth","parameterTypes":["double"] },
|
||||
{"name":"setPrefWidth","parameterTypes":["double"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"javafx.scene.layout.RowConstraints",
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicConstructors":true,
|
||||
"methods":[
|
||||
{"name":"<init>","parameterTypes":[] },
|
||||
{"name":"setValignment","parameterTypes":["javafx.geometry.VPos"] },
|
||||
{"name":"setVgrow","parameterTypes":["javafx.scene.layout.Priority"] },
|
||||
{"name":"setMinHeight","parameterTypes":["double"] },
|
||||
{"name":"setMaxHeight","parameterTypes":["double"] },
|
||||
{"name":"setPrefHeight","parameterTypes":["double"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"javafx.scene.layout.GridPane",
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicConstructors":true,
|
||||
"methods":[
|
||||
{"name":"<init>","parameterTypes":[] },
|
||||
{"name":"getColumnConstraints","parameterTypes":[] },
|
||||
{"name":"getRowConstraints","parameterTypes":[] },
|
||||
{"name":"setAlignment","parameterTypes":["javafx.geometry.Pos"] },
|
||||
{"name":"setAlignment","parameterTypes":["javafx.scene.Node","javafx.geometry.Pos"] },
|
||||
{"name":"setColumnIndex","parameterTypes":["javafx.scene.Node","java.lang.Integer"] },
|
||||
{"name":"setRowIndex","parameterTypes":["javafx.scene.Node","java.lang.Integer"] },
|
||||
{"name":"setColumnSpan","parameterTypes":["javafx.scene.Node","java.lang.Integer"] },
|
||||
{"name":"setRowSpan","parameterTypes":["javafx.scene.Node","java.lang.Integer"] },
|
||||
{"name":"setHalignment","parameterTypes":["javafx.scene.Node","javafx.geometry.HPos"] },
|
||||
{"name":"setValignment","parameterTypes":["javafx.scene.Node","javafx.geometry.VPos"] },
|
||||
{"name":"setMargin","parameterTypes":["javafx.scene.Node","javafx.geometry.Insets"] }
|
||||
],
|
||||
"queriedMethods":[
|
||||
{"name":"getAlignment","parameterTypes":["javafx.scene.Node"] },
|
||||
{"name":"getColumnIndex","parameterTypes":["javafx.scene.Node"] },
|
||||
{"name":"getRowIndex","parameterTypes":["javafx.scene.Node"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"javafx.scene.layout.HBox",
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicConstructors":true,
|
||||
"methods":[
|
||||
{"name":"<init>","parameterTypes":[] },
|
||||
{"name":"setAlignment","parameterTypes":["javafx.geometry.Pos"] },
|
||||
{"name":"setFillHeight","parameterTypes":["boolean"] },
|
||||
{"name":"setHgrow","parameterTypes":["javafx.scene.Node","javafx.scene.layout.Priority"] },
|
||||
{"name":"<init>","parameterTypes":[] },
|
||||
{"name":"setAlignment","parameterTypes":["javafx.geometry.Pos"] },
|
||||
{"name":"setFillHeight","parameterTypes":["boolean"] },
|
||||
{"name":"setHgrow","parameterTypes":["javafx.scene.Node","javafx.scene.layout.Priority"] },
|
||||
{"name":"setMargin","parameterTypes":["javafx.scene.Node","javafx.geometry.Insets"] }
|
||||
],
|
||||
"queriedMethods":[{"name":"getHgrow","parameterTypes":["javafx.scene.Node"] }]
|
||||
@@ -963,13 +905,11 @@
|
||||
"queryAllDeclaredMethods":true,
|
||||
"queryAllPublicConstructors":true,
|
||||
"methods":[
|
||||
{"name":"<init>","parameterTypes":[] },
|
||||
{"name":"setAlignment","parameterTypes":["javafx.geometry.Pos"] },
|
||||
{"name":"setAlignment","parameterTypes":["javafx.scene.Node","javafx.geometry.Pos"] },
|
||||
{"name":"setHgap","parameterTypes":["double"] },
|
||||
{"name":"setVgap","parameterTypes":["double"] },
|
||||
{"name":"setPrefColumns","parameterTypes":["int"] },
|
||||
{"name":"setPrefRows","parameterTypes":["int"] }
|
||||
{"name":"<init>","parameterTypes":[] },
|
||||
{"name":"setAlignment","parameterTypes":["javafx.geometry.Pos"] },
|
||||
{"name":"setAlignment","parameterTypes":["javafx.scene.Node","javafx.geometry.Pos"] },
|
||||
{"name":"setHgap","parameterTypes":["double"] },
|
||||
{"name":"setVgap","parameterTypes":["double"] }
|
||||
],
|
||||
"queriedMethods":[{"name":"getAlignment","parameterTypes":["javafx.scene.Node"] }]
|
||||
},
|
||||
|
||||
Binary file not shown.
@@ -8,6 +8,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jace.apple2e.Full65C02Test;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.core.Computer;
|
||||
|
||||
|
||||
@@ -15,16 +15,9 @@
|
||||
*/
|
||||
package jace;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.core.CPU;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Device;
|
||||
import jace.core.PagedMemory;
|
||||
import jace.core.RAM;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.Utility;
|
||||
import jace.ide.HeadlessProgram;
|
||||
import jace.ide.Program;
|
||||
@@ -43,89 +36,6 @@ public class TestUtils {
|
||||
Emulator.withComputer(Computer::reconfigure);
|
||||
}
|
||||
|
||||
public static class FakeRAM extends RAM128k {
|
||||
PagedMemory fakeMemory = new PagedMemory(0x0, PagedMemory.Type.RAM);
|
||||
byte[] memory = new byte[65536];
|
||||
public byte read(int address, TYPE eventType, boolean triggerEvent, boolean requireSyncronization) {
|
||||
return memory[address & 0x0ffff];
|
||||
}
|
||||
public byte readRaw(int address) {
|
||||
return memory[address & 0x0ffff];
|
||||
}
|
||||
|
||||
public void write(int address, byte value, boolean triggerEvent, boolean requireSyncronization) {
|
||||
memory[address & 0x0ffff] = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Fake ram";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "ram";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureActiveMemory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadRom(String path) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performExtendedCommand(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getState() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetState() {
|
||||
}
|
||||
@Override
|
||||
public PagedMemory getAuxVideoMemory() {
|
||||
return fakeMemory;
|
||||
}
|
||||
@Override
|
||||
public PagedMemory getAuxMemory() {
|
||||
return fakeMemory;
|
||||
}
|
||||
@Override
|
||||
public PagedMemory getAuxLanguageCard() {
|
||||
return fakeMemory;
|
||||
}
|
||||
@Override
|
||||
public PagedMemory getAuxLanguageCard2() {
|
||||
return fakeMemory;
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearFakeRam(RAM ram) {
|
||||
Arrays.fill(((FakeRAM) ram).memory, (byte) 0);
|
||||
}
|
||||
|
||||
public static RAM initFakeRam() {
|
||||
RAM ram = new FakeRAM();
|
||||
Emulator.withComputer(c -> {
|
||||
c.setMemory(ram);
|
||||
c.getCpu().setMemory(ram);
|
||||
});
|
||||
return ram;
|
||||
}
|
||||
|
||||
public static void assemble(String code, int addr) throws Exception {
|
||||
runAssemblyCode(code, addr, 0);
|
||||
}
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
package jace.apple2e;
|
||||
import static jace.TestUtils.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import jace.core.SoundMixer;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
|
||||
public class CpuUnitTest {
|
||||
// This will loop through each of the files in 65x02_unit_tests/wdc65c02 and run the tests in each file
|
||||
// The goal is to produce an output report that shows the number of tests that passed and failed
|
||||
// The output should be reported in a format compatible with junit but also capture multiple potential failures, not just the first faliure
|
||||
|
||||
static Computer computer;
|
||||
static MOS65C02 cpu;
|
||||
static RAM ram;
|
||||
|
||||
public static enum Operation {
|
||||
read, write
|
||||
}
|
||||
TypeToken<Collection<TestRecord>> testCollectionType = new TypeToken<Collection<TestRecord>>(){};
|
||||
record TestResult(String source, String testName, boolean passed, String message) {}
|
||||
// Note cycles are a mix of int and string so the parser doesn't like to serialize that into well-formed objects
|
||||
record TestRecord(String name, @SerializedName("initial") MachineState initialState, @SerializedName("final") MachineState finalState, List<List<String>> cycles) {}
|
||||
record MachineState(int pc, int s, int a, int x, int y, byte p, List<int[]> ram) {}
|
||||
|
||||
public static boolean BREAK_ON_FAIL = false;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
initComputer();
|
||||
SoundMixer.MUTE = true;
|
||||
computer = Emulator.withComputer(c->c, null);
|
||||
cpu = (MOS65C02) computer.getCpu();
|
||||
ram = initFakeRam();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void resetState() {
|
||||
// Reinit memory on each test to avoid weird side effects
|
||||
cpu.reset();
|
||||
cpu.resume();
|
||||
}
|
||||
|
||||
// Make a list of tests to skip
|
||||
public static String[] SKIP_TESTS = new String[] {
|
||||
"cb", "db"
|
||||
};
|
||||
|
||||
public static String TEST_FOLDER = "/65x02_unit_tests/wdc65c02/v1";
|
||||
@Test
|
||||
public void testAll() throws IOException, URISyntaxException {
|
||||
// Read all the files in the directory
|
||||
// For each file, read the contents and run the tests
|
||||
|
||||
List<TestResult> results = new ArrayList<>();
|
||||
// Path testFolder = Paths.get(getClass().getResource("/65x02_unit_tests_wdc65c02/v1").toURI());
|
||||
for (String path : getSorted(getResourceListing(TEST_FOLDER))) {
|
||||
boolean skip = false;
|
||||
for (String skipPattern : SKIP_TESTS) {
|
||||
if (path.contains(skipPattern)) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String file = TEST_FOLDER + "/" + path;
|
||||
results.addAll(runTest(file));
|
||||
if (BREAK_ON_FAIL && results.stream().anyMatch(r->!r.passed())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Report results
|
||||
int passed = 0;
|
||||
Set<String> failedTests = new HashSet<>();
|
||||
for (TestResult result : results) {
|
||||
if (result.passed()) {
|
||||
passed++;
|
||||
} else {
|
||||
failedTests.add(result.testName());
|
||||
if (failedTests.size() < 20) {
|
||||
System.err.println(result.source() + ";" + result.testName() + " " + "FAILED" + ": " + result.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
System.err.println("Passed: " + passed + " Failed: " + failedTests.size());
|
||||
if (failedTests.size() > 0) {
|
||||
throw new RuntimeException("One or more tests failed, see log for details");
|
||||
}
|
||||
}
|
||||
|
||||
private String getStatusBits(int status) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append((status & 0x80) != 0 ? "N" : "-");
|
||||
sb.append((status & 0x40) != 0 ? "V" : "-");
|
||||
sb.append((status & 0x20) != 0 ? "-" : "?");
|
||||
sb.append((status & 0x10) != 0 ? "B" : "-");
|
||||
sb.append((status & 0x08) != 0 ? "D" : "-");
|
||||
sb.append((status & 0x04) != 0 ? "I" : "-");
|
||||
sb.append((status & 0x02) != 0 ? "Z" : "-");
|
||||
sb.append((status & 0x01) != 0 ? "C" : "-");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private Collection<? extends TestResult> runTest(String file) {
|
||||
Gson gson = new Gson();
|
||||
List<TestResult> results = new ArrayList<>();
|
||||
// Read the file which is a JSON file and parse it.
|
||||
try {
|
||||
// Given the JSON data in source, parse it to a usable list of tests
|
||||
// For each test, run the test
|
||||
Collection<TestRecord> tests = gson.fromJson(new InputStreamReader(getClass().getResourceAsStream(file)), testCollectionType.getType());
|
||||
for (TestRecord t : tests) {
|
||||
String name = t.name() + "_%d cycles_%04X->%04X".formatted(t.cycles().size(), t.initialState().pc(), t.finalState().pc());
|
||||
|
||||
// Set up the initial state by setting CPU registers and RAM
|
||||
cpu.reset();
|
||||
cpu.setProgramCounter(t.initialState().pc());
|
||||
cpu.STACK = t.initialState().s();
|
||||
cpu.A = t.initialState().a();
|
||||
cpu.X = t.initialState().x();
|
||||
cpu.Y = t.initialState().y();
|
||||
cpu.setStatus(t.initialState().p(), true);
|
||||
// Set up the memory values
|
||||
for (int[] mem : t.initialState().ram()) {
|
||||
ram.write(mem[0], (byte) mem[1], false, false);
|
||||
}
|
||||
// Step the CPU for each cycle
|
||||
for (@SuppressWarnings("unused") List<String> c : t.cycles()) {
|
||||
if (BREAK_ON_FAIL) {
|
||||
cpu.traceLength = 100;
|
||||
cpu.setTraceEnabled(true);
|
||||
}
|
||||
cpu.doTick();
|
||||
}
|
||||
// Check the final state
|
||||
boolean passed = true;
|
||||
if (cpu.getProgramCounter() != t.finalState().pc()) {
|
||||
results.add(new TestResult(file.toString(), name, false, "Program Counter mismatch, expected %04X but got %04X".formatted(t.finalState().pc(), cpu.getProgramCounter())));
|
||||
passed = false;
|
||||
}
|
||||
if (cpu.STACK != t.finalState().s()) {
|
||||
results.add(new TestResult(file.toString(), name, false, "Stack Pointer mismatch, expected %02X but got %02X".formatted(t.finalState().s(), cpu.STACK)));
|
||||
passed = false;
|
||||
}
|
||||
if (cpu.A != t.finalState().a()) {
|
||||
results.add(new TestResult(file.toString(), name, false, "Accumulator mismatch, expected %02X but got %02X".formatted(t.finalState().a(), cpu.A)));
|
||||
passed = false;
|
||||
}
|
||||
if (cpu.X != t.finalState().x()) {
|
||||
results.add(new TestResult(file.toString(), name, false, "X Register mismatch, expected %02X but got %02X".formatted(t.finalState().x(), cpu.X)));
|
||||
passed = false;
|
||||
}
|
||||
if (cpu.Y != t.finalState().y()) {
|
||||
results.add(new TestResult(file.toString(), name, false, "Y Register mismatch, expected %02X but got %02X".formatted(t.finalState().y(), cpu.Y)));
|
||||
passed = false;
|
||||
}
|
||||
if (cpu.getStatus() != t.finalState().p()) {
|
||||
results.add(new TestResult(file.toString(), name, false, "Status Register mismatch, expected %s but got %s".formatted(getStatusBits(t.finalState().p()),getStatusBits(cpu.getStatus()))));
|
||||
passed = false;
|
||||
}
|
||||
// Check the memory values
|
||||
for (int[] mem : t.finalState().ram()) {
|
||||
byte value = ram.read(mem[0], TYPE.EXECUTE, false, false);
|
||||
if (value != (byte) mem[1]) {
|
||||
results.add(new TestResult(file.toString(), name, false, "Memory mismatch at address %04X, expected %02X but got %02X".formatted(mem[0], mem[1], value)));
|
||||
// results.add(new TestResult(file.toString(), name, false, "Memory mismatch, expected %s but got %s".formatted(getStatusBits(mem[1]),getStatusBits(value))));
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
if (passed) {
|
||||
results.add(new TestResult(file.toString(), t.name(), true, "All checks passed"));
|
||||
} else if (BREAK_ON_FAIL) {
|
||||
break;
|
||||
}
|
||||
// Clear out the memory for the next test
|
||||
for (int[] mem : t.finalState().ram()) {
|
||||
ram.write(mem[0], (byte) 0, false, false);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
results.add(new TestResult(file.toString(), "<INIT>", false, "Unable to read file: " + e.getMessage()));
|
||||
return results;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private String[] getSorted(String[] values) {
|
||||
Set<String> set = new TreeSet<>();
|
||||
for (String value : values) {
|
||||
set.add(value);
|
||||
}
|
||||
return set.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private String[] getResourceListing(String path) throws URISyntaxException, IOException {
|
||||
URL dirURL = getClass().getResource(path);
|
||||
if (dirURL != null && dirURL.getProtocol().equals("file")) {
|
||||
/* A file path: easy enough */
|
||||
return new File(dirURL.toURI()).list();
|
||||
}
|
||||
|
||||
if (dirURL == null) {
|
||||
/*
|
||||
* In case of a jar file, we can't actually find a directory.
|
||||
* Have to assume the same jar as clazz.
|
||||
*/
|
||||
String me = getClass().getName().replace(".", "/")+".class";
|
||||
dirURL = getClass().getClassLoader().getResource(me);
|
||||
}
|
||||
|
||||
if (dirURL.getProtocol().equals("jar")) {
|
||||
/* A JAR path */
|
||||
String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); //strip out only the JAR file
|
||||
try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"))) {
|
||||
Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
|
||||
Set<String> result = new HashSet<String>(); //avoid duplicates in case it is a subdirectory
|
||||
while(entries.hasMoreElements()) {
|
||||
String name = entries.nextElement().getName();
|
||||
if (name.startsWith(path)) { //filter according to the path
|
||||
String entry = name.substring(path.length());
|
||||
int checkSubdir = entry.indexOf("/");
|
||||
if (checkSubdir >= 0) {
|
||||
// if it is a subdirectory, we just return the directory name
|
||||
entry = entry.substring(0, checkSubdir);
|
||||
}
|
||||
result.add(entry);
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
}
|
||||
throw new IOException("Unable to locate resource folder for path: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,18 +400,18 @@ public class Full65C02Test {
|
||||
new TestProgram()
|
||||
.add("LDA #$FF")
|
||||
.add("STA $FF")
|
||||
.assertTimed("BIT #0", 3)
|
||||
.assertFlags(IS_ZERO, NEGATIVE, OVERFLOW_CLEAR)
|
||||
.assertTimed("BIT #$FF", 3)
|
||||
.assertTimed("BIT #0", 2)
|
||||
.assertFlags(IS_ZERO, POSITIVE, OVERFLOW_CLEAR)
|
||||
.assertTimed("BIT #$FF", 2)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, OVERFLOW_CLEAR)
|
||||
.assertTimed("BIT $FF", 3)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, OVERFLOW_SET)
|
||||
.add("CLV")
|
||||
.add("LDA #$40")
|
||||
.assertTimed("BIT #$40", 3)
|
||||
.assertTimed("BIT #$40", 2)
|
||||
.assertFlags(NOT_ZERO, POSITIVE, OVERFLOW_CLEAR)
|
||||
.assertTimed("BIT #$80", 3)
|
||||
.assertFlags(IS_ZERO, POSITIVE, OVERFLOW_CLEAR)
|
||||
.assertTimed("BIT #$80", 2)
|
||||
.assertFlags(IS_ZERO, NEGATIVE, OVERFLOW_CLEAR)
|
||||
.assertTimed("BIT $1000", 4)
|
||||
.assertTimed("BIT $1000,x", 4)
|
||||
.assertTimed("BIT $00,X", 4)
|
||||
|
||||
6257
src/test/java/jace/apple2e/Functional65C02Test.java
Normal file
6257
src/test/java/jace/apple2e/Functional65C02Test.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,6 @@ import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -37,7 +36,6 @@ import jace.TestProgram;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
|
||||
/**
|
||||
* Test that memory listeners fire appropriately.
|
||||
@@ -70,13 +68,9 @@ public class MemoryTest {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void resetEmulator() {
|
||||
public void setup() {
|
||||
computer.pause();
|
||||
cpu.clearState();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void resetSoftSwitches() {
|
||||
// Reset softswitches
|
||||
for (SoftSwitches softswitch : SoftSwitches.values()) {
|
||||
softswitch.getSwitch().reset();
|
||||
@@ -200,7 +194,7 @@ public class MemoryTest {
|
||||
* @throws ProgramException
|
||||
*/
|
||||
@Test
|
||||
public void machineIdentificationTest() throws ProgramException {
|
||||
public void machineIdentificationTEst() throws ProgramException {
|
||||
TestProgram memoryDetectTestProgram = new TestProgram(MEMORY_TEST_COMMONS);
|
||||
memoryDetectTestProgram.add(MACHINE_IDENTIFICATION);
|
||||
// Assert this is an Apple //e
|
||||
@@ -235,6 +229,7 @@ public class MemoryTest {
|
||||
sta $FE1F ; FE1F is $60 in Apple II/plus/e/enhanced
|
||||
cmp $FE1F
|
||||
""")
|
||||
//
|
||||
.assertEquals("E0005: We tried to put the language card into read RAM, write RAM, but failed to write.")
|
||||
.add("""
|
||||
lda $C083 ; Read and write bank 2
|
||||
@@ -505,81 +500,6 @@ public class MemoryTest {
|
||||
!byte $23, $34, $11, $23, $34
|
||||
!byte 0
|
||||
""")
|
||||
// .runForTicks(10000000);
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void auxLanguageCardTest() throws ProgramException {
|
||||
// This is a repeat of the LC test but with AUX enabled
|
||||
SoftSwitches.AUXZP.getSwitch().setState(true);
|
||||
SoftSwitches.RAMRD.getSwitch().setState(true);
|
||||
SoftSwitches.RAMWRT.getSwitch().setState(true);
|
||||
SoftSwitches._80STORE.getSwitch().setState(true);
|
||||
languageCardBankswitchTest();
|
||||
}
|
||||
|
||||
public record MemoryTestCase(int[] softswitches, byte... expected) {}
|
||||
|
||||
int[] testLocations = {
|
||||
0x0FF, 0x100, 0x200, 0x3FF, 0x427, 0x7FF, 0x800, 0x1FFF,
|
||||
0x2000, 0x3FFF, 0x4000, 0x5FFF, 0xBFFF
|
||||
};
|
||||
private void assertMemoryTest(MemoryTestCase testCase) {
|
||||
// Set the values in memory in main and aux banks
|
||||
// This is done directly to ensure the values are exactly as expected
|
||||
// The next tests will try to read these values using the softswitches
|
||||
for (int location : testLocations) {
|
||||
((RAM128k) ram).getMainMemory().writeByte(location, (byte) 1);
|
||||
((RAM128k) ram).getAuxMemory().writeByte(location, (byte) 3);
|
||||
}
|
||||
resetSoftSwitches();
|
||||
|
||||
for (int softswitch : testCase.softswitches) {
|
||||
System.out.println("Setting softswitch " + Integer.toHexString(softswitch));
|
||||
ram.write(softswitch, (byte) 0, true, false);
|
||||
}
|
||||
for (int i=0; i < testLocations.length; i++) {
|
||||
int address = testLocations[i];
|
||||
byte current = ram.read(address, TYPE.READ_DATA, false, false);
|
||||
ram.write(address, (byte) (current+1), false, false);
|
||||
byte expected = testCase.expected[i];
|
||||
try {
|
||||
assertEquals("Unexpected value at " + Integer.toHexString(address), expected, ram.read(address, TYPE.READ_DATA, false, false));
|
||||
} catch (AssertionError err) {
|
||||
for (SoftSwitches softswitch : SoftSwitches.values()) {
|
||||
System.out.println(MessageFormat.format("{0}\t{1}", softswitch.name(), (softswitch.isOn() ? "on" : "off")));
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void auxBankSwitchTest() throws ProgramException {
|
||||
byte M1 = (byte) 1; // Main + no change
|
||||
byte M2 = (byte) 2; // Main + 1
|
||||
byte A1 = (byte) 3; // Aux + no change
|
||||
byte A2 = (byte) 4; // Aux + 1
|
||||
|
||||
// 80 STORE + RAMWRT + HIRES / Page 1 (Main mem)
|
||||
assertMemoryTest(new MemoryTestCase(new int[] {0x0C005, 0x0C001, 0x0C057},
|
||||
M2, M2, M1, M1, M2, M2, M1, M1, M2, M2, M1, M1, M1));
|
||||
|
||||
// RAMRD + AUXZP
|
||||
assertMemoryTest(new MemoryTestCase(new int[] {0xC003, 0xC009},
|
||||
A2, A2, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1));
|
||||
|
||||
// RAMRD + MAINZP
|
||||
assertMemoryTest(new MemoryTestCase(new int[] {0xC003, 0xC008},
|
||||
M2, M2, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1, A1));
|
||||
|
||||
// 80 STORE + HIRES' + Page 2
|
||||
assertMemoryTest(new MemoryTestCase(new int[] {0x0C001, 0x0C056, 0x0C055},
|
||||
M2, M2, M2, M2, A2, A2, M2, M2, M2, M2, M2, M2, M2));
|
||||
|
||||
// 80 STORE + HIRES + Page 2
|
||||
assertMemoryTest(new MemoryTestCase(new int[] {0x0C001, 0x0C057, 0x0C055},
|
||||
M2, M2, M2, M2, A2, A2, M2, M2, A2, A2, M2, M2, M2));
|
||||
.runForTicks(10000000);
|
||||
}
|
||||
}
|
||||
|
||||
Submodule src/test/resources/65x02_unit_tests deleted from 3ecec7e679
6108
src/test/resources/jace/klauss_functional_orig.asm
Normal file
6108
src/test/resources/jace/klauss_functional_orig.asm
Normal file
File diff suppressed because it is too large
Load Diff
@@ -188,18 +188,15 @@ RESETALL
|
||||
sta CSW
|
||||
lda #>COUT1
|
||||
sta CSW+1
|
||||
; Zelly's original test resets flags, but we might want to test with flags set.
|
||||
; So only reset softswitches that don't affect memory state.
|
||||
; Anyway, our @before setup function does this part already.
|
||||
;sta RESET_RAMRD
|
||||
;sta RESET_RAMWRT
|
||||
sta RESET_RAMRD
|
||||
sta RESET_RAMWRT
|
||||
;; Save return address in X and A, in case we switch zero-page memory.
|
||||
;sta RESET_80STORE
|
||||
sta RESET_80STORE
|
||||
sta RESET_INTCXROM
|
||||
;sta RESET_ALTZP
|
||||
sta RESET_ALTZP
|
||||
sta RESET_SLOTC3ROM
|
||||
sta RESET_INTC8ROM
|
||||
;sta RESET_80COL
|
||||
sta RESET_80COL
|
||||
sta RESET_ALTCHRSET
|
||||
sta SET_TEXT
|
||||
sta RESET_MIXED
|
||||
|
||||
Reference in New Issue
Block a user