2015-07-16 23:56:02 +00:00
|
|
|
//
|
|
|
|
// Atari2600.cpp
|
2015-07-26 19:25:11 +00:00
|
|
|
// CLK
|
2015-07-16 23:56:02 +00:00
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 14/07/2015.
|
|
|
|
// Copyright © 2015 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "Atari2600.hpp"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <stdio.h>
|
|
|
|
|
2017-03-18 18:01:04 +00:00
|
|
|
#include "CartridgeUnpaged.hpp"
|
2017-03-18 19:04:01 +00:00
|
|
|
#include "CartridgeCommaVid.hpp"
|
|
|
|
#include "CartridgeAtari8k.hpp"
|
2017-03-18 18:01:04 +00:00
|
|
|
|
2015-07-16 23:56:02 +00:00
|
|
|
using namespace Atari2600;
|
2016-05-16 12:01:29 +00:00
|
|
|
namespace {
|
2016-08-14 17:33:20 +00:00
|
|
|
static const double NTSC_clock_rate = 1194720;
|
|
|
|
static const double PAL_clock_rate = 1182298;
|
2016-05-16 12:01:29 +00:00
|
|
|
}
|
2015-07-16 23:56:02 +00:00
|
|
|
|
2015-12-06 21:53:37 +00:00
|
|
|
Machine::Machine() :
|
2016-12-03 19:07:38 +00:00
|
|
|
rom_(nullptr),
|
2017-01-29 02:46:40 +00:00
|
|
|
rom_pages_{nullptr, nullptr, nullptr, nullptr},
|
2017-02-20 02:20:37 +00:00
|
|
|
frame_record_pointer_(0),
|
2017-03-15 00:07:54 +00:00
|
|
|
is_ntsc_(true) {
|
2016-08-14 17:33:20 +00:00
|
|
|
set_clock_rate(NTSC_clock_rate);
|
2016-04-24 10:56:08 +00:00
|
|
|
}
|
|
|
|
|
2017-03-15 00:07:54 +00:00
|
|
|
void Machine::setup_output(float aspect_ratio) {
|
2017-03-18 18:01:04 +00:00
|
|
|
bus_->tia_.reset(new TIA);
|
|
|
|
bus_->speaker_.reset(new Speaker);
|
|
|
|
bus_->speaker_->set_input_rate((float)(get_clock_rate() / 38.0));
|
|
|
|
bus_->tia_->get_crt()->set_delegate(this);
|
2016-05-12 01:07:18 +00:00
|
|
|
}
|
|
|
|
|
2017-03-15 00:07:54 +00:00
|
|
|
void Machine::close_output() {
|
2017-03-18 18:01:04 +00:00
|
|
|
bus_.reset();
|
2016-04-25 00:34:25 +00:00
|
|
|
}
|
|
|
|
|
2017-03-15 00:07:54 +00:00
|
|
|
Machine::~Machine() {
|
2016-04-25 00:34:25 +00:00
|
|
|
close_output();
|
2015-07-28 01:15:10 +00:00
|
|
|
}
|
|
|
|
|
2017-03-15 00:07:54 +00:00
|
|
|
void Machine::set_digital_input(Atari2600DigitalInput input, bool state) {
|
2015-08-19 00:33:24 +00:00
|
|
|
switch (input) {
|
2017-03-18 18:01:04 +00:00
|
|
|
case Atari2600DigitalInputJoy1Up: bus_->mos6532_.update_port_input(0, 0x10, state); break;
|
|
|
|
case Atari2600DigitalInputJoy1Down: bus_->mos6532_.update_port_input(0, 0x20, state); break;
|
|
|
|
case Atari2600DigitalInputJoy1Left: bus_->mos6532_.update_port_input(0, 0x40, state); break;
|
|
|
|
case Atari2600DigitalInputJoy1Right: bus_->mos6532_.update_port_input(0, 0x80, state); break;
|
2016-06-19 22:57:40 +00:00
|
|
|
|
2017-03-18 18:01:04 +00:00
|
|
|
case Atari2600DigitalInputJoy2Up: bus_->mos6532_.update_port_input(0, 0x01, state); break;
|
|
|
|
case Atari2600DigitalInputJoy2Down: bus_->mos6532_.update_port_input(0, 0x02, state); break;
|
|
|
|
case Atari2600DigitalInputJoy2Left: bus_->mos6532_.update_port_input(0, 0x04, state); break;
|
|
|
|
case Atari2600DigitalInputJoy2Right: bus_->mos6532_.update_port_input(0, 0x08, state); break;
|
2015-08-19 00:58:05 +00:00
|
|
|
|
|
|
|
// TODO: latching
|
2017-03-18 18:01:04 +00:00
|
|
|
case Atari2600DigitalInputJoy1Fire: if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break;
|
|
|
|
case Atari2600DigitalInputJoy2Fire: if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break;
|
2015-08-19 00:33:24 +00:00
|
|
|
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-15 00:07:54 +00:00
|
|
|
void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) {
|
2016-06-19 23:36:34 +00:00
|
|
|
switch(input) {
|
2017-03-18 18:01:04 +00:00
|
|
|
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
|
|
|
|
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
|
|
|
|
case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break;
|
|
|
|
case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break;
|
|
|
|
case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break;
|
2016-06-19 23:36:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-15 00:07:54 +00:00
|
|
|
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
|
2017-03-18 18:46:46 +00:00
|
|
|
switch(target.atari.paging_model) {
|
|
|
|
case StaticAnalyser::Atari2600PagingModel::None:
|
|
|
|
bus_.reset(new CartridgeUnpaged(target.cartridges.front()->get_segments().front().data));
|
|
|
|
break;
|
|
|
|
case StaticAnalyser::Atari2600PagingModel::CommaVid:
|
|
|
|
bus_.reset(new CartridgeCommaVid(target.cartridges.front()->get_segments().front().data));
|
|
|
|
break;
|
2017-03-18 19:04:01 +00:00
|
|
|
case StaticAnalyser::Atari2600PagingModel::Atari8k:
|
|
|
|
if(target.atari.uses_superchip) {
|
|
|
|
bus_.reset(new CartridgeAtari8kSuperChip(target.cartridges.front()->get_segments().front().data));
|
|
|
|
} else {
|
|
|
|
bus_.reset(new CartridgeAtari8k(target.cartridges.front()->get_segments().front().data));
|
|
|
|
}
|
|
|
|
break;
|
2017-03-18 18:46:46 +00:00
|
|
|
}
|
|
|
|
|
2017-03-18 18:01:04 +00:00
|
|
|
/* if(!target.cartridges.front()->get_segments().size()) return;
|
2016-09-15 23:34:45 +00:00
|
|
|
Storage::Cartridge::Cartridge::Segment segment = target.cartridges.front()->get_segments().front();
|
|
|
|
size_t length = segment.data.size();
|
|
|
|
|
2017-03-14 21:40:01 +00:00
|
|
|
rom_size_ = length;
|
2016-12-03 19:07:38 +00:00
|
|
|
delete[] rom_;
|
|
|
|
rom_ = new uint8_t[rom_size_];
|
2015-08-17 04:34:01 +00:00
|
|
|
|
|
|
|
size_t offset = 0;
|
2016-12-03 19:07:38 +00:00
|
|
|
const size_t copy_step = std::min(rom_size_, length);
|
2017-03-15 00:07:54 +00:00
|
|
|
while(offset < rom_size_) {
|
2016-12-03 19:07:38 +00:00
|
|
|
size_t copy_length = std::min(copy_step, rom_size_ - offset);
|
|
|
|
memcpy(&rom_[offset], &segment.data[0], copy_length);
|
2015-08-17 04:34:01 +00:00
|
|
|
offset += copy_length;
|
|
|
|
}
|
2015-08-13 12:24:02 +00:00
|
|
|
|
2017-03-01 12:59:25 +00:00
|
|
|
// On a real paged cartridge, any page may initially be visible. Various homebrew authors appear to have
|
|
|
|
// decided the last page will always be initially visible. So do that.
|
2017-03-11 18:04:23 +00:00
|
|
|
size_t rom_mask = rom_size_ - 1;
|
2017-03-01 12:59:25 +00:00
|
|
|
uint8_t *rom_base = rom_;
|
|
|
|
if(rom_size_ > 4096) rom_base = &rom_[rom_size_ - 4096];
|
|
|
|
rom_pages_[0] = rom_base;
|
2017-03-11 18:04:23 +00:00
|
|
|
rom_pages_[1] = &rom_base[1024 & rom_mask];
|
|
|
|
rom_pages_[2] = &rom_base[2048 & rom_mask];
|
|
|
|
rom_pages_[3] = &rom_base[3072 & rom_mask];
|
2017-02-26 22:47:29 +00:00
|
|
|
|
2017-03-15 00:07:54 +00:00
|
|
|
// By default, throw all stores away, and don't ever read from RAM
|
|
|
|
for(int c = 0; c < sizeof(ram_write_targets_) / sizeof(*ram_write_targets_); c++) {
|
|
|
|
ram_write_targets_[c] = throwaway_ram_;
|
|
|
|
ram_read_targets_[c] = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(target.atari.paging_model) {
|
2017-02-28 01:50:59 +00:00
|
|
|
default:
|
2017-03-15 00:07:54 +00:00
|
|
|
if(target.atari.uses_superchip) {
|
|
|
|
// allocate 128 bytes of RAM; allow writing from 0x1000, reading from 0x1080
|
2017-02-28 01:50:59 +00:00
|
|
|
ram_.resize(128);
|
2017-03-15 00:07:54 +00:00
|
|
|
ram_write_targets_[0] = ram_.data();
|
|
|
|
ram_read_targets_[1] = ram_write_targets_[0];
|
2017-02-28 01:50:59 +00:00
|
|
|
}
|
|
|
|
break;
|
2017-03-14 21:25:10 +00:00
|
|
|
case StaticAnalyser::Atari2600PagingModel::CBSRamPlus:
|
2017-03-15 00:07:54 +00:00
|
|
|
// allocate 256 bytes of RAM; allow writing from 0x1000, reading from 0x1100
|
2017-03-14 21:25:10 +00:00
|
|
|
ram_.resize(256);
|
2017-03-15 00:07:54 +00:00
|
|
|
ram_write_targets_[0] = ram_.data();
|
|
|
|
ram_write_targets_[1] = ram_write_targets_[0] + 128;
|
|
|
|
ram_read_targets_[2] = ram_write_targets_[0];
|
|
|
|
ram_read_targets_[3] = ram_write_targets_[1];
|
2017-03-14 21:25:10 +00:00
|
|
|
break;
|
2017-02-28 01:50:59 +00:00
|
|
|
case StaticAnalyser::Atari2600PagingModel::CommaVid:
|
2017-03-15 00:07:54 +00:00
|
|
|
// allocate 1kb of RAM; allow reading from 0x1000, writing from 0x1400
|
2017-02-28 01:50:59 +00:00
|
|
|
ram_.resize(1024);
|
2017-03-15 00:07:54 +00:00
|
|
|
for(int c = 0; c < 8; c++) {
|
|
|
|
ram_read_targets_[c] = ram_.data() + 128 * c;
|
|
|
|
ram_write_targets_[c + 8] = ram_.data() + 128 * c;
|
|
|
|
}
|
2017-02-28 01:50:59 +00:00
|
|
|
break;
|
2017-03-14 21:40:01 +00:00
|
|
|
case StaticAnalyser::Atari2600PagingModel::MegaBoy:
|
|
|
|
mega_boy_page_ = 15;
|
|
|
|
break;
|
2017-03-15 00:07:54 +00:00
|
|
|
case StaticAnalyser::Atari2600PagingModel::MNetwork:
|
|
|
|
ram_.resize(2048);
|
|
|
|
// Put 256 bytes of RAM for writing at 0x1800 and reading at 0x1900
|
|
|
|
ram_write_targets_[16] = ram_.data();
|
|
|
|
ram_write_targets_[17] = ram_write_targets_[16] + 128;
|
|
|
|
ram_read_targets_[18] = ram_write_targets_[16];
|
|
|
|
ram_read_targets_[19] = ram_write_targets_[17];
|
2017-03-15 00:24:05 +00:00
|
|
|
|
|
|
|
rom_pages_[0] = rom_;
|
|
|
|
rom_pages_[1] = rom_pages_[0] + 1024;
|
|
|
|
rom_pages_[2] = rom_pages_[0] + 2048;
|
|
|
|
rom_pages_[3] = rom_pages_[0] + 3072;
|
2017-03-15 00:07:54 +00:00
|
|
|
break;
|
2017-02-28 01:50:59 +00:00
|
|
|
}
|
2017-03-11 17:43:12 +00:00
|
|
|
|
2017-03-18 18:01:04 +00:00
|
|
|
paging_model_ = target.atari.paging_model;*/
|
2016-06-01 23:27:04 +00:00
|
|
|
}
|
2017-02-20 02:20:37 +00:00
|
|
|
|
|
|
|
#pragma mark - CRT delegate
|
|
|
|
|
2017-03-15 00:07:54 +00:00
|
|
|
void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
|
2017-02-20 02:20:37 +00:00
|
|
|
const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
|
2017-03-07 00:37:35 +00:00
|
|
|
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames;
|
|
|
|
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
|
|
|
|
frame_record_pointer_ ++;
|
2017-02-20 02:20:37 +00:00
|
|
|
|
2017-03-15 00:07:54 +00:00
|
|
|
if(frame_record_pointer_ >= 6) {
|
2017-03-07 00:37:35 +00:00
|
|
|
unsigned int total_number_of_frames = 0;
|
|
|
|
unsigned int total_number_of_unexpected_vertical_syncs = 0;
|
2017-03-15 00:07:54 +00:00
|
|
|
for(size_t c = 0; c < number_of_frame_records; c++) {
|
2017-03-07 00:37:35 +00:00
|
|
|
total_number_of_frames += frame_records_[c].number_of_frames;
|
|
|
|
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
|
2017-02-20 02:20:37 +00:00
|
|
|
}
|
|
|
|
|
2017-03-15 00:07:54 +00:00
|
|
|
if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) {
|
|
|
|
for(size_t c = 0; c < number_of_frame_records; c++) {
|
2017-03-07 00:37:35 +00:00
|
|
|
frame_records_[c].number_of_frames = 0;
|
|
|
|
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
|
|
|
|
}
|
|
|
|
is_ntsc_ ^= true;
|
2017-02-20 02:20:37 +00:00
|
|
|
|
2017-03-07 00:37:35 +00:00
|
|
|
double clock_rate;
|
2017-03-15 00:07:54 +00:00
|
|
|
if(is_ntsc_) {
|
2017-03-07 00:37:35 +00:00
|
|
|
clock_rate = NTSC_clock_rate;
|
2017-03-18 18:01:04 +00:00
|
|
|
bus_->tia_->set_output_mode(TIA::OutputMode::NTSC);
|
2017-03-15 00:07:54 +00:00
|
|
|
} else {
|
2017-03-07 00:37:35 +00:00
|
|
|
clock_rate = PAL_clock_rate;
|
2017-03-18 18:01:04 +00:00
|
|
|
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
|
2017-03-07 00:37:35 +00:00
|
|
|
}
|
|
|
|
|
2017-03-18 18:01:04 +00:00
|
|
|
bus_->speaker_->set_input_rate((float)(clock_rate / 38.0));
|
|
|
|
bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / (38.0 * 2.0)));
|
2017-03-07 00:37:35 +00:00
|
|
|
set_clock_rate(clock_rate);
|
|
|
|
}
|
2017-02-20 02:20:37 +00:00
|
|
|
}
|
|
|
|
}
|