2020-12-16 11:39:18 -05:00
|
|
|
// 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 cdf
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2023-08-09 04:25:50 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/coprocessor"
|
2023-04-16 11:39:36 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/environment"
|
2022-06-03 03:57:27 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm"
|
2020-12-16 11:39:18 -05:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
|
|
|
)
|
|
|
|
|
2022-01-18 13:23:28 -05:00
|
|
|
// cdf implements the mapper.CartMapper interface.
|
2020-12-16 11:39:18 -05:00
|
|
|
type cdf struct {
|
2023-04-16 11:39:36 -04:00
|
|
|
env *environment.Environment
|
2020-12-23 03:01:56 -05:00
|
|
|
mappingID string
|
|
|
|
|
2020-12-16 11:39:18 -05:00
|
|
|
// additional CPU - used by some ROMs
|
2022-06-03 03:57:27 -04:00
|
|
|
arm *arm.ARM
|
2020-12-16 11:39:18 -05:00
|
|
|
|
2022-11-01 11:05:08 -04:00
|
|
|
// the hook that handles cartridge yields
|
2023-08-09 04:25:50 -04:00
|
|
|
yieldHook coprocessor.CartYieldHook
|
2022-10-27 14:28:12 -04:00
|
|
|
|
2021-06-19 04:34:23 -04:00
|
|
|
// cdf comes in several different versions
|
|
|
|
version version
|
|
|
|
|
2020-12-16 11:39:18 -05:00
|
|
|
// banks and the currently selected bank
|
|
|
|
bankSize int
|
|
|
|
banks [][]byte
|
|
|
|
|
|
|
|
// rewindable state
|
|
|
|
state *State
|
|
|
|
}
|
|
|
|
|
|
|
|
// the sizes of these areas in a CDJF cartridge are fixed. the custom arm code
|
|
|
|
// (although it can expand into subsequent banks) and the 6507 program fit
|
|
|
|
// around these sizes.
|
|
|
|
const (
|
|
|
|
driverSize = 2048 // 2k
|
|
|
|
customSize = 2048 // 2k (may expand into subsequent banks)
|
|
|
|
)
|
|
|
|
|
2021-01-05 14:30:08 -05:00
|
|
|
// registers should be accessed via readDatastreamPointer() and
|
|
|
|
// updateDatastreamPointer(). Actually reading the data in the data stream
|
|
|
|
// should be done by streamData().
|
2020-12-16 11:39:18 -05:00
|
|
|
//
|
|
|
|
// The following values can be used for convenience. The numbered datastreams
|
|
|
|
// can be accessed numerically as expected.
|
|
|
|
//
|
|
|
|
// The AMPLITUDE register must be accessed with version.amplitude because it
|
|
|
|
// can change depending on the CDF version being emulated.
|
|
|
|
const (
|
|
|
|
DSCOMM = 32
|
|
|
|
DSJMP = 33
|
|
|
|
)
|
|
|
|
|
2021-12-12 06:24:16 -05:00
|
|
|
// NewCDF is the preferred method of initialisation for the CDF type.
|
2023-04-16 11:39:36 -04:00
|
|
|
func NewCDF(env *environment.Environment, version string, data []byte) (mapper.CartMapper, error) {
|
2020-12-16 11:39:18 -05:00
|
|
|
cart := &cdf{
|
2023-04-16 11:39:36 -04:00
|
|
|
env: env,
|
2020-12-23 03:01:56 -05:00
|
|
|
mappingID: "CDF",
|
|
|
|
bankSize: 4096,
|
|
|
|
state: newCDFstate(),
|
2023-08-09 04:25:50 -04:00
|
|
|
yieldHook: coprocessor.StubCartYieldHook{},
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
2021-05-29 14:10:22 -04:00
|
|
|
var err error
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.version, err = newVersion(env.Prefs.ARM.Model.Get().(string), version, data)
|
2021-05-29 14:10:22 -04:00
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return nil, fmt.Errorf("CDF: %w", err)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// allocate enough banks
|
2021-05-29 14:10:22 -04:00
|
|
|
cart.banks = make([][]uint8, cart.NumBanks())
|
2020-12-16 11:39:18 -05:00
|
|
|
|
|
|
|
// partition data into banks
|
|
|
|
for k := 0; k < cart.NumBanks(); k++ {
|
|
|
|
cart.banks[k] = make([]uint8, cart.bankSize)
|
|
|
|
offset := k * cart.bankSize
|
2021-05-29 14:10:22 -04:00
|
|
|
offset += driverSize
|
|
|
|
if cart.version.submapping != "CDFJ+" {
|
|
|
|
offset += customSize
|
|
|
|
}
|
2020-12-16 11:39:18 -05:00
|
|
|
cart.banks[k] = data[offset : offset+cart.bankSize]
|
|
|
|
}
|
|
|
|
|
|
|
|
// initialise static memory
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.state.static = cart.newCDFstatic(env, data, cart.version)
|
2020-12-16 11:39:18 -05:00
|
|
|
|
2022-04-09 16:34:45 -04:00
|
|
|
// datastream registers need to reference the incrementShift and
|
|
|
|
// fetcherShift values in the version type. we make a copy of these values
|
|
|
|
// on ROM initialisation
|
|
|
|
for i := range cart.state.registers.Datastream {
|
|
|
|
cart.state.registers.Datastream[i].incrementShift = cart.version.incrementShift
|
|
|
|
cart.state.registers.Datastream[i].fetcherShift = cart.version.fetcherShift
|
|
|
|
}
|
|
|
|
|
2020-12-16 11:39:18 -05:00
|
|
|
// initialise ARM processor
|
|
|
|
//
|
|
|
|
// if bank0 has any ARM code then it will start at offset 0x08. first eight
|
|
|
|
// bytes are the ARM header
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.arm = arm.NewARM(cart.version.mmap, cart.env.Prefs.ARM, cart.state.static, cart)
|
2020-12-16 11:39:18 -05:00
|
|
|
|
|
|
|
return cart, nil
|
|
|
|
}
|
|
|
|
|
2021-11-19 03:54:54 -05:00
|
|
|
// MappedBanks implements the mapper.CartMapper interface.
|
|
|
|
func (cart *cdf) MappedBanks() string {
|
2021-02-18 17:13:18 -05:00
|
|
|
return fmt.Sprintf("Bank: %d", cart.state.bank)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// ID implements the mapper.CartMapper interface.
|
|
|
|
func (cart *cdf) ID() string {
|
2021-05-29 14:10:22 -04:00
|
|
|
return cart.version.submapping
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Snapshot implements the mapper.CartMapper interface.
|
2020-12-30 16:20:56 -05:00
|
|
|
func (cart *cdf) Snapshot() mapper.CartMapper {
|
|
|
|
n := *cart
|
|
|
|
n.state = cart.state.Snapshot()
|
|
|
|
return &n
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Plumb implements the mapper.CartMapper interface.
|
2023-04-16 16:42:20 -04:00
|
|
|
func (cart *cdf) Plumb(env *environment.Environment) {
|
|
|
|
cart.env = env
|
2022-09-11 12:52:12 -04:00
|
|
|
cart.arm.Plumb(nil, cart.state.static, cart)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
2021-11-15 17:57:10 -05:00
|
|
|
// Plumb implements the mapper.CartMapper interface.
|
2023-04-16 16:42:20 -04:00
|
|
|
func (cart *cdf) PlumbFromDifferentEmulation(env *environment.Environment) {
|
|
|
|
cart.env = env
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.arm = arm.NewARM(cart.version.mmap, cart.env.Prefs.ARM, cart.state.static, cart)
|
2023-08-09 04:25:50 -04:00
|
|
|
cart.yieldHook = &coprocessor.StubCartYieldHook{}
|
2021-11-15 17:57:10 -05:00
|
|
|
}
|
|
|
|
|
2020-12-16 11:39:18 -05:00
|
|
|
// Reset implements the mapper.CartMapper interface.
|
2021-11-28 06:37:51 -05:00
|
|
|
func (cart *cdf) Reset() {
|
2020-12-16 11:39:18 -05:00
|
|
|
bank := len(cart.banks) - 1
|
2021-05-29 14:10:22 -04:00
|
|
|
if cart.version.submapping == "CDFJ+" {
|
2020-12-16 11:39:18 -05:00
|
|
|
bank = 0
|
|
|
|
}
|
|
|
|
cart.state.initialise(bank)
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
jmpAbsolute = 0x4c
|
|
|
|
ldaImmediate = 0xa9
|
2022-04-12 13:58:18 -04:00
|
|
|
ldxImmediate = 0xa2
|
|
|
|
ldyImmediate = 0xa0
|
2020-12-16 11:39:18 -05:00
|
|
|
)
|
|
|
|
|
2023-01-11 06:33:02 -05:00
|
|
|
// Access implements the mapper.CartMapper interface.
|
|
|
|
func (cart *cdf) Access(addr uint16, peek bool) (uint8, uint8, error) {
|
2022-10-27 14:28:12 -04:00
|
|
|
if b, ok := cart.state.callfn.Check(addr); ok {
|
2023-01-10 16:10:11 -05:00
|
|
|
return b, mapper.CartDrivenPins, nil
|
2021-01-27 01:29:22 -05:00
|
|
|
}
|
|
|
|
|
2023-01-10 14:31:15 -05:00
|
|
|
if !peek {
|
|
|
|
if cart.bankswitch(addr) {
|
2023-01-10 16:10:11 -05:00
|
|
|
return 0, mapper.CartDrivenPins, nil
|
2023-01-10 14:31:15 -05:00
|
|
|
}
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
data := cart.banks[cart.state.bank][addr]
|
|
|
|
|
2020-12-23 03:01:56 -05:00
|
|
|
if cart.state.registers.FastFetch && cart.state.fastJMP > 0 {
|
2020-12-16 11:39:18 -05:00
|
|
|
// maybe surprisingly, a fastJMP bay bave be triggered erroneousy.
|
|
|
|
//
|
|
|
|
// how so? well, for example, a branch operator will cause a phantom read
|
|
|
|
// before landing on the correct address. if the phantom read happens
|
|
|
|
// to land on a byte of 0x4c and if it just so happens that the next
|
|
|
|
// two bytes are value 0x00, then this FASTJMP branch will have been
|
|
|
|
// triggered.
|
|
|
|
//
|
|
|
|
// believe it or not this actually happens in the Galagon NTSC demo
|
|
|
|
// ROM. the BNE $f6fd instruction at $f72e (bank 6) will cause a
|
|
|
|
// phantom read of address $f7fd. as luck would have it (or maybe not),
|
|
|
|
// that address contains a sequence of $4c $00 $00. this causes the
|
|
|
|
// FASTJMP to be initialised and eventually for a BRK instruction to be
|
|
|
|
// returned at location $f6fd
|
|
|
|
//
|
|
|
|
// to mitigate this scenario, we take a note of what the operand
|
|
|
|
// address *should* be and use that to discard false positives.
|
|
|
|
if cart.state.fastJMP < 2 || (cart.state.fastJMP == 2 && cart.banks[cart.state.bank][addr-1] == jmpAbsolute) {
|
|
|
|
// reduce jmp counter
|
|
|
|
cart.state.fastJMP--
|
|
|
|
|
|
|
|
// which register should we use
|
|
|
|
reg := int(cart.banks[cart.state.bank][addr-1+uint16(cart.state.fastJMP)] + DSJMP)
|
|
|
|
|
|
|
|
// get current address for the data stream
|
2021-01-05 14:30:08 -05:00
|
|
|
jmp := cart.readDatastreamPointer(reg)
|
2023-03-16 04:59:16 -04:00
|
|
|
idx := int(jmp >> cart.version.fetcherShift)
|
|
|
|
if idx >= len(cart.state.static.dataRAM) {
|
|
|
|
return 0, mapper.CartDrivenPins, nil
|
|
|
|
}
|
|
|
|
data = cart.state.static.dataRAM[idx]
|
2020-12-16 11:39:18 -05:00
|
|
|
jmp += 1 << cart.version.fetcherShift
|
2021-01-05 14:30:08 -05:00
|
|
|
cart.updateDatastreamPointer(reg, jmp)
|
2020-12-16 11:39:18 -05:00
|
|
|
|
2023-01-10 16:10:11 -05:00
|
|
|
return data, mapper.CartDrivenPins, nil
|
2023-03-16 03:45:22 -04:00
|
|
|
} else {
|
|
|
|
cart.state.fastJMP = 0
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 03:45:22 -04:00
|
|
|
if cart.state.registers.FastFetch && cart.state.fastLoad > 0 {
|
|
|
|
cart.state.fastLoad--
|
2020-12-16 11:39:18 -05:00
|
|
|
|
|
|
|
// data fetchers
|
2022-04-12 13:58:18 -04:00
|
|
|
if data >= cart.version.datastreamOffset && data <= cart.version.datastreamOffset+DSCOMM {
|
2023-01-10 16:10:11 -05:00
|
|
|
return cart.streamData(int(data - cart.version.datastreamOffset)), mapper.CartDrivenPins, nil
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// music fetchers
|
|
|
|
if data == byte(cart.version.amplitudeRegister) {
|
2020-12-23 03:01:56 -05:00
|
|
|
if cart.state.registers.SampleMode {
|
2020-12-16 11:39:18 -05:00
|
|
|
addr := cart.readMusicFetcher(0)
|
2021-05-29 14:10:22 -04:00
|
|
|
addr += cart.state.registers.MusicFetcher[0].Count >> (cart.version.musicFetcherShift + 1)
|
2020-12-16 11:39:18 -05:00
|
|
|
|
|
|
|
// get sample from memory
|
2022-04-12 12:12:58 -04:00
|
|
|
data, _ = cart.state.static.Read8bit(addr)
|
2020-12-16 11:39:18 -05:00
|
|
|
|
|
|
|
// prevent excessive volume
|
2021-05-29 14:10:22 -04:00
|
|
|
if cart.state.registers.MusicFetcher[0].Count&(1<<cart.version.musicFetcherShift) == 0 {
|
2020-12-16 11:39:18 -05:00
|
|
|
data >>= 4
|
|
|
|
}
|
|
|
|
|
2023-01-10 16:10:11 -05:00
|
|
|
return data, mapper.CartDrivenPins, nil
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// data retrieval for non-SampleMode uses all three music fetchers
|
|
|
|
data = 0
|
|
|
|
for i := range cart.state.registers.MusicFetcher {
|
|
|
|
m := cart.readMusicFetcher(i)
|
|
|
|
m += (cart.state.registers.MusicFetcher[i].Count >> cart.state.registers.MusicFetcher[i].Waveform)
|
2022-04-12 12:12:58 -04:00
|
|
|
v, _ := cart.state.static.Read8bit(m)
|
|
|
|
data += v
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
2023-01-10 16:10:11 -05:00
|
|
|
return data, mapper.CartDrivenPins, nil
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// if data is higher than AMPLITUDE then the 0xa9 we detected in the
|
|
|
|
// previous Read() was just a normal value (maybe an LDA #immediate
|
|
|
|
// opcode but not one intended for fast fetch)
|
|
|
|
}
|
|
|
|
|
|
|
|
// set lda flag if fast fetch mode is on and data returned is LDA #immediate
|
2023-03-16 03:45:22 -04:00
|
|
|
if cart.state.registers.FastFetch {
|
|
|
|
switch data {
|
|
|
|
case jmpAbsolute:
|
|
|
|
// only "jmp absolute" instructions with certain address operands are
|
|
|
|
// treated as "FastJMPs". Generally, this address must be $0000 but in
|
|
|
|
// the case of the CDFJ version an address of $0001 is also acceptable
|
|
|
|
if cart.banks[cart.state.bank][(addr+1)&0xffff]&cart.version.fastJMPmask == 0x00 &&
|
|
|
|
cart.banks[cart.state.bank][(addr+2)&0xffff] == 0x00 {
|
|
|
|
cart.state.fastJMP = 2
|
|
|
|
}
|
|
|
|
case ldaImmediate:
|
|
|
|
cart.state.fastLoad = 2
|
|
|
|
case ldxImmediate:
|
|
|
|
if cart.version.fastLDX {
|
|
|
|
cart.state.fastLoad = 2
|
|
|
|
}
|
|
|
|
case ldyImmediate:
|
|
|
|
if cart.version.fastLDY {
|
|
|
|
cart.state.fastLoad = 2
|
|
|
|
}
|
2020-12-25 06:20:04 -05:00
|
|
|
}
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
2023-01-10 16:10:11 -05:00
|
|
|
return data, mapper.CartDrivenPins, nil
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
2023-02-06 04:14:28 -05:00
|
|
|
// AccessVolatile implements the mapper.CartMapper interface.
|
|
|
|
func (cart *cdf) AccessVolatile(addr uint16, data uint8, poke bool) error {
|
2022-02-20 11:33:01 -05:00
|
|
|
// bank switches can not take place if coprocessor is active
|
|
|
|
if cart.state.callfn.IsActive() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-10 14:31:15 -05:00
|
|
|
if !poke {
|
|
|
|
if cart.bankswitch(addr) {
|
|
|
|
return nil
|
|
|
|
}
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
switch addr {
|
|
|
|
case 0x0ff0:
|
|
|
|
// DSWRITE
|
|
|
|
|
|
|
|
// top 12 bits are significant
|
2021-01-05 14:30:08 -05:00
|
|
|
v := cart.readDatastreamPointer(DSCOMM)
|
2020-12-16 11:39:18 -05:00
|
|
|
|
|
|
|
// write data to ARM RAM
|
2023-03-16 04:59:16 -04:00
|
|
|
idx := int(v >> cart.version.fetcherShift)
|
|
|
|
if idx >= len(cart.state.static.dataRAM) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
cart.state.static.dataRAM[idx] = data
|
2020-12-16 11:39:18 -05:00
|
|
|
|
|
|
|
// advance address value
|
|
|
|
v += 1 << cart.version.fetcherShift
|
|
|
|
|
|
|
|
// write adjusted address (making sure to put the bits in the top 12 bits)
|
2021-01-05 14:30:08 -05:00
|
|
|
cart.updateDatastreamPointer(DSCOMM, v)
|
2020-12-16 11:39:18 -05:00
|
|
|
|
|
|
|
case 0x0ff1:
|
|
|
|
// DSPTR
|
2021-01-05 14:30:08 -05:00
|
|
|
v := cart.readDatastreamPointer(DSCOMM) << 8
|
2020-12-16 11:39:18 -05:00
|
|
|
v &= cart.version.fetcherMask
|
|
|
|
|
|
|
|
// add new data to lower byte of dsptr value
|
|
|
|
v |= (uint32(data) << cart.version.fetcherShift)
|
|
|
|
|
|
|
|
// write dsptr to dscomm register
|
2021-01-05 14:30:08 -05:00
|
|
|
cart.updateDatastreamPointer(DSCOMM, v)
|
2020-12-16 11:39:18 -05:00
|
|
|
|
|
|
|
case 0x0ff2:
|
|
|
|
// SETMODE
|
2020-12-23 03:01:56 -05:00
|
|
|
cart.state.registers.FastFetch = data&0x0f != 0x0f
|
|
|
|
cart.state.registers.SampleMode = data&0xf0 != 0xf0
|
2020-12-16 11:39:18 -05:00
|
|
|
|
2020-12-23 03:01:56 -05:00
|
|
|
if !cart.state.registers.FastFetch {
|
2023-03-16 03:45:22 -04:00
|
|
|
cart.state.fastLoad = 0
|
2020-12-16 11:39:18 -05:00
|
|
|
cart.state.fastJMP = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
case 0x0ff3:
|
|
|
|
fallthrough
|
|
|
|
|
|
|
|
case 0x0ff4:
|
|
|
|
// CALLFN
|
|
|
|
switch data {
|
|
|
|
case 0xfe:
|
|
|
|
// generate interrupt to update AUDV0 while running ARM code
|
|
|
|
fallthrough
|
|
|
|
case 0xff:
|
2023-07-19 15:06:45 -04:00
|
|
|
runArm := func() {
|
2023-08-09 05:15:55 -04:00
|
|
|
cart.arm.StartProfiling()
|
|
|
|
defer cart.arm.ProcessProfiling()
|
2023-07-19 15:06:45 -04:00
|
|
|
cart.state.yield = cart.runArm()
|
2022-09-05 06:02:08 -04:00
|
|
|
}
|
2022-10-27 14:28:12 -04:00
|
|
|
|
2023-07-16 14:02:10 -04:00
|
|
|
// keep calling runArm() for as long as program has not ended
|
2023-07-19 15:06:45 -04:00
|
|
|
runArm()
|
2023-08-09 04:25:50 -04:00
|
|
|
for cart.state.yield.Type != coprocessor.YieldProgramEnded {
|
|
|
|
if cart.yieldHook.CartYield(cart.state.yield.Type) == coprocessor.YieldHookEnd {
|
2023-07-16 14:02:10 -04:00
|
|
|
break
|
2022-11-04 03:23:35 -04:00
|
|
|
}
|
2023-07-19 15:06:45 -04:00
|
|
|
runArm()
|
2022-10-27 14:28:12 -04:00
|
|
|
}
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
2022-05-23 14:14:36 -04:00
|
|
|
default:
|
|
|
|
if poke {
|
|
|
|
cart.banks[cart.state.bank][addr] = data
|
|
|
|
}
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
2022-05-23 14:14:36 -04:00
|
|
|
return nil
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// bankswitch on hotspot access.
|
2023-01-10 14:31:15 -05:00
|
|
|
func (cart *cdf) bankswitch(addr uint16) bool {
|
2020-12-16 11:39:18 -05:00
|
|
|
if addr >= 0x0ff4 && addr <= 0x0ffb {
|
2020-12-23 03:01:56 -05:00
|
|
|
if addr == 0x0ff4 {
|
|
|
|
cart.state.bank = 6
|
|
|
|
} else if addr == 0x0ff5 {
|
|
|
|
cart.state.bank = 0
|
|
|
|
} else if addr == 0x0ff6 {
|
|
|
|
cart.state.bank = 1
|
|
|
|
} else if addr == 0x0ff7 {
|
|
|
|
cart.state.bank = 2
|
|
|
|
} else if addr == 0x0ff8 {
|
|
|
|
cart.state.bank = 3
|
|
|
|
} else if addr == 0x0ff9 {
|
|
|
|
cart.state.bank = 4
|
|
|
|
} else if addr == 0x0ffa {
|
|
|
|
cart.state.bank = 5
|
|
|
|
} else if addr == 0x0ffb {
|
|
|
|
cart.state.bank = 6
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
2020-12-23 03:01:56 -05:00
|
|
|
|
2021-05-29 14:10:22 -04:00
|
|
|
if cart.version.submapping == "CDFJ+" {
|
|
|
|
cart.state.bank++
|
|
|
|
cart.state.bank %= 7
|
|
|
|
}
|
|
|
|
|
2020-12-16 11:39:18 -05:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// NumBanks implements the mapper.CartMapper interface.
|
|
|
|
func (cart *cdf) NumBanks() int {
|
2021-05-29 14:10:22 -04:00
|
|
|
return 7
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetBank implements the mapper.CartMapper interface.
|
|
|
|
func (cart *cdf) GetBank(addr uint16) mapper.BankInfo {
|
2021-01-27 01:29:22 -05:00
|
|
|
return mapper.BankInfo{
|
|
|
|
Number: cart.state.bank,
|
|
|
|
IsRAM: false,
|
|
|
|
ExecutingCoprocessor: cart.state.callfn.IsActive(),
|
|
|
|
CoprocessorResumeAddr: cart.state.callfn.ResumeAddr,
|
|
|
|
}
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
2023-01-11 06:33:02 -05:00
|
|
|
// AccessPassive implements the mapper.CartMapper interface.
|
2023-10-23 13:26:27 -04:00
|
|
|
func (cart *cdf) AccessPassive(addr uint16, data uint8) error {
|
|
|
|
return nil
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Step implements the mapper.CartMapper interface.
|
2020-12-29 17:37:20 -05:00
|
|
|
func (cart *cdf) Step(clock float32) {
|
2023-03-16 03:45:22 -04:00
|
|
|
// reduce fastLoad activation counter. this filters out phantom reads that
|
|
|
|
// look like fastLoad activating instructions
|
|
|
|
if cart.state.fastLoad > 0 {
|
|
|
|
cart.state.fastLoad--
|
|
|
|
}
|
|
|
|
|
2020-12-16 11:39:18 -05:00
|
|
|
// sample rate of 20KHz.
|
|
|
|
//
|
|
|
|
// 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()
|
|
|
|
//
|
|
|
|
// the 20Khz is the same as the DPC format (see mapper_dpc for commentary).
|
|
|
|
|
|
|
|
cart.state.beats++
|
|
|
|
if cart.state.beats%59 == 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
|
|
|
|
}
|
|
|
|
|
2021-06-17 05:48:55 -04:00
|
|
|
// Step ARM state if the ARM program is NOT running
|
2021-06-08 15:41:15 -04:00
|
|
|
if cart.state.callfn.IsActive() {
|
2023-03-26 11:45:39 -04:00
|
|
|
if cart.arm.ImmediateMode() {
|
2021-06-08 15:41:15 -04:00
|
|
|
cart.arm.Step(clock)
|
2022-03-21 14:45:15 -04:00
|
|
|
} else {
|
|
|
|
timerClock := cart.state.callfn.Step(clock, cart.arm.Clk)
|
|
|
|
if timerClock > 0 {
|
|
|
|
cart.arm.Step(timerClock)
|
|
|
|
}
|
2021-06-08 15:41:15 -04:00
|
|
|
}
|
2022-09-03 15:55:16 -04:00
|
|
|
|
2023-08-09 05:15:55 -04:00
|
|
|
if !cart.state.callfn.IsActive() {
|
|
|
|
cart.arm.ProcessProfiling()
|
2022-09-03 15:55:16 -04:00
|
|
|
}
|
2021-06-29 08:08:59 -04:00
|
|
|
} else {
|
|
|
|
cart.arm.Step(clock)
|
2021-01-27 01:29:22 -05:00
|
|
|
}
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
|
2022-10-02 15:46:57 -04:00
|
|
|
// CopyBanks implements the mapper.CartMapper interface.
|
2020-12-16 11:39:18 -05:00
|
|
|
func (cart *cdf) 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
|
|
|
|
}
|
|
|
|
|
2022-10-02 15:46:57 -04:00
|
|
|
// Labels implements the mapper.CartLabelsBus interface.
|
2022-02-18 05:48:01 -05:00
|
|
|
func (cart *cdf) Labels() mapper.CartLabels {
|
|
|
|
return map[uint16]string{
|
|
|
|
0x0000: "FASTJMP1",
|
|
|
|
0x0001: "FASTJMP2",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-16 11:39:18 -05:00
|
|
|
// ReadHotspots implements the mapper.CartHotspotsBus interface.
|
|
|
|
func (cart *cdf) ReadHotspots() map[uint16]mapper.CartHotspotInfo {
|
|
|
|
return map[uint16]mapper.CartHotspotInfo{
|
|
|
|
0x1ff5: {Symbol: "BANK0", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ff6: {Symbol: "BANK1", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ff7: {Symbol: "BANK2", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ff8: {Symbol: "BANK3", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ff9: {Symbol: "BANK4", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ffa: {Symbol: "BANK5", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ffb: {Symbol: "BANK6", Action: mapper.HotspotBankSwitch},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteHotspots implements the mapper.CartHotspotsBus interface.
|
|
|
|
func (cart *cdf) WriteHotspots() map[uint16]mapper.CartHotspotInfo {
|
|
|
|
return map[uint16]mapper.CartHotspotInfo{
|
|
|
|
0x1ff0: {Symbol: "DSWRITE", Action: mapper.HotspotRegister},
|
|
|
|
0x1ff1: {Symbol: "DSPTR", Action: mapper.HotspotRegister},
|
|
|
|
0x1ff2: {Symbol: "SETMODE", Action: mapper.HotspotRegister},
|
|
|
|
0x1ff3: {Symbol: "CALLFN", Action: mapper.HotspotFunction},
|
2021-06-24 15:22:58 -04:00
|
|
|
0x1ff5: {Symbol: "BANK0", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ff6: {Symbol: "BANK1", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ff7: {Symbol: "BANK2", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ff8: {Symbol: "BANK3", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ff9: {Symbol: "BANK4", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ffa: {Symbol: "BANK5", Action: mapper.HotspotBankSwitch},
|
|
|
|
0x1ffb: {Symbol: "BANK6", Action: mapper.HotspotBankSwitch},
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-05 10:44:08 -04:00
|
|
|
// ARMinterrupt implements the arm.CatridgeHook interface.
|
2022-06-03 03:57:27 -04:00
|
|
|
func (cart *cdf) ARMinterrupt(addr uint32, val1 uint32, val2 uint32) (arm.ARMinterruptReturn, error) {
|
|
|
|
var r arm.ARMinterruptReturn
|
2020-12-16 11:39:18 -05:00
|
|
|
|
2020-12-23 03:01:56 -05:00
|
|
|
if cart.version.submapping == "CDF0" {
|
2020-12-16 11:39:18 -05:00
|
|
|
switch addr {
|
2021-12-11 10:13:48 -05:00
|
|
|
case cart.version.mmap.FlashOrigin | 0x000006e2:
|
2021-01-17 03:28:41 -05:00
|
|
|
r.InterruptEvent = "Set music note"
|
2020-12-16 11:39:18 -05:00
|
|
|
if val1 >= uint32(len(cart.state.registers.MusicFetcher)) {
|
2023-02-13 06:08:52 -05:00
|
|
|
return r, fmt.Errorf("music fetcher index (%d) too high ", val1)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
cart.state.registers.MusicFetcher[val1].Freq = val2
|
2021-07-06 06:24:12 -04:00
|
|
|
r.NumMemAccess = 2
|
|
|
|
r.NumAdditionalCycles = 11
|
2021-12-11 10:13:48 -05:00
|
|
|
case cart.version.mmap.FlashOrigin | 0x000006e6:
|
2020-12-16 11:39:18 -05:00
|
|
|
// reset wave
|
2021-01-17 03:28:41 -05:00
|
|
|
r.InterruptEvent = "Reset wave"
|
2020-12-16 11:39:18 -05:00
|
|
|
if val1 >= uint32(len(cart.state.registers.MusicFetcher)) {
|
2023-02-13 06:08:52 -05:00
|
|
|
return r, fmt.Errorf("music fetcher index (%d) too high ", val1)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
cart.state.registers.MusicFetcher[val1].Count = 0
|
2021-07-06 06:24:12 -04:00
|
|
|
r.NumMemAccess = 3
|
|
|
|
r.NumAdditionalCycles = 13
|
2021-12-11 10:13:48 -05:00
|
|
|
case cart.version.mmap.FlashOrigin | 0x000006ea:
|
2021-01-17 03:28:41 -05:00
|
|
|
r.InterruptEvent = "Get wave pointer"
|
2020-12-16 11:39:18 -05:00
|
|
|
if val1 >= uint32(len(cart.state.registers.MusicFetcher)) {
|
2023-02-13 06:08:52 -05:00
|
|
|
return r, fmt.Errorf("music fetcher index (%d) too high ", val1)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
r.SaveValue = cart.state.registers.MusicFetcher[val1].Count
|
|
|
|
r.SaveRegister = 2
|
|
|
|
r.SaveResult = true
|
2021-07-06 06:24:12 -04:00
|
|
|
r.NumMemAccess = 3
|
|
|
|
r.NumAdditionalCycles = 13
|
2021-12-11 10:13:48 -05:00
|
|
|
case cart.version.mmap.FlashOrigin | 0x000006ee:
|
2021-01-17 03:28:41 -05:00
|
|
|
r.InterruptEvent = "Set wave size"
|
2020-12-16 11:39:18 -05:00
|
|
|
if val1 >= uint32(len(cart.state.registers.MusicFetcher)) {
|
2023-02-13 06:08:52 -05:00
|
|
|
return r, fmt.Errorf("music fetcher index (%d) too high ", val1)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
cart.state.registers.MusicFetcher[val1].Waveform = uint8(val2)
|
2021-07-06 06:24:12 -04:00
|
|
|
r.NumMemAccess = 3
|
|
|
|
r.NumAdditionalCycles = 28
|
2020-12-16 11:39:18 -05:00
|
|
|
default:
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
r.InterruptServiced = true
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
switch addr {
|
2021-12-11 10:13:48 -05:00
|
|
|
case cart.version.mmap.FlashOrigin | 0x00000752:
|
2021-01-17 03:28:41 -05:00
|
|
|
r.InterruptEvent = "Set music note"
|
2020-12-16 11:39:18 -05:00
|
|
|
if val1 >= uint32(len(cart.state.registers.MusicFetcher)) {
|
2023-02-13 06:08:52 -05:00
|
|
|
return r, fmt.Errorf("music fetcher index (%d) too high ", val1)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
cart.state.registers.MusicFetcher[val1].Freq = val2
|
2021-07-06 06:24:12 -04:00
|
|
|
r.NumMemAccess = 2
|
|
|
|
r.NumAdditionalCycles = 11
|
2021-12-11 10:13:48 -05:00
|
|
|
case cart.version.mmap.FlashOrigin | 0x00000756:
|
2021-01-17 03:28:41 -05:00
|
|
|
r.InterruptEvent = "Reset wave"
|
2020-12-16 11:39:18 -05:00
|
|
|
if val1 >= uint32(len(cart.state.registers.MusicFetcher)) {
|
2023-02-13 06:08:52 -05:00
|
|
|
return r, fmt.Errorf("music fetcher index (%d) too high ", val1)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
cart.state.registers.MusicFetcher[val1].Count = 0
|
2021-07-06 06:24:12 -04:00
|
|
|
r.NumMemAccess = 3
|
|
|
|
r.NumAdditionalCycles = 13
|
2021-12-11 10:13:48 -05:00
|
|
|
case cart.version.mmap.FlashOrigin | 0x0000075a:
|
2021-01-17 03:28:41 -05:00
|
|
|
r.InterruptEvent = "Get wave pointer"
|
2020-12-16 11:39:18 -05:00
|
|
|
if val1 >= uint32(len(cart.state.registers.MusicFetcher)) {
|
2023-02-13 06:08:52 -05:00
|
|
|
return r, fmt.Errorf("music fetcher index (%d) too high ", val1)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
r.SaveValue = cart.state.registers.MusicFetcher[val1].Count
|
|
|
|
r.SaveRegister = 2
|
|
|
|
r.SaveResult = true
|
2021-07-06 06:24:12 -04:00
|
|
|
r.NumMemAccess = 3
|
|
|
|
r.NumAdditionalCycles = 13
|
2021-12-11 10:13:48 -05:00
|
|
|
case cart.version.mmap.FlashOrigin | 0x0000075e:
|
2021-01-17 03:28:41 -05:00
|
|
|
r.InterruptEvent = "Set wave size"
|
2020-12-16 11:39:18 -05:00
|
|
|
if val1 >= uint32(len(cart.state.registers.MusicFetcher)) {
|
2023-02-13 06:08:52 -05:00
|
|
|
return r, fmt.Errorf("music fetcher index (%d) too high ", val1)
|
2020-12-16 11:39:18 -05:00
|
|
|
}
|
|
|
|
cart.state.registers.MusicFetcher[val1].Waveform = uint8(val2)
|
2021-07-06 06:24:12 -04:00
|
|
|
r.NumMemAccess = 3
|
|
|
|
r.NumAdditionalCycles = 28
|
2020-12-16 11:39:18 -05:00
|
|
|
default:
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
r.InterruptServiced = true
|
|
|
|
return r, nil
|
|
|
|
}
|
2021-05-23 17:18:54 -04:00
|
|
|
|
2021-05-24 11:17:20 -04:00
|
|
|
// HotLoad implements the mapper.CartHotLoader interface.
|
2021-05-23 17:18:54 -04:00
|
|
|
func (cart *cdf) HotLoad(data []byte) error {
|
|
|
|
if len(data) == 0 {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("CDF: empty data")
|
2021-05-23 17:18:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for k := 0; k < cart.NumBanks(); k++ {
|
|
|
|
cart.banks[k] = make([]uint8, cart.bankSize)
|
|
|
|
offset := k * cart.bankSize
|
|
|
|
offset += driverSize + customSize
|
|
|
|
cart.banks[k] = data[offset : offset+cart.bankSize]
|
|
|
|
}
|
|
|
|
|
|
|
|
cart.state.static.HotLoad(data)
|
|
|
|
|
2022-09-11 12:52:12 -04:00
|
|
|
cart.arm.Plumb(nil, cart.state.static, cart)
|
2022-04-22 06:24:41 -04:00
|
|
|
cart.arm.ClearCaches()
|
|
|
|
|
2021-05-23 17:18:54 -04:00
|
|
|
return nil
|
|
|
|
}
|
2022-06-22 04:19:18 -04:00
|
|
|
|
2023-08-09 05:15:55 -04:00
|
|
|
// CoProcExecutionState implements the coprocessor.CartCoProcBus interface.
|
2023-08-09 04:25:50 -04:00
|
|
|
func (cart *cdf) CoProcExecutionState() coprocessor.CoProcExecutionState {
|
2022-08-15 22:52:24 -04:00
|
|
|
if cart.state.callfn.IsActive() {
|
2023-08-09 04:25:50 -04:00
|
|
|
return coprocessor.CoProcExecutionState{
|
|
|
|
Sync: coprocessor.CoProcNOPFeed,
|
2023-07-12 02:51:06 -04:00
|
|
|
Yield: cart.state.yield,
|
|
|
|
}
|
|
|
|
}
|
2023-08-09 04:25:50 -04:00
|
|
|
return coprocessor.CoProcExecutionState{
|
|
|
|
Sync: coprocessor.CoProcIdle,
|
2023-07-12 02:51:06 -04:00
|
|
|
Yield: cart.state.yield,
|
2022-08-15 22:52:24 -04:00
|
|
|
}
|
2022-06-27 03:03:10 -04:00
|
|
|
}
|
|
|
|
|
2023-08-09 05:15:55 -04:00
|
|
|
// CoProcRegister implements the coprocessor.CartCoProcBus interface.
|
|
|
|
func (cart *cdf) GetCoProc() coprocessor.CartCoProc {
|
|
|
|
return cart.arm
|
2022-06-26 02:54:14 -04:00
|
|
|
}
|
|
|
|
|
2023-08-09 05:15:55 -04:00
|
|
|
// SetYieldHook implements the coprocessor.CartCoProcBus interface.
|
2023-08-09 04:25:50 -04:00
|
|
|
func (cart *cdf) SetYieldHook(hook coprocessor.CartYieldHook) {
|
2022-11-01 11:05:08 -04:00
|
|
|
cart.yieldHook = hook
|
2022-10-27 14:28:12 -04:00
|
|
|
}
|
|
|
|
|
2023-08-09 04:25:50 -04:00
|
|
|
func (cart *cdf) runArm() coprocessor.CoProcYield {
|
2022-11-05 14:41:11 -04:00
|
|
|
yld, cycles := cart.arm.Run()
|
2022-08-17 05:23:21 -04:00
|
|
|
|
2022-11-06 15:27:00 -05:00
|
|
|
cart.state.callfn.Accumulate(cycles)
|
2022-06-26 02:54:14 -04:00
|
|
|
|
2022-11-06 15:27:00 -05:00
|
|
|
// update the Register types after each return from arm.Run() regardless of
|
|
|
|
// yield reason
|
2022-06-26 02:54:14 -04:00
|
|
|
for i := range cart.state.registers.Datastream {
|
|
|
|
cart.state.registers.Datastream[i].Pointer = cart.readDatastreamPointer(i)
|
|
|
|
cart.state.registers.Datastream[i].Increment = cart.readDatastreamIncrement(i)
|
|
|
|
cart.state.registers.Datastream[i].AfterCALLFN = cart.readDatastreamPointer(i)
|
|
|
|
}
|
|
|
|
|
2022-11-05 14:41:11 -04:00
|
|
|
return yld
|
2022-06-26 02:54:14 -04:00
|
|
|
}
|