2017-11-25 01:43:26 +00:00
|
|
|
|
//
|
|
|
|
|
// MSX.cpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 24/11/2017.
|
|
|
|
|
// Copyright © 2017 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#include "MSX.hpp"
|
|
|
|
|
|
2018-01-08 00:12:52 +00:00
|
|
|
|
#include "DiskROM.hpp"
|
2017-11-30 03:07:30 +00:00
|
|
|
|
#include "Keyboard.hpp"
|
2018-01-05 03:18:18 +00:00
|
|
|
|
#include "ROMSlotHandler.hpp"
|
|
|
|
|
|
2018-01-26 03:16:46 +00:00
|
|
|
|
#include "../../Analyser/Static/MSX/Cartridge.hpp"
|
2018-01-05 03:18:18 +00:00
|
|
|
|
#include "Cartridges/ASCII8kb.hpp"
|
|
|
|
|
#include "Cartridges/ASCII16kb.hpp"
|
|
|
|
|
#include "Cartridges/Konami.hpp"
|
|
|
|
|
#include "Cartridges/KonamiWithSCC.hpp"
|
2017-11-30 03:07:30 +00:00
|
|
|
|
|
2017-11-25 02:59:54 +00:00
|
|
|
|
#include "../../Processors/Z80/Z80.hpp"
|
|
|
|
|
|
2017-11-25 03:05:50 +00:00
|
|
|
|
#include "../../Components/1770/1770.hpp"
|
2017-11-26 18:28:26 +00:00
|
|
|
|
#include "../../Components/9918/9918.hpp"
|
2017-11-25 03:05:50 +00:00
|
|
|
|
#include "../../Components/8255/i8255.hpp"
|
|
|
|
|
#include "../../Components/AY38910/AY38910.hpp"
|
2018-01-07 01:15:55 +00:00
|
|
|
|
#include "../../Components/KonamiSCC/KonamiSCC.hpp"
|
2017-11-25 03:05:50 +00:00
|
|
|
|
|
2017-12-27 03:31:34 +00:00
|
|
|
|
#include "../../Storage/Tape/Parsers/MSX.hpp"
|
2017-12-20 02:53:04 +00:00
|
|
|
|
#include "../../Storage/Tape/Tape.hpp"
|
|
|
|
|
|
2017-11-25 18:33:51 +00:00
|
|
|
|
#include "../CRTMachine.hpp"
|
2017-11-26 18:28:26 +00:00
|
|
|
|
#include "../ConfigurationTarget.hpp"
|
2017-11-30 03:07:30 +00:00
|
|
|
|
#include "../KeyboardMachine.hpp"
|
2017-11-25 18:33:51 +00:00
|
|
|
|
|
2017-12-20 02:08:10 +00:00
|
|
|
|
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
|
2017-12-19 02:39:23 +00:00
|
|
|
|
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
2017-12-20 02:08:10 +00:00
|
|
|
|
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
2017-12-19 02:39:23 +00:00
|
|
|
|
|
2017-12-29 23:36:42 +00:00
|
|
|
|
#include "../../Configurable/StandardOptions.hpp"
|
|
|
|
|
|
2017-11-25 01:43:26 +00:00
|
|
|
|
namespace MSX {
|
|
|
|
|
|
2017-12-29 23:36:42 +00:00
|
|
|
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
|
|
|
|
return Configurable::standard_options(
|
|
|
|
|
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-20 02:08:10 +00:00
|
|
|
|
/*!
|
|
|
|
|
Provides a sample source that can programmatically be set to one of two values.
|
|
|
|
|
*/
|
|
|
|
|
class AudioToggle: public Outputs::Speaker::SampleSource {
|
|
|
|
|
public:
|
|
|
|
|
AudioToggle(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
|
|
|
|
audio_queue_(audio_queue) {}
|
|
|
|
|
|
|
|
|
|
void get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
|
|
|
|
for(std::size_t sample = 0; sample < number_of_samples; ++sample) {
|
|
|
|
|
target[sample] = level_;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void skip_samples(const std::size_t number_of_samples) {}
|
|
|
|
|
|
|
|
|
|
void set_output(bool enabled) {
|
|
|
|
|
if(is_enabled_ == enabled) return;
|
|
|
|
|
is_enabled_ = enabled;
|
|
|
|
|
audio_queue_.defer([=] {
|
|
|
|
|
level_ = enabled ? 4096 : 0;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool get_output() {
|
|
|
|
|
return is_enabled_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
bool is_enabled_ = false;
|
|
|
|
|
int16_t level_ = 0;
|
|
|
|
|
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
|
|
|
|
};
|
|
|
|
|
|
2017-12-20 02:53:04 +00:00
|
|
|
|
class AYPortHandler: public GI::AY38910::PortHandler {
|
|
|
|
|
public:
|
|
|
|
|
AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {}
|
|
|
|
|
|
|
|
|
|
void set_port_output(bool port_b, uint8_t value) {
|
|
|
|
|
if(port_b) {
|
|
|
|
|
// Bits 0–3: touchpad handshaking (?)
|
|
|
|
|
// Bit 4—5: monostable timer pulses
|
|
|
|
|
// Bit 6: joystick select
|
|
|
|
|
// Bit 7: code LED, if any
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t get_port_input(bool port_b) {
|
|
|
|
|
if(!port_b) {
|
|
|
|
|
// Bits 0–5: Joystick (up, down, left, right, A, B)
|
|
|
|
|
// Bit 6: keyboard switch (not universal)
|
|
|
|
|
|
|
|
|
|
// Bit 7: tape input
|
2017-12-20 03:17:42 +00:00
|
|
|
|
return 0x7f | (tape_player_.get_input() ? 0x00 : 0x80);
|
2017-12-20 02:53:04 +00:00
|
|
|
|
}
|
|
|
|
|
return 0xff;
|
|
|
|
|
}
|
2017-11-30 01:31:55 +00:00
|
|
|
|
|
2017-12-20 02:53:04 +00:00
|
|
|
|
private:
|
|
|
|
|
Storage::Tape::BinaryTapePlayer &tape_player_;
|
2017-11-26 21:47:59 +00:00
|
|
|
|
};
|
|
|
|
|
|
2017-11-25 01:43:26 +00:00
|
|
|
|
class ConcreteMachine:
|
2017-11-25 18:33:51 +00:00
|
|
|
|
public Machine,
|
2017-11-25 02:59:54 +00:00
|
|
|
|
public CPU::Z80::BusHandler,
|
2017-11-26 18:28:26 +00:00
|
|
|
|
public CRTMachine::Machine,
|
2017-11-30 03:07:30 +00:00
|
|
|
|
public ConfigurationTarget::Machine,
|
2017-12-29 23:36:42 +00:00
|
|
|
|
public KeyboardMachine::Machine,
|
2018-01-05 03:18:18 +00:00
|
|
|
|
public Configurable::Device,
|
|
|
|
|
public MemoryMap {
|
2017-11-25 02:59:54 +00:00
|
|
|
|
public:
|
|
|
|
|
ConcreteMachine():
|
2017-11-26 21:47:59 +00:00
|
|
|
|
z80_(*this),
|
2017-11-29 02:56:15 +00:00
|
|
|
|
i8255_(i8255_port_handler_),
|
2017-12-18 02:26:06 +00:00
|
|
|
|
ay_(audio_queue_),
|
2017-12-20 02:08:10 +00:00
|
|
|
|
audio_toggle_(audio_queue_),
|
2018-01-07 01:15:55 +00:00
|
|
|
|
scc_(audio_queue_),
|
|
|
|
|
mixer_(ay_, audio_toggle_, scc_),
|
2017-12-20 02:08:10 +00:00
|
|
|
|
speaker_(mixer_),
|
2017-12-20 02:53:04 +00:00
|
|
|
|
tape_player_(3579545 * 2),
|
|
|
|
|
i8255_port_handler_(*this, audio_toggle_, tape_player_),
|
|
|
|
|
ay_port_handler_(tape_player_) {
|
2017-11-25 18:33:51 +00:00
|
|
|
|
set_clock_rate(3579545);
|
2017-11-29 02:56:15 +00:00
|
|
|
|
std::memset(unpopulated_, 0xff, sizeof(unpopulated_));
|
2017-11-30 03:07:30 +00:00
|
|
|
|
clear_all_keys();
|
2017-12-18 02:26:06 +00:00
|
|
|
|
|
|
|
|
|
ay_.set_port_handler(&ay_port_handler_);
|
|
|
|
|
speaker_.set_input_rate(3579545.0f / 2.0f);
|
2017-11-25 18:33:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setup_output(float aspect_ratio) override {
|
2017-11-26 18:28:26 +00:00
|
|
|
|
vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A));
|
2017-11-25 18:33:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void close_output() override {
|
2017-12-02 21:30:43 +00:00
|
|
|
|
vdp_.reset();
|
2017-11-25 18:33:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-18 02:26:06 +00:00
|
|
|
|
Outputs::CRT::CRT *get_crt() override {
|
2017-11-26 18:28:26 +00:00
|
|
|
|
return vdp_->get_crt();
|
2017-11-25 18:33:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-18 02:26:06 +00:00
|
|
|
|
Outputs::Speaker::Speaker *get_speaker() override {
|
|
|
|
|
return &speaker_;
|
2017-11-25 18:33:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void run_for(const Cycles cycles) override {
|
|
|
|
|
z80_.run_for(cycles);
|
2018-02-01 12:53:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float get_confidence() override {
|
2018-02-09 14:10:56 +00:00
|
|
|
|
if(performed_unmapped_access_ || pc_zero_accesses_ > 1) return 0.0f;
|
2018-02-01 12:53:52 +00:00
|
|
|
|
if(memory_slots_[1].handler) {
|
|
|
|
|
return memory_slots_[1].handler->get_confidence();
|
|
|
|
|
}
|
|
|
|
|
return 0.5f;
|
|
|
|
|
}
|
2018-01-23 02:50:56 +00:00
|
|
|
|
|
2018-02-01 12:53:52 +00:00
|
|
|
|
void print_type() override {
|
2018-01-23 02:50:56 +00:00
|
|
|
|
if(memory_slots_[1].handler) {
|
2018-02-01 12:53:52 +00:00
|
|
|
|
memory_slots_[1].handler->print_type();
|
2018-01-23 02:50:56 +00:00
|
|
|
|
}
|
2017-11-25 18:33:51 +00:00
|
|
|
|
}
|
2017-11-27 01:07:30 +00:00
|
|
|
|
|
2018-01-25 02:48:44 +00:00
|
|
|
|
void configure_as_target(const Analyser::Static::Target &target) override {
|
2018-01-08 00:12:52 +00:00
|
|
|
|
// Add a disk cartridge if any disks were supplied.
|
|
|
|
|
if(!target.media.disks.empty()) {
|
|
|
|
|
map(2, 0, 0x4000, 0x2000);
|
|
|
|
|
unmap(2, 0x6000, 0x2000);
|
|
|
|
|
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Insert the media.
|
2017-12-02 21:06:04 +00:00
|
|
|
|
insert_media(target.media);
|
2017-12-29 23:30:46 +00:00
|
|
|
|
|
2018-01-08 00:12:52 +00:00
|
|
|
|
// Type whatever has been requested.
|
2017-12-29 23:30:46 +00:00
|
|
|
|
if(target.loading_command.length()) {
|
|
|
|
|
type_string(target.loading_command);
|
|
|
|
|
}
|
2017-11-26 18:28:26 +00:00
|
|
|
|
}
|
2017-12-03 19:52:42 +00:00
|
|
|
|
|
2018-01-25 02:48:44 +00:00
|
|
|
|
bool insert_media(const Analyser::Static::Media &media) override {
|
2017-12-02 21:06:04 +00:00
|
|
|
|
if(!media.cartridges.empty()) {
|
|
|
|
|
const auto &segment = media.cartridges.front()->get_segments().front();
|
2018-01-05 03:18:18 +00:00
|
|
|
|
memory_slots_[1].source = segment.data;
|
|
|
|
|
map(1, 0, static_cast<uint16_t>(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address));
|
2018-01-26 03:16:46 +00:00
|
|
|
|
|
|
|
|
|
auto msx_cartridge = dynamic_cast<Analyser::Static::MSX::Cartridge *>(media.cartridges.front().get());
|
|
|
|
|
if(msx_cartridge) {
|
|
|
|
|
switch(msx_cartridge->type) {
|
|
|
|
|
default: break;
|
|
|
|
|
case Analyser::Static::MSX::Cartridge::Konami:
|
|
|
|
|
memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1));
|
|
|
|
|
break;
|
|
|
|
|
case Analyser::Static::MSX::Cartridge::KonamiWithSCC:
|
|
|
|
|
memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1, scc_));
|
|
|
|
|
break;
|
|
|
|
|
case Analyser::Static::MSX::Cartridge::ASCII8kb:
|
|
|
|
|
memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1));
|
|
|
|
|
break;
|
|
|
|
|
case Analyser::Static::MSX::Cartridge::ASCII16kb:
|
|
|
|
|
memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-12-02 21:06:04 +00:00
|
|
|
|
}
|
2017-12-20 02:53:04 +00:00
|
|
|
|
|
|
|
|
|
if(!media.tapes.empty()) {
|
|
|
|
|
tape_player_.set_tape(media.tapes.front());
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-08 00:12:52 +00:00
|
|
|
|
if(!media.disks.empty()) {
|
|
|
|
|
DiskROM *disk_rom = dynamic_cast<DiskROM *>(memory_slots_[2].handler.get());
|
|
|
|
|
int drive = 0;
|
|
|
|
|
for(auto &disk : media.disks) {
|
|
|
|
|
disk_rom->set_disk(disk, drive);
|
|
|
|
|
drive++;
|
|
|
|
|
if(drive == 2) break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-26 18:28:26 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2017-11-25 02:59:54 +00:00
|
|
|
|
|
2017-12-29 23:30:46 +00:00
|
|
|
|
void type_string(const std::string &string) override final {
|
|
|
|
|
input_text_ += string;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-06 20:51:29 +00:00
|
|
|
|
// MARK: MSX::MemoryMap
|
2018-01-05 03:18:18 +00:00
|
|
|
|
void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) override {
|
|
|
|
|
assert(!(destination_address & 8191));
|
|
|
|
|
assert(!(length & 8191));
|
|
|
|
|
assert(static_cast<std::size_t>(destination_address) + length <= 65536);
|
|
|
|
|
|
|
|
|
|
for(std::size_t c = 0; c < (length >> 13); ++c) {
|
2018-01-06 20:51:29 +00:00
|
|
|
|
if(memory_slots_[slot].wrapping_strategy == ROMSlotHandler::WrappingStrategy::Repeat) source_address %= memory_slots_[slot].source.size();
|
|
|
|
|
memory_slots_[slot].read_pointers[(destination_address >> 13) + c] =
|
|
|
|
|
(source_address < memory_slots_[slot].source.size()) ? &memory_slots_[slot].source[source_address] : unpopulated_;
|
2018-01-05 03:18:18 +00:00
|
|
|
|
source_address += 8192;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
page_memory(paged_memory_);
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-06 20:51:29 +00:00
|
|
|
|
void unmap(int slot, uint16_t destination_address, std::size_t length) override {
|
|
|
|
|
assert(!(destination_address & 8191));
|
|
|
|
|
assert(!(length & 8191));
|
|
|
|
|
assert(static_cast<std::size_t>(destination_address) + length <= 65536);
|
|
|
|
|
|
|
|
|
|
for(std::size_t c = 0; c < (length >> 13); ++c) {
|
|
|
|
|
memory_slots_[slot].read_pointers[(destination_address >> 13) + c] = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
page_memory(paged_memory_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Ordinary paging.
|
2017-11-29 02:56:15 +00:00
|
|
|
|
void page_memory(uint8_t value) {
|
2018-01-05 03:18:18 +00:00
|
|
|
|
paged_memory_ = value;
|
|
|
|
|
for(std::size_t c = 0; c < 8; c += 2) {
|
2017-12-12 02:09:53 +00:00
|
|
|
|
read_pointers_[c] = memory_slots_[value & 3].read_pointers[c];
|
|
|
|
|
write_pointers_[c] = memory_slots_[value & 3].write_pointers[c];
|
2018-01-05 03:18:18 +00:00
|
|
|
|
read_pointers_[c+1] = memory_slots_[value & 3].read_pointers[c+1];
|
|
|
|
|
write_pointers_[c+1] = memory_slots_[value & 3].write_pointers[c+1];
|
2017-11-30 01:49:02 +00:00
|
|
|
|
value >>= 2;
|
2017-11-29 02:56:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-06 20:51:29 +00:00
|
|
|
|
// MARK: Z80::BusHandler
|
2017-11-26 21:47:59 +00:00
|
|
|
|
HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
2017-11-30 01:31:55 +00:00
|
|
|
|
if(time_until_interrupt_ > 0) {
|
|
|
|
|
time_until_interrupt_ -= cycle.length;
|
|
|
|
|
if(time_until_interrupt_ <= HalfCycles(0)) {
|
|
|
|
|
z80_.set_interrupt_line(true, time_until_interrupt_);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-26 21:47:59 +00:00
|
|
|
|
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
|
|
|
|
switch(cycle.operation) {
|
|
|
|
|
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
2018-01-05 03:18:18 +00:00
|
|
|
|
if(use_fast_tape_ && tape_player_.has_tape()) {
|
2017-12-29 23:36:42 +00:00
|
|
|
|
if(address == 0x1a63) {
|
|
|
|
|
// TAPION
|
|
|
|
|
|
|
|
|
|
// Enable the tape motor.
|
|
|
|
|
i8255_.set_register(0xab, 0x8);
|
|
|
|
|
|
|
|
|
|
// Disable interrupts.
|
|
|
|
|
z80_.set_value_of_register(CPU::Z80::Register::IFF1, 0);
|
|
|
|
|
z80_.set_value_of_register(CPU::Z80::Register::IFF2, 0);
|
|
|
|
|
|
|
|
|
|
// Use the parser to find a header, and if one is found then populate
|
|
|
|
|
// LOWLIM and WINWID, and reset carry. Otherwise set carry.
|
|
|
|
|
using Parser = Storage::Tape::MSX::Parser;
|
|
|
|
|
std::unique_ptr<Parser::FileSpeed> new_speed = Parser::find_header(tape_player_);
|
|
|
|
|
if(new_speed) {
|
|
|
|
|
ram_[0xfca4] = new_speed->minimum_start_bit_duration;
|
|
|
|
|
ram_[0xfca5] = new_speed->low_high_disrimination_duration;
|
|
|
|
|
z80_.set_value_of_register(CPU::Z80::Register::Flags, 0);
|
|
|
|
|
} else {
|
|
|
|
|
z80_.set_value_of_register(CPU::Z80::Register::Flags, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RET.
|
|
|
|
|
*cycle.value = 0xc9;
|
|
|
|
|
break;
|
2017-12-29 03:48:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-29 23:36:42 +00:00
|
|
|
|
if(address == 0x1abc) {
|
|
|
|
|
// TAPIN
|
|
|
|
|
|
|
|
|
|
// Grab the current values of LOWLIM and WINWID.
|
|
|
|
|
using Parser = Storage::Tape::MSX::Parser;
|
|
|
|
|
Parser::FileSpeed tape_speed;
|
|
|
|
|
tape_speed.minimum_start_bit_duration = ram_[0xfca4];
|
|
|
|
|
tape_speed.low_high_disrimination_duration = ram_[0xfca5];
|
|
|
|
|
|
|
|
|
|
// Ask the tape parser to grab a byte.
|
|
|
|
|
int next_byte = Parser::get_byte(tape_speed, tape_player_);
|
|
|
|
|
|
|
|
|
|
// If a byte was found, return it with carry unset. Otherwise set carry to
|
|
|
|
|
// indicate error.
|
|
|
|
|
if(next_byte >= 0) {
|
|
|
|
|
z80_.set_value_of_register(CPU::Z80::Register::A, static_cast<uint16_t>(next_byte));
|
|
|
|
|
z80_.set_value_of_register(CPU::Z80::Register::Flags, 0);
|
|
|
|
|
} else {
|
|
|
|
|
z80_.set_value_of_register(CPU::Z80::Register::Flags, 1);
|
|
|
|
|
}
|
2017-12-27 03:31:34 +00:00
|
|
|
|
|
2017-12-29 23:36:42 +00:00
|
|
|
|
// RET.
|
|
|
|
|
*cycle.value = 0xc9;
|
|
|
|
|
break;
|
2017-12-29 03:48:04 +00:00
|
|
|
|
}
|
2017-12-27 03:31:34 +00:00
|
|
|
|
}
|
2018-02-09 14:10:56 +00:00
|
|
|
|
|
|
|
|
|
if(!address) {
|
|
|
|
|
pc_zero_accesses_++;
|
|
|
|
|
}
|
|
|
|
|
if(read_pointers_[address >> 13] == unpopulated_) {
|
|
|
|
|
performed_unmapped_access_ = true;
|
|
|
|
|
}
|
2018-02-10 22:11:16 +00:00
|
|
|
|
pc_address_ = address; // This is retained so as to be able to name the source of an access to cartridge handlers.
|
2017-11-26 21:47:59 +00:00
|
|
|
|
case CPU::Z80::PartialMachineCycle::Read:
|
2018-01-06 20:51:29 +00:00
|
|
|
|
if(read_pointers_[address >> 13]) {
|
|
|
|
|
*cycle.value = read_pointers_[address >> 13][address & 8191];
|
|
|
|
|
} else {
|
|
|
|
|
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
|
|
|
|
|
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
|
|
|
|
|
*cycle.value = memory_slots_[slot_hit].handler->read(address);
|
|
|
|
|
}
|
2017-11-26 21:47:59 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2018-01-05 03:18:18 +00:00
|
|
|
|
case CPU::Z80::PartialMachineCycle::Write: {
|
|
|
|
|
write_pointers_[address >> 13][address & 8191] = *cycle.value;
|
|
|
|
|
|
|
|
|
|
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
|
2018-01-06 20:51:29 +00:00
|
|
|
|
if(memory_slots_[slot_hit].handler) {
|
2018-01-07 01:15:55 +00:00
|
|
|
|
update_audio();
|
2018-01-06 20:51:29 +00:00
|
|
|
|
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
|
2018-02-10 22:11:16 +00:00
|
|
|
|
memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]);
|
2018-01-06 20:51:29 +00:00
|
|
|
|
}
|
2018-01-05 03:18:18 +00:00
|
|
|
|
} break;
|
2017-11-26 21:47:59 +00:00
|
|
|
|
|
|
|
|
|
case CPU::Z80::PartialMachineCycle::Input:
|
|
|
|
|
switch(address & 0xff) {
|
|
|
|
|
case 0x98: case 0x99:
|
2017-12-02 21:30:43 +00:00
|
|
|
|
vdp_->run_for(time_since_vdp_update_.flush());
|
2017-11-26 21:47:59 +00:00
|
|
|
|
*cycle.value = vdp_->get_register(address);
|
2017-11-30 01:31:55 +00:00
|
|
|
|
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
|
|
|
|
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
2017-11-26 21:47:59 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0xa2:
|
2017-12-20 02:08:10 +00:00
|
|
|
|
update_audio();
|
2017-12-18 02:26:06 +00:00
|
|
|
|
ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(GI::AY38910::BC2 | GI::AY38910::BC1));
|
|
|
|
|
*cycle.value = ay_.get_data_output();
|
|
|
|
|
ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(0));
|
2017-11-26 21:47:59 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0xa8: case 0xa9:
|
|
|
|
|
case 0xaa: case 0xab:
|
|
|
|
|
*cycle.value = i8255_.get_register(address);
|
|
|
|
|
break;
|
2017-12-04 03:25:18 +00:00
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
*cycle.value = 0xff;
|
|
|
|
|
break;
|
2017-11-26 21:47:59 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2017-12-05 00:18:54 +00:00
|
|
|
|
case CPU::Z80::PartialMachineCycle::Output: {
|
|
|
|
|
const int port = address & 0xff;
|
|
|
|
|
switch(port) {
|
2017-11-26 21:47:59 +00:00
|
|
|
|
case 0x98: case 0x99:
|
2017-12-02 21:30:43 +00:00
|
|
|
|
vdp_->run_for(time_since_vdp_update_.flush());
|
2017-11-26 21:47:59 +00:00
|
|
|
|
vdp_->set_register(address, *cycle.value);
|
2017-11-30 01:31:55 +00:00
|
|
|
|
z80_.set_interrupt_line(vdp_->get_interrupt_line());
|
|
|
|
|
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
2017-11-26 21:47:59 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2017-12-05 00:18:54 +00:00
|
|
|
|
case 0xa0: case 0xa1:
|
2017-12-20 02:08:10 +00:00
|
|
|
|
update_audio();
|
2017-12-18 02:26:06 +00:00
|
|
|
|
ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0)));
|
|
|
|
|
ay_.set_data_input(*cycle.value);
|
|
|
|
|
ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(0));
|
2017-11-26 21:47:59 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0xa8: case 0xa9:
|
|
|
|
|
case 0xaa: case 0xab:
|
|
|
|
|
i8255_.set_register(address, *cycle.value);
|
|
|
|
|
break;
|
2017-12-12 02:09:53 +00:00
|
|
|
|
|
|
|
|
|
case 0xfc: case 0xfd: case 0xfe: case 0xff:
|
|
|
|
|
// printf("RAM banking %02x: %02x\n", port, *cycle.value);
|
|
|
|
|
break;
|
2017-11-26 21:47:59 +00:00
|
|
|
|
}
|
2017-12-05 00:18:54 +00:00
|
|
|
|
} break;
|
2017-11-26 21:47:59 +00:00
|
|
|
|
|
2017-12-14 03:44:03 +00:00
|
|
|
|
case CPU::Z80::PartialMachineCycle::Interrupt:
|
|
|
|
|
*cycle.value = 0xff;
|
2017-12-29 23:30:46 +00:00
|
|
|
|
|
|
|
|
|
// Take this as a convenient moment to jump into the keyboard buffer, if desired.
|
|
|
|
|
if(!input_text_.empty()) {
|
|
|
|
|
// TODO: is it safe to assume these addresses?
|
|
|
|
|
const int buffer_start = 0xfbf0;
|
|
|
|
|
const int buffer_end = 0xfb18;
|
|
|
|
|
|
|
|
|
|
int read_address = ram_[0xf3fa] | (ram_[0xf3fb] << 8);
|
|
|
|
|
int write_address = ram_[0xf3f8] | (ram_[0xf3f9] << 8);
|
|
|
|
|
|
|
|
|
|
const int buffer_size = buffer_end - buffer_start;
|
|
|
|
|
int available_space = write_address + buffer_size - read_address - 1;
|
|
|
|
|
|
|
|
|
|
const std::size_t characters_to_write = std::min(static_cast<std::size_t>(available_space), input_text_.size());
|
|
|
|
|
write_address -= buffer_start;
|
|
|
|
|
for(std::size_t c = 0; c < characters_to_write; ++c) {
|
|
|
|
|
char character = input_text_[c];
|
|
|
|
|
ram_[write_address + buffer_start] = static_cast<uint8_t>(character);
|
|
|
|
|
write_address = (write_address + 1) % buffer_size;
|
|
|
|
|
}
|
|
|
|
|
write_address += buffer_start;
|
|
|
|
|
input_text_.erase(input_text_.begin(), input_text_.begin() + static_cast<std::string::difference_type>(characters_to_write));
|
|
|
|
|
|
|
|
|
|
ram_[0xf3f8] = static_cast<uint8_t>(write_address);
|
|
|
|
|
ram_[0xf3f9] = static_cast<uint8_t>(write_address >> 8);
|
|
|
|
|
}
|
2017-12-14 03:44:03 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2017-11-26 21:47:59 +00:00
|
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-20 02:53:04 +00:00
|
|
|
|
// Update the tape. (TODO: allow for sleeping)
|
|
|
|
|
tape_player_.run_for(cycle.length.as_int());
|
|
|
|
|
|
2017-11-26 21:47:59 +00:00
|
|
|
|
// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read,
|
|
|
|
|
// but otherwise runs without pause.
|
2017-11-27 01:07:30 +00:00
|
|
|
|
HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
|
|
|
|
|
time_since_vdp_update_ += cycle.length + addition;
|
2017-12-02 21:30:43 +00:00
|
|
|
|
time_since_ay_update_ += cycle.length + addition;
|
2018-01-06 20:51:29 +00:00
|
|
|
|
memory_slots_[0].cycles_since_update += cycle.length + addition;
|
|
|
|
|
memory_slots_[1].cycles_since_update += cycle.length + addition;
|
|
|
|
|
memory_slots_[2].cycles_since_update += cycle.length + addition;
|
|
|
|
|
memory_slots_[3].cycles_since_update += cycle.length + addition;
|
2017-11-27 01:07:30 +00:00
|
|
|
|
return addition;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void flush() {
|
2017-12-02 21:30:43 +00:00
|
|
|
|
vdp_->run_for(time_since_vdp_update_.flush());
|
2017-12-20 02:08:10 +00:00
|
|
|
|
update_audio();
|
2017-12-18 02:26:06 +00:00
|
|
|
|
audio_queue_.perform();
|
2017-11-26 21:47:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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(
|
|
|
|
|
"MSX",
|
|
|
|
|
{
|
2018-01-08 00:12:52 +00:00
|
|
|
|
"msx.rom",
|
|
|
|
|
"disk.rom"
|
2017-11-26 21:47:59 +00:00
|
|
|
|
});
|
|
|
|
|
|
2018-01-08 00:12:52 +00:00
|
|
|
|
if(!roms[0] || !roms[1]) return false;
|
2017-11-26 21:47:59 +00:00
|
|
|
|
|
2018-01-05 03:18:18 +00:00
|
|
|
|
memory_slots_[0].source = std::move(*roms[0]);
|
|
|
|
|
memory_slots_[0].source.resize(32768);
|
2017-11-26 21:47:59 +00:00
|
|
|
|
|
2018-01-08 00:12:52 +00:00
|
|
|
|
memory_slots_[2].source = std::move(*roms[1]);
|
|
|
|
|
memory_slots_[2].source.resize(16384);
|
|
|
|
|
|
2018-01-05 03:18:18 +00:00
|
|
|
|
for(size_t c = 0; c < 8; ++c) {
|
2017-11-30 01:49:02 +00:00
|
|
|
|
for(size_t slot = 0; slot < 3; ++slot) {
|
2017-12-12 02:09:53 +00:00
|
|
|
|
memory_slots_[slot].read_pointers[c] = unpopulated_;
|
|
|
|
|
memory_slots_[slot].write_pointers[c] = scratch_;
|
2017-11-30 01:49:02 +00:00
|
|
|
|
}
|
2017-12-12 02:09:53 +00:00
|
|
|
|
|
|
|
|
|
memory_slots_[3].read_pointers[c] =
|
2018-01-05 03:18:18 +00:00
|
|
|
|
memory_slots_[3].write_pointers[c] = &ram_[c * 8192];
|
2017-11-30 01:49:02 +00:00
|
|
|
|
}
|
2017-12-12 02:09:53 +00:00
|
|
|
|
|
2018-01-05 03:18:18 +00:00
|
|
|
|
map(0, 0, 0, 32768);
|
|
|
|
|
page_memory(0);
|
2017-11-26 23:34:40 +00:00
|
|
|
|
|
2017-11-26 21:47:59 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-30 03:07:30 +00:00
|
|
|
|
void set_keyboard_line(int line) {
|
|
|
|
|
selected_key_line_ = line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t read_keyboard() {
|
|
|
|
|
return key_states_[selected_key_line_];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void clear_all_keys() override {
|
|
|
|
|
std::memset(key_states_, 0xff, sizeof(key_states_));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_key_state(uint16_t key, bool is_pressed) override {
|
|
|
|
|
int mask = 1 << (key & 7);
|
|
|
|
|
int line = key >> 4;
|
|
|
|
|
if(is_pressed) key_states_[line] &= ~mask; else key_states_[line] |= mask;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-09 21:31:05 +00:00
|
|
|
|
KeyboardMapper *get_keyboard_mapper() override {
|
|
|
|
|
return &keyboard_mapper_;
|
2017-11-30 03:07:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-12-29 23:36:42 +00:00
|
|
|
|
// MARK: - Configuration options.
|
|
|
|
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
|
|
|
|
return MSX::get_options();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
|
|
|
|
bool quickload;
|
|
|
|
|
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
|
|
|
|
|
use_fast_tape_ = quickload;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Configurable::Display display;
|
|
|
|
|
if(Configurable::get_display(selections_by_option, display)) {
|
|
|
|
|
get_crt()->set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Configurable::SelectionSet get_accurate_selections() override {
|
|
|
|
|
Configurable::SelectionSet selection_set;
|
|
|
|
|
Configurable::append_quick_load_tape_selection(selection_set, false);
|
|
|
|
|
Configurable::append_display_selection(selection_set, Configurable::Display::Composite);
|
|
|
|
|
return selection_set;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Configurable::SelectionSet get_user_friendly_selections() override {
|
|
|
|
|
Configurable::SelectionSet selection_set;
|
|
|
|
|
Configurable::append_quick_load_tape_selection(selection_set, true);
|
|
|
|
|
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
|
|
|
|
return selection_set;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-25 02:59:54 +00:00
|
|
|
|
private:
|
2017-12-20 02:08:10 +00:00
|
|
|
|
void update_audio() {
|
|
|
|
|
speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-29 02:56:15 +00:00
|
|
|
|
class i8255PortHandler: public Intel::i8255::PortHandler {
|
|
|
|
|
public:
|
2017-12-20 02:53:04 +00:00
|
|
|
|
i8255PortHandler(ConcreteMachine &machine, AudioToggle &audio_toggle, Storage::Tape::BinaryTapePlayer &tape_player) :
|
|
|
|
|
machine_(machine), audio_toggle_(audio_toggle), tape_player_(tape_player) {}
|
2017-11-29 02:56:15 +00:00
|
|
|
|
|
|
|
|
|
void set_value(int port, uint8_t value) {
|
|
|
|
|
switch(port) {
|
|
|
|
|
case 0: machine_.page_memory(value); break;
|
2017-12-20 02:08:10 +00:00
|
|
|
|
case 2: {
|
2017-11-30 03:07:30 +00:00
|
|
|
|
// TODO:
|
|
|
|
|
// b6 caps lock LED
|
|
|
|
|
// b5 audio output
|
2017-12-20 02:53:04 +00:00
|
|
|
|
|
|
|
|
|
// b4: cassette motor relay
|
2017-12-20 03:17:42 +00:00
|
|
|
|
tape_player_.set_motor_control(!(value & 0x10));
|
2017-12-20 02:08:10 +00:00
|
|
|
|
|
|
|
|
|
// b7: keyboard click
|
|
|
|
|
bool new_audio_level = !!(value & 0x80);
|
|
|
|
|
if(audio_toggle_.get_output() != new_audio_level) {
|
|
|
|
|
machine_.update_audio();
|
|
|
|
|
audio_toggle_.set_output(new_audio_level);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// b0–b3: keyboard line
|
2017-11-30 03:07:30 +00:00
|
|
|
|
machine_.set_keyboard_line(value & 0xf);
|
2017-12-20 02:08:10 +00:00
|
|
|
|
} break;
|
2017-12-04 03:25:18 +00:00
|
|
|
|
default: printf("What what what what?\n"); break;
|
2017-11-29 02:56:15 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t get_value(int port) {
|
|
|
|
|
if(port == 1) {
|
2017-11-30 03:07:30 +00:00
|
|
|
|
return machine_.read_keyboard();
|
2017-11-30 01:31:55 +00:00
|
|
|
|
} else printf("What what?\n");
|
2017-11-29 02:56:15 +00:00
|
|
|
|
return 0xff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
ConcreteMachine &machine_;
|
2017-12-20 02:08:10 +00:00
|
|
|
|
AudioToggle &audio_toggle_;
|
2017-12-20 02:53:04 +00:00
|
|
|
|
Storage::Tape::BinaryTapePlayer &tape_player_;
|
2017-11-29 02:56:15 +00:00
|
|
|
|
};
|
|
|
|
|
|
2017-11-25 02:59:54 +00:00
|
|
|
|
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
2017-11-26 18:28:26 +00:00
|
|
|
|
std::unique_ptr<TI::TMS9918> vdp_;
|
2017-11-26 21:47:59 +00:00
|
|
|
|
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
2017-12-18 02:26:06 +00:00
|
|
|
|
|
|
|
|
|
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
|
|
|
|
GI::AY38910::AY38910 ay_;
|
2017-12-20 02:08:10 +00:00
|
|
|
|
AudioToggle audio_toggle_;
|
2018-01-07 01:15:55 +00:00
|
|
|
|
Konami::SCC scc_;
|
|
|
|
|
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle, Konami::SCC> mixer_;
|
|
|
|
|
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle, Konami::SCC>> speaker_;
|
2017-11-26 21:47:59 +00:00
|
|
|
|
|
2017-12-20 02:53:04 +00:00
|
|
|
|
Storage::Tape::BinaryTapePlayer tape_player_;
|
2017-12-29 23:36:42 +00:00
|
|
|
|
bool use_fast_tape_ = false;
|
2017-12-20 02:53:04 +00:00
|
|
|
|
|
2017-11-26 21:47:59 +00:00
|
|
|
|
i8255PortHandler i8255_port_handler_;
|
|
|
|
|
AYPortHandler ay_port_handler_;
|
|
|
|
|
|
2018-01-05 03:18:18 +00:00
|
|
|
|
uint8_t paged_memory_ = 0;
|
|
|
|
|
uint8_t *read_pointers_[8];
|
|
|
|
|
uint8_t *write_pointers_[8];
|
2017-11-30 01:49:02 +00:00
|
|
|
|
|
2017-12-12 02:09:53 +00:00
|
|
|
|
struct MemorySlots {
|
2018-01-05 03:18:18 +00:00
|
|
|
|
uint8_t *read_pointers[8];
|
|
|
|
|
uint8_t *write_pointers[8];
|
|
|
|
|
|
2018-01-06 20:51:29 +00:00
|
|
|
|
void set_handler(ROMSlotHandler *slot_handler) {
|
|
|
|
|
handler.reset(slot_handler);
|
|
|
|
|
wrapping_strategy = handler->wrapping_strategy();
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-05 03:18:18 +00:00
|
|
|
|
std::unique_ptr<ROMSlotHandler> handler;
|
|
|
|
|
std::vector<uint8_t> source;
|
2018-01-06 20:51:29 +00:00
|
|
|
|
HalfCycles cycles_since_update;
|
|
|
|
|
ROMSlotHandler::WrappingStrategy wrapping_strategy = ROMSlotHandler::WrappingStrategy::Repeat;
|
2017-12-12 02:09:53 +00:00
|
|
|
|
} memory_slots_[4];
|
2017-11-30 01:49:02 +00:00
|
|
|
|
|
2017-11-26 21:47:59 +00:00
|
|
|
|
uint8_t ram_[65536];
|
2018-01-05 03:18:18 +00:00
|
|
|
|
uint8_t scratch_[8192];
|
|
|
|
|
uint8_t unpopulated_[8192];
|
2017-11-27 01:07:30 +00:00
|
|
|
|
|
|
|
|
|
HalfCycles time_since_vdp_update_;
|
2017-12-02 21:30:43 +00:00
|
|
|
|
HalfCycles time_since_ay_update_;
|
2017-11-30 01:31:55 +00:00
|
|
|
|
HalfCycles time_until_interrupt_;
|
2017-11-30 03:07:30 +00:00
|
|
|
|
|
|
|
|
|
uint8_t key_states_[16];
|
|
|
|
|
int selected_key_line_ = 0;
|
2017-12-29 23:30:46 +00:00
|
|
|
|
std::string input_text_;
|
2017-11-30 03:07:30 +00:00
|
|
|
|
|
|
|
|
|
MSX::KeyboardMapper keyboard_mapper_;
|
2018-02-09 14:10:56 +00:00
|
|
|
|
|
|
|
|
|
int pc_zero_accesses_ = 0;
|
|
|
|
|
bool performed_unmapped_access_ = false;
|
2018-02-10 22:11:16 +00:00
|
|
|
|
uint16_t pc_address_;
|
2017-11-25 01:43:26 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using namespace MSX;
|
|
|
|
|
|
|
|
|
|
Machine *Machine::MSX() {
|
|
|
|
|
return new ConcreteMachine;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Machine::~Machine() {}
|