mirror of
https://github.com/TomHarte/CLK.git
synced 2026-03-11 04:42:20 +00:00
Merge branch 'master' into QueueDelegate
This commit is contained in:
@@ -17,7 +17,10 @@
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
namespace {
|
||||
ROM::Name rom_name(Personality personality) {
|
||||
|
||||
// MARK: - Construction, including ROM requests.
|
||||
|
||||
ROM::Name rom_name(const Personality personality) {
|
||||
switch(personality) {
|
||||
default:
|
||||
case Personality::C1540: return ROM::Name::Commodore1540;
|
||||
@@ -26,11 +29,11 @@ ROM::Name rom_name(Personality personality) {
|
||||
}
|
||||
}
|
||||
|
||||
ROM::Request Machine::rom_request(Personality personality) {
|
||||
ROM::Request Machine::rom_request(const Personality personality) {
|
||||
return ROM::Request(rom_name(personality));
|
||||
}
|
||||
|
||||
MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
|
||||
MachineBase::MachineBase(const Personality personality, const ROM::Map &roms) :
|
||||
Storage::Disk::Controller(1000000),
|
||||
m6502_(*this),
|
||||
drive_VIA_(drive_VIA_port_handler_),
|
||||
@@ -58,14 +61,13 @@ MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
|
||||
std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size()));
|
||||
}
|
||||
|
||||
Machine::Machine(Personality personality, const ROM::Map &roms) :
|
||||
Machine::Machine(const Personality personality, const ROM::Map &roms) :
|
||||
MachineBase(personality, roms) {}
|
||||
|
||||
void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) {
|
||||
Commodore::Serial::attach(serial_port_, serial_bus);
|
||||
}
|
||||
// MARK: - 6502 bus.
|
||||
|
||||
Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||
Cycles MachineBase::perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
|
||||
/*
|
||||
Memory map (given that I'm unsure yet on any potential mirroring):
|
||||
|
||||
@@ -75,24 +77,28 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
|
||||
0xc000-0xffff ROM
|
||||
*/
|
||||
if(address < 0x800) {
|
||||
if(is_read(operation))
|
||||
*value = ram_[address];
|
||||
if constexpr (is_read(operation))
|
||||
value = ram_[address];
|
||||
else
|
||||
ram_[address] = *value;
|
||||
ram_[address] = value;
|
||||
} else if(address >= 0xc000) {
|
||||
if(is_read(operation)) {
|
||||
*value = rom_[address & 0x3fff];
|
||||
if constexpr (is_read(operation)) {
|
||||
value = rom_[address & 0x3fff];
|
||||
}
|
||||
} else if(address >= 0x1800 && address <= 0x180f) {
|
||||
if(is_read(operation))
|
||||
*value = serial_port_VIA_.read(address);
|
||||
if constexpr (is_read(operation))
|
||||
value = serial_port_VIA_.read(address);
|
||||
else
|
||||
serial_port_VIA_.write(address, *value);
|
||||
serial_port_VIA_.write(address, value);
|
||||
} else if(address >= 0x1c00 && address <= 0x1c0f) {
|
||||
if(is_read(operation))
|
||||
*value = drive_VIA_.read(address);
|
||||
if constexpr (is_read(operation))
|
||||
value = drive_VIA_.read(address);
|
||||
else
|
||||
drive_VIA_.write(address, *value);
|
||||
drive_VIA_.write(address, value);
|
||||
} else {
|
||||
if constexpr (is_read(operation)) {
|
||||
value = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
serial_port_VIA_.run_for(Cycles(1));
|
||||
@@ -101,34 +107,42 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
|
||||
return Cycles(1);
|
||||
}
|
||||
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||
get_drive().set_disk(disk);
|
||||
}
|
||||
|
||||
void Machine::run_for(const Cycles cycles) {
|
||||
m6502_.run_for(cycles);
|
||||
|
||||
const bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
|
||||
const bool drive_motor = drive_VIA_port_handler_.motor_enabled();
|
||||
get_drive().set_motor_on(drive_motor);
|
||||
if(drive_motor)
|
||||
if(drive_motor) {
|
||||
Storage::Disk::Controller::run_for(cycles);
|
||||
}
|
||||
}
|
||||
|
||||
void MachineBase::set_activity_observer(Activity::Observer *observer) {
|
||||
// MARK: - External attachments.
|
||||
|
||||
void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) {
|
||||
Commodore::Serial::attach(serial_port_, serial_bus);
|
||||
}
|
||||
|
||||
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
|
||||
get_drive().set_disk(disk);
|
||||
drive_VIA_port_handler_.set_is_read_only(disk->is_read_only());
|
||||
}
|
||||
|
||||
void MachineBase::set_activity_observer(Activity::Observer *const observer) {
|
||||
drive_VIA_.bus_handler().set_activity_observer(observer);
|
||||
get_drive().set_activity_observer(observer, "Drive", false);
|
||||
}
|
||||
|
||||
// MARK: - 6522 delegate
|
||||
// MARK: - 6522 delegate.
|
||||
|
||||
void MachineBase::mos6522_did_change_interrupt_status(void *) {
|
||||
// both VIAs are connected to the IRQ line
|
||||
m6502_.set_irq_line(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
||||
m6502_.set<CPU::MOS6502Mk2::Line::IRQ>(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line());
|
||||
}
|
||||
|
||||
// MARK: - Disk drive
|
||||
// MARK: - Disk drive.
|
||||
|
||||
void MachineBase::process_input_bit(int value) {
|
||||
void MachineBase::process_input_bit(const int value) {
|
||||
shift_register_ = (shift_register_ << 1) | value;
|
||||
if((shift_register_ & 0x3ff) == 0x3ff) {
|
||||
drive_VIA_port_handler_.set_sync_detected(true);
|
||||
@@ -140,11 +154,11 @@ void MachineBase::process_input_bit(int value) {
|
||||
if(bit_window_offset_ == 8) {
|
||||
drive_VIA_port_handler_.set_data_input(uint8_t(shift_register_));
|
||||
bit_window_offset_ = 0;
|
||||
if(drive_VIA_port_handler_.get_should_set_overflow()) {
|
||||
m6502_.set_overflow_line(true);
|
||||
if(drive_VIA_port_handler_.should_set_overflow()) {
|
||||
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(true);
|
||||
}
|
||||
}
|
||||
else m6502_.set_overflow_line(false);
|
||||
else m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(false);
|
||||
}
|
||||
|
||||
// the 1540 does not recognise index holes
|
||||
@@ -164,7 +178,9 @@ void MachineBase::drive_via_did_set_data_density(DriveVIA &, const int density)
|
||||
|
||||
template <MOS::MOS6522::Port port>
|
||||
uint8_t SerialPortVIA::get_port_input() const {
|
||||
if(port) return port_b_;
|
||||
if(port) {
|
||||
return port_b_;
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
@@ -203,13 +219,15 @@ void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) {
|
||||
|
||||
void SerialPortVIA::update_data_line() {
|
||||
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
|
||||
serial_port_->set_output(::Commodore::Serial::Line::Data,
|
||||
Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
|
||||
serial_port_->set_output(
|
||||
::Commodore::Serial::Line::Data,
|
||||
Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))
|
||||
);
|
||||
}
|
||||
|
||||
// MARK: - DriveVIA
|
||||
|
||||
void DriveVIA::set_delegate(Delegate *delegate) {
|
||||
void DriveVIA::set_delegate(Delegate *const delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
@@ -220,18 +238,22 @@ uint8_t DriveVIA::get_port_input() const {
|
||||
}
|
||||
|
||||
void DriveVIA::set_sync_detected(const bool sync_detected) {
|
||||
port_b_ = (port_b_ & 0x7f) | (sync_detected ? 0x00 : 0x80);
|
||||
port_b_ = (port_b_ & ~0x80) | (sync_detected ? 0x00 : 0x80);
|
||||
}
|
||||
|
||||
void DriveVIA::set_data_input(uint8_t value) {
|
||||
void DriveVIA::set_is_read_only(const bool is_read_only) {
|
||||
port_b_ = (port_b_ & ~0x10) | (is_read_only ? 0x00 : 0x10);
|
||||
}
|
||||
|
||||
void DriveVIA::set_data_input(const uint8_t value) {
|
||||
port_a_ = value;
|
||||
}
|
||||
|
||||
bool DriveVIA::get_should_set_overflow() {
|
||||
bool DriveVIA::should_set_overflow() {
|
||||
return should_set_overflow_;
|
||||
}
|
||||
|
||||
bool DriveVIA::get_motor_enabled() {
|
||||
bool DriveVIA::motor_enabled() {
|
||||
return drive_motor_;
|
||||
}
|
||||
|
||||
@@ -240,36 +262,48 @@ void DriveVIA::set_control_line_output(const bool value) {
|
||||
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
||||
should_set_overflow_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
template <MOS::MOS6522::Port port>
|
||||
void DriveVIA::set_port_output(const uint8_t value, uint8_t) {
|
||||
if(port) {
|
||||
if(previous_port_b_output_ != value) {
|
||||
// Record drive motor state.
|
||||
drive_motor_ = value&4;
|
||||
|
||||
// Check for a head step.
|
||||
const int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
||||
if(step_difference) {
|
||||
if(delegate_) delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1);
|
||||
}
|
||||
|
||||
// Check for a change in density.
|
||||
const int density_difference = (previous_port_b_output_^value) & (3 << 5);
|
||||
if(density_difference && delegate_) {
|
||||
delegate_->drive_via_did_set_data_density(*this, (value >> 5)&3);
|
||||
}
|
||||
|
||||
// Post the LED status.
|
||||
if(observer_) observer_->set_led_status("Drive", value&8);
|
||||
|
||||
previous_port_b_output_ = value;
|
||||
if(port == MOS::MOS6522::Port::B && line == MOS::MOS6522::Line::Two) {
|
||||
// TODO: 0 = write, 1 = read.
|
||||
if(!value) {
|
||||
printf("NOT IMPLEMENTED: write mode\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DriveVIA::set_activity_observer(Activity::Observer *observer) {
|
||||
template <>
|
||||
void DriveVIA::set_port_output<MOS::MOS6522::Port::B>(const uint8_t value, uint8_t) {
|
||||
if(previous_port_b_output_ != value) {
|
||||
// Record drive motor state.
|
||||
drive_motor_ = value&4;
|
||||
|
||||
// Check for a head step.
|
||||
const int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
||||
if(step_difference && delegate_) {
|
||||
delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1);
|
||||
}
|
||||
|
||||
// Check for a change in density.
|
||||
const int density_difference = (previous_port_b_output_^value) & (3 << 5);
|
||||
if(density_difference && delegate_) {
|
||||
delegate_->drive_via_did_set_data_density(*this, (value >> 5)&3);
|
||||
}
|
||||
|
||||
// Post the LED status.
|
||||
if(observer_) {
|
||||
observer_->set_led_status("Drive", value&8);
|
||||
}
|
||||
|
||||
previous_port_b_output_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void DriveVIA::set_port_output<MOS::MOS6522::Port::A>(const uint8_t value, uint8_t) {
|
||||
printf("TODO: output is %02x\n", value);
|
||||
}
|
||||
|
||||
void DriveVIA::set_activity_observer(Activity::Observer *const observer) {
|
||||
observer_ = observer;
|
||||
if(observer) {
|
||||
observer->register_led("Drive");
|
||||
@@ -277,9 +311,9 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SerialPort
|
||||
// MARK: - SerialPort.
|
||||
|
||||
void SerialPort::set_input(Serial::Line line, Serial::LineLevel level) {
|
||||
void SerialPort::set_input(const Serial::Line line, const Serial::LineLevel level) {
|
||||
serial_port_via_->set_serial_line_state(line, bool(level), *via_);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Processors/6502/6502.hpp"
|
||||
#include "Processors/6502Mk2/6502Mk2.hpp"
|
||||
#include "Components/6522/6522.hpp"
|
||||
|
||||
#include "Machines/Commodore/SerialBus.hpp"
|
||||
@@ -65,15 +65,17 @@ private:
|
||||
It is wired up such that Port B contains:
|
||||
Bits 0/1: head step direction
|
||||
Bit 2: motor control
|
||||
Bit 3: LED control (TODO)
|
||||
Bit 3: LED control
|
||||
Bit 4: write protect photocell status (TODO)
|
||||
Bits 5/6: read/write density
|
||||
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
|
||||
|
||||
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
|
||||
|
||||
It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on
|
||||
whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO.
|
||||
Elsewhere:
|
||||
* CA2 might is used to set processor overflow;
|
||||
* CA1 a strobe for data input; and
|
||||
* CB2 indicates read/write mode; 1 = read, 0 = write.
|
||||
*/
|
||||
class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
@@ -88,8 +90,9 @@ public:
|
||||
|
||||
void set_sync_detected(bool);
|
||||
void set_data_input(uint8_t);
|
||||
bool get_should_set_overflow();
|
||||
bool get_motor_enabled();
|
||||
void set_is_read_only(bool);
|
||||
bool should_set_overflow();
|
||||
bool motor_enabled();
|
||||
|
||||
template <MOS::MOS6522::Port, MOS::MOS6522::Line>
|
||||
void set_control_line_output(bool value);
|
||||
@@ -122,7 +125,6 @@ private:
|
||||
};
|
||||
|
||||
class MachineBase:
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||
public DriveVIA::Delegate,
|
||||
public Storage::Disk::Controller {
|
||||
@@ -134,7 +136,8 @@ public:
|
||||
void set_activity_observer(Activity::Observer *);
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation, uint16_t address, uint8_t *value);
|
||||
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||
Cycles perform(const AddressT, CPU::MOS6502Mk2::data_t<operation>);
|
||||
|
||||
protected:
|
||||
// to satisfy MOS::MOS6522::Delegate
|
||||
@@ -144,7 +147,12 @@ protected:
|
||||
void drive_via_did_step_head(DriveVIA &, int direction) override;
|
||||
void drive_via_did_set_data_density(DriveVIA &, int density) override;
|
||||
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
|
||||
struct M6502Traits {
|
||||
static constexpr auto uses_ready_line = false;
|
||||
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::AnyCycle;
|
||||
using BusHandlerT = MachineBase;
|
||||
};
|
||||
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
|
||||
|
||||
uint8_t ram_[0x800];
|
||||
uint8_t rom_[0x4000];
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#include "Machines/MachineTypes.hpp"
|
||||
#include "Machines/Utility/MemoryFuzzer.hpp"
|
||||
#include "Processors/6502/6502.hpp"
|
||||
#include "Processors/6502Mk2/6502Mk2.hpp"
|
||||
#include "Analyser/Static/Commodore/Target.hpp"
|
||||
#include "Outputs/Log.hpp"
|
||||
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
@@ -240,14 +240,11 @@ public:
|
||||
// HACK. NOCOMMIT.
|
||||
// int pulse_num_ = 0;
|
||||
|
||||
Cycles perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
const uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
|
||||
// Determine from the TED video subsystem the length of this clock cycle as perceived by the 6502,
|
||||
// relative to the master clock.
|
||||
const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready);
|
||||
const auto length = video_.cycle_length(operation == CPU::MOS6502Mk2::BusOperation::Ready);
|
||||
|
||||
// Update other subsystems.
|
||||
advance_timers_and_tape(length);
|
||||
@@ -262,7 +259,7 @@ public:
|
||||
audio_ += length;
|
||||
}
|
||||
|
||||
if(operation == CPU::MOS6502::BusOperation::Ready) {
|
||||
if(operation == CPU::MOS6502Mk2::BusOperation::Ready) {
|
||||
return length;
|
||||
}
|
||||
|
||||
@@ -279,17 +276,17 @@ public:
|
||||
// b1 = serial clock out and cassette write;
|
||||
// b0 = serial data out.
|
||||
|
||||
if(is_read(operation)) {
|
||||
if constexpr (is_read(operation)) {
|
||||
if(!address) {
|
||||
*value = io_direction_;
|
||||
value = io_direction_;
|
||||
} else {
|
||||
*value = io_input();
|
||||
value = io_input();
|
||||
}
|
||||
} else {
|
||||
if(!address) {
|
||||
io_direction_ = *value;
|
||||
io_direction_ = value;
|
||||
} else {
|
||||
io_output_ = *value;
|
||||
io_output_ = value;
|
||||
}
|
||||
|
||||
const auto output = io_output_ | ~io_direction_;
|
||||
@@ -320,36 +317,36 @@ public:
|
||||
// );
|
||||
// }
|
||||
|
||||
if(
|
||||
use_fast_tape_hack_ &&
|
||||
operation == CPU::MOS6502Esque::BusOperation::ReadOpcode &&
|
||||
(
|
||||
(use_hle && address == 0xe5fd) ||
|
||||
address == 0xe68b ||
|
||||
address == 0xe68d
|
||||
)
|
||||
) {
|
||||
// ++pulse_num_;
|
||||
if(use_hle) {
|
||||
read_dipole();
|
||||
}
|
||||
if constexpr (is_read(operation)) {
|
||||
if(
|
||||
use_fast_tape_hack_ &&
|
||||
operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode &&
|
||||
(
|
||||
(use_hle && address == 0xe5fd) ||
|
||||
address == 0xe68b ||
|
||||
address == 0xe68d
|
||||
)
|
||||
) {
|
||||
// ++pulse_num_;
|
||||
if(use_hle) {
|
||||
read_dipole();
|
||||
}
|
||||
|
||||
// using Flag = CPU::MOS6502::Flag;
|
||||
// using Register = CPU::MOS6502::Register;
|
||||
// const auto flags = m6502_.value_of(Register::Flags);
|
||||
// printf("to %lld: %c%c%c\n",
|
||||
// tape_player_->serialiser()->offset(),
|
||||
// flags & Flag::Sign ? 'n' : '-',
|
||||
// flags & Flag::Overflow ? 'v' : '-',
|
||||
// flags & Flag::Carry ? 'c' : '-'
|
||||
// );
|
||||
*value = 0x60;
|
||||
} else {
|
||||
if(is_read(operation)) {
|
||||
*value = map_.read(address);
|
||||
// using Flag = CPU::MOS6502::Flag;
|
||||
// using Register = CPU::MOS6502::Register;
|
||||
// const auto flags = m6502_.value_of(Register::Flags);
|
||||
// printf("to %lld: %c%c%c\n",
|
||||
// tape_player_->serialiser()->offset(),
|
||||
// flags & Flag::Sign ? 'n' : '-',
|
||||
// flags & Flag::Overflow ? 'v' : '-',
|
||||
// flags & Flag::Carry ? 'c' : '-'
|
||||
// );
|
||||
value = 0x60;
|
||||
} else {
|
||||
map_.write(address) = *value;
|
||||
value = map_.read(address);
|
||||
}
|
||||
} else {
|
||||
map_.write(address) = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -390,12 +387,12 @@ public:
|
||||
// ram_[0x90] = 0;
|
||||
// ram_[0x93] = 0;
|
||||
//
|
||||
// *value = 0x0c; // NOP abs.
|
||||
// value = 0x0c; // NOP abs.
|
||||
// }
|
||||
// }
|
||||
} else if(address < 0xff00) {
|
||||
// Miscellaneous hardware. All TODO.
|
||||
if(is_read(operation)) {
|
||||
if constexpr (is_read(operation)) {
|
||||
switch(address & 0xfff0) {
|
||||
case 0xfd10:
|
||||
// 6529 parallel port, about which I know only what I've found in kernel ROM disassemblies.
|
||||
@@ -403,7 +400,7 @@ public:
|
||||
// If play button is not currently pressed and this read is immediately followed by
|
||||
// an AND 4, press it. The kernel will deal with motor control subsequently.
|
||||
if(!play_button_) {
|
||||
const uint16_t pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter);
|
||||
const uint16_t pc = m6502_.registers().pc.full;
|
||||
const uint8_t next[] = {
|
||||
map_.read(pc+0),
|
||||
map_.read(pc+1),
|
||||
@@ -419,22 +416,23 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
*value = 0xff ^ (play_button_ ? 0x4 :0x0);
|
||||
value = 0xff ^ (play_button_ ? 0x4 :0x0);
|
||||
break;
|
||||
|
||||
case 0xfdd0:
|
||||
case 0xfdf0:
|
||||
*value = uint8_t(address >> 8);
|
||||
value = uint8_t(address >> 8);
|
||||
break;
|
||||
|
||||
default:
|
||||
value = 0xff;
|
||||
Logger::info().append("TODO: read @ %04x", address);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch(address & 0xfff0) {
|
||||
case 0xfd30:
|
||||
keyboard_mask_ = *value;
|
||||
keyboard_mask_ = value;
|
||||
break;
|
||||
|
||||
case 0xfdd0: {
|
||||
@@ -444,28 +442,28 @@ public:
|
||||
} break;
|
||||
|
||||
default:
|
||||
Logger::info().append("TODO: write of %02x @ %04x", *value, address);
|
||||
Logger::info().append("TODO: write of %02x @ %04x", value, address);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter);
|
||||
const auto pc = m6502_.registers().pc.full;
|
||||
const bool is_from_rom =
|
||||
(rom_is_paged_ && pc >= 0x8000) ||
|
||||
(pc >= 0x400 && pc < 0x500) ||
|
||||
(pc >= 0x700 && pc < 0x800);
|
||||
bool is_hit = true;
|
||||
|
||||
if(is_read(operation)) {
|
||||
if constexpr (is_read(operation)) {
|
||||
switch(address) {
|
||||
case 0xff00: *value = timers_.read<0>(); break;
|
||||
case 0xff01: *value = timers_.read<1>(); break;
|
||||
case 0xff02: *value = timers_.read<2>(); break;
|
||||
case 0xff03: *value = timers_.read<3>(); break;
|
||||
case 0xff04: *value = timers_.read<4>(); break;
|
||||
case 0xff05: *value = timers_.read<5>(); break;
|
||||
case 0xff06: *value = video_.read<0xff06>(); break;
|
||||
case 0xff07: *value = video_.read<0xff07>(); break;
|
||||
case 0xff00: value = timers_.read<0>(); break;
|
||||
case 0xff01: value = timers_.read<1>(); break;
|
||||
case 0xff02: value = timers_.read<2>(); break;
|
||||
case 0xff03: value = timers_.read<3>(); break;
|
||||
case 0xff04: value = timers_.read<4>(); break;
|
||||
case 0xff05: value = timers_.read<5>(); break;
|
||||
case 0xff06: value = video_.read<0xff06>(); break;
|
||||
case 0xff07: value = video_.read<0xff07>(); break;
|
||||
case 0xff08: {
|
||||
const uint8_t keyboard_input =
|
||||
~(
|
||||
@@ -484,50 +482,51 @@ public:
|
||||
((joystick_mask_ & 0x02) ? 0xff : (joystick(0).mask() | 0x40)) &
|
||||
((joystick_mask_ & 0x04) ? 0xff : (joystick(1).mask() | 0x80));
|
||||
|
||||
*value = keyboard_input & joystick_mask;
|
||||
value = keyboard_input & joystick_mask;
|
||||
} break;
|
||||
case 0xff09: *value = interrupts_.status(); break;
|
||||
case 0xff09: value = interrupts_.status(); break;
|
||||
case 0xff0a:
|
||||
*value = interrupts_.mask() | video_.read<0xff0a>() | 0xa0;
|
||||
value = interrupts_.mask() | video_.read<0xff0a>() | 0xa0;
|
||||
break;
|
||||
case 0xff0b: *value = video_.read<0xff0b>(); break;
|
||||
case 0xff0c: *value = video_.read<0xff0c>(); break;
|
||||
case 0xff0d: *value = video_.read<0xff0d>(); break;
|
||||
case 0xff0e: *value = ff0e_; break;
|
||||
case 0xff0f: *value = ff0f_; break;
|
||||
case 0xff10: *value = ff10_ | 0xfc; break;
|
||||
case 0xff11: *value = ff11_; break;
|
||||
case 0xff12: *value = ff12_ | 0xc0; break;
|
||||
case 0xff13: *value = ff13_ | (rom_is_paged_ ? 1 : 0); break;
|
||||
case 0xff14: *value = video_.read<0xff14>(); break;
|
||||
case 0xff15: *value = video_.read<0xff15>(); break;
|
||||
case 0xff16: *value = video_.read<0xff16>(); break;
|
||||
case 0xff17: *value = video_.read<0xff17>(); break;
|
||||
case 0xff18: *value = video_.read<0xff18>(); break;
|
||||
case 0xff19: *value = video_.read<0xff19>(); break;
|
||||
case 0xff1a: *value = video_.read<0xff1a>(); break;
|
||||
case 0xff1b: *value = video_.read<0xff1b>(); break;
|
||||
case 0xff1c: *value = video_.read<0xff1c>(); break;
|
||||
case 0xff1d: *value = video_.read<0xff1d>(); break;
|
||||
case 0xff1e: *value = video_.read<0xff1e>(); break;
|
||||
case 0xff1f: *value = video_.read<0xff1f>(); break;
|
||||
case 0xff0b: value = video_.read<0xff0b>(); break;
|
||||
case 0xff0c: value = video_.read<0xff0c>(); break;
|
||||
case 0xff0d: value = video_.read<0xff0d>(); break;
|
||||
case 0xff0e: value = ff0e_; break;
|
||||
case 0xff0f: value = ff0f_; break;
|
||||
case 0xff10: value = ff10_ | 0xfc; break;
|
||||
case 0xff11: value = ff11_; break;
|
||||
case 0xff12: value = ff12_ | 0xc0; break;
|
||||
case 0xff13: value = ff13_ | (rom_is_paged_ ? 1 : 0); break;
|
||||
case 0xff14: value = video_.read<0xff14>(); break;
|
||||
case 0xff15: value = video_.read<0xff15>(); break;
|
||||
case 0xff16: value = video_.read<0xff16>(); break;
|
||||
case 0xff17: value = video_.read<0xff17>(); break;
|
||||
case 0xff18: value = video_.read<0xff18>(); break;
|
||||
case 0xff19: value = video_.read<0xff19>(); break;
|
||||
case 0xff1a: value = video_.read<0xff1a>(); break;
|
||||
case 0xff1b: value = video_.read<0xff1b>(); break;
|
||||
case 0xff1c: value = video_.read<0xff1c>(); break;
|
||||
case 0xff1d: value = video_.read<0xff1d>(); break;
|
||||
case 0xff1e: value = video_.read<0xff1e>(); break;
|
||||
case 0xff1f: value = video_.read<0xff1f>(); break;
|
||||
|
||||
case 0xff3e: *value = 0; break;
|
||||
case 0xff3f: *value = 0; break;
|
||||
case 0xff3e: value = 0; break;
|
||||
case 0xff3f: value = 0; break;
|
||||
|
||||
default:
|
||||
Logger::info().append("TODO: TED read at %04x", address);
|
||||
value = 0xff;
|
||||
is_hit = false;
|
||||
}
|
||||
} else {
|
||||
switch(address) {
|
||||
case 0xff00: timers_.write<0>(*value); break;
|
||||
case 0xff01: timers_.write<1>(*value); break;
|
||||
case 0xff02: timers_.write<2>(*value); break;
|
||||
case 0xff03: timers_.write<3>(*value); break;
|
||||
case 0xff04: timers_.write<4>(*value); break;
|
||||
case 0xff05: timers_.write<5>(*value); break;
|
||||
case 0xff06: video_.write<0xff06>(*value); break;
|
||||
case 0xff00: timers_.write<0>(value); break;
|
||||
case 0xff01: timers_.write<1>(value); break;
|
||||
case 0xff02: timers_.write<2>(value); break;
|
||||
case 0xff03: timers_.write<3>(value); break;
|
||||
case 0xff04: timers_.write<4>(value); break;
|
||||
case 0xff05: timers_.write<5>(value); break;
|
||||
case 0xff06: video_.write<0xff06>(value); break;
|
||||
case 0xff07:
|
||||
video_.write<0xff07>(*value);
|
||||
audio_->set_divider(*value);
|
||||
@@ -536,25 +535,25 @@ public:
|
||||
// Observation here: the kernel posts a 0 to this
|
||||
// address upon completing each keyboard scan cycle,
|
||||
// once per frame.
|
||||
if(typer_ && !*value) {
|
||||
if(typer_ && !value) {
|
||||
if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
joystick_mask_ = *value;
|
||||
joystick_mask_ = value;
|
||||
break;
|
||||
case 0xff09:
|
||||
interrupts_.set_status(*value);
|
||||
interrupts_.set_status(value);
|
||||
break;
|
||||
case 0xff0a:
|
||||
interrupts_.set_mask(*value);
|
||||
video_.write<0xff0a>(*value);
|
||||
interrupts_.set_mask(value);
|
||||
video_.write<0xff0a>(value);
|
||||
break;
|
||||
case 0xff0b: video_.write<0xff0b>(*value); break;
|
||||
case 0xff0c: video_.write<0xff0c>(*value); break;
|
||||
case 0xff0d: video_.write<0xff0d>(*value); break;
|
||||
case 0xff0b: video_.write<0xff0b>(value); break;
|
||||
case 0xff0c: video_.write<0xff0c>(value); break;
|
||||
case 0xff0d: video_.write<0xff0d>(value); break;
|
||||
case 0xff0e:
|
||||
ff0e_ = *value;
|
||||
audio_->set_frequency_low<0>(*value);
|
||||
@@ -572,10 +571,10 @@ public:
|
||||
audio_->set_control(*value);
|
||||
break;
|
||||
case 0xff12:
|
||||
ff12_ = *value & 0x3f;
|
||||
video_.write<0xff12>(*value);
|
||||
ff12_ = value & 0x3f;
|
||||
video_.write<0xff12>(value);
|
||||
|
||||
if((*value & 4)) {
|
||||
if((value & 4)) {
|
||||
page_video_rom();
|
||||
} else {
|
||||
page_video_ram();
|
||||
@@ -584,21 +583,21 @@ public:
|
||||
audio_->set_frequency_high<0>(*value);
|
||||
break;
|
||||
case 0xff13:
|
||||
ff13_ = *value & 0xfe;
|
||||
video_.write<0xff13>(*value);
|
||||
ff13_ = value & 0xfe;
|
||||
video_.write<0xff13>(value);
|
||||
break;
|
||||
case 0xff14: video_.write<0xff14>(*value); break;
|
||||
case 0xff15: video_.write<0xff15>(*value); break;
|
||||
case 0xff16: video_.write<0xff16>(*value); break;
|
||||
case 0xff17: video_.write<0xff17>(*value); break;
|
||||
case 0xff18: video_.write<0xff18>(*value); break;
|
||||
case 0xff19: video_.write<0xff19>(*value); break;
|
||||
case 0xff1a: video_.write<0xff1a>(*value); break;
|
||||
case 0xff1b: video_.write<0xff1b>(*value); break;
|
||||
case 0xff1c: video_.write<0xff1c>(*value); break;
|
||||
case 0xff1d: video_.write<0xff1d>(*value); break;
|
||||
case 0xff1e: video_.write<0xff1e>(*value); break;
|
||||
case 0xff1f: video_.write<0xff1f>(*value); break;
|
||||
case 0xff14: video_.write<0xff14>(value); break;
|
||||
case 0xff15: video_.write<0xff15>(value); break;
|
||||
case 0xff16: video_.write<0xff16>(value); break;
|
||||
case 0xff17: video_.write<0xff17>(value); break;
|
||||
case 0xff18: video_.write<0xff18>(value); break;
|
||||
case 0xff19: video_.write<0xff19>(value); break;
|
||||
case 0xff1a: video_.write<0xff1a>(value); break;
|
||||
case 0xff1b: video_.write<0xff1b>(value); break;
|
||||
case 0xff1c: video_.write<0xff1c>(value); break;
|
||||
case 0xff1d: video_.write<0xff1d>(value); break;
|
||||
case 0xff1e: video_.write<0xff1e>(value); break;
|
||||
case 0xff1f: video_.write<0xff1f>(value); break;
|
||||
|
||||
case 0xff3e: page_cpu_rom(); break;
|
||||
case 0xff3f: page_cpu_ram(); break;
|
||||
@@ -617,8 +616,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
using Processor = CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, true>;
|
||||
Processor m6502_;
|
||||
struct M6502Traits {
|
||||
static constexpr auto uses_ready_line = true;
|
||||
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions;
|
||||
using BusHandlerT = ConcreteMachine;
|
||||
};
|
||||
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
return &audio_.speaker();
|
||||
@@ -628,11 +631,11 @@ private:
|
||||
if(c1541_) c1541_->set_activity_observer(observer);
|
||||
}
|
||||
|
||||
void set_irq_line(bool active) override {
|
||||
m6502_.set_irq_line(active);
|
||||
void set_irq_line(const bool active) override {
|
||||
m6502_.template set<CPU::MOS6502Mk2::Line::IRQ>(active);
|
||||
}
|
||||
void set_ready_line(bool active) override {
|
||||
m6502_.set_ready_line(active);
|
||||
void set_ready_line(const bool active) override {
|
||||
m6502_.template set<CPU::MOS6502Mk2::Line::Ready>(active);
|
||||
}
|
||||
|
||||
void page_video_rom() {
|
||||
@@ -768,26 +771,19 @@ private:
|
||||
// TODO: substantially simplify the below; at the minute it's a
|
||||
// literal transcription of the original as a simple first step.
|
||||
void read_dipole() {
|
||||
using Register = CPU::MOS6502::Register;
|
||||
using Flag = CPU::MOS6502::Flag;
|
||||
using Flag = CPU::MOS6502Mk2::Flag;
|
||||
|
||||
//
|
||||
// Get registers now and ensure they'll be written back at function exit.
|
||||
//
|
||||
CPU::MOS6502Esque::LazyFlags flags(uint8_t(m6502_.value_of(Register::Flags)));
|
||||
uint8_t x, y, a;
|
||||
uint8_t s = uint8_t(m6502_.value_of(Register::StackPointer));
|
||||
auto registers = m6502_.registers();
|
||||
struct ScopeGuard {
|
||||
ScopeGuard(std::function<void(void)> at_exit) : at_exit_(at_exit) {}
|
||||
~ScopeGuard() { at_exit_(); }
|
||||
private:
|
||||
std::function<void(void)> at_exit_;
|
||||
} registers([&] {
|
||||
m6502_.set_value_of(Register::Flags, flags.get());
|
||||
m6502_.set_value_of(Register::A, a);
|
||||
m6502_.set_value_of(Register::X, x);
|
||||
m6502_.set_value_of(Register::Y, y);
|
||||
m6502_.set_value_of(Register::StackPointer, s);
|
||||
} store_registers([&] {
|
||||
m6502_.set_registers(registers);
|
||||
});
|
||||
|
||||
//
|
||||
@@ -802,38 +798,38 @@ private:
|
||||
// 6502 pseudo-ops.
|
||||
//
|
||||
const auto ldabs = [&] (uint8_t &target, const uint16_t address) {
|
||||
flags.set_nz(target = map_.read(address));
|
||||
registers.flags.set_per<Flag::NegativeZero>(target = map_.read(address));
|
||||
};
|
||||
const auto ldimm = [&] (uint8_t &target, const uint8_t value) {
|
||||
flags.set_nz(target = value);
|
||||
registers.flags.set_per<Flag::NegativeZero>(target = value);
|
||||
};
|
||||
const auto pha = [&] () {
|
||||
map_.write(0x100 + s) = a;
|
||||
--s;
|
||||
map_.write(0x100 + registers.s) = registers.a;
|
||||
--registers.s;
|
||||
};
|
||||
const auto pla = [&] () {
|
||||
++s;
|
||||
a = map_.read(0x100 + s);
|
||||
++registers.s;
|
||||
registers.a = map_.read(0x100 + registers.s);
|
||||
};
|
||||
const auto bit = [&] (const uint8_t value) {
|
||||
flags.zero_result = a & value;
|
||||
flags.negative_result = value;
|
||||
flags.overflow = value & CPU::MOS6502Esque::Flag::Overflow;
|
||||
registers.flags.set_per<Flag::Zero>(registers.a & value);
|
||||
registers.flags.set_per<Flag::Negative>(value);
|
||||
registers.flags.set_per<Flag::Overflow>(value);
|
||||
};
|
||||
const auto cmp = [&] (const uint8_t value) {
|
||||
const uint16_t temp16 = a - value;
|
||||
flags.set_nz(uint8_t(temp16));
|
||||
flags.carry = ((~temp16) >> 8)&1;
|
||||
const uint16_t temp16 = registers.a - value;
|
||||
registers.flags.set_per<Flag::NegativeZero>(uint8_t(temp16));
|
||||
registers.flags.set_per<Flag::Carry>(((~temp16) >> 8)&1);
|
||||
};
|
||||
const auto andimm = [&] (const uint8_t value) {
|
||||
a &= value;
|
||||
flags.set_nz(a);
|
||||
registers.a &= value;
|
||||
registers.flags.set_per<Flag::NegativeZero>(registers.a);
|
||||
};
|
||||
const auto ne = [&]() -> bool {
|
||||
return flags.zero_result;
|
||||
return !registers.flags.get<Flag::Zero>();
|
||||
};
|
||||
const auto eq = [&]() -> bool {
|
||||
return !flags.zero_result;
|
||||
return registers.flags.get<Flag::Zero>();
|
||||
};
|
||||
|
||||
//
|
||||
@@ -842,7 +838,7 @@ private:
|
||||
const auto dipok = [&] {
|
||||
// clc ; everything's fine
|
||||
// rts
|
||||
flags.carry = 0;
|
||||
registers.flags.set_per<Flag::Carry>(0);
|
||||
};
|
||||
const auto rshort = [&] {
|
||||
// bit tshrtd ; got a short
|
||||
@@ -858,7 +854,7 @@ private:
|
||||
const auto rderr1 = [&] {
|
||||
// sec ; i'm confused
|
||||
// rts
|
||||
flags.carry = Flag::Carry;
|
||||
registers.flags.set_per<Flag::Carry>(Flag::Carry);
|
||||
};
|
||||
|
||||
//
|
||||
@@ -871,8 +867,8 @@ private:
|
||||
//rddipl
|
||||
// ldx dsamp1 ; setup x,y with 1st sample point
|
||||
// ldy dsamp1+1
|
||||
ldabs(x, dsamp1);
|
||||
ldabs(y, dsamp1 + 1);
|
||||
ldabs(registers.x, dsamp1);
|
||||
ldabs(registers.y, dsamp1 + 1);
|
||||
advance_cycles(8);
|
||||
|
||||
//badeg1
|
||||
@@ -881,9 +877,9 @@ private:
|
||||
// pha
|
||||
// lda dsamp2
|
||||
// pha
|
||||
ldabs(a, dsamp2 + 1);
|
||||
ldabs(registers.a, dsamp2 + 1);
|
||||
pha();
|
||||
ldabs(a, dsamp2);
|
||||
ldabs(registers.a, dsamp2);
|
||||
pha();
|
||||
advance_cycles(14);
|
||||
|
||||
@@ -891,7 +887,7 @@ private:
|
||||
//rwtl ; wait till rd line is high
|
||||
// bit port [= $0001]
|
||||
// beq rwtl ; !ls!
|
||||
ldimm(a, 0x10);
|
||||
ldimm(registers.a, 0x10);
|
||||
advance_cycles(2);
|
||||
do {
|
||||
bit(io_input());
|
||||
@@ -913,8 +909,8 @@ private:
|
||||
|
||||
// stx timr2l
|
||||
// sty timr2h
|
||||
timers_.write<2>(x);
|
||||
timers_.write<3>(y);
|
||||
timers_.write<2>(registers.x);
|
||||
timers_.write<3>(registers.y);
|
||||
advance_cycles(8);
|
||||
|
||||
|
||||
@@ -925,9 +921,9 @@ private:
|
||||
// pla
|
||||
// sta timr3h ;go! ...tb
|
||||
pla();
|
||||
timers_.write<4>(a);
|
||||
timers_.write<4>(registers.a);
|
||||
pla();
|
||||
timers_.write<5>(a);
|
||||
timers_.write<5>(registers.a);
|
||||
advance_cycles(14);
|
||||
|
||||
|
||||
@@ -935,8 +931,8 @@ private:
|
||||
//
|
||||
// lda #$50 ; clr ta,tb
|
||||
// sta tedirq
|
||||
ldimm(a, 0x50);
|
||||
interrupts_.set_status(a);
|
||||
ldimm(registers.a, 0x50);
|
||||
interrupts_.set_status(registers.a);
|
||||
advance_cycles(6);
|
||||
|
||||
|
||||
@@ -949,7 +945,7 @@ private:
|
||||
// and #$10 ; a look at that edge again
|
||||
// bne badeg1 ; woa! got a bad edge trigger !ls!
|
||||
do {
|
||||
ldimm(a, io_input());
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
@@ -972,7 +968,7 @@ private:
|
||||
|
||||
// lda #$10
|
||||
//wata ; wait for ta to timeout
|
||||
ldimm(a, 0x10);
|
||||
ldimm(registers.a, 0x10);
|
||||
advance_cycles(3);
|
||||
do {
|
||||
// bit port ; kuldge, kludge, kludge !!! <<><>>
|
||||
@@ -1000,7 +996,7 @@ private:
|
||||
do {
|
||||
// lda port
|
||||
// cmp port
|
||||
ldimm(a, io_input());
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
|
||||
if(advance_cycles(9)) {
|
||||
@@ -1028,7 +1024,7 @@ private:
|
||||
//
|
||||
//; wait for tb to timeout
|
||||
//; now do the dipole sample #2
|
||||
ldimm(a, 0x40);
|
||||
ldimm(registers.a, 0x40);
|
||||
advance_cycles(3);
|
||||
do {
|
||||
bit(interrupts_.status());
|
||||
@@ -1043,7 +1039,7 @@ private:
|
||||
// cmp port
|
||||
// bne casdb3
|
||||
do {
|
||||
ldimm(a, io_input());
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
@@ -1064,10 +1060,10 @@ private:
|
||||
// sta timr2l
|
||||
// lda zcell+1
|
||||
// sta timr2h
|
||||
ldabs(a, zcell);
|
||||
timers_.write<2>(a);
|
||||
ldabs(a, zcell + 1);
|
||||
timers_.write<3>(y);
|
||||
ldabs(registers.a, zcell);
|
||||
timers_.write<2>(registers.a);
|
||||
ldabs(registers.a, zcell + 1);
|
||||
timers_.write<3>(registers.y);
|
||||
advance_cycles(16);
|
||||
|
||||
|
||||
@@ -1076,9 +1072,9 @@ private:
|
||||
// lda #$10
|
||||
// sta tedirq ; verify +180 half of word dipole
|
||||
// lda #$10
|
||||
ldimm(a, 0x10);
|
||||
interrupts_.set_status(a);
|
||||
ldimm(a, 0x10);
|
||||
ldimm(registers.a, 0x10);
|
||||
interrupts_.set_status(registers.a);
|
||||
ldimm(registers.a, 0x10);
|
||||
advance_cycles(8);
|
||||
|
||||
//wata2
|
||||
@@ -1096,7 +1092,7 @@ private:
|
||||
// cmp port
|
||||
// bne casdb4
|
||||
do {
|
||||
ldimm(a, io_input());
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "Activity/Source.hpp"
|
||||
#include "Machines/MachineTypes.hpp"
|
||||
|
||||
#include "Processors/6502/6502.hpp"
|
||||
#include "Processors/6502Mk2/6502Mk2.hpp"
|
||||
#include "Components/6560/6560.hpp"
|
||||
#include "Components/6522/6522.hpp"
|
||||
|
||||
@@ -72,26 +72,30 @@ public:
|
||||
// Port A provides information about the presence or absence of a tape, and parts of
|
||||
// the joystick and serial port state, both of which have been statefully collected
|
||||
// into port_a_.
|
||||
if(!port) {
|
||||
if constexpr (port == MOS::MOS6522::Port::A) {
|
||||
return port_a_ | (tape_->has_tape() ? 0x00 : 0x40);
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
/// Receives announcements of control line output change from the 6522.
|
||||
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line> void set_control_line_output(const bool value) {
|
||||
// The CA2 output is used to control the tape motor.
|
||||
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
||||
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line>
|
||||
void set_control_line_output(const bool value) {
|
||||
// CA2: control the tape motor.
|
||||
if constexpr (port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
||||
tape_->set_motor_control(!value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Receives announcements of changes in the serial bus connected to the serial port and propagates them into Port A.
|
||||
void set_serial_line_state(Commodore::Serial::Line line, const bool value) {
|
||||
void set_serial_line_state(const Commodore::Serial::Line line, const bool value) {
|
||||
const auto set = [&](const uint8_t bit) {
|
||||
port_a_ = (port_a_ & ~bit) | (value ? bit : 0x00);
|
||||
};
|
||||
switch(line) {
|
||||
default: break;
|
||||
case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break;
|
||||
case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break;
|
||||
case ::Commodore::Serial::Line::Data: set(0x02); break;
|
||||
case ::Commodore::Serial::Line::Clock: set(0x01); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +150,7 @@ public:
|
||||
|
||||
/// Sets all keys as unpressed.
|
||||
void clear_all_keys() {
|
||||
memset(columns_, 0xff, sizeof(columns_));
|
||||
std::fill(std::begin(columns_), std::end(columns_), 0xff);
|
||||
}
|
||||
|
||||
/// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B.
|
||||
@@ -230,8 +234,6 @@ struct Vic6560BusHandler {
|
||||
// It is assumed that these pointers have been filled in by the machine.
|
||||
const uint8_t *video_memory_map[16]{}; // Segments video memory into 1kb portions.
|
||||
const uint8_t *colour_memory{}; // Colour memory must be contiguous.
|
||||
|
||||
// TODO: make the above const.
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -280,7 +282,6 @@ class ConcreteMachine:
|
||||
public MachineTypes::MappedKeyboardMachine,
|
||||
public MachineTypes::JoystickMachine,
|
||||
public Configurable::Device,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||
public Utility::TypeRecipient<CharacterMapper>,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
@@ -455,7 +456,7 @@ public:
|
||||
}
|
||||
|
||||
void set_key_state(const uint16_t key, const bool is_pressed) final {
|
||||
if(key < 0xfff0) {
|
||||
if(key < KeyUp) {
|
||||
keyboard_via_port_handler_.set_key_state(key, is_pressed);
|
||||
} else {
|
||||
switch(key) {
|
||||
@@ -481,28 +482,27 @@ public:
|
||||
|
||||
void clear_all_keys() final {
|
||||
keyboard_via_port_handler_.clear_all_keys();
|
||||
set_key_state(KeyRestore, false);
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
forceinline Cycles perform_bus_operation(
|
||||
const CPU::MOS6502::BusOperation operation,
|
||||
const uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
|
||||
// Tun the phase-1 part of this cycle, in which the VIC accesses memory.
|
||||
cycles_since_mos6560_update_++;
|
||||
|
||||
// Run the phase-2 part of the cycle, which is whatever the 6502 said it should be.
|
||||
const bool is_from_rom = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter) > 0x8000;
|
||||
if(is_read(operation)) {
|
||||
const auto is_from_rom = [&]() {
|
||||
return m6502_.registers().pc.full > 0x8000;
|
||||
};
|
||||
if constexpr (is_read(operation)) {
|
||||
const auto page = processor_read_memory_map_[address >> 10];
|
||||
uint8_t result;
|
||||
if(!page) {
|
||||
if(!is_from_rom) confidence_.add_miss();
|
||||
if(!is_from_rom()) confidence_.add_miss();
|
||||
result = 0xff;
|
||||
} else {
|
||||
result = processor_read_memory_map_[address >> 10][address & 0x3ff];
|
||||
@@ -515,7 +515,7 @@ public:
|
||||
if(address & 0x10) result &= user_port_via_.read(address);
|
||||
if(address & 0x20) result &= keyboard_via_.read(address);
|
||||
|
||||
if(!is_from_rom) {
|
||||
if(!is_from_rom()) {
|
||||
if((address & 0x100) && !(address & 0x30)) {
|
||||
confidence_.add_miss();
|
||||
} else {
|
||||
@@ -523,10 +523,10 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
*value = result;
|
||||
value = result;
|
||||
|
||||
// Consider applying the fast tape hack.
|
||||
if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
|
||||
if(use_fast_tape_hack_ && operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode) {
|
||||
if(address == 0xf7b2) {
|
||||
// Address 0xf7b2 contains a JSR to 0xf8c0 ('RDTPBLKS') that will fill the tape buffer with the
|
||||
// next header. Skip that via a three-byte NOP and fill in the next header programmatically.
|
||||
@@ -551,10 +551,10 @@ public:
|
||||
ram_[0x90] = 0;
|
||||
ram_[0x93] = 0;
|
||||
|
||||
*value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
|
||||
value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
|
||||
} else if(address == 0xf90b) {
|
||||
const auto x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X));
|
||||
if(x == 0xe) {
|
||||
auto registers = m6502_.registers();
|
||||
if(registers.x == 0xe) {
|
||||
Storage::Tape::Commodore::Parser parser(TargetPlatform::Vic20);
|
||||
const auto tape_position = tape_->serialiser()->offset();
|
||||
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(*tape_->serialiser());
|
||||
@@ -576,14 +576,13 @@ public:
|
||||
|
||||
// set tape status, carry and flag
|
||||
ram_[0x90] |= 0x40;
|
||||
uint8_t flags = uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags));
|
||||
flags &= ~uint8_t((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt));
|
||||
m6502_.set_value_of(CPU::MOS6502::Register::Flags, flags);
|
||||
registers.flags.set_per<CPU::MOS6502Mk2::Flag::Carry>(0);
|
||||
registers.flags.set_per<CPU::MOS6502Mk2::Flag::Interrupt>(0);
|
||||
|
||||
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
|
||||
// ensure that the PC leaps to 0xfccf
|
||||
m6502_.set_value_of(CPU::MOS6502::Register::ProgramCounter, 0xfccf);
|
||||
*value = 0xea; // i.e. NOP implied
|
||||
registers.pc.full = 0xfccf;
|
||||
value = 0xea; // i.e. NOP implied
|
||||
hold_tape_ = true;
|
||||
Logger::info().append("Found data");
|
||||
} else {
|
||||
@@ -592,27 +591,28 @@ public:
|
||||
Logger::info().append("Didn't find data");
|
||||
}
|
||||
}
|
||||
m6502_.set_registers(registers);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint8_t *const ram = processor_write_memory_map_[address >> 10];
|
||||
if(ram) {
|
||||
update_video();
|
||||
ram[address & 0x3ff] = *value;
|
||||
ram[address & 0x3ff] = value;
|
||||
}
|
||||
// Anything between 0x9000 and 0x9400 is the IO area.
|
||||
if((address&0xfc00) == 0x9000) {
|
||||
// The VIC is selected by bit 8 = 0
|
||||
if(!(address&0x100)) {
|
||||
update_video();
|
||||
mos6560_.write(address, *value);
|
||||
mos6560_.write(address, value);
|
||||
}
|
||||
// The first VIA is selected by bit 4 = 1.
|
||||
if(address & 0x10) user_port_via_.write(address, *value);
|
||||
if(address & 0x10) user_port_via_.write(address, value);
|
||||
// The second VIA is selected by bit 5 = 1.
|
||||
if(address & 0x20) keyboard_via_.write(address, *value);
|
||||
if(address & 0x20) keyboard_via_.write(address, value);
|
||||
|
||||
if(!is_from_rom) {
|
||||
if(!is_from_rom()) {
|
||||
if((address & 0x100) && !(address & 0x30)) {
|
||||
confidence_.add_miss();
|
||||
} else {
|
||||
@@ -620,13 +620,13 @@ public:
|
||||
}
|
||||
}
|
||||
} else if(!ram) {
|
||||
if(!is_from_rom) confidence_.add_miss();
|
||||
if(!is_from_rom()) confidence_.add_miss();
|
||||
}
|
||||
}
|
||||
|
||||
user_port_via_.run_for(Cycles(1));
|
||||
keyboard_via_.run_for(Cycles(1));
|
||||
if(typer_ && address == 0xeb1e && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
|
||||
if(typer_ && address == 0xeb1e && operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode) {
|
||||
if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
@@ -672,8 +672,8 @@ public:
|
||||
}
|
||||
|
||||
void mos6522_did_change_interrupt_status(void *) final {
|
||||
m6502_.set_nmi_line(user_port_via_.get_interrupt_line());
|
||||
m6502_.set_irq_line(keyboard_via_.get_interrupt_line());
|
||||
m6502_.template set<CPU::MOS6502Mk2::Line::NMI>(user_port_via_.get_interrupt_line());
|
||||
m6502_.template set<CPU::MOS6502Mk2::Line::IRQ>(keyboard_via_.get_interrupt_line());
|
||||
}
|
||||
|
||||
void type_string(const std::string &string) final {
|
||||
@@ -718,10 +718,16 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
struct M6502Traits {
|
||||
static constexpr auto uses_ready_line = false;
|
||||
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions;
|
||||
using BusHandlerT = ConcreteMachine;
|
||||
};
|
||||
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
|
||||
|
||||
void update_video() {
|
||||
mos6560_.run_for(cycles_since_mos6560_update_.flush<Cycles>());
|
||||
}
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
|
||||
std::vector<uint8_t> character_rom_;
|
||||
std::vector<uint8_t> basic_rom_;
|
||||
@@ -745,12 +751,22 @@ private:
|
||||
++address;
|
||||
}
|
||||
}
|
||||
void write_to_map(const uint8_t **const map, const uint8_t *area, uint16_t address, size_t length) {
|
||||
void write_to_map(
|
||||
const uint8_t **const map,
|
||||
const uint8_t *const area,
|
||||
const uint16_t address,
|
||||
const size_t length
|
||||
) {
|
||||
write_to_map([&](const uint16_t address, const size_t offset) {
|
||||
map[address] = &area[offset];
|
||||
}, address, length);
|
||||
}
|
||||
void write_to_map(uint8_t **const map, uint8_t *area, uint16_t address, size_t length) {
|
||||
void write_to_map(
|
||||
uint8_t **const map,
|
||||
uint8_t *const area,
|
||||
const uint16_t address,
|
||||
const size_t length
|
||||
) {
|
||||
write_to_map([&](const uint16_t address, const size_t offset) {
|
||||
map[address] = &area[offset];
|
||||
}, address, length);
|
||||
|
||||
83
OSBindings/Mac/Clock SignalTests/SIDTests.mm
Normal file
83
OSBindings/Mac/Clock SignalTests/SIDTests.mm
Normal file
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// SIDTests.mm
|
||||
// Clock SignalTests
|
||||
//
|
||||
// Created by Thomas Harte on 11/11/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "Components/SID/SID.hpp"
|
||||
|
||||
@interface SIDTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SIDTests
|
||||
|
||||
- (void)testOscillator {
|
||||
MOS::SID::Voice prior;
|
||||
MOS::SID::Voice voice;
|
||||
const uint32_t pulse_width = 0x02'3;
|
||||
|
||||
voice.oscillator.pitch = 0x00'1000'00;
|
||||
voice.oscillator.pulse_width = pulse_width << 20;
|
||||
voice.oscillator.reset_phase();
|
||||
|
||||
int c = 0;
|
||||
|
||||
// Run for first half of a cycle.
|
||||
while(!voice.oscillator.did_raise_b23()) {
|
||||
// Force envelope.
|
||||
voice.adsr.envelope = 255;
|
||||
|
||||
// Test sawtooth.
|
||||
voice.set_control(0x20);
|
||||
XCTAssertEqual(voice.output(prior), c);
|
||||
|
||||
// Test triangle.
|
||||
voice.set_control(0x10);
|
||||
XCTAssertEqual(voice.output(prior), c << 1);
|
||||
|
||||
// Test pulse.
|
||||
voice.set_control(0x40);
|
||||
XCTAssertEqual(voice.output(prior), (c < pulse_width) ? 0 : 4095);
|
||||
|
||||
// Advance.
|
||||
voice.update();
|
||||
++c;
|
||||
}
|
||||
|
||||
// B23 should go up halfway through the 12-bit range.
|
||||
XCTAssertEqual(c, 2048);
|
||||
|
||||
// Run for second half of a cycle.
|
||||
while(c < 4096) {
|
||||
// Force envelope.
|
||||
voice.adsr.envelope = 255;
|
||||
|
||||
// Test sawtooth.
|
||||
voice.set_control(0x20);
|
||||
XCTAssertEqual(voice.output(prior), c);
|
||||
|
||||
// Test triangle.
|
||||
voice.set_control(0x10);
|
||||
XCTAssertEqual(voice.output(prior), 4095 - ((c << 1) & 4095));
|
||||
|
||||
// Test pulse.
|
||||
voice.set_control(0x40);
|
||||
XCTAssertEqual(voice.output(prior), (c <= pulse_width) ? 0 : 4095);
|
||||
|
||||
// Advance.
|
||||
voice.update();
|
||||
++c;
|
||||
|
||||
XCTAssert(!voice.oscillator.did_raise_b23());
|
||||
}
|
||||
|
||||
// Check that B23 doesn't false rise again.
|
||||
voice.update();
|
||||
XCTAssert(!voice.oscillator.did_raise_b23());
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -66,6 +66,7 @@ enum class Line {
|
||||
PowerOn,
|
||||
Overflow,
|
||||
NMI,
|
||||
Ready,
|
||||
};
|
||||
|
||||
// MARK: - Address bus.
|
||||
@@ -185,14 +186,24 @@ public:
|
||||
(inputs_.interrupt_requests & Inputs::InterruptRequest::PowerOn);
|
||||
break;
|
||||
|
||||
// Level triggered.
|
||||
// Level triggered interrupts.
|
||||
case Line::Reset: level_sample(Inputs::InterruptRequest::Reset); break;
|
||||
case Line::IRQ: level_sample(Inputs::InterruptRequest::IRQ); break;
|
||||
|
||||
// Edge triggered.
|
||||
case Line::Overflow: edge_sample(Inputs::InterruptRequest::Reset, inputs_.overflow); break;
|
||||
// Edge triggered interrupts.
|
||||
case Line::NMI: edge_sample(Inputs::InterruptRequest::NMI, inputs_.nmi); break;
|
||||
|
||||
// Leval-capturing state.
|
||||
case Line::Ready: inputs_.ready = value; break;
|
||||
|
||||
// Edge-triggered state.
|
||||
case Line::Overflow:
|
||||
if(!inputs_.overflow && value) {
|
||||
registers_.flags.set_per<Flag::Overflow>(Flag::Overflow);
|
||||
}
|
||||
inputs_.overflow = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user