1
0
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:
Thomas Harte
2025-11-17 23:21:24 -05:00
6 changed files with 447 additions and 299 deletions

View File

@@ -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_);
}

View File

@@ -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];

View File

@@ -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;

View File

@@ -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);

View 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

View File

@@ -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();
}