// // 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 "../../Processors/68000/68000.hpp" #include "../../Components/AY38910/AY38910.hpp" #include "../../Components/68901/MFP68901.hpp" #include "../../Components/6850/6850.hpp" #include "Video.hpp" #include "../../ClockReceiver/JustInTime.hpp" #include "../../ClockReceiver/ForceInline.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../Utility/MemoryPacker.hpp" #include "../Utility/MemoryFuzzer.hpp" namespace Atari { namespace ST { const int CLOCK_RATE = 8000000; using Target = Analyser::Static::Target; class ConcreteMachine: public Atari::ST::Machine, public CPU::MC68000::BusHandler, public CRTMachine::Machine { public: ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : mc68000_(*this), ay_(audio_queue_), speaker_(ay_) { set_clock_rate(CLOCK_RATE); speaker_.set_input_rate(CLOCK_RATE / 4); ram_.resize(512 * 512); Memory::Fuzz(ram_); std::vector rom_descriptions = { {"AtariST", "the TOS ROM", "tos100.img", 192*1024, 0x1a586c64} }; 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; for(int c = 1; c < 0xf0; ++c) memory_map_[c] = BusDevice::RAM; // This is appropriate for: TOS 1.x, no cartridge. for(int c = 0xf0; c < 0xfc; ++c) memory_map_[c] = BusDevice::Unassigned; for(int c = 0xfc; c < 0xff; ++c) memory_map_[c] = BusDevice::ROM; memory_map_[0xfa] = memory_map_[0xfb] = BusDevice::Cartridge; memory_map_[0xff] = BusDevice::IO; } ~ConcreteMachine() { audio_queue_.flush(); } // MARK: CRTMachine::Machine void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { video_->set_scan_target(scan_target); } Outputs::Speaker::Speaker *get_speaker() final { return &speaker_; } void run_for(const Cycles cycles) final { mc68000_.run_for(cycles); } // MARK: MC68000::BusHandler using Microcycle = CPU::MC68000::Microcycle; HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { // Advance time. advance_time(cycle.length); // A null cycle leaves nothing else to do. if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0); /* TODO: DTack, bus error, VPA. */ auto address = cycle.word_address(); // if(cycle.data_select_active()) printf("%c %06x\n", (cycle.operation & Microcycle::Read) ? 'r' : 'w', *cycle.address & 0xffffff); uint16_t *memory; switch(memory_map_[address >> 15]) { case BusDevice::MostlyRAM: if(address < 4) { memory = rom_.data(); break; } case BusDevice::RAM: memory = ram_.data(); address &= ram_.size() - 1; // TODO: align with the next access window. break; case BusDevice::ROM: memory = rom_.data(); address %= rom_.size(); break; case BusDevice::Unassigned: // TODO: figure out the rules about bus errors. 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 HalfCycles(0); case BusDevice::IO: switch(address) { default: assert(false); case 0x7fc000: /* Memory controller configuration: b0, b1: bank 1 b2, b3: bank 0 00 = 128k 01 = 512k 10 = 2mb 11 = reserved */ break; case 0x7fc400: /* PSG: write to select register, read to read register. */ case 0x7fc401: /* PSG: write to write register. */ if(!cycle.data_select_active()) return HalfCycles(0); // TODO: byte accesses to the odd addresses shouldn't obey logic below. 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.value->halves.low = ay_.get_data_output(); ay_.set_control_lines(GI::AY38910::ControlLines(0)); } else { if(address == 0x7fc400) { ay_.set_control_lines(GI::AY38910::BC1); ay_.set_data_input(cycle.value->halves.low); ay_.set_control_lines(GI::AY38910::ControlLines(0)); } else { ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); ay_.set_data_input(cycle.value->halves.low); ay_.set_control_lines(GI::AY38910::ControlLines(0)); } } /* TODO: Port A: b7: reserved b6: "freely usable output (monitor jack)" b5: centronics strobe b4: RS-232 DTR output b3: RS-232 RTS output b2: select floppy drive 1 b1: select floppy drive 0 b0: "page choice signal for double-sided floppy drive" */ return HalfCycles(2); // The MFP block: case 0x7ffd00: case 0x7ffd01: case 0x7ffd02: case 0x7ffd03: case 0x7ffd04: case 0x7ffd05: case 0x7ffd06: case 0x7ffd07: case 0x7ffd08: case 0x7ffd09: case 0x7ffd0a: case 0x7ffd0b: case 0x7ffd0c: case 0x7ffd0d: case 0x7ffd0e: case 0x7ffd0f: case 0x7ffd10: case 0x7ffd11: case 0x7ffd12: case 0x7ffd13: case 0x7ffd14: case 0x7ffd15: case 0x7ffd16: case 0x7ffd17: case 0x7ffd18: case 0x7ffd19: case 0x7ffd1a: case 0x7ffd1b: case 0x7ffd1c: case 0x7ffd1d: case 0x7ffd1e: case 0x7ffd1f: if(!cycle.data_select_active()) return HalfCycles(0); // The lower data lines aren't connected. if(!cycle.upper_data_select()) { if(cycle.operation & Microcycle::Read) { cycle.value->halves.low = 0xff; } return HalfCycles(0); } if(cycle.operation & Microcycle::Read) { const uint8_t value = mfp_->read(int(address)); if(cycle.operation & Microcycle::SelectByte) { cycle.value->halves.low = value; } else { cycle.value->halves.high = value; cycle.value->halves.low = 0xff; } } else { if(cycle.operation & Microcycle::SelectByte) { mfp_->write(int(address), cycle.value->halves.low); } else { mfp_->write(int(address), cycle.value->halves.high); } } /* Atari ST GPIP bits: GPIP 7: monochrome monitor detect GPIP 6: RS-232 ring indicator GPIP 5: FD/HD interrupt GPIP 4: keyboard/MIDI interrupt GPIP 3: unused GPIP 2: RS-232 clear to send GPIP 1: RS-232 carrier detect GPIP 0: centronics busy */ break; // Video controls. case 0x7fc100: case 0x7fc101: case 0x7fc102: case 0x7fc103: case 0x7fc104: case 0x7fc105: case 0x7fc106: case 0x7fc107: case 0x7fc108: case 0x7fc109: case 0x7fc10a: case 0x7fc10b: case 0x7fc10c: case 0x7fc10d: case 0x7fc10e: case 0x7fc10f: case 0x7fc110: case 0x7fc111: case 0x7fc112: case 0x7fc113: case 0x7fc114: case 0x7fc115: case 0x7fc116: case 0x7fc117: case 0x7fc118: case 0x7fc119: case 0x7fc11a: case 0x7fc11b: case 0x7fc11c: case 0x7fc11d: case 0x7fc11e: case 0x7fc11f: case 0x7fc120: case 0x7fc121: case 0x7fc122: case 0x7fc123: case 0x7fc124: case 0x7fc125: case 0x7fc126: case 0x7fc127: case 0x7fc128: case 0x7fc129: case 0x7fc12a: case 0x7fc12b: case 0x7fc12c: case 0x7fc12d: case 0x7fc12e: case 0x7fc12f: case 0x7fc130: case 0x7fc131: if(!cycle.data_select_active()) return HalfCycles(0); if(cycle.operation & Microcycle::Read) { const uint8_t value = video_->read(int(address)); if(cycle.operation & Microcycle::SelectByte) { cycle.value->halves.low = value; } else { cycle.value->halves.high = value; cycle.value->halves.low = 0xff; } } else { if(cycle.operation & Microcycle::SelectByte) { video_->write(int(address), cycle.value->halves.low); } else { video_->write(int(address), cycle.value->halves.high); } } break; // ACIAs. case 0x7ffe00: case 0x7ffe01: case 0x7ffe02: case 0x7ffe03: { // Set VPA. mc68000_.set_is_peripheral_address(!cycle.data_select_active()); if(!cycle.data_select_active()) return HalfCycles(0); const auto acia_ = (address < 0x7ffe02) ? &keyboard_acia_ : &midi_acia_; if(cycle.operation & Microcycle::Read) { const uint8_t value = (*acia_)->read(int(address)); if(cycle.operation & Microcycle::SelectByte) { cycle.value->halves.low = value; } else { cycle.value->halves.high = value; cycle.value->halves.low = 0xff; } } else { if(cycle.operation & Microcycle::SelectByte) { (*acia_)->write(int(address), cycle.value->halves.low); } else { (*acia_)->write(int(address), cycle.value->halves.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 = memory[address]; break; case Microcycle::SelectByte | Microcycle::Read: cycle.value->halves.low = uint8_t(memory[address] >> cycle.byte_shift()); break; case Microcycle::SelectWord: memory[address] = cycle.value->full; break; case Microcycle::SelectByte: memory[address] = uint16_t( (cycle.value->halves.low << cycle.byte_shift()) | (memory[address] & cycle.untouched_byte_mask()) ); break; } return HalfCycles(0); } void flush() { audio_queue_.perform(); video_.flush(); } private: forceinline void advance_time(HalfCycles length) { cycles_since_audio_update_ += length; mfp_ += length; 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(); // TODO: push v/hsync/display_enable elsewhere. mfp_->set_timer_event_input(1, video_->display_enabled()); // printf("%c%c%c\n", video_->display_enabled() ? 'e' : '-', video_->hsync() ? 'h' : '-', video_->vsync() ? 'v' : '-'); } 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_; JustInTimeActor video_; HalfCycles cycles_until_video_event_; JustInTimeActor mfp_; JustInTimeActor keyboard_acia_; JustInTimeActor midi_acia_; Concurrency::DeferringAsyncTaskQueue audio_queue_; GI::AY38910::AY38910 ay_; Outputs::Speaker::LowpassSpeaker speaker_; HalfCycles cycles_since_audio_update_; std::vector ram_; std::vector rom_; enum class BusDevice { MostlyRAM, RAM, ROM, Cartridge, IO, Unassigned }; BusDevice memory_map_[256]; }; } } using namespace Atari::ST; Machine *Machine::AtariST(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { return new ConcreteMachine(*target, rom_fetcher); } Machine::~Machine() {}