mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-27 06:35:04 +00:00
Ensures neither the ColecoVision nor the MSX processes mid-cycles.
This commit is contained in:
parent
e511261b04
commit
d9e65cd758
@ -197,131 +197,134 @@ class ConcreteMachine:
|
||||
time_since_vdp_update_ += length;
|
||||
time_since_sn76489_update_ += length;
|
||||
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(!address) pc_zero_accesses_++;
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(address < 0x2000) {
|
||||
if(super_game_module_.replace_bios) {
|
||||
// Act only if necessary.
|
||||
if(cycle.is_terminal()) {
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(!address) pc_zero_accesses_++;
|
||||
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];
|
||||
} 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 {
|
||||
*cycle.value = bios_[address];
|
||||
*cycle.value = 0xff;
|
||||
}
|
||||
} else if(super_game_module_.replace_ram && address < 0x8000) {
|
||||
*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) {
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
if(super_game_module_.replace_bios && address < 0x2000) {
|
||||
super_game_module_.ram[address] = *cycle.value;
|
||||
} 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);
|
||||
}
|
||||
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
|
||||
} else {
|
||||
*cycle.value = 0xff;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
if(super_game_module_.replace_bios && address < 0x2000) {
|
||||
super_game_module_.ram[address] = *cycle.value;
|
||||
} 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);
|
||||
}
|
||||
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 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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
// Hitting exactly the recommended joypad input port is an indicator that
|
||||
// this really is a ColecoVision game. The BIOS won't do this when just waiting
|
||||
// to start a game (unlike accessing the VDP and SN).
|
||||
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
|
||||
} break;
|
||||
|
||||
// Hitting exactly the recommended joypad input port is an indicator that
|
||||
// this really is a ColecoVision game. The BIOS won't do this when just waiting
|
||||
// to start a game (unlike accessing the VDP and SN).
|
||||
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
|
||||
} break;
|
||||
default:
|
||||
switch(address&0xff) {
|
||||
default: *cycle.value = 0xff; break;
|
||||
case 0x52:
|
||||
// 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:
|
||||
switch(address&0xff) {
|
||||
default: *cycle.value = 0xff; break;
|
||||
case 0x52:
|
||||
// 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;
|
||||
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 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 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;
|
||||
|
||||
case 7:
|
||||
update_audio();
|
||||
sn76489_.set_register(*cycle.value);
|
||||
break;
|
||||
default:
|
||||
// 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:
|
||||
// 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;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if(time_until_interrupt_ > 0) {
|
||||
|
@ -361,183 +361,185 @@ class ConcreteMachine:
|
||||
memory_slots_[2].cycles_since_update += total_length;
|
||||
memory_slots_[3].cycles_since_update += total_length;
|
||||
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(use_fast_tape_) {
|
||||
if(address == 0x1a63) {
|
||||
// TAPION
|
||||
if(cycle.is_terminal()) {
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(use_fast_tape_) {
|
||||
if(address == 0x1a63) {
|
||||
// TAPION
|
||||
|
||||
// Enable the tape motor.
|
||||
i8255_.set_register(0xab, 0x8);
|
||||
// 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);
|
||||
// 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);
|
||||
// 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;
|
||||
}
|
||||
|
||||
// RET.
|
||||
*cycle.value = 0xc9;
|
||||
break;
|
||||
}
|
||||
if(address == 0x1abc) {
|
||||
// TAPIN
|
||||
|
||||
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];
|
||||
|
||||
// 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_);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// RET.
|
||||
*cycle.value = 0xc9;
|
||||
break;
|
||||
}
|
||||
|
||||
// RET.
|
||||
*cycle.value = 0xc9;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!address) {
|
||||
pc_zero_accesses_++;
|
||||
}
|
||||
if(read_pointers_[address >> 13] == unpopulated_) {
|
||||
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.
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(read_pointers_[address >> 13]) {
|
||||
*cycle.value = read_pointers_[address >> 13][address & 8191];
|
||||
} else {
|
||||
if(!address) {
|
||||
pc_zero_accesses_++;
|
||||
}
|
||||
if(read_pointers_[address >> 13] == unpopulated_) {
|
||||
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.
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write: {
|
||||
write_pointers_[address >> 13][address & 8191] = *cycle.value;
|
||||
|
||||
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;
|
||||
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:
|
||||
if(memory_slots_[slot_hit].handler) {
|
||||
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;
|
||||
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]);
|
||||
}
|
||||
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.
|
||||
write_address += buffer_start;
|
||||
ram_[0xf3f8] = static_cast<uint8_t>(write_address);
|
||||
ram_[0xf3f9] = static_cast<uint8_t>(write_address >> 8);
|
||||
}
|
||||
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;
|
||||
|
||||
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_)
|
||||
|
Loading…
x
Reference in New Issue
Block a user