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