mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 03:58:02 -04:00
84ad23c03e
this means that a mapper only needs to implement the Patch() if it makes sense mappers that don't need it have had the Patch function removed. implemented function for SCABS and UA corrected error messages for atari mappers - some messages weren't referencing the correct atari mapper and simply stated "atari"
930 lines
30 KiB
Go
930 lines
30 KiB
Go
// This file is part of Gopher2600.
|
|
//
|
|
// Gopher2600 is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Gopher2600 is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package dpcplus
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/jetsetilly/gopher2600/coprocessor"
|
|
"github.com/jetsetilly/gopher2600/environment"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
|
"github.com/jetsetilly/gopher2600/logger"
|
|
)
|
|
|
|
// dpcPlus implements the mapper.CartMapper interface.
|
|
//
|
|
// https://atariage.com/forums/topic/163495-harmony-dpc-programming
|
|
//
|
|
// https://atariage.com/forums/blogs/entry/11811-dpcarm-part-6-dpc-cartridge-layout/
|
|
type dpcPlus struct {
|
|
env *environment.Environment
|
|
mappingID string
|
|
|
|
// additional CPU - used by some ROMs
|
|
arm *arm.ARM
|
|
|
|
// the hook that handles cartridge yields
|
|
yieldHook coprocessor.CartYieldHook
|
|
|
|
// there is only one version of DPC+ currently but this method of
|
|
// specifying addresses mirrors how we do it in the CDF type
|
|
version version
|
|
|
|
// banks and the currently selected bank
|
|
bankSize int
|
|
banks [][]byte
|
|
|
|
// rewindable state
|
|
state *State
|
|
}
|
|
|
|
// the sizes of these areas in a DPC+ cartridge are fixed. the custom arm code
|
|
// and the 6507 program fit around these sizes.
|
|
const (
|
|
driverSize = 3072 // 3k
|
|
dataSize = 4096 // 4k
|
|
freqSize = 1024 // 1k
|
|
)
|
|
|
|
// NewDPCplus is the preferred method of initialisation for the dpcPlus type.
|
|
func NewDPCplus(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
|
cart := &dpcPlus{
|
|
env: env,
|
|
mappingID: "DPC+",
|
|
bankSize: 4096,
|
|
state: newDPCPlusState(),
|
|
yieldHook: coprocessor.StubCartYieldHook{},
|
|
}
|
|
|
|
var err error
|
|
|
|
// create addresses
|
|
cart.version, err = newVersion(env.Prefs.ARM.Model.Get().(string), data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("DPC+: %s", err.Error())
|
|
}
|
|
|
|
// amount of data used for cartridges
|
|
bankLen := len(data) - dataSize - driverSize - freqSize
|
|
|
|
// size check
|
|
if bankLen <= 0 || bankLen%cart.bankSize != 0 {
|
|
return nil, fmt.Errorf("DPC+: wrong number of bytes in cartridge data")
|
|
}
|
|
|
|
// allocate enough banks
|
|
cart.banks = make([][]uint8, bankLen/cart.bankSize)
|
|
|
|
// partition data into banks
|
|
for k := 0; k < cart.NumBanks(); k++ {
|
|
cart.banks[k] = make([]uint8, cart.bankSize)
|
|
offset := k * cart.bankSize
|
|
offset += driverSize
|
|
cart.banks[k] = data[offset : offset+cart.bankSize]
|
|
}
|
|
|
|
// initialise static memory
|
|
cart.state.static = cart.newDPCplusStatic(cart.version, data)
|
|
|
|
// initialise ARM processor
|
|
//
|
|
// if bank0 has any ARM code then it will start at offset 0x08. first eight
|
|
// bytes are the ARM header
|
|
cart.arm = arm.NewARM(cart.version.mmap, env.Prefs.ARM, cart.state.static, cart)
|
|
|
|
return cart, nil
|
|
}
|
|
|
|
// MappedBanks implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) MappedBanks() string {
|
|
return fmt.Sprintf("Bank: %d", cart.state.bank)
|
|
}
|
|
|
|
// ID implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) ID() string {
|
|
return cart.mappingID
|
|
}
|
|
|
|
// Snapshot implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) Snapshot() mapper.CartMapper {
|
|
n := *cart
|
|
n.state = cart.state.Snapshot()
|
|
return &n
|
|
}
|
|
|
|
// Plumb implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) Plumb(env *environment.Environment) {
|
|
cart.env = env
|
|
cart.arm.Plumb(nil, cart.state.static, cart)
|
|
}
|
|
|
|
// Plumb implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) PlumbFromDifferentEmulation(env *environment.Environment) {
|
|
cart.env = env
|
|
cart.arm = arm.NewARM(cart.version.mmap, cart.env.Prefs.ARM, cart.state.static, cart)
|
|
cart.yieldHook = &coprocessor.StubCartYieldHook{}
|
|
}
|
|
|
|
// Reset implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) Reset() {
|
|
cart.state.initialise(cart.env.Random, len(cart.banks)-1)
|
|
}
|
|
|
|
// Access implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) Access(addr uint16, peek bool) (uint8, uint8, error) {
|
|
if b, ok := cart.state.callfn.Check(addr); ok {
|
|
return b, mapper.CartDrivenPins, nil
|
|
}
|
|
|
|
if !peek {
|
|
if cart.bankswitch(addr) {
|
|
return 0, mapper.CartDrivenPins, nil
|
|
}
|
|
}
|
|
|
|
var data uint8
|
|
|
|
// if address is above register space then we only need to check for bank
|
|
// switching before returning data at the quoted address
|
|
if addr > 0x007f {
|
|
data = cart.banks[cart.state.bank][addr]
|
|
|
|
// if FastFetch mode is on and the preceding data value was 0xa9 (the
|
|
// opcode for LDA <immediate>) then the data we've just read this cycle
|
|
// should be interpreted as an address to read from. we can do this by
|
|
// recursing into the Read() function (there is no worry about deep
|
|
// recursions because we reset the lda flag before recursing and the
|
|
// lda flag being set is a prerequisite for the recursion to take
|
|
// place)
|
|
if cart.state.registers.FastFetch && cart.state.lda && data < 0x28 {
|
|
cart.state.lda = false
|
|
return cart.Access(uint16(data), peek)
|
|
}
|
|
|
|
cart.state.lda = cart.state.registers.FastFetch && data == 0xa9
|
|
return data, mapper.CartDrivenPins, nil
|
|
}
|
|
|
|
switch addr {
|
|
// random number generator
|
|
case 0x00:
|
|
cart.state.registers.RNG.next()
|
|
data = uint8(cart.state.registers.RNG.Value)
|
|
case 0x01:
|
|
cart.state.registers.RNG.prev()
|
|
data = uint8(cart.state.registers.RNG.Value)
|
|
case 0x02:
|
|
data = uint8(cart.state.registers.RNG.Value >> 8)
|
|
case 0x03:
|
|
data = uint8(cart.state.registers.RNG.Value >> 16)
|
|
case 0x04:
|
|
data = uint8(cart.state.registers.RNG.Value >> 24)
|
|
|
|
// music fetcher
|
|
case 0x05:
|
|
data = cart.state.static.dataRAM[(cart.state.registers.MusicFetcher[0].Waveform<<5)+(cart.state.registers.MusicFetcher[0].Count>>27)]
|
|
data += cart.state.static.dataRAM[(cart.state.registers.MusicFetcher[1].Waveform<<5)+(cart.state.registers.MusicFetcher[1].Count>>27)]
|
|
data += cart.state.static.dataRAM[(cart.state.registers.MusicFetcher[2].Waveform<<5)+(cart.state.registers.MusicFetcher[2].Count>>27)]
|
|
|
|
// reserved
|
|
case 0x06:
|
|
case 0x07:
|
|
|
|
// data fetcher
|
|
case 0x08:
|
|
fallthrough
|
|
case 0x09:
|
|
fallthrough
|
|
case 0x0a:
|
|
fallthrough
|
|
case 0x0b:
|
|
fallthrough
|
|
case 0x0c:
|
|
fallthrough
|
|
case 0x0d:
|
|
fallthrough
|
|
case 0x0e:
|
|
fallthrough
|
|
case 0x0f:
|
|
f := addr & 0x0007
|
|
dataAddr := uint16(cart.state.registers.Fetcher[f].Hi)<<8 | uint16(cart.state.registers.Fetcher[f].Low)
|
|
dataAddr &= 0x0fff
|
|
data = cart.state.static.dataRAM[dataAddr]
|
|
if !peek {
|
|
cart.state.registers.Fetcher[f].inc()
|
|
}
|
|
|
|
// data fetcher (windowed)
|
|
case 0x10:
|
|
fallthrough
|
|
case 0x11:
|
|
fallthrough
|
|
case 0x12:
|
|
fallthrough
|
|
case 0x13:
|
|
fallthrough
|
|
case 0x14:
|
|
fallthrough
|
|
case 0x15:
|
|
fallthrough
|
|
case 0x16:
|
|
fallthrough
|
|
case 0x17:
|
|
f := addr & 0x0007
|
|
dataAddr := uint16(cart.state.registers.Fetcher[f].Hi)<<8 | uint16(cart.state.registers.Fetcher[f].Low)
|
|
dataAddr &= 0x0fff
|
|
if cart.state.registers.Fetcher[f].isWindow() {
|
|
data = cart.state.static.dataRAM[dataAddr]
|
|
}
|
|
if !peek {
|
|
cart.state.registers.Fetcher[f].inc()
|
|
}
|
|
|
|
// fractional data fetcher
|
|
case 0x18:
|
|
fallthrough
|
|
case 0x19:
|
|
fallthrough
|
|
case 0x1a:
|
|
fallthrough
|
|
case 0x1b:
|
|
fallthrough
|
|
case 0x1c:
|
|
fallthrough
|
|
case 0x1d:
|
|
fallthrough
|
|
case 0x1e:
|
|
fallthrough
|
|
case 0x1f:
|
|
f := addr & 0x0007
|
|
dataAddr := uint16(cart.state.registers.FracFetcher[f].Hi)<<8 | uint16(cart.state.registers.FracFetcher[f].Low)
|
|
dataAddr &= 0x0fff
|
|
data = cart.state.static.dataRAM[dataAddr]
|
|
if !peek {
|
|
cart.state.registers.FracFetcher[f].inc()
|
|
}
|
|
|
|
// data fetcher window flag
|
|
case 0x20:
|
|
fallthrough
|
|
case 0x21:
|
|
fallthrough
|
|
case 0x22:
|
|
fallthrough
|
|
case 0x23:
|
|
f := addr & 0x0007
|
|
if cart.state.registers.Fetcher[f].isWindow() {
|
|
data = 0xff
|
|
}
|
|
|
|
// reserved
|
|
case 0x24:
|
|
case 0x25:
|
|
case 0x26:
|
|
case 0x27:
|
|
}
|
|
|
|
return data, mapper.CartDrivenPins, nil
|
|
}
|
|
|
|
// AccessVolatile implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) AccessVolatile(addr uint16, data uint8, poke bool) error {
|
|
// bank switches can not take place if coprocessor is active
|
|
if cart.state.callfn.IsActive() {
|
|
return nil
|
|
}
|
|
|
|
if !poke {
|
|
if cart.bankswitch(addr) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
switch addr {
|
|
// fractional data fetcher, low
|
|
case 0x28:
|
|
fallthrough
|
|
case 0x29:
|
|
fallthrough
|
|
case 0x2a:
|
|
fallthrough
|
|
case 0x2b:
|
|
fallthrough
|
|
case 0x2c:
|
|
fallthrough
|
|
case 0x2d:
|
|
fallthrough
|
|
case 0x2e:
|
|
fallthrough
|
|
case 0x2f:
|
|
f := addr & 0x0007
|
|
cart.state.registers.FracFetcher[f].Low = data
|
|
cart.state.registers.FracFetcher[f].Count = 0
|
|
|
|
// fractional data fetcher, high
|
|
case 0x30:
|
|
fallthrough
|
|
case 0x31:
|
|
fallthrough
|
|
case 0x32:
|
|
fallthrough
|
|
case 0x33:
|
|
fallthrough
|
|
case 0x34:
|
|
fallthrough
|
|
case 0x35:
|
|
fallthrough
|
|
case 0x36:
|
|
fallthrough
|
|
case 0x37:
|
|
f := addr & 0x0007
|
|
cart.state.registers.FracFetcher[f].Hi = data
|
|
cart.state.registers.FracFetcher[f].Count = 0
|
|
|
|
// fractional data fetcher, incrememnt
|
|
case 0x38:
|
|
fallthrough
|
|
case 0x39:
|
|
fallthrough
|
|
case 0x3a:
|
|
fallthrough
|
|
case 0x3b:
|
|
fallthrough
|
|
case 0x3c:
|
|
fallthrough
|
|
case 0x3d:
|
|
fallthrough
|
|
case 0x3e:
|
|
fallthrough
|
|
case 0x3f:
|
|
f := addr & 0x0007
|
|
cart.state.registers.FracFetcher[f].Increment = data
|
|
cart.state.registers.FracFetcher[f].Count = 0
|
|
|
|
// data fetcher, window top
|
|
case 0x40:
|
|
fallthrough
|
|
case 0x41:
|
|
fallthrough
|
|
case 0x42:
|
|
fallthrough
|
|
case 0x43:
|
|
fallthrough
|
|
case 0x44:
|
|
fallthrough
|
|
case 0x45:
|
|
fallthrough
|
|
case 0x46:
|
|
fallthrough
|
|
case 0x47:
|
|
f := addr & 0x0007
|
|
cart.state.registers.Fetcher[f].Top = data
|
|
|
|
// data fetcher, window bottom
|
|
case 0x48:
|
|
fallthrough
|
|
case 0x49:
|
|
fallthrough
|
|
case 0x4a:
|
|
fallthrough
|
|
case 0x4b:
|
|
fallthrough
|
|
case 0x4c:
|
|
fallthrough
|
|
case 0x4d:
|
|
fallthrough
|
|
case 0x4e:
|
|
fallthrough
|
|
case 0x4f:
|
|
f := addr & 0x0007
|
|
cart.state.registers.Fetcher[f].Bottom = data
|
|
|
|
// data fetcher, low pointer
|
|
case 0x50:
|
|
fallthrough
|
|
case 0x51:
|
|
fallthrough
|
|
case 0x52:
|
|
fallthrough
|
|
case 0x53:
|
|
fallthrough
|
|
case 0x54:
|
|
fallthrough
|
|
case 0x55:
|
|
fallthrough
|
|
case 0x56:
|
|
fallthrough
|
|
case 0x57:
|
|
f := addr & 0x0007
|
|
cart.state.registers.Fetcher[f].Low = data
|
|
|
|
// fast fetch mode
|
|
case 0x58:
|
|
// ----------------------------------------
|
|
// Fast Fetch Mode
|
|
// ----------------------------------------
|
|
// Fast Fetch Mode enables the fastest way to read DPC+ registers. Normal
|
|
// reads use LDA Absolute addressing (LDA DF0DATA) which takes 4 cycles to
|
|
// process. Fast Fetch Mode intercepts LDA Immediate addressing (LDA #<DF0DATA)
|
|
// which takes only 2 cycles! Only immediate values < $28 are intercepted
|
|
cart.state.registers.FastFetch = data == 0
|
|
|
|
// function support - parameter
|
|
case 0x59:
|
|
cart.state.parameters = append(cart.state.parameters, data)
|
|
|
|
// function support - call function
|
|
case 0x5a:
|
|
switch data {
|
|
case 0:
|
|
cart.state.parameters = cart.state.parameters[:0]
|
|
case 1:
|
|
// copy rom to fetcher
|
|
if len(cart.state.parameters) != 4 {
|
|
logger.Logf("DPC+", "wrong number of parameters for function call [%02x]", data)
|
|
break // switch data
|
|
}
|
|
|
|
addr := (uint16(cart.state.parameters[1]) << 8) | uint16(cart.state.parameters[0])
|
|
for i := uint8(0); i < cart.state.parameters[3]; i++ {
|
|
f := cart.state.registers.Fetcher[cart.state.parameters[2]&0x07]
|
|
o := uint16(f.Low) | (uint16(f.Hi) << 8) + uint16(i)
|
|
|
|
// copying from data ROM to data RAM
|
|
idx := uint16(i) + addr - uint16(len(cart.state.static.customROM))
|
|
cart.state.static.dataRAM[o] = cart.state.static.dataROM[idx]
|
|
}
|
|
cart.state.parameters = cart.state.parameters[:0]
|
|
case 2:
|
|
// copy value to fetcher
|
|
if len(cart.state.parameters) != 4 {
|
|
logger.Logf("DPC+", "wrong number of parameters for function call [%02x]", data)
|
|
break // switch data
|
|
}
|
|
|
|
for i := uint8(0); i < cart.state.parameters[3]; i++ {
|
|
f := cart.state.registers.Fetcher[cart.state.parameters[2]&0x07]
|
|
o := uint16(f.Low+i) | (uint16(f.Hi+i) << 8)
|
|
cart.state.static.dataRAM[o] = cart.state.parameters[0]
|
|
}
|
|
|
|
cart.state.parameters = cart.state.parameters[:0]
|
|
|
|
case 254:
|
|
fallthrough
|
|
case 255:
|
|
runArm := func() {
|
|
cart.arm.StartProfiling()
|
|
defer cart.arm.ProcessProfiling()
|
|
cart.state.yield = cart.runArm()
|
|
}
|
|
|
|
// keep calling runArm() for as long as program has not ended
|
|
runArm()
|
|
for cart.state.yield.Type != coprocessor.YieldProgramEnded {
|
|
if cart.yieldHook.CartYield(cart.state.yield.Type) == coprocessor.YieldHookEnd {
|
|
break
|
|
}
|
|
runArm()
|
|
}
|
|
}
|
|
|
|
// reserved
|
|
case 0x5b:
|
|
case 0x5c:
|
|
|
|
// waveforms
|
|
case 0x5d:
|
|
cart.state.registers.MusicFetcher[0].Waveform = uint32(data & 0x7f)
|
|
case 0x5e:
|
|
cart.state.registers.MusicFetcher[1].Waveform = uint32(data & 0x7f)
|
|
case 0x5f:
|
|
// ----------------------------------------
|
|
// Waveforms
|
|
// ----------------------------------------
|
|
// Waveforms are 32 byte tables that define a waveform. Waveforms must be 32
|
|
// byte aligned, and can only be stored in the 4K Display Data Bank. You MUST
|
|
// define an "OFF" waveform, comprised of all zeros. The sum of all waveforms
|
|
// being played should be <= 15, so typically you'll use a maximum of 5 for any
|
|
// given value.
|
|
//
|
|
// Valid values are 0-127 and point to the 4K Display Data bank. The formula
|
|
// (* & $1fff)/32 as shown below will calculate the value for you
|
|
cart.state.registers.MusicFetcher[2].Waveform = uint32(data & 0x7f)
|
|
|
|
// data fetcher, push stack
|
|
case 0x60:
|
|
fallthrough
|
|
case 0x61:
|
|
fallthrough
|
|
case 0x62:
|
|
fallthrough
|
|
case 0x63:
|
|
fallthrough
|
|
case 0x64:
|
|
fallthrough
|
|
case 0x65:
|
|
fallthrough
|
|
case 0x66:
|
|
fallthrough
|
|
case 0x67:
|
|
// ----------------------------------------
|
|
// Data Fetcher Push (stack)
|
|
// ----------------------------------------
|
|
// The Data Fetchers can also be used to update the contents of the 4K
|
|
// Display Data bank. Point the Data Fetcher to the data to change,
|
|
// then Push to it. The Data Fetcher's pointer will be decremented BEFORE
|
|
// the data is written.
|
|
f := addr & 0x0007
|
|
cart.state.registers.Fetcher[f].dec()
|
|
dataAddr := uint16(cart.state.registers.Fetcher[f].Hi)<<8 | uint16(cart.state.registers.Fetcher[f].Low)
|
|
dataAddr &= 0x0fff
|
|
cart.state.static.dataRAM[dataAddr] = data
|
|
|
|
// data fetcher, high pointer
|
|
case 0x68:
|
|
fallthrough
|
|
case 0x69:
|
|
fallthrough
|
|
case 0x6a:
|
|
fallthrough
|
|
case 0x6b:
|
|
fallthrough
|
|
case 0x6c:
|
|
fallthrough
|
|
case 0x6d:
|
|
fallthrough
|
|
case 0x6e:
|
|
fallthrough
|
|
case 0x6f:
|
|
f := addr & 0x0007
|
|
cart.state.registers.Fetcher[f].Hi = data
|
|
|
|
// random number initialisation
|
|
case 0x70:
|
|
cart.state.registers.RNG.Value = 0x2b435044
|
|
case 0x71:
|
|
cart.state.registers.RNG.Value &= 0xffffff00
|
|
cart.state.registers.RNG.Value |= uint32(data)
|
|
case 0x72:
|
|
cart.state.registers.RNG.Value &= 0xffff00ff
|
|
cart.state.registers.RNG.Value |= uint32(data) << 8
|
|
case 0x73:
|
|
cart.state.registers.RNG.Value &= 0xff00ffff
|
|
cart.state.registers.RNG.Value |= uint32(data) << 16
|
|
case 0x74:
|
|
cart.state.registers.RNG.Value &= 0x00ffffff
|
|
cart.state.registers.RNG.Value |= uint32(data) << 24
|
|
|
|
// musical notes
|
|
case 0x75:
|
|
cart.state.registers.MusicFetcher[0].Freq = uint32(cart.state.static.freqRAM[data<<2])
|
|
cart.state.registers.MusicFetcher[0].Freq += uint32(cart.state.static.freqRAM[(data<<2)+1]) << 8
|
|
cart.state.registers.MusicFetcher[0].Freq += uint32(cart.state.static.freqRAM[(data<<2)+2]) << 16
|
|
cart.state.registers.MusicFetcher[0].Freq += uint32(cart.state.static.freqRAM[(data<<2)+3]) << 24
|
|
case 0x76:
|
|
cart.state.registers.MusicFetcher[1].Freq = uint32(cart.state.static.freqRAM[data<<2])
|
|
cart.state.registers.MusicFetcher[1].Freq += uint32(cart.state.static.freqRAM[(data<<2)+1]) << 8
|
|
cart.state.registers.MusicFetcher[1].Freq += uint32(cart.state.static.freqRAM[(data<<2)+2]) << 16
|
|
cart.state.registers.MusicFetcher[1].Freq += uint32(cart.state.static.freqRAM[(data<<2)+3]) << 24
|
|
case 0x77:
|
|
cart.state.registers.MusicFetcher[2].Freq = uint32(cart.state.static.freqRAM[data<<2])
|
|
cart.state.registers.MusicFetcher[2].Freq += uint32(cart.state.static.freqRAM[(data<<2)+1]) << 8
|
|
cart.state.registers.MusicFetcher[2].Freq += uint32(cart.state.static.freqRAM[(data<<2)+2]) << 16
|
|
cart.state.registers.MusicFetcher[2].Freq += uint32(cart.state.static.freqRAM[(data<<2)+3]) << 24
|
|
|
|
// data fetcher, queue
|
|
case 0x78:
|
|
fallthrough
|
|
case 0x79:
|
|
fallthrough
|
|
case 0x7a:
|
|
fallthrough
|
|
case 0x7b:
|
|
fallthrough
|
|
case 0x7c:
|
|
fallthrough
|
|
case 0x7d:
|
|
fallthrough
|
|
case 0x7e:
|
|
fallthrough
|
|
case 0x7f:
|
|
// ----------------------------------------
|
|
// Data Fetcher Write (queue)
|
|
// ----------------------------------------
|
|
// The Data Fetchers can also be used to update the contents of the 4K
|
|
// Display Data bank. Point the Data Fetcher to the data to change,
|
|
// then Write to it The Data Fetcher's pointer will be incremented AFTER
|
|
// the data is written.
|
|
f := addr & 0x0007
|
|
dataAddr := uint16(cart.state.registers.Fetcher[f].Hi)<<8 | uint16(cart.state.registers.Fetcher[f].Low)
|
|
dataAddr &= 0x0fff
|
|
cart.state.static.dataRAM[dataAddr] = data
|
|
cart.state.registers.Fetcher[f].inc()
|
|
|
|
default:
|
|
if poke {
|
|
cart.banks[cart.state.bank][addr] = data
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// bankswitch on hotspot access.
|
|
func (cart *dpcPlus) bankswitch(addr uint16) bool {
|
|
if addr >= 0x0ff6 && addr <= 0x0ffb {
|
|
if addr == 0x0ff6 {
|
|
cart.state.bank = 0
|
|
} else if addr == 0x0ff7 {
|
|
cart.state.bank = 1
|
|
} else if addr == 0x0ff8 {
|
|
cart.state.bank = 2
|
|
} else if addr == 0x0ff9 {
|
|
cart.state.bank = 3
|
|
} else if addr == 0x0ffa {
|
|
cart.state.bank = 4
|
|
} else if addr == 0x0ffb {
|
|
cart.state.bank = 5
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// NumBanks implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) NumBanks() int {
|
|
return len(cart.banks)
|
|
}
|
|
|
|
// GetBank implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) GetBank(addr uint16) mapper.BankInfo {
|
|
return mapper.BankInfo{
|
|
Number: cart.state.bank,
|
|
IsRAM: false,
|
|
ExecutingCoprocessor: cart.state.callfn.IsActive(),
|
|
CoprocessorResumeAddr: cart.state.callfn.ResumeAddr,
|
|
}
|
|
}
|
|
|
|
// AccessPassive implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) AccessPassive(addr uint16, data uint8) error {
|
|
return nil
|
|
}
|
|
|
|
// Step implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) Step(clock float32) {
|
|
// sample rate of 20KHz.
|
|
//
|
|
// if Step() is called at a rate of 1.19Mhz. so:
|
|
//
|
|
// 1.19Mhz / 20KHz
|
|
// = 59
|
|
//
|
|
// ie. we clock the music data fetchers once every 59 calls to Step() when
|
|
// the VCS clock is running at 1.19Mhz
|
|
//
|
|
// the 20Khz is the same as the DPC format (see mapper_dpc for commentary).
|
|
divisor := int(clock * 50)
|
|
|
|
cart.state.beats++
|
|
if cart.state.beats%divisor == 0 {
|
|
cart.state.beats = 0
|
|
cart.state.registers.MusicFetcher[0].Count += cart.state.registers.MusicFetcher[0].Freq
|
|
cart.state.registers.MusicFetcher[1].Count += cart.state.registers.MusicFetcher[1].Freq
|
|
cart.state.registers.MusicFetcher[2].Count += cart.state.registers.MusicFetcher[2].Freq
|
|
}
|
|
|
|
// Step ARM state if the ARM program is NOT running
|
|
if cart.state.callfn.IsActive() {
|
|
if cart.arm.ImmediateMode() {
|
|
cart.arm.Step(clock)
|
|
} else {
|
|
timerClock := cart.state.callfn.Step(clock, cart.arm.Clk)
|
|
if timerClock > 0 {
|
|
cart.arm.Step(timerClock)
|
|
}
|
|
}
|
|
|
|
if !cart.state.callfn.IsActive() {
|
|
cart.arm.ProcessProfiling()
|
|
}
|
|
} else {
|
|
cart.arm.Step(clock)
|
|
}
|
|
}
|
|
|
|
// CopyBanks implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) CopyBanks() []mapper.BankContent {
|
|
c := make([]mapper.BankContent, len(cart.banks))
|
|
for b := 0; b < len(cart.banks); b++ {
|
|
c[b] = mapper.BankContent{Number: b,
|
|
Data: cart.banks[b],
|
|
Origins: []uint16{memorymap.OriginCart},
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
// ReadHotspots implements the mapper.CartHotspotsBus interface.
|
|
func (cart *dpcPlus) ReadHotspots() map[uint16]mapper.CartHotspotInfo {
|
|
return map[uint16]mapper.CartHotspotInfo{
|
|
0x1ff6: {Symbol: "BANK0", Action: mapper.HotspotBankSwitch},
|
|
0x1ff7: {Symbol: "BANK1", Action: mapper.HotspotBankSwitch},
|
|
0x1ff8: {Symbol: "BANK2", Action: mapper.HotspotBankSwitch},
|
|
0x1ff9: {Symbol: "BANK3", Action: mapper.HotspotBankSwitch},
|
|
0x1ffa: {Symbol: "BANK4", Action: mapper.HotspotBankSwitch},
|
|
0x1ffb: {Symbol: "BANK5", Action: mapper.HotspotBankSwitch},
|
|
0x1000: {Symbol: "RNG/next", Action: mapper.HotspotRegister},
|
|
0x1001: {Symbol: "RNG/0", Action: mapper.HotspotRegister},
|
|
0x1002: {Symbol: "RNG/1", Action: mapper.HotspotRegister},
|
|
0x1003: {Symbol: "RNG/2", Action: mapper.HotspotRegister},
|
|
0x1004: {Symbol: "RNG/3", Action: mapper.HotspotRegister},
|
|
0x1005: {Symbol: "MUSIC", Action: mapper.HotspotRegister},
|
|
0x1006: {Symbol: "RESERVED", Action: mapper.HotspotReserved},
|
|
0x1007: {Symbol: "RESERVED", Action: mapper.HotspotReserved},
|
|
0x1008: {Symbol: "DF0", Action: mapper.HotspotRegister},
|
|
0x1009: {Symbol: "DF1", Action: mapper.HotspotRegister},
|
|
0x100a: {Symbol: "DF2", Action: mapper.HotspotRegister},
|
|
0x100b: {Symbol: "DF3", Action: mapper.HotspotRegister},
|
|
0x100c: {Symbol: "DF4", Action: mapper.HotspotRegister},
|
|
0x100d: {Symbol: "DF5", Action: mapper.HotspotRegister},
|
|
0x100e: {Symbol: "DF6", Action: mapper.HotspotRegister},
|
|
0x100f: {Symbol: "DF7", Action: mapper.HotspotRegister},
|
|
0x1010: {Symbol: "DF0/win", Action: mapper.HotspotRegister},
|
|
0x1011: {Symbol: "DF1/win", Action: mapper.HotspotRegister},
|
|
0x1012: {Symbol: "DF2/win", Action: mapper.HotspotRegister},
|
|
0x1013: {Symbol: "DF3/win", Action: mapper.HotspotRegister},
|
|
0x1014: {Symbol: "DF4/win", Action: mapper.HotspotRegister},
|
|
0x1015: {Symbol: "DF5/win", Action: mapper.HotspotRegister},
|
|
0x1016: {Symbol: "DF6/win", Action: mapper.HotspotRegister},
|
|
0x1017: {Symbol: "DF7/win", Action: mapper.HotspotRegister},
|
|
0x1018: {Symbol: "DF0/frac", Action: mapper.HotspotRegister},
|
|
0x1019: {Symbol: "DF1/frac", Action: mapper.HotspotRegister},
|
|
0x101a: {Symbol: "DF2/frac", Action: mapper.HotspotRegister},
|
|
0x101b: {Symbol: "DF3/frac", Action: mapper.HotspotRegister},
|
|
0x101c: {Symbol: "DF4/frac", Action: mapper.HotspotRegister},
|
|
0x101d: {Symbol: "DF5/frac", Action: mapper.HotspotRegister},
|
|
0x101e: {Symbol: "DF6/frac", Action: mapper.HotspotRegister},
|
|
0x101f: {Symbol: "DF7/frac", Action: mapper.HotspotRegister},
|
|
0x1020: {Symbol: "ISWIN0", Action: mapper.HotspotRegister},
|
|
0x1021: {Symbol: "ISWIN1", Action: mapper.HotspotRegister},
|
|
0x1022: {Symbol: "ISWIN2", Action: mapper.HotspotRegister},
|
|
0x1023: {Symbol: "ISWIN3", Action: mapper.HotspotRegister},
|
|
0x1024: {Symbol: "RESERVED", Action: mapper.HotspotReserved},
|
|
0x1025: {Symbol: "RESERVED", Action: mapper.HotspotReserved},
|
|
0x1026: {Symbol: "RESERVED", Action: mapper.HotspotReserved},
|
|
0x1027: {Symbol: "RESERVED", Action: mapper.HotspotReserved},
|
|
}
|
|
}
|
|
|
|
// WriteHotspots implements the mapper.CartHotspotsBus interface.
|
|
func (cart *dpcPlus) WriteHotspots() map[uint16]mapper.CartHotspotInfo {
|
|
return map[uint16]mapper.CartHotspotInfo{
|
|
0x1ff6: {Symbol: "BANK0", Action: mapper.HotspotBankSwitch},
|
|
0x1ff7: {Symbol: "BANK1", Action: mapper.HotspotBankSwitch},
|
|
0x1ff8: {Symbol: "BANK2", Action: mapper.HotspotBankSwitch},
|
|
0x1ff9: {Symbol: "BANK3", Action: mapper.HotspotBankSwitch},
|
|
0x1ffa: {Symbol: "BANK4", Action: mapper.HotspotBankSwitch},
|
|
0x1ffb: {Symbol: "BANK5", Action: mapper.HotspotBankSwitch},
|
|
0x1028: {Symbol: "FDF0/low", Action: mapper.HotspotRegister},
|
|
0x1029: {Symbol: "FDF1/low", Action: mapper.HotspotRegister},
|
|
0x102a: {Symbol: "FDF2/low", Action: mapper.HotspotRegister},
|
|
0x102b: {Symbol: "FDF3/low", Action: mapper.HotspotRegister},
|
|
0x102c: {Symbol: "FDF4/low", Action: mapper.HotspotRegister},
|
|
0x102d: {Symbol: "FDF5/low", Action: mapper.HotspotRegister},
|
|
0x102e: {Symbol: "FDF6/low", Action: mapper.HotspotRegister},
|
|
0x102f: {Symbol: "FDF7/low", Action: mapper.HotspotRegister},
|
|
0x1030: {Symbol: "FDF0/hi", Action: mapper.HotspotRegister},
|
|
0x1031: {Symbol: "FDF1/hi", Action: mapper.HotspotRegister},
|
|
0x1032: {Symbol: "FDF2/hi", Action: mapper.HotspotRegister},
|
|
0x1033: {Symbol: "FDF3/hi", Action: mapper.HotspotRegister},
|
|
0x1034: {Symbol: "FDF4/hi", Action: mapper.HotspotRegister},
|
|
0x1035: {Symbol: "FDF5/hi", Action: mapper.HotspotRegister},
|
|
0x1036: {Symbol: "FDF6/hi", Action: mapper.HotspotRegister},
|
|
0x1037: {Symbol: "FDF7/hi", Action: mapper.HotspotRegister},
|
|
0x1038: {Symbol: "FDF0/inc", Action: mapper.HotspotRegister},
|
|
0x1039: {Symbol: "FDF1/inc", Action: mapper.HotspotRegister},
|
|
0x103a: {Symbol: "FDF2/inc", Action: mapper.HotspotRegister},
|
|
0x103b: {Symbol: "FDF3/inc", Action: mapper.HotspotRegister},
|
|
0x103c: {Symbol: "FDF4/inc", Action: mapper.HotspotRegister},
|
|
0x103d: {Symbol: "FDF5/inc", Action: mapper.HotspotRegister},
|
|
0x103e: {Symbol: "FDF6/inc", Action: mapper.HotspotRegister},
|
|
0x103f: {Symbol: "FDF7/inc", Action: mapper.HotspotRegister},
|
|
0x1040: {Symbol: "DF0/top", Action: mapper.HotspotRegister},
|
|
0x1041: {Symbol: "DF1/top", Action: mapper.HotspotRegister},
|
|
0x1042: {Symbol: "DF2/top", Action: mapper.HotspotRegister},
|
|
0x1043: {Symbol: "DF3/top", Action: mapper.HotspotRegister},
|
|
0x1044: {Symbol: "DF4/top", Action: mapper.HotspotRegister},
|
|
0x1045: {Symbol: "DF5/top", Action: mapper.HotspotRegister},
|
|
0x1046: {Symbol: "DF6/top", Action: mapper.HotspotRegister},
|
|
0x1047: {Symbol: "DF7/top", Action: mapper.HotspotRegister},
|
|
0x1048: {Symbol: "DF0/bot", Action: mapper.HotspotRegister},
|
|
0x1049: {Symbol: "DF1/bot", Action: mapper.HotspotRegister},
|
|
0x104a: {Symbol: "DF2/bot", Action: mapper.HotspotRegister},
|
|
0x104b: {Symbol: "DF3/bot", Action: mapper.HotspotRegister},
|
|
0x104c: {Symbol: "DF4/bot", Action: mapper.HotspotRegister},
|
|
0x104d: {Symbol: "DF5/bot", Action: mapper.HotspotRegister},
|
|
0x104e: {Symbol: "DF6/bot", Action: mapper.HotspotRegister},
|
|
0x104f: {Symbol: "DF7/bot", Action: mapper.HotspotRegister},
|
|
0x1050: {Symbol: "DF0/low", Action: mapper.HotspotRegister},
|
|
0x1051: {Symbol: "DF1/low", Action: mapper.HotspotRegister},
|
|
0x1052: {Symbol: "DF2/low", Action: mapper.HotspotRegister},
|
|
0x1053: {Symbol: "DF3/low", Action: mapper.HotspotRegister},
|
|
0x1054: {Symbol: "DF4/low", Action: mapper.HotspotRegister},
|
|
0x1055: {Symbol: "DF5/low", Action: mapper.HotspotRegister},
|
|
0x1056: {Symbol: "DF6/low", Action: mapper.HotspotRegister},
|
|
0x1057: {Symbol: "DF7/low", Action: mapper.HotspotRegister},
|
|
0x1058: {Symbol: "FASTFETCH", Action: mapper.HotspotRegister},
|
|
0x1059: {Symbol: "PARAM", Action: mapper.HotspotRegister},
|
|
0x105a: {Symbol: "CALLFN", Action: mapper.HotspotFunction},
|
|
0x105b: {Symbol: "RESERVED", Action: mapper.HotspotReserved},
|
|
0x105c: {Symbol: "RESERVED", Action: mapper.HotspotReserved},
|
|
0x105d: {Symbol: "MF0", Action: mapper.HotspotRegister},
|
|
0x105e: {Symbol: "MF1", Action: mapper.HotspotRegister},
|
|
0x105f: {Symbol: "MF2", Action: mapper.HotspotRegister},
|
|
0x1060: {Symbol: "DF0/push", Action: mapper.HotspotRegister},
|
|
0x1061: {Symbol: "DF1/push", Action: mapper.HotspotRegister},
|
|
0x1062: {Symbol: "DF2/push", Action: mapper.HotspotRegister},
|
|
0x1063: {Symbol: "DF3/push", Action: mapper.HotspotRegister},
|
|
0x1064: {Symbol: "DF4/push", Action: mapper.HotspotRegister},
|
|
0x1065: {Symbol: "DF5/push", Action: mapper.HotspotRegister},
|
|
0x1066: {Symbol: "DF6/push", Action: mapper.HotspotRegister},
|
|
0x1067: {Symbol: "DF7/push", Action: mapper.HotspotRegister},
|
|
0x1068: {Symbol: "DF0/hi", Action: mapper.HotspotRegister},
|
|
0x1069: {Symbol: "DF1/hi", Action: mapper.HotspotRegister},
|
|
0x106a: {Symbol: "DF2/hi", Action: mapper.HotspotRegister},
|
|
0x106b: {Symbol: "DF3/hi", Action: mapper.HotspotRegister},
|
|
0x106c: {Symbol: "DF4/hi", Action: mapper.HotspotRegister},
|
|
0x106d: {Symbol: "DF5/hi", Action: mapper.HotspotRegister},
|
|
0x106e: {Symbol: "DF6/hi", Action: mapper.HotspotRegister},
|
|
0x106f: {Symbol: "DF7/hi", Action: mapper.HotspotRegister},
|
|
0x1070: {Symbol: "RNGINIT", Action: mapper.HotspotFunction},
|
|
0x1071: {Symbol: "RNG0", Action: mapper.HotspotRegister},
|
|
0x1072: {Symbol: "RNG1", Action: mapper.HotspotRegister},
|
|
0x1073: {Symbol: "RNG2", Action: mapper.HotspotRegister},
|
|
0x1074: {Symbol: "RNG3", Action: mapper.HotspotRegister},
|
|
0x1075: {Symbol: "MUSIC0", Action: mapper.HotspotRegister},
|
|
0x1076: {Symbol: "MUSIC1", Action: mapper.HotspotRegister},
|
|
0x1077: {Symbol: "MUSIC2", Action: mapper.HotspotRegister},
|
|
0x1078: {Symbol: "DF0/queue", Action: mapper.HotspotRegister}, // DF0Write
|
|
0x1079: {Symbol: "DF1/queue", Action: mapper.HotspotRegister},
|
|
0x107a: {Symbol: "DF2/queue", Action: mapper.HotspotRegister},
|
|
0x107b: {Symbol: "DF3/queue", Action: mapper.HotspotRegister},
|
|
0x107c: {Symbol: "DF4/queue", Action: mapper.HotspotRegister},
|
|
0x107d: {Symbol: "DF5/queue", Action: mapper.HotspotRegister},
|
|
0x107e: {Symbol: "DF6/queue", Action: mapper.HotspotRegister},
|
|
0x107f: {Symbol: "DF7/queue", Action: mapper.HotspotRegister},
|
|
}
|
|
}
|
|
|
|
// ARMinterrupt implements the arm7tmdi.CatridgeHook interface.
|
|
func (cart *dpcPlus) ARMinterrupt(addr uint32, val1 uint32, val2 uint32) (arm.ARMinterruptReturn, error) {
|
|
return arm.ARMinterruptReturn{}, nil
|
|
}
|
|
|
|
// CoProcExecutionState implements the coprocessor.CartCoProcBus interface.
|
|
func (cart *dpcPlus) CoProcExecutionState() coprocessor.CoProcExecutionState {
|
|
if cart.state.callfn.IsActive() {
|
|
return coprocessor.CoProcExecutionState{
|
|
Sync: coprocessor.CoProcNOPFeed,
|
|
Yield: cart.state.yield,
|
|
}
|
|
}
|
|
return coprocessor.CoProcExecutionState{
|
|
Sync: coprocessor.CoProcIdle,
|
|
Yield: cart.state.yield,
|
|
}
|
|
}
|
|
|
|
// CoProcRegister implements the coprocessor.CartCoProcBus interface.
|
|
func (cart *dpcPlus) GetCoProc() coprocessor.CartCoProc {
|
|
return cart.arm
|
|
}
|
|
|
|
// SetYieldHook implements the coprocessor.CartCoProcBus interface.
|
|
func (cart *dpcPlus) SetYieldHook(hook coprocessor.CartYieldHook) {
|
|
cart.yieldHook = hook
|
|
}
|
|
|
|
func (cart *dpcPlus) runArm() coprocessor.CoProcYield {
|
|
yld, cycles := cart.arm.Run()
|
|
cart.state.callfn.Accumulate(cycles)
|
|
return yld
|
|
}
|