Gopher2600/hardware/memory/cartridge/ace/memory.go
2024-04-30 18:35:26 +01:00

436 lines
13 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 ace
import (
"encoding/binary"
"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/arm/architecture"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/preferences"
"github.com/jetsetilly/gopher2600/logger"
)
type interruptARM interface {
Interrupt()
}
type aceHeader struct {
version string
driverName string
driverVersion uint32
romSize uint32
checksum uint32
entry uint32
}
const (
aceHeaderMagic = 0
aceHeaderDriverName = 8
aceHeaderDriverVersion = 24
aceHeaderROMSize = 28
aceHeaderROMChecksum = 32
aceHeaderEntryPoint = 36
aceStartOfVCSProgram = 40
)
type aceMemory struct {
env *environment.Environment
header aceHeader
model architecture.Map
resetSP uint32
resetLR uint32
resetPC uint32
gpio []byte
gpioOrigin uint32
gpioMemtop uint32
// SRAM is called CCM in the Uno/PlusROM architecture
ccm []byte
ccmOrigin uint32
ccmMemtop uint32
// download memory is divided into three segments
download []byte
downloadOrigin uint32
downloadMemtop uint32
// the buffer and ARM segments must be consecutive
buffer []byte
bufferOrigin uint32
bufferMemtop uint32
// minimal interface to the ARM
arm interruptARM
armInterruptCt int
// parallelARM is true whenever the address bus is not a cartridge address (ie.
// a TIA or RIOT address). this means that the arm is running unhindered
// and will not have yielded for that colour clock
parallelARM bool
// most recent yield from the coprocessor
yield coprocessor.CoProcYield
// count of cycles accumulated
cycles float32
}
const (
DATA_MODER = 0x40020800
ADDR_IDR = 0x40020c10
DATA_IDR = 0x40020810
DATA_ODR = 0x40020814
GPIO_ORIGIN = 0x40020800
GPIO_MEMTOP = 0x40020cff
)
func (mem *aceMemory) isDataModeOut() bool {
return mem.gpio[DATA_MODER-mem.gpioOrigin] == 0x55 && mem.gpio[DATA_MODER-mem.gpioOrigin+1] == 0x55
}
func (mem *aceMemory) setDataMode(out bool) {
if out {
mem.gpio[DATA_MODER-mem.gpioOrigin] = 0x55
mem.gpio[DATA_MODER-mem.gpioOrigin+1] = 0x55
} else {
mem.gpio[DATA_MODER-mem.gpioOrigin] = 0x00
mem.gpio[DATA_MODER-mem.gpioOrigin+1] = 0x00
}
}
func newAceMemory(env *environment.Environment, data []byte, armPrefs *preferences.ARMPreferences) (*aceMemory, error) {
mem := &aceMemory{
env: env,
model: architecture.NewMap(architecture.PlusCart),
}
// CCM creation
mem.ccm = make([]byte, 0x00010000) // 4k
mem.ccmOrigin = mem.model.SRAMOrigin
mem.ccmMemtop = mem.ccmOrigin + uint32(len(mem.ccm)) - 1
// read header
mem.header.version = string(data[:aceHeaderDriverName])
logger.Logf(mem.env, "ACE", "header: version name: %s", mem.header.version)
mem.header.driverName = string(data[aceHeaderDriverName:aceHeaderDriverVersion])
logger.Logf(mem.env, "ACE", "header: driver name: %s", mem.header.driverName)
mem.header.driverVersion = (uint32(data[aceHeaderDriverVersion])) |
(uint32(data[aceHeaderDriverVersion+1]) << 8) |
(uint32(data[aceHeaderDriverVersion+2]) << 16) |
(uint32(data[aceHeaderDriverVersion+3]) << 24)
logger.Logf(mem.env, "ACE", "header: driver version: %08x", mem.header.driverVersion)
mem.header.romSize = (uint32(data[aceHeaderROMSize])) |
(uint32(data[aceHeaderROMSize+1]) << 8) |
(uint32(data[aceHeaderROMSize+2]) << 16) |
(uint32(data[aceHeaderROMSize+3]) << 24)
logger.Logf(mem.env, "ACE", "header: romsize: %08x", mem.header.romSize)
mem.header.checksum = (uint32(data[aceHeaderROMChecksum])) |
(uint32(data[aceHeaderROMChecksum+1]) << 8) |
(uint32(data[aceHeaderROMChecksum+2]) << 16) |
(uint32(data[aceHeaderROMChecksum+3]) << 24)
logger.Logf(mem.env, "ACE", "header: checksum: %08x", mem.header.checksum)
mem.header.entry = (uint32(data[aceHeaderEntryPoint])) |
(uint32(data[aceHeaderEntryPoint+1]) << 8) |
(uint32(data[aceHeaderEntryPoint+2]) << 16) |
(uint32(data[aceHeaderEntryPoint+3]) << 24)
logger.Logf(mem.env, "ACE", "header: entrypoint: %08x", mem.header.entry)
mem.download = data[:]
switch mem.header.version {
case "ACE-PC00":
mem.downloadOrigin = 0x08020000
mem.header.entry = 0x1028
logger.Logf(mem.env, "ACE", "header: entrypoint adjusted to: %08x", mem.header.entry)
case "ACE-UF00":
mem.downloadOrigin = 0x08020000
case "ACE-2600":
fallthrough
default:
return nil, fmt.Errorf("ACE: version: %s not supported", mem.header.version)
}
mem.downloadMemtop = mem.downloadOrigin + uint32(len(mem.download))
// the placement of data in memory revolves around the ARM entry point
mem.resetPC = arm.AlignTo16bits(mem.downloadOrigin + mem.header.entry)
mem.resetLR = mem.resetPC
mem.resetSP = mem.ccmMemtop - 3
// note the real entry point
logger.Logf(mem.env, "ACE", "actual entrypoint: %08x", mem.resetPC)
// define the Thumb-2 bytecode for a function whose only purpose is to jump
// back to where it came from bytecode is for instruction "BX LR" with a
// "true" value in R0
nullFunction := []byte{
0x01, 0x20, // MOV R1, #1 (the function returns true)
0x70, 0x47, // BX LR
}
// placing nullFunction at end of ARM program
nullFunctionAddress := mem.downloadMemtop + 1
// the code location of the null function must not be on a 16bit boundary
if arm.IsAlignedTo16bits(nullFunctionAddress) {
logger.Logf(mem.env, "ACE", "correcting alignment at end of ARM program")
mem.download = append(mem.download, 0x00)
mem.downloadMemtop++
nullFunctionAddress++
}
mem.download = append(mem.download, nullFunction...)
mem.downloadMemtop += uint32(len(nullFunction))
// although the code location of the null function is on a 16bit boundary
// (see above), the code is reached by interwork branching. we're using the
// Thumb-2 instruction set so this means that the zero bit of the address
// must be set to one
//
// interwork branching uses the BLX instruction. BLX ignores bit zero of the
// address. this means that the correct (aligned) address will be used when
// setting the program counter
nullFunctionAddress |= 0x01
logger.Logf(mem.env, "ACE", "null function place at %08x", nullFunctionAddress)
// choose size for the remainder of the flash memory and place at the flash
// origin value for architecture
const bufferOverhead = 64000
var buffserSize uint32
if len(data) < 128000-bufferOverhead {
buffserSize = 128000
} else if len(data) < 256000-bufferOverhead {
buffserSize = 256000
} else if len(data) < 512000-bufferOverhead {
buffserSize = 512000
} else {
buffserSize = bufferOverhead
}
mem.buffer = make([]byte, buffserSize)
mem.bufferOrigin = mem.model.FlashOrigin
mem.bufferMemtop = mem.bufferOrigin + uint32(len(mem.buffer)-1)
// set virtual argument. detailed information in the PlusCart firmware
// source:
//
// atari-2600-pluscart-master/source/STM32firmware/PlusCart/Src/cartridge_emulation_ACE.c
// ROM file argument
binary.LittleEndian.PutUint32(mem.buffer, mem.downloadOrigin)
// CCM memory argument
binary.LittleEndian.PutUint32(mem.buffer[4:], mem.ccmOrigin)
// addresses of func_reboot_into_cartridge() and emulate_firmware_cartridge()
// for our purposes, the function needs only to jump back to the link address
binary.LittleEndian.PutUint32(mem.buffer[8:], nullFunctionAddress)
binary.LittleEndian.PutUint32(mem.buffer[12:], nullFunctionAddress)
// system clock argument
clk := int(armPrefs.Clock.Get().(float64) * 1000000)
binary.LittleEndian.PutUint32(mem.buffer[16:], uint32(clk))
// ACE version number
aceVersion := 2
binary.LittleEndian.PutUint32(mem.buffer[20:], uint32(aceVersion))
// pluscart revision number
plusCartRevision := 3
binary.LittleEndian.PutUint32(mem.buffer[24:], uint32(plusCartRevision))
// GPIO addresses
binary.LittleEndian.PutUint32(mem.buffer[28:], ADDR_IDR)
binary.LittleEndian.PutUint32(mem.buffer[32:], DATA_IDR)
binary.LittleEndian.PutUint32(mem.buffer[36:], DATA_ODR)
binary.LittleEndian.PutUint32(mem.buffer[40:], DATA_MODER)
// end of argument indicator
copy(mem.buffer[44:48], []byte{0x00, 0x26, 0xe4, 0xac})
// GPIO pins
mem.gpio = make([]byte, GPIO_MEMTOP-GPIO_ORIGIN+1)
mem.gpioOrigin = GPIO_ORIGIN
mem.gpioMemtop = GPIO_MEMTOP
// default NOP instruction for opcode
mem.gpio[DATA_ODR-mem.gpioOrigin] = 0xea
return mem, nil
}
func (mem *aceMemory) Snapshot() *aceMemory {
m := *mem
m.gpio = make([]byte, len(mem.gpio))
copy(m.gpio, mem.gpio)
m.ccm = make([]byte, len(mem.ccm))
copy(m.ccm, mem.ccm)
m.download = make([]byte, len(mem.download))
copy(m.download, mem.download)
m.buffer = make([]byte, len(mem.buffer))
copy(m.buffer, mem.buffer)
return &m
}
// Plumb implements the mapper.CartMapper interface.
func (mem *aceMemory) Plumb(arm interruptARM) {
mem.arm = arm
}
// MapAddress implements the arm.SharedMemory interface.
func (mem *aceMemory) MapAddress(addr uint32, write bool) (*[]byte, uint32) {
switch addr {
case DATA_MODER:
return &mem.gpio, mem.gpioOrigin
case ADDR_IDR:
if !write {
mem.armInterruptCt++
mem.arm.Interrupt()
}
return &mem.gpio, mem.gpioOrigin
case DATA_IDR:
return &mem.gpio, mem.gpioOrigin
case DATA_ODR:
return &mem.gpio, mem.gpioOrigin
}
if addr >= mem.bufferOrigin && addr <= mem.bufferMemtop {
return &mem.buffer, mem.bufferOrigin
}
if addr >= mem.downloadOrigin && addr <= mem.downloadMemtop {
return &mem.download, mem.downloadOrigin
}
if addr >= mem.ccmOrigin && addr <= mem.ccmMemtop {
return &mem.ccm, mem.ccmOrigin
}
return nil, addr
}
// ResetVectors implements the arm.SharedMemory interface.
func (mem *aceMemory) ResetVectors() (uint32, uint32, uint32) {
return mem.resetSP, mem.resetLR, mem.resetPC
}
// IsExecutable implements the arm.SharedMemory interface.
func (mem *aceMemory) IsExecutable(addr uint32) bool {
return true
}
// returns a list of memory areas in the cartridge's static memory
func (a *aceMemory) Segments() []mapper.CartStaticSegment {
return []mapper.CartStaticSegment{
{
Name: "Download",
Origin: a.downloadOrigin,
Memtop: a.downloadMemtop,
},
{
Name: "Buffer",
Origin: a.bufferOrigin,
Memtop: a.bufferMemtop,
},
{
Name: "CCM",
Origin: a.ccmOrigin,
Memtop: a.ccmMemtop,
},
}
}
// returns a copy of the data in the named segment. the segment name should
// be taken from the Name field of one of the CartStaticSegment instances
// returned by the Segments() function
func (a *aceMemory) Reference(segment string) ([]uint8, bool) {
switch segment {
case "Download":
return a.download, true
case "Buffer":
return a.buffer, true
case "CCM":
return a.ccm, true
}
return []uint8{}, false
}
// read 8, 16 or 32 bit values from the address. the address should be in
// the range given in one of the CartStaticSegment returned by the
// Segments() function.
func (a *aceMemory) Read8bit(addr uint32) (uint8, bool) {
mem, origin := a.MapAddress(addr, false)
addr -= origin
if mem == nil || addr >= uint32(len(*mem)) {
return 0, false
}
return (*mem)[addr], true
}
func (a *aceMemory) Read16bit(addr uint32) (uint16, bool) {
mem, origin := a.MapAddress(addr, false)
addr -= origin
if mem == nil || addr >= uint32(len(*mem)-1) {
return 0, false
}
return uint16((*mem)[addr]) |
uint16((*mem)[addr+1])<<8, true
}
func (a *aceMemory) Read32bit(addr uint32) (uint32, bool) {
mem, origin := a.MapAddress(addr, false)
addr -= origin
if mem == nil || addr >= uint32(len(*mem)-3) {
return 0, false
}
return uint32((*mem)[addr]) |
uint32((*mem)[addr+1])<<8 |
uint32((*mem)[addr+2])<<16 |
uint32((*mem)[addr+3])<<24, true
}
// GetStatic implements the mapper.CartStaticBus interface.
func (cart *Ace) GetStatic() mapper.CartStatic {
return cart.mem.Snapshot()
}
// StaticWrite implements the mapper.CartStaticBus interface.
func (cart *Ace) PutStatic(segment string, idx int, data uint8) bool {
mem, ok := cart.mem.Reference(segment)
if !ok {
return false
}
if idx >= len(mem) {
return false
}
mem[idx] = data
return true
}