Separate read and write soft switches. Keyboard works now in Apple2e. Extended char set

This commit is contained in:
Ivan Izaguirre 2019-02-24 23:54:13 +01:00
parent 69cc4d15d5
commit 590dfb5cec
7 changed files with 174 additions and 101 deletions

View File

@ -18,10 +18,17 @@ Those tricks do not work with the Apple2e ROM
*/ */
type ansiConsoleFrontend struct { type ansiConsoleFrontend struct {
mmu *memoryManager
keyChannel chan uint8 keyChannel chan uint8
extraLineFeeds chan int extraLineFeeds chan int
} }
func newAnsiConsoleFrontend(mmu *memoryManager) ansiConsoleFrontend {
var fe ansiConsoleFrontend
fe.mmu = mmu
return fe
}
const refreshDelayMs = 100 const refreshDelayMs = 100
func (fe *ansiConsoleFrontend) getKey() (key uint8, ok bool) { func (fe *ansiConsoleFrontend) getKey() (key uint8, ok bool) {
@ -82,6 +89,7 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine(tp *textPages) {
// See "Understand the Apple II", page 5-10 // See "Understand the Apple II", page 5-10
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf // http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
isAltText := fe.mmu.isApple2e && fe.mmu.ioPage.isSoftSwitchExtActive(ioFlagAltChar)
var i, j, h uint8 var i, j, h uint8
// Top, middle and botton screen // Top, middle and botton screen
for i = 0; i < 120; i = i + 40 { for i = 0; i < 120; i = i + 40 {
@ -91,7 +99,7 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine(tp *textPages) {
for _, h = range []uint8{0, 128} { for _, h = range []uint8{0, 128} {
line := "" line := ""
for j = i + h; j < i+h+40; j++ { for j = i + h; j < i+h+40; j++ {
line += textMemoryByteToString(p.Peek(j)) line += textMemoryByteToString(p.Peek(j), isAltText)
} }
fmt.Printf("# %v #\n", line) fmt.Printf("# %v #\n", line)
} }
@ -106,19 +114,34 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine(tp *textPages) {
} }
} }
func textMemoryByteToString(value uint8) string { func textMemoryByteToString(value uint8, isAltCharSet bool) string {
// See https://en.wikipedia.org/wiki/Apple_II_character_set // See https://en.wikipedia.org/wiki/Apple_II_character_set
// Supports the new lowercase characters in the Apple2e
// Only ascii from 0x20 to 0x5F is visible // Only ascii from 0x20 to 0x5F is visible
// Does not support the new lowercase characters in the Apple2e
topBits := value >> 6 topBits := value >> 6
isInverse := topBits == 0 isInverse := topBits == 0
isFlash := topBits == 1 isFlash := topBits == 1
if isFlash && isAltCharSet {
// On the Apple2e with lowercase chars there is not flash mode.
isFlash = false
isInverse = true
}
if isAltCharSet {
value = value & 0x7F
} else {
value = value & 0x3F
}
value = (value & 0x3F)
if value < 0x20 { if value < 0x20 {
value += 0x40 value += 0x40
} }
if value == 0x7f {
// DEL is full box
value = '_'
}
if isFlash { if isFlash {
if value == ' ' { if value == ' ' {
// Flashing space in Apple is the full box. It can't be done with ANSI codes // Flashing space in Apple is the full box. It can't be done with ANSI codes
@ -132,6 +155,6 @@ func textMemoryByteToString(value uint8) string {
} }
} }
func textMemoryByteToStringHex(value uint8) string { func textMemoryByteToStringHex(value uint8, _ bool) string {
return fmt.Sprintf("%02x ", value) return fmt.Sprintf("%02x ", value)
} }

View File

@ -0,0 +1,33 @@
package apple2
import (
"testing"
)
func TestTextMemoryByteToString(t *testing.T) {
charExpectation(t, 0x01, false, "\033[7mA\033[0m")
charExpectation(t, 0x21, false, "\033[7m!\033[0m")
charExpectation(t, 0x41, false, "\033[5mA\033[0m")
charExpectation(t, 0x61, false, "\033[5m!\033[0m")
charExpectation(t, 0x81, false, "A")
charExpectation(t, 0xa1, false, "!")
charExpectation(t, 0xc1, false, "A")
charExpectation(t, 0xe1, false, "!")
charExpectation(t, 0x01, true, "\033[7mA\033[0m")
charExpectation(t, 0x21, true, "\033[7m!\033[0m")
charExpectation(t, 0x41, true, "\033[7mA\033[0m")
charExpectation(t, 0x61, true, "\033[7ma\033[0m")
charExpectation(t, 0x81, true, "A")
charExpectation(t, 0xa1, true, "!")
charExpectation(t, 0xc1, true, "A")
charExpectation(t, 0xe1, true, "a")
}
func charExpectation(t *testing.T, arg uint8, alt bool, expect string) {
s := textMemoryByteToString(arg, alt)
if s != expect {
t.Errorf("For 0x%02x:%v, got %v, expected %v", arg, alt, s, expect)
}
}

View File

@ -9,7 +9,7 @@ func Run(romFile string, log bool) {
var s core6502.State var s core6502.State
s.Mem = mmu s.Mem = mmu
var fe ansiConsoleFrontend fe := newAnsiConsoleFrontend(mmu)
mmu.ioPage.setKeyboardProvider(&fe) mmu.ioPage.setKeyboardProvider(&fe)
go fe.textModeGoRoutine(mmu.textPages1) go fe.textModeGoRoutine(mmu.textPages1)

View File

@ -5,13 +5,15 @@ import (
) )
type ioC0Page struct { type ioC0Page struct {
softSwitches [128]softSwitch softSwitchesR [128]softSwitchR
softSwitchesW [128]softSwitchW
softSwitchesData [128]uint8 softSwitchesData [128]uint8
keyboard keyboardProvider keyboard keyboardProvider
mmu *memoryManager mmu *memoryManager
} }
type softSwitch func(io *ioC0Page, isWrite bool, value uint8) uint8 type softSwitchR func(io *ioC0Page) uint8
type softSwitchW func(io *ioC0Page, value uint8)
type keyboardProvider interface { type keyboardProvider interface {
getKey() (key uint8, ok bool) getKey() (key uint8, ok bool)
@ -37,6 +39,27 @@ func newIoC0Page(mmu *memoryManager) *ioC0Page {
return &io return &io
} }
func (p *ioC0Page) addSoftSwitchRW(address uint8, ss softSwitchR) {
p.addSoftSwitchR(address, ss)
p.addSoftSwitchW(address, func(p *ioC0Page, _ uint8) {
ss(p)
})
}
func (p *ioC0Page) addSoftSwitchR(address uint8, ss softSwitchR) {
if p.softSwitchesR[address] != nil {
fmt.Printf("Addresss 0x0c%02x is already assigned for read", address)
}
p.softSwitchesR[address] = ss
}
func (p *ioC0Page) addSoftSwitchW(address uint8, ss softSwitchW) {
if p.softSwitchesW[address] != nil {
fmt.Printf("Addresss 0x0c%02x is already assigned for write", address)
}
p.softSwitchesW[address] = ss
}
func (p *ioC0Page) isSoftSwitchExtActive(ioFlag uint8) bool { func (p *ioC0Page) isSoftSwitchExtActive(ioFlag uint8) bool {
return (p.softSwitchesData[ioFlag] & ssOn) == ssOn return (p.softSwitchesData[ioFlag] & ssOn) == ssOn
} }
@ -47,25 +70,19 @@ func (p *ioC0Page) setKeyboardProvider(kb keyboardProvider) {
func (p *ioC0Page) Peek(address uint8) uint8 { func (p *ioC0Page) Peek(address uint8) uint8 {
//fmt.Printf("Peek on $C0%02x ", address) //fmt.Printf("Peek on $C0%02x ", address)
return p.access(address, false, 0) ss := p.softSwitchesR[address]
if ss == nil {
panic(fmt.Sprintf("Unknown softswitch on read to 0xC0%02x", address))
}
return ss(p)
} }
func (p *ioC0Page) Poke(address uint8, value uint8) { func (p *ioC0Page) Poke(address uint8, value uint8) {
//fmt.Printf("Poke on $C0%02x with %02x ", address, value) //fmt.Printf("Poke on $C0%02x with %02x ", address, value)
p.access(address, true, value) ss := p.softSwitchesW[address]
}
func (p *ioC0Page) access(address uint8, isWrite bool, value uint8) uint8 {
// The second half of the pages is reserved for slots
if address >= 0x90 {
// TODO reserved slots data
return 0
}
ss := p.softSwitches[address]
if ss == nil { if ss == nil {
panic(fmt.Sprintf("Unknown softswitch 0xC0%02x", address)) panic(fmt.Sprintf("Unknown softswitch on write to 0xC0%02x", address))
} }
ss(p, value)
return ss(p, isWrite, value)
} }

View File

@ -23,64 +23,66 @@ const (
) )
func addApple2SoftSwitches(io *ioC0Page) { func addApple2SoftSwitches(io *ioC0Page) {
ss := &io.softSwitches
ss[0x00] = getKeySoftSwitch // Keyboard io.addSoftSwitchRW(0x00, getKeySoftSwitch) // Keyboard
ss[0x10] = strobeKeyboardSoftSwitch // Keyboard Strobe io.addSoftSwitchRW(0x10, strobeKeyboardSoftSwitch) // Keyboard Strobe
ss[0x20] = notImplementedSoftSwitch // Cassette Output io.addSoftSwitchR(0x20, notImplementedSoftSwitchR) // Cassette Output
ss[0x30] = notImplementedSoftSwitch // Speaker io.addSoftSwitchR(0x30, notImplementedSoftSwitchR) // Speaker
ss[0x40] = notImplementedSoftSwitch // Game connector Strobe io.addSoftSwitchR(0x40, notImplementedSoftSwitchR) // Game connector Strobe
// Note: Some sources indicate that all these cover 16 positions // Note: Some sources indicate that all these cover 16 positions
// for read and write. But the Apple2e take over some of them, with // for read and write. But the Apple2e take over some of them, with
// the prevention on acting only on writes. // the prevention on acting only on writes.
ss[0x50] = getSoftSwitch(ioFlagGraphics, false) io.addSoftSwitchRW(0x50, getSoftSwitch(ioFlagGraphics, false))
ss[0x51] = getSoftSwitch(ioFlagGraphics, true) io.addSoftSwitchRW(0x51, getSoftSwitch(ioFlagGraphics, true))
ss[0x52] = getSoftSwitch(ioFlagMixed, false) io.addSoftSwitchRW(0x52, getSoftSwitch(ioFlagMixed, false))
ss[0x53] = getSoftSwitch(ioFlagMixed, true) io.addSoftSwitchRW(0x53, getSoftSwitch(ioFlagMixed, true))
ss[0x54] = getSoftSwitch(ioFlagSecondPage, false) io.addSoftSwitchRW(0x54, getSoftSwitch(ioFlagSecondPage, false))
ss[0x55] = getSoftSwitch(ioFlagSecondPage, true) io.addSoftSwitchRW(0x55, getSoftSwitch(ioFlagSecondPage, true))
ss[0x56] = getSoftSwitch(ioFlagHiRes, false) io.addSoftSwitchRW(0x56, getSoftSwitch(ioFlagHiRes, false))
ss[0x57] = getSoftSwitch(ioFlagHiRes, true) io.addSoftSwitchRW(0x57, getSoftSwitch(ioFlagHiRes, true))
ss[0x58] = getSoftSwitch(ioFlagAnnunciator0, false) io.addSoftSwitchRW(0x58, getSoftSwitch(ioFlagAnnunciator0, false))
ss[0x59] = getSoftSwitch(ioFlagAnnunciator0, true) io.addSoftSwitchRW(0x59, getSoftSwitch(ioFlagAnnunciator0, true))
ss[0x5a] = getSoftSwitch(ioFlagAnnunciator1, false) io.addSoftSwitchRW(0x5a, getSoftSwitch(ioFlagAnnunciator1, false))
ss[0x5b] = getSoftSwitch(ioFlagAnnunciator1, true) io.addSoftSwitchRW(0x5b, getSoftSwitch(ioFlagAnnunciator1, true))
ss[0x5c] = getSoftSwitch(ioFlagAnnunciator2, false) io.addSoftSwitchRW(0x5c, getSoftSwitch(ioFlagAnnunciator2, false))
ss[0x5d] = getSoftSwitch(ioFlagAnnunciator2, true) io.addSoftSwitchRW(0x5d, getSoftSwitch(ioFlagAnnunciator2, true))
ss[0x5e] = getSoftSwitch(ioFlagAnnunciator3, false) io.addSoftSwitchRW(0x5e, getSoftSwitch(ioFlagAnnunciator3, false))
ss[0x5f] = getSoftSwitch(ioFlagAnnunciator3, true) io.addSoftSwitchRW(0x5f, getSoftSwitch(ioFlagAnnunciator3, true))
ss[0x60] = notImplementedSoftSwitch // Cassetter Input io.addSoftSwitchR(0x60, notImplementedSoftSwitchR) // Cassette Input
ss[0x61] = getStatusSoftSwitch(ioFlagButton0) io.addSoftSwitchR(0x61, getStatusSoftSwitch(ioFlagButton0))
ss[0x62] = getStatusSoftSwitch(ioFlagButton1) io.addSoftSwitchR(0x62, getStatusSoftSwitch(ioFlagButton1))
ss[0x63] = getStatusSoftSwitch(ioFlagButton2) io.addSoftSwitchR(0x63, getStatusSoftSwitch(ioFlagButton2))
ss[0x64] = getStatusSoftSwitch(ioDataPaddle0) io.addSoftSwitchR(0x64, getStatusSoftSwitch(ioDataPaddle0))
ss[0x65] = getStatusSoftSwitch(ioDataPaddle1) io.addSoftSwitchR(0x65, getStatusSoftSwitch(ioDataPaddle1))
ss[0x66] = getStatusSoftSwitch(ioDataPaddle2) io.addSoftSwitchR(0x66, getStatusSoftSwitch(ioDataPaddle2))
ss[0x67] = getStatusSoftSwitch(ioDataPaddle3) io.addSoftSwitchR(0x67, getStatusSoftSwitch(ioDataPaddle3))
ss[0x68] = ss[0x60]
ss[0x69] = ss[0x61] // The previous 8 softswitches are repeated
ss[0x6A] = ss[0x62] io.addSoftSwitchR(0x68, notImplementedSoftSwitchR) // Cassette Input
ss[0x6B] = ss[0x63] io.addSoftSwitchR(0x69, getStatusSoftSwitch(ioFlagButton0))
ss[0x6C] = ss[0x64] io.addSoftSwitchR(0x6A, getStatusSoftSwitch(ioFlagButton1))
ss[0x6D] = ss[0x65] io.addSoftSwitchR(0x6B, getStatusSoftSwitch(ioFlagButton2))
ss[0x6E] = ss[0x66] io.addSoftSwitchR(0x6C, getStatusSoftSwitch(ioDataPaddle0))
ss[0x6F] = ss[0x67] io.addSoftSwitchR(0x6D, getStatusSoftSwitch(ioDataPaddle1))
ss[0x70] = notImplementedSoftSwitch // Game controllers reset io.addSoftSwitchR(0x6E, getStatusSoftSwitch(ioDataPaddle2))
io.addSoftSwitchR(0x6F, getStatusSoftSwitch(ioDataPaddle3))
io.addSoftSwitchR(0x70, notImplementedSoftSwitchR) // Game controllers reset
} }
func notImplementedSoftSwitch(*ioC0Page, bool, uint8) uint8 { func notImplementedSoftSwitchR(*ioC0Page) uint8 {
return 0 return 0
} }
func getStatusSoftSwitch(ioFlag uint8) softSwitch { func getStatusSoftSwitch(ioFlag uint8) softSwitchR {
return func(io *ioC0Page, isWrite bool, value uint8) uint8 { return func(io *ioC0Page) uint8 {
return io.softSwitchesData[ioFlag] return io.softSwitchesData[ioFlag]
} }
} }
func getSoftSwitch(ioFlag uint8, isSet bool) softSwitch { func getSoftSwitch(ioFlag uint8, isSet bool) softSwitchR {
return func(io *ioC0Page, isWrite bool, value uint8) uint8 { return func(io *ioC0Page) uint8 {
if isSet { if isSet {
io.softSwitchesData[ioFlag] = ssOn io.softSwitchesData[ioFlag] = ssOn
} else { } else {
@ -90,18 +92,18 @@ func getSoftSwitch(ioFlag uint8, isSet bool) softSwitch {
} }
} }
func getKeySoftSwitch(p *ioC0Page, _ bool, _ uint8) uint8 { func getKeySoftSwitch(io *ioC0Page) uint8 {
strobed := (p.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0 strobed := (io.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0
if strobed && p.keyboard != nil { if strobed && io.keyboard != nil {
if key, ok := p.keyboard.getKey(); ok { if key, ok := io.keyboard.getKey(); ok {
p.softSwitchesData[ioDataKeyboard] = key + (1 << 7) io.softSwitchesData[ioDataKeyboard] = key + (1 << 7)
} }
} }
return p.softSwitchesData[ioDataKeyboard] return io.softSwitchesData[ioDataKeyboard]
} }
func strobeKeyboardSoftSwitch(p *ioC0Page, _ bool, _ uint8) uint8 { func strobeKeyboardSoftSwitch(io *ioC0Page) uint8 {
result := p.softSwitchesData[ioDataKeyboard] result := io.softSwitchesData[ioDataKeyboard]
p.softSwitchesData[ioDataKeyboard] &^= 1 << 7 io.softSwitchesData[ioDataKeyboard] &^= 1 << 7
return result return result
} }

View File

@ -4,45 +4,43 @@ const (
ioFlagIntCxRom uint8 = 0x15 ioFlagIntCxRom uint8 = 0x15
ioFlagSlotC3Rom uint8 = 0x17 ioFlagSlotC3Rom uint8 = 0x17
ioFlag80Store uint8 = 0x18 ioFlag80Store uint8 = 0x18
ioFlagAltChar uint8 = 0x19
ioFlag80Col uint8 = 0x1F ioFlag80Col uint8 = 0x1F
) )
func addApple2ESoftSwitches(io *ioC0Page) { func addApple2ESoftSwitches(io *ioC0Page) {
ss := &io.softSwitches
ss[0x00] = getSoftSwitchExt(ioFlag80Store, ssOff, nil) io.addSoftSwitchW(0x00, getSoftSwitchExt(ioFlag80Store, ssOff, nil))
ss[0x01] = getSoftSwitchExt(ioFlag80Store, ssOn, nil) io.addSoftSwitchW(0x01, getSoftSwitchExt(ioFlag80Store, ssOn, nil))
ss[0x06] = getSoftSwitchExt(ioFlagIntCxRom, ssOff, softSwitchIntCxRomOff) io.addSoftSwitchW(0x06, getSoftSwitchExt(ioFlagIntCxRom, ssOff, softSwitchIntCxRomOff))
ss[0x07] = getSoftSwitchExt(ioFlagIntCxRom, ssOn, softSwitchIntCxRomOn) io.addSoftSwitchW(0x07, getSoftSwitchExt(ioFlagIntCxRom, ssOn, softSwitchIntCxRomOn))
ss[0x0A] = getSoftSwitchExt(ioFlagSlotC3Rom, ssOff, softSwitchSlotC3RomOff) io.addSoftSwitchW(0x0A, getSoftSwitchExt(ioFlagSlotC3Rom, ssOff, softSwitchSlotC3RomOff))
ss[0x0B] = getSoftSwitchExt(ioFlagSlotC3Rom, ssOn, softSwitchSlotC3RomOn) io.addSoftSwitchW(0x0B, getSoftSwitchExt(ioFlagSlotC3Rom, ssOn, softSwitchSlotC3RomOn))
ss[0x0C] = getSoftSwitchExt(ioFlag80Col, ssOff, nil) io.addSoftSwitchW(0x0C, getSoftSwitchExt(ioFlag80Col, ssOff, nil))
ss[0x0D] = getSoftSwitchExt(ioFlag80Col, ssOn, nil) io.addSoftSwitchW(0x0D, getSoftSwitchExt(ioFlag80Col, ssOn, nil))
io.addSoftSwitchW(0x0E, getSoftSwitchExt(ioFlagAltChar, ssOff, nil))
io.addSoftSwitchW(0x0F, getSoftSwitchExt(ioFlagAltChar, ssOn, nil))
io.softSwitchesData[ioFlagAltChar] = ssOn // Not sure about this.
ss[0x15] = getStatusSoftSwitch(ioFlagIntCxRom) io.addSoftSwitchR(0x15, getStatusSoftSwitch(ioFlagIntCxRom))
ss[0x17] = getStatusSoftSwitch(ioFlagSlotC3Rom) io.addSoftSwitchR(0x17, getStatusSoftSwitch(ioFlagSlotC3Rom))
ss[0x18] = getStatusSoftSwitch(ioFlag80Store) io.addSoftSwitchR(0x18, getStatusSoftSwitch(ioFlag80Store))
ss[0x1C] = getStatusSoftSwitch(ioFlagSecondPage) io.addSoftSwitchR(0x1C, getStatusSoftSwitch(ioFlagSecondPage))
ss[0x1F] = getStatusSoftSwitch(ioFlag80Col) io.addSoftSwitchR(0x1F, getStatusSoftSwitch(ioFlag80Col))
} }
type softSwitchExtAction func(io *ioC0Page) type softSwitchExtAction func(io *ioC0Page)
func getSoftSwitchExt(ioFlag uint8, dstValue uint8, action softSwitchExtAction) softSwitch { func getSoftSwitchExt(ioFlag uint8, dstValue uint8, action softSwitchExtAction) softSwitchW {
return func(io *ioC0Page, isWrite bool, value uint8) uint8 { return func(io *ioC0Page, _ uint8) {
//fmt.Printf("Softswitch 0x%02x %v %v\n", ioFlag, isWrite, dstValue)
if !isWrite {
return 0 // New Apple2e softswitches ignore reads
}
currentValue := io.softSwitchesData[ioFlag] currentValue := io.softSwitchesData[ioFlag]
if currentValue == dstValue { if currentValue == dstValue {
return 0 // Already switched, ignore return // Already switched, ignore
} }
if action != nil { if action != nil {
action(io) action(io)
} }
io.softSwitchesData[ioFlag] = value io.softSwitchesData[ioFlag] = dstValue
return 0
} }
} }

View File

@ -4,8 +4,8 @@ import "go6502/apple2"
func main() { func main() {
//romFile := "apple2/romdumps/Apple2.rom" //romFile := "apple2/romdumps/Apple2.rom"
romFile := "apple2/romdumps/Apple2_Plus.rom" //romFile := "apple2/romdumps/Apple2_Plus.rom"
//romFile := "apple2/romdumps/Apple2e.rom" romFile := "apple2/romdumps/Apple2e.rom"
log := false log := false
apple2.Run(romFile, log) apple2.Run(romFile, log)