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"
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()

View file

@ -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",

View file

@ -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

View file

@ -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("[")

View file

@ -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

View file

@ -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)
}

View file

@ -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
}

View file

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

View file

@ -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

View file

@ -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()
}

View file

@ -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

View file

@ -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{}
}

View file

@ -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{}
}