1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-23 03:29:04 +00:00

804 lines
26 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"
2017-10-19 22:27:30 -04:00
#include "Keyboard.hpp"
#include "../../../Activity/Source.hpp"
#include "../../MachineTypes.hpp"
#include "../../../Processors/6502/6502.hpp"
#include "../../../Components/6560/6560.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../../../ClockReceiver/ForceInline.hpp"
2019-03-02 18:07:05 -05:00
#include "../../../Outputs/Log.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 "../../../Configurable/StandardOptions.hpp"
#include "../../../Analyser/Dynamic/ConfidenceCounter.hpp"
#include "../../../Analyser/Static/Commodore/Target.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
2024-01-19 22:02:26 -05:00
namespace {
Log::Logger<Log::Source::Vic20> logger;
}
2023-05-10 16:02:18 -05:00
namespace Commodore::Vic20 {
enum ROMSlot {
Kernel = 0,
BASIC,
Characters,
2024-12-06 15:08:21 -05:00
Drive,
};
enum JoystickInput {
Up = 0x04,
Down = 0x08,
Left = 0x10,
Right = 0x80,
2024-12-06 15:08:21 -05:00
Fire = 0x20,
};
/*!
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::IRQDelegatePortHandler {
2024-12-06 13:52:42 -05:00
public:
UserPortVIA() : port_a_(0xbf) {}
/// Reports the current input to the 6522 port @c port.
template <MOS::MOS6522::Port port> uint8_t get_port_input() const {
2024-12-06 13:52:42 -05:00
// 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;
}
2024-12-06 13:52:42 -05:00
/// Receives announcements of control line output change from the 6522.
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line> void set_control_line_output(const bool value) {
2024-12-06 13:52:42 -05:00
// The CA2 output is used to control the tape motor.
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
tape_->set_motor_control(!value);
}
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
/// 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) {
2024-12-06 13:52:42 -05:00
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;
}
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
/// Allows the current joystick input to be set.
2024-12-06 15:08:21 -05:00
void set_joystick_state(const JoystickInput input, const bool value) {
2024-12-06 13:52:42 -05:00
if(input != JoystickInput::Right) {
port_a_ = (port_a_ & ~input) | (value ? 0 : input);
}
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
/// Receives announcements from the 6522 of user-port output, which might affect what's currently being presented onto the serial bus.
template <MOS::MOS6522::Port port> void set_port_output(const uint8_t value, uint8_t) {
2024-12-06 13:52:42 -05:00
// Line 7 of port A is inverted and output as serial ATN.
if(!port) {
serial_port_->set_output(Serial::Line::Attention, Serial::LineLevel(!(value&0x80)));
}
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
/// Sets @serial_port as this VIA's connection to the serial bus.
void set_serial_port(Serial::Port &serial_port) {
serial_port_ = &serial_port;
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
/// Sets @tape as the tape player connected to this VIA.
void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape) {
tape_ = tape;
}
2024-12-06 13:52:42 -05:00
private:
uint8_t port_a_;
Serial::Port *serial_port_ = nullptr;
2024-12-06 13:52:42 -05:00
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
};
2016-06-11 14:00:12 -04:00
/*!
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::IRQDelegatePortHandler {
2024-12-06 13:52:42 -05:00
public:
KeyboardVIA() : port_b_(0xff) {
clear_all_keys();
}
2024-12-06 13:52:42 -05:00
/// Sets whether @c key @c is_pressed.
2024-12-06 15:08:21 -05:00
void set_key_state(const uint16_t key, const bool is_pressed) {
2024-12-06 13:52:42 -05:00
if(is_pressed)
columns_[key & 7] &= ~(key >> 3);
else
columns_[key & 7] |= (key >> 3);
}
2024-12-06 13:52:42 -05:00
/// Sets all keys as unpressed.
void clear_all_keys() {
memset(columns_, 0xff, sizeof(columns_));
}
2024-12-06 13:52:42 -05:00
/// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B.
template <MOS::MOS6522::Port port> uint8_t get_port_input() const {
2024-12-06 13:52:42 -05:00
if(!port) {
uint8_t result = 0xff;
for(int c = 0; c < 8; c++) {
if(!(activation_mask_&(1 << c)))
result &= columns_[c];
}
2024-12-06 13:52:42 -05:00
return result;
}
2024-12-06 13:52:42 -05:00
return port_b_;
}
2024-12-06 13:52:42 -05:00
/// Called by the 6522 to set output. The value of Port B selects which part of the keyboard to read.
template <MOS::MOS6522::Port port> void set_port_output(const uint8_t value, const uint8_t mask) {
2024-12-06 13:52:42 -05:00
if(port) activation_mask_ = (value & mask) | (~mask);
}
2024-12-06 13:52:42 -05:00
/// Called by the 6522 to set control line output. Which affects the serial port.
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line> void set_control_line_output(const bool value) {
2024-12-06 13:52:42 -05:00
if(line == MOS::MOS6522::Line::Two) {
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
if(port == MOS::MOS6522::Port::A)
serial_port_->set_output(Serial::Line::Clock, Serial::LineLevel(!value));
else
serial_port_->set_output(Serial::Line::Data, Serial::LineLevel(!value));
}
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
/// Sets whether the joystick input @c input is pressed.
2024-12-06 15:08:21 -05:00
void set_joystick_state(const JoystickInput input, const bool value) {
2024-12-06 13:52:42 -05:00
if(input == JoystickInput::Right) {
port_b_ = (port_b_ & ~input) | (value ? 0 : input);
}
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
/// Sets the serial port to which this VIA is connected.
void set_serial_port(Serial::Port &port) {
serial_port_ = &port;
2024-12-06 13:52:42 -05:00
}
private:
uint8_t port_b_;
uint8_t columns_[8];
uint8_t activation_mask_;
Serial::Port *serial_port_ = nullptr;
};
/*!
Models the Vic's serial port, providing the receipticle for input.
*/
class SerialPort : public ::Commodore::Serial::Port {
2024-12-06 13:52:42 -05:00
public:
/// Receives an input change from the base serial port class, and communicates it to the user-port VIA.
2024-12-06 15:08:21 -05:00
void set_input(const ::Commodore::Serial::Line line, const ::Commodore::Serial::LineLevel level) {
if(user_port_via_) user_port_via_->set_serial_line_state(line, bool(level));
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
/// Sets the user-port VIA with which this serial port communicates.
void set_user_port_via(UserPortVIA &via) {
user_port_via_ = &via;
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
private:
UserPortVIA *user_port_via_ = nullptr;
};
/*!
Provides the bus over which the Vic 6560 fetches memory in a Vic-20.
*/
2024-12-06 13:52:42 -05:00
struct Vic6560BusHandler {
/// Performs a read on behalf of the 6560; in practice uses @c video_memory_map and @c colour_memory to find data.
2024-12-06 15:08:21 -05:00
forceinline void perform_read(const uint16_t address, uint8_t *const pixel_data, uint8_t *const colour_data) {
2024-12-06 13:52:42 -05:00
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
*colour_data = colour_memory[address & 0x03ff];
}
2024-12-06 13:52:42 -05:00
// 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.
2024-12-06 15:08:21 -05:00
// TODO: make the above const.
};
/*!
Interfaces a joystick to the two VIAs.
*/
class Joystick: public Inputs::ConcreteJoystick {
2024-12-06 13:52:42 -05:00
public:
Joystick(UserPortVIA &user_port_via_port_handler, KeyboardVIA &keyboard_via_port_handler) :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire)
}),
user_port_via_port_handler_(user_port_via_port_handler),
keyboard_via_port_handler_(keyboard_via_port_handler) {}
2024-12-06 15:08:21 -05:00
void did_set_input(const Input &digital_input, const bool is_active) final {
2024-12-06 13:52:42 -05:00
JoystickInput mapped_input;
switch(digital_input.type) {
default: return;
case Input::Up: mapped_input = Up; break;
case Input::Down: mapped_input = Down; break;
case Input::Left: mapped_input = Left; break;
case Input::Right: mapped_input = Right; break;
case Input::Fire: mapped_input = Fire; break;
}
user_port_via_port_handler_.set_joystick_state(mapped_input, is_active);
keyboard_via_port_handler_.set_joystick_state(mapped_input, is_active);
}
2024-12-06 13:52:42 -05:00
private:
UserPortVIA &user_port_via_port_handler_;
KeyboardVIA &keyboard_via_port_handler_;
};
class ConcreteMachine:
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer,
public MachineTypes::AudioProducer,
public MachineTypes::MediaTarget,
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::JoystickMachine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient<CharacterMapper>,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Machine,
public ClockingHint::Observer,
public Activity::Source {
2024-12-06 13:52:42 -05:00
public:
ConcreteMachine(const Analyser::Static::Commodore::Vic20Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
2024-12-06 13:52:42 -05:00
m6502_(*this),
mos6560_(mos6560_bus_handler_),
user_port_via_(user_port_via_port_handler_),
keyboard_via_(keyboard_via_port_handler_),
2024-12-06 13:52:42 -05:00
tape_(new Storage::Tape::BinaryTapePlayer(1022727)) {
// communicate the tape to the user-port VIA
user_port_via_port_handler_.set_tape(tape_);
2024-12-06 13:52:42 -05:00
// wire up the serial bus and serial port
Commodore::Serial::attach(serial_port_, serial_bus_);
2024-12-06 13:52:42 -05:00
// wire up 6522s and serial port
user_port_via_port_handler_.set_serial_port(serial_port_);
keyboard_via_port_handler_.set_serial_port(serial_port_);
serial_port_.set_user_port_via(user_port_via_port_handler_);
2024-12-06 13:52:42 -05:00
// wire up the 6522s, tape and machine
user_port_via_port_handler_.set_interrupt_delegate(this);
keyboard_via_port_handler_.set_interrupt_delegate(this);
2024-12-06 13:52:42 -05:00
tape_->set_delegate(this);
tape_->set_clocking_hint_observer(this);
// Install a joystick.
joysticks_.emplace_back(new Joystick(user_port_via_port_handler_, keyboard_via_port_handler_));
2024-12-06 13:52:42 -05:00
ROM::Request request(ROM::Name::Vic20BASIC);
ROM::Name kernel, character;
using Region = Analyser::Static::Commodore::Vic20Target::Region;
2024-12-06 13:52:42 -05:00
switch(target.region) {
default:
character = ROM::Name::Vic20EnglishCharacters;
kernel = ROM::Name::Vic20EnglishPALKernel;
break;
case Region::American:
2024-12-06 13:52:42 -05:00
character = ROM::Name::Vic20EnglishCharacters;
kernel = ROM::Name::Vic20EnglishNTSCKernel;
break;
case Region::Danish:
2024-12-06 13:52:42 -05:00
character = ROM::Name::Vic20DanishCharacters;
kernel = ROM::Name::Vic20DanishKernel;
break;
case Region::Japanese:
2024-12-06 13:52:42 -05:00
character = ROM::Name::Vic20JapaneseCharacters;
kernel = ROM::Name::Vic20JapaneseKernel;
break;
case Region::Swedish:
2024-12-06 13:52:42 -05:00
character = ROM::Name::Vic20SwedishCharacters;
kernel = ROM::Name::Vic20SwedishKernel;
break;
}
if(target.has_c1540) {
request = request && Commodore::C1540::Machine::rom_request(Commodore::C1540::Personality::C1540);
}
request = request && ROM::Request(character) && ROM::Request(kernel);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
basic_rom_ = std::move(roms.find(ROM::Name::Vic20BASIC)->second);
character_rom_ = std::move(roms.find(character)->second);
kernel_rom_ = std::move(roms.find(kernel)->second);
if(target.has_c1540) {
// construct the 1540
2024-12-28 21:49:04 -05:00
c1540_ = std::make_unique<C1540::Machine>(C1540::Personality::C1540, roms);
2024-12-06 13:52:42 -05:00
// attach it to the serial bus
c1540_->set_serial_bus(serial_bus_);
// give it a little warm up
c1540_->run_for(Cycles(2000000));
}
// Determine PAL/NTSC
if(target.region == Region::American || target.region == Region::Japanese) {
2024-12-06 13:52:42 -05:00
// NTSC
set_clock_rate(1022727);
mos6560_.set_output_mode(MOS::MOS6560::OutputMode::NTSC);
} else {
// PAL
set_clock_rate(1108404);
mos6560_.set_output_mode(MOS::MOS6560::OutputMode::PAL);
}
mos6560_.set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
mos6560_.set_clock_rate(get_clock_rate());
// Initialise the memory maps as all pointing to nothing
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
memset(mos6560_bus_handler_.video_memory_map, 0, sizeof(mos6560_bus_handler_.video_memory_map));
#define set_ram(baseaddr, length) { \
2024-12-06 13:52:42 -05:00
write_to_map(processor_read_memory_map_, &ram_[baseaddr], baseaddr, length); \
write_to_map(processor_write_memory_map_, &ram_[baseaddr], baseaddr, length); \
}
2024-12-06 13:52:42 -05:00
// Add 6502-visible RAM as requested.
set_ram(0x0000, 0x0400);
set_ram(0x1000, 0x1000); // Built-in RAM.
if(target.enabled_ram.bank0) set_ram(0x0400, 0x0c00); // Bank 0: 0x0400 -> 0x1000.
if(target.enabled_ram.bank1) set_ram(0x2000, 0x2000); // Bank 1: 0x2000 -> 0x4000.
if(target.enabled_ram.bank2) set_ram(0x4000, 0x2000); // Bank 2: 0x4000 -> 0x6000.
if(target.enabled_ram.bank3) set_ram(0x6000, 0x2000); // Bank 3: 0x6000 -> 0x8000.
if(target.enabled_ram.bank5) set_ram(0xa000, 0x2000); // Bank 5: 0xa000 -> 0xc000.
2016-10-20 21:05:32 -04:00
#undef set_ram
2024-12-06 13:52:42 -05:00
// all expansions also have colour RAM visible at 0x9400.
write_to_map(processor_read_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_));
write_to_map(processor_write_memory_map_, colour_ram_, 0x9400, sizeof(colour_ram_));
// also push memory resources into the 6560 video memory map; the 6560 has only a
// 14-bit address bus and the top bit is invested and used as bit 15 for the main
// memory bus. It can access only internal memory, so the first 1kb, then the 4kb from 0x1000.
struct Range {
const std::size_t start, end;
Range(std::size_t start, std::size_t end) : start(start), end(end) {}
};
const std::array<Range, 2> video_ranges = {{
Range(0x0000, 0x0400),
Range(0x1000, 0x2000),
}};
for(const auto &video_range : video_ranges) {
for(auto addr = video_range.start; addr < video_range.end; addr += 0x400) {
auto destination_address = (addr & 0x1fff) | (((addr & 0x8000) >> 2) ^ 0x2000);
if(processor_read_memory_map_[addr >> 10]) {
write_to_map(mos6560_bus_handler_.video_memory_map, &ram_[addr], uint16_t(destination_address), 0x400);
}
}
2024-12-06 13:52:42 -05:00
}
mos6560_bus_handler_.colour_memory = colour_ram_;
2016-10-20 21:05:32 -04:00
2024-12-06 13:52:42 -05:00
// install the BASIC ROM
write_to_map(processor_read_memory_map_, basic_rom_.data(), 0xc000, uint16_t(basic_rom_.size()));
2024-12-06 13:52:42 -05:00
// install the system ROM
write_to_map(processor_read_memory_map_, character_rom_.data(), 0x8000, uint16_t(character_rom_.size()));
write_to_map(mos6560_bus_handler_.video_memory_map, character_rom_.data(), 0x0000, uint16_t(character_rom_.size()));
write_to_map(processor_read_memory_map_, kernel_rom_.data(), 0xe000, uint16_t(kernel_rom_.size()));
2024-12-06 13:52:42 -05:00
// The insert_media occurs last, so if there's a conflict between cartridges and RAM,
// the cartridge wins.
insert_media(target.media);
if(!target.loading_command.empty()) {
type_string(target.loading_command);
}
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.tapes.empty()) {
tape_->set_tape(media.tapes.front(), TargetPlatform::Vic20);
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
if(!media.disks.empty() && c1540_) {
c1540_->set_disk(media.disks.front());
}
2024-12-06 13:52:42 -05:00
if(!media.cartridges.empty()) {
rom_address_ = 0xa000;
std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data;
rom_length_ = uint16_t(rom_image.size());
2024-12-06 13:52:42 -05:00
rom_ = rom_image;
rom_.resize(0x2000);
write_to_map(processor_read_memory_map_, rom_.data(), rom_address_, rom_length_);
}
2024-12-06 13:52:42 -05:00
set_use_fast_tape();
2024-12-06 13:52:42 -05:00
return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty();
}
2024-12-06 15:08:21 -05:00
void set_key_state(const uint16_t key, const bool is_pressed) final {
2024-12-06 13:52:42 -05:00
if(key < 0xfff0) {
keyboard_via_port_handler_.set_key_state(key, is_pressed);
2024-12-06 13:52:42 -05:00
} else {
switch(key) {
case KeyRestore:
user_port_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(!is_pressed);
2024-12-06 13:52:42 -05:00
break;
#define ShiftedMap(source, target) \
2024-12-06 13:52:42 -05:00
case source: \
keyboard_via_port_handler_.set_key_state(KeyLShift, is_pressed); \
keyboard_via_port_handler_.set_key_state(target, is_pressed); \
2024-12-06 13:52:42 -05:00
break;
ShiftedMap(KeyUp, KeyDown);
ShiftedMap(KeyLeft, KeyRight);
ShiftedMap(KeyF2, KeyF1);
ShiftedMap(KeyF4, KeyF3);
ShiftedMap(KeyF6, KeyF5);
ShiftedMap(KeyF8, KeyF7);
#undef ShiftedMap
}
}
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
void clear_all_keys() final {
keyboard_via_port_handler_.clear_all_keys();
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
2016-10-20 21:05:32 -04:00
2024-12-06 13:52:42 -05:00
// to satisfy CPU::MOS6502::Processor
2024-12-06 15:08:21 -05:00
forceinline Cycles perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
const uint16_t address,
uint8_t *const value
) {
// Tun the phase-1 part of this cycle, in which the VIC accesses memory.
2024-12-06 13:52:42 -05:00
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;
2025-01-07 22:55:19 -05:00
if(is_read(operation)) {
const auto page = processor_read_memory_map_[address >> 10];
uint8_t result;
if(!page) {
if(!is_from_rom) confidence_.add_miss();
result = 0xff;
} else {
result = processor_read_memory_map_[address >> 10][address & 0x3ff];
}
2024-12-06 13:52:42 -05:00
if((address&0xfc00) == 0x9000) {
if(!(address&0x100)) {
update_video();
result &= mos6560_.read(address);
}
2024-12-06 13:52:42 -05:00
if(address & 0x10) result &= user_port_via_.read(address);
if(address & 0x20) result &= keyboard_via_.read(address);
if(!is_from_rom) {
if((address & 0x100) && !(address & 0x30)) {
confidence_.add_miss();
} else {
confidence_.add_hit();
}
}
2024-12-06 13:52:42 -05:00
}
*value = result;
// Consider applying the fast tape hack.
if(use_fast_tape_hack_ && 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<Storage::Tape::Commodore::Header> header = parser.get_next_header(*tape_->serialiser());
2024-12-06 13:52:42 -05:00
const auto tape_position = tape_->serialiser()->offset();
2024-12-06 13:52:42 -05:00
if(header) {
// serialise to wherever b2:b3 points
const uint16_t tape_buffer_pointer = uint16_t(ram_[0xb2]) | uint16_t(ram_[0xb3] << 8);
header->serialise(&ram_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
hold_tape_ = true;
logger.info().append("Found header");
} else {
// no header found, so pretend this hack never interceded
tape_->serialiser()->set_offset(tape_position);
2024-12-06 13:52:42 -05:00
hold_tape_ = false;
logger.info().append("Didn't find header");
}
2024-12-06 13:52:42 -05:00
// clear status and the verify flag
ram_[0x90] = 0;
ram_[0x93] = 0;
2024-12-06 13:52:42 -05:00
*value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
} else if(address == 0xf90b) {
uint8_t x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X));
if(x == 0xe) {
Storage::Tape::Commodore::Parser parser;
const auto tape_position = tape_->serialiser()->offset();
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(*tape_->serialiser());
2024-12-06 13:52:42 -05:00
if(data) {
uint16_t start_address, end_address;
start_address = uint16_t(ram_[0xc1] | (ram_[0xc2] << 8));
end_address = uint16_t(ram_[0xae] | (ram_[0xaf] << 8));
// perform a via-processor_write_memory_map_ memcpy
uint8_t *data_ptr = data->data.data();
std::size_t data_left = data->data.size();
while(data_left && start_address != end_address) {
uint8_t *const tape_page = processor_write_memory_map_[start_address >> 10];
if(tape_page) tape_page[start_address & 0x3ff] = *data_ptr;
2024-12-06 13:52:42 -05:00
data_ptr++;
start_address++;
data_left--;
}
// 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);
// 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
hold_tape_ = true;
2024-12-06 13:52:42 -05:00
logger.info().append("Found data");
} else {
tape_->serialiser()->set_offset(tape_position);
hold_tape_ = false;
2024-12-06 13:52:42 -05:00
logger.info().append("Didn't find data");
}
}
}
}
2024-12-06 13:52:42 -05:00
} else {
uint8_t *const ram = processor_write_memory_map_[address >> 10];
2024-12-06 13:52:42 -05:00
if(ram) {
update_video();
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);
}
2024-12-06 13:52:42 -05:00
// The first VIA is selected by bit 4 = 1.
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(!is_from_rom) {
if((address & 0x100) && !(address & 0x30)) {
confidence_.add_miss();
} else {
confidence_.add_hit();
}
}
} else if(!ram) {
if(!is_from_rom) confidence_.add_miss();
}
}
2016-10-20 21:05:32 -04:00
2024-12-06 13:52:42 -05:00
user_port_via_.run_for(Cycles(1));
keyboard_via_.run_for(Cycles(1));
if(typer_ && address == 0xeb1e && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
if(!typer_->type_next_character()) {
clear_all_keys();
typer_.reset();
2022-07-08 16:04:32 -04:00
}
}
2024-12-06 13:52:42 -05:00
if(!tape_is_sleeping_ && !hold_tape_) tape_->run_for(Cycles(1));
if(c1540_) c1540_->run_for(Cycles(1));
2016-10-20 21:05:32 -04:00
2024-12-06 13:52:42 -05:00
return Cycles(1);
}
2016-10-20 21:05:32 -04:00
2024-12-06 15:08:21 -05:00
void flush_output(const int outputs) final {
2024-12-06 13:52:42 -05:00
if(outputs & Output::Video) {
update_video();
2016-10-20 21:05:32 -04:00
}
2024-12-06 13:52:42 -05:00
if(outputs & Output::Audio) {
mos6560_.flush();
}
2024-12-06 13:52:42 -05:00
}
2024-12-06 13:52:42 -05:00
void run_for(const Cycles cycles) final {
m6502_.run_for(cycles);
}
2018-11-29 20:44:21 -08:00
2024-12-06 15:08:21 -05:00
void set_scan_target(Outputs::Display::ScanTarget *const scan_target) final {
2024-12-06 13:52:42 -05:00
mos6560_.set_scan_target(scan_target);
}
2020-03-18 20:23:55 -04:00
2024-12-06 13:52:42 -05:00
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return mos6560_.get_scaled_scan_status();
}
2016-10-20 21:05:32 -04:00
2024-12-06 15:08:21 -05:00
void set_display_type(const Outputs::Display::DisplayType display_type) final {
2024-12-06 13:52:42 -05:00
mos6560_.set_display_type(display_type);
}
2016-10-20 21:05:32 -04:00
2024-12-06 13:52:42 -05:00
Outputs::Display::DisplayType get_display_type() const final {
return mos6560_.get_display_type();
}
2016-10-20 21:05:32 -04:00
2024-12-06 13:52:42 -05:00
Outputs::Speaker::Speaker *get_speaker() final {
return mos6560_.get_speaker();
}
2024-12-06 13:52:42 -05:00
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());
}
2024-12-06 13:52:42 -05:00
void type_string(const std::string &string) final {
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
}
2024-12-06 15:08:21 -05:00
bool can_type(const char c) const final {
2024-12-06 13:52:42 -05:00
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
2024-12-06 15:08:21 -05:00
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *const tape) final {
keyboard_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(!tape->input());
2024-12-06 13:52:42 -05:00
}
2020-03-18 20:23:55 -04:00
2024-12-06 13:52:42 -05:00
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
2024-12-06 13:52:42 -05:00
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->quickload = allow_fast_tape_hack_;
return options;
}
2024-12-06 13:52:42 -05:00
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
2024-12-06 13:52:42 -05:00
set_video_signal_configurable(options->output);
allow_fast_tape_hack_ = options->quickload;
set_use_fast_tape();
}
2024-12-06 15:08:21 -05:00
void set_component_prefers_clocking(ClockingHint::Source *, const ClockingHint::Preference clocking) final {
2024-12-06 13:52:42 -05:00
tape_is_sleeping_ = clocking == ClockingHint::Preference::None;
set_use_fast_tape();
}
// MARK: - Activity Source
2024-12-06 15:08:21 -05:00
void set_activity_observer(Activity::Observer *const observer) final {
2024-12-06 13:52:42 -05:00
if(c1540_) c1540_->set_activity_observer(observer);
}
2016-10-20 21:05:32 -04:00
2024-12-06 13:52:42 -05:00
private:
void update_video() {
mos6560_.run_for(cycles_since_mos6560_update_.flush<Cycles>());
}
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
std::vector<uint8_t> character_rom_;
std::vector<uint8_t> basic_rom_;
std::vector<uint8_t> kernel_rom_;
std::vector<uint8_t> rom_;
uint16_t rom_address_, rom_length_;
uint8_t ram_[0x10000];
uint8_t colour_ram_[0x0400];
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++;
}
2024-12-06 13:52:42 -05:00
}
Commodore::Vic20::KeyboardMapper keyboard_mapper_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Cycles cycles_since_mos6560_update_;
Vic6560BusHandler mos6560_bus_handler_;
MOS::MOS6560::MOS6560<Vic6560BusHandler> mos6560_;
UserPortVIA user_port_via_port_handler_;
KeyboardVIA keyboard_via_port_handler_;
SerialPort serial_port_;
Serial::Bus serial_bus_;
2024-12-06 13:52:42 -05:00
MOS::MOS6522::MOS6522<UserPortVIA> user_port_via_;
MOS::MOS6522::MOS6522<KeyboardVIA> keyboard_via_;
// Tape
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
bool use_fast_tape_hack_ = false;
bool hold_tape_ = false;
bool allow_fast_tape_hack_ = false;
bool tape_is_sleeping_ = true;
void set_use_fast_tape() {
use_fast_tape_hack_ = !tape_is_sleeping_ && allow_fast_tape_hack_ && tape_->has_tape();
}
2024-12-06 13:52:42 -05:00
// Disk
std::unique_ptr<::Commodore::C1540::Machine> c1540_;
// MARK: - Confidence.
Analyser::Dynamic::ConfidenceCounter confidence_;
float get_confidence() final { return confidence_.get_confidence(); }
2025-01-20 16:19:02 -05:00
std::string debug_type() final {
return "Vic20";
}
};
2016-10-20 21:05:32 -04:00
}
using namespace Commodore::Vic20;
2024-12-06 13:52:42 -05:00
std::unique_ptr<Machine> Machine::Vic20(
2024-12-06 15:08:21 -05:00
const Analyser::Static::Target *const target,
2024-12-06 13:52:42 -05:00
const ROMMachine::ROMFetcher &rom_fetcher
) {
using Target = Analyser::Static::Commodore::Vic20Target;
const Target *const commodore_target = dynamic_cast<const Target *>(target);
return std::make_unique<Vic20::ConcreteMachine>(*commodore_target, rom_fetcher);
2016-10-20 21:05:32 -04:00
}