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
// state.
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
@ -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
// immediately. this is particulatly important for cartridges with
// 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 {
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
var reg rune
switch searchState.CPU.LastResult.Defn.Operator {
switch searchState.VCS.CPU.LastResult.Defn.Operator {
case instructions.Sta:
reg = 'A'
case instructions.Stx:
@ -136,7 +136,7 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
case instructions.Sty:
reg = 'Y'
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)
@ -147,16 +147,16 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
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")
}
// writes to a register can happen from another register, and immediate
// value, or an address in memory (most probably from the cartridge or
// VCS RAM)
switch searchState.CPU.LastResult.Defn.AddressingMode {
switch searchState.VCS.CPU.LastResult.Defn.AddressingMode {
case instructions.Immediate:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.Address, false)
ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.Address, false)
switch area {
case memorymap.Cartridge:
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)
}
case instructions.AbsoluteIndexedX:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.InstructionData, false)
ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.InstructionData, false)
switch area {
case memorymap.Cartridge:
pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.X.Address())
pc.Add(searchState.VCS.CPU.X.Address())
poking.addr = pc.Address()
poking.state = searchState
poking.area = area
return poking, nil
case memorymap.RAM:
pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.X.Address())
pc.Add(searchState.VCS.CPU.X.Address())
poking.addr = pc.Address()
poking.state = searchState
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)
}
case instructions.AbsoluteIndexedY:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.InstructionData, false)
ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.InstructionData, false)
switch area {
case memorymap.Cartridge:
pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.Y.Address())
pc.Add(searchState.VCS.CPU.Y.Address())
poking.addr = pc.Address()
poking.state = searchState
poking.area = area
return poking, nil
case memorymap.RAM:
pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.Y.Address())
pc.Add(searchState.VCS.CPU.Y.Address())
poking.addr = pc.Address()
poking.state = searchState
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)
}
case instructions.ZeroPage:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.InstructionData, false)
ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.InstructionData, false)
switch area {
case memorymap.RAM:
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)
}
case instructions.ZeroPageIndexedX:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.InstructionData, false)
ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.InstructionData, false)
switch area {
case memorymap.RAM:
pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.X.Address())
pc.Add(searchState.VCS.CPU.X.Address())
poking.addr = pc.Address()
poking.state = searchState
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)
}
case instructions.ZeroPageIndexedY:
ma, area := memorymap.MapAddress(searchState.CPU.LastResult.InstructionData, false)
ma, area := memorymap.MapAddress(searchState.VCS.CPU.LastResult.InstructionData, false)
switch area {
case memorymap.RAM:
pc := registers.NewProgramCounter(ma)
pc.Add(searchState.CPU.Y.Address())
pc.Add(searchState.VCS.CPU.Y.Address())
poking.addr = pc.Address()
poking.state = searchState
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)
}
case instructions.IndirectIndexed:
pc := registers.NewProgramCounter(searchState.CPU.LastResult.InstructionData)
lo, err := searchState.Mem.Read(pc.Address())
pc := registers.NewProgramCounter(searchState.VCS.CPU.LastResult.InstructionData)
lo, err := searchState.VCS.Mem.Read(pc.Address())
if err != nil {
return deepPoking{}, err
}
pc.Add(1)
hi, err := searchState.Mem.Read(pc.Address())
hi, err := searchState.VCS.Mem.Read(pc.Address())
if err != nil {
return deepPoking{}, err
}
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)
switch area {
@ -280,7 +280,7 @@ func (dbg *Debugger) searchDeepPoke(searchState *rewind.State, searchAddr uint16
fallthrough
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) {
// get comparison data. assuming that there is such a thing and that it's
// 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)
for bank := range ram {

View file

@ -75,7 +75,7 @@ func (win *winCartStatic) debuggerDraw() bool {
func (win *winCartStatic) draw(static mapper.CartStatic) {
// get comparison data. assuming that there is such a thing and that it's
// 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
// 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() {
var diff []uint8
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 {
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()
}
// PlumbState attaches an existing television state.
func (tv *Television) PlumbState(vcs VCSReturnChannel, s *State) {
if s == nil {
// Plumb attaches an existing television state.
func (tv *Television) Plumb(vcs VCSReturnChannel, state *State) {
if state == nil {
panic("television: cannot plumb in a nil state")
}
tv.state = s
tv.state = state.Snapshot()
// make sure vcs knows about current spec
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
TV *television.Television
// references to the different components of the VCS. do not take copies of
// these pointer values because the rewind feature will change them.
// references to the different sub-systems of the VCS. these syb-systems can be
// copied with the Snapshot() function creating an instance of the State type
CPU *cpu.CPU
Mem *memory.Memory
RIOT *riot.RIOT
TIA *tia.TIA
// the input sub-system. this is not part of the Snapshot() process
Input *input.Input
// 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()
}
// 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
// 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/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/coords"
"github.com/jetsetilly/gopher2600/hardware/television/specification"
"github.com/jetsetilly/gopher2600/hardware/tia"
"github.com/jetsetilly/gopher2600/logger"
)
@ -55,21 +51,14 @@ type Runner interface {
// reference.
type State struct {
level snapshotLevel
CPU *cpu.CPU
Mem *memory.Memory
RIOT *riot.RIOT
TIA *tia.TIA
TV *television.State
VCS *hardware.State
TV *television.State
}
func (s *State) snapshot() *State {
return &State{
level: s.level,
CPU: s.CPU.Snapshot(),
Mem: s.Mem.Snapshot(),
RIOT: s.RIOT.Snapshot(),
TIA: s.TIA.Snapshot(),
VCS: s.VCS.Snapshot(),
TV: s.TV.Snapshot(),
}
}
@ -340,10 +329,7 @@ func (r *Rewind) lastEntryIdx() int {
func snapshot(vcs *hardware.VCS, level snapshotLevel) *State {
return &State{
level: level,
CPU: vcs.CPU.Snapshot(),
Mem: vcs.Mem.Snapshot(),
RIOT: vcs.RIOT.Snapshot(),
TIA: vcs.TIA.Snapshot(),
VCS: vcs.Snapshot(),
TV: vcs.TV.Snapshot(),
}
}
@ -443,18 +429,10 @@ func (r *Rewind) append(s *State) {
func Plumb(vcs *hardware.VCS, state *State, fromDifferentEmulation bool) {
// tv plumbing works a bit different to other areas because we're only
// recording the state of the TV not the entire TV itself.
vcs.TV.PlumbState(vcs, state.TV.Snapshot())
// 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.TV.Plumb(vcs, state.TV)
// finish off plumbing process
vcs.Plumb(fromDifferentEmulation)
vcs.Plumb(state.VCS, fromDifferentEmulation)
}
// run from the supplied state until the cooridinates are reached.