diff --git a/Machines/Atari2600/Atari2600.hpp b/Machines/Atari2600/Atari2600.hpp index be06b571b..8b5b807c7 100644 --- a/Machines/Atari2600/Atari2600.hpp +++ b/Machines/Atari2600/Atari2600.hpp @@ -16,6 +16,7 @@ #include "PIA.hpp" #include "Speaker.hpp" #include "TIA.hpp" +#include "Cartridge.hpp" #include "../ConfigurationTarget.hpp" #include "Atari2600Inputs.h" @@ -48,9 +49,9 @@ class Machine: // to satisfy CRTMachine::Machine virtual void setup_output(float aspect_ratio); virtual void close_output(); - virtual std::shared_ptr get_crt() { return tia_->get_crt(); } - virtual std::shared_ptr get_speaker() { return speaker_; } - virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } + virtual std::shared_ptr get_crt() { return cartridge_->tia_->get_crt(); } + virtual std::shared_ptr get_speaker() { return cartridge_->speaker_; } + virtual void run_for_cycles(int number_of_cycles) { cartridge_->run_for_cycles(number_of_cycles); } // to satisfy Outputs::CRT::Delegate virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs); @@ -70,27 +71,8 @@ class Machine: // Activision stack records uint8_t last_opcode_; - // the RIOT and TIA - PIA mos6532_; - std::unique_ptr tia_; - - // joystick state - uint8_t tia_input_value_[2]; - - // outputs - std::shared_ptr speaker_; - - // speaker backlog accumlation counter - unsigned int cycles_since_speaker_update_; - inline void update_audio(); - - // video backlog accumulation counter - unsigned int cycles_since_video_update_; - inline void update_video(); - - // RIOT backlog accumulation counter - unsigned int cycles_since_6532_update_; - inline void update_6532(); + // the cartridge + std::unique_ptr cartridge_; // output frame rate tracker struct FrameRecord diff --git a/Machines/Atari2600/Cartridge.hpp b/Machines/Atari2600/Cartridge.hpp new file mode 100644 index 000000000..0f18673d9 --- /dev/null +++ b/Machines/Atari2600/Cartridge.hpp @@ -0,0 +1,202 @@ +// +// Cartridge.h +// Clock Signal +// +// Created by Thomas Harte on 17/03/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef Atari2600_Cartridge_hpp +#define Atari2600_Cartridge_hpp + +namespace Atari2600 { + +class CartridgeBase { + public: + virtual void run_for_cycles(int number_of_cycles) = 0; + + // the RIOT, TIA and speaker + PIA mos6532_; + std::shared_ptr tia_; + std::shared_ptr speaker_; + + // joystick state + uint8_t tia_input_value_[2]; + + protected: + // speaker backlog accumlation counter + unsigned int cycles_since_speaker_update_; + inline void update_audio() { + unsigned int audio_cycles = cycles_since_speaker_update_ / 114; + cycles_since_speaker_update_ %= 114; + speaker_->run_for_cycles(audio_cycles); + } + + // video backlog accumulation counter + unsigned int cycles_since_video_update_; + inline void update_video() { + tia_->run_for_cycles((int)cycles_since_video_update_); + cycles_since_video_update_ = 0; + } + + // RIOT backlog accumulation counter + unsigned int cycles_since_6532_update_; + inline void update_6532() { + mos6532_.run_for_cycles(cycles_since_6532_update_); + cycles_since_6532_update_ = 0; + } +}; + +template class Cartridge: + public CPU6502::Processor>, + public CartridgeBase { + + public: + void run_for_cycles(int number_of_cycles) {} + + // to satisfy CPU6502::Processor + unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { + uint8_t returnValue = 0xff; + unsigned int cycles_run_for = 3; + + // this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for + // leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take + // effect until the next read; therefore it isn't safe to assume that signalling ready immediately + // skips to the end of the line. + if(operation == CPU6502::BusOperation::Ready) + cycles_run_for = (unsigned int)tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_); + + cycles_since_speaker_update_ += cycles_run_for; + cycles_since_video_update_ += cycles_run_for; + cycles_since_6532_update_ += (cycles_run_for / 3); + + if(operation != CPU6502::BusOperation::Ready) { + uint16_t masked_address = address & 0x1fff; + + // give the cartridge a chance to respond to the bus access + static_cast(this)->perform_bus_operation(operation, address, value); + + // check for a RIOT RAM access + if((address&0x1280) == 0x80) { + if(isReadOperation(operation)) { + returnValue &= mos6532_.get_ram(address); + } else { + mos6532_.set_ram(address, *value); + } + } + + // check for a TIA access + if(!(address&0x1080)) { + if(isReadOperation(operation)) { + const uint16_t decodedAddress = address & 0xf; + switch(decodedAddress) { + case 0x00: // missile 0 / player collisions + case 0x01: // missile 1 / player collisions + case 0x02: // player 0 / playfield / ball collisions + case 0x03: // player 1 / playfield / ball collisions + case 0x04: // missile 0 / playfield / ball collisions + case 0x05: // missile 1 / playfield / ball collisions + case 0x06: // ball / playfield collisions + case 0x07: // player / player, missile / missile collisions + returnValue &= tia_->get_collision_flags(decodedAddress); + break; + + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + // TODO: pot ports + returnValue &= 0; + break; + + case 0x0c: + case 0x0d: + returnValue &= tia_input_value_[decodedAddress - 0x0c]; + break; + } + } else { + const uint16_t decodedAddress = address & 0x3f; + switch(decodedAddress) { + case 0x00: update_video(); tia_->set_sync(*value & 0x02); break; + case 0x01: update_video(); tia_->set_blank(*value & 0x02); break; + + case 0x02: set_ready_line(true); break; + case 0x03: update_video(); tia_->reset_horizontal_counter(); break; + // TODO: audio will now be out of synchronisation — fix + + case 0x04: + case 0x05: update_video(); tia_->set_player_number_and_size(decodedAddress - 0x04, *value); break; + case 0x06: + case 0x07: update_video(); tia_->set_player_missile_colour(decodedAddress - 0x06, *value); break; + case 0x08: update_video(); tia_->set_playfield_ball_colour(*value); break; + case 0x09: update_video(); tia_->set_background_colour(*value); break; + case 0x0a: update_video(); tia_->set_playfield_control_and_ball_size(*value); break; + case 0x0b: + case 0x0c: update_video(); tia_->set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break; + case 0x0d: + case 0x0e: + case 0x0f: update_video(); tia_->set_playfield(decodedAddress - 0x0d, *value); break; + case 0x10: + case 0x11: update_video(); tia_->set_player_position(decodedAddress - 0x10); break; + case 0x12: + case 0x13: update_video(); tia_->set_missile_position(decodedAddress - 0x12); break; + case 0x14: update_video(); tia_->set_ball_position(); break; + case 0x1b: + case 0x1c: update_video(); tia_->set_player_graphic(decodedAddress - 0x1b, *value); break; + case 0x1d: + case 0x1e: update_video(); tia_->set_missile_enable(decodedAddress - 0x1d, (*value)&2); break; + case 0x1f: update_video(); tia_->set_ball_enable((*value)&2); break; + case 0x20: + case 0x21: update_video(); tia_->set_player_motion(decodedAddress - 0x20, *value); break; + case 0x22: + case 0x23: update_video(); tia_->set_missile_motion(decodedAddress - 0x22, *value); break; + case 0x24: update_video(); tia_->set_ball_motion(*value); break; + case 0x25: + case 0x26: tia_->set_player_delay(decodedAddress - 0x25, (*value)&1); break; + case 0x27: tia_->set_ball_delay((*value)&1); break; + case 0x28: + case 0x29: update_video(); tia_->set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break; + case 0x2a: update_video(); tia_->move(); break; + case 0x2b: update_video(); tia_->clear_motion(); break; + case 0x2c: update_video(); tia_->clear_collision_flags(); break; + + case 0x15: + case 0x16: update_audio(); speaker_->set_control(decodedAddress - 0x15, *value); break; + case 0x17: + case 0x18: update_audio(); speaker_->set_divider(decodedAddress - 0x17, *value); break; + case 0x19: + case 0x1a: update_audio(); speaker_->set_volume(decodedAddress - 0x19, *value); break; + } + } + } + + // check for a PIA access + if((address&0x1280) == 0x280) { + update_6532(); + if(isReadOperation(operation)) { + returnValue &= mos6532_.get_register(address); + } else { + mos6532_.set_register(address, *value); + } + } + + if(isReadOperation(operation)) { + *value &= returnValue; + } + } + + if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) set_ready_line(false); + + return cycles_run_for / 3; + } + + void synchronise() { + update_audio(); + update_video(); + speaker_->flush(); + } +}; + +} + +#endif /* Atari2600_Cartridge_hpp */ diff --git a/Machines/Atari2600/CartridgeUnpaged.hpp b/Machines/Atari2600/CartridgeUnpaged.hpp new file mode 100644 index 000000000..2440d0612 --- /dev/null +++ b/Machines/Atari2600/CartridgeUnpaged.hpp @@ -0,0 +1,18 @@ +// +// CartridgeUnpaged.h +// Clock Signal +// +// Created by Thomas Harte on 17/03/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef Atari2600_CartridgeUnpaged_hpp +#define Atari2600_CartridgeUnpaged_hpp + +#include "Cartridge.hpp" + +namespace Atari2600 { + +} + +#endif /* Atari2600_CartridgeUnpaged_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index e6c185ad8..9645d05f4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -928,6 +928,8 @@ 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = ""; }; 4BD69F921D98760000243FE1 /* AcornADF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AcornADF.cpp; sourceTree = ""; }; 4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = ""; }; + 4BE069991E7C942C00DD379F /* Cartridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = ""; }; + 4BE0699A1E7C9C5A00DD379F /* CartridgeUnpaged.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CartridgeUnpaged.hpp; sourceTree = ""; }; 4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = ""; }; 4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = ""; }; 4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = ""; }; @@ -1110,6 +1112,8 @@ 4BEA52671DF34909007E74F2 /* PIA.hpp */, 4BEA52651DF3472B007E74F2 /* Speaker.hpp */, 4BE7C9171E3D397100A5496D /* TIA.hpp */, + 4BE069991E7C942C00DD379F /* Cartridge.hpp */, + 4BE0699A1E7C9C5A00DD379F /* CartridgeUnpaged.hpp */, ); path = Atari2600; sourceTree = ""; diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index 9f1b4e125..930bb8329 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -646,8 +646,8 @@ template class Processor { scheduleProgramProgramCounter++; #define read_op(val, addr) nextBusOperation = BusOperation::ReadOpcode; busAddress = addr; busValue = &val -#define read_mem(val, addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &val -#define throwaway_read(addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &throwaway_target +#define read_mem(val, addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &val; val = 0xff +#define throwaway_read(addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &throwaway_target; throwaway_target = 0xff #define write_mem(val, addr) nextBusOperation = BusOperation::Write; busAddress = addr; busValue = &val switch(cycle) {