added CYCLE quantum

updated QUANTUM and STEP commands to accoodate new quantum

control window changed to support the three quantum options

improved/corrected the conditions under which the ONSTEP command is run

disassembly.ExecutedEntry() updates existing entry
This commit is contained in:
JetSetIlly 2023-11-14 15:18:32 +00:00
parent 0e6b1adc2a
commit 9f6cbdad58
32 changed files with 355 additions and 315 deletions

View file

@ -145,7 +145,7 @@ func (dsm *Disassembly) Start() {
// have been called on the last CPU cycle of the instruction that triggers
// the coprocessor reset. the TV will not have moved onto the beginning of
// the next instruction yet so we must figure it out here
dsm.disasm.LastStart = dsm.tv.AdjCoords(television.AdjCPUCycle, 1)
dsm.disasm.LastStart = dsm.tv.AdjCoords(television.AdjCycle, 1)
}
dsm.disasm.LastExecution = dsm.disasm.LastExecution[:0]

View file

@ -242,20 +242,25 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
mode = strings.ToUpper(mode)
if back {
var instruction bool
// step backwards
var adj television.Adj
switch mode {
case "":
// continue with current quantum state
if dbg.Quantum() == govern.QuantumInstruction {
instruction = true
} else {
// use current quantum state
switch dbg.Quantum() {
case govern.QuantumCycle:
adj = television.AdjCycle
case govern.QuantumClock:
adj = television.AdjClock
}
case "INSTRUCTION":
dbg.setQuantum(govern.QuantumInstruction)
instruction = true
case "CYCLE":
dbg.setQuantum(govern.QuantumCycle)
adj = television.AdjCycle
case "CLOCK":
dbg.setQuantum(govern.QuantumClock)
adj = television.AdjClock
@ -269,7 +274,7 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
var coords coords.TelevisionCoords
if instruction {
if dbg.Quantum() == govern.QuantumInstruction {
coords = dbg.cpuBoundaryLastInstruction
} else {
coords = dbg.vcs.TV.AdjCoords(adj, adjAmount)
@ -277,59 +282,69 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
dbg.setState(govern.Rewinding)
dbg.unwindLoop(func() error {
// update catchupQuantum before starting rewind process
dbg.catchupQuantum = dbg.Quantum()
dbg.catchupContext = catchupStepBack
return dbg.Rewind.GotoCoords(coords)
})
return nil
}
} else {
// step forwards
// step forward
switch mode {
case "":
// continue with current quantum state
switch mode {
case "":
// continue with current quantum state
// if quantum is instruction and CPU is not RDY then STEP is best
// implemented as TRAP RDY
if dbg.Quantum() == govern.QuantumInstruction && !dbg.vcs.CPU.RdyFlg {
_ = dbg.halting.volatileTraps.parseCommand(commandline.TokeniseInput("RDY"))
// if quantum is not the QuantumClock and CPU is not RDY then STEP
// is best implemented as TRAP RDY. this means that the emulation
// will stop on the next instruction boundary and will also skip
// over instructions that trigger WSYNC
//
// this behaviour is more intuitive to the user because it means
// they don't have to step over every cycle during the WSYNC state
if dbg.Quantum() != govern.QuantumClock && !dbg.vcs.CPU.RdyFlg {
// create volatile RDY trap
_ = dbg.halting.volatileTraps.parseCommand(commandline.TokeniseInput("RDY"))
dbg.runUntilHalt = true
// when the RDY flag changes the input loop will think it's
// inside a video step. we need to force the loop to return
// to the non-video step loop
dbg.stepOutOfVideoStepInputLoop = true
}
case "INSTRUCTION":
dbg.setQuantum(govern.QuantumInstruction)
case "CYCLE":
dbg.setQuantum(govern.QuantumCycle)
case "CLOCK":
dbg.setQuantum(govern.QuantumClock)
default:
// token not recognised so forward rest of tokens to the volatile
// traps parser
tokens.Unget()
_ = dbg.halting.volatileTraps.parseCommand(tokens)
// trap may take many cycles to trigger
dbg.runUntilHalt = true
// when the RDY flag changes the input loop will think it's
// inside a video step. we need to force the loop to return
// to the non-video step loop
dbg.stepOutOfVideoStepInputLoop = true
}
case "INSTRUCTION":
dbg.setQuantum(govern.QuantumInstruction)
case "CLOCK":
dbg.setQuantum(govern.QuantumClock)
default:
// do not change quantum
tokens.Unget()
// ignoring error
_ = dbg.halting.volatileTraps.parseCommand(tokens)
// trap may take many cycles to trigger
dbg.runUntilHalt = true
// continue emulation. note that we don't set runUntilHalt except in the
// specific cases above in the above switch. this is because we do no
// always set a volatile trap. without a trap the emulation will just
// run until it receives a HALT instruction.
dbg.continueEmulation = true
}
// always continue
dbg.continueEmulation = true
case cmdQuantum:
mode, _ := tokens.Get()
mode = strings.ToUpper(mode)
switch mode {
case "INSTRUCTION":
dbg.setQuantum(govern.QuantumInstruction)
case "CYCLE":
dbg.setQuantum(govern.QuantumCycle)
case "CLOCK":
dbg.setQuantum(govern.QuantumClock)
default:
dbg.printLine(terminal.StyleFeedback, "set to %s", dbg.Quantum)
dbg.printLine(terminal.StyleFeedback, "set to %s", strings.ToUpper(dbg.Quantum().String()))
}
case cmdScript:
@ -924,11 +939,11 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
return nil
case cmdLast:
// if debugger is running in clock quantum then the live disasm
// if debugger is running in a non-instruction quantum then the live disasm
// information will not have been updated. for the purposes of the last
// instruction however, we definitely do want that information to be
// current
if dbg.running && dbg.quantum.Load() == govern.QuantumClock {
if dbg.running && dbg.quantum.Load() != govern.QuantumInstruction {
dbg.liveBankInfo = dbg.vcs.Mem.Cart.GetBank(dbg.vcs.CPU.PC.Address())
dbg.liveDisasmEntry = dbg.Disasm.ExecutedEntry(dbg.liveBankInfo, dbg.vcs.CPU.LastResult, true, dbg.vcs.CPU.PC.Value())
}
@ -978,9 +993,9 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
// change terminal output style depending on condition of last CPU result
if dbg.liveDisasmEntry.Result.Final {
dbg.printLine(terminal.StyleCPUStep, s.String())
dbg.printLine(terminal.StyleInstructionStep, s.String())
} else {
dbg.printLine(terminal.StyleVideoStep, s.String())
dbg.printLine(terminal.StyleSubStep, s.String())
}
case cmdMemMap:

View file

@ -37,16 +37,24 @@ the STEP to end on the programme after the corresponding RTS. Note that if there
will run forever and you will need to stop the execution with the HALT command (or through the debugging GUI
or with a CTRL-C on some terminals)`,
cmdQuantum: `Change or view stepping quantum. The stepping quantum defines the frequency
at which the emulation is checked and reported upon by the debugger.
cmdQuantum: `Change or view the stepping quantum. The stepping quantum defines the
frequency at which the emulation is checked and reported upon by the emulation when
in debugging mode.
There are two quantum modes. The INSTRUCTION quantum mode causes the debugger
to step one CPU instruction at a time, regardless of how many cycles the
instruction takes.
The VIDEO quantum mode meanwhile, causes the debugger to step one color clock
(or one video cycle) at a time. Compared to the INSTRUCTION quantum mode, the
VIDEO quantum will cause the emulation to run slower.`,
The CYCLE quatum steps one CPU cycle. This can be useful to understand how the
memory buses react at each step of an instruction.
The CLOCK quantum causes the debugger to step one color clock at a time. There
are three color clocks per CYCLE. This quantum is useful to understand how
and precisely when the registers in the TIA change.
The three quantums have been listed above in order of descending efficiency.
In other words INSTRUCTION produces the fastest emulation and CLOCK produces
the slowest emulation.`,
cmdScript: `Run commands from specified file or record commands to a file. The RECORD
argument indicates that a new script is to be recorded. Recording will not

View file

@ -89,7 +89,7 @@ var commandTemplate = []string{
cmdRun,
cmdStep + " (BACK|OVER) (INSTRUCTION|CLOCK|SCANLINE|FRAME)",
cmdHalt,
cmdQuantum + " (INSTRUCTION|CLOCK)",
cmdQuantum + " (INSTRUCTION|CYCLE|CLOCK)",
cmdScript + " [RECORD %<new file>F|END|%<file>F]",
cmdRewind + " [%<frame>N|LAST|SUMMARY]",
cmdComparison + " [%<frame>N|LOCK|UNLOCK]",

View file

@ -193,17 +193,6 @@ type Debugger struct {
// Quantum to use when stepping/running
quantum atomic.Value // govern.Quantum
// catchupQuantum differs from the quantum field in that it only applies in
// the catchupLoop (part of the rewind system). it is set just before the
// rewind process is started.
//
// the value it is set to depends on the context. For the STEP BACK command
// it is set to the current stepQuantum
//
// for PushGoto() the quantum is set to QuantumVideo, while for
// PushRewind() it is set to the current stepQuantum
catchupQuantum govern.Quantum
// record user input to a script file
scriptScribe script.Scribe
@ -283,6 +272,9 @@ type Debugger struct {
catchupContinue func() bool
catchupEnd func()
// the context in which the catchup loop is running
catchupContext catchupContext
// the debugger catchup loop will end on a video cycle if necessary. this
// is what we want in most situations but occasionally it is useful to stop
// on an instruction boundary. catchupEndAdj will ensure that the debugger

View file

@ -21,16 +21,19 @@ type Quantum int
// List of valid QuantumModes.
const (
QuantumInstruction Quantum = iota
QuantumCycle
QuantumClock
)
func (mode Quantum) String() string {
switch mode {
func (q Quantum) String() string {
switch q {
case QuantumInstruction:
return "Instruction"
case QuantumCycle:
return "Cycle"
case QuantumClock:
return "Clock"
default:
return "unrecognised quantum mode"
return "unrecognised quantum"
}
}

View file

@ -18,6 +18,31 @@ package govern
// State indicates the emulation's state.
type State int
// List of possible emulation states.
//
// EmulatorStart is the default state and should never be entered once the
// emulator has begun.
//
// Initialising can be used when reinitialising the emulator. for example, when
// a new cartridge is being inserted.
//
// Values are ordered so that order comparisons are meaningful. For example,
// Running is "greater than" Stepping, Paused, etc.
//
// Note that there is a sub-state of the rewinding state that we can potentially
// think of as the "catch-up" state. This occurs in the brief transition period
// between Rewinding and the Running or Pausing state. For simplicity, the
// catch-up loop is part of the Rewinding state
const (
EmulatorStart State = iota
Initialising
Paused
Stepping
Rewinding
Running
Ending
)
func (s State) String() string {
switch s {
case EmulatorStart:
@ -38,31 +63,3 @@ func (s State) String() string {
return ""
}
// List of possible emulation states.
//
// EmulatorStart is the default state and should never be entered once the
// emulator has begun.
//
// Initialising can be used when reinitialising the emulator. for example, when
// a new cartridge is being inserted.
//
// Values are ordered so that order comparisons are meaningful. For example,
// Running is "greater than" Stepping, Paused, etc.
//
// * There is a sub-state of the rewinding state that we can think of as the
// "catch-up" state. This occurs in the brief transition period between
// Rewinding and the Running or Pausing state.
//
// Currently, we handle this state in the CartUpLoop() function of the debugger
// package. There is a good argument to be made for having the catch-up state
// as a distinct State listed below.
const (
EmulatorStart State = iota
Initialising
Paused
Stepping
Rewinding
Running
Ending
)

View file

@ -20,11 +20,28 @@ import (
"github.com/jetsetilly/gopher2600/hardware/television/coords"
)
// catchupContext is used to inform the loop of the context in which a
// post-rewind catchup is running in
type catchupContext int
// list of valid catchupContext values
const (
catchupGotoCoords catchupContext = iota
catchupRewindToFrame
catrupRerunLastNFrames
catchupStepBack
)
// CatchUpLoop implements the rewind.Runner interface.
//
// It is called from the rewind package and sets the functions that are
// required for catchupLoop().
func (dbg *Debugger) CatchUpLoop(tgt coords.TelevisionCoords) error {
// emulation state assertion
if dbg.State() != govern.Rewinding {
panic("catchup loop must only be run in the rewinding state")
}
switch dbg.Mode() {
case govern.ModePlay:
fpscap := dbg.vcs.TV.SetFPSCap(false)
@ -44,8 +61,6 @@ func (dbg *Debugger) CatchUpLoop(tgt coords.TelevisionCoords) error {
// turn off TV's fps frame limiter
fpsCap := dbg.vcs.TV.SetFPSCap(false)
// we've already set emulation state to govern.Rewinding
dbg.catchupContinue = func() bool {
newCoords := dbg.vcs.TV.GetCoords()

View file

@ -45,7 +45,7 @@ func (dbg *Debugger) unwindLoop(onRestart func() error) {
func (dbg *Debugger) catchupLoop(inputter terminal.Input) error {
var ended bool
callback := func() error {
callback := func(_ bool) error {
if dbg.unwindLoopRestart != nil {
return nil
}
@ -77,7 +77,9 @@ func (dbg *Debugger) catchupLoop(inputter terminal.Input) error {
ended = true
dbg.catchupEnd()
if dbg.catchupQuantum == govern.QuantumInstruction {
// small optimisation if the catchup context is rewind to frame. we
// never want to call the input loop in this case
if dbg.catchupContext == catchupRewindToFrame {
return nil
}
@ -411,7 +413,7 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, nonInstructionQuantum bo
}
func (dbg *Debugger) step(inputter terminal.Input, catchup bool) error {
callback := func() error {
callback := func(isCycle bool) error {
var err error
// check for unwind loop
@ -426,14 +428,18 @@ func (dbg *Debugger) step(inputter terminal.Input, catchup bool) error {
}
dbg.counter.Step(1, dbg.liveBankInfo)
// process commandOnStep for clock quantum (equivalent for instruction
// quantum is the main body of Debugger.step() below)
if dbg.Quantum() == govern.QuantumClock && dbg.commandOnStep != nil {
// we don't do this if we're in catchup mode
if !catchup {
err := dbg.processTokensList(dbg.commandOnStep)
if err != nil {
dbg.printLine(terminal.StyleError, "%s", err)
q := dbg.Quantum()
// process commandOnStep for non-instruction quantums (equivalent for
// instruction quantum is the main body of Debugger.step() below)
if dbg.commandOnStep != nil {
// we don't do this if we're in catchup mode or the "final" result from the CPU
if !catchup && !dbg.vcs.CPU.LastResult.Final {
if q == govern.QuantumClock || (q == govern.QuantumCycle && isCycle) {
err := dbg.processTokensList(dbg.commandOnStep)
if err != nil {
dbg.printLine(terminal.StyleError, "%s", err)
}
}
}
}
@ -442,7 +448,7 @@ func (dbg *Debugger) step(inputter terminal.Input, catchup bool) error {
// returns below
dbg.continueEmulation = dbg.halting.check()
if dbg.Quantum() == govern.QuantumClock || !dbg.continueEmulation {
if q == govern.QuantumClock || (q == govern.QuantumCycle && isCycle) || !dbg.continueEmulation {
// start another inputLoop() with the clockCycle boolean set to true
return dbg.inputLoop(inputter, true)
}
@ -502,11 +508,14 @@ func (dbg *Debugger) step(inputter terminal.Input, catchup bool) error {
// process commandOnStep for instruction quantum (equivalent for clock
// quantum is the vcs.Step() callback above)
if dbg.Quantum() == govern.QuantumInstruction && dbg.vcs.CPU.RdyFlg {
if dbg.commandOnStep != nil {
err := dbg.processTokensList(dbg.commandOnStep)
if err != nil {
dbg.printLine(terminal.StyleError, "%s", err)
if dbg.vcs.CPU.RdyFlg {
q := dbg.Quantum()
if q == govern.QuantumInstruction || (q != govern.QuantumInstruction && dbg.vcs.CPU.LastResult.Final) {
if dbg.commandOnStep != nil {
err := dbg.processTokensList(dbg.commandOnStep)
if err != nil {
dbg.printLine(terminal.StyleError, "%s", err)
}
}
}
}

View file

@ -50,8 +50,8 @@ func (dbg *Debugger) buildPrompt() terminal.Prompt {
// decoded it already
s.WriteString(fmt.Sprintf("%s %s", e.Address, e.Operator))
if e.Operand.String() != "" {
s.WriteString(fmt.Sprintf(" %s", e.Operand))
if e.Operand.Resolve() != "" {
s.WriteString(fmt.Sprintf(" %s", e.Operand.Resolve()))
}
}

View file

@ -84,8 +84,8 @@ func (dbg *Debugger) RewindToFrame(fn int, last bool) bool {
// the function to push to the debugger/emulation routine
doRewind := func() error {
// upate catchupQuantum before starting rewind process
dbg.catchupQuantum = dbg.Quantum()
// upate catchup context before starting rewind process
dbg.catchupContext = catchupRewindToFrame
if last {
err := dbg.Rewind.GotoLast()
@ -127,8 +127,8 @@ func (dbg *Debugger) GotoCoords(coords coords.TelevisionCoords) bool {
// the function to push to the debugger/emulation routine
doRewind := func() error {
// upate catchupQuantum before starting rewind process
dbg.catchupQuantum = govern.QuantumClock
// upate catchup context before starting rewind process
dbg.catchupContext = catchupGotoCoords
err := dbg.Rewind.GotoCoords(coords)
if err != nil {
@ -190,8 +190,8 @@ func (dbg *Debugger) RerunLastNFrames(frames int) bool {
// how we push the doRewind() function depends on what kind of inputloop we
// are currently in
dbg.PushFunctionImmediate(func() {
// upate catchupQuantum before starting rewind process
dbg.catchupQuantum = govern.QuantumClock
// upate catchup context before starting rewind process
dbg.catchupContext = catrupRerunLastNFrames
// set state to govern.Rewinding as soon as possible (but
// remembering that we must do it in the debugger goroutine)

View file

@ -46,10 +46,10 @@ func (ct *ColorTerminal) TermPrintLine(style terminal.Style, s string) {
case terminal.StyleFeedbackSecondary:
ct.EasyTerm.TermPrint(ansi.DimPens["gray"])
case terminal.StyleCPUStep:
case terminal.StyleInstructionStep:
ct.EasyTerm.TermPrint(ansi.Pens["yellow"])
case terminal.StyleVideoStep:
case terminal.StyleSubStep:
ct.EasyTerm.TermPrint(ansi.DimPens["yellow"])
case terminal.StyleInstrument:

View file

@ -36,11 +36,11 @@ const (
// secondary information from a command
StyleFeedbackSecondary
// disassembly output at CPU cycle boundaries
StyleCPUStep
// disassembly output for CPU instruction boundaries
StyleInstructionStep
// disassembly output at video cycle boundaries
StyleVideoStep
// disassembly output for non-CPU instruction boundaries
StyleSubStep
// information about the machine
StyleInstrument

View file

@ -64,7 +64,7 @@ type DisasmEntries struct {
// Also returns a reference to the disassembly's symbol table. This reference
// will never change over the course of the lifetime of the Disassembly type
// itself. ie. the returned reference is safe to use after calls to
// FromMemory() or FromCartrige().
// FromMemory() or FromCartridge().
func NewDisassembly(vcs *hardware.VCS) (*Disassembly, *symbols.Symbols, error) {
dsm := &Disassembly{vcs: vcs}
@ -251,9 +251,8 @@ func (dsm *Disassembly) ExecutedEntry(bank mapper.BankInfo, result execution.Res
o := dsm.disasmEntries.Entries[bank.Number][idx]
if o != nil && o.Result.Final {
e.updateExecutionEntry(result)
} else {
dsm.disasmEntries.Entries[bank.Number][idx] = e
}
dsm.disasmEntries.Entries[bank.Number][idx] = e
// bless next entry in case it was missed by the original decoding. there's
// no guarantee that the bank for the next address will be the same as the
@ -286,14 +285,14 @@ func (dsm *Disassembly) FormatResult(bank mapper.BankInfo, result execution.Resu
Level: level,
Bank: bank.Number,
Label: Label{
dsm: dsm,
address: result.Address,
bank: bank.Number,
dsm: dsm,
result: result,
bank: bank,
},
Operand: Operand{
dsm: dsm,
result: result,
bank: bank.Number,
bank: bank,
},
}
@ -325,14 +324,14 @@ func (dsm *Disassembly) FormatResult(bank mapper.BankInfo, result execution.Resu
switch result.ByteCount {
case 3:
operand := result.InstructionData
e.Operand.nonSymbolic = fmt.Sprintf("$%04x", operand)
e.Operand.partial = fmt.Sprintf("$%04x", operand)
e.Bytecode = fmt.Sprintf("%02x %02x %02x", result.Defn.OpCode, operand&0x00ff, operand&0xff00>>8)
case 2:
operand := result.InstructionData
e.Operand.nonSymbolic = fmt.Sprintf("$??%02x", result.InstructionData)
e.Operand.partial = fmt.Sprintf("$??%02x", result.InstructionData)
e.Bytecode = fmt.Sprintf("%02x %02x ?? ", result.Defn.OpCode, operand&0x00ff)
case 1:
e.Operand.nonSymbolic = "$????"
e.Operand.partial = "$????"
e.Bytecode = fmt.Sprintf("%02x ?? ??", result.Defn.OpCode)
case 0:
panic("this makes no sense. we must have read at least one byte to know how many bytes to expect")
@ -343,10 +342,10 @@ func (dsm *Disassembly) FormatResult(bank mapper.BankInfo, result execution.Resu
switch result.ByteCount {
case 2:
operand := result.InstructionData
e.Operand.nonSymbolic = fmt.Sprintf("$%02x", operand)
e.Operand.partial = fmt.Sprintf("$%02x", operand)
e.Bytecode = fmt.Sprintf("%02x %02x", result.Defn.OpCode, operand&0x00ff)
case 1:
e.Operand.nonSymbolic = "$??"
e.Operand.partial = "$??"
e.Bytecode = fmt.Sprintf("%02x ??", result.Defn.OpCode)
case 0:
panic("this makes no sense. we must have read at least one byte to know how many bytes to expect")
@ -372,11 +371,7 @@ func (dsm *Disassembly) FormatResult(bank mapper.BankInfo, result execution.Resu
// decorate operand with addressing mode indicators. this decorates the
// non-symbolic operand. we also call the decorate function from the
// Operand() function when a symbol has been found
if e.Result.Defn.IsBranch() {
e.Operand.nonSymbolic = fmt.Sprintf("$%04x", absoluteBranchDestination(e.Result.Address, e.Result.InstructionData))
} else {
e.Operand.nonSymbolic = addrModeDecoration(e.Operand.nonSymbolic, e.Result.Defn.AddressingMode)
}
e.Operand.partial = addrModeDecoration(e.Operand.partial, e.Result.Defn.AddressingMode)
return e
}

View file

@ -22,6 +22,7 @@ import (
"github.com/jetsetilly/gopher2600/hardware/cpu/execution"
"github.com/jetsetilly/gopher2600/hardware/cpu/instructions"
"github.com/jetsetilly/gopher2600/hardware/cpu/registers"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
)
@ -77,7 +78,6 @@ type Entry struct {
// the entries below are not defined if Level == EntryLevelUnused
// string representations of information in execution.Result
//
// entry.GetField() will apply white spacing padding suitable for columnation
Label Label
Bytecode string
@ -88,14 +88,15 @@ type Entry struct {
// some fields in the disassembly entry are updated on every execution.
func (e *Entry) updateExecutionEntry(result execution.Result) {
// update result instance
e.Result = result
// update address in label. we probably don't need to do this but it might
// be useful to know what the *actual* address of the instruction. ie.
// update result instance in Label. we probably don't need to do this but it
// might be useful to know what the *actual* address of the instruction. ie.
// which mirror is used by the program at that point in the execution.
e.Label.address = e.Result.Address
e.Label.result = e.Result
// update result instance in Operand fields
// update result instance in Operand
e.Operand.result = e.Result
// indicate that entry has been executed
@ -215,17 +216,17 @@ func absoluteBranchDestination(addr uint16, operand uint16) uint16 {
// returns any address label for the entry. Use GetField() function for
// a white-space padded label.
type Label struct {
dsm *Disassembly
address uint16
bank int
dsm *Disassembly
result execution.Result
bank mapper.BankInfo
}
// String returns the address label as a symbol (if a symbol is available)
// Resolve returns the address label as a symbol (if a symbol is available)
// if a symbol is not available then the the bool return value will be false.
func (lb Label) String() string {
if lb.dsm.Prefs.Symbols.Get().(bool) {
ma, _ := memorymap.MapAddress(lb.address, true)
if e, ok := lb.dsm.Sym.GetLabel(lb.bank, ma); ok {
func (l Label) Resolve() string {
if l.dsm.Prefs.Symbols.Get().(bool) {
ma, _ := memorymap.MapAddress(l.result.Address, true)
if e, ok := l.dsm.Sym.GetLabel(l.bank.Number, ma); ok {
return e.Symbol
}
}
@ -237,67 +238,71 @@ func (lb Label) String() string {
// returns the operand (with symbols if appropriate). Use GetField function for
// white-space padded operand string.
type Operand struct {
nonSymbolic string
dsm *Disassembly
result execution.Result
bank int
dsm *Disassembly
result execution.Result
bank mapper.BankInfo
// partial is the operand that will be used as the result from Resolve()
// when the execution result is not complete (ie. when not enough bytes have
// been read)
partial string
}
// String returns the operand as a symbol (if a symbol is available) if
// a symbol is not available then the the bool return value will be false.
func (op Operand) String() string {
// Resolve returns the operand as a symbol (if a symbol is available) if a symbol
// is not available then the returned value will be be numeric possibly with
// placeholders for unknown bytes
func (op Operand) Resolve() string {
if op.result.Defn == nil {
return op.nonSymbolic
return op.partial
}
if op.dsm == nil || !op.dsm.Prefs.Symbols.Get().(bool) {
return op.nonSymbolic
return op.partial
}
s := op.nonSymbolic
if op.result.ByteCount != op.result.Defn.Bytes {
return op.partial
}
res := op.partial
// use symbol for the operand if available/appropriate. we should only do
// this if operand has been decoded
if op.result.Defn.AddressingMode == instructions.Immediate {
// TODO: immediate symbols
} else if op.result.ByteCount > 1 {
// instruction data is only valid if bytecount is 2 or more
operand := op.result.InstructionData
// this if at least part of an operand has been decoded
if op.result.Defn.Bytes > 1 {
data := op.result.InstructionData
switch op.result.Defn.Effect {
case instructions.Flow:
if op.result.Defn.IsBranch() {
operand = absoluteBranchDestination(op.result.Address, operand)
data = absoluteBranchDestination(op.result.Address, data)
// look up mock program counter value in symbol table
if e, ok := op.dsm.Sym.GetLabel(op.bank, operand); ok {
s = e.Symbol
if e, ok := op.dsm.Sym.GetLabel(op.bank.Number, data); ok {
res = e.Symbol
}
} else if e, ok := op.dsm.Sym.GetLabel(op.bank, operand); ok {
s = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
} else if e, ok := op.dsm.Sym.GetLabel(op.bank.Number, data); ok {
res = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
}
case instructions.Subroutine:
if e, ok := op.dsm.Sym.GetLabel(op.bank, operand); ok {
s = e.Symbol
if e, ok := op.dsm.Sym.GetLabel(op.bank.Number, data); ok {
res = e.Symbol
}
case instructions.Read:
if e, ok := op.dsm.Sym.GetSymbol(operand, true); ok {
s = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
if e, ok := op.dsm.Sym.GetSymbol(data, true); ok {
res = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
}
case instructions.Write:
fallthrough
case instructions.RMW:
if e, ok := op.dsm.Sym.GetSymbol(operand, false); ok {
s = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
if e, ok := op.dsm.Sym.GetSymbol(data, false); ok {
res = addrModeDecoration(e.Symbol, op.result.Defn.AddressingMode)
}
}
}
return s
return res
}

View file

@ -74,7 +74,7 @@ func (e *Entry) GetField(field Field) string {
switch field {
case FldLabel:
s = e.Label.String()
s = e.Label.Resolve()
if s == "" {
w = 0
} else {
@ -95,7 +95,7 @@ func (e *Entry) GetField(field Field) string {
s = e.Operator
case FldOperand:
s = e.Operand.String()
s = e.Operand.Resolve()
w = e.dsm.Sym.SymbolWidth() + widthOperandDecoration
case FldCycles:

View file

@ -25,7 +25,7 @@ import (
//
// See StringColumnated() for a fancier option.
func (e *Entry) String() string {
operand := e.Operand.String()
operand := e.Operand.Resolve()
return fmt.Sprintf("%s %s %s", e.Address, e.Operator, operand)
}
@ -52,7 +52,7 @@ func (e *Entry) StringColumnated(attr ColumnAttr) string {
}
if attr.Label {
if e.Label.String() != "" {
if e.Label.Resolve() != "" {
b.Write([]byte(e.GetField(FldLabel)))
b.Write([]byte("\n"))
}

View file

@ -51,7 +51,7 @@ func (dsm *Disassembly) Grep(output io.Writer, scope GrepScope, search string, c
case GrepOperator:
s = e.Operator
case GrepOperand:
s = e.Operand.String()
s = e.Operand.Resolve()
case GrepAll:
s = e.String()
}

View file

@ -123,7 +123,7 @@ func (dsm *Disassembly) setCartMirror() {
// mask off bits that indicate the cartridge/segment origin and reset
// them with the chosen origin
a := e.Result.Address&memorymap.CartridgeBits | dsm.Prefs.mirrorOrigin
e.Operand.nonSymbolic = fmt.Sprintf("$%04x", absoluteBranchDestination(a, e.Result.InstructionData))
e.Operand.partial = fmt.Sprintf("$%04x", absoluteBranchDestination(a, e.Result.InstructionData))
}
}
}

View file

@ -24,10 +24,10 @@ var FontAwesome []byte
const (
Run = '\uf04b'
Halt = '\uf04c'
BackClock = '\uf104'
BackInstruction = '\uf100'
BackScanline = '\uf106'
BackFrame = '\uf102'
BackArrow = '\uf104'
BackArrowDouble = '\uf100'
UpArrow = '\uf106'
UpArrowDouble = '\uf102'
StepOver = '\uf2f9'
Disk = '\uf0c7'
Mouse = '\uf8cc'
@ -58,7 +58,7 @@ const (
CoProcBug = '\uf188'
ExecutionNotes = '\uf02b'
CPUBug = '\uf188'
CyclingInstruction = '\uf206'
Paw = '\uf1b0'
NonCartExecution = '\uf54c'
CoProcExecution = '\uf135'
DisasmGotoCurrent = '\uf530'

View file

@ -609,9 +609,9 @@ func (scr *screen) reflectionColor(ref *reflection.ReflectedVideoStep) color.RGB
return reflectionColors[reflection.WSYNC]
}
case reflection.OverlayLabels[reflection.OverlayCollision]:
if ref.Collision.LastVideoCycle.IsCXCLR() {
if ref.Collision.LastColorClock.IsCXCLR() {
return reflectionColors[reflection.CXCLR]
} else if !ref.Collision.LastVideoCycle.IsNothing() {
} else if !ref.Collision.LastColorClock.IsNothing() {
return reflectionColors[reflection.Collision]
}
case reflection.OverlayLabels[reflection.OverlayHMOVE]:

View file

@ -142,13 +142,7 @@ func (win *winControl) drawStep() {
// step button
imgui.TableNextColumn()
icon := fonts.BackInstruction
if win.img.dbg.Quantum() == govern.QuantumClock {
icon = fonts.BackClock
}
win.repeatButton(fmt.Sprintf("%c ##Step", icon), func() {
win.repeatButton(fmt.Sprintf("%c ##Step", fonts.BackArrowDouble), func() {
win.img.term.pushCommand("STEP BACK")
})
@ -159,21 +153,20 @@ func (win *winControl) drawStep() {
imgui.TableNextColumn()
if imguiToggleButton("##quantumToggle", win.img.dbg.Quantum() == govern.QuantumClock, win.img.cols.TitleBgActive) {
if win.img.dbg.Quantum() == govern.QuantumClock {
if imgui.BeginComboV("##quantum", "", imgui.ComboFlagsNoPreview) {
if imgui.Selectable(govern.QuantumInstruction.String()) {
win.img.term.pushCommand("QUANTUM INSTRUCTION")
} else {
}
if imgui.Selectable(govern.QuantumCycle.String()) {
win.img.term.pushCommand("QUANTUM CYCLE")
}
if imgui.Selectable(govern.QuantumClock.String()) {
win.img.term.pushCommand("QUANTUM CLOCK")
}
imgui.EndCombo()
}
imgui.SameLine()
imgui.AlignTextToFramePadding()
if win.img.dbg.Quantum() == govern.QuantumClock {
imgui.Text("Colour Clock")
} else {
imgui.Text("CPU Instruction")
}
imgui.SameLineV(0, 5)
imgui.Text(win.img.dbg.Quantum().String())
imgui.EndTable()
}
@ -188,7 +181,7 @@ func (win *winControl) drawStep() {
imgui.TableNextRow()
imgui.TableNextColumn()
win.repeatButton(fmt.Sprintf("%c ##Frame", fonts.BackFrame), func() {
win.repeatButton(fmt.Sprintf("%c ##Frame", fonts.UpArrowDouble), func() {
win.img.term.pushCommand("STEP BACK FRAME")
})
imgui.SameLineV(0.0, 0.0)
@ -198,7 +191,7 @@ func (win *winControl) drawStep() {
imgui.TableNextColumn()
win.repeatButton(fmt.Sprintf("%c ##Scanline", fonts.BackScanline), func() {
win.repeatButton(fmt.Sprintf("%c ##Scanline", fonts.UpArrow), func() {
win.img.term.pushCommand("STEP BACK SCANLINE")
})
imgui.SameLineV(0.0, 0.0)
@ -208,7 +201,6 @@ func (win *winControl) drawStep() {
imgui.EndTable()
}
}
func (win *winControl) drawFPS() {

View file

@ -18,6 +18,7 @@ package sdlimgui
import (
"fmt"
"github.com/jetsetilly/gopher2600/debugger/govern"
"github.com/jetsetilly/gopher2600/gui/fonts"
"github.com/jetsetilly/gopher2600/hardware/cpu/registers"
@ -194,15 +195,32 @@ func (win *winCPU) draw() {
imgui.SameLine()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmOperand)
imgui.Text(res.Operand.String())
imgui.Text(res.Operand.Resolve())
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmCycles)
imgui.Text(fmt.Sprintf("%s cycles", res.Cycles()))
if res.Result.PageFault {
imgui.SameLine()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmNotes)
imgui.Text("(page-fault)")
imgui.PopStyleColor()
if win.img.dbg.Quantum() == govern.QuantumClock {
if !win.img.cache.Dbg.LiveDisasmEntry.Result.Final {
imgui.SameLine()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmCycles)
switch win.img.cache.VCS.TIA.ClocksSinceCycle {
case 1:
imgui.Text(fmt.Sprintf("%c", fonts.Paw))
case 2:
imgui.Text(fmt.Sprintf("%c", fonts.Paw))
imgui.SameLineV(0, 4)
imgui.Text(fmt.Sprintf("%c", fonts.Paw))
case 3:
imgui.Text(fmt.Sprintf("%c", fonts.Paw))
imgui.SameLineV(0, 4)
imgui.Text(fmt.Sprintf("%c", fonts.Paw))
imgui.SameLineV(0, 4)
imgui.Text(fmt.Sprintf("%c", fonts.Paw))
}
imgui.PopStyleColor()
}
}
imgui.PopStyleColorV(5)

View file

@ -618,10 +618,10 @@ func (win *winDbgScr) drawReflectionTooltip() {
imgui.Text(e.Operator)
imgui.PopStyleColor()
if e.Operand.String() != "" {
if e.Operand.Resolve() != "" {
imgui.SameLine()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmOperand)
imgui.Text(e.Operand.String())
imgui.Text(e.Operand.Resolve())
imgui.PopStyleColor()
}
@ -681,7 +681,7 @@ func (win *winDbgScr) drawReflectionTooltip() {
imguiSeparator()
s := ref.Collision.LastVideoCycle.String()
s := ref.Collision.LastColorClock.String()
if s != "" {
imgui.Text(s)
} else {
@ -711,7 +711,7 @@ func (win *winDbgScr) drawReflectionTooltip() {
imguiColorLabelSimple("Audio phase 1", win.img.cols.reflectionColors[reflection.AudioPhase1])
}
if ref.AudioChanged {
reg := strings.Split(e.Operand.String(), ",")[0]
reg := strings.Split(e.Operand.Resolve(), ",")[0]
imguiColorLabelSimple(fmt.Sprintf("%s updated", reg), win.img.cols.reflectionColors[reflection.AudioChanged])
}
} else {

View file

@ -454,7 +454,7 @@ func (win *winDisasm) drawBank(currBank mapper.BankInfo, focusAddr uint16) {
for {
if iterateFilter(e) {
ct++
if e.Label.String() != "" {
if e.Label.Resolve() != "" {
ct++
}
@ -497,7 +497,7 @@ func (win *winDisasm) drawBank(currBank mapper.BankInfo, focusAddr uint16) {
// skip entries counting label as appropriate
skip--
if e.Label.String() != "" {
if e.Label.Resolve() != "" {
skip--
}
@ -572,9 +572,7 @@ func (win *winDisasm) drawBank(currBank mapper.BankInfo, focusAddr uint16) {
}
func (win *winDisasm) drawLabel(e *disassembly.Entry, bank int) {
// no label to draw
label := e.Label.String()
if len(label) == 0 {
if len(e.Label.Resolve()) == 0 {
return
}
@ -589,7 +587,7 @@ func (win *winDisasm) drawLabel(e *disassembly.Entry, bank int) {
imgui.SelectableV("", false, imgui.SelectableFlagsNone, imgui.Vec2{0, 0})
imgui.SameLine()
imgui.Text(e.Label.String())
imgui.Text(e.Label.Resolve())
}
func (win *winDisasm) drawCoProcTooltip() {
@ -618,7 +616,7 @@ func (win *winDisasm) drawEntry(currBank mapper.BankInfo, e *disassembly.Entry,
if onBank && (e.Result.Address&memorymap.CartridgeBits == focusAddr) {
imgui.TableSetBgColor(imgui.TableBgTargetRowBg0, win.img.cols.DisasmStep)
// focussed entry has been drawn so unset focus flag
// focused entry has been drawn so unset focus flag
win.focusOnAddr = false
}
@ -674,13 +672,13 @@ func (win *winDisasm) drawEntry(currBank mapper.BankInfo, e *disassembly.Entry,
imgui.PopStyleColor()
imgui.SameLine()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmOperand)
imgui.Text(e.Operand.String())
imgui.Text(e.Operand.Resolve())
imgui.PopStyleColor()
// treat an instruction that is "cycling" differently
if !e.Result.Final {
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmCycles)
imgui.Text(fmt.Sprintf("%c cycling instruction (%s)", fonts.CyclingInstruction, e.Cycles()))
imgui.Text(fmt.Sprintf("%c cycling instruction (%s)", fonts.Paw, e.Cycles()))
imgui.PopStyleColor()
} else {
imgui.Text("Cycles:")
@ -736,7 +734,7 @@ func (win *winDisasm) drawEntry(currBank mapper.BankInfo, e *disassembly.Entry,
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmOperand)
defer imgui.PopStyleColor()
}
imgui.Text(e.Operand.String())
imgui.Text(e.Operand.Resolve())
// cycles column
imgui.TableNextColumn()
@ -760,7 +758,7 @@ func (win *winDisasm) drawEntry(currBank mapper.BankInfo, e *disassembly.Entry,
exeAddress := win.img.cache.VCS.CPU.LastResult.Address & memorymap.CartridgeBits
entryAddress := e.Result.Address & memorymap.CartridgeBits
if exeAddress == entryAddress && currBank.Number == bank {
imgui.Text(string(fonts.CyclingInstruction))
imgui.Text(string(fonts.Paw))
}
} else if e.Level == disassembly.EntryLevelExecuted {
if win.usingColor {

View file

@ -334,10 +334,10 @@ func (l terminalOutput) draw(wrap bool) {
case terminal.StyleFeedbackSecondary:
imgui.PushStyleColor(imgui.StyleColorText, l.cols.TermStyleFeedbackSecondary)
case terminal.StyleCPUStep:
case terminal.StyleInstructionStep:
imgui.PushStyleColor(imgui.StyleColorText, l.cols.TermStyleCPUStep)
case terminal.StyleVideoStep:
case terminal.StyleSubStep:
imgui.PushStyleColor(imgui.StyleColorText, l.cols.TermStyleVideoStep)
case terminal.StyleInstrument:

View file

@ -22,8 +22,8 @@ import (
)
// While the continueCheck() function only runs at the end of a CPU instruction
// (unlike the corresponding function in VCS.Step() which runs every video
// cycle), it can still be expensive to do a full continue check every time.
// (unlike the corresponding function in VCS.Step() which runs every color clock),
// it can still be expensive to do a full continue check every time.
//
// It depends on context whether it is used or not but the PerformanceBrake is
// a standard value that can be used to filter out expensive code paths within
@ -45,20 +45,20 @@ func (vcs *VCS) Run(continueCheck func() (govern.State, error)) error {
continueCheck = func() (govern.State, error) { return govern.Running, nil }
}
// see the equivalient videoCycle() in the VCS.Step() function for an
// see the equivalient colorClock() in the VCS.Step() function for an
// explanation for what's going on here:
videoCycle := func() error {
colorClock := func() error {
if err := vcs.Input.Handle(); err != nil {
return err
}
vcs.TIA.QuickStep()
vcs.TIA.QuickStep()
vcs.TIA.QuickStep(1)
vcs.TIA.QuickStep(2)
if reg, ok := vcs.Mem.TIA.ChipHasChanged(); ok {
vcs.TIA.Step(reg)
vcs.TIA.Step(reg, 3)
} else {
vcs.TIA.QuickStep()
vcs.TIA.QuickStep(3)
}
if reg, ok := vcs.Mem.RIOT.ChipHasChanged(); ok {
@ -79,7 +79,7 @@ func (vcs *VCS) Run(continueCheck func() (govern.State, error)) error {
for state != govern.Ending && state != govern.Initialising {
switch state {
case govern.Running:
err := vcs.CPU.ExecuteInstruction(videoCycle)
err := vcs.CPU.ExecuteInstruction(colorClock)
if err != nil {
return err
}

View file

@ -15,21 +15,20 @@
package hardware
func nullVideoCycleCallback() error {
func nullCallback(_ bool) error {
return nil
}
// Step the emulator state one CPU instruction. With a bit of work the optional
// videoCycleCallback argument can be used for video-cycle stepping.
func (vcs *VCS) Step(videoCycleCallback func() error) error {
if videoCycleCallback == nil {
videoCycleCallback = nullVideoCycleCallback
// Step the emulator state one CPU instruction
func (vcs *VCS) Step(colorClockCallback func(isCycle bool) error) error {
if colorClockCallback == nil {
colorClockCallback = nullCallback
}
// the videoCycle function defines the order of operation for the rest of
// the cycle function defines the order of operation for the rest of
// the VCS for every CPU cycle. the function block represents the ϕ0 cycle
//
// the cpu calls the videoCycle function after every CPU cycle. this is a
// the cpu calls the cycle function after every CPU cycle. this is a
// bit backwards compared to the operation of a real VCS but I believe the
// effect is the same:
//
@ -41,11 +40,11 @@ func (vcs *VCS) Step(videoCycleCallback func() error) error {
//
// in this emulation meanwhile, the CPU-TIA is reversed. each call to
// Step() drives the CPU. After each CPU cycle the CPU emulation yields to
// the videoCycle() function defined below.
// the cycle() function defined below.
//
// the reason for this inside-out arrangement is simply a consequence of
// the how the CPU emulation is put together. it is easier for the large
// CPU ExecuteInstruction() function to call out to the videoCycle()
// CPU ExecuteInstruction() function to call out to the cycle()
// function. if we were to do it the other way around then keeping track of
// the interim CPU state becomes trickier.
//
@ -56,41 +55,44 @@ func (vcs *VCS) Step(videoCycleCallback func() error) error {
//
// I don't believe any visual or audible artefacts of the VCS (undocumented
// or not) rely on the details of the CPU-TIA relationship.
videoCycle := func() error {
//
// at the end of the cycle() function the cycleCallback() function is called
cycle := func() error {
if err := vcs.Input.Handle(); err != nil {
return err
}
vcs.TIA.QuickStep()
if err := videoCycleCallback(); err != nil {
vcs.TIA.QuickStep(1)
if err := colorClockCallback(false); err != nil {
return err
}
vcs.TIA.QuickStep()
if err := videoCycleCallback(); err != nil {
vcs.TIA.QuickStep(2)
if err := colorClockCallback(false); err != nil {
return err
}
if reg, ok := vcs.Mem.TIA.ChipHasChanged(); ok {
vcs.TIA.Step(reg)
vcs.TIA.Step(reg, 3)
} else {
vcs.TIA.QuickStep()
vcs.TIA.QuickStep(3)
}
if reg, ok := vcs.Mem.RIOT.ChipHasChanged(); ok {
vcs.RIOT.Step(reg)
} else {
vcs.RIOT.QuickStep()
}
if err := videoCycleCallback(); err != nil {
return err
}
vcs.Mem.Cart.Step(vcs.Clock)
if err := colorClockCallback(true); err != nil {
return err
}
return nil
}
err := vcs.CPU.ExecuteInstruction(videoCycle)
err := vcs.CPU.ExecuteInstruction(cycle)
if err != nil {
return err
}

View file

@ -26,7 +26,7 @@ func (tv *Television) AdjCoords(adj Adj, amount int) coords.TelevisionCoords {
coords := tv.GetCoords()
switch adj {
case AdjCPUCycle:
case AdjCycle:
// adjusting by CPU cycle is the same as adjusting by video cycle
// accept to say that a CPU cycle is the equivalent of 3 video cycles
amount *= 3
@ -80,6 +80,6 @@ type Adj int
const (
AdjFrame Adj = iota
AdjScanline
AdjCPUCycle
AdjCycle
AdjClock
)

View file

@ -63,11 +63,8 @@ type TIA struct {
// the VBLANK register also affects the RIOT sub-system
riot RIOTports
// number of video cycles since the last WSYNC. also cycles back to 0 on
// RSYNC and when polycounter reaches count 56
//
// cpu cycles can be attained by dividing videoCycles by 3
videoCycles int
// number of color clocks since the last CPU cycle boundary
ClocksSinceCycle int
// the last signal sent to the television. many signal attributes are
// sustained over many cycles; we use this to store that information
@ -131,9 +128,8 @@ func (tia *TIA) Label() string {
func (tia *TIA) String() string {
s := strings.Builder{}
s.WriteString(fmt.Sprintf("%s %s %03d %04.01f",
s.WriteString(fmt.Sprintf("%s %s",
tia.hsync, tia.PClk,
tia.videoCycles, float64(tia.videoCycles)/3.0,
))
return s.String()
}
@ -312,9 +308,6 @@ func (tia *TIA) newScanline() {
// on
tia.Hblank = true
// reset debugging information
tia.videoCycles = 0
// rather than include the reset signal in the delay, we will
// manually reset hsync counter when it reaches a count of 57
}
@ -382,20 +375,19 @@ func (tia *TIA) resolveDelayedEvents() {
// TIA memory has changed then the changes will propogate at the correct time.
// If the state of TIA memory has not changed then execution will be diverted
// to QuickStep().
func (tia *TIA) Step(reg chipbus.ChangedRegister) {
func (tia *TIA) Step(reg chipbus.ChangedRegister, ct int) {
tia.ClocksSinceCycle = ct
// make alterations to video state and playfield
update := tia.Update(reg)
// if update has happened we can jump to QuickStep() for the remainder of
// the Step() process
if !update {
tia.QuickStep()
tia.QuickStep(ct)
return
}
// update debugging information
tia.videoCycles++
// tick phase clock
tia.PClk++
if tia.PClk >= phaseclock.NumStates {
@ -636,9 +628,8 @@ func (tia *TIA) Step(reg chipbus.ChangedRegister) {
// QuickStep ticks the TIA forward one colour clock without checking to see if
// the state of TIA memory has changed.
func (tia *TIA) QuickStep() {
// update debugging information
tia.videoCycles++
func (tia *TIA) QuickStep(ct int) {
tia.ClocksSinceCycle = ct
// tick phase clock
tia.PClk++

View file

@ -25,9 +25,9 @@ import (
type Collisions struct {
mem chipbus.Memory
// LastVideoCycle records the combination of collision bits for the most recent
// LastColorClock records the combination of collision bits for the most recent
// video cycle. Facilitates production of string information.
LastVideoCycle CollisionEvent
LastColorClock CollisionEvent
}
// CollisionEvent is an emulator specific value that records the collision
@ -172,41 +172,41 @@ func (col *Collisions) Clear() {
col.mem.ChipWrite(chipbus.CXM1FB, 0x00)
col.mem.ChipWrite(chipbus.CXBLPF, 0x00)
col.mem.ChipWrite(chipbus.CXPPMM, 0x00)
col.LastVideoCycle = cxclr
col.LastColorClock = cxclr
}
// optimised tick of collision registers. memory is only written to when necessary.
//
// if this function is not called during a video cycle (which is possible for
// reasons of optimisation) then the LastVideoCycle value must be reset
// reasons of optimisation) then the LastCoorClock value must be reset
// instead.
func (col *Collisions) tick(p0, p1, m0, m1, bl, pf bool) {
col.LastVideoCycle.reset()
col.LastColorClock.reset()
if m0 {
if p1 {
v := col.mem.ChipRefer(chipbus.CXM0P)
v |= 0x80
col.LastVideoCycle |= m0p1
col.LastColorClock |= m0p1
col.mem.ChipWrite(chipbus.CXM0P, v)
}
if p0 {
v := col.mem.ChipRefer(chipbus.CXM0P)
v |= 0x40
col.LastVideoCycle |= m0p0
col.LastColorClock |= m0p0
col.mem.ChipWrite(chipbus.CXM0P, v)
}
if pf {
v := col.mem.ChipRefer(chipbus.CXM0FB)
v |= 0x80
col.LastVideoCycle |= m0pf
col.LastColorClock |= m0pf
col.mem.ChipWrite(chipbus.CXM0FB, v)
}
if bl {
v := col.mem.ChipRefer(chipbus.CXM0FB)
v |= 0x40
col.LastVideoCycle |= m0bl
col.LastColorClock |= m0bl
col.mem.ChipWrite(chipbus.CXM0FB, v)
}
}
@ -215,26 +215,26 @@ func (col *Collisions) tick(p0, p1, m0, m1, bl, pf bool) {
if p0 {
v := col.mem.ChipRefer(chipbus.CXM1P)
v |= 0x80
col.LastVideoCycle |= m1p0
col.LastColorClock |= m1p0
col.mem.ChipWrite(chipbus.CXM1P, v)
}
if p1 {
v := col.mem.ChipRefer(chipbus.CXM1P)
v |= 0x40
col.LastVideoCycle |= m1p1
col.LastColorClock |= m1p1
col.mem.ChipWrite(chipbus.CXM1P, v)
}
if pf {
v := col.mem.ChipRefer(chipbus.CXM1FB)
v |= 0x80
col.LastVideoCycle |= m1pf
col.LastColorClock |= m1pf
col.mem.ChipWrite(chipbus.CXM1FB, v)
}
if bl {
v := col.mem.ChipRefer(chipbus.CXM1FB)
v |= 0x40
col.LastVideoCycle |= m1bl
col.LastColorClock |= m1bl
col.mem.ChipWrite(chipbus.CXM1FB, v)
}
}
@ -243,13 +243,13 @@ func (col *Collisions) tick(p0, p1, m0, m1, bl, pf bool) {
if pf {
v := col.mem.ChipRefer(chipbus.CXP0FB)
v |= 0x80
col.LastVideoCycle |= p0pf
col.LastColorClock |= p0pf
col.mem.ChipWrite(chipbus.CXP0FB, v)
}
if bl {
v := col.mem.ChipRefer(chipbus.CXP0FB)
v |= 0x40
col.LastVideoCycle |= p0bl
col.LastColorClock |= p0bl
col.mem.ChipWrite(chipbus.CXP0FB, v)
}
}
@ -258,13 +258,13 @@ func (col *Collisions) tick(p0, p1, m0, m1, bl, pf bool) {
if pf {
v := col.mem.ChipRefer(chipbus.CXP1FB)
v |= 0x80
col.LastVideoCycle |= p1pf
col.LastColorClock |= p1pf
col.mem.ChipWrite(chipbus.CXP1FB, v)
}
if bl {
v := col.mem.ChipRefer(chipbus.CXP1FB)
v |= 0x40
col.LastVideoCycle |= p1bl
col.LastColorClock |= p1bl
col.mem.ChipWrite(chipbus.CXP1FB, v)
}
}
@ -272,7 +272,7 @@ func (col *Collisions) tick(p0, p1, m0, m1, bl, pf bool) {
if bl && pf {
v := col.mem.ChipRefer(chipbus.CXBLPF)
v |= 0x80
col.LastVideoCycle |= blpf
col.LastColorClock |= blpf
col.mem.ChipWrite(chipbus.CXBLPF, v)
}
// no bit 6 for CXBLPF
@ -280,14 +280,14 @@ func (col *Collisions) tick(p0, p1, m0, m1, bl, pf bool) {
if p0 && p1 {
v := col.mem.ChipRefer(chipbus.CXPPMM)
v |= 0x80
col.LastVideoCycle |= p0p1
col.LastColorClock |= p0p1
col.mem.ChipWrite(chipbus.CXPPMM, v)
}
if m0 && m1 {
v := col.mem.ChipRefer(chipbus.CXPPMM)
v |= 0x40
col.LastVideoCycle |= m0m1
col.LastColorClock |= m0m1
col.mem.ChipWrite(chipbus.CXPPMM, v)
}
}

View file

@ -308,7 +308,7 @@ func (vd *Video) Pixel() {
vd.Missile0.pixelCollision, vd.Missile1.pixelCollision,
vd.Ball.pixelCollision, vd.Playfield.colorLatch)
} else {
vd.Collisions.LastVideoCycle.reset()
vd.Collisions.LastColorClock.reset()
}
// prioritisation of pixels: