remove Poke() from cartMapper interface. replaced with an argument to

Write()

all cartridge types are now poke-able

all cartridge types are no patch-able

reworked error types/messages. replaced some errors with panics
This commit is contained in:
JetSetIlly 2020-06-11 13:14:29 +01:00
parent 4422f195e8
commit 39fd8381dd
22 changed files with 373 additions and 312 deletions

View file

@ -298,12 +298,14 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
}
}
case "STATIC":
// !TODO: poke/peek static cartridge static data areas
if db := dbg.VCS.Mem.Cart.GetDebugBus(); db != nil {
dbg.printInstrument(db.GetStatic())
} else {
dbg.printLine(terminal.StyleFeedback, "cartridge has no static data areas")
}
case "REGISTERS":
// !TODO: poke/peek cartridge registers
bus := dbg.VCS.Mem.Cart.GetDebugBus()
if bus != nil {
dbg.printInstrument(bus.GetRegisters())
@ -312,6 +314,8 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
}
case "RAM":
// cartridge RAM is accessible through the normal VCS buses so
// the normal peek/poke commands will work
bus := dbg.VCS.Mem.Cart.GetRAMbus()
if bus != nil {
s := &strings.Builder{}
@ -725,10 +729,14 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
// get address token
a, _ := tokens.Get()
// convert address
// convert address. note that the calls to dbgmem.poke() also call
// mapAddress(). the reason we map the address here is because we want
// a numeric address that we can iterate with in the for loop below.
// simply converting to a number is no good because we want the user to
// be able to specify an address by name, so we may as well just call
// mapAddress, even if it does seem redundant.
ai := dbg.dbgmem.mapAddress(a, false)
if ai == nil {
// using poke error because hexload is basically the same as poking
dbg.printLine(terminal.StyleError, errors.New(errors.UnpokeableAddress, a).Error())
return false, nil
}
@ -745,7 +753,6 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) (bool, error) {
continue // for loop (without advancing address)
}
// perform individual poke
ai, err := dbg.dbgmem.poke(addr, uint8(val))
if err != nil {
dbg.printLine(terminal.StyleError, "%s", err)

View file

@ -148,12 +148,10 @@ func (dbgmem memoryDebug) peek(address interface{}) (*addressInfo, error) {
return nil, errors.New(errors.DebuggerError, errors.New(errors.UnpeekableAddress, address))
}
ar, err := dbgmem.mem.GetArea(ai.area)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
}
area := dbgmem.mem.GetArea(ai.area)
ai.data, err = ar.Peek(ai.mappedAddress)
var err error
ai.data, err = area.Peek(ai.mappedAddress)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
}
@ -171,12 +169,9 @@ func (dbgmem memoryDebug) poke(address interface{}, data uint8) (*addressInfo, e
return nil, errors.New(errors.DebuggerError, errors.New(errors.UnpokeableAddress, address))
}
ar, err := dbgmem.mem.GetArea(ai.area)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
}
area := dbgmem.mem.GetArea(ai.area)
err = ar.Poke(ai.mappedAddress, data)
err := area.Poke(ai.mappedAddress, data)
if err != nil {
return nil, errors.New(errors.DebuggerError, err)
}

View file

@ -215,18 +215,6 @@ func parseTarget(dbg *Debugger, tokens *commandline.Tokens) (*target, error) {
},
}
case "BUS":
trg = &target{
label: "Bus Error",
currentValue: func() interface{} {
s := dbg.VCS.CPU.LastResult.BusError
if s == "" {
return "ok"
}
return s
},
}
default:
return nil, errors.New(errors.InvalidTarget, fmt.Sprintf("%s %s", keyword, subkey))
}

View file

@ -105,16 +105,15 @@ const (
CPUBug = "cpu bug: %v"
// memory
MemoryError = "memory error: %v"
UnpokeableAddress = "memory error: cannot poke address (%v)"
UnpeekableAddress = "memory error: cannot peek address (%v)"
BusError = "bus error: address %#04x"
MemoryBusError = "memory error: inaccessible address (%v)"
// cartridges
CartridgeError = "cartridge error: %v"
CartridgeEjected = "cartridge error: no cartridge attached"
UnpatchableCartType = "cartridge error: cannot patch this cartridge type (%v)"
CartridgeStaticOOB = "cartridge error: static data address to high (%#04x)"
CartridgeError = "cartridge error: %v"
CartridgeEjected = "cartridge error: no cartridge attached"
CartridgePatchOOB = "cartrdige error: patch offset too high (%#04x)"
CartridgeStaticOOB = "cartridge error: static data address too high (%#04x)"
// input
UnknownInputEvent = "input error: %v: unsupported event (%v)"

View file

@ -189,10 +189,10 @@ func (mc *CPU) read8Bit(address uint16) (uint8, error) {
val, err := mc.mem.Read(address)
if err != nil {
if !errors.Is(err, errors.BusError) {
if !errors.Is(err, errors.MemoryBusError) {
return 0, err
}
mc.LastResult.BusError = err.Error()
mc.LastResult.Error = err.Error()
}
// +1 cycle
@ -211,10 +211,10 @@ func (mc *CPU) read8BitZeroPage(address uint8) (uint8, error) {
val, err := mc.mem.ReadZeroPage(address)
if err != nil {
if !errors.Is(err, errors.BusError) {
if !errors.Is(err, errors.MemoryBusError) {
return 0, err
}
mc.LastResult.BusError = err.Error()
mc.LastResult.Error = err.Error()
}
// +1 cycle
@ -237,10 +237,10 @@ func (mc *CPU) write8Bit(address uint16, value uint8) error {
if err != nil {
// don't worry about unwritable addresses (unless strict addressing
// is on)
if !errors.Is(err, errors.BusError) {
if !errors.Is(err, errors.MemoryBusError) {
return err
}
mc.LastResult.BusError = err.Error()
mc.LastResult.Error = err.Error()
}
return nil
@ -252,10 +252,10 @@ func (mc *CPU) write8Bit(address uint16, value uint8) error {
func (mc *CPU) read16Bit(address uint16) (uint16, error) {
lo, err := mc.mem.Read(address)
if err != nil {
if !errors.Is(err, errors.BusError) {
if !errors.Is(err, errors.MemoryBusError) {
return 0, err
}
mc.LastResult.BusError = err.Error()
mc.LastResult.Error = err.Error()
}
// +1 cycle
@ -266,10 +266,10 @@ func (mc *CPU) read16Bit(address uint16) (uint16, error) {
hi, err := mc.mem.Read(address + 1)
if err != nil {
if !errors.Is(err, errors.BusError) {
if !errors.Is(err, errors.MemoryBusError) {
return 0, err
}
mc.LastResult.BusError = err.Error()
mc.LastResult.Error = err.Error()
}
// +1 cycle
@ -291,10 +291,10 @@ func (mc *CPU) read8BitPC(val *uint8, f func() error) error {
v, err := mc.mem.Read(mc.PC.Address())
if err != nil {
if !errors.Is(err, errors.BusError) {
if !errors.Is(err, errors.MemoryBusError) {
return err
}
mc.LastResult.BusError = err.Error()
mc.LastResult.Error = err.Error()
}
carry, _ := mc.PC.Add(1)
@ -601,19 +601,19 @@ func (mc *CPU) ExecuteInstruction(cycleCallback func() error) error {
lo, err := mc.mem.Read(indirectAddress)
if err != nil {
if !errors.Is(err, errors.BusError) {
if !errors.Is(err, errors.MemoryBusError) {
return err
}
mc.LastResult.BusError = err.Error()
mc.LastResult.Error = err.Error()
}
// +1 cycle
err = mc.endCycle()
if err != nil {
if !errors.Is(err, errors.BusError) {
if !errors.Is(err, errors.MemoryBusError) {
return err
}
mc.LastResult.BusError = err.Error()
mc.LastResult.Error = err.Error()
return err
}

View file

@ -66,7 +66,7 @@ func (mem *mockMem) Clear() {
func (mem mockMem) Read(address uint16) (uint8, error) {
if address&0xff00 == 0xff00 {
return 0, errors.New(errors.BusError, address)
return 0, errors.New(errors.MemoryBusError, address)
}
return mem.internal[address], nil
}
@ -77,7 +77,7 @@ func (mem mockMem) ReadZeroPage(address uint8) (uint8, error) {
func (mem *mockMem) Write(address uint16, data uint8) error {
if address&0xff00 == 0xff00 {
return errors.New(errors.BusError, address)
return errors.New(errors.MemoryBusError, address)
}
mem.internal[address] = data
return nil

View file

@ -66,8 +66,8 @@ type Result struct {
// whether a known buggy code path (in the emulated CPU) was triggered
CPUBug string
// whether the last memory access resulted in a bus error
BusError string
// error string. will be a memory access error
Error string
// whether this data has been finalised - some fields in this struct will
// be undefined if Final is false
@ -83,6 +83,6 @@ func (r *Result) Reset() {
r.ActualCycles = 0
r.PageFault = false
r.CPUBug = ""
r.BusError = ""
r.Error = ""
r.Final = false
}

View file

@ -33,27 +33,28 @@ type DebuggerBus interface {
Poke(address uint16, value uint8) error
}
// CartDebugBus defines the operations required for a debugger to access
// non-addressable areas of a cartridge. You have to know the precise cartridge
// mapper for PutRegister() to work effectively.
// CartDebugBus defines the operations required for a debugger to access the
// static and special function areas of a cartrudge.
//
// The mapper is allowed to panic if it is not interfaced with correctly.
//
// So what's the point of the interface if you need to know the details of the
// underlying type? Well, it goes some way to helping us understand what parts
// of the cartridge are beyond the scope of the regular buses.
// You should know the precise cartridge mapper for the CartRegister and
// CartStatic type to be usable.
//
// Primarily though, it is useful when used in conjunction with the lazy
// evaluation system used by GUI systems running in a different goroutine. The
// point of the lazy system is to prevent race conditions, the way we do that
// is to make a copy of the system variable before using it in the GUI. Now,
// because we must know the internals of the cartridge format, could we not
// just make those copies manually? Well we could, but it would mean another
// place where the cartridge internal knowledge needs to be coded (we need to
// use that knowledge in the GUI code but it would be nice to avoid it in the
// lazy system). The GetRegisters() allows us to conceptualise the copying
// process and to keep the details inside the cartridge implementation as much
// as possible.
// So what's the point of the interface if you need to know the details of the
// underlying type? Couldn't we just use a type assertion?
//
// Yes, but doing it this way helps with the lazy evaluation system used by
// debugging GUIs. The point of the lazy system is to prevent race conditions
// and the way we do that is to make copies of system variables before using it
// in the GUI. Now, because we must know the internals of the cartridge format,
// could we not just make those copies manually? Again, yes. But that would
// mean another place where the cartridge's internal knowledge needs to be
// coded (we need to use that knowledge in the GUI code but it would be nice to
// avoid it in the lazy system).
//
// The GetRegisters() allows us to conceptualise the copying process and to
// keep the details inside the cartridge implementation as much as possible.
type CartDebugBus interface {
// GetRegisters returns a copy of the cartridge's registers
GetRegisters() CartRegisters

View file

@ -65,16 +65,14 @@ func (cart *Cartridge) Peek(addr uint16) (uint8, error) {
return cart.Read(addr)
}
// Poke is an implementation of memory.DebuggerBus. This poke pokes the current
// cartridge bank. See Patch for a different method. Address must be
// normalised.
// Poke is an implementation of memory.DebuggerBus. Address must be normalised.
func (cart *Cartridge) Poke(addr uint16, data uint8) error {
return cart.mapper.Poke(addr^memorymap.OriginCart, data)
return cart.mapper.Write(addr^memorymap.OriginCart, data, true)
}
// Patch writes to cartridge memory. Offset is measured from the start of
// cartridge memory. It differs from Poke in that respect
func (cart *Cartridge) Patch(offset uint16, data uint8) error {
func (cart *Cartridge) Patch(offset int, data uint8) error {
return cart.mapper.Patch(offset, data)
}
@ -85,7 +83,7 @@ func (cart *Cartridge) Read(addr uint16) (uint8, error) {
// Write is an implementation of memory.CPUBus. Address must be normalised.
func (cart *Cartridge) Write(addr uint16, data uint8) error {
return cart.mapper.Write(addr^memorymap.OriginCart, data)
return cart.mapper.Write(addr^memorymap.OriginCart, data, false)
}
// Eject removes memory from cartridge space and unlike the real hardware,

View file

@ -32,8 +32,9 @@ type dpcPlus struct {
description string
// banks and the currently selected bank
banks [][]byte
bank int
bankSize int
banks [][]byte
bank int
registers DPCplusRegisters
static DPCplusStatic
@ -44,24 +45,36 @@ type dpcPlus struct {
// music fetchers are clocked at a fixed (slower) rate than the reference
// to the VCS's clock. see Step() function.
beats int
// patch help. offsets in the original data file for the different areas
// in the cartridge
//
// we only do this because of the complexity of the dpcPlus file and only
// for the purposes of the Patch() function. we don't bother with anything
// like this for the simpler cartridge formats
banksOffset int
dataOffset int
freqOffset int
fileSize int
}
// NewDPCplus is the preferred method of initialisation for the harmony type
func NewDPCplus(data []byte) (*dpcPlus, error) {
const armSize = 3072
const bankSize = 4096
const dataSize = 4096
const freqSize = 1024
cart := &dpcPlus{}
cart.mappingID = "DPC+"
cart.description = "DPC+ (Harmony)"
cart := &dpcPlus{
mappingID: "DPC+",
description: "DPC+ (Harmony)",
bankSize: 4096,
}
// amount of data used for cartridges
bankLen := len(data) - dataSize - armSize - freqSize
// size check
if bankLen%bankSize != 0 {
if bankLen%cart.bankSize != 0 {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: %d bytes not supported", cart.mappingID, len(data)))
}
@ -69,24 +82,30 @@ func NewDPCplus(data []byte) (*dpcPlus, error) {
cart.static.Arm = data[:armSize]
// allocate enough banks
cart.banks = make([][]uint8, bankLen/bankSize)
cart.banks = make([][]uint8, bankLen/cart.bankSize)
// partition data into banks
for k := 0; k < cart.NumBanks(); k++ {
cart.banks[k] = make([]uint8, bankSize)
offset := k * bankSize
cart.banks[k] = make([]uint8, cart.bankSize)
offset := k * cart.bankSize
offset += armSize
cart.banks[k] = data[offset : offset+bankSize]
cart.banks[k] = data[offset : offset+cart.bankSize]
}
// gfx and frequency table at end of file
s := armSize + (bankSize * cart.NumBanks())
cart.static.Data = data[s : s+dataSize]
cart.static.Freq = data[s+dataSize:]
dataOffset := armSize + (cart.bankSize * cart.NumBanks())
cart.static.Data = data[dataOffset : dataOffset+dataSize]
cart.static.Freq = data[dataOffset+dataSize:]
// initialise cartridge before returning success
cart.Initialise()
// patch offsets
cart.banksOffset = armSize
cart.dataOffset = dataOffset
cart.freqOffset = dataOffset + dataSize
cart.fileSize = len(data)
return cart, nil
}
@ -141,7 +160,7 @@ func (cart *dpcPlus) Read(addr uint16) (uint8, error) {
}
if addr > 0x0027 {
return 0, errors.New(errors.BusError, addr)
return 0, errors.New(errors.MemoryBusError, addr)
}
switch addr {
@ -260,7 +279,7 @@ func (cart *dpcPlus) Read(addr uint16) (uint8, error) {
return data, nil
}
func (cart *dpcPlus) Write(addr uint16, data uint8) error {
func (cart *dpcPlus) Write(addr uint16, data uint8, poke bool) error {
// if address is above register space then we only need to check for bank
// switching before returning data at the quoted address
if addr == 0x0ff6 {
@ -278,7 +297,7 @@ func (cart *dpcPlus) Write(addr uint16, data uint8) error {
}
if addr < 0x0028 || addr > 0x007f {
return errors.New(errors.BusError, addr)
return errors.New(errors.MemoryBusError, addr)
}
switch addr {
@ -548,7 +567,12 @@ func (cart *dpcPlus) Write(addr uint16, data uint8) error {
cart.registers.Fetcher[f].inc()
}
return nil
if poke {
cart.banks[cart.bank][addr] = data
return nil
}
return errors.New(errors.MemoryBusError, addr)
}
func (cart dpcPlus) NumBanks() int {
@ -572,12 +596,24 @@ func (cart *dpcPlus) RestoreState(state interface{}) error {
return nil
}
func (cart *dpcPlus) Poke(addr uint16, data uint8) error {
return errors.New(errors.UnpokeableAddress, addr)
}
func (cart *dpcPlus) Patch(offset int, data uint8) error {
if offset >= cart.fileSize {
return errors.New(errors.CartridgePatchOOB, offset)
}
func (cart *dpcPlus) Patch(addr uint16, data uint8) error {
return errors.New(errors.UnpatchableCartType, cart.description)
if offset >= cart.freqOffset {
cart.static.Freq[offset-cart.freqOffset] = data
} else if offset >= cart.dataOffset {
cart.static.Data[offset-cart.dataOffset] = data
} else if offset >= cart.banksOffset {
bank := int(offset) / cart.bankSize
offset = offset % cart.bankSize
cart.banks[bank][offset] = data
} else {
cart.static.Arm[offset-cart.banksOffset] = data
}
return nil
}
func (cart *dpcPlus) Listen(addr uint16, data uint8) {

View file

@ -27,23 +27,18 @@ type cartMapper interface {
Initialise()
ID() string
Read(addr uint16) (data uint8, err error)
Write(addr uint16, data uint8) error
Write(addr uint16, data uint8, poke bool) error
NumBanks() int
GetBank(addr uint16) (bank int)
SetBank(addr uint16, bank int) error
SaveState() interface{}
RestoreState(interface{}) error
// poke new value anywhere into currently selected bank of cartridge memory
// (including ROM).
Poke(addr uint16, data uint8) error
// cartMapper does not need a dedicated Peek() function. the Cartridge type
// implements Peek() and can just call the cartMapper's Read() function
// patch differs from poke in that it alters the data as though it was
// being read from disk
Patch(offset uint16, data uint8) error
// patch differs from write/poke in that it alters the data as though it
// was being read from disk. that is, the offset is measured from the start
// of the file. the cartmapper must translate the offset and update the
// correct data structure as appropriate.
Patch(offset int, data uint8) error
// see the commentary for the Listen() function in the Cartridge type for
// an explanation for what this does

View file

@ -72,11 +72,10 @@ type atari struct {
mappingID string
description string
bankSize int
// atari formats apart from 2k and 4k are divided into banks. 2k and 4k
// ROMs conceptually have one bank
banks [][]uint8
bankSize int
banks [][]uint8
// identifies the currently selected bank
bank int
@ -155,13 +154,19 @@ func (cart *atari) Read(addr uint16) (uint8, bool) {
}
// Write implements the cartMapper interface
func (cart *atari) Write(addr uint16, data uint8) bool {
func (cart *atari) Write(addr uint16, data uint8, poke bool) bool {
if cart.ram != nil {
if addr <= 127 {
cart.ram[addr] = data
return true
}
}
if poke {
cart.banks[cart.bank][addr] = data
return true
}
return false
}
@ -194,22 +199,20 @@ func (cart *atari) addSuperchip() bool {
return true
}
// Poke implements the cartMapper interface
func (cart *atari) Poke(addr uint16, data uint8) error {
cart.banks[cart.bank][addr] = data
return nil
}
// Patch implements the cartMapper interface
func (cart *atari) Patch(addr uint16, data uint8) error {
bank := int(addr) / cart.bankSize
addr = addr % uint16(cart.bankSize)
cart.banks[bank][addr] = data
func (cart *atari) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
}
bank := int(offset) / cart.bankSize
offset = offset % cart.bankSize
cart.banks[bank][offset] = data
return nil
}
// Listen implements the cartMapper interface
func (cart *atari) Listen(addr uint16, data uint8) {
func (cart *atari) Listen(_ uint16, _ uint8) {
}
// Step implements the cartMapper interface
@ -284,12 +287,12 @@ func (cart *atari4k) Read(addr uint16) (uint8, error) {
}
// Write implements the cartMapper interface
func (cart *atari4k) Write(addr uint16, data uint8) error {
if ok := cart.atari.Write(addr, data); ok {
func (cart *atari4k) Write(addr uint16, data uint8, poke bool) error {
if ok := cart.atari.Write(addr, data, poke); ok {
return nil
}
return errors.New(errors.BusError, addr)
return errors.New(errors.MemoryBusError, addr)
}
// atari2k is the half-size cartridge of 2048 bytes
@ -335,12 +338,12 @@ func (cart *atari2k) Read(addr uint16) (uint8, error) {
}
// Write implements the cartMapper interface
func (cart *atari2k) Write(addr uint16, data uint8) error {
if ok := cart.atari.Write(addr, data); ok {
func (cart *atari2k) Write(addr uint16, data uint8, poke bool) error {
if ok := cart.atari.Write(addr, data, poke); ok {
return nil
}
return errors.New(errors.BusError, addr)
return errors.New(errors.MemoryBusError, addr)
}
// atari8k (F8)
@ -396,17 +399,15 @@ func (cart *atari8k) Read(addr uint16) (uint8, error) {
}
// Write implements the cartMapper interface
func (cart *atari8k) Write(addr uint16, data uint8) error {
if ok := cart.atari.Write(addr, data); ok {
return nil
}
func (cart *atari8k) Write(addr uint16, data uint8, poke bool) error {
if addr == 0x0ff8 {
cart.bank = 0
} else if addr == 0x0ff9 {
cart.bank = 1
} else {
return errors.New(errors.BusError, addr)
}
if ok := cart.atari.Write(addr, data, poke); !ok {
return errors.New(errors.MemoryBusError, addr)
}
return nil
@ -470,11 +471,7 @@ func (cart *atari16k) Read(addr uint16) (uint8, error) {
}
// Write implements the cartMapper interface
func (cart *atari16k) Write(addr uint16, data uint8) error {
if ok := cart.atari.Write(addr, data); ok {
return nil
}
func (cart *atari16k) Write(addr uint16, data uint8, poke bool) error {
if addr == 0x0ff6 {
cart.bank = 0
} else if addr == 0x0ff7 {
@ -483,8 +480,10 @@ func (cart *atari16k) Write(addr uint16, data uint8) error {
cart.bank = 2
} else if addr == 0x0ff9 {
cart.bank = 3
} else {
return errors.New(errors.BusError, addr)
}
if ok := cart.atari.Write(addr, data, poke); !ok {
return errors.New(errors.MemoryBusError, addr)
}
return nil
@ -556,11 +555,7 @@ func (cart *atari32k) Read(addr uint16) (uint8, error) {
}
// Write implements the cartMapper interface
func (cart *atari32k) Write(addr uint16, data uint8) error {
if ok := cart.atari.Write(addr, data); ok {
return nil
}
func (cart *atari32k) Write(addr uint16, data uint8, poke bool) error {
if addr == 0x0ff4 {
cart.bank = 0
} else if addr == 0x0ff5 {
@ -577,8 +572,10 @@ func (cart *atari32k) Write(addr uint16, data uint8) error {
cart.bank = 6
} else if addr == 0x0ffb {
cart.bank = 7
} else {
return errors.New(errors.BusError, addr)
}
if ok := cart.atari.Write(addr, data, poke); !ok {
return errors.New(errors.MemoryBusError, addr)
}
return nil

View file

@ -31,7 +31,8 @@ type cbs struct {
description string
// cbs cartridges have 3 banks of 4096 bytes
banks [][]uint8
bankSize int
banks [][]uint8
// identifies the currently selected bank
bank int
@ -41,25 +42,24 @@ type cbs struct {
}
func newCBS(data []byte) (cartMapper, error) {
const bankSize = 4096
cart := &cbs{
description: "CBS",
mappingID: "FA",
bankSize: 4096,
ram: make([]uint8, 256),
}
cart := &cbs{}
cart.description = "CBS"
cart.mappingID = "FA"
cart.banks = make([][]uint8, cart.NumBanks())
if len(data) != bankSize*cart.NumBanks() {
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge file", cart.mappingID))
}
for k := 0; k < cart.NumBanks(); k++ {
cart.banks[k] = make([]uint8, bankSize)
offset := k * bankSize
copy(cart.banks[k], data[offset:offset+bankSize])
}
cart.banks = make([][]uint8, cart.NumBanks())
// 256 bytes of cartidge ram in all instances
cart.ram = make([]uint8, 256)
for k := 0; k < cart.NumBanks(); k++ {
cart.banks[k] = make([]uint8, cart.bankSize)
offset := k * cart.bankSize
copy(cart.banks[k], data[offset:offset+cart.bankSize])
}
cart.Initialise()
@ -103,7 +103,7 @@ func (cart *cbs) Read(addr uint16) (uint8, error) {
}
// Write implements the cartMapper interface
func (cart *cbs) Write(addr uint16, data uint8) error {
func (cart *cbs) Write(addr uint16, data uint8, poke bool) error {
if addr <= 0x00ff {
cart.ram[addr] = data
return nil
@ -115,11 +115,14 @@ func (cart *cbs) Write(addr uint16, data uint8) error {
cart.bank = 1
} else if addr == 0x0ffa {
cart.bank = 2
} else {
return errors.New(errors.BusError, addr)
}
return nil
if poke {
cart.banks[cart.bank][addr] = data
return nil
}
return errors.New(errors.MemoryBusError, addr)
}
// NumBanks implements the cartMapper interface
@ -154,18 +157,20 @@ func (cart *cbs) RestoreState(state interface{}) error {
return nil
}
// Poke implements the cartMapper interface
func (cart *cbs) Poke(addr uint16, data uint8) error {
return errors.New(errors.UnpokeableAddress, addr)
}
// Patch implements the cartMapper interface
func (cart *cbs) Patch(addr uint16, data uint8) error {
return errors.New(errors.UnpatchableCartType, cart.mappingID)
func (cart *cbs) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
}
bank := int(offset) / cart.bankSize
offset = offset % cart.bankSize
cart.banks[bank][offset] = data
return nil
}
// Listen implements the cartMapper interface
func (cart *cbs) Listen(addr uint16, data uint8) {
func (cart *cbs) Listen(_ uint16, _ uint8) {
}
// Step implements the cartMapper interface

View file

@ -33,12 +33,19 @@ type dpc struct {
description string
// banks and the currently selected bank
banks [][]byte
bank int
bankSize int
banks [][]byte
bank int
static DPCstatic
// DPC registers are directly accessible by the VCS but have a special
// meaning when written to and read. the DPCregisters type implements the
// functionality of these special addresses and a copy of the field is
// returned by the GetRegisters() function
registers DPCregisters
// dpc specific areas of the cartridge, not accessible by the normal VCS bus
static DPCstatic
// the OSC clock found in DPC cartridges runs at slower than the VCS itself
// to effectively emulate the slower clock therefore, we need to discount
// the excess steps. see the step() function for details
@ -126,7 +133,6 @@ func (df *DPCdataFetcher) clk() {
func (df *DPCdataFetcher) setFlag() {
// set flag register [col 6, ln 7-12]
if df.Low == df.Top {
df.Flag = true
} else if df.Low == df.Bottom {
@ -135,26 +141,28 @@ func (df *DPCdataFetcher) setFlag() {
}
func newDPC(data []byte) (cartMapper, error) {
const bankSize = 4096
const gfxSize = 2048
const staticSize = 2048
cart := &dpc{
mappingID: "DPC",
description: "DPC Pitfall2 style",
bankSize: 4096,
}
cart := &dpc{}
cart.mappingID = "DPC"
cart.description = "DPC Pitfall2 style"
cart.banks = make([][]uint8, cart.NumBanks())
if len(data) < bankSize*cart.NumBanks()+gfxSize {
if len(data) < cart.bankSize*cart.NumBanks()+staticSize {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge file", cart.mappingID))
}
for k := 0; k < cart.NumBanks(); k++ {
cart.banks[k] = make([]uint8, bankSize)
offset := k * bankSize
cart.banks[k] = data[offset : offset+bankSize]
cart.banks[k] = make([]uint8, cart.bankSize)
offset := k * cart.bankSize
cart.banks[k] = data[offset : offset+cart.bankSize]
}
gfxStart := cart.NumBanks() * bankSize
cart.static.Gfx = data[gfxStart : gfxStart+gfxSize]
staticStart := cart.NumBanks() * cart.bankSize
cart.static.Gfx = data[staticStart : staticStart+staticSize]
cart.Initialise()
@ -293,7 +301,7 @@ func (cart *dpc) Read(addr uint16) (uint8, error) {
}
// Write implements the cartMapper interface
func (cart *dpc) Write(addr uint16, data uint8) error {
func (cart *dpc) Write(addr uint16, data uint8, poke bool) error {
if addr == 0x0ff8 {
cart.bank = 0
} else if addr == 0x0ff9 {
@ -348,7 +356,12 @@ func (cart *dpc) Write(addr uint16, data uint8) error {
// other addresses are not write registers and are ignored
}
return nil
if poke {
cart.banks[cart.bank][addr] = data
return nil
}
return errors.New(errors.MemoryBusError, addr)
}
// NumBanks implements the cartMapper interface
@ -377,18 +390,25 @@ func (cart *dpc) RestoreState(state interface{}) error {
return nil
}
// Poke implements the cartMapper interface
func (cart *dpc) Poke(addr uint16, data uint8) error {
return errors.New(errors.UnpokeableAddress, addr)
}
// Patch implements the cartMapper interface
func (cart *dpc) Patch(addr uint16, data uint8) error {
return errors.New(errors.UnpatchableCartType, cart.description)
func (cart *dpc) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks)+len(cart.static.Gfx) {
return errors.New(errors.CartridgePatchOOB, offset)
}
staticStart := cart.NumBanks() * cart.bankSize
if staticStart >= staticStart {
cart.static.Gfx[offset] = data
} else {
bank := int(offset) / cart.bankSize
offset = offset % cart.bankSize
cart.banks[bank][offset] = data
}
return nil
}
// Listen implements the cartMapper interface
func (cart *dpc) Listen(addr uint16, data uint8) {
func (cart *dpc) Listen(_ uint16, _ uint8) {
}
// Step implements the cartMapper interface

View file

@ -20,8 +20,6 @@
package cartridge
import (
"fmt"
"github.com/jetsetilly/gopher2600/errors"
)
@ -56,12 +54,12 @@ func (cart *ejected) Initialise() {
}
// Read implements the cartMapper interface
func (cart *ejected) Read(addr uint16) (uint8, error) {
func (cart *ejected) Read(_ uint16) (uint8, error) {
return 0, errors.New(errors.CartridgeEjected)
}
// Write implements the cartMapper interface
func (cart *ejected) Write(addr uint16, data uint8) error {
func (cart *ejected) Write(_ uint16, _ uint8, _ bool) error {
return errors.New(errors.CartridgeEjected)
}
@ -71,12 +69,12 @@ func (cart ejected) NumBanks() int {
}
// SetBank implements the cartMapper interface
func (cart *ejected) SetBank(addr uint16, bank int) error {
return errors.New(errors.CartridgeError, fmt.Sprintf("ejected cartridge"))
func (cart *ejected) SetBank(_ uint16, _ int) error {
return errors.New(errors.CartridgeEjected)
}
// GetBank implements the cartMapper interface
func (cart ejected) GetBank(addr uint16) int {
func (cart ejected) GetBank(_ uint16) int {
return 0
}
@ -86,22 +84,17 @@ func (cart *ejected) SaveState() interface{} {
}
// RestoreState implements the cartMapper interface
func (cart *ejected) RestoreState(state interface{}) error {
func (cart *ejected) RestoreState(_ interface{}) error {
return nil
}
// Poke implements the cartMapper interface
func (cart *ejected) Poke(addr uint16, data uint8) error {
return errors.New(errors.UnpokeableAddress, addr)
}
// Patch implements the cartMapper interface
func (cart *ejected) Patch(addr uint16, data uint8) error {
return errors.New(errors.UnpatchableCartType, cart.description)
func (cart *ejected) Patch(_ int, _ uint8) error {
return errors.New(errors.CartridgeEjected)
}
// Listen implements the cartMapper interface
func (cart *ejected) Listen(addr uint16, data uint8) {
func (cart *ejected) Listen(_ uint16, _ uint8) {
}
// Step implements the cartMapper interface

View file

@ -86,6 +86,8 @@ type mnetwork struct {
mappingID string
description string
bankSize int
banks [][]uint8
bank int
@ -104,21 +106,22 @@ type mnetwork struct {
}
func newMnetwork(data []byte) (cartMapper, error) {
const bankSize = 2048
cart := &mnetwork{
description: "m-network",
mappingID: "E7",
bankSize: 2048,
}
cart := &mnetwork{}
cart.description = "m-network"
cart.mappingID = "E7"
cart.banks = make([][]uint8, cart.NumBanks())
if len(data) != bankSize*cart.NumBanks() {
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge file", cart.mappingID))
}
for k := 0; k < cart.NumBanks(); k++ {
cart.banks[k] = make([]uint8, bankSize)
offset := k * bankSize
copy(cart.banks[k], data[offset:offset+bankSize])
cart.banks[k] = make([]uint8, cart.bankSize)
offset := k * cart.bankSize
copy(cart.banks[k], data[offset:offset+cart.bankSize])
}
// not all m-network cartridges have any RAM but we'll allocate it for all
@ -186,14 +189,14 @@ func (cart *mnetwork) Read(addr uint16) (uint8, error) {
cart.bankSwitchOnAccess(addr)
}
} else {
return 0, errors.New(errors.BusError, addr)
return 0, errors.New(errors.MemoryBusError, addr)
}
return data, nil
}
// Write implements the cartMapper interface
func (cart *mnetwork) Write(addr uint16, data uint8) error {
func (cart *mnetwork) Write(addr uint16, data uint8, poke bool) error {
if addr >= 0x0000 && addr <= 0x07ff {
if addr <= 0x03ff && cart.bank == 7 {
cart.ram1k[addr&0x03ff] = data
@ -206,7 +209,12 @@ func (cart *mnetwork) Write(addr uint16, data uint8) error {
return nil
}
return errors.New(errors.BusError, addr)
if poke {
cart.banks[cart.bank][addr] = data
return nil
}
return errors.New(errors.MemoryBusError, addr)
}
func (cart *mnetwork) bankSwitchOnAccess(addr uint16) bool {
@ -311,18 +319,20 @@ func (cart *mnetwork) RestoreState(state interface{}) error {
return nil
}
// Poke implements the cartMapper interface
func (cart *mnetwork) Poke(addr uint16, data uint8) error {
return errors.New(errors.UnpokeableAddress, addr)
}
// Patch implements the cartMapper interface
func (cart *mnetwork) Patch(addr uint16, data uint8) error {
return errors.New(errors.UnpatchableCartType, cart.mappingID)
func (cart *mnetwork) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
}
bank := int(offset) / cart.bankSize
offset = offset % cart.bankSize
cart.banks[bank][offset] = data
return nil
}
// Listen implements the cartMapper interface
func (cart *mnetwork) Listen(addr uint16, data uint8) {
func (cart *mnetwork) Listen(_ uint16, _ uint8) {
}
// Step implements the cartMapper interface

View file

@ -62,7 +62,8 @@ type parkerBros struct {
mappingID string
description string
banks [][]uint8
bankSize int
banks [][]uint8
// parker bros. cartridges divide memory into 4 segments
// o the last segment always points to the last bank
@ -74,21 +75,22 @@ type parkerBros struct {
}
func newparkerBros(data []byte) (cartMapper, error) {
const bankSize = 1024
cart := &parkerBros{
description: "parker bros",
mappingID: "E0",
bankSize: 1024,
}
cart := &parkerBros{}
cart.description = "parker bros"
cart.mappingID = "E0"
cart.banks = make([][]uint8, cart.NumBanks())
if len(data) != bankSize*cart.NumBanks() {
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number of bytes in the cartridge file", cart.mappingID))
}
for k := 0; k < cart.NumBanks(); k++ {
cart.banks[k] = make([]uint8, bankSize)
offset := k * bankSize
copy(cart.banks[k], data[offset:offset+bankSize])
cart.banks[k] = make([]uint8, cart.bankSize)
offset := k * cart.bankSize
copy(cart.banks[k], data[offset:offset+cart.bankSize])
}
cart.Initialise()
@ -130,11 +132,24 @@ func (cart *parkerBros) Read(addr uint16) (uint8, error) {
}
// Write implements the cartMapper interface
func (cart *parkerBros) Write(addr uint16, data uint8) error {
func (cart *parkerBros) Write(addr uint16, data uint8, poke bool) error {
if cart.bankSwitchOnAccess(addr) {
return nil
}
return errors.New(errors.BusError, addr)
if poke {
if addr >= 0x0000 && addr <= 0x03ff {
cart.banks[cart.segment[0]][addr&0x3dd] = data
} else if addr >= 0x0400 && addr <= 0x07ff {
cart.banks[cart.segment[1]][addr&0x3dd] = data
} else if addr >= 0x0800 && addr <= 0x0bff {
cart.banks[cart.segment[2]][addr&0x3dd] = data
} else if addr >= 0x0c00 && addr <= 0x0fff {
cart.banks[cart.segment[3]][addr&0x3dd] = data
}
}
return errors.New(errors.MemoryBusError, addr)
}
func (cart *parkerBros) bankSwitchOnAccess(addr uint16) bool {
@ -247,18 +262,20 @@ func (cart *parkerBros) RestoreState(state interface{}) error {
return nil
}
// Poke implements the cartMapper interface
func (cart *parkerBros) Poke(addr uint16, data uint8) error {
return errors.New(errors.UnpokeableAddress, addr)
}
// Patch implements the cartMapper interface
func (cart *parkerBros) Patch(addr uint16, data uint8) error {
return errors.New(errors.UnpatchableCartType, cart.mappingID)
func (cart *parkerBros) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
}
bank := int(offset) / cart.bankSize
offset = offset % cart.bankSize
cart.banks[bank][offset] = data
return nil
}
// Listen implements the cartMapper interface
func (cart *parkerBros) Listen(addr uint16, data uint8) {
func (cart *parkerBros) Listen(_ uint16, _ uint8) {
}
// Step implements the cartMapper interface

View file

@ -60,7 +60,8 @@ type tigervision struct {
mappingID string
description string
banks [][]uint8
bankSize int
banks [][]uint8
// tigervision cartridges divide memory into two 2k segments
// o the last segment always points to the last bank
@ -74,27 +75,27 @@ type tigervision struct {
// should work with any size cartridge that is a multiple of 2048
// - tested with 8k (Miner2049 etc.) and 32k (Genesis_Egypt demo)
func newTigervision(data []byte) (cartMapper, error) {
const bankSize = 2048
cart := &tigervision{
description: "tigervision",
mappingID: "3F",
bankSize: 2048,
}
if len(data)%bankSize != 0 {
if len(data)%cart.bankSize != 0 {
return nil, errors.New(errors.CartridgeError, "tigervision (3F): cartridge size must be multiple of 2048")
}
numBanks := len(data) / bankSize
cart := &tigervision{}
cart.description = "tigervision"
cart.mappingID = "3F"
numBanks := len(data) / cart.bankSize
cart.banks = make([][]uint8, numBanks)
if len(data) != bankSize*numBanks {
if len(data) != cart.bankSize*numBanks {
return nil, errors.New(errors.CartridgeError, fmt.Sprintf("%s: wrong number bytes in the cartridge file", cart.mappingID))
}
for k := 0; k < numBanks; k++ {
cart.banks[k] = make([]uint8, bankSize)
offset := k * bankSize
copy(cart.banks[k], data[offset:offset+bankSize])
cart.banks[k] = make([]uint8, cart.bankSize)
offset := k * cart.bankSize
copy(cart.banks[k], data[offset:offset+cart.bankSize])
}
cart.Initialise()
@ -131,8 +132,15 @@ func (cart *tigervision) Read(addr uint16) (uint8, error) {
}
// Write implements the cartMapper interface
func (cart *tigervision) Write(addr uint16, data uint8) error {
return errors.New(errors.BusError, addr)
func (cart *tigervision) Write(addr uint16, data uint8, poke bool) error {
if poke {
if addr >= 0x0000 && addr <= 0x07ff {
cart.banks[cart.segment[0]][addr&0x07ff] = data
} else if addr >= 0x0800 && addr <= 0x0fff {
cart.banks[cart.segment[1]][addr&0x07ff] = data
}
}
return errors.New(errors.MemoryBusError, addr)
}
// NumBanks implements the cartMapper interface
@ -172,14 +180,16 @@ func (cart *tigervision) RestoreState(state interface{}) error {
return nil
}
// Poke implements the cartMapper interface
func (cart *tigervision) Poke(addr uint16, data uint8) error {
return errors.New(errors.UnpokeableAddress, addr)
}
// Patch implements the cartMapper interface
func (cart *tigervision) Patch(addr uint16, data uint8) error {
return errors.New(errors.UnpatchableCartType, cart.mappingID)
func (cart *tigervision) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return errors.New(errors.CartridgePatchOOB, offset)
}
bank := int(offset) / cart.bankSize
offset = offset % cart.bankSize
cart.banks[bank][offset] = data
return nil
}
// Listen implements the cartMapper interface

View file

@ -108,7 +108,7 @@ func (area *ChipMemory) Read(address uint16) (uint8, error) {
// do not allow reads from memory that do not have symbol name
if _, ok := addresses.CanonicalReadSymbols[address]; !ok {
return 0, errors.New(errors.BusError, address)
return 0, errors.New(errors.MemoryBusError, address)
}
return area.memory[address^area.origin], nil
@ -116,14 +116,15 @@ func (area *ChipMemory) Read(address uint16) (uint8, error) {
// Write is an implementation of memory.CPUBus. Address must be normalised.
func (area *ChipMemory) Write(address uint16, data uint8) error {
// check that the last write to this memory area has been serviced
// check that the last write to this memory area has been serviced. this
// shouldn't ever happen.
if area.writeSignal {
return errors.New(errors.MemoryError, fmt.Sprintf("unserviced write to chip memory (%s)", addresses.Write[area.writeAddress]))
panic(fmt.Sprintf("unserviced write to chip memory (%s)", addresses.Write[area.writeAddress]))
}
// do not allow writes to memory that do not have symbol name
if _, ok := addresses.CanonicalWriteSymbols[address]; !ok {
return errors.New(errors.BusError, address)
return errors.New(errors.MemoryBusError, address)
}
// signal the chips that their chip memory has been written to

View file

@ -22,7 +22,6 @@ package memory
import (
"math/rand"
"github.com/jetsetilly/gopher2600/errors"
"github.com/jetsetilly/gopher2600/hardware/memory/addresses"
"github.com/jetsetilly/gopher2600/hardware/memory/bus"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge"
@ -83,10 +82,6 @@ func NewVCSMemory() (*VCSMemory, error) {
mem.RAM = newRAM()
mem.Cart = cartridge.NewCartridge()
if mem.RIOT == nil || mem.TIA == nil || mem.RAM == nil || mem.Cart == nil {
return nil, errors.New(errors.MemoryError, "cannot create memory areas")
}
// create the memory map by associating all addresses in each memory area
// with that area
for i := memorymap.OriginTIA; i <= memorymap.MemtopTIA; i++ {
@ -109,19 +104,19 @@ func NewVCSMemory() (*VCSMemory, error) {
}
// GetArea returns the actual memory of the specified area type
func (mem *VCSMemory) GetArea(area memorymap.Area) (bus.DebuggerBus, error) {
func (mem *VCSMemory) GetArea(area memorymap.Area) bus.DebuggerBus {
switch area {
case memorymap.TIA:
return mem.TIA, nil
return mem.TIA
case memorymap.RAM:
return mem.RAM, nil
return mem.RAM
case memorymap.RIOT:
return mem.RIOT, nil
return mem.RIOT
case memorymap.Cartridge:
return mem.Cart, nil
return mem.Cart
}
return nil, errors.New(errors.MemoryError, "area not mapped correctly")
panic("memory areas are not mapped correctly")
}
// read maps an address to the normalised for all memory areas.
@ -129,10 +124,7 @@ func (mem *VCSMemory) read(address uint16, zeroPage bool) (uint8, error) {
// optimisation: called a lot. pointer to VCSMemory to prevent duffcopy
ma, ar := memorymap.MapAddress(address, true)
area, err := mem.GetArea(ar)
if err != nil {
return 0, err
}
area := mem.GetArea(ar)
data, err := area.(bus.CPUBus).Read(ma)
@ -187,10 +179,7 @@ func (mem *VCSMemory) ReadZeroPage(address uint8) (uint8, error) {
// processed by the correct memory areas.
func (mem *VCSMemory) Write(address uint16, data uint8) error {
ma, ar := memorymap.MapAddress(address, false)
area, err := mem.GetArea(ar)
if err != nil {
return err
}
area := mem.GetArea(ar)
mem.LastAccessAddress = ma
mem.LastAccessWrite = true

View file

@ -42,12 +42,12 @@
// Rules:
//
// 1. Lines beginning with a hyphen or white space are ignored
// 2. Addresses and values are expressed in hex (case-insensitive)
// 3. Values and addresses are separated by a colon
// 4. Multiple values on a line are poked into consecutive addresses, starting
// from the address value
// 2. Offset and values are expressed in hex (case-insensitive)
// 3. Values and offsets are separated by a colon
// 4. Multiple values on a line are poked into consecutive offsets, starting
// from the offset value
//
// Note that addresses are expressed with origin zero and have no relationship
// Note that offsets are expressed with origin zero and have no relationship
// to how memory is mapped inside the VCS. Imagine that the patches are being
// applied to the cartridge file image. The cartridge mapper handles the VCS
// memory side of things.

View file

@ -86,7 +86,7 @@ func CartridgeMemory(mem *cartridge.Cartridge, patchFile string) (bool, error) {
pokeLine := strings.Split(lines[i], pokeLineSeparator)
// ignore any lines that don't match the required [address: values...] format
// ignore any lines that don't match the required [offset: values...] format
if len(pokeLine) != 2 {
continue // for loop
}
@ -95,8 +95,8 @@ func CartridgeMemory(mem *cartridge.Cartridge, patchFile string) (bool, error) {
pokeLine[0] = strings.TrimSpace(pokeLine[0])
pokeLine[1] = strings.TrimSpace(pokeLine[1])
// parse address
address, err := strconv.ParseInt(pokeLine[0], 16, 16)
// parse offset
offset, err := strconv.ParseInt(pokeLine[0], 16, 16)
if err != nil {
continue // for loop
}
@ -120,14 +120,14 @@ func CartridgeMemory(mem *cartridge.Cartridge, patchFile string) (bool, error) {
}
// patch memory
err = mem.Patch(uint16(address), uint8(v))
err = mem.Patch(int(offset), uint8(v))
if err != nil {
return patched, errors.New(errors.PatchError, err)
}
patched = true
// advance address
address++
// advance offset
offset++
}
}