added VCS.Snapshot() function. simplifies the rewind package

also allows easy access to the VCS snapshot process when there is no
need to use the rewind package
This commit is contained in:
JetSetIlly 2024-02-04 17:55:25 +00:00
parent 5b7a22ebb9
commit 58315e5182
8 changed files with 123 additions and 78 deletions

View file

@ -77,7 +77,7 @@ func (dbg *Debugger) doDeepPoke(searchState *rewind.State, addr uint16, value ui
// the new value at the correct point rather than from the resume // the new value at the correct point rather than from the resume
// state. // state.
pokeHook := func(s *rewind.State) error { pokeHook := func(s *rewind.State) error {
return deepPoke(s.Mem, poking, newValue, valueMask) return deepPoke(s.VCS.Mem, poking, newValue, valueMask)
} }
// run from found poking.state to the "current state" in the real emulation // run from found poking.state to the "current state" in the real emulation
@ -89,7 +89,7 @@ func (dbg *Debugger) doDeepPoke(searchState *rewind.State, addr uint16, value ui
// if we're not poking RAM then we poke the state that we found // if we're not poking RAM then we poke the state that we found
// immediately. this is particulatly important for cartridges with // immediately. this is particulatly important for cartridges with
// multiple banks because we need to poke the bank we found. // multiple banks because we need to poke the bank we found.
err := deepPoke(poking.state.Mem, poking, newValue, valueMask) err := deepPoke(poking.state.VCS.Mem, poking, newValue, valueMask)
if err != nil { if err != nil {
return err return err
} }
@ -128,7 +128,7 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
// writes to memory always use a register as the source of the write // writes to memory always use a register as the source of the write
var reg rune var reg rune
switch searchState.CPU.LastResult.Defn.Operator { switch searchState.VCS.CPU.LastResult.Defn.Operator {
case instructions.Sta: case instructions.Sta:
reg = 'A' reg = 'A'
case instructions.Stx: case instructions.Stx:
@ -136,7 +136,7 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
case instructions.Sty: case instructions.Sty:
reg = 'Y' reg = 'Y'
default: default:
return deepPoking{}, fmt.Errorf("unexpected write sequence (%s)", searchState.CPU.LastResult.String()) return deepPoking{}, fmt.Errorf("unexpected write sequence (%s)", searchState.VCS.CPU.LastResult.String())
} }
searchState, err = dbg.Rewind.SearchRegisterWrite(searchState, reg, value, valueMask) searchState, err = dbg.Rewind.SearchRegisterWrite(searchState, reg, value, valueMask)
@ -147,16 +147,16 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
return poking, nil return poking, nil
} }
if searchState.CPU.LastResult.Defn == nil { if searchState.VCS.CPU.LastResult.Defn == nil {
return deepPoking{}, fmt.Errorf("unexpected CPU result with a nil definition") return deepPoking{}, fmt.Errorf("unexpected CPU result with a nil definition")
} }
// writes to a register can happen from another register, and immediate // writes to a register can happen from another register, and immediate
// value, or an address in memory (most probably from the cartridge or // value, or an address in memory (most probably from the cartridge or
// VCS RAM) // VCS RAM)
switch searchState.CPU.LastResult.Defn.AddressingMode { switch searchState.VCS.CPU.LastResult.Defn.AddressingMode {
case instructions.Immediate: case instructions.Immediate:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.Address, false) ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.Address, false)
switch area { switch area {
case memorymap.Cartridge: case memorymap.Cartridge:
pc := registers.NewProgramCounter(ma) pc := registers.NewProgramCounter(ma)
@ -169,18 +169,18 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM/Cartridge space (%s)", area) return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM/Cartridge space (%s)", area)
} }
case instructions.AbsoluteIndexedX: case instructions.AbsoluteIndexedX:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.InstructionData, false) ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.InstructionData, false)
switch area { switch area {
case memorymap.Cartridge: case memorymap.Cartridge:
pc := registers.NewProgramCounter(ma) pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.X.Address()) pc.Add(searchState.VCS.CPU.X.Address())
poking.addr = pc.Address() poking.addr = pc.Address()
poking.state = searchState poking.state = searchState
poking.area = area poking.area = area
return poking, nil return poking, nil
case memorymap.RAM: case memorymap.RAM:
pc := registers.NewProgramCounter(ma) pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.X.Address()) pc.Add(searchState.VCS.CPU.X.Address())
poking.addr = pc.Address() poking.addr = pc.Address()
poking.state = searchState poking.state = searchState
poking.area = area poking.area = area
@ -188,18 +188,18 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM/Cartridge space (%s)", area) return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM/Cartridge space (%s)", area)
} }
case instructions.AbsoluteIndexedY: case instructions.AbsoluteIndexedY:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.InstructionData, false) ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.InstructionData, false)
switch area { switch area {
case memorymap.Cartridge: case memorymap.Cartridge:
pc := registers.NewProgramCounter(ma) pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.Y.Address()) pc.Add(searchState.VCS.CPU.Y.Address())
poking.addr = pc.Address() poking.addr = pc.Address()
poking.state = searchState poking.state = searchState
poking.area = area poking.area = area
return poking, nil return poking, nil
case memorymap.RAM: case memorymap.RAM:
pc := registers.NewProgramCounter(ma) pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.Y.Address()) pc.Add(searchState.VCS.CPU.Y.Address())
poking.addr = pc.Address() poking.addr = pc.Address()
poking.state = searchState poking.state = searchState
poking.area = area poking.area = area
@ -207,7 +207,7 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM/Cartridge space (%s)", area) return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM/Cartridge space (%s)", area)
} }
case instructions.ZeroPage: case instructions.ZeroPage:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.InstructionData, false) ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.InstructionData, false)
switch area { switch area {
case memorymap.RAM: case memorymap.RAM:
poking.addr = ma poking.addr = ma
@ -220,11 +220,11 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM space (%s)", area) return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM space (%s)", area)
} }
case instructions.ZeroPageIndexedX: case instructions.ZeroPageIndexedX:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.InstructionData, false) ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.InstructionData, false)
switch area { switch area {
case memorymap.RAM: case memorymap.RAM:
pc := registers.NewProgramCounter(ma) pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.X.Address()) pc.Add(searchState.VCS.CPU.X.Address())
poking.addr = pc.Address() poking.addr = pc.Address()
poking.state = searchState poking.state = searchState
poking.area = area poking.area = area
@ -235,11 +235,11 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM space (%s)", area) return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM space (%s)", area)
} }
case instructions.ZeroPageIndexedY: case instructions.ZeroPageIndexedY:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.InstructionData, false) ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.InstructionData, false)
switch area { switch area {
case memorymap.RAM: case memorymap.RAM:
pc := registers.NewProgramCounter(ma) pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.Y.Address()) pc.Add(searchState.VCS.CPU.Y.Address())
poking.addr = pc.Address() poking.addr = pc.Address()
poking.state = searchState poking.state = searchState
poking.area = area poking.area = area
@ -250,18 +250,18 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM space (%s)", area) return deepPoking{}, fmt.Errorf("not deeppoking through non-RAM space (%s)", area)
} }
case instructions.IndirectIndexed: case instructions.IndirectIndexed:
pc := registers.NewProgramCounter(searchState.CPU.LastResult.InstructionData) pc := registers.NewProgramCounter(searchState.VCS.CPU.LastResult.InstructionData)
lo, err := searchState.Mem.Read(pc.Address()) lo, err := searchState.VCS.Mem.Read(pc.Address())
if err != nil { if err != nil {
return deepPoking{}, err return deepPoking{}, err
} }
pc.Add(1) pc.Add(1)
hi, err := searchState.Mem.Read(pc.Address()) hi, err := searchState.VCS.Mem.Read(pc.Address())
if err != nil { if err != nil {
return deepPoking{}, err return deepPoking{}, err
} }
pc.Load((uint16(hi) << 8) | uint16(lo)) pc.Load((uint16(hi) << 8) | uint16(lo))
pc.Add(searchState.CPU.Y.Address()) pc.Add(searchState.VCS.CPU.Y.Address())
ma, area := memorymap.MapAddress(pc.Address(), false) ma, area := memorymap.MapAddress(pc.Address(), false)
switch area { switch area {
@ -280,7 +280,7 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
fallthrough fallthrough
default: default:
return deepPoking{}, fmt.Errorf("unsupported addressing mode (%s)", searchState.CPU.LastResult.String()) return deepPoking{}, fmt.Errorf("unsupported addressing mode (%s)", searchState.VCS.CPU.LastResult.String())
} }
} }

View file

@ -82,7 +82,7 @@ func (win *winCartRAM) debuggerDraw() bool {
func (win *winCartRAM) draw(ram []mapper.CartRAM) { func (win *winCartRAM) draw(ram []mapper.CartRAM) {
// get comparison data. assuming that there is such a thing and that it's // get comparison data. assuming that there is such a thing and that it's
// safe to get StaticData from. // safe to get StaticData from.
comp := win.img.cache.Rewind.Comparison.State.Mem.Cart.GetRAMbus().GetRAM() comp := win.img.cache.Rewind.Comparison.State.VCS.Mem.Cart.GetRAMbus().GetRAM()
imgui.BeginTabBarV("", imgui.TabBarFlagsFittingPolicyScroll) imgui.BeginTabBarV("", imgui.TabBarFlagsFittingPolicyScroll)
for bank := range ram { for bank := range ram {

View file

@ -75,7 +75,7 @@ func (win *winCartStatic) debuggerDraw() bool {
func (win *winCartStatic) draw(static mapper.CartStatic) { func (win *winCartStatic) draw(static mapper.CartStatic) {
// get comparison data. assuming that there is such a thing and that it's // get comparison data. assuming that there is such a thing and that it's
// safe to get StaticData from. // safe to get StaticData from.
compStatic := win.img.cache.Rewind.Comparison.State.Mem.Cart.GetStaticBus().GetStatic() compStatic := win.img.cache.Rewind.Comparison.State.VCS.Mem.Cart.GetStaticBus().GetStatic()
// make a note of cell padding value. this changes for the duration of // make a note of cell padding value. this changes for the duration of
// drawByteGrid() but we want the default value for when we call // drawByteGrid() but we want the default value for when we call

View file

@ -61,7 +61,7 @@ func (win *winRAM) debuggerDraw() bool {
func (win *winRAM) draw() { func (win *winRAM) draw() {
var diff []uint8 var diff []uint8
if win.img.cache.Rewind.Comparison.State != nil { if win.img.cache.Rewind.Comparison.State != nil {
diff = win.img.cache.Rewind.Comparison.State.Mem.RAM.RAM diff = win.img.cache.Rewind.Comparison.State.VCS.Mem.RAM.RAM
} else { } else {
diff = win.img.cache.VCS.Mem.RAM.RAM diff = win.img.cache.VCS.Mem.RAM.RAM
} }

84
hardware/snapshot.go Normal file
View file

@ -0,0 +1,84 @@
// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package hardware
import (
"github.com/jetsetilly/gopher2600/hardware/cpu"
"github.com/jetsetilly/gopher2600/hardware/memory"
"github.com/jetsetilly/gopher2600/hardware/riot"
"github.com/jetsetilly/gopher2600/hardware/tia"
)
// State stores the VCS sub-systems. It is produced by the Snapshot() function
// and can be restored with the Plumb() function
//
// Note in particular that the TV is not part of the snapshot process
type State struct {
CPU *cpu.CPU
Mem *memory.Memory
RIOT *riot.RIOT
TIA *tia.TIA
}
// Snapshot creates a copy of a previously snapshotted VCS State
func (s *State) Snapshot() *State {
return &State{
CPU: s.CPU.Snapshot(),
Mem: s.Mem.Snapshot(),
RIOT: s.RIOT.Snapshot(),
TIA: s.TIA.Snapshot(),
}
}
// Snapshot the state of the VCS sub-systems
func (vcs *VCS) Snapshot() *State {
return &State{
CPU: vcs.CPU.Snapshot(),
Mem: vcs.Mem.Snapshot(),
RIOT: vcs.RIOT.Snapshot(),
TIA: vcs.TIA.Snapshot(),
}
}
// Plumb a previously snapshotted system
//
// The fromDifferentEmulation indicates that the State has been created by a
// different VCS emulation than the one being plumbed into
func (vcs *VCS) Plumb(state *State, fromDifferentEmulation bool) {
if state == nil {
panic("vcs: cannot plumb in a nil state")
}
// take another snapshot of the state before plumbing. we don't want the
// machine to change what we have stored in our state array (we learned
// that lesson the hard way :-)
vcs.CPU = state.CPU.Snapshot()
vcs.Mem = state.Mem.Snapshot()
vcs.RIOT = state.RIOT.Snapshot()
vcs.TIA = state.TIA.Snapshot()
vcs.CPU.Plumb(vcs.Env, vcs.Mem)
vcs.Mem.Plumb(vcs.Env, fromDifferentEmulation)
vcs.RIOT.Plumb(vcs.Env, vcs.Mem.RIOT, vcs.Mem.TIA)
vcs.TIA.Plumb(vcs.Env, vcs.TV, vcs.Mem.TIA, vcs.RIOT.Ports, vcs.CPU)
// reset peripherals after new state has been plumbed. without this,
// controllers can feel odd if the newly plumbed state has left RIOT memory
// in a latched state
vcs.RIOT.Ports.ResetPeripherals()
vcs.Input.Plumb(vcs.TV, vcs.RIOT.Ports)
}

View file

@ -267,13 +267,13 @@ func (tv *Television) Snapshot() *State {
return tv.state.Snapshot() return tv.state.Snapshot()
} }
// PlumbState attaches an existing television state. // Plumb attaches an existing television state.
func (tv *Television) PlumbState(vcs VCSReturnChannel, s *State) { func (tv *Television) Plumb(vcs VCSReturnChannel, state *State) {
if s == nil { if state == nil {
panic("television: cannot plumb in a nil state") panic("television: cannot plumb in a nil state")
} }
tv.state = s tv.state = state.Snapshot()
// make sure vcs knows about current spec // make sure vcs knows about current spec
tv.vcs = vcs tv.vcs = vcs

View file

@ -47,13 +47,14 @@ type VCS struct {
// the television is not "part" of the VCS console but it's part of the VCS system // the television is not "part" of the VCS console but it's part of the VCS system
TV *television.Television TV *television.Television
// references to the different components of the VCS. do not take copies of // references to the different sub-systems of the VCS. these syb-systems can be
// these pointer values because the rewind feature will change them. // copied with the Snapshot() function creating an instance of the State type
CPU *cpu.CPU CPU *cpu.CPU
Mem *memory.Memory Mem *memory.Memory
RIOT *riot.RIOT RIOT *riot.RIOT
TIA *tia.TIA TIA *tia.TIA
// the input sub-system. this is not part of the Snapshot() process
Input *input.Input Input *input.Input
// The Clock defines the basic speed at which the the machine is runningt. This governs // The Clock defines the basic speed at which the the machine is runningt. This governs
@ -133,24 +134,6 @@ func (vcs *VCS) End() {
vcs.RIOT.Ports.End() vcs.RIOT.Ports.End()
} }
// Plumb the various VCS sub-systems together after a rewind.
//
// The fromDifferentEmulation indicates that the State has been created by a
// different VCS emulation than the one being plumbed into.
func (vcs *VCS) Plumb(fromDifferentEmulation bool) {
vcs.CPU.Plumb(vcs.Env, vcs.Mem)
vcs.Mem.Plumb(vcs.Env, fromDifferentEmulation)
vcs.RIOT.Plumb(vcs.Env, vcs.Mem.RIOT, vcs.Mem.TIA)
vcs.TIA.Plumb(vcs.Env, vcs.TV, vcs.Mem.TIA, vcs.RIOT.Ports, vcs.CPU)
// reset peripherals after new state has been plumbed. without this,
// controllers can feel odd if the newly plumbed state has left RIOT memory
// in a latched state
vcs.RIOT.Ports.ResetPeripherals()
vcs.Input.Plumb(vcs.TV, vcs.RIOT.Ports)
}
// AttachCartridge to this VCS. While this function can be called directly it // AttachCartridge to this VCS. While this function can be called directly it
// is advised that the setup package be used in most circumstances. // is advised that the setup package be used in most circumstances.
// //

View file

@ -21,13 +21,9 @@ import (
"github.com/jetsetilly/gopher2600/debugger/govern" "github.com/jetsetilly/gopher2600/debugger/govern"
"github.com/jetsetilly/gopher2600/hardware" "github.com/jetsetilly/gopher2600/hardware"
"github.com/jetsetilly/gopher2600/hardware/cpu"
"github.com/jetsetilly/gopher2600/hardware/memory"
"github.com/jetsetilly/gopher2600/hardware/riot"
"github.com/jetsetilly/gopher2600/hardware/television" "github.com/jetsetilly/gopher2600/hardware/television"
"github.com/jetsetilly/gopher2600/hardware/television/coords" "github.com/jetsetilly/gopher2600/hardware/television/coords"
"github.com/jetsetilly/gopher2600/hardware/television/specification" "github.com/jetsetilly/gopher2600/hardware/television/specification"
"github.com/jetsetilly/gopher2600/hardware/tia"
"github.com/jetsetilly/gopher2600/logger" "github.com/jetsetilly/gopher2600/logger"
) )
@ -55,21 +51,14 @@ type Runner interface {
// reference. // reference.
type State struct { type State struct {
level snapshotLevel level snapshotLevel
VCS *hardware.State
CPU *cpu.CPU TV *television.State
Mem *memory.Memory
RIOT *riot.RIOT
TIA *tia.TIA
TV *television.State
} }
func (s *State) snapshot() *State { func (s *State) snapshot() *State {
return &State{ return &State{
level: s.level, level: s.level,
CPU: s.CPU.Snapshot(), VCS: s.VCS.Snapshot(),
Mem: s.Mem.Snapshot(),
RIOT: s.RIOT.Snapshot(),
TIA: s.TIA.Snapshot(),
TV: s.TV.Snapshot(), TV: s.TV.Snapshot(),
} }
} }
@ -340,10 +329,7 @@ func (r *Rewind) lastEntryIdx() int {
func snapshot(vcs *hardware.VCS, level snapshotLevel) *State { func snapshot(vcs *hardware.VCS, level snapshotLevel) *State {
return &State{ return &State{
level: level, level: level,
CPU: vcs.CPU.Snapshot(), VCS: vcs.Snapshot(),
Mem: vcs.Mem.Snapshot(),
RIOT: vcs.RIOT.Snapshot(),
TIA: vcs.TIA.Snapshot(),
TV: vcs.TV.Snapshot(), TV: vcs.TV.Snapshot(),
} }
} }
@ -443,18 +429,10 @@ func (r *Rewind) append(s *State) {
func Plumb(vcs *hardware.VCS, state *State, fromDifferentEmulation bool) { func Plumb(vcs *hardware.VCS, state *State, fromDifferentEmulation bool) {
// tv plumbing works a bit different to other areas because we're only // tv plumbing works a bit different to other areas because we're only
// recording the state of the TV not the entire TV itself. // recording the state of the TV not the entire TV itself.
vcs.TV.PlumbState(vcs, state.TV.Snapshot()) vcs.TV.Plumb(vcs, state.TV)
// take another snapshot of the state before plumbing. we don't want the
// machine to change what we have stored in our state array (we learned
// that lesson the hard way :-)
vcs.CPU = state.CPU.Snapshot()
vcs.Mem = state.Mem.Snapshot()
vcs.RIOT = state.RIOT.Snapshot()
vcs.TIA = state.TIA.Snapshot()
// finish off plumbing process // finish off plumbing process
vcs.Plumb(fromDifferentEmulation) vcs.Plumb(state.VCS, fromDifferentEmulation)
} }
// run from the supplied state until the cooridinates are reached. // run from the supplied state until the cooridinates are reached.