From 4c15e46fd1f036c9c1fdbc2d594ba799c1c417af Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Aug 2017 16:05:30 -0400 Subject: [PATCH 1/2] Performed the normative removal from public view of Vic-20 implementation details. Which were hefty. --- Machines/Commodore/Vic-20/Vic20.cpp | 982 ++++++++++-------- Machines/Commodore/Vic-20/Vic20.hpp | 164 +-- .../Clock Signal/Machine/Wrappers/CSVic20.mm | 64 +- 3 files changed, 591 insertions(+), 619 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 794310ba2..00853bd51 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -8,442 +8,558 @@ #include "Vic20.hpp" -#include -#include "../../../Storage/Tape/Formats/TapePRG.hpp" -#include "../../../Storage/Tape/Parsers/Commodore.hpp" -#include "../../../StaticAnalyser/StaticAnalyser.hpp" #include "CharacterMapper.hpp" +#include "../../../Processors/6502/6502.hpp" +#include "../../../Components/6560/6560.hpp" +#include "../../../Components/6522/6522.hpp" + +#include "../../../Storage/Tape/Parsers/Commodore.hpp" + +#include "../SerialBus.hpp" +#include "../1540/C1540.hpp" + +#include "../../../Storage/Tape/Tape.hpp" +#include "../../../Storage/Disk/Disk.hpp" + +#include + +namespace Commodore { +namespace Vic20 { + +class UserPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { + public: + UserPortVIA() : port_a_(0xbf) {} + using MOS6522IRQDelegate::set_interrupt_status; + + uint8_t get_port_input(Port port) { + if(!port) { + return port_a_ | (tape_->has_tape() ? 0x00 : 0x40); + } + return 0xff; + } + + void set_control_line_output(Port port, Line line, bool value) { + if(port == Port::A && line == Line::Two) { + tape_->set_motor_control(!value); + } + } + + void set_serial_line_state(::Commodore::Serial::Line line, bool value) { + 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; + } + } + + void set_joystick_state(JoystickInput input, bool value) { + if(input != JoystickInput::Right) { + port_a_ = (port_a_ & ~input) | (value ? 0 : input); + } + } + + void set_port_output(Port port, uint8_t value, uint8_t mask) { + // Line 7 of port A is inverted and output as serial ATN + if(!port) { + std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); + if(serialPort) + serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80)); + } + } + + void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { + serial_port_ = serialPort; + } + + void set_tape(std::shared_ptr tape) { + tape_ = tape; + } + + private: + uint8_t port_a_; + std::weak_ptr<::Commodore::Serial::Port> serial_port_; + std::shared_ptr tape_; +}; + +class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { + public: + KeyboardVIA() : port_b_(0xff) { + clear_all_keys(); + } + + using MOS6522IRQDelegate::set_interrupt_status; + + void set_key_state(uint16_t key, bool isPressed) { + if(isPressed) + columns_[key & 7] &= ~(key >> 3); + else + columns_[key & 7] |= (key >> 3); + } + + void clear_all_keys() { + memset(columns_, 0xff, sizeof(columns_)); + } + + uint8_t get_port_input(Port port) { + if(!port) { + uint8_t result = 0xff; + for(int c = 0; c < 8; c++) { + if(!(activation_mask_&(1 << c))) + result &= columns_[c]; + } + return result; + } + + return port_b_; + } + + void set_port_output(Port port, uint8_t value, uint8_t mask) { + if(port) + activation_mask_ = (value & mask) | (~mask); + } + + void set_control_line_output(Port port, Line line, bool value) { + if(line == Line::Two) { + std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); + if(serialPort) { + // CB2 is inverted to become serial data; CA2 is inverted to become serial clock + if(port == Port::A) + serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value); + else + serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value); + } + } + } + + void set_joystick_state(JoystickInput input, bool value) { + if(input == JoystickInput::Right) { + port_b_ = (port_b_ & ~input) | (value ? 0 : input); + } + } + + void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { + serial_port_ = serialPort; + } + + private: + uint8_t port_b_; + uint8_t columns_[8]; + uint8_t activation_mask_; + std::weak_ptr<::Commodore::Serial::Port> serial_port_; +}; + +class SerialPort : public ::Commodore::Serial::Port { + public: + void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { + std::shared_ptr userPortVIA = user_port_via_.lock(); + if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level); + } + + void set_user_port_via(std::shared_ptr userPortVIA) { + user_port_via_ = userPortVIA; + } + + private: + std::weak_ptr user_port_via_; +}; + +class Vic6560: public MOS::MOS6560 { + public: + inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) { + *pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO + *colour_data = colour_memory[address & 0x03ff]; + } + + uint8_t *video_memory_map[16]; + uint8_t *colour_memory; +}; + +class ConcreteMachine: + public CPU::MOS6502::BusHandler, + public MOS::MOS6522IRQDelegate::Delegate, + public Utility::TypeRecipient, + public Storage::Tape::BinaryTapePlayer::Delegate, + public Machine { + public: + ConcreteMachine() : + m6502_(*this), + rom_(nullptr), + is_running_at_zero_cost_(false), + tape_(new Storage::Tape::BinaryTapePlayer(1022727)), + user_port_via_(new UserPortVIA), + keyboard_via_(new KeyboardVIA), + serial_port_(new SerialPort), + serial_bus_(new ::Commodore::Serial::Bus) { + // communicate the tape to the user-port VIA + user_port_via_->set_tape(tape_); + + // wire up the serial bus and serial port + Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_); + + // wire up 6522s and serial port + user_port_via_->set_serial_port(serial_port_); + keyboard_via_->set_serial_port(serial_port_); + serial_port_->set_user_port_via(user_port_via_); + + // wire up the 6522s, tape and machine + user_port_via_->set_interrupt_delegate(this); + keyboard_via_->set_interrupt_delegate(this); + tape_->set_delegate(this); + + // establish the memory maps + set_memory_size(MemorySize::Default); + + // set the NTSC clock rate + set_region(NTSC); + // _debugPort.reset(new ::Commodore::Serial::DebugPort); + // _debugPort->set_serial_bus(serial_bus_); + // serial_bus_->add_port(_debugPort); + } + + ~ConcreteMachine() { + delete[] rom_; + } + + void set_rom(ROMSlot slot, size_t length, const uint8_t *data) { + uint8_t *target = nullptr; + size_t max_length = 0x2000; + switch(slot) { + case Kernel: target = kernel_rom_; break; + case Characters: target = character_rom_; max_length = 0x1000; break; + case BASIC: target = basic_rom_; break; + case Drive: + drive_rom_.resize(length); + memcpy(drive_rom_.data(), data, length); + install_disk_rom(); + return; + } + + if(target) { + size_t length_to_copy = std::min(max_length, length); + memcpy(target, data, length_to_copy); + } + } + + void configure_as_target(const StaticAnalyser::Target &target) { + if(target.tapes.size()) { + tape_->set_tape(target.tapes.front()); + } + + if(target.disks.size()) { + // construct the 1540 + c1540_.reset(new ::Commodore::C1540::Machine); + + // attach it to the serial bus + c1540_->set_serial_bus(serial_bus_); + + // hand it the disk + c1540_->set_disk(target.disks.front()); + + // install the ROM if it was previously set + install_disk_rom(); + } + + if(target.cartridges.size()) { + rom_address_ = 0xa000; + std::vector rom_image = target.cartridges.front()->get_segments().front().data; + rom_length_ = (uint16_t)(rom_image.size()); + + rom_ = new uint8_t[0x2000]; + memcpy(rom_, rom_image.data(), rom_image.size()); + write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000); + } + + if(target.loadingCommand.length()) { + set_typer_for_string(target.loadingCommand.c_str()); + } + + switch(target.vic20.memory_model) { + case StaticAnalyser::Vic20MemoryModel::Unexpanded: + set_memory_size(Default); + break; + case StaticAnalyser::Vic20MemoryModel::EightKB: + set_memory_size(ThreeKB); + break; + case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB: + set_memory_size(ThirtyTwoKB); + break; + } + } + + void set_key_state(uint16_t key, bool isPressed) { + keyboard_via_->set_key_state(key, isPressed); + } + + void clear_all_keys() { + keyboard_via_->clear_all_keys(); + } + + void set_joystick_state(JoystickInput input, bool isPressed) { + user_port_via_->set_joystick_state(input, isPressed); + keyboard_via_->set_joystick_state(input, isPressed); + } + + void set_memory_size(MemorySize size) { + memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_)); + memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); + + switch(size) { + default: break; + case ThreeKB: + write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000); + write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x1000); + break; + case ThirtyTwoKB: + write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x8000); + write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x8000); + break; + } + + // install the system ROMs and VIC-visible memory + write_to_map(processor_read_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); + write_to_map(processor_read_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); + write_to_map(processor_read_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); + write_to_map(processor_read_memory_map_, character_rom_, 0x8000, sizeof(character_rom_)); + write_to_map(processor_read_memory_map_, basic_rom_, 0xc000, sizeof(basic_rom_)); + write_to_map(processor_read_memory_map_, kernel_rom_, 0xe000, sizeof(kernel_rom_)); + + write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); + write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); + write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); + + // install the inserted ROM if there is one + if(rom_) { + write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_); + } + } + + void set_region(Region region) { + region_ = region; + switch(region) { + case PAL: + set_clock_rate(1108404); + if(mos6560_) { + mos6560_->set_output_mode(MOS::MOS6560::OutputMode::PAL); + mos6560_->set_clock_rate(1108404); + } + break; + case NTSC: + set_clock_rate(1022727); + if(mos6560_) { + mos6560_->set_output_mode(MOS::MOS6560::OutputMode::NTSC); + mos6560_->set_clock_rate(1022727); + } + break; + } + } + + void set_use_fast_tape_hack(bool activate) { + use_fast_tape_hack_ = activate; + } + + // to satisfy CPU::MOS6502::Processor + Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { + // run the phase-1 part of this cycle, in which the VIC accesses memory + if(!is_running_at_zero_cost_) mos6560_->run_for(Cycles(1)); + + // run the phase-2 part of the cycle, which is whatever the 6502 said it should be + if(isReadOperation(operation)) { + uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff; + if((address&0xfc00) == 0x9000) { + if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address); + if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address); + if((address&0xfc20) == 0x9020) result &= keyboard_via_->get_register(address); + } + *value = result; + + // This combined with the stuff below constitutes the fast tape hack. Performed here: if the + // PC hits the start of the loop that just waits for an interesting tape interrupt to have + // occurred then skip both 6522s and the tape ahead to the next interrupt without any further + // CPU or 6560 costs. + if(use_fast_tape_hack_ && tape_->has_tape() && operation == CPU::MOS6502::BusOperation::ReadOpcode) { + if(address == 0xf7b2) { + // Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header. + // So cancel that via a double NOP and fill in the next header programmatically. + Storage::Tape::Commodore::Parser parser; + std::unique_ptr header = parser.get_next_header(tape_->get_tape()); + + // serialise to wherever b2:b3 points + uint16_t tape_buffer_pointer = (uint16_t)user_basic_memory_[0xb2] | (uint16_t)(user_basic_memory_[0xb3] << 8); + if(header) { + header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer); + } else { + // no header found, so store end-of-tape + user_basic_memory_[tape_buffer_pointer] = 0x05; // i.e. end of tape + } + + // clear status and the verify flag + user_basic_memory_[0x90] = 0; + user_basic_memory_[0x93] = 0; + + *value = 0x0c; // i.e. NOP abs + } else if(address == 0xf90b) { + uint8_t x = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::X); + if(x == 0xe) { + Storage::Tape::Commodore::Parser parser; + std::unique_ptr data = parser.get_next_data(tape_->get_tape()); + uint16_t start_address, end_address; + start_address = (uint16_t)(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8)); + end_address = (uint16_t)(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8)); + + // perform a via-processor_write_memory_map_ memcpy + uint8_t *data_ptr = data->data.data(); + size_t data_left = data->data.size(); + while(data_left && start_address != end_address) { + uint8_t *page = processor_write_memory_map_[start_address >> 10]; + if(page) page[start_address & 0x3ff] = *data_ptr; + data_ptr++; + start_address++; + data_left--; + } + + // set tape status, carry and flag + user_basic_memory_[0x90] |= 0x40; + uint8_t flags = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::Flags); + flags &= ~(uint8_t)(CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt); + m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags); + + // to ensure that execution proceeds to 0xfccf, pretend a NOP was here and + // ensure that the PC leaps to 0xfccf + m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf); + *value = 0xea; // i.e. NOP implied + } + } + } + } else { + uint8_t *ram = processor_write_memory_map_[address >> 10]; + if(ram) ram[address & 0x3ff] = *value; + if((address&0xfc00) == 0x9000) { + if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value); + if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value); + if((address&0xfc20) == 0x9020) keyboard_via_->set_register(address, *value); + } + } + + user_port_via_->run_for(Cycles(1)); + keyboard_via_->run_for(Cycles(1)); + if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) { + if(!typer_->type_next_character()) { + clear_all_keys(); + typer_.reset(); + } + } + tape_->run_for(Cycles(1)); + if(c1540_) c1540_->run_for(Cycles(1)); + + return Cycles(1); + } + + void flush() { + mos6560_->flush(); + } + + void run_for(const Cycles cycles) { + m6502_.run_for(cycles); + } + + void setup_output(float aspect_ratio) { + mos6560_.reset(new Vic6560()); + mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20. + set_region(region_); + + memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map)); + write_to_map(mos6560_->video_memory_map, character_rom_, 0x0000, sizeof(character_rom_)); + write_to_map(mos6560_->video_memory_map, user_basic_memory_, 0x2000, sizeof(user_basic_memory_)); + write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_)); + mos6560_->colour_memory = colour_memory_; + } + + void close_output() { + mos6560_ = nullptr; + } + + std::shared_ptr get_crt() { + return mos6560_->get_crt(); + } + + std::shared_ptr get_speaker() { + return mos6560_->get_speaker(); + } + + void mos6522_did_change_interrupt_status(void *mos6522) { + m6502_.set_nmi_line(user_port_via_->get_interrupt_line()); + m6502_.set_irq_line(keyboard_via_->get_interrupt_line()); + } + + void set_typer_for_string(const char *string) { + std::unique_ptr mapper(new CharacterMapper()); + Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper)); + } + + void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) { + keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, !tape->get_input()); + } + + private: + CPU::MOS6502::Processor m6502_; + + uint8_t character_rom_[0x1000]; + uint8_t basic_rom_[0x2000]; + uint8_t kernel_rom_[0x2000]; + uint8_t expansion_ram_[0x8000]; + + uint8_t *rom_; + uint16_t rom_address_, rom_length_; + + uint8_t user_basic_memory_[0x0400]; + uint8_t screen_memory_[0x1000]; + uint8_t colour_memory_[0x0400]; + std::vector drive_rom_; + + uint8_t *processor_read_memory_map_[64]; + uint8_t *processor_write_memory_map_[64]; + void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) { + address >>= 10; + length >>= 10; + while(length--) { + map[address] = area; + area += 0x400; + address++; + } + } + + Region region_; + + std::unique_ptr mos6560_; + std::shared_ptr user_port_via_; + std::shared_ptr keyboard_via_; + std::shared_ptr serial_port_; + std::shared_ptr<::Commodore::Serial::Bus> serial_bus_; + + // Tape + std::shared_ptr tape_; + bool use_fast_tape_hack_; + bool is_running_at_zero_cost_; + + // Disk + std::shared_ptr<::Commodore::C1540::Machine> c1540_; + void install_disk_rom() { + if(!drive_rom_.empty() && c1540_) { + c1540_->set_rom(drive_rom_); + c1540_->run_for(Cycles(2000000)); + drive_rom_.clear(); + } + } +}; + +} +} + using namespace Commodore::Vic20; -Machine::Machine() : - m6502_(*this), - rom_(nullptr), - is_running_at_zero_cost_(false), - tape_(new Storage::Tape::BinaryTapePlayer(1022727)), - user_port_via_(new UserPortVIA), - keyboard_via_(new KeyboardVIA), - serial_port_(new SerialPort), - serial_bus_(new ::Commodore::Serial::Bus) { - // communicate the tape to the user-port VIA - user_port_via_->set_tape(tape_); - - // wire up the serial bus and serial port - Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_); - - // wire up 6522s and serial port - user_port_via_->set_serial_port(serial_port_); - keyboard_via_->set_serial_port(serial_port_); - serial_port_->set_user_port_via(user_port_via_); - - // wire up the 6522s, tape and machine - user_port_via_->set_interrupt_delegate(this); - keyboard_via_->set_interrupt_delegate(this); - tape_->set_delegate(this); - - // establish the memory maps - set_memory_size(MemorySize::Default); - - // set the NTSC clock rate - set_region(NTSC); -// _debugPort.reset(new ::Commodore::Serial::DebugPort); -// _debugPort->set_serial_bus(serial_bus_); -// serial_bus_->add_port(_debugPort); +Machine *Machine::Vic20() { + return new Vic20::ConcreteMachine; } -void Machine::set_memory_size(MemorySize size) { - memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_)); - memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_)); - - switch(size) { - default: break; - case ThreeKB: - write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x1000); - write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x1000); - break; - case ThirtyTwoKB: - write_to_map(processor_read_memory_map_, expansion_ram_, 0x0000, 0x8000); - write_to_map(processor_write_memory_map_, expansion_ram_, 0x0000, 0x8000); - break; - } - - // install the system ROMs and VIC-visible memory - write_to_map(processor_read_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); - write_to_map(processor_read_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); - write_to_map(processor_read_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); - write_to_map(processor_read_memory_map_, character_rom_, 0x8000, sizeof(character_rom_)); - write_to_map(processor_read_memory_map_, basic_rom_, 0xc000, sizeof(basic_rom_)); - write_to_map(processor_read_memory_map_, kernel_rom_, 0xe000, sizeof(kernel_rom_)); - - write_to_map(processor_write_memory_map_, user_basic_memory_, 0x0000, sizeof(user_basic_memory_)); - write_to_map(processor_write_memory_map_, screen_memory_, 0x1000, sizeof(screen_memory_)); - write_to_map(processor_write_memory_map_, colour_memory_, 0x9400, sizeof(colour_memory_)); - - // install the inserted ROM if there is one - if(rom_) { - write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_); - } -} - -void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) { - address >>= 10; - length >>= 10; - while(length--) { - map[address] = area; - area += 0x400; - address++; - } -} - -Machine::~Machine() { - delete[] rom_; -} - -Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { - // run the phase-1 part of this cycle, in which the VIC accesses memory - if(!is_running_at_zero_cost_) mos6560_->run_for(Cycles(1)); - - // run the phase-2 part of the cycle, which is whatever the 6502 said it should be - if(isReadOperation(operation)) { - uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff; - if((address&0xfc00) == 0x9000) { - if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address); - if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address); - if((address&0xfc20) == 0x9020) result &= keyboard_via_->get_register(address); - } - *value = result; - - // This combined with the stuff below constitutes the fast tape hack. Performed here: if the - // PC hits the start of the loop that just waits for an interesting tape interrupt to have - // occurred then skip both 6522s and the tape ahead to the next interrupt without any further - // CPU or 6560 costs. - if(use_fast_tape_hack_ && tape_->has_tape() && operation == CPU::MOS6502::BusOperation::ReadOpcode) { - if(address == 0xf7b2) { - // Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header. - // So cancel that via a double NOP and fill in the next header programmatically. - Storage::Tape::Commodore::Parser parser; - std::unique_ptr header = parser.get_next_header(tape_->get_tape()); - - // serialise to wherever b2:b3 points - uint16_t tape_buffer_pointer = (uint16_t)user_basic_memory_[0xb2] | (uint16_t)(user_basic_memory_[0xb3] << 8); - if(header) { - header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer); - } else { - // no header found, so store end-of-tape - user_basic_memory_[tape_buffer_pointer] = 0x05; // i.e. end of tape - } - - // clear status and the verify flag - user_basic_memory_[0x90] = 0; - user_basic_memory_[0x93] = 0; - - *value = 0x0c; // i.e. NOP abs - } else if(address == 0xf90b) { - uint8_t x = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::X); - if(x == 0xe) { - Storage::Tape::Commodore::Parser parser; - std::unique_ptr data = parser.get_next_data(tape_->get_tape()); - uint16_t start_address, end_address; - start_address = (uint16_t)(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8)); - end_address = (uint16_t)(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8)); - - // perform a via-processor_write_memory_map_ memcpy - uint8_t *data_ptr = data->data.data(); - size_t data_left = data->data.size(); - while(data_left && start_address != end_address) { - uint8_t *page = processor_write_memory_map_[start_address >> 10]; - if(page) page[start_address & 0x3ff] = *data_ptr; - data_ptr++; - start_address++; - data_left--; - } - - // set tape status, carry and flag - user_basic_memory_[0x90] |= 0x40; - uint8_t flags = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::Flags); - flags &= ~(uint8_t)(CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt); - m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags); - - // to ensure that execution proceeds to 0xfccf, pretend a NOP was here and - // ensure that the PC leaps to 0xfccf - m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf); - *value = 0xea; // i.e. NOP implied - } - } - } - } else { - uint8_t *ram = processor_write_memory_map_[address >> 10]; - if(ram) ram[address & 0x3ff] = *value; - if((address&0xfc00) == 0x9000) { - if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value); - if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value); - if((address&0xfc20) == 0x9020) keyboard_via_->set_register(address, *value); - } - } - - user_port_via_->run_for(Cycles(1)); - keyboard_via_->run_for(Cycles(1)); - if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) { - if(!typer_->type_next_character()) { - clear_all_keys(); - typer_.reset(); - } - } - tape_->run_for(Cycles(1)); - if(c1540_) c1540_->run_for(Cycles(1)); - - return Cycles(1); -} - -void Machine::run_for(const Cycles cycles) { - m6502_.run_for(cycles); -} - -#pragma mark - 6522 delegate - -void Machine::mos6522_did_change_interrupt_status(void *mos6522) { - m6502_.set_nmi_line(user_port_via_->get_interrupt_line()); - m6502_.set_irq_line(keyboard_via_->get_interrupt_line()); -} - -#pragma mark - Setup - -void Machine::set_region(Commodore::Vic20::Region region) { - region_ = region; - switch(region) { - case PAL: - set_clock_rate(1108404); - if(mos6560_) { - mos6560_->set_output_mode(MOS::MOS6560::OutputMode::PAL); - mos6560_->set_clock_rate(1108404); - } - break; - case NTSC: - set_clock_rate(1022727); - if(mos6560_) { - mos6560_->set_output_mode(MOS::MOS6560::OutputMode::NTSC); - mos6560_->set_clock_rate(1022727); - } - break; - } -} - -void Machine::setup_output(float aspect_ratio) { - mos6560_.reset(new Vic6560()); - mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20. - set_region(region_); - - memset(mos6560_->video_memory_map, 0, sizeof(mos6560_->video_memory_map)); - write_to_map(mos6560_->video_memory_map, character_rom_, 0x0000, sizeof(character_rom_)); - write_to_map(mos6560_->video_memory_map, user_basic_memory_, 0x2000, sizeof(user_basic_memory_)); - write_to_map(mos6560_->video_memory_map, screen_memory_, 0x3000, sizeof(screen_memory_)); - mos6560_->colour_memory = colour_memory_; -} - -void Machine::close_output() { - mos6560_ = nullptr; -} - -void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) { - uint8_t *target = nullptr; - size_t max_length = 0x2000; - switch(slot) { - case Kernel: target = kernel_rom_; break; - case Characters: target = character_rom_; max_length = 0x1000; break; - case BASIC: target = basic_rom_; break; - case Drive: - drive_rom_.resize(length); - memcpy(drive_rom_.data(), data, length); - install_disk_rom(); - return; - } - - if(target) { - size_t length_to_copy = std::min(max_length, length); - memcpy(target, data, length_to_copy); - } -} - -#pragma mar - Tape - -void Machine::configure_as_target(const StaticAnalyser::Target &target) { - if(target.tapes.size()) { - tape_->set_tape(target.tapes.front()); - } - - if(target.disks.size()) { - // construct the 1540 - c1540_.reset(new ::Commodore::C1540::Machine); - - // attach it to the serial bus - c1540_->set_serial_bus(serial_bus_); - - // hand it the disk - c1540_->set_disk(target.disks.front()); - - // install the ROM if it was previously set - install_disk_rom(); - } - - if(target.cartridges.size()) { - rom_address_ = 0xa000; - std::vector rom_image = target.cartridges.front()->get_segments().front().data; - rom_length_ = (uint16_t)(rom_image.size()); - - rom_ = new uint8_t[0x2000]; - memcpy(rom_, rom_image.data(), rom_image.size()); - write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000); - } - - if(target.loadingCommand.length()) { - set_typer_for_string(target.loadingCommand.c_str()); - } - - switch(target.vic20.memory_model) { - case StaticAnalyser::Vic20MemoryModel::Unexpanded: - set_memory_size(Default); - break; - case StaticAnalyser::Vic20MemoryModel::EightKB: - set_memory_size(ThreeKB); - break; - case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB: - set_memory_size(ThirtyTwoKB); - break; - } -} - -void Machine::set_typer_for_string(const char *string) { - std::unique_ptr mapper(new CharacterMapper()); - Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper)); -} - -void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) { - keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, !tape->get_input()); -} - -#pragma mark - Disc - -void Machine::install_disk_rom() { - if(!drive_rom_.empty() && c1540_) { - c1540_->set_rom(drive_rom_); - c1540_->run_for(Cycles(2000000)); - drive_rom_.clear(); - } -} - -#pragma mark - UserPortVIA - -uint8_t UserPortVIA::get_port_input(Port port) { - if(!port) { - return port_a_ | (tape_->has_tape() ? 0x00 : 0x40); - } - return 0xff; -} - -void UserPortVIA::set_control_line_output(Port port, Line line, bool value) { - if(port == Port::A && line == Line::Two) { - tape_->set_motor_control(!value); - } -} - -void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) { - 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; - } -} - -void UserPortVIA::set_joystick_state(JoystickInput input, bool value) { - if(input != JoystickInput::Right) { - port_a_ = (port_a_ & ~input) | (value ? 0 : input); - } -} - -void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) { - // Line 7 of port A is inverted and output as serial ATN - if(!port) { - std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); - if(serialPort) - serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80)); - } -} - -UserPortVIA::UserPortVIA() : port_a_(0xbf) {} - -void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { - serial_port_ = serialPort; -} - -void UserPortVIA::set_tape(std::shared_ptr tape) { - tape_ = tape; -} - -#pragma mark - KeyboardVIA - -KeyboardVIA::KeyboardVIA() : port_b_(0xff) { - clear_all_keys(); -} - -void KeyboardVIA::set_key_state(uint16_t key, bool isPressed) { - if(isPressed) - columns_[key & 7] &= ~(key >> 3); - else - columns_[key & 7] |= (key >> 3); -} - -void KeyboardVIA::clear_all_keys() { - memset(columns_, 0xff, sizeof(columns_)); -} - -uint8_t KeyboardVIA::get_port_input(Port port) { - if(!port) { - uint8_t result = 0xff; - for(int c = 0; c < 8; c++) { - if(!(activation_mask_&(1 << c))) - result &= columns_[c]; - } - return result; - } - - return port_b_; -} - -void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask) { - if(port) - activation_mask_ = (value & mask) | (~mask); -} - -void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) { - if(line == Line::Two) { - std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); - if(serialPort) { - // CB2 is inverted to become serial data; CA2 is inverted to become serial clock - if(port == Port::A) - serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value); - else - serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value); - } - } -} - -void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) { - if(input == JoystickInput::Right) { - port_b_ = (port_b_ & ~input) | (value ? 0 : input); - } -} - -void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { - serial_port_ = serialPort; -} - -#pragma mark - SerialPort - -void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { - std::shared_ptr userPortVIA = user_port_via_.lock(); - if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level); -} - -void SerialPort::set_user_port_via(std::shared_ptr userPortVIA) { - user_port_via_ = userPortVIA; -} +Machine::~Machine() {} diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index 957ed58da..599708755 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -13,15 +13,7 @@ #include "../../CRTMachine.hpp" #include "../../Typer.hpp" -#include "../../../Processors/6502/6502.hpp" -#include "../../../Components/6560/6560.hpp" -#include "../../../Components/6522/6522.hpp" - -#include "../SerialBus.hpp" -#include "../1540/C1540.hpp" - -#include "../../../Storage/Tape/Tape.hpp" -#include "../../../Storage/Disk/Disk.hpp" +#include namespace Commodore { namespace Vic20 { @@ -44,9 +36,8 @@ enum Region { PAL }; -#define key(line, mask) (((mask) << 3) | (line)) - enum Key: uint16_t { +#define key(line, mask) (((mask) << 3) | (line)) Key2 = key(7, 0x01), Key4 = key(7, 0x02), Key6 = key(7, 0x04), Key8 = key(7, 0x08), Key0 = key(7, 0x10), KeyDash = key(7, 0x20), KeyHome = key(7, 0x40), KeyF7 = key(7, 0x80), KeyQ = key(6, 0x01), KeyE = key(6, 0x02), KeyT = key(6, 0x04), KeyU = key(6, 0x08), @@ -63,6 +54,7 @@ enum Key: uint16_t { KeyI = key(1, 0x10), KeyP = key(1, 0x20), KeyAsterisk = key(1, 0x40), KeyReturn = key(1, 0x80), Key1 = key(0, 0x01), Key3 = key(0, 0x02), Key5 = key(0, 0x04), Key7 = key(0, 0x08), Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80), +#undef key }; enum JoystickInput { @@ -73,155 +65,23 @@ enum JoystickInput { Fire = 0x20 }; -class UserPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { - public: - UserPortVIA(); - using MOS6522IRQDelegate::set_interrupt_status; - - uint8_t get_port_input(Port port); - void set_control_line_output(Port port, Line line, bool value); - void set_serial_line_state(::Commodore::Serial::Line line, bool value); - void set_joystick_state(JoystickInput input, bool value); - void set_port_output(Port port, uint8_t value, uint8_t mask); - - void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort); - void set_tape(std::shared_ptr tape); - - private: - uint8_t port_a_; - std::weak_ptr<::Commodore::Serial::Port> serial_port_; - std::shared_ptr tape_; -}; - -class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { - public: - KeyboardVIA(); - using MOS6522IRQDelegate::set_interrupt_status; - - void set_key_state(uint16_t key, bool isPressed); - void clear_all_keys(); - - // to satisfy MOS::MOS6522 - uint8_t get_port_input(Port port); - - void set_port_output(Port port, uint8_t value, uint8_t mask); - void set_control_line_output(Port port, Line line, bool value); - - void set_joystick_state(JoystickInput input, bool value); - - void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort); - - private: - uint8_t port_b_; - uint8_t columns_[8]; - uint8_t activation_mask_; - std::weak_ptr<::Commodore::Serial::Port> serial_port_; -}; - -class SerialPort : public ::Commodore::Serial::Port { - public: - void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level); - void set_user_port_via(std::shared_ptr userPortVIA); - - private: - std::weak_ptr user_port_via_; -}; - -class Vic6560: public MOS::MOS6560 { - public: - inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) { - *pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO - *colour_data = colour_memory[address & 0x03ff]; - } - - uint8_t *video_memory_map[16]; - uint8_t *colour_memory; -}; - class Machine: - public CPU::MOS6502::BusHandler, + public ConfigurationTarget::Machine, public CRTMachine::Machine, - public MOS::MOS6522IRQDelegate::Delegate, - public Utility::TypeRecipient, - public Storage::Tape::BinaryTapePlayer::Delegate, - public ConfigurationTarget::Machine { - + public KeyboardMachine::Machine { public: - Machine(); - ~Machine(); + virtual ~Machine(); - void set_rom(ROMSlot slot, size_t length, const uint8_t *data); - void configure_as_target(const StaticAnalyser::Target &target); + static Machine *Vic20(); - void set_key_state(uint16_t key, bool isPressed) { keyboard_via_->set_key_state(key, isPressed); } - void clear_all_keys() { keyboard_via_->clear_all_keys(); } - void set_joystick_state(JoystickInput input, bool isPressed) { - user_port_via_->set_joystick_state(input, isPressed); - keyboard_via_->set_joystick_state(input, isPressed); - } + virtual void set_rom(ROMSlot slot, size_t length, const uint8_t *data) = 0; - void set_memory_size(MemorySize size); - void set_region(Region region); + virtual void set_joystick_state(JoystickInput input, bool isPressed) = 0; - inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; } + virtual void set_memory_size(MemorySize size) = 0; + virtual void set_region(Region region) = 0; - // to satisfy CPU::MOS6502::Processor - Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value); - void flush() { mos6560_->flush(); } - - // to satisfy CRTMachine::Machine - virtual void setup_output(float aspect_ratio); - virtual void close_output(); - virtual std::shared_ptr get_crt() { return mos6560_->get_crt(); } - virtual std::shared_ptr get_speaker() { return mos6560_->get_speaker(); } - virtual void run_for(const Cycles cycles); - - // to satisfy MOS::MOS6522::Delegate - virtual void mos6522_did_change_interrupt_status(void *mos6522); - - // for Utility::TypeRecipient - uint16_t *sequence_for_character(Utility::Typer *typer, char character); - void set_typer_for_string(const char *string); - - // for Tape::Delegate - virtual void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape); - - private: - CPU::MOS6502::Processor m6502_; - - uint8_t character_rom_[0x1000]; - uint8_t basic_rom_[0x2000]; - uint8_t kernel_rom_[0x2000]; - uint8_t expansion_ram_[0x8000]; - - uint8_t *rom_; - uint16_t rom_address_, rom_length_; - - uint8_t user_basic_memory_[0x0400]; - uint8_t screen_memory_[0x1000]; - uint8_t colour_memory_[0x0400]; - std::vector drive_rom_; - - uint8_t *processor_read_memory_map_[64]; - uint8_t *processor_write_memory_map_[64]; - void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length); - - Region region_; - - std::unique_ptr mos6560_; - std::shared_ptr user_port_via_; - std::shared_ptr keyboard_via_; - std::shared_ptr serial_port_; - std::shared_ptr<::Commodore::Serial::Bus> serial_bus_; - - // Tape - std::shared_ptr tape_; - bool use_fast_tape_hack_; - bool is_running_at_zero_cost_; - - // Disk - std::shared_ptr<::Commodore::C1540::Machine> c1540_; - void install_disk_rom(); + virtual void set_use_fast_tape_hack(bool activate) = 0; }; } diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm index c2f700ec5..a95f3afa2 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm @@ -18,17 +18,21 @@ using namespace Commodore::Vic20; @implementation CSVic20 { - Machine _vic20; + std::unique_ptr _vic20; BOOL _joystickMode; } -- (CRTMachine::Machine * const)machine { return &_vic20; } +- (CRTMachine::Machine * const)machine { + if(!_vic20) { + _vic20.reset(Commodore::Vic20::Machine::Vic20()); + } + return _vic20.get(); +} - (NSString *)userDefaultsPrefix { return @"vic20"; } - (instancetype)init { self = [super init]; - if(self) - { + if(self) { [self setDriveROM:[[NSBundle mainBundle] dataForResource:@"1540" withExtension:@"bin" subdirectory:@"ROMImages/Commodore1540"]]; [self setBASICROM:[self rom:@"basic"]]; [self setCountry:CSVic20CountryEuropean]; @@ -36,8 +40,7 @@ using namespace Commodore::Vic20; return self; } -- (NSData *)rom:(NSString *)name -{ +- (NSData *)rom:(NSString *)name { return [[NSBundle mainBundle] dataForResource:name withExtension:@"bin" subdirectory:@"ROMImages/Vic20"]; } @@ -45,7 +48,7 @@ using namespace Commodore::Vic20; - (void)setROM:(nonnull NSData *)rom slot:(ROMSlot)slot { @synchronized(self) { - _vic20.set_rom(slot, rom.length, (const uint8_t *)rom.bytes); + _vic20->set_rom(slot, rom.length, (const uint8_t *)rom.bytes); } } @@ -120,32 +123,26 @@ using namespace Commodore::Vic20; // KeyPlus // KeyGBP - if(key == VK_Tab && isPressed) - { + if(key == VK_Tab && isPressed) { _joystickMode ^= YES; } @synchronized(self) { - if(_joystickMode) - { - switch(key) - { - case VK_UpArrow: _vic20.set_joystick_state(JoystickInput::Up, isPressed); break; - case VK_DownArrow: _vic20.set_joystick_state(JoystickInput::Down, isPressed); break; - case VK_LeftArrow: _vic20.set_joystick_state(JoystickInput::Left, isPressed); break; - case VK_RightArrow: _vic20.set_joystick_state(JoystickInput::Right, isPressed); break; - case VK_ANSI_A: _vic20.set_joystick_state(JoystickInput::Fire, isPressed); break; + if(_joystickMode) { + switch(key) { + case VK_UpArrow: _vic20->set_joystick_state(JoystickInput::Up, isPressed); break; + case VK_DownArrow: _vic20->set_joystick_state(JoystickInput::Down, isPressed); break; + case VK_LeftArrow: _vic20->set_joystick_state(JoystickInput::Left, isPressed); break; + case VK_RightArrow: _vic20->set_joystick_state(JoystickInput::Right, isPressed); break; + case VK_ANSI_A: _vic20->set_joystick_state(JoystickInput::Fire, isPressed); break; } - } - else - { - switch(key) - { + } else { + switch(key) { default: { NSNumber *targetKey = vicKeysByKeys[@(key)]; if(targetKey) { - _vic20.set_key_state((Key)targetKey.integerValue, isPressed); + _vic20->set_key_state((Key)targetKey.integerValue, isPressed); } else { @@ -155,8 +152,8 @@ using namespace Commodore::Vic20; case VK_Shift: // Yuck - _vic20.set_key_state(Key::KeyLShift, isPressed); - _vic20.set_key_state(Key::KeyRShift, isPressed); + _vic20->set_key_state(Key::KeyLShift, isPressed); + _vic20->set_key_state(Key::KeyRShift, isPressed); break; } } @@ -165,7 +162,7 @@ using namespace Commodore::Vic20; - (void)clearAllKeys { @synchronized(self) { - _vic20.clear_all_keys(); + _vic20->clear_all_keys(); } } @@ -174,7 +171,7 @@ using namespace Commodore::Vic20; - (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack { _useFastLoadingHack = useFastLoadingHack; @synchronized(self) { - _vic20.set_use_fast_tape_hack(useFastLoadingHack ? true : false); + _vic20->set_use_fast_tape_hack(useFastLoadingHack ? true : false); } } @@ -182,8 +179,7 @@ using namespace Commodore::Vic20; _country = country; NSString *charactersROM, *kernelROM; Commodore::Vic20::Region region; - switch(country) - { + switch(country) { case CSVic20CountryDanish: region = Commodore::Vic20::Region::PAL; charactersROM = @"characters-danish"; @@ -212,7 +208,7 @@ using namespace Commodore::Vic20; } @synchronized(self) { - _vic20.set_region(region); + _vic20->set_region(region); [self setCharactersROM:[self rom:charactersROM]]; [self setKernelROM:[self rom:kernelROM]]; } @@ -222,9 +218,9 @@ using namespace Commodore::Vic20; _memorySize = memorySize; @synchronized(self) { switch(memorySize) { - case CSVic20MemorySize5Kb: _vic20.set_memory_size(Commodore::Vic20::Default); break; - case CSVic20MemorySize8Kb: _vic20.set_memory_size(Commodore::Vic20::ThreeKB); break; - case CSVic20MemorySize32Kb: _vic20.set_memory_size(Commodore::Vic20::ThirtyTwoKB); break; + case CSVic20MemorySize5Kb: _vic20->set_memory_size(Commodore::Vic20::Default); break; + case CSVic20MemorySize8Kb: _vic20->set_memory_size(Commodore::Vic20::ThreeKB); break; + case CSVic20MemorySize32Kb: _vic20->set_memory_size(Commodore::Vic20::ThirtyTwoKB); break; } } } From 925e7740153cb4415c557669722c35ef2f97f42c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 16 Aug 2017 16:23:33 -0400 Subject: [PATCH 2/2] Added a decent portion of documentation. But started feeling like I should address my various ownership decisions. Which would justify a separate pull request. --- Machines/Commodore/Vic-20/Vic20.cpp | 60 ++++++++++++++++++++++------- Machines/Commodore/Vic-20/Vic20.hpp | 8 ++++ 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 00853bd51..d1cb99383 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -27,24 +27,36 @@ namespace Commodore { namespace Vic20 { +/*! + Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder — + sensing the presence or absence of a tape and controlling the tape motor — and reading the current + state from its serial port. Most of the joystick input is also exposed here. +*/ class UserPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { public: UserPortVIA() : port_a_(0xbf) {} using MOS6522IRQDelegate::set_interrupt_status; + /// Reports the current input to the 6522 port @c port. uint8_t get_port_input(Port port) { + // 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) { return port_a_ | (tape_->has_tape() ? 0x00 : 0x40); } return 0xff; } + /// Receives announcements of control line output change from the 6522. void set_control_line_output(Port port, Line line, bool value) { + // The CA2 output is used to control the tape motor. if(port == Port::A && line == 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, bool value) { switch(line) { default: break; @@ -53,25 +65,28 @@ class UserPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg } } + /// Allows the current joystick input to be set. void set_joystick_state(JoystickInput input, bool value) { if(input != JoystickInput::Right) { port_a_ = (port_a_ & ~input) | (value ? 0 : input); } } + /// Receives announcements from the 6522 of user-port output, which might affect what's currently being presented onto the serial bus. void set_port_output(Port port, uint8_t value, uint8_t mask) { - // Line 7 of port A is inverted and output as serial ATN + // Line 7 of port A is inverted and output as serial ATN. if(!port) { std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); - if(serialPort) - serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80)); + if(serialPort) serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80)); } } - void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { - serial_port_ = serialPort; + /// Sets @serial_port as this VIA's connection to the serial bus. + void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serial_port) { + serial_port_ = serial_port; } + /// Sets @tape as the tape player connected to this VIA. void set_tape(std::shared_ptr tape) { tape_ = tape; } @@ -82,6 +97,10 @@ class UserPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg std::shared_ptr tape_; }; +/*! + Models the keyboard VIA, which is used by the Vic for reading its keyboard, to output to its serial port, + and for the small portion of joystick input not connected to the user-port VIA. +*/ class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { public: KeyboardVIA() : port_b_(0xff) { @@ -90,17 +109,20 @@ class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg using MOS6522IRQDelegate::set_interrupt_status; - void set_key_state(uint16_t key, bool isPressed) { - if(isPressed) + /// Sets whether @c key @c is_pressed. + void set_key_state(uint16_t key, bool is_pressed) { + if(is_pressed) columns_[key & 7] &= ~(key >> 3); else columns_[key & 7] |= (key >> 3); } + /// Sets all keys as unpressed. void clear_all_keys() { memset(columns_, 0xff, sizeof(columns_)); } + /// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B. uint8_t get_port_input(Port port) { if(!port) { uint8_t result = 0xff; @@ -114,11 +136,12 @@ class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg return port_b_; } + /// Called by the 6522 to set output. The value of Port B selects which part of the keyboard to read. void set_port_output(Port port, uint8_t value, uint8_t mask) { - if(port) - activation_mask_ = (value & mask) | (~mask); + if(port) activation_mask_ = (value & mask) | (~mask); } + /// Called by the 6522 to set control line output. Which affects the serial port. void set_control_line_output(Port port, Line line, bool value) { if(line == Line::Two) { std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); @@ -132,12 +155,14 @@ class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg } } + /// Sets whether the joystick input @c input is pressed. void set_joystick_state(JoystickInput input, bool value) { if(input == JoystickInput::Right) { port_b_ = (port_b_ & ~input) | (value ? 0 : input); } } + /// Sets the serial port to which this VIA is connected. void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) { serial_port_ = serialPort; } @@ -149,13 +174,18 @@ class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg std::weak_ptr<::Commodore::Serial::Port> serial_port_; }; +/*! + Models the Vic's serial port, providing the receipticle for input. +*/ class SerialPort : public ::Commodore::Serial::Port { public: + /// Receives an input change from the base serial port class, and communicates it to the user-port VIA. void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) { std::shared_ptr userPortVIA = user_port_via_.lock(); if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level); } + /// Sets the user-port VIA with which this serial port communicates. void set_user_port_via(std::shared_ptr userPortVIA) { user_port_via_ = userPortVIA; } @@ -164,15 +194,20 @@ class SerialPort : public ::Commodore::Serial::Port { std::weak_ptr user_port_via_; }; +/*! + Provides the bus over which the Vic 6560 fetches memory in a Vic-20. +*/ class Vic6560: public MOS::MOS6560 { public: + /// Performs a read on behalf of the 6560; in practice uses @c video_memory_map and @c colour_memory to find data. inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) { *pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO *colour_data = colour_memory[address & 0x03ff]; } - uint8_t *video_memory_map[16]; - uint8_t *colour_memory; + // It is assumed that these pointers have been filled in by the machine. + uint8_t *video_memory_map[16]; // Segments video memory into 1kb portions. + uint8_t *colour_memory; // Colour memory must be contiguous. }; class ConcreteMachine: @@ -212,9 +247,6 @@ class ConcreteMachine: // set the NTSC clock rate set_region(NTSC); - // _debugPort.reset(new ::Commodore::Serial::DebugPort); - // _debugPort->set_serial_bus(serial_bus_); - // serial_bus_->add_port(_debugPort); } ~ConcreteMachine() { diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index 599708755..6267010ca 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -72,15 +72,23 @@ class Machine: public: virtual ~Machine(); + /// Creates and returns a Vic-20. static Machine *Vic20(); + /// Sets the contents of the rom in @c slot to the buffer @c data of length @c length. virtual void set_rom(ROMSlot slot, size_t length, const uint8_t *data) = 0; + // TODO: take a std::vector to collapse length and data. + /// Sets the state of joystick input @c input. virtual void set_joystick_state(JoystickInput input, bool isPressed) = 0; + /// Sets the memory size of this Vic-20. virtual void set_memory_size(MemorySize size) = 0; + + /// Sets the region of this Vic-20. virtual void set_region(Region region) = 0; + /// Enables or disables turbo-speed tape loading. virtual void set_use_fast_tape_hack(bool activate) = 0; };