ROMXe font emulation

This commit is contained in:
Ivan Izaguirre 2021-10-12 12:26:40 +02:00
parent a93a32f63e
commit 4a2c094198
6 changed files with 152 additions and 54 deletions

View File

@ -33,6 +33,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
- FASTChip, limited to what Total Replay needs to set and clear fast mode - FASTChip, limited to what Total Replay needs to set and clear fast mode
- Mouse Card, emulates the entry points, not the softswitches. - Mouse Card, emulates the entry points, not the softswitches.
- Host console card. Maps the host STDIN and STDOUT to PR# and IN# - Host console card. Maps the host STDIN and STDOUT to PR# and IN#
- ROMXe, limited to font switching
- Graphic modes: - Graphic modes:
- Text 40 columns - Text 40 columns
@ -223,6 +224,8 @@ Only valid on SDL mode
emulate the RGB modes of the 80col RGB card for DHGR (default true) emulate the RGB modes of the 80col RGB card for DHGR (default true)
-rom string -rom string
main rom file (default "<default>") main rom file (default "<default>")
-romx
emulate a RomX
-saturnCardSlot int -saturnCardSlot int
slot for the 256kb Saturn card. -1 for none (default -1) slot for the 256kb Saturn card. -1 for none (default -1)
-sequencer -sequencer

View File

@ -209,9 +209,13 @@ func (a *Apple2) AddNoSlotClock() {
} }
// AddRomX inserts a RomX. It intercepts all memory accesses // AddRomX inserts a RomX. It intercepts all memory accesses
func (a *Apple2) AddRomX() { func (a *Apple2) AddRomX() error {
rx := newRomX(a, a.mmu) rx, err := newRomX(a, a.mmu)
if err != nil {
return err
}
a.cpu.SetMemory(rx) a.cpu.SetMemory(rx)
return nil
} }
// AddNoSlotClockInCard inserts a DS1215 no slot clock under a card ROM // AddNoSlotClockInCard inserts a DS1215 no slot clock under a card ROM

View File

@ -94,6 +94,10 @@ func MainApple() *Apple2 {
"rgb", "rgb",
true, true,
"emulate the RGB modes of the 80col RGB card for DHGR") "emulate the RGB modes of the 80col RGB card for DHGR")
romX := flag.Bool(
"romx",
false,
"emulate a RomX")
fastDisk := flag.Bool( fastDisk := flag.Bool(
"fastDisk", "fastDisk",
true, true,
@ -258,7 +262,7 @@ func MainApple() *Apple2 {
} }
// Load character generator if it loaded already // Load character generator if it loaded already
cg, err := newCharacterGenerator(*charRomFile, charGenMap) cg, err := newCharacterGenerator(*charRomFile, charGenMap, a.isApple2e)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -347,7 +351,13 @@ func MainApple() *Apple2 {
} }
// a.AddRomX() if *romX {
err := a.AddRomX()
if err != nil {
panic(err)
}
}
// a.AddCardLogger(4) // a.AddCardLogger(4)
return a return a

View File

@ -16,6 +16,7 @@ type CharacterGenerator struct {
data []uint8 data []uint8
columnMap charColumnMap columnMap charColumnMap
page int page int
pageSize int
} }
type charColumnMap func(column int) int type charColumnMap func(column int) int
@ -29,17 +30,24 @@ func charGenColumnsMap2e(column int) int {
} }
const ( const (
charGenPageSize = 2048 charGenPageSize2Plus = 2048
charGenPageSize2E = 2048 * 2
) )
// NewCharacterGenerator instantiates a new Character Generator with the rom on the file given // NewCharacterGenerator instantiates a new Character Generator with the rom on the file given
func newCharacterGenerator(filename string, order charColumnMap) (*CharacterGenerator, error) { func newCharacterGenerator(filename string, order charColumnMap, isApple2e bool) (*CharacterGenerator, error) {
var cg CharacterGenerator var cg CharacterGenerator
cg.columnMap = order
cg.pageSize = charGenPageSize2Plus
if isApple2e {
cg.pageSize = charGenPageSize2E
}
err := cg.load(filename) err := cg.load(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cg.columnMap = order
return &cg, nil return &cg, nil
} }
@ -49,7 +57,7 @@ func (cg *CharacterGenerator) load(filename string) error {
return err return err
} }
size := len(bytes) size := len(bytes)
if size < charGenPageSize { if size < cg.pageSize {
return errors.New("character ROM size not supported") return errors.New("character ROM size not supported")
} }
cg.data = bytes cg.data = bytes
@ -58,16 +66,20 @@ func (cg *CharacterGenerator) load(filename string) error {
func (cg *CharacterGenerator) setPage(page int) { func (cg *CharacterGenerator) setPage(page int) {
// Some clones had a switch to change codepage with extra characters // Some clones had a switch to change codepage with extra characters
pages := len(cg.data) / charGenPageSize pages := len(cg.data) / cg.pageSize
cg.page = page % pages cg.page = page % pages
} }
func (cg *CharacterGenerator) getPage() int {
return cg.page
}
func (cg *CharacterGenerator) nextPage() { func (cg *CharacterGenerator) nextPage() {
cg.setPage(cg.page + 1) cg.setPage(cg.page + 1)
} }
func (cg *CharacterGenerator) getPixel(char uint8, row int, column int) bool { func (cg *CharacterGenerator) getPixel(char uint8, row int, column int) bool {
bits := cg.data[int(char)*8+row+cg.page*charGenPageSize] bits := cg.data[int(char)*8+row+cg.page*cg.pageSize]
bit := cg.columnMap(column) bit := cg.columnMap(column)
value := bits >> uint(bit) & 1 value := bits >> uint(bit) & 1
return value == 1 return value == 1

147
romX.go
View File

@ -10,10 +10,14 @@ import (
RomX from https://theromexchange.com/ RomX from https://theromexchange.com/
This complement uses the RomX API spec to switch main ROM and character generator ROM This complement uses the RomX API spec to switch main ROM and character generator ROM
Only the font switch is implemented
See: See:
https://theromexchange.com/documentation/ROM%20X%20API%20Reference.pdf https://theromexchange.com/documentation/ROM%20X%20API%20Reference.pdf
https://theromexchange.com/downloads/ROM%20X%2020-10-22.zip https://theromexchange.com/downloads/ROM%20X%2020-10-22.zip
https://theromexchange.com/documentation/romxce/ROMXce%20API%20Reference.pdf
For romX:
It is not enough to intercept the ROM accesses. RomX intercept the 74LS138 in It is not enough to intercept the ROM accesses. RomX intercept the 74LS138 in
position F12, that has access to the full 0xc000-0xf000 on the Apple II+ position F12, that has access to the full 0xc000-0xf000 on the Apple II+
@ -30,32 +34,64 @@ type romX struct {
memory core6502.Memory memory core6502.Memory
activationStep int activationStep int
systemBank uint8 systemBank uint8
mainBank uint8
tempBank uint8
textBank uint8 textBank uint8
debug bool
} }
var romXActivationSequence = []uint16{0xcaca, 0xcaca, 0xcafe} var romXActivationSequence = []uint16{0xcaca, 0xcaca, 0xcafe}
var romXceActivationSequence = []uint16{0xfaca, 0xfaca, 0xfafe}
const ( const (
setupBank = uint8(0) romxSetupBank = uint8(0)
romXSetSystemBankBaseAddress = uint16(0xcef0) romXPlusSetSystemBankBaseAddress = uint16(0xcef0)
romXSetTextBankBaseAddress = uint16(0xcfd0) romXPlusSetTextBankBaseAddress = uint16(0xcfd0)
romXDefaultSystemBankAddress = uint16(0xd034)
romXDefaultTextBankAddress = uint16(0xd02e)
// Unknown // Unknown
romXFirmwareMark0Address = uint16(0xdffe) //romXFirmwareMark0Address = uint16(0xdffe)
romXFirmwareMark0Value = uint8(0x4a) //romXFirmwareMark0Value = uint8(0x4a)
romXFirmwareMark1Address = uint16(0xdfff) //romXFirmwareMark1Address = uint16(0xdfff)
romXFirmwareMark1Value = uint8(0xcd) //romXFirmwareMark1Value = uint8(0xcd)
romXceSelectTempBank = uint16(0xf850)
romXceSelectMainBank = uint16(0xf851)
romXceSetTempBank = uint16(0xf830) // 16 positions
romXceSetMainBank = uint16(0xf800) // 16 positions
romXcePresetTextBank = uint16(0xf810) // 16 positions
romXceMCP7940SDC = uint16(0xf860) // 16 positions
romXceLowerUpperBanks = uint16(0xf820) // 16 positions
romXGetDefaultSystemBank = uint16(0xd034) // $00 to $0f
romXGetDefaultTextBank = uint16(0xd02e) // $10 to $1f
romXGetCurrentBootDelay = uint16(0xdeca) // $00 to $0f
/*
romXceEntryPointSetClock = uint16(0xc803)
romXceEntryPointReadClock = uint16(0xc803)
romXceEntryPointLauncherToRam = uint16(0xdfd9)
romXceEntryPointLauncher = uint16(0xdfd0)
*/
) )
func newRomX(a *Apple2, memory core6502.Memory) *romX { func newRomX(a *Apple2, memory core6502.Memory) (*romX, error) {
var rx romX var rx romX
rx.a = a rx.a = a
rx.memory = memory rx.memory = memory
rx.systemBank = 0 rx.systemBank = 1
rx.mainBank = 1
rx.tempBank = 1
rx.textBank = 0 rx.textBank = 0
return &rx rx.debug = true
if a.isApple2e {
err := a.cg.load("<internal>/ROMXce Production 1Mb Text ROM V5.bin")
if err != nil {
return nil, err
}
}
return &rx, nil
} }
func (rx *romX) Peek(address uint16) uint8 { func (rx *romX) Peek(address uint16) uint8 {
@ -67,10 +103,10 @@ func (rx *romX) Peek(address uint16) uint8 {
} }
func (rx *romX) PeekCode(address uint16) uint8 { func (rx *romX) PeekCode(address uint16) uint8 {
//intercepted, value := rx.interceptAccess(address) intercepted, value := rx.interceptAccess(address)
//if intercepted { if intercepted {
// return value return value
//} }
return rx.memory.PeekCode(address) return rx.memory.PeekCode(address)
} }
@ -79,6 +115,11 @@ func (rx *romX) Poke(address uint16, value uint8) {
rx.memory.Poke(address, value) rx.memory.Poke(address, value)
} }
func (rx *romX) logf(format string, a ...interface{}) {
msg := fmt.Sprintf(format, a...)
fmt.Printf("[romX]%s\n", msg)
}
func (rx *romX) interceptAccess(address uint16) (bool, uint8) { func (rx *romX) interceptAccess(address uint16) (bool, uint8) {
// Intercept only $C080 to $FFFF as seen by the F12 chip // Intercept only $C080 to $FFFF as seen by the F12 chip
if address < 0xc080 { if address < 0xc080 {
@ -86,45 +127,65 @@ func (rx *romX) interceptAccess(address uint16) (bool, uint8) {
} }
// Setup mode when the setup bank is active // Setup mode when the setup bank is active
if rx.systemBank == setupBank { if rx.systemBank == romxSetupBank {
switch address {
case romXDefaultSystemBankAddress: // Range commands
fmt.Printf("[romX]Peek in $%04x, current system bank %v\n", address, rx.systemBank) nibble := uint8(address & 0xf)
return true, 0xe0 + rx.systemBank switch address & 0xfff0 {
case romXDefaultTextBankAddress: case romXceSetMainBank:
fmt.Printf("[romX]PeeK in $%04x, current text bank %v\n", address, rx.textBank) rx.mainBank = nibble
return true, 0xd0 + rx.textBank rx.logf("Main bank set to $%x", nibble)
case romXFirmwareMark0Address: case romXcePresetTextBank:
fmt.Printf("[romX]Peek in $%04x, ???\n", address) textBank := int(nibble)
return true, romXFirmwareMark0Value rx.a.cg.setPage(textBank)
case romXFirmwareMark1Address: rx.logf("[romX]Text bank set to $%x", nibble)
fmt.Printf("[romX]Peek in $%04x, ???\n", address) case romXceLowerUpperBanks:
return true, romXFirmwareMark1Value rx.logf("Configure lower upper banks $%x", address)
case romXceSetTempBank:
rx.tempBank = nibble
rx.logf("Temp bank set to $%x", nibble)
case romXceMCP7940SDC:
rx.logf("Configure MCP7940 $%x", address)
} }
if address&0xfff0 == romXSetSystemBankBaseAddress { // More commands
rx.systemBank = uint8(address & 0xf) switch address {
fmt.Printf("[romX]System bank set to %v\n", rx.systemBank) case romXceSelectTempBank:
} else if address&0xfff0 == romXSetTextBankBaseAddress { rx.systemBank = rx.tempBank
rx.textBank = uint8(address & 0xf) rx.logf("System bank set to temp bank $%x", rx.systemBank)
fmt.Printf("[romX]Text bank set to %v\n", rx.textBank) case romXceSelectMainBank:
} else if address < 0xe000 { rx.systemBank = rx.mainBank
fmt.Printf("[romX]Peek in $%04x\n", address) rx.logf("System bank set to main bank $%x", rx.systemBank)
}
// Queries
switch address {
case romXGetDefaultSystemBank:
bank := rx.systemBank
rx.logf("Peek in $%04x, current system bank %v", address, bank)
return true, bank
case romXGetDefaultTextBank:
page := uint8(rx.a.cg.getPage() & 0xf)
rx.logf("PeeK in $%04x, current text bank %v", address, page)
return true, 0x10 + page
case romXGetCurrentBootDelay:
delay := uint8(5) // We don't care
rx.logf("PeeK in $%04x, current boot delay %v", address, delay)
return true, delay
} }
return false, 0 return false, 0
} }
// Activation sequence detection // Activation sequence detection
if address == romXActivationSequence[rx.activationStep] { if address == romXceActivationSequence[rx.activationStep] {
rx.activationStep++ rx.activationStep++
//fmt.Printf("[romX]Activation step %v\n", rx.activationStep) rx.logf("Activation step %v", rx.activationStep)
if rx.activationStep == len(romXActivationSequence) { if rx.activationStep == len(romXActivationSequence) {
// Activation sequence completed // Activation sequence completed
rx.systemBank = setupBank rx.systemBank = romxSetupBank
rx.activationStep = 0 rx.activationStep = 0
// rx.a.cpu.SetTrace(true) rx.logf("System bank set to 0, %v", rx.systemBank)
fmt.Printf("[romX]System bank set to 0, %v\n", rx.systemBank)
} }
} else { } else {
rx.activationStep = 0 rx.activationStep = 0

File diff suppressed because one or more lines are too long