diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index d54bab118..a043d21a7 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -8,11 +8,141 @@ #include "ColecoVision.hpp" +#include "../../Processors/Z80/Z80.hpp" + +#include "../../Components/9918/9918.hpp" + +#include "../CRTMachine.hpp" + +#include "../../ClockReceiver/ForceInline.hpp" + namespace Coleco { namespace Vision { class ConcreteMachine: - public Machine { + public Machine, + public CPU::Z80::BusHandler, + public CRTMachine::Machine { + + public: + ConcreteMachine() : z80_(*this) { + set_clock_rate(3579545); + } + + 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 nullptr; + } + + void run_for(const Cycles cycles) override { + z80_.run_for(cycles); + } + + // Obtains the system ROMs. + bool set_rom_fetcher(const std::function>>(const std::string &machine, const std::vector &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) { + 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_); + } + } + + 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]; + } else if(address >= 0x6000 && address < 0x8000) { + *cycle.value = ram_[address & 1023]; + } 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: + vdp_->run_for(time_since_vdp_update_.flush()); + *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; + + default: + *cycle.value = 0xff; + break; + } + break; + + case CPU::Z80::PartialMachineCycle::Output: + switch((address >> 5) & 7) { + case 5: + vdp_->run_for(time_since_vdp_update_.flush()); + 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; + + default: break; + } + break; + + default: + break; + } + + time_since_vdp_update_ += cycle.length; + return HalfCycles(0); + } + + void flush() { + vdp_->run_for(time_since_vdp_update_.flush()); + } + + private: + CPU::Z80::Processor z80_; + std::unique_ptr vdp_; + + std::vector bios_; + uint8_t ram_[1024]; + + HalfCycles time_since_vdp_update_; + HalfCycles time_until_interrupt_; }; } diff --git a/Processors/Z80/Implementation/Z80Implementation.hpp b/Processors/Z80/Implementation/Z80Implementation.hpp index 0aea4029c..b1e48be49 100644 --- a/Processors/Z80/Implementation/Z80Implementation.hpp +++ b/Processors/Z80/Implementation/Z80Implementation.hpp @@ -1001,12 +1001,12 @@ 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. nmi_line_ = value; if(value) { request_status_ |= Interrupt::NMI; - if(offset < 0) { + if(offset.as_int() < 0) { last_request_status_ |= Interrupt::NMI; } } diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index 216dbc512..7a7dbbbe1 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -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. diff --git a/ROMImages/ColecoVision/readme.txt b/ROMImages/ColecoVision/readme.txt new file mode 100644 index 000000000..39b85b605 --- /dev/null +++ b/ROMImages/ColecoVision/readme.txt @@ -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.