1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-04-09 00:37:27 +00:00

Merge branch 'master' into Z80Lines

This commit is contained in:
Thomas Harte 2021-04-07 21:02:15 -04:00
commit 627b96f73c
21 changed files with 306 additions and 277 deletions

View File

@ -10,7 +10,9 @@
#define ClockReceiver_hpp
#include "ForceInline.hpp"
#include <cstdint>
#include <limits>
/*
Informal pattern for all classes that run from a clock cycle:
@ -176,6 +178,9 @@ class Cycles: public WrappedInt<Cycles> {
public:
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
forceinline static constexpr Cycles max() {
return Cycles(std::numeric_limits<IntType>::max());
}
private:
friend WrappedInt;
@ -195,6 +200,9 @@ class HalfCycles: public WrappedInt<HalfCycles> {
public:
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
forceinline static constexpr HalfCycles max() {
return HalfCycles(std::numeric_limits<IntType>::max());
}
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}

View File

@ -10,6 +10,7 @@
#define JustInTime_h
#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ClockingHintSource.hpp"
#include "ForceInline.hpp"
/*!
@ -24,29 +25,69 @@
If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
as and when sequence points are hit. Callers can use will_flush() to predict these.
If the held object is a subclass of ClockingHint::Source, this template will register as an
observer and potentially stop clocking or stop delaying clocking until just-in-time references
as directed.
TODO: incorporate and codify AsyncJustInTimeActor.
*/
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int divider = 1> class JustInTimeActor:
public ClockingHint::Observer {
private:
/*!
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
to it at construction if it implements get_next_sequence_point(). Otherwise destruction is a no-op.
**Does not delete the object.**
This is used by the -> operators below, which provide a unique pointer to the enclosed object and
update their sequence points upon its destruction i.e. after the caller has made whatever call
or calls as were relevant to the enclosed object.
*/
class SequencePointAwareDeleter {
public:
explicit SequencePointAwareDeleter(JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *actor) : actor_(actor) {}
explicit SequencePointAwareDeleter(JustInTimeActor<T, LocalTimeScale, multiplier, divider> *actor) noexcept
: actor_(actor) {}
void operator ()(const T *const) const {
forceinline void operator ()(const T *const) const {
if constexpr (has_sequence_points<T>::value) {
actor_->update_sequence_point();
}
}
private:
JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *const actor_;
JustInTimeActor<T, LocalTimeScale, multiplier, divider> *const actor_;
};
// This block of SFINAE determines whether objects of type T accepts Cycles or HalfCycles.
using HalfRunFor = void (T::*const)(HalfCycles);
static uint8_t half_sig(...);
static uint16_t half_sig(HalfRunFor);
using TargetTimeScale =
std::conditional_t<
sizeof(half_sig(&T::run_for)) == sizeof(uint16_t),
HalfCycles,
Cycles>;
public:
/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
object_.set_clocking_hint_observer(this);
}
}
/// Adds time to the actor.
forceinline void operator += (LocalTimeScale rhs) {
///
/// @returns @c true if adding time caused a flush; @c false otherwise.
forceinline bool operator += (LocalTimeScale rhs) {
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
if(clocking_preference_ == ClockingHint::Preference::None) {
return false;
}
}
if constexpr (multiplier != 1) {
time_since_update_ += rhs * multiplier;
} else {
@ -54,20 +95,31 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
}
is_flushed_ = false;
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
if (clocking_preference_ == ClockingHint::Preference::RealTime) {
flush();
return true;
}
}
if constexpr (has_sequence_points<T>::value) {
time_until_event_ -= rhs;
if(time_until_event_ <= LocalTimeScale(0)) {
time_overrun_ = time_until_event_;
flush();
update_sequence_point();
return true;
}
}
return false;
}
/// Flushes all accumulated time and returns a pointer to the included object.
///
/// If this object provides sequence points, checks for changes to the next
/// sequence point upon deletion of the pointer.
forceinline auto operator->() {
[[nodiscard]] forceinline auto operator->() {
flush();
return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
}
@ -75,19 +127,24 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
/// Acts exactly as per the standard ->, but preserves constness.
///
/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
forceinline auto operator -> () const {
auto non_const_this = const_cast<JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *>(this);
[[nodiscard]] forceinline auto operator -> () const {
auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
non_const_this->flush();
return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
}
/// @returns a pointer to the included object, without flushing time.
forceinline T *last_valid() {
[[nodiscard]] forceinline T *last_valid() {
return &object_;
}
/// @returns a const pointer to the included object, without flushing time.
[[nodiscard]] forceinline const T *last_valid() const {
return &object_;
}
/// @returns the amount of time since the object was last flushed, in the target time scale.
forceinline TargetTimeScale time_since_flush() const {
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
if constexpr (divider == 1) {
return time_since_update_;
@ -113,20 +170,26 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
}
/// Indicates whether a flush has occurred since the last call to did_flush().
forceinline bool did_flush() {
[[nodiscard]] forceinline bool did_flush() {
const bool did_flush = did_flush_;
did_flush_ = false;
return did_flush;
}
/// @returns a number in the range [-max, 0] indicating the offset of the most recent sequence
/// point from the final time at the end of the += that triggered the sequence point.
[[nodiscard]] forceinline LocalTimeScale last_sequence_point_overrun() {
return time_overrun_;
}
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
/// supports sequence points; @c LocalTimeScale() otherwise.
LocalTimeScale cycles_until_implicit_flush() const {
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
return time_until_event_;
}
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
forceinline bool will_flush(LocalTimeScale rhs) const {
[[nodiscard]] forceinline bool will_flush(LocalTimeScale rhs) const {
if constexpr (!has_sequence_points<T>::value) {
return false;
}
@ -141,60 +204,28 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
}
}
/// @returns A cached copy of the object's clocking preference.
ClockingHint::Preference clocking_preference() const {
return clocking_preference_;
}
private:
T object_;
LocalTimeScale time_since_update_, time_until_event_;
LocalTimeScale time_since_update_, time_until_event_, time_overrun_;
bool is_flushed_ = true;
bool did_flush_ = false;
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};
};
/*!
A RealTimeActor presents the same interface as a JustInTimeActor but doesn't defer work.
Time added will be performed immediately.
Its primary purpose is to allow consumers to remain flexible in their scheduling.
*/
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class RealTimeActor {
public:
template<typename... Args> RealTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
forceinline void operator += (const LocalTimeScale &rhs) {
if constexpr (multiplier == 1 && divider == 1) {
object_.run_for(TargetTimeScale(rhs));
return;
}
if constexpr (multiplier == 1) {
accumulated_time_ += rhs;
} else {
accumulated_time_ += rhs * multiplier;
}
if constexpr (divider == 1) {
const auto duration = accumulated_time_.template flush<TargetTimeScale>();
object_.run_for(duration);
} else {
const auto duration = accumulated_time_.template divide<TargetTimeScale>(LocalTimeScale(divider));
if(duration > TargetTimeScale(0))
object_.run_for(duration);
}
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
clocking_preference_ = clocking;
}
forceinline T *operator->() { return &object_; }
forceinline const T *operator->() const { return &object_; }
forceinline T *last_valid() { return &object_; }
forceinline void flush() {}
private:
T object_;
LocalTimeScale accumulated_time_;
};
/*!
A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
An AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
Any time the amount of accumulated time crosses a threshold provided at construction time,
the object will be updated on the AsyncTaskQueue.
*/

View File

@ -708,9 +708,9 @@ HalfCycles Base::half_cycles_before_internal_cycles(int internal_cycles) {
return HalfCycles(((internal_cycles << 2) + (2 - cycles_error_)) / 3);
}
HalfCycles TMS9918::get_time_until_interrupt() {
if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles(-1);
if(get_interrupt_line()) return HalfCycles(0);
HalfCycles TMS9918::get_next_sequence_point() {
if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles::max();
if(get_interrupt_line()) return HalfCycles::max();
// Calculate the amount of time until the next end-of-frame interrupt.
const int frame_length = 342 * mode_timing_.total_lines;

View File

@ -75,13 +75,13 @@ class TMS9918: public Base {
void latch_horizontal_counter();
/*!
Returns the amount of time until @c get_interrupt_line would next return true if
Returns the amount of time until @c get_interrupt_line would next change if
there are no interceding calls to @c write or to @c read.
If get_interrupt_line is true now, returns zero. If get_interrupt_line would
never return true, returns -1.
If get_interrupt_line is true now of if get_interrupt_line would
never return true, returns HalfCycles::max().
*/
HalfCycles get_time_until_interrupt();
HalfCycles get_next_sequence_point();
/*!
Returns the amount of time until the nominated line interrupt position is

View File

@ -1093,10 +1093,10 @@ class ConcreteMachine:
// MARK: - Other components.
Apple::Clock::ParallelClock clock_;
JustInTimeActor<Apple::IIgs::Video::Video, 1, 2, Cycles> video_; // i.e. run video at 7Mhz.
JustInTimeActor<Apple::IIgs::ADB::GLU, 1, 4, Cycles> adb_glu_; // i.e. 3,579,545Mhz.
JustInTimeActor<Apple::IIgs::Video::Video, Cycles, 1, 2> video_; // i.e. run video at 7Mhz.
JustInTimeActor<Apple::IIgs::ADB::GLU, Cycles, 1, 4> adb_glu_; // i.e. 3,579,545Mhz.
Zilog::SCC::z8530 scc_;
JustInTimeActor<Apple::IWM, 1, 2, Cycles> iwm_;
JustInTimeActor<Apple::IWM, Cycles, 1, 2> iwm_;
Cycles cycles_since_clock_tick_;
Apple::Macintosh::DoubleDensityDrive drives35_[2];
Apple::Disk::DiskIIDrive drives525_[2];

View File

@ -576,7 +576,7 @@ class MemoryMap {
if(region.write) { \
region.write[address] = *value; \
const bool _mm_is_shadowed = IsShadowed(map, region, address); \
map.shadow_base[is_shadowed][(&region.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \
map.shadow_base[_mm_is_shadowed][(&region.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \
}
// Quick notes on ::IsShadowed contortions:

View File

@ -647,7 +647,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
return mouse_;
}
using IWMActor = JustInTimeActor<IWM, 1, 1, HalfCycles, Cycles>;
using IWMActor = JustInTimeActor<IWM>;
class VIAPortHandler: public MOS::MOS6522::PortHandler {
public:

View File

@ -479,9 +479,9 @@ class ConcreteMachine:
JustInTimeActor<Video> video_;
// The MFP runs at 819200/2673749ths of the CPU clock rate.
JustInTimeActor<Motorola::MFP68901::MFP68901, 819200, 2673749> mfp_;
JustInTimeActor<Motorola::ACIA::ACIA, 16> keyboard_acia_;
JustInTimeActor<Motorola::ACIA::ACIA, 16> midi_acia_;
JustInTimeActor<Motorola::MFP68901::MFP68901, HalfCycles, 819200, 2673749> mfp_;
JustInTimeActor<Motorola::ACIA::ACIA, HalfCycles, 16> keyboard_acia_;
JustInTimeActor<Motorola::ACIA::ACIA, HalfCycles, 16> midi_acia_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910<false> ay_;

View File

@ -215,7 +215,9 @@ class ConcreteMachine:
}
const HalfCycles length = cycle.length + penalty;
vdp_ += length;
if(vdp_ += length) {
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
}
time_since_sn76489_update_ += length;
// Act only if necessary.
@ -263,7 +265,6 @@ class ConcreteMachine:
case 5:
*cycle.value = vdp_->read(address);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 7: {
@ -304,7 +305,6 @@ class ConcreteMachine:
case 5:
vdp_->write(address, *cycle.value);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 7:
@ -341,13 +341,6 @@ class ConcreteMachine:
}
}
if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= length;
if(time_until_interrupt_ <= HalfCycles(0)) {
z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_);
}
}
return penalty;
}
@ -384,7 +377,7 @@ class ConcreteMachine:
}
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
JustInTimeActor<TI::TMS::TMS9918, 1, 1, HalfCycles> vdp_;
JustInTimeActor<TI::TMS::TMS9918> vdp_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TI::SN76489 sn76489_;
@ -408,7 +401,6 @@ class ConcreteMachine:
bool joysticks_in_keypad_mode_ = false;
HalfCycles time_since_sn76489_update_;
HalfCycles time_until_interrupt_;
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
int pc_zero_accesses_ = 0;

View File

@ -25,6 +25,8 @@
#include "../Utility/Typer.hpp"
#include "../../Analyser/Static/Acorn/Target.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "Interrupts.hpp"
#include "Keyboard.hpp"
#include "Plus3.hpp"
@ -54,7 +56,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
scsi_bus_(4'000'000),
hard_drive_(scsi_bus_, 0),
scsi_device_(scsi_bus_.add_device()),
video_output_(ram_),
video_(ram_),
sound_generator_(audio_queue_),
speaker_(sound_generator_) {
memset(key_states_, 0, sizeof(key_states_));
@ -230,13 +232,15 @@ template <bool has_scsi_bus> class ConcreteMachine:
if(isReadOperation(operation)) {
*value = ram_[address];
} else {
if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display();
if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) {
video_.flush();
}
ram_[address] = *value;
}
// for the entire frame, RAM is accessible only on odd cycles; in modes below 4
// it's also accessible only outside of the pixel regions
cycles += video_output_.get_cycles_until_next_ram_availability(int(cycles_since_display_update_.as_integral()) + 1);
// For the entire frame, RAM is accessible only on odd cycles; in modes below 4
// it's also accessible only outside of the pixel regions.
cycles += video_.last_valid()->get_cycles_until_next_ram_availability(video_.time_since_flush().template as<int>() + 1);
} else {
switch(address & 0xff0f) {
case 0xfe00:
@ -274,10 +278,8 @@ template <bool has_scsi_bus> class ConcreteMachine:
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
if(!isReadOperation(operation)) {
update_display();
video_output_.write(address, *value);
video_access_range_ = video_output_.get_memory_access_range();
queue_next_display_interrupt();
video_->write(address, *value);
video_access_range_ = video_.last_valid()->get_memory_access_range();
}
break;
case 0xfe04:
@ -425,14 +427,14 @@ template <bool has_scsi_bus> class ConcreteMachine:
(address == 0xf0a8) // 0xf0a8 is from where a service call would normally be
// dispatched; we can check whether it would be call 14
// (i.e. read byte) and, if so, whether the OS was about to
// issue a read byte call to a ROM despite being the tape
// issue a read byte call to a ROM despite the tape
// FS being selected. If so then this is a get byte that
// we should service synthetically. Put the byte into Y
// and set A to zero to report that action was taken, then
// allow the PC read to return an RTS.
)
) {
uint8_t service_call = uint8_t(m6502_.get_value_of_register(CPU::MOS6502::Register::X));
const auto service_call = uint8_t(m6502_.get_value_of_register(CPU::MOS6502::Register::X));
if(address == 0xf0a8) {
if(!ram_[0x247] && service_call == 14) {
tape_.set_delegate(nullptr);
@ -441,7 +443,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
while(!tape_.get_tape()->is_at_end()) {
tape_.run_for_input_pulse();
cycles_left_while_plausibly_in_data--;
--cycles_left_while_plausibly_in_data;
if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false;
if( (tape_.get_interrupt_status() & Interrupt::ReceiveDataFull) &&
(fast_load_is_in_data_ || tape_.get_data_register() == 0x2a)
@ -483,18 +485,14 @@ template <bool has_scsi_bus> class ConcreteMachine:
}
}
cycles_since_display_update_ += Cycles(int(cycles));
if(video_ += Cycles(int(cycles))) {
signal_interrupt(video_.last_valid()->get_interrupts());
}
cycles_since_audio_update_ += Cycles(int(cycles));
if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
tape_.run_for(Cycles(int(cycles)));
cycles_until_display_interrupt_ -= cycles;
if(cycles_until_display_interrupt_ < 0) {
signal_interrupt(next_display_interrupt_);
update_display();
queue_next_display_interrupt();
}
if(typer_) typer_->run_for(Cycles(int(cycles)));
if(plus3_) plus3_->run_for(Cycles(4*int(cycles)));
if(shift_restart_counter_) {
@ -517,25 +515,25 @@ template <bool has_scsi_bus> class ConcreteMachine:
}
forceinline void flush() {
update_display();
video_.flush();
update_audio();
audio_queue_.perform();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_output_.set_scan_target(scan_target);
video_.last_valid()->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_output_.get_scaled_scan_status();
return video_.last_valid()->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
video_output_.set_display_type(display_type);
video_.last_valid()->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const final {
return video_output_.get_display_type();
return video_.last_valid()->get_display_type();
}
Outputs::Speaker::Speaker *get_speaker() final {
@ -693,18 +691,6 @@ template <bool has_scsi_bus> class ConcreteMachine:
}
// MARK: - Work deferral updates.
inline void update_display() {
if(cycles_since_display_update_ > 0) {
video_output_.run_for(cycles_since_display_update_.flush<Cycles>());
}
}
inline void queue_next_display_interrupt() {
VideoOutput::Interrupt next_interrupt = video_output_.get_next_interrupt();
cycles_until_display_interrupt_ = next_interrupt.cycles;
next_display_interrupt_ = next_interrupt.interrupt;
}
inline void update_audio() {
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(SoundGenerator::clock_rate_divider)));
}
@ -754,10 +740,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
Electron::KeyboardMapper keyboard_mapper_;
// Counters related to simultaneous subsystems
Cycles cycles_since_display_update_ = 0;
Cycles cycles_since_audio_update_ = 0;
int cycles_until_display_interrupt_ = 0;
Interrupt next_display_interrupt_ = Interrupt::RealTimeClock;
VideoOutput::Range video_access_range_ = {0, 0xffff};
// Tape
@ -794,7 +777,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
}
// Outputs
VideoOutput video_output_;
JustInTimeActor<VideoOutput, Cycles> video_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
SoundGenerator sound_generator_;

View File

@ -234,7 +234,23 @@ void VideoOutput::output_pixels(int number_of_cycles) {
void VideoOutput::run_for(const Cycles cycles) {
int number_of_cycles = int(cycles.as_integral());
const auto start_position = output_position_;
output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame;
if(
(start_position < real_time_clock_interrupt_1 && output_position_ >= real_time_clock_interrupt_1) ||
(start_position < real_time_clock_interrupt_2 && output_position_ >= real_time_clock_interrupt_2)
) {
interrupts_ = Electron::Interrupt(interrupts_ | Electron::Interrupt::RealTimeClock);
}
if(
(start_position < display_end_interrupt_1 && output_position_ >= display_end_interrupt_1) ||
(start_position < display_end_interrupt_2 && output_position_ >= display_end_interrupt_2)
) {
interrupts_ = Electron::Interrupt(interrupts_ | Electron::Interrupt::DisplayEnd);
}
while(number_of_cycles) {
int draw_action_length = screen_map_[screen_map_pointer_].length;
int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_);
@ -354,36 +370,30 @@ void VideoOutput::setup_base_address() {
// MARK: - Interrupts
VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
VideoOutput::Interrupt interrupt;
Cycles VideoOutput::get_next_sequence_point() {
if(output_position_ < real_time_clock_interrupt_1) {
interrupt.cycles = real_time_clock_interrupt_1 - output_position_;
interrupt.interrupt = RealTimeClock;
return interrupt;
return real_time_clock_interrupt_1 - output_position_;
}
if(output_position_ < display_end_interrupt_1) {
interrupt.cycles = display_end_interrupt_1 - output_position_;
interrupt.interrupt = DisplayEnd;
return interrupt;
return display_end_interrupt_1 - output_position_;
}
if(output_position_ < real_time_clock_interrupt_2) {
interrupt.cycles = real_time_clock_interrupt_2 - output_position_;
interrupt.interrupt = RealTimeClock;
return interrupt;
return real_time_clock_interrupt_2 - output_position_;
}
if(output_position_ < display_end_interrupt_2) {
interrupt.cycles = display_end_interrupt_2 - output_position_;
interrupt.interrupt = DisplayEnd;
return interrupt;
return display_end_interrupt_2 - output_position_;
}
interrupt.cycles = real_time_clock_interrupt_1 + cycles_per_frame - output_position_;
interrupt.interrupt = RealTimeClock;
return interrupt;
return real_time_clock_interrupt_1 + cycles_per_frame - output_position_;
}
Electron::Interrupt VideoOutput::get_interrupts() {
const auto interrupts = interrupts_;
interrupts_ = Electron::Interrupt(0);
return interrupts;
}
// MARK: - RAM timing and access information

View File

@ -54,15 +54,6 @@ class VideoOutput {
*/
void write(int address, uint8_t value);
/*!
Describes an interrupt the video hardware will generate by its identity and scheduling time.
*/
struct Interrupt {
/// The interrupt that will be signalled.
Electron::Interrupt interrupt;
/// The number of cycles until it is signalled.
int cycles;
};
/*!
@returns the next interrupt that should be generated as a result of the video hardware.
The time until signalling returned is the number of cycles after the final one triggered
@ -70,7 +61,12 @@ class VideoOutput {
This result may be mutated by calls to @c write.
*/
Interrupt get_next_interrupt();
Cycles get_next_sequence_point();
/*!
@returns a bit mask of all interrupts that have been triggered since the last call to get_interrupt().
*/
Electron::Interrupt get_interrupts();
/*!
@returns the number of cycles after (final cycle of last run_for batch + @c from_time)
@ -136,6 +132,8 @@ class VideoOutput {
void emplace_pixel_line();
std::size_t screen_map_pointer_ = 0;
int cycles_into_draw_action_ = 0;
Electron::Interrupt interrupts_ = Electron::Interrupt(0);
};
}

View File

@ -419,7 +419,9 @@ class ConcreteMachine:
// but otherwise runs without pause.
const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
const HalfCycles total_length = addition + cycle.length;
vdp_ += total_length;
if(vdp_ += total_length) {
z80_.set_interrupt_line(vdp_->get_interrupt_line(), vdp_.last_sequence_point_overrun());
}
time_since_ay_update_ += total_length;
memory_slots_[0].cycles_since_update += total_length;
memory_slots_[1].cycles_since_update += total_length;
@ -520,7 +522,6 @@ class ConcreteMachine:
case 0x98: case 0x99:
*cycle.value = vdp_->read(address);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xa2:
@ -545,7 +546,6 @@ class ConcreteMachine:
case 0x98: case 0x99:
vdp_->write(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xa0: case 0xa1:
@ -606,12 +606,6 @@ class ConcreteMachine:
if(!tape_player_is_sleeping_)
tape_player_.run_for(int(cycle.length.as_integral()));
if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= total_length;
if(time_until_interrupt_ <= HalfCycles(0)) {
z80_.set_interrupt_line(true, time_until_interrupt_);
}
}
return addition;
}
@ -785,7 +779,6 @@ class ConcreteMachine:
uint8_t unpopulated_[8192];
HalfCycles time_since_ay_update_;
HalfCycles time_until_interrupt_;
uint8_t key_states_[16];
int selected_key_line_ = 0;

View File

@ -214,7 +214,9 @@ class ConcreteMachine:
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
vdp_ += cycle.length;
if(vdp_ += cycle.length) {
z80_.set_interrupt_line(vdp_->get_interrupt_line(), vdp_.last_sequence_point_overrun());
}
time_since_sn76489_update_ += cycle.length;
if(cycle.is_terminal()) {
@ -266,7 +268,6 @@ class ConcreteMachine:
case 0x80: case 0x81:
*cycle.value = vdp_->read(address);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xc0: {
if(memory_control_ & 0x4) {
@ -330,7 +331,6 @@ class ConcreteMachine:
case 0x80: case 0x81: // i.e. ports 0x800xbf.
vdp_->write(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xc1: case 0xc0: // i.e. ports 0xc00xff.
if(has_fm_audio_) {
@ -374,13 +374,6 @@ class ConcreteMachine:
}
}
if(time_until_interrupt_ > 0) {
time_until_interrupt_ -= cycle.length;
if(time_until_interrupt_ <= HalfCycles(0)) {
z80_.set_interrupt_line(true, time_until_interrupt_);
}
}
// The pause button is debounced and takes effect only one line before pixels
// begin; time_until_debounce_ keeps track of the time until then.
time_until_debounce_ -= cycle.length;
@ -505,7 +498,6 @@ class ConcreteMachine:
bool reset_is_pressed_ = false, pause_is_pressed_ = false;
HalfCycles time_since_sn76489_update_;
HalfCycles time_until_interrupt_;
HalfCycles time_until_debounce_;
uint8_t ram_[8*1024];

View File

@ -34,6 +34,8 @@
#include "../../Analyser/Static/Oric/Target.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include <cstdint>
#include <memory>
#include <vector>
@ -217,7 +219,6 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Storage::Tape::BinaryTapePlayer::Delegate,
public DiskController::Delegate,
public ClockingHint::Observer,
public Activity::Source,
public Machine,
public Keyboard::SpecialKeyHandler {
@ -225,7 +226,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
public:
ConcreteMachine(const Analyser::Static::Oric::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
video_output_(ram_),
video_(ram_),
ay8910_(GI::AY38910::Personality::AY38910, audio_queue_),
speaker_(ay8910_),
via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_),
@ -247,10 +248,6 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
ram_[c] |= 0x40;
}
if constexpr (disk_interface == DiskInterface::Pravetz) {
diskii_.set_clocking_hint_observer(this);
}
const std::string machine_name = "Oric";
std::vector<ROMMachine::ROM> rom_names = { {machine_name, "the Oric colour ROM", "colour.rom", 128, 0xd50fca65} };
switch(target.rom) {
@ -292,7 +289,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
}
}
video_output_.set_colour_rom(*roms[0]);
video_->set_colour_rom(*roms[0]);
rom_ = std::move(*roms[1]);
switch(disk_interface) {
@ -313,7 +310,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
pravetz_rom_ = std::move(*roms[2]);
pravetz_rom_.resize(512);
diskii_.set_state_machine(*roms[diskii_state_machine_index]);
diskii_->set_state_machine(*roms[diskii_state_machine_index]);
} break;
}
@ -402,7 +399,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
case DiskInterface::BD500: inserted |= insert_disks(media, bd500_, 4); break;
case DiskInterface::Jasmin: inserted |= insert_disks(media, jasmin_, 4); break;
case DiskInterface::Microdisc: inserted |= insert_disks(media, microdisc_, 4); break;
case DiskInterface::Pravetz: inserted |= insert_disks(media, diskii_, 2); break;
case DiskInterface::Pravetz: inserted |= insert_disks(media, *diskii_.last_valid(), 2); break;
default: break;
}
}
@ -474,9 +471,8 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
}
}
} else {
flush_diskii();
const int disk_value = diskii_.read_address(address);
if(!isWriteOperation(operation) && disk_value != diskii_.DidNotLoad) *value = uint8_t(disk_value);
const int disk_value = diskii_->read_address(address);
if(!isWriteOperation(operation) && disk_value != Apple::DiskII::DidNotLoad) *value = uint8_t(disk_value);
}
break;
}
@ -485,7 +481,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
if(!isWriteOperation(operation))
*value = ram_[address];
else {
if(address >= 0x9800 && address <= 0xc000) update_video();
if(address >= 0x9800 && address <= 0xc000) video_.flush();
ram_[address] = *value;
}
}
@ -508,7 +504,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
bd500_.run_for(Cycles(9)); // i.e. effective clock rate of 9Mhz.
break;
case DiskInterface::Jasmin:
jasmin_.run_for(Cycles(8));; // i.e. effective clock rate of 8Mhz.
jasmin_.run_for(Cycles(8)); // i.e. effective clock rate of 8Mhz.
// Jasmin autostart hack: wait for a period, then trigger a reset, having forced
// the Jasmin to page its ROM in first. I assume the latter being what the Jasmin's
@ -521,43 +517,41 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
}
break;
case DiskInterface::Microdisc:
microdisc_.run_for(Cycles(8));; // i.e. effective clock rate of 8Mhz.
microdisc_.run_for(Cycles(8)); // i.e. effective clock rate of 8Mhz.
break;
case DiskInterface::Pravetz:
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
diskii_.set_data_input(*value);
diskii_.run_for(Cycles(2));; // i.e. effective clock rate of 2Mhz.
} else {
cycles_since_diskii_update_ += Cycles(2);
if(diskii_.clocking_preference() == ClockingHint::Preference::RealTime) {
diskii_->set_data_input(*value);
}
diskii_ += Cycles(2); // i.e. effective clock rate of 2Mhz.
break;
}
cycles_since_video_update_++;
video_ += Cycles(1);
return Cycles(1);
}
forceinline void flush() {
update_video();
video_.flush();
via_.flush();
flush_diskii();
diskii_.flush();
}
// to satisfy CRTMachine::Machine
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_output_.set_scan_target(scan_target);
video_.last_valid()->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_output_.get_scaled_scan_status();
return video_.last_valid()->get_scaled_scan_status();
}
void set_display_type(Outputs::Display::DisplayType display_type) final {
video_output_.set_display_type(display_type);
video_.last_valid()->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const final {
return video_output_.get_display_type();
return video_.last_valid()->get_display_type();
}
Outputs::Speaker::Speaker *get_speaker() final {
@ -644,15 +638,11 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
microdisc_.set_activity_observer(observer);
break;
case DiskInterface::Pravetz:
diskii_.set_activity_observer(observer);
diskii_->set_activity_observer(observer);
break;
}
}
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final {
diskii_clocking_preference_ = diskii_.preferred_clocking();
}
private:
const uint16_t basic_invisible_ram_top_ = 0xffff;
const uint16_t basic_visible_ram_top_ = 0xbfff;
@ -662,17 +652,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
// RAM and ROM
std::vector<uint8_t> rom_, disk_rom_;
uint8_t ram_[65536];
Cycles cycles_since_video_update_;
inline void update_video() {
video_output_.run_for(cycles_since_video_update_.flush<Cycles>());
}
// ROM bookkeeping
uint16_t tape_get_byte_address_ = 0, tape_speed_address_ = 0;
int keyboard_read_count_ = 0;
// Outputs
VideoOutput video_output_;
JustInTimeActor<VideoOutput, Cycles> video_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910<false> ay8910_;
@ -700,14 +686,9 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
BD500 bd500_;
// the Pravetz/Disk II, if in use.
Apple::DiskII diskii_;
Cycles cycles_since_diskii_update_;
void flush_diskii() {
diskii_.run_for(cycles_since_diskii_update_.flush<Cycles>());
}
JustInTimeActor<Apple::DiskII, Cycles> diskii_;
std::vector<uint8_t> pravetz_rom_;
std::size_t pravetz_rom_base_pointer_ = 0;
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
// Overlay RAM
uint16_t ram_top_ = basic_visible_ram_top_;

View File

@ -72,7 +72,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#define SHIFT(...) {KeyShift, __VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence}
#define SYMSHIFT(...) {KeySymbolShift, __VA_ARGS__, MachineTypes::MappedKeyboardMachine::KeyEndSequence}
#define X {MachineTypes::MappedKeyboardMachine::KeyNotMapped}
constexpr KeySequence spectrum_key_sequences[] = {
static constexpr KeySequence spectrum_key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,
/* EOT */ X, /* ENQ */ X,
@ -137,7 +137,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
/* z */ KEYS(KeyZ),
};
constexpr KeySequence zx81_key_sequences[] = {
static constexpr KeySequence zx81_key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,
/* EOT */ X, /* ENQ */ X,
@ -203,7 +203,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
/* | */ X, /* } */ X,
};
static KeySequence zx80_key_sequences[] = {
static constexpr KeySequence zx80_key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,
/* EOT */ X, /* ENQ */ X,

View File

@ -53,14 +53,14 @@ template <VideoTiming timing> class Video {
// Number of lines comprising a whole frame. Will be 311 or 312.
int lines_per_frame;
// Number of cycles after first pixel fetch at which interrupt is first signalled.
int interrupt_time;
// Number of cycles before first pixel fetch that contention starts to be applied.
int contention_leadin;
// Period in a line for which contention is applied.
int contention_duration;
// Number of cycles after first pixel fetch at which interrupt is first signalled.
int interrupt_time;
// Contention to apply, in half-cycles, as a function of number of half cycles since
// contention began.
int delays[16];
@ -88,11 +88,15 @@ template <VideoTiming timing> class Video {
.cycles_per_line = 228 * 2,
.lines_per_frame = 311,
.interrupt_time = 56545 * 2,
.contention_leadin = 2 * 2, // TODO: is this 2? Or 4? Or... ?
// i.e. video fetching begins five cycles after the start of the
// contended memory pattern below; that should put a clear two
// cycles between a Z80 access and the first video fetch.
.contention_leadin = 5 * 2,
.contention_duration = 129 * 2,
// i.e. interrupt is first signalled 14368 cycles before the first video fetch.
.interrupt_time = (228*311 - 14368) * 2,
.delays = {
2, 1,
0, 0,
@ -311,7 +315,9 @@ template <VideoTiming timing> class Video {
}
const int time_into_line = delay_time % timings.cycles_per_line;
if(time_into_line >= timings.contention_duration) return 0;
if(time_into_line >= timings.contention_duration) {
return 0;
}
return timings.delays[time_into_line & 15];
}

View File

@ -39,6 +39,8 @@
#include "../../../ClockReceiver/JustInTime.hpp"
#include "../../../Processors/Z80/State/State.hpp"
#include "../Keyboard/Keyboard.hpp"
#include <array>
@ -85,7 +87,6 @@ template<Model model> class ConcreteMachine:
memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size()));
// Register for sleeping notifications.
fdc_->set_clocking_hint_observer(this);
tape_player_.set_clocking_hint_observer(this);
// Set up initial memory map.
@ -185,37 +186,42 @@ template<Model model> class ConcreteMachine:
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
using PartialMachineCycle = CPU::Z80::PartialMachineCycle;
HalfCycles delay(0);
const uint16_t address = cycle.address ? *cycle.address : 0x0000;
// Apply contention if necessary.
if(
is_contended_[address >> 14] &&
cycle.operation >= PartialMachineCycle::ReadOpcodeStart &&
cycle.operation <= PartialMachineCycle::WriteStart) {
// Assumption here: the trigger for the ULA inserting a delay is the falling edge
// of MREQ, which is always half a cycle into a read or write.
//
// TODO: somehow provide that information in the PartialMachineCycle?
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
advance(cycle.length + delay);
return delay;
}
// For all other machine cycles, model the action as happening at the end of the machine cycle;
// that means advancing time now.
advance(cycle.length);
switch(cycle.operation) {
default: break;
case PartialMachineCycle::ReadOpcodeStart:
case PartialMachineCycle::ReadStart:
case PartialMachineCycle::WriteStart:
// Apply contention if necessary.
//
// Assumption here: the trigger for the ULA inserting a delay is the falling edge
// of MREQ, which is always half a cycle into a read or write.
//
// TODO: somehow provide that information in the PartialMachineCycle?
if(is_contended_[address >> 14]) {
delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
}
break;
case PartialMachineCycle::ReadOpcode:
// Fast loading: ROM version.
//
// The below patches over the 'LD-BYTES' routine from the 48kb ROM.
if(use_fast_tape_hack_ && address == 0x0556 && read_pointers_[0] == &rom_[0xc000]) {
if(perform_rom_ld_bytes()) {
// Stop pressing enter, if neccessry.
if(duration_to_press_enter_ > Cycles(0)) {
duration_to_press_enter_ = Cycles(0);
keyboard_.set_key_state(ZX::Keyboard::KeyEnter, false);
}
// The below patches over part of the 'LD-BYTES' routine from the 48kb ROM.
if(use_fast_tape_hack_ && address == 0x056b && read_pointers_[0] == &rom_[0xc000]) {
// Stop pressing enter, if neccessry.
if(duration_to_press_enter_ > Cycles(0)) {
duration_to_press_enter_ = Cycles(0);
keyboard_.set_key_state(ZX::Keyboard::KeyEnter, false);
}
if(perform_rom_ld_bytes_56b()) {
*cycle.value = 0xc9; // i.e. RET.
break;
}
@ -225,7 +231,7 @@ template<Model model> class ConcreteMachine:
*cycle.value = read_pointers_[address >> 14][address];
if(is_contended_[address >> 14]) {
video_.last_valid()->set_last_contended_area_access(*cycle.value);
video_->set_last_contended_area_access(*cycle.value);
}
break;
@ -239,7 +245,7 @@ template<Model model> class ConcreteMachine:
// Fill the floating bus buffer if this write is within the contended area.
if(is_contended_[address >> 14]) {
video_.last_valid()->set_last_contended_area_access(*cycle.value);
video_->set_last_contended_area_access(*cycle.value);
}
break;
@ -357,8 +363,7 @@ template<Model model> class ConcreteMachine:
break;
}
advance(cycle.length + delay);
return delay;
return HalfCycles(0);
}
private:
@ -367,7 +372,7 @@ template<Model model> class ConcreteMachine:
video_ += duration;
if(video_.did_flush()) {
z80_.set_interrupt_line(video_.last_valid()->get_interrupt_line());
z80_.set_interrupt_line(video_.last_valid()->get_interrupt_line(), video_.last_sequence_point_overrun());
}
if(!tape_player_is_sleeping_) tape_player_.run_for(duration.as_integral());
@ -384,7 +389,7 @@ template<Model model> class ConcreteMachine:
}
if constexpr (model == Model::Plus3) {
if(!fdc_is_sleeping_) fdc_ += Cycles(duration.as_integral());
fdc_ += Cycles(duration.as_integral());
}
if(typer_) typer_->run_for(duration);
@ -449,7 +454,6 @@ template<Model model> class ConcreteMachine:
// MARK: - ClockingHint::Observer.
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) override {
fdc_is_sleeping_ = fdc_.last_valid()->preferred_clocking() == ClockingHint::Preference::None;
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
}
@ -625,31 +629,42 @@ template<Model model> class ConcreteMachine:
}
// Reimplements the 'LD-BYTES' routine, as documented at
// https://skoolkid.github.io/rom/asm/0556.html i.e.
// https://skoolkid.github.io/rom/asm/0556.html but picking
// up from address 56b i.e.
//
// In:
// A: 0x00 or 0xff for block type;
// F: carry set if loading, clear if verifying;
// A': 0x00 or 0xff for block type;
// F': carry set if loading, clear if verifying;
// DE: block length;
// IX: start address.
//
// Out:
// F: carry set for success, clear for error.
bool perform_rom_ld_bytes() {
//
// And, empirically:
// IX: one beyond final address written;
// DE: 0;
// L: parity byte;
// H: 0 for no error, 0xff for error;
// A: same as H.
// BC: ???
bool perform_rom_ld_bytes_56b() {
using Parser = Storage::Tape::ZXSpectrum::Parser;
Parser parser(Parser::MachineType::ZXSpectrum);
using Register = CPU::Z80::Register;
uint8_t flags = uint8_t(z80_.get_value_of_register(Register::Flags));
uint8_t flags = uint8_t(z80_.get_value_of_register(Register::FlagsDash));
if(!(flags & 1)) return false;
const uint8_t block_type = uint8_t(z80_.get_value_of_register(Register::A));
const uint8_t block_type = uint8_t(z80_.get_value_of_register(Register::ADash));
const auto block = parser.find_block(tape_player_.get_tape());
if(!block || block_type != (*block).type) return false;
uint16_t length = z80_.get_value_of_register(Register::DE);
uint16_t target = z80_.get_value_of_register(Register::IX);
flags = 0x93;
uint8_t parity = 0x00;
while(length--) {
auto next = parser.get_byte(tape_player_.get_tape());
if(!next) {
@ -658,16 +673,30 @@ template<Model model> class ConcreteMachine:
}
write_pointers_[target >> 14][target] = *next;
parity ^= *next;
++target;
}
auto stored_parity = parser.get_byte(tape_player_.get_tape());
if(!stored_parity) {
flags &= ~1;
} else {
z80_.set_value_of_register(Register::L, *stored_parity);
}
z80_.set_value_of_register(Register::Flags, flags);
z80_.set_value_of_register(Register::DE, length);
z80_.set_value_of_register(Register::IX, target);
const uint8_t h = (flags & 1) ? 0x00 : 0xff;
z80_.set_value_of_register(Register::H, h);
z80_.set_value_of_register(Register::A, h);
return true;
}
// MARK: - Disc.
JustInTimeActor<Amstrad::FDC, 1, 1, Cycles> fdc_;
bool fdc_is_sleeping_ = false;
JustInTimeActor<Amstrad::FDC, Cycles> fdc_;
// MARK: - Automatic startup.
Cycles duration_to_press_enter_;

View File

@ -34,10 +34,10 @@
vdp.write(1, 0x8a);
// Get time until interrupt.
auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1;
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
// Check that an interrupt is now scheduled.
NSAssert(time_until_interrupt != -2, @"No interrupt scheduled");
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt scheduled");
NSAssert(time_until_interrupt > 0, @"Interrupt is scheduled in the past");
// Check interrupt flag isn't set prior to the reported time.
@ -53,7 +53,7 @@
NSAssert(!vdp.get_interrupt_line(), @"Interrupt wasn't reset by status read");
// Check interrupt flag isn't set prior to the reported time.
time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1;
time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
vdp.run_for(HalfCycles(time_until_interrupt));
NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [2]");
@ -80,10 +80,10 @@
// Clear the pending interrupt and ask about the next one (i.e. the first one).
vdp.read(1);
auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1;
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
// Check that an interrupt is now scheduled.
NSAssert(time_until_interrupt != -2, @"No interrupt scheduled");
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt scheduled");
NSAssert(time_until_interrupt > 0, @"Interrupt is scheduled in the past");
// Check interrupt flag isn't set prior to the reported time.
@ -114,20 +114,24 @@
// Now run through an entire frame...
int half_cycles = 262*228*2;
auto last_time_until_interrupt = vdp.get_time_until_interrupt().as_integral();
auto last_time_until_interrupt = vdp.get_next_sequence_point().as_integral();
while(half_cycles--) {
// Validate that an interrupt happened if one was expected, and clear anything that's present.
NSAssert(vdp.get_interrupt_line() == (last_time_until_interrupt == 0), @"Unexpected interrupt state change; expected %d but got %d; position %d %d @ %d", (last_time_until_interrupt == 0), vdp.get_interrupt_line(), c, with_eof, half_cycles);
vdp.read(1);
NSAssert(vdp.get_interrupt_line() == (last_time_until_interrupt == HalfCycles::max().as_integral()), @"Unexpected interrupt state change; expected %d but got %d; position %d %d @ %d", (last_time_until_interrupt == 0), vdp.get_interrupt_line(), c, with_eof, half_cycles);
if(vdp.get_interrupt_line()) {
vdp.read(1);
last_time_until_interrupt = 0;
}
vdp.run_for(HalfCycles(1));
// Get the time until interrupt.
auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral();
NSAssert(time_until_interrupt != -1, @"No interrupt scheduled; position %d %d @ %d", c, with_eof, half_cycles);
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral();
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() || vdp.get_interrupt_line(), @"No interrupt scheduled; position %d %d @ %d", c, with_eof, half_cycles);
NSAssert(time_until_interrupt >= 0, @"Interrupt is scheduled in the past; position %d %d @ %d", c, with_eof, half_cycles);
if(last_time_until_interrupt) {
if(last_time_until_interrupt > 1) {
NSAssert(
time_until_interrupt == (last_time_until_interrupt - 1),
@"Discontinuity found in interrupt prediction; from %@ to %@; position %d %d @ %d",

View File

@ -212,6 +212,7 @@ std::string Reflection::Struct::description() const {
for(const auto &key: all_keys()) {
if(!is_first) stream << ", ";
is_first = false;
stream << key << ": ";
const auto count = count_of(key);

View File

@ -12,6 +12,7 @@
#include <cstdlib>
#include <cmath>
#define LOG_PREFIX "[UEF] "
#include "../../../Outputs/Log.hpp"
// MARK: - ZLib extensions
@ -155,7 +156,7 @@ void UEF::get_next_pulses() {
break;
default:
LOG("!!! Skipping " << std::hex << next_chunk.id << std::endl);
LOG("Skipping chunk of type " << PADHEX(4) << next_chunk.id);
break;
}