mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-01 11:37:58 -04:00
da83fc311b
all cartridge data is read through cartridgeloader io.Reader interface
961 lines
31 KiB
Go
961 lines
31 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"
|
|
"io"
|
|
|
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
|
"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
|
|
|
|
// armState is a copy of the ARM's state at the moment of the most recent
|
|
// Snapshot. it's used only suring a Plumb() operation
|
|
armState *arm.ARMState
|
|
}
|
|
|
|
// 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, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
|
data, err := io.ReadAll(loader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("DPC+: %w", err)
|
|
}
|
|
|
|
cart := &dpcPlus{
|
|
env: env,
|
|
mappingID: "DPC+",
|
|
bankSize: 4096,
|
|
state: newDPCPlusState(),
|
|
yieldHook: coprocessor.StubCartYieldHook{},
|
|
}
|
|
|
|
// 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, err = cart.newDPCplusStatic(cart.version, data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("DPC+: %s", err.Error())
|
|
}
|
|
|
|
// 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
|
|
|
|
// taking a snapshot of ARM state via the ARM itself can cause havoc if
|
|
// this instance of the cart is not current (because the ARM pointer itself
|
|
// may be stale or pointing to another emulation)
|
|
if cart.armState == nil {
|
|
n.armState = cart.arm.Snapshot()
|
|
} else {
|
|
n.armState = cart.armState.Snapshot()
|
|
}
|
|
|
|
n.state = cart.state.Snapshot()
|
|
return &n
|
|
}
|
|
|
|
// Plumb implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) Plumb(env *environment.Environment) {
|
|
cart.env = env
|
|
if cart.armState == nil {
|
|
panic("cannot plumb this ELF instance because the ARM state is nil")
|
|
}
|
|
cart.arm.Plumb(cart.armState, cart.state.static, cart)
|
|
cart.armState = nil
|
|
}
|
|
|
|
// Plumb implements the mapper.CartMapper interface.
|
|
func (cart *dpcPlus) PlumbFromDifferentEmulation(env *environment.Environment) {
|
|
cart.env = env
|
|
if cart.armState == nil {
|
|
panic("cannot plumb this ELF instance because the ARM state is nil")
|
|
}
|
|
cart.arm = arm.NewARM(cart.version.mmap, cart.env.Prefs.ARM, cart.state.static, cart)
|
|
cart.arm.Plumb(cart.armState, cart.state.static, cart)
|
|
cart.armState = nil
|
|
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.data[(cart.state.registers.MusicFetcher[0].Waveform<<5)+(cart.state.registers.MusicFetcher[0].Count>>27)]
|
|
data += cart.state.static.dataRAM.data[(cart.state.registers.MusicFetcher[1].Waveform<<5)+(cart.state.registers.MusicFetcher[1].Count>>27)]
|
|
data += cart.state.static.dataRAM.data[(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.data[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.data[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.data[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.data))
|
|
cart.state.static.dataRAM.data[o] = cart.state.static.dataROM.data[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.data[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.data[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[data<<2])
|
|
cart.state.registers.MusicFetcher[0].Freq += uint32(cart.state.static.freqRAM.data[(data<<2)+1]) << 8
|
|
cart.state.registers.MusicFetcher[0].Freq += uint32(cart.state.static.freqRAM.data[(data<<2)+2]) << 16
|
|
cart.state.registers.MusicFetcher[0].Freq += uint32(cart.state.static.freqRAM.data[(data<<2)+3]) << 24
|
|
case 0x76:
|
|
cart.state.registers.MusicFetcher[1].Freq = uint32(cart.state.static.freqRAM.data[data<<2])
|
|
cart.state.registers.MusicFetcher[1].Freq += uint32(cart.state.static.freqRAM.data[(data<<2)+1]) << 8
|
|
cart.state.registers.MusicFetcher[1].Freq += uint32(cart.state.static.freqRAM.data[(data<<2)+2]) << 16
|
|
cart.state.registers.MusicFetcher[1].Freq += uint32(cart.state.static.freqRAM.data[(data<<2)+3]) << 24
|
|
case 0x77:
|
|
cart.state.registers.MusicFetcher[2].Freq = uint32(cart.state.static.freqRAM.data[data<<2])
|
|
cart.state.registers.MusicFetcher[2].Freq += uint32(cart.state.static.freqRAM.data[(data<<2)+1]) << 8
|
|
cart.state.registers.MusicFetcher[2].Freq += uint32(cart.state.static.freqRAM.data[(data<<2)+2]) << 16
|
|
cart.state.registers.MusicFetcher[2].Freq += uint32(cart.state.static.freqRAM.data[(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.data[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
|
|
}
|