1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-23 20:29:42 +00:00

The Enterprise is now an Activity::Source; also sketches out the owner of Dave's timed interrupt logic.

This commit is contained in:
Thomas Harte 2021-06-27 21:02:04 -04:00
parent b698056f78
commit 2f53b105bb
5 changed files with 79 additions and 17 deletions

View File

@ -10,6 +10,8 @@
using namespace Enterprise::Dave;
// MARK: - Audio generator
Audio::Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
@ -188,3 +190,20 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
));
}
}
// MARK: - Interrupt source
uint8_t TimedInterruptSource::get_new_interrupts() {
const uint8_t result = interrupts_;
interrupts_ = 0;
return result;
}
void TimedInterruptSource::write(uint16_t address, uint8_t value) {
(void)address;
(void)value;
}
void TimedInterruptSource::run_for(Cycles cycles) {
(void)cycles;
}

View File

@ -11,6 +11,7 @@
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Numeric/LFSR.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
@ -18,9 +19,14 @@
namespace Enterprise {
namespace Dave {
enum class Interrupt: uint8_t {
VariableFrequency = 0x02,
OneHz = 0x08,
Nick = 0x20,
};
/*!
Models a subset of Dave's behaviour; memory mapping and interrupt status
is integrated into the main Enterprise machine.
Models the audio-production subset of Dave's behaviour.
*/
class Audio: public Outputs::Speaker::SampleSource {
public:
@ -105,6 +111,24 @@ class Audio: public Outputs::Speaker::SampleSource {
uint8_t poly_state_[4];
};
/*!
Provides Dave's timed interrupts  those that are provided at 1 kHz,
50 Hz or according to the rate of tone generators 0 or 1, plus the fixed
1 Hz interrupt.
*/
class TimedInterruptSource {
public:
void write(uint16_t address, uint8_t value);
uint8_t get_new_interrupts();
void run_for(Cycles);
private:
uint8_t interrupts_ = 0;
};
}
}

View File

@ -70,3 +70,9 @@ void EXDos::set_motor_on(bool on) {
// writing state, so plenty of work to do in general here.
get_drive().set_motor_on(on);
}
void EXDos::set_activity_observer(Activity::Observer *observer) {
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
});
}

View File

@ -23,6 +23,8 @@ class EXDos final : public WD::WD1770 {
void set_control_register(uint8_t control);
uint8_t get_control_register();
void set_activity_observer(Activity::Observer *observer);
private:
bool disk_did_change_ = false;

View File

@ -62,6 +62,7 @@ namespace Enterprise {
*/
template <bool has_disk_controller> class ConcreteMachine:
public Activity::Source,
public CPU::Z80::BusHandler,
public Machine,
public MachineTypes::AudioProducer,
@ -86,8 +87,8 @@ template <bool has_disk_controller> class ConcreteMachine:
min_ram_slot_(min_ram_slot(target)),
z80_(*this),
nick_(ram_.end() - 65536),
dave_(audio_queue_),
speaker_(dave_) {
dave_audio_(audio_queue_),
speaker_(dave_audio_) {
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and Dave elsewhere.
set_clock_rate(4'000'000);
@ -226,7 +227,7 @@ template <bool has_disk_controller> class ConcreteMachine:
const auto nick = nick_.last_valid();
const bool nick_interrupt_line = nick->get_interrupt_line();
if(nick_interrupt_line && !previous_nick_interrupt_line_) {
set_interrupt(Interrupt::Nick, nick_.last_sequence_point_overrun());
set_interrupts(uint8_t(Dave::Interrupt::Nick), nick_.last_sequence_point_overrun());
}
previous_nick_interrupt_line_ = nick_interrupt_line;
}
@ -306,6 +307,9 @@ template <bool has_disk_controller> class ConcreteMachine:
const HalfCycles full_length = cycle.length + penalty;
time_since_audio_update_ += full_length;
advance_nick(full_length);
if(dave_timer_ += full_length) {
set_interrupts(dave_timer_.last_valid()->get_new_interrupts(), dave_timer_.last_sequence_point_overrun());
}
// The WD/etc runs at a nominal 8Mhz.
if constexpr (has_disk_controller) {
@ -387,7 +391,8 @@ template <bool has_disk_controller> class ConcreteMachine:
case 0xa8: case 0xa9: case 0xaa: case 0xab:
case 0xac: case 0xad: case 0xae: case 0xaf:
update_audio();
dave_.write(address, *cycle.value);
dave_audio_.write(address, *cycle.value);
dave_timer_->write(address, *cycle.value);
break;
case 0xb4:
@ -442,12 +447,6 @@ template <bool has_disk_controller> class ConcreteMachine:
audio_queue_.perform();
}
inline void update_audio() {
// TODO: divide by only 8, letting Dave divide itself by a further 2 or 3
// as per its own register.
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(16)));
}
private:
// MARK: - Memory layout
std::array<uint8_t, 256 * 1024> ram_{};
@ -560,12 +559,9 @@ template <bool has_disk_controller> class ConcreteMachine:
}
// MARK: - Interrupts
enum class Interrupt: uint8_t {
Nick = 0x20
};
uint8_t interrupt_mask_ = 0x00, interrupt_state_ = 0x00;
void set_interrupt(Interrupt mask, HalfCycles offset = HalfCycles(0)) {
void set_interrupts(uint8_t mask, HalfCycles offset = HalfCycles(0)) {
interrupt_state_ |= uint8_t(mask);
update_interrupts(offset);
}
@ -580,12 +576,27 @@ template <bool has_disk_controller> class ConcreteMachine:
// Cf. timing guesses above.
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Dave::Audio dave_;
Dave::Audio dave_audio_;
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
HalfCycles time_since_audio_update_;
// The following two should both use the same divider.
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, 16> dave_timer_;
inline void update_audio() {
// TODO: divide by only 8, letting Dave divide itself by a further 2 or 3
// as per its own register.
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(16)));
}
// MARK: - EXDos card.
EXDos exdos_;
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) final {
if constexpr (has_disk_controller) {
exdos_.set_activity_observer(observer);
}
}
};
}