mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
- 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()
202 lines
7.1 KiB
Go
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
|
|
}
|