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
- Mouse Card, emulates the entry points, not the softswitches.
- Host console card. Maps the host STDIN and STDOUT to PR# and IN#
- ROMXe, limited to font switching
- Graphic modes:
- 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)
-rom string
main rom file (default "<default>")
-romx
emulate a RomX
-saturnCardSlot int
slot for the 256kb Saturn card. -1 for none (default -1)
-sequencer

View File

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

View File

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

View File

@ -16,6 +16,7 @@ type CharacterGenerator struct {
data []uint8
columnMap charColumnMap
page int
pageSize int
}
type charColumnMap func(column int) int
@ -29,17 +30,24 @@ func charGenColumnsMap2e(column int) int {
}
const (
charGenPageSize = 2048
charGenPageSize2Plus = 2048
charGenPageSize2E = 2048 * 2
)
// 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
cg.columnMap = order
cg.pageSize = charGenPageSize2Plus
if isApple2e {
cg.pageSize = charGenPageSize2E
}
err := cg.load(filename)
if err != nil {
return nil, err
}
cg.columnMap = order
return &cg, nil
}
@ -49,7 +57,7 @@ func (cg *CharacterGenerator) load(filename string) error {
return err
}
size := len(bytes)
if size < charGenPageSize {
if size < cg.pageSize {
return errors.New("character ROM size not supported")
}
cg.data = bytes
@ -58,16 +66,20 @@ func (cg *CharacterGenerator) load(filename string) error {
func (cg *CharacterGenerator) setPage(page int) {
// 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
}
func (cg *CharacterGenerator) getPage() int {
return cg.page
}
func (cg *CharacterGenerator) nextPage() {
cg.setPage(cg.page + 1)
}
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)
value := bits >> uint(bit) & 1
return value == 1

147
romX.go
View File

@ -10,10 +10,14 @@ import (
RomX from https://theromexchange.com/
This complement uses the RomX API spec to switch main ROM and character generator ROM
Only the font switch is implemented
See:
https://theromexchange.com/documentation/ROM%20X%20API%20Reference.pdf
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
position F12, that has access to the full 0xc000-0xf000 on the Apple II+
@ -30,32 +34,64 @@ type romX struct {
memory core6502.Memory
activationStep int
systemBank uint8
mainBank uint8
tempBank uint8
textBank uint8
debug bool
}
var romXActivationSequence = []uint16{0xcaca, 0xcaca, 0xcafe}
var romXceActivationSequence = []uint16{0xfaca, 0xfaca, 0xfafe}
const (
setupBank = uint8(0)
romXSetSystemBankBaseAddress = uint16(0xcef0)
romXSetTextBankBaseAddress = uint16(0xcfd0)
romXDefaultSystemBankAddress = uint16(0xd034)
romXDefaultTextBankAddress = uint16(0xd02e)
romxSetupBank = uint8(0)
romXPlusSetSystemBankBaseAddress = uint16(0xcef0)
romXPlusSetTextBankBaseAddress = uint16(0xcfd0)
// Unknown
romXFirmwareMark0Address = uint16(0xdffe)
romXFirmwareMark0Value = uint8(0x4a)
romXFirmwareMark1Address = uint16(0xdfff)
romXFirmwareMark1Value = uint8(0xcd)
//romXFirmwareMark0Address = uint16(0xdffe)
//romXFirmwareMark0Value = uint8(0x4a)
//romXFirmwareMark1Address = uint16(0xdfff)
//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
rx.a = a
rx.memory = memory
rx.systemBank = 0
rx.systemBank = 1
rx.mainBank = 1
rx.tempBank = 1
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 {
@ -67,10 +103,10 @@ func (rx *romX) Peek(address uint16) uint8 {
}
func (rx *romX) PeekCode(address uint16) uint8 {
//intercepted, value := rx.interceptAccess(address)
//if intercepted {
// return value
//}
intercepted, value := rx.interceptAccess(address)
if intercepted {
return value
}
return rx.memory.PeekCode(address)
}
@ -79,6 +115,11 @@ func (rx *romX) Poke(address uint16, value uint8) {
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) {
// Intercept only $C080 to $FFFF as seen by the F12 chip
if address < 0xc080 {
@ -86,45 +127,65 @@ func (rx *romX) interceptAccess(address uint16) (bool, uint8) {
}
// Setup mode when the setup bank is active
if rx.systemBank == setupBank {
switch address {
case romXDefaultSystemBankAddress:
fmt.Printf("[romX]Peek in $%04x, current system bank %v\n", address, rx.systemBank)
return true, 0xe0 + rx.systemBank
case romXDefaultTextBankAddress:
fmt.Printf("[romX]PeeK in $%04x, current text bank %v\n", address, rx.textBank)
return true, 0xd0 + rx.textBank
case romXFirmwareMark0Address:
fmt.Printf("[romX]Peek in $%04x, ???\n", address)
return true, romXFirmwareMark0Value
case romXFirmwareMark1Address:
fmt.Printf("[romX]Peek in $%04x, ???\n", address)
return true, romXFirmwareMark1Value
if rx.systemBank == romxSetupBank {
// Range commands
nibble := uint8(address & 0xf)
switch address & 0xfff0 {
case romXceSetMainBank:
rx.mainBank = nibble
rx.logf("Main bank set to $%x", nibble)
case romXcePresetTextBank:
textBank := int(nibble)
rx.a.cg.setPage(textBank)
rx.logf("[romX]Text bank set to $%x", nibble)
case romXceLowerUpperBanks:
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 {
rx.systemBank = uint8(address & 0xf)
fmt.Printf("[romX]System bank set to %v\n", rx.systemBank)
} else if address&0xfff0 == romXSetTextBankBaseAddress {
rx.textBank = uint8(address & 0xf)
fmt.Printf("[romX]Text bank set to %v\n", rx.textBank)
} else if address < 0xe000 {
fmt.Printf("[romX]Peek in $%04x\n", address)
// More commands
switch address {
case romXceSelectTempBank:
rx.systemBank = rx.tempBank
rx.logf("System bank set to temp bank $%x", rx.systemBank)
case romXceSelectMainBank:
rx.systemBank = rx.mainBank
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
}
// Activation sequence detection
if address == romXActivationSequence[rx.activationStep] {
if address == romXceActivationSequence[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) {
// Activation sequence completed
rx.systemBank = setupBank
rx.systemBank = romxSetupBank
rx.activationStep = 0
// rx.a.cpu.SetTrace(true)
fmt.Printf("[romX]System bank set to 0, %v\n", rx.systemBank)
rx.logf("System bank set to 0, %v", rx.systemBank)
}
} else {
rx.activationStep = 0

File diff suppressed because one or more lines are too long