Explicit cards ROM layout

This commit is contained in:
Iván Izaguirre 2024-03-24 20:15:16 +01:00
parent cb355a17cd
commit 9e89f0a23f
21 changed files with 187 additions and 63 deletions

View File

@ -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
}
}

View File

@ -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) {

View File

@ -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])

17
cardBuilder_test.go Normal file
View File

@ -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)
}
})
}
}
}

View File

@ -80,7 +80,7 @@ func newCardDan2ControllerBuilder() *cardBuilder {
if c.improved {
romFilename = "<internal>/Apple2CardFirmwareImproved.bin"
}
err := c.loadRomFromResource(romFilename)
err := c.loadRomFromResource(romFilename, cardRomSimple)
if err != nil {
return nil, err
}

View File

@ -60,7 +60,7 @@ func newCardDisk2Builder() *cardBuilder {
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardDisk2
err := c.loadRomFromResource("<internal>/DISK2.rom")
err := c.loadRomFromResource("<internal>/DISK2.rom", cardRomSimple)
if err != nil {
return nil, err
}

View File

@ -66,7 +66,7 @@ func newCardDisk2SequencerBuilder() *cardBuilder {
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardDisk2Sequencer
err := c.loadRomFromResource("<internal>/DISK2.rom")
err := c.loadRomFromResource("<internal>/DISK2.rom", cardRomSimple)
if err != nil {
return nil, err
}

View File

@ -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("<internal>/MemoryExpansionCard-341-0344a.bin")
err = c.loadRomFromResource("<internal>/MemoryExpansionCard-341-0344a.bin", cardRomFull)
if err != nil {
return nil, err
}

View File

@ -36,7 +36,7 @@ func newCardParallelPrinterBuilder() *cardBuilder {
return nil, err
}
c.file = f
err = c.loadRomFromResource("<internal>/Apple II Parallel Printer Interface Card ROM fixed.bin")
err = c.loadRomFromResource("<internal>/Apple II Parallel Printer Interface Card ROM fixed.bin", cardRomSimple)
if err != nil {
return nil, err
}

View File

@ -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
}
}

View File

@ -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
},
}

View File

@ -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

View File

@ -33,7 +33,7 @@ func newCardThunderClockPlusBuilder() *cardBuilder {
description: "Clock card",
buildFunc: func(params map[string]string) (Card, error) {
var c CardThunderClockPlus
err := c.loadRomFromResource("<internal>/ThunderclockPlusROM.bin")
err := c.loadRomFromResource("<internal>/ThunderclockPlusROM.bin", cardRomUpper)
if err != nil {
return nil, err
}

View File

@ -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
},
}

View File

@ -42,7 +42,7 @@ func newCardVidexBuilder() *cardBuilder {
var c CardVidex
// The C800 area has ROM and RAM
err := c.loadRomFromResource("<internal>/Videx Videoterm ROM 2.4.bin")
err := c.loadRomFromResource("<internal>/Videx Videoterm ROM 2.4.bin", cardRomUpperHalfEnd)
if err != nil {
return nil, err
}

View File

@ -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

View File

@ -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:

4
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -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
}

View File

@ -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