diff --git a/README.md b/README.md index ff6a07c..d145452 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,12 @@ Portable emulator of an Apple II+ or //e. Written in Go. - RGB for Super High Resolution and RGB card - ANSI Console, avoiding the SDL2 dependency - Debug mode: shows four panels with actual screen, page1, page2 and extra info dependant of the video mode +- Tracing capabilities: + - CPU execution disassembled + - Softswitch reads and writes + - ProDOS MLI calls + - Apple Pascal BIOS calls + - Smartport commands - Other features: - Sound - Joystick support. Up to two joysticks or four paddles @@ -62,7 +68,6 @@ Portable emulator of an Apple II+ or //e. Written in Go. - Fast disk mode to set max speed while using the disks - Single file executable with embedded ROMs and DOS 3.3 - Pause (thanks a2geek) - - ProDOS MLI calls tracing - Passes the [A2AUDIT 1.06](https://github.com/zellyn/a2audit) tests as II+, //e, and //e Enhanced. By default the following configuration is launched: @@ -226,6 +231,8 @@ Only valid on SDL mode dump to the console the hd/smartport commands -traceMLI dump to the console the calls to ProDOS machine language interface calls to $BF00 + -tracePascal + dump to the console the calls to the Apple Pascal BIOS -traceSS dump to the console the sofswitches calls -traceSSReg diff --git a/apple2.go b/apple2.go index 1b296e4..3acf9cb 100644 --- a/apple2.go +++ b/apple2.go @@ -24,10 +24,14 @@ type Apple2 struct { profile bool showSpeed bool paused bool - traceMLI *traceProDOS + tracers []executionTracer forceCaps bool } +type executionTracer interface { + inspect() +} + const ( // CPUClockMhz is the actual Apple II clock speed CPUClockMhz = 14.318 / 14 @@ -211,8 +215,10 @@ func (a *Apple2) releaseFastMode() { } func (a *Apple2) executionTrace() { - if a.traceMLI != nil { - a.traceMLI.inspect() + if a.tracers != nil { + for _, v := range a.tracers { + v.inspect() + } } } @@ -223,15 +229,17 @@ func (a *Apple2) dumpDebugInfo() { 0x37: "CSWH", 0x38: "KSWL", 0x39: "KSWH", + 0xe2: "ACJVAFLDL", // Apple Pascal + 0xe3: "ACJVAFLDH", // Apple Pascal + 0xec: "JVBFOLDL", // Apple Pascal + 0xed: "JVBFOLDH", // Apple Pascal + 0xee: "JVAFOLDL", // Apple Pascal + 0xef: "JVAFOLDH", // Apple Pascal } fmt.Printf("Page zero values:\n") - for _, k := range []int{0x36, 0x37, 0x38, 0x39} { + for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} { d := a.mmu.physicalMainRAM.data[k] fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d) } - - if a.traceMLI != nil { - a.traceMLI.dumpDevices() - } } diff --git a/apple2Setup.go b/apple2Setup.go index 5540ee6..4575972 100644 --- a/apple2Setup.go +++ b/apple2Setup.go @@ -16,12 +16,9 @@ func newApple2() *Apple2 { return &a } -func (a *Apple2) setup(clockMhz float64, fastMode bool, traceMLI bool) { +func (a *Apple2) setup(clockMhz float64, fastMode bool) { a.commandChannel = make(chan int, 100) a.fastMode = fastMode - if traceMLI { - a.traceMLI = newTraceProDOS(a) - } if clockMhz <= 0 { // Full speed @@ -55,6 +52,14 @@ func setApple2eEnhanced(a *Apple2) { addApple2ESoftSwitches(a.io) } +func (a *Apple2) addTracer(tracer executionTracer) { + if a.tracers == nil { + a.tracers = make([]executionTracer, 0) + } + + a.tracers = append(a.tracers, tracer) +} + func (a *Apple2) insertCard(c Card, slot int) { c.assign(a, slot) a.cards[slot] = c diff --git a/apple2main.go b/apple2main.go index f988f1d..94ee823 100644 --- a/apple2main.go +++ b/apple2main.go @@ -130,6 +130,10 @@ func MainApple() *Apple2 { "traceMLI", false, "dump to the console the calls to ProDOS machine language interface calls to $BF00") + tracePascal := flag.Bool( + "tracePascal", + false, + "dump to the console the calls to the Apple Pascal BIOS") forceCaps := flag.Bool( "forceCaps", false, @@ -150,12 +154,18 @@ func MainApple() *Apple2 { } a := newApple2() - a.setup(*cpuClock, *fastDisk, *traceMLI) + a.setup(*cpuClock, *fastDisk) a.io.setTrace(*traceSS) a.io.setTraceRegistrations(*traceSSReg) a.io.setPanicNotImplemented(*panicSS) a.setProfiling(*profile) a.SetForceCaps(*forceCaps) + if *traceMLI { + a.addTracer(newTraceProDOS(a)) + } + if *tracePascal { + a.addTracer(newTracePascal(a)) + } var charGenMap charColumnMap initialCharGenPage := 0 diff --git a/core6502/registers.go b/core6502/registers.go index c6e9e26..90d2f7b 100644 --- a/core6502/registers.go +++ b/core6502/registers.go @@ -87,7 +87,11 @@ func (r *registers) updateFlagZN(t uint8) { } func (r registers) String() string { - ch := (r.getA() & 0x3F) + 0x40 + //ch := (r.getA() & 0x3F) + 0x40 + ch := (r.getA() & 0x7F) + if ch < 0x20 { + ch += 0x40 + } return fmt.Sprintf("A: %#02x(%v), X: %#02x, Y: %#02x, SP: %#02x, PC: %#04x, P: %#02x, (NV-BDIZC): %08b", r.getA(), string(ch), r.getX(), r.getY(), r.getSP(), r.getPC(), r.getP(), r.getP()) } diff --git a/memoryManager.go b/memoryManager.go index 67daea9..d280e99 100644 --- a/memoryManager.go +++ b/memoryManager.go @@ -29,7 +29,7 @@ type memoryManager struct { // Configuration switches, Language cards lcSelectedBlock uint8 // Language card block selected. Usually, allways 0. But Saturn has 8 lcActiveRead bool // Upper RAM active for read - lcActiveWrite bool // Upper RAM active for read + lcActiveWrite bool // Upper RAM active for write lcAltBank bool // Alternate // Configuration switches, Apple //e diff --git a/tracePascal.go b/tracePascal.go new file mode 100644 index 0000000..ecfadfa --- /dev/null +++ b/tracePascal.go @@ -0,0 +1,210 @@ +package izapple2 + +import "fmt" + +type tracePascal struct { + a *Apple2 + skipConsole bool +} + +const ( + pascalJvabfoldL uint16 = 0x00ec // Points to the BIOS entry points + pascalJvabfoldH uint16 = 0x00ed // Points to the BIOS entry points +) + +func newTracePascal(a *Apple2) *tracePascal { + var t tracePascal + t.a = a + t.skipConsole = true + return &t +} + +/* + See: + https://archive.org/details/Hyde_P-Source-A_Guide_to_the_APPLE_Pascal_System_1983/page/n415/mode/1up?view=theater + https://archive.org/details/Apple_II_Pascal_1.2_Device_and_Interrupt_Support_Tools_Manual + + Experimental. Not sure the paramters for DREAD and DWRITE are correct. +*/ +func (t *tracePascal) inspect() { + bios := uint16(t.a.mmu.physicalMainRAM.peek(pascalJvabfoldL)) + + uint16(t.a.mmu.physicalMainRAM.peek(pascalJvabfoldH))<<8 + pc, _ := t.a.cpu.GetPCAndSP() + if pc >= bios && pc < bios+0x5a { + offset := uint8(pc - bios) + + if t.skipConsole && offset <= 0x03 { + return + } + + _, regA := t.a.cpu.GetCarryAndAcc() + regAText := string(regA) + if regA < 0x20 { + regAText = "^" + string(regA+0x40) + + } + + fmt.Printf("Pascal BIOS call $%02x from $%04x ", offset, t.param(1)) + switch offset { + case 0: + fmt.Printf("CREAD()") + case 3: + fmt.Printf("CWRITE('%s'[%v])", regAText, regA) + case 6: + fmt.Printf("CINIT(BREAK=[$%04x], SYSCOM=[$%04x])", + t.param(3), t.param(5)) + case 9: + fmt.Printf("PWRITE('%s'[%v])", regAText, regA) + case 12: + fmt.Printf("PINIT()") + case 15: + fmt.Printf("DWRITE(UNIT=%v, BLOCK=%v, LEN=%v, DATA=[$%04x], DRIVE=%v, CONTROL=$%04x)", + regA, t.param(3), t.param(5), t.param(7), t.param(9), t.param(11)) + case 18: + fmt.Printf("DREAD(UNIT=%v, BLOCK=%v, LEN=%v, DATA=[$%04x], DRIVE=%v, CONTROL=$%04x)", + regA, t.param(3), t.param(5), t.param(7), t.param(9), t.param(11)) + case 21: + fmt.Printf("DINIT(DRIVE=%v)", regA) + case 24: + fmt.Printf("RREAD()") + case 27: + fmt.Printf("RWRITE('%s'[%v])", regAText, regA) + case 30: + fmt.Printf("RINIT()") + case 33: + fmt.Printf("GWRITE()") + case 36: + fmt.Printf("GRINIT()") + case 39: + fmt.Printf("PREAD()") + case 42: + fmt.Printf("CSTAT()") + case 45: + fmt.Printf("PSTAT()") + case 48: + fmt.Printf("DSTAT()") + case 51: + fmt.Printf("RSTAT()") + case 54: + fmt.Printf("CONCK()") + case 57: + fmt.Printf("UDRWI()") + case 60: + fmt.Printf("PSUBDRV()") + + default: + fmt.Printf("") + } + fmt.Printf("\n") + } +} + +/* + See http://www.bitsavers.org/pdf/softech/softechPascalIV_intArch1981.pdf + page 106 +*/ +func (t *tracePascal) inspectPerArchitectureGuide() { + bios := uint16(t.a.mmu.physicalMainRAM.peek(pascalJvabfoldL)) + + uint16(t.a.mmu.physicalMainRAM.peek(pascalJvabfoldH))<<8 + pc, _ := t.a.cpu.GetPCAndSP() + if pc >= bios && pc < bios+0x5a { + offset := uint8(pc - bios) + + if t.skipConsole && offset <= 0x03 { + return + } + + _, regA := t.a.cpu.GetCarryAndAcc() + regAText := string(regA) + if regA < 0x20 { + regAText = "^" + string(regA+0x40) + + } + + fmt.Printf("Pascal BIOS call $%02x from $%04x ", offset, t.param(1)) + switch offset { + // Console + case 0x00: + fmt.Printf("CONSOLEREAD()") + case 0x03: + fmt.Printf("CONSOLEWRITE('%s'[%v])", regAText, regA) + case 0x06: + fmt.Printf("CONSOLECTRL(BREAK=[%04x], SYSCOM=[%04x])", + t.param(3), t.param(5)) + case 0x09: + fmt.Printf("CONSOLESTAT(STATREC=[%04x], CONTROL=%04x)", + t.param(3), t.param(5)) + + // Printer + case 0x0c: + fmt.Printf("PRINTERREAD()") + case 0x0f: + fmt.Printf("PRINTERWRITE('%s'[%v])", regAText, regA) + case 0x12: + fmt.Printf("PRINTERCTRL()") + case 0x15: + fmt.Printf("PRINTERSTAT(STATREC=[%04x], CONTROL=%04x)", + t.param(3), t.param(5)) + + // Disk + case 0x18: + fmt.Printf("DISKREAD(BLOCK=%04x, LEN=%04x, DATA=[%04x], DRIVE=%v, CONTROL=%04x)", + t.param(3), t.param(5), t.param(7), t.param(9), t.param(11)) + case 0x1b: + fmt.Printf("DISKWRITE(BLOCK=%04x, LEN=%04x, DATA=[%04x], DRIVE=%v, CONTROL=%04x)", + t.param(3), t.param(5), t.param(7), t.param(9), t.param(11)) + case 0x1e: + fmt.Printf("DISKCTRL(DRIVE=%v)", regA) + case 0x21: + fmt.Printf("DISKSTAT(DRIVE=%v, STATREC=[%04x], CONTROL=%04x)", + regA, t.param(3), t.param(5)) + + // Remote + case 0x24: + fmt.Printf("REMOTEREAD()") + case 0x27: + fmt.Printf("REMOTEWRITE('%s'[%v])", regAText, regA) + case 0x2a: + fmt.Printf("REMOTECTRL()") + case 0x2d: + fmt.Printf("REMOTESTAT(STATREC=[%04x], CONTROL=%04x)", + t.param(3), t.param(5)) + + // User + case 0x30: + fmt.Printf("USERREAD(BLOCK=%04x, LEN=%04x, DATA=[%04x], DEVICE=%v, CONTROL=%04x)", + t.param(3), t.param(5), t.param(7), t.param(9), t.param(11)) + case 0x33: + fmt.Printf("USERWRITE(BLOCK=%04x, LEN=%04x, DATA=[%04x], DEVICE=%v, CONTROL=%04x)", + t.param(3), t.param(5), t.param(7), t.param(9), t.param(11)) + case 0x36: + fmt.Printf("USERCTRL(DEVICE=%v)", regA) + case 0x39: + fmt.Printf("USERSTAT(DEVICE=%v, STATREC=[%04x], CONTROL=%04x)", + regA, t.param(3), t.param(5)) + + // Sys + case 0x3c: + fmt.Printf("SYSREAD(BLOCK=%04x, LEN=%04x, DATA=[%04x], DEVICE=%v, CONTROL=%04x)", + t.param(3), t.param(5), t.param(7), t.param(9), t.param(11)) + case 0x3f: + fmt.Printf("SYSWRITE(BLOCK=%04x, LEN=%04x, DATA=[%04x], DEVICE=%v, CONTROL=%04x)", + t.param(3), t.param(5), t.param(7), t.param(9), t.param(11)) + case 0x42: + fmt.Printf("SYSCTRL(DEVICE=%v)", regA) + case 0x43: + fmt.Printf("SYSSTAT(DEVICE=%v, STATREC=[%04x], CONTROL=%04x)", + regA, t.param(3), t.param(5)) + + default: + fmt.Printf("") + } + fmt.Printf("\n") + } +} + +func (t *tracePascal) param(index uint8) uint16 { + _, sp := t.a.cpu.GetPCAndSP() + return uint16(t.a.mmu.Peek(0x100+uint16(sp+index))) + + uint16(t.a.mmu.Peek(0x100+uint16(sp+index+1)))<<8 - 2 +} diff --git a/traceProDOS.go b/traceProDOS.go index c676c50..49e9829 100644 --- a/traceProDOS.go +++ b/traceProDOS.go @@ -67,9 +67,9 @@ func (t *traceProDOS) inspect() { } func (t *traceProDOS) dumpMLICall() { - _, ps := t.a.cpu.GetPCAndSP() - caller := uint16(t.a.mmu.Peek(0x100+uint16(ps+1))) + - uint16(t.a.mmu.Peek(0x100+uint16(ps+2)))<<8 - 2 + _, sp := t.a.cpu.GetPCAndSP() + caller := uint16(t.a.mmu.Peek(0x100+uint16(sp+1))) + + uint16(t.a.mmu.Peek(0x100+uint16(sp+2)))<<8 - 2 t.functionCode = t.a.mmu.Peek(caller + 3) t.paramsAdddress = uint16(t.a.mmu.Peek(caller+4)) + uint16(t.a.mmu.Peek(caller+5))<<8 t.returnAddress = caller + 6