diff --git a/Analyser/Static/Commodore/Disk.cpp b/Analyser/Static/Commodore/Disk.cpp index 0551fd997..658b0e938 100644 --- a/Analyser/Static/Commodore/Disk.cpp +++ b/Analyser/Static/Commodore/Disk.cpp @@ -70,7 +70,7 @@ private: uint8_t track_; std::shared_ptr sector_cache_[65536]; - void process_input_bit(const int value) { + void process_input_bit(const int value) override { shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff; bit_count_++; } @@ -107,7 +107,7 @@ private: } } - void process_index_hole() { + void process_index_hole() override { index_count_++; } diff --git a/Machines/Apple/AppleII/AuxiliaryMemorySwitches.hpp b/Machines/Apple/AppleII/AuxiliaryMemorySwitches.hpp index f4abba555..1d2dab9bb 100644 --- a/Machines/Apple/AppleII/AuxiliaryMemorySwitches.hpp +++ b/Machines/Apple/AppleII/AuxiliaryMemorySwitches.hpp @@ -24,261 +24,261 @@ namespace Apple::II { Implementation observation: as implemented on the IIe, the zero page setting also affects what happens in the language card area. */ template class AuxiliaryMemorySwitches { - public: - static constexpr bool Auxiliary = true; - static constexpr bool Main = false; - static constexpr bool ROM = true; - static constexpr bool Card = false; +public: + static constexpr bool Auxiliary = true; + static constexpr bool Main = false; + static constexpr bool ROM = true; + static constexpr bool Card = false; - /// Describes banking state between $0200 and $BFFF. - struct MainState { - struct Region { - /// @c true indicates auxiliary memory should be read from; @c false indicates main. - bool read = false; - /// @c true indicates auxiliary memory should be written to; @c false indicates main. - bool write = false; - }; - - /// Describes banking state in the ranges $0200–$03FF, $0800–$1FFF and $4000–$BFFF. - Region base; - /// Describes banking state in the range $0400–$07FF. - Region region_04_08; - /// Describes banking state in the range $2000–$3FFF. - Region region_20_40; - - bool operator != (const MainState &rhs) const { - return - base.read != rhs.base.read || base.write != rhs.base.write || - region_04_08.read != rhs.region_04_08.read || region_04_08.write != rhs.region_04_08.write || - region_20_40.read != rhs.region_20_40.read || region_20_40.write != rhs.region_20_40.write; - } + /// Describes banking state between $0200 and $BFFF. + struct MainState { + struct Region { + /// @c true indicates auxiliary memory should be read from; @c false indicates main. + bool read = false; + /// @c true indicates auxiliary memory should be written to; @c false indicates main. + bool write = false; }; - /// Describes banking state between $C100 and $Cfff. - struct CardState { - /// @c true indicates that the built-in ROM should appear from $C100 to $C2FF @c false indicates that cards should service those accesses. - bool region_C1_C3 = false; - /// @c true indicates that the built-in ROM should appear from $C300 to $C3FF; @c false indicates that cards should service those accesses. - bool region_C3 = false; - /// @c true indicates that the built-in ROM should appear from $C400 to $C7FF; @c false indicates that cards should service those accesses. - bool region_C4_C8 = false; - /// @c true indicates that the built-in ROM should appear from $C800 to $CFFF; @c false indicates that cards should service those accesses. - bool region_C8_D0 = false; + /// Describes banking state in the ranges $0200–$03FF, $0800–$1FFF and $4000–$BFFF. + Region base; + /// Describes banking state in the range $0400–$07FF. + Region region_04_08; + /// Describes banking state in the range $2000–$3FFF. + Region region_20_40; - bool operator != (const CardState &rhs) const { - return - region_C1_C3 != rhs.region_C1_C3 || - region_C3 != rhs.region_C3 || - region_C4_C8 != rhs.region_C4_C8 || - region_C8_D0 != rhs.region_C8_D0; - } - }; - - /// Descibes banking state between $0000 and $01ff; @c true indicates that auxiliary memory should be used; @c false indicates main memory. - using ZeroState = bool; - - /// Returns raw switch state for all switches that affect banking, even if they're logically video switches. - struct SwitchState { - bool read_auxiliary_memory = false; - bool write_auxiliary_memory = false; - - bool internal_CX_rom = false; - bool slot_C3_rom = false; - bool internal_C8_rom = false; - - bool store_80 = false; - bool alternative_zero_page = false; - bool video_page_2 = false; - bool high_resolution = false; - - void reset() { - *this = SwitchState(); - } - }; - - AuxiliaryMemorySwitches(Machine &machine) : machine_(machine) {} - - /// Used by an owner to forward, at least, any access in the range $C000 to $C00B, - /// in $C054 to $C058, or in the range $C300 to $CFFF. Safe to call for any [16-bit] address. - void access(uint16_t address, bool is_read) { - if(address >= 0xc300 && address < 0xd000) { - switches_.internal_C8_rom |= ((address >> 8) == 0xc3) && !switches_.slot_C3_rom; - switches_.internal_C8_rom &= (address != 0xcfff); - set_card_paging(); - return; - } - - if(address < 0xc000 || address >= 0xc058) return; - - switch(address) { - default: break; - - case 0xc000: case 0xc001: - if(!is_read) { - switches_.store_80 = address & 1; - set_main_paging(); - } - break; - - case 0xc002: case 0xc003: - if(!is_read) { - switches_.read_auxiliary_memory = address & 1; - set_main_paging(); - } - break; - - case 0xc004: case 0xc005: - if(!is_read) { - switches_.write_auxiliary_memory = address & 1; - set_main_paging(); - } - break; - - case 0xc006: case 0xc007: - if(!is_read) { - switches_.internal_CX_rom = address & 1; - set_card_paging(); - } - break; - - case 0xc008: case 0xc009: { - const bool alternative_zero_page = address & 1; - if(!is_read && switches_.alternative_zero_page != alternative_zero_page) { - switches_.alternative_zero_page = alternative_zero_page; - set_zero_page_paging(); - } - } break; - - case 0xc00a: case 0xc00b: - if(!is_read) { - switches_.slot_C3_rom = address & 1; - set_card_paging(); - } - break; - - case 0xc054: case 0xc055: - switches_.video_page_2 = address & 1; - set_main_paging(); - break; - - case 0xc056: case 0xc057: - switches_.high_resolution = address & 1; - set_main_paging(); - break; - } - } - - /// Provides part of the IIgs interface. - void set_state(uint8_t value) { - // b7: 1 => use auxiliary memory for zero page; 0 => use main. [I think the Hardware Reference gets this the wrong way around] - // b6: 1 => text page 2 is selected; 0 => text page 1. - // b5: 1 => auxiliary RAM bank is selected for reads; 0 => main. - // b4: 1 => auxiliary RAM bank is selected for writes; 0 => main. - // b0: 1 => the internal ROM is selected for C800+; 0 => card ROM. - switches_.alternative_zero_page = value & 0x80; - switches_.video_page_2 = value & 0x40; - switches_.read_auxiliary_memory = value & 0x20; - switches_.write_auxiliary_memory = value & 0x10; - switches_.internal_CX_rom = value & 0x01; - - set_main_paging(); - set_zero_page_paging(); - set_card_paging(); - } - - uint8_t get_state() const { + bool operator != (const MainState &rhs) const { return - (switches_.alternative_zero_page ? 0x80 : 0x00) | - (switches_.video_page_2 ? 0x40 : 0x00) | - (switches_.read_auxiliary_memory ? 0x20 : 0x00) | - (switches_.write_auxiliary_memory ? 0x10 : 0x00) | - (switches_.internal_CX_rom ? 0x01 : 0x00); + base.read != rhs.base.read || base.write != rhs.base.write || + region_04_08.read != rhs.region_04_08.read || region_04_08.write != rhs.region_04_08.write || + region_20_40.read != rhs.region_20_40.read || region_20_40.write != rhs.region_20_40.write; } + }; - const MainState &main_state() const { - return main_state_; - } + /// Describes banking state between $C100 and $Cfff. + struct CardState { + /// @c true indicates that the built-in ROM should appear from $C100 to $C2FF @c false indicates that cards should service those accesses. + bool region_C1_C3 = false; + /// @c true indicates that the built-in ROM should appear from $C300 to $C3FF; @c false indicates that cards should service those accesses. + bool region_C3 = false; + /// @c true indicates that the built-in ROM should appear from $C400 to $C7FF; @c false indicates that cards should service those accesses. + bool region_C4_C8 = false; + /// @c true indicates that the built-in ROM should appear from $C800 to $CFFF; @c false indicates that cards should service those accesses. + bool region_C8_D0 = false; - const CardState &card_state() const { - return card_state_; + bool operator != (const CardState &rhs) const { + return + region_C1_C3 != rhs.region_C1_C3 || + region_C3 != rhs.region_C3 || + region_C4_C8 != rhs.region_C4_C8 || + region_C8_D0 != rhs.region_C8_D0; } + }; - /// @returns @c true if the alternative zero page should be used; @c false otherwise. - ZeroState zero_state() const { - return switches_.alternative_zero_page; - } + /// Descibes banking state between $0000 and $01ff; @c true indicates that auxiliary memory should be used; @c false indicates main memory. + using ZeroState = bool; - const SwitchState switches() const { - return switches_; - } + /// Returns raw switch state for all switches that affect banking, even if they're logically video switches. + struct SwitchState { + bool read_auxiliary_memory = false; + bool write_auxiliary_memory = false; + + bool internal_CX_rom = false; + bool slot_C3_rom = false; + bool internal_C8_rom = false; + + bool store_80 = false; + bool alternative_zero_page = false; + bool video_page_2 = false; + bool high_resolution = false; void reset() { - switches_.reset(); + *this = SwitchState(); + } + }; - set_main_paging(); - set_zero_page_paging(); + AuxiliaryMemorySwitches(Machine &machine) : machine_(machine) {} + + /// Used by an owner to forward, at least, any access in the range $C000 to $C00B, + /// in $C054 to $C058, or in the range $C300 to $CFFF. Safe to call for any [16-bit] address. + void access(uint16_t address, bool is_read) { + if(address >= 0xc300 && address < 0xd000) { + switches_.internal_C8_rom |= ((address >> 8) == 0xc3) && !switches_.slot_C3_rom; + switches_.internal_C8_rom &= (address != 0xcfff); set_card_paging(); + return; } - private: - Machine &machine_; - SwitchState switches_; + if(address < 0xc000 || address >= 0xc058) return; - MainState main_state_; - void set_main_paging() { - const auto previous_state = main_state_; + switch(address) { + default: break; - // The two appropriately named switches provide the base case. - main_state_.base.read = switches_.read_auxiliary_memory; - main_state_.base.write = switches_.write_auxiliary_memory; - - if(switches_.store_80) { - // If store 80 is set, use the page 2 flag for the lower carve out; - // if both store 80 and high resolution are set, use the page 2 flag for both carve outs. - main_state_.region_04_08.read = main_state_.region_04_08.write = switches_.video_page_2; - - if(switches_.high_resolution) { - main_state_.region_20_40.read = main_state_.region_20_40.write = switches_.video_page_2; - } else { - main_state_.region_20_40 = main_state_.base; + case 0xc000: case 0xc001: + if(!is_read) { + switches_.store_80 = address & 1; + set_main_paging(); } + break; + + case 0xc002: case 0xc003: + if(!is_read) { + switches_.read_auxiliary_memory = address & 1; + set_main_paging(); + } + break; + + case 0xc004: case 0xc005: + if(!is_read) { + switches_.write_auxiliary_memory = address & 1; + set_main_paging(); + } + break; + + case 0xc006: case 0xc007: + if(!is_read) { + switches_.internal_CX_rom = address & 1; + set_card_paging(); + } + break; + + case 0xc008: case 0xc009: { + const bool alternative_zero_page = address & 1; + if(!is_read && switches_.alternative_zero_page != alternative_zero_page) { + switches_.alternative_zero_page = alternative_zero_page; + set_zero_page_paging(); + } + } break; + + case 0xc00a: case 0xc00b: + if(!is_read) { + switches_.slot_C3_rom = address & 1; + set_card_paging(); + } + break; + + case 0xc054: case 0xc055: + switches_.video_page_2 = address & 1; + set_main_paging(); + break; + + case 0xc056: case 0xc057: + switches_.high_resolution = address & 1; + set_main_paging(); + break; + } + } + + /// Provides part of the IIgs interface. + void set_state(uint8_t value) { + // b7: 1 => use auxiliary memory for zero page; 0 => use main. [I think the Hardware Reference gets this the wrong way around] + // b6: 1 => text page 2 is selected; 0 => text page 1. + // b5: 1 => auxiliary RAM bank is selected for reads; 0 => main. + // b4: 1 => auxiliary RAM bank is selected for writes; 0 => main. + // b0: 1 => the internal ROM is selected for C800+; 0 => card ROM. + switches_.alternative_zero_page = value & 0x80; + switches_.video_page_2 = value & 0x40; + switches_.read_auxiliary_memory = value & 0x20; + switches_.write_auxiliary_memory = value & 0x10; + switches_.internal_CX_rom = value & 0x01; + + set_main_paging(); + set_zero_page_paging(); + set_card_paging(); + } + + uint8_t get_state() const { + return + (switches_.alternative_zero_page ? 0x80 : 0x00) | + (switches_.video_page_2 ? 0x40 : 0x00) | + (switches_.read_auxiliary_memory ? 0x20 : 0x00) | + (switches_.write_auxiliary_memory ? 0x10 : 0x00) | + (switches_.internal_CX_rom ? 0x01 : 0x00); + } + + const MainState &main_state() const { + return main_state_; + } + + const CardState &card_state() const { + return card_state_; + } + + /// @returns @c true if the alternative zero page should be used; @c false otherwise. + ZeroState zero_state() const { + return switches_.alternative_zero_page; + } + + const SwitchState switches() const { + return switches_; + } + + void reset() { + switches_.reset(); + + set_main_paging(); + set_zero_page_paging(); + set_card_paging(); + } + +private: + Machine &machine_; + SwitchState switches_; + + MainState main_state_; + void set_main_paging() { + const auto previous_state = main_state_; + + // The two appropriately named switches provide the base case. + main_state_.base.read = switches_.read_auxiliary_memory; + main_state_.base.write = switches_.write_auxiliary_memory; + + if(switches_.store_80) { + // If store 80 is set, use the page 2 flag for the lower carve out; + // if both store 80 and high resolution are set, use the page 2 flag for both carve outs. + main_state_.region_04_08.read = main_state_.region_04_08.write = switches_.video_page_2; + + if(switches_.high_resolution) { + main_state_.region_20_40.read = main_state_.region_20_40.write = switches_.video_page_2; } else { - main_state_.region_04_08 = main_state_.region_20_40 = main_state_.base; - } - - if(previous_state != main_state_) { - machine_.template set_paging(); + main_state_.region_20_40 = main_state_.base; } + } else { + main_state_.region_04_08 = main_state_.region_20_40 = main_state_.base; } - CardState card_state_; - void set_card_paging() { - const auto previous_state = card_state_; + if(previous_state != main_state_) { + machine_.template set_paging(); + } + } - // By default apply the CX switch through to $C7FF. - card_state_.region_C1_C3 = card_state_.region_C4_C8 = switches_.internal_CX_rom; + CardState card_state_; + void set_card_paging() { + const auto previous_state = card_state_; - // Allow the C3 region to be switched to internal ROM in isolation even if the rest of the - // first half of the CX region is diabled, if its specific switch is also disabled. - if(!switches_.internal_CX_rom && !switches_.slot_C3_rom) { - card_state_.region_C3 = true; - } else { - card_state_.region_C3 = card_state_.region_C1_C3; - } + // By default apply the CX switch through to $C7FF. + card_state_.region_C1_C3 = card_state_.region_C4_C8 = switches_.internal_CX_rom; - // Apply the CX switch to $C800+, but also allow the C8 switch to select that region in isolation. - card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom; - - if(previous_state != card_state_) { - machine_.template set_paging(); - } + // Allow the C3 region to be switched to internal ROM in isolation even if the rest of the + // first half of the CX region is diabled, if its specific switch is also disabled. + if(!switches_.internal_CX_rom && !switches_.slot_C3_rom) { + card_state_.region_C3 = true; + } else { + card_state_.region_C3 = card_state_.region_C1_C3; } - void set_zero_page_paging() { - // Believe it or not, the zero page is just set or cleared by a single flag. - // As though life were rational. - machine_.template set_paging(); + // Apply the CX switch to $C800+, but also allow the C8 switch to select that region in isolation. + card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom; + + if(previous_state != card_state_) { + machine_.template set_paging(); } + } + + void set_zero_page_paging() { + // Believe it or not, the zero page is just set or cleared by a single flag. + // As though life were rational. + machine_.template set_paging(); + } }; } diff --git a/Machines/Apple/AppleIIgs/ADB.hpp b/Machines/Apple/AppleIIgs/ADB.hpp index 9669c0f19..125687789 100644 --- a/Machines/Apple/AppleIIgs/ADB.hpp +++ b/Machines/Apple/AppleIIgs/ADB.hpp @@ -19,67 +19,67 @@ namespace Apple::IIgs::ADB { class GLU: public InstructionSet::M50740::PortHandler { - public: - GLU(); +public: + GLU(); - uint8_t get_keyboard_data(); - uint8_t get_mouse_data(); - uint8_t get_modifier_status(); - uint8_t get_any_key_down(); + uint8_t get_keyboard_data(); + uint8_t get_mouse_data(); + uint8_t get_modifier_status(); + uint8_t get_any_key_down(); - uint8_t get_data(); - uint8_t get_status(); + uint8_t get_data(); + uint8_t get_status(); - void set_command(uint8_t); - void set_status(uint8_t); - void clear_key_strobe(); + void set_command(uint8_t); + void set_status(uint8_t); + void clear_key_strobe(); - void set_microcontroller_rom(const std::vector &rom); + void set_microcontroller_rom(const std::vector &rom); - void run_for(Cycles cycles); + void run_for(Cycles cycles); - bool get_command_button() const; - bool get_option_button() const; + bool get_command_button() const; + bool get_option_button() const; - void set_vertical_blank(bool); - bool get_vertical_blank() { - return vertical_blank_; - } + void set_vertical_blank(bool); + bool get_vertical_blank() { + return vertical_blank_; + } - Apple::ADB::Keyboard &keyboard() { - return keyboard_; - } - Inputs::Mouse &get_mouse() { - return mouse_; - } + Apple::ADB::Keyboard &keyboard() { + return keyboard_; + } + Inputs::Mouse &get_mouse() { + return mouse_; + } - private: - InstructionSet::M50740::Executor executor_; +private: + InstructionSet::M50740::Executor executor_; - void run_ports_for(Cycles) override; - void set_port_output(int port, uint8_t value) override; - uint8_t get_port_input(int port) override; + void run_ports_for(Cycles) override; + void set_port_output(int port, uint8_t value) override; + uint8_t get_port_input(int port) override; - uint8_t registers_[16]{}; + uint8_t registers_[16]{}; - uint8_t register_address_; - uint8_t register_latch_ = 0xff; - bool register_strobe_ = false; + uint8_t register_address_; + uint8_t register_latch_ = 0xff; + bool register_strobe_ = false; - uint8_t status_ = 0x00; + uint8_t status_ = 0x00; - Apple::ADB::Bus bus_; - size_t controller_id_; + Apple::ADB::Bus bus_; + size_t controller_id_; - uint8_t modifier_state_ = 0; + uint8_t modifier_state_ = 0; - bool vertical_blank_ = false; + bool vertical_blank_ = false; - int visible_mouse_register_ = 2; + int visible_mouse_register_ = 2; - // For now, attach only a keyboard and mouse. - Apple::ADB::Mouse mouse_; - Apple::ADB::Keyboard keyboard_; + // For now, attach only a keyboard and mouse. + Apple::ADB::Mouse mouse_; + Apple::ADB::Keyboard keyboard_; }; } diff --git a/Machines/Apple/AppleIIgs/AppleIIgs.cpp b/Machines/Apple/AppleIIgs/AppleIIgs.cpp index a0c4ac564..674905f07 100644 --- a/Machines/Apple/AppleIIgs/AppleIIgs.cpp +++ b/Machines/Apple/AppleIIgs/AppleIIgs.cpp @@ -88,921 +88,921 @@ class ConcreteMachine: public MachineTypes::TimedMachine, public CPU::MOS6502Esque::BusHandler { - public: - ConcreteMachine(const Analyser::Static::AppleIIgs::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - m65816_(*this), - memory_(target.model >= Analyser::Static::AppleIIgs::Target::Model::ROM03), - iwm_(CLOCK_RATE / 2), - drives35_{ - {CLOCK_RATE / 2, true}, - {CLOCK_RATE / 2, true} - }, - drives525_{ - {CLOCK_RATE / 2}, - {CLOCK_RATE / 2} - }, - sound_glu_(audio_queue_), - audio_toggle_(audio_queue_), - mixer_(sound_glu_, audio_toggle_), - speaker_(mixer_) { +public: + ConcreteMachine(const Analyser::Static::AppleIIgs::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + m65816_(*this), + memory_(target.model >= Analyser::Static::AppleIIgs::Target::Model::ROM03), + iwm_(CLOCK_RATE / 2), + drives35_{ + {CLOCK_RATE / 2, true}, + {CLOCK_RATE / 2, true} + }, + drives525_{ + {CLOCK_RATE / 2}, + {CLOCK_RATE / 2} + }, + sound_glu_(audio_queue_), + audio_toggle_(audio_queue_), + mixer_(sound_glu_, audio_toggle_), + speaker_(mixer_) { - set_clock_rate(double(CLOCK_RATE)); - speaker_.set_input_rate(float(CLOCK_RATE) / float(audio_divider)); - clock_.ClockStorage::set_data(std::begin(default_bram), std::end(default_bram)); + set_clock_rate(double(CLOCK_RATE)); + speaker_.set_input_rate(float(CLOCK_RATE) / float(audio_divider)); + clock_.ClockStorage::set_data(std::begin(default_bram), std::end(default_bram)); - using Target = Analyser::Static::AppleIIgs::Target; - ROM::Name system; - switch(target.model) { - case Target::Model::ROM00: system = ROM::Name::AppleIIgsROM00; break; - case Target::Model::ROM01: system = ROM::Name::AppleIIgsROM01; break; - default: system = ROM::Name::AppleIIgsROM03; break; - } - constexpr ROM::Name characters = ROM::Name::AppleIIEnhancedECharacter; - constexpr ROM::Name microcontroller = ROM::Name::AppleIIgsMicrocontrollerROM03; + using Target = Analyser::Static::AppleIIgs::Target; + ROM::Name system; + switch(target.model) { + case Target::Model::ROM00: system = ROM::Name::AppleIIgsROM00; break; + case Target::Model::ROM01: system = ROM::Name::AppleIIgsROM01; break; + default: system = ROM::Name::AppleIIgsROM03; break; + } + constexpr ROM::Name characters = ROM::Name::AppleIIEnhancedECharacter; + constexpr ROM::Name microcontroller = ROM::Name::AppleIIgsMicrocontrollerROM03; - ROM::Request request = ROM::Request(system) && ROM::Request(characters) && ROM::Request(microcontroller); - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } - rom_ = roms.find(system)->second; - video_->set_character_rom(roms.find(characters)->second); - adb_glu_->set_microcontroller_rom(roms.find(microcontroller)->second); + ROM::Request request = ROM::Request(system) && ROM::Request(characters) && ROM::Request(microcontroller); + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + rom_ = roms.find(system)->second; + video_->set_character_rom(roms.find(characters)->second); + adb_glu_->set_microcontroller_rom(roms.find(microcontroller)->second); - // Run only the currently-interesting self test. -// rom_[0x36402] = 2; -// rom_[0x36403] = 0x7c; // ROM_CHECKSUM [working, when hacks like this are removed] -// rom_[0x36404] = 0x6c; + // Run only the currently-interesting self test. +// rom_[0x36402] = 2; +// rom_[0x36403] = 0x7c; // ROM_CHECKSUM [working, when hacks like this are removed] +// rom_[0x36404] = 0x6c; +// +// rom_[0x36403] = 0x82; // MOVIRAM [working] +// rom_[0x36404] = 0x67; +// +// rom_[0x36403] = 0x2c; // SOFT_SW [working] +// rom_[0x36404] = 0x6a; +// +// rom_[0x36403] = 0xe8; // RAM_ADDR [working] +// rom_[0x36404] = 0x6f; +// +// rom_[0x36403] = 0xc7; // FPI_SPEED [working] +// rom_[0x36404] = 0x6a; +// +// rom_[0x36403] = 0xd7; // SER_TST [broken] +// rom_[0x36404] = 0x68; +// +// rom_[0x36403] = 0xdc; // CLOCK [broken] +// rom_[0x36404] = 0x6c; +// +// rom_[0x36403] = 0x1b; // BAT_RAM [broken] +// rom_[0x36404] = 0x6e; +// +// rom_[0x36403] = 0x11; // FDB (/ADB?) [broken] +// rom_[0x36404] = 0x6f; +// +// rom_[0x36403] = 0x41; // SHADOW_TST [working] +// rom_[0x36404] = 0x6d; +// +// rom_[0x36403] = 0x09; // CUSTOM_IRQ [broken?] +// rom_[0x36404] = 0x6b; +// +// rom_[0x36403] = 0xf4; // DOC_EXEC +// rom_[0x36404] = 0x70; +// +// rom_[0x36403] = 0xab; // ECT_SEQ +// rom_[0x36404] = 0x64; +// +// rom_[0xfc146f] = rom_[0xfc1470] = 0xea; -// rom_[0x36403] = 0x82; // MOVIRAM [working] -// rom_[0x36404] = 0x67; + size_t ram_size = 0; + switch(target.memory_model) { + case Target::MemoryModel::TwoHundredAndFiftySixKB: + ram_size = 256; + break; -// rom_[0x36403] = 0x2c; // SOFT_SW [working] -// rom_[0x36404] = 0x6a; + case Target::MemoryModel::OneMB: + ram_size = 128 + 1024; + break; -// rom_[0x36403] = 0xe8; // RAM_ADDR [working] -// rom_[0x36404] = 0x6f; + case Target::MemoryModel::EightMB: + ram_size = 128 + 8 * 1024; + break; + } + ram_.resize(ram_size * 1024); -// rom_[0x36403] = 0xc7; // FPI_SPEED [working] -// rom_[0x36404] = 0x6a; + memory_.set_storage(ram_, rom_); + video_->set_internal_ram(&ram_[ram_.size() - 128*1024]); -// rom_[0x36403] = 0xd7; // SER_TST [broken] -// rom_[0x36404] = 0x68; + // Attach drives to the IWM. + iwm_->set_drive(0, &drives35_[0]); + iwm_->set_drive(1, &drives35_[1]); -// rom_[0x36403] = 0xdc; // CLOCK [broken] -// rom_[0x36404] = 0x6c; + // Randomise RAM contents. + Memory::Fuzz(ram_); -// rom_[0x36403] = 0x1b; // BAT_RAM [broken] -// rom_[0x36404] = 0x6e; - -// rom_[0x36403] = 0x11; // FDB (/ADB?) [broken] -// rom_[0x36404] = 0x6f; - -// rom_[0x36403] = 0x41; // SHADOW_TST [working] -// rom_[0x36404] = 0x6d; - -// rom_[0x36403] = 0x09; // CUSTOM_IRQ [broken?] -// rom_[0x36404] = 0x6b; - -// rom_[0x36403] = 0xf4; // DOC_EXEC -// rom_[0x36404] = 0x70; - -// rom_[0x36403] = 0xab; // ECT_SEQ -// rom_[0x36404] = 0x64; - -// rom_[0xfc146f] = rom_[0xfc1470] = 0xea; - - size_t ram_size = 0; - switch(target.memory_model) { - case Target::MemoryModel::TwoHundredAndFiftySixKB: - ram_size = 256; - break; - - case Target::MemoryModel::OneMB: - ram_size = 128 + 1024; - break; - - case Target::MemoryModel::EightMB: - ram_size = 128 + 8 * 1024; - break; - } - ram_.resize(ram_size * 1024); - - memory_.set_storage(ram_, rom_); - video_->set_internal_ram(&ram_[ram_.size() - 128*1024]); - - // Attach drives to the IWM. - iwm_->set_drive(0, &drives35_[0]); - iwm_->set_drive(1, &drives35_[1]); - - // Randomise RAM contents. - Memory::Fuzz(ram_); - - // Prior to ROM03 there's no power-on bit. - if(target.model != Target::Model::ROM03) { - speed_register_ &= ~0x40; - } - - // Sync up initial values. - memory_.set_speed_register(speed_register_ ^ 0x80); - - insert_media(target.media); + // Prior to ROM03 there's no power-on bit. + if(target.model != Target::Model::ROM03) { + speed_register_ &= ~0x40; } - ~ConcreteMachine() { - audio_queue_.flush(); + // Sync up initial values. + memory_.set_speed_register(speed_register_ ^ 0x80); + + insert_media(target.media); + } + + ~ConcreteMachine() { + audio_queue_.flush(); + } + + void run_for(const Cycles cycles) override { + m65816_.run_for(cycles); + } + + void flush_output(int outputs) final { + iwm_.flush(); + adb_glu_.flush(); + + if(outputs & Output::Video) { + video_.flush(); } - - void run_for(const Cycles cycles) override { - m65816_.run_for(cycles); + if(outputs & Output::Audio) { + AudioUpdater updater(this); + audio_queue_.perform(); } + } - void flush_output(int outputs) final { - iwm_.flush(); - adb_glu_.flush(); + void set_scan_target(Outputs::Display::ScanTarget *target) override { + video_->set_scan_target(target); + } - if(outputs & Output::Video) { - video_.flush(); - } - if(outputs & Output::Audio) { - AudioUpdater updater(this); - audio_queue_.perform(); + Outputs::Display::ScanStatus get_scaled_scan_status() const override { + return video_->get_scaled_scan_status() * 2.0f; // TODO: expose multiplier and divider via the JustInTime template? + } + + void set_display_type(Outputs::Display::DisplayType display_type) final { + video_->set_display_type(display_type); + } + + Outputs::Display::DisplayType get_display_type() const final { + return video_->get_display_type(); + } + + Outputs::Speaker::Speaker *get_speaker() final { + return &speaker_; + } + + // MARK: MediaTarget. + bool insert_media(const Analyser::Static::Media &media) final { + if(!media.disks.empty()) { + const auto disk = media.disks[0]; + if(disk->get_maximum_head_position().as_int() > 35) { + drives35_[0].set_disk(media.disks[0]); + } else { + drives525_[0].set_disk(media.disks[0]); } } + return true; + } - void set_scan_target(Outputs::Display::ScanTarget *target) override { - video_->set_scan_target(target); - } + // MARK: Activity::Source + void set_activity_observer(Activity::Observer *observer) final { + drives35_[0].set_activity_observer(observer, "First 3.5\" Drive", true); + drives35_[1].set_activity_observer(observer, "Second 3.5\" Drive", true); + drives525_[0].set_activity_observer(observer, "First 5.25\" Drive", true); + drives525_[1].set_activity_observer(observer, "Second 5.25\" Drive", true); + } - Outputs::Display::ScanStatus get_scaled_scan_status() const override { - return video_->get_scaled_scan_status() * 2.0f; // TODO: expose multiplier and divider via the JustInTime template? - } + // MARK: BusHandler. + forceinline Cycles perform_bus_operation(const CPU::WDC65816::BusOperation operation, const uint32_t address, uint8_t *const value) { + const auto ®ion = memory_.region(address); + bool is_1Mhz = false; - void set_display_type(Outputs::Display::DisplayType display_type) final { - video_->set_display_type(display_type); - } + if(operation == CPU::WDC65816::BusOperation::ReadVector && !(memory_.get_shadow_register()&0x40)) { + // I think vector pulls always go to ROM? + // That's slightly implied in the documentation, and doing so makes GS/OS boot, so... + // TODO: but is my guess above re: not doing that if IOLC shadowing is disabled correct? + assert(address <= 0xffff && address >= 0xffe4); + *value = rom_[rom_.size() - 65536 + address]; + } else if(region.flags & MemoryMap::Region::IsIO) { + // Ensure classic auxiliary and language card accesses have effect. + const bool is_read = isReadOperation(operation); + memory_.access(uint16_t(address), is_read); - Outputs::Display::DisplayType get_display_type() const final { - return video_->get_display_type(); - } - - Outputs::Speaker::Speaker *get_speaker() final { - return &speaker_; - } - - // MARK: MediaTarget. - bool insert_media(const Analyser::Static::Media &media) final { - if(!media.disks.empty()) { - const auto disk = media.disks[0]; - if(disk->get_maximum_head_position().as_int() > 35) { - drives35_[0].set_disk(media.disks[0]); - } else { - drives525_[0].set_disk(media.disks[0]); - } - } - return true; - } - - // MARK: Activity::Source - void set_activity_observer(Activity::Observer *observer) final { - drives35_[0].set_activity_observer(observer, "First 3.5\" Drive", true); - drives35_[1].set_activity_observer(observer, "Second 3.5\" Drive", true); - drives525_[0].set_activity_observer(observer, "First 5.25\" Drive", true); - drives525_[1].set_activity_observer(observer, "Second 5.25\" Drive", true); - } - - // MARK: BusHandler. - forceinline Cycles perform_bus_operation(const CPU::WDC65816::BusOperation operation, const uint32_t address, uint8_t *const value) { - const auto ®ion = memory_.region(address); - bool is_1Mhz = false; - - if(operation == CPU::WDC65816::BusOperation::ReadVector && !(memory_.get_shadow_register()&0x40)) { - // I think vector pulls always go to ROM? - // That's slightly implied in the documentation, and doing so makes GS/OS boot, so... - // TODO: but is my guess above re: not doing that if IOLC shadowing is disabled correct? - assert(address <= 0xffff && address >= 0xffe4); - *value = rom_[rom_.size() - 65536 + address]; - } else if(region.flags & MemoryMap::Region::IsIO) { - // Ensure classic auxiliary and language card accesses have effect. - const bool is_read = isReadOperation(operation); - memory_.access(uint16_t(address), is_read); - - const auto address_suffix = address & 0xffff; - assert(address_suffix >= 0xc000 && address_suffix < 0xd000); + const auto address_suffix = address & 0xffff; + assert(address_suffix >= 0xc000 && address_suffix < 0xd000); #define ReadWrite(x) (x) | (is_read * 0x10000) #define Read(x) (x) | 0x10000 #define Write(x) (x) - switch(ReadWrite(address_suffix)) { + switch(ReadWrite(address_suffix)) { - // New video register. - case Read(0xc029): - *value = video_->get_new_video(); - break; - case Write(0xc029): - video_->set_new_video(*value); - assert(*value & 1); + // New video register. + case Read(0xc029): + *value = video_->get_new_video(); + break; + case Write(0xc029): + video_->set_new_video(*value); + assert(*value & 1); - // TODO: I think bits 7 and 0 might also affect the memory map. - // The descripton isn't especially clear — P.90 of the Hardware Reference. - // Revisit if necessary. - break; + // TODO: I think bits 7 and 0 might also affect the memory map. + // The descripton isn't especially clear — P.90 of the Hardware Reference. + // Revisit if necessary. + break; - // Video [and clock] interrupt register. - case Read(0xc023): - *value = video_->get_interrupt_register(); - break; - case Write(0xc023): - video_->set_interrupt_register(*value); - break; + // Video [and clock] interrupt register. + case Read(0xc023): + *value = video_->get_interrupt_register(); + break; + case Write(0xc023): + video_->set_interrupt_register(*value); + break; - // Video interrupt-clear register. - case Write(0xc032): - video_->clear_interrupts(*value); - break; - case Read(0xc032): - // TODO: this seems to be undocumented, but used. What value is likely? - *value = 0xff; - break; + // Video interrupt-clear register. + case Write(0xc032): + video_->clear_interrupts(*value); + break; + case Read(0xc032): + // TODO: this seems to be undocumented, but used. What value is likely? + *value = 0xff; + break; - // Shadow register. - case Read(0xc035): - *value = memory_.get_shadow_register(); - break; - case Write(0xc035): - memory_.set_shadow_register(*value); - break; + // Shadow register. + case Read(0xc035): + *value = memory_.get_shadow_register(); + break; + case Write(0xc035): + memory_.set_shadow_register(*value); + break; - // Clock data. - case Read(0xc033): - *value = clock_.get_data(); - break; - case Write(0xc033): - clock_.set_data(*value); - break; + // Clock data. + case Read(0xc033): + *value = clock_.get_data(); + break; + case Write(0xc033): + clock_.set_data(*value); + break; - // Clock and border control. - case Read(0xc034): - *value = (clock_.get_control() & 0xf0) | (video_.last_valid()->get_border_colour() & 0x0f); - break; - case Write(0xc034): - clock_.set_control(*value); - video_->set_border_colour(*value); - break; + // Clock and border control. + case Read(0xc034): + *value = (clock_.get_control() & 0xf0) | (video_.last_valid()->get_border_colour() & 0x0f); + break; + case Write(0xc034): + clock_.set_control(*value); + video_->set_border_colour(*value); + break; - // Colour text control. - case Write(0xc022): - video_->set_text_colour(*value); - break; - case Read(0xc022): - *value = video_.last_valid()->get_text_colour(); - break; + // Colour text control. + case Write(0xc022): + video_->set_text_colour(*value); + break; + case Read(0xc022): + *value = video_.last_valid()->get_text_colour(); + break; - // Speed register. - case Read(0xc036): - *value = speed_register_ ^ 0x80; - break; - case Write(0xc036): - // b7: 1 => operate at 2.8Mhz; 0 => 1Mhz. - // b6: power-on status; 1 => system has been turned on by the power switch (TODO: what clears this?) - // b5: reserved - // b4: [bank shadowing bit; cf. the memory map] - // b0–3: motor on-off speed detectors; - // 1 => switch to 1Mhz if motor is on; 0 => don't; - // b0 = slot 4 (i.e. watches addresses c0c9, c0c8) - // b1 = slot 5 (i.e. c0d9, c0d8) - // b2 = slot 6 (i.e. c0e9, c0e8) - // b3 = slot 7 (i.e. c0f9, c0f8) - memory_.set_speed_register(*value); - speed_register_ = *value ^ 0x80; - break; + // Speed register. + case Read(0xc036): + *value = speed_register_ ^ 0x80; + break; + case Write(0xc036): + // b7: 1 => operate at 2.8Mhz; 0 => 1Mhz. + // b6: power-on status; 1 => system has been turned on by the power switch (TODO: what clears this?) + // b5: reserved + // b4: [bank shadowing bit; cf. the memory map] + // b0–3: motor on-off speed detectors; + // 1 => switch to 1Mhz if motor is on; 0 => don't; + // b0 = slot 4 (i.e. watches addresses c0c9, c0c8) + // b1 = slot 5 (i.e. c0d9, c0d8) + // b2 = slot 6 (i.e. c0e9, c0e8) + // b3 = slot 7 (i.e. c0f9, c0f8) + memory_.set_speed_register(*value); + speed_register_ = *value ^ 0x80; + break; - // [Memory] State register. - case Read(0xc068): - *value = memory_.get_state_register(); - break; - case Write(0xc068): - memory_.set_state_register(*value); - video_->set_page2(*value & 0x40); - break; + // [Memory] State register. + case Read(0xc068): + *value = memory_.get_state_register(); + break; + case Write(0xc068): + memory_.set_state_register(*value); + video_->set_page2(*value & 0x40); + break; - case Read(0xc069): - case Write(0xc069): - // Swallow silently; often hit as a side effect of a 16-bit write to 0xc068. - break; + case Read(0xc069): + case Write(0xc069): + // Swallow silently; often hit as a side effect of a 16-bit write to 0xc068. + break; - // Various independent memory switch reads [TODO: does the IIe-style keyboard provide the low seven?]. + // Various independent memory switch reads [TODO: does the IIe-style keyboard provide the low seven?]. #define SwitchRead(s) *value = memory_.s ? 0x80 : 0x00; is_1Mhz = true; #define LanguageRead(s) SwitchRead(language_card_switches().state().s) #define AuxiliaryRead(s) SwitchRead(auxiliary_switches().switches().s) #define VideoRead(s) *value = video_.last_valid()->s ? 0x80 : 0x00; is_1Mhz = true; - case Read(0xc011): LanguageRead(bank2); break; - case Read(0xc012): LanguageRead(read); break; - case Read(0xc013): AuxiliaryRead(read_auxiliary_memory); break; - case Read(0xc014): AuxiliaryRead(write_auxiliary_memory); break; - case Read(0xc015): AuxiliaryRead(internal_CX_rom); break; - case Read(0xc016): AuxiliaryRead(alternative_zero_page); break; - case Read(0xc017): AuxiliaryRead(slot_C3_rom); break; - case Read(0xc018): VideoRead(get_80_store()); break; - case Read(0xc019): - VideoRead(get_is_vertical_blank(video_.time_since_flush())); - break; - case Read(0xc01a): VideoRead(get_text()); break; - case Read(0xc01b): VideoRead(get_mixed()); break; - case Read(0xc01c): VideoRead(get_page2()); break; - case Read(0xc01d): VideoRead(get_high_resolution()); break; - case Read(0xc01e): VideoRead(get_alternative_character_set()); break; - case Read(0xc01f): VideoRead(get_80_columns()); break; + case Read(0xc011): LanguageRead(bank2); break; + case Read(0xc012): LanguageRead(read); break; + case Read(0xc013): AuxiliaryRead(read_auxiliary_memory); break; + case Read(0xc014): AuxiliaryRead(write_auxiliary_memory); break; + case Read(0xc015): AuxiliaryRead(internal_CX_rom); break; + case Read(0xc016): AuxiliaryRead(alternative_zero_page); break; + case Read(0xc017): AuxiliaryRead(slot_C3_rom); break; + case Read(0xc018): VideoRead(get_80_store()); break; + case Read(0xc019): + VideoRead(get_is_vertical_blank(video_.time_since_flush())); + break; + case Read(0xc01a): VideoRead(get_text()); break; + case Read(0xc01b): VideoRead(get_mixed()); break; + case Read(0xc01c): VideoRead(get_page2()); break; + case Read(0xc01d): VideoRead(get_high_resolution()); break; + case Read(0xc01e): VideoRead(get_alternative_character_set()); break; + case Read(0xc01f): VideoRead(get_80_columns()); break; #undef VideoRead #undef AuxiliaryRead #undef LanguageRead #undef SwitchRead - // Video switches (and annunciators). - case Read(0xc050): case Read(0xc051): - case Write(0xc050): case Write(0xc051): - video_->set_text(address & 1); - is_1Mhz = true; - break; - case Read(0xc052): case Read(0xc053): - case Write(0xc052): case Write(0xc053): - video_->set_mixed(address & 1); - is_1Mhz = true; - break; - case Read(0xc054): case Read(0xc055): - case Write(0xc054): case Write(0xc055): - video_->set_page2(address & 1); - is_1Mhz = true; - break; - case Read(0xc056): case Read(0xc057): - case Write(0xc056): case Write(0xc057): - video_->set_high_resolution(address&1); - is_1Mhz = true; - break; - case Read(0xc058): case Read(0xc059): - case Write(0xc058): case Write(0xc059): - case Read(0xc05a): case Read(0xc05b): - case Write(0xc05a): case Write(0xc05b): - case Read(0xc05c): case Read(0xc05d): - case Write(0xc05c): case Write(0xc05d): - // Annunciators 0, 1 and 2. - is_1Mhz = true; - break; - case Read(0xc05e): case Read(0xc05f): - case Write(0xc05e): case Write(0xc05f): - video_->set_annunciator_3(!(address&1)); - is_1Mhz = true; - break; - case Write(0xc000): case Write(0xc001): - video_->set_80_store(address & 1); - is_1Mhz = true; - break; - case Write(0xc00c): case Write(0xc00d): - video_->set_80_columns(address & 1); - is_1Mhz = true; - break; - case Write(0xc00e): case Write(0xc00f): - video_->set_alternative_character_set(address & 1); - is_1Mhz = true; - break; + // Video switches (and annunciators). + case Read(0xc050): case Read(0xc051): + case Write(0xc050): case Write(0xc051): + video_->set_text(address & 1); + is_1Mhz = true; + break; + case Read(0xc052): case Read(0xc053): + case Write(0xc052): case Write(0xc053): + video_->set_mixed(address & 1); + is_1Mhz = true; + break; + case Read(0xc054): case Read(0xc055): + case Write(0xc054): case Write(0xc055): + video_->set_page2(address & 1); + is_1Mhz = true; + break; + case Read(0xc056): case Read(0xc057): + case Write(0xc056): case Write(0xc057): + video_->set_high_resolution(address&1); + is_1Mhz = true; + break; + case Read(0xc058): case Read(0xc059): + case Write(0xc058): case Write(0xc059): + case Read(0xc05a): case Read(0xc05b): + case Write(0xc05a): case Write(0xc05b): + case Read(0xc05c): case Read(0xc05d): + case Write(0xc05c): case Write(0xc05d): + // Annunciators 0, 1 and 2. + is_1Mhz = true; + break; + case Read(0xc05e): case Read(0xc05f): + case Write(0xc05e): case Write(0xc05f): + video_->set_annunciator_3(!(address&1)); + is_1Mhz = true; + break; + case Write(0xc000): case Write(0xc001): + video_->set_80_store(address & 1); + is_1Mhz = true; + break; + case Write(0xc00c): case Write(0xc00d): + video_->set_80_columns(address & 1); + is_1Mhz = true; + break; + case Write(0xc00e): case Write(0xc00f): + video_->set_alternative_character_set(address & 1); + is_1Mhz = true; + break; - // ADB and keyboard. - case Read(0xc000): - *value = adb_glu_->get_keyboard_data(); - break; - case Read(0xc010): - *value = adb_glu_->get_any_key_down() ? 0x80 : 0x00; - [[fallthrough]]; - case Write(0xc010): - adb_glu_->clear_key_strobe(); - break; + // ADB and keyboard. + case Read(0xc000): + *value = adb_glu_->get_keyboard_data(); + break; + case Read(0xc010): + *value = adb_glu_->get_any_key_down() ? 0x80 : 0x00; + [[fallthrough]]; + case Write(0xc010): + adb_glu_->clear_key_strobe(); + break; - case Read(0xc024): - *value = adb_glu_->get_mouse_data(); - break; - case Read(0xc025): - *value = adb_glu_->get_modifier_status(); - break; - case Read(0xc026): - *value = adb_glu_->get_data(); - break; - case Write(0xc026): - adb_glu_->set_command(*value); - break; - case Read(0xc027): - *value = adb_glu_->get_status(); - break; - case Write(0xc027): - adb_glu_->set_status(*value); - break; + case Read(0xc024): + *value = adb_glu_->get_mouse_data(); + break; + case Read(0xc025): + *value = adb_glu_->get_modifier_status(); + break; + case Read(0xc026): + *value = adb_glu_->get_data(); + break; + case Write(0xc026): + adb_glu_->set_command(*value); + break; + case Read(0xc027): + *value = adb_glu_->get_status(); + break; + case Write(0xc027): + adb_glu_->set_status(*value); + break; - // The SCC. - case Read(0xc038): case Read(0xc039): case Read(0xc03a): case Read(0xc03b): - *value = scc_.read(int(address)); - break; - case Write(0xc038): case Write(0xc039): case Write(0xc03a): case Write(0xc03b): - scc_.write(int(address), *value); - break; + // The SCC. + case Read(0xc038): case Read(0xc039): case Read(0xc03a): case Read(0xc03b): + *value = scc_.read(int(address)); + break; + case Write(0xc038): case Write(0xc039): case Write(0xc03a): case Write(0xc03b): + scc_.write(int(address), *value); + break; - // The audio GLU. - case Read(0xc03c): { - AudioUpdater updater(this); - *value = sound_glu_.get_control(); - } break; - case Write(0xc03c): { - AudioUpdater updater(this); - sound_glu_.set_control(*value); - } break; - case Read(0xc03d): { - AudioUpdater updater(this); - *value = sound_glu_.get_data(); - } break; - case Write(0xc03d): { - AudioUpdater updater(this); - sound_glu_.set_data(*value); - } break; - case Read(0xc03e): { - AudioUpdater updater(this); - *value = sound_glu_.get_address_low(); - } break; - case Write(0xc03e): { - AudioUpdater updater(this); - sound_glu_.set_address_low(*value); - } break; - case Read(0xc03f): { - AudioUpdater updater(this); - *value = sound_glu_.get_address_high(); - } break; - case Write(0xc03f): { - AudioUpdater updater(this); - sound_glu_.set_address_high(*value); - } break; + // The audio GLU. + case Read(0xc03c): { + AudioUpdater updater(this); + *value = sound_glu_.get_control(); + } break; + case Write(0xc03c): { + AudioUpdater updater(this); + sound_glu_.set_control(*value); + } break; + case Read(0xc03d): { + AudioUpdater updater(this); + *value = sound_glu_.get_data(); + } break; + case Write(0xc03d): { + AudioUpdater updater(this); + sound_glu_.set_data(*value); + } break; + case Read(0xc03e): { + AudioUpdater updater(this); + *value = sound_glu_.get_address_low(); + } break; + case Write(0xc03e): { + AudioUpdater updater(this); + sound_glu_.set_address_low(*value); + } break; + case Read(0xc03f): { + AudioUpdater updater(this); + *value = sound_glu_.get_address_high(); + } break; + case Write(0xc03f): { + AudioUpdater updater(this); + sound_glu_.set_address_high(*value); + } break; - // These were all dealt with by the call to memory_.access. - // TODO: subject to read data? Does vapour lock apply? - case Read(0xc002): case Read(0xc003): case Read(0xc004): case Read(0xc005): - case Read(0xc006): case Read(0xc007): case Read(0xc008): case Read(0xc009): case Read(0xc00a): case Read(0xc00b): - *value = 0xff; - break; - case Write(0xc002): case Write(0xc003): case Write(0xc004): case Write(0xc005): - case Write(0xc006): case Write(0xc007): case Write(0xc008): case Write(0xc009): case Write(0xc00a): case Write(0xc00b): - break; + // These were all dealt with by the call to memory_.access. + // TODO: subject to read data? Does vapour lock apply? + case Read(0xc002): case Read(0xc003): case Read(0xc004): case Read(0xc005): + case Read(0xc006): case Read(0xc007): case Read(0xc008): case Read(0xc009): case Read(0xc00a): case Read(0xc00b): + *value = 0xff; + break; + case Write(0xc002): case Write(0xc003): case Write(0xc004): case Write(0xc005): + case Write(0xc006): case Write(0xc007): case Write(0xc008): case Write(0xc009): case Write(0xc00a): case Write(0xc00b): + break; - // Interrupt ROM addresses; Cf. P25 of the Hardware Reference. - case Read(0xc071): case Read(0xc072): case Read(0xc073): - case Read(0xc074): case Read(0xc075): case Read(0xc076): case Read(0xc077): - case Read(0xc078): case Read(0xc079): case Read(0xc07a): case Read(0xc07b): - case Read(0xc07c): case Read(0xc07d): case Read(0xc07e): case Read(0xc07f): - *value = rom_[rom_.size() - 65536 + address_suffix]; - break; + // Interrupt ROM addresses; Cf. P25 of the Hardware Reference. + case Read(0xc071): case Read(0xc072): case Read(0xc073): + case Read(0xc074): case Read(0xc075): case Read(0xc076): case Read(0xc077): + case Read(0xc078): case Read(0xc079): case Read(0xc07a): case Read(0xc07b): + case Read(0xc07c): case Read(0xc07d): case Read(0xc07e): case Read(0xc07f): + *value = rom_[rom_.size() - 65536 + address_suffix]; + break; + // Analogue inputs. + case Read(0xc061): + *value = (adb_glu_->get_command_button() || joysticks_.button(0)) ? 0x80 : 0x00; + is_1Mhz = true; + break; + + case Read(0xc062): + *value = (adb_glu_->get_option_button() || joysticks_.button(1)) ? 0x80 : 0x00; + is_1Mhz = true; + break; + + case Read(0xc063): + *value = joysticks_.button(2) ? 0x80 : 0x00; + is_1Mhz = true; + break; + + case Read(0xc064): + case Read(0xc065): + case Read(0xc066): + case Read(0xc067): { // Analogue inputs. - case Read(0xc061): - *value = (adb_glu_->get_command_button() || joysticks_.button(0)) ? 0x80 : 0x00; - is_1Mhz = true; - break; + const size_t input = address_suffix - 0xc064; + *value = joysticks_.analogue_channel_is_discharged(input) ? 0x00 : 0x80; + is_1Mhz = true; + } break; - case Read(0xc062): - *value = (adb_glu_->get_option_button() || joysticks_.button(1)) ? 0x80 : 0x00; - is_1Mhz = true; - break; + case Read(0xc070): case Write(0xc070): + joysticks_.access_c070(); + is_1Mhz = true; + break; - case Read(0xc063): - *value = joysticks_.button(2) ? 0x80 : 0x00; - is_1Mhz = true; - break; + // Monochome/colour register. + case Read(0xc021): + // "Uses bit 7 to determine whether composite output is colour 9) or gray scale (1)." + *value = video_.last_valid()->get_composite_is_colour() ? 0x00 : 0x80; + break; + case Write(0xc021): + video_->set_composite_is_colour(!(*value & 0x80)); + break; - case Read(0xc064): - case Read(0xc065): - case Read(0xc066): - case Read(0xc067): { - // Analogue inputs. - const size_t input = address_suffix - 0xc064; - *value = joysticks_.analogue_channel_is_discharged(input) ? 0x00 : 0x80; - is_1Mhz = true; - } break; + case Read(0xc02e): + *value = video_.last_valid()->get_vertical_counter(video_.time_since_flush()); + is_1Mhz = true; + break; + case Read(0xc02f): + *value = video_.last_valid()->get_horizontal_counter(video_.time_since_flush()); + is_1Mhz = true; + break; - case Read(0xc070): case Write(0xc070): - joysticks_.access_c070(); - is_1Mhz = true; - break; + // C037 seems to be just a full-speed storage register. + case Read(0xc037): + *value = c037_; + break; + case Write(0xc037): + c037_ = *value; + break; - // Monochome/colour register. - case Read(0xc021): - // "Uses bit 7 to determine whether composite output is colour 9) or gray scale (1)." - *value = video_.last_valid()->get_composite_is_colour() ? 0x00 : 0x80; - break; - case Write(0xc021): - video_->set_composite_is_colour(!(*value & 0x80)); - break; + case Read(0xc041): + *value = megaii_interrupt_mask_; + is_1Mhz = true; + break; + case Write(0xc041): + megaii_interrupt_mask_ = *value; + video_->set_megaii_interrupts_enabled(*value); + is_1Mhz = true; + break; + case Read(0xc044): + // MMDELTAX byte. + *value = 0; + is_1Mhz = true; + break; + case Read(0xc045): + // MMDELTAY byte. + *value = 0; + is_1Mhz = true; + break; + case Read(0xc046): + *value = video_->get_megaii_interrupt_status(); + is_1Mhz = true; + break; + case Read(0xc047): case Write(0xc047): + video_->clear_megaii_interrupts(); + is_1Mhz = true; + break; + case Read(0xc048): case Write(0xc048): + // No-op: Clear Mega II mouse interrupt flags + is_1Mhz = true; + break; - case Read(0xc02e): - *value = video_.last_valid()->get_vertical_counter(video_.time_since_flush()); - is_1Mhz = true; - break; - case Read(0xc02f): - *value = video_.last_valid()->get_horizontal_counter(video_.time_since_flush()); - is_1Mhz = true; - break; + // Language select. + // b7, b6, b5: character generator language select; + // b4: NTSC/PAL (0 = NTC); + // b3: language select — primary or secondary. + case Read(0xc02b): + *value = language_; + break; + case Write(0xc02b): + language_ = *value; + break; - // C037 seems to be just a full-speed storage register. - case Read(0xc037): - *value = c037_; - break; - case Write(0xc037): - c037_ = *value; - break; + // TODO: 0xc02c is "Addr for tst mode read of character ROM". So it reads... what? - case Read(0xc041): - *value = megaii_interrupt_mask_; - is_1Mhz = true; - break; - case Write(0xc041): - megaii_interrupt_mask_ = *value; - video_->set_megaii_interrupts_enabled(*value); - is_1Mhz = true; - break; - case Read(0xc044): - // MMDELTAX byte. - *value = 0; - is_1Mhz = true; - break; - case Read(0xc045): - // MMDELTAY byte. - *value = 0; - is_1Mhz = true; - break; - case Read(0xc046): - *value = video_->get_megaii_interrupt_status(); - is_1Mhz = true; - break; - case Read(0xc047): case Write(0xc047): - video_->clear_megaii_interrupts(); - is_1Mhz = true; - break; - case Read(0xc048): case Write(0xc048): - // No-op: Clear Mega II mouse interrupt flags - is_1Mhz = true; - break; + // Slot select. + case Read(0xc02d): + // b7: 0 = internal ROM code for slot 7; + // b6: 0 = internal ROM code for slot 6; + // b5: 0 = internal ROM code for slot 5; + // b4: 0 = internal ROM code for slot 4; + // b3: reserved; + // b2: internal ROM code for slot 2; + // b1: internal ROM code for slot 1; + // b0: reserved. + *value = card_mask_; + break; + case Write(0xc02d): + card_mask_ = *value; + break; - // Language select. - // b7, b6, b5: character generator language select; - // b4: NTSC/PAL (0 = NTC); - // b3: language select — primary or secondary. - case Read(0xc02b): - *value = language_; - break; - case Write(0xc02b): - language_ = *value; - break; + case Read(0xc030): case Write(0xc030): { + AudioUpdater updater(this); + audio_toggle_.set_output(!audio_toggle_.get_output()); + } break; - // TODO: 0xc02c is "Addr for tst mode read of character ROM". So it reads... what? + // 'Test Mode', whatever that is (?) + case Read(0xc06e): case Read(0xc06f): + case Write(0xc06e): case Write(0xc06f): + test_mode_ = address & 1; + break; + case Read(0xc06d): + *value = test_mode_ * 0x80; + break; - // Slot select. - case Read(0xc02d): - // b7: 0 = internal ROM code for slot 7; - // b6: 0 = internal ROM code for slot 6; - // b5: 0 = internal ROM code for slot 5; - // b4: 0 = internal ROM code for slot 4; - // b3: reserved; - // b2: internal ROM code for slot 2; - // b1: internal ROM code for slot 1; - // b0: reserved. - *value = card_mask_; - break; - case Write(0xc02d): - card_mask_ = *value; - break; + // Disk drive controls additional to the IWM. + case Read(0xc031): + *value = disk_select_; + break; + case Write(0xc031): + // b7: 0 = use head 0; 1 = use head 1. + // b6: 0 = use 5.25" disks; 1 = use 3.5". + disk_select_ = *value; + iwm_->set_select(*value & 0x80); - case Read(0xc030): case Write(0xc030): { - AudioUpdater updater(this); - audio_toggle_.set_output(!audio_toggle_.get_output()); - } break; + // Presumably bit 6 selects between two 5.25" drives rather than the two 3.5"? + if(*value & 0x40) { + iwm_->set_drive(0, &drives35_[0]); + iwm_->set_drive(1, &drives35_[1]); + } else { + iwm_->set_drive(0, &drives525_[0]); + iwm_->set_drive(1, &drives525_[1]); + } + break; - // 'Test Mode', whatever that is (?) - case Read(0xc06e): case Read(0xc06f): - case Write(0xc06e): case Write(0xc06f): - test_mode_ = address & 1; - break; - case Read(0xc06d): - *value = test_mode_ * 0x80; - break; + // Addresses on other Apple II devices which do nothing on the GS. + case Read(0xc020): case Write(0xc020): // Reserved for future system expansion. + case Read(0xc028): case Write(0xc028): // ROMBANK; "not used in Apple IIGS". + case Read(0xc02a): case Write(0xc02a): // Reserved for future system expansion. + case Read(0xc040): case Write(0xc040): // Reserved for future system expansion. + case Read(0xc042): case Write(0xc042): // Reserved for future system expansion. + case Read(0xc043): case Write(0xc043): // Reserved for future system expansion. + case Read(0xc049): case Write(0xc049): // Reserved for future system expansion. + case Read(0xc04a): case Write(0xc04a): // Reserved for future system expansion. + case Read(0xc04b): case Write(0xc04b): // Reserved for future system expansion. + case Read(0xc04c): case Write(0xc04c): // Reserved for future system expansion. + case Read(0xc04d): case Write(0xc04d): // Reserved for future system expansion. + case Read(0xc04e): case Write(0xc04e): // Reserved for future system expansion. + case Read(0xc04f): case Write(0xc04f): // Reserved for future system expansion. + case Read(0xc06b): case Write(0xc06b): // Reserved for future system expansion. + case Read(0xc06c): case Write(0xc06c): // Reserved for future system expansion. + case Write(0xc07e): + break; - // Disk drive controls additional to the IWM. - case Read(0xc031): - *value = disk_select_; - break; - case Write(0xc031): - // b7: 0 = use head 0; 1 = use head 1. - // b6: 0 = use 5.25" disks; 1 = use 3.5". - disk_select_ = *value; - iwm_->set_select(*value & 0x80); + default: + // Update motor mask bits. + switch(address_suffix) { + case 0xc0c8: motor_flags_ &= ~0x01; break; + case 0xc0c9: motor_flags_ |= 0x01; break; + case 0xc0d8: motor_flags_ &= ~0x02; break; + case 0xc0d9: motor_flags_ |= 0x02; break; + case 0xc0e8: motor_flags_ &= ~0x04; break; + case 0xc0e9: motor_flags_ |= 0x04; break; + case 0xc0f8: motor_flags_ &= ~0x08; break; + case 0xc0f9: motor_flags_ |= 0x08; break; + } - // Presumably bit 6 selects between two 5.25" drives rather than the two 3.5"? - if(*value & 0x40) { - iwm_->set_drive(0, &drives35_[0]); - iwm_->set_drive(1, &drives35_[1]); + // Check for a card access. + if(address_suffix >= 0xc080 && address_suffix < 0xc800) { + // This is an abridged version of the similar code in AppleII.cpp from + // line 653; it would be good to factor that out and support cards here. + // For now just either supply the internal ROM or nothing as per the + // current card mask. + + size_t card_number = 0; + if(address_suffix >= 0xc100) { + /* + Decode the area conventionally used by cards for ROMs: + 0xCn00 to 0xCnff: card n. + */ + card_number = (address_suffix - 0xc000) >> 8; } else { - iwm_->set_drive(0, &drives525_[0]); - iwm_->set_drive(1, &drives525_[1]); - } - break; - - // Addresses on other Apple II devices which do nothing on the GS. - case Read(0xc020): case Write(0xc020): // Reserved for future system expansion. - case Read(0xc028): case Write(0xc028): // ROMBANK; "not used in Apple IIGS". - case Read(0xc02a): case Write(0xc02a): // Reserved for future system expansion. - case Read(0xc040): case Write(0xc040): // Reserved for future system expansion. - case Read(0xc042): case Write(0xc042): // Reserved for future system expansion. - case Read(0xc043): case Write(0xc043): // Reserved for future system expansion. - case Read(0xc049): case Write(0xc049): // Reserved for future system expansion. - case Read(0xc04a): case Write(0xc04a): // Reserved for future system expansion. - case Read(0xc04b): case Write(0xc04b): // Reserved for future system expansion. - case Read(0xc04c): case Write(0xc04c): // Reserved for future system expansion. - case Read(0xc04d): case Write(0xc04d): // Reserved for future system expansion. - case Read(0xc04e): case Write(0xc04e): // Reserved for future system expansion. - case Read(0xc04f): case Write(0xc04f): // Reserved for future system expansion. - case Read(0xc06b): case Write(0xc06b): // Reserved for future system expansion. - case Read(0xc06c): case Write(0xc06c): // Reserved for future system expansion. - case Write(0xc07e): - break; - - default: - // Update motor mask bits. - switch(address_suffix) { - case 0xc0c8: motor_flags_ &= ~0x01; break; - case 0xc0c9: motor_flags_ |= 0x01; break; - case 0xc0d8: motor_flags_ &= ~0x02; break; - case 0xc0d9: motor_flags_ |= 0x02; break; - case 0xc0e8: motor_flags_ &= ~0x04; break; - case 0xc0e9: motor_flags_ |= 0x04; break; - case 0xc0f8: motor_flags_ &= ~0x08; break; - case 0xc0f9: motor_flags_ |= 0x08; break; + /* + Decode the area conventionally used by cards for registers: + C0n0 to C0nF: card n - 8. + */ + card_number = (address_suffix - 0xc080) >> 4; } - // Check for a card access. - if(address_suffix >= 0xc080 && address_suffix < 0xc800) { - // This is an abridged version of the similar code in AppleII.cpp from - // line 653; it would be good to factor that out and support cards here. - // For now just either supply the internal ROM or nothing as per the - // current card mask. - - size_t card_number = 0; - if(address_suffix >= 0xc100) { - /* - Decode the area conventionally used by cards for ROMs: - 0xCn00 to 0xCnff: card n. - */ - card_number = (address_suffix - 0xc000) >> 8; - } else { - /* - Decode the area conventionally used by cards for registers: - C0n0 to C0nF: card n - 8. - */ - card_number = (address_suffix - 0xc080) >> 4; + const uint8_t permitted_card_mask_ = card_mask_ & 0xf6; + if(permitted_card_mask_ & (1 << card_number)) { + // TODO: Access an actual card. + assert(operation != CPU::WDC65816::BusOperation::ReadOpcode); + if(is_read) { + *value = 0xff; } + } else { + switch(address_suffix) { + default: + // Temporary: log _potential_ mistakes. + if((address_suffix < 0xc100 && address_suffix >= 0xc090) || (address_suffix < 0xc080)) { + printf("Internal card-area access: %04x\n", address_suffix); + } + if(is_read) { + *value = rom_[rom_.size() - 65536 + address_suffix]; + } + break; - const uint8_t permitted_card_mask_ = card_mask_ & 0xf6; - if(permitted_card_mask_ & (1 << card_number)) { - // TODO: Access an actual card. - assert(operation != CPU::WDC65816::BusOperation::ReadOpcode); - if(is_read) { - *value = 0xff; - } - } else { - switch(address_suffix) { - default: - // Temporary: log _potential_ mistakes. - if((address_suffix < 0xc100 && address_suffix >= 0xc090) || (address_suffix < 0xc080)) { - printf("Internal card-area access: %04x\n", address_suffix); - } - if(is_read) { - *value = rom_[rom_.size() - 65536 + address_suffix]; - } - break; - - // IWM. - case 0xc0e0: case 0xc0e1: case 0xc0e2: case 0xc0e3: - case 0xc0e4: case 0xc0e5: case 0xc0e6: case 0xc0e7: - case 0xc0e8: case 0xc0e9: case 0xc0ea: case 0xc0eb: - case 0xc0ec: case 0xc0ed: case 0xc0ee: case 0xc0ef: - if(is_read) { - *value = iwm_->read(int(address_suffix)); - } else { - iwm_->write(int(address_suffix), *value); - } - break; - } + // IWM. + case 0xc0e0: case 0xc0e1: case 0xc0e2: case 0xc0e3: + case 0xc0e4: case 0xc0e5: case 0xc0e6: case 0xc0e7: + case 0xc0e8: case 0xc0e9: case 0xc0ea: case 0xc0eb: + case 0xc0ec: case 0xc0ed: case 0xc0ee: case 0xc0ef: + if(is_read) { + *value = iwm_->read(int(address_suffix)); + } else { + iwm_->write(int(address_suffix), *value); + } + break; } + } #undef ReadWrite #undef Read #undef Write - } else { - // Access the internal ROM. - // - // TODO: should probably occur only if there was a preceding access to a built-in - // card ROM? - if(is_read) { - *value = rom_[rom_.size() - 65536 + address_suffix]; - } - - if(address_suffix < 0xc080) { - // TODO: all other IO accesses. - printf("Unhandled IO %s: %04x\n", is_read ? "read" : "write", address_suffix); - } + } else { + // Access the internal ROM. + // + // TODO: should probably occur only if there was a preceding access to a built-in + // card ROM? + if(is_read) { + *value = rom_[rom_.size() - 65536 + address_suffix]; } - } - } else { - // For debugging purposes; if execution heads off into an unmapped page then - // it's pretty certain that my 65816 still has issues. - assert(operation != CPU::WDC65816::BusOperation::ReadOpcode || region.read); - is_1Mhz = region.flags & MemoryMap::Region::Is1Mhz; - if(isReadOperation(operation)) { - *value = memory_.read(region, address); - } else { - // Shadowed writes also occur "at 1Mhz". - // TODO: this is probably an approximation. I'm assuming that there's the ability asynchronously to post - // both a 1Mhz cycle and a 2.8Mhz cycle and since the latter always fits into the former, this is sufficiently - // descriptive. I suspect this isn't true as it wouldn't explain the speed boost that Wolfenstein and others - // get by adding periodic NOPs within their copy-to-shadow step. - // - // Maybe the interaction with 2.8Mhz refresh isn't as straightforward as I think? - const bool is_shadowed = memory_.is_shadowed(region, address); - is_1Mhz |= is_shadowed; - - // Use a very broad test for flushing video: any write to $e0 or $e1, or any write that is shadowed. - // TODO: at least restrict the e0/e1 test to possible video buffers! - if((address >= 0xe0'0400 && address < 0xe1'a000) || is_shadowed) { - video_.flush(); + if(address_suffix < 0xc080) { + // TODO: all other IO accesses. + printf("Unhandled IO %s: %04x\n", is_read ? "read" : "write", address_suffix); + } } - - memory_.write(region, address, *value); - } } + } else { + // For debugging purposes; if execution heads off into an unmapped page then + // it's pretty certain that my 65816 still has issues. + assert(operation != CPU::WDC65816::BusOperation::ReadOpcode || region.read); + is_1Mhz = region.flags & MemoryMap::Region::Is1Mhz; - Cycles duration; - - // In preparation for this test: the top bit of speed_register_ has been inverted, - // so 1 => 1Mhz, 0 => 2.8Mhz, and motor_flags_ always has that bit set. - if(is_1Mhz || (speed_register_ & motor_flags_)) { - // TODO: this is very implicitly linked to the video timing; make that overt somehow. Even if it's just with a redundant video setter at construction. - const int current_length = 14 + 2*(slow_access_phase_ / 896); // Length of cycle currently ongoing. - const int phase_adjust = (current_length - slow_access_phase_%14)%current_length; // Amount of time to expand waiting until end of cycle, if not actually at start. - const int access_phase = (slow_access_phase_ + phase_adjust)%912; // Phase at which access will begin. - const int next_length = 14 + 2*(access_phase / 896); // Length of cycle that this access will occur within. - duration = Cycles(next_length + phase_adjust); + if(isReadOperation(operation)) { + *value = memory_.read(region, address); } else { - // Clues as to 'fast' refresh timing: + // Shadowed writes also occur "at 1Mhz". + // TODO: this is probably an approximation. I'm assuming that there's the ability asynchronously to post + // both a 1Mhz cycle and a 2.8Mhz cycle and since the latter always fits into the former, this is sufficiently + // descriptive. I suspect this isn't true as it wouldn't explain the speed boost that Wolfenstein and others + // get by adding periodic NOPs within their copy-to-shadow step. // - // (i) "The time required for the refresh cycles reduces the effective - // processor speed for programs in RAM by about 8 percent."; - // (ii) "These cycles occur approximately every 3.5 microseconds" - // - // 3.5µs @ 14,318,180Hz => one every 50.11 cycles. Safe to assume every 10th fast cycle - // is refresh? That feels like a lot. - // - // (and the IIgs is smart enough that refresh is applicable only to RAM accesses). - const int phase_adjust = (5 - fast_access_phase_%5)%5; - const int refresh = (fast_access_phase_ / 45) * bool(region.write) * 5; - duration = Cycles(5 + phase_adjust + refresh); - } - // TODO: lookup tables to avoid the above? LCM of the two phases is 22,800 so probably 912+50 bytes plus two counters. - fast_access_phase_ = (fast_access_phase_ + duration.as()) % 50; - slow_access_phase_ = (slow_access_phase_ + duration.as()) % 912; + // Maybe the interaction with 2.8Mhz refresh isn't as straightforward as I think? + const bool is_shadowed = memory_.is_shadowed(region, address); + is_1Mhz |= is_shadowed; - // Propagate time far and wide. - cycles_since_clock_tick_ += duration; - auto ticks = cycles_since_clock_tick_.divide(Cycles(CLOCK_RATE)).as_integral(); - while(ticks--) { - clock_.update(); - video_.last_valid()->notify_clock_tick(); // The video controller marshalls the one-second interrupt. - // TODO: I think I may have made a false assumption here; does - // the VGC have an independent 1-second interrupt? - update_interrupts(); - } - - video_ += duration; - iwm_ += duration; - cycles_since_audio_update_ += duration; - adb_glu_ += duration; - - if(cycles_since_audio_update_ >= cycles_until_audio_event_) { - AudioUpdater updater(this); - update_interrupts(); - } - if(video_.did_flush()) { - update_interrupts(); - - const bool is_vertical_blank = video_.last_valid()->get_is_vertical_blank(video_.time_since_flush()); - if(is_vertical_blank != adb_glu_.last_valid()->get_vertical_blank()) { - adb_glu_->set_vertical_blank(is_vertical_blank); + // Use a very broad test for flushing video: any write to $e0 or $e1, or any write that is shadowed. + // TODO: at least restrict the e0/e1 test to possible video buffers! + if((address >= 0xe0'0400 && address < 0xe1'a000) || is_shadowed) { + video_.flush(); } + + memory_.write(region, address, *value); } - - joysticks_.update_charge(duration.as() / 14.0f); - - return duration; } - void update_interrupts() { - // Update the interrupt line. - // TODO: add ADB controller as event source. - m65816_.set_irq_line(video_.last_valid()->get_interrupt_line() || sound_glu_.get_interrupt_line()); + Cycles duration; + + // In preparation for this test: the top bit of speed_register_ has been inverted, + // so 1 => 1Mhz, 0 => 2.8Mhz, and motor_flags_ always has that bit set. + if(is_1Mhz || (speed_register_ & motor_flags_)) { + // TODO: this is very implicitly linked to the video timing; make that overt somehow. Even if it's just with a redundant video setter at construction. + const int current_length = 14 + 2*(slow_access_phase_ / 896); // Length of cycle currently ongoing. + const int phase_adjust = (current_length - slow_access_phase_%14)%current_length; // Amount of time to expand waiting until end of cycle, if not actually at start. + const int access_phase = (slow_access_phase_ + phase_adjust)%912; // Phase at which access will begin. + const int next_length = 14 + 2*(access_phase / 896); // Length of cycle that this access will occur within. + duration = Cycles(next_length + phase_adjust); + } else { + // Clues as to 'fast' refresh timing: + // + // (i) "The time required for the refresh cycles reduces the effective + // processor speed for programs in RAM by about 8 percent."; + // (ii) "These cycles occur approximately every 3.5 microseconds" + // + // 3.5µs @ 14,318,180Hz => one every 50.11 cycles. Safe to assume every 10th fast cycle + // is refresh? That feels like a lot. + // + // (and the IIgs is smart enough that refresh is applicable only to RAM accesses). + const int phase_adjust = (5 - fast_access_phase_%5)%5; + const int refresh = (fast_access_phase_ / 45) * bool(region.write) * 5; + duration = Cycles(5 + phase_adjust + refresh); + } + // TODO: lookup tables to avoid the above? LCM of the two phases is 22,800 so probably 912+50 bytes plus two counters. + fast_access_phase_ = (fast_access_phase_ + duration.as()) % 50; + slow_access_phase_ = (slow_access_phase_ + duration.as()) % 912; + + // Propagate time far and wide. + cycles_since_clock_tick_ += duration; + auto ticks = cycles_since_clock_tick_.divide(Cycles(CLOCK_RATE)).as_integral(); + while(ticks--) { + clock_.update(); + video_.last_valid()->notify_clock_tick(); // The video controller marshalls the one-second interrupt. + // TODO: I think I may have made a false assumption here; does + // the VGC have an independent 1-second interrupt? + update_interrupts(); } - // MARK: - Input. - KeyboardMapper *get_keyboard_mapper() final { - return &keyboard_mapper_; + video_ += duration; + iwm_ += duration; + cycles_since_audio_update_ += duration; + adb_glu_ += duration; + + if(cycles_since_audio_update_ >= cycles_until_audio_event_) { + AudioUpdater updater(this); + update_interrupts(); + } + if(video_.did_flush()) { + update_interrupts(); + + const bool is_vertical_blank = video_.last_valid()->get_is_vertical_blank(video_.time_since_flush()); + if(is_vertical_blank != adb_glu_.last_valid()->get_vertical_blank()) { + adb_glu_->set_vertical_blank(is_vertical_blank); + } } - void set_key_state(uint16_t key, bool is_pressed) final { - adb_glu_.last_valid()->keyboard().set_key_pressed(Apple::ADB::Key(key), is_pressed); - } + joysticks_.update_charge(duration.as() / 14.0f); - void clear_all_keys() final { - adb_glu_.last_valid()->keyboard().clear_all_keys(); - } + return duration; + } - Inputs::Mouse &get_mouse() final { - return adb_glu_.last_valid()->get_mouse(); - } + void update_interrupts() { + // Update the interrupt line. + // TODO: add ADB controller as event source. + m65816_.set_irq_line(video_.last_valid()->get_interrupt_line() || sound_glu_.get_interrupt_line()); + } - const std::vector> &get_joysticks() final { - return joysticks_.get_joysticks(); - } + // MARK: - Input. + KeyboardMapper *get_keyboard_mapper() final { + return &keyboard_mapper_; + } + void set_key_state(uint16_t key, bool is_pressed) final { + adb_glu_.last_valid()->keyboard().set_key_pressed(Apple::ADB::Key(key), is_pressed); + } + + void clear_all_keys() final { + adb_glu_.last_valid()->keyboard().clear_all_keys(); + } + + Inputs::Mouse &get_mouse() final { + return adb_glu_.last_valid()->get_mouse(); + } + + const std::vector> &get_joysticks() final { + return joysticks_.get_joysticks(); + } + +private: + CPU::WDC65816::Processor m65816_; + MemoryMap memory_; + + // MARK: - Timing. + + int fast_access_phase_ = 0; + int slow_access_phase_ = 0; + + uint8_t speed_register_ = 0x40; // i.e. Power-on status. (TODO: only if ROM03?) + uint8_t motor_flags_ = 0x80; + + // MARK: - Memory storage. + + std::vector ram_; + std::vector rom_; + uint8_t c037_ = 0; + + // MARK: - Other components. + + Apple::Clock::ParallelClock clock_; + JustInTimeActor video_; // i.e. run video at 7Mhz. + JustInTimeActor adb_glu_; // i.e. 3,579,545Mhz. + Zilog::SCC::z8530 scc_; + JustInTimeActor iwm_; + Cycles cycles_since_clock_tick_; + Apple::Macintosh::DoubleDensityDrive drives35_[2]; + Apple::Disk::DiskIIDrive drives525_[2]; + + // The audio parts. + Concurrency::AsyncTaskQueue audio_queue_; + Apple::IIgs::Sound::GLU sound_glu_; + Audio::Toggle audio_toggle_; + using AudioSource = Outputs::Speaker::CompoundSource; + AudioSource mixer_; + Outputs::Speaker::PullLowpass speaker_; + Cycles cycles_since_audio_update_; + Cycles cycles_until_audio_event_; + static constexpr int audio_divider = 16; + void update_audio() { + const auto divided_cycles = cycles_since_audio_update_.divide(Cycles(audio_divider)); + sound_glu_.run_for(divided_cycles); + speaker_.run_for(audio_queue_, divided_cycles); + } + class AudioUpdater { + public: + AudioUpdater(ConcreteMachine *machine) : machine_(machine) { + machine_->update_audio(); + } + ~AudioUpdater() { + machine_->cycles_until_audio_event_ = machine_->sound_glu_.next_sequence_point(); + } private: - CPU::WDC65816::Processor m65816_; - MemoryMap memory_; + ConcreteMachine *machine_; + }; + friend AudioUpdater; - // MARK: - Timing. + // MARK: - Keyboard and joystick. + Apple::ADB::KeyboardMapper keyboard_mapper_; + Apple::II::JoystickPair joysticks_; - int fast_access_phase_ = 0; - int slow_access_phase_ = 0; + // MARK: - Cards. - uint8_t speed_register_ = 0x40; // i.e. Power-on status. (TODO: only if ROM03?) - uint8_t motor_flags_ = 0x80; + // TODO: most of cards. + uint8_t card_mask_ = 0x00; - // MARK: - Memory storage. + bool test_mode_ = false; + uint8_t language_ = 0; + uint8_t disk_select_ = 0; - std::vector ram_; - std::vector rom_; - uint8_t c037_ = 0; - - // MARK: - Other components. - - Apple::Clock::ParallelClock clock_; - JustInTimeActor video_; // i.e. run video at 7Mhz. - JustInTimeActor adb_glu_; // i.e. 3,579,545Mhz. - Zilog::SCC::z8530 scc_; - JustInTimeActor iwm_; - Cycles cycles_since_clock_tick_; - Apple::Macintosh::DoubleDensityDrive drives35_[2]; - Apple::Disk::DiskIIDrive drives525_[2]; - - // The audio parts. - Concurrency::AsyncTaskQueue audio_queue_; - Apple::IIgs::Sound::GLU sound_glu_; - Audio::Toggle audio_toggle_; - using AudioSource = Outputs::Speaker::CompoundSource; - AudioSource mixer_; - Outputs::Speaker::PullLowpass speaker_; - Cycles cycles_since_audio_update_; - Cycles cycles_until_audio_event_; - static constexpr int audio_divider = 16; - void update_audio() { - const auto divided_cycles = cycles_since_audio_update_.divide(Cycles(audio_divider)); - sound_glu_.run_for(divided_cycles); - speaker_.run_for(audio_queue_, divided_cycles); - } - class AudioUpdater { - public: - AudioUpdater(ConcreteMachine *machine) : machine_(machine) { - machine_->update_audio(); - } - ~AudioUpdater() { - machine_->cycles_until_audio_event_ = machine_->sound_glu_.next_sequence_point(); - } - private: - ConcreteMachine *machine_; - }; - friend AudioUpdater; - - // MARK: - Keyboard and joystick. - Apple::ADB::KeyboardMapper keyboard_mapper_; - Apple::II::JoystickPair joysticks_; - - // MARK: - Cards. - - // TODO: most of cards. - uint8_t card_mask_ = 0x00; - - bool test_mode_ = false; - uint8_t language_ = 0; - uint8_t disk_select_ = 0; - - uint8_t megaii_interrupt_mask_ = 0; + uint8_t megaii_interrupt_mask_ = 0; }; } diff --git a/Machines/Apple/AppleIIgs/MemoryMap.hpp b/Machines/Apple/AppleIIgs/MemoryMap.hpp index 363758475..94fef9c05 100644 --- a/Machines/Apple/AppleIIgs/MemoryMap.hpp +++ b/Machines/Apple/AppleIIgs/MemoryMap.hpp @@ -20,144 +20,144 @@ namespace Apple::IIgs { class MemoryMap { - public: - // MARK: - Initial construction and configuration. +public: + // MARK: - Initial construction and configuration. - MemoryMap(bool is_rom03) : auxiliary_switches_(*this), language_card_(*this) { - setup_shadow_maps(is_rom03); - } + MemoryMap(bool is_rom03) : auxiliary_switches_(*this), language_card_(*this) { + setup_shadow_maps(is_rom03); + } - /// Sets the ROM and RAM storage underlying this MemoryMap. - void set_storage(std::vector &ram, std::vector &rom); + /// Sets the ROM and RAM storage underlying this MemoryMap. + void set_storage(std::vector &ram, std::vector &rom); - // MARK: - Live bus access notifications and register access. + // MARK: - Live bus access notifications and register access. - void set_shadow_register(uint8_t value); - uint8_t get_shadow_register() const; + void set_shadow_register(uint8_t value); + uint8_t get_shadow_register() const; - void set_speed_register(uint8_t value); + void set_speed_register(uint8_t value); - void set_state_register(uint8_t value); - uint8_t get_state_register() const; + void set_state_register(uint8_t value); + uint8_t get_state_register() const; - void access(uint16_t address, bool is_read); + void access(uint16_t address, bool is_read); - using AuxiliaryMemorySwitches = Apple::II::AuxiliaryMemorySwitches; - const AuxiliaryMemorySwitches &auxiliary_switches() const { - return auxiliary_switches_; - } + using AuxiliaryMemorySwitches = Apple::II::AuxiliaryMemorySwitches; + const AuxiliaryMemorySwitches &auxiliary_switches() const { + return auxiliary_switches_; + } - using LanguageCardSwitches = Apple::II::LanguageCardSwitches; - const LanguageCardSwitches &language_card_switches() const { - return language_card_; - } + using LanguageCardSwitches = Apple::II::LanguageCardSwitches; + const LanguageCardSwitches &language_card_switches() const { + return language_card_; + } - // MARK: - Accessors for reading and writing RAM. + // MARK: - Accessors for reading and writing RAM. - struct Region { - uint8_t *write = nullptr; - const uint8_t *read = nullptr; - uint8_t flags = 0; + struct Region { + uint8_t *write = nullptr; + const uint8_t *read = nullptr; + uint8_t flags = 0; - enum Flag: uint8_t { - Is1Mhz = 1 << 0, // Both reads and writes should be synchronised with the 1Mhz clock. - IsIO = 1 << 1, // Indicates that this region should be checked for soft switches, registers, etc. - }; + enum Flag: uint8_t { + Is1Mhz = 1 << 0, // Both reads and writes should be synchronised with the 1Mhz clock. + IsIO = 1 << 1, // Indicates that this region should be checked for soft switches, registers, etc. }; + }; - const Region ®ion(uint32_t address) const { return regions_[region_map_[address >> 8]]; } - uint8_t read(const Region ®ion, uint32_t address) const { - return region.read ? region.read[address] : 0xff; + const Region ®ion(uint32_t address) const { return regions_[region_map_[address >> 8]]; } + uint8_t read(const Region ®ion, uint32_t address) const { + return region.read ? region.read[address] : 0xff; + } + + bool is_shadowed(const Region ®ion, uint32_t address) const { + // ROM is never shadowed. + if(!region.write) { + return false; } - bool is_shadowed(const Region ®ion, uint32_t address) const { - // ROM is never shadowed. - if(!region.write) { - return false; - } - - const auto physical = physical_address(region, address); - assert(physical <= 0xff'ffff); - return shadow_pages_[(physical >> 10) & 127] && shadow_banks_[physical >> 17]; - } - void write(const Region ®ion, uint32_t address, uint8_t value) { - if(!region.write) { - return; - } - - // Write once. - region.write[address] = value; - - // Write again, either to the same place (if unshadowed) or to the shadow destination. - static constexpr std::size_t shadow_mask[2] = {0xff'ffff, 0x01'ffff}; - const bool shadowed = is_shadowed(region, address); - shadow_base_[shadowed][physical_address(region, address) & shadow_mask[shadowed]] = value; + const auto physical = physical_address(region, address); + assert(physical <= 0xff'ffff); + return shadow_pages_[(physical >> 10) & 127] && shadow_banks_[physical >> 17]; + } + void write(const Region ®ion, uint32_t address, uint8_t value) { + if(!region.write) { + return; } - // The objective is to support shadowing: - // 1. without storing a whole extra pointer, and such that the shadowing flags - // are orthogonal to the current auxiliary memory settings; - // 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and - // 3. to do so without introducing too much in the way of branching. - // - // Hence the implemented solution: if shadowing is enabled then use the distance from the start of - // physical RAM modulo 128k indexed into the bank $e0/$e1 RAM. - // - // With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch - // even on that. + // Write once. + region.write[address] = value; - private: - AuxiliaryMemorySwitches auxiliary_switches_; - LanguageCardSwitches language_card_; - friend AuxiliaryMemorySwitches; - friend LanguageCardSwitches; + // Write again, either to the same place (if unshadowed) or to the shadow destination. + static constexpr std::size_t shadow_mask[2] = {0xff'ffff, 0x01'ffff}; + const bool shadowed = is_shadowed(region, address); + shadow_base_[shadowed][physical_address(region, address) & shadow_mask[shadowed]] = value; + } - uint8_t shadow_register_ = 0x00; - uint8_t speed_register_ = 0x00; + // The objective is to support shadowing: + // 1. without storing a whole extra pointer, and such that the shadowing flags + // are orthogonal to the current auxiliary memory settings; + // 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and + // 3. to do so without introducing too much in the way of branching. + // + // Hence the implemented solution: if shadowing is enabled then use the distance from the start of + // physical RAM modulo 128k indexed into the bank $e0/$e1 RAM. + // + // With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch + // even on that. - // MARK: - Banking. +private: + AuxiliaryMemorySwitches auxiliary_switches_; + LanguageCardSwitches language_card_; + friend AuxiliaryMemorySwitches; + friend LanguageCardSwitches; - void assert_is_region(uint8_t start, uint8_t end); - template void set_paging(); + uint8_t shadow_register_ = 0x00; + uint8_t speed_register_ = 0x00; - uint8_t *ram_base_ = nullptr; + // MARK: - Banking. - // Memory layout here is done via double indirection; the main loop should: - // (i) use the top two bytes of the address to get an index from region_map; and - // (ii) use that to index the memory_regions table. - // - // Pointers are eight bytes at the time of writing, so the extra level of indirection - // reduces what would otherwise be a 1.25mb table down to not a great deal more than 64kb. - std::array region_map_{}; - std::array regions_; // An assert above ensures that this is large enough; there's no - // doctrinal reason for it to be whatever size it is now, just - // adjust as required. + void assert_is_region(uint8_t start, uint8_t end); + template void set_paging(); - std::size_t physical_address(const Region ®ion, uint32_t address) const { - return std::size_t(®ion.write[address] - ram_base_); - } + uint8_t *ram_base_ = nullptr; - // MARK: - Shadowing + // Memory layout here is done via double indirection; the main loop should: + // (i) use the top two bytes of the address to get an index from region_map; and + // (ii) use that to index the memory_regions table. + // + // Pointers are eight bytes at the time of writing, so the extra level of indirection + // reduces what would otherwise be a 1.25mb table down to not a great deal more than 64kb. + std::array region_map_{}; + std::array regions_; // An assert above ensures that this is large enough; there's no + // doctrinal reason for it to be whatever size it is now, just + // adjust as required. - // Various precomputed bitsets describing key regions; std::bitset doesn't support constexpr instantiation - // beyond the first 64 bits at the time of writing, alas, so these are generated at runtime. - std::bitset<128> shadow_text1_; - std::bitset<128> shadow_text2_; - std::bitset<128> shadow_highres1_, shadow_highres1_aux_; - std::bitset<128> shadow_highres2_, shadow_highres2_aux_; - std::bitset<128> shadow_superhighres_; - void setup_shadow_maps(bool is_rom03); - void set_shadowing(); + std::size_t physical_address(const Region ®ion, uint32_t address) const { + return std::size_t(®ion.write[address] - ram_base_); + } - uint8_t *shadow_base_[2] = {nullptr, nullptr}; + // MARK: - Shadowing - // Divide the final 128kb of memory into 1kb chunks and flag to indicate whether - // each is a potential destination for shadowing. - std::bitset<128> shadow_pages_{}; + // Various precomputed bitsets describing key regions; std::bitset doesn't support constexpr instantiation + // beyond the first 64 bits at the time of writing, alas, so these are generated at runtime. + std::bitset<128> shadow_text1_; + std::bitset<128> shadow_text2_; + std::bitset<128> shadow_highres1_, shadow_highres1_aux_; + std::bitset<128> shadow_highres2_, shadow_highres2_aux_; + std::bitset<128> shadow_superhighres_; + void setup_shadow_maps(bool is_rom03); + void set_shadowing(); - // Divide the whole 16mb of memory into 128kb chunks and flag to indicate whether - // each is a potential source of shadowing. - std::bitset<128> shadow_banks_{}; + uint8_t *shadow_base_[2] = {nullptr, nullptr}; + + // Divide the final 128kb of memory into 1kb chunks and flag to indicate whether + // each is a potential destination for shadowing. + std::bitset<128> shadow_pages_{}; + + // Divide the whole 16mb of memory into 128kb chunks and flag to indicate whether + // each is a potential source of shadowing. + std::bitset<128> shadow_banks_{}; }; } diff --git a/Machines/Apple/AppleIIgs/Sound.hpp b/Machines/Apple/AppleIIgs/Sound.hpp index 711873cbd..b217a59dc 100644 --- a/Machines/Apple/AppleIIgs/Sound.hpp +++ b/Machines/Apple/AppleIIgs/Sound.hpp @@ -17,90 +17,90 @@ namespace Apple::IIgs::Sound { class GLU: public Outputs::Speaker::BufferSource { // TODO: isn't this stereo? - public: - GLU(Concurrency::AsyncTaskQueue &audio_queue); +public: + GLU(Concurrency::AsyncTaskQueue &audio_queue); - void set_control(uint8_t); - uint8_t get_control(); - void set_data(uint8_t); - uint8_t get_data(); - void set_address_low(uint8_t); - uint8_t get_address_low(); - void set_address_high(uint8_t); - uint8_t get_address_high(); + void set_control(uint8_t); + uint8_t get_control(); + void set_data(uint8_t); + uint8_t get_data(); + void set_address_low(uint8_t); + uint8_t get_address_low(); + void set_address_high(uint8_t); + uint8_t get_address_high(); - void run_for(Cycles); - Cycles next_sequence_point() const; - bool get_interrupt_line(); + void run_for(Cycles); + Cycles next_sequence_point() const; + bool get_interrupt_line(); - // SampleSource. - template - void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); - void set_sample_volume_range(std::int16_t range); - bool is_zero_level() const { return false; } // TODO. + // SampleSource. + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); + void set_sample_volume_range(std::int16_t range); + bool is_zero_level() const { return false; } // TODO. - private: - Concurrency::AsyncTaskQueue &audio_queue_; +private: + Concurrency::AsyncTaskQueue &audio_queue_; - uint16_t address_ = 0; + uint16_t address_ = 0; - // Use a circular buffer for piping memory alterations onto the audio - // thread; it would be prohibitive to defer every write individually. - // - // Assumed: on most modern architectures, an atomic 64-bit read or - // write can be achieved locklessly. - struct MemoryWrite { - uint32_t time; - uint16_t address; - uint8_t value; - bool enabled; - }; - static_assert(sizeof(MemoryWrite) == 8); - constexpr static int StoreBufferSize = 16384; + // Use a circular buffer for piping memory alterations onto the audio + // thread; it would be prohibitive to defer every write individually. + // + // Assumed: on most modern architectures, an atomic 64-bit read or + // write can be achieved locklessly. + struct MemoryWrite { + uint32_t time; + uint16_t address; + uint8_t value; + bool enabled; + }; + static_assert(sizeof(MemoryWrite) == 8); + constexpr static int StoreBufferSize = 16384; - std::atomic pending_stores_[StoreBufferSize]; - uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0; - uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0; + std::atomic pending_stores_[StoreBufferSize]; + uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0; + uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0; - // Maintain state both 'locally' (i.e. on the emulation thread) and - // 'remotely' (i.e. on the audio thread). - struct EnsoniqState { - uint8_t ram_[65536]; - struct Oscillator { - uint32_t position; + // Maintain state both 'locally' (i.e. on the emulation thread) and + // 'remotely' (i.e. on the audio thread). + struct EnsoniqState { + uint8_t ram_[65536]; + struct Oscillator { + uint32_t position; - // Programmer-set values. - uint16_t velocity; - uint8_t volume; - uint8_t address; - uint8_t control; - uint8_t table_size; + // Programmer-set values. + uint16_t velocity; + uint8_t volume; + uint8_t address; + uint8_t control; + uint8_t table_size; - // Derived state. - uint32_t overflow_mask; // If a non-zero bit gets anywhere into the overflow mask, this channel - // has wrapped around. It's a function of table_size. - bool interrupt_request = false; // Will be non-zero if this channel would request an interrupt, were - // it currently enabled to do so. + // Derived state. + uint32_t overflow_mask; // If a non-zero bit gets anywhere into the overflow mask, this channel + // has wrapped around. It's a function of table_size. + bool interrupt_request = false; // Will be non-zero if this channel would request an interrupt, were + // it currently enabled to do so. - uint8_t sample(uint8_t *ram); - int16_t output(uint8_t *ram); - } oscillators[32]; + uint8_t sample(uint8_t *ram); + int16_t output(uint8_t *ram); + } oscillators[32]; - // Some of these aren't actually needed on both threads. - uint8_t control = 0; - int oscillator_count = 1; + // Some of these aren't actually needed on both threads. + uint8_t control = 0; + int oscillator_count = 1; - void set_register(uint16_t address, uint8_t value); - } local_, remote_; + void set_register(uint16_t address, uint8_t value); + } local_, remote_; - // Functions to update an EnsoniqState; these don't belong to the state itself - // because they also access the pending stores (inter alia). - template - void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target); - void skip_audio(EnsoniqState &state, size_t number_of_samples); + // Functions to update an EnsoniqState; these don't belong to the state itself + // because they also access the pending stores (inter alia). + template + void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target); + void skip_audio(EnsoniqState &state, size_t number_of_samples); - // Audio-thread state. - int16_t output_range_ = 0; + // Audio-thread state. + int16_t output_range_ = 0; }; } diff --git a/Machines/Apple/AppleIIgs/Video.hpp b/Machines/Apple/AppleIIgs/Video.hpp index c7d17e9bd..8845cd20c 100644 --- a/Machines/Apple/AppleIIgs/Video.hpp +++ b/Machines/Apple/AppleIIgs/Video.hpp @@ -20,236 +20,236 @@ namespace Apple::IIgs::Video { stretched cycle. */ class Video: public Apple::II::VideoSwitches { +public: + Video(); + void set_internal_ram(const uint8_t *); + + bool get_is_vertical_blank(Cycles offset); + uint8_t get_horizontal_counter(Cycles offset); + uint8_t get_vertical_counter(Cycles offset); + + void set_new_video(uint8_t); + uint8_t get_new_video(); + + void clear_interrupts(uint8_t); + uint8_t get_interrupt_register(); + void set_interrupt_register(uint8_t); + bool get_interrupt_line(); + + void notify_clock_tick(); + + void set_border_colour(uint8_t); + void set_text_colour(uint8_t); + uint8_t get_text_colour(); + uint8_t get_border_colour(); + + void set_composite_is_colour(bool); + bool get_composite_is_colour(); + + /// Sets the scan target. + void set_scan_target(Outputs::Display::ScanTarget *scan_target); + + /// Gets the current scan status. + Outputs::Display::ScanStatus get_scaled_scan_status() const; + + /// Sets the type of output. + void set_display_type(Outputs::Display::DisplayType); + + /// Gets the type of output. + Outputs::Display::DisplayType get_display_type() const; + + /// Determines the period until video might autonomously update its interrupt lines. + Cycles next_sequence_point() const; + + /// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are + /// generated here. + void set_megaii_interrupts_enabled(uint8_t); + + uint8_t get_megaii_interrupt_status(); + + void clear_megaii_interrupts(); + +private: + Outputs::CRT::CRT crt_; + + // This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs. + enum class GraphicsMode { + Text = 0, + DoubleText, + HighRes, + DoubleHighRes, + LowRes, + DoubleLowRes, + FatLowRes, + + // Additions: + DoubleHighResMono, + SuperHighRes + }; + constexpr bool is_colour_ntsc(const GraphicsMode m) { + return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes; + } + + GraphicsMode graphics_mode(int row) const { + if(new_video_ & 0x80) { + return GraphicsMode::SuperHighRes; + } + + const auto ii_mode = Apple::II::VideoSwitches::graphics_mode(row); + switch(ii_mode) { + // Coupling very much assumed here. + case Apple::II::GraphicsMode::DoubleHighRes: + if(new_video_ & 0x20) { + return GraphicsMode::DoubleHighResMono; + } + [[fallthrough]]; + + default: return GraphicsMode(int(ii_mode)); break; + } + } + + enum class PixelBufferFormat { + Text, DoubleText, NTSC, NTSCMono, SuperHighRes + }; + constexpr PixelBufferFormat format_for_mode(GraphicsMode m) { + switch(m) { + case GraphicsMode::Text: return PixelBufferFormat::Text; + case GraphicsMode::DoubleText: return PixelBufferFormat::DoubleText; + default: return PixelBufferFormat::NTSC; + case GraphicsMode::DoubleHighResMono: return PixelBufferFormat::NTSCMono; + case GraphicsMode::SuperHighRes: return PixelBufferFormat::SuperHighRes; + } + } + + void advance(Cycles); + + uint8_t new_video_ = 0x01; + + class Interrupts { public: - Video(); - void set_internal_ram(const uint8_t *); + void add(uint8_t value) { + // Taken literally, status accumulates regardless of being enabled, + // potentially to be polled, it simply doesn't trigger an interrupt. + value_ |= value; + test(); + } - bool get_is_vertical_blank(Cycles offset); - uint8_t get_horizontal_counter(Cycles offset); - uint8_t get_vertical_counter(Cycles offset); + void clear(uint8_t value) { + // Zeroes in bits 5 or 6 clear the respective interrupts. + value_ &= value | ~0x60; + test(); + } - void set_new_video(uint8_t); - uint8_t get_new_video(); + void set_control(uint8_t value) { + // Ones in bits 1 or 2 enable the respective interrupts. + value_ = (value_ & ~0x6) | (value & 0x6); + test(); + } - void clear_interrupts(uint8_t); - uint8_t get_interrupt_register(); - void set_interrupt_register(uint8_t); - bool get_interrupt_line(); + uint8_t status() const { + return value_; + } - void notify_clock_tick(); - - void set_border_colour(uint8_t); - void set_text_colour(uint8_t); - uint8_t get_text_colour(); - uint8_t get_border_colour(); - - void set_composite_is_colour(bool); - bool get_composite_is_colour(); - - /// Sets the scan target. - void set_scan_target(Outputs::Display::ScanTarget *scan_target); - - /// Gets the current scan status. - Outputs::Display::ScanStatus get_scaled_scan_status() const; - - /// Sets the type of output. - void set_display_type(Outputs::Display::DisplayType); - - /// Gets the type of output. - Outputs::Display::DisplayType get_display_type() const; - - /// Determines the period until video might autonomously update its interrupt lines. - Cycles next_sequence_point() const; - - /// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are - /// generated here. - void set_megaii_interrupts_enabled(uint8_t); - - uint8_t get_megaii_interrupt_status(); - - void clear_megaii_interrupts(); + bool active() const { + return value_ & 0x80; + } private: - Outputs::CRT::CRT crt_; - - // This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs. - enum class GraphicsMode { - Text = 0, - DoubleText, - HighRes, - DoubleHighRes, - LowRes, - DoubleLowRes, - FatLowRes, - - // Additions: - DoubleHighResMono, - SuperHighRes - }; - constexpr bool is_colour_ntsc(const GraphicsMode m) { - return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes; - } - - GraphicsMode graphics_mode(int row) const { - if(new_video_ & 0x80) { - return GraphicsMode::SuperHighRes; - } - - const auto ii_mode = Apple::II::VideoSwitches::graphics_mode(row); - switch(ii_mode) { - // Coupling very much assumed here. - case Apple::II::GraphicsMode::DoubleHighRes: - if(new_video_ & 0x20) { - return GraphicsMode::DoubleHighResMono; - } - [[fallthrough]]; - - default: return GraphicsMode(int(ii_mode)); break; + void test() { + value_ &= 0x7f; + if((value_ >> 4) & value_ & 0x6) { + value_ |= 0x80; } } - enum class PixelBufferFormat { - Text, DoubleText, NTSC, NTSCMono, SuperHighRes - }; - constexpr PixelBufferFormat format_for_mode(GraphicsMode m) { - switch(m) { - case GraphicsMode::Text: return PixelBufferFormat::Text; - case GraphicsMode::DoubleText: return PixelBufferFormat::DoubleText; - default: return PixelBufferFormat::NTSC; - case GraphicsMode::DoubleHighResMono: return PixelBufferFormat::NTSCMono; - case GraphicsMode::SuperHighRes: return PixelBufferFormat::SuperHighRes; - } - } - - void advance(Cycles); - - uint8_t new_video_ = 0x01; - - class Interrupts { - public: - void add(uint8_t value) { - // Taken literally, status accumulates regardless of being enabled, - // potentially to be polled, it simply doesn't trigger an interrupt. - value_ |= value; - test(); - } - - void clear(uint8_t value) { - // Zeroes in bits 5 or 6 clear the respective interrupts. - value_ &= value | ~0x60; - test(); - } - - void set_control(uint8_t value) { - // Ones in bits 1 or 2 enable the respective interrupts. - value_ = (value_ & ~0x6) | (value & 0x6); - test(); - } - - uint8_t status() const { - return value_; - } - - bool active() const { - return value_ & 0x80; - } - - private: - void test() { - value_ &= 0x7f; - if((value_ >> 4) & value_ & 0x6) { - value_ |= 0x80; - } - } - - // Overall meaning of value is as per the VGC interrupt register, i.e. - // - // b7: interrupt status; - // b6: 1-second interrupt status; - // b5: scan-line interrupt status; - // b4: reserved; - // b3: reserved; - // b2: 1-second interrupt enable; - // b1: scan-line interrupt enable; - // b0: reserved. - uint8_t value_ = 0x00; - } interrupts_; - - int cycles_into_frame_ = 0; - const uint8_t *ram_ = nullptr; - - // The modal colours. - uint16_t border_colour_ = 0; - uint8_t border_colour_entry_ = 0; - uint8_t text_colour_entry_ = 0xf0; - uint16_t text_colour_ = 0xffff; - uint16_t background_colour_ = 0; - - // Current pixel output buffer and conceptual format. - PixelBufferFormat pixels_format_; - uint16_t *pixels_ = nullptr, *next_pixel_ = nullptr; - int pixels_start_column_; - - void output_row(int row, int start, int end); - - uint16_t *output_super_high_res(uint16_t *target, int start, int end, int row) const; - - uint16_t *output_text(uint16_t *target, int start, int end, int row) const; - uint16_t *output_double_text(uint16_t *target, int start, int end, int row) const; - uint16_t *output_char(uint16_t *target, uint8_t source, int row) const; - - uint16_t *output_low_resolution(uint16_t *target, int start, int end, int row); - uint16_t *output_fat_low_resolution(uint16_t *target, int start, int end, int row); - uint16_t *output_double_low_resolution(uint16_t *target, int start, int end, int row); - - uint16_t *output_high_resolution(uint16_t *target, int start, int end, int row); - uint16_t *output_double_high_resolution(uint16_t *target, int start, int end, int row); - uint16_t *output_double_high_resolution_mono(uint16_t *target, int start, int end, int row); - - // Super high-res per-line state. - uint8_t line_control_; - uint16_t palette_[16]; - - // Storage used for fill mode. - uint16_t *palette_zero_[4] = {nullptr, nullptr, nullptr, nullptr}, palette_throwaway_; - - // Lookup tables and state to assist in the IIgs' mapping from NTSC to RGB. + // Overall meaning of value is as per the VGC interrupt register, i.e. // - // My understanding of the real-life algorithm is: maintain a four-bit buffer. - // Fill it in a circular fashion. Ordinarily, output the result of looking - // up the RGB mapping of those four bits of Apple II output (which outputs four - // bits per NTSC colour cycle), commuted as per current phase. But if the bit - // being inserted differs from that currently in its position in the shift - // register, hold the existing output for three shifts. - // - // From there I am using the following: + // b7: interrupt status; + // b6: 1-second interrupt status; + // b5: scan-line interrupt status; + // b4: reserved; + // b3: reserved; + // b2: 1-second interrupt enable; + // b1: scan-line interrupt enable; + // b0: reserved. + uint8_t value_ = 0x00; + } interrupts_; - // Maps from: - // - // b0 = b0 of the shift register - // b1 = b4 of the shift register - // b2– = current delay count - // - // to a new delay count. - uint8_t ntsc_delay_lookup_[20]; - uint32_t ntsc_shift_ = 0; // Assumption here: logical shifts will ensue, rather than arithmetic. - int ntsc_delay_ = 0; + int cycles_into_frame_ = 0; + const uint8_t *ram_ = nullptr; - /// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB. - /// Phase is derived from @c column. - uint16_t *output_shift(uint16_t *target, int column); + // The modal colours. + uint16_t border_colour_ = 0; + uint8_t border_colour_entry_ = 0; + uint8_t text_colour_entry_ = 0xf0; + uint16_t text_colour_ = 0xffff; + uint16_t background_colour_ = 0; - // Common getter for the two counters. - struct Counters { - Counters(int v, int h) : vertical(v), horizontal(h) {} - const int vertical, horizontal; - }; - Counters get_counters(Cycles offset); + // Current pixel output buffer and conceptual format. + PixelBufferFormat pixels_format_; + uint16_t *pixels_ = nullptr, *next_pixel_ = nullptr; + int pixels_start_column_; - // Marshalls the Mega II-style interrupt state. - uint8_t megaii_interrupt_mask_ = 0; - uint8_t megaii_interrupt_state_ = 0; - int megaii_frame_counter_ = 0; // To count up to quarter-second interrupts. + void output_row(int row, int start, int end); + + uint16_t *output_super_high_res(uint16_t *target, int start, int end, int row) const; + + uint16_t *output_text(uint16_t *target, int start, int end, int row) const; + uint16_t *output_double_text(uint16_t *target, int start, int end, int row) const; + uint16_t *output_char(uint16_t *target, uint8_t source, int row) const; + + uint16_t *output_low_resolution(uint16_t *target, int start, int end, int row); + uint16_t *output_fat_low_resolution(uint16_t *target, int start, int end, int row); + uint16_t *output_double_low_resolution(uint16_t *target, int start, int end, int row); + + uint16_t *output_high_resolution(uint16_t *target, int start, int end, int row); + uint16_t *output_double_high_resolution(uint16_t *target, int start, int end, int row); + uint16_t *output_double_high_resolution_mono(uint16_t *target, int start, int end, int row); + + // Super high-res per-line state. + uint8_t line_control_; + uint16_t palette_[16]; + + // Storage used for fill mode. + uint16_t *palette_zero_[4] = {nullptr, nullptr, nullptr, nullptr}, palette_throwaway_; + + // Lookup tables and state to assist in the IIgs' mapping from NTSC to RGB. + // + // My understanding of the real-life algorithm is: maintain a four-bit buffer. + // Fill it in a circular fashion. Ordinarily, output the result of looking + // up the RGB mapping of those four bits of Apple II output (which outputs four + // bits per NTSC colour cycle), commuted as per current phase. But if the bit + // being inserted differs from that currently in its position in the shift + // register, hold the existing output for three shifts. + // + // From there I am using the following: + + // Maps from: + // + // b0 = b0 of the shift register + // b1 = b4 of the shift register + // b2– = current delay count + // + // to a new delay count. + uint8_t ntsc_delay_lookup_[20]; + uint32_t ntsc_shift_ = 0; // Assumption here: logical shifts will ensue, rather than arithmetic. + int ntsc_delay_ = 0; + + /// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB. + /// Phase is derived from @c column. + uint16_t *output_shift(uint16_t *target, int column); + + // Common getter for the two counters. + struct Counters { + Counters(int v, int h) : vertical(v), horizontal(h) {} + const int vertical, horizontal; + }; + Counters get_counters(Cycles offset); + + // Marshalls the Mega II-style interrupt state. + uint8_t megaii_interrupt_mask_ = 0; + uint8_t megaii_interrupt_state_ = 0; + int megaii_frame_counter_ = 0; // To count up to quarter-second interrupts. }; } diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp index 1adb4b06a..762593aec 100644 --- a/Machines/Apple/Macintosh/Audio.hpp +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -24,61 +24,61 @@ namespace Apple::Macintosh { a shade less than 4Mhz. */ class Audio: public ::Outputs::Speaker::BufferSource { - public: - Audio(Concurrency::AsyncTaskQueue &task_queue); +public: + Audio(Concurrency::AsyncTaskQueue &task_queue); - /*! - Macintosh audio is (partly) sourced by the same scanning - hardware as the video; each line it collects an additional - word of memory, half of which is used for audio output. + /*! + Macintosh audio is (partly) sourced by the same scanning + hardware as the video; each line it collects an additional + word of memory, half of which is used for audio output. - Use this method to add a newly-collected sample to the queue. - */ - void post_sample(uint8_t sample); + Use this method to add a newly-collected sample to the queue. + */ + void post_sample(uint8_t sample); - /*! - Macintosh audio also separately receives an output volume - level, in the range 0 to 7. + /*! + Macintosh audio also separately receives an output volume + level, in the range 0 to 7. - Use this method to set the current output volume. - */ - void set_volume(int volume); + Use this method to set the current output volume. + */ + void set_volume(int volume); - /*! - A further factor in audio output is the on-off toggle. - */ - void set_enabled(bool on); + /*! + A further factor in audio output is the on-off toggle. + */ + void set_enabled(bool on); - // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. - template - void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); - bool is_zero_level() const; - void set_sample_volume_range(std::int16_t range); + // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target); + bool is_zero_level() const; + void set_sample_volume_range(std::int16_t range); - private: - Concurrency::AsyncTaskQueue &task_queue_; +private: + Concurrency::AsyncTaskQueue &task_queue_; - // A queue of fetched samples; read from by one thread, - // written to by another. - struct { - std::array, 740> buffer; - size_t read_pointer = 0, write_pointer = 0; - } sample_queue_; + // A queue of fetched samples; read from by one thread, + // written to by another. + struct { + std::array, 740> buffer; + size_t read_pointer = 0, write_pointer = 0; + } sample_queue_; - // Emulator-thread stateful variables, to avoid work posting - // deferral updates if possible. - int posted_volume_ = 0; - int posted_enable_mask_ = 0; + // Emulator-thread stateful variables, to avoid work posting + // deferral updates if possible. + int posted_volume_ = 0; + int posted_enable_mask_ = 0; - // Stateful variables, modified from the audio generation - // thread only. - int volume_ = 0; - int enabled_mask_ = 0; - std::int16_t output_volume_ = 0; + // Stateful variables, modified from the audio generation + // thread only. + int volume_ = 0; + int enabled_mask_ = 0; + std::int16_t output_volume_ = 0; - std::int16_t volume_multiplier_ = 0; - std::size_t subcycle_offset_ = 0; - void set_volume_multiplier(); + std::int16_t volume_multiplier_ = 0; + std::size_t subcycle_offset_ = 0; + void set_volume_multiplier(); }; } diff --git a/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp b/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp index 21a28066b..8bad1a572 100644 --- a/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp +++ b/Machines/Apple/Macintosh/DriveSpeedAccumulator.hpp @@ -15,27 +15,27 @@ namespace Apple::Macintosh { class DriveSpeedAccumulator { - public: - /*! - Accepts fetched motor control values. - */ - void post_sample(uint8_t sample); +public: + /*! + Accepts fetched motor control values. + */ + void post_sample(uint8_t sample); - struct Delegate { - virtual void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) = 0; - }; - /*! - Sets the delegate to receive drive speed changes. - */ - void set_delegate(Delegate *delegate) { - delegate_ = delegate; - } + struct Delegate { + virtual void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) = 0; + }; + /*! + Sets the delegate to receive drive speed changes. + */ + void set_delegate(Delegate *delegate) { + delegate_ = delegate; + } - private: - static constexpr int samples_per_bucket = 20; - int sample_count_ = 0; - int sample_total_ = 0; - Delegate *delegate_ = nullptr; +private: + static constexpr int samples_per_bucket = 20; + int sample_count_ = 0; + int sample_total_ = 0; + Delegate *delegate_ = nullptr; }; } diff --git a/Machines/Apple/Macintosh/Keyboard.hpp b/Machines/Apple/Macintosh/Keyboard.hpp index 2efd603c4..a5a74ea10 100644 --- a/Machines/Apple/Macintosh/Keyboard.hpp +++ b/Machines/Apple/Macintosh/Keyboard.hpp @@ -83,206 +83,206 @@ enum class Key: uint16_t { }; class Keyboard { - public: - void set_input(bool data) { - switch(mode_) { - case Mode::Waiting: - /* - "Only the computer can initiate communication over the keyboard lines. When the computer and keyboard - are turned on, the computer is in charge of the keyboard interface and the keyboard is passive. The - computer signals that it is ready to begin communication by pulling the Keyboard Data line low." - */ - if(!data) { - mode_ = Mode::AcceptingCommand; - phase_ = 0; - command_ = 0; - } - break; +public: + void set_input(bool data) { + switch(mode_) { + case Mode::Waiting: + /* + "Only the computer can initiate communication over the keyboard lines. When the computer and keyboard + are turned on, the computer is in charge of the keyboard interface and the keyboard is passive. The + computer signals that it is ready to begin communication by pulling the Keyboard Data line low." + */ + if(!data) { + mode_ = Mode::AcceptingCommand; + phase_ = 0; + command_ = 0; + } + break; - case Mode::AcceptingCommand: - /* Note value, so that it can be latched upon a clock transition. */ - data_input_ = data; - break; + case Mode::AcceptingCommand: + /* Note value, so that it can be latched upon a clock transition. */ + data_input_ = data; + break; - case Mode::AwaitingEndOfCommand: - /* - The last bit of the command leaves the Keyboard Data line low; the computer then indicates that it is ready - to receive the keyboard's response by setting the Keyboard Data line high. - */ - if(data) { - mode_ = Mode::PerformingCommand; - phase_ = 0; - } - break; + case Mode::AwaitingEndOfCommand: + /* + The last bit of the command leaves the Keyboard Data line low; the computer then indicates that it is ready + to receive the keyboard's response by setting the Keyboard Data line high. + */ + if(data) { + mode_ = Mode::PerformingCommand; + phase_ = 0; + } + break; - default: - case Mode::SendingResponse: - /* This line isn't currently an input; do nothing. */ - break; - } + default: + case Mode::SendingResponse: + /* This line isn't currently an input; do nothing. */ + break; } + } - bool get_clock() { - return clock_output_; + bool get_clock() { + return clock_output_; + } + + bool get_data() { + return !!(response_ & 0x80); + } + + /*! + The keyboard expects ~10 µs-frequency ticks, i.e. a clock rate of just around 100 kHz. + */ + void run_for(HalfCycles) { // TODO: honour the HalfCycles argument. + switch(mode_) { + default: + case Mode::Waiting: return; + + case Mode::AcceptingCommand: { + /* + "When the computer is sending data to the keyboard, the keyboard transmits eight cycles of 400 µS each (180 µS low, + 220 µS high) on the Keyboard Clock line. On the falling edge of each keyboard clock cycle, the Macintosh Plus places + a data bit on the data line and holds it there for 400 µS. The keyboard reads the data bit 80 µS after the rising edge + of the Keyboard Clock signal." + */ + const auto offset = phase_ % 40; + clock_output_ = offset >= 18; + + if(offset == 26) { + command_ = (command_ << 1) | (data_input_ ? 1 : 0); + } + + ++phase_; + if(phase_ == 8*40) { + mode_ = Mode::AwaitingEndOfCommand; + phase_ = 0; + clock_output_ = false; + } + } break; + + case Mode::AwaitingEndOfCommand: + // Time out if the end-of-command seems not to be forthcoming. + // This is an elaboration on my part; a guess. + ++phase_; + if(phase_ == 1000) { + clock_output_ = false; + mode_ = Mode::Waiting; + phase_ = 0; + } + return; + + case Mode::PerformingCommand: { + response_ = perform_command(command_); + + // Inquiry has a 0.25-second timeout; everything else is instant. + ++phase_; + if(phase_ == 25000 || command_ != 0x10 || response_ != 0x7b) { + mode_ = Mode::SendingResponse; + phase_ = 0; + } + } break; + + case Mode::SendingResponse: { + /* + "When sending data to the computer, the keyboard transmits eight cycles of 330 µS each (160 µS low, 170 µS high) + on the normally high Keyboard Clock line. It places a data bit on the data line 40 µS before the falling edge of each + clock cycle and maintains it for 330 µS. The VIA in the computer latches the data bit into its shift register on the + rising edge of the Keyboard Clock signal." + */ + const auto offset = phase_ % 33; + clock_output_ = offset >= 16; + + if(offset == 29) { + response_ <<= 1; + } + + ++phase_; + if(phase_ == 8*33) { + clock_output_ = false; + mode_ = Mode::Waiting; + phase_ = 0; + } + } break; } + } - bool get_data() { - return !!(response_ & 0x80); + void enqueue_key_state(uint16_t key, bool is_pressed) { + // Front insert; messages will be pop_back'd. + std::lock_guard lock(key_queue_mutex_); + + // Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme + // they are indicated by having bit 8 set. So add the $79 prefix if required. + if(key & KeypadMask) { + key_queue_.insert(key_queue_.begin(), 0x79); } + key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key)); + } - /*! - The keyboard expects ~10 µs-frequency ticks, i.e. a clock rate of just around 100 kHz. - */ - void run_for(HalfCycles) { // TODO: honour the HalfCycles argument. - switch(mode_) { - default: - case Mode::Waiting: return; +private: + /// Performs the pre-ADB Apple keyboard protocol command @c command, returning + /// the proper result if the command were to terminate now. So, it treats inquiry + /// and instant as the same command. + int perform_command(int command) { + switch(command) { + case 0x10: // Inquiry. + case 0x14: { // Instant. + std::lock_guard lock(key_queue_mutex_); + if(!key_queue_.empty()) { + const auto new_message = key_queue_.back(); + key_queue_.pop_back(); + return new_message; + } + } break; - case Mode::AcceptingCommand: { - /* - "When the computer is sending data to the keyboard, the keyboard transmits eight cycles of 400 µS each (180 µS low, - 220 µS high) on the Keyboard Clock line. On the falling edge of each keyboard clock cycle, the Macintosh Plus places - a data bit on the data line and holds it there for 400 µS. The keyboard reads the data bit 80 µS after the rising edge - of the Keyboard Clock signal." - */ - const auto offset = phase_ % 40; - clock_output_ = offset >= 18; + case 0x16: // Model number. + return + 0x01 | // b0: always 1 + (1 << 1) | // keyboard model number + (1 << 4); // next device number + // (b7 not set => no next device) - if(offset == 26) { - command_ = (command_ << 1) | (data_input_ ? 1 : 0); - } - - ++phase_; - if(phase_ == 8*40) { - mode_ = Mode::AwaitingEndOfCommand; - phase_ = 0; - clock_output_ = false; - } - } break; - - case Mode::AwaitingEndOfCommand: - // Time out if the end-of-command seems not to be forthcoming. - // This is an elaboration on my part; a guess. - ++phase_; - if(phase_ == 1000) { - clock_output_ = false; - mode_ = Mode::Waiting; - phase_ = 0; - } - return; - - case Mode::PerformingCommand: { - response_ = perform_command(command_); - - // Inquiry has a 0.25-second timeout; everything else is instant. - ++phase_; - if(phase_ == 25000 || command_ != 0x10 || response_ != 0x7b) { - mode_ = Mode::SendingResponse; - phase_ = 0; - } - } break; - - case Mode::SendingResponse: { - /* - "When sending data to the computer, the keyboard transmits eight cycles of 330 µS each (160 µS low, 170 µS high) - on the normally high Keyboard Clock line. It places a data bit on the data line 40 µS before the falling edge of each - clock cycle and maintains it for 330 µS. The VIA in the computer latches the data bit into its shift register on the - rising edge of the Keyboard Clock signal." - */ - const auto offset = phase_ % 33; - clock_output_ = offset >= 16; - - if(offset == 29) { - response_ <<= 1; - } - - ++phase_; - if(phase_ == 8*33) { - clock_output_ = false; - mode_ = Mode::Waiting; - phase_ = 0; - } - } break; - } + case 0x36: // Test + return 0x7d; // 0x7d = ACK, 0x77 = not ACK. } + return 0x7b; // No key transition. + } - void enqueue_key_state(uint16_t key, bool is_pressed) { - // Front insert; messages will be pop_back'd. - std::lock_guard lock(key_queue_mutex_); + /// Maintains the current operating mode — a record of what the + /// keyboard is doing now. + enum class Mode { + /// The keyboard is waiting to begin a transaction. + Waiting, + /// The keyboard is currently clocking in a new command. + AcceptingCommand, + /// The keyboard is waiting for the computer to indicate that it is ready for a response. + AwaitingEndOfCommand, + /// The keyboard is in the process of performing the command it most-recently received. + /// If the command was an 'inquiry', this state may persist for a non-neglibible period of time. + PerformingCommand, + /// The keyboard is currently shifting a response back to the computer. + SendingResponse, + } mode_ = Mode::Waiting; - // Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme - // they are indicated by having bit 8 set. So add the $79 prefix if required. - if(key & KeypadMask) { - key_queue_.insert(key_queue_.begin(), 0x79); - } - key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key)); - } + /// Holds a count of progress through the current @c Mode. Exact meaning depends on mode. + int phase_ = 0; + /// Holds the most-recently-received command; the command is shifted into here as it is received + /// so this may not be valid prior to Mode::PerformingCommand. + int command_ = 0; + /// Populated during PerformingCommand as the response to the most-recently-received command, this + /// is then shifted out to the host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse, + /// but not afterwards. + int response_ = 0; - private: - /// Performs the pre-ADB Apple keyboard protocol command @c command, returning - /// the proper result if the command were to terminate now. So, it treats inquiry - /// and instant as the same command. - int perform_command(int command) { - switch(command) { - case 0x10: // Inquiry. - case 0x14: { // Instant. - std::lock_guard lock(key_queue_mutex_); - if(!key_queue_.empty()) { - const auto new_message = key_queue_.back(); - key_queue_.pop_back(); - return new_message; - } - } break; + /// The current state of the serial connection's data input. + bool data_input_ = false; + /// The current clock output from this keyboard. + bool clock_output_ = false; - case 0x16: // Model number. - return - 0x01 | // b0: always 1 - (1 << 1) | // keyboard model number - (1 << 4); // next device number - // (b7 not set => no next device) - - case 0x36: // Test - return 0x7d; // 0x7d = ACK, 0x77 = not ACK. - } - return 0x7b; // No key transition. - } - - /// Maintains the current operating mode — a record of what the - /// keyboard is doing now. - enum class Mode { - /// The keyboard is waiting to begin a transaction. - Waiting, - /// The keyboard is currently clocking in a new command. - AcceptingCommand, - /// The keyboard is waiting for the computer to indicate that it is ready for a response. - AwaitingEndOfCommand, - /// The keyboard is in the process of performing the command it most-recently received. - /// If the command was an 'inquiry', this state may persist for a non-neglibible period of time. - PerformingCommand, - /// The keyboard is currently shifting a response back to the computer. - SendingResponse, - } mode_ = Mode::Waiting; - - /// Holds a count of progress through the current @c Mode. Exact meaning depends on mode. - int phase_ = 0; - /// Holds the most-recently-received command; the command is shifted into here as it is received - /// so this may not be valid prior to Mode::PerformingCommand. - int command_ = 0; - /// Populated during PerformingCommand as the response to the most-recently-received command, this - /// is then shifted out to the host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse, - /// but not afterwards. - int response_ = 0; - - /// The current state of the serial connection's data input. - bool data_input_ = false; - /// The current clock output from this keyboard. - bool clock_output_ = false; - - /// Guards multithread access to key_queue_. - std::mutex key_queue_mutex_; - /// A FIFO queue for key events, in the form they'd be communicated to the Macintosh, - /// with the newest events towards the front. - std::vector key_queue_; + /// Guards multithread access to key_queue_. + std::mutex key_queue_mutex_; + /// A FIFO queue for key events, in the form they'd be communicated to the Macintosh, + /// with the newest events towards the front. + std::vector key_queue_; }; /*! diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index e7c271408..a33dffed2 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -79,760 +79,760 @@ template class ConcreteMachin public Configurable::Device, public DriveSpeedAccumulator::Delegate, public ClockingHint::Observer { - public: - using Target = Analyser::Static::Macintosh::Target; +public: + using Target = Analyser::Static::Macintosh::Target; - ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - MachineTypes::MappedKeyboardMachine({ - Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift, - Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption, - Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta, - }), - mc68000_(*this), - iwm_(CLOCK_RATE), - video_(audio_, drive_speed_accumulator_), - via_port_handler_(*this, clock_, keyboard_, audio_, iwm_, mouse_), - via_(via_port_handler_), - scsi_bus_(CLOCK_RATE * 2), - scsi_(scsi_bus_, CLOCK_RATE * 2), - hard_drive_(scsi_bus_, 6 /* SCSI ID */), - drives_{ - {CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}, - {CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke} - }, - mouse_(1) { + ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + MachineTypes::MappedKeyboardMachine({ + Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift, + Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption, + Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta, + }), + mc68000_(*this), + iwm_(CLOCK_RATE), + video_(audio_, drive_speed_accumulator_), + via_port_handler_(*this, clock_, keyboard_, audio_, iwm_, mouse_), + via_(via_port_handler_), + scsi_bus_(CLOCK_RATE * 2), + scsi_(scsi_bus_, CLOCK_RATE * 2), + hard_drive_(scsi_bus_, 6 /* SCSI ID */), + drives_{ + {CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}, + {CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke} + }, + mouse_(1) { - // Select a ROM name and determine the proper ROM and RAM sizes - // based on the machine model. - using Model = Analyser::Static::Macintosh::Target::Model; - const std::string machine_name = "Macintosh"; - uint32_t ram_size, rom_size; - ROM::Name rom_name; - switch(model) { - default: - case Model::Mac128k: - ram_size = 128*1024; - rom_size = 64*1024; - rom_name = ROM::Name::Macintosh128k; - break; - case Model::Mac512k: - ram_size = 512*1024; - rom_size = 64*1024; - rom_name = ROM::Name::Macintosh512k; - break; - case Model::Mac512ke: - case Model::MacPlus: { - ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024; - rom_size = 128*1024; - rom_name = ROM::Name::MacintoshPlus; - } break; - } - ram_mask_ = ram_size - 1; - rom_mask_ = rom_size - 1; - ram_.resize(ram_size); - video_.set_ram(reinterpret_cast(ram_.data()), ram_mask_ >> 1); + // Select a ROM name and determine the proper ROM and RAM sizes + // based on the machine model. + using Model = Analyser::Static::Macintosh::Target::Model; + const std::string machine_name = "Macintosh"; + uint32_t ram_size, rom_size; + ROM::Name rom_name; + switch(model) { + default: + case Model::Mac128k: + ram_size = 128*1024; + rom_size = 64*1024; + rom_name = ROM::Name::Macintosh128k; + break; + case Model::Mac512k: + ram_size = 512*1024; + rom_size = 64*1024; + rom_name = ROM::Name::Macintosh512k; + break; + case Model::Mac512ke: + case Model::MacPlus: { + ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024; + rom_size = 128*1024; + rom_name = ROM::Name::MacintoshPlus; + } break; + } + ram_mask_ = ram_size - 1; + rom_mask_ = rom_size - 1; + ram_.resize(ram_size); + video_.set_ram(reinterpret_cast(ram_.data()), ram_mask_ >> 1); - // Grab a copy of the ROM and convert it into big-endian data. - ROM::Request request(rom_name); - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } - Memory::PackBigEndian16(roms.find(rom_name)->second, rom_); + // Grab a copy of the ROM and convert it into big-endian data. + ROM::Request request(rom_name); + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + Memory::PackBigEndian16(roms.find(rom_name)->second, rom_); - // Randomise memory contents. - Memory::Fuzz(ram_); + // Randomise memory contents. + Memory::Fuzz(ram_); - // Attach the drives to the IWM. - iwm_->set_drive(0, &drives_[0]); - iwm_->set_drive(1, &drives_[1]); + // Attach the drives to the IWM. + iwm_->set_drive(0, &drives_[0]); + iwm_->set_drive(1, &drives_[1]); - // If they are 400kb drives, also attach them to the drive-speed accumulator. - if(!drives_[0].is_800k() || !drives_[1].is_800k()) { - drive_speed_accumulator_.set_delegate(this); - } - - // Make sure interrupt changes from the SCC are observed. - scc_.set_delegate(this); - - // Also watch for changes in clocking requirement from the SCSI chip. - if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { - scsi_bus_.set_clocking_hint_observer(this); - } - - // The Mac runs at 7.8336mHz. - set_clock_rate(double(CLOCK_RATE)); - audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f); - - // Insert any supplied media. - insert_media(target.media); - - // Set the immutables of the memory map. - setup_memory_map(); + // If they are 400kb drives, also attach them to the drive-speed accumulator. + if(!drives_[0].is_800k() || !drives_[1].is_800k()) { + drive_speed_accumulator_.set_delegate(this); } - ~ConcreteMachine() { - audio_.queue.flush(); + // Make sure interrupt changes from the SCC are observed. + scc_.set_delegate(this); + + // Also watch for changes in clocking requirement from the SCSI chip. + if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { + scsi_bus_.set_clocking_hint_observer(this); } - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - video_.set_scan_target(scan_target); - } + // The Mac runs at 7.8336mHz. + set_clock_rate(double(CLOCK_RATE)); + audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f); - Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return video_.get_scaled_scan_status(); - } + // Insert any supplied media. + insert_media(target.media); - Outputs::Speaker::Speaker *get_speaker() final { - return &audio_.speaker; - } + // Set the immutables of the memory map. + setup_memory_map(); + } - void run_for(const Cycles cycles) final { - mc68000_.run_for(cycles); - } + ~ConcreteMachine() { + audio_.queue.flush(); + } - template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { - // Advance time. - advance_time(cycle.length); + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + video_.set_scan_target(scan_target); + } - // A null cycle leaves nothing else to do. - if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0); + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + return video_.get_scaled_scan_status(); + } - // Grab the address. - auto address = cycle.host_endian_byte_address(); + Outputs::Speaker::Speaker *get_speaker() final { + return &audio_.speaker; + } - // Everything above E0 0000 is signalled as being on the peripheral bus. - // - // This will also act to autovector interrupts, since an interrupt acknowledge - // cycle posts an address with all the higher-order bits set, and VPA doubles - // as the input to request an autovector. - mc68000_.set_is_peripheral_address(address >= 0xe0'0000); + void run_for(const Cycles cycles) final { + mc68000_.run_for(cycles); + } - // All code below deals only with reads and writes — cycles in which a - // data select is active. So quit now if this is not the active part of - // a read or write. - // - // The 68000 uses 6800-style autovectored interrupts, so the mere act of - // having set VPA above deals with those given that the generated address - // for interrupt acknowledge cycles always has all bits set except the - // lowest explicit address lines. - if( - !cycle.data_select_active() || - (cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) - ) return HalfCycles(0); + template HalfCycles perform_bus_operation(const Microcycle &cycle, int) { + // Advance time. + advance_time(cycle.length); - // Grab the word-precision address being accessed. - uint8_t *memory_base = nullptr; - HalfCycles delay; - switch(memory_map_[address >> 17]) { - default: assert(false); + // A null cycle leaves nothing else to do. + if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0); - case BusDevice::Unassigned: - fill_unmapped(cycle); - return delay; + // Grab the address. + auto address = cycle.host_endian_byte_address(); - case BusDevice::VIA: { - if(*cycle.address & 1) { - fill_unmapped(cycle); - } else { - const int register_address = address >> 9; + // Everything above E0 0000 is signalled as being on the peripheral bus. + // + // This will also act to autovector interrupts, since an interrupt acknowledge + // cycle posts an address with all the higher-order bits set, and VPA doubles + // as the input to request an autovector. + mc68000_.set_is_peripheral_address(address >= 0xe0'0000); - // VIA accesses are via address 0xefe1fe + register*512, - // which at word precision is 0x77f0ff + register*256. - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_high(via_.read(register_address)); - } else { - via_.write(register_address, cycle.value8_high()); - } - } - } return delay; + // All code below deals only with reads and writes — cycles in which a + // data select is active. So quit now if this is not the active part of + // a read or write. + // + // The 68000 uses 6800-style autovectored interrupts, so the mere act of + // having set VPA above deals with those given that the generated address + // for interrupt acknowledge cycles always has all bits set except the + // lowest explicit address lines. + if( + !cycle.data_select_active() || + (cycle.operation & CPU::MC68000::Operation::InterruptAcknowledge) + ) return HalfCycles(0); - case BusDevice::PhaseRead: { - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_low(phase_ & 7); - } - } return delay; - - case BusDevice::IWM: { - if(*cycle.address & 1) { - const int register_address = address >> 9; - - // The IWM; this is a purely polled device, so can be run on demand. - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_low(iwm_->read(register_address)); - } else { - iwm_->write(register_address, cycle.value8_low()); - } - } else { - fill_unmapped(cycle); - } - } return delay; - - case BusDevice::SCSI: { - const int register_address = address >> 4; - const bool dma_acknowledge = address & 0x200; - - // Even accesses = read; odd = write. - if(*cycle.address & 1) { - // Odd access => this is a write. Data will be in the upper byte. - if(cycle.operation & CPU::MC68000::Operation::Read) { - scsi_.write(register_address, 0xff, dma_acknowledge); - } else { - scsi_.write(register_address, cycle.value8_high()); - } - } else { - // Even access => this is a read. - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_high(scsi_.read(register_address, dma_acknowledge)); - } - } - } return delay; - - case BusDevice::SCCReadResetPhase: { - // Any word access here adjusts phase. - if(cycle.operation & CPU::MC68000::Operation::SelectWord) { - adjust_phase(); - } else { - // A0 = 1 => reset; A0 = 0 => read. - if(*cycle.address & 1) { - scc_.reset(); - - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value16(0xffff); - } - } else { - const auto read = scc_.read(int(address >> 1)); - if(cycle.operation & CPU::MC68000::Operation::Read) { - cycle.set_value8_high(read); - } - } - } - } return delay; - - case BusDevice::SCCWrite: { - // Any word access here adjusts phase. - if(cycle.operation & CPU::MC68000::Operation::SelectWord) { - adjust_phase(); - } else { - // This is definitely a byte access; either it's to an odd address, in which - // case it will reach the SCC, or it isn't, in which case it won't. - if(*cycle.address & 1) { - if(cycle.operation & CPU::MC68000::Operation::Read) { - scc_.write(int(address >> 1), 0xff); - cycle.value->b = 0xff; - } else { - scc_.write(int(address >> 1), cycle.value->b); - } - } else { - fill_unmapped(cycle); - } - } - } return delay; - - case BusDevice::RAM: { - // This is coupled with the Macintosh implementation of video; the magic - // constant should probably be factored into the Video class. - // It embodies knowledge of the fact that video (and audio) will always - // be fetched from the final $d900 bytes of memory. - // (And that ram_mask_ = ram size - 1). - if(address > ram_mask_ - 0xd900) - update_video(); - - memory_base = ram_.data(); - address &= ram_mask_; - - // Apply a delay due to video contention if applicable; scheme applied: - // only every other access slot is available during the period of video - // output. I believe this to be correct for the 128k, 512k and Plus. - // More research to do on other models. - if(video_is_outputting() && ram_subcycle_ < 8) { - delay = HalfCycles(8 - ram_subcycle_); - advance_time(delay); - } - } break; - - case BusDevice::ROM: { - if(!(cycle.operation & CPU::MC68000::Operation::Read)) return delay; - memory_base = rom_; - address &= rom_mask_; - } break; - } - - // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. - // Potential writes to ROM and all hardware accesses have already been weeded out. - cycle.apply(&memory_base[address]); + // Grab the word-precision address being accessed. + uint8_t *memory_base = nullptr; + HalfCycles delay; + switch(memory_map_[address >> 17]) { + default: assert(false); + case BusDevice::Unassigned: + fill_unmapped(cycle); return delay; - } - void flush_output(int) { - // Flush the video before the audio queue; in a Mac the - // video is responsible for providing part of the - // audio signal, so the two aren't as distinct as in - // most machines. - update_video(); + case BusDevice::VIA: { + if(*cycle.address & 1) { + fill_unmapped(cycle); + } else { + const int register_address = address >> 9; - // As above: flush audio after video. - via_.flush(); - audio_.queue.perform(); - - // This avoids deferring IWM costs indefinitely, until - // they become artbitrarily large. - iwm_.flush(); - } - - void set_rom_is_overlay(bool rom_is_overlay) { - ROM_is_overlay_ = rom_is_overlay; - - using Model = Analyser::Static::Macintosh::Target::Model; - switch(model) { - case Model::Mac128k: - case Model::Mac512k: - case Model::Mac512ke: - populate_memory_map(0, [rom_is_overlay] (std::function map_to) { - // Addresses up to $80 0000 aren't affected by this bit. - if(rom_is_overlay) { - // Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes. - for(int c = 0; c < 0x600000; c += 0x100000) { - map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM); - } - map_to(0x800000, BusDevice::RAM); - } else { - map_to(0x400000, BusDevice::RAM); - map_to(0x500000, BusDevice::ROM); - map_to(0x800000, BusDevice::Unassigned); - } - }); - break; - - case Model::MacPlus: - populate_memory_map(0, [rom_is_overlay] (std::function map_to) { - // Addresses up to $80 0000 aren't affected by this bit. - if(rom_is_overlay) { - for(int c = 0; c < 0x580000; c += 0x20000) { - map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); - } - map_to(0x600000, BusDevice::SCSI); - map_to(0x800000, BusDevice::RAM); - } else { - map_to(0x400000, BusDevice::RAM); - for(int c = 0x400000; c < 0x580000; c += 0x20000) { - map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); - } - map_to(0x600000, BusDevice::SCSI); - map_to(0x800000, BusDevice::Unassigned); - } - }); - break; - } - } - - bool video_is_outputting() { - return video_.is_outputting(time_since_video_update_); - } - - void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) { - update_video(); - video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer); - } - - bool insert_media(const Analyser::Static::Media &media) final { - if(media.disks.empty() && media.mass_storage_devices.empty()) - return false; - - // TODO: shouldn't allow disks to be replaced like this, as the Mac - // uses software eject. Will need to expand messaging ability of - // insert_media. - if(!media.disks.empty()) { - if(drives_[0].has_disk()) - drives_[1].set_disk(media.disks[0]); - else - drives_[0].set_disk(media.disks[0]); - } - - // TODO: allow this only at machine startup? - if(!media.mass_storage_devices.empty()) { - const auto volume = dynamic_cast(media.mass_storage_devices.front().get()); - if(volume) { - volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI); + // VIA accesses are via address 0xefe1fe + register*512, + // which at word precision is 0x77f0ff + register*256. + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_high(via_.read(register_address)); + } else { + via_.write(register_address, cycle.value8_high()); + } } - hard_drive_->set_storage(media.mass_storage_devices.front()); + } return delay; + + case BusDevice::PhaseRead: { + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_low(phase_ & 7); + } + } return delay; + + case BusDevice::IWM: { + if(*cycle.address & 1) { + const int register_address = address >> 9; + + // The IWM; this is a purely polled device, so can be run on demand. + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_low(iwm_->read(register_address)); + } else { + iwm_->write(register_address, cycle.value8_low()); + } + } else { + fill_unmapped(cycle); + } + } return delay; + + case BusDevice::SCSI: { + const int register_address = address >> 4; + const bool dma_acknowledge = address & 0x200; + + // Even accesses = read; odd = write. + if(*cycle.address & 1) { + // Odd access => this is a write. Data will be in the upper byte. + if(cycle.operation & CPU::MC68000::Operation::Read) { + scsi_.write(register_address, 0xff, dma_acknowledge); + } else { + scsi_.write(register_address, cycle.value8_high()); + } + } else { + // Even access => this is a read. + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_high(scsi_.read(register_address, dma_acknowledge)); + } + } + } return delay; + + case BusDevice::SCCReadResetPhase: { + // Any word access here adjusts phase. + if(cycle.operation & CPU::MC68000::Operation::SelectWord) { + adjust_phase(); + } else { + // A0 = 1 => reset; A0 = 0 => read. + if(*cycle.address & 1) { + scc_.reset(); + + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value16(0xffff); + } + } else { + const auto read = scc_.read(int(address >> 1)); + if(cycle.operation & CPU::MC68000::Operation::Read) { + cycle.set_value8_high(read); + } + } + } + } return delay; + + case BusDevice::SCCWrite: { + // Any word access here adjusts phase. + if(cycle.operation & CPU::MC68000::Operation::SelectWord) { + adjust_phase(); + } else { + // This is definitely a byte access; either it's to an odd address, in which + // case it will reach the SCC, or it isn't, in which case it won't. + if(*cycle.address & 1) { + if(cycle.operation & CPU::MC68000::Operation::Read) { + scc_.write(int(address >> 1), 0xff); + cycle.value->b = 0xff; + } else { + scc_.write(int(address >> 1), cycle.value->b); + } + } else { + fill_unmapped(cycle); + } + } + } return delay; + + case BusDevice::RAM: { + // This is coupled with the Macintosh implementation of video; the magic + // constant should probably be factored into the Video class. + // It embodies knowledge of the fact that video (and audio) will always + // be fetched from the final $d900 bytes of memory. + // (And that ram_mask_ = ram size - 1). + if(address > ram_mask_ - 0xd900) + update_video(); + + memory_base = ram_.data(); + address &= ram_mask_; + + // Apply a delay due to video contention if applicable; scheme applied: + // only every other access slot is available during the period of video + // output. I believe this to be correct for the 128k, 512k and Plus. + // More research to do on other models. + if(video_is_outputting() && ram_subcycle_ < 8) { + delay = HalfCycles(8 - ram_subcycle_); + advance_time(delay); + } + } break; + + case BusDevice::ROM: { + if(!(cycle.operation & CPU::MC68000::Operation::Read)) return delay; + memory_base = rom_; + address &= rom_mask_; + } break; + } + + // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. + // Potential writes to ROM and all hardware accesses have already been weeded out. + cycle.apply(&memory_base[address]); + + return delay; + } + + void flush_output(int) { + // Flush the video before the audio queue; in a Mac the + // video is responsible for providing part of the + // audio signal, so the two aren't as distinct as in + // most machines. + update_video(); + + // As above: flush audio after video. + via_.flush(); + audio_.queue.perform(); + + // This avoids deferring IWM costs indefinitely, until + // they become artbitrarily large. + iwm_.flush(); + } + + void set_rom_is_overlay(bool rom_is_overlay) { + ROM_is_overlay_ = rom_is_overlay; + + using Model = Analyser::Static::Macintosh::Target::Model; + switch(model) { + case Model::Mac128k: + case Model::Mac512k: + case Model::Mac512ke: + populate_memory_map(0, [rom_is_overlay] (std::function map_to) { + // Addresses up to $80 0000 aren't affected by this bit. + if(rom_is_overlay) { + // Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes. + for(int c = 0; c < 0x600000; c += 0x100000) { + map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM); + } + map_to(0x800000, BusDevice::RAM); + } else { + map_to(0x400000, BusDevice::RAM); + map_to(0x500000, BusDevice::ROM); + map_to(0x800000, BusDevice::Unassigned); + } + }); + break; + + case Model::MacPlus: + populate_memory_map(0, [rom_is_overlay] (std::function map_to) { + // Addresses up to $80 0000 aren't affected by this bit. + if(rom_is_overlay) { + for(int c = 0; c < 0x580000; c += 0x20000) { + map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); + } + map_to(0x600000, BusDevice::SCSI); + map_to(0x800000, BusDevice::RAM); + } else { + map_to(0x400000, BusDevice::RAM); + for(int c = 0x400000; c < 0x580000; c += 0x20000) { + map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM); + } + map_to(0x600000, BusDevice::SCSI); + map_to(0x800000, BusDevice::Unassigned); + } + }); + break; + } + } + + bool video_is_outputting() { + return video_.is_outputting(time_since_video_update_); + } + + void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) { + update_video(); + video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer); + } + + bool insert_media(const Analyser::Static::Media &media) final { + if(media.disks.empty() && media.mass_storage_devices.empty()) + return false; + + // TODO: shouldn't allow disks to be replaced like this, as the Mac + // uses software eject. Will need to expand messaging ability of + // insert_media. + if(!media.disks.empty()) { + if(drives_[0].has_disk()) + drives_[1].set_disk(media.disks[0]); + else + drives_[0].set_disk(media.disks[0]); + } + + // TODO: allow this only at machine startup? + if(!media.mass_storage_devices.empty()) { + const auto volume = dynamic_cast(media.mass_storage_devices.front().get()); + if(volume) { + volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI); + } + hard_drive_->set_storage(media.mass_storage_devices.front()); + } + + return true; + } + + // MARK: Keyboard input. + + KeyboardMapper *get_keyboard_mapper() final { + return &keyboard_mapper_; + } + + void set_key_state(uint16_t key, bool is_pressed) final { + keyboard_.enqueue_key_state(key, is_pressed); + } + + // TODO: clear all keys. + + // MARK: Interrupt updates. + + void did_change_interrupt_status(Zilog::SCC::z8530 *, bool) final { + update_interrupt_input(); + } + + void update_interrupt_input() { + // Update interrupt input. + // TODO: does this really cascade like this? + if(scc_.get_interrupt_line()) { + mc68000_.set_interrupt_level(2); + } else if(via_.get_interrupt_line()) { + mc68000_.set_interrupt_level(1); + } else { + mc68000_.set_interrupt_level(0); + } + } + + // MARK: - Activity Source + void set_activity_observer(Activity::Observer *observer) final { + iwm_->set_activity_observer(observer); + + if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { + scsi_bus_.set_activity_observer(observer); + } + } + + // MARK: - Configuration options. + std::unique_ptr get_options() const final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->quickboot = quickboot_; + return options; + } + + void set_options(const std::unique_ptr &str) final { + // TODO: should this really be a runtime option? + // It should probably be a construction option. + + const auto options = dynamic_cast(str.get()); + quickboot_ = options->quickboot; + + using Model = Analyser::Static::Macintosh::Target::Model; + const bool is_plus_rom = model == Model::Mac512ke || model == Model::MacPlus; + if(quickboot_ && is_plus_rom) { + // Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the + // test at $E00. TODO: adapt as(/if?) necessary for other Macs. + ram_[0x02ae] = 0x40; + ram_[0x02af] = 0x00; + ram_[0x02b0] = 0x00; + ram_[0x02b1] = 0x00; + } + } + +private: + bool quickboot_ = false; + + void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final { + scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None; + } + + void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) final { + iwm_.flush(); + drives_[0].set_rotation_speed(speed); + drives_[1].set_rotation_speed(speed); + } + + forceinline void adjust_phase() { + ++phase_; + } + + template + forceinline void fill_unmapped(const Microcycle &cycle) { + if(!(cycle.operation & CPU::MC68000::Operation::Read)) return; + cycle.set_value16(0xffff); + } + + /// Advances all non-CPU components by @c duration half cycles. + forceinline void advance_time(HalfCycles duration) { + time_since_video_update_ += duration; + iwm_ += duration; + ram_subcycle_ = (ram_subcycle_ + duration.as_integral()) & 15; + + // The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock. + // See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division + // may occur here in order to provide VSYNC at a proper moment. + + // Possibly route vsync. + if(time_since_video_update_ < time_until_video_event_) { + via_clock_ += duration; + via_.run_for(via_clock_.divide(HalfCycles(10))); + } else { + auto via_time_base = time_since_video_update_ - duration; + auto via_cycles_outstanding = duration; + while(time_until_video_event_ < time_since_video_update_) { + const auto via_cycles = time_until_video_event_ - via_time_base; + via_time_base = HalfCycles(0); + via_cycles_outstanding -= via_cycles; + + via_clock_ += via_cycles; + via_.run_for(via_clock_.divide(HalfCycles(10))); + + video_.run_for(time_until_video_event_); + time_since_video_update_ -= time_until_video_event_; + time_until_video_event_ = video_.next_sequence_point(); + + via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync()); } - return true; + via_clock_ += via_cycles_outstanding; + via_.run_for(via_clock_.divide(HalfCycles(10))); } - // MARK: Keyboard input. - - KeyboardMapper *get_keyboard_mapper() final { - return &keyboard_mapper_; + // The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second. + // Its clock and data lines are connected to the VIA. + keyboard_clock_ += duration; + if(keyboard_clock_ >= KEYBOARD_CLOCK_RATE) { + const auto keyboard_ticks = keyboard_clock_.divide(KEYBOARD_CLOCK_RATE); + keyboard_.run_for(keyboard_ticks); + via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data()); + via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock()); } - void set_key_state(uint16_t key, bool is_pressed) final { - keyboard_.enqueue_key_state(key, is_pressed); - } - - // TODO: clear all keys. - - // MARK: Interrupt updates. - - void did_change_interrupt_status(Zilog::SCC::z8530 *, bool) final { - update_interrupt_input(); - } - - void update_interrupt_input() { - // Update interrupt input. - // TODO: does this really cascade like this? - if(scc_.get_interrupt_line()) { - mc68000_.set_interrupt_level(2); - } else if(via_.get_interrupt_line()) { - mc68000_.set_interrupt_level(1); - } else { - mc68000_.set_interrupt_level(0); + // Feed mouse inputs within at most 1250 cycles of each other. + if(mouse_.has_steps()) { + time_since_mouse_update_ += duration; + const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500)); + if(mouse_ticks > HalfCycles(0)) { + mouse_.prepare_step(); + scc_.set_dcd(0, mouse_.get_channel(1) & 1); + scc_.set_dcd(1, mouse_.get_channel(0) & 1); } } - // MARK: - Activity Source - void set_activity_observer(Activity::Observer *observer) final { - iwm_->set_activity_observer(observer); + // TODO: SCC should be clocked at a divide-by-two, if and when it actually has + // anything connected. - if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { - scsi_bus_.set_activity_observer(observer); + // Consider updating the real-time clock. + real_time_clock_ += duration; + auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_integral(); + while(ticks--) { + clock_.update(); + // TODO: leave a delay between toggling the input rather than using this coupled hack. + via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true); + via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false); + } + + // Update the SCSI if currently active. + if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { + if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration); + } + } + + forceinline void update_video() { + video_.run_for(time_since_video_update_.flush()); + time_until_video_event_ = video_.next_sequence_point(); + } + + Inputs::Mouse &get_mouse() final { + return mouse_; + } + + using IWMActor = JustInTimeActor; + + class VIAPortHandler: public MOS::MOS6522::PortHandler { + public: + VIAPortHandler(ConcreteMachine &machine, Apple::Clock::SerialClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) : + machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {} + + using Port = MOS::MOS6522::Port; + using Line = MOS::MOS6522::Line; + + void set_port_output(Port port, uint8_t value, uint8_t) { + /* + Peripheral lines: keyboard data, interrupt configuration. + (See p176 [/215]) + */ + switch(port) { + case Port::A: + /* + Port A: + b7: [input] SCC wait/request (/W/REQA and /W/REQB wired together for a logical OR) + b6: 0 = alternate screen buffer, 1 = main screen buffer + b5: floppy disk SEL state control (upper/lower head "among other things") + b4: 1 = use ROM overlay memory map, 0 = use ordinary memory map + b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer + b2–b0: audio output volume + */ + iwm_->set_select(!!(value & 0x20)); + + machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08)); + machine_.set_rom_is_overlay(!!(value & 0x10)); + + audio_.flush(); + audio_.audio.set_volume(value & 7); + break; + + case Port::B: + /* + Port B: + b7: 0 = sound enabled, 1 = sound disabled + b6: [input] 0 = video beam in visible portion of line, 1 = outside + b5: [input] mouse y2 + b4: [input] mouse x2 + b3: [input] 0 = mouse button down, 1 = up + b2: 0 = real-time clock enabled, 1 = disabled + b1: clock's data-clock line + b0: clock's serial data line + */ + if(value & 0x4) clock_.abort(); + else clock_.set_input(!!(value & 0x2), !!(value & 0x1)); + + audio_.flush(); + audio_.audio.set_enabled(!(value & 0x80)); + break; } } - // MARK: - Configuration options. - std::unique_ptr get_options() const final { - auto options = std::make_unique(Configurable::OptionsType::UserFriendly); - options->quickboot = quickboot_; - return options; + uint8_t get_port_input(Port port) { + switch(port) { + case Port::A: +// printf("6522 r A\n"); + return 0x00; // TODO: b7 = SCC wait/request + + case Port::B: + return uint8_t( + ((mouse_.get_button_mask() & 1) ? 0x00 : 0x08) | + ((mouse_.get_channel(0) & 2) << 3) | + ((mouse_.get_channel(1) & 2) << 4) | + (clock_.get_data() ? 0x02 : 0x00) | + (machine_.video_is_outputting() ? 0x00 : 0x40) + ); + } + + // Should be unreachable. + return 0xff; } - void set_options(const std::unique_ptr &str) final { - // TODO: should this really be a runtime option? - // It should probably be a construction option. + void set_control_line_output(Port port, Line line, bool value) { + /* + Keyboard wiring (I believe): + CB2 = data (input/output) + CB1 = clock (input) - const auto options = dynamic_cast(str.get()); - quickboot_ = options->quickboot; - - using Model = Analyser::Static::Macintosh::Target::Model; - const bool is_plus_rom = model == Model::Mac512ke || model == Model::MacPlus; - if(quickboot_ && is_plus_rom) { - // Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the - // test at $E00. TODO: adapt as(/if?) necessary for other Macs. - ram_[0x02ae] = 0x40; - ram_[0x02af] = 0x00; - ram_[0x02b0] = 0x00; - ram_[0x02b1] = 0x00; + CA2 is used for receiving RTC interrupts. + CA1 is used for receiving vsync. + */ + if(port == Port::B && line == Line::Two) { + keyboard_.set_input(value); } + else logger.error().append("Unhandled 6522 control line output: %c%d", port ? 'B' : 'A', int(line)); + } + + void run_for(HalfCycles duration) { + // The 6522 enjoys a divide-by-ten, so multiply back up here to make the + // divided-by-two clock the audio works on. + audio_.time_since_update += HalfCycles(duration.as_integral() * 5); + } + + void flush() { + audio_.flush(); + } + + void set_interrupt_status(bool) { + machine_.update_interrupt_input(); } private: - bool quickboot_ = false; + ConcreteMachine &machine_; + Apple::Clock::SerialClock &clock_; + Keyboard &keyboard_; + DeferredAudio &audio_; + IWMActor &iwm_; + Inputs::QuadratureMouse &mouse_; + }; - void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final { - scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None; - } + CPU::MC68000::Processor mc68000_; - void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) final { - iwm_.flush(); - drives_[0].set_rotation_speed(speed); - drives_[1].set_rotation_speed(speed); - } + DriveSpeedAccumulator drive_speed_accumulator_; + IWMActor iwm_; - forceinline void adjust_phase() { - ++phase_; - } + DeferredAudio audio_; + Video video_; - template - forceinline void fill_unmapped(const Microcycle &cycle) { - if(!(cycle.operation & CPU::MC68000::Operation::Read)) return; - cycle.set_value16(0xffff); - } + Apple::Clock::SerialClock clock_; + Keyboard keyboard_; - /// Advances all non-CPU components by @c duration half cycles. - forceinline void advance_time(HalfCycles duration) { - time_since_video_update_ += duration; - iwm_ += duration; - ram_subcycle_ = (ram_subcycle_ + duration.as_integral()) & 15; + VIAPortHandler via_port_handler_; + MOS::MOS6522::MOS6522 via_; - // The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock. - // See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division - // may occur here in order to provide VSYNC at a proper moment. + Zilog::SCC::z8530 scc_; + SCSI::Bus scsi_bus_; + NCR::NCR5380::NCR5380 scsi_; + SCSI::Target::Target hard_drive_; + bool scsi_bus_is_clocked_ = false; - // Possibly route vsync. - if(time_since_video_update_ < time_until_video_event_) { - via_clock_ += duration; - via_.run_for(via_clock_.divide(HalfCycles(10))); - } else { - auto via_time_base = time_since_video_update_ - duration; - auto via_cycles_outstanding = duration; - while(time_until_video_event_ < time_since_video_update_) { - const auto via_cycles = time_until_video_event_ - via_time_base; - via_time_base = HalfCycles(0); - via_cycles_outstanding -= via_cycles; + HalfCycles via_clock_; + HalfCycles real_time_clock_; + HalfCycles keyboard_clock_; + HalfCycles time_since_video_update_; + HalfCycles time_until_video_event_; + HalfCycles time_since_mouse_update_; - via_clock_ += via_cycles; - via_.run_for(via_clock_.divide(HalfCycles(10))); + bool ROM_is_overlay_ = true; + int phase_ = 1; + int ram_subcycle_ = 0; - video_.run_for(time_until_video_event_); - time_since_video_update_ -= time_until_video_event_; - time_until_video_event_ = video_.next_sequence_point(); + DoubleDensityDrive drives_[2]; + Inputs::QuadratureMouse mouse_; - via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync()); - } + Apple::Macintosh::KeyboardMapper keyboard_mapper_; - via_clock_ += via_cycles_outstanding; - via_.run_for(via_clock_.divide(HalfCycles(10))); + enum class BusDevice { + RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned + }; + + /// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording + /// which device is current mapped in each area. Keeping it in a table is a bit faster + /// than the multi-level address inspection that is otherwise required, as well as + /// simplifying slightly the handling of different models. + /// + /// So: index with the top 7 bits of the 24-bit address. + BusDevice memory_map_[128]; + + void setup_memory_map() { + // Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true; + // start by calling into set_rom_is_overlay to seed everything up to $800000. + set_rom_is_overlay(true); + + populate_memory_map(0x800000, [] (std::function map_to) { + map_to(0x900000, BusDevice::Unassigned); + map_to(0xa00000, BusDevice::SCCReadResetPhase); + map_to(0xb00000, BusDevice::Unassigned); + map_to(0xc00000, BusDevice::SCCWrite); + map_to(0xd00000, BusDevice::Unassigned); + map_to(0xe00000, BusDevice::IWM); + map_to(0xe80000, BusDevice::Unassigned); + map_to(0xf00000, BusDevice::VIA); + map_to(0xf80000, BusDevice::PhaseRead); + map_to(0x1000000, BusDevice::Unassigned); + }); + } + + void populate_memory_map(int start_address, std::function)> populator) { + // Define semantics for below; map_to will write from the current cursor position + // to the supplied 24-bit address, setting a particular mapped device. + int segment = start_address >> 17; + auto map_to = [&segment, this](int address, BusDevice device) { + for(; segment < address >> 17; ++segment) { + this->memory_map_[segment] = device; } - - // The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second. - // Its clock and data lines are connected to the VIA. - keyboard_clock_ += duration; - if(keyboard_clock_ >= KEYBOARD_CLOCK_RATE) { - const auto keyboard_ticks = keyboard_clock_.divide(KEYBOARD_CLOCK_RATE); - keyboard_.run_for(keyboard_ticks); - via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data()); - via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock()); - } - - // Feed mouse inputs within at most 1250 cycles of each other. - if(mouse_.has_steps()) { - time_since_mouse_update_ += duration; - const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500)); - if(mouse_ticks > HalfCycles(0)) { - mouse_.prepare_step(); - scc_.set_dcd(0, mouse_.get_channel(1) & 1); - scc_.set_dcd(1, mouse_.get_channel(0) & 1); - } - } - - // TODO: SCC should be clocked at a divide-by-two, if and when it actually has - // anything connected. - - // Consider updating the real-time clock. - real_time_clock_ += duration; - auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_integral(); - while(ticks--) { - clock_.update(); - // TODO: leave a delay between toggling the input rather than using this coupled hack. - via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true); - via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false); - } - - // Update the SCSI if currently active. - if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) { - if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration); - } - } - - forceinline void update_video() { - video_.run_for(time_since_video_update_.flush()); - time_until_video_event_ = video_.next_sequence_point(); - } - - Inputs::Mouse &get_mouse() final { - return mouse_; - } - - using IWMActor = JustInTimeActor; - - class VIAPortHandler: public MOS::MOS6522::PortHandler { - public: - VIAPortHandler(ConcreteMachine &machine, Apple::Clock::SerialClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) : - machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {} - - using Port = MOS::MOS6522::Port; - using Line = MOS::MOS6522::Line; - - void set_port_output(Port port, uint8_t value, uint8_t) { - /* - Peripheral lines: keyboard data, interrupt configuration. - (See p176 [/215]) - */ - switch(port) { - case Port::A: - /* - Port A: - b7: [input] SCC wait/request (/W/REQA and /W/REQB wired together for a logical OR) - b6: 0 = alternate screen buffer, 1 = main screen buffer - b5: floppy disk SEL state control (upper/lower head "among other things") - b4: 1 = use ROM overlay memory map, 0 = use ordinary memory map - b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer - b2–b0: audio output volume - */ - iwm_->set_select(!!(value & 0x20)); - - machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08)); - machine_.set_rom_is_overlay(!!(value & 0x10)); - - audio_.flush(); - audio_.audio.set_volume(value & 7); - break; - - case Port::B: - /* - Port B: - b7: 0 = sound enabled, 1 = sound disabled - b6: [input] 0 = video beam in visible portion of line, 1 = outside - b5: [input] mouse y2 - b4: [input] mouse x2 - b3: [input] 0 = mouse button down, 1 = up - b2: 0 = real-time clock enabled, 1 = disabled - b1: clock's data-clock line - b0: clock's serial data line - */ - if(value & 0x4) clock_.abort(); - else clock_.set_input(!!(value & 0x2), !!(value & 0x1)); - - audio_.flush(); - audio_.audio.set_enabled(!(value & 0x80)); - break; - } - } - - uint8_t get_port_input(Port port) { - switch(port) { - case Port::A: -// printf("6522 r A\n"); - return 0x00; // TODO: b7 = SCC wait/request - - case Port::B: - return uint8_t( - ((mouse_.get_button_mask() & 1) ? 0x00 : 0x08) | - ((mouse_.get_channel(0) & 2) << 3) | - ((mouse_.get_channel(1) & 2) << 4) | - (clock_.get_data() ? 0x02 : 0x00) | - (machine_.video_is_outputting() ? 0x00 : 0x40) - ); - } - - // Should be unreachable. - return 0xff; - } - - void set_control_line_output(Port port, Line line, bool value) { - /* - Keyboard wiring (I believe): - CB2 = data (input/output) - CB1 = clock (input) - - CA2 is used for receiving RTC interrupts. - CA1 is used for receiving vsync. - */ - if(port == Port::B && line == Line::Two) { - keyboard_.set_input(value); - } - else logger.error().append("Unhandled 6522 control line output: %c%d", port ? 'B' : 'A', int(line)); - } - - void run_for(HalfCycles duration) { - // The 6522 enjoys a divide-by-ten, so multiply back up here to make the - // divided-by-two clock the audio works on. - audio_.time_since_update += HalfCycles(duration.as_integral() * 5); - } - - void flush() { - audio_.flush(); - } - - void set_interrupt_status(bool) { - machine_.update_interrupt_input(); - } - - private: - ConcreteMachine &machine_; - Apple::Clock::SerialClock &clock_; - Keyboard &keyboard_; - DeferredAudio &audio_; - IWMActor &iwm_; - Inputs::QuadratureMouse &mouse_; }; - CPU::MC68000::Processor mc68000_; + populator(map_to); + } - DriveSpeedAccumulator drive_speed_accumulator_; - IWMActor iwm_; - - DeferredAudio audio_; - Video video_; - - Apple::Clock::SerialClock clock_; - Keyboard keyboard_; - - VIAPortHandler via_port_handler_; - MOS::MOS6522::MOS6522 via_; - - Zilog::SCC::z8530 scc_; - SCSI::Bus scsi_bus_; - NCR::NCR5380::NCR5380 scsi_; - SCSI::Target::Target hard_drive_; - bool scsi_bus_is_clocked_ = false; - - HalfCycles via_clock_; - HalfCycles real_time_clock_; - HalfCycles keyboard_clock_; - HalfCycles time_since_video_update_; - HalfCycles time_until_video_event_; - HalfCycles time_since_mouse_update_; - - bool ROM_is_overlay_ = true; - int phase_ = 1; - int ram_subcycle_ = 0; - - DoubleDensityDrive drives_[2]; - Inputs::QuadratureMouse mouse_; - - Apple::Macintosh::KeyboardMapper keyboard_mapper_; - - enum class BusDevice { - RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned - }; - - /// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording - /// which device is current mapped in each area. Keeping it in a table is a bit faster - /// than the multi-level address inspection that is otherwise required, as well as - /// simplifying slightly the handling of different models. - /// - /// So: index with the top 7 bits of the 24-bit address. - BusDevice memory_map_[128]; - - void setup_memory_map() { - // Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true; - // start by calling into set_rom_is_overlay to seed everything up to $800000. - set_rom_is_overlay(true); - - populate_memory_map(0x800000, [] (std::function map_to) { - map_to(0x900000, BusDevice::Unassigned); - map_to(0xa00000, BusDevice::SCCReadResetPhase); - map_to(0xb00000, BusDevice::Unassigned); - map_to(0xc00000, BusDevice::SCCWrite); - map_to(0xd00000, BusDevice::Unassigned); - map_to(0xe00000, BusDevice::IWM); - map_to(0xe80000, BusDevice::Unassigned); - map_to(0xf00000, BusDevice::VIA); - map_to(0xf80000, BusDevice::PhaseRead); - map_to(0x1000000, BusDevice::Unassigned); - }); - } - - void populate_memory_map(int start_address, std::function)> populator) { - // Define semantics for below; map_to will write from the current cursor position - // to the supplied 24-bit address, setting a particular mapped device. - int segment = start_address >> 17; - auto map_to = [&segment, this](int address, BusDevice device) { - for(; segment < address >> 17; ++segment) { - this->memory_map_[segment] = device; - } - }; - - populator(map_to); - } - - uint32_t ram_mask_ = 0; - uint32_t rom_mask_ = 0; - uint8_t rom_[128*1024]; - std::vector ram_; + uint32_t ram_mask_ = 0; + uint32_t rom_mask_ = 0; + uint8_t rom_[128*1024]; + std::vector ram_; }; } diff --git a/Machines/Apple/Macintosh/Video.hpp b/Machines/Apple/Macintosh/Video.hpp index 6f0ed7658..9149232d4 100644 --- a/Machines/Apple/Macintosh/Video.hpp +++ b/Machines/Apple/Macintosh/Video.hpp @@ -28,76 +28,76 @@ constexpr int sync_end = 38; This class also collects audio and 400kb drive-speed data, forwarding those values. */ class Video { - public: - /*! - Constructs an instance of @c Video sourcing its pixel data from @c ram and - providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator. - */ - Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); +public: + /*! + Constructs an instance of @c Video sourcing its pixel data from @c ram and + providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator. + */ + Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator); - /*! - Sets the target device for video data. - */ - void set_scan_target(Outputs::Display::ScanTarget *scan_target); + /*! + Sets the target device for video data. + */ + void set_scan_target(Outputs::Display::ScanTarget *scan_target); - /// Gets the current scan status. - Outputs::Display::ScanStatus get_scaled_scan_status() const; + /// Gets the current scan status. + Outputs::Display::ScanStatus get_scaled_scan_status() const; - /*! - Produces the next @c duration period of pixels. - */ - void run_for(HalfCycles duration); + /*! + Produces the next @c duration period of pixels. + */ + void run_for(HalfCycles duration); - /*! - Sets whether the alternate screen and/or audio buffers should be used to source data. - */ - void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer); + /*! + Sets whether the alternate screen and/or audio buffers should be used to source data. + */ + void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer); - /*! - Provides a base address and a mask indicating which parts of the generated video and audio/drive addresses are - actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff. - */ - void set_ram(uint16_t *ram, uint32_t mask); + /*! + Provides a base address and a mask indicating which parts of the generated video and audio/drive addresses are + actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff. + */ + void set_ram(uint16_t *ram, uint32_t mask); - /*! - @returns @c true if the video is currently outputting a vertical sync, @c false otherwise. - */ - bool vsync(); + /*! + @returns @c true if the video is currently outputting a vertical sync, @c false otherwise. + */ + bool vsync(); - /* - @returns @c true if in @c offset half cycles from now, the video will be outputting pixels; - @c false otherwise. - */ - bool is_outputting(HalfCycles offset = HalfCycles(0)) { - const auto offset_position = frame_position_ + offset % frame_length; - const int column = int((offset_position % line_length).as_integral()) >> 4; - const int line = int((offset_position / line_length).as_integral()); - return line < 342 && column < 32; - } + /* + @returns @c true if in @c offset half cycles from now, the video will be outputting pixels; + @c false otherwise. + */ + bool is_outputting(HalfCycles offset = HalfCycles(0)) { + const auto offset_position = frame_position_ + offset % frame_length; + const int column = int((offset_position % line_length).as_integral()) >> 4; + const int line = int((offset_position / line_length).as_integral()); + return line < 342 && column < 32; + } - /*! - @returns the amount of time until there is next a transition on the - vsync signal. - */ - HalfCycles next_sequence_point(); + /*! + @returns the amount of time until there is next a transition on the + vsync signal. + */ + HalfCycles next_sequence_point(); - private: - DeferredAudio &audio_; - DriveSpeedAccumulator &drive_speed_accumulator_; +private: + DeferredAudio &audio_; + DriveSpeedAccumulator &drive_speed_accumulator_; - Outputs::CRT::CRT crt_; - uint16_t *ram_ = nullptr; - uint32_t ram_mask_ = 0; + Outputs::CRT::CRT crt_; + uint16_t *ram_ = nullptr; + uint32_t ram_mask_ = 0; - HalfCycles frame_position_; + HalfCycles frame_position_; - size_t video_address_ = 0; - size_t audio_address_ = 0; + size_t video_address_ = 0; + size_t audio_address_ = 0; - uint64_t *pixel_buffer_ = nullptr; + uint64_t *pixel_buffer_ = nullptr; - bool use_alternate_screen_buffer_ = false; - bool use_alternate_audio_buffer_ = false; + bool use_alternate_screen_buffer_ = false; + bool use_alternate_audio_buffer_ = false; }; } diff --git a/Machines/Atari/2600/Atari2600.cpp b/Machines/Atari/2600/Atari2600.cpp index 36db0226a..1e6eb9b8a 100644 --- a/Machines/Atari/2600/Atari2600.cpp +++ b/Machines/Atari/2600/Atari2600.cpp @@ -36,39 +36,39 @@ namespace { namespace Atari2600 { class Joystick: public Inputs::ConcreteJoystick { - public: - Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : - ConcreteJoystick({ - Input(Input::Up), - Input(Input::Down), - Input(Input::Left), - Input(Input::Right), - Input(Input::Fire) - }), - bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} +public: + Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : + ConcreteJoystick({ + Input(Input::Up), + Input(Input::Down), + Input(Input::Left), + Input(Input::Right), + Input(Input::Fire) + }), + bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} - void did_set_input(const Input &digital_input, bool is_active) final { - switch(digital_input.type) { - case Input::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break; - case Input::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break; - case Input::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break; - case Input::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break; + void did_set_input(const Input &digital_input, bool is_active) final { + switch(digital_input.type) { + case Input::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break; + case Input::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break; + case Input::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break; + case Input::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break; - // TODO: latching - case Input::Fire: - if(is_active) - bus_->tia_input_value_[fire_tia_input_] &= ~0x80; - else - bus_->tia_input_value_[fire_tia_input_] |= 0x80; - break; + // TODO: latching + case Input::Fire: + if(is_active) + bus_->tia_input_value_[fire_tia_input_] &= ~0x80; + else + bus_->tia_input_value_[fire_tia_input_] |= 0x80; + break; - default: break; - } + default: break; } + } - private: - Bus *bus_; - std::size_t shift_, fire_tia_input_; +private: + Bus *bus_; + std::size_t shift_, fire_tia_input_; }; using Target = Analyser::Static::Atari2600::Target; @@ -79,133 +79,133 @@ class ConcreteMachine: public MachineTypes::AudioProducer, public MachineTypes::ScanProducer, public MachineTypes::JoystickMachine { - public: - ConcreteMachine(const Target &target) : frequency_mismatch_warner_(*this) { - const std::vector &rom = target.media.cartridges.front()->get_segments().front().data; +public: + ConcreteMachine(const Target &target) : frequency_mismatch_warner_(*this) { + const std::vector &rom = target.media.cartridges.front()->get_segments().front().data; - using PagingModel = Target::PagingModel; - switch(target.paging_model) { - case PagingModel::ActivisionStack: bus_ = std::make_unique>(rom); break; - case PagingModel::CBSRamPlus: bus_ = std::make_unique>(rom); break; - case PagingModel::CommaVid: bus_ = std::make_unique>(rom); break; - case PagingModel::MegaBoy: bus_ = std::make_unique>(rom); break; - case PagingModel::MNetwork: bus_ = std::make_unique>(rom); break; - case PagingModel::None: bus_ = std::make_unique>(rom); break; - case PagingModel::ParkerBros: bus_ = std::make_unique>(rom); break; - case PagingModel::Pitfall2: bus_ = std::make_unique>(rom); break; - case PagingModel::Tigervision: bus_ = std::make_unique>(rom); break; + using PagingModel = Target::PagingModel; + switch(target.paging_model) { + case PagingModel::ActivisionStack: bus_ = std::make_unique>(rom); break; + case PagingModel::CBSRamPlus: bus_ = std::make_unique>(rom); break; + case PagingModel::CommaVid: bus_ = std::make_unique>(rom); break; + case PagingModel::MegaBoy: bus_ = std::make_unique>(rom); break; + case PagingModel::MNetwork: bus_ = std::make_unique>(rom); break; + case PagingModel::None: bus_ = std::make_unique>(rom); break; + case PagingModel::ParkerBros: bus_ = std::make_unique>(rom); break; + case PagingModel::Pitfall2: bus_ = std::make_unique>(rom); break; + case PagingModel::Tigervision: bus_ = std::make_unique>(rom); break; - case PagingModel::Atari8k: - if(target.uses_superchip) { - bus_ = std::make_unique>(rom); - } else { - bus_ = std::make_unique>(rom); - } - break; - case PagingModel::Atari16k: - if(target.uses_superchip) { - bus_ = std::make_unique>(rom); - } else { - bus_ = std::make_unique>(rom); - } - break; - case PagingModel::Atari32k: - if(target.uses_superchip) { - bus_ = std::make_unique>(rom); - } else { - bus_ = std::make_unique>(rom); - } - break; - } - - joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0)); - joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); - - set_is_ntsc(is_ntsc_); + case PagingModel::Atari8k: + if(target.uses_superchip) { + bus_ = std::make_unique>(rom); + } else { + bus_ = std::make_unique>(rom); + } + break; + case PagingModel::Atari16k: + if(target.uses_superchip) { + bus_ = std::make_unique>(rom); + } else { + bus_ = std::make_unique>(rom); + } + break; + case PagingModel::Atari32k: + if(target.uses_superchip) { + bus_ = std::make_unique>(rom); + } else { + bus_ = std::make_unique>(rom); + } + break; } - const std::vector> &get_joysticks() final { - return joysticks_; + joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0)); + joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); + + set_is_ntsc(is_ntsc_); + } + + const std::vector> &get_joysticks() final { + return joysticks_; + } + + void set_switch_is_enabled(Atari2600Switch input, bool state) final { + switch(input) { + case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break; + case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break; + case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break; + case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break; + case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break; } + } - void set_switch_is_enabled(Atari2600Switch input, bool state) final { - switch(input) { - case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break; - case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break; - case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break; - case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break; - case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break; - } + bool get_switch_is_enabled(Atari2600Switch input) const final { + uint8_t port_input = bus_->mos6532_.get_port_input(1); + switch(input) { + case Atari2600SwitchReset: return !!(port_input & 0x01); + case Atari2600SwitchSelect: return !!(port_input & 0x02); + case Atari2600SwitchColour: return !!(port_input & 0x08); + case Atari2600SwitchLeftPlayerDifficulty: return !!(port_input & 0x40); + case Atari2600SwitchRightPlayerDifficulty: return !!(port_input & 0x80); + default: return false; } + } - bool get_switch_is_enabled(Atari2600Switch input) const final { - uint8_t port_input = bus_->mos6532_.get_port_input(1); - switch(input) { - case Atari2600SwitchReset: return !!(port_input & 0x01); - case Atari2600SwitchSelect: return !!(port_input & 0x02); - case Atari2600SwitchColour: return !!(port_input & 0x08); - case Atari2600SwitchLeftPlayerDifficulty: return !!(port_input & 0x40); - case Atari2600SwitchRightPlayerDifficulty: return !!(port_input & 0x80); - default: return false; - } - } + void set_reset_switch(bool state) final { + bus_->set_reset_line(state); + } - void set_reset_switch(bool state) final { - bus_->set_reset_line(state); - } + // to satisfy CRTMachine::Machine + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + bus_->speaker_.set_input_rate(float(get_clock_rate() / double(CPUTicksPerAudioTick))); + bus_->tia_.set_crt_delegate(&frequency_mismatch_warner_); + bus_->tia_.set_scan_target(scan_target); + } - // to satisfy CRTMachine::Machine - void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { - bus_->speaker_.set_input_rate(float(get_clock_rate() / double(CPUTicksPerAudioTick))); - bus_->tia_.set_crt_delegate(&frequency_mismatch_warner_); - bus_->tia_.set_scan_target(scan_target); - } + Outputs::Display::ScanStatus get_scaled_scan_status() const final { + return bus_->tia_.get_scaled_scan_status() / 3.0f; + } - Outputs::Display::ScanStatus get_scaled_scan_status() const final { - return bus_->tia_.get_scaled_scan_status() / 3.0f; - } + Outputs::Speaker::Speaker *get_speaker() final { + return &bus_->speaker_; + } - Outputs::Speaker::Speaker *get_speaker() final { - return &bus_->speaker_; - } + void run_for(const Cycles cycles) final { + bus_->run_for(cycles); + bus_->apply_confidence(confidence_counter_); + } - void run_for(const Cycles cycles) final { - bus_->run_for(cycles); - bus_->apply_confidence(confidence_counter_); - } + void flush_output(int) final { + bus_->flush(); + } - void flush_output(int) final { - bus_->flush(); - } + void register_crt_frequency_mismatch() { + is_ntsc_ ^= true; + set_is_ntsc(is_ntsc_); + } - void register_crt_frequency_mismatch() { - is_ntsc_ ^= true; - set_is_ntsc(is_ntsc_); - } + float get_confidence() final { + return confidence_counter_.get_confidence(); + } - float get_confidence() final { - return confidence_counter_.get_confidence(); - } +private: + // The bus. + std::unique_ptr bus_; - private: - // The bus. - std::unique_ptr bus_; + // Output frame rate tracker. + Outputs::CRT::CRTFrequencyMismatchWarner frequency_mismatch_warner_; + bool is_ntsc_ = true; + std::vector> joysticks_; - // Output frame rate tracker. - Outputs::CRT::CRTFrequencyMismatchWarner frequency_mismatch_warner_; - bool is_ntsc_ = true; - std::vector> joysticks_; + // a confidence counter + Analyser::Dynamic::ConfidenceCounter confidence_counter_; - // a confidence counter - Analyser::Dynamic::ConfidenceCounter confidence_counter_; - - void set_is_ntsc(bool is_ntsc) { - bus_->tia_.set_output_mode(is_ntsc ? TIA::OutputMode::NTSC : TIA::OutputMode::PAL); - const double clock_rate = is_ntsc ? NTSC_clock_rate : PAL_clock_rate; - bus_->speaker_.set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick)); - bus_->speaker_.set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2)); - set_clock_rate(clock_rate); - } + void set_is_ntsc(bool is_ntsc) { + bus_->tia_.set_output_mode(is_ntsc ? TIA::OutputMode::NTSC : TIA::OutputMode::PAL); + const double clock_rate = is_ntsc ? NTSC_clock_rate : PAL_clock_rate; + bus_->speaker_.set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick)); + bus_->speaker_.set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2)); + set_clock_rate(clock_rate); + } }; } diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index c853acec6..2743933d5 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -64,644 +64,644 @@ class ConcreteMachine: 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); +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); - switch(target.memory_size) { - default: - case Target::MemorySize::FiveHundredAndTwelveKilobytes: - ram_.resize(512 * 1024); - break; - case Target::MemorySize::OneMegabyte: - ram_.resize(1024 * 1024); - break; - case Target::MemorySize::FourMegabytes: - ram_.resize(4 * 1024 * 1024); - break; - } - Memory::Fuzz(ram_); + switch(target.memory_size) { + default: + case Target::MemorySize::FiveHundredAndTwelveKilobytes: + ram_.resize(512 * 1024); + break; + case Target::MemorySize::OneMegabyte: + ram_.resize(1024 * 1024); + break; + case Target::MemorySize::FourMegabytes: + ram_.resize(4 * 1024 * 1024); + break; + } + Memory::Fuzz(ram_); - video_->set_ram( - reinterpret_cast(ram_.data()), - ram_.size() >> 1 - ); + video_->set_ram( + reinterpret_cast(ram_.data()), + ram_.size() >> 1 + ); - constexpr ROM::Name rom_name = ROM::Name::AtariSTTOS100; - ROM::Request request(rom_name); - auto roms = rom_fetcher(request); - if(!request.validate(roms)) { - throw ROMMachine::Error::MissingROMs; - } - Memory::PackBigEndian16(roms.find(rom_name)->second, rom_); + constexpr ROM::Name rom_name = ROM::Name::AtariSTTOS100; + ROM::Request request(rom_name); + auto roms = rom_fetcher(request); + if(!request.validate(roms)) { + throw ROMMachine::Error::MissingROMs; + } + Memory::PackBigEndian16(roms.find(rom_name)->second, rom_); - // Set up basic memory map. - int c = 0; - 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; + // Set up basic memory map. + int c = 0; + 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; + 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; + + // Copy the first 8 bytes of ROM into RAM. + reinstall_rom_vector(); + + 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::Display::DisplayType get_display_type() const final { + return video_->get_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 + template HalfCycles perform_bus_operation(const 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 & CPU::MC68000::Operation::Reset) { + logger.error().append("Unhandled Reset"); + } + + // A null cycle leaves nothing else to do. + if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0); + + // An interrupt acknowledge, perhaps? + if(cycle.operation & CPU::MC68000::Operation::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 { - 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; - - // Copy the first 8 bytes of ROM into RAM. - reinstall_rom_vector(); - - 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::Display::DisplayType get_display_type() const final { - return video_->get_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 - template HalfCycles perform_bus_operation(const 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 & CPU::MC68000::Operation::Reset) { - logger.error().append("Unhandled Reset"); - } - - // A null cycle leaves nothing else to do. - if(!(cycle.operation & (CPU::MC68000::Operation::NewAddress | CPU::MC68000::Operation::SameAddress))) return HalfCycles(0); - - // An interrupt acknowledge, perhaps? - if(cycle.operation & CPU::MC68000::Operation::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 & CPU::MC68000::Operation::SelectByte) { - const int interrupt = mfp_->acknowledge_interrupt(); - if(interrupt != Motorola::MFP68901::MFP68901::NoAcknowledgement) { - cycle.value->b = 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 & CPU::MC68000::Operation::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); + if(cycle.operation & CPU::MC68000::Operation::SelectByte) { + const int interrupt = mfp_->acknowledge_interrupt(); + if(interrupt != Motorola::MFP68901::MFP68901::NoAcknowledgement) { + cycle.value->b = uint8_t(interrupt); + } else { + // TODO: this should take a while. Find out how long. + mc68000_.set_bus_error(true); } } - } - - uint8_t *memory = nullptr; - switch(memory_map_[address >> 16]) { - default: - case BusDevice::RAM: - memory = ram_.data(); - break; - - case BusDevice::ROM: - memory = rom_.data(); - if(!(cycle.operation & CPU::MC68000::Operation::Read)) { - return delay; - } - 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 & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { - default: break; - case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: - cycle.value->w = 0xffff; - break; - case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: - cycle.value->b = 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 & CPU::MC68000::Operation::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 & CPU::MC68000::Operation::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 & CPU::MC68000::Operation::Read) { - cycle.set_value8_high(GI::AY38910::Utility::read(ay_)); - } else { - // Net effect here: addresses with bit 1 set write to a register, - // addresses with bit 1 clear select a register. - GI::AY38910::Utility::write(ay_, address&2, cycle.value8_high()); - } - 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 & CPU::MC68000::Operation::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 & CPU::MC68000::Operation::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. - // - // In both write cases, immediately reinstall the first eight bytes of RAM from ROM, so that any write to - // that area is in effect a no-op. This is cheaper than the conditionality of actually checking. - switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { - default: - break; + auto address = cycle.host_endian_byte_address(); - case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: - cycle.value->w = *reinterpret_cast(&memory[address]); - break; - case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: - cycle.value->b = memory[address]; - break; - case CPU::MC68000::Operation::SelectWord: - if(address >= video_range_.low_address && address < video_range_.high_address) - video_.flush(); - *reinterpret_cast(&memory[address]) = cycle.value->w; - reinstall_rom_vector(); - break; - case CPU::MC68000::Operation::SelectByte: - if(address >= video_range_.low_address && address < video_range_.high_address) - video_.flush(); - memory[address] = cycle.value->b; - reinstall_rom_vector(); - break; + // If this is a new strobing of the address signal, test for bus error and pre-DTack delay. + HalfCycles delay(0); + if(cycle.operation & CPU::MC68000::Operation::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::RAM: + memory = ram_.data(); + break; + + case BusDevice::ROM: + memory = rom_.data(); + if(!(cycle.operation & CPU::MC68000::Operation::Read)) { + return delay; + } + 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 & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { + default: break; + case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: + cycle.value->w = 0xffff; + break; + case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: + cycle.value->b = 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 & CPU::MC68000::Operation::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 & CPU::MC68000::Operation::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 & CPU::MC68000::Operation::Read) { + cycle.set_value8_high(GI::AY38910::Utility::read(ay_)); + } else { + // Net effect here: addresses with bit 1 set write to a register, + // addresses with bit 1 clear select a register. + GI::AY38910::Utility::write(ay_, address&2, cycle.value8_high()); + } + 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 & CPU::MC68000::Operation::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 & CPU::MC68000::Operation::Read) { + cycle.set_value8_high((*acia_)->read(int(address >> 1))); + } else { + (*acia_)->write(int(address >> 1), cycle.value8_high()); + } + } break; + } return HalfCycles(0); } - void reinstall_rom_vector() { - std::copy(rom_.begin(), rom_.begin() + 8, ram_.begin()); + // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. + // + // In both write cases, immediately reinstall the first eight bytes of RAM from ROM, so that any write to + // that area is in effect a no-op. This is cheaper than the conditionality of actually checking. + switch(cycle.operation & (CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read)) { + default: + break; + + case CPU::MC68000::Operation::SelectWord | CPU::MC68000::Operation::Read: + cycle.value->w = *reinterpret_cast(&memory[address]); + break; + case CPU::MC68000::Operation::SelectByte | CPU::MC68000::Operation::Read: + cycle.value->b = memory[address]; + break; + case CPU::MC68000::Operation::SelectWord: + if(address >= video_range_.low_address && address < video_range_.high_address) + video_.flush(); + *reinterpret_cast(&memory[address]) = cycle.value->w; + reinstall_rom_vector(); + break; + case CPU::MC68000::Operation::SelectByte: + if(address >= video_range_.low_address && address < video_range_.high_address) + video_.flush(); + memory[address] = cycle.value->b; + reinstall_rom_vector(); + break; } - void flush_output(int outputs) final { - dma_.flush(); - mfp_.flush(); + return HalfCycles(0); + } + + void reinstall_rom_vector() { + std::copy(rom_.begin(), rom_.begin() + 8, ram_.begin()); + } + + void flush_output(int outputs) final { + dma_.flush(); + mfp_.flush(); + keyboard_acia_.flush(); + midi_acia_.flush(); + + if(outputs & Output::Video) { + video_.flush(); + } + if(outputs & Output::Audio) { + update_audio(); + audio_queue_.perform(); + } + } + +private: + forceinline void advance_time(HalfCycles length) { + // Advance the relevant counters. + cycles_since_audio_update_ += length; + mfp_ += length; + if(dma_clocking_preference_ != ClockingHint::Preference::None) + 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(outputs & Output::Video) { - video_.flush(); - } - if(outputs & Output::Audio) { - update_audio(); - audio_queue_.perform(); - } } - private: - forceinline void advance_time(HalfCycles length) { - // Advance the relevant counters. - cycles_since_audio_update_ += length; - mfp_ += length; - if(dma_clocking_preference_ != ClockingHint::Preference::None) - 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_clocking_preference_ == ClockingHint::Preference::RealTime) { - dma_.flush(); - } - - // Update the video output, checking whether a sequence point has been hit. - if(video_.will_flush(length)) { - length -= video_.cycles_until_implicit_flush(); - video_ += video_.cycles_until_implicit_flush(); - - mfp_->set_timer_event_input<1>(video_->display_enabled()); - update_interrupt_input(); - } - - video_ += length; + if(mfp_is_realtime_) { + mfp_.flush(); } - void update_audio() { - speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide_cycles(Cycles(4))); + if(dma_clocking_preference_ == ClockingHint::Preference::RealTime) { + dma_.flush(); } - CPU::MC68000::Processor mc68000_; - HalfCycles bus_phase_; + // Update the video output, checking whether a sequence point has been hit. + if(video_.will_flush(length)) { + length -= video_.cycles_until_implicit_flush(); + video_ += video_.cycles_until_implicit_flush(); - JustInTimeActor