diff --git a/debugger/deeppoke.go b/debugger/deeppoke.go index 31c45bc5..1b324840 100644 --- a/debugger/deeppoke.go +++ b/debugger/deeppoke.go @@ -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()) } } diff --git a/gui/sdlimgui/win_cart_ram.go b/gui/sdlimgui/win_cart_ram.go index aa877baf..b9ffc4f4 100644 --- a/gui/sdlimgui/win_cart_ram.go +++ b/gui/sdlimgui/win_cart_ram.go @@ -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 { diff --git a/gui/sdlimgui/win_cart_static.go b/gui/sdlimgui/win_cart_static.go index d3dd0131..4552609d 100644 --- a/gui/sdlimgui/win_cart_static.go +++ b/gui/sdlimgui/win_cart_static.go @@ -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 diff --git a/gui/sdlimgui/win_ram.go b/gui/sdlimgui/win_ram.go index 32f74488..fc21b717 100644 --- a/gui/sdlimgui/win_ram.go +++ b/gui/sdlimgui/win_ram.go @@ -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 } diff --git a/hardware/snapshot.go b/hardware/snapshot.go new file mode 100644 index 00000000..6c9cc227 --- /dev/null +++ b/hardware/snapshot.go @@ -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 . + +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) +} diff --git a/hardware/television/television.go b/hardware/television/television.go index 20df4e70..1abc26a2 100644 --- a/hardware/television/television.go +++ b/hardware/television/television.go @@ -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 diff --git a/hardware/vcs.go b/hardware/vcs.go index b553edee..05fa07e5 100644 --- a/hardware/vcs.go +++ b/hardware/vcs.go @@ -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. // diff --git a/rewind/rewind.go b/rewind/rewind.go index c9f71e0b..83166475 100644 --- a/rewind/rewind.go +++ b/rewind/rewind.go @@ -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.