mirror of
https://github.com/badvision/jace.git
synced 2024-11-23 23:32:59 +00:00
commit
f34ba40ff0
@ -31,6 +31,7 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
public class ConfigurationUIController {
|
||||
@ -221,9 +222,9 @@ public class ConfigurationUIController {
|
||||
return Optional.of(row);
|
||||
}
|
||||
|
||||
private void editKeyboardShortcut(ConfigNode node, String actionName, Text widget) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
// private void editKeyboardShortcut(ConfigNode node, String actionName, Text widget) {
|
||||
// throw new UnsupportedOperationException("Not supported yet.");
|
||||
// }
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private Node buildEditField(ConfigNode node, String settingName, Serializable value) {
|
||||
@ -247,13 +248,40 @@ public class ConfigurationUIController {
|
||||
return buildTextField(node, settingName, value, null);
|
||||
}
|
||||
} else if (type.equals(File.class)) {
|
||||
// TODO: Add file support!
|
||||
return buildFileField(node, settingName, value);
|
||||
} else if (ISelection.class.isAssignableFrom(type)) {
|
||||
return buildDynamicSelectComponent(node, settingName, value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// NOTE: This was written but not tested/used currently. Test before using!
|
||||
private Node buildFileField(ConfigNode node, String settingName, Serializable value) {
|
||||
// Create a label that shows the name of the file and lets you select a file when the label is clicked.
|
||||
HBox hbox = new HBox();
|
||||
Label label = new Label(value == null ? "" : ((File) value).getName());
|
||||
label.setMinWidth(150.0);
|
||||
label.getStyleClass().add("setting-file-label");
|
||||
label.setOnMouseClicked((e) -> {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
File file = fileChooser.showOpenDialog(label.getScene().getWindow());
|
||||
if (file != null) {
|
||||
node.setFieldValue(settingName, file);
|
||||
label.setText(file.getName());
|
||||
}
|
||||
});
|
||||
hbox.getChildren().add(label);
|
||||
// Add a button that lets you clear the file selection.
|
||||
Label clearButton = new Label("Clear");
|
||||
clearButton.getStyleClass().add("setting-file-clear");
|
||||
clearButton.setOnMouseClicked((e) -> {
|
||||
node.setFieldValue(settingName, null);
|
||||
label.setText("");
|
||||
});
|
||||
return hbox;
|
||||
|
||||
}
|
||||
|
||||
private Node buildTextField(ConfigNode node, String settingName, Serializable value, String validationPattern) {
|
||||
TextField widget = new TextField(String.valueOf(value));
|
||||
widget.textProperty().addListener((e) -> node.setFieldValue(settingName, widget.getText()));
|
||||
|
103
src/main/java/jace/hardware/Votrax.java
Normal file
103
src/main/java/jace/hardware/Votrax.java
Normal file
@ -0,0 +1,103 @@
|
||||
package jace.hardware;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class Votrax {
|
||||
// This is a speech synthesizer based on the Votrax SC-02
|
||||
// There are 2 sound generators, one for the voice and one for the noise
|
||||
// The voice generator is a saw-tooth wave generator at a frequency determined by the voice frequency register
|
||||
// The noise generator is a pseudo-random noise generator
|
||||
|
||||
// The Votrax has 5 filters that can be applied to the voice generator, controlled by a filter frequency register
|
||||
// The fifth filter takes both the voice and noise generators as input, but other filters only take the voice generator
|
||||
// There is also a final high-pass filter that can be applied to the output of the voice and noise generators
|
||||
|
||||
// There is a phoneme register which controls the phoneme to be spoken (0-63)
|
||||
// There is a duration register which controls the duration of the phoneme (0-3)
|
||||
// There is a rate register which controls the rate of speech (0-15)
|
||||
// There is an inflection register which controls the inflection of the voice (0-15)
|
||||
|
||||
// For each phoneme there are 8 bytes that control the filters and sound generator levels
|
||||
byte[] phonemeData = new byte[64 * 8];
|
||||
// Phoneme chart:
|
||||
// 00: PA (pause)
|
||||
// 01: E (mEEt)
|
||||
// 02: E1 (bEnt)
|
||||
// 03: Y (bEfore)
|
||||
// 04: Y1 (Year)
|
||||
// 05: AY (plEAse)
|
||||
// 06: IE (anY)
|
||||
// 07: I (sIx)
|
||||
// 08: A (mAde)
|
||||
// 09: A1 (cAre)
|
||||
// 0a: EH (nEst)
|
||||
// 0b: EH1 (bElt)
|
||||
// 0c: AE (dAd)
|
||||
// 0d: AE1 (After)
|
||||
// 0e: AH (gOt)
|
||||
// 0f: AH1 (fAther)
|
||||
// 10: AW (Office)
|
||||
// 11: O (stOre)
|
||||
// 12: OU (bOAt)
|
||||
// 13: OO (lOOk)
|
||||
// 14: IU (yOU)
|
||||
// 15: IU1 (cOUld)
|
||||
// 16: U (tUne)
|
||||
// 17: U1 (cartOOn)
|
||||
// 18: UH (wOnder)
|
||||
// 19: UH1 (lOve)
|
||||
// 1a: UH2 (whAt)
|
||||
// 1b: UH3 (nUt)
|
||||
// 1c: ER (bIRd)
|
||||
// 1d: R (Roof)
|
||||
// 1e: R1 (Rug)
|
||||
// 1f: R2 (mutteR -- German)
|
||||
// 20: L (Lift)
|
||||
// 21: L1 (pLay)
|
||||
// 22: LF (faLL)
|
||||
// 23: W (Water)
|
||||
// 24: B (Bag)
|
||||
// 25: D (paiD)
|
||||
// 26: KV (taG)
|
||||
// 27: P (Pen)
|
||||
// 28: T (Tart)
|
||||
// 29: K (Kit)
|
||||
// 2a: HV - Hold Vocal
|
||||
// 2b: HVC - Hold Vocal Closure
|
||||
// 2c: HF - (Heart)
|
||||
// 2d: HFC - Hold Frictave Closure
|
||||
// 2e: HN - Hold Nasal
|
||||
// 2f: Z (Zero)
|
||||
// 30: S (Same)
|
||||
// 31: J (meaSure)
|
||||
// 32: SCH (SHip)
|
||||
// 33: V (Very)
|
||||
// 34: F (Four)
|
||||
// 35: THV (THere)
|
||||
// 36: TH (wiTH)
|
||||
// 37: M (More)
|
||||
// 38: N (NiNe)
|
||||
// 39: NG (raNG)
|
||||
// 3a: :A (mAErchen -- German)
|
||||
// 3b: :OH (lOwe - French)
|
||||
// 3c: :U (fUEnf -- German)
|
||||
// 3d: :UH (menU -- French)
|
||||
// 3e: E2 (bittE -- German)
|
||||
// 3f: LB (Lube)
|
||||
|
||||
public void loadPhonemeData() {
|
||||
InputStream romFile = Votrax.class.getResourceAsStream("/jace/data/sc01a.bin");
|
||||
if (romFile == null) {
|
||||
throw new RuntimeException("Cannot find Votrax SC-01A ROM");
|
||||
}
|
||||
// Load into phonemeData
|
||||
try {
|
||||
if (romFile.read(phonemeData) != phonemeData.length) {
|
||||
throw new RuntimeException("Bad Votrax SC-01A ROM size");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Error loading Votrax SC-01A ROM", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -152,7 +152,6 @@ public class DirectoryNode extends DiskNode implements FileFilter {
|
||||
end = start + ENTRIES_PER_BLOCK;
|
||||
}
|
||||
for (int i = start; i < end && i < directoryEntries.size(); i++, offset += FILE_ENTRY_SIZE) {
|
||||
// TODO: Add any parts that are not file entries.
|
||||
// System.out.println("Entry "+i+": "+children.get(i).getName()+"; offset "+offset);
|
||||
generateFileEntry(buffer, offset, i);
|
||||
}
|
||||
|
BIN
src/main/resources/jace/data/sc01a.bin
Normal file
BIN
src/main/resources/jace/data/sc01a.bin
Normal file
Binary file not shown.
@ -13,4 +13,4 @@ public abstract class AbstractFXTest {
|
||||
Platform.startup(() -> {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
44
src/test/java/jace/ProgramException.java
Normal file
44
src/test/java/jace/ProgramException.java
Normal file
@ -0,0 +1,44 @@
|
||||
package jace;
|
||||
|
||||
import jace.apple2e.MOS65C02;
|
||||
|
||||
public class ProgramException extends Exception {
|
||||
int breakpointNumber;
|
||||
String processorStats;
|
||||
String programLocation;
|
||||
public ProgramException(String message, int breakpointNumber) {
|
||||
super(message.replaceAll("<<.*>>", ""));
|
||||
this.breakpointNumber = breakpointNumber;
|
||||
this.processorStats = Emulator.withComputer(c-> ((MOS65C02) c.getCpu()).getState(), "N/A");
|
||||
// Look for a string pattern <<programLocation>> in the message and extract if found
|
||||
int start = message.indexOf("<<");
|
||||
if (start != -1) {
|
||||
int end = message.indexOf(">>", start);
|
||||
if (end != -1) {
|
||||
this.programLocation = message.substring(start + 2, end);
|
||||
}
|
||||
} else {
|
||||
this.programLocation = "N/A";
|
||||
}
|
||||
}
|
||||
public int getBreakpointNumber() {
|
||||
return breakpointNumber;
|
||||
}
|
||||
public String getProcessorStats() {
|
||||
return processorStats;
|
||||
}
|
||||
public String getProgramLocation() {
|
||||
return programLocation;
|
||||
}
|
||||
public String getMessage() {
|
||||
String message = super.getMessage();
|
||||
if (getBreakpointNumber() >= 0) {
|
||||
message += " at breakpoint " + getBreakpointNumber();
|
||||
}
|
||||
message += " \nStats: " + getProcessorStats();
|
||||
if (getProgramLocation() != null) {
|
||||
message += " \n at " + getProgramLocation();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
465
src/test/java/jace/TestProgram.java
Normal file
465
src/test/java/jace/TestProgram.java
Normal file
@ -0,0 +1,465 @@
|
||||
package jace;
|
||||
|
||||
import static jace.TestUtils.runAssemblyCode;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
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;
|
||||
|
||||
public class TestProgram {
|
||||
// Tests could be run in any order so it is really important that all registers/flags are preserved!
|
||||
public static enum Flag {
|
||||
CARRY_SET("BCS +", "Carry should be set"),
|
||||
CARRY_CLEAR("BCC +", "Carry should be clear"),
|
||||
ZERO_SET("BEQ +", "Zero should be set"),
|
||||
IS_ZERO("BEQ +", "Zero should be clear"),
|
||||
ZERO_CLEAR("BNE +", "Zero should be clear"),
|
||||
NOT_ZERO("BNE +", "Zero should be clear"),
|
||||
NEGATIVE("BMI +", "Negative should be set"),
|
||||
POSITIVE("BPL +", "Negative should be clear"),
|
||||
OVERFLOW_SET("BVS +", "Overflow should be set"),
|
||||
OVERFLOW_CLEAR("BVC +", "Overflow should be clear"),
|
||||
DECIMAL_SET("""
|
||||
PHP
|
||||
PHA
|
||||
PHP
|
||||
PLA
|
||||
BIT #%00001000
|
||||
BEQ ++
|
||||
PLA
|
||||
PLP
|
||||
BRA +
|
||||
++ ; Error
|
||||
""", "Decimal should be set"),
|
||||
DECIMAL_CLEAR("""
|
||||
PHP
|
||||
PHA
|
||||
PHP
|
||||
PLA
|
||||
BIT #%00001000
|
||||
BNE ++
|
||||
PLA
|
||||
PLP
|
||||
BRA +
|
||||
++ ; Error
|
||||
""", "Decimal should be clear"),
|
||||
INTERRUPT_SET("""
|
||||
PHP
|
||||
PHA
|
||||
PHP
|
||||
PLA
|
||||
BIT #%00000100
|
||||
BEQ ++
|
||||
PLA
|
||||
PLP
|
||||
BRA +
|
||||
++ ; Error
|
||||
""", "Interrupt should be set"),
|
||||
INTERRUPT_CLEAR("""
|
||||
PHP
|
||||
PHA
|
||||
PHP
|
||||
PLA
|
||||
BIT #%00000100
|
||||
BNE ++
|
||||
PLA
|
||||
PLP
|
||||
BRA +
|
||||
++ ; Error
|
||||
""", "Interrupt should be clear"),;
|
||||
String code;
|
||||
String condition;
|
||||
Flag(String code, String condition) {
|
||||
this.code = code;
|
||||
this.condition = condition;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<String> lines = new ArrayList<>();
|
||||
Consumer<Byte> timerHandler;
|
||||
Consumer<Byte> tickCountHandler;
|
||||
Consumer<Byte> errorHandler;
|
||||
Consumer<Byte> stopHandler;
|
||||
Consumer<Byte> progressHandler;
|
||||
Consumer<Byte> traceHandler;
|
||||
|
||||
public TestProgram() {
|
||||
lines.add(TestProgram.UNIT_TEST_MACROS);
|
||||
}
|
||||
|
||||
public TestProgram(String line1) {
|
||||
this();
|
||||
lines.add(line1);
|
||||
}
|
||||
|
||||
public TestProgram(String line1, int tickCount) {
|
||||
this();
|
||||
assertTimed(line1, tickCount);
|
||||
}
|
||||
|
||||
int tickCount = 0;
|
||||
|
||||
int timerStart = 0;
|
||||
int timerLastMark = 0;
|
||||
int timerEnd = 0;
|
||||
int timerLastEllapsed = 0;
|
||||
int lastBreakpoint = -1;
|
||||
int maxTicks = 10000;
|
||||
boolean programCompleted = false;
|
||||
boolean programReportedError = false;
|
||||
boolean programRunning = false;
|
||||
List<String> errors = new ArrayList<>();
|
||||
List<Integer> timings = new ArrayList<>();
|
||||
|
||||
ProgramException lastError = null;
|
||||
public static String UNIT_TEST_MACROS = """
|
||||
!cpu 65c02
|
||||
!macro extendedOp .code, .val {!byte $FC, .code, .val}
|
||||
!macro startTimer {+extendedOp $10, $80}
|
||||
!macro markTimer {+extendedOp $10, $81}
|
||||
!macro stopTimer {+extendedOp $10, $82}
|
||||
!macro assertTicks .ticks {+extendedOp $11, .ticks}
|
||||
!macro stop .p1, .p2 {
|
||||
+extendedOp .p1, .p2
|
||||
+extendedOp $13, $ff
|
||||
+traceOff
|
||||
-
|
||||
JMP -
|
||||
}
|
||||
!macro throwError .errorCode {+stop $12, .errorCode}
|
||||
!macro recordError .errorCode {+extendedOp $12, .errorCode}
|
||||
!macro success {+stop $14, $ff}
|
||||
!macro breakpoint .num {+extendedOp $14, .num}
|
||||
!macro traceOn {+extendedOp $15, $01}
|
||||
!macro traceOff {+extendedOp $15, $00}
|
||||
!macro resetRegs {
|
||||
LDA #0
|
||||
LDX #0
|
||||
LDY #0
|
||||
PHA
|
||||
PLP
|
||||
TXS
|
||||
PHP
|
||||
}
|
||||
+resetRegs
|
||||
""";
|
||||
public static String INDENT = " ";
|
||||
|
||||
public void attach() {
|
||||
timerHandler = this::handleTimer;
|
||||
tickCountHandler = this::countAndCompareTicks;
|
||||
errorHandler = this::recordError;
|
||||
stopHandler = b->stop();
|
||||
progressHandler = this::recordProgress;
|
||||
traceHandler = this::handleTrace;
|
||||
|
||||
Emulator.withComputer(c-> {
|
||||
MOS65C02 cpu = (MOS65C02) c.getCpu();
|
||||
cpu.registerExtendedCommandHandler(0x10, timerHandler);
|
||||
cpu.registerExtendedCommandHandler(0x11, tickCountHandler);
|
||||
cpu.registerExtendedCommandHandler(0x12, errorHandler);
|
||||
cpu.registerExtendedCommandHandler(0x13, stopHandler);
|
||||
cpu.registerExtendedCommandHandler(0x14, progressHandler);
|
||||
cpu.registerExtendedCommandHandler(0x15, traceHandler);
|
||||
});
|
||||
}
|
||||
|
||||
public void detach() {
|
||||
Emulator.withComputer(c-> {
|
||||
MOS65C02 cpu = (MOS65C02) c.getCpu();
|
||||
cpu.unregisterExtendedCommandHandler(timerHandler);
|
||||
cpu.unregisterExtendedCommandHandler(tickCountHandler);
|
||||
cpu.unregisterExtendedCommandHandler(errorHandler);
|
||||
cpu.unregisterExtendedCommandHandler(stopHandler);
|
||||
cpu.unregisterExtendedCommandHandler(progressHandler);
|
||||
cpu.unregisterExtendedCommandHandler(traceHandler);
|
||||
});
|
||||
}
|
||||
|
||||
private void handleTimer(byte val) {
|
||||
switch (val) {
|
||||
case (byte)0x80:
|
||||
timerStart = tickCount;
|
||||
timerLastMark = tickCount;
|
||||
break;
|
||||
case (byte)0x82:
|
||||
timerEnd = tickCount;
|
||||
// Fall through
|
||||
case (byte)0x81:
|
||||
// Don't count the time spent on the timer commands!
|
||||
timerLastEllapsed = (tickCount - timerLastMark) - 4;
|
||||
timerLastMark = tickCount;
|
||||
break;
|
||||
default:
|
||||
lastError = new ProgramException("Unknown timer command %s".formatted(val), lastBreakpoint);
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void countAndCompareTicks(byte val) {
|
||||
int expectedTickCountNum = val;
|
||||
int expectedTickCount = timings.get(expectedTickCountNum);
|
||||
String errorMessage = lastError != null ? lastError.getProgramLocation() : "";
|
||||
if (timerLastEllapsed != expectedTickCount) {
|
||||
lastError = new ProgramException("Expected %s ticks, instead counted %s <<%s>>".formatted(expectedTickCount, timerLastEllapsed, errorMessage), lastBreakpoint);
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void recordError(byte v) {
|
||||
int val = v & 0x0ff;
|
||||
|
||||
if (val >= 0 && val < errors.size()) {
|
||||
lastError = new ProgramException(errors.get(val), lastBreakpoint);
|
||||
} else if (val == 255) {
|
||||
lastError = null;
|
||||
} else {
|
||||
lastError = new ProgramException("Error %s".formatted(val), lastBreakpoint);
|
||||
}
|
||||
}
|
||||
|
||||
public TestProgram defineError(String error) {
|
||||
errors.add(error);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
programReportedError = lastError != null;
|
||||
programRunning = false;
|
||||
}
|
||||
|
||||
private void recordProgress(byte val) {
|
||||
if (val == (byte)0xff) {
|
||||
programCompleted = true;
|
||||
return;
|
||||
} else {
|
||||
lastBreakpoint = val;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTrace(byte val) {
|
||||
if (val == (byte)0x01) {
|
||||
System.out.println("Trace on");
|
||||
Full65C02Test.cpu.setTraceEnabled(true);
|
||||
} else {
|
||||
System.out.println("Trace off");
|
||||
Full65C02Test.cpu.setTraceEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
public TestProgram assertTimed(String line, int ticks) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
String caller = stackTrace[2].toString();
|
||||
int errNum = errors.size();
|
||||
errors.add("Expected %s ticks for %s <<%s>>".formatted(ticks, line, caller));
|
||||
int timingNum = timings.size();
|
||||
timings.add(ticks);
|
||||
lines.add(INDENT+"+startTimer");
|
||||
lines.add(line);
|
||||
lines.add(INDENT+"+markTimer");
|
||||
lines.add(INDENT+"+recordError %s".formatted(errNum));
|
||||
lines.add(INDENT+"+assertTicks %s ; Check for %s cycles".formatted(timingNum, ticks));
|
||||
lines.add(INDENT+"+recordError %s".formatted(255));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestProgram add(String line) {
|
||||
lines.add(line);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestProgram assertFlags(TestProgram.Flag... flags) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
String caller = stackTrace[2].toString();
|
||||
for (TestProgram.Flag flag : flags)
|
||||
_test(TestProgram.INDENT + flag.code, flag.condition + "<<" + caller + ">>");
|
||||
return this;
|
||||
}
|
||||
|
||||
/* Test the A register for a specific value */
|
||||
public TestProgram assertA(int val) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
String caller = stackTrace[2].toString();
|
||||
String condition = "A != %s <<%s>>".formatted(Integer.toHexString(val), caller);
|
||||
_test("""
|
||||
PHP
|
||||
CMP #%s
|
||||
BNE ++
|
||||
PLP
|
||||
BRA +
|
||||
++ ; Error
|
||||
""".formatted(val), condition + " in " + caller);
|
||||
return this;
|
||||
}
|
||||
|
||||
/* Test X register for a specific value */
|
||||
public TestProgram assertX(int val) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
String caller = stackTrace[2].toString();
|
||||
String condition = "X != %s <<%s>>".formatted(Integer.toHexString(val), caller);
|
||||
_test("""
|
||||
PHP
|
||||
CPX #%s
|
||||
BNE ++
|
||||
PLP
|
||||
BRA +
|
||||
++ ; Error
|
||||
""".formatted(val), condition);
|
||||
return this;
|
||||
}
|
||||
|
||||
/* Test Y register for a specific value */
|
||||
public TestProgram assertY(int val) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
String caller = stackTrace[2].toString();
|
||||
String condition = "Y != %s <<%s>>".formatted(Integer.toHexString(val), caller);
|
||||
_test("""
|
||||
PHP
|
||||
CPY #%s
|
||||
BNE ++
|
||||
PLP
|
||||
BRA +
|
||||
++ ; Error
|
||||
""".formatted(val), condition);
|
||||
return this;
|
||||
}
|
||||
|
||||
/* Test an address for a specific value. If the value is incorrect, leave it in A for inspection */
|
||||
public TestProgram assertAddrVal(int address, int val) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
String caller = stackTrace[2].toString();
|
||||
String condition = "$%s != %s <<%s>>".formatted(Integer.toHexString(address), Integer.toHexString(val), caller);
|
||||
_test("""
|
||||
PHP
|
||||
PHA
|
||||
LDA $%s
|
||||
CMP #%s
|
||||
BNE ++
|
||||
PLA
|
||||
PLP
|
||||
BRA +
|
||||
++ ; Error
|
||||
LDA $%s
|
||||
""".formatted(Integer.toHexString(address), val, val), condition);
|
||||
return this;
|
||||
}
|
||||
|
||||
/* Use provided code to test a condition. If successful it should jump or branch to +
|
||||
* If unsuccessful the error condition will be reported.
|
||||
*
|
||||
* @param code The code to test
|
||||
* @param condition The condition to report if the test fails
|
||||
*/
|
||||
public TestProgram test(String code, String condition) {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
String caller = stackTrace[2].toString();
|
||||
_test(code, condition + "<<" + caller + ">>");
|
||||
return this;
|
||||
}
|
||||
|
||||
private void _test(String code, String condition) {
|
||||
int errorNum = errors.size();
|
||||
errors.add(condition);
|
||||
lines.add("""
|
||||
; << Test %s
|
||||
%s
|
||||
+throwError %s
|
||||
+ ; >> Test """.formatted(condition, code, errorNum));
|
||||
}
|
||||
|
||||
/**
|
||||
* Note the current breakpoint, helpful in understanding error locations
|
||||
*
|
||||
* @param num Error number to report in any error that occurs
|
||||
*/
|
||||
public TestProgram breakpoint(int num) {
|
||||
lines.add(INDENT + "+breakpoint %s".formatted(num));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the registers to 0, clear the stack, and clear flags
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public TestProgram resetRegisters() {
|
||||
lines.add(INDENT + "+resetRegs");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on or off tracing
|
||||
*
|
||||
* @param state True to turn on tracing, false to turn it off
|
||||
*/
|
||||
public TestProgram setTrace(boolean state) {
|
||||
lines.add(INDENT +"+trace%s".formatted(state ? "On" : "Off"));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the program as unassembled code
|
||||
*
|
||||
* @return The program as a string
|
||||
*/
|
||||
public String build() {
|
||||
lines.add(INDENT +"+success");
|
||||
return String.join("\n", lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the program for a specific number of ticks, or until it completes (whichever comes first)
|
||||
*
|
||||
* @param ticks The number of ticks to run the program
|
||||
* @throws ProgramException If the program reports an error
|
||||
*/
|
||||
public void runForTicks(int ticks) throws ProgramException {
|
||||
this.maxTicks = ticks;
|
||||
run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the program until it completes, reports an error, or reaches the maximum number of ticks
|
||||
*
|
||||
* @throws ProgramException If the program reports an error
|
||||
*/
|
||||
public void run() throws ProgramException {
|
||||
Computer computer = Emulator.withComputer(c->c, null);
|
||||
MOS65C02 cpu = (MOS65C02) computer.getCpu();
|
||||
|
||||
attach();
|
||||
programRunning = true;
|
||||
String program = build();
|
||||
// We have to run the program more carefully so just load it first
|
||||
try {
|
||||
runAssemblyCode(program, 0);
|
||||
} catch (Exception e) {
|
||||
throw new ProgramException(e.getMessage() + "\n" + program, -1);
|
||||
}
|
||||
cpu.resume();
|
||||
try {
|
||||
for (int i=0; i < maxTicks; i++) {
|
||||
cpu.doTick();
|
||||
tickCount++;
|
||||
if (programReportedError) {
|
||||
throw lastError;
|
||||
}
|
||||
if (!programRunning) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cpu.suspend();
|
||||
detach();
|
||||
}
|
||||
assertFalse("Test reported an error", programReportedError);
|
||||
assertFalse("Program never ended fully after " + tickCount + " ticks; got to breakpoint " + lastBreakpoint, programRunning);
|
||||
assertTrue("Test did not complete fully", programCompleted);
|
||||
}
|
||||
}
|
83
src/test/java/jace/TestUtils.java
Normal file
83
src/test/java/jace/TestUtils.java
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2023 org.badvision.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package jace;
|
||||
|
||||
import jace.core.CPU;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Device;
|
||||
import jace.core.Utility;
|
||||
import jace.ide.HeadlessProgram;
|
||||
import jace.ide.Program;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author brobert
|
||||
*/
|
||||
public class TestUtils {
|
||||
private TestUtils() {
|
||||
// Utility class has no constructor
|
||||
}
|
||||
|
||||
public static void initComputer() {
|
||||
Utility.setHeadlessMode(true);
|
||||
Emulator.withComputer(Computer::reconfigure);
|
||||
}
|
||||
|
||||
public static void assemble(String code, int addr) throws Exception {
|
||||
runAssemblyCode(code, addr, 0);
|
||||
}
|
||||
|
||||
public static void runAssemblyCode(String code, int ticks) throws Exception {
|
||||
runAssemblyCode(code, 0x6000, ticks);
|
||||
}
|
||||
|
||||
public static void runAssemblyCode(String code, int addr, int ticks) throws Exception {
|
||||
CPU cpu = Emulator.withComputer(c->c.getCpu(), null);
|
||||
HeadlessProgram program = new HeadlessProgram(Program.DocumentType.assembly);
|
||||
program.setValue("*=$"+Integer.toHexString(addr)+"\n "+code+"\n NOP\n RTS");
|
||||
program.execute();
|
||||
if (ticks > 0) {
|
||||
cpu.resume();
|
||||
for (int i=0; i < ticks; i++) {
|
||||
cpu.doTick();
|
||||
}
|
||||
cpu.suspend();
|
||||
}
|
||||
}
|
||||
|
||||
public static Device createSimpleDevice(Runnable r, String name) {
|
||||
return new Device() {
|
||||
@Override
|
||||
public void tick() {
|
||||
r.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return name;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
74
src/test/java/jace/apple2e/CycleCountTest.java
Normal file
74
src/test/java/jace/apple2e/CycleCountTest.java
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2023 org.badvision.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package jace.apple2e;
|
||||
|
||||
import static jace.TestUtils.initComputer;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.ProgramException;
|
||||
import jace.TestProgram;
|
||||
import jace.core.SoundMixer;
|
||||
|
||||
|
||||
/**
|
||||
* More advanced cycle counting tests. These help ensure CPU runs correctly so things
|
||||
* like vapor lock and speaker sound work as expected.
|
||||
* @author brobert
|
||||
*/
|
||||
public class CycleCountTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void setupClass() {
|
||||
initComputer();
|
||||
SoundMixer.MUTE = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composite test which ensures the speaker beep is the right pitch.
|
||||
* Test that the wait routine for beep cycles correctly.
|
||||
* Calling WAIT with A=#$c (12) should take 535 cycles
|
||||
* according to the tech ref notes: =1/2*(26+27*A+5*A^2) where A = 12 (0x0c)
|
||||
* The BELL routine has an additional 12 cycles per iteration plus 1 extra cycle in the first iteration.
|
||||
* e.g. 2 iterations take 1093 cycles
|
||||
*
|
||||
* @throws ProgramException
|
||||
*/
|
||||
@Test
|
||||
public void testDirectBeeperCycleCount() throws ProgramException {
|
||||
new TestProgram("""
|
||||
SPKR = $C030
|
||||
jmp BELL
|
||||
WAIT sec
|
||||
WAIT2 pha
|
||||
WAIT3 sbc #$01
|
||||
bne WAIT3
|
||||
pla
|
||||
sbc #$01
|
||||
bne WAIT2
|
||||
rts
|
||||
BELL +markTimer
|
||||
ldy #$02
|
||||
BELL2 lda #$0c
|
||||
jsr WAIT
|
||||
lda SPKR
|
||||
dey
|
||||
bne BELL2
|
||||
""", 1093).run();
|
||||
}
|
||||
|
||||
}
|
804
src/test/java/jace/apple2e/Full65C02Test.java
Normal file
804
src/test/java/jace/apple2e/Full65C02Test.java
Normal file
@ -0,0 +1,804 @@
|
||||
/*
|
||||
* Copyright 2024 Brendan Robert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package jace.apple2e;
|
||||
|
||||
import static jace.TestProgram.Flag.CARRY_CLEAR;
|
||||
import static jace.TestProgram.Flag.CARRY_SET;
|
||||
import static jace.TestProgram.Flag.DECIMAL_CLEAR;
|
||||
import static jace.TestProgram.Flag.DECIMAL_SET;
|
||||
import static jace.TestProgram.Flag.INTERRUPT_CLEAR;
|
||||
import static jace.TestProgram.Flag.INTERRUPT_SET;
|
||||
import static jace.TestProgram.Flag.IS_ZERO;
|
||||
import static jace.TestProgram.Flag.NEGATIVE;
|
||||
import static jace.TestProgram.Flag.NOT_ZERO;
|
||||
import static jace.TestProgram.Flag.OVERFLOW_CLEAR;
|
||||
import static jace.TestProgram.Flag.OVERFLOW_SET;
|
||||
import static jace.TestProgram.Flag.POSITIVE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.ProgramException;
|
||||
import jace.TestProgram;
|
||||
import jace.TestUtils;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.SoundMixer;
|
||||
|
||||
/**
|
||||
* Basic test functionality to assert correct 6502 decode and execution.
|
||||
*
|
||||
* @author blurry
|
||||
*/
|
||||
public class Full65C02Test {
|
||||
|
||||
static Computer computer;
|
||||
public static MOS65C02 cpu;
|
||||
static RAM128k ram;
|
||||
|
||||
@BeforeClass
|
||||
public static void setupClass() {
|
||||
TestUtils.initComputer();
|
||||
SoundMixer.MUTE = true;
|
||||
computer = Emulator.withComputer(c->c, null);
|
||||
cpu = (MOS65C02) computer.getCpu();
|
||||
ram = (RAM128k) computer.getMemory();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void teardownClass() {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
computer.pause();
|
||||
cpu.clearState();
|
||||
}
|
||||
|
||||
@Test
|
||||
/* ADC: All CPU flags/modes */
|
||||
public void testAdditionCPUFlags() throws ProgramException {
|
||||
new TestProgram()
|
||||
// Add 1 w/o carry; 1+1 = 2
|
||||
.assertTimed("ADC #1", 2)
|
||||
.assertA(1)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||
// Add 1 w/ carry 1+0+c = 2
|
||||
.add("SEC")
|
||||
.assertTimed("ADC #0", 2)
|
||||
.assertA(2)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||
// Decimal: ADD 8 w/o carry; 2+8 = 10
|
||||
.add("SED")
|
||||
.assertTimed("ADC #8", 3)
|
||||
.assertA(0x10)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||
// Decimal: ADD 9 w/ carry; 10+9+c = 20
|
||||
.add("SEC")
|
||||
.assertTimed("ADC #09", 3)
|
||||
.assertA(0x20)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||
// Decimal: Overflow check; 20 + 99 + C = 20 (carry, no overflow)
|
||||
.add("""
|
||||
SED
|
||||
SEC
|
||||
LDA #$20
|
||||
ADC #$99
|
||||
""")
|
||||
.assertA(0x20)
|
||||
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||
// Decimal: Overflow check; 20 + 64 + C = 85 (overflow)
|
||||
.add("ADC #$64")
|
||||
.assertA(0x85)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_SET, NEGATIVE)
|
||||
// Overflow check; 0x7F + 0x01 = 0x80 (overflow)
|
||||
.add("CLD")
|
||||
.add("LDA #$7F")
|
||||
.assertTimed("ADC #1", 2)
|
||||
.assertA(0x80)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_SET, NEGATIVE)
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
/* ADC: All addressing modes -- these are used in many opcodes so we mostly just need to test here */
|
||||
public void testAdditionAddressingModes() throws ProgramException {
|
||||
// Start test by filling zero page and $1000...$10FF with 0...255
|
||||
new TestProgram("""
|
||||
LDX #0
|
||||
- TXA
|
||||
STA $00,X
|
||||
STA $1000,X
|
||||
INX
|
||||
BNE -
|
||||
LDA #1
|
||||
""")
|
||||
// 1: AB,X
|
||||
.add("LDX #$7F")
|
||||
.assertTimed("ADC $1000,X", 4)
|
||||
.assertA(0x80)
|
||||
.assertFlags(OVERFLOW_SET, CARRY_CLEAR, NEGATIVE)
|
||||
// 2: AB,Y
|
||||
.add("LDY #$20")
|
||||
.assertTimed("ADC $1000,Y", 4)
|
||||
.assertA(0xA0)
|
||||
.assertFlags(OVERFLOW_CLEAR, CARRY_CLEAR, NEGATIVE)
|
||||
// 3: izp ($09) == 0x100f ==> A+=f
|
||||
.assertTimed("ADC ($0f)", 5)
|
||||
.assertA(0xAF)
|
||||
// 4: izx ($00,x) where X=f = 0x100f ==> A+=f
|
||||
.add("LDX #$0F")
|
||||
.assertTimed("ADC ($00,x)", 6)
|
||||
.assertA(0xBE)
|
||||
// 5: izy ($00),y where Y=20 = 0x102F ==> A+=2F
|
||||
.add("LDY #$21")
|
||||
.assertTimed("ADC ($0F),y", 5)
|
||||
.assertA(0xEE)
|
||||
// 6: zpx $00,x where X=10 ==> A+=10
|
||||
.add("LDX #$10")
|
||||
.assertTimed("ADC $00,x", 4)
|
||||
.assertA(0xFE)
|
||||
// 7: zp $01 ==> A+=01
|
||||
.assertTimed("ADC $01", 3)
|
||||
.assertA(0xFF)
|
||||
// 8: abs $1001 ==> A+=01
|
||||
.assertTimed("ADC $1001", 4)
|
||||
.assertA(0x00)
|
||||
.assertFlags(IS_ZERO, OVERFLOW_CLEAR, CARRY_SET, POSITIVE)
|
||||
// Now check boundary conditions on indexed addressing for timing differences
|
||||
.add("LDX #$FF")
|
||||
.assertTimed("ADC $10FF,X", 5)
|
||||
.add("LDY #$FF")
|
||||
.assertTimed("ADC $10FF,Y", 5)
|
||||
.assertTimed("ADC ($0F),Y", 6)
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
/* ABS: All CPU flags/modes */
|
||||
public void testSubtraction() throws ProgramException {
|
||||
new TestProgram("SEC")
|
||||
// 0-1 = -1
|
||||
.assertTimed("SBC #1", 2)
|
||||
.assertA(0xFF)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE)
|
||||
// 127 - -1 = 128 (Overflow)
|
||||
.add("SEC")
|
||||
.add("LDA #$7F")
|
||||
.assertTimed("SBC #$FF", 2)
|
||||
.assertA(0x80)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_SET, NEGATIVE)
|
||||
// -128 - 1 = -129 (overflow)
|
||||
.add("SEC")
|
||||
.add("LDA #$80")
|
||||
.assertTimed("SBC #$01", 2)
|
||||
.assertA(0x7F)
|
||||
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_SET, POSITIVE)
|
||||
// 20-10=10 (no overflow)
|
||||
.add("LDA #$30")
|
||||
.assertTimed("SBC #$10", 2)
|
||||
.assertA(0x20)
|
||||
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||
// Decimal: 20-5=15
|
||||
.add("SED")
|
||||
.assertTimed("SBC #$05", 3)
|
||||
.assertA(0x15)
|
||||
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||
// Decimal: 0x15-0x05=0x10
|
||||
.assertTimed("SBC #$05", 3)
|
||||
.assertA(0x10)
|
||||
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE)
|
||||
// Decimal: 99-19=80
|
||||
.add("LDA #$99")
|
||||
.assertTimed("SBC #$19", 3)
|
||||
.assertA(0x80)
|
||||
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE)
|
||||
// Decimal: 99-50=49 (unintuitively causes overflow)
|
||||
.add("LDA #$99")
|
||||
.assertTimed("SBC #$50", 3)
|
||||
.assertA(0x49)
|
||||
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_SET, POSITIVE)
|
||||
// Decimal: 19 - 22 = 97 (arithmetic underflow clears carry)
|
||||
.add("LDA #$19")
|
||||
.assertTimed("SBC #$22", 3)
|
||||
.assertA(0x97)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE)
|
||||
// Test cycle counts for other addressing modes
|
||||
.add("CLD")
|
||||
.assertTimed("SBC $1000", 4)
|
||||
.assertTimed("SBC $1000,x", 4)
|
||||
.assertTimed("SBC $1000,y", 4)
|
||||
.assertTimed("SBC ($00)", 5)
|
||||
.assertTimed("SBC ($00,X)", 6)
|
||||
.assertTimed("SBC ($00),Y", 5)
|
||||
.assertTimed("SBC $00", 3)
|
||||
.assertTimed("SBC $00,X", 4)
|
||||
.run();
|
||||
}
|
||||
|
||||
@Test
|
||||
/* Full test of ADC and SBC with binary coded decimal mode */
|
||||
public void testBCD() throws ProgramException, URISyntaxException, IOException {
|
||||
Path resource = Paths.get(getClass().getResource("/jace/bcd_test.asm").toURI());
|
||||
String testCode = Files.readString(resource);
|
||||
TestProgram test = new TestProgram(testCode);
|
||||
test.defineError("Error when performing ADC operation");
|
||||
test.defineError("Error when performing SBC operation");
|
||||
try {
|
||||
test.runForTicks(50000000);
|
||||
} catch (ProgramException e) {
|
||||
// Dump memory from 0x0006 to 0x0015
|
||||
for (int i = 0x0006; i <= 0x0015; i++) {
|
||||
System.out.printf("%04X: %02X\n", i, ram.read(i, TYPE.READ_DATA, false, false));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
/* Test of the processor flags */
|
||||
public void testFlags() throws ProgramException {
|
||||
new TestProgram()
|
||||
// Test explicit flag set/clear commands (and the tests by way of this cover all branch instructions)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.assertTimed("SEC", 2)
|
||||
.assertFlags(CARRY_SET, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.assertTimed("CLC", 2)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.assertTimed("SED", 2)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_SET, INTERRUPT_CLEAR)
|
||||
.assertTimed("CLD", 2)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.assertTimed("SEI", 2)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_SET)
|
||||
.assertTimed("CLI", 2)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
// Set overflow flag by hacking the P register
|
||||
.add("""
|
||||
LDA #%01000000
|
||||
PHA
|
||||
PLP
|
||||
""")
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_SET, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.assertTimed("CLV", 2)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
// Test Zero and Negative flags (within reason, the ADC/SBC tests cover these more thoroughly)
|
||||
.assertTimed("LDA #0",2 )
|
||||
.assertFlags(CARRY_CLEAR, IS_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.assertTimed("LDA #$ff", 2)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.assertTimed("LDY #0", 2)
|
||||
.assertFlags(CARRY_CLEAR, IS_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.assertTimed("LDY #$ff", 2)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.assertTimed("LDX #0", 2)
|
||||
.assertFlags(CARRY_CLEAR, IS_ZERO, OVERFLOW_CLEAR, POSITIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.assertTimed("LDX #$ff", 2)
|
||||
.assertFlags(CARRY_CLEAR, NOT_ZERO, OVERFLOW_CLEAR, NEGATIVE, DECIMAL_CLEAR, INTERRUPT_CLEAR)
|
||||
.run();
|
||||
}
|
||||
|
||||
/* Test stack operations */
|
||||
@Test
|
||||
public void testStack() throws ProgramException {
|
||||
new TestProgram()
|
||||
.assertTimed("TSX", 2)
|
||||
.assertX(0xFF)
|
||||
.assertTimed("LDA #255", 2)
|
||||
.assertTimed("PHA", 3)
|
||||
.assertTimed("LDX #11", 2)
|
||||
.assertTimed("PHX", 3)
|
||||
.assertTimed("LDY #12", 2)
|
||||
.assertTimed("PHY", 3)
|
||||
.assertTimed("PHP", 3)
|
||||
.assertTimed("TSX", 2)
|
||||
.assertX(0xFB)
|
||||
.assertTimed("PLP", 4)
|
||||
.add("LDA #0")
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.assertTimed("PLA", 4)
|
||||
.assertA(12)
|
||||
.assertFlags(NOT_ZERO, POSITIVE)
|
||||
.add("LDY #$FF")
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("PLY", 4)
|
||||
.assertY(11)
|
||||
.assertFlags(NOT_ZERO, POSITIVE)
|
||||
.assertTimed("PLX", 4)
|
||||
.assertX(255)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.run();
|
||||
}
|
||||
|
||||
/* Test logic operations */
|
||||
@Test
|
||||
public void testLogic() throws ProgramException {
|
||||
// OR, AND, EOR
|
||||
new TestProgram()
|
||||
.assertTimed("LDA #0x55", 2)
|
||||
.assertTimed("AND #0xAA", 2)
|
||||
.assertA(0x00)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.assertTimed("LDA #0x55", 2)
|
||||
.assertTimed("ORA #0xAA", 2)
|
||||
.assertA(0xFF)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("LDA #0x55", 2)
|
||||
.assertTimed("EOR #0xFF", 2)
|
||||
.assertA(0xAA)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.run();
|
||||
TestProgram cycleCounts = new TestProgram();
|
||||
for (String opcode : new String[] {"AND", "ORA", "EOR"}) {
|
||||
cycleCounts.assertTimed(opcode + " $1000", 4)
|
||||
.assertTimed(opcode + " $1000,x", 4)
|
||||
.assertTimed(opcode + " $1000,y", 4)
|
||||
.assertTimed(opcode + " ($00)", 5)
|
||||
.assertTimed(opcode + " ($00,X)", 6)
|
||||
.assertTimed(opcode + " ($00),Y", 5)
|
||||
.assertTimed(opcode + " $00", 3)
|
||||
.assertTimed(opcode + " $00,X", 4);
|
||||
}
|
||||
cycleCounts.run();
|
||||
// ASL
|
||||
new TestProgram()
|
||||
.assertTimed("LDA #0x55", 2)
|
||||
.add("STA $1000")
|
||||
.assertTimed("ASL", 2)
|
||||
.assertA(0xAA)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||
.assertTimed("ASL", 2)
|
||||
.assertA(0x54)
|
||||
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||
.assertTimed("ASL $1000", 6)
|
||||
.assertAddrVal(0x1000, 0xAA)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||
.assertTimed("ASL $1000,x", 7)
|
||||
.assertTimed("ASL $00", 5)
|
||||
.assertTimed("ASL $00,x", 6)
|
||||
.run();
|
||||
// LSR
|
||||
new TestProgram()
|
||||
.assertTimed("LDA #0x55", 2)
|
||||
.add("STA $1000")
|
||||
.assertTimed("LSR", 2)
|
||||
.assertA(0x2A)
|
||||
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||
.assertTimed("LSR", 2)
|
||||
.assertA(0x15)
|
||||
.assertFlags(NOT_ZERO, POSITIVE, CARRY_CLEAR)
|
||||
.assertTimed("LSR $1000", 6)
|
||||
.assertAddrVal(0x1000, 0x2A)
|
||||
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||
.assertTimed("LSR $1000,x", 7)
|
||||
.assertTimed("LSR $00", 5)
|
||||
.assertTimed("LSR $00,x", 6)
|
||||
.run();
|
||||
// BIT
|
||||
new TestProgram()
|
||||
.add("LDA #$FF")
|
||||
.add("STA $FF")
|
||||
.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", 2)
|
||||
.assertFlags(NOT_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)
|
||||
.run();
|
||||
// ROL
|
||||
new TestProgram()
|
||||
.add("LDA #0x55")
|
||||
.add("STA $1000")
|
||||
.assertTimed("ROL", 2)
|
||||
.assertA(0xAA)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||
.assertTimed("ROL", 2)
|
||||
.assertA(0x54)
|
||||
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||
.add("CLC")
|
||||
.assertTimed("ROL $1000", 6)
|
||||
.assertAddrVal(0x1000, 0xAA)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||
.assertTimed("ROL $1000,x", 7)
|
||||
.assertTimed("ROL $00", 5)
|
||||
.assertTimed("ROL $00,x", 6)
|
||||
.run();
|
||||
// ROR
|
||||
new TestProgram()
|
||||
.add("LDA #0x55")
|
||||
.add("STA $1000")
|
||||
.assertTimed("ROR", 2)
|
||||
.assertA(0x2A)
|
||||
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||
.assertTimed("ROR", 2)
|
||||
.assertA(0x95)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||
.assertTimed("ROR $1000", 6)
|
||||
.assertAddrVal(0x1000, 0x2A)
|
||||
.assertFlags(NOT_ZERO, POSITIVE, CARRY_SET)
|
||||
.assertTimed("ROR $1000,x", 7)
|
||||
.assertTimed("ROR $00", 5)
|
||||
.assertTimed("ROR $00,x", 6)
|
||||
.run();
|
||||
}
|
||||
|
||||
/* Increment/Decrement instructions */
|
||||
@Test
|
||||
public void testIncDec() throws ProgramException {
|
||||
new TestProgram()
|
||||
.add("LDA #0")
|
||||
.add("STA $1000")
|
||||
.assertTimed("INC", 2)
|
||||
.assertA(1)
|
||||
.assertFlags(NOT_ZERO, POSITIVE)
|
||||
.assertTimed("DEC", 2)
|
||||
.assertA(0)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.add("LDA #$FF")
|
||||
.assertTimed("INC", 2)
|
||||
.assertA(0)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.assertTimed("DEC", 2)
|
||||
.assertA(0xFF)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("INC $1000", 6)
|
||||
.assertAddrVal(0x1000, 1)
|
||||
.assertFlags(NOT_ZERO, POSITIVE)
|
||||
.assertTimed("DEC $1000", 6)
|
||||
.assertAddrVal(0x1000, 0)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.assertTimed("INC $1000,x", 7)
|
||||
.assertTimed("DEC $1000,x", 7)
|
||||
.assertTimed("INC $00", 5)
|
||||
.assertTimed("DEC $00", 5)
|
||||
.assertTimed("INC $00,x", 6)
|
||||
.assertTimed("DEC $00,x", 6)
|
||||
// INX/DEX/INY/DEY
|
||||
.add("LDX #0")
|
||||
.assertTimed("INX", 2)
|
||||
.assertX(1)
|
||||
.assertFlags(NOT_ZERO, POSITIVE)
|
||||
.assertTimed("DEX", 2)
|
||||
.assertX(0)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.assertTimed("DEX", 2)
|
||||
.assertX(0xFF)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("INX", 2)
|
||||
.assertX(0)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.add("LDY #0")
|
||||
.assertTimed("INY", 2)
|
||||
.assertY(1)
|
||||
.assertFlags(NOT_ZERO, POSITIVE)
|
||||
.assertTimed("DEY", 2)
|
||||
.assertY(0)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.assertTimed("DEY", 2)
|
||||
.assertY(0xFF)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("INY", 2)
|
||||
.assertY(0)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
|
||||
.run();
|
||||
}
|
||||
|
||||
/* All compare instructions CMP, CPX, CPY */
|
||||
@Test
|
||||
public void testComparisons() throws ProgramException {
|
||||
new TestProgram()
|
||||
.add("LDA #0")
|
||||
.assertTimed("CMP #0", 2)
|
||||
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||
.assertTimed("CMP #1", 2)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||
.add("LDA #$FF")
|
||||
.assertTimed("CMP #0", 2)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_SET)
|
||||
.assertTimed("CMP #$FF", 2)
|
||||
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||
.add("LDX #0")
|
||||
.assertTimed("CPX #0", 2)
|
||||
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||
.assertTimed("CPX #1", 2)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||
.add("LDX #$FF")
|
||||
.assertTimed("CPX #0", 2)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_SET)
|
||||
.assertTimed("CPX #$FF", 2)
|
||||
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||
.add("LDY #0")
|
||||
.assertTimed("CPY #0", 2)
|
||||
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||
.assertTimed("CPY #1", 2)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_CLEAR)
|
||||
.add("LDY #$FF")
|
||||
.assertTimed("CPY #0", 2)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE, CARRY_SET)
|
||||
.assertTimed("CPY #$FF", 2)
|
||||
.assertFlags(IS_ZERO, POSITIVE, CARRY_SET)
|
||||
// Cycle count other modes
|
||||
.assertTimed("CMP $1000", 4)
|
||||
.assertTimed("CMP $1000,x", 4)
|
||||
.assertTimed("CMP $1000,y", 4)
|
||||
.assertTimed("CMP ($00)", 5)
|
||||
.assertTimed("CMP ($00,X)", 6)
|
||||
.assertTimed("CMP ($00),Y", 5)
|
||||
.assertTimed("CMP $00", 3)
|
||||
.assertTimed("CMP $00,X", 4)
|
||||
.assertTimed("CPX $1000", 4)
|
||||
.assertTimed("CPX $10", 3)
|
||||
.assertTimed("CPY $1000", 4)
|
||||
.assertTimed("CPY $10", 3)
|
||||
.run();
|
||||
}
|
||||
|
||||
/* Load/Store/Transfer operations */
|
||||
@Test
|
||||
public void testLoadStore() throws ProgramException {
|
||||
new TestProgram()
|
||||
.assertTimed("LDA #0", 2)
|
||||
.assertA(0)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.add("LDA #$FF")
|
||||
.assertA(0xFF)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("LDX #0", 2)
|
||||
.assertX(0)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.add("LDX #$FE")
|
||||
.assertX(0xFE)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("LDY #0", 2)
|
||||
.assertY(0)
|
||||
.assertFlags(IS_ZERO, POSITIVE)
|
||||
.add("LDY #$FD")
|
||||
.assertY(0xFD)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("STA $1000", 4)
|
||||
.assertAddrVal(0x1000, 0xFF)
|
||||
.assertTimed("STX $1001", 4)
|
||||
.assertAddrVal(0x1001, 0xFE)
|
||||
.assertTimed("STY $1002", 4)
|
||||
.assertAddrVal(0x1002, 0xFD)
|
||||
.assertTimed("LDA $1002", 4)
|
||||
.assertA(0xFD)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("LDX $1000", 4)
|
||||
.assertX(0xFF)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("LDY $1001", 4)
|
||||
.assertY(0xFE)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
// Cycle count for other LDA modes
|
||||
.assertTimed("LDA $1000,x", 4)
|
||||
.assertTimed("LDA $1000,y", 4)
|
||||
.assertTimed("LDA ($00)", 5)
|
||||
.assertTimed("LDA ($00,X)", 6)
|
||||
.assertTimed("LDA ($00),Y", 5)
|
||||
.assertTimed("LDA $00", 3)
|
||||
.assertTimed("LDA $00,X", 4)
|
||||
// Cycle counts for other STA modes
|
||||
.assertTimed("STA $1000,x", 5)
|
||||
.assertTimed("STA $1000,y", 5)
|
||||
.assertTimed("STA ($00)", 5)
|
||||
.assertTimed("STA ($00,X)", 6)
|
||||
.assertTimed("STA ($00),Y", 6)
|
||||
.assertTimed("STA $00", 3)
|
||||
.assertTimed("STA $00,X", 4)
|
||||
// Cycle counts for other LDX and LDY modes
|
||||
.assertTimed("LDX $1000", 4)
|
||||
.assertTimed("LDX $1000,y", 4)
|
||||
.assertTimed("LDX $00", 3)
|
||||
.assertTimed("LDX $00,y", 4)
|
||||
.assertTimed("LDY $1000", 4)
|
||||
.assertTimed("LDY $1000,x", 4)
|
||||
.assertTimed("LDY $00", 3)
|
||||
.assertTimed("LDY $00,x", 4)
|
||||
// Cycle counts for other STX and STY modes
|
||||
.assertTimed("STX $1000", 4)
|
||||
.assertTimed("STX $00", 3)
|
||||
.assertTimed("STX $00, Y", 4)
|
||||
.assertTimed("STY $1000", 4)
|
||||
.assertTimed("STY $00", 3)
|
||||
.assertTimed("STY $00, X", 4)
|
||||
// STZ
|
||||
.assertTimed("STZ $1000", 4)
|
||||
.assertAddrVal(0x1000, 0)
|
||||
.assertTimed("STZ $1000,x", 5)
|
||||
.assertTimed("STZ $00", 3)
|
||||
.assertTimed("STZ $00,x", 4)
|
||||
.run();
|
||||
// Now test the transfer instructions
|
||||
new TestProgram()
|
||||
.add("LDA #10")
|
||||
.add("LDX #20")
|
||||
.add("LDY #30")
|
||||
.assertTimed("TAX", 2)
|
||||
.assertX(10)
|
||||
.assertFlags(NOT_ZERO, POSITIVE)
|
||||
.assertTimed("TAY", 2)
|
||||
.assertY(10)
|
||||
.assertFlags(NOT_ZERO, POSITIVE)
|
||||
.add("LDA #$FF")
|
||||
.assertTimed("TAX", 2)
|
||||
.assertX(0xFF)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("TAY", 2)
|
||||
.assertY(0xFF)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.add("LDA #0")
|
||||
.assertTimed("TXA", 2)
|
||||
.assertA(0xFF)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.add("LDA #0")
|
||||
.assertTimed("TYA", 2)
|
||||
.assertA(0xFF)
|
||||
.assertFlags(NOT_ZERO, NEGATIVE)
|
||||
.assertTimed("TSX", 2)
|
||||
.assertX(0xFF)
|
||||
.assertTimed("TXS", 2)
|
||||
.run();
|
||||
}
|
||||
|
||||
/* Test branch instructions */
|
||||
@Test
|
||||
public void testBranches() throws ProgramException {
|
||||
new TestProgram()
|
||||
// Zero and Negative flags
|
||||
.add("LDA #0")
|
||||
.assertTimed("BEQ *+2",3)
|
||||
.assertTimed("BNE *+2",2)
|
||||
.assertTimed("BPL *+2",3)
|
||||
.assertTimed("BMI *+2",2)
|
||||
.add("LDA #$FF")
|
||||
.assertTimed("BEQ *+2",2)
|
||||
.assertTimed("BNE *+2",3)
|
||||
.assertTimed("BPL *+2",2)
|
||||
.assertTimed("BMI *+2",3)
|
||||
.assertTimed("BRA *+2", 3)
|
||||
// Carry flag
|
||||
.assertTimed("BCC *+2", 3)
|
||||
.assertTimed("BCS *+2", 2)
|
||||
.add("SEC")
|
||||
.assertTimed("BCC *+2", 2)
|
||||
.assertTimed("BCS *+2", 3)
|
||||
// Overflow flag
|
||||
.add("CLV")
|
||||
.assertTimed("BVC *+2", 3)
|
||||
.assertTimed("BVS *+2", 2)
|
||||
.add("""
|
||||
lda #$40
|
||||
sta $1000
|
||||
bit $1000
|
||||
""")
|
||||
.assertTimed("BVC *+2", 2)
|
||||
.assertTimed("BVS *+2", 3)
|
||||
.assertTimed("NOP", 2)
|
||||
.run();
|
||||
}
|
||||
|
||||
/* Test JMP */
|
||||
@Test
|
||||
public void testJmp() throws ProgramException {
|
||||
new TestProgram()
|
||||
.add("LDA #0")
|
||||
.assertTimed("""
|
||||
JMP +
|
||||
LDA #$FF
|
||||
NOP
|
||||
NOP
|
||||
+
|
||||
""", 3)
|
||||
.assertA(0)
|
||||
// Testing indirect jump using self-modifying code
|
||||
// Load the address of jmp target and store at $300
|
||||
.add("""
|
||||
LDA #<jmpTarget
|
||||
STA $300
|
||||
LDA #>jmpTarget
|
||||
STA $301
|
||||
LDX #$FF
|
||||
""")
|
||||
.assertTimed("JMP ($300)", 6)
|
||||
.add("""
|
||||
LDX #0
|
||||
jmpTarget LDA #$88
|
||||
""")
|
||||
.assertA(0x88)
|
||||
.assertX(0xFF)
|
||||
// Perform similar test using indirect,x addressing
|
||||
.add("""
|
||||
LDA #<(jmpTargetX)
|
||||
STA $310
|
||||
LDA #>(jmpTargetX)
|
||||
STA $311
|
||||
LDX #$10
|
||||
LDY #$FF
|
||||
""")
|
||||
.assertTimed("JMP ($300,X)", 6)
|
||||
.add("""
|
||||
LDY #88
|
||||
jmpTargetX LDA #$88
|
||||
""")
|
||||
.assertA(0x88)
|
||||
.assertY(0xFF)
|
||||
.run();
|
||||
}
|
||||
|
||||
/* Test JSR */
|
||||
@Test
|
||||
public void testJsr() throws ProgramException {
|
||||
// "Easy" test that JSR + RTS work as expected and together take 12 cycles
|
||||
new TestProgram()
|
||||
.add("""
|
||||
jmp +
|
||||
sub1 rts
|
||||
+throwError 69
|
||||
+
|
||||
""")
|
||||
.assertTimed("""
|
||||
JSR sub1
|
||||
""", 12)
|
||||
.run();
|
||||
// Check that JSR pushes the expected PC values to the stack
|
||||
new TestProgram()
|
||||
.add("""
|
||||
jmp start
|
||||
test
|
||||
plx
|
||||
ply
|
||||
phy
|
||||
phx
|
||||
rts
|
||||
""")
|
||||
.test("", "RTS did not return to the correct address")
|
||||
.add("""
|
||||
start
|
||||
jsr test
|
||||
ret
|
||||
""")
|
||||
.test("""
|
||||
cpy #>ret
|
||||
beq +
|
||||
""", "Y = MSB of return address")
|
||||
.test("""
|
||||
inx
|
||||
cpx #<ret
|
||||
beq +
|
||||
""", "X = LSB of return address-1")
|
||||
.run();
|
||||
}
|
||||
}
|
80
src/test/java/jace/apple2e/VideoDHGRTest.java
Normal file
80
src/test/java/jace/apple2e/VideoDHGRTest.java
Normal file
@ -0,0 +1,80 @@
|
||||
package jace.apple2e;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.AbstractFXTest;
|
||||
import javafx.scene.image.WritableImage;
|
||||
|
||||
// This is mostly to provide execution coverage to catch null pointer or index out of range exceptions
|
||||
public class VideoDHGRTest extends AbstractFXTest {
|
||||
WritableImage image = new WritableImage(560, 192);
|
||||
|
||||
private VideoDHGR video;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
video = new VideoDHGR();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitHgrDhgrTables() {
|
||||
// Test the initialization of HGR_TO_DHGR and HGR_TO_DHGR_BW tables
|
||||
assertNotNull(video.HGR_TO_DHGR);
|
||||
assertNotNull(video.HGR_TO_DHGR_BW);
|
||||
// Add more assertions here
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitCharMap() {
|
||||
// Test the initialization of CHAR_MAP1, CHAR_MAP2, and CHAR_MAP3 arrays
|
||||
assertNotNull(video.CHAR_MAP1);
|
||||
assertNotNull(video.CHAR_MAP2);
|
||||
assertNotNull(video.CHAR_MAP3);
|
||||
// Add more assertions here
|
||||
}
|
||||
|
||||
private void writeToScreen() {
|
||||
video.getCurrentWriter().displayByte(image, 0, 0, 0, 0);
|
||||
video.getCurrentWriter().displayByte(image, 0, 4, 0, 0);
|
||||
video.getCurrentWriter().displayByte(image, 0, 190, 0, 0);
|
||||
video.getCurrentWriter().displayByte(image, -1, 0, 0, 0);
|
||||
video.getCurrentWriter().actualWriter().displayByte(image, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetYOffset() {
|
||||
// Run through all possible combinations of soft switches to ensure the correct Y offset is returned each time
|
||||
SoftSwitches[] switches = {SoftSwitches.HIRES, SoftSwitches.TEXT, SoftSwitches.PAGE2, SoftSwitches._80COL, SoftSwitches.DHIRES, SoftSwitches.MIXED};
|
||||
for (int i=0; i < Math.pow(2.0, switches.length); i++) {
|
||||
String state = "";
|
||||
for (int j=0; j < switches.length; j++) {
|
||||
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||
state += switches[j].getSwitch().getName() + "=" + (switches[j].getSwitch().getState() ? "1" : "0") + " ";
|
||||
}
|
||||
video.configureVideoMode();
|
||||
int address = video.getCurrentWriter().getYOffset(0);
|
||||
int expected = SoftSwitches.TEXT.isOn() || SoftSwitches.HIRES.isOff() ? (SoftSwitches.PAGE2.isOn() ? 0x0800 : 0x0400)
|
||||
: (SoftSwitches.PAGE2.isOn() ? 0x04000 : 0x02000);
|
||||
assertEquals("Address for mode not correct: " + state, expected, address);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayByte() {
|
||||
// Run through all possible combinations of soft switches to ensure the video writer executes without error
|
||||
SoftSwitches[] switches = {SoftSwitches.HIRES, SoftSwitches.TEXT, SoftSwitches.PAGE2, SoftSwitches._80COL, SoftSwitches.DHIRES, SoftSwitches.MIXED};
|
||||
for (int i=0; i < Math.pow(2.0, switches.length); i++) {
|
||||
for (int j=0; j < switches.length; j++) {
|
||||
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||
}
|
||||
video.configureVideoMode();
|
||||
writeToScreen();
|
||||
}
|
||||
}
|
||||
|
||||
// Add more test cases for other methods in the VideoDHGR class
|
||||
|
||||
}
|
85
src/test/java/jace/apple2e/VideoNTSCTest.java
Normal file
85
src/test/java/jace/apple2e/VideoNTSCTest.java
Normal file
@ -0,0 +1,85 @@
|
||||
package jace.apple2e;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.AbstractFXTest;
|
||||
import javafx.scene.image.WritableImage;
|
||||
|
||||
// This is mostly to provide execution coverage to catch null pointer or index out of range exceptions
|
||||
public class VideoNTSCTest extends AbstractFXTest {
|
||||
WritableImage image = new WritableImage(560, 192);
|
||||
|
||||
private VideoNTSC video;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
video = new VideoNTSC();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitHgrDhgrTables() {
|
||||
// Test the initialization of HGR_TO_DHGR and HGR_TO_DHGR_BW tables
|
||||
assertNotNull(video.HGR_TO_DHGR);
|
||||
assertNotNull(video.HGR_TO_DHGR_BW);
|
||||
// Add more assertions here
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitCharMap() {
|
||||
// Test the initialization of CHAR_MAP1, CHAR_MAP2, and CHAR_MAP3 arrays
|
||||
assertNotNull(video.CHAR_MAP1);
|
||||
assertNotNull(video.CHAR_MAP2);
|
||||
assertNotNull(video.CHAR_MAP3);
|
||||
// Add more assertions here
|
||||
}
|
||||
|
||||
private void writeToScreen() {
|
||||
video.getCurrentWriter().displayByte(image, 0, 0, 0, 0);
|
||||
video.getCurrentWriter().displayByte(image, 0, 4, 0, 0);
|
||||
video.getCurrentWriter().displayByte(image, 0, 190, 0, 0);
|
||||
video.getCurrentWriter().displayByte(image, -1, 0, 0, 0);
|
||||
video.getCurrentWriter().actualWriter().displayByte(image, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetYOffset() {
|
||||
// Run through all possible combinations of soft switches to ensure the correct Y offset is returned each time
|
||||
SoftSwitches[] switches = {SoftSwitches.HIRES, SoftSwitches.TEXT, SoftSwitches.PAGE2, SoftSwitches._80COL, SoftSwitches.DHIRES, SoftSwitches.MIXED};
|
||||
for (int i=0; i < Math.pow(2.0, switches.length); i++) {
|
||||
String state = "";
|
||||
for (int j=0; j < switches.length; j++) {
|
||||
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||
state += switches[j].getSwitch().getName() + "=" + (switches[j].getSwitch().getState() ? "1" : "0") + " ";
|
||||
}
|
||||
video.configureVideoMode();
|
||||
int address = video.getCurrentWriter().getYOffset(0);
|
||||
int expected = SoftSwitches.TEXT.isOn() || SoftSwitches.HIRES.isOff() ? (SoftSwitches.PAGE2.isOn() ? 0x0800 : 0x0400)
|
||||
: (SoftSwitches.PAGE2.isOn() ? 0x04000 : 0x02000);
|
||||
assertEquals("Address for mode not correct: " + state, expected, address);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayByte() {
|
||||
// Run through all possible combinations of soft switches to ensure the video writer executes without error
|
||||
SoftSwitches[] switches = {SoftSwitches.HIRES, SoftSwitches.TEXT, SoftSwitches.PAGE2, SoftSwitches._80COL, SoftSwitches.DHIRES, SoftSwitches.MIXED};
|
||||
for (int i=0; i < Math.pow(2.0, switches.length); i++) {
|
||||
for (int j=0; j < switches.length; j++) {
|
||||
switches[j].getSwitch().setState((i & (1 << j)) != 0);
|
||||
}
|
||||
video.configureVideoMode();
|
||||
writeToScreen();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayModes() {
|
||||
for (VideoNTSC.VideoMode mode : VideoNTSC.VideoMode.values()) {
|
||||
VideoNTSC.setVideoMode(mode, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
38
src/test/java/jace/applesoft/ApplesoftTest.java
Normal file
38
src/test/java/jace/applesoft/ApplesoftTest.java
Normal file
@ -0,0 +1,38 @@
|
||||
package jace.applesoft;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ApplesoftTest {
|
||||
|
||||
@Test
|
||||
public void fromStringTest() {
|
||||
String programSource = "10 PRINT \"Hello, World!\"\n20 PRINT \"Goodbye!\"";
|
||||
ApplesoftProgram program = ApplesoftProgram.fromString(programSource);
|
||||
assertNotNull(program);
|
||||
assertEquals(2, program.lines.size());
|
||||
Line line1 = program.lines.get(0);
|
||||
assertEquals(10, line1.getNumber());
|
||||
assertEquals(1, line1.getCommands().size());
|
||||
Command command1 = line1.getCommands().get(0);
|
||||
assertEquals(0xBA, command1.parts.get(0).getByte() & 0x0ff);
|
||||
String match = "";
|
||||
for (int idx=1; idx < command1.parts.size(); idx++) {
|
||||
match += command1.parts.get(idx).toString();
|
||||
}
|
||||
assertEquals("\"Hello, World!\"", match);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringTest() {
|
||||
Line line1 = Line.fromString("10 print \"Hello, world!\"");
|
||||
Line line2 = Line.fromString("20 print \"Goodbye!\"");
|
||||
ApplesoftProgram program = new ApplesoftProgram();
|
||||
program.lines.add(line1);
|
||||
program.lines.add(line2);
|
||||
String programSource = program.toString();
|
||||
assertEquals("10 PRINT \"Hello, world!\"\n20 PRINT \"Goodbye!\"\n", programSource);
|
||||
}
|
||||
}
|
156
src/test/java/jace/core/MemoryTest.java
Normal file
156
src/test/java/jace/core/MemoryTest.java
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2023 org.badvision.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package jace.core;
|
||||
import static jace.TestUtils.initComputer;
|
||||
import static jace.TestUtils.runAssemblyCode;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.RAM128k;
|
||||
|
||||
/**
|
||||
* Test that memory listeners fire appropriately.
|
||||
* @author brobert
|
||||
*/
|
||||
public class MemoryTest {
|
||||
static Computer computer;
|
||||
static MOS65C02 cpu;
|
||||
static RAM128k ram;
|
||||
|
||||
@BeforeClass
|
||||
public static void setupClass() {
|
||||
initComputer();
|
||||
SoundMixer.MUTE = true;
|
||||
computer = Emulator.withComputer(c->c, null);
|
||||
cpu = (MOS65C02) computer.getCpu();
|
||||
ram = (RAM128k) computer.getMemory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertMemoryConfiguredCorrectly() {
|
||||
assertEquals("Active read bank 3 should be main memory page 3",
|
||||
ram.mainMemory.getMemoryPage(3),
|
||||
ram.activeRead.getMemoryPage(3));
|
||||
|
||||
assertEquals("Active write bank 3 should be main memory page 3",
|
||||
ram.mainMemory.getMemoryPage(3),
|
||||
ram.activeWrite.getMemoryPage(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListenerRelevance() throws Exception {
|
||||
AtomicInteger anyEventCaught = new AtomicInteger();
|
||||
RAMListener anyListener = new RAMListener("Execution test", RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
anyEventCaught.incrementAndGet();
|
||||
}
|
||||
};
|
||||
|
||||
AtomicInteger readAnyEventCaught = new AtomicInteger();
|
||||
RAMListener readAnyListener = new RAMListener("Execution test 1", RAMEvent.TYPE.READ, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
readAnyEventCaught.incrementAndGet();
|
||||
}
|
||||
};
|
||||
|
||||
AtomicInteger writeEventCaught = new AtomicInteger();
|
||||
RAMListener writeListener = new RAMListener("Execution test 2", RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
writeEventCaught.incrementAndGet();
|
||||
}
|
||||
};
|
||||
|
||||
AtomicInteger executeEventCaught = new AtomicInteger();
|
||||
RAMListener executeListener = new RAMListener("Execution test 3", RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
executeEventCaught.incrementAndGet();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
RAMEvent readDataEvent = new RAMEvent(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, 0x100, 0, 0);
|
||||
RAMEvent readOperandEvent = new RAMEvent(RAMEvent.TYPE.READ_OPERAND, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, 0x100, 0, 0);
|
||||
RAMEvent executeEvent = new RAMEvent(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, 0x100, 0, 0);
|
||||
RAMEvent writeEvent = new RAMEvent(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, 0x100, 0, 0);
|
||||
|
||||
// Any listener
|
||||
assertTrue("Any listener should handle all events", anyListener.isRelevant(readDataEvent));
|
||||
assertTrue("Any listener should handle all events", anyListener.isRelevant(readOperandEvent));
|
||||
assertTrue("Any listener should handle all events", anyListener.isRelevant(executeEvent));
|
||||
assertTrue("Any listener should handle all events", anyListener.isRelevant(writeEvent));
|
||||
|
||||
// Read listener
|
||||
assertTrue("Read listener should handle all read events", readAnyListener.isRelevant(readDataEvent));
|
||||
assertTrue("Read listener should handle all read events", readAnyListener.isRelevant(readOperandEvent));
|
||||
assertTrue("Read listener should handle all read events", readAnyListener.isRelevant(executeEvent));
|
||||
assertFalse("Read listener should ignore write events", readAnyListener.isRelevant(writeEvent));
|
||||
|
||||
// Write listener
|
||||
assertFalse("Write listener should ignore all read events", writeListener.isRelevant(readDataEvent));
|
||||
assertFalse("Write listener should ignore all read events", writeListener.isRelevant(readOperandEvent));
|
||||
assertFalse("Write listener should ignore all read events", writeListener.isRelevant(executeEvent));
|
||||
assertTrue("Write listener should handle write events", writeListener.isRelevant(writeEvent));
|
||||
|
||||
// Execution listener
|
||||
assertTrue("Execute listener should only catch execution events", executeListener.isRelevant(executeEvent));
|
||||
assertFalse("Execute listener should only catch execution events", executeListener.isRelevant(readDataEvent));
|
||||
assertFalse("Execute listener should only catch execution events", executeListener.isRelevant(readOperandEvent));
|
||||
assertFalse("Execute listener should only catch execution events", executeListener.isRelevant(writeEvent));
|
||||
|
||||
ram.addListener(anyListener);
|
||||
ram.addListener(executeListener);
|
||||
ram.addListener(readAnyListener);
|
||||
ram.addListener(writeListener);
|
||||
|
||||
runAssemblyCode("NOP", 0x0100, 2);
|
||||
|
||||
assertEquals("Should have no writes for 0x0100", 0, writeEventCaught.get());
|
||||
assertEquals("Should have read event for 0x0100", 1, readAnyEventCaught.get());
|
||||
assertEquals("Should have execute for 0x0100", 1, executeEventCaught.get());
|
||||
}
|
||||
}
|
84
src/test/java/jace/core/SoundTest.java
Normal file
84
src/test/java/jace/core/SoundTest.java
Normal file
@ -0,0 +1,84 @@
|
||||
package jace.core;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.AbstractFXTest;
|
||||
import jace.core.SoundMixer.SoundBuffer;
|
||||
import jace.core.SoundMixer.SoundError;
|
||||
|
||||
public class SoundTest extends AbstractFXTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
System.out.println("Init sound");
|
||||
Utility.setHeadlessMode(false);
|
||||
SoundMixer.initSound();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
Utility.setHeadlessMode(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
//(Only use this to ensure the sound engine produces audible output, it's otherwise annoying to hear all the time)
|
||||
public void soundGenerationTest() throws SoundError {
|
||||
try {
|
||||
System.out.println("Performing sound test...");
|
||||
SoundMixer mixer = new SoundMixer();
|
||||
System.out.println("Attach mixer");
|
||||
mixer.attach();
|
||||
System.out.println("Allocate buffer");
|
||||
SoundBuffer buffer = SoundMixer.createBuffer(false);
|
||||
System.out.println("Generate sound");
|
||||
// for (int i = 0; i < 100000; i++) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
// Gerate a sin wave with a frequency sweep so we can tell if the buffer is being fully processed
|
||||
double x = Math.sin(i*i * 0.0001);
|
||||
buffer.playSample((short) (Short.MAX_VALUE * x));
|
||||
}
|
||||
System.out.println("Closing buffer");
|
||||
buffer.shutdown();
|
||||
System.out.println("Deactivating sound");
|
||||
mixer.detach();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
// Commented out because it's annoying to hear all the time, but it worked without issues
|
||||
public void mixerTortureTest() throws SoundError, InterruptedException, ExecutionException {
|
||||
System.out.println("Performing speaker tick test...");
|
||||
SoundMixer.initSound();
|
||||
System.out.println("Create mixer");
|
||||
SoundMixer mixer = new SoundMixer();
|
||||
System.out.println("Attach mixer");
|
||||
mixer.attach();
|
||||
// We want to create and destroy lots of buffers to make sure we don't have any memory leaks
|
||||
// for (int i = 0; i < 10000; i++) {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
// Print status every 1000 iterations
|
||||
if (i % 1000 == 0) {
|
||||
System.out.println("Iteration %d".formatted(i));
|
||||
}
|
||||
SoundBuffer buffer = SoundMixer.createBuffer(false);
|
||||
for (int j = 0; j < SoundMixer.BUFFER_SIZE*2; j++) {
|
||||
// Gerate a sin wave with a frequency sweep so we can tell if the buffer is being fully processed
|
||||
double x = Math.sin(j*j * 0.0001);
|
||||
buffer.playSample((short) (Short.MAX_VALUE * x));
|
||||
}
|
||||
buffer.flush();
|
||||
buffer.shutdown();
|
||||
}
|
||||
// Assert buffers are empty
|
||||
assertEquals("All buffers should be empty", 0, mixer.getActiveBuffers());
|
||||
System.out.println("Deactivating sound");
|
||||
mixer.detach();
|
||||
}
|
||||
}
|
42
src/test/java/jace/core/UtilityTest.java
Normal file
42
src/test/java/jace/core/UtilityTest.java
Normal file
@ -0,0 +1,42 @@
|
||||
package jace.core;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class UtilityTest {
|
||||
|
||||
@Test
|
||||
public void testLevenshteinDistance() {
|
||||
String s1 = "kitten";
|
||||
String s2 = "sitting";
|
||||
int distance = Utility.levenshteinDistance(s1, s2);
|
||||
assertEquals(3, distance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdjustedLevenshteinDistance() {
|
||||
String s1 = "kitten";
|
||||
String s2 = "sitting";
|
||||
int adjustedDistance = Utility.adjustedLevenshteinDistance(s1, s2);
|
||||
assertEquals(4, adjustedDistance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRankMatch() {
|
||||
String s1 = "apple";
|
||||
String s2 = "banana";
|
||||
double score = Utility.rankMatch(s1, s2, 3);
|
||||
assertEquals(0, score, 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindBestMatch() {
|
||||
String match = "apple";
|
||||
Collection<String> search = Arrays.asList("banana", "orange", "apple pie");
|
||||
String bestMatch = Utility.findBestMatch(match, search);
|
||||
assertEquals("apple pie", bestMatch);
|
||||
}
|
||||
}
|
121
src/test/java/jace/hardware/CardAppleMouseTest.java
Normal file
121
src/test/java/jace/hardware/CardAppleMouseTest.java
Normal file
@ -0,0 +1,121 @@
|
||||
package jace.hardware;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.AbstractFXTest;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.SCOPE;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.RAMEvent.VALUE;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
|
||||
public class CardAppleMouseTest extends AbstractFXTest {
|
||||
|
||||
private CardAppleMouse cardAppleMouse;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cardAppleMouse = new CardAppleMouse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeviceName() {
|
||||
assertEquals("Apple Mouse", cardAppleMouse.getDeviceName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReset() {
|
||||
cardAppleMouse.mode = 1;
|
||||
cardAppleMouse.clampWindow = new Rectangle2D(10, 10, 100, 100);
|
||||
cardAppleMouse.detach();
|
||||
|
||||
cardAppleMouse.reset();
|
||||
|
||||
assertEquals(0, cardAppleMouse.mode);
|
||||
assertEquals(new Rectangle2D(0, 0, 0x03ff, 0x03ff), cardAppleMouse.clampWindow);
|
||||
}
|
||||
|
||||
@Test
|
||||
// Test mouseHandler responses to mouse events
|
||||
public void testMouseHandler() {
|
||||
MouseEvent clickEvent = new MouseEvent(MouseEvent.MOUSE_CLICKED,0,0,0,0,MouseButton.PRIMARY,1,false,false,false, false, false, false, false, false, false, false, null);
|
||||
MouseEvent releaseEvent = new MouseEvent(MouseEvent.MOUSE_RELEASED,0,0,0,0,MouseButton.PRIMARY,1,false,false,false, false, false, false, false, false, false, false, null);
|
||||
MouseEvent dragEvent = new MouseEvent(MouseEvent.MOUSE_DRAGGED,0,0,0,0,MouseButton.PRIMARY,1,false,false,false, false, false, false, false, false, false, false, null);
|
||||
|
||||
cardAppleMouse.mode = 1;
|
||||
cardAppleMouse.mouseHandler.handle(clickEvent);
|
||||
cardAppleMouse.mouseHandler.handle(dragEvent);
|
||||
cardAppleMouse.mouseHandler.handle(releaseEvent);
|
||||
assertEquals(1, cardAppleMouse.mode);
|
||||
}
|
||||
|
||||
@Test
|
||||
// Test firmware entry points
|
||||
public void testFirmware() {
|
||||
// Test reads
|
||||
RAMEvent event = new RAMEvent(TYPE.READ, SCOPE.ANY, VALUE.ANY, 0, 0, 0);
|
||||
cardAppleMouse.handleFirmwareAccess(0x80, TYPE.EXECUTE, 0, event);
|
||||
assertEquals(0x60, event.getNewValue());
|
||||
event.setNewValue(0x00);
|
||||
cardAppleMouse.handleFirmwareAccess(0x81, TYPE.EXECUTE, 0, event);
|
||||
assertEquals(0x60, event.getNewValue());
|
||||
event.setNewValue(0x00);
|
||||
cardAppleMouse.handleFirmwareAccess(0x82, TYPE.EXECUTE, 0, event);
|
||||
assertEquals(0x60, event.getNewValue());
|
||||
event.setNewValue(0x00);
|
||||
cardAppleMouse.handleFirmwareAccess(0x83, TYPE.EXECUTE, 0, event);
|
||||
assertEquals(0x60, event.getNewValue());
|
||||
event.setNewValue(0x00);
|
||||
cardAppleMouse.handleFirmwareAccess(0x84, TYPE.EXECUTE, 0, event);
|
||||
assertEquals(0x60, event.getNewValue());
|
||||
event.setNewValue(0x00);
|
||||
cardAppleMouse.handleFirmwareAccess(0x85, TYPE.EXECUTE, 0, event);
|
||||
assertEquals(0x60, event.getNewValue());
|
||||
event.setNewValue(0x00);
|
||||
cardAppleMouse.handleFirmwareAccess(0x86, TYPE.EXECUTE, 0, event);
|
||||
assertEquals(0x60, event.getNewValue());
|
||||
event.setNewValue(0x00);
|
||||
cardAppleMouse.handleFirmwareAccess(0x87, TYPE.EXECUTE, 0, event);
|
||||
assertEquals(0x60, event.getNewValue());
|
||||
event.setNewValue(0x00);
|
||||
cardAppleMouse.handleFirmwareAccess(0x88, TYPE.EXECUTE, 0, event);
|
||||
assertEquals(0x60, event.getNewValue());
|
||||
event.setNewValue(0x00);
|
||||
cardAppleMouse.handleFirmwareAccess(0x05, TYPE.READ, 0, event);
|
||||
assertEquals(0x38, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x07, TYPE.READ, 0, event);
|
||||
assertEquals(0x18, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x08, TYPE.READ, 0, event);
|
||||
assertEquals(0x01, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x0B, TYPE.READ, 0, event);
|
||||
assertEquals(0x01, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x0C, TYPE.READ, 0, event);
|
||||
assertEquals(0x20, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x11, TYPE.READ, 0, event);
|
||||
assertEquals(0x00, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x12, TYPE.READ, 0, event);
|
||||
assertEquals(0x080, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x13, TYPE.READ, 0, event);
|
||||
assertEquals(0x081, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x14, TYPE.READ, 0, event);
|
||||
assertEquals(0x082, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x15, TYPE.READ, 0, event);
|
||||
assertEquals(0x083, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x16, TYPE.READ, 0, event);
|
||||
assertEquals(0x084, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x17, TYPE.READ, 0, event);
|
||||
assertEquals(0x085, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x18, TYPE.READ, 0, event);
|
||||
assertEquals(0x086, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x19, TYPE.READ, 0, event);
|
||||
assertEquals(0x087, event.getNewValue());
|
||||
cardAppleMouse.handleFirmwareAccess(0x1A, TYPE.READ, 0, event);
|
||||
assertEquals(0x088, event.getNewValue());
|
||||
}
|
||||
|
||||
}
|
54
src/test/java/jace/hardware/CardSSCTest.java
Normal file
54
src/test/java/jace/hardware/CardSSCTest.java
Normal file
@ -0,0 +1,54 @@
|
||||
package jace.hardware;
|
||||
|
||||
import static jace.hardware.CardSSC.ACIA_Command;
|
||||
import static jace.hardware.CardSSC.ACIA_Control;
|
||||
import static jace.hardware.CardSSC.ACIA_Data;
|
||||
import static jace.hardware.CardSSC.ACIA_Status;
|
||||
import static jace.hardware.CardSSC.SW1;
|
||||
import static jace.hardware.CardSSC.SW2_CTS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.AbstractFXTest;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.SCOPE;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.RAMEvent.VALUE;
|
||||
|
||||
public class CardSSCTest extends AbstractFXTest {
|
||||
|
||||
private CardSSC cardSSC;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cardSSC = new CardSSC();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeviceName() {
|
||||
assertEquals("Super Serial Card", cardSSC.getDeviceName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSlot() {
|
||||
cardSSC.setSlot(1);
|
||||
// assertEquals("Slot 1", cardSSC.activityIndicator.getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReset() {
|
||||
cardSSC.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIOAccess() {
|
||||
RAMEvent event = new RAMEvent(TYPE.READ_DATA, SCOPE.ANY, VALUE.ANY, 0, 0, 0);
|
||||
int[] registers = {SW1, SW2_CTS, ACIA_Data, ACIA_Control, ACIA_Status, ACIA_Command};
|
||||
for (int register : registers) {
|
||||
cardSSC.handleIOAccess(register, TYPE.READ_DATA, 0, event);
|
||||
cardSSC.handleIOAccess(register, TYPE.WRITE, 0, event);
|
||||
}
|
||||
}
|
||||
}
|
62
src/test/java/jace/hardware/FloppyDiskTest.java
Normal file
62
src/test/java/jace/hardware/FloppyDiskTest.java
Normal file
@ -0,0 +1,62 @@
|
||||
package jace.hardware;
|
||||
import static jace.hardware.FloppyDisk.PRODOS_SECTOR_ORDER;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.AbstractFXTest;
|
||||
import jace.hardware.FloppyDisk.SectorOrder;
|
||||
|
||||
public class FloppyDiskTest extends AbstractFXTest {
|
||||
|
||||
private FloppyDisk floppyDisk;
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
floppyDisk = new FloppyDisk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readDisk_ValidDiskFile_Success() throws IOException {
|
||||
// Create a sample disk file
|
||||
byte[] diskData = new byte[232960];
|
||||
File diskFile = File.createTempFile("test_disk", ".dsk");
|
||||
diskFile.deleteOnExit();
|
||||
ByteArrayInputStream diskInputStream = new ByteArrayInputStream(diskData);
|
||||
|
||||
// Read the disk file
|
||||
floppyDisk.readDisk(diskInputStream, SectorOrder.DOS);
|
||||
|
||||
// Verify the disk properties
|
||||
assert(floppyDisk.isNibblizedImage);
|
||||
assertEquals(254, floppyDisk.volumeNumber);
|
||||
assertEquals(0, floppyDisk.headerLength);
|
||||
assertEquals(232960, floppyDisk.nibbles.length);
|
||||
assertEquals("Sector order not null", true, null != floppyDisk.currentSectorOrder);
|
||||
assertNull(floppyDisk.diskPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nibblize_ValidNibbles_Success() throws IOException {
|
||||
// Create a sample nibbles array
|
||||
byte[] nibbles = new byte[FloppyDisk.DISK_NIBBLE_LENGTH];
|
||||
for (int i = 0; i < nibbles.length; i++) {
|
||||
nibbles[i] = (byte) (i % 256);
|
||||
}
|
||||
floppyDisk.currentSectorOrder = PRODOS_SECTOR_ORDER;
|
||||
// Nibblize the nibbles array
|
||||
byte[] nibblizedData = floppyDisk.nibblize(nibbles);
|
||||
|
||||
// Verify the nibblized data
|
||||
assertEquals(FloppyDisk.DISK_NIBBLE_LENGTH, nibblizedData.length);
|
||||
// for (int i = 0; i < nibblizedData.length; i++) {
|
||||
// assertEquals((i % 256) >> 2, nibblizedData[i]);
|
||||
// }
|
||||
}
|
||||
}
|
44
src/test/java/jace/hardware/PassportMidiInterfaceTest.java
Normal file
44
src/test/java/jace/hardware/PassportMidiInterfaceTest.java
Normal file
@ -0,0 +1,44 @@
|
||||
package jace.hardware;
|
||||
|
||||
import static jace.hardware.PassportMidiInterface.ACIA_RECV;
|
||||
import static jace.hardware.PassportMidiInterface.ACIA_STATUS;
|
||||
import static jace.hardware.PassportMidiInterface.TIMER1_LSB;
|
||||
import static jace.hardware.PassportMidiInterface.TIMER1_MSB;
|
||||
import static jace.hardware.PassportMidiInterface.TIMER2_LSB;
|
||||
import static jace.hardware.PassportMidiInterface.TIMER2_MSB;
|
||||
import static jace.hardware.PassportMidiInterface.TIMER3_LSB;
|
||||
import static jace.hardware.PassportMidiInterface.TIMER3_MSB;
|
||||
import static jace.hardware.PassportMidiInterface.TIMER_CONTROL_1;
|
||||
import static jace.hardware.PassportMidiInterface.TIMER_CONTROL_2;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import jace.AbstractFXTest;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.SCOPE;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.RAMEvent.VALUE;
|
||||
|
||||
|
||||
public class PassportMidiInterfaceTest extends AbstractFXTest {
|
||||
PassportMidiInterface midi = new PassportMidiInterface();
|
||||
|
||||
@Test
|
||||
public void testDeviceSelection() {
|
||||
assertNotNull(PassportMidiInterface.preferredMidiDevice.getSelections());
|
||||
assertNotEquals(0, PassportMidiInterface.preferredMidiDevice.getSelections().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIOAccess() {
|
||||
RAMEvent event = new RAMEvent(TYPE.READ_DATA, SCOPE.ANY, VALUE.ANY, 0, 0, 0);
|
||||
int[] registers = {ACIA_STATUS, ACIA_RECV, TIMER_CONTROL_1, TIMER_CONTROL_2, TIMER1_LSB, TIMER1_MSB, TIMER2_LSB, TIMER2_MSB, TIMER3_LSB, TIMER3_MSB};
|
||||
for (int register : registers) {
|
||||
midi.handleIOAccess(register, TYPE.READ_DATA, 0, event);
|
||||
midi.handleIOAccess(register, TYPE.WRITE, 0, event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
50
src/test/java/jace/hardware/mockingboard/PSGTest.java
Normal file
50
src/test/java/jace/hardware/mockingboard/PSGTest.java
Normal file
@ -0,0 +1,50 @@
|
||||
package jace.hardware.mockingboard;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PSGTest {
|
||||
|
||||
private PSG psg;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
psg = new PSG(0, 100, 44100, "name", 255);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setControl_InactiveCommand_NoAction() {
|
||||
psg.setControl(0); // Set control to inactive
|
||||
// Assert that no action is taken
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setControl_LatchCommand_SelectedRegUpdated() {
|
||||
psg.setControl(1); // Set control to latch
|
||||
// Assert that selectedReg is updated correctly
|
||||
// Add your assertions here
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setControl_ReadCommand_BusUpdated() {
|
||||
psg.setControl(2); // Set control to read
|
||||
// Assert that bus is updated correctly
|
||||
// Add your assertions here
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setControl_WriteCommand_RegUpdated() {
|
||||
psg.setControl(3); // Set control to write
|
||||
// Assert that the corresponding register is updated correctly
|
||||
// Add your assertions here
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateTest() {
|
||||
AtomicInteger out = new AtomicInteger();
|
||||
psg.update(out, false, out, false, out, false);
|
||||
psg.update(out, true, out, true, out, true);
|
||||
}
|
||||
}
|
49
src/test/java/jace/hardware/mockingboard/R6522Test.java
Normal file
49
src/test/java/jace/hardware/mockingboard/R6522Test.java
Normal file
@ -0,0 +1,49 @@
|
||||
package jace.hardware.mockingboard;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class R6522Test {
|
||||
R6522 r6522 = new R6522() {
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "name";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendOutputA(int value) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendOutputB(int value) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public int receiveOutputA() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int receiveOutputB() {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testWriteRegs() {
|
||||
for (R6522.Register reg : R6522.Register.values()) {
|
||||
r6522.writeRegister(reg.val, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadRegs() {
|
||||
for (R6522.Register reg : R6522.Register.values()) {
|
||||
r6522.readRegister(reg.val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
83
src/test/java/jace/ide/ApplesoftTest.java
Normal file
83
src/test/java/jace/ide/ApplesoftTest.java
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package jace.ide;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import static jace.TestUtils.initComputer;
|
||||
import jace.applesoft.ApplesoftProgram;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author blurry
|
||||
*/
|
||||
public class ApplesoftTest {
|
||||
|
||||
public ApplesoftTest() {
|
||||
}
|
||||
|
||||
static Byte[] lemonadeStandBinary;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpClass() throws URISyntaxException, IOException {
|
||||
initComputer();
|
||||
byte[] lemonadeStand = readBinary("/jace/lemonade_stand.bin");
|
||||
lemonadeStandBinary = ApplesoftProgram.toObjects(lemonadeStand);
|
||||
}
|
||||
|
||||
public static byte[] readBinary(String path) throws IOException, URISyntaxException {
|
||||
Path resource = Paths.get(ApplesoftTest.class.getResource(path).toURI());
|
||||
return Files.readAllBytes(resource);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownClass() {
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeBinaryTest() {
|
||||
ApplesoftProgram program = ApplesoftProgram.fromBinary(Arrays.asList(lemonadeStandBinary), 0x0801);
|
||||
assertNotNull(program);
|
||||
assertNotSame("", program.toString());
|
||||
assertEquals("Lemonade stand has 380 lines", 380, program.getLength());
|
||||
assertTrue("Should have last line 31114", program.toString().contains("31114 "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roundTripStringComparisonTest() {
|
||||
ApplesoftProgram program = ApplesoftProgram.fromBinary(Arrays.asList(lemonadeStandBinary), 0x0801);
|
||||
String serialized = program.toString();
|
||||
ApplesoftProgram deserialized = ApplesoftProgram.fromString(serialized);
|
||||
String[] serializedLines = serialized.split("\\n");
|
||||
String[] researializedLines = deserialized.toString().split("\\n");
|
||||
assertEquals("Lemonade stand has 380 lines", 380, deserialized.getLength());
|
||||
assertArrayEquals("Program listing should be not change if re-keyed in as printed", serializedLines, researializedLines);
|
||||
}
|
||||
}
|
301
src/test/resources/jace/bcd_test.asm
Normal file
301
src/test/resources/jace/bcd_test.asm
Normal file
@ -0,0 +1,301 @@
|
||||
; Modified and adapted from http://www.6502.org/tutorials/decimal_mode.html#B
|
||||
;
|
||||
; Verify decimal mode behavior
|
||||
; Written by Bruce Clark. This code is public domain.
|
||||
;
|
||||
; Returns:
|
||||
; ERROR = 0 if the test passed
|
||||
; ERROR = 1 if the test failed
|
||||
;
|
||||
; This routine requires 17 bytes of RAM -- 1 byte each for:
|
||||
; AR, CF, DA, DNVZC, ERROR, HA, HNVZC, N1, N1H, N1L, N2, N2L, NF, VF, and ZF
|
||||
; and 2 bytes for N2H
|
||||
;
|
||||
; Variables:
|
||||
; N1 and N2 are the two numbers to be added or subtracted
|
||||
; N1H, N1L, N2H, and N2L are the upper 4 bits and lower 4 bits of N1 and N2
|
||||
; DA and DNVZC are the actual accumulator and flag results in decimal mode
|
||||
; HA and HNVZC are the accumulator and flag results when N1 and N2 are
|
||||
; added or subtracted using binary arithmetic
|
||||
; AR, NF, VF, ZF, and CF are the predicted decimal mode accumulator and
|
||||
; flag results, calculated using binary arithmetic
|
||||
;
|
||||
; This program takes approximately 1 minute at 1 MHz (a few seconds more on
|
||||
; a 65C02 than a 6502 or 65816)
|
||||
;
|
||||
AR = $06
|
||||
NF = $07
|
||||
VF = $08
|
||||
CF = $09
|
||||
ZF = $0a
|
||||
DA = $0b
|
||||
DNVZC = $0c
|
||||
HA = $0d
|
||||
HNVZC = $0e
|
||||
N1 = $0f
|
||||
N1H = $10
|
||||
N1L = $11
|
||||
N2 = $12
|
||||
N2H = $13
|
||||
N2L = $15
|
||||
|
||||
TEST ;LDY #1 ; initialize Y (used to loop through carry flag values)
|
||||
;STY ERROR ; store 1 in ERROR until the test passes
|
||||
LDA #0 ; initialize N1 and N2
|
||||
STA N1
|
||||
STA N2
|
||||
LOOP1 LDA N2 ; N2L = N2 & $0F
|
||||
AND #$0F ; [1] see text
|
||||
STA N2L
|
||||
LDA N2 ; N2H = N2 & $F0
|
||||
AND #$F0 ; [2] see text
|
||||
STA N2H
|
||||
ORA #$0F ; N2H+1 = (N2 & $F0) + $0F
|
||||
STA N2H+1
|
||||
LOOP2 LDA N1 ; N1L = N1 & $0F
|
||||
AND #$0F ; [3] see text
|
||||
STA N1L
|
||||
LDA N1 ; N1H = N1 & $F0
|
||||
AND #$F0 ; [4] see text
|
||||
STA N1H
|
||||
JSR ADD
|
||||
JSR A65C02
|
||||
JSR COMPARE
|
||||
BNE ADD_ERROR
|
||||
JSR SUB
|
||||
JSR S65C02
|
||||
JSR COMPARE
|
||||
BNE SUB_ERROR
|
||||
INC N1 ; [5] see text
|
||||
BNE LOOP2 ; loop through all 256 values of N1
|
||||
INC N2 ; [6] see text
|
||||
BNE LOOP1 ; loop through all 256 values of N2
|
||||
DEY
|
||||
BPL LOOP1 ; loop through both values of the carry flag
|
||||
SUCCESS +success
|
||||
ADD_ERROR
|
||||
CPY #1 ; Set carry based on Y reg
|
||||
LDA DA
|
||||
LDX N1
|
||||
LDY N2
|
||||
+throwError 0
|
||||
SUB_ERROR
|
||||
CPY #1 ; Set carry based on Y reg
|
||||
LDA DA
|
||||
LDX N1
|
||||
LDY N2
|
||||
+throwError 1
|
||||
|
||||
; Calculate the actual decimal mode accumulator and flags, the accumulator
|
||||
; and flag results when N1 is added to N2 using binary arithmetic, the
|
||||
; predicted accumulator result, the predicted carry flag, and the predicted
|
||||
; V flag
|
||||
;
|
||||
ADD SED ; decimal mode
|
||||
CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||
LDA N1
|
||||
ADC N2
|
||||
STA DA ; actual accumulator result in decimal mode
|
||||
PHP
|
||||
PLA
|
||||
STA DNVZC ; actual flags result in decimal mode
|
||||
CLD ; binary mode
|
||||
CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||
LDA N1
|
||||
ADC N2
|
||||
STA HA ; accumulator result of N1+N2 using binary arithmetic
|
||||
|
||||
PHP
|
||||
PLA
|
||||
STA HNVZC ; flags result of N1+N2 using binary arithmetic
|
||||
CPY #1
|
||||
LDA N1L
|
||||
ADC N2L
|
||||
CMP #$0A
|
||||
LDX #0
|
||||
BCC A1
|
||||
INX
|
||||
ADC #5 ; add 6 (carry is set)
|
||||
AND #$0F
|
||||
SEC
|
||||
A1 ORA N1H
|
||||
;
|
||||
; if N1L + N2L < $0A, then add N2 & $F0
|
||||
; if N1L + N2L >= $0A, then add (N2 & $F0) + $0F + 1 (carry is set)
|
||||
;
|
||||
ADC N2H,X
|
||||
PHP
|
||||
BCS A2
|
||||
CMP #$A0
|
||||
BCC A3
|
||||
A2 ADC #$5F ; add $60 (carry is set)
|
||||
SEC
|
||||
A3 STA AR ; predicted accumulator result
|
||||
PHP
|
||||
PLA
|
||||
STA CF ; predicted carry result
|
||||
PLA
|
||||
;
|
||||
; note that all 8 bits of the P register are stored in VF
|
||||
;
|
||||
STA VF ; predicted V flags
|
||||
RTS
|
||||
|
||||
; Calculate the actual decimal mode accumulator and flags, and the
|
||||
; accumulator and flag results when N2 is subtracted from N1 using binary
|
||||
; arithmetic
|
||||
;
|
||||
SUB SED ; decimal mode
|
||||
CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||
LDA N1
|
||||
SBC N2
|
||||
STA DA ; actual accumulator result in decimal mode
|
||||
PHP
|
||||
PLA
|
||||
STA DNVZC ; actual flags result in decimal mode
|
||||
CLD ; binary mode
|
||||
CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||
LDA N1
|
||||
SBC N2
|
||||
STA HA ; accumulator result of N1-N2 using binary arithmetic
|
||||
|
||||
PHP
|
||||
PLA
|
||||
STA HNVZC ; flags result of N1-N2 using binary arithmetic
|
||||
RTS
|
||||
|
||||
; Calculate the predicted SBC accumulator result for the 6502 and 65816
|
||||
|
||||
;
|
||||
SUB1 CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||
LDA N1L
|
||||
SBC N2L
|
||||
LDX #0
|
||||
BCS S11
|
||||
INX
|
||||
SBC #5 ; subtract 6 (carry is clear)
|
||||
AND #$0F
|
||||
CLC
|
||||
S11 ORA N1H
|
||||
;
|
||||
; if N1L - N2L >= 0, then subtract N2 & $F0
|
||||
; if N1L - N2L < 0, then subtract (N2 & $F0) + $0F + 1 (carry is clear)
|
||||
;
|
||||
SBC N2H,X
|
||||
BCS S12
|
||||
SBC #$5F ; subtract $60 (carry is clear)
|
||||
S12 STA AR
|
||||
RTS
|
||||
|
||||
; Calculate the predicted SBC accumulator result for the 6502 and 65C02
|
||||
|
||||
;
|
||||
SUB2 CPY #1 ; set carry if Y = 1, clear carry if Y = 0
|
||||
LDA N1L
|
||||
SBC N2L
|
||||
LDX #0
|
||||
BCS S21
|
||||
INX
|
||||
AND #$0F
|
||||
CLC
|
||||
S21 ORA N1H
|
||||
;
|
||||
; if N1L - N2L >= 0, then subtract N2 & $F0
|
||||
; if N1L - N2L < 0, then subtract (N2 & $F0) + $0F + 1 (carry is clear)
|
||||
;
|
||||
SBC N2H,X
|
||||
BCS S22
|
||||
SBC #$5F ; subtract $60 (carry is clear)
|
||||
S22 CPX #0
|
||||
BEQ S23
|
||||
SBC #6
|
||||
S23 STA AR ; predicted accumulator result
|
||||
RTS
|
||||
|
||||
; Compare accumulator actual results to predicted results
|
||||
;
|
||||
; Return:
|
||||
; Z flag = 1 (BEQ branch) if same
|
||||
; Z flag = 0 (BNE branch) if different
|
||||
;
|
||||
COMPARE LDA DA
|
||||
CMP AR
|
||||
+breakpoint 1
|
||||
BNE C1
|
||||
LDA DNVZC ; [7] see text
|
||||
EOR NF
|
||||
AND #$80 ; mask off N flag
|
||||
+breakpoint 2
|
||||
BNE C1
|
||||
LDA DNVZC ; [8] see text
|
||||
EOR VF
|
||||
AND #$40 ; mask off V flag
|
||||
+breakpoint 3
|
||||
BNE C1 ; [9] see text
|
||||
LDA DNVZC
|
||||
EOR ZF ; mask off Z flag
|
||||
AND #2
|
||||
+breakpoint 4
|
||||
BNE C1 ; [10] see text
|
||||
LDA DNVZC
|
||||
EOR CF
|
||||
AND #1 ; mask off C flag
|
||||
+breakpoint 5
|
||||
C1 RTS
|
||||
|
||||
; These routines store the predicted values for ADC and SBC for the 6502,
|
||||
; 65C02, and 65816 in AR, CF, NF, VF, and ZF
|
||||
|
||||
A6502 LDA VF
|
||||
;
|
||||
; since all 8 bits of the P register were stored in VF, bit 7 of VF contains
|
||||
; the N flag for NF
|
||||
;
|
||||
STA NF
|
||||
LDA HNVZC
|
||||
STA ZF
|
||||
RTS
|
||||
|
||||
S6502 JSR SUB1
|
||||
LDA HNVZC
|
||||
STA NF
|
||||
STA VF
|
||||
STA ZF
|
||||
STA CF
|
||||
RTS
|
||||
|
||||
A65C02 LDA AR
|
||||
PHP
|
||||
PLA
|
||||
STA NF
|
||||
STA ZF
|
||||
RTS
|
||||
|
||||
S65C02 JSR SUB2
|
||||
LDA AR
|
||||
PHP
|
||||
PLA
|
||||
STA NF
|
||||
STA ZF
|
||||
LDA HNVZC
|
||||
STA VF
|
||||
STA CF
|
||||
RTS
|
||||
|
||||
A65816 LDA AR
|
||||
PHP
|
||||
PLA
|
||||
STA NF
|
||||
STA ZF
|
||||
RTS
|
||||
|
||||
S65816 JSR SUB1
|
||||
LDA AR
|
||||
PHP
|
||||
PLA
|
||||
STA NF
|
||||
STA ZF
|
||||
LDA HNVZC
|
||||
STA VF
|
||||
STA CF
|
||||
RTS
|
Loading…
Reference in New Issue
Block a user