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
This commit is contained in:
steve 2019-09-02 22:43:18 +01:00
parent fea2357fdf
commit c59a4a2dde
13 changed files with 94 additions and 54 deletions

View file

@ -25,7 +25,6 @@ const (
cmdCPU = "CPU" cmdCPU = "CPU"
cmdCartridge = "CARTRIDGE" cmdCartridge = "CARTRIDGE"
cmdClear = "CLEAR" cmdClear = "CLEAR"
cmdClocks = "CLOCKS"
cmdDebuggerState = "DEBUGGERSTATE" cmdDebuggerState = "DEBUGGERSTATE"
cmdDigest = "DIGEST" cmdDigest = "DIGEST"
cmdDisassembly = "DISASSEMBLY" cmdDisassembly = "DISASSEMBLY"
@ -72,7 +71,6 @@ var commandTemplate = []string{
cmdCPU + " (SET [PC|A|X|Y|SP] [%N])", cmdCPU + " (SET [PC|A|X|Y|SP] [%N])",
cmdCartridge + " (ANALYSIS)", cmdCartridge + " (ANALYSIS)",
cmdClear + " [BREAKS|TRAPS|WATCHES|ALL]", cmdClear + " [BREAKS|TRAPS|WATCHES|ALL]",
cmdClocks,
cmdDebuggerState, cmdDebuggerState,
cmdDigest + " (RESET)", cmdDigest + " (RESET)",
cmdDisassembly, cmdDisassembly,
@ -90,10 +88,10 @@ var commandTemplate = []string{
cmdPeek + " [%S] {%S}", cmdPeek + " [%S] {%S}",
cmdPlayer + " (0|1)", cmdPlayer + " (0|1)",
cmdPlayfield, cmdPlayfield,
cmdPoke + " [%N|%S] %N", cmdPoke + " [%S] %N",
cmdQuit, cmdQuit,
cmdExit, cmdExit,
cmdRAM, cmdRAM + " (CART)",
cmdRIOT + " (TIMER)", cmdRIOT + " (TIMER)",
cmdReset, cmdReset,
cmdRun, cmdRun,
@ -108,7 +106,7 @@ var commandTemplate = []string{
cmdTrap + " [%S] {%S}", cmdTrap + " [%S] {%S}",
cmdVerbose, cmdVerbose,
cmdVerbosity, cmdVerbosity,
cmdWatch + " (READ|WRITE) %N (%N)", cmdWatch + " (READ|WRITE) [%S] (%S)",
} }
// list of commands that should not be executed when recording/playing scripts // 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: 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: case cmdRIOT:
option, present := tokens.Get() option, present := tokens.Get()

View file

@ -7,7 +7,6 @@ var Help = map[string]string{
cmdCPU: "Display the current state of the CPU", cmdCPU: "Display the current state of the CPU",
cmdCartridge: "Display information about the current cartridge", cmdCartridge: "Display information about the current cartridge",
cmdClear: "Clear all entries in BREAKS and TRAPS", cmdClear: "Clear all entries in BREAKS and TRAPS",
cmdClocks: "The current state of the VCS clocks",
cmdDebuggerState: "Display summary of debugger options", cmdDebuggerState: "Display summary of debugger options",
cmdDigest: "Return the cryptographic hash of the current screen", cmdDigest: "Return the cryptographic hash of the current screen",
cmdDisassembly: "Print the full cartridge disassembly", cmdDisassembly: "Print the full cartridge disassembly",

View file

@ -127,7 +127,7 @@ func (mem memoryDebug) mapAddress(address interface{}, cpuRead bool) *addressInf
func (mem memoryDebug) peek(address interface{}) (*addressInfo, error) { func (mem memoryDebug) peek(address interface{}) (*addressInfo, error) {
ai := mem.mapAddress(address, true) ai := mem.mapAddress(address, true)
if ai == nil { 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) 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) { func (mem memoryDebug) poke(address interface{}, value uint8) (*addressInfo, error) {
ai := mem.mapAddress(address, true) ai := mem.mapAddress(address, true)
if ai == nil { 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.value = value
ai.valueSeen = true ai.valueSeen = true

View file

@ -21,7 +21,7 @@ func (dbg *Debugger) buildPrompt(videoCycle bool) console.Prompt {
promptAddress = dbg.lastResult.Address promptAddress = dbg.lastResult.Address
} }
promptBank = dbg.vcs.Mem.Cart.GetAddressBank(promptAddress) promptBank = dbg.vcs.Mem.Cart.GetBank(promptAddress)
prompt := strings.Builder{} prompt := strings.Builder{}
prompt.WriteString("[") prompt.WriteString("[")

View file

@ -92,9 +92,13 @@ func (dsm *Disassembly) FromMemory(cart *memory.Cartridge, symtable *symbols.Tab
dsm.flow = make([]bank, dsm.Cart.NumBanks()) dsm.flow = make([]bank, dsm.Cart.NumBanks())
dsm.linear = make([]bank, dsm.Cart.NumBanks()) dsm.linear = make([]bank, dsm.Cart.NumBanks())
state := dsm.Cart.SaveBanks() // save cartridge state and defer at end of disassembly. this is necessary
defer dsm.Cart.RestoreBanks(state) // 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() dsm.Cart.Initialise()
// create new memory // create new memory

View file

@ -52,7 +52,7 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error {
return err 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 we've seen this before then finish the disassembly
if dsm.flow[bank][r.Address&disasmMask].IsInstruction() { 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.Defn.AddressingMode == definitions.Indirect {
if r.InstructionData.(uint16) > dsm.Cart.Origin() { if r.InstructionData.(uint16) > dsm.Cart.Origin() {
// note current location // note current location
state := dsm.Cart.SaveBanks() state := dsm.Cart.SaveState()
retPC := mc.PC.ToUint16() retPC := mc.PC.ToUint16()
// adjust program counter // adjust program counter
@ -87,7 +87,7 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error {
} }
// resume from where we left off // resume from where we left off
dsm.Cart.RestoreBanks(state) dsm.Cart.RestoreState(state)
mc.PC.Load(retPC) mc.PC.Load(retPC)
} else { } else {
// it's entirely possible for the program to jump // it's entirely possible for the program to jump
@ -105,7 +105,7 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error {
// absolute JMP addressing // absolute JMP addressing
// note current location // note current location
state := dsm.Cart.SaveBanks() state := dsm.Cart.SaveState()
retPC := mc.PC.ToUint16() retPC := mc.PC.ToUint16()
// adjust program counter // adjust program counter
@ -118,14 +118,14 @@ func (dsm *Disassembly) flowDisassembly(mc *cpu.CPU) error {
} }
// resume from where we left off // resume from where we left off
dsm.Cart.RestoreBanks(state) dsm.Cart.RestoreState(state)
mc.PC.Load(retPC) mc.PC.Load(retPC)
} }
} else { } else {
// branch instructions // branch instructions
// note current location // note current location
state := dsm.Cart.SaveBanks() state := dsm.Cart.SaveState()
retPC := mc.PC.ToUint16() retPC := mc.PC.ToUint16()
// sign extend address and add to program counter // 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 // resume from where we left off
dsm.Cart.RestoreBanks(state) dsm.Cart.RestoreState(state)
mc.PC.Load(retPC) mc.PC.Load(retPC)
} }

View file

@ -21,7 +21,7 @@ import (
func (dsm *Disassembly) linearDisassembly(mc *cpu.CPU) error { func (dsm *Disassembly) linearDisassembly(mc *cpu.CPU) error {
for bank := 0; bank < len(dsm.linear); bank++ { for bank := 0; bank < len(dsm.linear); bank++ {
for address := dsm.Cart.Origin(); address <= dsm.Cart.Memtop(); address++ { 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 return err
} }

View file

@ -60,6 +60,7 @@ const (
UnreadableAddress UnreadableAddress
UnwritableAddress UnwritableAddress
UnpokeableAddress UnpokeableAddress
UnpeekableAddress
UnrecognisedAddress UnrecognisedAddress
// cartridges // cartridges

View file

@ -59,6 +59,7 @@ var messages = map[Errno]string{
UnreadableAddress: "memory error: memory location is not readable (%#04x)", UnreadableAddress: "memory error: memory location is not readable (%#04x)",
UnwritableAddress: "memory error: memory location is not writable (%#04x)", UnwritableAddress: "memory error: memory location is not writable (%#04x)",
UnpokeableAddress: "memory error: cannot poke address (%v)", UnpokeableAddress: "memory error: cannot poke address (%v)",
UnpeekableAddress: "memory error: cannot peek address (%v)",
UnrecognisedAddress: "memory error: address unrecognised (%v)", UnrecognisedAddress: "memory error: address unrecognised (%v)",
// cartridges // cartridges

View file

@ -17,10 +17,11 @@ type cartMapper interface {
read(addr uint16) (data uint8, err error) read(addr uint16) (data uint8, err error)
write(addr uint16, data uint8, isPoke bool) error write(addr uint16, data uint8, isPoke bool) error
numBanks() int numBanks() int
getAddressBank(addr uint16) (bank int) getBank(addr uint16) (bank int)
setAddressBank(addr uint16, bank int) error setBank(addr uint16, bank int) error
saveBanks() interface{} saveState() interface{}
restoreBanks(interface{}) error restoreState(interface{}) error
ram() []uint8
} }
// Cartridge defines the information and operations for a VCS cartridge // 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) 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) { func (cart Cartridge) fingerprint8k(cf io.ReadSeeker) func(io.ReadSeeker) (cartMapper, error) {
byts := make([]byte, 8192) byts := make([]byte, 8192)
cf.Seek(0, io.SeekStart) cf.Seek(0, io.SeekStart)
@ -207,26 +210,31 @@ func (cart Cartridge) NumBanks() int {
return cart.mapper.numBanks() return cart.mapper.numBanks()
} }
// GetAddressBank calls the current mapper's addressBank function. it returns // GetBank calls the current mapper's addressBank function. it returns the
// the current bank number for the specified address // current bank number for the specified address
func (cart Cartridge) GetAddressBank(addr uint16) int { func (cart Cartridge) GetBank(addr uint16) int {
addr &= cart.Origin() - 1 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 // SetBank sets the bank for the specificed address. it sets the specified
// specified address to reference the specified bank // address to reference the specified bank
func (cart *Cartridge) SetAddressBank(addr uint16, bank int) error { func (cart *Cartridge) SetBank(addr uint16, bank int) error {
addr &= cart.Origin() - 1 addr &= cart.Origin() - 1
return cart.mapper.setAddressBank(addr, bank) return cart.mapper.setBank(addr, bank)
} }
// SaveBanks calls the current mapper's saveState function // SaveState calls the current mapper's saveState function
func (cart *Cartridge) SaveBanks() interface{} { func (cart *Cartridge) SaveState() interface{} {
return cart.mapper.saveBanks() return cart.mapper.saveState()
} }
// RestoreBanks calls the current mapper's restoreState function // RestoreState calls the current mapper's restoreState function
func (cart *Cartridge) RestoreBanks(state interface{}) error { func (cart *Cartridge) RestoreState(state interface{}) error {
return cart.mapper.restoreBanks(state) return cart.mapper.restoreState(state)
}
// RAM returns a read only instance of any cartridge RAM
func (cart Cartridge) RAM() []uint8 {
return cart.mapper.ram()
} }

View file

@ -30,16 +30,19 @@ func (cart atari) String() string {
func (cart *atari) initialise() { func (cart *atari) initialise() {
cart.bank = len(cart.banks) - 1 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 // because atari bank switching swaps out the entire memory space, every
// address points to whatever the current bank is. compare to parker bros. // address points to whatever the current bank is. compare to parker bros.
// cartridges. // cartridges.
return cart.bank 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) { if bank < 0 || bank > len(cart.banks) {
return errors.NewFormattedError(errors.CartridgeError, fmt.Sprintf("invalid bank (%d) for cartridge type (%s)", bank, cart.method)) 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 return nil
} }
func (cart *atari) saveBanks() interface{} { func (cart *atari) saveState() interface{} {
return cart.bank return []interface{}{cart.bank, cart.superchip}
} }
func (cart *atari) restoreBanks(state interface{}) error { func (cart *atari) restoreState(state interface{}) error {
cart.bank = state.(int) cart.bank = state.([]interface{})[0].(int)
copy(cart.superchip, state.([]interface{})[1].([]uint8))
return nil return nil
} }
@ -99,6 +103,10 @@ func (cart *atari) addSuperchip() bool {
return true return true
} }
func (cart atari) ram() []uint8 {
return cart.superchip
}
// atari4k is the original and most straightforward format // atari4k is the original and most straightforward format
// o Pitfall // o Pitfall
// o River Raid // o River Raid

View file

@ -40,18 +40,22 @@ func (cart ejected) numBanks() int {
return 0 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)) 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 return 0
} }
func (cart *ejected) saveBanks() interface{} { func (cart *ejected) saveState() interface{} {
return nil return nil
} }
func (cart *ejected) restoreBanks(state interface{}) error { func (cart *ejected) restoreState(state interface{}) error {
return nil return nil
} }
func (cart ejected) ram() []uint8 {
return []uint8{}
}

View file

@ -166,7 +166,7 @@ func (cart parkerBros) numBanks() int {
return 8 return 8
} }
func (cart parkerBros) getAddressBank(addr uint16) int { func (cart parkerBros) getBank(addr uint16) int {
if addr >= 0x0000 && addr <= 0x03ff { if addr >= 0x0000 && addr <= 0x03ff {
return cart.segment[0] return cart.segment[0]
} else if addr >= 0x0400 && addr <= 0x07ff { } else if addr >= 0x0400 && addr <= 0x07ff {
@ -177,7 +177,7 @@ func (cart parkerBros) getAddressBank(addr uint16) int {
return cart.segment[3] 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() { if bank < 0 || bank > cart.numBanks() {
return errors.NewFormattedError(errors.CartridgeError, fmt.Sprintf("invalid bank (%d) for cartridge type (%s)", bank, cart.method)) 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 return nil
} }
func (cart *parkerBros) saveBanks() interface{} { func (cart *parkerBros) saveState() interface{} {
return cart.segment return cart.segment
} }
func (cart *parkerBros) restoreBanks(state interface{}) error { func (cart *parkerBros) restoreState(state interface{}) error {
cart.segment = state.([4]int) cart.segment = state.([4]int)
return nil return nil
} }
func (cart parkerBros) ram() []uint8 {
return []uint8{}
}