1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-05 05:34:20 +00:00

Ensures neither the ColecoVision nor the MSX processes mid-cycles.

This commit is contained in:
Thomas Harte 2018-09-21 22:53:35 -04:00
parent e511261b04
commit d9e65cd758
2 changed files with 276 additions and 271 deletions

View File

@ -197,131 +197,134 @@ class ConcreteMachine:
time_since_vdp_update_ += length; time_since_vdp_update_ += length;
time_since_sn76489_update_ += length; time_since_sn76489_update_ += length;
uint16_t address = cycle.address ? *cycle.address : 0x0000; // Act only if necessary.
switch(cycle.operation) { if(cycle.is_terminal()) {
case CPU::Z80::PartialMachineCycle::ReadOpcode: uint16_t address = cycle.address ? *cycle.address : 0x0000;
if(!address) pc_zero_accesses_++; switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::Read: case CPU::Z80::PartialMachineCycle::ReadOpcode:
if(address < 0x2000) { if(!address) pc_zero_accesses_++;
if(super_game_module_.replace_bios) { case CPU::Z80::PartialMachineCycle::Read:
if(address < 0x2000) {
if(super_game_module_.replace_bios) {
*cycle.value = super_game_module_.ram[address];
} else {
*cycle.value = bios_[address];
}
} else if(super_game_module_.replace_ram && address < 0x8000) {
*cycle.value = super_game_module_.ram[address]; *cycle.value = super_game_module_.ram[address];
} else if(address >= 0x6000 && address < 0x8000) {
*cycle.value = ram_[address & 1023];
} else if(address >= 0x8000 && address <= cartridge_address_limit_) {
if(is_megacart_ && address >= 0xffc0) {
page_megacart(address);
}
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
} else { } else {
*cycle.value = bios_[address]; *cycle.value = 0xff;
} }
} else if(super_game_module_.replace_ram && address < 0x8000) { break;
*cycle.value = super_game_module_.ram[address];
} else if(address >= 0x6000 && address < 0x8000) { case CPU::Z80::PartialMachineCycle::Write:
*cycle.value = ram_[address & 1023]; if(super_game_module_.replace_bios && address < 0x2000) {
} else if(address >= 0x8000 && address <= cartridge_address_limit_) { super_game_module_.ram[address] = *cycle.value;
if(is_megacart_ && address >= 0xffc0) { } else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) {
super_game_module_.ram[address] = *cycle.value;
} else if(address >= 0x6000 && address < 0x8000) {
ram_[address & 1023] = *cycle.value;
} else if(is_megacart_ && address >= 0xffc0) {
page_megacart(address); page_megacart(address);
} }
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff]; break;
} else {
*cycle.value = 0xff;
}
break;
case CPU::Z80::PartialMachineCycle::Write: case CPU::Z80::PartialMachineCycle::Input:
if(super_game_module_.replace_bios && address < 0x2000) { switch((address >> 5) & 7) {
super_game_module_.ram[address] = *cycle.value; case 5:
} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) { update_video();
super_game_module_.ram[address] = *cycle.value; *cycle.value = vdp_->get_register(address);
} else if(address >= 0x6000 && address < 0x8000) { z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
ram_[address & 1023] = *cycle.value; time_until_interrupt_ = vdp_->get_time_until_interrupt();
} else if(is_megacart_ && address >= 0xffc0) { break;
page_megacart(address);
}
break;
case CPU::Z80::PartialMachineCycle::Input: case 7: {
switch((address >> 5) & 7) { const std::size_t joystick_id = (address&2) >> 1;
case 5: Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
update_video(); if(joysticks_in_keypad_mode_) {
*cycle.value = vdp_->get_register(address); *cycle.value = joystick->get_keypad_input();
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); } else {
time_until_interrupt_ = vdp_->get_time_until_interrupt(); *cycle.value = joystick->get_direction_input();
break; }
case 7: { // Hitting exactly the recommended joypad input port is an indicator that
const std::size_t joystick_id = (address&2) >> 1; // this really is a ColecoVision game. The BIOS won't do this when just waiting
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get()); // to start a game (unlike accessing the VDP and SN).
if(joysticks_in_keypad_mode_) { if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
*cycle.value = joystick->get_keypad_input(); } break;
} else {
*cycle.value = joystick->get_direction_input();
}
// Hitting exactly the recommended joypad input port is an indicator that default:
// this really is a ColecoVision game. The BIOS won't do this when just waiting switch(address&0xff) {
// to start a game (unlike accessing the VDP and SN). default: *cycle.value = 0xff; break;
if((address&0xfc) == 0xfc) confidence_counter_.add_hit(); case 0x52:
} break; // Read AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
*cycle.value = ay_.get_data_output();
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
}
break;
}
break;
default: case CPU::Z80::PartialMachineCycle::Output: {
switch(address&0xff) { const int eighth = (address >> 5) & 7;
default: *cycle.value = 0xff; break; switch(eighth) {
case 0x52: case 4: case 6:
// Read AY data. joysticks_in_keypad_mode_ = eighth == 4;
update_audio(); break;
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
*cycle.value = ay_.get_data_output();
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
}
break;
}
break;
case CPU::Z80::PartialMachineCycle::Output: { case 5:
const int eighth = (address >> 5) & 7; update_video();
switch(eighth) { vdp_->set_register(address, *cycle.value);
case 4: case 6: z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
joysticks_in_keypad_mode_ = eighth == 4; time_until_interrupt_ = vdp_->get_time_until_interrupt();
break; break;
case 5: case 7:
update_video(); update_audio();
vdp_->set_register(address, *cycle.value); sn76489_.set_register(*cycle.value);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); break;
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 7: default:
update_audio(); // Catch Super Game Module accesses; it decodes more thoroughly.
sn76489_.set_register(*cycle.value); switch(address&0xff) {
break; default: break;
case 0x7f:
super_game_module_.replace_bios = !((*cycle.value)&0x2);
break;
case 0x50:
// Set AY address.
update_audio();
ay_.set_control_lines(GI::AY38910::BC1);
ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
case 0x51:
// Set AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
case 0x53:
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
break;
}
break;
}
} break;
default: default: break;
// Catch Super Game Module accesses; it decodes more thoroughly. }
switch(address&0xff) {
default: break;
case 0x7f:
super_game_module_.replace_bios = !((*cycle.value)&0x2);
break;
case 0x50:
// Set AY address.
update_audio();
ay_.set_control_lines(GI::AY38910::BC1);
ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
case 0x51:
// Set AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
case 0x53:
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
break;
}
break;
}
} break;
default: break;
} }
if(time_until_interrupt_ > 0) { if(time_until_interrupt_ > 0) {

View File

@ -361,183 +361,185 @@ class ConcreteMachine:
memory_slots_[2].cycles_since_update += total_length; memory_slots_[2].cycles_since_update += total_length;
memory_slots_[3].cycles_since_update += total_length; memory_slots_[3].cycles_since_update += total_length;
uint16_t address = cycle.address ? *cycle.address : 0x0000; if(cycle.is_terminal()) {
switch(cycle.operation) { uint16_t address = cycle.address ? *cycle.address : 0x0000;
case CPU::Z80::PartialMachineCycle::ReadOpcode: switch(cycle.operation) {
if(use_fast_tape_) { case CPU::Z80::PartialMachineCycle::ReadOpcode:
if(address == 0x1a63) { if(use_fast_tape_) {
// TAPION if(address == 0x1a63) {
// TAPION
// Enable the tape motor. // Enable the tape motor.
i8255_.set_register(0xab, 0x8); i8255_.set_register(0xab, 0x8);
// Disable interrupts. // Disable interrupts.
z80_.set_value_of_register(CPU::Z80::Register::IFF1, 0); z80_.set_value_of_register(CPU::Z80::Register::IFF1, 0);
z80_.set_value_of_register(CPU::Z80::Register::IFF2, 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 // Use the parser to find a header, and if one is found then populate
// LOWLIM and WINWID, and reset carry. Otherwise set carry. // LOWLIM and WINWID, and reset carry. Otherwise set carry.
using Parser = Storage::Tape::MSX::Parser; using Parser = Storage::Tape::MSX::Parser;
std::unique_ptr<Parser::FileSpeed> new_speed = Parser::find_header(tape_player_); std::unique_ptr<Parser::FileSpeed> new_speed = Parser::find_header(tape_player_);
if(new_speed) { if(new_speed) {
ram_[0xfca4] = new_speed->minimum_start_bit_duration; ram_[0xfca4] = new_speed->minimum_start_bit_duration;
ram_[0xfca5] = new_speed->low_high_disrimination_duration; ram_[0xfca5] = new_speed->low_high_disrimination_duration;
z80_.set_value_of_register(CPU::Z80::Register::Flags, 0); z80_.set_value_of_register(CPU::Z80::Register::Flags, 0);
} else { } else {
z80_.set_value_of_register(CPU::Z80::Register::Flags, 1); z80_.set_value_of_register(CPU::Z80::Register::Flags, 1);
}
// RET.
*cycle.value = 0xc9;
break;
} }
// RET. if(address == 0x1abc) {
*cycle.value = 0xc9; // TAPIN
break;
}
if(address == 0x1abc) { // Grab the current values of LOWLIM and WINWID.
// TAPIN 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];
// Grab the current values of LOWLIM and WINWID. // Ask the tape parser to grab a byte.
using Parser = Storage::Tape::MSX::Parser; int next_byte = Parser::get_byte(tape_speed, tape_player_);
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. // If a byte was found, return it with carry unset. Otherwise set carry to
int next_byte = Parser::get_byte(tape_speed, tape_player_); // 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);
}
// If a byte was found, return it with carry unset. Otherwise set carry to // RET.
// indicate error. *cycle.value = 0xc9;
if(next_byte >= 0) { break;
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);
} }
// RET.
*cycle.value = 0xc9;
break;
} }
}
if(!address) { if(!address) {
pc_zero_accesses_++; pc_zero_accesses_++;
} }
if(read_pointers_[address >> 13] == unpopulated_) { if(read_pointers_[address >> 13] == unpopulated_) {
performed_unmapped_access_ = true; performed_unmapped_access_ = true;
} }
pc_address_ = address; // This is retained so as to be able to name the source of an access to cartridge handlers. pc_address_ = address; // This is retained so as to be able to name the source of an access to cartridge handlers.
case CPU::Z80::PartialMachineCycle::Read: case CPU::Z80::PartialMachineCycle::Read:
if(read_pointers_[address >> 13]) { if(read_pointers_[address >> 13]) {
*cycle.value = read_pointers_[address >> 13][address & 8191]; *cycle.value = read_pointers_[address >> 13][address & 8191];
} else { } 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);
}
break;
case CPU::Z80::PartialMachineCycle::Write: {
write_pointers_[address >> 13][address & 8191] = *cycle.value;
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); if(memory_slots_[slot_hit].handler) {
*cycle.value = memory_slots_[slot_hit].handler->read(address);
}
break;
case CPU::Z80::PartialMachineCycle::Write: {
write_pointers_[address >> 13][address & 8191] = *cycle.value;
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
if(memory_slots_[slot_hit].handler) {
update_audio();
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]);
}
} break;
case CPU::Z80::PartialMachineCycle::Input:
switch(address & 0xff) {
case 0x98: case 0x99:
vdp_->run_for(time_since_vdp_update_.flush());
*cycle.value = vdp_->get_register(address);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xa2:
update_audio(); update_audio();
ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(GI::AY38910::BC2 | GI::AY38910::BC1)); memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
*cycle.value = ay_.get_data_output(); memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]);
ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(0));
break;
case 0xa8: case 0xa9:
case 0xaa: case 0xab:
*cycle.value = i8255_.get_register(address);
break;
default:
*cycle.value = 0xff;
break;
}
break;
case CPU::Z80::PartialMachineCycle::Output: {
const int port = address & 0xff;
switch(port) {
case 0x98: case 0x99:
vdp_->run_for(time_since_vdp_update_.flush());
vdp_->set_register(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xa0: case 0xa1:
update_audio();
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));
break;
case 0xa8: case 0xa9:
case 0xaa: case 0xab:
i8255_.set_register(address, *cycle.value);
break;
case 0xfc: case 0xfd: case 0xfe: case 0xff:
// printf("RAM banking %02x: %02x\n", port, *cycle.value);
break;
}
} break;
case CPU::Z80::PartialMachineCycle::Interrupt:
*cycle.value = 0xff;
// Take this as a convenient moment to jump into the keyboard buffer, if desired.
if(!input_text_.empty()) {
// The following are KEYBUF per the Red Book; its address and its definition as DEFS 40.
const int buffer_start = 0xfbf0;
const int buffer_size = 40;
// Also from the Red Book: GETPNT is at F3FAH and PUTPNT is at F3F8H.
int read_address = ram_[0xf3fa] | (ram_[0xf3fb] << 8);
int write_address = ram_[0xf3f8] | (ram_[0xf3f9] << 8);
// Write until either the string is exhausted or the write_pointer is immediately
// behind the read pointer; temporarily map write_address and read_address into
// buffer-relative values.
std::size_t characters_written = 0;
write_address -= buffer_start;
read_address -= buffer_start;
while(characters_written < input_text_.size()) {
const int next_write_address = (write_address + 1) % buffer_size;
if(next_write_address == read_address) break;
ram_[write_address + buffer_start] = static_cast<uint8_t>(input_text_[characters_written]);
++characters_written;
write_address = next_write_address;
} }
input_text_.erase(input_text_.begin(), input_text_.begin() + static_cast<std::string::difference_type>(characters_written)); } break;
// Map the write address back into absolute terms and write it out again as PUTPNT. case CPU::Z80::PartialMachineCycle::Input:
write_address += buffer_start; switch(address & 0xff) {
ram_[0xf3f8] = static_cast<uint8_t>(write_address); case 0x98: case 0x99:
ram_[0xf3f9] = static_cast<uint8_t>(write_address >> 8); vdp_->run_for(time_since_vdp_update_.flush());
} *cycle.value = vdp_->get_register(address);
break; z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
default: break; case 0xa2:
update_audio();
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));
break;
case 0xa8: case 0xa9:
case 0xaa: case 0xab:
*cycle.value = i8255_.get_register(address);
break;
default:
*cycle.value = 0xff;
break;
}
break;
case CPU::Z80::PartialMachineCycle::Output: {
const int port = address & 0xff;
switch(port) {
case 0x98: case 0x99:
vdp_->run_for(time_since_vdp_update_.flush());
vdp_->set_register(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xa0: case 0xa1:
update_audio();
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));
break;
case 0xa8: case 0xa9:
case 0xaa: case 0xab:
i8255_.set_register(address, *cycle.value);
break;
case 0xfc: case 0xfd: case 0xfe: case 0xff:
// printf("RAM banking %02x: %02x\n", port, *cycle.value);
break;
}
} break;
case CPU::Z80::PartialMachineCycle::Interrupt:
*cycle.value = 0xff;
// Take this as a convenient moment to jump into the keyboard buffer, if desired.
if(!input_text_.empty()) {
// The following are KEYBUF per the Red Book; its address and its definition as DEFS 40.
const int buffer_start = 0xfbf0;
const int buffer_size = 40;
// Also from the Red Book: GETPNT is at F3FAH and PUTPNT is at F3F8H.
int read_address = ram_[0xf3fa] | (ram_[0xf3fb] << 8);
int write_address = ram_[0xf3f8] | (ram_[0xf3f9] << 8);
// Write until either the string is exhausted or the write_pointer is immediately
// behind the read pointer; temporarily map write_address and read_address into
// buffer-relative values.
std::size_t characters_written = 0;
write_address -= buffer_start;
read_address -= buffer_start;
while(characters_written < input_text_.size()) {
const int next_write_address = (write_address + 1) % buffer_size;
if(next_write_address == read_address) break;
ram_[write_address + buffer_start] = static_cast<uint8_t>(input_text_[characters_written]);
++characters_written;
write_address = next_write_address;
}
input_text_.erase(input_text_.begin(), input_text_.begin() + static_cast<std::string::difference_type>(characters_written));
// Map the write address back into absolute terms and write it out again as PUTPNT.
write_address += buffer_start;
ram_[0xf3f8] = static_cast<uint8_t>(write_address);
ram_[0xf3f9] = static_cast<uint8_t>(write_address >> 8);
}
break;
default: break;
}
} }
if(!tape_player_is_sleeping_) if(!tape_player_is_sleeping_)