Gopher2600/hardware/vcs.go
steve 35e31cccb7 o errors
- refined error package
2020-01-05 18:58:28 +00:00

163 lines
4.1 KiB
Go

package hardware
import (
"fmt"
"gopher2600/hardware/cpu"
"gopher2600/hardware/cpu/result"
"gopher2600/hardware/memory"
"gopher2600/hardware/peripherals"
"gopher2600/hardware/riot"
"gopher2600/hardware/tia"
"gopher2600/television"
)
// AddressReset is the address where the reset address is stored
// - used by VCS.Reset() and Disassembly module
const AddressReset = 0xfffc
// AddressIRQ is the address where the interrupt address is stored
const AddressIRQ = 0xfffe
// VCS struct is the main container for the emulated components of the VCS
type VCS struct {
MC *cpu.CPU
Mem *memory.VCSMemory
TIA *tia.TIA
RIOT *riot.RIOT
// tv is not part of the VCS but is attached to it
TV television.Television
panel *peripherals.Panel
controller *peripherals.Stick
}
// NewVCS creates a new VCS and everything associated with the hardware. It is
// used for all aspects of emulation: debugging sessions, and regular play
func NewVCS(tv television.Television) (*VCS, error) {
var err error
vcs := new(VCS)
vcs.TV = tv
vcs.Mem, err = memory.NewVCSMemory()
if err != nil {
return nil, err
}
vcs.MC, err = cpu.NewCPU(vcs.Mem)
if err != nil {
return nil, err
}
vcs.TIA = tia.NewTIA(vcs.TV, vcs.Mem.TIA)
if vcs.TIA == nil {
return nil, fmt.Errorf("can't create TIA")
}
vcs.RIOT = riot.NewRIOT(vcs.Mem.RIOT)
if vcs.RIOT == nil {
return nil, fmt.Errorf("can't create RIOT")
}
vcs.panel = peripherals.NewPanel(vcs.Mem.RIOT)
if vcs.panel == nil {
return nil, fmt.Errorf("can't create console control panel")
}
// TODO: better contoller support
vcs.controller = peripherals.NewStick(vcs.Mem.TIA, vcs.Mem.RIOT, vcs.panel)
if vcs.controller == nil {
return nil, fmt.Errorf("can't create stick controller")
}
return vcs, nil
}
// AttachCartridge loads a cartridge (given by filename) into the emulators memory
func (vcs *VCS) AttachCartridge(filename string) error {
if filename == "" {
vcs.Mem.Cart.Eject()
} else {
err := vcs.Mem.Cart.Attach(filename)
if err != nil {
return err
}
}
err := vcs.Reset()
if err != nil {
return err
}
return nil
}
// NullVideoCycleCallback can be used when calling Step() when no special
// behaviour is required
func NullVideoCycleCallback(*result.Instruction) error {
return nil
}
// Step the emulator state one CPU instruction
func (vcs *VCS) Step(videoCycleCallback func(*result.Instruction) error) (int, *result.Instruction, error) {
var r *result.Instruction
var err error
// the number of CPU cycles that have elapsed. note this is *not* the same
// as Instructionresult.ActualCycles because in the event of a WSYNC
// cpuCycles will continue to accumulate until the WSYNC has been resolved.
cpuCycles := 0
// the cpu calls the cycleVCS function after every CPU cycle. the cycleVCS
// function defines the order of operation for the rest of the VCS for
// every CPU cycle.
cycleVCS := func(r *result.Instruction) {
cpuCycles++
// run riot only once per CPU cycle
// TODO: not sure when in the video cycle sequence it should be run
// TODO: is this something that can drift, thereby causing subtly different
// results / graphical effects? is this what RSYNC is for?
vcs.RIOT.ReadRIOTMemory()
vcs.RIOT.Step()
// three color clocks per CPU cycle so we run video cycle three times
vcs.TIA.ReadTIAMemory()
vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle()
videoCycleCallback(r)
vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle()
videoCycleCallback(r)
vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle()
videoCycleCallback(r)
}
r, err = vcs.MC.ExecuteInstruction(cycleVCS)
if err != nil {
return cpuCycles, nil, err
}
// CPU has been left in the unready state - continue cycling the VCS hardware
// until the CPU is ready
for !vcs.MC.RdyFlg {
cycleVCS(r)
}
return cpuCycles, r, nil
}
// Reset emulates the reset switch on the console panel
// - reset the CPU
// - reload reset address into the PC
func (vcs *VCS) Reset() error {
if err := vcs.MC.Reset(); err != nil {
return err
}
err := vcs.MC.LoadPC(AddressReset)
if _, ok := err.(*memory.MissingCartridgeError); !ok {
return err
}
return nil
}