diff --git a/apple2Run.go b/apple2Run.go index 1b8158f..5ac700d 100644 --- a/apple2Run.go +++ b/apple2Run.go @@ -37,7 +37,7 @@ func (a *Apple2) Start(paused bool) { for i := 0; i < cpuSpinLoops; i++ { // Conditional tracing //pc, _ := a.cpu.GetPCAndSP() - //a.cpu.SetTrace(pc >= 0xc75e && pc < 0xc800) + //a.cpu.SetTrace(pc >= 0xc700 && pc < 0xc800) // Execution a.cpu.ExecuteInstruction() @@ -144,10 +144,16 @@ func (a *Apple2) dumpDebugInfo() { 0xee: "JVAFOLDL", // Apple Pascal 0xef: "JVAFOLDH", // Apple Pascal } - fmt.Printf("Page zero values:\n") 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) } + + pc := uint16(0xc700) + for pc < 0xc800 { + line, newPc := a.cpu.DisasmInstruction(pc) + fmt.Println(line) + pc = newPc + } } diff --git a/cardBase.go b/cardBase.go index f66e4f6..967c4e0 100644 --- a/cardBase.go +++ b/cardBase.go @@ -6,7 +6,7 @@ import ( // Card represents an Apple II card to be inserted in a slot type Card interface { - loadRom(data []uint8) + loadRom(data []uint8, layout cardRomLayout) error assign(a *Apple2, slot int) reset() @@ -51,45 +51,79 @@ func (c *cardBase) reset() { // nothing } -func (c *cardBase) loadRomFromResource(resource string) error { +type cardRomLayout int + +const ( + cardRomSimple cardRomLayout = iota // The ROM is on the slot area, there can be more than one page + cardRomUpper // The ROM is on the full C800 area. The slot area copies C8xx + cardRomUpperHalfEnd // The ROM is on half of the C800 areas. The slot area copies CBxx + cardRomFull // The ROM is on the full Cxxx area, with pages for each slot position +) + +func (c *cardBase) loadRomFromResource(resource string, layout cardRomLayout) error { data, _, err := LoadResource(resource) if err != nil { // The resource should be internal and never fail return err } - c.loadRom(data) + err = c.loadRom(data, layout) + if err != nil { + return err + } return nil } -func (c *cardBase) loadRom(data []uint8) { +func (c *cardBase) loadRom(data []uint8, layout cardRomLayout) error { if c.a != nil { - panic("Assert failed. ROM must be loaded before inserting the card in the slot") + return fmt.Errorf("ROM must be loaded before inserting the card in the slot") } - if len(data) == 0x100 { - // Just 256 bytes in Cs00 - c.romCsxx = newMemoryRangeROM(0, data, "Slot ROM") - } else if len(data) == 0x400 { - // The file has C800 to CBFF for ROM - // The 256 bytes in Cx00 are copied from the last page in C800-CBFF - // Used on the Videx 80 columns card - c.romCsxx = newMemoryRangeROM(0, data[0x300:], "Slot ROM") - c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM") - } else if len(data) == 0x800 { - // The file has C800 to CFFF - // The 256 bytes in Cx00 are copied from the first page in C800 - c.romCsxx = newMemoryRangeROM(0, data, "Slot ROM") - c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM") - } else if len(data) == 0x1000 { - // The file covers the full Cxxx range. Only showing the page - // corresponding to the slot used. - c.romCxxx = newMemoryRangeROM(0xc000, data, "Slot ROM") - } else if len(data)%0x100 == 0 { - // The ROM covers many 256 bytes pages oc Csxx - // Used on the Dan 2 controller card - c.romCsxx = newMemoryRangePagedROM(0, data, "Slot paged ROM", uint8(len(data)/0x100)) - } else { - panic("Invalid ROM size") + switch layout { + case cardRomSimple: + if len(data) == 0x100 { + // Just 256 bytes in Cs00 + c.romCsxx = newMemoryRangeROM(0, data, "Slot ROM") + } else if len(data)%0x100 == 0 { + // The ROM covers many 256 bytes pages of Csxx + // Used on the Dan 2 controller card + c.romCsxx = newMemoryRangePagedROM(0, data, "Slot paged ROM", uint8(len(data)/0x100)) + } else { + return fmt.Errorf("invalid ROM size for simple layout") + } + case cardRomUpper: + if len(data) == 0x800 { + // The file has C800 to CFFF + // The 256 bytes in Cx00 are copied from the first page in C800 + c.romCsxx = newMemoryRangeROM(0, data, "Slot ROM") + c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM") + } else { + return fmt.Errorf("invalid ROM size for upper layout") + } + case cardRomUpperHalfEnd: + if len(data) == 0x400 { + // The file has C800 to CBFF for ROM + // The 256 bytes in Cx00 are copied from the last page in C800-CBFF + // Used on the Videx 80 columns card + c.romCsxx = newMemoryRangeROM(0, data[0x300:], "Slot ROM") + c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM") + } else { + return fmt.Errorf("invalid ROM size for upper half end layout") + } + case cardRomFull: + if len(data) == 0x1000 { + // The file covers the full Cxxx range. Only showing the page + // corresponding to the slot used. + c.romCxxx = newMemoryRangeROM(0xc000, data, "Slot ROM") + } else if len(data)%0x1000 == 0 { + // The ROM covers the full Cxxx range with several pages + c.romCxxx = newMemoryRangePagedROM(0xc000, data, "Slot paged ROM", uint8(len(data)/0x1000)) + } else { + return fmt.Errorf("invalid ROM size for full layout") + } + default: + return fmt.Errorf("invalid card ROM layout") } + + return nil } func (c *cardBase) assign(a *Apple2, slot int) { diff --git a/cardBuilder.go b/cardBuilder.go index b50b000..f4e81ba 100644 --- a/cardBuilder.go +++ b/cardBuilder.go @@ -54,6 +54,7 @@ func getCardFactory() map[string]*cardBuilder { cardFactory["parallel"] = newCardParallelPrinterBuilder() cardFactory["prodosromdrive"] = newCardProDOSRomDriveBuilder() cardFactory["prodosromcard3"] = newCardProDOSRomCard3Builder() + //cardFactory["prodosnvramdrive"] = newCardProDOSNVRAMDriveBuilder() cardFactory["saturn"] = newCardSaturnBuilder() cardFactory["smartport"] = newCardSmartPortStorageBuilder() cardFactory["swyftcard"] = newCardSwyftBuilder() @@ -69,6 +70,20 @@ func availableCards() []string { return names } +func (cb *cardBuilder) fullDefaultParams() map[string]string { + finalParams := make(map[string]string) + for _, commonParam := range commonParams { + finalParams[commonParam.name] = commonParam.defaultValue + } + if cb.defaultParams != nil { + for _, defaultParam := range *cb.defaultParams { + finalParams[defaultParam.name] = defaultParam.defaultValue + } + } + + return finalParams +} + func setupCard(a *Apple2, slot int, paramString string) (Card, error) { actualArgs := splitConfigurationString(paramString, ',') @@ -86,16 +101,7 @@ func setupCard(a *Apple2, slot int, paramString string) (Card, error) { return nil, fmt.Errorf("card %s requires an Apple IIe", builder.name) } - finalParams := make(map[string]string) - for _, commonParam := range commonParams { - finalParams[commonParam.name] = commonParam.defaultValue - } - if builder.defaultParams != nil { - for _, defaultParam := range *builder.defaultParams { - finalParams[defaultParam.name] = defaultParam.defaultValue - } - } - + finalParams := builder.fullDefaultParams() for i := 1; i < len(actualArgs); i++ { actualArgSides := splitConfigurationString(actualArgs[i], '=') actualArgName := strings.ToLower(actualArgSides[0]) diff --git a/cardBuilder_test.go b/cardBuilder_test.go new file mode 100644 index 0000000..81c98bf --- /dev/null +++ b/cardBuilder_test.go @@ -0,0 +1,17 @@ +package izapple2 + +import "testing" + +func TestCardBuilder(t *testing.T) { + cardFactory := getCardFactory() + for name, builder := range cardFactory { + if name != "prodosromdrive" && name != "prodosromcard3" && name != "prodosnvramdrive" { + t.Run(name, func(t *testing.T) { + _, err := builder.buildFunc(builder.fullDefaultParams()) + if err != nil { + t.Errorf("Exception building card '%s': %s", name, err) + } + }) + } + } +} diff --git a/cardDan2Controller.go b/cardDan2Controller.go index ff43b05..c53ae9a 100644 --- a/cardDan2Controller.go +++ b/cardDan2Controller.go @@ -80,7 +80,7 @@ func newCardDan2ControllerBuilder() *cardBuilder { if c.improved { romFilename = "/Apple2CardFirmwareImproved.bin" } - err := c.loadRomFromResource(romFilename) + err := c.loadRomFromResource(romFilename, cardRomSimple) if err != nil { return nil, err } diff --git a/cardDisk2.go b/cardDisk2.go index cac9ef9..e78ad5e 100644 --- a/cardDisk2.go +++ b/cardDisk2.go @@ -60,7 +60,7 @@ func newCardDisk2Builder() *cardBuilder { }, buildFunc: func(params map[string]string) (Card, error) { var c CardDisk2 - err := c.loadRomFromResource("/DISK2.rom") + err := c.loadRomFromResource("/DISK2.rom", cardRomSimple) if err != nil { return nil, err } diff --git a/cardDisk2Sequencer.go b/cardDisk2Sequencer.go index ee3083f..ae06759 100644 --- a/cardDisk2Sequencer.go +++ b/cardDisk2Sequencer.go @@ -66,7 +66,7 @@ func newCardDisk2SequencerBuilder() *cardBuilder { }, buildFunc: func(params map[string]string) (Card, error) { var c CardDisk2Sequencer - err := c.loadRomFromResource("/DISK2.rom") + err := c.loadRomFromResource("/DISK2.rom", cardRomSimple) if err != nil { return nil, err } diff --git a/cardMemoryExpansion.go b/cardMemoryExpansion.go index 3116e52..ac84aaf 100644 --- a/cardMemoryExpansion.go +++ b/cardMemoryExpansion.go @@ -35,6 +35,11 @@ has one Megabyte or less, reading the high address byte will always return a value in the range $F0-FF. The top nybble can be any value when you write it, but it will always be “F” when you read it. If the card has more than one Megabyte of RAM, the top nybble will be a meaningful part of the address. + +Notes for RAMFactor: + - https://github.com/mamedev/mame/blob/master/src/devices/bus/a2bus/a2memexp.cpp + - ss 5 is for the ROM page, there are two. + - https://ae.applearchives.com/all_apple_iis/ramfactor/ */ const ( memoryExpansionMask = 0x000fffff // 10 bits, 1MB @@ -65,7 +70,7 @@ func newCardMemoryExpansionBuilder() *cardBuilder { var c CardMemoryExpansion c.ram = make([]uint8, size*1024) - err = c.loadRomFromResource("/MemoryExpansionCard-341-0344a.bin") + err = c.loadRomFromResource("/MemoryExpansionCard-341-0344a.bin", cardRomFull) if err != nil { return nil, err } diff --git a/cardParallelPrinter.go b/cardParallelPrinter.go index fca68fb..4ae3ed7 100644 --- a/cardParallelPrinter.go +++ b/cardParallelPrinter.go @@ -36,7 +36,7 @@ func newCardParallelPrinterBuilder() *cardBuilder { return nil, err } c.file = f - err = c.loadRomFromResource("/Apple II Parallel Printer Interface Card ROM fixed.bin") + err = c.loadRomFromResource("/Apple II Parallel Printer Interface Card ROM fixed.bin", cardRomSimple) if err != nil { return nil, err } diff --git a/cardProDOSRomCard3.go b/cardProDOSRomCard3.go index 8a98da5..98bea26 100644 --- a/cardProDOSRomCard3.go +++ b/cardProDOSRomCard3.go @@ -15,8 +15,10 @@ Note that this card disables the C800-CFFF range only on writes to CFFF, not as // CardProDOSRomCard3 is a Memory Expansion card type CardProDOSRomCard3 struct { cardBase - bank uint16 - data []uint8 + bank uint16 + data []uint8 + nvram bool + secondROMPage bool } func newCardProDOSRomCard3Builder() *cardBuilder { @@ -37,15 +39,51 @@ func newCardProDOSRomCard3Builder() *cardBuilder { return nil, err } + if len(data) != 4*1024*1024 { + return nil, fmt.Errorf("NVRAM image must be 4MB") + } + var c CardProDOSRomCard3 c.data = data - c.loadRom(data[0x200:0x300]) + c.loadRom(data[0x200:0x300], cardRomSimple) c.romC8xx = &c return &c, nil }, } } +func newCardProDOSNVRAMDriveBuilder() *cardBuilder { + return &cardBuilder{ + name: "ProDOS 4MB NVRAM DRive", + description: "A bootable 4 MB NVRAM card by Ralle Palaveev", + defaultParams: &[]paramSpec{ + {"image", "ROM image with the ProDOS volume", ""}, + }, + buildFunc: func(params map[string]string) (Card, error) { + image := paramsGetPath(params, "image") + if image == "" { + return nil, fmt.Errorf("image required for the ProDOS ROM drive") + } + + data, _, err := LoadResource(image) + if err != nil { + return nil, err + } + + if len(data) != 4*1024*1024 && len(data) != 512*1024 { + return nil, fmt.Errorf("NVRAM image must be 512KB or 4MB") + } + + var c CardProDOSRomCard3 + c.data = data + c.loadRom(data[0x200:0x400], cardRomSimple) + c.romC8xx = &c + c.nvram = true + return &c, nil + }, + } +} + func (c *CardProDOSRomCard3) assign(a *Apple2, slot int) { // Set pointer position @@ -56,6 +94,16 @@ func (c *CardProDOSRomCard3) assign(a *Apple2, slot int) { c.bank = uint16(value)<<8 | c.bank&0xff }, "BANKHI") + if c.nvram { + c.addCardSoftSwitchW(2, func(value uint8) { + if c.secondROMPage { + c.romCsxx.setPage(0) + } else { + c.romCsxx.setPage(1) + } + }, "?????") + } + c.cardBase.assign(a, slot) } @@ -71,9 +119,15 @@ func (c *CardProDOSRomCard3) translateAddress(address uint16) int { } func (c *CardProDOSRomCard3) peek(address uint16) uint8 { + if address&0xff == 0 { + fmt.Printf("CardProDOSRomCard3.peek: address=%04X\n", address) + } return c.data[c.translateAddress(address)] } func (c *CardProDOSRomCard3) poke(address uint16, value uint8) { - // Do nothing + fmt.Printf("CardProDOSRomCard3.poke: address=%04X, value=%02X\n", address, value) + if c.nvram && address != 0xcfff { + c.data[c.translateAddress(address)] = value + } } diff --git a/cardProDOSRomDrive.go b/cardProDOSRomDrive.go index cc457d3..552f848 100644 --- a/cardProDOSRomDrive.go +++ b/cardProDOSRomDrive.go @@ -43,7 +43,7 @@ func newCardProDOSRomDriveBuilder() *cardBuilder { var c CardProDOSRomDrive c.data = data - c.loadRom(data[0x300:0x400]) + c.loadRom(data[0x300:0x400], cardRomSimple) return &c, nil }, } diff --git a/cardSmartport.go b/cardSmartport.go index 0daa1bb..8e296a9 100644 --- a/cardSmartport.go +++ b/cardSmartport.go @@ -112,7 +112,7 @@ func (c *CardSmartPort) AddDevice(device smartPortDevice) { } func (c *CardSmartPort) assign(a *Apple2, slot int) { - c.loadRom(buildHardDiskRom(slot)) + c.loadRom(buildHardDiskRom(slot), cardRomSimple) c.addCardSoftSwitchR(0, func() uint8 { // Prodos entry point diff --git a/cardThunderClockPlus.go b/cardThunderClockPlus.go index 43e2918..2368e3f 100644 --- a/cardThunderClockPlus.go +++ b/cardThunderClockPlus.go @@ -33,7 +33,7 @@ func newCardThunderClockPlusBuilder() *cardBuilder { description: "Clock card", buildFunc: func(params map[string]string) (Card, error) { var c CardThunderClockPlus - err := c.loadRomFromResource("/ThunderclockPlusROM.bin") + err := c.loadRomFromResource("/ThunderclockPlusROM.bin", cardRomUpper) if err != nil { return nil, err } diff --git a/cardVidHD.go b/cardVidHD.go index 5b429c8..9a09ae9 100644 --- a/cardVidHD.go +++ b/cardVidHD.go @@ -19,7 +19,7 @@ func newCardVidHDBuilder() *cardBuilder { description: "Firmware signature of the VidHD card to trick Total Replay to use the SHR mode", buildFunc: func(params map[string]string) (Card, error) { var c CardVidHD - c.loadRom(buildVidHDRom()) + c.loadRom(buildVidHDRom(), cardRomSimple) return &c, nil }, } diff --git a/cardVidex.go b/cardVidex.go index 1841149..12f101a 100644 --- a/cardVidex.go +++ b/cardVidex.go @@ -42,7 +42,7 @@ func newCardVidexBuilder() *cardBuilder { var c CardVidex // The C800 area has ROM and RAM - err := c.loadRomFromResource("/Videx Videoterm ROM 2.4.bin") + err := c.loadRomFromResource("/Videx Videoterm ROM 2.4.bin", cardRomUpperHalfEnd) if err != nil { return nil, err } diff --git a/frontend/a2sdl/main.go b/frontend/a2sdl/main.go index 19d2c5b..9205b5c 100644 --- a/frontend/a2sdl/main.go +++ b/frontend/a2sdl/main.go @@ -151,6 +151,7 @@ var helpMessage = ` F1: Show/Hide help Ctrl-F2: Reset + F4: Show/Hide CPU trace F5: Fast/Normal speed Ctrl-F5: Show speed F6: Next screen mode @@ -158,7 +159,6 @@ var helpMessage = ` F10: Next character set Ctrl-F10: Show/Hide character set Shift-F10: Show/Hide alternate text - F11: Show/Hide CPU trace F12: Save screen snapshot Pause: Pause the emulation diff --git a/frontend/a2sdl/sdlKeyboard.go b/frontend/a2sdl/sdlKeyboard.go index 6d70a3b..c606e9a 100644 --- a/frontend/a2sdl/sdlKeyboard.go +++ b/frontend/a2sdl/sdlKeyboard.go @@ -99,6 +99,8 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) { if ctrl { k.a.SendCommand(izapple2.CommandReset) } + case sdl.K_F4: + k.a.SendCommand(izapple2.CommandToggleCPUTrace) case sdl.K_F5: if ctrl { k.a.SendCommand(izapple2.CommandShowSpeed) @@ -123,8 +125,6 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) { } else { k.a.SendCommand(izapple2.CommandNextCharGenPage) } - case sdl.K_F11: - k.a.SendCommand(izapple2.CommandToggleCPUTrace) case sdl.K_F12: fallthrough case sdl.K_PRINTSCREEN: diff --git a/go.mod b/go.mod index c983181..f89c8f2 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,14 @@ go 1.18 require ( fyne.io/fyne/v2 v2.1.4 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240118000515-a250818d05e3 - github.com/ivanizag/iz6502 v1.3.2 + github.com/ivanizag/iz6502 v1.4.0 github.com/pkg/profile v1.7.0 github.com/veandco/go-sdl2 v0.4.38 golang.org/x/exp v0.0.0-20240119083558-1b970713d09a ) +replace github.com/ivanizag/iz6502 => ../iz6502 + require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/fgprof v0.9.3 // indirect diff --git a/go.sum b/go.sum index 50c8aed..65b7674 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,6 @@ github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8I github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8= github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/ivanizag/iz6502 v1.3.2 h1:JQAxsGVXeerQc+L5wGpGPEgvX+yxLqpvm2Dx6aO7wGU= -github.com/ivanizag/iz6502 v1.3.2/go.mod h1:h4gbw3IK6WCYawi00kBhQ4ACeQkGWgqbUeAgDaQpy6s= github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= diff --git a/ioC0Page.go b/ioC0Page.go index 839cb00..0f9b537 100644 --- a/ioC0Page.go +++ b/ioC0Page.go @@ -128,7 +128,7 @@ func (p *ioC0Page) setMouseProvider(m MouseProvider) { func (p *ioC0Page) isTraced(address uint16) bool { ss := address & 0xff - return ss != 0xc000 && // Do not trace the spammy keyboard softswitch + return address != 0xc000 && // Do not trace the spammy keyboard softswitch (p.traceMask&(1<<(ss>>4))) != 0 } diff --git a/traceProDOS.go b/traceProDOS.go index e8c2142..884f6c7 100644 --- a/traceProDOS.go +++ b/traceProDOS.go @@ -189,6 +189,8 @@ func (t *traceProDOS) dumpMLIReturn() { default: fmt.Printf("Ok\n") } + + t.a.cpu.SetTrace(false) } } @@ -217,7 +219,7 @@ func (t *traceProDOS) dumpDriverCall() { if int(command) < len(proDosCommandNames) { commandName = proDosCommandNames[command] } - fmt.Printf("\n Prodos driver $%04x command %02x-%s on unit $%x, block %v to $%04x ==> ", pc, command, commandName, unit, block, address) + fmt.Printf("\n Prodos driver $%04x command %02x-%s on unit $%x, block %v to/from $%04x ==> ", pc, command, commandName, unit, block, address) } //lint:ignore U1000 unused but stays as reference