diff --git a/src/main/java/jace/apple2e/MOS65C02.java b/src/main/java/jace/apple2e/MOS65C02.java index c3a68f5..041a7ea 100644 --- a/src/main/java/jace/apple2e/MOS65C02.java +++ b/src/main/java/jace/apple2e/MOS65C02.java @@ -414,6 +414,8 @@ public class MOS65C02 extends CPU { ABSOLUTE_X(3, "$~2~1,X", (cpu) -> { int address2 = cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false); int address = 0x0FFFF & (address2 + cpu.X); + // False read + cpu.getMemory().read(address, TYPE.READ_DATA, true, false); cpu.setPageBoundaryPenalty((address & 0x00ff00) != (address2 & 0x00ff00)); cpu.setPageBoundaryApplied(true); return address; @@ -421,6 +423,8 @@ public class MOS65C02 extends CPU { ABSOLUTE_Y(3, "$~2~1,Y", (cpu) -> { int address2 = cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false); int address = 0x0FFFF & (address2 + cpu.Y); + // False read + cpu.getMemory().read(address, TYPE.READ_DATA, true, false); cpu.setPageBoundaryPenalty((address & 0x00ff00) != (address2 & 0x00ff00)); cpu.setPageBoundaryApplied(true); return address; diff --git a/src/main/java/jace/apple2e/softswitch/Memory2SoftSwitch.java b/src/main/java/jace/apple2e/softswitch/Memory2SoftSwitch.java index 5a93181..64f7e99 100644 --- a/src/main/java/jace/apple2e/softswitch/Memory2SoftSwitch.java +++ b/src/main/java/jace/apple2e/softswitch/Memory2SoftSwitch.java @@ -16,6 +16,7 @@ package jace.apple2e.softswitch; +import jace.core.RAMEvent; import jace.core.RAMEvent.TYPE; /** @@ -23,32 +24,30 @@ import jace.core.RAMEvent.TYPE; * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public class Memory2SoftSwitch extends MemorySoftSwitch { - public Memory2SoftSwitch(String name, int offAddress, int onAddress, int queryAddress, TYPE changeType, Boolean initalState) { - super(name, offAddress, onAddress, queryAddress, changeType, initalState); - } - public Memory2SoftSwitch(String name, int[] offAddrs, int[] onAddrs, int[] queryAddrs, TYPE changeType, Boolean initalState) { super(name, offAddrs, onAddrs, queryAddrs, changeType, initalState); } - // The switch must be set true two times in a row before it will actually be set. - int count = 0; + int readCount = 0; @Override - public void setState(boolean newState) { + public void setState(boolean newState, RAMEvent e) { if (!newState) { - count = 0; - super.setState(newState); + super.setState(false); + readCount = 0; } else { - count++; - if (count >= 2) { - super.setState(newState); - count = 0; + if (e.getType().isRead()) { + readCount++; + } else { + readCount = 0; + } + if (readCount >= 2) { + super.setState(true); } } } @Override public String toString() { - return getName()+(getState()?":1":":0")+"~~"+count; + return getName()+(getState()?":1":":0")+"~~"+readCount; } } \ No newline at end of file diff --git a/src/main/java/jace/core/SoftSwitch.java b/src/main/java/jace/core/SoftSwitch.java index ddc2262..ab03f51 100644 --- a/src/main/java/jace/core/SoftSwitch.java +++ b/src/main/java/jace/core/SoftSwitch.java @@ -114,8 +114,7 @@ public abstract class SoftSwitch { @Override protected void doEvent(RAMEvent e) { if (!exclusionActivate.contains(e.getAddress())) { - // System.out.println("Access to "+Integer.toHexString(e.getAddress())+" ENABLES switch "+getName()); - setState(!getState()); + setState(!getState(), e); } } }; @@ -148,7 +147,7 @@ public abstract class SoftSwitch { } if (!exclusionActivate.contains(e.getAddress())) { // System.out.println("Access to "+Integer.toHexString(e.getAddress())+" ENABLES switch "+getName()); - setState(true); + setState(true, e); } } }; @@ -177,7 +176,7 @@ public abstract class SoftSwitch { @Override protected void doEvent(RAMEvent e) { if (!exclusionDeactivate.contains(e.getAddress())) { - setState(false); + setState(false, e); // System.out.println("Access to "+Integer.toHexString(e.getAddress())+" disables switch "+getName()); } } @@ -250,6 +249,12 @@ public abstract class SoftSwitch { }); } + // Most softswitches act the same regardless of the ram event triggering them + // But some softswitches are a little tricky (such as language card write) and need to assert extra conditions + public void setState(boolean newState, RAMEvent e) { + setState(newState); + } + public void setState(boolean newState) { if (inhibit()) { return; diff --git a/src/test/java/jace/TestProgram.java b/src/test/java/jace/TestProgram.java index 17298be..705815f 100644 --- a/src/test/java/jace/TestProgram.java +++ b/src/test/java/jace/TestProgram.java @@ -246,10 +246,10 @@ public class TestProgram { private void handleTrace(byte val) { if (val == (byte)0x01) { System.out.println("Trace on"); - Full65C02Test.cpu.setTraceEnabled(true); + Emulator.withComputer(c->c.getCpu().setTraceEnabled(true)); } else { System.out.println("Trace off"); - Full65C02Test.cpu.setTraceEnabled(false); + Emulator.withComputer(c->c.getCpu().setTraceEnabled(false)); } } @@ -274,6 +274,13 @@ public class TestProgram { return this; } + public TestProgram assertEquals(String message) { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + String caller = stackTrace[2].toString(); + _test(TestProgram.INDENT + TestProgram.Flag.IS_ZERO.code, message + "<<" + caller + ">>"); + return this; + } + public TestProgram assertFlags(TestProgram.Flag... flags) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); String caller = stackTrace[2].toString(); @@ -372,7 +379,11 @@ public class TestProgram { +throwError %s + ; >> Test """.formatted(condition, code, errorNum)); } - + + public TestProgram throwError(String error) { + _test("", error); + return this; + } /** * Note the current breakpoint, helpful in understanding error locations * @@ -447,6 +458,12 @@ public class TestProgram { for (int i=0; i < maxTicks; i++) { cpu.doTick(); tickCount++; + if (cpu.interruptSignalled) { + if (lastError == null) { + lastError = new ProgramException("Interrupt signalled by BRK opcode", lastBreakpoint); + } + programReportedError=true; + } if (programReportedError) { throw lastError; } diff --git a/src/test/java/jace/core/MemoryTest.java b/src/test/java/jace/core/MemoryTest.java index 88b1186..21b7671 100644 --- a/src/test/java/jace/core/MemoryTest.java +++ b/src/test/java/jace/core/MemoryTest.java @@ -20,14 +20,22 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import jace.Emulator; +import jace.ProgramException; +import jace.TestProgram; import jace.apple2e.MOS65C02; import jace.apple2e.RAM128k; +import jace.apple2e.SoftSwitches; /** * Test that memory listeners fire appropriately. @@ -37,14 +45,36 @@ public class MemoryTest { static Computer computer; static MOS65C02 cpu; static RAM128k ram; + static String MEMORY_TEST_COMMONS; + static String MACHINE_IDENTIFICATION; @BeforeClass - public static void setupClass() { + public static void setupClass() throws IOException, URISyntaxException { initComputer(); SoundMixer.MUTE = true; computer = Emulator.withComputer(c->c, null); cpu = (MOS65C02) computer.getCpu(); ram = (RAM128k) computer.getMemory(); + ram.addExecutionTrap("COUT intercept", 0x0FDF0, (e)->{ + char c = (char) (cpu.A & 0x07f); + if (c == '\r') { + System.out.println(); + } else { + System.out.print(c); + } + }); + MEMORY_TEST_COMMONS = Files.readString(Paths.get(MemoryTest.class.getResource("/jace/memory_test_commons.asm").toURI())); + MACHINE_IDENTIFICATION = Files.readString(Paths.get(MemoryTest.class.getResource("/jace/machine_identification.asm").toURI())); + } + + @Before + public void setup() { + computer.pause(); + cpu.clearState(); + // Reset softswitches + for (SoftSwitches softswitch : SoftSwitches.values()) { + softswitch.getSwitch().reset(); + } } @Test @@ -152,5 +182,324 @@ public class MemoryTest { 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()); - } + } + + + /** + * Adapted version of the Apple II Family Identification Program from: + * http://www.1000bit.it/support/manuali/apple/technotes/misc/tn.misc.02.html00 + * + * Adapted to ACME by Zellyn Hunter + * + * @throws ProgramException + */ + @Test + public void machineIdentificationTEst() throws ProgramException { + TestProgram memoryDetectTestProgram = new TestProgram(MEMORY_TEST_COMMONS); + memoryDetectTestProgram.add(MACHINE_IDENTIFICATION); + // Assert this is an Apple //e + memoryDetectTestProgram.assertAddrVal(0x0800, 0x04); + // Assert this is an enhanced revision + memoryDetectTestProgram.assertAddrVal(0x0801, 0x02); + // Aser this is a 128k machine + memoryDetectTestProgram.assertAddrVal(0x0802, 128); + memoryDetectTestProgram.run(); + } + + /* + * Adapted from Zellyn Hunder's language card test: + * https://github.com/zellyn/a2audit/blob/main/audit/langcard.asm + * + * Adjusted to use JACE hooks to perform assertions and error reporting + */ + @Test + public void languageCardBankswitchTest() throws ProgramException { + TestProgram lcTestProgram = new TestProgram(MEMORY_TEST_COMMONS); + lcTestProgram.add(""" + ;; Setup - store differing values in bank first and second banked areas. + lda $C08B ; Read and write bank 1 + lda $C08B + lda #$11 + sta $D17B ; $D17B is $53 in Apple II/plus/e/enhanced + cmp $D17B + """) + .assertEquals("E0004: We tried to put the language card into read bank 1, write bank 1, but failed to write.") + .add(""" + lda #$33 + sta $FE1F ; FE1F is $60 in Apple II/plus/e/enhanced + cmp $FE1F + """) + // + .assertEquals("E0005: We tried to put the language card into read RAM, write RAM, but failed to write.") + .add(""" + lda $C083 ; Read and write bank 2 + lda $C083 + lda #$22 + sta $D17B + cmp $D17B + """) + .assertEquals("E0006: We tried to put the language card into read bank 2, write bank 2, but failed to write.") + .add(""" + lda $C08B ; Read and write bank 1 with single access (only one needed if banked in already) + lda #$11 + cmp $D17B + """) + .assertEquals("E000D: We tried to put the language card into read bank 1, but failed to read.") + .add(""" + lda $C081 ; Read ROM with single access (only one needed to bank out) + lda #$53 + cmp $D17B + """) + .assertEquals("E000E: We tried to put the language card into read ROM, but failed to read (from ROM).") + .add(""" + ;;; Main data-driven test. PCL,PCH holds the address of the next + ;;; data-driven test routine. We expect the various softswitches + ;;; to be reset each time we loop at .ddloop. + .datadriventests + lda #<.tests + sta PCL + lda #>.tests + sta PCH + ;;; Main data-drive-test loop. + .ddloop + ldy #0 + + ;; Initialize to known state: + ;; - $11 in $D17B bank 1 (ROM: $53) + ;; - $22 in $D17B bank 2 (ROM: $53) + ;; - $33 in $FE1F (ROM: $60) + lda $C08B ; Read and write bank 1 + lda $C08B + lda #$11 + sta $D17B + lda #$33 + sta $FE1F + lda $C083 ; Read and write bank 2 + lda $C083 + lda #$22 + sta $D17B + lda $C080 + + jmp (PCL) ; Jump to test routine + + + ;; Test routine will JSR back to here, so the check data address is on the stack. + + .test ;; ... test the quintiple of test values + inc $D17B + inc $FE1F + + ;; pull address off of stack: it points just below check data for this test. + pla + sta .checkdata + pla + sta .checkdata+1 + + ;; .checkdata now points to d17b-current,fe1f-current,bank1,bank2,fe1f-ram test quintiple + + ;; Test current $D17B + jsr NEXTCHECK + cmp $D17B + beq + + lda $D17B + pha + jsr .printseq + +print + !text "$D17B TO CONTAIN $" + +printed + jsr CURCHECK + jsr PRBYTE + +print + !text ", GOT $" + +printed + pla + jsr PRBYTE + lda #$8D + jsr COUT + jmp .datatesturl + + + ;; Test current $FE1F + jsr NEXTCHECK + cmp $FE1F + beq + + lda $FE1F + pha + jsr .printseq + +print + !text "$FE1F=$" + +printed + jsr CURCHECK + jsr PRBYTE + +print + !text ", GOT $" + +printed + pla + jsr PRBYTE + lda #$8D + jsr COUT + jmp .datatesturl + + + ;; Test bank 1 $D17B + lda $C088 + jsr NEXTCHECK + cmp $D17B + beq + + lda $D17B + pha + jsr .printseq + +print + !text "$D17B IN RAM BANK 1 TO CONTAIN $" + +printed + jsr CURCHECK + jsr PRBYTE + +print + !text ", GOT $" + +printed + pla + jsr PRBYTE + lda #$8D + jsr COUT + jmp .datatesturl + + + ;; Test bank 2 $D17B + lda $C080 + jsr NEXTCHECK + cmp $D17B + beq + + lda $D17B + pha + jsr .printseq + +print + !text "$D17B IN RAM BANK 2 TO CONTAIN $" + +printed + jsr CURCHECK + jsr PRBYTE + +print + !text ", GOT $" + +printed + pla + jsr PRBYTE + lda #$8D + jsr COUT + jmp .datatesturl + + + ;; Test RAM $FE1F + lda $C080 + jsr NEXTCHECK + cmp $FE1F + beq + + lda $FE1F + pha + jsr .printseq + +print + !text "RAM $FE1F=$" + +printed + jsr CURCHECK + jsr PRBYTE + +print + !text ", GOT $" + +printed + pla + jsr PRBYTE + lda #$8D + jsr COUT + jmp .datatesturl + + + ;; Jump PCL,PCH up to after the test data, and loop. + jsr NEXTCHECK + bne + + +success + + ldx .checkdata + ldy .checkdata+1 + stx PCL + sty PCH + jmp .ddloop + + .datatesturl + """) + .throwError("E0007: This is a data-driven test of Language Card operation. We initialize $D17B in RAM bank 1 to $11, $D17B in RAM bank 2 to $22, and $FE1F in RAM to $33. Then, we perform a testdata-driven sequence of LDA and STA to the $C08X range. Finally we (try to) increment $D17B and $FE1F. Then we test (a) the current live value in $D17B, (b) the current live value in $FE1F, (c) the RAM bank 1 value of $D17B, (d) the RAM bank 2 value of $D17B, and (e) the RAM value of $FE1F, to see whether they match expected values. $D17B is usually $53 in ROM, and $FE1F is usally $60. For more information on the operation of the language card soft-switches, see Understanding the Apple IIe, by James Fielding Sather, Pg 5-24.") + .add(""" + rts + + .printseq + +print + !text "AFTER SEQUENCE OF:",$8D,"- LDA $C080",$8D + +printed + jsr PRINTTEST + +print + !text "- INC $D17B",$8D,"- INC $FE1F",$8D,"EXPECTED " + +printed + rts + + .tests + ;; Format: + ;; Sequence of test instructions, finishing with `jsr .test`. + ;; - quint: expected current $d17b and fe1f, then d17b in bank1, d17b in bank 2, and fe1f + ;; (All sequences start with lda $C080, just to reset things to a known state.) + ;; 0-byte to terminate tests. + + lda $C088 ; Read $C088 (RAM read, write protected) + jsr .test ; + !byte $11, $33, $11, $22, $33 ; + jsr .test ; + !byte $22, $33, $11, $22, $33 ; + lda $C081 ; Read $C081 (ROM read, write disabled) + jsr .test ; + !byte $53, $60, $11, $22, $33 + lda $C081 ; Read $C081, $C089 (ROM read, bank 1 write) + lda $C089 ; + jsr .test ; + !byte $53, $60, $54, $22, $61 + lda $C081 ; Read $C081, $C081 (read ROM, write RAM bank 2) + lda $C081 ; + jsr .test ; + !byte $53, $60, $11, $54, $61 + lda $C081 ; Read $C081, $C081, write $C081 (read ROM, write RAM bank bank 2) + lda $C081 ; See https://github.com/zellyn/a2audit/issues/3 + sta $C081 ; + jsr .test ; + !byte $53, $60, $11, $54, $61 + lda $C081 ; Read $C081, $C081; write $C081, $C081 + lda $C081 ; See https://github.com/zellyn/a2audit/issues/4 + sta $C081 ; + sta $C081 ; + jsr .test ; + !byte $53, $60, $11, $54, $61 + lda $C08B ; Read $C08B (read RAM bank 1, no write) + jsr .test ; + !byte $11, $33, $11, $22, $33 + lda $C083 ; Read $C083 (read RAM bank 2, no write) + jsr .test ; + !byte $22, $33, $11, $22, $33 + lda $C08B ; Read $C08B, $C08B (read/write RAM bank 1) + lda $C08B ; + jsr .test ; + !byte $12, $34, $12, $22, $34 + lda $C08F ; Read $C08F, $C087 (read/write RAM bank 2) + lda $C087 ; + jsr .test ; + !byte $23, $34, $11, $23, $34 + lda $C087 ; Read $C087, read $C08D (read ROM, write bank 1) + lda $C08D ; + jsr .test ; + !byte $53, $60, $54, $22, $61 + lda $C08B ; Read $C08B, write $C08B, read $C08B (read RAM bank 1, no write) + sta $C08B ; (this one is tricky: reset WRTCOUNT by writing halfway) + lda $C08B ; + jsr .test ; + !byte $11, $33, $11, $22, $33 + sta $C08B ; Write $C08B, write $C08B, read $C08B (read RAM bank 1, no write) + sta $C08B ; + lda $C08B ; + jsr .test ; + !byte $11, $33, $11, $22, $33 + clc ; Read $C083, $C083 (read/write RAM bank 2) + ldx #0 ; Uses "6502 false read" + inc $C083,x ; + jsr .test ; + !byte $23, $34, $11, $23, $34 + !byte 0 + """) + .runForTicks(10000000); + } } diff --git a/src/test/resources/jace/machine_identification.asm b/src/test/resources/jace/machine_identification.asm new file mode 100644 index 0000000..21ba279 --- /dev/null +++ b/src/test/resources/jace/machine_identification.asm @@ -0,0 +1,396 @@ +JSR IDENTIFY +JMP END_DETECT_PROGRAM +;;; From http://www.1000bit.it/support/manuali/apple/technotes/misc/tn.misc.02.html +;;; ********************************************* +;;; * * +;;; * Apple II Family Identification Program * +;;; * * +;;; * Version 2.2 * +;;; * * +;;; * March, 1990 * +;;; * * +;;; * Includes support for the Apple IIe Card * +;;; * for the Macintosh LC. * +;;; * * +;;; ********************************************* + +; First, some global equates for the routine: +IIplain = $01 ;Apple II +IIplus = $02 ;Apple II+ +IIIem = $03 ;Apple /// in emulation mode +IIe = $04 ;Apple IIe +IIc = $05 ;Apple IIc +IIeCard = $06 ;Apple IIe Card for the Macintosh LC + +.safe = $0001 ;start of code relocated to zp +.location = $06 ;zero page location to use + +.test1 = $AA ;test byte #1 +.test2 = $55 ;lsr of test1 +.test3 = $88 ;test byte #3 +.test4 = $EE ;test byte #4 + +.begpage1 = $400 ;beginning of text page 1 +.begpage2 = $800 ;beginning of text page 2 +.begsprse = $C00 ;byte after text page 2 + +.clr80col = $C000 ;disable 80-column store +.set80col = $C001 ;enable 80-column store +.rdmainram = $C002 ;read main ram +.rdcardram = $C003 ;read aux ram +.wrmainram = $C004 ;write main ram +.wrcardram = $C005 ;write aux ram +.rdramrd = $C013 ;are we reading aux ram? +.rdaltzp = $C016 ;are we reading aux zero page? +.rd80col = $C018 ;are we using 80-columns? +.rdtext = $C01A ;read if text is displayed +.rdpage2 = $C01C ;read if page 2 is displayed +.txtclr = $C050 ;switch in graphics +.txtset = $C051 ;switch in text +.txtpage1 = $C054 ;switch in page 1 +.txtpage2 = $C055 ;switch in page 2 +.ramin = $C080 ;read LC bank 2, write protected +.romin = $C081 ;read ROM, 2 reads write enable LC +.lcbank1 = $C08B ;LC bank 1 enable + +.lc1 = $E000 ;bytes to save for LC +.lc2 = $D000 ;save/restore routine +.lc3 = $D400 +.lc4 = $D800 + +.idroutine = $FE1F ;IIgs id routine + +; Start by saving the state of the language card banks and +; by switching in main ROM. + +IDENTIFY + php ;save the processor state + sei ;before disabling interrupts + lda .lc1 ;save four bytes from + sta .save ;ROM/RAM area for later + lda .lc2 ;restoring of RAM/ROM + sta .save+1 ;to original condition + lda .lc3 + sta .save+2 + lda .lc4 + sta .save+3 + lda $C081 ;read ROM + lda $C081 + lda #0 ;start by assuming unknown machine + sta MACHINE + sta ROMLEVEL +.IdStart + lda .location ;save zero page locations + sta .save+4 ;for later restoration + lda .location+1 + sta .save+5 + lda #$FB ;all ID bytes are in page $FB + sta .location+1 ;save in zero page as high byte + ldx #0 ;init pointer to start of ID table +.loop lda .IDTable,x ;get the machine we are testing for + sta MACHINE ;and save it + lda .IDTable+1,x ;get the ROM level we are testing for + sta ROMLEVEL ;and save it + ora MACHINE ;are both zero? + beq .matched ;yes - at end of list - leave + +.loop2 inx ;bump index to loc/byte pair to test + inx + lda .IDTable,x ;get the byte that should be in ROM + beq .matched ;if zero, we're at end of list + sta .location ;save in zero page + + ldy #0 ;init index for indirect addressing + lda .IDTable+1,x ;get the byte that should be in ROM + cmp (.location),y ;is it there? + beq .loop2 ;yes, so keep on looping + +.loop3 inx ;we didn't match. Scoot to the end of the + inx ;line in the ID table so we can start + lda .IDTable,x ;checking for another machine + bne .loop3 + inx ;point to start of next line + bne .loop ;should always be taken + +.matched ; anop + +; Here we check the 16-bit ID routine at idroutine ($FE1F). If it +; returns with carry clear, we call it again in 16-bit +; mode to provide more information on the machine. + + !cpu 65816 { +.idIIgs + sec ;set the carry bit + jsr .idroutine ;Apple IIgs ID Routine + bcc .idIIgs2 ;it's a IIgs or equivalent + jmp .IIgsOut ;nope, go check memory +.idIIgs2 + lda MACHINE ;get the value for machine + ora #$80 ;and set the high bit + sta MACHINE ;put it back + clc ;get ready to switch into native mode + xce + php ;save the processor status + rep #$30 ;sets 16-bit registers + !al { ;longa on + !rl { ;longi on + jsr .idroutine ;call the ID routine again + sta .IIgsA ;16-bit store! + stx .IIgsX ;16-bit store! + sty .IIgsY ;16-bit store! + plp ;restores 8-bit registers + xce ;switches back to whatever it was before + } ;longi off + } ;longa off + + ldy .IIgsY ;get the ROM vers number (starts at 0) + cpy #$02 ;is it ROM 01 or 00? + bcs .idIIgs3 ;if not, don't increment + iny ;bump it up for romlevel +.idIIgs3 + sty ROMLEVEL ;and put it there + cpy #$01 ;is it the first ROM? + bne .IIgsOut ;no, go on with things + lda .IIgsY+1 ;check the other byte too + bne .IIgsOut ;nope, it's a IIgs successor + lda #$7F ;fix faulty ROM 00 on the IIgs + sta .IIgsA +.IIgsOut ; anop +} + +;;; ****************************************** +;;; * This part of the code checks for the * +;;; * memory configuration of the machine. * +;;; * If it's a IIgs, we've already stored * +;;; * the total memory from above. If it's * +;;; * a IIc or a IIe Card, we know it's * +;;; * 128K; if it's a ][+, we know it's at * +;;; * least 48K and maybe 64K. We won't * +;;; * check for less than 48K, since that's * +;;; * a really rare circumstance. * +;;; ****************************************** + +.exit lda MACHINE ;get the machine kind + bmi .exit128 ;it's a 16-bit machine (has 128K) + cmp #IIc ;is it a IIc? + beq .exit128 ;yup, it's got 128K + cmp #IIeCard ;is it a IIe Card? + beq .exit128 ;yes, it's got 128K + cmp #IIe ;is it a IIe? + bne .contexit ;yes, go muck with aux memory + jmp .muckaux +.contexit + cmp #IIIem ;is it a /// in emulation? + bne .exitII ;nope, it's a ][ or ][+ + lda #48 ;/// emulation has 48K + jmp .exita +.exit128 + lda #128 ;128K +.exita sta MEMORY +.exit1 lda .lc1 ;time to restore the LC + cmp .save ;if all 4 bytes are the same + bne .exit2 ;then LC was never on so + lda .lc2 ;do nothing + cmp .save+1 + bne .exit2 + lda .lc3 + cmp .save+2 + bne .exit2 + lda .lc4 + cmp .save+3 + beq .exit6 +.exit2 lda $C088 ;no match! so turn first LC + lda .lc1 ;bank on and check + cmp .save + beq .exit3 + lda $C080 + jmp .exit6 +.exit3 lda .lc2 + cmp .save+1 ;if all locations check + beq .exit4 ;then do more more else + lda $C080 ;turn on bank 2 + jmp .exit6 +.exit4 lda .lc3 ;check second byte in bank 1 + cmp .save+2 + beq .exit5 + lda $C080 ;select bank 2 + jmp .exit6 +.exit5 lda .lc4 ;check third byte in bank 1 + cmp .save+3 + beq .exit6 + lda $C080 ;select bank 2 +.exit6 plp ;restore interrupt status + lda .save+4 ;put zero page back + sta .location + lda .save+5 ;like we found it + sta .location+1 + rts ;and go home. + +.exitII + lda .lcbank1 ;force in language card + lda .lcbank1 ;bank 1 + ldx .lc2 ;save the byte there + lda #.test1 ;use this as a test byte + sta .lc2 + eor .lc2 ;if the same, should return zero + bne .noLC + lsr .lc2 ;check twice just to be sure + lda #.test2 ;this is the shifted value + eor .lc2 ;here's the second check + bne .noLC + stx .lc2 ;put it back! + lda #64 ;there's 64K here + jmp .exita +.noLC lda #48 ;no restore - no LC! + jmp .exita ;and get out of here + +.muckaux + ldx .rdtext ;remember graphics in X + lda .rdpage2 ;remember current video display + asl ;in the carry bit + lda #.test3 ;another test character + bit .rd80col ;remember video mode in N + sta .set80col ;enable 80-column store + php ;save N and C flags + sta .txtpage2 ;set page two + sta .txtset ;set text + ldy .begpage1 ;save first character + sta .begpage1 ;and replace it with test character + lda .begpage1 ;get it back + sty .begpage1 ;and put back what was there + plp + bcs .muck2 ;stay in page 2 + sta .txtpage1 ;restore page 1 +.muck1 bmi .muck2 ;stay in 80-columns + sta $c000 ;turn off 80-columns +.muck2 tay ;save returned character + txa ;get graphics/text setting + bmi .muck3 + sta .txtclr ;turn graphics back on +.muck3 cpy #.test3 ;finally compare it + bne .nocard ;no 80-column card! + lda .rdramrd ;is aux memory being read? + bmi .muck128 ;yup, there's 128K! + lda .rdaltzp ;is aux zero page used? + bmi .muck128 ;yup! + ldy #.done-.start +.move ldx .start-1,y ;swap section of zero page + lda <.safe-1,y ;code needing safe location during + stx <.safe-1,y ;reading of aux mem + sta .start-1,Y + dey + bne .move + jmp .safe ;jump to safe ground +.back php ;save status + ldy #.done-.start ;move zero page back +.move2 lda .start-1,y + sta .safe-1,y + dey + bne .move2 + pla + bcs .noaux +.isaux jmp .muck128 ;there is 128K + +;;; * You can put your own routine at "noaux" if you wish to +;;; * distinguish between 64K without an 80-column card and +;;; * 64K with an 80-column card. + +.noaux ; anop +.nocard lda #64 ;only 64K + jmp .exita +.muck128 + jmp .exit128 ;there's 128K + +;;; * This is the routine run in the safe area not affected +;;; * by bank-switching the main and aux RAM. + +.start lda #.test4 ;yet another test byte + sta .wrcardram ;write to aux while on main zero page + sta .rdcardram ;read aux ram as well + sta .begpage2 ;check for sparse memory mapping + lda .begsprse ;if sparse, these will be the same + cmp #.test4 ;value since they're 1K apart + bne .auxmem ;yup, there's 128K! + asl .begsprse ;may have been lucky so we'll + lda .begpage2 ;change the value and see what happens + cmp .begsprse + bne .auxmem + sec ;oops, no auxiliary memory + bcs .goback +.auxmem clc +.goback sta .wrmainram ;write main RAM + sta .rdmainram ;read main RAM + jmp .back ;continue with program in main mem +.done nop ;end of relocated program marker + + +;;; * The storage locations for the returned machine ID: + +.IIgsA !word 0 ;16-bit field +.IIgsX !word 0 ;16-bit field +.IIgsY !word 0 ;16-bit field +.save !fill 6,0 ;six bytes for saved data + +.IDTable + ;dc I1'1,1' ;Apple ][ + ;dc H'B3 38 00' + !byte 1,1 + !byte $B3,$38,0 + + ;dc I1'2,1' ;Apple ][+ + ;dc H'B3 EA 1E AD 00' + !byte 2,1 + !byte $B3,$EA,$1E,$AD,0 + + ;dc I1'3,1' ;Apple /// (emulation) + ;dc H'B3 EA 1E 8A 00' + !byte 3,1 + !byte $B3,$EA,$1E,$8A,0 + + ;dc I1'4,1' ;Apple IIe (original) + ;dc H'B3 06 C0 EA 00' + !byte 4,1 + !byte $B3,$06,$C0,$EA,0 + +; Note: You must check for the Apple IIe Card BEFORE you +; check for the enhanced Apple IIe since the first +; two identification bytes are the same. + + ;dc I1'6,1' ;Apple IIe Card for the Macintosh LC (1st release) + ;dc H'B3 06 C0 E0 DD 02 BE 00 00' + !byte 6,1 + !byte $B3,$06,$C0,$E0,$DD,$02,$BE,$00,0 + + ;dc I1'4,2' ;Apple IIe (enhanced) + ;dc H'B3 06 C0 E0 00' + !byte 4,2 + !byte $B3,$06,$C0,$E0,0 + + ;dc I1'5,1' ;Apple IIc (original) + ;dc H'B3 06 C0 00 BF FF 00' + !byte 5,1 + !byte $B3,$06,$C0,$00,$BF,$FF,0 + + ;dc I1'5,2' ;Apple IIc (3.5 ROM) + ;dc H'B3 06 C0 00 BF 00 00' + !byte 5,2 + !byte $B3,$06,$C0,$00,$BF,$00,0 + + ;dc I1'5,3' ;Apple IIc (Mem. Exp) + ;dc H'B3 06 C0 00 BF 03 00' + !byte 5,3 + !byte $B3,$06,$C0,$00,$BF,$03,0 + + ;dc I1'5,4' ;Apple IIc (Rev. Mem. Exp.) + ;dc H'B3 06 C0 00 BF 04 00' + !byte 5,4 + !byte $B3,$06,$C0,$00,$BF,$04,0 + + ;dc I1'5,5' ;Apple IIc Plus + ;dc H'B3 06 C0 00 BF 05 00' + !byte 5,5 + !byte $B3,$06,$C0,$00,$BF,$05,0 + + ;dc I1'0,0' ;end of table + !byte 0,0 +END_DETECT_PROGRAM diff --git a/src/test/resources/jace/memory_test_commons.asm b/src/test/resources/jace/memory_test_commons.asm new file mode 100644 index 0000000..9d0b3bc --- /dev/null +++ b/src/test/resources/jace/memory_test_commons.asm @@ -0,0 +1,204 @@ +jmp START +MACHINE = $800 ;the type of Apple II +ROMLEVEL = $801 ;which revision of the machine +MEMORY = $802 ;how much memory (up to 128K) + +LCRESULT = $10 +LCRESULT1 = $11 + +lda #0 +sta LCRESULT + +;; Zero-page locations. +SCRATCH = $1 +SCRATCH2 = $2 +SCRATCH3 = $3 +LCRESULT = $10 +LCRESULT1 = $11 +AUXRESULT = $12 +SOFTSWITCHRESULT = $13 + +CSW = $36 +KSW = $38 + +PCL=$3A +PCH=$3B +A1L=$3C +A1H=$3D +A2L=$3E +A2H=$3F +A3L=$40 +A3H=$41 +A4L=$42 +A4H=$43 + +!addr tmp0 = $f9 +!addr tmp1 = $fa +!addr tmp2 = $fb +!addr tmp3 = $fc +!addr tmp4 = $fd +!addr tmp5 = $fe +!addr tmp6 = $ff +.checkdata = tmp1 + +STRINGS = $8000 +!set LASTSTRING = STRINGS + +KBD = $C000 +KBDSTRB = $C010 + +;; Monitor locations. +;HOME = $FC58 +COUT = $FDED +COUT1 = $FDF0 +KEYIN = $FD1B +CROUT = $FD8E +PRBYTE = $FDDA +PRNTYX = $F940 + +;; Softswitch locations. +RESET_80STORE = $C000 +SET_80STORE = $C001 +READ_80STORE = $C018 + +RESET_RAMRD = $C002 +SET_RAMRD = $C003 +READ_RAMRD = $C013 + +RESET_RAMWRT = $C004 +SET_RAMWRT = $C005 +READ_RAMWRT = $C014 + +RESET_INTCXROM = $C006 +SET_INTCXROM = $C007 +READ_INTCXROM = $C015 + +RESET_ALTZP = $C008 +SET_ALTZP = $C009 +READ_ALTZP = $C016 + +RESET_SLOTC3ROM = $C00A +SET_SLOTC3ROM = $C00B +READ_SLOTC3ROM = $C017 + +RESET_80COL = $C00C +SET_80COL = $C00D +READ_80COL = $C01F + +RESET_ALTCHRSET = $C00E +SET_ALTCHRSET = $C00F +READ_ALTCHRSET = $C01E + +RESET_TEXT = $C050 +SET_TEXT = $C051 +READ_TEXT = $C01A + +RESET_MIXED = $C052 +SET_MIXED = $C053 +READ_MIXED = $C01B + +RESET_PAGE2 = $C054 +SET_PAGE2 = $C055 +READ_PAGE2 = $C01C + +RESET_HIRES = $C056 +SET_HIRES = $C057 +READ_HIRES = $C01D + +RESET_AN3 = $C05E +SET_AN3 = $C05F + +RESET_INTC8ROM = $CFFF + +;; Readable things without corresponding set/reset pairs. +READ_HRAM_BANK2 = $C011 +READ_HRAMRD = $C012 +READ_VBL = $C019 + +print + lda $C081 + lda $C081 + pla + sta getch+1 + pla + sta getch+2 +- inc getch+1 + bne getch + inc getch+2 +getch lda $FEED ; FEED gets modified + beq + + jsr COUT + jmp - ++ rts + +PRINTTEST +- + ldy #0 + lda (PCL),y + cmp #$20 + beq +++ + lda #'-' + jsr COUT + lda #' ' + jsr COUT + ldx #0 + lda (PCL,x) + jsr $f88e + ldx #3 + jsr $f8ea + jsr $f953 + sta PCL + sty PCH + lda #$8D + jsr COUT + jmp - ++++ rts + +;;; Increment .checkdata pointer to the next memory location, and load +;;; it into the accumulator. X and Y are preserved. +NEXTCHECK + inc .checkdata + bne CURCHECK + inc .checkdata+1 +CURCHECK + sty SCRATCH + ldy #0 + lda (.checkdata),y + ldy SCRATCH + ora #0 + rts + +!macro print { + jsr LASTSTRING + !set TEMP = * + * = LASTSTRING + jsr print +} +!macro printed { + !byte 0 + !set LASTSTRING=* + * = TEMP +} + +START +;;; Reset all soft-switches to known-good state. Burns $300 and $301 in main mem. +RESETALL + ; The COUT hook isn't set up yet, so the monitor routine will crash unless we set it up + lda #COUT1 + sta CSW+1 + sta RESET_RAMRD + sta RESET_RAMWRT + ;; Save return address in X and A, in case we switch zero-page memory. + sta RESET_80STORE + sta RESET_INTCXROM + sta RESET_ALTZP + sta RESET_SLOTC3ROM + sta RESET_INTC8ROM + sta RESET_80COL + sta RESET_ALTCHRSET + sta SET_TEXT + sta RESET_MIXED + sta RESET_PAGE2 + sta RESET_HIRES \ No newline at end of file