// // AtariST.cpp // Clock Signal // // Created by Thomas Harte on 03/10/2019. // Copyright © 2019 Thomas Harte. All rights reserved. // #include "AtariST.hpp" #include "../../CRTMachine.hpp" #include "../../JoystickMachine.hpp" #include "../../KeyboardMachine.hpp" #include "../../MouseMachine.hpp" #include "../../MediaTarget.hpp" #include "../../../Activity/Source.hpp" //#define LOG_TRACE //bool should_log = false; #include "../../../Processors/68000/68000.hpp" #include "../../../Components/AY38910/AY38910.hpp" #include "../../../Components/68901/MFP68901.hpp" #include "../../../Components/6850/6850.hpp" #include "DMAController.hpp" #include "IntelligentKeyboard.hpp" #include "Video.hpp" #include "../../../ClockReceiver/JustInTime.hpp" #include "../../../ClockReceiver/ForceInline.hpp" #include "../../../Configurable/StandardOptions.hpp" #include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #define LOG_PREFIX "[ST] " #include "../../../Outputs/Log.hpp" #include "../../Utility/MemoryPacker.hpp" #include "../../Utility/MemoryFuzzer.hpp" namespace Atari { namespace ST { std::vector> get_options() { return Configurable::standard_options( static_cast(Configurable::DisplayRGB | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape) ); } constexpr int CLOCK_RATE = 8021247; using Target = Analyser::Static::Target; class ConcreteMachine: public Atari::ST::Machine, public CPU::MC68000::BusHandler, public CRTMachine::Machine, public ClockingHint::Observer, public Motorola::ACIA::ACIA::InterruptDelegate, public Motorola::MFP68901::MFP68901::InterruptDelegate, public DMAController::Delegate, public MouseMachine::Machine, public JoystickMachine::Machine, public KeyboardMachine::MappedMachine, public Activity::Source, public MediaTarget::Machine, public GI::AY38910::PortHandler, public Configurable::Device, public Video::RangeObserver { public: ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : mc68000_(*this), keyboard_acia_(Cycles(500000)), midi_acia_(Cycles(500000)), ay_(GI::AY38910::Personality::YM2149F, audio_queue_), speaker_(ay_), ikbd_(keyboard_acia_->transmit, keyboard_acia_->receive) { set_clock_rate(CLOCK_RATE); speaker_.set_input_rate(float(CLOCK_RATE) / 4.0f); ram_.resize(512 * 1024); // i.e. 512kb video_->set_ram(reinterpret_cast(ram_.data()), ram_.size()); Memory::Fuzz(ram_); std::vector rom_descriptions = { {"AtariST", "the UK TOS 1.00 ROM", "tos100.img", 192*1024, 0x1a586c64} // {"AtariST", "the UK TOS 1.04 ROM", "tos104.img", 192*1024, 0xa50d1d43} }; const auto roms = rom_fetcher(rom_descriptions); if(!roms[0]) { throw ROMMachine::Error::MissingROMs; } Memory::PackBigEndian16(*roms[0], rom_); // Set up basic memory map. memory_map_[0] = BusDevice::MostlyRAM; int c = 1; for(; c < int(ram_.size() >> 16); ++c) memory_map_[c] = BusDevice::RAM; for(; c < 0x40; ++c) memory_map_[c] = BusDevice::Floating; for(; c < 0xff; ++c) memory_map_[c] = BusDevice::Unassigned; const bool is_early_tos = true; if(is_early_tos) { rom_start_ = 0xfc0000; for(c = 0xfc; c < 0xff; ++c) memory_map_[c] = BusDevice::ROM; } else { rom_start_ = 0xe00000; for(c = 0xe0; c < 0xe4; ++c) memory_map_[c] = BusDevice::ROM; } memory_map_[0xfa] = memory_map_[0xfb] = BusDevice::Cartridge; memory_map_[0xff] = BusDevice::IO; midi_acia_->set_interrupt_delegate(this); keyboard_acia_->set_interrupt_delegate(this); midi_acia_->set_clocking_hint_observer(this); keyboard_acia_->set_clocking_hint_observer(this); ikbd_.set_clocking_hint_observer(this); mfp_->set_clocking_hint_observer(this); dma_->set_clocking_hint_observer(this); mfp_->set_interrupt_delegate(this); dma_->set_delegate(this); ay_.set_port_handler(this); set_gpip_input(); video_->set_range_observer(this); // Insert any supplied media. insert_media(target.media); } ~ConcreteMachine() { audio_queue_.flush(); } // MARK: CRTMachine::Machine void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { video_->set_scan_target(scan_target); } Outputs::Display::ScanStatus get_scaled_scan_status() const final { return video_->get_scaled_scan_status(); } void set_display_type(Outputs::Display::DisplayType display_type) final { video_->set_display_type(display_type); } Outputs::Speaker::Speaker *get_speaker() final { return &speaker_; } void run_for(const Cycles cycles) final { // Give the keyboard an opportunity to consume any events. if(!keyboard_needs_clock_) { ikbd_.run_for(HalfCycles(0)); } mc68000_.run_for(cycles); } // MARK: MC68000::BusHandler using Microcycle = CPU::MC68000::Microcycle; HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { // Just in case the last cycle was an interrupt acknowledge or bus error. TODO: find a better solution? mc68000_.set_is_peripheral_address(false); mc68000_.set_bus_error(false); // Advance time. advance_time(cycle.length); // Check for assertion of reset. if(cycle.operation & Microcycle::Reset) { LOG("Unhandled Reset"); } // A null cycle leaves nothing else to do. if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0); // An interrupt acknowledge, perhaps? if(cycle.operation & Microcycle::InterruptAcknowledge) { // Current implementation: everything other than 6 (i.e. the MFP is autovectored. const int interrupt_level = cycle.word_address()&7; if(interrupt_level != 6) { video_interrupts_pending_ &= ~interrupt_level; update_interrupt_input(); mc68000_.set_is_peripheral_address(true); return HalfCycles(0); } else { if(cycle.operation & Microcycle::SelectByte) { const int interrupt = mfp_->acknowledge_interrupt(); if(interrupt != Motorola::MFP68901::MFP68901::NoAcknowledgement) { cycle.value->halves.low = uint8_t(interrupt); } else { // TODO: this should take a while. Find out how long. mc68000_.set_bus_error(true); } } return HalfCycles(0); } } auto address = cycle.host_endian_byte_address(); // If this is a new strobing of the address signal, test for bus error and pre-DTack delay. HalfCycles delay(0); if(cycle.operation & Microcycle::NewAddress) { // Bus error test. if( // Anything unassigned should generate a bus error. (memory_map_[address >> 16] == BusDevice::Unassigned) || // Bus errors also apply to unprivileged access to the first 0x800 bytes, or the IO area. (!is_supervisor && (address < 0x800 || memory_map_[address >> 16] == BusDevice::IO)) ) { mc68000_.set_bus_error(true); return delay; // TODO: there should be an extra delay here. } // DTack delay rule: if accessing RAM or the shifter, align with the two cycles next available // for the CPU to access that side of the bus. if(address < ram_.size() || (address == 0xff8260)) { // DTack will be implicit; work out how long until that should be, // and apply bus error constraints. const int i_phase = bus_phase_.as() & 7; if(i_phase < 4) { delay = HalfCycles(4 - i_phase); advance_time(delay); } } } uint8_t *memory = nullptr; switch(memory_map_[address >> 16]) { default: case BusDevice::MostlyRAM: if(address < 8) { memory = rom_.data(); break; } case BusDevice::RAM: memory = ram_.data(); break; case BusDevice::ROM: memory = rom_.data(); address -= rom_start_; break; case BusDevice::Floating: // TODO: provide vapour reads here. But: will these always be of the last video fetch? case BusDevice::Unassigned: case BusDevice::Cartridge: /* TOS 1.0 appears to attempt to read from the catridge before it has setup the bus error vector. Therefore I assume no bus error flows. */ switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { default: break; case Microcycle::SelectWord | Microcycle::Read: *cycle.value = 0xffff; break; case Microcycle::SelectByte | Microcycle::Read: cycle.value->halves.low = 0xff; break; } return delay; case BusDevice::IO: switch(address & 0xfffe) { // TODO: surely it's going to be even less precise than this? default: // assert(false); case 0x8000: /* Memory controller configuration: b0, b1: bank 1 b2, b3: bank 0 00 = 128k 01 = 512k 10 = 2mb 11 = reserved */ break; // Video controls. case 0x8200: case 0x8202: case 0x8204: case 0x8206: case 0x8208: case 0x820a: case 0x820c: case 0x820e: case 0x8210: case 0x8212: case 0x8214: case 0x8216: case 0x8218: case 0x821a: case 0x821c: case 0x821e: case 0x8220: case 0x8222: case 0x8224: case 0x8226: case 0x8228: case 0x822a: case 0x822c: case 0x822e: case 0x8230: case 0x8232: case 0x8234: case 0x8236: case 0x8238: case 0x823a: case 0x823c: case 0x823e: case 0x8240: case 0x8242: case 0x8244: case 0x8246: case 0x8248: case 0x824a: case 0x824c: case 0x824e: case 0x8250: case 0x8252: case 0x8254: case 0x8256: case 0x8258: case 0x825a: case 0x825c: case 0x825e: case 0x8260: case 0x8262: if(!cycle.data_select_active()) return delay; if(cycle.operation & Microcycle::Read) { cycle.set_value16(video_->read(int(address >> 1))); } else { video_->write(int(address >> 1), cycle.value16()); } break; // DMA. case 0x8604: case 0x8606: case 0x8608: case 0x860a: case 0x860c: if(!cycle.data_select_active()) return delay; if(cycle.operation & Microcycle::Read) { cycle.set_value16(dma_->read(int(address >> 1))); } else { dma_->write(int(address >> 1), cycle.value16()); } break; // Audio. // // Re: mirrors, Dan Hollis' hardware register list asserts: // // "Note: PSG Registers are now fixed at these addresses. All other addresses are masked out on the Falcon. Any // writes to the shadow registers $8804-$88FF will cause bus errors.", which I am taking to imply that those shadow // registers exist on the Atari ST. case 0x8800: case 0x8802: case 0x8804: case 0x8806: case 0x8808: case 0x880a: case 0x880c: case 0x880e: case 0x8810: case 0x8812: case 0x8814: case 0x8816: case 0x8818: case 0x881a: case 0x881c: case 0x881e: case 0x8820: case 0x8822: case 0x8824: case 0x8826: case 0x8828: case 0x882a: case 0x882c: case 0x882e: case 0x8830: case 0x8832: case 0x8834: case 0x8836: case 0x8838: case 0x883a: case 0x883c: case 0x883e: case 0x8840: case 0x8842: case 0x8844: case 0x8846: case 0x8848: case 0x884a: case 0x884c: case 0x884e: case 0x8850: case 0x8852: case 0x8854: case 0x8856: case 0x8858: case 0x885a: case 0x885c: case 0x885e: case 0x8860: case 0x8862: case 0x8864: case 0x8866: case 0x8868: case 0x886a: case 0x886c: case 0x886e: case 0x8870: case 0x8872: case 0x8874: case 0x8876: case 0x8878: case 0x887a: case 0x887c: case 0x887e: case 0x8880: case 0x8882: case 0x8884: case 0x8886: case 0x8888: case 0x888a: case 0x888c: case 0x888e: case 0x8890: case 0x8892: case 0x8894: case 0x8896: case 0x8898: case 0x889a: case 0x889c: case 0x889e: case 0x88a0: case 0x88a2: case 0x88a4: case 0x88a6: case 0x88a8: case 0x88aa: case 0x88ac: case 0x88ae: case 0x88b0: case 0x88b2: case 0x88b4: case 0x88b6: case 0x88b8: case 0x88ba: case 0x88bc: case 0x88be: case 0x88c0: case 0x88c2: case 0x88c4: case 0x88c6: case 0x88c8: case 0x88ca: case 0x88cc: case 0x88ce: case 0x88d0: case 0x88d2: case 0x88d4: case 0x88d6: case 0x88d8: case 0x88da: case 0x88dc: case 0x88de: case 0x88e0: case 0x88e2: case 0x88e4: case 0x88e6: case 0x88e8: case 0x88ea: case 0x88ec: case 0x88ee: case 0x88f0: case 0x88f2: case 0x88f4: case 0x88f6: case 0x88f8: case 0x88fa: case 0x88fc: case 0x88fe: if(!cycle.data_select_active()) return delay; advance_time(HalfCycles(2)); update_audio(); if(cycle.operation & Microcycle::Read) { ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); cycle.set_value8_high(ay_.get_data_output()); ay_.set_control_lines(GI::AY38910::ControlLines(0)); } else { // Net effect here: addresses with bit 1 set write to a register, // addresses with bit 1 clear select a register. ay_.set_control_lines(GI::AY38910::ControlLines( GI::AY38910::BC2 | GI::AY38910::BDIR | ((address&2) ? 0 : GI::AY38910::BC1) )); ay_.set_data_input(cycle.value8_high()); ay_.set_control_lines(GI::AY38910::ControlLines(0)); } return delay + HalfCycles(2); // The MFP block: case 0xfa00: case 0xfa02: case 0xfa04: case 0xfa06: case 0xfa08: case 0xfa0a: case 0xfa0c: case 0xfa0e: case 0xfa10: case 0xfa12: case 0xfa14: case 0xfa16: case 0xfa18: case 0xfa1a: case 0xfa1c: case 0xfa1e: case 0xfa20: case 0xfa22: case 0xfa24: case 0xfa26: case 0xfa28: case 0xfa2a: case 0xfa2c: case 0xfa2e: case 0xfa30: case 0xfa32: case 0xfa34: case 0xfa36: case 0xfa38: case 0xfa3a: case 0xfa3c: case 0xfa3e: if(!cycle.data_select_active()) return delay; if(cycle.operation & Microcycle::Read) { cycle.set_value8_low(mfp_->read(int(address >> 1))); } else { mfp_->write(int(address >> 1), cycle.value8_low()); } break; // ACIAs. case 0xfc00: case 0xfc02: case 0xfc04: case 0xfc06: { // Set VPA. mc68000_.set_is_peripheral_address(!cycle.data_select_active()); if(!cycle.data_select_active()) return delay; const auto acia_ = (address & 4) ? &midi_acia_ : &keyboard_acia_; if(cycle.operation & Microcycle::Read) { cycle.set_value8_high((*acia_)->read(int(address >> 1))); } else { (*acia_)->write(int(address >> 1), cycle.value8_high()); } } break; } return HalfCycles(0); } // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { default: break; case Microcycle::SelectWord | Microcycle::Read: cycle.value->full = *reinterpret_cast(&memory[address]); break; case Microcycle::SelectByte | Microcycle::Read: cycle.value->halves.low = memory[address]; break; case Microcycle::SelectWord: if(address >= video_range_.low_address && address < video_range_.high_address) video_.flush(); *reinterpret_cast(&memory[address]) = cycle.value->full; break; case Microcycle::SelectByte: if(address >= video_range_.low_address && address < video_range_.high_address) video_.flush(); memory[address] = cycle.value->halves.low; break; } return HalfCycles(0); } void flush() { dma_.flush(); mfp_.flush(); keyboard_acia_.flush(); midi_acia_.flush(); video_.flush(); update_audio(); audio_queue_.perform(); } private: forceinline void advance_time(HalfCycles length) { // Advance the relevant counters. cycles_since_audio_update_ += length; mfp_ += length; dma_ += length; keyboard_acia_ += length; midi_acia_ += length; bus_phase_ += length; // Don't even count time for the keyboard unless it has requested it. if(keyboard_needs_clock_) { cycles_since_ikbd_update_ += length; ikbd_.run_for(cycles_since_ikbd_update_.divide(HalfCycles(512))); } // Flush anything that needs real-time updating. if(!may_defer_acias_) { keyboard_acia_.flush(); midi_acia_.flush(); } if(mfp_is_realtime_) { mfp_.flush(); } if(dma_is_realtime_) { dma_.flush(); } // Update the video output, checking whether a sequence point has been hit. while(length >= cycles_until_video_event_) { length -= cycles_until_video_event_; video_ += cycles_until_video_event_; cycles_until_video_event_ = video_->get_next_sequence_point(); assert(cycles_until_video_event_ > HalfCycles(0)); mfp_->set_timer_event_input(1, video_->display_enabled()); update_interrupt_input(); } cycles_until_video_event_ -= length; video_ += length; } void update_audio() { speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide_cycles(Cycles(4))); } CPU::MC68000::Processor mc68000_; HalfCycles bus_phase_; JustInTimeActor