From c59a4a2dde83c7e1de014945d967c0e6cdfe77c7 Mon Sep 17 00:00:00 2001 From: steve Date: Mon, 2 Sep 2019 22:43:18 +0100 Subject: [PATCH] o debugger - RAM command now has CART option. displays any additional RAM the cartridge may have o cartridge - implemented RAM() command. returns copy of RAM array - save/restore banks functions are now save/restore state and deal with cartridge RAM in addition to bank information o debugger/memory - better error messages for peek and poke --- debugger/commands.go | 27 ++++++++++----- debugger/help.go | 1 - debugger/memory.go | 4 +-- debugger/prompt.go | 2 +- disassembly/disassembly.go | 8 +++-- disassembly/flow.go | 14 ++++---- disassembly/linear.go | 2 +- errors/categories.go | 1 + errors/messages.go | 1 + hardware/memory/cartridge.go | 44 +++++++++++++++---------- hardware/memory/cartridge_atari.go | 20 +++++++---- hardware/memory/cartridge_ejected.go | 12 ++++--- hardware/memory/cartridge_parkerbros.go | 12 ++++--- 13 files changed, 94 insertions(+), 54 deletions(-) diff --git a/debugger/commands.go b/debugger/commands.go index dc75d693..f9fde85c 100644 --- a/debugger/commands.go +++ b/debugger/commands.go @@ -25,7 +25,6 @@ const ( cmdCPU = "CPU" cmdCartridge = "CARTRIDGE" cmdClear = "CLEAR" - cmdClocks = "CLOCKS" cmdDebuggerState = "DEBUGGERSTATE" cmdDigest = "DIGEST" cmdDisassembly = "DISASSEMBLY" @@ -72,7 +71,6 @@ var commandTemplate = []string{ cmdCPU + " (SET [PC|A|X|Y|SP] [%N])", cmdCartridge + " (ANALYSIS)", cmdClear + " [BREAKS|TRAPS|WATCHES|ALL]", - cmdClocks, cmdDebuggerState, cmdDigest + " (RESET)", cmdDisassembly, @@ -90,10 +88,10 @@ var commandTemplate = []string{ cmdPeek + " [%S] {%S}", cmdPlayer + " (0|1)", cmdPlayfield, - cmdPoke + " [%N|%S] %N", + cmdPoke + " [%S] %N", cmdQuit, cmdExit, - cmdRAM, + cmdRAM + " (CART)", cmdRIOT + " (TIMER)", cmdReset, cmdRun, @@ -108,7 +106,7 @@ var commandTemplate = []string{ cmdTrap + " [%S] {%S}", cmdVerbose, cmdVerbosity, - cmdWatch + " (READ|WRITE) %N (%N)", + cmdWatch + " (READ|WRITE) [%S] (%S)", } // list of commands that should not be executed when recording/playing scripts @@ -808,10 +806,23 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) } case cmdRAM: - dbg.printMachineInfo(dbg.vcs.Mem.PIA) + option, present := tokens.Get() + if present { + option = strings.ToUpper(option) + switch option { + case "CART": + cartRAM := dbg.vcs.Mem.Cart.RAM() + if len(cartRAM) > 0 { + // !!TODO: better presentation of cartridge RAM + dbg.print(console.StyleMachineInfo, fmt.Sprintf("%v", dbg.vcs.Mem.Cart.RAM())) + } else { + dbg.print(console.StyleFeedback, "cartridge does not contain any additional RAM") + } - case cmdClocks: - dbg.print(console.StyleMachineInfo, "not implemented yet") + } + } else { + dbg.printMachineInfo(dbg.vcs.Mem.PIA) + } case cmdRIOT: option, present := tokens.Get() diff --git a/debugger/help.go b/debugger/help.go index 77755d15..249b7a6c 100644 --- a/debugger/help.go +++ b/debugger/help.go @@ -7,7 +7,6 @@ var Help = map[string]string{ cmdCPU: "Display the current state of the CPU", cmdCartridge: "Display information about the current cartridge", cmdClear: "Clear all entries in BREAKS and TRAPS", - cmdClocks: "The current state of the VCS clocks", cmdDebuggerState: "Display summary of debugger options", cmdDigest: "Return the cryptographic hash of the current screen", cmdDisassembly: "Print the full cartridge disassembly", diff --git a/debugger/memory.go b/debugger/memory.go index d77f3bb7..2a62f8a3 100644 --- a/debugger/memory.go +++ b/debugger/memory.go @@ -127,7 +127,7 @@ func (mem memoryDebug) mapAddress(address interface{}, cpuRead bool) *addressInf func (mem memoryDebug) peek(address interface{}) (*addressInfo, error) { ai := mem.mapAddress(address, true) if ai == nil { - return nil, errors.NewFormattedError(errors.MemoryError, fmt.Sprintf("%#04x not mapped correctly", address)) + return nil, errors.NewFormattedError(errors.UnpeekableAddress, address) } value, err := ai.area.Peek(ai.mappedAddress) @@ -141,7 +141,7 @@ func (mem memoryDebug) peek(address interface{}) (*addressInfo, error) { func (mem memoryDebug) poke(address interface{}, value uint8) (*addressInfo, error) { ai := mem.mapAddress(address, true) if ai == nil { - return nil, errors.NewFormattedError(errors.MemoryError, fmt.Sprintf("%#04x not mapped correctly", address)) + return nil, errors.NewFormattedError(errors.UnpokeableAddress, address) } ai.value = value ai.valueSeen = true diff --git a/debugger/prompt.go b/debugger/prompt.go index 35dddb4c..95d0fbf7 100644 --- a/debugger/prompt.go +++ b/debugger/prompt.go @@ -21,7 +21,7 @@ func (dbg *Debugger) buildPrompt(videoCycle bool) console.Prompt { promptAddress = dbg.lastResult.Address } - promptBank = dbg.vcs.Mem.Cart.GetAddressBank(promptAddress) + promptBank = dbg.vcs.Mem.Cart.GetBank(promptAddress) prompt := strings.Builder{} prompt.WriteString("[") diff --git a/disassembly/disassembly.go b/disassembly/disassembly.go index 8c9bb15e..d55a5d44 100644 --- a/disassembly/disassembly.go +++ b/disassembly/disassembly.go @@ -92,9 +92,13 @@ func (dsm *Disassembly) FromMemory(cart *memory.Cartridge, symtable *symbols.Tab dsm.flow = make([]bank, dsm.Cart.NumBanks()) dsm.linear = make([]bank, dsm.Cart.NumBanks()) - state := dsm.Cart.SaveBanks() - defer dsm.Cart.RestoreBanks(state) + // save cartridge state and defer at end of disassembly. this is necessary + // because during the disassembly process we may changed mutable parts of + // the cartridge (eg. extra RAM) + state := dsm.Cart.SaveState() + defer dsm.Cart.RestoreState(state) + // put cart into its initial state dsm.Cart.Initialise() // create new memory diff --git a/disassembly/flow.go b/disassembly/flow.go index e370a163..c576196d 100644 --- a/disassembly/flow.go +++ b/disassembly/flow.go @@ -52,7 +52,7 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error { return err } - bank := dsm.Cart.GetAddressBank(r.Address) + bank := dsm.Cart.GetBank(r.Address) // if we've seen this before then finish the disassembly if dsm.flow[bank][r.Address&disasmMask].IsInstruction() { @@ -74,7 +74,7 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error { if r.Defn.AddressingMode == definitions.Indirect { if r.InstructionData.(uint16) > dsm.Cart.Origin() { // note current location - state := dsm.Cart.SaveBanks() + state := dsm.Cart.SaveState() retPC := mc.PC.ToUint16() // adjust program counter @@ -87,7 +87,7 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error { } // resume from where we left off - dsm.Cart.RestoreBanks(state) + dsm.Cart.RestoreState(state) mc.PC.Load(retPC) } else { // it's entirely possible for the program to jump @@ -105,7 +105,7 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error { // absolute JMP addressing // note current location - state := dsm.Cart.SaveBanks() + state := dsm.Cart.SaveState() retPC := mc.PC.ToUint16() // adjust program counter @@ -118,14 +118,14 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error { } // resume from where we left off - dsm.Cart.RestoreBanks(state) + dsm.Cart.RestoreState(state) mc.PC.Load(retPC) } } else { // branch instructions // note current location - state := dsm.Cart.SaveBanks() + state := dsm.Cart.SaveState() retPC := mc.PC.ToUint16() // sign extend address and add to program counter @@ -142,7 +142,7 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error { } // resume from where we left off - dsm.Cart.RestoreBanks(state) + dsm.Cart.RestoreState(state) mc.PC.Load(retPC) } diff --git a/disassembly/linear.go b/disassembly/linear.go index 2c400902..fb471bdc 100644 --- a/disassembly/linear.go +++ b/disassembly/linear.go @@ -21,7 +21,7 @@ import ( func (dsm *Disassembly) linearDisassembly(mc *cpu.CPU) error { for bank := 0; bank < len(dsm.linear); bank++ { for address := dsm.Cart.Origin(); address <= dsm.Cart.Memtop(); address++ { - if err := dsm.Cart.SetAddressBank(address, bank); err != nil { + if err := dsm.Cart.SetBank(address, bank); err != nil { return err } diff --git a/errors/categories.go b/errors/categories.go index e623355a..3e8d4128 100644 --- a/errors/categories.go +++ b/errors/categories.go @@ -60,6 +60,7 @@ const ( UnreadableAddress UnwritableAddress UnpokeableAddress + UnpeekableAddress UnrecognisedAddress // cartridges diff --git a/errors/messages.go b/errors/messages.go index a7f5e292..2a24b71d 100644 --- a/errors/messages.go +++ b/errors/messages.go @@ -59,6 +59,7 @@ var messages = map[Errno]string{ UnreadableAddress: "memory error: memory location is not readable (%#04x)", UnwritableAddress: "memory error: memory location is not writable (%#04x)", UnpokeableAddress: "memory error: cannot poke address (%v)", + UnpeekableAddress: "memory error: cannot peek address (%v)", UnrecognisedAddress: "memory error: address unrecognised (%v)", // cartridges diff --git a/hardware/memory/cartridge.go b/hardware/memory/cartridge.go index 6bf37188..e6fa6ac1 100644 --- a/hardware/memory/cartridge.go +++ b/hardware/memory/cartridge.go @@ -17,10 +17,11 @@ type cartMapper interface { read(addr uint16) (data uint8, err error) write(addr uint16, data uint8, isPoke bool) error numBanks() int - getAddressBank(addr uint16) (bank int) - setAddressBank(addr uint16, bank int) error - saveBanks() interface{} - restoreBanks(interface{}) error + getBank(addr uint16) (bank int) + setBank(addr uint16, bank int) error + saveState() interface{} + restoreState(interface{}) error + ram() []uint8 } // Cartridge defines the information and operations for a VCS cartridge @@ -108,6 +109,8 @@ func (cart Cartridge) Poke(addr uint16, data uint8) error { return cart.mapper.write(addr, data, true) } +// fingerprint8k attempts a divination of 8k cartridge data and decide on a +// suitable cartMapper implementation func (cart Cartridge) fingerprint8k(cf io.ReadSeeker) func(io.ReadSeeker) (cartMapper, error) { byts := make([]byte, 8192) cf.Seek(0, io.SeekStart) @@ -207,26 +210,31 @@ func (cart Cartridge) NumBanks() int { return cart.mapper.numBanks() } -// GetAddressBank calls the current mapper's addressBank function. it returns -// the current bank number for the specified address -func (cart Cartridge) GetAddressBank(addr uint16) int { +// GetBank calls the current mapper's addressBank function. it returns the +// current bank number for the specified address +func (cart Cartridge) GetBank(addr uint16) int { addr &= cart.Origin() - 1 - return cart.mapper.getAddressBank(addr) + return cart.mapper.getBank(addr) } -// SetAddressBank sets the bank for the specificed address. it sets the -// specified address to reference the specified bank -func (cart *Cartridge) SetAddressBank(addr uint16, bank int) error { +// SetBank sets the bank for the specificed address. it sets the specified +// address to reference the specified bank +func (cart *Cartridge) SetBank(addr uint16, bank int) error { addr &= cart.Origin() - 1 - return cart.mapper.setAddressBank(addr, bank) + return cart.mapper.setBank(addr, bank) } -// SaveBanks calls the current mapper's saveState function -func (cart *Cartridge) SaveBanks() interface{} { - return cart.mapper.saveBanks() +// SaveState calls the current mapper's saveState function +func (cart *Cartridge) SaveState() interface{} { + return cart.mapper.saveState() } -// RestoreBanks calls the current mapper's restoreState function -func (cart *Cartridge) RestoreBanks(state interface{}) error { - return cart.mapper.restoreBanks(state) +// RestoreState calls the current mapper's restoreState function +func (cart *Cartridge) RestoreState(state interface{}) error { + return cart.mapper.restoreState(state) +} + +// RAM returns a read only instance of any cartridge RAM +func (cart Cartridge) RAM() []uint8 { + return cart.mapper.ram() } diff --git a/hardware/memory/cartridge_atari.go b/hardware/memory/cartridge_atari.go index 60da56d8..4c78c1c6 100644 --- a/hardware/memory/cartridge_atari.go +++ b/hardware/memory/cartridge_atari.go @@ -30,16 +30,19 @@ func (cart atari) String() string { func (cart *atari) initialise() { cart.bank = len(cart.banks) - 1 + if len(cart.superchip) > 0 { + cart.superchip = make([]uint8, len(cart.superchip)) + } } -func (cart atari) getAddressBank(addr uint16) int { +func (cart atari) getBank(addr uint16) int { // because atari bank switching swaps out the entire memory space, every // address points to whatever the current bank is. compare to parker bros. // cartridges. return cart.bank } -func (cart *atari) setAddressBank(addr uint16, bank int) error { +func (cart *atari) setBank(addr uint16, bank int) error { if bank < 0 || bank > len(cart.banks) { return errors.NewFormattedError(errors.CartridgeError, fmt.Sprintf("invalid bank (%d) for cartridge type (%s)", bank, cart.method)) } @@ -47,12 +50,13 @@ func (cart *atari) setAddressBank(addr uint16, bank int) error { return nil } -func (cart *atari) saveBanks() interface{} { - return cart.bank +func (cart *atari) saveState() interface{} { + return []interface{}{cart.bank, cart.superchip} } -func (cart *atari) restoreBanks(state interface{}) error { - cart.bank = state.(int) +func (cart *atari) restoreState(state interface{}) error { + cart.bank = state.([]interface{})[0].(int) + copy(cart.superchip, state.([]interface{})[1].([]uint8)) return nil } @@ -99,6 +103,10 @@ func (cart *atari) addSuperchip() bool { return true } +func (cart atari) ram() []uint8 { + return cart.superchip +} + // atari4k is the original and most straightforward format // o Pitfall // o River Raid diff --git a/hardware/memory/cartridge_ejected.go b/hardware/memory/cartridge_ejected.go index 188e0b20..481bc649 100644 --- a/hardware/memory/cartridge_ejected.go +++ b/hardware/memory/cartridge_ejected.go @@ -40,18 +40,22 @@ func (cart ejected) numBanks() int { return 0 } -func (cart *ejected) setAddressBank(addr uint16, bank int) error { +func (cart *ejected) setBank(addr uint16, bank int) error { return errors.NewFormattedError(errors.CartridgeError, fmt.Sprintf("invalid bank (%d) for cartridge type (%s)", bank, cart.method)) } -func (cart ejected) getAddressBank(addr uint16) int { +func (cart ejected) getBank(addr uint16) int { return 0 } -func (cart *ejected) saveBanks() interface{} { +func (cart *ejected) saveState() interface{} { return nil } -func (cart *ejected) restoreBanks(state interface{}) error { +func (cart *ejected) restoreState(state interface{}) error { return nil } + +func (cart ejected) ram() []uint8 { + return []uint8{} +} diff --git a/hardware/memory/cartridge_parkerbros.go b/hardware/memory/cartridge_parkerbros.go index 1feaf690..0b4d5f15 100644 --- a/hardware/memory/cartridge_parkerbros.go +++ b/hardware/memory/cartridge_parkerbros.go @@ -166,7 +166,7 @@ func (cart parkerBros) numBanks() int { return 8 } -func (cart parkerBros) getAddressBank(addr uint16) int { +func (cart parkerBros) getBank(addr uint16) int { if addr >= 0x0000 && addr <= 0x03ff { return cart.segment[0] } else if addr >= 0x0400 && addr <= 0x07ff { @@ -177,7 +177,7 @@ func (cart parkerBros) getAddressBank(addr uint16) int { return cart.segment[3] } -func (cart *parkerBros) setAddressBank(addr uint16, bank int) error { +func (cart *parkerBros) setBank(addr uint16, bank int) error { if bank < 0 || bank > cart.numBanks() { return errors.NewFormattedError(errors.CartridgeError, fmt.Sprintf("invalid bank (%d) for cartridge type (%s)", bank, cart.method)) } @@ -197,11 +197,15 @@ func (cart *parkerBros) setAddressBank(addr uint16, bank int) error { return nil } -func (cart *parkerBros) saveBanks() interface{} { +func (cart *parkerBros) saveState() interface{} { return cart.segment } -func (cart *parkerBros) restoreBanks(state interface{}) error { +func (cart *parkerBros) restoreState(state interface{}) error { cart.segment = state.([4]int) return nil } + +func (cart parkerBros) ram() []uint8 { + return []uint8{} +}