1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-28 07:29:45 +00:00

Merge pull request #204 from TomHarte/VicInline

Brings the Vic-20 into line with the new idiom on machine declaration
This commit is contained in:
Thomas Harte 2017-08-16 16:24:38 -04:00 committed by GitHub
commit bcd7a312a4
3 changed files with 630 additions and 618 deletions

View File

@ -8,15 +8,216 @@
#include "Vic20.hpp"
#include <algorithm>
#include "../../../Storage/Tape/Formats/TapePRG.hpp"
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
#include "../../../StaticAnalyser/StaticAnalyser.hpp"
#include "CharacterMapper.hpp"
using namespace Commodore::Vic20;
#include "../../../Processors/6502/6502.hpp"
#include "../../../Components/6560/6560.hpp"
#include "../../../Components/6522/6522.hpp"
Machine::Machine() :
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
#include "../SerialBus.hpp"
#include "../1540/C1540.hpp"
#include "../../../Storage/Tape/Tape.hpp"
#include "../../../Storage/Disk/Disk.hpp"
#include <algorithm>
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<UserPortVIA>, 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;
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;
}
}
/// 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.
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));
}
}
/// 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<Storage::Tape::BinaryTapePlayer> tape) {
tape_ = tape;
}
private:
uint8_t port_a_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
std::shared_ptr<Storage::Tape::BinaryTapePlayer> 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<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
public:
KeyboardVIA() : port_b_(0xff) {
clear_all_keys();
}
using MOS6522IRQDelegate::set_interrupt_status;
/// 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;
for(int c = 0; c < 8; c++) {
if(!(activation_mask_&(1 << c)))
result &= columns_[c];
}
return result;
}
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);
}
/// 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();
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);
}
}
}
/// 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;
}
private:
uint8_t port_b_;
uint8_t columns_[8];
uint8_t activation_mask_;
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> 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> userPortVIA) {
user_port_via_ = userPortVIA;
}
private:
std::weak_ptr<UserPortVIA> user_port_via_;
};
/*!
Provides the bus over which the Vic 6560 fetches memory in a Vic-20.
*/
class Vic6560: public MOS::MOS6560<Vic6560> {
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];
}
// 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:
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),
@ -46,12 +247,92 @@ Machine::Machine() :
// 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);
}
}
void Machine::set_memory_size(MemorySize size) {
~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<uint8_t> 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_));
@ -83,23 +364,34 @@ void Machine::set_memory_size(MemorySize size) {
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_;
}
void set_region(Region region) {
region_ = region;
switch(region) {
case PAL:
set_clock_rate(1108404);
if(mos6560_) {
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::PAL);
mos6560_->set_clock_rate(1108404);
}
break;
case NTSC:
set_clock_rate(1022727);
if(mos6560_) {
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
mos6560_->set_clock_rate(1022727);
}
break;
}
}
Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
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));
@ -193,42 +485,17 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
if(c1540_) c1540_->run_for(Cycles(1));
return Cycles(1);
}
}
void Machine::run_for(const Cycles cycles) {
void flush() {
mos6560_->flush();
}
void 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<Commodore::Vic20::Vic6560>::OutputMode::PAL);
mos6560_->set_clock_rate(1108404);
}
break;
case NTSC:
set_clock_rate(1022727);
if(mos6560_) {
mos6560_->set_output_mode(MOS::MOS6560<Commodore::Vic20::Vic6560>::OutputMode::NTSC);
mos6560_->set_clock_rate(1022727);
}
break;
}
}
void Machine::setup_output(float aspect_ratio) {
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_);
@ -238,212 +505,93 @@ void Machine::setup_output(float aspect_ratio) {
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() {
void 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());
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
return mos6560_->get_crt();
}
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();
std::shared_ptr<Outputs::Speaker> get_speaker() {
return mos6560_->get_speaker();
}
if(target.cartridges.size()) {
rom_address_ = 0xa000;
std::vector<uint8_t> 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);
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());
}
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) {
void set_typer_for_string(const char *string) {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
}
}
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) {
void 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
private:
CPU::MOS6502::Processor<ConcreteMachine> m6502_;
void Machine::install_disk_rom() {
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<uint8_t> 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<Vic6560> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_;
std::shared_ptr<KeyboardVIA> keyboard_via_;
std::shared_ptr<SerialPort> serial_port_;
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
// Tape
std::shared_ptr<Storage::Tape::BinaryTapePlayer> 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();
}
}
#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);
}
using namespace Commodore::Vic20;
Machine *Machine::Vic20() {
return new Vic20::ConcreteMachine;
}
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<Storage::Tape::BinaryTapePlayer> 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> 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> userPortVIA) {
user_port_via_ = userPortVIA;
}
Machine::~Machine() {}

View File

@ -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 <cstdint>
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,31 @@ enum JoystickInput {
Fire = 0x20
};
class UserPortVIA: public MOS::MOS6522<UserPortVIA>, 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<Storage::Tape::BinaryTapePlayer> tape);
private:
uint8_t port_a_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
};
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, 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> userPortVIA);
private:
std::weak_ptr<UserPortVIA> user_port_via_;
};
class Vic6560: public MOS::MOS6560<Vic6560> {
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);
/// Creates and returns a Vic-20.
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);
}
/// 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<uint8_t> to collapse length and data.
void set_memory_size(MemorySize size);
void set_region(Region region);
/// Sets the state of joystick input @c input.
virtual void set_joystick_state(JoystickInput input, bool isPressed) = 0;
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
/// Sets the memory size of this Vic-20.
virtual void set_memory_size(MemorySize size) = 0;
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
void flush() { mos6560_->flush(); }
/// Sets the region of this Vic-20.
virtual void set_region(Region region) = 0;
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return mos6560_->get_crt(); }
virtual std::shared_ptr<Outputs::Speaker> 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<Machine> 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<uint8_t> 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<Vic6560> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_;
std::shared_ptr<KeyboardVIA> keyboard_via_;
std::shared_ptr<SerialPort> serial_port_;
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
// Tape
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
bool use_fast_tape_hack_;
bool is_running_at_zero_cost_;
// Disk
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
void install_disk_rom();
/// Enables or disables turbo-speed tape loading.
virtual void set_use_fast_tape_hack(bool activate) = 0;
};
}

View File

@ -18,17 +18,21 @@
using namespace Commodore::Vic20;
@implementation CSVic20 {
Machine _vic20;
std::unique_ptr<Machine> _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;
}
}
}