diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index 7fd07a82c..7fe86dd47 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -17,7 +17,10 @@ using namespace Commodore::C1540; namespace { -ROM::Name rom_name(Personality personality) { + +// MARK: - Construction, including ROM requests. + +ROM::Name rom_name(const Personality personality) { switch(personality) { default: case Personality::C1540: return ROM::Name::Commodore1540; @@ -26,11 +29,11 @@ ROM::Name rom_name(Personality personality) { } } -ROM::Request Machine::rom_request(Personality personality) { +ROM::Request Machine::rom_request(const Personality personality) { return ROM::Request(rom_name(personality)); } -MachineBase::MachineBase(Personality personality, const ROM::Map &roms) : +MachineBase::MachineBase(const Personality personality, const ROM::Map &roms) : Storage::Disk::Controller(1000000), m6502_(*this), drive_VIA_(drive_VIA_port_handler_), @@ -58,14 +61,13 @@ MachineBase::MachineBase(Personality personality, const ROM::Map &roms) : std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size())); } -Machine::Machine(Personality personality, const ROM::Map &roms) : +Machine::Machine(const Personality personality, const ROM::Map &roms) : MachineBase(personality, roms) {} -void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) { - Commodore::Serial::attach(serial_port_, serial_bus); -} +// MARK: - 6502 bus. -Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { +template +Cycles MachineBase::perform(const AddressT address, CPU::MOS6502Mk2::data_t value) { /* Memory map (given that I'm unsure yet on any potential mirroring): @@ -75,24 +77,28 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, 0xc000-0xffff ROM */ if(address < 0x800) { - if(is_read(operation)) - *value = ram_[address]; + if constexpr (is_read(operation)) + value = ram_[address]; else - ram_[address] = *value; + ram_[address] = value; } else if(address >= 0xc000) { - if(is_read(operation)) { - *value = rom_[address & 0x3fff]; + if constexpr (is_read(operation)) { + value = rom_[address & 0x3fff]; } } else if(address >= 0x1800 && address <= 0x180f) { - if(is_read(operation)) - *value = serial_port_VIA_.read(address); + if constexpr (is_read(operation)) + value = serial_port_VIA_.read(address); else - serial_port_VIA_.write(address, *value); + serial_port_VIA_.write(address, value); } else if(address >= 0x1c00 && address <= 0x1c0f) { - if(is_read(operation)) - *value = drive_VIA_.read(address); + if constexpr (is_read(operation)) + value = drive_VIA_.read(address); else - drive_VIA_.write(address, *value); + drive_VIA_.write(address, value); + } else { + if constexpr (is_read(operation)) { + value = 0xff; + } } serial_port_VIA_.run_for(Cycles(1)); @@ -101,34 +107,42 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, return Cycles(1); } -void Machine::set_disk(std::shared_ptr disk) { - get_drive().set_disk(disk); -} - void Machine::run_for(const Cycles cycles) { m6502_.run_for(cycles); - const bool drive_motor = drive_VIA_port_handler_.get_motor_enabled(); + const bool drive_motor = drive_VIA_port_handler_.motor_enabled(); get_drive().set_motor_on(drive_motor); - if(drive_motor) + if(drive_motor) { Storage::Disk::Controller::run_for(cycles); + } } -void MachineBase::set_activity_observer(Activity::Observer *observer) { +// MARK: - External attachments. + +void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) { + Commodore::Serial::attach(serial_port_, serial_bus); +} + +void Machine::set_disk(std::shared_ptr disk) { + get_drive().set_disk(disk); + drive_VIA_port_handler_.set_is_read_only(disk->is_read_only()); +} + +void MachineBase::set_activity_observer(Activity::Observer *const observer) { drive_VIA_.bus_handler().set_activity_observer(observer); get_drive().set_activity_observer(observer, "Drive", false); } -// MARK: - 6522 delegate +// MARK: - 6522 delegate. void MachineBase::mos6522_did_change_interrupt_status(void *) { // both VIAs are connected to the IRQ line - m6502_.set_irq_line(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line()); + m6502_.set(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line()); } -// MARK: - Disk drive +// MARK: - Disk drive. -void MachineBase::process_input_bit(int value) { +void MachineBase::process_input_bit(const int value) { shift_register_ = (shift_register_ << 1) | value; if((shift_register_ & 0x3ff) == 0x3ff) { drive_VIA_port_handler_.set_sync_detected(true); @@ -140,11 +154,11 @@ void MachineBase::process_input_bit(int value) { if(bit_window_offset_ == 8) { drive_VIA_port_handler_.set_data_input(uint8_t(shift_register_)); bit_window_offset_ = 0; - if(drive_VIA_port_handler_.get_should_set_overflow()) { - m6502_.set_overflow_line(true); + if(drive_VIA_port_handler_.should_set_overflow()) { + m6502_.set(true); } } - else m6502_.set_overflow_line(false); + else m6502_.set(false); } // the 1540 does not recognise index holes @@ -164,7 +178,9 @@ void MachineBase::drive_via_did_set_data_density(DriveVIA &, const int density) template uint8_t SerialPortVIA::get_port_input() const { - if(port) return port_b_; + if(port) { + return port_b_; + } return 0xff; } @@ -203,13 +219,15 @@ void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) { void SerialPortVIA::update_data_line() { // "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1" - serial_port_->set_output(::Commodore::Serial::Line::Data, - Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))); + serial_port_->set_output( + ::Commodore::Serial::Line::Data, + Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)) + ); } // MARK: - DriveVIA -void DriveVIA::set_delegate(Delegate *delegate) { +void DriveVIA::set_delegate(Delegate *const delegate) { delegate_ = delegate; } @@ -220,18 +238,22 @@ uint8_t DriveVIA::get_port_input() const { } void DriveVIA::set_sync_detected(const bool sync_detected) { - port_b_ = (port_b_ & 0x7f) | (sync_detected ? 0x00 : 0x80); + port_b_ = (port_b_ & ~0x80) | (sync_detected ? 0x00 : 0x80); } -void DriveVIA::set_data_input(uint8_t value) { +void DriveVIA::set_is_read_only(const bool is_read_only) { + port_b_ = (port_b_ & ~0x10) | (is_read_only ? 0x00 : 0x10); +} + +void DriveVIA::set_data_input(const uint8_t value) { port_a_ = value; } -bool DriveVIA::get_should_set_overflow() { +bool DriveVIA::should_set_overflow() { return should_set_overflow_; } -bool DriveVIA::get_motor_enabled() { +bool DriveVIA::motor_enabled() { return drive_motor_; } @@ -240,36 +262,48 @@ void DriveVIA::set_control_line_output(const bool value) { if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) { should_set_overflow_ = value; } -} -template -void DriveVIA::set_port_output(const uint8_t value, uint8_t) { - if(port) { - if(previous_port_b_output_ != value) { - // Record drive motor state. - drive_motor_ = value&4; - - // Check for a head step. - const int step_difference = ((value&3) - (previous_port_b_output_&3))&3; - if(step_difference) { - if(delegate_) delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1); - } - - // Check for a change in density. - const int density_difference = (previous_port_b_output_^value) & (3 << 5); - if(density_difference && delegate_) { - delegate_->drive_via_did_set_data_density(*this, (value >> 5)&3); - } - - // Post the LED status. - if(observer_) observer_->set_led_status("Drive", value&8); - - previous_port_b_output_ = value; + if(port == MOS::MOS6522::Port::B && line == MOS::MOS6522::Line::Two) { + // TODO: 0 = write, 1 = read. + if(!value) { + printf("NOT IMPLEMENTED: write mode\n"); } } } -void DriveVIA::set_activity_observer(Activity::Observer *observer) { +template <> +void DriveVIA::set_port_output(const uint8_t value, uint8_t) { + if(previous_port_b_output_ != value) { + // Record drive motor state. + drive_motor_ = value&4; + + // Check for a head step. + const int step_difference = ((value&3) - (previous_port_b_output_&3))&3; + if(step_difference && delegate_) { + delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1); + } + + // Check for a change in density. + const int density_difference = (previous_port_b_output_^value) & (3 << 5); + if(density_difference && delegate_) { + delegate_->drive_via_did_set_data_density(*this, (value >> 5)&3); + } + + // Post the LED status. + if(observer_) { + observer_->set_led_status("Drive", value&8); + } + + previous_port_b_output_ = value; + } +} + +template <> +void DriveVIA::set_port_output(const uint8_t value, uint8_t) { + printf("TODO: output is %02x\n", value); +} + +void DriveVIA::set_activity_observer(Activity::Observer *const observer) { observer_ = observer; if(observer) { observer->register_led("Drive"); @@ -277,9 +311,9 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) { } } -// MARK: - SerialPort +// MARK: - SerialPort. -void SerialPort::set_input(Serial::Line line, Serial::LineLevel level) { +void SerialPort::set_input(const Serial::Line line, const Serial::LineLevel level) { serial_port_via_->set_serial_line_state(line, bool(level), *via_); } diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index 88dc21db8..0ffd2940f 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -8,7 +8,7 @@ #pragma once -#include "Processors/6502/6502.hpp" +#include "Processors/6502Mk2/6502Mk2.hpp" #include "Components/6522/6522.hpp" #include "Machines/Commodore/SerialBus.hpp" @@ -65,15 +65,17 @@ private: It is wired up such that Port B contains: Bits 0/1: head step direction Bit 2: motor control - Bit 3: LED control (TODO) + Bit 3: LED control Bit 4: write protect photocell status (TODO) Bits 5/6: read/write density Bit 7: 0 if sync marks are currently being detected, 1 otherwise. ... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction. - It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on - whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO. + Elsewhere: + * CA2 might is used to set processor overflow; + * CA1 a strobe for data input; and + * CB2 indicates read/write mode; 1 = read, 0 = write. */ class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { public: @@ -88,8 +90,9 @@ public: void set_sync_detected(bool); void set_data_input(uint8_t); - bool get_should_set_overflow(); - bool get_motor_enabled(); + void set_is_read_only(bool); + bool should_set_overflow(); + bool motor_enabled(); template void set_control_line_output(bool value); @@ -122,7 +125,6 @@ private: }; class MachineBase: - public CPU::MOS6502::BusHandler, public MOS::MOS6522::IRQDelegatePortHandler::Delegate, public DriveVIA::Delegate, public Storage::Disk::Controller { @@ -134,7 +136,8 @@ public: void set_activity_observer(Activity::Observer *); // to satisfy CPU::MOS6502::Processor - Cycles perform_bus_operation(CPU::MOS6502::BusOperation, uint16_t address, uint8_t *value); + template + Cycles perform(const AddressT, CPU::MOS6502Mk2::data_t); protected: // to satisfy MOS::MOS6522::Delegate @@ -144,7 +147,12 @@ protected: void drive_via_did_step_head(DriveVIA &, int direction) override; void drive_via_did_set_data_density(DriveVIA &, int density) override; - CPU::MOS6502::Processor m6502_; + struct M6502Traits { + static constexpr auto uses_ready_line = false; + static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::AnyCycle; + using BusHandlerT = MachineBase; + }; + CPU::MOS6502Mk2::Processor m6502_; uint8_t ram_[0x800]; uint8_t rom_[0x4000]; diff --git a/Machines/Commodore/Plus4/Plus4.cpp b/Machines/Commodore/Plus4/Plus4.cpp index e6a7f546d..fa2e89fe9 100644 --- a/Machines/Commodore/Plus4/Plus4.cpp +++ b/Machines/Commodore/Plus4/Plus4.cpp @@ -16,7 +16,7 @@ #include "Machines/MachineTypes.hpp" #include "Machines/Utility/MemoryFuzzer.hpp" -#include "Processors/6502/6502.hpp" +#include "Processors/6502Mk2/6502Mk2.hpp" #include "Analyser/Static/Commodore/Target.hpp" #include "Outputs/Log.hpp" #include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp" @@ -240,14 +240,11 @@ public: // HACK. NOCOMMIT. // int pulse_num_ = 0; - Cycles perform_bus_operation( - const CPU::MOS6502::BusOperation operation, - const uint16_t address, - uint8_t *const value - ) { + template + Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t value) { // Determine from the TED video subsystem the length of this clock cycle as perceived by the 6502, // relative to the master clock. - const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready); + const auto length = video_.cycle_length(operation == CPU::MOS6502Mk2::BusOperation::Ready); // Update other subsystems. advance_timers_and_tape(length); @@ -262,7 +259,7 @@ public: audio_ += length; } - if(operation == CPU::MOS6502::BusOperation::Ready) { + if(operation == CPU::MOS6502Mk2::BusOperation::Ready) { return length; } @@ -279,17 +276,17 @@ public: // b1 = serial clock out and cassette write; // b0 = serial data out. - if(is_read(operation)) { + if constexpr (is_read(operation)) { if(!address) { - *value = io_direction_; + value = io_direction_; } else { - *value = io_input(); + value = io_input(); } } else { if(!address) { - io_direction_ = *value; + io_direction_ = value; } else { - io_output_ = *value; + io_output_ = value; } const auto output = io_output_ | ~io_direction_; @@ -320,36 +317,36 @@ public: // ); // } - if( - use_fast_tape_hack_ && - operation == CPU::MOS6502Esque::BusOperation::ReadOpcode && - ( - (use_hle && address == 0xe5fd) || - address == 0xe68b || - address == 0xe68d - ) - ) { -// ++pulse_num_; - if(use_hle) { - read_dipole(); - } + if constexpr (is_read(operation)) { + if( + use_fast_tape_hack_ && + operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode && + ( + (use_hle && address == 0xe5fd) || + address == 0xe68b || + address == 0xe68d + ) + ) { + // ++pulse_num_; + if(use_hle) { + read_dipole(); + } -// using Flag = CPU::MOS6502::Flag; -// using Register = CPU::MOS6502::Register; -// const auto flags = m6502_.value_of(Register::Flags); -// printf("to %lld: %c%c%c\n", -// tape_player_->serialiser()->offset(), -// flags & Flag::Sign ? 'n' : '-', -// flags & Flag::Overflow ? 'v' : '-', -// flags & Flag::Carry ? 'c' : '-' -// ); - *value = 0x60; - } else { - if(is_read(operation)) { - *value = map_.read(address); + // using Flag = CPU::MOS6502::Flag; + // using Register = CPU::MOS6502::Register; + // const auto flags = m6502_.value_of(Register::Flags); + // printf("to %lld: %c%c%c\n", + // tape_player_->serialiser()->offset(), + // flags & Flag::Sign ? 'n' : '-', + // flags & Flag::Overflow ? 'v' : '-', + // flags & Flag::Carry ? 'c' : '-' + // ); + value = 0x60; } else { - map_.write(address) = *value; + value = map_.read(address); } + } else { + map_.write(address) = value; } @@ -390,12 +387,12 @@ public: // ram_[0x90] = 0; // ram_[0x93] = 0; // -// *value = 0x0c; // NOP abs. +// value = 0x0c; // NOP abs. // } // } } else if(address < 0xff00) { // Miscellaneous hardware. All TODO. - if(is_read(operation)) { + if constexpr (is_read(operation)) { switch(address & 0xfff0) { case 0xfd10: // 6529 parallel port, about which I know only what I've found in kernel ROM disassemblies. @@ -403,7 +400,7 @@ public: // If play button is not currently pressed and this read is immediately followed by // an AND 4, press it. The kernel will deal with motor control subsequently. if(!play_button_) { - const uint16_t pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter); + const uint16_t pc = m6502_.registers().pc.full; const uint8_t next[] = { map_.read(pc+0), map_.read(pc+1), @@ -419,22 +416,23 @@ public: } } - *value = 0xff ^ (play_button_ ? 0x4 :0x0); + value = 0xff ^ (play_button_ ? 0x4 :0x0); break; case 0xfdd0: case 0xfdf0: - *value = uint8_t(address >> 8); + value = uint8_t(address >> 8); break; default: + value = 0xff; Logger::info().append("TODO: read @ %04x", address); break; } } else { switch(address & 0xfff0) { case 0xfd30: - keyboard_mask_ = *value; + keyboard_mask_ = value; break; case 0xfdd0: { @@ -444,28 +442,28 @@ public: } break; default: - Logger::info().append("TODO: write of %02x @ %04x", *value, address); + Logger::info().append("TODO: write of %02x @ %04x", value, address); break; } } } else { - const auto pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter); + const auto pc = m6502_.registers().pc.full; const bool is_from_rom = (rom_is_paged_ && pc >= 0x8000) || (pc >= 0x400 && pc < 0x500) || (pc >= 0x700 && pc < 0x800); bool is_hit = true; - if(is_read(operation)) { + if constexpr (is_read(operation)) { switch(address) { - case 0xff00: *value = timers_.read<0>(); break; - case 0xff01: *value = timers_.read<1>(); break; - case 0xff02: *value = timers_.read<2>(); break; - case 0xff03: *value = timers_.read<3>(); break; - case 0xff04: *value = timers_.read<4>(); break; - case 0xff05: *value = timers_.read<5>(); break; - case 0xff06: *value = video_.read<0xff06>(); break; - case 0xff07: *value = video_.read<0xff07>(); break; + case 0xff00: value = timers_.read<0>(); break; + case 0xff01: value = timers_.read<1>(); break; + case 0xff02: value = timers_.read<2>(); break; + case 0xff03: value = timers_.read<3>(); break; + case 0xff04: value = timers_.read<4>(); break; + case 0xff05: value = timers_.read<5>(); break; + case 0xff06: value = video_.read<0xff06>(); break; + case 0xff07: value = video_.read<0xff07>(); break; case 0xff08: { const uint8_t keyboard_input = ~( @@ -484,50 +482,51 @@ public: ((joystick_mask_ & 0x02) ? 0xff : (joystick(0).mask() | 0x40)) & ((joystick_mask_ & 0x04) ? 0xff : (joystick(1).mask() | 0x80)); - *value = keyboard_input & joystick_mask; + value = keyboard_input & joystick_mask; } break; - case 0xff09: *value = interrupts_.status(); break; + case 0xff09: value = interrupts_.status(); break; case 0xff0a: - *value = interrupts_.mask() | video_.read<0xff0a>() | 0xa0; + value = interrupts_.mask() | video_.read<0xff0a>() | 0xa0; break; - case 0xff0b: *value = video_.read<0xff0b>(); break; - case 0xff0c: *value = video_.read<0xff0c>(); break; - case 0xff0d: *value = video_.read<0xff0d>(); break; - case 0xff0e: *value = ff0e_; break; - case 0xff0f: *value = ff0f_; break; - case 0xff10: *value = ff10_ | 0xfc; break; - case 0xff11: *value = ff11_; break; - case 0xff12: *value = ff12_ | 0xc0; break; - case 0xff13: *value = ff13_ | (rom_is_paged_ ? 1 : 0); break; - case 0xff14: *value = video_.read<0xff14>(); break; - case 0xff15: *value = video_.read<0xff15>(); break; - case 0xff16: *value = video_.read<0xff16>(); break; - case 0xff17: *value = video_.read<0xff17>(); break; - case 0xff18: *value = video_.read<0xff18>(); break; - case 0xff19: *value = video_.read<0xff19>(); break; - case 0xff1a: *value = video_.read<0xff1a>(); break; - case 0xff1b: *value = video_.read<0xff1b>(); break; - case 0xff1c: *value = video_.read<0xff1c>(); break; - case 0xff1d: *value = video_.read<0xff1d>(); break; - case 0xff1e: *value = video_.read<0xff1e>(); break; - case 0xff1f: *value = video_.read<0xff1f>(); break; + case 0xff0b: value = video_.read<0xff0b>(); break; + case 0xff0c: value = video_.read<0xff0c>(); break; + case 0xff0d: value = video_.read<0xff0d>(); break; + case 0xff0e: value = ff0e_; break; + case 0xff0f: value = ff0f_; break; + case 0xff10: value = ff10_ | 0xfc; break; + case 0xff11: value = ff11_; break; + case 0xff12: value = ff12_ | 0xc0; break; + case 0xff13: value = ff13_ | (rom_is_paged_ ? 1 : 0); break; + case 0xff14: value = video_.read<0xff14>(); break; + case 0xff15: value = video_.read<0xff15>(); break; + case 0xff16: value = video_.read<0xff16>(); break; + case 0xff17: value = video_.read<0xff17>(); break; + case 0xff18: value = video_.read<0xff18>(); break; + case 0xff19: value = video_.read<0xff19>(); break; + case 0xff1a: value = video_.read<0xff1a>(); break; + case 0xff1b: value = video_.read<0xff1b>(); break; + case 0xff1c: value = video_.read<0xff1c>(); break; + case 0xff1d: value = video_.read<0xff1d>(); break; + case 0xff1e: value = video_.read<0xff1e>(); break; + case 0xff1f: value = video_.read<0xff1f>(); break; - case 0xff3e: *value = 0; break; - case 0xff3f: *value = 0; break; + case 0xff3e: value = 0; break; + case 0xff3f: value = 0; break; default: Logger::info().append("TODO: TED read at %04x", address); + value = 0xff; is_hit = false; } } else { switch(address) { - case 0xff00: timers_.write<0>(*value); break; - case 0xff01: timers_.write<1>(*value); break; - case 0xff02: timers_.write<2>(*value); break; - case 0xff03: timers_.write<3>(*value); break; - case 0xff04: timers_.write<4>(*value); break; - case 0xff05: timers_.write<5>(*value); break; - case 0xff06: video_.write<0xff06>(*value); break; + case 0xff00: timers_.write<0>(value); break; + case 0xff01: timers_.write<1>(value); break; + case 0xff02: timers_.write<2>(value); break; + case 0xff03: timers_.write<3>(value); break; + case 0xff04: timers_.write<4>(value); break; + case 0xff05: timers_.write<5>(value); break; + case 0xff06: video_.write<0xff06>(value); break; case 0xff07: video_.write<0xff07>(*value); audio_->set_divider(*value); @@ -536,25 +535,25 @@ public: // Observation here: the kernel posts a 0 to this // address upon completing each keyboard scan cycle, // once per frame. - if(typer_ && !*value) { + if(typer_ && !value) { if(!typer_->type_next_character()) { clear_all_keys(); typer_.reset(); } } - joystick_mask_ = *value; + joystick_mask_ = value; break; case 0xff09: - interrupts_.set_status(*value); + interrupts_.set_status(value); break; case 0xff0a: - interrupts_.set_mask(*value); - video_.write<0xff0a>(*value); + interrupts_.set_mask(value); + video_.write<0xff0a>(value); break; - case 0xff0b: video_.write<0xff0b>(*value); break; - case 0xff0c: video_.write<0xff0c>(*value); break; - case 0xff0d: video_.write<0xff0d>(*value); break; + case 0xff0b: video_.write<0xff0b>(value); break; + case 0xff0c: video_.write<0xff0c>(value); break; + case 0xff0d: video_.write<0xff0d>(value); break; case 0xff0e: ff0e_ = *value; audio_->set_frequency_low<0>(*value); @@ -572,10 +571,10 @@ public: audio_->set_control(*value); break; case 0xff12: - ff12_ = *value & 0x3f; - video_.write<0xff12>(*value); + ff12_ = value & 0x3f; + video_.write<0xff12>(value); - if((*value & 4)) { + if((value & 4)) { page_video_rom(); } else { page_video_ram(); @@ -584,21 +583,21 @@ public: audio_->set_frequency_high<0>(*value); break; case 0xff13: - ff13_ = *value & 0xfe; - video_.write<0xff13>(*value); + ff13_ = value & 0xfe; + video_.write<0xff13>(value); break; - case 0xff14: video_.write<0xff14>(*value); break; - case 0xff15: video_.write<0xff15>(*value); break; - case 0xff16: video_.write<0xff16>(*value); break; - case 0xff17: video_.write<0xff17>(*value); break; - case 0xff18: video_.write<0xff18>(*value); break; - case 0xff19: video_.write<0xff19>(*value); break; - case 0xff1a: video_.write<0xff1a>(*value); break; - case 0xff1b: video_.write<0xff1b>(*value); break; - case 0xff1c: video_.write<0xff1c>(*value); break; - case 0xff1d: video_.write<0xff1d>(*value); break; - case 0xff1e: video_.write<0xff1e>(*value); break; - case 0xff1f: video_.write<0xff1f>(*value); break; + case 0xff14: video_.write<0xff14>(value); break; + case 0xff15: video_.write<0xff15>(value); break; + case 0xff16: video_.write<0xff16>(value); break; + case 0xff17: video_.write<0xff17>(value); break; + case 0xff18: video_.write<0xff18>(value); break; + case 0xff19: video_.write<0xff19>(value); break; + case 0xff1a: video_.write<0xff1a>(value); break; + case 0xff1b: video_.write<0xff1b>(value); break; + case 0xff1c: video_.write<0xff1c>(value); break; + case 0xff1d: video_.write<0xff1d>(value); break; + case 0xff1e: video_.write<0xff1e>(value); break; + case 0xff1f: video_.write<0xff1f>(value); break; case 0xff3e: page_cpu_rom(); break; case 0xff3f: page_cpu_ram(); break; @@ -617,8 +616,12 @@ public: } private: - using Processor = CPU::MOS6502::Processor; - Processor m6502_; + struct M6502Traits { + static constexpr auto uses_ready_line = true; + static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions; + using BusHandlerT = ConcreteMachine; + }; + CPU::MOS6502Mk2::Processor m6502_; Outputs::Speaker::Speaker *get_speaker() override { return &audio_.speaker(); @@ -628,11 +631,11 @@ private: if(c1541_) c1541_->set_activity_observer(observer); } - void set_irq_line(bool active) override { - m6502_.set_irq_line(active); + void set_irq_line(const bool active) override { + m6502_.template set(active); } - void set_ready_line(bool active) override { - m6502_.set_ready_line(active); + void set_ready_line(const bool active) override { + m6502_.template set(active); } void page_video_rom() { @@ -768,26 +771,19 @@ private: // TODO: substantially simplify the below; at the minute it's a // literal transcription of the original as a simple first step. void read_dipole() { - using Register = CPU::MOS6502::Register; - using Flag = CPU::MOS6502::Flag; + using Flag = CPU::MOS6502Mk2::Flag; // // Get registers now and ensure they'll be written back at function exit. // - CPU::MOS6502Esque::LazyFlags flags(uint8_t(m6502_.value_of(Register::Flags))); - uint8_t x, y, a; - uint8_t s = uint8_t(m6502_.value_of(Register::StackPointer)); + auto registers = m6502_.registers(); struct ScopeGuard { ScopeGuard(std::function at_exit) : at_exit_(at_exit) {} ~ScopeGuard() { at_exit_(); } private: std::function at_exit_; - } registers([&] { - m6502_.set_value_of(Register::Flags, flags.get()); - m6502_.set_value_of(Register::A, a); - m6502_.set_value_of(Register::X, x); - m6502_.set_value_of(Register::Y, y); - m6502_.set_value_of(Register::StackPointer, s); + } store_registers([&] { + m6502_.set_registers(registers); }); // @@ -802,38 +798,38 @@ private: // 6502 pseudo-ops. // const auto ldabs = [&] (uint8_t &target, const uint16_t address) { - flags.set_nz(target = map_.read(address)); + registers.flags.set_per(target = map_.read(address)); }; const auto ldimm = [&] (uint8_t &target, const uint8_t value) { - flags.set_nz(target = value); + registers.flags.set_per(target = value); }; const auto pha = [&] () { - map_.write(0x100 + s) = a; - --s; + map_.write(0x100 + registers.s) = registers.a; + --registers.s; }; const auto pla = [&] () { - ++s; - a = map_.read(0x100 + s); + ++registers.s; + registers.a = map_.read(0x100 + registers.s); }; const auto bit = [&] (const uint8_t value) { - flags.zero_result = a & value; - flags.negative_result = value; - flags.overflow = value & CPU::MOS6502Esque::Flag::Overflow; + registers.flags.set_per(registers.a & value); + registers.flags.set_per(value); + registers.flags.set_per(value); }; const auto cmp = [&] (const uint8_t value) { - const uint16_t temp16 = a - value; - flags.set_nz(uint8_t(temp16)); - flags.carry = ((~temp16) >> 8)&1; + const uint16_t temp16 = registers.a - value; + registers.flags.set_per(uint8_t(temp16)); + registers.flags.set_per(((~temp16) >> 8)&1); }; const auto andimm = [&] (const uint8_t value) { - a &= value; - flags.set_nz(a); + registers.a &= value; + registers.flags.set_per(registers.a); }; const auto ne = [&]() -> bool { - return flags.zero_result; + return !registers.flags.get(); }; const auto eq = [&]() -> bool { - return !flags.zero_result; + return registers.flags.get(); }; // @@ -842,7 +838,7 @@ private: const auto dipok = [&] { // clc ; everything's fine // rts - flags.carry = 0; + registers.flags.set_per(0); }; const auto rshort = [&] { // bit tshrtd ; got a short @@ -858,7 +854,7 @@ private: const auto rderr1 = [&] { // sec ; i'm confused // rts - flags.carry = Flag::Carry; + registers.flags.set_per(Flag::Carry); }; // @@ -871,8 +867,8 @@ private: //rddipl // ldx dsamp1 ; setup x,y with 1st sample point // ldy dsamp1+1 - ldabs(x, dsamp1); - ldabs(y, dsamp1 + 1); + ldabs(registers.x, dsamp1); + ldabs(registers.y, dsamp1 + 1); advance_cycles(8); //badeg1 @@ -881,9 +877,9 @@ private: // pha // lda dsamp2 // pha - ldabs(a, dsamp2 + 1); + ldabs(registers.a, dsamp2 + 1); pha(); - ldabs(a, dsamp2); + ldabs(registers.a, dsamp2); pha(); advance_cycles(14); @@ -891,7 +887,7 @@ private: //rwtl ; wait till rd line is high // bit port [= $0001] // beq rwtl ; !ls! - ldimm(a, 0x10); + ldimm(registers.a, 0x10); advance_cycles(2); do { bit(io_input()); @@ -913,8 +909,8 @@ private: // stx timr2l // sty timr2h - timers_.write<2>(x); - timers_.write<3>(y); + timers_.write<2>(registers.x); + timers_.write<3>(registers.y); advance_cycles(8); @@ -925,9 +921,9 @@ private: // pla // sta timr3h ;go! ...tb pla(); - timers_.write<4>(a); + timers_.write<4>(registers.a); pla(); - timers_.write<5>(a); + timers_.write<5>(registers.a); advance_cycles(14); @@ -935,8 +931,8 @@ private: // // lda #$50 ; clr ta,tb // sta tedirq - ldimm(a, 0x50); - interrupts_.set_status(a); + ldimm(registers.a, 0x50); + interrupts_.set_status(registers.a); advance_cycles(6); @@ -949,7 +945,7 @@ private: // and #$10 ; a look at that edge again // bne badeg1 ; woa! got a bad edge trigger !ls! do { - ldimm(a, io_input()); + ldimm(registers.a, io_input()); cmp(io_input()); if(advance_cycles(9)) { return; @@ -972,7 +968,7 @@ private: // lda #$10 //wata ; wait for ta to timeout - ldimm(a, 0x10); + ldimm(registers.a, 0x10); advance_cycles(3); do { // bit port ; kuldge, kludge, kludge !!! <<><>> @@ -1000,7 +996,7 @@ private: do { // lda port // cmp port - ldimm(a, io_input()); + ldimm(registers.a, io_input()); cmp(io_input()); if(advance_cycles(9)) { @@ -1028,7 +1024,7 @@ private: // //; wait for tb to timeout //; now do the dipole sample #2 - ldimm(a, 0x40); + ldimm(registers.a, 0x40); advance_cycles(3); do { bit(interrupts_.status()); @@ -1043,7 +1039,7 @@ private: // cmp port // bne casdb3 do { - ldimm(a, io_input()); + ldimm(registers.a, io_input()); cmp(io_input()); if(advance_cycles(9)) { return; @@ -1064,10 +1060,10 @@ private: // sta timr2l // lda zcell+1 // sta timr2h - ldabs(a, zcell); - timers_.write<2>(a); - ldabs(a, zcell + 1); - timers_.write<3>(y); + ldabs(registers.a, zcell); + timers_.write<2>(registers.a); + ldabs(registers.a, zcell + 1); + timers_.write<3>(registers.y); advance_cycles(16); @@ -1076,9 +1072,9 @@ private: // lda #$10 // sta tedirq ; verify +180 half of word dipole // lda #$10 - ldimm(a, 0x10); - interrupts_.set_status(a); - ldimm(a, 0x10); + ldimm(registers.a, 0x10); + interrupts_.set_status(registers.a); + ldimm(registers.a, 0x10); advance_cycles(8); //wata2 @@ -1096,7 +1092,7 @@ private: // cmp port // bne casdb4 do { - ldimm(a, io_input()); + ldimm(registers.a, io_input()); cmp(io_input()); if(advance_cycles(9)) { return; diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index ea858f5f2..e9452d6d8 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -13,7 +13,7 @@ #include "Activity/Source.hpp" #include "Machines/MachineTypes.hpp" -#include "Processors/6502/6502.hpp" +#include "Processors/6502Mk2/6502Mk2.hpp" #include "Components/6560/6560.hpp" #include "Components/6522/6522.hpp" @@ -72,26 +72,30 @@ public: // Port A provides information about the presence or absence of a tape, and parts of // the joystick and serial port state, both of which have been statefully collected // into port_a_. - if(!port) { + if constexpr (port == MOS::MOS6522::Port::A) { return port_a_ | (tape_->has_tape() ? 0x00 : 0x40); } return 0xff; } /// Receives announcements of control line output change from the 6522. - template void set_control_line_output(const bool value) { - // The CA2 output is used to control the tape motor. - if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) { + template + void set_control_line_output(const bool value) { + // CA2: control the tape motor. + if constexpr (port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) { tape_->set_motor_control(!value); } } /// Receives announcements of changes in the serial bus connected to the serial port and propagates them into Port A. - void set_serial_line_state(Commodore::Serial::Line line, const bool value) { + void set_serial_line_state(const Commodore::Serial::Line line, const bool value) { + const auto set = [&](const uint8_t bit) { + port_a_ = (port_a_ & ~bit) | (value ? bit : 0x00); + }; switch(line) { default: break; - case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break; - case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break; + case ::Commodore::Serial::Line::Data: set(0x02); break; + case ::Commodore::Serial::Line::Clock: set(0x01); break; } } @@ -146,7 +150,7 @@ public: /// Sets all keys as unpressed. void clear_all_keys() { - memset(columns_, 0xff, sizeof(columns_)); + std::fill(std::begin(columns_), std::end(columns_), 0xff); } /// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B. @@ -230,8 +234,6 @@ struct Vic6560BusHandler { // It is assumed that these pointers have been filled in by the machine. const uint8_t *video_memory_map[16]{}; // Segments video memory into 1kb portions. const uint8_t *colour_memory{}; // Colour memory must be contiguous. - - // TODO: make the above const. }; /*! @@ -280,7 +282,6 @@ class ConcreteMachine: public MachineTypes::MappedKeyboardMachine, public MachineTypes::JoystickMachine, public Configurable::Device, - public CPU::MOS6502::BusHandler, public MOS::MOS6522::IRQDelegatePortHandler::Delegate, public Utility::TypeRecipient, public Storage::Tape::BinaryTapePlayer::Delegate, @@ -455,7 +456,7 @@ public: } void set_key_state(const uint16_t key, const bool is_pressed) final { - if(key < 0xfff0) { + if(key < KeyUp) { keyboard_via_port_handler_.set_key_state(key, is_pressed); } else { switch(key) { @@ -481,28 +482,27 @@ public: void clear_all_keys() final { keyboard_via_port_handler_.clear_all_keys(); + set_key_state(KeyRestore, false); } const std::vector> &get_joysticks() final { return joysticks_; } - // to satisfy CPU::MOS6502::Processor - forceinline Cycles perform_bus_operation( - const CPU::MOS6502::BusOperation operation, - const uint16_t address, - uint8_t *const value - ) { + template + Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t value) { // Tun the phase-1 part of this cycle, in which the VIC accesses memory. cycles_since_mos6560_update_++; // Run the phase-2 part of the cycle, which is whatever the 6502 said it should be. - const bool is_from_rom = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter) > 0x8000; - if(is_read(operation)) { + const auto is_from_rom = [&]() { + return m6502_.registers().pc.full > 0x8000; + }; + if constexpr (is_read(operation)) { const auto page = processor_read_memory_map_[address >> 10]; uint8_t result; if(!page) { - if(!is_from_rom) confidence_.add_miss(); + if(!is_from_rom()) confidence_.add_miss(); result = 0xff; } else { result = processor_read_memory_map_[address >> 10][address & 0x3ff]; @@ -515,7 +515,7 @@ public: if(address & 0x10) result &= user_port_via_.read(address); if(address & 0x20) result &= keyboard_via_.read(address); - if(!is_from_rom) { + if(!is_from_rom()) { if((address & 0x100) && !(address & 0x30)) { confidence_.add_miss(); } else { @@ -523,10 +523,10 @@ public: } } } - *value = result; + value = result; // Consider applying the fast tape hack. - if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) { + if(use_fast_tape_hack_ && operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode) { if(address == 0xf7b2) { // Address 0xf7b2 contains a JSR to 0xf8c0 ('RDTPBLKS') that will fill the tape buffer with the // next header. Skip that via a three-byte NOP and fill in the next header programmatically. @@ -551,10 +551,10 @@ public: ram_[0x90] = 0; ram_[0x93] = 0; - *value = 0x0c; // i.e. NOP abs, to swallow the entire JSR + value = 0x0c; // i.e. NOP abs, to swallow the entire JSR } else if(address == 0xf90b) { - const auto x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X)); - if(x == 0xe) { + auto registers = m6502_.registers(); + if(registers.x == 0xe) { Storage::Tape::Commodore::Parser parser(TargetPlatform::Vic20); const auto tape_position = tape_->serialiser()->offset(); const std::unique_ptr data = parser.get_next_data(*tape_->serialiser()); @@ -576,14 +576,13 @@ public: // set tape status, carry and flag ram_[0x90] |= 0x40; - uint8_t flags = uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags)); - flags &= ~uint8_t((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt)); - m6502_.set_value_of(CPU::MOS6502::Register::Flags, flags); + registers.flags.set_per(0); + registers.flags.set_per(0); // to ensure that execution proceeds to 0xfccf, pretend a NOP was here and // ensure that the PC leaps to 0xfccf - m6502_.set_value_of(CPU::MOS6502::Register::ProgramCounter, 0xfccf); - *value = 0xea; // i.e. NOP implied + registers.pc.full = 0xfccf; + value = 0xea; // i.e. NOP implied hold_tape_ = true; Logger::info().append("Found data"); } else { @@ -592,27 +591,28 @@ public: Logger::info().append("Didn't find data"); } } + m6502_.set_registers(registers); } } } else { uint8_t *const ram = processor_write_memory_map_[address >> 10]; if(ram) { update_video(); - ram[address & 0x3ff] = *value; + ram[address & 0x3ff] = value; } // Anything between 0x9000 and 0x9400 is the IO area. if((address&0xfc00) == 0x9000) { // The VIC is selected by bit 8 = 0 if(!(address&0x100)) { update_video(); - mos6560_.write(address, *value); + mos6560_.write(address, value); } // The first VIA is selected by bit 4 = 1. - if(address & 0x10) user_port_via_.write(address, *value); + if(address & 0x10) user_port_via_.write(address, value); // The second VIA is selected by bit 5 = 1. - if(address & 0x20) keyboard_via_.write(address, *value); + if(address & 0x20) keyboard_via_.write(address, value); - if(!is_from_rom) { + if(!is_from_rom()) { if((address & 0x100) && !(address & 0x30)) { confidence_.add_miss(); } else { @@ -620,13 +620,13 @@ public: } } } else if(!ram) { - if(!is_from_rom) confidence_.add_miss(); + if(!is_from_rom()) confidence_.add_miss(); } } user_port_via_.run_for(Cycles(1)); keyboard_via_.run_for(Cycles(1)); - if(typer_ && address == 0xeb1e && operation == CPU::MOS6502::BusOperation::ReadOpcode) { + if(typer_ && address == 0xeb1e && operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode) { if(!typer_->type_next_character()) { clear_all_keys(); typer_.reset(); @@ -672,8 +672,8 @@ public: } void mos6522_did_change_interrupt_status(void *) final { - m6502_.set_nmi_line(user_port_via_.get_interrupt_line()); - m6502_.set_irq_line(keyboard_via_.get_interrupt_line()); + m6502_.template set(user_port_via_.get_interrupt_line()); + m6502_.template set(keyboard_via_.get_interrupt_line()); } void type_string(const std::string &string) final { @@ -718,10 +718,16 @@ public: } private: + struct M6502Traits { + static constexpr auto uses_ready_line = false; + static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions; + using BusHandlerT = ConcreteMachine; + }; + CPU::MOS6502Mk2::Processor m6502_; + void update_video() { mos6560_.run_for(cycles_since_mos6560_update_.flush()); } - CPU::MOS6502::Processor m6502_; std::vector character_rom_; std::vector basic_rom_; @@ -745,12 +751,22 @@ private: ++address; } } - void write_to_map(const uint8_t **const map, const uint8_t *area, uint16_t address, size_t length) { + void write_to_map( + const uint8_t **const map, + const uint8_t *const area, + const uint16_t address, + const size_t length + ) { write_to_map([&](const uint16_t address, const size_t offset) { map[address] = &area[offset]; }, address, length); } - void write_to_map(uint8_t **const map, uint8_t *area, uint16_t address, size_t length) { + void write_to_map( + uint8_t **const map, + uint8_t *const area, + const uint16_t address, + const size_t length + ) { write_to_map([&](const uint16_t address, const size_t offset) { map[address] = &area[offset]; }, address, length); diff --git a/OSBindings/Mac/Clock SignalTests/SIDTests.mm b/OSBindings/Mac/Clock SignalTests/SIDTests.mm new file mode 100644 index 000000000..b6f6942e3 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/SIDTests.mm @@ -0,0 +1,83 @@ +// +// SIDTests.mm +// Clock SignalTests +// +// Created by Thomas Harte on 11/11/2025. +// Copyright © 2025 Thomas Harte. All rights reserved. +// + +#import + +#include "Components/SID/SID.hpp" + +@interface SIDTests : XCTestCase +@end + +@implementation SIDTests + +- (void)testOscillator { + MOS::SID::Voice prior; + MOS::SID::Voice voice; + const uint32_t pulse_width = 0x02'3; + + voice.oscillator.pitch = 0x00'1000'00; + voice.oscillator.pulse_width = pulse_width << 20; + voice.oscillator.reset_phase(); + + int c = 0; + + // Run for first half of a cycle. + while(!voice.oscillator.did_raise_b23()) { + // Force envelope. + voice.adsr.envelope = 255; + + // Test sawtooth. + voice.set_control(0x20); + XCTAssertEqual(voice.output(prior), c); + + // Test triangle. + voice.set_control(0x10); + XCTAssertEqual(voice.output(prior), c << 1); + + // Test pulse. + voice.set_control(0x40); + XCTAssertEqual(voice.output(prior), (c < pulse_width) ? 0 : 4095); + + // Advance. + voice.update(); + ++c; + } + + // B23 should go up halfway through the 12-bit range. + XCTAssertEqual(c, 2048); + + // Run for second half of a cycle. + while(c < 4096) { + // Force envelope. + voice.adsr.envelope = 255; + + // Test sawtooth. + voice.set_control(0x20); + XCTAssertEqual(voice.output(prior), c); + + // Test triangle. + voice.set_control(0x10); + XCTAssertEqual(voice.output(prior), 4095 - ((c << 1) & 4095)); + + // Test pulse. + voice.set_control(0x40); + XCTAssertEqual(voice.output(prior), (c <= pulse_width) ? 0 : 4095); + + // Advance. + voice.update(); + ++c; + + XCTAssert(!voice.oscillator.did_raise_b23()); + } + + // Check that B23 doesn't false rise again. + voice.update(); + XCTAssert(!voice.oscillator.did_raise_b23()); +} + +@end diff --git a/Processors/6502Mk2/6502Mk2.hpp b/Processors/6502Mk2/6502Mk2.hpp index bd4388347..888f5d505 100644 --- a/Processors/6502Mk2/6502Mk2.hpp +++ b/Processors/6502Mk2/6502Mk2.hpp @@ -66,6 +66,7 @@ enum class Line { PowerOn, Overflow, NMI, + Ready, }; // MARK: - Address bus. @@ -185,14 +186,24 @@ public: (inputs_.interrupt_requests & Inputs::InterruptRequest::PowerOn); break; - // Level triggered. + // Level triggered interrupts. case Line::Reset: level_sample(Inputs::InterruptRequest::Reset); break; case Line::IRQ: level_sample(Inputs::InterruptRequest::IRQ); break; - // Edge triggered. - case Line::Overflow: edge_sample(Inputs::InterruptRequest::Reset, inputs_.overflow); break; + // Edge triggered interrupts. case Line::NMI: edge_sample(Inputs::InterruptRequest::NMI, inputs_.nmi); break; + // Leval-capturing state. + case Line::Ready: inputs_.ready = value; break; + + // Edge-triggered state. + case Line::Overflow: + if(!inputs_.overflow && value) { + registers_.flags.set_per(Flag::Overflow); + } + inputs_.overflow = value; + break; + default: __builtin_unreachable(); }