more flexible yield information

This commit is contained in:
JetSetIlly 2023-07-12 07:51:06 +01:00
parent 123a96cd09
commit 81f4a83490
26 changed files with 303 additions and 200 deletions

View file

@ -22,7 +22,7 @@ import (
// YieldState records the most recent yield.
type YieldState struct {
InstructionPC uint32
Reason mapper.YieldReason
Reason mapper.CoProcYieldType
LocalVariables []*SourceVariableLocal
}
@ -32,15 +32,15 @@ func (y *YieldState) Cmp(w *YieldState) bool {
}
// OnYield implements the mapper.CartCoProcDeveloper interface.
func (dev *Developer) OnYield(instructionPC uint32, currentPC uint32, reason mapper.YieldReason) {
func (dev *Developer) OnYield(instructionPC uint32, currentPC uint32, yield mapper.CoProcYield) {
// do nothing if yield reason is YieldSyncWithVCS
//
// yielding for this reason is likely to be followed by another yield
// very soon after so there is no point gathering this information
if reason == mapper.YieldSyncWithVCS {
if yield.Type == mapper.YieldSyncWithVCS {
dev.BorrowYieldState(func(yld *YieldState) {
yld.InstructionPC = instructionPC
yld.Reason = reason
yld.Reason = yield.Type
yld.LocalVariables = yld.LocalVariables[:0]
})
@ -64,7 +64,7 @@ func (dev *Developer) OnYield(instructionPC uint32, currentPC uint32, reason map
}
// log a bug for any of these reasons
switch reason {
switch yield.Type {
case mapper.YieldMemoryAccessError:
fallthrough
case mapper.YieldExecutionError:
@ -127,7 +127,7 @@ func (dev *Developer) OnYield(instructionPC uint32, currentPC uint32, reason map
dev.BorrowYieldState(func(yld *YieldState) {
yld.InstructionPC = instructionPC
yld.Reason = reason
yld.Reason = yield.Type
// clear list of local variables from previous yield
yld.LocalVariables = yld.LocalVariables[:0]

View file

@ -858,14 +858,14 @@ func (dbg *Debugger) StartInPlayMode(filename string) error {
}
// CartYield implements the mapper.CartYieldHook interface.
func (dbg *Debugger) CartYield(reason mapper.YieldReason) bool {
func (dbg *Debugger) CartYield(yield mapper.CoProcYieldType) bool {
// if the emulator wants to quit we need to return true to instruct the
// cartridge to return to the main loop immediately
if !dbg.running {
return true
}
switch reason {
switch yield {
case mapper.YieldProgramEnded:
// expected reason for CDF and DPC+ cartridges
return false

View file

@ -599,7 +599,8 @@ func (dbg *Debugger) handleInterrupt(inputter terminal.Input) {
_, err = inputter.TermRead(confirm,
terminal.Prompt{
Content: "really quit (y/n) ",
Type: terminal.PromptTypeConfirm},
Type: terminal.PromptTypeConfirm,
},
dbg.events)
if err != nil {

View file

@ -21,59 +21,48 @@ import (
"github.com/jetsetilly/gopher2600/debugger/terminal"
"github.com/jetsetilly/gopher2600/disassembly"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
)
func (dbg *Debugger) buildPrompt() terminal.Prompt {
content := strings.Builder{}
s := strings.Builder{}
// to keep things simple for now, only the idle coprocessor state will
// result in a detailed 6507 prompt. the other states are better served
// (for now) by a descriptive prompt without any disassembly
//
// TODO: better prompts for non-idle coprocessor states
switch dbg.vcs.Mem.Cart.CoProcState() {
case mapper.CoProcNOPFeed:
content.WriteString(fmt.Sprintf("$%04x (NOP feed)", dbg.vcs.CPU.PC.Address()))
case mapper.CoProcStrongARMFeed:
content.WriteString(fmt.Sprintf("$%04x (StrongARM feed)", dbg.vcs.CPU.PC.Address()))
case mapper.CoProcParallel:
content.WriteString(fmt.Sprintf("$%04x (parallel)", dbg.vcs.CPU.PC.Address()))
case mapper.CoProcIdle:
var e *disassembly.Entry
var e *disassembly.Entry
// decide which address value to use
if dbg.vcs.CPU.LastResult.Final || dbg.vcs.CPU.HasReset() {
e = dbg.Disasm.GetEntryByAddress(dbg.vcs.CPU.PC.Address())
} else {
// if we're in the middle of an instruction then use the addresss in
// lastResult. in these instances we want the prompt to report the
// instruction that the CPU is working on, not the next one to be
// stepped into.
e = dbg.liveDisasmEntry
}
// decide which address value to use
if dbg.vcs.CPU.LastResult.Final || dbg.vcs.CPU.HasReset() {
e = dbg.Disasm.GetEntryByAddress(dbg.vcs.CPU.PC.Address())
} else {
// if we're in the middle of an instruction then use the addresss in
// lastResult. in these instances we want the prompt to report the
// instruction that the CPU is working on, not the next one to be
// stepped into.
e = dbg.liveDisasmEntry
}
// build prompt based on how confident we are of the contents of the
// disassembly entry. starting with the condition of no disassembly at all
if e == nil {
content.WriteString(fmt.Sprintf("$%04x", dbg.vcs.CPU.PC.Address()))
} else if e.Level == disassembly.EntryLevelUnmappable {
content.WriteString(e.Address)
} else {
// this is the ideal path. the address is in the disassembly and we've
// decoded it already
content.WriteString(fmt.Sprintf("%s %s", e.Address, e.Operator))
// build prompt based on how confident we are of the contents of the
// disassembly entry. starting with the condition of no disassembly at all
if e == nil {
s.WriteString(fmt.Sprintf("$%04x", dbg.vcs.CPU.PC.Address()))
} else if e.Level == disassembly.EntryLevelUnmappable {
s.WriteString(e.Address)
} else {
// this is the ideal path. the address is in the disassembly and we've
// decoded it already
s.WriteString(fmt.Sprintf("%s %s", e.Address, e.Operator))
if e.Operand.String() != "" {
content.WriteString(fmt.Sprintf(" %s", e.Operand))
}
if e.Operand.String() != "" {
s.WriteString(fmt.Sprintf(" %s", e.Operand))
}
}
p := terminal.Prompt{
Content: content.String(),
Content: s.String(),
Recording: dbg.scriptScribe.IsActive(),
CPURdy: dbg.vcs.CPU.RdyFlg,
}
if coproc := dbg.vcs.Mem.Cart.GetCoProc(); coproc != nil {
state := coproc.CoProcExecutionState()
p.Yield = state.Yield
}
// LastResult final is false on CPU reset so we must check for that also

View file

@ -133,7 +133,7 @@ func (ct *ColorTerminal) TermRead(input []byte, prompt terminal.Prompt, events *
inputLen = 0
cursorPos = 0
ct.EasyTerm.TermPrint("\r")
ct.EasyTerm.TermPrint(ansi.CursorMove(len(prompt.Content)))
ct.EasyTerm.TermPrint(ansi.CursorMove(len(prompt.String())))
} else {
// there is no input so return UserInterrupt error
ct.EasyTerm.TermPrint("\r\n")

View file

@ -15,17 +15,21 @@
package terminal
import "strings"
import (
"strings"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
)
// Prompt specifies the prompt text and the prompt style. For CPUStep
// and VideoStep prompt types thre is some additional information that
// can be used to decorate the prompt.
type Prompt struct {
Content string
Type PromptType
Type PromptType
Content string
Yield mapper.CoProcYield
// valid for PromptTypeCPUStep and PromptTypeVideoStep
CPURdy bool
Recording bool
}
@ -36,6 +40,7 @@ type PromptType int
const (
PromptTypeCPUStep PromptType = iota
PromptTypeVideoStep
PromptTypeCartYield
PromptTypeConfirm
)
@ -54,17 +59,15 @@ func (p Prompt) String() string {
}
s.WriteString(" ")
s.WriteString(p.Content)
s.WriteString(" ]")
if !p.CPURdy {
s.WriteString(" !")
}
if p.Type == PromptTypeCPUStep {
switch p.Type {
case PromptTypeCPUStep:
s.WriteString(" >> ")
} else {
case PromptTypeVideoStep:
s.WriteString(" > ")
case PromptTypeCartYield:
s.WriteString(" . ")
}
return s.String()

View file

@ -623,7 +623,7 @@ func (scr *screen) reflectionColor(ref *reflection.ReflectedVideoStep) color.RGB
return reflectionColors[reflection.AudioPhase1]
}
case reflection.OverlayLabels[reflection.OverlayCoproc]:
switch ref.CoProcState {
switch ref.CoProcSync {
case mapper.CoProcIdle:
return reflectionColors[reflection.CoProcInactive]
case mapper.CoProcNOPFeed:

View file

@ -312,7 +312,7 @@ func (win *winCoProcPerformance) draw() {
}
func (win *winCoProcPerformance) drawFrameStats() {
accumulate := func(s mapper.CoProcState) int {
accumulate := func(s mapper.CoProcSynchronisation) int {
switch s {
case mapper.CoProcIdle:
case mapper.CoProcNOPFeed:
@ -354,18 +354,18 @@ func (win *winCoProcPerformance) drawFrameStats() {
switch win.kernelFocus {
case developer.KernelAny:
clockCount += float32(accumulate(r.CoProcState))
clockCount += float32(accumulate(r.CoProcSync))
case developer.KernelScreen:
if sl >= win.img.screen.crit.frameInfo.VisibleTop && sl <= win.img.screen.crit.frameInfo.VisibleBottom {
clockCount += float32(accumulate(r.CoProcState))
clockCount += float32(accumulate(r.CoProcSync))
}
case developer.KernelVBLANK:
if sl < win.img.screen.crit.frameInfo.VisibleTop {
clockCount += float32(accumulate(r.CoProcState))
clockCount += float32(accumulate(r.CoProcSync))
}
case developer.KernelOverscan:
if sl > win.img.screen.crit.frameInfo.VisibleBottom {
clockCount += float32(accumulate(r.CoProcState))
clockCount += float32(accumulate(r.CoProcSync))
}
}
}

View file

@ -734,7 +734,7 @@ func (win *winDbgScr) drawReflectionTooltip() {
}
case reflection.OverlayLabels[reflection.OverlayCoproc]:
imguiSeparator()
switch ref.CoProcState {
switch ref.CoProcSync {
case mapper.CoProcIdle:
imgui.Text(fmt.Sprintf("%s is idle", win.img.lz.Cart.CoProcID))
case mapper.CoProcNOPFeed:

View file

@ -184,6 +184,13 @@ func (win *winTerm) debuggerDraw() bool {
}
} else {
imgui.Text(win.prompt.Content)
if !win.prompt.Yield.Type.Normal() {
imgui.SameLine()
imgui.Text(win.prompt.Yield.Type.String())
if win.prompt.Yield.Detail != nil {
imguiTooltipSimple(win.prompt.Yield.Detail.Error(), true)
}
}
}
// chevron indicator

View file

@ -36,11 +36,6 @@ type Ace struct {
// the hook that handles cartridge yields
yieldHook mapper.CartYieldHook
// parallelARM is true whenever the address bus is not a cartridge address (ie.
// a TIA or RIOT address). this means that the arm is running unhindered
// and will not have yielded for that colour clock
parallelARM bool
// armState is a copy of the ARM's state at the moment of the most recent
// Snapshot. it's used only suring a Plumb() operation
armState *arm.ARMState
@ -154,15 +149,15 @@ func (cart *Ace) Patch(_ int, _ uint8) error {
func (cart *Ace) runARM() {
// call arm once and then check for yield conditions
yld, _ := cart.arm.Run()
cart.mem.yield, _ = cart.arm.Run()
// keep calling runArm() for as long as program does not need to sync with the VCS...
for yld != mapper.YieldSyncWithVCS {
for cart.mem.yield.Type != mapper.YieldSyncWithVCS {
// ... or if the yield hook says to return to the VCS immedtiately
if cart.yieldHook.CartYield(yld) {
if cart.yieldHook.CartYield(cart.mem.yield.Type) {
return
}
yld, _ = cart.arm.Run()
cart.mem.yield, _ = cart.arm.Run()
}
}
@ -170,7 +165,7 @@ func (cart *Ace) runARM() {
func (cart *Ace) AccessPassive(addr uint16, data uint8) {
// if memory access is not a cartridge address (ie. a TIA or RIOT address)
// then the ARM is running in parallel (ie. no synchronisation)
cart.parallelARM = (addr&memorymap.OriginCart != memorymap.OriginCart)
cart.mem.parallelARM = (addr&memorymap.OriginCart != memorymap.OriginCart)
// start profiling before the run sequence
if cart.dev != nil {
@ -247,12 +242,18 @@ func (cart *Ace) ExecutableOrigin() uint32 {
return cart.mem.flashARMOrigin
}
// CoProcState implements the mapper.CartCoProc interface.
func (cart *Ace) CoProcState() mapper.CoProcState {
if cart.parallelARM {
return mapper.CoProcParallel
// CoProcExecutionState implements the mapper.CartCoProc interface.
func (cart *Ace) CoProcExecutionState() mapper.CoProcExecutionState {
if cart.mem.parallelARM {
return mapper.CoProcExecutionState{
Sync: mapper.CoProcParallel,
Yield: cart.mem.yield,
}
}
return mapper.CoProcExecutionState{
Sync: mapper.CoProcStrongARMFeed,
Yield: cart.mem.yield,
}
return mapper.CoProcStrongARMFeed
}
// CoProcRegister implements the mapper.CartCoProc interface.

View file

@ -82,6 +82,14 @@ type aceMemory struct {
flashARMMemtop uint32
arm interruptARM
// parallelARM is true whenever the address bus is not a cartridge address (ie.
// a TIA or RIOT address). this means that the arm is running unhindered
// and will not have yielded for that colour clock
parallelARM bool
// most recent yield from the coprocessor
yield mapper.CoProcYield
}
const (

View file

@ -110,7 +110,7 @@ type ARMState struct {
interrupt bool
// the yield reason explains the reason for why the ARM execution ended
yieldReason mapper.YieldReason
yield mapper.CoProcYield
// for clarity both the interrupt and yieldReason fields should be set at
// the same time wher possible
@ -590,7 +590,13 @@ func (arm *ARM) SetInitialRegisters(args ...uint32) error {
// previous execution ran to completion (ie. was uninterrupted).
//
// Returns the yield reason, the number of ARM cycles consumed.
func (arm *ARM) Run() (mapper.YieldReason, float32) {
func (arm *ARM) Run() (mapper.CoProcYield, float32) {
if arm.dev != nil {
defer func() {
arm.dev.OnYield(arm.state.instructionPC, arm.state.registers[rPC], arm.state.yield)
}()
}
if !arm.state.interrupt {
arm.resetRegisters()
}
@ -598,7 +604,7 @@ func (arm *ARM) Run() (mapper.YieldReason, float32) {
// reset cycles count
arm.state.cyclesTotal = 0
// arm.staten.prefetchCycle reset in reset() function. we don't want to change
// arm.state.prefetchCycle reset in reset() function. we don't want to change
// the value if we're resuming from a yield
// reset continue flag and error conditions
@ -618,12 +624,10 @@ func (arm *ARM) Run() (mapper.YieldReason, float32) {
if err != nil {
logger.Logf("ARM7", err.Error())
// returing early so we must call OnYield here
if arm.dev != nil {
arm.dev.OnYield(arm.state.instructionPC, arm.state.registers[rPC], arm.state.yieldReason)
}
arm.state.yield.Type = mapper.YieldMemoryAccessError
arm.state.yield.Detail = err
return mapper.YieldMemoryAccessError, 0
return arm.state.yield, 0
}
// fill pipeline cannot happen immediately after resetRegisters()
@ -633,7 +637,8 @@ func (arm *ARM) Run() (mapper.YieldReason, float32) {
// default to an uninterrupted state and a sync with VCS yield reason
arm.state.interrupt = false
arm.state.yieldReason = mapper.YieldSyncWithVCS
arm.state.yield.Type = mapper.YieldSyncWithVCS
arm.state.yield.Detail = nil
return arm.run()
}
@ -680,7 +685,7 @@ func (arm *ARM) BreakpointsEnable(enable bool) {
arm.breakpointsEnabled = enable
}
func (arm *ARM) run() (mapper.YieldReason, float32) {
func (arm *ARM) run() (mapper.CoProcYield, float32) {
select {
case <-arm.prefsPulse.C:
arm.updatePrefs()
@ -905,7 +910,9 @@ func (arm *ARM) run() (mapper.YieldReason, float32) {
}
if arm.abortOnStackCollision && arm.breakpointsEnabled {
return mapper.YieldMemoryAccessError, 0
arm.state.interrupt = true
arm.state.yield.Type = mapper.YieldMemoryAccessError
arm.state.yield.Detail = err
}
}
@ -924,16 +931,16 @@ func (arm *ARM) run() (mapper.YieldReason, float32) {
logger.Logf("ARM7: memory error", arm.disasmVerbose(entry))
}
if arm.abortOnIllegalMem && arm.breakpointsEnabled {
arm.state.interrupt = true
arm.state.yield.Type = mapper.YieldMemoryAccessError
arm.state.yield.Detail = arm.memoryError
}
// we need to reset the memory error instances so that we don't end
// up printing the same message over and over
arm.memoryError = nil
arm.memoryErrorDev = nil
if arm.abortOnIllegalMem && arm.breakpointsEnabled {
arm.state.interrupt = true
arm.state.yieldReason = mapper.YieldMemoryAccessError
}
}
// handle execution errors
@ -942,7 +949,8 @@ func (arm *ARM) run() (mapper.YieldReason, float32) {
if arm.breakpointsEnabled {
arm.state.interrupt = true
arm.state.yieldReason = mapper.YieldExecutionError
arm.state.yield.Type = mapper.YieldExecutionError
arm.state.yield.Detail = arm.executionError
}
}
@ -952,7 +960,8 @@ func (arm *ARM) run() (mapper.YieldReason, float32) {
if arm.dev != nil && arm.breakpointsEnabled && !arm.state.function32bitDecoding {
if arm.dev.CheckBreakpoint(arm.state.instructionPC) {
arm.state.interrupt = true
arm.state.yieldReason = mapper.YieldBreakpoint
arm.state.yield.Type = mapper.YieldBreakpoint
arm.state.yield.Detail = fmt.Errorf("%08x", arm.state.instructionPC)
}
}
@ -965,12 +974,7 @@ func (arm *ARM) run() (mapper.YieldReason, float32) {
}
}
// update yield information
if arm.dev != nil {
arm.dev.OnYield(arm.state.instructionPC, arm.state.registers[rPC], arm.state.yieldReason)
}
return arm.state.yieldReason, arm.state.cyclesTotal
return arm.state.yield, arm.state.cyclesTotal
}
func (arm *ARM) stepARM7TDMI(opcode uint16, memIdx int) {

View file

@ -16,6 +16,7 @@
package arm
import (
"errors"
"fmt"
"math/bits"
@ -919,7 +920,8 @@ func (arm *ARM) decodeThumbHiRegisterOps(opcode uint16) *DisasmEntry {
// cannot switch to ARM mode in the ARMv7-M architecture
arm.continueExecution = false
arm.state.interrupt = true
arm.state.yieldReason = mapper.YieldUndefinedBehaviour
arm.state.yield.Type = mapper.YieldUndefinedBehaviour
arm.state.yield.Detail = errors.New("cannot switch to ARM mode in ARMv7-M architecture")
}
arm.state.registers[rPC] = (target + 2) & 0xfffffffe
} else {
@ -929,7 +931,8 @@ func (arm *ARM) decodeThumbHiRegisterOps(opcode uint16) *DisasmEntry {
// cannot switch to ARM mode in the ARMv7-M architecture
arm.continueExecution = false
arm.state.interrupt = true
arm.state.yieldReason = mapper.YieldUndefinedBehaviour
arm.state.yield.Type = mapper.YieldUndefinedBehaviour
arm.state.yield.Detail = errors.New("cannot switch to ARM mode in ARMv7-M architecture")
}
arm.state.registers[rPC] = (target + 2) & 0xfffffffe
}
@ -996,7 +999,8 @@ func (arm *ARM) decodeThumbHiRegisterOps(opcode uint16) *DisasmEntry {
if !res.InterruptServiced {
arm.continueExecution = false
arm.state.interrupt = false
arm.state.yieldReason = mapper.YieldProgramEnded
arm.state.yield.Type = mapper.YieldProgramEnded
arm.state.yield.Detail = nil
// "7.6 Data Operations" in "ARM7TDMI-S Technical Reference Manual r4p3"
// - interrupted
return nil

View file

@ -149,7 +149,8 @@ func (arm *ARM) decodeThumb2Miscellaneous(opcode uint16) decodeFunction {
return func(_ uint16) *DisasmEntry {
arm.continueExecution = false
arm.state.interrupt = true
arm.state.yieldReason = mapper.YieldSyncWithVCS
arm.state.yield.Type = mapper.YieldSyncWithVCS
arm.state.yield.Detail = nil
return nil
}
} else if opcode&0xff00 == 0xba00 {

View file

@ -533,12 +533,15 @@ func (cart *Cartridge) SetYieldHook(hook mapper.CartYieldHook) {
}
}
// CoProcState implements the mapper.CartCoProc interface.
func (cart *Cartridge) CoProcState() mapper.CoProcState {
// CoProcExecutionState implements the mapper.CartCoProc interface
//
// If cartridge does not have a coprocessor then an empty instance of
// mapper.CoProcExecutionState is returned
func (cart *Cartridge) CoProcExecutionState() mapper.CoProcExecutionState {
if cart.hasCoProc {
return cart.coproc.CoProcState()
return cart.coproc.CoProcExecutionState()
}
return mapper.CoProcIdle
return mapper.CoProcExecutionState{}
}
// BusStuff implements the mapper.CartBusStuff interface.

View file

@ -379,15 +379,15 @@ func (cart *cdf) AccessVolatile(addr uint16, data uint8, poke bool) error {
}
// call runArm() once and then check for yield conditions
yld := cart.runArm()
cart.state.yield = cart.runArm()
// keep calling runArm() for as long as program has not ended...
for yld != mapper.YieldProgramEnded {
for cart.state.yield.Type != mapper.YieldProgramEnded {
// ... or if the yield hook says to return to the VCS immediately
if cart.yieldHook.CartYield(yld) {
if cart.yieldHook.CartYield(cart.state.yield.Type) {
break // for loop
}
yld = cart.runArm()
cart.state.yield = cart.runArm()
}
}
@ -664,12 +664,18 @@ func (cart *cdf) HotLoad(data []byte) error {
return nil
}
// CoProcState implements the mapper.CartCoProc interface.
func (cart *cdf) CoProcState() mapper.CoProcState {
// CoProcExecutionState implements the mapper.CartCoProc interface.
func (cart *cdf) CoProcExecutionState() mapper.CoProcExecutionState {
if cart.state.callfn.IsActive() {
return mapper.CoProcNOPFeed
return mapper.CoProcExecutionState{
Sync: mapper.CoProcNOPFeed,
Yield: cart.state.yield,
}
}
return mapper.CoProcExecutionState{
Sync: mapper.CoProcIdle,
Yield: cart.state.yield,
}
return mapper.CoProcIdle
}
// CoProcRegister implements the mapper.CartCoProc interface.
@ -715,7 +721,7 @@ func (cart *cdf) SetYieldHook(hook mapper.CartYieldHook) {
cart.yieldHook = hook
}
func (cart *cdf) runArm() mapper.YieldReason {
func (cart *cdf) runArm() mapper.CoProcYield {
yld, cycles := cart.arm.Run()
cart.state.callfn.Accumulate(cycles)

View file

@ -15,7 +15,10 @@
package cdf
import "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm/callfn"
import (
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm/callfn"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
)
type State struct {
// currently selected bank
@ -42,6 +45,9 @@ type State struct {
// the callfn process is stateful
callfn callfn.CallFn
// most recent yield from the coprocessor
yield mapper.CoProcYield
}
// initialise should be called as soon as convenient.

View file

@ -507,15 +507,15 @@ func (cart *dpcPlus) AccessVolatile(addr uint16, data uint8, poke bool) error {
cart.dev.StartProfiling()
}
yld := cart.runArm()
cart.state.yield = cart.runArm()
// keep calling runArm() for as long as program has not ended...
for yld != mapper.YieldProgramEnded {
for cart.state.yield.Type != mapper.YieldProgramEnded {
// ... or if the yield hook says to return to the VCS immediately
if cart.yieldHook.CartYield(yld) {
if cart.yieldHook.CartYield(cart.state.yield.Type) {
break // for loop
}
yld = cart.runArm()
cart.state.yield = cart.runArm()
}
}
@ -917,12 +917,18 @@ func (cart *dpcPlus) ARMinterrupt(addr uint32, val1 uint32, val2 uint32) (arm.AR
return arm.ARMinterruptReturn{}, nil
}
// CoProcState implements the mapper.CartCoProc interface.
func (cart *dpcPlus) CoProcState() mapper.CoProcState {
// CoProcExecutionState implements the mapper.CartCoProc interface.
func (cart *dpcPlus) CoProcExecutionState() mapper.CoProcExecutionState {
if cart.state.callfn.IsActive() {
return mapper.CoProcNOPFeed
return mapper.CoProcExecutionState{
Sync: mapper.CoProcNOPFeed,
Yield: cart.state.yield,
}
}
return mapper.CoProcExecutionState{
Sync: mapper.CoProcIdle,
Yield: cart.state.yield,
}
return mapper.CoProcIdle
}
// CoProcRegister implements the mapper.CartCoProc interface.
@ -968,7 +974,7 @@ func (cart *dpcPlus) SetYieldHook(hook mapper.CartYieldHook) {
cart.yieldHook = hook
}
func (cart *dpcPlus) runArm() mapper.YieldReason {
func (cart *dpcPlus) runArm() mapper.CoProcYield {
yld, cycles := cart.arm.Run()
cart.state.callfn.Accumulate(cycles)
return yld

View file

@ -17,6 +17,7 @@ package dpcplus
import (
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm/callfn"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/random"
)
@ -42,6 +43,9 @@ type State struct {
// the callfn process is stateful
callfn callfn.CallFn
// most recent yield from the coprocessor
yield mapper.CoProcYield
}
func newDPCPlusState() *State {

View file

@ -43,11 +43,6 @@ type Elf struct {
// the hook that handles cartridge yields
yieldHook mapper.CartYieldHook
// parallelARM is true whenever the address bus is not a cartridge address (ie.
// a TIA or RIOT address). this means that the arm is running unhindered
// and will not have yielded for that colour clock
parallelARM bool
// armState is a copy of the ARM's state at the moment of the most recent
// Snapshot. it's used only suring a Plumb() operation
armState *arm.ARMState
@ -258,15 +253,15 @@ func (cart *Elf) runARM() {
}
// call arm once and then check for yield conditions
yld, _ := cart.arm.Run()
cart.mem.yield, _ = cart.arm.Run()
// keep calling runArm() for as long as program does not need to sync with the VCS...
for yld != mapper.YieldSyncWithVCS {
for cart.mem.yield.Type != mapper.YieldSyncWithVCS {
// ... or if the yield hook says to return to the VCS immediately
if cart.yieldHook.CartYield(yld) {
if cart.yieldHook.CartYield(cart.mem.yield.Type) {
return
}
yld, _ = cart.arm.Run()
cart.mem.yield, _ = cart.arm.Run()
}
}
@ -305,7 +300,7 @@ func (cart *Elf) runStrongarm(addr uint16, data uint8) bool {
func (cart *Elf) AccessPassive(addr uint16, data uint8) {
// if memory access is not a cartridge address (ie. a TIA or RIOT address)
// then the ARM is running in parallel (ie. no synchronisation)
cart.parallelARM = (addr&memorymap.OriginCart != memorymap.OriginCart)
cart.mem.parallelARM = (addr&memorymap.OriginCart != memorymap.OriginCart)
// if address is the reset address then trigger the reset procedure
if (addr&memorymap.CartridgeBits)|memorymap.OriginCart == (cpubus.Reset&memorymap.CartridgeBits)|memorymap.OriginCart {
@ -405,12 +400,18 @@ func (cart *Elf) ELFSection(name string) ([]uint8, uint32, bool) {
return nil, 0, false
}
// CoProcState implements the mapper.CartCoProc interface.
func (cart *Elf) CoProcState() mapper.CoProcState {
if cart.parallelARM {
return mapper.CoProcParallel
// CoProcExecutionState implements the mapper.CartCoProc interface.
func (cart *Elf) CoProcExecutionState() mapper.CoProcExecutionState {
if cart.mem.parallelARM {
return mapper.CoProcExecutionState{
Sync: mapper.CoProcParallel,
Yield: cart.mem.yield,
}
}
return mapper.CoProcExecutionState{
Sync: mapper.CoProcStrongARMFeed,
Yield: cart.mem.yield,
}
return mapper.CoProcStrongARMFeed
}
// CoProcRegister implements the mapper.CartCoProc interface.
@ -455,3 +456,23 @@ func (cart *Elf) BreakpointsEnable(enable bool) {
func (cart *Elf) SetYieldHook(hook mapper.CartYieldHook) {
cart.yieldHook = hook
}
// GetStatic implements the mapper.CartStaticBus interface.
func (cart *Elf) GetStatic() mapper.CartStatic {
return cart.mem.Snapshot()
}
// StaticWrite implements the mapper.CartStaticBus interface.
func (cart *Elf) PutStatic(segment string, idx int, data uint8) bool {
mem, ok := cart.mem.Reference(segment)
if !ok {
return false
}
if idx >= len(mem) {
return false
}
mem[idx] = data
return true
}

View file

@ -123,6 +123,14 @@ type elfMemory struct {
// args is a special memory area that is used for the arguments passed to
// the main function on startup
args []byte
// parallelARM is true whenever the address bus is not a cartridge address (ie.
// a TIA or RIOT address). this means that the arm is running unhindered
// and will not have yielded for that colour clock
parallelARM bool
// most recent yield from the coprocessor
yield mapper.CoProcYield
}
func newElfMemory(ef *elf.File) (*elfMemory, error) {
@ -674,23 +682,3 @@ func (m *elfMemory) Read32bit(addr uint32) (uint32, bool) {
uint32((*mem)[addr+2])<<16 |
uint32((*mem)[addr+3])<<24, true
}
// GetStatic implements the mapper.CartStaticBus interface.
func (cart *Elf) GetStatic() mapper.CartStatic {
return cart.mem.Snapshot()
}
// StaticWrite implements the mapper.CartStaticBus interface.
func (cart *Elf) PutStatic(segment string, idx int, data uint8) bool {
mem, ok := cart.mem.Reference(segment)
if !ok {
return false
}
if idx >= len(mem) {
return false
}
mem[idx] = data
return true
}

View file

@ -19,16 +19,35 @@ import (
"fmt"
)
// CoProcState is used to describe the state of the coprocessor. We can think
// of these values as descriptions of the synchronisation state between the
// coprocessor and the VCS.
type CoProcState int
// CoProcExecutionState details the current condition of the coprocessor's execution
type CoProcExecutionState struct {
Sync CoProcSynchronisation
Yield CoProcYield
}
// List of valid CoProcState values. A mapper will probably not employ all of
// these states depending on the synchronisation strategy.
// CoProcSynchronisation is used to describe the VCS synchronisation state of
// the coprocessor
type CoProcSynchronisation int
func (s CoProcSynchronisation) String() string {
switch s {
case CoProcIdle:
return "idle"
case CoProcNOPFeed:
return "nop feed"
case CoProcStrongARMFeed:
return "strongarm feed"
case CoProcParallel:
return "parallel"
}
panic("unknown CoProcSynchronisation")
}
// List of valid CoProcSynchronisation values.
//
// In reality the state will alternate between Idle-and-NOPFeed and
// StronARMFeed-and-Parallel.
// A mapper will probably not employ all of these states depending on the
// synchronisation strategy. In reality the state will alternate between
// Idle-and-NOPFeed and StronARMFeed-and-Parallel.
//
// Other synchronisation strategies may need the addition of additional states
// or a different mechanism altogether.
@ -36,7 +55,7 @@ const (
// the idle state means that the coprocessor is not interacting with the
// VCS at that moment. the coprocessor might be running but it is waiting
// to be instructed by the VCS program
CoProcIdle CoProcState = iota
CoProcIdle CoProcSynchronisation = iota
// a NOP feed describes the state where a cartridge mapper is waiting for
// the coprocessor to finish processing. in the meantime, the cartridge is
@ -56,21 +75,21 @@ const (
// coprocessor has reached a breakpoint or some other yield point (eg.
// undefined behaviour)
type CartYieldHook interface {
// CartYield returns true if the YieldReason cannot be handled without
// CartYield returns true if the yield type cannot be handled without
// breaking into a debugging loop
//
// CartYield will return true if the cartridge mapper should cancel
// CartYield will also return true if the cartridge mapper should cancel
// coprocessing immediately
//
// all other yield reasons will return false
CartYield(YieldReason) bool
CartYield(CoProcYieldType) bool
}
// StubCartYieldHook is a stub implementation for the CartYieldHook interface.
type StubCartYieldHook struct{}
// CartYield is a stub implementation for the CartYieldHook interface.
func (_ StubCartYieldHook) CartYield(_ YieldReason) bool {
func (_ StubCartYieldHook) CartYield(_ CoProcYieldType) bool {
return true
}
@ -84,7 +103,7 @@ type CartCoProc interface {
SetDeveloper(CartCoProcDeveloper)
// the state of the coprocessor
CoProcState() CoProcState
CoProcExecutionState() CoProcExecutionState
// breakpoint control of coprocessor
BreakpointsEnable(bool)
@ -168,42 +187,74 @@ type CartCoProcDeveloper interface {
// OnYield is called whenever the ARM yields to the VCS. It communicates the PC of the most
// recent instruction, the current PC (as it is now), and the reason for the yield
OnYield(instructionPC uint32, currentPC uint32, reason YieldReason)
OnYield(instructionPC uint32, currentPC uint32, reason CoProcYield)
}
// YieldReason specifies the reason for a yield
type YieldReason string
// CoProcYield describes a coprocessor yield state
type CoProcYield struct {
Type CoProcYieldType
Detail error
}
// List of YieldReason values
// CoProcYieldType specifies the type of yield. This is a broad categorisation
type CoProcYieldType int
func (t CoProcYieldType) String() string {
switch t {
case YieldProgramEnded:
return "ended"
case YieldSyncWithVCS:
return "sync"
case YieldBreakpoint:
return "break"
case YieldUndefinedBehaviour:
return "undefined behaviour"
case YieldUnimplementedFeature:
return "unimplement feature"
case YieldMemoryAccessError:
return "memory error"
case YieldExecutionError:
return "execution error"
}
panic("unknown CoProcYieldType")
}
// Normal returns true if yield type is expected during normal operation of the
// coprocessor
func (t CoProcYieldType) Normal() bool {
return t == YieldProgramEnded || t == YieldSyncWithVCS
}
// List of CoProcYieldType values
const (
// the coprocessor has yielded because the program has ended. in this instance the
// CoProcessor is not considered to be in a "yielded" state and can be modified
//
// Expected YieldReason for CDF and DPC+ type ROMs
YieldProgramEnded YieldReason = "Program Ended"
YieldProgramEnded CoProcYieldType = iota
// the coprocessor has reached a synchronisation point in the program. it
// must wait for the VCS before continuing
//
// Expected YieldReason for ACE and ELF type ROMs
YieldSyncWithVCS YieldReason = "Sync With VCS"
YieldSyncWithVCS
// a user supplied breakpoint has been encountered
YieldBreakpoint YieldReason = "Breakpoint"
YieldBreakpoint
// the program has triggered undefined behaviour in the coprocessor
YieldUndefinedBehaviour YieldReason = "Undefined Behaviour"
YieldUndefinedBehaviour
// the program has triggered an unimplemented feature in the coprocessor
YieldUnimplementedFeature YieldReason = "Unimplemented Feature"
YieldUnimplementedFeature
// the program has tried to access memory illegally. details will have been
// communicated by the IllegalAccess() function of the CartCoProcDeveloper
// interface
YieldMemoryAccessError YieldReason = "Memory Access Error"
YieldMemoryAccessError
// execution error indicates that something has gone very wrong
YieldExecutionError YieldReason = "Execution Error"
YieldExecutionError
)
// CartCoProcDisasmSummary represents a summary of a coprocessor execution.

View file

@ -39,8 +39,8 @@ const (
// for graphical purposes we only distinguish between active and inactive
// coprocessor states. the underlying states as defined in the mapper
// package (mapper.CoProcState) are used to decided whether the coproc is
// active or inactive
// package (mapper.CoProcSynchronisation) are used to decided whether the
// coproc is active or inactive
CoProcInactive
CoProcActive
)

View file

@ -59,7 +59,7 @@ type ReflectedVideoStep struct {
VideoElement video.Element
WSYNC bool
IsRAM bool
CoProcState mapper.CoProcState
CoProcSync mapper.CoProcSynchronisation
IsHblank bool
RSYNCalign bool
RSYNCreset bool

View file

@ -100,7 +100,7 @@ func (ref *Reflector) Step(bank mapper.BankInfo) error {
h[0].Signal = sig
h[0].Collision = *ref.vcs.TIA.Video.Collisions
h[0].IsHblank = ref.vcs.TIA.Hblank
h[0].CoProcState = ref.vcs.Mem.Cart.CoProcState()
h[0].CoProcSync = ref.vcs.Mem.Cart.CoProcExecutionState().Sync
if ref.vcs.TIA.Hmove.Future.IsActive() {
h[0].Hmove.Delay = true