Gopher2600/debugger/reflection/reflection.go
steve f39d1d33e3 o debugger
- removed REFLECT ON/OFF system
    - the performance hit is not that great

o cpu
    - added backward branching test
    - added branching page fault test

o debugger_test
    - increased timeout for rcvOutput()
2020-01-05 18:58:42 +00:00

202 lines
7.1 KiB
Go

// Package reflection monitors the emulated hardware for conditions that would
// otherwise not be visible. In particular it signals the MetaPixelRenderer
// when certain memory addresses have been written to. For example, the HMOVE
// register.
//
// In addition it monitors the state of WSYNC and signals the
// MetaPixelRenderer when the CPU is idle. This makes for quite a nice visual
// indication of "lost cycles" or potential extra cycles that could be regained
// with a bit of reorgnisation.
//
// There are lots of other things we could potentially do with the reflection
// idea but as it currently is, it is a little underdeveloped. In particular,
// it's rather slow but I'm not too worried about that because this is for
// debugging not actually playing games and such.
//
// I think the next thing this needs is a way of making the various monitors
// switchable at runtime. As it is, what's compiled is what we get. If we
// monitored every possible thing, the MetaPixelRenderer would get cluttered
// very quickly. It would be nice to be able to define groups (say, a player
// sprites group, a HMOVE group, etc.) and to turn them on and off according to
// our needs.
package reflection
import (
"gopher2600/gui"
"gopher2600/hardware"
"gopher2600/hardware/memory"
"gopher2600/hardware/tia/future"
)
// Monitor watches for writes to specific video related memory locations. when
// these locations are written to, a signal is sent to the metapixels.Renderer
// implementation. moreover, if the monitor detects that the effect of the
// memory write is delayed or sustained, then the signal is repeated as
// appropriate.
type Monitor struct {
vcs *hardware.VCS
renderer gui.MetaPixelRenderer
groupTIA addressMonitor
groupPlayer0 addressMonitor
groupPlayer1 addressMonitor
groupMissile0 addressMonitor
groupMissile1 addressMonitor
groupBall addressMonitor
}
// NewMonitor is the preferred method of initialisation for the Monitor type
func NewMonitor(vcs *hardware.VCS, renderer gui.MetaPixelRenderer) *Monitor {
mon := &Monitor{vcs: vcs, renderer: renderer}
mon.groupTIA.addresses = overlaySignals{
0x03: gui.MetaPixel{Label: "RSYNC", Red: 255, Green: 10, Blue: 0, Alpha: 255, Scheduled: true},
0x2a: gui.MetaPixel{Label: "HMOVE", Red: 255, Green: 20, Blue: 0, Alpha: 255, Scheduled: true},
0x2b: gui.MetaPixel{Label: "HMCLR", Red: 255, Green: 30, Blue: 0, Alpha: 255, Scheduled: false},
}
mon.groupPlayer0.addresses = overlaySignals{
0x04: gui.MetaPixel{Label: "NUSIZx", Red: 0, Green: 10, Blue: 255, Alpha: 255, Scheduled: true},
0x10: gui.MetaPixel{Label: "RESPx", Red: 0, Green: 30, Blue: 255, Alpha: 255, Scheduled: true},
}
mon.groupPlayer1.addresses = overlaySignals{
0x05: gui.MetaPixel{Label: "NUSIZx", Red: 0, Green: 50, Blue: 255, Alpha: 255, Scheduled: true},
0x11: gui.MetaPixel{Label: "RESPx", Red: 0, Green: 70, Blue: 255, Alpha: 255, Scheduled: true},
}
mon.groupMissile0.addresses = overlaySignals{
0x04: gui.MetaPixel{Label: "NUSIZx", Red: 0, Green: 50, Blue: 255, Alpha: 255, Scheduled: false},
0x11: gui.MetaPixel{Label: "RESMx", Red: 0, Green: 70, Blue: 0, Alpha: 255, Scheduled: true},
}
mon.groupMissile1.addresses = overlaySignals{
0x05: gui.MetaPixel{Label: "NUSIZx", Red: 0, Green: 50, Blue: 0, Alpha: 255, Scheduled: false},
0x12: gui.MetaPixel{Label: "RESMx", Red: 0, Green: 70, Blue: 0, Alpha: 255, Scheduled: true},
}
mon.groupBall.addresses = overlaySignals{
0x14: gui.MetaPixel{Label: "RESBL", Red: 0, Green: 255, Blue: 10, Alpha: 255, Scheduled: true},
}
return mon
}
// Check should be called every video cycle to record the current state of the
// emulation/system
func (mon *Monitor) Check() error {
if err := mon.checkWSYNC(); err != nil {
return err
}
if err := mon.groupTIA.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Delay); err != nil {
return err
}
if err := mon.groupPlayer0.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Video.Player0.Delay); err != nil {
return err
}
if err := mon.groupPlayer1.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Video.Player1.Delay); err != nil {
return err
}
if err := mon.groupMissile0.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Video.Missile0.Delay); err != nil {
return err
}
if err := mon.groupMissile1.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Video.Missile1.Delay); err != nil {
return err
}
if err := mon.groupBall.check(mon.renderer, mon.vcs.Mem, mon.vcs.TIA.Video.Ball.Delay); err != nil {
return err
}
return nil
}
func (mon *Monitor) checkWSYNC() error {
if mon.vcs.CPU.RdyFlg {
return nil
}
// special handling of WSYNC signal - we want every pixel to be coloured
// while the RdyFlag is false, not just when WSYNC is first triggered.
sig := gui.MetaPixel{Label: "WSYNC", Red: 0, Green: 0, Blue: 0, Alpha: 200}
return mon.renderer.SetMetaPixel(sig)
}
type overlaySignals map[uint16]gui.MetaPixel
type addressMonitor struct {
// the map of memory addresses to monitor
addresses overlaySignals
// when memory has been written to we note the address and timestamp. then,
// a few cycles later, we check to see if lastAddress is one the group is
// interested in seeing
lastAddress uint16
lastAddressAccessID int
lastAddressFound int
// if the memory write resulted in an effect that won't occur until
// sometime in the future then the Delay attribute for the part of the
// system monitored by the group will yield a pointer to the future Event
lastEvent *future.Event
// a copy of the last signal sent to the overlay renderer. we use
// this to repeat a signal when lastEvent is not nil and has not yet
// completed
signal gui.MetaPixel
}
func (adm *addressMonitor) check(rend gui.MetaPixelRenderer, mem *memory.VCSMemory, delay future.Observer) error {
// if a new memory location (any memory location) has been written, then
// note the new address and begin the delayed signalling process
//
// we filter on LastAccessTimeStamp rather than LastAccessAddress.
// filtering by address will probably work in most instances but it won't
// capture repeated writes to the same memory location.
if mem.LastAccessWrite && mem.LastAccessID != adm.lastAddressAccessID {
adm.lastAddress = mem.LastAccessAddress
adm.lastAddressAccessID = mem.LastAccessID
// 4 cycles seems plenty of time for an address to be serviced
adm.lastAddressFound = 4
}
var signalStart bool
var sig gui.MetaPixel
if adm.lastAddressFound > 0 {
if sig, signalStart = adm.addresses[adm.lastAddress]; signalStart {
if sig.Scheduled {
// associate memory write with delay observation
if ev, ok := delay.Observe(sig.Label); ok {
adm.lastEvent = ev
adm.signal = sig
adm.lastAddressFound = 1 // reduced to 0 almost immediately
}
} else {
adm.lastEvent = nil
adm.signal = sig
adm.lastAddressFound = 1 // reduced to 0 almost immediately
}
}
adm.lastAddressFound--
}
// send signal if an event is still running or if this is the end of a
// writeDelay period. the second condition catches memory writes that do
// not have an associated future.Event
if adm.lastEvent != nil || signalStart {
adm.lastEvent = nil
err := rend.SetMetaPixel(adm.signal)
if err != nil {
return err
}
}
return nil
}