1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-05 10:28:58 +00:00
CLK/Machines/Commodore/Vic-20/Vic20.cpp

440 lines
14 KiB
C++
Raw Normal View History

//
// Vic20.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Vic20.hpp"
#include <algorithm>
#include "../../../Storage/Tape/Formats/TapePRG.hpp"
#include "../../../StaticAnalyser/StaticAnalyser.hpp"
using namespace Commodore::Vic20;
Machine::Machine() :
2017-03-26 18:34:47 +00:00
rom_(nullptr),
is_running_at_zero_cost_(false),
tape_(1022727) {
// create 6522s, serial port and bus
2016-12-03 18:30:27 +00:00
user_port_via_.reset(new UserPortVIA);
keyboard_via_.reset(new KeyboardVIA);
serial_port_.reset(new SerialPort);
serial_bus_.reset(new ::Commodore::Serial::Bus);
// wire up the serial bus and serial port
2016-12-03 18:30:27 +00:00
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
// wire up 6522s and serial port
2016-12-03 18:30:27 +00:00
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
2016-12-03 18:30:27 +00:00
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);
2016-12-03 18:30:27 +00:00
// _debugPort->set_serial_bus(serial_bus_);
// serial_bus_->add_port(_debugPort);
}
2017-03-26 18:34:47 +00:00
void Machine::set_memory_size(MemorySize size) {
2016-12-03 18:30:27 +00:00
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
2017-03-26 18:34:47 +00:00
switch(size) {
default: break;
case ThreeKB:
2016-12-03 18:30:27 +00:00
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:
2016-12-03 18:30:27 +00:00
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
2016-12-03 18:30:27 +00:00
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_));
2016-12-03 18:30:27 +00:00
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
2017-03-26 18:34:47 +00:00
if(rom_) {
2016-12-03 18:30:27 +00:00
write_to_map(processor_read_memory_map_, rom_, rom_address_, rom_length_);
}
}
2017-03-26 18:34:47 +00:00
void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) {
address >>= 10;
length >>= 10;
2017-03-26 18:34:47 +00:00
while(length--) {
map[address] = area;
area += 0x400;
address++;
}
}
2017-03-26 18:34:47 +00:00
Machine::~Machine() {
2016-12-03 18:30:27 +00:00
delete[] rom_;
2016-06-11 18:00:12 +00:00
}
2017-03-26 18:34:47 +00:00
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
// static int logCount = 0;
// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500;
// if(operation == CPU6502::BusOperation::ReadOpcode && logCount) {
// logCount--;
// printf("%04x\n", address);
// }
2017-03-26 18:34:47 +00:00
// if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192)) {
// printf("\n[%04x] <- %02x\n", address, *value);
// }
// run the phase-1 part of this cycle, in which the VIC accesses memory
2016-12-03 18:30:27 +00:00
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
2017-03-26 18:34:47 +00:00
if(isReadOperation(operation)) {
2016-12-03 18:30:27 +00:00
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
2017-03-26 18:34:47 +00:00
if((address&0xfc00) == 0x9000) {
2016-12-03 18:30:27 +00:00
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.
2017-03-26 18:34:47 +00:00
if(use_fast_tape_hack_ && tape_.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode) {
while(!user_port_via_->get_interrupt_line() && !keyboard_via_->get_interrupt_line() && !tape_.get_tape()->is_at_end()) {
2016-12-03 18:30:27 +00:00
user_port_via_->run_for_cycles(1);
keyboard_via_->run_for_cycles(1);
tape_.run_for_cycles(1);
}
}
2017-03-26 18:34:47 +00:00
} else {
2016-12-03 18:30:27 +00:00
uint8_t *ram = processor_write_memory_map_[address >> 10];
if(ram) ram[address & 0x3ff] = *value;
2017-03-26 18:34:47 +00:00
if((address&0xfc00) == 0x9000) {
2016-12-03 18:30:27 +00:00
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);
}
}
2016-12-03 18:30:27 +00:00
user_port_via_->run_for_cycles(1);
keyboard_via_->run_for_cycles(1);
2017-03-26 18:34:47 +00:00
if(typer_ && operation == CPU6502::BusOperation::ReadOpcode && address == 0xEB1E) {
if(!typer_->type_next_character()) {
clear_all_keys();
typer_.reset();
}
}
2016-12-03 18:30:27 +00:00
tape_.run_for_cycles(1);
if(c1540_) c1540_->run_for_cycles(1);
// If using fast tape then:
// if the PC hits 0xf98e, the ROM's tape loading routine, then begin zero cost processing;
// if the PC heads into RAM
//
// Where 'zero cost processing' is taken to be taking the 6560 off the bus (because I know it's
// expensive, and not relevant) then running the tape, the CPU and both 6522s as usual but not
// counting cycles towards the processing budget. So the limit is the host machine.
//
// Note the additional test above for PC hitting 0xf92f, which is a loop in the ROM that waits
// for an interesting interrupt. Up there the fast tape hack goes even further in also cutting
// the CPU out of the action.
2017-03-26 18:34:47 +00:00
if(use_fast_tape_hack_ && tape_.has_tape()) {
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode) {
2016-12-03 18:30:27 +00:00
is_running_at_zero_cost_ = true;
set_clock_is_unlimited(true);
}
if(
(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) ||
2016-12-03 18:30:27 +00:00
tape_.get_tape()->is_at_end()
2017-03-26 18:34:47 +00:00
) {
2016-12-03 18:30:27 +00:00
is_running_at_zero_cost_ = false;
set_clock_is_unlimited(false);
}
}
return 1;
}
#pragma mark - 6522 delegate
2017-03-26 18:34:47 +00:00
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
2016-12-03 18:30:27 +00:00
set_nmi_line(user_port_via_->get_interrupt_line());
set_irq_line(keyboard_via_->get_interrupt_line());
}
#pragma mark - Setup
2017-03-26 18:34:47 +00:00
void Machine::set_region(Commodore::Vic20::Region region) {
2016-12-03 18:30:27 +00:00
region_ = region;
2017-03-26 18:34:47 +00:00
switch(region) {
case PAL:
set_clock_rate(1108404);
2017-03-26 18:34:47 +00:00
if(mos6560_) {
2016-12-03 18:30:27 +00:00
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
mos6560_->set_clock_rate(1108404);
}
break;
case NTSC:
set_clock_rate(1022727);
2017-03-26 18:34:47 +00:00
if(mos6560_) {
2016-12-03 18:30:27 +00:00
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
mos6560_->set_clock_rate(1022727);
}
break;
}
}
2017-03-26 18:34:47 +00:00
void Machine::setup_output(float aspect_ratio) {
2016-12-03 18:30:27 +00:00
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_;
}
2017-03-26 18:34:47 +00:00
void Machine::close_output() {
2016-12-03 18:30:27 +00:00
mos6560_ = nullptr;
}
2017-03-26 18:34:47 +00:00
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) {
uint8_t *target = nullptr;
size_t max_length = 0x2000;
2017-03-26 18:34:47 +00:00
switch(slot) {
2016-12-03 18:30:27 +00:00
case Kernel: target = kernel_rom_; break;
case Characters: target = character_rom_; max_length = 0x1000; break;
case BASIC: target = basic_rom_; break;
case Drive:
2016-12-03 18:30:27 +00:00
drive_rom_.reset(new uint8_t[length]);
memcpy(drive_rom_.get(), data, length);
install_disk_rom();
return;
}
2017-03-26 18:34:47 +00:00
if(target) {
size_t length_to_copy = std::min(max_length, length);
memcpy(target, data, length_to_copy);
}
}
2017-03-26 18:34:47 +00:00
//void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) {
// if(length > 2) {
// _rom_address = (uint16_t)(data[0] | (data[1] << 8));
// _rom_length = (uint16_t)(length - 2);
//
// // install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism
2017-03-26 18:34:47 +00:00
// if(_rom_address == 0xa000) {
// _rom = new uint8_t[0x2000];
// memcpy(_rom, &data[2], length - 2);
2016-12-03 18:30:27 +00:00
// write_to_map(processor_read_memory_map_, _rom, _rom_address, 0x2000);
2017-03-26 18:34:47 +00:00
// } else {
// set_tape(std::shared_ptr<Storage::Tape::Tape>(new Storage::Tape::PRG(file_name)));
// }
// }
//}
#pragma mar - Tape
2017-03-26 18:34:47 +00:00
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
if(target.tapes.size()) {
2016-12-03 18:30:27 +00:00
tape_.set_tape(target.tapes.front());
}
2017-03-26 18:34:47 +00:00
if(target.disks.size()) {
// construct the 1540
2016-12-03 18:30:27 +00:00
c1540_.reset(new ::Commodore::C1540::Machine);
// attach it to the serial bus
2016-12-03 18:30:27 +00:00
c1540_->set_serial_bus(serial_bus_);
// hand it the disk
2016-12-03 18:30:27 +00:00
c1540_->set_disk(target.disks.front());
// install the ROM if it was previously set
install_disk_rom();
}
2017-03-26 18:34:47 +00:00
if(target.cartridges.size()) {
2016-12-03 18:30:27 +00:00
rom_address_ = 0xa000;
2016-09-30 00:15:25 +00:00
std::vector<uint8_t> rom_image = target.cartridges.front()->get_segments().front().data;
2016-12-03 18:30:27 +00:00
rom_length_ = (uint16_t)(rom_image.size());
2016-09-30 00:15:25 +00:00
2016-12-03 18:30:27 +00:00
rom_ = new uint8_t[0x2000];
memcpy(rom_, rom_image.data(), rom_image.size());
write_to_map(processor_read_memory_map_, rom_, rom_address_, 0x2000);
2016-09-30 00:15:25 +00:00
}
2017-03-26 18:34:47 +00:00
if(should_automatically_load_media_) {
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
set_typer_for_string(target.loadingCommand.c_str());
}
2017-03-26 18:34:47 +00:00
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;
}
}
}
2017-03-26 18:34:47 +00:00
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) {
2016-12-03 18:30:27 +00:00
keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
}
#pragma mark - Disc
2017-03-26 18:34:47 +00:00
void Machine::install_disk_rom() {
if(drive_rom_ && c1540_) {
2016-12-03 18:30:27 +00:00
c1540_->set_rom(drive_rom_.get());
c1540_->run_for_cycles(2000000);
drive_rom_.reset();
}
}
2016-10-21 01:05:32 +00:00
#pragma mark - UserPortVIA
2017-03-26 18:34:47 +00:00
uint8_t UserPortVIA::get_port_input(Port port) {
if(!port) {
2016-12-03 18:30:27 +00:00
return port_a_; // TODO: bit 6 should be high if there is no tape, low otherwise
2016-10-21 01:05:32 +00:00
}
return 0xff;
}
2017-03-26 18:34:47 +00:00
void UserPortVIA::set_control_line_output(Port port, Line line, bool value) {
2016-10-21 01:05:32 +00:00
// if(port == Port::A && line == Line::Two) {
// printf("Tape motor %s\n", value ? "on" : "off");
// }
}
2017-03-26 18:34:47 +00:00
void UserPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
switch(line) {
2016-10-21 01:05:32 +00:00
default: break;
2016-12-03 18:30:27 +00:00
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;
2016-10-21 01:05:32 +00:00
}
}
2017-03-26 18:34:47 +00:00
void UserPortVIA::set_joystick_state(JoystickInput input, bool value) {
if(input != JoystickInput::Right) {
2016-12-03 18:30:27 +00:00
port_a_ = (port_a_ & ~input) | (value ? 0 : input);
2016-10-21 01:05:32 +00:00
}
}
2017-03-26 18:34:47 +00:00
void UserPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
2016-10-21 01:05:32 +00:00
// Line 7 of port A is inverted and output as serial ATN
2017-03-26 18:34:47 +00:00
if(!port) {
2016-12-03 18:30:27 +00:00
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
2016-10-21 01:05:32 +00:00
if(serialPort)
serialPort->set_output(::Commodore::Serial::Line::Attention, (::Commodore::Serial::LineLevel)!(value&0x80));
}
}
2016-12-03 18:30:27 +00:00
UserPortVIA::UserPortVIA() : port_a_(0xbf) {}
2016-10-21 01:05:32 +00:00
2017-03-26 18:34:47 +00:00
void UserPortVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
2016-12-03 18:30:27 +00:00
serial_port_ = serialPort;
2016-10-21 01:05:32 +00:00
}
#pragma mark - KeyboardVIA
2017-03-26 18:34:47 +00:00
KeyboardVIA::KeyboardVIA() : port_b_(0xff) {
2016-10-21 01:05:32 +00:00
clear_all_keys();
}
2017-03-26 18:34:47 +00:00
void KeyboardVIA::set_key_state(uint16_t key, bool isPressed) {
2016-10-21 01:05:32 +00:00
if(isPressed)
2016-12-03 18:30:27 +00:00
columns_[key & 7] &= ~(key >> 3);
2016-10-21 01:05:32 +00:00
else
2016-12-03 18:30:27 +00:00
columns_[key & 7] |= (key >> 3);
2016-10-21 01:05:32 +00:00
}
2017-03-26 18:34:47 +00:00
void KeyboardVIA::clear_all_keys() {
2016-12-03 18:30:27 +00:00
memset(columns_, 0xff, sizeof(columns_));
2016-10-21 01:05:32 +00:00
}
2017-03-26 18:34:47 +00:00
uint8_t KeyboardVIA::get_port_input(Port port) {
if(!port) {
2016-10-21 01:05:32 +00:00
uint8_t result = 0xff;
2017-03-26 18:34:47 +00:00
for(int c = 0; c < 8; c++) {
2016-12-03 18:30:27 +00:00
if(!(activation_mask_&(1 << c)))
result &= columns_[c];
2016-10-21 01:05:32 +00:00
}
return result;
}
2016-12-03 18:30:27 +00:00
return port_b_;
2016-10-21 01:05:32 +00:00
}
2017-03-26 18:34:47 +00:00
void KeyboardVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
2016-10-21 01:05:32 +00:00
if(port)
2016-12-03 18:30:27 +00:00
activation_mask_ = (value & mask) | (~mask);
2016-10-21 01:05:32 +00:00
}
2017-03-26 18:34:47 +00:00
void KeyboardVIA::set_control_line_output(Port port, Line line, bool value) {
if(line == Line::Two) {
2016-12-03 18:30:27 +00:00
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
2017-03-26 18:34:47 +00:00
if(serialPort) {
2016-10-21 01:05:32 +00:00
// 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);
}
}
}
2017-03-26 18:34:47 +00:00
void KeyboardVIA::set_joystick_state(JoystickInput input, bool value) {
if(input == JoystickInput::Right) {
2016-12-03 18:30:27 +00:00
port_b_ = (port_b_ & ~input) | (value ? 0 : input);
2016-10-21 01:05:32 +00:00
}
}
2017-03-26 18:34:47 +00:00
void KeyboardVIA::set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort) {
2016-12-03 18:30:27 +00:00
serial_port_ = serialPort;
2016-10-21 01:05:32 +00:00
}
#pragma mark - SerialPort
2017-03-26 18:34:47 +00:00
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
2016-12-03 18:30:27 +00:00
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
2016-10-21 01:05:32 +00:00
if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level);
}
2017-03-26 18:34:47 +00:00
void SerialPort::set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA) {
2016-12-03 18:30:27 +00:00
user_port_via_ = userPortVIA;
2016-10-21 01:05:32 +00:00
}