Gopher2600/hardware/memory/vcs.go
steve 35e31cccb7 o errors
- refined error package
2020-01-05 18:58:28 +00:00

141 lines
3.8 KiB
Go

package memory
import (
"fmt"
"gopher2600/errors"
"gopher2600/hardware/memory/vcssymbols"
)
// VCSMemory presents a monolithic representation of system memory to the CPU -
// the CPU only ever access memory through an instance of this structure.
// Other parts of the system access ChipMemory directly
type VCSMemory struct {
CPUBus
// memmap is a hash for every address in the VCS address space, returning
// one of the four memory areas
memmap map[uint16]Area
// the four memory areas
RIOT *ChipMemory
TIA *ChipMemory
PIA *PIA
Cart *Cartridge
}
// TODO: allow reading only when 02 clock is high and writing when it is low
// NewVCSMemory is the preferred method of initialisation for VCSMemory
func NewVCSMemory() (*VCSMemory, error) {
mem := new(VCSMemory)
mem.memmap = make(map[uint16]Area)
mem.RIOT = newRIOT()
mem.TIA = newTIA()
mem.PIA = newPIA()
mem.Cart = newCart()
if mem.RIOT == nil || mem.TIA == nil || mem.PIA == nil || mem.Cart == nil {
return nil, fmt.Errorf("error creating memory areas")
}
// create the memory map; each address in the memory map points to the
// memory area it resides in. we only record 'primary' addresses; all
// addresses should be passed through the MapAddress() function in order
// to iron out any mirrors
for i := mem.TIA.origin; i <= mem.TIA.memtop; i++ {
mem.memmap[i] = mem.TIA
}
for i := mem.PIA.origin; i <= mem.PIA.memtop; i++ {
mem.memmap[i] = mem.PIA
}
for i := mem.RIOT.origin; i <= mem.RIOT.memtop; i++ {
mem.memmap[i] = mem.RIOT
}
for i := mem.Cart.origin; i <= mem.Cart.memtop; i++ {
mem.memmap[i] = mem.Cart
}
return mem, nil
}
func (mem VCSMemory) String() string {
return mem.MemoryMap()
}
// MapAddress translates the quoted address from mirror space to primary space.
// Generally, all access to the different memory areas should be passed through
// this function. Any other information about an address can be accessed
// through mem.memmap[mappedAddress]
func (mem VCSMemory) MapAddress(address uint16) uint16 {
// note that the order of these filters is important
// cartridge addresses
if address&mem.Cart.origin == mem.Cart.origin {
return address & mem.Cart.memtop
}
// RIOT addresses
if address&mem.RIOT.origin == mem.RIOT.origin {
return address & mem.RIOT.memtop
}
// PIA RAM addresses
if address&mem.PIA.origin == mem.PIA.origin {
return address & mem.PIA.memtop
}
// everything else is in TIA space
return address & mem.TIA.memtop
}
// Implementation of CPUBus.Read
func (mem VCSMemory) Read(address uint16) (uint8, error) {
ma := mem.MapAddress(address)
area, present := mem.memmap[ma]
if !present {
panic(fmt.Errorf("%04x not mapped correctly", address))
}
return area.(CPUBus).Read(ma)
}
// Implementation of CPUBus.Write
func (mem *VCSMemory) Write(address uint16, data uint8) error {
ma := mem.MapAddress(address)
area, present := mem.memmap[ma]
if !present {
return fmt.Errorf("%04x not mapped correctly", address)
}
return area.(CPUBus).Write(ma, data)
}
// Peek returns the contents of the memory address, without triggering any side
// effects. returns:
// o value
// o mapped address
// o area name
// o address label
// o error
func (mem VCSMemory) Peek(address interface{}) (uint8, uint16, string, string, error) {
var ma uint16
switch address := address.(type) {
case uint16:
ma = mem.MapAddress(uint16(address))
case string:
// search for symbolic address in standard vcs read symbols
// TODO: peeking of cartridge specific symbols
for a, sym := range vcssymbols.ReadSymbols {
if sym == address {
ma = a
}
}
default:
return 0, 0, "", "", errors.NewGopherError(errors.UnrecognisedAddress, address)
}
area, present := mem.memmap[ma]
if !present {
panic(fmt.Errorf("%04x not mapped correctly", address))
}
return area.(Area).Peek(ma)
}