// // Commodore1540.cpp // Clock Signal // // Created by Thomas Harte on 05/07/2016. // Copyright 2016 Thomas Harte. All rights reserved. // #include "Machines/Commodore/1540/C1540.hpp" #include #include #include #include "Storage/Disk/Encodings/CommodoreGCR.hpp" using namespace Commodore::C1540; namespace { ROM::Name rom_name(Personality personality) { switch(personality) { default: case Personality::C1540: return ROM::Name::Commodore1540; case Personality::C1541: return ROM::Name::Commodore1541; } } } ROM::Request Machine::rom_request(Personality personality) { return ROM::Request(rom_name(personality)); } MachineBase::MachineBase(Personality personality, const ROM::Map &roms) : Storage::Disk::Controller(1000000), m6502_(*this), drive_VIA_(drive_VIA_port_handler_), serial_port_VIA_(serial_port_VIA_port_handler_) { // Attach the serial port to its VIA and vice versa. serial_port_.connect(serial_port_VIA_port_handler_, serial_port_VIA_); serial_port_VIA_port_handler_.set_serial_port(serial_port_); // Set this instance as the delegate to receive interrupt requests from both VIAs. serial_port_VIA_port_handler_.set_interrupt_delegate(this); drive_VIA_port_handler_.set_interrupt_delegate(this); drive_VIA_port_handler_.set_delegate(this); // Set a bit rate. set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3)); // Attach the only drive there is. emplace_drive(1000000, 300, 2); set_drive(1); const auto rom = roms.find(rom_name(personality)); if(rom == roms.end()) { throw ROMMachine::Error::MissingROMs; } std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size())); } Machine::Machine(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); } Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { /* Memory map (given that I'm unsure yet on any potential mirroring): 0x0000-0x07ff RAM 0x1800-0x180f the serial-port VIA 0x1c00-0x1c0f the drive VIA 0xc000-0xffff ROM */ if(address < 0x800) { if(is_read(operation)) *value = ram_[address]; else ram_[address] = *value; } else if(address >= 0xc000) { if(is_read(operation)) { *value = rom_[address & 0x3fff]; } } else if(address >= 0x1800 && address <= 0x180f) { if(is_read(operation)) *value = serial_port_VIA_.read(address); else serial_port_VIA_.write(address, *value); } else if(address >= 0x1c00 && address <= 0x1c0f) { if(is_read(operation)) *value = drive_VIA_.read(address); else drive_VIA_.write(address, *value); } serial_port_VIA_.run_for(Cycles(1)); drive_VIA_.run_for(Cycles(1)); 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(); get_drive().set_motor_on(drive_motor); if(drive_motor) Storage::Disk::Controller::run_for(cycles); } void MachineBase::set_activity_observer(Activity::Observer *observer) { drive_VIA_.bus_handler().set_activity_observer(observer); get_drive().set_activity_observer(observer, "Drive", false); } // 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()); } // MARK: - Disk drive void MachineBase::process_input_bit(int value) { shift_register_ = (shift_register_ << 1) | value; if((shift_register_ & 0x3ff) == 0x3ff) { drive_VIA_port_handler_.set_sync_detected(true); bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be } else { drive_VIA_port_handler_.set_sync_detected(false); } bit_window_offset_++; 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); } } else m6502_.set_overflow_line(false); } // the 1540 does not recognise index holes void MachineBase::process_index_hole() {} // MARK: - Drive VIA delegate void MachineBase::drive_via_did_step_head(DriveVIA &, const int direction) { get_drive().step(Storage::Disk::HeadPosition(direction, 2)); } void MachineBase::drive_via_did_set_data_density(DriveVIA &, const int density) { set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned(density))); } // MARK: - SerialPortVIA template uint8_t SerialPortVIA::get_port_input() const { if(port) return port_b_; return 0xff; } template void SerialPortVIA::set_port_output(const uint8_t value, uint8_t) { if(port) { attention_acknowledge_level_ = !(value&0x10); data_level_output_ = (value&0x02); serial_port_->set_output(::Commodore::Serial::Line::Clock, ::Commodore::Serial::LineLevel(!(value&0x08))); update_data_line(); } } void SerialPortVIA::set_serial_line_state( const Commodore::Serial::Line line, const bool value, MOS::MOS6522::MOS6522 &via ) { switch(line) { default: break; case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break; case ::Commodore::Serial::Line::Clock: port_b_ = (port_b_ & ~0x04) | (value ? 0x00 : 0x04); break; case ::Commodore::Serial::Line::Attention: attention_level_input_ = !value; port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80); via.set_control_line_input(!value); update_data_line(); break; } } void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) { 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_))); } // MARK: - DriveVIA void DriveVIA::set_delegate(Delegate *delegate) { delegate_ = delegate; } // write protect tab uncovered template uint8_t DriveVIA::get_port_input() const { return port ? port_b_ : port_a_; } void DriveVIA::set_sync_detected(const bool sync_detected) { port_b_ = (port_b_ & 0x7f) | (sync_detected ? 0x00 : 0x80); } void DriveVIA::set_data_input(uint8_t value) { port_a_ = value; } bool DriveVIA::get_should_set_overflow() { return should_set_overflow_; } bool DriveVIA::get_motor_enabled() { return drive_motor_; } template 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; } } } void DriveVIA::set_activity_observer(Activity::Observer *observer) { observer_ = observer; if(observer) { observer->register_led("Drive"); observer->set_led_status("Drive", previous_port_b_output_&8); } } // MARK: - SerialPort void SerialPort::set_input(Serial::Line line, Serial::LineLevel level) { serial_port_via_->set_serial_line_state(line, bool(level), *via_); } void SerialPort::connect(SerialPortVIA &serial_port_via, MOS::MOS6522::MOS6522 &via) { serial_port_via_ = &serial_port_via; via_ = &via; }