mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-10-07 12:55:59 +00:00
Improved memory manager to support Apple2e shadow ROM
This commit is contained in:
parent
a28536d561
commit
d2c897f7ea
@ -1,110 +0,0 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
)
|
||||
|
||||
// See https://i.stack.imgur.com/yn21s.gif
|
||||
|
||||
type addressSpace struct {
|
||||
activeMemory *pagedMemory
|
||||
physicalRAM [256]ramPage // up to 64 Kb
|
||||
physicalROM [48]romPage // up to 12 Kb
|
||||
ioPage ioC0Page
|
||||
textPages1 *textPages
|
||||
activeSlow int // Slot that has the addressing 0xc800 to 0ccfff
|
||||
}
|
||||
|
||||
const (
|
||||
ioAreaMask uint16 = 0xFF80
|
||||
ioAreaValue uint16 = 0xC000
|
||||
ioC8Off uint16 = 0xCFFF
|
||||
)
|
||||
|
||||
// Peek returns the data on the given address
|
||||
func (a *addressSpace) Peek(address uint16) uint8 {
|
||||
if address == ioC8Off {
|
||||
a.resetSlotRoms()
|
||||
}
|
||||
if (address & ioAreaMask) == ioAreaValue {
|
||||
return a.ioPage.Peek(uint8(address))
|
||||
}
|
||||
return a.activeMemory.Peek(address)
|
||||
}
|
||||
|
||||
// Poke sets the data at the given address
|
||||
func (a *addressSpace) Poke(address uint16, value uint8) {
|
||||
if address == ioC8Off {
|
||||
a.resetSlotRoms()
|
||||
}
|
||||
if (address & ioAreaMask) == ioAreaValue {
|
||||
a.ioPage.Poke(uint8(address), value)
|
||||
}
|
||||
a.activeMemory.Poke(address, value)
|
||||
}
|
||||
|
||||
func (a *addressSpace) resetSlotRoms() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func newAddressSpace() *addressSpace {
|
||||
var a addressSpace
|
||||
|
||||
var m pagedMemory
|
||||
a.activeMemory = &m
|
||||
|
||||
// Assign RAM from 0x0000 to 0xbfff, 48kb
|
||||
for i := 0; i <= 0xbf; i++ {
|
||||
m.SetPage(uint8(i), &(a.physicalRAM[i]))
|
||||
}
|
||||
|
||||
// Assign ROM from 0xd000 to 0xfff, 12 kb. The ROM is empty
|
||||
for i := 0xd0; i <= 0xff; i++ {
|
||||
m.SetPage(uint8(i), &(a.physicalROM[i-0xd0]))
|
||||
}
|
||||
|
||||
// Set the 0xc000 to 0xcfff as unasigned, 4kb. It wil be taken by slot cards.
|
||||
for i := uint8(0xc0); i <= 0xcf; i++ {
|
||||
var p unassignedPage
|
||||
p.page = i
|
||||
m.SetPage(i, &p)
|
||||
}
|
||||
|
||||
// Replace RAM in the TEXT1 area.
|
||||
// TODO: treat as normal ram. Add is dirty in all RAM pages
|
||||
var t textPages
|
||||
a.textPages1 = &t
|
||||
for i := 0; i < 4; i++ {
|
||||
m.SetPage(uint8(4+i), &(t.pages[i]))
|
||||
}
|
||||
|
||||
return &a
|
||||
}
|
||||
|
||||
// LoadRom loads a binary file to the top of the memory.
|
||||
func (a *addressSpace) loadRom(filename string) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
stats, statsErr := f.Stat()
|
||||
if statsErr != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
size := stats.Size()
|
||||
if size != 12288 {
|
||||
panic("Rom size not supported")
|
||||
}
|
||||
bytes := make([]byte, size)
|
||||
|
||||
buf := bufio.NewReader(f)
|
||||
buf.Read(bytes)
|
||||
|
||||
for i, v := range bytes {
|
||||
a.physicalROM[i>>8].burn(uint8(i), uint8(v))
|
||||
}
|
||||
}
|
@ -4,15 +4,17 @@ import "go6502/core6502"
|
||||
|
||||
// Run instantiates an apple2 and start emulation
|
||||
func Run(romFile string, log bool) {
|
||||
a := newAddressSpace()
|
||||
a.loadRom(romFile)
|
||||
mmu := newAddressSpace(romFile)
|
||||
if mmu.isApple2e {
|
||||
addApple2ESoftSwitches(mmu)
|
||||
}
|
||||
|
||||
var s core6502.State
|
||||
s.Mem = a
|
||||
s.Mem = mmu
|
||||
|
||||
var fe ansiConsoleFrontend
|
||||
a.ioPage.setKeyboardProvider(&fe)
|
||||
go fe.textModeGoRoutine(a.textPages1)
|
||||
mmu.ioPage.setKeyboardProvider(&fe)
|
||||
go fe.textModeGoRoutine(mmu.textPages1)
|
||||
|
||||
// Start the processor
|
||||
core6502.Reset(&s)
|
||||
|
@ -5,12 +5,15 @@ import (
|
||||
)
|
||||
|
||||
type ioC0Page struct {
|
||||
ioFlags uint64
|
||||
data [1]uint8
|
||||
keyboard keyboardProvider
|
||||
addressSpace *addressSpace
|
||||
softSwitches [128]softSwitch
|
||||
softSwitchesData [128]uint8
|
||||
keyboard keyboardProvider
|
||||
mmu *memoryManager
|
||||
}
|
||||
|
||||
type softSwitch func(io *ioC0Page, isWrite bool, value uint8) uint8
|
||||
|
||||
// TODO: change interface to func
|
||||
type keyboardProvider interface {
|
||||
getKey() (key uint8, ok bool)
|
||||
}
|
||||
@ -19,44 +22,52 @@ type keyboardProvider interface {
|
||||
// See https://stason.org/TULARC/pc/apple2/programmer/004-I-d-like-to-do-some-serious-Apple-II-programming-Whe.html
|
||||
|
||||
const (
|
||||
ioFlagNone uint8 = 0
|
||||
ioFlagGraphics uint8 = 3
|
||||
ioFlagMixed uint8 = 8
|
||||
ioFlagSecondPage uint8 = 1
|
||||
ioFlagHiRes uint8 = 2
|
||||
ioFlagAnnunciator0 uint8 = 4
|
||||
ioFlagAnnunciator1 uint8 = 5
|
||||
ioFlagAnnunciator2 uint8 = 6
|
||||
ioFlagAnnunciator3 uint8 = 7
|
||||
ioDataKeyboard uint8 = 0x10
|
||||
ioFlagGraphics uint8 = 0x50
|
||||
ioFlagMixed uint8 = 0x52
|
||||
ioFlagSecondPage uint8 = 0x54
|
||||
ioFlagHiRes uint8 = 0x56
|
||||
ioFlagAnnunciator0 uint8 = 0x58
|
||||
ioFlagAnnunciator1 uint8 = 0x5a
|
||||
ioFlagAnnunciator2 uint8 = 0x5c
|
||||
ioFlagAnnunciator3 uint8 = 0x5e
|
||||
)
|
||||
|
||||
const (
|
||||
ioDataKeyboard uint8 = 0
|
||||
)
|
||||
|
||||
type softSwitch struct {
|
||||
ioFlag uint8
|
||||
value bool
|
||||
onWriteOnly bool
|
||||
func (p *ioC0Page) isSoftSwitchExtActive(ioFlag uint8) bool {
|
||||
return (p.softSwitchesData[ioFlag] & 0x08) == 0x80
|
||||
}
|
||||
|
||||
var softSwitches = [256]softSwitch{
|
||||
0x50: softSwitch{ioFlagGraphics, false, false},
|
||||
0x51: softSwitch{ioFlagGraphics, true, false},
|
||||
0x52: softSwitch{ioFlagMixed, false, false},
|
||||
0x53: softSwitch{ioFlagMixed, true, false},
|
||||
0x54: softSwitch{ioFlagSecondPage, false, false},
|
||||
0x55: softSwitch{ioFlagSecondPage, true, false},
|
||||
0x56: softSwitch{ioFlagHiRes, false, false},
|
||||
0x57: softSwitch{ioFlagHiRes, true, false},
|
||||
0x58: softSwitch{ioFlagAnnunciator0, false, false},
|
||||
0x59: softSwitch{ioFlagAnnunciator0, true, false},
|
||||
0x5a: softSwitch{ioFlagAnnunciator1, false, false},
|
||||
0x5b: softSwitch{ioFlagAnnunciator1, true, false},
|
||||
0x5c: softSwitch{ioFlagAnnunciator2, false, false},
|
||||
0x5d: softSwitch{ioFlagAnnunciator2, true, false},
|
||||
0x5e: softSwitch{ioFlagAnnunciator3, false, false},
|
||||
0x5f: softSwitch{ioFlagAnnunciator3, true, false},
|
||||
func newIoC0Page(mmu *memoryManager) *ioC0Page {
|
||||
var p ioC0Page
|
||||
p.mmu = mmu
|
||||
ss := &p.softSwitches
|
||||
|
||||
ss[0x00] = getKeySoftSwitch // Keyboard
|
||||
ss[0x10] = strobeKeyboardSoftSwitch // Keyboard Strobe
|
||||
ss[0x30] = notImplementedSoftSwitch // Speaker
|
||||
|
||||
ss[0x50] = getSoftSwitch(ioFlagGraphics, false)
|
||||
ss[0x51] = getSoftSwitch(ioFlagGraphics, true)
|
||||
ss[0x52] = getSoftSwitch(ioFlagMixed, false)
|
||||
ss[0x53] = getSoftSwitch(ioFlagMixed, true)
|
||||
ss[0x54] = getSoftSwitch(ioFlagSecondPage, false)
|
||||
ss[0x55] = getSoftSwitch(ioFlagSecondPage, true)
|
||||
ss[0x56] = getSoftSwitch(ioFlagHiRes, false)
|
||||
ss[0x57] = getSoftSwitch(ioFlagHiRes, true)
|
||||
ss[0x58] = getSoftSwitch(ioFlagAnnunciator0, false)
|
||||
ss[0x59] = getSoftSwitch(ioFlagAnnunciator0, true)
|
||||
ss[0x5a] = getSoftSwitch(ioFlagAnnunciator1, false)
|
||||
ss[0x5b] = getSoftSwitch(ioFlagAnnunciator1, true)
|
||||
ss[0x5c] = getSoftSwitch(ioFlagAnnunciator2, false)
|
||||
ss[0x5d] = getSoftSwitch(ioFlagAnnunciator2, true)
|
||||
ss[0x5e] = getSoftSwitch(ioFlagAnnunciator3, false)
|
||||
ss[0x5f] = getSoftSwitch(ioFlagAnnunciator3, true)
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *ioC0Page) setKeyboardProvider(kb keyboardProvider) {
|
||||
p.keyboard = kb
|
||||
}
|
||||
|
||||
func (p *ioC0Page) Peek(address uint8) uint8 {
|
||||
@ -70,47 +81,47 @@ func (p *ioC0Page) Poke(address uint8, value uint8) {
|
||||
}
|
||||
|
||||
func (p *ioC0Page) access(address uint8, isWrite bool, value uint8) uint8 {
|
||||
|
||||
ss := softSwitches[address]
|
||||
if ss.ioFlag != ioFlagNone {
|
||||
if !isWrite || !!ss.onWriteOnly {
|
||||
if ss.value {
|
||||
p.ioFlags |= 1 << ss.ioFlag
|
||||
} else {
|
||||
p.ioFlags &^= 1 << ss.ioFlag
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch address {
|
||||
case 0x00: // keyboard (Is this the full range 0x0?)
|
||||
return p.getKey()
|
||||
case 0x10: // strobe (Is this the full range 0x1?)
|
||||
return p.strobeKeyboard()
|
||||
case 0x30: // spkr
|
||||
// TODO: Support sound
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown softswitch 0xC0%02x", address))
|
||||
}
|
||||
// The second hals of the pages is reserved for slots
|
||||
if address >= 0x80 {
|
||||
// TODO reserved slots data
|
||||
return 0
|
||||
}
|
||||
|
||||
ss := p.softSwitches[address]
|
||||
if ss == nil {
|
||||
panic(fmt.Sprintf("Unknown softswitch 0xC0%02x", address))
|
||||
}
|
||||
|
||||
return ss(p, isWrite, value)
|
||||
}
|
||||
|
||||
func getSoftSwitch(ioFlag uint8, isSet bool) softSwitch {
|
||||
return func(io *ioC0Page, isWrite bool, value uint8) uint8 {
|
||||
if isSet {
|
||||
io.softSwitchesData[ioFlag] = 0x80
|
||||
} else {
|
||||
io.softSwitchesData[ioFlag] = 0
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func notImplementedSoftSwitch(*ioC0Page, bool, uint8) uint8 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *ioC0Page) setKeyboardProvider(kb keyboardProvider) {
|
||||
p.keyboard = kb
|
||||
}
|
||||
|
||||
func (p *ioC0Page) getKey() uint8 {
|
||||
strobed := (p.data[ioDataKeyboard] & (1 << 7)) == 0
|
||||
func getKeySoftSwitch(p *ioC0Page, _ bool, _ uint8) uint8 {
|
||||
strobed := (p.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0
|
||||
if strobed && p.keyboard != nil {
|
||||
if key, ok := p.keyboard.getKey(); ok {
|
||||
p.data[ioDataKeyboard] = key + (1 << 7)
|
||||
p.softSwitchesData[ioDataKeyboard] = key + (1 << 7)
|
||||
}
|
||||
}
|
||||
return p.data[ioDataKeyboard]
|
||||
return p.softSwitchesData[ioDataKeyboard]
|
||||
}
|
||||
|
||||
func (p *ioC0Page) strobeKeyboard() uint8 {
|
||||
result := p.data[ioDataKeyboard]
|
||||
p.data[ioDataKeyboard] &^= 1 << 7
|
||||
func strobeKeyboardSoftSwitch(p *ioC0Page, _ bool, _ uint8) uint8 {
|
||||
result := p.softSwitchesData[ioDataKeyboard]
|
||||
p.softSwitchesData[ioDataKeyboard] &^= 1 << 7
|
||||
return result
|
||||
}
|
||||
|
144
apple2/memoryManager.go
Normal file
144
apple2/memoryManager.go
Normal file
@ -0,0 +1,144 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
)
|
||||
|
||||
// See https://fabiensanglard.net/fd_proxy/prince_of_persia/Inside%20the%20Apple%20IIe.pdf
|
||||
// See https://i.stack.imgur.com/yn21s.gif
|
||||
|
||||
type memoryManager struct {
|
||||
// Map of assigned pages
|
||||
activeMemory *pagedMemory
|
||||
// Pages prepared to be paged in and out
|
||||
physicalMainRAM []ramPage // 0x0000 to 0xbfff, Up to 48 Kb
|
||||
physicalROM []romPage // 0xd000 to 0xffff, 12 Kb
|
||||
physicalROMe []romPage // 0xc000 to 0xcfff, Zero or 4bk in the Apple2e
|
||||
unassignedExpansionROM []unassignedPage // 0xc000 to 0xcfff
|
||||
ioPage *ioC0Page // 0xc000 to 0xc080
|
||||
isApple2e bool
|
||||
textPages1 *textPages // 0x0400 to 0x07ff
|
||||
activeSlow int // Slot that has the addressing 0xc800 to 0ccfff
|
||||
}
|
||||
|
||||
const (
|
||||
ioAreaMask uint16 = 0xFF80
|
||||
ioAreaValue uint16 = 0xC000
|
||||
ioC8Off uint16 = 0xCFFF
|
||||
)
|
||||
|
||||
// Peek returns the data on the given address
|
||||
func (mmu *memoryManager) Peek(address uint16) uint8 {
|
||||
if address == ioC8Off {
|
||||
mmu.resetSlotExpansionRoms()
|
||||
}
|
||||
return mmu.activeMemory.Peek(address)
|
||||
}
|
||||
|
||||
// Poke sets the data at the given address
|
||||
func (mmu *memoryManager) Poke(address uint16, value uint8) {
|
||||
if address == ioC8Off {
|
||||
mmu.resetSlotExpansionRoms()
|
||||
}
|
||||
mmu.activeMemory.Poke(address, value)
|
||||
}
|
||||
|
||||
// When 0xcfff is accessed the card expansion rom is unassigned
|
||||
func (mmu *memoryManager) resetSlotExpansionRoms() {
|
||||
if mmu.ioPage.isSoftSwitchExtActive(ioFlagIntCxRom) {
|
||||
// Ignore if the Apple2 shadow ROM is active
|
||||
return
|
||||
}
|
||||
for i := 8; i < 16; i++ {
|
||||
p := mmu.unassignedExpansionROM[i]
|
||||
mmu.activeMemory.SetPage(uint8(i+0xc0), &p)
|
||||
}
|
||||
}
|
||||
|
||||
func newAddressSpace(romImage string) *memoryManager {
|
||||
var mmu memoryManager
|
||||
|
||||
var m pagedMemory
|
||||
mmu.activeMemory = &m
|
||||
|
||||
// Assign RAM from 0x0000 to 0xbfff, 48kb
|
||||
mmu.physicalMainRAM = make([]ramPage, 0xc0)
|
||||
for i := 0; i <= 0xbf; i++ {
|
||||
m.SetPage(uint8(i), &(mmu.physicalMainRAM[i]))
|
||||
}
|
||||
|
||||
mmu.loadRom(romImage)
|
||||
// Assign the first 12kb of ROM from 0xd000 to 0xfff
|
||||
for i := 0xd0; i <= 0xff; i++ {
|
||||
m.SetPage(uint8(i), &(mmu.physicalROM[i-0xd0]))
|
||||
}
|
||||
|
||||
// Set the io in 0xc000
|
||||
mmu.ioPage = newIoC0Page(&mmu)
|
||||
m.SetPage(0xc0, mmu.ioPage)
|
||||
|
||||
// Set the 0xc100 to 0xcfff as unasigned, 4kb. It wil be taken by slot cards.
|
||||
mmu.unassignedExpansionROM = make([]unassignedPage, 0x10)
|
||||
for i := 1; i < 0x10; i++ {
|
||||
page := uint8(i + 0xc0)
|
||||
p := &mmu.unassignedExpansionROM[i]
|
||||
p.page = page
|
||||
m.SetPage(page, p)
|
||||
}
|
||||
|
||||
// Replace RAM in the TEXT1 area.
|
||||
// TODO: treat as normal ram. Add is dirty in all RAM pages
|
||||
var t textPages
|
||||
mmu.textPages1 = &t
|
||||
for i := 0; i < 4; i++ {
|
||||
m.SetPage(uint8(4+i), &(t.pages[i]))
|
||||
}
|
||||
|
||||
return &mmu
|
||||
}
|
||||
|
||||
// LoadRom loads a binary file to the top of the memory.
|
||||
const (
|
||||
apple2RomSize = 12 * 1024
|
||||
apple2eRomSize = 16 * 1024
|
||||
)
|
||||
|
||||
func (mmu *memoryManager) loadRom(filename string) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
stats, statsErr := f.Stat()
|
||||
if statsErr != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
size := stats.Size()
|
||||
if size != apple2RomSize && size != apple2eRomSize {
|
||||
panic("Rom size not supported")
|
||||
}
|
||||
bytes := make([]byte, size)
|
||||
buf := bufio.NewReader(f)
|
||||
buf.Read(bytes)
|
||||
|
||||
romStart := 0
|
||||
if size == apple2eRomSize {
|
||||
// The extra 4kb ROM is first in the rom file.
|
||||
// It starts with 256 unused bytes not mapped to 0xc000.
|
||||
mmu.isApple2e = true
|
||||
extraRomSize := apple2eRomSize - apple2RomSize
|
||||
mmu.physicalROMe = make([]romPage, extraRomSize>>8)
|
||||
for i := 0; i < extraRomSize; i++ {
|
||||
mmu.physicalROMe[i>>8].burn(uint8(i), bytes[i])
|
||||
}
|
||||
romStart = extraRomSize
|
||||
}
|
||||
|
||||
mmu.physicalROM = make([]romPage, apple2RomSize>>8)
|
||||
for i := 0; i < apple2RomSize; i++ {
|
||||
mmu.physicalROM[i>>8].burn(uint8(i), bytes[i+romStart])
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package apple2
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// memoryPage is a data page of 256 bytes
|
||||
type memoryPage interface {
|
||||
@ -24,12 +27,11 @@ func (m *pagedMemory) Peek(address uint16) uint8 {
|
||||
func (m *pagedMemory) Poke(address uint16, value uint8) {
|
||||
hi := uint8(address >> 8)
|
||||
lo := uint8(address)
|
||||
//fmt.Println(hi)
|
||||
m.data[hi].Poke(lo, value)
|
||||
}
|
||||
|
||||
// SetPage assigns a MemoryPage implementation on the page given
|
||||
func (m *pagedMemory) SetPage(index uint8, page memoryPage) {
|
||||
fmt.Printf("Seeting page 0x%02x\n", index)
|
||||
fmt.Printf("Assigning page 0x%02x type %s\n", index, reflect.TypeOf(page))
|
||||
m.data[index] = page
|
||||
}
|
||||
|
BIN
apple2/romdumps/Apple2.rom
Normal file
BIN
apple2/romdumps/Apple2.rom
Normal file
Binary file not shown.
BIN
apple2/romdumps/Apple2_Plus.rom
Normal file
BIN
apple2/romdumps/Apple2_Plus.rom
Normal file
Binary file not shown.
BIN
apple2/romdumps/Apple2e.rom
Normal file
BIN
apple2/romdumps/Apple2e.rom
Normal file
Binary file not shown.
BIN
apple2/romdumps/Apple2e_Enhanced.rom
Normal file
BIN
apple2/romdumps/Apple2e_Enhanced.rom
Normal file
Binary file not shown.
74
apple2/softSwitches2e.go
Normal file
74
apple2/softSwitches2e.go
Normal file
@ -0,0 +1,74 @@
|
||||
package apple2
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
ioFlagIntCxRom uint8 = 0x15
|
||||
ioFlagSlotC3Rom uint8 = 0x17
|
||||
)
|
||||
|
||||
func addApple2ESoftSwitches(mmu *memoryManager) {
|
||||
ss := &mmu.ioPage.softSwitches
|
||||
ss[0x06] = getSoftSwitchExt(ioFlagIntCxRom, 0x00, softSwitchIntCxRomOff)
|
||||
ss[0x07] = getSoftSwitchExt(ioFlagIntCxRom, 0x80, softSwitchIntCxRomOn)
|
||||
ss[0x15] = getStatusSoftSwitchExt(ioFlagIntCxRom)
|
||||
|
||||
ss[0x0A] = getSoftSwitchExt(ioFlagSlotC3Rom, 0x00, softSwitchSlotC3RomOff)
|
||||
ss[0x0B] = getSoftSwitchExt(ioFlagSlotC3Rom, 0x80, softSwitchSlotC3RomOn)
|
||||
ss[0x17] = getStatusSoftSwitchExt(ioFlagSlotC3Rom)
|
||||
|
||||
ss[0x1c] = getStatusSoftSwitchExt(ioFlagSecondPage)
|
||||
|
||||
}
|
||||
|
||||
type softSwitchExtAction func(io *ioC0Page)
|
||||
|
||||
func getStatusSoftSwitchExt(ioFlag uint8) softSwitch {
|
||||
return func(io *ioC0Page, isWrite bool, value uint8) uint8 {
|
||||
return io.softSwitchesData[ioFlag]
|
||||
}
|
||||
}
|
||||
|
||||
func getSoftSwitchExt(ioFlag uint8, dstValue uint8, action softSwitchExtAction) softSwitch {
|
||||
return func(io *ioC0Page, isWrite bool, value uint8) uint8 {
|
||||
fmt.Printf("Softswitch 0x%02x %v %v\n", ioFlag, isWrite, dstValue)
|
||||
if !isWrite {
|
||||
return 0 // Ignore reads
|
||||
}
|
||||
currentValue := io.softSwitchesData[ioFlag]
|
||||
if currentValue == dstValue {
|
||||
return 0 // Already switched, ignore
|
||||
}
|
||||
action(io)
|
||||
io.softSwitchesData[ioFlag] = value
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func softSwitchIntCxRomOn(io *ioC0Page) {
|
||||
for i := uint8(1); i < 16; i++ {
|
||||
io.mmu.activeMemory.SetPage(uint8(0xc0+i), &io.mmu.physicalROMe[i])
|
||||
}
|
||||
}
|
||||
|
||||
func softSwitchIntCxRomOff(io *ioC0Page) {
|
||||
// TODO restore all the ROM from the slot for 0xc1 to 0xc7
|
||||
for i := 1; i <= 16; i++ {
|
||||
io.mmu.activeMemory.SetPage(uint8(0xc0+i), &io.mmu.unassignedExpansionROM[i])
|
||||
}
|
||||
}
|
||||
|
||||
func softSwitchSlotC3RomOn(io *ioC0Page) {
|
||||
if io.isSoftSwitchExtActive(ioFlagIntCxRom) {
|
||||
return // Ignore if allt the Apple2 shadow ROM is active
|
||||
}
|
||||
// TODO restore the slot 3 ROM
|
||||
io.mmu.activeMemory.SetPage(0xC3, &io.mmu.unassignedExpansionROM[3])
|
||||
}
|
||||
|
||||
func softSwitchSlotC3RomOff(io *ioC0Page) {
|
||||
if io.isSoftSwitchExtActive(ioFlagIntCxRom) {
|
||||
return // Ignore if allt the Apple2 shadow ROM is active
|
||||
}
|
||||
io.mmu.activeMemory.SetPage(0xC3, &io.mmu.physicalROMe[3])
|
||||
}
|
@ -4,7 +4,7 @@ import "fmt"
|
||||
|
||||
// State represents the state of the simulated device
|
||||
type State struct {
|
||||
Reg Registers
|
||||
Reg registers
|
||||
Mem Memory
|
||||
}
|
||||
|
||||
@ -41,25 +41,46 @@ func getWordInLine(line []uint8) uint16 {
|
||||
return uint16(line[1]) + 0x100*uint16(line[2])
|
||||
}
|
||||
|
||||
func resolve(s *State, line []uint8, opcode opcode) (value uint8, address uint16, setValue func(uint8)) {
|
||||
func resolveValue(s *State, line []uint8, opcode opcode) uint8 {
|
||||
getValue, _, _ := resolve(s, line, opcode)
|
||||
return getValue()
|
||||
}
|
||||
|
||||
func resolveGetSetValue(s *State, line []uint8, opcode opcode) (value uint8, setValue func(uint8)) {
|
||||
getValue, setValue, _ := resolve(s, line, opcode)
|
||||
value = getValue()
|
||||
return
|
||||
}
|
||||
|
||||
func resolveSetValue(s *State, line []uint8, opcode opcode) func(uint8) {
|
||||
_, setValue, _ := resolve(s, line, opcode)
|
||||
return setValue
|
||||
}
|
||||
|
||||
func resolveAddress(s *State, line []uint8, opcode opcode) uint16 {
|
||||
_, _, address := resolve(s, line, opcode)
|
||||
return address
|
||||
}
|
||||
|
||||
func resolve(s *State, line []uint8, opcode opcode) (getValue func() uint8, setValue func(uint8), address uint16) {
|
||||
hasAddress := true
|
||||
register := regNone
|
||||
|
||||
switch opcode.addressMode {
|
||||
case modeAccumulator:
|
||||
value = s.Reg.getA()
|
||||
getValue = func() uint8 { return s.Reg.getA() }
|
||||
hasAddress = false
|
||||
register = regA
|
||||
case modeImplicitX:
|
||||
value = s.Reg.getX()
|
||||
getValue = func() uint8 { return s.Reg.getX() }
|
||||
hasAddress = false
|
||||
register = regX
|
||||
case modeImplicitY:
|
||||
value = s.Reg.getY()
|
||||
getValue = func() uint8 { return s.Reg.getY() }
|
||||
hasAddress = false
|
||||
register = regY
|
||||
case modeImmediate:
|
||||
value = line[1]
|
||||
getValue = func() uint8 { return line[1] }
|
||||
hasAddress = false
|
||||
case modeZeroPage:
|
||||
address = uint16(line[1])
|
||||
@ -85,7 +106,7 @@ func resolve(s *State, line []uint8, opcode opcode) (value uint8, address uint16
|
||||
}
|
||||
|
||||
if hasAddress {
|
||||
value = s.Mem.Peek(address)
|
||||
getValue = func() uint8 { return s.Mem.Peek(address) }
|
||||
}
|
||||
|
||||
setValue = func(value uint8) {
|
||||
@ -122,7 +143,7 @@ func buildOpTransfer(regSrc int, regDst int) opFunc {
|
||||
|
||||
func buildOpIncDec(inc bool) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
value, _, setValue := resolve(s, line, opcode)
|
||||
value, setValue := resolveGetSetValue(s, line, opcode)
|
||||
if inc {
|
||||
value++
|
||||
} else {
|
||||
@ -135,7 +156,7 @@ func buildOpIncDec(inc bool) opFunc {
|
||||
|
||||
func buildOpShift(isLeft bool, isRotate bool) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
value, _, setValue := resolve(s, line, opcode)
|
||||
value, setValue := resolveGetSetValue(s, line, opcode)
|
||||
|
||||
oldCarry := s.Reg.getFlagBit(flagC)
|
||||
var carry bool
|
||||
@ -160,7 +181,7 @@ func buildOpShift(isLeft bool, isRotate bool) opFunc {
|
||||
|
||||
func buildOpLoad(regDst int) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
value, _, _ := resolve(s, line, opcode)
|
||||
value := resolveValue(s, line, opcode)
|
||||
s.Reg.setRegister(regDst, value)
|
||||
s.Reg.updateFlagZN(value)
|
||||
}
|
||||
@ -168,7 +189,7 @@ func buildOpLoad(regDst int) opFunc {
|
||||
|
||||
func buildOpStore(regSrc int) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
_, _, setValue := resolve(s, line, opcode)
|
||||
setValue := resolveSetValue(s, line, opcode)
|
||||
value := s.Reg.getRegister(regSrc)
|
||||
setValue(value)
|
||||
}
|
||||
@ -192,7 +213,7 @@ func buildOpBranch(flag uint8, value bool) opFunc {
|
||||
}
|
||||
|
||||
func opBIT(s *State, line []uint8, opcode opcode) {
|
||||
value, _, _ := resolve(s, line, opcode)
|
||||
value := resolveValue(s, line, opcode)
|
||||
acc := s.Reg.getA()
|
||||
// Future note: The immediate addressing mode (65C02 or 65816 only) does not affect V.
|
||||
s.Reg.updateFlag(flagZ, value&acc == 0)
|
||||
@ -202,7 +223,7 @@ func opBIT(s *State, line []uint8, opcode opcode) {
|
||||
|
||||
func buildOpCompare(reg int) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
value, _, _ := resolve(s, line, opcode)
|
||||
value := resolveValue(s, line, opcode)
|
||||
reference := s.Reg.getRegister(reg)
|
||||
s.Reg.updateFlagZN(reference - value)
|
||||
s.Reg.updateFlag(flagC, reference >= value)
|
||||
@ -215,7 +236,7 @@ func operationXor(a uint8, b uint8) uint8 { return a ^ b }
|
||||
|
||||
func buildOpLogic(operation func(uint8, uint8) uint8) opFunc {
|
||||
return func(s *State, line []uint8, opcode opcode) {
|
||||
value, _, _ := resolve(s, line, opcode)
|
||||
value := resolveValue(s, line, opcode)
|
||||
result := operation(value, s.Reg.getA())
|
||||
s.Reg.setA(result)
|
||||
s.Reg.updateFlagZN(result)
|
||||
@ -223,7 +244,7 @@ func buildOpLogic(operation func(uint8, uint8) uint8) opFunc {
|
||||
}
|
||||
|
||||
func opADC(s *State, line []uint8, opcode opcode) {
|
||||
value, _, _ := resolve(s, line, opcode)
|
||||
value := resolveValue(s, line, opcode)
|
||||
aValue := s.Reg.getA()
|
||||
carry := s.Reg.getFlagBit(flagC)
|
||||
|
||||
@ -251,7 +272,7 @@ func opADC(s *State, line []uint8, opcode opcode) {
|
||||
}
|
||||
|
||||
func opSBC(s *State, line []uint8, opcode opcode) {
|
||||
value, _, _ := resolve(s, line, opcode)
|
||||
value := resolveValue(s, line, opcode)
|
||||
aValue := s.Reg.getA()
|
||||
carry := s.Reg.getFlagBit(flagC)
|
||||
|
||||
@ -323,7 +344,7 @@ func opPHP(s *State, line []uint8, opcode opcode) {
|
||||
}
|
||||
|
||||
func opJMP(s *State, line []uint8, opcode opcode) {
|
||||
_, address, _ := resolve(s, line, opcode)
|
||||
address := resolveAddress(s, line, opcode)
|
||||
s.Reg.setPC(address)
|
||||
}
|
||||
|
||||
@ -331,7 +352,7 @@ func opNOP(s *State, line []uint8, opcode opcode) {}
|
||||
|
||||
func opJSR(s *State, line []uint8, opcode opcode) {
|
||||
pushWord(s, s.Reg.getPC()-1)
|
||||
_, address, _ := resolve(s, line, opcode)
|
||||
address := resolveAddress(s, line, opcode)
|
||||
s.Reg.setPC(address)
|
||||
}
|
||||
|
||||
@ -556,9 +577,6 @@ func ExecuteInstruction(s *State, log bool) {
|
||||
}
|
||||
opcode.action(s, line, opcode)
|
||||
if log {
|
||||
// Warning: this create double accesses and can interfere on memory mapped I/O
|
||||
//value, address, _ := resolve(s, line, opcode)
|
||||
//fmt.Printf("%v, [%04x:%02x], [%02x]\n", s.Reg, address, value, line)
|
||||
fmt.Printf("%v, [%02x]\n", s.Reg, line)
|
||||
}
|
||||
}
|
||||
|
@ -23,56 +23,56 @@ const (
|
||||
flagC uint8 = 1 << 0
|
||||
)
|
||||
|
||||
type Registers struct {
|
||||
type registers struct {
|
||||
data [8]uint8
|
||||
}
|
||||
|
||||
func (r *Registers) getRegister(i int) uint8 { return r.data[i] }
|
||||
func (r *registers) getRegister(i int) uint8 { return r.data[i] }
|
||||
|
||||
func (r *Registers) getA() uint8 { return r.data[regA] }
|
||||
func (r *Registers) getX() uint8 { return r.data[regX] }
|
||||
func (r *Registers) getY() uint8 { return r.data[regY] }
|
||||
func (r *Registers) getP() uint8 { return r.data[regP] }
|
||||
func (r *Registers) getSP() uint8 { return r.data[regSP] }
|
||||
func (r *registers) getA() uint8 { return r.data[regA] }
|
||||
func (r *registers) getX() uint8 { return r.data[regX] }
|
||||
func (r *registers) getY() uint8 { return r.data[regY] }
|
||||
func (r *registers) getP() uint8 { return r.data[regP] }
|
||||
func (r *registers) getSP() uint8 { return r.data[regSP] }
|
||||
|
||||
func (r *Registers) setRegister(i int, v uint8) {
|
||||
func (r *registers) setRegister(i int, v uint8) {
|
||||
r.data[i] = v
|
||||
}
|
||||
func (r *Registers) setA(v uint8) { r.setRegister(regA, v) }
|
||||
func (r *Registers) setX(v uint8) { r.setRegister(regX, v) }
|
||||
func (r *Registers) setY(v uint8) { r.setRegister(regY, v) }
|
||||
func (r *Registers) setP(v uint8) { r.setRegister(regP, v) }
|
||||
func (r *Registers) setSP(v uint8) { r.setRegister(regSP, v) }
|
||||
func (r *registers) setA(v uint8) { r.setRegister(regA, v) }
|
||||
func (r *registers) setX(v uint8) { r.setRegister(regX, v) }
|
||||
func (r *registers) setY(v uint8) { r.setRegister(regY, v) }
|
||||
func (r *registers) setP(v uint8) { r.setRegister(regP, v) }
|
||||
func (r *registers) setSP(v uint8) { r.setRegister(regSP, v) }
|
||||
|
||||
func (r *Registers) getPC() uint16 {
|
||||
func (r *registers) getPC() uint16 {
|
||||
return uint16(r.data[regPC])*256 + uint16(r.data[regPC+1])
|
||||
}
|
||||
|
||||
func (r *Registers) setPC(v uint16) {
|
||||
func (r *registers) setPC(v uint16) {
|
||||
r.data[regPC] = uint8(v >> 8)
|
||||
r.data[regPC+1] = uint8(v)
|
||||
}
|
||||
|
||||
func (r *Registers) getFlagBit(i uint8) uint8 {
|
||||
func (r *registers) getFlagBit(i uint8) uint8 {
|
||||
if r.getFlag(i) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *Registers) getFlag(i uint8) bool {
|
||||
func (r *registers) getFlag(i uint8) bool {
|
||||
return (r.data[regP] & i) != 0
|
||||
}
|
||||
|
||||
func (r *Registers) setFlag(i uint8) {
|
||||
func (r *registers) setFlag(i uint8) {
|
||||
r.data[regP] |= i
|
||||
}
|
||||
|
||||
func (r *Registers) clearFlag(i uint8) {
|
||||
func (r *registers) clearFlag(i uint8) {
|
||||
r.data[regP] &^= i
|
||||
}
|
||||
|
||||
func (r *Registers) updateFlag(i uint8, v bool) {
|
||||
func (r *registers) updateFlag(i uint8, v bool) {
|
||||
if v {
|
||||
r.setFlag(i)
|
||||
} else {
|
||||
@ -80,12 +80,12 @@ func (r *Registers) updateFlag(i uint8, v bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registers) updateFlagZN(t uint8) {
|
||||
func (r *registers) updateFlagZN(t uint8) {
|
||||
r.updateFlag(flagZ, t == 0)
|
||||
r.updateFlag(flagN, t >= (1<<7))
|
||||
}
|
||||
|
||||
func (r Registers) String() string {
|
||||
func (r registers) String() string {
|
||||
return fmt.Sprintf("A: %#02x, X: %#02x, Y: %#02x, SP: %#02x, PC: %#04x, P: %#02x, (NV-BDIZC): %08b",
|
||||
r.getA(), r.getX(), r.getY(), r.getSP(), r.getPC(), r.getP(), r.getP())
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package core6502
|
||||
import "testing"
|
||||
|
||||
func TestRegA(t *testing.T) {
|
||||
var r Registers
|
||||
var r registers
|
||||
var data uint8
|
||||
data = 200
|
||||
r.setA(data)
|
||||
@ -12,7 +12,7 @@ func TestRegA(t *testing.T) {
|
||||
}
|
||||
}
|
||||
func TestRegPC(t *testing.T) {
|
||||
var r Registers
|
||||
var r registers
|
||||
var data uint16
|
||||
data = 0xc600
|
||||
r.setPC(data)
|
||||
@ -22,7 +22,7 @@ func TestRegPC(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFlags(t *testing.T) {
|
||||
var r Registers
|
||||
var r registers
|
||||
r.setP(0x23)
|
||||
if r.getP() != 0x23 {
|
||||
t.Error("Error storing and loading P")
|
||||
@ -51,7 +51,7 @@ func TestFlags(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateFlagZN(t *testing.T) {
|
||||
var r Registers
|
||||
var r registers
|
||||
r.updateFlagZN(0)
|
||||
if r.getP() != flagZ {
|
||||
t.Error("Error update flags ZN with 0")
|
||||
|
8
main.go
8
main.go
@ -3,10 +3,10 @@ package main
|
||||
import "go6502/apple2"
|
||||
|
||||
func main() {
|
||||
romFile := "../roms/apple.rom"
|
||||
//romFile := "../roms/APPLE2.ROM"
|
||||
|
||||
log := true
|
||||
//romFile := "apple2/romdumps/Apple2.rom"
|
||||
romFile := "apple2/romdumps/Apple2_Plus.rom"
|
||||
//romFile := "apple2/romdumps/Apple2e.rom"
|
||||
|
||||
log := false
|
||||
apple2.Run(romFile, log)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user