1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-11 14:05:21 +00:00

Merge pull request #353 from TomHarte/ColecoVision

Adds provisional emulation of the ColecoVision
This commit is contained in:
Thomas Harte 2018-03-01 22:33:34 -05:00 committed by GitHub
commit 6cce9aa54e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 819 additions and 42 deletions

View File

@ -14,6 +14,7 @@ namespace Analyser {
enum class Machine {
AmstradCPC,
Atari2600,
ColecoVision,
Electron,
MSX,
Oric,

View File

@ -0,0 +1,45 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 23/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges;
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
// only one mapped item is allowed
if(segments.size() != 1) continue;
// which must be 8, 12, 16, 24 or 32 kb in size
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
const std::size_t data_size = segment.data.size();
if((data_size&8191) && (data_size != 12*1024)) continue;
if(data_size < 8192 || data_size > 32768) continue;
// the two first bytes must be 0xaa and 0x55, either way around
if(segment.data[0] != 0xaa && segment.data[0] != 0x55 && segment.data[1] != 0xaa && segment.data[1] != 0x55) continue;
if(segment.data[0] == segment.data[1]) continue;
// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768.
coleco_cartridges.push_back(cartridge);
}
return coleco_cartridges;
}
void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::ColecoVision;
target->confidence = 0.5;
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
if(!target->media.empty())
destination.push_back(std::move(target));
}

View File

@ -0,0 +1,25 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp
#define StaticAnalyser_Coleco_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Coleco {
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@ -16,6 +16,7 @@
#include "Acorn/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "Atari/StaticAnalyser.hpp"
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
@ -90,6 +91,7 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
@ -115,7 +117,10 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
}
}
Format("rom", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn | TargetPlatform::MSX) // ROM
Format( "rom",
result.cartridges,
Cartridge::BinaryDump,
TargetPlatform::Acorn | TargetPlatform::MSX | TargetPlatform::ColecoVision) // ROM
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
@ -153,6 +158,7 @@ std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const char *fi
if(potential_platforms & TargetPlatform::Acorn) Acorn::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::AmstradCPC) AmstradCPC::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Atari2600) Atari::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::ColecoVision) Coleco::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Commodore) Commodore::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::MSX) MSX::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Oric) Oric::AddTargets(media, targets);

View File

@ -108,6 +108,7 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
}
evaluate_output_volume();
printf("%d ", output_volume_);
for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
target[c] = output_volume_;

View File

@ -0,0 +1,153 @@
//
// SN76489.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "SN76489.hpp"
#include <cassert>
#include <cmath>
using namespace TI;
SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) {
// Build a volume table.
double multiplier = pow(10.0, -0.1);
double volume = 8191.0f;
for(int c = 0; c < 16; ++c) {
volumes_[c] = (int)round(volume);
volume *= multiplier;
}
volumes_[15] = 0;
evaluate_output_volume();
switch(personality) {
case Personality::SN76494:
master_divider_period_ = 2;
shifter_is_16bit_ = false;
break;
case Personality::SN76489:
master_divider_period_ = 16;
shifter_is_16bit_ = false;
break;
case Personality::SMS:
master_divider_period_ = 16;
shifter_is_16bit_ = true;
break;
}
assert((master_divider_period_ % additional_divider) == 0);
assert(additional_divider < master_divider_period_);
master_divider_period_ /= additional_divider;
}
void SN76489::set_register(uint8_t value) {
task_queue_.defer([value, this] () {
if(value & 0x80) {
active_register_ = value;
}
const int channel = (active_register_ >> 5)&3;
if(active_register_ & 0x10) {
// latch for volume
channels_[channel].volume = value & 0xf;
evaluate_output_volume();
} else {
// latch for tone/data
if(channel < 3) {
if(value & 0x80) {
channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf);
} else {
channels_[channel].divider = static_cast<uint16_t>((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4));
}
} else {
// writes to the noise register always reset the shifter
noise_shifter_ = shifter_is_16bit_ ? 0x8000 : 0x4000;
if(value & 4) {
noise_mode_ = shifter_is_16bit_ ? Noise16 : Noise15;
} else {
noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15;
}
channels_[3].divider = static_cast<uint16_t>(0x10 << (value & 3));
// Special case: if these bits are both set, the noise channel should track channel 2,
// which is marked with a divider of 0xffff.
if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff;
}
}
});
}
void SN76489::evaluate_output_volume() {
output_volume_ = static_cast<int16_t>(
channels_[0].level * volumes_[channels_[0].volume] +
channels_[1].level * volumes_[channels_[1].volume] +
channels_[2].level * volumes_[channels_[2].volume] +
channels_[3].level * volumes_[channels_[3].volume]
);
}
void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) {
std::size_t c = 0;
while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) {
target[c] = output_volume_;
master_divider_++;
c++;
}
while(c < number_of_samples) {
bool did_flip = false;
#define step_channel(x, s) \
if(channels_[x].counter) channels_[x].counter--;\
else {\
channels_[x].level ^= 1;\
channels_[x].counter = channels_[x].divider;\
s;\
}
step_channel(0, /**/);
step_channel(1, /**/);
step_channel(2, did_flip = true);
#undef step_channel
if(channels_[3].divider != 0xffff) {
if(channels_[3].counter) channels_[3].counter--;
else {
did_flip = true;
channels_[3].counter = channels_[3].divider;
}
}
if(did_flip) {
channels_[3].level = noise_shifter_ & 1;
int new_bit = channels_[3].level;
switch(noise_mode_) {
default: break;
case Noise15:
new_bit ^= (noise_shifter_ >> 1);
break;
case Noise16:
new_bit ^= (noise_shifter_ >> 3);
break;
}
noise_shifter_ >>= 1;
noise_shifter_ |= (new_bit & 1) << (shifter_is_16bit_ ? 15 : 14);
}
evaluate_output_volume();
for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) {
target[c] = output_volume_;
c++;
master_divider_++;
}
}
master_divider_ &= (master_divider_period_ - 1);
}

View File

@ -0,0 +1,66 @@
//
// SN76489.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef SN76489_hpp
#define SN76489_hpp
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
namespace TI {
class SN76489: public Outputs::Speaker::SampleSource {
public:
enum class Personality {
SN76489,
SN76494,
SMS
};
/// Creates a new SN76489.
SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1);
/// Writes a new value to the SN76489.
void set_register(uint8_t value);
// As per SampleSource.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
private:
int master_divider_ = 0;
int master_divider_period_ = 16;
int16_t output_volume_ = 0;
void evaluate_output_volume();
int volumes_[16];
Concurrency::DeferringAsyncTaskQueue &task_queue_;
struct ToneChannel {
// Programmatically-set state; updated by the processor.
uint16_t divider = 0;
uint8_t volume = 0xf;
// Active state; self-evolving as a function of time.
uint16_t counter = 0;
int level = 0;
} channels_[4];
enum {
Periodic15,
Periodic16,
Noise15,
Noise16
} noise_mode_ = Periodic15;
uint16_t noise_shifter_ = 0;
int active_register_ = 0;
bool shifter_is_16bit_ = false;
};
}
#endif /* SN76489_hpp */

View File

@ -80,6 +80,10 @@ void AsyncTaskQueue::flush() {
#endif
}
DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
perform();
}
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
if(!deferred_tasks_) {
deferred_tasks_.reset(new std::list<std::function<void(void)>>);

View File

@ -30,7 +30,7 @@ namespace Concurrency {
class AsyncTaskQueue {
public:
AsyncTaskQueue();
~AsyncTaskQueue();
virtual ~AsyncTaskQueue();
/*!
Adds @c function to the queue.
@ -69,6 +69,8 @@ class AsyncTaskQueue {
*/
class DeferringAsyncTaskQueue: public AsyncTaskQueue {
public:
~DeferringAsyncTaskQueue();
/*!
Adds a function to the deferral list.

View File

@ -17,6 +17,11 @@ BestEffortUpdater::BestEffortUpdater() {
update_is_ongoing_.clear();
}
BestEffortUpdater::~BestEffortUpdater() {
// Don't allow further deconstruction until the task queue is stopped.
flush();
}
void BestEffortUpdater::update() {
// Perform an update only if one is not currently ongoing.
if(!update_is_ongoing_.test_and_set()) {

View File

@ -26,6 +26,7 @@ namespace Concurrency {
class BestEffortUpdater {
public:
BestEffortUpdater();
~BestEffortUpdater();
/// A delegate receives timing cues.
struct Delegate {

View File

@ -21,18 +21,45 @@ class Joystick {
public:
virtual ~Joystick() {}
enum class DigitalInput {
Up, Down, Left, Right, Fire
struct DigitalInput {
enum Type {
Up, Down, Left, Right, Fire,
Key
} type;
union {
struct {
int index;
} control;
struct {
wchar_t symbol;
} key;
} info;
DigitalInput(Type type, int index = 0) : type(type) {
info.control.index = index;
}
DigitalInput(wchar_t symbol) : type(Key) {
info.key.symbol = symbol;
}
bool operator == (const DigitalInput &rhs) {
if(rhs.type != type) return false;
if(rhs.type == Key) {
return rhs.info.key.symbol == info.key.symbol;
} else {
return rhs.info.control.index == info.control.index;
}
}
};
virtual std::vector<DigitalInput> get_inputs() = 0;
// Host interface.
virtual void set_digital_input(DigitalInput digital_input, bool is_active) = 0;
virtual void set_digital_input(const DigitalInput &digital_input, bool is_active) = 0;
virtual void reset_all_inputs() {
set_digital_input(DigitalInput::Up, false);
set_digital_input(DigitalInput::Down, false);
set_digital_input(DigitalInput::Left, false);
set_digital_input(DigitalInput::Right, false);
set_digital_input(DigitalInput::Fire, false);
for(const auto &input: get_inputs()) {
set_digital_input(input, false);
}
}
};

View File

@ -36,8 +36,18 @@ class Joystick: public Inputs::Joystick {
Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) :
bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {}
void set_digital_input(DigitalInput digital_input, bool is_active) {
switch(digital_input) {
std::vector<DigitalInput> get_inputs() override {
return {
DigitalInput(DigitalInput::Up),
DigitalInput(DigitalInput::Down),
DigitalInput(DigitalInput::Left),
DigitalInput(DigitalInput::Right),
DigitalInput(DigitalInput::Fire)
};
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
switch(digital_input.type) {
case DigitalInput::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
case DigitalInput::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
case DigitalInput::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break;
@ -50,6 +60,8 @@ class Joystick: public Inputs::Joystick {
else
bus_->tia_input_value_[fire_tia_input_] |= 0x80;
break;
default: break;
}
}

View File

@ -0,0 +1,304 @@
//
// ColecoVision.cpp
// Clock Signal
//
// Created by Thomas Harte on 23/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "ColecoVision.hpp"
#include "../../Processors/Z80/Z80.hpp"
#include "../../Components/9918/9918.hpp"
#include "../../Components/SN76489/SN76489.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
namespace {
const int sn76489_divider = 4;
}
namespace Coleco {
namespace Vision {
class Joystick: public Inputs::Joystick {
public:
std::vector<DigitalInput> get_inputs() override {
return {
DigitalInput(DigitalInput::Up),
DigitalInput(DigitalInput::Down),
DigitalInput(DigitalInput::Left),
DigitalInput(DigitalInput::Right),
DigitalInput(DigitalInput::Fire, 0),
DigitalInput(DigitalInput::Fire, 1),
DigitalInput('0'), DigitalInput('1'), DigitalInput('2'),
DigitalInput('3'), DigitalInput('4'), DigitalInput('5'),
DigitalInput('6'), DigitalInput('7'), DigitalInput('8'),
DigitalInput('9'), DigitalInput('*'), DigitalInput('#'),
};
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
switch(digital_input.type) {
default: return;
case DigitalInput::Key:
if(!is_active) keypad_ |= 0xf;
else {
uint8_t mask = 0xf;
switch(digital_input.info.key.symbol) {
case '8': mask = 0x1; break;
case '4': mask = 0x2; break;
case '5': mask = 0x3; break;
case '7': mask = 0x5; break;
case '#': mask = 0x6; break;
case '2': mask = 0x7; break;
case '*': mask = 0x9; break;
case '0': mask = 0xa; break;
case '9': mask = 0xb; break;
case '3': mask = 0xc; break;
case '1': mask = 0xd; break;
case '6': mask = 0xe; break;
default: break;
}
keypad_ = (keypad_ & 0xf0) | mask;
}
break;
case DigitalInput::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
case DigitalInput::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
case DigitalInput::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
case DigitalInput::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
case DigitalInput::Fire:
switch(digital_input.info.control.index) {
default: break;
case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break;
case 1: if(is_active) keypad_ &= ~0x40; else keypad_ |= 0x40; break;
}
break;
}
}
uint8_t get_direction_input() {
return direction_;
}
uint8_t get_keypad_input() {
return keypad_;
}
private:
uint8_t direction_ = 0xff;
uint8_t keypad_ = 0xff;
};
class ConcreteMachine:
public Machine,
public CPU::Z80::BusHandler,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public JoystickMachine::Machine {
public:
ConcreteMachine() :
z80_(*this),
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
speaker_(sn76489_) {
speaker_.set_input_rate(3579545.0f / static_cast<float>(sn76489_divider));
set_clock_rate(3579545);
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
void setup_output(float aspect_ratio) override {
vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A));
get_crt()->set_output_device(Outputs::CRT::OutputDevice::Television);
}
void close_output() override {
vdp_.reset();
}
Outputs::CRT::CRT *get_crt() override {
return vdp_->get_crt();
}
Outputs::Speaker::Speaker *get_speaker() override {
return &speaker_;
}
void run_for(const Cycles cycles) override {
z80_.run_for(cycles);
}
void configure_as_target(const Analyser::Static::Target &target) override {
// Insert the media.
insert_media(target.media);
}
bool insert_media(const Analyser::Static::Media &media) override {
if(!media.cartridges.empty()) {
const auto &segment = media.cartridges.front()->get_segments().front();
cartridge_ = segment.data;
}
return true;
}
// Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
auto roms = roms_with_names(
"ColecoVision",
{ "coleco.rom" });
if(!roms[0]) return false;
bios_ = *roms[0];
bios_.resize(8192);
return true;
}
// MARK: Z80::BusHandler
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
case CPU::Z80::PartialMachineCycle::Read:
if(address < 0x2000) {
*cycle.value = bios_[address & 0x1fff];
} else if(address >= 0x6000 && address < 0x8000) {
*cycle.value = ram_[address & 1023];
} else if(address >= 0x8000) {
*cycle.value = cartridge_[(address - 0x8000) % cartridge_.size()]; // This probably isn't how 24kb ROMs work?
} else {
*cycle.value = 0xff;
}
break;
case CPU::Z80::PartialMachineCycle::Write:
if(address >= 0x6000 && address < 0x8000) {
ram_[address & 1023] = *cycle.value;
}
break;
case CPU::Z80::PartialMachineCycle::Input:
switch((address >> 5) & 7) {
case 5:
update_video();
*cycle.value = vdp_->get_register(address);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 7: {
const std::size_t joystick_id = (address&2) >> 1;
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
if(joysticks_in_keypad_mode_) {
*cycle.value = joystick->get_keypad_input();
} else {
*cycle.value = joystick->get_direction_input();
}
} break;
default:
*cycle.value = 0xff;
break;
}
break;
case CPU::Z80::PartialMachineCycle::Output: {
const int eighth = (address >> 5) & 7;
switch(eighth) {
case 4: case 6:
joysticks_in_keypad_mode_ = eighth == 4;
break;
case 5:
update_video();
vdp_->set_register(address, *cycle.value);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 7:
update_audio();
sn76489_.set_register(*cycle.value);
break;
default: break;
}
} break;
default: break;
}
time_since_vdp_update_ += cycle.length;
time_since_sn76489_update_ += cycle.length;
if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= cycle.length;
if(time_until_interrupt_ <= HalfCycles(0)) {
z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_);
}
}
return HalfCycles(0);
}
void flush() {
update_video();
update_audio();
audio_queue_.perform();
}
private:
void update_audio() {
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider)));
}
void update_video() {
vdp_->run_for(time_since_vdp_update_.flush());
}
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
std::unique_ptr<TI::TMS9918> vdp_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TI::SN76489 sn76489_;
Outputs::Speaker::LowpassSpeaker<TI::SN76489> speaker_;
std::vector<uint8_t> bios_;
std::vector<uint8_t> cartridge_;
uint8_t ram_[1024];
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
bool joysticks_in_keypad_mode_ = false;
HalfCycles time_since_vdp_update_;
HalfCycles time_since_sn76489_update_;
HalfCycles time_until_interrupt_;
};
}
}
using namespace Coleco::Vision;
Machine *Machine::ColecoVision() {
return new ConcreteMachine;
}
Machine::~Machine() {}

View File

@ -0,0 +1,24 @@
//
// ColecoVision.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ColecoVision_hpp
#define ColecoVision_hpp
namespace Coleco {
namespace Vision {
class Machine {
public:
virtual ~Machine();
static Machine *ColecoVision();
};
}
}
#endif /* ColecoVision_hpp */

View File

@ -252,9 +252,19 @@ class Joystick: public Inputs::Joystick {
user_port_via_port_handler_(user_port_via_port_handler),
keyboard_via_port_handler_(keyboard_via_port_handler) {}
void set_digital_input(DigitalInput digital_input, bool is_active) override {
std::vector<DigitalInput> get_inputs() override {
return {
DigitalInput(DigitalInput::Up),
DigitalInput(DigitalInput::Down),
DigitalInput(DigitalInput::Left),
DigitalInput(DigitalInput::Right),
DigitalInput(DigitalInput::Fire)
};
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
JoystickInput mapped_input;
switch (digital_input) {
switch(digital_input.type) {
default: return;
case DigitalInput::Up: mapped_input = Up; break;
case DigitalInput::Down: mapped_input = Down; break;

View File

@ -10,6 +10,7 @@
#include "../AmstradCPC/AmstradCPC.hpp"
#include "../Atari2600/Atari2600.hpp"
#include "../ColecoVision/ColecoVision.hpp"
#include "../Commodore/Vic-20/Vic20.hpp"
#include "../Electron/Electron.hpp"
#include "../MSX/MSX.hpp"
@ -27,6 +28,7 @@ namespace {
switch(target.machine) {
case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC()); break;
case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600()); break;
case Analyser::Machine::ColecoVision: machine = new Machine::TypedDynamicMachine<Coleco::Vision::Machine>(Coleco::Vision::Machine::ColecoVision()); break;
case Analyser::Machine::Electron: machine = new Machine::TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron()); break;
case Analyser::Machine::MSX: machine = new Machine::TypedDynamicMachine<MSX::Machine>(MSX::Machine::MSX()); break;
case Analyser::Machine::Oric: machine = new Machine::TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric()); break;
@ -88,6 +90,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
switch(machine) {
case Analyser::Machine::AmstradCPC: return "AmstradCPC";
case Analyser::Machine::Atari2600: return "Atari2600";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Electron";
case Analyser::Machine::MSX: return "MSX";
case Analyser::Machine::Oric: return "Oric";
@ -102,6 +105,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
switch(machine) {
case Analyser::Machine::AmstradCPC: return "Amstrad CPC";
case Analyser::Machine::Atari2600: return "Atari 2600";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Acorn Electron";
case Analyser::Machine::MSX: return "MSX";
case Analyser::Machine::Oric: return "Oric";

View File

@ -220,6 +220,8 @@
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; };
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4431E3AF38600141F11 /* floppy525.png */; };
4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90E42041097C008514A2 /* ColecoVision.cpp */; };
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; };
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; };
4B7BC7F61F58F7D200D1B1B4 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */; };
@ -297,6 +299,10 @@
4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */; };
4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAF2B4C2004580C00480230 /* DMK.cpp */; };
4BAF2B4F2004580C00480230 /* DMK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAF2B4C2004580C00480230 /* DMK.cpp */; };
4BB0A65B2044FD3000FB3688 /* SN76489.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0A6592044FD3000FB3688 /* SN76489.cpp */; };
4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0A6592044FD3000FB3688 /* SN76489.cpp */; };
4BB0A65D2045009000FB3688 /* ColecoVision.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90E42041097C008514A2 /* ColecoVision.cpp */; };
4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; };
4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; };
4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; };
4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; };
@ -880,6 +886,10 @@
4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = "<group>"; };
4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = "<group>"; };
4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = "<group>"; };
4B7A90E32041097C008514A2 /* ColecoVision.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ColecoVision.hpp; sourceTree = "<group>"; };
4B7A90E42041097C008514A2 /* ColecoVision.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ColecoVision.cpp; sourceTree = "<group>"; };
4B7A90EB20410A85008514A2 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BestEffortUpdater.cpp; path = ../../Concurrency/BestEffortUpdater.cpp; sourceTree = "<group>"; };
4B80ACFF1F85CACA00176895 /* BestEffortUpdater.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BestEffortUpdater.hpp; path = ../../Concurrency/BestEffortUpdater.hpp; sourceTree = "<group>"; };
4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PartialMachineCycle.cpp; sourceTree = "<group>"; };
@ -978,6 +988,8 @@
4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = "<group>"; };
4BAF2B4D2004580C00480230 /* DMK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMK.hpp; sourceTree = "<group>"; };
4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; };
4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = "<group>"; };
4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = "<group>"; };
4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = "<group>"; };
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
@ -2020,6 +2032,24 @@
name = MSX;
sourceTree = "<group>";
};
4B7A90E22041097C008514A2 /* ColecoVision */ = {
isa = PBXGroup;
children = (
4B7A90E32041097C008514A2 /* ColecoVision.hpp */,
4B7A90E42041097C008514A2 /* ColecoVision.cpp */,
);
path = ColecoVision;
sourceTree = "<group>";
};
4B7A90EA20410A85008514A2 /* Coleco */ = {
isa = PBXGroup;
children = (
4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */,
4B7A90EB20410A85008514A2 /* StaticAnalyser.hpp */,
);
path = Coleco;
sourceTree = "<group>";
};
4B8334881F5DB8470097E338 /* Implementation */ = {
isa = PBXGroup;
children = (
@ -2114,6 +2144,7 @@
4B8944EB201967B4007DE474 /* Acorn */,
4B894514201967B4007DE474 /* AmstradCPC */,
4B8944F3201967B4007DE474 /* Atari */,
4B7A90EA20410A85008514A2 /* Coleco */,
4B8944FB201967B4007DE474 /* Commodore */,
4B894507201967B4007DE474 /* Disassembler */,
4B89450F201967B4007DE474 /* MSX */,
@ -2242,6 +2273,15 @@
path = Disk;
sourceTree = "<group>";
};
4BB0A6582044FD3000FB3688 /* SN76489 */ = {
isa = PBXGroup;
children = (
4BB0A6592044FD3000FB3688 /* SN76489.cpp */,
4BB0A65A2044FD3000FB3688 /* SN76489.hpp */,
);
path = SN76489;
sourceTree = "<group>";
};
4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = {
isa = PBXGroup;
children = (
@ -2647,6 +2687,7 @@
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */,
4B38F3491F2EC12000D9235D /* AmstradCPC */,
4B2E2D961C3A06EC00138695 /* Atari2600 */,
4B7A90E22041097C008514A2 /* ColecoVision */,
4B4DC81D1D2C2425003C5BF8 /* Commodore */,
4B2E2D9E1C3A070900138695 /* Electron */,
4B79A4FC1FC8FF9800EEDAD5 /* MSX */,
@ -2753,6 +2794,7 @@
4B0E04F71FC9F2C800F43484 /* 9918 */,
4B4A762D1DB1A35C007AAE2E /* AY38910 */,
4B4B1A39200198C900A0F866 /* KonamiSCC */,
4BB0A6582044FD3000FB3688 /* SN76489 */,
);
name = Components;
path = ../../Components;
@ -3395,6 +3437,7 @@
4B894529201967B4007DE474 /* Disk.cpp in Sources */,
4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */,
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */,
4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */,
4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */,
4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */,
4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */,
@ -3505,6 +3548,8 @@
4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */,
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */,
4B055AE31FAE9B6F0060FFFF /* TextureBuilder.cpp in Sources */,
4BB0A65D2045009000FB3688 /* ColecoVision.cpp in Sources */,
4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */,
4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */,
4B89452D201967B4007DE474 /* Tape.cpp in Sources */,
@ -3520,6 +3565,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */,
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */,
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
@ -3528,6 +3574,7 @@
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */,
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */,
4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */,
@ -3565,6 +3612,7 @@
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */,
4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */,
4BB0A65B2044FD3000FB3688 /* SN76489.cpp in Sources */,
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */,
4B894532201967B4007DE474 /* 6502.cpp in Sources */,
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */,

View File

@ -106,6 +106,7 @@ class MachineDocument:
bestEffortLock.lock()
bestEffortUpdater!.delegate = nil
bestEffortUpdater!.flush()
bestEffortUpdater = nil
bestEffortLock.unlock()
@ -209,17 +210,17 @@ class MachineDocument:
}
func keyDown(_ event: NSEvent) {
self.machine.setKey(event.keyCode, isPressed: true)
self.machine.setKey(event.keyCode, characters: event.characters, isPressed: true)
}
func keyUp(_ event: NSEvent) {
self.machine.setKey(event.keyCode, isPressed: false)
self.machine.setKey(event.keyCode, characters: event.characters, isPressed: false)
}
func flagsChanged(_ newModifiers: NSEvent) {
self.machine.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.shift))
self.machine.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.control))
self.machine.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.command))
self.machine.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.option))
self.machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift))
self.machine.setKey(VK_Control, characters: nil, isPressed: newModifiers.modifierFlags.contains(.control))
self.machine.setKey(VK_Command, characters: nil, isPressed: newModifiers.modifierFlags.contains(.command))
self.machine.setKey(VK_Option, characters: nil, isPressed: newModifiers.modifierFlags.contains(.option))
}
}

View File

@ -373,6 +373,26 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>col</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cartridge</string>
<key>CFBundleTypeName</key>
<string>ColecoVision Cartridge</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@ -41,7 +41,7 @@
- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio;
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty;
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed;
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed;
- (void)clearAllKeys;
@property (nonatomic, strong) CSAudioQueue *audioQueue;

View File

@ -192,7 +192,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
}
}
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
auto keyboard_machine = _machine->keyboard_machine();
if(keyboard_machine) {
@synchronized(self) {
@ -267,8 +267,15 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
case VK_RightArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Right, isPressed); break;
case VK_UpArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Up, isPressed); break;
case VK_DownArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Down, isPressed); break;
case VK_Space: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed); break;
case VK_ANSI_A: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput(Inputs::Joystick::DigitalInput::Fire, 0), isPressed); break;
case VK_ANSI_S: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput(Inputs::Joystick::DigitalInput::Fire, 1), isPressed); break;
default:
joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed); break;
if(characters) {
joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput([characters characterAtIndex:0]), isPressed);
} else {
joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed);
}
break;
}
}

View File

@ -18,6 +18,7 @@ SOURCES += glob.glob('../../Analyser/Static/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Acorn/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/AmstradCPC/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Atari/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Coleco/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Commodore/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/Disassembler/*.cpp')
SOURCES += glob.glob('../../Analyser/Static/MSX/*.cpp')
@ -32,6 +33,7 @@ SOURCES += glob.glob('../../Components/9918/*.cpp')
SOURCES += glob.glob('../../Components/9918/Implementation/*.cpp')
SOURCES += glob.glob('../../Components/AY38910/*.cpp')
SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp')
SOURCES += glob.glob('../../Components/SN76489/*.cpp')
SOURCES += glob.glob('../../Concurrency/*.cpp')
@ -42,6 +44,7 @@ SOURCES += glob.glob('../../Inputs/*.cpp')
SOURCES += glob.glob('../../Machines/*.cpp')
SOURCES += glob.glob('../../Machines/AmstradCPC/*.cpp')
SOURCES += glob.glob('../../Machines/Atari2600/*.cpp')
SOURCES += glob.glob('../../Machines/ColecoVision/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/1540/Implementation/*.cpp')
SOURCES += glob.glob('../../Machines/Commodore/Vic-20/*.cpp')

View File

@ -1001,15 +1001,17 @@ bool ProcessorBase::get_interrupt_line() {
return irq_line_;
}
void ProcessorBase::set_non_maskable_interrupt_line(bool value, int offset) {
void ProcessorBase::set_non_maskable_interrupt_line(bool value, HalfCycles offset) {
// NMIs are edge triggered and cannot be masked.
if(nmi_line_ != value) {
nmi_line_ = value;
if(value) {
request_status_ |= Interrupt::NMI;
if(offset < 0) {
if(offset.as_int() < 0) {
last_request_status_ |= Interrupt::NMI;
}
}
}
}
bool ProcessorBase::get_non_maskable_interrupt_line() {

View File

@ -206,7 +206,7 @@ class ProcessorBase: public ProcessorStorage {
@param offset See discussion in set_interrupt_line.
*/
inline void set_non_maskable_interrupt_line(bool value, int offset = 0);
inline void set_non_maskable_interrupt_line(bool value, HalfCycles offset = 0);
/*!
Gets the value of the non-maskable interrupt line.

View File

@ -0,0 +1,5 @@
ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository.
Expected files:
coleco.rom; an 8kb image of the ColecoVision's BIOS ROM.

View File

@ -20,11 +20,12 @@ enum Type: IntType {
BBCMaster = 1 << 5,
BBCModelA = 1 << 6,
BBCModelB = 1 << 7,
Commodore = 1 << 8,
MSX = 1 << 9,
Oric = 1 << 10,
ZX80 = 1 << 11,
ZX81 = 1 << 12,
ColecoVision = 1 << 8,
Commodore = 1 << 9,
MSX = 1 << 10,
Oric = 1 << 11,
ZX80 = 1 << 12,
ZX81 = 1 << 13,
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB,
ZX8081 = ZX80 | ZX81,