mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 20:18:20 -04:00
11fb367ff5
float32 pair defined as ports.EventDataPaddle added functions to convert the float32 to and from a playback entry. conversion from the playback entry tolerates a single float32 value meaning that old recording files will work rename plugging.PortLeftPlayer and plugging.PortRightPlayer to just plugging.PortLeft and plugging.PortRight
284 lines
8.9 KiB
Go
284 lines
8.9 KiB
Go
// This file is part of Gopher2600.
|
|
//
|
|
// Gopher2600 is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Gopher2600 is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package hardware
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
|
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
|
"github.com/jetsetilly/gopher2600/hardware/input"
|
|
"github.com/jetsetilly/gopher2600/hardware/instance"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cpubus"
|
|
"github.com/jetsetilly/gopher2600/hardware/peripherals"
|
|
"github.com/jetsetilly/gopher2600/hardware/peripherals/controllers"
|
|
"github.com/jetsetilly/gopher2600/hardware/preferences"
|
|
"github.com/jetsetilly/gopher2600/hardware/riot"
|
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/panel"
|
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
|
"github.com/jetsetilly/gopher2600/hardware/television"
|
|
"github.com/jetsetilly/gopher2600/hardware/tia"
|
|
"github.com/jetsetilly/gopher2600/logger"
|
|
)
|
|
|
|
// The number of times the TIA updates every CPU cycle.
|
|
const ColorClocksPerCPUCycle = 3
|
|
|
|
// VCS struct is the main container for the emulated components of the VCS.
|
|
type VCS struct {
|
|
Instance *instance.Instance
|
|
|
|
// the television is not "part" of the VCS console but it's part of the VCS system
|
|
TV *television.Television
|
|
|
|
// references to the different components of the VCS. do not take copies of
|
|
// these pointer values because the rewind feature will change them.
|
|
CPU *cpu.CPU
|
|
Mem *memory.Memory
|
|
RIOT *riot.RIOT
|
|
TIA *tia.TIA
|
|
|
|
Input *input.Input
|
|
|
|
// The Clock defines the basic speed at which the the machine is runningt. This governs
|
|
// the speed of the CPU, the RIOT and attached peripherals. The TIA runs at
|
|
// exactly three times this speed.
|
|
//
|
|
// The different clock speeds are due to the nature of the different TV
|
|
// specifications. Put simply, a PAL machine must run slightly slower in
|
|
// order to be able to send a correct PAL signal to the television.
|
|
//
|
|
// Unlike the real hardware however, it is not the console that governs the
|
|
// clock speed but the television. A ROM will send a signal to the
|
|
// television, the timings of which will be used by the tv implementation
|
|
// to decide what type of TV signal (PAL or NTSC) is being sent. When the
|
|
// television detects a change in the TV signal it will notify the emulated
|
|
// console, allowing it to note the new implied clock speed.
|
|
Clock float32
|
|
}
|
|
|
|
// NewVCS creates a new VCS and everything associated with the hardware. It is
|
|
// used for all aspects of emulation: debugging sessions, and regular play.
|
|
//
|
|
// The two arguments must be supplied. In the case of the prefs field it can by
|
|
// nil and a new prefs instance will be created. Providing a non-nil value
|
|
// allows the preferences of more than one VCS instance to be synchronised.
|
|
//
|
|
// The Instance.Context field should be updated except in the case of the
|
|
// "main" emulation.
|
|
func NewVCS(tv *television.Television, prefs *preferences.Preferences) (*VCS, error) {
|
|
// set up instance
|
|
instance, err := instance.NewInstance(tv, prefs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// set up hardware
|
|
vcs := &VCS{
|
|
Instance: instance,
|
|
TV: tv,
|
|
Clock: ntscClock,
|
|
}
|
|
|
|
vcs.Mem = memory.NewMemory(vcs.Instance)
|
|
vcs.CPU = cpu.NewCPU(vcs.Instance, vcs.Mem)
|
|
vcs.RIOT = riot.NewRIOT(vcs.Instance, vcs.Mem.RIOT, vcs.Mem.TIA)
|
|
|
|
vcs.Input = input.NewInput(vcs.TV, vcs.RIOT.Ports)
|
|
|
|
vcs.TIA, err = tia.NewTIA(vcs.Instance, vcs.TV, vcs.Mem.TIA, vcs.RIOT.Ports, vcs.CPU)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = vcs.RIOT.Ports.Plug(plugging.PortLeft, controllers.NewStick)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = vcs.RIOT.Ports.Plug(plugging.PortRight, controllers.NewStick)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = vcs.RIOT.Ports.Plug(plugging.PortPanel, panel.NewPanel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vcs.TV.AttachVCS(vcs)
|
|
|
|
return vcs, nil
|
|
}
|
|
|
|
// End cleans up any resources that may be dangling.
|
|
func (vcs *VCS) End() {
|
|
vcs.TV.End()
|
|
vcs.RIOT.Ports.End()
|
|
}
|
|
|
|
// Plumb the various VCS sub-systems together after a rewind.
|
|
//
|
|
// The fromDifferentEmulation indicates that the State has been created by a
|
|
// different VCS instance than the one being plumbed into.
|
|
func (vcs *VCS) Plumb(fromDifferentEmulation bool) {
|
|
vcs.CPU.Plumb(vcs.Mem)
|
|
vcs.Mem.Plumb(fromDifferentEmulation)
|
|
vcs.RIOT.Plumb(vcs.Mem.RIOT, vcs.Mem.TIA)
|
|
vcs.TIA.Plumb(vcs.TV, vcs.Mem.TIA, vcs.RIOT.Ports, vcs.CPU)
|
|
|
|
// reset peripherals after new state has been plumbed. without this,
|
|
// controllers can feel odd if the newly plumbed state has left RIOT memory
|
|
// in a latched state
|
|
vcs.RIOT.Ports.ResetPeripherals()
|
|
|
|
vcs.Input.Plumb(vcs.RIOT.Ports)
|
|
}
|
|
|
|
// AttachCartridge to this VCS. While this function can be called directly it
|
|
// is advised that the setup package be used in most circumstances.
|
|
//
|
|
// The emulated VCS is *not* reset after AttachCartridge() unless the reset
|
|
// argument is true.
|
|
//
|
|
// Note that the emulation should always be reset before emulation commences
|
|
// but some applications might need to prepare the emulation further before
|
|
// that happens.
|
|
func (vcs *VCS) AttachCartridge(cartload cartridgeloader.Loader, reset bool) error {
|
|
err := vcs.TV.SetSpecConditional(cartload.Spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cartload.Filename == "" {
|
|
vcs.Mem.Cart.Eject()
|
|
} else {
|
|
err := vcs.Mem.Cart.Attach(cartload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// fingerprint new peripherals. peripherals are not changed if option is not set
|
|
err = vcs.FingerprintPeripheral(plugging.PortLeft, cartload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = vcs.FingerprintPeripheral(plugging.PortRight, cartload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if reset {
|
|
err = vcs.Reset()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FingerprintPeripheral inserts the peripheral that is thought to be best
|
|
// suited for the current inserted cartridge.
|
|
func (vcs *VCS) FingerprintPeripheral(id plugging.PortID, cartload cartridgeloader.Loader) error {
|
|
return vcs.RIOT.Ports.Plug(id, peripherals.Fingerprint(id, cartload.Data))
|
|
}
|
|
|
|
// Reset emulates the reset switch on the console panel.
|
|
func (vcs *VCS) Reset() error {
|
|
err := vcs.TV.Reset(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// easiest way of resetting the TIA is to just create new one
|
|
//
|
|
// 27/10/21 - we do want to save the audio though in order to keep any
|
|
// attached trackers
|
|
//
|
|
// TODO: proper Reset() function for the TIA
|
|
audio := vcs.TIA.Audio
|
|
vcs.TIA, err = tia.NewTIA(vcs.Instance, vcs.TV, vcs.Mem.TIA, vcs.RIOT.Ports, vcs.CPU)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vcs.TIA.Audio = audio
|
|
|
|
// other areas of the VCS are simply reset because the emulation may have
|
|
// altered the part of the state that we do *not* want to reset. notably,
|
|
// memory may have a cartridge attached - we wouldn't want to lose that.
|
|
|
|
vcs.Mem.Reset()
|
|
vcs.CPU.Reset()
|
|
vcs.RIOT.Timer.Reset()
|
|
|
|
// reset of ports must happen after reset of memory because ports will
|
|
// update memory to the current state of the peripherals
|
|
vcs.RIOT.Ports.ResetPeripherals()
|
|
|
|
// reset PC using reset address in cartridge memory
|
|
err = vcs.CPU.LoadPCIndirect(cpubus.Reset)
|
|
if err != nil {
|
|
if !errors.Is(err, cartridge.Ejected) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// reset cart after loaded PC value. this seems unnecessary but some
|
|
// cartridge types may switch banks on LoadPCIndirect() - those that switch
|
|
// on Listen() - this is an artefact of the emulation method so we need to make
|
|
// sure it's initialised correctly.
|
|
vcs.Mem.Cart.Reset()
|
|
|
|
return nil
|
|
}
|
|
|
|
const (
|
|
ntscClock = 1.193182
|
|
palClock = 1.182298
|
|
)
|
|
|
|
// SetClockSpeed is an implemtation of the television.VCSReturnChannel interface.
|
|
func (vcs *VCS) SetClockSpeed(tvSpec string) error {
|
|
switch tvSpec {
|
|
case "NTSC":
|
|
if vcs.Clock != ntscClock {
|
|
vcs.Clock = ntscClock
|
|
logger.Log("vcs", "switching to NTSC clock")
|
|
}
|
|
case "PAL":
|
|
if vcs.Clock != palClock {
|
|
logger.Log("vcs", "switching to PAL clock")
|
|
vcs.Clock = palClock
|
|
}
|
|
}
|
|
return fmt.Errorf("vcs: cannot set clock speed for unknown tv specification (%s)", tvSpec)
|
|
}
|
|
|
|
// DetatchEmulationExtras removes all possible monitors, recorders, etc. from
|
|
// the emulation. Currently this mean: the TIA audio tracker, the RIOT event
|
|
// recorders and playback, and RIOT plug monitor.
|
|
func (vcs *VCS) DetatchEmulationExtras() {
|
|
vcs.TIA.Audio.SetTracker(nil)
|
|
vcs.Input.AttachRecorder(nil)
|
|
vcs.Input.AttachPlayback(nil)
|
|
vcs.RIOT.Ports.AttachPlugMonitor(nil)
|
|
}
|